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}