From fd00cd0e08f39a3ace414472a99f57b92e51be14 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila <61410899+RitvikKapila@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:32:38 -0700 Subject: [PATCH] chore(examples): Shared cache across Hierarchical Keyrings (#2045) --- ...acheAcrossHierarchicalKeyringsExample.java | 270 ++++++++++++++++++ ...AcrossHierarchicalKeyringsExampleTest.java | 19 ++ .../encryptionsdk/AllTestsSuite.java | 2 + .../encryptionsdk/kms/KMSTestFixtures.java | 3 + 4 files changed, 294 insertions(+) create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/SharedCacheAcrossHierarchicalKeyringsExample.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/SharedCacheAcrossHierarchicalKeyringsExampleTest.java diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/SharedCacheAcrossHierarchicalKeyringsExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/SharedCacheAcrossHierarchicalKeyringsExample.java new file mode 100644 index 00000000..3fe9b1c3 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/SharedCacheAcrossHierarchicalKeyringsExample.java @@ -0,0 +1,270 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings.hierarchical; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoResult; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptography.keystore.KeyStore; +import software.amazon.cryptography.keystore.model.CreateKeyInput; +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; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 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: + * + *
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. + * + *
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. + * + *
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 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. + * + *
This example requires access to the DDB Table (K) where you are storing the Branch Keys. 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. You need the following access on this key:
+ * - GenerateDataKeyWithoutPlaintext
+ * - Decrypt
+ */
+public class SharedCacheAcrossHierarchicalKeyringsExample {
+ private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8);
+
+ public static void encryptAndDecryptWithKeyring(
+ String keyStoreTableName, String logicalKeyStoreName, String partitionId, String kmsKeyId) {
+ // 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);
+
+ // 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();
+
+ // Instantiate the SDK
+ // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy,
+ // which enforces that this client only encrypts using committing algorithm suites and enforces
+ // that this client will only decrypt encrypted messages that were created with a committing
+ // algorithm suite.
+ // This is the default commitment policy if you build the client with
+ // `AwsCrypto.builder().build()`
+ // or `AwsCrypto.standard()`.
+ final AwsCrypto crypto = AwsCrypto.builder().build();
+
+ // Configure your KeyStore resource keystore1.
+ // This SHOULD be the same configuration that you used
+ // to initially create and populate your physical KeyStore.
+ // Note that ddbTableName 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();
+
+ // Call CreateKey to create a new active branch key
+ final String branchKeyId =
+ keystore1.CreateKey(CreateKeyInput.builder().build()).branchKeyIdentifier();
+
+ // 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.
+
+ // 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)
+ .cache(sharedCache)
+ .partitionId(partitionId)
+ .build();
+ final IKeyring hierarchicalKeyring1 = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput1);
+
+ // Create example encryption context
+ Map