Skip to main content

rsa/pkcs1v15/
generic_signing_key.rs

1//! Heapless `RSASSA-PKCS1-v1_5` signing key. Generic over `D` (digest),
2//! `T` (integer), and `M` (Montgomery parameters), mirrors the shape of
3//! [`super::GenericVerifyingKey`] on the verify side.
4//!
5//! Compatible with the no_alloc build path —
6//! [`GenericRsaPrivateKey<T, M>`] storage, fixed-size [`Prefix`] for
7//! the DigestInfo prefix, caller-supplied scratch buffers for the EM
8//! and signature output.
9
10#[cfg(feature = "alloc")]
11use super::pkcs1v15_generate_prefix;
12#[cfg(not(feature = "alloc"))]
13use super::{pkcs1v15_generate_prefix_helper, Prefix};
14use super::{sign_into, sign_with_rng_into, GenericVerifyingKey};
15use crate::{
16    errors::Result,
17    key::GenericRsaPrivateKey,
18    traits::{
19        modular::{ModulusParams, Pow, PowBoundedExp},
20        PublicKeyParts, UnsignedModularInt,
21    },
22};
23use const_oid::AssociatedOid;
24use core::fmt;
25use core::marker::PhantomData;
26use digest::Digest;
27use zeroize::Zeroize;
28
29/// Signing key for `RSASSA-PKCS1-v1_5` signatures — heapless variant.
30///
31/// Generic over the digest `D`, integer type `T`, and Montgomery parameters
32/// `M`. Holds a [`GenericRsaPrivateKey<T, M>`] plus the precomputed
33/// DigestInfo prefix for `D`.
34pub struct GenericSigningKey<D, T, M>
35where
36    D: Digest,
37    T: UnsignedModularInt + Zeroize,
38    M: ModulusParams<Modulus = T>,
39{
40    pub(super) inner: GenericRsaPrivateKey<T, M>,
41    #[cfg(feature = "alloc")]
42    pub(super) prefix: alloc::vec::Vec<u8>,
43    #[cfg(not(feature = "alloc"))]
44    pub(super) prefix: Prefix,
45    pub(super) phantom: PhantomData<D>,
46}
47
48// Manual `Debug` — `#[derive]` would synthesize unwanted bounds on
49// `D`/`T`/`M`; the inner private key already redacts `d`.
50impl<D, T, M> fmt::Debug for GenericSigningKey<D, T, M>
51where
52    D: Digest,
53    T: UnsignedModularInt + Zeroize,
54    M: ModulusParams<Modulus = T>,
55    GenericRsaPrivateKey<T, M>: fmt::Debug,
56{
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        f.debug_struct("GenericSigningKey")
59            .field("inner", &self.inner)
60            .field("prefix", &self.prefix)
61            .finish()
62    }
63}
64
65// Manual Clone impls — split by `alloc` cfg to avoid imposing
66// the `M::MontgomeryForm: Clone` bound on heapless backends where it
67// isn't needed (only `GenericRsaPrivateKey`'s `precomputed` field
68// requires it, and that field is `cfg(alloc)`).
69#[cfg(feature = "alloc")]
70impl<D, T, M> Clone for GenericSigningKey<D, T, M>
71where
72    D: Digest,
73    T: UnsignedModularInt + Zeroize + Clone,
74    M: ModulusParams<Modulus = T> + Clone,
75    M::MontgomeryForm: Clone,
76{
77    fn clone(&self) -> Self {
78        Self {
79            inner: self.inner.clone(),
80            prefix: self.prefix.clone(),
81            phantom: PhantomData,
82        }
83    }
84}
85
86#[cfg(not(feature = "alloc"))]
87impl<D, T, M> Clone for GenericSigningKey<D, T, M>
88where
89    D: Digest,
90    T: UnsignedModularInt + Zeroize + Clone,
91    M: ModulusParams<Modulus = T> + Clone,
92{
93    fn clone(&self) -> Self {
94        Self {
95            inner: self.inner.clone(),
96            prefix: self.prefix.clone(),
97            phantom: PhantomData,
98        }
99    }
100}
101
102impl<D, T, M> GenericSigningKey<D, T, M>
103where
104    D: Digest + AssociatedOid,
105    T: UnsignedModularInt + Zeroize,
106    M: ModulusParams<Modulus = T>,
107{
108    /// Create a new signing key with a precomputed prefix for digest `D`.
109    pub fn new(key: GenericRsaPrivateKey<T, M>) -> Self {
110        Self {
111            inner: key,
112            #[cfg(feature = "alloc")]
113            prefix: pkcs1v15_generate_prefix::<D>(),
114            #[cfg(not(feature = "alloc"))]
115            prefix: pkcs1v15_generate_prefix_helper::<D>(),
116            phantom: PhantomData,
117        }
118    }
119}
120
121impl<D, T, M> GenericSigningKey<D, T, M>
122where
123    D: Digest,
124    T: UnsignedModularInt + Zeroize,
125    M: ModulusParams<Modulus = T>,
126{
127    /// Create a new signing key with an empty prefix (raw signatures, no
128    /// DigestInfo wrapper). Unusual — most callers want [`new`] instead.
129    pub fn new_unprefixed(key: GenericRsaPrivateKey<T, M>) -> Self {
130        Self {
131            inner: key,
132            #[cfg(feature = "alloc")]
133            prefix: alloc::vec::Vec::new(),
134            #[cfg(not(feature = "alloc"))]
135            prefix: Prefix::new(),
136            phantom: PhantomData,
137        }
138    }
139}
140
141// Borrow the inner private-key components — `AsRef` impl mirrors
142// the verifier's `AsRef<GenericRsaPublicKey<T, M>>`.
143impl<D, T, M> AsRef<GenericRsaPrivateKey<T, M>> for GenericSigningKey<D, T, M>
144where
145    D: Digest,
146    T: UnsignedModularInt + Zeroize,
147    M: ModulusParams<Modulus = T>,
148{
149    fn as_ref(&self) -> &GenericRsaPrivateKey<T, M> {
150        &self.inner
151    }
152}
153
154impl<D, T, M> GenericSigningKey<D, T, M>
155where
156    D: Digest,
157    T: UnsignedModularInt + Zeroize,
158    T::Bytes: Zeroize,
159    M: ModulusParams<Modulus = T> + crate::traits::modular::CtModulusParams,
160    M::MontgomeryForm: Pow<M> + PowBoundedExp<M>,
161{
162    /// Sign `msg` (hashed internally with `D`) into `sig_storage`.
163    /// Uses `em_storage` as scratch for the EM encoding.
164    ///
165    /// Both buffers must be at least `self.inner.size()` bytes.
166    /// Mirrors the shape of
167    /// [`crate::traits::RandomizedEncryptor::encrypt_with_rng_into`] on
168    /// the encrypt side: caller owns storage, returned slice borrows from
169    /// `sig_storage` with the same lifetime.
170    pub fn try_sign_into<'sig>(
171        &self,
172        msg: &[u8],
173        em_storage: &mut [u8],
174        sig_storage: &'sig mut [u8],
175    ) -> Result<&'sig [u8]> {
176        let digest = D::digest(msg);
177        let k = self.inner.size();
178        sign_into(
179            self.inner.n_params(),
180            crate::traits::keys::PrivateKeyParts::d(&self.inner),
181            self.inner.e(),
182            self.prefix.as_ref(),
183            digest.as_ref(),
184            k,
185            em_storage,
186            sig_storage,
187        )
188    }
189
190    /// Sign a precomputed `prehash` (`D::output_size()` bytes) into
191    /// `sig_storage`. The caller is responsible for the hash; this skips
192    /// the internal `D::digest(msg)` step.
193    ///
194    /// Returns [`Error::InputNotHashed`] if `prehash.len()` doesn't match
195    /// `D::output_size()` — a mismatch would silently produce malformed
196    /// PKCS#1 v1.5 padding rather than a usable signature.
197    pub fn try_sign_prehash_into<'sig>(
198        &self,
199        prehash: &[u8],
200        em_storage: &mut [u8],
201        sig_storage: &'sig mut [u8],
202    ) -> Result<&'sig [u8]> {
203        if prehash.len() != <D as Digest>::output_size() {
204            return Err(crate::errors::Error::InputNotHashed);
205        }
206        let k = self.inner.size();
207        sign_into(
208            self.inner.n_params(),
209            crate::traits::keys::PrivateKeyParts::d(&self.inner),
210            self.inner.e(),
211            self.prefix.as_ref(),
212            prehash,
213            k,
214            em_storage,
215            sig_storage,
216        )
217    }
218}
219
220// RNG-taking blinded sign variants — mirror the deterministic pair
221// above but route through the blinded private op
222// (`rsa_private_op_and_check_blinded`), so the exponentiation with
223// `d` never operates on the attacker-known `EM` directly. The extra
224// bounds are satisfied by both alloc (`BoxedUint`/`BoxedMontyParams`)
225// and modmath (`ModMathValue<T>` / `ModMathParams<T, Ct>`) backends.
226impl<D, T, M> GenericSigningKey<D, T, M>
227where
228    D: Digest,
229    T: UnsignedModularInt + Zeroize + crate::traits::modular::TryRandomMod,
230    T::Bytes: Zeroize,
231    M: ModulusParams<Modulus = T> + crate::traits::modular::CtModulusParams,
232    M::MontgomeryForm: Pow<M>
233        + PowBoundedExp<M>
234        + crate::traits::modular::InvertCt<M>
235        + crate::traits::modular::MulCt<M>,
236{
237    /// RNG-driven blinded variant of [`Self::try_sign_into`]. Uses
238    /// `rng` to sample a fresh blinding factor per signature.
239    pub fn try_sign_with_rng_into<'sig, R: rand_core::TryCryptoRng + ?Sized>(
240        &self,
241        rng: &mut R,
242        msg: &[u8],
243        em_storage: &mut [u8],
244        sig_storage: &'sig mut [u8],
245    ) -> Result<&'sig [u8]> {
246        let digest = D::digest(msg);
247        let k = self.inner.size();
248        sign_with_rng_into(
249            rng,
250            self.inner.n_params(),
251            crate::traits::keys::PrivateKeyParts::d(&self.inner),
252            self.inner.e(),
253            self.prefix.as_ref(),
254            digest.as_ref(),
255            k,
256            em_storage,
257            sig_storage,
258        )
259    }
260
261    /// RNG-driven blinded variant of [`Self::try_sign_prehash_into`].
262    pub fn try_sign_prehash_with_rng_into<'sig, R: rand_core::TryCryptoRng + ?Sized>(
263        &self,
264        rng: &mut R,
265        prehash: &[u8],
266        em_storage: &mut [u8],
267        sig_storage: &'sig mut [u8],
268    ) -> Result<&'sig [u8]> {
269        if prehash.len() != <D as Digest>::output_size() {
270            return Err(crate::errors::Error::InputNotHashed);
271        }
272        let k = self.inner.size();
273        sign_with_rng_into(
274            rng,
275            self.inner.n_params(),
276            crate::traits::keys::PrivateKeyParts::d(&self.inner),
277            self.inner.e(),
278            self.prefix.as_ref(),
279            prehash,
280            k,
281            em_storage,
282            sig_storage,
283        )
284    }
285}
286
287// Allow callers to wrap a `GenericSigningKey` in `zeroize::Zeroizing<_>`
288// or invoke `.zeroize()` directly. The prefix is public DigestInfo
289// material; only the inner `GenericRsaPrivateKey` carries secret bytes.
290impl<D, T, M> Zeroize for GenericSigningKey<D, T, M>
291where
292    D: Digest,
293    T: UnsignedModularInt + Zeroize,
294    M: ModulusParams<Modulus = T>,
295{
296    fn zeroize(&mut self) {
297        self.inner.zeroize();
298    }
299}
300
301impl<D, T, M> GenericSigningKey<D, T, M>
302where
303    D: Digest,
304    T: UnsignedModularInt + Zeroize,
305    M: ModulusParams<Modulus = T>,
306{
307    /// Derive the matching [`GenericVerifyingKey`] from this signing
308    /// key, preserving the existing DigestInfo prefix (so unprefixed
309    /// signing keys yield unprefixed verifying keys). Not named
310    /// `verifying_key` because that would shadow
311    /// [`signature::Keypair::verifying_key`] for callers using
312    /// method-call syntax.
313    pub fn to_verifying_key(&self) -> GenericVerifyingKey<D, T, M>
314    where
315        crate::key::GenericRsaPublicKey<T, M>: Clone,
316    {
317        GenericVerifyingKey {
318            inner: self.inner.as_public().clone(),
319            prefix: self.prefix.clone(),
320            phantom: PhantomData,
321        }
322    }
323}