Skip to main content

rsa/pkcs1v15/
signing_key.rs

1use super::{pkcs1v15_generate_prefix, sign, GenericVerifyingKey, Signature, VerifyingKey};
2use crate::{dummy_rng::DummyRng, Result, RsaPrivateKey};
3#[cfg(feature = "alloc")]
4use alloc::vec::Vec;
5use const_oid::AssociatedOid;
6use core::marker::PhantomData;
7use digest::{Digest, FixedOutput, HashMarker, Update};
8use rand_core::{CryptoRng, TryCryptoRng};
9use signature::{
10    hazmat::PrehashSigner, DigestSigner, Keypair, MultipartSigner, RandomizedDigestSigner,
11    RandomizedMultipartSigner, RandomizedSigner, Signer,
12};
13use zeroize::ZeroizeOnDrop;
14
15#[cfg(feature = "encoding")]
16use {
17    super::oid,
18    pkcs8::{EncodePrivateKey, SecretDocument},
19    spki::{
20        der::AnyRef, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier,
21        SignatureAlgorithmIdentifier,
22    },
23};
24#[cfg(feature = "serde")]
25use {
26    pkcs8::DecodePrivateKey,
27    serdect::serde::{de, ser, Deserialize, Serialize},
28};
29
30/// Signing key for `RSASSA-PKCS1-v1_5` signatures as described in [RFC8017 § 8.2].
31///
32/// [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2
33#[derive(Debug, Clone)]
34pub struct SigningKey<D>
35where
36    D: Digest,
37{
38    inner: RsaPrivateKey,
39    prefix: Vec<u8>,
40    phantom: PhantomData<D>,
41}
42
43impl<D> SigningKey<D>
44where
45    D: Digest + AssociatedOid,
46{
47    /// Create a new signing key with a prefix for the digest `D`.
48    pub fn new(key: RsaPrivateKey) -> Self {
49        Self {
50            inner: key,
51            prefix: pkcs1v15_generate_prefix::<D>(),
52            phantom: Default::default(),
53        }
54    }
55
56    /// Generate a new signing key with a prefix for the digest `D`.
57    pub fn random<R: CryptoRng + ?Sized>(rng: &mut R, bit_size: usize) -> Result<Self> {
58        Ok(Self {
59            inner: RsaPrivateKey::new(rng, bit_size)?,
60            prefix: pkcs1v15_generate_prefix::<D>(),
61            phantom: Default::default(),
62        })
63    }
64}
65
66impl<D> SigningKey<D>
67where
68    D: Digest,
69{
70    /// Create a new signing key from the give RSA private key with an empty prefix.
71    ///
72    /// ## Note: unprefixed signatures are uncommon
73    ///
74    /// In most cases you'll want to use [`SigningKey::new`].
75    pub fn new_unprefixed(key: RsaPrivateKey) -> Self {
76        Self {
77            inner: key,
78            prefix: Vec::new(),
79            phantom: Default::default(),
80        }
81    }
82
83    /// Generate a new signing key with an empty prefix.
84    pub fn random_unprefixed<R: CryptoRng + ?Sized>(rng: &mut R, bit_size: usize) -> Result<Self> {
85        Ok(Self {
86            inner: RsaPrivateKey::new(rng, bit_size)?,
87            prefix: Vec::new(),
88            phantom: Default::default(),
89        })
90    }
91}
92
93//
94// `*Signer` trait impls
95//
96
97impl<D> DigestSigner<D, Signature> for SigningKey<D>
98where
99    D: Default + FixedOutput + HashMarker + Update,
100{
101    fn try_sign_digest<F: Fn(&mut D) -> signature::Result<()>>(
102        &self,
103        f: F,
104    ) -> signature::Result<Signature> {
105        let mut digest = D::default();
106        f(&mut digest)?;
107        sign::<DummyRng>(None, &self.inner, &self.prefix, &digest.finalize_fixed())?
108            .as_slice()
109            .try_into()
110    }
111}
112
113impl<D> PrehashSigner<Signature> for SigningKey<D>
114where
115    D: Digest,
116{
117    fn sign_prehash(&self, prehash: &[u8]) -> signature::Result<Signature> {
118        sign::<DummyRng>(None, &self.inner, &self.prefix, prehash)?
119            .as_slice()
120            .try_into()
121    }
122}
123
124impl<D> RandomizedDigestSigner<D, Signature> for SigningKey<D>
125where
126    D: Default + FixedOutput + HashMarker + Update,
127{
128    fn try_sign_digest_with_rng<
129        R: TryCryptoRng + ?Sized,
130        F: Fn(&mut D) -> signature::Result<()>,
131    >(
132        &self,
133        rng: &mut R,
134        f: F,
135    ) -> signature::Result<Signature> {
136        let mut digest = D::default();
137        f(&mut digest)?;
138        sign(
139            Some(rng),
140            &self.inner,
141            &self.prefix,
142            &digest.finalize_fixed(),
143        )?
144        .as_slice()
145        .try_into()
146    }
147}
148
149impl<D> RandomizedSigner<Signature> for SigningKey<D>
150where
151    D: Digest,
152{
153    fn try_sign_with_rng<R: TryCryptoRng + ?Sized>(
154        &self,
155        rng: &mut R,
156        msg: &[u8],
157    ) -> signature::Result<Signature> {
158        self.try_multipart_sign_with_rng(rng, &[msg])
159    }
160}
161
162impl<D> RandomizedMultipartSigner<Signature> for SigningKey<D>
163where
164    D: Digest,
165{
166    fn try_multipart_sign_with_rng<R: TryCryptoRng + ?Sized>(
167        &self,
168        rng: &mut R,
169        msg: &[&[u8]],
170    ) -> signature::Result<Signature> {
171        let mut digest = D::new();
172        msg.iter().for_each(|slice| digest.update(slice));
173        sign(Some(rng), &self.inner, &self.prefix, &digest.finalize())?
174            .as_slice()
175            .try_into()
176    }
177}
178
179impl<D> Signer<Signature> for SigningKey<D>
180where
181    D: Digest,
182{
183    fn try_sign(&self, msg: &[u8]) -> signature::Result<Signature> {
184        self.try_multipart_sign(&[msg])
185    }
186}
187
188impl<D> MultipartSigner<Signature> for SigningKey<D>
189where
190    D: Digest,
191{
192    fn try_multipart_sign(&self, msg: &[&[u8]]) -> signature::Result<Signature> {
193        let mut digest = D::new();
194        msg.iter().for_each(|slice| digest.update(slice));
195        sign::<DummyRng>(None, &self.inner, &self.prefix, &digest.finalize())?
196            .as_slice()
197            .try_into()
198    }
199}
200
201//
202// Other trait impls
203//
204
205impl<D> AsRef<RsaPrivateKey> for SigningKey<D>
206where
207    D: Digest,
208{
209    fn as_ref(&self) -> &RsaPrivateKey {
210        &self.inner
211    }
212}
213
214#[cfg(feature = "encoding")]
215impl<D> AssociatedAlgorithmIdentifier for SigningKey<D>
216where
217    D: Digest,
218{
219    type Params = AnyRef<'static>;
220
221    const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
222}
223
224#[cfg(feature = "encoding")]
225impl<D> EncodePrivateKey for SigningKey<D>
226where
227    D: Digest,
228{
229    fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
230        self.inner.to_pkcs8_der()
231    }
232}
233
234impl<D> From<RsaPrivateKey> for SigningKey<D>
235where
236    D: Digest + AssociatedOid,
237{
238    fn from(key: RsaPrivateKey) -> Self {
239        Self::new(key)
240    }
241}
242
243impl<D> From<SigningKey<D>> for RsaPrivateKey
244where
245    D: Digest,
246{
247    fn from(key: SigningKey<D>) -> Self {
248        key.inner
249    }
250}
251
252impl<D> Keypair for SigningKey<D>
253where
254    D: Digest,
255{
256    type VerifyingKey = VerifyingKey<D>;
257
258    fn verifying_key(&self) -> Self::VerifyingKey {
259        GenericVerifyingKey {
260            inner: self.inner.to_public_key(),
261            prefix: self.prefix.clone(),
262            phantom: Default::default(),
263        }
264    }
265}
266
267#[cfg(feature = "encoding")]
268impl<D> SignatureAlgorithmIdentifier for SigningKey<D>
269where
270    D: Digest + oid::RsaSignatureAssociatedOid,
271{
272    type Params = AnyRef<'static>;
273
274    const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> =
275        AlgorithmIdentifierRef {
276            oid: D::OID,
277            parameters: Some(AnyRef::NULL),
278        };
279}
280
281#[cfg(feature = "encoding")]
282impl<D> TryFrom<pkcs8::PrivateKeyInfoRef<'_>> for SigningKey<D>
283where
284    D: Digest + AssociatedOid,
285{
286    type Error = pkcs8::Error;
287
288    fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
289        private_key_info
290            .algorithm
291            .assert_algorithm_oid(pkcs1::ALGORITHM_OID)?;
292        RsaPrivateKey::try_from(private_key_info).map(Self::new)
293    }
294}
295
296impl<D> ZeroizeOnDrop for SigningKey<D> where D: Digest {}
297
298impl<D> PartialEq for SigningKey<D>
299where
300    D: Digest,
301{
302    fn eq(&self, other: &Self) -> bool {
303        self.inner == other.inner && self.prefix == other.prefix
304    }
305}
306
307#[cfg(feature = "serde")]
308impl<D> Serialize for SigningKey<D>
309where
310    D: Digest,
311{
312    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
313    where
314        S: serdect::serde::Serializer,
315    {
316        let der = self.to_pkcs8_der().map_err(ser::Error::custom)?;
317        serdect::slice::serialize_hex_lower_or_bin(&der.as_bytes(), serializer)
318    }
319}
320
321#[cfg(feature = "serde")]
322impl<'de, D> Deserialize<'de> for SigningKey<D>
323where
324    D: Digest + AssociatedOid,
325{
326    fn deserialize<De>(deserializer: De) -> core::result::Result<Self, De::Error>
327    where
328        De: serdect::serde::Deserializer<'de>,
329    {
330        let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
331        Self::from_pkcs8_der(&der_bytes).map_err(de::Error::custom)
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    #[test]
338    #[cfg(all(feature = "hazmat", feature = "serde"))]
339    fn test_serde() {
340        use super::*;
341        use crate::RsaPrivateKey;
342        use rand::rngs::ChaCha8Rng;
343        use rand_core::SeedableRng;
344        use serde_test::{assert_tokens, Configure, Token};
345        use sha2::Sha256;
346
347        let mut rng = ChaCha8Rng::from_seed([42; 32]);
348        let priv_key = RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key");
349        let signing_key = SigningKey::<Sha256>::new(priv_key);
350
351        let tokens = [Token::Str(concat!(
352            "3056020100300d06092a864886f70d010101050004423040020100020900ab240c",
353            "3361d02e370203010001020811e54a15259d22f9020500ceff5cf3020500d3a7aa",
354            "ad020500ccaddf17020500cb529d3d020500bb526d6f",
355        ))];
356
357        assert_tokens(&signing_key.readable(), &tokens);
358    }
359}