diff --git a/aws-lc-rs/src/agreement.rs b/aws-lc-rs/src/agreement.rs index 61a3876ea4e..89f33b18b0a 100644 --- a/aws-lc-rs/src/agreement.rs +++ b/aws-lc-rs/src/agreement.rs @@ -55,30 +55,29 @@ use crate::ec::encoding::sec1::{ marshal_sec1_private_key, marshal_sec1_public_point, marshal_sec1_public_point_into_buffer, parse_sec1_private_bn, }; +use crate::ec::evp_key_generate; #[cfg(feature = "fips")] use crate::ec::validate_ec_evp_key; #[cfg(not(feature = "fips"))] use crate::ec::verify_evp_key_nid; -use crate::ec::{encoding, evp_key_generate}; use crate::error::{KeyRejected, Unspecified}; use crate::hex; pub use ephemeral::{agree_ephemeral, EphemeralPrivateKey}; use crate::aws_lc::{ - EVP_PKEY_derive, EVP_PKEY_derive_init, EVP_PKEY_derive_set_peer, EVP_PKEY_get0_EC_KEY, - NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1, EVP_PKEY, EVP_PKEY_EC, EVP_PKEY_X25519, - NID_X25519, + i2d_ECPrivateKey, EVP_PKEY_get0_EC_KEY, NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1, + EVP_PKEY, EVP_PKEY_EC, EVP_PKEY_X25519, NID_X25519, }; use crate::buffer::Buffer; use crate::ec; +use crate::ec::encoding::parse_ec_public_key; use crate::ec::encoding::rfc5915::parse_rfc5915_private_key; use crate::encoding::{ AsBigEndian, AsDer, Curve25519SeedBin, EcPrivateKeyBin, EcPrivateKeyRfc5915Der, EcPublicKeyCompressedBin, EcPublicKeyUncompressedBin, Pkcs8V1Der, PublicKeyX509Der, }; use crate::evp_pkey::No_EVP_PKEY_CTX_consumer; -use crate::fips::indicator_check; use crate::pkcs8::Version; use crate::ptr::LcPtr; use core::fmt; @@ -455,7 +454,7 @@ impl AsDer> for PrivateKey { |evp_pkey| EVP_PKEY_get0_EC_KEY(*evp_pkey.as_const()) })? }; - let length = usize::try_from(unsafe { aws_lc::i2d_ECPrivateKey(*ec_key, &mut outp) }) + let length = usize::try_from(unsafe { i2d_ECPrivateKey(*ec_key, &mut outp) }) .map_err(|_| Unspecified)?; let mut outp = LcPtr::new(outp)?; Ok(EcPrivateKeyRfc5915Der::take_from_slice(unsafe { @@ -665,6 +664,118 @@ impl> UnparsedPublicKey { } } +#[derive(Debug)] +/// TODO +pub struct ParsedPublicKey { + format: ParsedPublicKeyFormat, + nid: i32, + key: LcPtr, +} + +unsafe impl Send for ParsedPublicKey {} +unsafe impl Sync for ParsedPublicKey {} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +/// The format of a parsed public key. +/// +/// This is used to distinguish between different types of public key formats +/// supported by *aws-lc-rs*. +#[non_exhaustive] +pub enum ParsedPublicKeyFormat { + /// The key is in an X.509 SubjectPublicKeyInfo format. + X509, + /// The key is in an uncompressed form (X9.62). + Uncompressed, + /// The key is in a compressed form (SEC 1: Elliptic Curve Cryptography, Version 2.0). + Compressed, + /// The key is in a raw form. + Raw, +} + +/// A parsed public key for key agreement. +impl ParsedPublicKey { + fn nid(&self) -> i32 { + self.nid + } + + #[allow(unused)] + fn format(&self) -> ParsedPublicKeyFormat { + self.format + } + + pub(crate) fn key(&self) -> &LcPtr { + &self.key + } + + /// The algorithm of the public key. + #[must_use] + #[allow(non_upper_case_globals)] + pub fn alg(&self) -> &'static Algorithm { + match self.nid() { + NID_X25519 => &X25519, + NID_X9_62_prime256v1 => &ECDH_P256, + NID_secp384r1 => &ECDH_P384, + NID_secp521r1 => &ECDH_P521, + _ => unreachable!("Unreachable agreement algorithm nid: {}", self.nid()), + } + } +} + +impl ParsedPublicKey { + #[allow(non_upper_case_globals)] + pub(crate) fn new(bytes: impl AsRef<[u8]>, nid: i32) -> Result { + let key_bytes = bytes.as_ref(); + if key_bytes.is_empty() { + return Err(KeyRejected::unspecified()); + } + let format = match key_bytes[0] { + 0x04 => ParsedPublicKeyFormat::Uncompressed, + 0x02 | 0x03 => ParsedPublicKeyFormat::Compressed, + _ => ParsedPublicKeyFormat::X509, + }; + match nid { + NID_X25519 => { + let pub_key = try_parse_x25519_public_key_bytes(key_bytes)?; + Ok(ParsedPublicKey { + format, + nid, + key: pub_key, + }) + } + NID_X9_62_prime256v1 | NID_secp384r1 | NID_secp521r1 => { + let pub_key = parse_ec_public_key(key_bytes, nid)?; + Ok(ParsedPublicKey { + format, + nid, + key: pub_key, + }) + } + _ => Err(KeyRejected::unspecified()), + } + } +} + +impl> UnparsedPublicKey { + #[allow(dead_code)] + fn parse(&self) -> Result { + ParsedPublicKey::new(&self.bytes, self.alg.id.nid()) + } +} + +impl> TryFrom<&UnparsedPublicKey> for ParsedPublicKey { + type Error = KeyRejected; + fn try_from(upk: &UnparsedPublicKey) -> Result { + upk.parse() + } +} + +impl> TryFrom> for ParsedPublicKey { + type Error = KeyRejected; + fn try_from(upk: UnparsedPublicKey) -> Result { + upk.parse() + } +} + /// Performs a key agreement with a private key and the given public key. /// /// `my_private_key` is the private key to use. Only a reference to the key @@ -694,9 +805,9 @@ impl> UnparsedPublicKey { /// `error_value` on internal failure. #[inline] #[allow(clippy::missing_panics_doc)] -pub fn agree, F, R, E>( +pub fn agree, F, R, E>( my_private_key: &PrivateKey, - peer_public_key: &UnparsedPublicKey, + peer_public_key: B, error_value: E, kdf: F, ) -> Result @@ -704,115 +815,39 @@ where F: FnOnce(&[u8]) -> Result, { let expected_alg = my_private_key.algorithm(); - 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(); + let parse_result = peer_public_key.try_into(); - 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))? + if let Ok(peer_pub_key) = parse_result { + if peer_pub_key.alg() != expected_alg { + return Err(error_value); } - }; - kdf(secret) -} - -// Current max secret length is P-521's. -const MAX_AGREEMENT_SECRET_LEN: usize = AlgorithmID::ECDH_P521.private_key_len(); - -#[inline] -#[allow(clippy::needless_pass_by_value)] -fn ec_key_ecdh<'a>( - buffer: &'a mut [u8; MAX_AGREEMENT_SECRET_LEN], - priv_key: &LcPtr, - peer_pub_key_bytes: &[u8], - nid: i32, -) -> Result<&'a [u8], Unspecified> { - let mut pub_key = encoding::parse_ec_public_key(peer_pub_key_bytes, nid)?; - - let mut pkey_ctx = priv_key.create_EVP_PKEY_CTX()?; - - if 1 != unsafe { EVP_PKEY_derive_init(*pkey_ctx.as_mut()) } { - return Err(Unspecified); - } - - if 1 != unsafe { EVP_PKEY_derive_set_peer(*pkey_ctx.as_mut(), *pub_key.as_mut()) } { - return Err(Unspecified); + let secret = my_private_key + .inner_key + .get_evp_pkey() + .agree(peer_pub_key.key()) + .or(Err(error_value))?; + + kdf(secret.as_ref()) + } else { + Err(error_value) } - - let mut out_key_len = buffer.len(); - - if 1 != indicator_check!(unsafe { - EVP_PKEY_derive(*pkey_ctx.as_mut(), buffer.as_mut_ptr(), &mut out_key_len) - }) { - return Err(Unspecified); - } - - if 0 == out_key_len { - return Err(Unspecified); - } - - Ok(&buffer[0..out_key_len]) -} - -#[inline] -fn x25519_diffie_hellman<'a>( - buffer: &'a mut [u8; MAX_AGREEMENT_SECRET_LEN], - priv_key: &LcPtr, - peer_pub_key: &[u8], -) -> Result<&'a [u8], ()> { - let mut pkey_ctx = priv_key.create_EVP_PKEY_CTX()?; - - if 1 != unsafe { EVP_PKEY_derive_init(*pkey_ctx.as_mut()) } { - return Err(()); - } - - let mut pub_key = try_parse_x25519_public_key_bytes(peer_pub_key)?; - - if 1 != unsafe { EVP_PKEY_derive_set_peer(*pkey_ctx.as_mut(), *pub_key.as_mut()) } { - return Err(()); - } - - let mut out_key_len = buffer.len(); - - if 1 != indicator_check!(unsafe { - EVP_PKEY_derive(*pkey_ctx.as_mut(), buffer.as_mut_ptr(), &mut out_key_len) - }) { - return Err(()); - } - - debug_assert!(out_key_len == AlgorithmID::X25519.pub_key_len()); - - Ok(&buffer[0..AlgorithmID::X25519.pub_key_len()]) } pub(crate) fn try_parse_x25519_public_key_bytes( key_bytes: &[u8], -) -> Result, Unspecified> { +) -> Result, KeyRejected> { LcPtr::::parse_rfc5280_public_key(key_bytes, EVP_PKEY_X25519) .or(try_parse_x25519_public_key_raw_bytes(key_bytes)) } -fn try_parse_x25519_public_key_raw_bytes(key_bytes: &[u8]) -> Result, Unspecified> { +fn try_parse_x25519_public_key_raw_bytes(key_bytes: &[u8]) -> Result, KeyRejected> { let expected_pub_key_len = X25519.id.pub_key_len(); if key_bytes.len() != expected_pub_key_len { - return Err(Unspecified); + return Err(KeyRejected::invalid_encoding()); } - Ok(LcPtr::::parse_raw_public_key( - key_bytes, - EVP_PKEY_X25519, - )?) + LcPtr::::parse_raw_public_key(key_bytes, EVP_PKEY_X25519) } #[cfg(test)] @@ -893,7 +928,7 @@ mod tests { assert!(PrivateKey::from_private_key_der(alg, test_key).is_err()); assert!(agree( my_private_key, - &UnparsedPublicKey::new(alg, test_key), + UnparsedPublicKey::new(alg, test_key), (), |_| Ok(()) ) diff --git a/aws-lc-rs/src/agreement/ephemeral.rs b/aws-lc-rs/src/agreement/ephemeral.rs index c52b00ad1e1..41e35b8ec3e 100644 --- a/aws-lc-rs/src/agreement/ephemeral.rs +++ b/aws-lc-rs/src/agreement/ephemeral.rs @@ -1,7 +1,7 @@ // 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::agreement::{agree, Algorithm, ParsedPublicKey, PrivateKey, PublicKey}; use crate::error::Unspecified; use crate::rand::SecureRandom; use core::fmt; @@ -98,9 +98,9 @@ impl EphemeralPrivateKey { #[allow(clippy::needless_pass_by_value)] #[allow(clippy::missing_panics_doc)] #[allow(clippy::module_name_repetitions)] -pub fn agree_ephemeral, F, R, E>( +pub fn agree_ephemeral, F, R, E>( my_private_key: EphemeralPrivateKey, - peer_public_key: &UnparsedPublicKey, + peer_public_key: B, error_value: E, kdf: F, ) -> Result @@ -496,7 +496,7 @@ mod tests { 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| { + agreement::agree_ephemeral(private_key, public_key, Unspecified, |agreed_value| { Ok(Vec::from(agreed_value)) }) } diff --git a/aws-lc-rs/src/ec/signature.rs b/aws-lc-rs/src/ec/signature.rs index 3044c32a35a..a246980ef00 100644 --- a/aws-lc-rs/src/ec/signature.rs +++ b/aws-lc-rs/src/ec/signature.rs @@ -6,6 +6,7 @@ use crate::aws_lc::{ NID_secp384r1, NID_secp521r1, BIGNUM, ECDSA_SIG, EVP_PKEY, }; +use crate::digest::Digest; use crate::ec::compressed_public_key_size_bytes; use crate::ec::encoding::parse_ec_public_key; use crate::ec::encoding::sec1::marshal_sec1_public_point; @@ -15,7 +16,7 @@ use crate::encoding::{ use crate::error::Unspecified; use crate::evp_pkey::No_EVP_PKEY_CTX_consumer; use crate::ptr::{DetachableLcPtr, LcPtr}; -use crate::signature::VerificationAlgorithm; +use crate::signature::{ParsedPublicKey, ParsedVerificationAlgorithm, VerificationAlgorithm}; use crate::{digest, sealed}; use core::fmt; use core::fmt::{Debug, Formatter}; @@ -192,36 +193,59 @@ impl VerificationAlgorithm for EcdsaVerificationAlgorithm { public_key: &[u8], msg: &[u8], signature: &[u8], + ) -> Result<(), Unspecified> { + let public_key = parse_ec_public_key(public_key, self.id.nid())?; + self.verify_ecdsa(msg, signature, &public_key) + } + + fn verify_digest_sig( + &self, + public_key: &[u8], + digest: &digest::Digest, + signature: &[u8], + ) -> Result<(), Unspecified> { + let public_key = parse_ec_public_key(public_key, self.id.nid())?; + + self.verify_digest_ecdsa(digest, signature, &public_key) + } +} + +impl EcdsaVerificationAlgorithm { + fn verify_ecdsa( + &self, + msg: &[u8], + signature: &[u8], + public_key: &LcPtr, ) -> Result<(), Unspecified> { match self.sig_format { EcdsaSignatureFormat::ASN1 => { - verify_asn1_signature(self.id, self.digest, public_key, msg, signature) + verify_asn1_signature(self.digest, public_key, msg, signature) } EcdsaSignatureFormat::Fixed => { let (out_bytes, out_bytes_len) = convert_fixed_signature(self.id, signature)?; - verify_asn1_signature(self.id, self.digest, public_key, msg, unsafe { + verify_asn1_signature(self.digest, public_key, msg, unsafe { out_bytes.as_slice(out_bytes_len) }) } } } - fn verify_digest_sig( + fn verify_digest_ecdsa( &self, - public_key: &[u8], - digest: &digest::Digest, + digest: &Digest, signature: &[u8], + public_key: &LcPtr, ) -> Result<(), Unspecified> { if self.digest != digest.algorithm() { return Err(Unspecified); } match self.sig_format { EcdsaSignatureFormat::ASN1 => { - verify_asn1_digest_signature(self.id, digest, public_key, signature) + verify_asn1_digest_signature(digest, public_key, signature) } EcdsaSignatureFormat::Fixed => { let (out_bytes, out_bytes_len) = convert_fixed_signature(self.id, signature)?; - verify_asn1_digest_signature(self.id, digest, public_key, unsafe { + verify_asn1_digest_signature(digest, public_key, unsafe { out_bytes.as_slice(out_bytes_len) }) } @@ -229,6 +253,26 @@ impl VerificationAlgorithm for EcdsaVerificationAlgorithm { } } +impl ParsedVerificationAlgorithm for EcdsaVerificationAlgorithm { + fn parsed_verify_sig( + &self, + public_key: &ParsedPublicKey, + msg: &[u8], + signature: &[u8], + ) -> Result<(), Unspecified> { + self.verify_ecdsa(msg, signature, public_key.key()) + } + + fn parsed_verify_digest_sig( + &self, + public_key: &ParsedPublicKey, + digest: &Digest, + signature: &[u8], + ) -> Result<(), Unspecified> { + self.verify_digest_ecdsa(digest, signature, public_key.key()) + } +} + fn convert_fixed_signature( alg: &'static AlgorithmID, signature: &[u8], @@ -247,24 +291,20 @@ fn convert_fixed_signature( } fn verify_asn1_signature( - alg: &'static AlgorithmID, digest_alg: &'static digest::Algorithm, - public_key: &[u8], + public_key: &LcPtr, msg: &[u8], signature: &[u8], ) -> Result<(), Unspecified> { - let evp_pkey = parse_ec_public_key(public_key, alg.nid())?; - evp_pkey.verify(msg, Some(digest_alg), No_EVP_PKEY_CTX_consumer, signature) + public_key.verify(msg, Some(digest_alg), No_EVP_PKEY_CTX_consumer, signature) } fn verify_asn1_digest_signature( - alg: &'static AlgorithmID, - digest: &digest::Digest, - public_key: &[u8], + digest: &Digest, + public_key: &LcPtr, signature: &[u8], ) -> Result<(), Unspecified> { - let evp_pkey = parse_ec_public_key(public_key, alg.nid())?; - evp_pkey.verify_digest_sig(digest, No_EVP_PKEY_CTX_consumer, signature) + public_key.verify_digest_sig(digest, No_EVP_PKEY_CTX_consumer, signature) } #[inline] diff --git a/aws-lc-rs/src/ed25519.rs b/aws-lc-rs/src/ed25519.rs index 19469c1f60d..635237f9944 100644 --- a/aws-lc-rs/src/ed25519.rs +++ b/aws-lc-rs/src/ed25519.rs @@ -13,6 +13,7 @@ use untrusted::Input; use crate::aws_lc::{EVP_PKEY, EVP_PKEY_ED25519}; use crate::buffer::Buffer; +use crate::digest::Digest; use crate::encoding::{ AsBigEndian, AsDer, Curve25519SeedBin, Pkcs8V1Der, Pkcs8V2Der, PublicKeyX509Der, }; @@ -21,7 +22,9 @@ use crate::evp_pkey::No_EVP_PKEY_CTX_consumer; use crate::pkcs8::{Document, Version}; use crate::ptr::LcPtr; use crate::rand::SecureRandom; -use crate::signature::{KeyPair, Signature, VerificationAlgorithm}; +use crate::signature::{ + KeyPair, ParsedPublicKey, ParsedVerificationAlgorithm, Signature, VerificationAlgorithm, +}; use crate::{constant_time, digest, hex, sealed}; /// The length of an Ed25519 public key. @@ -35,6 +38,28 @@ pub struct EdDSAParameters; impl sealed::Sealed for EdDSAParameters {} +impl ParsedVerificationAlgorithm for EdDSAParameters { + fn parsed_verify_sig( + &self, + public_key: &ParsedPublicKey, + msg: &[u8], + signature: &[u8], + ) -> Result<(), Unspecified> { + public_key + .key() + .verify(msg, None, No_EVP_PKEY_CTX_consumer, signature) + } + + fn parsed_verify_digest_sig( + &self, + _public_key: &ParsedPublicKey, + _digest: &Digest, + _signature: &[u8], + ) -> Result<(), Unspecified> { + Err(Unspecified) + } +} + impl VerificationAlgorithm for EdDSAParameters { #[inline] #[cfg(feature = "ring-sig-verify")] @@ -44,11 +69,9 @@ impl VerificationAlgorithm for EdDSAParameters { msg: Input<'_>, signature: Input<'_>, ) -> Result<(), Unspecified> { - let evp_pkey = try_ed25519_public_key_from_bytes(public_key.as_slice_less_safe())?; - evp_pkey.verify( + self.verify_sig( + public_key.as_slice_less_safe(), msg.as_slice_less_safe(), - None, - No_EVP_PKEY_CTX_consumer, signature.as_slice_less_safe(), ) } @@ -63,7 +86,7 @@ impl VerificationAlgorithm for EdDSAParameters { msg: &[u8], signature: &[u8], ) -> Result<(), Unspecified> { - let evp_pkey = try_ed25519_public_key_from_bytes(public_key)?; + let evp_pkey = parse_ed25519_public_key(public_key)?; evp_pkey.verify(msg, None, No_EVP_PKEY_CTX_consumer, signature) } @@ -81,13 +104,14 @@ impl VerificationAlgorithm for EdDSAParameters { } } -fn try_ed25519_public_key_from_bytes(key_bytes: &[u8]) -> Result, KeyRejected> { +pub(crate) fn parse_ed25519_public_key(key_bytes: &[u8]) -> Result, KeyRejected> { // If the length of key bytes matches the raw public key size then it has to be that if key_bytes.len() == ED25519_PUBLIC_KEY_LEN { - return LcPtr::::parse_raw_public_key(key_bytes, EVP_PKEY_ED25519); + LcPtr::::parse_raw_public_key(key_bytes, EVP_PKEY_ED25519) + } else { + // Otherwise we support X.509 SubjectPublicKeyInfo formatted keys which are inherently larger + LcPtr::::parse_rfc5280_public_key(key_bytes, EVP_PKEY_ED25519) } - // Otherwise we support X.509 SubjectPublicKeyInfo formatted keys which are inherently larger - LcPtr::::parse_rfc5280_public_key(key_bytes, EVP_PKEY_ED25519) } /// An Ed25519 key pair, for signing. diff --git a/aws-lc-rs/src/evp_pkey.rs b/aws-lc-rs/src/evp_pkey.rs index cb2d051c7bb..d8daf2ac5b7 100644 --- a/aws-lc-rs/src/evp_pkey.rs +++ b/aws-lc-rs/src/evp_pkey.rs @@ -3,7 +3,8 @@ use crate::aws_lc::{ EVP_DigestSign, EVP_DigestSignInit, EVP_DigestVerify, EVP_DigestVerifyInit, EVP_PKEY_CTX_new, - EVP_PKEY_CTX_new_id, EVP_PKEY_bits, EVP_PKEY_cmp, EVP_PKEY_get0_EC_KEY, EVP_PKEY_get0_RSA, + EVP_PKEY_CTX_new_id, EVP_PKEY_bits, EVP_PKEY_cmp, EVP_PKEY_derive, EVP_PKEY_derive_init, + EVP_PKEY_derive_set_peer, EVP_PKEY_get0_EC_KEY, EVP_PKEY_get0_RSA, EVP_PKEY_get_raw_private_key, EVP_PKEY_get_raw_public_key, EVP_PKEY_id, EVP_PKEY_keygen, EVP_PKEY_keygen_init, EVP_PKEY_new_raw_private_key, EVP_PKEY_new_raw_public_key, EVP_PKEY_sign, EVP_PKEY_sign_init, EVP_PKEY_size, EVP_PKEY_up_ref, EVP_PKEY_verify, EVP_PKEY_verify_init, @@ -489,6 +490,33 @@ impl LcPtr { } } + pub(crate) fn agree(&self, peer_key: &Self) -> Result, Unspecified> { + let mut pctx = self.create_EVP_PKEY_CTX()?; + + if 1 != unsafe { EVP_PKEY_derive_init(*pctx.as_mut()) } { + return Err(Unspecified); + } + + let mut secret_len = 0; + if 1 != unsafe { EVP_PKEY_derive_set_peer(*pctx.as_mut(), *peer_key.as_mut_unsafe()) } { + return Err(Unspecified); + } + + if 1 != unsafe { EVP_PKEY_derive(*pctx.as_mut(), null_mut(), &mut secret_len) } { + return Err(Unspecified); + } + + let mut secret = vec![0u8; secret_len]; + if 1 != indicator_check!(unsafe { + EVP_PKEY_derive(*pctx.as_mut(), secret.as_mut_ptr(), &mut secret_len) + }) { + return Err(Unspecified); + } + secret.truncate(secret_len); + + Ok(secret.into_boxed_slice()) + } + pub(crate) fn generate(pkey_type: c_int, params_fn: Option) -> Result where F: EVP_PKEY_CTX_consumer, diff --git a/aws-lc-rs/src/pqdsa/signature.rs b/aws-lc-rs/src/pqdsa/signature.rs index 624e75c48f8..e7b1f2f4d3d 100644 --- a/aws-lc-rs/src/pqdsa/signature.rs +++ b/aws-lc-rs/src/pqdsa/signature.rs @@ -3,12 +3,13 @@ use crate::aws_lc::EVP_PKEY; use crate::buffer::Buffer; +use crate::digest::Digest; use crate::encoding::{AsDer, PublicKeyX509Der}; use crate::error::Unspecified; use crate::evp_pkey::No_EVP_PKEY_CTX_consumer; use crate::pqdsa::{parse_pqdsa_public_key, AlgorithmID}; use crate::ptr::LcPtr; -use crate::signature::VerificationAlgorithm; +use crate::signature::{ParsedPublicKey, ParsedVerificationAlgorithm, VerificationAlgorithm}; use crate::{digest, sealed}; use core::fmt; use core::fmt::{Debug, Formatter}; @@ -55,6 +56,28 @@ impl PublicKey { } } +impl ParsedVerificationAlgorithm for PqdsaVerificationAlgorithm { + fn parsed_verify_sig( + &self, + public_key: &ParsedPublicKey, + msg: &[u8], + signature: &[u8], + ) -> Result<(), Unspecified> { + let evp_pkey = public_key.key(); + evp_pkey.verify(msg, None, No_EVP_PKEY_CTX_consumer, signature) + } + + fn parsed_verify_digest_sig( + &self, + public_key: &ParsedPublicKey, + digest: &Digest, + signature: &[u8], + ) -> Result<(), Unspecified> { + let evp_pkey = public_key.key(); + evp_pkey.verify_digest_sig(digest, No_EVP_PKEY_CTX_consumer, signature) + } +} + impl VerificationAlgorithm for PqdsaVerificationAlgorithm { /// Verifies the the signature of `msg` using the public key `public_key`. /// diff --git a/aws-lc-rs/src/rsa/key.rs b/aws-lc-rs/src/rsa/key.rs index 4c5c0098fe9..789ec011444 100644 --- a/aws-lc-rs/src/rsa/key.rs +++ b/aws-lc-rs/src/rsa/key.rs @@ -367,6 +367,10 @@ impl PublicKey { } } +pub(crate) fn parse_rsa_public_key(input: &[u8]) -> Result, KeyRejected> { + rfc8017::decode_public_key_der(input).or(rfc5280::decode_public_key_der(input)) +} + impl Debug for PublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { f.write_str(&format!( diff --git a/aws-lc-rs/src/rsa/signature.rs b/aws-lc-rs/src/rsa/signature.rs index 4ea141a52fe..9dc3d7960a9 100644 --- a/aws-lc-rs/src/rsa/signature.rs +++ b/aws-lc-rs/src/rsa/signature.rs @@ -13,7 +13,7 @@ use crate::digest::{self, match_digest_type, Digest}; use crate::error::Unspecified; use crate::ptr::LcPtr; use crate::sealed::Sealed; -use crate::signature::VerificationAlgorithm; +use crate::signature::{ParsedPublicKey, ParsedVerificationAlgorithm, VerificationAlgorithm}; use super::encoding; #[cfg(feature = "ring-sig-verify")] @@ -52,6 +52,41 @@ impl RsaParameters { } } +impl ParsedVerificationAlgorithm for RsaParameters { + fn parsed_verify_sig( + &self, + public_key: &ParsedPublicKey, + msg: &[u8], + signature: &[u8], + ) -> Result<(), Unspecified> { + let evp_pkey = public_key.key(); + verify_rsa_signature( + self.digest_algorithm(), + self.padding(), + evp_pkey, + msg, + signature, + self.bit_size_range(), + ) + } + + fn parsed_verify_digest_sig( + &self, + public_key: &ParsedPublicKey, + digest: &Digest, + signature: &[u8], + ) -> Result<(), Unspecified> { + let evp_pkey = public_key.key(); + verify_rsa_digest_signature( + self.padding(), + evp_pkey, + digest, + signature, + self.bit_size_range(), + ) + } +} + impl VerificationAlgorithm for RsaParameters { #[cfg(feature = "ring-sig-verify")] fn verify( diff --git a/aws-lc-rs/src/signature.rs b/aws-lc-rs/src/signature.rs index dca77098429..145df34e322 100644 --- a/aws-lc-rs/src/signature.rs +++ b/aws-lc-rs/src/signature.rs @@ -233,16 +233,16 @@ //! sign_and_verify_rsa(&private_key_path, &public_key_path).unwrap() //! } //! ``` -use core::fmt::{Debug, Formatter}; - -#[cfg(feature = "ring-sig-verify")] -use untrusted::Input; - pub use crate::rsa::signature::RsaEncoding; pub use crate::rsa::{ KeyPair as RsaKeyPair, PublicKey as RsaSubjectPublicKey, PublicKeyComponents as RsaPublicKeyComponents, RsaParameters, }; +use aws_lc::EVP_PKEY; +use core::fmt::{Debug, Formatter}; +use std::any::{Any, TypeId}; +#[cfg(feature = "ring-sig-verify")] +use untrusted::Input; use crate::rsa::signature::{RsaSignatureEncoding, RsaSigningAlgorithmId}; use crate::rsa::RsaVerificationAlgorithmId; @@ -258,6 +258,13 @@ pub use crate::ed25519::{ }; use crate::digest::Digest; +use crate::ec::encoding::parse_ec_public_key; +use crate::ed25519::parse_ed25519_public_key; +use crate::error::KeyRejected; +#[cfg(all(feature = "unstable", not(feature = "fips")))] +use crate::pqdsa::{parse_pqdsa_public_key, signature::PqdsaVerificationAlgorithm}; +use crate::ptr::LcPtr; +use crate::rsa::key::parse_rsa_public_key; use crate::{digest, ec, error, hex, rsa, sealed}; /// The longest signature is for ML-DSA-87 @@ -301,8 +308,25 @@ pub trait KeyPair: Debug + Send + Sized + Sync { fn public_key(&self) -> &Self::PublicKey; } +// Private trait +pub(crate) trait ParsedVerificationAlgorithm: Debug + Sync { + fn parsed_verify_sig( + &self, + public_key: &ParsedPublicKey, + msg: &[u8], + signature: &[u8], + ) -> Result<(), error::Unspecified>; + + fn parsed_verify_digest_sig( + &self, + public_key: &ParsedPublicKey, + digest: &Digest, + signature: &[u8], + ) -> Result<(), error::Unspecified>; +} + /// A signature verification algorithm. -pub trait VerificationAlgorithm: Debug + Sync + sealed::Sealed { +pub trait VerificationAlgorithm: Debug + Sync + Any + sealed::Sealed { /// Verify the signature `signature` of message `msg` with the public key /// `public_key`. /// @@ -362,15 +386,122 @@ pub struct UnparsedPublicKey> { algorithm: &'static dyn VerificationAlgorithm, bytes: B, } +/// A parsed public key for signature verification. +/// +/// A `ParsedPublicKey` can be created in two ways: +/// - Directly from public key bytes using [`ParsedPublicKey::new`] +/// - By parsing an `UnparsedPublicKey` using [`UnparsedPublicKey::parse`] +/// +/// This pre-validates the public key format and stores the parsed key material, +/// allowing for more efficient signature verification operations compared to +/// parsing the key on each verification. +/// +/// See the [`crate::signature`] module-level documentation for examples. +pub struct ParsedPublicKey { + algorithm: &'static dyn VerificationAlgorithm, + parsed_algorithm: &'static dyn ParsedVerificationAlgorithm, + key: LcPtr, +} -impl> Copy for UnparsedPublicKey {} +impl ParsedPublicKey { + /// Creates a new `ParsedPublicKey` directly from public key bytes. + /// + /// This method validates the public key format and creates a `ParsedPublicKey` + /// that can be used for efficient signature verification operations. + /// + /// # Errors + /// `KeyRejected` if the public key bytes are malformed or incompatible + /// with the specified algorithm. + /// + /// # Examples + /// + /// ``` + /// use aws_lc_rs::signature::{self, ParsedPublicKey}; + /// + /// # fn main() -> Result<(), Box> { + /// let parsed_key = ParsedPublicKey::new(&signature::ED25519, include_bytes!("../tests/data/ed25519_test_public_key.bin"))?; + /// let signature = [ + /// 0xED, 0xDB, 0x67, 0xE9, 0xF7, 0x8C, 0x9A, 0x0, 0xFD, 0xEE, 0x2D, 0x22, 0x21, 0xA3, 0x9A, + /// 0x8A, 0x79, 0xF2, 0x53, 0x88, 0x78, 0xF0, 0xA0, 0x1, 0x80, 0xA, 0x49, 0xA4, 0x17, 0x88, + /// 0xAB, 0x44, 0x4B, 0xD2, 0x58, 0xB0, 0x3B, 0x51, 0x8A, 0x1B, 0x61, 0x24, 0x52, 0x78, 0x48, + /// 0x58, 0x40, 0x5, 0xB5, 0x45, 0x22, 0xB6, 0x40, 0xBD, 0x14, 0x47, 0xB1, 0xF0, 0xDC, 0x13, + /// 0xB3, 0xE9, 0xD0, 0x6, + /// ]; + /// assert!(parsed_key.verify(b"hello world!", &signature).is_ok()); + /// assert!(parsed_key.verify(b"hello world.", &signature).is_err()); + /// # Ok(()) + /// # } + /// ``` + pub fn new>( + algorithm: &'static dyn VerificationAlgorithm, + bytes: B, + ) -> Result { + parse_public_key(bytes.as_ref(), algorithm) + } -impl> Debug for UnparsedPublicKey { + /// Returns the algorithm used by this public key. + #[must_use] + pub fn algorithm(&self) -> &'static dyn VerificationAlgorithm { + self.algorithm + } + + pub(crate) fn key(&self) -> &LcPtr { + &self.key + } + + /// Uses the public key to verify that `signature` is a valid signature of + /// `message`. + /// + /// This method is more efficient than [`UnparsedPublicKey::verify`] when + /// performing multiple signature verifications with the same public key, + /// as the key parsing overhead is avoided. + /// + /// See the [`crate::signature`] module-level documentation for examples. + /// + // # FIPS + // The following conditions must be met: + // * RSA Key Sizes: 1024, 2048, 3072, 4096 + // * NIST Elliptic Curves: P256, P384, P521 + // * Digest Algorithms: SHA1, SHA256, SHA384, SHA512 + // + /// # Errors + /// `error::Unspecified` if the signature is invalid or verification fails. + #[inline] + pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), error::Unspecified> { + self.parsed_algorithm + .parsed_verify_sig(self, message, signature) + } + + /// Uses the public key to verify that `signature` is a valid signature of + /// `digest`. + /// + /// This method is more efficient than [`UnparsedPublicKey::verify_digest`] when + /// performing multiple signature verifications with the same public key, + /// as the key parsing overhead is avoided. + /// + /// See the [`crate::signature`] module-level documentation for examples. + /// + // # FIPS + // Not allowed + // + /// # Errors + /// `error::Unspecified` if the signature is invalid or verification fails. + #[inline] + pub fn verify_digest( + &self, + digest: &Digest, + signature: &[u8], + ) -> Result<(), error::Unspecified> { + self.parsed_algorithm + .parsed_verify_digest_sig(self, digest, signature) + } +} + +impl Debug for ParsedPublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.write_str(&format!( - "UnparsedPublicKey {{ algorithm: {:?}, bytes: \"{}\" }}", + "ParsedPublicKey {{ algorithm: {:?}, }}", self.algorithm, - hex::encode(self.bytes.as_ref()) )) } } @@ -382,6 +513,18 @@ impl> AsRef<[u8]> for UnparsedPublicKey { } } +impl> Copy for UnparsedPublicKey {} + +impl> Debug for UnparsedPublicKey { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.write_str(&format!( + "UnparsedPublicKey {{ algorithm: {:?}, bytes: \"{}\" }}", + self.algorithm, + hex::encode(self.bytes.as_ref()) + )) + } +} + impl> UnparsedPublicKey { /// Construct a new `UnparsedPublicKey`. /// @@ -429,6 +572,78 @@ impl> UnparsedPublicKey { self.algorithm .verify_digest_sig(self.bytes.as_ref(), digest, signature) } + + /// Parses the public key bytes and returns a `ParsedPublicKey`. + /// + /// This method validates the public key format and creates a `ParsedPublicKey` + /// that can be used for more efficient signature verification operations. + /// The parsing overhead is incurred once, making subsequent verifications + /// faster compared to using `UnparsedPublicKey::verify` directly. + /// + /// This is equivalent to calling [`ParsedPublicKey::new`] with the same + /// algorithm and bytes. + /// + /// # Errors + /// `KeyRejected` if the public key bytes are malformed or incompatible + /// with the specified algorithm. + /// + /// # Examples + /// + /// ``` + /// use aws_lc_rs::signature::{self, UnparsedPublicKey}; + /// + /// # fn example() -> Result<(), Box> { + /// let public_key_bytes = &[/* public key bytes */]; + /// let unparsed_key = UnparsedPublicKey::new(&signature::ED25519, public_key_bytes); + /// let parsed_key = unparsed_key.parse()?; + /// + /// // Now use parsed_key for efficient signature verification + /// let message = b"hello, world"; + /// let signature = &[/* signature bytes */]; + /// // parsed_key.verify(message, signature)?; + /// # Ok(()) + /// # } + /// ``` + pub fn parse(&self) -> Result { + parse_public_key(self.bytes.as_ref(), self.algorithm) + } +} + +pub(crate) fn parse_public_key( + bytes: &[u8], + algorithm: &'static dyn VerificationAlgorithm, +) -> Result { + let parsed_algorithm: &'static dyn ParsedVerificationAlgorithm; + let key = if let Some(ec_alg) = + (algorithm as &dyn Any).downcast_ref::() + { + parsed_algorithm = ec_alg; + parse_ec_public_key(bytes, ec_alg.id.nid())? + } else if let Some(ed_alg) = (algorithm as &dyn Any).downcast_ref::() { + parsed_algorithm = ed_alg; + parse_ed25519_public_key(bytes)? + } else if let Some(rsa_alg) = (algorithm as &dyn Any).downcast_ref::() { + parsed_algorithm = rsa_alg; + parse_rsa_public_key(bytes)? + } else { + #[cfg(all(feature = "unstable", not(feature = "fips")))] + if let Some(pqdsa_alg) = + (algorithm as &dyn Any).downcast_ref::() + { + parsed_algorithm = pqdsa_alg; + parse_pqdsa_public_key(bytes, pqdsa_alg.id)? + } else { + return Err(KeyRejected::invalid_encoding()); + } + #[cfg(any(not(feature = "unstable"), feature = "fips"))] + return Err(KeyRejected::invalid_encoding()); + }; + + Ok(ParsedPublicKey { + algorithm, + parsed_algorithm, + key, + }) } /// Verification of signatures using RSA keys of 1024-8192 bits, PKCS#1.5 padding, and SHA-1.