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: - * - */ - 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