Skip to content

Commit 200eec9

Browse files
committed
Re add support for large exponents in rsa private keys
1 parent 4f23607 commit 200eec9

1 file changed

Lines changed: 226 additions & 47 deletions

File tree

src/key.rs

Lines changed: 226 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -339,20 +339,16 @@ impl RsaPrivateKey {
339339
)
340340
}
341341

342-
/// Constructs an RSA key pair from individual components:
342+
/// Private helper function that constructs an RSA key pair from components
343+
/// WITHOUT performing any validation or precomputation.
343344
///
344-
/// - `n`: RSA modulus
345-
/// - `e`: public exponent (i.e. encrypting exponent)
346-
/// - `d`: private exponent (i.e. decrypting exponent)
347-
/// - `primes`: prime factors of `n`: typically two primes `p` and `q`. More than two primes can
348-
/// be provided for multiprime RSA, however this is generally not recommended. If no `primes`
349-
/// are provided, a prime factor recovery algorithm will be employed to attempt to recover the
350-
/// factors (as described in [NIST SP 800-56B Revision 2] Appendix C.2). This algorithm only
351-
/// works if there are just two prime factors `p` and `q` (as opposed to multiprime), and `e`
352-
/// is between 2^16 and 2^256.
345+
/// This is the shared implementation used by `from_components` and
346+
/// `from_components_with_large_exponent`.
353347
///
354-
/// [NIST SP 800-56B Revision 2]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf
355-
pub fn from_components(
348+
/// Callers are responsible for:
349+
/// 1. Validating the key (to ensure precomputation won't fail)
350+
/// 2. Calling precompute() after validation
351+
fn from_components_inner(
356352
n: BoxedUint,
357353
e: BoxedUint,
358354
d: BoxedUint,
@@ -398,7 +394,7 @@ impl RsaPrivateKey {
398394
})
399395
.collect();
400396

401-
let mut k = RsaPrivateKey {
397+
let k = RsaPrivateKey {
402398
pubkey_components: RsaPublicKey {
403399
n: n_c,
404400
e,
@@ -409,6 +405,62 @@ impl RsaPrivateKey {
409405
precomputed: None,
410406
};
411407

408+
Ok(k)
409+
}
410+
411+
/// Constructs an RSA key pair from individual components, accepting exponents outside
412+
/// the normal size bounds.
413+
///
414+
/// See [`RsaPrivateKey::from_components`] for an explanation on the parameters.
415+
///
416+
/// # ⚠️ Warning: Hazmat!
417+
///
418+
/// This method accepts public exponents outside the standard bounds (2 ≤ e ≤ 2^33-1),
419+
/// but still performs full cryptographic validation to ensure the key is mathematically
420+
/// correct (i.e., verifies that de ≡ 1 mod λ(n)).
421+
///
422+
/// This is intended for interoperating with systems that use non-standard exponents
423+
/// or loading legacy keys. Use [`RsaPrivateKey::from_components`] for standard key
424+
/// construction.
425+
#[cfg(feature = "hazmat")]
426+
pub fn from_components_with_large_exponent(
427+
n: BoxedUint,
428+
e: BoxedUint,
429+
d: BoxedUint,
430+
primes: Vec<BoxedUint>,
431+
) -> Result<RsaPrivateKey> {
432+
let mut k = Self::from_components_inner(n, e, d, primes)?;
433+
434+
// Validate everything except exponent size bounds (to ensure precompute can't fail)
435+
validate_skip_exponent_size(&k)?;
436+
437+
// Precompute when possible, ignore error otherwise.
438+
k.precompute().ok();
439+
440+
Ok(k)
441+
}
442+
443+
/// Constructs an RSA key pair from individual components:
444+
///
445+
/// - `n`: RSA modulus
446+
/// - `e`: public exponent (i.e. encrypting exponent)
447+
/// - `d`: private exponent (i.e. decrypting exponent)
448+
/// - `primes`: prime factors of `n`: typically two primes `p` and `q`. More than two primes can
449+
/// be provided for multiprime RSA, however this is generally not recommended. If no `primes`
450+
/// are provided, a prime factor recovery algorithm will be employed to attempt to recover the
451+
/// factors (as described in [NIST SP 800-56B Revision 2] Appendix C.2). This algorithm only
452+
/// works if there are just two prime factors `p` and `q` (as opposed to multiprime), and `e`
453+
/// is between 2^16 and 2^256.
454+
///
455+
/// [NIST SP 800-56B Revision 2]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf
456+
pub fn from_components(
457+
n: BoxedUint,
458+
e: BoxedUint,
459+
d: BoxedUint,
460+
primes: Vec<BoxedUint>,
461+
) -> Result<RsaPrivateKey> {
462+
let mut k = Self::from_components_inner(n, e, d, primes)?;
463+
412464
// Always validate the key, to ensure precompute can't fail
413465
k.validate()?;
414466

@@ -562,36 +614,7 @@ impl RsaPrivateKey {
562614
/// Returns `Ok(())` if everything is good, otherwise an appropriate error.
563615
pub fn validate(&self) -> Result<()> {
564616
check_public(self)?;
565-
566-
// Check that Πprimes == n.
567-
let mut m = BoxedUint::one_with_precision(self.pubkey_components.n.bits_precision());
568-
let one = BoxedUint::one();
569-
for prime in &self.primes {
570-
// Any primes ≤ 1 will cause divide-by-zero panics later.
571-
if prime < &one {
572-
return Err(Error::InvalidPrime);
573-
}
574-
m = m.wrapping_mul(prime);
575-
}
576-
if m != *self.pubkey_components.n {
577-
return Err(Error::InvalidModulus);
578-
}
579-
580-
// Check that de ≡ 1 mod p-1, for each prime.
581-
// This implies that e is coprime to each p-1 as e has a multiplicative
582-
// inverse. Therefore e is coprime to lcm(p-1,q-1,r-1,...) =
583-
// exponent(ℤ/nℤ). It also implies that a^de ≡ a mod p as a^(p-1) ≡ 1
584-
// mod p. Thus a^de ≡ a mod n for all a coprime to n, as required.
585-
let de = self.d.mul(&self.pubkey_components.e);
586-
587-
for prime in &self.primes {
588-
let x = NonZero::new(prime.wrapping_sub(&BoxedUint::one())).unwrap();
589-
let congruence = de.rem_vartime(&x);
590-
if !bool::from(congruence.is_one()) {
591-
return Err(Error::InvalidExponent);
592-
}
593-
}
594-
617+
validate_private_key_parts(self)?;
595618
Ok(())
596619
}
597620

@@ -686,6 +709,24 @@ fn check_public_with_max_size(n: &BoxedUint, e: &BoxedUint, max_size: Option<usi
686709
}
687710
}
688711

712+
check_public_skip_exponent_size(n, e)?;
713+
714+
if e < &BoxedUint::from(RsaPublicKey::MIN_PUB_EXPONENT) {
715+
return Err(Error::PublicExponentTooSmall);
716+
}
717+
718+
if e > &BoxedUint::from(RsaPublicKey::MAX_PUB_EXPONENT) {
719+
return Err(Error::PublicExponentTooLarge);
720+
}
721+
722+
Ok(())
723+
}
724+
725+
/// Check that the public key is well formed, skipping exponent size bounds checks.
726+
///
727+
/// This is used internally by both public validation functions and hazmat APIs.
728+
#[inline]
729+
fn check_public_skip_exponent_size(n: &BoxedUint, e: &BoxedUint) -> Result<()> {
689730
if e >= n || n.is_even().into() || n.is_zero().into() {
690731
return Err(Error::InvalidModulus);
691732
}
@@ -694,17 +735,63 @@ fn check_public_with_max_size(n: &BoxedUint, e: &BoxedUint, max_size: Option<usi
694735
return Err(Error::InvalidExponent);
695736
}
696737

697-
if e < &BoxedUint::from(RsaPublicKey::MIN_PUB_EXPONENT) {
698-
return Err(Error::PublicExponentTooSmall);
738+
// Skip exponent size bounds checks
739+
Ok(())
740+
}
741+
742+
/// Helper function that validates the private key structure and cryptographic correctness.
743+
///
744+
/// This performs the structural and mathematical validation checks that are common to both
745+
/// `validate()` and `validate_skip_exponent_size()`.
746+
fn validate_private_key_parts(key: &RsaPrivateKey) -> Result<()> {
747+
// Check that Πprimes == n.
748+
let mut m = BoxedUint::one_with_precision(key.pubkey_components.n.bits_precision());
749+
let one = BoxedUint::one();
750+
for prime in &key.primes {
751+
// Any primes ≤ 1 will cause divide-by-zero panics later.
752+
if prime < &one {
753+
return Err(Error::InvalidPrime);
754+
}
755+
m = m.wrapping_mul(prime);
756+
}
757+
if m != *key.pubkey_components.n {
758+
return Err(Error::InvalidModulus);
699759
}
700760

701-
if e > &BoxedUint::from(RsaPublicKey::MAX_PUB_EXPONENT) {
702-
return Err(Error::PublicExponentTooLarge);
761+
// Check that de ≡ 1 mod p-1, for each prime.
762+
// This implies that e is coprime to each p-1 as e has a multiplicative
763+
// inverse. Therefore e is coprime to lcm(p-1,q-1,r-1,...) =
764+
// exponent(ℤ/nℤ). It also implies that a^de ≡ a mod p as a^(p-1) ≡ 1
765+
// mod p. Thus a^de ≡ a mod n for all a coprime to n, as required.
766+
let de = key.d.mul(&key.pubkey_components.e);
767+
768+
for prime in &key.primes {
769+
let x = NonZero::new(prime.wrapping_sub(&BoxedUint::one())).unwrap();
770+
let congruence = de.rem_vartime(&x);
771+
if !bool::from(congruence.is_one()) {
772+
return Err(Error::InvalidExponent);
773+
}
703774
}
704775

705776
Ok(())
706777
}
707778

779+
/// Validate the private key structure and cryptographic correctness,
780+
/// skipping only the exponent size bounds checks.
781+
///
782+
/// This performs all the same checks as `RsaPrivateKey::validate()` except
783+
/// it doesn't verify that the exponent is within the standard bounds.
784+
#[cfg(feature = "hazmat")]
785+
fn validate_skip_exponent_size(key: &RsaPrivateKey) -> Result<()> {
786+
// Check public key properties (without exponent size checks)
787+
check_public_skip_exponent_size(key.pubkey_components.n.as_ref(), &key.pubkey_components.e)?;
788+
789+
// Perform common private key validation
790+
validate_private_key_parts(key)?;
791+
792+
Ok(())
793+
}
794+
708795
#[cfg(feature = "serde")]
709796
impl Serialize for RsaPublicKey {
710797
fn serialize<S>(&self, serializer: S) -> core::prelude::v1::Result<S::Ok, S::Error>
@@ -1114,4 +1201,96 @@ mod tests {
11141201

11151202
assert_eq!(PrivateKeyParts::d(&key), PrivateKeyParts::d(&ref_key));
11161203
}
1204+
1205+
#[test]
1206+
#[cfg(feature = "hazmat")]
1207+
fn test_from_components_with_large_exponent() {
1208+
// Test that from_components_with_large_exponent accepts exponents outside normal bounds
1209+
// while from_components would reject them
1210+
1211+
use rand::rngs::ChaCha8Rng;
1212+
use rand_core::SeedableRng;
1213+
1214+
let mut rng = ChaCha8Rng::from_seed([42; 32]);
1215+
1216+
// Use an exponent larger than the normal maximum (2^33 - 1)
1217+
let large_e = BoxedUint::from((1u64 << 34) + 1); // 2^34 + 1 (odd number)
1218+
1219+
// Generate a key with this large exponent
1220+
let components =
1221+
generate_multi_prime_key_with_exp(&mut rng, 2, 1024, large_e.clone()).unwrap();
1222+
1223+
// Extract components
1224+
let n = components.n.get().clone();
1225+
let d = components.d;
1226+
let primes = components.primes;
1227+
1228+
// from_components should fail with PublicExponentTooLarge
1229+
let result =
1230+
RsaPrivateKey::from_components(n.clone(), large_e.clone(), d.clone(), primes.clone());
1231+
assert!(result.is_err());
1232+
assert_eq!(result.unwrap_err(), Error::PublicExponentTooLarge);
1233+
1234+
// from_components_with_large_exponent should succeed
1235+
let key_with_large_exp = RsaPrivateKey::from_components_with_large_exponent(
1236+
n.clone(),
1237+
large_e.clone(),
1238+
d.clone(),
1239+
primes.clone(),
1240+
);
1241+
assert!(key_with_large_exp.is_ok());
1242+
1243+
let key_with_large_exp = key_with_large_exp.unwrap();
1244+
assert_eq!(PublicKeyParts::e(&key_with_large_exp), &large_e);
1245+
assert_eq!(PublicKeyParts::n(&key_with_large_exp).as_ref(), &n);
1246+
assert_eq!(PrivateKeyParts::d(&key_with_large_exp), &d);
1247+
1248+
// Verify that the key is still cryptographically valid (de ≡ 1 mod λ(n))
1249+
// by checking that validation with skip_exponent_size passes
1250+
assert!(validate_skip_exponent_size(&key_with_large_exp).is_ok());
1251+
}
1252+
1253+
#[test]
1254+
#[cfg(feature = "hazmat")]
1255+
fn test_from_components_with_small_exponent() {
1256+
// Test that from_components_with_large_exponent accepts exponents below normal minimum
1257+
// (despite the name, it works for any non-standard exponent size)
1258+
1259+
use rand::rngs::ChaCha8Rng;
1260+
use rand_core::SeedableRng;
1261+
1262+
let mut rng = ChaCha8Rng::from_seed([43; 32]);
1263+
1264+
// Use an exponent smaller than the normal minimum (2)
1265+
let small_e = BoxedUint::from(1u64); // This is odd, which is required
1266+
1267+
// Generate a key with this small exponent
1268+
let components =
1269+
generate_multi_prime_key_with_exp(&mut rng, 2, 1024, small_e.clone()).unwrap();
1270+
1271+
// Extract components
1272+
let n = components.n.get().clone();
1273+
let d = components.d;
1274+
let primes = components.primes;
1275+
1276+
// from_components should fail
1277+
let result =
1278+
RsaPrivateKey::from_components(n.clone(), small_e.clone(), d.clone(), primes.clone());
1279+
assert!(result.is_err());
1280+
1281+
// from_components_with_large_exponent should succeed
1282+
let key_with_small_exp = RsaPrivateKey::from_components_with_large_exponent(
1283+
n.clone(),
1284+
small_e.clone(),
1285+
d.clone(),
1286+
primes,
1287+
);
1288+
assert!(key_with_small_exp.is_ok());
1289+
1290+
let key_with_small_exp = key_with_small_exp.unwrap();
1291+
assert_eq!(PublicKeyParts::e(&key_with_small_exp), &small_e);
1292+
1293+
// Verify that the key is cryptographically valid
1294+
assert!(validate_skip_exponent_size(&key_with_small_exp).is_ok());
1295+
}
11171296
}

0 commit comments

Comments
 (0)