From 7badaa39fec5360d5c0e9c66a24f7ae5abec704f Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Tue, 21 Nov 2023 12:31:09 -0800 Subject: [PATCH] Adds `RsaPrivateKey::from_primes` and `RsaPrivateKey::from_p_q` methods This is used on Yubico HSM for import/export under wrap as well as when importing a key unsealed. --- src/algorithms/generate.rs | 22 ++--- src/algorithms/rsa.rs | 58 ++++++++++++ src/key.rs | 89 +++++++++++++++++- .../examples/pkcs8/rsa2048-sp800-56b-priv.der | Bin 0 -> 1217 bytes 4 files changed, 154 insertions(+), 15 deletions(-) create mode 100644 tests/examples/pkcs8/rsa2048-sp800-56b-priv.der diff --git a/src/algorithms/generate.rs b/src/algorithms/generate.rs index 8908fd0b..7fa8acc1 100644 --- a/src/algorithms/generate.rs +++ b/src/algorithms/generate.rs @@ -1,14 +1,16 @@ //! Generate prime components for the RSA Private Key use alloc::vec::Vec; -use num_bigint::traits::ModInverse; use num_bigint::{BigUint, RandPrime}; #[allow(unused_imports)] use num_traits::Float; -use num_traits::{One, Zero}; +use num_traits::Zero; use rand_core::CryptoRngCore; -use crate::errors::{Error, Result}; +use crate::{ + algorithms::rsa::{compute_modulus, compute_private_exponent_euler_totient}, + errors::{Error, Result}, +}; pub struct RsaPrivateKeyComponents { pub n: BigUint, @@ -89,13 +91,7 @@ pub(crate) fn generate_multi_prime_key_with_exp( } } - let mut n = BigUint::one(); - let mut totient = BigUint::one(); - - for prime in &primes { - n *= prime; - totient *= prime - BigUint::one(); - } + let n = compute_modulus(&primes); if n.bits() != bit_size { // This should never happen for nprimes == 2 because @@ -104,11 +100,9 @@ pub(crate) fn generate_multi_prime_key_with_exp( continue 'next; } - // NOTE: `mod_inverse` checks if `exp` evenly divides `totient` and returns `None` if so. - // This ensures that `exp` is not a factor of any `(prime - 1)`. - if let Some(d) = exp.mod_inverse(totient) { + if let Ok(d) = compute_private_exponent_euler_totient(&primes, exp) { n_final = n; - d_final = d.to_biguint().unwrap(); + d_final = d; break; } } diff --git a/src/algorithms/rsa.rs b/src/algorithms/rsa.rs index 6473f378..35101526 100644 --- a/src/algorithms/rsa.rs +++ b/src/algorithms/rsa.rs @@ -245,6 +245,64 @@ pub fn recover_primes(n: &BigUint, e: &BigUint, d: &BigUint) -> Result<(BigUint, Ok((p, q)) } +/// Compute the modulus of a key from its primes. +pub(crate) fn compute_modulus(primes: &[BigUint]) -> BigUint { + primes.iter().product() +} + +/// Compute the private exponent from its primes (p and q) and public exponent +/// This uses Euler's totient function +#[inline] +pub(crate) fn compute_private_exponent_euler_totient( + primes: &[BigUint], + exp: &BigUint, +) -> Result { + if primes.len() < 2 { + return Err(Error::InvalidPrime); + } + + let mut totient = BigUint::one(); + + for prime in primes { + totient *= prime - BigUint::one(); + } + + // NOTE: `mod_inverse` checks if `exp` evenly divides `totient` and returns `None` if so. + // This ensures that `exp` is not a factor of any `(prime - 1)`. + if let Some(d) = exp.mod_inverse(totient) { + Ok(d.to_biguint().unwrap()) + } else { + // `exp` evenly divides `totient` + Err(Error::InvalidPrime) + } +} + +/// Compute the private exponent from its primes (p and q) and public exponent +/// +/// This is using the method defined by +/// [NIST 800-56B Section 6.2.1](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf#page=47). +/// (Carmichael function) +/// +/// FIPS 186-4 **requires** the private exponent to be less than λ(n), which would +/// make Euler's totiem unreliable. +#[inline] +pub(crate) fn compute_private_exponent_carmicheal( + p: &BigUint, + q: &BigUint, + exp: &BigUint, +) -> Result { + let p1 = p - BigUint::one(); + let q1 = q - BigUint::one(); + + let lcm = p1.lcm(&q1); + if let Some(d) = exp.mod_inverse(lcm) { + Ok(d.to_biguint().unwrap()) + } else { + // `exp` evenly divides `lcm` + Err(Error::InvalidPrime) + } +} + #[cfg(test)] mod tests { use num_traits::FromPrimitive; diff --git a/src/key.rs b/src/key.rs index 9589e1d3..5e6de22f 100644 --- a/src/key.rs +++ b/src/key.rs @@ -11,7 +11,10 @@ use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; use crate::algorithms::generate::generate_multi_prime_key_with_exp; -use crate::algorithms::rsa::recover_primes; +use crate::algorithms::rsa::{ + compute_modulus, compute_private_exponent_carmicheal, compute_private_exponent_euler_totient, + recover_primes, +}; use crate::dummy_rng::DummyRng; use crate::errors::{Error, Result}; @@ -279,6 +282,46 @@ impl RsaPrivateKey { Ok(k) } + /// Constructs an RSA key pair from its two primes p and q. + /// + /// This will rebuild the private exponent and the modulus. + /// + /// Private exponent will be rebuilt using the method defined in + /// [NIST 800-56B Section 6.2.1](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf#page=47). + pub fn from_p_q(p: BigUint, q: BigUint, public_exponent: BigUint) -> Result { + if p == q { + return Err(Error::InvalidPrime); + } + + let n = compute_modulus(&[p.clone(), q.clone()]); + let d = compute_private_exponent_carmicheal(&p, &q, &public_exponent)?; + + Self::from_components(n, public_exponent, d, vec![p, q]) + } + + /// Constructs an RSA key pair from its primes. + /// + /// This will rebuild the private exponent and the modulus. + pub fn from_primes(primes: Vec, public_exponent: BigUint) -> Result { + if primes.len() < 2 { + return Err(Error::NprimesTooSmall); + } + + // Makes sure that primes is pairwise unequal. + for (i, prime1) in primes.iter().enumerate() { + for prime2 in primes.iter().take(i) { + if prime1 == prime2 { + return Err(Error::InvalidPrime); + } + } + } + + let n = compute_modulus(&primes); + let d = compute_private_exponent_euler_totient(&primes, &public_exponent)?; + + Self::from_components(n, public_exponent, d, primes) + } + /// Get the public key from the private key, cloning `n` and `e`. /// /// Generally this is not needed since `RsaPrivateKey` implements the `PublicKey` trait, @@ -495,6 +538,7 @@ mod tests { use hex_literal::hex; use num_traits::{FromPrimitive, ToPrimitive}; + use pkcs8::DecodePrivateKey; use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; #[test] @@ -753,4 +797,47 @@ mod tests { Error::ModulusTooLarge ); } + + #[test] + fn build_key_from_primes() { + const RSA_2048_PRIV_DER: &[u8] = include_bytes!("../tests/examples/pkcs8/rsa2048-priv.der"); + let ref_key = RsaPrivateKey::from_pkcs8_der(RSA_2048_PRIV_DER).unwrap(); + assert_eq!(ref_key.validate(), Ok(())); + + let primes = ref_key.primes().to_vec(); + + let exp = ref_key.e().clone(); + let key = + RsaPrivateKey::from_primes(primes, exp).expect("failed to import key from primes"); + assert_eq!(key.validate(), Ok(())); + + assert_eq!(key.n(), ref_key.n()); + + assert_eq!(key.dp(), ref_key.dp()); + assert_eq!(key.dq(), ref_key.dq()); + + assert_eq!(key.d(), ref_key.d()); + } + + #[test] + fn build_key_from_p_q() { + const RSA_2048_SP800_PRIV_DER: &[u8] = + include_bytes!("../tests/examples/pkcs8/rsa2048-sp800-56b-priv.der"); + let ref_key = RsaPrivateKey::from_pkcs8_der(RSA_2048_SP800_PRIV_DER).unwrap(); + assert_eq!(ref_key.validate(), Ok(())); + + let primes = ref_key.primes().to_vec(); + let exp = ref_key.e().clone(); + + let key = RsaPrivateKey::from_p_q(primes[0].clone(), primes[1].clone(), exp) + .expect("failed to import key from primes"); + assert_eq!(key.validate(), Ok(())); + + assert_eq!(key.n(), ref_key.n()); + + assert_eq!(key.dp(), ref_key.dp()); + assert_eq!(key.dq(), ref_key.dq()); + + assert_eq!(key.d(), ref_key.d()); + } } diff --git a/tests/examples/pkcs8/rsa2048-sp800-56b-priv.der b/tests/examples/pkcs8/rsa2048-sp800-56b-priv.der new file mode 100644 index 0000000000000000000000000000000000000000..7c03cbaa9a77113575e3cd5bcca91b449d9f096e GIT binary patch literal 1217 zcmV;y1U~yPf&{$+0RS)!1_>&LNQUrr!ay9qXGc{0)hbn0HzZvroZ5z zS*uI)t(!6UY58u~=2H=q5%r21V8E;qs=P3%W&B#>G+`{^J0DpC`smc8noT+nhS?En zpr$f82g{O`&uincpw<3x0ev!@2#QK{7?ZEXSfqd6dypr36qtLh&AKFO{Fbgr5oy)t zPRBavCG4Y%)6BbWQ#wpW#X5pu5-Ls(4llMhaY3tQh?)Qe6XpW9(lVuzNe(r3M-%iO zlKC4qcL@&;m4(8gOBlR**&U??2hEx$fWFw&;4~@&3K}wPa!{Q;FS!*7AMSao$)mEO z-(%C4V@#z&HRZW47iE{Gn8Ii>sF@dNKO>%V?p?~HGcWe{@4+R9(E8Phssj}rcmB1&Ce@TvIS~-TuJI3JJxm>K zk(D$>q9|&1#o_{YxT_FlCh%jB>Z;V^-}21(rD1@JP5F9}M2xeePS+QUhEqnOCRF#l z6aJ#87I5RLzee}lQnw^2hN1kDn%0M(yyYP^GVrwm8_mtD(qHL8`{(*;SbEe7U+e71 zmZ}|6WwtK5GKYzyqk3JI5P(VGW9n!YT-wiz4W)PhZ^W$p-Z;2;*#V-6?0nDnH9XY- zi#Z*v)FEAD0UhGXpUNZ1iFwP0?ov^VxMf3O1^?02_JV7})Qz6N&9CDD0)c@5)AuR2 zGHGhefl2^-C%kxU5yR{5RkxE}OWsYz05Y_epn~ws!*p`O_{KC$y;leb^MS|>3jq*t zX*P<}DlQ8Qq!&vUv0otxKap`*%SNee5kI6M%5(F^rRC{7XsvQp^_+PtygU1)B zA)&uD_f(EDbn#Pd3d||BOD|wZ#1hV3zh;Lus}AiL2=-@+5RHEeWJR;4oVb^ujxmrp zjBHh!a`7#6<|-&PGSccG(Ce7tJX+n5XNQ(JhKts^-l9*F&?~_^j@pG0)c@5 zpsv7I*#=_huBH2-V5t@y&W6}VZ-QT29aUpp5Z+%|#7Z9H3e+y&Fqw!l7-DXF;-sRJ zh@k-HMobHbGIf6V(Kow1HNdEui!bF}>ysdY1if0`Cd?_LkVUS(m6y(u?L+nRI-OV= zWYi<HDB(`;!g$} z8h8qX6um6JKq5Q53}4Jw1*vDO)I_K>59t)(t*SLpK}`BZlh#|qC+@iSpRx=iFMPyO6M9&||F@!>T||5V z0)c@5n1I@6x0)x@cnF@X*G#_cUxf#8li1JaeDfCl!d>bKO!MkgL9rlEnypHwOGAut zi*0qW`nS2gGc-wvKCHkP5R)>}in@gQR?PsgVl}Pa)*&3As)#`W=Mz%(^C-gpR{SaJ f$B@(zQLS{pMc2VGdm+**Fuk&->T>Ys56DCP9Y;vY literal 0 HcmV?d00001