From e719b8e5043ff1c972b3481ff1677db583289844 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Fri, 20 Nov 2020 15:12:41 +0100 Subject: [PATCH 01/70] Added vault version 8 migrator (references #95) --- pom.xml | 20 +++-- .../migration/v8/Version8Migrator.java | 84 +++++++++++++++++++ .../migration/v8/Version8MigratorTest.java | 67 +++++++++++++++ 3 files changed, 162 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java create mode 100644 src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java diff --git a/pom.xml b/pom.xml index ab1a2afe..e89b0c79 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ <!-- dependencies --> <cryptolib.version>1.4.0</cryptolib.version> + <jwt.version>3.11.0</jwt.version> <dagger.version>2.29.1</dagger.version> <guava.version>30.0-jre</guava.version> <slf4j.version>1.7.30</slf4j.version> @@ -67,26 +68,27 @@ <artifactId>cryptolib</artifactId> <version>${cryptolib.version}</version> </dependency> + <dependency> + <groupId>com.auth0</groupId> + <artifactId>java-jwt</artifactId> + <version>${jwt.version}</version> + </dependency> + <dependency> + <groupId>com.google.dagger</groupId> + <artifactId>dagger</artifactId> + <version>${dagger.version}</version> + </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>${guava.version}</version> </dependency> - - <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> - <!-- DI --> - <dependency> - <groupId>com.google.dagger</groupId> - <artifactId>dagger</artifactId> - <version>${dagger.version}</version> - </dependency> - <!-- Test --> <dependency> <groupId>org.junit.jupiter</groupId> diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java new file mode 100644 index 00000000..83fdd9f4 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the accompanying LICENSE file. + *******************************************************************************/ +package org.cryptomator.cryptofs.migration.v8; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import org.cryptomator.cryptofs.common.MasterkeyBackupHelper; +import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener; +import org.cryptomator.cryptofs.migration.api.MigrationProgressListener; +import org.cryptomator.cryptofs.migration.api.Migrator; +import org.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.cryptolib.api.CryptorProvider; +import org.cryptomator.cryptolib.api.InvalidPassphraseException; +import org.cryptomator.cryptolib.api.KeyFile; +import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.text.Normalizer; +import java.text.Normalizer.Form; +import java.util.Arrays; +import java.util.UUID; + +public class Version8Migrator implements Migrator { + + private static final String CONFIG_FILE_NAME = "vaultconfig.cryptomator"; + private static final Logger LOG = LoggerFactory.getLogger(Version8Migrator.class); + + private final CryptorProvider cryptorProvider; + + @Inject + public Version8Migrator(CryptorProvider cryptorProvider) { + this.cryptorProvider = cryptorProvider; + } + + @Override + public void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { + LOG.info("Upgrading {} from version 7 to version 8.", vaultRoot); + progressListener.update(MigrationProgressListener.ProgressState.INITIALIZING, 0.0); + Path masterkeyFile = vaultRoot.resolve(masterkeyFilename); + Path vaultConfigFile = vaultRoot.resolve(CONFIG_FILE_NAME); + byte[] fileContentsBeforeUpgrade = Files.readAllBytes(masterkeyFile); + byte[] rawKey = new byte[0]; + KeyFile keyFile = KeyFile.parse(fileContentsBeforeUpgrade); + try (Cryptor cryptor = cryptorProvider.createFromKeyFile(keyFile, passphrase, 7)) { + // create backup, as soon as we know the password was correct: + Path masterkeyBackupFile = MasterkeyBackupHelper.attemptMasterKeyBackup(masterkeyFile); + LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName()); + + // create vaultconfig.cryptomator + rawKey = cryptor.getRawKey(); + Algorithm algorithm = Algorithm.HMAC256(rawKey); + var config = JWT.create() // + .withJWTId(UUID.randomUUID().toString()) // + .withClaim("format", 8) // + .withClaim("keysource", "MASTERKEY_FILE") // + .withClaim("ciphermode", "SIV_CTRMAC") // + .withClaim("maxFileNameLen", 220) // + .sign(algorithm); + Files.writeString(vaultConfigFile, config, StandardCharsets.US_ASCII, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); + LOG.info("Wrote vault config to {}.", vaultConfigFile); + + progressListener.update(MigrationProgressListener.ProgressState.FINALIZING, 0.0); + + // rewrite masterkey file with normalized passphrase: + byte[] fileContentsAfterUpgrade = cryptor.writeKeysToMasterkeyFile(passphrase, 999).serialize(); + Files.write(masterkeyFile, fileContentsAfterUpgrade, StandardOpenOption.TRUNCATE_EXISTING); + LOG.info("Updated masterkey."); + } finally { + Arrays.fill(rawKey, (byte) 0x00); + } + LOG.info("Upgraded {} from version 7 to version 8.", vaultRoot); + } + +} diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java new file mode 100644 index 00000000..5dcac963 --- /dev/null +++ b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java @@ -0,0 +1,67 @@ +package org.cryptomator.cryptofs.migration.v8; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; +import org.cryptomator.cryptofs.migration.api.Migrator; +import org.cryptomator.cryptofs.mocks.NullSecureRandom; +import org.cryptomator.cryptolib.Cryptors; +import org.cryptomator.cryptolib.api.CryptorProvider; +import org.cryptomator.cryptolib.api.KeyFile; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; + +public class Version8MigratorTest { + + private FileSystem fs; + private Path pathToVault; + private Path masterkeyFile; + private Path vaultConfigFile; + private CryptorProvider cryptorProvider; + + @BeforeEach + public void setup() throws IOException { + cryptorProvider = Cryptors.version1(NullSecureRandom.INSTANCE); + fs = Jimfs.newFileSystem(Configuration.unix()); + pathToVault = fs.getPath("/vaultDir"); + masterkeyFile = pathToVault.resolve("masterkey.cryptomator"); + vaultConfigFile = pathToVault.resolve("vaultconfig.cryptomator"); + Files.createDirectory(pathToVault); + } + + @AfterEach + public void teardown() throws IOException { + fs.close(); + } + + @Test + public void testMigrate() throws IOException { + KeyFile beforeMigration = cryptorProvider.createNew().writeKeysToMasterkeyFile("topsecret", 7); + Assumptions.assumeTrue(beforeMigration.getVersion() == 7); + Assumptions.assumeFalse(Files.exists(vaultConfigFile)); + Files.write(masterkeyFile, beforeMigration.serialize()); + + Migrator migrator = new Version8Migrator(cryptorProvider); + migrator.migrate(pathToVault, masterkeyFile.getFileName().toString(), "topsecret"); + + KeyFile afterMigration = KeyFile.parse(Files.readAllBytes(masterkeyFile)); + Assertions.assertEquals(999, afterMigration.getVersion()); + Assertions.assertTrue(Files.exists(vaultConfigFile)); + DecodedJWT token = JWT.decode(Files.readString(vaultConfigFile)); + Assertions.assertNotNull(token.getId()); + Assertions.assertEquals(8, token.getClaim("format").asInt()); + Assertions.assertEquals("SIV_CTRMAC", token.getClaim("ciphermode").asString()); + Assertions.assertEquals("MASTERKEY_FILE", token.getClaim("keysource").asString()); + Assertions.assertEquals(220, token.getClaim("maxFileNameLen").asInt()); + } + +} \ No newline at end of file From 0d6050000fcb6efa7c4c922effb5ed7437afbc2e Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 23 Nov 2020 10:08:05 +0100 Subject: [PATCH 02/70] use kid field to signify which key to use --- .../org/cryptomator/cryptofs/migration/v8/Version8Migrator.java | 2 +- .../cryptomator/cryptofs/migration/v8/Version8MigratorTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java index 83fdd9f4..9b224da9 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java @@ -61,8 +61,8 @@ public void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passp Algorithm algorithm = Algorithm.HMAC256(rawKey); var config = JWT.create() // .withJWTId(UUID.randomUUID().toString()) // + .withKeyId("MASTERKEY_FILE") // .withClaim("format", 8) // - .withClaim("keysource", "MASTERKEY_FILE") // .withClaim("ciphermode", "SIV_CTRMAC") // .withClaim("maxFileNameLen", 220) // .sign(algorithm); diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java index 5dcac963..0b786df3 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java @@ -58,9 +58,9 @@ public void testMigrate() throws IOException { Assertions.assertTrue(Files.exists(vaultConfigFile)); DecodedJWT token = JWT.decode(Files.readString(vaultConfigFile)); Assertions.assertNotNull(token.getId()); + Assertions.assertEquals("MASTERKEY_FILE", token.getKeyId()); Assertions.assertEquals(8, token.getClaim("format").asInt()); Assertions.assertEquals("SIV_CTRMAC", token.getClaim("ciphermode").asString()); - Assertions.assertEquals("MASTERKEY_FILE", token.getClaim("keysource").asString()); Assertions.assertEquals(220, token.getClaim("maxFileNameLen").asInt()); } From 797fbc430bde59b28b1fe1c046f8574ee6dca778 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Tue, 24 Nov 2020 08:21:43 +0100 Subject: [PATCH 03/70] Updated migrator API --- .../cryptofs/CryptoFileSystemProperties.java | 16 ++++++++++++ .../cryptofs/CryptoFileSystemProvider.java | 2 +- .../cryptofs/migration/Migrators.java | 9 ++++--- .../cryptofs/migration/api/Migrator.java | 13 ++++++---- .../migration/v6/Version6Migrator.java | 7 +++++- .../migration/v7/Version7Migrator.java | 17 ++++++++++++- .../migration/v8/Version8Migrator.java | 13 +++++++--- .../CryptoFileSystemPropertiesTest.java | 20 ++++++--------- .../cryptofs/migration/MigratorsTest.java | 25 +++++++------------ .../migration/v6/Version6MigratorTest.java | 2 +- .../migration/v7/Version7MigratorTest.java | 12 ++++----- .../migration/v8/Version8MigratorTest.java | 4 +-- 12 files changed, 88 insertions(+), 52 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index 7329773d..a9efb4fa 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -70,6 +70,16 @@ public class CryptoFileSystemProperties extends AbstractMap<String, Object> { static final byte[] DEFAULT_PEPPER = new byte[0]; + + /** + * Key identifying the name of the vault config file located inside the vault directory. + * + * @since 2.0.0 + */ + public static final String PROPERTY_VAULTCONFIG_FILENAME = "vaultConfigFilename"; + + static final String DEFAULT_VAULTCONFIG_FILENAME = "vault.cryptomator"; + /** * Key identifying the name of the masterkey file located inside the vault directory. * @@ -132,6 +142,7 @@ private CryptoFileSystemProperties(Builder builder) { entry(PROPERTY_PASSPHRASE, builder.passphrase), // entry(PROPERTY_PEPPER, builder.pepper), // entry(PROPERTY_FILESYSTEM_FLAGS, builder.flags), // + entry(PROPERTY_VAULTCONFIG_FILENAME, builder.vaultConfigFilename), // entry(PROPERTY_MASTERKEY_FILENAME, builder.masterkeyFilename), // entry(PROPERTY_MAX_PATH_LENGTH, builder.maxPathLength), // entry(PROPERTY_MAX_NAME_LENGTH, builder.maxNameLength) // @@ -163,6 +174,10 @@ boolean initializeImplicitly() { return flags().contains(FileSystemFlags.INIT_IMPLICITLY); } + String vaultConfigFilename() { + return (String) get(PROPERTY_VAULTCONFIG_FILENAME); + } + String masterkeyFilename() { return (String) get(PROPERTY_MASTERKEY_FILENAME); } @@ -257,6 +272,7 @@ public static class Builder { private CharSequence passphrase; public byte[] pepper = DEFAULT_PEPPER; 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; diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 9ae7236e..d25102c3 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -305,7 +305,7 @@ public CryptoFileSystem newFileSystem(URI uri, Map<String, ?> rawProperties) thr private void migrateFileSystemIfRequired(CryptoFileSystemUri parsedUri, CryptoFileSystemProperties properties) throws IOException, FileSystemNeedsMigrationException { if (Migrators.get().needsMigration(parsedUri.pathToVault(), properties.masterkeyFilename())) { if (properties.migrateImplicitly()) { - Migrators.get().migrate(parsedUri.pathToVault(), properties.masterkeyFilename(), properties.passphrase(), (state, progress) -> {}, (event) -> ContinuationResult.CANCEL); + Migrators.get().migrate(parsedUri.pathToVault(), properties.vaultConfigFilename(), properties.masterkeyFilename(), properties.passphrase(), (state, progress) -> {}, (event) -> ContinuationResult.CANCEL); } else { throw new FileSystemNeedsMigrationException(parsedUri.pathToVault()); } diff --git a/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java b/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java index a0456fb0..899da75d 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java @@ -34,7 +34,7 @@ * <pre> * <code> * if (Migrators.get().{@link #needsMigration(Path, String) needsMigration(pathToVault, masterkeyFileName)}) { - * Migrators.get().{@link #migrate(Path, String, CharSequence, MigrationProgressListener, MigrationContinuationListener) migrate(pathToVault, masterkeyFileName, passphrase, progressListener, continuationListener)}; + * Migrators.get().{@link #migrate(Path, String, String, CharSequence, MigrationProgressListener, MigrationContinuationListener) migrate(pathToVault, masterkeyFileName, passphrase, progressListener, continuationListener)}; * } * </code> * </pre> @@ -91,7 +91,8 @@ public boolean needsMigration(Path pathToVault, String masterkeyFilename) throws * Performs the actual migration. This task may take a while and this method will block. * * @param pathToVault Path to the vault's root - * @param masterkeyFilename Name of the masterkey file located in the vault + * @param vaultConfigFilename Name of the vault config file located inside <code>pathToVault</code> + * @param masterkeyFilename Name of the masterkey file located inside <code>pathToVault</code> * @param passphrase The passphrase needed to unlock the vault * @param progressListener Listener that will get notified of progress updates * @param continuationListener Listener that will get asked if there are events that require feedback @@ -100,7 +101,7 @@ public boolean needsMigration(Path pathToVault, String masterkeyFilename) throws * @throws FileSystemCapabilityChecker.MissingCapabilityException If the underlying filesystem lacks features required to store a vault * @throws IOException if an I/O error occurs migrating the vault */ - public void migrate(Path pathToVault, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws NoApplicableMigratorException, InvalidPassphraseException, IOException { + public void migrate(Path pathToVault, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws NoApplicableMigratorException, InvalidPassphraseException, IOException { fsCapabilityChecker.assertAllCapabilities(pathToVault); Path masterKeyPath = pathToVault.resolve(masterkeyFilename); @@ -109,7 +110,7 @@ public void migrate(Path pathToVault, String masterkeyFilename, CharSequence pas try { Migrator migrator = findApplicableMigrator(keyFile.getVersion()).orElseThrow(NoApplicableMigratorException::new); - migrator.migrate(pathToVault, masterkeyFilename, passphrase, progressListener, continuationListener); + migrator.migrate(pathToVault, vaultConfigFilename, masterkeyFilename, passphrase, progressListener, continuationListener); } catch (UnsupportedVaultFormatException e) { // might be a tampered masterkey file, as this exception is also thrown if the vault version MAC is not authentic. throw new IllegalStateException("Vault version checked beforehand but not supported by migrator."); diff --git a/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java index 6973e8ac..d1bc9ba8 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java @@ -20,20 +20,22 @@ public interface Migrator { * Performs the migration this migrator is built for. * * @param vaultRoot + * @param vaultConfigFilename * @param masterkeyFilename * @param passphrase * @throws InvalidPassphraseException * @throws UnsupportedVaultFormatException * @throws IOException */ - default void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { - migrate(vaultRoot, masterkeyFilename, passphrase, (state, progress) -> {}); + default void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { + migrate(vaultRoot, vaultConfigFilename, masterkeyFilename, passphrase, (state, progress) -> {}); } /** * Performs the migration this migrator is built for. * * @param vaultRoot + * @param vaultConfigFilename * @param masterkeyFilename * @param passphrase * @param progressListener @@ -41,14 +43,15 @@ default void migrate(Path vaultRoot, String masterkeyFilename, CharSequence pass * @throws UnsupportedVaultFormatException * @throws IOException */ - default void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { - migrate(vaultRoot, masterkeyFilename, passphrase, progressListener, (event) -> MigrationContinuationListener.ContinuationResult.CANCEL); + default void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { + migrate(vaultRoot, vaultConfigFilename, masterkeyFilename, passphrase, progressListener, (event) -> MigrationContinuationListener.ContinuationResult.CANCEL); } /** * Performs the migration this migrator is built for. * * @param vaultRoot + * @param vaultConfigFilename * @param masterkeyFilename * @param passphrase * @param progressListener @@ -57,6 +60,6 @@ default void migrate(Path vaultRoot, String masterkeyFilename, CharSequence pass * @throws UnsupportedVaultFormatException * @throws IOException */ - void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException; + void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException; } diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java index 8c39a1c0..9d12fb4b 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java @@ -26,6 +26,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Updates masterkey.cryptomator: + * + * Version 6 encodes the passphrase in Unicode NFC. + */ public class Version6Migrator implements Migrator { private static final Logger LOG = LoggerFactory.getLogger(Version6Migrator.class); @@ -38,7 +43,7 @@ public Version6Migrator(CryptorProvider cryptorProvider) { } @Override - public void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { + public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { LOG.info("Upgrading {} from version 5 to version 6.", vaultRoot); progressListener.update(MigrationProgressListener.ProgressState.INITIALIZING, 0.0); Path masterkeyFile = vaultRoot.resolve(masterkeyFilename); diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java index b4f5bc5b..61b6f0b9 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java @@ -30,6 +30,21 @@ import java.nio.file.StandardOpenOption; import java.util.EnumSet; +/** + * Renames ciphertext names: + * + * <ul> + * <li>Files: BASE32== -> base64==.c9r</li> + * <li>Dirs: 0BASE32== -> base64==.c9r/dir.c9r</li> + * <li>Symlinks: 1SBASE32== -> base64.c9r/symlink.c9r</li> + * </ul> + * + * Shortened names: + * <ul> + * <li>shortened.lng -> shortened.c9s</li> + * <li>m/shortened.lng -> shortened.c9s/contents.c9r</li> + * </ul> + */ public class Version7Migrator implements Migrator { private static final Logger LOG = LoggerFactory.getLogger(Version7Migrator.class); @@ -42,7 +57,7 @@ public Version7Migrator(CryptorProvider cryptorProvider) { } @Override - public void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { + public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { LOG.info("Upgrading {} from version 6 to version 7.", vaultRoot); progressListener.update(MigrationProgressListener.ProgressState.INITIALIZING, 0.0); Path masterkeyFile = vaultRoot.resolve(masterkeyFilename); diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java index 9b224da9..5695e161 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java @@ -30,9 +30,16 @@ import java.util.Arrays; import java.util.UUID; +/** + * Splits up <code>masterkey.cryptomator</code>: + * + * <ul> + * <li><code>vault.cryptomator</code> contains vault version and vault-specific metadata</li> + * <li><code>masterkey.cryptomator</code> contains KDF params and may become obsolete when other key sources are supported</li> + * </ul> + */ public class Version8Migrator implements Migrator { - private static final String CONFIG_FILE_NAME = "vaultconfig.cryptomator"; private static final Logger LOG = LoggerFactory.getLogger(Version8Migrator.class); private final CryptorProvider cryptorProvider; @@ -43,11 +50,11 @@ public Version8Migrator(CryptorProvider cryptorProvider) { } @Override - public void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { + public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { LOG.info("Upgrading {} from version 7 to version 8.", vaultRoot); progressListener.update(MigrationProgressListener.ProgressState.INITIALIZING, 0.0); Path masterkeyFile = vaultRoot.resolve(masterkeyFilename); - Path vaultConfigFile = vaultRoot.resolve(CONFIG_FILE_NAME); + Path vaultConfigFile = vaultRoot.resolve(vaultConfigFilename); byte[] fileContentsBeforeUpgrade = Files.readAllBytes(masterkeyFile); byte[] rawKey = new byte[0]; KeyFile keyFile = KeyFile.parse(fileContentsBeforeUpgrade); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java index c556af23..38d0eafc 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java @@ -14,18 +14,7 @@ import java.util.Map; import java.util.Map.Entry; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.DEFAULT_MASTERKEY_FILENAME; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.DEFAULT_MAX_NAME_LENGTH; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.DEFAULT_MAX_PATH_LENGTH; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.DEFAULT_PEPPER; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.PROPERTY_FILESYSTEM_FLAGS; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.PROPERTY_MASTERKEY_FILENAME; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.PROPERTY_MAX_NAME_LENGTH; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.PROPERTY_MAX_PATH_LENGTH; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.PROPERTY_PASSPHRASE; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.PROPERTY_PEPPER; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemPropertiesFrom; +import static org.cryptomator.cryptofs.CryptoFileSystemProperties.*; import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; @@ -56,6 +45,7 @@ public void testSetOnlyPassphrase() { containsInAnyOrder( // anEntry(PROPERTY_PASSPHRASE, passphrase), // anEntry(PROPERTY_PEPPER, DEFAULT_PEPPER), // + anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, DEFAULT_MASTERKEY_FILENAME), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // @@ -80,6 +70,7 @@ public void testSetPassphraseAndReadonlyFlag() { containsInAnyOrder( // anEntry(PROPERTY_PASSPHRASE, passphrase), // anEntry(PROPERTY_PEPPER, DEFAULT_PEPPER), // + anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, DEFAULT_MASTERKEY_FILENAME), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // @@ -106,6 +97,7 @@ public void testSetPassphraseAndMasterkeyFilenameAndReadonlyFlag() { containsInAnyOrder( // anEntry(PROPERTY_PASSPHRASE, passphrase), // anEntry(PROPERTY_PEPPER, DEFAULT_PEPPER), // + anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // @@ -137,6 +129,7 @@ public void testFromMap() { containsInAnyOrder( // anEntry(PROPERTY_PASSPHRASE, passphrase), // anEntry(PROPERTY_PEPPER, pepper), // + anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, 1000), // anEntry(PROPERTY_MAX_NAME_LENGTH, 255), // @@ -164,6 +157,7 @@ public void testWrapMapWithTrueReadonly() { containsInAnyOrder( // anEntry(PROPERTY_PASSPHRASE, passphrase), // anEntry(PROPERTY_PEPPER, pepper), // + anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // @@ -191,6 +185,7 @@ public void testWrapMapWithFalseReadonly() { containsInAnyOrder( // anEntry(PROPERTY_PASSPHRASE, passphrase), // anEntry(PROPERTY_PEPPER, pepper), // + anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // @@ -252,6 +247,7 @@ public void testWrapMapWithoutReadonly() { containsInAnyOrder( // anEntry(PROPERTY_PASSPHRASE, passphrase), // anEntry(PROPERTY_PEPPER, pepper), // + anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, DEFAULT_MASTERKEY_FILENAME), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // diff --git a/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java b/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java index e4391c22..32130047 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java @@ -27,6 +27,7 @@ import java.nio.file.spi.FileSystemProvider; import java.util.Collections; import java.util.HashMap; +import java.util.Map; public class MigratorsTest { @@ -79,10 +80,10 @@ public void testNeedsNoMigration() throws IOException { } @Test - public void testMigrateWithoutMigrators() throws IOException { + public void testMigrateWithoutMigrators() { Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); Assertions.assertThrows(NoApplicableMigratorException.class, () -> { - migrators.migrate(pathToVault, "masterkey.cryptomator", "secret", (state, progress) -> {}, (event) -> ContinuationResult.CANCEL); + migrators.migrate(pathToVault, "vault.cryptomator","masterkey.cryptomator", "secret", (state, progress) -> {}, (event) -> ContinuationResult.CANCEL); }); } @@ -92,27 +93,19 @@ public void testMigrate() throws NoApplicableMigratorException, InvalidPassphras MigrationProgressListener progressListener = Mockito.mock(MigrationProgressListener.class); MigrationContinuationListener continuationListener = Mockito.mock(MigrationContinuationListener.class); Migrator migrator = Mockito.mock(Migrator.class); - Migrators migrators = new Migrators(new HashMap<Migration, Migrator>() { - { - put(Migration.ZERO_TO_ONE, migrator); - } - }, fsCapabilityChecker); - migrators.migrate(pathToVault, "masterkey.cryptomator", "secret", progressListener, continuationListener); - Mockito.verify(migrator).migrate(pathToVault, "masterkey.cryptomator", "secret", progressListener, continuationListener); + Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker); + migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", progressListener, continuationListener); + Mockito.verify(migrator).migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", progressListener, continuationListener); } @Test @SuppressWarnings("deprecation") public void testMigrateUnsupportedVaultFormat() throws NoApplicableMigratorException, InvalidPassphraseException, IOException { Migrator migrator = Mockito.mock(Migrator.class); - Migrators migrators = new Migrators(new HashMap<Migration, Migrator>() { - { - put(Migration.ZERO_TO_ONE, migrator); - } - }, fsCapabilityChecker); - Mockito.doThrow(new UnsupportedVaultFormatException(Integer.MAX_VALUE, 1)).when(migrator).migrate(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); + Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker); + Mockito.doThrow(new UnsupportedVaultFormatException(Integer.MAX_VALUE, 1)).when(migrator).migrate(Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any()); Assertions.assertThrows(IllegalStateException.class, () -> { - migrators.migrate(pathToVault, "masterkey.cryptomator", "secret", (state, progress) -> {}, (event) -> ContinuationResult.CANCEL); + migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", (state, progress) -> {}, (event) -> ContinuationResult.CANCEL); }); } diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java index 1d3620a9..5bc3105e 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java @@ -56,7 +56,7 @@ public void testMigrate() throws IOException { Path masterkeyBackupFile = pathToVault.resolve("masterkey.cryptomator" + MasterkeyBackupHelper.generateFileIdSuffix(beforeMigration.serialize()) + Constants.MASTERKEY_BACKUP_SUFFIX); Migrator migrator = new Version6Migrator(cryptorProvider); - migrator.migrate(pathToVault, "masterkey.cryptomator", oldPassword); + migrator.migrate(pathToVault, null, "masterkey.cryptomator", oldPassword); KeyFile afterMigration = KeyFile.parse(Files.readAllBytes(masterkeyFile)); Assertions.assertEquals(6, afterMigration.getVersion()); diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java index f7fbeaae..2ebcfa25 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java @@ -55,7 +55,7 @@ public void testKeyfileGetsUpdates() throws IOException { Assertions.assertEquals(6, beforeMigration.getVersion()); Migrator migrator = new Version7Migrator(cryptorProvider); - migrator.migrate(vaultRoot, "masterkey.cryptomator", "test"); + migrator.migrate(vaultRoot, null, "masterkey.cryptomator", "test"); KeyFile afterMigration = KeyFile.parse(Files.readAllBytes(masterkeyFile)); Assertions.assertEquals(7, afterMigration.getVersion()); @@ -64,7 +64,7 @@ public void testKeyfileGetsUpdates() throws IOException { @Test public void testMDirectoryGetsDeleted() throws IOException { Migrator migrator = new Version7Migrator(cryptorProvider); - migrator.migrate(vaultRoot, "masterkey.cryptomator", "test"); + migrator.migrate(vaultRoot, null, "masterkey.cryptomator", "test"); Assertions.assertFalse(Files.exists(metaDir)); } @@ -79,7 +79,7 @@ public void testMigrationFailsIfEncounteringUnsyncediCloudContent() throws IOExc Migrator migrator = new Version7Migrator(cryptorProvider); IOException e = Assertions.assertThrows(PreMigrationVisitor.PreMigrationChecksFailedException.class, () -> { - migrator.migrate(vaultRoot, "masterkey.cryptomator", "test"); + migrator.migrate(vaultRoot, null, "masterkey.cryptomator", "test"); }); Assertions.assertTrue(e.getMessage().contains("MZUWYZLOMFWWK===.icloud")); } @@ -93,7 +93,7 @@ public void testMigrationOfNormalFile() throws IOException { Files.createFile(fileBeforeMigration); Migrator migrator = new Version7Migrator(cryptorProvider); - migrator.migrate(vaultRoot, "masterkey.cryptomator", "test"); + migrator.migrate(vaultRoot, null, "masterkey.cryptomator", "test"); Assertions.assertFalse(Files.exists(fileBeforeMigration)); Assertions.assertTrue(Files.exists(fileAfterMigration)); @@ -108,7 +108,7 @@ public void testMigrationOfNormalDirectory() throws IOException { Files.createFile(fileBeforeMigration); Migrator migrator = new Version7Migrator(cryptorProvider); - migrator.migrate(vaultRoot, "masterkey.cryptomator", "test"); + migrator.migrate(vaultRoot, null, "masterkey.cryptomator", "test"); Assertions.assertFalse(Files.exists(fileBeforeMigration)); Assertions.assertTrue(Files.exists(fileAfterMigration)); @@ -123,7 +123,7 @@ public void testMigrationOfNormalSymlink() throws IOException { Files.createFile(fileBeforeMigration); Migrator migrator = new Version7Migrator(cryptorProvider); - migrator.migrate(vaultRoot, "masterkey.cryptomator", "test"); + migrator.migrate(vaultRoot, null, "masterkey.cryptomator", "test"); Assertions.assertFalse(Files.exists(fileBeforeMigration)); Assertions.assertTrue(Files.exists(fileAfterMigration)); diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java index 0b786df3..3b0acbac 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java @@ -34,7 +34,7 @@ public void setup() throws IOException { fs = Jimfs.newFileSystem(Configuration.unix()); pathToVault = fs.getPath("/vaultDir"); masterkeyFile = pathToVault.resolve("masterkey.cryptomator"); - vaultConfigFile = pathToVault.resolve("vaultconfig.cryptomator"); + vaultConfigFile = pathToVault.resolve("vault.cryptomator"); Files.createDirectory(pathToVault); } @@ -51,7 +51,7 @@ public void testMigrate() throws IOException { Files.write(masterkeyFile, beforeMigration.serialize()); Migrator migrator = new Version8Migrator(cryptorProvider); - migrator.migrate(pathToVault, masterkeyFile.getFileName().toString(), "topsecret"); + migrator.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "topsecret"); KeyFile afterMigration = KeyFile.parse(Files.readAllBytes(masterkeyFile)); Assertions.assertEquals(999, afterMigration.getVersion()); From a968253fb35fb31f24d86549f0e81bee9c9db697 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Wed, 25 Nov 2020 13:39:50 +0100 Subject: [PATCH 04/70] Allow mocking of final fields, therefore removing workarounds using reflection --- .../cryptofs/CryptoFileSystemImplTest.java | 5 - .../cryptofs/DirectoryIdLoaderTest.java | 36 ++----- .../ch/AsyncDelegatingFileChannelTest.java | 95 +++++++------------ .../cryptofs/fh/OpenCryptoFilesTest.java | 5 - .../org.mockito.plugins.MockMaker | 1 + 5 files changed, 40 insertions(+), 102 deletions(-) create mode 100644 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java index 670694d3..6cc3a56e 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java @@ -26,10 +26,8 @@ import org.mockito.Mockito; import java.io.IOException; -import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; -import java.nio.channels.spi.AbstractInterruptibleChannel; import java.nio.file.AccessDeniedException; import java.nio.file.AccessMode; import java.nio.file.AtomicMoveNotSupportedException; @@ -699,9 +697,6 @@ public void setup() throws IOException, ReflectiveOperationException { when(cryptoPathMapper.getCiphertextDir(cleartextTargetParent)).thenReturn(new CiphertextDirectory("41", ciphertextTargetParent)); when(cryptoPathMapper.getCiphertextDir(cleartextDestination)).thenReturn(new CiphertextDirectory("42", ciphertextDestinationDir)); when(physicalFsProv.newFileChannel(Mockito.same(ciphertextDestinationDirFile), Mockito.anySet(), Mockito.any())).thenReturn(ciphertextTargetDirFileChannel); - Field closeLockField = AbstractInterruptibleChannel.class.getDeclaredField("closeLock"); - closeLockField.setAccessible(true); - closeLockField.set(ciphertextTargetDirFileChannel, new Object()); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/DirectoryIdLoaderTest.java b/src/test/java/org/cryptomator/cryptofs/DirectoryIdLoaderTest.java index 036f2412..3943207a 100644 --- a/src/test/java/org/cryptomator/cryptofs/DirectoryIdLoaderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DirectoryIdLoaderTest.java @@ -7,10 +7,8 @@ import org.mockito.Mockito; import java.io.IOException; -import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; -import java.nio.channels.spi.AbstractInterruptibleChannel; import java.nio.file.FileSystem; import java.nio.file.NoSuchFileException; import java.nio.file.Path; @@ -62,10 +60,10 @@ public void testDirectoryIdForNonExistingFileIsNotEmpty() throws IOException { } @Test - public void testDirectoryIdIsReadFromExistingFile() throws IOException, ReflectiveOperationException { + public void testDirectoryIdIsReadFromExistingFile() throws IOException { String expectedId = "asdüßT°z¬╚‗"; byte[] expectedIdBytes = expectedId.getBytes(UTF_8); - FileChannel channel = createFileChannelMock(); + FileChannel channel = Mockito.mock(FileChannel.class); when(provider.newFileChannel(eq(dirFilePath), any())).thenReturn(channel); when(channel.size()).thenReturn((long) expectedIdBytes.length); when(channel.read(any(ByteBuffer.class))).then(invocation -> { @@ -80,8 +78,8 @@ public void testDirectoryIdIsReadFromExistingFile() throws IOException, Reflecti } @Test - public void testIOExceptionWhenExistingFileIsEmpty() throws IOException, ReflectiveOperationException { - FileChannel channel = createFileChannelMock(); + public void testIOExceptionWhenExistingFileIsEmpty() throws IOException { + FileChannel channel = Mockito.mock(FileChannel.class); when(provider.newFileChannel(eq(dirFilePath), any())).thenReturn(channel); when(channel.size()).thenReturn(0l); @@ -92,8 +90,8 @@ public void testIOExceptionWhenExistingFileIsEmpty() throws IOException, Reflect } @Test - public void testIOExceptionWhenExistingFileIsTooLarge() throws IOException, ReflectiveOperationException { - FileChannel channel = createFileChannelMock(); + public void testIOExceptionWhenExistingFileIsTooLarge() throws IOException { + FileChannel channel = Mockito.mock(FileChannel.class); when(provider.newFileChannel(eq(dirFilePath), any())).thenReturn(channel); when(channel.size()).thenReturn((long) Integer.MAX_VALUE); @@ -103,26 +101,4 @@ public void testIOExceptionWhenExistingFileIsTooLarge() throws IOException, Refl MatcherAssert.assertThat(e.getMessage(), containsString("Unexpectedly large directory file")); } - private FileChannel createFileChannelMock() throws ReflectiveOperationException { - FileChannel channel = Mockito.mock(FileChannel.class); - try { - Field channelOpenField = AbstractInterruptibleChannel.class.getDeclaredField("open"); - channelOpenField.setAccessible(true); - channelOpenField.set(channel, true); - } catch (NoSuchFieldException e) { - // field only declared in jdk8 - } - try { - Field channelClosedField = AbstractInterruptibleChannel.class.getDeclaredField("closed"); - channelClosedField.setAccessible(true); - channelClosedField.set(channel, false); - } catch (NoSuchFieldException e) { - // field only declared in jdk 9 - } - Field channelCloseLockField = AbstractInterruptibleChannel.class.getDeclaredField("closeLock"); - channelCloseLockField.setAccessible(true); - channelCloseLockField.set(channel, new Object()); - return channel; - } - } diff --git a/src/test/java/org/cryptomator/cryptofs/ch/AsyncDelegatingFileChannelTest.java b/src/test/java/org/cryptomator/cryptofs/ch/AsyncDelegatingFileChannelTest.java index ce452646..9d10cea3 100644 --- a/src/test/java/org/cryptomator/cryptofs/ch/AsyncDelegatingFileChannelTest.java +++ b/src/test/java/org/cryptomator/cryptofs/ch/AsyncDelegatingFileChannelTest.java @@ -8,7 +8,6 @@ *******************************************************************************/ package org.cryptomator.cryptofs.ch; -import org.cryptomator.cryptofs.ch.AsyncDelegatingFileChannel; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Assertions; @@ -16,17 +15,13 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import java.io.IOException; -import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.CompletionHandler; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; -import java.nio.channels.spi.AbstractInterruptibleChannel; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -44,38 +39,22 @@ public class AsyncDelegatingFileChannelTest { @BeforeEach public void setup() throws ReflectiveOperationException { channel = Mockito.mock(FileChannel.class); - try { - Field channelOpenField = AbstractInterruptibleChannel.class.getDeclaredField("open"); - channelOpenField.setAccessible(true); - channelOpenField.set(channel, true); - } catch (NoSuchFieldException e) { - // field only declared in jdk8 - } - try { - Field channelClosedField = AbstractInterruptibleChannel.class.getDeclaredField("closed"); - channelClosedField.setAccessible(true); - channelClosedField.set(channel, false); - } catch (NoSuchFieldException e) { - // field only declared in jdk 9 - } - Field channelCloseLockField = AbstractInterruptibleChannel.class.getDeclaredField("closeLock"); - channelCloseLockField.setAccessible(true); - channelCloseLockField.set(channel, new Object()); asyncChannel = new AsyncDelegatingFileChannel(channel, executor); } @Test - public void testIsOpen() throws IOException { + public void testIsOpen() { + Mockito.when(channel.isOpen()).thenReturn(true); Assertions.assertTrue(asyncChannel.isOpen()); - channel.close(); + + Mockito.when(channel.isOpen()).thenReturn(false); Assertions.assertFalse(asyncChannel.isOpen()); } @Test public void testClose() throws IOException { - Assertions.assertTrue(asyncChannel.isOpen()); asyncChannel.close(); - Assertions.assertFalse(asyncChannel.isOpen()); + Mockito.verify(channel).close(); } @Test @@ -112,21 +91,19 @@ public void testTryLock() throws IOException { public class LockTest { @Test - public void testSuccess() throws IOException, InterruptedException, ExecutionException { + public void testSuccess() throws IOException, InterruptedException { + Mockito.when(channel.isOpen()).thenReturn(true); final FileLock lock = Mockito.mock(FileLock.class); - Mockito.when(channel.lock(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyBoolean())).thenAnswer(new Answer<FileLock>() { - @Override - public FileLock answer(InvocationOnMock invocation) throws Throwable { - Thread.sleep(100); - return lock; - } + Mockito.when(channel.lock(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyBoolean())).thenAnswer(invocation -> { + Thread.sleep(100); + return lock; }); CountDownLatch cdl = new CountDownLatch(1); AtomicReference<FileLock> result = new AtomicReference<>(); AtomicReference<String> attachment = new AtomicReference<>(); AtomicReference<Throwable> exception = new AtomicReference<>(); - asyncChannel.lock(123l, 234l, true, "bam", new CompletionHandler<FileLock, String>() { + asyncChannel.lock(123l, 234l, true, "bam", new CompletionHandler<>() { @Override public void completed(FileLock r, String a) { @@ -162,18 +139,16 @@ public void testClosed() throws Throwable { @Test public void testExecutionException() throws Throwable { - Mockito.when(channel.lock(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyBoolean())).thenAnswer(new Answer<FileLock>() { - @Override - public FileLock answer(InvocationOnMock invocation) throws Throwable { - throw new java.lang.ArithmeticException("fail"); - } + Mockito.when(channel.isOpen()).thenReturn(true); + Mockito.when(channel.lock(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyBoolean())).thenAnswer(invocation -> { + throw new ArithmeticException("fail"); }); CountDownLatch cdl = new CountDownLatch(1); AtomicReference<FileLock> result = new AtomicReference<>(); AtomicReference<String> attachment = new AtomicReference<>(); AtomicReference<Throwable> exception = new AtomicReference<>(); - asyncChannel.lock(123l, 234l, true, "bam", new CompletionHandler<FileLock, String>() { + asyncChannel.lock(123l, 234l, true, "bam", new CompletionHandler<>() { @Override public void completed(FileLock r, String a) { @@ -204,16 +179,14 @@ public void failed(Throwable e, String a) { public class ReadTest { @Test - public void testSuccess() throws IOException, InterruptedException, ExecutionException { - Mockito.when(channel.read(Mockito.any(), Mockito.anyLong())).thenAnswer(new Answer<Integer>() { - @Override - public Integer answer(InvocationOnMock invocation) throws Throwable { - ByteBuffer dst = invocation.getArgument(0); - Thread.sleep(100); - int read = dst.remaining(); - dst.position(dst.position() + read); - return read; - } + public void testSuccess() throws IOException, InterruptedException { + Mockito.when(channel.isOpen()).thenReturn(true); + Mockito.when(channel.read(Mockito.any(), Mockito.anyLong())).thenAnswer(invocation -> { + ByteBuffer dst = invocation.getArgument(0); + Thread.sleep(100); + int read = dst.remaining(); + dst.position(dst.position() + read); + return read; }); CountDownLatch cdl = new CountDownLatch(1); @@ -221,7 +194,7 @@ public Integer answer(InvocationOnMock invocation) throws Throwable { AtomicReference<String> attachment = new AtomicReference<>(); AtomicReference<Throwable> exception = new AtomicReference<>(); ByteBuffer buf = ByteBuffer.allocate(42); - asyncChannel.read(buf, 0l, "bam", new CompletionHandler<Integer, String>() { + asyncChannel.read(buf, 0l, "bam", new CompletionHandler<>() { @Override public void completed(Integer r, String a) { @@ -261,16 +234,14 @@ public void testClosed() throws Throwable { public class WriteTest { @Test - public void testSuccess() throws IOException, InterruptedException, ExecutionException { - Mockito.when(channel.write(Mockito.any(), Mockito.anyLong())).thenAnswer(new Answer<Integer>() { - @Override - public Integer answer(InvocationOnMock invocation) throws Throwable { - ByteBuffer dst = invocation.getArgument(0); - Thread.sleep(100); - int read = dst.remaining(); - dst.position(dst.position() + read); - return read; - } + public void testSuccess() throws IOException, InterruptedException { + Mockito.when(channel.isOpen()).thenReturn(true); + Mockito.when(channel.write(Mockito.any(), Mockito.anyLong())).thenAnswer(invocation -> { + ByteBuffer dst = invocation.getArgument(0); + Thread.sleep(100); + int read = dst.remaining(); + dst.position(dst.position() + read); + return read; }); CountDownLatch cdl = new CountDownLatch(1); @@ -278,7 +249,7 @@ public Integer answer(InvocationOnMock invocation) throws Throwable { AtomicReference<String> attachment = new AtomicReference<>(); AtomicReference<Throwable> exception = new AtomicReference<>(); ByteBuffer buf = ByteBuffer.allocate(42); - asyncChannel.write(buf, 0l, "bam", new CompletionHandler<Integer, String>() { + asyncChannel.write(buf, 0l, "bam", new CompletionHandler<>() { @Override public void completed(Integer r, String a) { diff --git a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java index c49de017..a60e65af 100644 --- a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java @@ -9,10 +9,8 @@ import javax.inject.Provider; import java.io.IOException; -import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; -import java.nio.channels.spi.AbstractInterruptibleChannel; import java.nio.charset.StandardCharsets; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Path; @@ -40,9 +38,6 @@ public void setup() throws IOException, ReflectiveOperationException { Mockito.when(openCryptoFileComponentBuilder.build()).thenReturn(subComponent); Mockito.when(file.newFileChannel(Mockito.any())).thenReturn(ciphertextFileChannel); - Field closeLockField = AbstractInterruptibleChannel.class.getDeclaredField("closeLock"); - closeLockField.setAccessible(true); - closeLockField.set(ciphertextFileChannel, new Object()); inTest = new OpenCryptoFiles(openCryptoFileComponentBuilderProvider); } diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 00000000..ca6ee9ce --- /dev/null +++ b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file From a041c5e6480611e1e4d21e9a88444507467cde97 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Wed, 25 Nov 2020 14:31:26 +0100 Subject: [PATCH 05/70] * Read `vault.cryptomator` instead of `masterkey.cryptomator` * Remove methods from CryptoFileSystemProvider that deal with password handling or key derivation (will be added to CryptoLib) * Added generic `KeyLoader` interface to allow usage of keys from other sources than just password-based * Added `VaultConfiguration` to deal with `vault.cryptomator` files Related to #7, #95 and #94 --- .../cryptofs/CryptoFileSystemComponent.java | 13 +- .../cryptofs/CryptoFileSystemModule.java | 17 -- .../cryptofs/CryptoFileSystemProperties.java | 200 +++++----------- .../cryptofs/CryptoFileSystemProvider.java | 213 +++--------------- .../CryptoFileSystemProviderComponent.java | 8 - .../CryptoFileSystemProviderModule.java | 15 +- .../cryptofs/CryptoFileSystems.java | 72 ++++-- ...leSystemInitializationFailedException.java | 14 ++ .../org/cryptomator/cryptofs/KeyLoader.java | 17 ++ .../cryptofs/KeyLoadingFailedException.java | 15 ++ .../cryptomator/cryptofs/VaultCipherMode.java | 33 +++ .../org/cryptomator/cryptofs/VaultConfig.java | 188 ++++++++++++++++ .../cryptofs/VaultConfigLoadException.java | 12 + .../cryptofs/VaultKeyInvalidException.java | 12 + .../VaultVersionMismatchException.java | 9 + .../cryptofs/common/Constants.java | 3 +- .../common/FileSystemCapabilityChecker.java | 9 +- .../migration/v8/Version8Migrator.java | 4 +- ...toFileChannelWriteReadIntegrationTest.java | 14 +- .../CryptoFileSystemPropertiesTest.java | 107 ++------- ...yptoFileSystemProviderIntegrationTest.java | 103 +++------ .../CryptoFileSystemProviderTest.java | 106 +-------- .../cryptofs/CryptoFileSystemUriTest.java | 3 +- .../cryptofs/CryptoFileSystemsTest.java | 90 ++++++-- ...ptyCiphertextDirectoryIntegrationTest.java | 3 +- .../cryptofs/ReadmeCodeSamplesTest.java | 6 +- .../RealFileSystemIntegrationTest.java | 3 +- .../cryptomator/cryptofs/VaultConfigTest.java | 105 +++++++++ ...iteFileWhileReadonlyChannelIsOpenTest.java | 3 +- .../attr/FileAttributeIntegrationTest.java | 3 +- .../migration/v8/Version8MigratorTest.java | 2 +- 31 files changed, 744 insertions(+), 658 deletions(-) create mode 100644 src/main/java/org/cryptomator/cryptofs/FileSystemInitializationFailedException.java create mode 100644 src/main/java/org/cryptomator/cryptofs/KeyLoader.java create mode 100644 src/main/java/org/cryptomator/cryptofs/KeyLoadingFailedException.java create mode 100644 src/main/java/org/cryptomator/cryptofs/VaultCipherMode.java create mode 100644 src/main/java/org/cryptomator/cryptofs/VaultConfig.java create mode 100644 src/main/java/org/cryptomator/cryptofs/VaultConfigLoadException.java create mode 100644 src/main/java/org/cryptomator/cryptofs/VaultKeyInvalidException.java create mode 100644 src/main/java/org/cryptomator/cryptofs/VaultVersionMismatchException.java create mode 100644 src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemComponent.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemComponent.java index 28ab66f4..da268113 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemComponent.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemComponent.java @@ -1,14 +1,11 @@ package org.cryptomator.cryptofs; +import com.auth0.jwt.interfaces.DecodedJWT; import dagger.BindsInstance; import dagger.Subcomponent; -import org.cryptomator.cryptofs.attr.AttributeViewComponent; -import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; -import org.cryptomator.cryptofs.dir.DirectoryStreamComponent; -import org.cryptomator.cryptofs.fh.OpenCryptoFileComponent; +import org.cryptomator.cryptolib.api.Cryptor; import java.nio.file.Path; -import java.util.Set; @CryptoFileSystemScoped @Subcomponent(modules = {CryptoFileSystemModule.class}) @@ -19,6 +16,12 @@ public interface CryptoFileSystemComponent { @Subcomponent.Builder interface Builder { + @BindsInstance + Builder cryptor(Cryptor cryptor); + + @BindsInstance + Builder vaultConfig(VaultConfig vaultConfig); + @BindsInstance Builder provider(CryptoFileSystemProvider provider); diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java index 2ccb7f48..0276e590 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java @@ -31,23 +31,6 @@ class CryptoFileSystemModule { private static final Logger LOG = LoggerFactory.getLogger(CryptoFileSystemModule.class); - @Provides - @CryptoFileSystemScoped - public Cryptor provideCryptor(CryptorProvider cryptorProvider, @PathToVault Path pathToVault, CryptoFileSystemProperties properties, ReadonlyFlag readonlyFlag) { - try { - Path masterKeyPath = pathToVault.resolve(properties.masterkeyFilename()); - assert Files.exists(masterKeyPath); // since 1.3.0 a file system can only be created for existing vaults. initialization is done before. - byte[] keyFileContents = Files.readAllBytes(masterKeyPath); - Cryptor cryptor = cryptorProvider.createFromKeyFile(KeyFile.parse(keyFileContents), properties.passphrase(), properties.pepper(), Constants.VAULT_VERSION); - if (!readonlyFlag.isSet()) { - MasterkeyBackupHelper.attemptMasterKeyBackup(masterKeyPath); - } - return cryptor; - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - @Provides @CryptoFileSystemScoped public Optional<FileStore> provideNativeFileStore(@PathToVault Path pathToVault) { diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index a9efb4fa..b24475e5 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -8,24 +8,20 @@ *******************************************************************************/ package org.cryptomator.cryptofs; -import static java.util.Arrays.asList; -import static java.util.Collections.unmodifiableSet; +import com.google.common.base.Strings; +import org.cryptomator.cryptofs.common.Constants; import java.net.URI; import java.nio.file.FileSystems; import java.nio.file.Path; -import java.text.Normalizer; -import java.text.Normalizer.Form; 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; -import com.google.common.base.Strings; -import org.cryptomator.cryptofs.common.Constants; +import static java.util.Arrays.asList; /** * Properties to pass to @@ -33,16 +29,11 @@ * <li>{@link FileSystems#newFileSystem(URI, Map)} or * <li>{@link CryptoFileSystemProvider#newFileSystem(Path, CryptoFileSystemProperties)}. * </ul> - * + * * @author Markus Kreusch */ public class CryptoFileSystemProperties extends AbstractMap<String, Object> { - /** - * Key identifying the passphrase for an encrypted vault. - */ - public static final String PROPERTY_PASSPHRASE = "passphrase"; - /** * Maximum ciphertext path length. * @@ -62,13 +53,13 @@ public class CryptoFileSystemProperties extends AbstractMap<String, Object> { static final int DEFAULT_MAX_NAME_LENGTH = Constants.MAX_CIPHERTEXT_NAME_LENGTH; /** - * Key identifying the pepper used during key derivation. - * - * @since 1.3.2 + * Key identifying the key loader used during initialization. + * + * @since 2.0.0 */ - public static final String PROPERTY_PEPPER = "pepper"; + public static final String PROPERTY_KEYLOADER = "keyLoader"; - static final byte[] DEFAULT_PEPPER = new byte[0]; + static final KeyLoader DEFAULT_KEYLOADER = null; /** @@ -82,7 +73,7 @@ public class CryptoFileSystemProperties extends AbstractMap<String, Object> { /** * Key identifying the name of the masterkey file located inside the vault directory. - * + * * @since 1.1.0 */ public static final String PROPERTY_MASTERKEY_FILENAME = "masterkeyFilename"; @@ -91,70 +82,44 @@ public class CryptoFileSystemProperties extends AbstractMap<String, Object> { /** * Key identifying the filesystem flags. - * + * * @since 1.3.0 */ public static final String PROPERTY_FILESYSTEM_FLAGS = "flags"; - static final Set<FileSystemFlags> DEFAULT_FILESYSTEM_FLAGS = unmodifiableSet(EnumSet.of(FileSystemFlags.MIGRATE_IMPLICITLY, FileSystemFlags.INIT_IMPLICITLY)); + static final Set<FileSystemFlags> DEFAULT_FILESYSTEM_FLAGS = EnumSet.noneOf(FileSystemFlags.class); public enum FileSystemFlags { /** * If present, the vault is opened in read-only mode. - * <p> - * This flag can not be set together with {@link #INIT_IMPLICITLY} or {@link #MIGRATE_IMPLICITLY}. */ READONLY, - /** - * If present, the vault gets automatically migrated during file system creation, which might become significantly slower. - * If absent, a {@link FileSystemNeedsMigrationException} will get thrown during the attempt to open a vault that needs migration. - * <p> - * This flag can not be set together with {@link #READONLY}. - * - * @since 1.4.0 - */ - MIGRATE_IMPLICITLY, - - /** - * If present, the vault structure will implicitly get initialized upon filesystem creation. - * <p> - * This flag can not be set together with {@link #READONLY}. - * - * @deprecated Will get removed in version 2.0.0. Use {@link CryptoFileSystemProvider#initialize(Path, String, CharSequence)} explicitly. - */ - @Deprecated INIT_IMPLICITLY, - /** * If present, the maximum ciphertext path length (beginning from the root of the vault directory). * <p> * If exceeding the limit during a file operation, an exception is thrown. - * + * * @since 1.9.8 */ MAX_PATH_LENGTH, - }; + } private final Set<Entry<String, Object>> entries; private CryptoFileSystemProperties(Builder builder) { - this.entries = unmodifiableSet(new HashSet<>(asList( // - entry(PROPERTY_PASSPHRASE, builder.passphrase), // - entry(PROPERTY_PEPPER, builder.pepper), // - entry(PROPERTY_FILESYSTEM_FLAGS, builder.flags), // - entry(PROPERTY_VAULTCONFIG_FILENAME, builder.vaultConfigFilename), // - entry(PROPERTY_MASTERKEY_FILENAME, builder.masterkeyFilename), // - entry(PROPERTY_MAX_PATH_LENGTH, builder.maxPathLength), // - entry(PROPERTY_MAX_NAME_LENGTH, builder.maxNameLength) // - ))); - } - - CharSequence passphrase() { - return (CharSequence) get(PROPERTY_PASSPHRASE); + this.entries = Set.of( // + 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) // + ); } - byte[] pepper() { - return (byte[]) get(PROPERTY_PEPPER); + KeyLoader keyLoader() { + return (KeyLoader) get(PROPERTY_KEYLOADER); } @SuppressWarnings("unchecked") @@ -166,14 +131,6 @@ public boolean readonly() { return flags().contains(FileSystemFlags.READONLY); } - boolean migrateImplicitly() { - return flags().contains(FileSystemFlags.MIGRATE_IMPLICITLY); - } - - boolean initializeImplicitly() { - return flags().contains(FileSystemFlags.INIT_IMPLICITLY); - } - String vaultConfigFilename() { return (String) get(PROPERTY_VAULTCONFIG_FILENAME); } @@ -185,7 +142,7 @@ String masterkeyFilename() { int maxPathLength() { return (int) get(PROPERTY_MAX_PATH_LENGTH); } - + int maxNameLength() { return (int) get(PROPERTY_MAX_NAME_LENGTH); } @@ -195,49 +152,18 @@ public Set<Entry<String, Object>> entrySet() { return entries; } - private static Entry<String, Object> entry(String key, Object value) { - return new Entry<String, Object>() { - @Override - public String getKey() { - return key; - } - - @Override - public Object getValue() { - return value; - } - - @Override - public Object setValue(Object value) { - throw new UnsupportedOperationException(); - } - }; - } - /** * Starts construction of {@code CryptoFileSystemProperties} - * + * * @return a {@link Builder} which can be used to construct {@code CryptoFileSystemProperties} */ public static Builder cryptoFileSystemProperties() { return new Builder(); } - /** - * Starts construction of {@code CryptoFileSystemProperties}. - * Convenience function for <code>cryptoFileSystemProperties().withPassphrase(passphrase)</code>. - * - * @param passphrase the passphrase to use - * @return a {@link Builder} which can be used to construct {@code CryptoFileSystemProperties} - * @since 1.4.0 - */ - public static Builder withPassphrase(CharSequence passphrase) { - return new Builder().withPassphrase(passphrase); - } - /** * Starts construction of {@code CryptoFileSystemProperties} - * + * * @param properties a {@link Map} containing properties used to initialize the builder * @return a {@link Builder} which can be used to construct {@code CryptoFileSystemProperties} and has been initialized with the values from properties */ @@ -247,7 +173,7 @@ public static Builder cryptoFileSystemPropertiesFrom(Map<String, ?> properties) /** * Constructs {@code CryptoFileSystemProperties} from a {@link Map}. - * + * * @param properties the {@code Map} to convert * @return the passed in {@code Map} if already of type {@code CryptoFileSystemProperties} or a new {@code CryptoFileSystemProperties} instance holding the values from the {@code Map} * @throws IllegalArgumentException if a value in the {@code Map} does not have the expected type or if a required value is missing @@ -269,8 +195,7 @@ public static CryptoFileSystemProperties wrap(Map<String, ?> properties) { */ public static class Builder { - private CharSequence passphrase; - public byte[] pepper = DEFAULT_PEPPER; + private KeyLoader keyLoader = DEFAULT_KEYLOADER; private final Set<FileSystemFlags> flags = EnumSet.copyOf(DEFAULT_FILESYSTEM_FLAGS); private String vaultConfigFilename = DEFAULT_VAULTCONFIG_FILENAME; private String masterkeyFilename = DEFAULT_MASTERKEY_FILENAME; @@ -281,8 +206,8 @@ private Builder() { } private Builder(Map<String, ?> properties) { - checkedSet(CharSequence.class, PROPERTY_PASSPHRASE, properties, this::withPassphrase); - checkedSet(byte[].class, PROPERTY_PEPPER, properties, this::withPepper); + checkedSet(KeyLoader.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); @@ -300,16 +225,6 @@ private <T> void checkedSet(Class<T> type, String key, Map<String, ?> properties } } - /** - * Sets the passphrase to use for a CryptoFileSystem. - * - * @param passphrase the passphrase to use - * @return this - */ - public Builder withPassphrase(CharSequence passphrase) { - this.passphrase = Normalizer.normalize(passphrase, Form.NFC); - return this; - } /** * Sets the maximum ciphertext path length for a CryptoFileSystem. @@ -336,20 +251,20 @@ public Builder withMaxNameLength(int maxNameLength) { } /** - * Sets the pepper for a CryptoFileSystem. - * - * @param pepper A pepper used during key derivation + * Sets the keyLoader for a CryptoFileSystem. + * + * @param keyLoader A keyLoader used during initialization * @return this - * @since 1.3.2 + * @since 2.0.0 */ - public Builder withPepper(byte[] pepper) { - this.pepper = pepper; + public Builder withKeyLoader(KeyLoader keyLoader) { + this.keyLoader = keyLoader; return this; } /** * Sets the flags for a CryptoFileSystem. - * + * * @param flags File system flags * @return this * @since 1.3.1 @@ -360,46 +275,45 @@ public Builder withFlags(FileSystemFlags... flags) { /** * Sets the flags for a CryptoFileSystem. - * + * * @param flags collection of file system flags * @return this * @since 1.3.0 */ public Builder withFlags(Collection<FileSystemFlags> flags) { - validate(flags); this.flags.clear(); this.flags.addAll(flags); return this; } - private void validate(Collection<FileSystemFlags> flags) { - if (flags.contains(FileSystemFlags.READONLY)) { - if (flags.contains(FileSystemFlags.INIT_IMPLICITLY)) { - throw new IllegalStateException("Can not set flag INIT_IMPLICITLY in conjunction with flag READONLY."); - } - if (flags.contains(FileSystemFlags.MIGRATE_IMPLICITLY)) { - throw new IllegalStateException("Can not set flag MIGRATE_IMPLICITLY in conjunction with flag READONLY."); - } - } - } - /** * Sets the readonly flag for a CryptoFileSystem. - * + * * @return this * @deprecated Will be removed in 2.0.0. Use {@link #withFlags(FileSystemFlags...) withFlags(FileSystemFlags.READONLY)} */ @Deprecated public Builder withReadonlyFlag() { flags.add(FileSystemFlags.READONLY); - flags.remove(FileSystemFlags.INIT_IMPLICITLY); - flags.remove(FileSystemFlags.MIGRATE_IMPLICITLY); + return this; + } + + + /** + * Sets the name of the vault config file located inside the vault directory. + * + * @param vaultConfigFilename the filename of the jwt file containing the vault configuration + * @return this + * @since 2.0.0 + */ + public Builder withVaultConfigFilename(String vaultConfigFilename) { + this.vaultConfigFilename = vaultConfigFilename; return this; } /** * Sets the name of the masterkey file located inside the vault directory. - * + * * @param masterkeyFilename the filename of the json file containing configuration to decrypt the masterkey * @return this * @since 1.1.0 @@ -411,7 +325,7 @@ public Builder withMasterkeyFilename(String masterkeyFilename) { /** * Validates the values and creates new {@link CryptoFileSystemProperties}. - * + * * @return a new {@code CryptoFileSystemProperties} with the values from this builder * @throws IllegalStateException if a required value was not set on this {@code Builder} */ @@ -421,8 +335,8 @@ public CryptoFileSystemProperties build() { } private void validate() { - if (passphrase == null) { - throw new IllegalStateException("passphrase is required"); + if (keyLoader == null) { + throw new IllegalStateException("keyloader is required"); } if (Strings.nullToEmpty(masterkeyFilename).trim().isEmpty()) { throw new IllegalStateException("masterkeyFilename is required"); diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index d25102c3..aff67f11 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -11,19 +11,13 @@ import org.cryptomator.cryptofs.ch.AsyncDelegatingFileChannel; import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; -import org.cryptomator.cryptofs.common.MasterkeyBackupHelper; -import org.cryptomator.cryptofs.migration.Migrators; -import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener.ContinuationResult; -import org.cryptomator.cryptolib.Cryptors; -import org.cryptomator.cryptolib.api.Cryptor; -import org.cryptomator.cryptolib.api.CryptorProvider; -import org.cryptomator.cryptolib.api.InvalidPassphraseException; import java.io.IOException; import java.net.URI; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.FileChannel; import java.nio.channels.SeekableByteChannel; +import java.nio.charset.StandardCharsets; import java.nio.file.AccessMode; import java.nio.file.CopyOption; import java.nio.file.DirectoryStream; @@ -33,7 +27,6 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.LinkOption; -import java.nio.file.NoSuchFileException; import java.nio.file.NotDirectoryException; import java.nio.file.OpenOption; import java.nio.file.Path; @@ -43,16 +36,11 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.nio.file.spi.FileSystemProvider; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.text.Normalizer; -import java.text.Normalizer.Form; +import java.util.Arrays; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; -import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.nio.file.StandardOpenOption.CREATE_NEW; import static java.nio.file.StandardOpenOption.WRITE; @@ -92,14 +80,12 @@ */ public class CryptoFileSystemProvider extends FileSystemProvider { - private static final CryptorProvider CRYPTOR_PROVIDER = Cryptors.version1(strongSecureRandom()); - private final CryptoFileSystems fileSystems; private final MoveOperation moveOperation; private final CopyOperation copyOperation; public CryptoFileSystemProvider() { - this(DaggerCryptoFileSystemProviderComponent.builder().cryptorProvider(CRYPTOR_PROVIDER).build()); + this(DaggerCryptoFileSystemProviderComponent.create()); } /** @@ -111,23 +97,15 @@ public CryptoFileSystemProvider() { this.copyOperation = component.copyOperation(); } - private static SecureRandom strongSecureRandom() { - try { - return SecureRandom.getInstanceStrong(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("A strong algorithm must exist in every Java platform.", e); - } - } - /** * Typesafe alternative to {@link FileSystems#newFileSystem(URI, Map)}. Default way to retrieve a CryptoFS instance. * * @param pathToVault Path to this vault's storage location - * @param properties Parameters used during initialization of the file system + * @param properties Parameters used during initialization of the file system * @return a new file system - * @throws FileSystemNeedsMigrationException if the vault format needs to get updated and <code>properties</code> did not contain a flag for implicit migration. + * @throws FileSystemNeedsMigrationException if the vault format needs to get updated and <code>properties</code> did not contain a flag for implicit migration. * @throws FileSystemCapabilityChecker.MissingCapabilityException If the underlying filesystem lacks features required to store a vault - * @throws IOException if an I/O error occurs creating the file system + * @throws IOException if an I/O error occurs creating the file system */ public static CryptoFileSystem newFileSystem(Path pathToVault, CryptoFileSystemProperties properties) throws FileSystemNeedsMigrationException, IOException { URI uri = CryptoFileSystemUri.create(pathToVault.toAbsolutePath()); @@ -137,143 +115,47 @@ public static CryptoFileSystem newFileSystem(Path pathToVault, CryptoFileSystemP /** * Creates a new vault at the given directory path. * - * @param pathToVault Path to an existing directory - * @param masterkeyFilename Name of the masterkey file - * @param passphrase Passphrase that should be used to unlock the vault - * @throws NotDirectoryException If the given path is not an existing directory. - * @throws FileSystemCapabilityChecker.MissingCapabilityException If the underlying filesystem lacks features required to store a vault - * @throws IOException If the vault structure could not be initialized due to I/O errors - * @since 1.3.0 - */ - public static void initialize(Path pathToVault, String masterkeyFilename, CharSequence passphrase) throws NotDirectoryException, IOException { - initialize(pathToVault, masterkeyFilename, new byte[0], passphrase); - } - - /** - * Creates a new vault at the given directory path. - * - * @param pathToVault Path to an existing directory - * @param masterkeyFilename Name of the masterkey file - * @param pepper Application-specific pepper used during key derivation - * @param passphrase Passphrase that should be used to unlock the vault - * @throws NotDirectoryException If the given path is not an existing directory. + * @param pathToVault Path to an existing directory + * @param vaultConfigFilename Name of the vault config file + * @param keyId The ID of the key to associate with this vault + * @param keyLoader A key loader providing the masterkey for this new vault + * @throws NotDirectoryException If the given path is not an existing directory. * @throws FileSystemCapabilityChecker.MissingCapabilityException If the underlying filesystem lacks features required to store a vault - * @throws IOException If the vault structure could not be initialized due to I/O errors - * @since 1.3.2 + * @throws IOException If the vault structure could not be initialized due to I/O errors + * @since 2.0.0 */ - public static void initialize(Path pathToVault, String masterkeyFilename, byte[] pepper, CharSequence passphrase) throws NotDirectoryException, IOException { + public static void initialize(Path pathToVault, String vaultConfigFilename, String keyId, KeyLoader keyLoader) throws NotDirectoryException, IOException { if (!Files.isDirectory(pathToVault)) { throw new NotDirectoryException(pathToVault.toString()); } - try (Cryptor cryptor = CRYPTOR_PROVIDER.createNew()) { - // save masterkey file: - Path masterKeyPath = pathToVault.resolve(masterkeyFilename); - byte[] keyFileContents = cryptor.writeKeysToMasterkeyFile(Normalizer.normalize(passphrase, Form.NFC), pepper, Constants.VAULT_VERSION).serialize(); - Files.write(masterKeyPath, keyFileContents, CREATE_NEW, WRITE); - // create "d/RO/OTDIRECTORY": - String rootDirHash = cryptor.fileNameCryptor().hashDirectoryId(Constants.ROOT_DIR_ID); - Path rootDirPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(rootDirHash.substring(0, 2)).resolve(rootDirHash.substring(2)); - Files.createDirectories(rootDirPath); + byte[] rawKey = keyLoader.loadKey(keyId); + try { + // save vault config: + Path vaultConfigPath = pathToVault.resolve(vaultConfigFilename); + var config = VaultConfig.createNew().cipherMode(VaultCipherMode.SIV_CTRMAC).maxFilenameLength(Constants.MAX_CIPHERTEXT_NAME_LENGTH).build(); + var token = config.toToken(keyId, rawKey); + Files.writeString(vaultConfigPath, token, StandardCharsets.US_ASCII, WRITE, CREATE_NEW); + // create "d" dir: + Path dataDirPath = pathToVault.resolve(Constants.DATA_DIR_NAME); + Files.createDirectories(dataDirPath); + } finally { + Arrays.fill(rawKey, (byte) 0x00); } - assert containsVault(pathToVault, masterkeyFilename); + assert containsVault(pathToVault, vaultConfigFilename); } /** * Checks if the folder represented by the given path exists and contains a valid vault structure. * - * @param pathToVault A directory path - * @param masterkeyFilename Name of the masterkey file + * @param pathToVault A directory path + * @param vaultConfigFilename Name of the vault config file * @return <code>true</code> if the directory seems to contain a vault. - * @since 1.1.0 + * @since 2.0.0 */ - public static boolean containsVault(Path pathToVault, String masterkeyFilename) { - Path masterKeyPath = pathToVault.resolve(masterkeyFilename); + public static boolean containsVault(Path pathToVault, String vaultConfigFilename) { + Path vaultConfigPath = pathToVault.resolve(vaultConfigFilename); Path dataDirPath = pathToVault.resolve(Constants.DATA_DIR_NAME); - return Files.isReadable(masterKeyPath) && Files.isDirectory(dataDirPath); - } - - /** - * Changes the passphrase of a vault at the given path. - * - * @param pathToVault Vault directory - * @param masterkeyFilename Name of the masterkey file - * @param oldPassphrase Current passphrase - * @param newPassphrase Future passphrase - * @throws InvalidPassphraseException If <code>oldPassphrase</code> can not be used to unlock the vault. - * @throws FileSystemNeedsMigrationException if the vault format needs to get updated. - * @throws IOException If the masterkey could not be read or written. - * @see #changePassphrase(Path, String, byte[], CharSequence, CharSequence) - * @since 1.1.0 - */ - public static void changePassphrase(Path pathToVault, String masterkeyFilename, CharSequence oldPassphrase, CharSequence newPassphrase) - throws InvalidPassphraseException, FileSystemNeedsMigrationException, IOException { - changePassphrase(pathToVault, masterkeyFilename, new byte[0], oldPassphrase, newPassphrase); - } - - /** - * Changes the passphrase of a vault at the given path. - * - * @param pathToVault Vault directory - * @param masterkeyFilename Name of the masterkey file - * @param pepper An application-specific pepper added to the salt during key-derivation (if applicable) - * @param oldPassphrase Current passphrase - * @param newPassphrase Future passphrase - * @throws InvalidPassphraseException If <code>oldPassphrase</code> can not be used to unlock the vault. - * @throws FileSystemNeedsMigrationException if the vault format needs to get updated. - * @throws IOException If the masterkey could not be read or written. - * @since 1.4.0 - */ - public static void changePassphrase(Path pathToVault, String masterkeyFilename, byte[] pepper, CharSequence oldPassphrase, CharSequence newPassphrase) - throws InvalidPassphraseException, FileSystemNeedsMigrationException, IOException { - if (Migrators.get().needsMigration(pathToVault, masterkeyFilename)) { - throw new FileSystemNeedsMigrationException(pathToVault); - } - String normalizedOldPassphrase = Normalizer.normalize(oldPassphrase, Form.NFC); - String normalizedNewPassphrase = Normalizer.normalize(newPassphrase, Form.NFC); - Path masterKeyPath = pathToVault.resolve(masterkeyFilename); - byte[] oldMasterkeyBytes = Files.readAllBytes(masterKeyPath); - byte[] newMasterkeyBytes = Cryptors.changePassphrase(CRYPTOR_PROVIDER, oldMasterkeyBytes, pepper, normalizedOldPassphrase, normalizedNewPassphrase); - Path backupKeyPath = pathToVault.resolve(masterkeyFilename + MasterkeyBackupHelper.generateFileIdSuffix(oldMasterkeyBytes) + Constants.MASTERKEY_BACKUP_SUFFIX); - Files.move(masterKeyPath, backupKeyPath, REPLACE_EXISTING, ATOMIC_MOVE); - Files.write(masterKeyPath, newMasterkeyBytes, CREATE_NEW, WRITE); - } - - /** - * Exports the raw key for backup purposes or external key management. - * - * @param pathToVault Vault directory - * @param masterkeyFilename Name of the masterkey file - * @param pepper An application-specific pepper added to the salt during key-derivation (if applicable) - * @param passphrase Current passphrase - * @return A 64 byte array consisting of 32 byte aes key and 32 byte mac key - * @since 1.9.0 - */ - public static byte[] exportRawKey(Path pathToVault, String masterkeyFilename, byte[] pepper, CharSequence passphrase) throws InvalidPassphraseException, IOException { - String normalizedPassphrase = Normalizer.normalize(passphrase, Form.NFC); - Path masterKeyPath = pathToVault.resolve(masterkeyFilename); - byte[] masterKeyBytes = Files.readAllBytes(masterKeyPath); - return Cryptors.exportRawKey(CRYPTOR_PROVIDER, masterKeyBytes, pepper, normalizedPassphrase); - } - - /** - * Imports a raw key from backup or external key management. - * - * @param pathToVault Vault directory - * @param masterkeyFilename Name of the masterkey file - * @param pepper An application-specific pepper added to the salt during key-derivation (if applicable) - * @param passphrase Future passphrase - * @since 1.9.0 - */ - public static void restoreRawKey(Path pathToVault, String masterkeyFilename, byte[] rawKey, byte[] pepper, CharSequence passphrase) throws InvalidPassphraseException, IOException { - String normalizedPassphrase = Normalizer.normalize(passphrase, Form.NFC); - byte[] masterKeyBytes = Cryptors.restoreRawKey(CRYPTOR_PROVIDER, rawKey, pepper, normalizedPassphrase, Constants.VAULT_VERSION); - Path masterKeyPath = pathToVault.resolve(masterkeyFilename); - if (Files.exists(masterKeyPath)) { - byte[] oldMasterkeyBytes = Files.readAllBytes(masterKeyPath); - Path backupKeyPath = pathToVault.resolve(masterkeyFilename + MasterkeyBackupHelper.generateFileIdSuffix(oldMasterkeyBytes) + Constants.MASTERKEY_BACKUP_SUFFIX); - Files.move(masterKeyPath, backupKeyPath, REPLACE_EXISTING, ATOMIC_MOVE); - } - Files.write(masterKeyPath, masterKeyBytes, CREATE_NEW, WRITE); + return Files.isReadable(vaultConfigPath) && Files.isDirectory(dataDirPath); } /** @@ -293,36 +175,9 @@ public String getScheme() { public CryptoFileSystem newFileSystem(URI uri, Map<String, ?> rawProperties) throws IOException { CryptoFileSystemUri parsedUri = CryptoFileSystemUri.parse(uri); CryptoFileSystemProperties properties = CryptoFileSystemProperties.wrap(rawProperties); - - // TODO remove implicit initialization in 2.0.0 - initializeFileSystemIfRequired(parsedUri, properties); - migrateFileSystemIfRequired(parsedUri, properties); - return fileSystems.create(this, parsedUri.pathToVault(), properties); } - @Deprecated - private void migrateFileSystemIfRequired(CryptoFileSystemUri parsedUri, CryptoFileSystemProperties properties) throws IOException, FileSystemNeedsMigrationException { - if (Migrators.get().needsMigration(parsedUri.pathToVault(), properties.masterkeyFilename())) { - if (properties.migrateImplicitly()) { - Migrators.get().migrate(parsedUri.pathToVault(), properties.vaultConfigFilename(), properties.masterkeyFilename(), properties.passphrase(), (state, progress) -> {}, (event) -> ContinuationResult.CANCEL); - } else { - throw new FileSystemNeedsMigrationException(parsedUri.pathToVault()); - } - } - } - - @Deprecated - private void initializeFileSystemIfRequired(CryptoFileSystemUri parsedUri, CryptoFileSystemProperties properties) throws NotDirectoryException, IOException, NoSuchFileException { - if (!CryptoFileSystemProvider.containsVault(parsedUri.pathToVault(), properties.masterkeyFilename())) { - if (properties.initializeImplicitly()) { - CryptoFileSystemProvider.initialize(parsedUri.pathToVault(), properties.masterkeyFilename(), properties.passphrase()); - } else { - throw new NoSuchFileException(parsedUri.pathToVault().toString(), null, "Vault not initialized."); - } - } - } - @Override public CryptoFileSystem getFileSystem(URI uri) { CryptoFileSystemUri parsedUri = CryptoFileSystemUri.parse(uri); @@ -394,7 +249,7 @@ public boolean isHidden(Path cleartextPath) throws IOException { } @Override - public FileStore getFileStore(Path cleartextPath) throws IOException { + public FileStore getFileStore(Path cleartextPath) { return fileSystem(cleartextPath).getFileStore(); } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderComponent.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderComponent.java index ce52f6e6..dfd2e4bc 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderComponent.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderComponent.java @@ -16,12 +16,4 @@ interface CryptoFileSystemProviderComponent { CopyOperation copyOperation(); - @Component.Builder - interface Builder { - @BindsInstance - Builder cryptorProvider(CryptorProvider cryptorProvider); - - CryptoFileSystemProviderComponent build(); - } - } \ No newline at end of file diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderModule.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderModule.java index 9bd7b33e..17c13a17 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderModule.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderModule.java @@ -2,17 +2,22 @@ import dagger.Module; import dagger.Provides; -import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; import javax.inject.Singleton; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; @Module(subcomponents = {CryptoFileSystemComponent.class}) public class CryptoFileSystemProviderModule { - + @Provides @Singleton - public FileSystemCapabilityChecker provideFileSystemCapabilityChecker() { - return new FileSystemCapabilityChecker(); + public SecureRandom provideCSPRNG() { + try { + return SecureRandom.getInstanceStrong(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("A strong algorithm must exist in every Java platform.", e); + } } - + } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java index d2332dab..085ba9c3 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java @@ -1,16 +1,22 @@ package org.cryptomator.cryptofs; +import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; +import org.cryptomator.cryptolib.api.Cryptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Singleton; import java.io.IOException; -import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.nio.file.FileSystemAlreadyExistsException; import java.nio.file.FileSystemNotFoundException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.security.SecureRandom; +import java.util.Arrays; import java.util.EnumSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -26,31 +32,69 @@ class CryptoFileSystems { private final ConcurrentMap<Path, CryptoFileSystemImpl> fileSystems = new ConcurrentHashMap<>(); private final CryptoFileSystemComponent.Builder cryptoFileSystemComponentBuilder; // sharing reusable builder via synchronized private final FileSystemCapabilityChecker capabilityChecker; + private final SecureRandom csprng; @Inject - public CryptoFileSystems(CryptoFileSystemComponent.Builder cryptoFileSystemComponentBuilder, FileSystemCapabilityChecker capabilityChecker) { + public CryptoFileSystems(CryptoFileSystemComponent.Builder cryptoFileSystemComponentBuilder, FileSystemCapabilityChecker capabilityChecker, SecureRandom csprng) { this.cryptoFileSystemComponentBuilder = cryptoFileSystemComponentBuilder; this.capabilityChecker = capabilityChecker; + this.csprng = csprng; } - public synchronized CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathToVault, CryptoFileSystemProperties properties) throws IOException { + public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathToVault, CryptoFileSystemProperties properties) throws IOException { + Path normalizedPathToVault = pathToVault.normalize(); + var token = readVaultConfigFile(normalizedPathToVault, properties); + + var configLoader = VaultConfig.decode(token); + byte[] rawKey = properties.keyLoader().loadKey(configLoader.getKeyId()); try { - Path normalizedPathToVault = pathToVault.normalize(); - CryptoFileSystemProperties adjustedProperties = adjustForCapabilities(normalizedPathToVault, properties); + var config = configLoader.load(rawKey, Constants.VAULT_VERSION); + var adjustedProperties = adjustForCapabilities(pathToVault, properties); return fileSystems.compute(normalizedPathToVault, (key, value) -> { if (value == null) { - return cryptoFileSystemComponentBuilder // - .pathToVault(key) // - .properties(adjustedProperties) // - .provider(provider) // - .build() // - .cryptoFileSystem(); + return create(provider, normalizedPathToVault, adjustedProperties, rawKey, config); } else { throw new FileSystemAlreadyExistsException(); } }); - } catch (UncheckedIOException e) { - throw new IOException("Error during file system creation.", e); + } finally { + Arrays.fill(rawKey, (byte) 0x00); + } + } + + // synchronized access to non-threadsafe cryptoFileSystemComponentBuilder required + private synchronized CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathToVault, CryptoFileSystemProperties properties, byte[] rawKey, VaultConfig config) { + Cryptor cryptor = config.getCiphermode().getCryptorProvider(csprng).createFromRawKey(rawKey); + return cryptoFileSystemComponentBuilder // + .cryptor(cryptor) // + .vaultConfig(config) // + .pathToVault(pathToVault) // + .properties(properties) // + .provider(provider) // + .build() // + .cryptoFileSystem(); + } + + /** + * Attempts to read a vault config file + * @param pathToVault path to the vault's root + * @param properties properties used when attempting to construct a fs for this vault + * @return The contents of the file decoded in ASCII + * @throws IOException If the file could not be read + * @throws FileSystemNeedsMigrationException If the file doesn't exists, but a legacy masterkey file was found instead + */ + private String readVaultConfigFile(Path pathToVault, CryptoFileSystemProperties properties) throws IOException, FileSystemNeedsMigrationException { + Path vaultConfigFile = pathToVault.resolve(properties.vaultConfigFilename()); + try { + return Files.readString(vaultConfigFile, StandardCharsets.US_ASCII); + } catch (NoSuchFileException e) { + Path masterkeyPath = pathToVault.resolve(properties.masterkeyFilename()); + if (Files.exists(masterkeyPath)) { + LOG.warn("Failed to read {}, but found {}}", vaultConfigFile, masterkeyPath); + throw new FileSystemNeedsMigrationException(pathToVault); + } else { + throw e; + } } } @@ -83,7 +127,7 @@ public CryptoFileSystemImpl get(Path pathToVault) { Path normalizedPathToVault = pathToVault.normalize(); CryptoFileSystemImpl fs = fileSystems.get(normalizedPathToVault); if (fs == null) { - throw new FileSystemNotFoundException(format("CryptoFileSystem at %s not initialized", pathToVault)); + throw new FileSystemNotFoundException(format("CryptoFileSystem at %s not initialized", normalizedPathToVault)); } else { return fs; } diff --git a/src/main/java/org/cryptomator/cryptofs/FileSystemInitializationFailedException.java b/src/main/java/org/cryptomator/cryptofs/FileSystemInitializationFailedException.java new file mode 100644 index 00000000..442c7eb3 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/FileSystemInitializationFailedException.java @@ -0,0 +1,14 @@ +package org.cryptomator.cryptofs; + +import java.io.IOException; + +public class FileSystemInitializationFailedException extends IOException { + + public FileSystemInitializationFailedException(String message, Throwable cause) { + super(message, cause); + } + + public FileSystemInitializationFailedException(String message) { + super(message); + } +} diff --git a/src/main/java/org/cryptomator/cryptofs/KeyLoader.java b/src/main/java/org/cryptomator/cryptofs/KeyLoader.java new file mode 100644 index 00000000..9e2e5d16 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/KeyLoader.java @@ -0,0 +1,17 @@ +package org.cryptomator.cryptofs; + +@FunctionalInterface +public interface KeyLoader { + + /** + * Loads a key required to unlock a vault. + * <p> + * This might be a long-running operation, as it may require user input or expensive computations. + * + * @param keyId a string uniquely identifying the source of the key and its identity, if multiple keys can be obtained from the same source + * @return The raw key bytes. Must not be null + * @throws KeyLoadingFailedException Thrown when it is impossible to fulfill the request + */ + byte[] loadKey(String keyId) throws KeyLoadingFailedException; + +} diff --git a/src/main/java/org/cryptomator/cryptofs/KeyLoadingFailedException.java b/src/main/java/org/cryptomator/cryptofs/KeyLoadingFailedException.java new file mode 100644 index 00000000..9532ea33 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/KeyLoadingFailedException.java @@ -0,0 +1,15 @@ +package org.cryptomator.cryptofs; + +import java.io.IOException; + +/** + * Thrown by a {@link KeyLoader} when loading a key required to unlock a vault failed. + * <p> + * Possible reasons for this exception are: Unsupported key type, key for given id not found, user cancelled key entry, ... + */ +public class KeyLoadingFailedException extends FileSystemInitializationFailedException { + + public KeyLoadingFailedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/cryptomator/cryptofs/VaultCipherMode.java b/src/main/java/org/cryptomator/cryptofs/VaultCipherMode.java new file mode 100644 index 00000000..c752a00b --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/VaultCipherMode.java @@ -0,0 +1,33 @@ +package org.cryptomator.cryptofs; + +import org.cryptomator.cryptolib.Cryptors; +import org.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.cryptolib.api.CryptorProvider; + +import java.security.SecureRandom; +import java.util.function.Function; + +public enum VaultCipherMode { + /** + * AES-SIV for file name encryption + * AES-CTR + HMAC for content encryption + */ + SIV_CTRMAC(Cryptors::version1); + +// TODO enable eventually (issue 94): +// /** +// * AES-SIV for file name encryption +// * AES-GCM for content encryption +// */ +// SIV_GCM(Cryptors::version2); + + private final Function<SecureRandom, CryptorProvider> cryptorProvider; + + VaultCipherMode(Function<SecureRandom, CryptorProvider> cryptorProvider) { + this.cryptorProvider = cryptorProvider; + } + + public CryptorProvider getCryptorProvider(SecureRandom csprng) { + return cryptorProvider.apply(csprng); + } +} diff --git a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java new file mode 100644 index 00000000..8c660fd7 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java @@ -0,0 +1,188 @@ +package org.cryptomator.cryptofs; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.InvalidClaimException; +import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import org.cryptomator.cryptofs.common.Constants; + +import java.util.Arrays; +import java.util.UUID; + +/** + * Typesafe representation of vault configuration files. + * + * To prevent config tampering, such as downgrade attacks, vault configurations are cryptographically signed using HMAC-256 + * with the vault's 64 byte master key. + * + * If the signature could be successfully verified, the configuration can be assumed valid and the masterkey can be assumed + * eligible for the vault. + * + * When {@link #load(String, KeyLoader, int) loading} a vault configuration, a key must be provided and the signature is checked. + * It is impossible to create an instance of this class from an existing configuration without signature verification. + */ +public class VaultConfig { + + private static final String JSON_KEY_VAULTVERSION = "format"; + private static final String JSON_KEY_CIPHERCONFIG = "ciphermode"; + private static final String JSON_KEY_MAXFILENAMELEN = "maxFilenameLen"; + + private final String id; + private final int vaultVersion; + private final VaultCipherMode ciphermode; + private final int maxFilenameLength; + + private VaultConfig(DecodedJWT verifiedConfig) { + this.id = verifiedConfig.getId(); + this.vaultVersion = verifiedConfig.getClaim(JSON_KEY_VAULTVERSION).asInt(); + this.ciphermode = VaultCipherMode.valueOf(verifiedConfig.getClaim(JSON_KEY_CIPHERCONFIG).asString()); + this.maxFilenameLength = verifiedConfig.getClaim(JSON_KEY_MAXFILENAMELEN).asInt(); + } + + private VaultConfig(VaultConfigBuilder builder) { + this.id = builder.id; + this.vaultVersion = builder.vaultVersion; + this.ciphermode = builder.ciphermode; + this.maxFilenameLength = builder.maxFilenameLength; + } + + public String getId() { + return id; + } + + public int getVaultVersion() { + return vaultVersion; + } + + public VaultCipherMode getCiphermode() { + return ciphermode; + } + + public int getMaxFilenameLength() { + return maxFilenameLength; + } + + public String toToken(String keyId, byte[] rawKey) { + return JWT.create() // + .withKeyId(keyId) // + .withJWTId(id) // + .withClaim(JSON_KEY_VAULTVERSION, vaultVersion) // + .withClaim(JSON_KEY_CIPHERCONFIG, ciphermode.name()) // + .withClaim(JSON_KEY_MAXFILENAMELEN, maxFilenameLength) // + .sign(Algorithm.HMAC256(rawKey)); + } + + /** + * Convenience wrapper for {@link #decode(String)} and {@link VaultConfigLoader#load(byte[], int)} + * + * @param token The token + * @param keyLoader A key loader capable of providing a key for this token + * @param expectedVaultVersion The vault version this token should contain + * @return The decoded configuration + * @throws KeyLoadingFailedException If the key loader was unable to provide a key for this vault configuration + * @throws VaultConfigLoadException When loading the configuration fails (see {@link VaultConfigLoader#load(String, KeyLoader, int)} for details + */ + public static VaultConfig load(String token, KeyLoader keyLoader, int expectedVaultVersion) throws KeyLoadingFailedException, VaultConfigLoadException { + byte[] rawKey = new byte[0]; + try { + var configLoader = decode(token); + rawKey = keyLoader.loadKey(configLoader.getKeyId()); + return configLoader.load(rawKey, expectedVaultVersion); + } finally { + Arrays.fill(rawKey, (byte) 0x00); + } + } + + /** + * Decodes a vault configuration stored in JWT format to load it + * + * @param token The token + * @return A loader object that allows loading the configuration (if providing the required key) + * @throws VaultConfigLoadException When parsing the token failed + */ + public static VaultConfigLoader decode(String token) throws VaultConfigLoadException { + try { + return new VaultConfigLoader(JWT.decode(token)); + } catch (JWTDecodeException e) { + throw new VaultConfigLoadException("Failed to parse config: " + token); + } + } + + /** + * Create a new configuration object for a new vault. + * + * @return A new configuration builder + */ + public static VaultConfigBuilder createNew() { + return new VaultConfigBuilder(); + } + + public static class VaultConfigLoader { + + private final DecodedJWT unverifiedConfig; + + private VaultConfigLoader(DecodedJWT unverifiedConfig) { + this.unverifiedConfig = unverifiedConfig; + } + + /** + * @return The ID of the key required to {@link #load(byte[], int) load} this config. + */ + public String getKeyId() { + return unverifiedConfig.getKeyId(); + } + + /** + * Decodes a vault configuration stored in JWT format. + * + * @param rawKey The key matching the id in {@link #getKeyId()} + * @param expectedVaultVersion The vault version this token should contain + * @return The decoded configuration + * @throws VaultKeyInvalidException If the provided key was invalid + * @throws VaultVersionMismatchException If the token did not match the expected vault version + * @throws VaultConfigLoadException Generic parse error + */ + public VaultConfig load(byte[] rawKey, int expectedVaultVersion) throws VaultKeyInvalidException, VaultVersionMismatchException, VaultConfigLoadException { + try { + var verifier = JWT.require(Algorithm.HMAC256(rawKey)) // + .withClaim(JSON_KEY_VAULTVERSION, expectedVaultVersion) // + .build(); + var verifiedConfig = verifier.verify(unverifiedConfig); + return new VaultConfig(verifiedConfig); + } catch (SignatureVerificationException e) { + throw new VaultKeyInvalidException(); + } catch (InvalidClaimException e) { + throw new VaultVersionMismatchException("Vault config not for version " + expectedVaultVersion); + } catch (JWTVerificationException e) { + throw new VaultConfigLoadException("Failed to verify vault config: " + unverifiedConfig.getToken()); + } + } + } + + public static class VaultConfigBuilder { + + private final String id = UUID.randomUUID().toString(); + private final int vaultVersion = Constants.VAULT_VERSION; + private VaultCipherMode ciphermode; + private int maxFilenameLength; + + public VaultConfigBuilder cipherMode(VaultCipherMode ciphermode) { + this.ciphermode = ciphermode; + return this; + } + + public VaultConfigBuilder maxFilenameLength(int maxFilenameLength) { + this.maxFilenameLength = maxFilenameLength; + return this; + } + + public VaultConfig build() { + return new VaultConfig(this); + } + + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/VaultConfigLoadException.java b/src/main/java/org/cryptomator/cryptofs/VaultConfigLoadException.java new file mode 100644 index 00000000..ac0f6bf8 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/VaultConfigLoadException.java @@ -0,0 +1,12 @@ +package org.cryptomator.cryptofs; + +/** + * Failed to parse or verify vault config token. + */ +public class VaultConfigLoadException extends FileSystemInitializationFailedException { + + public VaultConfigLoadException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/VaultKeyInvalidException.java b/src/main/java/org/cryptomator/cryptofs/VaultKeyInvalidException.java new file mode 100644 index 00000000..ba6396e6 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/VaultKeyInvalidException.java @@ -0,0 +1,12 @@ +package org.cryptomator.cryptofs; + +/** + * An attempt was made to verify the signature of a vault config token using an invalid key. + */ +public class VaultKeyInvalidException extends VaultConfigLoadException { + + public VaultKeyInvalidException() { + super("Failed to verify vault config signature using the provided key."); + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/VaultVersionMismatchException.java b/src/main/java/org/cryptomator/cryptofs/VaultVersionMismatchException.java new file mode 100644 index 00000000..6c429063 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/VaultVersionMismatchException.java @@ -0,0 +1,9 @@ +package org.cryptomator.cryptofs; + +public class VaultVersionMismatchException extends VaultConfigLoadException { + + public VaultVersionMismatchException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/common/Constants.java b/src/main/java/org/cryptomator/cryptofs/common/Constants.java index 0ad683f8..84a7bff4 100644 --- a/src/main/java/org/cryptomator/cryptofs/common/Constants.java +++ b/src/main/java/org/cryptomator/cryptofs/common/Constants.java @@ -10,7 +10,7 @@ public final class Constants { - public static final int VAULT_VERSION = 7; + public static final int VAULT_VERSION = 8; public static final String MASTERKEY_BACKUP_SUFFIX = ".bkup"; public static final String DATA_DIR_NAME = "d"; public static final String ROOT_DIR_ID = ""; @@ -30,5 +30,4 @@ public final class Constants { public static final int MAX_DIR_FILE_LENGTH = 36; // UUIDv4: hex-encoded 16 byte int + 4 hyphens = 36 ASCII chars public static final String SEPARATOR = "/"; - } diff --git a/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java b/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java index 30ff3f6c..b122e4bc 100644 --- a/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java +++ b/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java @@ -7,6 +7,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.inject.Inject; +import javax.inject.Singleton; import java.io.IOException; import java.nio.file.DirectoryIteratorException; import java.nio.file.DirectoryStream; @@ -15,6 +17,7 @@ import java.nio.file.Files; import java.nio.file.Path; +@Singleton public class FileSystemCapabilityChecker { private static final Logger LOG = LoggerFactory.getLogger(FileSystemCapabilityChecker.class); @@ -35,6 +38,10 @@ public enum Capability { WRITE_ACCESS, } + @Inject + public FileSystemCapabilityChecker() { + } + /** * Checks whether the underlying filesystem has all required capabilities. * @@ -85,7 +92,7 @@ public void assertWriteAccess(Path pathToVault) throws MissingCapabilityExceptio /** * Determinse the number of chars a ciphertext filename (including its extension) is allowed to have inside a vault's <code>d/XX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY/</code> directory. - * + * * @param pathToVault Path to the vault * @return Number of chars a .c9r file is allowed to have * @throws IOException If unable to perform this check diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java index 5695e161..4f6980d7 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java @@ -25,8 +25,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.text.Normalizer; -import java.text.Normalizer.Form; import java.util.Arrays; import java.util.UUID; @@ -71,7 +69,7 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey .withKeyId("MASTERKEY_FILE") // .withClaim("format", 8) // .withClaim("ciphermode", "SIV_CTRMAC") // - .withClaim("maxFileNameLen", 220) // + .withClaim("maxFilenameLen", 220) // .sign(algorithm); Files.writeString(vaultConfigFile, config, StandardCharsets.US_ASCII, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); LOG.info("Wrote vault config to {}.", vaultConfigFile); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java index 538d488f..627e2b54 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java @@ -60,7 +60,7 @@ public class Windows { @BeforeAll public void setupClass(@TempDir Path tmpDir) throws IOException { - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(tmpDir), cryptoFileSystemProperties().withPassphrase("asd").build()); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(tmpDir), cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); } // tests https://github.com/cryptomator/cryptofs/issues/69 @@ -89,21 +89,21 @@ public void testLastModifiedIsPreservedOverSeveralOperations() throws IOExceptio try (FileChannel ch = FileChannel.open(file, CREATE_NEW, WRITE)) { t1 = Files.getLastModifiedTime(file).toInstant().truncatedTo(ChronoUnit.MILLIS); - Thread.currentThread().sleep(50); + Thread.sleep(50); ch.write(data); ch.force(true); - Thread.currentThread().sleep(50); + Thread.sleep(50); t2 = Files.getLastModifiedTime(file).toInstant().truncatedTo(ChronoUnit.MILLIS); Files.setLastModifiedTime(file, FileTime.from(t0)); ch.force(true); - Thread.currentThread().sleep(50); + Thread.sleep(50); t3 = Files.getLastModifiedTime(file).toInstant().truncatedTo(ChronoUnit.MILLIS); ch.write(data); ch.force(true); - Thread.currentThread().sleep(1000); + Thread.sleep(1000); t4 = Files.getLastModifiedTime(file).toInstant().truncatedTo(ChronoUnit.SECONDS); } @@ -131,8 +131,8 @@ public void beforeAll() throws IOException { inMemoryFs = Jimfs.newFileSystem(); Path vaultPath = inMemoryFs.getPath("vault"); Files.createDirectories(vaultPath); - CryptoFileSystemProvider.initialize(vaultPath, "masterkey.cryptomator", "asd"); - fileSystem = new CryptoFileSystemProvider().newFileSystem(vaultPath, cryptoFileSystemProperties().withPassphrase("asd").withFlags().build()); + CryptoFileSystemProvider.initialize(vaultPath, "vault.cryptomator", "MASTERKEY_FILE", ignored -> new byte[64]); + fileSystem = new CryptoFileSystemProvider().newFileSystem(vaultPath, cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).withFlags().build()); file = fileSystem.getPath("/test.txt"); } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java index 38d0eafc..6c273b56 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java @@ -7,6 +7,7 @@ import org.hamcrest.TypeSafeDiagnosingMatcher; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import java.nio.charset.StandardCharsets; import java.util.EnumSet; @@ -21,6 +22,8 @@ public class CryptoFileSystemPropertiesTest { + private final KeyLoader keyLoader = Mockito.mock(KeyLoader.class); + @Test public void testSetNoPassphrase() { Assertions.assertThrows(IllegalStateException.class, () -> { @@ -30,46 +33,17 @@ public void testSetNoPassphrase() { @Test @SuppressWarnings({"unchecked", "deprecation"}) - public void testSetOnlyPassphrase() { - String passphrase = "aPassphrase"; - CryptoFileSystemProperties inTest = cryptoFileSystemProperties() // - .withPassphrase(passphrase) // - .build(); - - MatcherAssert.assertThat(inTest.passphrase(), is(passphrase)); - MatcherAssert.assertThat(inTest.masterkeyFilename(), is(DEFAULT_MASTERKEY_FILENAME)); - MatcherAssert.assertThat(inTest.readonly(), is(false)); - MatcherAssert.assertThat(inTest.initializeImplicitly(), is(true)); - MatcherAssert.assertThat(inTest.migrateImplicitly(), is(true)); - MatcherAssert.assertThat(inTest.entrySet(), - containsInAnyOrder( // - anEntry(PROPERTY_PASSPHRASE, passphrase), // - anEntry(PROPERTY_PEPPER, DEFAULT_PEPPER), // - anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // - anEntry(PROPERTY_MASTERKEY_FILENAME, DEFAULT_MASTERKEY_FILENAME), // - anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // - anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // - anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.INIT_IMPLICITLY, FileSystemFlags.MIGRATE_IMPLICITLY)))); - } - - @Test - @SuppressWarnings({"unchecked", "deprecation"}) - public void testSetPassphraseAndReadonlyFlag() { - String passphrase = "aPassphrase"; + public void testSetReadonlyFlag() { CryptoFileSystemProperties inTest = cryptoFileSystemProperties() // - .withPassphrase(passphrase) // + .withKeyLoader(keyLoader) // .withReadonlyFlag() // .build(); - MatcherAssert.assertThat(inTest.passphrase(), is(passphrase)); MatcherAssert.assertThat(inTest.masterkeyFilename(), is(DEFAULT_MASTERKEY_FILENAME)); MatcherAssert.assertThat(inTest.readonly(), is(true)); - MatcherAssert.assertThat(inTest.initializeImplicitly(), is(false)); - MatcherAssert.assertThat(inTest.migrateImplicitly(), is(false)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_PASSPHRASE, passphrase), // - anEntry(PROPERTY_PEPPER, DEFAULT_PEPPER), // + anEntry(PROPERTY_KEYLOADER, keyLoader), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, DEFAULT_MASTERKEY_FILENAME), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // @@ -79,24 +53,19 @@ public void testSetPassphraseAndReadonlyFlag() { @Test @SuppressWarnings({"unchecked", "deprecation"}) - public void testSetPassphraseAndMasterkeyFilenameAndReadonlyFlag() { - String passphrase = "aPassphrase"; + public void testSetMasterkeyFilenameAndReadonlyFlag() { String masterkeyFilename = "aMasterkeyFilename"; CryptoFileSystemProperties inTest = cryptoFileSystemProperties() // - .withPassphrase(passphrase) // + .withKeyLoader(keyLoader) // .withMasterkeyFilename(masterkeyFilename) // .withReadonlyFlag() // .build(); - MatcherAssert.assertThat(inTest.passphrase(), is(passphrase)); MatcherAssert.assertThat(inTest.masterkeyFilename(), is(masterkeyFilename)); MatcherAssert.assertThat(inTest.readonly(), is(true)); - MatcherAssert.assertThat(inTest.initializeImplicitly(), is(false)); - MatcherAssert.assertThat(inTest.migrateImplicitly(), is(false)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_PASSPHRASE, passphrase), // - anEntry(PROPERTY_PEPPER, DEFAULT_PEPPER), // + anEntry(PROPERTY_KEYLOADER, keyLoader), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // @@ -108,27 +77,21 @@ public void testSetPassphraseAndMasterkeyFilenameAndReadonlyFlag() { @SuppressWarnings({"unchecked"}) public void testFromMap() { Map<String, Object> map = new HashMap<>(); - String passphrase = "aPassphrase"; - byte[] pepper = "aPepper".getBytes(StandardCharsets.US_ASCII); String masterkeyFilename = "aMasterkeyFilename"; - map.put(PROPERTY_PASSPHRASE, passphrase); - map.put(PROPERTY_PEPPER, pepper); + map.put(PROPERTY_KEYLOADER, keyLoader); map.put(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename); map.put(PROPERTY_MAX_PATH_LENGTH, 1000); map.put(PROPERTY_MAX_NAME_LENGTH, 255); map.put(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)); CryptoFileSystemProperties inTest = cryptoFileSystemPropertiesFrom(map).build(); - MatcherAssert.assertThat(inTest.passphrase(), is(passphrase)); MatcherAssert.assertThat(inTest.masterkeyFilename(), is(masterkeyFilename)); MatcherAssert.assertThat(inTest.readonly(), is(true)); - MatcherAssert.assertThat(inTest.initializeImplicitly(), is(false)); MatcherAssert.assertThat(inTest.maxPathLength(), is(1000)); MatcherAssert.assertThat(inTest.maxNameLength(), is(255)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_PASSPHRASE, passphrase), // - anEntry(PROPERTY_PEPPER, pepper), // + anEntry(PROPERTY_KEYLOADER, keyLoader), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, 1000), // @@ -140,23 +103,17 @@ public void testFromMap() { @SuppressWarnings("unchecked") public void testWrapMapWithTrueReadonly() { Map<String, Object> map = new HashMap<>(); - String passphrase = "aPassphrase"; - byte[] pepper = "aPepper".getBytes(StandardCharsets.US_ASCII); String masterkeyFilename = "aMasterkeyFilename"; - map.put(PROPERTY_PASSPHRASE, passphrase); - map.put(PROPERTY_PEPPER, pepper); + map.put(PROPERTY_KEYLOADER, keyLoader); map.put(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename); map.put(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)); CryptoFileSystemProperties inTest = CryptoFileSystemProperties.wrap(map); - MatcherAssert.assertThat(inTest.passphrase(), is(passphrase)); MatcherAssert.assertThat(inTest.masterkeyFilename(), is(masterkeyFilename)); MatcherAssert.assertThat(inTest.readonly(), is(true)); - MatcherAssert.assertThat(inTest.initializeImplicitly(), is(false)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_PASSPHRASE, passphrase), // - anEntry(PROPERTY_PEPPER, pepper), // + anEntry(PROPERTY_KEYLOADER, keyLoader), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // @@ -168,23 +125,17 @@ public void testWrapMapWithTrueReadonly() { @SuppressWarnings("unchecked") public void testWrapMapWithFalseReadonly() { Map<String, Object> map = new HashMap<>(); - String passphrase = "aPassphrase"; - byte[] pepper = "aPepper".getBytes(StandardCharsets.US_ASCII); String masterkeyFilename = "aMasterkeyFilename"; - map.put(PROPERTY_PASSPHRASE, passphrase); - map.put(PROPERTY_PEPPER, pepper); + map.put(PROPERTY_KEYLOADER, keyLoader); map.put(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename); map.put(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)); CryptoFileSystemProperties inTest = CryptoFileSystemProperties.wrap(map); - MatcherAssert.assertThat(inTest.passphrase(), is(passphrase)); MatcherAssert.assertThat(inTest.masterkeyFilename(), is(masterkeyFilename)); MatcherAssert.assertThat(inTest.readonly(), is(false)); - MatcherAssert.assertThat(inTest.initializeImplicitly(), is(false)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_PASSPHRASE, passphrase), // - anEntry(PROPERTY_PEPPER, pepper), // + anEntry(PROPERTY_KEYLOADER, keyLoader), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // @@ -195,7 +146,6 @@ public void testWrapMapWithFalseReadonly() { @Test public void testWrapMapWithInvalidFilesystemFlags() { Map<String, Object> map = new HashMap<>(); - map.put(PROPERTY_PASSPHRASE, "any"); map.put(PROPERTY_MASTERKEY_FILENAME, "any"); map.put(PROPERTY_FILESYSTEM_FLAGS, "invalidType"); @@ -207,7 +157,6 @@ public void testWrapMapWithInvalidFilesystemFlags() { @Test public void testWrapMapWithInvalidMasterkeyFilename() { Map<String, Object> map = new HashMap<>(); - map.put(PROPERTY_PASSPHRASE, "any"); map.put(PROPERTY_MASTERKEY_FILENAME, ""); map.put(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)); @@ -219,7 +168,6 @@ public void testWrapMapWithInvalidMasterkeyFilename() { @Test public void testWrapMapWithInvalidPassphrase() { Map<String, Object> map = new HashMap<>(); - map.put(PROPERTY_PASSPHRASE, new Object()); map.put(PROPERTY_MASTERKEY_FILENAME, "any"); map.put(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)); @@ -232,26 +180,21 @@ public void testWrapMapWithInvalidPassphrase() { @SuppressWarnings({"unchecked", "deprecation"}) public void testWrapMapWithoutReadonly() { Map<String, Object> map = new HashMap<>(); - String passphrase = "aPassphrase"; - byte[] pepper = "aPepper".getBytes(StandardCharsets.US_ASCII); - map.put(PROPERTY_PASSPHRASE, passphrase); - map.put(PROPERTY_PEPPER, pepper); + map.put(PROPERTY_KEYLOADER, keyLoader); CryptoFileSystemProperties inTest = CryptoFileSystemProperties.wrap(map); - MatcherAssert.assertThat(inTest.passphrase(), is(passphrase)); MatcherAssert.assertThat(inTest.masterkeyFilename(), is(DEFAULT_MASTERKEY_FILENAME)); MatcherAssert.assertThat(inTest.readonly(), is(false)); - MatcherAssert.assertThat(inTest.initializeImplicitly(), is(true)); - MatcherAssert.assertThat(inTest.migrateImplicitly(), is(true)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_PASSPHRASE, passphrase), // - anEntry(PROPERTY_PEPPER, pepper), // + anEntry(PROPERTY_KEYLOADER, keyLoader), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, DEFAULT_MASTERKEY_FILENAME), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // - anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.INIT_IMPLICITLY, FileSystemFlags.MIGRATE_IMPLICITLY)))); + anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)) + ) + ); } @Test @@ -263,9 +206,7 @@ public void testWrapMapWithoutPassphrase() { @Test public void testWrapCryptoFileSystemProperties() { - CryptoFileSystemProperties inTest = cryptoFileSystemProperties() // - .withPassphrase("any") // - .build(); + CryptoFileSystemProperties inTest = cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); MatcherAssert.assertThat(CryptoFileSystemProperties.wrap(inTest), is(sameInstance(inTest))); } @@ -274,7 +215,7 @@ public void testWrapCryptoFileSystemProperties() { public void testMapIsImmutable() { Assertions.assertThrows(UnsupportedOperationException.class, () -> { cryptoFileSystemProperties() // - .withPassphrase("irrelevant") // + .withKeyLoader(keyLoader) // .build() // .put("test", "test"); }); @@ -284,7 +225,7 @@ public void testMapIsImmutable() { public void testEntrySetIsImmutable() { Assertions.assertThrows(UnsupportedOperationException.class, () -> { cryptoFileSystemProperties() // - .withPassphrase("irrelevant") // + .withKeyLoader(keyLoader) // .build() // .entrySet() // .add(null); @@ -295,7 +236,7 @@ public void testEntrySetIsImmutable() { public void testEntryIsImmutable() { Assertions.assertThrows(UnsupportedOperationException.class, () -> { cryptoFileSystemProperties() // - .withPassphrase("irrelevant") // + .withKeyLoader(keyLoader) // .build() // .entrySet() // .iterator().next() // diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index 571bcdff..c64d062d 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -48,18 +48,17 @@ import java.nio.channels.WritableByteChannel; import java.nio.charset.StandardCharsets; import java.nio.file.AccessDeniedException; -import java.nio.file.CopyOption; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.DosFileAttributeView; +import java.util.Arrays; import java.util.EnumSet; import static java.nio.file.Files.readAllBytes; @@ -74,6 +73,7 @@ public class CryptoFileSystemProviderIntegrationTest { @TestInstance(TestInstance.Lifecycle.PER_CLASS) class WithLimitedPaths { + private KeyLoader keyLoader = ignored -> new byte[64]; private CryptoFileSystem fs; private Path shortFilePath; private Path shortSymlinkPath; @@ -81,11 +81,11 @@ class WithLimitedPaths { @BeforeAll public void setup(@TempDir Path tmpDir) throws IOException { - CryptoFileSystemProvider.initialize(tmpDir, "masterkey.cryptomator", "asd"); + CryptoFileSystemProvider.initialize(tmpDir, "vault.cryptomator", "MASTERKEY_FILE", keyLoader); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // - .withPassphrase("asd") // + .withKeyLoader(keyLoader) // .withMaxPathLength(100) .build(); fs = CryptoFileSystemProvider.newFileSystem(tmpDir, properties); @@ -169,22 +169,30 @@ public void testCopyExceedingPathLengthLimit(String path) { class InMemory { private FileSystem tmpFs; + private KeyLoader keyLoader1; + private KeyLoader keyLoader2; private Path pathToVault1; private Path pathToVault2; - private Path masterkeyFile1; - private Path masterkeyFile2; + private Path vaultConfigFile1; + private Path vaultConfigFile2; private FileSystem fs1; private FileSystem fs2; @BeforeAll public void setup() throws IOException { tmpFs = Jimfs.newFileSystem(Configuration.unix()); + byte[] key1 = new byte[64]; + byte[] key2 = new byte[64]; + Arrays.fill(key1, (byte) 0x55); + Arrays.fill(key2, (byte) 0x77); + keyLoader1 = ignored -> Arrays.copyOf(key1, 64); + keyLoader2 = ignored -> Arrays.copyOf(key2, 64); pathToVault1 = tmpFs.getPath("/vaultDir1"); pathToVault2 = tmpFs.getPath("/vaultDir2"); Files.createDirectory(pathToVault1); Files.createDirectory(pathToVault2); - masterkeyFile1 = pathToVault1.resolve("masterkey.cryptomator"); - masterkeyFile2 = pathToVault2.resolve("masterkey.cryptomator"); + vaultConfigFile1 = pathToVault1.resolve("vault.cryptomator"); + vaultConfigFile2 = pathToVault2.resolve("vault.cryptomator"); } @AfterAll @@ -198,14 +206,13 @@ public void teardown() throws IOException { public void initializeVaults() { Assertions.assertAll( () -> { - CryptoFileSystemProvider.initialize(pathToVault1, "masterkey.cryptomator", "asd"); + CryptoFileSystemProvider.initialize(pathToVault1, "vault.cryptomator", "MASTERKEY_FILE", keyLoader1); Assertions.assertTrue(Files.isDirectory(pathToVault1.resolve("d"))); - Assertions.assertTrue(Files.isRegularFile(masterkeyFile1)); + Assertions.assertTrue(Files.isRegularFile(vaultConfigFile1)); }, () -> { - byte[] pepper = "pepper".getBytes(StandardCharsets.US_ASCII); - CryptoFileSystemProvider.initialize(pathToVault2, "masterkey.cryptomator", pepper, "asd"); + CryptoFileSystemProvider.initialize(pathToVault2, "vault.cryptomator", "MASTERKEY_FILE", keyLoader2); Assertions.assertTrue(Files.isDirectory(pathToVault2.resolve("d"))); - Assertions.assertTrue(Files.isRegularFile(masterkeyFile2)); + Assertions.assertTrue(Files.isRegularFile(vaultConfigFile2)); }); } @@ -213,91 +220,51 @@ public void initializeVaults() { @Order(2) @DisplayName("get filesystem with incorrect credentials") public void testGetFsWithWrongCredentials() { - Assumptions.assumeTrue(Files.exists(masterkeyFile1)); - Assumptions.assumeTrue(Files.exists(masterkeyFile2)); + Assumptions.assumeTrue(CryptoFileSystemProvider.containsVault(pathToVault1, "vault.cryptomator")); + Assumptions.assumeTrue(CryptoFileSystemProvider.containsVault(pathToVault2, "vault.cryptomator")); Assertions.assertAll( () -> { URI fsUri = CryptoFileSystemUri.create(pathToVault1); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // - .withPassphrase("qwe") // + .withKeyLoader(keyLoader2) // .build(); - Assertions.assertThrows(InvalidPassphraseException.class, () -> { + Assertions.assertThrows(VaultKeyInvalidException.class, () -> { FileSystems.newFileSystem(fsUri, properties); }); }, () -> { - byte[] pepper = "salt".getBytes(StandardCharsets.US_ASCII); URI fsUri = CryptoFileSystemUri.create(pathToVault2); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // - .withPassphrase("qwe") // - .withPepper(pepper) + .withKeyLoader(keyLoader1) // .build(); - Assertions.assertThrows(InvalidPassphraseException.class, () -> { + Assertions.assertThrows(VaultKeyInvalidException.class, () -> { FileSystems.newFileSystem(fsUri, properties); }); }); } - @Test - @Order(3) - @DisplayName("change password") - public void testChangePassword() { - Assumptions.assumeTrue(Files.exists(masterkeyFile1)); - Assumptions.assumeTrue(Files.exists(masterkeyFile2)); - Assertions.assertAll( - () -> { - Path pathToVault = tmpFs.getPath("/tmpVault"); - Files.createDirectory(pathToVault); - Path masterkeyFile = pathToVault.resolve("masterkey.cryptomator"); - Files.write(masterkeyFile, "{\"version\": 0}".getBytes(StandardCharsets.US_ASCII)); - Assertions.assertThrows(FileSystemNeedsMigrationException.class, () -> { - CryptoFileSystemProvider.changePassphrase(pathToVault, "masterkey.cryptomator", "asd", "qwe"); - }); - }, - () -> { - Assertions.assertThrows(InvalidPassphraseException.class, () -> { - CryptoFileSystemProvider.changePassphrase(pathToVault1, "masterkey.cryptomator", "WRONG", "qwe"); - }); - }, - () -> { - CryptoFileSystemProvider.changePassphrase(pathToVault1, "masterkey.cryptomator", "asd", "qwe"); - }, - () -> { - byte[] pepper = "salt".getBytes(StandardCharsets.US_ASCII); - Assertions.assertThrows(InvalidPassphraseException.class, () -> { - CryptoFileSystemProvider.changePassphrase(pathToVault2, "masterkey.cryptomator", pepper, "asd", "qwe"); - }); - }, - () -> { - byte[] pepper = "pepper".getBytes(StandardCharsets.US_ASCII); - CryptoFileSystemProvider.changePassphrase(pathToVault2, "masterkey.cryptomator", pepper, "asd", "qwe"); - } - ); - } - @Test @Order(4) @DisplayName("get filesystem with correct credentials") public void testGetFsViaNioApi() { - Assumptions.assumeTrue(Files.exists(masterkeyFile1)); - Assumptions.assumeTrue(Files.exists(masterkeyFile2)); + Assumptions.assumeTrue(Files.exists(vaultConfigFile1)); + Assumptions.assumeTrue(Files.exists(vaultConfigFile2)); Assertions.assertAll( () -> { URI fsUri = CryptoFileSystemUri.create(pathToVault1); - fs1 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withPassphrase("qwe").build()); + fs1 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader1).build()); Assertions.assertTrue(fs1 instanceof CryptoFileSystemImpl); FileSystem sameFs = FileSystems.getFileSystem(fsUri); Assertions.assertSame(fs1, sameFs); }, () -> { - byte[] pepper = "pepper".getBytes(StandardCharsets.US_ASCII); URI fsUri = CryptoFileSystemUri.create(pathToVault2); - fs2 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withPassphrase("qwe").withPepper(pepper).build()); + fs2 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader2).build()); Assertions.assertTrue(fs2 instanceof CryptoFileSystemImpl); FileSystem sameFs = FileSystems.getFileSystem(fsUri); @@ -556,8 +523,9 @@ class PosixTests { public void setup(@TempDir Path tmpDir) throws IOException { Path pathToVault = tmpDir.resolve("vaultDir1"); Files.createDirectories(pathToVault); - CryptoFileSystemProvider.initialize(pathToVault, "masterkey.cryptomator", "asd"); - fs = CryptoFileSystemProvider.newFileSystem(pathToVault, cryptoFileSystemProperties().withPassphrase("asd").build()); + KeyLoader keyLoader = ignored -> new byte[64]; + CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "MASTERKEY_FILE", keyLoader); + fs = CryptoFileSystemProvider.newFileSystem(pathToVault, cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); } @Nested @@ -645,8 +613,9 @@ class WindowsTests { public void setup(@TempDir Path tmpDir) throws IOException { Path pathToVault = tmpDir.resolve("vaultDir1"); Files.createDirectories(pathToVault); - CryptoFileSystemProvider.initialize(pathToVault, "masterkey.cryptomator", "asd"); - fs = CryptoFileSystemProvider.newFileSystem(pathToVault, cryptoFileSystemProperties().withPassphrase("asd").build()); + KeyLoader keyLoader = ignored -> new byte[64]; + CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "MASTERKEY_FILE", keyLoader); + fs = CryptoFileSystemProvider.newFileSystem(pathToVault, cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index 4ba4731e..fd088071 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; import java.io.IOException; import java.net.URI; @@ -167,7 +168,7 @@ public void testInitializeFailWithNotDirectoryException() { Path pathToVault = fs.getPath("/vaultDir"); Assertions.assertThrows(NotDirectoryException.class, () -> { - CryptoFileSystemProvider.initialize(pathToVault, "irrelevant.txt", "asd"); + CryptoFileSystemProvider.initialize(pathToVault, "irrelevant.txt", "irrelevant", ignored -> new byte[0]); }); } @@ -175,63 +176,28 @@ public void testInitializeFailWithNotDirectoryException() { public void testInitialize() throws IOException { FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); Path pathToVault = fs.getPath("/vaultDir"); - Path masterkeyFile = pathToVault.resolve("masterkey.cryptomator"); + Path vaultConfigFile = pathToVault.resolve("vault.cryptomator"); Path dataDir = pathToVault.resolve("d"); Files.createDirectory(pathToVault); - CryptoFileSystemProvider.initialize(pathToVault, "masterkey.cryptomator", "asd"); + CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "MASTERKEY_FILE", ignored -> new byte[64]); Assertions.assertTrue(Files.isDirectory(dataDir)); - Assertions.assertTrue(Files.isRegularFile(masterkeyFile)); + Assertions.assertTrue(Files.isRegularFile(vaultConfigFile)); } @Test - public void testNoImplicitInitialization() throws IOException { - FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); - Path pathToVault = fs.getPath("/vaultDir"); - Path masterkeyFile = pathToVault.resolve("masterkey.cryptomator"); - Path dataDir = pathToVault.resolve("d"); - - Files.createDirectory(pathToVault); + public void testNewFileSystem() throws IOException { + Path pathToVault = Path.of("/vaultDir"); URI uri = CryptoFileSystemUri.create(pathToVault); - CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // - .withMasterkeyFilename("masterkey.cryptomator") // - .withPassphrase("asd") // + .withKeyLoader(ignored -> new byte[64]) // .build(); - NoSuchFileException e = Assertions.assertThrows(NoSuchFileException.class, () -> { - inTest.newFileSystem(uri, properties); - }); - MatcherAssert.assertThat(e.getMessage(), containsString("Vault not initialized")); - Assertions.assertTrue(Files.notExists(dataDir)); - Assertions.assertTrue(Files.notExists(masterkeyFile)); - } - - @Test - @SuppressWarnings("deprecation") - public void testImplicitInitialization() throws IOException { - FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); - Path pathToVault = fs.getPath("/vaultDir"); - Path masterkeyFile = pathToVault.resolve("masterkey.cryptomator"); - Path dataDir = pathToVault.resolve("d"); - - Files.createDirectory(pathToVault); - URI uri = CryptoFileSystemUri.create(pathToVault); - - CryptoFileSystemProperties properties = cryptoFileSystemProperties() // - .withFlags(FileSystemFlags.INIT_IMPLICITLY) // - .withMasterkeyFilename("masterkey.cryptomator") // - .withPassphrase("asd") // - .build(); - when(fileSystems.create(eq(inTest), eq(pathToVault), eq(properties))).thenReturn(cryptoFileSystem); - FileSystem result = inTest.newFileSystem(uri, properties); - verify(fileSystems).create(eq(inTest), eq(pathToVault), eq(properties)); + inTest.newFileSystem(uri, properties); - Assertions.assertSame(cryptoFileSystem, result); - Assertions.assertTrue(Files.isDirectory(dataDir)); - Assertions.assertTrue(Files.isRegularFile(masterkeyFile)); + Mockito.verify(fileSystems).create(Mockito.same(inTest), Mockito.eq(pathToVault), Mockito.eq(properties)); } @Test @@ -276,58 +242,6 @@ public void testContainsVaultReturnsFalseIfDirectoryContainsMasterkeyFileButNoDa Assertions.assertFalse(containsVault(pathToVault, masterkeyFilename)); } - @Test - public void testVaultWithChangedPassphraseCanBeOpenedWithNewPassphrase() throws IOException { - String oldPassphrase = "oldPassphrase838283"; - String newPassphrase = "newPassphrase954810921"; - String masterkeyFilename = "masterkey.foo.baz"; - FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); - Path pathToVault = fs.getPath("/vaultDir"); - Files.createDirectory(pathToVault); - newFileSystem( // - pathToVault, // - cryptoFileSystemProperties() // - .withMasterkeyFilename(masterkeyFilename) // - .withPassphrase(oldPassphrase) // - .build()).close(); - - CryptoFileSystemProvider.changePassphrase(pathToVault, masterkeyFilename, oldPassphrase, newPassphrase); - - newFileSystem( // - pathToVault, // - cryptoFileSystemProperties() // - .withMasterkeyFilename(masterkeyFilename) // - .withPassphrase(newPassphrase) // - .build()).close(); - } - - @Test - public void testVaultWithChangedPassphraseCanNotBeOpenedWithOldPassphrase() throws IOException { - String oldPassphrase = "oldPassphrase838283"; - String newPassphrase = "newPassphrase954810921"; - String masterkeyFilename = "masterkey.foo.baz"; - FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); - Path pathToVault = fs.getPath("/vaultDir"); - Files.createDirectory(pathToVault); - newFileSystem( // - pathToVault, // - cryptoFileSystemProperties() // - .withMasterkeyFilename(masterkeyFilename) // - .withPassphrase(oldPassphrase) // - .build()).close(); - - CryptoFileSystemProvider.changePassphrase(pathToVault, masterkeyFilename, oldPassphrase, newPassphrase); - - Assertions.assertThrows(InvalidPassphraseException.class, () -> { - newFileSystem( // - pathToVault, // - cryptoFileSystemProperties() // - .withMasterkeyFilename(masterkeyFilename) // - .withPassphrase(oldPassphrase) // - .build()); - }); - } - @Test public void testGetFileSystemInvokesFileSystemsGetWithPathToVaultFromUri() { Path pathToVault = get("a").toAbsolutePath(); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java index fa7cbd71..5452bf4c 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java @@ -71,7 +71,8 @@ public void testCreateWithPathComponents() throws URISyntaxException { public void testCreateWithPathToVaultFromNonDefaultProvider() throws IOException { Path tempDir = createTempDirectory("CryptoFileSystemUrisTest").toAbsolutePath(); try { - FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(tempDir, cryptoFileSystemProperties().withPassphrase("asd").build()); + CryptoFileSystemProvider.initialize(tempDir, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); + FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(tempDir, cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); Path absolutePathToVault = fileSystem.getPath("a").toAbsolutePath(); URI uri = CryptoFileSystemUri.create(absolutePathToVault, "a", "b"); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java index 56687d7b..9542102c 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java @@ -1,15 +1,24 @@ package org.cryptomator.cryptofs; +import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; +import org.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.cryptolib.api.CryptorProvider; import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.FileSystemAlreadyExistsException; import java.nio.file.FileSystemNotFoundException; +import java.nio.file.Files; import java.nio.file.Path; +import java.security.SecureRandom; import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; @@ -19,26 +28,61 @@ public class CryptoFileSystemsTest { - private final Path path = mock(Path.class); - private final Path normalizedPath = mock(Path.class); + private final Path pathToVault = mock(Path.class, "vaultPath"); + private final Path normalizedPathToVault = mock(Path.class, "normalizedVaultPath"); + private final Path configFilePath = mock(Path.class, "normalizedVaultPath/vault.cryptomator"); + private final FileSystemCapabilityChecker capabilityChecker = mock(FileSystemCapabilityChecker.class); private final CryptoFileSystemProvider provider = mock(CryptoFileSystemProvider.class); private final CryptoFileSystemProperties properties = mock(CryptoFileSystemProperties.class); private final CryptoFileSystemComponent cryptoFileSystemComponent = mock(CryptoFileSystemComponent.class); private final CryptoFileSystemImpl cryptoFileSystem = mock(CryptoFileSystemImpl.class); - + private final VaultConfig.VaultConfigLoader configLoader = mock(VaultConfig.VaultConfigLoader.class); + private final byte[] rawKey = new byte[64]; + private final KeyLoader keyLoader = mock(KeyLoader.class); + private final VaultConfig vaultConfig = mock(VaultConfig.class); + private final VaultCipherMode cipherMode = mock(VaultCipherMode.class); + private final SecureRandom csprng = Mockito.mock(SecureRandom.class); + private final CryptorProvider cryptorProvider = mock(CryptorProvider.class); + private final Cryptor cryptor = mock(Cryptor.class); private final CryptoFileSystemComponent.Builder cryptoFileSystemComponentBuilder = mock(CryptoFileSystemComponent.Builder.class); - private final FileSystemCapabilityChecker capabilityChecker = mock(FileSystemCapabilityChecker.class); - private final CryptoFileSystems inTest = new CryptoFileSystems(cryptoFileSystemComponentBuilder, capabilityChecker); + + private MockedStatic<VaultConfig> vaultConficClass; + private MockedStatic<Files> filesClass; + + private final CryptoFileSystems inTest = new CryptoFileSystems(cryptoFileSystemComponentBuilder, capabilityChecker, csprng); @BeforeEach - public void setup() { - when(cryptoFileSystemComponentBuilder.provider(any())).thenReturn(cryptoFileSystemComponentBuilder); + public void setup() throws IOException { + filesClass = Mockito.mockStatic(Files.class); + vaultConficClass = Mockito.mockStatic(VaultConfig.class); + + when(pathToVault.normalize()).thenReturn(normalizedPathToVault); + when(normalizedPathToVault.resolve("vault.cryptomator")).thenReturn(configFilePath); + when(properties.vaultConfigFilename()).thenReturn("vault.cryptomator"); + when(properties.keyLoader()).thenReturn(keyLoader); + filesClass.when(() -> Files.readString(configFilePath, StandardCharsets.US_ASCII)).thenReturn("jwt-vault-config"); + vaultConficClass.when(() -> VaultConfig.decode("jwt-vault-config")).thenReturn(configLoader); + when(VaultConfig.decode("jwt-vault-config")).thenReturn(configLoader); + when(configLoader.getKeyId()).thenReturn("key-id"); + when(keyLoader.loadKey("key-id")).thenReturn(rawKey); + when(configLoader.load(rawKey, Constants.VAULT_VERSION)).thenReturn(vaultConfig); + when(vaultConfig.getCiphermode()).thenReturn(cipherMode); + when(cipherMode.getCryptorProvider(csprng)).thenReturn(cryptorProvider); + when(cryptorProvider.createFromRawKey(rawKey)).thenReturn(cryptor); + when(cryptoFileSystemComponentBuilder.cryptor(any())).thenReturn(cryptoFileSystemComponentBuilder); + when(cryptoFileSystemComponentBuilder.vaultConfig(any())).thenReturn(cryptoFileSystemComponentBuilder); when(cryptoFileSystemComponentBuilder.pathToVault(any())).thenReturn(cryptoFileSystemComponentBuilder); when(cryptoFileSystemComponentBuilder.properties(any())).thenReturn(cryptoFileSystemComponentBuilder); + when(cryptoFileSystemComponentBuilder.provider(any())).thenReturn(cryptoFileSystemComponentBuilder); when(cryptoFileSystemComponentBuilder.build()).thenReturn(cryptoFileSystemComponent); when(cryptoFileSystemComponent.cryptoFileSystem()).thenReturn(cryptoFileSystem); - when(path.normalize()).thenReturn(normalizedPath); + } + + @AfterEach + public void tearDown() { + vaultConficClass.close(); + filesClass.close(); } @Test @@ -48,46 +92,52 @@ public void testContainsReturnsFalseForNonContainedFileSystem() { @Test public void testContainsReturnsTrueForContainedFileSystem() throws IOException { - CryptoFileSystemImpl impl = inTest.create(provider, path, properties); + CryptoFileSystemImpl impl = inTest.create(provider, pathToVault, properties); Assertions.assertSame(cryptoFileSystem, impl); Assertions.assertTrue(inTest.contains(cryptoFileSystem)); - verify(cryptoFileSystemComponentBuilder).provider(provider); + verify(cryptoFileSystemComponentBuilder).cryptor(cryptor); + verify(cryptoFileSystemComponentBuilder).vaultConfig(vaultConfig); + verify(cryptoFileSystemComponentBuilder).pathToVault(normalizedPathToVault); verify(cryptoFileSystemComponentBuilder).properties(properties); - verify(cryptoFileSystemComponentBuilder).pathToVault(normalizedPath); + verify(cryptoFileSystemComponentBuilder).provider(provider); verify(cryptoFileSystemComponentBuilder).build(); } @Test public void testCreateThrowsFileSystemAlreadyExistsExceptionIfInvokedWithSamePathTwice() throws IOException { - inTest.create(provider, path, properties); + inTest.create(provider, pathToVault, properties); Assertions.assertThrows(FileSystemAlreadyExistsException.class, () -> { - inTest.create(provider, path, properties); + inTest.create(provider, pathToVault, properties); }); } @Test public void testCreateDoesNotThrowFileSystemAlreadyExistsExceptionIfFileSystemIsRemovedBefore() throws IOException { - CryptoFileSystemImpl fileSystem = inTest.create(provider, path, properties); - inTest.remove(fileSystem); + CryptoFileSystemImpl fileSystem1 = inTest.create(provider, pathToVault, properties); + Assertions.assertTrue(inTest.contains(fileSystem1)); + inTest.remove(fileSystem1); + Assertions.assertFalse(inTest.contains(fileSystem1)); - inTest.create(provider, path, properties); + CryptoFileSystemImpl fileSystem2 = inTest.create(provider, pathToVault, properties); + Assertions.assertTrue(inTest.contains(fileSystem2)); } @Test public void testGetReturnsFileSystemForPathIfItExists() throws IOException { - inTest.create(provider, path, properties); + CryptoFileSystemImpl fileSystem = inTest.create(provider, pathToVault, properties); - Assertions.assertSame(cryptoFileSystem, inTest.get(path)); + Assertions.assertTrue(inTest.contains(fileSystem)); + Assertions.assertSame(cryptoFileSystem, inTest.get(pathToVault)); } @Test public void testThrowsFileSystemNotFoundExceptionIfItDoesNotExists() { FileSystemNotFoundException e = Assertions.assertThrows(FileSystemNotFoundException.class, () -> { - inTest.get(path); + inTest.get(pathToVault); }); - MatcherAssert.assertThat(e.getMessage(), containsString(path.toString())); + MatcherAssert.assertThat(e.getMessage(), containsString(normalizedPathToVault.toString())); } } diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java index d8d47c54..94f299dd 100644 --- a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java @@ -44,7 +44,8 @@ public class DeleteNonEmptyCiphertextDirectoryIntegrationTest { public static void setupClass(@TempDir Path tmpDir) throws IOException { pathToVault = tmpDir.resolve("vault"); Files.createDirectory(pathToVault); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withPassphrase("asd").build()); + CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java index 9b7cf232..6e634c73 100644 --- a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java @@ -26,7 +26,8 @@ public class ReadmeCodeSamplesTest { @Test public void testReadmeCodeSampleUsingFileSystemConstructionMethodA(@TempDir Path storageLocation) throws IOException { - FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(storageLocation, CryptoFileSystemProperties.cryptoFileSystemProperties().withPassphrase("password").build()); + CryptoFileSystemProvider.initialize(storageLocation, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); + FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(storageLocation, CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); runCodeSample(fileSystem); } @@ -34,7 +35,8 @@ public void testReadmeCodeSampleUsingFileSystemConstructionMethodA(@TempDir Path @Test public void testReadmeCodeSampleUsingFileSystemConstructionMethodB(@TempDir Path storageLocation) throws IOException { URI uri = CryptoFileSystemUri.create(storageLocation); - FileSystem fileSystem = FileSystems.newFileSystem(uri, CryptoFileSystemProperties.cryptoFileSystemProperties().withPassphrase("password").build()); + CryptoFileSystemProvider.initialize(storageLocation, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); + FileSystem fileSystem = FileSystems.newFileSystem(uri, CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); runCodeSample(fileSystem); } diff --git a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java index 8cd282b1..742c07a0 100644 --- a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java @@ -32,7 +32,8 @@ public class RealFileSystemIntegrationTest { public static void setupClass(@TempDir Path tmpDir) throws IOException { pathToVault = tmpDir.resolve("vault"); Files.createDirectory(pathToVault); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withPassphrase("asd").build()); + CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java new file mode 100644 index 00000000..3eec8355 --- /dev/null +++ b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java @@ -0,0 +1,105 @@ +package org.cryptomator.cryptofs; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.Verification; +import org.cryptomator.cryptofs.common.Constants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; + +import java.util.Arrays; + +public class VaultConfigTest { + + @Test + public void testLoadMalformedToken() { + Assertions.assertThrows(VaultConfigLoadException.class, () -> { + VaultConfig.load("hello world", ignored -> new byte[64], 42); + }); + } + + @Nested + public class WithValidToken { + + private byte[] key = new byte[64]; + private VaultConfig originalConfig; + private String token; + + + @BeforeEach + public void setup() { + Arrays.fill(key, (byte) 0x55); + originalConfig = VaultConfig.createNew().cipherMode(VaultCipherMode.SIV_CTRMAC).maxFilenameLength(220).build(); + token = originalConfig.toToken("TEST_KEY", key); + } + + @Test + public void testSuccessfulLoad() throws VaultConfigLoadException, KeyLoadingFailedException { + var loaded = VaultConfig.load(token, ignored -> key, originalConfig.getVaultVersion()); + + Assertions.assertEquals(originalConfig.getId(), loaded.getId()); + Assertions.assertEquals(originalConfig.getVaultVersion(), loaded.getVaultVersion()); + Assertions.assertEquals(originalConfig.getCiphermode(), loaded.getCiphermode()); + Assertions.assertEquals(originalConfig.getMaxFilenameLength(), loaded.getMaxFilenameLength()); + } + + @ParameterizedTest + @ValueSource(ints = {0, 1, 2, 3, 10, 20, 30, 63}) + public void testLoadWithInvalidKey(int pos) { + key[pos] = (byte) 0x77; + + Assertions.assertThrows(VaultKeyInvalidException.class, () -> { + VaultConfig.load(token, ignored -> key, originalConfig.getVaultVersion()); + }); + } + + } + + @Test + public void testCreateNew() { + var config = VaultConfig.createNew().cipherMode(VaultCipherMode.SIV_CTRMAC).maxFilenameLength(220).build(); + + Assertions.assertNotNull(config.getId()); + Assertions.assertEquals(Constants.VAULT_VERSION, config.getVaultVersion()); + Assertions.assertEquals(VaultCipherMode.SIV_CTRMAC, config.getCiphermode()); + Assertions.assertEquals(220, config.getMaxFilenameLength()); + } + + @Test + public void testLoadExisting() throws KeyLoadingFailedException, VaultConfigLoadException { + var decodedJwt = Mockito.mock(DecodedJWT.class); + var formatClaim = Mockito.mock(Claim.class); + var ciphermodeClaim = Mockito.mock(Claim.class); + var maxFilenameLenClaim = Mockito.mock(Claim.class); + var keyLoader = Mockito.mock(KeyLoader.class); + var verification = Mockito.mock(Verification.class); + var verifier = Mockito.mock(JWTVerifier.class); + Mockito.when(decodedJwt.getKeyId()).thenReturn("key-id"); + Mockito.when(decodedJwt.getClaim("format")).thenReturn(formatClaim); + Mockito.when(decodedJwt.getClaim("ciphermode")).thenReturn(ciphermodeClaim); + Mockito.when(decodedJwt.getClaim("maxFilenameLen")).thenReturn(maxFilenameLenClaim); + Mockito.when(keyLoader.loadKey("key-id")).thenReturn(new byte[64]); + Mockito.when(verification.withClaim("format", 42)).thenReturn(verification); + Mockito.when(verification.build()).thenReturn(verifier); + Mockito.when(verifier.verify(decodedJwt)).thenReturn(decodedJwt); + Mockito.when(formatClaim.asInt()).thenReturn(42); + Mockito.when(ciphermodeClaim.asString()).thenReturn("SIV_CTRMAC"); + Mockito.when(maxFilenameLenClaim.asInt()).thenReturn(220); + try (var jwtMock = Mockito.mockStatic(JWT.class)) { + jwtMock.when(() -> JWT.decode("jwt-vault-config")).thenReturn(decodedJwt); + jwtMock.when(() -> JWT.require(Mockito.any())).thenReturn(verification); + + var config = VaultConfig.load("jwt-vault-config", keyLoader, 42); + Assertions.assertNotNull(config); + Assertions.assertEquals(42, config.getVaultVersion()); + } + } + +} \ No newline at end of file diff --git a/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java b/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java index ec60d06f..3a46c54f 100644 --- a/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java +++ b/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java @@ -31,7 +31,8 @@ public void setup() throws IOException { inMemoryFs = Jimfs.newFileSystem(); Path pathToVault = inMemoryFs.getRootDirectories().iterator().next().resolve("vault"); Files.createDirectory(pathToVault); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withPassphrase("asd").build()); + CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); root = fileSystem.getPath("/"); } diff --git a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java index 70eb5834..bd51fa80 100644 --- a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java @@ -56,7 +56,8 @@ public static void setupClass() throws IOException { inMemoryFs = Jimfs.newFileSystem(); pathToVault = inMemoryFs.getRootDirectories().iterator().next().resolve("vault"); Files.createDirectory(pathToVault); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withPassphrase("asd").build()); + CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); } @AfterAll diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java index 3b0acbac..dedd2af7 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java @@ -61,7 +61,7 @@ public void testMigrate() throws IOException { Assertions.assertEquals("MASTERKEY_FILE", token.getKeyId()); Assertions.assertEquals(8, token.getClaim("format").asInt()); Assertions.assertEquals("SIV_CTRMAC", token.getClaim("ciphermode").asString()); - Assertions.assertEquals(220, token.getClaim("maxFileNameLen").asInt()); + Assertions.assertEquals(220, token.getClaim("maxFilenameLen").asInt()); } } \ No newline at end of file From 7d2bd9891b6b90b73c0c8edadb629a5a106cd080 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Fri, 4 Dec 2020 12:16:27 +0100 Subject: [PATCH 06/70] Adjusted to new cryptolib API --- pom.xml | 2 +- .../cryptofs/CryptoFileSystemModule.java | 6 - .../cryptofs/CryptoFileSystemProperties.java | 13 +- .../cryptofs/CryptoFileSystemProvider.java | 18 +- .../cryptofs/CryptoFileSystems.java | 30 +- .../org/cryptomator/cryptofs/KeyLoader.java | 17 -- .../cryptofs/KeyLoadingFailedException.java | 15 - .../org/cryptomator/cryptofs/VaultConfig.java | 48 ++-- .../cryptomator/cryptofs/fh/ChunkCache.java | 29 +- .../cryptomator/cryptofs/fh/ChunkLoader.java | 3 +- .../cryptofs/migration/Migration.java | 9 +- .../migration/MigrationComponent.java | 12 + .../cryptofs/migration/MigrationModule.java | 30 +- .../cryptofs/migration/Migrators.java | 93 ++++--- .../api/MigrationContinuationListener.java | 2 + .../api/MigrationProgressListener.java | 2 + .../cryptofs/migration/api/Migrator.java | 10 +- .../migration/v6/Version6Migrator.java | 41 ++- .../migration/v7/Version7Migrator.java | 29 +- .../migration/v8/Version8Migrator.java | 27 +- .../cryptofs/CopyOperationTest.java | 3 +- ...toFileChannelWriteReadIntegrationTest.java | 13 +- .../cryptofs/CryptoFileSystemImplTest.java | 5 +- .../CryptoFileSystemPropertiesTest.java | 3 +- ...yptoFileSystemProviderIntegrationTest.java | 31 ++- .../CryptoFileSystemProviderTest.java | 24 +- .../cryptofs/CryptoFileSystemUriTest.java | 10 +- .../cryptofs/CryptoFileSystemsTest.java | 25 +- .../cryptomator/cryptofs/CryptoPathTest.java | 3 +- ...ptyCiphertextDirectoryIntegrationTest.java | 10 +- .../cryptofs/MoveOperationTest.java | 3 +- .../cryptofs/ReadmeCodeSamplesTest.java | 17 +- .../RealFileSystemIntegrationTest.java | 10 +- .../RootDirectoryInitializerTest.java | 3 +- .../cryptomator/cryptofs/VaultConfigTest.java | 26 +- ...iteFileWhileReadonlyChannelIsOpenTest.java | 10 +- .../attr/FileAttributeIntegrationTest.java | 10 +- .../cryptofs/dir/C9SInflatorTest.java | 4 +- .../cryptofs/dir/C9rDecryptorTest.java | 6 +- .../cryptofs/fh/ChunkCacheTest.java | 27 +- .../cryptofs/fh/ChunkLoaderTest.java | 7 +- .../cryptofs/fh/ChunkSaverTest.java | 5 +- .../cryptofs/fh/FileHeaderHolderTest.java | 7 +- .../migration/MigrationComponentTest.java | 19 -- .../cryptofs/migration/MigratorsTest.java | 263 +++++++++++++----- .../migration/TestMigrationComponent.java | 14 - .../migration/v6/Version6MigratorTest.java | 45 +-- .../migration/v7/Version7MigratorTest.java | 55 ++-- .../migration/v8/Version8MigratorTest.java | 25 +- 49 files changed, 641 insertions(+), 478 deletions(-) delete mode 100644 src/main/java/org/cryptomator/cryptofs/KeyLoader.java delete mode 100644 src/main/java/org/cryptomator/cryptofs/KeyLoadingFailedException.java delete mode 100644 src/test/java/org/cryptomator/cryptofs/migration/MigrationComponentTest.java delete mode 100644 src/test/java/org/cryptomator/cryptofs/migration/TestMigrationComponent.java diff --git a/pom.xml b/pom.xml index e89b0c79..956bd1ed 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- dependencies --> - <cryptolib.version>1.4.0</cryptolib.version> + <cryptolib.version>2.0.0-beta2</cryptolib.version> <jwt.version>3.11.0</jwt.version> <dagger.version>2.29.1</dagger.version> <guava.version>30.0-jre</guava.version> diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java index 0276e590..eacc1972 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java @@ -9,18 +9,12 @@ import dagger.Provides; import org.cryptomator.cryptofs.attr.AttributeComponent; import org.cryptomator.cryptofs.attr.AttributeViewComponent; -import org.cryptomator.cryptofs.common.Constants; -import org.cryptomator.cryptofs.common.MasterkeyBackupHelper; import org.cryptomator.cryptofs.dir.DirectoryStreamComponent; import org.cryptomator.cryptofs.fh.OpenCryptoFileComponent; -import org.cryptomator.cryptolib.api.Cryptor; -import org.cryptomator.cryptolib.api.CryptorProvider; -import org.cryptomator.cryptolib.api.KeyFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.io.UncheckedIOException; import java.nio.file.FileStore; import java.nio.file.Files; import java.nio.file.Path; diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index b24475e5..05259575 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -10,6 +10,7 @@ import com.google.common.base.Strings; import org.cryptomator.cryptofs.common.Constants; +import org.cryptomator.cryptolib.api.MasterkeyLoader; import java.net.URI; import java.nio.file.FileSystems; @@ -59,7 +60,7 @@ public class CryptoFileSystemProperties extends AbstractMap<String, Object> { */ public static final String PROPERTY_KEYLOADER = "keyLoader"; - static final KeyLoader DEFAULT_KEYLOADER = null; + static final MasterkeyLoader DEFAULT_KEYLOADER = null; /** @@ -118,8 +119,8 @@ private CryptoFileSystemProperties(Builder builder) { ); } - KeyLoader keyLoader() { - return (KeyLoader) get(PROPERTY_KEYLOADER); + MasterkeyLoader keyLoader() { + return (MasterkeyLoader) get(PROPERTY_KEYLOADER); } @SuppressWarnings("unchecked") @@ -195,7 +196,7 @@ public static CryptoFileSystemProperties wrap(Map<String, ?> properties) { */ public static class Builder { - private KeyLoader keyLoader = DEFAULT_KEYLOADER; + private MasterkeyLoader keyLoader = DEFAULT_KEYLOADER; private final Set<FileSystemFlags> flags = EnumSet.copyOf(DEFAULT_FILESYSTEM_FLAGS); private String vaultConfigFilename = DEFAULT_VAULTCONFIG_FILENAME; private String masterkeyFilename = DEFAULT_MASTERKEY_FILENAME; @@ -206,7 +207,7 @@ private Builder() { } private Builder(Map<String, ?> properties) { - checkedSet(KeyLoader.class, PROPERTY_KEYLOADER, properties, this::withKeyLoader); + 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); @@ -257,7 +258,7 @@ public Builder withMaxNameLength(int maxNameLength) { * @return this * @since 2.0.0 */ - public Builder withKeyLoader(KeyLoader keyLoader) { + public Builder withKeyLoader(MasterkeyLoader keyLoader) { this.keyLoader = keyLoader; return this; } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index aff67f11..18056194 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -11,6 +11,9 @@ import org.cryptomator.cryptofs.ch.AsyncDelegatingFileChannel; import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import java.io.IOException; import java.net.URI; @@ -120,16 +123,17 @@ public static CryptoFileSystem newFileSystem(Path pathToVault, CryptoFileSystemP * @param keyId The ID of the key to associate with this vault * @param keyLoader A key loader providing the masterkey for this new vault * @throws NotDirectoryException If the given path is not an existing directory. - * @throws FileSystemCapabilityChecker.MissingCapabilityException If the underlying filesystem lacks features required to store a vault * @throws IOException If the vault structure could not be initialized due to I/O errors + * @throws MasterkeyLoadingFailedException * @since 2.0.0 */ - public static void initialize(Path pathToVault, String vaultConfigFilename, String keyId, KeyLoader keyLoader) throws NotDirectoryException, IOException { + public static void initialize(Path pathToVault, String vaultConfigFilename, String keyId, MasterkeyLoader keyLoader) throws NotDirectoryException, IOException, MasterkeyLoadingFailedException { if (!Files.isDirectory(pathToVault)) { throw new NotDirectoryException(pathToVault.toString()); } - byte[] rawKey = keyLoader.loadKey(keyId); - try { + byte[] rawKey = new byte[0]; + try (Masterkey key = keyLoader.loadKey(keyId)) { + rawKey = key.getEncoded(); // save vault config: Path vaultConfigPath = pathToVault.resolve(vaultConfigFilename); var config = VaultConfig.createNew().cipherMode(VaultCipherMode.SIV_CTRMAC).maxFilenameLength(Constants.MAX_CIPHERTEXT_NAME_LENGTH).build(); @@ -175,7 +179,11 @@ public String getScheme() { public CryptoFileSystem newFileSystem(URI uri, Map<String, ?> rawProperties) throws IOException { CryptoFileSystemUri parsedUri = CryptoFileSystemUri.parse(uri); CryptoFileSystemProperties properties = CryptoFileSystemProperties.wrap(rawProperties); - return fileSystems.create(this, parsedUri.pathToVault(), properties); + try { + return fileSystems.create(this, parsedUri.pathToVault(), properties); + } catch (MasterkeyLoadingFailedException e) { + throw new IOException("Used invalid key to init filesystem.", e); + } } @Override diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java index 085ba9c3..12e90ecc 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java @@ -3,6 +3,8 @@ import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; import org.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,7 +28,7 @@ @Singleton class CryptoFileSystems { - + private static final Logger LOG = LoggerFactory.getLogger(CryptoFileSystems.class); private final ConcurrentMap<Path, CryptoFileSystemImpl> fileSystems = new ConcurrentHashMap<>(); @@ -41,18 +43,19 @@ public CryptoFileSystems(CryptoFileSystemComponent.Builder cryptoFileSystemCompo this.csprng = csprng; } - public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathToVault, CryptoFileSystemProperties properties) throws IOException { + public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathToVault, CryptoFileSystemProperties properties) throws IOException, MasterkeyLoadingFailedException { Path normalizedPathToVault = pathToVault.normalize(); var token = readVaultConfigFile(normalizedPathToVault, properties); var configLoader = VaultConfig.decode(token); - byte[] rawKey = properties.keyLoader().loadKey(configLoader.getKeyId()); - try { - var config = configLoader.load(rawKey, Constants.VAULT_VERSION); + byte[] rawKey = new byte[0]; + try (Masterkey key = properties.keyLoader().loadKey(configLoader.getKeyId())) { + rawKey = key.getEncoded(); + var config = configLoader.verify(rawKey, Constants.VAULT_VERSION); var adjustedProperties = adjustForCapabilities(pathToVault, properties); - return fileSystems.compute(normalizedPathToVault, (key, value) -> { - if (value == null) { - return create(provider, normalizedPathToVault, adjustedProperties, rawKey, config); + return fileSystems.compute(normalizedPathToVault, (path, fs) -> { + if (fs == null) { + return create(provider, normalizedPathToVault, adjustedProperties, key, config); } else { throw new FileSystemAlreadyExistsException(); } @@ -63,8 +66,8 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT } // synchronized access to non-threadsafe cryptoFileSystemComponentBuilder required - private synchronized CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathToVault, CryptoFileSystemProperties properties, byte[] rawKey, VaultConfig config) { - Cryptor cryptor = config.getCiphermode().getCryptorProvider(csprng).createFromRawKey(rawKey); + private synchronized CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathToVault, CryptoFileSystemProperties properties, Masterkey masterkey, VaultConfig config) { + Cryptor cryptor = config.getCiphermode().getCryptorProvider(csprng).withKey(masterkey); return cryptoFileSystemComponentBuilder // .cryptor(cryptor) // .vaultConfig(config) // @@ -77,10 +80,11 @@ private synchronized CryptoFileSystemImpl create(CryptoFileSystemProvider provid /** * Attempts to read a vault config file + * * @param pathToVault path to the vault's root - * @param properties properties used when attempting to construct a fs for this vault + * @param properties properties used when attempting to construct a fs for this vault * @return The contents of the file decoded in ASCII - * @throws IOException If the file could not be read + * @throws IOException If the file could not be read * @throws FileSystemNeedsMigrationException If the file doesn't exists, but a legacy masterkey file was found instead */ private String readVaultConfigFile(Path pathToVault, CryptoFileSystemProperties properties) throws IOException, FileSystemNeedsMigrationException { @@ -97,7 +101,7 @@ private String readVaultConfigFile(Path pathToVault, CryptoFileSystemProperties } } } - + private CryptoFileSystemProperties adjustForCapabilities(Path pathToVault, CryptoFileSystemProperties originalProperties) throws FileSystemCapabilityChecker.MissingCapabilityException { if (!originalProperties.readonly()) { try { diff --git a/src/main/java/org/cryptomator/cryptofs/KeyLoader.java b/src/main/java/org/cryptomator/cryptofs/KeyLoader.java deleted file mode 100644 index 9e2e5d16..00000000 --- a/src/main/java/org/cryptomator/cryptofs/KeyLoader.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.cryptomator.cryptofs; - -@FunctionalInterface -public interface KeyLoader { - - /** - * Loads a key required to unlock a vault. - * <p> - * This might be a long-running operation, as it may require user input or expensive computations. - * - * @param keyId a string uniquely identifying the source of the key and its identity, if multiple keys can be obtained from the same source - * @return The raw key bytes. Must not be null - * @throws KeyLoadingFailedException Thrown when it is impossible to fulfill the request - */ - byte[] loadKey(String keyId) throws KeyLoadingFailedException; - -} diff --git a/src/main/java/org/cryptomator/cryptofs/KeyLoadingFailedException.java b/src/main/java/org/cryptomator/cryptofs/KeyLoadingFailedException.java deleted file mode 100644 index 9532ea33..00000000 --- a/src/main/java/org/cryptomator/cryptofs/KeyLoadingFailedException.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.cryptomator.cryptofs; - -import java.io.IOException; - -/** - * Thrown by a {@link KeyLoader} when loading a key required to unlock a vault failed. - * <p> - * Possible reasons for this exception are: Unsupported key type, key for given id not found, user cancelled key entry, ... - */ -public class KeyLoadingFailedException extends FileSystemInitializationFailedException { - - public KeyLoadingFailedException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java index 8c660fd7..be4c342d 100644 --- a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java +++ b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java @@ -8,20 +8,23 @@ import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; import org.cryptomator.cryptofs.common.Constants; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import java.util.Arrays; import java.util.UUID; /** * Typesafe representation of vault configuration files. - * + * <p> * To prevent config tampering, such as downgrade attacks, vault configurations are cryptographically signed using HMAC-256 * with the vault's 64 byte master key. - * + * <p> * If the signature could be successfully verified, the configuration can be assumed valid and the masterkey can be assumed * eligible for the vault. - * - * When {@link #load(String, KeyLoader, int) loading} a vault configuration, a key must be provided and the signature is checked. + * <p> + * When {@link #load(String, MasterkeyLoader, int) loading} a vault configuration, a key must be provided and the signature is checked. * It is impossible to create an instance of this class from an existing configuration without signature verification. */ public class VaultConfig { @@ -76,21 +79,21 @@ public String toToken(String keyId, byte[] rawKey) { } /** - * Convenience wrapper for {@link #decode(String)} and {@link VaultConfigLoader#load(byte[], int)} + * Convenience wrapper for {@link #decode(String)} and {@link UnverifiedVaultConfig#verify(byte[], int)} * * @param token The token * @param keyLoader A key loader capable of providing a key for this token * @param expectedVaultVersion The vault version this token should contain * @return The decoded configuration - * @throws KeyLoadingFailedException If the key loader was unable to provide a key for this vault configuration - * @throws VaultConfigLoadException When loading the configuration fails (see {@link VaultConfigLoader#load(String, KeyLoader, int)} for details + * @throws MasterkeyLoadingFailedException If the key loader was unable to provide a key for this vault configuration + * @throws VaultConfigLoadException When loading the configuration fails */ - public static VaultConfig load(String token, KeyLoader keyLoader, int expectedVaultVersion) throws KeyLoadingFailedException, VaultConfigLoadException { + public static VaultConfig load(String token, MasterkeyLoader keyLoader, int expectedVaultVersion) throws MasterkeyLoadingFailedException, VaultConfigLoadException { + var configLoader = decode(token); byte[] rawKey = new byte[0]; - try { - var configLoader = decode(token); - rawKey = keyLoader.loadKey(configLoader.getKeyId()); - return configLoader.load(rawKey, expectedVaultVersion); + try (Masterkey key = keyLoader.loadKey(configLoader.getKeyId())) { + rawKey = key.getEncoded(); + return configLoader.verify(rawKey, expectedVaultVersion); } finally { Arrays.fill(rawKey, (byte) 0x00); } @@ -99,13 +102,13 @@ public static VaultConfig load(String token, KeyLoader keyLoader, int expectedVa /** * Decodes a vault configuration stored in JWT format to load it * - * @param token The token + * @param token The token * @return A loader object that allows loading the configuration (if providing the required key) * @throws VaultConfigLoadException When parsing the token failed */ - public static VaultConfigLoader decode(String token) throws VaultConfigLoadException { + public static UnverifiedVaultConfig decode(String token) throws VaultConfigLoadException { try { - return new VaultConfigLoader(JWT.decode(token)); + return new UnverifiedVaultConfig(JWT.decode(token)); } catch (JWTDecodeException e) { throw new VaultConfigLoadException("Failed to parse config: " + token); } @@ -120,21 +123,28 @@ public static VaultConfigBuilder createNew() { return new VaultConfigBuilder(); } - public static class VaultConfigLoader { + public static class UnverifiedVaultConfig { private final DecodedJWT unverifiedConfig; - private VaultConfigLoader(DecodedJWT unverifiedConfig) { + private UnverifiedVaultConfig(DecodedJWT unverifiedConfig) { this.unverifiedConfig = unverifiedConfig; } /** - * @return The ID of the key required to {@link #load(byte[], int) load} this config. + * @return The ID of the key required to {@link #verify(byte[], int) load} this config */ public String getKeyId() { return unverifiedConfig.getKeyId(); } + /** + * @return The unverified vault version (JWT signature not verified) + */ + public int allegedVaultVersion() { + return unverifiedConfig.getClaim(JSON_KEY_VAULTVERSION).asInt(); + } + /** * Decodes a vault configuration stored in JWT format. * @@ -145,7 +155,7 @@ public String getKeyId() { * @throws VaultVersionMismatchException If the token did not match the expected vault version * @throws VaultConfigLoadException Generic parse error */ - public VaultConfig load(byte[] rawKey, int expectedVaultVersion) throws VaultKeyInvalidException, VaultVersionMismatchException, VaultConfigLoadException { + public VaultConfig verify(byte[] rawKey, int expectedVaultVersion) throws VaultKeyInvalidException, VaultVersionMismatchException, VaultConfigLoadException { try { var verifier = JWT.require(Algorithm.HMAC256(rawKey)) // .withClaim(JSON_KEY_VAULTVERSION, expectedVaultVersion) // diff --git a/src/main/java/org/cryptomator/cryptofs/fh/ChunkCache.java b/src/main/java/org/cryptomator/cryptofs/fh/ChunkCache.java index 67f6419e..2963af85 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/ChunkCache.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/ChunkCache.java @@ -1,5 +1,7 @@ package org.cryptomator.cryptofs.fh; +import com.google.common.base.Throwables; +import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -21,7 +23,7 @@ public class ChunkCache { private final ChunkLoader chunkLoader; private final ChunkSaver chunkSaver; private final CryptoFileSystemStats stats; - private final LoadingCache<Long, ChunkData> chunks; + private final Cache<Long, ChunkData> chunks; @Inject public ChunkCache(ChunkLoader chunkLoader, ChunkSaver chunkSaver, CryptoFileSystemStats stats) { @@ -31,15 +33,16 @@ public ChunkCache(ChunkLoader chunkLoader, ChunkSaver chunkSaver, CryptoFileSyst this.chunks = CacheBuilder.newBuilder() // .maximumSize(MAX_CACHED_CLEARTEXT_CHUNKS) // .removalListener(this::removeChunk) // - .build(CacheLoader.from(this::loadChunk)); + .build(); } - private ChunkData loadChunk(Long chunkIndex) { + private ChunkData loadChunk(long chunkIndex) throws IOException { + stats.addChunkCacheMiss(); try { - stats.addChunkCacheMiss(); return chunkLoader.load(chunkIndex); - } catch (IOException e) { - throw new UncheckedIOException(e); + } catch (AuthenticationFailedException e) { + // TODO provide means to pass an AuthenticationFailedException handler using an OpenOption + throw new IOException("Unauthentic ciphertext in chunk " + chunkIndex, e); } } @@ -54,20 +57,10 @@ private void removeChunk(RemovalNotification<Long, ChunkData> removal) { public ChunkData get(long chunkIndex) throws IOException { try { stats.addChunkCacheAccess(); - return chunks.get(chunkIndex); + return chunks.get(chunkIndex, () -> loadChunk(chunkIndex)); } catch (ExecutionException e) { - assert e.getCause() != null; // no exception in ChunkLoader -> no executionException during chunk loading ;-) + assert e.getCause() instanceof IOException; // the only checked exception thrown by #loadChunk(long) throw (IOException) e.getCause(); - } catch (UncheckedExecutionException e) { - if (e.getCause() instanceof UncheckedIOException) { - UncheckedIOException uioe = (UncheckedIOException) e.getCause(); - throw uioe.getCause(); - } else if (e.getCause() instanceof AuthenticationFailedException) { - // TODO provide means to pass an AuthenticationFailedException handler using an OpenOption - throw new IOException(e.getCause()); - } else { - throw e; - } } } diff --git a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java index fb7e9157..278fa68b 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java @@ -1,6 +1,7 @@ package org.cryptomator.cryptofs.fh; import org.cryptomator.cryptofs.CryptoFileSystemStats; +import org.cryptomator.cryptolib.api.AuthenticationFailedException; import org.cryptomator.cryptolib.api.Cryptor; import javax.inject.Inject; @@ -23,7 +24,7 @@ public ChunkLoader(Cryptor cryptor, ChunkIO ciphertext, FileHeaderHolder headerH this.stats = stats; } - public ChunkData load(Long chunkIndex) throws IOException { + public ChunkData load(Long chunkIndex) throws IOException, AuthenticationFailedException { stats.addChunkCacheMiss(); int payloadSize = cryptor.fileContentCryptor().cleartextChunkSize(); int chunkSize = cryptor.fileContentCryptor().ciphertextChunkSize(); diff --git a/src/main/java/org/cryptomator/cryptofs/migration/Migration.java b/src/main/java/org/cryptomator/cryptofs/migration/Migration.java index c214ca2d..3180e37e 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/Migration.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/Migration.java @@ -17,9 +17,14 @@ enum Migration { FIVE_TO_SIX(5), /** - * Migrates vault format 5 to 6. + * Migrates vault format 6 to 7. + */ + SIX_TO_SEVEN(6), + + /** + * Migrates vault format 7 to 8 */ - SIX_TO_SEVEN(6); + SEVEN_TO_EIGHT(7); private final int applicableVersion; diff --git a/src/main/java/org/cryptomator/cryptofs/migration/MigrationComponent.java b/src/main/java/org/cryptomator/cryptofs/migration/MigrationComponent.java index 35165dde..ce4126fb 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/MigrationComponent.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/MigrationComponent.java @@ -5,11 +5,23 @@ *******************************************************************************/ package org.cryptomator.cryptofs.migration; +import dagger.BindsInstance; import dagger.Component; +import java.security.SecureRandom; + @Component(modules = {MigrationModule.class}) interface MigrationComponent { Migrators migrators(); + @Component.Builder + interface Builder { + + @BindsInstance + Builder csprng(SecureRandom csprng); + + MigrationComponent build(); + } + } diff --git a/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java b/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java index 0d700add..8b777654 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java @@ -5,10 +5,6 @@ *******************************************************************************/ package org.cryptomator.cryptofs.migration; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - import dagger.MapKey; import dagger.Module; import dagger.Provides; @@ -17,25 +13,26 @@ import org.cryptomator.cryptofs.migration.api.Migrator; import org.cryptomator.cryptofs.migration.v6.Version6Migrator; import org.cryptomator.cryptofs.migration.v7.Version7Migrator; +import org.cryptomator.cryptofs.migration.v8.Version8Migrator; +import org.cryptomator.cryptolib.Cryptors; import org.cryptomator.cryptolib.api.CryptorProvider; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.security.SecureRandom; + import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Module class MigrationModule { - private final CryptorProvider version1Cryptor; - - MigrationModule(CryptorProvider version1Cryptor) { - this.version1Cryptor = version1Cryptor; - } - @Provides - CryptorProvider provideVersion1CryptorProvider() { - return version1Cryptor; + CryptorProvider provideVersion1CryptorProvider(SecureRandom csprng) { + return Cryptors.version1(csprng); } - + @Provides FileSystemCapabilityChecker provideFileSystemCapabilityChecker() { return new FileSystemCapabilityChecker(); @@ -55,6 +52,13 @@ Migrator provideVersion7Migrator(Version7Migrator migrator) { return migrator; } + @Provides + @IntoMap + @MigratorKey(Migration.SEVEN_TO_EIGHT) + Migrator provideVersion8Migrator(Version8Migrator migrator) { + return migrator; + } + @Documented @Target(METHOD) @Retention(RUNTIME) diff --git a/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java b/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java index 899da75d..02704807 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java @@ -5,16 +5,7 @@ *******************************************************************************/ package org.cryptomator.cryptofs.migration; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Map; -import java.util.Optional; - -import javax.inject.Inject; - +import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener; @@ -22,30 +13,38 @@ import org.cryptomator.cryptofs.migration.api.Migrator; import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException; import org.cryptomator.cryptolib.Cryptors; +import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.InvalidPassphraseException; -import org.cryptomator.cryptolib.api.KeyFile; import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; +import org.cryptomator.cryptolib.common.MasterkeyFile; + +import javax.inject.Inject; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Map; +import java.util.Optional; /** * Used to perform migration from an older vault format to a newer one. * <p> * Example Usage: - * + * * <pre> * <code> - * if (Migrators.get().{@link #needsMigration(Path, String) needsMigration(pathToVault, masterkeyFileName)}) { + * if (Migrators.get().{@link #needsMigration(Path, String, String)} needsMigration(pathToVault, vaultConfigFilename, masterkeyFileName)}) { * Migrators.get().{@link #migrate(Path, String, String, CharSequence, MigrationProgressListener, MigrationContinuationListener) migrate(pathToVault, masterkeyFileName, passphrase, progressListener, continuationListener)}; * } * </code> * </pre> - * + * * @since 1.4.0 */ public class Migrators { - private static final MigrationComponent COMPONENT = DaggerMigrationComponent.builder() // - .migrationModule(new MigrationModule(Cryptors.version1(strongSecureRandom()))) // - .build(); + private static final MigrationComponent COMPONENT = DaggerMigrationComponent.builder().csprng(strongSecureRandom()).build(); private final Map<Migration, Migrator> migrators; private final FileSystemCapabilityChecker fsCapabilityChecker; @@ -70,46 +69,37 @@ public static Migrators get() { /** * Inspects the vault and checks if it is supported by this library. - * - * @param pathToVault Path to the vault's root - * @param masterkeyFilename Name of the masterkey file located in the vault + * + * @param pathToVault Path to the vault's root + * @param vaultConfigFilename Name of the vault config file located in the vault + * @param masterkeyFilename Name of the masterkey file optionally located in the vault * @return <code>true</code> if the vault at the given path is of an older format than supported by this library * @throws IOException if an I/O error occurs parsing the masterkey file */ - public boolean needsMigration(Path pathToVault, String masterkeyFilename) throws IOException { - Path masterKeyPath = pathToVault.resolve(masterkeyFilename); - byte[] keyFileContents = Files.readAllBytes(masterKeyPath); - try { - KeyFile keyFile = KeyFile.parse(keyFileContents); - return keyFile.getVersion() < Constants.VAULT_VERSION; - } catch (IllegalArgumentException e) { - throw new IOException("Malformed masterkey file " + masterKeyPath, e); - } + public boolean needsMigration(Path pathToVault, String vaultConfigFilename, String masterkeyFilename) throws IOException { + int vaultVersion = determineVaultVersion(pathToVault, vaultConfigFilename, masterkeyFilename); + return vaultVersion < Constants.VAULT_VERSION; } /** * Performs the actual migration. This task may take a while and this method will block. - * - * @param pathToVault Path to the vault's root - * @param vaultConfigFilename Name of the vault config file located inside <code>pathToVault</code> - * @param masterkeyFilename Name of the masterkey file located inside <code>pathToVault</code> - * @param passphrase The passphrase needed to unlock the vault - * @param progressListener Listener that will get notified of progress updates + * + * @param pathToVault Path to the vault's root + * @param vaultConfigFilename Name of the vault config file located inside <code>pathToVault</code> + * @param masterkeyFilename Name of the masterkey file located inside <code>pathToVault</code> + * @param passphrase The passphrase needed to unlock the vault + * @param progressListener Listener that will get notified of progress updates * @param continuationListener Listener that will get asked if there are events that require feedback - * @throws NoApplicableMigratorException If the vault can not be migrated, because no migrator could be found - * @throws InvalidPassphraseException If the passphrase could not be used to unlock the vault + * @throws NoApplicableMigratorException If the vault can not be migrated, because no migrator could be found + * @throws InvalidPassphraseException If the passphrase could not be used to unlock the vault * @throws FileSystemCapabilityChecker.MissingCapabilityException If the underlying filesystem lacks features required to store a vault - * @throws IOException if an I/O error occurs migrating the vault + * @throws IOException if an I/O error occurs migrating the vault */ - public void migrate(Path pathToVault, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws NoApplicableMigratorException, InvalidPassphraseException, IOException { + public void migrate(Path pathToVault, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws NoApplicableMigratorException, CryptoException, IOException { fsCapabilityChecker.assertAllCapabilities(pathToVault); - - Path masterKeyPath = pathToVault.resolve(masterkeyFilename); - byte[] keyFileContents = Files.readAllBytes(masterKeyPath); - KeyFile keyFile = KeyFile.parse(keyFileContents); - + int vaultVersion = determineVaultVersion(pathToVault, vaultConfigFilename, masterkeyFilename); try { - Migrator migrator = findApplicableMigrator(keyFile.getVersion()).orElseThrow(NoApplicableMigratorException::new); + Migrator migrator = findApplicableMigrator(vaultVersion).orElseThrow(NoApplicableMigratorException::new); migrator.migrate(pathToVault, vaultConfigFilename, masterkeyFilename, passphrase, progressListener, continuationListener); } catch (UnsupportedVaultFormatException e) { // might be a tampered masterkey file, as this exception is also thrown if the vault version MAC is not authentic. @@ -117,6 +107,19 @@ public void migrate(Path pathToVault, String vaultConfigFilename, String masterk } } + private int determineVaultVersion(Path pathToVault, String vaultConfigFilename, String masterkeyFilename) throws IOException { + Path vaultConfigPath = pathToVault.resolve(vaultConfigFilename); + Path masterKeyPath = pathToVault.resolve(masterkeyFilename); + if (Files.exists(vaultConfigPath)) { + var jwt = Files.readString(vaultConfigPath); + return VaultConfig.decode(jwt).allegedVaultVersion(); + } else if (Files.exists(masterKeyPath)) { + return MasterkeyFile.withContentFromFile(masterKeyPath).allegedVaultVersion(); + } else { + throw new IOException("Did not find " + vaultConfigFilename + " nor " + masterkeyFilename); + } + } + private Optional<Migrator> findApplicableMigrator(int version) { return migrators.entrySet().stream().filter(entry -> entry.getKey().isApplicable(version)).map(Map.Entry::getValue).findAny(); } diff --git a/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationContinuationListener.java b/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationContinuationListener.java index a531565f..4f08321e 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationContinuationListener.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationContinuationListener.java @@ -3,6 +3,8 @@ @FunctionalInterface public interface MigrationContinuationListener { + MigrationContinuationListener CANCEL_ALWAYS = event -> ContinuationResult.CANCEL; + /** * Invoked when the migration requires action. * <p> diff --git a/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationProgressListener.java b/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationProgressListener.java index 9fe9ef68..c8cc82e9 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationProgressListener.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationProgressListener.java @@ -3,6 +3,8 @@ @FunctionalInterface public interface MigrationProgressListener { + MigrationProgressListener IGNORE = (state, progress) -> {}; + /** * Called on every step during migration that might change the progress. * diff --git a/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java index d1bc9ba8..e933234c 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.nio.file.Path; +import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; @@ -25,9 +26,10 @@ public interface Migrator { * @param passphrase * @throws InvalidPassphraseException * @throws UnsupportedVaultFormatException + * @throws CryptoException * @throws IOException */ - default void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { + default void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase) throws InvalidPassphraseException, UnsupportedVaultFormatException, CryptoException, IOException { migrate(vaultRoot, vaultConfigFilename, masterkeyFilename, passphrase, (state, progress) -> {}); } @@ -41,9 +43,10 @@ default void migrate(Path vaultRoot, String vaultConfigFilename, String masterke * @param progressListener * @throws InvalidPassphraseException * @throws UnsupportedVaultFormatException + * @throws CryptoException * @throws IOException */ - default void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { + default void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, CryptoException, IOException { migrate(vaultRoot, vaultConfigFilename, masterkeyFilename, passphrase, progressListener, (event) -> MigrationContinuationListener.ContinuationResult.CANCEL); } @@ -58,8 +61,9 @@ default void migrate(Path vaultRoot, String vaultConfigFilename, String masterke * @param continuationListener * @throws InvalidPassphraseException * @throws UnsupportedVaultFormatException + * @throws CryptoException * @throws IOException */ - void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException; + void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, CryptoException, IOException; } diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java index 9d12fb4b..99527464 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java @@ -5,27 +5,26 @@ *******************************************************************************/ package org.cryptomator.cryptofs.migration.v6; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.text.Normalizer; -import java.text.Normalizer.Form; - -import javax.inject.Inject; - import org.cryptomator.cryptofs.common.MasterkeyBackupHelper; import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener; import org.cryptomator.cryptofs.migration.api.MigrationProgressListener; import org.cryptomator.cryptofs.migration.api.Migrator; -import org.cryptomator.cryptolib.api.Cryptor; -import org.cryptomator.cryptolib.api.CryptorProvider; -import org.cryptomator.cryptolib.api.InvalidPassphraseException; -import org.cryptomator.cryptolib.api.KeyFile; -import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; +import org.cryptomator.cryptolib.api.CryptoException; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.common.MasterkeyFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.inject.Inject; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.SecureRandom; +import java.text.Normalizer; +import java.text.Normalizer.Form; +import java.util.Optional; + /** * Updates masterkey.cryptomator: * @@ -35,21 +34,21 @@ public class Version6Migrator implements Migrator { private static final Logger LOG = LoggerFactory.getLogger(Version6Migrator.class); - private final CryptorProvider cryptorProvider; + private final SecureRandom csprng; @Inject - public Version6Migrator(CryptorProvider cryptorProvider) { - this.cryptorProvider = cryptorProvider; + public Version6Migrator(SecureRandom csprng) { + this.csprng = csprng; } @Override - public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { + public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws CryptoException, IOException { LOG.info("Upgrading {} from version 5 to version 6.", vaultRoot); progressListener.update(MigrationProgressListener.ProgressState.INITIALIZING, 0.0); Path masterkeyFile = vaultRoot.resolve(masterkeyFilename); byte[] fileContentsBeforeUpgrade = Files.readAllBytes(masterkeyFile); - KeyFile keyFile = KeyFile.parse(fileContentsBeforeUpgrade); - try (Cryptor cryptor = cryptorProvider.createFromKeyFile(keyFile, passphrase, 5)) { + MasterkeyFile keyFile = MasterkeyFile.withContentFromFile(masterkeyFile); + try (Masterkey masterkey = keyFile.unlock(passphrase, new byte[0], Optional.of(5)).loadKeyAndClose()) { // create backup, as soon as we know the password was correct: Path masterkeyBackupFile = MasterkeyBackupHelper.attemptMasterKeyBackup(masterkeyFile); LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName()); @@ -57,7 +56,7 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey progressListener.update(MigrationProgressListener.ProgressState.FINALIZING, 0.0); // rewrite masterkey file with normalized passphrase: - byte[] fileContentsAfterUpgrade = cryptor.writeKeysToMasterkeyFile(Normalizer.normalize(passphrase, Form.NFC), 6).serialize(); + byte[] fileContentsAfterUpgrade = MasterkeyFile.lock(masterkey, Normalizer.normalize(passphrase, Form.NFC), new byte[0], 6, csprng); Files.write(masterkeyFile, fileContentsAfterUpgrade, StandardOpenOption.TRUNCATE_EXISTING); LOG.info("Updated masterkey."); } diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java index 61b6f0b9..e041282d 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java @@ -14,11 +14,9 @@ import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener.ContinuationResult; import org.cryptomator.cryptofs.migration.api.MigrationProgressListener; import org.cryptomator.cryptofs.migration.api.Migrator; -import org.cryptomator.cryptolib.api.Cryptor; -import org.cryptomator.cryptolib.api.CryptorProvider; -import org.cryptomator.cryptolib.api.InvalidPassphraseException; -import org.cryptomator.cryptolib.api.KeyFile; -import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; +import org.cryptomator.cryptolib.api.CryptoException; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.common.MasterkeyFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,7 +26,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.security.SecureRandom; import java.util.EnumSet; +import java.util.Optional; /** * Renames ciphertext names: @@ -38,7 +38,7 @@ * <li>Dirs: 0BASE32== -> base64==.c9r/dir.c9r</li> * <li>Symlinks: 1SBASE32== -> base64.c9r/symlink.c9r</li> * </ul> - * + * <p> * Shortened names: * <ul> * <li>shortened.lng -> shortened.c9s</li> @@ -49,25 +49,24 @@ public class Version7Migrator implements Migrator { private static final Logger LOG = LoggerFactory.getLogger(Version7Migrator.class); - private final CryptorProvider cryptorProvider; + private final SecureRandom csprng; @Inject - public Version7Migrator(CryptorProvider cryptorProvider) { - this.cryptorProvider = cryptorProvider; + public Version7Migrator(SecureRandom csprng) { + this.csprng = csprng; } @Override - public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { + public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws CryptoException, IOException { LOG.info("Upgrading {} from version 6 to version 7.", vaultRoot); progressListener.update(MigrationProgressListener.ProgressState.INITIALIZING, 0.0); Path masterkeyFile = vaultRoot.resolve(masterkeyFilename); - byte[] fileContentsBeforeUpgrade = Files.readAllBytes(masterkeyFile); - KeyFile keyFile = KeyFile.parse(fileContentsBeforeUpgrade); - try (Cryptor cryptor = cryptorProvider.createFromKeyFile(keyFile, passphrase, 6)) { + MasterkeyFile keyFile = MasterkeyFile.withContentFromFile(masterkeyFile); + try (Masterkey masterkey = keyFile.unlock(passphrase, new byte[0], Optional.of(6)).loadKeyAndClose()) { // create backup, as soon as we know the password was correct: Path masterkeyBackupFile = MasterkeyBackupHelper.attemptMasterKeyBackup(masterkeyFile); LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName()); - + // check file system capabilities: int filenameLengthLimit = new FileSystemCapabilityChecker().determineSupportedFileNameLength(vaultRoot.resolve("c"), 46, 28, 220); int pathLengthLimit = filenameLengthLimit + 48; // TODO @@ -118,7 +117,7 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey Files.walkFileTree(vaultRoot.resolve("m"), DeletingFileVisitor.INSTANCE); // rewrite masterkey file with normalized passphrase: - byte[] fileContentsAfterUpgrade = cryptor.writeKeysToMasterkeyFile(passphrase, 7).serialize(); + byte[] fileContentsAfterUpgrade = MasterkeyFile.lock(masterkey, passphrase, new byte[0], 7, csprng); Files.write(masterkeyFile, fileContentsAfterUpgrade, StandardOpenOption.TRUNCATE_EXISTING); LOG.info("Updated masterkey."); } diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java index 4f6980d7..455622f6 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java @@ -11,11 +11,9 @@ import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener; import org.cryptomator.cryptofs.migration.api.MigrationProgressListener; import org.cryptomator.cryptofs.migration.api.Migrator; -import org.cryptomator.cryptolib.api.Cryptor; -import org.cryptomator.cryptolib.api.CryptorProvider; -import org.cryptomator.cryptolib.api.InvalidPassphraseException; -import org.cryptomator.cryptolib.api.KeyFile; -import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; +import org.cryptomator.cryptolib.api.CryptoException; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.common.MasterkeyFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,7 +23,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.security.SecureRandom; import java.util.Arrays; +import java.util.Optional; import java.util.UUID; /** @@ -40,29 +40,28 @@ public class Version8Migrator implements Migrator { private static final Logger LOG = LoggerFactory.getLogger(Version8Migrator.class); - private final CryptorProvider cryptorProvider; + private final SecureRandom csprng; @Inject - public Version8Migrator(CryptorProvider cryptorProvider) { - this.cryptorProvider = cryptorProvider; + public Version8Migrator(SecureRandom csprng) { + this.csprng = csprng; } @Override - public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException { + public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws CryptoException, IOException { LOG.info("Upgrading {} from version 7 to version 8.", vaultRoot); progressListener.update(MigrationProgressListener.ProgressState.INITIALIZING, 0.0); Path masterkeyFile = vaultRoot.resolve(masterkeyFilename); Path vaultConfigFile = vaultRoot.resolve(vaultConfigFilename); - byte[] fileContentsBeforeUpgrade = Files.readAllBytes(masterkeyFile); byte[] rawKey = new byte[0]; - KeyFile keyFile = KeyFile.parse(fileContentsBeforeUpgrade); - try (Cryptor cryptor = cryptorProvider.createFromKeyFile(keyFile, passphrase, 7)) { + MasterkeyFile keyFile = MasterkeyFile.withContentFromFile(masterkeyFile); + try (Masterkey masterkey = keyFile.unlock(passphrase, new byte[0], Optional.of(7)).loadKeyAndClose()) { // create backup, as soon as we know the password was correct: Path masterkeyBackupFile = MasterkeyBackupHelper.attemptMasterKeyBackup(masterkeyFile); LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName()); // create vaultconfig.cryptomator - rawKey = cryptor.getRawKey(); + rawKey = masterkey.getEncoded(); Algorithm algorithm = Algorithm.HMAC256(rawKey); var config = JWT.create() // .withJWTId(UUID.randomUUID().toString()) // @@ -77,7 +76,7 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey progressListener.update(MigrationProgressListener.ProgressState.FINALIZING, 0.0); // rewrite masterkey file with normalized passphrase: - byte[] fileContentsAfterUpgrade = cryptor.writeKeysToMasterkeyFile(passphrase, 999).serialize(); + byte[] fileContentsAfterUpgrade = MasterkeyFile.lock(masterkey, passphrase, new byte[0], 999, csprng); Files.write(masterkeyFile, fileContentsAfterUpgrade, StandardOpenOption.TRUNCATE_EXISTING); LOG.info("Updated masterkey."); } finally { diff --git a/src/test/java/org/cryptomator/cryptofs/CopyOperationTest.java b/src/test/java/org/cryptomator/cryptofs/CopyOperationTest.java index 283149e4..bd0c71aa 100644 --- a/src/test/java/org/cryptomator/cryptofs/CopyOperationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CopyOperationTest.java @@ -25,6 +25,7 @@ import static org.cryptomator.cryptofs.util.ByteBuffers.repeat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -54,7 +55,7 @@ public void setup() { public void testCopyWithEqualPathDoesNothing() throws IOException { inTest.copy(aPathFromFsA, aPathFromFsA); - verifyZeroInteractions(aPathFromFsA); + verifyNoInteractions(aPathFromFsA); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java index 627e2b54..46f4a0a1 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java @@ -9,6 +9,9 @@ package org.cryptomator.cryptofs; import com.google.common.jimfs.Jimfs; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -60,7 +63,8 @@ public class Windows { @BeforeAll public void setupClass(@TempDir Path tmpDir) throws IOException { - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(tmpDir), cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); + MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(tmpDir), cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); } // tests https://github.com/cryptomator/cryptofs/issues/69 @@ -127,12 +131,13 @@ public class PlatformIndependent { private Path file; @BeforeAll - public void beforeAll() throws IOException { + public void beforeAll() throws IOException, MasterkeyLoadingFailedException { inMemoryFs = Jimfs.newFileSystem(); Path vaultPath = inMemoryFs.getPath("vault"); Files.createDirectories(vaultPath); - CryptoFileSystemProvider.initialize(vaultPath, "vault.cryptomator", "MASTERKEY_FILE", ignored -> new byte[64]); - fileSystem = new CryptoFileSystemProvider().newFileSystem(vaultPath, cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).withFlags().build()); + MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); + CryptoFileSystemProvider.initialize(vaultPath, "vault.cryptomator", "MASTERKEY_FILE", keyLoader); + fileSystem = new CryptoFileSystemProvider().newFileSystem(vaultPath, cryptoFileSystemProperties().withKeyLoader(keyLoader).withFlags().build()); file = fileSystem.getPath("/test.txt"); } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java index 6cc3a56e..f0140b37 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java @@ -72,6 +72,7 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -548,7 +549,7 @@ public void moveFileToItselfDoesNothing() throws IOException { inTest.move(cleartextSource, cleartextSource); verify(readonlyFlag).assertWritable(); - verifyZeroInteractions(cleartextSource); + verifyNoInteractions(cleartextSource); } @Test @@ -704,7 +705,7 @@ public void copyFileToItselfDoesNothing() throws IOException { inTest.copy(cleartextSource, cleartextSource); verify(readonlyFlag).assertWritable(); - verifyZeroInteractions(cleartextSource); + verifyNoInteractions(cleartextSource); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java index 6c273b56..3bacaac5 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java @@ -1,6 +1,7 @@ package org.cryptomator.cryptofs; import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags; +import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.MatcherAssert; @@ -22,7 +23,7 @@ public class CryptoFileSystemPropertiesTest { - private final KeyLoader keyLoader = Mockito.mock(KeyLoader.class); + private final MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); @Test public void testSetNoPassphrase() { diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index c64d062d..ea73af4f 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -12,7 +12,9 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import org.cryptomator.cryptofs.ch.CleartextFileChannel; -import org.cryptomator.cryptolib.api.InvalidPassphraseException; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterAll; @@ -73,14 +75,15 @@ public class CryptoFileSystemProviderIntegrationTest { @TestInstance(TestInstance.Lifecycle.PER_CLASS) class WithLimitedPaths { - private KeyLoader keyLoader = ignored -> new byte[64]; + private byte[] rawKey = new byte[64]; + private MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(rawKey); private CryptoFileSystem fs; private Path shortFilePath; private Path shortSymlinkPath; private Path shortDirPath; @BeforeAll - public void setup(@TempDir Path tmpDir) throws IOException { + public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { CryptoFileSystemProvider.initialize(tmpDir, "vault.cryptomator", "MASTERKEY_FILE", keyLoader); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // @@ -90,7 +93,7 @@ public void setup(@TempDir Path tmpDir) throws IOException { .build(); fs = CryptoFileSystemProvider.newFileSystem(tmpDir, properties); } - + @BeforeEach public void setupEach() throws IOException { shortFilePath = fs.getPath("/short-enough.txt"); @@ -100,7 +103,7 @@ public void setupEach() throws IOException { Files.createDirectory(shortDirPath); Files.createSymbolicLink(shortSymlinkPath, shortFilePath); } - + @AfterEach public void tearDownEach() throws IOException { Files.deleteIfExists(shortFilePath); @@ -160,7 +163,7 @@ public void testCopyExceedingPathLengthLimit(String path) { Assertions.assertTrue(Files.exists(src)); Assertions.assertTrue(Files.notExists(dst)); } - + } @Nested @@ -169,8 +172,8 @@ public void testCopyExceedingPathLengthLimit(String path) { class InMemory { private FileSystem tmpFs; - private KeyLoader keyLoader1; - private KeyLoader keyLoader2; + private MasterkeyLoader keyLoader1; + private MasterkeyLoader keyLoader2; private Path pathToVault1; private Path pathToVault2; private Path vaultConfigFile1; @@ -185,8 +188,8 @@ public void setup() throws IOException { byte[] key2 = new byte[64]; Arrays.fill(key1, (byte) 0x55); Arrays.fill(key2, (byte) 0x77); - keyLoader1 = ignored -> Arrays.copyOf(key1, 64); - keyLoader2 = ignored -> Arrays.copyOf(key2, 64); + keyLoader1 = ignored -> Masterkey.createFromRaw(key1); + keyLoader2 = ignored -> Masterkey.createFromRaw(key2); pathToVault1 = tmpFs.getPath("/vaultDir1"); pathToVault2 = tmpFs.getPath("/vaultDir2"); Files.createDirectory(pathToVault1); @@ -520,10 +523,10 @@ class PosixTests { private FileSystem fs; @BeforeAll - public void setup(@TempDir Path tmpDir) throws IOException { + public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { Path pathToVault = tmpDir.resolve("vaultDir1"); Files.createDirectories(pathToVault); - KeyLoader keyLoader = ignored -> new byte[64]; + MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "MASTERKEY_FILE", keyLoader); fs = CryptoFileSystemProvider.newFileSystem(pathToVault, cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); } @@ -610,10 +613,10 @@ class WindowsTests { private FileSystem fs; @BeforeAll - public void setup(@TempDir Path tmpDir) throws IOException { + public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { Path pathToVault = tmpDir.resolve("vaultDir1"); Files.createDirectories(pathToVault); - KeyLoader keyLoader = ignored -> new byte[64]; + MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "MASTERKEY_FILE", keyLoader); fs = CryptoFileSystemProvider.newFileSystem(pathToVault, cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index fd088071..489bb05b 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -2,9 +2,10 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; -import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags; import org.cryptomator.cryptofs.ch.AsyncDelegatingFileChannel; -import org.cryptomator.cryptolib.api.InvalidPassphraseException; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -26,7 +27,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.LinkOption; -import java.nio.file.NoSuchFileException; import java.nio.file.NotDirectoryException; import java.nio.file.OpenOption; import java.nio.file.Path; @@ -46,17 +46,15 @@ import static java.util.Arrays.asList; import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; import static org.cryptomator.cryptofs.CryptoFileSystemProvider.containsVault; -import static org.cryptomator.cryptofs.CryptoFileSystemProvider.newFileSystem; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class CryptoFileSystemProviderTest { + private final MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); private final CryptoFileSystems fileSystems = mock(CryptoFileSystems.class); private final CryptoPath cryptoPath = mock(CryptoPath.class); @@ -168,31 +166,31 @@ public void testInitializeFailWithNotDirectoryException() { Path pathToVault = fs.getPath("/vaultDir"); Assertions.assertThrows(NotDirectoryException.class, () -> { - CryptoFileSystemProvider.initialize(pathToVault, "irrelevant.txt", "irrelevant", ignored -> new byte[0]); + CryptoFileSystemProvider.initialize(pathToVault, "irrelevant.txt", "irrelevant", keyLoader); }); } @Test - public void testInitialize() throws IOException { + public void testInitialize() throws IOException, MasterkeyLoadingFailedException { FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); Path pathToVault = fs.getPath("/vaultDir"); Path vaultConfigFile = pathToVault.resolve("vault.cryptomator"); Path dataDir = pathToVault.resolve("d"); Files.createDirectory(pathToVault); - CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "MASTERKEY_FILE", ignored -> new byte[64]); + CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "MASTERKEY_FILE", keyLoader); Assertions.assertTrue(Files.isDirectory(dataDir)); Assertions.assertTrue(Files.isRegularFile(vaultConfigFile)); } @Test - public void testNewFileSystem() throws IOException { + public void testNewFileSystem() throws IOException, MasterkeyLoadingFailedException { Path pathToVault = Path.of("/vaultDir"); URI uri = CryptoFileSystemUri.create(pathToVault); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // - .withKeyLoader(ignored -> new byte[64]) // + .withKeyLoader(keyLoader) // .build(); inTest.newFileSystem(uri, properties); @@ -284,7 +282,7 @@ public void testNewAsyncFileChannelReturnsAsyncDelegatingFileChannel() throws IO when(cryptoFileSystem.newFileChannel(cryptoPath, options)).thenReturn(channel); AsynchronousFileChannel result = inTest.newAsynchronousFileChannel(cryptoPath, options, executor); - + MatcherAssert.assertThat(result, instanceOf(AsyncDelegatingFileChannel.class)); } @@ -405,7 +403,7 @@ public void testCheckAccessDelegatesToFileSystem() throws IOException { } @Test - public void testGetFileStoreDelegatesToFileSystem() throws IOException { + public void testGetFileStoreDelegatesToFileSystem() { CryptoFileStore fileStore = mock(CryptoFileStore.class); when(cryptoFileSystem.getFileStore()).thenReturn(fileStore); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java index 5452bf4c..5a694017 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java @@ -1,6 +1,9 @@ package org.cryptomator.cryptofs; import org.cryptomator.cryptofs.common.DeletingFileVisitor; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -68,11 +71,12 @@ public void testCreateWithPathComponents() throws URISyntaxException { } @Test - public void testCreateWithPathToVaultFromNonDefaultProvider() throws IOException { + public void testCreateWithPathToVaultFromNonDefaultProvider() throws IOException, MasterkeyLoadingFailedException { Path tempDir = createTempDirectory("CryptoFileSystemUrisTest").toAbsolutePath(); try { - CryptoFileSystemProvider.initialize(tempDir, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); - FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(tempDir, cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); + MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); + CryptoFileSystemProvider.initialize(tempDir, "vault.cryptomator", "irrelevant", keyLoader); + FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(tempDir, cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); Path absolutePathToVault = fileSystem.getPath("a").toAbsolutePath(); URI uri = CryptoFileSystemUri.create(absolutePathToVault, "a", "b"); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java index 9542102c..c1a2536c 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java @@ -4,6 +4,9 @@ import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.CryptorProvider; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -36,9 +39,10 @@ public class CryptoFileSystemsTest { private final CryptoFileSystemProperties properties = mock(CryptoFileSystemProperties.class); private final CryptoFileSystemComponent cryptoFileSystemComponent = mock(CryptoFileSystemComponent.class); private final CryptoFileSystemImpl cryptoFileSystem = mock(CryptoFileSystemImpl.class); - private final VaultConfig.VaultConfigLoader configLoader = mock(VaultConfig.VaultConfigLoader.class); + private final VaultConfig.UnverifiedVaultConfig configLoader = mock(VaultConfig.UnverifiedVaultConfig.class); + private final MasterkeyLoader keyLoader = mock(MasterkeyLoader.class); + private final Masterkey masterkey = mock(Masterkey.class); private final byte[] rawKey = new byte[64]; - private final KeyLoader keyLoader = mock(KeyLoader.class); private final VaultConfig vaultConfig = mock(VaultConfig.class); private final VaultCipherMode cipherMode = mock(VaultCipherMode.class); private final SecureRandom csprng = Mockito.mock(SecureRandom.class); @@ -53,7 +57,7 @@ public class CryptoFileSystemsTest { private final CryptoFileSystems inTest = new CryptoFileSystems(cryptoFileSystemComponentBuilder, capabilityChecker, csprng); @BeforeEach - public void setup() throws IOException { + public void setup() throws IOException, MasterkeyLoadingFailedException { filesClass = Mockito.mockStatic(Files.class); vaultConficClass = Mockito.mockStatic(VaultConfig.class); @@ -65,11 +69,12 @@ public void setup() throws IOException { vaultConficClass.when(() -> VaultConfig.decode("jwt-vault-config")).thenReturn(configLoader); when(VaultConfig.decode("jwt-vault-config")).thenReturn(configLoader); when(configLoader.getKeyId()).thenReturn("key-id"); - when(keyLoader.loadKey("key-id")).thenReturn(rawKey); - when(configLoader.load(rawKey, Constants.VAULT_VERSION)).thenReturn(vaultConfig); + when(keyLoader.loadKey("key-id")).thenReturn(masterkey); + when(masterkey.getEncoded()).thenReturn(rawKey); + when(configLoader.verify(rawKey, Constants.VAULT_VERSION)).thenReturn(vaultConfig); when(vaultConfig.getCiphermode()).thenReturn(cipherMode); when(cipherMode.getCryptorProvider(csprng)).thenReturn(cryptorProvider); - when(cryptorProvider.createFromRawKey(rawKey)).thenReturn(cryptor); + when(cryptorProvider.withKey(masterkey)).thenReturn(cryptor); when(cryptoFileSystemComponentBuilder.cryptor(any())).thenReturn(cryptoFileSystemComponentBuilder); when(cryptoFileSystemComponentBuilder.vaultConfig(any())).thenReturn(cryptoFileSystemComponentBuilder); when(cryptoFileSystemComponentBuilder.pathToVault(any())).thenReturn(cryptoFileSystemComponentBuilder); @@ -91,7 +96,7 @@ public void testContainsReturnsFalseForNonContainedFileSystem() { } @Test - public void testContainsReturnsTrueForContainedFileSystem() throws IOException { + public void testContainsReturnsTrueForContainedFileSystem() throws IOException, MasterkeyLoadingFailedException { CryptoFileSystemImpl impl = inTest.create(provider, pathToVault, properties); Assertions.assertSame(cryptoFileSystem, impl); @@ -105,7 +110,7 @@ public void testContainsReturnsTrueForContainedFileSystem() throws IOException { } @Test - public void testCreateThrowsFileSystemAlreadyExistsExceptionIfInvokedWithSamePathTwice() throws IOException { + public void testCreateThrowsFileSystemAlreadyExistsExceptionIfInvokedWithSamePathTwice() throws IOException, MasterkeyLoadingFailedException { inTest.create(provider, pathToVault, properties); Assertions.assertThrows(FileSystemAlreadyExistsException.class, () -> { @@ -114,7 +119,7 @@ public void testCreateThrowsFileSystemAlreadyExistsExceptionIfInvokedWithSamePat } @Test - public void testCreateDoesNotThrowFileSystemAlreadyExistsExceptionIfFileSystemIsRemovedBefore() throws IOException { + public void testCreateDoesNotThrowFileSystemAlreadyExistsExceptionIfFileSystemIsRemovedBefore() throws IOException, MasterkeyLoadingFailedException { CryptoFileSystemImpl fileSystem1 = inTest.create(provider, pathToVault, properties); Assertions.assertTrue(inTest.contains(fileSystem1)); inTest.remove(fileSystem1); @@ -125,7 +130,7 @@ public void testCreateDoesNotThrowFileSystemAlreadyExistsExceptionIfFileSystemIs } @Test - public void testGetReturnsFileSystemForPathIfItExists() throws IOException { + public void testGetReturnsFileSystemForPathIfItExists() throws IOException, MasterkeyLoadingFailedException { CryptoFileSystemImpl fileSystem = inTest.create(provider, pathToVault, properties); Assertions.assertTrue(inTest.contains(fileSystem)); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoPathTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoPathTest.java index 8340d524..6475fd38 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoPathTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoPathTest.java @@ -35,6 +35,7 @@ import static org.hamcrest.Matchers.lessThan; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -268,7 +269,7 @@ public void testToRealPathDoesNotResolveSymlinksWhenNotFollowingLinks() throws I Path normalizedAndAbsolute = new CryptoPath(fileSystem, symlinks, asList("a", "b"), true); Assertions.assertEquals(normalizedAndAbsolute, inTest.toRealPath(LinkOption.NOFOLLOW_LINKS)); - verifyZeroInteractions(symlinks); + verifyNoInteractions(symlinks); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java index 94f299dd..b79c4d1b 100644 --- a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java @@ -10,6 +10,9 @@ import com.google.common.base.Strings; import org.cryptomator.cryptofs.common.Constants; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; @@ -41,11 +44,12 @@ public class DeleteNonEmptyCiphertextDirectoryIntegrationTest { private static FileSystem fileSystem; @BeforeAll - public static void setupClass(@TempDir Path tmpDir) throws IOException { + public static void setupClass(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { pathToVault = tmpDir.resolve("vault"); Files.createDirectory(pathToVault); - CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); + MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); + CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", keyLoader); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/MoveOperationTest.java b/src/test/java/org/cryptomator/cryptofs/MoveOperationTest.java index 1ec8cb53..4744bb43 100644 --- a/src/test/java/org/cryptomator/cryptofs/MoveOperationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/MoveOperationTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -54,7 +55,7 @@ public void setup() { public void testMoveWithEqualPathDoesNothing() throws IOException { inTest.move(aPathFromFsA, aPathFromFsA); - verifyZeroInteractions(aPathFromFsA); + verifyNoInteractions(aPathFromFsA); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java index 6e634c73..1cf8fe53 100644 --- a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java @@ -8,6 +8,9 @@ *******************************************************************************/ package org.cryptomator.cryptofs; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -25,18 +28,20 @@ public class ReadmeCodeSamplesTest { @Test - public void testReadmeCodeSampleUsingFileSystemConstructionMethodA(@TempDir Path storageLocation) throws IOException { - CryptoFileSystemProvider.initialize(storageLocation, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); - FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(storageLocation, CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); + public void testReadmeCodeSampleUsingFileSystemConstructionMethodA(@TempDir Path storageLocation) throws IOException, MasterkeyLoadingFailedException { + MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); + CryptoFileSystemProvider.initialize(storageLocation, "vault.cryptomator", "irrelevant", keyLoader); + FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(storageLocation, CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); runCodeSample(fileSystem); } @Test - public void testReadmeCodeSampleUsingFileSystemConstructionMethodB(@TempDir Path storageLocation) throws IOException { + public void testReadmeCodeSampleUsingFileSystemConstructionMethodB(@TempDir Path storageLocation) throws IOException, MasterkeyLoadingFailedException { URI uri = CryptoFileSystemUri.create(storageLocation); - CryptoFileSystemProvider.initialize(storageLocation, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); - FileSystem fileSystem = FileSystems.newFileSystem(uri, CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); + MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); + CryptoFileSystemProvider.initialize(storageLocation, "vault.cryptomator", "irrelevant", keyLoader); + FileSystem fileSystem = FileSystems.newFileSystem(uri, CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); runCodeSample(fileSystem); } diff --git a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java index 742c07a0..433927a3 100644 --- a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java @@ -8,6 +8,9 @@ *******************************************************************************/ package org.cryptomator.cryptofs; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -29,11 +32,12 @@ public class RealFileSystemIntegrationTest { private static FileSystem fileSystem; @BeforeAll - public static void setupClass(@TempDir Path tmpDir) throws IOException { + public static void setupClass(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { pathToVault = tmpDir.resolve("vault"); Files.createDirectory(pathToVault); - CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); + MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); + CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", keyLoader); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/RootDirectoryInitializerTest.java b/src/test/java/org/cryptomator/cryptofs/RootDirectoryInitializerTest.java index 02fd7364..c1f0491b 100644 --- a/src/test/java/org/cryptomator/cryptofs/RootDirectoryInitializerTest.java +++ b/src/test/java/org/cryptomator/cryptofs/RootDirectoryInitializerTest.java @@ -9,6 +9,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -43,7 +44,7 @@ public void testInitializeDoesNotCreateRootDirectoryIfReadonlyFlagIsSet() throws inTest.initialize(cleartextRoot); - verifyZeroInteractions(filesWrapper); + verifyNoInteractions(filesWrapper); } } diff --git a/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java index 3eec8355..9d13385d 100644 --- a/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java +++ b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java @@ -6,6 +6,9 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; import org.cryptomator.cryptofs.common.Constants; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -21,27 +24,29 @@ public class VaultConfigTest { @Test public void testLoadMalformedToken() { Assertions.assertThrows(VaultConfigLoadException.class, () -> { - VaultConfig.load("hello world", ignored -> new byte[64], 42); + VaultConfig.load("hello world", ignored -> null, 42); }); } @Nested public class WithValidToken { - private byte[] key = new byte[64]; + private byte[] rawKey = new byte[64]; + private Masterkey key = Mockito.mock(Masterkey.class); private VaultConfig originalConfig; private String token; @BeforeEach public void setup() { - Arrays.fill(key, (byte) 0x55); + Arrays.fill(rawKey, (byte) 0x55); + Mockito.when(key.getEncoded()).thenReturn(rawKey); originalConfig = VaultConfig.createNew().cipherMode(VaultCipherMode.SIV_CTRMAC).maxFilenameLength(220).build(); - token = originalConfig.toToken("TEST_KEY", key); + token = originalConfig.toToken("TEST_KEY", rawKey); } @Test - public void testSuccessfulLoad() throws VaultConfigLoadException, KeyLoadingFailedException { + public void testSuccessfulLoad() throws VaultConfigLoadException, MasterkeyLoadingFailedException { var loaded = VaultConfig.load(token, ignored -> key, originalConfig.getVaultVersion()); Assertions.assertEquals(originalConfig.getId(), loaded.getId()); @@ -53,7 +58,8 @@ public void testSuccessfulLoad() throws VaultConfigLoadException, KeyLoadingFail @ParameterizedTest @ValueSource(ints = {0, 1, 2, 3, 10, 20, 30, 63}) public void testLoadWithInvalidKey(int pos) { - key[pos] = (byte) 0x77; + rawKey[pos] = (byte) 0x77; + Mockito.when(key.getEncoded()).thenReturn(rawKey); Assertions.assertThrows(VaultKeyInvalidException.class, () -> { VaultConfig.load(token, ignored -> key, originalConfig.getVaultVersion()); @@ -73,19 +79,21 @@ public void testCreateNew() { } @Test - public void testLoadExisting() throws KeyLoadingFailedException, VaultConfigLoadException { + public void testLoadExisting() throws VaultConfigLoadException, MasterkeyLoadingFailedException { var decodedJwt = Mockito.mock(DecodedJWT.class); var formatClaim = Mockito.mock(Claim.class); var ciphermodeClaim = Mockito.mock(Claim.class); var maxFilenameLenClaim = Mockito.mock(Claim.class); - var keyLoader = Mockito.mock(KeyLoader.class); + var keyLoader = Mockito.mock(MasterkeyLoader.class); + var key = Mockito.mock(Masterkey.class); var verification = Mockito.mock(Verification.class); var verifier = Mockito.mock(JWTVerifier.class); Mockito.when(decodedJwt.getKeyId()).thenReturn("key-id"); Mockito.when(decodedJwt.getClaim("format")).thenReturn(formatClaim); Mockito.when(decodedJwt.getClaim("ciphermode")).thenReturn(ciphermodeClaim); Mockito.when(decodedJwt.getClaim("maxFilenameLen")).thenReturn(maxFilenameLenClaim); - Mockito.when(keyLoader.loadKey("key-id")).thenReturn(new byte[64]); + Mockito.when(keyLoader.loadKey("key-id")).thenReturn(key); + Mockito.when(key.getEncoded()).thenReturn(new byte[64]); Mockito.when(verification.withClaim("format", 42)).thenReturn(verification); Mockito.when(verification.build()).thenReturn(verifier); Mockito.when(verifier.verify(decodedJwt)).thenReturn(decodedJwt); diff --git a/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java b/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java index 3a46c54f..2b7778dd 100644 --- a/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java +++ b/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java @@ -1,6 +1,9 @@ package org.cryptomator.cryptofs; import com.google.common.jimfs.Jimfs; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -27,12 +30,13 @@ public class WriteFileWhileReadonlyChannelIsOpenTest { private Path root; @BeforeEach - public void setup() throws IOException { + public void setup() throws IOException, MasterkeyLoadingFailedException { inMemoryFs = Jimfs.newFileSystem(); Path pathToVault = inMemoryFs.getRootDirectories().iterator().next().resolve("vault"); Files.createDirectory(pathToVault); - CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); + MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); + CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", keyLoader); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); root = fileSystem.getPath("/"); } diff --git a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java index bd51fa80..35dd526c 100644 --- a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java @@ -10,6 +10,9 @@ import com.google.common.jimfs.Jimfs; import org.cryptomator.cryptofs.CryptoFileSystemProvider; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -52,12 +55,13 @@ public class FileAttributeIntegrationTest { private static FileSystem fileSystem; @BeforeAll - public static void setupClass() throws IOException { + public static void setupClass() throws IOException, MasterkeyLoadingFailedException { inMemoryFs = Jimfs.newFileSystem(); pathToVault = inMemoryFs.getRootDirectories().iterator().next().resolve("vault"); Files.createDirectory(pathToVault); - CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", ignored -> new byte[64]); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(ignored -> new byte[64]).build()); + MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); + CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", keyLoader); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); } @AfterAll diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9SInflatorTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9SInflatorTest.java index 1c771b0c..9bdb7f8b 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/C9SInflatorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/C9SInflatorTest.java @@ -30,7 +30,7 @@ public void setup() { } @Test - public void inflateDeflated() throws IOException { + public void inflateDeflated() throws IOException, AuthenticationFailedException { Node deflated = new Node(Paths.get("foo.c9s")); Mockito.when(longFileNameProvider.inflate(deflated.ciphertextPath)).thenReturn("foo.c9r"); Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.eq("foo"), Mockito.any())).thenReturn("hello world.txt"); @@ -52,7 +52,7 @@ public void inflateUninflatableDueToIOException() throws IOException { } @Test - public void inflateUninflatableDueToInvalidCiphertext() throws IOException { + public void inflateUninflatableDueToInvalidCiphertext() throws IOException, AuthenticationFailedException { Node deflated = new Node(Paths.get("foo.c9s")); Mockito.when(longFileNameProvider.inflate(deflated.ciphertextPath)).thenReturn("foo.c9r"); Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.eq("foo"), Mockito.any())).thenThrow(new AuthenticationFailedException("peng!")); diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java index 3c1e0ba6..6cf26197 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java @@ -58,7 +58,7 @@ public void testInvalidBase64Pattern(String input) { @Test @DisplayName("process canonical filename") - public void testProcessFullMatch() { + public void testProcessFullMatch() throws AuthenticationFailedException { Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn("helloWorld.txt"); Node input = new Node(Paths.get("aaaaBBBBccccDDDDeeeeFFFF.c9r")); @@ -81,7 +81,7 @@ public void testProcessFullMatch() { "foo_aaaaBBBBcccc_--_11112222_foo.c9r", "aaaaBBBBccccDDDDeeeeFFFF___aaaaBBBBcccc_--_11112222----aaaaBBBBccccDDDDeeeeFFFF.c9r", }) - public void testProcessPartialMatch(String filename) { + public void testProcessPartialMatch(String filename) throws AuthenticationFailedException { Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.any(), Mockito.any())).then(invocation -> { String ciphertext = invocation.getArgument(1); if (ciphertext.equals("aaaaBBBBcccc_--_11112222")) { @@ -107,7 +107,7 @@ public void testProcessPartialMatch(String filename) { "aaaaBBBB$$$$DDDDeeeeFFFF.c9r", "aaaaBBBBxxxxDDDDeeeeFFFF.c9r", }) - public void testProcessNoMatch(String filename) { + public void testProcessNoMatch(String filename) throws AuthenticationFailedException { Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.any(), Mockito.any())).thenThrow(new AuthenticationFailedException("Invalid ciphertext.")); Node input = new Node(Paths.get(filename)); diff --git a/src/test/java/org/cryptomator/cryptofs/fh/ChunkCacheTest.java b/src/test/java/org/cryptomator/cryptofs/fh/ChunkCacheTest.java index a15bf342..2e5bc541 100644 --- a/src/test/java/org/cryptomator/cryptofs/fh/ChunkCacheTest.java +++ b/src/test/java/org/cryptomator/cryptofs/fh/ChunkCacheTest.java @@ -2,16 +2,12 @@ import org.cryptomator.cryptofs.CryptoFileSystemStats; import org.cryptomator.cryptolib.api.AuthenticationFailedException; -import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.io.IOException; -import java.util.List; -import static java.util.Arrays.asList; -import static org.hamcrest.Matchers.containsInAnyOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -26,7 +22,7 @@ public class ChunkCacheTest { private final ChunkCache inTest = new ChunkCache(chunkLoader, chunkSaver, stats); @Test - public void testGetInvokesLoaderIfEntryNotInCache() throws IOException { + public void testGetInvokesLoaderIfEntryNotInCache() throws IOException, AuthenticationFailedException { long index = 42L; ChunkData data = mock(ChunkData.class); when(chunkLoader.load(index)).thenReturn(data); @@ -36,7 +32,7 @@ public void testGetInvokesLoaderIfEntryNotInCache() throws IOException { } @Test - public void testGetDoesNotInvokeLoaderIfEntryInCacheFromPreviousGet() throws IOException { + public void testGetDoesNotInvokeLoaderIfEntryInCacheFromPreviousGet() throws IOException, AuthenticationFailedException { long index = 42L; ChunkData data = mock(ChunkData.class); when(chunkLoader.load(index)).thenReturn(data); @@ -58,7 +54,7 @@ public void testGetDoesNotInvokeLoaderIfEntryInCacheFromPreviousSet() throws IOE } @Test - public void testGetInvokesSaverIfMaxEntriesInCacheAreReachedAndAnEntryNotInCacheIsRequested() throws IOException { + public void testGetInvokesSaverIfMaxEntriesInCacheAreReachedAndAnEntryNotInCacheIsRequested() throws IOException, AuthenticationFailedException { long firstIndex = 42L; long indexNotInCache = 40L; ChunkData firstData = mock(ChunkData.class); @@ -108,7 +104,7 @@ public void testGetInvokesSaverIfMaxEntriesInCacheAreReachedAndAnEntryInCacheIsS } @Test - public void testGetRethrowsAuthenticationFailedExceptionFromLoader() throws IOException { + public void testGetRethrowsAuthenticationFailedExceptionFromLoader() throws IOException, AuthenticationFailedException { long index = 42L; AuthenticationFailedException authenticationFailedException = new AuthenticationFailedException("Foo"); when(chunkLoader.load(index)).thenThrow(authenticationFailedException); @@ -120,7 +116,7 @@ public void testGetRethrowsAuthenticationFailedExceptionFromLoader() throws IOEx } @Test - public void testGetThrowsUncheckedExceptionFromLoader() throws IOException { + public void testGetThrowsUncheckedExceptionFromLoader() throws IOException, AuthenticationFailedException { long index = 42L; RuntimeException uncheckedException = new RuntimeException(); when(chunkLoader.load(index)).thenThrow(uncheckedException); @@ -132,7 +128,7 @@ public void testGetThrowsUncheckedExceptionFromLoader() throws IOException { } @Test - public void testInvalidateAllInvokesSaverForAllEntriesInCache() throws IOException { + public void testInvalidateAllInvokesSaverForAllEntriesInCache() throws IOException, AuthenticationFailedException { long index = 42L; long index2 = 43L; ChunkData data = mock(ChunkData.class); @@ -148,16 +144,7 @@ public void testInvalidateAllInvokesSaverForAllEntriesInCache() throws IOExcepti } @Test - @SuppressWarnings("unchecked") - public void testLoaderThrowsOnlyIOException() throws NoSuchMethodException { - List<Class<?>> exceptionsThrownByLoader = asList(ChunkLoader.class.getMethod("load", Long.class).getExceptionTypes()); - - // INFO: when adding exception types here add a corresponding test like testGetRethrowsIOExceptionFromLoader - MatcherAssert.assertThat(exceptionsThrownByLoader, containsInAnyOrder(IOException.class)); - } - - @Test - public void testGetRethrowsIOExceptionFromLoader() throws IOException { + public void testGetRethrowsIOExceptionFromLoader() throws IOException, AuthenticationFailedException { long index = 42L; IOException ioException = new IOException(); when(chunkLoader.load(index)).thenThrow(ioException); diff --git a/src/test/java/org/cryptomator/cryptofs/fh/ChunkLoaderTest.java b/src/test/java/org/cryptomator/cryptofs/fh/ChunkLoaderTest.java index 498e1b24..0705da4c 100644 --- a/src/test/java/org/cryptomator/cryptofs/fh/ChunkLoaderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/fh/ChunkLoaderTest.java @@ -1,6 +1,7 @@ package org.cryptomator.cryptofs.fh; import org.cryptomator.cryptofs.CryptoFileSystemStats; +import org.cryptomator.cryptolib.api.AuthenticationFailedException; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.FileContentCryptor; import org.cryptomator.cryptolib.api.FileHeader; @@ -49,7 +50,7 @@ public void setup() throws IOException { } @Test - public void testChunkLoaderReturnsEmptyDataOfChunkAfterEndOfFile() throws IOException { + public void testChunkLoaderReturnsEmptyDataOfChunkAfterEndOfFile() throws IOException, AuthenticationFailedException { long chunkIndex = 482L; long chunkOffset = chunkIndex * CIPHERTEXT_CHUNK_SIZE + HEADER_SIZE; when(chunkIO.read(argThat(hasAtLeastRemaining(CIPHERTEXT_CHUNK_SIZE)), eq(chunkOffset))).thenReturn(-1); @@ -63,7 +64,7 @@ public void testChunkLoaderReturnsEmptyDataOfChunkAfterEndOfFile() throws IOExce } @Test - public void testChunkLoaderReturnsDecryptedDataOfChunkInsideFile() throws IOException { + public void testChunkLoaderReturnsDecryptedDataOfChunkInsideFile() throws IOException, AuthenticationFailedException { long chunkIndex = 482L; long chunkOffset = chunkIndex * CIPHERTEXT_CHUNK_SIZE + HEADER_SIZE; Supplier<ByteBuffer> decryptedData = () -> repeat(9).times(CLEARTEXT_CHUNK_SIZE).asByteBuffer(); @@ -81,7 +82,7 @@ public void testChunkLoaderReturnsDecryptedDataOfChunkInsideFile() throws IOExce } @Test - public void testChunkLoaderReturnsDecrytedDataOfChunkContainingEndOfFile() throws IOException { + public void testChunkLoaderReturnsDecrytedDataOfChunkContainingEndOfFile() throws IOException, AuthenticationFailedException { long chunkIndex = 482L; long chunkOffset = chunkIndex * CIPHERTEXT_CHUNK_SIZE + HEADER_SIZE; Supplier<ByteBuffer> decryptedData = () -> repeat(9).times(CLEARTEXT_CHUNK_SIZE - 3).asByteBuffer(); diff --git a/src/test/java/org/cryptomator/cryptofs/fh/ChunkSaverTest.java b/src/test/java/org/cryptomator/cryptofs/fh/ChunkSaverTest.java index 3b8b2830..c2a669ae 100644 --- a/src/test/java/org/cryptomator/cryptofs/fh/ChunkSaverTest.java +++ b/src/test/java/org/cryptomator/cryptofs/fh/ChunkSaverTest.java @@ -18,6 +18,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.mockito.hamcrest.MockitoHamcrest.argThat; @@ -87,8 +88,8 @@ public void testChunkThatWasNotWrittenIsNotWritten() throws IOException { inTest.save(chunkIndex, chunkData); - verifyZeroInteractions(chunkIO); - verifyZeroInteractions(stats); + verifyNoInteractions(chunkIO); + verifyNoInteractions(stats); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java b/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java index 1f22043f..c8e92b69 100644 --- a/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java @@ -1,5 +1,6 @@ package org.cryptomator.cryptofs.fh; +import org.cryptomator.cryptolib.api.AuthenticationFailedException; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.FileHeader; import org.cryptomator.cryptolib.api.FileHeaderCryptor; @@ -51,7 +52,7 @@ class ExistingHeader { private FileChannel channel = Mockito.mock(FileChannel.class); @BeforeEach - public void setup() throws IOException { + public void setup() throws IOException, AuthenticationFailedException { byte[] headerBytes = "leHeader".getBytes(StandardCharsets.US_ASCII); when(fileHeaderCryptor.headerSize()).thenReturn(headerBytes.length); when(channel.read(Mockito.any(ByteBuffer.class), Mockito.eq(0l))).thenAnswer(invocation -> { @@ -65,7 +66,7 @@ public void setup() throws IOException { @Test @DisplayName("load") - public void testLoadExisting() throws IOException { + public void testLoadExisting() throws IOException, AuthenticationFailedException { FileHeader loadedHeader1 = inTest.loadExisting(channel); FileHeader loadedHeader2 = inTest.get(); FileHeader loadedHeader3 = inTest.get(); @@ -90,7 +91,7 @@ public void setup() throws IOException { } @AfterEach - public void tearDown() { + public void tearDown() throws AuthenticationFailedException { verify(fileHeaderCryptor, Mockito.never()).decryptHeader(Mockito.any()); } diff --git a/src/test/java/org/cryptomator/cryptofs/migration/MigrationComponentTest.java b/src/test/java/org/cryptomator/cryptofs/migration/MigrationComponentTest.java deleted file mode 100644 index fb43df89..00000000 --- a/src/test/java/org/cryptomator/cryptofs/migration/MigrationComponentTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.cryptomator.cryptofs.migration; - -import org.cryptomator.cryptofs.mocks.NullSecureRandom; -import org.cryptomator.cryptolib.Cryptors; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.security.NoSuchAlgorithmException; - -public class MigrationComponentTest { - - @Test - public void testAvailableMigrators() throws NoSuchAlgorithmException { - MigrationModule migrationModule = new MigrationModule(Cryptors.version1(NullSecureRandom.INSTANCE)); - TestMigrationComponent comp = DaggerTestMigrationComponent.builder().migrationModule(migrationModule).build(); - Assertions.assertFalse(comp.availableMigrators().isEmpty()); - } - -} diff --git a/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java b/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java index 32130047..0f6b9e4f 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java @@ -5,108 +5,245 @@ *******************************************************************************/ package org.cryptomator.cryptofs.migration; +import org.cryptomator.cryptofs.VaultConfig; +import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener; import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener.ContinuationResult; import org.cryptomator.cryptofs.migration.api.MigrationProgressListener; import org.cryptomator.cryptofs.migration.api.Migrator; import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException; -import org.cryptomator.cryptolib.api.InvalidPassphraseException; +import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; +import org.cryptomator.cryptolib.common.MasterkeyFile; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; import org.mockito.Mockito; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; +import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.spi.FileSystemProvider; import java.util.Collections; -import java.util.HashMap; import java.util.Map; public class MigratorsTest { - private ByteBuffer keyFile; + private MockedStatic<Files> filesClass; private Path pathToVault; private FileSystemCapabilityChecker fsCapabilityChecker; + private Path vaultConfigPath; + private Path masterkeyPath; @BeforeEach - public void setup() throws IOException { - keyFile = StandardCharsets.UTF_8.encode("{\"version\": 0000}"); - pathToVault = Mockito.mock(Path.class); + public void setup() { + filesClass = Mockito.mockStatic(Files.class); + pathToVault = Mockito.mock(Path.class, "path/to/vault"); fsCapabilityChecker = Mockito.mock(FileSystemCapabilityChecker.class); + vaultConfigPath = Mockito.mock(Path.class, "path/to/vault/vault.cryptomator"); + masterkeyPath = Mockito.mock(Path.class, "path/to/vault/masterkey.cryptomator"); - Path pathToMasterkey = Mockito.mock(Path.class); - FileSystem fs = Mockito.mock(FileSystem.class); - FileSystemProvider provider = Mockito.mock(FileSystemProvider.class); - SeekableByteChannel sbc = Mockito.mock(SeekableByteChannel.class); - - Mockito.when(pathToVault.resolve("masterkey.cryptomator")).thenReturn(pathToMasterkey); - Mockito.when(pathToMasterkey.getFileSystem()).thenReturn(fs); - Mockito.when(fs.provider()).thenReturn(provider); - Mockito.when(provider.newByteChannel(Mockito.eq(pathToMasterkey), Mockito.any(), Mockito.any())).thenReturn(sbc); - Mockito.when(sbc.size()).thenReturn((long) keyFile.remaining()); - Mockito.when(sbc.read(Mockito.any())).then(invocation -> { - ByteBuffer dst = invocation.getArgument(0); - int n = Math.min(keyFile.remaining(), dst.remaining()); - byte[] tmp = new byte[n]; - keyFile.get(tmp); - dst.put(tmp); - return n; - }); + Mockito.when(pathToVault.resolve("masterkey.cryptomator")).thenReturn(masterkeyPath); + Mockito.when(pathToVault.resolve("vault.cryptomator")).thenReturn(vaultConfigPath); } - @Test - public void testNeedsMigration() throws IOException { - Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); - boolean result = migrators.needsMigration(pathToVault, "masterkey.cryptomator"); - - Assertions.assertTrue(result); + @AfterEach + public void tearDown() { + filesClass.close(); } @Test - public void testNeedsNoMigration() throws IOException { - keyFile = StandardCharsets.UTF_8.encode("{\"version\": 9999}"); + @DisplayName("can't determine vault version without masterkey.cryptomator or vault.cryptomator") + public void throwsExceptionIfNeitherMasterkeyNorVaultConfigExists() { + filesClass.when(() -> Files.exists(vaultConfigPath)).thenReturn(false); + filesClass.when(() -> Files.exists(masterkeyPath)).thenReturn(false); Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); - boolean result = migrators.needsMigration(pathToVault, "masterkey.cryptomator"); - - Assertions.assertFalse(result); - } - @Test - public void testMigrateWithoutMigrators() { - Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); - Assertions.assertThrows(NoApplicableMigratorException.class, () -> { - migrators.migrate(pathToVault, "vault.cryptomator","masterkey.cryptomator", "secret", (state, progress) -> {}, (event) -> ContinuationResult.CANCEL); + IOException thrown = Assertions.assertThrows(IOException.class, () -> { + migrators.needsMigration(pathToVault, "vault.cryptomator", "masterkey.cryptomator"); }); + MatcherAssert.assertThat(thrown.getMessage(), CoreMatchers.containsString("Did not find vault.cryptomator nor masterkey.cryptomator")); } - - @Test - @SuppressWarnings("deprecation") - public void testMigrate() throws NoApplicableMigratorException, InvalidPassphraseException, IOException { - MigrationProgressListener progressListener = Mockito.mock(MigrationProgressListener.class); - MigrationContinuationListener continuationListener = Mockito.mock(MigrationContinuationListener.class); - Migrator migrator = Mockito.mock(Migrator.class); - Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker); - migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", progressListener, continuationListener); - Mockito.verify(migrator).migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", progressListener, continuationListener); + + @Nested + public class WithExistingVaultConfig { + + private MockedStatic<VaultConfig> vaultConfigClass; + private VaultConfig.UnverifiedVaultConfig unverifiedVaultConfig; + + @BeforeEach + public void setup() { + Assumptions.assumeFalse(Files.exists(masterkeyPath)); + vaultConfigClass = Mockito.mockStatic(VaultConfig.class); + unverifiedVaultConfig = Mockito.mock(VaultConfig.UnverifiedVaultConfig.class); + + filesClass.when(() -> Files.exists(vaultConfigPath)).thenReturn(true); + filesClass.when(() -> Files.readString(vaultConfigPath)).thenReturn("vault-config"); + vaultConfigClass.when(() -> VaultConfig.decode("vault-config")).thenReturn(unverifiedVaultConfig); + } + + @AfterEach + public void tearDown() { + vaultConfigClass.close(); + } + + @Test + @DisplayName("needs migration if vault version < Constants.VAULT_VERSION") + public void testNeedsMigration() throws IOException { + Mockito.when(unverifiedVaultConfig.allegedVaultVersion()).thenReturn(Constants.VAULT_VERSION - 1); + Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); + + boolean result = migrators.needsMigration(pathToVault, "vault.cryptomator", "masterkey.cryptomator"); + + Assertions.assertTrue(result); + } + + @Test + @DisplayName("needs no migration if vault version >= Constants.VAULT_VERSION") + public void testNeedsNoMigration() throws IOException { + Mockito.when(unverifiedVaultConfig.allegedVaultVersion()).thenReturn(Constants.VAULT_VERSION); + Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); + + boolean result = migrators.needsMigration(pathToVault, "vault.cryptomator", "masterkey.cryptomator"); + + Assertions.assertFalse(result); + } + + @Test + @DisplayName("throws NoApplicableMigratorException if no migrator was found") + public void testMigrateWithoutMigrators() { + Mockito.when(unverifiedVaultConfig.allegedVaultVersion()).thenReturn(42); + + Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); + Assertions.assertThrows(NoApplicableMigratorException.class, () -> { + migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", MigrationProgressListener.IGNORE, MigrationContinuationListener.CANCEL_ALWAYS); + }); + } + + @Test + @DisplayName("migrate successfully") + @SuppressWarnings("deprecation") + public void testMigrate() throws NoApplicableMigratorException, CryptoException, IOException { + MigrationProgressListener progressListener = Mockito.mock(MigrationProgressListener.class); + MigrationContinuationListener continuationListener = Mockito.mock(MigrationContinuationListener.class); + Migrator migrator = Mockito.mock(Migrator.class); + Mockito.when(unverifiedVaultConfig.allegedVaultVersion()).thenReturn(0); + Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker); + + migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", progressListener, continuationListener); + + Mockito.verify(migrator).migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", progressListener, continuationListener); + } + + @Test + @DisplayName("migrate throws UnsupportedVaultFormatException") + @SuppressWarnings("deprecation") + public void testMigrateUnsupportedVaultFormat() throws NoApplicableMigratorException, CryptoException, IOException { + Migrator migrator = Mockito.mock(Migrator.class); + Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker); + Mockito.when(unverifiedVaultConfig.allegedVaultVersion()).thenReturn(0); + Mockito.doThrow(new UnsupportedVaultFormatException(Integer.MAX_VALUE, 1)).when(migrator).migrate(Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any()); + + Assertions.assertThrows(IllegalStateException.class, () -> { + migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", MigrationProgressListener.IGNORE, MigrationContinuationListener.CANCEL_ALWAYS); + }); + } + } - @Test - @SuppressWarnings("deprecation") - public void testMigrateUnsupportedVaultFormat() throws NoApplicableMigratorException, InvalidPassphraseException, IOException { - Migrator migrator = Mockito.mock(Migrator.class); - Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker); - Mockito.doThrow(new UnsupportedVaultFormatException(Integer.MAX_VALUE, 1)).when(migrator).migrate(Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any()); - Assertions.assertThrows(IllegalStateException.class, () -> { - migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", (state, progress) -> {}, (event) -> ContinuationResult.CANCEL); - }); + @Nested + public class WithExistingMasterkeyFile { + + private MockedStatic<MasterkeyFile> masterkeyFileClass; + private MasterkeyFile masterkeyFile; + + @BeforeEach + public void setup() { + Assumptions.assumeFalse(Files.exists(vaultConfigPath)); + masterkeyFileClass = Mockito.mockStatic(MasterkeyFile.class); + masterkeyFile = Mockito.mock(MasterkeyFile.class); + + filesClass.when(() -> Files.exists(masterkeyPath)).thenReturn(true); + masterkeyFileClass.when(() -> MasterkeyFile.withContentFromFile(masterkeyPath)).thenReturn(masterkeyFile); + } + + @AfterEach + public void tearDown() { + masterkeyFileClass.close(); + } + + @Test + @DisplayName("needs migration if vault version < Constants.VAULT_VERSION") + public void testNeedsMigration() throws IOException { + Mockito.when(masterkeyFile.allegedVaultVersion()).thenReturn(Constants.VAULT_VERSION - 1); + Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); + + boolean result = migrators.needsMigration(pathToVault, "vault.cryptomator", "masterkey.cryptomator"); + + Assertions.assertTrue(result); + } + + @Test + @DisplayName("needs no migration if vault version >= Constants.VAULT_VERSION") + public void testNeedsNoMigration() throws IOException { + Mockito.when(masterkeyFile.allegedVaultVersion()).thenReturn(Constants.VAULT_VERSION); + Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); + + boolean result = migrators.needsMigration(pathToVault, "vault.cryptomator", "masterkey.cryptomator"); + + Assertions.assertFalse(result); + } + + @Test + @DisplayName("throws NoApplicableMigratorException if no migrator was found") + public void testMigrateWithoutMigrators() { + Mockito.when(masterkeyFile.allegedVaultVersion()).thenReturn(42); + + Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); + Assertions.assertThrows(NoApplicableMigratorException.class, () -> { + migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", MigrationProgressListener.IGNORE, MigrationContinuationListener.CANCEL_ALWAYS); + }); + } + + @Test + @DisplayName("migrate successfully") + @SuppressWarnings("deprecation") + public void testMigrate() throws NoApplicableMigratorException, CryptoException, IOException { + MigrationProgressListener progressListener = Mockito.mock(MigrationProgressListener.class); + MigrationContinuationListener continuationListener = Mockito.mock(MigrationContinuationListener.class); + Migrator migrator = Mockito.mock(Migrator.class); + Mockito.when(masterkeyFile.allegedVaultVersion()).thenReturn(0); + Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker); + + migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", progressListener, continuationListener); + + Mockito.verify(migrator).migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", progressListener, continuationListener); + } + + @Test + @DisplayName("migrate throws UnsupportedVaultFormatException") + @SuppressWarnings("deprecation") + public void testMigrateUnsupportedVaultFormat() throws NoApplicableMigratorException, CryptoException, IOException { + Migrator migrator = Mockito.mock(Migrator.class); + Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker); + Mockito.when(masterkeyFile.allegedVaultVersion()).thenReturn(0); + Mockito.doThrow(new UnsupportedVaultFormatException(Integer.MAX_VALUE, 1)).when(migrator).migrate(Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any()); + + Assertions.assertThrows(IllegalStateException.class, () -> { + migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", MigrationProgressListener.IGNORE, MigrationContinuationListener.CANCEL_ALWAYS); + }); + } + } } diff --git a/src/test/java/org/cryptomator/cryptofs/migration/TestMigrationComponent.java b/src/test/java/org/cryptomator/cryptofs/migration/TestMigrationComponent.java deleted file mode 100644 index 40089d2f..00000000 --- a/src/test/java/org/cryptomator/cryptofs/migration/TestMigrationComponent.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.cryptomator.cryptofs.migration; - -import java.util.Map; - -import org.cryptomator.cryptofs.migration.api.Migrator; - -import dagger.Component; - -@Component(modules = {MigrationModule.class}) -interface TestMigrationComponent extends MigrationComponent { - - Map<Migration, Migrator> availableMigrators(); - -} diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java index 5bc3105e..effecd6c 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java @@ -2,36 +2,40 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; -import org.cryptomator.cryptofs.common.MasterkeyBackupHelper; import org.cryptomator.cryptofs.common.Constants; +import org.cryptomator.cryptofs.common.MasterkeyBackupHelper; import org.cryptomator.cryptofs.migration.api.Migrator; import org.cryptomator.cryptofs.mocks.NullSecureRandom; -import org.cryptomator.cryptolib.Cryptors; -import org.cryptomator.cryptolib.api.Cryptor; -import org.cryptomator.cryptolib.api.CryptorProvider; -import org.cryptomator.cryptolib.api.KeyFile; +import org.cryptomator.cryptolib.api.CryptoException; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.common.MasterkeyFile; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; +import java.security.SecureRandom; import java.text.Normalizer; import java.text.Normalizer.Form; +import java.util.Optional; public class Version6MigratorTest { private FileSystem fs; private Path pathToVault; private Path masterkeyFile; - private CryptorProvider cryptorProvider; + private SecureRandom csprng = NullSecureRandom.INSTANCE; @BeforeEach public void setup() throws IOException { - cryptorProvider = Cryptors.version1(NullSecureRandom.INSTANCE); fs = Jimfs.newFileSystem(Configuration.unix()); pathToVault = fs.getPath("/vaultDir"); masterkeyFile = pathToVault.resolve("masterkey.cryptomator"); @@ -45,28 +49,31 @@ public void teardown() throws IOException { } @Test - public void testMigrate() throws IOException { + public void testMigrate() throws IOException, CryptoException { String oldPassword = Normalizer.normalize("ä", Form.NFD); String newPassword = Normalizer.normalize("ä", Form.NFC); Assertions.assertNotEquals(oldPassword, newPassword); - KeyFile beforeMigration = cryptorProvider.createNew().writeKeysToMasterkeyFile(oldPassword, 5); - Assertions.assertEquals(5, beforeMigration.getVersion()); - Files.write(masterkeyFile, beforeMigration.serialize()); - Path masterkeyBackupFile = pathToVault.resolve("masterkey.cryptomator" + MasterkeyBackupHelper.generateFileIdSuffix(beforeMigration.serialize()) + Constants.MASTERKEY_BACKUP_SUFFIX); + Masterkey masterkey = Masterkey.createNew(csprng); + byte[] beforeMigration = MasterkeyFile.lock(masterkey, oldPassword, new byte[0], 5, csprng); - Migrator migrator = new Version6Migrator(cryptorProvider); + Files.write(masterkeyFile, beforeMigration); + Path masterkeyBackupFile = pathToVault.resolve("masterkey.cryptomator" + MasterkeyBackupHelper.generateFileIdSuffix(beforeMigration) + Constants.MASTERKEY_BACKUP_SUFFIX); + + Migrator migrator = new Version6Migrator(csprng); migrator.migrate(pathToVault, null, "masterkey.cryptomator", oldPassword); - KeyFile afterMigration = KeyFile.parse(Files.readAllBytes(masterkeyFile)); - Assertions.assertEquals(6, afterMigration.getVersion()); - try (Cryptor cryptor = cryptorProvider.createFromKeyFile(afterMigration, newPassword, 6)) { - Assertions.assertNotNull(cryptor); + byte[] afterMigration = Files.readAllBytes(masterkeyFile); + String afterMigrationJson = new String(afterMigration, StandardCharsets.UTF_8); + MatcherAssert.assertThat(afterMigrationJson, CoreMatchers.containsString("\"version\": 6")); + + try (var keyLoader = MasterkeyFile.withContent(new ByteArrayInputStream(afterMigration)).unlock(newPassword, new byte[0], Optional.of(6))) { + Assertions.assertNotNull(keyLoader); } Assertions.assertTrue(Files.exists(masterkeyBackupFile)); - KeyFile backupKey = KeyFile.parse(Files.readAllBytes(masterkeyBackupFile)); - Assertions.assertEquals(5, backupKey.getVersion()); + String backedUpJson = Files.readString(masterkeyBackupFile, StandardCharsets.UTF_8); + MatcherAssert.assertThat(backedUpJson, CoreMatchers.containsString("\"version\": 5")); } } diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java index 2ebcfa25..eb841c7c 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java @@ -4,19 +4,22 @@ import com.google.common.jimfs.Jimfs; import org.cryptomator.cryptofs.migration.api.Migrator; import org.cryptomator.cryptofs.mocks.NullSecureRandom; -import org.cryptomator.cryptolib.Cryptors; -import org.cryptomator.cryptolib.api.Cryptor; -import org.cryptomator.cryptolib.api.CryptorProvider; -import org.cryptomator.cryptolib.api.KeyFile; +import org.cryptomator.cryptolib.api.CryptoException; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.common.MasterkeyFile; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; +import java.security.SecureRandom; public class Version7MigratorTest { @@ -25,11 +28,10 @@ public class Version7MigratorTest { private Path dataDir; private Path metaDir; private Path masterkeyFile; - private CryptorProvider cryptorProvider; + private SecureRandom csprng = NullSecureRandom.INSTANCE; @BeforeEach public void setup() throws IOException { - cryptorProvider = Cryptors.version1(NullSecureRandom.INSTANCE); fs = Jimfs.newFileSystem(Configuration.unix()); vaultRoot = fs.getPath("/vaultDir"); dataDir = vaultRoot.resolve("d"); @@ -38,10 +40,10 @@ public void setup() throws IOException { Files.createDirectory(vaultRoot); Files.createDirectory(dataDir); Files.createDirectory(metaDir); - try (Cryptor cryptor = cryptorProvider.createNew()) { - KeyFile keyFile = cryptor.writeKeysToMasterkeyFile("test", 6); - Files.write(masterkeyFile, keyFile.serialize()); - } + + Masterkey masterkey = Masterkey.createNew(csprng); + byte[] unmigrated = MasterkeyFile.lock(masterkey, "test", new byte[0], 6, csprng); + Files.write(masterkeyFile, unmigrated); } @AfterEach @@ -50,22 +52,19 @@ public void teardown() throws IOException { } @Test - public void testKeyfileGetsUpdates() throws IOException { - KeyFile beforeMigration = KeyFile.parse(Files.readAllBytes(masterkeyFile)); - Assertions.assertEquals(6, beforeMigration.getVersion()); - - Migrator migrator = new Version7Migrator(cryptorProvider); + public void testKeyfileGetsUpdates() throws CryptoException, IOException { + Migrator migrator = new Version7Migrator(csprng); migrator.migrate(vaultRoot, null, "masterkey.cryptomator", "test"); - KeyFile afterMigration = KeyFile.parse(Files.readAllBytes(masterkeyFile)); - Assertions.assertEquals(7, afterMigration.getVersion()); + String migrated = Files.readString(masterkeyFile, StandardCharsets.UTF_8); + MatcherAssert.assertThat(migrated, CoreMatchers.containsString("\"version\": 7")); } @Test - public void testMDirectoryGetsDeleted() throws IOException { - Migrator migrator = new Version7Migrator(cryptorProvider); + public void testMDirectoryGetsDeleted() throws CryptoException, IOException { + Migrator migrator = new Version7Migrator(csprng); migrator.migrate(vaultRoot, null, "masterkey.cryptomator", "test"); - + Assertions.assertFalse(Files.exists(metaDir)); } @@ -76,8 +75,8 @@ public void testMigrationFailsIfEncounteringUnsyncediCloudContent() throws IOExc Path fileBeforeMigration = dir.resolve("MZUWYZLOMFWWK===.icloud"); Files.createFile(fileBeforeMigration); - Migrator migrator = new Version7Migrator(cryptorProvider); - + Migrator migrator = new Version7Migrator(csprng); + IOException e = Assertions.assertThrows(PreMigrationVisitor.PreMigrationChecksFailedException.class, () -> { migrator.migrate(vaultRoot, null, "masterkey.cryptomator", "test"); }); @@ -85,14 +84,14 @@ public void testMigrationFailsIfEncounteringUnsyncediCloudContent() throws IOExc } @Test - public void testMigrationOfNormalFile() throws IOException { + public void testMigrationOfNormalFile() throws CryptoException, IOException { Path dir = dataDir.resolve("AA/BBBBBCCCCCDDDDDEEEEEFFFFFGGGGG"); Files.createDirectories(dir); Path fileBeforeMigration = dir.resolve("MZUWYZLOMFWWK==="); Path fileAfterMigration = dir.resolve("ZmlsZW5hbWU=.c9r"); Files.createFile(fileBeforeMigration); - Migrator migrator = new Version7Migrator(cryptorProvider); + Migrator migrator = new Version7Migrator(csprng); migrator.migrate(vaultRoot, null, "masterkey.cryptomator", "test"); Assertions.assertFalse(Files.exists(fileBeforeMigration)); @@ -100,14 +99,14 @@ public void testMigrationOfNormalFile() throws IOException { } @Test - public void testMigrationOfNormalDirectory() throws IOException { + public void testMigrationOfNormalDirectory() throws CryptoException, IOException { Path dir = dataDir.resolve("AA/BBBBBCCCCCDDDDDEEEEEFFFFFGGGGG"); Files.createDirectories(dir); Path fileBeforeMigration = dir.resolve("0MZUWYZLOMFWWK==="); Path fileAfterMigration = dir.resolve("ZmlsZW5hbWU=.c9r/dir.c9r"); Files.createFile(fileBeforeMigration); - Migrator migrator = new Version7Migrator(cryptorProvider); + Migrator migrator = new Version7Migrator(csprng); migrator.migrate(vaultRoot, null, "masterkey.cryptomator", "test"); Assertions.assertFalse(Files.exists(fileBeforeMigration)); @@ -115,14 +114,14 @@ public void testMigrationOfNormalDirectory() throws IOException { } @Test - public void testMigrationOfNormalSymlink() throws IOException { + public void testMigrationOfNormalSymlink() throws CryptoException, IOException { Path dir = dataDir.resolve("AA/BBBBBCCCCCDDDDDEEEEEFFFFFGGGGG"); Files.createDirectories(dir); Path fileBeforeMigration = dir.resolve("1SMZUWYZLOMFWWK==="); Path fileAfterMigration = dir.resolve("ZmlsZW5hbWU=.c9r/symlink.c9r"); Files.createFile(fileBeforeMigration); - Migrator migrator = new Version7Migrator(cryptorProvider); + Migrator migrator = new Version7Migrator(csprng); migrator.migrate(vaultRoot, null, "masterkey.cryptomator", "test"); Assertions.assertFalse(Files.exists(fileBeforeMigration)); diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java index dedd2af7..4bfb21c5 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java @@ -7,8 +7,12 @@ import org.cryptomator.cryptofs.migration.api.Migrator; import org.cryptomator.cryptofs.mocks.NullSecureRandom; import org.cryptomator.cryptolib.Cryptors; +import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.CryptorProvider; -import org.cryptomator.cryptolib.api.KeyFile; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.common.MasterkeyFile; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; @@ -16,9 +20,11 @@ import org.junit.jupiter.api.Test; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; +import java.security.SecureRandom; public class Version8MigratorTest { @@ -26,11 +32,10 @@ public class Version8MigratorTest { private Path pathToVault; private Path masterkeyFile; private Path vaultConfigFile; - private CryptorProvider cryptorProvider; + private SecureRandom csprng = NullSecureRandom.INSTANCE; @BeforeEach public void setup() throws IOException { - cryptorProvider = Cryptors.version1(NullSecureRandom.INSTANCE); fs = Jimfs.newFileSystem(Configuration.unix()); pathToVault = fs.getPath("/vaultDir"); masterkeyFile = pathToVault.resolve("masterkey.cryptomator"); @@ -44,17 +49,17 @@ public void teardown() throws IOException { } @Test - public void testMigrate() throws IOException { - KeyFile beforeMigration = cryptorProvider.createNew().writeKeysToMasterkeyFile("topsecret", 7); - Assumptions.assumeTrue(beforeMigration.getVersion() == 7); + public void testMigrate() throws CryptoException, IOException { + Masterkey masterkey = Masterkey.createNew(csprng); + byte[] unmigrated = MasterkeyFile.lock(masterkey, "topsecret", new byte[0], 7, csprng); Assumptions.assumeFalse(Files.exists(vaultConfigFile)); - Files.write(masterkeyFile, beforeMigration.serialize()); + Files.write(masterkeyFile, unmigrated); - Migrator migrator = new Version8Migrator(cryptorProvider); + Migrator migrator = new Version8Migrator(csprng); migrator.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "topsecret"); - KeyFile afterMigration = KeyFile.parse(Files.readAllBytes(masterkeyFile)); - Assertions.assertEquals(999, afterMigration.getVersion()); + String migrated = Files.readString(masterkeyFile, StandardCharsets.UTF_8); + MatcherAssert.assertThat(migrated, CoreMatchers.containsString("\"version\": 999")); Assertions.assertTrue(Files.exists(vaultConfigFile)); DecodedJWT token = JWT.decode(Files.readString(vaultConfigFile)); Assertions.assertNotNull(token.getId()); From 08c3871aa0ee92afa43d9274c13cfb40bbd0bfc8 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 7 Dec 2020 12:55:27 +0100 Subject: [PATCH 07/70] Use CryptoFileSystemProperties during vault initialization --- .../cryptofs/CryptoFileSystemProperties.java | 9 ------- .../cryptofs/CryptoFileSystemProvider.java | 24 +++++++++---------- ...toFileChannelWriteReadIntegrationTest.java | 5 ++-- ...yptoFileSystemProviderIntegrationTest.java | 18 ++++++++------ .../CryptoFileSystemProviderTest.java | 6 +++-- .../cryptofs/CryptoFileSystemUriTest.java | 6 ++--- ...ptyCiphertextDirectoryIntegrationTest.java | 24 +++++++++---------- .../cryptofs/ReadmeCodeSamplesTest.java | 10 ++++---- .../RealFileSystemIntegrationTest.java | 6 ++--- ...iteFileWhileReadonlyChannelIsOpenTest.java | 6 ++--- .../attr/FileAttributeIntegrationTest.java | 7 +++--- 11 files changed, 60 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index 05259575..50134717 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -95,15 +95,6 @@ public enum FileSystemFlags { * If present, the vault is opened in read-only mode. */ READONLY, - - /** - * If present, the maximum ciphertext path length (beginning from the root of the vault directory). - * <p> - * If exceeding the limit during a file operation, an exception is thrown. - * - * @since 1.9.8 - */ - MAX_PATH_LENGTH, } private final Set<Entry<String, Object>> entries; diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 18056194..f9b83495 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -118,25 +118,25 @@ public static CryptoFileSystem newFileSystem(Path pathToVault, CryptoFileSystemP /** * Creates a new vault at the given directory path. * - * @param pathToVault Path to an existing directory - * @param vaultConfigFilename Name of the vault config file - * @param keyId The ID of the key to associate with this vault - * @param keyLoader A key loader providing the masterkey for this new vault - * @throws NotDirectoryException If the given path is not an existing directory. - * @throws IOException If the vault structure could not be initialized due to I/O errors - * @throws MasterkeyLoadingFailedException + * @param pathToVault Path to an existing directory + * @param properties Parameters to use when writing the vault configuration + * @param keyId ID of the master key to use for this vault + * @throws NotDirectoryException If the given path is not an existing directory. + * @throws IOException If the vault structure could not be initialized due to I/O errors + * @throws MasterkeyLoadingFailedException If thrown by the supplied keyLoader * @since 2.0.0 */ - public static void initialize(Path pathToVault, String vaultConfigFilename, String keyId, MasterkeyLoader keyLoader) throws NotDirectoryException, IOException, MasterkeyLoadingFailedException { + public static void initialize(Path pathToVault, CryptoFileSystemProperties properties, String keyId) throws NotDirectoryException, IOException, MasterkeyLoadingFailedException { if (!Files.isDirectory(pathToVault)) { throw new NotDirectoryException(pathToVault.toString()); } byte[] rawKey = new byte[0]; - try (Masterkey key = keyLoader.loadKey(keyId)) { + try (Masterkey key = properties.keyLoader().loadKey(keyId)) { rawKey = key.getEncoded(); // save vault config: - Path vaultConfigPath = pathToVault.resolve(vaultConfigFilename); - var config = VaultConfig.createNew().cipherMode(VaultCipherMode.SIV_CTRMAC).maxFilenameLength(Constants.MAX_CIPHERTEXT_NAME_LENGTH).build(); + Path vaultConfigPath = pathToVault.resolve(properties.vaultConfigFilename()); + // TODO move ciphermode to properties + var config = VaultConfig.createNew().cipherMode(VaultCipherMode.SIV_CTRMAC).maxFilenameLength(properties.maxNameLength()).build(); var token = config.toToken(keyId, rawKey); Files.writeString(vaultConfigPath, token, StandardCharsets.US_ASCII, WRITE, CREATE_NEW); // create "d" dir: @@ -145,7 +145,7 @@ public static void initialize(Path pathToVault, String vaultConfigFilename, Stri } finally { Arrays.fill(rawKey, (byte) 0x00); } - assert containsVault(pathToVault, vaultConfigFilename); + assert containsVault(pathToVault, properties.vaultConfigFilename()); } /** diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java index 46f4a0a1..42c6a83a 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java @@ -136,8 +136,9 @@ public void beforeAll() throws IOException, MasterkeyLoadingFailedException { Path vaultPath = inMemoryFs.getPath("vault"); Files.createDirectories(vaultPath); MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProvider.initialize(vaultPath, "vault.cryptomator", "MASTERKEY_FILE", keyLoader); - fileSystem = new CryptoFileSystemProvider().newFileSystem(vaultPath, cryptoFileSystemProperties().withKeyLoader(keyLoader).withFlags().build()); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + CryptoFileSystemProvider.initialize(vaultPath, properties, "MASTERKEY_FILE"); + fileSystem = new CryptoFileSystemProvider().newFileSystem(vaultPath, properties); file = fileSystem.getPath("/test.txt"); } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index ea73af4f..2d7076d6 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -84,13 +84,13 @@ class WithLimitedPaths { @BeforeAll public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { - CryptoFileSystemProvider.initialize(tmpDir, "vault.cryptomator", "MASTERKEY_FILE", keyLoader); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // .withKeyLoader(keyLoader) // .withMaxPathLength(100) .build(); + CryptoFileSystemProvider.initialize(tmpDir, properties, "MASTERKEY_FILE"); fs = CryptoFileSystemProvider.newFileSystem(tmpDir, properties); } @@ -209,11 +209,13 @@ public void teardown() throws IOException { public void initializeVaults() { Assertions.assertAll( () -> { - CryptoFileSystemProvider.initialize(pathToVault1, "vault.cryptomator", "MASTERKEY_FILE", keyLoader1); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader1).build(); + CryptoFileSystemProvider.initialize(pathToVault1, properties, "MASTERKEY_FILE"); Assertions.assertTrue(Files.isDirectory(pathToVault1.resolve("d"))); Assertions.assertTrue(Files.isRegularFile(vaultConfigFile1)); }, () -> { - CryptoFileSystemProvider.initialize(pathToVault2, "vault.cryptomator", "MASTERKEY_FILE", keyLoader2); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader2).build(); + CryptoFileSystemProvider.initialize(pathToVault2, properties, "MASTERKEY_FILE"); Assertions.assertTrue(Files.isDirectory(pathToVault2.resolve("d"))); Assertions.assertTrue(Files.isRegularFile(vaultConfigFile2)); }); @@ -527,8 +529,9 @@ public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFail Path pathToVault = tmpDir.resolve("vaultDir1"); Files.createDirectories(pathToVault); MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "MASTERKEY_FILE", keyLoader); - fs = CryptoFileSystemProvider.newFileSystem(pathToVault, cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, "MASTERKEY_FILE"); + fs = CryptoFileSystemProvider.newFileSystem(pathToVault, properties); } @Nested @@ -617,8 +620,9 @@ public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFail Path pathToVault = tmpDir.resolve("vaultDir1"); Files.createDirectories(pathToVault); MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "MASTERKEY_FILE", keyLoader); - fs = CryptoFileSystemProvider.newFileSystem(pathToVault, cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, "MASTERKEY_FILE"); + fs = CryptoFileSystemProvider.newFileSystem(pathToVault, properties); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index 489bb05b..b65d91df 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -164,9 +164,10 @@ public void testGetSchemeReturnsCryptomatorScheme() { public void testInitializeFailWithNotDirectoryException() { FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); Path pathToVault = fs.getPath("/vaultDir"); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); Assertions.assertThrows(NotDirectoryException.class, () -> { - CryptoFileSystemProvider.initialize(pathToVault, "irrelevant.txt", "irrelevant", keyLoader); + CryptoFileSystemProvider.initialize(pathToVault, properties, "irrelevant"); }); } @@ -176,9 +177,10 @@ public void testInitialize() throws IOException, MasterkeyLoadingFailedException Path pathToVault = fs.getPath("/vaultDir"); Path vaultConfigFile = pathToVault.resolve("vault.cryptomator"); Path dataDir = pathToVault.resolve("d"); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); Files.createDirectory(pathToVault); - CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "MASTERKEY_FILE", keyLoader); + CryptoFileSystemProvider.initialize(pathToVault, properties, "MASTERKEY_FILE"); Assertions.assertTrue(Files.isDirectory(dataDir)); Assertions.assertTrue(Files.isRegularFile(vaultConfigFile)); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java index 5a694017..16605b5d 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java @@ -16,7 +16,6 @@ import java.nio.file.Paths; import static java.nio.file.Files.createTempDirectory; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; public class CryptoFileSystemUriTest { @@ -75,8 +74,9 @@ public void testCreateWithPathToVaultFromNonDefaultProvider() throws IOException Path tempDir = createTempDirectory("CryptoFileSystemUrisTest").toAbsolutePath(); try { MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProvider.initialize(tempDir, "vault.cryptomator", "irrelevant", keyLoader); - FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(tempDir, cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + CryptoFileSystemProvider.initialize(tempDir, properties, "irrelevant"); + FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(tempDir, properties); Path absolutePathToVault = fileSystem.getPath("a").toAbsolutePath(); URI uri = CryptoFileSystemUri.create(absolutePathToVault, "a", "b"); diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java index b79c4d1b..9b4417aa 100644 --- a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java @@ -26,14 +26,11 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; import static java.nio.file.StandardOpenOption.CREATE_NEW; -import static org.cryptomator.cryptofs.common.Constants.MAX_CIPHERTEXT_NAME_LENGTH; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; import static org.cryptomator.cryptofs.CryptoFileSystemUri.create; +import static org.cryptomator.cryptofs.common.Constants.MAX_CIPHERTEXT_NAME_LENGTH; /** * Regression tests https://github.com/cryptomator/cryptofs/issues/17. @@ -48,8 +45,9 @@ public static void setupClass(@TempDir Path tmpDir) throws IOException, Masterke pathToVault = tmpDir.resolve("vault"); Files.createDirectory(pathToVault); MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", keyLoader); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, "irrelevant"); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); } @Test @@ -58,7 +56,7 @@ public void testDeleteCiphertextDirectoryContainingNonCryptoFile() throws IOExce Files.createDirectory(cleartextDirectory); Path ciphertextDirectory = firstEmptyCiphertextDirectory(); - createFile(ciphertextDirectory, "foo01234.txt", new byte[] {65}); + createFile(ciphertextDirectory, "foo01234.txt", new byte[]{65}); Files.delete(cleartextDirectory); } @@ -77,9 +75,9 @@ public void testDeleteCiphertextDirectoryContainingDirectories() throws IOExcept // .... text.data Path foo0123 = createFolder(ciphertextDirectory, "foo0123"); Path foobar = createFolder(foo0123, "foobar"); - createFile(foo0123, "test.txt", new byte[] {65}); - createFile(foo0123, "text.data", new byte[] {65}); - createFile(foobar, "test.baz", new byte[] {65}); + createFile(foo0123, "test.txt", new byte[]{65}); + createFile(foo0123, "text.data", new byte[]{65}); + createFile(foobar, "test.baz", new byte[]{65}); Files.delete(cleartextDirectory); } @@ -92,7 +90,7 @@ public void testDeleteDirectoryContainingLongNameFileWithoutMetadata() throws IO Path ciphertextDirectory = firstEmptyCiphertextDirectory(); Path longNameDir = createFolder(ciphertextDirectory, "HHEZJURE.c9s"); - createFile(longNameDir, Constants.CONTENTS_FILE_NAME, new byte[] {65}); + createFile(longNameDir, Constants.CONTENTS_FILE_NAME, new byte[]{65}); Files.delete(cleartextDirectory); } @@ -106,7 +104,7 @@ public void testDeleteDirectoryContainingUnauthenticLongNameDirectoryFile() thro Path ciphertextDirectory = firstEmptyCiphertextDirectory(); Path longNameDir = createFolder(ciphertextDirectory, "HHEZJURE.c9s"); createFile(longNameDir, Constants.INFLATED_FILE_NAME, "HHEZJUREHHEZJUREHHEZJURE".getBytes()); - createFile(longNameDir, Constants.CONTENTS_FILE_NAME, new byte[] {65}); + createFile(longNameDir, Constants.CONTENTS_FILE_NAME, new byte[]{65}); Files.delete(cleartextDirectory); } @@ -115,7 +113,7 @@ public void testDeleteDirectoryContainingUnauthenticLongNameDirectoryFile() thro public void testDeleteNonEmptyDir() throws IOException { Path cleartextDirectory = fileSystem.getPath("/d"); Files.createDirectory(cleartextDirectory); - createFile(cleartextDirectory, "test", new byte[] {65}); + createFile(cleartextDirectory, "test", new byte[]{65}); Assertions.assertThrows(DirectoryNotEmptyException.class, () -> { Files.delete(cleartextDirectory); diff --git a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java index 1cf8fe53..8bbdca1c 100644 --- a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java @@ -30,8 +30,9 @@ public class ReadmeCodeSamplesTest { @Test public void testReadmeCodeSampleUsingFileSystemConstructionMethodA(@TempDir Path storageLocation) throws IOException, MasterkeyLoadingFailedException { MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProvider.initialize(storageLocation, "vault.cryptomator", "irrelevant", keyLoader); - FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(storageLocation, CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + CryptoFileSystemProvider.initialize(storageLocation, properties, "irrelevant"); + FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(storageLocation, properties); runCodeSample(fileSystem); } @@ -40,8 +41,9 @@ public void testReadmeCodeSampleUsingFileSystemConstructionMethodA(@TempDir Path public void testReadmeCodeSampleUsingFileSystemConstructionMethodB(@TempDir Path storageLocation) throws IOException, MasterkeyLoadingFailedException { URI uri = CryptoFileSystemUri.create(storageLocation); MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProvider.initialize(storageLocation, "vault.cryptomator", "irrelevant", keyLoader); - FileSystem fileSystem = FileSystems.newFileSystem(uri, CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + CryptoFileSystemProvider.initialize(storageLocation, properties, "irrelevant"); + FileSystem fileSystem = FileSystems.newFileSystem(uri, properties); runCodeSample(fileSystem); } diff --git a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java index 433927a3..7589fbcc 100644 --- a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java @@ -23,7 +23,6 @@ import java.nio.file.Path; import java.nio.file.attribute.UserPrincipal; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; import static org.cryptomator.cryptofs.CryptoFileSystemUri.create; public class RealFileSystemIntegrationTest { @@ -36,8 +35,9 @@ public static void setupClass(@TempDir Path tmpDir) throws IOException, Masterke pathToVault = tmpDir.resolve("vault"); Files.createDirectory(pathToVault); MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", keyLoader); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, "irrelevant"); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java b/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java index 2b7778dd..c3c0ce81 100644 --- a/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java +++ b/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java @@ -17,7 +17,6 @@ import java.nio.file.StandardOpenOption; import static java.nio.file.StandardOpenOption.READ; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; import static org.cryptomator.cryptofs.CryptoFileSystemUri.create; /** @@ -35,8 +34,9 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { Path pathToVault = inMemoryFs.getRootDirectories().iterator().next().resolve("vault"); Files.createDirectory(pathToVault); MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", keyLoader); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, "irrelevant"); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); root = fileSystem.getPath("/"); } diff --git a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java index 35dd526c..6fe7f503 100644 --- a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java @@ -9,6 +9,7 @@ package org.cryptomator.cryptofs.attr; import com.google.common.jimfs.Jimfs; +import org.cryptomator.cryptofs.CryptoFileSystemProperties; import org.cryptomator.cryptofs.CryptoFileSystemProvider; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoader; @@ -42,7 +43,6 @@ import static java.lang.Boolean.TRUE; import static java.lang.System.currentTimeMillis; import static java.nio.file.Files.readAttributes; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; import static org.cryptomator.cryptofs.CryptoFileSystemUri.create; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.greaterThan; @@ -60,8 +60,9 @@ public static void setupClass() throws IOException, MasterkeyLoadingFailedExcept pathToVault = inMemoryFs.getRootDirectories().iterator().next().resolve("vault"); Files.createDirectory(pathToVault); MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProvider.initialize(pathToVault, "vault.cryptomator", "irrelevant", keyLoader); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, "irrelevant"); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); } @AfterAll From 1ae75cf57e047da6cb74f2c4143509a5a09422ac Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 7 Dec 2020 13:32:18 +0100 Subject: [PATCH 08/70] Renamed cipher mode to cipher combo (as ciphermode is misleading) --- .../cryptofs/CryptoFileSystemProperties.java | 32 +++++++++++++++++-- .../cryptofs/CryptoFileSystemProvider.java | 4 +-- .../cryptofs/CryptoFileSystems.java | 2 +- ...tCipherMode.java => VaultCipherCombo.java} | 8 +++-- .../org/cryptomator/cryptofs/VaultConfig.java | 20 ++++++------ .../migration/v8/Version8Migrator.java | 2 +- .../CryptoFileSystemPropertiesTest.java | 6 ++++ .../cryptofs/CryptoFileSystemsTest.java | 6 ++-- .../cryptomator/cryptofs/VaultConfigTest.java | 14 ++++---- .../migration/v8/Version8MigratorTest.java | 2 +- 10 files changed, 65 insertions(+), 31 deletions(-) rename src/main/java/org/cryptomator/cryptofs/{VaultCipherMode.java => VaultCipherCombo.java} (78%) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index 50134717..7499509a 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -62,7 +62,6 @@ public class CryptoFileSystemProperties extends AbstractMap<String, Object> { static final MasterkeyLoader DEFAULT_KEYLOADER = null; - /** * Key identifying the name of the vault config file located inside the vault directory. * @@ -96,6 +95,15 @@ public enum FileSystemFlags { */ READONLY, } + /** + * Key identifying the combination of ciphers to use in a vault. Only meaningful during vault initialization. + * + * @since 2.0.0 + */ + public static final String PROPERTY_CIPHER_COMBO = "cipherCombo"; + + // TODO: change to SIV_GCM with issue 94 + static final VaultCipherCombo DEFAULT_CIPHER_COMBO = VaultCipherCombo.SIV_CTRMAC; private final Set<Entry<String, Object>> entries; @@ -106,7 +114,8 @@ private CryptoFileSystemProperties(Builder builder) { 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_NAME_LENGTH, builder.maxNameLength), // + Map.entry(PROPERTY_CIPHER_COMBO, builder.cipherCombo) // ); } @@ -114,6 +123,10 @@ MasterkeyLoader keyLoader() { return (MasterkeyLoader) get(PROPERTY_KEYLOADER); } + public VaultCipherCombo cipherCombo() { + return (VaultCipherCombo) get(PROPERTY_CIPHER_COMBO); + } + @SuppressWarnings("unchecked") public Set<FileSystemFlags> flags() { return (Set<FileSystemFlags>) get(PROPERTY_FILESYSTEM_FLAGS); @@ -187,6 +200,7 @@ public static CryptoFileSystemProperties wrap(Map<String, ?> properties) { */ public static class Builder { + public VaultCipherCombo cipherCombo = DEFAULT_CIPHER_COMBO; private MasterkeyLoader keyLoader = DEFAULT_KEYLOADER; private final Set<FileSystemFlags> flags = EnumSet.copyOf(DEFAULT_FILESYSTEM_FLAGS); private String vaultConfigFilename = DEFAULT_VAULTCONFIG_FILENAME; @@ -204,6 +218,7 @@ private Builder(Map<String, ?> properties) { 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(VaultCipherCombo.class, PROPERTY_CIPHER_COMBO, properties, this::withCipherCombo); } private <T> void checkedSet(Class<T> type, String key, Map<String, ?> properties, Consumer<T> setter) { @@ -242,6 +257,19 @@ public Builder withMaxNameLength(int maxNameLength) { return this; } + + /** + * Sets the cipher combo used during vault initialization. + * + * @param cipherCombo The cipher combo + * @return this + * @since 2.0.0 + */ + public Builder withCipherCombo(VaultCipherCombo cipherCombo) { + this.cipherCombo = cipherCombo; + return this; + } + /** * Sets the keyLoader for a CryptoFileSystem. * diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index f9b83495..0d27e4e3 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -12,7 +12,6 @@ import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import java.io.IOException; @@ -135,8 +134,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope rawKey = key.getEncoded(); // save vault config: Path vaultConfigPath = pathToVault.resolve(properties.vaultConfigFilename()); - // TODO move ciphermode to properties - var config = VaultConfig.createNew().cipherMode(VaultCipherMode.SIV_CTRMAC).maxFilenameLength(properties.maxNameLength()).build(); + var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).maxFilenameLength(properties.maxNameLength()).build(); var token = config.toToken(keyId, rawKey); Files.writeString(vaultConfigPath, token, StandardCharsets.US_ASCII, WRITE, CREATE_NEW); // create "d" dir: diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java index 12e90ecc..7bb928d4 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java @@ -67,7 +67,7 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT // synchronized access to non-threadsafe cryptoFileSystemComponentBuilder required private synchronized CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathToVault, CryptoFileSystemProperties properties, Masterkey masterkey, VaultConfig config) { - Cryptor cryptor = config.getCiphermode().getCryptorProvider(csprng).withKey(masterkey); + Cryptor cryptor = config.getCipherCombo().getCryptorProvider(csprng).withKey(masterkey); return cryptoFileSystemComponentBuilder // .cryptor(cryptor) // .vaultConfig(config) // diff --git a/src/main/java/org/cryptomator/cryptofs/VaultCipherMode.java b/src/main/java/org/cryptomator/cryptofs/VaultCipherCombo.java similarity index 78% rename from src/main/java/org/cryptomator/cryptofs/VaultCipherMode.java rename to src/main/java/org/cryptomator/cryptofs/VaultCipherCombo.java index c752a00b..fa2ec1c3 100644 --- a/src/main/java/org/cryptomator/cryptofs/VaultCipherMode.java +++ b/src/main/java/org/cryptomator/cryptofs/VaultCipherCombo.java @@ -1,13 +1,15 @@ package org.cryptomator.cryptofs; import org.cryptomator.cryptolib.Cryptors; -import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.CryptorProvider; import java.security.SecureRandom; import java.util.function.Function; -public enum VaultCipherMode { +/** + * A combination of different ciphers and/or cipher modes in a Cryptomator vault. + */ +public enum VaultCipherCombo { /** * AES-SIV for file name encryption * AES-CTR + HMAC for content encryption @@ -23,7 +25,7 @@ public enum VaultCipherMode { private final Function<SecureRandom, CryptorProvider> cryptorProvider; - VaultCipherMode(Function<SecureRandom, CryptorProvider> cryptorProvider) { + VaultCipherCombo(Function<SecureRandom, CryptorProvider> cryptorProvider) { this.cryptorProvider = cryptorProvider; } diff --git a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java index be4c342d..8389f324 100644 --- a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java +++ b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java @@ -30,25 +30,25 @@ public class VaultConfig { private static final String JSON_KEY_VAULTVERSION = "format"; - private static final String JSON_KEY_CIPHERCONFIG = "ciphermode"; + private static final String JSON_KEY_CIPHERCONFIG = "cipherCombo"; private static final String JSON_KEY_MAXFILENAMELEN = "maxFilenameLen"; private final String id; private final int vaultVersion; - private final VaultCipherMode ciphermode; + private final VaultCipherCombo cipherCombo; private final int maxFilenameLength; private VaultConfig(DecodedJWT verifiedConfig) { this.id = verifiedConfig.getId(); this.vaultVersion = verifiedConfig.getClaim(JSON_KEY_VAULTVERSION).asInt(); - this.ciphermode = VaultCipherMode.valueOf(verifiedConfig.getClaim(JSON_KEY_CIPHERCONFIG).asString()); + this.cipherCombo = VaultCipherCombo.valueOf(verifiedConfig.getClaim(JSON_KEY_CIPHERCONFIG).asString()); this.maxFilenameLength = verifiedConfig.getClaim(JSON_KEY_MAXFILENAMELEN).asInt(); } private VaultConfig(VaultConfigBuilder builder) { this.id = builder.id; this.vaultVersion = builder.vaultVersion; - this.ciphermode = builder.ciphermode; + this.cipherCombo = builder.cipherCombo; this.maxFilenameLength = builder.maxFilenameLength; } @@ -60,8 +60,8 @@ public int getVaultVersion() { return vaultVersion; } - public VaultCipherMode getCiphermode() { - return ciphermode; + public VaultCipherCombo getCipherCombo() { + return cipherCombo; } public int getMaxFilenameLength() { @@ -73,7 +73,7 @@ public String toToken(String keyId, byte[] rawKey) { .withKeyId(keyId) // .withJWTId(id) // .withClaim(JSON_KEY_VAULTVERSION, vaultVersion) // - .withClaim(JSON_KEY_CIPHERCONFIG, ciphermode.name()) // + .withClaim(JSON_KEY_CIPHERCONFIG, cipherCombo.name()) // .withClaim(JSON_KEY_MAXFILENAMELEN, maxFilenameLength) // .sign(Algorithm.HMAC256(rawKey)); } @@ -176,11 +176,11 @@ public static class VaultConfigBuilder { private final String id = UUID.randomUUID().toString(); private final int vaultVersion = Constants.VAULT_VERSION; - private VaultCipherMode ciphermode; + private VaultCipherCombo cipherCombo; private int maxFilenameLength; - public VaultConfigBuilder cipherMode(VaultCipherMode ciphermode) { - this.ciphermode = ciphermode; + public VaultConfigBuilder cipherCombo(VaultCipherCombo cipherCombo) { + this.cipherCombo = cipherCombo; return this; } diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java index 455622f6..2410f321 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java @@ -67,7 +67,7 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey .withJWTId(UUID.randomUUID().toString()) // .withKeyId("MASTERKEY_FILE") // .withClaim("format", 8) // - .withClaim("ciphermode", "SIV_CTRMAC") // + .withClaim("cipherCombo", "SIV_CTRMAC") // .withClaim("maxFilenameLen", 220) // .sign(algorithm); Files.writeString(vaultConfigFile, config, StandardCharsets.US_ASCII, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java index 3bacaac5..2f30b9a7 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java @@ -49,6 +49,7 @@ public void testSetReadonlyFlag() { anEntry(PROPERTY_MASTERKEY_FILENAME, DEFAULT_MASTERKEY_FILENAME), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // + anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)))); } @@ -71,6 +72,7 @@ public void testSetMasterkeyFilenameAndReadonlyFlag() { anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // + anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)))); } @@ -97,6 +99,7 @@ public void testFromMap() { anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, 1000), // anEntry(PROPERTY_MAX_NAME_LENGTH, 255), // + anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)))); } @@ -119,6 +122,7 @@ public void testWrapMapWithTrueReadonly() { anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // + anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)))); } @@ -141,6 +145,7 @@ public void testWrapMapWithFalseReadonly() { anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // + anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)))); } @@ -193,6 +198,7 @@ public void testWrapMapWithoutReadonly() { anEntry(PROPERTY_MASTERKEY_FILENAME, DEFAULT_MASTERKEY_FILENAME), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // + anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)) ) ); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java index c1a2536c..bdb2fee2 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java @@ -44,7 +44,7 @@ public class CryptoFileSystemsTest { private final Masterkey masterkey = mock(Masterkey.class); private final byte[] rawKey = new byte[64]; private final VaultConfig vaultConfig = mock(VaultConfig.class); - private final VaultCipherMode cipherMode = mock(VaultCipherMode.class); + private final VaultCipherCombo cipherCombo = mock(VaultCipherCombo.class); private final SecureRandom csprng = Mockito.mock(SecureRandom.class); private final CryptorProvider cryptorProvider = mock(CryptorProvider.class); private final Cryptor cryptor = mock(Cryptor.class); @@ -72,8 +72,8 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { when(keyLoader.loadKey("key-id")).thenReturn(masterkey); when(masterkey.getEncoded()).thenReturn(rawKey); when(configLoader.verify(rawKey, Constants.VAULT_VERSION)).thenReturn(vaultConfig); - when(vaultConfig.getCiphermode()).thenReturn(cipherMode); - when(cipherMode.getCryptorProvider(csprng)).thenReturn(cryptorProvider); + when(vaultConfig.getCipherCombo()).thenReturn(cipherCombo); + when(cipherCombo.getCryptorProvider(csprng)).thenReturn(cryptorProvider); when(cryptorProvider.withKey(masterkey)).thenReturn(cryptor); when(cryptoFileSystemComponentBuilder.cryptor(any())).thenReturn(cryptoFileSystemComponentBuilder); when(cryptoFileSystemComponentBuilder.vaultConfig(any())).thenReturn(cryptoFileSystemComponentBuilder); diff --git a/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java index 9d13385d..4e347b95 100644 --- a/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java +++ b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java @@ -41,7 +41,7 @@ public class WithValidToken { public void setup() { Arrays.fill(rawKey, (byte) 0x55); Mockito.when(key.getEncoded()).thenReturn(rawKey); - originalConfig = VaultConfig.createNew().cipherMode(VaultCipherMode.SIV_CTRMAC).maxFilenameLength(220).build(); + originalConfig = VaultConfig.createNew().cipherCombo(VaultCipherCombo.SIV_CTRMAC).maxFilenameLength(220).build(); token = originalConfig.toToken("TEST_KEY", rawKey); } @@ -51,7 +51,7 @@ public void testSuccessfulLoad() throws VaultConfigLoadException, MasterkeyLoadi Assertions.assertEquals(originalConfig.getId(), loaded.getId()); Assertions.assertEquals(originalConfig.getVaultVersion(), loaded.getVaultVersion()); - Assertions.assertEquals(originalConfig.getCiphermode(), loaded.getCiphermode()); + Assertions.assertEquals(originalConfig.getCipherCombo(), loaded.getCipherCombo()); Assertions.assertEquals(originalConfig.getMaxFilenameLength(), loaded.getMaxFilenameLength()); } @@ -70,11 +70,11 @@ public void testLoadWithInvalidKey(int pos) { @Test public void testCreateNew() { - var config = VaultConfig.createNew().cipherMode(VaultCipherMode.SIV_CTRMAC).maxFilenameLength(220).build(); + var config = VaultConfig.createNew().cipherCombo(VaultCipherCombo.SIV_CTRMAC).maxFilenameLength(220).build(); Assertions.assertNotNull(config.getId()); Assertions.assertEquals(Constants.VAULT_VERSION, config.getVaultVersion()); - Assertions.assertEquals(VaultCipherMode.SIV_CTRMAC, config.getCiphermode()); + Assertions.assertEquals(VaultCipherCombo.SIV_CTRMAC, config.getCipherCombo()); Assertions.assertEquals(220, config.getMaxFilenameLength()); } @@ -82,7 +82,7 @@ public void testCreateNew() { public void testLoadExisting() throws VaultConfigLoadException, MasterkeyLoadingFailedException { var decodedJwt = Mockito.mock(DecodedJWT.class); var formatClaim = Mockito.mock(Claim.class); - var ciphermodeClaim = Mockito.mock(Claim.class); + var cipherComboClaim = Mockito.mock(Claim.class); var maxFilenameLenClaim = Mockito.mock(Claim.class); var keyLoader = Mockito.mock(MasterkeyLoader.class); var key = Mockito.mock(Masterkey.class); @@ -90,7 +90,7 @@ public void testLoadExisting() throws VaultConfigLoadException, MasterkeyLoading var verifier = Mockito.mock(JWTVerifier.class); Mockito.when(decodedJwt.getKeyId()).thenReturn("key-id"); Mockito.when(decodedJwt.getClaim("format")).thenReturn(formatClaim); - Mockito.when(decodedJwt.getClaim("ciphermode")).thenReturn(ciphermodeClaim); + Mockito.when(decodedJwt.getClaim("cipherCombo")).thenReturn(cipherComboClaim); Mockito.when(decodedJwt.getClaim("maxFilenameLen")).thenReturn(maxFilenameLenClaim); Mockito.when(keyLoader.loadKey("key-id")).thenReturn(key); Mockito.when(key.getEncoded()).thenReturn(new byte[64]); @@ -98,7 +98,7 @@ public void testLoadExisting() throws VaultConfigLoadException, MasterkeyLoading Mockito.when(verification.build()).thenReturn(verifier); Mockito.when(verifier.verify(decodedJwt)).thenReturn(decodedJwt); Mockito.when(formatClaim.asInt()).thenReturn(42); - Mockito.when(ciphermodeClaim.asString()).thenReturn("SIV_CTRMAC"); + Mockito.when(cipherComboClaim.asString()).thenReturn("SIV_CTRMAC"); Mockito.when(maxFilenameLenClaim.asInt()).thenReturn(220); try (var jwtMock = Mockito.mockStatic(JWT.class)) { jwtMock.when(() -> JWT.decode("jwt-vault-config")).thenReturn(decodedJwt); diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java index 4bfb21c5..34ff3700 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java @@ -65,7 +65,7 @@ public void testMigrate() throws CryptoException, IOException { Assertions.assertNotNull(token.getId()); Assertions.assertEquals("MASTERKEY_FILE", token.getKeyId()); Assertions.assertEquals(8, token.getClaim("format").asInt()); - Assertions.assertEquals("SIV_CTRMAC", token.getClaim("ciphermode").asString()); + Assertions.assertEquals("SIV_CTRMAC", token.getClaim("cipherCombo").asString()); Assertions.assertEquals(220, token.getClaim("maxFilenameLen").asInt()); } From a1b12f3e3755163c62dca60e532216e1c528950b Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 7 Dec 2020 17:25:24 +0100 Subject: [PATCH 09/70] fix for jackson vulnerability --- pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pom.xml b/pom.xml index 956bd1ed..ddb22255 100644 --- a/pom.xml +++ b/pom.xml @@ -62,6 +62,17 @@ </repository> </repositories> + <dependencyManagement> + <dependencies> + <!-- temporary fix for XXE attack, can be removed once java-jwt is updated --> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>2.10.5.1</version> + </dependency> + </dependencies> + </dependencyManagement> + <dependencies> <dependency> <groupId>org.cryptomator</groupId> From d2a184b8b67898c8f102a19419e9262ef1265f57 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Tue, 8 Dec 2020 13:26:31 +0100 Subject: [PATCH 10/70] changed `containsVault` check to respect old vault format --- .../cryptofs/CryptoFileSystemProvider.java | 8 +++-- ...yptoFileSystemProviderIntegrationTest.java | 4 +-- .../CryptoFileSystemProviderTest.java | 31 ++++++++++++++++--- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 0d27e4e3..63ffec92 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -143,7 +143,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope } finally { Arrays.fill(rawKey, (byte) 0x00); } - assert containsVault(pathToVault, properties.vaultConfigFilename()); + assert containsVault(pathToVault, properties.vaultConfigFilename(), properties.masterkeyFilename()); } /** @@ -151,13 +151,15 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope * * @param pathToVault A directory path * @param vaultConfigFilename Name of the vault config file + * @param masterkeyFilename Name of the masterkey file * @return <code>true</code> if the directory seems to contain a vault. * @since 2.0.0 */ - public static boolean containsVault(Path pathToVault, String vaultConfigFilename) { + public static boolean containsVault(Path pathToVault, String vaultConfigFilename, String masterkeyFilename) { Path vaultConfigPath = pathToVault.resolve(vaultConfigFilename); + Path masterkeyPath = pathToVault.resolve(masterkeyFilename); Path dataDirPath = pathToVault.resolve(Constants.DATA_DIR_NAME); - return Files.isReadable(vaultConfigPath) && Files.isDirectory(dataDirPath); + return (Files.isReadable(vaultConfigPath) || Files.isReadable(masterkeyPath)) && Files.isDirectory(dataDirPath); } /** diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index 2d7076d6..2d5a4aa7 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -225,8 +225,8 @@ public void initializeVaults() { @Order(2) @DisplayName("get filesystem with incorrect credentials") public void testGetFsWithWrongCredentials() { - Assumptions.assumeTrue(CryptoFileSystemProvider.containsVault(pathToVault1, "vault.cryptomator")); - Assumptions.assumeTrue(CryptoFileSystemProvider.containsVault(pathToVault2, "vault.cryptomator")); + Assumptions.assumeTrue(CryptoFileSystemProvider.containsVault(pathToVault1, "vault.cryptomator", "masterkey.cryptomator")); + Assumptions.assumeTrue(CryptoFileSystemProvider.containsVault(pathToVault2, "vault.cryptomator", "masterkey.cryptomator")); Assertions.assertAll( () -> { URI fsUri = CryptoFileSystemUri.create(pathToVault1); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index b65d91df..48bed8a8 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -200,10 +200,27 @@ public void testNewFileSystem() throws IOException, MasterkeyLoadingFailedExcept Mockito.verify(fileSystems).create(Mockito.same(inTest), Mockito.eq(pathToVault), Mockito.eq(properties)); } + @Test + public void testContainsVaultReturnsTrueIfDirectoryContainsVaultConfigFileAndDataDir() throws IOException { + FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); + + String vaultConfigFilename = "vaultconfig.foo.baz"; + String masterkeyFilename = "masterkey.foo.baz"; + Path pathToVault = fs.getPath("/vaultDir"); + + Path vaultConfigFile = pathToVault.resolve(vaultConfigFilename); + Path dataDir = pathToVault.resolve("d"); + Files.createDirectories(dataDir); + Files.write(vaultConfigFile, new byte[0]); + + Assertions.assertTrue(containsVault(pathToVault, vaultConfigFilename, masterkeyFilename)); + } + @Test public void testContainsVaultReturnsTrueIfDirectoryContainsMasterkeyFileAndDataDir() throws IOException { FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); + String vaultConfigFilename = "vaultconfig.foo.baz"; String masterkeyFilename = "masterkey.foo.baz"; Path pathToVault = fs.getPath("/vaultDir"); @@ -212,34 +229,38 @@ public void testContainsVaultReturnsTrueIfDirectoryContainsMasterkeyFileAndDataD Files.createDirectories(dataDir); Files.write(masterkeyFile, new byte[0]); - Assertions.assertTrue(containsVault(pathToVault, masterkeyFilename)); + Assertions.assertTrue(containsVault(pathToVault, vaultConfigFilename, masterkeyFilename)); } @Test - public void testContainsVaultReturnsFalseIfDirectoryContainsNoMasterkeyFileButDataDir() throws IOException { + public void testContainsVaultReturnsFalseIfDirectoryContainsOnlyDataDir() throws IOException { FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); + String vaultConfigFilename = "vaultconfig.foo.baz"; String masterkeyFilename = "masterkey.foo.baz"; Path pathToVault = fs.getPath("/vaultDir"); Path dataDir = pathToVault.resolve("d"); Files.createDirectories(dataDir); - Assertions.assertFalse(containsVault(pathToVault, masterkeyFilename)); + Assertions.assertFalse(containsVault(pathToVault, vaultConfigFilename, masterkeyFilename)); } @Test - public void testContainsVaultReturnsFalseIfDirectoryContainsMasterkeyFileButNoDataDir() throws IOException { + public void testContainsVaultReturnsFalseIfDirectoryContainsNoDataDir() throws IOException { FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); + String vaultConfigFilename = "vaultconfig.foo.baz"; String masterkeyFilename = "masterkey.foo.baz"; Path pathToVault = fs.getPath("/vaultDir"); + Path vaultConfigFile = pathToVault.resolve(vaultConfigFilename); Path masterkeyFile = pathToVault.resolve(masterkeyFilename); Files.createDirectories(pathToVault); + Files.write(vaultConfigFile, new byte[0]); Files.write(masterkeyFile, new byte[0]); - Assertions.assertFalse(containsVault(pathToVault, masterkeyFilename)); + Assertions.assertFalse(containsVault(pathToVault, vaultConfigFilename, masterkeyFilename)); } @Test From 22843d8e80d7c39b80bef09f0f538a05a1e83344 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Tue, 8 Dec 2020 13:34:18 +0100 Subject: [PATCH 11/70] fixes #94 --- .../cryptofs/CryptoFileSystemProperties.java | 3 +-- .../org/cryptomator/cryptofs/VaultCipherCombo.java | 13 ++++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index 7499509a..f942c22d 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -102,8 +102,7 @@ public enum FileSystemFlags { */ public static final String PROPERTY_CIPHER_COMBO = "cipherCombo"; - // TODO: change to SIV_GCM with issue 94 - static final VaultCipherCombo DEFAULT_CIPHER_COMBO = VaultCipherCombo.SIV_CTRMAC; + static final VaultCipherCombo DEFAULT_CIPHER_COMBO = VaultCipherCombo.SIV_GCM; private final Set<Entry<String, Object>> entries; diff --git a/src/main/java/org/cryptomator/cryptofs/VaultCipherCombo.java b/src/main/java/org/cryptomator/cryptofs/VaultCipherCombo.java index fa2ec1c3..101cdd0c 100644 --- a/src/main/java/org/cryptomator/cryptofs/VaultCipherCombo.java +++ b/src/main/java/org/cryptomator/cryptofs/VaultCipherCombo.java @@ -14,14 +14,13 @@ public enum VaultCipherCombo { * AES-SIV for file name encryption * AES-CTR + HMAC for content encryption */ - SIV_CTRMAC(Cryptors::version1); + SIV_CTRMAC(Cryptors::version1), -// TODO enable eventually (issue 94): -// /** -// * AES-SIV for file name encryption -// * AES-GCM for content encryption -// */ -// SIV_GCM(Cryptors::version2); + /** + * AES-SIV for file name encryption + * AES-GCM for content encryption + */ + SIV_GCM(Cryptors::version2); private final Function<SecureRandom, CryptorProvider> cryptorProvider; From 790c20cabda06921fb1443d61b295825625733fa Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Tue, 8 Dec 2020 13:41:16 +0100 Subject: [PATCH 12/70] bump version and suppress false positive in owasp dependency check --- pom.xml | 2 +- suppression.xml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ddb22255..4a3bda84 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.cryptomator</groupId> <artifactId>cryptofs</artifactId> - <version>1.10.0-SNAPSHOT</version> + <version>2.0.0-SNAPSHOT</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> diff --git a/suppression.xml b/suppression.xml index 9edca825..23090b9b 100644 --- a/suppression.xml +++ b/suppression.xml @@ -6,4 +6,9 @@ <gav>org.slf4j:slf4j-api:1.7.25</gav> <cve>CVE-2018-8088</cve> </suppress> + <suppress> + <notes><![CDATA[ Upstream fix backported from 2.11.0 to 2.10.5.1, see https://github.com/FasterXML/jackson-databind/issues/2589#issuecomment-714833837. ]]></notes> + <gav>com.fasterxml.jackson.core:jackson-databind:2.10.5.1</gav> + <cve>CVE-2020-25649</cve> + </suppress> </suppressions> \ No newline at end of file From 883cd55422aacfd8d39090e1abc951d893b98349 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Tue, 8 Dec 2020 13:46:27 +0100 Subject: [PATCH 13/70] preparing 2.0.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4a3bda84..8fc4ddeb 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.cryptomator</groupId> <artifactId>cryptofs</artifactId> - <version>2.0.0-SNAPSHOT</version> + <version>2.0.0</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> From 96a4465368457c2bc68a9f7e7ab4fe5e4168dc46 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Tue, 8 Dec 2020 13:55:19 +0100 Subject: [PATCH 14/70] use release profile for release builds --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f6a99184..849d810f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,7 +50,7 @@ jobs: path: target/cryptofs-${{ env.BUILD_VERSION }}.jar # what about target/dependency-list.txt? - name: Build and deploy to jcenter if: startsWith(github.ref, 'refs/tags/') - run: mvn -B deploy -DskipTests + run: mvn -B deploy -DskipTests -Prelease env: BINTRAY_USERNAME: cryptobot BINTRAY_API_KEY: ${{ secrets.BINTRAY_API_KEY }} From 33d7ef102f9f6838ca1944815e01e762b6ca9201 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Tue, 8 Dec 2020 14:05:55 +0100 Subject: [PATCH 15/70] javadoc fixes --- .../cryptomator/cryptofs/CryptoFileSystemProvider.java | 1 - .../org/cryptomator/cryptofs/CryptoFileSystemUri.java | 1 + .../cryptofs/migration/v7/Version7Migrator.java | 10 +++++----- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 63ffec92..2256a727 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -72,7 +72,6 @@ * <p> * Afterwards you can use the created {@code FileSystem} to create paths, do directory listings, create files and so on. * <p> - * <p> * To create a new FileSystem from a URI using {@link FileSystems#newFileSystem(URI, Map)} you may have a look at {@link CryptoFileSystemUri}. * * @see CryptoFileSystemUri diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemUri.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemUri.java index 15f4299f..fbf866bc 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemUri.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemUri.java @@ -69,6 +69,7 @@ static Path uncCompatibleUriToPath(URI uri) { * * @param pathToVault path to the vault * @param pathComponentsInsideVault path components to node inside the vault + * @return An URI pointing to the root of the CryptoFileSystem */ public static URI create(Path pathToVault, String... pathComponentsInsideVault) { try { diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java index e041282d..0503d72b 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java @@ -34,15 +34,15 @@ * Renames ciphertext names: * * <ul> - * <li>Files: BASE32== -> base64==.c9r</li> - * <li>Dirs: 0BASE32== -> base64==.c9r/dir.c9r</li> - * <li>Symlinks: 1SBASE32== -> base64.c9r/symlink.c9r</li> + * <li>Files: BASE32== → base64==.c9r</li> + * <li>Dirs: 0BASE32== → base64==.c9r/dir.c9r</li> + * <li>Symlinks: 1SBASE32== → base64.c9r/symlink.c9r</li> * </ul> * <p> * Shortened names: * <ul> - * <li>shortened.lng -> shortened.c9s</li> - * <li>m/shortened.lng -> shortened.c9s/contents.c9r</li> + * <li>shortened.lng → shortened.c9s</li> + * <li>m/shortened.lng → shortened.c9s/contents.c9r</li> * </ul> */ public class Version7Migrator implements Migrator { From de4d0faee36f42acdfc2b956be34cfbba87f7bb1 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 18 Jan 2021 15:29:49 +0100 Subject: [PATCH 16/70] dependency updates --- pom.xml | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index 4a3bda84..b935f7e5 100644 --- a/pom.xml +++ b/pom.xml @@ -18,14 +18,14 @@ <!-- dependencies --> <cryptolib.version>2.0.0-beta2</cryptolib.version> - <jwt.version>3.11.0</jwt.version> - <dagger.version>2.29.1</dagger.version> - <guava.version>30.0-jre</guava.version> + <jwt.version>3.12.0</jwt.version> + <dagger.version>2.31</dagger.version> + <guava.version>30.1-jre</guava.version> <slf4j.version>1.7.30</slf4j.version> <!-- test dependencies --> <junit.jupiter.version>5.7.0</junit.jupiter.version> - <mockito.version>3.6.0</mockito.version> + <mockito.version>3.7.7</mockito.version> <hamcrest.version>2.2</hamcrest.version> </properties> @@ -62,17 +62,6 @@ </repository> </repositories> - <dependencyManagement> - <dependencies> - <!-- temporary fix for XXE attack, can be removed once java-jwt is updated --> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-databind</artifactId> - <version>2.10.5.1</version> - </dependency> - </dependencies> - </dependencyManagement> - <dependencies> <dependency> <groupId>org.cryptomator</groupId> @@ -167,7 +156,7 @@ <plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> - <version>6.0.3</version> + <version>6.0.5</version> <configuration> <cveValidForHours>24</cveValidForHours> <failBuildOnCVSS>0</failBuildOnCVSS> From 0e5cd91f905745d31e3318a771217cdceb27c0cb Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 25 Jan 2021 19:12:08 +0100 Subject: [PATCH 17/70] adjustments to new cryptolib API --- pom.xml | 2 +- .../cryptofs/CryptoFileSystemComponent.java | 1 - .../cryptofs/CryptoFileSystemProperties.java | 70 ++++++++++------- .../cryptofs/CryptoFileSystemProvider.java | 29 ++++--- .../CryptoFileSystemProviderComponent.java | 10 ++- .../CryptoFileSystemProviderModule.java | 10 --- .../cryptofs/CryptoFileSystems.java | 3 +- .../org/cryptomator/cryptofs/VaultConfig.java | 5 +- .../cryptofs/common/Constants.java | 3 + .../cryptofs/migration/Migrators.java | 5 +- .../migration/v6/Version6Migrator.java | 11 +-- .../migration/v7/Version7Migrator.java | 11 +-- .../migration/v8/Version8Migrator.java | 10 +-- ...toFileChannelWriteReadIntegrationTest.java | 17 ++-- .../CryptoFileSystemPropertiesTest.java | 78 ++++++++----------- ...yptoFileSystemProviderIntegrationTest.java | 50 +++++++----- .../CryptoFileSystemProviderTest.java | 17 ++-- .../cryptofs/CryptoFileSystemUriTest.java | 9 ++- .../cryptofs/CryptoFileSystemsTest.java | 7 +- ...ptyCiphertextDirectoryIntegrationTest.java | 10 ++- .../cryptofs/ReadmeCodeSamplesTest.java | 17 ++-- .../RealFileSystemIntegrationTest.java | 10 ++- .../cryptomator/cryptofs/VaultConfigTest.java | 30 +++---- ...iteFileWhileReadonlyChannelIsOpenTest.java | 10 ++- .../attr/FileAttributeIntegrationTest.java | 10 ++- .../cryptofs/migration/MigratorsTest.java | 25 +++--- .../migration/v6/Version6MigratorTest.java | 12 +-- .../migration/v7/Version7MigratorTest.java | 6 +- .../migration/v8/Version8MigratorTest.java | 8 +- 29 files changed, 264 insertions(+), 222 deletions(-) diff --git a/pom.xml b/pom.xml index b935f7e5..2d156f01 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- dependencies --> - <cryptolib.version>2.0.0-beta2</cryptolib.version> + <cryptolib.version>2.0.0-beta3</cryptolib.version> <jwt.version>3.12.0</jwt.version> <dagger.version>2.31</dagger.version> <guava.version>30.1-jre</guava.version> diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemComponent.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemComponent.java index da268113..d82b2675 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemComponent.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemComponent.java @@ -1,6 +1,5 @@ package org.cryptomator.cryptofs; -import com.auth0.jwt.interfaces.DecodedJWT; import dagger.BindsInstance; import dagger.Subcomponent; import org.cryptomator.cryptolib.api.Cryptor; diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index f942c22d..a8571317 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -11,6 +11,7 @@ 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; @@ -18,6 +19,7 @@ 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; @@ -58,9 +60,9 @@ public class CryptoFileSystemProperties extends AbstractMap<String, Object> { * * @since 2.0.0 */ - public static final String PROPERTY_KEYLOADER = "keyLoader"; + public static final String PROPERTY_KEYLOADERS = "keyLoaders"; - static final MasterkeyLoader DEFAULT_KEYLOADER = null; + static final Collection<MasterkeyLoader> DEFAULT_KEYLOADERS = Set.of(); /** * Key identifying the name of the vault config file located inside the vault directory. @@ -95,6 +97,7 @@ public enum FileSystemFlags { */ READONLY, } + /** * Key identifying the combination of ciphers to use in a vault. Only meaningful during vault initialization. * @@ -108,7 +111,7 @@ public enum FileSystemFlags { private CryptoFileSystemProperties(Builder builder) { this.entries = Set.of( // - Map.entry(PROPERTY_KEYLOADER, builder.keyLoader), // + Map.entry(PROPERTY_KEYLOADERS, builder.keyLoaders), // Map.entry(PROPERTY_FILESYSTEM_FLAGS, builder.flags), // Map.entry(PROPERTY_VAULTCONFIG_FILENAME, builder.vaultConfigFilename), // Map.entry(PROPERTY_MASTERKEY_FILENAME, builder.masterkeyFilename), // @@ -118,8 +121,24 @@ private CryptoFileSystemProperties(Builder builder) { ); } - MasterkeyLoader keyLoader() { - return (MasterkeyLoader) get(PROPERTY_KEYLOADER); + 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); } public VaultCipherCombo cipherCombo() { @@ -200,7 +219,7 @@ public static CryptoFileSystemProperties wrap(Map<String, ?> properties) { public static class Builder { public VaultCipherCombo cipherCombo = DEFAULT_CIPHER_COMBO; - private MasterkeyLoader keyLoader = DEFAULT_KEYLOADER; + private Collection<MasterkeyLoader> keyLoaders = new HashSet<>(DEFAULT_KEYLOADERS); private final Set<FileSystemFlags> flags = EnumSet.copyOf(DEFAULT_FILESYSTEM_FLAGS); private String vaultConfigFilename = DEFAULT_VAULTCONFIG_FILENAME; private String masterkeyFilename = DEFAULT_MASTERKEY_FILENAME; @@ -211,7 +230,7 @@ private Builder() { } private Builder(Map<String, ?> properties) { - checkedSet(MasterkeyLoader.class, PROPERTY_KEYLOADER, properties, this::withKeyLoader); + checkedSet(Collection.class, PROPERTY_KEYLOADERS, properties, this::withKeyLoaders); 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); @@ -270,14 +289,26 @@ public Builder withCipherCombo(VaultCipherCombo cipherCombo) { } /** - * Sets the keyLoader for a CryptoFileSystem. + * Sets the keyLoaders for a CryptoFileSystem. * - * @param keyLoader A keyLoader used during initialization + * @param keyLoaders A set of keyLoaders to load the key configured in the vault configuration * @return this * @since 2.0.0 */ - public Builder withKeyLoader(MasterkeyLoader keyLoader) { - this.keyLoader = keyLoader; + public Builder withKeyLoaders(MasterkeyLoader... keyLoaders) { + return withKeyLoaders(asList(keyLoaders)); + } + + /** + * 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(Collection<MasterkeyLoader> keyLoaders) { + this.keyLoaders.clear(); + this.keyLoaders.addAll(keyLoaders); return this; } @@ -305,19 +336,6 @@ public Builder withFlags(Collection<FileSystemFlags> flags) { return this; } - /** - * Sets the readonly flag for a CryptoFileSystem. - * - * @return this - * @deprecated Will be removed in 2.0.0. Use {@link #withFlags(FileSystemFlags...) withFlags(FileSystemFlags.READONLY)} - */ - @Deprecated - public Builder withReadonlyFlag() { - flags.add(FileSystemFlags.READONLY); - return this; - } - - /** * Sets the name of the vault config file located inside the vault directory. * @@ -354,8 +372,8 @@ public CryptoFileSystemProperties build() { } private void validate() { - if (keyLoader == null) { - throw new IllegalStateException("keyloader is required"); + if (keyLoaders.isEmpty()) { + throw new IllegalStateException("at least one keyloader is required"); } if (Strings.nullToEmpty(masterkeyFilename).trim().isEmpty()) { throw new IllegalStateException("masterkeyFilename is required"); diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 2256a727..21183c3e 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -38,6 +38,8 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.nio.file.spi.FileSystemProvider; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.Arrays; import java.util.Map; import java.util.Set; @@ -86,7 +88,15 @@ public class CryptoFileSystemProvider extends FileSystemProvider { private final CopyOperation copyOperation; public CryptoFileSystemProvider() { - this(DaggerCryptoFileSystemProviderComponent.create()); + this(DaggerCryptoFileSystemProviderComponent.builder().csprng(strongSecureRandom()).build()); + } + + private static SecureRandom strongSecureRandom() { + try { + return SecureRandom.getInstanceStrong(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("A strong algorithm must exist in every Java platform.", e); + } } /** @@ -107,8 +117,9 @@ public CryptoFileSystemProvider() { * @throws FileSystemNeedsMigrationException if the vault format needs to get updated and <code>properties</code> did not contain a flag for implicit migration. * @throws FileSystemCapabilityChecker.MissingCapabilityException If the underlying filesystem lacks features required to store a vault * @throws IOException if an I/O error occurs creating the file system + * @throws MasterkeyLoadingFailedException if the masterkey for this vault could not be loaded */ - public static CryptoFileSystem newFileSystem(Path pathToVault, CryptoFileSystemProperties properties) throws FileSystemNeedsMigrationException, IOException { + public static CryptoFileSystem newFileSystem(Path pathToVault, CryptoFileSystemProperties properties) throws FileSystemNeedsMigrationException, IOException, MasterkeyLoadingFailedException { URI uri = CryptoFileSystemUri.create(pathToVault.toAbsolutePath()); return (CryptoFileSystem) FileSystems.newFileSystem(uri, properties); } @@ -124,17 +135,17 @@ public static CryptoFileSystem newFileSystem(Path pathToVault, CryptoFileSystemP * @throws MasterkeyLoadingFailedException If thrown by the supplied keyLoader * @since 2.0.0 */ - public static void initialize(Path pathToVault, CryptoFileSystemProperties properties, String keyId) throws NotDirectoryException, IOException, MasterkeyLoadingFailedException { + public static void initialize(Path pathToVault, CryptoFileSystemProperties properties, URI keyId) throws NotDirectoryException, IOException, MasterkeyLoadingFailedException { if (!Files.isDirectory(pathToVault)) { throw new NotDirectoryException(pathToVault.toString()); } byte[] rawKey = new byte[0]; - try (Masterkey key = properties.keyLoader().loadKey(keyId)) { + try (Masterkey key = properties.keyLoader(keyId.getScheme()).loadKey(keyId)) { rawKey = key.getEncoded(); // save vault config: Path vaultConfigPath = pathToVault.resolve(properties.vaultConfigFilename()); var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).maxFilenameLength(properties.maxNameLength()).build(); - var token = config.toToken(keyId, rawKey); + var token = config.toToken(keyId.toString(), rawKey); Files.writeString(vaultConfigPath, token, StandardCharsets.US_ASCII, WRITE, CREATE_NEW); // create "d" dir: Path dataDirPath = pathToVault.resolve(Constants.DATA_DIR_NAME); @@ -175,14 +186,10 @@ public String getScheme() { } @Override - public CryptoFileSystem newFileSystem(URI uri, Map<String, ?> rawProperties) throws IOException { + public CryptoFileSystem newFileSystem(URI uri, Map<String, ?> rawProperties) throws IOException, MasterkeyLoadingFailedException { CryptoFileSystemUri parsedUri = CryptoFileSystemUri.parse(uri); CryptoFileSystemProperties properties = CryptoFileSystemProperties.wrap(rawProperties); - try { - return fileSystems.create(this, parsedUri.pathToVault(), properties); - } catch (MasterkeyLoadingFailedException e) { - throw new IOException("Used invalid key to init filesystem.", e); - } + return fileSystems.create(this, parsedUri.pathToVault(), properties); } @Override diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderComponent.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderComponent.java index dfd2e4bc..78d47e44 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderComponent.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderComponent.java @@ -2,9 +2,9 @@ import dagger.BindsInstance; import dagger.Component; -import org.cryptomator.cryptolib.api.CryptorProvider; import javax.inject.Singleton; +import java.security.SecureRandom; @Singleton @Component(modules = {CryptoFileSystemProviderModule.class}) @@ -16,4 +16,12 @@ interface CryptoFileSystemProviderComponent { CopyOperation copyOperation(); + @Component.Builder + interface Builder { + @BindsInstance + Builder csprng(SecureRandom csprng); + + CryptoFileSystemProviderComponent build(); + } + } \ No newline at end of file diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderModule.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderModule.java index 17c13a17..782137f4 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderModule.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderModule.java @@ -10,14 +10,4 @@ @Module(subcomponents = {CryptoFileSystemComponent.class}) public class CryptoFileSystemProviderModule { - @Provides - @Singleton - public SecureRandom provideCSPRNG() { - try { - return SecureRandom.getInstanceStrong(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("A strong algorithm must exist in every Java platform.", e); - } - } - } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java index 7bb928d4..d30b69cc 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java @@ -48,8 +48,9 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT var token = readVaultConfigFile(normalizedPathToVault, properties); var configLoader = VaultConfig.decode(token); + var keyId = configLoader.getKeyId(); byte[] rawKey = new byte[0]; - try (Masterkey key = properties.keyLoader().loadKey(configLoader.getKeyId())) { + try (Masterkey key = properties.keyLoader(keyId.getScheme()).loadKey(keyId)) { rawKey = key.getEncoded(); var config = configLoader.verify(rawKey, Constants.VAULT_VERSION); var adjustedProperties = adjustForCapabilities(pathToVault, properties); diff --git a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java index 8389f324..f6aa87e8 100644 --- a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java +++ b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java @@ -12,6 +12,7 @@ import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; +import java.net.URI; import java.util.Arrays; import java.util.UUID; @@ -134,8 +135,8 @@ private UnverifiedVaultConfig(DecodedJWT unverifiedConfig) { /** * @return The ID of the key required to {@link #verify(byte[], int) load} this config */ - public String getKeyId() { - return unverifiedConfig.getKeyId(); + public URI getKeyId() { + return URI.create(unverifiedConfig.getKeyId()); } /** diff --git a/src/main/java/org/cryptomator/cryptofs/common/Constants.java b/src/main/java/org/cryptomator/cryptofs/common/Constants.java index 84a7bff4..3d68600f 100644 --- a/src/main/java/org/cryptomator/cryptofs/common/Constants.java +++ b/src/main/java/org/cryptomator/cryptofs/common/Constants.java @@ -10,6 +10,9 @@ public final class Constants { + private Constants() { + } + public static final int VAULT_VERSION = 8; public static final String MASTERKEY_BACKUP_SUFFIX = ".bkup"; public static final String DATA_DIR_NAME = "d"; diff --git a/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java b/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java index 02704807..09a43b95 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java @@ -12,11 +12,10 @@ import org.cryptomator.cryptofs.migration.api.MigrationProgressListener; import org.cryptomator.cryptofs.migration.api.Migrator; import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException; -import org.cryptomator.cryptolib.Cryptors; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; -import org.cryptomator.cryptolib.common.MasterkeyFile; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import javax.inject.Inject; import java.io.IOException; @@ -114,7 +113,7 @@ private int determineVaultVersion(Path pathToVault, String vaultConfigFilename, var jwt = Files.readString(vaultConfigPath); return VaultConfig.decode(jwt).allegedVaultVersion(); } else if (Files.exists(masterKeyPath)) { - return MasterkeyFile.withContentFromFile(masterKeyPath).allegedVaultVersion(); + return MasterkeyFileAccess.readAllegedVaultVersion(Files.readAllBytes(masterKeyPath)); } else { throw new IOException("Did not find " + vaultConfigFilename + " nor " + masterkeyFilename); } diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java index 99527464..e07e6bc8 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java @@ -11,7 +11,7 @@ import org.cryptomator.cryptofs.migration.api.Migrator; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.cryptolib.common.MasterkeyFile; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,11 +19,9 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.security.SecureRandom; import java.text.Normalizer; import java.text.Normalizer.Form; -import java.util.Optional; /** * Updates masterkey.cryptomator: @@ -47,8 +45,8 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey progressListener.update(MigrationProgressListener.ProgressState.INITIALIZING, 0.0); Path masterkeyFile = vaultRoot.resolve(masterkeyFilename); byte[] fileContentsBeforeUpgrade = Files.readAllBytes(masterkeyFile); - MasterkeyFile keyFile = MasterkeyFile.withContentFromFile(masterkeyFile); - try (Masterkey masterkey = keyFile.unlock(passphrase, new byte[0], Optional.of(5)).loadKeyAndClose()) { + MasterkeyFileAccess masterkeyFileAccess = new MasterkeyFileAccess(new byte[0], csprng); + try (Masterkey masterkey = masterkeyFileAccess.load(masterkeyFile, passphrase)) { // create backup, as soon as we know the password was correct: Path masterkeyBackupFile = MasterkeyBackupHelper.attemptMasterKeyBackup(masterkeyFile); LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName()); @@ -56,8 +54,7 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey progressListener.update(MigrationProgressListener.ProgressState.FINALIZING, 0.0); // rewrite masterkey file with normalized passphrase: - byte[] fileContentsAfterUpgrade = MasterkeyFile.lock(masterkey, Normalizer.normalize(passphrase, Form.NFC), new byte[0], 6, csprng); - Files.write(masterkeyFile, fileContentsAfterUpgrade, StandardOpenOption.TRUNCATE_EXISTING); + masterkeyFileAccess.persist(masterkey, masterkeyFile, Normalizer.normalize(passphrase, Form.NFC), 6); LOG.info("Updated masterkey."); } LOG.info("Upgraded {} from version 5 to version 6.", vaultRoot); diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java index 0503d72b..b52efb5e 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java @@ -16,7 +16,7 @@ import org.cryptomator.cryptofs.migration.api.Migrator; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.cryptolib.common.MasterkeyFile; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,10 +25,8 @@ import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.security.SecureRandom; import java.util.EnumSet; -import java.util.Optional; /** * Renames ciphertext names: @@ -61,8 +59,8 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey LOG.info("Upgrading {} from version 6 to version 7.", vaultRoot); progressListener.update(MigrationProgressListener.ProgressState.INITIALIZING, 0.0); Path masterkeyFile = vaultRoot.resolve(masterkeyFilename); - MasterkeyFile keyFile = MasterkeyFile.withContentFromFile(masterkeyFile); - try (Masterkey masterkey = keyFile.unlock(passphrase, new byte[0], Optional.of(6)).loadKeyAndClose()) { + MasterkeyFileAccess masterkeyFileAccess = new MasterkeyFileAccess(new byte[0], csprng); + try (Masterkey masterkey = masterkeyFileAccess.load(masterkeyFile, passphrase)) { // create backup, as soon as we know the password was correct: Path masterkeyBackupFile = MasterkeyBackupHelper.attemptMasterKeyBackup(masterkeyFile); LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName()); @@ -117,8 +115,7 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey Files.walkFileTree(vaultRoot.resolve("m"), DeletingFileVisitor.INSTANCE); // rewrite masterkey file with normalized passphrase: - byte[] fileContentsAfterUpgrade = MasterkeyFile.lock(masterkey, passphrase, new byte[0], 7, csprng); - Files.write(masterkeyFile, fileContentsAfterUpgrade, StandardOpenOption.TRUNCATE_EXISTING); + masterkeyFileAccess.persist(masterkey, masterkeyFile, passphrase, 7); LOG.info("Updated masterkey."); } LOG.info("Upgraded {} from version 6 to version 7.", vaultRoot); diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java index 2410f321..7481b73b 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java @@ -13,7 +13,7 @@ import org.cryptomator.cryptofs.migration.api.Migrator; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.cryptolib.common.MasterkeyFile; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,7 +25,6 @@ import java.nio.file.StandardOpenOption; import java.security.SecureRandom; import java.util.Arrays; -import java.util.Optional; import java.util.UUID; /** @@ -54,8 +53,8 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey Path masterkeyFile = vaultRoot.resolve(masterkeyFilename); Path vaultConfigFile = vaultRoot.resolve(vaultConfigFilename); byte[] rawKey = new byte[0]; - MasterkeyFile keyFile = MasterkeyFile.withContentFromFile(masterkeyFile); - try (Masterkey masterkey = keyFile.unlock(passphrase, new byte[0], Optional.of(7)).loadKeyAndClose()) { + MasterkeyFileAccess masterkeyFileAccess = new MasterkeyFileAccess(new byte[0], csprng); + try (Masterkey masterkey = masterkeyFileAccess.load(masterkeyFile, passphrase)) { // create backup, as soon as we know the password was correct: Path masterkeyBackupFile = MasterkeyBackupHelper.attemptMasterKeyBackup(masterkeyFile); LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName()); @@ -76,8 +75,7 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey progressListener.update(MigrationProgressListener.ProgressState.FINALIZING, 0.0); // rewrite masterkey file with normalized passphrase: - byte[] fileContentsAfterUpgrade = MasterkeyFile.lock(masterkey, passphrase, new byte[0], 999, csprng); - Files.write(masterkeyFile, fileContentsAfterUpgrade, StandardOpenOption.TRUNCATE_EXISTING); + masterkeyFileAccess.persist(masterkey, masterkeyFile, passphrase, 999); LOG.info("Updated masterkey."); } finally { Arrays.fill(rawKey, (byte) 0x00); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java index 42c6a83a..aae4e59a 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java @@ -25,8 +25,10 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; import java.io.IOException; +import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; @@ -62,9 +64,10 @@ public class Windows { private FileSystem fileSystem; @BeforeAll - public void setupClass(@TempDir Path tmpDir) throws IOException { - MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(tmpDir), cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); + public void setupClass(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { + MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + fileSystem = new CryptoFileSystemProvider().newFileSystem(create(tmpDir), cryptoFileSystemProperties().withKeyLoaders(keyLoader).build()); } // tests https://github.com/cryptomator/cryptofs/issues/69 @@ -135,9 +138,11 @@ public void beforeAll() throws IOException, MasterkeyLoadingFailedException { inMemoryFs = Jimfs.newFileSystem(); Path vaultPath = inMemoryFs.getPath("vault"); Files.createDirectories(vaultPath); - MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); - CryptoFileSystemProvider.initialize(vaultPath, properties, "MASTERKEY_FILE"); + MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProvider.initialize(vaultPath, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(vaultPath, properties); file = fileSystem.getPath("/test.txt"); } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java index 2f30b9a7..41f5414c 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java @@ -1,6 +1,6 @@ package org.cryptomator.cryptofs; -import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags; +import org.cryptomator.cryptofs.CryptoFileSystemProperties.*; import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -10,11 +10,13 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import java.nio.charset.StandardCharsets; +import java.util.Collection; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; import static org.cryptomator.cryptofs.CryptoFileSystemProperties.*; import static org.hamcrest.CoreMatchers.sameInstance; @@ -33,41 +35,19 @@ public void testSetNoPassphrase() { } @Test - @SuppressWarnings({"unchecked", "deprecation"}) - public void testSetReadonlyFlag() { - CryptoFileSystemProperties inTest = cryptoFileSystemProperties() // - .withKeyLoader(keyLoader) // - .withReadonlyFlag() // - .build(); - - MatcherAssert.assertThat(inTest.masterkeyFilename(), is(DEFAULT_MASTERKEY_FILENAME)); - MatcherAssert.assertThat(inTest.readonly(), is(true)); - MatcherAssert.assertThat(inTest.entrySet(), - containsInAnyOrder( // - anEntry(PROPERTY_KEYLOADER, keyLoader), // - anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // - anEntry(PROPERTY_MASTERKEY_FILENAME, DEFAULT_MASTERKEY_FILENAME), // - anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // - anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // - anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // - anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)))); - } - - @Test - @SuppressWarnings({"unchecked", "deprecation"}) public void testSetMasterkeyFilenameAndReadonlyFlag() { String masterkeyFilename = "aMasterkeyFilename"; CryptoFileSystemProperties inTest = cryptoFileSystemProperties() // - .withKeyLoader(keyLoader) // + .withKeyLoaders(keyLoader) // .withMasterkeyFilename(masterkeyFilename) // - .withReadonlyFlag() // + .withFlags(FileSystemFlags.READONLY) .build(); MatcherAssert.assertThat(inTest.masterkeyFilename(), is(masterkeyFilename)); MatcherAssert.assertThat(inTest.readonly(), is(true)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_KEYLOADER, keyLoader), // + anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // @@ -77,11 +57,10 @@ public void testSetMasterkeyFilenameAndReadonlyFlag() { } @Test - @SuppressWarnings({"unchecked"}) public void testFromMap() { Map<String, Object> map = new HashMap<>(); String masterkeyFilename = "aMasterkeyFilename"; - map.put(PROPERTY_KEYLOADER, keyLoader); + map.put(PROPERTY_KEYLOADERS, Set.of(keyLoader)); map.put(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename); map.put(PROPERTY_MAX_PATH_LENGTH, 1000); map.put(PROPERTY_MAX_NAME_LENGTH, 255); @@ -94,7 +73,7 @@ public void testFromMap() { MatcherAssert.assertThat(inTest.maxNameLength(), is(255)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_KEYLOADER, keyLoader), // + anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, 1000), // @@ -104,11 +83,10 @@ public void testFromMap() { } @Test - @SuppressWarnings("unchecked") public void testWrapMapWithTrueReadonly() { Map<String, Object> map = new HashMap<>(); String masterkeyFilename = "aMasterkeyFilename"; - map.put(PROPERTY_KEYLOADER, keyLoader); + map.put(PROPERTY_KEYLOADERS, Set.of(keyLoader)); map.put(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename); map.put(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)); CryptoFileSystemProperties inTest = CryptoFileSystemProperties.wrap(map); @@ -117,7 +95,7 @@ public void testWrapMapWithTrueReadonly() { MatcherAssert.assertThat(inTest.readonly(), is(true)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_KEYLOADER, keyLoader), // + anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // @@ -127,11 +105,10 @@ public void testWrapMapWithTrueReadonly() { } @Test - @SuppressWarnings("unchecked") public void testWrapMapWithFalseReadonly() { Map<String, Object> map = new HashMap<>(); String masterkeyFilename = "aMasterkeyFilename"; - map.put(PROPERTY_KEYLOADER, keyLoader); + map.put(PROPERTY_KEYLOADERS, Set.of(keyLoader)); map.put(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename); map.put(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)); CryptoFileSystemProperties inTest = CryptoFileSystemProperties.wrap(map); @@ -140,7 +117,7 @@ public void testWrapMapWithFalseReadonly() { MatcherAssert.assertThat(inTest.readonly(), is(false)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_KEYLOADER, keyLoader), // + anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // @@ -183,17 +160,16 @@ public void testWrapMapWithInvalidPassphrase() { } @Test - @SuppressWarnings({"unchecked", "deprecation"}) public void testWrapMapWithoutReadonly() { Map<String, Object> map = new HashMap<>(); - map.put(PROPERTY_KEYLOADER, keyLoader); + map.put(PROPERTY_KEYLOADERS, Set.of(keyLoader)); CryptoFileSystemProperties inTest = CryptoFileSystemProperties.wrap(map); MatcherAssert.assertThat(inTest.masterkeyFilename(), is(DEFAULT_MASTERKEY_FILENAME)); MatcherAssert.assertThat(inTest.readonly(), is(false)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_KEYLOADER, keyLoader), // + anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, DEFAULT_MASTERKEY_FILENAME), // anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // @@ -213,7 +189,7 @@ public void testWrapMapWithoutPassphrase() { @Test public void testWrapCryptoFileSystemProperties() { - CryptoFileSystemProperties inTest = cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + CryptoFileSystemProperties inTest = cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); MatcherAssert.assertThat(CryptoFileSystemProperties.wrap(inTest), is(sameInstance(inTest))); } @@ -222,7 +198,7 @@ public void testWrapCryptoFileSystemProperties() { public void testMapIsImmutable() { Assertions.assertThrows(UnsupportedOperationException.class, () -> { cryptoFileSystemProperties() // - .withKeyLoader(keyLoader) // + .withKeyLoaders(keyLoader) // .build() // .put("test", "test"); }); @@ -232,7 +208,7 @@ public void testMapIsImmutable() { public void testEntrySetIsImmutable() { Assertions.assertThrows(UnsupportedOperationException.class, () -> { cryptoFileSystemProperties() // - .withKeyLoader(keyLoader) // + .withKeyLoaders(keyLoader) // .build() // .entrySet() // .add(null); @@ -243,7 +219,7 @@ public void testEntrySetIsImmutable() { public void testEntryIsImmutable() { Assertions.assertThrows(UnsupportedOperationException.class, () -> { cryptoFileSystemProperties() // - .withKeyLoader(keyLoader) // + .withKeyLoaders(keyLoader) // .build() // .entrySet() // .iterator().next() // @@ -252,7 +228,7 @@ public void testEntryIsImmutable() { } private <K, V> Matcher<Map.Entry<K, V>> anEntry(K key, V value) { - return new TypeSafeDiagnosingMatcher<Map.Entry<K, V>>(Map.Entry.class) { + return new TypeSafeDiagnosingMatcher<>(Map.Entry.class) { @Override public void describeTo(Description description) { description.appendText("an entry ").appendValue(key).appendText(" = ").appendValue(value); @@ -268,11 +244,21 @@ protected boolean matchesSafely(Entry<K, V> item, Description mismatchDescriptio } private boolean keyMatches(K itemKey) { - return key == null ? itemKey == null : key.equals(itemKey); + return Objects.equals(key, itemKey); } + @SuppressWarnings("rawtypes") private boolean valueMatches(V itemValue) { - return value == null ? itemValue == null : value.equals(itemValue); + if (value instanceof Collection && itemValue instanceof Collection) { + return valuesMatch((Collection) value, (Collection) itemValue); + } else { + return Objects.equals(value, itemValue); + } + } + + @SuppressWarnings("rawtypes") + private boolean valuesMatch(Collection<?> value, Collection<?> itemValue) { + return value.containsAll(itemValue) && itemValue.containsAll(value); } }; } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index 2d5a4aa7..dab62050 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -36,6 +36,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; import java.io.IOException; import java.net.URI; @@ -76,7 +77,7 @@ public class CryptoFileSystemProviderIntegrationTest { class WithLimitedPaths { private byte[] rawKey = new byte[64]; - private MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(rawKey); + private MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); private CryptoFileSystem fs; private Path shortFilePath; private Path shortSymlinkPath; @@ -84,13 +85,15 @@ class WithLimitedPaths { @BeforeAll public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { + Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(rawKey)); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // - .withKeyLoader(keyLoader) // + .withKeyLoaders(keyLoader) // .withMaxPathLength(100) .build(); - CryptoFileSystemProvider.initialize(tmpDir, properties, "MASTERKEY_FILE"); + CryptoFileSystemProvider.initialize(tmpDir, properties, URI.create("test:key")); fs = CryptoFileSystemProvider.newFileSystem(tmpDir, properties); } @@ -182,14 +185,18 @@ class InMemory { private FileSystem fs2; @BeforeAll - public void setup() throws IOException { + public void setup() throws IOException, MasterkeyLoadingFailedException { tmpFs = Jimfs.newFileSystem(Configuration.unix()); byte[] key1 = new byte[64]; byte[] key2 = new byte[64]; Arrays.fill(key1, (byte) 0x55); Arrays.fill(key2, (byte) 0x77); - keyLoader1 = ignored -> Masterkey.createFromRaw(key1); - keyLoader2 = ignored -> Masterkey.createFromRaw(key2); + keyLoader1 = Mockito.mock(MasterkeyLoader.class); + keyLoader2 = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader1.supportsScheme("test")).thenReturn(true); + Mockito.when(keyLoader2.supportsScheme("test")).thenReturn(true); + Mockito.when(keyLoader1.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(key1)); + Mockito.when(keyLoader2.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(key2)); pathToVault1 = tmpFs.getPath("/vaultDir1"); pathToVault2 = tmpFs.getPath("/vaultDir2"); Files.createDirectory(pathToVault1); @@ -209,13 +216,13 @@ public void teardown() throws IOException { public void initializeVaults() { Assertions.assertAll( () -> { - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader1).build(); - CryptoFileSystemProvider.initialize(pathToVault1, properties, "MASTERKEY_FILE"); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader1).build(); + CryptoFileSystemProvider.initialize(pathToVault1, properties, URI.create("test:key")); Assertions.assertTrue(Files.isDirectory(pathToVault1.resolve("d"))); Assertions.assertTrue(Files.isRegularFile(vaultConfigFile1)); }, () -> { - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader2).build(); - CryptoFileSystemProvider.initialize(pathToVault2, properties, "MASTERKEY_FILE"); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader2).build(); + CryptoFileSystemProvider.initialize(pathToVault2, properties, URI.create("test:key")); Assertions.assertTrue(Files.isDirectory(pathToVault2.resolve("d"))); Assertions.assertTrue(Files.isRegularFile(vaultConfigFile2)); }); @@ -233,7 +240,7 @@ public void testGetFsWithWrongCredentials() { CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // - .withKeyLoader(keyLoader2) // + .withKeyLoaders(keyLoader2) // .build(); Assertions.assertThrows(VaultKeyInvalidException.class, () -> { FileSystems.newFileSystem(fsUri, properties); @@ -244,7 +251,7 @@ public void testGetFsWithWrongCredentials() { CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // - .withKeyLoader(keyLoader1) // + .withKeyLoaders(keyLoader1) // .build(); Assertions.assertThrows(VaultKeyInvalidException.class, () -> { FileSystems.newFileSystem(fsUri, properties); @@ -261,7 +268,7 @@ public void testGetFsViaNioApi() { Assertions.assertAll( () -> { URI fsUri = CryptoFileSystemUri.create(pathToVault1); - fs1 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader1).build()); + fs1 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoaders(keyLoader1).build()); Assertions.assertTrue(fs1 instanceof CryptoFileSystemImpl); FileSystem sameFs = FileSystems.getFileSystem(fsUri); @@ -269,7 +276,7 @@ public void testGetFsViaNioApi() { }, () -> { URI fsUri = CryptoFileSystemUri.create(pathToVault2); - fs2 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader2).build()); + fs2 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoaders(keyLoader2).build()); Assertions.assertTrue(fs2 instanceof CryptoFileSystemImpl); FileSystem sameFs = FileSystems.getFileSystem(fsUri); @@ -528,9 +535,11 @@ class PosixTests { public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { Path pathToVault = tmpDir.resolve("vaultDir1"); Files.createDirectories(pathToVault); - MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); - CryptoFileSystemProvider.initialize(pathToVault, properties, "MASTERKEY_FILE"); + MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fs = CryptoFileSystemProvider.newFileSystem(pathToVault, properties); } @@ -619,9 +628,10 @@ class WindowsTests { public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { Path pathToVault = tmpDir.resolve("vaultDir1"); Files.createDirectories(pathToVault); - MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); - CryptoFileSystemProvider.initialize(pathToVault, properties, "MASTERKEY_FILE"); + MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fs = CryptoFileSystemProvider.newFileSystem(pathToVault, properties); } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index 48bed8a8..0b0ce156 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -54,7 +54,7 @@ public class CryptoFileSystemProviderTest { - private final MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); + private final MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); private final CryptoFileSystems fileSystems = mock(CryptoFileSystems.class); private final CryptoPath cryptoPath = mock(CryptoPath.class); @@ -112,7 +112,10 @@ private static final Stream<InvocationWhichShouldFail> shouldFailWithRelativePat @BeforeEach @SuppressWarnings("deprecation") - public void setup() { + public void setup() throws MasterkeyLoadingFailedException { + Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); + when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + CryptoFileSystemProviderComponent component = mock(CryptoFileSystemProviderComponent.class); when(component.fileSystems()).thenReturn(fileSystems); when(component.copyOperation()).thenReturn(copyOperation); @@ -164,10 +167,10 @@ public void testGetSchemeReturnsCryptomatorScheme() { public void testInitializeFailWithNotDirectoryException() { FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); Path pathToVault = fs.getPath("/vaultDir"); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); Assertions.assertThrows(NotDirectoryException.class, () -> { - CryptoFileSystemProvider.initialize(pathToVault, properties, "irrelevant"); + CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); }); } @@ -177,10 +180,10 @@ public void testInitialize() throws IOException, MasterkeyLoadingFailedException Path pathToVault = fs.getPath("/vaultDir"); Path vaultConfigFile = pathToVault.resolve("vault.cryptomator"); Path dataDir = pathToVault.resolve("d"); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); Files.createDirectory(pathToVault); - CryptoFileSystemProvider.initialize(pathToVault, properties, "MASTERKEY_FILE"); + CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); Assertions.assertTrue(Files.isDirectory(dataDir)); Assertions.assertTrue(Files.isRegularFile(vaultConfigFile)); @@ -192,7 +195,7 @@ public void testNewFileSystem() throws IOException, MasterkeyLoadingFailedExcept URI uri = CryptoFileSystemUri.create(pathToVault); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // - .withKeyLoader(keyLoader) // + .withKeyLoaders(keyLoader) // .build(); inTest.newFileSystem(uri, properties); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java index 16605b5d..d1dfc2a8 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java @@ -6,6 +6,7 @@ import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import java.io.IOException; import java.net.URI; @@ -73,9 +74,11 @@ public void testCreateWithPathComponents() throws URISyntaxException { public void testCreateWithPathToVaultFromNonDefaultProvider() throws IOException, MasterkeyLoadingFailedException { Path tempDir = createTempDirectory("CryptoFileSystemUrisTest").toAbsolutePath(); try { - MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); - CryptoFileSystemProvider.initialize(tempDir, properties, "irrelevant"); + MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProvider.initialize(tempDir, properties, URI.create("test:key")); FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(tempDir, properties); Path absolutePathToVault = fileSystem.getPath("a").toAbsolutePath(); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java index bdb2fee2..66db8b19 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java @@ -16,6 +16,7 @@ import org.mockito.Mockito; import java.io.IOException; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystemAlreadyExistsException; import java.nio.file.FileSystemNotFoundException; @@ -64,12 +65,12 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { when(pathToVault.normalize()).thenReturn(normalizedPathToVault); when(normalizedPathToVault.resolve("vault.cryptomator")).thenReturn(configFilePath); when(properties.vaultConfigFilename()).thenReturn("vault.cryptomator"); - when(properties.keyLoader()).thenReturn(keyLoader); + when(properties.keyLoader(Mockito.any())).thenReturn(keyLoader); filesClass.when(() -> Files.readString(configFilePath, StandardCharsets.US_ASCII)).thenReturn("jwt-vault-config"); vaultConficClass.when(() -> VaultConfig.decode("jwt-vault-config")).thenReturn(configLoader); when(VaultConfig.decode("jwt-vault-config")).thenReturn(configLoader); - when(configLoader.getKeyId()).thenReturn("key-id"); - when(keyLoader.loadKey("key-id")).thenReturn(masterkey); + when(configLoader.getKeyId()).thenReturn(URI.create("test:key")); + when(keyLoader.loadKey(Mockito.any())).thenReturn(masterkey); when(masterkey.getEncoded()).thenReturn(rawKey); when(configLoader.verify(rawKey, Constants.VAULT_VERSION)).thenReturn(vaultConfig); when(vaultConfig.getCipherCombo()).thenReturn(cipherCombo); diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java index 9b4417aa..b32e7260 100644 --- a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java @@ -18,10 +18,12 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; +import java.net.URI; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.FileSystem; import java.nio.file.Files; @@ -44,9 +46,11 @@ public class DeleteNonEmptyCiphertextDirectoryIntegrationTest { public static void setupClass(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { pathToVault = tmpDir.resolve("vault"); Files.createDirectory(pathToVault); - MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); - CryptoFileSystemProvider.initialize(pathToVault, properties, "irrelevant"); + MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); } diff --git a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java index 8bbdca1c..ebfc1d9f 100644 --- a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; import java.io.IOException; import java.net.URI; @@ -29,9 +30,11 @@ public class ReadmeCodeSamplesTest { @Test public void testReadmeCodeSampleUsingFileSystemConstructionMethodA(@TempDir Path storageLocation) throws IOException, MasterkeyLoadingFailedException { - MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); - CryptoFileSystemProvider.initialize(storageLocation, properties, "irrelevant"); + MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProvider.initialize(storageLocation, properties, URI.create("test:key")); FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(storageLocation, properties); runCodeSample(fileSystem); @@ -40,9 +43,11 @@ public void testReadmeCodeSampleUsingFileSystemConstructionMethodA(@TempDir Path @Test public void testReadmeCodeSampleUsingFileSystemConstructionMethodB(@TempDir Path storageLocation) throws IOException, MasterkeyLoadingFailedException { URI uri = CryptoFileSystemUri.create(storageLocation); - MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); - CryptoFileSystemProvider.initialize(storageLocation, properties, "irrelevant"); + MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProvider.initialize(storageLocation, properties, URI.create("test:key")); FileSystem fileSystem = FileSystems.newFileSystem(uri, properties); runCodeSample(fileSystem); diff --git a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java index 7589fbcc..2431642f 100644 --- a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java @@ -15,8 +15,10 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; import java.io.IOException; +import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; @@ -34,9 +36,11 @@ public class RealFileSystemIntegrationTest { public static void setupClass(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { pathToVault = tmpDir.resolve("vault"); Files.createDirectory(pathToVault); - MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); - CryptoFileSystemProvider.initialize(pathToVault, properties, "irrelevant"); + MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); } diff --git a/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java index 4e347b95..8fec1870 100644 --- a/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java +++ b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java @@ -21,33 +21,39 @@ public class VaultConfigTest { + private MasterkeyLoader masterkeyLoader = Mockito.mock(MasterkeyLoader.class); + private byte[] rawKey = new byte[64]; + private Masterkey key = Mockito.mock(Masterkey.class); + + @BeforeEach + public void setup() throws MasterkeyLoadingFailedException { + Arrays.fill(rawKey, (byte) 0x55); + Mockito.when(masterkeyLoader.loadKey(Mockito.any())).thenReturn(key); + Mockito.when(key.getEncoded()).thenReturn(rawKey); + } + @Test public void testLoadMalformedToken() { Assertions.assertThrows(VaultConfigLoadException.class, () -> { - VaultConfig.load("hello world", ignored -> null, 42); + VaultConfig.load("hello world", masterkeyLoader, 42); }); } @Nested public class WithValidToken { - private byte[] rawKey = new byte[64]; - private Masterkey key = Mockito.mock(Masterkey.class); private VaultConfig originalConfig; private String token; - @BeforeEach - public void setup() { - Arrays.fill(rawKey, (byte) 0x55); - Mockito.when(key.getEncoded()).thenReturn(rawKey); + public void setup() throws MasterkeyLoadingFailedException { originalConfig = VaultConfig.createNew().cipherCombo(VaultCipherCombo.SIV_CTRMAC).maxFilenameLength(220).build(); token = originalConfig.toToken("TEST_KEY", rawKey); } @Test public void testSuccessfulLoad() throws VaultConfigLoadException, MasterkeyLoadingFailedException { - var loaded = VaultConfig.load(token, ignored -> key, originalConfig.getVaultVersion()); + var loaded = VaultConfig.load(token, masterkeyLoader, originalConfig.getVaultVersion()); Assertions.assertEquals(originalConfig.getId(), loaded.getId()); Assertions.assertEquals(originalConfig.getVaultVersion(), loaded.getVaultVersion()); @@ -62,7 +68,7 @@ public void testLoadWithInvalidKey(int pos) { Mockito.when(key.getEncoded()).thenReturn(rawKey); Assertions.assertThrows(VaultKeyInvalidException.class, () -> { - VaultConfig.load(token, ignored -> key, originalConfig.getVaultVersion()); + VaultConfig.load(token, masterkeyLoader, originalConfig.getVaultVersion()); }); } @@ -84,15 +90,13 @@ public void testLoadExisting() throws VaultConfigLoadException, MasterkeyLoading var formatClaim = Mockito.mock(Claim.class); var cipherComboClaim = Mockito.mock(Claim.class); var maxFilenameLenClaim = Mockito.mock(Claim.class); - var keyLoader = Mockito.mock(MasterkeyLoader.class); var key = Mockito.mock(Masterkey.class); var verification = Mockito.mock(Verification.class); var verifier = Mockito.mock(JWTVerifier.class); - Mockito.when(decodedJwt.getKeyId()).thenReturn("key-id"); + Mockito.when(decodedJwt.getKeyId()).thenReturn("test:key"); Mockito.when(decodedJwt.getClaim("format")).thenReturn(formatClaim); Mockito.when(decodedJwt.getClaim("cipherCombo")).thenReturn(cipherComboClaim); Mockito.when(decodedJwt.getClaim("maxFilenameLen")).thenReturn(maxFilenameLenClaim); - Mockito.when(keyLoader.loadKey("key-id")).thenReturn(key); Mockito.when(key.getEncoded()).thenReturn(new byte[64]); Mockito.when(verification.withClaim("format", 42)).thenReturn(verification); Mockito.when(verification.build()).thenReturn(verifier); @@ -104,7 +108,7 @@ public void testLoadExisting() throws VaultConfigLoadException, MasterkeyLoading jwtMock.when(() -> JWT.decode("jwt-vault-config")).thenReturn(decodedJwt); jwtMock.when(() -> JWT.require(Mockito.any())).thenReturn(verification); - var config = VaultConfig.load("jwt-vault-config", keyLoader, 42); + var config = VaultConfig.load("jwt-vault-config", masterkeyLoader, 42); Assertions.assertNotNull(config); Assertions.assertEquals(42, config.getVaultVersion()); } diff --git a/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java b/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java index c3c0ce81..dee2dddc 100644 --- a/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java +++ b/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java @@ -7,8 +7,10 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import java.io.IOException; +import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.FileSystem; @@ -33,9 +35,11 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { inMemoryFs = Jimfs.newFileSystem(); Path pathToVault = inMemoryFs.getRootDirectories().iterator().next().resolve("vault"); Files.createDirectory(pathToVault); - MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); - CryptoFileSystemProvider.initialize(pathToVault, properties, "irrelevant"); + MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); root = fileSystem.getPath("/"); } diff --git a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java index 6fe7f503..de6ddb9a 100644 --- a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java @@ -23,8 +23,10 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import java.io.IOException; +import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.FileSystem; @@ -59,9 +61,11 @@ public static void setupClass() throws IOException, MasterkeyLoadingFailedExcept inMemoryFs = Jimfs.newFileSystem(); pathToVault = inMemoryFs.getRootDirectories().iterator().next().resolve("vault"); Files.createDirectory(pathToVault); - MasterkeyLoader keyLoader = ignored -> Masterkey.createFromRaw(new byte[64]); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); - CryptoFileSystemProvider.initialize(pathToVault, properties, "irrelevant"); + MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); } diff --git a/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java b/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java index 0f6b9e4f..f17acd8b 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java @@ -9,13 +9,12 @@ import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener; -import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener.ContinuationResult; import org.cryptomator.cryptofs.migration.api.MigrationProgressListener; import org.cryptomator.cryptofs.migration.api.Migrator; import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; -import org.cryptomator.cryptolib.common.MasterkeyFile; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.AfterEach; @@ -29,8 +28,6 @@ import org.mockito.Mockito; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; @@ -164,28 +161,24 @@ public void testMigrateUnsupportedVaultFormat() throws NoApplicableMigratorExcep @Nested public class WithExistingMasterkeyFile { - private MockedStatic<MasterkeyFile> masterkeyFileClass; - private MasterkeyFile masterkeyFile; + private MockedStatic<MasterkeyFileAccess> masterkeyFileAccessClass; @BeforeEach public void setup() { Assumptions.assumeFalse(Files.exists(vaultConfigPath)); - masterkeyFileClass = Mockito.mockStatic(MasterkeyFile.class); - masterkeyFile = Mockito.mock(MasterkeyFile.class); - + masterkeyFileAccessClass = Mockito.mockStatic(MasterkeyFileAccess.class); filesClass.when(() -> Files.exists(masterkeyPath)).thenReturn(true); - masterkeyFileClass.when(() -> MasterkeyFile.withContentFromFile(masterkeyPath)).thenReturn(masterkeyFile); } @AfterEach public void tearDown() { - masterkeyFileClass.close(); + masterkeyFileAccessClass.close(); } @Test @DisplayName("needs migration if vault version < Constants.VAULT_VERSION") public void testNeedsMigration() throws IOException { - Mockito.when(masterkeyFile.allegedVaultVersion()).thenReturn(Constants.VAULT_VERSION - 1); + masterkeyFileAccessClass.when(() -> MasterkeyFileAccess.readAllegedVaultVersion(Mockito.any())).thenReturn(Constants.VAULT_VERSION - 1); Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); boolean result = migrators.needsMigration(pathToVault, "vault.cryptomator", "masterkey.cryptomator"); @@ -196,7 +189,7 @@ public void testNeedsMigration() throws IOException { @Test @DisplayName("needs no migration if vault version >= Constants.VAULT_VERSION") public void testNeedsNoMigration() throws IOException { - Mockito.when(masterkeyFile.allegedVaultVersion()).thenReturn(Constants.VAULT_VERSION); + masterkeyFileAccessClass.when(() -> MasterkeyFileAccess.readAllegedVaultVersion(Mockito.any())).thenReturn(Constants.VAULT_VERSION); Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); boolean result = migrators.needsMigration(pathToVault, "vault.cryptomator", "masterkey.cryptomator"); @@ -207,7 +200,7 @@ public void testNeedsNoMigration() throws IOException { @Test @DisplayName("throws NoApplicableMigratorException if no migrator was found") public void testMigrateWithoutMigrators() { - Mockito.when(masterkeyFile.allegedVaultVersion()).thenReturn(42); + masterkeyFileAccessClass.when(() -> MasterkeyFileAccess.readAllegedVaultVersion(Mockito.any())).thenReturn(1337); Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); Assertions.assertThrows(NoApplicableMigratorException.class, () -> { @@ -222,7 +215,7 @@ public void testMigrate() throws NoApplicableMigratorException, CryptoException, MigrationProgressListener progressListener = Mockito.mock(MigrationProgressListener.class); MigrationContinuationListener continuationListener = Mockito.mock(MigrationContinuationListener.class); Migrator migrator = Mockito.mock(Migrator.class); - Mockito.when(masterkeyFile.allegedVaultVersion()).thenReturn(0); + masterkeyFileAccessClass.when(() -> MasterkeyFileAccess.readAllegedVaultVersion(Mockito.any())).thenReturn(0); Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker); migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", progressListener, continuationListener); @@ -236,7 +229,7 @@ public void testMigrate() throws NoApplicableMigratorException, CryptoException, public void testMigrateUnsupportedVaultFormat() throws NoApplicableMigratorException, CryptoException, IOException { Migrator migrator = Mockito.mock(Migrator.class); Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker); - Mockito.when(masterkeyFile.allegedVaultVersion()).thenReturn(0); + masterkeyFileAccessClass.when(() -> MasterkeyFileAccess.readAllegedVaultVersion(Mockito.any())).thenReturn(0); Mockito.doThrow(new UnsupportedVaultFormatException(Integer.MAX_VALUE, 1)).when(migrator).migrate(Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any()); Assertions.assertThrows(IllegalStateException.class, () -> { diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java index effecd6c..00df36ef 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java @@ -8,7 +8,7 @@ import org.cryptomator.cryptofs.mocks.NullSecureRandom; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.cryptolib.common.MasterkeyFile; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.AfterEach; @@ -16,7 +16,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; @@ -25,7 +24,6 @@ import java.security.SecureRandom; import java.text.Normalizer; import java.text.Normalizer.Form; -import java.util.Optional; public class Version6MigratorTest { @@ -55,7 +53,9 @@ public void testMigrate() throws IOException, CryptoException { Assertions.assertNotEquals(oldPassword, newPassword); Masterkey masterkey = Masterkey.createNew(csprng); - byte[] beforeMigration = MasterkeyFile.lock(masterkey, oldPassword, new byte[0], 5, csprng); + MasterkeyFileAccess masterkeyFileAccess = new MasterkeyFileAccess(new byte[0], csprng); + masterkeyFileAccess.persist(masterkey, masterkeyFile, oldPassword, 5); + byte[] beforeMigration = Files.readAllBytes(masterkeyFile); Files.write(masterkeyFile, beforeMigration); Path masterkeyBackupFile = pathToVault.resolve("masterkey.cryptomator" + MasterkeyBackupHelper.generateFileIdSuffix(beforeMigration) + Constants.MASTERKEY_BACKUP_SUFFIX); @@ -67,8 +67,8 @@ public void testMigrate() throws IOException, CryptoException { String afterMigrationJson = new String(afterMigration, StandardCharsets.UTF_8); MatcherAssert.assertThat(afterMigrationJson, CoreMatchers.containsString("\"version\": 6")); - try (var keyLoader = MasterkeyFile.withContent(new ByteArrayInputStream(afterMigration)).unlock(newPassword, new byte[0], Optional.of(6))) { - Assertions.assertNotNull(keyLoader); + try (var key = masterkeyFileAccess.load(masterkeyFile, newPassword)) { + Assertions.assertNotNull(key); } Assertions.assertTrue(Files.exists(masterkeyBackupFile)); diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java index eb841c7c..ec776e0d 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java @@ -6,7 +6,7 @@ import org.cryptomator.cryptofs.mocks.NullSecureRandom; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.cryptolib.common.MasterkeyFile; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.AfterEach; @@ -42,8 +42,8 @@ public void setup() throws IOException { Files.createDirectory(metaDir); Masterkey masterkey = Masterkey.createNew(csprng); - byte[] unmigrated = MasterkeyFile.lock(masterkey, "test", new byte[0], 6, csprng); - Files.write(masterkeyFile, unmigrated); + MasterkeyFileAccess masterkeyFileAccess = new MasterkeyFileAccess(new byte[0], csprng); + masterkeyFileAccess.persist(masterkey, masterkeyFile, "test", 6); } @AfterEach diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java index 34ff3700..809482ef 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java @@ -6,11 +6,9 @@ import com.google.common.jimfs.Jimfs; import org.cryptomator.cryptofs.migration.api.Migrator; import org.cryptomator.cryptofs.mocks.NullSecureRandom; -import org.cryptomator.cryptolib.Cryptors; import org.cryptomator.cryptolib.api.CryptoException; -import org.cryptomator.cryptolib.api.CryptorProvider; import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.cryptolib.common.MasterkeyFile; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.AfterEach; @@ -51,9 +49,9 @@ public void teardown() throws IOException { @Test public void testMigrate() throws CryptoException, IOException { Masterkey masterkey = Masterkey.createNew(csprng); - byte[] unmigrated = MasterkeyFile.lock(masterkey, "topsecret", new byte[0], 7, csprng); + MasterkeyFileAccess masterkeyFileAccess = new MasterkeyFileAccess(new byte[0], csprng); + masterkeyFileAccess.persist(masterkey, masterkeyFile, "topsecret", 7); Assumptions.assumeFalse(Files.exists(vaultConfigFile)); - Files.write(masterkeyFile, unmigrated); Migrator migrator = new Version8Migrator(csprng); migrator.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "topsecret"); From 593fe234e74869b31420ba51d7cca05d849863c4 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Wed, 27 Jan 2021 11:27:48 +0100 Subject: [PATCH 18/70] updated dependencies --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2d156f01..abcd124c 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.cryptomator</groupId> <artifactId>cryptofs</artifactId> - <version>2.0.0-SNAPSHOT</version> + <version>2.0.0</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> @@ -17,7 +17,7 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- dependencies --> - <cryptolib.version>2.0.0-beta3</cryptolib.version> + <cryptolib.version>2.0.0-beta4</cryptolib.version> <jwt.version>3.12.0</jwt.version> <dagger.version>2.31</dagger.version> <guava.version>30.1-jre</guava.version> From fd532f9897e1a7c118432fb77954b2da1a5ee5a2 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Fri, 12 Feb 2021 16:30:16 +0100 Subject: [PATCH 19/70] java-jwt 3.12.0 no longer depends on affected jackson-databind version --- suppression.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/suppression.xml b/suppression.xml index 23090b9b..9edca825 100644 --- a/suppression.xml +++ b/suppression.xml @@ -6,9 +6,4 @@ <gav>org.slf4j:slf4j-api:1.7.25</gav> <cve>CVE-2018-8088</cve> </suppress> - <suppress> - <notes><![CDATA[ Upstream fix backported from 2.11.0 to 2.10.5.1, see https://github.com/FasterXML/jackson-databind/issues/2589#issuecomment-714833837. ]]></notes> - <gav>com.fasterxml.jackson.core:jackson-databind:2.10.5.1</gav> - <cve>CVE-2020-25649</cve> - </suppress> </suppressions> \ No newline at end of file From 754a419b481226e7ff40fe12d420f52aaf7d6374 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Fri, 12 Feb 2021 16:30:45 +0100 Subject: [PATCH 20/70] slf4j 1.7.30 no longer affected --- suppression.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/suppression.xml b/suppression.xml index 9edca825..cc17d63b 100644 --- a/suppression.xml +++ b/suppression.xml @@ -1,9 +1,4 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- This file lists false positives found by org.owasp:dependency-check-maven build plugin --> <suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.2.xsd"> - <suppress> - <notes><![CDATA[ Vulnerability affects org.slf4j.ext.EventData, which is part of slf4j-ext and therefore falsly reported for slf4j-api. ]]></notes> - <gav>org.slf4j:slf4j-api:1.7.25</gav> - <cve>CVE-2018-8088</cve> - </suppress> </suppressions> \ No newline at end of file From 6f22a58ae81a4eba8b1f190714ef8ad16c9d1a2c Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Fri, 5 Mar 2021 14:58:20 +0100 Subject: [PATCH 21/70] bumped cryptolib version (moved to maven central) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 150315f9..a5aeafc5 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- dependencies --> - <cryptolib.version>2.0.0-beta4</cryptolib.version> + <cryptolib.version>2.0.0-beta5</cryptolib.version> <jwt.version>3.12.0</jwt.version> <dagger.version>2.31</dagger.version> <guava.version>30.1-jre</guava.version> From 86930f37d4429fdaaf7ae640fa8f3318d5db707f Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Wed, 10 Mar 2021 11:31:09 +0100 Subject: [PATCH 22/70] fix vaultformat 8 migrator (see #97) * change entry of kid field to correct scheme and scheme-specific part --- .../org/cryptomator/cryptofs/migration/v8/Version8Migrator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java index 7481b73b..87f0032f 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java @@ -64,7 +64,7 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey Algorithm algorithm = Algorithm.HMAC256(rawKey); var config = JWT.create() // .withJWTId(UUID.randomUUID().toString()) // - .withKeyId("MASTERKEY_FILE") // + .withKeyId("masterkeyfile:masterkey.cryptomator") // .withClaim("format", 8) // .withClaim("cipherCombo", "SIV_CTRMAC") // .withClaim("maxFilenameLen", 220) // From 48015a7d293c55efaee16fef64d07659f1702ff6 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Wed, 10 Mar 2021 12:06:12 +0100 Subject: [PATCH 23/70] fix unit test for v8 migration --- .../cryptomator/cryptofs/migration/v8/Version8MigratorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java index 809482ef..f754e672 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java @@ -61,7 +61,7 @@ public void testMigrate() throws CryptoException, IOException { Assertions.assertTrue(Files.exists(vaultConfigFile)); DecodedJWT token = JWT.decode(Files.readString(vaultConfigFile)); Assertions.assertNotNull(token.getId()); - Assertions.assertEquals("MASTERKEY_FILE", token.getKeyId()); + Assertions.assertEquals("masterkeyfile:masterkey.cryptomator", token.getKeyId()); Assertions.assertEquals(8, token.getClaim("format").asInt()); Assertions.assertEquals("SIV_CTRMAC", token.getClaim("cipherCombo").asString()); Assertions.assertEquals(220, token.getClaim("maxFilenameLen").asInt()); From c6c0b73dc83ba56d34c1595b3a5ec0aa77c47342 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Thu, 11 Mar 2021 12:03:53 +0100 Subject: [PATCH 24/70] fix unit tests: * use absoute path to vault in cryptoFileSystemProviderTest * add method catch for keyloader.supportsScheme(String s) in keyloader mock * initialize filesystem properly by using static facotry method in cryptoFileChannelRW integration test --- .../cryptofs/CryptoFileChannelWriteReadIntegrationTest.java | 5 ++++- .../cryptofs/CryptoFileSystemProviderIntegrationTest.java | 1 + .../cryptomator/cryptofs/CryptoFileSystemProviderTest.java | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java index aae4e59a..d5d8ea51 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java @@ -66,8 +66,11 @@ public class Windows { @BeforeAll public void setupClass(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.supportsScheme(Mockito.any())).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); - fileSystem = new CryptoFileSystemProvider().newFileSystem(create(tmpDir), cryptoFileSystemProperties().withKeyLoaders(keyLoader).build()); + CryptoFileSystemProperties properties = cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProvider.initialize(tmpDir, properties, URI.create("test:key")); + fileSystem = CryptoFileSystemProvider.newFileSystem(tmpDir, properties); } // tests https://github.com/cryptomator/cryptofs/issues/69 diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index dab62050..8677bb00 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -629,6 +629,7 @@ public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFail Path pathToVault = tmpDir.resolve("vaultDir1"); Files.createDirectories(pathToVault); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index 0b0ce156..ffb96148 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -200,7 +200,7 @@ public void testNewFileSystem() throws IOException, MasterkeyLoadingFailedExcept inTest.newFileSystem(uri, properties); - Mockito.verify(fileSystems).create(Mockito.same(inTest), Mockito.eq(pathToVault), Mockito.eq(properties)); + Mockito.verify(fileSystems).create(Mockito.same(inTest), Mockito.eq(pathToVault.toAbsolutePath()), Mockito.eq(properties)); } @Test From 1535197adf73f027022a9b691c6829f2577bce00 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Fri, 12 Mar 2021 11:46:19 +0100 Subject: [PATCH 25/70] Closes #98 --- .../cryptofs/CryptoFileSystemImpl.java | 4 +- .../cryptofs/CryptoFileSystemProvider.java | 9 ++-- .../cryptomator/cryptofs/FilesWrapper.java | 26 ---------- .../cryptofs/RootDirectoryInitializer.java | 34 ------------- .../cryptofs/CryptoFileSystemImplTest.java | 3 +- .../RootDirectoryInitializerTest.java | 50 ------------------- 6 files changed, 8 insertions(+), 118 deletions(-) delete mode 100644 src/main/java/org/cryptomator/cryptofs/FilesWrapper.java delete mode 100644 src/main/java/org/cryptomator/cryptofs/RootDirectoryInitializer.java delete mode 100644 src/test/java/org/cryptomator/cryptofs/RootDirectoryInitializerTest.java diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index fb10f3ef..df40ab7c 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -105,7 +105,7 @@ public CryptoFileSystemImpl(CryptoFileSystemProvider provider, CryptoFileSystems PathMatcherFactory pathMatcherFactory, DirectoryStreamFactory directoryStreamFactory, DirectoryIdProvider dirIdProvider, AttributeProvider fileAttributeProvider, AttributeByNameProvider fileAttributeByNameProvider, AttributeViewProvider fileAttributeViewProvider, OpenCryptoFiles openCryptoFiles, Symlinks symlinks, FinallyUtil finallyUtil, CiphertextDirectoryDeleter ciphertextDirDeleter, ReadonlyFlag readonlyFlag, - CryptoFileSystemProperties fileSystemProperties, RootDirectoryInitializer rootDirectoryInitializer) { + CryptoFileSystemProperties fileSystemProperties) { this.provider = provider; this.cryptoFileSystems = cryptoFileSystems; this.pathToVault = pathToVault; @@ -129,8 +129,6 @@ public CryptoFileSystemImpl(CryptoFileSystemProvider provider, CryptoFileSystems this.rootPath = cryptoPathFactory.rootFor(this); this.emptyPath = cryptoPathFactory.emptyFor(this); - - rootDirectoryInitializer.initialize(rootPath); } @Override diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 21183c3e..05750378 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -11,6 +11,7 @@ import org.cryptomator.cryptofs.ch.AsyncDelegatingFileChannel; import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; +import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; @@ -147,9 +148,11 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).maxFilenameLength(properties.maxNameLength()).build(); var token = config.toToken(keyId.toString(), rawKey); Files.writeString(vaultConfigPath, token, StandardCharsets.US_ASCII, WRITE, CREATE_NEW); - // create "d" dir: - Path dataDirPath = pathToVault.resolve(Constants.DATA_DIR_NAME); - Files.createDirectories(dataDirPath); + // create "d" dir and root: + Cryptor cryptor = config.getCipherCombo().getCryptorProvider(new SecureRandom()).withKey(key); //TODO: show we create a secure random instance like in CryptoFileSystemProviderComponent? + String dirHash = cryptor.fileNameCryptor().hashDirectoryId(Constants.ROOT_DIR_ID); + Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2)); + Files.createDirectories(vaultCipherRootPath); } finally { Arrays.fill(rawKey, (byte) 0x00); } diff --git a/src/main/java/org/cryptomator/cryptofs/FilesWrapper.java b/src/main/java/org/cryptomator/cryptofs/FilesWrapper.java deleted file mode 100644 index 05e8fe90..00000000 --- a/src/main/java/org/cryptomator/cryptofs/FilesWrapper.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.cryptomator.cryptofs; - -import javax.inject.Inject; -import javax.inject.Singleton; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileAttribute; - -/** - * Mockable wrapper around {@link Files} operations. - * - * @author Markus Kreusch - */ -@Singleton -class FilesWrapper { - - @Inject - public FilesWrapper() { - } - - public Path createDirectories(Path dir, FileAttribute<?>... attrs) throws IOException { - return Files.createDirectories(dir, attrs); - } - -} diff --git a/src/main/java/org/cryptomator/cryptofs/RootDirectoryInitializer.java b/src/main/java/org/cryptomator/cryptofs/RootDirectoryInitializer.java deleted file mode 100644 index 1b3a69cb..00000000 --- a/src/main/java/org/cryptomator/cryptofs/RootDirectoryInitializer.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cryptomator.cryptofs; - -import javax.inject.Inject; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Path; - -@CryptoFileSystemScoped -class RootDirectoryInitializer { - - private final CryptoPathMapper cryptoPathMapper; - private final ReadonlyFlag readonlyFlag; - private final FilesWrapper files; - - @Inject - public RootDirectoryInitializer(CryptoPathMapper cryptoPathMapper, ReadonlyFlag readonlyFlag, FilesWrapper files) { - this.cryptoPathMapper = cryptoPathMapper; - this.readonlyFlag = readonlyFlag; - this.files = files; - } - - public void initialize(CryptoPath cleartextRoot) { - if (readonlyFlag.isSet()) { - return; - } - try { - Path ciphertextRoot = cryptoPathMapper.getCiphertextDir(cleartextRoot).path; - files.createDirectories(ciphertextRoot); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - -} diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java index f0140b37..1d878ebb 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java @@ -95,7 +95,6 @@ public class CryptoFileSystemImplTest { private final PathMatcherFactory pathMatcherFactory = mock(PathMatcherFactory.class); private final CryptoPathFactory cryptoPathFactory = mock(CryptoPathFactory.class); private final CryptoFileSystemStats stats = mock(CryptoFileSystemStats.class); - private final RootDirectoryInitializer rootDirectoryInitializer = mock(RootDirectoryInitializer.class); private final DirectoryStreamFactory directoryStreamFactory = mock(DirectoryStreamFactory.class); private final FinallyUtil finallyUtil = mock(FinallyUtil.class); private final CiphertextDirectoryDeleter ciphertextDirDeleter = mock(CiphertextDirectoryDeleter.class); @@ -124,7 +123,7 @@ public void setup() { pathMatcherFactory, directoryStreamFactory, dirIdProvider, fileAttributeProvider, fileAttributeByNameProvider, fileAttributeViewProvider, openCryptoFiles, symlinks, finallyUtil, ciphertextDirDeleter, readonlyFlag, - fileSystemProperties, rootDirectoryInitializer); + fileSystemProperties); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/RootDirectoryInitializerTest.java b/src/test/java/org/cryptomator/cryptofs/RootDirectoryInitializerTest.java deleted file mode 100644 index c1f0491b..00000000 --- a/src/test/java/org/cryptomator/cryptofs/RootDirectoryInitializerTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.cryptomator.cryptofs; - -import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.nio.file.Path; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -public class RootDirectoryInitializerTest { - - private final CryptoPathMapper cryptoPathMapper = mock(CryptoPathMapper.class); - private final ReadonlyFlag readonlyFlag = mock(ReadonlyFlag.class); - private final FilesWrapper filesWrapper = mock(FilesWrapper.class); - - private final CryptoPath cleartextRoot = mock(CryptoPath.class); - private final Path ciphertextRoot = mock(Path.class); - - private RootDirectoryInitializer inTest = new RootDirectoryInitializer(cryptoPathMapper, readonlyFlag, filesWrapper); - - @BeforeEach - public void setup() throws IOException { - when(cryptoPathMapper.getCiphertextDir(cleartextRoot)).thenReturn(new CiphertextDirectory("", ciphertextRoot)); - } - - @Test - public void testInitializeCreatesRootDirectoryIfReadonlyFlagIsNotSet() throws IOException { - when(readonlyFlag.isSet()).thenReturn(false); - - inTest.initialize(cleartextRoot); - - verify(filesWrapper).createDirectories(ciphertextRoot); - } - - @Test - public void testInitializeDoesNotCreateRootDirectoryIfReadonlyFlagIsSet() throws IOException { - when(readonlyFlag.isSet()).thenReturn(true); - - inTest.initialize(cleartextRoot); - - verifyNoInteractions(filesWrapper); - } - -} From 0785942930f0d2f44d8e8e4f6ac649edf255b8c1 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Fri, 12 Mar 2021 12:48:47 +0100 Subject: [PATCH 26/70] adapt unit test of vault initialization --- .../cryptofs/CryptoFileSystemProviderTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index ffb96148..655395e8 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -37,6 +37,7 @@ import java.nio.file.spi.FileSystemProvider; import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.stream.Stream; @@ -187,6 +188,14 @@ public void testInitialize() throws IOException, MasterkeyLoadingFailedException Assertions.assertTrue(Files.isDirectory(dataDir)); Assertions.assertTrue(Files.isRegularFile(vaultConfigFile)); + + Optional<Path> preRootDir = Files.list(dataDir).findFirst(); + Assertions.assertTrue(preRootDir.isPresent()); + Assertions.assertTrue(Files.isDirectory(preRootDir.get())); + + Optional<Path> rootDir = Files.list(preRootDir.get()).findFirst(); + Assertions.assertTrue(rootDir.isPresent()); + Assertions.assertTrue(Files.isDirectory(rootDir.get())); } @Test From ba887995fc5f532d2c53bca2bc33fc9d0b3deaac Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Fri, 12 Mar 2021 15:16:56 +0100 Subject: [PATCH 27/70] add check for content root existence in CryptoFIleSystems and add/adapt unit tests --- .../cryptofs/ContentRootMissingException.java | 10 ++++++++ .../cryptofs/CryptoFileSystems.java | 25 +++++++++++++++---- .../cryptofs/CryptoFileSystemsTest.java | 17 +++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/cryptomator/cryptofs/ContentRootMissingException.java diff --git a/src/main/java/org/cryptomator/cryptofs/ContentRootMissingException.java b/src/main/java/org/cryptomator/cryptofs/ContentRootMissingException.java new file mode 100644 index 00000000..d74a2f99 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/ContentRootMissingException.java @@ -0,0 +1,10 @@ +package org.cryptomator.cryptofs; + +import java.nio.file.NoSuchFileException; + +public class ContentRootMissingException extends NoSuchFileException { + + public ContentRootMissingException(String msg) { + super(msg); + } +} diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java index d30b69cc..06e169bb 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java @@ -54,9 +54,11 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT rawKey = key.getEncoded(); var config = configLoader.verify(rawKey, Constants.VAULT_VERSION); var adjustedProperties = adjustForCapabilities(pathToVault, properties); + Cryptor cryptor = config.getCipherCombo().getCryptorProvider(csprng).withKey(key); + checkVaultRootExistence(pathToVault, cryptor); return fileSystems.compute(normalizedPathToVault, (path, fs) -> { if (fs == null) { - return create(provider, normalizedPathToVault, adjustedProperties, key, config); + return create(provider, normalizedPathToVault, adjustedProperties, cryptor, config); } else { throw new FileSystemAlreadyExistsException(); } @@ -66,9 +68,22 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT } } + /** + * Checks if the vault has a content root folder. If not, an exception is raised. + * @param pathToVault Path to the vault root + * @param cryptor Cryptor object initialized with the correct masterkey + * @throws ContentRootMissingException If the existence of encrypted vault content root cannot be ensured + */ + private void checkVaultRootExistence(Path pathToVault, Cryptor cryptor) throws ContentRootMissingException { + String dirHash = cryptor.fileNameCryptor().hashDirectoryId(Constants.ROOT_DIR_ID); + Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2)); + if(! Files.exists(vaultCipherRootPath)){ + throw new ContentRootMissingException("The encrypted root directory of the vault "+pathToVault+" is missing."); + } + } + // synchronized access to non-threadsafe cryptoFileSystemComponentBuilder required - private synchronized CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathToVault, CryptoFileSystemProperties properties, Masterkey masterkey, VaultConfig config) { - Cryptor cryptor = config.getCipherCombo().getCryptorProvider(csprng).withKey(masterkey); + private synchronized CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathToVault, CryptoFileSystemProperties properties, Cryptor cryptor, VaultConfig config) { return cryptoFileSystemComponentBuilder // .cryptor(cryptor) // .vaultConfig(config) // @@ -83,9 +98,9 @@ private synchronized CryptoFileSystemImpl create(CryptoFileSystemProvider provid * Attempts to read a vault config file * * @param pathToVault path to the vault's root - * @param properties properties used when attempting to construct a fs for this vault + * @param properties properties used when attempting to construct a fs for this vault * @return The contents of the file decoded in ASCII - * @throws IOException If the file could not be read + * @throws IOException If the file could not be read * @throws FileSystemNeedsMigrationException If the file doesn't exists, but a legacy masterkey file was found instead */ private String readVaultConfigFile(Path pathToVault, CryptoFileSystemProperties properties) throws IOException, FileSystemNeedsMigrationException { diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java index 66db8b19..f7138e2d 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java @@ -4,6 +4,7 @@ import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.CryptorProvider; +import org.cryptomator.cryptolib.api.FileNameCryptor; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; @@ -35,6 +36,9 @@ public class CryptoFileSystemsTest { private final Path pathToVault = mock(Path.class, "vaultPath"); private final Path normalizedPathToVault = mock(Path.class, "normalizedVaultPath"); private final Path configFilePath = mock(Path.class, "normalizedVaultPath/vault.cryptomator"); + private final Path dataDirPath = mock(Path.class, "normalizedVaultPath/d"); + private final Path preContenRootPath = mock(Path.class, "normalizedVaultPath/d/AB"); + private final Path contenRootPath = mock(Path.class, "normalizedVaultPath/d/AB/CDEFGHIJKLMNOP"); private final FileSystemCapabilityChecker capabilityChecker = mock(FileSystemCapabilityChecker.class); private final CryptoFileSystemProvider provider = mock(CryptoFileSystemProvider.class); private final CryptoFileSystemProperties properties = mock(CryptoFileSystemProperties.class); @@ -49,6 +53,7 @@ public class CryptoFileSystemsTest { private final SecureRandom csprng = Mockito.mock(SecureRandom.class); private final CryptorProvider cryptorProvider = mock(CryptorProvider.class); private final Cryptor cryptor = mock(Cryptor.class); + private final FileNameCryptor fileNameCryptor = mock(FileNameCryptor.class); private final CryptoFileSystemComponent.Builder cryptoFileSystemComponentBuilder = mock(CryptoFileSystemComponent.Builder.class); @@ -76,6 +81,12 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { when(vaultConfig.getCipherCombo()).thenReturn(cipherCombo); when(cipherCombo.getCryptorProvider(csprng)).thenReturn(cryptorProvider); when(cryptorProvider.withKey(masterkey)).thenReturn(cryptor); + when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor); + when(fileNameCryptor.hashDirectoryId("")).thenReturn("ABCDEFGHIJKLMNOP"); + when(pathToVault.resolve(Constants.DATA_DIR_NAME)).thenReturn(dataDirPath); + when(dataDirPath.resolve("AB")).thenReturn(preContenRootPath); + when(preContenRootPath.resolve("CDEFGHIJKLMNOP")).thenReturn(contenRootPath); + filesClass.when(() -> Files.exists(contenRootPath)).thenReturn(true); when(cryptoFileSystemComponentBuilder.cryptor(any())).thenReturn(cryptoFileSystemComponentBuilder); when(cryptoFileSystemComponentBuilder.vaultConfig(any())).thenReturn(cryptoFileSystemComponentBuilder); when(cryptoFileSystemComponentBuilder.pathToVault(any())).thenReturn(cryptoFileSystemComponentBuilder); @@ -130,6 +141,12 @@ public void testCreateDoesNotThrowFileSystemAlreadyExistsExceptionIfFileSystemIs Assertions.assertTrue(inTest.contains(fileSystem2)); } + @Test + public void testCreateThrowsIOExceptionIfContentRootExistenceCheckFails() { + filesClass.when(() -> Files.exists(contenRootPath)).thenReturn(false); + Assertions.assertThrows(IOException.class, () -> inTest.create(provider, pathToVault, properties)); + } + @Test public void testGetReturnsFileSystemForPathIfItExists() throws IOException, MasterkeyLoadingFailedException { CryptoFileSystemImpl fileSystem = inTest.create(provider, pathToVault, properties); From a03974cfa5e29a26c128b1144a58e8f104c92f16 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Fri, 12 Mar 2021 15:32:32 +0100 Subject: [PATCH 28/70] use strong secure random instance [ci skip] --- .../java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 05750378..70e3eeba 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -149,7 +149,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope var token = config.toToken(keyId.toString(), rawKey); Files.writeString(vaultConfigPath, token, StandardCharsets.US_ASCII, WRITE, CREATE_NEW); // create "d" dir and root: - Cryptor cryptor = config.getCipherCombo().getCryptorProvider(new SecureRandom()).withKey(key); //TODO: show we create a secure random instance like in CryptoFileSystemProviderComponent? + Cryptor cryptor = config.getCipherCombo().getCryptorProvider(strongSecureRandom()).withKey(key); String dirHash = cryptor.fileNameCryptor().hashDirectoryId(Constants.ROOT_DIR_ID); Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2)); Files.createDirectories(vaultCipherRootPath); From caf1f2f7e20a357eafe22040d8bc8a57baab2114 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Mon, 15 Mar 2021 10:50:14 +0100 Subject: [PATCH 29/70] autoclose & destroy cryptor after usage in cryptofs initialize method --- .../org/cryptomator/cryptofs/CryptoFileSystemProvider.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 70e3eeba..151981c2 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -141,15 +141,15 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope throw new NotDirectoryException(pathToVault.toString()); } byte[] rawKey = new byte[0]; - try (Masterkey key = properties.keyLoader(keyId.getScheme()).loadKey(keyId)) { + var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).maxFilenameLength(properties.maxNameLength()).build(); + try (Masterkey key = properties.keyLoader(keyId.getScheme()).loadKey(keyId); + Cryptor cryptor = config.getCipherCombo().getCryptorProvider(strongSecureRandom()).withKey(key)) { rawKey = key.getEncoded(); // save vault config: Path vaultConfigPath = pathToVault.resolve(properties.vaultConfigFilename()); - var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).maxFilenameLength(properties.maxNameLength()).build(); var token = config.toToken(keyId.toString(), rawKey); Files.writeString(vaultConfigPath, token, StandardCharsets.US_ASCII, WRITE, CREATE_NEW); // create "d" dir and root: - Cryptor cryptor = config.getCipherCombo().getCryptorProvider(strongSecureRandom()).withKey(key); String dirHash = cryptor.fileNameCryptor().hashDirectoryId(Constants.ROOT_DIR_ID); Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2)); Files.createDirectories(vaultCipherRootPath); From 7b4213d87578f8b20320eb4c177e340149e30cda Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Mon, 15 Mar 2021 10:51:27 +0100 Subject: [PATCH 30/70] reformatting --- src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java index 06e169bb..c324c46f 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java @@ -77,8 +77,8 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT private void checkVaultRootExistence(Path pathToVault, Cryptor cryptor) throws ContentRootMissingException { String dirHash = cryptor.fileNameCryptor().hashDirectoryId(Constants.ROOT_DIR_ID); Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2)); - if(! Files.exists(vaultCipherRootPath)){ - throw new ContentRootMissingException("The encrypted root directory of the vault "+pathToVault+" is missing."); + if (!Files.exists(vaultCipherRootPath)) { + throw new ContentRootMissingException("The encrypted root directory of the vault " + pathToVault + " is missing."); } } From 450bdc83c0011508ed3ba6c7762896360b248877 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Mon, 15 Mar 2021 11:14:47 +0100 Subject: [PATCH 31/70] close cryptor on any error during filesystem creation --- .../cryptofs/CryptoFileSystems.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java index c324c46f..5f6dc9a4 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java @@ -54,15 +54,20 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT rawKey = key.getEncoded(); var config = configLoader.verify(rawKey, Constants.VAULT_VERSION); var adjustedProperties = adjustForCapabilities(pathToVault, properties); - Cryptor cryptor = config.getCipherCombo().getCryptorProvider(csprng).withKey(key); - checkVaultRootExistence(pathToVault, cryptor); - return fileSystems.compute(normalizedPathToVault, (path, fs) -> { - if (fs == null) { - return create(provider, normalizedPathToVault, adjustedProperties, cryptor, config); - } else { - throw new FileSystemAlreadyExistsException(); - } - }); + var cryptor = config.getCipherCombo().getCryptorProvider(csprng).withKey(key); + try { + checkVaultRootExistence(pathToVault, cryptor); + return fileSystems.compute(normalizedPathToVault, (path, fs) -> { + if (fs == null) { + return create(provider, normalizedPathToVault, adjustedProperties, cryptor, config); + } else { + throw new FileSystemAlreadyExistsException(); + } + }); + } catch (Exception e){ //on any exception, close the cryptor + cryptor.destroy(); + throw e; + } } finally { Arrays.fill(rawKey, (byte) 0x00); } From f137db677154741464900556d4ab9936040ac0a2 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Mon, 15 Mar 2021 11:17:15 +0100 Subject: [PATCH 32/70] [ci skip] apply format --- src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java index 5f6dc9a4..c6207567 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java @@ -64,7 +64,7 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT throw new FileSystemAlreadyExistsException(); } }); - } catch (Exception e){ //on any exception, close the cryptor + } catch (Exception e) { //on any exception, destroy the cryptor cryptor.destroy(); throw e; } From 197b5243bd53829bd2d8f4582032cc51d40c1942 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Tue, 16 Mar 2021 06:52:52 +0100 Subject: [PATCH 33/70] make sure, "key" used in cryptor survives the try-with-resource block --- src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java index c6207567..ba0c4423 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java @@ -54,7 +54,8 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT rawKey = key.getEncoded(); var config = configLoader.verify(rawKey, Constants.VAULT_VERSION); var adjustedProperties = adjustForCapabilities(pathToVault, properties); - var cryptor = config.getCipherCombo().getCryptorProvider(csprng).withKey(key); + var keyCopy = Masterkey.createFromRaw(key.getEncoded()); // TODO replace with key.clone() eventually + var cryptor = config.getCipherCombo().getCryptorProvider(csprng).withKey(keyCopy); try { checkVaultRootExistence(pathToVault, cryptor); return fileSystems.compute(normalizedPathToVault, (path, fs) -> { From 85e7fc2f60c5fe931cb5bdf5eb37b2a527b62f42 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Tue, 16 Mar 2021 07:20:32 +0100 Subject: [PATCH 34/70] fixed unit test --- .../org/cryptomator/cryptofs/CryptoFileSystemsTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java index f7138e2d..5cdfccb1 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java @@ -59,13 +59,15 @@ public class CryptoFileSystemsTest { private MockedStatic<VaultConfig> vaultConficClass; private MockedStatic<Files> filesClass; + private MockedStatic<Masterkey> masterkeyClass; private final CryptoFileSystems inTest = new CryptoFileSystems(cryptoFileSystemComponentBuilder, capabilityChecker, csprng); @BeforeEach public void setup() throws IOException, MasterkeyLoadingFailedException { - filesClass = Mockito.mockStatic(Files.class); vaultConficClass = Mockito.mockStatic(VaultConfig.class); + filesClass = Mockito.mockStatic(Files.class); + masterkeyClass = Mockito.mockStatic(Masterkey.class); when(pathToVault.normalize()).thenReturn(normalizedPathToVault); when(normalizedPathToVault.resolve("vault.cryptomator")).thenReturn(configFilePath); @@ -100,6 +102,7 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { public void tearDown() { vaultConficClass.close(); filesClass.close(); + masterkeyClass.close(); } @Test @@ -143,7 +146,11 @@ public void testCreateDoesNotThrowFileSystemAlreadyExistsExceptionIfFileSystemIs @Test public void testCreateThrowsIOExceptionIfContentRootExistenceCheckFails() { + Masterkey clonedMasterkey = Mockito.mock(Masterkey.class, "clonedMasterkey"); filesClass.when(() -> Files.exists(contenRootPath)).thenReturn(false); + masterkeyClass.when(() -> Masterkey.createFromRaw(Mockito.any())).thenReturn(clonedMasterkey); + when(cryptorProvider.withKey(clonedMasterkey)).thenReturn(cryptor); + Assertions.assertThrows(IOException.class, () -> inTest.create(provider, pathToVault, properties)); } From 5237e83d1d83b0fea26cb940e8bbb4ad3cb70767 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Tue, 16 Mar 2021 07:24:58 +0100 Subject: [PATCH 35/70] fixed more tests --- .../org/cryptomator/cryptofs/CryptoFileSystemsTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java index 5cdfccb1..0f8d5669 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java @@ -47,6 +47,7 @@ public class CryptoFileSystemsTest { private final VaultConfig.UnverifiedVaultConfig configLoader = mock(VaultConfig.UnverifiedVaultConfig.class); private final MasterkeyLoader keyLoader = mock(MasterkeyLoader.class); private final Masterkey masterkey = mock(Masterkey.class); + private final Masterkey clonedMasterkey = Mockito.mock(Masterkey.class); private final byte[] rawKey = new byte[64]; private final VaultConfig vaultConfig = mock(VaultConfig.class); private final VaultCipherCombo cipherCombo = mock(VaultCipherCombo.class); @@ -80,6 +81,8 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { when(keyLoader.loadKey(Mockito.any())).thenReturn(masterkey); when(masterkey.getEncoded()).thenReturn(rawKey); when(configLoader.verify(rawKey, Constants.VAULT_VERSION)).thenReturn(vaultConfig); + masterkeyClass.when(() -> Masterkey.createFromRaw(rawKey)).thenReturn(clonedMasterkey); + when(cryptorProvider.withKey(clonedMasterkey)).thenReturn(cryptor); when(vaultConfig.getCipherCombo()).thenReturn(cipherCombo); when(cipherCombo.getCryptorProvider(csprng)).thenReturn(cryptorProvider); when(cryptorProvider.withKey(masterkey)).thenReturn(cryptor); @@ -146,10 +149,7 @@ public void testCreateDoesNotThrowFileSystemAlreadyExistsExceptionIfFileSystemIs @Test public void testCreateThrowsIOExceptionIfContentRootExistenceCheckFails() { - Masterkey clonedMasterkey = Mockito.mock(Masterkey.class, "clonedMasterkey"); filesClass.when(() -> Files.exists(contenRootPath)).thenReturn(false); - masterkeyClass.when(() -> Masterkey.createFromRaw(Mockito.any())).thenReturn(clonedMasterkey); - when(cryptorProvider.withKey(clonedMasterkey)).thenReturn(cryptor); Assertions.assertThrows(IOException.class, () -> inTest.create(provider, pathToVault, properties)); } From e9c02b86db60f9385178db5b9e88ffc8237b8a54 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Tue, 16 Mar 2021 14:01:47 +0100 Subject: [PATCH 36/70] Adjusted to new masterkey API --- pom.xml | 2 +- .../org/cryptomator/cryptofs/CryptoFileSystems.java | 9 ++------- .../CryptoFileChannelWriteReadIntegrationTest.java | 4 ++-- .../CryptoFileSystemProviderIntegrationTest.java | 11 +++++------ .../cryptofs/CryptoFileSystemProviderTest.java | 2 +- .../cryptomator/cryptofs/CryptoFileSystemUriTest.java | 2 +- .../cryptomator/cryptofs/CryptoFileSystemsTest.java | 5 +---- ...eteNonEmptyCiphertextDirectoryIntegrationTest.java | 2 +- .../cryptomator/cryptofs/ReadmeCodeSamplesTest.java | 4 ++-- .../cryptofs/RealFileSystemIntegrationTest.java | 2 +- .../WriteFileWhileReadonlyChannelIsOpenTest.java | 2 +- .../cryptofs/attr/FileAttributeIntegrationTest.java | 2 +- .../cryptofs/migration/v6/Version6MigratorTest.java | 2 +- .../cryptofs/migration/v7/Version7MigratorTest.java | 2 +- .../cryptofs/migration/v8/Version8MigratorTest.java | 2 +- 15 files changed, 22 insertions(+), 31 deletions(-) diff --git a/pom.xml b/pom.xml index a5aeafc5..471ca4eb 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- dependencies --> - <cryptolib.version>2.0.0-beta5</cryptolib.version> + <cryptolib.version>2.0.0-beta6</cryptolib.version> <jwt.version>3.12.0</jwt.version> <dagger.version>2.31</dagger.version> <guava.version>30.1-jre</guava.version> diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java index ba0c4423..438b9577 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java @@ -49,13 +49,10 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT var configLoader = VaultConfig.decode(token); var keyId = configLoader.getKeyId(); - byte[] rawKey = new byte[0]; try (Masterkey key = properties.keyLoader(keyId.getScheme()).loadKey(keyId)) { - rawKey = key.getEncoded(); - var config = configLoader.verify(rawKey, Constants.VAULT_VERSION); + var config = configLoader.verify(key.getEncoded(), Constants.VAULT_VERSION); var adjustedProperties = adjustForCapabilities(pathToVault, properties); - var keyCopy = Masterkey.createFromRaw(key.getEncoded()); // TODO replace with key.clone() eventually - var cryptor = config.getCipherCombo().getCryptorProvider(csprng).withKey(keyCopy); + var cryptor = config.getCipherCombo().getCryptorProvider(csprng).withKey(key.clone()); try { checkVaultRootExistence(pathToVault, cryptor); return fileSystems.compute(normalizedPathToVault, (path, fs) -> { @@ -69,8 +66,6 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT cryptor.destroy(); throw e; } - } finally { - Arrays.fill(rawKey, (byte) 0x00); } } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java index d5d8ea51..7bca22ee 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java @@ -67,7 +67,7 @@ public class Windows { public void setupClass(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); Mockito.when(keyLoader.supportsScheme(Mockito.any())).thenReturn(true); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); CryptoFileSystemProperties properties = cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); CryptoFileSystemProvider.initialize(tmpDir, properties, URI.create("test:key")); fileSystem = CryptoFileSystemProvider.newFileSystem(tmpDir, properties); @@ -143,7 +143,7 @@ public void beforeAll() throws IOException, MasterkeyLoadingFailedException { Files.createDirectories(vaultPath); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); CryptoFileSystemProvider.initialize(vaultPath, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(vaultPath, properties); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index 8677bb00..97ef9c39 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -76,7 +76,6 @@ public class CryptoFileSystemProviderIntegrationTest { @TestInstance(TestInstance.Lifecycle.PER_CLASS) class WithLimitedPaths { - private byte[] rawKey = new byte[64]; private MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); private CryptoFileSystem fs; private Path shortFilePath; @@ -86,7 +85,7 @@ class WithLimitedPaths { @BeforeAll public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(rawKey)); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // @@ -195,8 +194,8 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { keyLoader2 = Mockito.mock(MasterkeyLoader.class); Mockito.when(keyLoader1.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader2.supportsScheme("test")).thenReturn(true); - Mockito.when(keyLoader1.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(key1)); - Mockito.when(keyLoader2.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(key2)); + Mockito.when(keyLoader1.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(key1)); + Mockito.when(keyLoader2.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(key2)); pathToVault1 = tmpFs.getPath("/vaultDir1"); pathToVault2 = tmpFs.getPath("/vaultDir2"); Files.createDirectory(pathToVault1); @@ -537,7 +536,7 @@ public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFail Files.createDirectories(pathToVault); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fs = CryptoFileSystemProvider.newFileSystem(pathToVault, properties); @@ -630,7 +629,7 @@ public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFail Files.createDirectories(pathToVault); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fs = CryptoFileSystemProvider.newFileSystem(pathToVault, properties); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index 655395e8..ed488d7b 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -115,7 +115,7 @@ private static final Stream<InvocationWhichShouldFail> shouldFailWithRelativePat @SuppressWarnings("deprecation") public void setup() throws MasterkeyLoadingFailedException { Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); - when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + when(keyLoader.loadKey(Mockito.any())).thenReturn(new Masterkey(new byte[64])); CryptoFileSystemProviderComponent component = mock(CryptoFileSystemProviderComponent.class); when(component.fileSystems()).thenReturn(fileSystems); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java index d1dfc2a8..fe60e12d 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java @@ -76,7 +76,7 @@ public void testCreateWithPathToVaultFromNonDefaultProvider() throws IOException try { MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); CryptoFileSystemProvider.initialize(tempDir, properties, URI.create("test:key")); FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(tempDir, properties); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java index 0f8d5669..43456ad2 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java @@ -60,7 +60,6 @@ public class CryptoFileSystemsTest { private MockedStatic<VaultConfig> vaultConficClass; private MockedStatic<Files> filesClass; - private MockedStatic<Masterkey> masterkeyClass; private final CryptoFileSystems inTest = new CryptoFileSystems(cryptoFileSystemComponentBuilder, capabilityChecker, csprng); @@ -68,7 +67,6 @@ public class CryptoFileSystemsTest { public void setup() throws IOException, MasterkeyLoadingFailedException { vaultConficClass = Mockito.mockStatic(VaultConfig.class); filesClass = Mockito.mockStatic(Files.class); - masterkeyClass = Mockito.mockStatic(Masterkey.class); when(pathToVault.normalize()).thenReturn(normalizedPathToVault); when(normalizedPathToVault.resolve("vault.cryptomator")).thenReturn(configFilePath); @@ -80,8 +78,8 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { when(configLoader.getKeyId()).thenReturn(URI.create("test:key")); when(keyLoader.loadKey(Mockito.any())).thenReturn(masterkey); when(masterkey.getEncoded()).thenReturn(rawKey); + when(masterkey.clone()).thenReturn(clonedMasterkey); when(configLoader.verify(rawKey, Constants.VAULT_VERSION)).thenReturn(vaultConfig); - masterkeyClass.when(() -> Masterkey.createFromRaw(rawKey)).thenReturn(clonedMasterkey); when(cryptorProvider.withKey(clonedMasterkey)).thenReturn(cryptor); when(vaultConfig.getCipherCombo()).thenReturn(cipherCombo); when(cipherCombo.getCryptorProvider(csprng)).thenReturn(cryptorProvider); @@ -105,7 +103,6 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { public void tearDown() { vaultConficClass.close(); filesClass.close(); - masterkeyClass.close(); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java index b32e7260..021d3d86 100644 --- a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java @@ -48,7 +48,7 @@ public static void setupClass(@TempDir Path tmpDir) throws IOException, Masterke Files.createDirectory(pathToVault); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); diff --git a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java index ebfc1d9f..30c1a857 100644 --- a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java @@ -32,7 +32,7 @@ public class ReadmeCodeSamplesTest { public void testReadmeCodeSampleUsingFileSystemConstructionMethodA(@TempDir Path storageLocation) throws IOException, MasterkeyLoadingFailedException { MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); CryptoFileSystemProvider.initialize(storageLocation, properties, URI.create("test:key")); FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(storageLocation, properties); @@ -45,7 +45,7 @@ public void testReadmeCodeSampleUsingFileSystemConstructionMethodB(@TempDir Path URI uri = CryptoFileSystemUri.create(storageLocation); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); CryptoFileSystemProvider.initialize(storageLocation, properties, URI.create("test:key")); FileSystem fileSystem = FileSystems.newFileSystem(uri, properties); diff --git a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java index 2431642f..d0c65013 100644 --- a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java @@ -38,7 +38,7 @@ public static void setupClass(@TempDir Path tmpDir) throws IOException, Masterke Files.createDirectory(pathToVault); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); diff --git a/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java b/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java index dee2dddc..203f600f 100644 --- a/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java +++ b/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java @@ -37,7 +37,7 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { Files.createDirectory(pathToVault); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); diff --git a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java index de6ddb9a..9b5e59c6 100644 --- a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java @@ -63,7 +63,7 @@ public static void setupClass() throws IOException, MasterkeyLoadingFailedExcept Files.createDirectory(pathToVault); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenReturn(Masterkey.createFromRaw(new byte[64])); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java index 00df36ef..b099554e 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java @@ -52,7 +52,7 @@ public void testMigrate() throws IOException, CryptoException { String newPassword = Normalizer.normalize("ä", Form.NFC); Assertions.assertNotEquals(oldPassword, newPassword); - Masterkey masterkey = Masterkey.createNew(csprng); + Masterkey masterkey = Masterkey.generate(csprng); MasterkeyFileAccess masterkeyFileAccess = new MasterkeyFileAccess(new byte[0], csprng); masterkeyFileAccess.persist(masterkey, masterkeyFile, oldPassword, 5); byte[] beforeMigration = Files.readAllBytes(masterkeyFile); diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java index ec776e0d..63b8ce50 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java @@ -41,7 +41,7 @@ public void setup() throws IOException { Files.createDirectory(dataDir); Files.createDirectory(metaDir); - Masterkey masterkey = Masterkey.createNew(csprng); + Masterkey masterkey = Masterkey.generate(csprng); MasterkeyFileAccess masterkeyFileAccess = new MasterkeyFileAccess(new byte[0], csprng); masterkeyFileAccess.persist(masterkey, masterkeyFile, "test", 6); } diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java index f754e672..716e5bfa 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java @@ -48,7 +48,7 @@ public void teardown() throws IOException { @Test public void testMigrate() throws CryptoException, IOException { - Masterkey masterkey = Masterkey.createNew(csprng); + Masterkey masterkey = Masterkey.generate(csprng); MasterkeyFileAccess masterkeyFileAccess = new MasterkeyFileAccess(new byte[0], csprng); masterkeyFileAccess.persist(masterkey, masterkeyFile, "topsecret", 7); Assumptions.assumeFalse(Files.exists(vaultConfigFile)); From bab52ca50293363493ba9a1222e78e9d805e9ed6 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann <tobias.hagemann@skymatic.de> Date: Mon, 22 Mar 2021 12:26:35 +0100 Subject: [PATCH 37/70] Update README.md [ci skip] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cf1af941..353aae74 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ ![cryptomator](cryptomator.png) [![Build](https://github.com/cryptomator/cryptofs/workflows/Build/badge.svg)](https://github.com/cryptomator/cryptofs/actions?query=workflow%3ABuild) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/7248ca7d466843f785f79f33374302c2)](https://www.codacy.com/app/cryptomator/cryptofs) -[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/7248ca7d466843f785f79f33374302c2)](https://www.codacy.com/app/cryptomator/cryptofs?utm_source=github.com&utm_medium=referral&utm_content=cryptomator/cryptofs&utm_campaign=Badge_Coverage) +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/7248ca7d466843f785f79f33374302c2)](https://www.codacy.com/gh/cryptomator/cryptofs/dashboard) +[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/7248ca7d466843f785f79f33374302c2)](https://www.codacy.com/gh/cryptomator/cryptofs/dashboard) [![Known Vulnerabilities](https://snyk.io/test/github/cryptomator/cryptofs/badge.svg)](https://snyk.io/test/github/cryptomator/cryptofs) **CryptoFS:** Implementation of the [Cryptomator](https://github.com/cryptomator/cryptomator) encryption scheme. From 19fd7fe5b15d3125ce324dc64739c581079b1f7a Mon Sep 17 00:00:00 2001 From: Tobias Hagemann <tobias.hagemann@skymatic.de> Date: Mon, 22 Mar 2021 12:28:06 +0100 Subject: [PATCH 38/70] updated slack notification [ci skip] --- .github/workflows/publish-github.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-github.yml b/.github/workflows/publish-github.yml index 32a3041b..8be609b4 100644 --- a/.github/workflows/publish-github.yml +++ b/.github/workflows/publish-github.yml @@ -35,6 +35,6 @@ jobs: SLACK_ICON_EMOJI: ':bot:' SLACK_CHANNEL: 'cryptomator-desktop' SLACK_TITLE: "Published ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}" - SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions?query=workflow%3A%22Publish+to+Maven+Central%22|deploy to Maven Central>." + SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/publish-central.yml|deploy to Maven Central>." SLACK_FOOTER: - MSG_MINIMAL: true \ No newline at end of file + MSG_MINIMAL: true From 0d17a7a3515063203f924f59fc4d92051527d558 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 22 Mar 2021 14:32:06 +0100 Subject: [PATCH 39/70] update to JDK 16 --- .github/workflows/build.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/publish-central.yml | 2 +- .github/workflows/publish-github.yml | 2 +- README.md | 2 +- pom.xml | 23 +---------------------- 6 files changed, 6 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 36fde6cc..37afd896 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 16 - uses: actions/cache@v2 with: path: ~/.m2/repository diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 51683060..d100a440 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -20,7 +20,7 @@ jobs: fetch-depth: 2 - uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 16 - uses: actions/cache@v2 with: path: ~/.m2/repository diff --git a/.github/workflows/publish-central.yml b/.github/workflows/publish-central.yml index f22b7a96..d9be480e 100644 --- a/.github/workflows/publish-central.yml +++ b/.github/workflows/publish-central.yml @@ -15,7 +15,7 @@ jobs: ref: "refs/tags/${{ github.event.inputs.tag }}" - uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 16 server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml server-username: MAVEN_USERNAME # env variable for username in deploy server-password: MAVEN_PASSWORD # env variable for token in deploy diff --git a/.github/workflows/publish-github.yml b/.github/workflows/publish-github.yml index 8be609b4..3923b47f 100644 --- a/.github/workflows/publish-github.yml +++ b/.github/workflows/publish-github.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 16 gpg-private-key: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase - uses: actions/cache@v2 diff --git a/README.md b/README.md index 353aae74..ccd045d0 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ For more details on how to use the constructed `FileSystem`, you may consult the ### Dependencies -* Java 11 +* Java 16 (will be updated to 17 in late 2021) * Maven 3 ### Run Maven diff --git a/pom.xml b/pom.xml index 471ca4eb..6f9b3e62 100644 --- a/pom.xml +++ b/pom.xml @@ -116,33 +116,12 @@ <build> <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-enforcer-plugin</artifactId> - <version>3.0.0-M3</version> - <executions> - <execution> - <id>enforce-java</id> - <goals> - <goal>enforce</goal> - </goals> - <configuration> - <rules> - <requireJavaVersion> - <message>You need at least JDK 11.0.3 to build this project.</message> - <version>[11.0.3,)</version> - </requireJavaVersion> - </rules> - </configuration> - </execution> - </executions> - </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> - <release>11</release> + <release>16</release> <showWarnings>true</showWarnings> <annotationProcessorPaths> <path> From 01b1680d0930a7cff96d92d9ad1c477ff8f05e8c Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 22 Mar 2021 14:33:42 +0100 Subject: [PATCH 40/70] update IDE config [ci skip] --- .idea/misc.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 2fc03d02..d7e555a5 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -7,5 +7,5 @@ </list> </option> </component> - <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="14" project-jdk-type="JavaSDK" /> + <component name="ProjectRootManager" version="2" languageLevel="JDK_16" project-jdk-name="16" project-jdk-type="JavaSDK" /> </project> \ No newline at end of file From 124aac654c9791ba0325207b369c268a9230de05 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 22 Mar 2021 14:38:41 +0100 Subject: [PATCH 41/70] apply pattern-matching-instanceof where applicable --- .../org/cryptomator/cryptofs/CiphertextFilePath.java | 3 +-- .../cryptofs/CryptoFileSystemProperties.java | 4 ++-- src/main/java/org/cryptomator/cryptofs/CryptoPath.java | 10 ++++------ .../org/cryptomator/cryptofs/CryptoPathMapper.java | 6 ++---- .../org/cryptomator/cryptofs/attr/AttributeModule.java | 8 ++++---- .../cryptofs/CryptoFileSystemPropertiesTest.java | 5 ++--- .../cryptofs/attr/FileAttributeIntegrationTest.java | 7 +++---- 7 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CiphertextFilePath.java b/src/main/java/org/cryptomator/cryptofs/CiphertextFilePath.java index fdd2c2ed..e655861a 100644 --- a/src/main/java/org/cryptomator/cryptofs/CiphertextFilePath.java +++ b/src/main/java/org/cryptomator/cryptofs/CiphertextFilePath.java @@ -47,8 +47,7 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (obj instanceof CiphertextFilePath) { - CiphertextFilePath other = (CiphertextFilePath) obj; + if (obj instanceof CiphertextFilePath other) { return this.path.equals(other.path) && this.deflatedFileName.equals(other.deflatedFileName); } else { return false; diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index a8571317..a0543a65 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -202,8 +202,8 @@ public static Builder cryptoFileSystemPropertiesFrom(Map<String, ?> properties) * @throws IllegalArgumentException if a value in the {@code Map} does not have the expected type or if a required value is missing */ public static CryptoFileSystemProperties wrap(Map<String, ?> properties) { - if (properties instanceof CryptoFileSystemProperties) { - return (CryptoFileSystemProperties) properties; + if (properties instanceof CryptoFileSystemProperties p) { + return p; } else { try { return cryptoFileSystemPropertiesFrom(properties).build(); diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPath.java b/src/main/java/org/cryptomator/cryptofs/CryptoPath.java index 044a0cdc..e6165aa8 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoPath.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoPath.java @@ -55,10 +55,9 @@ static CryptoPath castAndAssertAbsolute(Path path) { } static CryptoPath cast(Path path) { - if (path instanceof CryptoPath) { - CryptoPath cryptoPath = (CryptoPath) path; - cryptoPath.getFileSystem().assertOpen(); - return cryptoPath; + if (path instanceof CryptoPath p) { + p.getFileSystem().assertOpen(); + return p; } else { throw new ProviderMismatchException("Used a path from different provider: " + path); } @@ -351,8 +350,7 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (obj instanceof CryptoPath) { - CryptoPath other = (CryptoPath) obj; + if (obj instanceof CryptoPath other) { return this.fileSystem.equals(other.fileSystem) // && this.compareTo(other) == 0; } else { diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java index fbb8688e..3bab4afc 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java @@ -198,8 +198,7 @@ public int hashCode() { public boolean equals(Object obj) { if (obj == this) { return true; - } else if (obj instanceof CiphertextDirectory) { - CiphertextDirectory other = (CiphertextDirectory) obj; + } else if (obj instanceof CiphertextDirectory other) { return this.dirId.equals(other.dirId) && this.path.equals(other.path); } else { return false; @@ -225,8 +224,7 @@ public int hashCode() { public boolean equals(Object obj) { if (obj == this) { return true; - } else if (obj instanceof DirIdAndName) { - DirIdAndName other = (DirIdAndName) obj; + } else if (obj instanceof DirIdAndName other) { return this.dirId.equals(other.dirId) && this.name.equals(other.name); } else { return false; diff --git a/src/main/java/org/cryptomator/cryptofs/attr/AttributeModule.java b/src/main/java/org/cryptomator/cryptofs/attr/AttributeModule.java index 52fc9ca3..46b70c0d 100644 --- a/src/main/java/org/cryptomator/cryptofs/attr/AttributeModule.java +++ b/src/main/java/org/cryptomator/cryptofs/attr/AttributeModule.java @@ -28,8 +28,8 @@ public static Optional<OpenCryptoFile> provideOpenCryptoFile(OpenCryptoFiles ope @Provides @AttributeScoped public static PosixFileAttributes providePosixFileAttributes(BasicFileAttributes ciphertextAttributes) { - if (ciphertextAttributes instanceof PosixFileAttributes) { - return (PosixFileAttributes) ciphertextAttributes; + if (ciphertextAttributes instanceof PosixFileAttributes attr) { + return attr; } else { throw new IllegalStateException("Attempted to inject instance of type " + ciphertextAttributes.getClass() + " but expected PosixFileAttributes."); } @@ -38,8 +38,8 @@ public static PosixFileAttributes providePosixFileAttributes(BasicFileAttributes @Provides @AttributeScoped public static DosFileAttributes provideDosFileAttributes(BasicFileAttributes ciphertextAttributes) { - if (ciphertextAttributes instanceof DosFileAttributes) { - return (DosFileAttributes) ciphertextAttributes; + if (ciphertextAttributes instanceof DosFileAttributes attr) { + return attr; } else { throw new IllegalStateException("Attempted to inject instance of type " + ciphertextAttributes.getClass() + " but expected DosFileAttributes."); } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java index 41f5414c..676174e8 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java @@ -247,10 +247,9 @@ private boolean keyMatches(K itemKey) { return Objects.equals(key, itemKey); } - @SuppressWarnings("rawtypes") private boolean valueMatches(V itemValue) { - if (value instanceof Collection && itemValue instanceof Collection) { - return valuesMatch((Collection) value, (Collection) itemValue); + if (value instanceof Collection v && itemValue instanceof Collection c) { + return valuesMatch(v, c); } else { return Objects.equals(value, itemValue); } diff --git a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java index 9b5e59c6..11a6ee09 100644 --- a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java @@ -197,12 +197,11 @@ public void testFileAttributeViewUpdatesAfterMove() throws IOException { } private static Matcher<FileTime> isAfter(FileTime previousFileTime) { - return new BaseMatcher<FileTime>() { + return new BaseMatcher<>() { @Override public boolean matches(Object item) { - if (item instanceof FileTime) { - FileTime subject = (FileTime) item; - return subject.compareTo(previousFileTime) > 0; + if (item instanceof FileTime ft) { + return ft.compareTo(previousFileTime) > 0; } else { return false; } From 250b37199ea3dda761fa1e95f3c550f185b2274f Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 22 Mar 2021 15:02:42 +0100 Subject: [PATCH 42/70] use switch expressions where applicable --- .../cryptofs/CryptoFileSystemImpl.java | 82 ++++++------------- .../attr/AbstractCryptoFileAttributeView.java | 20 ++--- .../cryptofs/attr/AttributeProvider.java | 15 ++-- .../attr/CryptoBasicFileAttributes.java | 15 +--- .../dir/CiphertextDirectoryDeleter.java | 9 +- 5 files changed, 48 insertions(+), 93 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index df40ab7c..c941c747 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -21,12 +21,9 @@ import org.cryptomator.cryptofs.dir.DirectoryStreamFactory; import org.cryptomator.cryptofs.fh.OpenCryptoFiles; import org.cryptomator.cryptolib.api.Cryptor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.inject.Inject; import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.AccessDeniedException; import java.nio.file.AccessMode; @@ -38,7 +35,6 @@ import java.nio.file.DirectoryStream.Filter; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; -import java.nio.file.FileSystemException; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; @@ -267,16 +263,11 @@ void checkAccess(CryptoPath cleartextPath, AccessMode... modes) throws IOExcepti } private boolean hasAccess(Set<PosixFilePermission> permissions, AccessMode accessMode) { - switch (accessMode) { - case READ: - return permissions.contains(PosixFilePermission.OWNER_READ); - case WRITE: - return permissions.contains(PosixFilePermission.OWNER_WRITE); - case EXECUTE: - return permissions.contains(PosixFilePermission.OWNER_EXECUTE); - default: - throw new UnsupportedOperationException("AccessMode " + accessMode + " not supported."); - } + return switch (accessMode) { + case READ -> permissions.contains(PosixFilePermission.OWNER_READ); + case WRITE -> permissions.contains(PosixFilePermission.OWNER_WRITE); + case EXECUTE -> permissions.contains(PosixFilePermission.OWNER_EXECUTE); + }; } boolean isHidden(CryptoPath cleartextPath) throws IOException { @@ -340,22 +331,23 @@ FileChannel newFileChannel(CryptoPath cleartextPath, Set<? extends OpenOption> o throw e; } } - switch (ciphertextFileType) { - case SYMLINK: - if (options.noFollowLinks()) { - throw new UnsupportedOperationException("Unsupported OpenOption LinkOption.NOFOLLOW_LINKS. Can not create file channel for symbolic link."); - } else { - CryptoPath resolvedPath = symlinks.resolveRecursively(cleartextPath); - return newFileChannel(resolvedPath, options, attrs); - } - case FILE: - return newFileChannel(cleartextPath, options, attrs); - default: - throw new UnsupportedOperationException("Can not create file channel for " + ciphertextFileType.name()); + return switch (ciphertextFileType) { + case SYMLINK -> newFileChannelFromSymlink(cleartextPath, options, attrs); + case FILE -> newFileChannelFromFile(cleartextPath, options, attrs); + case DIRECTORY -> throw new UnsupportedOperationException("Can not create file channel for " + ciphertextFileType.name()); + }; + } + + private FileChannel newFileChannelFromSymlink(CryptoPath cleartextPath, EffectiveOpenOptions options, FileAttribute<?>... attrs) throws IOException { + if (options.noFollowLinks()) { + throw new UnsupportedOperationException("Unsupported OpenOption LinkOption.NOFOLLOW_LINKS. Can not create file channel for symbolic link."); + } else { + CryptoPath resolvedPath = symlinks.resolveRecursively(cleartextPath); + return newFileChannelFromFile(resolvedPath, options, attrs); } } - private FileChannel newFileChannel(CryptoPath cleartextFilePath, EffectiveOpenOptions options, FileAttribute<?>... attrs) throws IOException { + private FileChannel newFileChannelFromFile(CryptoPath cleartextFilePath, EffectiveOpenOptions options, FileAttribute<?>... attrs) throws IOException { CiphertextFilePath ciphertextPath = cryptoPathMapper.getCiphertextFilePath(cleartextFilePath); Path ciphertextFilePath = ciphertextPath.getFilePath(); assertCiphertextPathLengthMeetsLimitations(ciphertextFilePath); @@ -384,12 +376,8 @@ void delete(CryptoPath cleartextPath) throws IOException { CiphertextFileType ciphertextFileType = cryptoPathMapper.getCiphertextFileType(cleartextPath); CiphertextFilePath ciphertextPath = cryptoPathMapper.getCiphertextFilePath(cleartextPath); switch (ciphertextFileType) { - case DIRECTORY: - deleteDirectory(cleartextPath, ciphertextPath); - return; - default: - Files.walkFileTree(ciphertextPath.getRawPath(), DeletingFileVisitor.INSTANCE); - return; + case DIRECTORY -> deleteDirectory(cleartextPath, ciphertextPath); + case FILE, SYMLINK -> Files.walkFileTree(ciphertextPath.getRawPath(), DeletingFileVisitor.INSTANCE); } } @@ -420,17 +408,9 @@ void copy(CryptoPath cleartextSource, CryptoPath cleartextTarget, CopyOption... cryptoPathMapper.assertNonExisting(cleartextTarget); } switch (ciphertextFileType) { - case SYMLINK: - copySymlink(cleartextSource, cleartextTarget, options); - return; - case FILE: - copyFile(cleartextSource, cleartextTarget, options); - return; - case DIRECTORY: - copyDirectory(cleartextSource, cleartextTarget, options); - return; - default: - throw new UnsupportedOperationException("Unhandled node type " + ciphertextFileType); + case SYMLINK -> copySymlink(cleartextSource, cleartextTarget, options); + case FILE -> copyFile(cleartextSource, cleartextTarget, options); + case DIRECTORY -> copyDirectory(cleartextSource, cleartextTarget, options); } } @@ -526,17 +506,9 @@ void move(CryptoPath cleartextSource, CryptoPath cleartextTarget, CopyOption... cryptoPathMapper.assertNonExisting(cleartextTarget); } switch (ciphertextFileType) { - case SYMLINK: - moveSymlink(cleartextSource, cleartextTarget, options); - return; - case FILE: - moveFile(cleartextSource, cleartextTarget, options); - return; - case DIRECTORY: - moveDirectory(cleartextSource, cleartextTarget, options); - return; - default: - throw new UnsupportedOperationException("Unhandled node type " + ciphertextFileType); + case SYMLINK -> moveSymlink(cleartextSource, cleartextTarget, options); + case FILE -> moveFile(cleartextSource, cleartextTarget, options); + case DIRECTORY -> moveDirectory(cleartextSource, cleartextTarget, options); } } diff --git a/src/main/java/org/cryptomator/cryptofs/attr/AbstractCryptoFileAttributeView.java b/src/main/java/org/cryptomator/cryptofs/attr/AbstractCryptoFileAttributeView.java index 6e45aeaf..abd91859 100644 --- a/src/main/java/org/cryptomator/cryptofs/attr/AbstractCryptoFileAttributeView.java +++ b/src/main/java/org/cryptomator/cryptofs/attr/AbstractCryptoFileAttributeView.java @@ -8,13 +8,13 @@ *******************************************************************************/ package org.cryptomator.cryptofs.attr; -import org.cryptomator.cryptofs.common.ArrayUtils; -import org.cryptomator.cryptofs.common.CiphertextFileType; import org.cryptomator.cryptofs.CryptoPath; import org.cryptomator.cryptofs.CryptoPathMapper; -import org.cryptomator.cryptofs.fh.OpenCryptoFiles; import org.cryptomator.cryptofs.Symlinks; +import org.cryptomator.cryptofs.common.ArrayUtils; +import org.cryptomator.cryptofs.common.CiphertextFileType; import org.cryptomator.cryptofs.fh.OpenCryptoFile; +import org.cryptomator.cryptofs.fh.OpenCryptoFiles; import java.io.IOException; import java.nio.file.LinkOption; @@ -50,19 +50,19 @@ protected Optional<OpenCryptoFile> getOpenCryptoFile() throws IOException { private Path getCiphertextPath(CryptoPath path) throws IOException { CiphertextFileType type = pathMapper.getCiphertextFileType(path); - switch (type) { + return switch (type) { case SYMLINK: if (ArrayUtils.contains(linkOptions, LinkOption.NOFOLLOW_LINKS)) { - return pathMapper.getCiphertextFilePath(path).getSymlinkFilePath(); + yield pathMapper.getCiphertextFilePath(path).getSymlinkFilePath(); } else { CryptoPath resolved = symlinks.resolveRecursively(path); - return getCiphertextPath(resolved); + yield getCiphertextPath(resolved); } case DIRECTORY: - return pathMapper.getCiphertextDir(path).path; - default: - return pathMapper.getCiphertextFilePath(path).getFilePath(); - } + yield pathMapper.getCiphertextDir(path).path; + case FILE: + yield pathMapper.getCiphertextFilePath(path).getFilePath(); + }; } } diff --git a/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java b/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java index 6a7c0db6..d9c445bb 100644 --- a/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java @@ -62,16 +62,11 @@ public <A extends BasicFileAttributes> A readAttributes(CryptoPath cleartextPath } private Path getCiphertextPath(CryptoPath path, CiphertextFileType type) throws IOException { - switch (type) { - case SYMLINK: - return pathMapper.getCiphertextFilePath(path).getSymlinkFilePath(); - case DIRECTORY: - return pathMapper.getCiphertextDir(path).path; - case FILE: - return pathMapper.getCiphertextFilePath(path).getFilePath(); - default: - throw new UnsupportedOperationException("Unhandled node type " + type); - } + return switch (type) { + case SYMLINK -> pathMapper.getCiphertextFilePath(path).getSymlinkFilePath(); + case DIRECTORY -> pathMapper.getCiphertextDir(path).path; + case FILE -> pathMapper.getCiphertextFilePath(path).getFilePath(); + }; } } diff --git a/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java b/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java index 3f84ec1c..92a8d320 100644 --- a/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java +++ b/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java @@ -37,17 +37,10 @@ class CryptoBasicFileAttributes implements BasicFileAttributes { @Inject public CryptoBasicFileAttributes(BasicFileAttributes delegate, CiphertextFileType ciphertextFileType, Path ciphertextPath, Cryptor cryptor, Optional<OpenCryptoFile> openCryptoFile) { this.ciphertextFileType = ciphertextFileType; - switch (ciphertextFileType) { - case SYMLINK: - case DIRECTORY: - this.size = delegate.size(); - break; - case FILE: - this.size = getPlaintextFileSize(ciphertextPath, delegate.size(), openCryptoFile, cryptor); - break; - default: - throw new IllegalArgumentException("Unsupported ciphertext file type: " + ciphertextFileType); - } + this.size = switch (ciphertextFileType) { + case SYMLINK, DIRECTORY -> delegate.size(); + case 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); this.creationTime = delegate.creationTime(); diff --git a/src/main/java/org/cryptomator/cryptofs/dir/CiphertextDirectoryDeleter.java b/src/main/java/org/cryptomator/cryptofs/dir/CiphertextDirectoryDeleter.java index 1efdcc4f..acb1adeb 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/CiphertextDirectoryDeleter.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/CiphertextDirectoryDeleter.java @@ -45,13 +45,8 @@ public void deleteCiphertextDirIncludingNonCiphertextFiles(Path ciphertextDir, C * case 2 is true. */ switch (deleteNonCiphertextFiles(ciphertextDir, cleartextDir)) { - case NO_FILES_DELETED: - throw e; - case SOME_FILES_DELETED: - Files.delete(ciphertextDir); - break; - default: - throw new IllegalStateException("Unexpected enum constant"); + case NO_FILES_DELETED -> throw e; + case SOME_FILES_DELETED-> Files.delete(ciphertextDir); } } } From 0d179298026e5369c94a32a834e7a2e93392da4d Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 22 Mar 2021 15:47:28 +0100 Subject: [PATCH 43/70] replaced mock --- .../cryptofs/ch/CleartextFileLockTest.java | 7 +- .../cryptofs/ch/DummyFileChannel.java | 97 ------------------- 2 files changed, 4 insertions(+), 100 deletions(-) delete mode 100644 src/test/java/org/cryptomator/cryptofs/ch/DummyFileChannel.java diff --git a/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileLockTest.java b/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileLockTest.java index 64ba9ed4..9171bead 100644 --- a/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileLockTest.java +++ b/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileLockTest.java @@ -21,7 +21,8 @@ public class CleartextFileLockTest { @BeforeEach public void setup() { - channel = Mockito.spy(new DummyFileChannel()); + channel = Mockito.mock(FileChannel.class); + Mockito.when(channel.isOpen()).thenReturn(true); } @Nested @@ -99,7 +100,7 @@ class ClosedChannel { @BeforeEach public void setup() throws IOException { - channel.close(); + Mockito.when(channel.isOpen()).thenReturn(false); } @Test @@ -187,7 +188,7 @@ class ClosedChannel { @BeforeEach public void setup() throws IOException { - channel.close(); + Mockito.when(channel.isOpen()).thenReturn(false); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/ch/DummyFileChannel.java b/src/test/java/org/cryptomator/cryptofs/ch/DummyFileChannel.java deleted file mode 100644 index ab9c5abc..00000000 --- a/src/test/java/org/cryptomator/cryptofs/ch/DummyFileChannel.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.cryptomator.cryptofs.ch; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.WritableByteChannel; - -class DummyFileChannel extends FileChannel { - - @Override - public int read(ByteBuffer dst) throws IOException { - return 0; - } - - @Override - public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { - return 0; - } - - @Override - public int write(ByteBuffer src) throws IOException { - return 0; - } - - @Override - public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { - return 0; - } - - @Override - public long position() throws IOException { - return 0; - } - - @Override - public FileChannel position(long newPosition) throws IOException { - return null; - } - - @Override - public long size() throws IOException { - return 0; - } - - @Override - public FileChannel truncate(long size) throws IOException { - return null; - } - - @Override - public void force(boolean metaData) throws IOException { - } - - @Override - public long transferTo(long position, long count, WritableByteChannel target) throws IOException { - return 0; - } - - @Override - public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { - return 0; - } - - @Override - public int read(ByteBuffer dst, long position) throws IOException { - return 0; - } - - @Override - public int write(ByteBuffer src, long position) throws IOException { - return 0; - } - - @Override - public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { - return null; - } - - @Override - public FileLock lock(long position, long size, boolean shared) throws IOException { - return null; - } - - @Override - public FileLock tryLock(long position, long size, boolean shared) throws IOException { - return null; - } - - @Override - protected void implCloseChannel() throws IOException { - } - -} - From fb9fb78ae23c97ea69929ed76d59ee0b2eed7cd5 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Fri, 16 Apr 2021 11:44:35 +0200 Subject: [PATCH 44/70] shorten long names when reaching configured limit instead of hardcoded 220 --- .../cryptofs/CryptoFileSystemProperties.java | 4 ++-- .../cryptofs/CryptoPathMapper.java | 6 +++-- .../cryptofs/common/Constants.java | 1 - .../cryptofs/dir/C9rConflictResolver.java | 20 ++++++++++++----- .../cryptofs/CryptoPathMapperTest.java | 22 ++++++++++--------- ...ptyCiphertextDirectoryIntegrationTest.java | 2 -- .../cryptofs/dir/C9rConflictResolverTest.java | 14 +++++++++++- 7 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index 7329773d..4c218d9b 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -167,11 +167,11 @@ String masterkeyFilename() { return (String) get(PROPERTY_MASTERKEY_FILENAME); } - int maxPathLength() { + public int maxPathLength() { return (int) get(PROPERTY_MAX_PATH_LENGTH); } - int maxNameLength() { + public int maxNameLength() { return (int) get(PROPERTY_MAX_NAME_LENGTH); } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java index fbb8688e..8cc1783e 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java @@ -48,17 +48,19 @@ public class CryptoPathMapper { private final Path dataRoot; private final DirectoryIdProvider dirIdProvider; private final LongFileNameProvider longFileNameProvider; + private final CryptoFileSystemProperties fileSystemProperties; 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, CryptoFileSystemProperties fileSystemProperties) { this.dataRoot = pathToVault.resolve(DATA_DIR_NAME); this.cryptor = cryptor; this.dirIdProvider = dirIdProvider; this.longFileNameProvider = longFileNameProvider; + this.fileSystemProperties = fileSystemProperties; 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); @@ -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() > fileSystemProperties.maxNameLength()) { LongFileNameProvider.DeflatedFileName deflatedFileName = longFileNameProvider.deflate(c9rPath); return new CiphertextFilePath(deflatedFileName.c9sPath, Optional.of(deflatedFileName)); } else { diff --git a/src/main/java/org/cryptomator/cryptofs/common/Constants.java b/src/main/java/org/cryptomator/cryptofs/common/Constants.java index 0ad683f8..5bbfa7e5 100644 --- a/src/main/java/org/cryptomator/cryptofs/common/Constants.java +++ b/src/main/java/org/cryptomator/cryptofs/common/Constants.java @@ -23,7 +23,6 @@ public final class Constants { public static final int MAX_CIPHERTEXT_NAME_LENGTH = 220; // inclusive. calculations done in https://github.com/cryptomator/cryptofs/issues/60#issuecomment-523238303 public static final int MIN_CIPHERTEXT_NAME_LENGTH = 28; // base64(iv).c9r - public static final int MAX_CLEARTEXT_NAME_LENGTH = 146; // inclusive. calculations done in https://github.com/cryptomator/cryptofs/issues/60#issuecomment-523238303 public static final int MAX_ADDITIONAL_PATH_LENGTH = 48; // beginning at d/... see https://github.com/cryptomator/cryptofs/issues/77 public static final int MAX_CIPHERTEXT_PATH_LENGTH = MAX_CIPHERTEXT_NAME_LENGTH + MAX_ADDITIONAL_PATH_LENGTH; public static final int MAX_SYMLINK_LENGTH = 32767; // max path length on NTFS and FAT32: 32k-1 diff --git a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java index dc34068d..f404ee5e 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java @@ -4,6 +4,7 @@ import com.google.common.io.BaseEncoding; import com.google.common.io.MoreFiles; import com.google.common.io.RecursiveDeleteOption; +import org.cryptomator.cryptofs.CryptoFileSystemProperties; import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptolib.api.Cryptor; import org.slf4j.Logger; @@ -23,8 +24,6 @@ import java.util.stream.Stream; import static org.cryptomator.cryptofs.common.Constants.DIR_FILE_NAME; -import static org.cryptomator.cryptofs.common.Constants.MAX_CIPHERTEXT_NAME_LENGTH; -import static org.cryptomator.cryptofs.common.Constants.MAX_CLEARTEXT_NAME_LENGTH; import static org.cryptomator.cryptofs.common.Constants.MAX_DIR_FILE_LENGTH; import static org.cryptomator.cryptofs.common.Constants.MAX_SYMLINK_LENGTH; import static org.cryptomator.cryptofs.common.Constants.SYMLINK_FILE_NAME; @@ -36,11 +35,13 @@ class C9rConflictResolver { private final Cryptor cryptor; private final byte[] dirId; + private final CryptoFileSystemProperties fileSystemProperties; @Inject - public C9rConflictResolver(Cryptor cryptor, @Named("dirId") String dirId) { + public C9rConflictResolver(Cryptor cryptor, @Named("dirId") String dirId, CryptoFileSystemProperties fileSystemProperties) { this.cryptor = cryptor; this.dirId = dirId.getBytes(StandardCharsets.US_ASCII); + this.fileSystemProperties = fileSystemProperties; } public Stream<Node> process(Node node) { @@ -79,6 +80,13 @@ private Stream<Node> resolveConflict(Node conflicting, Path canonicalPath) throw } } + // visible for testing + int calcMaxCleartextNameLength(int maxCiphertextNameLength) { + // math explained in https://github.com/cryptomator/cryptofs/issues/60#issuecomment-523238303; + // subtract 4 for file extension, base64-decode, subtract 16 for IV + return (maxCiphertextNameLength - 4) / 4 * 3 - 16; + } + /** * Resolves a conflict by renaming the conflicting file. * @@ -91,9 +99,11 @@ private Stream<Node> resolveConflict(Node conflicting, Path canonicalPath) throw private Node renameConflictingFile(Path canonicalPath, Path conflictingPath, String cleartext) throws IOException { assert Files.exists(canonicalPath); final int beginOfFileExtension = cleartext.lastIndexOf('.'); + final int maxCiphertextNameLength = fileSystemProperties.maxNameLength(); + final int maxCleartextNameLength = calcMaxCleartextNameLength(maxCiphertextNameLength); final String fileExtension = (beginOfFileExtension > 0) ? cleartext.substring(beginOfFileExtension) : ""; final String basename = (beginOfFileExtension > 0) ? cleartext.substring(0, beginOfFileExtension) : cleartext; - final String lengthRestrictedBasename = basename.substring(0, Math.min(basename.length(), MAX_CLEARTEXT_NAME_LENGTH - fileExtension.length() - 5)); // 5 chars for conflict suffix " (42)" + final String lengthRestrictedBasename = basename.substring(0, Math.min(basename.length(), maxCleartextNameLength - fileExtension.length() - 5)); // 5 chars for conflict suffix " (42)" String alternativeCleartext; String alternativeCiphertext; String alternativeCiphertextName; @@ -105,7 +115,7 @@ private Node renameConflictingFile(Path canonicalPath, Path conflictingPath, Str alternativeCiphertextName = alternativeCiphertext + Constants.CRYPTOMATOR_FILE_SUFFIX; alternativePath = canonicalPath.resolveSibling(alternativeCiphertextName); } while (Files.exists(alternativePath)); - assert alternativeCiphertextName.length() <= MAX_CIPHERTEXT_NAME_LENGTH; + assert alternativeCiphertextName.length() <= maxCiphertextNameLength; LOG.info("Moving conflicting file {} to {}", conflictingPath, alternativePath); Files.move(conflictingPath, alternativePath, StandardCopyOption.ATOMIC_MOVE); Node node = new Node(alternativePath); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java index 9384fce0..2efe7eda 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java @@ -35,6 +35,7 @@ public class CryptoPathMapperTest { private final FileNameCryptor fileNameCryptor = Mockito.mock(FileNameCryptor.class); private final DirectoryIdProvider dirIdProvider = Mockito.mock(DirectoryIdProvider.class); private final LongFileNameProvider longFileNameProvider = Mockito.mock(LongFileNameProvider.class); + private final CryptoFileSystemProperties fileSystemProperties = Mockito.mock(CryptoFileSystemProperties.class); private final Symlinks symlinks = Mockito.mock(Symlinks.class); private final CryptoFileSystemImpl fileSystem = Mockito.mock(CryptoFileSystemImpl.class); @@ -45,6 +46,7 @@ public void setup() { CryptoPath empty = cryptoPathFactory.emptyFor(fileSystem); Mockito.when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor); Mockito.when(pathToVault.resolve("d")).thenReturn(dataRoot); + Mockito.when(fileSystemProperties.maxNameLength()).thenReturn(220); Mockito.when(fileSystem.getPath(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenAnswer(invocation -> { String first = invocation.getArgument(0); if (invocation.getArguments().length == 1) { @@ -68,7 +70,7 @@ public void testPathEncryptionForRoot() throws IOException { Path d0000 = Mockito.mock(Path.class); Mockito.when(d00.resolve("00")).thenReturn(d0000); - CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider); + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, fileSystemProperties); Path path = mapper.getCiphertextDir(fileSystem.getRootPath()).path; Assertions.assertEquals(d0000, path); } @@ -92,7 +94,7 @@ public void testPathEncryptionForFoo() throws IOException { Path d0001 = Mockito.mock(Path.class); Mockito.when(d00.resolve("01")).thenReturn(d0001); - CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider); + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, fileSystemProperties); Path path = mapper.getCiphertextDir(fileSystem.getPath("/foo")).path; Assertions.assertEquals(d0001, path); } @@ -126,7 +128,7 @@ public void testPathEncryptionForFooBar() throws IOException { Path d0002 = Mockito.mock(Path.class); Mockito.when(d00.resolve("02")).thenReturn(d0002); - CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider); + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, fileSystemProperties); Path path = mapper.getCiphertextDir(fileSystem.getPath("/foo/bar")).path; Assertions.assertEquals(d0002, path); } @@ -163,7 +165,7 @@ public void testPathEncryptionForFooBarBaz() throws IOException { Mockito.when(d0002.resolve("zab.c9r")).thenReturn(d0002zab); Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("baz"), Mockito.any())).thenReturn("zab"); - CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider); + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, fileSystemProperties); Path path = mapper.getCiphertextFilePath(fileSystem.getPath("/foo/bar/baz")).getRawPath(); Assertions.assertEquals(d0002zab, path); } @@ -211,7 +213,7 @@ public void setup() throws IOException { @Test public void testGetCiphertextFileTypeOfRootPath() throws IOException { - CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider); + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, fileSystemProperties); CiphertextFileType type = mapper.getCiphertextFileType(fileSystem.getRootPath()); Assertions.assertEquals(CiphertextFileType.DIRECTORY, type); } @@ -220,7 +222,7 @@ public void testGetCiphertextFileTypeOfRootPath() throws IOException { public void testGetCiphertextFileTypeForNonexistingFile() throws IOException { Mockito.when(underlyingFileSystemProvider.readAttributes(c9rPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class); - CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider); + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, fileSystemProperties); CryptoPath path = fileSystem.getPath("/CLEAR"); Assertions.assertThrows(NoSuchFileException.class, () -> { @@ -233,7 +235,7 @@ public void testGetCiphertextFileTypeForFile() throws IOException { Mockito.when(underlyingFileSystemProvider.readAttributes(c9rPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(c9rAttrs); Mockito.when(c9rAttrs.isDirectory()).thenReturn(false); - CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider); + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, fileSystemProperties); CryptoPath path = fileSystem.getPath("/CLEAR"); CiphertextFileType type = mapper.getCiphertextFileType(path); @@ -248,7 +250,7 @@ public void testGetCiphertextFileTypeForDirectory() throws IOException { Mockito.when(underlyingFileSystemProvider.readAttributes(symlinkFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class); Mockito.when(underlyingFileSystemProvider.readAttributes(contentsFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class); - CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider); + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, fileSystemProperties); CryptoPath path = fileSystem.getPath("/CLEAR"); CiphertextFileType type = mapper.getCiphertextFileType(path); @@ -263,7 +265,7 @@ public void testGetCiphertextFileTypeForSymlink() throws IOException { Mockito.when(underlyingFileSystemProvider.readAttributes(symlinkFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(Mockito.mock(BasicFileAttributes.class)); Mockito.when(underlyingFileSystemProvider.readAttributes(contentsFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class); - CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider); + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, fileSystemProperties); CryptoPath path = fileSystem.getPath("/CLEAR"); CiphertextFileType type = mapper.getCiphertextFileType(path); @@ -280,7 +282,7 @@ public void testGetCiphertextFileTypeForShortenedFile() throws IOException { Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("LONGCLEAR"), Mockito.any())).thenReturn(Strings.repeat("A", 1000)); Mockito.when(longFileNameProvider.deflate(Mockito.any())).thenReturn(new LongFileNameProvider.DeflatedFileName(c9rPath, null, null)); - CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider); + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, fileSystemProperties); CryptoPath path = fileSystem.getPath("/LONGCLEAR"); CiphertextFileType type = mapper.getCiphertextFileType(path); diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java index d8d47c54..1cc8a3cf 100644 --- a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java @@ -23,8 +23,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; import static java.nio.file.StandardOpenOption.CREATE_NEW; diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java index 2e3944ea..753f3a3d 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java @@ -1,5 +1,6 @@ package org.cryptomator.cryptofs.dir; +import org.cryptomator.cryptofs.CryptoFileSystemProperties; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.FileNameCryptor; import org.junit.jupiter.api.Assertions; @@ -7,6 +8,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mockito; @@ -20,14 +22,17 @@ class C9rConflictResolverTest { private Cryptor cryptor; private FileNameCryptor fileNameCryptor; + private CryptoFileSystemProperties fileSystemProperties; private C9rConflictResolver conflictResolver; @BeforeEach public void setup() { cryptor = Mockito.mock(Cryptor.class); fileNameCryptor = Mockito.mock(FileNameCryptor.class); + fileSystemProperties = Mockito.mock(CryptoFileSystemProperties.class); Mockito.when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor); - conflictResolver = new C9rConflictResolver(cryptor, "foo"); + Mockito.when(fileSystemProperties.maxNameLength()).thenReturn(220); + conflictResolver = new C9rConflictResolver(cryptor, "foo", fileSystemProperties); } @Test @@ -126,4 +131,11 @@ public void testResolveConflictingSymlinkTrivially(@TempDir Path dir) throws IOE Assertions.assertFalse(Files.exists(unresolved.ciphertextPath)); } + @ParameterizedTest + @CsvSource({"220, 146", "219, 143", "218, 143", "217, 143", "216, 143", "215, 140"}) + public void testCalcMaxCleartextNameLength(int input, int expectedResult) { + int result = conflictResolver.calcMaxCleartextNameLength(input); + Assertions.assertEquals(expectedResult, result); + } + } \ No newline at end of file From 484f4169244f0217befeb79b909135d78e7753c3 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Fri, 16 Apr 2021 15:38:25 +0200 Subject: [PATCH 45/70] renamed constant to shorteningThreshold (related to #95, #102) --- .../cryptofs/CryptoFileSystemProvider.java | 2 +- .../cryptofs/CryptoPathMapper.java | 2 +- .../org/cryptomator/cryptofs/VaultConfig.java | 20 +++++++++---------- .../cryptofs/common/Constants.java | 1 + .../migration/v8/Version8Migrator.java | 2 +- .../cryptomator/cryptofs/VaultConfigTest.java | 10 +++++----- .../migration/v8/Version8MigratorTest.java | 2 +- 7 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 151981c2..0a1330f5 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -141,7 +141,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()).maxFilenameLength(properties.maxNameLength()).build(); + var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).shorteningThreshold(Constants.DEFAULT_SHORTENING_THRESHOLD).build(); try (Masterkey key = properties.keyLoader(keyId.getScheme()).loadKey(keyId); Cryptor cryptor = config.getCipherCombo().getCryptorProvider(strongSecureRandom()).withKey(key)) { rawKey = key.getEncoded(); diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java index c941b13e..c32cc6cd 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java @@ -129,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() > vaultConfig.getMaxFilenameLength()) { + if (ciphertextName.length() > vaultConfig.getShorteningThreshold()) { LongFileNameProvider.DeflatedFileName deflatedFileName = longFileNameProvider.deflate(c9rPath); return new CiphertextFilePath(deflatedFileName.c9sPath, Optional.of(deflatedFileName)); } else { diff --git a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java index f6aa87e8..9574f3b3 100644 --- a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java +++ b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java @@ -32,25 +32,25 @@ public class VaultConfig { private static final String JSON_KEY_VAULTVERSION = "format"; private static final String JSON_KEY_CIPHERCONFIG = "cipherCombo"; - private static final String JSON_KEY_MAXFILENAMELEN = "maxFilenameLen"; + private static final String JSON_KEY_SHORTENING_THRESHOLD = "shorteningThreshold"; private final String id; private final int vaultVersion; private final VaultCipherCombo cipherCombo; - private final int maxFilenameLength; + private final int shorteningThreshold; private VaultConfig(DecodedJWT verifiedConfig) { this.id = verifiedConfig.getId(); this.vaultVersion = verifiedConfig.getClaim(JSON_KEY_VAULTVERSION).asInt(); this.cipherCombo = VaultCipherCombo.valueOf(verifiedConfig.getClaim(JSON_KEY_CIPHERCONFIG).asString()); - this.maxFilenameLength = verifiedConfig.getClaim(JSON_KEY_MAXFILENAMELEN).asInt(); + this.shorteningThreshold = verifiedConfig.getClaim(JSON_KEY_SHORTENING_THRESHOLD).asInt(); } private VaultConfig(VaultConfigBuilder builder) { this.id = builder.id; this.vaultVersion = builder.vaultVersion; this.cipherCombo = builder.cipherCombo; - this.maxFilenameLength = builder.maxFilenameLength; + this.shorteningThreshold = builder.shorteningThreshold; } public String getId() { @@ -65,8 +65,8 @@ public VaultCipherCombo getCipherCombo() { return cipherCombo; } - public int getMaxFilenameLength() { - return maxFilenameLength; + public int getShorteningThreshold() { + return shorteningThreshold; } public String toToken(String keyId, byte[] rawKey) { @@ -75,7 +75,7 @@ public String toToken(String keyId, byte[] rawKey) { .withJWTId(id) // .withClaim(JSON_KEY_VAULTVERSION, vaultVersion) // .withClaim(JSON_KEY_CIPHERCONFIG, cipherCombo.name()) // - .withClaim(JSON_KEY_MAXFILENAMELEN, maxFilenameLength) // + .withClaim(JSON_KEY_SHORTENING_THRESHOLD, shorteningThreshold) // .sign(Algorithm.HMAC256(rawKey)); } @@ -178,15 +178,15 @@ public static class VaultConfigBuilder { private final String id = UUID.randomUUID().toString(); private final int vaultVersion = Constants.VAULT_VERSION; private VaultCipherCombo cipherCombo; - private int maxFilenameLength; + private int shorteningThreshold; public VaultConfigBuilder cipherCombo(VaultCipherCombo cipherCombo) { this.cipherCombo = cipherCombo; return this; } - public VaultConfigBuilder maxFilenameLength(int maxFilenameLength) { - this.maxFilenameLength = maxFilenameLength; + public VaultConfigBuilder shorteningThreshold(int shorteningThreshold) { + this.shorteningThreshold = shorteningThreshold; return this; } diff --git a/src/main/java/org/cryptomator/cryptofs/common/Constants.java b/src/main/java/org/cryptomator/cryptofs/common/Constants.java index eca0fce5..1ad24fe8 100644 --- a/src/main/java/org/cryptomator/cryptofs/common/Constants.java +++ b/src/main/java/org/cryptomator/cryptofs/common/Constants.java @@ -24,6 +24,7 @@ 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_CIPHERTEXT_NAME_LENGTH = 220; // inclusive. calculations done in https://github.com/cryptomator/cryptofs/issues/60#issuecomment-523238303 public static final int MIN_CIPHERTEXT_NAME_LENGTH = 28; // base64(iv).c9r public static final int MAX_ADDITIONAL_PATH_LENGTH = 48; // beginning at d/... see https://github.com/cryptomator/cryptofs/issues/77 diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java index 87f0032f..afa56b10 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java @@ -67,7 +67,7 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey .withKeyId("masterkeyfile:masterkey.cryptomator") // .withClaim("format", 8) // .withClaim("cipherCombo", "SIV_CTRMAC") // - .withClaim("maxFilenameLen", 220) // + .withClaim("shorteningThreshold", 220) // .sign(algorithm); Files.writeString(vaultConfigFile, config, StandardCharsets.US_ASCII, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); LOG.info("Wrote vault config to {}.", vaultConfigFile); diff --git a/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java index 8fec1870..546c0aba 100644 --- a/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java +++ b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java @@ -47,7 +47,7 @@ public class WithValidToken { @BeforeEach public void setup() throws MasterkeyLoadingFailedException { - originalConfig = VaultConfig.createNew().cipherCombo(VaultCipherCombo.SIV_CTRMAC).maxFilenameLength(220).build(); + originalConfig = VaultConfig.createNew().cipherCombo(VaultCipherCombo.SIV_CTRMAC).shorteningThreshold(220).build(); token = originalConfig.toToken("TEST_KEY", rawKey); } @@ -58,7 +58,7 @@ public void testSuccessfulLoad() throws VaultConfigLoadException, MasterkeyLoadi Assertions.assertEquals(originalConfig.getId(), loaded.getId()); Assertions.assertEquals(originalConfig.getVaultVersion(), loaded.getVaultVersion()); Assertions.assertEquals(originalConfig.getCipherCombo(), loaded.getCipherCombo()); - Assertions.assertEquals(originalConfig.getMaxFilenameLength(), loaded.getMaxFilenameLength()); + Assertions.assertEquals(originalConfig.getShorteningThreshold(), loaded.getShorteningThreshold()); } @ParameterizedTest @@ -76,12 +76,12 @@ public void testLoadWithInvalidKey(int pos) { @Test public void testCreateNew() { - var config = VaultConfig.createNew().cipherCombo(VaultCipherCombo.SIV_CTRMAC).maxFilenameLength(220).build(); + var config = VaultConfig.createNew().cipherCombo(VaultCipherCombo.SIV_CTRMAC).shorteningThreshold(220).build(); Assertions.assertNotNull(config.getId()); Assertions.assertEquals(Constants.VAULT_VERSION, config.getVaultVersion()); Assertions.assertEquals(VaultCipherCombo.SIV_CTRMAC, config.getCipherCombo()); - Assertions.assertEquals(220, config.getMaxFilenameLength()); + Assertions.assertEquals(220, config.getShorteningThreshold()); } @Test @@ -96,7 +96,7 @@ public void testLoadExisting() throws VaultConfigLoadException, MasterkeyLoading Mockito.when(decodedJwt.getKeyId()).thenReturn("test:key"); Mockito.when(decodedJwt.getClaim("format")).thenReturn(formatClaim); Mockito.when(decodedJwt.getClaim("cipherCombo")).thenReturn(cipherComboClaim); - Mockito.when(decodedJwt.getClaim("maxFilenameLen")).thenReturn(maxFilenameLenClaim); + Mockito.when(decodedJwt.getClaim("shorteningThreshold")).thenReturn(maxFilenameLenClaim); Mockito.when(key.getEncoded()).thenReturn(new byte[64]); Mockito.when(verification.withClaim("format", 42)).thenReturn(verification); Mockito.when(verification.build()).thenReturn(verifier); diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java index 716e5bfa..cd15fe73 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v8/Version8MigratorTest.java @@ -64,7 +64,7 @@ public void testMigrate() throws CryptoException, IOException { Assertions.assertEquals("masterkeyfile:masterkey.cryptomator", token.getKeyId()); Assertions.assertEquals(8, token.getClaim("format").asInt()); Assertions.assertEquals("SIV_CTRMAC", token.getClaim("cipherCombo").asString()); - Assertions.assertEquals(220, token.getClaim("maxFilenameLen").asInt()); + Assertions.assertEquals(220, token.getClaim("shorteningThreshold").asInt()); } } \ No newline at end of file From 16695e97abdddd0be42f6b0ece59e984b076b0a3 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Fri, 16 Apr 2021 15:55:57 +0200 Subject: [PATCH 46/70] fixes #102 --- .../cryptofs/CryptoFileSystemImpl.java | 27 ++++----- .../cryptofs/CryptoFileSystemProperties.java | 55 +++++-------------- .../cryptofs/FileNameTooLongException.java | 6 +- .../cryptofs/LongFileNameProvider.java | 3 +- .../cryptofs/common/Constants.java | 4 -- .../common/FileSystemCapabilityChecker.java | 7 ++- .../cryptofs/dir/C9rConflictResolver.java | 2 +- .../migration/v7/Version7Migrator.java | 4 +- .../cryptofs/CryptoFileSystemImplTest.java | 36 ++++++------ .../CryptoFileSystemPropertiesTest.java | 22 +++----- ...yptoFileSystemProviderIntegrationTest.java | 16 +++--- .../cryptofs/CryptoPathMapperTest.java | 2 +- ...ptyCiphertextDirectoryIntegrationTest.java | 4 +- .../cryptofs/dir/C9rConflictResolverTest.java | 3 +- 14 files changed, 71 insertions(+), 120 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index c941c747..46e1ad86 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -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; @@ -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()); @@ -348,9 +348,9 @@ private FileChannel newFileChannelFromSymlink(CryptoPath cleartextPath, Effectiv } private FileChannel newFileChannelFromFile(CryptoPath cleartextFilePath, EffectiveOpenOptions options, FileAttribute<?>... attrs) throws IOException { + 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 { @@ -400,6 +400,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; } @@ -418,7 +419,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); @@ -434,7 +434,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()); } @@ -498,6 +497,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; } @@ -517,7 +517,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()) { @@ -534,7 +533,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()); @@ -553,7 +551,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)) { @@ -593,8 +590,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); } @@ -612,13 +609,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()); } } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index fe8d1836..1f64b181 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -38,22 +38,13 @@ 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. @@ -115,8 +106,7 @@ private CryptoFileSystemProperties(Builder builder) { 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) // ); } @@ -162,12 +152,8 @@ String masterkeyFilename() { return (String) get(PROPERTY_MASTERKEY_FILENAME); } - public int maxPathLength() { - return (int) get(PROPERTY_MAX_PATH_LENGTH); - } - - public int maxNameLength() { - return (int) get(PROPERTY_MAX_NAME_LENGTH); + public int maxCleartextNameLength() { + return (int) get(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH); } @Override @@ -223,8 +209,7 @@ public static class Builder { 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() { } @@ -234,8 +219,7 @@ private Builder(Map<String, ?> properties) { 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); } @@ -250,28 +234,15 @@ 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. * - * @param maxNameLength The maximum ciphertext filename length allowed + * @param maxCleartextNameLength The maximum ciphertext 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; } diff --git a/src/main/java/org/cryptomator/cryptofs/FileNameTooLongException.java b/src/main/java/org/cryptomator/cryptofs/FileNameTooLongException.java index 127798db..fcd582b1 100644 --- a/src/main/java/org/cryptomator/cryptofs/FileNameTooLongException.java +++ b/src/main/java/org/cryptomator/cryptofs/FileNameTooLongException.java @@ -7,12 +7,12 @@ * Indicates that an operation failed, as it would result in a ciphertext path that is too long for the underlying file system. * * @see org.cryptomator.cryptofs.common.FileSystemCapabilityChecker#determineSupportedFileNameLength(Path) - * @since 1.9.8 + * @since 2.0.0 */ public class FileNameTooLongException extends FileSystemException { - public FileNameTooLongException(String c9rPathRelativeToVaultRoot, int maxPathLength, int maxNameLength) { - super(c9rPathRelativeToVaultRoot, null, "File name or path too long. Max ciphertext path name length is " + maxPathLength + ". Max ciphertext name is " + maxNameLength); + public FileNameTooLongException(String path, int maxNameLength) { + super(path, null, "File name or path too long. Max cleartext filename name length is " + maxNameLength); } } diff --git a/src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java b/src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java index 0ebab05e..e427a2b0 100644 --- a/src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java @@ -36,7 +36,8 @@ @CryptoFileSystemScoped public class LongFileNameProvider { - private static final int MAX_FILENAME_BUFFER_SIZE = 10 * 1024; // no sane person gives a file a 10kb long name. + public static final int MAX_FILENAME_BUFFER_SIZE = 10 * 1024; // no sane person gives a file a 10kb long name. + private static final BaseEncoding BASE64 = BaseEncoding.base64Url(); private static final Duration MAX_CACHE_AGE = Duration.ofMinutes(1); diff --git a/src/main/java/org/cryptomator/cryptofs/common/Constants.java b/src/main/java/org/cryptomator/cryptofs/common/Constants.java index 1ad24fe8..c9085c44 100644 --- a/src/main/java/org/cryptomator/cryptofs/common/Constants.java +++ b/src/main/java/org/cryptomator/cryptofs/common/Constants.java @@ -25,10 +25,6 @@ private Constants() { public static final String INFLATED_FILE_NAME = "name.c9s"; public static final int DEFAULT_SHORTENING_THRESHOLD = 220; - public static final int MAX_CIPHERTEXT_NAME_LENGTH = 220; // inclusive. calculations done in https://github.com/cryptomator/cryptofs/issues/60#issuecomment-523238303 - public static final int MIN_CIPHERTEXT_NAME_LENGTH = 28; // base64(iv).c9r - public static final int MAX_ADDITIONAL_PATH_LENGTH = 48; // beginning at d/... see https://github.com/cryptomator/cryptofs/issues/77 - public static final int MAX_CIPHERTEXT_PATH_LENGTH = MAX_CIPHERTEXT_NAME_LENGTH + MAX_ADDITIONAL_PATH_LENGTH; 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 diff --git a/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java b/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java index b122e4bc..f3ea0f78 100644 --- a/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java +++ b/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java @@ -21,6 +21,9 @@ public class FileSystemCapabilityChecker { private static final Logger LOG = LoggerFactory.getLogger(FileSystemCapabilityChecker.class); + private static final int MAX_CIPHERTEXT_NAME_LENGTH = 220; // inclusive. calculations done in https://github.com/cryptomator/cryptofs/issues/60#issuecomment-523238303 + private static final int MIN_CIPHERTEXT_NAME_LENGTH = 28; // base64(iv).c9r + private static final int MAX_ADDITIONAL_PATH_LENGTH = 48; // beginning at d/... see https://github.com/cryptomator/cryptofs/issues/77 public enum Capability { /** @@ -98,8 +101,8 @@ public void assertWriteAccess(Path pathToVault) throws MissingCapabilityExceptio * @throws IOException If unable to perform this check */ public int determineSupportedFileNameLength(Path pathToVault) throws IOException { - int subPathLength = Constants.MAX_ADDITIONAL_PATH_LENGTH - 2; // subtract "c/" - return determineSupportedFileNameLength(pathToVault.resolve("c"), subPathLength, Constants.MIN_CIPHERTEXT_NAME_LENGTH, Constants.MAX_CIPHERTEXT_NAME_LENGTH); + int subPathLength = MAX_ADDITIONAL_PATH_LENGTH - 2; // subtract "c/" + return determineSupportedFileNameLength(pathToVault.resolve("c"), subPathLength, MIN_CIPHERTEXT_NAME_LENGTH, MAX_CIPHERTEXT_NAME_LENGTH); } /** diff --git a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java index 7a5b1a1b..0dd1a748 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java @@ -99,7 +99,7 @@ int calcMaxCleartextNameLength(int maxCiphertextNameLength) { private Node renameConflictingFile(Path canonicalPath, Path conflictingPath, String cleartext) throws IOException { assert Files.exists(canonicalPath); final int beginOfFileExtension = cleartext.lastIndexOf('.'); - final int maxCiphertextNameLength = vaultConfig.getMaxFilenameLength(); + final int maxCiphertextNameLength = vaultConfig.getShorteningThreshold(); final int maxCleartextNameLength = calcMaxCleartextNameLength(maxCiphertextNameLength); final String fileExtension = (beginOfFileExtension > 0) ? cleartext.substring(beginOfFileExtension) : ""; final String basename = (beginOfFileExtension > 0) ? cleartext.substring(0, beginOfFileExtension) : cleartext; diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java index b52efb5e..c883f909 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java @@ -94,13 +94,13 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey // fail if ciphertext paths are too long: if (preMigrationVisitor.getMaxCiphertextPathLength() > pathLengthLimit) { LOG.error("Migration aborted due to unsupported path length (required {}) of underlying file system (supports {}). Vault is unchanged.", preMigrationVisitor.getMaxCiphertextPathLength(), pathLengthLimit); - throw new FileNameTooLongException(preMigrationVisitor.getLongestPath().toString(), pathLengthLimit, filenameLengthLimit); + throw new FileNameTooLongException(preMigrationVisitor.getLongestPath().toString(), filenameLengthLimit); } // fail if ciphertext names are too long: if (preMigrationVisitor.getMaxCiphertextNameLength() > filenameLengthLimit) { LOG.error("Migration aborted due to unsupported filename length (required {}) of underlying file system (supports {}). Vault is unchanged.", preMigrationVisitor.getMaxCiphertextNameLength(), filenameLengthLimit); - throw new FileNameTooLongException(preMigrationVisitor.getPathWithLongestName().toString(), pathLengthLimit, filenameLengthLimit); + throw new FileNameTooLongException(preMigrationVisitor.getPathWithLongestName().toString(), filenameLengthLimit); } // start migration: diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java index 1d878ebb..e5740192 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java @@ -74,7 +74,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.mockito.internal.verification.VerificationModeFactory.atLeast; @@ -115,8 +114,7 @@ public void setup() { return other; }); - when(fileSystemProperties.maxPathLength()).thenReturn(Constants.MAX_CIPHERTEXT_PATH_LENGTH); - when(fileSystemProperties.maxNameLength()).thenReturn(Constants.MAX_CIPHERTEXT_NAME_LENGTH); + when(fileSystemProperties.maxCleartextNameLength()).thenReturn(32768); inTest = new CryptoFileSystemImpl(provider, cryptoFileSystems, pathToVault, cryptor, fileStore, stats, cryptoPathMapper, cryptoPathFactory, @@ -360,6 +358,7 @@ public class NewFileChannel { @BeforeEach public void setup() throws IOException { + when(cleartextPath.getFileName()).thenReturn(cleartextPath); when(cryptoPathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE); when(cryptoPathMapper.getCiphertextFilePath(cleartextPath)).thenReturn(ciphertextPath); when(ciphertextPath.getFilePath()).thenReturn(ciphertextFilePath); @@ -509,6 +508,7 @@ public class CopyAndMove { @BeforeEach public void setup() throws IOException { + when(cleartextDestination.getFileName()).thenReturn(cleartextDestination); when(ciphertextSource.getRawPath()).thenReturn(ciphertextSourceFile); when(ciphertextSource.getFilePath()).thenReturn(ciphertextSourceFile); when(ciphertextSource.getSymlinkFilePath()).thenReturn(ciphertextSourceFile); @@ -545,10 +545,12 @@ public class Move { @Test public void moveFileToItselfDoesNothing() throws IOException { + when(cleartextSource.getFileName()).thenReturn(cleartextSource); + inTest.move(cleartextSource, cleartextSource); verify(readonlyFlag).assertWritable(); - verifyNoInteractions(cleartextSource); + verifyNoInteractions(cryptoPathMapper); } @Test @@ -701,10 +703,12 @@ public void setup() throws IOException, ReflectiveOperationException { @Test public void copyFileToItselfDoesNothing() throws IOException { + when(cleartextSource.getFileName()).thenReturn(cleartextSource); + inTest.copy(cleartextSource, cleartextSource); verify(readonlyFlag).assertWritable(); - verifyNoInteractions(cleartextSource); + verifyNoInteractions(cryptoPathMapper); } @Test @@ -744,6 +748,7 @@ public void copySymlink() throws IOException { public void copySymlinkTarget() throws IOException { when(cryptoPathMapper.getCiphertextFileType(cleartextSource)).thenReturn(CiphertextFileType.SYMLINK); when(cryptoPathMapper.getCiphertextFileType(cleartextDestination)).thenThrow(NoSuchFileException.class); + when(destinationLinkTarget.getFileName()).thenReturn(destinationLinkTarget); CopyOption option1 = mock(CopyOption.class); CopyOption option2 = mock(CopyOption.class); @@ -932,30 +937,30 @@ public class CreateDirectory { private final CryptoFileSystemProvider provider = mock(CryptoFileSystemProvider.class); private final CryptoFileSystemImpl fileSystem = mock(CryptoFileSystemImpl.class); + private final CryptoPath path = mock(CryptoPath.class, "path"); + private final CryptoPath parent = mock(CryptoPath.class, "parent"); @BeforeEach public void setup() { when(fileSystem.provider()).thenReturn(provider); + when(path.getFileName()).thenReturn(path); + when(path.getParent()).thenReturn(parent); } @Test public void createDirectoryIfPathHasNoParentDoesNothing() throws IOException { - CryptoPath path = mock(CryptoPath.class); when(path.getParent()).thenReturn(null); inTest.createDirectory(path); verify(readonlyFlag).assertWritable(); verify(path).getParent(); - verifyNoMoreInteractions(path); + verifyNoMoreInteractions(cryptoPathMapper); } @Test public void createDirectoryIfPathsParentDoesNotExistsThrowsNoSuchFileException() throws IOException { - CryptoPath path = mock(CryptoPath.class); - CryptoPath parent = mock(CryptoPath.class); Path ciphertextParent = mock(Path.class); - when(path.getParent()).thenReturn(parent); when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("foo", ciphertextParent)); when(ciphertextParent.getFileSystem()).thenReturn(fileSystem); doThrow(NoSuchFileException.class).when(provider).checkAccess(ciphertextParent); @@ -967,11 +972,8 @@ public void createDirectoryIfPathsParentDoesNotExistsThrowsNoSuchFileException() } @Test - public void createDirectoryIfPathCyphertextFileDoesExistThrowsFileAlreadyException() throws IOException { - CryptoPath path = mock(CryptoPath.class); - CryptoPath parent = mock(CryptoPath.class); + public void createDirectoryIfPathCiphertextFileDoesExistThrowsFileAlreadyException() throws IOException { Path ciphertextParent = mock(Path.class); - when(path.getParent()).thenReturn(parent); when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("foo", ciphertextParent)); when(ciphertextParent.getFileSystem()).thenReturn(fileSystem); doThrow(new FileAlreadyExistsException(path.toString())).when(cryptoPathMapper).assertNonExisting(path); @@ -984,8 +986,6 @@ public void createDirectoryIfPathCyphertextFileDoesExistThrowsFileAlreadyExcepti @Test public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOException { - CryptoPath path = mock(CryptoPath.class, "path"); - CryptoPath parent = mock(CryptoPath.class, "parent"); Path ciphertextParent = mock(Path.class, "ciphertextParent"); Path ciphertextRawPath = mock(Path.class, "d/00/00/path.c9r"); Path ciphertextDirFile = mock(Path.class, "d/00/00/path.c9r/dir.c9r"); @@ -993,7 +993,6 @@ public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOExceptio CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class, "ciphertext"); String dirId = "DirId1234ABC"; FileChannelMock channel = new FileChannelMock(100); - when(path.getParent()).thenReturn(parent); when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile); when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath); when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, ciphertextDirPath)); @@ -1016,8 +1015,6 @@ public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOExceptio @Test public void createDirectoryClearsDirIdAndDeletesDirFileIfCreatingDirFails() throws IOException { - CryptoPath path = mock(CryptoPath.class, "path"); - CryptoPath parent = mock(CryptoPath.class, "parent"); Path ciphertextParent = mock(Path.class, "ciphertextParent"); Path ciphertextRawPath = mock(Path.class, "d/00/00/path.c9r"); Path ciphertextDirFile = mock(Path.class, "d/00/00/path.c9r/dir.c9r"); @@ -1025,7 +1022,6 @@ public void createDirectoryClearsDirIdAndDeletesDirFileIfCreatingDirFails() thro CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class, "ciphertext"); String dirId = "DirId1234ABC"; FileChannelMock channel = new FileChannelMock(100); - when(path.getParent()).thenReturn(parent); when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile); when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath); when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, ciphertextDirPath)); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java index 676174e8..78fc7cac 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java @@ -1,6 +1,5 @@ package org.cryptomator.cryptofs; -import org.cryptomator.cryptofs.CryptoFileSystemProperties.*; import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -50,8 +49,7 @@ public void testSetMasterkeyFilenameAndReadonlyFlag() { anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // - anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // - anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // + anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)))); } @@ -62,22 +60,19 @@ public void testFromMap() { String masterkeyFilename = "aMasterkeyFilename"; map.put(PROPERTY_KEYLOADERS, Set.of(keyLoader)); map.put(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename); - map.put(PROPERTY_MAX_PATH_LENGTH, 1000); - map.put(PROPERTY_MAX_NAME_LENGTH, 255); + map.put(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, 255); map.put(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)); CryptoFileSystemProperties inTest = cryptoFileSystemPropertiesFrom(map).build(); MatcherAssert.assertThat(inTest.masterkeyFilename(), is(masterkeyFilename)); MatcherAssert.assertThat(inTest.readonly(), is(true)); - MatcherAssert.assertThat(inTest.maxPathLength(), is(1000)); - MatcherAssert.assertThat(inTest.maxNameLength(), is(255)); + MatcherAssert.assertThat(inTest.maxCleartextNameLength(), is(255)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // - anEntry(PROPERTY_MAX_PATH_LENGTH, 1000), // - anEntry(PROPERTY_MAX_NAME_LENGTH, 255), // + anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, 255), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)))); } @@ -98,8 +93,7 @@ public void testWrapMapWithTrueReadonly() { anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // - anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // - anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // + anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)))); } @@ -120,8 +114,7 @@ public void testWrapMapWithFalseReadonly() { anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // - anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // - anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // + anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)))); } @@ -172,8 +165,7 @@ public void testWrapMapWithoutReadonly() { anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, DEFAULT_MASTERKEY_FILENAME), // - anEntry(PROPERTY_MAX_PATH_LENGTH, DEFAULT_MAX_PATH_LENGTH), // - anEntry(PROPERTY_MAX_NAME_LENGTH, DEFAULT_MAX_NAME_LENGTH), // + anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)) ) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index 97ef9c39..65b1b845 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -90,7 +90,7 @@ public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFail .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // .withKeyLoaders(keyLoader) // - .withMaxPathLength(100) + .withMaxCleartextNameLength(50) .build(); CryptoFileSystemProvider.initialize(tmpDir, properties, URI.create("test:key")); fs = CryptoFileSystemProvider.newFileSystem(tmpDir, properties); @@ -116,7 +116,7 @@ public void tearDownEach() throws IOException { @DisplayName("expect create file to fail with FileNameTooLongException") @Test public void testCreateFileExceedingPathLengthLimit() { - Path p = fs.getPath("/this-should-result-in-ciphertext-path-longer-than-100"); + Path p = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); Assertions.assertThrows(FileNameTooLongException.class, () -> { Files.createFile(p); }); @@ -125,7 +125,7 @@ public void testCreateFileExceedingPathLengthLimit() { @DisplayName("expect create directory to fail with FileNameTooLongException") @Test public void testCreateDirExceedingPathLengthLimit() { - Path p = fs.getPath("/this-should-result-in-ciphertext-path-longer-than-100"); + Path p = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); Assertions.assertThrows(FileNameTooLongException.class, () -> { Files.createDirectory(p); }); @@ -134,18 +134,18 @@ public void testCreateDirExceedingPathLengthLimit() { @DisplayName("expect create symlink to fail with FileNameTooLongException") @Test public void testCreateSymlinkExceedingPathLengthLimit() { - Path p = fs.getPath("/this-should-result-in-ciphertext-path-longer-than-100"); + Path p = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); Assertions.assertThrows(FileNameTooLongException.class, () -> { Files.createSymbolicLink(p, shortFilePath); }); } @DisplayName("expect move to fail with FileNameTooLongException") - @ParameterizedTest(name = "move {0} -> this-should-result-in-ciphertext-path-longer-than-100") + @ParameterizedTest(name = "move {0} -> this-cleartext-filename-is-longer-than-50-characters") @ValueSource(strings = {"/short-enough.txt", "/short-enough-dir", "/symlink.txt"}) public void testMoveExceedingPathLengthLimit(String path) { Path src = fs.getPath(path); - Path dst = fs.getPath("/this-should-result-in-ciphertext-path-longer-than-100"); + Path dst = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); Assertions.assertThrows(FileNameTooLongException.class, () -> { Files.move(src, dst); }); @@ -154,11 +154,11 @@ public void testMoveExceedingPathLengthLimit(String path) { } @DisplayName("expect copy to fail with FileNameTooLongException") - @ParameterizedTest(name = "copy {0} -> this-should-result-in-ciphertext-path-longer-than-100") + @ParameterizedTest(name = "copy {0} -> this-cleartext-filename-is-longer-than-50-characters") @ValueSource(strings = {"/short-enough.txt", "/short-enough-dir", "/symlink.txt"}) public void testCopyExceedingPathLengthLimit(String path) { Path src = fs.getPath(path); - Path dst = fs.getPath("/this-should-result-in-ciphertext-path-longer-than-100"); + Path dst = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); Assertions.assertThrows(FileNameTooLongException.class, () -> { Files.copy(src, dst, LinkOption.NOFOLLOW_LINKS); }); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java index e0512869..76ac28f1 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java @@ -46,7 +46,7 @@ public void setup() { CryptoPath empty = cryptoPathFactory.emptyFor(fileSystem); Mockito.when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor); Mockito.when(pathToVault.resolve("d")).thenReturn(dataRoot); - Mockito.when(vaultConfig.getMaxFilenameLength()).thenReturn(220); + Mockito.when(vaultConfig.getShorteningThreshold()).thenReturn(220); Mockito.when(fileSystem.getPath(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenAnswer(invocation -> { String first = invocation.getArgument(0); if (invocation.getArguments().length == 1) { diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java index 021d3d86..fe566bac 100644 --- a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java @@ -32,7 +32,6 @@ import static java.nio.file.StandardOpenOption.CREATE_NEW; import static org.cryptomator.cryptofs.CryptoFileSystemUri.create; -import static org.cryptomator.cryptofs.common.Constants.MAX_CIPHERTEXT_NAME_LENGTH; /** * Regression tests https://github.com/cryptomator/cryptofs/issues/17. @@ -125,14 +124,13 @@ public void testDeleteNonEmptyDir() throws IOException { } @Test - @Disabled // c9s not yet implemented public void testDeleteDirectoryContainingLongNamedDirectory() throws IOException { Path cleartextDirectory = fileSystem.getPath("/e"); Files.createDirectory(cleartextDirectory); // a // .. LongNameaaa... - String name = "LongName" + Strings.repeat("a", MAX_CIPHERTEXT_NAME_LENGTH); + String name = "LongName" + Strings.repeat("a", Constants.DEFAULT_SHORTENING_THRESHOLD); createFolder(cleartextDirectory, name); Assertions.assertThrows(DirectoryNotEmptyException.class, () -> { diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java index 678e3f16..096bd538 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java @@ -1,6 +1,5 @@ package org.cryptomator.cryptofs.dir; -import org.cryptomator.cryptofs.CryptoFileSystemProperties; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.FileNameCryptor; @@ -32,7 +31,7 @@ public void setup() { fileNameCryptor = Mockito.mock(FileNameCryptor.class); vaultConfig = Mockito.mock(VaultConfig.class); Mockito.when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor); - Mockito.when(vaultConfig.getMaxFilenameLength()).thenReturn(220); + Mockito.when(vaultConfig.getShorteningThreshold()).thenReturn(220); conflictResolver = new C9rConflictResolver(cryptor, "foo", vaultConfig); } From bbc3c86662fedfa347fa0ddcd2edf5445321d469 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Fri, 16 Apr 2021 16:30:13 +0200 Subject: [PATCH 47/70] Updated FileSystemCapabilityChecker API (related to #102) --- .../cryptofs/FileNameTooLongException.java | 2 +- .../common/FileSystemCapabilityChecker.java | 22 +++++++++++++------ .../migration/v7/Version7Migrator.java | 2 +- .../FileSystemCapabilityCheckerTest.java | 22 +++++++++++++++---- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/FileNameTooLongException.java b/src/main/java/org/cryptomator/cryptofs/FileNameTooLongException.java index fcd582b1..cdcdc598 100644 --- a/src/main/java/org/cryptomator/cryptofs/FileNameTooLongException.java +++ b/src/main/java/org/cryptomator/cryptofs/FileNameTooLongException.java @@ -6,7 +6,7 @@ /** * Indicates that an operation failed, as it would result in a ciphertext path that is too long for the underlying file system. * - * @see org.cryptomator.cryptofs.common.FileSystemCapabilityChecker#determineSupportedFileNameLength(Path) + * @see org.cryptomator.cryptofs.common.FileSystemCapabilityChecker#determineSupportedCleartextFileNameLength(Path) * @since 2.0.0 */ public class FileNameTooLongException extends FileSystemException { diff --git a/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java b/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java index f3ea0f78..f6162bfd 100644 --- a/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java +++ b/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java @@ -93,6 +93,14 @@ public void assertWriteAccess(Path pathToVault) throws MissingCapabilityExceptio } } + public int determineSupportedCleartextFileNameLength(Path pathToVault) throws IOException { + int maxCiphertextLen = determineSupportedCiphertextFileNameLength(pathToVault); + assert maxCiphertextLen >= MIN_CIPHERTEXT_NAME_LENGTH; + // math explained in https://github.com/cryptomator/cryptofs/issues/60#issuecomment-523238303; + // subtract 4 for file extension, base64-decode, subtract 16 for IV + return (maxCiphertextLen - 4) / 4 * 3 - 16; + } + /** * Determinse the number of chars a ciphertext filename (including its extension) is allowed to have inside a vault's <code>d/XX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY/</code> directory. * @@ -100,9 +108,9 @@ public void assertWriteAccess(Path pathToVault) throws MissingCapabilityExceptio * @return Number of chars a .c9r file is allowed to have * @throws IOException If unable to perform this check */ - public int determineSupportedFileNameLength(Path pathToVault) throws IOException { + public int determineSupportedCiphertextFileNameLength(Path pathToVault) throws IOException { int subPathLength = MAX_ADDITIONAL_PATH_LENGTH - 2; // subtract "c/" - return determineSupportedFileNameLength(pathToVault.resolve("c"), subPathLength, MIN_CIPHERTEXT_NAME_LENGTH, MAX_CIPHERTEXT_NAME_LENGTH); + return determineSupportedCiphertextFileNameLength(pathToVault.resolve("c"), subPathLength, MIN_CIPHERTEXT_NAME_LENGTH, MAX_CIPHERTEXT_NAME_LENGTH); } /** @@ -115,7 +123,7 @@ public int determineSupportedFileNameLength(Path pathToVault) throws IOException * @return The supported filename length inside a subdirectory of <code>dir</code> with <code>subPathLength</code> chars * @throws IOException If unable to perform this check */ - public int determineSupportedFileNameLength(Path dir, int subPathLength, int minFileNameLength, int maxFileNameLength) throws IOException { + public int determineSupportedCiphertextFileNameLength(Path dir, int subPathLength, int minFileNameLength, int maxFileNameLength) throws IOException { Preconditions.checkArgument(subPathLength >= 6, "subPathLength must be larger than charcount(a/nnn/)"); Preconditions.checkArgument(minFileNameLength > 0); Preconditions.checkArgument(maxFileNameLength <= 999); @@ -130,13 +138,13 @@ public int determineSupportedFileNameLength(Path dir, int subPathLength, int min throw new IOException("Unable to read dir"); } // perform actual check: - return determineSupportedFileNameLength(fillerDir, minFileNameLength, maxFileNameLength + 1); + return determineSupportedCiphertextFileNameLength(fillerDir, minFileNameLength, maxFileNameLength + 1); } finally { deleteRecursivelySilently(fillerDir); } } - private int determineSupportedFileNameLength(Path p, int lowerBoundIncl, int upperBoundExcl) { + private int determineSupportedCiphertextFileNameLength(Path p, int lowerBoundIncl, int upperBoundExcl) { assert lowerBoundIncl < upperBoundExcl; int mid = (lowerBoundIncl + upperBoundExcl) / 2; assert mid < upperBoundExcl; @@ -145,9 +153,9 @@ private int determineSupportedFileNameLength(Path p, int lowerBoundIncl, int upp } assert lowerBoundIncl < mid; if (canHandleFileNameLength(p, mid)) { - return determineSupportedFileNameLength(p, mid, upperBoundExcl); + return determineSupportedCiphertextFileNameLength(p, mid, upperBoundExcl); } else { - return determineSupportedFileNameLength(p, lowerBoundIncl, mid); + return determineSupportedCiphertextFileNameLength(p, lowerBoundIncl, mid); } } diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java index c883f909..e61c3cab 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java @@ -66,7 +66,7 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName()); // check file system capabilities: - int filenameLengthLimit = new FileSystemCapabilityChecker().determineSupportedFileNameLength(vaultRoot.resolve("c"), 46, 28, 220); + int filenameLengthLimit = new FileSystemCapabilityChecker().determineSupportedCiphertextFileNameLength(vaultRoot.resolve("c"), 46, 28, 220); int pathLengthLimit = filenameLengthLimit + 48; // TODO PreMigrationVisitor preMigrationVisitor; if (filenameLengthLimit >= 220) { diff --git a/src/test/java/org/cryptomator/cryptofs/common/FileSystemCapabilityCheckerTest.java b/src/test/java/org/cryptomator/cryptofs/common/FileSystemCapabilityCheckerTest.java index 3a58f8f0..4f7fefd1 100644 --- a/src/test/java/org/cryptomator/cryptofs/common/FileSystemCapabilityCheckerTest.java +++ b/src/test/java/org/cryptomator/cryptofs/common/FileSystemCapabilityCheckerTest.java @@ -3,12 +3,13 @@ import org.cryptomator.cryptofs.mocks.DirectoryStreamMock; import org.cryptomator.cryptofs.mocks.SeekableByteChannelMock; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.mockito.Mockito; import java.io.IOException; @@ -62,7 +63,7 @@ public void testUnlimitedLength() throws IOException { return checkDirMock; }); - int determinedLimit = new FileSystemCapabilityChecker().determineSupportedFileNameLength(pathToVault); + int determinedLimit = new FileSystemCapabilityChecker().determineSupportedCiphertextFileNameLength(pathToVault); Assertions.assertEquals(220, determinedLimit); } @@ -95,7 +96,7 @@ public void testLimitedLengthDuringDirListing() throws IOException { return checkDirMock; }); - int determinedLimit = new FileSystemCapabilityChecker().determineSupportedFileNameLength(pathToVault); + int determinedLimit = new FileSystemCapabilityChecker().determineSupportedCiphertextFileNameLength(pathToVault); Assertions.assertEquals(limit, determinedLimit); } @@ -125,10 +126,23 @@ public void testLimitedLengthDuringFileCreation() throws IOException { return checkDirMock; }); - int determinedLimit = new FileSystemCapabilityChecker().determineSupportedFileNameLength(pathToVault); + int determinedLimit = new FileSystemCapabilityChecker().determineSupportedCiphertextFileNameLength(pathToVault); Assertions.assertEquals(limit, determinedLimit); } + + @DisplayName("determineSupportedCleartextFileNameLength(...)") + @ParameterizedTest(name = "ciphertext length {0} -> cleartext length {1}") + @CsvSource({"220, 146", "219, 143", "218, 143", "217, 143", "216, 143", "215, 140"}) + public void testDetermineSupportedCleartextFileNameLength(int ciphertextLimit, int expectedCleartextLimit) throws IOException { + Path path = Mockito.mock(Path.class); + FileSystemCapabilityChecker checker = Mockito.spy(new FileSystemCapabilityChecker()); + Mockito.doReturn(ciphertextLimit).when(checker).determineSupportedCiphertextFileNameLength(path); + + int result = checker.determineSupportedCleartextFileNameLength(path); + + Assertions.assertEquals(expectedCleartextLimit, result); + } } From 9eba1d9e0f1ab77e76a5fe2db9d8a662d58fc246 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Fri, 16 Apr 2021 16:48:45 +0200 Subject: [PATCH 48/70] added test case hitting shortening threshold --- .../cryptofs/dir/C9rConflictResolver.java | 19 ++++--------- .../cryptofs/dir/C9rConflictResolverTest.java | 28 +++++++++++++------ 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java index 0dd1a748..33570fd1 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java @@ -35,13 +35,15 @@ class C9rConflictResolver { private final Cryptor cryptor; private final byte[] dirId; - private final VaultConfig vaultConfig; + private final int maxC9rFileNameLength; + private final int maxCleartextFileNameLength; @Inject public C9rConflictResolver(Cryptor cryptor, @Named("dirId") String dirId, VaultConfig vaultConfig) { this.cryptor = cryptor; this.dirId = dirId.getBytes(StandardCharsets.US_ASCII); - this.vaultConfig = vaultConfig; + this.maxC9rFileNameLength = vaultConfig.getShorteningThreshold(); + this.maxCleartextFileNameLength = (maxC9rFileNameLength - 4) / 4 * 3 - 16; // math from FileSystemCapabilityChecker.determineSupportedCleartextFileNameLength() } public Stream<Node> process(Node node) { @@ -80,13 +82,6 @@ private Stream<Node> resolveConflict(Node conflicting, Path canonicalPath) throw } } - // visible for testing - int calcMaxCleartextNameLength(int maxCiphertextNameLength) { - // math explained in https://github.com/cryptomator/cryptofs/issues/60#issuecomment-523238303; - // subtract 4 for file extension, base64-decode, subtract 16 for IV - return (maxCiphertextNameLength - 4) / 4 * 3 - 16; - } - /** * Resolves a conflict by renaming the conflicting file. * @@ -99,11 +94,9 @@ int calcMaxCleartextNameLength(int maxCiphertextNameLength) { private Node renameConflictingFile(Path canonicalPath, Path conflictingPath, String cleartext) throws IOException { assert Files.exists(canonicalPath); final int beginOfFileExtension = cleartext.lastIndexOf('.'); - final int maxCiphertextNameLength = vaultConfig.getShorteningThreshold(); - final int maxCleartextNameLength = calcMaxCleartextNameLength(maxCiphertextNameLength); final String fileExtension = (beginOfFileExtension > 0) ? cleartext.substring(beginOfFileExtension) : ""; final String basename = (beginOfFileExtension > 0) ? cleartext.substring(0, beginOfFileExtension) : cleartext; - final String lengthRestrictedBasename = basename.substring(0, Math.min(basename.length(), maxCleartextNameLength - fileExtension.length() - 5)); // 5 chars for conflict suffix " (42)" + final String lengthRestrictedBasename = basename.substring(0, Math.min(basename.length(), maxCleartextFileNameLength - fileExtension.length() - 5)); // 5 chars for conflict suffix " (42)" String alternativeCleartext; String alternativeCiphertext; String alternativeCiphertextName; @@ -115,7 +108,7 @@ private Node renameConflictingFile(Path canonicalPath, Path conflictingPath, Str alternativeCiphertextName = alternativeCiphertext + Constants.CRYPTOMATOR_FILE_SUFFIX; alternativePath = canonicalPath.resolveSibling(alternativeCiphertextName); } while (Files.exists(alternativePath)); - assert alternativeCiphertextName.length() <= maxCiphertextNameLength; + assert alternativeCiphertextName.length() <= maxC9rFileNameLength; LOG.info("Moving conflicting file {} to {}", conflictingPath, alternativePath); Files.move(conflictingPath, alternativePath, StandardCopyOption.ATOMIC_MOVE); Node node = new Node(alternativePath); diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java index 096bd538..75113ae3 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java @@ -31,7 +31,7 @@ public void setup() { fileNameCryptor = Mockito.mock(FileNameCryptor.class); vaultConfig = Mockito.mock(VaultConfig.class); Mockito.when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor); - Mockito.when(vaultConfig.getShorteningThreshold()).thenReturn(220); + Mockito.when(vaultConfig.getShorteningThreshold()).thenReturn(44); // results in max cleartext size = 14 conflictResolver = new C9rConflictResolver(cryptor, "foo", vaultConfig); } @@ -77,6 +77,25 @@ public void testResolveConflictingFileByChoosingNewName(@TempDir Path dir) throw Assertions.assertFalse(Files.exists(unresolved.ciphertextPath)); } + @Test + public void testResolveConflictingFileByChoosingNewLengthLimitedName(@TempDir Path dir) throws IOException { + Files.createFile(dir.resolve("foo (1).c9r")); + Files.createFile(dir.resolve("foo.c9r")); + Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn("baz"); + Node unresolved = new Node(dir.resolve("foo (1).c9r")); + unresolved.cleartextName = "hello world.txt"; + unresolved.extractedCiphertext = "foo"; + + Stream<Node> result = conflictResolver.process(unresolved); + Node resolved = result.findAny().get(); + + Assertions.assertNotEquals(unresolved, resolved); + Assertions.assertEquals("baz.c9r", resolved.fullCiphertextFileName); + Assertions.assertEquals("hello (1).txt", resolved.cleartextName); + Assertions.assertTrue(Files.exists(resolved.ciphertextPath)); + Assertions.assertFalse(Files.exists(unresolved.ciphertextPath)); + } + @Test public void testResolveConflictingFileTrivially(@TempDir Path dir) throws IOException { Files.createFile(dir.resolve("foo (1).c9r")); @@ -131,11 +150,4 @@ public void testResolveConflictingSymlinkTrivially(@TempDir Path dir) throws IOE Assertions.assertFalse(Files.exists(unresolved.ciphertextPath)); } - @ParameterizedTest - @CsvSource({"220, 146", "219, 143", "218, 143", "217, 143", "216, 143", "215, 140"}) - public void testCalcMaxCleartextNameLength(int input, int expectedResult) { - int result = conflictResolver.calcMaxCleartextNameLength(input); - Assertions.assertEquals(expectedResult, result); - } - } \ No newline at end of file From aa51d2114d35132220c536fb6da35a4d47f7a537 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 19 Apr 2021 13:03:34 +0200 Subject: [PATCH 49/70] allow reading the shortening threshold from an unverified vault config --- src/main/java/org/cryptomator/cryptofs/VaultConfig.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java index 9574f3b3..18356d0a 100644 --- a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java +++ b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java @@ -140,12 +140,19 @@ public URI getKeyId() { } /** - * @return The unverified vault version (JWT signature not verified) + * @return The unverified vault version (signature not verified) */ public int allegedVaultVersion() { return unverifiedConfig.getClaim(JSON_KEY_VAULTVERSION).asInt(); } + /** + * @return The unverified shortening threshold (signature not verified) + */ + public int allegedShorteningThreshold() { + return unverifiedConfig.getClaim(JSON_KEY_SHORTENING_THRESHOLD).asInt(); + } + /** * Decodes a vault configuration stored in JWT format. * From f84d710dcf2c6a903a37c2088eac2245171fd12b Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Tue, 20 Apr 2021 13:17:00 +0200 Subject: [PATCH 50/70] allow readonly file channels even when exceeding maxCleartextNameLength --- .../cryptofs/CryptoFileSystemImpl.java | 4 +- .../cryptofs/CryptoFileSystemProperties.java | 8 ++-- .../cryptofs/CryptoFileSystemImplTest.java | 45 +++++++++++++++++++ 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index 46e1ad86..5da6de99 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -348,7 +348,9 @@ private FileChannel newFileChannelFromSymlink(CryptoPath cleartextPath, Effectiv } private FileChannel newFileChannelFromFile(CryptoPath cleartextFilePath, EffectiveOpenOptions options, FileAttribute<?>... attrs) throws IOException { - assertCleartextNameLengthAllowed(cleartextFilePath); + if (options.create() || options.createNew()) { + assertCleartextNameLengthAllowed(cleartextFilePath); + } CiphertextFilePath ciphertextPath = cryptoPathMapper.getCiphertextFilePath(cleartextFilePath); Path ciphertextFilePath = ciphertextPath.getFilePath(); if (options.createNew() && openCryptoFiles.get(ciphertextFilePath).isPresent()) { diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index 1f64b181..b917a60f 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -152,7 +152,7 @@ String masterkeyFilename() { return (String) get(PROPERTY_MASTERKEY_FILENAME); } - public int maxCleartextNameLength() { + int maxCleartextNameLength() { return (int) get(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH); } @@ -235,9 +235,11 @@ private <T> void checkedSet(Class<T> type, String key, Map<String, ?> properties } /** - * 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 maxCleartextNameLength The maximum ciphertext filename length allowed + * @param maxCleartextNameLength The maximum cleartext filename length allowed * @return this * @since 2.0.0 */ diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java index e5740192..cc75fdea 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java @@ -19,6 +19,7 @@ import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -367,6 +368,50 @@ public void setup() throws IOException { when(openCryptoFile.newFileChannel(any())).thenReturn(fileChannel); } + @Nested + public class LimitedCleartextNameLength { + + @BeforeEach + public void setup() throws IOException { + Assumptions.assumeTrue(cleartextPath.getFileName().toString().length() == 9); + } + + @Test + @DisplayName("read-only always works") + public void testNewFileChannelReadOnlyDespiteMaxName() throws IOException { + Mockito.doReturn(0).when(fileSystemProperties).maxCleartextNameLength(); + + FileChannel ch = inTest.newFileChannel(cleartextPath, EnumSet.of(StandardOpenOption.READ)); + + Assertions.assertSame(fileChannel, ch); + verify(readonlyFlag, Mockito.never()).assertWritable(); + } + + @Test + @DisplayName("create new fails when exceeding limit") + public void testNewFileChannelCreate1() { + Mockito.doReturn(0).when(fileSystemProperties).maxCleartextNameLength(); + + Assertions.assertThrows(FileNameTooLongException.class, () -> { + inTest.newFileChannel(cleartextPath, EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE)); + }); + + verifyNoInteractions(openCryptoFiles); + } + + @Test + @DisplayName("create new succeeds when within limit") + public void testNewFileChannelCreate2() throws IOException { + Mockito.doReturn(10).when(fileSystemProperties).maxCleartextNameLength(); + + FileChannel ch = inTest.newFileChannel(cleartextPath, EnumSet.of(StandardOpenOption.READ)); + + Assertions.assertSame(fileChannel, ch); + verify(readonlyFlag, Mockito.never()).assertWritable(); + } + + } + @Test @DisplayName("newFileChannel read-only") public void testNewFileChannelReadOnly() throws IOException { From f5e9748fd02e8b8e979bd5c12989a2638cb68571 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Thu, 22 Apr 2021 22:11:01 +0200 Subject: [PATCH 51/70] support only one single key loader that must be provided by the user --- pom.xml | 2 +- .../cryptofs/CryptoFileSystemProperties.java | 57 ++++--------------- .../cryptofs/CryptoFileSystemProvider.java | 2 +- .../cryptofs/CryptoFileSystems.java | 2 +- ...toFileChannelWriteReadIntegrationTest.java | 6 +- .../CryptoFileSystemPropertiesTest.java | 29 +++++----- ...yptoFileSystemProviderIntegrationTest.java | 23 +++----- .../CryptoFileSystemProviderTest.java | 7 +-- .../cryptofs/CryptoFileSystemUriTest.java | 3 +- .../cryptofs/CryptoFileSystemsTest.java | 2 +- ...ptyCiphertextDirectoryIntegrationTest.java | 3 +- .../cryptofs/ReadmeCodeSamplesTest.java | 6 +- .../RealFileSystemIntegrationTest.java | 4 +- ...iteFileWhileReadonlyChannelIsOpenTest.java | 4 +- .../attr/FileAttributeIntegrationTest.java | 3 +- 15 files changed, 53 insertions(+), 100 deletions(-) diff --git a/pom.xml b/pom.xml index 6f9b3e62..e72520e9 100644 --- a/pom.xml +++ b/pom.xml @@ -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> diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index b917a60f..39d3c04d 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -9,9 +9,7 @@ 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; @@ -19,7 +17,6 @@ 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; @@ -51,9 +48,7 @@ public class CryptoFileSystemProperties extends AbstractMap<String, Object> { * * @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. @@ -102,7 +97,7 @@ 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), // @@ -111,24 +106,8 @@ private CryptoFileSystemProperties(Builder builder) { ); } - 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() { @@ -205,7 +184,7 @@ 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; @@ -215,7 +194,7 @@ 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); @@ -262,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; } @@ -345,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"); diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 0a1330f5..4a401c84 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -142,7 +142,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope } byte[] rawKey = new byte[0]; var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).shorteningThreshold(Constants.DEFAULT_SHORTENING_THRESHOLD).build(); - try (Masterkey key = properties.keyLoader(keyId.getScheme()).loadKey(keyId); + try (Masterkey key = properties.keyLoader().loadKey(keyId); Cryptor cryptor = config.getCipherCombo().getCryptorProvider(strongSecureRandom()).withKey(key)) { rawKey = key.getEncoded(); // save vault config: diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java index 438b9577..16c5e2ca 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java @@ -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()); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java index 7bca22ee..3b12c4c0 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java @@ -66,9 +66,8 @@ public class Windows { @BeforeAll public void setupClass(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.supportsScheme(Mockito.any())).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - CryptoFileSystemProperties properties = cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProperties properties = cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); CryptoFileSystemProvider.initialize(tmpDir, properties, URI.create("test:key")); fileSystem = CryptoFileSystemProvider.newFileSystem(tmpDir, properties); } @@ -142,9 +141,8 @@ public void beforeAll() throws IOException, MasterkeyLoadingFailedException { Path vaultPath = inMemoryFs.getPath("vault"); Files.createDirectories(vaultPath); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + var properties = cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); CryptoFileSystemProvider.initialize(vaultPath, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(vaultPath, properties); file = fileSystem.getPath("/test.txt"); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java index 78fc7cac..185dbc08 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java @@ -15,7 +15,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Objects; -import java.util.Set; import static org.cryptomator.cryptofs.CryptoFileSystemProperties.*; import static org.hamcrest.CoreMatchers.sameInstance; @@ -37,7 +36,7 @@ public void testSetNoPassphrase() { public void testSetMasterkeyFilenameAndReadonlyFlag() { String masterkeyFilename = "aMasterkeyFilename"; CryptoFileSystemProperties inTest = cryptoFileSystemProperties() // - .withKeyLoaders(keyLoader) // + .withKeyLoader(keyLoader) // .withMasterkeyFilename(masterkeyFilename) // .withFlags(FileSystemFlags.READONLY) .build(); @@ -46,7 +45,7 @@ public void testSetMasterkeyFilenameAndReadonlyFlag() { MatcherAssert.assertThat(inTest.readonly(), is(true)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // + anEntry(PROPERTY_KEYLOADER, keyLoader), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), // @@ -58,7 +57,7 @@ public void testSetMasterkeyFilenameAndReadonlyFlag() { public void testFromMap() { Map<String, Object> map = new HashMap<>(); String masterkeyFilename = "aMasterkeyFilename"; - map.put(PROPERTY_KEYLOADERS, Set.of(keyLoader)); + map.put(PROPERTY_KEYLOADER, keyLoader); map.put(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename); map.put(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, 255); map.put(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)); @@ -69,7 +68,7 @@ public void testFromMap() { MatcherAssert.assertThat(inTest.maxCleartextNameLength(), is(255)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // + anEntry(PROPERTY_KEYLOADER, keyLoader), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, 255), // @@ -81,7 +80,7 @@ public void testFromMap() { public void testWrapMapWithTrueReadonly() { Map<String, Object> map = new HashMap<>(); String masterkeyFilename = "aMasterkeyFilename"; - map.put(PROPERTY_KEYLOADERS, Set.of(keyLoader)); + map.put(PROPERTY_KEYLOADER, keyLoader); map.put(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename); map.put(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)); CryptoFileSystemProperties inTest = CryptoFileSystemProperties.wrap(map); @@ -90,7 +89,7 @@ public void testWrapMapWithTrueReadonly() { MatcherAssert.assertThat(inTest.readonly(), is(true)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // + anEntry(PROPERTY_KEYLOADER, keyLoader), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), // @@ -102,7 +101,7 @@ public void testWrapMapWithTrueReadonly() { public void testWrapMapWithFalseReadonly() { Map<String, Object> map = new HashMap<>(); String masterkeyFilename = "aMasterkeyFilename"; - map.put(PROPERTY_KEYLOADERS, Set.of(keyLoader)); + map.put(PROPERTY_KEYLOADER, keyLoader); map.put(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename); map.put(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)); CryptoFileSystemProperties inTest = CryptoFileSystemProperties.wrap(map); @@ -111,7 +110,7 @@ public void testWrapMapWithFalseReadonly() { MatcherAssert.assertThat(inTest.readonly(), is(false)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // + anEntry(PROPERTY_KEYLOADER, keyLoader), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), // anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), // @@ -155,14 +154,14 @@ public void testWrapMapWithInvalidPassphrase() { @Test public void testWrapMapWithoutReadonly() { Map<String, Object> map = new HashMap<>(); - map.put(PROPERTY_KEYLOADERS, Set.of(keyLoader)); + map.put(PROPERTY_KEYLOADER, keyLoader); CryptoFileSystemProperties inTest = CryptoFileSystemProperties.wrap(map); MatcherAssert.assertThat(inTest.masterkeyFilename(), is(DEFAULT_MASTERKEY_FILENAME)); MatcherAssert.assertThat(inTest.readonly(), is(false)); MatcherAssert.assertThat(inTest.entrySet(), containsInAnyOrder( // - anEntry(PROPERTY_KEYLOADERS, Set.of(keyLoader)), // + anEntry(PROPERTY_KEYLOADER, keyLoader), // anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), // anEntry(PROPERTY_MASTERKEY_FILENAME, DEFAULT_MASTERKEY_FILENAME), // anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), // @@ -181,7 +180,7 @@ public void testWrapMapWithoutPassphrase() { @Test public void testWrapCryptoFileSystemProperties() { - CryptoFileSystemProperties inTest = cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProperties inTest = cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); MatcherAssert.assertThat(CryptoFileSystemProperties.wrap(inTest), is(sameInstance(inTest))); } @@ -190,7 +189,7 @@ public void testWrapCryptoFileSystemProperties() { public void testMapIsImmutable() { Assertions.assertThrows(UnsupportedOperationException.class, () -> { cryptoFileSystemProperties() // - .withKeyLoaders(keyLoader) // + .withKeyLoader(keyLoader) // .build() // .put("test", "test"); }); @@ -200,7 +199,7 @@ public void testMapIsImmutable() { public void testEntrySetIsImmutable() { Assertions.assertThrows(UnsupportedOperationException.class, () -> { cryptoFileSystemProperties() // - .withKeyLoaders(keyLoader) // + .withKeyLoader(keyLoader) // .build() // .entrySet() // .add(null); @@ -211,7 +210,7 @@ public void testEntrySetIsImmutable() { public void testEntryIsImmutable() { Assertions.assertThrows(UnsupportedOperationException.class, () -> { cryptoFileSystemProperties() // - .withKeyLoaders(keyLoader) // + .withKeyLoader(keyLoader) // .build() // .entrySet() // .iterator().next() // diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index 65b1b845..5ae7a36a 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -84,12 +84,11 @@ class WithLimitedPaths { @BeforeAll public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { - Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // - .withKeyLoaders(keyLoader) // + .withKeyLoader(keyLoader) // .withMaxCleartextNameLength(50) .build(); CryptoFileSystemProvider.initialize(tmpDir, properties, URI.create("test:key")); @@ -192,8 +191,6 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { Arrays.fill(key2, (byte) 0x77); keyLoader1 = Mockito.mock(MasterkeyLoader.class); keyLoader2 = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader1.supportsScheme("test")).thenReturn(true); - Mockito.when(keyLoader2.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader1.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(key1)); Mockito.when(keyLoader2.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(key2)); pathToVault1 = tmpFs.getPath("/vaultDir1"); @@ -215,12 +212,12 @@ public void teardown() throws IOException { public void initializeVaults() { Assertions.assertAll( () -> { - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader1).build(); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader1).build(); CryptoFileSystemProvider.initialize(pathToVault1, properties, URI.create("test:key")); Assertions.assertTrue(Files.isDirectory(pathToVault1.resolve("d"))); Assertions.assertTrue(Files.isRegularFile(vaultConfigFile1)); }, () -> { - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader2).build(); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader2).build(); CryptoFileSystemProvider.initialize(pathToVault2, properties, URI.create("test:key")); Assertions.assertTrue(Files.isDirectory(pathToVault2.resolve("d"))); Assertions.assertTrue(Files.isRegularFile(vaultConfigFile2)); @@ -239,7 +236,7 @@ public void testGetFsWithWrongCredentials() { CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // - .withKeyLoaders(keyLoader2) // + .withKeyLoader(keyLoader2) // .build(); Assertions.assertThrows(VaultKeyInvalidException.class, () -> { FileSystems.newFileSystem(fsUri, properties); @@ -250,7 +247,7 @@ public void testGetFsWithWrongCredentials() { CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // - .withKeyLoaders(keyLoader1) // + .withKeyLoader(keyLoader1) // .build(); Assertions.assertThrows(VaultKeyInvalidException.class, () -> { FileSystems.newFileSystem(fsUri, properties); @@ -267,7 +264,7 @@ public void testGetFsViaNioApi() { Assertions.assertAll( () -> { URI fsUri = CryptoFileSystemUri.create(pathToVault1); - fs1 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoaders(keyLoader1).build()); + fs1 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader1).build()); Assertions.assertTrue(fs1 instanceof CryptoFileSystemImpl); FileSystem sameFs = FileSystems.getFileSystem(fsUri); @@ -275,7 +272,7 @@ public void testGetFsViaNioApi() { }, () -> { URI fsUri = CryptoFileSystemUri.create(pathToVault2); - fs2 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoaders(keyLoader2).build()); + fs2 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader2).build()); Assertions.assertTrue(fs2 instanceof CryptoFileSystemImpl); FileSystem sameFs = FileSystems.getFileSystem(fsUri); @@ -535,9 +532,8 @@ public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFail Path pathToVault = tmpDir.resolve("vaultDir1"); Files.createDirectories(pathToVault); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fs = CryptoFileSystemProvider.newFileSystem(pathToVault, properties); } @@ -628,9 +624,8 @@ public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFail Path pathToVault = tmpDir.resolve("vaultDir1"); Files.createDirectories(pathToVault); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fs = CryptoFileSystemProvider.newFileSystem(pathToVault, properties); } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index ed488d7b..d786d9d0 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -114,7 +114,6 @@ private static final Stream<InvocationWhichShouldFail> shouldFailWithRelativePat @BeforeEach @SuppressWarnings("deprecation") public void setup() throws MasterkeyLoadingFailedException { - Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); when(keyLoader.loadKey(Mockito.any())).thenReturn(new Masterkey(new byte[64])); CryptoFileSystemProviderComponent component = mock(CryptoFileSystemProviderComponent.class); @@ -168,7 +167,7 @@ public void testGetSchemeReturnsCryptomatorScheme() { public void testInitializeFailWithNotDirectoryException() { FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); Path pathToVault = fs.getPath("/vaultDir"); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + var properties = cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); Assertions.assertThrows(NotDirectoryException.class, () -> { CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); @@ -181,7 +180,7 @@ public void testInitialize() throws IOException, MasterkeyLoadingFailedException Path pathToVault = fs.getPath("/vaultDir"); Path vaultConfigFile = pathToVault.resolve("vault.cryptomator"); Path dataDir = pathToVault.resolve("d"); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + var properties = cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); Files.createDirectory(pathToVault); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); @@ -204,7 +203,7 @@ public void testNewFileSystem() throws IOException, MasterkeyLoadingFailedExcept URI uri = CryptoFileSystemUri.create(pathToVault); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // - .withKeyLoaders(keyLoader) // + .withKeyLoader(keyLoader) // .build(); inTest.newFileSystem(uri, properties); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java index fe60e12d..d6d186b5 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java @@ -75,9 +75,8 @@ public void testCreateWithPathToVaultFromNonDefaultProvider() throws IOException Path tempDir = createTempDirectory("CryptoFileSystemUrisTest").toAbsolutePath(); try { MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); CryptoFileSystemProvider.initialize(tempDir, properties, URI.create("test:key")); FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(tempDir, properties); Path absolutePathToVault = fileSystem.getPath("a").toAbsolutePath(); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java index 43456ad2..fd2e6bca 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java @@ -71,7 +71,7 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { when(pathToVault.normalize()).thenReturn(normalizedPathToVault); when(normalizedPathToVault.resolve("vault.cryptomator")).thenReturn(configFilePath); when(properties.vaultConfigFilename()).thenReturn("vault.cryptomator"); - when(properties.keyLoader(Mockito.any())).thenReturn(keyLoader); + when(properties.keyLoader()).thenReturn(keyLoader); filesClass.when(() -> Files.readString(configFilePath, StandardCharsets.US_ASCII)).thenReturn("jwt-vault-config"); vaultConficClass.when(() -> VaultConfig.decode("jwt-vault-config")).thenReturn(configLoader); when(VaultConfig.decode("jwt-vault-config")).thenReturn(configLoader); diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java index fe566bac..21754ca2 100644 --- a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java @@ -46,9 +46,8 @@ public static void setupClass(@TempDir Path tmpDir) throws IOException, Masterke pathToVault = tmpDir.resolve("vault"); Files.createDirectory(pathToVault); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); } diff --git a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java index 30c1a857..fc0037e5 100644 --- a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java @@ -31,9 +31,8 @@ public class ReadmeCodeSamplesTest { @Test public void testReadmeCodeSampleUsingFileSystemConstructionMethodA(@TempDir Path storageLocation) throws IOException, MasterkeyLoadingFailedException { MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); CryptoFileSystemProvider.initialize(storageLocation, properties, URI.create("test:key")); FileSystem fileSystem = CryptoFileSystemProvider.newFileSystem(storageLocation, properties); @@ -44,9 +43,8 @@ public void testReadmeCodeSampleUsingFileSystemConstructionMethodA(@TempDir Path public void testReadmeCodeSampleUsingFileSystemConstructionMethodB(@TempDir Path storageLocation) throws IOException, MasterkeyLoadingFailedException { URI uri = CryptoFileSystemUri.create(storageLocation); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); CryptoFileSystemProvider.initialize(storageLocation, properties, URI.create("test:key")); FileSystem fileSystem = FileSystems.newFileSystem(uri, properties); diff --git a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java index d0c65013..6706ddf3 100644 --- a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java @@ -25,6 +25,7 @@ import java.nio.file.Path; import java.nio.file.attribute.UserPrincipal; +import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; import static org.cryptomator.cryptofs.CryptoFileSystemUri.create; public class RealFileSystemIntegrationTest { @@ -37,9 +38,8 @@ public static void setupClass(@TempDir Path tmpDir) throws IOException, Masterke pathToVault = tmpDir.resolve("vault"); Files.createDirectory(pathToVault); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProperties properties = cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); } diff --git a/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java b/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java index 203f600f..2e39d116 100644 --- a/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java +++ b/src/test/java/org/cryptomator/cryptofs/WriteFileWhileReadonlyChannelIsOpenTest.java @@ -19,6 +19,7 @@ import java.nio.file.StandardOpenOption; import static java.nio.file.StandardOpenOption.READ; +import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; import static org.cryptomator.cryptofs.CryptoFileSystemUri.create; /** @@ -36,9 +37,8 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { Path pathToVault = inMemoryFs.getRootDirectories().iterator().next().resolve("vault"); Files.createDirectory(pathToVault); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProperties properties = cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); root = fileSystem.getPath("/"); diff --git a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java index 11a6ee09..ffb26afa 100644 --- a/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/attr/FileAttributeIntegrationTest.java @@ -62,9 +62,8 @@ public static void setupClass() throws IOException, MasterkeyLoadingFailedExcept pathToVault = inMemoryFs.getRootDirectories().iterator().next().resolve("vault"); Files.createDirectory(pathToVault); MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.supportsScheme("test")).thenReturn(true); Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoaders(keyLoader).build(); + CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), properties); } From 4958fcd64425a1cdb0bce22964955a829ce66488 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Tue, 4 May 2021 11:53:24 +0200 Subject: [PATCH 52/70] move versions of non-default build plugins to properties --- pom.xml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index e72520e9..2fc86268 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,11 @@ <junit.jupiter.version>5.7.0</junit.jupiter.version> <mockito.version>3.7.7</mockito.version> <hamcrest.version>2.2</hamcrest.version> + + <!-- build plugin dependencies --> + <dependency-check.version>6.1.6</dependency-check.version> + <jacoco.version>0.8.6</jacoco.version> + <nexus-staging.version>1.6.8</nexus-staging.version> </properties> <licenses> @@ -219,7 +224,7 @@ <plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> - <version>6.1.0</version> + <version>${dependency-check.version}</version> <configuration> <cveValidForHours>24</cveValidForHours> <failBuildOnCVSS>0</failBuildOnCVSS> @@ -246,7 +251,7 @@ <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> - <version>0.8.6</version> + <version>${jacoco.version}</version> <executions> <execution> <id>prepare-agent</id> @@ -307,7 +312,7 @@ <plugin> <groupId>org.sonatype.plugins</groupId> <artifactId>nexus-staging-maven-plugin</artifactId> - <version>1.6.8</version> + <version>${nexus-staging.version}</version> <extensions>true</extensions> <configuration> <serverId>ossrh</serverId> From 450624c371faf0b6583a19ef8b022796a4008627 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Tue, 4 May 2021 13:18:10 +0200 Subject: [PATCH 53/70] move used JDK to more prominent place --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2fc86268..54bf57bd 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <maven.compiler.release>16</maven.compiler.release> <!-- dependencies --> <cryptolib.version>2.0.0-beta7</cryptolib.version> @@ -126,7 +127,6 @@ <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> - <release>16</release> <showWarnings>true</showWarnings> <annotationProcessorPaths> <path> From fe410fdef30bf59bc007fb56828501a6439423fc Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Tue, 4 May 2021 13:18:23 +0200 Subject: [PATCH 54/70] bump dependencies --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 54bf57bd..a3edce77 100644 --- a/pom.xml +++ b/pom.xml @@ -19,14 +19,14 @@ <!-- dependencies --> <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> + <jwt.version>3.15.0</jwt.version> + <dagger.version>2.35.1</dagger.version> + <guava.version>30.1.1-jre</guava.version> <slf4j.version>1.7.30</slf4j.version> <!-- test dependencies --> - <junit.jupiter.version>5.7.0</junit.jupiter.version> - <mockito.version>3.7.7</mockito.version> + <junit.jupiter.version>5.7.1</junit.jupiter.version> + <mockito.version>3.9.0</mockito.version> <hamcrest.version>2.2</hamcrest.version> <!-- build plugin dependencies --> From a967c22809963e040ef217d9116b359e31d2c8d9 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Wed, 5 May 2021 12:10:38 +0200 Subject: [PATCH 55/70] closes #103 --- .../cryptofs/CryptoFileSystemProvider.java | 31 +++++++-- .../cryptomator/cryptofs/DirStructure.java | 64 +++++++++++++++++++ 2 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/cryptomator/cryptofs/DirStructure.java diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 4a401c84..6e74293b 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -161,18 +161,35 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope /** * Checks if the folder represented by the given path exists and contains a valid vault structure. + * <p> + * See {@link DirStructure#VAULT} for the criterias of being a valid vault. * - * @param pathToVault A directory path + * @param pathToAssumedVault A directory path * @param vaultConfigFilename Name of the vault config file - * @param masterkeyFilename Name of the masterkey file + * @param masterkeyFilename Name of the masterkey file * @return <code>true</code> if the directory seems to contain a vault. * @since 2.0.0 */ - public static boolean containsVault(Path pathToVault, String vaultConfigFilename, String masterkeyFilename) { - Path vaultConfigPath = pathToVault.resolve(vaultConfigFilename); - Path masterkeyPath = pathToVault.resolve(masterkeyFilename); - Path dataDirPath = pathToVault.resolve(Constants.DATA_DIR_NAME); - return (Files.isReadable(vaultConfigPath) || Files.isReadable(masterkeyPath)) && Files.isDirectory(dataDirPath); + public static boolean containsVault(Path pathToAssumedVault, String vaultConfigFilename, String masterkeyFilename) { + try { + return checkDirStructure(pathToAssumedVault, vaultConfigFilename, masterkeyFilename) == DirStructure.VAULT; + } catch (IOException e) { + return false; + } + } + + /** + * Convenience method for {@link DirStructure#checkDirStructure(Path, String, String)}. + * + * @param pathToAssumedVault + * @param vaultConfigFilename + * @param masterkeyFilename + * @return a {@link DirStructure} object + * @throws IOException + * @since 2.0.0 + */ + public static DirStructure checkDirStructure(Path pathToAssumedVault, String vaultConfigFilename, String masterkeyFilename) throws IOException { + return DirStructure.checkDirStructure(pathToAssumedVault, vaultConfigFilename, masterkeyFilename); } /** diff --git a/src/main/java/org/cryptomator/cryptofs/DirStructure.java b/src/main/java/org/cryptomator/cryptofs/DirStructure.java new file mode 100644 index 00000000..037d0733 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/DirStructure.java @@ -0,0 +1,64 @@ +package org.cryptomator.cryptofs; + +import org.cryptomator.cryptofs.common.Constants; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Enumeration of the vault directory structure resemblances. + * <p> + * A valid vault must contain a `d` directory. + * If the vault version is 8, it must also contains a vault config file. + * If the vault version is smaller than 8, it must also contain a masterkey file. + * <p> + * In the latter case, to distinct between a damaged vault 8 directory and a legacy vault the masterkey file must be read. + * For efficiency reasons, this class only checks for existence/readability of the above elements. + * Hence, if the result of {@link #checkDirStructure(Path, String, String)} is {@link #MAYBE_LEGACY}, one needs to parse + * the masterkey file and read out the vault version to determine this case. + * + * @since 2.0.0 + */ +public enum DirStructure { + + /** + * Dir contains a <code>d</code> dir as well as a vault config file. + */ + VAULT, + + /** + * Dir contains a <code>d</code> dir and a masterkey file, but misses a vault config file. + * Either needs migration to a newer format or damaged. + */ + MAYBE_LEGACY, + + /** + * Dir does not qualify as vault. + */ + UNRELATED; + + + /** + * Analyzes the structure of the given directory under certain vault existence criteria. + * + * @param pathToVault A directory path + * @param vaultConfigFilename Name of the vault config file + * @param masterkeyFilename Name of the masterkey file + * @return enum indicating what this directory might be + * @throws IOException if the directory itself or certain components cannot be accessed/read. + */ + public static DirStructure checkDirStructure(Path pathToVault, String vaultConfigFilename, String masterkeyFilename) throws IOException { + Path vaultConfigPath = pathToVault.resolve(vaultConfigFilename); + Path masterkeyPath = pathToVault.resolve(masterkeyFilename); + Path dataDirPath = pathToVault.resolve(Constants.DATA_DIR_NAME); + if (Files.isDirectory(dataDirPath)) { + if (Files.isReadable(vaultConfigPath)) { + return VAULT; + } else if (Files.isReadable(masterkeyPath)) { + return MAYBE_LEGACY; + } + } + return UNRELATED; + } +} From 2cae7a59dc6131f0f063de1a6df43f2484a52524 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Wed, 5 May 2021 13:35:48 +0200 Subject: [PATCH 56/70] change behavior and add unit tests --- .../cryptofs/CryptoFileSystemProvider.java | 4 +- .../cryptomator/cryptofs/DirStructure.java | 7 +- .../cryptofs/DirStructureTest.java | 75 +++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 src/test/java/org/cryptomator/cryptofs/DirStructureTest.java diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 6e74293b..33e64236 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -162,7 +162,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope /** * Checks if the folder represented by the given path exists and contains a valid vault structure. * <p> - * See {@link DirStructure#VAULT} for the criterias of being a valid vault. + * See {@link DirStructure#VAULT} for the criteria of being a valid vault. * * @param pathToAssumedVault A directory path * @param vaultConfigFilename Name of the vault config file @@ -179,7 +179,7 @@ public static boolean containsVault(Path pathToAssumedVault, String vaultConfigF } /** - * Convenience method for {@link DirStructure#checkDirStructure(Path, String, String)}. + * Delegate to {@link DirStructure#checkDirStructure(Path, String, String)}. * * @param pathToAssumedVault * @param vaultConfigFilename diff --git a/src/main/java/org/cryptomator/cryptofs/DirStructure.java b/src/main/java/org/cryptomator/cryptofs/DirStructure.java index 037d0733..2e33d1c3 100644 --- a/src/main/java/org/cryptomator/cryptofs/DirStructure.java +++ b/src/main/java/org/cryptomator/cryptofs/DirStructure.java @@ -4,7 +4,9 @@ import java.io.IOException; import java.nio.file.Files; +import java.nio.file.NotDirectoryException; import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; /** * Enumeration of the vault directory structure resemblances. @@ -46,9 +48,12 @@ public enum DirStructure { * @param vaultConfigFilename Name of the vault config file * @param masterkeyFilename Name of the masterkey file * @return enum indicating what this directory might be - * @throws IOException if the directory itself or certain components cannot be accessed/read. + * @throws IOException if the provided path is not a directory, does not exist or cannot be read */ public static DirStructure checkDirStructure(Path pathToVault, String vaultConfigFilename, String masterkeyFilename) throws IOException { + if(! Files.readAttributes(pathToVault, BasicFileAttributes.class).isDirectory()) { + throw new NotDirectoryException(pathToVault.toString()); + } Path vaultConfigPath = pathToVault.resolve(vaultConfigFilename); Path masterkeyPath = pathToVault.resolve(masterkeyFilename); Path dataDirPath = pathToVault.resolve(Constants.DATA_DIR_NAME); diff --git a/src/test/java/org/cryptomator/cryptofs/DirStructureTest.java b/src/test/java/org/cryptomator/cryptofs/DirStructureTest.java new file mode 100644 index 00000000..f35818bc --- /dev/null +++ b/src/test/java/org/cryptomator/cryptofs/DirStructureTest.java @@ -0,0 +1,75 @@ +package org.cryptomator.cryptofs; + +import org.cryptomator.cryptofs.common.Constants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +public class DirStructureTest { + + private static final String KEY = "key"; + private static final String CONFIG = "config"; + + @TempDir + Path vaultPath; + + @Test + public void testNonExistingVaultPathThrowsIOException() { + Path vaultPath = Path.of("this/certainly/does/not/exist"); + Assumptions.assumeTrue(Files.notExists(vaultPath)); + + Assertions.assertThrows(IOException.class, () -> DirStructure.checkDirStructure(vaultPath, CONFIG, KEY)); + } + + @Test + public void testNonDirectoryVaultPathThrowsIOException() throws IOException { + Path tmp = vaultPath.resolve("this"); + Files.createFile(tmp); + Assumptions.assumeTrue(Files.exists(tmp)); + + Assertions.assertThrows(IOException.class, () -> DirStructure.checkDirStructure(tmp, CONFIG, KEY)); + } + + @ParameterizedTest(name = "Testing all combinations of data dir, config and masterkey file existence.") + @MethodSource("provideAllCases") + public void testAllCombosOfDataAndConfigAndKey(boolean createDataDir, boolean createConfig, boolean createKey, DirStructure expectedResult) throws IOException { + Path keyPath = vaultPath.resolve(KEY); + Path configPath = vaultPath.resolve(CONFIG); + Path dataDir = vaultPath.resolve(Constants.DATA_DIR_NAME); + + if (createDataDir) { + Files.createDirectory(dataDir); + } + if (createConfig) { + Files.createFile(configPath); + } + if (createKey) { + Files.createFile(keyPath); + } + + Assertions.assertEquals(expectedResult, DirStructure.checkDirStructure(vaultPath, CONFIG, KEY)); + } + + private static Stream<Arguments> provideAllCases() { + return Stream.of( + Arguments.of(true, true, true, DirStructure.VAULT), + Arguments.of(true, true, false, DirStructure.VAULT), + Arguments.of(true, false, true, DirStructure.MAYBE_LEGACY), + Arguments.of(true, false, false, DirStructure.UNRELATED), + Arguments.of(false, false, false, DirStructure.UNRELATED), + Arguments.of(false, false, true, DirStructure.UNRELATED), + Arguments.of(false, true, false, DirStructure.UNRELATED), + Arguments.of(false, true, true, DirStructure.UNRELATED) + ); + } + +} From 27b45aad775266c623ba833f12755dab760936ef Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Wed, 5 May 2021 13:36:21 +0200 Subject: [PATCH 57/70] change unit test for CryptoFileSystemProvider --- .../cryptomator/cryptofs/CryptoFileSystemProviderTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index d786d9d0..6105c309 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -228,7 +228,7 @@ public void testContainsVaultReturnsTrueIfDirectoryContainsVaultConfigFileAndDat } @Test - public void testContainsVaultReturnsTrueIfDirectoryContainsMasterkeyFileAndDataDir() throws IOException { + public void testContainsVaultReturnsFalseIfDirectoryContainsMasterkeyFileAndDataDir() throws IOException { FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); String vaultConfigFilename = "vaultconfig.foo.baz"; @@ -240,7 +240,7 @@ public void testContainsVaultReturnsTrueIfDirectoryContainsMasterkeyFileAndDataD Files.createDirectories(dataDir); Files.write(masterkeyFile, new byte[0]); - Assertions.assertTrue(containsVault(pathToVault, vaultConfigFilename, masterkeyFilename)); + Assertions.assertFalse(containsVault(pathToVault, vaultConfigFilename, masterkeyFilename)); } @Test From 1155a7be65ccfcb276c235a3ff77e1e6ac603903 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Wed, 5 May 2021 13:44:45 +0200 Subject: [PATCH 58/70] renamed method --- .../org/cryptomator/cryptofs/CryptoFileSystemProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 33e64236..36ee1bf8 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -172,7 +172,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope */ public static boolean containsVault(Path pathToAssumedVault, String vaultConfigFilename, String masterkeyFilename) { try { - return checkDirStructure(pathToAssumedVault, vaultConfigFilename, masterkeyFilename) == DirStructure.VAULT; + return checkDirStructureForVault(pathToAssumedVault, vaultConfigFilename, masterkeyFilename) == DirStructure.VAULT; } catch (IOException e) { return false; } @@ -188,7 +188,7 @@ public static boolean containsVault(Path pathToAssumedVault, String vaultConfigF * @throws IOException * @since 2.0.0 */ - public static DirStructure checkDirStructure(Path pathToAssumedVault, String vaultConfigFilename, String masterkeyFilename) throws IOException { + public static DirStructure checkDirStructureForVault(Path pathToAssumedVault, String vaultConfigFilename, String masterkeyFilename) throws IOException { return DirStructure.checkDirStructure(pathToAssumedVault, vaultConfigFilename, masterkeyFilename); } From 2938d45429cc5e637d17036d5f68158f0aa70b5d Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@zoho.eu> Date: Wed, 5 May 2021 15:32:36 +0200 Subject: [PATCH 59/70] Apply suggestions from code review Improve doc [ci skip] Co-authored-by: Sebastian Stenzel <overheadhunter@users.noreply.github.com> --- src/main/java/org/cryptomator/cryptofs/DirStructure.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/DirStructure.java b/src/main/java/org/cryptomator/cryptofs/DirStructure.java index 2e33d1c3..daff7fb4 100644 --- a/src/main/java/org/cryptomator/cryptofs/DirStructure.java +++ b/src/main/java/org/cryptomator/cryptofs/DirStructure.java @@ -12,10 +12,10 @@ * Enumeration of the vault directory structure resemblances. * <p> * A valid vault must contain a `d` directory. - * If the vault version is 8, it must also contains a vault config file. - * If the vault version is smaller than 8, it must also contain a masterkey file. + * Beginning with vault format 8, it must also contain a vault config file. + * If the vault format is lower than 8, it must instead contain a masterkey file. * <p> - * In the latter case, to distinct between a damaged vault 8 directory and a legacy vault the masterkey file must be read. + * In the latter case, to distinguish between a damaged vault 8 directory and a legacy vault the masterkey file must be read. * For efficiency reasons, this class only checks for existence/readability of the above elements. * Hence, if the result of {@link #checkDirStructure(Path, String, String)} is {@link #MAYBE_LEGACY}, one needs to parse * the masterkey file and read out the vault version to determine this case. From c92a45ceeb86c5d934e30a9f590abbc504b44267 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Wed, 5 May 2021 15:44:13 +0200 Subject: [PATCH 60/70] remove CryptoFileSystemProvider.containsVault() method --- .../cryptofs/CryptoFileSystemProvider.java | 21 +----- ...yptoFileSystemProviderIntegrationTest.java | 6 +- .../CryptoFileSystemProviderTest.java | 64 ------------------- 3 files changed, 4 insertions(+), 87 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 36ee1bf8..19d5be63 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -156,26 +156,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope } finally { Arrays.fill(rawKey, (byte) 0x00); } - assert containsVault(pathToVault, properties.vaultConfigFilename(), properties.masterkeyFilename()); - } - - /** - * Checks if the folder represented by the given path exists and contains a valid vault structure. - * <p> - * See {@link DirStructure#VAULT} for the criteria of being a valid vault. - * - * @param pathToAssumedVault A directory path - * @param vaultConfigFilename Name of the vault config file - * @param masterkeyFilename Name of the masterkey file - * @return <code>true</code> if the directory seems to contain a vault. - * @since 2.0.0 - */ - public static boolean containsVault(Path pathToAssumedVault, String vaultConfigFilename, String masterkeyFilename) { - try { - return checkDirStructureForVault(pathToAssumedVault, vaultConfigFilename, masterkeyFilename) == DirStructure.VAULT; - } catch (IOException e) { - return false; - } + assert checkDirStructureForVault(pathToVault, properties.vaultConfigFilename(), properties.masterkeyFilename()) == DirStructure.VAULT; } /** diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index 5ae7a36a..6ae7b903 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -227,9 +227,9 @@ public void initializeVaults() { @Test @Order(2) @DisplayName("get filesystem with incorrect credentials") - public void testGetFsWithWrongCredentials() { - Assumptions.assumeTrue(CryptoFileSystemProvider.containsVault(pathToVault1, "vault.cryptomator", "masterkey.cryptomator")); - Assumptions.assumeTrue(CryptoFileSystemProvider.containsVault(pathToVault2, "vault.cryptomator", "masterkey.cryptomator")); + public void testGetFsWithWrongCredentials() throws IOException { + Assumptions.assumeTrue(CryptoFileSystemProvider.checkDirStructureForVault(pathToVault1, "vault.cryptomator", "masterkey.cryptomator") == DirStructure.VAULT); + Assumptions.assumeTrue(CryptoFileSystemProvider.checkDirStructureForVault(pathToVault2, "vault.cryptomator", "masterkey.cryptomator") == DirStructure.VAULT); Assertions.assertAll( () -> { URI fsUri = CryptoFileSystemUri.create(pathToVault1); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index 6105c309..64836e78 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -46,7 +46,6 @@ import static java.nio.file.StandardOpenOption.APPEND; import static java.util.Arrays.asList; import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; -import static org.cryptomator.cryptofs.CryptoFileSystemProvider.containsVault; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; import static org.mockito.Mockito.mock; @@ -211,69 +210,6 @@ public void testNewFileSystem() throws IOException, MasterkeyLoadingFailedExcept Mockito.verify(fileSystems).create(Mockito.same(inTest), Mockito.eq(pathToVault.toAbsolutePath()), Mockito.eq(properties)); } - @Test - public void testContainsVaultReturnsTrueIfDirectoryContainsVaultConfigFileAndDataDir() throws IOException { - FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); - - String vaultConfigFilename = "vaultconfig.foo.baz"; - String masterkeyFilename = "masterkey.foo.baz"; - Path pathToVault = fs.getPath("/vaultDir"); - - Path vaultConfigFile = pathToVault.resolve(vaultConfigFilename); - Path dataDir = pathToVault.resolve("d"); - Files.createDirectories(dataDir); - Files.write(vaultConfigFile, new byte[0]); - - Assertions.assertTrue(containsVault(pathToVault, vaultConfigFilename, masterkeyFilename)); - } - - @Test - public void testContainsVaultReturnsFalseIfDirectoryContainsMasterkeyFileAndDataDir() throws IOException { - FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); - - String vaultConfigFilename = "vaultconfig.foo.baz"; - String masterkeyFilename = "masterkey.foo.baz"; - Path pathToVault = fs.getPath("/vaultDir"); - - Path masterkeyFile = pathToVault.resolve(masterkeyFilename); - Path dataDir = pathToVault.resolve("d"); - Files.createDirectories(dataDir); - Files.write(masterkeyFile, new byte[0]); - - Assertions.assertFalse(containsVault(pathToVault, vaultConfigFilename, masterkeyFilename)); - } - - @Test - public void testContainsVaultReturnsFalseIfDirectoryContainsOnlyDataDir() throws IOException { - FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); - - String vaultConfigFilename = "vaultconfig.foo.baz"; - String masterkeyFilename = "masterkey.foo.baz"; - Path pathToVault = fs.getPath("/vaultDir"); - - Path dataDir = pathToVault.resolve("d"); - Files.createDirectories(dataDir); - - Assertions.assertFalse(containsVault(pathToVault, vaultConfigFilename, masterkeyFilename)); - } - - @Test - public void testContainsVaultReturnsFalseIfDirectoryContainsNoDataDir() throws IOException { - FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); - - String vaultConfigFilename = "vaultconfig.foo.baz"; - String masterkeyFilename = "masterkey.foo.baz"; - Path pathToVault = fs.getPath("/vaultDir"); - - Path vaultConfigFile = pathToVault.resolve(vaultConfigFilename); - Path masterkeyFile = pathToVault.resolve(masterkeyFilename); - Files.createDirectories(pathToVault); - Files.write(vaultConfigFile, new byte[0]); - Files.write(masterkeyFile, new byte[0]); - - Assertions.assertFalse(containsVault(pathToVault, vaultConfigFilename, masterkeyFilename)); - } - @Test public void testGetFileSystemInvokesFileSystemsGetWithPathToVaultFromUri() { Path pathToVault = get("a").toAbsolutePath(); From c7b713b639c67a78d7b735cbeee9b2c1da3c0542 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Thu, 6 May 2021 15:54:13 +0200 Subject: [PATCH 61/70] use release candidate of cryptolib --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a3edce77..1cb32b17 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ <maven.compiler.release>16</maven.compiler.release> <!-- dependencies --> - <cryptolib.version>2.0.0-beta7</cryptolib.version> + <cryptolib.version>2.0.0-rc1</cryptolib.version> <jwt.version>3.15.0</jwt.version> <dagger.version>2.35.1</dagger.version> <guava.version>30.1.1-jre</guava.version> From 7b24e848bd0623467138697556f3988834f28e4d Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Fri, 21 May 2021 18:52:38 +0200 Subject: [PATCH 62/70] added module-info --- pom.xml | 15 ++----- src/main/java/module-info.java | 21 +++++++++ .../cryptofs/CryptoFileSystems.java | 1 - .../cryptofs/CryptoFileStoreTest.java | 4 +- ...yptoFileSystemProviderIntegrationTest.java | 12 ++--- .../CryptoFileSystemProviderTest.java | 4 +- .../cryptofs/DirStructureTest.java | 4 +- .../attr/CryptoDosFileAttributesTest.java | 4 +- .../cryptofs/ch/CleartextFileChannelTest.java | 2 +- .../cryptofs/ch/CleartextFileLockTest.java | 41 ++++++----------- .../FileSystemCapabilityCheckerTest.java | 4 +- .../common/MasterkeyBackupHelperTest.java | 4 +- .../dir/BrokenDirectoryFilterTest.java | 2 +- .../cryptofs/dir/C9SInflatorTest.java | 2 +- .../cryptofs/dir/C9rConflictResolverTest.java | 2 +- .../cryptofs/dir/C9rDecryptorTest.java | 2 +- .../cryptofs/fh/FileHeaderHolderTest.java | 6 +-- .../cryptofs/fh/OpenCryptoFileTest.java | 2 +- .../cryptofs/migration/MigratorsTest.java | 45 +++++++++---------- ...mpleMigrationContinuationListenerTest.java | 2 +- .../migration/v7/FilePathMigrationTest.java | 4 +- 21 files changed, 88 insertions(+), 95 deletions(-) create mode 100644 src/main/java/module-info.java diff --git a/pom.xml b/pom.xml index 1cb32b17..713f05a4 100644 --- a/pom.xml +++ b/pom.xml @@ -18,15 +18,15 @@ <maven.compiler.release>16</maven.compiler.release> <!-- dependencies --> - <cryptolib.version>2.0.0-rc1</cryptolib.version> + <cryptolib.version>2.0.0-rc2</cryptolib.version> <jwt.version>3.15.0</jwt.version> <dagger.version>2.35.1</dagger.version> <guava.version>30.1.1-jre</guava.version> <slf4j.version>1.7.30</slf4j.version> <!-- test dependencies --> - <junit.jupiter.version>5.7.1</junit.jupiter.version> - <mockito.version>3.9.0</mockito.version> + <junit.jupiter.version>5.7.2</junit.jupiter.version> + <mockito.version>3.10.0</mockito.version> <hamcrest.version>2.2</hamcrest.version> <!-- build plugin dependencies --> @@ -140,19 +140,12 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> - <version>2.22.2</version> + <version>3.0.0-M5</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.0</version> - <configuration> - <archive> - <manifestEntries> - <Automatic-Module-Name>org.cryptomator.cryptofs</Automatic-Module-Name> - </manifestEntries> - </archive> - </configuration> </plugin> <plugin> <artifactId>maven-source-plugin</artifactId> diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 00000000..7782d4bb --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,21 @@ +import org.cryptomator.cryptofs.CryptoFileSystemProvider; + +import java.nio.file.spi.FileSystemProvider; + +module org.cryptomator.cryptofs { + requires transitive org.cryptomator.cryptolib; + requires com.google.common; + requires org.slf4j; + + /* TODO: filename-based modules: */ + requires java.jwt; + requires dagger; + requires static javax.inject; // probably no longer needed if dagger is an automatic module (but might require --patch-module in case of split packages) + + exports org.cryptomator.cryptofs; + exports org.cryptomator.cryptofs.common; + exports org.cryptomator.cryptofs.migration; + exports org.cryptomator.cryptofs.migration.api; + + provides FileSystemProvider with CryptoFileSystemProvider; +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java index 16c5e2ca..de071d9a 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java @@ -18,7 +18,6 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.security.SecureRandom; -import java.util.Arrays; import java.util.EnumSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileStoreTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileStoreTest.java index a5b5ba7a..bee34ccb 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileStoreTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileStoreTest.java @@ -38,7 +38,7 @@ public class CryptoFileStoreTest { @Nested @DisplayName("with delegate present") - class DelegatingCryptoFileStoreTest { + public class DelegatingCryptoFileStoreTest { private final FileStore delegate = mock(FileStore.class); private CryptoFileStore cryptoFileStore; @@ -128,7 +128,7 @@ public void testGetAttribute() { @Nested @DisplayName("with delegate absent") - class FallbackCryptoFileStoreTest { + public class FallbackCryptoFileStoreTest { private CryptoFileStore cryptoFileStore; diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index 6ae7b903..549edc6f 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -74,7 +74,7 @@ public class CryptoFileSystemProviderIntegrationTest { @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) - class WithLimitedPaths { + public class WithLimitedPaths { private MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); private CryptoFileSystem fs; @@ -170,7 +170,7 @@ public void testCopyExceedingPathLengthLimit(String path) { @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) - class InMemory { + public class InMemory { private FileSystem tmpFs; private MasterkeyLoader keyLoader1; @@ -523,7 +523,7 @@ public void testMoveFileFromOneCryptoFileSystemToAnother() throws IOException { @EnabledOnOs({OS.MAC, OS.LINUX}) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @DisplayName("On POSIX Systems") - class PosixTests { + public class PosixTests { private FileSystem fs; @@ -540,7 +540,7 @@ public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFail @Nested @DisplayName("File Locks") - class FileLockTests { + public class FileLockTests { private Path file = fs.getPath("/lock.txt"); @@ -615,7 +615,7 @@ public void testOverlappingLocks(boolean shared) throws IOException { @EnabledOnOs(OS.WINDOWS) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @DisplayName("On Windows Systems") - class WindowsTests { + public class WindowsTests { private FileSystem fs; @@ -661,7 +661,7 @@ public void testDosFileAttributes() throws IOException { @Nested @DisplayName("read-only file") - class OnReadOnlyFile { + public class OnReadOnlyFile { private Path file = fs.getPath("/readonly.txt"); private DosFileAttributeView attrView; diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index 64836e78..cba944b0 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -70,7 +70,7 @@ public class CryptoFileSystemProviderTest { private CryptoFileSystemProvider inTest; - private static final Stream<InvocationWhichShouldFail> shouldFailWithProviderMismatch() { + public static Stream<InvocationWhichShouldFail> shouldFailWithProviderMismatch() { return Stream.of( // invocation("newAsynchronousFileChannel", (inTest, path) -> inTest.newAsynchronousFileChannel(path, new HashSet<>(), mock(ExecutorService.class))), // invocation("newFileChannel", (inTest, path) -> inTest.newFileChannel(path, new HashSet<>())), // @@ -91,7 +91,7 @@ private static final Stream<InvocationWhichShouldFail> shouldFailWithProviderMis } @SuppressWarnings("unchecked") - private static final Stream<InvocationWhichShouldFail> shouldFailWithRelativePath() { + public static Stream<InvocationWhichShouldFail> shouldFailWithRelativePath() { return Stream.of( // invocation("newAsynchronousFileChannel", (inTest, path) -> inTest.newAsynchronousFileChannel(path, new HashSet<>(), mock(ExecutorService.class))), // invocation("newFileChannel", (inTest, path) -> inTest.newFileChannel(path, new HashSet<>())), // diff --git a/src/test/java/org/cryptomator/cryptofs/DirStructureTest.java b/src/test/java/org/cryptomator/cryptofs/DirStructureTest.java index f35818bc..2cb27ddb 100644 --- a/src/test/java/org/cryptomator/cryptofs/DirStructureTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DirStructureTest.java @@ -20,7 +20,7 @@ public class DirStructureTest { private static final String CONFIG = "config"; @TempDir - Path vaultPath; + public Path vaultPath; @Test public void testNonExistingVaultPathThrowsIOException() { @@ -59,7 +59,7 @@ public void testAllCombosOfDataAndConfigAndKey(boolean createDataDir, boolean cr Assertions.assertEquals(expectedResult, DirStructure.checkDirStructure(vaultPath, CONFIG, KEY)); } - private static Stream<Arguments> provideAllCases() { + public static Stream<Arguments> provideAllCases() { return Stream.of( Arguments.of(true, true, true, DirStructure.VAULT), Arguments.of(true, true, false, DirStructure.VAULT), diff --git a/src/test/java/org/cryptomator/cryptofs/attr/CryptoDosFileAttributesTest.java b/src/test/java/org/cryptomator/cryptofs/attr/CryptoDosFileAttributesTest.java index 05d6c2e8..31c3ffd5 100644 --- a/src/test/java/org/cryptomator/cryptofs/attr/CryptoDosFileAttributesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/attr/CryptoDosFileAttributesTest.java @@ -45,7 +45,7 @@ public void setup() { @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) @DisplayName("on read-write filesystem") - class ReadWriteFileSystem { + public class ReadWriteFileSystem { private CryptoDosFileAttributes inTest; @@ -96,7 +96,7 @@ public void testIsSystemDelegates(boolean value) { @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) @DisplayName("on read-only filesystem") - class ReadOnlyFileSystem { + public class ReadOnlyFileSystem { private CryptoDosFileAttributes inTest; diff --git a/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileChannelTest.java b/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileChannelTest.java index b38f4458..2856e31f 100644 --- a/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileChannelTest.java +++ b/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileChannelTest.java @@ -252,7 +252,7 @@ public void testMapThrowsUnsupportedOperationException() throws IOException { } @Nested - class Locking { + public class Locking { private FileLock delegate = Mockito.mock(FileLock.class); diff --git a/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileLockTest.java b/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileLockTest.java index 9171bead..e439be59 100644 --- a/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileLockTest.java +++ b/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileLockTest.java @@ -22,16 +22,18 @@ public class CleartextFileLockTest { @BeforeEach public void setup() { channel = Mockito.mock(FileChannel.class); + delegate = Mockito.mock(FileLock.class); Mockito.when(channel.isOpen()).thenReturn(true); } @Nested @DisplayName("Shared Locks") - class ValidSharedLockTests { + public class SharedLockTests { @BeforeEach public void setup() { - delegate = Mockito.spy(new FileLockMock(channel, position, size, true)); + Mockito.when(delegate.isValid()).thenReturn(true); + Mockito.when(delegate.isShared()).thenReturn(true); inTest = new CleartextFileLock(channel, delegate, position, size); } @@ -73,11 +75,12 @@ public void testIsValid() { @Nested @DisplayName("After releasing the lock") - class ReleasedLock { + public class ReleasedLock { @BeforeEach public void setup() throws IOException { inTest.release(); + Mockito.when(delegate.isValid()).thenReturn(false); } @Test @@ -96,7 +99,7 @@ public void testReleaseDelegate() throws IOException { @Nested @DisplayName("After closing the channel") - class ClosedChannel { + public class ClosedChannel { @BeforeEach public void setup() throws IOException { @@ -115,11 +118,12 @@ public void testIsValid() { @Nested @DisplayName("Exclusive Locks") - class InvalidSharedLockTests { + public class ExclusiveLockTests { @BeforeEach public void setup() { - delegate = Mockito.spy(new FileLockMock(channel, position, size, false)); + Mockito.when(delegate.isValid()).thenReturn(true); + Mockito.when(delegate.isShared()).thenReturn(false); inTest = new CleartextFileLock(channel, delegate, position, size); } @@ -161,11 +165,12 @@ public void testIsValid() { @Nested @DisplayName("After releasing the lock") - class ReleasedLock { + public class ReleasedLock { @BeforeEach public void setup() throws IOException { inTest.release(); + Mockito.when(delegate.isValid()).thenReturn(false); } @Test @@ -184,7 +189,7 @@ public void testReleaseDelegate() throws IOException { @Nested @DisplayName("After closing the channel") - class ClosedChannel { + public class ClosedChannel { @BeforeEach public void setup() throws IOException { @@ -201,24 +206,4 @@ public void testIsValid() { } - private static class FileLockMock extends FileLock { - - private boolean valid; - - protected FileLockMock(FileChannel channel, long position, long size, boolean shared) { - super(channel, position, size, shared); - this.valid = true; - } - - @Override - public boolean isValid() { - return valid; - } - - @Override - public void release() { - valid = false; - } - } - } diff --git a/src/test/java/org/cryptomator/cryptofs/common/FileSystemCapabilityCheckerTest.java b/src/test/java/org/cryptomator/cryptofs/common/FileSystemCapabilityCheckerTest.java index 4f7fefd1..1c363eaf 100644 --- a/src/test/java/org/cryptomator/cryptofs/common/FileSystemCapabilityCheckerTest.java +++ b/src/test/java/org/cryptomator/cryptofs/common/FileSystemCapabilityCheckerTest.java @@ -19,11 +19,11 @@ import java.nio.file.spi.FileSystemProvider; import java.util.Collections; -class FileSystemCapabilityCheckerTest { +public class FileSystemCapabilityCheckerTest { @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) - class PathLengthLimits { + public class PathLengthLimits { private Path pathToVault = Mockito.mock(Path.class); private Path cDir = Mockito.mock(Path.class); diff --git a/src/test/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelperTest.java b/src/test/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelperTest.java index f4b4c15a..20467f00 100644 --- a/src/test/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelperTest.java +++ b/src/test/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelperTest.java @@ -15,7 +15,7 @@ import java.util.Random; import java.util.stream.Stream; -class MasterkeyBackupHelperTest { +public class MasterkeyBackupHelperTest { @EnabledOnOs({OS.LINUX, OS.MAC}) @ParameterizedTest @@ -47,7 +47,7 @@ public void testBackupFileWin(byte[] contents, @TempDir Path tmp) throws IOExcep Assertions.assertEquals(backupFile, backupFile2); } - static Stream<byte[]> createRandomBytes() { + public static Stream<byte[]> createRandomBytes() { Random rnd = new Random(42l); return Stream.generate(() -> { byte[] bytes = new byte[100]; diff --git a/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java b/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java index 1741b73b..e18810e1 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java @@ -11,7 +11,7 @@ import java.nio.file.Path; import java.util.stream.Stream; -class BrokenDirectoryFilterTest { +public class BrokenDirectoryFilterTest { private CryptoPathMapper cryptoPathMapper = Mockito.mock(CryptoPathMapper.class); private BrokenDirectoryFilter brokenDirectoryFilter = new BrokenDirectoryFilter(cryptoPathMapper); diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9SInflatorTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9SInflatorTest.java index 9bdb7f8b..e0ec9c00 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/C9SInflatorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/C9SInflatorTest.java @@ -13,7 +13,7 @@ import java.nio.file.Paths; import java.util.stream.Stream; -class C9SInflatorTest { +public class C9SInflatorTest { private LongFileNameProvider longFileNameProvider; private Cryptor cryptor; diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java index 75113ae3..fc868950 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java @@ -18,7 +18,7 @@ import java.nio.file.Paths; import java.util.stream.Stream; -class C9rConflictResolverTest { +public class C9rConflictResolverTest { private Cryptor cryptor; private FileNameCryptor fileNameCryptor; diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java index 6cf26197..05455820 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java @@ -15,7 +15,7 @@ import java.util.Optional; import java.util.stream.Stream; -class C9rDecryptorTest { +public class C9rDecryptorTest { private Cryptor cryptor; private FileNameCryptor fileNameCryptor; diff --git a/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java b/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java index c8e92b69..bad4f2cd 100644 --- a/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java @@ -24,7 +24,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -class FileHeaderHolderTest { +public class FileHeaderHolderTest { static { System.setProperty("org.slf4j.simpleLogger.log.org.cryptomator.cryptofs.ch.FileHeaderHolder", "trace"); @@ -46,7 +46,7 @@ public void setup() throws IOException { @Nested @DisplayName("existing header") - class ExistingHeader { + public class ExistingHeader { private FileHeader headerToLoad = Mockito.mock(FileHeader.class); private FileChannel channel = Mockito.mock(FileChannel.class); @@ -81,7 +81,7 @@ public void testLoadExisting() throws IOException, AuthenticationFailedException @Nested @DisplayName("new header") - class NewHeader { + public class NewHeader { private FileHeader headerToCreate = Mockito.mock(FileHeader.class); diff --git a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java index f424d770..13873697 100644 --- a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java +++ b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java @@ -90,7 +90,7 @@ public void testCloseImmediatelyIfOpeningFirstChannelFails() { @TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @DisplayName("FileChannels") - class FileChannelFactoryTest { + public class FileChannelFactoryTest { private OpenCryptoFile openCryptoFile; private CleartextFileChannel cleartextFileChannel; diff --git a/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java b/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java index f17acd8b..5cb2295e 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java @@ -24,46 +24,39 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.mockito.MockedStatic; import org.mockito.Mockito; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.spi.FileSystemProvider; import java.util.Collections; import java.util.Map; public class MigratorsTest { - private MockedStatic<Files> filesClass; private Path pathToVault; - private FileSystemCapabilityChecker fsCapabilityChecker; private Path vaultConfigPath; private Path masterkeyPath; + private FileSystemCapabilityChecker fsCapabilityChecker; @BeforeEach - public void setup() { - filesClass = Mockito.mockStatic(Files.class); - pathToVault = Mockito.mock(Path.class, "path/to/vault"); + public void setup(@TempDir Path tmpDir) { + pathToVault = tmpDir; + vaultConfigPath = tmpDir.resolve("vault.cryptomator"); + masterkeyPath = tmpDir.resolve("masterkey.cryptomator"); fsCapabilityChecker = Mockito.mock(FileSystemCapabilityChecker.class); - vaultConfigPath = Mockito.mock(Path.class, "path/to/vault/vault.cryptomator"); - masterkeyPath = Mockito.mock(Path.class, "path/to/vault/masterkey.cryptomator"); - - Mockito.when(pathToVault.resolve("masterkey.cryptomator")).thenReturn(masterkeyPath); - Mockito.when(pathToVault.resolve("vault.cryptomator")).thenReturn(vaultConfigPath); - } - - @AfterEach - public void tearDown() { - filesClass.close(); } @Test @DisplayName("can't determine vault version without masterkey.cryptomator or vault.cryptomator") public void throwsExceptionIfNeitherMasterkeyNorVaultConfigExists() { - filesClass.when(() -> Files.exists(vaultConfigPath)).thenReturn(false); - filesClass.when(() -> Files.exists(masterkeyPath)).thenReturn(false); - Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker); IOException thrown = Assertions.assertThrows(IOException.class, () -> { @@ -79,13 +72,14 @@ public class WithExistingVaultConfig { private VaultConfig.UnverifiedVaultConfig unverifiedVaultConfig; @BeforeEach - public void setup() { - Assumptions.assumeFalse(Files.exists(masterkeyPath)); + public void setup() throws IOException { vaultConfigClass = Mockito.mockStatic(VaultConfig.class); unverifiedVaultConfig = Mockito.mock(VaultConfig.UnverifiedVaultConfig.class); - filesClass.when(() -> Files.exists(vaultConfigPath)).thenReturn(true); - filesClass.when(() -> Files.readString(vaultConfigPath)).thenReturn("vault-config"); + Files.write(vaultConfigPath, "vault-config".getBytes(StandardCharsets.UTF_8)); + Assumptions.assumeTrue(Files.exists(vaultConfigPath)); + Assumptions.assumeFalse(Files.exists(masterkeyPath)); + vaultConfigClass.when(() -> VaultConfig.decode("vault-config")).thenReturn(unverifiedVaultConfig); } @@ -164,10 +158,11 @@ public class WithExistingMasterkeyFile { private MockedStatic<MasterkeyFileAccess> masterkeyFileAccessClass; @BeforeEach - public void setup() { - Assumptions.assumeFalse(Files.exists(vaultConfigPath)); + public void setup() throws IOException { masterkeyFileAccessClass = Mockito.mockStatic(MasterkeyFileAccess.class); - filesClass.when(() -> Files.exists(masterkeyPath)).thenReturn(true); + Files.createFile(masterkeyPath); + Assumptions.assumeFalse(Files.exists(vaultConfigPath)); + Assumptions.assumeTrue(Files.exists(masterkeyPath)); } @AfterEach diff --git a/src/test/java/org/cryptomator/cryptofs/migration/api/SimpleMigrationContinuationListenerTest.java b/src/test/java/org/cryptomator/cryptofs/migration/api/SimpleMigrationContinuationListenerTest.java index 3e2738d9..7db601f9 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/api/SimpleMigrationContinuationListenerTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/api/SimpleMigrationContinuationListenerTest.java @@ -9,7 +9,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -class SimpleMigrationContinuationListenerTest { +public class SimpleMigrationContinuationListenerTest { @Test public void testConcurrency() { diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v7/FilePathMigrationTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v7/FilePathMigrationTest.java index 120592bb..9d0cb5a0 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v7/FilePathMigrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v7/FilePathMigrationTest.java @@ -145,7 +145,7 @@ public void testGetTargetPath(String oldCanonicalName, String attemptSuffix, Str @DisplayName("FilePathMigration.parse(...)") @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) - class Parsing { + public class Parsing { private FileSystem fs; private Path vaultRoot; @@ -275,7 +275,7 @@ public void testParseShortenedFile(String oldPath, String metadataFilePath, Stri @DisplayName("FilePathMigration.parse(...).get().migrate(...)") @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) - class Migrating { + public class Migrating { private FileSystem fs; private Path vaultRoot; From 6445f1ddb06c201dfa6e4e9c81459201c17dec70 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Fri, 21 May 2021 21:32:33 +0200 Subject: [PATCH 63/70] fix for https://issues.sonatype.org/browse/OSSRH-66257 cherry-picked from 2.1.0 branch --- .github/workflows/publish-central.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/publish-central.yml b/.github/workflows/publish-central.yml index d9be480e..93b31389 100644 --- a/.github/workflows/publish-central.yml +++ b/.github/workflows/publish-central.yml @@ -32,6 +32,11 @@ jobs: - name: Deploy run: mvn deploy -B -DskipTests -Psign,deploy-central --no-transfer-progress env: + MAVEN_OPTS: > + --add-opens=java.base/java.util=ALL-UNNAMED + --add-opens=java.base/java.lang.reflect=ALL-UNNAMED + --add-opens=java.base/java.text=ALL-UNNAMED + --add-opens=java.desktop/java.awt.font=ALL-UNNAMED MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} \ No newline at end of file From e32836d8b7594d107cfe1728e95ddbcf81179a42 Mon Sep 17 00:00:00 2001 From: Armin Schrenk <armin.schrenk@skymatic.de> Date: Tue, 15 Jun 2021 10:57:03 +0200 Subject: [PATCH 64/70] add IDE specific code style --- .idea/codeStyles/Project.xml | 78 ++++++++++++++++++++++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 ++ 2 files changed, 83 insertions(+) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..d361191e --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,78 @@ +<component name="ProjectCodeStyleConfiguration"> + <code_scheme name="Project" version="173"> + <option name="OTHER_INDENT_OPTIONS"> + <value> + <option name="USE_TAB_CHARACTER" value="true" /> + </value> + </option> + <option name="LINE_SEPARATOR" value=" " /> + <option name="RIGHT_MARGIN" value="220" /> + <option name="FORMATTER_TAGS_ENABLED" value="true" /> + <JavaCodeStyleSettings> + <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" /> + <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="10" /> + <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND"> + <value /> + </option> + <option name="IMPORT_LAYOUT_TABLE"> + <value> + <package name="" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="javax" withSubpackages="true" static="false" /> + <package name="javafx" withSubpackages="true" static="false" /> + <package name="java" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="" withSubpackages="true" static="true" /> + </value> + </option> + <option name="JD_ALIGN_PARAM_COMMENTS" value="false" /> + <option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" /> + </JavaCodeStyleSettings> + <Properties> + <option name="KEEP_BLANK_LINES" value="true" /> + </Properties> + <XML> + <option name="XML_ATTRIBUTE_WRAP" value="0" /> + </XML> + <codeStyleSettings language="CSS"> + <indentOptions> + <option name="USE_TAB_CHARACTER" value="true" /> + </indentOptions> + </codeStyleSettings> + <codeStyleSettings language="Groovy"> + <indentOptions> + <option name="USE_TAB_CHARACTER" value="true" /> + </indentOptions> + </codeStyleSettings> + <codeStyleSettings language="HTML"> + <indentOptions> + <option name="USE_TAB_CHARACTER" value="true" /> + </indentOptions> + </codeStyleSettings> + <codeStyleSettings language="JAVA"> + <option name="KEEP_LINE_BREAKS" value="false" /> + <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" /> + <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" /> + <option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" /> + <option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" /> + <option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" /> + <option name="ENUM_CONSTANTS_WRAP" value="2" /> + <indentOptions> + <option name="USE_TAB_CHARACTER" value="true" /> + </indentOptions> + <arrangement> + <rules /> + </arrangement> + </codeStyleSettings> + <codeStyleSettings language="JSON"> + <indentOptions> + <option name="USE_TAB_CHARACTER" value="true" /> + </indentOptions> + </codeStyleSettings> + <codeStyleSettings language="XML"> + <indentOptions> + <option name="USE_TAB_CHARACTER" value="true" /> + </indentOptions> + </codeStyleSettings> + </code_scheme> +</component> \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ +<component name="ProjectCodeStyleConfiguration"> + <state> + <option name="USE_PER_PROJECT_SETTINGS" value="true" /> + </state> +</component> \ No newline at end of file From fb251f77407160f24cb491d59a535fff53727a04 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Thu, 17 Jun 2021 08:38:06 +0200 Subject: [PATCH 65/70] bumped dependency --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 713f05a4..6480821c 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ <maven.compiler.release>16</maven.compiler.release> <!-- dependencies --> - <cryptolib.version>2.0.0-rc2</cryptolib.version> + <cryptolib.version>2.0.0-rc4</cryptolib.version> <jwt.version>3.15.0</jwt.version> <dagger.version>2.35.1</dagger.version> <guava.version>30.1.1-jre</guava.version> From 59ff24cc4752830c8a51e93ec2839e085c49135e Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Thu, 24 Jun 2021 14:42:04 +0200 Subject: [PATCH 66/70] dependency bump --- pom.xml | 12 ++++++------ src/main/java/module-info.java | 9 +++++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 6480821c..b440f7b3 100644 --- a/pom.xml +++ b/pom.xml @@ -18,20 +18,20 @@ <maven.compiler.release>16</maven.compiler.release> <!-- dependencies --> - <cryptolib.version>2.0.0-rc4</cryptolib.version> + <cryptolib.version>2.0.0-rc5</cryptolib.version> <jwt.version>3.15.0</jwt.version> - <dagger.version>2.35.1</dagger.version> + <dagger.version>2.37</dagger.version> <guava.version>30.1.1-jre</guava.version> - <slf4j.version>1.7.30</slf4j.version> + <slf4j.version>1.7.31</slf4j.version> <!-- test dependencies --> <junit.jupiter.version>5.7.2</junit.jupiter.version> - <mockito.version>3.10.0</mockito.version> + <mockito.version>3.11.2</mockito.version> <hamcrest.version>2.2</hamcrest.version> <!-- build plugin dependencies --> - <dependency-check.version>6.1.6</dependency-check.version> - <jacoco.version>0.8.6</jacoco.version> + <dependency-check.version>6.2.2</dependency-check.version> + <jacoco.version>0.8.7</jacoco.version> <nexus-staging.version>1.6.8</nexus-staging.version> </properties> diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 7782d4bb..49a75a59 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -6,11 +6,16 @@ requires transitive org.cryptomator.cryptolib; requires com.google.common; requires org.slf4j; + requires dagger; /* TODO: filename-based modules: */ requires java.jwt; - requires dagger; - requires static javax.inject; // probably no longer needed if dagger is an automatic module (but might require --patch-module in case of split packages) + + // filename-based module required by dagger + // we will probably need to live with this for a while: + // https://github.com/javax-inject/javax-inject/issues/33 + // May be provided by another lib during runtime + requires static javax.inject; exports org.cryptomator.cryptofs; exports org.cryptomator.cryptofs.common; From 1178bf1f6c8c3c078a7faf447e3a6b8ec0781807 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Sat, 26 Jun 2021 13:28:59 +0200 Subject: [PATCH 67/70] dependency bump --- pom.xml | 2 +- src/main/java/module-info.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index b440f7b3..0d79dafb 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ <!-- dependencies --> <cryptolib.version>2.0.0-rc5</cryptolib.version> - <jwt.version>3.15.0</jwt.version> + <jwt.version>3.17.0</jwt.version> <dagger.version>2.37</dagger.version> <guava.version>30.1.1-jre</guava.version> <slf4j.version>1.7.31</slf4j.version> diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 49a75a59..c645dcc5 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -7,9 +7,7 @@ requires com.google.common; requires org.slf4j; requires dagger; - - /* TODO: filename-based modules: */ - requires java.jwt; + requires com.auth0.jwt; // filename-based module required by dagger // we will probably need to live with this for a while: From 9e6f94f07056f34bfbe3dfac02a914b3ec0b6e31 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 28 Jun 2021 15:39:02 +0200 Subject: [PATCH 68/70] adjusted to latest cryptolib api changes --- pom.xml | 2 +- .../cryptofs/CryptoFileSystemProperties.java | 13 +++---- .../cryptofs/CryptoFileSystemProvider.java | 3 +- .../cryptofs/CryptoFileSystems.java | 3 +- .../cryptofs/VaultCipherCombo.java | 34 ------------------- .../org/cryptomator/cryptofs/VaultConfig.java | 11 +++--- .../attr/CryptoBasicFileAttributes.java | 4 +-- .../cryptofs/ch/CleartextFileChannel.java | 3 +- .../cryptofs/fh/OpenCryptoFile.java | 4 +-- .../cryptofs/fh/OpenCryptoFileModule.java | 2 -- .../cryptofs/migration/MigrationModule.java | 8 ----- .../cryptofs/CryptoFileSystemsTest.java | 10 +++--- .../cryptomator/cryptofs/VaultConfigTest.java | 7 ++-- .../attr/CryptoBasicFileAttributesTest.java | 1 + 14 files changed, 34 insertions(+), 71 deletions(-) delete mode 100644 src/main/java/org/cryptomator/cryptofs/VaultCipherCombo.java diff --git a/pom.xml b/pom.xml index 0d79dafb..6b6c5c8f 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ <maven.compiler.release>16</maven.compiler.release> <!-- dependencies --> - <cryptolib.version>2.0.0-rc5</cryptolib.version> + <cryptolib.version>2.0.0-rc6</cryptolib.version> <jwt.version>3.17.0</jwt.version> <dagger.version>2.37</dagger.version> <guava.version>30.1.1-jre</guava.version> diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index 39d3c04d..d9e1b944 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -9,6 +9,7 @@ package org.cryptomator.cryptofs; import com.google.common.base.Strings; +import org.cryptomator.cryptolib.api.CryptorProvider; import org.cryptomator.cryptolib.api.MasterkeyLoader; import java.net.URI; @@ -91,7 +92,7 @@ public enum FileSystemFlags { */ public static final String PROPERTY_CIPHER_COMBO = "cipherCombo"; - static final VaultCipherCombo DEFAULT_CIPHER_COMBO = VaultCipherCombo.SIV_GCM; + static final CryptorProvider.Scheme DEFAULT_CIPHER_COMBO = CryptorProvider.Scheme.SIV_GCM; private final Set<Entry<String, Object>> entries; @@ -110,8 +111,8 @@ MasterkeyLoader keyLoader() { return (MasterkeyLoader) get(PROPERTY_KEYLOADER); } - public VaultCipherCombo cipherCombo() { - return (VaultCipherCombo) get(PROPERTY_CIPHER_COMBO); + public CryptorProvider.Scheme cipherCombo() { + return (CryptorProvider.Scheme) get(PROPERTY_CIPHER_COMBO); } @SuppressWarnings("unchecked") @@ -183,7 +184,7 @@ public static CryptoFileSystemProperties wrap(Map<String, ?> properties) { */ public static class Builder { - public VaultCipherCombo cipherCombo = DEFAULT_CIPHER_COMBO; + public CryptorProvider.Scheme cipherCombo = DEFAULT_CIPHER_COMBO; private MasterkeyLoader keyLoader = null; private final Set<FileSystemFlags> flags = EnumSet.copyOf(DEFAULT_FILESYSTEM_FLAGS); private String vaultConfigFilename = DEFAULT_VAULTCONFIG_FILENAME; @@ -199,7 +200,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(VaultCipherCombo.class, PROPERTY_CIPHER_COMBO, properties, this::withCipherCombo); + checkedSet(CryptorProvider.Scheme.class, PROPERTY_CIPHER_COMBO, properties, this::withCipherCombo); } private <T> void checkedSet(Class<T> type, String key, Map<String, ?> properties, Consumer<T> setter) { @@ -235,7 +236,7 @@ public Builder withMaxCleartextNameLength(int maxCleartextNameLength) { * @return this * @since 2.0.0 */ - public Builder withCipherCombo(VaultCipherCombo cipherCombo) { + public Builder withCipherCombo(CryptorProvider.Scheme cipherCombo) { this.cipherCombo = cipherCombo; return this; } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 19d5be63..d6d32188 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -12,6 +12,7 @@ import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; import org.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.cryptolib.api.CryptorProvider; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; @@ -143,7 +144,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope byte[] rawKey = new byte[0]; 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)) { + Cryptor cryptor = CryptorProvider.forScheme(config.getCipherCombo()).provide(key, strongSecureRandom())) { rawKey = key.getEncoded(); // save vault config: Path vaultConfigPath = pathToVault.resolve(properties.vaultConfigFilename()); diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java index de071d9a..81f422bf 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java @@ -3,6 +3,7 @@ import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; import org.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.cryptolib.api.CryptorProvider; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.slf4j.Logger; @@ -51,7 +52,7 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT 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()); + var cryptor = CryptorProvider.forScheme(config.getCipherCombo()).provide(key.clone(), csprng); try { checkVaultRootExistence(pathToVault, cryptor); return fileSystems.compute(normalizedPathToVault, (path, fs) -> { diff --git a/src/main/java/org/cryptomator/cryptofs/VaultCipherCombo.java b/src/main/java/org/cryptomator/cryptofs/VaultCipherCombo.java deleted file mode 100644 index 101cdd0c..00000000 --- a/src/main/java/org/cryptomator/cryptofs/VaultCipherCombo.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cryptomator.cryptofs; - -import org.cryptomator.cryptolib.Cryptors; -import org.cryptomator.cryptolib.api.CryptorProvider; - -import java.security.SecureRandom; -import java.util.function.Function; - -/** - * A combination of different ciphers and/or cipher modes in a Cryptomator vault. - */ -public enum VaultCipherCombo { - /** - * AES-SIV for file name encryption - * AES-CTR + HMAC for content encryption - */ - SIV_CTRMAC(Cryptors::version1), - - /** - * AES-SIV for file name encryption - * AES-GCM for content encryption - */ - SIV_GCM(Cryptors::version2); - - private final Function<SecureRandom, CryptorProvider> cryptorProvider; - - VaultCipherCombo(Function<SecureRandom, CryptorProvider> cryptorProvider) { - this.cryptorProvider = cryptorProvider; - } - - public CryptorProvider getCryptorProvider(SecureRandom csprng) { - return cryptorProvider.apply(csprng); - } -} diff --git a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java index 18356d0a..95e63312 100644 --- a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java +++ b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java @@ -8,6 +8,7 @@ import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; import org.cryptomator.cryptofs.common.Constants; +import org.cryptomator.cryptolib.api.CryptorProvider; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; @@ -36,13 +37,13 @@ public class VaultConfig { private final String id; private final int vaultVersion; - private final VaultCipherCombo cipherCombo; + private final CryptorProvider.Scheme cipherCombo; private final int shorteningThreshold; private VaultConfig(DecodedJWT verifiedConfig) { this.id = verifiedConfig.getId(); this.vaultVersion = verifiedConfig.getClaim(JSON_KEY_VAULTVERSION).asInt(); - this.cipherCombo = VaultCipherCombo.valueOf(verifiedConfig.getClaim(JSON_KEY_CIPHERCONFIG).asString()); + this.cipherCombo = CryptorProvider.Scheme.valueOf(verifiedConfig.getClaim(JSON_KEY_CIPHERCONFIG).asString()); this.shorteningThreshold = verifiedConfig.getClaim(JSON_KEY_SHORTENING_THRESHOLD).asInt(); } @@ -61,7 +62,7 @@ public int getVaultVersion() { return vaultVersion; } - public VaultCipherCombo getCipherCombo() { + public CryptorProvider.Scheme getCipherCombo() { return cipherCombo; } @@ -184,10 +185,10 @@ public static class VaultConfigBuilder { private final String id = UUID.randomUUID().toString(); private final int vaultVersion = Constants.VAULT_VERSION; - private VaultCipherCombo cipherCombo; + private CryptorProvider.Scheme cipherCombo; private int shorteningThreshold; - public VaultConfigBuilder cipherCombo(VaultCipherCombo cipherCombo) { + public VaultConfigBuilder cipherCombo(CryptorProvider.Scheme cipherCombo) { this.cipherCombo = cipherCombo; return this; } diff --git a/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java b/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java index 92a8d320..252a9a3f 100644 --- a/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java +++ b/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java @@ -10,7 +10,6 @@ import org.cryptomator.cryptofs.common.CiphertextFileType; import org.cryptomator.cryptofs.fh.OpenCryptoFile; -import org.cryptomator.cryptolib.Cryptors; import org.cryptomator.cryptolib.api.Cryptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,7 +52,8 @@ private static long getPlaintextFileSize(Path ciphertextPath, long size, Optiona private static long calculatePlaintextFileSize(Path ciphertextPath, long size, Cryptor cryptor) { try { - return Cryptors.cleartextSize(size - cryptor.fileHeaderCryptor().headerSize(), cryptor); + long payloadSize = size - cryptor.fileHeaderCryptor().headerSize(); + return cryptor.fileContentCryptor().cleartextSize(payloadSize); } catch (IllegalArgumentException e) { LOG.warn("Unable to calculate cleartext file size for {}. Ciphertext size (including header): {}", ciphertextPath, size); return 0l; diff --git a/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java b/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java index 75abe860..b41b626d 100644 --- a/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java +++ b/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java @@ -32,7 +32,6 @@ import static java.lang.Math.max; import static java.lang.Math.min; -import static org.cryptomator.cryptolib.Cryptors.ciphertextSize; @ChannelScoped public class CleartextFileChannel extends AbstractFileChannel { @@ -192,7 +191,7 @@ protected void truncateLocked(long newSize) throws IOException { if (sizeOfIncompleteChunk > 0) { chunkCache.get(indexOfLastChunk).truncate(sizeOfIncompleteChunk); } - long ciphertextFileSize = cryptor.fileHeaderCryptor().headerSize() + ciphertextSize(newSize, cryptor); + long ciphertextFileSize = cryptor.fileHeaderCryptor().headerSize() + cryptor.fileContentCryptor().ciphertextSize(newSize); chunkCache.invalidateAll(); // make sure no chunks _after_ newSize exist that would otherwise be written during the next cache eviction ciphertextFileChannel.truncate(ciphertextFileSize); position = min(newSize, position); diff --git a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java index d9352fa6..98b22756 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java @@ -11,7 +11,6 @@ import org.cryptomator.cryptofs.EffectiveOpenOptions; import org.cryptomator.cryptofs.ch.ChannelComponent; import org.cryptomator.cryptofs.ch.CleartextFileChannel; -import org.cryptomator.cryptolib.Cryptors; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.FileHeader; import org.slf4j.Logger; @@ -135,7 +134,8 @@ private void initFileSize(FileChannel ciphertextFileChannel) throws IOException try { long ciphertextSize = ciphertextFileChannel.size(); if (ciphertextSize > 0l) { - cleartextSize = Cryptors.cleartextSize(ciphertextSize - cryptor.fileHeaderCryptor().headerSize(), cryptor); + long payloadSize = ciphertextSize - cryptor.fileHeaderCryptor().headerSize(); + cleartextSize = cryptor.fileContentCryptor().cleartextSize(payloadSize); } } catch (IllegalArgumentException e) { LOG.warn("Invalid cipher text file size. Assuming empty file.", e); diff --git a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFileModule.java b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFileModule.java index 281d8e7f..81cac714 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFileModule.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFileModule.java @@ -18,8 +18,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Supplier; -import static org.cryptomator.cryptolib.Cryptors.cleartextSize; - @Module public class OpenCryptoFileModule { diff --git a/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java b/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java index 8b777654..fcc9ba7e 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java @@ -14,13 +14,10 @@ import org.cryptomator.cryptofs.migration.v6.Version6Migrator; import org.cryptomator.cryptofs.migration.v7.Version7Migrator; import org.cryptomator.cryptofs.migration.v8.Version8Migrator; -import org.cryptomator.cryptolib.Cryptors; -import org.cryptomator.cryptolib.api.CryptorProvider; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import java.security.SecureRandom; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -28,11 +25,6 @@ @Module class MigrationModule { - @Provides - CryptorProvider provideVersion1CryptorProvider(SecureRandom csprng) { - return Cryptors.version1(csprng); - } - @Provides FileSystemCapabilityChecker provideFileSystemCapabilityChecker() { return new FileSystemCapabilityChecker(); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java index fd2e6bca..37dd143b 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java @@ -50,7 +50,7 @@ public class CryptoFileSystemsTest { private final Masterkey clonedMasterkey = Mockito.mock(Masterkey.class); private final byte[] rawKey = new byte[64]; private final VaultConfig vaultConfig = mock(VaultConfig.class); - private final VaultCipherCombo cipherCombo = mock(VaultCipherCombo.class); + private final CryptorProvider.Scheme cipherCombo = mock(CryptorProvider.Scheme.class); private final SecureRandom csprng = Mockito.mock(SecureRandom.class); private final CryptorProvider cryptorProvider = mock(CryptorProvider.class); private final Cryptor cryptor = mock(Cryptor.class); @@ -60,6 +60,7 @@ public class CryptoFileSystemsTest { private MockedStatic<VaultConfig> vaultConficClass; private MockedStatic<Files> filesClass; + private MockedStatic<CryptorProvider> cryptorProviderClass; private final CryptoFileSystems inTest = new CryptoFileSystems(cryptoFileSystemComponentBuilder, capabilityChecker, csprng); @@ -67,6 +68,7 @@ public class CryptoFileSystemsTest { public void setup() throws IOException, MasterkeyLoadingFailedException { vaultConficClass = Mockito.mockStatic(VaultConfig.class); filesClass = Mockito.mockStatic(Files.class); + cryptorProviderClass = Mockito.mockStatic(CryptorProvider.class); when(pathToVault.normalize()).thenReturn(normalizedPathToVault); when(normalizedPathToVault.resolve("vault.cryptomator")).thenReturn(configFilePath); @@ -74,16 +76,15 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { when(properties.keyLoader()).thenReturn(keyLoader); filesClass.when(() -> Files.readString(configFilePath, StandardCharsets.US_ASCII)).thenReturn("jwt-vault-config"); vaultConficClass.when(() -> VaultConfig.decode("jwt-vault-config")).thenReturn(configLoader); + cryptorProviderClass.when(() -> CryptorProvider.forScheme(cipherCombo)).thenReturn(cryptorProvider); when(VaultConfig.decode("jwt-vault-config")).thenReturn(configLoader); when(configLoader.getKeyId()).thenReturn(URI.create("test:key")); when(keyLoader.loadKey(Mockito.any())).thenReturn(masterkey); when(masterkey.getEncoded()).thenReturn(rawKey); when(masterkey.clone()).thenReturn(clonedMasterkey); when(configLoader.verify(rawKey, Constants.VAULT_VERSION)).thenReturn(vaultConfig); - when(cryptorProvider.withKey(clonedMasterkey)).thenReturn(cryptor); + when(cryptorProvider.provide(clonedMasterkey, csprng)).thenReturn(cryptor); when(vaultConfig.getCipherCombo()).thenReturn(cipherCombo); - when(cipherCombo.getCryptorProvider(csprng)).thenReturn(cryptorProvider); - when(cryptorProvider.withKey(masterkey)).thenReturn(cryptor); when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor); when(fileNameCryptor.hashDirectoryId("")).thenReturn("ABCDEFGHIJKLMNOP"); when(pathToVault.resolve(Constants.DATA_DIR_NAME)).thenReturn(dataDirPath); @@ -103,6 +104,7 @@ public void setup() throws IOException, MasterkeyLoadingFailedException { public void tearDown() { vaultConficClass.close(); filesClass.close(); + cryptorProviderClass.close(); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java index 546c0aba..a7abbda7 100644 --- a/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java +++ b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java @@ -6,6 +6,7 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; import org.cryptomator.cryptofs.common.Constants; +import org.cryptomator.cryptolib.api.CryptorProvider; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; @@ -47,7 +48,7 @@ public class WithValidToken { @BeforeEach public void setup() throws MasterkeyLoadingFailedException { - originalConfig = VaultConfig.createNew().cipherCombo(VaultCipherCombo.SIV_CTRMAC).shorteningThreshold(220).build(); + originalConfig = VaultConfig.createNew().cipherCombo(CryptorProvider.Scheme.SIV_CTRMAC).shorteningThreshold(220).build(); token = originalConfig.toToken("TEST_KEY", rawKey); } @@ -76,11 +77,11 @@ public void testLoadWithInvalidKey(int pos) { @Test public void testCreateNew() { - var config = VaultConfig.createNew().cipherCombo(VaultCipherCombo.SIV_CTRMAC).shorteningThreshold(220).build(); + var config = VaultConfig.createNew().cipherCombo(CryptorProvider.Scheme.SIV_CTRMAC).shorteningThreshold(220).build(); Assertions.assertNotNull(config.getId()); Assertions.assertEquals(Constants.VAULT_VERSION, config.getVaultVersion()); - Assertions.assertEquals(VaultCipherCombo.SIV_CTRMAC, config.getCipherCombo()); + Assertions.assertEquals(CryptorProvider.Scheme.SIV_CTRMAC, config.getCipherCombo()); Assertions.assertEquals(220, config.getShorteningThreshold()); } diff --git a/src/test/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributesTest.java b/src/test/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributesTest.java index 87af1128..abda39d7 100644 --- a/src/test/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributesTest.java @@ -40,6 +40,7 @@ public void setup() { Mockito.when(cryptor.fileContentCryptor()).thenReturn(contentCryptor); Mockito.when(contentCryptor.cleartextChunkSize()).thenReturn(32 * 1024); Mockito.when(contentCryptor.ciphertextChunkSize()).thenReturn(16 + 32 * 1024 + 32); + Mockito.doCallRealMethod().when(contentCryptor).cleartextSize(Mockito.anyLong()); ciphertextFilePath = Mockito.mock(Path.class, "ciphertextFile"); delegateAttr = Mockito.mock(BasicFileAttributes.class); From 2a623cfadd4685497dde9a0f2b119e4472a4bd35 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 19 Jul 2021 13:01:51 +0200 Subject: [PATCH 69/70] dependency update --- pom.xml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 6b6c5c8f..e847712d 100644 --- a/pom.xml +++ b/pom.xml @@ -18,8 +18,8 @@ <maven.compiler.release>16</maven.compiler.release> <!-- dependencies --> - <cryptolib.version>2.0.0-rc6</cryptolib.version> - <jwt.version>3.17.0</jwt.version> + <cryptolib.version>2.0.0-rc7</cryptolib.version> + <jwt.version>3.18.1</jwt.version> <dagger.version>2.37</dagger.version> <guava.version>30.1.1-jre</guava.version> <slf4j.version>1.7.31</slf4j.version> @@ -161,7 +161,7 @@ </plugin> <plugin> <artifactId>maven-javadoc-plugin</artifactId> - <version>3.2.0</version> + <version>3.3.0</version> <executions> <execution> <id>attach-javadocs</id> @@ -196,14 +196,6 @@ <tag><name>serialData</name></tag> <tag><name>see</name></tag> </tags> - <!-- Used for javax.annotation.Generated in dagger-generated code. Can be removed when using JDK 11+ --> - <additionalDependencies> - <additionalDependency> - <groupId>javax.annotation</groupId> - <artifactId>jsr250-api</artifactId> - <version>1.0</version> - </additionalDependency> - </additionalDependencies> </configuration> </plugin> </plugins> @@ -270,7 +262,7 @@ <plugins> <plugin> <artifactId>maven-gpg-plugin</artifactId> - <version>1.6</version> + <version>3.0.1</version> <executions> <execution> <id>sign-artifacts</id> From c982beedec1cc061b8a2fd919cffdd1d088c6ff0 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel <sebastian.stenzel@gmail.com> Date: Mon, 19 Jul 2021 14:53:52 +0200 Subject: [PATCH 70/70] updated cryptolib --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e847712d..62515786 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ <maven.compiler.release>16</maven.compiler.release> <!-- dependencies --> - <cryptolib.version>2.0.0-rc7</cryptolib.version> + <cryptolib.version>2.0.0</cryptolib.version> <jwt.version>3.18.1</jwt.version> <dagger.version>2.37</dagger.version> <guava.version>30.1.1-jre</guava.version>