@@ -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,65 @@ 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+ /// **Note:** This method is dangerous as it can be used as a DOS vector if used with
423+ /// untrusted input https://www.imperialviolet.org/2012/03/17/rsados.html
424+ ///
425+ /// This is intended for interoperating with systems that use non-standard exponents
426+ /// or loading legacy keys. Use [`RsaPrivateKey::from_components`] for standard key
427+ /// construction.
428+ #[ cfg( feature = "hazmat" ) ]
429+ pub fn from_components_with_large_exponent (
430+ n : BoxedUint ,
431+ e : BoxedUint ,
432+ d : BoxedUint ,
433+ primes : Vec < BoxedUint > ,
434+ ) -> Result < RsaPrivateKey > {
435+ let mut k = Self :: from_components_inner ( n, e, d, primes) ?;
436+
437+ // Validate everything except exponent size bounds (to ensure precompute can't fail)
438+ validate_skip_exponent_size ( & k) ?;
439+
440+ // Precompute when possible, ignore error otherwise.
441+ k. precompute ( ) . ok ( ) ;
442+
443+ Ok ( k)
444+ }
445+
446+ /// Constructs an RSA key pair from individual components:
447+ ///
448+ /// - `n`: RSA modulus
449+ /// - `e`: public exponent (i.e. encrypting exponent)
450+ /// - `d`: private exponent (i.e. decrypting exponent)
451+ /// - `primes`: prime factors of `n`: typically two primes `p` and `q`. More than two primes can
452+ /// be provided for multiprime RSA, however this is generally not recommended. If no `primes`
453+ /// are provided, a prime factor recovery algorithm will be employed to attempt to recover the
454+ /// factors (as described in [NIST SP 800-56B Revision 2] Appendix C.2). This algorithm only
455+ /// works if there are just two prime factors `p` and `q` (as opposed to multiprime), and `e`
456+ /// is between 2^16 and 2^256.
457+ ///
458+ /// [NIST SP 800-56B Revision 2]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf
459+ pub fn from_components (
460+ n : BoxedUint ,
461+ e : BoxedUint ,
462+ d : BoxedUint ,
463+ primes : Vec < BoxedUint > ,
464+ ) -> Result < RsaPrivateKey > {
465+ let mut k = Self :: from_components_inner ( n, e, d, primes) ?;
466+
412467 // Always validate the key, to ensure precompute can't fail
413468 k. validate ( ) ?;
414469
@@ -562,36 +617,7 @@ impl RsaPrivateKey {
562617 /// Returns `Ok(())` if everything is good, otherwise an appropriate error.
563618 pub fn validate ( & self ) -> Result < ( ) > {
564619 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-
620+ validate_private_key_parts ( self ) ?;
595621 Ok ( ( ) )
596622 }
597623
@@ -686,6 +712,24 @@ fn check_public_with_max_size(n: &BoxedUint, e: &BoxedUint, max_size: Option<usi
686712 }
687713 }
688714
715+ check_public_skip_exponent_size ( n, e) ?;
716+
717+ if e < & BoxedUint :: from ( RsaPublicKey :: MIN_PUB_EXPONENT ) {
718+ return Err ( Error :: PublicExponentTooSmall ) ;
719+ }
720+
721+ if e > & BoxedUint :: from ( RsaPublicKey :: MAX_PUB_EXPONENT ) {
722+ return Err ( Error :: PublicExponentTooLarge ) ;
723+ }
724+
725+ Ok ( ( ) )
726+ }
727+
728+ /// Check that the public key is well formed, skipping exponent size bounds checks.
729+ ///
730+ /// This is used internally by both public validation functions and hazmat APIs.
731+ #[ inline]
732+ fn check_public_skip_exponent_size ( n : & BoxedUint , e : & BoxedUint ) -> Result < ( ) > {
689733 if e >= n || n. is_even ( ) . into ( ) || n. is_zero ( ) . into ( ) {
690734 return Err ( Error :: InvalidModulus ) ;
691735 }
@@ -694,17 +738,63 @@ fn check_public_with_max_size(n: &BoxedUint, e: &BoxedUint, max_size: Option<usi
694738 return Err ( Error :: InvalidExponent ) ;
695739 }
696740
697- if e < & BoxedUint :: from ( RsaPublicKey :: MIN_PUB_EXPONENT ) {
698- return Err ( Error :: PublicExponentTooSmall ) ;
741+ // Skip exponent size bounds checks
742+ Ok ( ( ) )
743+ }
744+
745+ /// Helper function that validates the private key structure and cryptographic correctness.
746+ ///
747+ /// This performs the structural and mathematical validation checks that are common to both
748+ /// `validate()` and `validate_skip_exponent_size()`.
749+ fn validate_private_key_parts ( key : & RsaPrivateKey ) -> Result < ( ) > {
750+ // Check that Πprimes == n.
751+ let mut m = BoxedUint :: one_with_precision ( key. pubkey_components . n . bits_precision ( ) ) ;
752+ let one = BoxedUint :: one ( ) ;
753+ for prime in & key. primes {
754+ // Any primes ≤ 1 will cause divide-by-zero panics later.
755+ if prime < & one {
756+ return Err ( Error :: InvalidPrime ) ;
757+ }
758+ m = m. wrapping_mul ( prime) ;
759+ }
760+ if m != * key. pubkey_components . n {
761+ return Err ( Error :: InvalidModulus ) ;
699762 }
700763
701- if e > & BoxedUint :: from ( RsaPublicKey :: MAX_PUB_EXPONENT ) {
702- return Err ( Error :: PublicExponentTooLarge ) ;
764+ // Check that de ≡ 1 mod p-1, for each prime.
765+ // This implies that e is coprime to each p-1 as e has a multiplicative
766+ // inverse. Therefore e is coprime to lcm(p-1,q-1,r-1,...) =
767+ // exponent(ℤ/nℤ). It also implies that a^de ≡ a mod p as a^(p-1) ≡ 1
768+ // mod p. Thus a^de ≡ a mod n for all a coprime to n, as required.
769+ let de = key. d . mul ( & key. pubkey_components . e ) ;
770+
771+ for prime in & key. primes {
772+ let x = NonZero :: new ( prime. wrapping_sub ( & BoxedUint :: one ( ) ) ) . unwrap ( ) ;
773+ let congruence = de. rem_vartime ( & x) ;
774+ if !bool:: from ( congruence. is_one ( ) ) {
775+ return Err ( Error :: InvalidExponent ) ;
776+ }
703777 }
704778
705779 Ok ( ( ) )
706780}
707781
782+ /// Validate the private key structure and cryptographic correctness,
783+ /// skipping only the exponent size bounds checks.
784+ ///
785+ /// This performs all the same checks as `RsaPrivateKey::validate()` except
786+ /// it doesn't verify that the exponent is within the standard bounds.
787+ #[ cfg( feature = "hazmat" ) ]
788+ fn validate_skip_exponent_size ( key : & RsaPrivateKey ) -> Result < ( ) > {
789+ // Check public key properties (without exponent size checks)
790+ check_public_skip_exponent_size ( key. pubkey_components . n . as_ref ( ) , & key. pubkey_components . e ) ?;
791+
792+ // Perform common private key validation
793+ validate_private_key_parts ( key) ?;
794+
795+ Ok ( ( ) )
796+ }
797+
708798#[ cfg( feature = "serde" ) ]
709799impl Serialize for RsaPublicKey {
710800 fn serialize < S > ( & self , serializer : S ) -> core:: prelude:: v1:: Result < S :: Ok , S :: Error >
@@ -1114,4 +1204,96 @@ mod tests {
11141204
11151205 assert_eq ! ( PrivateKeyParts :: d( & key) , PrivateKeyParts :: d( & ref_key) ) ;
11161206 }
1207+
1208+ #[ test]
1209+ #[ cfg( feature = "hazmat" ) ]
1210+ fn test_from_components_with_large_exponent ( ) {
1211+ // Test that from_components_with_large_exponent accepts exponents outside normal bounds
1212+ // while from_components would reject them
1213+
1214+ use rand:: rngs:: ChaCha8Rng ;
1215+ use rand_core:: SeedableRng ;
1216+
1217+ let mut rng = ChaCha8Rng :: from_seed ( [ 42 ; 32 ] ) ;
1218+
1219+ // Use an exponent larger than the normal maximum (2^33 - 1)
1220+ let large_e = BoxedUint :: from ( ( 1u64 << 34 ) + 1 ) ; // 2^34 + 1 (odd number)
1221+
1222+ // Generate a key with this large exponent
1223+ let components =
1224+ generate_multi_prime_key_with_exp ( & mut rng, 2 , 1024 , large_e. clone ( ) ) . unwrap ( ) ;
1225+
1226+ // Extract components
1227+ let n = components. n . get ( ) . clone ( ) ;
1228+ let d = components. d ;
1229+ let primes = components. primes ;
1230+
1231+ // from_components should fail with PublicExponentTooLarge
1232+ let result =
1233+ RsaPrivateKey :: from_components ( n. clone ( ) , large_e. clone ( ) , d. clone ( ) , primes. clone ( ) ) ;
1234+ assert ! ( result. is_err( ) ) ;
1235+ assert_eq ! ( result. unwrap_err( ) , Error :: PublicExponentTooLarge ) ;
1236+
1237+ // from_components_with_large_exponent should succeed
1238+ let key_with_large_exp = RsaPrivateKey :: from_components_with_large_exponent (
1239+ n. clone ( ) ,
1240+ large_e. clone ( ) ,
1241+ d. clone ( ) ,
1242+ primes. clone ( ) ,
1243+ ) ;
1244+ assert ! ( key_with_large_exp. is_ok( ) ) ;
1245+
1246+ let key_with_large_exp = key_with_large_exp. unwrap ( ) ;
1247+ assert_eq ! ( PublicKeyParts :: e( & key_with_large_exp) , & large_e) ;
1248+ assert_eq ! ( PublicKeyParts :: n( & key_with_large_exp) . as_ref( ) , & n) ;
1249+ assert_eq ! ( PrivateKeyParts :: d( & key_with_large_exp) , & d) ;
1250+
1251+ // Verify that the key is still cryptographically valid (de ≡ 1 mod λ(n))
1252+ // by checking that validation with skip_exponent_size passes
1253+ assert ! ( validate_skip_exponent_size( & key_with_large_exp) . is_ok( ) ) ;
1254+ }
1255+
1256+ #[ test]
1257+ #[ cfg( feature = "hazmat" ) ]
1258+ fn test_from_components_with_small_exponent ( ) {
1259+ // Test that from_components_with_large_exponent accepts exponents below normal minimum
1260+ // (despite the name, it works for any non-standard exponent size)
1261+
1262+ use rand:: rngs:: ChaCha8Rng ;
1263+ use rand_core:: SeedableRng ;
1264+
1265+ let mut rng = ChaCha8Rng :: from_seed ( [ 43 ; 32 ] ) ;
1266+
1267+ // Use an exponent smaller than the normal minimum (2)
1268+ let small_e = BoxedUint :: from ( 1u64 ) ; // This is odd, which is required
1269+
1270+ // Generate a key with this small exponent
1271+ let components =
1272+ generate_multi_prime_key_with_exp ( & mut rng, 2 , 1024 , small_e. clone ( ) ) . unwrap ( ) ;
1273+
1274+ // Extract components
1275+ let n = components. n . get ( ) . clone ( ) ;
1276+ let d = components. d ;
1277+ let primes = components. primes ;
1278+
1279+ // from_components should fail
1280+ let result =
1281+ RsaPrivateKey :: from_components ( n. clone ( ) , small_e. clone ( ) , d. clone ( ) , primes. clone ( ) ) ;
1282+ assert ! ( result. is_err( ) ) ;
1283+
1284+ // from_components_with_large_exponent should succeed
1285+ let key_with_small_exp = RsaPrivateKey :: from_components_with_large_exponent (
1286+ n. clone ( ) ,
1287+ small_e. clone ( ) ,
1288+ d. clone ( ) ,
1289+ primes,
1290+ ) ;
1291+ assert ! ( key_with_small_exp. is_ok( ) ) ;
1292+
1293+ let key_with_small_exp = key_with_small_exp. unwrap ( ) ;
1294+ assert_eq ! ( PublicKeyParts :: e( & key_with_small_exp) , & small_e) ;
1295+
1296+ // Verify that the key is cryptographically valid
1297+ assert ! ( validate_skip_exponent_size( & key_with_small_exp) . is_ok( ) ) ;
1298+ }
11171299}
0 commit comments