diff --git a/pom.xml b/pom.xml index 5e5387e1..d3f6a1aa 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.cryptomator cryptofs - 2.1.1 + 2.2.0 Cryptomator Crypto Filesystem This library provides the Java filesystem provider used by Cryptomator. https://github.com/cryptomator/cryptofs diff --git a/src/main/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelper.java b/src/main/java/org/cryptomator/cryptofs/common/BackupHelper.java similarity index 50% rename from src/main/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelper.java rename to src/main/java/org/cryptomator/cryptofs/common/BackupHelper.java index 97a1097a..f5d2406e 100644 --- a/src/main/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelper.java +++ b/src/main/java/org/cryptomator/cryptofs/common/BackupHelper.java @@ -17,11 +17,12 @@ import java.security.NoSuchAlgorithmException; /** - * Utility class for generating a suffix for the backup file to make it unique to its original master key file. + * Utility class for generating a suffix for the backup file to make it unique to its original file. */ -public final class MasterkeyBackupHelper { +public final class BackupHelper { - private static final Logger LOG = LoggerFactory.getLogger(MasterkeyBackupHelper.class); + private final static long NO_MISMATCH = -1L; + private static final Logger LOG = LoggerFactory.getLogger(BackupHelper.class); /** * Computes the SHA-256 digest of the given byte array and returns a file suffix containing the first 4 bytes in hex string format. @@ -40,42 +41,36 @@ public static String generateFileIdSuffix(byte[] fileBytes) { } /** - * Do a best-effort attempt to backup the masterkey at the given path. + * Do a best-effort attempt to back up the file at the given path. * Fails silently if a _valid_ backup already exists and fails with a log entry, if any IO error occurs while creating or reading the backup file. * - * @param masterKeyPath The masterkey file to backup - * @throws IOException If the masterkey cannot be read. + * @param path The file to back up + * @throws IOException If the path cannot be read. */ - public static Path attemptMasterKeyBackup(Path masterKeyPath) throws IOException { - byte[] keyFileContents = Files.readAllBytes(masterKeyPath); - String backupFileName = masterKeyPath.getFileName().toString() + generateFileIdSuffix(keyFileContents) + Constants.MASTERKEY_BACKUP_SUFFIX; - Path backupFilePath = masterKeyPath.resolveSibling(backupFileName); + public static Path attemptBackup(Path path) throws IOException { + byte[] fileContents = Files.readAllBytes(path); + final String fileToBackup = path.getFileName().toString(); + String backupFileName = fileToBackup + generateFileIdSuffix(fileContents) + Constants.BACKUP_SUFFIX; + Path backupFilePath = path.resolveSibling(backupFileName); try (WritableByteChannel ch = Files.newByteChannel(backupFilePath, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { - ch.write(ByteBuffer.wrap(keyFileContents)); + ch.write(ByteBuffer.wrap(fileContents)); } catch (AccessDeniedException | FileAlreadyExistsException e) { - assertExistingBackupMatchesContent(backupFilePath, ByteBuffer.wrap(keyFileContents)); + assertSameContent(backupFilePath, path); } catch (IOException e) { - LOG.warn("Failed to backup valid masterkey file."); + LOG.warn("Failed to backup valid {} file.", fileToBackup); } return backupFilePath; } - private static void assertExistingBackupMatchesContent(Path backupFilePath, ByteBuffer expectedContent) { - if (Files.exists(backupFilePath)) { - // TODO replace by Files.mismatch() when using JDK > 12 - ByteBuffer buf = ByteBuffer.allocateDirect(expectedContent.remaining() + 1); - try (ReadableByteChannel ch = Files.newByteChannel(backupFilePath, StandardOpenOption.READ)) { - ch.read(buf); - buf.flip(); - if (buf.compareTo(expectedContent) != 0) { - LOG.warn("Corrupt masterkey backup {}. Please replace it manually or unlock the vault on a writable storage device.", backupFilePath); - } else { - LOG.debug("Verified backup file: {}", backupFilePath); - } - } catch (IOException e) { - LOG.warn("Failed to compare valid masterkey with backup file.", e); + private static void assertSameContent(final Path backupFile, final Path originalFile) { + try { + if (Files.mismatch(backupFile, originalFile) == NO_MISMATCH) { + LOG.debug("Verified backup file: {}", backupFile); + } else { + LOG.warn("Corrupt {} backup for: {}. Please replace it manually or unlock the vault on a writable storage device.", backupFile.getFileName(), backupFile); } + } catch (IOException e) { + LOG.warn("Failed to compare valid %s with backup file.".formatted(backupFile), e); } } - } diff --git a/src/main/java/org/cryptomator/cryptofs/common/Constants.java b/src/main/java/org/cryptomator/cryptofs/common/Constants.java index 2a02bfe6..dd95f02c 100644 --- a/src/main/java/org/cryptomator/cryptofs/common/Constants.java +++ b/src/main/java/org/cryptomator/cryptofs/common/Constants.java @@ -14,7 +14,7 @@ private Constants() { } public static final int VAULT_VERSION = 8; - public static final String MASTERKEY_BACKUP_SUFFIX = ".bkup"; + public static final String BACKUP_SUFFIX = ".bkup"; public static final String DATA_DIR_NAME = "d"; public static final String ROOT_DIR_ID = ""; public static final String RECOVERY_DIR_ID = "recovery"; 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 e07e6bc8..0805a325 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java @@ -5,7 +5,7 @@ *******************************************************************************/ package org.cryptomator.cryptofs.migration.v6; -import org.cryptomator.cryptofs.common.MasterkeyBackupHelper; +import org.cryptomator.cryptofs.common.BackupHelper; import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener; import org.cryptomator.cryptofs.migration.api.MigrationProgressListener; import org.cryptomator.cryptofs.migration.api.Migrator; @@ -48,11 +48,11 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey 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); + Path masterkeyBackupFile = BackupHelper.attemptBackup(masterkeyFile); LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName()); progressListener.update(MigrationProgressListener.ProgressState.FINALIZING, 0.0); - + // rewrite masterkey file with normalized passphrase: masterkeyFileAccess.persist(masterkey, masterkeyFile, Normalizer.normalize(passphrase, Form.NFC), 6); 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 e61c3cab..14e25f43 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java @@ -8,7 +8,8 @@ import org.cryptomator.cryptofs.FileNameTooLongException; import org.cryptomator.cryptofs.common.DeletingFileVisitor; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; -import org.cryptomator.cryptofs.common.MasterkeyBackupHelper; +import org.cryptomator.cryptofs.common.BackupHelper; +import org.cryptomator.cryptofs.migration.Migrators; import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener; import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener.ContinuationEvent; import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener.ContinuationResult; @@ -58,11 +59,11 @@ public Version7Migrator(SecureRandom csprng) { 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); + final Path masterkeyFile = vaultRoot.resolve(masterkeyFilename); 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); + Path masterkeyBackupFile = BackupHelper.attemptBackup(masterkeyFile); LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName()); // check file system capabilities: @@ -76,14 +77,12 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey LOG.warn("Underlying file system only supports names with up to {} chars (required: 220). Asking for user feedback...", filenameLengthLimit); ContinuationResult result = continuationListener.continueMigrationOnEvent(ContinuationEvent.REQUIRES_FULL_VAULT_DIR_SCAN); switch (result) { - case PROCEED: - preMigrationVisitor = new PreMigrationVisitor(vaultRoot, true); - break; - case CANCEL: + case PROCEED -> preMigrationVisitor = new PreMigrationVisitor(vaultRoot, true); + case CANCEL -> { LOG.info("Migration canceled by user."); return; - default: - throw new IllegalStateException("Unexpected result " + result); + } + default -> throw new IllegalStateException("Unexpected result " + result); } } 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 afa56b10..ef3cfcff 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v8/Version8Migrator.java @@ -7,7 +7,7 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; -import org.cryptomator.cryptofs.common.MasterkeyBackupHelper; +import org.cryptomator.cryptofs.common.BackupHelper; import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener; import org.cryptomator.cryptofs.migration.api.MigrationProgressListener; import org.cryptomator.cryptofs.migration.api.Migrator; @@ -56,7 +56,7 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey 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); + Path masterkeyBackupFile = BackupHelper.attemptBackup(masterkeyFile); LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName()); // create vaultconfig.cryptomator diff --git a/src/test/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelperTest.java b/src/test/java/org/cryptomator/cryptofs/common/BackupHelperTest.java similarity index 81% rename from src/test/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelperTest.java rename to src/test/java/org/cryptomator/cryptofs/common/BackupHelperTest.java index 20467f00..119c1a97 100644 --- a/src/test/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelperTest.java +++ b/src/test/java/org/cryptomator/cryptofs/common/BackupHelperTest.java @@ -15,7 +15,7 @@ import java.util.Random; import java.util.stream.Stream; -public class MasterkeyBackupHelperTest { +public class BackupHelperTest { @EnabledOnOs({OS.LINUX, OS.MAC}) @ParameterizedTest @@ -24,11 +24,11 @@ public void testBackupFilePosix(byte[] contents, @TempDir Path tmp) throws IOExc Path originalFile = tmp.resolve("original"); Files.write(originalFile, contents); - Path backupFile = MasterkeyBackupHelper.attemptMasterKeyBackup(originalFile); + Path backupFile = BackupHelper.attemptBackup(originalFile); Assertions.assertArrayEquals(contents, Files.readAllBytes(backupFile)); Files.setPosixFilePermissions(backupFile, PosixFilePermissions.fromString("r--r--r--")); - Path backupFile2 = MasterkeyBackupHelper.attemptMasterKeyBackup(originalFile); + Path backupFile2 = BackupHelper.attemptBackup(originalFile); Assertions.assertEquals(backupFile, backupFile2); } @@ -39,16 +39,16 @@ public void testBackupFileWin(byte[] contents, @TempDir Path tmp) throws IOExcep Path originalFile = tmp.resolve("original"); Files.write(originalFile, contents); - Path backupFile = MasterkeyBackupHelper.attemptMasterKeyBackup(originalFile); + Path backupFile = BackupHelper.attemptBackup(originalFile); Assertions.assertArrayEquals(contents, Files.readAllBytes(backupFile)); Files.getFileAttributeView(backupFile, DosFileAttributeView.class).setReadOnly(true); - Path backupFile2 = MasterkeyBackupHelper.attemptMasterKeyBackup(originalFile); + Path backupFile2 = BackupHelper.attemptBackup(originalFile); Assertions.assertEquals(backupFile, backupFile2); } public static Stream createRandomBytes() { - Random rnd = new Random(42l); + Random rnd = new Random(42L); return Stream.generate(() -> { byte[] bytes = new byte[100]; rnd.nextBytes(bytes); 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 b099554e..0fb3daf3 100644 --- a/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java +++ b/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java @@ -3,7 +3,7 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import org.cryptomator.cryptofs.common.Constants; -import org.cryptomator.cryptofs.common.MasterkeyBackupHelper; +import org.cryptomator.cryptofs.common.BackupHelper; import org.cryptomator.cryptofs.migration.api.Migrator; import org.cryptomator.cryptofs.mocks.NullSecureRandom; import org.cryptomator.cryptolib.api.CryptoException; @@ -58,7 +58,7 @@ public void testMigrate() throws IOException, CryptoException { byte[] beforeMigration = Files.readAllBytes(masterkeyFile); Files.write(masterkeyFile, beforeMigration); - Path masterkeyBackupFile = pathToVault.resolve("masterkey.cryptomator" + MasterkeyBackupHelper.generateFileIdSuffix(beforeMigration) + Constants.MASTERKEY_BACKUP_SUFFIX); + Path masterkeyBackupFile = pathToVault.resolve("masterkey.cryptomator" + BackupHelper.generateFileIdSuffix(beforeMigration) + Constants.BACKUP_SUFFIX); Migrator migrator = new Version6Migrator(csprng); migrator.migrate(pathToVault, null, "masterkey.cryptomator", oldPassword);