main/client_supplier/
client_supplier_example.rs

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
18use super::regional_role_client_supplier::RegionalRoleClientSupplier;
19use aws_esdk::client as esdk_client;
20use aws_esdk::material_providers::client as mpl_client;
21use aws_esdk::material_providers::types::error::Error::AwsCryptographicMaterialProvidersException;
22use aws_esdk::material_providers::types::material_providers_config::MaterialProvidersConfig;
23use aws_esdk::material_providers::types::DiscoveryFilter;
24use aws_esdk::types::aws_encryption_sdk_config::AwsEncryptionSdkConfig;
25use std::collections::HashMap;
26
27pub async fn encrypt_and_decrypt_with_keyring(
28    example_data: &str,
29    mrk_key_id_encrypt: &str,
30    aws_account_id: &str,
31    aws_regions: Vec<String>,
32) -> Result<(), crate::BoxError> {
33    // 1. Instantiate the encryption SDK client.
34    // This builds the default client with the RequireEncryptRequireDecrypt commitment policy,
35    // which enforces that this client only encrypts using committing algorithm suites and enforces
36    // that this client will only decrypt encrypted messages that were created with a committing
37    // algorithm suite.
38    let esdk_config = AwsEncryptionSdkConfig::builder().build()?;
39    let esdk_client = esdk_client::Client::from_conf(esdk_config)?;
40
41    // 2. Create encryption context.
42    // Remember that your encryption context is NOT SECRET.
43    // For more information, see
44    // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
45    let encryption_context = HashMap::from([
46        ("encryption".to_string(), "context".to_string()),
47        ("is not".to_string(), "secret".to_string()),
48        ("but adds".to_string(), "useful metadata".to_string()),
49        (
50            "that can help you".to_string(),
51            "be confident that".to_string(),
52        ),
53        (
54            "the data you are handling".to_string(),
55            "is what you think it is".to_string(),
56        ),
57    ]);
58
59    // 3. Create a single MRK multi-keyring.
60    //    This can be either a single-region KMS key or an MRK.
61    //    For this example to succeed, the key's region must either
62    //    1) be in the regions list, or
63    //    2) the key must be an MRK with a replica defined
64    //    in a region in the regions list, and the client
65    //    must have the correct permissions to access the replica.
66    let mpl_config = MaterialProvidersConfig::builder().build()?;
67    let mpl = mpl_client::Client::from_conf(mpl_config)?;
68
69    // Create the multi-keyring using our custom client supplier
70    // defined in the RegionalRoleClientSupplier class in this directory.
71    // Note: RegionalRoleClientSupplier will internally use the key_arn's region
72    // to retrieve the correct IAM role.
73    let mrk_keyring_with_client_supplier = mpl
74        .create_aws_kms_mrk_multi_keyring()
75        .client_supplier(RegionalRoleClientSupplier {})
76        .generator(mrk_key_id_encrypt)
77        .send()
78        .await?;
79
80    // 4. Encrypt the data with the encryption_context using the encrypt_keyring.
81    let plaintext = example_data.as_bytes();
82
83    let encryption_response = esdk_client
84        .encrypt()
85        .plaintext(plaintext)
86        .keyring(mrk_keyring_with_client_supplier)
87        .encryption_context(encryption_context.clone())
88        .send()
89        .await?;
90
91    let ciphertext = encryption_response
92        .ciphertext
93        .expect("Unable to unwrap ciphertext from encryption response");
94
95    // 5. Demonstrate that the ciphertext and plaintext are different.
96    // (This is an example for demonstration; you do not need to do this in your own code.)
97    assert_ne!(
98        ciphertext,
99        aws_smithy_types::Blob::new(plaintext),
100        "Ciphertext and plaintext data are the same. Invalid encryption"
101    );
102
103    // 6. Create a MRK discovery multi-keyring with a custom client supplier.
104    //    A discovery MRK multi-keyring will be composed of
105    //    multiple discovery MRK keyrings, one for each region.
106    //    Each component keyring has its own KMS client in a particular region.
107    //    When we provide a client supplier to the multi-keyring, all component
108    //    keyrings will use that client supplier configuration.
109    //    In our tests, we make `mrk_key_id_encrypt` an MRK with a replica, and
110    //    provide only the replica region in our discovery filter.
111    let discovery_filter = DiscoveryFilter::builder()
112        .account_ids(vec![aws_account_id.to_string()])
113        .partition("aws".to_string())
114        .build()?;
115
116    let mrk_discovery_client_supplier_keyring = mpl
117        .create_aws_kms_mrk_discovery_multi_keyring()
118        .client_supplier(RegionalRoleClientSupplier {})
119        .discovery_filter(discovery_filter.clone())
120        .regions(aws_regions)
121        .send()
122        .await?;
123
124    // 7. Decrypt your encrypted data using the discovery multi keyring.
125    // On Decrypt, the header of the encrypted message (ciphertext) will be parsed.
126    // The header contains the Encrypted Data Keys (EDKs), which, if the EDK
127    // was encrypted by a KMS Keyring, includes the KMS Key ARN.
128    // For each member of the Multi Keyring, every EDK will try to be decrypted until a decryption
129    // is successful.
130    // Since every member of the Multi Keyring is a Discovery Keyring:
131    //   Each Keyring will filter the EDKs by the Discovery Filter and the Keyring's region.
132    //      For each filtered EDK, the keyring will attempt decryption with the keyring's client.
133    // All of this is done serially, until a success occurs or all keyrings have failed
134    // all (filtered) EDKs. KMS MRK Discovery Keyrings will attempt to decrypt
135    // Multi Region Keys (MRKs) and regular KMS Keys.
136    let decryption_response = esdk_client
137        .decrypt()
138        .ciphertext(ciphertext)
139        .keyring(mrk_discovery_client_supplier_keyring)
140        // Provide the encryption context that was supplied to the encrypt method
141        .encryption_context(encryption_context)
142        .send()
143        .await?;
144
145    let decrypted_plaintext = decryption_response
146        .plaintext
147        .expect("Unable to unwrap plaintext from decryption response");
148
149    // 8. Demonstrate that the decrypted plaintext is identical to the original plaintext.
150    // (This is an example for demonstration; you do not need to do this in your own code.)
151    assert_eq!(
152        decrypted_plaintext,
153        aws_smithy_types::Blob::new(plaintext),
154        "Decrypted plaintext should be identical to the original plaintext. Invalid decryption"
155    );
156
157    // 9. Test the Missing Region Exception
158    // (This is an example for demonstration; you do not need to do this in your own code.)
159    let mrk_discovery_client_supplier_keyring_missing_region = mpl
160        .create_aws_kms_mrk_discovery_multi_keyring()
161        .client_supplier(RegionalRoleClientSupplier {})
162        .discovery_filter(discovery_filter)
163        .regions(vec!["fake-region".to_string()])
164        .send()
165        .await;
166
167    // Swallow the exception
168    // (This is an example for demonstration; you do not need to do this in your own code.)
169    match mrk_discovery_client_supplier_keyring_missing_region {
170        Ok(_) => panic!(
171            "Decryption using discovery keyring with missing region MUST \
172                            raise AwsCryptographicMaterialProvidersException"
173        ),
174        Err(AwsCryptographicMaterialProvidersException { message: _e }) => (),
175        _ => panic!("Unexpected error type"),
176    }
177
178    println!("Client Supplier Example Completed Successfully");
179
180    Ok(())
181}
182
183#[tokio::test(flavor = "multi_thread")]
184pub async fn test_encrypt_and_decrypt_with_keyring() -> Result<(), crate::BoxError2> {
185    // Test function for encrypt and decrypt using the Client Supplier example
186    use crate::example_utils::utils;
187
188    // Note that we pass in an MRK in us-east-1. The RegionalRoleClientSupplier
189    // will internally use the key_arn's region (us-east-1)
190    // to retrieve the correct IAM role.
191    // and access its replica in eu-west-1
192    let aws_regions: Vec<String> = vec!["eu-west-1".to_string()];
193
194    encrypt_and_decrypt_with_keyring(
195        utils::TEST_EXAMPLE_DATA,
196        utils::TEST_MRK_KEY_ID_US_EAST_1,
197        utils::TEST_DEFAULT_KMS_KEY_ACCOUNT_ID,
198        aws_regions,
199    )
200    .await?;
201
202    Ok(())
203}