sad_rsa/pss/
blinded_signing_key.rs

1use super::{sign_digest, Signature, VerifyingKey};
2use crate::{Result, RsaPrivateKey};
3use core::marker::PhantomData;
4use digest::{Digest, FixedOutputReset, HashMarker, Update};
5use rand_core::{CryptoRng, TryCryptoRng};
6use signature::{
7    hazmat::RandomizedPrehashSigner, Keypair, RandomizedDigestSigner, RandomizedMultipartSigner,
8    RandomizedSigner,
9};
10use zeroize::ZeroizeOnDrop;
11
12#[cfg(feature = "encoding")]
13use {
14    super::get_pss_signature_algo_id,
15    const_oid::AssociatedOid,
16    pkcs8::{EncodePrivateKey, SecretDocument},
17    spki::{
18        der::AnyRef, AlgorithmIdentifierOwned, AlgorithmIdentifierRef,
19        AssociatedAlgorithmIdentifier, DynSignatureAlgorithmIdentifier,
20    },
21};
22#[cfg(feature = "serde")]
23use {
24    pkcs8::DecodePrivateKey,
25    serdect::serde::{de, ser, Deserialize, Serialize},
26};
27
28/// Signing key for producing "blinded" RSASSA-PSS signatures as described in
29/// [draft-irtf-cfrg-rsa-blind-signatures](https://datatracker.ietf.org/doc/draft-irtf-cfrg-rsa-blind-signatures/).
30#[derive(Debug, Clone)]
31pub struct BlindedSigningKey<D>
32where
33    D: Digest,
34{
35    inner: RsaPrivateKey,
36    salt_len: usize,
37    phantom: PhantomData<D>,
38}
39
40impl<D> BlindedSigningKey<D>
41where
42    D: Digest,
43{
44    /// Create a new RSASSA-PSS signing key which produces "blinded"
45    /// signatures.
46    /// Digest output size is used as a salt length.
47    pub fn new(key: RsaPrivateKey) -> Self {
48        Self::new_with_salt_len(key, <D as Digest>::output_size())
49    }
50
51    /// Create a new RSASSA-PSS signing key which produces "blinded"
52    /// signatures with a salt of the given length.
53    pub fn new_with_salt_len(key: RsaPrivateKey, salt_len: usize) -> Self {
54        Self {
55            inner: key,
56            salt_len,
57            phantom: Default::default(),
58        }
59    }
60
61    /// Create a new random RSASSA-PSS signing key which produces "blinded"
62    /// signatures.
63    /// Digest output size is used as a salt length.
64    pub fn random<R: CryptoRng + ?Sized>(rng: &mut R, bit_size: usize) -> Result<Self> {
65        Self::random_with_salt_len(rng, bit_size, <D as Digest>::output_size())
66    }
67
68    /// Create a new random RSASSA-PSS signing key which produces "blinded"
69    /// signatures with a salt of the given length.
70    pub fn random_with_salt_len<R: CryptoRng + ?Sized>(
71        rng: &mut R,
72        bit_size: usize,
73        salt_len: usize,
74    ) -> Result<Self> {
75        Ok(Self {
76            inner: RsaPrivateKey::new(rng, bit_size)?,
77            salt_len,
78            phantom: Default::default(),
79        })
80    }
81
82    /// Return specified salt length for this key
83    pub fn salt_len(&self) -> usize {
84        self.salt_len
85    }
86}
87
88//
89// `*Signer` trait impls
90//
91
92impl<D> RandomizedSigner<Signature> for BlindedSigningKey<D>
93where
94    D: Digest + FixedOutputReset,
95{
96    fn try_sign_with_rng<R: TryCryptoRng + ?Sized>(
97        &self,
98        rng: &mut R,
99        msg: &[u8],
100    ) -> signature::Result<Signature> {
101        self.try_multipart_sign_with_rng(rng, &[msg])
102    }
103}
104
105impl<D> RandomizedMultipartSigner<Signature> for BlindedSigningKey<D>
106where
107    D: Digest + FixedOutputReset,
108{
109    fn try_multipart_sign_with_rng<R: TryCryptoRng + ?Sized>(
110        &self,
111        rng: &mut R,
112        msg: &[&[u8]],
113    ) -> signature::Result<Signature> {
114        let mut digest = D::new();
115        msg.iter()
116            .for_each(|slice| <D as Digest>::update(&mut digest, slice));
117        sign_digest::<_, D>(rng, true, &self.inner, &digest.finalize(), self.salt_len)?
118            .as_slice()
119            .try_into()
120    }
121}
122
123impl<D> RandomizedDigestSigner<D, Signature> for BlindedSigningKey<D>
124where
125    D: Default + FixedOutputReset + HashMarker + Update,
126{
127    fn try_sign_digest_with_rng<
128        R: TryCryptoRng + ?Sized,
129        F: Fn(&mut D) -> signature::Result<()>,
130    >(
131        &self,
132        rng: &mut R,
133        f: F,
134    ) -> signature::Result<Signature> {
135        let mut digest = D::default();
136        f(&mut digest)?;
137        sign_digest::<_, D>(rng, true, &self.inner, &digest.finalize(), self.salt_len)?
138            .as_slice()
139            .try_into()
140    }
141}
142
143impl<D> RandomizedPrehashSigner<Signature> for BlindedSigningKey<D>
144where
145    D: Digest + FixedOutputReset,
146{
147    fn sign_prehash_with_rng<R: TryCryptoRng + ?Sized>(
148        &self,
149        rng: &mut R,
150        prehash: &[u8],
151    ) -> signature::Result<Signature> {
152        sign_digest::<_, D>(rng, true, &self.inner, prehash, self.salt_len)?
153            .as_slice()
154            .try_into()
155    }
156}
157
158//
159// Other trait impls
160//
161
162impl<D> AsRef<RsaPrivateKey> for BlindedSigningKey<D>
163where
164    D: Digest,
165{
166    fn as_ref(&self) -> &RsaPrivateKey {
167        &self.inner
168    }
169}
170
171#[cfg(feature = "encoding")]
172impl<D> AssociatedAlgorithmIdentifier for BlindedSigningKey<D>
173where
174    D: Digest,
175{
176    type Params = AnyRef<'static>;
177
178    const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
179}
180
181#[cfg(feature = "encoding")]
182impl<D> DynSignatureAlgorithmIdentifier for BlindedSigningKey<D>
183where
184    D: Digest + AssociatedOid,
185{
186    fn signature_algorithm_identifier(&self) -> spki::Result<AlgorithmIdentifierOwned> {
187        get_pss_signature_algo_id::<D>(self.salt_len as u8)
188    }
189}
190
191#[cfg(feature = "encoding")]
192impl<D> EncodePrivateKey for BlindedSigningKey<D>
193where
194    D: Digest,
195{
196    fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
197        self.inner.to_pkcs8_der()
198    }
199}
200
201impl<D> From<RsaPrivateKey> for BlindedSigningKey<D>
202where
203    D: Digest,
204{
205    fn from(key: RsaPrivateKey) -> Self {
206        Self::new(key)
207    }
208}
209
210impl<D> From<BlindedSigningKey<D>> for RsaPrivateKey
211where
212    D: Digest,
213{
214    fn from(key: BlindedSigningKey<D>) -> Self {
215        key.inner
216    }
217}
218
219impl<D> Keypair for BlindedSigningKey<D>
220where
221    D: Digest,
222{
223    type VerifyingKey = VerifyingKey<D>;
224    fn verifying_key(&self) -> Self::VerifyingKey {
225        VerifyingKey {
226            inner: self.inner.to_public_key(),
227            salt_len: Some(self.salt_len),
228            phantom: Default::default(),
229        }
230    }
231}
232
233#[cfg(feature = "encoding")]
234impl<D> TryFrom<pkcs8::PrivateKeyInfoRef<'_>> for BlindedSigningKey<D>
235where
236    D: Digest + AssociatedOid,
237{
238    type Error = pkcs8::Error;
239
240    fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
241        RsaPrivateKey::try_from(private_key_info).map(Self::new)
242    }
243}
244
245impl<D> ZeroizeOnDrop for BlindedSigningKey<D> where D: Digest {}
246
247impl<D> PartialEq for BlindedSigningKey<D>
248where
249    D: Digest,
250{
251    fn eq(&self, other: &Self) -> bool {
252        self.inner == other.inner && self.salt_len == other.salt_len
253    }
254}
255
256#[cfg(feature = "serde")]
257impl<D> Serialize for BlindedSigningKey<D>
258where
259    D: Digest,
260{
261    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
262    where
263        S: serde::Serializer,
264    {
265        let der = self.to_pkcs8_der().map_err(ser::Error::custom)?;
266        serdect::slice::serialize_hex_lower_or_bin(&der.as_bytes(), serializer)
267    }
268}
269
270#[cfg(feature = "serde")]
271impl<'de, D> Deserialize<'de> for BlindedSigningKey<D>
272where
273    D: Digest + AssociatedOid,
274{
275    fn deserialize<De>(deserializer: De) -> core::result::Result<Self, De::Error>
276    where
277        De: serde::Deserializer<'de>,
278    {
279        let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
280        Self::from_pkcs8_der(&der_bytes).map_err(de::Error::custom)
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    #[test]
287    #[cfg(all(feature = "hazmat", feature = "serde"))]
288    fn test_serde() {
289        use super::*;
290        use rand::rngs::ChaCha8Rng;
291        use rand_core::SeedableRng;
292        use serde_test::{assert_tokens, Configure, Token};
293        use sha2::Sha256;
294
295        let mut rng = ChaCha8Rng::from_seed([42; 32]);
296        let signing_key = BlindedSigningKey::<Sha256>::new(
297            RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key"),
298        );
299
300        let tokens = [Token::Str(concat!(
301            "3056020100300d06092a864886f70d010101050004423040020100020900ab240c",
302            "3361d02e370203010001020811e54a15259d22f9020500ceff5cf3020500d3a7aa",
303            "ad020500ccaddf17020500cb529d3d020500bb526d6f"
304        ))];
305        assert_tokens(&signing_key.readable(), &tokens);
306    }
307}