diff --git a/pom.xml b/pom.xml index 55bcaa7e..3536385b 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.cryptomator cryptofs - 1.9.12 + 1.9.13 Cryptomator Crypto Filesystem This library provides the Java filesystem provider used by Cryptomator. https://github.com/cryptomator/cryptofs @@ -16,7 +16,7 @@ 1.3.2 2.27 - 29.0-jre + 30.0-jre 1.7.30 5.6.2 diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index f17b8dc8..fb10f3ef 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -372,6 +372,10 @@ private FileChannel newFileChannel(CryptoPath cleartextFilePath, EffectiveOpenOp FileChannel ch = openCryptoFiles.getOrCreate(ciphertextFilePath).newFileChannel(options); // might throw FileAlreadyExists if (options.writable()) { ciphertextPath.persistLongFileName(); + stats.incrementAccessesWritten(); + } + if (options.readable()) { + stats.incrementAccessesRead(); } return ch; } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java index e7412e64..2ccb7f48 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java @@ -40,7 +40,7 @@ public Cryptor provideCryptor(CryptorProvider cryptorProvider, @PathToVault Path byte[] keyFileContents = Files.readAllBytes(masterKeyPath); Cryptor cryptor = cryptorProvider.createFromKeyFile(KeyFile.parse(keyFileContents), properties.passphrase(), properties.pepper(), Constants.VAULT_VERSION); if (!readonlyFlag.isSet()) { - MasterkeyBackupHelper.backupMasterKey(masterKeyPath); + MasterkeyBackupHelper.attemptMasterKeyBackup(masterKeyPath); } return cryptor; } catch (IOException e) { diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemStats.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemStats.java index b83c4776..9c76f5d2 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemStats.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemStats.java @@ -18,9 +18,15 @@ public class CryptoFileSystemStats { private final LongAdder bytesWritten = new LongAdder(); private final LongAdder bytesDecrypted = new LongAdder(); private final LongAdder bytesEncrypted = new LongAdder(); + private final LongAdder totalBytesRead = new LongAdder(); + private final LongAdder totalBytesWritten = new LongAdder(); + private final LongAdder totalBytesDecrypted = new LongAdder(); + private final LongAdder totalBytesEncrypted = new LongAdder(); private final LongAdder chunkCacheAccesses = new LongAdder(); private final LongAdder chunkCacheMisses = new LongAdder(); private final LongAdder chunkCacheHits = new LongAdder(); + private final LongAdder amountOfAccessesRead = new LongAdder(); + private final LongAdder amountOfAccessesWritten = new LongAdder(); @Inject CryptoFileSystemStats() { @@ -30,32 +36,52 @@ public long pollBytesRead() { return bytesRead.sumThenReset(); } + public long pollTotalBytesRead() { + return totalBytesRead.sum(); + } + public void addBytesRead(long numBytes) { bytesRead.add(numBytes); + totalBytesRead.add(numBytes); } public long pollBytesWritten() { return bytesWritten.sumThenReset(); } + public long pollTotalBytesWritten() { + return totalBytesWritten.sum(); + } + public void addBytesWritten(long numBytes) { bytesWritten.add(numBytes); + totalBytesWritten.add(numBytes); } public long pollBytesDecrypted() { return bytesDecrypted.sumThenReset(); } + public long pollTotalBytesDecrypted() { + return totalBytesDecrypted.sum(); + } + public void addBytesDecrypted(long numBytes) { bytesDecrypted.add(numBytes); + totalBytesDecrypted.add(numBytes); } public long pollBytesEncrypted() { return bytesEncrypted.sumThenReset(); } + public long pollTotalBytesEncrypted() { + return totalBytesEncrypted.sum(); + } + public void addBytesEncrypted(long numBytes) { bytesEncrypted.add(numBytes); + totalBytesEncrypted.add(numBytes); } public long pollChunkCacheAccesses() { @@ -80,4 +106,20 @@ public void addChunkCacheMiss() { chunkCacheHits.decrement(); } -} + public long pollAmountOfAccessesRead() { + return amountOfAccessesRead.sum(); + } + + public void incrementAccessesRead() { + amountOfAccessesRead.increment(); + } + + public long pollAmountOfAccessesWritten() { + return amountOfAccessesWritten.sum(); + } + + public void incrementAccessesWritten() { + amountOfAccessesWritten.increment(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelper.java b/src/main/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelper.java index d96283fb..97a1097a 100644 --- a/src/main/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelper.java +++ b/src/main/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelper.java @@ -5,28 +5,22 @@ import org.slf4j.LoggerFactory; import java.io.IOException; -import java.io.InputStream; import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; -import java.nio.channels.SeekableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.file.AccessDeniedException; +import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; /** * Utility class for generating a suffix for the backup file to make it unique to its original master key file. */ public final class MasterkeyBackupHelper { - + private static final Logger LOG = LoggerFactory.getLogger(MasterkeyBackupHelper.class); /** @@ -46,25 +40,27 @@ public static String generateFileIdSuffix(byte[] fileBytes) { } /** - * Do a best-effort attempt to backup the masterkey at the given path. Fail silently if a valid backup already exists. - * + * Do a best-effort attempt to backup the masterkey 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 Any non-recoverable I/O exception that occurs during this attempt + * @throws IOException If the masterkey cannot be read. */ - public static Path backupMasterKey(Path masterKeyPath) throws IOException { + 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); - try (WritableByteChannel ch = Files.newByteChannel(backupFilePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) { + try (WritableByteChannel ch = Files.newByteChannel(backupFilePath, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { ch.write(ByteBuffer.wrap(keyFileContents)); - } catch (AccessDeniedException e) { - LOG.info("Storage device does not allow writing backup file. Comparing masterkey with backup directly."); + } catch (AccessDeniedException | FileAlreadyExistsException e) { assertExistingBackupMatchesContent(backupFilePath, ByteBuffer.wrap(keyFileContents)); + } catch (IOException e) { + LOG.warn("Failed to backup valid masterkey file."); } return backupFilePath; } - - private static void assertExistingBackupMatchesContent(Path backupFilePath, ByteBuffer expectedContent) throws IOException { + + 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); @@ -72,12 +68,14 @@ private static void assertExistingBackupMatchesContent(Path backupFilePath, Byte ch.read(buf); buf.flip(); if (buf.compareTo(expectedContent) != 0) { - throw new IllegalStateException("Corrupt masterkey backup: " + backupFilePath); + 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); } - LOG.debug("Verified backup file: {}", backupFilePath); - } catch (NoSuchFileException e) { - LOG.warn("Did not find backup file: {}", backupFilePath); + } catch (IOException e) { + LOG.warn("Failed to compare valid masterkey with backup file.", e); } } } + } 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 69956dc8..8c39a1c0 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java @@ -8,7 +8,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.text.Normalizer; import java.text.Normalizer.Form; @@ -16,7 +15,6 @@ import javax.inject.Inject; import org.cryptomator.cryptofs.common.MasterkeyBackupHelper; -import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener; import org.cryptomator.cryptofs.migration.api.MigrationProgressListener; import org.cryptomator.cryptofs.migration.api.Migrator; @@ -48,7 +46,7 @@ public void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passp KeyFile keyFile = KeyFile.parse(fileContentsBeforeUpgrade); try (Cryptor cryptor = cryptorProvider.createFromKeyFile(keyFile, passphrase, 5)) { // create backup, as soon as we know the password was correct: - Path masterkeyBackupFile = MasterkeyBackupHelper.backupMasterKey(masterkeyFile); + Path masterkeyBackupFile = MasterkeyBackupHelper.attemptMasterKeyBackup(masterkeyFile); LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName()); progressListener.update(MigrationProgressListener.ProgressState.FINALIZING, 0.0); 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 e681a67f..b4f5bc5b 100644 --- a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java +++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java @@ -6,7 +6,6 @@ package org.cryptomator.cryptofs.migration.v7; import org.cryptomator.cryptofs.FileNameTooLongException; -import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.common.DeletingFileVisitor; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; import org.cryptomator.cryptofs.common.MasterkeyBackupHelper; @@ -28,7 +27,6 @@ import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.util.EnumSet; @@ -52,7 +50,7 @@ public void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passp KeyFile keyFile = KeyFile.parse(fileContentsBeforeUpgrade); try (Cryptor cryptor = cryptorProvider.createFromKeyFile(keyFile, passphrase, 6)) { // create backup, as soon as we know the password was correct: - Path masterkeyBackupFile = MasterkeyBackupHelper.backupMasterKey(masterkeyFile); + Path masterkeyBackupFile = MasterkeyBackupHelper.attemptMasterKeyBackup(masterkeyFile); LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName()); // check file system capabilities: diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemStatsTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemStatsTest.java index 96fbcda5..b3db77aa 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemStatsTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemStatsTest.java @@ -97,4 +97,54 @@ public void testPollChunkCacheMisses() { Assertions.assertEquals(2l, inTest.pollChunkCacheMisses()); } + @Test + public void testPollTotalBytesRead() { + Assertions.assertEquals(0l, inTest.pollTotalBytesRead()); + inTest.addBytesRead(1l); + Assertions.assertEquals(1l, inTest.pollTotalBytesRead()); + inTest.addBytesRead(5l); + Assertions.assertEquals(6l, inTest.pollTotalBytesRead()); + } + + @Test + public void testPollTotalBytesWritten() { + Assertions.assertEquals(0l, inTest.pollTotalBytesWritten()); + inTest.addBytesWritten(1l); + Assertions.assertEquals(1l, inTest.pollTotalBytesWritten()); + inTest.addBytesWritten(5l); + Assertions.assertEquals(6l, inTest.pollTotalBytesWritten()); + } + + @Test + public void testPollTotalBytesDecrypted() { + Assertions.assertEquals(0l, inTest.pollTotalBytesDecrypted()); + inTest.addBytesDecrypted(1l); + Assertions.assertEquals(1l, inTest.pollTotalBytesDecrypted()); + inTest.addBytesDecrypted(5l); + Assertions.assertEquals(6l, inTest.pollTotalBytesDecrypted()); + } + + @Test + public void testPollTotalBytesEncrypted() { + Assertions.assertEquals(0l, inTest.pollTotalBytesEncrypted()); + inTest.addBytesEncrypted(1l); + Assertions.assertEquals(1l, inTest.pollTotalBytesEncrypted()); + inTest.addBytesEncrypted(5l); + Assertions.assertEquals(6l, inTest.pollTotalBytesEncrypted()); + } + + @Test + public void testPollAmountOfFilesRead() { + Assertions.assertEquals(0l, inTest.pollAmountOfAccessesRead()); + inTest.incrementAccessesRead(); + Assertions.assertEquals(1l, inTest.pollAmountOfAccessesRead()); + } + + @Test + public void testPollAmountOfFilesWritten() { + Assertions.assertEquals(0l, inTest.pollAmountOfAccessesWritten()); + inTest.incrementAccessesWritten(); + Assertions.assertEquals(1l, inTest.pollAmountOfAccessesWritten()); + } + } diff --git a/src/test/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelperTest.java b/src/test/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelperTest.java index da51a340..f4b4c15a 100644 --- a/src/test/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelperTest.java +++ b/src/test/java/org/cryptomator/cryptofs/common/MasterkeyBackupHelperTest.java @@ -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.backupMasterKey(originalFile); + Path backupFile = MasterkeyBackupHelper.attemptMasterKeyBackup(originalFile); Assertions.assertArrayEquals(contents, Files.readAllBytes(backupFile)); Files.setPosixFilePermissions(backupFile, PosixFilePermissions.fromString("r--r--r--")); - Path backupFile2 = MasterkeyBackupHelper.backupMasterKey(originalFile); + Path backupFile2 = MasterkeyBackupHelper.attemptMasterKeyBackup(originalFile); Assertions.assertEquals(backupFile, backupFile2); } @@ -39,11 +39,11 @@ public void testBackupFileWin(byte[] contents, @TempDir Path tmp) throws IOExcep Path originalFile = tmp.resolve("original"); Files.write(originalFile, contents); - Path backupFile = MasterkeyBackupHelper.backupMasterKey(originalFile); + Path backupFile = MasterkeyBackupHelper.attemptMasterKeyBackup(originalFile); Assertions.assertArrayEquals(contents, Files.readAllBytes(backupFile)); Files.getFileAttributeView(backupFile, DosFileAttributeView.class).setReadOnly(true); - Path backupFile2 = MasterkeyBackupHelper.backupMasterKey(originalFile); + Path backupFile2 = MasterkeyBackupHelper.attemptMasterKeyBackup(originalFile); Assertions.assertEquals(backupFile, backupFile2); }