Skip to content

Commit

Permalink
Adds RsaPrivateKey::from_primes and RsaPrivateKey::from_p_q metho…
Browse files Browse the repository at this point in the history
…ds (#386)

This is used on Yubico HSM for import/export under wrap as well as when
importing a key unsealed.
  • Loading branch information
baloo authored Nov 28, 2023
1 parent 00eaa91 commit 63409e5
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 15 deletions.
22 changes: 8 additions & 14 deletions src/algorithms/generate.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -89,13 +91,7 @@ pub(crate) fn generate_multi_prime_key_with_exp<R: CryptoRngCore + ?Sized>(
}
}

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
Expand All @@ -104,11 +100,9 @@ pub(crate) fn generate_multi_prime_key_with_exp<R: CryptoRngCore + ?Sized>(
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;
}
}
Expand Down
58 changes: 58 additions & 0 deletions src/algorithms/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BigUint> {
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<BigUint> {
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;
Expand Down
89 changes: 88 additions & 1 deletion src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<RsaPrivateKey> {
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<BigUint>, public_exponent: BigUint) -> Result<RsaPrivateKey> {
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,
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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());
}
}
Binary file added tests/examples/pkcs8/rsa2048-sp800-56b-priv.der
Binary file not shown.

0 comments on commit 63409e5

Please sign in to comment.