main/keyring/ecdh/
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 Raw ECDH Keyring.
6
7This example takes in the sender's private key located at
8EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER as a UTF8 PEM-encoded
9(PKCS #8 PrivateKeyInfo structures) private key,
10and the recipient's public key located at
11EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT as a
12UTF8 PEM-encoded X.509 public key,
13also known as SubjectPublicKeyInfo (SPKI),
14and the Curve Specification where the keys lie.
15
16This example loads ECC keys from PEM files with paths defined in
17 - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER
18 - EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT
19
20If you do not provide these files, running this example through this
21class' main method will generate three files required for all raw ECDH examples
22EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER, EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT
23and EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT for you.
24These files will be generated in the directory where the example is run.
25In practice, users of this library should not generate new key pairs
26like this, and should instead retrieve an existing key from a secure
27key management system (e.g. an HSM).
28You may also provide your own key pair by placing PEM files in the
29directory where the example is run or modifying the paths in the code
30below. These files must be valid PEM encodings of the key pair as UTF-8
31encoded bytes. If you do provide your own key pair, or if a key pair
32already exists, this class' main method will not generate a new key pair.
33
34This example creates a RawECDH keyring with the RawPrivateKeyToStaticPublicKey key agreement scheme.
35On encrypt, the shared secret is derived from the sender's private key and the recipient's public key.
36On decrypt, the shared secret is derived from the sender's private key and the recipient's public key;
37however, on decrypt the recipient can construct a keyring such that the shared secret is calculated with
38the recipient's private key and the sender's public key. In both scenarios the shared secret will be the same.
39
40This example creates a Raw ECDH Keyring and then encrypts a custom input EXAMPLE_DATA
41with an encryption context. This example also includes some sanity checks for demonstration:
421. Ciphertext and plaintext data are not the same
432. Decrypted plaintext value matches EXAMPLE_DATA
44These sanity checks are for demonstration in the example only. You do not need these in your code.
45
46For more information on this configuration see:
47https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-ecdh-keyring.html#raw-ecdh-RawPrivateKeyToStaticPublicKey
48*/
49
50use crate::example_utils::utils::exists;
51use crate::example_utils::utils::write_raw_ecdh_ecc_keys;
52use crate::example_utils::utils::EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER;
53use crate::example_utils::utils::EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT;
54use aws_esdk::aws_cryptography_primitives::types::EcdhCurveSpec;
55use aws_esdk::client as esdk_client;
56use aws_esdk::material_providers::client as mpl_client;
57use aws_esdk::material_providers::types::material_providers_config::MaterialProvidersConfig;
58use aws_esdk::material_providers::types::RawEcdhStaticConfigurations;
59use aws_esdk::material_providers::types::RawPrivateKeyToStaticPublicKeyInput;
60use aws_esdk::types::aws_encryption_sdk_config::AwsEncryptionSdkConfig;
61use pem::parse;
62use std::collections::HashMap;
63use std::fs::File;
64use std::io::Read;
65use std::path::Path;
66
67pub async fn encrypt_and_decrypt_with_keyring(
68    example_data: &str,
69    ecdh_curve_spec: EcdhCurveSpec,
70) -> Result<(), crate::BoxError> {
71    // 1. Instantiate the encryption SDK client.
72    // This builds the default client with the RequireEncryptRequireDecrypt commitment policy,
73    // which enforces that this client only encrypts using committing algorithm suites and enforces
74    // that this client will only decrypt encrypted messages that were created with a committing
75    // algorithm suite.
76    let esdk_config = AwsEncryptionSdkConfig::builder().build()?;
77    let esdk_client = esdk_client::Client::from_conf(esdk_config)?;
78
79    // 2. Create encryption context.
80    // Remember that your encryption context is NOT SECRET.
81    // For more information, see
82    // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
83    let encryption_context = HashMap::from([
84        ("encryption".to_string(), "context".to_string()),
85        ("is not".to_string(), "secret".to_string()),
86        ("but adds".to_string(), "useful metadata".to_string()),
87        (
88            "that can help you".to_string(),
89            "be confident that".to_string(),
90        ),
91        (
92            "the data you are handling".to_string(),
93            "is what you think it is".to_string(),
94        ),
95    ]);
96
97    // 3. You may provide your own ECC keys in the files located at
98    // - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER
99    // - EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT
100
101    // If you do not provide these files, running this example through this
102    // class' main method will generate three files required for all raw ECDH examples
103    // EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER, EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT
104    // and EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT for you.
105
106    // Do not use these files for any other purpose.
107    if should_generate_new_ecc_key_pair_raw_ecdh()? {
108        write_raw_ecdh_ecc_keys(ecdh_curve_spec)?;
109    }
110
111    // 4. Load keys from UTF-8 encoded PEM files.
112    let mut file = File::open(Path::new(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER))?;
113    let mut private_key_sender_utf8_bytes = Vec::new();
114    file.read_to_end(&mut private_key_sender_utf8_bytes)?;
115
116    // Load public key from UTF-8 encoded PEM files into a DER encoded public key.
117    let public_key_file_content =
118        std::fs::read_to_string(Path::new(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT))?;
119    let parsed_public_key_file_content = parse(public_key_file_content)?;
120    let public_key_recipient_utf8_bytes = parsed_public_key_file_content.contents();
121
122    // 5. Create the RawPrivateKeyToStaticPublicKeyInput
123    let raw_ecdh_static_configuration_input = RawPrivateKeyToStaticPublicKeyInput::builder()
124        // Must be a UTF8 PEM-encoded private key
125        .sender_static_private_key(private_key_sender_utf8_bytes)
126        // Must be a UTF8 DER-encoded X.509 public key
127        .recipient_public_key(public_key_recipient_utf8_bytes)
128        .build()?;
129
130    let raw_ecdh_static_configuration = RawEcdhStaticConfigurations::RawPrivateKeyToStaticPublicKey(
131        raw_ecdh_static_configuration_input,
132    );
133
134    // 6. Create the Raw ECDH keyring.
135    let mpl_config = MaterialProvidersConfig::builder().build()?;
136    let mpl = mpl_client::Client::from_conf(mpl_config)?;
137
138    // Create the keyring.
139    // This keyring uses static sender and recipient keys. This configuration calls for both of
140    // the keys to be on the same curve (P256 / P384 / P521).
141    // On encrypt, the shared secret is derived from the sender's private key and the recipient's public key.
142    // For this example, on decrypt, the shared secret is derived from the sender's private key and the recipient's public key;
143    // However, on decrypt, the recipient can construct a keyring such that the shared secret is calculated with
144    // the recipient's private key and the sender's public key. In both scenarios the shared secret will be the same.
145    let raw_ecdh_keyring = mpl
146        .create_raw_ecdh_keyring()
147        .curve_spec(ecdh_curve_spec)
148        .key_agreement_scheme(raw_ecdh_static_configuration)
149        .send()
150        .await?;
151
152    // 7. Encrypt the data with the encryption_context
153    let plaintext = example_data.as_bytes();
154
155    let encryption_response = esdk_client
156        .encrypt()
157        .plaintext(plaintext)
158        .keyring(raw_ecdh_keyring.clone())
159        .encryption_context(encryption_context.clone())
160        .send()
161        .await?;
162
163    let ciphertext = encryption_response
164        .ciphertext
165        .expect("Unable to unwrap ciphertext from encryption response");
166
167    // 8. Demonstrate that the ciphertext and plaintext are different.
168    // (This is an example for demonstration; you do not need to do this in your own code.)
169    assert_ne!(
170        ciphertext,
171        aws_smithy_types::Blob::new(plaintext),
172        "Ciphertext and plaintext data are the same. Invalid encryption"
173    );
174
175    // 9. Decrypt your encrypted data using the same keyring you used on encrypt.
176    let decryption_response = esdk_client
177        .decrypt()
178        .ciphertext(ciphertext)
179        .keyring(raw_ecdh_keyring)
180        // Provide the encryption context that was supplied to the encrypt method
181        .encryption_context(encryption_context)
182        .send()
183        .await?;
184
185    let decrypted_plaintext = decryption_response
186        .plaintext
187        .expect("Unable to unwrap plaintext from decryption response");
188
189    // 10. Demonstrate that the decrypted plaintext is identical to the original plaintext.
190    // (This is an example for demonstration; you do not need to do this in your own code.)
191    assert_eq!(
192        decrypted_plaintext,
193        aws_smithy_types::Blob::new(plaintext),
194        "Decrypted plaintext should be identical to the original plaintext. Invalid decryption"
195    );
196
197    println!("Raw ECDH Keyring Example Completed Successfully");
198
199    Ok(())
200}
201
202fn should_generate_new_ecc_key_pair_raw_ecdh() -> Result<bool, String> {
203    // If keys already exist: do not overwrite existing keys
204    if exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER)
205        && exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT)
206    {
207        Ok(false)
208    }
209    // If only one file is present: throw exception
210    else if !exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER)
211        && exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT)
212    {
213        Err("Missing key file at ".to_string() + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER)
214    } else if exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER)
215        && !exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT)
216    {
217        Err("Missing key file at ".to_string() + EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT)
218    }
219    // If neither file is present, generate a new key pair
220    else {
221        Ok(true)
222    }
223}
224
225#[tokio::test(flavor = "multi_thread")]
226pub async fn test_encrypt_and_decrypt_with_keyring() -> Result<(), crate::BoxError2> {
227    // Test function for encrypt and decrypt using the Raw ECDH Keyring example
228    use crate::example_utils::utils;
229
230    encrypt_and_decrypt_with_keyring(utils::TEST_EXAMPLE_DATA, EcdhCurveSpec::EccNistP256).await?;
231
232    Ok(())
233}