From 23b8ab73967c29e2fa838c6725ca6a6880853265 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 13 Oct 2025 11:01:52 -0700 Subject: [PATCH 1/3] rsa: Split public key modulue into public/private parts. Move implementation details `Inner` and `ValidatedInput` to a `base` submodulue, and rename `Inner` to `base::PublicKey`. This facilitates future refactorings to eliminate some wasteful allocations. --- src/rsa.rs | 5 +- src/rsa/base/mod.rs | 21 +++ src/rsa/{ => base}/public_exponent.rs | 4 +- src/rsa/base/public_key.rs | 179 ++++++++++++++++++++++++++ src/rsa/{ => base}/public_modulus.rs | 2 +- src/rsa/keypair.rs | 3 +- src/rsa/public_key.rs | 176 ++----------------------- src/rsa/verification.rs | 4 +- 8 files changed, 216 insertions(+), 178 deletions(-) create mode 100644 src/rsa/base/mod.rs rename src/rsa/{ => base}/public_exponent.rs (96%) create mode 100644 src/rsa/base/public_key.rs rename src/rsa/{ => base}/public_modulus.rs (97%) diff --git a/src/rsa.rs b/src/rsa.rs index 58f33403c3..eff4b5b7ad 100644 --- a/src/rsa.rs +++ b/src/rsa.rs @@ -55,17 +55,14 @@ enum N {} impl bigint::PublicModulus for N {} +mod base; mod keypair; mod keypair_components; -mod public_exponent; mod public_key; mod public_key_components; -mod public_modulus; pub(crate) mod verification; -use self::{public_exponent::PublicExponent, public_modulus::PublicModulus}; - pub use self::{ keypair::KeyPair, keypair_components::KeyPairComponents, public_key::PublicKey, public_key_components::PublicKeyComponents, diff --git a/src/rsa/base/mod.rs b/src/rsa/base/mod.rs new file mode 100644 index 0000000000..3113097b3e --- /dev/null +++ b/src/rsa/base/mod.rs @@ -0,0 +1,21 @@ +// Copyright 2015 Brian Smith. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +mod public_exponent; +pub(super) mod public_key; +mod public_modulus; + +pub(super) use self::{ + public_exponent::PublicExponent, public_key::PublicKey, public_modulus::PublicModulus, +}; diff --git a/src/rsa/public_exponent.rs b/src/rsa/base/public_exponent.rs similarity index 96% rename from src/rsa/public_exponent.rs rename to src/rsa/base/public_exponent.rs index b87b30b217..171497aab9 100644 --- a/src/rsa/public_exponent.rs +++ b/src/rsa/base/public_exponent.rs @@ -10,8 +10,8 @@ impl PublicExponent { #[cfg(test)] const ALL_CONSTANTS: [Self; 3] = [Self::_3, Self::_65537, Self::MAX]; - pub(super) const _3: Self = Self(unwrap_const(NonZeroU64::new(3))); - pub(super) const _65537: Self = Self(unwrap_const(NonZeroU64::new(65537))); + pub(crate) const _3: Self = Self(unwrap_const(NonZeroU64::new(3))); + pub(crate) const _65537: Self = Self(unwrap_const(NonZeroU64::new(65537))); // This limit was chosen to bound the performance of the simple // exponentiation-by-squaring implementation in `elem_exp_vartime`. In diff --git a/src/rsa/base/public_key.rs b/src/rsa/base/public_key.rs new file mode 100644 index 0000000000..28c9900942 --- /dev/null +++ b/src/rsa/base/public_key.rs @@ -0,0 +1,179 @@ +// Copyright 2015-2021 Brian Smith. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +use super::{ + super::{PublicKeyComponents, N, PUBLIC_KEY_PUBLIC_MODULUS_MAX_LEN}, + public_modulus, PublicExponent, PublicModulus, +}; +use crate::{arithmetic::bigint, bits, cpu, error, limb::LIMB_BYTES}; +use core::num::NonZeroU64; + +/// An RSA Public Key. +#[derive(Clone)] +pub(crate) struct PublicKey { + n: PublicModulus, + e: PublicExponent, +} + +pub struct ValidatedInput<'a> { + n: public_modulus::ValidatedInput<'a>, + e: PublicExponent, + e_input: untrusted::Input<'a>, +} + +impl<'a> ValidatedInput<'a> { + pub(in super::super) fn try_from_be_bytes( + components: PublicKeyComponents<&'a [u8]>, + n_min_bits: bits::BitLength, + n_max_bits: bits::BitLength, + e_min_value: PublicExponent, + ) -> Result { + let n = + public_modulus::ValidatedInput::from_be_bytes(components.n, n_min_bits..=n_max_bits)?; + let e_input = components.e.into(); + let e = PublicExponent::from_be_bytes(e_input, e_min_value)?; + Ok(Self { n, e, e_input }) + } + + pub fn n(&self) -> &public_modulus::ValidatedInput<'_> { + &self.n + } + + pub(in super::super) fn e_input(&self) -> untrusted::Input<'_> { + self.e_input + } + + pub(in super::super) fn build(&self, cpu_features: cpu::Features) -> PublicKey { + // This is an incomplete implementation of NIST SP800-56Br1 Section + // 6.4.2.2, "Partial Public-Key Validation for RSA." That spec defers + // to NIST SP800-89 Section 5.3.3, "(Explicit) Partial Public Key + // Validation for RSA," "with the caveat that the length of the modulus + // shall be a length that is specified in this Recommendation." In + // SP800-89, two different sets of steps are given, one set numbered, + // and one set lettered. TODO: Document this in the end-user + // documentation for RSA keys. + + let n = self.n.build(cpu_features); + + // If `n` is less than `e` then somebody has probably accidentally swapped + // them. The largest acceptable `e` is smaller than the smallest acceptable + // `n`, so no additional checks need to be done. + + // XXX: Steps 4 & 5 / Steps d, e, & f are not implemented. This is also the + // case in most other commonly-used crypto libraries. + + PublicKey { n, e: self.e } + } +} + +impl PublicKey { + /// The public modulus. + #[inline] + pub(in super::super) fn n(&self) -> &PublicModulus { + &self.n + } + + /// The public exponent. + #[inline] + pub(in super::super) fn e(&self) -> PublicExponent { + self.e + } + + /// Calculates base**e (mod n), filling the first part of `out_buffer` with + /// the result. + /// + /// This is constant-time with respect to the value in `base` (only). + /// + /// The result will be a slice of the encoded bytes of the result within + /// `out_buffer`, if successful. + pub(in super::super) fn exponentiate<'out>( + &self, + base: untrusted::Input, + out_buffer: &'out mut [u8; PUBLIC_KEY_PUBLIC_MODULUS_MAX_LEN], + cpu_features: cpu::Features, + ) -> Result<&'out [u8], error::Unspecified> { + let n = &self.n.value(); + let n = &n.modulus(cpu_features); + + // The encoded value of the base must be the same length as the modulus, + // in bytes. + if base.len() != self.n.len_bits().as_usize_bytes_rounded_up() { + return Err(error::Unspecified); + } + + // RFC 8017 Section 5.2.2: RSAVP1. + + // Step 1. + let s = n.alloc_uninit().into_elem_from_be_bytes_padded(base, n)?; + if s.is_zero() { + return Err(error::Unspecified); + } + + // Step 2. + let m = n.alloc_uninit(); + let m = self.exponentiate_elem(m, &s, cpu_features); + + // Step 3. + Ok(fill_be_bytes_n(m, self.n.len_bits(), out_buffer)) + } + + /// Calculates base**e (mod n). + /// + /// This is constant-time with respect to `base` only. + pub(in super::super) fn exponentiate_elem( + &self, + out: bigint::Uninit, + base: &bigint::Elem, + cpu_features: cpu::Features, + ) -> bigint::Elem { + // The exponent was already checked to be at least 3. + let exponent_without_low_bit = NonZeroU64::try_from(self.e.value().get() & !1).unwrap(); + // The exponent was already checked to be odd. + debug_assert_ne!(exponent_without_low_bit, self.e.value()); + + let n = &self.n.value(); + let nm = &n.modulus(cpu_features); + + let tmp = nm.alloc_uninit(); + let base_r = base.clone_into(tmp).encode_mont(n, cpu_features); + + // During RSA public key operations the exponent is almost always either + // 65537 (0b10000000000000001) or 3 (0b11), both of which have a Hamming + // weight of 2. The maximum bit length and maximum Hamming weight of the + // exponent is bounded by the value of `PublicExponent::MAX`. + let acc = bigint::elem_exp_vartime(out, base_r, exponent_without_low_bit, nm); + + // Now do the multiplication for the low bit and convert out of the Montgomery domain. + bigint::elem_mul(base, acc, nm) + } +} + +/// Returns the big-endian representation of `elem` that is +/// the same length as the minimal-length big-endian representation of +/// the modulus `n`. +/// +/// `n_bits` must be the bit length of the public modulus `n`. +fn fill_be_bytes_n( + elem: bigint::Elem, + n_bits: bits::BitLength, + out: &mut [u8; PUBLIC_KEY_PUBLIC_MODULUS_MAX_LEN], +) -> &[u8] { + let n_bytes = n_bits.as_usize_bytes_rounded_up(); + let n_bytes_padded = ((n_bytes + (LIMB_BYTES - 1)) / LIMB_BYTES) * LIMB_BYTES; + let out = &mut out[..n_bytes_padded]; + elem.fill_be_bytes(out); + let (padding, out) = out.split_at(n_bytes_padded - n_bytes); + assert!(padding.iter().all(|&b| b == 0)); + out +} diff --git a/src/rsa/public_modulus.rs b/src/rsa/base/public_modulus.rs similarity index 97% rename from src/rsa/public_modulus.rs rename to src/rsa/base/public_modulus.rs index f1bb362501..abc2c09e9e 100644 --- a/src/rsa/public_modulus.rs +++ b/src/rsa/base/public_modulus.rs @@ -88,7 +88,7 @@ impl PublicModulus { self.value.reborrow().len_bits() } - pub(super) fn value(&self) -> bigint::IntoMont<'_, N, RR> { + pub(in super::super) fn value(&self) -> bigint::IntoMont<'_, N, RR> { self.value.reborrow() } } diff --git a/src/rsa/keypair.rs b/src/rsa/keypair.rs index 33f7562ee1..c247cad9f4 100644 --- a/src/rsa/keypair.rs +++ b/src/rsa/keypair.rs @@ -13,8 +13,9 @@ // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. use super::{ + base::PublicExponent, padding::{self, RsaEncoding}, - public_key, KeyPairComponents, PublicExponent, PublicKey, PublicKeyComponents, N, + public_key, KeyPairComponents, PublicKey, PublicKeyComponents, N, }; /// RSA PKCS#1 1.5 signatures. diff --git a/src/rsa/public_key.rs b/src/rsa/public_key.rs index ec44a8d2b0..eaf16457e0 100644 --- a/src/rsa/public_key.rs +++ b/src/rsa/public_key.rs @@ -12,23 +12,19 @@ // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -use super::{ - public_modulus, PublicExponent, PublicKeyComponents, PublicModulus, N, - PUBLIC_KEY_PUBLIC_MODULUS_MAX_LEN, -}; +use super::base; use crate::{ - arithmetic::bigint, - bits, cpu, error, + cpu, error, io::{self, der, der_writer}, - limb::LIMB_BYTES, }; use alloc::boxed::Box; -use core::num::NonZeroU64; + +pub(super) use base::public_key::ValidatedInput; /// An RSA Public Key. #[derive(Clone)] pub struct PublicKey { - inner: Inner, + inner: base::PublicKey, serialized: Box<[u8]>, } @@ -41,8 +37,8 @@ impl PublicKey { ) -> Result { let inner = input.build(cpu_features); - let n_bytes = input.n.input(); - let e_bytes = input.e_input; + let n_bytes = input.n().input(); + let e_bytes = input.e_input(); // TODO: Remove this re-parsing, and stop allocating this here. // Instead we should serialize on demand without allocation, from @@ -69,170 +65,14 @@ impl PublicKey { self.inner.n().len_bits().as_usize_bytes_rounded_up() } - pub(super) fn inner(&self) -> &Inner { + pub(super) fn inner(&self) -> &base::PublicKey { &self.inner } } -/// `PublicKey` but without any superfluous allocations, optimized for one-shot -/// RSA signature verification. -#[derive(Clone)] -pub(crate) struct Inner { - n: PublicModulus, - e: PublicExponent, -} - -pub struct ValidatedInput<'a> { - n: public_modulus::ValidatedInput<'a>, - e: PublicExponent, - e_input: untrusted::Input<'a>, -} - -impl<'a> ValidatedInput<'a> { - pub(super) fn try_from_be_bytes( - components: PublicKeyComponents<&'a [u8]>, - n_min_bits: bits::BitLength, - n_max_bits: bits::BitLength, - e_min_value: PublicExponent, - ) -> Result { - let n = - public_modulus::ValidatedInput::from_be_bytes(components.n, n_min_bits..=n_max_bits)?; - let e_input = components.e.into(); - let e = PublicExponent::from_be_bytes(e_input, e_min_value)?; - Ok(Self { n, e, e_input }) - } - - pub fn n(&self) -> &public_modulus::ValidatedInput<'_> { - &self.n - } - - pub(super) fn build(&self, cpu_features: cpu::Features) -> Inner { - // This is an incomplete implementation of NIST SP800-56Br1 Section - // 6.4.2.2, "Partial Public-Key Validation for RSA." That spec defers - // to NIST SP800-89 Section 5.3.3, "(Explicit) Partial Public Key - // Validation for RSA," "with the caveat that the length of the modulus - // shall be a length that is specified in this Recommendation." In - // SP800-89, two different sets of steps are given, one set numbered, - // and one set lettered. TODO: Document this in the end-user - // documentation for RSA keys. - - let n = self.n.build(cpu_features); - - // If `n` is less than `e` then somebody has probably accidentally swapped - // them. The largest acceptable `e` is smaller than the smallest acceptable - // `n`, so no additional checks need to be done. - - // XXX: Steps 4 & 5 / Steps d, e, & f are not implemented. This is also the - // case in most other commonly-used crypto libraries. - - Inner { n, e: self.e } - } -} - -impl Inner { - /// The public modulus. - #[inline] - pub(super) fn n(&self) -> &PublicModulus { - &self.n - } - - /// The public exponent. - #[inline] - pub(super) fn e(&self) -> PublicExponent { - self.e - } - - /// Calculates base**e (mod n), filling the first part of `out_buffer` with - /// the result. - /// - /// This is constant-time with respect to the value in `base` (only). - /// - /// The result will be a slice of the encoded bytes of the result within - /// `out_buffer`, if successful. - pub(super) fn exponentiate<'out>( - &self, - base: untrusted::Input, - out_buffer: &'out mut [u8; PUBLIC_KEY_PUBLIC_MODULUS_MAX_LEN], - cpu_features: cpu::Features, - ) -> Result<&'out [u8], error::Unspecified> { - let n = &self.n.value(); - let n = &n.modulus(cpu_features); - - // The encoded value of the base must be the same length as the modulus, - // in bytes. - if base.len() != self.n.len_bits().as_usize_bytes_rounded_up() { - return Err(error::Unspecified); - } - - // RFC 8017 Section 5.2.2: RSAVP1. - - // Step 1. - let s = n.alloc_uninit().into_elem_from_be_bytes_padded(base, n)?; - if s.is_zero() { - return Err(error::Unspecified); - } - - // Step 2. - let m = n.alloc_uninit(); - let m = self.exponentiate_elem(m, &s, cpu_features); - - // Step 3. - Ok(fill_be_bytes_n(m, self.n.len_bits(), out_buffer)) - } - - /// Calculates base**e (mod n). - /// - /// This is constant-time with respect to `base` only. - pub(super) fn exponentiate_elem( - &self, - out: bigint::Uninit, - base: &bigint::Elem, - cpu_features: cpu::Features, - ) -> bigint::Elem { - // The exponent was already checked to be at least 3. - let exponent_without_low_bit = NonZeroU64::try_from(self.e.value().get() & !1).unwrap(); - // The exponent was already checked to be odd. - debug_assert_ne!(exponent_without_low_bit, self.e.value()); - - let n = &self.n.value(); - let nm = &n.modulus(cpu_features); - - let tmp = nm.alloc_uninit(); - let base_r = base.clone_into(tmp).encode_mont(n, cpu_features); - - // During RSA public key operations the exponent is almost always either - // 65537 (0b10000000000000001) or 3 (0b11), both of which have a Hamming - // weight of 2. The maximum bit length and maximum Hamming weight of the - // exponent is bounded by the value of `PublicExponent::MAX`. - let acc = bigint::elem_exp_vartime(out, base_r, exponent_without_low_bit, nm); - - // Now do the multiplication for the low bit and convert out of the Montgomery domain. - bigint::elem_mul(base, acc, nm) - } -} - // XXX: Refactor `signature::KeyPair` to get rid of this. impl AsRef<[u8]> for PublicKey { fn as_ref(&self) -> &[u8] { &self.serialized } } - -/// Returns the big-endian representation of `elem` that is -/// the same length as the minimal-length big-endian representation of -/// the modulus `n`. -/// -/// `n_bits` must be the bit length of the public modulus `n`. -fn fill_be_bytes_n( - elem: bigint::Elem, - n_bits: bits::BitLength, - out: &mut [u8; PUBLIC_KEY_PUBLIC_MODULUS_MAX_LEN], -) -> &[u8] { - let n_bytes = n_bits.as_usize_bytes_rounded_up(); - let n_bytes_padded = ((n_bytes + (LIMB_BYTES - 1)) / LIMB_BYTES) * LIMB_BYTES; - let out = &mut out[..n_bytes_padded]; - elem.fill_be_bytes(out); - let (padding, out) = out.split_at(n_bytes_padded - n_bytes); - assert!(padding.iter().all(|&b| b == 0)); - out -} diff --git a/src/rsa/verification.rs b/src/rsa/verification.rs index 40c0eba472..c60fc0664d 100644 --- a/src/rsa/verification.rs +++ b/src/rsa/verification.rs @@ -15,8 +15,8 @@ //! Verification of RSA signatures. use super::{ - parse_public_key, public_key, PublicExponent, PublicKeyComponents, RsaParameters, - PUBLIC_KEY_PUBLIC_MODULUS_MAX_LEN, + base::{public_key, PublicExponent}, + parse_public_key, PublicKeyComponents, RsaParameters, PUBLIC_KEY_PUBLIC_MODULUS_MAX_LEN, }; use crate::{ bits::{self, FromByteLen as _}, From 57bbdbf864f4b5acf21055708928cf7751ce24a8 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 14 Oct 2025 14:58:13 -0700 Subject: [PATCH 2/3] rsa: Move some compliance comments to the appropriate place. These comments make more sense in the new location. They should have been moved in an earlier refactoring. --- src/rsa/base/public_key.rs | 39 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/rsa/base/public_key.rs b/src/rsa/base/public_key.rs index 28c9900942..d2301f40ac 100644 --- a/src/rsa/base/public_key.rs +++ b/src/rsa/base/public_key.rs @@ -39,22 +39,6 @@ impl<'a> ValidatedInput<'a> { n_max_bits: bits::BitLength, e_min_value: PublicExponent, ) -> Result { - let n = - public_modulus::ValidatedInput::from_be_bytes(components.n, n_min_bits..=n_max_bits)?; - let e_input = components.e.into(); - let e = PublicExponent::from_be_bytes(e_input, e_min_value)?; - Ok(Self { n, e, e_input }) - } - - pub fn n(&self) -> &public_modulus::ValidatedInput<'_> { - &self.n - } - - pub(in super::super) fn e_input(&self) -> untrusted::Input<'_> { - self.e_input - } - - pub(in super::super) fn build(&self, cpu_features: cpu::Features) -> PublicKey { // This is an incomplete implementation of NIST SP800-56Br1 Section // 6.4.2.2, "Partial Public-Key Validation for RSA." That spec defers // to NIST SP800-89 Section 5.3.3, "(Explicit) Partial Public Key @@ -64,8 +48,6 @@ impl<'a> ValidatedInput<'a> { // and one set lettered. TODO: Document this in the end-user // documentation for RSA keys. - let n = self.n.build(cpu_features); - // If `n` is less than `e` then somebody has probably accidentally swapped // them. The largest acceptable `e` is smaller than the smallest acceptable // `n`, so no additional checks need to be done. @@ -73,7 +55,26 @@ impl<'a> ValidatedInput<'a> { // XXX: Steps 4 & 5 / Steps d, e, & f are not implemented. This is also the // case in most other commonly-used crypto libraries. - PublicKey { n, e: self.e } + let n = + public_modulus::ValidatedInput::from_be_bytes(components.n, n_min_bits..=n_max_bits)?; + let e_input = components.e.into(); + let e = PublicExponent::from_be_bytes(e_input, e_min_value)?; + Ok(Self { n, e, e_input }) + } + + pub fn n(&self) -> &public_modulus::ValidatedInput<'_> { + &self.n + } + + pub(in super::super) fn e_input(&self) -> untrusted::Input<'_> { + self.e_input + } + + pub(in super::super) fn build(&self, cpu_features: cpu::Features) -> PublicKey { + PublicKey { + n: self.n.build(cpu_features), + e: self.e, + } } } From 629dda91b6b34a069bdf42287fb5b04af097c776 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 13 Oct 2025 12:02:27 -0700 Subject: [PATCH 3/3] rsa: Allocate public key on the stack in one-shot verification. --- src/arithmetic/bigint.rs | 4 ++- src/arithmetic/bigint/modulus/mont.rs | 11 +++++- src/arithmetic/bigint/oversized_uninit.rs | 34 +++++++++++++++++++ src/rsa/base/mod.rs | 6 ++-- src/rsa/base/public_key.rs | 41 +++++++++++++++++++---- src/rsa/base/public_modulus.rs | 37 +++++++++++++++----- src/rsa/keypair.rs | 6 ++-- src/rsa/public_key.rs | 17 ++++++---- src/rsa/verification.rs | 4 ++- 9 files changed, 130 insertions(+), 30 deletions(-) create mode 100644 src/arithmetic/bigint/oversized_uninit.rs diff --git a/src/arithmetic/bigint.rs b/src/arithmetic/bigint.rs index a86b8d790d..7729b832b2 100644 --- a/src/arithmetic/bigint.rs +++ b/src/arithmetic/bigint.rs @@ -40,7 +40,7 @@ use crate::polyfill::prelude::*; use self::boxed_limbs::BoxedLimbs; -use super::{montgomery::*, LimbSliceError}; +use super::{montgomery::*, LimbSliceError, MAX_LIMBS}; use crate::{ error::{self, LenMismatchError}, limb::{self, Limb}, @@ -54,6 +54,7 @@ pub(crate) use { }, exp::elem_exp_consttime, modulus::{BoxedIntoMont, IntoMont, Mont, One}, + oversized_uninit::OversizedUninit, private_exponent::PrivateExponent, }, super::exp_vartime::elem_exp_vartime, @@ -63,6 +64,7 @@ mod boxed_limbs; mod elem; mod exp; pub mod modulus; +mod oversized_uninit; mod private_exponent; pub trait PublicModulus {} diff --git a/src/arithmetic/bigint/modulus/mont.rs b/src/arithmetic/bigint/modulus/mont.rs index d9d05022f3..e49118f305 100644 --- a/src/arithmetic/bigint/modulus/mont.rs +++ b/src/arithmetic/bigint/modulus/mont.rs @@ -19,7 +19,7 @@ use super::{ super::{ super::montgomery::{limbs_square_mont, Unencoded, RR, RRR}, modulus::value::Value, - unwrap_impossible_limb_slice_error, Elem, One, PublicModulus, Uninit, N0, + unwrap_impossible_limb_slice_error, Elem, One, OversizedUninit, PublicModulus, Uninit, N0, }, ValidatedInput, }; @@ -106,6 +106,15 @@ impl ValidatedInput<'_> { } } + pub(crate) fn build_into_mont<'o, M>( + &self, + uninit: &'o mut OversizedUninit<2>, + cpu: cpu::Features, + ) -> IntoMont<'o, M, RR> { + self.write_into_mont(&mut uninit.as_uninit().into_cursor(), cpu) + .unwrap_or_else(|LenMismatchError { .. }| unreachable!()) + } + fn write_into_mont<'o, M>( &self, out: &mut Cursor<'o, Limb>, diff --git a/src/arithmetic/bigint/oversized_uninit.rs b/src/arithmetic/bigint/oversized_uninit.rs new file mode 100644 index 0000000000..b6baaa1d29 --- /dev/null +++ b/src/arithmetic/bigint/oversized_uninit.rs @@ -0,0 +1,34 @@ +// Copyright 2025 Brian Smith. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +#[allow(unused_imports)] +use crate::polyfill::prelude::*; + +use super::{Limb, MAX_LIMBS}; +use crate::polyfill; +use core::mem::MaybeUninit; + +/// A buffer that has enough space to hold `N` values of the maximum size, +/// hiding the representation of values from the user. +pub struct OversizedUninit([[MaybeUninit; MAX_LIMBS]; N]); + +impl OversizedUninit { + pub fn new() -> Self { + Self(unsafe { MaybeUninit::uninit().assume_init() }) + } + + pub(super) fn as_uninit(&mut self) -> polyfill::slice::Uninit<'_, Limb> { + self.0.as_flattened_mut().into() + } +} diff --git a/src/rsa/base/mod.rs b/src/rsa/base/mod.rs index 3113097b3e..0df0a568bd 100644 --- a/src/rsa/base/mod.rs +++ b/src/rsa/base/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2015 Brian Smith. +// Copyright 2025 Brian Smith. // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -16,6 +16,4 @@ mod public_exponent; pub(super) mod public_key; mod public_modulus; -pub(super) use self::{ - public_exponent::PublicExponent, public_key::PublicKey, public_modulus::PublicModulus, -}; +pub(super) use self::{public_exponent::PublicExponent, public_modulus::PublicModulus}; diff --git a/src/rsa/base/public_key.rs b/src/rsa/base/public_key.rs index d2301f40ac..d3ea93129f 100644 --- a/src/rsa/base/public_key.rs +++ b/src/rsa/base/public_key.rs @@ -16,13 +16,17 @@ use super::{ super::{PublicKeyComponents, N, PUBLIC_KEY_PUBLIC_MODULUS_MAX_LEN}, public_modulus, PublicExponent, PublicModulus, }; -use crate::{arithmetic::bigint, bits, cpu, error, limb::LIMB_BYTES}; +use crate::{ + arithmetic::{bigint, montgomery::RR}, + bits, cpu, error, + limb::LIMB_BYTES, +}; use core::num::NonZeroU64; /// An RSA Public Key. #[derive(Clone)] -pub(crate) struct PublicKey { - n: PublicModulus, +pub(crate) struct PublicKey { + n: PublicModulus, e: PublicExponent, } @@ -70,18 +74,41 @@ impl<'a> ValidatedInput<'a> { self.e_input } - pub(in super::super) fn build(&self, cpu_features: cpu::Features) -> PublicKey { + pub(in super::super) fn build_boxed( + &self, + cpu: cpu::Features, + ) -> PublicKey> { + PublicKey { + n: self.n.build_boxed_into_mont(cpu), + e: self.e, + } + } + + pub(in super::super) fn build<'o>( + &self, + out: &'o mut bigint::OversizedUninit<2>, + cpu: cpu::Features, + ) -> PublicKey> { + PublicKey { + n: self.n.build(out, cpu), + e: self.e, + } + } +} + +impl PublicKey> { + pub fn reborrow(&self) -> PublicKey> { PublicKey { - n: self.n.build(cpu_features), + n: self.n.reborrow(), e: self.e, } } } -impl PublicKey { +impl PublicKey> { /// The public modulus. #[inline] - pub(in super::super) fn n(&self) -> &PublicModulus { + pub(in super::super) fn n(&self) -> &PublicModulus> { &self.n } diff --git a/src/rsa/base/public_modulus.rs b/src/rsa/base/public_modulus.rs index abc2c09e9e..e50ee47317 100644 --- a/src/rsa/base/public_modulus.rs +++ b/src/rsa/base/public_modulus.rs @@ -9,8 +9,8 @@ use core::ops::RangeInclusive; /// The modulus (n) of an RSA public key. #[derive(Clone)] -pub struct PublicModulus { - value: bigint::BoxedIntoMont, +pub struct PublicModulus { + value: S, } /* @@ -68,27 +68,48 @@ impl<'a> ValidatedInput<'a> { self.input.len_bits() } - pub(super) fn build(&self, cpu_features: cpu::Features) -> PublicModulus { + pub(super) fn build_boxed_into_mont( + &self, + cpu_features: cpu::Features, + ) -> PublicModulus> { PublicModulus { value: self.input.build_boxed_into_mont(cpu_features), } } + + pub(super) fn build<'o>( + &self, + out: &'o mut bigint::OversizedUninit<2>, + cpu_features: cpu::Features, + ) -> PublicModulus> { + PublicModulus { + value: self.input.build_into_mont(out, cpu_features), + } + } +} + +impl PublicModulus> { + pub fn reborrow(&self) -> PublicModulus> { + PublicModulus { + value: self.value.reborrow(), + } + } } -impl PublicModulus { +impl PublicModulus> { /// The big-endian encoding of the modulus. /// /// There are no leading zeros. pub fn be_bytes(&self) -> impl ExactSizeIterator + Clone + '_ { - self.value.reborrow().be_bytes() + self.value.be_bytes() } /// The length of the modulus in bits. pub fn len_bits(&self) -> bits::BitLength { - self.value.reborrow().len_bits() + self.value.len_bits() } - pub(in super::super) fn value(&self) -> bigint::IntoMont<'_, N, RR> { - self.value.reborrow() + pub(in super::super) fn value(&self) -> &bigint::IntoMont<'_, N, RR> { + &self.value } } diff --git a/src/rsa/keypair.rs b/src/rsa/keypair.rs index c247cad9f4..78bafaa097 100644 --- a/src/rsa/keypair.rs +++ b/src/rsa/keypair.rs @@ -330,7 +330,8 @@ impl KeyPair { // checking p * q == 0 (mod n) is equivalent to checking p * q == n. let public_key = PublicKey::new(public_key, cpu_features)?; - let n = &public_key.inner().n().value(); + let borrowed_public_key = public_key.inner(); + let n = borrowed_public_key.n().value(); let nm = &n.modulus(cpu_features); let q = q.build(cpu_features); @@ -607,7 +608,8 @@ impl KeyPair { // RFC 8017 Section 5.1.2: RSADP, using the Chinese Remainder Theorem // with Garner's algorithm. - let n = &self.public.inner().n().value(); + let borrowed_public = self.public.inner(); + let n = borrowed_public.n().value(); let nm = &n.modulus(cpu_features); // Step 1. The value zero is also rejected. diff --git a/src/rsa/public_key.rs b/src/rsa/public_key.rs index eaf16457e0..5e5934e8fa 100644 --- a/src/rsa/public_key.rs +++ b/src/rsa/public_key.rs @@ -12,8 +12,9 @@ // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -use super::base; +use super::{base, N}; use crate::{ + arithmetic::{bigint, montgomery::RR}, cpu, error, io::{self, der, der_writer}, }; @@ -24,7 +25,7 @@ pub(super) use base::public_key::ValidatedInput; /// An RSA Public Key. #[derive(Clone)] pub struct PublicKey { - inner: base::PublicKey, + inner: base::public_key::PublicKey>, serialized: Box<[u8]>, } @@ -35,7 +36,7 @@ impl PublicKey { input: ValidatedInput<'_>, cpu_features: cpu::Features, ) -> Result { - let inner = input.build(cpu_features); + let inner = input.build_boxed(cpu_features); let n_bytes = input.n().input(); let e_bytes = input.e_input(); @@ -62,11 +63,15 @@ impl PublicKey { /// The modulus length is rounded up to a whole number of bytes if its /// bit length isn't a multiple of 8. pub fn modulus_len(&self) -> usize { - self.inner.n().len_bits().as_usize_bytes_rounded_up() + self.inner + .reborrow() + .n() + .len_bits() + .as_usize_bytes_rounded_up() } - pub(super) fn inner(&self) -> &base::PublicKey { - &self.inner + pub(super) fn inner(&self) -> base::public_key::PublicKey> { + self.inner.reborrow() } } diff --git a/src/rsa/verification.rs b/src/rsa/verification.rs index c60fc0664d..96665f4ee5 100644 --- a/src/rsa/verification.rs +++ b/src/rsa/verification.rs @@ -19,6 +19,7 @@ use super::{ parse_public_key, PublicKeyComponents, RsaParameters, PUBLIC_KEY_PUBLIC_MODULUS_MAX_LEN, }; use crate::{ + arithmetic::bigint, bits::{self, FromByteLen as _}, cpu, digest, error::{self, InputTooLongError}, @@ -207,7 +208,8 @@ fn verify( // exponent value is 2**16 + 1, but it isn't clear if this is just for // signing or also for verification. We support exponents of 3 and larger // for compatibility with other commonly-used crypto libraries. - let key = validated.build(cpu_features); + let mut out = bigint::OversizedUninit::<2>::new(); + let key = validated.build(&mut out, cpu_features); // RFC 8017 Section 5.2.2: RSAVP1. let mut decoded = [0u8; PUBLIC_KEY_PUBLIC_MODULUS_MAX_LEN];