From d8ca214c6635b1d5bc25faee836eecdf72a93f61 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila <61410899+RitvikKapila@users.noreply.github.com> Date: Wed, 2 Oct 2024 11:49:49 -0700 Subject: [PATCH] chore(examples; MPL bump): Shared cache across Hierarchical Keyrings; Bump MPL to 1.7.0 (#680) --- .../SharedCacheAcrossHierarchicalKeyrings.cs | 252 ++++++++++++++++++ mpl | 2 +- 2 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/SharedCacheAcrossHierarchicalKeyrings.cs diff --git a/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/SharedCacheAcrossHierarchicalKeyrings.cs b/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/SharedCacheAcrossHierarchicalKeyrings.cs new file mode 100644 index 000000000..9aa93a6bf --- /dev/null +++ b/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/SharedCacheAcrossHierarchicalKeyrings.cs @@ -0,0 +1,252 @@ +using Amazon.DynamoDBv2; +using Amazon.KeyManagementService; +using AWS.Cryptography.EncryptionSDK; +using AWS.Cryptography.KeyStore; +using AWS.Cryptography.MaterialProviders; +using Xunit; + +/// +/// 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 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 SharedCacheAcrossHierarchicalKeyrings +{ + // THESE ARE PUBLIC RESOURCES DO NOT USE IN A PRODUCTION ENVIRONMENT + private static string branchKeyId = "43574aa0-de30-424e-bad4-0b06f6e89478"; + private static void Run(MemoryStream plaintext) + { + // 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); + + // 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 }; + + // 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()`. + var encryptionSDK = new ESDK(new AwsEncryptionSdkConfig()); + + // 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. + + // Create an AWS KMS Configuration to use with your KeyStore. + // The KMS Configuration MUST have the right access to the resources in the KeyStore. + var kmsConfig = new KMSConfiguration { KmsKeyArn = ExampleUtils.ExampleUtils.GetBranchKeyArn() }; + + var keystoreConfig = new KeyStoreConfig + { + // Client MUST have permissions to decrypt kmsConfig.KmsKeyArn + KmsClient = new AmazonKeyManagementServiceClient(), + KmsConfiguration = kmsConfig, + DdbTableName = ExampleUtils.ExampleUtils.GetKeyStoreName(), + DdbClient = new AmazonDynamoDBClient(), + LogicalKeyStoreName = ExampleUtils.ExampleUtils.GetLogicalKeyStoreName() + }; + + + var keystore1 = new KeyStore(keystoreConfig); + + // 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. + + // partitionId for this example is a random UUID + var partitionId = "91c1b6a2-6fc3-4539-ad5e-938d597ed730"; + + // 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 + + var createKeyringInput1 = new CreateAwsKmsHierarchicalKeyringInput + { + KeyStore = keystore1, + // This branchKeyId you have configured your keyring with MUST be decrypted by the + // KMS config in the keystore and therefore MUST have the right permissions. + BranchKeyId = branchKeyId, + // The value provided to `EntryCapacity` dictates how many branch keys will be held locally + Cache = sharedCache, + // This dictates how often we call back to KMS to authorize use of the branch keys + TtlSeconds = 600, + PartitionId = partitionId + }; + var keyring1 = materialProviders.CreateAwsKmsHierarchicalKeyring(createKeyringInput1); + + // Create example encryption context + var encryptionContext = new Dictionary() + { + {"encryption", "context"}, + {"is not", "secret"}, + {"but adds", "useful metadata"}, + {"that can help you", "be confident that"}, + {"the data you are handling", "is what you think it is"} + }; + + // Encrypt the data for encryptionContext using keyring1 + var encryptInput1 = new EncryptInput + { + Plaintext = plaintext, + Keyring = keyring1, + EncryptionContext = encryptionContext + }; + + var encryptOutput1 = encryptionSDK.Encrypt(encryptInput1); + + + // Decrypt your encrypted data using the same keyring HK1 you used on encrypt. + var decryptOutput1 = encryptionSDK.Decrypt(new DecryptInput { + Ciphertext = encryptOutput1.Ciphertext, + Keyring = keyring1 } + ); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + var decrypted1 = decryptOutput1.Plaintext; + Assert.Equal(decrypted1.ToArray(), plaintext.ToArray()); + + // 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 + + // Configure your KeyStore resource keystore2. + // 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 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(keystoreConfig); + + // 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 + + var createKeyringInput2 = new CreateAwsKmsHierarchicalKeyringInput + { + KeyStore = keystore2, + // This branchKeyId you have configured your keyring with MUST be decrypted by the + // KMS config in the keystore and therefore MUST have the right permissions. + BranchKeyId = branchKeyId, + // The value provided to `EntryCapacity` dictates how many branch keys will be held locally + Cache = sharedCache, + // This dictates how often we call back to KMS to authorize use of the branch keys + TtlSeconds = 600, + PartitionId = partitionId + }; + var keyring2 = materialProviders.CreateAwsKmsHierarchicalKeyring(createKeyringInput2); + + // This encrypt-decrypt roundtrip with HK2 will experience Cache HITS from previous HK1 roundtrip + // Encrypt the data for encryptionContext using hierarchicalKeyring2 + var encryptInput2 = new EncryptInput + { + Plaintext = plaintext, + Keyring = keyring2, + EncryptionContext = encryptionContext + }; + + var encryptOutput2 = encryptionSDK.Encrypt(encryptInput2); + + // Decrypt your encrypted data using the same keyring HK2 you used on encrypt. + var decryptOutput2 = encryptionSDK.Decrypt(new DecryptInput { + Ciphertext = encryptOutput2.Ciphertext, + Keyring = keyring2 } + ); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + var decrypted2 = decryptOutput2.Plaintext; + Assert.Equal(decrypted2.ToArray(), plaintext.ToArray()); + } + + // We test examples to ensure they remain up-to-date. + [Fact] + public void TestAwsKmsHierarchicalKeyringExample() + { + Run(ExampleUtils.ExampleUtils.GetPlaintextStream()); + } + +} diff --git a/mpl b/mpl index 66a04bf31..ea0fe508d 160000 --- a/mpl +++ b/mpl @@ -1 +1 @@ -Subproject commit 66a04bf31bcfd8fd514acea740d8e670ab565ed9 +Subproject commit ea0fe508dca214b9c9bdaf0fc21122e83971ef1b