diff --git a/aws-lc-rs/src/agreement.rs b/aws-lc-rs/src/agreement.rs index 65fb4ff7ac1..b8ab3074a1f 100644 --- a/aws-lc-rs/src/agreement.rs +++ b/aws-lc-rs/src/agreement.rs @@ -51,6 +51,7 @@ //! ``` use crate::ec::{ ec_group_from_nid, ec_point_from_bytes, evp_key_generate, evp_pkey_from_public_point, + evp_pkey_from_x509_pubkey, marshal_x509_public_key_to_buffer, PUBLIC_KEY_MAX_LEN, }; use crate::error::Unspecified; use crate::fips::indicator_check; @@ -58,14 +59,15 @@ use crate::ptr::LcPtr; use crate::rand::SecureRandom; use crate::{ec, test}; use aws_lc::{ - EVP_PKEY_CTX_new, EVP_PKEY_CTX_new_id, EVP_PKEY_derive, EVP_PKEY_derive_init, - EVP_PKEY_derive_set_peer, EVP_PKEY_get_raw_public_key, EVP_PKEY_keygen, EVP_PKEY_keygen_init, - EVP_PKEY_new_raw_public_key, NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1, EVP_PKEY, - EVP_PKEY_X25519, NID_X25519, + d2i_X509_PUBKEY, EVP_PKEY_CTX_new, EVP_PKEY_CTX_new_id, EVP_PKEY_derive, EVP_PKEY_derive_init, + EVP_PKEY_derive_set_peer, EVP_PKEY_get_raw_private_key, EVP_PKEY_get_raw_public_key, + EVP_PKEY_keygen, EVP_PKEY_keygen_init, EVP_PKEY_new_raw_public_key, NID_X9_62_prime256v1, + NID_secp384r1, NID_secp521r1, EVP_PKEY, EVP_PKEY_X25519, NID_X25519, X25519_PUBLIC_VALUE_LEN, }; use core::fmt; use std::fmt::{Debug, Formatter}; +use std::os::raw::{c_long, c_uchar}; use std::ptr::null_mut; #[allow(non_camel_case_types)] @@ -138,6 +140,30 @@ pub static ECDH_P521: Algorithm = Algorithm { id: AlgorithmID::ECDH_P521, }; +#[derive(Debug, PartialEq, Eq)] +enum EncodingID { + // A sequence of 8-bit bytes + OctetString, + // X509 DER encoding + X509, +} + +/// Encoding of bytes +#[derive(Debug, PartialEq, Eq)] +pub struct Encoding { + id: EncodingID, +} + +/// Octet String encoding +pub static OCTET_STRING: Encoding = Encoding { + id: EncodingID::OctetString, +}; + +/// X509 DER encoding +pub static X509: Encoding = Encoding { + id: EncodingID::X509, +}; + /// X25519 (ECDH using Curve25519) as described in [RFC 7748]. /// /// Everything is as described in RFC 7748. Key agreement will fail if the @@ -149,7 +175,6 @@ pub static ECDH_P521: Algorithm = Algorithm { pub static X25519: Algorithm = Algorithm { id: AlgorithmID::X25519, }; -#[cfg(test)] const X25519_PRIVATE_KEY_LEN: usize = aws_lc::X25519_PRIVATE_KEY_LEN as usize; #[cfg(test)] const ECDH_P256_PRIVATE_KEY_LEN: usize = 32; @@ -446,6 +471,7 @@ impl Clone for PublicKey { pub struct UnparsedPublicKey> { alg: &'static Algorithm, bytes: B, + encoding: &'static Encoding, } impl> Copy for UnparsedPublicKey {} @@ -461,23 +487,42 @@ impl> Debug for UnparsedPublicKey { } impl> UnparsedPublicKey { - /// Constructs a new `UnparsedPublicKey`. + /// Constructs a new `UnparsedPublicKey` with [`OCTET_STRING`] encoding. pub fn new(algorithm: &'static Algorithm, bytes: B) -> Self { UnparsedPublicKey { alg: algorithm, bytes, + encoding: &OCTET_STRING, } } - /// The agreement algorithm associated with this public key + /// Constructs a new `UnparsedPublicKey` with the specified [Encoding]. + pub fn new_with_encoding( + algorithm: &'static Algorithm, + bytes: B, + encoding: &'static Encoding, + ) -> Self { + UnparsedPublicKey { + alg: algorithm, + bytes, + encoding, + } + } + + /// The agreement algorithm associated with this public key. pub fn algorithm(&self) -> &'static Algorithm { self.alg } - /// The bytes provided for this public key + /// The bytes provided for this public key. pub fn bytes(&self) -> &B { &self.bytes } + + /// The encoding of the bytes provided for this public key. + pub fn encoding(&self) -> &'static Encoding { + self.encoding + } } /// Performs a key agreement with an ephemeral private key and the given public @@ -513,7 +558,7 @@ impl> UnparsedPublicKey { #[inline] #[allow(clippy::needless_pass_by_value)] #[allow(clippy::missing_panics_doc)] -pub fn agree_ephemeral, F, R, E>( +pub fn agree_ephemeral, F, R, E: Copy>( my_private_key: EphemeralPrivateKey, peer_public_key: &UnparsedPublicKey, error_value: E, @@ -530,25 +575,90 @@ where return Err(error_value); } let peer_pub_bytes = peer_public_key.bytes.as_ref(); - if peer_pub_bytes.len() != expected_pub_key_len { - return Err(error_value); + if peer_public_key.encoding == &OCTET_STRING { + let peer_pub_bytes_len = peer_pub_bytes.len(); + if peer_pub_bytes_len != expected_pub_key_len { + return Err(error_value); + } } let mut buffer = [0u8; MAX_AGREEMENT_SECRET_LEN]; let secret: &[u8] = match &my_private_key.inner_key { - KeyInner::X25519(priv_key) => { - x25519_diffie_hellman(&mut buffer, priv_key, peer_pub_bytes).or(Err(error_value))? - } - KeyInner::ECDH_P256(priv_key) - | KeyInner::ECDH_P384(priv_key) - | KeyInner::ECDH_P521(priv_key) => { - ec_key_ecdh(&mut buffer, priv_key, peer_pub_bytes, expected_nid).or(Err(error_value))? + KeyInner::X25519(priv_key, ..) => match peer_public_key.encoding.id { + EncodingID::OctetString => { + x25519_dh_with_octstr_peer_pubkey( + peer_pub_bytes, + &mut buffer, + priv_key, + error_value, + )?; + &buffer[0..X25519_SHARED_KEY_LEN] + } + EncodingID::X509 => { + x25519_dh_with_x509_peer_pubkey( + peer_pub_bytes, + &mut buffer, + priv_key, + error_value, + )?; + &buffer[0..X25519_SHARED_KEY_LEN] + } + }, + KeyInner::ECDH_P256(ec_key) | KeyInner::ECDH_P384(ec_key) | KeyInner::ECDH_P521(ec_key) => { + let pub_key_bytes = peer_public_key.bytes.as_ref(); + ec_key_ecdh( + &mut buffer, + ec_key, + pub_key_bytes, + peer_public_key.encoding, + expected_nid, + ) + .or(Err(error_value))? } }; kdf(secret) } +#[inline] +fn x25519_dh_with_octstr_peer_pubkey( + peer_pub_bytes: &[u8], + shared_secret: &mut [u8; MAX_AGREEMENT_SECRET_LEN], + priv_key: &LcPtr, + error_value: E, +) -> Result<(), E> { + let mut pub_key = [0u8; X25519_PUBLIC_VALUE_LEN as usize]; + pub_key.copy_from_slice(peer_pub_bytes); + x25519_diffie_hellman(shared_secret, priv_key, &pub_key).map_err(|()| error_value)?; + Ok(()) +} + +#[inline] +fn x25519_dh_with_x509_peer_pubkey( + peer_x509_pubkey: &[u8], + shared_secret: &mut [u8; MAX_AGREEMENT_SECRET_LEN], + priv_key: &LcPtr, + error_value: E, +) -> Result<(), E> { + let len = match c_long::try_from(peer_x509_pubkey.len()) { + Ok(len) => len, + Err(_) => return Err(error_value), + }; + let x509_pubkey = LcPtr::new(unsafe { + d2i_X509_PUBKEY( + null_mut(), + &peer_x509_pubkey.as_ptr() as *const *const u8 as *mut *const c_uchar, + len, + ) + }) + .map_err(|()| error_value)?; + let mut peer_octstr_pubkey = [0u8; PUBLIC_KEY_MAX_LEN]; + marshal_x509_public_key_to_buffer(&mut peer_octstr_pubkey, &x509_pubkey) + .map_err(|_| error_value)?; + x25519_diffie_hellman_x509(shared_secret, priv_key, &peer_octstr_pubkey) + .map_err(|()| error_value) +} + // Current max secret length is P-521's. const MAX_AGREEMENT_SECRET_LEN: usize = ECDH_P521_PRIVATE_KEY_LEN; @@ -558,11 +668,18 @@ fn ec_key_ecdh<'a>( buffer: &'a mut [u8; MAX_AGREEMENT_SECRET_LEN], priv_key: &LcPtr, peer_pub_key_bytes: &[u8], + peer_pub_key_bytes_encoding: &Encoding, nid: i32, ) -> Result<&'a [u8], ()> { - let ec_group = unsafe { ec_group_from_nid(nid)? }; - let pub_key_point = unsafe { ec_point_from_bytes(&ec_group, peer_pub_key_bytes) }?; - let pub_key = unsafe { evp_pkey_from_public_point(&ec_group, &pub_key_point) }?; + // let peer_ec_key = match peer_pub_key_bytes_encoding.id { + let pub_key = match peer_pub_key_bytes_encoding.id { + EncodingID::OctetString => { + let ec_group = unsafe { ec_group_from_nid(nid)? }; + let pub_key_point = unsafe { ec_point_from_bytes(&ec_group, peer_pub_key_bytes)? }; + unsafe { evp_pkey_from_public_point(&ec_group, &pub_key_point)? } + } + EncodingID::X509 => evp_pkey_from_x509_pubkey(peer_pub_key_bytes)?, + }; let pkey_ctx = LcPtr::new(unsafe { EVP_PKEY_CTX_new(**priv_key, null_mut()) })?; @@ -627,10 +744,42 @@ fn x25519_diffie_hellman<'a>( Ok(&buffer[0..X25519_SHARED_KEY_LEN]) } +#[inline] +fn x25519_diffie_hellman_x509( + buffer: &mut [u8], + evp_priv_key: &LcPtr, + peer_pub_key: &[u8], +) -> Result<(), ()> { + let mut priv_key = [0u8; X25519_PRIVATE_KEY_LEN]; + if 1 != unsafe { + EVP_PKEY_get_raw_private_key( + **evp_priv_key, + priv_key.as_mut_ptr(), + &priv_key.len() as *const _ as *mut usize, + ) + } { + return Err(()); + } + debug_assert!(buffer.len() >= X25519_SHARED_KEY_LEN); + if 1 != unsafe { + aws_lc::X25519( + buffer.as_mut_ptr(), + priv_key.as_ptr(), + peer_pub_key.as_ptr(), + ) + } { + return Err(()); + } + Ok(()) +} + #[cfg(test)] mod tests { + use crate::agreement::Algorithm; use crate::error::Unspecified; + use crate::rand::SystemRandom; use crate::{agreement, rand, test, test_file}; + use aws_lc::EVP_DecodeBase64; #[cfg(feature = "fips")] mod fips; @@ -1036,4 +1185,102 @@ mod tests { Ok(Vec::from(agreed_value)) }) } + + fn agreement_agree_ephemeral_x509_peer_pubkey_( + x509_peer_public_key: &str, + algorithm: &'static Algorithm, + rng: &SystemRandom, + ) { + const SUCCESS: i32 = 1; + let in_ = x509_peer_public_key.as_bytes(); + let max_out: usize = in_.len() * 3 / 4; + let mut out = vec![0u8; max_out]; + let mut out_len: usize = 0; + // EVP_DecodeBase64 decodes in_len bytes from base64 and writes *out_len bytes to out. + // max_out is the size of the output buffer. + // If it is not enough for the maximum output size, the operation fails. + // It returns one on success or zero on error. + let ret = unsafe { + EVP_DecodeBase64( + out.as_mut_ptr(), + &mut out_len, + max_out, + in_.as_ptr(), + in_.len(), + ) + }; + assert_eq!(ret, SUCCESS); + assert!(out_len <= max_out); + out.truncate(out_len); + let my_private_key = agreement::EphemeralPrivateKey::generate(algorithm, rng) + .expect("Failed to generate key"); + let peer_public_key = { + agreement::UnparsedPublicKey::new_with_encoding( + algorithm, + out.as_slice(), + &agreement::X509, + ) + }; + agreement::agree_ephemeral( + my_private_key, + &peer_public_key, + Unspecified, + |key_material| { + let expected_key_len = match algorithm.id { + agreement::AlgorithmID::ECDH_P256 | agreement::AlgorithmID::X25519 => 32, + agreement::AlgorithmID::ECDH_P384 => 48, + agreement::AlgorithmID::ECDH_P521 => 66, + }; + assert_eq!(key_material.len(), expected_key_len); + Ok(()) + }, + ) + .expect("Failed in agree_ephemeral"); + } + + #[test] + fn agreement_agree_ephemeral_x509_peer_pubkey() { + const ECDH_P256_X509_PEER_PUBLIC_KEY: &str = concat!( + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECKby9ft1JuAokk2umfeafRyOL1pN", + "Z3T+LVvb5Cx6f/ngOdL2vJM2hQy9S962OodCQq0ZnhXE4KjnvycuFvCvOg==", + ); + const ECDH_P384_X509_PEER_PUBLIC_KEY: &str = concat!( + "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEInBNE3e20bBl8ZM9zbxPWP0oF7/vRTou", + "BNayEEitfB2HUQ45TltlAvq2LbaF08687o5jQOAJfDA6T5mKn6/19MwX5zI7Wt7/", + "xCYH0kg7Bz26I1hi6XfhQ49Owhh0BMKH", + ); + const ECDH_P521_X509_PEER_PUBLIC_KEY: &str = concat!( + "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA/KHBKOQAB4kAyQ/ED7GdO+ICswP7s", + "stFf7nljB5Unbu5fTpVTwqR1GGWWBnnew58gSU1h5ECwSOpMY7vfGbIGHUAIZ0V9Y", + "j3Foo+FvAoE4dog1Gy+LpK+cXbacpZhQQrzBnWMeicZjs3R7esCymjjtULEZjVple", + "HDYiVLAHdHIzfYdo=", + ); + const X25519_X509_PEER_PUBLIC_KEY: &str = + concat!("MCowBQYDK2VuAyEAXei13Z9eh6EzPZ+OvAFha+rIMANFwT6IN5w6tMm9A0Y=",); + + let rng = rand::SystemRandom::new(); + agreement_agree_ephemeral_x509_peer_pubkey_( + ECDH_P256_X509_PEER_PUBLIC_KEY, + &agreement::ECDH_P256, + &rng, + ); + + agreement_agree_ephemeral_x509_peer_pubkey_( + ECDH_P384_X509_PEER_PUBLIC_KEY, + &agreement::ECDH_P384, + &rng, + ); + + agreement_agree_ephemeral_x509_peer_pubkey_( + ECDH_P521_X509_PEER_PUBLIC_KEY, + &agreement::ECDH_P521, + &rng, + ); + + agreement_agree_ephemeral_x509_peer_pubkey_( + X25519_X509_PEER_PUBLIC_KEY, + &agreement::X25519, + &rng, + ); + } } diff --git a/aws-lc-rs/src/ec.rs b/aws-lc-rs/src/ec.rs index 97f8ea9b2ca..09ad5fbba3b 100644 --- a/aws-lc-rs/src/ec.rs +++ b/aws-lc-rs/src/ec.rs @@ -17,15 +17,16 @@ use aws_lc::EC_KEY_check_fips; #[cfg(not(feature = "fips"))] use aws_lc::EC_KEY_check_key; use aws_lc::{ - point_conversion_form_t, ECDSA_SIG_from_bytes, ECDSA_SIG_get0_r, ECDSA_SIG_get0_s, - ECDSA_SIG_new, ECDSA_SIG_set0, ECDSA_SIG_to_bytes, EC_GROUP_get_curve_name, - EC_GROUP_new_by_curve_name, EC_KEY_get0_group, EC_KEY_get0_public_key, EC_KEY_new, - EC_KEY_set_group, EC_KEY_set_private_key, EC_KEY_set_public_key, EC_POINT_new, - EC_POINT_oct2point, EC_POINT_point2oct, EVP_DigestVerify, EVP_DigestVerifyInit, - EVP_PKEY_CTX_new_id, EVP_PKEY_CTX_set_ec_paramgen_curve_nid, EVP_PKEY_assign_EC_KEY, - EVP_PKEY_get0_EC_KEY, EVP_PKEY_keygen, EVP_PKEY_keygen_init, EVP_PKEY_new, - NID_X9_62_prime256v1, NID_secp256k1, NID_secp384r1, NID_secp521r1, BIGNUM, ECDSA_SIG, EC_GROUP, - EC_POINT, EVP_PKEY, EVP_PKEY_EC, + d2i_PUBKEY_bio, i2d_PUBKEY_bio, point_conversion_form_t, BIO_get_mem_data, BIO_new, BIO_s_mem, + BIO_write, ECDSA_SIG_from_bytes, ECDSA_SIG_get0_r, ECDSA_SIG_get0_s, ECDSA_SIG_new, + ECDSA_SIG_set0, ECDSA_SIG_to_bytes, EC_GROUP_get_curve_name, EC_GROUP_new_by_curve_name, + EC_KEY_get0_group, EC_KEY_get0_public_key, EC_KEY_new, EC_KEY_set_group, + EC_KEY_set_private_key, EC_KEY_set_public_key, EC_POINT_new, EC_POINT_oct2point, + EC_POINT_point2oct, EVP_DigestVerify, EVP_DigestVerifyInit, EVP_PKEY_CTX_new_id, + EVP_PKEY_CTX_set_ec_paramgen_curve_nid, EVP_PKEY_assign_EC_KEY, EVP_PKEY_get0_EC_KEY, + EVP_PKEY_keygen, EVP_PKEY_keygen_init, EVP_PKEY_new, NID_X9_62_prime256v1, NID_secp256k1, + NID_secp384r1, NID_secp521r1, X509_PUBKEY_get, BIGNUM, ECDSA_SIG, EC_GROUP, EC_POINT, EVP_PKEY, + EVP_PKEY_EC, X509_PUBKEY, }; #[cfg(test)] @@ -34,7 +35,7 @@ use aws_lc::EC_POINT_mul; use std::fmt::{Debug, Formatter}; use std::mem::MaybeUninit; use std::ops::Deref; -use std::os::raw::{c_int, c_uint}; +use std::os::raw::{c_char, c_int, c_uint, c_void}; #[cfg(test)] use std::ptr::null; use std::ptr::null_mut; @@ -265,6 +266,23 @@ unsafe fn validate_evp_key( Ok(()) } +#[allow(clippy::cast_ptr_alignment)] +pub(crate) fn marshal_x509_public_key_to_buffer( + buffer: &mut [u8; PUBLIC_KEY_MAX_LEN], + x509_pubkey: &LcPtr, +) -> Result { + let evp_pkey = LcPtr::new(unsafe { X509_PUBKEY_get(**x509_pubkey) })?; + let bio = LcPtr::new(unsafe { BIO_new(BIO_s_mem()) })?; + if unsafe { i2d_PUBKEY_bio(*bio, *evp_pkey) } <= 0 { + return Err(Unspecified); + } + let size = BIO_get_mem_data(*bio, buffer.as_mut_ptr().cast::<*mut c_char>()); + if size <= 0 { + return Err(Unspecified); + } + usize::try_from(size).map_err(|_| Unspecified) +} + pub(crate) unsafe fn marshal_public_key_to_buffer( buffer: &mut [u8; PUBLIC_KEY_MAX_LEN], evp_pkey: &ConstPointer, @@ -320,6 +338,23 @@ pub(crate) unsafe fn evp_pkey_from_public_point( Ok(pkey) } +#[inline] +pub(crate) fn evp_pkey_from_x509_pubkey( + pubkey_data: &[u8], +) -> Result, Unspecified> { + // Create a memory BIO and write the public key data to it + let mem_bio = LcPtr::new(unsafe { BIO_new(BIO_s_mem()) })?; + let len = match c_int::try_from(pubkey_data.len()) { + Ok(len) => len, + Err(_) => return Err(Unspecified), + }; + if unsafe { BIO_write(*mem_bio, pubkey_data.as_ptr().cast::(), len) } <= 0 { + return Err(Unspecified); + } + // Use d2i_PUBKEY_bio to read the public key from the memory BIO + Ok(LcPtr::new(unsafe { d2i_PUBKEY_bio(*mem_bio, null_mut()) })?) +} + #[cfg(test)] pub(crate) unsafe fn evp_pkey_from_private( ec_group: &ConstPointer, diff --git a/aws-lc-rs/src/ptr.rs b/aws-lc-rs/src/ptr.rs index 5103cc5f146..2bd8cbcac9a 100644 --- a/aws-lc-rs/src/ptr.rs +++ b/aws-lc-rs/src/ptr.rs @@ -4,9 +4,10 @@ use std::ops::Deref; use aws_lc::{ - BN_free, ECDSA_SIG_free, EC_GROUP_free, EC_KEY_free, EC_POINT_free, EVP_AEAD_CTX_free, - EVP_PKEY_CTX_free, EVP_PKEY_free, OPENSSL_free, RSA_free, BIGNUM, ECDSA_SIG, EC_GROUP, EC_KEY, - EC_POINT, EVP_AEAD_CTX, EVP_PKEY, EVP_PKEY_CTX, RSA, + BIO_free, BN_free, ECDSA_SIG_free, EC_GROUP_free, EC_KEY_free, EC_POINT_free, + EVP_AEAD_CTX_free, EVP_PKEY_CTX_free, EVP_PKEY_free, OPENSSL_free, RSA_free, X509_PUBKEY_free, + BIGNUM, BIO, ECDSA_SIG, EC_GROUP, EC_KEY, EC_POINT, EVP_AEAD_CTX, EVP_PKEY, EVP_PKEY_CTX, RSA, + X509_PUBKEY, }; use mirai_annotations::verify_unreachable; @@ -198,18 +199,20 @@ macro_rules! create_pointer { } // `OPENSSL_free` and the other `XXX_free` functions perform a zeroization of the memory when it's -// freed. This is different than functions of the same name in OpenSSL which generally do not zero +// freed. This is different than functions of the same name in OpenSSL which generally do not zerorise // memory. -create_pointer!(u8, OPENSSL_free); +create_pointer!(BIGNUM, BN_free); +create_pointer!(BIO, BIO_free); create_pointer!(EC_GROUP, EC_GROUP_free); -create_pointer!(EC_POINT, EC_POINT_free); create_pointer!(EC_KEY, EC_KEY_free); +create_pointer!(EC_POINT, EC_POINT_free); create_pointer!(ECDSA_SIG, ECDSA_SIG_free); -create_pointer!(BIGNUM, BN_free); +create_pointer!(EVP_AEAD_CTX, EVP_AEAD_CTX_free); create_pointer!(EVP_PKEY, EVP_PKEY_free); create_pointer!(EVP_PKEY_CTX, EVP_PKEY_CTX_free); create_pointer!(RSA, RSA_free); -create_pointer!(EVP_AEAD_CTX, EVP_AEAD_CTX_free); +create_pointer!(X509_PUBKEY, X509_PUBKEY_free); +create_pointer!(u8, OPENSSL_free); #[cfg(test)] mod tests {