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}