diff --git a/aws-lc-rs/Cargo.toml b/aws-lc-rs/Cargo.toml index 61f7b55d367..99e91ca2ef2 100644 --- a/aws-lc-rs/Cargo.toml +++ b/aws-lc-rs/Cargo.toml @@ -54,3 +54,4 @@ regex = "1.6.0" lazy_static = "1.4.0" clap = { version = "4.1.8", features = ["derive"] } hex = "0.4.3" +base64 = "0" diff --git a/aws-lc-rs/src/ec.rs b/aws-lc-rs/src/ec.rs index 10f1d6e40c1..47cdf33f9ac 100644 --- a/aws-lc-rs/src/ec.rs +++ b/aws-lc-rs/src/ec.rs @@ -12,15 +12,15 @@ use crate::ptr::{ConstPointer, DetachableLcPtr, LcPtr, Pointer}; use crate::fips::indicator_check; use crate::signature::{Signature, VerificationAlgorithm}; -use crate::{digest, sealed, test}; +use crate::{digest, public_key, sealed, test}; #[cfg(feature = "fips")] use aws_lc::EC_KEY_check_fips; #[cfg(not(feature = "fips"))] use aws_lc::EC_KEY_check_key; use aws_lc::{ - point_conversion_form_t, 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, + point_conversion_form_t, BIO_new, BIO_s_mem, BN_bn2bin_padded, BN_num_bytes, + ECDSA_SIG_from_bytes, ECDSA_SIG_get0_r, ECDSA_SIG_get0_s, ECDSA_SIG_new, ECDSA_SIG_set0, + ECDSA_SIG_to_bytes, EC_GROUP_get_curve_name, EC_GROUP_new_by_curve_name, EC_KEY_get0_group, EC_KEY_get0_private_key, EC_KEY_get0_public_key, EC_KEY_new, EC_KEY_set_group, EC_KEY_set_private_key, EC_KEY_set_public_key, EC_POINT_new, EC_POINT_oct2point, EC_POINT_point2oct, EVP_DigestVerify, EVP_DigestVerifyInit, EVP_PKEY_CTX_new_id, @@ -29,13 +29,15 @@ use aws_lc::{ NID_secp384r1, NID_secp521r1, BIGNUM, ECDSA_SIG, EC_GROUP, EC_POINT, EVP_PKEY, EVP_PKEY_EC, }; +use aws_lc::d2i_PUBKEY_bio; +use aws_lc::BIO_write; #[cfg(test)] use aws_lc::EC_POINT_mul; use std::fmt::{Debug, Formatter}; use std::mem::MaybeUninit; use std::ops::Deref; -use std::os::raw::{c_int, c_uint}; +use std::os::raw::{c_int, c_uint, c_void}; #[cfg(test)] use std::ptr::null; use std::ptr::null_mut; @@ -181,12 +183,48 @@ impl VerificationAlgorithm for EcdsaVerificationAlgorithm { signature: &[u8], ) -> Result<(), Unspecified> { match self.sig_format { - EcdsaSignatureFormat::ASN1 => { - verify_asn1_signature(self.id, self.digest, public_key, msg, signature) - } - EcdsaSignatureFormat::Fixed => { - verify_fixed_signature(self.id, self.digest, public_key, msg, signature) - } + EcdsaSignatureFormat::ASN1 => verify_asn1_signature( + self.id, + self.digest, + public_key, + &public_key::OCTET_STRING, + msg, + signature, + ), + EcdsaSignatureFormat::Fixed => verify_fixed_signature( + self.id, + self.digest, + public_key, + &public_key::OCTET_STRING, + msg, + signature, + ), + } + } + + fn verify_sig_with_x509_pubkey( + &self, + public_key: &[u8], + msg: &[u8], + signature: &[u8], + ) -> Result<(), crate::error::Unspecified> { + match self.sig_format { + EcdsaSignatureFormat::ASN1 => verify_asn1_signature( + self.id, + self.digest, + public_key, + &public_key::X509, + msg, + signature, + ), + EcdsaSignatureFormat::Fixed => verify_fixed_signature( + self.id, + self.digest, + public_key, + &public_key::X509, + msg, + signature, + ), } } } @@ -195,6 +233,7 @@ fn verify_fixed_signature( alg: &'static AlgorithmID, digest: &'static digest::Algorithm, public_key: &[u8], + encoding: &public_key::Encoding, msg: &[u8], signature: &[u8], ) -> Result<(), Unspecified> { @@ -208,17 +247,21 @@ fn verify_fixed_signature( } let out_bytes = LcPtr::new(out_bytes)?; let signature = unsafe { out_bytes.as_slice(out_bytes_len.assume_init()) }; - verify_asn1_signature(alg, digest, public_key, msg, signature) + verify_asn1_signature(alg, digest, public_key, encoding, msg, signature) } fn verify_asn1_signature( alg: &'static AlgorithmID, digest: &'static digest::Algorithm, public_key: &[u8], + encoding: &public_key::Encoding, msg: &[u8], signature: &[u8], ) -> Result<(), Unspecified> { - let pkey = evp_pkey_from_public_key(alg, public_key)?; + let pkey = match encoding.id { + public_key::EncodingID::OctetString => evp_pkey_from_public_key(alg, public_key)?, + public_key::EncodingID::X509 => evp_pkey_from_x509_pubkey(public_key)?, + }; let mut md_ctx = DigestContext::new_uninit(); @@ -376,6 +419,23 @@ pub(crate) unsafe fn evp_pkey_from_public_point( Ok(pkey) } +#[inline] +pub(crate) fn evp_pkey_from_x509_pubkey( + pubkey_data: &[u8], +) -> Result, Unspecified> { + // Create a memory BIO and write the public key data to it + let mem_bio = LcPtr::new(unsafe { BIO_new(BIO_s_mem()) })?; + let len = match c_int::try_from(pubkey_data.len()) { + Ok(len) => len, + Err(_) => return Err(Unspecified), + }; + if unsafe { BIO_write(*mem_bio, pubkey_data.as_ptr().cast::(), len) } <= 0 { + return Err(Unspecified); + } + // Use d2i_PUBKEY_bio to read the public key from the memory BIO + Ok(LcPtr::new(unsafe { d2i_PUBKEY_bio(*mem_bio, null_mut()) })?) +} + #[cfg(test)] pub(crate) unsafe fn evp_pkey_from_private( ec_group: &ConstPointer, @@ -580,6 +640,9 @@ unsafe fn ecdsa_sig_from_fixed( #[cfg(test)] mod tests { + use base64::engine::general_purpose; + use base64::Engine; + use crate::ec::key_pair::EcdsaKeyPair; use crate::signature::{KeyPair, ECDSA_P256_SHA256_FIXED_SIGNING}; use crate::test::from_dirty_hex; @@ -643,4 +706,32 @@ mod tests { let actual_result = unparsed_pub_key.verify(msg.as_bytes(), &sig); assert!(actual_result.is_ok(), "Key: {}", test::to_hex(public_key)); } + + #[test] + fn test_p384_verify_with_x509_pubkey() { + let alg = &signature::ECDSA_P384_SHA384_ASN1; + let msg = "hello, world"; + + // Generated from bc-fips using "SHA384withECDSA" + let x509_pubkey_b64 = concat!( + "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEPhS06qHiqNSnyanUSHHMMebqu2h4Ho3oSwlNfLOtCXlIsm91", + "684Hor2X1b056aWGymprw8W6cXn/d6O2Y6x0FGu0uJnEEkvsIAwzu+stRHzgxuiky633R7zsSIfI+rsc", + ); + let x509_pubkey = general_purpose::STANDARD + .decode(x509_pubkey_b64) + .expect("Invalid base64 encoding"); + let unparsed_pub_key = + signature::UnparsedPublicKey::new_with_x509(alg, x509_pubkey.as_slice()); + + // Output from bc-fips + let sig_b64 = concat!( + "MGYCMQCBzjtJuLZol+KWCN6Tsv+FBENp1QpOlfVpOSlBB82LpGn3fMcQcAnopkrTwqJA0gICMQDfHqOr", + "Kebqy7qOj26odws7oROlIParYYl9FfLWGjIysipMk51ZbakcUVDVuDHu3QY=" + ); + let sig = general_purpose::STANDARD + .decode(sig_b64) + .expect("Invalid base64 encoding"); + let actual_result = unparsed_pub_key.verify(msg.as_bytes(), sig.as_slice()); + assert!(actual_result.is_ok(), "Key: {x509_pubkey_b64}"); + } } diff --git a/aws-lc-rs/src/ed25519.rs b/aws-lc-rs/src/ed25519.rs index 371234c709a..9093c690e60 100644 --- a/aws-lc-rs/src/ed25519.rs +++ b/aws-lc-rs/src/ed25519.rs @@ -32,6 +32,10 @@ pub(crate) const ED25519_PRIVATE_KEY_SEED_LEN: usize = aws_lc::ED25519_PRIVATE_KEY_SEED_LEN as usize; const ED25519_SIGNATURE_LEN: usize = aws_lc::ED25519_SIGNATURE_LEN as usize; const ED25519_SEED_LEN: usize = 32; +// Hex equivalent: 0x302a300506032b6570032100 +const ED25519_X509_PUBLIC_KEY_PREFIX: [u8; 12] = [48, 42, 48, 5, 6, 3, 43, 101, 112, 3, 33, 0]; +const ED25519_X509_PUBLIC_KEY_LEN: usize = + ED25519_X509_PUBLIC_KEY_PREFIX.len() + ED25519_PUBLIC_KEY_LEN; /// Parameters for `EdDSA` signing and verification. #[derive(Debug)] @@ -74,6 +78,21 @@ impl VerificationAlgorithm for EdDSAParameters { crate::fips::set_fips_service_status_unapproved(); Ok(()) } + + fn verify_sig_with_x509_pubkey( + &self, + public_key: &[u8], + msg: &[u8], + signature: &[u8], + ) -> Result<(), Unspecified> { + if public_key.len() == ED25519_X509_PUBLIC_KEY_LEN + && public_key[..12] == ED25519_X509_PUBLIC_KEY_PREFIX + { + self.verify_sig(&public_key[12..], msg, signature) + } else { + Err(Unspecified) + } + } } /// An Ed25519 key pair, for signing. @@ -414,9 +433,12 @@ impl Ed25519KeyPair { #[cfg(test)] mod tests { + use base64::engine::general_purpose; + use base64::Engine; + use crate::ed25519::Ed25519KeyPair; use crate::rand::SystemRandom; - use crate::test; + use crate::{signature, test}; #[test] fn test_generate_pkcs8() { @@ -481,4 +503,24 @@ mod tests { ); } } + + #[test] + fn verify_ed25519_signature() { + const MESSAGE: &[u8] = b"hello, world"; + // Generated using "ED25519" from BC-FIPS + let b64_x509_pubkey = "MCowBQYDK2VwAyEAo7urSMCwnkczfz1hxyj15uE+ja/nK0aOenoNCtqE+9Q="; + let b64_sig = "ZLiMhsOeHjGmnO+B3CzUe8B6Hl1O9j/aaDJqPU4lZZHtmxZxCQIN1lGqvFTP9c6AxRblTlftPK/oZF2xNM+5Ag=="; + let x509_pubkey = general_purpose::STANDARD + .decode(b64_x509_pubkey) + .expect("Invalid base64 encoding"); + let signature = general_purpose::STANDARD + .decode(b64_sig) + .expect("Invalid base64 encoding"); + // Verify the signature. + let public_key = + signature::UnparsedPublicKey::new_with_x509(&signature::ED25519, &x509_pubkey); + public_key + .verify(MESSAGE, &signature) + .expect("Signature verification failure"); + } } diff --git a/aws-lc-rs/src/ptr.rs b/aws-lc-rs/src/ptr.rs index 5103cc5f146..29f32e438e3 100644 --- a/aws-lc-rs/src/ptr.rs +++ b/aws-lc-rs/src/ptr.rs @@ -4,9 +4,9 @@ use std::ops::Deref; use aws_lc::{ - BN_free, ECDSA_SIG_free, EC_GROUP_free, EC_KEY_free, EC_POINT_free, EVP_AEAD_CTX_free, - EVP_PKEY_CTX_free, EVP_PKEY_free, OPENSSL_free, RSA_free, BIGNUM, ECDSA_SIG, EC_GROUP, EC_KEY, - EC_POINT, EVP_AEAD_CTX, EVP_PKEY, EVP_PKEY_CTX, RSA, + BIO_free, BN_free, ECDSA_SIG_free, EC_GROUP_free, EC_KEY_free, EC_POINT_free, + EVP_AEAD_CTX_free, EVP_PKEY_CTX_free, EVP_PKEY_free, OPENSSL_free, RSA_free, BIGNUM, BIO, + ECDSA_SIG, EC_GROUP, EC_KEY, EC_POINT, EVP_AEAD_CTX, EVP_PKEY, EVP_PKEY_CTX, RSA, }; use mirai_annotations::verify_unreachable; @@ -201,6 +201,7 @@ macro_rules! create_pointer { // freed. This is different than functions of the same name in OpenSSL which generally do not zero // memory. create_pointer!(u8, OPENSSL_free); +create_pointer!(BIO, BIO_free); create_pointer!(EC_GROUP, EC_GROUP_free); create_pointer!(EC_POINT, EC_POINT_free); create_pointer!(EC_KEY, EC_KEY_free); diff --git a/aws-lc-rs/src/public_key.rs b/aws-lc-rs/src/public_key.rs index e73d0e0a710..0169a210383 100644 --- a/aws-lc-rs/src/public_key.rs +++ b/aws-lc-rs/src/public_key.rs @@ -22,7 +22,7 @@ pub(crate) enum EncodingID { /// Encoding of bytes #[derive(Debug, PartialEq, Eq)] -pub(crate) struct Encoding { +pub struct Encoding { /// Encoding ID pub(crate) id: EncodingID, } diff --git a/aws-lc-rs/src/rsa.rs b/aws-lc-rs/src/rsa.rs index 55a0c12b1f2..897a2c98c85 100644 --- a/aws-lc-rs/src/rsa.rs +++ b/aws-lc-rs/src/rsa.rs @@ -14,6 +14,7 @@ use crate::fips::indicator_check; #[cfg(feature = "ring-io")] use crate::io; use crate::ptr::{ConstPointer, DetachableLcPtr, LcPtr}; +use crate::public_key::evp_pkey_from_x509_pubkey; use crate::sealed::Sealed; use crate::signature::{KeyPair, VerificationAlgorithm}; use crate::{cbs, digest, rand, test}; @@ -211,6 +212,16 @@ impl VerificationAlgorithm for RsaParameters { verify_RSA(self.0, self.1, &rsa, msg, signature, &self.2) } } + + fn verify_sig_with_x509_pubkey( + &self, + public_key: &[u8], + msg: &[u8], + signature: &[u8], + ) -> Result<(), Unspecified> { + let rsa = evp_pkey_from_x509_pubkey(public_key)?; + verify_RSA(self.0, self.1, &rsa, msg, signature, &self.2) + } } impl RsaKeyPair { @@ -730,6 +741,10 @@ where #[cfg(test)] mod tests { + use base64::{engine::general_purpose, Engine}; + + use crate::signature; + #[cfg(feature = "ring-io")] #[test] fn test_rsa() { @@ -785,4 +800,37 @@ mod tests { format!("{:?}", signature::RSA_PSS_2048_8192_SHA256) ); } + + #[test] + fn verify_rsa_signature() { + const MESSAGE: &[u8] = b"hello, world"; + // Generated using "SHA384withRSA" from BC-FIPS + let b64_x509_pubkey = concat!( + "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAzmMMJclDHPi+YvGUNjDtvGV+x40Fou5Nv0+c+uBH/xaO3+D", + "VDp/psg11UsA/haODPTNdFASqxu76/m1BLGmf+2MiLc1RNhvFJYTTJADsPtzObroBhXOhv1HYycB14JYFDFGJ0Bn/uf", + "tGyEn7wWHulcclTPRUg4F5i/MzzfiXimsn8sMme2hZXvlFvwjuSp6BGHCqu2DfXHY16BDZsEtK+DpABz8mGCN/D7ilc", + "8feSeyCc5wbFSbRgkJXRPYxuqg0icTQDCwx6OWdcNPRaQq5ty2HLG2PXmSDomtwsKPuAwvEqOy0BgZ0VjW0nCwMH1e/", + "jFvZqXDI7Sf9dWw3eMtc6jk9cTat+AdcvgtvFIEEcH9k48q+pFvfGQ8YBSbZBrYyCWZxPKuDOSuQHO682WX2mAsG1cY", + "Y+cLrsH2asqsCKCAwKcz43IucqqvEFqtnwpnkCIlGxqHWGbVrWTxmcC1qEM9Xkik2xJcXHGKmtMnoPgnsHiV7vVoglK", + "iDUfDyi5VTAgMBAAE="); + let b64_sig = concat!( + "y0NNWwHRLA0B2MvQ63QKcBYvlIx7gOxQXDh0IRdTryQ2pYuod3w1k5STGR3f+CTm83wp/MvBnCEEvnlxruK5Vrz3g1h", + "ThneUnZGwPo1MwCgvVLmR6RtoXy9+5M4BBcl/PqJTQcWo92RsbxEGVEVzsH6CNBAf4QDVW5ox1g5HjH4HsTutyezdE0", + "cmsHwz6IRiMeyuGWvs9sXECiIeripVpoHD7qz1TpvJPHA+wpvwDOUxJppk2naP5cVY+B0jRzTJ1TmX0IM1KMSrN/J5m", + "vkHm5TsyWkRBj6GD+4fsibSsF5W6vLSLJtQAk4dFpaafGcCgKsGSzpU605DeaHhrVOUnrAmLLCN5uIBRkGmekKowPkJ", + "AsdHP04nJ7QwQoIjxcaX9WJpPotcaqaKGdGf4xy2QIYRnhoRDq9DXfl6Wckqh6z4wz+R6gUr1wS3X5SiYvSgjEYQOYE", + "6A1xo+cpuPBpWuj9eL77QZBsn0rGwJSJNr4ds7gfIk7tYfkNcI/C7MFOh"); + let x509_pubkey = general_purpose::STANDARD + .decode(b64_x509_pubkey) + .expect("Invalid base64 encoding"); + let signature = general_purpose::STANDARD + .decode(b64_sig) + .expect("Invalid base64 encoding"); + // Verify the signature. + let public_key = signature::UnparsedPublicKey::new_with_x509( + &signature::RSA_PKCS1_2048_8192_SHA384, + &x509_pubkey, + ); + public_key.verify(MESSAGE, &signature).unwrap(); + } } diff --git a/aws-lc-rs/src/signature.rs b/aws-lc-rs/src/signature.rs index 6f754f0c30e..3e4d9a8f75f 100644 --- a/aws-lc-rs/src/signature.rs +++ b/aws-lc-rs/src/signature.rs @@ -239,7 +239,7 @@ //! sign_and_verify_rsa(&private_key_path, &public_key_path).unwrap() //! } //! ``` -use crate::rsa; +use crate::{public_key, rsa}; use rsa::{RSASigningAlgorithmId, RSAVerificationAlgorithmId, RsaSignatureEncoding}; pub use rsa::{ RsaEncoding, RsaKeyPair, RsaParameters, RsaPublicKeyComponents, RsaSubjectPublicKey, @@ -333,7 +333,7 @@ pub trait VerificationAlgorithm: Debug + Sync + sealed::Sealed { ) -> Result<(), error::Unspecified>; /// Verify the signature `signature` of message `msg` with the public key - /// `public_key`. + /// `public_key` in Octet String encoding. /// // # FIPS // The following conditions must be met: @@ -349,6 +349,24 @@ pub trait VerificationAlgorithm: Debug + Sync + sealed::Sealed { msg: &[u8], signature: &[u8], ) -> Result<(), error::Unspecified>; + + /// Verify the signature `signature` of message `msg` with the public key + /// `public_key` in X509 DER encoding. + /// + // # 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 inputs not verified. + fn verify_sig_with_x509_pubkey( + &self, + public_key: &[u8], + msg: &[u8], + signature: &[u8], + ) -> Result<(), error::Unspecified>; } /// An unparsed, possibly malformed, public key for signature verification. @@ -356,6 +374,7 @@ pub trait VerificationAlgorithm: Debug + Sync + sealed::Sealed { pub struct UnparsedPublicKey> { algorithm: &'static dyn VerificationAlgorithm, bytes: B, + encoding: &'static public_key::Encoding, } impl> Copy for UnparsedPublicKey {} @@ -371,12 +390,28 @@ impl> Debug for UnparsedPublicKey { } impl> UnparsedPublicKey { - /// Construct a new `UnparsedPublicKey`. + /// Construct a new `UnparsedPublicKey` with [`public_key::OCTET_STRING`] encoding. /// /// No validation of `bytes` is done until `verify()` is called. #[inline] pub fn new(algorithm: &'static dyn VerificationAlgorithm, bytes: B) -> Self { - Self { algorithm, bytes } + Self { + algorithm, + bytes, + encoding: &public_key::OCTET_STRING, + } + } + + /// Construct a new `UnparsedPublicKey` with [`public_key::X509`] encoding. + /// + /// No validation of `bytes` is done until `verify()` is called. + #[inline] + pub fn new_with_x509(algorithm: &'static dyn VerificationAlgorithm, bytes: B) -> Self { + Self { + algorithm, + bytes, + encoding: &public_key::X509, + } } /// Parses the public key and verifies `signature` is a valid signature of @@ -394,8 +429,16 @@ impl> UnparsedPublicKey { /// `error::Unspecified` if inputs not verified. #[inline] pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), error::Unspecified> { - self.algorithm - .verify_sig(self.bytes.as_ref(), message, signature) + match &self.encoding.id { + public_key::EncodingID::OctetString => { + self.algorithm + .verify_sig(self.bytes.as_ref(), message, signature) + } + public_key::EncodingID::X509 => { + self.algorithm + .verify_sig_with_x509_pubkey(self.bytes.as_ref(), message, signature) + } + } } }