From b014eeb3bb75e73dae2e980d06e5dc7d953cd0fe Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Fri, 12 Jan 2024 20:26:46 +0000 Subject: [PATCH] Key generation changes for FIPS --- aws-lc-rs/src/rsa/encoding.rs | 12 ++-- aws-lc-rs/src/rsa/encryption.rs | 101 +++++++++++++++++++++++++++++-- aws-lc-rs/src/rsa/key.rs | 102 +++++++++++++++++++++++++++----- 3 files changed, 192 insertions(+), 23 deletions(-) diff --git a/aws-lc-rs/src/rsa/encoding.rs b/aws-lc-rs/src/rsa/encoding.rs index 69c0869fadb..1a726b9d95d 100644 --- a/aws-lc-rs/src/rsa/encoding.rs +++ b/aws-lc-rs/src/rsa/encoding.rs @@ -1,7 +1,7 @@ /// [RFC 8017](https://www.rfc-editor.org/rfc/rfc8017.html) /// /// PKCS #1: RSA Cryptography Specifications Version 2.2 -pub mod rfc8017 { +pub(super) mod rfc8017 { use crate::{ cbs, error::Unspecified, @@ -70,7 +70,7 @@ pub mod rfc8017 { /// [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280.html) /// /// Encodings that use the `SubjectPublicKeyInfo` structure. -pub mod rfc5280 { +pub(super) mod rfc5280 { use crate::{cbb::LcCBB, cbs, encoding::RsaPublicKeyX509Der, error::Unspecified, ptr::LcPtr}; use aws_lc::{EVP_marshal_public_key, EVP_parse_public_key, EVP_PKEY}; @@ -93,8 +93,12 @@ pub mod rfc5280 { } /// PKCS#8 Encoding Functions -pub mod pkcs8 { - use crate::{cbb::LcCBB, error::{Unspecified, KeyRejected}, ptr::LcPtr}; +pub(super) mod pkcs8 { + use crate::{ + cbb::LcCBB, + error::{KeyRejected, Unspecified}, + ptr::LcPtr, + }; use aws_lc::{EVP_marshal_private_key, EVP_PKEY}; // Based on a measurement of a PKCS#8 v1 document containing an RSA-8192 key with an additional 1% capacity buffer diff --git a/aws-lc-rs/src/rsa/encryption.rs b/aws-lc-rs/src/rsa/encryption.rs index f0e470304ef..ebfc73bf75e 100644 --- a/aws-lc-rs/src/rsa/encryption.rs +++ b/aws-lc-rs/src/rsa/encryption.rs @@ -7,6 +7,8 @@ use crate::{ fips::indicator_check, ptr::LcPtr, }; +#[cfg(feature = "fips")] +use aws_lc::RSA_check_fips; use aws_lc::{ EVP_PKEY_CTX_new, EVP_PKEY_CTX_set_rsa_mgf1_md, EVP_PKEY_CTX_set_rsa_oaep_md, EVP_PKEY_CTX_set_rsa_padding, EVP_PKEY_decrypt, EVP_PKEY_decrypt_init, EVP_PKEY_encrypt, @@ -90,7 +92,7 @@ impl OaepAlgorithm { } impl Debug for OaepAlgorithm { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { Debug::fmt(&self.id, f) } } @@ -129,7 +131,22 @@ impl PrivateDecryptingKey { /// # Errors /// * `Unspecified` for any error that occurs during the generation of the RSA keypair. pub fn generate(size: EncryptionKeySize) -> Result { - let key = generate_rsa_key(size.bit_len())?; + let key = generate_rsa_key(size.bit_len(), false)?; + Self::new(key) + } + + /// Generate a RSA `KeyPair` of the specified key-strength. + /// + /// Supports the following key sizes: + /// * `EncryptionKeySize::Rsa2048` + /// * `EncryptionKeySize::Rsa3072` + /// * `EncryptionKeySize::Rsa4096` + /// + /// # Errors + /// * `Unspecified`: Any key generation failure. + #[cfg(feature = "fips")] + pub fn generate_fips(size: EncryptionKeySize) -> Result { + let key = generate_rsa_key(size.bit_len(), true)?; Self::new(key) } @@ -144,6 +161,19 @@ impl PrivateDecryptingKey { Self::new(evp_pkey).map_err(|_| KeyRejected::unexpected_error()) } + /// Returns a boolean indicator if this RSA key is an approved FIPS 140-3 key. + #[cfg(feature = "fips")] + #[must_use] + pub fn is_valid_fips_key(&self) -> bool { + let rsa_key = if let Ok(key) = self.0.key.get_rsa() { + key + } else { + return false; + }; + + 1 == unsafe { RSA_check_fips(*rsa_key) } + } + /// Returns the RSA key size in bytes. #[must_use] pub fn key_size(&self) -> usize { @@ -208,6 +238,12 @@ impl PrivateDecryptingKey { } } +impl Debug for PrivateDecryptingKey { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("PrivateDecryptingKey").finish() + } +} + impl AsDer> for PrivateDecryptingKey { fn as_der(&self) -> Result, Unspecified> { AsDer::>::as_der(&self.0) @@ -279,6 +315,12 @@ impl PublicEncryptingKey { } } +impl Debug for PublicEncryptingKey { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("PublicEncryptingKey").finish() + } +} + fn configure_oaep_crypto_operation( evp_pkey_ctx: &LcPtr, oaep_hash_fn: OaepHashFn, @@ -353,8 +395,7 @@ mod tests { ($name:ident, $size:expr) => { #[test] fn $name() { - let private_key = - PrivateDecryptingKey::generate($size).expect("generation"); + let private_key = PrivateDecryptingKey::generate($size).expect("generation"); let pkcs8v1 = private_key.as_der().expect("encoded"); @@ -378,6 +419,58 @@ mod tests { generate_encode_decode!(rsa4096_generate_encode_decode, EncryptionKeySize::Rsa4096); generate_encode_decode!(rsa8192_generate_encode_decode, EncryptionKeySize::Rsa8192); + macro_rules! generate_fips_encode_decode { + ($name:ident, $size:expr) => { + #[cfg(feature = "fips")] + #[test] + fn $name() { + let private_key = PrivateDecryptingKey::generate_fips($size).expect("generation"); + + assert_eq!(true, private_key.is_valid_fips_key()); + + let pkcs8v1 = private_key.as_der().expect("encoded"); + + let private_key = + PrivateDecryptingKey::from_pkcs8(pkcs8v1.as_ref()).expect("decoded"); + + let public_key = private_key.public_key().expect("public key"); + + drop(private_key); + + let public_key_der = public_key.as_der().expect("encoded"); + + let _public_key = + PublicEncryptingKey::from_der(public_key_der.as_ref()).expect("decoded"); + } + }; + ($name:ident, $size:expr, false) => { + #[cfg(feature = "fips")] + #[test] + fn $name() { + let _ = PrivateDecryptingKey::generate_fips($size) + .expect_err("should fail for key size"); + } + }; + } + + generate_fips_encode_decode!( + rsa2048_generate_fips_encode_decode, + EncryptionKeySize::Rsa2048 + ); + generate_fips_encode_decode!( + rsa3072_generate_fips_encode_decode, + EncryptionKeySize::Rsa3072 + ); + generate_fips_encode_decode!( + rsa4096_generate_fips_encode_decode, + EncryptionKeySize::Rsa4096 + ); + generate_fips_encode_decode!( + rsa8192_generate_fips_encode_decode, + EncryptionKeySize::Rsa8192, + false + ); + macro_rules! round_trip_algorithm { ($name:ident, $alg:expr, $keysize:expr) => { #[test] diff --git a/aws-lc-rs/src/rsa/key.rs b/aws-lc-rs/src/rsa/key.rs index a474f9ac62b..52664f19794 100644 --- a/aws-lc-rs/src/rsa/key.rs +++ b/aws-lc-rs/src/rsa/key.rs @@ -26,9 +26,9 @@ use crate::{ #[cfg(feature = "fips")] use aws_lc::RSA_check_fips; use aws_lc::{ - EVP_DigestSignInit, EVP_PKEY_CTX_new_id, EVP_PKEY_CTX_set_rsa_keygen_bits, EVP_PKEY_assign_RSA, - EVP_PKEY_bits, EVP_PKEY_id, EVP_PKEY_keygen, EVP_PKEY_keygen_init, EVP_PKEY_new, EVP_PKEY_size, - RSA_get0_d, RSA_new, RSA_set0_key, RSA_size, EVP_PKEY, EVP_PKEY_CTX, EVP_PKEY_RSA, + EVP_DigestSignInit, EVP_PKEY_assign_RSA, EVP_PKEY_bits, EVP_PKEY_id, EVP_PKEY_new, + EVP_PKEY_size, RSA_generate_key_ex, RSA_generate_key_fips, RSA_get0_d, RSA_new, RSA_set0_key, + RSA_size, BIGNUM, EVP_PKEY, EVP_PKEY_CTX, EVP_PKEY_RSA, RSA_F4, }; #[cfg(feature = "ring-io")] use aws_lc::{RSA_get0_e, RSA_get0_n}; @@ -130,7 +130,22 @@ impl KeyPair { /// # Errors /// * `Unspecified`: Any key generation failure. pub fn generate(size: SignatureKeySize) -> Result { - let private_key = generate_rsa_key(size.bit_len())?; + let private_key = generate_rsa_key(size.bit_len(), false)?; + unsafe { Self::new(private_key).map_err(|_| Unspecified) } + } + + /// Generate a RSA `KeyPair` of the specified key-strength. + /// + /// Supports the following key sizes: + /// * `SignatureKeySize::Rsa2048` + /// * `SignatureKeySize::Rsa3072` + /// * `SignatureKeySize::Rsa4096` + /// + /// # Errors + /// * `Unspecified`: Any key generation failure. + #[cfg(feature = "fips")] + pub fn generate_fips(size: SignatureKeySize) -> Result { + let private_key = generate_rsa_key(size.bit_len(), true)?; unsafe { Self::new(private_key).map_err(|_| Unspecified) } } @@ -144,7 +159,7 @@ impl KeyPair { /// RSA keys. Thus signatures may be generated by keys that are not accepted /// by *ring*. In particular: /// * RSA keys ranging between 2048-bit keys and 8192-bit keys are supported. - /// * The public modulous does not have a required minimum size. + /// * The public exponenet does not have a required minimum size. /// /// # Errors /// `error::KeyRejected` if bytes do not encode an RSA private key or if the key is otherwise @@ -439,24 +454,36 @@ where } } -pub(super) fn generate_rsa_key(size: core::ffi::c_int) -> Result, Unspecified> { - let evp_pkey_ctx = LcPtr::new(unsafe { EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, null_mut()) })?; +pub(super) fn generate_rsa_key( + size: core::ffi::c_int, + fips: bool, +) -> Result, Unspecified> { + // We explicitly don't use `EVP_PKEY_keygen`, as it will force usage of either the FIPS or non-FIPS + // keygen function based on the whether the build of AWS-LC had FIPS enbaled. Rather we delegate to the desired + // generation function. - if 1 != unsafe { EVP_PKEY_keygen_init(*evp_pkey_ctx) } { - return Err(Unspecified); - }; + let rsa = DetachableLcPtr::new(unsafe { RSA_new() })?; - if 1 != unsafe { EVP_PKEY_CTX_set_rsa_keygen_bits(*evp_pkey_ctx, size) } { + if 1 != if fips { + indicator_check!(unsafe { RSA_generate_key_fips(*rsa, size, null_mut()) }) + } else { + // Safety: RSA_F4 == 65537, RSA_F4 an i32 is safe to cast to u64 + debug_assert_eq!(RSA_F4 as u64, 65537u64); + let e: DetachableLcPtr = (RSA_F4 as u64).try_into()?; + unsafe { RSA_generate_key_ex(*rsa, size, *e, null_mut()) } + } { return Err(Unspecified); - }; + } - let mut pkey: *mut EVP_PKEY = null_mut(); + let evp_pkey = LcPtr::new(unsafe { EVP_PKEY_new() })?; - if 1 != indicator_check!(unsafe { EVP_PKEY_keygen(*evp_pkey_ctx, &mut pkey) }) { + if 1 != unsafe { EVP_PKEY_assign_RSA(*evp_pkey, *rsa) } { return Err(Unspecified); }; - Ok(LcPtr::new(pkey)?) + rsa.detach(); + + Ok(evp_pkey) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -665,4 +692,49 @@ mod tests { generate_encode_decode!(rsa3072_generate_encode_decode, SignatureKeySize::Rsa3072); generate_encode_decode!(rsa4096_generate_encode_decode, SignatureKeySize::Rsa4096); generate_encode_decode!(rsa8192_generate_encode_decode, SignatureKeySize::Rsa8192); + + macro_rules! generate_fips_encode_decode { + ($name:ident, $size:expr) => { + #[cfg(feature = "fips")] + #[test] + fn $name() { + let private_key = KeyPair::generate_fips($size).expect("generation"); + + assert_eq!(true, private_key.is_valid_fips_key()); + + let pkcs8v1 = private_key.as_der().expect("encoded"); + + let private_key = KeyPair::from_pkcs8(pkcs8v1.as_ref()).expect("decoded"); + + let public_key = crate::signature::KeyPair::public_key(&private_key); + + let _ = public_key.as_ref(); + } + }; + ($name:ident, $size:expr, false) => { + #[cfg(feature = "fips")] + #[test] + fn $name() { + let _ = KeyPair::generate_fips($size).expect_err("should fail for key size"); + } + }; + } + + generate_fips_encode_decode!( + rsa2048_generate_fips_encode_decode, + SignatureKeySize::Rsa2048 + ); + generate_fips_encode_decode!( + rsa3072_generate_fips_encode_decode, + SignatureKeySize::Rsa3072 + ); + generate_fips_encode_decode!( + rsa4096_generate_fips_encode_decode, + SignatureKeySize::Rsa4096 + ); + generate_fips_encode_decode!( + rsa8192_generate_fips_encode_decode, + SignatureKeySize::Rsa8192, + false + ); }