Skip to main content

rsa/pss/
blinded_signing_key.rs

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