|
| 1 | +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +/* |
| 5 | + This example sets up an MRK multi-keyring and an MRK discovery |
| 6 | + multi-keyring using a custom client supplier. |
| 7 | + A custom client supplier grants users access to more granular |
| 8 | + configuration aspects of their authentication details and KMS |
| 9 | + client. In this example, we create a simple custom client supplier |
| 10 | + that authenticates with a different IAM role based on the |
| 11 | + region of the KMS key. |
| 12 | +
|
| 13 | + This example creates a MRK multi-keyring configured with a custom |
| 14 | + client supplier using a single MRK and encrypts the example_data with it. |
| 15 | + Then, it creates a MRK discovery multi-keyring to decrypt the ciphertext. |
| 16 | +*/ |
| 17 | + |
| 18 | +package clientsupplier |
| 19 | + |
| 20 | +import ( |
| 21 | + "context" |
| 22 | + "fmt" |
| 23 | + |
| 24 | + mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated" |
| 25 | + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes" |
| 26 | + client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated" |
| 27 | + esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes" |
| 28 | +) |
| 29 | + |
| 30 | +func ClientSupplierExample(exampleText, mrkKeyIdEncrypt, awsAccountId string, awsRegions []string) { |
| 31 | + // Step 1: Instantiate the encryption SDK client. |
| 32 | + // This builds the default client with the RequireEncryptRequireDecrypt commitment policy, |
| 33 | + // which enforces that this client only encrypts using committing algorithm suites and enforces |
| 34 | + // that this client will only decrypt encrypted messages that were created with a committing |
| 35 | + // algorithm suite. |
| 36 | + encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{}) |
| 37 | + if err != nil { |
| 38 | + panic(err) |
| 39 | + } |
| 40 | + // Step 2: Create your encryption context. |
| 41 | + // Remember that your encryption context is NOT SECRET. |
| 42 | + // For more information, see |
| 43 | + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context |
| 44 | + encryptionContext := map[string]string{ |
| 45 | + "encryption": "context", |
| 46 | + "is not": "secret", |
| 47 | + "but adds": "useful metadata", |
| 48 | + "that can help you": "be confident that", |
| 49 | + "the data you are handling": "is what you think it is", |
| 50 | + } |
| 51 | + // Step 3: Initialize the mpl client |
| 52 | + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) |
| 53 | + if err != nil { |
| 54 | + panic(err) |
| 55 | + } |
| 56 | + // Step 4: Create keyrings |
| 57 | + // First Keyring: Create the multi-keyring using our custom client supplier |
| 58 | + // defined in the RegionalRoleClientSupplier class in this directory. |
| 59 | + // Note: RegionalRoleClientSupplier will internally use the key_arn's region |
| 60 | + // to retrieve the correct IAM role. |
| 61 | + awsKmsMrkKeyringMultiInput := mpltypes.CreateAwsKmsMrkMultiKeyringInput{ |
| 62 | + ClientSupplier: &RegionalRoleClientSupplier{}, |
| 63 | + Generator: &mrkKeyIdEncrypt, |
| 64 | + } |
| 65 | + awsKmsMrkMultiKeyring, err := matProv.CreateAwsKmsMrkMultiKeyring(context.Background(), awsKmsMrkKeyringMultiInput) |
| 66 | + if err != nil { |
| 67 | + panic(err) |
| 68 | + } |
| 69 | + // Second Keyring: Create a MRK discovery multi-keyring with a custom client supplier. |
| 70 | + // A discovery MRK multi-keyring will be composed of |
| 71 | + // multiple discovery MRK keyrings, one for each region. |
| 72 | + // Each component keyring has its own KMS client in a particular region. |
| 73 | + // When we provide a client supplier to the multi-keyring, all component |
| 74 | + // keyrings will use that client supplier configuration. |
| 75 | + // In our tests, we make `mrk_key_id_encrypt` an MRK with a replica, and |
| 76 | + // provide only the replica region in our discovery filter. |
| 77 | + discoveryFilter := mpltypes.DiscoveryFilter{ |
| 78 | + AccountIds: []string{awsAccountId}, |
| 79 | + Partition: "aws", |
| 80 | + } |
| 81 | + awsKmsMrkDiscoveryMultiKeyringInput := mpltypes.CreateAwsKmsMrkDiscoveryMultiKeyringInput{ |
| 82 | + ClientSupplier: &RegionalRoleClientSupplier{}, |
| 83 | + Regions: awsRegions, |
| 84 | + DiscoveryFilter: &discoveryFilter, |
| 85 | + } |
| 86 | + awsKmsMrkDiscoveryMultiKeyring, err := matProv.CreateAwsKmsMrkDiscoveryMultiKeyring(context.Background(), awsKmsMrkDiscoveryMultiKeyringInput) |
| 87 | + // Step 5a: Encrypt |
| 88 | + res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{ |
| 89 | + Plaintext: []byte(exampleText), |
| 90 | + EncryptionContext: encryptionContext, |
| 91 | + Keyring: awsKmsMrkMultiKeyring, |
| 92 | + }) |
| 93 | + if err != nil { |
| 94 | + panic(err) |
| 95 | + } |
| 96 | + // Validate Ciphertext and Plaintext before encryption are NOT the same |
| 97 | + if string(res.Ciphertext) == exampleText { |
| 98 | + panic("Ciphertext and Plaintext before encryption are the same") |
| 99 | + } |
| 100 | + // Step 5b: Decrypt |
| 101 | + // Decrypt your encrypted data using the discovery multi keyring. |
| 102 | + // On Decrypt, the header of the encrypted message (ciphertext) will be parsed. |
| 103 | + // The header contains the Encrypted Data Keys (EDKs), which, if the EDK |
| 104 | + // was encrypted by a KMS Keyring, includes the KMS Key ARN. |
| 105 | + // For each member of the Multi Keyring, every EDK will try to be decrypted until a decryption |
| 106 | + // is successful. |
| 107 | + // Since every member of the Multi Keyring is a Discovery Keyring: |
| 108 | + // Each Keyring will filter the EDKs by the Discovery Filter and the Keyring's region. |
| 109 | + // For each filtered EDK, the keyring will attempt decryption with the keyring's client. |
| 110 | + // All of this is done serially, until a success occurs or all keyrings have failed |
| 111 | + // all (filtered) EDKs. KMS MRK Discovery Keyrings will attempt to decrypt |
| 112 | + // Multi Region Keys (MRKs) and regular KMS Keys. |
| 113 | + decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{ |
| 114 | + EncryptionContext: encryptionContext, |
| 115 | + Keyring: awsKmsMrkDiscoveryMultiKeyring, |
| 116 | + Ciphertext: res.Ciphertext, |
| 117 | + }) |
| 118 | + if err != nil { |
| 119 | + panic(err) |
| 120 | + } |
| 121 | + // Validate Plaintext after decryption and Plaintext before encryption ARE the same |
| 122 | + if string(decryptOutput.Plaintext) != exampleText { |
| 123 | + panic("Plaintext after decryption and Plaintext before encryption are NOT the same") |
| 124 | + } |
| 125 | + // If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches. |
| 126 | + // The encryption context was specified on decrypt; we are validating the encryption context for demonstration only. |
| 127 | + // Before your application uses plaintext data, verify that the encryption context that |
| 128 | + // you used to encrypt the message is included in the encryption context that was used to |
| 129 | + // decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match. |
| 130 | + if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil { |
| 131 | + panic(err) |
| 132 | + } |
| 133 | + // Test the Missing Region Exception |
| 134 | + // (This is for demonstration; you do not need to do this in your code.) |
| 135 | + |
| 136 | + // Create a MRK discovery multi-keyring with a custom client supplier and a fake region. |
| 137 | + awsKmsMrkDiscoveryMultiKeyringInputMissingRegion := mpltypes.CreateAwsKmsMrkDiscoveryMultiKeyringInput{ |
| 138 | + ClientSupplier: &RegionalRoleClientSupplier{}, |
| 139 | + Regions: []string{"fake-region"}, |
| 140 | + DiscoveryFilter: &discoveryFilter, |
| 141 | + } |
| 142 | + _, err = matProv.CreateAwsKmsMrkDiscoveryMultiKeyring(context.Background(), awsKmsMrkDiscoveryMultiKeyringInputMissingRegion) |
| 143 | + // Swallow the AwsCryptographicMaterialProvidersException but you may choose how to handle the exception |
| 144 | + switch err.(type) { |
| 145 | + case mpltypes.AwsCryptographicMaterialProvidersException: |
| 146 | + // You may choose how to handle the exception in this switch case. |
| 147 | + default: |
| 148 | + panic("Decryption using discovery keyring with missing region MUST raise AwsCryptographicMaterialProvidersException") |
| 149 | + } |
| 150 | + fmt.Println("Client Supplier Example completed successfully") |
| 151 | +} |
| 152 | + |
| 153 | +// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match. |
| 154 | +func validateEncryptionContext(expected, actual map[string]string) error { |
| 155 | + for expectedKey, expectedValue := range expected { |
| 156 | + actualValue, exists := actual[expectedKey] |
| 157 | + if !exists || actualValue != expectedValue { |
| 158 | + return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'", |
| 159 | + expectedKey, expectedValue) |
| 160 | + } |
| 161 | + } |
| 162 | + return nil |
| 163 | +} |
0 commit comments