From d797eff8da4a7450a4706d449b4ca06de221266b Mon Sep 17 00:00:00 2001 From: Darwin Chowdary <39110935+imabhichow@users.noreply.github.com> Date: Fri, 10 Oct 2025 02:54:03 -0700 Subject: [PATCH 1/7] chore(dotnet): polymorph --- .../Model/AwsCryptographyEncryptionSdkTypes.dfy | 11 +---------- .../net/Generated/AwsEncryptionSdk/TypeConversion.cs | 2 -- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/Model/AwsCryptographyEncryptionSdkTypes.dfy b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/Model/AwsCryptographyEncryptionSdkTypes.dfy index 6ac7e28aa..eb23dbb55 100644 --- a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/Model/AwsCryptographyEncryptionSdkTypes.dfy +++ b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/Model/AwsCryptographyEncryptionSdkTypes.dfy @@ -176,16 +176,7 @@ module {:extern "software.amazon.cryptography.encryptionsdk.internaldafny.types" | CollectionOfErrors(list: seq, nameonly message: string) // The Opaque error, used for native, extern, wrapped or unknown errors | Opaque(obj: object) - // A better Opaque, with a visible string representation. - | OpaqueWithText(obj: object, objMessage : string) - type OpaqueError = e: Error | e.Opaque? || e.OpaqueWithText? witness * - // This dummy subset type is included to make sure Dafny - // always generates a _ExternBase___default.java class. - type DummySubsetType = x: int | IsDummySubsetType(x) witness 1 - predicate method IsDummySubsetType(x: int) { - 0 < x - } - + type OpaqueError = e: Error | e.Opaque? witness * } abstract module AbstractAwsCryptographyEncryptionSdkService { diff --git a/AwsEncryptionSDK/runtimes/net/Generated/AwsEncryptionSdk/TypeConversion.cs b/AwsEncryptionSDK/runtimes/net/Generated/AwsEncryptionSdk/TypeConversion.cs index e4f6b17e6..dc6e24f9d 100644 --- a/AwsEncryptionSDK/runtimes/net/Generated/AwsEncryptionSdk/TypeConversion.cs +++ b/AwsEncryptionSDK/runtimes/net/Generated/AwsEncryptionSdk/TypeConversion.cs @@ -442,8 +442,6 @@ public static System.Exception FromDafny_CommonError(software.amazon.cryptograph new string(dafnyVal.dtor_message.Elements)); case software.amazon.cryptography.encryptionsdk.internaldafny.types.Error_Opaque dafnyVal: return new OpaqueError(dafnyVal._obj); - case software.amazon.cryptography.encryptionsdk.internaldafny.types.Error_OpaqueWithText dafnyVal: - return new OpaqueWithTextError(dafnyVal._obj, dafnyVal._obj.ToString()); default: // The switch MUST be complete for _IError, so `value` MUST NOT be an _IError. (How did you get here?) return new OpaqueError(); From bba72232ee1fbc9d66ae37a41e77ff045e195902 Mon Sep 17 00:00:00 2001 From: Darwin Chowdary <39110935+imabhichow@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:22:37 -0700 Subject: [PATCH 2/7] m --- .../AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy index 77d9ac2c3..3c3067115 100644 --- a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy +++ b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy @@ -62,7 +62,7 @@ module TestRequiredEncryptionContext { algorithmSuiteId := None, frameLength := None )); - + print encryptOutput; expect encryptOutput.Success?; var esdkCiphertext := encryptOutput.value.ciphertext; @@ -170,7 +170,7 @@ module TestRequiredEncryptionContext { algorithmSuiteId := None, frameLength := None )); - + print encryptOutput; expect encryptOutput.Success?; var esdkCiphertext := encryptOutput.value.ciphertext; From 022366f5564a890212d04b26e7816be5cd518ec3 Mon Sep 17 00:00:00 2001 From: Darwin Chowdary <39110935+imabhichow@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:59:14 -0700 Subject: [PATCH 3/7] m --- .../test/TestEncryptDecrypt.dfy | 95 +++++++++++++++++-- 1 file changed, 88 insertions(+), 7 deletions(-) diff --git a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestEncryptDecrypt.dfy b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestEncryptDecrypt.dfy index 767292d45..19f48d4f2 100644 --- a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestEncryptDecrypt.dfy +++ b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestEncryptDecrypt.dfy @@ -17,23 +17,69 @@ module TestEncryptDecrypt { method {:test} TestEncryptDecrypt() { var kmsKey := Fixtures.keyArn; + print "=== TestEncryptDecrypt Starting ===\n"; + print "Using KMS Key: ", kmsKey, "\n"; + // The string "asdf" as bytes var asdf := [ 97, 115, 100, 102 ]; + print "Plaintext data: ", asdf, "\n"; + print "Creating ESDK config...\n"; var defaultConfig := ESDK.DefaultAwsEncryptionSdkConfig(); - var esdk :- expect ESDK.ESDK(config := defaultConfig); - var mpl :- expect MaterialProviders.MaterialProviders(); - var clientSupplier :- expect mpl.CreateDefaultClientSupplier(mplTypes.CreateDefaultClientSupplierInput); - var kmsClient :- expect clientSupplier.GetClient(mplTypes.GetClientInput(region := "us-west-2")); + + print "Initializing ESDK...\n"; + var esdkResult := ESDK.ESDK(config := defaultConfig); + if esdkResult.Failure? { + print "ESDK initialization failed: ", esdkResult.error, "\n"; + expect false; + } + var esdk := esdkResult.value; + print "ESDK initialized successfully\n"; + + print "Initializing MaterialProviders...\n"; + var mplResult := MaterialProviders.MaterialProviders(); + if mplResult.Failure? { + print "MaterialProviders initialization failed: ", mplResult.error, "\n"; + expect false; + } + var mpl := mplResult.value; + print "MaterialProviders initialized successfully\n"; + + print "Creating client supplier...\n"; + var clientSupplierResult := mpl.CreateDefaultClientSupplier(mplTypes.CreateDefaultClientSupplierInput); + if clientSupplierResult.Failure? { + print "Client supplier creation failed: ", clientSupplierResult.error, "\n"; + expect false; + } + var clientSupplier := clientSupplierResult.value; + print "Client supplier created successfully\n"; + + print "Getting KMS client for region us-west-2...\n"; + var kmsClientResult := clientSupplier.GetClient(mplTypes.GetClientInput(region := "us-west-2")); + if kmsClientResult.Failure? { + print "KMS client creation failed: ", kmsClientResult.error, "\n"; + expect false; + } + var kmsClient := kmsClientResult.value; + print "KMS client created successfully\n"; - var kmsKeyring :- expect mpl.CreateAwsKmsKeyring( + print "Creating KMS keyring...\n"; + var kmsKeyringResult := mpl.CreateAwsKmsKeyring( mplTypes.CreateAwsKmsKeyringInput( kmsKeyId := kmsKey, kmsClient := kmsClient, grantTokens := None ) ); + + if kmsKeyringResult.Failure? { + print "KMS keyring creation failed: ", kmsKeyringResult.error, "\n"; + expect false; + } + var kmsKeyring := kmsKeyringResult.value; + print "KMS keyring created successfully\n"; + print "Starting encryption...\n"; var encryptOutput := esdk.Encrypt(Types.EncryptInput( plaintext := asdf, encryptionContext := None, @@ -43,9 +89,29 @@ module TestEncryptDecrypt { frameLength := None )); - expect encryptOutput.Success?; + if encryptOutput.Failure? { + print "Encryption failed with error: ", encryptOutput.error, "\n"; + print "Error details: "; + match encryptOutput.error { + case AwsCryptographyMaterialProviders(mplError) => { + print "MaterialProviders error: ", mplError, "\n"; + match mplError { + case AwsCryptographicMaterialProvidersException(message) => { + print "Exception message: ", message, "\n"; + } + case _ => print "Other MPL error type\n"; + } + } + case _ => print "Non-MPL error\n"; + } + expect false; + } + + print "Encryption successful\n"; var esdkCiphertext := encryptOutput.value.ciphertext; + print "Ciphertext length: ", |esdkCiphertext|, " bytes\n"; + print "Starting decryption...\n"; var decryptOutput := esdk.Decrypt(Types.DecryptInput( ciphertext := esdkCiphertext, materialsManager := None, @@ -53,9 +119,24 @@ module TestEncryptDecrypt { encryptionContext := None )); - expect decryptOutput.Success?; + if decryptOutput.Failure? { + print "Decryption failed with error: ", decryptOutput.error, "\n"; + expect false; + } + + print "Decryption successful\n"; var cycledPlaintext := decryptOutput.value.plaintext; + print "Decrypted plaintext: ", cycledPlaintext, "\n"; + if cycledPlaintext == asdf { + print "Plaintext matches original - TEST PASSED\n"; + } else { + print "Plaintext mismatch - TEST FAILED\n"; + print "Expected: ", asdf, "\n"; + print "Got: ", cycledPlaintext, "\n"; + } + expect cycledPlaintext == asdf; + print "=== TestEncryptDecrypt Completed Successfully ===\n"; } } From 2a83d697b140c900cab46e9ef6c2c4eb479411ec Mon Sep 17 00:00:00 2001 From: Darwin Chowdary <39110935+imabhichow@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:03:45 -0700 Subject: [PATCH 4/7] m --- .../test/TestRequiredEncryptionContext.dfy | 56 ++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy index 3c3067115..723ee0a08 100644 --- a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy +++ b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy @@ -1133,20 +1133,60 @@ module TestRequiredEncryptionContext { ensures output.ValidState() && fresh(output) && fresh(output.History) && fresh(output.Modifies) { var kmsKey := Fixtures.keyArn; + print "=== GetKmsKeyring ===\n"; + print "Using KMS Key ARN: ", kmsKey, "\n"; + var defaultConfig := ESDK.DefaultAwsEncryptionSdkConfig(); - var esdk :- expect ESDK.ESDK(config := defaultConfig); - var mpl :- expect MaterialProviders.MaterialProviders(); - var clientSupplier :- expect mpl.CreateDefaultClientSupplier(mplTypes.CreateDefaultClientSupplierInput); - var kmsClient :- expect clientSupplier.GetClient(mplTypes.GetClientInput(region := "us-west-2")); - - output :- expect mpl.CreateAwsKmsKeyring( + var esdkResult := ESDK.ESDK(config := defaultConfig); + if esdkResult.Failure? { + print "ESDK creation failed: ", esdkResult.error, "\n"; + expect false; + } + var esdk := esdkResult.value; + print "ESDK created successfully\n"; + + var mplResult := MaterialProviders.MaterialProviders(); + if mplResult.Failure? { + print "MaterialProviders creation failed: ", mplResult.error, "\n"; + expect false; + } + var mpl := mplResult.value; + print "MaterialProviders created successfully\n"; + + var clientSupplierResult := mpl.CreateDefaultClientSupplier(mplTypes.CreateDefaultClientSupplierInput); + if clientSupplierResult.Failure? { + print "Client supplier creation failed: ", clientSupplierResult.error, "\n"; + expect false; + } + var clientSupplier := clientSupplierResult.value; + print "Client supplier created successfully\n"; + + var kmsClientResult := clientSupplier.GetClient(mplTypes.GetClientInput(region := "us-west-2")); + if kmsClientResult.Failure? { + print "KMS client creation failed: ", kmsClientResult.error, "\n"; + expect false; + } + var kmsClient := kmsClientResult.value; + print "KMS client created successfully\n"; + + var keyringResult := mpl.CreateAwsKmsKeyring( mplTypes.CreateAwsKmsKeyringInput( kmsKeyId := kmsKey, kmsClient := kmsClient, grantTokens := None ) ); - + + if keyringResult.Failure? { + print "KMS keyring creation failed: ", keyringResult.error, "\n"; + print "This is likely due to account access issues\n"; + print "Expected account: 370957321024 (from assumed role)\n"; + print "Key account: Check if key ARN matches assumed role account\n"; + expect false; + } + + output := keyringResult.value; + print "KMS keyring created successfully\n"; } -} \ No newline at end of file +} From 6a73fecb936b16e81705a089a46373ccfa30d625 Mon Sep 17 00:00:00 2001 From: Darwin Chowdary <39110935+imabhichow@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:29:05 -0700 Subject: [PATCH 5/7] m --- .../AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy index 723ee0a08..aae728502 100644 --- a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy +++ b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy @@ -58,7 +58,7 @@ module TestRequiredEncryptionContext { plaintext := asdf, encryptionContext := Some(encryptionContext), materialsManager := None, - keyring := Some(multiKeyring), + keyring := Some(kmsKeyring), algorithmSuiteId := None, frameLength := None )); @@ -70,7 +70,7 @@ module TestRequiredEncryptionContext { var decryptOutput := esdk.Decrypt(Types.DecryptInput( ciphertext := esdkCiphertext, materialsManager := None, - keyring := Some(rsaKeyring), + keyring := Some(kmsKeyring), encryptionContext := Some(encryptionContext) )); From 139f6b4bdf8509ddcf1d8c1452ca5103311db9cc Mon Sep 17 00:00:00 2001 From: Darwin Chowdary <39110935+imabhichow@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:40:28 -0700 Subject: [PATCH 6/7] m --- .../AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy index aae728502..37277a5af 100644 --- a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy +++ b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy @@ -58,7 +58,7 @@ module TestRequiredEncryptionContext { plaintext := asdf, encryptionContext := Some(encryptionContext), materialsManager := None, - keyring := Some(kmsKeyring), + keyring := Some(hKeyring), algorithmSuiteId := None, frameLength := None )); @@ -70,7 +70,7 @@ module TestRequiredEncryptionContext { var decryptOutput := esdk.Decrypt(Types.DecryptInput( ciphertext := esdkCiphertext, materialsManager := None, - keyring := Some(kmsKeyring), + keyring := Some(hKeyring), encryptionContext := Some(encryptionContext) )); From c3b9ff54b6e4653d1c7cc9d6f31d970d4dcb7388 Mon Sep 17 00:00:00 2001 From: Darwin Chowdary <39110935+imabhichow@users.noreply.github.com> Date: Fri, 10 Oct 2025 12:04:33 -0700 Subject: [PATCH 7/7] EC Example --- .github/workflows/library_net_tests.yml | 34 ++- .../test/TestRequiredEncryptionContext.dfy | 13 +- ...redEncryptionContextMultiKeyringExample.cs | 288 ++++++++++++++++++ 3 files changed, 318 insertions(+), 17 deletions(-) create mode 100644 AwsEncryptionSDK/runtimes/net/Examples/CryptographicMaterialsManager/RequiredEncryptionContext/RequiredEncryptionContextMultiKeyringExample.cs diff --git a/.github/workflows/library_net_tests.yml b/.github/workflows/library_net_tests.yml index 0accb6758..9015a446c 100644 --- a/.github/workflows/library_net_tests.yml +++ b/.github/workflows/library_net_tests.yml @@ -88,22 +88,6 @@ jobs: CORES=$(node -e 'console.log(os.cpus().length)') make transpile_net CORES=$CORES - - name: Test .NET Framework net48 - working-directory: ${{ matrix.library }} - shell: bash - run: | - make test_net FRAMEWORK=net48 - - - name: Test .NET net6.0 - working-directory: ${{ matrix.library }} - shell: bash - run: | - if [ "$RUNNER_OS" == "macOS" ]; then - make test_net_mac_intel FRAMEWORK=net6.0 - else - make test_net FRAMEWORK=net6.0 - fi - - name: Test Examples on .NET Framework net48 working-directory: ${{ matrix.library }} if: matrix.os == 'windows-latest' @@ -127,6 +111,24 @@ jobs: runtimes/net/Examples \ --framework net6.0 fi + + - name: Test .NET Framework net48 + working-directory: ${{ matrix.library }} + shell: bash + run: | + make test_net FRAMEWORK=net48 + + - name: Test .NET net6.0 + working-directory: ${{ matrix.library }} + shell: bash + run: | + if [ "$RUNNER_OS" == "macOS" ]; then + make test_net_mac_intel FRAMEWORK=net6.0 + else + make test_net FRAMEWORK=net6.0 + fi + + testVectors: strategy: fail-fast: false diff --git a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy index 37277a5af..5648a8e6b 100644 --- a/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy +++ b/AwsEncryptionSDK/dafny/AwsEncryptionSdk/test/TestRequiredEncryptionContext.dfy @@ -1065,7 +1065,6 @@ module TestRequiredEncryptionContext { returns (output: mplTypes.IKeyring) ensures output.ValidState() && fresh(output) && fresh(output.History) && fresh(output.Modifies) { - var branchKeyId := BRANCH_KEY_ID; var ttl : mplTypes.PositiveLong := (1 * 60000) * 10; var mpl :- expect MaterialProviders.MaterialProviders(); @@ -1085,6 +1084,16 @@ module TestRequiredEncryptionContext { var keyStore :- expect KeyStore.KeyStore(keyStoreConfig); + // Try to create a new branch key instead of using the hardcoded one + print "Creating new branch key for hierarchical keyring...\n"; + var createKeyResult := keyStore.CreateKey(KeyStoreTypes.CreateKeyInput()); + var branchKeyId := if createKeyResult.Success? then + createKeyResult.value.branchKeyIdentifier + else + BRANCH_KEY_ID; // fallback to hardcoded ID if creation fails + + print "Using branch key: ", branchKeyId, "\n"; + output :- expect mpl.CreateAwsKmsHierarchicalKeyring( mplTypes.CreateAwsKmsHierarchicalKeyringInput( branchKeyId := Some(branchKeyId), @@ -1093,6 +1102,8 @@ module TestRequiredEncryptionContext { ttlSeconds := ttl, cache := None )); + + print "Hierarchical keyring created successfully\n"; } method GetRsaKeyring() diff --git a/AwsEncryptionSDK/runtimes/net/Examples/CryptographicMaterialsManager/RequiredEncryptionContext/RequiredEncryptionContextMultiKeyringExample.cs b/AwsEncryptionSDK/runtimes/net/Examples/CryptographicMaterialsManager/RequiredEncryptionContext/RequiredEncryptionContextMultiKeyringExample.cs new file mode 100644 index 000000000..456ceba11 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/net/Examples/CryptographicMaterialsManager/RequiredEncryptionContext/RequiredEncryptionContextMultiKeyringExample.cs @@ -0,0 +1,288 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.IO; +using Amazon.DynamoDBv2; +using Amazon.KeyManagementService; +using AWS.Cryptography.EncryptionSDK; +using AWS.Cryptography.KeyStore; +using AWS.Cryptography.MaterialProviders; +using Xunit; +using static ExampleUtils.ExampleUtils; + +/// Demonstrate an encrypt/decrypt cycle using a Required Encryption Context CMM with a Multi keyring. +/// This example shows how to use Required Encryption Context with multiple keyrings (KMS, Raw AES, Raw RSA, and Hierarchical), +/// where encryption context keys are not stored on the message but are required for decryption. +/// The example demonstrates that any of the individual keyrings can be used to decrypt the message +/// as long as the required encryption context is provided. +public class RequiredEncryptionContextMultiKeyringExample +{ + private static void Run(MemoryStream plaintext, string keyArn) + { + // Create your encryption context. + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#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"} + }; + + // Create your required encryption context keys. + // These keys MUST be in your encryption context. + // These keys and their corresponding values WILL NOT be stored on the message but will be used + // for authentication during decryption. + var requiredEncryptionContextKeys = new List() + { + "encryption", + "but adds", + "the data you are handling" + }; + + // Instantiate the Material Providers and the AWS Encryption SDK + var materialProviders = new MaterialProviders(new MaterialProvidersConfig()); + var encryptionSdk = new ESDK(new AwsEncryptionSdkConfig()); + + // Create a KMS keyring to use as the generator. + var createKmsKeyringInput = new CreateAwsKmsKeyringInput + { + KmsClient = new AmazonKeyManagementServiceClient(), + KmsKeyId = keyArn + }; + var kmsKeyring = materialProviders.CreateAwsKmsKeyring(createKmsKeyringInput); + + // Create a raw AES keyring to additionally encrypt under + var rawAESKeyring = GetRawAESKeyring(materialProviders); + + // Create a raw RSA keyring + var rawRSAKeyring = GetRawRSAKeyring(materialProviders); + + // Create a hierarchical keyring + var hierarchicalKeyring = GetHierarchicalKeyring(materialProviders); + + // Create a MultiKeyring that consists of all the previously created Keyrings. + // When using this MultiKeyring to encrypt data, any of the individual keyrings + // may be used to decrypt the data. + var createMultiKeyringInput = new CreateMultiKeyringInput + { + Generator = hierarchicalKeyring, + ChildKeyrings = new List() {kmsKeyring, rawRSAKeyring, rawAESKeyring} + }; + var multiKeyring = materialProviders.CreateMultiKeyring(createMultiKeyringInput); + + // Create a required encryption context CMM using the multi keyring. + var cmm = GetRequiredEncryptionContextCMM(materialProviders, requiredEncryptionContextKeys, multiKeyring); + + // Encrypt your plaintext data. NOTE: the keys "encryption", "but adds", and "the data you are handling" + // WILL NOT be stored in the message header, but "is not" and "that can help you" WILL be stored. + var encryptInput = new EncryptInput + { + Plaintext = plaintext, + MaterialsManager = cmm, + EncryptionContext = encryptionContext + }; + var encryptOutput = encryptionSdk.Encrypt(encryptInput); + var ciphertext = encryptOutput.Ciphertext; + + // Demonstrate that the ciphertext and plaintext are different. + Assert.NotEqual(ciphertext.ToArray(), plaintext.ToArray()); + + // Create the reproduced encryption context that contains ONLY the encryption context that + // was NOT stored on the message but is required for authentication. + var reproducedEncryptionContext = new Dictionary() + { + {"encryption", "context"}, + {"but adds", "useful metadata"}, + {"the data you are handling", "is what you think it is"} + }; + + // Demonstrate that you can decrypt using the KMS keyring directly + // as long as you provide the required encryption context. + var kmsDecryptInput = new DecryptInput + { + Ciphertext = ciphertext, + Keyring = kmsKeyring, + EncryptionContext = reproducedEncryptionContext + }; + var kmsDecryptOutput = encryptionSdk.Decrypt(kmsDecryptInput); + + // Verify the decrypted plaintext is identical to the original plaintext. + VerifyDecryptedIsPlaintext(kmsDecryptOutput, plaintext); + + // Demonstrate that you can also decrypt using the raw AES keyring directly + // as long as you provide the required encryption context. + var aesDecryptInput = new DecryptInput + { + Ciphertext = ciphertext, + Keyring = rawAESKeyring, + EncryptionContext = reproducedEncryptionContext + }; + var aesDecryptOutput = encryptionSdk.Decrypt(aesDecryptInput); + + // Verify the decrypted plaintext is identical to the original plaintext. + VerifyDecryptedIsPlaintext(aesDecryptOutput, plaintext); + + // Demonstrate that you can also decrypt using the raw RSA keyring directly + // as long as you provide the required encryption context. + var rsaDecryptInput = new DecryptInput + { + Ciphertext = ciphertext, + Keyring = rawRSAKeyring, + EncryptionContext = reproducedEncryptionContext + }; + var rsaDecryptOutput = encryptionSdk.Decrypt(rsaDecryptInput); + + // Verify the decrypted plaintext is identical to the original plaintext. + VerifyDecryptedIsPlaintext(rsaDecryptOutput, plaintext); + + // Demonstrate that you can also decrypt using the hierarchical keyring directly + // as long as you provide the required encryption context. + var hierarchicalDecryptInput = new DecryptInput + { + Ciphertext = ciphertext, + Keyring = hierarchicalKeyring, + EncryptionContext = reproducedEncryptionContext + }; + var hierarchicalDecryptOutput = encryptionSdk.Decrypt(hierarchicalDecryptInput); + + // Verify the decrypted plaintext is identical to the original plaintext. + VerifyDecryptedIsPlaintext(hierarchicalDecryptOutput, plaintext); + + // Demonstrate that you can also decrypt using the multi keyring directly + // as long as you provide the required encryption context. + var multiKeyringDecryptInput = new DecryptInput + { + Ciphertext = ciphertext, + Keyring = multiKeyring, + EncryptionContext = reproducedEncryptionContext + }; + var multiKeyringDecryptOutput = encryptionSdk.Decrypt(multiKeyringDecryptInput); + + // Verify the decrypted plaintext is identical to the original plaintext. + VerifyDecryptedIsPlaintext(multiKeyringDecryptOutput, plaintext); + + // Demonstrate that decryption fails without the required encryption context + var decryptFailed = false; + var failDecryptInput = new DecryptInput + { + Ciphertext = ciphertext, + Keyring = kmsKeyring, + // Not providing the required encryption context + }; + try + { + encryptionSdk.Decrypt(failDecryptInput); + } + catch (Exception) + { + decryptFailed = true; + } + + Assert.True(decryptFailed, "Decryption should fail without required encryption context"); + + // Demonstrate that decryption fails with incorrect encryption context values + decryptFailed = false; + var incorrectEncryptionContext = new Dictionary() + { + {"encryption", "wrong_value"}, + {"but adds", "useful metadata"}, + {"the data you are handling", "is what you think it is"} + }; + + var failDecryptInput2 = new DecryptInput + { + Ciphertext = ciphertext, + Keyring = rawAESKeyring, + EncryptionContext = incorrectEncryptionContext + }; + try + { + encryptionSdk.Decrypt(failDecryptInput2); + } + catch (Exception) + { + decryptFailed = true; + } + + Assert.True(decryptFailed, "Decryption should fail with incorrect encryption context values"); + } + + private static void VerifyDecryptedIsPlaintext(DecryptOutput decryptOutput, MemoryStream plaintext) + { + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + var decrypted = decryptOutput.Plaintext; + Assert.Equal(decrypted.ToArray(), plaintext.ToArray()); + } + + // Helper method to create a Raw RSA keyring for the example. + private static IKeyring GetRawRSAKeyring(MaterialProviders materialProviders) + { + // Generate RSA key pair for the example + RSAEncryption.RSA.GenerateKeyPairBytes(2048, out var publicKeyBytes, out var privateKeyBytes); + var publicKey = new MemoryStream(publicKeyBytes); + var privateKey = new MemoryStream(privateKeyBytes); + + // The key namespace and key name are defined by you + // and are used by the raw RSA keyring to determine + // whether it should attempt to decrypt an encrypted data key. + var keyNamespace = "Some managed raw keys"; + var keyName = "My 2048-bit RSA wrapping key"; + + // Create the RSA keyring + var createRawRsaKeyringInput = new CreateRawRsaKeyringInput + { + KeyNamespace = keyNamespace, + KeyName = keyName, + PaddingScheme = PaddingScheme.OAEP_SHA512_MGF1, + PublicKey = publicKey, + PrivateKey = privateKey + }; + return materialProviders.CreateRawRsaKeyring(createRawRsaKeyringInput); + } + + // Helper method to create a Hierarchical keyring for the example. + private static IKeyring GetHierarchicalKeyring(MaterialProviders materialProviders) + { + // THESE ARE PUBLIC RESOURCES DO NOT USE IN A PRODUCTION ENVIRONMENT + var branchKeyId = "43574aa0-de30-424e-bad4-0b06f6e89478"; + + var kmsConfig = new KMSConfiguration { KmsKeyArn = 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 = GetKeyStoreName(), + DdbClient = new AmazonDynamoDBClient(), + LogicalKeyStoreName = GetLogicalKeyStoreName() + }; + var keystore = new KeyStore(keystoreConfig); + + // Create the Hierarchical keyring + var createKeyringInput = new CreateAwsKmsHierarchicalKeyringInput + { + 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 + }; + return materialProviders.CreateAwsKmsHierarchicalKeyring(createKeyringInput); + } + + // We test examples to ensure they remain up-to-date. + [Fact] + public void TestRequiredEncryptionContextMultiKeyringExample() + { + Run(GetPlaintextStream(), GetDefaultRegionKmsKeyArn()); + } +}