diff --git a/src/examples/java/com/amazonaws/crypto/examples/v2/CustomCMMExample.java b/src/examples/java/com/amazonaws/crypto/examples/v2/CustomCMMExample.java
new file mode 100644
index 000000000..fad8e44b8
--- /dev/null
+++ b/src/examples/java/com/amazonaws/crypto/examples/v2/CustomCMMExample.java
@@ -0,0 +1,124 @@
+// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package com.amazonaws.crypto.examples.v2;
+
+import com.amazonaws.encryptionsdk.AwsCrypto;
+import com.amazonaws.encryptionsdk.CommitmentPolicy;
+import com.amazonaws.encryptionsdk.CryptoMaterialsManager;
+import com.amazonaws.encryptionsdk.CryptoResult;
+import com.amazonaws.encryptionsdk.DefaultCryptoMaterialsManager;
+import com.amazonaws.encryptionsdk.MasterKeyProvider;
+import com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKeyProvider;
+import com.amazonaws.encryptionsdk.model.DecryptionMaterials;
+import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequest;
+import com.amazonaws.encryptionsdk.model.EncryptionMaterials;
+import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ *
+ * Creates a custom implementation of the CryptoMaterialsManager interface,
+ * then uses that implementation to encrypt and decrypt a file using an AWS KMS CMK.
+ *
+ *
+ * Arguments:
+ *
+ * - Key ARN: For help finding the Amazon Resource Name (ARN) of your AWS KMS customer master
+ * key (CMK), see 'Viewing Keys' at http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html
+ *
+ */
+public class CustomCMMExample {
+
+ private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8);
+
+ public static void main(final String[] args) {
+ final String keyArn = args[0];
+
+ CryptoMaterialsManager cmm = new SigningSuiteOnlyCMM(
+ KmsMasterKeyProvider.builder().buildStrict(keyArn)
+ );
+
+ encryptAndDecryptWithCMM(cmm);
+ }
+
+ static void encryptAndDecryptWithCMM(final CryptoMaterialsManager cmm) {
+ // 1. Instantiate the SDK
+ // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy,
+ // which enforces that this client only encrypts using committing algorithm suites and enforces
+ // that this client will only decrypt encrypted messages that were created with a committing algorithm suite.
+ // This is the default commitment policy if you build the client with `AwsCrypto.builder().build()`
+ // or `AwsCrypto.standard()`.
+ final AwsCrypto crypto = AwsCrypto.builder()
+ .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt)
+ .build();
+
+ // 2. Create an encryption context
+ // Most encrypted data should have an associated encryption context
+ // to protect integrity. This sample uses placeholder values.
+ // For more information see:
+ // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management
+ final Map encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue");
+
+ // 3. Encrypt the data with the provided CMM
+ final CryptoResult encryptResult = crypto.encryptData(cmm, EXAMPLE_DATA, encryptionContext);
+ final byte[] ciphertext = encryptResult.getResult();
+
+ // 4. Decrypt the data
+ final CryptoResult decryptResult = crypto.decryptData(cmm, ciphertext);
+
+ // 5. Verify that the encryption context in the result contains the
+ // encryption context supplied to the encryptData method. Because the
+ // SDK can add values to the encryption context, don't require that
+ // the entire context matches.
+ if (!encryptionContext.entrySet().stream()
+ .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) {
+ throw new IllegalStateException("Wrong Encryption Context!");
+ }
+
+ // 6. Verify that the decrypted plaintext matches the original plaintext
+ assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA);
+ }
+
+ // Custom CMM implementation.
+ // This CMM only allows encryption/decryption using signing algorithms.
+ // It wraps an underlying CMM implementation and checks its materials
+ // to ensure that it is only using signed encryption algorithms.
+ public static class SigningSuiteOnlyCMM implements CryptoMaterialsManager {
+
+ // The underlying CMM.
+ private CryptoMaterialsManager underlyingCMM;
+
+ // If only a MasterKeyProvider is constructed, the underlying CMM is the default CMM.
+ public SigningSuiteOnlyCMM(MasterKeyProvider> mkp) {
+ this.underlyingCMM = new DefaultCryptoMaterialsManager(mkp);
+ }
+
+ // This CMM can wrap any other CMM implementation.
+ public SigningSuiteOnlyCMM(CryptoMaterialsManager underlyingCMM) {
+ this.underlyingCMM = underlyingCMM;
+ }
+
+ @Override
+ public EncryptionMaterials getMaterialsForEncrypt(EncryptionMaterialsRequest request) {
+ EncryptionMaterials materials = underlyingCMM.getMaterialsForEncrypt(request);
+ if (materials.getAlgorithm().getTrailingSignatureAlgo() == null) {
+ throw new IllegalArgumentException("Algorithm provided to SigningSuiteOnlyCMM is not a supported signing algorithm: " + materials.getAlgorithm());
+ }
+ return materials;
+ }
+
+ @Override
+ public DecryptionMaterials decryptMaterials(DecryptionMaterialsRequest request) {
+ if (request.getAlgorithm().getTrailingSignatureAlgo() == null) {
+ throw new IllegalArgumentException("Algorithm provided to SigningSuiteOnlyCMM is not a supported signing algorithm: " + request.getAlgorithm());
+ }
+ return underlyingCMM.decryptMaterials(request);
+ }
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/encryptionsdk/CMMHandler.java b/src/main/java/com/amazonaws/encryptionsdk/CMMHandler.java
index 3b6e64b0e..3e3eff504 100644
--- a/src/main/java/com/amazonaws/encryptionsdk/CMMHandler.java
+++ b/src/main/java/com/amazonaws/encryptionsdk/CMMHandler.java
@@ -4,6 +4,7 @@
package com.amazonaws.encryptionsdk;
import com.amazonaws.encryptionsdk.internal.Utils;
+import com.amazonaws.encryptionsdk.model.DecryptionMaterials;
import com.amazonaws.encryptionsdk.model.DecryptionMaterialsHandler;
import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequest;
import com.amazonaws.encryptionsdk.model.EncryptionMaterialsHandler;
@@ -63,7 +64,37 @@ private GetEncryptionMaterialsInput getEncryptionMaterialsRequestInput(
public DecryptionMaterialsHandler decryptMaterials(
DecryptionMaterialsRequest request, CommitmentPolicy commitmentPolicy) {
if (cmm != null && mplCMM == null) {
- return new DecryptionMaterialsHandler(cmm.decryptMaterials(request));
+ // This is an implementation of the legacy native CryptoMaterialsManager interface from
+ // ESDK-Java.
+ DecryptionMaterials materials = cmm.decryptMaterials(request);
+ if (materials.getEncryptionContext().isEmpty() && !request.getEncryptionContext().isEmpty()) {
+ // If the request specified an encryption context,
+ // and we are using the legacy native CMM,
+ // add the encryptionContext to the materials.
+ //
+ // ESDK-Java 3.0 changed internals of decrypt behavior,
+ // This code makes earlier CMM implementations compatible with post-3.0 behavior.
+ //
+ // Version 3.0 assumes that CMMs' implementations of decryptMaterials
+ // will set an encryptionContext attribute on returned DecryptionMaterials.
+ // The DefaultCryptoMaterialsManager's behavior was changed in 3.0.
+ // It now sets the encryptionContext attribute with the value from the ciphertext's headers.
+ //
+ // But custom CMMs' behavior was not updated.
+ // However, there is no custom CMM before version 3.0 that could set an encryptionContext
+ // attribute.
+ // The encryptionContext attribute was only introduced to decryptMaterials objects
+ // in ESDK 3.0, so no CMM could have configured this attribute before 3.0.
+ // As a result, the ESDK assumes that any native CMM
+ // that does not add encryptionContext to its decryptMaterials
+ // SHOULD add encryptionContext to its decryptMaterials,
+ //
+ // If a custom CMM implementation conflicts with this assumption.
+ // that CMM implementation MUST move to the MPL.
+ materials =
+ materials.toBuilder().setEncryptionContext(request.getEncryptionContext()).build();
+ }
+ return new DecryptionMaterialsHandler(materials);
} else {
DecryptMaterialsInput input = getDecryptMaterialsInput(request, commitmentPolicy);
DecryptMaterialsOutput output = mplCMM.DecryptMaterials(input);
diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java
index 1394b0c98..253414e21 100644
--- a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java
+++ b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java
@@ -2,6 +2,7 @@
import com.amazonaws.encryptionsdk.DataKey;
import java.security.PublicKey;
+import java.util.Collections;
import java.util.Map;
public final class DecryptionMaterials {
@@ -12,7 +13,11 @@ public final class DecryptionMaterials {
private DecryptionMaterials(Builder b) {
dataKey = b.getDataKey();
trailingSignatureKey = b.getTrailingSignatureKey();
- encryptionContext = b.getEncryptionContext();
+ if (b.getEncryptionContext() != null) {
+ encryptionContext = b.getEncryptionContext();
+ } else {
+ encryptionContext = Collections.emptyMap();
+ }
}
public DataKey> getDataKey() {
diff --git a/src/test/java/com/amazonaws/crypto/examples/v2/CustomCMMExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/v2/CustomCMMExampleTest.java
new file mode 100644
index 000000000..55115dfb5
--- /dev/null
+++ b/src/test/java/com/amazonaws/crypto/examples/v2/CustomCMMExampleTest.java
@@ -0,0 +1,28 @@
+// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package com.amazonaws.crypto.examples.v2;
+
+import com.amazonaws.encryptionsdk.CryptoMaterialsManager;
+import com.amazonaws.encryptionsdk.kms.KMSTestFixtures;
+import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider;
+import org.junit.Test;
+
+public class CustomCMMExampleTest {
+
+ @Test
+ public void testCustomCMMExample() {
+ CryptoMaterialsManager cmm =
+ new CustomCMMExample.SigningSuiteOnlyCMM(
+ KmsMasterKeyProvider.builder().buildStrict(KMSTestFixtures.US_WEST_2_KEY_ID));
+ CustomCMMExample.encryptAndDecryptWithCMM(cmm);
+ }
+
+ @Test
+ public void testV2Cmm() {
+ V2DefaultCryptoMaterialsManager cmm =
+ new V2DefaultCryptoMaterialsManager(
+ KmsMasterKeyProvider.builder().buildStrict(KMSTestFixtures.US_WEST_2_KEY_ID));
+ CustomCMMExample.encryptAndDecryptWithCMM(cmm);
+ }
+}
diff --git a/src/test/java/com/amazonaws/crypto/examples/v2/V2DefaultCryptoMaterialsManager.java b/src/test/java/com/amazonaws/crypto/examples/v2/V2DefaultCryptoMaterialsManager.java
new file mode 100644
index 000000000..650a8ef5f
--- /dev/null
+++ b/src/test/java/com/amazonaws/crypto/examples/v2/V2DefaultCryptoMaterialsManager.java
@@ -0,0 +1,171 @@
+package com.amazonaws.crypto.examples.v2;
+
+import static com.amazonaws.encryptionsdk.internal.Utils.assertNonNull;
+
+import com.amazonaws.encryptionsdk.CommitmentPolicy;
+import com.amazonaws.encryptionsdk.CryptoAlgorithm;
+import com.amazonaws.encryptionsdk.CryptoMaterialsManager;
+import com.amazonaws.encryptionsdk.DataKey;
+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.internal.Constants;
+import com.amazonaws.encryptionsdk.internal.TrailingSignatureAlgorithm;
+import com.amazonaws.encryptionsdk.model.DecryptionMaterials;
+import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequest;
+import com.amazonaws.encryptionsdk.model.EncryptionMaterials;
+import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest;
+import com.amazonaws.encryptionsdk.model.KeyBlob;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/*
+ This is a copy-paste of the DefaultCryptoMaterialsManager implementation
+ from the final commit of the V2 ESDK: 1870a082358d59e32c60d74116d6f43c0efa466b
+ ESDK V3 implicitly changed the contract between CMMs and the ESDK.
+ After V3, DecryptMaterials has an `encryptionContext` attribute,
+ and CMMs are expected to set this attribute.
+ The V3 commit modified this DefaultCMM's `decryptMaterials` implementation
+ to set encryptionContext on returned DecryptionMaterials objects.
+ However, there are custom implementations of the legacy native CMM
+ that do not set encryptionContext.
+ This CMM is used to explicitly assert that the V2 implementation of
+ the DefaultCMM is compatible with V3 logic,
+ which implicitly asserts that custom implementations of V2-compatible CMMs
+ are also compatible with V3 logic.
+*/
+public class V2DefaultCryptoMaterialsManager implements CryptoMaterialsManager {
+ private final MasterKeyProvider> mkp;
+
+ private final CryptoAlgorithm DEFAULT_CRYPTO_ALGORITHM =
+ CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384;
+
+ /** @param mkp The master key provider to delegate to */
+ public V2DefaultCryptoMaterialsManager(MasterKeyProvider> mkp) {
+ assertNonNull(mkp, "mkp");
+ this.mkp = mkp;
+ }
+
+ @Override
+ public EncryptionMaterials getMaterialsForEncrypt(EncryptionMaterialsRequest request) {
+ Map context = request.getContext();
+
+ CryptoAlgorithm algo = request.getRequestedAlgorithm();
+ CommitmentPolicy commitmentPolicy = request.getCommitmentPolicy();
+ // Set default according to commitment policy
+ if (algo == null && commitmentPolicy == CommitmentPolicy.ForbidEncryptAllowDecrypt) {
+ algo = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384;
+ } else if (algo == null) {
+ algo = CryptoAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384;
+ }
+
+ KeyPair trailingKeys = null;
+ if (algo.getTrailingSignatureLength() > 0) {
+ try {
+ trailingKeys = generateTrailingSigKeyPair(algo);
+ if (context.containsKey(Constants.EC_PUBLIC_KEY_FIELD)) {
+ throw new IllegalArgumentException(
+ "EncryptionContext contains reserved field " + Constants.EC_PUBLIC_KEY_FIELD);
+ }
+ // make mutable
+ context = new HashMap<>(context);
+ context.put(Constants.EC_PUBLIC_KEY_FIELD, serializeTrailingKeyForEc(algo, trailingKeys));
+ } catch (final GeneralSecurityException ex) {
+ throw new AwsCryptoException(ex);
+ }
+ }
+
+ final MasterKeyRequest.Builder mkRequestBuilder = MasterKeyRequest.newBuilder();
+ mkRequestBuilder.setEncryptionContext(context);
+
+ mkRequestBuilder.setStreaming(request.getPlaintextSize() == -1);
+ if (request.getPlaintext() != null) {
+ mkRequestBuilder.setPlaintext(request.getPlaintext());
+ } else {
+ mkRequestBuilder.setSize(request.getPlaintextSize());
+ }
+
+ @SuppressWarnings("unchecked")
+ final List mks =
+ (List)
+ assertNonNull(mkp, "provider").getMasterKeysForEncryption(mkRequestBuilder.build());
+
+ if (mks.isEmpty()) {
+ throw new IllegalArgumentException("No master keys provided");
+ }
+
+ DataKey> dataKey = mks.get(0).generateDataKey(algo, context);
+
+ List keyBlobs = new ArrayList<>(mks.size());
+ keyBlobs.add(new KeyBlob(dataKey));
+
+ for (int i = 1; i < mks.size(); i++) {
+ //noinspection unchecked
+ keyBlobs.add(new KeyBlob(mks.get(i).encryptDataKey(algo, context, dataKey)));
+ }
+
+ //noinspection unchecked
+ return EncryptionMaterials.newBuilder()
+ .setAlgorithm(algo)
+ .setCleartextDataKey(dataKey.getKey())
+ .setEncryptedDataKeys(keyBlobs)
+ .setEncryptionContext(context)
+ .setTrailingSignatureKey(trailingKeys == null ? null : trailingKeys.getPrivate())
+ .setMasterKeys(mks)
+ .build();
+ }
+
+ @Override
+ public DecryptionMaterials decryptMaterials(DecryptionMaterialsRequest request) {
+ DataKey> dataKey =
+ mkp.decryptDataKey(
+ request.getAlgorithm(), request.getEncryptedDataKeys(), request.getEncryptionContext());
+
+ if (dataKey == null) {
+ throw new CannotUnwrapDataKeyException("Could not decrypt any data keys");
+ }
+
+ PublicKey pubKey = null;
+ if (request.getAlgorithm().getTrailingSignatureLength() > 0) {
+ try {
+ String serializedPubKey = request.getEncryptionContext().get(Constants.EC_PUBLIC_KEY_FIELD);
+
+ if (serializedPubKey == null) {
+ throw new AwsCryptoException("Missing trailing signature public key");
+ }
+
+ pubKey = deserializeTrailingKeyFromEc(request.getAlgorithm(), serializedPubKey);
+ } catch (final IllegalStateException ex) {
+ throw new AwsCryptoException(ex);
+ }
+ } else if (request.getEncryptionContext().containsKey(Constants.EC_PUBLIC_KEY_FIELD)) {
+ throw new AwsCryptoException("Trailing signature public key found for non-signed algorithm");
+ }
+
+ return DecryptionMaterials.newBuilder()
+ .setDataKey(dataKey)
+ .setTrailingSignatureKey(pubKey)
+ .build();
+ }
+
+ private PublicKey deserializeTrailingKeyFromEc(CryptoAlgorithm algo, String pubKey) {
+ return TrailingSignatureAlgorithm.forCryptoAlgorithm(algo).deserializePublicKey(pubKey);
+ }
+
+ private static String serializeTrailingKeyForEc(CryptoAlgorithm algo, KeyPair trailingKeys) {
+ return TrailingSignatureAlgorithm.forCryptoAlgorithm(algo)
+ .serializePublicKey(trailingKeys.getPublic());
+ }
+
+ private static KeyPair generateTrailingSigKeyPair(CryptoAlgorithm algo)
+ throws GeneralSecurityException {
+ return TrailingSignatureAlgorithm.forCryptoAlgorithm(algo).generateKey();
+ }
+}
diff --git a/src/test/java/com/amazonaws/encryptionsdk/AllTestsSuite.java b/src/test/java/com/amazonaws/encryptionsdk/AllTestsSuite.java
index 9367233cd..6f337f6cb 100644
--- a/src/test/java/com/amazonaws/encryptionsdk/AllTestsSuite.java
+++ b/src/test/java/com/amazonaws/encryptionsdk/AllTestsSuite.java
@@ -13,6 +13,7 @@
import com.amazonaws.crypto.examples.keyrings.SetEncryptionAlgorithmKeyringExampleTest;
import com.amazonaws.crypto.examples.v2.BasicEncryptionExampleTest;
import com.amazonaws.crypto.examples.v2.BasicMultiRegionKeyEncryptionExampleTest;
+import com.amazonaws.crypto.examples.v2.CustomCMMExampleTest;
import com.amazonaws.crypto.examples.v2.DiscoveryDecryptionExampleTest;
import com.amazonaws.crypto.examples.v2.DiscoveryMultiRegionDecryptionExampleTest;
import com.amazonaws.crypto.examples.v2.MultipleCmkEncryptExampleTest;
@@ -50,6 +51,7 @@
import com.amazonaws.encryptionsdk.model.CipherFrameHeadersTest;
import com.amazonaws.encryptionsdk.model.CiphertextHeadersTest;
import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequestTest;
+import com.amazonaws.encryptionsdk.model.DecryptionMaterialsTest;
import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequestTest;
import com.amazonaws.encryptionsdk.model.KeyBlobTest;
import com.amazonaws.encryptionsdk.multi.MultipleMasterKeyTest;
@@ -73,6 +75,7 @@
CipherBlockHeadersTest.class,
CipherFrameHeadersTest.class,
KeyBlobTest.class,
+ DecryptionMaterialsTest.class,
DecryptionMaterialsRequestTest.class,
MultipleMasterKeyTest.class,
AwsCryptoTest.class,
@@ -100,6 +103,7 @@
CommitmentKATRunner.class,
BasicEncryptionExampleTest.class,
BasicMultiRegionKeyEncryptionExampleTest.class,
+ CustomCMMExampleTest.class,
DiscoveryDecryptionExampleTest.class,
DiscoveryMultiRegionDecryptionExampleTest.class,
MultipleCmkEncryptExampleTest.class,
diff --git a/src/test/java/com/amazonaws/encryptionsdk/CMMHandlerTest.java b/src/test/java/com/amazonaws/encryptionsdk/CMMHandlerTest.java
new file mode 100644
index 000000000..84073879b
--- /dev/null
+++ b/src/test/java/com/amazonaws/encryptionsdk/CMMHandlerTest.java
@@ -0,0 +1,160 @@
+// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package com.amazonaws.encryptionsdk;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.amazonaws.encryptionsdk.model.DecryptionMaterials;
+import com.amazonaws.encryptionsdk.model.DecryptionMaterialsHandler;
+import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequest;
+import com.amazonaws.encryptionsdk.model.KeyBlob;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+
+public class CMMHandlerTest {
+
+ private static final CryptoAlgorithm SOME_CRYPTO_ALGORITHM =
+ CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384;
+ private static final List SOME_EDK_LIST =
+ new ArrayList<>(Collections.singletonList(new KeyBlob()));
+ private static final CommitmentPolicy SOME_COMMITMENT_POLICY =
+ CommitmentPolicy.RequireEncryptRequireDecrypt;
+ private static final Map SOME_NON_EMPTY_ENCRYPTION_CONTEXT = new HashMap<>();
+
+ static {
+ {
+ SOME_NON_EMPTY_ENCRYPTION_CONTEXT.put("SomeKey", "SomeValue");
+ }
+ }
+
+ private static final DecryptionMaterialsRequest SOME_DECRYPTION_MATERIALS_REQUEST_NON_EMPTY_EC =
+ DecryptionMaterialsRequest.newBuilder()
+ .setAlgorithm(SOME_CRYPTO_ALGORITHM)
+ // Given: Request has some non-empty encryption context
+ .setEncryptionContext(SOME_NON_EMPTY_ENCRYPTION_CONTEXT)
+ .setReproducedEncryptionContext(new HashMap<>())
+ .setEncryptedDataKeys(SOME_EDK_LIST)
+ .build();
+
+ private static final DecryptionMaterialsRequest SOME_DECRYPTION_MATERIALS_REQUEST_EMPTY_EC =
+ DecryptionMaterialsRequest.newBuilder()
+ .setAlgorithm(SOME_CRYPTO_ALGORITHM)
+ // Given: Request has empty encryption context
+ .setEncryptionContext(new HashMap<>())
+ .setReproducedEncryptionContext(new HashMap<>())
+ .setEncryptedDataKeys(SOME_EDK_LIST)
+ .build();
+
+ @Test
+ public void
+ GIVEN_CMM_does_not_add_encryption_context_AND_request_has_nonempty_encryption_context_WHEN_decryptMaterials_THEN_output_has_nonempty_encryption_context() {
+ CryptoMaterialsManager anyNativeCMM = mock(CryptoMaterialsManager.class);
+
+ // Given: native CMM does not set an encryptionContext on returned DecryptionMaterials objects
+ DecryptionMaterials someDecryptionMaterialsWithoutEC =
+ DecryptionMaterials.newBuilder()
+ .setDataKey(mock(DataKey.class))
+ .setTrailingSignatureKey(mock(PublicKey.class))
+ .setEncryptionContext(new HashMap<>())
+ .build();
+ // Given: request with nonempty encryption context
+ when(anyNativeCMM.decryptMaterials(SOME_DECRYPTION_MATERIALS_REQUEST_NON_EMPTY_EC))
+ .thenReturn(someDecryptionMaterialsWithoutEC);
+
+ // When: decryptMaterials
+ CMMHandler handlerUnderTest = new CMMHandler(anyNativeCMM);
+ DecryptionMaterialsHandler output =
+ handlerUnderTest.decryptMaterials(
+ SOME_DECRYPTION_MATERIALS_REQUEST_NON_EMPTY_EC, SOME_COMMITMENT_POLICY);
+
+ // Then: output DecryptionMaterialsHandler has encryption context
+ assertEquals(SOME_NON_EMPTY_ENCRYPTION_CONTEXT, output.getEncryptionContext());
+ }
+
+ @Test
+ public void
+ GIVEN_CMM_does_not_add_encryption_context_AND_request_has_empty_encryption_context_WHEN_decryptMaterials_THEN_output_has_empty_encryption_context() {
+ CryptoMaterialsManager anyNativeCMM = mock(CryptoMaterialsManager.class);
+
+ // Given: native CMM does not set an encryptionContext on returned DecryptionMaterials objects
+ DecryptionMaterials someDecryptionMaterialsWithoutEC =
+ DecryptionMaterials.newBuilder()
+ .setDataKey(mock(DataKey.class))
+ .setTrailingSignatureKey(mock(PublicKey.class))
+ .setEncryptionContext(new HashMap<>())
+ .build();
+ // Given: request with empty encryption context
+ when(anyNativeCMM.decryptMaterials(SOME_DECRYPTION_MATERIALS_REQUEST_EMPTY_EC))
+ .thenReturn(someDecryptionMaterialsWithoutEC);
+
+ // When: decryptMaterials
+ CMMHandler handlerUnderTest = new CMMHandler(anyNativeCMM);
+ DecryptionMaterialsHandler output =
+ handlerUnderTest.decryptMaterials(
+ SOME_DECRYPTION_MATERIALS_REQUEST_EMPTY_EC, SOME_COMMITMENT_POLICY);
+
+ // Then: output DecryptionMaterialsHandler has empty encryption context
+ assertTrue(output.getEncryptionContext().isEmpty());
+ }
+
+ @Test
+ public void
+ GIVEN_CMM_adds_encryption_context_AND_request_has_nonempty_encryption_context_WHEN_decryptMaterials_THEN_output_has_nonempty_encryption_context() {
+ CryptoMaterialsManager anyNativeCMM = mock(CryptoMaterialsManager.class);
+
+ // Given: native CMM sets encryptionContext on returned DecryptionMaterials objects
+ DecryptionMaterials someDecryptionMaterialsWithoutEC =
+ DecryptionMaterials.newBuilder()
+ .setDataKey(mock(DataKey.class))
+ .setTrailingSignatureKey(mock(PublicKey.class))
+ .setEncryptionContext(SOME_NON_EMPTY_ENCRYPTION_CONTEXT)
+ .build();
+ // Given: request with nonempty encryption context
+ when(anyNativeCMM.decryptMaterials(SOME_DECRYPTION_MATERIALS_REQUEST_NON_EMPTY_EC))
+ .thenReturn(someDecryptionMaterialsWithoutEC);
+
+ // When: decryptMaterials
+ CMMHandler handlerUnderTest = new CMMHandler(anyNativeCMM);
+ DecryptionMaterialsHandler output =
+ handlerUnderTest.decryptMaterials(
+ SOME_DECRYPTION_MATERIALS_REQUEST_NON_EMPTY_EC, SOME_COMMITMENT_POLICY);
+
+ // Then: output DecryptionMaterialsHandler has nonempty encryption context
+ assertEquals(SOME_NON_EMPTY_ENCRYPTION_CONTEXT, output.getEncryptionContext());
+ }
+
+ @Test
+ public void
+ GIVEN_CMM_adds_encryption_context_AND_request_has_empty_encryption_context_WHEN_decryptMaterials_THEN_output_has_empty_encryption_context() {
+ CryptoMaterialsManager anyNativeCMM = mock(CryptoMaterialsManager.class);
+
+ // Given: native CMM sets encryptionContext on returned DecryptionMaterials objects
+ DecryptionMaterials someDecryptionMaterialsWithoutEC =
+ DecryptionMaterials.newBuilder()
+ .setDataKey(mock(DataKey.class))
+ .setTrailingSignatureKey(mock(PublicKey.class))
+ .setEncryptionContext(new HashMap<>())
+ .build();
+ // Given: request with empty encryption context
+ when(anyNativeCMM.decryptMaterials(SOME_DECRYPTION_MATERIALS_REQUEST_EMPTY_EC))
+ .thenReturn(someDecryptionMaterialsWithoutEC);
+
+ // When: decryptMaterials
+ CMMHandler handlerUnderTest = new CMMHandler(anyNativeCMM);
+ DecryptionMaterialsHandler output =
+ handlerUnderTest.decryptMaterials(
+ SOME_DECRYPTION_MATERIALS_REQUEST_EMPTY_EC, SOME_COMMITMENT_POLICY);
+
+ // Then: output DecryptionMaterialsHandler has empty encryption context
+ assertTrue(output.getEncryptionContext().isEmpty());
+ }
+}
diff --git a/src/test/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsTest.java b/src/test/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsTest.java
new file mode 100644
index 000000000..af1972b95
--- /dev/null
+++ b/src/test/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsTest.java
@@ -0,0 +1,40 @@
+// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package com.amazonaws.encryptionsdk.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import java.util.Collections;
+import java.util.Map;
+import org.junit.Test;
+
+public class DecryptionMaterialsTest {
+
+ @Test
+ public void GIVEN_builder_with_unset_EC_WHEN_constructor_THEN_object_EC_is_empty_map() {
+ // Given: DecryptionMaterials.Builder with unset encryption context
+ DecryptionMaterials.Builder builder = DecryptionMaterials.newBuilder();
+
+ // When: constructor
+ DecryptionMaterials decryptionMaterials = builder.build();
+
+ // Then: constructor assigns an empty map to DecryptionMaterials objects
+ assertEquals(Collections.emptyMap(), decryptionMaterials.getEncryptionContext());
+ }
+
+ @Test
+ public void GIVEN_builder_with_EC_WHEN_constructor_THEN_object_EC_is_builder_EC() {
+ // Given: DecryptionMaterials.Builder with any encryption context map set
+ Map anyEncryptionContext = mock(Map.class);
+ DecryptionMaterials.Builder builder = DecryptionMaterials.newBuilder();
+ builder.setEncryptionContext(anyEncryptionContext);
+
+ // When: constructor
+ DecryptionMaterials decryptionMaterials = builder.build();
+
+ // Then: constructor assigns that encryption context map to DecryptionMaterials objects
+ assertEquals(anyEncryptionContext, decryptionMaterials.getEncryptionContext());
+ }
+}