diff --git a/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java b/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java index a648fcf5b..01cd5f876 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java +++ b/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java @@ -86,7 +86,7 @@ public final class WebDavServer { * _ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 * @return servlet */ - public ServletLifeCycleAdapter createServlet(final Path workDir, final boolean checkFileIntegrity, final Cryptor cryptor, String name) { + public ServletLifeCycleAdapter createServlet(final Path workDir, final Cryptor cryptor, String name) { try { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("name empty"); @@ -97,7 +97,7 @@ public final class WebDavServer { final URI uri = new URI(null, null, localConnector.getHost(), localConnector.getLocalPort(), "/" + UUID.randomUUID().toString() + "/" + name, null, null); final ServletContextHandler servletContext = new ServletContextHandler(servletCollection, uri.getRawPath(), ServletContextHandler.SESSIONS); - final ServletHolder servlet = getWebDavServletHolder(workDir.toString(), checkFileIntegrity, cryptor); + final ServletHolder servlet = getWebDavServletHolder(workDir.toString(), cryptor); servletContext.addServlet(servlet, "/*"); servletCollection.mapContexts(); @@ -109,10 +109,9 @@ public final class WebDavServer { } } - private ServletHolder getWebDavServletHolder(final String workDir, final boolean checkFileIntegrity, final Cryptor cryptor) { + private ServletHolder getWebDavServletHolder(final String workDir, final Cryptor cryptor) { final ServletHolder result = new ServletHolder("Cryptomator-WebDAV-Servlet", new WebDavServlet(cryptor)); result.setInitParameter(WebDavServlet.CFG_FS_ROOT, workDir); - result.setInitParameter(WebDavServlet.CFG_CHECK_FILE_INTEGRITY, Boolean.toString(checkFileIntegrity)); return result; } diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavResourceFactoryImpl.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavResourceFactoryImpl.java index f8f0ddb0e..d37fd41d8 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavResourceFactoryImpl.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavResourceFactoryImpl.java @@ -34,11 +34,9 @@ class DavResourceFactoryImpl implements DavResourceFactory { private final LockManager lockManager = new SimpleLockManager(); private final Cryptor cryptor; - private final boolean checkFileIntegrity; - DavResourceFactoryImpl(Cryptor cryptor, boolean checkFileIntegrity) { + DavResourceFactoryImpl(Cryptor cryptor) { this.cryptor = cryptor; - this.checkFileIntegrity = checkFileIntegrity; } @Override @@ -72,11 +70,11 @@ class DavResourceFactoryImpl implements DavResourceFactory { } private EncryptedFile createFilePart(DavResourceLocator locator, DavSession session, DavServletRequest request) { - return new EncryptedFilePart(this, locator, session, request, lockManager, cryptor, checkFileIntegrity); + return new EncryptedFilePart(this, locator, session, request, lockManager, cryptor); } private EncryptedFile createFile(DavResourceLocator locator, DavSession session) { - return new EncryptedFile(this, locator, session, lockManager, cryptor, checkFileIntegrity); + return new EncryptedFile(this, locator, session, lockManager, cryptor); } private EncryptedDir createDirectory(DavResourceLocator locator, DavSession session) { diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java index 1f8ec6cb7..4379216f9 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java @@ -23,7 +23,6 @@ public class WebDavServlet extends AbstractWebdavServlet { private static final long serialVersionUID = 7965170007048673022L; public static final String CFG_FS_ROOT = "cfg.fs.root"; - public static final String CFG_CHECK_FILE_INTEGRITY = "cfg.checkFileIntegrity"; private DavSessionProvider davSessionProvider; private DavLocatorFactory davLocatorFactory; private DavResourceFactory davResourceFactory; @@ -41,10 +40,9 @@ public class WebDavServlet extends AbstractWebdavServlet { davSessionProvider = new DavSessionProviderImpl(); final String fsRoot = config.getInitParameter(CFG_FS_ROOT); - final boolean checkFileIntegrity = Boolean.parseBoolean(config.getInitParameter(CFG_CHECK_FILE_INTEGRITY)); this.davLocatorFactory = new DavLocatorFactoryImpl(fsRoot, cryptor); - this.davResourceFactory = new DavResourceFactoryImpl(cryptor, checkFileIntegrity); + this.davResourceFactory = new DavResourceFactoryImpl(cryptor); } @Override diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java index 2d7590fbf..83d017a8f 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java @@ -40,11 +40,8 @@ public class EncryptedFile extends AbstractEncryptedNode { private static final Logger LOG = LoggerFactory.getLogger(EncryptedFile.class); - protected final boolean checkIntegrity; - - public EncryptedFile(DavResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, boolean checkIntegrity) { + public EncryptedFile(DavResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) { super(factory, locator, session, lockManager, cryptor); - this.checkIntegrity = checkIntegrity; } @Override @@ -76,9 +73,6 @@ public class EncryptedFile extends AbstractEncryptedNode { SeekableByteChannel channel = null; try { channel = Files.newByteChannel(path, StandardOpenOption.READ); - if (checkIntegrity && !cryptor.authenticateContent(channel)) { - throw new DecryptFailedException("File content compromised: " + path.toString()); - } outputContext.setContentLength(cryptor.decryptedContentLength(channel)); if (outputContext.hasStream()) { cryptor.decryptedFile(channel, outputContext.getOutputStream()); diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java index 9253f3f7d..c257b39fb 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java @@ -50,8 +50,8 @@ public class EncryptedFilePart extends EncryptedFile { private final Set> requestedContentRanges = new HashSet>(); - public EncryptedFilePart(DavResourceFactory factory, DavResourceLocator locator, DavSession session, DavServletRequest request, LockManager lockManager, Cryptor cryptor, boolean checkIntegrity) { - super(factory, locator, session, lockManager, cryptor, checkIntegrity); + public EncryptedFilePart(DavResourceFactory factory, DavResourceLocator locator, DavSession session, DavServletRequest request, LockManager lockManager, Cryptor cryptor) { + super(factory, locator, session, lockManager, cryptor); final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString()); if (rangeHeader == null) { throw new IllegalArgumentException("HTTP request doesn't contain a range header"); @@ -116,9 +116,6 @@ public class EncryptedFilePart extends EncryptedFile { SeekableByteChannel channel = null; try { channel = Files.newByteChannel(path, StandardOpenOption.READ); - if (checkIntegrity && !cryptor.authenticateContent(channel)) { - throw new DecryptFailedException("File content compromised: " + path.toString()); - } final Long fileSize = cryptor.decryptedContentLength(channel); final Pair range = getUnionRange(fileSize); final Long rangeLength = range.getRight() - range.getLeft() + 1; diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java index fe4f6eb72..cc632d720 100644 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java +++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java @@ -293,7 +293,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo final String[] cleartextPathComps = StringUtils.split(cleartextPath, cleartextPathSep); final List encryptedPathComps = new ArrayList<>(cleartextPathComps.length); for (final String cleartext : cleartextPathComps) { - final String encrypted = encryptPathComponent(cleartext, primaryMasterKey, ioSupport); + final String encrypted = encryptPathComponent(cleartext, primaryMasterKey, hMacMasterKey, ioSupport); encryptedPathComps.add(encrypted); } return StringUtils.join(encryptedPathComps, encryptedPathSep); @@ -317,11 +317,11 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo * These alternative names consist of the checksum, a unique id and a special file extension defined in * {@link FileNamingConventions#LONG_NAME_FILE_EXT}. */ - private String encryptPathComponent(final String cleartext, final SecretKey key, CryptorIOSupport ioSupport) throws IOException, InvalidKeyException { + private String encryptPathComponent(final String cleartext, final SecretKey aesKey, final SecretKey macKey, CryptorIOSupport ioSupport) throws IOException, InvalidKeyException { final byte[] cleartextBytes = cleartext.getBytes(StandardCharsets.UTF_8); // encrypt: - final byte[] encryptedBytes = AesSivCipherUtil.sivEncrypt(key.getEncoded(), cleartextBytes); + final byte[] encryptedBytes = AesSivCipherUtil.sivEncrypt(aesKey, macKey, cleartextBytes); final String ivAndCiphertext = ENCRYPTED_FILENAME_CODEC.encodeAsString(encryptedBytes); if (ivAndCiphertext.length() + BASIC_FILE_EXT.length() > ENCRYPTED_FILENAME_LENGTH_LIMIT) { @@ -342,7 +342,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo final String[] encryptedPathComps = StringUtils.split(encryptedPath, encryptedPathSep); final List cleartextPathComps = new ArrayList<>(encryptedPathComps.length); for (final String encrypted : encryptedPathComps) { - final String cleartext = decryptPathComponent(encrypted, primaryMasterKey, ioSupport); + final String cleartext = decryptPathComponent(encrypted, primaryMasterKey, hMacMasterKey, ioSupport); cleartextPathComps.add(new String(cleartext)); } return StringUtils.join(cleartextPathComps, cleartextPathSep); @@ -354,7 +354,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo /** * @see #encryptPathComponent(String, SecretKey, CryptorIOSupport) */ - private String decryptPathComponent(final String encrypted, final SecretKey key, CryptorIOSupport ioSupport) throws IOException, InvalidKeyException, DecryptFailedException { + private String decryptPathComponent(final String encrypted, final SecretKey aesKey, final SecretKey macKey, CryptorIOSupport ioSupport) throws IOException, InvalidKeyException, DecryptFailedException { final String ciphertext; if (encrypted.endsWith(LONG_NAME_FILE_EXT)) { final String basename = StringUtils.removeEnd(encrypted, LONG_NAME_FILE_EXT); @@ -371,7 +371,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo // decrypt: final byte[] encryptedBytes = ENCRYPTED_FILENAME_CODEC.decode(ciphertext); - final byte[] cleartextBytes = AesSivCipherUtil.sivDecrypt(key.getEncoded(), encryptedBytes); + final byte[] cleartextBytes = AesSivCipherUtil.sivDecrypt(aesKey, macKey, encryptedBytes); return new String(cleartextBytes, StandardCharsets.UTF_8); } @@ -389,8 +389,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo ioSupport.writePathSpecificMetadata(metadataFile, objectMapper.writeValueAsBytes(metadata)); } - @Override - public boolean authenticateContent(SeekableByteChannel encryptedFile) throws IOException { + private void authenticateContent(SeekableByteChannel encryptedFile) throws IOException, DecryptFailedException { // init mac: final Mac calculatedMac = this.hmacSha256(hMacMasterKey); @@ -411,7 +410,11 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo IOUtils.copyLarge(macIn, new NullOutputStream()); // compare (in constant time): - return MessageDigest.isEqual(storedMac.array(), calculatedMac.doFinal()); + boolean macMatches = MessageDigest.isEqual(storedMac.array(), calculatedMac.doFinal()); + + if (!macMatches) { + throw new DecryptFailedException("MAC authentication failed."); + } } @Override @@ -440,7 +443,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo } @Override - public Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException { + public Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException { // read iv: encryptedFile.position(0); final ByteBuffer countingIv = ByteBuffer.allocate(AES_BLOCK_LENGTH); @@ -454,6 +457,9 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo throw new IOException("Failed to read file header."); } + // check MAC: + this.authenticateContent(encryptedFile); + // go to begin of content: encryptedFile.position(64); @@ -467,7 +473,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo } @Override - public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length) throws IOException { + public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length) throws IOException, DecryptFailedException { // read iv: encryptedFile.position(0); final ByteBuffer countingIv = ByteBuffer.allocate(AES_BLOCK_LENGTH); @@ -478,6 +484,9 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo throw new IOException("Failed to read file header."); } + // check MAC: + this.authenticateContent(encryptedFile); + // seek relevant position and update iv: long firstRelevantBlock = pos / AES_BLOCK_LENGTH; // cut of fraction! long beginOfFirstRelevantBlock = firstRelevantBlock * AES_BLOCK_LENGTH; diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesSivCipherUtil.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesSivCipherUtil.java index 76ab79ec6..4a4d9b3f1 100644 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesSivCipherUtil.java +++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesSivCipherUtil.java @@ -13,6 +13,8 @@ import java.security.InvalidKeyException; import java.security.MessageDigest; import java.util.Arrays; +import javax.crypto.SecretKey; + import org.apache.commons.lang3.ArrayUtils; import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.CipherParameters; @@ -31,15 +33,26 @@ final class AesSivCipherUtil { private static final byte[] BYTES_ZERO = new byte[16]; private static final byte DOUBLING_CONST = (byte) 0x87; - static byte[] sivEncrypt(byte[] key, byte[] plaintext, byte[]... additionalData) throws InvalidKeyException { - if (key.length != 32 && key.length != 48 && key.length != 64) { - throw new InvalidKeyException("Invalid key length " + key.length); + static byte[] sivEncrypt(SecretKey aesKey, SecretKey macKey, byte[] plaintext, byte[]... additionalData) throws InvalidKeyException { + final byte[] aesKeyBytes = aesKey.getEncoded(); + final byte[] macKeyBytes = macKey.getEncoded(); + if (aesKeyBytes == null || macKeyBytes == null) { + throw new IllegalArgumentException("Can't get bytes of given key."); + } + try { + return sivEncrypt(aesKeyBytes, macKeyBytes, plaintext, additionalData); + } finally { + Arrays.fill(aesKeyBytes, (byte) 0); + Arrays.fill(macKeyBytes, (byte) 0); + } + } + + static byte[] sivEncrypt(byte[] aesKey, byte[] macKey, byte[] plaintext, byte[]... additionalData) throws InvalidKeyException { + if (aesKey.length != 16 && aesKey.length != 24 && aesKey.length != 32) { + throw new InvalidKeyException("Invalid aesKey length " + aesKey.length); } - final byte[] k1 = Arrays.copyOf(key, key.length / 2); - final byte[] k2 = Arrays.copyOfRange(key, k1.length, key.length); - - final byte[] iv = s2v(k1, plaintext, additionalData); + final byte[] iv = s2v(macKey, plaintext, additionalData); final int numBlocks = (plaintext.length + 15) / 16; @@ -52,7 +65,7 @@ final class AesSivCipherUtil { final byte[] x = new byte[numBlocks * 16]; final BlockCipher aes = new AESFastEngine(); - aes.init(true, new KeyParameter(k2)); + aes.init(true, new KeyParameter(aesKey)); for (int i = 0; i < numBlocks; i++) { final long ctrVal = initialCtrVal + i; ctrBuf.putLong(8, ctrVal); @@ -65,13 +78,24 @@ final class AesSivCipherUtil { return ArrayUtils.addAll(iv, ciphertext); } - static byte[] sivDecrypt(byte[] key, byte[] ciphertext, byte[]... additionalData) throws DecryptFailedException, InvalidKeyException { - if (key.length != 32 && key.length != 48 && key.length != 64) { - throw new InvalidKeyException("Invalid key length " + key.length); + static byte[] sivDecrypt(SecretKey aesKey, SecretKey macKey, byte[] plaintext, byte[]... additionalData) throws InvalidKeyException, DecryptFailedException { + final byte[] aesKeyBytes = aesKey.getEncoded(); + final byte[] macKeyBytes = macKey.getEncoded(); + if (aesKeyBytes == null || macKeyBytes == null) { + throw new IllegalArgumentException("Can't get bytes of given key."); } + try { + return sivDecrypt(aesKeyBytes, macKeyBytes, plaintext, additionalData); + } finally { + Arrays.fill(aesKeyBytes, (byte) 0); + Arrays.fill(macKeyBytes, (byte) 0); + } + } - final byte[] k1 = Arrays.copyOf(key, key.length / 2); - final byte[] k2 = Arrays.copyOfRange(key, k1.length, key.length); + static byte[] sivDecrypt(byte[] aesKey, byte[] macKey, byte[] ciphertext, byte[]... additionalData) throws DecryptFailedException, InvalidKeyException { + if (aesKey.length != 16 && aesKey.length != 24 && aesKey.length != 32) { + throw new InvalidKeyException("Invalid aesKey length " + aesKey.length); + } final byte[] iv = Arrays.copyOf(ciphertext, 16); @@ -87,7 +111,7 @@ final class AesSivCipherUtil { final byte[] x = new byte[numBlocks * 16]; final BlockCipher aes = new AESFastEngine(); - aes.init(true, new KeyParameter(k2)); + aes.init(true, new KeyParameter(aesKey)); for (int i = 0; i < numBlocks; i++) { final long ctrVal = initialCtrVal + i; ctrBuf.putLong(8, ctrVal); @@ -97,7 +121,7 @@ final class AesSivCipherUtil { final byte[] plaintext = xor(actualCiphertext, x); - final byte[] control = s2v(k1, plaintext, additionalData); + final byte[] control = s2v(macKey, plaintext, additionalData); if (MessageDigest.isEqual(control, iv)) { return plaintext; @@ -106,8 +130,8 @@ final class AesSivCipherUtil { } } - static byte[] s2v(byte[] key, byte[] plaintext, byte[]... additionalData) { - final CipherParameters params = new KeyParameter(key); + static byte[] s2v(byte[] macKey, byte[] plaintext, byte[]... additionalData) { + final CipherParameters params = new KeyParameter(macKey); final BlockCipher aes = new AESFastEngine(); final CMac mac = new CMac(aes); mac.init(params); diff --git a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java index 151af42dc..668250e80 100644 --- a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java +++ b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java @@ -14,26 +14,12 @@ import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Security; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Random; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - import org.apache.commons.io.IOUtils; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.cryptomator.crypto.CryptorIOSupport; import org.cryptomator.crypto.exceptions.DecryptFailedException; import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException; @@ -86,8 +72,8 @@ public class Aes256CryptorTest { } } - @Test - public void testIntegrityAuthentication() throws IOException { + @Test(expected = DecryptFailedException.class) + public void testIntegrityAuthentication() throws IOException, DecryptFailedException { // our test plaintext data: final byte[] plaintextData = "Hello World".getBytes(); final InputStream plaintextIn = new ByteArrayInputStream(plaintextData); @@ -104,11 +90,6 @@ public class Aes256CryptorTest { encryptedData.position(0); - // authenticate unmodified content: - final SeekableByteChannel encryptedIn1 = new ByteBufferBackedSeekableChannel(encryptedData); - final boolean isContentUnmodified1 = cryptor.authenticateContent(encryptedIn1); - Assert.assertTrue(isContentUnmodified1); - // toggle one bit inf first content byte: encryptedData.position(64); final byte fifthByte = encryptedData.get(); @@ -117,30 +98,10 @@ public class Aes256CryptorTest { encryptedData.position(0); - // authenticate modified content: - final SeekableByteChannel encryptedIn2 = new ByteBufferBackedSeekableChannel(encryptedData); - final boolean isContentUnmodified2 = cryptor.authenticateContent(encryptedIn2); - Assert.assertFalse(isContentUnmodified2); - } - - @Test - public void foo() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException { - Security.addProvider(new BouncyCastleProvider()); - - final byte[] iv = new byte[16]; - final byte[] keyBytes = new byte[16]; - final SecretKey key = new SecretKeySpec(keyBytes, "AES"); - final Cipher pkcs5PaddedCipher = Cipher.getInstance("AES/CTR/PKCS5Padding", BouncyCastleProvider.PROVIDER_NAME); - pkcs5PaddedCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); - final Cipher unpaddedCipher = Cipher.getInstance("AES/CTR/NoPadding"); - unpaddedCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); - - // test data: - final byte[] plaintextData = "Hello World".getBytes(); - final byte[] pkcs5PaddedCiphertext = pkcs5PaddedCipher.doFinal(plaintextData); - final byte[] unpaddedCiphertext = unpaddedCipher.doFinal(plaintextData); - - Assert.assertFalse(Arrays.equals(pkcs5PaddedCiphertext, unpaddedCiphertext)); + // decrypt modified content (should fail with DecryptFailedException): + final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData); + final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream(); + cryptor.decryptedFile(encryptedIn, plaintextOut); } @Test diff --git a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/AesSivCipherUtilTest.java b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/AesSivCipherUtilTest.java index 1e355fee5..f263e2f85 100644 --- a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/AesSivCipherUtilTest.java +++ b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/AesSivCipherUtilTest.java @@ -14,7 +14,7 @@ public class AesSivCipherUtilTest { @Test public void testS2v() throws DecoderException { - final byte[] key = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, // + final byte[] macKey = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, // (byte) 0xfb, (byte) 0xfa, (byte) 0xf9, (byte) 0xf8, // (byte) 0xf7, (byte) 0xf6, (byte) 0xf5, (byte) 0xf4, // (byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0}; @@ -36,17 +36,18 @@ public class AesSivCipherUtilTest { (byte) 0x95, (byte) 0x0a, (byte) 0xcd, (byte) 0x32, // (byte) 0x0a, (byte) 0x2e, (byte) 0xcc, (byte) 0x93}; - final byte[] result = AesSivCipherUtil.s2v(key, plaintext, ad); + final byte[] result = AesSivCipherUtil.s2v(macKey, plaintext, ad); Assert.assertArrayEquals(expected, result); } @Test public void testSivEncrypt() throws InvalidKeyException { - final byte[] key = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, // + final byte[] macKey = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, // (byte) 0xfb, (byte) 0xfa, (byte) 0xf9, (byte) 0xf8, // (byte) 0xf7, (byte) 0xf6, (byte) 0xf5, (byte) 0xf4, // - (byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0, // - (byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, // + (byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0}; + + final byte[] aesKey = {(byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, // (byte) 0xf4, (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, // (byte) 0xf8, (byte) 0xf9, (byte) 0xfa, (byte) 0xfb, // (byte) 0xfc, (byte) 0xfd, (byte) 0xfe, (byte) 0xff}; @@ -72,17 +73,18 @@ public class AesSivCipherUtilTest { (byte) 0xda, (byte) 0xef, (byte) 0x7f, (byte) 0x6a, // (byte) 0xfe, (byte) 0x5c}; - final byte[] result = AesSivCipherUtil.sivEncrypt(key, plaintext, ad); + final byte[] result = AesSivCipherUtil.sivEncrypt(aesKey, macKey, plaintext, ad); Assert.assertArrayEquals(expected, result); } @Test public void testSivDecrypt() throws DecryptFailedException, InvalidKeyException { - final byte[] key = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, // + final byte[] macKey = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, // (byte) 0xfb, (byte) 0xfa, (byte) 0xf9, (byte) 0xf8, // (byte) 0xf7, (byte) 0xf6, (byte) 0xf5, (byte) 0xf4, // - (byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0, // - (byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, // + (byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0}; + + final byte[] aesKey = {(byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, // (byte) 0xf4, (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, // (byte) 0xf8, (byte) 0xf9, (byte) 0xfa, (byte) 0xfb, // (byte) 0xfc, (byte) 0xfd, (byte) 0xfe, (byte) 0xff}; @@ -108,17 +110,18 @@ public class AesSivCipherUtilTest { (byte) 0x99, (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, // (byte) 0xdd, (byte) 0xee}; - final byte[] result = AesSivCipherUtil.sivDecrypt(key, ciphertext, ad); + final byte[] result = AesSivCipherUtil.sivDecrypt(aesKey, macKey, ciphertext, ad); Assert.assertArrayEquals(expected, result); } @Test(expected = DecryptFailedException.class) public void testSivDecryptWithInvalidKey() throws DecryptFailedException, InvalidKeyException { - final byte[] key = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, // + final byte[] macKey = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, // (byte) 0xfb, (byte) 0xfa, (byte) 0xf9, (byte) 0xf8, // (byte) 0xf7, (byte) 0xf6, (byte) 0xf5, (byte) 0xf4, // - (byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0, // - (byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, // + (byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0}; + + final byte[] aesKey = {(byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, // (byte) 0xf4, (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, // (byte) 0xf8, (byte) 0xf9, (byte) 0xfa, (byte) 0xfb, // (byte) 0xfc, (byte) 0xfd, (byte) 0xfe, (byte) 0x00}; @@ -144,7 +147,7 @@ public class AesSivCipherUtilTest { (byte) 0x99, (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, // (byte) 0xdd, (byte) 0xee}; - final byte[] result = AesSivCipherUtil.sivDecrypt(key, ciphertext, ad); + final byte[] result = AesSivCipherUtil.sivDecrypt(aesKey, macKey, ciphertext, ad); Assert.assertArrayEquals(expected, result); } @@ -153,12 +156,12 @@ public class AesSivCipherUtilTest { */ @Test public void testNonceBasedAuthenticatedEncryption() throws InvalidKeyException { - - final byte[] key = {(byte) 0x7f, (byte) 0x7e, (byte) 0x7d, (byte) 0x7c, // + final byte[] macKey = {(byte) 0x7f, (byte) 0x7e, (byte) 0x7d, (byte) 0x7c, // (byte) 0x7b, (byte) 0x7a, (byte) 0x79, (byte) 0x78, // (byte) 0x77, (byte) 0x76, (byte) 0x75, (byte) 0x74, // - (byte) 0x73, (byte) 0x72, (byte) 0x71, (byte) 0x70, // - (byte) 0x40, (byte) 0x41, (byte) 0x42, (byte) 0x43, // + (byte) 0x73, (byte) 0x72, (byte) 0x71, (byte) 0x70}; + + final byte[] aesKey = {(byte) 0x40, (byte) 0x41, (byte) 0x42, (byte) 0x43, // (byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, // (byte) 0x48, (byte) 0x49, (byte) 0x4a, (byte) 0x4b, // (byte) 0x4c, (byte) 0x4d, (byte) 0x4e, (byte) 0x4f}; @@ -196,7 +199,7 @@ public class AesSivCipherUtilTest { (byte) 0x53, (byte) 0x49, (byte) 0x56, (byte) 0x2d, // (byte) 0x41, (byte) 0x45, (byte) 0x53}; - final byte[] result = AesSivCipherUtil.sivEncrypt(key, plaintext, ad1, ad2, nonce); + final byte[] result = AesSivCipherUtil.sivEncrypt(aesKey, macKey, plaintext, ad1, ad2, nonce); final byte[] expected = {(byte) 0x7b, (byte) 0xdb, (byte) 0x6e, (byte) 0x3b, // (byte) 0x43, (byte) 0x26, (byte) 0x67, (byte) 0xeb, // diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java index 96c8b9918..2183c8048 100644 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java +++ b/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java @@ -69,11 +69,6 @@ public interface Cryptor extends SensitiveDataSwipeListener { */ String decryptPath(String encryptedPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport) throws DecryptFailedException; - /** - * @return true If the integrity of the file can be assured. - */ - boolean authenticateContent(SeekableByteChannel encryptedFile) throws IOException; - /** * @param metadataSupport Support object allowing the Cryptor to read and write its own metadata to the location of the encrypted file. * @return Content length of the decrypted file or null if unknown. diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingDecorator.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingDecorator.java index bf0d33b19..e160838ab 100644 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingDecorator.java +++ b/main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingDecorator.java @@ -76,11 +76,6 @@ public class SamplingDecorator implements Cryptor, CryptorIOSampling { return cryptor.decryptPath(encryptedPath, encryptedPathSep, cleartextPathSep, ioSupport); } - @Override - public boolean authenticateContent(SeekableByteChannel encryptedFile) throws IOException { - return cryptor.authenticateContent(encryptedFile); - } - @Override public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException { return cryptor.decryptedContentLength(encryptedFile); diff --git a/main/ui/src/main/java/org/cryptomator/ui/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/UnlockController.java index 242c4a5e8..0c44636e1 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/UnlockController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/UnlockController.java @@ -26,7 +26,6 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; -import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; @@ -63,9 +62,6 @@ public class UnlockController implements Initializable { @FXML private SecPasswordField passwordField; - @FXML - private CheckBox checkIntegrity; - @FXML private TextField mountName; @@ -127,7 +123,6 @@ public class UnlockController implements Initializable { try { progressIndicator.setVisible(true); masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ); - directory.setVerifyFileIntegrity(checkIntegrity.isSelected()); directory.getCryptor().decryptMasterKey(masterKeyInputStream, password); if (!directory.startServer(server, closer)) { messageLabel.setText(rb.getString("unlock.messageLabel.startServerFailed")); @@ -166,7 +161,6 @@ public class UnlockController implements Initializable { private void setControlsDisabled(boolean disable) { usernameBox.setDisable(disable); passwordField.setDisable(disable); - checkIntegrity.setDisable(disable); unlockButton.setDisable(disable); } @@ -216,7 +210,6 @@ public class UnlockController implements Initializable { public void setDirectory(Vault directory) { this.directory = directory; this.findExistingUsernames(); - this.checkIntegrity.setSelected(directory.shouldVerifyFileIntegrity()); this.mountName.setText(directory.getMountName()); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java index 9b8543108..c688b6b0a 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java @@ -41,7 +41,6 @@ public class Vault implements Serializable { private final Cryptor cryptor = SamplingDecorator.decorate(new Aes256Cryptor()); private final ObjectProperty unlocked = new SimpleObjectProperty(this, "unlocked", Boolean.FALSE); private final Path path; - private boolean verifyFileIntegrity; private String mountName; private DeferredClosable webDavServlet = DeferredClosable.empty(); private DeferredClosable webDavMount = DeferredClosable.empty(); @@ -68,7 +67,7 @@ public class Vault implements Serializable { if (o.isPresent() && o.get().isRunning()) { return false; } - ServletLifeCycleAdapter servlet = server.createServlet(path, verifyFileIntegrity, cryptor, getMountName()); + ServletLifeCycleAdapter servlet = server.createServlet(path, cryptor, getMountName()); if (servlet.start()) { webDavServlet = closer.closeLater(servlet, ServletLifeCycleAdapter::stop); return true; @@ -106,14 +105,6 @@ public class Vault implements Serializable { return path; } - public boolean shouldVerifyFileIntegrity() { - return verifyFileIntegrity; - } - - public void setVerifyFileIntegrity(boolean verifyFileIntegrity) { - this.verifyFileIntegrity = verifyFileIntegrity; - } - /** * @return Directory name without preceeding path components and file extension */ diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultDeserializer.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultDeserializer.java index a9134d506..890a08310 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultDeserializer.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultDeserializer.java @@ -18,8 +18,6 @@ public class VaultDeserializer extends JsonDeserializer { final String pathStr = node.get("path").asText(); final Path path = FileSystems.getDefault().getPath(pathStr); final Vault dir = new Vault(path); - final boolean verifyFileIntegrity = node.has("checkIntegrity") ? node.get("checkIntegrity").asBoolean() : false; - dir.setVerifyFileIntegrity(verifyFileIntegrity); if (node.has("mountName")) { dir.setMountName(node.get("mountName").asText()); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultSerializer.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultSerializer.java index f413d48ff..c77ceb367 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultSerializer.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultSerializer.java @@ -13,7 +13,6 @@ public class VaultSerializer extends JsonSerializer { public void serialize(Vault value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { jgen.writeStartObject(); jgen.writeStringField("path", value.getPath().toString()); - jgen.writeBooleanField("checkIntegrity", value.shouldVerifyFileIntegrity()); jgen.writeStringField("mountName", value.getMountName().toString()); jgen.writeEndObject(); } diff --git a/main/ui/src/main/resources/fxml/unlock.fxml b/main/ui/src/main/resources/fxml/unlock.fxml index 6019be0df..264fdb8c0 100644 --- a/main/ui/src/main/resources/fxml/unlock.fxml +++ b/main/ui/src/main/resources/fxml/unlock.fxml @@ -39,21 +39,17 @@ -