main/keyring/raw_rsa_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 RSA Keyring
6
7The Raw RSA keyring performs asymmetric encryption and decryption of data keys in local memory
8with RSA public and private keys that you provide.
9
10This keyring accepts PEM encodings of the key pair as UTF-8 interpreted bytes.
11The encryption function encrypts the data key under the RSA public key. The decryption function
12decrypts the data key using the private key.
13
14This example loads a key pair from PEM files with paths defined in
15 - EXAMPLE_RSA_PRIVATE_KEY_FILENAME
16 - EXAMPLE_RSA_PUBLIC_KEY_FILENAME
17If you do not provide these files, running this example through this
18class' main method will generate these files for you. These files will
19be generated in the directory where the example is run.
20In practice, users of this library should not generate new key pairs
21like this, and should instead retrieve an existing key from a secure
22key management system (e.g. an HSM).
23You may also provide your own key pair by placing PEM files in the
24directory where the example is run or modifying the paths in the code
25below. These files must be valid PEM encodings of the key pair as UTF-8
26encoded bytes. If you do provide your own key pair, or if a key pair
27already exists, this class' main method will not generate a new key pair.
28
29This example creates a Raw RSA Keyring and then encrypts a custom input EXAMPLE_DATA
30with an encryption context. This example also includes some sanity checks for demonstration:
311. Ciphertext and plaintext data are not the same
322. Decrypted plaintext value matches EXAMPLE_DATA
333. The original ciphertext is not decryptable using a keyring with a different RSA key pair
34These sanity checks are for demonstration in the example only. You do not need these in your code.
35
36A Raw RSA keyring that encrypts and decrypts must include an asymmetric public key and private
37key pair. However, you can encrypt data with a Raw RSA keyring that has only a public key,
38and you can decrypt data with a Raw RSA keyring that has only a private key. This example requires
39the user to either provide both private and public keys, or not provide any keys and the example
40generates both to test encryption and decryption. If you configure a Raw RSA keyring with a
41public and private key, be sure that they are part of the same key pair. Some language
42implementations of the AWS Encryption SDK will not construct a Raw RSA keyring with keys
43from different pairs. Others rely on you to verify that your keys are from the same key pair.
44You can include any Raw RSA keyring in a multi-keyring.
45
46For more information on how to use Raw RSA keyrings, see
47https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-rsa-keyring.html
48*/
49
50use aws_esdk::client as esdk_client;
51use aws_esdk::material_providers::client as mpl_client;
52use aws_esdk::material_providers::types::material_providers_config::MaterialProvidersConfig;
53use aws_esdk::material_providers::types::PaddingScheme;
54use aws_esdk::types::aws_encryption_sdk_config::AwsEncryptionSdkConfig;
55use std::collections::HashMap;
56use std::fs::File;
57use std::io::Read;
58use std::io::Write;
59use std::path::Path;
60
61const EXAMPLE_RSA_PRIVATE_KEY_FILENAME: &str = "RawRsaKeyringExamplePrivateKey.pem";
62const EXAMPLE_RSA_PUBLIC_KEY_FILENAME: &str = "RawRsaKeyringExamplePublicKey.pem";
63
64pub async fn encrypt_and_decrypt_with_keyring(example_data: &str) -> Result<(), crate::BoxError> {
65 // 1. Instantiate the encryption SDK client.
66 // This builds the default client with the RequireEncryptRequireDecrypt commitment policy,
67 // which enforces that this client only encrypts using committing algorithm suites and enforces
68 // that this client will only decrypt encrypted messages that were created with a committing
69 // algorithm suite.
70 let esdk_config = AwsEncryptionSdkConfig::builder().build()?;
71 let esdk_client = esdk_client::Client::from_conf(esdk_config)?;
72
73 // 2. Create encryption context.
74 // Remember that your encryption context is NOT SECRET.
75 // For more information, see
76 // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
77 let encryption_context = HashMap::from([
78 ("encryption".to_string(), "context".to_string()),
79 ("is not".to_string(), "secret".to_string()),
80 ("but adds".to_string(), "useful metadata".to_string()),
81 (
82 "that can help you".to_string(),
83 "be confident that".to_string(),
84 ),
85 (
86 "the data you are handling".to_string(),
87 "is what you think it is".to_string(),
88 ),
89 ]);
90
91 // 3. You may provide your own RSA key pair in the files located at
92 // - EXAMPLE_RSA_PRIVATE_KEY_FILENAME
93 // - EXAMPLE_RSA_PUBLIC_KEY_FILENAME
94 // If these files are not present, this will generate a pair for you
95 if should_generate_new_rsa_key_pair()? {
96 generate_rsa_key_pair()?;
97 }
98
99 // 4. Load key pair from UTF-8 encoded PEM files.
100 // You may provide your own PEM files to use here.
101 // If you do not, the main method in this class will generate PEM
102 // files for example use. Do not use these files for any other purpose.
103 let mut file = File::open(Path::new(EXAMPLE_RSA_PUBLIC_KEY_FILENAME))?;
104 let mut public_key_utf8_bytes = Vec::new();
105 file.read_to_end(&mut public_key_utf8_bytes)?;
106
107 let mut file = File::open(Path::new(EXAMPLE_RSA_PRIVATE_KEY_FILENAME))?;
108 let mut private_key_utf8_bytes = Vec::new();
109 file.read_to_end(&mut private_key_utf8_bytes)?;
110
111 // 5. The key namespace and key name are defined by you.
112 // and are used by the Raw RSA keyring
113 // For more information, see
114 // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-rsa-keyring.html
115 let key_namespace: &str = "my-key-namespace";
116 let key_name: &str = "my-rsa-key-name";
117
118 // 6. Create the Raw RSA keyring.
119 let mpl_config = MaterialProvidersConfig::builder().build()?;
120 let mpl = mpl_client::Client::from_conf(mpl_config)?;
121
122 let raw_rsa_keyring = mpl
123 .create_raw_rsa_keyring()
124 .key_name(key_name)
125 .key_namespace(key_namespace)
126 .padding_scheme(PaddingScheme::OaepSha256Mgf1)
127 .public_key(public_key_utf8_bytes)
128 .private_key(private_key_utf8_bytes)
129 .send()
130 .await?;
131
132 // 7. Encrypt the data with the encryption_context
133 let plaintext = example_data.as_bytes();
134
135 let encryption_response = esdk_client
136 .encrypt()
137 .plaintext(plaintext)
138 .keyring(raw_rsa_keyring.clone())
139 .encryption_context(encryption_context.clone())
140 .send()
141 .await?;
142
143 let ciphertext = encryption_response
144 .ciphertext
145 .expect("Unable to unwrap ciphertext from encryption response");
146
147 // 8. Demonstrate that the ciphertext and plaintext are different.
148 // (This is an example for demonstration; you do not need to do this in your own code.)
149 assert_ne!(
150 ciphertext,
151 aws_smithy_types::Blob::new(plaintext),
152 "Ciphertext and plaintext data are the same. Invalid encryption"
153 );
154
155 // 9. Decrypt your encrypted data using the same keyring you used on encrypt.
156 let decryption_response = esdk_client
157 .decrypt()
158 .ciphertext(ciphertext)
159 .keyring(raw_rsa_keyring)
160 // Provide the encryption context that was supplied to the encrypt method
161 .encryption_context(encryption_context)
162 .send()
163 .await?;
164
165 let decrypted_plaintext = decryption_response
166 .plaintext
167 .expect("Unable to unwrap plaintext from decryption response");
168
169 // 10. Demonstrate that the decrypted plaintext is identical to the original plaintext.
170 // (This is an example for demonstration; you do not need to do this in your own code.)
171 assert_eq!(
172 decrypted_plaintext,
173 aws_smithy_types::Blob::new(plaintext),
174 "Decrypted plaintext should be identical to the original plaintext. Invalid decryption"
175 );
176
177 println!("Raw RSA Keyring Example Completed Successfully");
178
179 Ok(())
180}
181
182fn exists(f: &str) -> bool {
183 Path::new(f).exists()
184}
185
186fn should_generate_new_rsa_key_pair() -> Result<bool, String> {
187 // If a key pair already exists: do not overwrite existing key pair
188 if exists(EXAMPLE_RSA_PRIVATE_KEY_FILENAME) && exists(EXAMPLE_RSA_PUBLIC_KEY_FILENAME) {
189 Ok(false)
190 }
191 // If only one file is present: throw exception
192 else if exists(EXAMPLE_RSA_PRIVATE_KEY_FILENAME) && !exists(EXAMPLE_RSA_PUBLIC_KEY_FILENAME) {
193 Err("Missing public key file at ".to_string() + EXAMPLE_RSA_PUBLIC_KEY_FILENAME)
194 } else if exists(EXAMPLE_RSA_PRIVATE_KEY_FILENAME) && !exists(EXAMPLE_RSA_PUBLIC_KEY_FILENAME) {
195 Err("Missing private key file at ".to_string() + EXAMPLE_RSA_PRIVATE_KEY_FILENAME)
196 }
197 // If neither file is present, generate a new key pair
198 else {
199 Ok(true)
200 }
201}
202
203fn generate_rsa_key_pair() -> Result<(), crate::BoxError> {
204 use aws_lc_rs::encoding::AsDer;
205 use aws_lc_rs::encoding::Pkcs8V1Der;
206 use aws_lc_rs::encoding::PublicKeyX509Der;
207 use aws_lc_rs::rsa::KeySize;
208 use aws_lc_rs::rsa::PrivateDecryptingKey;
209
210 // Safety check: Validate neither file is present
211 if exists(EXAMPLE_RSA_PRIVATE_KEY_FILENAME) || exists(EXAMPLE_RSA_PUBLIC_KEY_FILENAME) {
212 return Err(crate::BoxError(
213 "generate_rsa_key_pair will not overwrite existing PEM files".to_string(),
214 ));
215 }
216
217 // This code will generate a new RSA key pair for example use.
218 // The public and private key will be written to the files:
219 // - public: EXAMPLE_RSA_PUBLIC_KEY_FILENAME
220 // - private: EXAMPLE_RSA_PRIVATE_KEY_FILENAME
221 // This example uses aws-lc-rs's KeyPairGenerator to generate the key pair.
222 // In practice, you should not generate this in your code, and should instead
223 // retrieve this key from a secure key management system (e.g. HSM)
224 // This key is created here for example purposes only.
225
226 let private_key = PrivateDecryptingKey::generate(KeySize::Rsa2048)?;
227 let public_key = private_key.public_key();
228
229 let public_key = AsDer::<PublicKeyX509Der>::as_der(&public_key)?;
230 let public_key = pem::Pem::new("RSA PUBLIC KEY", public_key.as_ref());
231 let public_key = pem::encode(&public_key);
232
233 let private_key = AsDer::<Pkcs8V1Der>::as_der(&private_key)?;
234 let private_key = pem::Pem::new("RSA PRIVATE KEY", private_key.as_ref());
235 let private_key = pem::encode(&private_key);
236
237 std::fs::OpenOptions::new()
238 .write(true)
239 .create(true)
240 .truncate(true)
241 .open(Path::new(EXAMPLE_RSA_PRIVATE_KEY_FILENAME))?
242 .write_all(private_key.as_bytes())?;
243
244 std::fs::OpenOptions::new()
245 .write(true)
246 .create(true)
247 .truncate(true)
248 .open(Path::new(EXAMPLE_RSA_PUBLIC_KEY_FILENAME))?
249 .write_all(public_key.as_bytes())?;
250
251 Ok(())
252}
253
254#[tokio::test(flavor = "multi_thread")]
255pub async fn test_encrypt_and_decrypt_with_keyring() -> Result<(), crate::BoxError2> {
256 // Test function for encrypt and decrypt using the Raw RSA Keyring example
257 use crate::example_utils::utils;
258
259 encrypt_and_decrypt_with_keyring(utils::TEST_EXAMPLE_DATA).await?;
260
261 Ok(())
262}