Skip to content

Commit

Permalink
Merge branch 'release/2.0.0' into release/2.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed May 3, 2021
2 parents fe0f776 + f5e9748 commit f43c2df
Show file tree
Hide file tree
Showing 31 changed files with 281 additions and 267 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<!-- dependencies -->
<cryptolib.version>2.0.0-beta6</cryptolib.version>
<cryptolib.version>2.0.0-beta7</cryptolib.version>
<jwt.version>3.12.0</jwt.version>
<dagger.version>2.31</dagger.version>
<guava.version>30.1-jre</guava.version>
Expand Down
29 changes: 13 additions & 16 deletions src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ boolean isHidden(CryptoPath cleartextPath) throws IOException {

void createDirectory(CryptoPath cleartextDir, FileAttribute<?>... attrs) throws IOException {
readonlyFlag.assertWritable();
assertCleartextNameLengthAllowed(cleartextDir);
CryptoPath cleartextParentDir = cleartextDir.getParent();
if (cleartextParentDir == null) {
return;
Expand All @@ -292,7 +293,6 @@ void createDirectory(CryptoPath cleartextDir, FileAttribute<?>... attrs) throws
cryptoPathMapper.assertNonExisting(cleartextDir);
CiphertextFilePath ciphertextPath = cryptoPathMapper.getCiphertextFilePath(cleartextDir);
Path ciphertextDirFile = ciphertextPath.getDirFilePath();
assertCiphertextPathLengthMeetsLimitations(ciphertextDirFile);
CiphertextDirectory ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir);
// atomically check for FileAlreadyExists and create otherwise:
Files.createDirectory(ciphertextPath.getRawPath());
Expand Down Expand Up @@ -348,9 +348,11 @@ private FileChannel newFileChannelFromSymlink(CryptoPath cleartextPath, Effectiv
}

private FileChannel newFileChannelFromFile(CryptoPath cleartextFilePath, EffectiveOpenOptions options, FileAttribute<?>... attrs) throws IOException {
if (options.create() || options.createNew()) {
assertCleartextNameLengthAllowed(cleartextFilePath);
}
CiphertextFilePath ciphertextPath = cryptoPathMapper.getCiphertextFilePath(cleartextFilePath);
Path ciphertextFilePath = ciphertextPath.getFilePath();
assertCiphertextPathLengthMeetsLimitations(ciphertextFilePath);
if (options.createNew() && openCryptoFiles.get(ciphertextFilePath).isPresent()) {
throw new FileAlreadyExistsException(cleartextFilePath.toString());
} else {
Expand Down Expand Up @@ -400,6 +402,7 @@ private void deleteDirectory(CryptoPath cleartextPath, CiphertextFilePath cipher

void copy(CryptoPath cleartextSource, CryptoPath cleartextTarget, CopyOption... options) throws IOException {
readonlyFlag.assertWritable();
assertCleartextNameLengthAllowed(cleartextTarget);
if (cleartextSource.equals(cleartextTarget)) {
return;
}
Expand All @@ -418,7 +421,6 @@ private void copySymlink(CryptoPath cleartextSource, CryptoPath cleartextTarget,
if (ArrayUtils.contains(options, LinkOption.NOFOLLOW_LINKS)) {
CiphertextFilePath ciphertextSourceFile = cryptoPathMapper.getCiphertextFilePath(cleartextSource);
CiphertextFilePath ciphertextTargetFile = cryptoPathMapper.getCiphertextFilePath(cleartextTarget);
assertCiphertextPathLengthMeetsLimitations(ciphertextTargetFile.getSymlinkFilePath());
CopyOption[] resolvedOptions = ArrayUtils.without(options, LinkOption.NOFOLLOW_LINKS).toArray(CopyOption[]::new);
Files.createDirectories(ciphertextTargetFile.getRawPath());
Files.copy(ciphertextSourceFile.getSymlinkFilePath(), ciphertextTargetFile.getSymlinkFilePath(), resolvedOptions);
Expand All @@ -434,7 +436,6 @@ private void copySymlink(CryptoPath cleartextSource, CryptoPath cleartextTarget,
private void copyFile(CryptoPath cleartextSource, CryptoPath cleartextTarget, CopyOption[] options) throws IOException {
CiphertextFilePath ciphertextSource = cryptoPathMapper.getCiphertextFilePath(cleartextSource);
CiphertextFilePath ciphertextTarget = cryptoPathMapper.getCiphertextFilePath(cleartextTarget);
assertCiphertextPathLengthMeetsLimitations(ciphertextTarget.getFilePath());
if (ciphertextTarget.isShortened()) {
Files.createDirectories(ciphertextTarget.getRawPath());
}
Expand Down Expand Up @@ -498,6 +499,7 @@ private void copyAttributes(Path src, Path dst) throws IOException {

void move(CryptoPath cleartextSource, CryptoPath cleartextTarget, CopyOption... options) throws IOException {
readonlyFlag.assertWritable();
assertCleartextNameLengthAllowed(cleartextTarget);
if (cleartextSource.equals(cleartextTarget)) {
return;
}
Expand All @@ -517,7 +519,6 @@ private void moveSymlink(CryptoPath cleartextSource, CryptoPath cleartextTarget,
// "the symbolic link itself, not the target of the link, is moved"
CiphertextFilePath ciphertextSource = cryptoPathMapper.getCiphertextFilePath(cleartextSource);
CiphertextFilePath ciphertextTarget = cryptoPathMapper.getCiphertextFilePath(cleartextTarget);
assertCiphertextPathLengthMeetsLimitations(ciphertextTarget.getSymlinkFilePath());
try (OpenCryptoFiles.TwoPhaseMove twoPhaseMove = openCryptoFiles.prepareMove(ciphertextSource.getRawPath(), ciphertextTarget.getRawPath())) {
Files.move(ciphertextSource.getRawPath(), ciphertextTarget.getRawPath(), options);
if (ciphertextTarget.isShortened()) {
Expand All @@ -534,7 +535,6 @@ private void moveFile(CryptoPath cleartextSource, CryptoPath cleartextTarget, Co
// we need to re-map the OpenCryptoFile entry.
CiphertextFilePath ciphertextSource = cryptoPathMapper.getCiphertextFilePath(cleartextSource);
CiphertextFilePath ciphertextTarget = cryptoPathMapper.getCiphertextFilePath(cleartextTarget);
assertCiphertextPathLengthMeetsLimitations(ciphertextTarget.getFilePath());
try (OpenCryptoFiles.TwoPhaseMove twoPhaseMove = openCryptoFiles.prepareMove(ciphertextSource.getRawPath(), ciphertextTarget.getRawPath())) {
if (ciphertextTarget.isShortened()) {
Files.createDirectory(ciphertextTarget.getRawPath());
Expand All @@ -553,7 +553,6 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge
// Hence there is no need to re-map OpenCryptoFile entries.
CiphertextFilePath ciphertextSource = cryptoPathMapper.getCiphertextFilePath(cleartextSource);
CiphertextFilePath ciphertextTarget = cryptoPathMapper.getCiphertextFilePath(cleartextTarget);
assertCiphertextPathLengthMeetsLimitations(ciphertextTarget.getDirFilePath());
if (ArrayUtils.contains(options, StandardCopyOption.REPLACE_EXISTING)) {
// check if not attempting to move atomically:
if (ArrayUtils.contains(options, StandardCopyOption.ATOMIC_MOVE)) {
Expand Down Expand Up @@ -593,8 +592,8 @@ CryptoFileStore getFileStore() {

void createSymbolicLink(CryptoPath cleartextPath, Path target, FileAttribute<?>... attrs) throws IOException {
assertOpen();
CiphertextFilePath ciphertextFilePath = cryptoPathMapper.getCiphertextFilePath(cleartextPath);
assertCiphertextPathLengthMeetsLimitations(ciphertextFilePath.getSymlinkFilePath());
readonlyFlag.assertWritable();
assertCleartextNameLengthAllowed(cleartextPath);
symlinks.createSymbolicLink(cleartextPath, target, attrs);
}

Expand All @@ -612,13 +611,11 @@ CryptoPath getRootPath() {
CryptoPath getEmptyPath() {
return emptyPath;
}

void assertCiphertextPathLengthMeetsLimitations(Path cdrFilePath) throws FileNameTooLongException {
Path vaultRelativePath = pathToVault.relativize(cdrFilePath);
String fileName = vaultRelativePath.getName(3).toString(); // fourth path element (d/xx/yyyyy/file.c9r/symlink.c9r)
String path = vaultRelativePath.toString();
if (fileName.length() > fileSystemProperties.maxNameLength() || path.length() > fileSystemProperties.maxPathLength()) {
throw new FileNameTooLongException(path, fileSystemProperties.maxPathLength(), fileSystemProperties.maxNameLength());

void assertCleartextNameLengthAllowed(CryptoPath cleartextPath) throws FileNameTooLongException {
String filename = cleartextPath.getFileName().toString();
if (filename.length() > fileSystemProperties.maxCleartextNameLength()) {
throw new FileNameTooLongException(cleartextPath.toString(), fileSystemProperties.maxCleartextNameLength());
}
}

Expand Down
116 changes: 28 additions & 88 deletions src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,14 @@
package org.cryptomator.cryptofs;

import com.google.common.base.Strings;
import org.cryptomator.cryptofs.common.Constants;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;

import java.net.URI;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
Expand All @@ -38,31 +35,20 @@
public class CryptoFileSystemProperties extends AbstractMap<String, Object> {

/**
* Maximum ciphertext path length.
* Maximum cleartext filename length.
*
* @since 1.9.8
*/
public static final String PROPERTY_MAX_PATH_LENGTH = "maxPathLength";

static final int DEFAULT_MAX_PATH_LENGTH = Constants.MAX_CIPHERTEXT_PATH_LENGTH;

/**
* Maximum filename length of .c9r files.
*
* @since 1.9.9
* @since 2.0.0
*/
public static final String PROPERTY_MAX_NAME_LENGTH = "maxNameLength";
public static final String PROPERTY_MAX_CLEARTEXT_NAME_LENGTH = "maxCleartextNameLength";

static final int DEFAULT_MAX_NAME_LENGTH = Constants.MAX_CIPHERTEXT_NAME_LENGTH;
static final int DEFAULT_MAX_CLEARTEXT_NAME_LENGTH = LongFileNameProvider.MAX_FILENAME_BUFFER_SIZE;

/**
* Key identifying the key loader used during initialization.
*
* @since 2.0.0
*/
public static final String PROPERTY_KEYLOADERS = "keyLoaders";

static final Collection<MasterkeyLoader> DEFAULT_KEYLOADERS = Set.of();
public static final String PROPERTY_KEYLOADER = "keyLoader";

/**
* Key identifying the name of the vault config file located inside the vault directory.
Expand Down Expand Up @@ -111,34 +97,17 @@ public enum FileSystemFlags {

private CryptoFileSystemProperties(Builder builder) {
this.entries = Set.of( //
Map.entry(PROPERTY_KEYLOADERS, builder.keyLoaders), //
Map.entry(PROPERTY_KEYLOADER, builder.keyLoader), //
Map.entry(PROPERTY_FILESYSTEM_FLAGS, builder.flags), //
Map.entry(PROPERTY_VAULTCONFIG_FILENAME, builder.vaultConfigFilename), //
Map.entry(PROPERTY_MASTERKEY_FILENAME, builder.masterkeyFilename), //
Map.entry(PROPERTY_MAX_PATH_LENGTH, builder.maxPathLength), //
Map.entry(PROPERTY_MAX_NAME_LENGTH, builder.maxNameLength), //
Map.entry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, builder.maxCleartextNameLength), //
Map.entry(PROPERTY_CIPHER_COMBO, builder.cipherCombo) //
);
}

Collection<MasterkeyLoader> keyLoaders() {
return (Collection<MasterkeyLoader>) get(PROPERTY_KEYLOADERS);
}

/**
* Selects the first applicable MasterkeyLoader that supports the given scheme.
*
* @param scheme An URI scheme used in key IDs
* @return A key loader
* @throws MasterkeyLoadingFailedException If the scheme is not supported by any key loader
*/
MasterkeyLoader keyLoader(String scheme) throws MasterkeyLoadingFailedException {
for (MasterkeyLoader loader : keyLoaders()) {
if (loader.supportsScheme(scheme)) {
return loader;
}
}
throw new MasterkeyLoadingFailedException("No key loader for key type: " + scheme);
MasterkeyLoader keyLoader() {
return (MasterkeyLoader) get(PROPERTY_KEYLOADER);
}

public VaultCipherCombo cipherCombo() {
Expand All @@ -162,12 +131,8 @@ String masterkeyFilename() {
return (String) get(PROPERTY_MASTERKEY_FILENAME);
}

int maxPathLength() {
return (int) get(PROPERTY_MAX_PATH_LENGTH);
}

int maxNameLength() {
return (int) get(PROPERTY_MAX_NAME_LENGTH);
int maxCleartextNameLength() {
return (int) get(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH);
}

@Override
Expand Down Expand Up @@ -219,23 +184,21 @@ public static CryptoFileSystemProperties wrap(Map<String, ?> properties) {
public static class Builder {

public VaultCipherCombo cipherCombo = DEFAULT_CIPHER_COMBO;
private Collection<MasterkeyLoader> keyLoaders = new HashSet<>(DEFAULT_KEYLOADERS);
private MasterkeyLoader keyLoader = null;
private final Set<FileSystemFlags> flags = EnumSet.copyOf(DEFAULT_FILESYSTEM_FLAGS);
private String vaultConfigFilename = DEFAULT_VAULTCONFIG_FILENAME;
private String masterkeyFilename = DEFAULT_MASTERKEY_FILENAME;
private int maxPathLength = DEFAULT_MAX_PATH_LENGTH;
private int maxNameLength = DEFAULT_MAX_NAME_LENGTH;
private int maxCleartextNameLength = DEFAULT_MAX_CLEARTEXT_NAME_LENGTH;

private Builder() {
}

private Builder(Map<String, ?> properties) {
checkedSet(Collection.class, PROPERTY_KEYLOADERS, properties, this::withKeyLoaders);
checkedSet(MasterkeyLoader.class, PROPERTY_KEYLOADER, properties, this::withKeyLoader);
checkedSet(String.class, PROPERTY_VAULTCONFIG_FILENAME, properties, this::withVaultConfigFilename);
checkedSet(String.class, PROPERTY_MASTERKEY_FILENAME, properties, this::withMasterkeyFilename);
checkedSet(Set.class, PROPERTY_FILESYSTEM_FLAGS, properties, this::withFlags);
checkedSet(Integer.class, PROPERTY_MAX_PATH_LENGTH, properties, this::withMaxPathLength);
checkedSet(Integer.class, PROPERTY_MAX_NAME_LENGTH, properties, this::withMaxNameLength);
checkedSet(Integer.class, PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, properties, this::withMaxCleartextNameLength);
checkedSet(VaultCipherCombo.class, PROPERTY_CIPHER_COMBO, properties, this::withCipherCombo);
}

Expand All @@ -250,28 +213,17 @@ private <T> void checkedSet(Class<T> type, String key, Map<String, ?> properties
}
}


/**
* Sets the maximum ciphertext path length for a CryptoFileSystem.
*
* @param maxPathLength The maximum ciphertext path length allowed
* @return this
* @since 1.9.8
*/
public Builder withMaxPathLength(int maxPathLength) {
this.maxPathLength = maxPathLength;
return this;
}

/**
* Sets the maximum ciphertext filename length for a CryptoFileSystem.
* Sets the maximum cleartext filename length for a CryptoFileSystem. This value is checked during write
* operations. Read access to nodes with longer names should be unaffected. Setting this value to {@code 0} or
* a negative value effectively disables write access.
*
* @param maxNameLength The maximum ciphertext filename length allowed
* @param maxCleartextNameLength The maximum cleartext filename length allowed
* @return this
* @since 1.9.9
* @since 2.0.0
*/
public Builder withMaxNameLength(int maxNameLength) {
this.maxNameLength = maxNameLength;
public Builder withMaxCleartextNameLength(int maxCleartextNameLength) {
this.maxCleartextNameLength = maxCleartextNameLength;
return this;
}

Expand All @@ -289,26 +241,14 @@ public Builder withCipherCombo(VaultCipherCombo cipherCombo) {
}

/**
* Sets the keyLoaders for a CryptoFileSystem.
*
* @param keyLoaders A set of keyLoaders to load the key configured in the vault configuration
* @return this
* @since 2.0.0
*/
public Builder withKeyLoaders(MasterkeyLoader... keyLoaders) {
return withKeyLoaders(asList(keyLoaders));
}

/**
* Sets the keyLoaders for a CryptoFileSystem.
* Sets the keyloader for a CryptoFileSystem.
*
* @param keyLoaders A set of keyLoaders to load the key configured in the vault configuration
* @param keyLoader A factory creating a {@link MasterkeyLoader} capable of handling the given {@code scheme}.
* @return this
* @since 2.0.0
*/
public Builder withKeyLoaders(Collection<MasterkeyLoader> keyLoaders) {
this.keyLoaders.clear();
this.keyLoaders.addAll(keyLoaders);
public Builder withKeyLoader(MasterkeyLoader keyLoader) {
this.keyLoader = keyLoader;
return this;
}

Expand Down Expand Up @@ -372,8 +312,8 @@ public CryptoFileSystemProperties build() {
}

private void validate() {
if (keyLoaders.isEmpty()) {
throw new IllegalStateException("at least one keyloader is required");
if (keyLoader == null) {
throw new IllegalStateException("keyLoader is required");
}
if (Strings.nullToEmpty(masterkeyFilename).trim().isEmpty()) {
throw new IllegalStateException("masterkeyFilename is required");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ 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()).maxFilenameLength(properties.maxNameLength()).build();
try (Masterkey key = properties.keyLoader(keyId.getScheme()).loadKey(keyId);
var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).shorteningThreshold(Constants.DEFAULT_SHORTENING_THRESHOLD).build();
try (Masterkey key = properties.keyLoader().loadKey(keyId);
Cryptor cryptor = config.getCipherCombo().getCryptorProvider(strongSecureRandom()).withKey(key)) {
rawKey = key.getEncoded();
// save vault config:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT

var configLoader = VaultConfig.decode(token);
var keyId = configLoader.getKeyId();
try (Masterkey key = properties.keyLoader(keyId.getScheme()).loadKey(keyId)) {
try (Masterkey key = properties.keyLoader().loadKey(keyId)) {
var config = configLoader.verify(key.getEncoded(), Constants.VAULT_VERSION);
var adjustedProperties = adjustForCapabilities(pathToVault, properties);
var cryptor = config.getCipherCombo().getCryptorProvider(csprng).withKey(key.clone());
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,19 @@ public class CryptoPathMapper {
private final Path dataRoot;
private final DirectoryIdProvider dirIdProvider;
private final LongFileNameProvider longFileNameProvider;
private final VaultConfig vaultConfig;
private final LoadingCache<DirIdAndName, String> ciphertextNames;
private final Cache<CryptoPath, CiphertextDirectory> ciphertextDirectories;

private final CiphertextDirectory rootDirectory;

@Inject
CryptoPathMapper(@PathToVault Path pathToVault, Cryptor cryptor, DirectoryIdProvider dirIdProvider, LongFileNameProvider longFileNameProvider) {
CryptoPathMapper(@PathToVault Path pathToVault, Cryptor cryptor, DirectoryIdProvider dirIdProvider, LongFileNameProvider longFileNameProvider, VaultConfig vaultConfig) {
this.dataRoot = pathToVault.resolve(DATA_DIR_NAME);
this.cryptor = cryptor;
this.dirIdProvider = dirIdProvider;
this.longFileNameProvider = longFileNameProvider;
this.vaultConfig = vaultConfig;
this.ciphertextNames = CacheBuilder.newBuilder().maximumSize(MAX_CACHED_CIPHERTEXT_NAMES).build(CacheLoader.from(this::getCiphertextFileName));
this.ciphertextDirectories = CacheBuilder.newBuilder().maximumSize(MAX_CACHED_DIR_PATHS).expireAfterWrite(MAX_CACHE_AGE).build();
this.rootDirectory = resolveDirectory(Constants.ROOT_DIR_ID);
Expand Down Expand Up @@ -127,7 +129,7 @@ public CiphertextFilePath getCiphertextFilePath(CryptoPath cleartextPath) throws
public CiphertextFilePath getCiphertextFilePath(Path parentCiphertextDir, String parentDirId, String cleartextName) {
String ciphertextName = ciphertextNames.getUnchecked(new DirIdAndName(parentDirId, cleartextName));
Path c9rPath = parentCiphertextDir.resolve(ciphertextName);
if (ciphertextName.length() > Constants.MAX_CIPHERTEXT_NAME_LENGTH) {
if (ciphertextName.length() > vaultConfig.getShorteningThreshold()) {
LongFileNameProvider.DeflatedFileName deflatedFileName = longFileNameProvider.deflate(c9rPath);
return new CiphertextFilePath(deflatedFileName.c9sPath, Optional.of(deflatedFileName));
} else {
Expand Down
Loading

0 comments on commit f43c2df

Please sign in to comment.