Skip to content

Commit

Permalink
Merge branch 'release/2.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
infeo committed Nov 9, 2021
2 parents 9759128 + 5f23672 commit 51bdba5
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 52 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptofs</artifactId>
<version>2.1.1</version>
<version>2.2.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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand All @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}

Expand All @@ -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<byte[]> createRandomBytes() {
Random rnd = new Random(42l);
Random rnd = new Random(42L);
return Stream.generate(() -> {
byte[] bytes = new byte[100];
rnd.nextBytes(bytes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 51bdba5

Please sign in to comment.