From a95a4561dc4c4c7927ec9af1bd2b549b31d6034b Mon Sep 17 00:00:00 2001 From: Hanson Char Date: Fri, 20 Oct 2023 13:24:26 -0700 Subject: [PATCH] Add support of Octet String and X509 bytes encoding (#255) Address Mark's review comments at https://github.com/aws/aws-lc-rs/pull/256 Address Mark's 2nd round of review comments at https://github.com/aws/aws-lc-rs/pull/256 Computes the public key in X509 format from the private key used for key agreement. Fix doc per Mark's feedback Move Encoding and EncodingID from agreement.rs to public_key.rs so they can be reused them in both signature::UnparsedPublicKey and agreement::UnparsedPublicKey Move evp_pkey_from_x509_pubkey from ec.rs to public_key.rs as it can be reused for not only EC public keys but also RSA public keys. --- aws-lc-rs/Cargo.toml | 1 + aws-lc-rs/src/agreement.rs | 254 ++++++++++++++++++++++++++++++++---- aws-lc-rs/src/ec.rs | 79 ++++++++--- aws-lc-rs/src/lib.rs | 1 + aws-lc-rs/src/ptr.rs | 19 +-- aws-lc-rs/src/public_key.rs | 86 ++++++++++++ 6 files changed, 386 insertions(+), 54 deletions(-) create mode 100644 aws-lc-rs/src/public_key.rs diff --git a/aws-lc-rs/Cargo.toml b/aws-lc-rs/Cargo.toml index 1d1cc61927..cdadc0180f 100644 --- a/aws-lc-rs/Cargo.toml +++ b/aws-lc-rs/Cargo.toml @@ -53,3 +53,4 @@ regex = "1.6.0" lazy_static = "1.4.0" clap = { version = "4.1.8", features = ["derive"] } hex = "0.4.3" +base64 = "0" diff --git a/aws-lc-rs/src/agreement.rs b/aws-lc-rs/src/agreement.rs index c38252451c..6237fcbd36 100644 --- a/aws-lc-rs/src/agreement.rs +++ b/aws-lc-rs/src/agreement.rs @@ -51,21 +51,26 @@ //! ``` use crate::ec::{ ec_group_from_nid, ec_point_from_bytes, evp_key_generate, evp_pkey_from_public_point, + marshal_x509_public_key_to_buffer, }; use crate::error::Unspecified; use crate::fips::indicator_check; -use crate::ptr::LcPtr; +use crate::hex; +use crate::ptr::{ConstPointer, LcPtr}; +use crate::public_key::evp_pkey_from_x509_pubkey; use crate::rand::SecureRandom; -use crate::{ec, hex}; +use crate::{ec, public_key}; 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, + d2i_X509_PUBKEY, i2d_PUBKEY, EVP_PKEY_CTX_new, EVP_PKEY_CTX_new_id, EVP_PKEY_derive, + EVP_PKEY_derive_init, EVP_PKEY_derive_set_peer, EVP_PKEY_get0_EC_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, + 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)] @@ -360,6 +365,37 @@ impl EphemeralPrivateKey { } } + /// Computes the public key in X509 DER encoding format from the private key. + /// + /// # Errors + /// `error::Unspecified` when operation fails due to internal error. + pub fn compute_x509_pubkey(&self) -> Result, Unspecified> { + let mut buffer = std::ptr::null_mut::(); + match &self.inner_key { + KeyInner::ECDH_P256(evp_pkey) + | KeyInner::ECDH_P384(evp_pkey) + | KeyInner::ECDH_P521(evp_pkey) => { + let ec_key = ConstPointer::new(unsafe { EVP_PKEY_get0_EC_KEY(**evp_pkey) })?; + let len = unsafe { aws_lc::i2d_EC_PUBKEY(*ec_key, &mut buffer) }; + if len < 0 { + return Err(Unspecified); + } + let buffer = LcPtr::new(buffer)?; + let der = unsafe { std::slice::from_raw_parts(*buffer, len.try_into()?).to_vec() }; + Ok(der) + } + KeyInner::X25519(priv_key) => { + let len = unsafe { i2d_PUBKEY(**priv_key, &mut buffer) }; + if len < 0 { + return Err(Unspecified); + } + let buffer = LcPtr::new(buffer)?; + let der = unsafe { std::slice::from_raw_parts(*buffer, len.try_into()?).to_vec() }; + Ok(der) + } + } + } + /// The algorithm for the private key. #[inline] #[must_use] @@ -446,6 +482,7 @@ impl Clone for PublicKey { pub struct UnparsedPublicKey> { alg: &'static Algorithm, bytes: B, + encoding: &'static public_key::Encoding, } impl> Copy for UnparsedPublicKey {} @@ -461,20 +498,30 @@ impl> Debug for UnparsedPublicKey { } impl> UnparsedPublicKey { - /// Constructs a new `UnparsedPublicKey`. + /// Constructs a new `UnparsedPublicKey` with [`public_key::OCTET_STRING`] encoding. pub fn new(algorithm: &'static Algorithm, bytes: B) -> Self { UnparsedPublicKey { alg: algorithm, bytes, + encoding: &public_key::OCTET_STRING, } } - /// The agreement algorithm associated with this public key + /// Constructs a new `UnparsedPublicKey` with [`public_key::X509`] encoding. + pub fn new_with_x509(algorithm: &'static Algorithm, bytes: B) -> Self { + UnparsedPublicKey { + alg: algorithm, + bytes, + encoding: &public_key::X509, + } + } + + /// 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 } @@ -523,32 +570,70 @@ where F: FnOnce(&[u8]) -> Result, { let expected_alg = my_private_key.algorithm(); - let expected_pub_key_len = expected_alg.id.pub_key_len(); let expected_nid = expected_alg.id.nid(); if peer_public_key.alg != expected_alg { 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 == &public_key::OCTET_STRING { + let expected_pub_key_len = expected_alg.id.pub_key_len(); + 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 { + public_key::EncodingID::OctetString => { + x25519_diffie_hellman(&mut buffer, priv_key, peer_pub_bytes) + .map_err(|()| error_value)?; + &buffer[0..X25519_SHARED_KEY_LEN] + } + public_key::EncodingID::X509 => { + x25519_dh_with_x509_peer_pubkey(peer_pub_bytes, &mut buffer, priv_key) + .map_err(|()| 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_x509_peer_pubkey( + peer_x509_pubkey: &[u8], + shared_secret: &mut [u8; MAX_AGREEMENT_SECRET_LEN], + priv_key: &LcPtr, +) -> Result<(), ()> { + let len = c_long::try_from(peer_x509_pubkey.len()).map_err(|_| ())?; + let x509_pubkey = LcPtr::new(unsafe { + d2i_X509_PUBKEY( + null_mut(), + &mut peer_x509_pubkey.as_ptr() as *mut *const c_uchar, + len, + ) + })?; + let mut peer_octstr_pubkey = [0u8; X25519_PUBLIC_VALUE_LEN as usize]; + marshal_x509_public_key_to_buffer(&mut peer_octstr_pubkey, &x509_pubkey)?; + x25519_diffie_hellman(shared_secret, priv_key, &peer_octstr_pubkey)?; + Ok(()) +} + // Current max secret length is P-521's. const MAX_AGREEMENT_SECRET_LEN: usize = ECDH_P521_PRIVATE_KEY_LEN; @@ -558,11 +643,17 @@ 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: &public_key::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 pub_key = match peer_pub_key_bytes_encoding.id { + public_key::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)? } + } + public_key::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()) })?; @@ -590,11 +681,11 @@ fn ec_key_ecdh<'a>( } #[inline] -fn x25519_diffie_hellman<'a>( - buffer: &'a mut [u8; MAX_AGREEMENT_SECRET_LEN], +fn x25519_diffie_hellman( + buffer: &mut [u8; MAX_AGREEMENT_SECRET_LEN], priv_key: &LcPtr, peer_pub_key: &[u8], -) -> Result<&'a [u8], ()> { +) -> Result<(), ()> { let pkey_ctx = LcPtr::new(unsafe { EVP_PKEY_CTX_new(**priv_key, null_mut()) })?; if 1 != unsafe { EVP_PKEY_derive_init(*pkey_ctx) } { @@ -623,14 +714,18 @@ fn x25519_diffie_hellman<'a>( } debug_assert!(out_key_len == X25519_SHARED_KEY_LEN); - - Ok(&buffer[0..X25519_SHARED_KEY_LEN]) + 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; + use base64::engine::general_purpose; + use base64::Engine; #[cfg(feature = "fips")] mod fips; @@ -1037,4 +1132,111 @@ 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 x509_pubkey = my_private_key + .compute_x509_pubkey() + .expect("Failed to compute x509 public key"); + let expected_x509_key_len = match algorithm.id { + agreement::AlgorithmID::ECDH_P256 => 91, + agreement::AlgorithmID::ECDH_P384 => 120, + agreement::AlgorithmID::ECDH_P521 => 158, + agreement::AlgorithmID::X25519 => 44, + }; + assert_eq!(x509_pubkey.len(), expected_x509_key_len); + let b64 = general_purpose::STANDARD.encode(&x509_pubkey); + // Useful to print out the X509 public key in base64 as input to other libraries, such as bc-fips + println!("{:?} x509 pubkey: {b64}", algorithm.id); + + let peer_public_key = + { agreement::UnparsedPublicKey::new_with_x509(algorithm, out.as_slice()) }; + 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 0d08a509d9..1e9bd239a3 100644 --- a/aws-lc-rs/src/ec.rs +++ b/aws-lc-rs/src/ec.rs @@ -4,12 +4,42 @@ // SPDX-License-Identifier: Apache-2.0 OR ISC use core::fmt; + +use crate::ptr::{ConstPointer, DetachableLcPtr, LcPtr, Pointer}; + +use crate::fips::indicator_check; +use crate::signature::{Signature, VerificationAlgorithm}; +use crate::{digest, sealed}; + +#[cfg(not(feature = "fips"))] +use aws_lc::EC_KEY_check_key; + +use aws_lc::{ + i2d_PUBKEY_bio, point_conversion_form_t, BIO_get_mem_data, BIO_new, BIO_s_mem, + 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, + X25519_PUBLIC_VALUE_LEN, X509_PUBKEY, +}; + +#[cfg(test)] +use aws_lc::EC_POINT_mul; + +use aws_lc::EC_KEY_get0_private_key; + 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}; + #[cfg(test)] use std::ptr::null; + use std::ptr::null_mut; #[cfg(feature = "ring-sig-verify")] @@ -17,30 +47,14 @@ use untrusted::Input; #[cfg(feature = "fips")] use aws_lc::EC_KEY_check_fips; -#[cfg(not(feature = "fips"))] -use aws_lc::EC_KEY_check_key; -#[cfg(test)] -use aws_lc::EC_POINT_mul; -use aws_lc::{ - point_conversion_form_t, BN_bn2bin_padded, BN_num_bytes, 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_private_key, 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, -}; + +use aws_lc::{BN_bn2bin_padded, BN_num_bytes}; use crate::buffer::Buffer; use crate::digest::digest_ctx::DigestContext; use crate::encoding::AsDer; use crate::error::{KeyRejected, Unspecified}; -use crate::fips::indicator_check; -use crate::ptr::{ConstPointer, DetachableLcPtr, LcPtr, Pointer}; -use crate::signature::{Signature, VerificationAlgorithm}; -use crate::{digest, hex, sealed}; +use crate::hex; pub(crate) mod key_pair; @@ -323,6 +337,31 @@ pub(crate) unsafe fn marshal_private_key_to_buffer( Ok(buffer) } +pub(crate) fn marshal_x509_public_key_to_buffer( + buffer: &mut [u8; X25519_PUBLIC_VALUE_LEN as usize], + x509_pubkey: &LcPtr, +) -> Result<(), ()> { + let buffer_len = buffer.len(); + 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(()); + } + + let mut ptr = std::ptr::null_mut::(); + let size = BIO_get_mem_data(*bio, &mut ptr); + let size = usize::try_from(size).map_err(|_| ())?; + + if size < buffer_len { + return Err(()); + } + + let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, size) }; + buffer.copy_from_slice(&slice[..buffer_len]); + Ok(()) +} + pub(crate) unsafe fn marshal_public_key_to_buffer( buffer: &mut [u8; PUBLIC_KEY_MAX_LEN], evp_pkey: &ConstPointer, diff --git a/aws-lc-rs/src/lib.rs b/aws-lc-rs/src/lib.rs index 375a36ee39..d128070f18 100644 --- a/aws-lc-rs/src/lib.rs +++ b/aws-lc-rs/src/lib.rs @@ -120,6 +120,7 @@ pub mod hmac; pub mod io; pub mod pbkdf2; pub mod pkcs8; +pub(crate) mod public_key; pub mod rand; pub mod signature; pub mod test; diff --git a/aws-lc-rs/src/ptr.rs b/aws-lc-rs/src/ptr.rs index 5103cc5f14..2bd8cbcac9 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 { diff --git a/aws-lc-rs/src/public_key.rs b/aws-lc-rs/src/public_key.rs new file mode 100644 index 0000000000..0015edc6ae --- /dev/null +++ b/aws-lc-rs/src/public_key.rs @@ -0,0 +1,86 @@ +// Copyright 2015-2016 Hanson Char. +// SPDX-License-Identifier: ISC +// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +//! Public key common construts, such as the encoding used in an `UnparsedPublicKey`. + +use std::{ + os::raw::{c_int, c_void}, + ptr::null_mut, +}; + +use aws_lc::{d2i_PUBKEY_bio, BIO_new, BIO_s_mem, BIO_write, EVP_PKEY}; + +use crate::{error::Unspecified, ptr::LcPtr}; + +/// Encoding ID. +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum EncodingID { + /// A sequence of 8-bit bytes + OctetString, + /// X509 DER encoding + X509, +} + +/// Encoding of bytes +#[derive(Debug, PartialEq, Eq)] +pub struct Encoding { + /// Encoding ID + pub(crate) id: EncodingID, +} + +/// Octet String encoding +pub(crate) static OCTET_STRING: Encoding = Encoding { + id: EncodingID::OctetString, +}; + +/// X509 DER encoding +pub(crate) static X509: Encoding = Encoding { + id: EncodingID::X509, +}; + +#[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)] +mod tests { + use base64::engine::general_purpose; + use base64::Engine; + + use crate::public_key::evp_pkey_from_x509_pubkey; + + #[test] + fn test_evp_pkey_from_x509_pubkey() { + // Generated using "SHA384withRSA" from BC-FIPS + let b64_x509_pubkey = concat!( + "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA28pm7EDzXRvFmgK2/UNriVNWF4slKNDKtY", + "q6hEZsJstfVU/J7zOAJKjUjR/abgnLYAd6I8M9aiYNaAh/GfnIfOXhymjSqfiimCu14dJsQQLP/Thd", + "yR6jSKvQCkyWPWpo1S5H7qkmPpjxU6CeyYAkaNc+B1TnAblyLQ90wwY5OmAzQD0A6k1UX6NoHB/W5P", + "G731y16QTv34xVycXYFfp+pSyKHm5Q7YXPQLKrWPTIFoOvVHi94s+c7nqmYxfXhtzBf9WSr9so6Dgz", + "vlsFK4FhyOq4zKN7XQkOwAVyZ5X//bMwfzVmm7TJTvfGyMNU0YaCPLgRSWn2bDeiY9hbfERurAkBIN", + "/piGXl/12xv8tZGa5lAQe4fcj+O1Uc9b9tDbuba9HiYxS1OAfeAO25kqBa24qqvEMxTgDf0G6AnCzj", + "dQFsP4pVfRBmMVjo6Zikq+TStr0+UID2u21N3MrcLue7BzbujU/9buFtSES5QWYUdQoYb3pzYEgojJ", + "HriYBA1yiJAgMBAAE=" + ); + let x509_pubkey = general_purpose::STANDARD + .decode(b64_x509_pubkey) + .expect("Invalid base64 encoding"); + evp_pkey_from_x509_pubkey(x509_pubkey.as_slice()) + .expect("Failed evp_pkey_from_x509_pubkey"); + } +}