main/keyring/ecdh/
kms_ecdh_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 KMS ECDH Discovery Keyring.
6
7This example takes in the recipient's KMS ECC key ARN.
8This example attempts to decrypt a ciphertext using the provided ecc_recipient_key_arn,
9it does so by checking if the message header contains the recipient's public key.
10
11This example also requires access to a KMS ECC key.
12Our tests provide a KMS ECC Key ARN that anyone can use, but you
13can also provide your own KMS ECC key.
14To use your own KMS ECC key, you must have:
15    - kms:GetPublicKey permissions on that key.
16This example will call kms:GetPublicKey on keyring creation.
17You must also have kms:DeriveSharedSecret permissions on the KMS ECC key.
18
19This example creates a KMS ECDH Discovery Keyring and then decrypts a ciphertext.
20For getting the ciphertext, we create a KMS ECDH keyring without discovery
21because kms_ecdh_discovery_keyring cannot encrypt data.
22This example also includes some sanity checks for demonstration:
231. Decrypted plaintext value matches EXAMPLE_DATA
24These sanity checks are for demonstration in the example only. You do not need these in your code.
25
26For more information on this configuration see:
27https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-ecdh-keyring.html#kms-ecdh-discovery
28*/
29
30use crate::example_utils::utils::generate_kms_ecc_public_key;
31use crate::example_utils::utils::TEST_KMS_ECDH_KEY_ID_P256_SENDER;
32use aws_esdk::aws_cryptography_primitives::types::EcdhCurveSpec;
33use aws_esdk::client as esdk_client;
34use aws_esdk::material_providers::client as mpl_client;
35use aws_esdk::material_providers::types::material_providers_config::MaterialProvidersConfig;
36use aws_esdk::material_providers::types::KmsEcdhStaticConfigurations;
37use aws_esdk::material_providers::types::KmsPrivateKeyToStaticPublicKeyInput;
38use aws_esdk::material_providers::types::KmsPublicKeyDiscoveryInput;
39use aws_esdk::types::aws_encryption_sdk_config::AwsEncryptionSdkConfig;
40use aws_smithy_types::Blob;
41use std::collections::HashMap;
42
43pub async fn decrypt_with_keyring(
44    example_data: &str,
45    ecdh_curve_spec: EcdhCurveSpec,
46    ecc_recipient_key_arn: &str,
47) -> Result<(), crate::BoxError> {
48    // 1. Instantiate the encryption SDK client.
49    // This builds the default client with the RequireEncryptRequireDecrypt commitment policy,
50    // which enforces that this client only encrypts using committing algorithm suites and enforces
51    // that this client will only decrypt encrypted messages that were created with a committing
52    // algorithm suite.
53    let esdk_config = AwsEncryptionSdkConfig::builder().build()?;
54    let esdk_client = esdk_client::Client::from_conf(esdk_config)?;
55
56    // 2. Create a KMS client.
57    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
58    let kms_client = aws_sdk_kms::Client::new(&sdk_config);
59
60    // 3. Create encryption context.
61    // Remember that your encryption context is NOT SECRET.
62    // For more information, see
63    // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
64    let encryption_context = HashMap::from([
65        ("encryption".to_string(), "context".to_string()),
66        ("is not".to_string(), "secret".to_string()),
67        ("but adds".to_string(), "useful metadata".to_string()),
68        (
69            "that can help you".to_string(),
70            "be confident that".to_string(),
71        ),
72        (
73            "the data you are handling".to_string(),
74            "is what you think it is".to_string(),
75        ),
76    ]);
77
78    // 4. Create the KmsPublicKeyDiscoveryInput
79    let kms_ecdh_discovery_static_configuration_input = KmsPublicKeyDiscoveryInput::builder()
80        .recipient_kms_identifier(ecc_recipient_key_arn)
81        .build()?;
82
83    let kms_ecdh_discovery_static_configuration =
84        KmsEcdhStaticConfigurations::KmsPublicKeyDiscovery(
85            kms_ecdh_discovery_static_configuration_input,
86        );
87
88    // 5. Create the KMS ECDH keyring.
89    let mpl_config = MaterialProvidersConfig::builder().build()?;
90    let mpl = mpl_client::Client::from_conf(mpl_config)?;
91
92    // Create a KMS ECDH Discovery keyring.
93    // This keyring uses the KmsPublicKeyDiscovery configuration.
94    // On encrypt, the keyring will fail as it is not allowed to encrypt data under this configuration.
95    // On decrypt, the keyring will check if its corresponding public key is stored in the message header. It
96    // will AWS KMS to derive the shared from the recipient's KMS ECC Key ARN and the sender's public key;
97    // For more information on this configuration see:
98    // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-ecdh-keyring.html#kms-ecdh-discovery
99    // This keyring takes in:
100    //  - kmsClient
101    //  - recipientKmsIdentifier: Must be an ARN representing a KMS ECC key meant for KeyAgreement
102    //  - curveSpec: The curve name where the public keys lie
103    let kms_ecdh_discovery_keyring = mpl
104        .create_aws_kms_ecdh_keyring()
105        .kms_client(kms_client.clone())
106        .curve_spec(ecdh_curve_spec)
107        .key_agreement_scheme(kms_ecdh_discovery_static_configuration)
108        .send()
109        .await?;
110
111    // 6. Get ciphertext by creating a KMS ECDH keyring WITHOUT discovery
112    // because the KMS ECDH keyring WITH discovery CANNOT encrypt data.
113    let plaintext = example_data.as_bytes();
114
115    // Get ciphertext by creating a KMS ECDH keyring WITHOUT discovery.
116    // The recipient's public key used in the encrypting KMS ECDH keyring WITHOUT discovery
117    // is a public key generated from ecc_recipient_key_arn, the same ecc key used
118    // when creating the KMS ECDH keyring WITH discovery used for decryption in this example.
119    // We then decrypt this ciphertext using a KMS ECDH keyring WITH discovery
120    let ciphertext = get_ciphertext(
121        example_data,
122        encryption_context.clone(),
123        ecc_recipient_key_arn,
124        ecdh_curve_spec,
125        kms_client,
126        esdk_client.clone(),
127    )
128    .await?;
129
130    // 7. Decrypt your encrypted data using the same keyring you used on encrypt.
131    let decryption_response = esdk_client
132        .decrypt()
133        .ciphertext(ciphertext)
134        .keyring(kms_ecdh_discovery_keyring)
135        // Provide the encryption context that was supplied to the encrypt method
136        .encryption_context(encryption_context)
137        .send()
138        .await?;
139
140    let decrypted_plaintext = decryption_response
141        .plaintext
142        .expect("Unable to unwrap plaintext from decryption response");
143
144    // 8. Demonstrate that the decrypted plaintext is identical to the original plaintext.
145    // (This is an example for demonstration; you do not need to do this in your own code.)
146    assert_eq!(
147        decrypted_plaintext,
148        aws_smithy_types::Blob::new(plaintext),
149        "Decrypted plaintext should be identical to the original plaintext. Invalid decryption"
150    );
151
152    println!("KMS ECDH Discovery Keyring Example Completed Successfully");
153
154    Ok(())
155}
156
157async fn get_ciphertext(
158    example_data: &str,
159    encryption_context: HashMap<String, String>,
160    ecc_recipient_key_arn: &str,
161    ecdh_curve_spec: EcdhCurveSpec,
162    kms_client: aws_sdk_kms::Client,
163    esdk_client: esdk_client::Client,
164) -> Result<Blob, crate::BoxError> {
165    // 1. Create the public keys for sender and recipient
166    // Recipient keys are taken as input for this example
167    // Sender ECC key used in this example is TEST_KMS_ECDH_KEY_ID_P256_SENDER
168    let public_key_sender_utf8_bytes =
169        generate_kms_ecc_public_key(TEST_KMS_ECDH_KEY_ID_P256_SENDER).await?;
170    let public_key_recipient_utf8_bytes =
171        generate_kms_ecc_public_key(ecc_recipient_key_arn).await?;
172
173    // 2. Create the KmsPrivateKeyToStaticPublicKeyInput
174    let kms_ecdh_static_configuration_input = KmsPrivateKeyToStaticPublicKeyInput::builder()
175        .sender_kms_identifier(TEST_KMS_ECDH_KEY_ID_P256_SENDER)
176        // Must be a UTF8 DER-encoded X.509 public key
177        .sender_public_key(public_key_sender_utf8_bytes)
178        // Must be a UTF8 DER-encoded X.509 public key
179        .recipient_public_key(public_key_recipient_utf8_bytes)
180        .build()?;
181
182    let kms_ecdh_static_configuration = KmsEcdhStaticConfigurations::KmsPrivateKeyToStaticPublicKey(
183        kms_ecdh_static_configuration_input,
184    );
185
186    // 3. Create the KMS ECDH keyring.
187    let mpl_config = MaterialProvidersConfig::builder().build()?;
188    let mpl = mpl_client::Client::from_conf(mpl_config)?;
189
190    let kms_ecdh_keyring = mpl
191        .create_aws_kms_ecdh_keyring()
192        .kms_client(kms_client)
193        .curve_spec(ecdh_curve_spec)
194        .key_agreement_scheme(kms_ecdh_static_configuration)
195        .send()
196        .await?;
197
198    // 4. Encrypt the data with the encryption_context
199    let plaintext = example_data.as_bytes();
200
201    let encryption_response = esdk_client
202        .encrypt()
203        .plaintext(plaintext)
204        .keyring(kms_ecdh_keyring.clone())
205        .encryption_context(encryption_context.clone())
206        .send()
207        .await?;
208
209    let ciphertext = encryption_response
210        .ciphertext
211        .expect("Unable to unwrap ciphertext from encryption response");
212
213    // 5. Demonstrate that the ciphertext and plaintext are different.
214    // (This is an example for demonstration; you do not need to do this in your own code.)
215    assert_ne!(
216        ciphertext,
217        aws_smithy_types::Blob::new(plaintext),
218        "Ciphertext and plaintext data are the same. Invalid encryption"
219    );
220
221    Ok(ciphertext)
222}
223
224#[tokio::test(flavor = "multi_thread")]
225pub async fn test_decrypt_with_keyring() -> Result<(), crate::BoxError2> {
226    // Test function for decrypt using the KMS ECDH Discovery Keyring example
227    use crate::example_utils::utils;
228
229    decrypt_with_keyring(
230        utils::TEST_EXAMPLE_DATA,
231        EcdhCurveSpec::EccNistP256,
232        utils::TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT,
233    )
234    .await?;
235
236    Ok(())
237}