Repository: aws/aws-lc-rs (language: rust)

Issue:
feature request: RSA key generation support
### Problem:

It looks as though aws-lc-rs shares the same API limitation as *ring* with respect to key generation being supported for [EcdsaKeyPair](https://docs.rs/aws-lc-rs/latest/aws_lc_rs/signature/struct.EcdsaKeyPair.html) and [Ed25519KeyPair](https://docs.rs/aws-lc-rs/latest/aws_lc_rs/signature/struct.Ed25519KeyPair.html), but **not** [RsaKeyPair](https://docs.rs/aws-lc-rs/latest/aws_lc_rs/signature/struct.RsaKeyPair.html).

As we're evaluating using aws-lc-rs as [a backend for rcgen](https://github.com/rustls/rcgen/pull/201) it would be really nice if aws-lc-rs supported RSA key generation. There's a class of use-case (e.g. Rustls' integration tests) where we have to use the `openssl` command line in order to generate RSA keypairs. Having support for this in `rcgen` would make replacing that usage feasible.

### Solution:

aws-lc-rs could augment the `RsaKeyPair` struct to offer methods for generating a keypair comparable to those offered by `Ed25519KeyPair` and `EcdsaKeyPair`.

* **Does this change any public APIs?** Yes: the `RsaKeyPair` struct would gain a `generate_pkcs8` and possibly a `generate_pkcs1`  fns.
* **Which algorithm(s) will this impact?** RSA.

### Requirements / Acceptance Criteria:

It must be possible to generate an RSA keypair with both the public and private components. It should be convenient to load the serialized private key using the existing `RsaKeyPair::from_pkcs8` and `RsaKeyPair::from_der` methods. Acceptance should look something like an end-to-end test generating a RSA keypair, loading it, using it to produce a signature, and then verifying the signature.

* **RFC links:** https://www.rfc-editor.org/rfc/rfc8017, https://www.rfc-editor.org/rfc/rfc5208
* **Related Issues:** https://github.com/rustls/rcgen/pull/201, https://github.com/briansmith/ring/issues/219, https://github.com/briansmith/ring/pull/733
* **Will the Usage Guide or other documentation need to be updated?** Yes - API documentation would need updating.
* **Testing:** I expect testing would look similar to the existing Ed25519KeyPair and EcdsaKeyPair key generation test coverage.




Discussion / hints:
I have a branch for RSA OAEP support that also adds RSA key generation. I just need to rebase that branch and open a PR for the change.

Fix patch:
diff --git a/aws-lc-rs/src/bn.rs b/aws-lc-rs/src/bn.rs
index 01a67532069..5c90502d087 100644
--- a/aws-lc-rs/src/bn.rs
+++ b/aws-lc-rs/src/bn.rs
@@ -15,6 +15,20 @@ impl TryFrom<&[u8]> for LcPtr<BIGNUM> {
     }
 }
 
+impl TryFrom<u64> for LcPtr<BIGNUM> {
+    type Error = ();
+
+    fn try_from(value: u64) -> Result<Self, Self::Error> {
+        unsafe {
+            let bn = LcPtr::new(BN_new())?;
+            if 1 != BN_set_u64(*bn, value) {
+                return Err(());
+            }
+            Ok(bn)
+        }
+    }
+}
+
 impl TryFrom<&[u8]> for DetachableLcPtr<BIGNUM> {
     type Error = ();
 
diff --git a/aws-lc-rs/src/cbb.rs b/aws-lc-rs/src/cbb.rs
index 8ba195ced82..bb4853ade7b 100644
--- a/aws-lc-rs/src/cbb.rs
+++ b/aws-lc-rs/src/cbb.rs
@@ -1,18 +1,71 @@
 // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 // SPDX-License-Identifier: Apache-2.0 OR ISC
 
-use aws_lc::{CBB_cleanup, CBB_init, CBB};
+use crate::buffer::Buffer;
+use crate::error::Unspecified;
+use crate::ptr::LcPtr;
+use aws_lc::{CBB_cleanup, CBB_finish, CBB_init, CBB_init_fixed, CBB};
+use std::marker::PhantomData;
 use std::mem::MaybeUninit;
+use std::ptr::null_mut;
 
-pub(crate) struct LcCBB(CBB);
+pub(crate) struct LcCBB<'a>(CBB, PhantomData<&'a CBB>);
 
-impl LcCBB {
+impl LcCBB<'static> {
+    #[allow(dead_code)]
+    pub(crate) fn new(initial_capacity: usize) -> LcCBB<'static> {
+        let mut cbb = MaybeUninit::<CBB>::uninit();
+        let cbb = unsafe {
+            CBB_init(cbb.as_mut_ptr(), initial_capacity);
+            cbb.assume_init()
+        };
+        Self(cbb, PhantomData)
+    }
+
+    #[allow(dead_code)]
+    pub(crate) fn into_buffer<'a, T>(mut self) -> Result<Buffer<'a, T>, Unspecified> {
+        let mut out_data = null_mut::<u8>();
+        let mut out_len: usize = 0;
+
+        if 1 != unsafe { CBB_finish(self.as_mut_ptr(), &mut out_data, &mut out_len) } {
+            return Err(Unspecified);
+        };
+
+        let out_data = LcPtr::new(out_data)?;
+
+        // TODO: Need a type to just hold the owned pointer from CBB rather then copying
+        Ok(Buffer::take_from_slice(unsafe {
+            out_data.as_slice_mut(out_len)
+        }))
+    }
+}
+
+impl<'a> LcCBB<'a> {
+    pub(crate) fn new_fixed<const N: usize>(buffer: &'a mut [u8; N]) -> LcCBB<'a> {
+        let mut cbb = MaybeUninit::<CBB>::uninit();
+        let cbb = unsafe {
+            CBB_init_fixed(cbb.as_mut_ptr(), buffer.as_mut_ptr(), N);
+            cbb.assume_init()
+        };
+        Self(cbb, PhantomData)
+    }
+
+    pub(crate) fn finish(mut self) -> Result<usize, Unspecified> {
+        let mut pkcs8_bytes_ptr = null_mut::<u8>();
+        let mut out_len: usize = 0;
+        if 1 != unsafe { CBB_finish(self.as_mut_ptr(), &mut pkcs8_bytes_ptr, &mut out_len) } {
+            return Err(Unspecified);
+        }
+        Ok(out_len)
+    }
+}
+impl LcCBB<'_> {
     pub(crate) fn as_mut_ptr(&mut self) -> *mut CBB {
         &mut self.0
     }
 }
 
-impl Drop for LcCBB {
+impl Drop for LcCBB<'_> {
     fn drop(&mut self) {
         unsafe {
             CBB_cleanup(&mut self.0);
@@ -20,10 +73,40 @@ impl Drop for LcCBB {
     }
 }
 
-#[inline]
-#[allow(non_snake_case)]
-pub(crate) unsafe fn build_CBB(initial_capacity: usize) -> LcCBB {
-    let mut cbb = MaybeUninit::<CBB>::uninit();
-    CBB_init(cbb.as_mut_ptr(), initial_capacity);
-    LcCBB(cbb.assume_init())
+#[cfg(test)]
+mod tests {
+    use super::LcCBB;
+    use aws_lc::CBB_add_asn1_bool;
+
+    #[test]
+    fn dynamic_buffer() {
+        let mut cbb = LcCBB::new(4);
+        assert_eq!(1, unsafe { CBB_add_asn1_bool(cbb.as_mut_ptr(), 1) });
+        let buffer = cbb.into_buffer::<'_, ()>().expect("be copied to buffer");
+        assert_eq!(buffer.as_ref(), &[1, 1, 255]);
+    }
+
+    #[test]
+    fn dynamic_buffer_grows() {
+        let mut cbb = LcCBB::new(1);
+        assert_eq!(1, unsafe { CBB_add_asn1_bool(cbb.as_mut_ptr(), 1) });
+        let buffer = cbb.into_buffer::<'_, ()>().expect("be copied to buffer");
+        assert_eq!(buffer.as_ref(), &[1, 1, 255]);
+    }
+
+    #[test]
+    fn fixed_buffer() {
+        let mut buffer = [0u8; 4];
+        let mut cbb = LcCBB::new_fixed(&mut buffer);
+        assert_eq!(1, unsafe { CBB_add_asn1_bool(cbb.as_mut_ptr(), 1) });
+        let out_len = cbb.finish().expect("cbb finishable");
+        assert_eq!(&buffer[..out_len], &[1, 1, 255]);
+    }
+
+    #[test]
+    fn fixed_buffer_no_growth() {
+        let mut buffer = [0u8; 1];
+        let mut cbb = LcCBB::new_fixed(&mut buffer);
+        assert_ne!(1, unsafe { CBB_add_asn1_bool(cbb.as_mut_ptr(), 1) });
+    }
 }
diff --git a/aws-lc-rs/src/encoding.rs b/aws-lc-rs/src/encoding.rs
index 351aacbe006..f01f9562aed 100644
--- a/aws-lc-rs/src/encoding.rs
+++ b/aws-lc-rs/src/encoding.rs
@@ -9,6 +9,8 @@ use crate::encoding::types::{
     EcPublicKeyX509DerType,
 };
 
+use self::types::Pkcs8V1DerType;
+
 mod types {
     pub struct EcPrivateKeyBinType {
         _priv: (),
@@ -25,6 +27,10 @@ mod types {
     pub struct Curve25519SeedBufferType {
         _priv: (),
     }
+
+    pub struct Pkcs8V1DerType {
+        _priv: (),
+    }
 }
 
 /// Trait for types that can be serialized into a DER format.
@@ -56,3 +62,6 @@ 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>;
+
+/// A PKCS#8 v1 (RFC 5208) DER encoded structure.
+pub type Pkcs8V1Der<'a> = Buffer<'a, Pkcs8V1DerType>;
diff --git a/aws-lc-rs/src/evp_pkey.rs b/aws-lc-rs/src/evp_pkey.rs
index 3a82abd852d..6a50a1bc361 100644
--- a/aws-lc-rs/src/evp_pkey.rs
+++ b/aws-lc-rs/src/evp_pkey.rs
@@ -1,19 +1,17 @@
 // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 // SPDX-License-Identifier: Apache-2.0 OR ISC
 
+use crate::cbb::LcCBB;
+use crate::cbs;
 use crate::ec::PKCS8_DOCUMENT_MAX_LEN;
 use crate::error::{KeyRejected, Unspecified};
 use crate::pkcs8::{Document, Version};
 use crate::ptr::LcPtr;
-use crate::{cbb, cbs};
 use aws_lc::{
-    CBB_finish, EVP_PKEY_bits, EVP_PKEY_get1_EC_KEY, EVP_PKEY_get1_RSA, EVP_PKEY_id,
-    EVP_marshal_private_key, EVP_marshal_private_key_v2, EVP_parse_private_key, EC_KEY, EVP_PKEY,
-    RSA,
+    EVP_PKEY_bits, EVP_PKEY_get1_EC_KEY, EVP_PKEY_get1_RSA, EVP_PKEY_id, EVP_marshal_private_key,
+    EVP_marshal_private_key_v2, EVP_parse_private_key, EC_KEY, EVP_PKEY, RSA,
 };
-use std::mem::MaybeUninit;
 use std::os::raw::c_int;
-use std::ptr::null_mut;
 
 impl TryFrom<&[u8]> for LcPtr<EVP_PKEY> {
     type Error = KeyRejected;
@@ -85,38 +83,30 @@ impl LcPtr<EVP_PKEY> {
     }
 
     pub(crate) fn marshall_private_key(&self, version: Version) -> Result<Document, Unspecified> {
-        unsafe {
-            let mut cbb = cbb::build_CBB(PKCS8_DOCUMENT_MAX_LEN);
+        let mut buffer = vec![0u8; PKCS8_DOCUMENT_MAX_LEN];
+
+        let out_len = {
+            let mut cbb = LcCBB::new_fixed(<&mut [u8; PKCS8_DOCUMENT_MAX_LEN]>::try_from(
+                buffer.as_mut_slice(),
+            )?);
 
             match version {
                 Version::V1 => {
-                    if 1 != EVP_marshal_private_key(cbb.as_mut_ptr(), **self) {
+                    if 1 != unsafe { EVP_marshal_private_key(cbb.as_mut_ptr(), **self) } {
                         return Err(Unspecified);
                     }
                 }
                 Version::V2 => {
-                    if 1 != EVP_marshal_private_key_v2(cbb.as_mut_ptr(), **self) {
+                    if 1 != unsafe { EVP_marshal_private_key_v2(cbb.as_mut_ptr(), **self) } {
                         return Err(Unspecified);
                     }
                 }
             }
+            cbb.finish()?
+        };
 
-            let mut pkcs8_bytes_ptr = null_mut::<u8>();
-            let mut out_len = MaybeUninit::<usize>::uninit();
-            if 1 != CBB_finish(cbb.as_mut_ptr(), &mut pkcs8_bytes_ptr, out_len.as_mut_ptr()) {
-                return Err(Unspecified);
-            }
-            let pkcs8_bytes_ptr = LcPtr::new(pkcs8_bytes_ptr)?;
-            let out_len = out_len.assume_init();
+        buffer.truncate(out_len);
 
-            let bytes_slice = pkcs8_bytes_ptr.as_slice(out_len);
-            let mut pkcs8_bytes = [0u8; PKCS8_DOCUMENT_MAX_LEN];
-            pkcs8_bytes[0..out_len].copy_from_slice(bytes_slice);
-
-            Ok(Document {
-                bytes: pkcs8_bytes,
-                len: out_len,
-            })
-        }
+        Ok(Document::new(buffer.into_boxed_slice()))
     }
 }
diff --git a/aws-lc-rs/src/pkcs8.rs b/aws-lc-rs/src/pkcs8.rs
index 095cb6da19f..6c46f6bd7f0 100644
--- a/aws-lc-rs/src/pkcs8.rs
+++ b/aws-lc-rs/src/pkcs8.rs
@@ -7,19 +7,23 @@
 //!
 //! [RFC 5208]: https://tools.ietf.org/html/rfc5208.
 
-use crate::ec;
 use zeroize::Zeroize;
 
 /// A generated PKCS#8 document.
 pub struct Document {
-    pub(crate) bytes: [u8; ec::PKCS8_DOCUMENT_MAX_LEN],
-    pub(crate) len: usize,
+    bytes: Box<[u8]>,
+}
+
+impl Document {
+    pub(crate) fn new(bytes: Box<[u8]>) -> Self {
+        Self { bytes }
+    }
 }
 
 impl AsRef<[u8]> for Document {
     #[inline]
     fn as_ref(&self) -> &[u8] {
-        &self.bytes[..self.len]
+        &self.bytes
     }
 }
 
diff --git a/aws-lc-rs/src/ptr.rs b/aws-lc-rs/src/ptr.rs
index 5103cc5f146..6f8dc6b850a 100644
--- a/aws-lc-rs/src/ptr.rs
+++ b/aws-lc-rs/src/ptr.rs
@@ -40,6 +40,11 @@ impl<P: Pointer> ManagedPointer<P> {
     pub unsafe fn as_slice(&self, len: usize) -> &[P::T] {
         std::slice::from_raw_parts(self.pointer.as_const_ptr(), len)
     }
+
+    #[allow(clippy::mut_from_ref)]
+    pub unsafe fn as_slice_mut(&self, len: usize) -> &mut [P::T] {
+        std::slice::from_raw_parts_mut(self.pointer.as_mut_ptr(), len)
+    }
 }
 
 impl<P: Pointer> Drop for ManagedPointer<P> {
@@ -160,6 +165,7 @@ pub(crate) trait Pointer {
 
     fn free(&mut self);
     fn as_const_ptr(&self) -> *const Self::T;
+    fn as_mut_ptr(&self) -> *mut Self::T;
 }
 
 pub(crate) trait IntoPointer<P> {
@@ -190,9 +196,15 @@ macro_rules! create_pointer {
                 }
             }
 
+            #[inline]
             fn as_const_ptr(&self) -> *const Self::T {
                 self.cast()
             }
+
+            #[inline]
+            fn as_mut_ptr(&self) -> *mut Self::T {
+                *self
+            }
         }
     };
 }
diff --git a/aws-lc-rs/src/rsa.rs b/aws-lc-rs/src/rsa.rs
index e836d8b5e88..82f77b1f4b7 100644
--- a/aws-lc-rs/src/rsa.rs
+++ b/aws-lc-rs/src/rsa.rs
@@ -10,10 +10,11 @@
 // naming conventions. Also the standard camelCase names are used for `KeyPair`
 // components.
 
+mod encoding;
 pub(crate) mod key;
 pub(crate) mod signature;
 
-pub use self::key::{KeyPair, PublicKey, PublicKeyComponents};
+pub use self::key::{KeyPair, KeySize, PublicKey, PublicKeyComponents};
 #[allow(clippy::module_name_repetitions)]
 pub use self::signature::RsaParameters;
 
diff --git a/aws-lc-rs/src/rsa/encoding.rs b/aws-lc-rs/src/rsa/encoding.rs
new file mode 100644
index 00000000000..63902999312
--- /dev/null
+++ b/aws-lc-rs/src/rsa/encoding.rs
@@ -0,0 +1,39 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0 OR ISC
+
+/// PKCS#8 Encoding Functions
+pub(in super::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
+    // rounded to an even 64-bit words (4678 + 1% + padding ≈ 4728).
+    const PKCS8_FIXED_CAPACITY_BUFFER: usize = 4728;
+
+    pub(in super::super) fn encode_v1_der(key: &LcPtr<EVP_PKEY>) -> Result<Vec<u8>, Unspecified> {
+        let mut buffer = vec![0u8; PKCS8_FIXED_CAPACITY_BUFFER];
+        let out_len = {
+            let mut cbb = LcCBB::new_fixed(<&mut [u8; PKCS8_FIXED_CAPACITY_BUFFER]>::try_from(
+                buffer.as_mut_slice(),
+            )?);
+
+            if 1 != unsafe { EVP_marshal_private_key(cbb.as_mut_ptr(), *key.as_const()) } {
+                return Err(Unspecified);
+            }
+            cbb.finish()?
+        };
+
+        buffer.truncate(out_len);
+
+        Ok(buffer)
+    }
+
+    // Supports v1 and v2 encodings through a single API entry-point.
+    pub(in super::super) fn decode_der(pkcs8: &[u8]) -> Result<LcPtr<EVP_PKEY>, KeyRejected> {
+        LcPtr::try_from(pkcs8)
+    }
+}
diff --git a/aws-lc-rs/src/rsa/key.rs b/aws-lc-rs/src/rsa/key.rs
index 6acfca5f1d9..3f920424e93 100644
--- a/aws-lc-rs/src/rsa/key.rs
+++ b/aws-lc-rs/src/rsa/key.rs
@@ -10,12 +10,19 @@ use core::{
     ptr::null_mut,
 };
 
+use crate::{
+    buffer::Buffer,
+    encoding::{AsDer, Pkcs8V1Der},
+    fips::indicator_check,
+};
+
 #[cfg(feature = "fips")]
 use aws_lc::RSA_check_fips;
 use aws_lc::{
-    EVP_DigestSignInit, EVP_PKEY_assign_RSA, EVP_PKEY_new, RSA_get0_e, RSA_get0_n, RSA_get0_p,
-    RSA_get0_q, RSA_new, RSA_parse_private_key, RSA_parse_public_key, RSA_public_key_to_bytes,
-    RSA_set0_key, RSA_size, EVP_PKEY, EVP_PKEY_CTX, RSA,
+    EVP_DigestSignInit, EVP_PKEY_assign_RSA, EVP_PKEY_new, RSA_generate_key_ex,
+    RSA_generate_key_fips, RSA_get0_e, RSA_get0_n, RSA_get0_p, RSA_get0_q, RSA_new,
+    RSA_parse_private_key, RSA_parse_public_key, RSA_public_key_to_bytes, RSA_set0_key, RSA_size,
+    BIGNUM, EVP_PKEY, EVP_PKEY_CTX, RSA,
 };
 
 use mirai_annotations::verify_unreachable;
@@ -26,6 +33,7 @@ use untrusted::Input;
 use zeroize::Zeroize;
 
 use super::{
+    encoding,
     signature::{compute_rsa_signature, RsaEncoding, RsaPadding},
     RsaParameters,
 };
@@ -41,6 +49,50 @@ use crate::{
     sealed::Sealed,
 };
 
+/// RSA key-size.
+#[allow(clippy::module_name_repetitions)]
+#[non_exhaustive]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum KeySize {
+    /// 2048-bit key
+    Rsa2048,
+
+    /// 3072-bit key
+    Rsa3072,
+
+    /// 4096-bit key
+    Rsa4096,
+
+    /// 8192-bit key
+    Rsa8192,
+}
+
+#[allow(clippy::len_without_is_empty)]
+impl KeySize {
+    /// Returns the size of the key in bytes.
+    #[inline]
+    #[must_use]
+    pub fn len(self) -> usize {
+        match self {
+            Self::Rsa2048 => 256,
+            Self::Rsa3072 => 384,
+            Self::Rsa4096 => 512,
+            Self::Rsa8192 => 1024,
+        }
+    }
+
+    /// Returns the key size in bits.
+    #[inline]
+    fn bit_len(self) -> i32 {
+        match self {
+            Self::Rsa2048 => 2048,
+            Self::Rsa3072 => 3072,
+            Self::Rsa4096 => 4096,
+            Self::Rsa8192 => 8192,
+        }
+    }
+}
+
 /// An RSA key pair, used for signing.
 #[allow(clippy::module_name_repetitions)]
 pub struct KeyPair {
@@ -70,8 +122,34 @@ impl KeyPair {
         }
     }
 
+    /// Generate a RSA `KeyPair` of the specified key-strength.
+    ///
+    /// # Errors
+    /// * `Unspecified`: Any key generation failure.
+    pub fn generate(size: KeySize) -> Result<Self, Unspecified> {
+        let private_key = generate_rsa_key(size.bit_len(), false)?;
+        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: KeySize) -> Result<Self, Unspecified> {
+        let private_key = generate_rsa_key(size.bit_len(), true)?;
+        Self::new(private_key).map_err(|_| Unspecified)
+    }
+
     /// Parses an unencrypted PKCS#8-encoded RSA private key.
     ///
+    /// A RSA keypair may be generated using [`KeyPair::generate`].
+    ///
     /// Only two-prime (not multi-prime) keys are supported. The public modulus
     /// (n) must be at least 2047 bits. The public modulus must be no larger
     /// than 4096 bits. It is recommended that the public modulus be exactly
@@ -119,7 +197,7 @@ impl KeyPair {
     /// not acceptable.
     pub fn from_pkcs8(pkcs8: &[u8]) -> Result<Self, KeyRejected> {
         unsafe {
-            let evp_pkey = LcPtr::try_from(pkcs8)?;
+            let evp_pkey = encoding::pkcs8::decode_der(pkcs8)?;
             Self::validate_rsa_pkey(&evp_pkey)?;
             Self::new(evp_pkey)
         }
@@ -283,6 +361,12 @@ impl crate::signature::KeyPair for KeyPair {
     }
 }
 
+impl AsDer<Pkcs8V1Der<'static>> for KeyPair {
+    fn as_der(&self) -> Result<Pkcs8V1Der<'static>, Unspecified> {
+        Ok(Buffer::new(encoding::pkcs8::encode_v1_der(&self.evp_pkey)?))
+    }
+}
+
 #[inline]
 #[allow(non_snake_case)]
 unsafe fn build_private_RSA_PKEY(private_key: &[u8]) -> Result<LcPtr<EVP_PKEY>, KeyRejected> {
@@ -499,3 +583,35 @@ unsafe fn serialize_RSA_pubkey(pubkey: &ConstPointer<RSA>) -> Result<Box<[u8]>,
     let pubkey_vec = Vec::from(pubkey_slice);
     Ok(pubkey_vec.into_boxed_slice())
 }
+
+pub(super) fn generate_rsa_key(
+    size: std::os::raw::c_int,
+    fips: bool,
+) -> Result<LcPtr<EVP_PKEY>, 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.
+
+    const RSA_F4: u64 = 65537;
+
+    let rsa = DetachableLcPtr::new(unsafe { RSA_new() })?;
+
+    if 1 != if fips {
+        indicator_check!(unsafe { RSA_generate_key_fips(*rsa, size, null_mut()) })
+    } else {
+        let e: LcPtr<BIGNUM> = RSA_F4.try_into()?;
+        unsafe { RSA_generate_key_ex(*rsa, size, *e, null_mut()) }
+    } {
+        return Err(Unspecified);
+    }
+
+    let evp_pkey = LcPtr::new(unsafe { EVP_PKEY_new() })?;
+
+    if 1 != unsafe { EVP_PKEY_assign_RSA(*evp_pkey, *rsa) } {
+        return Err(Unspecified);
+    };
+
+    rsa.detach();
+
+    Ok(evp_pkey)
+}
diff --git a/aws-lc-rs/src/signature.rs b/aws-lc-rs/src/signature.rs
index 3a6c986c73b..0764dba0e64 100644
--- a/aws-lc-rs/src/signature.rs
+++ b/aws-lc-rs/src/signature.rs
@@ -245,9 +245,8 @@ use std::fmt::{Debug, Formatter};
 use untrusted::Input;
 
 pub use crate::rsa::{
-    key::{PublicKey as RsaPublicKey, PublicKeyComponents as RsaPublicKeyComponents},
-    signature::RsaEncoding,
-    KeyPair as RsaKeyPair, PublicKey as RsaSubjectPublicKey, RsaParameters,
+    signature::RsaEncoding, KeyPair as RsaKeyPair, PublicKey as RsaSubjectPublicKey,
+    PublicKeyComponents as RsaPublicKeyComponents, RsaParameters,
 };
 
 use crate::rsa::{


Test patch:
diff --git a/aws-lc-rs/tests/rsa_test.rs b/aws-lc-rs/tests/rsa_test.rs
index af2925086e4..0a6bc491bd9 100644
--- a/aws-lc-rs/tests/rsa_test.rs
+++ b/aws-lc-rs/tests/rsa_test.rs
@@ -3,7 +3,11 @@
 // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 // SPDX-License-Identifier: Apache-2.0 OR ISC
 
-use aws_lc_rs::signature::{KeyPair, RsaKeyPair, RsaParameters, RsaPublicKey};
+use aws_lc_rs::encoding::AsDer;
+use aws_lc_rs::rsa::KeySize;
+use aws_lc_rs::signature::{
+    KeyPair, RsaKeyPair, RsaParameters, RsaPublicKeyComponents, RsaSubjectPublicKey,
+};
 use aws_lc_rs::test::to_hex_upper;
 use aws_lc_rs::{rand, signature, test, test_file};
 
@@ -11,8 +15,8 @@ use aws_lc_rs::{rand, signature, test, test_file};
 fn rsa_traits() {
     test::compile_time_assert_send::<RsaKeyPair>();
     test::compile_time_assert_sync::<RsaKeyPair>();
-    test::compile_time_assert_send::<signature::RsaPublicKey>();
-    test::compile_time_assert_sync::<signature::RsaPublicKey>();
+    test::compile_time_assert_send::<signature::RsaSubjectPublicKey>();
+    test::compile_time_assert_sync::<signature::RsaSubjectPublicKey>();
     test::compile_time_assert_send::<signature::RsaPublicKeyComponents<&[u8]>>();
     test::compile_time_assert_sync::<signature::RsaPublicKeyComponents<&[u8]>>();
     test::compile_time_assert_send::<signature::RsaPublicKeyComponents<Vec<u8>>>();
@@ -249,7 +253,7 @@ fn rsa_test_public_key_coverage() {
 
     // Test `Clone`.
     #[allow(let_underscore_drop)]
-    let _: RsaPublicKey = pubkey.clone();
+    let _: RsaSubjectPublicKey = pubkey.clone();
 
     #[cfg(feature = "ring-io")]
     assert_eq!(
@@ -267,3 +271,72 @@ fn rsa_test_public_key_coverage() {
         format!("{key_pair:?}")
     );
 }
+
+#[test]
+fn keysize_len() {
+    assert_eq!(KeySize::Rsa2048.len(), 256);
+    assert_eq!(KeySize::Rsa3072.len(), 384);
+    assert_eq!(KeySize::Rsa4096.len(), 512);
+    assert_eq!(KeySize::Rsa8192.len(), 1024);
+}
+
+macro_rules! generate_encode_decode {
+    ($name:ident, $size:expr) => {
+        #[test]
+        fn $name() {
+            let private_key = RsaKeyPair::generate($size).expect("generation");
+
+            let pkcs8v1 = private_key.as_der().expect("encoded");
+
+            let private_key = RsaKeyPair::from_pkcs8(pkcs8v1.as_ref()).expect("decoded");
+
+            let public_key = crate::signature::KeyPair::public_key(&private_key);
+
+            let _ = public_key.as_ref();
+        }
+    };
+}
+
+generate_encode_decode!(rsa2048_generate_encode_decode, KeySize::Rsa2048);
+generate_encode_decode!(rsa3072_generate_encode_decode, KeySize::Rsa3072);
+generate_encode_decode!(rsa4096_generate_encode_decode, KeySize::Rsa4096);
+generate_encode_decode!(rsa8192_generate_encode_decode, KeySize::Rsa8192);
+
+macro_rules! generate_fips_encode_decode {
+    ($name:ident, $size:expr) => {
+        #[cfg(feature = "fips")]
+        #[test]
+        fn $name() {
+            let private_key = RsaKeyPair::generate_fips($size).expect("generation");
+
+            let pkcs8v1 = private_key.as_der().expect("encoded");
+
+            let private_key = RsaKeyPair::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 _ = RsaKeyPair::generate_fips($size).expect_err("should fail for key size");
+        }
+    };
+}
+
+generate_fips_encode_decode!(rsa2048_generate_fips_encode_decode, KeySize::Rsa2048);
+generate_fips_encode_decode!(rsa3072_generate_fips_encode_decode, KeySize::Rsa3072);
+generate_fips_encode_decode!(rsa4096_generate_fips_encode_decode, KeySize::Rsa4096);
+generate_fips_encode_decode!(rsa8192_generate_fips_encode_decode, KeySize::Rsa8192, false);
+
+#[test]
+fn public_key_components_clone_debug() {
+    let pkc = RsaPublicKeyComponents::<&[u8]> {
+        n: &[0x63, 0x61, 0x6d, 0x65, 0x6c, 0x6f, 0x74],
+        e: &[0x61, 0x76, 0x61, 0x6c, 0x6f, 0x6e],
+    };
+    assert_eq!("RsaPublicKeyComponents { n: [99, 97, 109, 101, 108, 111, 116], e: [97, 118, 97, 108, 111, 110] }", format!("{pkc:?}"));
+}