Skip to content

Commit 2e76dd7

Browse files
authored
chore(examples): Raw ECDH and KMS ECDH (#692)
1 parent d5b7218 commit 2e76dd7

30 files changed

+1513
-115
lines changed

AwsEncryptionSDK/runtimes/rust/examples/client_supplier/client_supplier_example.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ pub async fn encrypt_and_decrypt_with_keyring(
7171
.send()
7272
.await?;
7373

74-
// 4. Encrypt the data with the encryptionContext using the encrypt_keyring.
75-
let plaintext = aws_smithy_types::Blob::new(example_data);
74+
// 4. Encrypt the data with the encryption_context using the encrypt_keyring.
75+
let plaintext = example_data.as_bytes();
7676

7777
let encryption_response = esdk_client.encrypt()
78-
.plaintext(plaintext.clone())
78+
.plaintext(plaintext)
7979
.keyring(mrk_keyring_with_client_supplier)
8080
.encryption_context(encryption_context.clone())
8181
.send()
@@ -87,7 +87,7 @@ pub async fn encrypt_and_decrypt_with_keyring(
8787

8888
// 5. Demonstrate that the ciphertext and plaintext are different.
8989
// (This is an example for demonstration; you do not need to do this in your own code.)
90-
assert_ne!(ciphertext, plaintext,
90+
assert_ne!(ciphertext, aws_smithy_types::Blob::new(plaintext),
9191
"Ciphertext and plaintext data are the same. Invalid encryption");
9292

9393
// 6. Create a MRK discovery multi-keyring with a custom client supplier.
@@ -137,7 +137,7 @@ pub async fn encrypt_and_decrypt_with_keyring(
137137

138138
// 8. Demonstrate that the decrypted plaintext is identical to the original plaintext.
139139
// (This is an example for demonstration; you do not need to do this in your own code.)
140-
assert_eq!(decrypted_plaintext, plaintext,
140+
assert_eq!(decrypted_plaintext, aws_smithy_types::Blob::new(plaintext),
141141
"Decrypted plaintext should be identical to the original plaintext. Invalid decryption");
142142

143143
// 9. Test the Missing Region Exception

AwsEncryptionSDK/runtimes/rust/examples/cryptographic_materials_manager/required_encryption_context/required_encryption_context_example.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ pub async fn encrypt_and_decrypt_with_cmm(
8484
// NOTE: the keys "requiredKey1", and "requiredKey2"
8585
// WILL NOT be stored in the message header, but "encryption", "is not",
8686
// "but adds", "that can help you", and "the data you are handling" WILL be stored.
87-
let plaintext = aws_smithy_types::Blob::new(example_data);
87+
let plaintext = example_data.as_bytes();
8888

8989
let encryption_response = esdk_client.encrypt()
90-
.plaintext(plaintext.clone())
90+
.plaintext(plaintext)
9191
.materials_manager(required_ec_cmm.clone())
9292
.encryption_context(encryption_context.clone())
9393
.send()
@@ -99,7 +99,7 @@ pub async fn encrypt_and_decrypt_with_cmm(
9999

100100
// 8. Demonstrate that the ciphertext and plaintext are different.
101101
// (This is an example for demonstration; you do not need to do this in your own code.)
102-
assert_ne!(ciphertext, plaintext,
102+
assert_ne!(ciphertext, aws_smithy_types::Blob::new(plaintext),
103103
"Ciphertext and plaintext data are the same. Invalid encryption");
104104

105105
// 9. Decrypt your encrypted data using the same keyring you used on encrypt.
@@ -117,7 +117,7 @@ pub async fn encrypt_and_decrypt_with_cmm(
117117

118118
// 10. Demonstrate that the decrypted plaintext is identical to the original plaintext.
119119
// (This is an example for demonstration; you do not need to do this in your own code.)
120-
assert_eq!(decrypted_plaintext, plaintext,
120+
assert_eq!(decrypted_plaintext, aws_smithy_types::Blob::new(plaintext),
121121
"Decrypted plaintext should be identical to the original plaintext. Invalid decryption");
122122

123123
// 11. Attempt to decrypt your encrypted data using the same cryptographic material manager
@@ -159,7 +159,7 @@ pub async fn encrypt_and_decrypt_with_cmm(
159159

160160
// Demonstrate that the decrypted plaintext is identical to the original plaintext.
161161
// (This is an example for demonstration; you do not need to do this in your own code.)
162-
assert_eq!(decrypted_plaintext_with_reproduced_ec, plaintext,
162+
assert_eq!(decrypted_plaintext_with_reproduced_ec, aws_smithy_types::Blob::new(plaintext),
163163
"Decrypted plaintext should be identical to the original plaintext. Invalid decryption");
164164

165165
// 13. You can decrypt the ciphertext using the underlying cmm, but not providing the
@@ -181,7 +181,7 @@ pub async fn encrypt_and_decrypt_with_cmm(
181181

182182
// Demonstrate that the decrypted plaintext is identical to the original plaintext.
183183
// (This is an example for demonstration; you do not need to do this in your own code.)
184-
assert_eq!(decrypted_plaintext_with_ec_underlying_cmm, plaintext,
184+
assert_eq!(decrypted_plaintext_with_ec_underlying_cmm, aws_smithy_types::Blob::new(plaintext),
185185
"Decrypted plaintext should be identical to the original plaintext. Invalid decryption");
186186

187187
// This will fail

AwsEncryptionSDK/runtimes/rust/examples/cryptographic_materials_manager/restrict_algorithm_suite/signing_only_example.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ pub async fn encrypt_and_decrypt_with_cmm(
6262
};
6363

6464
// 5. Encrypt the data with the encryption_context
65-
let plaintext = aws_smithy_types::Blob::new(example_data);
65+
let plaintext = example_data.as_bytes();
6666

6767
let encryption_response = esdk_client.encrypt()
68-
.plaintext(plaintext.clone())
68+
.plaintext(plaintext)
6969
.materials_manager(signing_suite_only_cmm_ref.clone())
7070
.encryption_context(encryption_context.clone())
7171
.algorithm_suite_id(EsdkAlgorithmSuiteId::AlgAes256GcmHkdfSha512CommitKeyEcdsaP384)
@@ -78,7 +78,7 @@ pub async fn encrypt_and_decrypt_with_cmm(
7878

7979
// 6. Demonstrate that the ciphertext and plaintext are different.
8080
// (This is an example for demonstration; you do not need to do this in your own code.)
81-
assert_ne!(ciphertext, plaintext,
81+
assert_ne!(ciphertext, aws_smithy_types::Blob::new(plaintext),
8282
"Ciphertext and plaintext data are the same. Invalid encryption");
8383

8484
// 7. Decrypt your encrypted data using the same keyring you used on encrypt.
@@ -96,13 +96,13 @@ pub async fn encrypt_and_decrypt_with_cmm(
9696

9797
// 8. Demonstrate that the decrypted plaintext is identical to the original plaintext.
9898
// (This is an example for demonstration; you do not need to do this in your own code.)
99-
assert_eq!(decrypted_plaintext, plaintext,
99+
assert_eq!(decrypted_plaintext, aws_smithy_types::Blob::new(plaintext),
100100
"Decrypted plaintext should be identical to the original plaintext. Invalid decryption");
101101

102102
// 9. Demonstrate that a Non Signing Algorithm Suite will be rejected
103103
// by the CMM.
104104
let encryption_response_non_signing = esdk_client.encrypt()
105-
.plaintext(plaintext.clone())
105+
.plaintext(plaintext)
106106
.materials_manager(signing_suite_only_cmm_ref)
107107
.encryption_context(encryption_context.clone())
108108
.algorithm_suite_id(EsdkAlgorithmSuiteId::AlgAes256GcmHkdfSha512CommitKey)

AwsEncryptionSDK/runtimes/rust/examples/example_utils/utils.rs

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,238 @@ pub const TEST_LOGICAL_KEY_STORE_NAME: &str = "KeyStoreDdbTable";
3636

3737
pub const TEST_KEY_STORE_KMS_KEY_ID: &str =
3838
"arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126";
39+
40+
// ECDH Utils
41+
use aws_esdk::aws_cryptography_primitives::types::EcdhCurveSpec;
42+
use std::path::Path;
43+
use std::io::Write;
44+
45+
pub const TEST_KMS_ECDH_KEY_ID_P256_SENDER: &str =
46+
"arn:aws:kms:us-west-2:370957321024:key/eabdf483-6be2-4d2d-8ee4-8c2583d416e9";
47+
pub const TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT: &str =
48+
"arn:aws:kms:us-west-2:370957321024:key/0265c8e9-5b6a-4055-8f70-63719e09fda5";
49+
50+
pub const EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER: &str = "RawEcdhKeyringExamplePrivateKeySender.pem";
51+
pub const EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT: &str = "RawEcdhKeyringExamplePrivateKeyRecipient.pem";
52+
pub const EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT: &str = "RawEcdhKeyringExamplePublicKeyRecipient.pem";
53+
pub const EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_SENDER: &str = "KmsEccKeyringExamplePublicKeySender.pem";
54+
pub const EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_RECIPIENT: &str = "KmsEccKeyringExamplePublicKeyRecipient.pem";
55+
56+
// Following are the helper functions for running ECDH examples
57+
58+
pub(crate) fn x962_to_x509(
59+
public_key: &[u8],
60+
nid: i32
61+
) -> Result<Vec<u8>, String> {
62+
use aws_lc_sys::EC_POINT_new;
63+
use aws_lc_sys::EC_GROUP_new_by_curve_name;
64+
use aws_lc_sys::EC_POINT_oct2point;
65+
use aws_lc_sys::EC_KEY_new_by_curve_name;
66+
use aws_lc_sys::EC_KEY_set_public_key;
67+
use aws_lc_sys::EVP_PKEY_new;
68+
use aws_lc_sys::EVP_PKEY_assign_EC_KEY;
69+
use aws_lc_sys::EVP_PKEY_size;
70+
use aws_lc_sys::EVP_marshal_public_key;
71+
use aws_lc_sys::CBB_finish;
72+
use aws_lc_sys::CBB_init;
73+
use aws_lc_sys::CBB;
74+
use aws_lc_sys::OPENSSL_free;
75+
use aws_lc_sys::EVP_PKEY_free;
76+
use aws_lc_sys::EC_POINT_free;
77+
use std::ptr::null_mut;
78+
79+
let ec_group = unsafe { EC_GROUP_new_by_curve_name(nid) };
80+
let ec_point = unsafe { EC_POINT_new(ec_group) };
81+
82+
if 1 != unsafe {
83+
EC_POINT_oct2point(
84+
ec_group,
85+
ec_point,
86+
public_key.as_ptr(),
87+
public_key.len(),
88+
null_mut(),
89+
)
90+
} {
91+
return Err("Error in EC_POINT_oct2point.".to_string());
92+
}
93+
94+
let ec_key = unsafe { EC_KEY_new_by_curve_name(nid) };
95+
if 1 != unsafe { EC_KEY_set_public_key(ec_key, ec_point) } {
96+
return Err("Error in EC_KEY_set_public_key.".to_string());
97+
}
98+
99+
let evp_pkey = unsafe { EVP_PKEY_new() };
100+
if 1 != unsafe { EVP_PKEY_assign_EC_KEY(evp_pkey, ec_key) } {
101+
return Err("Error in EVP_PKEY_assign_EC_KEY.".to_string());
102+
}
103+
104+
let key_size_bytes: usize = unsafe { EVP_PKEY_size(evp_pkey) }.try_into().unwrap();
105+
let mut cbb: CBB = Default::default();
106+
unsafe { CBB_init(&mut cbb as *mut CBB, key_size_bytes * 5) };
107+
108+
if 1 != unsafe { EVP_marshal_public_key(&mut cbb, evp_pkey) } {
109+
return Err("Error in EVP_marshal_public_key in GetPublicKey.".to_string());
110+
};
111+
112+
let mut out_data = null_mut::<u8>();
113+
let mut out_len: usize = 0;
114+
115+
if 1 != unsafe { CBB_finish(&mut cbb, &mut out_data, &mut out_len) } {
116+
return Err("Error in CBB_finish in GetPublicKey.".to_string());
117+
};
118+
let slice = unsafe { std::slice::from_raw_parts(out_data, out_len) };
119+
let slice = slice.to_vec();
120+
121+
unsafe { OPENSSL_free(out_data as *mut ::std::os::raw::c_void) };
122+
unsafe { EVP_PKEY_free(evp_pkey) };
123+
unsafe { EC_POINT_free(ec_point) };
124+
Ok(slice)
125+
}
126+
127+
fn get_nid(x: EcdhCurveSpec) -> i32 {
128+
match x {
129+
EcdhCurveSpec::EccNistP256 {} => aws_lc_sys::NID_X9_62_prime256v1,
130+
EcdhCurveSpec::EccNistP384 {} => aws_lc_sys::NID_secp384r1,
131+
EcdhCurveSpec::EccNistP521 {} => aws_lc_sys::NID_secp521r1,
132+
EcdhCurveSpec::Sm2 {} => panic!("No SM2 in Rust"),
133+
}
134+
}
135+
136+
fn get_alg(x: EcdhCurveSpec) -> &'static aws_lc_rs::agreement::Algorithm {
137+
match x {
138+
EcdhCurveSpec::EccNistP256 {} => &aws_lc_rs::agreement::ECDH_P256,
139+
EcdhCurveSpec::EccNistP384 {} => &aws_lc_rs::agreement::ECDH_P384,
140+
EcdhCurveSpec::EccNistP521 {} => &aws_lc_rs::agreement::ECDH_P521,
141+
EcdhCurveSpec::Sm2 {} => panic!("No SM2 in Rust"),
142+
}
143+
}
144+
145+
pub(crate) fn exists(f: &str) -> bool {
146+
Path::new(f).exists()
147+
}
148+
149+
pub(crate) fn write_raw_ecdh_ecc_keys(
150+
ecdh_curve_spec: EcdhCurveSpec
151+
) -> Result<(), crate::BoxError> {
152+
// Safety check: Validate neither file is present
153+
if exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER)
154+
|| exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)
155+
|| exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT)
156+
{
157+
return Err(crate::BoxError(
158+
"write_raw_ecdh_ecc_keys will not overwrite existing PEM files".to_string(),
159+
));
160+
}
161+
162+
let (_public_key_sender, private_key_sender) = generate_raw_ecc_key_pair(ecdh_curve_spec)?;
163+
let (public_key_recipient, private_key_recipient) = generate_raw_ecc_key_pair(ecdh_curve_spec)?;
164+
165+
std::fs::OpenOptions::new()
166+
.write(true)
167+
.create(true)
168+
.truncate(true)
169+
.open(Path::new(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER))?
170+
.write_all(private_key_sender.as_bytes())?;
171+
172+
std::fs::OpenOptions::new()
173+
.write(true)
174+
.create(true)
175+
.truncate(true)
176+
.open(Path::new(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT))?
177+
.write_all(private_key_recipient.as_bytes())?;
178+
179+
std::fs::OpenOptions::new()
180+
.write(true)
181+
.create(true)
182+
.truncate(true)
183+
.open(Path::new(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT))?
184+
.write_all(public_key_recipient.as_bytes())?;
185+
186+
Ok(())
187+
}
188+
189+
fn generate_raw_ecc_key_pair(
190+
ecdh_curve_spec: EcdhCurveSpec
191+
) -> Result<(String, String), crate::BoxError> {
192+
use aws_lc_rs::encoding::AsDer;
193+
use aws_lc_rs::encoding::EcPrivateKeyRfc5915Der;
194+
195+
// This code will generate new ECC keys for example use.
196+
// The public and private keys will be written to the files:
197+
// - public: EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT
198+
// - private: EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER, EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT
199+
// This example uses aws-lc-rs's KeyPairGenerator to generate the key pair.
200+
// In practice, you should not generate this in your code, and should instead
201+
// retrieve this key from a secure key management system (e.g. HSM)
202+
// These examples only demonstrate using the P256 curve while the keyring accepts
203+
// P256, P384, or P521.
204+
// This key is created here for example purposes only.
205+
let private_key =
206+
aws_lc_rs::agreement::PrivateKey::generate(get_alg(ecdh_curve_spec))
207+
.map_err(|e| format!("{:?}", e))?;
208+
209+
let public_key = private_key
210+
.compute_public_key()
211+
.map_err(|e| format!("{:?}", e))?;
212+
213+
let public_key: Vec<u8> = x962_to_x509(public_key.as_ref(), get_nid(ecdh_curve_spec))?;
214+
let public_key = pem::Pem::new("PUBLIC KEY", public_key);
215+
let public_key = pem::encode(&public_key);
216+
217+
let private_key_der = AsDer::<EcPrivateKeyRfc5915Der>::as_der(&private_key)
218+
.map_err(|e| format!("{:?}", e))?;
219+
let private_key = pem::Pem::new("PRIVATE KEY", private_key_der.as_ref());
220+
let private_key = pem::encode(&private_key);
221+
222+
Ok((public_key, private_key))
223+
}
224+
225+
pub(crate) async fn write_kms_ecdh_ecc_public_key (
226+
ecc_key_arn: &str,
227+
public_key_file_path: &str
228+
) -> Result<(), crate::BoxError> {
229+
if exists(public_key_file_path)
230+
{
231+
return Err(crate::BoxError(
232+
"write_kms_ecdh_ecc_public_key will not overwrite existing PEM files".to_string(),
233+
));
234+
}
235+
236+
let public_key = generate_kms_ecc_public_key(ecc_key_arn).await?;
237+
238+
let public_key = pem::Pem::new("PUBLIC KEY", public_key);
239+
let public_key = pem::encode(&public_key);
240+
241+
std::fs::OpenOptions::new()
242+
.write(true)
243+
.create(true)
244+
.truncate(true)
245+
.open(Path::new(public_key_file_path))?
246+
.write_all(public_key.as_bytes())?;
247+
248+
Ok(())
249+
}
250+
251+
pub(crate) async fn generate_kms_ecc_public_key(
252+
ecc_key_arn: &str,
253+
) -> Result<aws_smithy_types::Blob, crate::BoxError> {
254+
// Create KMS client to get public key
255+
let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
256+
let kms_client = aws_sdk_kms::Client::new(&sdk_config);
257+
258+
// This code will call KMS to get the public key for the KMS ECC key.
259+
// You must have kms:GetPublicKey permissions on the key for this to succeed.
260+
// The public key generated here will be written to the file EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_SENDER
261+
// or EXAMPLE_KMS_ECC_PUBLIC_KEY_FILENAME_RECIPIENT.
262+
let kms_response = kms_client
263+
.get_public_key()
264+
.key_id(ecc_key_arn)
265+
.send()
266+
.await?;
267+
268+
let public_key = kms_response
269+
.public_key
270+
.expect("Error unwrapping public key from KMS response.");
271+
272+
Ok(public_key)
273+
}

AwsEncryptionSDK/runtimes/rust/examples/keyring/aws_kms_discovery_keyring_example.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@ pub async fn encrypt_and_decrypt_with_keyring(
8787
.await?;
8888

8989
// 5. Encrypt the data with the encryption_context
90-
let plaintext = aws_smithy_types::Blob::new(example_data);
90+
let plaintext = example_data.as_bytes();
9191

9292
let encryption_response = esdk_client.encrypt()
93-
.plaintext(plaintext.clone())
93+
.plaintext(plaintext)
9494
.keyring(encrypt_kms_keyring)
9595
.encryption_context(encryption_context.clone())
9696
.send()
@@ -102,7 +102,7 @@ pub async fn encrypt_and_decrypt_with_keyring(
102102

103103
// 6. Demonstrate that the ciphertext and plaintext are different.
104104
// (This is an example for demonstration; you do not need to do this in your own code.)
105-
assert_ne!(ciphertext, plaintext,
105+
assert_ne!(ciphertext, aws_smithy_types::Blob::new(plaintext),
106106
"Ciphertext and plaintext data are the same. Invalid encryption");
107107

108108
// 7. Now create a Discovery keyring to use for decryption. We'll add a discovery filter
@@ -147,7 +147,7 @@ pub async fn encrypt_and_decrypt_with_keyring(
147147

148148
// 9. Demonstrate that the decrypted plaintext is identical to the original plaintext.
149149
// (This is an example for demonstration; you do not need to do this in your own code.)
150-
assert_eq!(decrypted_plaintext, plaintext,
150+
assert_eq!(decrypted_plaintext, aws_smithy_types::Blob::new(plaintext),
151151
"Decrypted plaintext should be identical to the original plaintext. Invalid decryption");
152152

153153
// 10. Demonstrate that if a different discovery keyring (Bob's) doesn't have the correct

0 commit comments

Comments
 (0)