From 9047ca6249f9ebe19675d59bbf38c9b7b2583817 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 12 Oct 2026 14:41:46 -0600 Subject: [PATCH] [WIP] Address PKCS#1 v1.5 constant-time issues Follows `draft-irtf-cfrg-rsa-guidance` --- src/algorithms/pad.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/algorithms/pad.rs b/src/algorithms/pad.rs index 4b73d591..01c78895 100644 --- a/src/algorithms/pad.rs +++ b/src/algorithms/pad.rs @@ -49,9 +49,29 @@ pub(crate) fn uint_to_zeroizing_be_pad(input: BoxedUint, padded_len: usize) -> Z out } +/// Encode a private RSA representative using the modulus width in octets. +/// +/// The caller has established that `input` fits in `k` octets. Offsets depend only on the public +/// storage and modulus widths, not on `input`'s value. +/// +/// From draft-irtf-cfrg-rsa-guidance-10 §7.2: +/// > I2OSP MUST be constant-time with respect of m. +#[inline] +pub(crate) fn i2osp_modulus_width(input: BoxedUint, k: usize) -> Zeroizing> { + debug_assert!(k >= 11); + + let input = Zeroizing::new(input); + let bytes = Zeroizing::new(input.to_be_bytes()); + let copy_len = bytes.len().min(k); + let mut out = Zeroizing::new(vec![0u8; k]); + out[k - copy_len..].copy_from_slice(&bytes[bytes.len() - copy_len..]); + out +} + #[cfg(test)] mod tests { use super::*; + #[test] fn be_pad() { let x = BoxedUint::from(0x0102u64); @@ -75,4 +95,27 @@ mod tests { fn be_pad_rejects_broken_precondition() { uint_to_zeroizing_be_pad(BoxedUint::from(0x0102u64), 1); } + + #[test] + fn i2osp_modulus_width_uses_octet_width_not_storage_precision() { + // From draft-irtf-cfrg-rsa-guidance-10 §7.2: + // > EM = I2OSP (m, k). + // + // A 2049-bit modulus has `k = 257`, even when BoxedUint stores more bytes. + let k = 257; + let mut expected = vec![0u8; k]; + expected[k - 2..].copy_from_slice(&[0x12, 0x34]); + + for bits_precision in [2048, 2049] { + let m = BoxedUint::from_be_slice(&[0x12, 0x34], bits_precision).unwrap(); + let em = i2osp_modulus_width(m, k); + assert_eq!(em.as_slice(), expected.as_slice()); + } + + // The most significant octet of a 2049-bit value must also be retained. + expected[0] = 1; + let m = BoxedUint::from_be_slice(&expected, 2049).unwrap(); + let em = i2osp_modulus_width(m, k); + assert_eq!(em.as_slice(), expected.as_slice()); + } }