Skip to content

Commit 98ba382

Browse files
committed
feat(Examples): Wrap H-Keyring as a Master Key
This allows the H-Keyring to be used with a Caching CMM, but only if the Algorithm suite does not support Digital Signatures, only if there is one Encrypted Data Key, and only if a static Branch Key ID is used. Though, it would not be hard to add multiple Encrypted Data Key support, the other caveats are harder to work around.
1 parent fd00cd0 commit 98ba382

File tree

3 files changed

+327
-0
lines changed

3 files changed

+327
-0
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
package com.amazonaws.crypto.examples.v2;
2+
3+
import com.amazonaws.encryptionsdk.CryptoAlgorithm;
4+
import com.amazonaws.encryptionsdk.DataKey;
5+
import com.amazonaws.encryptionsdk.EncryptedDataKey;
6+
import com.amazonaws.encryptionsdk.MasterKey;
7+
import com.amazonaws.encryptionsdk.exception.AwsCryptoException;
8+
import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException;
9+
import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException;
10+
import software.amazon.cryptography.materialproviders.IKeyring;
11+
import software.amazon.cryptography.materialproviders.MaterialProviders;
12+
import software.amazon.cryptography.materialproviders.model.*;
13+
14+
import javax.crypto.SecretKey;
15+
import javax.crypto.spec.SecretKeySpec;
16+
import java.nio.ByteBuffer;
17+
import java.util.*;
18+
19+
public class HKeyringMasterKey extends MasterKey<HKeyringMasterKey> {
20+
21+
final private IKeyring hKeyring;
22+
final private CreateAwsKmsHierarchicalKeyringInput hKeyringInput;
23+
final private MaterialProviders mpl;
24+
25+
public HKeyringMasterKey(
26+
CreateAwsKmsHierarchicalKeyringInput input
27+
) {
28+
if (input.branchKeyIdSupplier() != null) throw new UnsupportedProviderException("branchKeyIdSupplier must be null");
29+
if (input.branchKeyId() == null) throw new UnsupportedProviderException("branchKeyId cannot be null");
30+
hKeyringInput = input;
31+
mpl =
32+
MaterialProviders.builder()
33+
.MaterialProvidersConfig(MaterialProvidersConfig.builder().build())
34+
.build();
35+
hKeyring = mpl.CreateAwsKmsHierarchicalKeyring(hKeyringInput);
36+
}
37+
38+
@Override
39+
public String getProviderId() {
40+
return "aws-kms-hierarchy";
41+
}
42+
43+
@Override
44+
public String getKeyId() {
45+
return this.hKeyringInput.branchKeyId();
46+
}
47+
48+
/**
49+
* Generates a new {@link DataKey} which is protected by this {@link MasterKey} for use with
50+
* {@code algorithm} and associated with the provided {@code encryptionContext}.
51+
*
52+
* @param algorithm
53+
* @param encryptionContext
54+
*/
55+
@Override
56+
public DataKey<HKeyringMasterKey> generateDataKey(CryptoAlgorithm algorithm, Map<String, String> encryptionContext) {
57+
AlgorithmSuiteInfo algorithmSuiteInfo = ValidateAndConvertAlgo(algorithm);
58+
EncryptionMaterials encryptionMaterials = EncryptionMaterials.builder()
59+
.algorithmSuite(algorithmSuiteInfo)
60+
.encryptionContext(encryptionContext)
61+
.encryptedDataKeys(Collections.emptyList())
62+
.requiredEncryptionContextKeys(Collections.emptyList())
63+
.build();
64+
OnEncryptInput eInput = OnEncryptInput.builder()
65+
.materials(encryptionMaterials)
66+
.build();
67+
OnEncryptOutput onEncryptOutput = hKeyring.OnEncrypt(eInput);
68+
software.amazon.cryptography.materialproviders.model.EncryptedDataKey encryptedDataKey = onEncryptOutput.materials().encryptedDataKeys().get(0);
69+
return new DataKey<>(
70+
new SecretKeySpec(onEncryptOutput.materials().plaintextDataKey().array(), algorithm.getDataKeyAlgo()),
71+
encryptedDataKey.ciphertext().array(),
72+
encryptedDataKey.keyProviderInfo().array(),
73+
this);
74+
}
75+
76+
/**
77+
* Returns a new copy of the provided {@code dataKey} which is protected by this {@link MasterKey}
78+
* for use with {@code algorithm} and associated with the provided {@code encryptionContext}.
79+
*
80+
* @param algorithm
81+
* @param encryptionContext
82+
* @param dataKey
83+
*/
84+
@Override
85+
public DataKey<HKeyringMasterKey> encryptDataKey(
86+
CryptoAlgorithm algorithm,
87+
Map<String, String> encryptionContext,
88+
DataKey<?> dataKey
89+
) {
90+
AlgorithmSuiteInfo algorithmSuiteInfo = ValidateAndConvertAlgo(algorithm);
91+
final SecretKey key = dataKey.getKey();
92+
if (!key.getFormat().equals("RAW")) {
93+
throw new IllegalArgumentException(
94+
"Can only re-encrypt data keys which are in RAW format, not "
95+
+ dataKey.getKey().getFormat());
96+
}
97+
EncryptionMaterials encryptionMaterials = EncryptionMaterials.builder()
98+
.algorithmSuite(algorithmSuiteInfo)
99+
.encryptionContext(encryptionContext)
100+
.encryptedDataKeys(Collections.emptyList())
101+
.requiredEncryptionContextKeys(Collections.emptyList())
102+
.plaintextDataKey(ByteBuffer.wrap(dataKey.getKey().getEncoded()))
103+
.build();
104+
OnEncryptInput eInput = OnEncryptInput.builder()
105+
.materials(encryptionMaterials)
106+
.build();
107+
OnEncryptOutput onEncryptOutput = hKeyring.OnEncrypt(eInput);
108+
software.amazon.cryptography.materialproviders.model.EncryptedDataKey encryptedDataKey = onEncryptOutput.materials().encryptedDataKeys().get(0);
109+
return new DataKey<>(
110+
key,
111+
encryptedDataKey.ciphertext().array(),
112+
encryptedDataKey.keyProviderInfo().array(),
113+
this);
114+
}
115+
116+
/**
117+
* Iterates through {@code encryptedDataKeys} and returns the first one which can be successfully
118+
* decrypted.
119+
*
120+
* @param algorithm
121+
* @param encryptedDataKeys
122+
* @param encryptionContext
123+
* @return a DataKey if one can be decrypted, otherwise returns {@code null}
124+
* @throws UnsupportedProviderException if the {@code encryptedDataKey} is associated with an
125+
* unsupported provider
126+
* @throws CannotUnwrapDataKeyException if the {@code encryptedDataKey} cannot be decrypted
127+
*/
128+
@Override
129+
public DataKey<HKeyringMasterKey> decryptDataKey(
130+
CryptoAlgorithm algorithm,
131+
Collection<? extends EncryptedDataKey> encryptedDataKeys,
132+
Map<String, String> encryptionContext
133+
) throws UnsupportedProviderException, AwsCryptoException
134+
{
135+
AlgorithmSuiteInfo algorithmSuiteInfo = ValidateAndConvertAlgo(algorithm);
136+
if (encryptedDataKeys.size() != 1) {
137+
// TODO: If needed, we could refactor this to properly support multiple EDKs, it would not be hard.
138+
throw new UnsupportedProviderException("Alas, this Master Key Provider can work with one (1) Encrypted Data Key; got " + encryptedDataKeys.size());
139+
}
140+
List<EncryptedDataKey> nativeEDKS = EDKCollectionToNative(encryptedDataKeys);
141+
List<software.amazon.cryptography.materialproviders.model.EncryptedDataKey> mplEDKS = EDKCollectionToMPL(encryptedDataKeys);
142+
DecryptionMaterials decryptionMaterials = DecryptionMaterials.builder()
143+
.algorithmSuite(algorithmSuiteInfo)
144+
.encryptionContext(encryptionContext)
145+
.requiredEncryptionContextKeys(Collections.emptyList())
146+
.build();
147+
OnDecryptInput onDecryptInput = OnDecryptInput.builder()
148+
.encryptedDataKeys(mplEDKS)
149+
.materials(decryptionMaterials)
150+
.build();
151+
OnDecryptOutput onDecryptOutput = this.hKeyring.OnDecrypt(onDecryptInput);
152+
if (onDecryptOutput.materials().plaintextDataKey() != null) {
153+
if (onDecryptOutput.materials().plaintextDataKey().array().length != algorithm.getDataKeyLength())
154+
throw new AwsCryptoException("Decrypted Data Key is incorrect length!");
155+
return new DataKey<>(
156+
new SecretKeySpec(onDecryptOutput.materials().plaintextDataKey().array(), algorithm.getDataKeyAlgo()),
157+
nativeEDKS.get(0).getEncryptedDataKey(),
158+
nativeEDKS.get(0).getProviderInformation(),
159+
this
160+
);
161+
}
162+
return null;
163+
}
164+
165+
private List<software.amazon.cryptography.materialproviders.model.EncryptedDataKey> EDKCollectionToMPL(
166+
Collection<? extends EncryptedDataKey> encryptedDataKeys
167+
) {
168+
List<software.amazon.cryptography.materialproviders.model.EncryptedDataKey> mplEDKS =
169+
new ArrayList<>(encryptedDataKeys.size());
170+
171+
for (EncryptedDataKey keyBlob : encryptedDataKeys) {
172+
mplEDKS.add(
173+
software.amazon.cryptography.materialproviders.model.EncryptedDataKey.builder()
174+
.keyProviderId(keyBlob.getProviderId())
175+
.keyProviderInfo(
176+
ByteBuffer.wrap(
177+
keyBlob.getProviderInformation(), 0, keyBlob.getProviderInformation().length))
178+
.ciphertext(
179+
ByteBuffer.wrap(
180+
keyBlob.getEncryptedDataKey(), 0, keyBlob.getEncryptedDataKey().length))
181+
.build());
182+
}
183+
return mplEDKS;
184+
}
185+
186+
private List<EncryptedDataKey> EDKCollectionToNative(
187+
Collection<? extends EncryptedDataKey> encryptedDataKeys
188+
) {
189+
List<EncryptedDataKey> nativeEDKS = new ArrayList<>(encryptedDataKeys.size());
190+
nativeEDKS.addAll(encryptedDataKeys);
191+
return nativeEDKS;
192+
}
193+
194+
private AlgorithmSuiteInfo ValidateAndConvertAlgo(
195+
CryptoAlgorithm algorithm
196+
) {
197+
if (algorithm.getTrailingSignatureAlgo() != null) {
198+
throw new UnsupportedProviderException("The HKeyringMasterKey provider does not support trailing signature algorithms!");
199+
}
200+
return mpl.GetAlgorithmSuiteInfo(ByteBuffer.allocate(2).putShort((short) algorithm.getValue()));
201+
}
202+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package com.amazonaws.crypto.examples.v2;
5+
6+
import com.amazonaws.encryptionsdk.AwsCrypto;
7+
import com.amazonaws.encryptionsdk.CommitmentPolicy;
8+
import com.amazonaws.encryptionsdk.CryptoAlgorithm;
9+
import com.amazonaws.encryptionsdk.CryptoMaterialsManager;
10+
import com.amazonaws.encryptionsdk.CryptoResult;
11+
import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManager;
12+
import com.amazonaws.encryptionsdk.caching.CryptoMaterialsCache;
13+
import com.amazonaws.encryptionsdk.caching.LocalCryptoMaterialsCache;
14+
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
15+
import software.amazon.awssdk.services.kms.KmsClient;
16+
import software.amazon.cryptography.keystore.KeyStore;
17+
import software.amazon.cryptography.keystore.model.CreateKeyInput;
18+
import software.amazon.cryptography.keystore.model.KMSConfiguration;
19+
import software.amazon.cryptography.keystore.model.KeyStoreConfig;
20+
import software.amazon.cryptography.materialproviders.IKeyring;
21+
import software.amazon.cryptography.materialproviders.MaterialProviders;
22+
import software.amazon.cryptography.materialproviders.model.*;
23+
24+
import java.nio.charset.StandardCharsets;
25+
import java.util.Arrays;
26+
import java.util.HashMap;
27+
import java.util.Map;
28+
import java.util.concurrent.TimeUnit;
29+
30+
public class HKeyringMasterKeyWithCachingCMM {
31+
private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8);
32+
33+
public static void encryptAndDecryptWithKeyringViaCachingCMM(
34+
String keyStoreTableName, String logicalKeyStoreName, String kmsKeyId) {
35+
final AwsCrypto crypto = AwsCrypto.builder()
36+
.withCommitmentPolicy(CommitmentPolicy.RequireEncryptAllowDecrypt)
37+
.withEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY)
38+
.withMaxEncryptedDataKeys(1)
39+
.build();
40+
41+
// Configure your KeyStore resource.
42+
// This SHOULD be the same configuration that you used
43+
// to initially create and populate your KeyStore.
44+
final KeyStore keystore =
45+
KeyStore.builder()
46+
.KeyStoreConfig(
47+
KeyStoreConfig.builder()
48+
.ddbClient(DynamoDbClient.create())
49+
.ddbTableName(keyStoreTableName)
50+
.logicalKeyStoreName(logicalKeyStoreName)
51+
.kmsClient(KmsClient.create())
52+
.kmsConfiguration(KMSConfiguration.builder().kmsKeyArn(kmsKeyId).build())
53+
.build())
54+
.build();
55+
56+
// Call CreateKey to create two new active branch keys
57+
final String branchKeyIdA =
58+
keystore.CreateKey(CreateKeyInput.builder().build()).branchKeyIdentifier();
59+
60+
// 4. Create the Hierarchical Keyring.
61+
final MaterialProviders matProv =
62+
MaterialProviders.builder()
63+
.MaterialProvidersConfig(MaterialProvidersConfig.builder().build())
64+
.build();
65+
final CreateAwsKmsHierarchicalKeyringInput keyringInput =
66+
CreateAwsKmsHierarchicalKeyringInput.builder()
67+
.keyStore(keystore)
68+
.branchKeyId(branchKeyIdA)
69+
.ttlSeconds(600)
70+
.cache(
71+
CacheType.builder()
72+
.MultiThreaded(MultiThreadedCache.builder().entryCapacity(100).build())// OPTIONAL
73+
.build())
74+
.build();
75+
76+
Map<String, String> encryptionContextA = new HashMap<>();
77+
encryptionContextA.put("tenant", "TenantA");
78+
HKeyringMasterKey masterKey = new HKeyringMasterKey(keyringInput);
79+
final IKeyring hierarchicalKeyringA = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput);
80+
81+
// Create a cache
82+
CryptoMaterialsCache cache = new LocalCryptoMaterialsCache(100);
83+
84+
// Create a caching CMM
85+
CryptoMaterialsManager cachingCmm =
86+
CachingCryptoMaterialsManager.newBuilder().withMasterKeyProvider(masterKey)
87+
.withCache(cache)
88+
.withMaxAge(100, TimeUnit.SECONDS)
89+
.withMessageUseLimit(1000)
90+
.build();
91+
92+
// Encrypt the data for encryptionContextA & encryptionContextB
93+
final CryptoResult<byte[], ?> encryptResultA =
94+
crypto.encryptData(cachingCmm, EXAMPLE_DATA, encryptionContextA);
95+
96+
// OK. Can the Keyring decrypt it?
97+
CryptoResult<byte[], ?> decryptResultA =
98+
crypto.decryptData(hierarchicalKeyringA, encryptResultA.getResult());
99+
assert Arrays.equals(decryptResultA.getResult(), EXAMPLE_DATA);
100+
101+
// OK. Can the Caching CMM Decrypt it?
102+
decryptResultA = crypto.decryptData(cachingCmm, encryptResultA.getResult());
103+
assert Arrays.equals(decryptResultA.getResult(), EXAMPLE_DATA);
104+
}
105+
106+
public static void main(final String[] args) {
107+
if (args.length <= 0) {
108+
throw new IllegalArgumentException(
109+
"To run this example, include the keyStoreTableName, logicalKeyStoreName, and kmsKeyId in args");
110+
}
111+
final String keyStoreTableName = args[0];
112+
final String logicalKeyStoreName = args[1];
113+
final String kmsKeyId = args[2];
114+
encryptAndDecryptWithKeyringViaCachingCMM(keyStoreTableName, logicalKeyStoreName, kmsKeyId);
115+
}
116+
}

src/test/java/com/amazonaws/crypto/examples/keyrings/AwsKmsHierarchicalKeyringExampleTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package com.amazonaws.crypto.examples.keyrings;
55

66
import com.amazonaws.crypto.examples.keyrings.hierarchical.AwsKmsHierarchicalKeyringExample;
7+
import com.amazonaws.crypto.examples.v2.HKeyringMasterKeyWithCachingCMM;
78
import com.amazonaws.encryptionsdk.kms.KMSTestFixtures;
89
import org.junit.Test;
910

@@ -23,4 +24,12 @@ public void testEncryptAndDecryptWithKeyringThreadSafe() {
2324
KMSTestFixtures.TEST_LOGICAL_KEYSTORE_NAME,
2425
KMSTestFixtures.TEST_KEYSTORE_KMS_KEY_ID);
2526
}
27+
28+
@Test
29+
public void testCrazy() {
30+
HKeyringMasterKeyWithCachingCMM.encryptAndDecryptWithKeyringViaCachingCMM(
31+
KMSTestFixtures.TEST_KEYSTORE_NAME,
32+
KMSTestFixtures.TEST_LOGICAL_KEYSTORE_NAME,
33+
KMSTestFixtures.TEST_KEYSTORE_KMS_KEY_ID);
34+
}
2635
}

0 commit comments

Comments
 (0)