From 3ce6a0afa2fbce9e443a4512225f6261f5dcc1b0 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Wed, 2 Oct 2024 14:16:44 -0700 Subject: [PATCH 1/9] chore(examples): Shared cache across Hierarchical Keyrings --- ...acheAcrossHierarchicalKeyringsExample.java | 425 ++++++++++++++++++ .../cryptography/examples/TestUtils.java | 2 + ...acheAcrossHierarchicalKeyringsExample.java | 32 ++ 3 files changed, 459 insertions(+) create mode 100644 Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java create mode 100644 Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestSharedCacheAcrossHierarchicalKeyringsExample.java diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java new file mode 100644 index 000000000..1858b91a3 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java @@ -0,0 +1,425 @@ +package software.amazon.cryptography.examples.keyring; + +import java.util.HashMap; +import java.util.Map; + +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.*; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryption; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbEncryptionConfig; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbTableEncryptionConfig; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbTablesEncryptionConfig; +import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.CryptoAction; +import software.amazon.cryptography.keystore.KeyStore; +import software.amazon.cryptography.keystore.model.CreateKeyStoreInput; +import software.amazon.cryptography.keystore.model.KMSConfiguration; +import software.amazon.cryptography.keystore.model.KeyStoreConfig; +import software.amazon.cryptography.materialproviders.ICryptographicMaterialsCache; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CacheType; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsHierarchicalKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateCryptographicMaterialsCacheInput; +import software.amazon.cryptography.materialproviders.model.DefaultCache; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +/* + This example demonstrates how to use a shared cache across multiple Hierarchical Keyrings. + With this functionality, users only need to maintain one common shared cache across multiple + Hierarchical Keyrings with different Key Stores instances/KMS Clients/KMS Keys. + + There are three important parameters that users need to carefully set while providing the shared cache: + + 1. Partition ID - Partition ID is an optional parameter provided to the Hierarchical Keyring input, + which distinguishes Cryptographic Material Providers (i.e: Keyrings) writing to a cache. + - If the Partition ID is set and is the same for two Hierarchical Keyrings (or another Material Provider), + they CAN share the same cache entries in the cache. + - If the Partition ID is set and is different for two Hierarchical Keyrings (or another Material Provider), + they CANNOT share the same cache entries in the cache. + - If the Partition ID is not set by the user, it is initialized as a random 16-byte UUID which makes + it unique for every Hierarchical Keyring, and two Hierarchical Keyrings (or another Material Provider) + CANNOT share the same cache entries in the cache. + + 2. Logical Key Store Name - This parameter is set by the user when configuring the Key Store for + the Hierarchical Keyring. This is a logical name for the branch key store. + Suppose you have a physical Key Store (K). You create two instances of K (K1 and K2). Now, you create + two Hierarchical Keyrings (HK1 and HK2) with these Key Store instances (K1 and K2 respectively). + - If you want to share cache entries across these two keyrings, you should set the Logical Key Store Names + for both the Key Store instances (K1 and K2) to be the same. + - If you set the Logical Key Store Names for K1 and K2 to be different, HK1 (which uses Key Store instance K1) + and HK2 (which uses Key Store instance K2) will NOT be able to share cache entries. + + 3. Branch Key ID - Choose an effective Branch Key ID Schema + + This is demonstrated in the example below. + Notice that both K1 and K2 are instances of the same physical Key Store (K). + You MUST NEVER have two different physical Key Stores with the same Logical Key Store Name. + + Important Note: If you have two or more Hierarchy Keyrings with: + - Same Partition ID + - Same Logical Key Store Name of the Key Store for the Hierarchical Keyring + - Same Branch Key ID + then they WILL share the cache entries in the Shared Cache. + Please make sure that you set all of Partition ID, Logical Key Store Name and Branch Key ID + to be the same for two Hierarchical Keyrings if and only if you want them to share cache entries. + + This example sets up DynamoDb Encryption for the AWS SDK client using the Hierarchical + Keyring, which establishes a key hierarchy where "branch" keys are persisted in DynamoDb. + These branch keys are used to protect your data keys, and these branch keys are themselves + protected by a root KMS Key. + + This example first creates a shared cache that you can use across multiple Hierarchical Keyrings. + The example then configures a Hierarchical Keyring (HK1 and HK2) with the shared cache, + a Branch Key ID and two instances (K1 and K2) of the same physical Key Store (K) respectively, + i.e. HK1 with K1 and HK2 with K2. The example demonstrates that if you set the same Partition ID + for HK1 and HK2, the two keyrings can share cache entries. + If you set different Partition ID of the Hierarchical Keyrings, or different + Logical Key Store Names of the Key Store instances, then the keyrings will NOT + be able to share cache entries. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + + This example also requires using a KMS Key whose ARN + is provided in CLI arguments. You need the following access + on this key: + - GenerateDataKeyWithoutPlaintext + - Decrypt + */ +public class SharedCacheAcrossHierarchicalKeyringsExample { + + public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( + String ddbTableName, + String branchKeyId, + String keyStoreTableName, + String logicalKeyStoreName, + String partitionId, + String kmsKeyId + ) { + // 1. Create the CryptographicMaterialsCache (CMC) to share across multiple Hierarchical Keyrings + // using the Material Providers Library + // This CMC takes in: + // - CacheType + final MaterialProviders matProv = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + + final CacheType cache = + CacheType.builder() + .Default(DefaultCache.builder().entryCapacity(100).build()) + .build(); + + final CreateCryptographicMaterialsCacheInput cryptographicMaterialsCacheInput = + CreateCryptographicMaterialsCacheInput.builder() + .cache(cache) + .build(); + + final ICryptographicMaterialsCache sharedCryptographicMaterialsCache = + matProv.CreateCryptographicMaterialsCache(cryptographicMaterialsCacheInput); + + // 2. Create a CacheType object for the sharedCryptographicMaterialsCache + // Note that the `cache` parameter in the Hierarchical Keyring Input takes a `CacheType` as input + final CacheType sharedCache = + CacheType.builder() + // This is the `Shared` CacheType that passes an already initialized shared cache + .Shared(sharedCryptographicMaterialsCache) + .build(); + + // Initial KeyStore Setup: This example requires that you have already + // created your KeyStore, and have populated it with a new branch key. + + // 3. Configure your KeyStore resource keystore1. + // This SHOULD be the same configuration that you used + // to initially create and populate your KeyStore. + // Note that keyStoreTableName is the physical Key Store, + // and keystore1 is instances of this physical Key Store. + final KeyStore keystore1 = KeyStore + .builder() + .KeyStoreConfig( + KeyStoreConfig + .builder() + .ddbClient(DynamoDbClient.create()) + .ddbTableName(keyStoreTableName) + .logicalKeyStoreName(logicalKeyStoreName) + .kmsClient(KmsClient.create()) + .kmsConfiguration( + KMSConfiguration.builder().kmsKeyArn(kmsKeyId).build() + ) + .build() + ) + .build(); + + // 4. Create the Hierarchical Keyring HK1 with Key Store instance K1, partitionId, + // the shared Cache and the BranchKeyId. + // Note that we are now providing an already initialized shared cache instead of just mentioning + // the cache type and the Hierarchical Keyring initializing a cache at initialization. + + // This example creates a Hierarchical Keyring for a single BranchKeyId. You can, however, use a + // BranchKeyIdSupplier as per your use-case. See the HierarchicalKeyringsExample.java for more + // information. + + // Please make sure that you read the guidance on how to set Partition ID, Logical Key Store Name and + // Branch Key ID at the top of this example before creating Hierarchical Keyrings with a Shared Cache. + // partitionId for this example is a random UUID + final CreateAwsKmsHierarchicalKeyringInput keyringInput1 = + CreateAwsKmsHierarchicalKeyringInput + .builder() + .keyStore(keystore1) + .branchKeyId(branchKeyId) + .ttlSeconds(600) // This dictates how often we call back to KMS to authorize use of the branch keys + .cache(sharedCache) + .partitionId(partitionId) + .build(); + final IKeyring hierarchicalKeyring1 = + matProv.CreateAwsKmsHierarchicalKeyring(keyringInput1); + + // 4. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + final Map attributeActionsOnEncrypt = new HashMap<>(); + attributeActionsOnEncrypt.put("partition_key", CryptoAction.SIGN_ONLY); // Our partition attribute must be SIGN_ONLY + attributeActionsOnEncrypt.put("sort_key", CryptoAction.SIGN_ONLY); // Our sort attribute must be SIGN_ONLY + attributeActionsOnEncrypt.put( + "sensitive_data", + CryptoAction.ENCRYPT_AND_SIGN + ); + + // 5. Get the DDB Client for Hierarchical Keyring 1. + final DynamoDbClient ddbClient1 = GetDDBClient(ddbTableName, hierarchicalKeyring1, attributeActionsOnEncrypt); + + // 6. Encrypt Decrypt roundtrip with ddbClient1 + PutGetItems(ddbTableName, ddbClient1); + + // Through the above encrypt and decrypt roundtrip, the cache will be populated and + // the cache entries can be used by another Hierarchical Keyring with the + // - Same Partition ID + // - Same Logical Key Store Name of the Key Store for the Hierarchical Keyring + // - Same Branch Key ID + + // 7. Configure your KeyStore resource keystore2. + // This SHOULD be the same configuration that you used + // to initially create and populate your physical KeyStore. + // Note that keyStoreTableName is the physical Key Store, + // and keystore2 is instances of this physical Key Store. + + // Note that for this example, keystore2 is identical to keystore1. + // You can optionally change configurations like KMS Client or KMS Key ID based + // on your use-case. + // Make sure you have the required permissions to use different configurations. + + // - If you want to share cache entries across two keyrings HK1 and HK2, + // you should set the Logical Key Store Names for both + // Key Store instances (K1 and K2) to be the same. + // - If you set the Logical Key Store Names for K1 and K2 to be different, + // HK1 (which uses Key Store instance K1) and HK2 (which uses Key Store + // instance K2) will NOT be able to share cache entries. + final KeyStore keystore2 = KeyStore + .builder() + .KeyStoreConfig( + KeyStoreConfig + .builder() + .ddbClient(DynamoDbClient.create()) + .ddbTableName(keyStoreTableName) + .logicalKeyStoreName(logicalKeyStoreName) + .kmsClient(KmsClient.create()) + .kmsConfiguration( + KMSConfiguration.builder().kmsKeyArn(kmsKeyId).build() + ) + .build() + ) + .build(); + + // 8. Create the Hierarchical Keyring HK2 with Key Store instance K2, the shared Cache + // and the same partitionId and BranchKeyId used in HK1 because we want to share cache entries + // (and experience cache HITS). + + // Please make sure that you read the guidance on how to set Partition ID, Logical Key Store Name and + // Branch Key ID at the top of this example before creating Hierarchical Keyrings with a Shared Cache. + // partitionId for this example is a random UUID + final CreateAwsKmsHierarchicalKeyringInput keyringInput2 = + CreateAwsKmsHierarchicalKeyringInput + .builder() + .keyStore(keystore2) + .branchKeyId(branchKeyId) + .ttlSeconds(600) // This dictates how often we call back to KMS to authorize use of the branch keys + .cache(sharedCache) + .partitionId(partitionId) + .build(); + final IKeyring hierarchicalKeyring2 = + matProv.CreateAwsKmsHierarchicalKeyring(keyringInput2); + + // 9. Get the DDB Client for Hierarchical Keyring 2. + final DynamoDbClient ddbClient2 = GetDDBClient(ddbTableName, hierarchicalKeyring2, attributeActionsOnEncrypt); + + // 10. Encrypt Decrypt roundtrip with ddbClient2 + PutGetItems(ddbTableName, ddbClient2); + } + + public static DynamoDbClient GetDDBClient( + String ddbTableName, + IKeyring hierarchicalKeyring, + Map attributeActionsOnEncrypt + ) { + // Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + final String unsignAttrPrefix = ":"; + + // Create the DynamoDb Encryption configuration for the table we will be writing to. + final Map tableConfigs = + new HashMap<>(); + final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig + .builder() + .logicalTableName(ddbTableName) + .partitionKeyName("partition_key") + .sortKeyName("sort_key") + .attributeActionsOnEncrypt(attributeActionsOnEncrypt) + .keyring(hierarchicalKeyring) + .allowedUnsignedAttributePrefix(unsignAttrPrefix) + .build(); + tableConfigs.put(ddbTableName, config); + + // Create the DynamoDb Encryption Interceptor + DynamoDbEncryptionInterceptor encryptionInterceptor = + DynamoDbEncryptionInterceptor + .builder() + .config( + DynamoDbTablesEncryptionConfig + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ) + .build(); + + // Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + final DynamoDbClient ddbClient = DynamoDbClient + .builder() + .overrideConfiguration( + ClientOverrideConfiguration + .builder() + .addExecutionInterceptor(encryptionInterceptor) + .build() + ) + .build(); + + return ddbClient; + } + + public static void PutGetItems( + String ddbTableName, + DynamoDbClient ddbClient + ) { + // Put an item into our table using the given ddb client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + // This example creates a Hierarchical Keyring for a single BranchKeyId. You can, however, use a + // BranchKeyIdSupplier as per your use-case. See the HierarchicalKeyringsExample.java for more + // information. + final HashMap item = new HashMap<>(); + item.put("partition_key", AttributeValue.builder().s("id").build()); + item.put("sort_key", AttributeValue.builder().n("0").build()); + item.put( + "sensitive_data", + AttributeValue.builder().s("encrypt and sign me!").build() + ); + + final PutItemRequest putRequest = PutItemRequest + .builder() + .tableName(ddbTableName) + .item(item) + .build(); + + final PutItemResponse putResponse = ddbClient.putItem(putRequest); + + // Demonstrate that PutItem succeeded + assert 200 == putResponse.sdkHttpResponse().statusCode(); + + // Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + // This example creates a Hierarchical Keyring for a single BranchKeyId. You can, however, use a + // BranchKeyIdSupplier as per your use-case. See the HierarchicalKeyringsExample.java for more + // information. + final HashMap keyToGet = new HashMap<>(); + keyToGet.put( + "partition_key", + AttributeValue.builder().s("id").build() + ); + keyToGet.put("sort_key", AttributeValue.builder().n("0").build()); + + final GetItemRequest getRequest = GetItemRequest + .builder() + .key(keyToGet) + .tableName(ddbTableName) + .build(); + + final GetItemResponse getResponse = ddbClient.getItem(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + assert 200 == getResponse.sdkHttpResponse().statusCode(); + final Map returnedItem = getResponse.item(); + assert returnedItem + .get("sensitive_data") + .s() + .equals("encrypt and sign me!"); + } + + public static void main(final String[] args) { + if (args.length <= 0) { + throw new IllegalArgumentException( + "To run this example, include the ddbTable, branchKeyId, " + + "keyStoreTableName, logicalKeyStoreName, partitionId, and kmsKeyId in args" + ); + } + final String ddbTableName = args[0]; + final String branchKeyId = args[1]; + final String keyStoreTableName = args[2]; + final String logicalKeyStoreName = args[3]; + final String partitionId = args[4]; + final String kmsKeyId = args[5]; + SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( + ddbTableName, + branchKeyId, + keyStoreTableName, + logicalKeyStoreName, + partitionId, + kmsKeyId + ); + } +} diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/TestUtils.java b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/TestUtils.java index 76fe7cbb8..0d4eb8e5e 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/TestUtils.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/TestUtils.java @@ -6,6 +6,8 @@ public class TestUtils { public static final String TEST_LOGICAL_KEYSTORE_NAME = "KeyStoreDdbTable"; public static final String TEST_KEYSTORE_KMS_KEY_ID = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126"; + public static final String TEST_PARTITION_ID = + "91c1b6a2-6fc3-4539-ad5e-938d597ed730"; public static final String TEST_AWS_ACCOUNT_ID = "658956600833"; public static final String TEST_AWS_REGION = "us-west-2"; diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestSharedCacheAcrossHierarchicalKeyringsExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestSharedCacheAcrossHierarchicalKeyringsExample.java new file mode 100644 index 000000000..d759e3a21 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestSharedCacheAcrossHierarchicalKeyringsExample.java @@ -0,0 +1,32 @@ +package software.amazon.cryptography.examples.keyring; + +import org.testng.annotations.Test; +import software.amazon.cryptography.examples.CreateKeyStoreKeyExample; +import software.amazon.cryptography.examples.TestUtils; + +public class TestSharedCacheAcrossHierarchicalKeyringsExample { + + @Test + public void TestSharedCacheAcrossHierarchicalKeyringsExample() throws InterruptedException { + // Create new branch key for test + String keyId = CreateKeyStoreKeyExample.KeyStoreCreateKey( + TestUtils.TEST_KEYSTORE_NAME, + TestUtils.TEST_LOGICAL_KEYSTORE_NAME, + TestUtils.TEST_KEYSTORE_KMS_KEY_ID + ); + + // Key creation is eventually consistent, so wait 5 seconds to decrease the likelihood + // our test fails due to eventual consistency issues. + Thread.sleep(5000); + + SharedCacheAcrossHierarchicalKeyringsExample + .SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( + TestUtils.TEST_DDB_TABLE_NAME, + keyId, + TestUtils.TEST_KEYSTORE_NAME, + TestUtils.TEST_LOGICAL_KEYSTORE_NAME, + TestUtils.TEST_PARTITION_ID, + TestUtils.TEST_KEYSTORE_KMS_KEY_ID + ); + } +} From a8969e2138b581edef0afbab27c5d889edb9177c Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Wed, 2 Oct 2024 14:26:45 -0700 Subject: [PATCH 2/9] prettier fix --- ...acheAcrossHierarchicalKeyringsExample.java | 115 ++++++++---------- ...acheAcrossHierarchicalKeyringsExample.java | 20 +-- 2 files changed, 62 insertions(+), 73 deletions(-) diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java index 1858b91a3..d0093d465 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java @@ -2,7 +2,6 @@ import java.util.HashMap; import java.util.Map; - import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.*; @@ -107,31 +106,28 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // using the Material Providers Library // This CMC takes in: // - CacheType - final MaterialProviders matProv = - MaterialProviders.builder() - .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) - .build(); + final MaterialProviders matProv = MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); - final CacheType cache = - CacheType.builder() - .Default(DefaultCache.builder().entryCapacity(100).build()) - .build(); + final CacheType cache = CacheType.builder() + .Default(DefaultCache.builder().entryCapacity(100).build()) + .build(); final CreateCryptographicMaterialsCacheInput cryptographicMaterialsCacheInput = - CreateCryptographicMaterialsCacheInput.builder() - .cache(cache) - .build(); + CreateCryptographicMaterialsCacheInput.builder().cache(cache).build(); final ICryptographicMaterialsCache sharedCryptographicMaterialsCache = - matProv.CreateCryptographicMaterialsCache(cryptographicMaterialsCacheInput); + matProv.CreateCryptographicMaterialsCache( + cryptographicMaterialsCacheInput + ); // 2. Create a CacheType object for the sharedCryptographicMaterialsCache // Note that the `cache` parameter in the Hierarchical Keyring Input takes a `CacheType` as input - final CacheType sharedCache = - CacheType.builder() - // This is the `Shared` CacheType that passes an already initialized shared cache - .Shared(sharedCryptographicMaterialsCache) - .build(); + final CacheType sharedCache = CacheType.builder() + // This is the `Shared` CacheType that passes an already initialized shared cache + .Shared(sharedCryptographicMaterialsCache) + .build(); // Initial KeyStore Setup: This example requires that you have already // created your KeyStore, and have populated it with a new branch key. @@ -141,11 +137,9 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // to initially create and populate your KeyStore. // Note that keyStoreTableName is the physical Key Store, // and keystore1 is instances of this physical Key Store. - final KeyStore keystore1 = KeyStore - .builder() + final KeyStore keystore1 = KeyStore.builder() .KeyStoreConfig( - KeyStoreConfig - .builder() + KeyStoreConfig.builder() .ddbClient(DynamoDbClient.create()) .ddbTableName(keyStoreTableName) .logicalKeyStoreName(logicalKeyStoreName) @@ -161,7 +155,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // the shared Cache and the BranchKeyId. // Note that we are now providing an already initialized shared cache instead of just mentioning // the cache type and the Hierarchical Keyring initializing a cache at initialization. - + // This example creates a Hierarchical Keyring for a single BranchKeyId. You can, however, use a // BranchKeyIdSupplier as per your use-case. See the HierarchicalKeyringsExample.java for more // information. @@ -170,8 +164,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // Branch Key ID at the top of this example before creating Hierarchical Keyrings with a Shared Cache. // partitionId for this example is a random UUID final CreateAwsKmsHierarchicalKeyringInput keyringInput1 = - CreateAwsKmsHierarchicalKeyringInput - .builder() + CreateAwsKmsHierarchicalKeyringInput.builder() .keyStore(keystore1) .branchKeyId(branchKeyId) .ttlSeconds(600) // This dictates how often we call back to KMS to authorize use of the branch keys @@ -180,7 +173,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( .build(); final IKeyring hierarchicalKeyring1 = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput1); - + // 4. Configure which attributes are encrypted and/or signed when writing new items. // For each attribute that may exist on the items we plan to write to our DynamoDbTable, // we must explicitly configure how they should be treated during item encryption: @@ -196,7 +189,11 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( ); // 5. Get the DDB Client for Hierarchical Keyring 1. - final DynamoDbClient ddbClient1 = GetDDBClient(ddbTableName, hierarchicalKeyring1, attributeActionsOnEncrypt); + final DynamoDbClient ddbClient1 = GetDDBClient( + ddbTableName, + hierarchicalKeyring1, + attributeActionsOnEncrypt + ); // 6. Encrypt Decrypt roundtrip with ddbClient1 PutGetItems(ddbTableName, ddbClient1); @@ -204,7 +201,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // Through the above encrypt and decrypt roundtrip, the cache will be populated and // the cache entries can be used by another Hierarchical Keyring with the // - Same Partition ID - // - Same Logical Key Store Name of the Key Store for the Hierarchical Keyring + // - Same Logical Key Store Name of the Key Store for the Hierarchical Keyring // - Same Branch Key ID // 7. Configure your KeyStore resource keystore2. @@ -212,7 +209,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // to initially create and populate your physical KeyStore. // Note that keyStoreTableName is the physical Key Store, // and keystore2 is instances of this physical Key Store. - + // Note that for this example, keystore2 is identical to keystore1. // You can optionally change configurations like KMS Client or KMS Key ID based // on your use-case. @@ -224,11 +221,9 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // - If you set the Logical Key Store Names for K1 and K2 to be different, // HK1 (which uses Key Store instance K1) and HK2 (which uses Key Store // instance K2) will NOT be able to share cache entries. - final KeyStore keystore2 = KeyStore - .builder() + final KeyStore keystore2 = KeyStore.builder() .KeyStoreConfig( - KeyStoreConfig - .builder() + KeyStoreConfig.builder() .ddbClient(DynamoDbClient.create()) .ddbTableName(keyStoreTableName) .logicalKeyStoreName(logicalKeyStoreName) @@ -248,8 +243,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // Branch Key ID at the top of this example before creating Hierarchical Keyrings with a Shared Cache. // partitionId for this example is a random UUID final CreateAwsKmsHierarchicalKeyringInput keyringInput2 = - CreateAwsKmsHierarchicalKeyringInput - .builder() + CreateAwsKmsHierarchicalKeyringInput.builder() .keyStore(keystore2) .branchKeyId(branchKeyId) .ttlSeconds(600) // This dictates how often we call back to KMS to authorize use of the branch keys @@ -260,7 +254,11 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( matProv.CreateAwsKmsHierarchicalKeyring(keyringInput2); // 9. Get the DDB Client for Hierarchical Keyring 2. - final DynamoDbClient ddbClient2 = GetDDBClient(ddbTableName, hierarchicalKeyring2, attributeActionsOnEncrypt); + final DynamoDbClient ddbClient2 = GetDDBClient( + ddbTableName, + hierarchicalKeyring2, + attributeActionsOnEncrypt + ); // 10. Encrypt Decrypt roundtrip with ddbClient2 PutGetItems(ddbTableName, ddbClient2); @@ -304,41 +302,37 @@ public static DynamoDbClient GetDDBClient( // Create the DynamoDb Encryption configuration for the table we will be writing to. final Map tableConfigs = new HashMap<>(); - final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig - .builder() - .logicalTableName(ddbTableName) - .partitionKeyName("partition_key") - .sortKeyName("sort_key") - .attributeActionsOnEncrypt(attributeActionsOnEncrypt) - .keyring(hierarchicalKeyring) - .allowedUnsignedAttributePrefix(unsignAttrPrefix) - .build(); + final DynamoDbTableEncryptionConfig config = + DynamoDbTableEncryptionConfig.builder() + .logicalTableName(ddbTableName) + .partitionKeyName("partition_key") + .sortKeyName("sort_key") + .attributeActionsOnEncrypt(attributeActionsOnEncrypt) + .keyring(hierarchicalKeyring) + .allowedUnsignedAttributePrefix(unsignAttrPrefix) + .build(); tableConfigs.put(ddbTableName, config); // Create the DynamoDb Encryption Interceptor DynamoDbEncryptionInterceptor encryptionInterceptor = - DynamoDbEncryptionInterceptor - .builder() + DynamoDbEncryptionInterceptor.builder() .config( - DynamoDbTablesEncryptionConfig - .builder() + DynamoDbTablesEncryptionConfig.builder() .tableEncryptionConfigs(tableConfigs) .build() ) .build(); // Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above - final DynamoDbClient ddbClient = DynamoDbClient - .builder() + final DynamoDbClient ddbClient = DynamoDbClient.builder() .overrideConfiguration( - ClientOverrideConfiguration - .builder() + ClientOverrideConfiguration.builder() .addExecutionInterceptor(encryptionInterceptor) .build() ) .build(); - - return ddbClient; + + return ddbClient; } public static void PutGetItems( @@ -359,8 +353,7 @@ public static void PutGetItems( AttributeValue.builder().s("encrypt and sign me!").build() ); - final PutItemRequest putRequest = PutItemRequest - .builder() + final PutItemRequest putRequest = PutItemRequest.builder() .tableName(ddbTableName) .item(item) .build(); @@ -377,14 +370,10 @@ public static void PutGetItems( // BranchKeyIdSupplier as per your use-case. See the HierarchicalKeyringsExample.java for more // information. final HashMap keyToGet = new HashMap<>(); - keyToGet.put( - "partition_key", - AttributeValue.builder().s("id").build() - ); + keyToGet.put("partition_key", AttributeValue.builder().s("id").build()); keyToGet.put("sort_key", AttributeValue.builder().n("0").build()); - final GetItemRequest getRequest = GetItemRequest - .builder() + final GetItemRequest getRequest = GetItemRequest.builder() .key(keyToGet) .tableName(ddbTableName) .build(); diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestSharedCacheAcrossHierarchicalKeyringsExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestSharedCacheAcrossHierarchicalKeyringsExample.java index d759e3a21..452da3f36 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestSharedCacheAcrossHierarchicalKeyringsExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestSharedCacheAcrossHierarchicalKeyringsExample.java @@ -7,7 +7,8 @@ public class TestSharedCacheAcrossHierarchicalKeyringsExample { @Test - public void TestSharedCacheAcrossHierarchicalKeyringsExample() throws InterruptedException { + public void TestSharedCacheAcrossHierarchicalKeyringsExample() + throws InterruptedException { // Create new branch key for test String keyId = CreateKeyStoreKeyExample.KeyStoreCreateKey( TestUtils.TEST_KEYSTORE_NAME, @@ -19,14 +20,13 @@ public void TestSharedCacheAcrossHierarchicalKeyringsExample() throws Interrupte // our test fails due to eventual consistency issues. Thread.sleep(5000); - SharedCacheAcrossHierarchicalKeyringsExample - .SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( - TestUtils.TEST_DDB_TABLE_NAME, - keyId, - TestUtils.TEST_KEYSTORE_NAME, - TestUtils.TEST_LOGICAL_KEYSTORE_NAME, - TestUtils.TEST_PARTITION_ID, - TestUtils.TEST_KEYSTORE_KMS_KEY_ID - ); + SharedCacheAcrossHierarchicalKeyringsExample.SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( + TestUtils.TEST_DDB_TABLE_NAME, + keyId, + TestUtils.TEST_KEYSTORE_NAME, + TestUtils.TEST_LOGICAL_KEYSTORE_NAME, + TestUtils.TEST_PARTITION_ID, + TestUtils.TEST_KEYSTORE_KMS_KEY_ID + ); } } From ea188a78691b467c72711426d1387e8e1b8a6bfa Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Wed, 2 Oct 2024 15:18:49 -0700 Subject: [PATCH 3/9] add net example --- ...acheAcrossHierarchicalKeyringsExample.java | 6 +- Examples/runtimes/net/src/Examples.cs | 1 + Examples/runtimes/net/src/TestUtils.cs | 2 + ...dCacheAcrossHierarchicalKeyringsExample.cs | 338 ++++++++++++++++++ 4 files changed, 344 insertions(+), 3 deletions(-) create mode 100644 Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java index d0093d465..f2a2fe189 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java @@ -189,7 +189,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( ); // 5. Get the DDB Client for Hierarchical Keyring 1. - final DynamoDbClient ddbClient1 = GetDDBClient( + final DynamoDbClient ddbClient1 = GetDdbClient( ddbTableName, hierarchicalKeyring1, attributeActionsOnEncrypt @@ -254,7 +254,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( matProv.CreateAwsKmsHierarchicalKeyring(keyringInput2); // 9. Get the DDB Client for Hierarchical Keyring 2. - final DynamoDbClient ddbClient2 = GetDDBClient( + final DynamoDbClient ddbClient2 = GetDdbClient( ddbTableName, hierarchicalKeyring2, attributeActionsOnEncrypt @@ -264,7 +264,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( PutGetItems(ddbTableName, ddbClient2); } - public static DynamoDbClient GetDDBClient( + public static DynamoDbClient GetDdbClient( String ddbTableName, IKeyring hierarchicalKeyring, Map attributeActionsOnEncrypt diff --git a/Examples/runtimes/net/src/Examples.cs b/Examples/runtimes/net/src/Examples.cs index c7f5b15f3..3c29b6ed7 100644 --- a/Examples/runtimes/net/src/Examples.cs +++ b/Examples/runtimes/net/src/Examples.cs @@ -30,6 +30,7 @@ static async Task Main() Thread.Sleep(5000); await HierarchicalKeyringExample.HierarchicalKeyringGetItemPutItem(keyId, keyId2); + await SharedCacheAcrossHierarchicalKeyringsExample.SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(keyId); await BasicSearchableEncryptionExample.PutItemQueryItemWithBeacon(keyId); await CompoundBeaconSearchableEncryptionExample.PutItemQueryItemWithCompoundBeacon(keyId); diff --git a/Examples/runtimes/net/src/TestUtils.cs b/Examples/runtimes/net/src/TestUtils.cs index 2fd898553..e2c61be6f 100644 --- a/Examples/runtimes/net/src/TestUtils.cs +++ b/Examples/runtimes/net/src/TestUtils.cs @@ -7,6 +7,8 @@ public class TestUtils public static readonly string TEST_KEYSTORE_NAME = "KeyStoreDdbTable"; public static readonly string TEST_LOGICAL_KEYSTORE_NAME = "KeyStoreDdbTable"; + public static readonly string TEST_PARTITION_ID = "91c1b6a2-6fc3-4539-ad5e-938d597ed730"; + public static readonly string TEST_KEYSTORE_KMS_KEY_ID = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126"; diff --git a/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs b/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs new file mode 100644 index 000000000..b0db8a6c6 --- /dev/null +++ b/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs @@ -0,0 +1,338 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Threading.Tasks; +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.Model; +using Amazon.KeyManagementService; +using AWS.Cryptography.DbEncryptionSDK.DynamoDb; +using AWS.Cryptography.DbEncryptionSDK.StructuredEncryption; +using AWS.Cryptography.KeyStore; +using AWS.Cryptography.MaterialProviders; + +/* + This example demonstrates how to use a shared cache across multiple Hierarchical Keyrings. + With this functionality, users only need to maintain one common shared cache across multiple + Hierarchical Keyrings with different Key Stores instances/KMS Clients/KMS Keys. + + There are three important parameters that users need to carefully set while providing the shared cache: + + 1. Partition ID - Partition ID is an optional parameter provided to the Hierarchical Keyring input, + which distinguishes Cryptographic Material Providers (i.e: Keyrings) writing to a cache. + - If the Partition ID is set and is the same for two Hierarchical Keyrings (or another Material Provider), + they CAN share the same cache entries in the cache. + - If the Partition ID is set and is different for two Hierarchical Keyrings (or another Material Provider), + they CANNOT share the same cache entries in the cache. + - If the Partition ID is not set by the user, it is initialized as a random 16-byte UUID which makes + it unique for every Hierarchical Keyring, and two Hierarchical Keyrings (or another Material Provider) + CANNOT share the same cache entries in the cache. + + 2. Logical Key Store Name - This parameter is set by the user when configuring the Key Store for + the Hierarchical Keyring. This is a logical name for the branch key store. + Suppose you have a physical Key Store (K). You create two instances of K (K1 and K2). Now, you create + two Hierarchical Keyrings (HK1 and HK2) with these Key Store instances (K1 and K2 respectively). + - If you want to share cache entries across these two keyrings, you should set the Logical Key Store Names + for both the Key Store instances (K1 and K2) to be the same. + - If you set the Logical Key Store Names for K1 and K2 to be different, HK1 (which uses Key Store instance K1) + and HK2 (which uses Key Store instance K2) will NOT be able to share cache entries. + + 3. Branch Key ID - Choose an effective Branch Key ID Schema + + This is demonstrated in the example below. + Notice that both K1 and K2 are instances of the same physical Key Store (K). + You MUST NEVER have two different physical Key Stores with the same Logical Key Store Name. + + Important Note: If you have two or more Hierarchy Keyrings with: + - Same Partition ID + - Same Logical Key Store Name of the Key Store for the Hierarchical Keyring + - Same Branch Key ID + then they WILL share the cache entries in the Shared Cache. + Please make sure that you set all of Partition ID, Logical Key Store Name and Branch Key ID + to be the same for two Hierarchical Keyrings if and only if you want them to share cache entries. + + This example sets up DynamoDb Encryption for the AWS SDK client using the Hierarchical + Keyring, which establishes a key hierarchy where "branch" keys are persisted in DynamoDb. + These branch keys are used to protect your data keys, and these branch keys are themselves + protected by a root KMS Key. + + This example first creates a shared cache that you can use across multiple Hierarchical Keyrings. + The example then configures a Hierarchical Keyring (HK1 and HK2) with the shared cache, + a Branch Key ID and two instances (K1 and K2) of the same physical Key Store (K) respectively, + i.e. HK1 with K1 and HK2 with K2. The example demonstrates that if you set the same Partition ID + for HK1 and HK2, the two keyrings can share cache entries. + If you set different Partition ID of the Hierarchical Keyrings, or different + Logical Key Store Names of the Key Store instances, then the keyrings will NOT + be able to share cache entries. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + + This example also requires using a KMS Key whose ARN + is provided in CLI arguments. You need the following access + on this key: + - GenerateDataKeyWithoutPlaintext + - Decrypt + */ +public class SharedCacheAcrossHierarchicalKeyringsExample +{ + public static async Task SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(String branchKeyId) + { + var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; + var keyStoreTableName = TestUtils.TEST_KEYSTORE_NAME; + var logicalKeyStoreName = TestUtils.TEST_LOGICAL_KEYSTORE_NAME; + var partitionId = TestUtils.TEST_PARTITION_ID; + var kmsKeyId = TestUtils.TEST_KEYSTORE_KMS_KEY_ID; + + // 1. Create the CryptographicMaterialsCache (CMC) to share across multiple Hierarchical Keyrings + // using the Material Providers Library + // This CMC takes in: + // - CacheType + var materialProviders = new MaterialProviders(new MaterialProvidersConfig()); + + var cache = new CacheType { Default = new DefaultCache{EntryCapacity = 100} }; + + var cryptographicMaterialsCacheInput = new CreateCryptographicMaterialsCacheInput {Cache = cache}; + + var sharedCryptographicMaterialsCache = materialProviders.CreateCryptographicMaterialsCache(cryptographicMaterialsCacheInput); + + // 2. Create a CacheType object for the sharedCryptographicMaterialsCache + // Note that the `cache` parameter in the Hierarchical Keyring Input takes a `CacheType` as input + // Here, we pass a `Shared` CacheType that passes an already initialized shared cache + var sharedCache = new CacheType { Shared = sharedCryptographicMaterialsCache }; + + // Initial KeyStore Setup: This example requires that you have already + // created your KeyStore, and have populated it with a new branch key. + + // 3. Configure your KeyStore resource keystore1. + // This SHOULD be the same configuration that you used + // to initially create and populate your KeyStore. + // Note that keyStoreTableName is the physical Key Store, + // and keystore1 is instances of this physical Key Store. + var keystore1 = new KeyStore(new KeyStoreConfig + { + DdbClient = new AmazonDynamoDBClient(), + DdbTableName = keyStoreTableName, + LogicalKeyStoreName = logicalKeyStoreName, + KmsClient = new AmazonKeyManagementServiceClient(), + KmsConfiguration = new KMSConfiguration { KmsKeyArn = kmsKeyId } + }); + + // 4. Create the Hierarchical Keyring HK1 with Key Store instance K1, partitionId, + // the shared Cache and the BranchKeyId. + // Note that we are now providing an already initialized shared cache instead of just mentioning + // the cache type and the Hierarchical Keyring initializing a cache at initialization. + + // This example creates a Hierarchical Keyring for a single BranchKeyId. You can, however, use a + // BranchKeyIdSupplier as per your use-case. See the HierarchicalKeyringsExample.java for more + // information. + + // Please make sure that you read the guidance on how to set Partition ID, Logical Key Store Name and + // Branch Key ID at the top of this example before creating Hierarchical Keyrings with a Shared Cache. + // partitionId for this example is a random UUID + var keyringInput1 = new CreateAwsKmsHierarchicalKeyringInput + { + KeyStore = keystore1, + branchKeyId = branchKeyId, + TtlSeconds = 600, // This dictates how often we call back to KMS to authorize use of the branch keys + Cache = sharedCache, + PartitionId = partitionId + }; + var hierarchicalKeyring1 = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput1); + + // 4. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + var attributeActionsOnEncrypt = new Dictionary + { + ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY + ["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY + ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN + }; + + // 5. Get the DDB Client for Hierarchical Keyring 1. + var ddbClient1 = GetDdbClient( + ddbTableName, + hierarchicalKeyring1, + attributeActionsOnEncrypt + ); + + // 6. Encrypt Decrypt roundtrip with ddbClient1 + PutGetItems(ddbTableName, ddbClient1); + + // Through the above encrypt and decrypt roundtrip, the cache will be populated and + // the cache entries can be used by another Hierarchical Keyring with the + // - Same Partition ID + // - Same Logical Key Store Name of the Key Store for the Hierarchical Keyring + // - Same Branch Key ID + + // 7. Configure your KeyStore resource keystore2. + // This SHOULD be the same configuration that you used + // to initially create and populate your physical KeyStore. + // Note that keyStoreTableName is the physical Key Store, + // and keystore2 is instances of this physical Key Store. + + // Note that for this example, keystore2 is identical to keystore1. + // You can optionally change configurations like KMS Client or KMS Key ID based + // on your use-case. + // Make sure you have the required permissions to use different configurations. + + // - If you want to share cache entries across two keyrings HK1 and HK2, + // you should set the Logical Key Store Names for both + // Key Store instances (K1 and K2) to be the same. + // - If you set the Logical Key Store Names for K1 and K2 to be different, + // HK1 (which uses Key Store instance K1) and HK2 (which uses Key Store + // instance K2) will NOT be able to share cache entries. + var keystore2 = new KeyStore(new KeyStoreConfig + { + DdbClient = new AmazonDynamoDBClient(), + DdbTableName = keyStoreTableName, + LogicalKeyStoreName = logicalKeyStoreName, + KmsClient = new AmazonKeyManagementServiceClient(), + KmsConfiguration = new KMSConfiguration { KmsKeyArn = kmsKeyId } + }); + + // 8. Create the Hierarchical Keyring HK2 with Key Store instance K2, the shared Cache + // and the same partitionId and BranchKeyId used in HK1 because we want to share cache entries + // (and experience cache HITS). + + // Please make sure that you read the guidance on how to set Partition ID, Logical Key Store Name and + // Branch Key ID at the top of this example before creating Hierarchical Keyrings with a Shared Cache. + // partitionId for this example is a random UUID + var keyringInput2 = new CreateAwsKmsHierarchicalKeyringInput + { + KeyStore = keystore2, + branchKeyId = branchKeyId, + TtlSeconds = 600, // This dictates how often we call back to KMS to authorize use of the branch keys + Cache = sharedCache, + PartitionId = partitionId + }; + var hierarchicalKeyring2 = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput2); + + // 9. Get the DDB Client for Hierarchical Keyring 2. + var ddbClient2 = GetDdbClient( + ddbTableName, + hierarchicalKeyring2, + attributeActionsOnEncrypt + ); + + // 10. Encrypt Decrypt roundtrip with ddbClient2 + PutGetItems(ddbTableName, ddbClient2); + } + + public static Client.DynamoDbClient GetDdbClient( + String ddbTableName, + MaterialProviders.IKeyring hierarchicalKeyring, + Dictionary attributeActionsOnEncrypt + ) + { + // Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const String unsignAttrPrefix = ":"; + + // Create the DynamoDb Encryption configuration for the table we will be writing to. + var tableConfigs = new Dictionary + { + [ddbTableName] = new DynamoDbTableEncryptionConfig + { + LogicalTableName = ddbTableName, + PartitionKeyName = "partition_key", + SortKeyName = "sort_key", + AttributeActionsOnEncrypt = attributeActionsOnEncrypt, + Keyring = hierarchicalKeyring, + AllowedUnsignedAttributePrefix = unsignAttrPrefix + } + }; + + // Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + var ddbClient = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + return ddbClient; + } + + public static void PutGetItems( + String ddbTableName, + Client.DynamoDbClient ddbClient + ) + { + // Put an item into our table using the given ddb client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + // This example creates a Hierarchical Keyring for a single BranchKeyId. You can, however, use a + // BranchKeyIdSupplier as per your use-case. See the HierarchicalKeyringsExample.java for more + // information. + var item = new Dictionary + { + ["partition_key"] = new AttributeValue("id"), + ["sort_key"] = new AttributeValue { N = "0" }, + ["sensitive_data"] = new AttributeValue("encrypt and sign me!") + }; + var putRequest = new PutItemRequest + { + TableName = ddbTableName, + Item = item + }; + + var putResponse = await ddbClient.PutItemAsync(putRequest); + + // Demonstrate that PutItem succeeded + Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK); + + // Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + // This example creates a Hierarchical Keyring for a single BranchKeyId. You can, however, use a + // BranchKeyIdSupplier as per your use-case. See the HierarchicalKeyringsExample.java for more + // information. + var keyToGet = new Dictionary + { + ["partition_key"] = new AttributeValue("id"), + ["sort_key"] = new AttributeValue { N = "0" } + }; + var getRequest = new GetItemRequest + { + Key = keyToGet, + TableName = ddbTableName + }; + var getResponse = await ddbClient.GetItemAsync(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + Debug.Assert(getResponse.HttpStatusCode == HttpStatusCode.OK); + var returnedItem = getResponse.Item; + Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!")); + } +} \ No newline at end of file From 88290f0e451c8befdf9bb6feedd890895b9c47ff Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Wed, 2 Oct 2024 15:26:07 -0700 Subject: [PATCH 4/9] fix prettier --- ...acheAcrossHierarchicalKeyringsExample.java | 63 ++++++++++++------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java index f2a2fe189..dfed4b84d 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java @@ -106,11 +106,13 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // using the Material Providers Library // This CMC takes in: // - CacheType - final MaterialProviders matProv = MaterialProviders.builder() + final MaterialProviders matProv = MaterialProviders + .builder() .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) .build(); - final CacheType cache = CacheType.builder() + final CacheType cache = CacheType + .builder() .Default(DefaultCache.builder().entryCapacity(100).build()) .build(); @@ -124,7 +126,8 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // 2. Create a CacheType object for the sharedCryptographicMaterialsCache // Note that the `cache` parameter in the Hierarchical Keyring Input takes a `CacheType` as input - final CacheType sharedCache = CacheType.builder() + final CacheType sharedCache = CacheType + .builder() // This is the `Shared` CacheType that passes an already initialized shared cache .Shared(sharedCryptographicMaterialsCache) .build(); @@ -137,9 +140,11 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // to initially create and populate your KeyStore. // Note that keyStoreTableName is the physical Key Store, // and keystore1 is instances of this physical Key Store. - final KeyStore keystore1 = KeyStore.builder() + final KeyStore keystore1 = KeyStore + .builder() .KeyStoreConfig( - KeyStoreConfig.builder() + KeyStoreConfig + .builder() .ddbClient(DynamoDbClient.create()) .ddbTableName(keyStoreTableName) .logicalKeyStoreName(logicalKeyStoreName) @@ -164,7 +169,8 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // Branch Key ID at the top of this example before creating Hierarchical Keyrings with a Shared Cache. // partitionId for this example is a random UUID final CreateAwsKmsHierarchicalKeyringInput keyringInput1 = - CreateAwsKmsHierarchicalKeyringInput.builder() + CreateAwsKmsHierarchicalKeyringInput + .builder() .keyStore(keystore1) .branchKeyId(branchKeyId) .ttlSeconds(600) // This dictates how often we call back to KMS to authorize use of the branch keys @@ -221,9 +227,11 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // - If you set the Logical Key Store Names for K1 and K2 to be different, // HK1 (which uses Key Store instance K1) and HK2 (which uses Key Store // instance K2) will NOT be able to share cache entries. - final KeyStore keystore2 = KeyStore.builder() + final KeyStore keystore2 = KeyStore + .builder() .KeyStoreConfig( - KeyStoreConfig.builder() + KeyStoreConfig + .builder() .ddbClient(DynamoDbClient.create()) .ddbTableName(keyStoreTableName) .logicalKeyStoreName(logicalKeyStoreName) @@ -243,7 +251,8 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem( // Branch Key ID at the top of this example before creating Hierarchical Keyrings with a Shared Cache. // partitionId for this example is a random UUID final CreateAwsKmsHierarchicalKeyringInput keyringInput2 = - CreateAwsKmsHierarchicalKeyringInput.builder() + CreateAwsKmsHierarchicalKeyringInput + .builder() .keyStore(keystore2) .branchKeyId(branchKeyId) .ttlSeconds(600) // This dictates how often we call back to KMS to authorize use of the branch keys @@ -302,31 +311,35 @@ public static DynamoDbClient GetDdbClient( // Create the DynamoDb Encryption configuration for the table we will be writing to. final Map tableConfigs = new HashMap<>(); - final DynamoDbTableEncryptionConfig config = - DynamoDbTableEncryptionConfig.builder() - .logicalTableName(ddbTableName) - .partitionKeyName("partition_key") - .sortKeyName("sort_key") - .attributeActionsOnEncrypt(attributeActionsOnEncrypt) - .keyring(hierarchicalKeyring) - .allowedUnsignedAttributePrefix(unsignAttrPrefix) - .build(); + final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig + .builder() + .logicalTableName(ddbTableName) + .partitionKeyName("partition_key") + .sortKeyName("sort_key") + .attributeActionsOnEncrypt(attributeActionsOnEncrypt) + .keyring(hierarchicalKeyring) + .allowedUnsignedAttributePrefix(unsignAttrPrefix) + .build(); tableConfigs.put(ddbTableName, config); // Create the DynamoDb Encryption Interceptor DynamoDbEncryptionInterceptor encryptionInterceptor = - DynamoDbEncryptionInterceptor.builder() + DynamoDbEncryptionInterceptor + .builder() .config( - DynamoDbTablesEncryptionConfig.builder() + DynamoDbTablesEncryptionConfig + .builder() .tableEncryptionConfigs(tableConfigs) .build() ) .build(); // Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above - final DynamoDbClient ddbClient = DynamoDbClient.builder() + final DynamoDbClient ddbClient = DynamoDbClient + .builder() .overrideConfiguration( - ClientOverrideConfiguration.builder() + ClientOverrideConfiguration + .builder() .addExecutionInterceptor(encryptionInterceptor) .build() ) @@ -353,7 +366,8 @@ public static void PutGetItems( AttributeValue.builder().s("encrypt and sign me!").build() ); - final PutItemRequest putRequest = PutItemRequest.builder() + final PutItemRequest putRequest = PutItemRequest + .builder() .tableName(ddbTableName) .item(item) .build(); @@ -373,7 +387,8 @@ public static void PutGetItems( keyToGet.put("partition_key", AttributeValue.builder().s("id").build()); keyToGet.put("sort_key", AttributeValue.builder().n("0").build()); - final GetItemRequest getRequest = GetItemRequest.builder() + final GetItemRequest getRequest = GetItemRequest + .builder() .key(keyToGet) .tableName(ddbTableName) .build(); From e09ee4783cec57c5b4e037ba4622ae50d17cefd5 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Wed, 2 Oct 2024 15:29:49 -0700 Subject: [PATCH 5/9] fix --- .../keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs b/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs index b0db8a6c6..a31a0cab3 100644 --- a/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs +++ b/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs @@ -142,7 +142,7 @@ public static async Task SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(Str Cache = sharedCache, PartitionId = partitionId }; - var hierarchicalKeyring1 = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput1); + IKeyring hierarchicalKeyring1 = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput1); // 4. Configure which attributes are encrypted and/or signed when writing new items. // For each attribute that may exist on the items we plan to write to our DynamoDbTable, @@ -214,7 +214,7 @@ public static async Task SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(Str Cache = sharedCache, PartitionId = partitionId }; - var hierarchicalKeyring2 = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput2); + IKeyring hierarchicalKeyring2 = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput2); // 9. Get the DDB Client for Hierarchical Keyring 2. var ddbClient2 = GetDdbClient( @@ -229,7 +229,7 @@ public static async Task SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(Str public static Client.DynamoDbClient GetDdbClient( String ddbTableName, - MaterialProviders.IKeyring hierarchicalKeyring, + IKeyring hierarchicalKeyring, Dictionary attributeActionsOnEncrypt ) { From a5303f37eff15a6e33c061bbbadf9bafafe37c74 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Wed, 2 Oct 2024 16:00:09 -0700 Subject: [PATCH 6/9] fix --- .../SharedCacheAcrossHierarchicalKeyringsExample.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs b/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs index a31a0cab3..540f239ca 100644 --- a/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs +++ b/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs @@ -87,17 +87,17 @@ public static async Task SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(Str var logicalKeyStoreName = TestUtils.TEST_LOGICAL_KEYSTORE_NAME; var partitionId = TestUtils.TEST_PARTITION_ID; var kmsKeyId = TestUtils.TEST_KEYSTORE_KMS_KEY_ID; - + // 1. Create the CryptographicMaterialsCache (CMC) to share across multiple Hierarchical Keyrings // using the Material Providers Library // This CMC takes in: // - CacheType var materialProviders = new MaterialProviders(new MaterialProvidersConfig()); - var cache = new CacheType { Default = new DefaultCache{EntryCapacity = 100} }; + var cache = new CacheType { Default = new DefaultCache { EntryCapacity = 100 } }; + + var cryptographicMaterialsCacheInput = new CreateCryptographicMaterialsCacheInput { Cache = cache }; - var cryptographicMaterialsCacheInput = new CreateCryptographicMaterialsCacheInput {Cache = cache}; - var sharedCryptographicMaterialsCache = materialProviders.CreateCryptographicMaterialsCache(cryptographicMaterialsCacheInput); // 2. Create a CacheType object for the sharedCryptographicMaterialsCache From fb72db122e5f0a58ebe161585b6abcea1c20a837 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Wed, 2 Oct 2024 16:07:43 -0700 Subject: [PATCH 7/9] fix net --- .../SharedCacheAcrossHierarchicalKeyringsExample.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs b/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs index 540f239ca..b25d3639a 100644 --- a/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs +++ b/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs @@ -137,12 +137,12 @@ public static async Task SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(Str var keyringInput1 = new CreateAwsKmsHierarchicalKeyringInput { KeyStore = keystore1, - branchKeyId = branchKeyId, + BranchKeyId = branchKeyId, TtlSeconds = 600, // This dictates how often we call back to KMS to authorize use of the branch keys Cache = sharedCache, PartitionId = partitionId }; - IKeyring hierarchicalKeyring1 = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput1); + IKeyring hierarchicalKeyring1 = materialProviders.CreateAwsKmsHierarchicalKeyring(keyringInput1); // 4. Configure which attributes are encrypted and/or signed when writing new items. // For each attribute that may exist on the items we plan to write to our DynamoDbTable, @@ -209,12 +209,12 @@ public static async Task SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(Str var keyringInput2 = new CreateAwsKmsHierarchicalKeyringInput { KeyStore = keystore2, - branchKeyId = branchKeyId, + BranchKeyId = branchKeyId, TtlSeconds = 600, // This dictates how often we call back to KMS to authorize use of the branch keys Cache = sharedCache, PartitionId = partitionId }; - IKeyring hierarchicalKeyring2 = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput2); + IKeyring hierarchicalKeyring2 = materialProviders.CreateAwsKmsHierarchicalKeyring(keyringInput2); // 9. Get the DDB Client for Hierarchical Keyring 2. var ddbClient2 = GetDdbClient( @@ -284,7 +284,7 @@ Dictionary attributeActionsOnEncrypt return ddbClient; } - public static void PutGetItems( + public static async void PutGetItems( String ddbTableName, Client.DynamoDbClient ddbClient ) From 797501f9d034e448057c008fffff94c6bc6b9256 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Thu, 21 Nov 2024 17:09:19 -0800 Subject: [PATCH 8/9] fix --- .../src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs b/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs index b25d3639a..2a3d728df 100644 --- a/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs +++ b/Examples/runtimes/net/src/keyring/SharedCacheAcrossHierarchicalKeyringsExample.cs @@ -335,4 +335,4 @@ Client.DynamoDbClient ddbClient var returnedItem = getResponse.Item; Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!")); } -} \ No newline at end of file +} From d1a4ac423848b084b81db5a2324ba002859235bc Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Wed, 15 Jan 2025 10:20:27 -0800 Subject: [PATCH 9/9] update rust min stack --- .github/workflows/library_rust_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/library_rust_tests.yml b/.github/workflows/library_rust_tests.yml index 10fe5dfcf..e921284a8 100644 --- a/.github/workflows/library_rust_tests.yml +++ b/.github/workflows/library_rust_tests.yml @@ -27,7 +27,7 @@ jobs: id-token: write contents: read env: - RUST_MIN_STACK: 104857600 + RUST_MIN_STACK: 404857600 steps: - name: Support longpaths on Git checkout run: |