@@ -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" ) ]
709796impl 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