Skip to main content

main/cryptographic_materials_manager/required_encryption_context/
required_encryption_context_example.rs

1// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4/*
5Demonstrate an encrypt/decrypt cycle using a Required Encryption Context CMM.
6A required encryption context CMM asks for required keys in the encryption context field
7on encrypt such that they will not be stored on the message, but WILL be included in the header signature.
8On decrypt, the client MUST supply the key/value pair(s) that were not stored to successfully decrypt the message.
9*/
10
11use aws_esdk::client as esdk_client;
12use aws_esdk::material_providers::client as mpl_client;
13use aws_esdk::material_providers::types::material_providers_config::MaterialProvidersConfig;
14use aws_esdk::types::aws_encryption_sdk_config::AwsEncryptionSdkConfig;
15use aws_esdk::types::error::Error::AwsCryptographicMaterialProvidersError;
16use std::collections::HashMap;
17use std::vec::Vec;
18
19pub async fn encrypt_and_decrypt_with_cmm(
20    example_data: &str,
21    kms_key_id: &str,
22) -> Result<(), crate::BoxError> {
23    // 1. Instantiate the encryption SDK client.
24    // This builds the default client with the RequireEncryptRequireDecrypt commitment policy,
25    // which enforces that this client only encrypts using committing algorithm suites and enforces
26    // that this client will only decrypt encrypted messages that were created with a committing
27    // algorithm suite.
28    let esdk_config = AwsEncryptionSdkConfig::builder().build()?;
29    let esdk_client = esdk_client::Client::from_conf(esdk_config)?;
30
31    // 2. Create a KMS client.
32    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
33    let kms_client = aws_sdk_kms::Client::new(&sdk_config);
34
35    // 3. Create encryption context.
36    // Remember that your encryption context is NOT SECRET.
37    // For more information, see
38    // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
39    let encryption_context = HashMap::from([
40        ("encryption".to_string(), "context".to_string()),
41        ("is not".to_string(), "secret".to_string()),
42        ("but adds".to_string(), "useful metadata".to_string()),
43        (
44            "that can help you".to_string(),
45            "be confident that".to_string(),
46        ),
47        (
48            "the data you are handling".to_string(),
49            "is what you think it is".to_string(),
50        ),
51        ("requiredKey1".to_string(), "requiredValue1".to_string()),
52        ("requiredKey2".to_string(), "requiredValue2".to_string()),
53    ]);
54
55    // 4. Create your required encryption context keys.
56    // These keys MUST be in your encryption context.
57    // These keys and their corresponding values WILL NOT be stored on the message but will be used
58    // for authentication.
59    let required_encryption_context_keys: Vec<String> =
60        vec!["requiredKey1".to_string(), "requiredKey2".to_string()];
61
62    // 5. Create a KMS keyring
63    let mpl_config = MaterialProvidersConfig::builder().build()?;
64    let mpl = mpl_client::Client::from_conf(mpl_config)?;
65
66    let kms_keyring = mpl
67        .create_aws_kms_keyring()
68        .kms_key_id(kms_key_id)
69        .kms_client(kms_client)
70        .send()
71        .await?;
72
73    // 6. Create the required encryption context CMM.
74    let underlying_cmm = mpl
75        .create_default_cryptographic_materials_manager()
76        .keyring(kms_keyring)
77        .send()
78        .await?;
79
80    let required_ec_cmm = mpl
81        .create_required_encryption_context_cmm()
82        .underlying_cmm(underlying_cmm.clone())
83        .required_encryption_context_keys(required_encryption_context_keys)
84        .send()
85        .await?;
86
87    // 7. Encrypt the data with the encryption_context
88    // NOTE: the keys "requiredKey1", and "requiredKey2"
89    // WILL NOT be stored in the message header, but "encryption", "is not",
90    // "but adds", "that can help you", and "the data you are handling" WILL be stored.
91    let plaintext = example_data.as_bytes();
92
93    let encryption_response = esdk_client
94        .encrypt()
95        .plaintext(plaintext)
96        .materials_manager(required_ec_cmm.clone())
97        .encryption_context(encryption_context.clone())
98        .send()
99        .await?;
100
101    let ciphertext = encryption_response
102        .ciphertext
103        .expect("Unable to unwrap ciphertext from encryption response");
104
105    // 8. Demonstrate that the ciphertext and plaintext are different.
106    // (This is an example for demonstration; you do not need to do this in your own code.)
107    assert_ne!(
108        ciphertext,
109        aws_smithy_types::Blob::new(plaintext),
110        "Ciphertext and plaintext data are the same. Invalid encryption"
111    );
112
113    // 9. Decrypt your encrypted data using the same keyring you used on encrypt.
114    let decryption_response = esdk_client
115        .decrypt()
116        .ciphertext(ciphertext.clone())
117        .materials_manager(required_ec_cmm.clone())
118        // Provide the encryption context that was supplied to the encrypt method
119        .encryption_context(encryption_context.clone())
120        .send()
121        .await?;
122
123    let decrypted_plaintext = decryption_response
124        .plaintext
125        .expect("Unable to unwrap plaintext from decryption response");
126
127    // 10. Demonstrate that the decrypted plaintext is identical to the original plaintext.
128    // (This is an example for demonstration; you do not need to do this in your own code.)
129    assert_eq!(
130        decrypted_plaintext,
131        aws_smithy_types::Blob::new(plaintext),
132        "Decrypted plaintext should be identical to the original plaintext. Invalid decryption"
133    );
134
135    // 11. Attempt to decrypt your encrypted data using the same cryptographic material manager
136    // you used on encrypt, but we won't pass the encryption context we DID NOT store on the message.
137    // This will fail
138    let decryption_response_without_ec = esdk_client
139        .decrypt()
140        .ciphertext(ciphertext.clone())
141        .materials_manager(required_ec_cmm.clone())
142        .send()
143        .await;
144
145    match decryption_response_without_ec {
146        Ok(_) => panic!(
147            "Decrypt without encryption context MUST \
148                            raise AwsCryptographicMaterialProvidersError"
149        ),
150        Err(AwsCryptographicMaterialProvidersError { error: _e }) => (),
151        _ => panic!("Unexpected error type"),
152    }
153
154    // 12. Decrypt your encrypted data using the same cryptographic material manager
155    // you used to encrypt, but supply encryption context that contains ONLY the encryption context that
156    // was NOT stored. This will pass.
157    let reproduced_encryption_context = HashMap::from([
158        ("requiredKey1".to_string(), "requiredValue1".to_string()),
159        ("requiredKey2".to_string(), "requiredValue2".to_string()),
160    ]);
161
162    let decryption_response_with_reproduced_ec = esdk_client
163        .decrypt()
164        .ciphertext(ciphertext.clone())
165        .materials_manager(required_ec_cmm)
166        // Provide the encryption context that was supplied to the encrypt method
167        .encryption_context(reproduced_encryption_context)
168        .send()
169        .await?;
170
171    let decrypted_plaintext_with_reproduced_ec = decryption_response_with_reproduced_ec
172        .plaintext
173        .expect("Unable to unwrap plaintext from decryption response");
174
175    // Demonstrate that the decrypted plaintext is identical to the original plaintext.
176    // (This is an example for demonstration; you do not need to do this in your own code.)
177    assert_eq!(
178        decrypted_plaintext_with_reproduced_ec,
179        aws_smithy_types::Blob::new(plaintext),
180        "Decrypted plaintext should be identical to the original plaintext. Invalid decryption"
181    );
182
183    // 13. You can decrypt the ciphertext using the underlying cmm, but not providing the
184    // encryption context with the request will result in an AwsCryptographicMaterialProvidersError
185
186    // This will pass
187    let decryption_response_with_ec_underlying_cmm = esdk_client
188        .decrypt()
189        .ciphertext(ciphertext.clone())
190        .materials_manager(underlying_cmm.clone())
191        // Provide the encryption context that was supplied to the encrypt method
192        .encryption_context(encryption_context)
193        .send()
194        .await?;
195
196    let decrypted_plaintext_with_ec_underlying_cmm = decryption_response_with_ec_underlying_cmm
197        .plaintext
198        .expect("Unable to unwrap plaintext from decryption response");
199
200    // Demonstrate that the decrypted plaintext is identical to the original plaintext.
201    // (This is an example for demonstration; you do not need to do this in your own code.)
202    assert_eq!(
203        decrypted_plaintext_with_ec_underlying_cmm,
204        aws_smithy_types::Blob::new(plaintext),
205        "Decrypted plaintext should be identical to the original plaintext. Invalid decryption"
206    );
207
208    // This will fail
209    let decryption_response_without_ec_underlying_cmm = esdk_client
210        .decrypt()
211        .ciphertext(ciphertext)
212        .materials_manager(underlying_cmm)
213        .send()
214        .await;
215
216    match decryption_response_without_ec_underlying_cmm {
217        Ok(_) => panic!(
218            "Decrypt without encryption context MUST \
219                            raise AwsCryptographicMaterialProvidersError"
220        ),
221        Err(AwsCryptographicMaterialProvidersError { error: _e }) => (),
222        _ => panic!("Unexpected error type"),
223    }
224
225    println!("Required Encryption Context CMM Example Completed Successfully");
226
227    Ok(())
228}
229
230#[tokio::test(flavor = "multi_thread")]
231pub async fn test_encrypt_and_decrypt_with_cmm() -> Result<(), crate::BoxError2> {
232    // Test function for encrypt and decrypt using the Required Encryption Context CMM example
233    use crate::example_utils::utils;
234
235    encrypt_and_decrypt_with_cmm(utils::TEST_EXAMPLE_DATA, utils::TEST_DEFAULT_KMS_KEY_ID).await?;
236
237    Ok(())
238}