main/keyring/aws_kms_discovery_keyring_example.rs
1// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4/*
5This example sets up the AWS KMS Discovery Keyring
6
7AWS KMS discovery keyring is an AWS KMS keyring that doesn't specify any wrapping keys.
8
9The AWS Encryption SDK provides a standard AWS KMS discovery keyring and a discovery keyring
10for AWS KMS multi-Region keys. For information about using multi-Region keys with the
11AWS Encryption SDK, see
12https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/configure.html#config-mrks
13
14Because it doesn't specify any wrapping keys, a discovery keyring can't encrypt data.
15If you use a discovery keyring to encrypt data, alone or in a multi-keyring, the encrypt
16operation fails.
17
18When decrypting, a discovery keyring allows the AWS Encryption SDK to ask AWS KMS to decrypt
19any encrypted data key by using the AWS KMS key that encrypted it, regardless of who owns or
20has access to that AWS KMS key. The call succeeds only when the caller has kms:Decrypt
21permission on the AWS KMS key.
22
23This example creates a KMS Keyring and then encrypts a custom input EXAMPLE_DATA
24with an encryption context. This encrypted ciphertext is then decrypted using the Discovery keyring.
25This example also includes some sanity checks for demonstration:
261. Ciphertext and plaintext data are not the same
272. Decrypted plaintext value matches EXAMPLE_DATA
283. Decryption is only possible if the Discovery Keyring contains the correct AWS Account ID's to
29 which the KMS key used for encryption belongs
30These sanity checks are for demonstration in the example only. You do not need these in your code.
31
32For more information on how to use KMS Discovery keyrings, see
33https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery
34
35For more information on KMS Key identifiers, see
36https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id
37*/
38
39use aws_esdk::client as esdk_client;
40use aws_esdk::material_providers::client as mpl_client;
41use aws_esdk::material_providers::types::material_providers_config::MaterialProvidersConfig;
42use aws_esdk::material_providers::types::DiscoveryFilter;
43use aws_esdk::types::aws_encryption_sdk_config::AwsEncryptionSdkConfig;
44use aws_esdk::types::error::Error::AwsCryptographicMaterialProvidersError;
45use std::collections::HashMap;
46
47pub async fn encrypt_and_decrypt_with_keyring(
48 example_data: &str,
49 kms_key_id: &str,
50 aws_account_id: &str,
51) -> Result<(), crate::BoxError> {
52 // 1. Instantiate the encryption SDK client.
53 // This builds the default client with the RequireEncryptRequireDecrypt commitment policy,
54 // which enforces that this client only encrypts using committing algorithm suites and enforces
55 // that this client will only decrypt encrypted messages that were created with a committing
56 // algorithm suite.
57 let esdk_config = AwsEncryptionSdkConfig::builder().build()?;
58 let esdk_client = esdk_client::Client::from_conf(esdk_config)?;
59
60 // 2. Create a KMS client.
61 let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
62 let kms_client = aws_sdk_kms::Client::new(&sdk_config);
63
64 // 3. Create encryption context.
65 // Remember that your encryption context is NOT SECRET.
66 // For more information, see
67 // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
68 let encryption_context = HashMap::from([
69 ("encryption".to_string(), "context".to_string()),
70 ("is not".to_string(), "secret".to_string()),
71 ("but adds".to_string(), "useful metadata".to_string()),
72 (
73 "that can help you".to_string(),
74 "be confident that".to_string(),
75 ),
76 (
77 "the data you are handling".to_string(),
78 "is what you think it is".to_string(),
79 ),
80 ]);
81
82 // 4. Create the keyring that determines how your data keys are protected.
83 // Although this example highlights Discovery keyrings, Discovery keyrings cannot
84 // be used to encrypt, so for encryption we create a KMS keyring without discovery mode.
85 let mpl_config = MaterialProvidersConfig::builder().build()?;
86 let mpl = mpl_client::Client::from_conf(mpl_config)?;
87
88 let encrypt_kms_keyring = mpl
89 .create_aws_kms_keyring()
90 .kms_key_id(kms_key_id)
91 .kms_client(kms_client.clone())
92 .send()
93 .await?;
94
95 // 5. Encrypt the data with the encryption_context
96 let plaintext = example_data.as_bytes();
97
98 let encryption_response = esdk_client
99 .encrypt()
100 .plaintext(plaintext)
101 .keyring(encrypt_kms_keyring)
102 .encryption_context(encryption_context.clone())
103 .send()
104 .await?;
105
106 let ciphertext = encryption_response
107 .ciphertext
108 .expect("Unable to unwrap ciphertext from encryption response");
109
110 // 6. Demonstrate that the ciphertext and plaintext are different.
111 // (This is an example for demonstration; you do not need to do this in your own code.)
112 assert_ne!(
113 ciphertext,
114 aws_smithy_types::Blob::new(plaintext),
115 "Ciphertext and plaintext data are the same. Invalid encryption"
116 );
117
118 // 7. Now create a Discovery keyring to use for decryption. We'll add a discovery filter
119 // so that we limit the set of ciphertexts we are willing to decrypt to only ones
120 // created by KMS keys in our account and partition.
121 let discovery_filter = DiscoveryFilter::builder()
122 .account_ids(vec![aws_account_id.to_string()])
123 .partition("aws".to_string())
124 .build()?;
125
126 let discovery_keyring = mpl
127 .create_aws_kms_discovery_keyring()
128 .kms_client(kms_client.clone())
129 .discovery_filter(discovery_filter)
130 .send()
131 .await?;
132
133 // 8. Decrypt your encrypted data using the discovery keyring.
134 // On Decrypt, the header of the encrypted message (ciphertext) will be parsed.
135 // The header contains the Encrypted Data Keys (EDKs), which, if the EDK
136 // was encrypted by a KMS Keyring, includes the KMS Key ARN.
137 // The Discovery Keyring filters these EDKs for
138 // EDKs encrypted by Single Region OR Multi Region KMS Keys.
139 // If a Discovery Filter is present, these KMS Keys must belong
140 // to an AWS Account ID in the discovery filter's AccountIds and
141 // must be from the discovery filter's partition.
142 // Finally, KMS is called to decrypt each filtered EDK until an EDK is
143 // successfully decrypted. The resulting data key is used to decrypt the
144 // ciphertext's message.
145 // If all calls to KMS fail, the decryption fails.
146 let decryption_response = esdk_client
147 .decrypt()
148 .ciphertext(ciphertext.clone())
149 .keyring(discovery_keyring)
150 // Provide the encryption context that was supplied to the encrypt method
151 .encryption_context(encryption_context.clone())
152 .send()
153 .await?;
154
155 let decrypted_plaintext = decryption_response
156 .plaintext
157 .expect("Unable to unwrap plaintext from decryption response");
158
159 // 9. Demonstrate that the decrypted plaintext is identical to the original plaintext.
160 // (This is an example for demonstration; you do not need to do this in your own code.)
161 assert_eq!(
162 decrypted_plaintext,
163 aws_smithy_types::Blob::new(plaintext),
164 "Decrypted plaintext should be identical to the original plaintext. Invalid decryption"
165 );
166
167 // 10. Demonstrate that if a different discovery keyring (Bob's) doesn't have the correct
168 // AWS Account ID's, the decrypt will fail with an error message
169 // Note that this assumes Account ID used here ('888888888888') is different than the one used
170 // during encryption
171 let discovery_filter_bob = DiscoveryFilter::builder()
172 .account_ids(vec!["888888888888".to_string()])
173 .partition("aws".to_string())
174 .build()?;
175
176 let discovery_keyring_bob = mpl
177 .create_aws_kms_discovery_keyring()
178 .kms_client(kms_client)
179 .discovery_filter(discovery_filter_bob)
180 .send()
181 .await?;
182
183 // Decrypt the ciphertext using Bob's discovery keyring which doesn't contain the required
184 // Account ID's for the KMS keyring used for encryption.
185 // This should throw an AwsCryptographicMaterialProvidersError exception
186 let decryption_response_bob = esdk_client
187 .decrypt()
188 .ciphertext(ciphertext)
189 .keyring(discovery_keyring_bob)
190 // Provide the encryption context that was supplied to the encrypt method
191 .encryption_context(encryption_context)
192 .send()
193 .await;
194
195 match decryption_response_bob {
196 Ok(_) => panic!(
197 "Decrypt using discovery keyring with wrong AWS Account ID MUST \
198 raise AwsCryptographicMaterialProvidersError"
199 ),
200 Err(AwsCryptographicMaterialProvidersError { error: _e }) => (),
201 _ => panic!("Unexpected error type"),
202 }
203
204 println!("KMS Discovery Keyring Example Completed Successfully");
205
206 Ok(())
207}
208
209#[tokio::test(flavor = "multi_thread")]
210pub async fn test_encrypt_and_decrypt_with_keyring() -> Result<(), crate::BoxError2> {
211 // Test function for encrypt and decrypt using the AWS KMS Discovery Keyring example
212 use crate::example_utils::utils;
213
214 encrypt_and_decrypt_with_keyring(
215 utils::TEST_EXAMPLE_DATA,
216 utils::TEST_DEFAULT_KMS_KEY_ID,
217 utils::TEST_DEFAULT_KMS_KEY_ACCOUNT_ID,
218 )
219 .await?;
220
221 Ok(())
222}