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}