From 3448b87c03b97f75fe85dc320f2fc7655c305b68 Mon Sep 17 00:00:00 2001 From: Justin W Smith <103147162+justsmth@users.noreply.github.com> Date: Thu, 18 Jan 2024 14:40:15 -0500 Subject: [PATCH] Persistent private keys for agreement (#302) --- aws-lc-rs/src/agreement.rs | 721 ++++++++++-------- .../{ => agreement}/data/agreement_tests.txt | 0 aws-lc-rs/src/agreement/ephemeral.rs | 462 +++++++++++ aws-lc-rs/src/bn.rs | 10 +- aws-lc-rs/src/ec.rs | 216 +++--- aws-lc-rs/src/ec/key_pair.rs | 83 +- aws-lc-rs/src/ed25519.rs | 100 ++- aws-lc-rs/src/encoding.rs | 58 ++ aws-lc-rs/src/evp_pkey.rs | 2 +- aws-lc-rs/src/lib.rs | 21 +- aws-lc-rs/src/signature.rs | 3 +- aws-lc-rs/tests/ecdsa_tests.rs | 3 +- aws-lc-rs/tests/ed25519_tests.rs | 5 +- 13 files changed, 1154 insertions(+), 530 deletions(-) rename aws-lc-rs/src/{ => agreement}/data/agreement_tests.txt (100%) create mode 100644 aws-lc-rs/src/agreement/ephemeral.rs create mode 100644 aws-lc-rs/src/encoding.rs diff --git a/aws-lc-rs/src/agreement.rs b/aws-lc-rs/src/agreement.rs index c38252451c3..43c023b1ac1 100644 --- a/aws-lc-rs/src/agreement.rs +++ b/aws-lc-rs/src/agreement.rs @@ -49,21 +49,29 @@ //! //! # Ok::<(), aws_lc_rs::error::Unspecified>(()) //! ``` +mod ephemeral; + +pub use ephemeral::{agree_ephemeral, EphemeralPrivateKey}; + use crate::ec::{ ec_group_from_nid, ec_point_from_bytes, evp_key_generate, evp_pkey_from_public_point, }; -use crate::error::Unspecified; +use crate::error::{KeyRejected, Unspecified}; use crate::fips::indicator_check; -use crate::ptr::LcPtr; -use crate::rand::SecureRandom; +use crate::ptr::{ConstPointer, LcPtr, Pointer}; use crate::{ec, hex}; 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, + EVP_PKEY_derive_set_peer, EVP_PKEY_get0_EC_KEY, EVP_PKEY_get_raw_private_key, + EVP_PKEY_get_raw_public_key, EVP_PKEY_keygen, EVP_PKEY_keygen_init, + EVP_PKEY_new_raw_private_key, EVP_PKEY_new_raw_public_key, NID_X9_62_prime256v1, NID_secp384r1, + NID_secp521r1, BIGNUM, EVP_PKEY, EVP_PKEY_X25519, NID_X25519, }; +use crate::buffer::Buffer; +use crate::encoding::{ + AsBigEndian, AsDer, Curve25519SeedBin, EcPrivateKeyBin, EcPrivateKeyRfc5915Der, +}; use core::fmt; use std::fmt::{Debug, Formatter}; use std::ptr::null_mut; @@ -79,7 +87,7 @@ enum AlgorithmID { impl AlgorithmID { #[inline] - fn nid(&self) -> i32 { + const fn nid(&self) -> i32 { match self { AlgorithmID::ECDH_P256 => NID_X9_62_prime256v1, AlgorithmID::ECDH_P384 => NID_secp384r1, @@ -89,7 +97,7 @@ impl AlgorithmID { } #[inline] - fn pub_key_len(&self) -> usize { + const fn pub_key_len(&self) -> usize { match self { AlgorithmID::ECDH_P256 => 65, AlgorithmID::ECDH_P384 => 97, @@ -97,6 +105,15 @@ impl AlgorithmID { AlgorithmID::X25519 => 32, } } + + #[inline] + const fn private_key_len(&self) -> usize { + match self { + AlgorithmID::ECDH_P256 | AlgorithmID::X25519 => 32, + AlgorithmID::ECDH_P384 => 48, + AlgorithmID::ECDH_P521 => 66, + } + } } impl Debug for AlgorithmID { @@ -124,17 +141,17 @@ impl Debug for Algorithm { } /// ECDH using the NSA Suite B P-256 (secp256r1) curve. -pub static ECDH_P256: Algorithm = Algorithm { +pub const ECDH_P256: Algorithm = Algorithm { id: AlgorithmID::ECDH_P256, }; /// ECDH using the NSA Suite B P-384 (secp384r1) curve. -pub static ECDH_P384: Algorithm = Algorithm { +pub const ECDH_P384: Algorithm = Algorithm { id: AlgorithmID::ECDH_P384, }; /// ECDH using the NSA Suite B P-521 (secp521r1) curve. -pub static ECDH_P521: Algorithm = Algorithm { +pub const ECDH_P521: Algorithm = Algorithm { id: AlgorithmID::ECDH_P521, }; @@ -146,17 +163,10 @@ pub static ECDH_P521: Algorithm = Algorithm { /// /// [RFC 7748]: https://tools.ietf.org/html/rfc7748 /// [RFC 7748 section 6.1]: https://tools.ietf.org/html/rfc7748#section-6.1 -pub static X25519: Algorithm = Algorithm { +pub const 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; -#[cfg(test)] -const ECDH_P384_PRIVATE_KEY_LEN: usize = 48; -const ECDH_P521_PRIVATE_KEY_LEN: usize = 66; -const X25519_SHARED_KEY_LEN: usize = aws_lc::X25519_SHARED_KEY_LEN as usize; + #[allow(non_camel_case_types)] enum KeyInner { ECDH_P256(LcPtr), @@ -165,10 +175,10 @@ enum KeyInner { X25519(LcPtr), } -/// An ephemeral private key for use (only) with `agree_ephemeral`. The -/// signature of `agree_ephemeral` ensures that an `EphemeralPrivateKey` can be -/// used for at most one key agreement. -pub struct EphemeralPrivateKey { +/// A private key for use (only) with `agree`. The +/// signature of `agree` allows `PrivateKey` to be +/// used for more than one key agreement. +pub struct PrivateKey { inner_key: KeyInner, } @@ -182,9 +192,18 @@ impl KeyInner { KeyInner::X25519(..) => &X25519, } } + + fn get_evp_pkey(&self) -> &LcPtr { + match self { + KeyInner::ECDH_P256(evp_pkey) + | KeyInner::ECDH_P384(evp_pkey) + | KeyInner::ECDH_P521(evp_pkey) + | KeyInner::X25519(evp_pkey) => evp_pkey, + } + } } -unsafe impl Send for EphemeralPrivateKey {} +unsafe impl Send for PrivateKey {} // https://github.com/awslabs/aws-lc/blob/main/include/openssl/ec_key.h#L88 // An |EC_KEY| object represents a public or private EC key. A given object may @@ -192,23 +211,37 @@ unsafe impl Send for EphemeralPrivateKey {} // no other thread is concurrently calling a mutating function. Unless otherwise // documented, functions which take a |const| pointer are non-mutating and // functions which take a non-|const| pointer are mutating. -unsafe impl Sync for EphemeralPrivateKey {} +unsafe impl Sync for PrivateKey {} -impl Debug for EphemeralPrivateKey { +impl Debug for PrivateKey { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { f.write_str(&format!( - "EphemeralPrivateKey {{ algorithm: {:?} }}", + "PrivateKey {{ algorithm: {:?} }}", self.inner_key.algorithm() )) } } -impl EphemeralPrivateKey { +impl PrivateKey { + fn new(alg: &'static Algorithm, evp_pkey: LcPtr) -> Self { + match alg.id { + AlgorithmID::X25519 => Self { + inner_key: KeyInner::X25519(evp_pkey), + }, + AlgorithmID::ECDH_P256 => Self { + inner_key: KeyInner::ECDH_P256(evp_pkey), + }, + AlgorithmID::ECDH_P384 => Self { + inner_key: KeyInner::ECDH_P384(evp_pkey), + }, + AlgorithmID::ECDH_P521 => Self { + inner_key: KeyInner::ECDH_P521(evp_pkey), + }, + } + } + #[inline] - /// Generate a new ephemeral private key for the given algorithm. - /// - /// # *ring* Compatibility - /// Our implementation ignores the `SecureRandom` parameter. + /// Generate a new private key for the given algorithm. /// // # FIPS // Use this function with one of the following algorithms: @@ -218,59 +251,95 @@ impl EphemeralPrivateKey { // /// # Errors /// `error::Unspecified` when operation fails due to internal error. - pub fn generate(alg: &'static Algorithm, _rng: &dyn SecureRandom) -> Result { - match alg.id { - AlgorithmID::X25519 => { - let priv_key = generate_x25519()?; - Ok(EphemeralPrivateKey { - inner_key: KeyInner::X25519(priv_key), - }) - } - AlgorithmID::ECDH_P256 => { - let ec_key = evp_key_generate(ECDH_P256.id.nid())?; - Ok(EphemeralPrivateKey { - inner_key: KeyInner::ECDH_P256(ec_key), - }) - } - AlgorithmID::ECDH_P384 => { - let ec_key = evp_key_generate(ECDH_P384.id.nid())?; - Ok(EphemeralPrivateKey { - inner_key: KeyInner::ECDH_P384(ec_key), - }) - } - AlgorithmID::ECDH_P521 => { - let ec_key = evp_key_generate(ECDH_P521.id.nid())?; - Ok(EphemeralPrivateKey { - inner_key: KeyInner::ECDH_P521(ec_key), - }) - } + pub fn generate(alg: &'static Algorithm) -> Result { + let evp_pkey = match alg.id { + AlgorithmID::X25519 => generate_x25519()?, + _ => evp_key_generate(alg.id.nid())?, + }; + Ok(Self::new(alg, evp_pkey)) + } + + /// Deserializes a DER-encoded private key structure to produce a `agreement::PrivateKey`. + /// + /// This function is typically used to deserialize RFC 5915 encoded private keys, but it will + /// attempt to automatically detect other key formats. This function supports unencrypted + /// PKCS#8 `PrivateKeyInfo` structures as well as key type specific formats. + /// + /// X25519 keys are not supported. See `PrivateKey::as_der`. + /// + /// # Errors + /// `error::KeyRejected` if parsing failed or key otherwise unacceptable. + /// + /// # Panics + pub fn from_private_key_der( + alg: &'static Algorithm, + key_bytes: &[u8], + ) -> Result { + if AlgorithmID::X25519 == alg.id { + return Err(KeyRejected::invalid_encoding()); + } + let evp_pkey = unsafe { ec::unmarshal_der_to_private_key(key_bytes, alg.id.nid())? }; + Ok(Self::new(alg, evp_pkey)) + } + + /// Constructs an ECDH key from private key bytes + /// + /// The private key must encoded as a big-endian fixed-length integer. For + /// example, a P-256 private key must be 32 bytes prefixed with leading + /// zeros as needed. + /// + /// # Errors + /// `error::KeyRejected` if parsing failed or key otherwise unacceptable. + pub fn from_private_key( + alg: &'static Algorithm, + key_bytes: &[u8], + ) -> Result { + if key_bytes.len() != alg.id.private_key_len() { + return Err(KeyRejected::wrong_algorithm()); } + let evp_pkey = if AlgorithmID::X25519 == alg.id { + LcPtr::new(unsafe { + EVP_PKEY_new_raw_private_key( + EVP_PKEY_X25519, + null_mut(), + key_bytes.as_ptr(), + AlgorithmID::X25519.private_key_len(), + ) + })? + } else { + let ec_group = ec_group_from_nid(alg.id.nid())?; + let private_bn = LcPtr::::try_from(key_bytes)?; + + ec::evp_pkey_from_private(&ec_group.as_const(), &private_bn.as_const()) + .map_err(|_| KeyRejected::invalid_encoding())? + }; + Ok(Self::new(alg, evp_pkey)) } #[cfg(test)] #[allow(clippy::missing_errors_doc)] pub fn generate_for_test( alg: &'static Algorithm, - rng: &dyn SecureRandom, + rng: &dyn crate::rand::SecureRandom, ) -> Result { match alg.id { AlgorithmID::X25519 => { - let mut priv_key = [0u8; X25519_PRIVATE_KEY_LEN]; + let mut priv_key = [0u8; AlgorithmID::X25519.private_key_len()]; rng.fill(&mut priv_key)?; Self::from_x25519_private_key(&priv_key) } AlgorithmID::ECDH_P256 => { - let mut priv_key = [0u8; ECDH_P256_PRIVATE_KEY_LEN]; + let mut priv_key = [0u8; AlgorithmID::ECDH_P256.private_key_len()]; rng.fill(&mut priv_key)?; Self::from_p256_private_key(&priv_key) } AlgorithmID::ECDH_P384 => { - let mut priv_key = [0u8; ECDH_P384_PRIVATE_KEY_LEN]; + let mut priv_key = [0u8; AlgorithmID::ECDH_P384.private_key_len()]; rng.fill(&mut priv_key)?; Self::from_p384_private_key(&priv_key) } AlgorithmID::ECDH_P521 => { - let mut priv_key = [0u8; ECDH_P521_PRIVATE_KEY_LEN]; + let mut priv_key = [0u8; AlgorithmID::ECDH_P521.private_key_len()]; rng.fill(&mut priv_key)?; Self::from_p521_private_key(&priv_key) } @@ -279,10 +348,8 @@ impl EphemeralPrivateKey { #[cfg(test)] fn from_x25519_private_key( - priv_key: &[u8; X25519_PRIVATE_KEY_LEN], + priv_key: &[u8; AlgorithmID::X25519.private_key_len()], ) -> Result { - use aws_lc::EVP_PKEY_new_raw_private_key; - let pkey = LcPtr::new(unsafe { EVP_PKEY_new_raw_private_key( EVP_PKEY_X25519, @@ -292,7 +359,7 @@ impl EphemeralPrivateKey { ) })?; - Ok(EphemeralPrivateKey { + Ok(PrivateKey { inner_key: KeyInner::X25519(pkey), }) } @@ -300,7 +367,7 @@ impl EphemeralPrivateKey { #[cfg(test)] fn from_p256_private_key(priv_key: &[u8]) -> Result { let pkey = from_ec_private_key(priv_key, ECDH_P256.id.nid())?; - Ok(EphemeralPrivateKey { + Ok(PrivateKey { inner_key: KeyInner::ECDH_P256(pkey), }) } @@ -308,7 +375,7 @@ impl EphemeralPrivateKey { #[cfg(test)] fn from_p384_private_key(priv_key: &[u8]) -> Result { let pkey = from_ec_private_key(priv_key, ECDH_P384.id.nid())?; - Ok(EphemeralPrivateKey { + Ok(PrivateKey { inner_key: KeyInner::ECDH_P384(pkey), }) } @@ -316,7 +383,7 @@ impl EphemeralPrivateKey { #[cfg(test)] fn from_p521_private_key(priv_key: &[u8]) -> Result { let pkey = from_ec_private_key(priv_key, ECDH_P521.id.nid())?; - Ok(EphemeralPrivateKey { + Ok(PrivateKey { inner_key: KeyInner::ECDH_P521(pkey), }) } @@ -368,14 +435,84 @@ impl EphemeralPrivateKey { } } +impl AsDer> for PrivateKey { + /// Serializes the key as a DER-encoded `ECPrivateKey` (RFC 5915) structure. + /// + /// X25519 is not supported. + /// + /// # Errors + /// `error::Unspecified` if serialization failed. + fn as_der(&self) -> Result, Unspecified> { + if AlgorithmID::X25519 == self.inner_key.algorithm().id { + return Err(Unspecified); + } + + let mut outp = null_mut::(); + let ec_key = { + ConstPointer::new(unsafe { + EVP_PKEY_get0_EC_KEY(self.inner_key.get_evp_pkey().as_const_ptr()) + })? + }; + let length = usize::try_from(unsafe { aws_lc::i2d_ECPrivateKey(*ec_key, &mut outp) }) + .map_err(|_| Unspecified)?; + let outp = LcPtr::new(outp)?; + Ok(Buffer::take_from_slice(unsafe { + std::slice::from_raw_parts_mut(*outp, length) + })) + } +} + +impl AsBigEndian> for PrivateKey { + /// Exposes the private key encoded as a big-endian fixed-length integer. + /// + /// X25519 is not supported. + /// + /// # Errors + /// `error::Unspecified` if serialization failed. + fn as_be_bytes(&self) -> Result, Unspecified> { + if AlgorithmID::X25519 == self.inner_key.algorithm().id { + return Err(Unspecified); + } + let buffer = unsafe { + ec::marshal_private_key_to_buffer( + self.inner_key.algorithm().id.private_key_len(), + &self.inner_key.get_evp_pkey().as_const(), + )? + }; + Ok(EcPrivateKeyBin::new(buffer)) + } +} + +impl AsBigEndian> for PrivateKey { + /// Exposes the seed encoded as a big-endian fixed-length integer. + /// + /// Only X25519 is supported. + /// + /// # Errors + /// `error::Unspecified` if serialization failed. + fn as_be_bytes(&self) -> Result, Unspecified> { + if AlgorithmID::X25519 != self.inner_key.algorithm().id { + return Err(Unspecified); + } + let evp_pkey = self.inner_key.get_evp_pkey().as_const(); + let mut buffer = [0u8; AlgorithmID::X25519.private_key_len()]; + let mut out_len = AlgorithmID::X25519.private_key_len(); + if 1 != unsafe { + EVP_PKEY_get_raw_private_key(*evp_pkey, buffer.as_mut_ptr(), &mut out_len) + } { + return Err(Unspecified); + } + debug_assert_eq!(32, out_len); + Ok(Curve25519SeedBin::new(Vec::from(buffer))) + } +} + #[cfg(test)] fn from_ec_private_key(priv_key: &[u8], nid: i32) -> Result, Unspecified> { - use crate::ptr::DetachableLcPtr; - - let ec_group = unsafe { ec_group_from_nid(nid)? }; - let priv_key = DetachableLcPtr::try_from(priv_key)?; + let ec_group = ec_group_from_nid(nid)?; + let priv_key = LcPtr::::try_from(priv_key)?; - let pkey = unsafe { ec::evp_pkey_from_private(&ec_group.as_const(), &priv_key.as_const())? }; + let pkey = ec::evp_pkey_from_private(&ec_group.as_const(), &priv_key.as_const())?; Ok(pkey) } @@ -480,16 +617,14 @@ impl> UnparsedPublicKey { } } -/// Performs a key agreement with an ephemeral private key and the given public -/// key. +/// Performs a key agreement with a private key and the given public key. /// -/// `my_private_key` is the ephemeral private key to use. Since it is moved, it -/// will not be usable after calling `agree_ephemeral`, thus guaranteeing that -/// the key is used for only one key agreement. +/// `my_private_key` is the private key to use. Only a reference to the key +/// is required, allowing the key to continue to be used. /// -/// `peer_public_key` is the peer's public key. `agree_ephemeral` will return +/// `peer_public_key` is the peer's public key. `agree` will return /// `Err(error_value)` if it does not match `my_private_key's` algorithm/curve. -/// `agree_ephemeral` verifies that it is encoded in the standard form for the +/// `agree` verifies that it is encoded in the standard form for the /// algorithm and that the key is *valid*; see the algorithm's documentation for /// details on how keys are to be encoded and what constitutes a valid key for /// that algorithm. @@ -498,7 +633,7 @@ impl> UnparsedPublicKey { /// called, e.g. when decoding of the peer's public key fails or when the public /// key is otherwise invalid. /// -/// After the key agreement is done, `agree_ephemeral` calls `kdf` with the raw +/// After the key agreement is done, `agree` calls `kdf` with the raw /// key material from the key agreement operation and then returns what `kdf` /// returns. /// @@ -511,10 +646,9 @@ impl> UnparsedPublicKey { /// # Errors /// `error_value` on internal failure. #[inline] -#[allow(clippy::needless_pass_by_value)] #[allow(clippy::missing_panics_doc)] -pub fn agree_ephemeral, F, R, E>( - my_private_key: EphemeralPrivateKey, +pub fn agree, F, R, E>( + my_private_key: &PrivateKey, peer_public_key: &UnparsedPublicKey, error_value: E, kdf: F, @@ -550,7 +684,7 @@ where } // Current max secret length is P-521's. -const MAX_AGREEMENT_SECRET_LEN: usize = ECDH_P521_PRIVATE_KEY_LEN; +const MAX_AGREEMENT_SECRET_LEN: usize = AlgorithmID::ECDH_P521.private_key_len(); #[inline] #[allow(clippy::needless_pass_by_value)] @@ -560,9 +694,9 @@ fn ec_key_ecdh<'a>( peer_pub_key_bytes: &[u8], 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 ec_group = ec_group_from_nid(nid)?; + let pub_key_point = ec_point_from_bytes(&ec_group, peer_pub_key_bytes)?; + let pub_key = evp_pkey_from_public_point(&ec_group, &pub_key_point)?; let pkey_ctx = LcPtr::new(unsafe { EVP_PKEY_CTX_new(**priv_key, null_mut()) })?; @@ -622,70 +756,21 @@ fn x25519_diffie_hellman<'a>( return Err(()); } - debug_assert!(out_key_len == X25519_SHARED_KEY_LEN); + debug_assert!(out_key_len == AlgorithmID::X25519.pub_key_len()); - Ok(&buffer[0..X25519_SHARED_KEY_LEN]) + Ok(&buffer[0..AlgorithmID::X25519.pub_key_len()]) } #[cfg(test)] mod tests { - use crate::error::Unspecified; - use crate::{agreement, rand, test, test_file}; - - #[cfg(feature = "fips")] - mod fips; - - #[test] - fn test_agreement_ecdh_x25519_rfc_iterated() { - fn expect_iterated_x25519( - expected_result: &str, - range: core::ops::Range, - k: &mut Vec, - u: &mut Vec, - ) { - for _ in range { - let new_k = x25519(k, u); - *u = k.clone(); - *k = new_k; - } - assert_eq!(&h(expected_result), k); - } - - let mut k = h("0900000000000000000000000000000000000000000000000000000000000000"); - let mut u = k.clone(); - - expect_iterated_x25519( - "422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079", - 0..1, - &mut k, - &mut u, - ); - expect_iterated_x25519( - "684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51", - 1..1_000, - &mut k, - &mut u, - ); - - // The spec gives a test vector for 1,000,000 iterations but it takes - // too long to do 1,000,000 iterations by default right now. This - // 10,000 iteration vector is self-computed. - expect_iterated_x25519( - "2c125a20f639d504a7703d2e223c79a79de48c4ee8c23379aa19a62ecd211815", - 1_000..10_000, - &mut k, - &mut u, - ); - - if cfg!(feature = "slow_tests") { - expect_iterated_x25519( - "7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424", - 10_000..1_000_000, - &mut k, - &mut u, - ); - } - } + use crate::agreement::{ + agree, Algorithm, PrivateKey, PublicKey, UnparsedPublicKey, ECDH_P256, ECDH_P384, + ECDH_P521, X25519, + }; + use crate::encoding::{ + AsBigEndian, AsDer, Curve25519SeedBin, EcPrivateKeyBin, EcPrivateKeyRfc5915Der, + }; + use crate::{agreement, rand, test}; #[test] fn test_agreement_x25519() { @@ -703,7 +788,7 @@ mod tests { let my_private = { let rng = test::rand::FixedSliceRandom { bytes: &my_private }; - agreement::EphemeralPrivateKey::generate_for_test(alg, &rng).unwrap() + PrivateKey::generate_for_test(alg, &rng).unwrap() }; let my_public = test::from_dirty_hex( @@ -715,16 +800,65 @@ mod tests { assert_eq!(my_private.algorithm(), alg); + let be_private_key_buffer: Curve25519SeedBin = my_private.as_be_bytes().unwrap(); + let be_private_key = + PrivateKey::from_private_key(&X25519, be_private_key_buffer.as_ref()).unwrap(); + { + let result = agreement::agree(&be_private_key, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + let computed_public = my_private.compute_public_key().unwrap(); assert_eq!(computed_public.as_ref(), &my_public[..]); assert_eq!(computed_public.algorithm(), alg); + { + let result = agreement::agree(&my_private, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + { + let result = agreement::agree(&my_private, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + } - let result = agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| { - assert_eq!(key_material, &output[..]); - Ok(()) - }); - assert_eq!(result, Ok(())); + #[test] + fn test_agreement_invalid_keys() { + fn test_with_key(alg: &'static Algorithm, my_private_key: &PrivateKey, test_key: &[u8]) { + assert!(PrivateKey::from_private_key(alg, test_key).is_err()); + assert!(PrivateKey::from_private_key_der(alg, test_key).is_err()); + assert!(agree( + my_private_key, + &UnparsedPublicKey::new(alg, test_key), + (), + |_| Ok(()) + ) + .is_err()); + } + + let alg_variants: [&'static Algorithm; 4] = [&X25519, &ECDH_P256, &ECDH_P384, &ECDH_P521]; + + for alg in alg_variants { + let my_private_key = PrivateKey::generate(alg).unwrap(); + + let empty_key = []; + test_with_key(alg, &my_private_key, &empty_key); + + let wrong_size_key: [u8; 31] = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, + ]; + test_with_key(alg, &my_private_key, &wrong_size_key); + } } #[test] @@ -745,7 +879,7 @@ mod tests { let my_private = { let rng = test::rand::FixedSliceRandom { bytes: &my_private }; - agreement::EphemeralPrivateKey::generate_for_test(alg, &rng).unwrap() + PrivateKey::generate_for_test(alg, &rng).unwrap() }; let my_public = test::from_dirty_hex( @@ -757,16 +891,48 @@ mod tests { assert_eq!(my_private.algorithm(), alg); + let be_private_key_buffer: EcPrivateKeyBin = my_private.as_be_bytes().unwrap(); + let be_private_key = + PrivateKey::from_private_key(&ECDH_P256, be_private_key_buffer.as_ref()).unwrap(); + { + let result = agreement::agree(&be_private_key, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + + let der_private_key_buffer: EcPrivateKeyRfc5915Der = my_private.as_der().unwrap(); + let der_private_key = + PrivateKey::from_private_key_der(&ECDH_P256, der_private_key_buffer.as_ref()).unwrap(); + { + let result = agreement::agree(&der_private_key, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + let computed_public = my_private.compute_public_key().unwrap(); assert_eq!(computed_public.as_ref(), &my_public[..]); assert_eq!(computed_public.algorithm(), alg); - let result = agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| { - assert_eq!(key_material, &output[..]); - Ok(()) - }); - assert_eq!(result, Ok(())); + { + let result = agreement::agree(&my_private, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + + { + let result = agreement::agree(&my_private, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } } #[test] @@ -785,7 +951,7 @@ mod tests { let my_private = { let rng = test::rand::FixedSliceRandom { bytes: &my_private }; - agreement::EphemeralPrivateKey::generate_for_test(alg, &rng).unwrap() + PrivateKey::generate_for_test(alg, &rng).unwrap() }; let my_public = test::from_dirty_hex( @@ -797,16 +963,40 @@ mod tests { assert_eq!(my_private.algorithm(), alg); + let be_private_key_buffer: EcPrivateKeyBin = my_private.as_be_bytes().unwrap(); + let be_private_key = + PrivateKey::from_private_key(&ECDH_P384, be_private_key_buffer.as_ref()).unwrap(); + { + let result = agreement::agree(&be_private_key, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + + let der_private_key_buffer: EcPrivateKeyRfc5915Der = my_private.as_der().unwrap(); + let der_private_key = + PrivateKey::from_private_key_der(&ECDH_P384, der_private_key_buffer.as_ref()).unwrap(); + { + let result = agreement::agree(&der_private_key, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + let computed_public = my_private.compute_public_key().unwrap(); assert_eq!(computed_public.as_ref(), &my_public[..]); assert_eq!(computed_public.algorithm(), alg); - let result = agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| { - assert_eq!(key_material, &output[..]); - Ok(()) - }); - assert_eq!(result, Ok(())); + { + let result = agreement::agree(&my_private, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } } #[test] @@ -825,7 +1015,7 @@ mod tests { let my_private = { let rng = test::rand::FixedSliceRandom { bytes: &my_private }; - agreement::EphemeralPrivateKey::generate_for_test(alg, &rng).unwrap() + agreement::PrivateKey::generate_for_test(alg, &rng).unwrap() }; let my_public = test::from_dirty_hex( @@ -837,16 +1027,46 @@ mod tests { assert_eq!(my_private.algorithm(), alg); + let be_private_key_buffer: EcPrivateKeyBin = my_private.as_be_bytes().unwrap(); + let be_private_key = + PrivateKey::from_private_key(&ECDH_P521, be_private_key_buffer.as_ref()).unwrap(); + { + let result = agreement::agree(&be_private_key, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + + let der_private_key_buffer: EcPrivateKeyRfc5915Der = my_private.as_der().unwrap(); + let der_private_key = + PrivateKey::from_private_key_der(&ECDH_P521, der_private_key_buffer.as_ref()).unwrap(); + { + let result = agreement::agree(&der_private_key, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + let computed_public = my_private.compute_public_key().unwrap(); assert_eq!(computed_public.as_ref(), &my_public[..]); assert_eq!(computed_public.algorithm(), alg); - - let result = agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| { - assert_eq!(key_material, &output[..]); - Ok(()) - }); - assert_eq!(result, Ok(())); + { + let result = agreement::agree(&my_private, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + { + let result = agreement::agree(&my_private, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } } #[test] @@ -856,15 +1076,24 @@ mod tests { use regex::Regex; let rng = rand::SystemRandom::new(); - let private_key = - agreement::EphemeralPrivateKey::generate_for_test(&agreement::ECDH_P256, &rng).unwrap(); + let private_key = PrivateKey::generate_for_test(&ECDH_P256, &rng).unwrap(); - test::compile_time_assert_send::(); - //test::compile_time_assert_sync::(); + test::compile_time_assert_send::(); + test::compile_time_assert_sync::(); assert_eq!( format!("{:?}", &private_key), - "EphemeralPrivateKey { algorithm: Algorithm { curve: P256 } }" + "PrivateKey { algorithm: Algorithm { curve: P256 } }" + ); + + let ephemeral_private_key = PrivateKey::generate_for_test(&ECDH_P256, &rng).unwrap(); + + test::compile_time_assert_send::(); + test::compile_time_assert_sync::(); + + assert_eq!( + format!("{:?}", &ephemeral_private_key), + "PrivateKey { algorithm: Algorithm { curve: P256 } }" ); let public_key = private_key.compute_public_key().unwrap(); @@ -884,25 +1113,25 @@ mod tests { assert_eq!(public_key.as_ref(), pubkey_clone.as_ref()); assert_eq!(pubkey_debug, format!("{:?}", &pubkey_clone)); - test::compile_time_assert_clone::(); - test::compile_time_assert_send::(); - //test::compile_time_assert_sync::(); + test::compile_time_assert_clone::(); + test::compile_time_assert_send::(); + test::compile_time_assert_sync::(); // Verify `PublicKey` implements `Debug`. // // TODO: Test the actual output. let _: &dyn core::fmt::Debug = &public_key; - test::compile_time_assert_clone::>(); - test::compile_time_assert_copy::>(); - test::compile_time_assert_sync::>(); + test::compile_time_assert_clone::>(); + test::compile_time_assert_copy::>(); + test::compile_time_assert_sync::>(); - test::compile_time_assert_clone::>>(); - test::compile_time_assert_sync::>>(); + test::compile_time_assert_clone::>>(); + test::compile_time_assert_sync::>>(); let bytes = [0x01, 0x02, 0x03]; - let unparsed_public_key = agreement::UnparsedPublicKey::new(&agreement::X25519, &bytes); + let unparsed_public_key = UnparsedPublicKey::new(&X25519, &bytes); let unparsed_pubkey_clone = unparsed_public_key; assert_eq!( format!("{unparsed_public_key:?}"), @@ -913,8 +1142,7 @@ mod tests { r#"UnparsedPublicKey { algorithm: Algorithm { curve: Curve25519 }, bytes: "010203" }"# ); - let unparsed_public_key = - agreement::UnparsedPublicKey::new(&agreement::X25519, Vec::from(bytes)); + let unparsed_public_key = UnparsedPublicKey::new(&X25519, Vec::from(bytes)); #[allow(clippy::redundant_clone)] let unparsed_pubkey_clone = unparsed_public_key.clone(); assert_eq!( @@ -926,115 +1154,4 @@ mod tests { r#"UnparsedPublicKey { algorithm: Algorithm { curve: Curve25519 }, bytes: "010203" }"# ); } - - #[test] - fn agreement_agree_ephemeral() { - let rng = rand::SystemRandom::new(); - - test::run( - test_file!("data/agreement_tests.txt"), - |section, test_case| { - assert_eq!(section, ""); - - let curve_name = test_case.consume_string("Curve"); - let alg = alg_from_curve_name(&curve_name); - let peer_public = - agreement::UnparsedPublicKey::new(alg, test_case.consume_bytes("PeerQ")); - - match test_case.consume_optional_string("Error") { - None => { - let my_private_bytes = test_case.consume_bytes("D"); - let my_private = { - let rng = test::rand::FixedSliceRandom { - bytes: &my_private_bytes, - }; - agreement::EphemeralPrivateKey::generate_for_test(alg, &rng)? - }; - let my_public = test_case.consume_bytes("MyQ"); - let output = test_case.consume_bytes("Output"); - - assert_eq!(my_private.algorithm(), alg); - - let computed_public = my_private.compute_public_key().unwrap(); - assert_eq!(computed_public.as_ref(), &my_public[..]); - - assert_eq!(my_private.algorithm(), alg); - - let result = agreement::agree_ephemeral( - my_private, - &peer_public, - (), - |key_material| { - assert_eq!(key_material, &output[..]); - Ok(()) - }, - ); - assert_eq!( - result, - Ok(()), - "Failed on private key: {:?}", - test::to_hex(my_private_bytes) - ); - } - - Some(_) => { - fn kdf_not_called(_: &[u8]) -> Result<(), ()> { - panic!( - "The KDF was called during ECDH when the peer's \ - public key is invalid." - ); - } - let dummy_private_key = - agreement::EphemeralPrivateKey::generate(alg, &rng)?; - assert!(agreement::agree_ephemeral( - dummy_private_key, - &peer_public, - (), - kdf_not_called - ) - .is_err()); - } - } - - Ok(()) - }, - ); - } - - fn h(s: &str) -> Vec { - match test::from_hex(s) { - Ok(v) => v, - Err(msg) => { - panic!("{msg} in {s}"); - } - } - } - - fn alg_from_curve_name(curve_name: &str) -> &'static agreement::Algorithm { - if curve_name == "P-256" { - &agreement::ECDH_P256 - } else if curve_name == "P-384" { - &agreement::ECDH_P384 - } else if curve_name == "P-521" { - &agreement::ECDH_P521 - } else if curve_name == "X25519" { - &agreement::X25519 - } else { - panic!("Unsupported curve: {curve_name}"); - } - } - - fn x25519(private_key: &[u8], public_key: &[u8]) -> Vec { - x25519_(private_key, public_key).unwrap() - } - - fn x25519_(private_key: &[u8], public_key: &[u8]) -> Result, Unspecified> { - let rng = test::rand::FixedSliceRandom { bytes: private_key }; - let private_key = - agreement::EphemeralPrivateKey::generate_for_test(&agreement::X25519, &rng)?; - let public_key = agreement::UnparsedPublicKey::new(&agreement::X25519, public_key); - agreement::agree_ephemeral(private_key, &public_key, Unspecified, |agreed_value| { - Ok(Vec::from(agreed_value)) - }) - } } diff --git a/aws-lc-rs/src/data/agreement_tests.txt b/aws-lc-rs/src/agreement/data/agreement_tests.txt similarity index 100% rename from aws-lc-rs/src/data/agreement_tests.txt rename to aws-lc-rs/src/agreement/data/agreement_tests.txt diff --git a/aws-lc-rs/src/agreement/ephemeral.rs b/aws-lc-rs/src/agreement/ephemeral.rs new file mode 100644 index 00000000000..88ec3b1190c --- /dev/null +++ b/aws-lc-rs/src/agreement/ephemeral.rs @@ -0,0 +1,462 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +use crate::agreement::{agree, Algorithm, PrivateKey, PublicKey, UnparsedPublicKey}; +use crate::error::Unspecified; +use crate::rand::SecureRandom; +use core::fmt; +use std::fmt::{Debug, Formatter}; + +/// An ephemeral private key for use (only) with `agree_ephemeral`. The +/// signature of `agree_ephemeral` ensures that an `PrivateKey` can be +/// used for at most one key agreement. +#[allow(clippy::module_name_repetitions)] +pub struct EphemeralPrivateKey(PrivateKey); + +impl Debug for EphemeralPrivateKey { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(&format!( + "EphemeralPrivateKey {{ algorithm: {:?} }}", + self.0.inner_key.algorithm() + )) + } +} + +impl EphemeralPrivateKey { + #[inline] + /// Generate a new ephemeral private key for the given algorithm. + /// + /// # *ring* Compatibility + /// Our implementation ignores the `SecureRandom` parameter. + /// + // # FIPS + // Use this function with one of the following algorithms: + // * `ECDH_P256` + // * `ECDH_P384` + // * `ECDH_P521` + // + /// # Errors + /// `error::Unspecified` when operation fails due to internal error. + pub fn generate(alg: &'static Algorithm, _rng: &dyn SecureRandom) -> Result { + Ok(Self(PrivateKey::generate(alg)?)) + } + + #[cfg(test)] + #[allow(clippy::missing_errors_doc)] + pub fn generate_for_test( + alg: &'static Algorithm, + rng: &dyn SecureRandom, + ) -> Result { + Ok(Self(PrivateKey::generate_for_test(alg, rng)?)) + } + + /// Computes the public key from the private key. + /// + /// # Errors + /// `error::Unspecified` when operation fails due to internal error. + pub fn compute_public_key(&self) -> Result { + self.0.compute_public_key() + } + + /// The algorithm for the private key. + #[inline] + #[must_use] + pub fn algorithm(&self) -> &'static Algorithm { + self.0.algorithm() + } +} + +/// Performs a key agreement with an ephemeral private key and the given public +/// key. +/// +/// `my_private_key` is the ephemeral private key to use. Since it is moved, it +/// will not be usable after calling `agree_ephemeral`, thus guaranteeing that +/// the key is used for only one key agreement. +/// +/// `peer_public_key` is the peer's public key. `agree_ephemeral` will return +/// `Err(error_value)` if it does not match `my_private_key's` algorithm/curve. +/// `agree_ephemeral` verifies that it is encoded in the standard form for the +/// algorithm and that the key is *valid*; see the algorithm's documentation for +/// details on how keys are to be encoded and what constitutes a valid key for +/// that algorithm. +/// +/// `error_value` is the value to return if an error occurs before `kdf` is +/// called, e.g. when decoding of the peer's public key fails or when the public +/// key is otherwise invalid. +/// +/// After the key agreement is done, `agree_ephemeral` calls `kdf` with the raw +/// key material from the key agreement operation and then returns what `kdf` +/// returns. +/// +// # FIPS +// Use this function with one of the following key algorithms: +// * `ECDH_P256` +// * `ECDH_P384` +// * `ECDH_P521` +// +/// # Errors +/// `error_value` on internal failure. +#[inline] +#[allow(clippy::needless_pass_by_value)] +#[allow(clippy::missing_panics_doc)] +#[allow(clippy::module_name_repetitions)] +pub fn agree_ephemeral, F, R, E>( + my_private_key: EphemeralPrivateKey, + peer_public_key: &UnparsedPublicKey, + error_value: E, + kdf: F, +) -> Result +where + F: FnOnce(&[u8]) -> Result, +{ + agree(&my_private_key.0, peer_public_key, error_value, kdf) +} + +#[cfg(test)] +mod tests { + use crate::error::Unspecified; + use crate::{agreement, rand, test, test_file}; + + #[test] + fn test_agreement_ecdh_x25519_rfc_iterated() { + fn expect_iterated_x25519( + expected_result: &str, + range: core::ops::Range, + k: &mut Vec, + u: &mut Vec, + ) { + for _ in range { + let new_k = x25519(k, u); + *u = k.clone(); + *k = new_k; + } + assert_eq!(&from_hex(expected_result), k); + } + + let mut k = from_hex("0900000000000000000000000000000000000000000000000000000000000000"); + let mut u = k.clone(); + + expect_iterated_x25519( + "422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079", + 0..1, + &mut k, + &mut u, + ); + expect_iterated_x25519( + "684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51", + 1..1_000, + &mut k, + &mut u, + ); + + // The spec gives a test vector for 1,000,000 iterations but it takes + // too long to do 1,000,000 iterations by default right now. This + // 10,000 iteration vector is self-computed. + expect_iterated_x25519( + "2c125a20f639d504a7703d2e223c79a79de48c4ee8c23379aa19a62ecd211815", + 1_000..10_000, + &mut k, + &mut u, + ); + + if cfg!(feature = "slow_tests") { + expect_iterated_x25519( + "7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424", + 10_000..1_000_000, + &mut k, + &mut u, + ); + } + } + + #[test] + fn test_agreement_x25519() { + let alg = &agreement::X25519; + let peer_public = agreement::UnparsedPublicKey::new( + alg, + test::from_dirty_hex( + "e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c", + ), + ); + + let my_private = test::from_dirty_hex( + "a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4", + ); + + let my_private = { + let rng = test::rand::FixedSliceRandom { bytes: &my_private }; + agreement::EphemeralPrivateKey::generate_for_test(alg, &rng).unwrap() + }; + + let my_public = test::from_dirty_hex( + "1c9fd88f45606d932a80c71824ae151d15d73e77de38e8e000852e614fae7019", + ); + let output = test::from_dirty_hex( + "c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552", + ); + + assert_eq!(my_private.algorithm(), alg); + + let computed_public = my_private.compute_public_key().unwrap(); + assert_eq!(computed_public.as_ref(), &my_public[..]); + + assert_eq!(computed_public.algorithm(), alg); + + let result = agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + + #[test] + fn test_agreement_ecdh_p256() { + let alg = &agreement::ECDH_P256; + let peer_public = agreement::UnparsedPublicKey::new( + alg, + test::from_dirty_hex( + "04D12DFB5289C8D4F81208B70270398C342296970A0BCCB74C736FC7554494BF6356FBF3CA366CC23E8157854C13C58D6AAC23F046ADA30F8353E74F33039872AB", + ), + ); + assert_eq!(peer_public.algorithm(), alg); + assert_eq!(peer_public.bytes(), &peer_public.bytes); + + let my_private = test::from_dirty_hex( + "C88F01F510D9AC3F70A292DAA2316DE544E9AAB8AFE84049C62A9C57862D1433", + ); + + let my_private = { + let rng = test::rand::FixedSliceRandom { bytes: &my_private }; + agreement::EphemeralPrivateKey::generate_for_test(alg, &rng).unwrap() + }; + + let my_public = test::from_dirty_hex( + "04DAD0B65394221CF9B051E1FECA5787D098DFE637FC90B9EF945D0C37725811805271A0461CDB8252D61F1C456FA3E59AB1F45B33ACCF5F58389E0577B8990BB3", + ); + let output = test::from_dirty_hex( + "D6840F6B42F6EDAFD13116E0E12565202FEF8E9ECE7DCE03812464D04B9442DE", + ); + + assert_eq!(my_private.algorithm(), alg); + + let computed_public = my_private.compute_public_key().unwrap(); + assert_eq!(computed_public.as_ref(), &my_public[..]); + + assert_eq!(computed_public.algorithm(), alg); + + let result = agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + + #[test] + fn test_agreement_ecdh_p384() { + let alg = &agreement::ECDH_P384; + let peer_public = agreement::UnparsedPublicKey::new( + alg, + test::from_dirty_hex( + "04E558DBEF53EECDE3D3FCCFC1AEA08A89A987475D12FD950D83CFA41732BC509D0D1AC43A0336DEF96FDA41D0774A3571DCFBEC7AACF3196472169E838430367F66EEBE3C6E70C416DD5F0C68759DD1FFF83FA40142209DFF5EAAD96DB9E6386C", + ), + ); + + let my_private = test::from_dirty_hex( + "099F3C7034D4A2C699884D73A375A67F7624EF7C6B3C0F160647B67414DCE655E35B538041E649EE3FAEF896783AB194", + ); + + let my_private = { + let rng = test::rand::FixedSliceRandom { bytes: &my_private }; + agreement::EphemeralPrivateKey::generate_for_test(alg, &rng).unwrap() + }; + + let my_public = test::from_dirty_hex( + "04667842D7D180AC2CDE6F74F37551F55755C7645C20EF73E31634FE72B4C55EE6DE3AC808ACB4BDB4C88732AEE95F41AA9482ED1FC0EEB9CAFC4984625CCFC23F65032149E0E144ADA024181535A0F38EEB9FCFF3C2C947DAE69B4C634573A81C", + ); + let output = test::from_dirty_hex( + "11187331C279962D93D604243FD592CB9D0A926F422E47187521287E7156C5C4D603135569B9E9D09CF5D4A270F59746", + ); + + assert_eq!(my_private.algorithm(), alg); + + let computed_public = my_private.compute_public_key().unwrap(); + assert_eq!(computed_public.as_ref(), &my_public[..]); + + assert_eq!(computed_public.algorithm(), alg); + + let result = agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + + #[test] + fn test_agreement_ecdh_p521() { + let alg = &agreement::ECDH_P521; + let peer_public = agreement::UnparsedPublicKey::new( + alg, + test::from_dirty_hex( + "0401a32099b02c0bd85371f60b0dd20890e6c7af048c8179890fda308b359dbbc2b7a832bb8c6526c4af99a7ea3f0b3cb96ae1eb7684132795c478ad6f962e4a6f446d017627357b39e9d7632a1370b3e93c1afb5c851b910eb4ead0c9d387df67cde85003e0e427552f1cd09059aad0262e235cce5fba8cedc4fdc1463da76dcd4b6d1a46", + ), + ); + + let my_private = test::from_dirty_hex( + "00df14b1f1432a7b0fb053965fd8643afee26b2451ecb6a8a53a655d5fbe16e4c64ce8647225eb11e7fdcb23627471dffc5c2523bd2ae89957cba3a57a23933e5a78", + ); + + let my_private = { + let rng = test::rand::FixedSliceRandom { bytes: &my_private }; + agreement::EphemeralPrivateKey::generate_for_test(alg, &rng).unwrap() + }; + + let my_public = test::from_dirty_hex( + "04004e8583bbbb2ecd93f0714c332dff5ab3bc6396e62f3c560229664329baa5138c3bb1c36428abd4e23d17fcb7a2cfcc224b2e734c8941f6f121722d7b6b9415457601cf0874f204b0363f020864672fadbf87c8811eb147758b254b74b14fae742159f0f671a018212bbf25b8519e126d4cad778cfff50d288fd39ceb0cac635b175ec0", + ); + let output = test::from_dirty_hex( + "01aaf24e5d47e4080c18c55ea35581cd8da30f1a079565045d2008d51b12d0abb4411cda7a0785b15d149ed301a3697062f42da237aa7f07e0af3fd00eb1800d9c41", + ); + + assert_eq!(my_private.algorithm(), alg); + + let computed_public = my_private.compute_public_key().unwrap(); + assert_eq!(computed_public.as_ref(), &my_public[..]); + + assert_eq!(computed_public.algorithm(), alg); + + let result = agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }); + assert_eq!(result, Ok(())); + } + + #[test] + fn agreement_traits() { + use crate::test; + + let rng = rand::SystemRandom::new(); + + let ephemeral_private_key = + agreement::EphemeralPrivateKey::generate_for_test(&agreement::ECDH_P256, &rng).unwrap(); + + test::compile_time_assert_send::(); + test::compile_time_assert_sync::(); + + assert_eq!( + format!("{:?}", &ephemeral_private_key), + "EphemeralPrivateKey { algorithm: Algorithm { curve: P256 } }" + ); + } + + #[test] + fn agreement_agree_ephemeral() { + let rng = rand::SystemRandom::new(); + + test::run( + test_file!("data/agreement_tests.txt"), + |section, test_case| { + assert_eq!(section, ""); + + let curve_name = test_case.consume_string("Curve"); + let alg = alg_from_curve_name(&curve_name); + let peer_public = + agreement::UnparsedPublicKey::new(alg, test_case.consume_bytes("PeerQ")); + + match test_case.consume_optional_string("Error") { + None => { + let my_private_bytes = test_case.consume_bytes("D"); + let my_private = { + let rng = test::rand::FixedSliceRandom { + bytes: &my_private_bytes, + }; + agreement::EphemeralPrivateKey::generate_for_test(alg, &rng)? + }; + let my_public = test_case.consume_bytes("MyQ"); + let output = test_case.consume_bytes("Output"); + + assert_eq!(my_private.algorithm(), alg); + + let computed_public = my_private.compute_public_key().unwrap(); + assert_eq!(computed_public.as_ref(), &my_public[..]); + + assert_eq!(my_private.algorithm(), alg); + + let result = agreement::agree_ephemeral( + my_private, + &peer_public, + (), + |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }, + ); + assert_eq!( + result, + Ok(()), + "Failed on private key: {:?}", + test::to_hex(my_private_bytes) + ); + } + + Some(_) => { + fn kdf_not_called(_: &[u8]) -> Result<(), ()> { + panic!( + "The KDF was called during ECDH when the peer's \ + public key is invalid." + ); + } + let dummy_private_key = + agreement::EphemeralPrivateKey::generate(alg, &rng)?; + assert!(agreement::agree_ephemeral( + dummy_private_key, + &peer_public, + (), + kdf_not_called + ) + .is_err()); + } + } + + Ok(()) + }, + ); + } + + fn from_hex(s: &str) -> Vec { + match test::from_hex(s) { + Ok(v) => v, + Err(msg) => { + panic!("{msg} in {s}"); + } + } + } + + fn alg_from_curve_name(curve_name: &str) -> &'static agreement::Algorithm { + if curve_name == "P-256" { + &agreement::ECDH_P256 + } else if curve_name == "P-384" { + &agreement::ECDH_P384 + } else if curve_name == "P-521" { + &agreement::ECDH_P521 + } else if curve_name == "X25519" { + &agreement::X25519 + } else { + panic!("Unsupported curve: {curve_name}"); + } + } + + fn x25519(private_key: &[u8], public_key: &[u8]) -> Vec { + try_x25519(private_key, public_key).unwrap() + } + + fn try_x25519(private_key: &[u8], public_key: &[u8]) -> Result, Unspecified> { + let rng = test::rand::FixedSliceRandom { bytes: private_key }; + let private_key = + agreement::EphemeralPrivateKey::generate_for_test(&agreement::X25519, &rng)?; + let public_key = agreement::UnparsedPublicKey::new(&agreement::X25519, public_key); + agreement::agree_ephemeral(private_key, &public_key, Unspecified, |agreed_value| { + Ok(Vec::from(agreed_value)) + }) + } +} diff --git a/aws-lc-rs/src/bn.rs b/aws-lc-rs/src/bn.rs index 90fe0a8cbc1..01a67532069 100644 --- a/aws-lc-rs/src/bn.rs +++ b/aws-lc-rs/src/bn.rs @@ -1,12 +1,20 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC -use crate::ptr::{ConstPointer, DetachableLcPtr}; +use crate::ptr::{ConstPointer, DetachableLcPtr, LcPtr}; use aws_lc::{BN_bin2bn, BN_bn2bin, BN_cmp, BN_new, BN_num_bits, BN_num_bytes, BN_set_u64, BIGNUM}; use mirai_annotations::unrecoverable; use std::cmp::Ordering; use std::ptr::null_mut; +impl TryFrom<&[u8]> for LcPtr { + type Error = (); + + fn try_from(bytes: &[u8]) -> Result { + unsafe { LcPtr::new(BN_bin2bn(bytes.as_ptr(), bytes.len(), null_mut())) } + } +} + impl TryFrom<&[u8]> for DetachableLcPtr { type Error = (); diff --git a/aws-lc-rs/src/ec.rs b/aws-lc-rs/src/ec.rs index 0d08a509d99..8aa58f3e235 100644 --- a/aws-lc-rs/src/ec.rs +++ b/aws-lc-rs/src/ec.rs @@ -8,7 +8,6 @@ use std::fmt::{Debug, Formatter}; use std::mem::MaybeUninit; use std::ops::Deref; use std::os::raw::{c_int, c_uint}; -#[cfg(test)] use std::ptr::null; use std::ptr::null_mut; @@ -19,23 +18,22 @@ use untrusted::Input; 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_KEY_set_private_key, EC_KEY_set_public_key, EC_POINT_mul, 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, + NID_secp384r1, NID_secp521r1, BIGNUM, ECDSA_SIG, EC_GROUP, EC_KEY, EC_POINT, EVP_PKEY, + EVP_PKEY_EC, }; use crate::buffer::Buffer; use crate::digest::digest_ctx::DigestContext; -use crate::encoding::AsDer; +use crate::encoding::{AsDer, EcPublicKeyX509Der}; use crate::error::{KeyRejected, Unspecified}; use crate::fips::indicator_check; use crate::ptr::{ConstPointer, DetachableLcPtr, LcPtr, Pointer}; @@ -112,6 +110,13 @@ impl AlgorithmID { AlgorithmID::ECDSA_P256K1 => NID_secp256k1, } } + pub(crate) fn private_key_size(&self) -> usize { + match self { + AlgorithmID::ECDSA_P256 | AlgorithmID::ECDSA_P256K1 => 32, + AlgorithmID::ECDSA_P384 => 48, + AlgorithmID::ECDSA_P521 => 66, + } + } } /// Elliptic curve public key. @@ -121,22 +126,14 @@ pub struct PublicKey { octets: Box<[u8]>, } -#[allow(clippy::module_name_repetitions)] -pub struct EcPublicKeyX509DerType { - _priv: (), -} -/// An elliptic curve public key as a DER-encoded (X509) `SubjectPublicKeyInfo` structure -#[allow(clippy::module_name_repetitions)] -pub type EcPublicKeyX509Der = Buffer<'static, EcPublicKeyX509DerType>; - -impl AsDer for PublicKey { +impl AsDer> for PublicKey { /// Provides the public key as a DER-encoded (X.509) `SubjectPublicKeyInfo` structure. /// # Errors /// Returns an error if the underlying implementation is unable to marshal the point. - fn as_der(&self) -> Result { - let ec_group = unsafe { LcPtr::new(EC_GROUP_new_by_curve_name(self.algorithm.id.nid()))? }; - let ec_point = unsafe { ec_point_from_bytes(&ec_group, self.as_ref())? }; - let ec_key = unsafe { LcPtr::new(EC_KEY_new())? }; + fn as_der(&self) -> Result, Unspecified> { + let ec_group = LcPtr::new(unsafe { EC_GROUP_new_by_curve_name(self.algorithm.id.nid()) })?; + let ec_point = ec_point_from_bytes(&ec_group, self.as_ref())?; + let ec_key = LcPtr::new(unsafe { EC_KEY_new() })?; if 1 != unsafe { EC_KEY_set_group(*ec_key, *ec_group) } { return Err(Unspecified); } @@ -269,34 +266,53 @@ fn evp_pkey_from_public_key( alg: &'static AlgorithmID, public_key: &[u8], ) -> Result, Unspecified> { - let ec_group = unsafe { ec_group_from_nid(alg.nid())? }; - let ec_point = unsafe { ec_point_from_bytes(&ec_group, public_key)? }; - let pkey = unsafe { evp_pkey_from_public_point(&ec_group, &ec_point)? }; + let ec_group = ec_group_from_nid(alg.nid())?; + let ec_point = ec_point_from_bytes(&ec_group, public_key)?; + let pkey = evp_pkey_from_public_point(&ec_group, &ec_point)?; Ok(pkey) } -#[inline] -unsafe fn validate_evp_key( - evp_pkey: &ConstPointer, +fn verify_ec_key_nid( + ec_key: &ConstPointer, expected_curve_nid: i32, ) -> Result<(), KeyRejected> { - let ec_key = ConstPointer::new(EVP_PKEY_get0_EC_KEY(**evp_pkey))?; - - let ec_group = ConstPointer::new(EC_KEY_get0_group(*ec_key))?; - let key_nid = EC_GROUP_get_curve_name(*ec_group); + let ec_group = ConstPointer::new(unsafe { EC_KEY_get0_group(**ec_key) })?; + let key_nid = unsafe { EC_GROUP_get_curve_name(*ec_group) }; if key_nid != expected_curve_nid { return Err(KeyRejected::wrong_algorithm()); } + Ok(()) +} + +#[inline] +#[cfg(not(feature = "fips"))] +pub(crate) fn verify_evp_key_nid( + evp_pkey: &ConstPointer, + expected_curve_nid: i32, +) -> Result<(), KeyRejected> { + let ec_key = ConstPointer::new(unsafe { EVP_PKEY_get0_EC_KEY(**evp_pkey) })?; + verify_ec_key_nid(&ec_key, expected_curve_nid)?; + + Ok(()) +} + +#[inline] +fn validate_evp_key( + evp_pkey: &ConstPointer, + expected_curve_nid: i32, +) -> Result<(), KeyRejected> { + let ec_key = ConstPointer::new(unsafe { EVP_PKEY_get0_EC_KEY(**evp_pkey) })?; + verify_ec_key_nid(&ec_key, expected_curve_nid)?; #[cfg(not(feature = "fips"))] - if 1 != EC_KEY_check_key(*ec_key) { + if 1 != unsafe { EC_KEY_check_key(*ec_key) } { return Err(KeyRejected::inconsistent_components()); } #[cfg(feature = "fips")] - if 1 != indicator_check!(EC_KEY_check_fips(*ec_key)) { + if 1 != indicator_check!(unsafe { EC_KEY_check_fips(*ec_key) }) { return Err(KeyRejected::inconsistent_components()); } @@ -304,12 +320,11 @@ unsafe fn validate_evp_key( } pub(crate) unsafe fn marshal_private_key_to_buffer( - alg_id: &'static AlgorithmID, + private_size: usize, evp_pkey: &ConstPointer, ) -> Result, Unspecified> { let ec_key = ConstPointer::new(EVP_PKEY_get0_EC_KEY(**evp_pkey))?; let private_bn = ConstPointer::new(EC_KEY_get0_private_key(*ec_key))?; - let private_size: usize = ecdsa_fixed_number_byte_size(alg_id); { let size: usize = BN_num_bytes(*private_bn).try_into()?; debug_assert!(size <= private_size); @@ -323,18 +338,44 @@ pub(crate) unsafe fn marshal_private_key_to_buffer( Ok(buffer) } +pub(crate) unsafe fn unmarshal_der_to_private_key( + key_bytes: &[u8], + nid: i32, +) -> Result, KeyRejected> { + let mut out = null_mut(); + // `d2i_PrivateKey` -> ... -> `EC_KEY_parse_private_key` -> `EC_KEY_check_key` + let evp_pkey = LcPtr::new(aws_lc::d2i_PrivateKey( + EVP_PKEY_EC, + &mut out, + &mut key_bytes.as_ptr(), + key_bytes + .len() + .try_into() + .map_err(|_| KeyRejected::too_large())?, + ))?; + #[cfg(not(feature = "fips"))] + verify_evp_key_nid(&evp_pkey.as_const(), nid)?; + #[cfg(feature = "fips")] + validate_evp_key(&evp_pkey.as_const(), nid)?; + + Ok(evp_pkey) +} + pub(crate) unsafe fn marshal_public_key_to_buffer( buffer: &mut [u8; PUBLIC_KEY_MAX_LEN], evp_pkey: &ConstPointer, ) -> Result { - let ec_key = EVP_PKEY_get0_EC_KEY(**evp_pkey); - if ec_key.is_null() { - return Err(Unspecified); - } + let ec_key = ConstPointer::new(EVP_PKEY_get0_EC_KEY(**evp_pkey))?; + marshal_ec_public_key_to_buffer(buffer, &ec_key) +} - let ec_group = ConstPointer::new(EC_KEY_get0_group(ec_key))?; +pub(crate) unsafe fn marshal_ec_public_key_to_buffer( + buffer: &mut [u8; PUBLIC_KEY_MAX_LEN], + ec_key: &ConstPointer, +) -> Result { + let ec_group = ConstPointer::new(EC_KEY_get0_group(**ec_key))?; - let ec_point = ConstPointer::new(EC_KEY_get0_public_key(ec_key))?; + let ec_point = ConstPointer::new(EC_KEY_get0_public_key(**ec_key))?; let out_len = ec_point_to_bytes(&ec_group, &ec_point, buffer)?; Ok(out_len) @@ -356,16 +397,16 @@ pub(crate) fn marshal_public_key( } #[inline] -pub(crate) unsafe fn evp_pkey_from_public_point( +pub(crate) fn evp_pkey_from_public_point( ec_group: &LcPtr, public_ec_point: &LcPtr, ) -> Result, Unspecified> { - let nid = EC_GROUP_get_curve_name(ec_group.as_const_ptr()); - let ec_key = DetachableLcPtr::new(EC_KEY_new())?; - if 1 != EC_KEY_set_group(*ec_key, **ec_group) { + let nid = unsafe { EC_GROUP_get_curve_name(ec_group.as_const_ptr()) }; + let ec_key = DetachableLcPtr::new(unsafe { EC_KEY_new() })?; + if 1 != unsafe { EC_KEY_set_group(*ec_key, **ec_group) } { return Err(Unspecified); } - if 1 != EC_KEY_set_public_key(*ec_key, **public_ec_point) { + if 1 != unsafe { EC_KEY_set_public_key(*ec_key, **public_ec_point) } { return Err(Unspecified); } @@ -382,33 +423,34 @@ pub(crate) unsafe fn evp_pkey_from_public_point( Ok(pkey) } -#[cfg(test)] -pub(crate) unsafe fn evp_pkey_from_private( +pub(crate) fn evp_pkey_from_private( ec_group: &ConstPointer, private_big_num: &ConstPointer, ) -> Result, Unspecified> { - let ec_key = DetachableLcPtr::new(EC_KEY_new())?; - if 1 != EC_KEY_set_group(*ec_key, **ec_group) { + let ec_key = DetachableLcPtr::new(unsafe { EC_KEY_new() })?; + if 1 != unsafe { EC_KEY_set_group(*ec_key, **ec_group) } { return Err(Unspecified); } - if 1 != EC_KEY_set_private_key(*ec_key, **private_big_num) { + if 1 != unsafe { EC_KEY_set_private_key(*ec_key, **private_big_num) } { return Err(Unspecified); } - let pub_key = LcPtr::new(EC_POINT_new(**ec_group))?; - if 1 != EC_POINT_mul( - **ec_group, - *pub_key, - **private_big_num, - null(), - null(), - null_mut(), - ) { + let pub_key = LcPtr::new(unsafe { EC_POINT_new(**ec_group) })?; + if 1 != unsafe { + EC_POINT_mul( + **ec_group, + *pub_key, + **private_big_num, + null(), + null(), + null_mut(), + ) + } { return Err(Unspecified); } - if 1 != EC_KEY_set_public_key(*ec_key, *pub_key) { + if 1 != unsafe { EC_KEY_set_public_key(*ec_key, *pub_key) } { return Err(Unspecified); } - let expected_curve_nid = EC_GROUP_get_curve_name(**ec_group); + let expected_curve_nid = unsafe { EC_GROUP_get_curve_name(**ec_group) }; let pkey = LcPtr::new(unsafe { EVP_PKEY_new() })?; @@ -447,17 +489,19 @@ pub(crate) fn evp_key_generate(nid: c_int) -> Result, Unspecifie } #[inline] -unsafe fn evp_key_from_public_private( +pub(crate) unsafe fn evp_key_from_public_private( ec_group: &LcPtr, - public_ec_point: &LcPtr, + public_ec_point: Option<&LcPtr>, private_bignum: &DetachableLcPtr, ) -> Result, KeyRejected> { let ec_key = DetachableLcPtr::new(EC_KEY_new())?; if 1 != EC_KEY_set_group(*ec_key, **ec_group) { return Err(KeyRejected::unexpected_error()); } - if 1 != EC_KEY_set_public_key(*ec_key, **public_ec_point) { - return Err(KeyRejected::unexpected_error()); + if let Some(ec_point) = public_ec_point { + if 1 != EC_KEY_set_public_key(*ec_key, **ec_point) { + return Err(KeyRejected::unexpected_error()); + } } if 1 != EC_KEY_set_private_key(*ec_key, **private_bignum) { return Err(KeyRejected::unexpected_error()); @@ -477,24 +521,26 @@ unsafe fn evp_key_from_public_private( } #[inline] -pub(crate) unsafe fn ec_group_from_nid(nid: i32) -> Result, ()> { - LcPtr::new(EC_GROUP_new_by_curve_name(nid)) +pub(crate) fn ec_group_from_nid(nid: i32) -> Result, ()> { + LcPtr::new(unsafe { EC_GROUP_new_by_curve_name(nid) }) } #[inline] -pub(crate) unsafe fn ec_point_from_bytes( +pub(crate) fn ec_point_from_bytes( ec_group: &LcPtr, bytes: &[u8], ) -> Result, Unspecified> { - let ec_point = LcPtr::new(EC_POINT_new(**ec_group))?; + let ec_point = LcPtr::new(unsafe { EC_POINT_new(**ec_group) })?; - if 1 != EC_POINT_oct2point( - **ec_group, - *ec_point, - bytes.as_ptr(), - bytes.len(), - null_mut(), - ) { + if 1 != unsafe { + EC_POINT_oct2point( + **ec_group, + *ec_point, + bytes.as_ptr(), + bytes.len(), + null_mut(), + ) + } { return Err(Unspecified); } @@ -526,7 +572,7 @@ unsafe fn ec_point_to_bytes( #[inline] fn ecdsa_asn1_to_fixed(alg_id: &'static AlgorithmID, sig: &[u8]) -> Result { - let expected_number_size = ecdsa_fixed_number_byte_size(alg_id); + let expected_number_size = alg_id.private_key_size(); let ecdsa_sig = LcPtr::new(unsafe { ECDSA_SIG_from_bytes(sig.as_ptr(), sig.len()) })?; @@ -552,26 +598,17 @@ fn ecdsa_asn1_to_fixed(alg_id: &'static AlgorithmID, sig: &[u8]) -> Result usize { - match alg_id { - AlgorithmID::ECDSA_P256 | AlgorithmID::ECDSA_P256K1 => 32, - AlgorithmID::ECDSA_P384 => 48, - AlgorithmID::ECDSA_P521 => 66, - } -} - #[inline] unsafe fn ecdsa_sig_from_fixed( alg_id: &'static AlgorithmID, signature: &[u8], ) -> Result, ()> { - let num_size_bytes = ecdsa_fixed_number_byte_size(alg_id); + let num_size_bytes = alg_id.private_key_size(); if signature.len() != 2 * num_size_bytes { return Err(()); } - let r_bn = DetachableLcPtr::try_from(&signature[..num_size_bytes])?; - let s_bn = DetachableLcPtr::try_from(&signature[num_size_bytes..])?; + let r_bn = DetachableLcPtr::::try_from(&signature[..num_size_bytes])?; + let s_bn = DetachableLcPtr::::try_from(&signature[num_size_bytes..])?; let ecdsa_sig = LcPtr::new(ECDSA_SIG_new())?; @@ -586,8 +623,7 @@ unsafe fn ecdsa_sig_from_fixed( #[cfg(test)] mod tests { - use crate::encoding::AsDer; - use crate::signature::EcPublicKeyX509Der; + use crate::encoding::{AsDer, EcPublicKeyX509Der}; use crate::signature::EcdsaKeyPair; use crate::signature::{KeyPair, ECDSA_P256_SHA256_FIXED_SIGNING}; use crate::test::from_dirty_hex; diff --git a/aws-lc-rs/src/ec/key_pair.rs b/aws-lc-rs/src/ec/key_pair.rs index a9cbfb6a166..c8ad5f18ce7 100644 --- a/aws-lc-rs/src/ec/key_pair.rs +++ b/aws-lc-rs/src/ec/key_pair.rs @@ -8,14 +8,17 @@ use std::fmt::{Debug, Formatter}; use std::mem::MaybeUninit; use std::ptr::{null, null_mut}; -use aws_lc::{EVP_DigestSign, EVP_DigestSignInit, EVP_PKEY_get0_EC_KEY, EVP_PKEY, EVP_PKEY_EC}; +use aws_lc::{EVP_DigestSign, EVP_DigestSignInit, EVP_PKEY_get0_EC_KEY, EVP_PKEY}; use crate::buffer::Buffer; use crate::digest::digest_ctx::DigestContext; -use crate::ec::{ - evp_key_generate, validate_evp_key, EcdsaSignatureFormat, EcdsaSigningAlgorithm, PublicKey, -}; -use crate::encoding::{AsBigEndian, AsDer}; +#[cfg(feature = "fips")] +use crate::ec::validate_evp_key; +#[cfg(not(feature = "fips"))] +use crate::ec::verify_evp_key_nid; +use crate::ec::{evp_key_generate, EcdsaSignatureFormat, EcdsaSigningAlgorithm, PublicKey}; + +use crate::encoding::{AsBigEndian, AsDer, EcPrivateKeyBin, EcPrivateKeyRfc5915Der}; use crate::error::{KeyRejected, Unspecified}; use crate::fips::indicator_check; use crate::pkcs8::{Document, Version}; @@ -54,7 +57,7 @@ impl KeyPair for EcdsaKeyPair { impl EcdsaKeyPair { #[allow(clippy::needless_pass_by_value)] - unsafe fn new( + fn new( algorithm: &'static EcdsaSigningAlgorithm, evp_pkey: LcPtr, ) -> Result { @@ -73,11 +76,9 @@ impl EcdsaKeyPair { /// `error::Unspecified` on internal error. /// pub fn generate(alg: &'static EcdsaSigningAlgorithm) -> Result { - unsafe { - let evp_pkey = evp_key_generate(alg.0.id.nid())?; + let evp_pkey = evp_key_generate(alg.0.id.nid())?; - Ok(Self::new(alg, evp_pkey)?) - } + Ok(Self::new(alg, evp_pkey)?) } /// Constructs an ECDSA key pair by parsing an unencrypted PKCS#8 v1 @@ -90,15 +91,17 @@ impl EcdsaKeyPair { alg: &'static EcdsaSigningAlgorithm, pkcs8: &[u8], ) -> Result { - unsafe { - let evp_pkey = LcPtr::try_from(pkcs8)?; + // Includes a call to `EC_KEY_check_key` + let evp_pkey = LcPtr::::try_from(pkcs8)?; - validate_evp_key(&evp_pkey.as_const(), alg.id.nid())?; + #[cfg(not(feature = "fips"))] + verify_evp_key_nid(&evp_pkey.as_const(), alg.id.nid())?; + #[cfg(feature = "fips")] + validate_evp_key(&evp_pkey.as_const(), alg.id.nid())?; - let key_pair = Self::new(alg, evp_pkey)?; + let key_pair = Self::new(alg, evp_pkey)?; - Ok(key_pair) - } + Ok(key_pair) } /// Generates a new key pair and returns the key pair serialized as a @@ -157,7 +160,7 @@ impl EcdsaKeyPair { .map_err(|_| KeyRejected::invalid_encoding())?; let private_bn = DetachableLcPtr::try_from(private_key)?; let evp_pkey = - ec::evp_key_from_public_private(&ec_group, &public_ec_point, &private_bn)?; + ec::evp_key_from_public_private(&ec_group, Some(&public_ec_point), &private_bn)?; let key_pair = Self::new(alg, evp_pkey)?; Ok(key_pair) @@ -180,27 +183,9 @@ impl EcdsaKeyPair { alg: &'static EcdsaSigningAlgorithm, private_key: &[u8], ) -> Result { - unsafe { - let mut out = std::ptr::null_mut(); - if aws_lc::d2i_PrivateKey( - EVP_PKEY_EC, - &mut out, - &mut private_key.as_ptr(), - private_key - .len() - .try_into() - .map_err(|_| KeyRejected::too_large())?, - ) - .is_null() - { - // FIXME: unclear which error or if we can get more detail - return Err(KeyRejected::unexpected_error()); - } - let evp_pkey = LcPtr::new(out)?; - validate_evp_key(&evp_pkey.as_const(), alg.id.nid())?; - - Ok(Self::new(alg, evp_pkey)?) - } + let evp_pkey = unsafe { ec::unmarshal_der_to_private_key(private_key, alg.id.nid())? }; + + Ok(Self::new(alg, evp_pkey)?) } /// Access functions related to the private key. @@ -305,29 +290,17 @@ impl Debug for PrivateKey<'_> { } } -pub struct EcPrivateKeyBinType { - _priv: (), -} -/// Elliptic curve private key data encoded as a big-endian fixed-length integer. -pub type EcPrivateKeyBin = Buffer<'static, EcPrivateKeyBinType>; - -pub struct EcPrivateKeyRfc5915DerType { - _priv: (), -} -/// Elliptic curve private key as a DER-encoded `ECPrivateKey` (RFC 5915) structure. -pub type EcPrivateKeyRfc5915Der = Buffer<'static, EcPrivateKeyRfc5915DerType>; - -impl AsBigEndian for PrivateKey<'_> { +impl AsBigEndian> for PrivateKey<'_> { /// Exposes the private key encoded as a big-endian fixed-length integer. /// /// For most use-cases, `EcdsaKeyPair::to_pkcs8()` should be preferred. /// /// # Errors /// `error::Unspecified` if serialization failed. - fn as_be_bytes(&self) -> Result { + fn as_be_bytes(&self) -> Result, Unspecified> { unsafe { let buffer = ec::marshal_private_key_to_buffer( - self.0.algorithm.id, + self.0.algorithm.id.private_key_size(), &self.0.evp_pkey.as_const(), )?; Ok(EcPrivateKeyBin::new(buffer)) @@ -335,12 +308,12 @@ impl AsBigEndian for PrivateKey<'_> { } } -impl AsDer for PrivateKey<'_> { +impl AsDer> for PrivateKey<'_> { /// Serializes the key as a DER-encoded `ECPrivateKey` (RFC 5915) structure. /// /// # Errors /// `error::Unspecified` if serialization failed. - fn as_der(&self) -> Result { + fn as_der(&self) -> Result, Unspecified> { unsafe { let mut outp = null_mut::(); let ec_key = ConstPointer::new(EVP_PKEY_get0_EC_KEY(*self.0.evp_pkey))?; diff --git a/aws-lc-rs/src/ed25519.rs b/aws-lc-rs/src/ed25519.rs index 7804a6cfa83..6d856a1ce04 100644 --- a/aws-lc-rs/src/ed25519.rs +++ b/aws-lc-rs/src/ed25519.rs @@ -18,8 +18,7 @@ use aws_lc::{ EVP_PKEY_keygen_init, EVP_PKEY_new_raw_private_key, EVP_PKEY, EVP_PKEY_ED25519, }; -use crate::buffer::Buffer; -use crate::encoding::AsBigEndian; +use crate::encoding::{AsBigEndian, Curve25519SeedBin}; use crate::error::{KeyRejected, Unspecified}; use crate::fips::indicator_check; use crate::pkcs8::{Document, Version}; @@ -106,24 +105,16 @@ impl Drop for Ed25519KeyPair { /// The seed value for the `EdDSA` signature scheme using Curve25519 pub struct Seed<'a>(&'a Ed25519KeyPair); -#[allow(clippy::module_name_repetitions)] -pub struct Ed25519SeedBufferType { - _priv: (), -} -/// Elliptic curve private key data encoded as a big-endian fixed-length integer. -#[allow(clippy::module_name_repetitions)] -pub type Ed25519SeedBin = Buffer<'static, Ed25519SeedBufferType>; - -impl AsBigEndian for Seed<'_> { +impl AsBigEndian> for Seed<'_> { /// Exposes the seed encoded as a big-endian fixed-length integer. /// /// For most use-cases, `EcdsaKeyPair::to_pkcs8()` should be preferred. /// /// # Errors /// `error::Unspecified` if serialization failed. - fn as_be_bytes(&self) -> Result { + fn as_be_bytes(&self) -> Result, Unspecified> { let buffer = Vec::from(&self.0.private_key[..ED25519_PRIVATE_KEY_SEED_LEN]); - Ok(Ed25519SeedBin::new(buffer)) + Ok(Curve25519SeedBin::new(buffer)) } } @@ -247,16 +238,16 @@ impl Ed25519KeyPair { /// `error::Unspecified` on internal error. /// pub fn to_pkcs8v1(&self) -> Result { - unsafe { - let evp_pkey: LcPtr = LcPtr::new(EVP_PKEY_new_raw_private_key( + let evp_pkey: LcPtr = LcPtr::new(unsafe { + EVP_PKEY_new_raw_private_key( EVP_PKEY_ED25519, null_mut(), self.private_key.as_ref().as_ptr(), ED25519_PRIVATE_KEY_SEED_LEN, - ))?; + ) + })?; - evp_pkey.marshall_private_key(Version::V1) - } + evp_pkey.marshall_private_key(Version::V1) } /// Constructs an Ed25519 key pair from the private key seed `seed` and its @@ -277,27 +268,27 @@ impl Ed25519KeyPair { return Err(KeyRejected::inconsistent_components()); } + let mut derived_public_key = MaybeUninit::<[u8; ED25519_PUBLIC_KEY_LEN]>::uninit(); + let mut private_key = MaybeUninit::<[u8; ED25519_PRIVATE_KEY_LEN]>::uninit(); unsafe { - let mut derived_public_key = MaybeUninit::<[u8; ED25519_PUBLIC_KEY_LEN]>::uninit(); - let mut private_key = MaybeUninit::<[u8; ED25519_PRIVATE_KEY_LEN]>::uninit(); ED25519_keypair_from_seed( derived_public_key.as_mut_ptr().cast(), private_key.as_mut_ptr().cast(), seed.as_ptr(), ); - let derived_public_key = derived_public_key.assume_init(); - let mut private_key = private_key.assume_init(); - - constant_time::verify_slices_are_equal(public_key, &derived_public_key) - .map_err(|_| KeyRejected::inconsistent_components())?; - - let key_pair = Self { - private_key: Box::new(private_key), - public_key: PublicKey(derived_public_key), - }; - private_key.zeroize(); - Ok(key_pair) } + let derived_public_key = unsafe { derived_public_key.assume_init() }; + let mut private_key = unsafe { private_key.assume_init() }; + + constant_time::verify_slices_are_equal(public_key, &derived_public_key) + .map_err(|_| KeyRejected::inconsistent_components())?; + + let key_pair = Self { + private_key: Box::new(private_key), + public_key: PublicKey(derived_public_key), + }; + private_key.zeroize(); + Ok(key_pair) } /// Constructs an Ed25519 key pair by parsing an unencrypted PKCS#8 v1 or v2 @@ -339,33 +330,34 @@ impl Ed25519KeyPair { } fn parse_pkcs8(pkcs8: &[u8]) -> Result { - unsafe { - let evp_pkey = LcPtr::try_from(pkcs8)?; + let evp_pkey = LcPtr::::try_from(pkcs8)?; - evp_pkey.validate_as_ed25519()?; + evp_pkey.validate_as_ed25519()?; - let mut private_key = [0u8; ED25519_PRIVATE_KEY_LEN]; - let mut out_len: usize = ED25519_PRIVATE_KEY_LEN; - if 1 != EVP_PKEY_get_raw_private_key(*evp_pkey, private_key.as_mut_ptr(), &mut out_len) - { - return Err(KeyRejected::wrong_algorithm()); - } + let mut private_key = [0u8; ED25519_PRIVATE_KEY_LEN]; + let mut out_len: usize = ED25519_PRIVATE_KEY_LEN; + if 1 != unsafe { + EVP_PKEY_get_raw_private_key(*evp_pkey, private_key.as_mut_ptr(), &mut out_len) + } { + return Err(KeyRejected::wrong_algorithm()); + } - let mut public_key = [0u8; ED25519_PUBLIC_KEY_LEN]; - let mut out_len: usize = ED25519_PUBLIC_KEY_LEN; - if 1 != EVP_PKEY_get_raw_public_key(*evp_pkey, public_key.as_mut_ptr(), &mut out_len) { - return Err(KeyRejected::wrong_algorithm()); - } - private_key[ED25519_PRIVATE_KEY_SEED_LEN..].copy_from_slice(&public_key); + let mut public_key = [0u8; ED25519_PUBLIC_KEY_LEN]; + let mut out_len: usize = ED25519_PUBLIC_KEY_LEN; + if 1 != unsafe { + EVP_PKEY_get_raw_public_key(*evp_pkey, public_key.as_mut_ptr(), &mut out_len) + } { + return Err(KeyRejected::wrong_algorithm()); + } + private_key[ED25519_PRIVATE_KEY_SEED_LEN..].copy_from_slice(&public_key); - let key_pair = Self { - private_key: Box::new(private_key), - public_key: PublicKey(public_key), - }; - private_key.zeroize(); + let key_pair = Self { + private_key: Box::new(private_key), + public_key: PublicKey(public_key), + }; + private_key.zeroize(); - Ok(key_pair) - } + Ok(key_pair) } /// Returns the signature of the message msg. diff --git a/aws-lc-rs/src/encoding.rs b/aws-lc-rs/src/encoding.rs new file mode 100644 index 00000000000..351aacbe006 --- /dev/null +++ b/aws-lc-rs/src/encoding.rs @@ -0,0 +1,58 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +//! Serialization formats + +use crate::buffer::Buffer; +use crate::encoding::types::{ + Curve25519SeedBufferType, EcPrivateKeyBinType, EcPrivateKeyRfc5915DerType, + EcPublicKeyX509DerType, +}; + +mod types { + pub struct EcPrivateKeyBinType { + _priv: (), + } + + pub struct EcPrivateKeyRfc5915DerType { + _priv: (), + } + + pub struct EcPublicKeyX509DerType { + _priv: (), + } + + pub struct Curve25519SeedBufferType { + _priv: (), + } +} + +/// Trait for types that can be serialized into a DER format. +pub trait AsDer { + /// Serializes into a DER format. + /// + /// # Errors + /// Returns Unspecified if serialization fails. + fn as_der(&self) -> Result; +} + +/// Trait for values that can be serialized into a big-endian format +pub trait AsBigEndian { + /// Serializes into a big-endian format. + /// + /// # Errors + /// Returns Unspecified if serialization fails. + fn as_be_bytes(&self) -> Result; +} + +/// Elliptic curve private key data encoded as a big-endian fixed-length integer. +pub type EcPrivateKeyBin<'a> = Buffer<'a, EcPrivateKeyBinType>; + +/// Elliptic curve private key as a DER-encoded `ECPrivateKey` (RFC 5915) structure. +pub type EcPrivateKeyRfc5915Der<'a> = Buffer<'a, EcPrivateKeyRfc5915DerType>; + +/// An elliptic curve public key as a DER-encoded (X509) `SubjectPublicKeyInfo` structure +pub type EcPublicKeyX509Der<'a> = Buffer<'a, EcPublicKeyX509DerType>; + +/// Elliptic curve private key data encoded as a big-endian fixed-length integer. +pub type Curve25519SeedBin<'a> = Buffer<'a, Curve25519SeedBufferType>; diff --git a/aws-lc-rs/src/evp_pkey.rs b/aws-lc-rs/src/evp_pkey.rs index ee71169d6d7..3a82abd852d 100644 --- a/aws-lc-rs/src/evp_pkey.rs +++ b/aws-lc-rs/src/evp_pkey.rs @@ -21,7 +21,7 @@ impl TryFrom<&[u8]> for LcPtr { fn try_from(bytes: &[u8]) -> Result { unsafe { let mut cbs = cbs::build_CBS(bytes); - + // `EVP_parse_private_key` -> ... -> `eckey_priv_decode` -> ... -> `EC_KEY_check_key` LcPtr::new(EVP_parse_private_key(&mut cbs)) .map_err(|()| KeyRejected::invalid_encoding()) } diff --git a/aws-lc-rs/src/lib.rs b/aws-lc-rs/src/lib.rs index 907ad335214..fd329963b53 100644 --- a/aws-lc-rs/src/lib.rs +++ b/aws-lc-rs/src/lib.rs @@ -133,6 +133,7 @@ pub mod cipher; mod debug; mod ec; mod ed25519; +pub mod encoding; mod endian; mod evp_pkey; mod fips; @@ -214,26 +215,6 @@ mod sealed { // ``` pub trait Sealed {} } -/// Serialization formats -pub mod encoding { - /// Trait for structs that can be serialized into a DER format. - pub trait AsDer { - /// Serializes into a DER format. - /// - /// # Errors - /// Returns Unspecified if serialization fails. - fn as_der(&self) -> Result; - } - - /// Trait for values that can be serialized into a big-endian format - pub trait AsBigEndian { - /// Serializes into a big-endian format. - /// - /// # Errors - /// Returns Unspecified if serialization fails. - fn as_be_bytes(&self) -> Result; - } -} #[cfg(test)] mod tests { diff --git a/aws-lc-rs/src/signature.rs b/aws-lc-rs/src/signature.rs index 67c92c4646a..3a6c986c73b 100644 --- a/aws-lc-rs/src/signature.rs +++ b/aws-lc-rs/src/signature.rs @@ -258,11 +258,10 @@ use crate::rsa::{ pub use crate::ec::key_pair::{EcdsaKeyPair, PrivateKey as EcdsaPrivateKey}; use crate::ec::EcdsaSignatureFormat; pub use crate::ec::{ - key_pair::EcPrivateKeyBin, key_pair::EcPrivateKeyRfc5915Der, EcPublicKeyX509Der, EcdsaSigningAlgorithm, EcdsaVerificationAlgorithm, PublicKey as EcdsaPublicKey, }; pub use crate::ed25519::{ - Ed25519KeyPair, Ed25519SeedBin, EdDSAParameters, Seed as Ed25519Seed, ED25519_PUBLIC_KEY_LEN, + Ed25519KeyPair, EdDSAParameters, Seed as Ed25519Seed, ED25519_PUBLIC_KEY_LEN, }; use crate::rsa; use crate::{digest, ec, error, hex, sealed}; diff --git a/aws-lc-rs/tests/ecdsa_tests.rs b/aws-lc-rs/tests/ecdsa_tests.rs index 48939d39f63..ec6f6be058f 100644 --- a/aws-lc-rs/tests/ecdsa_tests.rs +++ b/aws-lc-rs/tests/ecdsa_tests.rs @@ -3,8 +3,7 @@ // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC -use aws_lc_rs::encoding::AsBigEndian; -use aws_lc_rs::signature::EcPrivateKeyRfc5915Der; +use aws_lc_rs::encoding::{AsBigEndian, EcPrivateKeyRfc5915Der}; use aws_lc_rs::{ encoding::AsDer, rand::SystemRandom, diff --git a/aws-lc-rs/tests/ed25519_tests.rs b/aws-lc-rs/tests/ed25519_tests.rs index 50c1c820851..0dcda20ac42 100644 --- a/aws-lc-rs/tests/ed25519_tests.rs +++ b/aws-lc-rs/tests/ed25519_tests.rs @@ -3,9 +3,8 @@ // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC -use aws_lc_rs::encoding::AsBigEndian; +use aws_lc_rs::encoding::{AsBigEndian, Curve25519SeedBin}; use aws_lc_rs::rand::SystemRandom; -use aws_lc_rs::signature::Ed25519SeedBin; use aws_lc_rs::{ error, signature::{self, Ed25519KeyPair, KeyPair}, @@ -224,7 +223,7 @@ fn test_seed() { let key_pair = Ed25519KeyPair::from_pkcs8(key_pair_doc.as_ref()).unwrap(); let seed = key_pair.seed().unwrap(); - let seed_buffer: Ed25519SeedBin = seed.as_be_bytes().unwrap(); + let seed_buffer: Curve25519SeedBin = seed.as_be_bytes().unwrap(); let pub_key = key_pair.public_key();