lease = mdPool.get();
+ lease.get().reset();
+ return lease;
+ }
+
+ /**
+ * Creates a new MessageDigest.
+ *
+ * @return New MessageDigest instance
+ * @deprecated Use {@link #instance()}
+ */
+ @Deprecated
public MessageDigest get() {
- final MessageDigest result = threadLocal.get();
+ final MessageDigest result = createMessageDigest();
result.reset();
return result;
}
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..00de730
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptolib/common/ObjectPool.java
@@ -0,0 +1,71 @@
+package org.cryptomator.cryptolib.common;
+
+import java.lang.ref.WeakReference;
+import java.util.Queue;
+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;
+ private final Supplier factory;
+
+ public ObjectPool(Supplier factory) {
+ this.returnedInstances = new ConcurrentLinkedQueue<>();
+ this.factory = factory;
+ }
+
+ public Lease get() {
+ WeakReference ref;
+ while ((ref = returnedInstances.poll()) != null) {
+ T cached = ref.get();
+ if (cached != null) {
+ return new Lease<>(this, cached);
+ }
+ }
+ return new Lease<>(this, factory.get());
+ }
+
+ /**
+ * 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;
+
+ private Lease(ObjectPool pool, T obj) {
+ this.pool = pool;
+ this.obj = obj;
+ }
+
+ public T get() {
+ return obj;
+ }
+
+ @Override
+ public void close() {
+ pool.returnedInstances.add(new WeakReference<>(obj));
+ obj = null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/cryptolib/common/P384KeyPair.java b/src/main/java/org/cryptomator/cryptolib/common/P384KeyPair.java
index a6ecbbb..feb7d5f 100644
--- a/src/main/java/org/cryptomator/cryptolib/common/P384KeyPair.java
+++ b/src/main/java/org/cryptomator/cryptolib/common/P384KeyPair.java
@@ -7,11 +7,20 @@
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.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 {
@@ -20,7 +29,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() {
@@ -28,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
*
@@ -97,4 +125,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/main/java/org/cryptomator/cryptolib/common/Scrypt.java b/src/main/java/org/cryptomator/cryptolib/common/Scrypt.java
index 4d35aaf..05134a0 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");
+ ObjectPool.Lease 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/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 891ef83..0000000
--- a/src/main/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonce.java
+++ /dev/null
@@ -1,47 +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 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);
- Cipher cipher = CipherSupplier.AES_GCM.forEncryption(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce));
- return cipher.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);
- Cipher cipher = CipherSupplier.AES_GCM.forDecryption(key, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce));
- return cipher.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 f7a6b95..0000000
--- a/src/main/java/org/cryptomator/cryptolib/ecies/KeyDerivationFunction.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package org.cryptomator.cryptolib.ecies;
-
-import org.cryptomator.cryptolib.common.MessageDigestSupplier;
-
-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) {
- 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();
-
- // 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 {
- for (int i = 0; i < n; i++) {
- digest.update(sharedSecret);
- counter.clear();
- counter.putInt(i + 1);
- counter.flip();
- digest.update(counter);
- digest.update(sharedInfo);
- digest.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/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/v1/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java
index cad0d0b..004ff10 100644
--- a/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java
+++ b/src/main/java/org/cryptomator/cryptolib/v1/FileContentCryptorImpl.java
@@ -15,6 +15,7 @@
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;
@@ -107,17 +108,18 @@ 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);
+ try (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.encryptionCipher(fk, new IvParameterSpec(nonce))) {
+ assert ciphertextChunk.remaining() >= cipher.get().getOutputSize(cleartextChunk.remaining()) + MAC_SIZE;
+ cipher.get().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,18 +136,17 @@ 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:
- final Cipher cipher = CipherSupplier.AES_CTR.forDecryption(fk, new IvParameterSpec(nonce));
- assert cleartextChunk.remaining() >= cipher.getOutputSize(payloadBuf.remaining());
- cipher.doFinal(payloadBuf, cleartextChunk);
+ 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);
+ }
} catch (ShortBufferException e) {
throw new IllegalStateException("Buffer allocated for reported output size apparently not big enough.", e);
} catch (IllegalBlockSizeException | BadPaddingException e) {
@@ -157,38 +158,31 @@ 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) {
- try (DestroyableSecretKey mk = masterkey.getMacKey()) {
+ private byte[] calcChunkMac(byte[] headerNonce, long chunkNumber, ByteBuffer nonceAndCiphertext) {
+ try (DestroyableSecretKey mk = masterkey.getMacKey();
+ ObjectPool.Lease 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(nonceAndCiphertext);
+ 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 aeaa7ee..f1b0c59 100644
--- a/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java
+++ b/src/main/java/org/cryptomator/cryptolib/v1/FileHeaderCryptorImpl.java
@@ -15,6 +15,7 @@
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;
@@ -61,16 +62,18 @@ 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 (ObjectPool.Lease cipher = CipherSupplier.AES_CTR.encryptionCipher(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();
nonceAndCiphertextBuf.flip();
- Mac mac = MacSupplier.HMAC_SHA256.withKey(mk);
- mac.update(nonceAndCiphertextBuf);
- result.put(mac.doFinal());
+ try (ObjectPool.Lease mac = MacSupplier.HMAC_SHA256.keyed(mk)) {
+ mac.get().update(nonceAndCiphertextBuf);
+ result.put(mac.get().doFinal());
+ }
result.flip();
return result;
@@ -88,7 +91,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);
@@ -100,12 +103,12 @@ public FileHeader decryptHeader(ByteBuffer ciphertextHeaderBuf) throws Authentic
buf.get(expectedMac);
// check mac:
- try (DestroyableSecretKey mk = masterkey.getMacKey()) {
+ try (DestroyableSecretKey mk = masterkey.getMacKey();
+ 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 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.");
}
@@ -114,10 +117,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 (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;
+ }
payloadCleartextBuf.flip();
FileHeaderImpl.Payload payload = FileHeaderImpl.Payload.decode(payloadCleartextBuf);
diff --git a/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java
index ad99625..5104aa5 100644
--- a/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java
+++ b/src/main/java/org/cryptomator/cryptolib/v1/FileNameCryptorImpl.java
@@ -14,22 +14,19 @@
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 {
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;
@@ -39,28 +36,32 @@ 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();
+ 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[] hashedBytes = MessageDigestSupplier.SHA1.get().digest(encryptedBytes);
+ byte[] encryptedBytes = siv.get().encrypt(ek, mk, cleartextBytes);
+ byte[] hashedBytes = sha1.get().digest(encryptedBytes);
return BASE32.encode(hashedBytes);
}
}
@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/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/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java
index a03eea9..affb486 100644
--- a/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java
+++ b/src/main/java/org/cryptomator/cryptolib/v2/FileContentCryptorImpl.java
@@ -13,6 +13,7 @@
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;
@@ -103,13 +104,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 (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);
+ 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) {
@@ -124,22 +126,21 @@ 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;
// 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 (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);
+ 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..e17d85b 100644
--- a/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java
+++ b/src/main/java/org/cryptomator/cryptolib/v2/FileHeaderCryptorImpl.java
@@ -14,6 +14,7 @@
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;
@@ -61,10 +62,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 (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;
+ }
result.flip();
return result;
} catch (ShortBufferException e) {
@@ -81,7 +82,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);
@@ -93,9 +94,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 (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;
+ }
payloadCleartextBuf.flip();
FileHeaderImpl.Payload payload = FileHeaderImpl.Payload.decode(payloadCleartextBuf);
diff --git a/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java b/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java
index 9f14680..0498afe 100644
--- a/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java
+++ b/src/main/java/org/cryptomator/cryptolib/v2/FileNameCryptorImpl.java
@@ -14,22 +14,19 @@
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 {
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;
@@ -39,28 +36,32 @@ 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();
+ 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[] hashedBytes = MessageDigestSupplier.SHA1.get().digest(encryptedBytes);
+ byte[] encryptedBytes = siv.get().encrypt(ek, mk, cleartextBytes);
+ byte[] hashedBytes = sha1.get().digest(encryptedBytes);
return BASE32.encode(hashedBytes);
}
}
@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);
diff --git a/src/main/java9/module-info.java b/src/main/java9/module-info.java
new file mode 100644
index 0000000..512fb83
--- /dev/null
+++ b/src/main/java9/module-info.java
@@ -0,0 +1,28 @@
+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
+ 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 CryptorProvider;
+
+ provides CryptorProvider
+ with org.cryptomator.cryptolib.v1.CryptorProviderImpl, org.cryptomator.cryptolib.v2.CryptorProviderImpl;
+}
\ No newline at end of file
diff --git a/src/test/java/org/cryptomator/cryptolib/api/CryptoLibIntegrationTest.java b/src/test/java/org/cryptomator/cryptolib/api/CryptoLibIntegrationTest.java
index 0b8f78f..f846629 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;
@@ -34,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)
);
}
diff --git a/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java b/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java
index 03a97eb..9923cce 100644
--- a/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java
+++ b/src/test/java/org/cryptomator/cryptolib/common/CipherSupplierTest.java
@@ -13,10 +13,10 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
-import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.RC5ParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
+import java.security.spec.AlgorithmParameterSpec;
public class CipherSupplierTest {
@@ -30,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.forMode(Cipher.ENCRYPT_MODE, new DestroyableSecretKey(new byte[13], "AES"), new IvParameterSpec(new byte[16]));
+ supplier.encryptionCipher(key, params);
});
MatcherAssert.assertThat(exception.getMessage(), CoreMatchers.containsString("Invalid key"));
}
@@ -39,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.forMode(Cipher.ENCRYPT_MODE, 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/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
diff --git a/src/test/java/org/cryptomator/cryptolib/common/MacSupplierTest.java b/src/test/java/org/cryptomator/cryptolib/common/MacSupplierTest.java
index 9a3458d..2155138 100644
--- a/src/test/java/org/cryptomator/cryptolib/common/MacSupplierTest.java
+++ b/src/test/java/org/cryptomator/cryptolib/common/MacSupplierTest.java
@@ -15,7 +15,6 @@
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 +23,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 (ObjectPool.Lease mac1 = MacSupplier.HMAC_SHA256.keyed(key)) {
+ Assertions.assertNotNull(mac1);
+ }
- Mac mac2 = MacSupplier.HMAC_SHA256.withKey(key);
- Assertions.assertSame(mac1, mac2);
+ try (ObjectPool.Lease 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);
});
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/common/MessageDigestSupplierTest.java b/src/test/java/org/cryptomator/cryptolib/common/MessageDigestSupplierTest.java
index ad67c66..6c4d290 100644
--- a/src/test/java/org/cryptomator/cryptolib/common/MessageDigestSupplierTest.java
+++ b/src/test/java/org/cryptomator/cryptolib/common/MessageDigestSupplierTest.java
@@ -18,17 +18,19 @@ 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 (ObjectPool.Lease digest = MessageDigestSupplier.SHA1.instance()) {
+ Assertions.assertNotNull(digest);
+ }
- MessageDigest digest2 = MessageDigestSupplier.SHA1.get();
- Assertions.assertSame(digest1, digest2);
+ 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
new file mode 100644
index 0000000..a435c7d
--- /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
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) {
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());
+ }
+ }
+
+}
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/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 91ed77a..0000000
--- a/src/test/java/org/cryptomator/cryptolib/ecies/GcmWithSecretNonceTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package org.cryptomator.cryptolib.ecies;
-
-import org.cryptomator.cryptolib.common.CipherSupplier;
-import org.cryptomator.cryptolib.common.GcmTestHelper;
-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;
-
-/**
- * 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) -> {
- CipherSupplier.AES_GCM.forEncryption(key, params);
- });
- }
-
- @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
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;
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 1cfc50f..baea1a5 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 java.nio.ByteBuffer;
import java.security.SecureRandom;
@@ -35,7 +37,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 (ObjectPool.Lease cipher = CipherSupplier.AES_GCM.encryptionCipher(key, params)) {
+ cipher.get();
+ }
});
}
@@ -74,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);
});
}
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