diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/AesGcmJceKeyCipher.java b/src/main/java/com/amazonaws/encryptionsdk/internal/AesGcmJceKeyCipher.java index 7a4511f01..00f2cbbd1 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/AesGcmJceKeyCipher.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/AesGcmJceKeyCipher.java @@ -32,7 +32,7 @@ class AesGcmJceKeyCipher extends JceKeyCipher { private static final int SPEC_LENGTH = Integer.BYTES + Integer.BYTES + NONCE_LENGTH; AesGcmJceKeyCipher(SecretKey key) { - super(key, key); + super(key, key, true); } private static byte[] specToBytes(final GCMParameterSpec spec) { diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/JceKeyCipher.java b/src/main/java/com/amazonaws/encryptionsdk/internal/JceKeyCipher.java index 643278a71..16aefda35 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/JceKeyCipher.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/JceKeyCipher.java @@ -36,6 +36,7 @@ public abstract class JceKeyCipher { private final Key wrappingKey; private final Key unwrappingKey; private static final Charset KEY_NAME_ENCODING = StandardCharsets.UTF_8; + private final boolean encryptionContextSigned; /** * Returns a new instance of a JceKeyCipher based on the @@ -60,9 +61,10 @@ public static JceKeyCipher rsa(PublicKey wrappingKey, PrivateKey unwrappingKey, return new RsaJceKeyCipher(wrappingKey, unwrappingKey, transformation); } - JceKeyCipher(Key wrappingKey, Key unwrappingKey) { + JceKeyCipher(Key wrappingKey, Key unwrappingKey, boolean encryptionContextSigned) { this.wrappingKey = wrappingKey; this.unwrappingKey = unwrappingKey; + this.encryptionContextSigned = encryptionContextSigned; } abstract WrappingData buildWrappingCipher(Key key, Map encryptionContext) throws GeneralSecurityException; @@ -70,6 +72,15 @@ public static JceKeyCipher rsa(PublicKey wrappingKey, PrivateKey unwrappingKey, abstract Cipher buildUnwrappingCipher(Key key, byte[] extraInfo, int offset, Map encryptionContext) throws GeneralSecurityException; + /** + * Returns true if this key cipher supports signing and verification + * of the encryption context. + * + * @return True if encryption context signing/verification is supported. + */ + public boolean isEncryptionContextSigned() { + return encryptionContextSigned; + } /** * Encrypts the given key, incorporating the given keyName and encryptionContext. diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/RsaJceKeyCipher.java b/src/main/java/com/amazonaws/encryptionsdk/internal/RsaJceKeyCipher.java index c830f5487..47b65514a 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/RsaJceKeyCipher.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/RsaJceKeyCipher.java @@ -44,7 +44,7 @@ class RsaJceKeyCipher extends JceKeyCipher { private final String transformation_; RsaJceKeyCipher(PublicKey wrappingKey, PrivateKey unwrappingKey, String transformation) { - super(wrappingKey, unwrappingKey); + super(wrappingKey, unwrappingKey, false); final Matcher matcher = SUPPORTED_TRANSFORMATIONS.matcher(transformation); if (matcher.matches()) { diff --git a/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java b/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java index 4995066cb..6c6e03e34 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java +++ b/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java @@ -104,6 +104,16 @@ public String getKeyId() { return keyId_; } + /** + * Returns true if the underlying key cipher supports signing and + * verification of the encryption context. + * + * @return True if encryption context signing/verification is supported. + */ + public boolean isEncryptionContextSigned() { + return jceKeyCipher_.isEncryptionContextSigned(); + } + @Override public DataKey generateDataKey(final CryptoAlgorithm algorithm, final Map encryptionContext) { diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyring.java new file mode 100644 index 000000000..7e0b14675 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyring.java @@ -0,0 +1,137 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.DataKey; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.MasterKey; +import com.amazonaws.encryptionsdk.MasterKeyProvider; +import com.amazonaws.encryptionsdk.MasterKeyRequest; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; + +import java.util.ArrayList; +import java.util.List; + +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.DECRYPTED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.ENCRYPTED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT; +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.ArrayUtils.EMPTY_BYTE_ARRAY; + +/** + * A keyring which wraps a legacy MasterKeyProvider to + * facilitate transition to keyrings. + */ +class MasterKeyProviderKeyring> implements Keyring { + + private final MasterKeyProvider masterKeyProvider; + + MasterKeyProviderKeyring(MasterKeyProvider masterKeyProvider) { + requireNonNull(masterKeyProvider, "masterKeyProvider is required"); + + this.masterKeyProvider = masterKeyProvider; + } + + @Override + public void onEncrypt(EncryptionMaterials encryptionMaterials) { + requireNonNull(encryptionMaterials, "encryptionMaterials are required"); + + final List masterKeys = masterKeyProvider.getMasterKeysForEncryption(MasterKeyRequest.newBuilder() + .setEncryptionContext(encryptionMaterials.getEncryptionContext()).build()); + + if (masterKeys == null || masterKeys.isEmpty()) { + throw new AwsCryptoException("No master keys available from the master key provider."); + } + + final K primaryMasterKey = masterKeys.get(0); + final List masterKeysToEncryptWith = new ArrayList<>(masterKeys); + + if (!encryptionMaterials.hasPlaintextDataKey()) { + final DataKey dataKey = primaryMasterKey.generateDataKey( + encryptionMaterials.getAlgorithmSuite(), encryptionMaterials.getEncryptionContext()); + encryptionMaterials.setPlaintextDataKey(dataKey.getKey(), new KeyringTraceEntry( + primaryMasterKey.getProviderId(), primaryMasterKey.getKeyId(), KeyringTraceFlag.GENERATED_DATA_KEY)); + encryptionMaterials.addEncryptedDataKey(dataKey, encryptTraceEntry(primaryMasterKey)); + // The primary master key has already been used for encryption, so remove it from the list to encrypt with + masterKeysToEncryptWith.remove(primaryMasterKey); + } + + final DataKey dataKey = new DataKey<>(encryptionMaterials.getPlaintextDataKey(), EMPTY_BYTE_ARRAY, + EMPTY_BYTE_ARRAY, primaryMasterKey); + + for (K masterKey : masterKeysToEncryptWith) { + final EncryptedDataKey encryptedDataKey = masterKey.encryptDataKey(encryptionMaterials.getAlgorithmSuite(), + encryptionMaterials.getEncryptionContext(), dataKey); + encryptionMaterials.addEncryptedDataKey(encryptedDataKey, encryptTraceEntry(masterKey)); + } + } + + @Override + public void onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { + requireNonNull(decryptionMaterials, "decryptionMaterials are required"); + requireNonNull(encryptedDataKeys, "encryptedDataKeys are required"); + + if (decryptionMaterials.hasPlaintextDataKey()) { + return; + } + + final DataKey dataKey; + try { + dataKey = masterKeyProvider.decryptDataKey(decryptionMaterials.getAlgorithmSuite(), encryptedDataKeys, + decryptionMaterials.getEncryptionContext()); + } catch (CannotUnwrapDataKeyException e) { + return; + } + + decryptionMaterials.setPlaintextDataKey(dataKey.getKey(), decryptTraceEntry(dataKey.getMasterKey())); + } + + private boolean signedEncryptionContext(MasterKey masterKey) { + if (masterKey instanceof KmsMasterKey) { + return true; + } + + if (masterKey instanceof JceMasterKey) { + return ((JceMasterKey) masterKey).isEncryptionContextSigned(); + } + + return false; + } + + private KeyringTraceEntry encryptTraceEntry(MasterKey masterKey) { + final List flags = new ArrayList<>(); + flags.add(ENCRYPTED_DATA_KEY); + + if (signedEncryptionContext(masterKey)) { + flags.add(SIGNED_ENCRYPTION_CONTEXT); + } + + return new KeyringTraceEntry(masterKey.getProviderId(), masterKey.getKeyId(), flags.toArray(new KeyringTraceFlag[]{})); + } + + private KeyringTraceEntry decryptTraceEntry(MasterKey masterKey) { + final List flags = new ArrayList<>(); + flags.add(DECRYPTED_DATA_KEY); + + if (signedEncryptionContext(masterKey)) { + flags.add(VERIFIED_ENCRYPTION_CONTEXT); + } + + return new KeyringTraceEntry(masterKey.getProviderId(), masterKey.getKeyId(), flags.toArray(new KeyringTraceFlag[]{})); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyring.java index 5832658a9..bbd2a7495 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyring.java @@ -48,18 +48,4 @@ boolean validToDecrypt(EncryptedDataKey encryptedDataKey) { return true; } - - @Override - KeyringTraceEntry traceOnEncrypt() { - return new KeyringTraceEntry(keyNamespace, keyName, - KeyringTraceFlag.ENCRYPTED_DATA_KEY, - KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT); - } - - @Override - KeyringTraceEntry traceOnDecrypt() { - return new KeyringTraceEntry(keyNamespace, keyName, - KeyringTraceFlag.DECRYPTED_DATA_KEY, - KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java index 8daa567f8..3b4a8606e 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java @@ -24,6 +24,11 @@ import java.util.logging.Logger; import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.DECRYPTED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.ENCRYPTED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.GENERATED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT; import static java.util.Objects.requireNonNull; import static org.apache.commons.lang3.Validate.notBlank; @@ -57,20 +62,6 @@ abstract class RawKeyring implements Keyring { */ abstract boolean validToDecrypt(EncryptedDataKey encryptedDataKey); - /** - * Gets the trace entry to add the the keyring trace upon successful encryption. - * - * @return The keyring trace entry. - */ - abstract KeyringTraceEntry traceOnEncrypt(); - - /** - * Gets the trace entry to add to the keyring trace upon successful decryption. - * - * @return The keyring trace entry. - */ - abstract KeyringTraceEntry traceOnDecrypt(); - @Override public void onEncrypt(EncryptionMaterials encryptionMaterials) { requireNonNull(encryptionMaterials, "encryptionMaterials are required"); @@ -82,7 +73,8 @@ public void onEncrypt(EncryptionMaterials encryptionMaterials) { final EncryptedDataKey encryptedDataKey = jceKeyCipher.encryptKey( encryptionMaterials.getPlaintextDataKey().getEncoded(), keyName, keyNamespace, encryptionMaterials.getEncryptionContext()); - encryptionMaterials.addEncryptedDataKey(encryptedDataKey, traceOnEncrypt()); + encryptionMaterials.addEncryptedDataKey(encryptedDataKey, + new KeyringTraceEntry(keyNamespace, keyName, encryptTraceFlags())); } @Override @@ -101,7 +93,7 @@ public void onDecrypt(DecryptionMaterials decryptionMaterials, List masterKeyProvider) { + return new MasterKeyProviderKeyring<>(masterKeyProvider); + } + + /** * Constructs a {@code Keyring} which interacts with AWS Key Management Service (KMS) to create, * encrypt, and decrypt data keys using KMS defined Customer Master Keys (CMKs). * diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyringTest.java new file mode 100644 index 000000000..64b8b26dd --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyringTest.java @@ -0,0 +1,332 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.DataKey; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.MasterKey; +import com.amazonaws.encryptionsdk.MasterKeyProvider; +import com.amazonaws.encryptionsdk.MasterKeyRequest; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; +import com.amazonaws.encryptionsdk.model.KeyBlob; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.DECRYPTED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.ENCRYPTED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.GENERATED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class MasterKeyProviderKeyringTest { + + private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256; + private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); + private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("myKey", "myValue"); + + @Test + void testOnEncryptWithoutPlaintextDataKey() { + + MasterKeyProvider masterKeyProvider = mock(JceMasterKey.class); + + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + + JceMasterKey masterKey1 = mock(JceMasterKey.class); + JceMasterKey masterKey2 = mock(JceMasterKey.class); + + List masterKeys = new ArrayList<>(); + masterKeys.add(masterKey1); + masterKeys.add(masterKey2); + + ArgumentCaptor masterKeyRequestCaptor = ArgumentCaptor.forClass(MasterKeyRequest.class); + ArgumentCaptor dataKeyCaptor = ArgumentCaptor.forClass(DataKey.class); + + final String KEY_ID_1 = "KeyId1"; + final String KEY_ID_2 = "KeyId2"; + final String PROVIDER_1 = "Provider1"; + final String PROVIDER_2 = "Provider2"; + + DataKey dataKey1 = new DataKey<>(PLAINTEXT_DATA_KEY, generate(100), KEY_ID_1.getBytes(PROVIDER_ENCODING), masterKey1); + DataKey dataKey2 = new DataKey<>(PLAINTEXT_DATA_KEY, generate(100), KEY_ID_2.getBytes(PROVIDER_ENCODING), masterKey2); + + when(masterKeyProvider.getMasterKeysForEncryption(masterKeyRequestCaptor.capture())).thenReturn(masterKeys); + when(masterKey1.generateDataKey(ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenReturn(dataKey1); + when(masterKey2.encryptDataKey(eq(ALGORITHM_SUITE), eq(ENCRYPTION_CONTEXT), dataKeyCaptor.capture())) + .thenReturn(dataKey2); + when(masterKey1.getProviderId()).thenReturn(PROVIDER_1); + when(masterKey1.getKeyId()).thenReturn(KEY_ID_1); + when(masterKey2.getProviderId()).thenReturn(PROVIDER_2); + when(masterKey2.getKeyId()).thenReturn(KEY_ID_2); + when(masterKey1.isEncryptionContextSigned()).thenReturn(true); + when(masterKey2.isEncryptionContextSigned()).thenReturn(false); + + keyring.onEncrypt(encryptionMaterials); + + assertEquals(ENCRYPTION_CONTEXT, masterKeyRequestCaptor.getValue().getEncryptionContext()); + assertEquals(PLAINTEXT_DATA_KEY, dataKeyCaptor.getValue().getKey()); + assertEncryptedDataKeyEquals(dataKey1, encryptionMaterials.getEncryptedDataKeys().get(0)); + assertEncryptedDataKeyEquals(dataKey2, encryptionMaterials.getEncryptedDataKeys().get(1)); + assertEquals(new KeyringTraceEntry(PROVIDER_1, KEY_ID_1, GENERATED_DATA_KEY), + encryptionMaterials.getKeyringTrace().getEntries().get(0)); + assertEquals(new KeyringTraceEntry(PROVIDER_1, KEY_ID_1, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT), + encryptionMaterials.getKeyringTrace().getEntries().get(1)); + assertEquals(new KeyringTraceEntry(PROVIDER_2, KEY_ID_2, ENCRYPTED_DATA_KEY), + encryptionMaterials.getKeyringTrace().getEntries().get(2)); + } + + @Test + void testOnEncryptWithPlaintextDataKey() { + + MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); + + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .plaintextDataKey(PLAINTEXT_DATA_KEY) + .build(); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + + KmsMasterKey masterKey1 = mock(KmsMasterKey.class); + KmsMasterKey masterKey2 = mock(KmsMasterKey.class); + + List masterKeys = new ArrayList<>(); + masterKeys.add(masterKey1); + masterKeys.add(masterKey2); + + ArgumentCaptor masterKeyRequestCaptor = ArgumentCaptor.forClass(MasterKeyRequest.class); + ArgumentCaptor dataKeyCaptor = ArgumentCaptor.forClass(DataKey.class); + + final String KEY_ID_1 = "KeyId1"; + final String KEY_ID_2 = "KeyId2"; + final String PROVIDER_1 = "Provider1"; + final String PROVIDER_2 = "Provider2"; + + DataKey dataKey1 = new DataKey<>(PLAINTEXT_DATA_KEY, generate(100), KEY_ID_1.getBytes(PROVIDER_ENCODING), masterKey1); + DataKey dataKey2 = new DataKey<>(PLAINTEXT_DATA_KEY, generate(100), KEY_ID_2.getBytes(PROVIDER_ENCODING), masterKey2); + + when(masterKeyProvider.getMasterKeysForEncryption(masterKeyRequestCaptor.capture())).thenReturn(masterKeys); + when(masterKey1.encryptDataKey(eq(ALGORITHM_SUITE), eq(ENCRYPTION_CONTEXT), dataKeyCaptor.capture())) + .thenReturn(dataKey1); + when(masterKey2.encryptDataKey(eq(ALGORITHM_SUITE), eq(ENCRYPTION_CONTEXT), dataKeyCaptor.capture())) + .thenReturn(dataKey2); + when(masterKey1.getProviderId()).thenReturn(PROVIDER_1); + when(masterKey1.getKeyId()).thenReturn(KEY_ID_1); + when(masterKey2.getProviderId()).thenReturn(PROVIDER_2); + when(masterKey2.getKeyId()).thenReturn(KEY_ID_2); + + keyring.onEncrypt(encryptionMaterials); + + assertEquals(ENCRYPTION_CONTEXT, masterKeyRequestCaptor.getValue().getEncryptionContext()); + assertEquals(PLAINTEXT_DATA_KEY, dataKeyCaptor.getAllValues().get(0).getKey()); + assertEquals(PLAINTEXT_DATA_KEY, dataKeyCaptor.getAllValues().get(1).getKey()); + assertEncryptedDataKeyEquals(dataKey1, encryptionMaterials.getEncryptedDataKeys().get(0)); + assertEncryptedDataKeyEquals(dataKey2, encryptionMaterials.getEncryptedDataKeys().get(1)); + assertEquals(new KeyringTraceEntry(PROVIDER_1, KEY_ID_1, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT), + encryptionMaterials.getKeyringTrace().getEntries().get(0)); + assertEquals(new KeyringTraceEntry(PROVIDER_2, KEY_ID_2, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT), + encryptionMaterials.getKeyringTrace().getEntries().get(1)); + } + + @SuppressWarnings("unchecked") + @Test + void testOnEncryptWithNonKmsOrJceMasterKeyProvider() { + + MasterKeyProvider masterKeyProvider = mock(MasterKeyProvider.class); + + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .plaintextDataKey(PLAINTEXT_DATA_KEY) + .build(); + + Keyring keyring = new MasterKeyProviderKeyring(masterKeyProvider); + + MasterKey masterKey = mock(MasterKey.class); + + final String KEY_ID = "KeyId1"; + final String PROVIDER = "Provider1"; + + DataKey dataKey = new DataKey(PLAINTEXT_DATA_KEY, generate(100), KEY_ID.getBytes(PROVIDER_ENCODING), masterKey); + + when(masterKeyProvider.getMasterKeysForEncryption(isA(MasterKeyRequest.class))).thenReturn(singletonList(masterKey)); + when(masterKey.encryptDataKey(eq(ALGORITHM_SUITE), eq(ENCRYPTION_CONTEXT), isA(DataKey.class))) + .thenReturn(dataKey); + when(masterKey.getProviderId()).thenReturn(PROVIDER); + when(masterKey.getKeyId()).thenReturn(KEY_ID); + + keyring.onEncrypt(encryptionMaterials); + + assertEncryptedDataKeyEquals(dataKey, encryptionMaterials.getEncryptedDataKeys().get(0)); + assertEquals(new KeyringTraceEntry(PROVIDER, KEY_ID, ENCRYPTED_DATA_KEY), + encryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testOnEncryptWithNoMasterKeys() { + MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); + + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + + when(masterKeyProvider.getMasterKeysForEncryption(isA(MasterKeyRequest.class))).thenReturn(emptyList()); + + assertThrows(AwsCryptoException.class, () -> keyring.onEncrypt(encryptionMaterials)); + } + + @Test + void testOnDecryptWithPlaintextDataKey() { + MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .plaintextDataKey(PLAINTEXT_DATA_KEY) + .build(); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + keyring.onDecrypt(decryptionMaterials, emptyList()); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + } + + @Test + void testOnDecrypt() { + MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); + KmsMasterKey masterKey = mock(KmsMasterKey.class); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + final String KEY_ID = "KeyId1"; + final String PROVIDER = "Provider1"; + + EncryptedDataKey encryptedDataKey = new KeyBlob(PROVIDER, + KEY_ID.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + + when(masterKeyProvider.decryptDataKey(ALGORITHM_SUITE, singletonList(encryptedDataKey), ENCRYPTION_CONTEXT)) + .thenReturn(new DataKey<>(PLAINTEXT_DATA_KEY, encryptedDataKey.getEncryptedDataKey(), + encryptedDataKey.getProviderInformation(), masterKey)); + when(masterKey.getProviderId()).thenReturn(PROVIDER); + when(masterKey.getKeyId()).thenReturn(KEY_ID); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + keyring.onDecrypt(decryptionMaterials, singletonList(encryptedDataKey)); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(new KeyringTraceEntry(PROVIDER, KEY_ID, DECRYPTED_DATA_KEY, VERIFIED_ENCRYPTION_CONTEXT), + decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testOnDecryptMasterKeyCannotUnwrapDataKeyException() { + MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + EncryptedDataKey encryptedDataKey = mock(EncryptedDataKey.class); + + when(masterKeyProvider.decryptDataKey(ALGORITHM_SUITE, singletonList(encryptedDataKey), ENCRYPTION_CONTEXT)) + .thenThrow(new CannotUnwrapDataKeyException()); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + keyring.onDecrypt(decryptionMaterials, singletonList(encryptedDataKey)); + + assertFalse(decryptionMaterials.hasPlaintextDataKey()); + } + + @Test + void testOnDecryptMasterKeyOtherException() { + MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + EncryptedDataKey encryptedDataKey = mock(EncryptedDataKey.class); + + when(masterKeyProvider.decryptDataKey(ALGORITHM_SUITE, singletonList(encryptedDataKey), ENCRYPTION_CONTEXT)) + .thenThrow(new AwsCryptoException()); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + assertThrows(AwsCryptoException.class, () -> keyring.onDecrypt(decryptionMaterials, singletonList(encryptedDataKey))); + } + + @Test + void testOnDecryptNonVerifiedEncryptionContext() { + MasterKeyProvider masterKeyProvider = mock(JceMasterKey.class); + JceMasterKey masterKey = mock(JceMasterKey.class); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + final String KEY_ID = "KeyId1"; + final String PROVIDER = "Provider1"; + + EncryptedDataKey encryptedDataKey = new KeyBlob(PROVIDER, + KEY_ID.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + + when(masterKeyProvider.decryptDataKey(ALGORITHM_SUITE, singletonList(encryptedDataKey), ENCRYPTION_CONTEXT)) + .thenReturn(new DataKey<>(PLAINTEXT_DATA_KEY, encryptedDataKey.getEncryptedDataKey(), + encryptedDataKey.getProviderInformation(), masterKey)); + when(masterKey.getProviderId()).thenReturn(PROVIDER); + when(masterKey.getKeyId()).thenReturn(KEY_ID); + when(masterKey.isEncryptionContextSigned()).thenReturn(false); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + keyring.onDecrypt(decryptionMaterials, singletonList(encryptedDataKey)); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(new KeyringTraceEntry(PROVIDER, KEY_ID, DECRYPTED_DATA_KEY), + decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + private static void assertEncryptedDataKeyEquals(EncryptedDataKey expected, EncryptedDataKey actual) { + assertEquals(expected.getProviderId(), actual.getProviderId()); + assertArrayEquals(expected.getProviderInformation(), actual.getProviderInformation()); + assertArrayEquals(expected.getEncryptedDataKey(), actual.getEncryptedDataKey()); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java index 945aa17bc..3fe1fb416 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java @@ -66,16 +66,6 @@ void setup() throws Exception { boolean validToDecrypt(EncryptedDataKey encryptedDataKey) { return !encryptedDataKey.getProviderId().equals(INVALID_DATA_KEY.getProviderId()); } - - @Override - KeyringTraceEntry traceOnEncrypt() { - return ENCRYPTED_DATA_KEY_TRACE; - } - - @Override - KeyringTraceEntry traceOnDecrypt() { - return DECRYPTED_DATA_KEY_TRACE; - } }; }