From c3ba5e314e1118efb1fd663c85e3d56578622b91 Mon Sep 17 00:00:00 2001 From: Rishav karanjit Date: Wed, 8 Jan 2025 16:00:35 -0800 Subject: [PATCH] chore(examples): Add examples for Go with CI (#733) --- .github/workflows/library_go_tests.yml | 9 +- AwsEncryptionSDK/Makefile | 15 + .../runtimes/go/examples/README.md | 90 +++++ .../clientsupplier/clientSupplierExample.go | 163 +++++++++ .../regionalroleclientsupplier.go | 57 ++++ .../regionalroleclientsupplierconfig.go | 22 ++ .../requiredencryptioncontext.go | 160 +++++++++ .../signingonlyexample.go | 137 ++++++++ .../signingsuiteonlycmm.go | 77 +++++ AwsEncryptionSDK/runtimes/go/examples/go.mod | 43 +++ AwsEncryptionSDK/runtimes/go/examples/go.sum | 48 +++ .../awskmsdiscoverykeyring.go | 189 +++++++++++ .../awskmsdiscoverymultikeyring.go | 169 +++++++++ .../awskmshierarchicalkeyring.go | 296 ++++++++++++++++ .../branchkeysupplier.go | 45 +++ .../createbranchkeyid.go | 45 +++ .../sharedcacheacrosshierarchicalkeyring.go | 228 +++++++++++++ .../versionbranchkeyid.go | 93 +++++ .../keyring/awskmskeyring/awskmskeyring.go | 122 +++++++ .../awskmsmrkdiscoverykeyring.go | 172 ++++++++++ .../awskmsmrkdiscoverymultikeyring.go | 164 +++++++++ .../awskmsmrkkeyring/awskmsmrkkeyring.go | 152 +++++++++ .../awskmsmrkmultikeyring.go | 153 +++++++++ .../awskmsmultikeyring/awskmsmultikeyring.go | 172 ++++++++++ .../awskmsrsakeyring/awskmsrsakeyring.go | 127 +++++++ .../ecdh/awskmsecdhdiscoverykeyring.go | 219 ++++++++++++ .../keyring/ecdh/awskmsecdhkeyring.go | 193 +++++++++++ .../keyring/ecdh/ephemeralrawecdhkeyring.go | 137 ++++++++ .../ecdh/publickeyrawdiscoveryecdhkeyring.go | 218 ++++++++++++ .../examples/keyring/ecdh/rawecdhkeyring.go | 194 +++++++++++ .../keyring/multikeyring/multikeyring.go | 233 +++++++++++++ .../keyring/rawaeskeyring/rawaeskeyring.go | 138 ++++++++ .../keyring/rawrsakeyring/rawrasakeyring.go | 178 ++++++++++ AwsEncryptionSDK/runtimes/go/examples/main.go | 161 +++++++++ .../go/examples/misc/commitmentpolicy.go | 149 ++++++++ .../misc/limitencrypteddatakeysexample.go | 176 ++++++++++ .../misc/setencryptionalgorithmsuite.go | 171 ++++++++++ .../go/examples/utils/exampleUtils.go | 321 ++++++++++++++++++ 38 files changed, 5435 insertions(+), 1 deletion(-) create mode 100644 AwsEncryptionSDK/runtimes/go/examples/README.md create mode 100644 AwsEncryptionSDK/runtimes/go/examples/clientsupplier/clientSupplierExample.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/clientsupplier/regionalroleclientsupplier.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/clientsupplier/regionalroleclientsupplierconfig.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/cryptographicmaterialsmanager/requiredencryptioncontext/requiredencryptioncontext.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/cryptographicmaterialsmanager/restrictalgorithmsuite/signingonlyexample.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/cryptographicmaterialsmanager/restrictalgorithmsuite/signingsuiteonlycmm.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/go.mod create mode 100644 AwsEncryptionSDK/runtimes/go/examples/go.sum create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsdiscoverykeyring/awskmsdiscoverykeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsdiscoverymultikeyring/awskmsdiscoverymultikeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/awskmshierarchicalkeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/branchkeysupplier.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/createbranchkeyid.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/sharedcacheacrosshierarchicalkeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/versionbranchkeyid.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmskeyring/awskmskeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkdiscoverykeyring/awskmsmrkdiscoverykeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkdiscoverymultikeyring/awskmsmrkdiscoverymultikeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkkeyring/awskmsmrkkeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkmultikeyring/awskmsmrkmultikeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmultikeyring/awskmsmultikeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsrsakeyring/awskmsrsakeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/awskmsecdhdiscoverykeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/awskmsecdhkeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/ephemeralrawecdhkeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/publickeyrawdiscoveryecdhkeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/rawecdhkeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/multikeyring/multikeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/rawaeskeyring/rawaeskeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/keyring/rawrsakeyring/rawrasakeyring.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/main.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/misc/commitmentpolicy.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/misc/limitencrypteddatakeysexample.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/misc/setencryptionalgorithmsuite.go create mode 100644 AwsEncryptionSDK/runtimes/go/examples/utils/exampleUtils.go diff --git a/.github/workflows/library_go_tests.yml b/.github/workflows/library_go_tests.yml index 8f31e011f..1d84cd991 100644 --- a/.github/workflows/library_go_tests.yml +++ b/.github/workflows/library_go_tests.yml @@ -95,4 +95,11 @@ jobs: working-directory: ${{ matrix.library }} shell: bash run: | - make test_go \ No newline at end of file + make test_go + + - name: Test Examples for Go + if: matrix.library == 'AwsEncryptionSDK' + working-directory: ${{ matrix.library }}/runtimes/go/examples + shell: bash + run: | + go run main.go \ No newline at end of file diff --git a/AwsEncryptionSDK/Makefile b/AwsEncryptionSDK/Makefile index a7b4437cd..ce35387ba 100644 --- a/AwsEncryptionSDK/Makefile +++ b/AwsEncryptionSDK/Makefile @@ -92,3 +92,18 @@ TYPES_FILE_WITHOUT_EXTERN_STRING="module AwsCryptographyEncryptionSdkTypes" INDEX_FILE_PATH=dafny/AwsEncryptionSdk/src/Index.dfy INDEX_FILE_WITH_EXTERN_STRING="module {:extern \"software.amazon.cryptography.encryptionsdk.internaldafny\" } ESDK refines AbstractAwsCryptographyEncryptionSdkService {" INDEX_FILE_WITHOUT_EXTERN_STRING="module ESDK refines AbstractAwsCryptographyEncryptionSdkService {" + +# Target to restore all directories in a list +# TODO: remove this once we don't copy all of the directories into implementation and test. This is done by make file target _mv_polymorph_go in smithy-dafny. +RESTORE_DIRS := examples +_polymorph_go: restore_directories +restore_directories: + @for dir in $(RESTORE_DIRS); do \ + if [ -d "runtimes/go/ImplementationFromDafny-go/$$dir" ]; then \ + cp -Rf runtimes/go/ImplementationFromDafny-go/$$dir runtimes/go/; \ + rm -rf runtimes/go/ImplementationFromDafny-go/$$dir; \ + rm -rf runtimes/go/TestsFromDafny-go/$$dir; \ + else \ + echo "Directory $$dir not found"; \ + fi \ + done diff --git a/AwsEncryptionSDK/runtimes/go/examples/README.md b/AwsEncryptionSDK/runtimes/go/examples/README.md new file mode 100644 index 000000000..df7de5e1b --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/README.md @@ -0,0 +1,90 @@ +# AWS Encryption SDK for Go Examples + +This section features examples that show you +how to use the AWS Encryption SDK. +We demonstrate how to use the encryption and decryption APIs +and how to set up some common configuration patterns. + +## APIs + +The AWS Encryption SDK provides two high-level APIs: +one-step APIs that process the entire operation in memory +and streaming APIs. + +You can find examples that demonstrate these APIs +in the [`examples/`](./) directory. + +* [How to encrypt and decrypt](./keyring/awskmskeyring/awskmskeyring.go) +* [How to change the algorithm suite](./misc/setencryptionalgorithmsuite.go) +* [How to set the commitment policy](./misc/commitmentpolicy.go) +* [How to limit the number of encrypted data keys (EDKs)](./misc/limitencrypteddatakeysexample.go) + +## Configuration + +To use the encryption and decryption APIs, +you need to describe how you want the library to protect your data keys. +You can do this by configuring +[keyrings](#keyrings) or [cryptographic materials managers](#cryptographic-materials-managers). +These examples will show you how to use the configuration tools that we include for you +and how to create some of your own. +We start with AWS KMS examples, then show how to use other wrapping keys. + +* Using AWS Key Management Service (AWS KMS) + * [How to use one AWS KMS key](./keyring/awskmskeyring/awskmskeyring.go) + * [How to use multiple AWS KMS keys in different regions](./keyring/awskmsmrkmultikeyring/awskmsmrkmultikeyring.go) + * [How to decrypt when you don't know the AWS KMS key](./keyring/awskmsdiscoverykeyring/awskmsdiscoverykeyring.go) + * [How to limit decryption to a single region](./keyring/awskmsmrkdiscoverykeyring/awskmsmrkdiscoverykeyring.go) + * [How to decrypt with a preferred region but failover to others](./keyring/awskmsmrkdiscoverykeyring/awskmsmrkdiscoverykeyring.go) + * [How to reproduce the behavior of an AWS KMS master key provider](./keyring/awskmsmultikeyring/awskmsmultikeyring.go) +* Using raw wrapping keys + * [How to use a raw AES wrapping key](./keyring/rawaeskeyring/rawaeskeyring.go) + * [How to use a raw RSA wrapping key](./keyring/rawrsakeyring/rawrasakeyring.go) +* Combining wrapping keys + * [How to combine AWS KMS with an offline escrow key](./keyring/multikeyring/multikeyring.go) +* How to restrict algorithm suites + * [with a custom cryptographic materials manager](./cryptographicmaterialsmanager/restrictalgorithmsuite/signingsuiteonlycmm.go) + +### Keyrings + +Keyrings are the most common way for you to configure the AWS Encryption SDK. +They determine how the AWS Encryption SDK protects your data. +You can find these examples in [`examples/keyring`](./keyring). + +### Cryptographic Materials Managers + +Keyrings define how your data keys are protected, +but there is more going on here than just protecting data keys. + +Cryptographic materials managers give you higher-level controls +over how the AWS Encryption SDK protects your data. +This can include things like +enforcing the use of certain algorithm suites or encryption context settings, +reusing data keys across messages, +or changing how you interact with keyrings. +You can find these examples in +[`examples/cryptographic_materials_manager`](./cryptographicmaterialsmanager). + +### Client Supplier + +The AWS Encryption SDK creates AWS KMS clients when interacting with AWS KMS. +In case the default AWS KMS client configuration doesn't suit your needs, +you can configure clients by defining a custom Client Supplier. +For example, your Client Supplier could tune +the retry and timeout settings on the client, or use different credentials +based on which region is being called. In our +[regional_role_client_supplier](./clientsupplier/regionalroleclientsupplier.go) +example, we show how you can build a custom Client Supplier which +creates clients by assuming different IAM roles for different regions. + +# Writing Examples + +If you want to contribute a new example, that's awesome! +To make sure that your example runs in our CI, +please make sure that it meets the following requirements: + +1. The example MUST be a distinct subdirectory or file in the [`examples/`](./) directory. +1. The example MAY be nested arbitrarily deeply. +1. Each example file MUST contain exactly one example. +1. Each example filename MUST be descriptive. +1. Each example file MUST contain validation checks to check for expected returned values and MUST panic is the returned value is no expected. +1. Each example MUST also be called inside the `main` function of [main.go](./main.go). \ No newline at end of file diff --git a/AwsEncryptionSDK/runtimes/go/examples/clientsupplier/clientSupplierExample.go b/AwsEncryptionSDK/runtimes/go/examples/clientsupplier/clientSupplierExample.go new file mode 100644 index 000000000..d06cc467e --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/clientsupplier/clientSupplierExample.go @@ -0,0 +1,163 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + This example sets up an MRK multi-keyring and an MRK discovery + multi-keyring using a custom client supplier. + A custom client supplier grants users access to more granular + configuration aspects of their authentication details and KMS + client. In this example, we create a simple custom client supplier + that authenticates with a different IAM role based on the + region of the KMS key. + + This example creates a MRK multi-keyring configured with a custom + client supplier using a single MRK and encrypts the example_data with it. + Then, it creates a MRK discovery multi-keyring to decrypt the ciphertext. +*/ + +package clientsupplier + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" +) + +func ClientSupplierExample(exampleText, mrkKeyIdEncrypt, awsAccountId string, awsRegions []string) { + // Step 1: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 2: Create your encryption context. + // Remember that your encryption context is NOT SECRET. + // For more information, see + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 3: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 4: Create keyrings + // First Keyring: Create the multi-keyring using our custom client supplier + // defined in the RegionalRoleClientSupplier class in this directory. + // Note: RegionalRoleClientSupplier will internally use the key_arn's region + // to retrieve the correct IAM role. + awsKmsMrkKeyringMultiInput := mpltypes.CreateAwsKmsMrkMultiKeyringInput{ + ClientSupplier: &RegionalRoleClientSupplier{}, + Generator: &mrkKeyIdEncrypt, + } + awsKmsMrkMultiKeyring, err := matProv.CreateAwsKmsMrkMultiKeyring(context.Background(), awsKmsMrkKeyringMultiInput) + if err != nil { + panic(err) + } + // Second Keyring: Create a MRK discovery multi-keyring with a custom client supplier. + // A discovery MRK multi-keyring will be composed of + // multiple discovery MRK keyrings, one for each region. + // Each component keyring has its own KMS client in a particular region. + // When we provide a client supplier to the multi-keyring, all component + // keyrings will use that client supplier configuration. + // In our tests, we make `mrk_key_id_encrypt` an MRK with a replica, and + // provide only the replica region in our discovery filter. + discoveryFilter := mpltypes.DiscoveryFilter{ + AccountIds: []string{awsAccountId}, + Partition: "aws", + } + awsKmsMrkDiscoveryMultiKeyringInput := mpltypes.CreateAwsKmsMrkDiscoveryMultiKeyringInput{ + ClientSupplier: &RegionalRoleClientSupplier{}, + Regions: awsRegions, + DiscoveryFilter: &discoveryFilter, + } + awsKmsMrkDiscoveryMultiKeyring, err := matProv.CreateAwsKmsMrkDiscoveryMultiKeyring(context.Background(), awsKmsMrkDiscoveryMultiKeyringInput) + // Step 5a: Encrypt + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: awsKmsMrkMultiKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 5b: Decrypt + // Decrypt your encrypted data using the discovery multi keyring. + // On Decrypt, the header of the encrypted message (ciphertext) will be parsed. + // The header contains the Encrypted Data Keys (EDKs), which, if the EDK + // was encrypted by a KMS Keyring, includes the KMS Key ARN. + // For each member of the Multi Keyring, every EDK will try to be decrypted until a decryption + // is successful. + // Since every member of the Multi Keyring is a Discovery Keyring: + // Each Keyring will filter the EDKs by the Discovery Filter and the Keyring's region. + // For each filtered EDK, the keyring will attempt decryption with the keyring's client. + // All of this is done serially, until a success occurs or all keyrings have failed + // all (filtered) EDKs. KMS MRK Discovery Keyrings will attempt to decrypt + // Multi Region Keys (MRKs) and regular KMS Keys. + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: awsKmsMrkDiscoveryMultiKeyring, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Test the Missing Region Exception + // (This is for demonstration; you do not need to do this in your code.) + + // Create a MRK discovery multi-keyring with a custom client supplier and a fake region. + awsKmsMrkDiscoveryMultiKeyringInputMissingRegion := mpltypes.CreateAwsKmsMrkDiscoveryMultiKeyringInput{ + ClientSupplier: &RegionalRoleClientSupplier{}, + Regions: []string{"fake-region"}, + DiscoveryFilter: &discoveryFilter, + } + _, err = matProv.CreateAwsKmsMrkDiscoveryMultiKeyring(context.Background(), awsKmsMrkDiscoveryMultiKeyringInputMissingRegion) + // Swallow the AwsCryptographicMaterialProvidersException but you may choose how to handle the exception + switch err.(type) { + case mpltypes.AwsCryptographicMaterialProvidersException: + // You may choose how to handle the exception in this switch case. + default: + panic("Decryption using discovery keyring with missing region MUST raise AwsCryptographicMaterialProvidersException") + } + fmt.Println("Client Supplier Example completed successfully") +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/clientsupplier/regionalroleclientsupplier.go b/AwsEncryptionSDK/runtimes/go/examples/clientsupplier/regionalroleclientsupplier.go new file mode 100644 index 000000000..762cdf745 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/clientsupplier/regionalroleclientsupplier.go @@ -0,0 +1,57 @@ +package clientsupplier + +import ( + "context" + + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials/stscreds" + "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/aws/aws-sdk-go-v2/service/sts" +) + +/* + Example class demonstrating an implementation of a custom client supplier. + This particular implementation will create KMS clients with different IAM roles, + depending on the region passed. +*/ + +// RegionalRoleClientSupplier provides implementation for mpltypes.IClientSupplier +type RegionalRoleClientSupplier struct { +} + +func (this *RegionalRoleClientSupplier) GetClient(input mpltypes.GetClientInput) (kms.Client, error) { + region := input.Region + // Check if the region is supported + regionIamRoleMap := RegionIamRoleMap() + var defaultVal kms.Client + // Check if region is supported + if _, exists := regionIamRoleMap[region]; !exists { + return defaultVal, mpltypes.AwsCryptographicMaterialProvidersException{ + Message: "Region is not supported by this client supplier", + } + } + // Get the IAM role ARN associated with the region + arn := regionIamRoleMap[region] + ctx := context.TODO() + cfg, err := config.LoadDefaultConfig(ctx, + config.WithRegion(region), + ) + if err != nil { + return defaultVal, err + } + stsClient := sts.NewFromConfig(cfg) + // Create the AssumeRoleProvider + provider := stscreds.NewAssumeRoleProvider(stsClient, arn, func(o *stscreds.AssumeRoleOptions) { + o.RoleSessionName = "Go-ESDK-Client-Supplier-Example-Session" + }) + // Load AWS SDK configuration with the AssumeRoleProvider + sdkConfig, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region), config.WithCredentialsProvider(provider)) + if err != nil { + return defaultVal, mpltypes.AwsCryptographicMaterialProvidersException{Message: "failed to load AWS SDK config"} + } + // Create the KMS client + kmsClient := kms.NewFromConfig(sdkConfig) + // Return the KMS client wrapped in a custom type + return *kmsClient, nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/clientsupplier/regionalroleclientsupplierconfig.go b/AwsEncryptionSDK/runtimes/go/examples/clientsupplier/regionalroleclientsupplierconfig.go new file mode 100644 index 000000000..2c82f40fb --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/clientsupplier/regionalroleclientsupplierconfig.go @@ -0,0 +1,22 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package clientsupplier + +/* + File containing config for the RegionalRoleClientSupplier. + In your own code, this might be hardcoded, or reference + an external source, e.g. environment variables or AWS AppConfig. +*/ + +const ( + usEast1IamRole = "arn:aws:iam::370957321024:role/GitHub-CI-Public-ESDK-Dafny-Role-only-us-east-1-KMS-keys" + euWest1IamRole = "arn:aws:iam::370957321024:role/GitHub-CI-Public-ESDK-Dafny-Role-only-eu-west-1-KMS-keys" +) + +func RegionIamRoleMap() map[string]string { + return map[string]string{ + "us-east-1": usEast1IamRole, + "eu-west-1": euWest1IamRole, + } +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/cryptographicmaterialsmanager/requiredencryptioncontext/requiredencryptioncontext.go b/AwsEncryptionSDK/runtimes/go/examples/cryptographicmaterialsmanager/requiredencryptioncontext/requiredencryptioncontext.go new file mode 100644 index 000000000..0d85508ba --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/cryptographicmaterialsmanager/requiredencryptioncontext/requiredencryptioncontext.go @@ -0,0 +1,160 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* +Demonstrate an encrypt/decrypt cycle using a Required Encryption Context CMM. +A required encryption context CMM asks for required keys in the encryption context field +on encrypt such that they will not be stored on the message, but WILL be included in the header signature. +On decrypt, the client MUST supply the key/value pair(s) that were not stored to successfully decrypt the message. +*/ + +package requiredencryptioncontext + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func RequiredEncryptionContextExample(exampleText, defaultKMSKeyId, defaultKmsKeyRegion string) { + // Step 1: Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = defaultKmsKeyRegion + }) + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Create the keyring + awsKmsKeyringInput := mpltypes.CreateAwsKmsKeyringInput{ + KmsClient: kmsClient, + KmsKeyId: defaultKMSKeyId, + } + awsKmsKeyring, err := matProv.CreateAwsKmsKeyring(context.Background(), awsKmsKeyringInput) + if err != nil { + panic(err) + } + // Step 4: Instantiate the encryption SDK client. + // This builds the default 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 + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: 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 + encryptionContext := map[string]string{ + "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", + "requiredKey1": "requiredValue1", + "requiredKey2": "requiredValue2", + } + // Step 6: 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. + underlyingCMM, err := matProv.CreateDefaultCryptographicMaterialsManager(context.Background(), mpltypes.CreateDefaultCryptographicMaterialsManagerInput{Keyring: awsKmsKeyring}) + if err != nil { + panic(err) + } + requiredEncryptionContextKeys := []string{} + requiredEncryptionContextKeys = append(requiredEncryptionContextKeys, "requiredKey1", "requiredKey2") + requiredEncryptionContextInput := mpltypes.CreateRequiredEncryptionContextCMMInput{ + UnderlyingCMM: underlyingCMM, + // If you pass in a keyring but no underlying cmm, it will result in a failure because only cmm is supported. + RequiredEncryptionContextKeys: requiredEncryptionContextKeys, + } + requiredEC, err := matProv.CreateRequiredEncryptionContextCMM(context.Background(), requiredEncryptionContextInput) + if err != nil { + panic(err) + } + // Step 7a: Encrypt + // NOTE: the keys "requiredKey1", and "requiredKey2" + // WILL NOT be stored in the message header, but "encryption", "is not", + // "but adds", "that can help you", and "the data you are handling" WILL be stored. + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + MaterialsManager: requiredEC, + EncryptionContext: encryptionContext, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 7b: Decrypt + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Ciphertext: res.Ciphertext, + MaterialsManager: requiredEC, + }) + if err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // For demonstration attempt to decrypt your encrypted data using the same cryptographic material manager + // you used on encrypt, but we won't pass the encryption context we DID NOT store on the message. + // This will fail + _, err = encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: res.Ciphertext, + MaterialsManager: requiredEC, + }) + // We expect failure. + if err == nil { + panic("Decryption passed without any error when encryption context was not provided.") + } + // Decrypt your encrypted data using the same cryptographic material manager + // you used to encrypt, but supply encryption context that contains ONLY the encryption context that + // was NOT stored. This will pass. + reproducedEncryptionContext := map[string]string{ + "requiredKey1": "requiredValue1", + "requiredKey2": "requiredValue2", + } + decryptOutputreproducedEC, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: reproducedEncryptionContext, + Ciphertext: res.Ciphertext, + MaterialsManager: requiredEC, + }) + if err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutputreproducedEC.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // You can also decrypt with the underlyingCMM, but must still provide the reproducedEncryptionContext. + decryptOutputWithCMM, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: reproducedEncryptionContext, + Ciphertext: res.Ciphertext, + MaterialsManager: underlyingCMM, + }) + if err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutputWithCMM.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("Required Encryption Context CMM Example Completed Successfully") +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/cryptographicmaterialsmanager/restrictalgorithmsuite/signingonlyexample.go b/AwsEncryptionSDK/runtimes/go/examples/cryptographicmaterialsmanager/restrictalgorithmsuite/signingonlyexample.go new file mode 100644 index 000000000..6ce7c5460 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/cryptographicmaterialsmanager/restrictalgorithmsuite/signingonlyexample.go @@ -0,0 +1,137 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + Demonstrate an encrypt/decrypt cycle using a Custom Cryptographic Materials Manager (CMM). + `signingsuiteonlycmm.go` demonstrates creating a custom CMM to reject Non-Signing Algorithms. +*/ + +package restrictalgorithmsuite + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func SigningOnlyExample(exampleText, defaultKMSKeyId, defaultKmsKeyRegion string) { + // Step 1: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 2: Create the AWS KMS client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = defaultKmsKeyRegion + }) + // Step 3: Create your encryption context. + // Remember that your encryption context is NOT SECRET. + // For more information, see + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Create the Aws KMS Keyring + awsKmsKeyringInput := mpltypes.CreateAwsKmsKeyringInput{ + KmsClient: kmsClient, + KmsKeyId: defaultKMSKeyId, + } + awsKmsKeyring, err := matProv.CreateAwsKmsKeyring(context.Background(), awsKmsKeyringInput) + if err != nil { + panic(err) + } + // Step 4: Create an instance of the custom CMM + cmm, err := NewSigningSuiteOnlyCMM(awsKmsKeyring) + if err != nil { + panic(err) + } + // Step 5a: Encrypt + algorithmSuiteId := mpltypes.ESDKAlgorithmSuiteIdAlgAes256GcmHkdfSha512CommitKeyEcdsaP384 + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + MaterialsManager: cmm, + AlgorithmSuiteId: &algorithmSuiteId, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 5b: Decrypt + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: res.Ciphertext, + EncryptionContext: encryptionContext, + MaterialsManager: cmm, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // Demonstrate that a Non Signing Algorithm Suite will be rejected by the CMM. + nonSigningAlgorithmSuiteId := mpltypes.ESDKAlgorithmSuiteIdAlgAes256GcmHkdfSha512CommitKey + _, err = encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + MaterialsManager: cmm, + AlgorithmSuiteId: &nonSigningAlgorithmSuiteId, + }) + if err == nil { + panic("Expected error but error is nil") + } + switch err.(type) { + case mpltypes.AwsCryptographicMaterialProvidersException: + // You may choose how to handle the exception in this switch case. + default: + panic("error is expected to be a type of AwsCryptographicMaterialProvidersException") + } + fmt.Println("SigningSuiteOnlyCMM Example Completed Successfully") +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/cryptographicmaterialsmanager/restrictalgorithmsuite/signingsuiteonlycmm.go b/AwsEncryptionSDK/runtimes/go/examples/cryptographicmaterialsmanager/restrictalgorithmsuite/signingsuiteonlycmm.go new file mode 100644 index 000000000..7d02d7a1b --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/cryptographicmaterialsmanager/restrictalgorithmsuite/signingsuiteonlycmm.go @@ -0,0 +1,77 @@ +package restrictalgorithmsuite + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" +) + +type SigningSuiteOnlyCMM struct { + approvedAlgos map[mpltypes.ESDKAlgorithmSuiteId]bool + cmm mpltypes.ICryptographicMaterialsManager +} + +// NewSigningSuiteOnlyCMM creates a new SigningSuiteOnlyCMM +func NewSigningSuiteOnlyCMM(keyring mpltypes.IKeyring) (*SigningSuiteOnlyCMM, error) { + // Initialize the MPL client + materialProviders, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Create a DefaultCryptographicMaterialsManager + cmmInput := mpltypes.CreateDefaultCryptographicMaterialsManagerInput{ + Keyring: keyring, + } + cmm, err := materialProviders.CreateDefaultCryptographicMaterialsManager(context.Background(), cmmInput) + if err != nil { + return nil, err + } + // Create list of approved algorithm + var approvedAlgos = map[mpltypes.ESDKAlgorithmSuiteId]bool{ + mpltypes.ESDKAlgorithmSuiteIdAlgAes128GcmIv12Tag16HkdfSha256EcdsaP256: true, + mpltypes.ESDKAlgorithmSuiteIdAlgAes192GcmIv12Tag16HkdfSha384EcdsaP384: true, + mpltypes.ESDKAlgorithmSuiteIdAlgAes256GcmIv12Tag16HkdfSha384EcdsaP384: true, + mpltypes.ESDKAlgorithmSuiteIdAlgAes256GcmHkdfSha512CommitKeyEcdsaP384: true, + } + return &SigningSuiteOnlyCMM{ + approvedAlgos: approvedAlgos, + cmm: cmm, + }, nil +} + +func (signingSuiteOnlyCMM *SigningSuiteOnlyCMM) GetEncryptionMaterials(input mpltypes.GetEncryptionMaterialsInput) (*mpltypes.GetEncryptionMaterialsOutput, error) { + // Get the algorithm suite from the input + esdkAlgorithmSuite, err := getESDKAlgorithmSuite(input.AlgorithmSuiteId) + if err != nil { + return nil, err + } + // Check if the algorithm is approved + if !signingSuiteOnlyCMM.approvedAlgos[esdkAlgorithmSuite.Value] { + return nil, mpltypes.AwsCryptographicMaterialProvidersException{Message: "Algorithm Suite must use Signing"} + } + // Delegate to the underlying CMM + return signingSuiteOnlyCMM.cmm.GetEncryptionMaterials(input) +} + +func getESDKAlgorithmSuite(algSuite mpltypes.AlgorithmSuiteId) (*mpltypes.AlgorithmSuiteIdMemberESDK, error) { + if esdk, ok := algSuite.(*mpltypes.AlgorithmSuiteIdMemberESDK); ok { + return esdk, nil + } + return nil, fmt.Errorf("algorithm suite is not ESDK type") +} + +func (signingSuiteOnlyCMM *SigningSuiteOnlyCMM) DecryptMaterials(input mpltypes.DecryptMaterialsInput) (*mpltypes.DecryptMaterialsOutput, error) { + // Get the algorithm suite from the input + esdkAlgorithmSuite, err := getESDKAlgorithmSuite(input.AlgorithmSuiteId) + if err != nil { + return nil, err + } + // Check if the algorithm is approved + if !signingSuiteOnlyCMM.approvedAlgos[esdkAlgorithmSuite.Value] { + return nil, mpltypes.AwsCryptographicMaterialProvidersException{Message: "Algorithm Suite must use Signing"} + } + // Delegate to the underlying CMM + return signingSuiteOnlyCMM.cmm.DecryptMaterials(input) +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/go.mod b/AwsEncryptionSDK/runtimes/go/examples/go.mod new file mode 100644 index 000000000..10303df09 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/go.mod @@ -0,0 +1,43 @@ +module github.com/aws/aws-encryption-sdk/examples + +go 1.23.0 + +replace ( + github.com/aws/aws-cryptographic-material-providers-library/dynamodb v0.0.0 => ../../../../mpl/ComAmazonawsDynamodb/runtimes/go/ImplementationFromDafny-go/ + github.com/aws/aws-cryptographic-material-providers-library/kms v0.0.0 => ../../../../mpl/ComAmazonawsKms/runtimes/go/ImplementationFromDafny-go/ + github.com/aws/aws-cryptographic-material-providers-library/mpl v0.0.0 => ../../../../mpl/AwsCryptographicMaterialProviders/runtimes/go/ImplementationFromDafny-go/ + github.com/aws/aws-cryptographic-material-providers-library/primitives v0.0.0 => ../../../../mpl/AwsCryptographyPrimitives/runtimes/go/ImplementationFromDafny-go/ + github.com/aws/aws-encryption-sdk => ../ImplementationFromDafny-go/ + github.com/dafny-lang/DafnyStandardLibGo => ../../../../mpl/StandardLibrary/runtimes/go/ImplementationFromDafny-go/ +) + +require ( + github.com/aws/aws-cryptographic-material-providers-library/mpl v0.0.0 + github.com/aws/aws-cryptographic-material-providers-library/primitives v0.0.0 + github.com/aws/aws-encryption-sdk v0.0.0-00010101000000-000000000000 + github.com/aws/aws-sdk-go-v2/config v1.27.37 + github.com/aws/aws-sdk-go-v2/credentials v1.17.35 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.35.1 + github.com/aws/aws-sdk-go-v2/service/kms v1.36.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.31.1 +) + +require ( + github.com/aws/aws-cryptographic-material-providers-library/dynamodb v0.0.0 // indirect + github.com/aws/aws-cryptographic-material-providers-library/kms v0.0.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.31.0 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.23.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.1 // indirect + github.com/aws/smithy-go v1.21.0 // indirect + github.com/dafny-lang/DafnyRuntimeGo/v4 v4.9.1 // indirect + github.com/dafny-lang/DafnyStandardLibGo v0.0.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect +) diff --git a/AwsEncryptionSDK/runtimes/go/examples/go.sum b/AwsEncryptionSDK/runtimes/go/examples/go.sum new file mode 100644 index 000000000..55861093d --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/go.sum @@ -0,0 +1,48 @@ +github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= +github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= +github.com/aws/aws-sdk-go-v2/config v1.27.37 h1:xaoIwzHVuRWRHFI0jhgEdEGc8xE1l91KaeRDsWEIncU= +github.com/aws/aws-sdk-go-v2/config v1.27.37/go.mod h1:S2e3ax9/8KnMSyRVNd3sWTKs+1clJ2f1U6nE0lpvQRg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.35 h1:7QknrZhYySEB1lEXJxGAmuD5sWwys5ZXNr4m5oEz0IE= +github.com/aws/aws-sdk-go-v2/credentials v1.17.35/go.mod h1:8Vy4kk7at4aPSmibr7K+nLTzG6qUQAUO4tW49fzUV4E= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.35.1 h1:DDN8yqYzFUDy2W5zk3tLQNKaO/1t0h3fNixPJacu264= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.35.1/go.mod h1:k5XW8MoMxsNZ20RJmsokakvENUwQyjv69R9GqrI4xdQ= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.19 h1:dOxqOlOEa2e2heC/74+ZzcJOa27+F1aXFZpYgY/4QfA= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.19/go.mod h1:aV6U1beLFvk3qAgognjS3wnGGoDId8hlPEiBsLHXVZE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= +github.com/aws/aws-sdk-go-v2/service/kms v1.36.0 h1:jwWMpQ/1obJRdHaix9k10zWSnSMZGdDTZIDiS5CGzq8= +github.com/aws/aws-sdk-go-v2/service/kms v1.36.0/go.mod h1:OHmlX4+o0XIlJAQGAHPIy0N9yZcYS/vNG+T7geSNcFw= +github.com/aws/aws-sdk-go-v2/service/sso v1.23.1 h1:2jrVsMHqdLD1+PA4BA6Nh1eZp0Gsy3mFSB5MxDvcJtU= +github.com/aws/aws-sdk-go-v2/service/sso v1.23.1/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.1 h1:0L7yGCg3Hb3YQqnSgBTZM5wepougtL1aEccdcdYhHME= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.1/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= +github.com/aws/aws-sdk-go-v2/service/sts v1.31.1 h1:8K0UNOkZiK9Uh3HIF6Bx0rcNCftqGCeKmOaR7Gp5BSo= +github.com/aws/aws-sdk-go-v2/service/sts v1.31.1/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= +github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= +github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/dafny-lang/DafnyRuntimeGo/v4 v4.9.1 h1:dOgaw3i0I9nWKPjfXYzEfgWsVRJykL6FA18DErvQiJQ= +github.com/dafny-lang/DafnyRuntimeGo/v4 v4.9.1/go.mod h1:l2Tm4N2DKuq3ljONC2vOATeM9PUpXbIc8SgXdwwqEto= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsdiscoverykeyring/awskmsdiscoverykeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsdiscoverykeyring/awskmsdiscoverykeyring.go new file mode 100644 index 000000000..e3ccfbbd2 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsdiscoverykeyring/awskmsdiscoverykeyring.go @@ -0,0 +1,189 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +/* +This example sets up the AWS KMS Discovery Keyring +AWS KMS discovery keyring is an AWS KMS keyring that doesn't specify any wrapping keys. +The AWS Encryption SDK provides a standard AWS KMS discovery keyring and a discovery keyring +for AWS KMS multi-Region keys. For information about using multi-Region keys with the +AWS Encryption SDK, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/configure.html#config-mrks +Because it doesn't specify any wrapping keys, a discovery keyring can't encrypt data. +If you use a discovery keyring to encrypt data, alone or in a multi-keyring, the encrypt +operation fails. +When decrypting, a discovery keyring allows the AWS Encryption SDK to ask AWS KMS to decrypt +any encrypted data key by using the AWS KMS key that encrypted it, regardless of who owns or +has access to that AWS KMS key. The call succeeds only when the caller has kms:Decrypt +permission on the AWS KMS key. +This example creates a KMS Keyring and then encrypts a custom input exampleText +with an encryption context. This encrypted ciphertext is then decrypted using the Discovery keyring. +This example also includes some sanity checks for demonstration: + 1. Ciphertext and plaintext data are not the same + 2. Decrypted plaintext value matches exampleText + 3. Decryption is only possible if the Discovery Keyring contains the correct AWS Account ID's to + which the KMS key used for encryption belongs +These sanity checks are for demonstration in the example only. You do not need these in your code. +For more information on how to use KMS Discovery keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery +For more information on KMS Key identifiers, see +https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id +*/ + +package awskmsdiscoverykeyring + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func AwsKmsDiscoveryKeyringExample(exampleText string, defaultKmsKeyId string, defaultKMSKeyAccountID string) { + // Step 1: Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = "us-west-2" + }) + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient( + mpltypes.MaterialProvidersConfig{}, + ) + if err != nil { + panic(err) + } + // Step 3: Create the keyring + // Although this example highlights Discovery keyrings, Discovery keyrings cannot + // be used to encrypt, so for encryption we create a KMS keyring without discovery mode. + // So, we create two keyrings, one for encrypt and another for decrypt + // First Keyring: Create a AwsKmsKeyring to use for encryption + awsKmsKeyringInput := mpltypes.CreateAwsKmsKeyringInput{ + KmsClient: kmsClient, + KmsKeyId: defaultKmsKeyId, + } + awsKmsKeyring, err := matProv.CreateAwsKmsKeyring(context.Background(), awsKmsKeyringInput) + if err != nil { + panic(err) + } + // Second Keyring: Create a Discovery keyring to use for decryption. + // We'll add a discovery filter so that we limit + // the set of ciphertexts we are willing to decrypt to only ones created by KMS keys in our account and + // partition. + discoveryFilter := mpltypes.DiscoveryFilter{ + AccountIds: []string{defaultKMSKeyAccountID}, + Partition: "aws", + } + awsKmsDiscoveryKeyringInput := mpltypes.CreateAwsKmsDiscoveryKeyringInput{ + KmsClient: kmsClient, + DiscoveryFilter: &discoveryFilter, + } + awsKmsDiscoveryKeyring, err := matProv.CreateAwsKmsDiscoveryKeyring(context.Background(), awsKmsDiscoveryKeyringInput) + if err != nil { + panic(err) + } + // Step 4: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 6a: Encrypt + algorithmSuiteID := mpltypes.ESDKAlgorithmSuiteIdAlgAes256GcmHkdfSha512CommitKey + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + AlgorithmSuiteId: &algorithmSuiteID, + EncryptionContext: encryptionContext, + Keyring: awsKmsKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 6b: Decrypt + // On Decrypt, the header of the encrypted message (ciphertext) will be parsed. + // The header contains the Encrypted Data Keys (EDKs), which, if the EDK + // was encrypted by a KMS Keyring, includes the KMS Key ARN. + // The Discovery Keyring filters these EDKs for + // EDKs encrypted by Single Region OR Multi Region KMS Keys. + // If a Discovery Filter is present, these KMS Keys must belong + // to an AWS Account ID in the discovery filter's AccountIds and + // must be from the discovery filter's partition. + // Finally, KMS is called to decrypt each filtered EDK until an EDK is + // successfully decrypted. The resulting data key is used to decrypt the + // ciphertext's message. + // If all calls to KMS fail, the decryption fails. + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Keyring: awsKmsDiscoveryKeyring, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate that if a different discovery keyring doesn't have the correct + // AWS Account ID's, the decrypt will fail with an error message + // Note that this assumes Account ID used here ('888888888888') is different than the one used + // during encryption + discoveryFilterFailureCase := mpltypes.DiscoveryFilter{ + AccountIds: []string{"888888888888"}, + Partition: "aws", + } + awsKmsDiscoveryKeyringInputFailureCase := mpltypes.CreateAwsKmsDiscoveryKeyringInput{ + KmsClient: kmsClient, + DiscoveryFilter: &discoveryFilterFailureCase, + } + awsKmsDiscoveryKeyringFailureCase, err := matProv.CreateAwsKmsDiscoveryKeyring(context.Background(), awsKmsDiscoveryKeyringInputFailureCase) + _, err = encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Keyring: awsKmsDiscoveryKeyringFailureCase, + Ciphertext: res.Ciphertext, + }) + // We expected error in failure case + if err == nil { + panic("Expected failure case to fail") + } + fmt.Println("AWS KMS Discovery Keyring Example Completed Successfully") +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsdiscoverymultikeyring/awskmsdiscoverymultikeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsdiscoverymultikeyring/awskmsdiscoverymultikeyring.go new file mode 100644 index 000000000..d9682072c --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsdiscoverymultikeyring/awskmsdiscoverymultikeyring.go @@ -0,0 +1,169 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +/* +This example sets up the AWS KMS Discovery Multi Keyring and demonstrates decryption +using a Multi-Keyring containing multiple AWS KMS Discovery Keyrings. +The AWS Encryption SDK provides a standard AWS KMS discovery keyring and a discovery keyring +for AWS KMS multi-Region keys. For information about using multi-Region keys with the +AWS Encryption SDK, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/configure.html#config-mrks +Because it doesn't specify any wrapping keys, a discovery keyring can't encrypt data. +If you use a discovery keyring to encrypt data, alone or in a multi-keyring, the encrypt +operation fails. +When decrypting, a discovery keyring allows the AWS Encryption SDK to ask AWS KMS to decrypt +any encrypted data key by using the AWS KMS key that encrypted it, regardless of who owns or +has access to that AWS KMS key. The call succeeds only when the caller has kms:Decrypt +permission on the AWS KMS key. +This example creates a KMS Keyring and then encrypts a custom input exampleText +with an encryption context. This encrypted ciphertext is then decrypted using the Discovery Multi +keyring. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decrypted plaintext value matches exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. +For more information on how to use KMS Discovery keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery +For more information on KMS Key identifiers, see +https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id +*/ + +package awskmsdiscoverymultikeyring + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func AwsKmsDiscoveryMultiKeyringExample( + exampleText string, + defaultKmsKeyId string, + defaultKMSKeyAccountID string, + regions []string) { + // Step 1: Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = "us-west-2" + }) + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient( + mpltypes.MaterialProvidersConfig{}, + ) + if err != nil { + panic(err) + } + // Step 3: Create the keyring + // Although this example highlights Discovery keyrings, Discovery keyrings cannot + // be used to encrypt, so for encryption we create a KMS keyring without discovery mode. + // So, we create two keyrings, one for encrypt and another for decrypt + // First Keyring: Create a AwsKmsKeyring to use for encryption + awsKmsKeyringInput := mpltypes.CreateAwsKmsKeyringInput{ + KmsClient: kmsClient, + KmsKeyId: defaultKmsKeyId, + } + awsKmsKeyring, err := matProv.CreateAwsKmsKeyring(context.Background(), awsKmsKeyringInput) + if err != nil { + panic(err) + } + // Second Keyring: Create a Discovery keyring to use for decryption. + // We'll add a discovery filter so that we limit the set of ciphertexts we are willing to + // decrypt to only ones created by KMS keys in our account and partition. + discoveryFilter := mpltypes.DiscoveryFilter{ + AccountIds: []string{defaultKMSKeyAccountID}, + Partition: "aws", + } + awsKmsDiscoveryMultiKeyringInput := mpltypes.CreateAwsKmsDiscoveryMultiKeyringInput{ + Regions: regions, + DiscoveryFilter: &discoveryFilter, + } + awsKmsDiscoveryMultiKeyring, err := matProv.CreateAwsKmsDiscoveryMultiKeyring(context.Background(), awsKmsDiscoveryMultiKeyringInput) + if err != nil { + panic(err) + } + // Step 4: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 6a: Encrypt + algorithmSuiteID := mpltypes.ESDKAlgorithmSuiteIdAlgAes256GcmHkdfSha512CommitKey + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + AlgorithmSuiteId: &algorithmSuiteID, + EncryptionContext: encryptionContext, + Keyring: awsKmsKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 6b: Decrypt + // On Decrypt, the header of the encrypted message (ciphertext) will be parsed. + // The header contains the Encrypted Data Keys (EDKs), which, if the EDK + // was encrypted by a KMS Keyring, includes the KMS Key ARN. + // The Discovery Keyring filters these EDKs for + // EDKs encrypted by Single Region OR Multi Region KMS Keys. + // If a Discovery Filter is present, these KMS Keys must belong + // to an AWS Account ID in the discovery filter's AccountIds and + // must be from the discovery filter's partition. + // Finally, KMS is called to decrypt each filtered EDK until an EDK is + // successfully decrypted. The resulting data key is used to decrypt the + // ciphertext's message. + // If all calls to KMS fail, the decryption fails. + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Keyring: awsKmsDiscoveryMultiKeyring, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err := validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same.") + } + fmt.Println("AWS KMS Discovery Multi Keyring Example Completed Successfully") +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/awskmshierarchicalkeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/awskmshierarchicalkeyring.go new file mode 100644 index 000000000..024598d8c --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/awskmshierarchicalkeyring.go @@ -0,0 +1,296 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + 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 + + For more information on how to use Hierarchical Keyrings, see + https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-hierarchical-keyring.html +*/ + +package awskmshierarchicalkeyring + +import ( + "context" + "fmt" + + keystore "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographykeystoresmithygenerated" + keystoretypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographykeystoresmithygeneratedtypes" + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func AwsKmsHKeyExample(exampletext, keyStoreKMSKeyRegion, keyStoreRegion, keyStoreKMSKeyID, keyStoreName, logicalKeyStoreName string) { + // Step 1: Create the aws sdk clients + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + // Step 1a: Create the aws kms client + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = keyStoreKMSKeyRegion + }) + // Step 1b: Create the ddb client + ddbClient := dynamodb.NewFromConfig(cfg, func(options *dynamodb.Options) { + options.Region = keyStoreRegion + }) + // Step 2: Instantiate the encryption SDK client. + // This builds the default 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. + client, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 2: Create the keystore to manage the tenant keys + // This SHOULD be the same configuration that you used + // to initially create and populate your KeyStore. + kmsConfig := keystoretypes.KMSConfigurationMemberkmsKeyArn{ + Value: keyStoreKMSKeyID, + } + keyStore, err := keystore.NewClient(keystoretypes.KeyStoreConfig{ + DdbTableName: keyStoreName, + KmsConfiguration: &kmsConfig, + LogicalKeyStoreName: logicalKeyStoreName, + DdbClient: ddbClient, + KmsClient: kmsClient, + }) + if err != nil { + panic(err) + } + // Step 3: Create two new branch keys + branchKeyA, err := createbranchkeyid(keyStoreName, logicalKeyStoreName, keyStoreKMSKeyID, ddbClient, kmsClient) + if err != nil { + panic(err) + } + branchKeyB, err := createbranchkeyid(keyStoreName, logicalKeyStoreName, keyStoreKMSKeyID, ddbClient, kmsClient) + if err != nil { + panic(err) + } + // Step 4: Create a branch key supplier that maps the branch key id to a more readable format + // See branchkeysupplier.go in this package for the branchKeySupplier structure + keySupplier := branchKeySupplier{branchKeyA: branchKeyA, branchKeyB: branchKeyB} + // Step 5: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 6: Create the Hierarchical Keyring. + hkeyringInput := mpltypes.CreateAwsKmsHierarchicalKeyringInput{ + KeyStore: keyStore, + BranchKeyIdSupplier: &keySupplier, + TtlSeconds: 600, + } + hKeyRing, err := matProv.CreateAwsKmsHierarchicalKeyring(context.Background(), hkeyringInput) + if err != nil { + panic(err) + } + // Step 7: Create encryption context for both tenants. + // The Branch Key Id supplier uses the encryption context to determine which branch key id will + // be used to encrypt data. + // Remember that your encryption context is NOT SECRET. + // For more information, see + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + + // Create encryption context for TenantA + encryptionContextA := map[string]string{ + "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", + } + // Create encryption context for TenantB + encryptionContextB := map[string]string{ + "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", + } + // Step 8a: Encrypt the data + // Encrypt data for Tenant A + encryptOutputA, err := client.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampletext), + EncryptionContext: encryptionContextA, + Keyring: hKeyRing, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(encryptOutputA.Ciphertext) == exampletext { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Encrypt data for Tenant B + encryptOutputB, err := client.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampletext), + EncryptionContext: encryptionContextB, + Keyring: hKeyRing, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(encryptOutputB.Ciphertext) == exampletext { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 8b: Decrypt the data with various scenerios for demonstration + + // For demonstration, let's attest that TenantKeyB cannot decrypt a message written by TenantKeyA, + // and vice versa and construct more restrictive hierarchical keyrings. + hkeyringInputA := mpltypes.CreateAwsKmsHierarchicalKeyringInput{ + KeyStore: keyStore, + BranchKeyId: &branchKeyA, + TtlSeconds: 600, + } + hKeyRingA, err := matProv.CreateAwsKmsHierarchicalKeyring(context.Background(), hkeyringInputA) + if err != nil { + panic(err) + } + hkeyringInputB := mpltypes.CreateAwsKmsHierarchicalKeyringInput{ + KeyStore: keyStore, + BranchKeyId: &branchKeyB, + TtlSeconds: 600, + } + hKeyRingB, err := matProv.CreateAwsKmsHierarchicalKeyring(context.Background(), hkeyringInputB) + if err != nil { + panic(err) + } + // Demonstrate that data encrypted by one tenant's key + // cannot be decrypted with by a keyring specific to another tenant. + + // Keyring with tenant B's branch key cannot decrypt data encrypted with tenant A's branch key + // This will fail and raise a AwsCryptographicMaterialProvidersException, + // which we swallow ONLY for demonstration purposes. + _, err = client.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: encryptOutputA.Ciphertext, + EncryptionContext: encryptionContextA, + Keyring: hKeyRingB, + }) + if err == nil { + panic("Expected error did not occur") + } + switch err.(type) { + case mpltypes.AwsCryptographicMaterialProvidersException: + // You may choose how to handle the exception in this switch case. + default: + panic("error is expected to be a type of AwsCryptographicMaterialProvidersException") + } + // Keyring with tenant A's branch key cannot decrypt data encrypted with tenant B's branch key. + // This will fail and raise a AwsCryptographicMaterialProvidersException, + // which we swallow ONLY for demonstration purposes. + _, err = client.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: encryptOutputB.Ciphertext, + EncryptionContext: encryptionContextA, + Keyring: hKeyRingA, + }) + if err == nil { + panic("Expected error did not occur") + } + switch err.(type) { + case mpltypes.AwsCryptographicMaterialProvidersException: + // You may choose how to handle the exception in this switch case. + default: + panic("error is expected to be a type of AwsCryptographicMaterialProvidersException") + } + // Demonstrate that data encrypted by one tenant's branch key can be decrypted by that tenant, + // and that the decrypted data matches the input data. + + // For tenant A + decryptOutputA, err := client.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: encryptOutputA.Ciphertext, + EncryptionContext: encryptionContextA, + Keyring: hKeyRingA, + }) + if err != nil { + panic(err) + } + // If you are not specifying the encryption context on decrypt. Its recommended to check if the encryption context matches. + // Although, we are specifying the encryption context on decrypt, only for demonstration we are validating the encryption context. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContextA, decryptOutputA.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutputA.Plaintext) != exampletext { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // For tenant B + decryptOutputB, err := client.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: encryptOutputB.Ciphertext, + EncryptionContext: encryptionContextB, + Keyring: hKeyRingB, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContextB, decryptOutputB.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutputB.Plaintext) != exampletext { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutputA.Plaintext) != exampletext { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("Aws Kms Hierarchical Keyring Example Completed Successfully") +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/branchkeysupplier.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/branchkeysupplier.go new file mode 100644 index 000000000..fa066fd70 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/branchkeysupplier.go @@ -0,0 +1,45 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package awskmshierarchicalkeyring + +import ( + "fmt" + + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" +) + +/* +Demonstrates how to create a BranchKeyIdSupplier. + +The BranchKeyIdSupplier determines which Branch Key is used +to protect or access data. +It is an important component in a Multi-tenant solution, +where each tenant is cryptographically isolated. +The Branch Key ID Supplier uses the Encryption Context +provided at Encrypt or Decrypt +to determine what "shared secret" (Branch Key) +is used. +*/ + +type branchKeySupplier struct { + branchKeyA string + branchKeyB string +} + +func (b *branchKeySupplier) GetBranchKeyId(input mpltypes.GetBranchKeyIdInput) (*mpltypes.GetBranchKeyIdOutput, error) { + // We MUST use the encryption context to determine + // the Branch Key ID. + ec := input.EncryptionContext + if value, exists := ec["tenant"]; !exists || value == "" { + return nil, fmt.Errorf("EncryptionContext invalid, does not contain expected tenant key value pair.") + } + branchKeyIdentifier := ec["tenant"] + if branchKeyIdentifier == "TenantA" { + return &mpltypes.GetBranchKeyIdOutput{BranchKeyId: b.branchKeyA}, nil + } else if branchKeyIdentifier == "TenantB" { + return &mpltypes.GetBranchKeyIdOutput{BranchKeyId: b.branchKeyB}, nil + } else { + return &mpltypes.GetBranchKeyIdOutput{}, fmt.Errorf("unknown branch key identifier") + } +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/createbranchkeyid.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/createbranchkeyid.go new file mode 100644 index 000000000..f236e9ac4 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/createbranchkeyid.go @@ -0,0 +1,45 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package awskmshierarchicalkeyring + +import ( + "context" + + keystore "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographykeystoresmithygenerated" + keystoretypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographykeystoresmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +/* + The Hierarchical Keyring Example relies on the existence + of a DDB-backed key store with pre-existing + branch key material. + + This example demonstrates configuring a KeyStore and creating a branch key. +*/ + +func createbranchkeyid(keyStoreTableName, logicalKeyStoreName, kmsKeyArn string, ddbClient *dynamodb.Client, kmsClient *kms.Client) (string, error) { + // 1. Create the keystore + // The KMS Configuration you use in the KeyStore MUST have the right access to the resources in the KeyStore. + kmsConfig := keystoretypes.KMSConfigurationMemberkmsKeyArn{ + Value: kmsKeyArn, + } + keyStore, err := keystore.NewClient(keystoretypes.KeyStoreConfig{ + DdbTableName: keyStoreTableName, + KmsConfiguration: &kmsConfig, + LogicalKeyStoreName: logicalKeyStoreName, + DdbClient: ddbClient, + KmsClient: kmsClient, + }) + if err != nil { + return "", err + } + // 2. Create a branch key identifier with the AWS KMS Key configured in the KeyStore Configuration. + branchKey, err := keyStore.CreateKey(context.Background(), keystoretypes.CreateKeyInput{}) + if err != nil { + return "", err + } + return branchKey.BranchKeyIdentifier, nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/sharedcacheacrosshierarchicalkeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/sharedcacheacrosshierarchicalkeyring.go new file mode 100644 index 000000000..2aead2ccb --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/sharedcacheacrosshierarchicalkeyring.go @@ -0,0 +1,228 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + 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. + + If you want to use a Shared Cache, you need to initialize it only once, and + pass the same cache `shared_cache` to different hierarchical keyrings. + + 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 +*/ + +package awskmshierarchicalkeyring + +import ( + "context" + "fmt" + + keystore "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographykeystoresmithygenerated" + keystoretypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographykeystoresmithygeneratedtypes" + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func SharedCacheExample(exampletext, keyStoreKMSKeyRegion, keyStoreRegion, keyStoreKMSKeyID, keyStoreName, logicalKeyStoreName string) { + // Step 1: Create the aws sdk clients + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + fmt.Println(err) + panic(err) + } + // Step 1a: Create the aws kms client + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = keyStoreKMSKeyRegion + }) + // Step 1b: Create the ddb client + ddbClient := dynamodb.NewFromConfig(cfg, func(options *dynamodb.Options) { + options.Region = keyStoreRegion + }) + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Create the CryptographicMaterialsCache (CMC) to share across multiple Hierarchical Keyrings + // using the Material Providers Library + // This CMC takes in: + // - CacheType + cache := mpltypes.CacheTypeMemberDefault{ + Value: mpltypes.DefaultCache{ + EntryCapacity: 100, + }, + } + cmcCacheInput := mpltypes.CreateCryptographicMaterialsCacheInput{ + Cache: &cache, + } + sharedCryptographicMaterialsCache, err := matProv.CreateCryptographicMaterialsCache(context.Background(), cmcCacheInput) + if err != nil { + panic(err) + } + // Step 4: 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. + + // If you want to use a Shared Cache, you need to initialize it only once, and + // pass the same cache `shared_cache` to different hierarchical keyrings. + + // CryptographicMaterialsCacheRef is an Rc (Reference Counted), so if you clone it to + // pass it to different Hierarchical Keyrings, it will still point to the same + // underlying cache, and increment the reference count accordingly. + shared_cache := mpltypes.CacheTypeMemberShared{sharedCryptographicMaterialsCache} + // Step 2: Instantiate the encryption SDK client. + // This builds the default 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. + client, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: Configure your Key Store resource keyStore1. + // This SHOULD be the same configuration that you used + // to initially create and populate your physical Key Store. + // Note that key_store_table_name is the physical Key Store, + // and key_store1 is instances of this physical Key Store. + kmsConfig := keystoretypes.KMSConfigurationMemberkmsKeyArn{ + Value: keyStoreKMSKeyID, + } + keyStore1, err := keystore.NewClient(keystoretypes.KeyStoreConfig{ + DdbTableName: keyStoreName, + KmsConfiguration: &kmsConfig, + LogicalKeyStoreName: logicalKeyStoreName, + DdbClient: ddbClient, + KmsClient: kmsClient, + }) + if err != nil { + panic(err) + } + // Step 6: Call create_branch_key_id to create one new branch key + branchKeyId, err := createbranchkeyid(keyStoreName, logicalKeyStoreName, keyStoreKMSKeyID, ddbClient, kmsClient) + if err != nil { + panic(err) + } + // Step 7: Create the Hierarchical Keyring HK1 with Key Store instance K1, partition_id, + // the shared_cache and the branch_key_id. + // 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. + // partition_id for this example is a random UUID + 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 + hkeyringInput := mpltypes.CreateAwsKmsHierarchicalKeyringInput{ + KeyStore: keyStore1, + BranchKeyId: &branchKeyId, + TtlSeconds: 600, + Cache: &shared_cache, + PartitionId: &partitionId, + } + keyring1, err := matProv.CreateAwsKmsHierarchicalKeyring(context.Background(), hkeyringInput) + // Step 8: Create encryption context for both tenants. + // The Branch Key Id supplier uses the encryption context to determine which branch key id will + // be used to encrypt data. + // Remember that your encryption context is NOT SECRET. + // For more information, see + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + + // Create encryption context for TenantA + encryptionContext := map[string]string{ + "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", + } + // Step 9: Encrypt the data for encryptionContext using keyring1 + encryptOutput, err := client.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampletext), + EncryptionContext: encryptionContext, + Keyring: keyring1, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(encryptOutput.Ciphertext) == exampletext { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 10: Decrypt your encrypted data using the same keyring HK1 you used on encrypt. + decryptOutput, err := client.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: encryptOutput.Ciphertext, + EncryptionContext: encryptionContext, + Keyring: keyring1, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampletext { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("Shared Cache Example Completed Successfully") +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/versionbranchkeyid.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/versionbranchkeyid.go new file mode 100644 index 000000000..f12f48715 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmshierarchicalkeyring/versionbranchkeyid.go @@ -0,0 +1,93 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package awskmshierarchicalkeyring + +import ( + "context" + "fmt" + + keystore "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographykeystoresmithygenerated" + keystoretypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographykeystoresmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +/* +This example demonstrates configuring a KeyStore and then +uses a helper method to version a branch key. +*/ +func versionBranchKeyId(keyStoreTableName, logicalKeyStoreName, kmsKeyArn, branchKeyId string) error { + // Load the AWS SDK configuration + cfg, err := config.LoadDefaultConfig(context.Background()) + if err != nil { + return err + } + // Create DDB and KMS clients + ddbClient := dynamodb.NewFromConfig(cfg) + kmsClient := kms.NewFromConfig(cfg) + // Create the keystore + // The KMS Configuration you use in the KeyStore MUST have the right access to the resources in the KeyStore. + kmsConfig := keystoretypes.KMSConfigurationMemberkmsKeyArn{ + Value: kmsKeyArn, + } + keyStore, err := keystore.NewClient(keystoretypes.KeyStoreConfig{ + DdbTableName: keyStoreTableName, + KmsConfiguration: &kmsConfig, + LogicalKeyStoreName: logicalKeyStoreName, + DdbClient: ddbClient, + KmsClient: kmsClient, + }) + if err != nil { + return err + } + // To version a branch key you MUST have access to kms:ReEncrypt* and kms:GenerateDataKeyWithoutPlaintext + _, err = keyStore.VersionKey(context.Background(), keystoretypes.VersionKeyInput{ + BranchKeyIdentifier: branchKeyId, + }) + if err != nil { + return err + } + return nil +} + +// Function to test versionBranchKeyId in main.go in examples directory +func CreateAndVersionBranchKeyId(keyStoreKMSKeyRegion, keyStoreRegion, keyStoreKMSKeyID, keyStoreName, logicalKeyStoreName string) error { + // Create the aws sdk clients + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + // Create the aws kms client + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = keyStoreKMSKeyRegion + }) + // Create the ddb client + ddbClient := dynamodb.NewFromConfig(cfg, func(options *dynamodb.Options) { + options.Region = keyStoreRegion + }) + // create branch key ID + branchKeyId, err := createbranchkeyid( + keyStoreName, + logicalKeyStoreName, + keyStoreKMSKeyID, + ddbClient, + kmsClient, + ) + if err != nil { + panic(err) + } + // Version Branch Key + err = versionBranchKeyId( + keyStoreName, + logicalKeyStoreName, + keyStoreKMSKeyID, + branchKeyId, + ) + if err != nil { + panic(err) + } + fmt.Println("Create and version branchKey Id Example Completed Successfully") + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmskeyring/awskmskeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmskeyring/awskmskeyring.go new file mode 100644 index 000000000..246b8d9ac --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmskeyring/awskmskeyring.go @@ -0,0 +1,122 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +/* +This example sets up the AWS KMS Keyring +The AWS KMS keyring uses symmetric encryption KMS keys to generate, encrypt and +decrypt data keys. This example creates a KMS Keyring and then encrypts a custom input exampleText +with an encryption context. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decrypted plaintext value matches exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. +AWS KMS keyrings can be used independently or in a multi-keyring with other keyrings +of the same or a different type. +For more information on how to use KMS keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html +For more information on KMS Key identifiers, see +https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id +*/ + +package awskmskeyring + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func AwsKmsKeyringExample(exampleText string, defaultKmsKeyId string, defaultKMSKeyAccountID string) { + // Step 1: Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = "us-west-2" + }) + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Create the keyring + awsKmsKeyringInput := mpltypes.CreateAwsKmsKeyringInput{ + KmsClient: kmsClient, + KmsKeyId: defaultKmsKeyId, + } + awsKmsKeyring, err := matProv.CreateAwsKmsKeyring(context.Background(), awsKmsKeyringInput) + if err != nil { + panic(err) + } + // Step 4: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 6a: Encrypt + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: awsKmsKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 6b: Decrypt + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: awsKmsKeyring, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("AWS KMS Keyring Example Completed Successfully") +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkdiscoverykeyring/awskmsmrkdiscoverykeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkdiscoverykeyring/awskmsmrkdiscoverykeyring.go new file mode 100644 index 000000000..6822bbcfc --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkdiscoverykeyring/awskmsmrkdiscoverykeyring.go @@ -0,0 +1,172 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +/* +This example sets up the AWS KMS MRK (multi-region key) Discovery Keyring +The AWS KMS discovery keyring is an AWS KMS keyring that doesn't specify any wrapping keys. +When decrypting, an MRK discovery keyring allows the AWS Encryption SDK to ask AWS KMS to decrypt +any encrypted data key by using the AWS KMS MRK that encrypted it, regardless of who owns or +has access to that AWS KMS key. The call succeeds only when the caller has kms:Decrypt +permission on the AWS KMS MRK. +The AWS Encryption SDK provides a standard AWS KMS discovery keyring and a discovery keyring +for AWS KMS multi-Region keys. Because it doesn't specify any wrapping keys, a discovery keyring +can't encrypt data. If you use a discovery keyring to encrypt data, alone or in a multi-keyring, +the encrypt operation fails. +The AWS Key Management Service (AWS KMS) MRK keyring interacts with AWS KMS to +create, encrypt, and decrypt data keys with multi-region AWS KMS keys (MRKs). +This example creates a KMS MRK Keyring and then encrypts a custom input exampleText +with an encryption context. This encrypted ciphertext is then decrypted using an +MRK Discovery keyring. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decrypted plaintext value matches exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. +For information about using multi-Region keys with the AWS Encryption SDK, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/configure.html#config-mrks +For more info on KMS MRKs (multi-region keys), see the KMS documentation: +https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html +For more information on how to use KMS Discovery keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery +For more information on KMS Key identifiers, see +https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id +*/ +package awskmsmrkdiscoverykeyring + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func AwsKmsMrkDiscoveryKeyringExample(exampleText, defaultRegionMrkKeyArn, defaultMRKKeyRegion, alternateRegionMrkKeyRegion, defaultKMSKeyAccountID string) { + // Step 1: Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClientEncrypt := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = defaultMRKKeyRegion + }) + kmsClientDecrypt := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = alternateRegionMrkKeyRegion + }) + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Create the keyring + // Though this example highlights Discovery keyrings, Discovery keyrings + // cannot be used to encrypt, so for encryption we create a KMS MRK keyring. + // So, we create two keyrings. One for encryption, second one for decryption + // First Keyring: Create KMS MRK Keyring used for encryption + awsKmsMrkKeyringInputEncrypt := mpltypes.CreateAwsKmsMrkKeyringInput{ + KmsClient: kmsClientEncrypt, + KmsKeyId: defaultRegionMrkKeyArn, + } + awsKmsMrkKeyringEncrypt, err := matProv.CreateAwsKmsMrkKeyring(context.Background(), awsKmsMrkKeyringInputEncrypt) + if err != nil { + panic(err) + } + // Second Keyring: create a Discovery keyring to use for decryption. + discoveryFilter := mpltypes.DiscoveryFilter{ + AccountIds: []string{defaultKMSKeyAccountID}, + Partition: "aws", + } + // In order to illustrate the MRK behavior of this keyring, we configure + // the keyring to use the second KMS region where the MRK is replicated to. + // This example assumes you have already replicated your key, but since we + // are using a discovery keyring, we don't need to provide the mrk replica key id + awsKmsMrkDiscoveryInput := mpltypes.CreateAwsKmsMrkDiscoveryKeyringInput{ + KmsClient: kmsClientDecrypt, + Region: alternateRegionMrkKeyRegion, + DiscoveryFilter: &discoveryFilter, + } + awsKmsMrkDiscoveryKeyring, err := matProv.CreateAwsKmsMrkDiscoveryKeyring(context.Background(), awsKmsMrkDiscoveryInput) + if err != nil { + panic(err) + } + // Step 4: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 6a: Encrypt + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: awsKmsMrkKeyringEncrypt, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption ARE the same") + } + // Step 6b: Decrypt + // Create a Discovery keyring to use for decryption. + // On Decrypt, the header of the encrypted message (ciphertext) will be parsed. + // The header contains the Encrypted Data Keys (EDKs), which, if the EDK + // was encrypted by a KMS Keyring, includes the KMS Key ARN. + // The Discovery Keyring filters these EDKs for + // EDKs encrypted by Single Region OR Multi Region KMS Keys. + // If a Discovery Filter is present, these KMS Keys must belong + // to an AWS Account ID in the discovery filter's AccountIds and + // must be from the discovery filter's partition. + // Finally, KMS is called to decrypt each filtered EDK until an EDK is + // successfully decrypted. The resulting data key is used to decrypt the + // ciphertext's message. + // If all calls to KMS fail, the decryption fails. + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: awsKmsMrkDiscoveryKeyring, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("AWS KMS MRK Discovery Keyring Example Completed Successfully") +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkdiscoverymultikeyring/awskmsmrkdiscoverymultikeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkdiscoverymultikeyring/awskmsmrkdiscoverymultikeyring.go new file mode 100644 index 000000000..54dbdf17a --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkdiscoverymultikeyring/awskmsmrkdiscoverymultikeyring.go @@ -0,0 +1,164 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +/* +This example sets up the AWS KMS MRK (multi-region key) Discovery Multi Keyring +AWS KMS MRK Discovery Multi Keyring is composed of multiple MRK discovery keyrings. +The AWS KMS discovery keyring is an AWS KMS keyring that doesn't specify any wrapping keys. +When decrypting, an MRK discovery keyring allows the AWS Encryption SDK to ask AWS KMS to decrypt +any encrypted data key by using the AWS KMS MRK that encrypted it, regardless of who owns or +has access to that AWS KMS key. The call succeeds only when the caller has kms:Decrypt +permission on the AWS KMS MRK. +The AWS Encryption SDK provides a standard AWS KMS discovery keyring and a discovery keyring +for AWS KMS multi-Region keys. Because it doesn't specify any wrapping keys, a discovery keyring +can't encrypt data. If you use a discovery keyring to encrypt data, alone or in a multi-keyring, +the encrypt operation fails. +The AWS Key Management Service (AWS KMS) MRK keyring interacts with AWS KMS to +create, encrypt, and decrypt data keys with multi-region AWS KMS keys (MRKs). +This example creates a KMS MRK Keyring and then encrypts a custom input exampleText +with an encryption context. This encrypted ciphertext is then decrypted using an +MRK Discovery Multi keyring. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decrypted plaintext value matches exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. +For information about using multi-Region keys with the AWS Encryption SDK, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/configure.html#config-mrks +For more info on KMS MRKs (multi-region keys), see the KMS documentation: +https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html +For more information on how to use KMS Discovery keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery +For more information on KMS Key identifiers, see +https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id +*/ +package awskmsmrkdiscoverymultikeyring + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func AwsKmsMrkDiscoveryMultiKeyringExample(exampleText, defaultRegionMrkKeyArn, defaultMRKKeyRegion, defaultKMSKeyAccountID string, regionsOfMRKKeys []string) { + // Step 1: Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = defaultMRKKeyRegion + }) + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Create the keyring + // Though this example highlights Discovery keyrings, Discovery keyrings + // cannot be used to encrypt, so for encryption we create a KMS MRK keyring. + // So, we create two keyrings. One for encryption, second one for decryption + // First Keyring: Create KMS MRK Keyring used for encryption + awsKmsMrkKeyringInput := mpltypes.CreateAwsKmsMrkKeyringInput{ + KmsClient: kmsClient, + KmsKeyId: defaultRegionMrkKeyArn, + } + awsKmsMrkKeyring, err := matProv.CreateAwsKmsMrkKeyring(context.Background(), awsKmsMrkKeyringInput) + if err != nil { + panic(err) + } + // Second Keyring: Create a MRK Discovery Multi Keyring to use for decryption + // We'll add a discovery filter to limit the set of encrypted data keys + // we are willing to decrypt to only ones created by KMS keys in select + // accounts and the partition `aws`. + // MRK Discovery keyrings also filter encrypted data keys by the region + // the keyring is created with. + discoveryFilter := mpltypes.DiscoveryFilter{ + AccountIds: []string{defaultKMSKeyAccountID}, + Partition: "aws", + } + awsKmsMrkDiscoveryMultiKeyringInput := mpltypes.CreateAwsKmsMrkDiscoveryMultiKeyringInput{ + Regions: regionsOfMRKKeys, + DiscoveryFilter: &discoveryFilter, + } + awsKmsMrkDiscoveryMultiKeyring, err := matProv.CreateAwsKmsMrkDiscoveryMultiKeyring(context.Background(), awsKmsMrkDiscoveryMultiKeyringInput) + // Step 4: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 6a: Encrypt + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: awsKmsMrkKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 6b: Decrypt + // On Decrypt, the header of the encrypted message (ciphertext) will be parsed. + // The header contains the Encrypted Data Keys (EDKs), which, if the EDK + // was encrypted by a KMS Keyring, includes the KMS Key ARN. + // The Discovery Keyring filters these EDKs for + // EDKs encrypted by Single Region OR Multi Region KMS Keys. + // If a Discovery Filter is present, these KMS Keys must belong + // to an AWS Account ID in the discovery filter's AccountIds and + // must be from the discovery filter's partition. + // Finally, KMS is called to decrypt each filtered EDK until an EDK is + // successfully decrypted. The resulting data key is used to decrypt the + // ciphertext's message. + // If all calls to KMS fail, the decryption fails. + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Keyring: awsKmsMrkDiscoveryMultiKeyring, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("AWS KMS MRK Discovery Multi Keyring Example Completed Successfully") +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkkeyring/awskmsmrkkeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkkeyring/awskmsmrkkeyring.go new file mode 100644 index 000000000..814a51328 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkkeyring/awskmsmrkkeyring.go @@ -0,0 +1,152 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +/* +This example sets up the AWS KMS MRK (multi-region key) Keyring +The AWS Key Management Service (AWS KMS) MRK keyring interacts with AWS KMS to +create, encrypt, and decrypt data keys with multi-region AWS KMS keys (MRKs). +This example creates a KMS MRK Keyring and then encrypts a custom input exampleText +with an encryption context. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decrypted plaintext value matches exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. +AWS KMS MRK keyrings can be used independently or in a multi-keyring with other keyrings +of the same or a different type. +For more information on how to use KMS keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html +For more info on KMS MRK (multi-region keys), see the KMS documentation: +https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html +For more information on KMS Key identifiers, see +https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id +*/ +package awskmsmrkkeyring + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func AwsKmsMrkKeyringExample(exampleText, defaultRegionMrkKeyArn, alternateRegionMrkKeyArn, defaultMRKKeyRegion, alternateRegionMrkKeyRegion string) { + // Step 1: Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClientEncrypt := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = defaultMRKKeyRegion + }) + kmsClientDecrypt := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = alternateRegionMrkKeyRegion + }) + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Create the keyrings + // Create one keyring for encrypt with KMS client on defaultMRKKeyRegion region + // Create second keyring for decrypt with KMS client on alternateRegionMrkKeyRegion region. + // In order to illustrate the MRK behavior, we are creating two keyrings with two different regions + awsKmsMrkKeyringInputEncrypt := mpltypes.CreateAwsKmsMrkKeyringInput{ + KmsClient: kmsClientEncrypt, + KmsKeyId: defaultRegionMrkKeyArn, + } + awsKmsMrkKeyringInputDecrypt := mpltypes.CreateAwsKmsMrkKeyringInput{ + KmsClient: kmsClientDecrypt, + KmsKeyId: alternateRegionMrkKeyArn, + } + awsKmsMrkKeyringEncrypt, err := matProv.CreateAwsKmsMrkKeyring(context.Background(), awsKmsMrkKeyringInputEncrypt) + if err != nil { + panic(err) + } + awsKmsMrkKeyringDecrypt, err := matProv.CreateAwsKmsMrkKeyring(context.Background(), awsKmsMrkKeyringInputDecrypt) + if err != nil { + panic(err) + } + // Step 4: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 6a: Encrypt + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: awsKmsMrkKeyringEncrypt, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 6b: Decrypt + // 1. Decrypt with the same keyring (same region) as encrypt + decryptOutputSameRegion, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: awsKmsMrkKeyringEncrypt, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutputSameRegion.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // 2. Decrypt with different keyring on different region. + decryptOutputDifferentRegion, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: awsKmsMrkKeyringDecrypt, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutputDifferentRegion.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutputDifferentRegion.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("AWS KMS MRK Keyring Example Completed Successfully") +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkmultikeyring/awskmsmrkmultikeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkmultikeyring/awskmsmrkmultikeyring.go new file mode 100644 index 000000000..76a09046f --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmrkmultikeyring/awskmsmrkmultikeyring.go @@ -0,0 +1,153 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +/* +This example sets up the AWS KMS MRK (multi-region key) Multi Keyring +The AWS Key Management Service (AWS KMS) MRK keyring interacts with AWS KMS to +create, encrypt, and decrypt data keys with AWS KMS MRK keys. +The KMS MRK multi-keyring consists of one or more individual keyrings of the +same or different type. The keys can either be regular KMS keys or MRKs. +The effect is like using several keyrings in a series. +This example creates a AwsKmsMrkMultiKeyring using an mrk_key_id (generator) and a kms_key_id +as a child key, and then encrypts a custom input exampleText with an encryption context. +Either KMS Key individually is capable of decrypting data encrypted under this keyring. +This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decrypted plaintext value matches exampleText +3. Ciphertext can be decrypted using an AwsKmsMrkKeyring containing a replica of the + MRK (from the multi-keyring used for encryption) copied from the first region into + the second region +These sanity checks are for demonstration in the example only. You do not need these in your code. +For more information on how to use KMS keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html +For more info on KMS MRK (multi-region keys), see the KMS documentation: +https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html +For more information on KMS Key identifiers, see +https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id +*/ +package awskmsmrkmultikeyring + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func AwsKmsMrkMultiKeyringExample(exampleText, defaultRegionMrkKeyArn, alternateRegionMrkKeyArn, defaultKMSKeyId, alternateRegionMrkKeyRegion string) { + // Step 1: Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = alternateRegionMrkKeyRegion + }) + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + defaultMrkKey := defaultRegionMrkKeyArn + // Step 3: Create the keyring + awsKmsMrkKeyringMultiInput := mpltypes.CreateAwsKmsMrkMultiKeyringInput{ + Generator: &defaultMrkKey, + KmsKeyIds: []string{defaultKMSKeyId}, + } + awsKmsMrkMultiKeyring, err := matProv.CreateAwsKmsMrkMultiKeyring(context.Background(), awsKmsMrkKeyringMultiInput) + if err != nil { + panic(err) + } + // Step 4: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 6a: Encrypt + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: awsKmsMrkMultiKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 6b: Decrypt + decryptOutputMulti, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: awsKmsMrkMultiKeyring, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutputMulti.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // Demonstrate that a single AwsKmsMrkKeyring configured with a replica of a MRK from the + // multi-keyring used to encrypt the data is also capable of decrypting the data. + awsKmsMrkKeyringInput := mpltypes.CreateAwsKmsMrkKeyringInput{ + KmsClient: kmsClient, + KmsKeyId: alternateRegionMrkKeyArn, + } + awsKmsMrkKeyring, err := matProv.CreateAwsKmsMrkKeyring(context.Background(), awsKmsMrkKeyringInput) + if err != nil { + panic(err) + } + decryptOutputMrk, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: awsKmsMrkKeyring, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutputMrk.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutputMrk.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("AWS KMS MRK Multi Keyring Example Completed Successfully") +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmultikeyring/awskmsmultikeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmultikeyring/awskmsmultikeyring.go new file mode 100644 index 000000000..ea6710deb --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsmultikeyring/awskmsmultikeyring.go @@ -0,0 +1,172 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* +This example sets up the AWS KMS Multi Keyring made up of multiple AWS KMS Keyrings. + +A multi-keyring is a keyring that consists of one or more individual keyrings of the +same or a different type. The effect is like using several keyrings in a series. +When you use a multi-keyring to encrypt data, any of the wrapping keys in any of its +keyrings can decrypt that data. + +When you create a multi-keyring to encrypt data, you designate one of the keyrings as +the generator keyring. All other keyrings are known as child keyrings. The generator keyring +generates and encrypts the plaintext data key. Then, all of the wrapping keys in all of the +child keyrings encrypt the same plaintext data key. The multi-keyring returns the plaintext +key and one encrypted data key for each wrapping key in the multi-keyring. If you create a +multi-keyring with no generator keyring, you can use it to decrypt data, but not to encrypt. +If the generator keyring is a KMS keyring, the generator key in the AWS KMS keyring generates +and encrypts the plaintext key. Then, all additional AWS KMS keys in the AWS KMS keyring, +and all wrapping keys in all child keyrings in the multi-keyring, encrypt the same plaintext key. + +When decrypting, the AWS Encryption SDK uses the keyrings to try to decrypt one of the encrypted +data keys. The keyrings are called in the order that they are specified in the multi-keyring. +Processing stops as soon as any key in any keyring can decrypt an encrypted data key. + +This example creates a Multi Keyring and then encrypts a custom input exampleText +with an encryption context. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decryption of ciphertext is possible using the multi_keyring, + and every one of the keyrings from the multi_keyring separately +3. All decrypted plaintext value match exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. + +This example creates a multi_keyring using a KMS keyring as generator keyring and +another KMS keyring as a child keyring. + +For more information on how to use Multi keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-multi-keyring.html + +For more information on KMS Key identifiers, see +https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id +*/ + +package awskmsmultikeyring + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func AwsKmsMultiKeyringExample(exampleText, defaultKMSKeyId, alternateRegionKMSKeyId, alternateRegionKMSKeyRegion string) { + // Step 1: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 2: Create an AwsKmsMultiKeyring that protects your data under two different KMS Keys. + // Either KMS Key individually is capable of decrypting data encrypted under this Multi Keyring. + generatorKeyId := defaultKMSKeyId + awsKmsMultiKeyringInput := mpltypes.CreateAwsKmsMultiKeyringInput{ + Generator: &generatorKeyId, + KmsKeyIds: []string{alternateRegionKMSKeyId}, + } + awsKmsMultiKeyring, err := matProv.CreateAwsKmsMultiKeyring(context.Background(), awsKmsMultiKeyringInput) + if err != nil { + panic(err) + } + // Step 3: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 4: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 5a: Encrypt + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: awsKmsMultiKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 5b: Decrypt + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: awsKmsMultiKeyring, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // Demonstrate that a single AwsKmsKeyring configured with either KMS key + // is also capable of decrypting the data. + // Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = alternateRegionKMSKeyRegion + }) + // Create a single AwsKmsKeyring with the KMS key from our second region. + awsKmsKeyringInput := mpltypes.CreateAwsKmsKeyringInput{ + KmsClient: kmsClient, + KmsKeyId: alternateRegionKMSKeyId, + } + awsKmsKeyring, err := matProv.CreateAwsKmsKeyring(context.Background(), awsKmsKeyringInput) + if err != nil { + panic(err) + } + decryptOutputKmsKeyring, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: res.Ciphertext, + EncryptionContext: encryptionContext, + Keyring: awsKmsKeyring, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutputKmsKeyring.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutputKmsKeyring.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("KMS Multi Keyring Example Completed Successfully") +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsrsakeyring/awskmsrsakeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsrsakeyring/awskmsrsakeyring.go new file mode 100644 index 000000000..4d6ba051b --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/awskmsrsakeyring/awskmsrsakeyring.go @@ -0,0 +1,127 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +/* +This example sets up the AWS KMS RSA Keyring +This example creates a KMS RSA Keyring and then encrypts a custom input +exampleText with an encryption context. +This example also includes some sanity checks for demonstration: + 1. Ciphertext and plaintext data are not the same + 2. Decrypted plaintext value matches exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. +# For more information on how to use KMS keyrings, see +# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html +For more information on KMS Key identifiers, see +https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id +*/ + +package awskmsrsakeyring + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" + kmstypes "github.com/aws/aws-sdk-go-v2/service/kms/types" +) + +func AwsKmsRsaExample(exampleText string, kmsRsaKeyID string, kmsRSAPublicKey []byte) { + // Step 1: Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = "us-west-2" + }) + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient( + mpltypes.MaterialProvidersConfig{}, + ) + if err != nil { + panic(err) + } + // Step 3: Create the keyring + awsKmsRSAKeyringInput := mpltypes.CreateAwsKmsRsaKeyringInput{ + KmsClient: kmsClient, + KmsKeyId: kmsRsaKeyID, + PublicKey: kmsRSAPublicKey, + EncryptionAlgorithm: kmstypes.EncryptionAlgorithmSpecRsaesOaepSha256, + } + awsKmsRSAKeyring, err := matProv.CreateAwsKmsRsaKeyring(context.Background(), awsKmsRSAKeyringInput) + if err != nil { + panic(err) + } + // Step 4: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 6a: Encrypt + algorithmSuiteID := mpltypes.ESDKAlgorithmSuiteIdAlgAes256GcmHkdfSha512CommitKey + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + AlgorithmSuiteId: &algorithmSuiteID, + EncryptionContext: encryptionContext, + Keyring: awsKmsRSAKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 6b: Decrypt + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: awsKmsRSAKeyring, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("AWS KMS RSA Keyring Example Completed Successfully") +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/awskmsecdhdiscoverykeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/awskmsecdhdiscoverykeyring.go new file mode 100644 index 000000000..ba6c960ba --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/awskmsecdhdiscoverykeyring.go @@ -0,0 +1,219 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* +This example sets up the KMS ECDH Discovery Keyring. + +This example takes in the recipient's KMS ECC key ARN. +This example attempts to decrypt a ciphertext using the kmsEcdhKeyIdP256RecipientKeyId, +it does so by checking if the message header contains the recipient's public key. + +This example also requires access to a KMS ECC key. +Our tests provide a KMS ECC Key ARN that anyone can use, but you +can also provide your own KMS ECC key. +To use your own KMS ECC key, you must have: + - kms:GetPublicKey permissions on that key. +This example will call kms:GetPublicKey on keyring creation. +You must also have kms:DeriveSharedSecret permissions on the KMS ECC key. + +This example creates a KMS ECDH Discovery Keyring and then decrypts a ciphertext. +For getting the ciphertext, we create a KMS ECDH keyring without discovery +because kms_ecdh_discovery_keyring cannot encrypt data. +This example also includes some sanity checks for demonstration: +1. Decrypted plaintext value matches exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. + +For more information on this configuration see: +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-ecdh-keyring.html#kms-ecdh-discovery +*/ + +package ecdh + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + primitivestypes "github.com/aws/aws-cryptographic-material-providers-library/primitives/awscryptographyprimitivessmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-encryption-sdk/examples/utils" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func AwsKmsEcdhDiscoveryKeyringExample( + exampleText string, + ecdhCurveSpec primitivestypes.ECDHCurveSpec, + kmsEcdhKeyIdP256RecipientKeyId string, + kmsEcdhKeyIdP256SenderKeyId string, + kmsEccPublicKeyFileNameSender string, + kmsEccPublicKeyFileNameRecipient string) { + // Step 1: Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = "us-west-2" + }) + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 4: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // For more information, see + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 5: Create the KMS ECDH keyring. + // This keyring uses the KmsPublicKeyDiscovery configuration. + // On encrypt, the keyring will fail as it is not allowed to encrypt data under this configuration. + // On decrypt, the keyring will check if its corresponding public key is stored in the message header. It + // will call AWS KMS to derive the shared from the recipient's KMS ECC Key ARN and the sender's public key; + // For more information on this configuration see: + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-ecdh-keyring.html#kms-ecdh-discovery + // This keyring takes in: + // - kmsClient + // - recipientKmsIdentifier: Must be an ARN representing a KMS ECC key meant for KeyAgreement + // - curveSpec: The curve name where the public keys lie + kmsEcdhDiscoveryStaticConfigurationInput := mpltypes.KmsPublicKeyDiscoveryInput{ + RecipientKmsIdentifier: kmsEcdhKeyIdP256RecipientKeyId, + } + kmsEcdhDiscoveryStaticConfiguration := &mpltypes.KmsEcdhStaticConfigurationsMemberKmsPublicKeyDiscovery{ + Value: kmsEcdhDiscoveryStaticConfigurationInput, + } + awsKmsEcdhDiscoveryKeyringInput := mpltypes.CreateAwsKmsEcdhKeyringInput{ + CurveSpec: ecdhCurveSpec, + KeyAgreementScheme: kmsEcdhDiscoveryStaticConfiguration, + KmsClient: kmsClient, + } + awsKmsEcdhDiscoveryKeyring, err := matProv.CreateAwsKmsEcdhKeyring(context.Background(), awsKmsEcdhDiscoveryKeyringInput) + if err != nil { + panic(err) + } + // Step 6: Get ciphertext by creating a KMS ECDH keyring WITHOUT discovery + // because the KMS ECDH keyring WITH discovery CANNOT encrypt data. + // We are generating a message intended for the kmsEcdhKeyIdP256RecipientKeyId recipient. + // Since a KMS ECDH keyring WITHOUT discovery cannot encrypt data, this example will ONLY decrypt + // messages where the configured key on the Discovery keyring is present on the message ciphertext. + // In this example we call `kms:GetPublicKey` to get the public key associated with the + // kmsEcdhKeyIdP256RecipientKeyId KMS key ID. + // If the message contains this public key, message decryption will be attempted. + cipherText := getCipherTextKmsEcdh(matProv, encryptionClient, ecdhCurveSpec, exampleText, encryptionContext, kmsClient, kmsEcdhKeyIdP256RecipientKeyId, kmsEcdhKeyIdP256SenderKeyId, kmsEccPublicKeyFileNameSender, kmsEccPublicKeyFileNameRecipient) + + // Step 7: Decrypt your encrypted data using the keyring with discovery behavior we created in step 5. + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Keyring: awsKmsEcdhDiscoveryKeyring, + EncryptionContext: encryptionContext, + Ciphertext: cipherText, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + if string(decryptOutput.Plaintext) == exampleText { + fmt.Println("AWS KMS ECDH Discovery Keyring Example Completed Successfully") + } else { + panic("FAILED!") + } +} + +// This function creates a AWS KMS ECDH keyring and encrypt the exampleText +func getCipherTextKmsEcdh( + matProv *mpl.Client, + encryptionClient *client.Client, + ecdhCurveSpec primitivestypes.ECDHCurveSpec, + exampleText string, + encryptionContext map[string]string, + kmsClient *kms.Client, + kmsEcdhKeyIdP256RecipientKeyId string, + kmsEcdhKeyIdP256SenderKeyId string, + kmsEccPublicKeyFileNameSender string, + kmsEccPublicKeyFileNameRecipient string) []byte { + // 1. Create the public key files for sender and recipient + // You may provide your own ECC keys. + // If not, this class will call the KMS ECC key, retrieve its public key, and store it + // in a PEM file for example use. + // Sender ECC key used in this example is retrieved with kmsEcdhKeyIdP256SenderKeyId + // Recipent ECC key used in this example is retrieved with kmsEcdhKeyIdP256RecipientKeyId + if !utils.FileExists(kmsEccPublicKeyFileNameSender) { + err := utils.WriteKmsEcdhEccPublicKey(kmsEcdhKeyIdP256SenderKeyId, kmsEccPublicKeyFileNameSender, kmsClient) + if err != nil { + panic(err) + } + } + if !utils.FileExists(kmsEccPublicKeyFileNameRecipient) { + err := utils.WriteKmsEcdhEccPublicKey(kmsEcdhKeyIdP256RecipientKeyId, kmsEccPublicKeyFileNameRecipient, kmsClient) + if err != nil { + panic(err) + } + } + // 2. Load public key from UTF-8 encoded PEM files into a DER encoded public key. + publicKeySender, err := utils.LoadPublicKeyFromPEM(kmsEccPublicKeyFileNameSender) + if err != nil { + panic(err) + } + publicKeyRecipient, err := utils.LoadPublicKeyFromPEM(kmsEccPublicKeyFileNameRecipient) + if err != nil { + panic(err) + } + // 3. Create the KmsPrivateKeyToStaticPublicKeyInput and kmsEcdhStaticConfiguration + kmsEcdhStaticConfigurationInput := mpltypes.KmsPrivateKeyToStaticPublicKeyInput{ + RecipientPublicKey: publicKeyRecipient, + SenderKmsIdentifier: kmsEcdhKeyIdP256SenderKeyId, + SenderPublicKey: publicKeySender, + } + kmsEcdhStaticConfiguration := &mpltypes.KmsEcdhStaticConfigurationsMemberKmsPrivateKeyToStaticPublicKey{ + Value: kmsEcdhStaticConfigurationInput, + } + // 4. Create the KMS ECDH keyring. + awsKmsEcdhKeyringInput := mpltypes.CreateAwsKmsEcdhKeyringInput{ + CurveSpec: ecdhCurveSpec, + KeyAgreementScheme: kmsEcdhStaticConfiguration, + KmsClient: kmsClient, + } + awsKmsEcdhKeyring, err := matProv.CreateAwsKmsEcdhKeyring(context.Background(), awsKmsEcdhKeyringInput) + if err != nil { + panic(err) + } + // 5. Encrypt the data with the encryption_context + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: awsKmsEcdhKeyring, + }) + if err != nil { + panic(err) + } + // 6. Return the ciphertext + return res.Ciphertext +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/awskmsecdhkeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/awskmsecdhkeyring.go new file mode 100644 index 000000000..fd51138cb --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/awskmsecdhkeyring.go @@ -0,0 +1,193 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* +This example sets up the KMS ECDH Keyring. + +This example takes in the sender's KMS ECC key ARN, the sender's public key, +the recipient's public key, and the algorithm definition where the ECC keys lie. + +Both public keys MUST be UTF8 PEM-encoded X.509 public key, +also known as SubjectPublicKeyInfo (SPKI), + +This keyring, depending on its KeyAgreement scheme, +takes in the sender's KMS ECC Key ARN, and the recipient's ECC Public Key +to derive a shared secret. +The keyring uses the shared secret to derive a data key to protect the +data keys that encrypt and decrypt exampletext. + +This example also requires access to a KMS ECC key. +Our tests provide a KMS ECC Key ARN that you need permissions to, but you +can also provide your own KMS ECC key. +To use your own KMS ECC key, you must have either: +- Its public key downloaded in a UTF-8 encoded PEM file +- kms:GetPublicKey permissions on that key. +If you do not have the public key downloaded, running this example +through its main method will download the public key for you +by calling kms:GetPublicKey. +You must also have kms:DeriveSharedSecret permissions on the KMS ECC key. +This example also requires a recipient ECC Public Key that lies on the same +curve as the sender public key. This examples uses another distinct +KMS ECC Public Key, it does not have to be a KMS key; it can be a +valid SubjectPublicKeyInfo (SPKI) Public Key. + +This example creates a KMS ECDH Keyring and then encrypts a custom input exampleText +with an encryption context. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decrypted plaintext value matches exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. + +For more information on this configuration see: +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-ecdh-keyring.html#kms-ecdh-create +*/ + +package ecdh + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + primitivestypes "github.com/aws/aws-cryptographic-material-providers-library/primitives/awscryptographyprimitivessmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-encryption-sdk/examples/utils" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func AwsKmsEcdhKeyringExample( + exampleText string, + ecdhCurveSpec primitivestypes.ECDHCurveSpec, + kmsEcdhKeyIdP256RecipientKeyId string, + kmsEcdhKeyIdP256SenderKeyId string, + kmsEccPublicKeyFileNameSender string, + kmsEccPublicKeyFileNameRecipient string) { + // Step 1: Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = "us-west-2" + }) + // Step 2: Load public key from UTF-8 encoded PEM files into a DER encoded public key. + // You may provide your own ECC keys. + // If not, this class will call the KMS ECC key, retrieve its public key, and store it + // in a PEM file for example use. + if !utils.FileExists(kmsEccPublicKeyFileNameSender) { + err = utils.WriteKmsEcdhEccPublicKey(kmsEcdhKeyIdP256SenderKeyId, kmsEccPublicKeyFileNameSender, kmsClient) + if err != nil { + panic(err) + } + } + if !utils.FileExists(kmsEccPublicKeyFileNameRecipient) { + err = utils.WriteKmsEcdhEccPublicKey(kmsEcdhKeyIdP256RecipientKeyId, kmsEccPublicKeyFileNameRecipient, kmsClient) + if err != nil { + panic(err) + } + } + publicKeySender, err := utils.LoadPublicKeyFromPEM(kmsEccPublicKeyFileNameSender) + if err != nil { + panic(err) + } + publicKeyRecipient, err := utils.LoadPublicKeyFromPEM(kmsEccPublicKeyFileNameRecipient) + if err != nil { + panic(err) + } + // Step 3: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 4: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // For more information, see + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 6: Create the KMS ECDH keyring. + // This keyring uses the KmsPrivateKeyToStaticPublicKey configuration. This configuration calls for both of + // the keys to be on the same curve (P256, P384, P521). + // On encrypt, the keyring calls AWS KMS to derive the shared secret from the sender's KMS ECC Key ARN and the recipient's public key. + // For this example, on decrypt, the keyring calls AWS KMS to derive the shared secret from the sender's KMS ECC Key ARN and the recipient's public key; + // however, on decrypt, the recipient can construct a keyring such that the shared secret is calculated with + // the recipient's private key and the sender's public key. In both scenarios the shared secret will be the same. + // For more information on this configuration see: + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-ecdh-keyring.html#kms-ecdh-create + // This keyring takes in: + // - kmsClient + // - kmsKeyId: Must be an ARN representing a KMS ECC key meant for KeyAgreement + // - curveSpec: The curve name where the public keys lie + // - senderPublicKey: A ByteBuffer of a UTF-8 encoded public + // key for the key passed into kmsKeyId in DER format + // - recipientPublicKey: A ByteBuffer of a UTF-8 encoded public + // key for the key passed into kmsKeyId in DER format + kmsEcdhStaticConfigurationInput := mpltypes.KmsPrivateKeyToStaticPublicKeyInput{ + RecipientPublicKey: publicKeyRecipient, + SenderKmsIdentifier: kmsEcdhKeyIdP256SenderKeyId, + SenderPublicKey: publicKeySender, + } + kmsEcdhStaticConfiguration := &mpltypes.KmsEcdhStaticConfigurationsMemberKmsPrivateKeyToStaticPublicKey{ + Value: kmsEcdhStaticConfigurationInput, + } + awsKmsEcdhKeyringInput := mpltypes.CreateAwsKmsEcdhKeyringInput{ + CurveSpec: ecdhCurveSpec, + KeyAgreementScheme: kmsEcdhStaticConfiguration, + KmsClient: kmsClient, + } + awsKmsEcdhKeyring, err := matProv.CreateAwsKmsEcdhKeyring(context.Background(), awsKmsEcdhKeyringInput) + if err != nil { + panic(err) + } + // Step 7a: Encrypt the data + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: awsKmsEcdhKeyring, + }) + if err != nil { + panic(err) + } + // Step 7b: Decrypt the data + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: res.Ciphertext, + EncryptionContext: encryptionContext, + Keyring: awsKmsEcdhKeyring, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + if string(decryptOutput.Plaintext) == exampleText { + fmt.Println("AWS KMS ECDH Keyring Example Completed Successfully") + } else { + panic("FAILED!") + } +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/ephemeralrawecdhkeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/ephemeralrawecdhkeyring.go new file mode 100644 index 000000000..2368826d3 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/ephemeralrawecdhkeyring.go @@ -0,0 +1,137 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* +This example sets up the Ephemeral Raw ECDH Keyring. + +This example takes in the recipient's public key located at +eccPublicKeyFileNameRecipient as a +UTF8 PEM-encoded X.509 public key, +and the Curve Specification where the key lies. + +This example loads ECC keys from PEM files with paths defined in + - eccPublicKeyFileNameRecipient + +If you do not provide these files, running this example through this +class' main method will generate three files required for all raw ECDH examples +eccPrivateKeyFileNameSender, eccPrivateKeyFileNameRecipient +and eccPublicKeyFileNameRecipient for you. +In practice, users of this library should not generate new key pairs +like this, and should instead retrieve an existing key from a secure +key management system (e.g. an HSM). +You may also provide your own key pair by placing PEM files in the +directory where the example is run or modifying the paths in the code +below. These files must be valid PEM encodings of the key pair as UTF-8 +encoded bytes. If you do provide your own key pair, or if a key pair +already exists, this class' main method will not generate a new key pair. + +This examples creates a RawECDH keyring with the EphemeralPrivateKeyToStaticPublicKey key agreement scheme. +This configuration will always create a new key pair as the sender key pair for the key agreement operation. +The ephemeral configuration can only encrypt data and CANNOT decrypt messages. + +This example creates an Ephemeral Raw ECDH Keyring and then encrypts a custom input exampleText +with an encryption context. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +These sanity checks are for demonstration in the example only. You do not need these in your code. + +For more information on this configuration see: +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-ecdh-keyring.html#raw-ecdh-EphemeralPrivateKeyToStaticPublicKey +*/ + +package ecdh + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + primitivestypes "github.com/aws/aws-cryptographic-material-providers-library/primitives/awscryptographyprimitivessmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-encryption-sdk/examples/utils" +) + +func EphemeralRawECDHKeyringExample( + exampleText string, + ecdhCurveSpec primitivestypes.ECDHCurveSpec, + eccPublicKeyFileNameRecipient string) { + // Step 1: Generate Raw ECDH ECC keys and load public key. + // You may provide your own ECC keys in the files returned by eccPublicKeyFileNameRecipient + + // If you do not provide these files, running this example through this + // class' main method will generate three files required for all raw ECDH examples + // eccPrivateKeyFileNameSender, eccPrivateKeyFileNameRecipient + // and eccPublicKeyFileNameRecipient for you. + if !utils.FileExists(eccPublicKeyFileNameRecipient) { + err := utils.WriteRawEcdhEccKeys(ecdhCurveSpec) + if err != nil { + panic(err) + } + } + publicKeyRecipient, err := utils.LoadPublicKeyFromPEM(eccPublicKeyFileNameRecipient) + if err != nil { + panic(err) + } + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 4: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 5: Create the keyring. + // This keyring uses an ephemeral configuration. This configuration will always create a new + // key pair as the sender key pair for the key agreement operation. The ephemeral configuration can only + // encrypt data and CANNOT decrypt messages. + ephemeralRawEcdhStaticConfigurationInput := mpltypes.EphemeralPrivateKeyToStaticPublicKeyInput{ + RecipientPublicKey: publicKeyRecipient, + } + ephemeralRawECDHStaticConfiguration := + mpltypes.RawEcdhStaticConfigurationsMemberEphemeralPrivateKeyToStaticPublicKey{ + Value: ephemeralRawEcdhStaticConfigurationInput, + } + rawEcdhKeyRingInput := mpltypes.CreateRawEcdhKeyringInput{ + CurveSpec: ecdhCurveSpec, + KeyAgreementScheme: &ephemeralRawECDHStaticConfiguration, + } + ecdhKeyring, err := matProv.CreateRawEcdhKeyring(context.Background(), rawEcdhKeyRingInput) + if err != nil { + panic(err) + } + // Step 6: Encrypt + // A raw ecdh keyring with Ephemeral configuration cannot decrypt data since the key pair + // used as the sender is ephemeral. This means that at decrypt time it does not have + // the private key that corresponds to the public key that is stored on the message. + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: ecdhKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + // (This is an example for demonstration; you do not need to do this in your own code.) + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + fmt.Println("Ephemeral Raw ECDH Keyring Example Completed Successfully") +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/publickeyrawdiscoveryecdhkeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/publickeyrawdiscoveryecdhkeyring.go new file mode 100644 index 000000000..71d30bed3 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/publickeyrawdiscoveryecdhkeyring.go @@ -0,0 +1,218 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* +This example sets up the Public Key Discovery Raw ECDH Keyring. + +A public key discovery Raw ECDH Keyring takes in the recipient's private key located +at eccPrivateKeyFileNameRecipient +as a UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures) private key, +and the Curve Specification where the key lies. + +If you provide the eccPrivateKeyFileNameRecipient, make sure to also +provide the recipient's public key located at eccPublicKeyFileNameRecipient +in the directory that you run this example. Even though the Public Key Discovery Raw ECDH keyring +uses the eccPrivateKeyFileNameRecipient to decrypt the data, +the eccPublicKeyFileNameRecipient is needed to generate the ciphertext to decrypt. + +This example loads ECC keys from PEM files and the ciphertext with paths defined in + - eccPrivateKeyFileNameRecipient + - eccPublicKeyFileNameRecipient + +If you do not provide these files, running this example through this +class' main method will generate three files required for all raw ECDH examples +eccPrivateKeyFilenameSender, eccPrivateKeyFileNameRecipient +and eccPublicKeyFileNameRecipient for you. +In practice, users of this library should not generate new key pairs +like this, and should instead retrieve an existing key from a secure +key management system (e.g. an HSM). +You may also provide your own key pair by placing PEM files in the +directory where the example is run or modifying the paths in the code +below. These files must be valid PEM encodings of the key pair as UTF-8 +encoded bytes. If you do provide your own key pair, or if a key pair +already exists, this class' main method will not generate a new key pair. + +This example creates a RawECDH keyring with the PublicKeyDiscovery key agreement scheme. +This scheme is only available on decrypt. + +This example creates a Public Key Discovery Raw ECDH Keyring and takes in a ciphertext to decrypt it. +This example also includes some sanity checks for demonstration: +1. Decrypted plaintext value matches exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. + +For more information on this configuration see: +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-ecdh-keyring.html#raw-ecdh-PublicKeyDiscovery +*/ + +package ecdh + +import ( + "context" + "fmt" + "os" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + primitivestypes "github.com/aws/aws-cryptographic-material-providers-library/primitives/awscryptographyprimitivessmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-encryption-sdk/examples/utils" +) + +func PublicKeyRawEcdhDiscoveryKeyringExample( + exampleText string, + ecdhCurveSpec primitivestypes.ECDHCurveSpec, + eccPublicKeyFileNameRecipient string, + eccPrivateKeyFileNameRecipient string) { + // Step 1: Generate Raw ECDH ECC keys and load the recipient's private key. + // You may provide your own ECC keys in the files returned by eccPublicKeyFileNameRecipient + + // If you do not provide these files, running this example through this + // class' main method will generate three files required for all raw ECDH examples + // eccPrivateKeyFileNameSender, eccPrivateKeyFileNameRecipient + // and eccPublicKeyFileNameRecipient for you. + if !utils.FileExists(eccPublicKeyFileNameRecipient) { + err := utils.WriteRawEcdhEccKeys(ecdhCurveSpec) + if err != nil { + panic(err) + } + } + privateKeyRecipient, err := os.ReadFile(eccPrivateKeyFileNameRecipient) + if err != nil { + panic(err) + } + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 4: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // For more information, see + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 5: Create the Public Key Discovery Raw ECDH keyring. + // Create the keyring. + // This keyring uses a discovery configuration. This configuration will check on decrypt + // if it is meant to decrypt the message by checking if the configured public key is stored on the message. + // The discovery configuration can only decrypt messages and CANNOT encrypt messages. + discoveryRawEcdhStaticConfigurationInput := mpltypes.PublicKeyDiscoveryInput{ + RecipientStaticPrivateKey: privateKeyRecipient, + } + discoveryRawEcdhStaticConfiguration := &mpltypes.RawEcdhStaticConfigurationsMemberPublicKeyDiscovery{ + Value: discoveryRawEcdhStaticConfigurationInput, + } + discoveryRawEcdhKeyringInput := mpltypes.CreateRawEcdhKeyringInput{ + CurveSpec: ecdhCurveSpec, + KeyAgreementScheme: discoveryRawEcdhStaticConfiguration, + } + discoveryRawEcdhKeyring, err := matProv.CreateRawEcdhKeyring(context.Background(), discoveryRawEcdhKeyringInput) + if err != nil { + panic(err) + } + // Step 6a: Get the ciphertext + // Although this example highlights Public Key Discovery Raw ECDH Keyring keyring, Discovery keyrings cannot + // be used to encrypt, so for encryption we create a Ephemeral Raw ECDH keyring without discovery mode. + cipherText := getCipherTextRawEcdh(matProv, encryptionClient, ecdhCurveSpec, exampleText, encryptionContext, eccPublicKeyFileNameRecipient) + // Step 6b: Decrypt + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Keyring: discoveryRawEcdhKeyring, + EncryptionContext: encryptionContext, + Ciphertext: cipherText, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + if string(decryptOutput.Plaintext) == exampleText { + fmt.Println("Public Key Discovery Raw ECDH Keyring Example Completed Successfully") + } else { + panic("FAILED!") + } +} + +// This function creates a Ephemeral Raw ECDH keyring and encrypt the exampleText +func getCipherTextRawEcdh( + matProv *mpl.Client, + encryptionClient *client.Client, + ecdhCurveSpec primitivestypes.ECDHCurveSpec, + exampleText string, + encryptionContext map[string]string, + eccPublicKeyFileNameRecipient string) []byte { + // 1. Generate Raw ECDH ECC keys and load public key. + // You may provide your own ECC keys in the files returned by eccPublicKeyFileNameRecipient + + // If you do not provide these files, running this example through this + // class' main method will generate three files required for all raw ECDH examples + // eccPrivateKeyFileNameSender, eccPrivateKeyFileNameRecipient + // and eccPublicKeyFileNameRecipient for you. + // Load public key from UTF-8 encoded PEM files into a DER encoded public key. + if !utils.FileExists(eccPublicKeyFileNameRecipient) { + err := utils.WriteRawEcdhEccKeys(ecdhCurveSpec) + if err != nil { + panic(err) + } + } + publicKeyRecipient, err := utils.LoadPublicKeyFromPEM(eccPublicKeyFileNameRecipient) + if err != nil { + panic(err) + } + // Create the RawEcdhStaticConfigurations + ephemeralRawEcdhStaticConfigurationInput := mpltypes.EphemeralPrivateKeyToStaticPublicKeyInput{ + RecipientPublicKey: publicKeyRecipient, + } + ephemeralRawECDHStaticConfiguration := mpltypes.RawEcdhStaticConfigurationsMemberEphemeralPrivateKeyToStaticPublicKey{ + Value: ephemeralRawEcdhStaticConfigurationInput, + } + // Create the Ephemeral Raw ECDH keyring. + // This keyring uses an ephemeral configuration. This configuration will always create a new + // key pair as the sender key pair for the key agreement operation. The ephemeral configuration can only + // encrypt data and CANNOT decrypt messages. + rawEcdhKeyRingInput := mpltypes.CreateRawEcdhKeyringInput{ + CurveSpec: ecdhCurveSpec, + KeyAgreementScheme: &ephemeralRawECDHStaticConfiguration, + } + ecdhKeyring, err := matProv.CreateRawEcdhKeyring(context.Background(), rawEcdhKeyRingInput) + if err != nil { + panic(err) + } + // Encrypt the data + // A raw ecdh keyring with Ephemeral configuration cannot decrypt data since the key pair + // used as the sender is ephemeral. This means that at decrypt time it does not have + // the private key that corresponds to the public key that is stored on the message. + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: ecdhKeyring, + }) + if err != nil { + panic(err) + } + return res.Ciphertext +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/rawecdhkeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/rawecdhkeyring.go new file mode 100644 index 000000000..461b1be66 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/ecdh/rawecdhkeyring.go @@ -0,0 +1,194 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* +This example sets up the Raw ECDH Keyring. + +This example takes in the sender's private key located at +eccPrivateKeyFileNameSender as a UTF8 PEM-encoded +(PKCS #8 PrivateKeyInfo structures) private key, +and the recipient's public key located at +eccPublicKeyFileNameRecipient as a +UTF8 PEM-encoded X.509 public key, +also known as SubjectPublicKeyInfo (SPKI), +and the Curve Specification where the keys lie. + +This example loads ECC keys from PEM files with paths defined in + - eccPrivateKeyFileNameSender + - eccPublicKeyFileNameRecipient + +If you do not provide these files, running this example through this +class' main method will generate three files required for all raw ECDH examples +eccPrivateKeyFileNameSender, eccPrivateKeyFileNameRecipient +and eccPublicKeyFileNameRecipient for you. +These files will be generated in the directory where the example is run. +In practice, users of this library should not generate new key pairs +like this, and should instead retrieve an existing key from a secure +key management system (e.g. an HSM). +You may also provide your own key pair by placing PEM files in the +directory where the example is run or modifying the paths in the code +below. These files must be valid PEM encodings of the key pair as UTF-8 +encoded bytes. If you do provide your own key pair, or if a key pair +already exists, this class' main method will not generate a new key pair. + +This example creates a RawECDH keyring with the RawPrivateKeyToStaticPublicKey key agreement scheme. +On encrypt, the shared secret is derived from the sender's private key and the recipient's public key. +On decrypt, the shared secret is derived from the sender's private key and the recipient's public key; +however, on decrypt the recipient can construct a keyring such that the shared secret is calculated with +the recipient's private key and the sender's public key. In both scenarios the shared secret will be the same. + +This example creates a Raw ECDH Keyring and then encrypts a custom input exampleText +with an encryption context. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decrypted plaintext value matches exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. + +For more information on this configuration see: +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-ecdh-keyring.html#raw-ecdh-RawPrivateKeyToStaticPublicKey +*/ + +package ecdh + +import ( + "context" + "fmt" + "os" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + primitivestypes "github.com/aws/aws-cryptographic-material-providers-library/primitives/awscryptographyprimitivessmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-encryption-sdk/examples/utils" +) + +func RawECDHKeyringExample( + exampleText string, + ecdhCurveSpec primitivestypes.ECDHCurveSpec, + eccPublicKeyFileNameRecipient string, + eccPrivateKeyFileNameSender string) { + // Step 1: Generate Raw ECDH ECC keys and load public key. + // You may provide your own ECC keys in the files returned by eccPublicKeyFileNameRecipient + + // If you do not provide these files, running this example through this + // class' main method will generate three files required for all raw ECDH examples + // eccPrivateKeyFileNameSender, eccPrivateKeyFileNameRecipient + // and eccPublicKeyFileNameRecipient for you. + if !utils.FileExists(eccPublicKeyFileNameRecipient) || !utils.FileExists(eccPrivateKeyFileNameSender) { + err := utils.WriteRawEcdhEccKeys(ecdhCurveSpec) + if err != nil { + panic(err) + } + } + privateKeySender, err := os.ReadFile(eccPrivateKeyFileNameSender) + if err != nil { + panic(err) + } + publicKeyRecipient, err := utils.LoadPublicKeyFromPEM(eccPublicKeyFileNameRecipient) + if err != nil { + panic(err) + } + + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 4: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // For more information, see + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 5: Create the Raw ECDH keyring. + // This keyring uses static sender and recipient keys. This configuration calls for both of + // the keys to be on the same curve (P256 / P384 / P521). + // On encrypt, the shared secret is derived from the sender's private key and the recipient's public key. + // For this example, on decrypt, the shared secret is derived from the sender's private key and the recipient's public key; + // However, on decrypt, the recipient can construct a keyring such that the shared secret is calculated with + // the recipient's private key and the sender's public key. In both scenarios the shared secret will be the same. + RawEcdhStaticConfigurationInput := mpltypes.RawPrivateKeyToStaticPublicKeyInput{ + SenderStaticPrivateKey: privateKeySender, + RecipientPublicKey: publicKeyRecipient, + } + RawECDHStaticConfiguration := &mpltypes.RawEcdhStaticConfigurationsMemberRawPrivateKeyToStaticPublicKey{ + Value: RawEcdhStaticConfigurationInput, + } + rawEcdhKeyRingInput := mpltypes.CreateRawEcdhKeyringInput{ + CurveSpec: ecdhCurveSpec, + KeyAgreementScheme: RawECDHStaticConfiguration, + } + rawEcdhKeyring, err := matProv.CreateRawEcdhKeyring(context.Background(), rawEcdhKeyRingInput) + if err != nil { + panic(err) + } + // Step 6a: Encrypt + // A raw ecdh keyring with Ephemeral configuration cannot decrypt data since the key pair + // used as the sender is ephemeral. This means that at decrypt time it does not have + // the private key that corresponds to the public key that is stored on the message. + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: rawEcdhKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + // (This is an example for demonstration; you do not need to do this in your own code.) + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 6b: Decrypt + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: res.Ciphertext, + EncryptionContext: encryptionContext, + Keyring: rawEcdhKeyring, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + if string(decryptOutput.Plaintext) == exampleText { + fmt.Println("Raw ECDH Keyring Example Completed Successfully") + } else { + panic("FAILED!") + } +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/multikeyring/multikeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/multikeyring/multikeyring.go new file mode 100644 index 000000000..32d4cc5c7 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/multikeyring/multikeyring.go @@ -0,0 +1,233 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* +This example sets up the Multi Keyring + +A multi-keyring is a keyring that consists of one or more individual keyrings of the +same or a different type. The effect is like using several keyrings in a series. +When you use a multi-keyring to encrypt data, any of the wrapping keys in any of its +keyrings can decrypt that data. + +When you create a multi-keyring to encrypt data, you designate one of the keyrings as +the generator keyring. All other keyrings are known as child keyrings. The generator keyring +generates and encrypts the plaintext data key. Then, all of the wrapping keys in all of the +child keyrings encrypt the same plaintext data key. The multi-keyring returns the plaintext +key and one encrypted data key for each wrapping key in the multi-keyring. If you create a +multi-keyring with no generator keyring, you can use it to decrypt data, but not to encrypt. +If the generator keyring is a KMS keyring, the generator key in the AWS KMS keyring generates +and encrypts the plaintext key. Then, all additional AWS KMS keys in the AWS KMS keyring, +and all wrapping keys in all child keyrings in the multi-keyring, encrypt the same plaintext key. + +When decrypting, the AWS Encryption SDK uses the keyrings to try to decrypt one of the encrypted +data keys. The keyrings are called in the order that they are specified in the multi-keyring. +Processing stops as soon as any key in any keyring can decrypt an encrypted data key. + +This example creates a Multi Keyring and then encrypts a custom input exampleText +with an encryption context. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decryption of ciphertext is possible using the multi_keyring, +and every one of the keyrings from the multi_keyring separately +3. All decrypted plaintext value match exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. + +This example creates a multi_keyring using a KMS keyring as generator keyring and a raw AES keyring +as a child keyring. You can use different combinations of keyrings in the multi_keyring. + +For more information on how to use Multi keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-multi-keyring.html + +For more information on KMS Key identifiers, see +https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id +*/ + +package multikeyring + +import ( + "context" + "crypto/rand" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func MultiKeyringExample(exampleText, defaultKMSKeyId, defaultKmsKeyRegion string) { + // Step 1: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 2: Create the MultiKeyring that consists of the KMS Keyring as generator and Raw AES Keyring as child keyring + // When using this MultiKeyring to encrypt data, either KMS Keyring or + // Raw AES Keyring (or a MultiKeyring containing either) may be used to decrypt the data + awsKmsKeyring := getKMSKeyring(defaultKMSKeyId, defaultKmsKeyRegion, matProv) + rawAESKeyring := getRawAESKeyring(matProv) + createMultiKeyringInput := mpltypes.CreateMultiKeyringInput{ + Generator: awsKmsKeyring, + ChildKeyrings: []mpltypes.IKeyring{rawAESKeyring}, + } + multiKeyring, err := matProv.CreateMultiKeyring(context.Background(), createMultiKeyringInput) + if err != nil { + panic(err) + } + // Step 3: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 4: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 5a: Encrypt + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: multiKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 5b: Decrypt + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: multiKeyring, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // Demonstrate that you can also successfully decrypt data using the `rawAESKeyring` directly. + // Because you used a MultiKeyring on Encrypt, you can use either the `kmsKeyring` or + // `rawAESKeyring` individually to decrypt the data. + decryptOutputRawAES, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: res.Ciphertext, + EncryptionContext: encryptionContext, + Keyring: rawAESKeyring, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutputRawAES.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutputRawAES.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // Demonstrate that you can also successfully decrypt data using the `awsKmsKeyring` directly. + decryptOutputAwsKms, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: res.Ciphertext, + EncryptionContext: encryptionContext, + Keyring: awsKmsKeyring, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutputAwsKms.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutputAwsKms.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("Multi Keyring Example Completed Successfully") +} +func getKMSKeyring(kmsKeyId string, kmsRegion string, matProv *mpl.Client) mpltypes.IKeyring { + // 1. Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = kmsRegion + }) + // 2. Create Aws Kms keyring + awsKmsKeyringInput := mpltypes.CreateAwsKmsKeyringInput{ + KmsClient: kmsClient, + KmsKeyId: kmsKeyId, + } + awsKmsKeyring, err := matProv.CreateAwsKmsKeyring(context.Background(), awsKmsKeyringInput) + if err != nil { + panic(err) + } + return awsKmsKeyring +} +func getRawAESKeyring(matProv *mpl.Client) mpltypes.IKeyring { + // 1. Generate a 256-bit AES key to use with your keyring. + // In practice, you should get this key from a secure key management system such as an HSM. + key, err := generateAes256KeyBytes() + if err != nil { + panic(err) + } + // The key namespace and key name are defined by you + // and are used by the raw AES keyring to determine + // whether it should attempt to decrypt an encrypted data key. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-aes-keyring + var keyNamespace = "A managed aes keys" + var keyName = "My 256-bit AES wrapping key" + // 2. Create the keyring + aesKeyRingInput := mpltypes.CreateRawAesKeyringInput{ + KeyName: keyName, + KeyNamespace: keyNamespace, + WrappingKey: key, + WrappingAlg: mpltypes.AesWrappingAlgAlgAes256GcmIv12Tag16, + } + aesKeyring, err := matProv.CreateRawAesKeyring(context.Background(), aesKeyRingInput) + return aesKeyring +} +func generateAes256KeyBytes() ([]byte, error) { + const keySize = 32 // 256 bits = 32 bytes + key := make([]byte, keySize) + // Use crypto/rand for cryptographically secure random numbers + _, err := rand.Read(key) + if err != nil { + return nil, err + } + return key, nil +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/rawaeskeyring/rawaeskeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/rawaeskeyring/rawaeskeyring.go new file mode 100644 index 000000000..d8ccaa21d --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/rawaeskeyring/rawaeskeyring.go @@ -0,0 +1,138 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +/* +This example sets up the Raw AES Keyring +The Raw AES keyring lets you use an AES symmetric key that you provide as a wrapping key that +protects your data key. You need to generate, store, and protect the key material, +preferably in a hardware security module (HSM) or key management system. Use a Raw AES keyring +when you need to provide the wrapping key and encrypt the data keys locally or offline. +This example creates a Raw AES Keyring and then encrypts a custom input exampleText +with an encryption context. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decrypted plaintext value matches exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. +The Raw AES keyring encrypts data by using the AES-GCM algorithm and a wrapping key that +you specify as a byte array. You can specify only one wrapping key in each Raw AES keyring, +but you can include multiple Raw AES keyrings, alone or with other keyrings, in a multi-keyring. +For more information on how to use Raw AES keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-aes-keyring.html +*/ +package rawaeskeyring + +import ( + "context" + "crypto/rand" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" +) + +func RawAesExample(exampleText string) { + // Step 1: Generate a 256-bit AES key to use with your keyring. + // In practice, you should get this key from a secure key management system such as an HSM. + key, err := generateAes256KeyBytes() + if err != nil { + panic(err) + } + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Create the keyring + // The key namespace and key name are defined by you + // and are used by the raw AES keyring to determine + // whether it should attempt to decrypt an encrypted data key. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-aes-keyring + var keyNamespace = "A managed aes keys" + var keyName = "My 256-bit AES wrapping key" + aesKeyRingInput := mpltypes.CreateRawAesKeyringInput{ + KeyName: keyName, + KeyNamespace: keyNamespace, + WrappingKey: key, + WrappingAlg: mpltypes.AesWrappingAlgAlgAes256GcmIv12Tag16, + } + aesKeyring, err := matProv.CreateRawAesKeyring(context.Background(), aesKeyRingInput) + if err != nil { + panic(err) + } + // Step 4: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 6a: Encrypt + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: aesKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 6b: Decrypt + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: res.Ciphertext, + EncryptionContext: encryptionContext, + Keyring: aesKeyring, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("Raw AES Keyring Example Completed Successfully") +} + +func generateAes256KeyBytes() ([]byte, error) { + key := make([]byte, 32) // 256 bits = 32 bytes + // Use crypto/rand for cryptographically secure random numbers + _, err := rand.Read(key) + if err != nil { + return nil, err + } + return key, nil +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/keyring/rawrsakeyring/rawrasakeyring.go b/AwsEncryptionSDK/runtimes/go/examples/keyring/rawrsakeyring/rawrasakeyring.go new file mode 100644 index 000000000..bf70d31c2 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/keyring/rawrsakeyring/rawrasakeyring.go @@ -0,0 +1,178 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +/* +This example sets up the Raw RSA Keyring +The Raw RSA keyring performs asymmetric encryption and decryption of data keys in local memory +with RSA public and private keys that you provide. +This keyring accepts PEM encodings of the key pair as UTF-8 interpreted bytes. +The encryption function encrypts the data key under the RSA public key. The decryption function +decrypts the data key using the private key. +This example generate private and public key pairs. +In practice, users of this library should not generate new key pairs +like this, and should instead retrieve an existing key from a secure +key management system (e.g. an HSM). +You may also provide your own key pair by placing PEM files in the +directory where the example is run or modifying the paths in the code +below. These files must be valid PEM encodings of the key pair as UTF-8 +encoded bytes. If you do provide your own key pair, or if a key pair +already exists, this class' main method will not generate a new key pair. +This example creates a Raw RSA Keyring and then encrypts a custom input exampleText +with an encryption context. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decrypted plaintext value matches exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. +A Raw RSA keyring that encrypts and decrypts must include an asymmetric public key and private +key pair. However, you can encrypt data with a Raw RSA keyring that has only a public key, +and you can decrypt data with a Raw RSA keyring that has only a private key. This example requires +the user to either provide both private and public keys, or not provide any keys and the example +generates both to test encryption and decryption. If you configure a Raw RSA keyring with a +public and private key, be sure that they are part of the same key pair. Some language +implementations of the AWS Encryption SDK will not construct a Raw RSA keyring with keys +from different pairs. Others rely on you to verify that your keys are from the same key pair. +You can include any Raw RSA keyring in a multi-keyring. +For more information on how to use Raw RSA keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-rsa-keyring.html +*/ + +package rawrsakeyring + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" +) + +func RawRsaExample(exampleText string) { + // Step 1: Generate the key-pairs + publicKeyBlock, privateKeyBlock, err := generateKeyPair() + if err != nil { + panic(err) + } + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Create the keyring + // 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. + // + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + keyNamespace := "Some managed raw keys" + keyName := "My 2048-bit RSA wrapping key" + rsaKeyRingInput := mpltypes.CreateRawRsaKeyringInput{ + KeyName: keyName, + KeyNamespace: keyNamespace, + PaddingScheme: mpltypes.PaddingSchemeOaepSha512Mgf1, + PublicKey: pem.EncodeToMemory(publicKeyBlock), + PrivateKey: pem.EncodeToMemory(privateKeyBlock), + } + rsaKeyring, err := matProv.CreateRawRsaKeyring(context.Background(), rsaKeyRingInput) + if err != nil { + panic(err) + } + // Step 4: Instantiate the encryption SDK client. + // This builds the default 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. + cryptoClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 6a: Encrypt + res, err := cryptoClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: rsaKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 6b: Decrypt + // You do not need to specify the encryption context on decrypt + // because the header of the encrypted message includes the encryption context. + decryptOutput, err := cryptoClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: res.Ciphertext, + Keyring: rsaKeyring, + EncryptionContext: encryptionContext, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("Raw RSA Keyring Example Completed Successfully") +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} + +func generateKeyPair() (*pem.Block, *pem.Block, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + // Extract public key from the private key + publicKey := &privateKey.PublicKey + // Encode public key to PKCS1 DER format + publicKeyDER, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return nil, nil, err + } + privateKeyDer, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return nil, nil, err + } + // Encode to PEM format + publicKeyBlock := &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: publicKeyDER, + } + privateKeyBlock := &pem.Block{ + Type: "PRIVATE KEY", + Bytes: privateKeyDer, + } + return publicKeyBlock, privateKeyBlock, nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/main.go b/AwsEncryptionSDK/runtimes/go/examples/main.go new file mode 100644 index 000000000..434034783 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/main.go @@ -0,0 +1,161 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + primitivestypes "github.com/aws/aws-cryptographic-material-providers-library/primitives/awscryptographyprimitivessmithygeneratedtypes" + "github.com/aws/aws-encryption-sdk/examples/clientsupplier" + "github.com/aws/aws-encryption-sdk/examples/cryptographicmaterialsmanager/requiredencryptioncontext" + "github.com/aws/aws-encryption-sdk/examples/cryptographicmaterialsmanager/restrictalgorithmsuite" + "github.com/aws/aws-encryption-sdk/examples/keyring/awskmsdiscoverykeyring" + "github.com/aws/aws-encryption-sdk/examples/keyring/awskmsdiscoverymultikeyring" + "github.com/aws/aws-encryption-sdk/examples/keyring/awskmshierarchicalkeyring" + "github.com/aws/aws-encryption-sdk/examples/keyring/awskmskeyring" + "github.com/aws/aws-encryption-sdk/examples/keyring/awskmsmrkdiscoverykeyring" + "github.com/aws/aws-encryption-sdk/examples/keyring/awskmsmrkdiscoverymultikeyring" + "github.com/aws/aws-encryption-sdk/examples/keyring/awskmsmrkkeyring" + "github.com/aws/aws-encryption-sdk/examples/keyring/awskmsmrkmultikeyring" + "github.com/aws/aws-encryption-sdk/examples/keyring/awskmsmultikeyring" + "github.com/aws/aws-encryption-sdk/examples/keyring/awskmsrsakeyring" + "github.com/aws/aws-encryption-sdk/examples/keyring/ecdh" + "github.com/aws/aws-encryption-sdk/examples/keyring/multikeyring" + "github.com/aws/aws-encryption-sdk/examples/keyring/rawaeskeyring" + "github.com/aws/aws-encryption-sdk/examples/keyring/rawrsakeyring" + "github.com/aws/aws-encryption-sdk/examples/misc" + "github.com/aws/aws-encryption-sdk/examples/utils" +) + +func main() { + const stringToEncrypt = "Text To encrypt" + clientsupplier.ClientSupplierExample( + stringToEncrypt, + utils.DefaultRegionMrkKeyArn(), + utils.DefaultKMSKeyAccountID(), + []string{"eu-west-1"}) + misc.CommitmentPolicyExample( + stringToEncrypt, + utils.DefaultKMSKeyId(), + utils.DefaultKmsKeyRegion()) + misc.SetEncryptionAlgorithmSuiteExample(stringToEncrypt) + var maxEncryptedDataKeys int64 = 3 + misc.LimitEncryptedDataKeyExample( + stringToEncrypt, + utils.DefaultKMSKeyId(), + utils.DefaultKmsKeyRegion(), + maxEncryptedDataKeys) + requiredencryptioncontext.RequiredEncryptionContextExample( + stringToEncrypt, + utils.DefaultKMSKeyId(), + utils.DefaultKmsKeyRegion()) + restrictalgorithmsuite.SigningOnlyExample( + stringToEncrypt, + utils.DefaultKMSKeyId(), + utils.DefaultKmsKeyRegion()) + // keyrings + ecdh.PublicKeyRawEcdhDiscoveryKeyringExample( + stringToEncrypt, + primitivestypes.ECDHCurveSpecEccNistP256, + utils.EccPublicKeyFileNameRecipient(), + utils.EccPrivateKeyFileNameRecipient()) + ecdh.EphemeralRawECDHKeyringExample( + stringToEncrypt, + primitivestypes.ECDHCurveSpecEccNistP256, + utils.EccPublicKeyFileNameRecipient()) + ecdh.RawECDHKeyringExample( + stringToEncrypt, + primitivestypes.ECDHCurveSpecEccNistP256, + utils.EccPublicKeyFileNameRecipient(), + utils.EccPrivateKeyFileNameSender()) + ecdh.AwsKmsEcdhKeyringExample( + stringToEncrypt, + primitivestypes.ECDHCurveSpecEccNistP256, + utils.KmsEcdhKeyIdP256RecipientKeyId(), + utils.KmsEcdhKeyIdP256SenderKeyId(), + utils.KmsEccPublicKeyFileNameSender(), + utils.KmsEccPublicKeyFileNameRecipient()) + ecdh.AwsKmsEcdhDiscoveryKeyringExample( + stringToEncrypt, + primitivestypes.ECDHCurveSpecEccNistP256, + utils.KmsEcdhKeyIdP256RecipientKeyId(), + utils.KmsEcdhKeyIdP256SenderKeyId(), + utils.KmsEccPublicKeyFileNameSender(), + utils.KmsEccPublicKeyFileNameRecipient()) + awskmskeyring.AwsKmsKeyringExample( + stringToEncrypt, + utils.DefaultKMSKeyId(), + utils.DefaultKMSKeyAccountID()) + awskmsrsakeyring.AwsKmsRsaExample( + stringToEncrypt, + utils.TestKmsRsaKeyID(), + utils.KmsRSAPublicKey()) + awskmsmultikeyring.AwsKmsMultiKeyringExample( + stringToEncrypt, + utils.DefaultKMSKeyId(), + utils.AlternateRegionKMSKeyId(), + utils.AlternateRegionKMSKeyRegion()) + awskmsdiscoverykeyring.AwsKmsDiscoveryKeyringExample( + stringToEncrypt, + utils.DefaultKMSKeyId(), + utils.DefaultKMSKeyAccountID()) + awskmsdiscoverymultikeyring.AwsKmsDiscoveryMultiKeyringExample( + stringToEncrypt, + utils.DefaultKMSKeyId(), + utils.DefaultKMSKeyAccountID(), + utils.Regions()) + rawrsakeyring.RawRsaExample(stringToEncrypt) + awskmsmrkkeyring.AwsKmsMrkKeyringExample( + stringToEncrypt, + utils.DefaultRegionMrkKeyArn(), + utils.AlternateRegionMrkKeyArn(), + utils.DefaultMRKKeyRegion(), + utils.AlternateRegionMrkKeyRegion()) + awskmsmrkmultikeyring.AwsKmsMrkMultiKeyringExample( + stringToEncrypt, + utils.DefaultRegionMrkKeyArn(), + utils.AlternateRegionMrkKeyArn(), + utils.DefaultKMSKeyId(), + utils.AlternateRegionMrkKeyRegion()) + awskmsmrkdiscoverykeyring.AwsKmsMrkDiscoveryKeyringExample( + stringToEncrypt, + utils.DefaultRegionMrkKeyArn(), + utils.DefaultMRKKeyRegion(), + utils.AlternateRegionMrkKeyRegion(), + utils.DefaultKMSKeyAccountID()) + awskmsmrkdiscoverymultikeyring.AwsKmsMrkDiscoveryMultiKeyringExample( + stringToEncrypt, + utils.DefaultRegionMrkKeyArn(), + utils.DefaultMRKKeyRegion(), + utils.DefaultKMSKeyAccountID(), + utils.RegionsOfMRKKeys(), + ) + awskmshierarchicalkeyring.AwsKmsHKeyExample( + stringToEncrypt, + utils.KeyStoreKMSKeyRegion(), + utils.KeyStoreRegion(), + utils.KeyStoreKMSKeyID(), + utils.KeyStoreName(), + utils.LogicalKeyStoreName(), + ) + awskmshierarchicalkeyring.CreateAndVersionBranchKeyId( + utils.KeyStoreKMSKeyRegion(), + utils.KeyStoreRegion(), + utils.KeyStoreKMSKeyID(), + utils.KeyStoreName(), + utils.LogicalKeyStoreName(), + ) + awskmshierarchicalkeyring.SharedCacheExample( + stringToEncrypt, + utils.KeyStoreKMSKeyRegion(), + utils.KeyStoreRegion(), + utils.KeyStoreKMSKeyID(), + utils.KeyStoreName(), + utils.LogicalKeyStoreName(), + ) + rawaeskeyring.RawAesExample(stringToEncrypt) + multikeyring.MultiKeyringExample( + stringToEncrypt, + utils.DefaultKMSKeyId(), + utils.DefaultKmsKeyRegion(), + ) +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/misc/commitmentpolicy.go b/AwsEncryptionSDK/runtimes/go/examples/misc/commitmentpolicy.go new file mode 100644 index 000000000..f7cdb2636 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/misc/commitmentpolicy.go @@ -0,0 +1,149 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* +This example configures a client with a specific commitment policy for the +AWS Encryption SDK client, then encrypts and decrypts data using an AWS KMS Keyring. + +The commitment policy in this example (ForbidEncryptAllowDecrypt) should only be +used as part of a migration from version 1.x to 2.x, or for advanced users with +specialized requirements. Most AWS Encryption SDK users should use the default +commitment policy (RequireEncryptRequireDecrypt). + +This example creates a KMS Keyring and then encrypts a custom input exampleText +with an encryption context for the commitment policy ForbidEncryptAllowDecrypt. +This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decrypted plaintext value matches exampleText +These sanity checks are for demonstration in the example only. You do not need these in your code. + +For more information on setting your commitment policy, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#commitment-policy + +For more information on KMS Key identifiers, see +https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id +*/ + +package misc + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func CommitmentPolicyExample(exampleText, defaultKMSKeyId, defaultKmsKeyRegion string) { + // Step 1: Create the aws kms client + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + panic(err) + } + kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.Region = defaultKmsKeyRegion + }) + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Create the keyring + awsKmsKeyringInput := mpltypes.CreateAwsKmsKeyringInput{ + KmsClient: kmsClient, + KmsKeyId: defaultKMSKeyId, + } + awsKmsKeyring, err := matProv.CreateAwsKmsKeyring(context.Background(), awsKmsKeyringInput) + if err != nil { + panic(err) + } + // Step 4: Instantiate the encryption SDK client. + // Build the default 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. + + // Create one with the commitment policy RequireEncryptAllowDecrypt and another with ForbidEncryptAllowDecrypt. + // Read more about commitment policies here: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#commitment-policy + commitPolicyRequireEncryptRequireDecrypt := mpltypes.ESDKCommitmentPolicyRequireEncryptRequireDecrypt + commitPolicyForbidEncryptAllowDecrypt := mpltypes.ESDKCommitmentPolicyForbidEncryptAllowDecrypt + forbidEncryptClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{CommitmentPolicy: &commitPolicyForbidEncryptAllowDecrypt}) + if err != nil { + panic(err) + } + requireEncryptClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{CommitmentPolicy: &commitPolicyRequireEncryptRequireDecrypt}) + if err != nil { + panic(err) + } + // Step 5: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 6a: Encrypt + // Make sure you use a non-committing algorithm with the commitment policy ForbidEncryptAllowDecrypt. + // Otherwise encrypt() will throw + // Error: AwsCryptographicMaterialProvidersError + // { + // error: InvalidAlgorithmSuiteInfoOnEncrypt + // { + // message: "Configuration conflict. Commitment policy requires only non-committing algorithm suites" + // } + // } + // By default for ForbidEncryptAllowDecrypt, the algorithm used is + // AlgAes256GcmIv12Tag16HkdfSha384EcdsaP384 which is a non-committing algorithm. + res, err := forbidEncryptClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: awsKmsKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 6b: Decrypt + decryptOutput, err := forbidEncryptClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: awsKmsKeyring, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Demonstrate that an EncryptionSDK that enforces Key Commitment on Decryption + // will fail to decrypt the encrypted message (as it was encrypted without Key Commitment). + _, err = requireEncryptClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: awsKmsKeyring, + Ciphertext: res.Ciphertext, + }) + // We expect this to fail + if err == nil { + panic("Expected error but error is nil") + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("Set Commitment Policy Example Completed Successfully") +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/misc/limitencrypteddatakeysexample.go b/AwsEncryptionSDK/runtimes/go/examples/misc/limitencrypteddatakeysexample.go new file mode 100644 index 000000000..708b4dcc4 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/misc/limitencrypteddatakeysexample.go @@ -0,0 +1,176 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* +Demonstrate limiting the number of Encrypted Data Keys [EDKs] allowed +when encrypting or decrypting a message. +Limiting encrypted data keys is most valuable when you are decrypting +messages from an untrusted source. +By default, the ESDK will allow up to 65,535 encrypted data keys. +A malicious actor might construct an encrypted message with thousands of +encrypted data keys, none of which can be decrypted. +As a result, the AWS Encryption SDK would attempt to decrypt each +encrypted data key until it exhausted the encrypted data keys in the message. + +For more information on limiting EDKs, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/configure.html#config-limit-keys +*/ + +package misc + +import ( + "context" + "crypto/rand" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" +) + +func LimitEncryptedDataKeyExample(exampleText, defaultKMSKeyId, defaultKmsKeyRegion string, maxEncryptedDataKeys int64) { + // Step 1: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 2: Instantiate the encryption SDK client. + // This builds the default 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. + // Also, set the EncryptionSDK's MaxEncryptedDataKeys parameter here + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{ + MaxEncryptedDataKeys: &maxEncryptedDataKeys, + }) + if err != nil { + panic(err) + } + // Step 3: Generate `maxEncryptedDataKeys` AES keyrings to use with your keyring. + // In practice, you should get this key from a secure key management system such as an HSM. + rawAESKeyrings := make([]mpltypes.IKeyring, 0, maxEncryptedDataKeys) + var i int64 = 0 + for i < maxEncryptedDataKeys { + rawAESKeyrings = append(rawAESKeyrings, getRawAESKeyring(matProv)) + i++ + } + // Step 4: Create a Multi Keyring with `maxEncryptedDataKeys` AES Keyrings + createMultiKeyringInput := mpltypes.CreateMultiKeyringInput{ + Generator: rawAESKeyrings[0], + ChildKeyrings: rawAESKeyrings[1:], + } + multiKeyring, err := matProv.CreateMultiKeyring(context.Background(), createMultiKeyringInput) + if err != nil { + panic(err) + } + // Step 4: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 5a: Encrypt + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: multiKeyring, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 5b: Decrypt + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: multiKeyring, + Ciphertext: res.Ciphertext, + }) + if err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + // Demonstrate that an EncryptionSDK with a lower MaxEncryptedDataKeys + // will fail to decrypt the encrypted message. + // (This is an example for demonstration; you do not need to do this in your own code.) + lowerMaxEncryptedDataKeys := maxEncryptedDataKeys - 1 + encryptionClientIncorrectMaxEncryptedKeys, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{ + MaxEncryptedDataKeys: &lowerMaxEncryptedDataKeys, + }) + if err != nil { + panic(err) + } + _, err = encryptionClientIncorrectMaxEncryptedKeys.Decrypt(context.Background(), esdktypes.DecryptInput{ + EncryptionContext: encryptionContext, + Keyring: multiKeyring, + Ciphertext: res.Ciphertext, + }) + if err == nil { + panic("Expected error not found.") + } + // Swallow the AwsCryptographicMaterialProvidersException but you may choose how to handle the exception + switch err.(type) { + case esdktypes.AwsEncryptionSdkException: + // You may choose how to handle the exception in this switch case. + default: + panic("Decryption using lower then max encrypted data keys MUST raise AwsEncryptionSdkException") + } + fmt.Println("Limit Encrypted Data Key Example completed successfully") +} + +func getRawAESKeyring(matProv *mpl.Client) mpltypes.IKeyring { + // 1. Generate a 256-bit AES key to use with your keyring. + // In practice, you should get this key from a secure key management system such as an HSM. + key, err := generate256KeyBytesAES() + if err != nil { + panic(err) + } + // The key namespace and key name are defined by you + // and are used by the raw AES keyring to determine + // whether it should attempt to decrypt an encrypted data key. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-aes-keyring + var keyNamespace = "A managed aes keys" + var keyName = "My 256-bit AES wrapping key" + // 2. Create the keyring + aesKeyRingInput := mpltypes.CreateRawAesKeyringInput{ + KeyName: keyName, + KeyNamespace: keyNamespace, + WrappingKey: key, + WrappingAlg: mpltypes.AesWrappingAlgAlgAes256GcmIv12Tag16, + } + aesKeyring, err := matProv.CreateRawAesKeyring(context.Background(), aesKeyRingInput) + return aesKeyring +} + +func generate256KeyBytesAES() ([]byte, error) { + const keySize = 32 // 256 bits = 32 bytes + key := make([]byte, keySize) + // Use crypto/rand for cryptographically secure random numbers + _, err := rand.Read(key) + if err != nil { + return nil, err + } + return key, nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/misc/setencryptionalgorithmsuite.go b/AwsEncryptionSDK/runtimes/go/examples/misc/setencryptionalgorithmsuite.go new file mode 100644 index 000000000..b14153de5 --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/misc/setencryptionalgorithmsuite.go @@ -0,0 +1,171 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* +This example demonstrates how to set an algorithm suite while using the Raw AES Keyring +in the AWS Encryption SDK. + +The algorithm suite used in the encrypt() method is the algorithm used to protect your +data using the data key. By setting this algorithm, you can configure the algorithm used +to encrypt and decrypt your data. + +Algorithm suites can be set in a similar manner in other keyrings as well. However, +please make sure that you're using a logical algorithm suite that is compatible with your +keyring. For more information on algorithm suites supported by the AWS Encryption SDK, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/supported-algorithms.html + +The AES wrapping algorithm (AesWrappingAlg::AlgAes256GcmIv12Tag16) protects your data key using +the user-provided wrapping key. In contrast, the algorithm suite used in the encrypt() method +is the algorithm used to protect your data using the data key. This example demonstrates setting the +latter, which is the algorithm suite for protecting your data. When the commitment policy is +RequireEncryptRequireDecrypt, the default algorithm used in the encrypt method is +AlgAes256GcmHkdfSha512CommitKeyEcdsaP384, which is a committing and signing algorithm. +Signature verification ensures the integrity of a digital message as it goes across trust +boundaries. However, signature verification adds a significant performance cost to encryption +and decryption. If encryptors and decryptors are equally trusted, we can consider using an algorithm +suite that does not include signing. This example sets the algorithm suite as +AlgAes256GcmHkdfSha512CommitKey, which is a committing but non-signing algorithm. +For more information on digital signatures, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#digital-sigs + +This example creates a Raw AES Keyring and then encrypts a custom input EXAMPLE_DATA +with an encryption context and the algorithm suite AlgAes256GcmHkdfSha512CommitKey. +This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decrypted plaintext value matches EXAMPLE_DATA +These sanity checks are for demonstration in the example only. You do not need these in your code. + +For more information on how to use Raw AES keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-aes-keyring.html +*/ + +package misc + +import ( + "context" + "crypto/rand" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" +) + +func SetEncryptionAlgorithmSuiteExample(exampleText string) { + // Step 1: Generate a 256-bit AES key to use with your keyring. + // In practice, you should get this key from a secure key management system such as an HSM. + key, err := generateAes256KeyBytes() + if err != nil { + panic(err) + } + // Step 2: Initialize the mpl client + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + if err != nil { + panic(err) + } + // Step 3: Create the keyring + // The key namespace and key name are defined by you + // and are used by the raw AES keyring to determine + // whether it should attempt to decrypt an encrypted data key. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-aes-keyring + var keyNamespace = "A managed aes keys" + var keyName = "My 256-bit AES wrapping key" + // Note: The wrapping algorithm here is NOT the algorithm suite we set in this example. + aesKeyRingInput := mpltypes.CreateRawAesKeyringInput{ + KeyName: keyName, + KeyNamespace: keyNamespace, + WrappingKey: key, + WrappingAlg: mpltypes.AesWrappingAlgAlgAes256GcmIv12Tag16, + } + aesKeyring, err := matProv.CreateRawAesKeyring(context.Background(), aesKeyRingInput) + if err != nil { + panic(err) + } + // Step 4: Instantiate the encryption SDK client. + // This builds the default 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. + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) + if err != nil { + panic(err) + } + // Step 5: Create your encryption context (Optional). + // Remember that your encryption context is NOT SECRET. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryptionContext := map[string]string{ + "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", + } + // Step 6a: Encrypt + // Here, we customize the Algorithm Suite that is used to Encrypt the plaintext. + // In particular, we use an Algorithm Suite without Signing. + // Signature verification adds a significant performance cost on decryption. + // If the users encrypting data and the users decrypting data are equally trusted, + // consider using an algorithm suite that does not include signing. + // See more about Digital Signatures: + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#digital-sigs + algorithmSuiteId := mpltypes.ESDKAlgorithmSuiteIdAlgAes256GcmHkdfSha512CommitKey + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ + Plaintext: []byte(exampleText), + EncryptionContext: encryptionContext, + Keyring: aesKeyring, + AlgorithmSuiteId: &algorithmSuiteId, + }) + if err != nil { + panic(err) + } + // Validate Ciphertext and Plaintext before encryption are NOT the same + if string(res.Ciphertext) == exampleText { + panic("Ciphertext and Plaintext before encryption are the same") + } + // Step 6b: Decrypt + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ + Ciphertext: res.Ciphertext, + EncryptionContext: encryptionContext, + Keyring: aesKeyring, + }) + if err != nil { + panic(err) + } + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. + // Before your application uses plaintext data, verify that the encryption context that + // you used to encrypt the message is included in the encryption context that was used to + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { + panic(err) + } + // Validate Plaintext after decryption and Plaintext before encryption ARE the same + if string(decryptOutput.Plaintext) != exampleText { + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") + } + fmt.Println("Set Encryption Algorithm Suite Example Completed Successfully") +} + +func generateAes256KeyBytes() ([]byte, error) { + numOfBytes := 32 // 256 bits = 32 bytes + key := make([]byte, numOfBytes) + // Use crypto/rand for cryptographically secure random numbers + _, err := rand.Read(key) + if err != nil { + return nil, err + } + return key, nil +} + +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. +func validateEncryptionContext(expected, actual map[string]string) error { + for expectedKey, expectedValue := range expected { + actualValue, exists := actual[expectedKey] + if !exists || actualValue != expectedValue { + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", + expectedKey, expectedValue) + } + } + return nil +} diff --git a/AwsEncryptionSDK/runtimes/go/examples/utils/exampleUtils.go b/AwsEncryptionSDK/runtimes/go/examples/utils/exampleUtils.go new file mode 100644 index 000000000..ddc75bf1b --- /dev/null +++ b/AwsEncryptionSDK/runtimes/go/examples/utils/exampleUtils.go @@ -0,0 +1,321 @@ +package utils + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "os" + + "github.com/aws/aws-cryptographic-material-providers-library/primitives/awscryptographyprimitivessmithygeneratedtypes" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +const ( + testKmsRsaPublicKey = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA27Uc/fBaMVhxCE/SpCMQ +oSBRSzQJw+o2hBaA+FiPGtiJ/aPy7sn18aCkelaSj4kwoC79b/arNHlkjc7OJFsN +/GoFKgNvaiY4lOeJqEiWQGSSgHtsJLdbO2u4OOSxh8qIRAMKbMgQDVX4FR/PLKeK +fc2aCDvcNSpAM++8NlNmv7+xQBJydr5ce91eISbHkFRkK3/bAM+1iddupoRw4Wo2 +r3avzrg5xBHmzR7u1FTab22Op3Hgb2dBLZH43wNKAceVwKqKA8UNAxashFON7xK9 +yy4kfOL0Z/nhxRKe4jRZ/5v508qIzgzCksYy7Y3QbMejAtiYnr7s5/d5KWw0swou +twIDAQAB +-----END PUBLIC KEY-----` + testKmsRsaKeyID = "arn:aws:kms:us-west-2:370957321024:key/mrk-63d386cb70614ea59b32ad65c9315297" + testDefaultKMSKeyId = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + defaultKmsKeyRegion = "us-west-2" + testAlternateRegionKMSKeyId = "arn:aws:kms:eu-central-1:658956600833:key/75414c93-5285-4b57-99c9-30c1cf0a22c2" + testAlternateRegionKMSKeyRegion = "eu-central-1" + testDefaultMRKKeyId = "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + defaultMRKKeyRegion = "us-east-1" + testAlternateRegionMrkKeyId = "arn:aws:kms:eu-west-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + alternateRegionMrkKeyRegion = "eu-west-1" + testKeyStoreKMSKeyRegion = "us-west-2" + testKeyStoreKMSKeyID = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" + testLogicalKeyStoreName = "KeyStoreDdbTable" + testKeyStoreName = "KeyStoreDdbTable" + testKeyStoreRegion = "us-west-2" + defaultKMSKeyAccountID = "658956600833" + eccPrivateKeyFileNameSender = "sender_private.pem" + eccPrivateKeyFileNameRecipient = "recipient_private.pem" + eccPublicKeyFileNameRecipient = "recipient_public.pem" + kmsEccPublicKeyFileNameRecipient = "KmsEccKeyringExamplePublicKeyRecipient.pem" + kmsEccPublicKeyFileNameSender = "KmsEccKeyringExamplePublicKeySender.pem" + testKmsEcdhKeyIdP256SenderKeyId = "arn:aws:kms:us-west-2:370957321024:key/eabdf483-6be2-4d2d-8ee4-8c2583d416e9" + testKmsEcdhKeyIdP256RecipientKeyId = "arn:aws:kms:us-west-2:370957321024:key/0265c8e9-5b6a-4055-8f70-63719e09fda5" +) + +// Getter functions + +func KmsEcdhKeyIdP256SenderKeyId() string { + return testKmsEcdhKeyIdP256SenderKeyId +} + +func KmsEcdhKeyIdP256RecipientKeyId() string { + return testKmsEcdhKeyIdP256RecipientKeyId +} + +func KmsEccPublicKeyFileNameRecipient() string { + return kmsEccPublicKeyFileNameRecipient +} + +func KmsEccPublicKeyFileNameSender() string { + return kmsEccPublicKeyFileNameSender +} + +func EccPrivateKeyFileNameSender() string { + return eccPrivateKeyFileNameSender +} + +func EccPrivateKeyFileNameRecipient() string { + return eccPrivateKeyFileNameRecipient +} + +func EccPublicKeyFileNameRecipient() string { + return eccPublicKeyFileNameRecipient +} + +func RegionsOfMRKKeys() []string { + return []string{defaultMRKKeyRegion, alternateRegionMrkKeyRegion} +} + +func Regions() []string { + return []string{defaultKmsKeyRegion, testAlternateRegionKMSKeyRegion} +} + +func DefaultKmsKeyRegion() string { + return defaultKmsKeyRegion +} + +func DefaultMRKKeyRegion() string { + return defaultMRKKeyRegion +} + +func AlternateRegionMrkKeyRegion() string { + return alternateRegionMrkKeyRegion +} + +func AlternateRegionMrkKeyArn() string { + return testAlternateRegionMrkKeyId +} + +func DefaultRegionMrkKeyArn() string { + return testDefaultMRKKeyId +} + +func AlternateRegionKMSKeyRegion() string { + return testAlternateRegionKMSKeyRegion +} + +func AlternateRegionKMSKeyId() string { + return testAlternateRegionKMSKeyId +} + +func DefaultKMSKeyAccountID() string { + return defaultKMSKeyAccountID +} + +func DefaultKMSKeyId() string { + return testDefaultKMSKeyId +} + +func TestKmsRsaKeyID() string { + return testKmsRsaKeyID +} + +func KmsRSAPublicKey() []byte { + return []byte(testKmsRsaPublicKey) +} + +func KeyStoreRegion() string { + return testKeyStoreRegion +} + +func KeyStoreKMSKeyRegion() string { + return testKeyStoreKMSKeyRegion +} + +func KeyStoreKMSKeyID() string { + return testKeyStoreKMSKeyID +} + +func LogicalKeyStoreName() string { + return testLogicalKeyStoreName +} + +func KeyStoreName() string { + return testKeyStoreName +} + +// Utility functions + +func WriteRawEcdhEccKeys(ecdhCurveSpec awscryptographyprimitivessmithygeneratedtypes.ECDHCurveSpec) error { + // Safety check: Validate neither file is present + if FileExists(eccPrivateKeyFileNameSender) || + FileExists(eccPrivateKeyFileNameRecipient) || + FileExists(eccPublicKeyFileNameRecipient) { + return errors.New("WriteRawEcdhEccKeys will not overwrite existing PEM files") + } + + // Generate key pairs + _, privateKeySender, err := generateRawEccKeyPair(ecdhCurveSpec) + if err != nil { + return err + } + + publicKeyRecipient, privateKeyRecipient, err := generateRawEccKeyPair(ecdhCurveSpec) + if err != nil { + return err + } + + // Create PEM blocks + privateKeySenderPEM := &pem.Block{ + Type: "PRIVATE KEY", + Bytes: privateKeySender, + } + + privateKeyRecipientPEM := &pem.Block{ + Type: "PRIVATE KEY", + Bytes: privateKeyRecipient, + } + + publicKeyRecipientPEM := &pem.Block{ + Type: "PUBLIC KEY", + Bytes: publicKeyRecipient, + } + + // Write private key for sender in PEM format + err = os.WriteFile( + eccPrivateKeyFileNameSender, + pem.EncodeToMemory(privateKeySenderPEM), + 0600, + ) + if err != nil { + return fmt.Errorf("failed to write sender's private key: %w", err) + } + + // Write private key for recipient in PEM format + err = os.WriteFile( + eccPrivateKeyFileNameRecipient, + pem.EncodeToMemory(privateKeyRecipientPEM), + 0600, + ) + if err != nil { + return fmt.Errorf("failed to write recipient's private key: %w", err) + } + + // Write public key for recipient in PEM format + err = os.WriteFile( + eccPublicKeyFileNameRecipient, + pem.EncodeToMemory(publicKeyRecipientPEM), + 0600, + ) + if err != nil { + return fmt.Errorf("failed to write recipient's public key: %w", err) + } + + return nil +} + +func LoadPublicKeyFromPEM(filename string) ([]byte, error) { + // Read the PEM file content as string + pemContent, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to read PEM file: %w", err) + } + // Parse PEM block + block, _ := pem.Decode(pemContent) + + if block == nil { + return nil, fmt.Errorf("failed to decode PEM block") + } + + // The block.Bytes contains the DER encoded key + return block.Bytes, nil +} + +func FileExists(filename string) bool { + _, err := os.Stat(filename) + return !os.IsNotExist(err) +} + +func generateRawEccKeyPair(curveSpec awscryptographyprimitivessmithygeneratedtypes.ECDHCurveSpec) ([]byte, []byte, error) { + // Select the appropriate elliptic curve based on the specification + var curve elliptic.Curve + switch curveSpec { + case awscryptographyprimitivessmithygeneratedtypes.ECDHCurveSpecEccNistP256: + curve = elliptic.P256() + case awscryptographyprimitivessmithygeneratedtypes.ECDHCurveSpecEccNistP384: + curve = elliptic.P384() + case awscryptographyprimitivessmithygeneratedtypes.ECDHCurveSpecEccNistP521: + curve = elliptic.P521() + default: + return nil, nil, fmt.Errorf("unsupported curve specification: %s", curveSpec) + } + // Generate the private key + privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate private key: %w", err) + } + // Extract the public key + publicKey := &privateKey.PublicKey + // Marshal the private key to bytes (X.509 PKCS#8 format) + privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal private key: %w", err) + } + // Marshal the public key to bytes (X.509 SPKI format) + publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal public key: %w", err) + } + return publicKeyBytes, privateKeyBytes, nil +} + +func WriteKmsEcdhEccPublicKey(eccKeyArn, publicKeyFileName string, kmsClient *kms.Client) error { + // Safety check: Validate neither file is present + if FileExists(publicKeyFileName) { + return errors.New("WriteKmsEcdhEccPublicKey will not overwrite existing PEM files") + } + // Generate public key + publicKey, err := GenerateKmsEccPublicKey(eccKeyArn, kmsClient) + if err != nil { + return fmt.Errorf("failed to generate public key: %w", err) + } + // Create PEM block + pemBlock := &pem.Block{ + Type: "PUBLIC KEY", + Bytes: publicKey, + } + // Encode PEM + pemData := pem.EncodeToMemory(pemBlock) + if pemData == nil { + return errors.New("failed to encode PEM data") + } + // Write file with proper permissions + err = os.WriteFile(publicKeyFileName, pemData, 0600) + if err != nil { + return fmt.Errorf("failed to write public key file: %w", err) + } + return nil +} + +func GenerateKmsEccPublicKey(eccKeyArn string, kmsClient *kms.Client) ([]byte, error) { + ctx := context.Background() + // Get public key from KMS + response, err := kmsClient.GetPublicKey(ctx, &kms.GetPublicKeyInput{ + KeyId: &eccKeyArn, + }) + if err != nil { + return nil, fmt.Errorf("failed to get public key from KMS: %w", err) + } + // Check if public key is present + if response.PublicKey == nil { + return nil, errors.New("no public key in KMS response") + } + return response.PublicKey, nil +}