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}