From 318e574b515ecc147de409e487ec9c687cf227a8 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 20 Aug 2021 12:45:43 +0200 Subject: [PATCH 01/30] preparing 2.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 71b7a5c..6dbf118 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.cryptomator cryptolib - 2.1.0-SNAPSHOT + 2.1.0 Cryptomator Crypto Library This library contains all cryptographic functions that are used by Cryptomator. https://github.com/cryptomator/cryptolib From 0d2a6da9f8ff3d3785f3a7df6c7f32d6c7c61c14 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 3 Dec 2021 13:50:44 +0100 Subject: [PATCH 02/30] create object pool --- .../cryptolib/common/ObjectPool.java | 48 +++++++++++++++ .../cryptolib/common/ObjectPoolTest.java | 61 +++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/main/java/org/cryptomator/cryptolib/common/ObjectPool.java create mode 100644 src/test/java/org/cryptomator/cryptolib/common/ObjectPoolTest.java diff --git a/src/main/java/org/cryptomator/cryptolib/common/ObjectPool.java b/src/main/java/org/cryptomator/cryptolib/common/ObjectPool.java new file mode 100644 index 0000000..730a3f7 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptolib/common/ObjectPool.java @@ -0,0 +1,48 @@ +package org.cryptomator.cryptolib.common; + +import java.lang.ref.WeakReference; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.Supplier; + +public class ObjectPool { + + private final Queue> pool; + private final Supplier factory; + + public ObjectPool(Supplier factory) { + this.pool = new ConcurrentLinkedQueue<>(); + this.factory = factory; + } + + public Lease get() { + WeakReference ref; + while ((ref = pool.poll()) != null) { + T cached = ref.get(); + if (cached != null) { + return new Lease(cached); + } + } + return new Lease(factory.get()); + } + + public class Lease implements AutoCloseable { + + private T obj; + + public Lease(T obj) { + this.obj = obj; + } + + public T get() { + return obj; + } + + @Override + public void close() { + pool.offer(new WeakReference<>(obj)); + obj = null; + } + } + +} diff --git a/src/test/java/org/cryptomator/cryptolib/common/ObjectPoolTest.java b/src/test/java/org/cryptomator/cryptolib/common/ObjectPoolTest.java new file mode 100644 index 0000000..2838d76 --- /dev/null +++ b/src/test/java/org/cryptomator/cryptolib/common/ObjectPoolTest.java @@ -0,0 +1,61 @@ +package org.cryptomator.cryptolib.common; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.function.Supplier; + +public class ObjectPoolTest { + + private Supplier factory = Mockito.mock(Supplier.class); + private ObjectPool pool = new ObjectPool<>(factory); + + @BeforeEach + public void setup() { + Mockito.doAnswer(invocation -> new Foo()).when(factory).get(); + } + + @Test + @DisplayName("new instance is created if pool is empty") + public void testCreateNewObjWhenPoolIsEmpty() { + try (ObjectPool.Lease lease1 = pool.get()) { + try (ObjectPool.Lease lease2 = pool.get()) { + Assertions.assertNotSame(lease1.get(), lease2.get()); + } + } + Mockito.verify(factory, Mockito.times(2)).get(); + } + + @Test + @DisplayName("recycle existing instance") + public void testRecycleExistingObj() { + Foo foo1; + try (ObjectPool.Lease lease = pool.get()) { + foo1 = lease.get(); + } + try (ObjectPool.Lease lease = pool.get()) { + Assertions.assertSame(foo1, lease.get()); + } + Mockito.verify(factory, Mockito.times(1)).get(); + } + + @Test + @DisplayName("create new instance when pool is GC'ed") + public void testGc() { + try (ObjectPool.Lease lease = pool.get()) { + Assertions.assertNotNull(lease.get()); + } + System.gc(); // seems to be reliable on Temurin 17 with @RepeatedTest(1000) + try (ObjectPool.Lease lease = pool.get()) { + Assertions.assertNotNull(lease.get()); + } + Mockito.verify(factory, Mockito.times(2)).get(); + } + + private static class Foo { + } + +} \ No newline at end of file From 05554f6b0a5326a5f10479b49e24b6ca55386dee Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 3 Dec 2021 14:28:29 +0100 Subject: [PATCH 03/30] use object pool in CipherSupplier --- .../cryptolib/common/CipherSupplier.java | 89 ++++++++++++++----- .../cryptolib/common/CipherSupplierTest.java | 4 +- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java b/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java index edbc13e..45e1875 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java +++ b/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java @@ -15,7 +15,6 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.AlgorithmParameterSpec; -import java.util.function.Function; public final class CipherSupplier { @@ -24,51 +23,101 @@ public final class CipherSupplier { public static final CipherSupplier RFC3394_KEYWRAP = new CipherSupplier("AESWrap"); private final String cipherAlgorithm; - private final ThreadLocal threadLocal; + private final ObjectPool cipherPool; public CipherSupplier(String cipherAlgorithm) { this.cipherAlgorithm = cipherAlgorithm; - this.threadLocal = new Provider(); - this.threadLocal.get(); // eagerly initialize to provoke exceptions + this.cipherPool = new ObjectPool<>(this::createCipher); + try (ObjectPool.Lease lease = cipherPool.get()) { + lease.get(); // eagerly initialize to provoke exceptions + } } - private class Provider extends ThreadLocal { - @Override - protected Cipher initialValue() { - try { - return Cipher.getInstance(cipherAlgorithm); - } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new IllegalArgumentException("Invalid cipher algorithm or padding.", e); - } + private Cipher createCipher() { + try { + return Cipher.getInstance(cipherAlgorithm); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new IllegalArgumentException("Invalid cipher algorithm or padding.", e); } } + public ReusableCipher encrypt(SecretKey key, AlgorithmParameterSpec params) { + ObjectPool.Lease lease = cipherPool.get(); + initMode(lease.get(), Cipher.ENCRYPT_MODE, key, params); + return new ReusableCipher(lease); + } + + @Deprecated public Cipher forEncryption(SecretKey key, AlgorithmParameterSpec params) { - return forMode(Cipher.ENCRYPT_MODE, key, params); + final Cipher cipher = createCipher(); + initMode(cipher, Cipher.ENCRYPT_MODE, key, params); + return cipher; } + public ReusableCipher decrypt(SecretKey key, AlgorithmParameterSpec params) { + ObjectPool.Lease lease = cipherPool.get(); + initMode(lease.get(), Cipher.DECRYPT_MODE, key, params); + return new ReusableCipher(lease); + } + + @Deprecated public Cipher forDecryption(SecretKey key, AlgorithmParameterSpec params) { - return forMode(Cipher.DECRYPT_MODE, key, params); + final Cipher cipher = createCipher(); + initMode(cipher, Cipher.DECRYPT_MODE, key, params); + return cipher; + } + + public ReusableCipher wrap(SecretKey kek) { + ObjectPool.Lease lease = cipherPool.get(); + initMode(lease.get(), Cipher.WRAP_MODE, kek, null); + return new ReusableCipher(lease); } + @Deprecated public Cipher forWrapping(SecretKey kek) { - return forMode(Cipher.WRAP_MODE, kek, null); + final Cipher cipher = createCipher(); + initMode(cipher, Cipher.WRAP_MODE, kek, null); + return cipher; + } + + public ReusableCipher unwrap(SecretKey kek) { + ObjectPool.Lease lease = cipherPool.get(); + initMode(lease.get(), Cipher.UNWRAP_MODE, kek, null); + return new ReusableCipher(lease); } + @Deprecated public Cipher forUnwrapping(SecretKey kek) { - return forMode(Cipher.UNWRAP_MODE, kek, null); + final Cipher cipher = createCipher(); + initMode(cipher, Cipher.UNWRAP_MODE, kek, null); + return cipher; } - // visible for testing - Cipher forMode(int ciphermode, SecretKey key, AlgorithmParameterSpec params) { - final Cipher cipher = threadLocal.get(); + private void initMode(Cipher cipher, int ciphermode, SecretKey key, AlgorithmParameterSpec params) { try { cipher.init(ciphermode, key, params); - return cipher; } catch (InvalidKeyException e) { throw new IllegalArgumentException("Invalid key.", e); } catch (InvalidAlgorithmParameterException e) { throw new IllegalArgumentException("Algorithm parameter not appropriate for " + cipher.getAlgorithm() + ".", e); } } + + public static class ReusableCipher implements AutoCloseable { + + private final ObjectPool.Lease lease; + + private ReusableCipher(ObjectPool.Lease lease) { + this.lease = lease; + } + + public Cipher get() { + return lease.get(); + } + + @Override + public void close() { + lease.close(); + } + } } \ No newline at end of file diff --git a/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java b/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java index 03a97eb..4142649 100644 --- a/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java +++ b/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java @@ -31,7 +31,7 @@ public void testGetUnknownCipher() { public void testGetCipherWithInvalidKey() { CipherSupplier supplier = new CipherSupplier("AES/CBC/PKCS5Padding"); IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> { - supplier.forMode(Cipher.ENCRYPT_MODE, new DestroyableSecretKey(new byte[13], "AES"), new IvParameterSpec(new byte[16])); + supplier.forEncryption(new DestroyableSecretKey(new byte[13], "AES"), new IvParameterSpec(new byte[16])); }); MatcherAssert.assertThat(exception.getMessage(), CoreMatchers.containsString("Invalid key")); } @@ -40,7 +40,7 @@ public void testGetCipherWithInvalidKey() { public void testGetCipherWithInvalidAlgorithmParam() { CipherSupplier supplier = new CipherSupplier("AES/CBC/PKCS5Padding"); IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> { - supplier.forMode(Cipher.ENCRYPT_MODE, new DestroyableSecretKey(new byte[16], "AES"), new RC5ParameterSpec(1, 1, 8)); + supplier.forEncryption(new DestroyableSecretKey(new byte[16], "AES"), new RC5ParameterSpec(1, 1, 8)); }); MatcherAssert.assertThat(exception.getMessage(), CoreMatchers.containsString("Algorithm parameter not appropriate for")); } From 94d629a4592e122be7c7e145003d396da26e44a4 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 3 Dec 2021 14:28:40 +0100 Subject: [PATCH 04/30] use new CipherSupplier API --- .../cryptolib/common/AesKeyWrap.java | 12 ++++---- .../cryptolib/ecies/GcmWithSecretNonce.java | 11 +++++--- .../cryptolib/v1/FileContentCryptorImpl.java | 17 ++++++----- .../cryptolib/v1/FileHeaderCryptorImpl.java | 17 +++++------ .../cryptolib/v2/FileContentCryptorImpl.java | 28 ++++++++++--------- .../cryptolib/v2/FileHeaderCryptorImpl.java | 15 +++++----- .../cryptolib/common/CipherSupplierTest.java | 4 +-- .../ecies/GcmWithSecretNonceTest.java | 5 +++- .../v2/FileHeaderCryptorImplTest.java | 7 ++++- 9 files changed, 67 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java b/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java index 0e9bcd9..619dd49 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java +++ b/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java @@ -22,9 +22,9 @@ public class AesKeyWrap { * @return Wrapped key */ public static byte[] wrap(DestroyableSecretKey kek, SecretKey key) { - try (DestroyableSecretKey kekCopy = kek.copy()) { - final Cipher cipher = CipherSupplier.RFC3394_KEYWRAP.forWrapping(kekCopy); - return cipher.wrap(key); + try (DestroyableSecretKey kekCopy = kek.copy(); + CipherSupplier.ReusableCipher cipher = CipherSupplier.RFC3394_KEYWRAP.wrap(kekCopy)) { + return cipher.get().wrap(key); } catch (InvalidKeyException | IllegalBlockSizeException e) { throw new IllegalArgumentException("Unable to wrap key.", e); } @@ -43,9 +43,9 @@ public static DestroyableSecretKey unwrap(DestroyableSecretKey kek, byte[] wrapp // visible for testing static DestroyableSecretKey unwrap(DestroyableSecretKey kek, byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType) throws InvalidKeyException { - try (DestroyableSecretKey kekCopy = kek.copy()) { - final Cipher cipher = CipherSupplier.RFC3394_KEYWRAP.forUnwrapping(kekCopy); - return DestroyableSecretKey.from(cipher.unwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType)); + try (DestroyableSecretKey kekCopy = kek.copy(); + CipherSupplier.ReusableCipher cipher = CipherSupplier.RFC3394_KEYWRAP.unwrap(kekCopy)) { + return DestroyableSecretKey.from(cipher.get().unwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType)); } catch (NoSuchAlgorithmException e) { throw new IllegalArgumentException("Invalid algorithm: " + wrappedKeyAlgorithm, e); } diff --git a/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java b/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java index 891ef83..3b053c1 100644 --- a/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java +++ b/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java @@ -9,6 +9,7 @@ import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.IvParameterSpec; import java.util.Arrays; class GcmWithSecretNonce implements AuthenticatedEncryption { @@ -26,8 +27,9 @@ public int requiredSecretBytes() { public byte[] encrypt(byte[] secret, byte[] plaintext) { try (DestroyableSecretKey key = new DestroyableSecretKey(secret, 0, GCM_KEY_SIZE, "AES")) { byte[] nonce = Arrays.copyOfRange(secret, GCM_KEY_SIZE, GCM_KEY_SIZE + GCM_NONCE_SIZE); - Cipher cipher = CipherSupplier.AES_GCM.forEncryption(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce)); - return cipher.doFinal(plaintext); + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.encrypt(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + return cipher.get().doFinal(plaintext); + } } catch (IllegalBlockSizeException | BadPaddingException e) { throw new IllegalStateException("Unexpected exception during GCM decryption.", e); } @@ -37,8 +39,9 @@ public byte[] encrypt(byte[] secret, byte[] plaintext) { public byte[] decrypt(byte[] secret, byte[] ciphertext) throws AEADBadTagException { try (DestroyableSecretKey key = new DestroyableSecretKey(secret, 0, GCM_KEY_SIZE, "AES")) { byte[] nonce = Arrays.copyOfRange(secret, GCM_KEY_SIZE, GCM_KEY_SIZE + GCM_NONCE_SIZE); - Cipher cipher = CipherSupplier.AES_GCM.forDecryption(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce)); - return cipher.doFinal(ciphertext); + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.decrypt(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + return cipher.get().doFinal(ciphertext); + } } catch (IllegalBlockSizeException | BadPaddingException e) { Throwables.throwIfInstanceOf(e, AEADBadTagException.class); throw new IllegalStateException("Unexpected exception during GCM decryption.", e); diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java index cad0d0b..ee024ef 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java @@ -107,12 +107,14 @@ void encryptChunk(ByteBuffer cleartextChunk, ByteBuffer ciphertextChunk, long ch // nonce: byte[] nonce = new byte[NONCE_SIZE]; random.nextBytes(nonce); + ciphertextChunk.put(nonce); // payload: - final Cipher cipher = CipherSupplier.AES_CTR.forEncryption(fk, new IvParameterSpec(nonce)); - ciphertextChunk.put(nonce); - assert ciphertextChunk.remaining() >= cipher.getOutputSize(cleartextChunk.remaining()) + MAC_SIZE; - int bytesEncrypted = cipher.doFinal(cleartextChunk, ciphertextChunk); + int bytesEncrypted; + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_CTR.encrypt(fk, new IvParameterSpec(nonce))) { + assert ciphertextChunk.remaining() >= cipher.get().getOutputSize(cleartextChunk.remaining()) + MAC_SIZE; + bytesEncrypted = cipher.get().doFinal(cleartextChunk, ciphertextChunk); + } // mac: final ByteBuffer ciphertextBuf = ciphertextChunk.asReadOnlyBuffer(); @@ -143,9 +145,10 @@ void decryptChunk(ByteBuffer ciphertextChunk, ByteBuffer cleartextChunk, Destroy payloadBuf.position(NONCE_SIZE).limit(ciphertextChunk.limit() - MAC_SIZE); // payload: - final Cipher cipher = CipherSupplier.AES_CTR.forDecryption(fk, new IvParameterSpec(nonce)); - assert cleartextChunk.remaining() >= cipher.getOutputSize(payloadBuf.remaining()); - cipher.doFinal(payloadBuf, cleartextChunk); + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_CTR.encrypt(fk, new IvParameterSpec(nonce))) { + assert cleartextChunk.remaining() >= cipher.get().getOutputSize(payloadBuf.remaining()); + cipher.get().doFinal(payloadBuf, cleartextChunk); + } } catch (ShortBufferException e) { throw new IllegalStateException("Buffer allocated for reported output size apparently not big enough.", e); } catch (IllegalBlockSizeException | BadPaddingException e) { diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java index aeaa7ee..6064188 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java @@ -17,7 +17,6 @@ import org.cryptomator.cryptolib.common.MacSupplier; import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.Mac; import javax.crypto.ShortBufferException; @@ -61,9 +60,10 @@ public ByteBuffer encryptHeader(FileHeader header) { result.put(headerImpl.getNonce()); // encrypt payload: - Cipher cipher = CipherSupplier.AES_CTR.forEncryption(ek, new IvParameterSpec(headerImpl.getNonce())); - int encrypted = cipher.doFinal(payloadCleartextBuf, result); - assert encrypted == FileHeaderImpl.Payload.SIZE; + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_CTR.encrypt(ek, new IvParameterSpec(headerImpl.getNonce()))) { + int encrypted = cipher.get().doFinal(payloadCleartextBuf, result); + assert encrypted == FileHeaderImpl.Payload.SIZE; + } // mac nonce and ciphertext: ByteBuffer nonceAndCiphertextBuf = result.duplicate(); @@ -114,10 +114,11 @@ public FileHeader decryptHeader(ByteBuffer ciphertextHeaderBuf) throws Authentic ByteBuffer payloadCleartextBuf = ByteBuffer.allocate(FileHeaderImpl.Payload.SIZE); try (DestroyableSecretKey ek = masterkey.getEncKey()) { // decrypt payload: - Cipher cipher = CipherSupplier.AES_CTR.forDecryption(ek, new IvParameterSpec(nonce)); - assert cipher.getOutputSize(ciphertextPayload.length) == payloadCleartextBuf.remaining(); - int decrypted = cipher.doFinal(ByteBuffer.wrap(ciphertextPayload), payloadCleartextBuf); - assert decrypted == FileHeaderImpl.Payload.SIZE; + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_CTR.encrypt(ek, new IvParameterSpec(nonce))) { + assert cipher.get().getOutputSize(ciphertextPayload.length) == payloadCleartextBuf.remaining(); + int decrypted = cipher.get().doFinal(ByteBuffer.wrap(ciphertextPayload), payloadCleartextBuf); + assert decrypted == FileHeaderImpl.Payload.SIZE; + } payloadCleartextBuf.flip(); FileHeaderImpl.Payload payload = FileHeaderImpl.Payload.decode(payloadCleartextBuf); diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java index a03eea9..5e7673f 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java @@ -103,13 +103,14 @@ void encryptChunk(ByteBuffer cleartextChunk, ByteBuffer ciphertextChunk, long ch random.nextBytes(nonce); // payload: - final Cipher cipher = CipherSupplier.AES_GCM.forEncryption(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce)); - final byte[] chunkNumberBigEndian = longToBigEndianByteArray(chunkNumber); - cipher.updateAAD(chunkNumberBigEndian); - cipher.updateAAD(headerNonce); - ciphertextChunk.put(nonce); - assert ciphertextChunk.remaining() >= cipher.getOutputSize(cleartextChunk.remaining()); - cipher.doFinal(cleartextChunk, ciphertextChunk); + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.encrypt(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + final byte[] chunkNumberBigEndian = longToBigEndianByteArray(chunkNumber); + cipher.get().updateAAD(chunkNumberBigEndian); + cipher.get().updateAAD(headerNonce); + ciphertextChunk.put(nonce); + assert ciphertextChunk.remaining() >= cipher.get().getOutputSize(cleartextChunk.remaining()); + cipher.get().doFinal(cleartextChunk, ciphertextChunk); + } } catch (ShortBufferException e) { throw new IllegalStateException("Buffer allocated for reported output size apparently not big enough.", e); } catch (IllegalBlockSizeException | BadPaddingException e) { @@ -134,12 +135,13 @@ void decryptChunk(ByteBuffer ciphertextChunk, ByteBuffer cleartextChunk, long ch assert payloadBuf.remaining() >= GCM_TAG_SIZE; // payload: - final Cipher cipher = CipherSupplier.AES_GCM.forDecryption(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce)); - final byte[] chunkNumberBigEndian = longToBigEndianByteArray(chunkNumber); - cipher.updateAAD(chunkNumberBigEndian); - cipher.updateAAD(headerNonce); - assert cleartextChunk.remaining() >= cipher.getOutputSize(payloadBuf.remaining()); - cipher.doFinal(payloadBuf, cleartextChunk); + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.decrypt(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + final byte[] chunkNumberBigEndian = longToBigEndianByteArray(chunkNumber); + cipher.get().updateAAD(chunkNumberBigEndian); + cipher.get().updateAAD(headerNonce); + assert cleartextChunk.remaining() >= cipher.get().getOutputSize(payloadBuf.remaining()); + cipher.get().doFinal(payloadBuf, cleartextChunk); + } } catch (AEADBadTagException e) { throw new AuthenticationFailedException("Content tag mismatch.", e); } catch (ShortBufferException e) { diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java index 509a5b9..c2a5ae5 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java @@ -61,10 +61,10 @@ public ByteBuffer encryptHeader(FileHeader header) { result.put(headerImpl.getNonce()); // encrypt payload: - Cipher cipher = CipherSupplier.AES_GCM.forEncryption(ek, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, headerImpl.getNonce())); - int encrypted = cipher.doFinal(payloadCleartextBuf, result); - assert encrypted == FileHeaderImpl.PAYLOAD_LEN + FileHeaderImpl.TAG_LEN; - + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.encrypt(ek, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, headerImpl.getNonce()))) { + int encrypted = cipher.get().doFinal(payloadCleartextBuf, result); + assert encrypted == FileHeaderImpl.PAYLOAD_LEN + FileHeaderImpl.TAG_LEN; + } result.flip(); return result; } catch (ShortBufferException e) { @@ -93,9 +93,10 @@ public FileHeader decryptHeader(ByteBuffer ciphertextHeaderBuf) throws Authentic ByteBuffer payloadCleartextBuf = ByteBuffer.allocate(FileHeaderImpl.Payload.SIZE + GCM_TAG_SIZE); try (DestroyableSecretKey ek = masterkey.getEncKey()) { // decrypt payload: - Cipher cipher = CipherSupplier.AES_GCM.forDecryption(ek, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce)); - int decrypted = cipher.doFinal(ByteBuffer.wrap(ciphertextAndTag), payloadCleartextBuf); - assert decrypted == FileHeaderImpl.Payload.SIZE; + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.decrypt(ek, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + int decrypted = cipher.get().doFinal(ByteBuffer.wrap(ciphertextAndTag), payloadCleartextBuf); + assert decrypted == FileHeaderImpl.Payload.SIZE; + } payloadCleartextBuf.flip(); FileHeaderImpl.Payload payload = FileHeaderImpl.Payload.decode(payloadCleartextBuf); diff --git a/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java b/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java index 4142649..0c62435 100644 --- a/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java +++ b/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java @@ -31,7 +31,7 @@ public void testGetUnknownCipher() { public void testGetCipherWithInvalidKey() { CipherSupplier supplier = new CipherSupplier("AES/CBC/PKCS5Padding"); IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> { - supplier.forEncryption(new DestroyableSecretKey(new byte[13], "AES"), new IvParameterSpec(new byte[16])); + supplier.encrypt(new DestroyableSecretKey(new byte[13], "AES"), new IvParameterSpec(new byte[16])); }); MatcherAssert.assertThat(exception.getMessage(), CoreMatchers.containsString("Invalid key")); } @@ -40,7 +40,7 @@ public void testGetCipherWithInvalidKey() { public void testGetCipherWithInvalidAlgorithmParam() { CipherSupplier supplier = new CipherSupplier("AES/CBC/PKCS5Padding"); IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> { - supplier.forEncryption(new DestroyableSecretKey(new byte[16], "AES"), new RC5ParameterSpec(1, 1, 8)); + supplier.encrypt(new DestroyableSecretKey(new byte[16], "AES"), new RC5ParameterSpec(1, 1, 8)); }); MatcherAssert.assertThat(exception.getMessage(), CoreMatchers.containsString("Algorithm parameter not appropriate for")); } diff --git a/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java b/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java index 91ed77a..6873e2b 100644 --- a/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java +++ b/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.params.provider.CsvSource; import javax.crypto.AEADBadTagException; +import javax.crypto.spec.GCMParameterSpec; /** * Test vectors from https://csrc.nist.rip/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf @@ -22,7 +23,9 @@ public class GcmWithSecretNonceTest { public void setup() { // reset cipher state to avoid InvalidAlgorithmParameterExceptions due to IV-reuse GcmTestHelper.reset((mode, key, params) -> { - CipherSupplier.AES_GCM.forEncryption(key, params); + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.encrypt(key, params)) { + cipher.get(); + } }); } diff --git a/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java b/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java index 1cfc50f..fd4796c 100644 --- a/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java +++ b/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java @@ -19,9 +19,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import javax.crypto.spec.GCMParameterSpec; import java.nio.ByteBuffer; import java.security.SecureRandom; +import static org.cryptomator.cryptolib.v2.Constants.GCM_TAG_SIZE; + public class FileHeaderCryptorImplTest { private static final SecureRandom RANDOM_MOCK = SecureRandomMock.NULL_RANDOM; @@ -35,7 +38,9 @@ public void setup() { // reset cipher state to avoid InvalidAlgorithmParameterExceptions due to IV-reuse GcmTestHelper.reset((mode, key, params) -> { - CipherSupplier.AES_GCM.forEncryption(key, params); + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.encrypt(key, params)) { + cipher.get(); + } }); } From bf462146d6a770446b493e310a22c0e943ae5e2b Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 3 Dec 2021 14:44:23 +0100 Subject: [PATCH 05/30] use object pool in MacSupplier --- .../cryptolib/common/MacSupplier.java | 59 ++++++++++++++----- .../cryptolib/common/MacSupplierTest.java | 21 ++++--- 2 files changed, 54 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java b/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java index f58514b..54615e9 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java +++ b/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java @@ -8,43 +8,72 @@ *******************************************************************************/ package org.cryptomator.cryptolib.common; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - +import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.SecretKey; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; public final class MacSupplier { public static final MacSupplier HMAC_SHA256 = new MacSupplier("HmacSHA256"); private final String macAlgorithm; - private final ThreadLocal threadLocal; + private final ObjectPool macPool; public MacSupplier(String macAlgorithm) { this.macAlgorithm = macAlgorithm; - this.threadLocal = new Provider(); + this.macPool = new ObjectPool<>(this::createMac); + try (ObjectPool.Lease lease = macPool.get()) { + lease.get(); // eagerly initialize to provoke exceptions + } } - private class Provider extends ThreadLocal { - @Override - protected Mac initialValue() { - try { - return Mac.getInstance(macAlgorithm); - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("Invalid MAC algorithm.", e); - } + private Mac createMac() { + try { + return Mac.getInstance(macAlgorithm); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Invalid MAC algorithm.", e); } } + public ReusableMac keyed(SecretKey key) { + ObjectPool.Lease lease = macPool.get(); + init(lease.get(), key); + return new ReusableMac(lease); + } + + @Deprecated public Mac withKey(SecretKey key) { + final Mac mac = createMac(); + init(mac, key); + return mac; + } + + private void init(Mac mac, SecretKey key) { try { - final Mac mac = threadLocal.get(); mac.init(key); - return mac; } catch (InvalidKeyException e) { throw new IllegalArgumentException("Invalid key.", e); } } + public static class ReusableMac implements AutoCloseable { + + private final ObjectPool.Lease lease; + + private ReusableMac(ObjectPool.Lease lease) { + this.lease = lease; + } + + public Mac get() { + return lease.get(); + } + + @Override + public void close() { + lease.close(); + } + } + } \ No newline at end of file diff --git a/src/test/java/org/cryptomator/cryptolib/common/MacSupplierTest.java b/src/test/java/org/cryptomator/cryptolib/common/MacSupplierTest.java index 9a3458d..4aefda4 100644 --- a/src/test/java/org/cryptomator/cryptolib/common/MacSupplierTest.java +++ b/src/test/java/org/cryptomator/cryptolib/common/MacSupplierTest.java @@ -11,11 +11,9 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.lang.reflect.Method; -import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; @@ -24,27 +22,28 @@ public class MacSupplierTest { @Test public void testConstructorWithInvalidDigest() { - SecretKey key = new SecretKeySpec(new byte[16], "HmacSHA256"); Assertions.assertThrows(IllegalArgumentException.class, () -> { - new MacSupplier("FOO3000").withKey(key); + new MacSupplier("FOO3000"); }); } @Test - public void testGetMac() throws InvalidKeyException, NoSuchAlgorithmException { + public void testGetMac() { SecretKey key = new SecretKeySpec(new byte[16], "HmacSHA256"); - Mac mac1 = MacSupplier.HMAC_SHA256.withKey(key); - Assertions.assertNotNull(mac1); + try (MacSupplier.ReusableMac mac1 = MacSupplier.HMAC_SHA256.keyed(key)) { + Assertions.assertNotNull(mac1); + } - Mac mac2 = MacSupplier.HMAC_SHA256.withKey(key); - Assertions.assertSame(mac1, mac2); + try (MacSupplier.ReusableMac mac2 = MacSupplier.HMAC_SHA256.keyed(key)) { + Assertions.assertNotNull(mac2); + } } @Test - public void testGetMacWithInvalidKey() throws InvalidKeyException, NoSuchAlgorithmException, ReflectiveOperationException { + public void testGetMacWithInvalidKey() throws NoSuchAlgorithmException, ReflectiveOperationException { Key key = KeyPairGenerator.getInstance("RSA").generateKeyPair().getPrivate(); // invoked via reflection, as we can not cast Key to SecretKey. - Method m = MacSupplier.class.getMethod("withKey", SecretKey.class); + Method m = MacSupplier.class.getMethod("keyed", SecretKey.class); Assertions.assertThrows(IllegalArgumentException.class, () -> { m.invoke(MacSupplier.HMAC_SHA256, key); }); From 3b9b00c0d41862be26748df5fcac259765b3200c Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 3 Dec 2021 14:44:38 +0100 Subject: [PATCH 06/30] use new MacSupplier API --- .../cryptolib/common/MasterkeyFileAccess.java | 7 ++++--- .../org/cryptomator/cryptolib/common/Scrypt.java | 8 ++++---- .../cryptolib/v1/FileContentCryptorImpl.java | 14 +++++++------- .../cryptolib/v1/FileHeaderCryptorImpl.java | 15 ++++++++------- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/common/MasterkeyFileAccess.java b/src/main/java/org/cryptomator/cryptolib/common/MasterkeyFileAccess.java index 45e3e92..be920b9 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/MasterkeyFileAccess.java +++ b/src/main/java/org/cryptomator/cryptolib/common/MasterkeyFileAccess.java @@ -192,9 +192,10 @@ MasterkeyFile lock(Masterkey masterkey, CharSequence passphrase, int vaultVersio final byte[] salt = new byte[DEFAULT_SCRYPT_SALT_LENGTH]; csprng.nextBytes(salt); - try (DestroyableSecretKey kek = scrypt(passphrase, salt, pepper, scryptCostParam, DEFAULT_SCRYPT_BLOCK_SIZE)) { - final Mac mac = MacSupplier.HMAC_SHA256.withKey(masterkey.getMacKey()); - final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt(vaultVersion).array()); + try (DestroyableSecretKey kek = scrypt(passphrase, salt, pepper, scryptCostParam, DEFAULT_SCRYPT_BLOCK_SIZE); + DestroyableSecretKey macKey = masterkey.getMacKey(); + MacSupplier.ReusableMac mac = MacSupplier.HMAC_SHA256.keyed(macKey)) { + final byte[] versionMac = mac.get().doFinal(ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt(vaultVersion).array()); MasterkeyFile result = new MasterkeyFile(); result.version = vaultVersion; result.versionMac = versionMac; diff --git a/src/main/java/org/cryptomator/cryptolib/common/Scrypt.java b/src/main/java/org/cryptomator/cryptolib/common/Scrypt.java index 4d35aaf..09c0d44 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/Scrypt.java +++ b/src/main/java/org/cryptomator/cryptolib/common/Scrypt.java @@ -72,21 +72,21 @@ public static byte[] scrypt(byte[] passphrase, byte[] salt, int costParam, int b throw new IllegalArgumentException("Parameter r is too large"); } - try (DestroyableSecretKey key = new DestroyableSecretKey(passphrase, "HmacSHA256")) { - Mac mac = MacSupplier.HMAC_SHA256.withKey(key); + try (DestroyableSecretKey key = new DestroyableSecretKey(passphrase, "HmacSHA256"); + MacSupplier.ReusableMac mac = MacSupplier.HMAC_SHA256.keyed(key)) { byte[] DK = new byte[keyLengthInBytes]; byte[] B = new byte[128 * blockSize * P]; byte[] XY = new byte[256 * blockSize]; byte[] V = new byte[128 * blockSize * costParam]; - pbkdf2(mac, salt, 1, B, P * 128 * blockSize); + pbkdf2(mac.get(), salt, 1, B, P * 128 * blockSize); for (int i = 0; i < P; i++) { smix(B, i * 128 * blockSize, blockSize, costParam, V, XY); } - pbkdf2(mac, B, 1, DK, keyLengthInBytes); + pbkdf2(mac.get(), B, 1, DK, keyLengthInBytes); return DK; } diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java index ee024ef..9154fd2 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java @@ -184,14 +184,14 @@ boolean checkChunkMac(byte[] headerNonce, long chunkNumber, ByteBuffer chunkBuf) } private byte[] calcChunkMac(byte[] headerNonce, long chunkNumber, byte[] chunkNonce, ByteBuffer ciphertext) { - try (DestroyableSecretKey mk = masterkey.getMacKey()) { + try (DestroyableSecretKey mk = masterkey.getMacKey(); + MacSupplier.ReusableMac mac = MacSupplier.HMAC_SHA256.keyed(mk)) { final byte[] chunkNumberBigEndian = ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.BIG_ENDIAN).putLong(chunkNumber).array(); - final Mac mac = MacSupplier.HMAC_SHA256.withKey(mk); - mac.update(headerNonce); - mac.update(chunkNumberBigEndian); - mac.update(chunkNonce); - mac.update(ciphertext); - return mac.doFinal(); + mac.get().update(headerNonce); + mac.get().update(chunkNumberBigEndian); + mac.get().update(chunkNonce); + mac.get().update(ciphertext); + return mac.get().doFinal(); } } diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java index 6064188..6b01851 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java @@ -68,9 +68,10 @@ public ByteBuffer encryptHeader(FileHeader header) { // mac nonce and ciphertext: ByteBuffer nonceAndCiphertextBuf = result.duplicate(); nonceAndCiphertextBuf.flip(); - Mac mac = MacSupplier.HMAC_SHA256.withKey(mk); - mac.update(nonceAndCiphertextBuf); - result.put(mac.doFinal()); + try (MacSupplier.ReusableMac mac = MacSupplier.HMAC_SHA256.keyed(mk)) { + mac.get().update(nonceAndCiphertextBuf); + result.put(mac.get().doFinal()); + } result.flip(); return result; @@ -100,12 +101,12 @@ public FileHeader decryptHeader(ByteBuffer ciphertextHeaderBuf) throws Authentic buf.get(expectedMac); // check mac: - try (DestroyableSecretKey mk = masterkey.getMacKey()) { + try (DestroyableSecretKey mk = masterkey.getMacKey(); + MacSupplier.ReusableMac mac = MacSupplier.HMAC_SHA256.keyed(mk)) { ByteBuffer nonceAndCiphertextBuf = buf.duplicate(); nonceAndCiphertextBuf.position(FileHeaderImpl.NONCE_POS).limit(FileHeaderImpl.NONCE_POS + FileHeaderImpl.NONCE_LEN + FileHeaderImpl.PAYLOAD_LEN); - Mac mac = MacSupplier.HMAC_SHA256.withKey(mk); - mac.update(nonceAndCiphertextBuf); - byte[] calculatedMac = mac.doFinal(); + mac.get().update(nonceAndCiphertextBuf); + byte[] calculatedMac = mac.get().doFinal(); if (!MessageDigest.isEqual(expectedMac, calculatedMac)) { throw new AuthenticationFailedException("Header MAC doesn't match."); } From a414d6680a4bd1d75adef1aadccf464d7353eecb Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 3 Dec 2021 15:00:06 +0100 Subject: [PATCH 07/30] use object pool in MessageDigestSupplier --- .../common/MessageDigestSupplier.java | 46 ++++++++++++++----- .../common/MessageDigestSupplierTest.java | 14 +++--- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/common/MessageDigestSupplier.java b/src/main/java/org/cryptomator/cryptolib/common/MessageDigestSupplier.java index 12c3ca7..cb8f602 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/MessageDigestSupplier.java +++ b/src/main/java/org/cryptomator/cryptolib/common/MessageDigestSupplier.java @@ -17,28 +17,52 @@ public final class MessageDigestSupplier { public static final MessageDigestSupplier SHA256 = new MessageDigestSupplier("SHA-256"); private final String digestAlgorithm; - private final ThreadLocal threadLocal; + private final ObjectPool mdPool; public MessageDigestSupplier(String digestAlgorithm) { this.digestAlgorithm = digestAlgorithm; - this.threadLocal = new Provider(); + this.mdPool = new ObjectPool<>(this::createMessageDigest); + try (ObjectPool.Lease lease = mdPool.get()) { + lease.get(); // eagerly initialize to provoke exceptions + } } - private class Provider extends ThreadLocal { - @Override - protected MessageDigest initialValue() { - try { - return MessageDigest.getInstance(digestAlgorithm); - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("Invalid digest algorithm.", e); - } + private MessageDigest createMessageDigest() { + try { + return MessageDigest.getInstance(digestAlgorithm); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Invalid digest algorithm.", e); } } + public ReusableMessageDigest instance() { + ObjectPool.Lease lease = mdPool.get(); + lease.get().reset(); + return new ReusableMessageDigest(lease); + } + + @Deprecated public MessageDigest get() { - final MessageDigest result = threadLocal.get(); + final MessageDigest result = createMessageDigest(); result.reset(); return result; } + public static class ReusableMessageDigest implements AutoCloseable { + + private final ObjectPool.Lease lease; + + private ReusableMessageDigest(ObjectPool.Lease lease) { + this.lease = lease; + } + + public MessageDigest get() { + return lease.get(); + } + + @Override + public void close() { + lease.close(); + } + } } \ No newline at end of file diff --git a/src/test/java/org/cryptomator/cryptolib/common/MessageDigestSupplierTest.java b/src/test/java/org/cryptomator/cryptolib/common/MessageDigestSupplierTest.java index ad67c66..aab00fc 100644 --- a/src/test/java/org/cryptomator/cryptolib/common/MessageDigestSupplierTest.java +++ b/src/test/java/org/cryptomator/cryptolib/common/MessageDigestSupplierTest.java @@ -11,24 +11,24 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.security.MessageDigest; - public class MessageDigestSupplierTest { @Test public void testConstructorWithInvalidDigest() { Assertions.assertThrows(IllegalArgumentException.class, () -> { - new MessageDigestSupplier("FOO3000").get(); + new MessageDigestSupplier("FOO3000"); }); } @Test public void testGetSha1() { - MessageDigest digest1 = MessageDigestSupplier.SHA1.get(); - Assertions.assertNotNull(digest1); + try (MessageDigestSupplier.ReusableMessageDigest digest = MessageDigestSupplier.SHA1.instance()) { + Assertions.assertNotNull(digest); + } - MessageDigest digest2 = MessageDigestSupplier.SHA1.get(); - Assertions.assertSame(digest1, digest2); + try (MessageDigestSupplier.ReusableMessageDigest digest = MessageDigestSupplier.SHA1.instance()) { + Assertions.assertNotNull(digest); + } } } From ba9c1e2a447e701f89764d1735450980f24fed62 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 3 Dec 2021 15:00:29 +0100 Subject: [PATCH 08/30] use new MessageDigestSupplier API --- .../cryptolib/ecies/KeyDerivationFunction.java | 15 +++++++-------- .../cryptolib/v1/FileNameCryptorImpl.java | 5 +++-- .../cryptolib/v2/FileNameCryptorImpl.java | 5 +++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunction.java b/src/main/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunction.java index f7a6b95..9a9c65e 100644 --- a/src/main/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunction.java +++ b/src/main/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunction.java @@ -6,7 +6,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.DigestException; -import java.security.MessageDigest; import java.util.Arrays; @FunctionalInterface @@ -32,8 +31,8 @@ public interface KeyDerivationFunction { * @return key data */ static byte[] ansiX963sha256Kdf(byte[] sharedSecret, byte[] sharedInfo, int keyDataLen) { - MessageDigest digest = MessageDigestSupplier.SHA256.get(); // max input length is 2^64 - 1, see https://doi.org/10.6028/NIST.SP.800-56Cr2, Table 1 - int hashLen = digest.getDigestLength(); + // max input length is 2^64 - 1, see https://doi.org/10.6028/NIST.SP.800-56Cr2, Table 1 + int hashLen = 32; // fixed digest length for SHA-256 in bytes // These two checks must be performed according to spec. However with 32 bit integers, we can't exceed any limits anyway: assert BigInteger.valueOf(4L + sharedSecret.length + sharedInfo.length).compareTo(BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE)) < 0 : "input larger than hashmaxlen"; @@ -43,15 +42,15 @@ static byte[] ansiX963sha256Kdf(byte[] sharedSecret, byte[] sharedInfo, int keyD assert ByteOrder.BIG_ENDIAN.equals(counter.order()); int n = (keyDataLen + hashLen - 1) / hashLen; byte[] buffer = new byte[n * hashLen]; - try { + try (MessageDigestSupplier.ReusableMessageDigest sha256 = MessageDigestSupplier.SHA256.instance()) { for (int i = 0; i < n; i++) { - digest.update(sharedSecret); + sha256.get().update(sharedSecret); counter.clear(); counter.putInt(i + 1); counter.flip(); - digest.update(counter); - digest.update(sharedInfo); - digest.digest(buffer, i * hashLen, hashLen); + sha256.get().update(counter); + sha256.get().update(sharedInfo); + sha256.get().digest(buffer, i * hashLen, hashLen); } return Arrays.copyOf(buffer, keyDataLen); } catch (DigestException e) { diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java index ad99625..af6d16d 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java @@ -39,10 +39,11 @@ protected SivMode initialValue() { @Override public String hashDirectoryId(String cleartextDirectoryId) { - try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey()) { + try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey(); + MessageDigestSupplier.ReusableMessageDigest sha1 = MessageDigestSupplier.SHA1.instance()) { byte[] cleartextBytes = cleartextDirectoryId.getBytes(UTF_8); byte[] encryptedBytes = AES_SIV.get().encrypt(ek, mk, cleartextBytes); - byte[] hashedBytes = MessageDigestSupplier.SHA1.get().digest(encryptedBytes); + byte[] hashedBytes = sha1.get().digest(encryptedBytes); return BASE32.encode(hashedBytes); } } diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java index 9f14680..fe21abb 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java @@ -39,10 +39,11 @@ protected SivMode initialValue() { @Override public String hashDirectoryId(String cleartextDirectoryId) { - try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey()) { + try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey(); + MessageDigestSupplier.ReusableMessageDigest sha1 = MessageDigestSupplier.SHA1.instance()) { byte[] cleartextBytes = cleartextDirectoryId.getBytes(UTF_8); byte[] encryptedBytes = AES_SIV.get().encrypt(ek, mk, cleartextBytes); - byte[] hashedBytes = MessageDigestSupplier.SHA1.get().digest(encryptedBytes); + byte[] hashedBytes = sha1.get().digest(encryptedBytes); return BASE32.encode(hashedBytes); } } From 597d537f690f6fdfea7fb972c6f8fb74e5fc3191 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 3 Dec 2021 15:40:39 +0100 Subject: [PATCH 09/30] use correct method (despite being the same for CTR) --- .../org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java | 2 +- .../org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java index 9154fd2..6f05b14 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java @@ -145,7 +145,7 @@ void decryptChunk(ByteBuffer ciphertextChunk, ByteBuffer cleartextChunk, Destroy payloadBuf.position(NONCE_SIZE).limit(ciphertextChunk.limit() - MAC_SIZE); // payload: - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_CTR.encrypt(fk, new IvParameterSpec(nonce))) { + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_CTR.decrypt(fk, new IvParameterSpec(nonce))) { assert cleartextChunk.remaining() >= cipher.get().getOutputSize(payloadBuf.remaining()); cipher.get().doFinal(payloadBuf, cleartextChunk); } diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java index 6b01851..55efed4 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java @@ -115,7 +115,7 @@ public FileHeader decryptHeader(ByteBuffer ciphertextHeaderBuf) throws Authentic ByteBuffer payloadCleartextBuf = ByteBuffer.allocate(FileHeaderImpl.Payload.SIZE); try (DestroyableSecretKey ek = masterkey.getEncKey()) { // decrypt payload: - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_CTR.encrypt(ek, new IvParameterSpec(nonce))) { + try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_CTR.decrypt(ek, new IvParameterSpec(nonce))) { assert cipher.get().getOutputSize(ciphertextPayload.length) == payloadCleartextBuf.remaining(); int decrypted = cipher.get().doFinal(ByteBuffer.wrap(ciphertextPayload), payloadCleartextBuf); assert decrypted == FileHeaderImpl.Payload.SIZE; From d0141e3fdc19bdcbf1e1c1ad9d99a6f6a2048b4a Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 6 Dec 2021 09:20:08 +0100 Subject: [PATCH 10/30] add javadoc --- .../cryptolib/common/CipherSupplier.java | 56 +++++++++++++++++++ .../cryptolib/common/MacSupplier.java | 13 +++++ .../common/MessageDigestSupplier.java | 11 ++++ 3 files changed, 80 insertions(+) diff --git a/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java b/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java index 45e1875..0dc4ce9 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java +++ b/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java @@ -41,12 +41,27 @@ private Cipher createCipher() { } } + /** + * Leases a reusable cipher object initialized for encryption. + * + * @param key Encryption key + * @param params Params such as IV/Nonce + * @return A ReusableCipher instance holding a refurbished Cipher + */ public ReusableCipher encrypt(SecretKey key, AlgorithmParameterSpec params) { ObjectPool.Lease lease = cipherPool.get(); initMode(lease.get(), Cipher.ENCRYPT_MODE, key, params); return new ReusableCipher(lease); } + /** + * Creates a new Cipher object initialized for encryption. + * + * @param key Encryption key + * @param params Params such as IV/Nonce + * @return New Cipher instance + * @deprecated Use {@link #encrypt(SecretKey, AlgorithmParameterSpec)} instead. + */ @Deprecated public Cipher forEncryption(SecretKey key, AlgorithmParameterSpec params) { final Cipher cipher = createCipher(); @@ -54,12 +69,27 @@ public Cipher forEncryption(SecretKey key, AlgorithmParameterSpec params) { return cipher; } + /** + * Leases a reusable cipher object initialized for decryption. + * + * @param key Decryption key + * @param params Params such as IV/Nonce + * @return A ReusableCipher instance holding a refurbished Cipher + */ public ReusableCipher decrypt(SecretKey key, AlgorithmParameterSpec params) { ObjectPool.Lease lease = cipherPool.get(); initMode(lease.get(), Cipher.DECRYPT_MODE, key, params); return new ReusableCipher(lease); } + /** + * Creates a new Cipher object initialized for decryption. + * + * @param key Encryption key + * @param params Params such as IV/Nonce + * @return New Cipher instance + * @deprecated Use {@link #decrypt(SecretKey, AlgorithmParameterSpec)} instead. + */ @Deprecated public Cipher forDecryption(SecretKey key, AlgorithmParameterSpec params) { final Cipher cipher = createCipher(); @@ -67,12 +97,25 @@ public Cipher forDecryption(SecretKey key, AlgorithmParameterSpec params) { return cipher; } + /** + * Leases a reusable cipher object initialized for wrapping a key. + * + * @param kek Key encryption key + * @return A ReusableCipher instance holding a refurbished Cipher + */ public ReusableCipher wrap(SecretKey kek) { ObjectPool.Lease lease = cipherPool.get(); initMode(lease.get(), Cipher.WRAP_MODE, kek, null); return new ReusableCipher(lease); } + /** + * Creates a new Cipher object initialized for wrapping a key. + * + * @param kek Key encryption key + * @return New Cipher instance + * @deprecated Use {@link #wrap(SecretKey)} instead. + */ @Deprecated public Cipher forWrapping(SecretKey kek) { final Cipher cipher = createCipher(); @@ -80,12 +123,25 @@ public Cipher forWrapping(SecretKey kek) { return cipher; } + /** + * Leases a reusable cipher object initialized for unwrapping a key. + * + * @param kek Key encryption key + * @return A ReusableCipher instance holding a refurbished Cipher + */ public ReusableCipher unwrap(SecretKey kek) { ObjectPool.Lease lease = cipherPool.get(); initMode(lease.get(), Cipher.UNWRAP_MODE, kek, null); return new ReusableCipher(lease); } + /** + * Creates a new Cipher object initialized for unwrapping a key. + * + * @param kek Key encryption key + * @return New Cipher instance + * @deprecated Use {@link #unwrap(SecretKey)} instead. + */ @Deprecated public Cipher forUnwrapping(SecretKey kek) { final Cipher cipher = createCipher(); diff --git a/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java b/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java index 54615e9..c54d386 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java +++ b/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java @@ -37,12 +37,25 @@ private Mac createMac() { } } + /** + * Leases a reusable MAC object initialized with the given key. + * + * @param key Key to use in keyed MAC + * @return A ReusableMac instance holding a refurbished MAC + */ public ReusableMac keyed(SecretKey key) { ObjectPool.Lease lease = macPool.get(); init(lease.get(), key); return new ReusableMac(lease); } + /** + * Creates a new MAC + * + * @param key Key to use in keyed MAC + * @return New Mac instance + * @deprecated Use {@link #keyed(SecretKey)} instead + */ @Deprecated public Mac withKey(SecretKey key) { final Mac mac = createMac(); diff --git a/src/main/java/org/cryptomator/cryptolib/common/MessageDigestSupplier.java b/src/main/java/org/cryptomator/cryptolib/common/MessageDigestSupplier.java index cb8f602..6f82e8d 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/MessageDigestSupplier.java +++ b/src/main/java/org/cryptomator/cryptolib/common/MessageDigestSupplier.java @@ -35,12 +35,23 @@ private MessageDigest createMessageDigest() { } } + /** + * Leases a reusable MessageDigest. + * + * @return A ReusableMessageDigest instance holding a refurbished MessageDigest + */ public ReusableMessageDigest instance() { ObjectPool.Lease lease = mdPool.get(); lease.get().reset(); return new ReusableMessageDigest(lease); } + /** + * Creates a new MessageDigest. + * + * @deprecated Use {@link #instance()} + * @return New MessageDigest instance + */ @Deprecated public MessageDigest get() { final MessageDigest result = createMessageDigest(); From c532025ba94059dd51e5ba2ed8557f536dabf079 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 6 Dec 2021 09:20:22 +0100 Subject: [PATCH 11/30] remove unused imports --- src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java | 1 - .../org/cryptomator/cryptolib/common/MasterkeyFileAccess.java | 1 - .../org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java | 2 -- .../org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java | 2 -- .../org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java | 1 - .../org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java | 1 - .../org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java | 1 - 7 files changed, 9 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java b/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java index c54d386..fcd62c6 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java +++ b/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java @@ -8,7 +8,6 @@ *******************************************************************************/ package org.cryptomator.cryptolib.common; -import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.SecretKey; import java.security.InvalidKeyException; diff --git a/src/main/java/org/cryptomator/cryptolib/common/MasterkeyFileAccess.java b/src/main/java/org/cryptomator/cryptolib/common/MasterkeyFileAccess.java index be920b9..f3e902f 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/MasterkeyFileAccess.java +++ b/src/main/java/org/cryptomator/cryptolib/common/MasterkeyFileAccess.java @@ -5,7 +5,6 @@ import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; -import javax.crypto.Mac; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java b/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java index 3b053c1..9d7d9b1 100644 --- a/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java +++ b/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java @@ -6,10 +6,8 @@ import javax.crypto.AEADBadTagException; import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.spec.GCMParameterSpec; -import javax.crypto.spec.IvParameterSpec; import java.util.Arrays; class GcmWithSecretNonce implements AuthenticatedEncryption { diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java index 6f05b14..1f4d086 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java @@ -17,9 +17,7 @@ import org.cryptomator.cryptolib.common.MacSupplier; import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; -import javax.crypto.Mac; import javax.crypto.ShortBufferException; import javax.crypto.spec.IvParameterSpec; import java.nio.ByteBuffer; diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java index 55efed4..e4df32b 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java @@ -18,7 +18,6 @@ import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; -import javax.crypto.Mac; import javax.crypto.ShortBufferException; import javax.crypto.spec.IvParameterSpec; import java.nio.ByteBuffer; diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java index 5e7673f..6d38e1a 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java @@ -16,7 +16,6 @@ import javax.crypto.AEADBadTagException; import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.ShortBufferException; import javax.crypto.spec.GCMParameterSpec; diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java index c2a5ae5..ae365e3 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java @@ -17,7 +17,6 @@ import javax.crypto.AEADBadTagException; import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.ShortBufferException; import javax.crypto.spec.GCMParameterSpec; From fb50af96143feca7d18ad2b1e2456645c19e5d97 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 6 Dec 2021 10:07:36 +0100 Subject: [PATCH 12/30] Removed redundant classes --- .../cryptolib/common/AesKeyWrap.java | 4 +- .../cryptolib/common/CipherSupplier.java | 51 +++++++------------ .../cryptolib/common/MacSupplier.java | 28 ++-------- .../cryptolib/common/MasterkeyFileAccess.java | 3 +- .../common/MessageDigestSupplier.java | 27 ++-------- .../cryptolib/common/ObjectPool.java | 27 ++++++---- .../cryptomator/cryptolib/common/Scrypt.java | 2 +- .../cryptolib/ecies/GcmWithSecretNonce.java | 6 ++- .../ecies/KeyDerivationFunction.java | 4 +- .../cryptolib/v1/FileContentCryptorImpl.java | 9 ++-- .../cryptolib/v1/FileHeaderCryptorImpl.java | 11 ++-- .../cryptolib/v1/FileNameCryptorImpl.java | 5 +- .../cryptolib/v2/FileContentCryptorImpl.java | 6 ++- .../cryptolib/v2/FileHeaderCryptorImpl.java | 6 ++- .../cryptolib/v2/FileNameCryptorImpl.java | 5 +- .../cryptolib/common/MacSupplierTest.java | 5 +- .../common/MessageDigestSupplierTest.java | 6 ++- .../cryptolib/common/ObjectPoolTest.java | 12 ++--- .../ecies/GcmWithSecretNonceTest.java | 4 +- .../v2/FileHeaderCryptorImplTest.java | 4 +- 20 files changed, 105 insertions(+), 120 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java b/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java index 619dd49..e00480d 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java +++ b/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java @@ -23,7 +23,7 @@ public class AesKeyWrap { */ public static byte[] wrap(DestroyableSecretKey kek, SecretKey key) { try (DestroyableSecretKey kekCopy = kek.copy(); - CipherSupplier.ReusableCipher cipher = CipherSupplier.RFC3394_KEYWRAP.wrap(kekCopy)) { + ObjectPool.Lease cipher = CipherSupplier.RFC3394_KEYWRAP.wrap(kekCopy)) { return cipher.get().wrap(key); } catch (InvalidKeyException | IllegalBlockSizeException e) { throw new IllegalArgumentException("Unable to wrap key.", e); @@ -44,7 +44,7 @@ public static DestroyableSecretKey unwrap(DestroyableSecretKey kek, byte[] wrapp // visible for testing static DestroyableSecretKey unwrap(DestroyableSecretKey kek, byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType) throws InvalidKeyException { try (DestroyableSecretKey kekCopy = kek.copy(); - CipherSupplier.ReusableCipher cipher = CipherSupplier.RFC3394_KEYWRAP.unwrap(kekCopy)) { + ObjectPool.Lease cipher = CipherSupplier.RFC3394_KEYWRAP.unwrap(kekCopy)) { return DestroyableSecretKey.from(cipher.get().unwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType)); } catch (NoSuchAlgorithmException e) { throw new IllegalArgumentException("Invalid algorithm: " + wrappedKeyAlgorithm, e); diff --git a/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java b/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java index 0dc4ce9..b3af7d9 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java +++ b/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java @@ -28,7 +28,7 @@ public final class CipherSupplier { public CipherSupplier(String cipherAlgorithm) { this.cipherAlgorithm = cipherAlgorithm; this.cipherPool = new ObjectPool<>(this::createCipher); - try (ObjectPool.Lease lease = cipherPool.get()) { + try (ObjectPool.Lease lease = cipherPool.get()) { lease.get(); // eagerly initialize to provoke exceptions } } @@ -46,12 +46,12 @@ private Cipher createCipher() { * * @param key Encryption key * @param params Params such as IV/Nonce - * @return A ReusableCipher instance holding a refurbished Cipher + * @return A lease supplying a refurbished Cipher */ - public ReusableCipher encrypt(SecretKey key, AlgorithmParameterSpec params) { - ObjectPool.Lease lease = cipherPool.get(); + public ObjectPool.Lease encrypt(SecretKey key, AlgorithmParameterSpec params) { + ObjectPool.Lease lease = cipherPool.get(); initMode(lease.get(), Cipher.ENCRYPT_MODE, key, params); - return new ReusableCipher(lease); + return lease; } /** @@ -74,12 +74,12 @@ public Cipher forEncryption(SecretKey key, AlgorithmParameterSpec params) { * * @param key Decryption key * @param params Params such as IV/Nonce - * @return A ReusableCipher instance holding a refurbished Cipher + * @return A lease supplying a refurbished Cipher */ - public ReusableCipher decrypt(SecretKey key, AlgorithmParameterSpec params) { - ObjectPool.Lease lease = cipherPool.get(); + public ObjectPool.Lease decrypt(SecretKey key, AlgorithmParameterSpec params) { + ObjectPool.Lease lease = cipherPool.get(); initMode(lease.get(), Cipher.DECRYPT_MODE, key, params); - return new ReusableCipher(lease); + return lease; } /** @@ -101,12 +101,12 @@ public Cipher forDecryption(SecretKey key, AlgorithmParameterSpec params) { * Leases a reusable cipher object initialized for wrapping a key. * * @param kek Key encryption key - * @return A ReusableCipher instance holding a refurbished Cipher + * @return A lease supplying a refurbished Cipher */ - public ReusableCipher wrap(SecretKey kek) { - ObjectPool.Lease lease = cipherPool.get(); + public ObjectPool.Lease wrap(SecretKey kek) { + ObjectPool.Lease lease = cipherPool.get(); initMode(lease.get(), Cipher.WRAP_MODE, kek, null); - return new ReusableCipher(lease); + return lease; } /** @@ -127,12 +127,12 @@ public Cipher forWrapping(SecretKey kek) { * Leases a reusable cipher object initialized for unwrapping a key. * * @param kek Key encryption key - * @return A ReusableCipher instance holding a refurbished Cipher + * @return A lease supplying a refurbished Cipher */ - public ReusableCipher unwrap(SecretKey kek) { - ObjectPool.Lease lease = cipherPool.get(); + public ObjectPool.Lease unwrap(SecretKey kek) { + ObjectPool.Lease lease = cipherPool.get(); initMode(lease.get(), Cipher.UNWRAP_MODE, kek, null); - return new ReusableCipher(lease); + return lease; } /** @@ -159,21 +159,4 @@ private void initMode(Cipher cipher, int ciphermode, SecretKey key, AlgorithmPar } } - public static class ReusableCipher implements AutoCloseable { - - private final ObjectPool.Lease lease; - - private ReusableCipher(ObjectPool.Lease lease) { - this.lease = lease; - } - - public Cipher get() { - return lease.get(); - } - - @Override - public void close() { - lease.close(); - } - } } \ No newline at end of file diff --git a/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java b/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java index fcd62c6..6093489 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java +++ b/src/main/java/org/cryptomator/cryptolib/common/MacSupplier.java @@ -23,7 +23,7 @@ public final class MacSupplier { public MacSupplier(String macAlgorithm) { this.macAlgorithm = macAlgorithm; this.macPool = new ObjectPool<>(this::createMac); - try (ObjectPool.Lease lease = macPool.get()) { + try (ObjectPool.Lease lease = macPool.get()) { lease.get(); // eagerly initialize to provoke exceptions } } @@ -40,12 +40,12 @@ private Mac createMac() { * Leases a reusable MAC object initialized with the given key. * * @param key Key to use in keyed MAC - * @return A ReusableMac instance holding a refurbished MAC + * @return A lease supplying a refurbished MAC */ - public ReusableMac keyed(SecretKey key) { - ObjectPool.Lease lease = macPool.get(); + public ObjectPool.Lease keyed(SecretKey key) { + ObjectPool.Lease lease = macPool.get(); init(lease.get(), key); - return new ReusableMac(lease); + return lease; } /** @@ -70,22 +70,4 @@ private void init(Mac mac, SecretKey key) { } } - public static class ReusableMac implements AutoCloseable { - - private final ObjectPool.Lease lease; - - private ReusableMac(ObjectPool.Lease lease) { - this.lease = lease; - } - - public Mac get() { - return lease.get(); - } - - @Override - public void close() { - lease.close(); - } - } - } \ No newline at end of file diff --git a/src/main/java/org/cryptomator/cryptolib/common/MasterkeyFileAccess.java b/src/main/java/org/cryptomator/cryptolib/common/MasterkeyFileAccess.java index f3e902f..dc43d97 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/MasterkeyFileAccess.java +++ b/src/main/java/org/cryptomator/cryptolib/common/MasterkeyFileAccess.java @@ -5,6 +5,7 @@ import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; +import javax.crypto.Mac; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -193,7 +194,7 @@ MasterkeyFile lock(Masterkey masterkey, CharSequence passphrase, int vaultVersio csprng.nextBytes(salt); try (DestroyableSecretKey kek = scrypt(passphrase, salt, pepper, scryptCostParam, DEFAULT_SCRYPT_BLOCK_SIZE); DestroyableSecretKey macKey = masterkey.getMacKey(); - MacSupplier.ReusableMac mac = MacSupplier.HMAC_SHA256.keyed(macKey)) { + ObjectPool.Lease mac = MacSupplier.HMAC_SHA256.keyed(macKey)) { final byte[] versionMac = mac.get().doFinal(ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt(vaultVersion).array()); MasterkeyFile result = new MasterkeyFile(); result.version = vaultVersion; diff --git a/src/main/java/org/cryptomator/cryptolib/common/MessageDigestSupplier.java b/src/main/java/org/cryptomator/cryptolib/common/MessageDigestSupplier.java index 6f82e8d..5be3de5 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/MessageDigestSupplier.java +++ b/src/main/java/org/cryptomator/cryptolib/common/MessageDigestSupplier.java @@ -22,7 +22,7 @@ public final class MessageDigestSupplier { public MessageDigestSupplier(String digestAlgorithm) { this.digestAlgorithm = digestAlgorithm; this.mdPool = new ObjectPool<>(this::createMessageDigest); - try (ObjectPool.Lease lease = mdPool.get()) { + try (ObjectPool.Lease lease = mdPool.get()) { lease.get(); // eagerly initialize to provoke exceptions } } @@ -40,17 +40,17 @@ private MessageDigest createMessageDigest() { * * @return A ReusableMessageDigest instance holding a refurbished MessageDigest */ - public ReusableMessageDigest instance() { - ObjectPool.Lease lease = mdPool.get(); + public ObjectPool.Lease instance() { + ObjectPool.Lease lease = mdPool.get(); lease.get().reset(); - return new ReusableMessageDigest(lease); + return lease; } /** * Creates a new MessageDigest. * - * @deprecated Use {@link #instance()} * @return New MessageDigest instance + * @deprecated Use {@link #instance()} */ @Deprecated public MessageDigest get() { @@ -59,21 +59,4 @@ public MessageDigest get() { return result; } - public static class ReusableMessageDigest implements AutoCloseable { - - private final ObjectPool.Lease lease; - - private ReusableMessageDigest(ObjectPool.Lease lease) { - this.lease = lease; - } - - public MessageDigest get() { - return lease.get(); - } - - @Override - public void close() { - lease.close(); - } - } } \ No newline at end of file diff --git a/src/main/java/org/cryptomator/cryptolib/common/ObjectPool.java b/src/main/java/org/cryptomator/cryptolib/common/ObjectPool.java index 730a3f7..715f07d 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/ObjectPool.java +++ b/src/main/java/org/cryptomator/cryptolib/common/ObjectPool.java @@ -7,30 +7,39 @@ public class ObjectPool { - private final Queue> pool; + private final Queue> returnedInstances; private final Supplier factory; public ObjectPool(Supplier factory) { - this.pool = new ConcurrentLinkedQueue<>(); + this.returnedInstances = new ConcurrentLinkedQueue<>(); this.factory = factory; } - public Lease get() { + public Lease get() { WeakReference ref; - while ((ref = pool.poll()) != null) { + while ((ref = returnedInstances.poll()) != null) { T cached = ref.get(); if (cached != null) { - return new Lease(cached); + return new Lease<>(this, cached); } } - return new Lease(factory.get()); + return new Lease<>(this, factory.get()); } - public class Lease implements AutoCloseable { + /** + * A holder for resource leased from an {@link ObjectPool}. + * This is basically an {@link AutoCloseable autocloseable} {@link Supplier} that is intended to be used + * via try-with-resource blocks. + * + * @param Type of the leased instance + */ + public static class Lease implements AutoCloseable, Supplier { + private final ObjectPool pool; private T obj; - public Lease(T obj) { + private Lease(ObjectPool pool, T obj) { + this.pool = pool; this.obj = obj; } @@ -40,7 +49,7 @@ public T get() { @Override public void close() { - pool.offer(new WeakReference<>(obj)); + pool.returnedInstances.add(new WeakReference<>(obj)); obj = null; } } diff --git a/src/main/java/org/cryptomator/cryptolib/common/Scrypt.java b/src/main/java/org/cryptomator/cryptolib/common/Scrypt.java index 09c0d44..05134a0 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/Scrypt.java +++ b/src/main/java/org/cryptomator/cryptolib/common/Scrypt.java @@ -73,7 +73,7 @@ public static byte[] scrypt(byte[] passphrase, byte[] salt, int costParam, int b } try (DestroyableSecretKey key = new DestroyableSecretKey(passphrase, "HmacSHA256"); - MacSupplier.ReusableMac mac = MacSupplier.HMAC_SHA256.keyed(key)) { + ObjectPool.Lease mac = MacSupplier.HMAC_SHA256.keyed(key)) { byte[] DK = new byte[keyLengthInBytes]; byte[] B = new byte[128 * blockSize * P]; diff --git a/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java b/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java index 9d7d9b1..9cfc015 100644 --- a/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java +++ b/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java @@ -3,9 +3,11 @@ import com.google.common.base.Throwables; import org.cryptomator.cryptolib.common.CipherSupplier; import org.cryptomator.cryptolib.common.DestroyableSecretKey; +import org.cryptomator.cryptolib.common.ObjectPool; import javax.crypto.AEADBadTagException; import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.spec.GCMParameterSpec; import java.util.Arrays; @@ -25,7 +27,7 @@ public int requiredSecretBytes() { public byte[] encrypt(byte[] secret, byte[] plaintext) { try (DestroyableSecretKey key = new DestroyableSecretKey(secret, 0, GCM_KEY_SIZE, "AES")) { byte[] nonce = Arrays.copyOfRange(secret, GCM_KEY_SIZE, GCM_KEY_SIZE + GCM_NONCE_SIZE); - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.encrypt(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encrypt(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { return cipher.get().doFinal(plaintext); } } catch (IllegalBlockSizeException | BadPaddingException e) { @@ -37,7 +39,7 @@ public byte[] encrypt(byte[] secret, byte[] plaintext) { public byte[] decrypt(byte[] secret, byte[] ciphertext) throws AEADBadTagException { try (DestroyableSecretKey key = new DestroyableSecretKey(secret, 0, GCM_KEY_SIZE, "AES")) { byte[] nonce = Arrays.copyOfRange(secret, GCM_KEY_SIZE, GCM_KEY_SIZE + GCM_NONCE_SIZE); - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.decrypt(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.decrypt(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { return cipher.get().doFinal(ciphertext); } } catch (IllegalBlockSizeException | BadPaddingException e) { diff --git a/src/main/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunction.java b/src/main/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunction.java index 9a9c65e..0b8ecf0 100644 --- a/src/main/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunction.java +++ b/src/main/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunction.java @@ -1,11 +1,13 @@ package org.cryptomator.cryptolib.ecies; import org.cryptomator.cryptolib.common.MessageDigestSupplier; +import org.cryptomator.cryptolib.common.ObjectPool; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.DigestException; +import java.security.MessageDigest; import java.util.Arrays; @FunctionalInterface @@ -42,7 +44,7 @@ static byte[] ansiX963sha256Kdf(byte[] sharedSecret, byte[] sharedInfo, int keyD assert ByteOrder.BIG_ENDIAN.equals(counter.order()); int n = (keyDataLen + hashLen - 1) / hashLen; byte[] buffer = new byte[n * hashLen]; - try (MessageDigestSupplier.ReusableMessageDigest sha256 = MessageDigestSupplier.SHA256.instance()) { + try (ObjectPool.Lease sha256 = MessageDigestSupplier.SHA256.instance()) { for (int i = 0; i < n; i++) { sha256.get().update(sharedSecret); counter.clear(); diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java index 1f4d086..807e686 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java @@ -15,9 +15,12 @@ import org.cryptomator.cryptolib.common.CipherSupplier; import org.cryptomator.cryptolib.common.DestroyableSecretKey; import org.cryptomator.cryptolib.common.MacSupplier; +import org.cryptomator.cryptolib.common.ObjectPool; import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; +import javax.crypto.Mac; import javax.crypto.ShortBufferException; import javax.crypto.spec.IvParameterSpec; import java.nio.ByteBuffer; @@ -109,7 +112,7 @@ void encryptChunk(ByteBuffer cleartextChunk, ByteBuffer ciphertextChunk, long ch // payload: int bytesEncrypted; - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_CTR.encrypt(fk, new IvParameterSpec(nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.encrypt(fk, new IvParameterSpec(nonce))) { assert ciphertextChunk.remaining() >= cipher.get().getOutputSize(cleartextChunk.remaining()) + MAC_SIZE; bytesEncrypted = cipher.get().doFinal(cleartextChunk, ciphertextChunk); } @@ -143,7 +146,7 @@ void decryptChunk(ByteBuffer ciphertextChunk, ByteBuffer cleartextChunk, Destroy payloadBuf.position(NONCE_SIZE).limit(ciphertextChunk.limit() - MAC_SIZE); // payload: - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_CTR.decrypt(fk, new IvParameterSpec(nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.decrypt(fk, new IvParameterSpec(nonce))) { assert cleartextChunk.remaining() >= cipher.get().getOutputSize(payloadBuf.remaining()); cipher.get().doFinal(payloadBuf, cleartextChunk); } @@ -183,7 +186,7 @@ boolean checkChunkMac(byte[] headerNonce, long chunkNumber, ByteBuffer chunkBuf) private byte[] calcChunkMac(byte[] headerNonce, long chunkNumber, byte[] chunkNonce, ByteBuffer ciphertext) { try (DestroyableSecretKey mk = masterkey.getMacKey(); - MacSupplier.ReusableMac mac = MacSupplier.HMAC_SHA256.keyed(mk)) { + ObjectPool.Lease mac = MacSupplier.HMAC_SHA256.keyed(mk)) { final byte[] chunkNumberBigEndian = ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.BIG_ENDIAN).putLong(chunkNumber).array(); mac.get().update(headerNonce); mac.get().update(chunkNumberBigEndian); diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java index e4df32b..d5b127b 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java @@ -15,9 +15,12 @@ import org.cryptomator.cryptolib.common.CipherSupplier; import org.cryptomator.cryptolib.common.DestroyableSecretKey; import org.cryptomator.cryptolib.common.MacSupplier; +import org.cryptomator.cryptolib.common.ObjectPool; import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; +import javax.crypto.Mac; import javax.crypto.ShortBufferException; import javax.crypto.spec.IvParameterSpec; import java.nio.ByteBuffer; @@ -59,7 +62,7 @@ public ByteBuffer encryptHeader(FileHeader header) { result.put(headerImpl.getNonce()); // encrypt payload: - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_CTR.encrypt(ek, new IvParameterSpec(headerImpl.getNonce()))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.encrypt(ek, new IvParameterSpec(headerImpl.getNonce()))) { int encrypted = cipher.get().doFinal(payloadCleartextBuf, result); assert encrypted == FileHeaderImpl.Payload.SIZE; } @@ -67,7 +70,7 @@ public ByteBuffer encryptHeader(FileHeader header) { // mac nonce and ciphertext: ByteBuffer nonceAndCiphertextBuf = result.duplicate(); nonceAndCiphertextBuf.flip(); - try (MacSupplier.ReusableMac mac = MacSupplier.HMAC_SHA256.keyed(mk)) { + try (ObjectPool.Lease mac = MacSupplier.HMAC_SHA256.keyed(mk)) { mac.get().update(nonceAndCiphertextBuf); result.put(mac.get().doFinal()); } @@ -101,7 +104,7 @@ public FileHeader decryptHeader(ByteBuffer ciphertextHeaderBuf) throws Authentic // check mac: try (DestroyableSecretKey mk = masterkey.getMacKey(); - MacSupplier.ReusableMac mac = MacSupplier.HMAC_SHA256.keyed(mk)) { + ObjectPool.Lease mac = MacSupplier.HMAC_SHA256.keyed(mk)) { ByteBuffer nonceAndCiphertextBuf = buf.duplicate(); nonceAndCiphertextBuf.position(FileHeaderImpl.NONCE_POS).limit(FileHeaderImpl.NONCE_POS + FileHeaderImpl.NONCE_LEN + FileHeaderImpl.PAYLOAD_LEN); mac.get().update(nonceAndCiphertextBuf); @@ -114,7 +117,7 @@ public FileHeader decryptHeader(ByteBuffer ciphertextHeaderBuf) throws Authentic ByteBuffer payloadCleartextBuf = ByteBuffer.allocate(FileHeaderImpl.Payload.SIZE); try (DestroyableSecretKey ek = masterkey.getEncKey()) { // decrypt payload: - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_CTR.decrypt(ek, new IvParameterSpec(nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.decrypt(ek, new IvParameterSpec(nonce))) { assert cipher.get().getOutputSize(ciphertextPayload.length) == payloadCleartextBuf.remaining(); int decrypted = cipher.get().doFinal(ByteBuffer.wrap(ciphertextPayload), payloadCleartextBuf); assert decrypted == FileHeaderImpl.Payload.SIZE; diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java index af6d16d..309d1d6 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java @@ -14,11 +14,14 @@ import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.common.DestroyableSecretKey; import org.cryptomator.cryptolib.common.MessageDigestSupplier; +import org.cryptomator.cryptolib.common.ObjectPool; import org.cryptomator.siv.SivMode; import org.cryptomator.siv.UnauthenticCiphertextException; import javax.crypto.IllegalBlockSizeException; +import java.security.MessageDigest; + import static java.nio.charset.StandardCharsets.UTF_8; class FileNameCryptorImpl implements FileNameCryptor { @@ -40,7 +43,7 @@ protected SivMode initialValue() { @Override public String hashDirectoryId(String cleartextDirectoryId) { try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey(); - MessageDigestSupplier.ReusableMessageDigest sha1 = MessageDigestSupplier.SHA1.instance()) { + ObjectPool.Lease sha1 = MessageDigestSupplier.SHA1.instance()) { byte[] cleartextBytes = cleartextDirectoryId.getBytes(UTF_8); byte[] encryptedBytes = AES_SIV.get().encrypt(ek, mk, cleartextBytes); byte[] hashedBytes = sha1.get().digest(encryptedBytes); diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java index 6d38e1a..98bdf61 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java @@ -13,9 +13,11 @@ import org.cryptomator.cryptolib.api.FileHeader; import org.cryptomator.cryptolib.common.CipherSupplier; import org.cryptomator.cryptolib.common.DestroyableSecretKey; +import org.cryptomator.cryptolib.common.ObjectPool; import javax.crypto.AEADBadTagException; import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.ShortBufferException; import javax.crypto.spec.GCMParameterSpec; @@ -102,7 +104,7 @@ void encryptChunk(ByteBuffer cleartextChunk, ByteBuffer ciphertextChunk, long ch random.nextBytes(nonce); // payload: - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.encrypt(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encrypt(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { final byte[] chunkNumberBigEndian = longToBigEndianByteArray(chunkNumber); cipher.get().updateAAD(chunkNumberBigEndian); cipher.get().updateAAD(headerNonce); @@ -134,7 +136,7 @@ void decryptChunk(ByteBuffer ciphertextChunk, ByteBuffer cleartextChunk, long ch assert payloadBuf.remaining() >= GCM_TAG_SIZE; // payload: - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.decrypt(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.decrypt(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { final byte[] chunkNumberBigEndian = longToBigEndianByteArray(chunkNumber); cipher.get().updateAAD(chunkNumberBigEndian); cipher.get().updateAAD(headerNonce); diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java index ae365e3..f9e13c4 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java @@ -14,9 +14,11 @@ import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.common.CipherSupplier; import org.cryptomator.cryptolib.common.DestroyableSecretKey; +import org.cryptomator.cryptolib.common.ObjectPool; import javax.crypto.AEADBadTagException; import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.ShortBufferException; import javax.crypto.spec.GCMParameterSpec; @@ -60,7 +62,7 @@ public ByteBuffer encryptHeader(FileHeader header) { result.put(headerImpl.getNonce()); // encrypt payload: - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.encrypt(ek, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, headerImpl.getNonce()))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encrypt(ek, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, headerImpl.getNonce()))) { int encrypted = cipher.get().doFinal(payloadCleartextBuf, result); assert encrypted == FileHeaderImpl.PAYLOAD_LEN + FileHeaderImpl.TAG_LEN; } @@ -92,7 +94,7 @@ public FileHeader decryptHeader(ByteBuffer ciphertextHeaderBuf) throws Authentic ByteBuffer payloadCleartextBuf = ByteBuffer.allocate(FileHeaderImpl.Payload.SIZE + GCM_TAG_SIZE); try (DestroyableSecretKey ek = masterkey.getEncKey()) { // decrypt payload: - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.decrypt(ek, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.decrypt(ek, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { int decrypted = cipher.get().doFinal(ByteBuffer.wrap(ciphertextAndTag), payloadCleartextBuf); assert decrypted == FileHeaderImpl.Payload.SIZE; } diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java index fe21abb..1f4a573 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java @@ -14,11 +14,14 @@ import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.common.DestroyableSecretKey; import org.cryptomator.cryptolib.common.MessageDigestSupplier; +import org.cryptomator.cryptolib.common.ObjectPool; import org.cryptomator.siv.SivMode; import org.cryptomator.siv.UnauthenticCiphertextException; import javax.crypto.IllegalBlockSizeException; +import java.security.MessageDigest; + import static java.nio.charset.StandardCharsets.UTF_8; class FileNameCryptorImpl implements FileNameCryptor { @@ -40,7 +43,7 @@ protected SivMode initialValue() { @Override public String hashDirectoryId(String cleartextDirectoryId) { try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey(); - MessageDigestSupplier.ReusableMessageDigest sha1 = MessageDigestSupplier.SHA1.instance()) { + ObjectPool.Lease sha1 = MessageDigestSupplier.SHA1.instance()) { byte[] cleartextBytes = cleartextDirectoryId.getBytes(UTF_8); byte[] encryptedBytes = AES_SIV.get().encrypt(ek, mk, cleartextBytes); byte[] hashedBytes = sha1.get().digest(encryptedBytes); diff --git a/src/test/java/org/cryptomator/cryptolib/common/MacSupplierTest.java b/src/test/java/org/cryptomator/cryptolib/common/MacSupplierTest.java index 4aefda4..2155138 100644 --- a/src/test/java/org/cryptomator/cryptolib/common/MacSupplierTest.java +++ b/src/test/java/org/cryptomator/cryptolib/common/MacSupplierTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.lang.reflect.Method; @@ -30,11 +31,11 @@ public void testConstructorWithInvalidDigest() { @Test public void testGetMac() { SecretKey key = new SecretKeySpec(new byte[16], "HmacSHA256"); - try (MacSupplier.ReusableMac mac1 = MacSupplier.HMAC_SHA256.keyed(key)) { + try (ObjectPool.Lease mac1 = MacSupplier.HMAC_SHA256.keyed(key)) { Assertions.assertNotNull(mac1); } - try (MacSupplier.ReusableMac mac2 = MacSupplier.HMAC_SHA256.keyed(key)) { + try (ObjectPool.Lease mac2 = MacSupplier.HMAC_SHA256.keyed(key)) { Assertions.assertNotNull(mac2); } } diff --git a/src/test/java/org/cryptomator/cryptolib/common/MessageDigestSupplierTest.java b/src/test/java/org/cryptomator/cryptolib/common/MessageDigestSupplierTest.java index aab00fc..6c4d290 100644 --- a/src/test/java/org/cryptomator/cryptolib/common/MessageDigestSupplierTest.java +++ b/src/test/java/org/cryptomator/cryptolib/common/MessageDigestSupplierTest.java @@ -11,6 +11,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.security.MessageDigest; + public class MessageDigestSupplierTest { @Test @@ -22,11 +24,11 @@ public void testConstructorWithInvalidDigest() { @Test public void testGetSha1() { - try (MessageDigestSupplier.ReusableMessageDigest digest = MessageDigestSupplier.SHA1.instance()) { + try (ObjectPool.Lease digest = MessageDigestSupplier.SHA1.instance()) { Assertions.assertNotNull(digest); } - try (MessageDigestSupplier.ReusableMessageDigest digest = MessageDigestSupplier.SHA1.instance()) { + try (ObjectPool.Lease digest = MessageDigestSupplier.SHA1.instance()) { Assertions.assertNotNull(digest); } } diff --git a/src/test/java/org/cryptomator/cryptolib/common/ObjectPoolTest.java b/src/test/java/org/cryptomator/cryptolib/common/ObjectPoolTest.java index 2838d76..a435c7d 100644 --- a/src/test/java/org/cryptomator/cryptolib/common/ObjectPoolTest.java +++ b/src/test/java/org/cryptomator/cryptolib/common/ObjectPoolTest.java @@ -21,8 +21,8 @@ public void setup() { @Test @DisplayName("new instance is created if pool is empty") public void testCreateNewObjWhenPoolIsEmpty() { - try (ObjectPool.Lease lease1 = pool.get()) { - try (ObjectPool.Lease lease2 = pool.get()) { + try (ObjectPool.Lease lease1 = pool.get()) { + try (ObjectPool.Lease lease2 = pool.get()) { Assertions.assertNotSame(lease1.get(), lease2.get()); } } @@ -33,10 +33,10 @@ public void testCreateNewObjWhenPoolIsEmpty() { @DisplayName("recycle existing instance") public void testRecycleExistingObj() { Foo foo1; - try (ObjectPool.Lease lease = pool.get()) { + try (ObjectPool.Lease lease = pool.get()) { foo1 = lease.get(); } - try (ObjectPool.Lease lease = pool.get()) { + try (ObjectPool.Lease lease = pool.get()) { Assertions.assertSame(foo1, lease.get()); } Mockito.verify(factory, Mockito.times(1)).get(); @@ -45,11 +45,11 @@ public void testRecycleExistingObj() { @Test @DisplayName("create new instance when pool is GC'ed") public void testGc() { - try (ObjectPool.Lease lease = pool.get()) { + try (ObjectPool.Lease lease = pool.get()) { Assertions.assertNotNull(lease.get()); } System.gc(); // seems to be reliable on Temurin 17 with @RepeatedTest(1000) - try (ObjectPool.Lease lease = pool.get()) { + try (ObjectPool.Lease lease = pool.get()) { Assertions.assertNotNull(lease.get()); } Mockito.verify(factory, Mockito.times(2)).get(); diff --git a/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java b/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java index 6873e2b..d9fcbc9 100644 --- a/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java +++ b/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java @@ -2,6 +2,7 @@ import org.cryptomator.cryptolib.common.CipherSupplier; import org.cryptomator.cryptolib.common.GcmTestHelper; +import org.cryptomator.cryptolib.common.ObjectPool; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -10,6 +11,7 @@ import org.junit.jupiter.params.provider.CsvSource; import javax.crypto.AEADBadTagException; +import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; /** @@ -23,7 +25,7 @@ public class GcmWithSecretNonceTest { public void setup() { // reset cipher state to avoid InvalidAlgorithmParameterExceptions due to IV-reuse GcmTestHelper.reset((mode, key, params) -> { - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.encrypt(key, params)) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encrypt(key, params)) { cipher.get(); } }); diff --git a/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java b/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java index fd4796c..25ce394 100644 --- a/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java +++ b/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java @@ -14,11 +14,13 @@ import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.common.CipherSupplier; import org.cryptomator.cryptolib.common.GcmTestHelper; +import org.cryptomator.cryptolib.common.ObjectPool; import org.cryptomator.cryptolib.common.SecureRandomMock; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import java.nio.ByteBuffer; import java.security.SecureRandom; @@ -38,7 +40,7 @@ public void setup() { // reset cipher state to avoid InvalidAlgorithmParameterExceptions due to IV-reuse GcmTestHelper.reset((mode, key, params) -> { - try (CipherSupplier.ReusableCipher cipher = CipherSupplier.AES_GCM.encrypt(key, params)) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encrypt(key, params)) { cipher.get(); } }); From 0c2871f83799eb1182ce21ac3f152c2a03a71660 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 6 Dec 2021 10:18:27 +0100 Subject: [PATCH 13/30] added more javadoc --- .../cryptomator/cryptolib/common/ObjectPool.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/org/cryptomator/cryptolib/common/ObjectPool.java b/src/main/java/org/cryptomator/cryptolib/common/ObjectPool.java index 715f07d..00de730 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/ObjectPool.java +++ b/src/main/java/org/cryptomator/cryptolib/common/ObjectPool.java @@ -5,6 +5,20 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.Supplier; +/** + * A simple object pool for resources that are expensive to create but are needed frequently. + *

+ * Example Usage: + *

{@code
+ *     Supplier fooFactory = () -> new Foo();
+ *     ObjectPool fooPool = new ObjectPool(fooFactory);
+ *     try (ObjectPool.Lease lease = fooPool.get()) { // attempts to get a pooled Foo or invokes factory
+ *         lease.get().foo(); // exclusively use Foo instance
+ *     } // releases instance back to the pool when done
+ * }
+ * + * @param Type of the pooled objects + */ public class ObjectPool { private final Queue> returnedInstances; From 5bffe87e1116859c852ee221d876bf944d43d50a Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 6 Dec 2021 10:25:08 +0100 Subject: [PATCH 14/30] renamed methods in CipherSupplier --- .../cryptomator/cryptolib/common/AesKeyWrap.java | 4 ++-- .../cryptolib/common/CipherSupplier.java | 16 ++++++++-------- .../cryptolib/ecies/GcmWithSecretNonce.java | 4 ++-- .../cryptolib/v1/FileContentCryptorImpl.java | 4 ++-- .../cryptolib/v1/FileHeaderCryptorImpl.java | 4 ++-- .../cryptolib/v2/FileContentCryptorImpl.java | 4 ++-- .../cryptolib/v2/FileHeaderCryptorImpl.java | 4 ++-- .../cryptolib/common/CipherSupplierTest.java | 6 ++---- .../cryptolib/ecies/GcmWithSecretNonceTest.java | 3 +-- .../cryptolib/v2/FileHeaderCryptorImplTest.java | 5 +---- 10 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java b/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java index e00480d..5b603e7 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java +++ b/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java @@ -23,7 +23,7 @@ public class AesKeyWrap { */ public static byte[] wrap(DestroyableSecretKey kek, SecretKey key) { try (DestroyableSecretKey kekCopy = kek.copy(); - ObjectPool.Lease cipher = CipherSupplier.RFC3394_KEYWRAP.wrap(kekCopy)) { + ObjectPool.Lease cipher = CipherSupplier.RFC3394_KEYWRAP.keyWrapCipher(kekCopy)) { return cipher.get().wrap(key); } catch (InvalidKeyException | IllegalBlockSizeException e) { throw new IllegalArgumentException("Unable to wrap key.", e); @@ -44,7 +44,7 @@ public static DestroyableSecretKey unwrap(DestroyableSecretKey kek, byte[] wrapp // visible for testing static DestroyableSecretKey unwrap(DestroyableSecretKey kek, byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType) throws InvalidKeyException { try (DestroyableSecretKey kekCopy = kek.copy(); - ObjectPool.Lease cipher = CipherSupplier.RFC3394_KEYWRAP.unwrap(kekCopy)) { + ObjectPool.Lease cipher = CipherSupplier.RFC3394_KEYWRAP.keyUnwrapCipher(kekCopy)) { return DestroyableSecretKey.from(cipher.get().unwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType)); } catch (NoSuchAlgorithmException e) { throw new IllegalArgumentException("Invalid algorithm: " + wrappedKeyAlgorithm, e); diff --git a/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java b/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java index b3af7d9..36c3b22 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java +++ b/src/main/java/org/cryptomator/cryptolib/common/CipherSupplier.java @@ -48,7 +48,7 @@ private Cipher createCipher() { * @param params Params such as IV/Nonce * @return A lease supplying a refurbished Cipher */ - public ObjectPool.Lease encrypt(SecretKey key, AlgorithmParameterSpec params) { + public ObjectPool.Lease encryptionCipher(SecretKey key, AlgorithmParameterSpec params) { ObjectPool.Lease lease = cipherPool.get(); initMode(lease.get(), Cipher.ENCRYPT_MODE, key, params); return lease; @@ -60,7 +60,7 @@ public ObjectPool.Lease encrypt(SecretKey key, AlgorithmParameterSpec pa * @param key Encryption key * @param params Params such as IV/Nonce * @return New Cipher instance - * @deprecated Use {@link #encrypt(SecretKey, AlgorithmParameterSpec)} instead. + * @deprecated Use {@link #encryptionCipher(SecretKey, AlgorithmParameterSpec)} instead. */ @Deprecated public Cipher forEncryption(SecretKey key, AlgorithmParameterSpec params) { @@ -76,7 +76,7 @@ public Cipher forEncryption(SecretKey key, AlgorithmParameterSpec params) { * @param params Params such as IV/Nonce * @return A lease supplying a refurbished Cipher */ - public ObjectPool.Lease decrypt(SecretKey key, AlgorithmParameterSpec params) { + public ObjectPool.Lease decryptionCipher(SecretKey key, AlgorithmParameterSpec params) { ObjectPool.Lease lease = cipherPool.get(); initMode(lease.get(), Cipher.DECRYPT_MODE, key, params); return lease; @@ -88,7 +88,7 @@ public ObjectPool.Lease decrypt(SecretKey key, AlgorithmParameterSpec pa * @param key Encryption key * @param params Params such as IV/Nonce * @return New Cipher instance - * @deprecated Use {@link #decrypt(SecretKey, AlgorithmParameterSpec)} instead. + * @deprecated Use {@link #decryptionCipher(SecretKey, AlgorithmParameterSpec)} instead. */ @Deprecated public Cipher forDecryption(SecretKey key, AlgorithmParameterSpec params) { @@ -103,7 +103,7 @@ public Cipher forDecryption(SecretKey key, AlgorithmParameterSpec params) { * @param kek Key encryption key * @return A lease supplying a refurbished Cipher */ - public ObjectPool.Lease wrap(SecretKey kek) { + public ObjectPool.Lease keyWrapCipher(SecretKey kek) { ObjectPool.Lease lease = cipherPool.get(); initMode(lease.get(), Cipher.WRAP_MODE, kek, null); return lease; @@ -114,7 +114,7 @@ public ObjectPool.Lease wrap(SecretKey kek) { * * @param kek Key encryption key * @return New Cipher instance - * @deprecated Use {@link #wrap(SecretKey)} instead. + * @deprecated Use {@link #keyWrapCipher(SecretKey)} instead. */ @Deprecated public Cipher forWrapping(SecretKey kek) { @@ -129,7 +129,7 @@ public Cipher forWrapping(SecretKey kek) { * @param kek Key encryption key * @return A lease supplying a refurbished Cipher */ - public ObjectPool.Lease unwrap(SecretKey kek) { + public ObjectPool.Lease keyUnwrapCipher(SecretKey kek) { ObjectPool.Lease lease = cipherPool.get(); initMode(lease.get(), Cipher.UNWRAP_MODE, kek, null); return lease; @@ -140,7 +140,7 @@ public ObjectPool.Lease unwrap(SecretKey kek) { * * @param kek Key encryption key * @return New Cipher instance - * @deprecated Use {@link #unwrap(SecretKey)} instead. + * @deprecated Use {@link #keyUnwrapCipher(SecretKey)} instead. */ @Deprecated public Cipher forUnwrapping(SecretKey kek) { diff --git a/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java b/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java index 9cfc015..093af59 100644 --- a/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java +++ b/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java @@ -27,7 +27,7 @@ public int requiredSecretBytes() { public byte[] encrypt(byte[] secret, byte[] plaintext) { try (DestroyableSecretKey key = new DestroyableSecretKey(secret, 0, GCM_KEY_SIZE, "AES")) { byte[] nonce = Arrays.copyOfRange(secret, GCM_KEY_SIZE, GCM_KEY_SIZE + GCM_NONCE_SIZE); - try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encrypt(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encryptionCipher(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { return cipher.get().doFinal(plaintext); } } catch (IllegalBlockSizeException | BadPaddingException e) { @@ -39,7 +39,7 @@ public byte[] encrypt(byte[] secret, byte[] plaintext) { public byte[] decrypt(byte[] secret, byte[] ciphertext) throws AEADBadTagException { try (DestroyableSecretKey key = new DestroyableSecretKey(secret, 0, GCM_KEY_SIZE, "AES")) { byte[] nonce = Arrays.copyOfRange(secret, GCM_KEY_SIZE, GCM_KEY_SIZE + GCM_NONCE_SIZE); - try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.decrypt(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.decryptionCipher(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { return cipher.get().doFinal(ciphertext); } } catch (IllegalBlockSizeException | BadPaddingException e) { diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java index 807e686..74b62c6 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java @@ -112,7 +112,7 @@ void encryptChunk(ByteBuffer cleartextChunk, ByteBuffer ciphertextChunk, long ch // payload: int bytesEncrypted; - try (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.encrypt(fk, new IvParameterSpec(nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.encryptionCipher(fk, new IvParameterSpec(nonce))) { assert ciphertextChunk.remaining() >= cipher.get().getOutputSize(cleartextChunk.remaining()) + MAC_SIZE; bytesEncrypted = cipher.get().doFinal(cleartextChunk, ciphertextChunk); } @@ -146,7 +146,7 @@ void decryptChunk(ByteBuffer ciphertextChunk, ByteBuffer cleartextChunk, Destroy payloadBuf.position(NONCE_SIZE).limit(ciphertextChunk.limit() - MAC_SIZE); // payload: - try (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.decrypt(fk, new IvParameterSpec(nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.decryptionCipher(fk, new IvParameterSpec(nonce))) { assert cleartextChunk.remaining() >= cipher.get().getOutputSize(payloadBuf.remaining()); cipher.get().doFinal(payloadBuf, cleartextChunk); } diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java index d5b127b..6721814 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java @@ -62,7 +62,7 @@ public ByteBuffer encryptHeader(FileHeader header) { result.put(headerImpl.getNonce()); // encrypt payload: - try (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.encrypt(ek, new IvParameterSpec(headerImpl.getNonce()))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.encryptionCipher(ek, new IvParameterSpec(headerImpl.getNonce()))) { int encrypted = cipher.get().doFinal(payloadCleartextBuf, result); assert encrypted == FileHeaderImpl.Payload.SIZE; } @@ -117,7 +117,7 @@ public FileHeader decryptHeader(ByteBuffer ciphertextHeaderBuf) throws Authentic ByteBuffer payloadCleartextBuf = ByteBuffer.allocate(FileHeaderImpl.Payload.SIZE); try (DestroyableSecretKey ek = masterkey.getEncKey()) { // decrypt payload: - try (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.decrypt(ek, new IvParameterSpec(nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.decryptionCipher(ek, new IvParameterSpec(nonce))) { assert cipher.get().getOutputSize(ciphertextPayload.length) == payloadCleartextBuf.remaining(); int decrypted = cipher.get().doFinal(ByteBuffer.wrap(ciphertextPayload), payloadCleartextBuf); assert decrypted == FileHeaderImpl.Payload.SIZE; diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java index 98bdf61..4abaf68 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java @@ -104,7 +104,7 @@ void encryptChunk(ByteBuffer cleartextChunk, ByteBuffer ciphertextChunk, long ch random.nextBytes(nonce); // payload: - try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encrypt(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encryptionCipher(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { final byte[] chunkNumberBigEndian = longToBigEndianByteArray(chunkNumber); cipher.get().updateAAD(chunkNumberBigEndian); cipher.get().updateAAD(headerNonce); @@ -136,7 +136,7 @@ void decryptChunk(ByteBuffer ciphertextChunk, ByteBuffer cleartextChunk, long ch assert payloadBuf.remaining() >= GCM_TAG_SIZE; // payload: - try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.decrypt(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.decryptionCipher(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { final byte[] chunkNumberBigEndian = longToBigEndianByteArray(chunkNumber); cipher.get().updateAAD(chunkNumberBigEndian); cipher.get().updateAAD(headerNonce); diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java index f9e13c4..ac294bf 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java @@ -62,7 +62,7 @@ public ByteBuffer encryptHeader(FileHeader header) { result.put(headerImpl.getNonce()); // encrypt payload: - try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encrypt(ek, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, headerImpl.getNonce()))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encryptionCipher(ek, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, headerImpl.getNonce()))) { int encrypted = cipher.get().doFinal(payloadCleartextBuf, result); assert encrypted == FileHeaderImpl.PAYLOAD_LEN + FileHeaderImpl.TAG_LEN; } @@ -94,7 +94,7 @@ public FileHeader decryptHeader(ByteBuffer ciphertextHeaderBuf) throws Authentic ByteBuffer payloadCleartextBuf = ByteBuffer.allocate(FileHeaderImpl.Payload.SIZE + GCM_TAG_SIZE); try (DestroyableSecretKey ek = masterkey.getEncKey()) { // decrypt payload: - try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.decrypt(ek, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.decryptionCipher(ek, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { int decrypted = cipher.get().doFinal(ByteBuffer.wrap(ciphertextAndTag), payloadCleartextBuf); assert decrypted == FileHeaderImpl.Payload.SIZE; } diff --git a/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java b/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java index 0c62435..777ef95 100644 --- a/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java +++ b/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java @@ -13,10 +13,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.RC5ParameterSpec; -import javax.crypto.spec.SecretKeySpec; public class CipherSupplierTest { @@ -31,7 +29,7 @@ public void testGetUnknownCipher() { public void testGetCipherWithInvalidKey() { CipherSupplier supplier = new CipherSupplier("AES/CBC/PKCS5Padding"); IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> { - supplier.encrypt(new DestroyableSecretKey(new byte[13], "AES"), new IvParameterSpec(new byte[16])); + supplier.encryptionCipher(new DestroyableSecretKey(new byte[13], "AES"), new IvParameterSpec(new byte[16])); }); MatcherAssert.assertThat(exception.getMessage(), CoreMatchers.containsString("Invalid key")); } @@ -40,7 +38,7 @@ public void testGetCipherWithInvalidKey() { public void testGetCipherWithInvalidAlgorithmParam() { CipherSupplier supplier = new CipherSupplier("AES/CBC/PKCS5Padding"); IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> { - supplier.encrypt(new DestroyableSecretKey(new byte[16], "AES"), new RC5ParameterSpec(1, 1, 8)); + supplier.encryptionCipher(new DestroyableSecretKey(new byte[16], "AES"), new RC5ParameterSpec(1, 1, 8)); }); MatcherAssert.assertThat(exception.getMessage(), CoreMatchers.containsString("Algorithm parameter not appropriate for")); } diff --git a/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java b/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java index d9fcbc9..48cb923 100644 --- a/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java +++ b/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java @@ -12,7 +12,6 @@ import javax.crypto.AEADBadTagException; import javax.crypto.Cipher; -import javax.crypto.spec.GCMParameterSpec; /** * Test vectors from https://csrc.nist.rip/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf @@ -25,7 +24,7 @@ public class GcmWithSecretNonceTest { public void setup() { // reset cipher state to avoid InvalidAlgorithmParameterExceptions due to IV-reuse GcmTestHelper.reset((mode, key, params) -> { - try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encrypt(key, params)) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encryptionCipher(key, params)) { cipher.get(); } }); diff --git a/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java b/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java index 25ce394..c9e1c3b 100644 --- a/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java +++ b/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java @@ -21,12 +21,9 @@ import org.junit.jupiter.api.Test; import javax.crypto.Cipher; -import javax.crypto.spec.GCMParameterSpec; import java.nio.ByteBuffer; import java.security.SecureRandom; -import static org.cryptomator.cryptolib.v2.Constants.GCM_TAG_SIZE; - public class FileHeaderCryptorImplTest { private static final SecureRandom RANDOM_MOCK = SecureRandomMock.NULL_RANDOM; @@ -40,7 +37,7 @@ public void setup() { // reset cipher state to avoid InvalidAlgorithmParameterExceptions due to IV-reuse GcmTestHelper.reset((mode, key, params) -> { - try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encrypt(key, params)) { + try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encryptionCipher(key, params)) { cipher.get(); } }); From a0954d48548da1701aa3d37dde1bb200271636ce Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 6 Dec 2021 12:37:13 +0100 Subject: [PATCH 15/30] Add benchmark for pooled suppliers --- .../common/PooledSuppliersBenchmarkTest.java | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/test/java/org/cryptomator/cryptolib/common/PooledSuppliersBenchmarkTest.java diff --git a/src/test/java/org/cryptomator/cryptolib/common/PooledSuppliersBenchmarkTest.java b/src/test/java/org/cryptomator/cryptolib/common/PooledSuppliersBenchmarkTest.java new file mode 100644 index 0000000..471515a --- /dev/null +++ b/src/test/java/org/cryptomator/cryptolib/common/PooledSuppliersBenchmarkTest.java @@ -0,0 +1,99 @@ +package org.cryptomator.cryptolib.common; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.MessageDigest; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +@State(Scope.Thread) +@Warmup(iterations = 2) +@Measurement(iterations = 2) +@BenchmarkMode(value = {Mode.AverageTime}) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +public class PooledSuppliersBenchmarkTest { + + private static final Random RNG = new Random(42); + private static final CipherSupplier CIPHER_SUPPLIER = CipherSupplier.AES_GCM; + private static final MessageDigestSupplier MD_SUPPLIER = MessageDigestSupplier.SHA256; + private static final MacSupplier MAC_SUPPLIER = MacSupplier.HMAC_SHA256; + private SecretKey key; + private GCMParameterSpec gcmParams; + + @Disabled("only on demand") + @Test + public void runBenchmarks() throws RunnerException { + Options opt = new OptionsBuilder() // + .include(getClass().getName()) // + .threads(2).forks(1) // + .shouldFailOnError(true).shouldDoGC(true) // + .build(); + new Runner(opt).run(); + } + + @Setup(Level.Invocation) + public void shuffleData() { + byte[] bytes = new byte[28]; + RNG.nextBytes(bytes); + this.key = new SecretKeySpec(bytes, 0, 16, "AES"); + this.gcmParams = new GCMParameterSpec(128, bytes, 16, 12); + } + + @Benchmark + public void createCipher(Blackhole blackHole) { + blackHole.consume(CIPHER_SUPPLIER.forEncryption(key, gcmParams)); + } + + @Benchmark + public void recycleCipher(Blackhole blackHole) { + try (ObjectPool.Lease lease = CIPHER_SUPPLIER.encryptionCipher(key, gcmParams)) { + blackHole.consume(lease.get()); + } + } + + @Benchmark + public void createMac(Blackhole blackHole) { + blackHole.consume(MAC_SUPPLIER.withKey(key)); + } + + @Benchmark + public void recycleMac(Blackhole blackHole) { + try (ObjectPool.Lease lease = MAC_SUPPLIER.keyed(key)) { + blackHole.consume(lease.get()); + } + } + + @Benchmark + public void createMd(Blackhole blackHole) { + blackHole.consume(MD_SUPPLIER.get()); + } + + @Benchmark + public void recycleMd(Blackhole blackHole) { + try (ObjectPool.Lease lease = MD_SUPPLIER.instance()) { + blackHole.consume(lease.get()); + } + } + +} From f83db3db8b063ac82512f67d88814f3d645e95a3 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 8 Dec 2021 16:05:17 +0100 Subject: [PATCH 16/30] check if public key is a point on the curve --- .../cryptolib/common/ECKeyPair.java | 39 +++++++- .../cryptolib/common/P384KeyPair.java | 17 +++- .../cryptolib/common/ECKeyPairTest.java | 93 +++++++++++++++++-- 3 files changed, 139 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/common/ECKeyPair.java b/src/main/java/org/cryptomator/cryptolib/common/ECKeyPair.java index 8092e05..19bbe12 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/ECKeyPair.java +++ b/src/main/java/org/cryptomator/cryptolib/common/ECKeyPair.java @@ -3,10 +3,16 @@ import com.google.common.base.Preconditions; import javax.security.auth.Destroyable; +import java.math.BigInteger; import java.security.KeyPair; import java.security.MessageDigest; +import java.security.PublicKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.security.spec.ECFieldFp; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.EllipticCurve; import java.util.Arrays; import java.util.Objects; @@ -15,10 +21,10 @@ public class ECKeyPair implements Destroyable { private final KeyPair keyPair; private boolean destroyed; - ECKeyPair(KeyPair keyPair) { + ECKeyPair(KeyPair keyPair, ECParameterSpec curveParams) { Preconditions.checkArgument(keyPair.getPrivate() instanceof ECPrivateKey); Preconditions.checkArgument(keyPair.getPublic() instanceof ECPublicKey); - this.keyPair = keyPair; + this.keyPair = verify(keyPair, curveParams); } public KeyPair keyPair() { @@ -37,6 +43,35 @@ public ECPublicKey getPublic() { return (ECPublicKey) keyPair.getPublic(); } + // validations taken from https://neilmadden.blog/2017/05/17/so-how-do-you-validate-nist-ecdh-public-keys/ + private static KeyPair verify(KeyPair keyPair, ECParameterSpec curveParams) { + PublicKey pk = keyPair.getPublic(); + Preconditions.checkArgument(pk instanceof ECPublicKey, "Not an EC key"); + Preconditions.checkArgument(curveParams.getCofactor() == 1, "Verifying points on curves with cofactor not supported"); // see "Step 4" in linked post + ECPublicKey publicKey = (ECPublicKey) pk; + EllipticCurve curve = curveParams.getCurve(); + + // Step 1: Verify public key is not point at infinity. + Preconditions.checkArgument(!ECPoint.POINT_INFINITY.equals(publicKey.getW()), "Invalid Key"); + + final BigInteger x = publicKey.getW().getAffineX(); + final BigInteger y = publicKey.getW().getAffineY(); + final BigInteger p = ((ECFieldFp) curve.getField()).getP(); + + // Step 2: Verify x and y are in range [0,p-1] + Preconditions.checkArgument(x.compareTo(BigInteger.ZERO) >= 0 && x.compareTo(p) < 0, "Invalid Key"); + Preconditions.checkArgument(y.compareTo(BigInteger.ZERO) >= 0 && y.compareTo(p) < 0, "Invalid Key"); + + // Step 3: Verify that y^2 == x^3 + ax + b (mod p) + final BigInteger a = curve.getA(); + final BigInteger b = curve.getB(); + final BigInteger ySquared = y.modPow(BigInteger.valueOf(2), p); + final BigInteger xCubedPlusAXPlusB = x.modPow(BigInteger.valueOf(3), p).add(a.multiply(x)).add(b).mod(p); + Preconditions.checkArgument(ySquared.equals(xCubedPlusAXPlusB), "Invalid key"); + + return keyPair; + } + @Override public boolean isDestroyed() { return destroyed; diff --git a/src/main/java/org/cryptomator/cryptolib/common/P384KeyPair.java b/src/main/java/org/cryptomator/cryptolib/common/P384KeyPair.java index a6ecbbb..79bf72c 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/P384KeyPair.java +++ b/src/main/java/org/cryptomator/cryptolib/common/P384KeyPair.java @@ -7,11 +7,14 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; +import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.InvalidParameterSpecException; public class P384KeyPair extends ECKeyPair { @@ -20,7 +23,7 @@ public class P384KeyPair extends ECKeyPair { private static final String SIGNATURE_ALG = "SHA384withECDSA"; private P384KeyPair(KeyPair keyPair) { - super(keyPair); + super(keyPair, getCurveParams()); } public static P384KeyPair generate() { @@ -97,4 +100,16 @@ private static KeyPairGenerator getKeyPairGenerator() { } } + private static ECParameterSpec getCurveParams() { + try { + AlgorithmParameters parameters = AlgorithmParameters.getInstance(EC_ALG); + parameters.init(new ECGenParameterSpec(EC_CURVE_NAME)); + return parameters.getParameterSpec(ECParameterSpec.class); + } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) { + throw new IllegalStateException(EC_CURVE_NAME + " curve not supported"); + } + } + + + } diff --git a/src/test/java/org/cryptomator/cryptolib/common/ECKeyPairTest.java b/src/test/java/org/cryptomator/cryptolib/common/ECKeyPairTest.java index bc6ddcb..22f6390 100644 --- a/src/test/java/org/cryptomator/cryptolib/common/ECKeyPairTest.java +++ b/src/test/java/org/cryptomator/cryptolib/common/ECKeyPairTest.java @@ -5,21 +5,36 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.Mockito; +import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECFieldFp; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.EllipticCurve; public class ECKeyPairTest { @Test public void testConstructorFailsForInvalidAlgorithm() throws NoSuchAlgorithmException { KeyPair rsaKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + ECParameterSpec curveParams = Mockito.mock(ECParameterSpec.class); Assertions.assertThrows(IllegalArgumentException.class, () -> { - new ECKeyPair(rsaKeyPair); + new ECKeyPair(rsaKeyPair, curveParams); }); } + private ECParameterSpec getParamsFromPublicKey(KeyPair keyPair) { + return ((ECPublicKey)keyPair.getPublic()).getParams(); + } + @Nested @DisplayName("With undestroyed key...") public class WithUndestroyed { @@ -32,7 +47,7 @@ public class WithUndestroyed { public void setup() throws NoSuchAlgorithmException { this.keyPair1 = KeyPairGenerator.getInstance("EC").generateKeyPair(); this.keyPair2 = KeyPairGenerator.getInstance("EC").generateKeyPair(); - this.ecKeyPair = new ECKeyPair(keyPair1); + this.ecKeyPair = new ECKeyPair(keyPair1, getParamsFromPublicKey(keyPair1)); } @Test @@ -57,8 +72,8 @@ public void testDestroy() { @Test public void testEquals() { - ECKeyPair other1 = new ECKeyPair(keyPair1); - ECKeyPair other2 = new ECKeyPair(keyPair2); + ECKeyPair other1 = new ECKeyPair(keyPair1, getParamsFromPublicKey(keyPair1)); + ECKeyPair other2 = new ECKeyPair(keyPair2, getParamsFromPublicKey(keyPair2)); Assertions.assertNotSame(ecKeyPair, other1); Assertions.assertEquals(ecKeyPair, other1); Assertions.assertNotSame(ecKeyPair, other2); @@ -67,8 +82,8 @@ public void testEquals() { @Test public void testHashCode() { - ECKeyPair other1 = new ECKeyPair(keyPair1); - ECKeyPair other2 = new ECKeyPair(keyPair2); + ECKeyPair other1 = new ECKeyPair(keyPair1, getParamsFromPublicKey(keyPair1)); + ECKeyPair other2 = new ECKeyPair(keyPair2, getParamsFromPublicKey(keyPair2)); Assertions.assertEquals(ecKeyPair.hashCode(), other1.hashCode()); Assertions.assertNotEquals(ecKeyPair.hashCode(), other2.hashCode()); } @@ -85,7 +100,7 @@ public class WithDestroyed { @BeforeEach public void setup() throws NoSuchAlgorithmException { this.keyPair = KeyPairGenerator.getInstance("EC").generateKeyPair(); - this.ecKeyPair = new ECKeyPair(keyPair); + this.ecKeyPair = new ECKeyPair(keyPair, getParamsFromPublicKey(keyPair)); this.ecKeyPair.destroy(); } @@ -111,5 +126,69 @@ public void testDestroy() { } + @Nested + @DisplayName("With invalid public key...") + public class WithInvalidPublicKey { + + private ECParameterSpec curveParams = Mockito.mock(ECParameterSpec.class); + private EllipticCurve curve = Mockito.mock(EllipticCurve.class); + private ECFieldFp field = Mockito.mock(ECFieldFp.class); + private ECPublicKey publicKey = Mockito.mock(ECPublicKey.class); + private ECPrivateKey privateKey = Mockito.mock(ECPrivateKey.class); + private KeyPair keyPair = new KeyPair(publicKey, privateKey); + + @BeforeEach + public void setup() { + Mockito.doReturn(curve).when(curveParams).getCurve(); + Mockito.doReturn(field).when(curve).getField(); + Mockito.doReturn(BigInteger.ZERO).when(curve).getA(); + Mockito.doReturn(BigInteger.ZERO).when(curve).getB(); + Mockito.doReturn(1).when(curveParams).getCofactor(); + Mockito.doReturn(new ECPoint(BigInteger.ONE, BigInteger.ONE)).when(publicKey).getW(); + Mockito.doReturn(BigInteger.valueOf(2)).when(field).getP(); + } + + @Test + public void testValid() { + Assertions.assertDoesNotThrow(() -> new ECKeyPair(keyPair, curveParams)); + } + + @Test + public void testUnsupportedCofactor() { + Mockito.doReturn(2).when(curveParams).getCofactor(); + Assertions.assertThrows(IllegalArgumentException.class, () -> new ECKeyPair(keyPair, curveParams)); + } + + @Test + public void testInfiniteW() { + Mockito.doReturn(ECPoint.POINT_INFINITY).when(publicKey).getW(); + Assertions.assertThrows(IllegalArgumentException.class, () -> new ECKeyPair(keyPair, curveParams)); + } + + @ParameterizedTest + @CsvSource(value = { + "-1, 0", + "0, -1", + "2, 0", + "0, 2", + }) + public void testInvalidPoint(int x, int y) { + Mockito.doReturn(new ECPoint(BigInteger.valueOf(x), BigInteger.valueOf(y))).when(publicKey).getW(); + Assertions.assertThrows(IllegalArgumentException.class, () -> new ECKeyPair(keyPair, curveParams)); + } + + @ParameterizedTest + @CsvSource(value = { + "1, 0", + "0, 1", + }) + public void testInvalidCurveCoefficients(int a, int b) { + Mockito.doReturn(BigInteger.valueOf(a)).when(curve).getA(); + Mockito.doReturn(BigInteger.valueOf(b)).when(curve).getB(); + Assertions.assertThrows(IllegalArgumentException.class, () -> new ECKeyPair(keyPair, curveParams)); + } + + } + } \ No newline at end of file From 718639e4fbf2d26db7d4e8539d0ad662335efeed Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 8 Dec 2021 16:06:00 +0100 Subject: [PATCH 17/30] Added new P384KeyPair factory to create a keypair from DER or PEM-encoded keys --- .../cryptolib/common/P384KeyPair.java | 25 +++++++++++++++++++ .../cryptolib/common/P384KeyPairTest.java | 14 +++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/main/java/org/cryptomator/cryptolib/common/P384KeyPair.java b/src/main/java/org/cryptomator/cryptolib/common/P384KeyPair.java index 79bf72c..feb7d5f 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/P384KeyPair.java +++ b/src/main/java/org/cryptomator/cryptolib/common/P384KeyPair.java @@ -9,12 +9,18 @@ import java.nio.file.StandardOpenOption; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; +import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; +import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidParameterSpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; public class P384KeyPair extends ECKeyPair { @@ -31,6 +37,25 @@ public static P384KeyPair generate() { return new P384KeyPair(keyPair); } + /** + * Creates a key pair from the given key specs. + * + * @param publicKeySpec DER formatted public key + * @param privateKeySpec DER formatted private key + * @return created key pair + * @throws InvalidKeySpecException If the supplied key specs are unsuitable for {@value #EC_ALG} keys + */ + public static P384KeyPair create(X509EncodedKeySpec publicKeySpec, PKCS8EncodedKeySpec privateKeySpec) throws InvalidKeySpecException { + try { + KeyFactory factory = KeyFactory.getInstance(EC_ALG); + PublicKey publicKey = factory.generatePublic(publicKeySpec); + PrivateKey privateKey = factory.generatePrivate(privateKeySpec); + return new P384KeyPair(new KeyPair(publicKey, privateKey)); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(EC_ALG + " not supported"); + } + } + /** * Loads a key pair from the given file * diff --git a/src/test/java/org/cryptomator/cryptolib/common/P384KeyPairTest.java b/src/test/java/org/cryptomator/cryptolib/common/P384KeyPairTest.java index 13a8aaa..a3c2cea 100644 --- a/src/test/java/org/cryptomator/cryptolib/common/P384KeyPairTest.java +++ b/src/test/java/org/cryptomator/cryptolib/common/P384KeyPairTest.java @@ -9,6 +9,10 @@ import java.io.IOException; import java.nio.file.Path; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; public class P384KeyPairTest { @@ -22,6 +26,16 @@ public void testGenerate() { Assertions.assertNotEquals(keyPair1, keyPair2); } + @Test + @DisplayName("create()") + public void testCreate() throws InvalidKeySpecException { + X509EncodedKeySpec publicKey = new X509EncodedKeySpec(Base64.getDecoder().decode("MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAERxQR+NRN6Wga01370uBBzr2NHDbKIC56tPUEq2HX64RhITGhii8Zzbkb1HnRmdF0aq6uqmUy4jUhuxnKxsv59A6JeK7Unn+mpmm3pQAygjoGc9wrvoH4HWJSQYUlsXDu")); + PKCS8EncodedKeySpec privateKey = new PKCS8EncodedKeySpec(Base64.getDecoder().decode("ME8CAQAwEAYHKoZIzj0CAQYFK4EEACIEODA2AgEBBDEA6QybmBitf94veD5aCLr7nlkF5EZpaXHCfq1AXm57AKQyGOjTDAF9EQB28fMywTDQ")); + + P384KeyPair keyPair = P384KeyPair.create(publicKey, privateKey); + Assertions.assertNotNull(keyPair); + } + @Test @DisplayName("store(...)") public void testStore(@TempDir Path tmpDir) { From 96f00c9dddbe246f579336381edcf305b874d96c Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 25 Jan 2022 21:38:29 +0100 Subject: [PATCH 18/30] cleanup --- .../cryptolib/api/CryptoLibIntegrationTest.java | 4 ---- .../cryptolib/common/SeekableByteChannelMock.java | 10 +++++----- .../cryptolib/v1/FileContentCryptorImplTest.java | 1 - 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/test/java/org/cryptomator/cryptolib/api/CryptoLibIntegrationTest.java b/src/test/java/org/cryptomator/cryptolib/api/CryptoLibIntegrationTest.java index 0b8f78f..f0c57f8 100644 --- a/src/test/java/org/cryptomator/cryptolib/api/CryptoLibIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptolib/api/CryptoLibIntegrationTest.java @@ -8,10 +8,6 @@ *******************************************************************************/ package org.cryptomator.cryptolib.api; -import org.cryptomator.cryptolib.api.AuthenticationFailedException; -import org.cryptomator.cryptolib.api.Cryptor; -import org.cryptomator.cryptolib.api.CryptorProvider; -import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.common.DecryptingReadableByteChannel; import org.cryptomator.cryptolib.common.EncryptingWritableByteChannel; import org.cryptomator.cryptolib.common.SecureRandomMock; diff --git a/src/test/java/org/cryptomator/cryptolib/common/SeekableByteChannelMock.java b/src/test/java/org/cryptomator/cryptolib/common/SeekableByteChannelMock.java index 9f2db2f..8aa9116 100644 --- a/src/test/java/org/cryptomator/cryptolib/common/SeekableByteChannelMock.java +++ b/src/test/java/org/cryptomator/cryptolib/common/SeekableByteChannelMock.java @@ -27,7 +27,7 @@ public boolean isOpen() { } @Override - public void close() throws IOException { + public void close() { open = false; } @@ -46,7 +46,7 @@ public int read(ByteBuffer dst) throws IOException { } @Override - public int write(ByteBuffer src) throws IOException { + public int write(ByteBuffer src) { int num = Math.min(buf.remaining(), src.remaining()); ByteBuffer limitedSrc = src.asReadOnlyBuffer(); limitedSrc.limit(limitedSrc.position() + num); @@ -55,12 +55,12 @@ public int write(ByteBuffer src) throws IOException { } @Override - public long position() throws IOException { + public long position() { return buf.position(); } @Override - public SeekableByteChannel position(long newPosition) throws IOException { + public SeekableByteChannel position(long newPosition) { assert newPosition < Integer.MAX_VALUE; buf.position((int) newPosition); return this; @@ -72,7 +72,7 @@ public long size() throws IOException { } @Override - public SeekableByteChannel truncate(long size) throws IOException { + public SeekableByteChannel truncate(long size) { assert size < Integer.MAX_VALUE; if (size < buf.position()) { buf.position((int) size); diff --git a/src/test/java/org/cryptomator/cryptolib/v1/FileContentCryptorImplTest.java b/src/test/java/org/cryptomator/cryptolib/v1/FileContentCryptorImplTest.java index 007fe3f..ea6fc9a 100644 --- a/src/test/java/org/cryptomator/cryptolib/v1/FileContentCryptorImplTest.java +++ b/src/test/java/org/cryptomator/cryptolib/v1/FileContentCryptorImplTest.java @@ -38,7 +38,6 @@ import java.nio.channels.ReadableByteChannel; import java.nio.channels.SeekableByteChannel; import java.nio.channels.WritableByteChannel; -import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; From 08308819a44bc4da1b143fcbb5098830d38d993a Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 7 Feb 2022 16:29:05 +0100 Subject: [PATCH 19/30] Avoid `ByteBuffer#asReadOnlyBuffer()` (#29) --- .../cryptolib/v1/FileContentCryptorImpl.java | 35 +++++++------------ .../cryptolib/v1/FileHeaderCryptorImpl.java | 2 +- .../cryptolib/v2/FileContentCryptorImpl.java | 6 ++-- .../cryptolib/v2/FileHeaderCryptorImpl.java | 2 +- 4 files changed, 17 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java index cad0d0b..986e78f 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java @@ -112,12 +112,12 @@ void encryptChunk(ByteBuffer cleartextChunk, ByteBuffer ciphertextChunk, long ch final Cipher cipher = CipherSupplier.AES_CTR.forEncryption(fk, new IvParameterSpec(nonce)); ciphertextChunk.put(nonce); assert ciphertextChunk.remaining() >= cipher.getOutputSize(cleartextChunk.remaining()) + MAC_SIZE; - int bytesEncrypted = cipher.doFinal(cleartextChunk, ciphertextChunk); + cipher.doFinal(cleartextChunk, ciphertextChunk); // mac: - final ByteBuffer ciphertextBuf = ciphertextChunk.asReadOnlyBuffer(); - ciphertextBuf.position(NONCE_SIZE).limit(NONCE_SIZE + bytesEncrypted); - byte[] authenticationCode = calcChunkMac(headerNonce, chunkNumber, nonce, ciphertextBuf); + ByteBuffer nonceAndPayload = ciphertextChunk.duplicate(); + nonceAndPayload.flip(); + byte[] authenticationCode = calcChunkMac(headerNonce, chunkNumber, nonceAndPayload); assert authenticationCode.length == MAC_SIZE; ciphertextChunk.put(authenticationCode); } catch (ShortBufferException e) { @@ -134,12 +134,10 @@ void decryptChunk(ByteBuffer ciphertextChunk, ByteBuffer cleartextChunk, Destroy try (DestroyableSecretKey fk = fileKey.copy()) { // nonce: final byte[] nonce = new byte[NONCE_SIZE]; - final ByteBuffer chunkNonceBuf = ciphertextChunk.asReadOnlyBuffer(); - chunkNonceBuf.position(0).limit(NONCE_SIZE); - chunkNonceBuf.get(nonce); + ciphertextChunk.get(nonce, 0, NONCE_SIZE); // payload: - final ByteBuffer payloadBuf = ciphertextChunk.asReadOnlyBuffer(); + final ByteBuffer payloadBuf = ciphertextChunk.duplicate(); payloadBuf.position(NONCE_SIZE).limit(ciphertextChunk.limit() - MAC_SIZE); // payload: @@ -157,37 +155,30 @@ void decryptChunk(ByteBuffer ciphertextChunk, ByteBuffer cleartextChunk, Destroy boolean checkChunkMac(byte[] headerNonce, long chunkNumber, ByteBuffer chunkBuf) { assert chunkBuf.remaining() >= NONCE_SIZE + MAC_SIZE; - // get three components: nonce + payload + mac - final ByteBuffer chunkNonceBuf = chunkBuf.asReadOnlyBuffer(); - chunkNonceBuf.position(0).limit(NONCE_SIZE); - final ByteBuffer payloadBuf = chunkBuf.asReadOnlyBuffer(); - payloadBuf.position(NONCE_SIZE).limit(chunkBuf.limit() - MAC_SIZE); - final ByteBuffer expectedMacBuf = chunkBuf.asReadOnlyBuffer(); + // get nonce + payload + final ByteBuffer nonceAndPayload = chunkBuf.duplicate(); + nonceAndPayload.position(0).limit(chunkBuf.limit() - MAC_SIZE); + final ByteBuffer expectedMacBuf = chunkBuf.duplicate(); expectedMacBuf.position(chunkBuf.limit() - MAC_SIZE); - // get nonce: - final byte[] chunkNonce = new byte[NONCE_SIZE]; - chunkNonceBuf.get(chunkNonce); - // get expected MAC: final byte[] expectedMac = new byte[MAC_SIZE]; expectedMacBuf.get(expectedMac); // get actual MAC: - final byte[] calculatedMac = calcChunkMac(headerNonce, chunkNumber, chunkNonce, payloadBuf); + final byte[] calculatedMac = calcChunkMac(headerNonce, chunkNumber, nonceAndPayload); // time-constant equality check of two MACs: return MessageDigest.isEqual(expectedMac, calculatedMac); } - private byte[] calcChunkMac(byte[] headerNonce, long chunkNumber, byte[] chunkNonce, ByteBuffer ciphertext) { + private byte[] calcChunkMac(byte[] headerNonce, long chunkNumber, ByteBuffer nonceAndCiphertext) { try (DestroyableSecretKey mk = masterkey.getMacKey()) { final byte[] chunkNumberBigEndian = ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.BIG_ENDIAN).putLong(chunkNumber).array(); final Mac mac = MacSupplier.HMAC_SHA256.withKey(mk); mac.update(headerNonce); mac.update(chunkNumberBigEndian); - mac.update(chunkNonce); - mac.update(ciphertext); + mac.update(nonceAndCiphertext); return mac.doFinal(); } } diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java index aeaa7ee..46e8d4f 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java @@ -88,7 +88,7 @@ public FileHeader decryptHeader(ByteBuffer ciphertextHeaderBuf) throws Authentic if (ciphertextHeaderBuf.remaining() < FileHeaderImpl.SIZE) { throw new IllegalArgumentException("Malformed ciphertext header"); } - ByteBuffer buf = ciphertextHeaderBuf.asReadOnlyBuffer(); + ByteBuffer buf = ciphertextHeaderBuf.duplicate(); byte[] nonce = new byte[FileHeaderImpl.NONCE_LEN]; buf.position(FileHeaderImpl.NONCE_POS); buf.get(nonce); diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java index a03eea9..e59beff 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java @@ -124,12 +124,10 @@ void decryptChunk(ByteBuffer ciphertextChunk, ByteBuffer cleartextChunk, long ch try (DestroyableSecretKey fk = fileKey.copy()) { // nonce: final byte[] nonce = new byte[GCM_NONCE_SIZE]; - final ByteBuffer chunkNonceBuf = ciphertextChunk.asReadOnlyBuffer(); - chunkNonceBuf.position(0).limit(GCM_NONCE_SIZE); - chunkNonceBuf.get(nonce); + ciphertextChunk.get(nonce, 0, GCM_NONCE_SIZE); // payload: - final ByteBuffer payloadBuf = ciphertextChunk.asReadOnlyBuffer(); + final ByteBuffer payloadBuf = ciphertextChunk.duplicate(); payloadBuf.position(GCM_NONCE_SIZE); assert payloadBuf.remaining() >= GCM_TAG_SIZE; diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java index 509a5b9..2182e7b 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java @@ -81,7 +81,7 @@ public FileHeader decryptHeader(ByteBuffer ciphertextHeaderBuf) throws Authentic if (ciphertextHeaderBuf.remaining() < FileHeaderImpl.SIZE) { throw new IllegalArgumentException("Malformed ciphertext header"); } - ByteBuffer buf = ciphertextHeaderBuf.asReadOnlyBuffer(); + ByteBuffer buf = ciphertextHeaderBuf.duplicate(); byte[] nonce = new byte[FileHeaderImpl.NONCE_LEN]; buf.position(FileHeaderImpl.NONCE_POS); buf.get(nonce); From 3134896b74faa4d80d4b3dd704d4f5d6e712bab8 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 8 Feb 2022 10:05:11 +0100 Subject: [PATCH 20/30] replace ThreadLocal with ObjectPool for SivMode instances --- .../cryptolib/v1/FileNameCryptorImpl.java | 23 ++++++++----------- .../cryptolib/v2/FileNameCryptorImpl.java | 23 ++++++++----------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java index 309d1d6..5104aa5 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java @@ -19,7 +19,6 @@ import org.cryptomator.siv.UnauthenticCiphertextException; import javax.crypto.IllegalBlockSizeException; - import java.security.MessageDigest; import static java.nio.charset.StandardCharsets.UTF_8; @@ -27,12 +26,7 @@ class FileNameCryptorImpl implements FileNameCryptor { private static final BaseEncoding BASE32 = BaseEncoding.base32(); - private static final ThreadLocal AES_SIV = new ThreadLocal() { - @Override - protected SivMode initialValue() { - return new SivMode(); - } - }; + private static final ObjectPool AES_SIV = new ObjectPool<>(SivMode::new); private final Masterkey masterkey; @@ -43,9 +37,10 @@ protected SivMode initialValue() { @Override public String hashDirectoryId(String cleartextDirectoryId) { try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey(); - ObjectPool.Lease sha1 = MessageDigestSupplier.SHA1.instance()) { + ObjectPool.Lease sha1 = MessageDigestSupplier.SHA1.instance(); + ObjectPool.Lease siv = AES_SIV.get()) { byte[] cleartextBytes = cleartextDirectoryId.getBytes(UTF_8); - byte[] encryptedBytes = AES_SIV.get().encrypt(ek, mk, cleartextBytes); + byte[] encryptedBytes = siv.get().encrypt(ek, mk, cleartextBytes); byte[] hashedBytes = sha1.get().digest(encryptedBytes); return BASE32.encode(hashedBytes); } @@ -53,18 +48,20 @@ public String hashDirectoryId(String cleartextDirectoryId) { @Override public String encryptFilename(BaseEncoding encoding, String cleartextName, byte[]... associatedData) { - try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey()) { + try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey(); + ObjectPool.Lease siv = AES_SIV.get()) { byte[] cleartextBytes = cleartextName.getBytes(UTF_8); - byte[] encryptedBytes = AES_SIV.get().encrypt(ek, mk, cleartextBytes, associatedData); + byte[] encryptedBytes = siv.get().encrypt(ek, mk, cleartextBytes, associatedData); return encoding.encode(encryptedBytes); } } @Override public String decryptFilename(BaseEncoding encoding, String ciphertextName, byte[]... associatedData) throws AuthenticationFailedException { - try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey()) { + try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey(); + ObjectPool.Lease siv = AES_SIV.get()) { byte[] encryptedBytes = encoding.decode(ciphertextName); - byte[] cleartextBytes = AES_SIV.get().decrypt(ek, mk, encryptedBytes, associatedData); + byte[] cleartextBytes = siv.get().decrypt(ek, mk, encryptedBytes, associatedData); return new String(cleartextBytes, UTF_8); } catch (UnauthenticCiphertextException | IllegalArgumentException | IllegalBlockSizeException e) { throw new AuthenticationFailedException("Invalid Ciphertext.", e); diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java index 1f4a573..0498afe 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java @@ -19,7 +19,6 @@ import org.cryptomator.siv.UnauthenticCiphertextException; import javax.crypto.IllegalBlockSizeException; - import java.security.MessageDigest; import static java.nio.charset.StandardCharsets.UTF_8; @@ -27,12 +26,7 @@ class FileNameCryptorImpl implements FileNameCryptor { private static final BaseEncoding BASE32 = BaseEncoding.base32(); - private static final ThreadLocal AES_SIV = new ThreadLocal() { - @Override - protected SivMode initialValue() { - return new SivMode(); - } - }; + private static final ObjectPool AES_SIV = new ObjectPool<>(SivMode::new); private final Masterkey masterkey; @@ -43,9 +37,10 @@ protected SivMode initialValue() { @Override public String hashDirectoryId(String cleartextDirectoryId) { try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey(); - ObjectPool.Lease sha1 = MessageDigestSupplier.SHA1.instance()) { + ObjectPool.Lease sha1 = MessageDigestSupplier.SHA1.instance(); + ObjectPool.Lease siv = AES_SIV.get()) { byte[] cleartextBytes = cleartextDirectoryId.getBytes(UTF_8); - byte[] encryptedBytes = AES_SIV.get().encrypt(ek, mk, cleartextBytes); + byte[] encryptedBytes = siv.get().encrypt(ek, mk, cleartextBytes); byte[] hashedBytes = sha1.get().digest(encryptedBytes); return BASE32.encode(hashedBytes); } @@ -53,18 +48,20 @@ public String hashDirectoryId(String cleartextDirectoryId) { @Override public String encryptFilename(BaseEncoding encoding, String cleartextName, byte[]... associatedData) { - try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey()) { + try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey(); + ObjectPool.Lease siv = AES_SIV.get()) { byte[] cleartextBytes = cleartextName.getBytes(UTF_8); - byte[] encryptedBytes = AES_SIV.get().encrypt(ek, mk, cleartextBytes, associatedData); + byte[] encryptedBytes = siv.get().encrypt(ek, mk, cleartextBytes, associatedData); return encoding.encode(encryptedBytes); } } @Override public String decryptFilename(BaseEncoding encoding, String ciphertextName, byte[]... associatedData) throws AuthenticationFailedException { - try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey()) { + try (DestroyableSecretKey ek = masterkey.getEncKey(); DestroyableSecretKey mk = masterkey.getMacKey(); + ObjectPool.Lease siv = AES_SIV.get()) { byte[] encryptedBytes = encoding.decode(ciphertextName); - byte[] cleartextBytes = AES_SIV.get().decrypt(ek, mk, encryptedBytes, associatedData); + byte[] cleartextBytes = siv.get().decrypt(ek, mk, encryptedBytes, associatedData); return new String(cleartextBytes, UTF_8); } catch (IllegalArgumentException | UnauthenticCiphertextException | IllegalBlockSizeException e) { throw new AuthenticationFailedException("Invalid Ciphertext.", e); From 4991b5695bdaeeb4f0b6fc21d094b7eab037a567 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 8 Feb 2022 15:03:51 +0100 Subject: [PATCH 21/30] fixed code smells --- .../cryptomator/cryptolib/common/AesKeyWrap.java | 7 +++++-- .../cryptomator/cryptolib/common/ByteBuffers.java | 3 +++ .../cryptolib/common/DestroyableSecretKey.java | 10 ++++++---- .../cryptolib/common/Destroyables.java | 3 +++ .../org/cryptomator/cryptolib/v1/Constants.java | 3 +++ .../org/cryptomator/cryptolib/v2/Constants.java | 3 +++ .../cryptolib/common/CipherSupplierTest.java | 10 ++++++++-- .../cryptolib/v1/FileHeaderCryptorImplTest.java | 12 ++++++------ .../cryptolib/v1/FileNameCryptorImplTest.java | 6 ++++-- .../cryptolib/v2/FileHeaderCryptorImplTest.java | 15 +++++++++------ .../cryptolib/v2/FileNameCryptorImplTest.java | 6 ++++-- 11 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java b/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java index 5b603e7..86e7489 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java +++ b/src/main/java/org/cryptomator/cryptolib/common/AesKeyWrap.java @@ -16,6 +16,9 @@ public class AesKeyWrap { + private AesKeyWrap() { + } + /** * @param kek Key encrypting key * @param key Key to be wrapped @@ -31,8 +34,8 @@ public static byte[] wrap(DestroyableSecretKey kek, SecretKey key) { } /** - * @param kek Key encrypting key - * @param wrappedKey Key to be unwrapped + * @param kek Key encrypting key + * @param wrappedKey Key to be unwrapped * @param wrappedKeyAlgorithm Key designation, i.e. algorithm to be associated with the unwrapped key. * @return Unwrapped key * @throws InvalidKeyException If unwrapping failed (i.e. wrong kek) diff --git a/src/main/java/org/cryptomator/cryptolib/common/ByteBuffers.java b/src/main/java/org/cryptomator/cryptolib/common/ByteBuffers.java index d30ee36..d80662e 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/ByteBuffers.java +++ b/src/main/java/org/cryptomator/cryptolib/common/ByteBuffers.java @@ -14,6 +14,9 @@ public class ByteBuffers { + private ByteBuffers() { + } + /** * Copies as many bytes as possible from the given source to the destination buffer. * The position of both buffers will be incremented by as many bytes as have been copied. diff --git a/src/main/java/org/cryptomator/cryptolib/common/DestroyableSecretKey.java b/src/main/java/org/cryptomator/cryptolib/common/DestroyableSecretKey.java index 4f91ea2..f28baeb 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/DestroyableSecretKey.java +++ b/src/main/java/org/cryptomator/cryptolib/common/DestroyableSecretKey.java @@ -29,6 +29,8 @@ */ public class DestroyableSecretKey implements SecretKey, AutoCloseable { + private static final String KEY_DESTROYED_ERROR = "Key has been destroyed"; + private final transient byte[] key; private final String algorithm; private boolean destroyed; @@ -95,13 +97,13 @@ public static DestroyableSecretKey generate(SecureRandom csprng, String algorith @Override public String getAlgorithm() { - Preconditions.checkState(!destroyed, "Key has been destroyed"); + Preconditions.checkState(!destroyed, KEY_DESTROYED_ERROR); return algorithm; } @Override public String getFormat() { - Preconditions.checkState(!destroyed, "Key has been destroyed"); + Preconditions.checkState(!destroyed, KEY_DESTROYED_ERROR); return "RAW"; } @@ -115,7 +117,7 @@ public String getFormat() { */ @Override public byte[] getEncoded() { - Preconditions.checkState(!destroyed, "Key has been destroyed"); + Preconditions.checkState(!destroyed, KEY_DESTROYED_ERROR); return key; } @@ -124,7 +126,7 @@ public byte[] getEncoded() { * @return New copy of this */ public DestroyableSecretKey copy() { - Preconditions.checkState(!destroyed, "Key has been destroyed"); + Preconditions.checkState(!destroyed, KEY_DESTROYED_ERROR); return new DestroyableSecretKey(key, algorithm); // key will get copied by the constructor as per contract } diff --git a/src/main/java/org/cryptomator/cryptolib/common/Destroyables.java b/src/main/java/org/cryptomator/cryptolib/common/Destroyables.java index 163a00f..84fe04d 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/Destroyables.java +++ b/src/main/java/org/cryptomator/cryptolib/common/Destroyables.java @@ -5,6 +5,9 @@ public class Destroyables { + private Destroyables() { + } + public static void destroySilently(Destroyable destroyable) { if (destroyable == null) { return; diff --git a/src/main/java/org/cryptomator/cryptolib/v1/Constants.java b/src/main/java/org/cryptomator/cryptolib/v1/Constants.java index e4cc9d5..4662fae 100644 --- a/src/main/java/org/cryptomator/cryptolib/v1/Constants.java +++ b/src/main/java/org/cryptomator/cryptolib/v1/Constants.java @@ -10,6 +10,9 @@ final class Constants { + private Constants() { + } + static final String CONTENT_ENC_ALG = "AES"; static final int NONCE_SIZE = 16; diff --git a/src/main/java/org/cryptomator/cryptolib/v2/Constants.java b/src/main/java/org/cryptomator/cryptolib/v2/Constants.java index b0a7380..f6c0f4d 100644 --- a/src/main/java/org/cryptomator/cryptolib/v2/Constants.java +++ b/src/main/java/org/cryptomator/cryptolib/v2/Constants.java @@ -10,6 +10,9 @@ final class Constants { + private Constants() { + } + static final String CONTENT_ENC_ALG = "AES"; static final int GCM_NONCE_SIZE = 12; // 96 bit IVs strongly recommended for GCM diff --git a/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java b/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java index 777ef95..9923cce 100644 --- a/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java +++ b/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java @@ -13,8 +13,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.RC5ParameterSpec; +import java.security.spec.AlgorithmParameterSpec; public class CipherSupplierTest { @@ -28,8 +30,10 @@ public void testGetUnknownCipher() { @Test public void testGetCipherWithInvalidKey() { CipherSupplier supplier = new CipherSupplier("AES/CBC/PKCS5Padding"); + SecretKey key = new DestroyableSecretKey(new byte[13], "AES"); + AlgorithmParameterSpec params = new IvParameterSpec(new byte[16]); IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> { - supplier.encryptionCipher(new DestroyableSecretKey(new byte[13], "AES"), new IvParameterSpec(new byte[16])); + supplier.encryptionCipher(key, params); }); MatcherAssert.assertThat(exception.getMessage(), CoreMatchers.containsString("Invalid key")); } @@ -37,8 +41,10 @@ public void testGetCipherWithInvalidKey() { @Test public void testGetCipherWithInvalidAlgorithmParam() { CipherSupplier supplier = new CipherSupplier("AES/CBC/PKCS5Padding"); + SecretKey key = new DestroyableSecretKey(new byte[16], "AES"); + AlgorithmParameterSpec params = new RC5ParameterSpec(1, 1, 8); IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> { - supplier.encryptionCipher(new DestroyableSecretKey(new byte[16], "AES"), new RC5ParameterSpec(1, 1, 8)); + supplier.encryptionCipher(key, params); }); MatcherAssert.assertThat(exception.getMessage(), CoreMatchers.containsString("Algorithm parameter not appropriate for")); } diff --git a/src/test/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImplTest.java b/src/test/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImplTest.java index 11fa682..5605700 100644 --- a/src/test/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImplTest.java +++ b/src/test/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImplTest.java @@ -69,28 +69,28 @@ public void testDecryption() throws AuthenticationFailedException { @Test public void testDecryptionWithTooShortHeader() { - byte[] ciphertext = new byte[7]; + ByteBuffer ciphertext = ByteBuffer.allocate(7); Assertions.assertThrows(IllegalArgumentException.class, () -> { - headerCryptor.decryptHeader(ByteBuffer.wrap(ciphertext)); + headerCryptor.decryptHeader(ciphertext); }); } @Test public void testDecryptionWithInvalidMac1() { - byte[] ciphertext = BaseEncoding.base64().decode("AAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJa=="); + ByteBuffer ciphertext = ByteBuffer.wrap(BaseEncoding.base64().decode("AAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJa==")); Assertions.assertThrows(AuthenticationFailedException.class, () -> { - headerCryptor.decryptHeader(ByteBuffer.wrap(ciphertext)); + headerCryptor.decryptHeader(ciphertext); }); } @Test public void testDecryptionWithInvalidMac2() { - byte[] ciphertext = BaseEncoding.base64().decode("aAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJA=="); + ByteBuffer ciphertext = ByteBuffer.wrap(BaseEncoding.base64().decode("aAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJA==")); Assertions.assertThrows(AuthenticationFailedException.class, () -> { - headerCryptor.decryptHeader(ByteBuffer.wrap(ciphertext)); + headerCryptor.decryptHeader(ciphertext); }); } diff --git a/src/test/java/org/cryptomator/cryptolib/v1/FileNameCryptorImplTest.java b/src/test/java/org/cryptomator/cryptolib/v1/FileNameCryptorImplTest.java index ec77ab0..99ef409 100644 --- a/src/test/java/org/cryptomator/cryptolib/v1/FileNameCryptorImplTest.java +++ b/src/test/java/org/cryptomator/cryptolib/v1/FileNameCryptorImplTest.java @@ -97,9 +97,10 @@ public void testDecryptionOfMalformedFilename() { public void testDecryptionOfManipulatedFilename() { final byte[] encrypted = filenameCryptor.encryptFilename(BASE32, "test").getBytes(UTF_8); encrypted[0] ^= (byte) 0x01; // change 1 bit in first byte + String ciphertextName = new String(encrypted, UTF_8); AuthenticationFailedException e = Assertions.assertThrows(AuthenticationFailedException.class, () -> { - filenameCryptor.decryptFilename(BASE32, new String(encrypted, UTF_8)); + filenameCryptor.decryptFilename(BASE32, ciphertextName); }); MatcherAssert.assertThat(e.getCause(), CoreMatchers.instanceOf(UnauthenticCiphertextException.class)); } @@ -124,9 +125,10 @@ public void testDeterministicEncryptionOfFilenamesWithAssociatedData() throws Au @DisplayName("decrypt ciphertext with incorrect AD") public void testDeterministicEncryptionOfFilenamesWithWrongAssociatedData() { final String encrypted = filenameCryptor.encryptFilename(BASE32, "test", "right".getBytes(UTF_8)); + final byte[] ad = "wrong".getBytes(UTF_8); Assertions.assertThrows(AuthenticationFailedException.class, () -> { - filenameCryptor.decryptFilename(BASE32, encrypted, "wrong".getBytes(UTF_8)); + filenameCryptor.decryptFilename(BASE32, encrypted, ad); }); } diff --git a/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java b/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java index c9e1c3b..baea1a5 100644 --- a/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java +++ b/src/test/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImplTest.java @@ -78,25 +78,28 @@ public void testDecryption() throws AuthenticationFailedException { @Test public void testDecryptionWithTooShortHeader() { - byte[] ciphertext = new byte[7]; + ByteBuffer ciphertext = ByteBuffer.allocate(7); + Assertions.assertThrows(IllegalArgumentException.class, () -> { - headerCryptor.decryptHeader(ByteBuffer.wrap(ciphertext)); + headerCryptor.decryptHeader(ciphertext); }); } @Test public void testDecryptionWithInvalidTag1() { - byte[] ciphertext = BaseEncoding.base64().decode("AAAAAAAAAAAAAAAAMVi/wrKflJEHTsXTuvOdGHJgA8o3pip00aL1jnUGNY7dSrEoTUrhey+maVG6P0F2RBmZR74SjUA="); + ByteBuffer ciphertext = ByteBuffer.wrap(BaseEncoding.base64().decode("AAAAAAAAAAAAAAAAMVi/wrKflJEHTsXTuvOdGHJgA8o3pip00aL1jnUGNY7dSrEoTUrhey+maVG6P0F2RBmZR74SjUA=")); + Assertions.assertThrows(AuthenticationFailedException.class, () -> { - headerCryptor.decryptHeader(ByteBuffer.wrap(ciphertext)); + headerCryptor.decryptHeader(ciphertext); }); } @Test public void testDecryptionWithInvalidTag2() { - byte[] ciphertext = BaseEncoding.base64().decode("AAAAAAAAAAAAAAAAMVi/wrKflJEHTsXTuvOdGHJgA8o3pip00aL1jnUGNY7dSrEoTUrhey+maVG6P0F2RBmZR74SjUa="); + ByteBuffer ciphertext = ByteBuffer.wrap(BaseEncoding.base64().decode("AAAAAAAAAAAAAAAAMVi/wrKflJEHTsXTuvOdGHJgA8o3pip00aL1jnUGNY7dSrEoTUrhey+maVG6P0F2RBmZR74SjUa=")); + Assertions.assertThrows(AuthenticationFailedException.class, () -> { - headerCryptor.decryptHeader(ByteBuffer.wrap(ciphertext)); + headerCryptor.decryptHeader(ciphertext); }); } diff --git a/src/test/java/org/cryptomator/cryptolib/v2/FileNameCryptorImplTest.java b/src/test/java/org/cryptomator/cryptolib/v2/FileNameCryptorImplTest.java index daa5da1..d808f89 100644 --- a/src/test/java/org/cryptomator/cryptolib/v2/FileNameCryptorImplTest.java +++ b/src/test/java/org/cryptomator/cryptolib/v2/FileNameCryptorImplTest.java @@ -98,9 +98,10 @@ public void testDecryptionOfMalformedFilename() { public void testDecryptionOfManipulatedFilename() { final byte[] encrypted = filenameCryptor.encryptFilename(BASE32, "test").getBytes(UTF_8); encrypted[0] ^= (byte) 0x01; // change 1 bit in first byte + String ciphertextName = new String(encrypted, UTF_8); AuthenticationFailedException e = Assertions.assertThrows(AuthenticationFailedException.class, () -> { - filenameCryptor.decryptFilename(BASE32, new String(encrypted, UTF_8)); + filenameCryptor.decryptFilename(BASE32, ciphertextName); }); MatcherAssert.assertThat(e.getCause(), CoreMatchers.instanceOf(UnauthenticCiphertextException.class)); } @@ -125,9 +126,10 @@ public void testDeterministicEncryptionOfFilenamesWithAssociatedData() throws Au @DisplayName("decrypt ciphertext with incorrect AD") public void testDeterministicEncryptionOfFilenamesWithWrongAssociatedData() { final String encrypted = filenameCryptor.encryptFilename(BASE32, "test", "right".getBytes(UTF_8)); + final byte[] ad = "wrong".getBytes(UTF_8); Assertions.assertThrows(AuthenticationFailedException.class, () -> { - filenameCryptor.decryptFilename(BASE32, encrypted, "wrong".getBytes(UTF_8)); + filenameCryptor.decryptFilename(BASE32, encrypted, ad); }); } From cb45c0b6901b3887f4396262a055624394579a2b Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 8 Feb 2022 15:44:20 +0100 Subject: [PATCH 22/30] `com.google.common.io.BaseEncoding` is part of the public API of FileNameCryptor --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 61629df..127e8e9 100644 --- a/pom.xml +++ b/pom.xml @@ -213,7 +213,7 @@ module org.cryptomator.cryptolib { requires org.cryptomator.siv; requires com.google.gson; - requires com.google.common; + requires transitive com.google.common; requires org.slf4j; exports org.cryptomator.cryptolib.api; From e0416458795a9eb519a325a534e3c6c24a6b457a Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 8 Feb 2022 16:47:41 +0100 Subject: [PATCH 23/30] use a new key for each test run --- .../cryptomator/cryptolib/api/CryptoLibIntegrationTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/cryptomator/cryptolib/api/CryptoLibIntegrationTest.java b/src/test/java/org/cryptomator/cryptolib/api/CryptoLibIntegrationTest.java index f0c57f8..f846629 100644 --- a/src/test/java/org/cryptomator/cryptolib/api/CryptoLibIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptolib/api/CryptoLibIntegrationTest.java @@ -30,12 +30,11 @@ public class CryptoLibIntegrationTest { private static final SecureRandom RANDOM_MOCK = SecureRandomMock.PRNG_RANDOM; - private static final Masterkey MASTERKEY = Masterkey.generate(RANDOM_MOCK); private static Stream getCryptors() { return Stream.of( - CryptorProvider.forScheme(CryptorProvider.Scheme.SIV_CTRMAC).provide(MASTERKEY, RANDOM_MOCK), - CryptorProvider.forScheme(CryptorProvider.Scheme.SIV_GCM).provide(MASTERKEY, RANDOM_MOCK) + CryptorProvider.forScheme(CryptorProvider.Scheme.SIV_CTRMAC).provide(Masterkey.generate(RANDOM_MOCK), RANDOM_MOCK), + CryptorProvider.forScheme(CryptorProvider.Scheme.SIV_GCM).provide(Masterkey.generate(RANDOM_MOCK), RANDOM_MOCK) ); } From eccaa3b64535126f26c58fcf702648379c0c2f17 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 8 Feb 2022 16:47:50 +0100 Subject: [PATCH 24/30] bump dependencies --- pom.xml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pom.xml b/pom.xml index 127e8e9..5c14f35 100644 --- a/pom.xml +++ b/pom.xml @@ -19,19 +19,19 @@ 2.8.9 - 30.1.1-jre - 1.4.3 - 1.69 - 1.7.31 + 31.0.1-jre + 1.4.4 + 1.70 + 1.7.35 - 5.7.2 - 3.11.2 + 5.8.2 + 3.12.4 2.2 - 1.32 + 1.34 - 6.2.2 + 6.5.3 0.8.7 1.6.8 @@ -131,7 +131,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.0.0-M3 + 3.0.0 enforce-java @@ -151,7 +151,7 @@ maven-compiler-plugin - 3.8.1 + 3.9.0 UTF-8 true @@ -197,7 +197,7 @@ org.moditect moditect-maven-plugin - 1.0.0.RC1 + 1.0.0.RC2 add-module-infos @@ -238,12 +238,12 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + 3.0.0-M5 org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.2.2 @@ -267,7 +267,7 @@ maven-javadoc-plugin - 3.3.0 + 3.3.1 attach-javadocs From 6eb83a863c690ea55dd3dad71961518302239e38 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 8 Feb 2022 17:10:23 +0100 Subject: [PATCH 25/30] update to Mockito 4.x --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5c14f35..a2240bb 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ 5.8.2 - 3.12.4 + 4.3.1 2.2 1.34 From 7d7b0aad3f129b243cae7dba8198339519e3744b Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 8 Feb 2022 17:20:52 +0100 Subject: [PATCH 26/30] introduce constant for recurring error message --- .../org/cryptomator/cryptolib/common/ECKeyPair.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptolib/common/ECKeyPair.java b/src/main/java/org/cryptomator/cryptolib/common/ECKeyPair.java index 19bbe12..6bf04a7 100644 --- a/src/main/java/org/cryptomator/cryptolib/common/ECKeyPair.java +++ b/src/main/java/org/cryptomator/cryptolib/common/ECKeyPair.java @@ -18,6 +18,8 @@ public class ECKeyPair implements Destroyable { + private static final String INVALID_KEY_ERROR = "Invalid EC Key"; + private final KeyPair keyPair; private boolean destroyed; @@ -46,28 +48,28 @@ public ECPublicKey getPublic() { // validations taken from https://neilmadden.blog/2017/05/17/so-how-do-you-validate-nist-ecdh-public-keys/ private static KeyPair verify(KeyPair keyPair, ECParameterSpec curveParams) { PublicKey pk = keyPair.getPublic(); - Preconditions.checkArgument(pk instanceof ECPublicKey, "Not an EC key"); + Preconditions.checkArgument(pk instanceof ECPublicKey, INVALID_KEY_ERROR); Preconditions.checkArgument(curveParams.getCofactor() == 1, "Verifying points on curves with cofactor not supported"); // see "Step 4" in linked post ECPublicKey publicKey = (ECPublicKey) pk; EllipticCurve curve = curveParams.getCurve(); // Step 1: Verify public key is not point at infinity. - Preconditions.checkArgument(!ECPoint.POINT_INFINITY.equals(publicKey.getW()), "Invalid Key"); + Preconditions.checkArgument(!ECPoint.POINT_INFINITY.equals(publicKey.getW()), INVALID_KEY_ERROR); final BigInteger x = publicKey.getW().getAffineX(); final BigInteger y = publicKey.getW().getAffineY(); final BigInteger p = ((ECFieldFp) curve.getField()).getP(); // Step 2: Verify x and y are in range [0,p-1] - Preconditions.checkArgument(x.compareTo(BigInteger.ZERO) >= 0 && x.compareTo(p) < 0, "Invalid Key"); - Preconditions.checkArgument(y.compareTo(BigInteger.ZERO) >= 0 && y.compareTo(p) < 0, "Invalid Key"); + Preconditions.checkArgument(x.compareTo(BigInteger.ZERO) >= 0 && x.compareTo(p) < 0, INVALID_KEY_ERROR); + Preconditions.checkArgument(y.compareTo(BigInteger.ZERO) >= 0 && y.compareTo(p) < 0, INVALID_KEY_ERROR); // Step 3: Verify that y^2 == x^3 + ax + b (mod p) final BigInteger a = curve.getA(); final BigInteger b = curve.getB(); final BigInteger ySquared = y.modPow(BigInteger.valueOf(2), p); final BigInteger xCubedPlusAXPlusB = x.modPow(BigInteger.valueOf(3), p).add(a.multiply(x)).add(b).mod(p); - Preconditions.checkArgument(ySquared.equals(xCubedPlusAXPlusB), "Invalid key"); + Preconditions.checkArgument(ySquared.equals(xCubedPlusAXPlusB), INVALID_KEY_ERROR); return keyPair; } From ccfe34dc0052f906ad9c840c73f715e2ae6d4837 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 15 Mar 2022 16:21:09 +0100 Subject: [PATCH 27/30] updated false positives --- suppression.xml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/suppression.xml b/suppression.xml index c714921..ebb877b 100644 --- a/suppression.xml +++ b/suppression.xml @@ -1,4 +1,12 @@ - + + + + org\.cryptomator:.* + cpe:/a:cryptomator:cryptomator + CVE-2022-25366 + \ No newline at end of file From 47a6c2c32f115859b5c1d057e26b3c8e3202d520 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 5 Oct 2022 16:16:22 +0200 Subject: [PATCH 28/30] removed MasterkeyHubAccess with custom ECIES, as this got replaced with JWE --- .../cryptolib/common/MasterkeyHubAccess.java | 65 ------- .../ecies/AuthenticatedEncryption.java | 37 ---- .../ecies/ECIntegratedEncryptionScheme.java | 90 ---------- .../cryptolib/ecies/EncryptedMessage.java | 23 --- .../cryptolib/ecies/GcmWithSecretNonce.java | 50 ------ .../ecies/KeyDerivationFunction.java | 65 ------- .../common/MasterkeyHubAccessTest.java | 76 --------- .../ECIntegratedEncryptionSchemeTest.java | 161 ------------------ .../ecies/GcmWithSecretNonceTest.java | 72 -------- .../cryptolib/ecies/HexConverter.java | 20 --- .../ecies/KeyDerivationFunctionTest.java | 38 ----- 11 files changed, 697 deletions(-) delete mode 100644 src/main/java/org/cryptomator/cryptolib/common/MasterkeyHubAccess.java delete mode 100644 src/main/java/org/cryptomator/cryptolib/ecies/AuthenticatedEncryption.java delete mode 100644 src/main/java/org/cryptomator/cryptolib/ecies/ECIntegratedEncryptionScheme.java delete mode 100644 src/main/java/org/cryptomator/cryptolib/ecies/EncryptedMessage.java delete mode 100644 src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java delete mode 100644 src/main/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunction.java delete mode 100644 src/test/java/org/cryptomator/cryptolib/common/MasterkeyHubAccessTest.java delete mode 100644 src/test/java/org/cryptomator/cryptolib/ecies/ECIntegratedEncryptionSchemeTest.java delete mode 100644 src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java delete mode 100644 src/test/java/org/cryptomator/cryptolib/ecies/HexConverter.java delete mode 100644 src/test/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunctionTest.java diff --git a/src/main/java/org/cryptomator/cryptolib/common/MasterkeyHubAccess.java b/src/main/java/org/cryptomator/cryptolib/common/MasterkeyHubAccess.java deleted file mode 100644 index be0fef0..0000000 --- a/src/main/java/org/cryptomator/cryptolib/common/MasterkeyHubAccess.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.cryptomator.cryptolib.common; - -import com.google.common.io.BaseEncoding; -import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; -import org.cryptomator.cryptolib.ecies.EncryptedMessage; -import org.cryptomator.cryptolib.ecies.ECIntegratedEncryptionScheme; - -import javax.crypto.AEADBadTagException; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; -import java.util.Arrays; - -public class MasterkeyHubAccess { - - private static final BaseEncoding BASE64_URL = BaseEncoding.base64Url().omitPadding(); - - private MasterkeyHubAccess() { - } - - /** - * Decrypts a masterkey retrieved from Cryptomator Hub - * - * @param devicePrivateKey Private key of the device this ciphertext is intended for - * @param encodedCiphertext The encrypted masterkey - * @param encodedEphPubKey The ephemeral public key to be used to derive a secret shared between message sender and this device - * @return The decrypted masterkey - * @throws MasterkeyLoadingFailedException If the parameters don't match and decryption fails - */ - public static Masterkey decryptMasterkey(ECPrivateKey devicePrivateKey, String encodedCiphertext, String encodedEphPubKey) throws MasterkeyLoadingFailedException { - byte[] cleartext = new byte[0]; - try { - EncryptedMessage message = decode(encodedCiphertext, encodedEphPubKey); - cleartext = ECIntegratedEncryptionScheme.HUB.decrypt(devicePrivateKey, message); - return new Masterkey(cleartext); - } catch (IllegalArgumentException | AEADBadTagException e) { - throw new MasterkeyLoadingFailedException("Key and ciphertext don't match", e); - } finally { - Arrays.fill(cleartext, (byte) 0x00); - } - } - - private static EncryptedMessage decode(String encodedCiphertext, String encodedEphPubKey) throws IllegalArgumentException { - byte[] ciphertext = BASE64_URL.decode(encodedCiphertext); - byte[] keyBytes = BASE64_URL.decode(encodedEphPubKey); - try { - PublicKey key = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(keyBytes)); - if (key instanceof ECPublicKey) { - return new EncryptedMessage((ECPublicKey) key, ciphertext); - } else { - throw new IllegalArgumentException("Key not an EC public key."); - } - } catch (InvalidKeySpecException e) { - throw new IllegalArgumentException("Invalid license public key", e); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(e); - } - } - -} diff --git a/src/main/java/org/cryptomator/cryptolib/ecies/AuthenticatedEncryption.java b/src/main/java/org/cryptomator/cryptolib/ecies/AuthenticatedEncryption.java deleted file mode 100644 index 654ff14..0000000 --- a/src/main/java/org/cryptomator/cryptolib/ecies/AuthenticatedEncryption.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cryptomator.cryptolib.ecies; - -import javax.crypto.AEADBadTagException; - -public interface AuthenticatedEncryption { - - /** - * AES-GCM with a 96 bit nonce taken from a the shared secret. - * - * Since the secret is derived via ECDH with an ephemeral key, the nonce is guaranteed to be unique. - */ - AuthenticatedEncryption GCM_WITH_SECRET_NONCE = new GcmWithSecretNonce(); - - /** - * @return number of bytes required during {@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])} - */ - int requiredSecretBytes(); - - /** - * Encrypts the given plaintext - * - * @param secret secret data required for encryption, such as (but not limited to) a key - * @param plaintext The data to encrypt - * @return The encrypted data, including all required information for authentication, such as a tag - */ - byte[] encrypt(byte[] secret, byte[] plaintext); - - /** - * Encrypts the given ciphertext - * - * @param secret secret data required for encryption, such as (but not limited to) a key - * @param ciphertext The data to decrypt - * @return The decrypted data - * @throws AEADBadTagException In case of an authentication failure (including wrong secret) - */ - byte[] decrypt(byte[] secret, byte[] ciphertext) throws AEADBadTagException; -} diff --git a/src/main/java/org/cryptomator/cryptolib/ecies/ECIntegratedEncryptionScheme.java b/src/main/java/org/cryptomator/cryptolib/ecies/ECIntegratedEncryptionScheme.java deleted file mode 100644 index 1df67f1..0000000 --- a/src/main/java/org/cryptomator/cryptolib/ecies/ECIntegratedEncryptionScheme.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.cryptomator.cryptolib.ecies; - -import org.cryptomator.cryptolib.common.Destroyables; - -import javax.crypto.AEADBadTagException; -import javax.crypto.KeyAgreement; -import java.security.InvalidKeyException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.util.Arrays; - -public class ECIntegratedEncryptionScheme { - - /** - * The ECIES used in Cryptomator Hub: - *
    - *
  • To be used with {@link org.cryptomator.cryptolib.common.P384KeyPair P-384 EC keys}
  • - *
  • Use ANSI X9.63 KDF with SHA-256 to derive a 352 bit shared secret
  • - *
  • Cut shared secret into 256 bit key + 96 bit nonce used for AES-GCM to encrypt/decrypt
  • - *
- */ - public static final ECIntegratedEncryptionScheme HUB = new ECIntegratedEncryptionScheme(AuthenticatedEncryption.GCM_WITH_SECRET_NONCE, KeyDerivationFunction.ANSI_X963_SHA256_KDF); - - private final AuthenticatedEncryption ae; - private final KeyDerivationFunction kdf; - - public ECIntegratedEncryptionScheme(AuthenticatedEncryption ae, KeyDerivationFunction kdf) { - this.ae = ae; - this.kdf = kdf; - } - - public EncryptedMessage encrypt(KeyPairGenerator ephKeyGen, ECPublicKey receiverPublicKey, byte[] plaintext) { - KeyPair ephKeyPair = ephKeyGen.generateKeyPair(); - try { - if (ephKeyPair.getPrivate() instanceof ECPrivateKey) { - assert ephKeyPair.getPublic() instanceof ECPublicKey; - byte[] ciphertext = encrypt((ECPrivateKey) ephKeyPair.getPrivate(), receiverPublicKey, plaintext); - return new EncryptedMessage((ECPublicKey) ephKeyPair.getPublic(), ciphertext); - } else { - throw new IllegalArgumentException("key generator didn't create EC key pair"); - } - } finally { - Destroyables.destroySilently(ephKeyPair.getPrivate()); - } - } - - public byte[] decrypt(ECPrivateKey receiverPrivateKey, EncryptedMessage encryptedMessage) throws AEADBadTagException { - return decrypt(receiverPrivateKey, encryptedMessage.getEphPublicKey(), encryptedMessage.getCiphertext()); - } - - // visible for testing - byte[] encrypt(ECPrivateKey ephPrivateKey, ECPublicKey receiverPublicKey, byte[] plaintext) { - byte[] secret = ecdhAndKdf(ephPrivateKey, receiverPublicKey, ae.requiredSecretBytes()); - return ae.encrypt(secret, plaintext); - } - - // visible for testing - byte[] decrypt(ECPrivateKey receiverPrivateKey, ECPublicKey ephPublicKey, byte[] plaintext) throws AEADBadTagException { - byte[] secret = ecdhAndKdf(receiverPrivateKey, ephPublicKey, ae.requiredSecretBytes()); - return ae.decrypt(secret, plaintext); - } - - private byte[] ecdhAndKdf(ECPrivateKey privateKey, ECPublicKey publicKey, int numBytes) { - byte[] sharedSecret = new byte[0]; - try { - KeyAgreement keyAgreement = createKeyAgreement(); - keyAgreement.init(privateKey); - keyAgreement.doPhase(publicKey, true); - sharedSecret = keyAgreement.generateSecret(); - return kdf.deriveKey(sharedSecret, numBytes); - } catch (InvalidKeyException e) { - throw new IllegalArgumentException("Invalid keys", e); - } finally { - Arrays.fill(sharedSecret, (byte) 0x00); - } - } - - private static KeyAgreement createKeyAgreement() { - try { - return KeyAgreement.getInstance("ECDH"); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("ECDH not supported"); - } - } - - -} diff --git a/src/main/java/org/cryptomator/cryptolib/ecies/EncryptedMessage.java b/src/main/java/org/cryptomator/cryptolib/ecies/EncryptedMessage.java deleted file mode 100644 index 08657bc..0000000 --- a/src/main/java/org/cryptomator/cryptolib/ecies/EncryptedMessage.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.cryptomator.cryptolib.ecies; - -import java.security.interfaces.ECPublicKey; - -public class EncryptedMessage { - - private final ECPublicKey ephPublicKey; - private final byte[] ciphertext; - - public EncryptedMessage(ECPublicKey ephPublicKey, byte[] ciphertext) { - this.ephPublicKey = ephPublicKey; - this.ciphertext = ciphertext; - } - - public ECPublicKey getEphPublicKey() { - return ephPublicKey; - } - - public byte[] getCiphertext() { - return ciphertext; - } - -} diff --git a/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java b/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java deleted file mode 100644 index 093af59..0000000 --- a/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.cryptomator.cryptolib.ecies; - -import com.google.common.base.Throwables; -import org.cryptomator.cryptolib.common.CipherSupplier; -import org.cryptomator.cryptolib.common.DestroyableSecretKey; -import org.cryptomator.cryptolib.common.ObjectPool; - -import javax.crypto.AEADBadTagException; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.spec.GCMParameterSpec; -import java.util.Arrays; - -class GcmWithSecretNonce implements AuthenticatedEncryption { - - private static final int GCM_KEY_SIZE = 32; - private static final int GCM_TAG_SIZE = 16; - private static final int GCM_NONCE_SIZE = 12; // 96 bit IVs strongly recommended for GCM - - @Override - public int requiredSecretBytes() { - return GCM_KEY_SIZE + GCM_NONCE_SIZE; - } - - @Override - public byte[] encrypt(byte[] secret, byte[] plaintext) { - try (DestroyableSecretKey key = new DestroyableSecretKey(secret, 0, GCM_KEY_SIZE, "AES")) { - byte[] nonce = Arrays.copyOfRange(secret, GCM_KEY_SIZE, GCM_KEY_SIZE + GCM_NONCE_SIZE); - try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encryptionCipher(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { - return cipher.get().doFinal(plaintext); - } - } catch (IllegalBlockSizeException | BadPaddingException e) { - throw new IllegalStateException("Unexpected exception during GCM decryption.", e); - } - } - - @Override - public byte[] decrypt(byte[] secret, byte[] ciphertext) throws AEADBadTagException { - try (DestroyableSecretKey key = new DestroyableSecretKey(secret, 0, GCM_KEY_SIZE, "AES")) { - byte[] nonce = Arrays.copyOfRange(secret, GCM_KEY_SIZE, GCM_KEY_SIZE + GCM_NONCE_SIZE); - try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.decryptionCipher(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { - return cipher.get().doFinal(ciphertext); - } - } catch (IllegalBlockSizeException | BadPaddingException e) { - Throwables.throwIfInstanceOf(e, AEADBadTagException.class); - throw new IllegalStateException("Unexpected exception during GCM decryption.", e); - } - } -} diff --git a/src/main/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunction.java b/src/main/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunction.java deleted file mode 100644 index 0b8ecf0..0000000 --- a/src/main/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunction.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.cryptomator.cryptolib.ecies; - -import org.cryptomator.cryptolib.common.MessageDigestSupplier; -import org.cryptomator.cryptolib.common.ObjectPool; - -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.DigestException; -import java.security.MessageDigest; -import java.util.Arrays; - -@FunctionalInterface -public interface KeyDerivationFunction { - - KeyDerivationFunction ANSI_X963_SHA256_KDF = (sharedSecret, keyDataLen) -> ansiX963sha256Kdf(sharedSecret, new byte[0], keyDataLen); - - /** - * Derives a key of desired length - * - * @param sharedSecret A shared secret - * @param keyDataLen Desired key length (in bytes) - * @return key data - */ - byte[] deriveKey(byte[] sharedSecret, int keyDataLen); - - /** - * Performs ANSI-X9.63-KDF with SHA-256 - * - * @param sharedSecret A shared secret - * @param sharedInfo Additional authenticated data - * @param keyDataLen Desired key length (in bytes) - * @return key data - */ - static byte[] ansiX963sha256Kdf(byte[] sharedSecret, byte[] sharedInfo, int keyDataLen) { - // max input length is 2^64 - 1, see https://doi.org/10.6028/NIST.SP.800-56Cr2, Table 1 - int hashLen = 32; // fixed digest length for SHA-256 in bytes - - // These two checks must be performed according to spec. However with 32 bit integers, we can't exceed any limits anyway: - assert BigInteger.valueOf(4L + sharedSecret.length + sharedInfo.length).compareTo(BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE)) < 0 : "input larger than hashmaxlen"; - assert keyDataLen < (1L << 32 - 1) * hashLen : "keyDataLen larger than hashLen × (2^32 − 1)"; - - ByteBuffer counter = ByteBuffer.allocate(Integer.BYTES); - assert ByteOrder.BIG_ENDIAN.equals(counter.order()); - int n = (keyDataLen + hashLen - 1) / hashLen; - byte[] buffer = new byte[n * hashLen]; - try (ObjectPool.Lease sha256 = MessageDigestSupplier.SHA256.instance()) { - for (int i = 0; i < n; i++) { - sha256.get().update(sharedSecret); - counter.clear(); - counter.putInt(i + 1); - counter.flip(); - sha256.get().update(counter); - sha256.get().update(sharedInfo); - sha256.get().digest(buffer, i * hashLen, hashLen); - } - return Arrays.copyOf(buffer, keyDataLen); - } catch (DigestException e) { - throw new IllegalStateException("Invalid digest output buffer offset", e); - } finally { - Arrays.fill(buffer, (byte) 0x00); - } - } - -} diff --git a/src/test/java/org/cryptomator/cryptolib/common/MasterkeyHubAccessTest.java b/src/test/java/org/cryptomator/cryptolib/common/MasterkeyHubAccessTest.java deleted file mode 100644 index 7b779ef..0000000 --- a/src/test/java/org/cryptomator/cryptolib/common/MasterkeyHubAccessTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.cryptomator.cryptolib.common; - -import com.google.common.io.BaseEncoding; -import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.ECPrivateKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Arrays; - -public class MasterkeyHubAccessTest { - - private ECPrivateKey devicePrivateKey; - - @BeforeEach - public void setup() throws NoSuchAlgorithmException, InvalidKeySpecException { - byte[] keyBytes = BaseEncoding.base64Url().decode("ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDDzj9mBnoqoYTO0wQDvM2iyI2wrNe468US1mHMjdJcKWGGvky4pMexIvmvmDsZLdsY"); - this.devicePrivateKey = (ECPrivateKey) KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); - } - - @Test - @DisplayName("decryptMasterkey(...)") - public void testDecrypt() { - String ephPk = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEkcv3x-hkCnb8Kr8TNfaLpD4q64ZqPn4p1yuM8r2r16h6f6mG01kFBp2EoY575bCcmT54PxiDFkf3KKqHXFjZwBhdm6zMp22l37ZlmKyHG96vkB7Rh6qFyzEhSQ_nvl2G"; - String ciphertext = "KQ48XS6ziW3tS7SMLR5sc2o_Y80OR4SS_htHpk8SHn4KrqI07EtDFFbNJ9AcNOazSu3TXrml--t_bEXprfnPqa3MlBvmPUVBcwUFJPDTR9Y"; - - Masterkey masterkey = MasterkeyHubAccess.decryptMasterkey(devicePrivateKey, ciphertext, ephPk); - - byte[] expectedKey = new byte[64]; - Arrays.fill(expectedKey, 0, 32, (byte) 0x55); - Arrays.fill(expectedKey, 32, 64, (byte) 0x77); - Assertions.assertArrayEquals(expectedKey, masterkey.getEncoded()); - } - - @Test - @DisplayName("decryptMasterkey(...) with tampered ephemeral public key") - public void testDecryptWithInvalidEphemeralPublicKey() { - String ephPk = "mHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEkcv3x-hkCnb8Kr8TNfaLpD4q64ZqPn4p1yuM8r2r16h6f6mG01kFBp2EoY575bCcmT54PxiDFkf3KKqHXFjZwBhdm6zMp22l37ZlmKyHG96vkB7Rh6qFyzEhSQ_nvl2G"; - String ciphertext = "KQ48XS6ziW3tS7SMLR5sc2o_Y80OR4SS_htHpk8SHn4KrqI07EtDFFbNJ9AcNOazSu3TXrml--t_bEXprfnPqa3MlBvmPUVBcwUFJPDTR9Y"; - - Assertions.assertThrows(MasterkeyLoadingFailedException.class, () -> { - MasterkeyHubAccess.decryptMasterkey(devicePrivateKey, ciphertext, ephPk); - }); - } - - @Test - @DisplayName("decryptMasterkey(...) with tampered ciphertext") - public void testDecryptWithInvalidCiphertext() { - String ephPk = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEkcv3x-hkCnb8Kr8TNfaLpD4q64ZqPn4p1yuM8r2r16h6f6mG01kFBp2EoY575bCcmT54PxiDFkf3KKqHXFjZwBhdm6zMp22l37ZlmKyHG96vkB7Rh6qFyzEhSQ_nvl2G"; - String ciphertext = "kQ48XS6ziW3tS7SMLR5sc2o_Y80OR4SS_htHpk8SHn4KrqI07EtDFFbNJ9AcNOazSu3TXrml--t_bEXprfnPqa3MlBvmPUVBcwUFJPDTR9Y"; - - Assertions.assertThrows(MasterkeyLoadingFailedException.class, () -> { - MasterkeyHubAccess.decryptMasterkey(devicePrivateKey, ciphertext, ephPk); - }); - } - - @Test - @DisplayName("decryptMasterkey(...) with invalid device key") - public void testDecryptWithInvalidDeviceKey() { - String ephPk = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEkcv3x-hkCnb8Kr8TNfaLpD4q64ZqPn4p1yuM8r2r16h6f6mG01kFBp2EoY575bCcmT54PxiDFkf3KKqHXFjZwBhdm6zMp22l37ZlmKyHG96vkB7Rh6qFyzEhSQ_nvl2G"; - String ciphertext = "KQ48XS6ziW3tS7SMLR5sc2o_Y80OR4SS_htHpk8SHn4KrqI07EtDFFbNJ9AcNOazSu3TXrml--t_bEXprfnPqa3MlBvmPUVBcwUFJPDTR9Y"; - ECPrivateKey wrongKey = P384KeyPair.generate().getPrivate(); - - Assertions.assertThrows(MasterkeyLoadingFailedException.class, () -> { - MasterkeyHubAccess.decryptMasterkey(wrongKey, ciphertext, ephPk); - }); - } - -} \ No newline at end of file diff --git a/src/test/java/org/cryptomator/cryptolib/ecies/ECIntegratedEncryptionSchemeTest.java b/src/test/java/org/cryptomator/cryptolib/ecies/ECIntegratedEncryptionSchemeTest.java deleted file mode 100644 index df836a5..0000000 --- a/src/test/java/org/cryptomator/cryptolib/ecies/ECIntegratedEncryptionSchemeTest.java +++ /dev/null @@ -1,161 +0,0 @@ -package org.cryptomator.cryptolib.ecies; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import javax.crypto.AEADBadTagException; -import javax.crypto.KeyAgreement; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.util.Arrays; - -public class ECIntegratedEncryptionSchemeTest { - - @Nested - @DisplayName("With null encryption scheme...") - public class WithIdentityCipher { - - private AuthenticatedEncryption ae; - private KeyDerivationFunction kdf; - private ECIntegratedEncryptionScheme ecies; - private KeyPair ephemeral; - private KeyPair receiver; - private byte[] expectedSharedSecret; - private byte[] derivedSecret; - - @BeforeEach - public void setup() throws AEADBadTagException, NoSuchAlgorithmException, InvalidKeyException { - this.ephemeral = KeyPairGenerator.getInstance("EC").generateKeyPair(); - this.receiver = KeyPairGenerator.getInstance("EC").generateKeyPair(); - KeyAgreement ecdh = KeyAgreement.getInstance("ECDH"); - ecdh.init(ephemeral.getPrivate()); - ecdh.doPhase(receiver.getPublic(), true); - this.expectedSharedSecret = ecdh.generateSecret(); - this.ae = Mockito.mock(AuthenticatedEncryption.class); - this.kdf = Mockito.mock(KeyDerivationFunction.class); - this.ecies = new ECIntegratedEncryptionScheme(ae, kdf); - this.derivedSecret = new byte[32]; - Arrays.fill(derivedSecret, (byte) 0xAA); - - // set up null encryption - Mockito.doReturn(32).when(ae).requiredSecretBytes(); - Mockito.doAnswer(invocation -> invocation.getArgument(1)).when(ae).encrypt(Mockito.any(), Mockito.any()); - Mockito.doAnswer(invocation -> invocation.getArgument(1)).when(ae).decrypt(Mockito.any(), Mockito.any()); - - // set up null KDF - Mockito.doReturn(derivedSecret).when(kdf).deriveKey(expectedSharedSecret, 32); - } - - @Test - public void testEncryptWithInvalidKey() { - ECPrivateKey invalidSk = Mockito.mock(ECPrivateKey.class); - ECPublicKey validPk = (ECPublicKey) receiver.getPublic(); - Mockito.doReturn("WRONG").when(invalidSk).getAlgorithm(); - - Assertions.assertThrows(IllegalArgumentException.class, () -> { - ecies.encrypt(invalidSk, validPk, new byte[42]); - }); - } - - @Test - public void testEncrypt() { - byte[] cleartext = "secret message".getBytes(StandardCharsets.UTF_8); - - byte[] ciphertext = ecies.encrypt((ECPrivateKey) ephemeral.getPrivate(), (ECPublicKey) receiver.getPublic(), cleartext); - - Assertions.assertArrayEquals(cleartext, ciphertext); - Mockito.verify(kdf).deriveKey(Mockito.any(), Mockito.eq(32)); - Mockito.verify(ae).encrypt(derivedSecret, cleartext); - } - - @Test - public void testDecrypt() throws AEADBadTagException { - byte[] ciphertext = "secret message".getBytes(StandardCharsets.UTF_8); - - byte[] cleartext = ecies.decrypt((ECPrivateKey) receiver.getPrivate(), (ECPublicKey) ephemeral.getPublic(), ciphertext); - - Assertions.assertArrayEquals(ciphertext, cleartext); - Mockito.verify(kdf).deriveKey(Mockito.any(), Mockito.eq(32)); - Mockito.verify(ae).decrypt(derivedSecret, cleartext); - } - - } - - @Nested - @DisplayName("With Cryptomator Hub encryption scheme...") - public class WithHubScheme { - - private ECIntegratedEncryptionScheme ecies = ECIntegratedEncryptionScheme.HUB; - private KeyPairGenerator keyGen; - private KeyPair receiverKeyPair; - - @BeforeEach - public void setup() throws NoSuchAlgorithmException { - this.keyGen = KeyPairGenerator.getInstance("EC"); - this.receiverKeyPair = keyGen.generateKeyPair(); - } - - @Test - @DisplayName("encrypt(...)") - public void testEncrypt() { - byte[] cleartext = "hello world".getBytes(StandardCharsets.UTF_8); - EncryptedMessage msg = ecies.encrypt(keyGen, (ECPublicKey) receiverKeyPair.getPublic(), cleartext); - Assertions.assertNotNull(msg.getCiphertext()); - Assertions.assertNotNull(msg.getEphPublicKey()); - } - - @Test - @DisplayName("encrypt(...) with invalid keygen") - public void testEncryptWithInvalidKeyGen() throws NoSuchAlgorithmException { - KeyPairGenerator rsaKeyGen = KeyPairGenerator.getInstance("RSA"); - byte[] cleartext = "hello world".getBytes(StandardCharsets.UTF_8); - ECPublicKey receiverPublicKey = (ECPublicKey) receiverKeyPair.getPublic(); - - Assertions.assertThrows(IllegalArgumentException.class, () -> { - ecies.encrypt(rsaKeyGen, receiverPublicKey, cleartext); - }); - } - - @Nested - @DisplayName("With Cryptomator Hub encryption scheme...") - public class WithEncryptedMessage { - - private byte[] expectedCleartext = "hello world".getBytes(StandardCharsets.UTF_8); - private EncryptedMessage encryptedMessage; - - @BeforeEach - public void setup() throws NoSuchAlgorithmException { - byte[] cleartext = "hello world".getBytes(StandardCharsets.UTF_8); - this.encryptedMessage = ecies.encrypt(keyGen, (ECPublicKey) receiverKeyPair.getPublic(), cleartext); - } - - @Test - @DisplayName("decrypt(...)") - public void testDecrypt() throws AEADBadTagException { - byte[] cleartext = ecies.decrypt((ECPrivateKey) receiverKeyPair.getPrivate(), encryptedMessage); - Assertions.assertArrayEquals(expectedCleartext, cleartext); - } - - @Test - @DisplayName("decrypt(...) with invalid key") - public void testDecryptWithInvalidKey() { - ECPrivateKey wrongPrivateKey = (ECPrivateKey) keyGen.generateKeyPair().getPrivate(); - Assertions.assertThrows(AEADBadTagException.class, () -> { - ecies.decrypt(wrongPrivateKey, encryptedMessage); - }); - } - - } - - } - -} \ No newline at end of file diff --git a/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java b/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java deleted file mode 100644 index 48cb923..0000000 --- a/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.cryptomator.cryptolib.ecies; - -import org.cryptomator.cryptolib.common.CipherSupplier; -import org.cryptomator.cryptolib.common.GcmTestHelper; -import org.cryptomator.cryptolib.common.ObjectPool; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.converter.ConvertWith; -import org.junit.jupiter.params.provider.CsvSource; - -import javax.crypto.AEADBadTagException; -import javax.crypto.Cipher; - -/** - * Test vectors from https://csrc.nist.rip/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf - */ -public class GcmWithSecretNonceTest { - - private final GcmWithSecretNonce ae = new GcmWithSecretNonce(); - - @BeforeEach - public void setup() { - // reset cipher state to avoid InvalidAlgorithmParameterExceptions due to IV-reuse - GcmTestHelper.reset((mode, key, params) -> { - try (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encryptionCipher(key, params)) { - cipher.get(); - } - }); - } - - @Test - public void testRequiredSecretBytes() { - Assertions.assertEquals(44, ae.requiredSecretBytes()); - } - - @ParameterizedTest - @CsvSource(value = { - "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, , 530f8afbc74536b9a963b4f1c4cb738b", // test case 13 - "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, 00000000000000000000000000000000, cea7403d4d606b6e074ec5d3baf39d18d0d1c8a799996bf0265b98b5d48ab919", // test case 14 - "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308cafebabefacedbaddecaf888, d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255, 522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015adb094dac5d93471bdec1a502270e3cc6c", // test case 15 - }) - public void testEncrypt(@ConvertWith(HexConverter.class) byte[] secret, @ConvertWith(HexConverter.class) byte[] plaintext, @ConvertWith(HexConverter.class) byte[] expectedCiphertext) { - byte[] ciphertext = ae.encrypt(secret, plaintext); - Assertions.assertArrayEquals(expectedCiphertext, ciphertext); - } - - @ParameterizedTest - @CsvSource(value = { - "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, , 530f8afbc74536b9a963b4f1c4cb738b", // test case 13 - "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, 00000000000000000000000000000000, cea7403d4d606b6e074ec5d3baf39d18d0d1c8a799996bf0265b98b5d48ab919", // test case 14 - "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308cafebabefacedbaddecaf888, d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255, 522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015adb094dac5d93471bdec1a502270e3cc6c", // test case 15 - }) - public void testDecrypt(@ConvertWith(HexConverter.class) byte[] secret, @ConvertWith(HexConverter.class) byte[] expectedPlaintext, @ConvertWith(HexConverter.class) byte[] ciphertext) throws AEADBadTagException { - byte[] plaintext = ae.decrypt(secret, ciphertext); - Assertions.assertArrayEquals(expectedPlaintext, plaintext); - } - - - @ParameterizedTest - @CsvSource(value = { - "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, 530f8afbc74536b9a963b4f1c4cb738b", - "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000, 530f8afbc74536b9a963b4f1c4cb738b", - }) - public void testDecryptInvalid(@ConvertWith(HexConverter.class) byte[] secret, @ConvertWith(HexConverter.class) byte[] ciphertext) { - Assertions.assertThrows(AEADBadTagException.class, () -> { - ae.decrypt(secret, ciphertext); - }); - } - -} \ No newline at end of file diff --git a/src/test/java/org/cryptomator/cryptolib/ecies/HexConverter.java b/src/test/java/org/cryptomator/cryptolib/ecies/HexConverter.java deleted file mode 100644 index 2daf59e..0000000 --- a/src/test/java/org/cryptomator/cryptolib/ecies/HexConverter.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.cryptomator.cryptolib.ecies; - -import com.google.common.io.BaseEncoding; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.params.converter.ArgumentConversionException; -import org.junit.jupiter.params.converter.ArgumentConverter; - -class HexConverter implements ArgumentConverter { - - @Override - public byte[] convert(Object source, ParameterContext context) throws ArgumentConversionException { - if (source == null) { - return new byte[0]; - } else if (source instanceof String) { - return BaseEncoding.base16().lowerCase().decode((String) source); - } else { - return null; - } - } -} diff --git a/src/test/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunctionTest.java b/src/test/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunctionTest.java deleted file mode 100644 index 6395dc9..0000000 --- a/src/test/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunctionTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.cryptomator.cryptolib.ecies; - -import com.google.common.io.BaseEncoding; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.converter.ConvertWith; -import org.junit.jupiter.params.provider.CsvSource; - -public class KeyDerivationFunctionTest { - - @DisplayName("ANSI-X9.63-KDF") - @ParameterizedTest - @CsvSource(value = { - "96c05619d56c328ab95fe84b18264b08725b85e33fd34f08, , 16, 443024c3dae66b95e6f5670601558f71", - "96f600b73ad6ac5629577eced51743dd2c24c21b1ac83ee4, , 16, b6295162a7804f5667ba9070f82fa522", - "22518b10e70f2a3f243810ae3254139efbee04aa57c7af7d, 75eef81aa3041e33b80971203d2c0c52, 128, c498af77161cc59f2962b9a713e2b215152d139766ce34a776df11866a69bf2e52a13d9c7c6fc878c50c5ea0bc7b00e0da2447cfd874f6cf92f30d0097111485500c90c3af8b487872d04685d14c8d1dc8d7fa08beb0ce0ababc11f0bd496269142d43525a78e5bc79a17f59676a5706dc54d54d4d1f0bd7e386128ec26afc21", - "7e335afa4b31d772c0635c7b0e06f26fcd781df947d2990a, d65a4812733f8cdbcdfb4b2f4c191d87, 128, c0bd9e38a8f9de14c2acd35b2f3410c6988cf02400543631e0d6a4c1d030365acbf398115e51aaddebdc9590664210f9aa9fed770d4c57edeafa0b8c14f93300865251218c262d63dadc47dfa0e0284826793985137e0a544ec80abf2fdf5ab90bdaea66204012efe34971dc431d625cd9a329b8217cc8fd0d9f02b13f2f6b0b", - }) - // test vectors from https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/components/800-135testvectors/ansx963_2001.zip - public void testAnsiX963sha256(@ConvertWith(HexConverter.class) byte[] sharedSecret, @ConvertWith(HexConverter.class) byte[] sharedInfo, int outLen, @ConvertWith(HexConverter.class) byte[] expectedResult) { - byte[] result = KeyDerivationFunction.ansiX963sha256Kdf(sharedSecret, sharedInfo, outLen); - Assertions.assertArrayEquals(expectedResult, result); - } - - @DisplayName("kdf.deriveKey()") - @Test - public void testDeriveKey() { - byte[] secret = BaseEncoding.base16().lowerCase().decode("96c05619d56c328ab95fe84b18264b08725b85e33fd34f08"); - - byte[] result = KeyDerivationFunction.ANSI_X963_SHA256_KDF.deriveKey(secret, 16); - - byte[] expected = BaseEncoding.base16().lowerCase().decode("443024c3dae66b95e6f5670601558f71"); - Assertions.assertArrayEquals(expected, result); - } - -} \ No newline at end of file From 2d696b4d8293a2d266b2261358bdc4e5ebce6b4c Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 12 Oct 2022 13:36:08 +0200 Subject: [PATCH 29/30] updated module-info and replaced moditect-maven-plugin --- pom.xml | 61 ++++++++++----------------------- src/main/java9/module-info.java | 19 ++++++++++ 2 files changed, 37 insertions(+), 43 deletions(-) create mode 100644 src/main/java9/module-info.java diff --git a/pom.xml b/pom.xml index 8020be3..93c7da9 100644 --- a/pom.xml +++ b/pom.xml @@ -156,10 +156,26 @@ UTF-8 true
+ + + java9 + compile + + compile + + + 9 + + ${project.basedir}/src/main/java9 + + true + + +
maven-shade-plugin - 3.2.4 + 3.4.0 package @@ -179,7 +195,7 @@ org.bouncycastle - org.cryptomator.cryptolib.org.bouncycastle + org.cryptomator.cryptolib.shaded.bouncycastle @@ -194,47 +210,6 @@ - - org.moditect - moditect-maven-plugin - 1.0.0.RC2 - - - add-module-infos - package - - add-module-info - - - 9 - true - - - module org.cryptomator.cryptolib { - requires org.cryptomator.siv; - requires com.google.gson; - requires transitive com.google.common; - requires org.slf4j; - - exports org.cryptomator.cryptolib.api; - exports org.cryptomator.cryptolib.common; - - opens org.cryptomator.cryptolib.common to com.google.gson; - - uses org.cryptomator.cryptolib.api.CryptorProvider; - - provides org.cryptomator.cryptolib.api.CryptorProvider - with org.cryptomator.cryptolib.v1.CryptorProviderImpl, org.cryptomator.cryptolib.v2.CryptorProviderImpl; - } - - - - --multi-release=9 - - - - - org.apache.maven.plugins maven-surefire-plugin diff --git a/src/main/java9/module-info.java b/src/main/java9/module-info.java new file mode 100644 index 0000000..8d362b2 --- /dev/null +++ b/src/main/java9/module-info.java @@ -0,0 +1,19 @@ +module org.cryptomator.cryptolib { + requires static org.bouncycastle.provider; // will be shaded + requires static org.bouncycastle.pkix; // will be shaded + requires jdk.crypto.ec; // required at runtime for ECC + requires org.cryptomator.siv; + requires com.google.gson; + requires transitive com.google.common; + requires org.slf4j; + + exports org.cryptomator.cryptolib.api; + exports org.cryptomator.cryptolib.common; + + opens org.cryptomator.cryptolib.common to com.google.gson; + + uses org.cryptomator.cryptolib.api.CryptorProvider; + + provides org.cryptomator.cryptolib.api.CryptorProvider + with org.cryptomator.cryptolib.v1.CryptorProviderImpl, org.cryptomator.cryptolib.v2.CryptorProviderImpl; +} \ No newline at end of file From 739490c3c502aacb8ce10db72366b885559e0f6a Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 12 Oct 2022 14:11:32 +0200 Subject: [PATCH 30/30] added JavaDoc --- pom.xml | 2 ++ src/main/java9/module-info.java | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 93c7da9..2189bb5 100644 --- a/pom.xml +++ b/pom.xml @@ -252,6 +252,8 @@ + 9 + ${project.basedir}/src/main/java9:${project.build.sourceDirectory} diff --git a/src/main/java9/module-info.java b/src/main/java9/module-info.java index 8d362b2..512fb83 100644 --- a/src/main/java9/module-info.java +++ b/src/main/java9/module-info.java @@ -1,3 +1,12 @@ +import org.cryptomator.cryptolib.api.CryptorProvider; + +/** + * This module provides the highlevel cryptographic API used by Cryptomator. + * + * @uses CryptorProvider See {@link CryptorProvider#forScheme(CryptorProvider.Scheme)} + * @provides CryptorProvider Providers for {@link org.cryptomator.cryptolib.api.CryptorProvider.Scheme#SIV_CTRMAC SIV/CTR-then-MAC} + * and {@link org.cryptomator.cryptolib.api.CryptorProvider.Scheme#SIV_GCM SIV/GCM} + */ module org.cryptomator.cryptolib { requires static org.bouncycastle.provider; // will be shaded requires static org.bouncycastle.pkix; // will be shaded @@ -12,8 +21,8 @@ opens org.cryptomator.cryptolib.common to com.google.gson; - uses org.cryptomator.cryptolib.api.CryptorProvider; + uses CryptorProvider; - provides org.cryptomator.cryptolib.api.CryptorProvider + provides CryptorProvider with org.cryptomator.cryptolib.v1.CryptorProviderImpl, org.cryptomator.cryptolib.v2.CryptorProviderImpl; } \ No newline at end of file