diff --git a/src/key.rs b/src/key.rs index de565495..9f0e6881 100644 --- a/src/key.rs +++ b/src/key.rs @@ -21,6 +21,7 @@ use crate::algorithms::rsa::{ recover_primes, to_monty, }; +#[cfg(not(feature = "getrandom"))] use crate::dummy_rng::DummyRng; use crate::errors::{Error, Result}; use crate::traits::keys::{CrtValue, PrivateKeyParts, PublicKeyParts}; @@ -733,9 +734,20 @@ impl RsaPrivateKey { } /// Sign the given digest. + /// + /// With the `getrandom` feature (enabled by default), supplies the operating system RNG for + /// RSA blinding and any randomness required by the signature scheme. Without this feature, + /// use [`Self::sign_with_rng`] to supply an RNG explicitly. #[allow(clippy::missing_errors_doc, reason = "TODO")] pub fn sign(&self, padding: S, digest_in: &[u8]) -> Result> { - padding.sign(Option::<&mut DummyRng>::None, self, digest_in) + #[cfg(feature = "getrandom")] + { + padding.sign(Some(&mut crypto_common::getrandom::SysRng), self, digest_in) + } + #[cfg(not(feature = "getrandom"))] + { + padding.sign(Option::<&mut DummyRng>::None, self, digest_in) + } } /// Sign the given digest using the provided `rng`, which is used in the @@ -745,9 +757,8 @@ impl RsaPrivateKey { /// to mask the private key operation with random blinding, which helps /// mitigate sidechannel attacks. /// - [`Pss`][`crate::Pss`] always requires randomness. Use - /// [`Pss::new`][`crate::Pss::new`] for a standard RSASSA-PSS signature, or - /// [`Pss::new_blinded`][`crate::Pss::new_blinded`] for RSA-BSSA blind - /// signatures. + /// [`Pss::new`][`crate::Pss::new`] for a standard RSASSA-PSS signature with + /// RSA base blinding enabled by default. #[allow(clippy::missing_errors_doc, reason = "TODO")] pub fn sign_with_rng( &self, diff --git a/src/lib.rs b/src/lib.rs index e7211bd0..53ad8af9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -235,6 +235,7 @@ pub mod pkcs1v15; pub mod pss; pub mod traits; +#[cfg(not(feature = "getrandom"))] mod dummy_rng; mod encoding; mod key; diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index 1e14b992..8acfdb44 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -217,6 +217,27 @@ fn decrypt( Ok(em[k - length..].to_vec()) } +/// Sign with the default source of blinding randomness, when enabled. +fn sign_with_default_rng( + priv_key: &RsaPrivateKey, + prefix: &[u8], + hashed: &[u8], +) -> Result> { + #[cfg(feature = "getrandom")] + { + sign( + Some(&mut crypto_common::getrandom::SysRng), + priv_key, + prefix, + hashed, + ) + } + #[cfg(not(feature = "getrandom"))] + { + sign::(None, priv_key, prefix, hashed) + } +} + /// Calculates the signature of hashed using `RSASSA-PKCS1-V1_5-SIGN` from RSA PKCS#1 v1.5. /// Note that `hashed` must be the result of hashing the input message using the given hash /// function. If hash is `None`, hashed is signed directly. This isn't advisable except for diff --git a/src/pkcs1v15/signing_key.rs b/src/pkcs1v15/signing_key.rs index ce118248..5206f456 100644 --- a/src/pkcs1v15/signing_key.rs +++ b/src/pkcs1v15/signing_key.rs @@ -1,5 +1,5 @@ -use super::{Signature, VerifyingKey, pkcs1v15_generate_prefix, sign}; -use crate::{Result, RsaPrivateKey, dummy_rng::DummyRng}; +use super::{Signature, VerifyingKey, pkcs1v15_generate_prefix, sign, sign_with_default_rng}; +use crate::{Result, RsaPrivateKey}; use alloc::vec::Vec; use const_oid::AssociatedOid; use core::marker::PhantomData; @@ -28,6 +28,10 @@ use { /// Signing key for `RSASSA-PKCS1-v1_5` signatures as described in [RFC8017 § 8.2]. /// +/// With the `getrandom` feature (enabled by default), signing uses the operating system RNG for +/// RSA blinding. Without this feature, use the randomized signing traits to supply an RNG. +/// Blinding does not change the deterministic signature value. +/// /// [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2 #[derive(Debug, Clone)] pub struct SigningKey @@ -107,7 +111,7 @@ where ) -> signature::Result { let mut digest = D::default(); f(&mut digest)?; - sign::(None, &self.inner, &self.prefix, &digest.finalize_fixed())? + sign_with_default_rng(&self.inner, &self.prefix, &digest.finalize_fixed())? .as_slice() .try_into() } @@ -118,7 +122,7 @@ where D: Digest, { fn sign_prehash(&self, prehash: &[u8]) -> signature::Result { - sign::(None, &self.inner, &self.prefix, prehash)? + sign_with_default_rng(&self.inner, &self.prefix, prehash)? .as_slice() .try_into() } @@ -195,7 +199,7 @@ where fn try_multipart_sign(&self, msg: &[&[u8]]) -> signature::Result { let mut digest = D::new(); msg.iter().for_each(|slice| digest.update(slice)); - sign::(None, &self.inner, &self.prefix, &digest.finalize())? + sign_with_default_rng(&self.inner, &self.prefix, &digest.finalize())? .as_slice() .try_into() } diff --git a/src/pss.rs b/src/pss.rs index e139f5e8..7ffdfb56 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -9,15 +9,11 @@ //! [Probabilistic Signature Scheme]: https://en.wikipedia.org/wiki/Probabilistic_signature_scheme //! [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1 -mod blinded_signing_key; mod signature; mod signing_key; mod verifying_key; -pub use self::{ - blinded_signing_key::BlindedSigningKey, signature::Signature, signing_key::SigningKey, - verifying_key::VerifyingKey, -}; +pub use self::{signature::Signature, signing_key::SigningKey, verifying_key::VerifyingKey}; use alloc::vec::Vec; use core::fmt::{self, Debug}; @@ -33,6 +29,14 @@ use crate::traits::PublicKeyParts; use crate::traits::SignatureScheme; use crate::{RsaPrivateKey, RsaPublicKey}; +/// DEPRECATED: type alias for legacy blinded signing key. Blinding is now applied automatically +/// when available. +#[deprecated( + since = "0.10.0", + note = "use `SigningKey` which is blinded automatically" +)] +pub type BlindedSigningKey = SigningKey; + #[cfg(feature = "encoding")] use { crate::encoding::ID_RSASSA_PSS, @@ -46,7 +50,8 @@ use { /// Digital signatures using PSS padding. pub struct Pss { - /// Create blinded signatures. + /// Enable RSA base blinding for the private operation (enabled by default). + /// This does not change the RSASSA-PSS signature format. pub blinded: bool, /// Digest type to use. @@ -81,20 +86,20 @@ where #[must_use] pub fn new_with_salt(len: usize) -> Self { Self { - blinded: false, + blinded: true, digest: D::new(), salt_len: Some(len), } } - /// New PSS padding for blinded signatures (RSA-BSSA) for the given digest. + /// New PSS padding with RSA base blinding for the given digest. /// Digest output size is used as a salt length. #[must_use] pub fn new_blinded() -> Self { Self::new_blinded_with_salt(::output_size()) } - /// New PSS padding for blinded signatures (RSA-BSSA) for the given digest + /// New PSS padding with RSA base blinding for the given digest /// with a salt value of the given length. #[must_use] pub fn new_blinded_with_salt(len: usize) -> Self { @@ -309,7 +314,7 @@ where #[cfg(all(test, feature = "encoding"))] mod test { - use crate::pss::{BlindedSigningKey, Pss, Signature, SigningKey, VerifyingKey}; + use crate::pss::{Pss, Signature, SigningKey, VerifyingKey}; use crate::{RsaPrivateKey, RsaPublicKey}; use crate::traits::PublicKeyParts; @@ -509,21 +514,6 @@ tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V .expect("failed to verify"); } - #[rstest] - #[case("test\n")] - fn test_sign_and_verify_roundtrip_blinded_signer(#[case] test: &str) { - let priv_key = get_private_key(); - - let mut rng = ChaCha8Rng::from_seed([42; 32]); - let signing_key = BlindedSigningKey::::new(priv_key); - let verifying_key = signing_key.verifying_key(); - - let sig = signing_key.sign_with_rng(&mut rng, test.as_bytes()); - verifying_key - .verify(test.as_bytes(), &sig) - .expect("failed to verify"); - } - #[rstest] #[case("test\n")] fn test_sign_and_verify_roundtrip_digest_signer(#[case] test: &str) { @@ -547,29 +537,6 @@ tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V .expect("failed to verify"); } - #[rstest] - #[case("test\n")] - fn test_sign_and_verify_roundtrip_blinded_digest_signer(#[case] test: &str) { - let priv_key = get_private_key(); - - let mut rng = ChaCha8Rng::from_seed([42; 32]); - let signing_key = BlindedSigningKey::::new(priv_key); - let verifying_key = signing_key.verifying_key(); - - let sig = signing_key - .sign_digest_with_rng(&mut rng, |digest: &mut Sha1| digest.update(test.as_bytes())); - - verifying_key - .verify_digest( - |digest: &mut Sha1| { - digest.update(test.as_bytes()); - Ok(()) - }, - &sig, - ) - .expect("failed to verify"); - } - #[rstest] #[case( "test\n", @@ -622,24 +589,6 @@ tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V .expect("failed to verify"); } - #[rstest] - #[case("test\n")] - fn test_sign_and_verify_pss_blinded_hazmat(#[case] test: &str) { - let priv_key = get_private_key(); - - let test = &Sha1::digest(test); - let mut rng = ChaCha8Rng::from_seed([42; 32]); - let signing_key = BlindedSigningKey::::new(priv_key); - let verifying_key = signing_key.verifying_key(); - - let sig = signing_key - .sign_prehash_with_rng(&mut rng, test) - .expect("failed to sign"); - verifying_key - .verify_prehash(test, &sig) - .expect("failed to verify"); - } - #[test] // Tests the corner case where the key is multiple of 8 + 1 bits long fn test_sign_and_verify_2049bit_key() { @@ -708,10 +657,6 @@ tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V // would wrap to 0 let bad_key = SigningKey::::new_with_salt_len(priv_key.clone(), 256); assert!(bad_key.signature_algorithm_identifier().is_err()); - - // blinded key uses the same path - let bad_blinded = BlindedSigningKey::::new_with_salt_len(priv_key, 256); - assert!(bad_blinded.signature_algorithm_identifier().is_err()); } /// `signature_algorithm_identifier()` must advertise the configured salt length @@ -726,29 +671,20 @@ tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V let priv_key = get_private_key(); let signing_key = SigningKey::::new_with_salt_len(priv_key.clone(), salt_len); - let blinded_signing_key = - BlindedSigningKey::::new_with_salt_len(priv_key.clone(), salt_len); - - let algo_ids = [ - signing_key - .signature_algorithm_identifier() - .expect("salt_len fits in a u8, so encoding must succeed"), - blinded_signing_key - .signature_algorithm_identifier() - .expect("salt_len fits in a u8, so encoding must succeed"), - ]; - for algo_id in &algo_ids { - let params_bytes = algo_id - .parameters - .as_ref() - .expect("RSA-PSS AlgorithmIdentifier carries parameters") - .to_der() - .expect("parameters are DER-encodable"); - let pss_params = - ::from_der(¶ms_bytes) - .expect("RSA-PSS parameters are DER-decodable"); - assert_eq!(pss_params.salt_len as usize, salt_len); - } + + let algo_id = signing_key + .signature_algorithm_identifier() + .expect("salt_len fits in a u8, so encoding must succeed"); + + let params_bytes = algo_id + .parameters + .as_ref() + .expect("RSA-PSS AlgorithmIdentifier carries parameters") + .to_der() + .expect("parameters are DER-encodable"); + let pss_params = ::from_der(¶ms_bytes) + .expect("RSA-PSS parameters are DER-decodable"); + assert_eq!(pss_params.salt_len as usize, salt_len); } #[rstest] @@ -757,16 +693,11 @@ tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V #[case(usize::MAX)] fn test_pss_algo_id_salt_len_out_of_range(#[case] salt_len: usize) { let priv_key = get_private_key(); - let signing_key = SigningKey::::new_with_salt_len(priv_key.clone(), salt_len); - let blinded_signing_key = BlindedSigningKey::::new_with_salt_len(priv_key, salt_len); signing_key .signature_algorithm_identifier() .expect_err("salt_len overflows the u8 in RsaPssParams, so encoding must fail"); - blinded_signing_key - .signature_algorithm_identifier() - .expect_err("salt_len overflows the u8 in RsaPssParams, so encoding must fail"); } /// The digest-size default salt length is unchanged by the `u8` validation. diff --git a/src/pss/blinded_signing_key.rs b/src/pss/blinded_signing_key.rs deleted file mode 100644 index 7dcf54e1..00000000 --- a/src/pss/blinded_signing_key.rs +++ /dev/null @@ -1,312 +0,0 @@ -use super::{Signature, VerifyingKey, sign_digest}; -use crate::{Result, RsaPrivateKey}; -use core::marker::PhantomData; -use digest::{Digest, FixedOutputReset, HashMarker, Update}; -use rand_core::{CryptoRng, TryCryptoRng}; -use signature::{ - Keypair, RandomizedDigestSigner, RandomizedMultipartSigner, RandomizedSigner, - hazmat::RandomizedPrehashSigner, -}; -use zeroize::ZeroizeOnDrop; - -#[cfg(feature = "encoding")] -use { - super::get_pss_signature_algo_id, - const_oid::AssociatedOid, - pkcs8::{EncodePrivateKey, SecretDocument}, - spki::{ - AlgorithmIdentifierOwned, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier, - DynSignatureAlgorithmIdentifier, der::AnyRef, - }, -}; -#[cfg(feature = "serde")] -use { - pkcs8::DecodePrivateKey, - serdect::serde::{Deserialize, Serialize, de, ser}, -}; - -/// Signing key for producing "blinded" RSASSA-PSS signatures as described in -/// [draft-irtf-cfrg-rsa-blind-signatures](https://datatracker.ietf.org/doc/draft-irtf-cfrg-rsa-blind-signatures/). -#[derive(Debug, Clone)] -pub struct BlindedSigningKey -where - D: Digest, -{ - inner: RsaPrivateKey, - salt_len: usize, - phantom: PhantomData, -} - -impl BlindedSigningKey -where - D: Digest, -{ - /// Create a new RSASSA-PSS signing key which produces "blinded" - /// signatures. - /// Digest output size is used as a salt length. - #[must_use] - pub fn new(key: RsaPrivateKey) -> Self { - Self::new_with_salt_len(key, ::output_size()) - } - - /// Create a new RSASSA-PSS signing key which produces "blinded" - /// signatures with a salt of the given length. - #[must_use] - pub fn new_with_salt_len(key: RsaPrivateKey, salt_len: usize) -> Self { - Self { - inner: key, - salt_len, - phantom: Default::default(), - } - } - - /// Create a new random RSASSA-PSS signing key which produces "blinded" - /// signatures. - /// Digest output size is used as a salt length. - #[allow(clippy::missing_errors_doc, reason = "TODO")] - pub fn random(rng: &mut R, bit_size: usize) -> Result { - Self::random_with_salt_len(rng, bit_size, ::output_size()) - } - - /// Create a new random RSASSA-PSS signing key which produces "blinded" - /// signatures with a salt of the given length. - #[allow(clippy::missing_errors_doc, reason = "TODO")] - pub fn random_with_salt_len( - rng: &mut R, - bit_size: usize, - salt_len: usize, - ) -> Result { - Ok(Self { - inner: RsaPrivateKey::new(rng, bit_size)?, - salt_len, - phantom: Default::default(), - }) - } - - /// Return specified salt length for this key - #[must_use] - pub fn salt_len(&self) -> usize { - self.salt_len - } -} - -// -// `*Signer` trait impls -// - -impl RandomizedSigner for BlindedSigningKey -where - D: Digest + FixedOutputReset, -{ - fn try_sign_with_rng( - &self, - rng: &mut R, - msg: &[u8], - ) -> signature::Result { - self.try_multipart_sign_with_rng(rng, &[msg]) - } -} - -impl RandomizedMultipartSigner for BlindedSigningKey -where - D: Digest + FixedOutputReset, -{ - fn try_multipart_sign_with_rng( - &self, - rng: &mut R, - msg: &[&[u8]], - ) -> signature::Result { - let mut digest = D::new(); - msg.iter() - .for_each(|slice| ::update(&mut digest, slice)); - sign_digest::<_, D>(rng, true, &self.inner, &digest.finalize(), self.salt_len)? - .as_slice() - .try_into() - } -} - -impl RandomizedDigestSigner for BlindedSigningKey -where - D: Default + FixedOutputReset + HashMarker + Update, -{ - fn try_sign_digest_with_rng< - R: TryCryptoRng + ?Sized, - F: Fn(&mut D) -> signature::Result<()>, - >( - &self, - rng: &mut R, - f: F, - ) -> signature::Result { - let mut digest = D::default(); - f(&mut digest)?; - sign_digest::<_, D>(rng, true, &self.inner, &digest.finalize(), self.salt_len)? - .as_slice() - .try_into() - } -} - -impl RandomizedPrehashSigner for BlindedSigningKey -where - D: Digest + FixedOutputReset, -{ - fn sign_prehash_with_rng( - &self, - rng: &mut R, - prehash: &[u8], - ) -> signature::Result { - sign_digest::<_, D>(rng, true, &self.inner, prehash, self.salt_len)? - .as_slice() - .try_into() - } -} - -// -// Other trait impls -// - -impl AsRef for BlindedSigningKey -where - D: Digest, -{ - fn as_ref(&self) -> &RsaPrivateKey { - &self.inner - } -} - -#[cfg(feature = "encoding")] -impl AssociatedAlgorithmIdentifier for BlindedSigningKey -where - D: Digest, -{ - type Params = AnyRef<'static>; - - const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID; -} - -#[cfg(feature = "encoding")] -impl DynSignatureAlgorithmIdentifier for BlindedSigningKey -where - D: Digest + AssociatedOid, -{ - fn signature_algorithm_identifier(&self) -> spki::Result { - get_pss_signature_algo_id::(self.salt_len) - } -} - -#[cfg(feature = "encoding")] -impl EncodePrivateKey for BlindedSigningKey -where - D: Digest, -{ - fn to_pkcs8_der(&self) -> pkcs8::Result { - self.inner.to_pkcs8_der() - } -} - -impl From for BlindedSigningKey -where - D: Digest, -{ - fn from(key: RsaPrivateKey) -> Self { - Self::new(key) - } -} - -impl From> for RsaPrivateKey -where - D: Digest, -{ - fn from(key: BlindedSigningKey) -> Self { - key.inner - } -} - -impl Keypair for BlindedSigningKey -where - D: Digest, -{ - type VerifyingKey = VerifyingKey; - fn verifying_key(&self) -> Self::VerifyingKey { - VerifyingKey { - inner: self.inner.to_public_key(), - salt_len: Some(self.salt_len), - phantom: Default::default(), - } - } -} - -#[cfg(feature = "encoding")] -impl TryFrom> for BlindedSigningKey -where - D: Digest + AssociatedOid, -{ - type Error = pkcs8::Error; - - fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result { - RsaPrivateKey::try_from(private_key_info).map(Self::new) - } -} - -impl ZeroizeOnDrop for BlindedSigningKey where D: Digest {} - -impl PartialEq for BlindedSigningKey -where - D: Digest, -{ - fn eq(&self, other: &Self) -> bool { - self.inner == other.inner && self.salt_len == other.salt_len - } -} - -#[cfg(feature = "serde")] -impl Serialize for BlindedSigningKey -where - D: Digest, -{ - fn serialize(&self, serializer: S) -> core::result::Result - where - S: serde::Serializer, - { - let der = self.to_pkcs8_der().map_err(ser::Error::custom)?; - serdect::slice::serialize_hex_lower_or_bin(&der.as_bytes(), serializer) - } -} - -#[cfg(feature = "serde")] -impl<'de, D> Deserialize<'de> for BlindedSigningKey -where - D: Digest + AssociatedOid, -{ - fn deserialize(deserializer: De) -> core::result::Result - where - De: serde::Deserializer<'de>, - { - let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?; - Self::from_pkcs8_der(&der_bytes).map_err(de::Error::custom) - } -} - -#[cfg(test)] -mod tests { - #[test] - #[cfg(all(feature = "hazmat", feature = "serde"))] - fn test_serde() { - use super::*; - use rand::rngs::ChaCha8Rng; - use rand_core::SeedableRng; - use serde_test::{Configure, Token, assert_tokens}; - use sha2::Sha256; - - let mut rng = ChaCha8Rng::from_seed([42; 32]); - let signing_key = BlindedSigningKey::::new( - RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key"), - ); - - let tokens = [Token::Str(concat!( - "3056020100300d06092a864886f70d010101050004423040020100020900ab240c", - "3361d02e370203010001020811e54a15259d22f9020500ceff5cf3020500d3a7aa", - "ad020500ccaddf17020500cb529d3d020500bb526d6f" - ))]; - assert_tokens(&signing_key.readable(), &tokens); - } -} diff --git a/src/pss/signing_key.rs b/src/pss/signing_key.rs index bf079208..0b66fafc 100644 --- a/src/pss/signing_key.rs +++ b/src/pss/signing_key.rs @@ -35,6 +35,8 @@ use { /// Signing key for producing RSASSA-PSS signatures as described in /// [RFC8017 § 8.1]. /// +/// Signing uses the supplied RNG for both the salt and RSA base blinding. +/// /// [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1 #[derive(Debug, Clone)] pub struct SigningKey @@ -113,7 +115,7 @@ where ) -> signature::Result { let mut digest = D::new(); f(&mut digest)?; - sign_digest::<_, D>(rng, false, &self.inner, &digest.finalize(), self.salt_len)? + sign_digest::<_, D>(rng, true, &self.inner, &digest.finalize(), self.salt_len)? .as_slice() .try_into() } @@ -160,7 +162,7 @@ where rng: &mut R, prehash: &[u8], ) -> signature::Result { - sign_digest::<_, D>(rng, false, &self.inner, prehash, self.salt_len)? + sign_digest::<_, D>(rng, true, &self.inner, prehash, self.salt_len)? .as_slice() .try_into() } diff --git a/tests/signing_blinding.rs b/tests/signing_blinding.rs new file mode 100644 index 00000000..ea376107 --- /dev/null +++ b/tests/signing_blinding.rs @@ -0,0 +1,164 @@ +//! Default signing must pass blinding randomness through every signing API. +#![cfg(feature = "encoding")] + +use pkcs8::DecodePrivateKey; +use rand_core::{TryCryptoRng, TryRng}; +use rsa::{ + Error, Pkcs1v15Sign, Pss, RsaPrivateKey, RsaPublicKey, pkcs1v15, pss, traits::SignatureScheme, +}; +use sha2::{Digest, Sha256}; +use signature::{ + DigestSigner, MultipartSigner, RandomizedDigestSigner, RandomizedMultipartSigner, + RandomizedSigner, SignatureEncoding, Signer, + hazmat::{PrehashSigner, RandomizedPrehashSigner}, +}; + +fn key() -> RsaPrivateKey { + RsaPrivateKey::from_pkcs8_der(include_bytes!("examples/pkcs8/rsa2048-priv.der")) + .expect("valid fixture") +} + +#[derive(Debug)] +struct Exhausted; +impl core::fmt::Display for Exhausted { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("test entropy budget exhausted") + } +} +impl core::error::Error for Exhausted {} + +// Supplies the PSS salt, then fails when base blinding requests more randomness. +struct SaltOnlyRng(usize); +impl TryRng for SaltOnlyRng { + type Error = Exhausted; + fn try_next_u32(&mut self) -> Result { + let mut bytes = [0; 4]; + self.try_fill_bytes(&mut bytes)?; + Ok(u32::from_le_bytes(bytes)) + } + fn try_next_u64(&mut self) -> Result { + let mut bytes = [0; 8]; + self.try_fill_bytes(&mut bytes)?; + Ok(u64::from_le_bytes(bytes)) + } + fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Self::Error> { + if bytes.len() > self.0 { + return Err(Exhausted); + } + self.0 -= bytes.len(); + bytes.fill(0); + Ok(()) + } +} +impl TryCryptoRng for SaltOnlyRng {} + +#[test] +fn pss_default_requests_blinding_randomness() { + let key = key(); + let digest = Sha256::digest(b"message"); + for salt_len in [0, 32] { + let padding = Pss::::new_with_salt(salt_len); + assert_eq!( + padding.sign(Some(&mut SaltOnlyRng(salt_len)), &key, &digest), + Err(Error::Rng) + ); + + // Control: the same salt, key and digest work when explicitly opting out of blinding. + let mut padding = Pss::::new_with_salt(salt_len); + padding.blinded = false; + let signature = padding + .sign(Some(&mut SaltOnlyRng(salt_len)), &key, &digest) + .expect("unblinded control"); + key.to_public_key() + .verify(Pss::::new_with_salt(salt_len), &digest, &signature) + .expect("valid control signature"); + + let signing = pss::SigningKey::::new_with_salt_len(key.clone(), salt_len); + assert!( + signing + .try_sign_with_rng(&mut SaltOnlyRng(salt_len), b"message") + .is_err() + ); + assert!( + signing + .try_multipart_sign_with_rng(&mut SaltOnlyRng(salt_len), &[b"mes", b"sage"]) + .is_err() + ); + assert!( + signing + .try_sign_digest_with_rng(&mut SaltOnlyRng(salt_len), |d| { + d.update(b"message"); + Ok(()) + }) + .is_err() + ); + assert!( + signing + .sign_prehash_with_rng(&mut SaltOnlyRng(salt_len), &digest) + .is_err() + ); + } +} + +#[test] +fn default_rng_reaches_signature_scheme() { + struct Probe; + impl SignatureScheme for Probe { + fn sign( + self, + rng: Option<&mut R>, + _: &RsaPrivateKey, + _: &[u8], + ) -> rsa::Result> { + assert_eq!(rng.is_some(), cfg!(feature = "getrandom")); + Ok(Vec::new()) + } + fn verify(self, _: &RsaPublicKey, _: &[u8], _: &[u8]) -> rsa::Result<()> { + unreachable!() + } + } + key().sign(Probe, b"").expect("default RNG dispatch"); +} + +#[test] +fn pkcs1v15_default_signatures_are_unchanged() { + let key = key(); + let digest = Sha256::digest(b"message"); + let expected = Pkcs1v15Sign::new::() + .sign::(None, &key, &digest) + .expect("unblinded reference"); + assert_eq!( + key.sign(Pkcs1v15Sign::new::(), &digest) + .expect("generic sign"), + expected + ); + let signing = pkcs1v15::SigningKey::::new(key); + assert_eq!( + signing.try_sign(b"message").expect("sign").to_vec(), + expected + ); + assert_eq!( + signing + .try_multipart_sign(&[b"mes", b"sage"]) + .expect("multipart sign") + .to_vec(), + expected + ); + assert_eq!( + signing + .sign_prehash(&digest) + .expect("prehash sign") + .to_vec(), + expected + ); + assert_eq!( + signing + .try_sign_digest(|d| { + d.update(b"message"); + Ok(()) + }) + .expect("digest sign") + .to_vec(), + expected + ); +}