Skip to content

Commit

Permalink
Adds RsaPrivateKey::from_primes and RsaPrivateKey::from_p_q methods
Browse files Browse the repository at this point in the history
This is used on Yubico HSM for import/export under wrap as well as when
importing a key unsealed.
  • Loading branch information
baloo committed Nov 21, 2023
1 parent 00eaa91 commit dd51b36
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 1 deletion.
52 changes: 52 additions & 0 deletions src/algorithms/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,58 @@ 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 {
let mut n = BigUint::one();

for prime in primes {
n *= prime;
}

n
}

/// Compute the private exponent from its primes (p and q) and public exponent
pub(crate) fn compute_private_exponent(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();
}

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).
pub(crate) fn compute_private_exponent_nist_sp800_56b(
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, compute_private_exponent_nist_sp800_56b,
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_nist_sp800_56b(&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(&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 dd51b36

Please sign in to comment.