Skip to content

Commit

Permalink
Merge branch 'release/2.4.3'
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed Oct 6, 2022
2 parents c5432f8 + 4137bef commit 88608cc
Show file tree
Hide file tree
Showing 29 changed files with 441 additions and 123 deletions.
38 changes: 21 additions & 17 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptofs</artifactId>
<version>2.4.2</version>
<version>2.4.3</version>
<name>Cryptomator Crypto Filesystem</name>
<description>This library provides the Java filesystem provider used by Cryptomator.</description>
<url>https://github.com/cryptomator/cryptofs</url>
Expand All @@ -18,21 +18,21 @@
<maven.compiler.release>17</maven.compiler.release>

<!-- dependencies -->
<cryptolib.version>2.0.3</cryptolib.version>
<jwt.version>3.19.1</jwt.version>
<dagger.version>2.41</dagger.version>
<cryptolib.version>2.1.0-rc1</cryptolib.version>
<jwt.version>4.0.0</jwt.version>
<dagger.version>2.44</dagger.version>
<guava.version>31.1-jre</guava.version>
<slf4j.version>1.7.36</slf4j.version>
<slf4j.version>2.0.3</slf4j.version>

<!-- test dependencies -->
<junit.jupiter.version>5.8.2</junit.jupiter.version>
<mockito.version>4.4.0</mockito.version>
<junit.jupiter.version>5.9.1</junit.jupiter.version>
<mockito.version>4.8.0</mockito.version>
<hamcrest.version>2.2</hamcrest.version>

<!-- build plugin dependencies -->
<dependency-check.version>7.0.3</dependency-check.version>
<jacoco.version>0.8.7</jacoco.version>
<nexus-staging.version>1.6.12</nexus-staging.version>
<dependency-check.version>7.2.1</dependency-check.version>
<jacoco.version>0.8.8</jacoco.version>
<nexus-staging.version>1.6.13</nexus-staging.version>
</properties>

<licenses>
Expand Down Expand Up @@ -71,6 +71,12 @@
<artifactId>java-jwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<!-- fix for CVE-2022-42003, CVE-2022-42004 in java-jwt - can be removed when we update java-jwt: -->
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.0-rc1</version>
</dependency>
<dependency>
<groupId>com.google.dagger</groupId>
<artifactId>dagger</artifactId>
Expand All @@ -96,7 +102,7 @@
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
Expand Down Expand Up @@ -125,7 +131,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<version>3.10.1</version>
<configuration>
<showWarnings>true</showWarnings>
<annotationProcessorPaths>
Expand All @@ -140,19 +146,17 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M7</version>
<configuration>
<systemPropertyVariables>
<org.slf4j.simpleLogger.defaultLogLevel>ERROR</org.slf4j.simpleLogger.defaultLogLevel>
</systemPropertyVariables>
<!-- JPMS blocks Mockito from deep reflection unless opening the module... -->
<useModulePath>false</useModulePath>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<version>3.3.0</version>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
Expand All @@ -168,7 +172,7 @@
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.0</version>
<version>3.4.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ private FileChannel newFileChannelFromFile(CryptoPath cleartextFilePath, Effecti
Files.createDirectories(ciphertextPath.getRawPath()); // suppresses FileAlreadyExists
}

FileChannel ch = openCryptoFiles.getOrCreate(ciphertextFilePath).newFileChannel(options); // might throw FileAlreadyExists
FileChannel ch = openCryptoFiles.getOrCreate(ciphertextFilePath).newFileChannel(options, attrs); // might throw FileAlreadyExists
try {
if (options.writable()) {
ciphertextPath.persistLongFileName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ public class CryptoFileSystemProperties extends AbstractMap<String, Object> {

static final int DEFAULT_MAX_CLEARTEXT_NAME_LENGTH = LongFileNameProvider.MAX_FILENAME_BUFFER_SIZE;

/**
* Shortening threshold for ciphertext filenames.
*
* @since 2.5.0
*/
public static final String PROPERTY_SHORTENING_THRESHOLD = "shorteningThreshold";

static final int DEFAULT_SHORTENING_THRESHOLD = 220;

/**
* Key identifying the key loader used during initialization.
*
Expand Down Expand Up @@ -105,6 +114,7 @@ private CryptoFileSystemProperties(Builder builder) {
Map.entry(PROPERTY_VAULTCONFIG_FILENAME, builder.vaultConfigFilename), //
Map.entry(PROPERTY_MASTERKEY_FILENAME, builder.masterkeyFilename), //
Map.entry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, builder.maxCleartextNameLength), //
Map.entry(PROPERTY_SHORTENING_THRESHOLD, builder.shorteningThreshold), //
Map.entry(PROPERTY_CIPHER_COMBO, builder.cipherCombo) //
);
}
Expand Down Expand Up @@ -139,6 +149,10 @@ int maxCleartextNameLength() {
return (int) get(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH);
}

int shorteningThreshold() {
return (int) get(PROPERTY_SHORTENING_THRESHOLD);
}

@Override
public Set<Entry<String, Object>> entrySet() {
return entries;
Expand Down Expand Up @@ -193,6 +207,7 @@ public static class Builder {
private String vaultConfigFilename = DEFAULT_VAULTCONFIG_FILENAME;
private String masterkeyFilename = DEFAULT_MASTERKEY_FILENAME;
private int maxCleartextNameLength = DEFAULT_MAX_CLEARTEXT_NAME_LENGTH;
private int shorteningThreshold = DEFAULT_SHORTENING_THRESHOLD;

private Builder() {
}
Expand All @@ -203,6 +218,7 @@ private Builder(Map<String, ?> properties) {
checkedSet(String.class, PROPERTY_MASTERKEY_FILENAME, properties, this::withMasterkeyFilename);
checkedSet(Set.class, PROPERTY_FILESYSTEM_FLAGS, properties, this::withFlags);
checkedSet(Integer.class, PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, properties, this::withMaxCleartextNameLength);
checkedSet(Integer.class, PROPERTY_SHORTENING_THRESHOLD, properties, this::withShorteningThreshold);
checkedSet(CryptorProvider.Scheme.class, PROPERTY_CIPHER_COMBO, properties, this::withCipherCombo);
}

Expand Down Expand Up @@ -231,6 +247,18 @@ public Builder withMaxCleartextNameLength(int maxCleartextNameLength) {
return this;
}

/**
* Sets the shortening threshold used during vault initialization.
*
* @param shorteningThreshold The maximum ciphertext filename length not to be shortened
* @return this
* @since 2.5.0
*/
public Builder withShorteningThreshold(int shorteningThreshold) {
this.shorteningThreshold = shorteningThreshold;
return this;
}


/**
* Sets the cipher combo used during vault initialization.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope
throw new NotDirectoryException(pathToVault.toString());
}
byte[] rawKey = new byte[0];
var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).shorteningThreshold(Constants.DEFAULT_SHORTENING_THRESHOLD).build();
var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).shorteningThreshold(properties.shorteningThreshold()).build();
try (Masterkey key = properties.keyLoader().loadKey(keyId);
Cryptor cryptor = CryptorProvider.forScheme(config.getCipherCombo()).provide(key, strongSecureRandom())) {
rawKey = key.getEncoded();
Expand Down
12 changes: 7 additions & 5 deletions src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,13 @@ public String inflate(Path c9sPath) throws IOException {
public DeflatedFileName deflate(Path c9rPath) {
String longFileName = c9rPath.getFileName().toString();
byte[] longFileNameBytes = longFileName.getBytes(UTF_8);
byte[] hash = MessageDigestSupplier.SHA1.get().digest(longFileNameBytes);
String shortName = BASE64.encode(hash) + DEFLATED_FILE_SUFFIX;
Path c9sPath = c9rPath.resolveSibling(shortName);
longNames.put(c9sPath, longFileName);
return new DeflatedFileName(c9sPath, longFileName, readonlyFlag);
try (var sha1 = MessageDigestSupplier.SHA1.instance()) {
byte[] hash = sha1.get().digest(longFileNameBytes);
String shortName = BASE64.encode(hash) + DEFLATED_FILE_SUFFIX;
Path c9sPath = c9rPath.resolveSibling(shortName);
longNames.put(c9sPath, longFileName);
return new DeflatedFileName(c9sPath, longFileName, readonlyFlag);
}
}

public static class DeflatedFileName {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ sealed class CryptoBasicFileAttributes implements BasicFileAttributes
public CryptoBasicFileAttributes(BasicFileAttributes delegate, CiphertextFileType ciphertextFileType, Path ciphertextPath, Cryptor cryptor, Optional<OpenCryptoFile> openCryptoFile) {
this.ciphertextFileType = ciphertextFileType;
this.size = switch (ciphertextFileType) {
case SYMLINK, DIRECTORY -> delegate.size();
case FILE -> getPlaintextFileSize(ciphertextPath, delegate.size(), openCryptoFile, cryptor);
case DIRECTORY -> delegate.size();
case SYMLINK, FILE -> getPlaintextFileSize(ciphertextPath, delegate.size(), openCryptoFile, cryptor);
};
this.lastModifiedTime = openCryptoFile.map(OpenCryptoFile::getLastModifiedTime).orElseGet(delegate::lastModifiedTime);
this.lastAccessTime = openCryptoFile.map(openFile -> FileTime.from(Instant.now())).orElseGet(delegate::lastAccessTime);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ private Constants() {
public static final String CONTENTS_FILE_NAME = "contents.c9r";
public static final String INFLATED_FILE_NAME = "name.c9s";

public static final int DEFAULT_SHORTENING_THRESHOLD = 220;
public static final int MAX_SYMLINK_LENGTH = 32767; // max path length on NTFS and FAT32: 32k-1
public static final int MAX_DIR_FILE_LENGTH = 36; // UUIDv4: hex-encoded 16 byte int + 4 hyphens = 36 ASCII chars
public static final int MIN_CIPHER_NAME_LENGTH = 26; //rounded up base64url encoded (16 bytes IV + 0 bytes empty string) + file suffix = 26 ASCII chars
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.Optional;
Expand Down Expand Up @@ -65,7 +66,7 @@ public OpenCryptoFile(FileCloseListener listener, ChunkCache chunkCache, Cryptor
* @return A new file channel. Ideally used in a try-with-resource statement. If the channel is not properly closed, this OpenCryptoFile will stay open indefinite.
* @throws IOException
*/
public synchronized FileChannel newFileChannel(EffectiveOpenOptions options) throws IOException {
public synchronized FileChannel newFileChannel(EffectiveOpenOptions options, FileAttribute<?>... attrs) throws IOException {
Path path = currentFilePath.get();

if (options.truncateExisting()) {
Expand All @@ -75,7 +76,7 @@ public synchronized FileChannel newFileChannel(EffectiveOpenOptions options) thr
FileChannel ciphertextFileChannel = null;
CleartextFileChannel cleartextFileChannel = null;
try {
ciphertextFileChannel = path.getFileSystem().provider().newFileChannel(path, options.createOpenOptionsForEncryptedFile());
ciphertextFileChannel = path.getFileSystem().provider().newFileChannel(path, options.createOpenOptionsForEncryptedFile(), attrs);
final FileHeader header;
final boolean isNewHeader;
if (ciphertextFileChannel.size() == 0l) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,22 @@ public void check(Path pathToVault, VaultConfig config, Masterkey masterkey, Cry
boolean foundDir = dirVisitor.secondLevelDirs.remove(expectedDir);
if (foundDir) {
iter.remove();
resultCollector.accept(new HealthyDir(dirId, dirIdFile, expectedDir));
if (Files.exists(expectedDir.resolve(Constants.DIR_ID_FILE))) {
resultCollector.accept(new HealthyDir(dirId, dirIdFile, expectedDir));
} else {
resultCollector.accept(new MissingDirIdBackup(dirId, expectedDir));
}
}
}

// remaining dirIds (i.e. missing dirs):
dirVisitor.dirIds.forEach((dirId, dirIdFile) -> {
resultCollector.accept(new MissingDirectory(dirId, dirIdFile));
resultCollector.accept(new MissingContentDir(dirId, dirIdFile));
});

// remaining folders (i.e. missing dir.c9r files):
dirVisitor.secondLevelDirs.forEach(dir -> {
resultCollector.accept(new OrphanDir(dir));
resultCollector.accept(new OrphanContentDir(dir));
});
}

Expand All @@ -83,6 +87,8 @@ static class DirVisitor extends SimpleFileVisitor<Path> {
private final Consumer<DiagnosticResult> resultCollector;
public final Map<String, Path> dirIds = new HashMap<>(); // contents of all found dir.c9r files
public final Set<Path> secondLevelDirs = new HashSet<>(); // all d/2/30 dirs
public final Set<Path> c9rDirsWithDirId = new HashSet<>(); // all d/2/30/abcd=.c9r dirs containing a dirId file


public DirVisitor(Path dataDirPath, Consumer<DiagnosticResult> resultCollector) {
this.dataDirPath = dataDirPath;
Expand All @@ -93,19 +99,28 @@ public DirVisitor(Path dataDirPath, Consumer<DiagnosticResult> resultCollector)
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (Constants.DIR_FILE_NAME.equals(file.getFileName().toString())) {
c9rDirsWithDirId.add(file.getParent());
return visitDirFile(file, attrs);
}
return FileVisitResult.CONTINUE;
}

private FileVisitResult visitDirFile(Path file, BasicFileAttributes attrs) throws IOException {
assert Constants.DIR_FILE_NAME.equals(file.getFileName().toString());
var parentDirName = file.getParent().getFileName().toString();

if (!(parentDirName.endsWith(Constants.CRYPTOMATOR_FILE_SUFFIX) || parentDirName.endsWith(Constants.DEFLATED_FILE_SUFFIX))) {
LOG.warn("Encountered loose dir.c9r file.");
resultCollector.accept(new LooseDirIdFile(file));
return FileVisitResult.CONTINUE;
}

if (attrs.size() > Constants.MAX_DIR_FILE_LENGTH) {
LOG.warn("Encountered dir.c9r file of size {}", attrs.size());
resultCollector.accept(new ObeseDirFile(file, attrs.size()));
resultCollector.accept(new ObeseDirIdFile(file, attrs.size()));
} else if (attrs.size() == 0) {
LOG.warn("Empty dir.c9r file at {}.", file);
resultCollector.accept(new EmptyDirFile(file));
resultCollector.accept(new EmptyDirIdFile(file));
} else {
byte[] bytes = Files.readAllBytes(file);
String dirId = new String(bytes, StandardCharsets.UTF_8);
Expand All @@ -115,6 +130,7 @@ private FileVisitResult visitDirFile(Path file, BasicFileAttributes attrs) throw
resultCollector.accept(new DirIdCollision(dirId, file, otherFile));
} else {
dirIds.put(dirId, file);
c9rDirsWithDirId.add(file);
}
}
return FileVisitResult.SKIP_SIBLINGS;
Expand All @@ -128,6 +144,16 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
}
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e) {
var dirName = dir.getFileName().toString();
if (dirName.endsWith(Constants.CRYPTOMATOR_FILE_SUFFIX) && !c9rDirsWithDirId.contains(dir)) {
LOG.warn("Missing dirId file for c9r directory {}.", dir);
resultCollector.accept(new MissingDirIdFile(dir));
}
return FileVisitResult.CONTINUE;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
*
* @see org.cryptomator.cryptofs.common.Constants#ROOT_DIR_ID
*/
public class EmptyDirFile implements DiagnosticResult {
public class EmptyDirIdFile implements DiagnosticResult {

final Path dirFile;
final Path dirIdFile;

public EmptyDirFile(Path dirFile) {this.dirFile = dirFile;}
public EmptyDirIdFile(Path dirIdFile) {this.dirIdFile = dirIdFile;}

@Override
public Severity getSeverity() {
Expand All @@ -29,7 +29,7 @@ public Severity getSeverity() {

@Override
public String toString() {
return String.format("File %s is empty, expected content", dirFile);
return String.format("File %s is empty, expected content", dirIdFile);
}

/*
Expand All @@ -44,6 +44,6 @@ public void fix(Path pathToVault, VaultConfig config, Masterkey masterkey, Crypt

@Override
public Map<String, String> details() {
return Map.of(DIR_ID_FILE, dirFile.toString());
return Map.of(DIR_ID_FILE, dirIdFile.toString());
}
}
Loading

0 comments on commit 88608cc

Please sign in to comment.