-
Notifications
You must be signed in to change notification settings - Fork 122
fix: Update DecryptionMaterials code to support legacy custom CMMs #2037
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
aad31c5
fix: DecryptionMaterials defaults to empty encryption context
lucasmcdonald3 ae1902d
reset submodule
lucasmcdonald3 35fe060
unit tests
lucasmcdonald3 bd2b2e2
cleanup
lucasmcdonald3 16aa79b
cleanup
lucasmcdonald3 23db7f9
changes
lucasmcdonald3 5f491f5
wip
lucasmcdonald3 68e954d
undo
lucasmcdonald3 bdcd293
tests, cleanup
lucasmcdonald3 1082dc0
undo
lucasmcdonald3 43f92fb
cleanup
lucasmcdonald3 24ee4f4
cleanup
lucasmcdonald3 abfc8d9
checkstyle?
lucasmcdonald3 98fbe39
?
lucasmcdonald3 6c96166
Update src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMate…
lucasmcdonald3 51db28e
?
lucasmcdonald3 5008c0d
fix
lucasmcdonald3 dbbccfb
??
lucasmcdonald3 ff5bf3b
fix
lucasmcdonald3 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
124 changes: 124 additions & 0 deletions
124
src/examples/java/com/amazonaws/crypto/examples/v2/CustomCMMExample.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
|
||
/** | ||
* <p> | ||
* Creates a custom implementation of the CryptoMaterialsManager interface, | ||
* then uses that implementation to encrypt and decrypt a file using an AWS KMS CMK. | ||
* | ||
* <p> | ||
* Arguments: | ||
* <ol> | ||
* <li>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 | ||
* </ol> | ||
*/ | ||
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<String, String> encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); | ||
|
||
// 3. Encrypt the data with the provided CMM | ||
final CryptoResult<byte[], ?> encryptResult = crypto.encryptData(cmm, EXAMPLE_DATA, encryptionContext); | ||
final byte[] ciphertext = encryptResult.getResult(); | ||
|
||
// 4. Decrypt the data | ||
final CryptoResult<byte[], ?> 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); | ||
} | ||
} | ||
|
||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
src/test/java/com/amazonaws/crypto/examples/v2/CustomCMMExampleTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
171 changes: 171 additions & 0 deletions
171
src/test/java/com/amazonaws/crypto/examples/v2/V2DefaultCryptoMaterialsManager.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<String, String> 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<MasterKey> mks = | ||
(List<MasterKey>) | ||
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<KeyBlob> 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(); | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.