Skip to content

Commit ea188a7

Browse files
committed
add net example
1 parent a8969e2 commit ea188a7

File tree

4 files changed

+344
-3
lines changed

4 files changed

+344
-3
lines changed

Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(
189189
);
190190

191191
// 5. Get the DDB Client for Hierarchical Keyring 1.
192-
final DynamoDbClient ddbClient1 = GetDDBClient(
192+
final DynamoDbClient ddbClient1 = GetDdbClient(
193193
ddbTableName,
194194
hierarchicalKeyring1,
195195
attributeActionsOnEncrypt
@@ -254,7 +254,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(
254254
matProv.CreateAwsKmsHierarchicalKeyring(keyringInput2);
255255

256256
// 9. Get the DDB Client for Hierarchical Keyring 2.
257-
final DynamoDbClient ddbClient2 = GetDDBClient(
257+
final DynamoDbClient ddbClient2 = GetDdbClient(
258258
ddbTableName,
259259
hierarchicalKeyring2,
260260
attributeActionsOnEncrypt
@@ -264,7 +264,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(
264264
PutGetItems(ddbTableName, ddbClient2);
265265
}
266266

267-
public static DynamoDbClient GetDDBClient(
267+
public static DynamoDbClient GetDdbClient(
268268
String ddbTableName,
269269
IKeyring hierarchicalKeyring,
270270
Map<String, CryptoAction> attributeActionsOnEncrypt

Examples/runtimes/net/src/Examples.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ static async Task Main()
3030
Thread.Sleep(5000);
3131

3232
await HierarchicalKeyringExample.HierarchicalKeyringGetItemPutItem(keyId, keyId2);
33+
await SharedCacheAcrossHierarchicalKeyringsExample.SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(keyId);
3334

3435
await BasicSearchableEncryptionExample.PutItemQueryItemWithBeacon(keyId);
3536
await CompoundBeaconSearchableEncryptionExample.PutItemQueryItemWithCompoundBeacon(keyId);

Examples/runtimes/net/src/TestUtils.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ public class TestUtils
77
public static readonly string TEST_KEYSTORE_NAME = "KeyStoreDdbTable";
88
public static readonly string TEST_LOGICAL_KEYSTORE_NAME = "KeyStoreDdbTable";
99

10+
public static readonly string TEST_PARTITION_ID = "91c1b6a2-6fc3-4539-ad5e-938d597ed730";
11+
1012
public static readonly string TEST_KEYSTORE_KMS_KEY_ID =
1113
"arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126";
1214

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Net;
5+
using System.Threading.Tasks;
6+
using Amazon.DynamoDBv2;
7+
using Amazon.DynamoDBv2.Model;
8+
using Amazon.KeyManagementService;
9+
using AWS.Cryptography.DbEncryptionSDK.DynamoDb;
10+
using AWS.Cryptography.DbEncryptionSDK.StructuredEncryption;
11+
using AWS.Cryptography.KeyStore;
12+
using AWS.Cryptography.MaterialProviders;
13+
14+
/*
15+
This example demonstrates how to use a shared cache across multiple Hierarchical Keyrings.
16+
With this functionality, users only need to maintain one common shared cache across multiple
17+
Hierarchical Keyrings with different Key Stores instances/KMS Clients/KMS Keys.
18+
19+
There are three important parameters that users need to carefully set while providing the shared cache:
20+
21+
1. Partition ID - Partition ID is an optional parameter provided to the Hierarchical Keyring input,
22+
which distinguishes Cryptographic Material Providers (i.e: Keyrings) writing to a cache.
23+
- If the Partition ID is set and is the same for two Hierarchical Keyrings (or another Material Provider),
24+
they CAN share the same cache entries in the cache.
25+
- If the Partition ID is set and is different for two Hierarchical Keyrings (or another Material Provider),
26+
they CANNOT share the same cache entries in the cache.
27+
- If the Partition ID is not set by the user, it is initialized as a random 16-byte UUID which makes
28+
it unique for every Hierarchical Keyring, and two Hierarchical Keyrings (or another Material Provider)
29+
CANNOT share the same cache entries in the cache.
30+
31+
2. Logical Key Store Name - This parameter is set by the user when configuring the Key Store for
32+
the Hierarchical Keyring. This is a logical name for the branch key store.
33+
Suppose you have a physical Key Store (K). You create two instances of K (K1 and K2). Now, you create
34+
two Hierarchical Keyrings (HK1 and HK2) with these Key Store instances (K1 and K2 respectively).
35+
- If you want to share cache entries across these two keyrings, you should set the Logical Key Store Names
36+
for both the Key Store instances (K1 and K2) to be the same.
37+
- If you set the Logical Key Store Names for K1 and K2 to be different, HK1 (which uses Key Store instance K1)
38+
and HK2 (which uses Key Store instance K2) will NOT be able to share cache entries.
39+
40+
3. Branch Key ID - Choose an effective Branch Key ID Schema
41+
42+
This is demonstrated in the example below.
43+
Notice that both K1 and K2 are instances of the same physical Key Store (K).
44+
You MUST NEVER have two different physical Key Stores with the same Logical Key Store Name.
45+
46+
Important Note: If you have two or more Hierarchy Keyrings with:
47+
- Same Partition ID
48+
- Same Logical Key Store Name of the Key Store for the Hierarchical Keyring
49+
- Same Branch Key ID
50+
then they WILL share the cache entries in the Shared Cache.
51+
Please make sure that you set all of Partition ID, Logical Key Store Name and Branch Key ID
52+
to be the same for two Hierarchical Keyrings if and only if you want them to share cache entries.
53+
54+
This example sets up DynamoDb Encryption for the AWS SDK client using the Hierarchical
55+
Keyring, which establishes a key hierarchy where "branch" keys are persisted in DynamoDb.
56+
These branch keys are used to protect your data keys, and these branch keys are themselves
57+
protected by a root KMS Key.
58+
59+
This example first creates a shared cache that you can use across multiple Hierarchical Keyrings.
60+
The example then configures a Hierarchical Keyring (HK1 and HK2) with the shared cache,
61+
a Branch Key ID and two instances (K1 and K2) of the same physical Key Store (K) respectively,
62+
i.e. HK1 with K1 and HK2 with K2. The example demonstrates that if you set the same Partition ID
63+
for HK1 and HK2, the two keyrings can share cache entries.
64+
If you set different Partition ID of the Hierarchical Keyrings, or different
65+
Logical Key Store Names of the Key Store instances, then the keyrings will NOT
66+
be able to share cache entries.
67+
68+
Running this example requires access to the DDB Table whose name
69+
is provided in CLI arguments.
70+
This table must be configured with the following
71+
primary key configuration:
72+
- Partition key is named "partition_key" with type (S)
73+
- Sort key is named "sort_key" with type (S)
74+
75+
This example also requires using a KMS Key whose ARN
76+
is provided in CLI arguments. You need the following access
77+
on this key:
78+
- GenerateDataKeyWithoutPlaintext
79+
- Decrypt
80+
*/
81+
public class SharedCacheAcrossHierarchicalKeyringsExample
82+
{
83+
public static async Task SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(String branchKeyId)
84+
{
85+
var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME;
86+
var keyStoreTableName = TestUtils.TEST_KEYSTORE_NAME;
87+
var logicalKeyStoreName = TestUtils.TEST_LOGICAL_KEYSTORE_NAME;
88+
var partitionId = TestUtils.TEST_PARTITION_ID;
89+
var kmsKeyId = TestUtils.TEST_KEYSTORE_KMS_KEY_ID;
90+
91+
// 1. Create the CryptographicMaterialsCache (CMC) to share across multiple Hierarchical Keyrings
92+
// using the Material Providers Library
93+
// This CMC takes in:
94+
// - CacheType
95+
var materialProviders = new MaterialProviders(new MaterialProvidersConfig());
96+
97+
var cache = new CacheType { Default = new DefaultCache{EntryCapacity = 100} };
98+
99+
var cryptographicMaterialsCacheInput = new CreateCryptographicMaterialsCacheInput {Cache = cache};
100+
101+
var sharedCryptographicMaterialsCache = materialProviders.CreateCryptographicMaterialsCache(cryptographicMaterialsCacheInput);
102+
103+
// 2. Create a CacheType object for the sharedCryptographicMaterialsCache
104+
// Note that the `cache` parameter in the Hierarchical Keyring Input takes a `CacheType` as input
105+
// Here, we pass a `Shared` CacheType that passes an already initialized shared cache
106+
var sharedCache = new CacheType { Shared = sharedCryptographicMaterialsCache };
107+
108+
// Initial KeyStore Setup: This example requires that you have already
109+
// created your KeyStore, and have populated it with a new branch key.
110+
111+
// 3. Configure your KeyStore resource keystore1.
112+
// This SHOULD be the same configuration that you used
113+
// to initially create and populate your KeyStore.
114+
// Note that keyStoreTableName is the physical Key Store,
115+
// and keystore1 is instances of this physical Key Store.
116+
var keystore1 = new KeyStore(new KeyStoreConfig
117+
{
118+
DdbClient = new AmazonDynamoDBClient(),
119+
DdbTableName = keyStoreTableName,
120+
LogicalKeyStoreName = logicalKeyStoreName,
121+
KmsClient = new AmazonKeyManagementServiceClient(),
122+
KmsConfiguration = new KMSConfiguration { KmsKeyArn = kmsKeyId }
123+
});
124+
125+
// 4. Create the Hierarchical Keyring HK1 with Key Store instance K1, partitionId,
126+
// the shared Cache and the BranchKeyId.
127+
// Note that we are now providing an already initialized shared cache instead of just mentioning
128+
// the cache type and the Hierarchical Keyring initializing a cache at initialization.
129+
130+
// This example creates a Hierarchical Keyring for a single BranchKeyId. You can, however, use a
131+
// BranchKeyIdSupplier as per your use-case. See the HierarchicalKeyringsExample.java for more
132+
// information.
133+
134+
// Please make sure that you read the guidance on how to set Partition ID, Logical Key Store Name and
135+
// Branch Key ID at the top of this example before creating Hierarchical Keyrings with a Shared Cache.
136+
// partitionId for this example is a random UUID
137+
var keyringInput1 = new CreateAwsKmsHierarchicalKeyringInput
138+
{
139+
KeyStore = keystore1,
140+
branchKeyId = branchKeyId,
141+
TtlSeconds = 600, // This dictates how often we call back to KMS to authorize use of the branch keys
142+
Cache = sharedCache,
143+
PartitionId = partitionId
144+
};
145+
var hierarchicalKeyring1 = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput1);
146+
147+
// 4. Configure which attributes are encrypted and/or signed when writing new items.
148+
// For each attribute that may exist on the items we plan to write to our DynamoDbTable,
149+
// we must explicitly configure how they should be treated during item encryption:
150+
// - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
151+
// - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
152+
// - DO_NOTHING: The attribute is not encrypted and not included in the signature
153+
var attributeActionsOnEncrypt = new Dictionary<String, CryptoAction>
154+
{
155+
["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY
156+
["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY
157+
["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN
158+
};
159+
160+
// 5. Get the DDB Client for Hierarchical Keyring 1.
161+
var ddbClient1 = GetDdbClient(
162+
ddbTableName,
163+
hierarchicalKeyring1,
164+
attributeActionsOnEncrypt
165+
);
166+
167+
// 6. Encrypt Decrypt roundtrip with ddbClient1
168+
PutGetItems(ddbTableName, ddbClient1);
169+
170+
// Through the above encrypt and decrypt roundtrip, the cache will be populated and
171+
// the cache entries can be used by another Hierarchical Keyring with the
172+
// - Same Partition ID
173+
// - Same Logical Key Store Name of the Key Store for the Hierarchical Keyring
174+
// - Same Branch Key ID
175+
176+
// 7. Configure your KeyStore resource keystore2.
177+
// This SHOULD be the same configuration that you used
178+
// to initially create and populate your physical KeyStore.
179+
// Note that keyStoreTableName is the physical Key Store,
180+
// and keystore2 is instances of this physical Key Store.
181+
182+
// Note that for this example, keystore2 is identical to keystore1.
183+
// You can optionally change configurations like KMS Client or KMS Key ID based
184+
// on your use-case.
185+
// Make sure you have the required permissions to use different configurations.
186+
187+
// - If you want to share cache entries across two keyrings HK1 and HK2,
188+
// you should set the Logical Key Store Names for both
189+
// Key Store instances (K1 and K2) to be the same.
190+
// - If you set the Logical Key Store Names for K1 and K2 to be different,
191+
// HK1 (which uses Key Store instance K1) and HK2 (which uses Key Store
192+
// instance K2) will NOT be able to share cache entries.
193+
var keystore2 = new KeyStore(new KeyStoreConfig
194+
{
195+
DdbClient = new AmazonDynamoDBClient(),
196+
DdbTableName = keyStoreTableName,
197+
LogicalKeyStoreName = logicalKeyStoreName,
198+
KmsClient = new AmazonKeyManagementServiceClient(),
199+
KmsConfiguration = new KMSConfiguration { KmsKeyArn = kmsKeyId }
200+
});
201+
202+
// 8. 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+
// partitionId for this example is a random UUID
209+
var keyringInput2 = new CreateAwsKmsHierarchicalKeyringInput
210+
{
211+
KeyStore = keystore2,
212+
branchKeyId = branchKeyId,
213+
TtlSeconds = 600, // This dictates how often we call back to KMS to authorize use of the branch keys
214+
Cache = sharedCache,
215+
PartitionId = partitionId
216+
};
217+
var hierarchicalKeyring2 = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput2);
218+
219+
// 9. Get the DDB Client for Hierarchical Keyring 2.
220+
var ddbClient2 = GetDdbClient(
221+
ddbTableName,
222+
hierarchicalKeyring2,
223+
attributeActionsOnEncrypt
224+
);
225+
226+
// 10. Encrypt Decrypt roundtrip with ddbClient2
227+
PutGetItems(ddbTableName, ddbClient2);
228+
}
229+
230+
public static Client.DynamoDbClient GetDdbClient(
231+
String ddbTableName,
232+
MaterialProviders.IKeyring hierarchicalKeyring,
233+
Dictionary<String, CryptoAction> attributeActionsOnEncrypt
234+
)
235+
{
236+
// Configure which attributes we expect to be included in the signature
237+
// when reading items. There are two options for configuring this:
238+
//
239+
// - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
240+
// When defining your DynamoDb schema and deciding on attribute names,
241+
// choose a distinguishing prefix (such as ":") for all attributes that
242+
// you do not want to include in the signature.
243+
// This has two main benefits:
244+
// - It is easier to reason about the security and authenticity of data within your item
245+
// when all unauthenticated data is easily distinguishable by their attribute name.
246+
// - If you need to add new unauthenticated attributes in the future,
247+
// you can easily make the corresponding update to your `attributeActionsOnEncrypt`
248+
// and immediately start writing to that new attribute, without
249+
// any other configuration update needed.
250+
// Once you configure this field, it is not safe to update it.
251+
//
252+
// - Configure `allowedUnsignedAttributes`: You may also explicitly list
253+
// a set of attributes that should be considered unauthenticated when encountered
254+
// on read. Be careful if you use this configuration. Do not remove an attribute
255+
// name from this configuration, even if you are no longer writing with that attribute,
256+
// as old items may still include this attribute, and our configuration needs to know
257+
// to continue to exclude this attribute from the signature scope.
258+
// If you add new attribute names to this field, you must first deploy the update to this
259+
// field to all readers in your host fleet before deploying the update to start writing
260+
// with that new attribute.
261+
//
262+
// For this example, we currently authenticate all attributes. To make it easier to
263+
// add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
264+
const String unsignAttrPrefix = ":";
265+
266+
// Create the DynamoDb Encryption configuration for the table we will be writing to.
267+
var tableConfigs = new Dictionary<String, DynamoDbTableEncryptionConfig>
268+
{
269+
[ddbTableName] = new DynamoDbTableEncryptionConfig
270+
{
271+
LogicalTableName = ddbTableName,
272+
PartitionKeyName = "partition_key",
273+
SortKeyName = "sort_key",
274+
AttributeActionsOnEncrypt = attributeActionsOnEncrypt,
275+
Keyring = hierarchicalKeyring,
276+
AllowedUnsignedAttributePrefix = unsignAttrPrefix
277+
}
278+
};
279+
280+
// Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above
281+
var ddbClient = new Client.DynamoDbClient(
282+
new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs });
283+
284+
return ddbClient;
285+
}
286+
287+
public static void PutGetItems(
288+
String ddbTableName,
289+
Client.DynamoDbClient ddbClient
290+
)
291+
{
292+
// Put an item into our table using the given ddb client.
293+
// Before the item gets sent to DynamoDb, it will be encrypted
294+
// client-side, according to our configuration.
295+
// This example creates a Hierarchical Keyring for a single BranchKeyId. You can, however, use a
296+
// BranchKeyIdSupplier as per your use-case. See the HierarchicalKeyringsExample.java for more
297+
// information.
298+
var item = new Dictionary<String, AttributeValue>
299+
{
300+
["partition_key"] = new AttributeValue("id"),
301+
["sort_key"] = new AttributeValue { N = "0" },
302+
["sensitive_data"] = new AttributeValue("encrypt and sign me!")
303+
};
304+
var putRequest = new PutItemRequest
305+
{
306+
TableName = ddbTableName,
307+
Item = item
308+
};
309+
310+
var putResponse = await ddbClient.PutItemAsync(putRequest);
311+
312+
// Demonstrate that PutItem succeeded
313+
Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK);
314+
315+
// Get the item back from our table using the same client.
316+
// The client will decrypt the item client-side, and return
317+
// back the original item.
318+
// This example creates a Hierarchical Keyring for a single BranchKeyId. You can, however, use a
319+
// BranchKeyIdSupplier as per your use-case. See the HierarchicalKeyringsExample.java for more
320+
// information.
321+
var keyToGet = new Dictionary<String, AttributeValue>
322+
{
323+
["partition_key"] = new AttributeValue("id"),
324+
["sort_key"] = new AttributeValue { N = "0" }
325+
};
326+
var getRequest = new GetItemRequest
327+
{
328+
Key = keyToGet,
329+
TableName = ddbTableName
330+
};
331+
var getResponse = await ddbClient.GetItemAsync(getRequest);
332+
333+
// Demonstrate that GetItem succeeded and returned the decrypted item
334+
Debug.Assert(getResponse.HttpStatusCode == HttpStatusCode.OK);
335+
var returnedItem = getResponse.Item;
336+
Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!"));
337+
}
338+
}

0 commit comments

Comments
 (0)