Skip to content

Commit d8ca214

Browse files
authored
chore(examples; MPL bump): Shared cache across Hierarchical Keyrings; Bump MPL to 1.7.0 (#680)
1 parent 68e75fb commit d8ca214

File tree

2 files changed

+253
-1
lines changed

2 files changed

+253
-1
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
using Amazon.DynamoDBv2;
2+
using Amazon.KeyManagementService;
3+
using AWS.Cryptography.EncryptionSDK;
4+
using AWS.Cryptography.KeyStore;
5+
using AWS.Cryptography.MaterialProviders;
6+
using Xunit;
7+
8+
/// <summary>
9+
/// This example demonstrates how to use a shared cache across multiple Hierarchical Keyrings.
10+
/// With this functionality, users only need to maintain one common shared cache across multiple
11+
/// Hierarchical Keyrings with different Key Stores instances/KMS Clients/KMS Keys.
12+
///
13+
/// There are three important parameters that users need to carefully set while providing the shared cache:
14+
///
15+
/// 1. Partition ID - Partition ID is an optional parameter provided to the Hierarchical Keyring input,
16+
/// which distinguishes Cryptographic Material Providers (i.e: Keyrings) writing to a cache.
17+
/// - If the Partition ID is set and is the same for two Hierarchical Keyrings (or another Material Provider),
18+
/// they CAN share the same cache entries in the cache.
19+
/// - If the Partition ID is set and is different for two Hierarchical Keyrings (or another Material Provider),
20+
/// they CANNOT share the same cache entries in the cache.
21+
/// - If the Partition ID is not set by the user, it is initialized as a random 16-byte UUID which makes
22+
/// it unique for every Hierarchical Keyring, and two Hierarchical Keyrings (or another Material Provider)
23+
/// CANNOT share the same cache entries in the cache.
24+
///
25+
/// 2. Logical Key Store Name - This parameter is set by the user when configuring the Key Store for
26+
/// the Hierarchical Keyring. This is a logical name for the branch key store.
27+
/// Suppose you have a physical Key Store (K). You create two instances of K (K1 and K2). Now, you create
28+
/// two Hierarchical Keyrings (HK1 and HK2) with these Key Store instances (K1 and K2 respectively).
29+
/// - If you want to share cache entries across these two keyrings, you should set the Logical Key Store Names
30+
/// for both the Key Store instances (K1 and K2) to be the same.
31+
/// - If you set the Logical Key Store Names for K1 and K2 to be different, HK1 (which uses Key Store instance K1)
32+
/// and HK2 (which uses Key Store instance K2) will NOT be able to share cache entries.
33+
///
34+
/// 3. Branch Key ID - Choose an effective Branch Key ID Schema
35+
///
36+
/// This is demonstrated in the example below.
37+
/// Notice that both K1 and K2 are instances of the same physical Key Store (K).
38+
/// You MUST NEVER have two different physical Key Stores with the same Logical Key Store Name.
39+
///
40+
/// Important Note: If you have two or more Hierarchy Keyrings with:
41+
/// - Same Partition ID
42+
/// - Same Logical Key Store Name of the Key Store for the Hierarchical Keyring
43+
/// - Same Branch Key ID
44+
/// then they WILL share the cache entries in the Shared Cache.
45+
/// Please make sure that you set all of Partition ID, Logical Key Store Name and Branch Key ID
46+
/// to be the same for two Hierarchical Keyrings if and only if you want them to share cache entries.
47+
///
48+
/// This example first creates a shared cache that you can use across multiple Hierarchical Keyrings.
49+
/// The example then configures a Hierarchical Keyring (HK1 and HK2) with the shared cache,
50+
/// a Branch Key ID and two instances (K1 and K2) of the same physical Key Store (K) respectively,
51+
/// i.e. HK1 with K1 and HK2 with K2. The example demonstrates that if you set the same Partition ID
52+
/// for HK1 and HK2, the two keyrings can share cache entries.
53+
/// If you set different Partition ID of the Hierarchical Keyrings, or different
54+
/// Logical Key Store Names of the Key Store instances, then the keyrings will NOT
55+
/// be able to share cache entries.
56+
///
57+
/// This example requires access to the DDB Table (K) where you are storing the Branch Keys. This
58+
/// table must be configured with the following primary key configuration: - Partition key is named
59+
/// "partition_key" with type (S) - Sort key is named "sort_key" with type (S)
60+
///
61+
/// This example also requires using a KMS Key. You need the following access on this key:
62+
/// - GenerateDataKeyWithoutPlaintext
63+
/// - Decrypt
64+
/// </summary>
65+
public class SharedCacheAcrossHierarchicalKeyrings
66+
{
67+
// THESE ARE PUBLIC RESOURCES DO NOT USE IN A PRODUCTION ENVIRONMENT
68+
private static string branchKeyId = "43574aa0-de30-424e-bad4-0b06f6e89478";
69+
private static void Run(MemoryStream plaintext)
70+
{
71+
// Create the CryptographicMaterialsCache (CMC) to share across multiple Hierarchical Keyrings
72+
// using the Material Providers Library
73+
// This CMC takes in:
74+
// - CacheType
75+
var materialProviders = new MaterialProviders(new MaterialProvidersConfig());
76+
77+
var cache = new CacheType { Default = new DefaultCache{EntryCapacity = 100} };
78+
79+
var cryptographicMaterialsCacheInput = new CreateCryptographicMaterialsCacheInput {Cache = cache};
80+
81+
var sharedCryptographicMaterialsCache = materialProviders.CreateCryptographicMaterialsCache(cryptographicMaterialsCacheInput);
82+
83+
// Create a CacheType object for the sharedCryptographicMaterialsCache
84+
// Note that the `cache` parameter in the Hierarchical Keyring Input takes a `CacheType` as input
85+
// Here, we pass a `Shared` CacheType that passes an already initialized shared cache
86+
var sharedCache = new CacheType { Shared = sharedCryptographicMaterialsCache };
87+
88+
// Instantiate the SDK
89+
// This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy,
90+
// which enforces that this client only encrypts using committing algorithm suites and enforces
91+
// that this client will only decrypt encrypted messages that were created with a committing
92+
// algorithm suite.
93+
// This is the default commitment policy if you build the client with
94+
// `AwsCrypto.builder().build()`
95+
// or `AwsCrypto.standard()`.
96+
var encryptionSDK = new ESDK(new AwsEncryptionSdkConfig());
97+
98+
// Configure your KeyStore resource keystore1.
99+
// This SHOULD be the same configuration that you used
100+
// to initially create and populate your physical KeyStore.
101+
// Note that ddbTableName keyStoreTableName is the physical Key Store,
102+
// and keystore1 is instances of this physical Key Store.
103+
104+
// Create an AWS KMS Configuration to use with your KeyStore.
105+
// The KMS Configuration MUST have the right access to the resources in the KeyStore.
106+
var kmsConfig = new KMSConfiguration { KmsKeyArn = ExampleUtils.ExampleUtils.GetBranchKeyArn() };
107+
108+
var keystoreConfig = new KeyStoreConfig
109+
{
110+
// Client MUST have permissions to decrypt kmsConfig.KmsKeyArn
111+
KmsClient = new AmazonKeyManagementServiceClient(),
112+
KmsConfiguration = kmsConfig,
113+
DdbTableName = ExampleUtils.ExampleUtils.GetKeyStoreName(),
114+
DdbClient = new AmazonDynamoDBClient(),
115+
LogicalKeyStoreName = ExampleUtils.ExampleUtils.GetLogicalKeyStoreName()
116+
};
117+
118+
119+
var keystore1 = new KeyStore(keystoreConfig);
120+
121+
// Create the Hierarchical Keyring HK1 with Key Store instance K1, partitionId,
122+
// the shared Cache and the BranchKeyId.
123+
// Note that we are now providing an already initialized shared cache instead of just mentioning
124+
// the cache type and the Hierarchical Keyring initializing a cache at initialization.
125+
126+
// partitionId for this example is a random UUID
127+
var partitionId = "91c1b6a2-6fc3-4539-ad5e-938d597ed730";
128+
129+
// Please make sure that you read the guidance on how to set Partition ID, Logical Key Store Name and
130+
// Branch Key ID at the top of this example before creating Hierarchical Keyrings with a Shared Cache
131+
132+
var createKeyringInput1 = new CreateAwsKmsHierarchicalKeyringInput
133+
{
134+
KeyStore = keystore1,
135+
// This branchKeyId you have configured your keyring with MUST be decrypted by the
136+
// KMS config in the keystore and therefore MUST have the right permissions.
137+
BranchKeyId = branchKeyId,
138+
// The value provided to `EntryCapacity` dictates how many branch keys will be held locally
139+
Cache = sharedCache,
140+
// This dictates how often we call back to KMS to authorize use of the branch keys
141+
TtlSeconds = 600,
142+
PartitionId = partitionId
143+
};
144+
var keyring1 = materialProviders.CreateAwsKmsHierarchicalKeyring(createKeyringInput1);
145+
146+
// Create example encryption context
147+
var encryptionContext = new Dictionary<string, string>()
148+
{
149+
{"encryption", "context"},
150+
{"is not", "secret"},
151+
{"but adds", "useful metadata"},
152+
{"that can help you", "be confident that"},
153+
{"the data you are handling", "is what you think it is"}
154+
};
155+
156+
// Encrypt the data for encryptionContext using keyring1
157+
var encryptInput1 = new EncryptInput
158+
{
159+
Plaintext = plaintext,
160+
Keyring = keyring1,
161+
EncryptionContext = encryptionContext
162+
};
163+
164+
var encryptOutput1 = encryptionSDK.Encrypt(encryptInput1);
165+
166+
167+
// Decrypt your encrypted data using the same keyring HK1 you used on encrypt.
168+
var decryptOutput1 = encryptionSDK.Decrypt(new DecryptInput {
169+
Ciphertext = encryptOutput1.Ciphertext,
170+
Keyring = keyring1 }
171+
);
172+
173+
// Demonstrate that the decrypted plaintext is identical to the original plaintext.
174+
var decrypted1 = decryptOutput1.Plaintext;
175+
Assert.Equal(decrypted1.ToArray(), plaintext.ToArray());
176+
177+
// Through the above encrypt and decrypt roundtrip, the cache will be populated and
178+
// the cache entries can be used by another Hierarchical Keyring with the
179+
// - Same Partition ID
180+
// - Same Logical Key Store Name of the Key Store for the Hierarchical Keyring
181+
// - Same Branch Key ID
182+
183+
// Configure your KeyStore resource keystore2.
184+
// This SHOULD be the same configuration that you used
185+
// to initially create and populate your physical KeyStore.
186+
// Note that ddbTableName keyStoreTableName is the physical Key Store,
187+
// and keystore2 is instances of this physical Key Store.
188+
189+
// Note that for this example, keystore2 is identical to keystore1.
190+
// You can optionally change configurations like KMS Client or KMS Key ID based
191+
// on your use-case.
192+
// Make sure you have the required permissions to use different configurations.
193+
194+
// - If you want to share cache entries across two keyrings HK1 and HK2,
195+
// you should set the Logical Key Store Names for both
196+
// Key Store instances (K1 and K2) to be the same.
197+
// - If you set the Logical Key Store Names for K1 and K2 to be different,
198+
// HK1 (which uses Key Store instance K1) and HK2 (which uses Key Store
199+
// instance K2) will NOT be able to share cache entries.
200+
var keystore2 = new KeyStore(keystoreConfig);
201+
202+
// Create the Hierarchical Keyring HK2 with Key Store instance K2, the shared Cache
203+
// and the same partitionId and BranchKeyId used in HK1 because we want to share cache entries
204+
// (and experience cache HITS).
205+
206+
// Please make sure that you read the guidance on how to set Partition ID, Logical Key Store Name and
207+
// Branch Key ID at the top of this example before creating Hierarchical Keyrings with a Shared Cache
208+
209+
var createKeyringInput2 = new CreateAwsKmsHierarchicalKeyringInput
210+
{
211+
KeyStore = keystore2,
212+
// This branchKeyId you have configured your keyring with MUST be decrypted by the
213+
// KMS config in the keystore and therefore MUST have the right permissions.
214+
BranchKeyId = branchKeyId,
215+
// The value provided to `EntryCapacity` dictates how many branch keys will be held locally
216+
Cache = sharedCache,
217+
// This dictates how often we call back to KMS to authorize use of the branch keys
218+
TtlSeconds = 600,
219+
PartitionId = partitionId
220+
};
221+
var keyring2 = materialProviders.CreateAwsKmsHierarchicalKeyring(createKeyringInput2);
222+
223+
// This encrypt-decrypt roundtrip with HK2 will experience Cache HITS from previous HK1 roundtrip
224+
// Encrypt the data for encryptionContext using hierarchicalKeyring2
225+
var encryptInput2 = new EncryptInput
226+
{
227+
Plaintext = plaintext,
228+
Keyring = keyring2,
229+
EncryptionContext = encryptionContext
230+
};
231+
232+
var encryptOutput2 = encryptionSDK.Encrypt(encryptInput2);
233+
234+
// Decrypt your encrypted data using the same keyring HK2 you used on encrypt.
235+
var decryptOutput2 = encryptionSDK.Decrypt(new DecryptInput {
236+
Ciphertext = encryptOutput2.Ciphertext,
237+
Keyring = keyring2 }
238+
);
239+
240+
// Demonstrate that the decrypted plaintext is identical to the original plaintext.
241+
var decrypted2 = decryptOutput2.Plaintext;
242+
Assert.Equal(decrypted2.ToArray(), plaintext.ToArray());
243+
}
244+
245+
// We test examples to ensure they remain up-to-date.
246+
[Fact]
247+
public void TestAwsKmsHierarchicalKeyringExample()
248+
{
249+
Run(ExampleUtils.ExampleUtils.GetPlaintextStream());
250+
}
251+
252+
}

mpl

Submodule mpl updated from 66a04bf to ea0fe50

0 commit comments

Comments
 (0)