main/keyring/ecdh/
public_key_discovery_raw_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 Public Key Discovery Raw ECDH Keyring.
6
7A public key discovery Raw ECDH Keyring takes in the recipient's private key located
8at EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT
9as a UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures) private key,
10and the Curve Specification where the key lies.
11
12If you provide the EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT, make sure to also
13provide the recipient's public key located at EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT
14in the directory that you run this example. Even though the Public Key Discovery Raw ECDH keyring
15uses the EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT to decrypt the data,
16the EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT is needed to generate the ciphertext to decrypt.
17
18This example loads ECC keys from PEM files and the ciphertext with paths defined in
19 - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT
20 - EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT
21
22If you do not provide these files, running this example through this
23class' main method will generate three files required for all raw ECDH examples
24EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER, EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT
25and EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT for you.
26In practice, users of this library should not generate new key pairs
27like this, and should instead retrieve an existing key from a secure
28key management system (e.g. an HSM).
29You may also provide your own key pair by placing PEM files in the
30directory where the example is run or modifying the paths in the code
31below. These files must be valid PEM encodings of the key pair as UTF-8
32encoded bytes. If you do provide your own key pair, or if a key pair
33already exists, this class' main method will not generate a new key pair.
34
35This examples creates a RawECDH keyring with the PublicKeyDiscovery key agreement scheme.
36This scheme is only available on decrypt.
37
38This example creates a Public Key Discovery Raw ECDH Keyring and takes in a ciphertext to decrypt it.
39This example also includes some sanity checks for demonstration:
401. Decrypted plaintext value matches EXAMPLE_DATA
41These sanity checks are for demonstration in the example only. You do not need these in your code.
42
43For more information on this configuration see:
44https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-ecdh-keyring.html#raw-ecdh-PublicKeyDiscovery
45*/
46
47use crate::example_utils::utils::exists;
48use crate::example_utils::utils::write_raw_ecdh_ecc_keys;
49use crate::example_utils::utils::EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT;
50use crate::example_utils::utils::EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT;
51use aws_esdk::aws_cryptography_primitives::types::EcdhCurveSpec;
52use aws_esdk::client as esdk_client;
53use aws_esdk::material_providers::client as mpl_client;
54use aws_esdk::material_providers::types::material_providers_config::MaterialProvidersConfig;
55use aws_esdk::material_providers::types::EphemeralPrivateKeyToStaticPublicKeyInput;
56use aws_esdk::material_providers::types::PublicKeyDiscoveryInput;
57use aws_esdk::material_providers::types::RawEcdhStaticConfigurations;
58use aws_esdk::types::aws_encryption_sdk_config::AwsEncryptionSdkConfig;
59use aws_smithy_types::Blob;
60use pem::parse;
61use std::collections::HashMap;
62use std::fs::File;
63use std::io::Read;
64use std::path::Path;
65
66pub async fn decrypt_with_keyring(
67    example_data: &str,
68    ecdh_curve_spec: EcdhCurveSpec,
69) -> Result<(), crate::BoxError> {
70    // 1. Instantiate the encryption SDK client.
71    // This builds the default client with the RequireEncryptRequireDecrypt commitment policy,
72    // which enforces that this client only encrypts using committing algorithm suites and enforces
73    // that this client will only decrypt encrypted messages that were created with a committing
74    // algorithm suite.
75    let esdk_config = AwsEncryptionSdkConfig::builder().build()?;
76    let esdk_client = esdk_client::Client::from_conf(esdk_config)?;
77
78    let mpl_config = MaterialProvidersConfig::builder().build()?;
79    let mpl = mpl_client::Client::from_conf(mpl_config)?;
80
81    // 2. 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    // 3. You may provide your own ECC keys in the files located at
100    // - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT
101
102    // If you do not provide these files, running this example through this
103    // class' main method will generate three files required for all raw ECDH examples
104    // EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER, EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT
105    // and EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT for you.
106
107    // Do not use these files for any other purpose.
108    if should_generate_new_ecc_key_pair_discovery_raw_ecdh()? {
109        write_raw_ecdh_ecc_keys(ecdh_curve_spec)?;
110    }
111
112    // 4. Load keys from UTF-8 encoded PEM files.
113    let mut file = File::open(Path::new(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT))?;
114    let mut private_key_recipient_utf8_bytes = Vec::new();
115    file.read_to_end(&mut private_key_recipient_utf8_bytes)?;
116
117    // Generate the ciphertext
118    let ciphertext = get_ciphertext(
119        example_data,
120        ecdh_curve_spec,
121        encryption_context.clone(),
122        esdk_client.clone(),
123        mpl.clone(),
124    )
125    .await?;
126
127    // 5. Create the PublicKeyDiscoveryInput
128    let discovery_raw_ecdh_static_configuration_input = PublicKeyDiscoveryInput::builder()
129        // Must be a UTF8 PEM-encoded private key
130        .recipient_static_private_key(private_key_recipient_utf8_bytes)
131        .build()?;
132
133    let discovery_raw_ecdh_static_configuration = RawEcdhStaticConfigurations::PublicKeyDiscovery(
134        discovery_raw_ecdh_static_configuration_input,
135    );
136
137    // 6. Create the Public Key Discovery Raw ECDH keyring.
138
139    // Create the keyring.
140    // This keyring uses a discovery configuration. This configuration will check on decrypt
141    // if it is meant to decrypt the message by checking if the configured public key is stored on the message.
142    // The discovery configuration can only decrypt messages and CANNOT encrypt messages.
143    let discovery_raw_ecdh_keyring = mpl
144        .create_raw_ecdh_keyring()
145        .curve_spec(ecdh_curve_spec)
146        .key_agreement_scheme(discovery_raw_ecdh_static_configuration)
147        .send()
148        .await?;
149
150    // 7. Decrypt your encrypted data using the same keyring you used on encrypt.
151    let decryption_response = esdk_client
152        .decrypt()
153        .ciphertext(ciphertext)
154        .keyring(discovery_raw_ecdh_keyring)
155        // Provide the encryption context that was supplied to the encrypt method
156        .encryption_context(encryption_context)
157        .send()
158        .await?;
159
160    let decrypted_plaintext = decryption_response
161        .plaintext
162        .expect("Unable to unwrap plaintext from decryption response");
163
164    // 8. Demonstrate that the decrypted plaintext is identical to the original plaintext.
165    // (This is an example for demonstration; you do not need to do this in your own code.)
166    let plaintext = example_data.as_bytes();
167
168    assert_eq!(
169        decrypted_plaintext,
170        aws_smithy_types::Blob::new(plaintext),
171        "Decrypted plaintext should be identical to the original plaintext. Invalid decryption"
172    );
173
174    println!("Public Key Discovery Raw ECDH Keyring Example Completed Successfully");
175
176    Ok(())
177}
178
179fn should_generate_new_ecc_key_pair_discovery_raw_ecdh() -> Result<bool, String> {
180    // If keys already exist: do not overwrite existing keys
181    if exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)
182        && exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT)
183    {
184        Ok(false)
185    }
186    // If only one file is present: throw exception
187    else if !exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)
188        && exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT)
189    {
190        Err("Missing key file at ".to_string() + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)
191    } else if exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)
192        && !exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT)
193    {
194        Err("Missing key file at ".to_string() + EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT)
195    }
196    // If neither file is present, generate a new key pair
197    else {
198        Ok(true)
199    }
200}
201
202async fn get_ciphertext(
203    example_data: &str,
204    ecdh_curve_spec: EcdhCurveSpec,
205    encryption_context: HashMap<String, String>,
206    esdk_client: esdk_client::Client,
207    mpl: mpl_client::Client,
208) -> Result<Blob, crate::BoxError> {
209    // 1. Load keys from UTF-8 encoded PEM files.
210
211    // Load public key from UTF-8 encoded PEM files into a DER encoded public key.
212    let public_key_file_content =
213        std::fs::read_to_string(Path::new(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT))?;
214    let parsed_public_key_file_content = parse(public_key_file_content)?;
215    let public_key_recipient_utf8_bytes = parsed_public_key_file_content.contents();
216
217    // 2. Create the EphemeralPrivateKeyToStaticPublicKeyInput to generate the ciphertext
218    let ephemeral_raw_ecdh_static_configuration_input =
219        EphemeralPrivateKeyToStaticPublicKeyInput::builder()
220            // Must be a UTF8 DER-encoded X.509 public key
221            .recipient_public_key(public_key_recipient_utf8_bytes)
222            .build()?;
223
224    let ephemeral_raw_ecdh_static_configuration =
225        RawEcdhStaticConfigurations::EphemeralPrivateKeyToStaticPublicKey(
226            ephemeral_raw_ecdh_static_configuration_input,
227        );
228
229    // 3. Create the Ephemeral Raw ECDH keyring.
230
231    // Create the keyring.
232    // This keyring uses an ephemeral configuration. This configuration will always create a new
233    // key pair as the sender key pair for the key agreement operation. The ephemeral configuration can only
234    // encrypt data and CANNOT decrypt messages.
235    let ephemeral_raw_ecdh_keyring = mpl
236        .create_raw_ecdh_keyring()
237        .curve_spec(ecdh_curve_spec)
238        .key_agreement_scheme(ephemeral_raw_ecdh_static_configuration)
239        .send()
240        .await?;
241
242    // 4. Encrypt the data with the encryption_context
243
244    // A raw ecdh keyring with Ephemeral configuration cannot decrypt data since the key pair
245    // used as the sender is ephemeral. This means that at decrypt time it does not have
246    // the private key that corresponds to the public key that is stored on the message.
247    let plaintext = example_data.as_bytes();
248
249    let encryption_response = esdk_client
250        .encrypt()
251        .plaintext(plaintext)
252        .keyring(ephemeral_raw_ecdh_keyring)
253        .encryption_context(encryption_context)
254        .send()
255        .await?;
256
257    let ciphertext = encryption_response
258        .ciphertext
259        .expect("Unable to unwrap ciphertext from encryption response");
260
261    // 5. Demonstrate that the ciphertext and plaintext are different.
262    // (This is an example for demonstration; you do not need to do this in your own code.)
263    assert_ne!(
264        ciphertext,
265        aws_smithy_types::Blob::new(plaintext),
266        "Ciphertext and plaintext data are the same. Invalid encryption"
267    );
268
269    Ok(ciphertext)
270}
271
272#[tokio::test(flavor = "multi_thread")]
273pub async fn test_decrypt_with_keyring() -> Result<(), crate::BoxError2> {
274    // Test function for decrypt using the Public Key Discovery Raw ECDH Keyring example
275    use crate::example_utils::utils;
276
277    decrypt_with_keyring(utils::TEST_EXAMPLE_DATA, EcdhCurveSpec::EccNistP256).await?;
278
279    Ok(())
280}