diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentCryptor.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentCryptor.java index 0363c4558..0782bf84d 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentCryptor.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentCryptor.java @@ -32,9 +32,10 @@ public interface FileContentCryptor { * @param header The full fixed-length header of an encrypted file. The caller is required to pass the exact amount of bytes returned by {@link #getHeaderSize()}. * @param firstCiphertextByte Position of the first ciphertext byte passed to the decryptor. If the decryptor can not fast-forward to the requested byte, an exception is thrown. * If firstCiphertextByte is an invalid starting point, i.e. doesn't align with the decryptors internal block size, an IllegalArgumentException will be thrown. + * @param authenticate Skip authentication by setting this flag to false. Should be true by default. * @return A possibly new FileContentDecryptor instance which is capable of decrypting ciphertexts associated with the given file header. */ - FileContentDecryptor createFileContentDecryptor(ByteBuffer header, long firstCiphertextByte) throws IllegalArgumentException; + FileContentDecryptor createFileContentDecryptor(ByteBuffer header, long firstCiphertextByte, boolean authenticate) throws IllegalArgumentException; /** * @param header The full fixed-length header of an encrypted file or {@link Optional#empty()}. The caller is required to pass the exact amount of bytes returned by {@link #getHeaderSize()}. diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImpl.java index acb4b456e..db444dfdc 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImpl.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImpl.java @@ -50,14 +50,14 @@ public class FileContentCryptorImpl implements FileContentCryptor { } @Override - public FileContentDecryptor createFileContentDecryptor(ByteBuffer header, long firstCiphertextByte) { + public FileContentDecryptor createFileContentDecryptor(ByteBuffer header, long firstCiphertextByte, boolean authenticate) { if (header.remaining() != getHeaderSize()) { throw new IllegalArgumentException("Invalid header."); } if (firstCiphertextByte % (CHUNK_SIZE + MAC_SIZE) != 0) { throw new IllegalArgumentException("Invalid starting point for decryption."); } - return new FileContentDecryptorImpl(encryptionKey, macKey, header, firstCiphertextByte); + return new FileContentDecryptorImpl(encryptionKey, macKey, header, firstCiphertextByte, authenticate); } @Override diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java index f4b652f0a..f56027187 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java @@ -41,13 +41,15 @@ class FileContentDecryptorImpl implements FileContentDecryptor { private final FifoParallelDataProcessor dataProcessor = new FifoParallelDataProcessor<>(NUM_THREADS, NUM_THREADS + READ_AHEAD); private final ThreadLocal hmacSha256; private final FileHeader header; + private final boolean authenticate; private ByteBuffer ciphertextBuffer = ByteBuffer.allocate(CHUNK_SIZE + MAC_SIZE); private long chunkNumber = 0; - public FileContentDecryptorImpl(SecretKey headerKey, SecretKey macKey, ByteBuffer header, long firstCiphertextByte) { + public FileContentDecryptorImpl(SecretKey headerKey, SecretKey macKey, ByteBuffer header, long firstCiphertextByte, boolean authenticate) { final ThreadLocalMac hmacSha256 = new ThreadLocalMac(macKey, HMAC_SHA256); this.hmacSha256 = hmacSha256; this.header = FileHeader.decrypt(headerKey, hmacSha256, header); + this.authenticate = authenticate; this.chunkNumber = firstCiphertextByte / CHUNK_SIZE; // floor() by int-truncation } @@ -141,10 +143,12 @@ class FileContentDecryptorImpl implements FileContentDecryptor { @Override public ByteBuffer call() { try { - Mac mac = hmacSha256.get(); - mac.update(ciphertextChunk.asReadOnlyBuffer()); - if (!MessageDigest.isEqual(expectedMac, mac.doFinal())) { - throw new AuthenticationFailedException(); + if (authenticate) { + Mac mac = hmacSha256.get(); + mac.update(ciphertextChunk.asReadOnlyBuffer()); + if (!MessageDigest.isEqual(expectedMac, mac.doFinal())) { + throw new AuthenticationFailedException(); + } } Cipher cipher = ThreadLocalAesCtrCipher.get(); diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFile.java index fccf13b0e..746691b09 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFile.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFile.java @@ -40,7 +40,8 @@ public class CryptoFile extends CryptoNode implements File { @Override public ReadableFile openReadable() { - return new CryptoReadableFile(cryptor.getFileContentCryptor(), physicalFile().openReadable()); + boolean authenticate = !fileSystem().delegate().shouldSkipAuthentication(toString()); + return new CryptoReadableFile(cryptor.getFileContentCryptor(), physicalFile().openReadable(), authenticate); } @Override diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java index f6b99033f..52c2a35d6 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java @@ -27,10 +27,12 @@ public class CryptoFileSystem extends CryptoFolder implements FileSystem { private static final String MASTERKEY_BACKUP_FILENAME = "masterkey.cryptomator.bkup"; private final Folder physicalRoot; + private final CryptoFileSystemDelegate delegate; - public CryptoFileSystem(Folder physicalRoot, Cryptor cryptor, CharSequence passphrase) throws InvalidPassphraseException { + public CryptoFileSystem(Folder physicalRoot, Cryptor cryptor, CryptoFileSystemDelegate delegate, CharSequence passphrase) throws InvalidPassphraseException { super(null, "", cryptor); this.physicalRoot = physicalRoot; + this.delegate = delegate; final File masterkeyFile = physicalRoot.file(MASTERKEY_FILENAME); if (masterkeyFile.exists()) { final boolean unlocked = decryptMasterKeyFile(cryptor, masterkeyFile, passphrase); @@ -68,6 +70,10 @@ public class CryptoFileSystem extends CryptoFolder implements FileSystem { } } + CryptoFileSystemDelegate delegate() { + return delegate; + } + @Override protected File physicalFile() { return physicalDataRoot().file(ROOT_DIR_FILE); @@ -107,7 +113,7 @@ public class CryptoFileSystem extends CryptoFolder implements FileSystem { @Override public String toString() { - return physicalRoot + ":::/"; + return "/"; } } diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemDelegate.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemDelegate.java new file mode 100644 index 000000000..36f5d5470 --- /dev/null +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemDelegate.java @@ -0,0 +1,21 @@ +package org.cryptomator.filesystem.crypto; + +public interface CryptoFileSystemDelegate { + + /** + * Reports the path for resources, that could not be decrypted due to authentication errors. + * + * @param cleartextPath Unix-style vault-relative path + */ + void authenticationFailed(String cleartextPath); + + /** + * Allows the delegate to deactivate authentication during decryption. + * This bears the risk of CCAs, thus this method should only return true for data recovery purposes. + * + * @param cleartextPath Unix-style vault-relative path + * @return Must always default to false, except when authentication should be skipped. + */ + boolean shouldSkipAuthentication(String cleartextPath); + +} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemFactory.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemFactory.java index de89639c2..5e732e8c0 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemFactory.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemFactory.java @@ -24,9 +24,9 @@ public class CryptoFileSystemFactory { this.blockAlignedFileSystemFactory = blockAlignedFileSystemFactory; } - public FileSystem get(Folder root, CharSequence passphrase) { + public FileSystem get(Folder root, CharSequence passphrase, CryptoFileSystemDelegate delegate) { final FileSystem nameShorteningFs = shorteningFileSystemFactory.get(root); - final FileSystem cryptoFs = new CryptoFileSystem(nameShorteningFs, cryptorProvider.get(), passphrase); + final FileSystem cryptoFs = new CryptoFileSystem(nameShorteningFs, cryptorProvider.get(), delegate, passphrase); return blockAlignedFileSystemFactory.get(cryptoFs); } } diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java index 0caaa25b3..b0edb13ae 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java @@ -37,6 +37,11 @@ abstract class CryptoNode implements Node { return parent.physicalFolder().file(encryptedName()); } + @Override + public CryptoFileSystem fileSystem() { + return (CryptoFileSystem) Node.super.fileSystem(); + } + @Override public Optional parent() { return Optional.of(parent); @@ -74,4 +79,12 @@ abstract class CryptoNode implements Node { } } + /** + * Unix-style cleartext path rooted at the vault's top-level directory. + * + * @return Vault-relative cleartext path. + */ + @Override + public abstract String toString(); + } diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java index 14a2c7b26..e751f97b2 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java @@ -29,14 +29,16 @@ class CryptoReadableFile implements ReadableFile { private final ByteBuffer header; private final FileContentCryptor cryptor; private final ReadableFile file; + private final boolean authenticate; private FileContentDecryptor decryptor; private Future readAheadTask; private ByteBuffer bufferedCleartext = EMPTY_BUFFER; - public CryptoReadableFile(FileContentCryptor cryptor, ReadableFile file) { + public CryptoReadableFile(FileContentCryptor cryptor, ReadableFile file, boolean authenticate) { this.header = ByteBuffer.allocate(cryptor.getHeaderSize()); this.cryptor = cryptor; this.file = file; + this.authenticate = authenticate; file.position(0); file.read(header); header.flip(); @@ -73,7 +75,7 @@ class CryptoReadableFile implements ReadableFile { bufferedCleartext = EMPTY_BUFFER; } long ciphertextPos = cryptor.toCiphertextPos(position); - decryptor = cryptor.createFileContentDecryptor(header.asReadOnlyBuffer(), ciphertextPos); + decryptor = cryptor.createFileContentDecryptor(header.asReadOnlyBuffer(), ciphertextPos, authenticate); readAheadTask = executorService.submit(new CiphertextReader(file, decryptor, header.remaining() + ciphertextPos)); } diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java index 76a09ea63..7348da451 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java @@ -29,7 +29,7 @@ class NoFileContentCryptor implements FileContentCryptor { } @Override - public FileContentDecryptor createFileContentDecryptor(ByteBuffer header, long firstCiphertextByte) { + public FileContentDecryptor createFileContentDecryptor(ByteBuffer header, long firstCiphertextByte, boolean authenticate) { if (header.remaining() != getHeaderSize()) { throw new IllegalArgumentException("Invalid header size."); } diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImplTest.java index 780e1c760..d8fce4283 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImplTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImplTest.java @@ -53,7 +53,7 @@ public class FileContentCryptorImplTest { FileContentCryptor cryptor = new FileContentCryptorImpl(encryptionKey, macKey, RANDOM_MOCK); ByteBuffer tooShortHeader = ByteBuffer.allocate(63); - cryptor.createFileContentDecryptor(tooShortHeader, 0); + cryptor.createFileContentDecryptor(tooShortHeader, 0, true); } @Test(expected = IllegalArgumentException.class) @@ -75,7 +75,7 @@ public class FileContentCryptorImplTest { FileContentCryptor cryptor = new FileContentCryptorImpl(encryptionKey, macKey, RANDOM_MOCK); ByteBuffer header = ByteBuffer.allocate(cryptor.getHeaderSize()); - cryptor.createFileContentDecryptor(header, 3); + cryptor.createFileContentDecryptor(header, 3, true); } @Test(expected = IllegalArgumentException.class) @@ -110,7 +110,7 @@ public class FileContentCryptorImplTest { ciphertext.flip(); ByteBuffer plaintext = ByteBuffer.allocate(100); - try (FileContentDecryptor decryptor = cryptor.createFileContentDecryptor(header, 0)) { + try (FileContentDecryptor decryptor = cryptor.createFileContentDecryptor(header, 0, true)) { decryptor.append(ciphertext); decryptor.append(FileContentCryptor.EOF); ByteBuffer buf; @@ -163,7 +163,7 @@ public class FileContentCryptorImplTest { final Thread fileReader; final long decStart = System.nanoTime(); - try (FileContentDecryptor decryptor = cryptor.createFileContentDecryptor(header, 0)) { + try (FileContentDecryptor decryptor = cryptor.createFileContentDecryptor(header, 0, true)) { fileReader = new Thread(() -> { try (FileChannel fc = FileChannel.open(tmpFile, StandardOpenOption.READ)) { ByteBuffer ciphertext = ByteBuffer.allocate(654321); diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImplTest.java index b89bdca5e..8abab6a9c 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImplTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImplTest.java @@ -19,6 +19,7 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.bouncycastle.util.encoders.Base64; +import org.cryptomator.crypto.engine.AuthenticationFailedException; import org.cryptomator.crypto.engine.FileContentCryptor; import org.cryptomator.crypto.engine.FileContentDecryptor; import org.cryptomator.crypto.engine.FileContentEncryptor; @@ -47,7 +48,51 @@ public class FileContentDecryptorImplTest { final byte[] header = Base64.decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwN74OFIGKQKgsI7bakfCYm1VXJZiKFLyhZkQCz0Ye/il0PmdZOYsSYEH9h6S00RsdHL3wLtB1FJsb9QLTtP00H8M2theZaZdlKTmjhXsmbc="); final byte[] content = Base64.decode("tPCsFM1g/ubfJMa+AocdPh/WPHfXMFRJdIz6PkLuRijSIIXvxn7IUwVzHQ=="); - try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0)) { + try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0, true)) { + decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 0, 15))); + decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 15, 43))); + decryptor.append(FileContentCryptor.EOF); + + ByteBuffer result = ByteBuffer.allocate(11); // we just care about the first 11 bytes, as this is the ciphertext. + ByteBuffer buf; + while ((buf = decryptor.cleartext()) != FileContentCryptor.EOF) { + ByteBuffers.copy(buf, result); + } + + Assert.assertArrayEquals("hello world".getBytes(), result.array()); + } + } + + @Test(expected = AuthenticationFailedException.class) + public void testManipulatedDecryption() throws InterruptedException { + final byte[] keyBytes = new byte[32]; + final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); + final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); + final byte[] header = Base64.decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwN74OFIGKQKgsI7bakfCYm1VXJZiKFLyhZkQCz0Ye/il0PmdZOYsSYEH9h6S00RsdHL3wLtB1FJsb9QLTtP00H8M2theZaZdlKTmjhXsmbc="); + final byte[] content = Base64.decode("tPCsFM1g/ubfJMa+AocdPh/WPHfXMFRJdIz6PkLuRijSIIXvxn7IUwVzHq=="); + + try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0, true)) { + decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 0, 15))); + decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 15, 43))); + decryptor.append(FileContentCryptor.EOF); + + ByteBuffer result = ByteBuffer.allocate(11); // we just care about the first 11 bytes, as this is the ciphertext. + ByteBuffer buf; + while ((buf = decryptor.cleartext()) != FileContentCryptor.EOF) { + ByteBuffers.copy(buf, result); + } + } + } + + @Test + public void testManipulatedDecryptionWithSuppressedAuthentication() throws InterruptedException { + final byte[] keyBytes = new byte[32]; + final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); + final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); + final byte[] header = Base64.decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwN74OFIGKQKgsI7bakfCYm1VXJZiKFLyhZkQCz0Ye/il0PmdZOYsSYEH9h6S00RsdHL3wLtB1FJsb9QLTtP00H8M2theZaZdlKTmjhXsmbc="); + final byte[] content = Base64.decode("tPCsFM1g/ubfJMa+AocdPh/WPHfXMFRJdIz6PkLuRijSIIXvxn7IUwVzHq=="); + + try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0, false)) { decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 0, 15))); decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 15, 43))); decryptor.append(FileContentCryptor.EOF); @@ -69,7 +114,7 @@ public class FileContentDecryptorImplTest { final SecretKey macKey = new SecretKeySpec(keyBytes, "AES"); final byte[] header = Base64.decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwN74OFIGKQKgsI7bakfCYm1VXJZiKFLyhZkQCz0Ye/il0PmdZOYsSYEH9h6S00RsdHL3wLtB1FJsb9QLTtP00H8M2theZaZdlKTmjhXsmbc="); - try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0)) { + try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0, true)) { decryptor.cancelWithException(new IOException("can not do")); decryptor.cleartext(); } @@ -113,7 +158,7 @@ public class FileContentDecryptorImplTest { for (int i = 3; i >= 0; i--) { final int ciphertextPos = (int) cryptor.toCiphertextPos(i * 32768); - try (FileContentDecryptor decryptor = cryptor.createFileContentDecryptor(header, ciphertextPos)) { + try (FileContentDecryptor decryptor = cryptor.createFileContentDecryptor(header, ciphertextPos, true)) { final Thread ciphertextReader = new Thread(() -> { try { ciphertext.position(ciphertextPos); diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemComponentIntegrationTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemComponentIntegrationTest.java index e549a2de1..5911797d1 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemComponentIntegrationTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemComponentIntegrationTest.java @@ -15,6 +15,7 @@ import org.cryptomator.filesystem.inmem.InMemoryFileSystem; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,13 +25,15 @@ public class CryptoFileSystemComponentIntegrationTest { private static final Logger LOG = LoggerFactory.getLogger(CryptoFileSystemComponentIntegrationTest.class); + private CryptoFileSystemDelegate cryptoDelegate; private FileSystem ciphertextFs; private FileSystem cleartextFs; @Before public void setupFileSystems() { + cryptoDelegate = Mockito.mock(CryptoFileSystemDelegate.class); ciphertextFs = new InMemoryFileSystem(); - cleartextFs = cryptoFsComp.cryptoFileSystemFactory().get(ciphertextFs, "TopSecret"); + cleartextFs = cryptoFsComp.cryptoFileSystemFactory().get(ciphertextFs, "TopSecret", cryptoDelegate); cleartextFs.create(); } @@ -77,7 +80,38 @@ public class CryptoFileSystemComponentIntegrationTest { } } - @Test(timeout = 2000000) // assuming a minimum speed of 10mb/s during encryption and decryption 20s should be enough + @Test + public void testForcedDecryptionOfManipulatedFile() { + // write test content to encrypted file + try (WritableFile writable = cleartextFs.file("test1.txt").openWritable()) { + writable.write(ByteBuffer.wrap("Hello World".getBytes())); + } + + File physicalFile = ciphertextFs.folder("d").folders().findAny().get().folders().findAny().get().files().findAny().get(); + Assert.assertTrue(physicalFile.exists()); + + // toggle last bit + try (WritableFile writable = physicalFile.openWritable(); ReadableFile readable = physicalFile.openReadable()) { + ByteBuffer buf = ByteBuffer.allocate((int) readable.size()); + readable.read(buf); + buf.array()[buf.limit() - 1] ^= 0x01; + buf.flip(); + writable.write(buf); + } + + // whitelist + Mockito.when(cryptoDelegate.shouldSkipAuthentication("/test1.txt")).thenReturn(true); + + // read test content from decrypted file + try (ReadableFile readable = cleartextFs.file("test1.txt").openReadable()) { + ByteBuffer buf = ByteBuffer.allocate(11); + readable.read(buf); + buf.flip(); + Assert.assertArrayEquals("Hello World".getBytes(), buf.array()); + } + } + + @Test(timeout = 20000) // assuming a minimum speed of 10mb/s during encryption and decryption 20s should be enough public void testEncryptionAndDecryptionSpeed() throws InterruptedException, IOException { File file = cleartextFs.file("benchmark.test"); diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTest.java index c23c60680..52536e637 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTest.java @@ -27,6 +27,7 @@ import org.cryptomator.filesystem.WritableFile; import org.cryptomator.filesystem.inmem.InMemoryFileSystem; import org.junit.Assert; import org.junit.Test; +import org.mockito.Mockito; public class CryptoFileSystemTest { @@ -45,7 +46,7 @@ public class CryptoFileSystemTest { Assert.assertFalse(physicalDataRoot.exists()); // init crypto fs: - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, "foo"); + final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); Assert.assertTrue(masterkeyFile.exists()); Assert.assertTrue(masterkeyBkupFile.exists()); fs.create(); @@ -66,7 +67,7 @@ public class CryptoFileSystemTest { Assert.assertFalse(masterkeyBkupFile.exists()); // first initialization: - new CryptoFileSystem(physicalFs, cryptor, "foo"); + new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); Assert.assertTrue(masterkeyBkupFile.exists()); final Instant bkupDateT0 = masterkeyBkupFile.lastModified(); @@ -75,7 +76,7 @@ public class CryptoFileSystemTest { Thread.sleep(1); // second initialization: - new CryptoFileSystem(physicalFs, cryptor, "foo"); + new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); Assert.assertTrue(masterkeyBkupFile.exists()); final Instant bkupDateT1 = masterkeyBkupFile.lastModified(); @@ -88,7 +89,7 @@ public class CryptoFileSystemTest { final Cryptor cryptor = new NoCryptor(); final FileSystem physicalFs = new InMemoryFileSystem(); final Folder physicalDataRoot = physicalFs.folder("d"); - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, "foo"); + final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); fs.create(); // add another encrypted folder: @@ -108,7 +109,7 @@ public class CryptoFileSystemTest { // mock stuff and prepare crypto FS: final Cryptor cryptor = new NoCryptor(); final FileSystem physicalFs = new InMemoryFileSystem(); - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, "foo"); + final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); fs.create(); // create foo/bar/ and then move foo/ to baz/: @@ -131,7 +132,7 @@ public class CryptoFileSystemTest { // mock stuff and prepare crypto FS: final Cryptor cryptor = new NoCryptor(); final FileSystem physicalFs = new InMemoryFileSystem(); - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, "foo"); + final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); fs.create(); // create foo/bar/ and then try to move foo/bar/ to foo/ @@ -141,12 +142,12 @@ public class CryptoFileSystemTest { fooBarFolder.moveTo(fooFolder); } - @Test(timeout = 10000000) + @Test(timeout = 10000) public void testWriteAndReadEncryptedFile() { // mock stuff and prepare crypto FS: final Cryptor cryptor = new NoCryptor(); final FileSystem physicalFs = new InMemoryFileSystem(); - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, "foo"); + final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); fs.create(); // write test content to file diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoReadableFileTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoReadableFileTest.java index 94ddba031..86ce080c4 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoReadableFileTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoReadableFileTest.java @@ -30,7 +30,7 @@ public class CryptoReadableFileTest { }).thenThrow(new UncheckedIOException(new IOException("failed."))); @SuppressWarnings("resource") - ReadableFile cryptoReadableFile = new CryptoReadableFile(fileContentCryptor, underlyingFile); + ReadableFile cryptoReadableFile = new CryptoReadableFile(fileContentCryptor, underlyingFile, true); cryptoReadableFile.read(ByteBuffer.allocate(1)); } diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java index f4a3db6bd..7dd4cc36e 100644 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java +++ b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java @@ -13,9 +13,11 @@ import java.util.List; import org.cryptomator.crypto.engine.impl.CryptorImpl; import org.cryptomator.filesystem.FileSystem; import org.cryptomator.filesystem.crypto.CryptoFileSystem; +import org.cryptomator.filesystem.crypto.CryptoFileSystemDelegate; import org.cryptomator.filesystem.inmem.InMemoryFileSystem; import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory; import org.cryptomator.filesystem.nio.NioFileSystem; +import org.mockito.Mockito; class FileSystemFactories implements Iterable { @@ -48,11 +50,11 @@ class FileSystemFactories implements Iterable { } private FileSystem createCryptoFileSystemInMemory() { - return new CryptoFileSystem(createInMemoryFileSystem(), new CryptorImpl(RANDOM_MOCK), "aPassphrase"); + return new CryptoFileSystem(createInMemoryFileSystem(), new CryptorImpl(RANDOM_MOCK), Mockito.mock(CryptoFileSystemDelegate.class), "aPassphrase"); } private FileSystem createCryptoFileSystemNio() { - return new CryptoFileSystem(createNioFileSystem(), new CryptorImpl(RANDOM_MOCK), "aPassphrase"); + return new CryptoFileSystem(createNioFileSystem(), new CryptorImpl(RANDOM_MOCK), Mockito.mock(CryptoFileSystemDelegate.class), "aPassphrase"); } private void add(String name, FileSystemFactory factory) {