main/keyring/ecdh/kms_ecdh_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 KMS ECDH Keyring.
6
7This example takes in the sender's KMS ECC key ARN, the sender's public key,
8the recipient's public key, and the algorithm definition where the ECC keys lie.
9
10Both public keys MUST be UTF8 PEM-encoded X.509 public key,
11also known as SubjectPublicKeyInfo (SPKI),
12
13This keyring, depending on its KeyAgreement scheme,
14takes in the sender's KMS ECC Key ARN, and the recipient's ECC Public Key
15to derive a shared secret.
16The keyring uses the shared secret to derive a data key to protect the
17data keys that encrypt and decrypt example_data.
18
19This example also requires access to a KMS ECC key.
20Our tests provide a KMS ECC Key ARN that anyone can use, but you
21can also provide your own KMS ECC key.
22To use your own KMS ECC key, you must have either:
23- Its public key downloaded in a UTF-8 encoded PEM file
24- kms:GetPublicKey permissions on that key.
25If you do not have the public key downloaded, running this example
26through its main method will download the public key for you
27by calling kms:GetPublicKey.
28You must also have kms:DeriveSharedSecret permissions on the KMS ECC key.
29This example also requires a recipient ECC Public Key that lies on the same
30curve as the sender public key. This examples uses another distinct
31KMS ECC Public Key, it does not have to be a KMS key; it can be a
32valid SubjectPublicKeyInfo (SPKI) Public Key.
33
34This example creates a KMS ECDH Keyring and then encrypts a custom input EXAMPLE_DATA
35with an encryption context. This example also includes some sanity checks for demonstration:
361. Ciphertext and plaintext data are not the same
372. Decrypted plaintext value matches EXAMPLE_DATA
38These sanity checks are for demonstration in the example only. You do not need these in your code.
39
40For more information on this configuration see:
41https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-ecdh-keyring.html#kms-ecdh-create
42*/
43
44use crate::example_utils::utils::exists;
45use crate::example_utils::utils::write_kms_ecdh_ecc_public_key;
46use crate::example_utils::utils::EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_RECIPIENT;
47use crate::example_utils::utils::EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_SENDER;
48use aws_esdk::aws_cryptography_primitives::types::EcdhCurveSpec;
49use aws_esdk::client as esdk_client;
50use aws_esdk::material_providers::client as mpl_client;
51use aws_esdk::material_providers::types::material_providers_config::MaterialProvidersConfig;
52use aws_esdk::material_providers::types::KmsEcdhStaticConfigurations;
53use aws_esdk::material_providers::types::KmsPrivateKeyToStaticPublicKeyInput;
54use aws_esdk::types::aws_encryption_sdk_config::AwsEncryptionSdkConfig;
55use pem::parse;
56use std::collections::HashMap;
57use std::path::Path;
58
59pub async fn encrypt_and_decrypt_with_keyring(
60 example_data: &str,
61 ecc_key_arn: &str,
62 ecdh_curve_spec: EcdhCurveSpec,
63 ecc_recipient_key_arn: Option<&str>,
64) -> Result<(), crate::BoxError> {
65 // 1. If ecc_recipient_key_arn is not provided, set the private key for the recipient to TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT
66 let ecc_recipient_key_arn = ecc_recipient_key_arn
67 .unwrap_or(crate::example_utils::utils::TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT);
68
69 // 2. Instantiate the encryption SDK client.
70 // This builds the default client with the RequireEncryptRequireDecrypt commitment policy,
71 // which enforces that this client only encrypts using committing algorithm suites and enforces
72 // that this client will only decrypt encrypted messages that were created with a committing
73 // algorithm suite.
74 let esdk_config = AwsEncryptionSdkConfig::builder().build()?;
75 let esdk_client = esdk_client::Client::from_conf(esdk_config)?;
76
77 // 3. Create a KMS client.
78 let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
79 let kms_client = aws_sdk_kms::Client::new(&sdk_config);
80
81 // 4. Create encryption context.
82 // Remember that your encryption context is NOT SECRET.
83 // For more information, see
84 // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
85 let encryption_context = HashMap::from([
86 ("encryption".to_string(), "context".to_string()),
87 ("is not".to_string(), "secret".to_string()),
88 ("but adds".to_string(), "useful metadata".to_string()),
89 (
90 "that can help you".to_string(),
91 "be confident that".to_string(),
92 ),
93 (
94 "the data you are handling".to_string(),
95 "is what you think it is".to_string(),
96 ),
97 ]);
98
99 // 5. You may provide your own ECC keys in the files located at
100 // - EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_SENDER
101 // - EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_RECIPIENT
102
103 // If not, the main method in this class will call
104 // the KMS ECC key, retrieve its public key, and store it
105 // in a PEM file for example use.
106 if should_generate_new_kms_ecc_public_key(EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_SENDER)? {
107 write_kms_ecdh_ecc_public_key(ecc_key_arn, EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_SENDER)
108 .await?;
109 }
110
111 if should_generate_new_kms_ecc_public_key(EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_RECIPIENT)? {
112 write_kms_ecdh_ecc_public_key(
113 ecc_recipient_key_arn,
114 EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_RECIPIENT,
115 )
116 .await?;
117 }
118
119 // 6. Load public key from UTF-8 encoded PEM files into a DER encoded public key.
120 let public_key_file_content_sender =
121 std::fs::read_to_string(Path::new(EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_SENDER))?;
122 let parsed_public_key_file_content_sender = parse(public_key_file_content_sender)?;
123 let public_key_sender_utf8_bytes = parsed_public_key_file_content_sender.contents();
124
125 let public_key_file_content_recipient =
126 std::fs::read_to_string(Path::new(EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_RECIPIENT))?;
127 let parsed_public_key_file_content_recipient = parse(public_key_file_content_recipient)?;
128 let public_key_recipient_utf8_bytes = parsed_public_key_file_content_recipient.contents();
129
130 // 7. Create the KmsPrivateKeyToStaticPublicKeyInput
131 let kms_ecdh_static_configuration_input = KmsPrivateKeyToStaticPublicKeyInput::builder()
132 .sender_kms_identifier(ecc_key_arn)
133 // Must be a UTF8 DER-encoded X.509 public key
134 .sender_public_key(public_key_sender_utf8_bytes)
135 // Must be a UTF8 DER-encoded X.509 public key
136 .recipient_public_key(public_key_recipient_utf8_bytes)
137 .build()?;
138
139 let kms_ecdh_static_configuration = KmsEcdhStaticConfigurations::KmsPrivateKeyToStaticPublicKey(
140 kms_ecdh_static_configuration_input,
141 );
142
143 // 8. Create the KMS ECDH keyring.
144 let mpl_config = MaterialProvidersConfig::builder().build()?;
145 let mpl = mpl_client::Client::from_conf(mpl_config)?;
146
147 // Create a KMS ECDH keyring.
148 // This keyring uses the KmsPrivateKeyToStaticPublicKey configuration. This configuration calls for both of
149 // the keys to be on the same curve (P256, P384, P521).
150 // On encrypt, the keyring calls AWS KMS to derive the shared secret from the sender's KMS ECC Key ARN and the recipient's public key.
151 // For this example, on decrypt, the keyring calls AWS KMS to derive the shared secret from the sender's KMS ECC Key ARN and the recipient's public key;
152 // however, on decrypt, the recipient can construct a keyring such that the shared secret is calculated with
153 // the recipient's private key and the sender's public key. In both scenarios the shared secret will be the same.
154 // For more information on this configuration see:
155 // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-ecdh-keyring.html#kms-ecdh-create
156 // This keyring takes in:
157 // - kmsClient
158 // - kmsKeyId: Must be an ARN representing a KMS ECC key meant for KeyAgreement
159 // - curveSpec: The curve name where the public keys lie
160 // - senderPublicKey: A ByteBuffer of a UTF-8 encoded public
161 // key for the key passed into kmsKeyId in DER format
162 // - recipientPublicKey: A ByteBuffer of a UTF-8 encoded public
163 // key for the key passed into kmsKeyId in DER format
164 let kms_ecdh_keyring = mpl
165 .create_aws_kms_ecdh_keyring()
166 .kms_client(kms_client)
167 .curve_spec(ecdh_curve_spec)
168 .key_agreement_scheme(kms_ecdh_static_configuration)
169 .send()
170 .await?;
171
172 // 9. Encrypt the data with the encryption_context
173 let plaintext = example_data.as_bytes();
174
175 let encryption_response = esdk_client
176 .encrypt()
177 .plaintext(plaintext)
178 .keyring(kms_ecdh_keyring.clone())
179 .encryption_context(encryption_context.clone())
180 .send()
181 .await?;
182
183 let ciphertext = encryption_response
184 .ciphertext
185 .expect("Unable to unwrap ciphertext from encryption response");
186
187 // 10. Demonstrate that the ciphertext and plaintext are different.
188 // (This is an example for demonstration; you do not need to do this in your own code.)
189 assert_ne!(
190 ciphertext,
191 aws_smithy_types::Blob::new(plaintext),
192 "Ciphertext and plaintext data are the same. Invalid encryption"
193 );
194
195 // 11. Decrypt your encrypted data using the same keyring you used on encrypt.
196 let decryption_response = esdk_client
197 .decrypt()
198 .ciphertext(ciphertext)
199 .keyring(kms_ecdh_keyring)
200 // Provide the encryption context that was supplied to the encrypt method
201 .encryption_context(encryption_context)
202 .send()
203 .await?;
204
205 let decrypted_plaintext = decryption_response
206 .plaintext
207 .expect("Unable to unwrap plaintext from decryption response");
208
209 // 12. Demonstrate that the decrypted plaintext is identical to the original plaintext.
210 // (This is an example for demonstration; you do not need to do this in your own code.)
211 assert_eq!(
212 decrypted_plaintext,
213 aws_smithy_types::Blob::new(plaintext),
214 "Decrypted plaintext should be identical to the original plaintext. Invalid decryption"
215 );
216
217 println!("KMS ECDH Keyring Example Completed Successfully");
218
219 Ok(())
220}
221
222fn should_generate_new_kms_ecc_public_key(ecc_public_key_filename: &str) -> Result<bool, String> {
223 // If key already exists: do not overwrite existing key
224 if exists(ecc_public_key_filename) {
225 Ok(false)
226 }
227 // If file is not present, generate a new key pair
228 else {
229 Ok(true)
230 }
231}
232
233#[tokio::test(flavor = "multi_thread")]
234pub async fn test_encrypt_and_decrypt_with_keyring() -> Result<(), crate::BoxError2> {
235 // Test function for encrypt and decrypt using the KMS ECDH Keyring example
236 use crate::example_utils::utils;
237
238 encrypt_and_decrypt_with_keyring(
239 utils::TEST_EXAMPLE_DATA,
240 utils::TEST_KMS_ECDH_KEY_ID_P256_SENDER,
241 EcdhCurveSpec::EccNistP256,
242 Some(utils::TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT),
243 )
244 .await?;
245
246 Ok(())
247}