blob: dd6ebcc1ac7692933f5e4a4e977f3ff9cffbe3dc [file] [log] [blame]
//! Support for the [Probabilistic Signature Scheme] (PSS) a.k.a. RSASSA-PSS.
//!
//! Designed by Mihir Bellare and Phillip Rogaway. Specified in [RFC8017 § 8.1].
//!
//! # Usage
//!
//! See [code example in the toplevel rustdoc](../index.html#pss-signatures).
//!
//! [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,
};
use alloc::{boxed::Box, vec::Vec};
use core::fmt::{self, Debug};
use const_oid::{AssociatedOid, ObjectIdentifier};
use digest::{Digest, DynDigest, FixedOutputReset};
use num_bigint::BigUint;
use pkcs1::RsaPssParams;
use pkcs8::spki::{der::Any, AlgorithmIdentifierOwned};
use rand_core::CryptoRngCore;
use crate::algorithms::pad::{uint_to_be_pad, uint_to_zeroizing_be_pad};
use crate::algorithms::pss::*;
use crate::algorithms::rsa::{rsa_decrypt_and_check, rsa_encrypt};
use crate::errors::{Error, Result};
use crate::traits::PublicKeyParts;
use crate::traits::SignatureScheme;
use crate::{RsaPrivateKey, RsaPublicKey};
/// Digital signatures using PSS padding.
pub struct Pss {
/// Create blinded signatures.
pub blinded: bool,
/// Digest type to use.
pub digest: Box<dyn DynDigest + Send + Sync>,
/// Salt length.
pub salt_len: usize,
}
impl Pss {
/// New PSS padding for the given digest.
/// Digest output size is used as a salt length.
pub fn new<T: 'static + Digest + DynDigest + Send + Sync>() -> Self {
Self::new_with_salt::<T>(<T as Digest>::output_size())
}
/// New PSS padding for the given digest with a salt value of the given length.
pub fn new_with_salt<T: 'static + Digest + DynDigest + Send + Sync>(len: usize) -> Self {
Self {
blinded: false,
digest: Box::new(T::new()),
salt_len: len,
}
}
/// New PSS padding for blinded signatures (RSA-BSSA) for the given digest.
/// Digest output size is used as a salt length.
pub fn new_blinded<T: 'static + Digest + DynDigest + Send + Sync>() -> Self {
Self::new_blinded_with_salt::<T>(<T as Digest>::output_size())
}
/// New PSS padding for blinded signatures (RSA-BSSA) for the given digest
/// with a salt value of the given length.
pub fn new_blinded_with_salt<T: 'static + Digest + DynDigest + Send + Sync>(
len: usize,
) -> Self {
Self {
blinded: true,
digest: Box::new(T::new()),
salt_len: len,
}
}
}
impl SignatureScheme for Pss {
fn sign<Rng: CryptoRngCore>(
mut self,
rng: Option<&mut Rng>,
priv_key: &RsaPrivateKey,
hashed: &[u8],
) -> Result<Vec<u8>> {
sign(
rng.ok_or(Error::InvalidPaddingScheme)?,
self.blinded,
priv_key,
hashed,
self.salt_len,
&mut *self.digest,
)
}
fn verify(mut self, pub_key: &RsaPublicKey, hashed: &[u8], sig: &[u8]) -> Result<()> {
verify(
pub_key,
hashed,
&BigUint::from_bytes_be(sig),
sig.len(),
&mut *self.digest,
self.salt_len,
)
}
}
impl Debug for Pss {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PSS")
.field("blinded", &self.blinded)
.field("digest", &"...")
.field("salt_len", &self.salt_len)
.finish()
}
}
pub(crate) fn verify(
pub_key: &RsaPublicKey,
hashed: &[u8],
sig: &BigUint,
sig_len: usize,
digest: &mut dyn DynDigest,
salt_len: usize,
) -> Result<()> {
if sig_len != pub_key.size() {
return Err(Error::Verification);
}
let mut em = uint_to_be_pad(rsa_encrypt(pub_key, sig)?, pub_key.size())?;
emsa_pss_verify(hashed, &mut em, salt_len, digest, pub_key.n().bits())
}
pub(crate) fn verify_digest<D>(
pub_key: &RsaPublicKey,
hashed: &[u8],
sig: &BigUint,
sig_len: usize,
salt_len: usize,
) -> Result<()>
where
D: Digest + FixedOutputReset,
{
if sig >= pub_key.n() || sig_len != pub_key.size() {
return Err(Error::Verification);
}
let mut em = uint_to_be_pad(rsa_encrypt(pub_key, sig)?, pub_key.size())?;
emsa_pss_verify_digest::<D>(hashed, &mut em, salt_len, pub_key.n().bits())
}
/// SignPSS calculates the signature of hashed using RSASSA-PSS.
///
/// Note that hashed must be the result of hashing the input message using the
/// given hash function. The opts argument may be nil, in which case sensible
/// defaults are used.
pub(crate) fn sign<T: CryptoRngCore>(
rng: &mut T,
blind: bool,
priv_key: &RsaPrivateKey,
hashed: &[u8],
salt_len: usize,
digest: &mut dyn DynDigest,
) -> Result<Vec<u8>> {
let mut salt = vec![0; salt_len];
rng.fill_bytes(&mut salt[..]);
sign_pss_with_salt(blind.then_some(rng), priv_key, hashed, &salt, digest)
}
pub(crate) fn sign_digest<T: CryptoRngCore + ?Sized, D: Digest + FixedOutputReset>(
rng: &mut T,
blind: bool,
priv_key: &RsaPrivateKey,
hashed: &[u8],
salt_len: usize,
) -> Result<Vec<u8>> {
let mut salt = vec![0; salt_len];
rng.fill_bytes(&mut salt[..]);
sign_pss_with_salt_digest::<_, D>(blind.then_some(rng), priv_key, hashed, &salt)
}
/// signPSSWithSalt calculates the signature of hashed using PSS with specified salt.
///
/// Note that hashed must be the result of hashing the input message using the
/// given hash function. salt is a random sequence of bytes whose length will be
/// later used to verify the signature.
fn sign_pss_with_salt<T: CryptoRngCore>(
blind_rng: Option<&mut T>,
priv_key: &RsaPrivateKey,
hashed: &[u8],
salt: &[u8],
digest: &mut dyn DynDigest,
) -> Result<Vec<u8>> {
let em_bits = priv_key.n().bits() - 1;
let em = emsa_pss_encode(hashed, em_bits, salt, digest)?;
uint_to_zeroizing_be_pad(
rsa_decrypt_and_check(priv_key, blind_rng, &BigUint::from_bytes_be(&em))?,
priv_key.size(),
)
}
fn sign_pss_with_salt_digest<T: CryptoRngCore + ?Sized, D: Digest + FixedOutputReset>(
blind_rng: Option<&mut T>,
priv_key: &RsaPrivateKey,
hashed: &[u8],
salt: &[u8],
) -> Result<Vec<u8>> {
let em_bits = priv_key.n().bits() - 1;
let em = emsa_pss_encode_digest::<D>(hashed, em_bits, salt)?;
uint_to_zeroizing_be_pad(
rsa_decrypt_and_check(priv_key, blind_rng, &BigUint::from_bytes_be(&em))?,
priv_key.size(),
)
}
fn get_pss_signature_algo_id<D>(salt_len: u8) -> pkcs8::spki::Result<AlgorithmIdentifierOwned>
where
D: Digest + AssociatedOid,
{
const ID_RSASSA_PSS: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.10");
let pss_params = RsaPssParams::new::<D>(salt_len);
Ok(AlgorithmIdentifierOwned {
oid: ID_RSASSA_PSS,
parameters: Some(Any::encode_from(&pss_params)?),
})
}
#[cfg(test)]
mod test {
use crate::pss::{BlindedSigningKey, Pss, Signature, SigningKey, VerifyingKey};
use crate::{RsaPrivateKey, RsaPublicKey};
use hex_literal::hex;
use num_bigint::BigUint;
use num_traits::{FromPrimitive, Num};
use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
use sha1::{Digest, Sha1};
use signature::hazmat::{PrehashVerifier, RandomizedPrehashSigner};
use signature::{DigestVerifier, Keypair, RandomizedDigestSigner, RandomizedSigner, Verifier};
fn get_private_key() -> RsaPrivateKey {
// In order to generate new test vectors you'll need the PEM form of this key:
// -----BEGIN RSA PRIVATE KEY-----
// MIIBOgIBAAJBALKZD0nEffqM1ACuak0bijtqE2QrI/KLADv7l3kK3ppMyCuLKoF0
// fd7Ai2KW5ToIwzFofvJcS/STa6HA5gQenRUCAwEAAQJBAIq9amn00aS0h/CrjXqu
// /ThglAXJmZhOMPVn4eiu7/ROixi9sex436MaVeMqSNf7Ex9a8fRNfWss7Sqd9eWu
// RTUCIQDasvGASLqmjeffBNLTXV2A5g4t+kLVCpsEIZAycV5GswIhANEPLmax0ME/
// EO+ZJ79TJKN5yiGBRsv5yvx5UiHxajEXAiAhAol5N4EUyq6I9w1rYdhPMGpLfk7A
// IU2snfRJ6Nq2CQIgFrPsWRCkV+gOYcajD17rEqmuLrdIRexpg8N1DOSXoJ8CIGlS
// tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V
// -----END RSA PRIVATE KEY-----
RsaPrivateKey::from_components(
BigUint::from_str_radix("9353930466774385905609975137998169297361893554149986716853295022578535724979677252958524466350471210367835187480748268864277464700638583474144061408845077", 10).unwrap(),
BigUint::from_u64(65537).unwrap(),
BigUint::from_str_radix("7266398431328116344057699379749222532279343923819063639497049039389899328538543087657733766554155839834519529439851673014800261285757759040931985506583861", 10).unwrap(),
vec![
BigUint::from_str_radix("98920366548084643601728869055592650835572950932266967461790948584315647051443",10).unwrap(),
BigUint::from_str_radix("94560208308847015747498523884063394671606671904944666360068158221458669711639", 10).unwrap()
],
).unwrap()
}
#[test]
fn test_verify_pss() {
let priv_key = get_private_key();
let tests = [
(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f"
),
true,
),
(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962e"
),
false,
),
];
let pub_key: RsaPublicKey = priv_key.into();
for (text, sig, expected) in &tests {
let digest = Sha1::digest(text.as_bytes()).to_vec();
let result = pub_key.verify(Pss::new::<Sha1>(), &digest, sig);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
}
#[test]
fn test_verify_pss_signer() {
let priv_key = get_private_key();
let tests = [
(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f"
),
true,
),
(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962e"
),
false,
),
];
let pub_key: RsaPublicKey = priv_key.into();
let verifying_key: VerifyingKey<Sha1> = VerifyingKey::new(pub_key);
for (text, sig, expected) in &tests {
let result = verifying_key.verify(
text.as_bytes(),
&Signature::try_from(sig.as_slice()).unwrap(),
);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
}
#[test]
fn test_verify_pss_digest_signer() {
let priv_key = get_private_key();
let tests = [
(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f"
),
true,
),
(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962e"
),
false,
),
];
let pub_key: RsaPublicKey = priv_key.into();
let verifying_key = VerifyingKey::new(pub_key);
for (text, sig, expected) in &tests {
let mut digest = Sha1::new();
digest.update(text.as_bytes());
let result =
verifying_key.verify_digest(digest, &Signature::try_from(sig.as_slice()).unwrap());
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
}
#[test]
fn test_sign_and_verify_roundtrip() {
let priv_key = get_private_key();
let tests = ["test\n"];
let rng = ChaCha8Rng::from_seed([42; 32]);
for test in &tests {
let digest = Sha1::digest(test.as_bytes()).to_vec();
let sig = priv_key
.sign_with_rng(&mut rng.clone(), Pss::new::<Sha1>(), &digest)
.expect("failed to sign");
priv_key
.to_public_key()
.verify(Pss::new::<Sha1>(), &digest, &sig)
.expect("failed to verify");
}
}
#[test]
fn test_sign_blinded_and_verify_roundtrip() {
let priv_key = get_private_key();
let tests = ["test\n"];
let rng = ChaCha8Rng::from_seed([42; 32]);
for test in &tests {
let digest = Sha1::digest(test.as_bytes()).to_vec();
let sig = priv_key
.sign_with_rng(&mut rng.clone(), Pss::new_blinded::<Sha1>(), &digest)
.expect("failed to sign");
priv_key
.to_public_key()
.verify(Pss::new::<Sha1>(), &digest, &sig)
.expect("failed to verify");
}
}
#[test]
fn test_sign_and_verify_roundtrip_signer() {
let priv_key = get_private_key();
let tests = ["test\n"];
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = SigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
for test in &tests {
let sig = signing_key.sign_with_rng(&mut rng, test.as_bytes());
verifying_key
.verify(test.as_bytes(), &sig)
.expect("failed to verify");
}
}
#[test]
fn test_sign_and_verify_roundtrip_blinded_signer() {
let priv_key = get_private_key();
let tests = ["test\n"];
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = BlindedSigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
for test in &tests {
let sig = signing_key.sign_with_rng(&mut rng, test.as_bytes());
verifying_key
.verify(test.as_bytes(), &sig)
.expect("failed to verify");
}
}
#[test]
fn test_sign_and_verify_roundtrip_digest_signer() {
let priv_key = get_private_key();
let tests = ["test\n"];
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = SigningKey::new(priv_key);
let verifying_key = signing_key.verifying_key();
for test in &tests {
let mut digest = Sha1::new();
digest.update(test.as_bytes());
let sig = signing_key.sign_digest_with_rng(&mut rng, digest);
let mut digest = Sha1::new();
digest.update(test.as_bytes());
verifying_key
.verify_digest(digest, &sig)
.expect("failed to verify");
}
}
#[test]
fn test_sign_and_verify_roundtrip_blinded_digest_signer() {
let priv_key = get_private_key();
let tests = ["test\n"];
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = BlindedSigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
for test in &tests {
let mut digest = Sha1::new();
digest.update(test.as_bytes());
let sig = signing_key.sign_digest_with_rng(&mut rng, digest);
let mut digest = Sha1::new();
digest.update(test.as_bytes());
verifying_key
.verify_digest(digest, &sig)
.expect("failed to verify");
}
}
#[test]
fn test_verify_pss_hazmat() {
let priv_key = get_private_key();
let tests = [
(
Sha1::digest("test\n"),
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f"
),
true,
),
(
Sha1::digest("test\n"),
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962e"
),
false,
),
];
let pub_key: RsaPublicKey = priv_key.into();
let verifying_key = VerifyingKey::<Sha1>::new(pub_key);
for (text, sig, expected) in &tests {
let result = verifying_key
.verify_prehash(text.as_ref(), &Signature::try_from(sig.as_slice()).unwrap());
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
}
#[test]
fn test_sign_and_verify_pss_hazmat() {
let priv_key = get_private_key();
let tests = [Sha1::digest("test\n")];
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = SigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
for test in &tests {
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]
fn test_sign_and_verify_pss_blinded_hazmat() {
let priv_key = get_private_key();
let tests = [Sha1::digest("test\n")];
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = BlindedSigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
for test in &tests {
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() {
let plaintext = "Hello\n";
let rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = RsaPrivateKey::new(&mut rng.clone(), 2049).unwrap();
let digest = Sha1::digest(plaintext.as_bytes()).to_vec();
let sig = priv_key
.sign_with_rng(&mut rng.clone(), Pss::new::<Sha1>(), &digest)
.expect("failed to sign");
priv_key
.to_public_key()
.verify(Pss::new::<Sha1>(), &digest, &sig)
.expect("failed to verify");
}
}