Skip to main content

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}