Skip to content

Commit 77d9996

Browse files
tarcieripinkforest
andauthored
Minimum modulus size checks (#526)
Changes the existing checked APIs to respect a minimum modulus size in addition to a maximum one. Closes #445 Co-authored-by: pinkforest(she/her) <[email protected]>
1 parent a8b1fdb commit 77d9996

15 files changed

+318
-222
lines changed

.github/workflows/workspace.yml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ jobs:
5151
RUSTDOCFLAGS: "-Dwarnings --cfg docsrs"
5252
run: cargo doc --no-deps --features std,serde,hazmat,sha2
5353

54-
typos:
55-
runs-on: ubuntu-latest
56-
steps:
57-
- uses: actions/checkout@v4
58-
- uses: crate-ci/[email protected]
54+
# does not understand concat! macro
55+
# typos:
56+
# runs-on: ubuntu-latest
57+
# steps:
58+
# - uses: actions/checkout@v4
59+
# - uses: crate-ci/[email protected]

src/errors.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ pub enum Error {
4040
/// Invalid coefficient.
4141
InvalidCoefficient,
4242

43+
/// Modulus too small.
44+
ModulusTooSmall,
45+
4346
/// Modulus too large.
4447
ModulusTooLarge,
4548

@@ -94,6 +97,7 @@ impl core::fmt::Display for Error {
9497
Error::InvalidModulus => write!(f, "invalid modulus"),
9598
Error::InvalidExponent => write!(f, "invalid exponent"),
9699
Error::InvalidCoefficient => write!(f, "invalid coefficient"),
100+
Error::ModulusTooSmall => write!(f, "modulus too small"),
97101
Error::ModulusTooLarge => write!(f, "modulus too large"),
98102
Error::PublicExponentTooSmall => write!(f, "public exponent too small"),
99103
Error::PublicExponentTooLarge => write!(f, "public exponent too large"),

src/key.rs

Lines changed: 145 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use alloc::vec::Vec;
22
use core::cmp::Ordering;
33
use core::fmt;
44
use core::hash::{Hash, Hasher};
5+
use core::ops::Range;
56

67
use crypto_bigint::modular::{BoxedMontyForm, BoxedMontyParams};
78
use crypto_bigint::{BoxedUint, Integer, NonZero, Odd, Resize};
@@ -206,29 +207,37 @@ impl RsaPublicKey {
206207
pub fn verify<S: SignatureScheme>(&self, scheme: S, hashed: &[u8], sig: &[u8]) -> Result<()> {
207208
scheme.verify(self, hashed, sig)
208209
}
209-
}
210210

211-
impl RsaPublicKey {
212211
/// Minimum value of the public exponent `e`.
213212
pub const MIN_PUB_EXPONENT: u64 = 2;
214213

215214
/// Maximum value of the public exponent `e`.
216215
pub const MAX_PUB_EXPONENT: u64 = (1 << 33) - 1;
217216

218-
/// Maximum size of the modulus `n` in bits.
219-
pub const MAX_SIZE: usize = 4096;
217+
/// Default minimum size of the modulus `n` in bits.
218+
pub const MIN_SIZE: u32 = 1024;
219+
220+
/// Default maximum size of the modulus `n` in bits.
221+
pub const MAX_SIZE: u32 = 4096;
220222

221223
/// Create a new public key from its components.
222224
///
223225
/// This function accepts public keys with a modulus size up to 4096-bits,
224226
/// i.e. [`RsaPublicKey::MAX_SIZE`].
225227
pub fn new(n: BoxedUint, e: BoxedUint) -> Result<Self> {
226-
Self::new_with_max_size(n, e, Self::MAX_SIZE)
228+
Self::new_with_size_limits(n, e, Self::MIN_SIZE..Self::MAX_SIZE)
227229
}
228230

229231
/// Create a new public key from its components.
230-
pub fn new_with_max_size(n: BoxedUint, e: BoxedUint, max_size: usize) -> Result<Self> {
231-
check_public_with_max_size(&n, &e, max_size)?;
232+
///
233+
/// Accepts a third argument which specifies a range of allowed sizes from minimum to maximum
234+
/// in bits, which by default is `1024..4096`.
235+
pub fn new_with_size_limits(
236+
n: BoxedUint,
237+
e: BoxedUint,
238+
size_range_bits: Range<u32>,
239+
) -> Result<Self> {
240+
check_public_with_size_limits(&n, &e, size_range_bits)?;
232241

233242
let n_odd = Odd::new(n.clone())
234243
.into_option()
@@ -239,19 +248,30 @@ impl RsaPublicKey {
239248
Ok(Self { n, e, n_params })
240249
}
241250

251+
/// Deprecated: this has been replaced with [`RsaPublicKey::new_with_size_limits`].
252+
#[deprecated(since = "0.10.0", note = "please use `new_with_size_limits` instead")]
253+
pub fn new_with_max_size(n: BoxedUint, e: BoxedUint, max_size: usize) -> Result<Self> {
254+
Self::new_with_size_limits(n, e, Self::MIN_SIZE..(max_size as u32))
255+
}
256+
242257
/// Create a new public key, bypassing checks around the modulus and public
243258
/// exponent size.
244259
///
245260
/// This method is not recommended, and only intended for unusual use cases.
246261
/// Most applications should use [`RsaPublicKey::new`] or
247-
/// [`RsaPublicKey::new_with_max_size`] instead.
262+
/// [`RsaPublicKey::new_with_size_limits`] instead.
248263
pub fn new_unchecked(n: BoxedUint, e: BoxedUint) -> Self {
249264
let n_odd = Odd::new(n.clone()).expect("n must be odd");
250265
let n_params = BoxedMontyParams::new(n_odd);
251266
let n = NonZero::new(n).expect("odd numbers are non zero");
252267

253268
Self { n, e, n_params }
254269
}
270+
271+
/// Get the size of the modulus `n` in bits.
272+
pub fn bits(&self) -> u32 {
273+
self.n.bits_vartime()
274+
}
255275
}
256276

257277
impl PublicKeyParts for RsaPrivateKey {
@@ -277,6 +297,25 @@ impl RsaPrivateKey {
277297
Self::new_with_exp(rng, bit_size, BoxedUint::from(Self::EXP))
278298
}
279299

300+
/// Generate a new Rsa key pair of the given bit size using the passed in `rng
301+
/// and allowing hazardous insecure or weak constructions of `RsaPrivateKey
302+
///
303+
/// Unless you have specific needs, you should use `RsaPrivateKey::new` instead
304+
#[cfg(feature = "hazmat")]
305+
pub fn new_unchecked<R: CryptoRng + ?Sized>(
306+
rng: &mut R,
307+
bit_size: usize,
308+
) -> Result<RsaPrivateKey> {
309+
let components =
310+
generate_multi_prime_key_with_exp(rng, 2, bit_size, BoxedUint::from(Self::EXP))?;
311+
RsaPrivateKey::from_components_unchecked(
312+
components.n.get(),
313+
components.e,
314+
components.d,
315+
components.primes,
316+
)
317+
}
318+
280319
/// Generate a new RSA key pair of the given bit size and the public exponent
281320
/// using the passed in `rng`.
282321
///
@@ -309,6 +348,36 @@ impl RsaPrivateKey {
309348
///
310349
/// [NIST SP 800-56B Revision 2]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf
311350
pub fn from_components(
351+
n: BoxedUint,
352+
e: BoxedUint,
353+
d: BoxedUint,
354+
primes: Vec<BoxedUint>,
355+
) -> Result<RsaPrivateKey> {
356+
// The primes may come in padded with zeros too, so we need to shorten them as well.
357+
let primes = primes
358+
.into_iter()
359+
.map(|p| {
360+
let p_bits = p.bits();
361+
p.resize_unchecked(p_bits)
362+
})
363+
.collect();
364+
365+
let mut k = Self::from_components_unchecked(n, e, d, primes)?;
366+
367+
// Always validate the key, to ensure precompute can't fail
368+
k.validate()?;
369+
370+
// Precompute when possible, ignore error otherwise.
371+
k.precompute().ok();
372+
373+
Ok(k)
374+
}
375+
376+
/// Constructs an RSA key pair from individual components. Bypasses checks on the key's
377+
/// validity like the modulus size.
378+
///
379+
/// Please use [`RsaPrivateKey::from_components`] whenever possible.
380+
pub fn from_components_unchecked(
312381
n: BoxedUint,
313382
e: BoxedUint,
314383
d: BoxedUint,
@@ -337,8 +406,8 @@ impl RsaPrivateKey {
337406
1 => return Err(Error::NprimesTooSmall),
338407
_ => {
339408
// Check that the product of primes matches the modulus.
340-
// This also ensures that `bit_precision` of each prime is <= that of the modulus,
341-
// and `bit_precision` of their product is >= that of the modulus.
409+
// This also ensures that `bits_precision` of each prime is <= that of the modulus,
410+
// and `bits_precision` of their product is >= that of the modulus.
342411
if &primes.iter().fold(BoxedUint::one(), |acc, p| acc * p) != n_c.as_ref() {
343412
return Err(Error::InvalidModulus);
344413
}
@@ -354,7 +423,7 @@ impl RsaPrivateKey {
354423
})
355424
.collect();
356425

357-
let mut k = RsaPrivateKey {
426+
Ok(RsaPrivateKey {
358427
pubkey_components: RsaPublicKey {
359428
n: n_c,
360429
e,
@@ -363,15 +432,7 @@ impl RsaPrivateKey {
363432
d,
364433
primes,
365434
precomputed: None,
366-
};
367-
368-
// Always validate the key, to ensure precompute can't fail
369-
k.validate()?;
370-
371-
// Precompute when possible, ignore error otherwise.
372-
k.precompute().ok();
373-
374-
Ok(k)
435+
})
375436
}
376437

377438
/// Constructs an RSA key pair from its two primes p and q.
@@ -584,6 +645,11 @@ impl RsaPrivateKey {
584645
) -> Result<Vec<u8>> {
585646
padding.sign(Some(rng), self, digest_in)
586647
}
648+
649+
/// Get the size of the modulus `n` in bits.
650+
pub fn bits(&self) -> u32 {
651+
self.pubkey_components.bits()
652+
}
587653
}
588654

589655
impl PrivateKeyParts for RsaPrivateKey {
@@ -620,16 +686,30 @@ impl PrivateKeyParts for RsaPrivateKey {
620686
}
621687
}
622688

623-
/// Check that the public key is well formed and has an exponent within acceptable bounds.
689+
/// Check that the public key is well-formed and has an exponent within acceptable bounds.
624690
#[inline]
625691
pub fn check_public(public_key: &impl PublicKeyParts) -> Result<()> {
626-
check_public_with_max_size(public_key.n(), public_key.e(), RsaPublicKey::MAX_SIZE)
692+
check_public_with_size_limits(
693+
public_key.n(),
694+
public_key.e(),
695+
RsaPublicKey::MIN_SIZE..RsaPublicKey::MAX_SIZE,
696+
)
627697
}
628698

629-
/// Check that the public key is well formed and has an exponent within acceptable bounds.
699+
/// Check that the public key is well-formed and has an exponent within acceptable bounds.
630700
#[inline]
631-
fn check_public_with_max_size(n: &BoxedUint, e: &BoxedUint, max_size: usize) -> Result<()> {
632-
if n.bits_vartime() as usize > max_size {
701+
fn check_public_with_size_limits(
702+
n: &BoxedUint,
703+
e: &BoxedUint,
704+
size_range_bits: Range<u32>,
705+
) -> Result<()> {
706+
let modulus_bits = n.bits_vartime();
707+
708+
if modulus_bits < size_range_bits.start {
709+
return Err(Error::ModulusTooSmall);
710+
}
711+
712+
if modulus_bits > size_range_bits.end {
633713
return Err(Error::ModulusTooLarge);
634714
}
635715

@@ -732,7 +812,10 @@ mod tests {
732812
}
733813

734814
fn test_key_basics(private_key: &RsaPrivateKey) {
735-
private_key.validate().expect("invalid private key");
815+
// Some test keys have moduli which are smaller than 1024-bits
816+
if private_key.bits() >= RsaPublicKey::MIN_SIZE {
817+
private_key.validate().expect("invalid private key");
818+
}
736819

737820
assert!(
738821
PrivateKeyParts::d(private_key) < PublicKeyParts::n(private_key).as_ref(),
@@ -778,29 +861,17 @@ mod tests {
778861
};
779862
}
780863

781-
key_generation!(key_generation_128, 2, 128);
782864
key_generation!(key_generation_1024, 2, 1024);
783-
784-
key_generation!(key_generation_multi_3_256, 3, 256);
785-
786-
key_generation!(key_generation_multi_4_64, 4, 64);
787-
788-
key_generation!(key_generation_multi_5_64, 5, 64);
789-
key_generation!(key_generation_multi_8_576, 8, 576);
790865
key_generation!(key_generation_multi_16_1024, 16, 1024);
791866

792867
#[test]
793868
fn test_negative_decryption_value() {
794869
let bits = 128;
795-
let private_key = RsaPrivateKey::from_components(
796-
BoxedUint::from_le_slice(
797-
&[
798-
99, 192, 208, 179, 0, 220, 7, 29, 49, 151, 75, 107, 75, 73, 200, 180,
799-
],
800-
bits,
801-
)
802-
.unwrap(),
803-
BoxedUint::from_le_slice(&[1, 0, 1, 0, 0, 0, 0, 0], 64).unwrap(),
870+
let private_key = RsaPrivateKey::from_components_unchecked(
871+
BoxedUint::from_le_slice_vartime(&[
872+
99, 192, 208, 179, 0, 220, 7, 29, 49, 151, 75, 107, 75, 73, 200, 180,
873+
]),
874+
BoxedUint::from_le_slice_vartime(&[1, 0, 1, 0, 0, 0, 0, 0]),
804875
BoxedUint::from_le_slice(
805876
&[
806877
81, 163, 254, 144, 171, 159, 144, 42, 244, 133, 51, 249, 28, 12, 63, 65,
@@ -827,21 +898,44 @@ mod tests {
827898
use serde_test::{assert_tokens, Configure, Token};
828899

829900
let mut rng = ChaCha8Rng::from_seed([42; 32]);
830-
let priv_key = RsaPrivateKey::new(&mut rng, 64).expect("failed to generate key");
901+
let priv_key = RsaPrivateKey::new(&mut rng, 1024).expect("failed to generate key");
831902

832903
let priv_tokens = [Token::Str(concat!(
833-
"3056020100300d06092a864886f70d010101050004423040020100020900a",
834-
"b240c3361d02e370203010001020811e54a15259d22f9020500ceff5cf302",
835-
"0500d3a7aaad020500ccaddf17020500cb529d3d020500bb526d6f"
904+
"30820278020100300d06092a864886f70d0101010500048202623082025e0",
905+
"2010002818100cd1419dc3771354bee0955a90489cce0c98aee6577851358",
906+
"afe386a68bc95287862a1157d5aba8847e8e57b6f2f94748ab7efda3f3c74",
907+
"a6702329397ffe0b1d4f76e1b025d87d583e48b3cfce99d6a507d94eb46c5",
908+
"242b3addb54d346ecf43eb0d7343bcb258a31d5fa51f47b9e0d7280623901",
909+
"d1d29af1a986fec92ba5fe2430203010001028181009bb3203326d0c7b31f",
910+
"456d08c6ce4c8379e10640792ecad271afe002406d184096a707c5d50ee00",
911+
"1c00818266970c3233439551f0e2d879a8f7b90bd3d62fdffa3e661f14c8d",
912+
"cce071f081966e25bb351289810c2f8a012f2fa3f001029d7f2e0cf24f6a4",
913+
"b139292f8078fac24e7fc8185bab4f02f539267bd09b615e4e19fe1024100",
914+
"e90ad93c4b19bb40807391b5a9404ce5ea359e7b0556ee25cb2e7455aeb5c",
915+
"af83fc26f34457cdbb173347962c66b6fe0c4686b54dbe0d2c913a7aa924e",
916+
"ff6031024100e148067566a1fa3aabd0672361be62715516c9d62790b03f4",
917+
"326cc00b2f782e6b64a167689e5c9aebe6a4cf594f3083380fe2a0a7edf1f",
918+
"325e58c523b981a0b3024100ab96e85323bd038a3fca588c58ddd681278d6",
919+
"96e8d84ef7ef676f303afcb7d728287e897a55e84e8c8b9e772da447b3115",
920+
"8d0912877fa7d4945b4d15c382f7d102400ddde317e2e36185af01baf7809",
921+
"2b97884664cb233e9421002d0268a7c79a3c313c167b4903466bfacd4da3b",
922+
"db99420df988ab89cdd96a102da2852ff7c134e5024100bafb0dac0fda53f",
923+
"9c755c23483343922727b88a5256a6fb47242e1c99b8f8a2c914f39f7af30",
924+
"1219245786a6bb15336231d6a9b57ee7e0b3dd75129f93f54ecf"
836925
))];
837926
assert_tokens(&priv_key.clone().readable(), &priv_tokens);
838927

839-
let priv_tokens = [Token::Str(
840-
"3024300d06092a864886f70d01010105000313003010020900ab240c3361d02e370203010001",
841-
)];
928+
let pub_tokens = [Token::Str(concat!(
929+
"30819f300d06092a864886f70d010101050003818d0030818902818100cd1",
930+
"419dc3771354bee0955a90489cce0c98aee6577851358afe386a68bc95287",
931+
"862a1157d5aba8847e8e57b6f2f94748ab7efda3f3c74a6702329397ffe0b",
932+
"1d4f76e1b025d87d583e48b3cfce99d6a507d94eb46c5242b3addb54d346e",
933+
"cf43eb0d7343bcb258a31d5fa51f47b9e0d7280623901d1d29af1a986fec9",
934+
"2ba5fe2430203010001"
935+
))];
842936
assert_tokens(
843937
&RsaPublicKey::from(priv_key.clone()).readable(),
844-
&priv_tokens,
938+
&pub_tokens,
845939
);
846940
}
847941

src/oaep/decrypting_key.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ mod tests {
107107

108108
let mut rng = ChaCha8Rng::from_seed([42; 32]);
109109
let decrypting_key = DecryptingKey::<Sha256>::new(
110-
RsaPrivateKey::new(&mut rng, 64).expect("failed to generate key"),
110+
RsaPrivateKey::new(&mut rng, 2048).expect("failed to generate key"),
111111
);
112112

113113
let tokens = [
@@ -117,9 +117,7 @@ mod tests {
117117
},
118118
Token::Str("inner"),
119119
Token::Str(concat!(
120-
"3056020100300d06092a864886f70d010101050004423040020100020900ab",
121-
"240c3361d02e370203010001020811e54a15259d22f9020500ceff5cf30205",
122-
"00d3a7aaad020500ccaddf17020500cb529d3d020500bb526d6f"
120+
"308204bd020100300d06092a864886f70d0101010500048204a7308204a30201000282010100cf823bdbad23cda55787e9d1dbd630457e3e8407f3a4da723656a120866a8284ce211ff8464904cf7dab256d0b5544549719f4155d32187ad3eb928ada9cd4152a9e4153e21c68022e654b0d10b065519e9ef5619f431740c2a0f568141c27670485f28d1643fe650af3757f4775af5d01ed3c992a6269c5aa5ff7f52450c30a84783e36931b8855b091559540ec34e0730c511d62e09ea86d66b0f4cb92d1a609e7fb6f34ae8cf08bd791eee85150850e943fb5e4d9b7fd44a5eb474ed7e0bb7faa2e1dca443d5df8f77468fb0905731e421b2e06e864f957f3a517b2b0e3ad09118310b9fd74cb54bb07308d009e3ec6cecc17f06cddf10e0b1b9eff5ff8b90203010001028201000a7071d4765c63bf1aad32bd25031c80927e50a419c4c45c94913d1fe6c33af7b56b0331b94f79177b29fe03035bf1c913a4f19b9589aca3993fb3aa9a9ee32881715eb5fa9d153a6edd17ae7b9574336bf8713dcd065208270273f61d74e122949eac7a1e91a31db0345947e2ef6fb80d1dc33bad5f30150aa2335638d27b4d57f47262b31059351b08c2350d8afe88d1dfbd1b398daf317db8c0cd42859072b8ddadcc2d50c5ad1d6d06a56594bdabb7dd51c77fe2b5d404c64ff99e6500de5da418c5c49c6ebd7ecfc400f18ba26fd4d6e7b31e435d494326585a9efff7bdb3c51ba19399918df4a999453dfed65e84adb15b0a183416b5ec5f221491978102818100e148067566a1fa3aabd0672361be62715516c9d62790b03f4326cc00b2f782e6b64a167689e5c9aebe6a4cf594f3083380fe2a0a7edf1f325e58c523b9819747a90ad93c4b19bb40807391b5a9404ce5ea359e7b0556ee25cb2e7455aeb5caf83fc26f34457cdbb173347962c66b6fe0c4686b54dbe0d2c913a7aa924eff6ec902818100ebcdd03d9b1dfd2ea4f2d6dade79fcb02727d84426f9d756121525f14696434fa594867ca839d1025b823a7576eb6c8b33e6dd4ff4fcb72c6069d1e5e74885e90b76b0bf3994501dd0ef212694e73cbf43855731ba543c771debf979eea8f77fcab8a53d56fc46d5398893f3421ca54b371afb10ecb2137892f5062c506e82710281801c345542ab87c9f9407b85fe23059ff38a70a0f263dfb481271a1b5e5709afe4cc9bb7f63d4b7c9599175bed3f29b234288929a048c40c76d4e30e436bbd32c071047fb011c2f5f39c615bb3bfade232c2c0d5c797228c0c4544daa1c38ed50b8188093e2518fdb458b5102172b00ec0b8364e81c049847a5230a2a550a8a029028180718bebc89e9734416fc057e190dbe0e7da12ffbae1a1d1256b13afef9cf3e279c9dbd95ed18af5b052ec44c6277b7a0b15f50780e711820ae66a4e5e8c9e898d0cae1cb21841e8ca52bfb390e686eae396d9f080cb9ea077237b6be8611a10040354228d85037a0056f2037c51cb8574d096376b90eeb71d8a765e809c427aa102818100886afe7a9610e60cd2da4cf3137ba5f597cd9cdc344f36c4101720363341c42cdfe09f68ee25a3dd63e191b6542bcd97aaa0af776eb68aaab84db4594e5340591b4fe194ea2fe2f7586ac3c3aaf8bc337963c4e05d6556b1a6024ac6e07710cdf01bcd9543e263a35ad13baaa2aa6c3af60880cc56622959916cab038a51fff9",
123121
)),
124122
Token::Str("label"),
125123
Token::None,

0 commit comments

Comments
 (0)