Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<S: SignatureScheme>(&self, padding: S, digest_in: &[u8]) -> Result<Vec<u8>> {
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
Expand All @@ -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<R: CryptoRng + ?Sized, S: SignatureScheme>(
&self,
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ pub mod pkcs1v15;
pub mod pss;
pub mod traits;

#[cfg(not(feature = "getrandom"))]
mod dummy_rng;
mod encoding;
mod key;
Expand Down
21 changes: 21 additions & 0 deletions src/pkcs1v15.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,27 @@ fn decrypt<R: TryCryptoRng + ?Sized>(
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<Vec<u8>> {
#[cfg(feature = "getrandom")]
{
sign(
Some(&mut crypto_common::getrandom::SysRng),
priv_key,
prefix,
hashed,
)
}
#[cfg(not(feature = "getrandom"))]
{
sign::<crate::dummy_rng::DummyRng>(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
Expand Down
14 changes: 9 additions & 5 deletions src/pkcs1v15/signing_key.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<D>
Expand Down Expand Up @@ -107,7 +111,7 @@ where
) -> signature::Result<Signature> {
let mut digest = D::default();
f(&mut digest)?;
sign::<DummyRng>(None, &self.inner, &self.prefix, &digest.finalize_fixed())?
sign_with_default_rng(&self.inner, &self.prefix, &digest.finalize_fixed())?
.as_slice()
.try_into()
}
Expand All @@ -118,7 +122,7 @@ where
D: Digest,
{
fn sign_prehash(&self, prehash: &[u8]) -> signature::Result<Signature> {
sign::<DummyRng>(None, &self.inner, &self.prefix, prehash)?
sign_with_default_rng(&self.inner, &self.prefix, prehash)?
.as_slice()
.try_into()
}
Expand Down Expand Up @@ -195,7 +199,7 @@ where
fn try_multipart_sign(&self, msg: &[&[u8]]) -> signature::Result<Signature> {
let mut digest = D::new();
msg.iter().for_each(|slice| digest.update(slice));
sign::<DummyRng>(None, &self.inner, &self.prefix, &digest.finalize())?
sign_with_default_rng(&self.inner, &self.prefix, &digest.finalize())?
.as_slice()
.try_into()
}
Expand Down
127 changes: 29 additions & 98 deletions src/pss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<D> = SigningKey<D>;

#[cfg(feature = "encoding")]
use {
crate::encoding::ID_RSASSA_PSS,
Expand All @@ -46,7 +50,8 @@ use {

/// Digital signatures using PSS padding.
pub struct Pss<D> {
/// 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.
Expand Down Expand Up @@ -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(<D as Digest>::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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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::<Sha1>::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) {
Expand All @@ -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::<Sha1>::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",
Expand Down Expand Up @@ -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::<Sha1>::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() {
Expand Down Expand Up @@ -708,10 +657,6 @@ tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V
// would wrap to 0
let bad_key = SigningKey::<Sha1>::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::<Sha1>::new_with_salt_len(priv_key, 256);
assert!(bad_blinded.signature_algorithm_identifier().is_err());
}

/// `signature_algorithm_identifier()` must advertise the configured salt length
Expand All @@ -726,29 +671,20 @@ tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V
let priv_key = get_private_key();

let signing_key = SigningKey::<Sha1>::new_with_salt_len(priv_key.clone(), salt_len);
let blinded_signing_key =
BlindedSigningKey::<Sha1>::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 =
<pkcs1::RsaPssParamsOwned as spki::der::Decode>::from_der(&params_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 = <pkcs1::RsaPssParamsOwned as spki::der::Decode>::from_der(&params_bytes)
.expect("RSA-PSS parameters are DER-decodable");
assert_eq!(pss_params.salt_len as usize, salt_len);
}

#[rstest]
Expand All @@ -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::<Sha1>::new_with_salt_len(priv_key.clone(), salt_len);
let blinded_signing_key = BlindedSigningKey::<Sha1>::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.
Expand Down
Loading
Loading