From f27071843c9bef0fa6981d32d6be984e173b5237 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Tue, 17 Oct 2023 14:53:55 -0700 Subject: [PATCH] chore(.NET): update hierarchy examples --- .../AwsKmsHierarchicalKeyring.cs | 202 +++++++--------- ...KmsHierarchicalKeyringBranchKeySupplier.cs | 227 ++++++++++++++++++ ...reateBranchKeyId.cs => CreateBranchKey.cs} | 4 +- ...sionBranchKeyId.cs => VersionBranchKey.cs} | 4 +- .../runtimes/net/Examples/README.md | 1 + 5 files changed, 318 insertions(+), 120 deletions(-) create mode 100644 AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/AwsKmsHierarchicalKeyringBranchKeySupplier.cs rename AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/{CreateBranchKeyId.cs => CreateBranchKey.cs} (93%) rename AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/{VersionBranchKeyId.cs => VersionBranchKey.cs} (92%) diff --git a/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/AwsKmsHierarchicalKeyring.cs b/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/AwsKmsHierarchicalKeyring.cs index 933ca4e50..a424506b5 100644 --- a/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/AwsKmsHierarchicalKeyring.cs +++ b/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/AwsKmsHierarchicalKeyring.cs @@ -1,3 +1,6 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + using Amazon.DynamoDBv2; using Amazon.KeyManagementService; using AWS.Cryptography.EncryptionSDK; @@ -34,9 +37,13 @@ /// or you can implement an interface that selects /// the Branch Key based on the Encryption Context. /// -/// This example demonstrates configuring a Hierarchical Keyring -/// with a Branch Key ID Supplier to encrypt and decrypt data for -/// two separate tenants. +/// This example demonstrates how to: +/// - Create a key store +/// - Create a branch key +/// - Version a branch key +/// - Configure a Hierarchical Keyring +/// with a static branch key configuration to ensure we are restricting +/// access to a single tenant. /// /// This example requires access to the DDB Table where you are storing the Branch Keys. /// This table must be configured with the following @@ -51,59 +58,76 @@ /// public class AwsKmsHierarchicalKeyring { - // THESE ARE PUBLIC RESOURCES DO NOT USE IN A PRODUCTION ENVIRONMENT - private static string branchKeyIdA = "43574aa0-de30-424e-bad4-0b06f6e89478"; - private static string branchKeyIdB = "a2f4be37-15ec-489a-bcb5-dcce1f6bfe84"; - private static void Run(MemoryStream plaintext) + private void Run(MemoryStream plaintext) { - var kmsConfig = new KMSConfiguration { KmsKeyArn = ExampleUtils.ExampleUtils.GetBranchKeyArn() }; - // 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 keystoreConfig = new KeyStoreConfig + // 1. Configure your KeyStore resource. + // `ddbTableName` is the name you want for the DDB table that + // will back your keystore. + // `kmsKeyArn` is the KMS Key that will protect your branch keys and beacon keys + // when they are stored in your DDB table. + var keyStoreConfig = new KeyStoreConfig { - // Client MUST have permissions to decrypt kmsConfig.KmsKeyArn KmsClient = new AmazonKeyManagementServiceClient(), - KmsConfiguration = kmsConfig, + KmsConfiguration = new KMSConfiguration{ KmsKeyArn = ExampleUtils.ExampleUtils.GetBranchKeyArn() }, DdbTableName = ExampleUtils.ExampleUtils.GetKeyStoreName(), DdbClient = new AmazonDynamoDBClient(), LogicalKeyStoreName = ExampleUtils.ExampleUtils.GetLogicalKeyStoreName() }; - var materialProviders = new MaterialProviders(new MaterialProvidersConfig()); - var keystore = new KeyStore(keystoreConfig); + var keyStore = new KeyStore(keyStoreConfig); + + // 2. Create the DynamoDb table that will store the branch keys and beacon keys. + // This checks if the correct table already exists at `ddbTableName` + // by using the DescribeTable API. If no table exists, + // it will create one. If a table exists, it will verify + // the table's configuration and will error if the configuration is incorrect. + // It may take a couple minutes for the table to become ACTIVE, + // at which point it is ready to store branch and beacon keys. + + // keyStore.CreateKeyStore(new CreateKeyStoreInput()); + + // We have already created a Table with the specified configuration. + // For testing purposes we will not create a table when we run this example. - // Create a branch key supplier that maps the branch key id to a more readable format - var branchKeySupplier = new ExampleBranchKeySupplier(branchKeyIdA, branchKeyIdB); + // 3. Create a new branch key and beacon key in our KeyStore. + // Both the branch key and the beacon key will share an Id. + // This creation is eventually consistent. See the CreateBranchKey + // Example for how to populate this table. - // Create an AWS Hierarchical Keyring with the branch key id supplier + // var branchKeyId = CreateBranchKey.createBranchKey(); + + // For testing purposes we will not create a table when we run this example. + // We will use an existing branch key created using the above code to run this + // example. + + // THIS IS A PUBLIC RESOURCE DO NOT USE IN A PRODUCTION ENVIRONMENT + var branchKeyId = "43574aa0-de30-424e-bad4-0b06f6e89478"; + + // 4. Create the Hierarchical Keyring, using the Branch Key ID Supplier above. + // With this configuration, the AWS SDK Client ultimately configured will be capable + // of encrypting or decrypting items for either tenant (assuming correct KMS access). + // If you want to restrict the client to only encrypt or decrypt for a single tenant, + // configure this Hierarchical Keyring using `BranchKeyId = tenant1BranchKeyId` instead + // of `BranchKeyIdSupplier = branchKeySupplier`. var createKeyringInput = new CreateAwsKmsHierarchicalKeyringInput { - KeyStore = keystore, - // 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. - BranchKeyIdSupplier = branchKeySupplier, + KeyStore = keyStore, + BranchKeyId = branchKeyId, // The value provided to `EntryCapacity` dictates how many branch keys will be held locally Cache = new CacheType { Default = new DefaultCache{EntryCapacity = 100} }, // This dictates how often we call back to KMS to authorize use of the branch keys TtlSeconds = 600 }; + var materialProviders = new MaterialProviders(new MaterialProvidersConfig()); var keyring = materialProviders.CreateAwsKmsHierarchicalKeyring(createKeyringInput); - // The Branch Key Id supplier uses the encryption context to determine which branch key id - // will be used to encrypt data. - var encryptionContextA = new Dictionary() + + // 5. Create an encryption context + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + var encryptionContext = new Dictionary() { - // We will encrypt with TenantKeyA - {"tenant", "TenantA"}, - {"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"} - }; - var encryptionContextB = new Dictionary() - { - // We will encrypt with TenantKeyB - {"tenant", "TenantB"}, {"encryption", "context"}, {"is not", "secret"}, {"but adds", "useful metadata"}, @@ -111,98 +135,44 @@ private static void Run(MemoryStream plaintext) {"the data you are handling", "is what you think it is"} }; - var encryptionSDK = new ESDK(new AwsEncryptionSdkConfig()); + var encryptionSdk = new ESDK(new AwsEncryptionSdkConfig()); - var encryptInputA = new EncryptInput + var encryptInput = new EncryptInput { Plaintext = plaintext, Keyring = keyring, - // We will encrypt with TenantKeyA - EncryptionContext = encryptionContextA - }; - - var encryptInputB = new EncryptInput{ - Plaintext = plaintext, - Keyring = keyring, - // We will encrypt with TenantKeyB - EncryptionContext = encryptionContextB + EncryptionContext = encryptionContext }; - var encryptOutputA = encryptionSDK.Encrypt(encryptInputA); - var encryptOutputB = encryptionSDK.Encrypt(encryptInputB); + // 6. Encrypt the Data + var encryptOutput = encryptionSdk.Encrypt(encryptInput); - // To attest that TenantKeyB cannot decrypt a message written by TenantKeyA - // let's construct more restrictive hierarchical keyrings. - - var createHierarchicalKeyringA = new CreateAwsKmsHierarchicalKeyringInput() - { - KeyStore = keystore, - BranchKeyId = branchKeyIdA, - Cache = new CacheType { Default = new DefaultCache{EntryCapacity = 100} }, - TtlSeconds = 600 - }; - var hierarchicalKeyringA = materialProviders.CreateAwsKmsHierarchicalKeyring(createHierarchicalKeyringA); + // Demonstrate that the ciphertext and plaintext are different. + Assert.NotEqual(encryptOutput.Ciphertext.ToArray(), plaintext.ToArray()); - var createHierarchicalKeyringB = new CreateAwsKmsHierarchicalKeyringInput() + var decryptInput = new DecryptInput { - KeyStore = keystore, - BranchKeyId = branchKeyIdB, - Cache = new CacheType { Default = new DefaultCache{EntryCapacity = 100} }, - TtlSeconds = 600 + Ciphertext = encryptOutput.Ciphertext, + EncryptionContext = encryptionContext, + Keyring = keyring }; - var hierarchicalKeyringB = materialProviders.CreateAwsKmsHierarchicalKeyring(createHierarchicalKeyringB); - var decryptFailed = false; - try - { - // Try to use keyring for Tenant B to decrypt a message encrypted with Tenant A's key - // Expected to fail. - var decryptInput = new DecryptInput - { - Ciphertext = encryptOutputA.Ciphertext, - Keyring = hierarchicalKeyringB, - }; - encryptionSDK.Decrypt(decryptInput); - } - catch (AwsCryptographicMaterialProvidersException) - { - decryptFailed = true; - } - Assert.True(decryptFailed); + // 7. Decrypt the Data + var decryptOutput = encryptionSdk.Decrypt(decryptInput); - decryptFailed = false; - try - { - // Try to use keyring for Tenant A to decrypt a message encrypted with Tenant B's key - // Expected to fail. - var decryptInput = new DecryptInput - { - Ciphertext = encryptOutputB.Ciphertext, - Keyring = hierarchicalKeyringA, - }; - encryptionSDK.Decrypt(decryptInput); - } - catch (AwsCryptographicMaterialProvidersException) - { - decryptFailed = true; - } - Assert.True(decryptFailed); - - // Decrypt your encrypted data using the same keyring you used on encrypt. - encryptionSDK.Decrypt(new DecryptInput { - Ciphertext = encryptOutputA.Ciphertext, - Keyring = keyring } - ); - encryptionSDK.Decrypt(new DecryptInput { - Ciphertext = encryptOutputB.Ciphertext, - Keyring = keyring } - ); - + // Demonstrate that the decrypted ciphertext and plaintext are the same + Assert.Equal(decryptOutput.Plaintext.ToArray(), plaintext.ToArray()); + + // 8. Version the Branch Key in our KeyStore. + // Only the branch key will be rotated. + // This rotation is eventually consistent. See the VersionBranchKey + // Example for how to version a branch key. + + // For testing purposes we will not version this key when we run this example. + // VersionBranchKey.versionBranchKey(branchKeyId); } - - // We test examples to ensure they remain up-to-date. - [Fact] - public void TestAwsKmsHierarchicalKeyringExample() + + public void TestAwsKmsHierarchicalKeyring() { Run(ExampleUtils.ExampleUtils.GetPlaintextStream()); } diff --git a/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/AwsKmsHierarchicalKeyringBranchKeySupplier.cs b/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/AwsKmsHierarchicalKeyringBranchKeySupplier.cs new file mode 100644 index 000000000..77f06510a --- /dev/null +++ b/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/AwsKmsHierarchicalKeyringBranchKeySupplier.cs @@ -0,0 +1,227 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +using Amazon.DynamoDBv2; +using Amazon.KeyManagementService; +using AWS.Cryptography.EncryptionSDK; +using AWS.Cryptography.KeyStore; +using AWS.Cryptography.MaterialProviders; +using Xunit; + +/// +/// This example sets up 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 KMS Key. +/// +/// Establishing a key hierarchy like this has two benefits: +/// +/// First, by caching the branch key material, and only calling +/// KMS to re-establish authentication regularly according to your configured TTL, +/// you limit how often you need to call KMS to protect your data. +/// This is a performance/security tradeoff, where your authentication, audit, and +/// logging from KMS is no longer one-to-one with every encrypt or decrypt call. +/// Additionally KMS Cloudtrail cannot be used to distinguish Encrypt and Decrypt calls, +/// and you cannot restrict who has Encryption rights from who has Decryption rights +/// since they both ONLY need KMS:Decrypt. +/// However, the benefit is that you no longer have to make a +/// network call to KMS for every encrypt or decrypt. +/// +/// Second, this key hierarchy facilitates cryptographic isolation +/// of a tenant's data in a multi-tenant data store. +/// Each tenant can have a unique Branch Key, +/// that is only used to protect the tenant's data. +/// You can either statically configure a single branch key +/// to ensure you are restricting access to a single tenant, +/// or you can implement an interface that selects +/// the Branch Key based on the Encryption Context. +/// +/// This example demonstrates configuring a Hierarchical Keyring +/// with a Branch Key ID Supplier to encrypt and decrypt data for +/// two separate tenants. +/// +/// This example requires access to the DDB Table 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 AwsKmsHierarchicalKeyringBranchKeySupplier +{ + // THESE ARE PUBLIC RESOURCES DO NOT USE IN A PRODUCTION ENVIRONMENT + private static string branchKeyIdA = "43574aa0-de30-424e-bad4-0b06f6e89478"; + private static string branchKeyIdB = "a2f4be37-15ec-489a-bcb5-dcce1f6bfe84"; + private static void Run(MemoryStream plaintext) + { + // Initial KeyStore Setup: This example requires that you have already + // created your KeyStore, and have populated it with two new branch keys. + // See the "Create KeyStore Table Example" and "Create KeyStore Key Example" + // for an example of how to do this. + + // 1. Configure your KeyStore resource. + // This SHOULD be the same configuration that you used + // to initially create and populate your KeyStore. + var kmsConfig = new KMSConfiguration { KmsKeyArn = ExampleUtils.ExampleUtils.GetBranchKeyArn() }; + // 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 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 keystore = new KeyStore(keystoreConfig); + var materialProviders = new MaterialProviders(new MaterialProvidersConfig()); + + // 2. Create a Branch Key ID Supplier. See ExampleBranchKeyIdSupplier in this directory. + var branchKeySupplier = new ExampleBranchKeySupplier(branchKeyIdA, branchKeyIdB); + + + // 3. Create the Hierarchical Keyring, using the Branch Key ID Supplier above. + // With this configuration, the AWS SDK Client ultimately configured will be capable + // of encrypting or decrypting items for either tenant (assuming correct KMS access). + // If you want to restrict the client to only encrypt or decrypt for a single tenant, + // configure this Hierarchical Keyring using `BranchKeyId = tenant1BranchKeyId` instead + // of `BranchKeyIdSupplier = branchKeySupplier`. + var createKeyringInput = new CreateAwsKmsHierarchicalKeyringInput + { + KeyStore = keystore, + // 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. + BranchKeyIdSupplier = branchKeySupplier, + // The value provided to `EntryCapacity` dictates how many branch keys will be held locally + Cache = new CacheType { Default = new DefaultCache{EntryCapacity = 100} }, + // This dictates how often we call back to KMS to authorize use of the branch keys + TtlSeconds = 600 + }; + var keyring = materialProviders.CreateAwsKmsHierarchicalKeyring(createKeyringInput); + + // The Branch Key Id supplier uses the encryption context to determine which branch key id + // will be used to encrypt data. + var encryptionContextA = new Dictionary() + { + // We will encrypt with TenantKeyA + {"tenant", "TenantA"}, + {"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"} + }; + var encryptionContextB = new Dictionary() + { + // We will encrypt with TenantKeyB + {"tenant", "TenantB"}, + {"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"} + }; + + var encryptionSDK = new ESDK(new AwsEncryptionSdkConfig()); + + var encryptInputA = new EncryptInput + { + Plaintext = plaintext, + Keyring = keyring, + // We will encrypt with TenantKeyA + EncryptionContext = encryptionContextA + }; + + var encryptInputB = new EncryptInput{ + Plaintext = plaintext, + Keyring = keyring, + // We will encrypt with TenantKeyB + EncryptionContext = encryptionContextB + }; + + var encryptOutputA = encryptionSDK.Encrypt(encryptInputA); + var encryptOutputB = encryptionSDK.Encrypt(encryptInputB); + + // To attest that TenantKeyB cannot decrypt a message written by TenantKeyA + // let's construct more restrictive hierarchical keyrings. + + var createHierarchicalKeyringA = new CreateAwsKmsHierarchicalKeyringInput() + { + KeyStore = keystore, + BranchKeyId = branchKeyIdA, + Cache = new CacheType { Default = new DefaultCache{EntryCapacity = 100} }, + TtlSeconds = 600 + }; + var hierarchicalKeyringA = materialProviders.CreateAwsKmsHierarchicalKeyring(createHierarchicalKeyringA); + + var createHierarchicalKeyringB = new CreateAwsKmsHierarchicalKeyringInput() + { + KeyStore = keystore, + BranchKeyId = branchKeyIdB, + Cache = new CacheType { Default = new DefaultCache{EntryCapacity = 100} }, + TtlSeconds = 600 + }; + var hierarchicalKeyringB = materialProviders.CreateAwsKmsHierarchicalKeyring(createHierarchicalKeyringB); + + var decryptFailed = false; + try + { + // Try to use keyring for Tenant B to decrypt a message encrypted with Tenant A's key + // Expected to fail. + var decryptInput = new DecryptInput + { + Ciphertext = encryptOutputA.Ciphertext, + Keyring = hierarchicalKeyringB, + }; + encryptionSDK.Decrypt(decryptInput); + } + catch (AwsCryptographicMaterialProvidersException) + { + decryptFailed = true; + } + Assert.True(decryptFailed); + + decryptFailed = false; + try + { + // Try to use keyring for Tenant A to decrypt a message encrypted with Tenant B's key + // Expected to fail. + var decryptInput = new DecryptInput + { + Ciphertext = encryptOutputB.Ciphertext, + Keyring = hierarchicalKeyringA, + }; + encryptionSDK.Decrypt(decryptInput); + } + catch (AwsCryptographicMaterialProvidersException) + { + decryptFailed = true; + } + Assert.True(decryptFailed); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + encryptionSDK.Decrypt(new DecryptInput { + Ciphertext = encryptOutputA.Ciphertext, + Keyring = keyring } + ); + encryptionSDK.Decrypt(new DecryptInput { + Ciphertext = encryptOutputB.Ciphertext, + Keyring = keyring } + ); + + } + + // We test examples to ensure they remain up-to-date. + [Fact] + public void TestAwsKmsHierarchicalKeyringExample() + { + Run(ExampleUtils.ExampleUtils.GetPlaintextStream()); + } + +} diff --git a/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/CreateBranchKeyId.cs b/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/CreateBranchKey.cs similarity index 93% rename from AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/CreateBranchKeyId.cs rename to AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/CreateBranchKey.cs index dd68b6993..a6c552fab 100644 --- a/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/CreateBranchKeyId.cs +++ b/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/CreateBranchKey.cs @@ -5,9 +5,9 @@ using Amazon.KeyManagementService; using AWS.Cryptography.KeyStore; -public class CreateBranchKeyId +public class CreateBranchKey { - public static string createBranchKeyId() + public static string createBranchKey() { // Create an AWS KMS Configuration to use with your KeyStore. // The KMS Configuration MUST have the right access to the resources in the KeyStore. diff --git a/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/VersionBranchKeyId.cs b/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/VersionBranchKey.cs similarity index 92% rename from AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/VersionBranchKeyId.cs rename to AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/VersionBranchKey.cs index ae9c8585d..79df069ca 100644 --- a/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/VersionBranchKeyId.cs +++ b/AwsEncryptionSDK/runtimes/net/Examples/Keyring/AwsKmsHierarchical/VersionBranchKey.cs @@ -5,9 +5,9 @@ using Amazon.KeyManagementService; using AWS.Cryptography.KeyStore; -public class VersionBranchKeyId +public class VersionBranchKey { - public static void versionBranchKeyId(string branchKeyId) + public static void versionBranchKey(string branchKeyId) { // Create an AWS KMS Configuration to use with your KeyStore. // The KMS Configuration MUST have the right access to the resource in the KeyStore. diff --git a/AwsEncryptionSDK/runtimes/net/Examples/README.md b/AwsEncryptionSDK/runtimes/net/Examples/README.md index f2c320e75..3e83979cb 100644 --- a/AwsEncryptionSDK/runtimes/net/Examples/README.md +++ b/AwsEncryptionSDK/runtimes/net/Examples/README.md @@ -36,6 +36,7 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * [How to limit decryption to a single region](./Keyring/AwsKmsMrkDiscoveryKeyringExample.cs) * [How to decrypt with a preferred region but failover to others](./Keyring/AwsKmsMrkDiscoveryMultiKeyringExample.cs) * [How to reproduce the behavior of an AWS KMS master key provider](./Keyring/AwsKmsMultiKeyringExample.cs) + * [How to establish a Key Hierarchy with AWS KMS and Amazon DynamoDB](./Keyring/AwsKmsHierarchical/AwsKmsHierarchicalKeyring.cs) * Using raw wrapping keys * [How to use a raw AES wrapping key](./Keyring/RawAESKeyringExample.cs) * [How to use a raw RSA wrapping key](./Keyring/RawRSAKeyringExample.cs)