diff --git a/Cargo.toml b/Cargo.toml index cdf33374..a9397ab6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,12 +24,17 @@ digest = { version = "0.10.0", default-features = false, features = ["alloc"] } pkcs1 = { version = "0.4", default-features = false, features = ["pkcs8", "alloc"] } pkcs8 = { version = "0.9", default-features = false, features = ["alloc"] } #To keep the rand_core versions properly pinnen, specify exact version -signature = { version = ">=1.5, <1.7", default-features = false , features = ["rand-preview"] } +signature = { version = ">=1.5, <1.7", default-features = false , features = ["digest-preview", "rand-preview"] } zeroize = { version = "1", features = ["alloc"] } # Temporary workaround until https://github.com/dignifiedquire/num-bigint/pull/42 lands smallvec = { version = "1.6.1", default-features = false } +# Temporary until the link from Digest to OID is moved to corresponding crates +sha1 = { version = "0.10.1", default-features = false, optional = true } +sha2 = { version = "0.10.2", default-features = false, optional = true } +sha3 = { version = "0.10.1", default-features = false, optional = true } + [dependencies.serde_crate] package = "serde" optional = true @@ -53,7 +58,7 @@ sha3 = { version = "0.10.1", default-features = false } name = "key" [features] -default = ["std", "pem"] +default = ["std", "pem", "sha2"] nightly = ["num-bigint/nightly"] serde = ["num-bigint/serde", "serde_crate"] expose-internals = [] @@ -63,7 +68,7 @@ pkcs5 = ["pkcs8/encryption"] getrandom = ["rand_core/getrandom"] [package.metadata.docs.rs] -features = ["std", "pem", "serde", "expose-internals"] +features = ["std", "pem", "serde", "expose-internals", "sha1", "sha2", "sha3"] rustdoc-args = ["--cfg", "docsrs"] [profile.dev] diff --git a/src/algorithms.rs b/src/algorithms.rs index dd6fa086..5e5738d3 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -1,5 +1,5 @@ use alloc::vec; -use digest::DynDigest; +use digest::{Digest, DynDigest, FixedOutputReset}; use num_bigint::traits::ModInverse; use num_bigint::{BigUint, RandPrime}; #[allow(unused_imports)] @@ -165,6 +165,37 @@ pub fn mgf1_xor(out: &mut [u8], digest: &mut dyn DynDigest, seed: &[u8]) { } } +/// Mask generation function. +/// +/// Panics if out is larger than 2**32. This is in accordance with RFC 8017 - PKCS #1 B.2.1 +pub fn mgf1_xor_digest(out: &mut [u8], digest: &mut D, seed: &[u8]) +where + D: Digest + FixedOutputReset, +{ + let mut counter = [0u8; 4]; + let mut i = 0; + + const MAX_LEN: u64 = core::u32::MAX as u64 + 1; + assert!(out.len() as u64 <= MAX_LEN); + + while i < out.len() { + Digest::update(digest, seed); + Digest::update(digest, counter); + + let digest_output = digest.finalize_reset(); + let mut j = 0; + loop { + if j >= digest_output.len() || i >= out.len() { + break; + } + + out[i] ^= digest_output[j]; + j += 1; + i += 1; + } + inc_counter(&mut counter); + } +} fn inc_counter(counter: &mut [u8; 4]) { for i in (0..4).rev() { counter[i] = counter[i].wrapping_add(1); diff --git a/src/hash.rs b/src/hash.rs index aa753187..ffa8433f 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -83,3 +83,55 @@ impl Hash { } } } + +/* FIXME: This trait should be refactored into per-digest implementations returning OID */ +pub trait AssociatedHash { + const HASH: Hash; +} + +#[cfg(feature = "sha1")] +impl AssociatedHash for sha1::Sha1 { + const HASH: Hash = Hash::SHA1; +} + +#[cfg(feature = "sha2")] +impl AssociatedHash for sha2::Sha224 { + const HASH: Hash = Hash::SHA2_224; +} + +#[cfg(feature = "sha2")] +impl AssociatedHash for sha2::Sha256 { + const HASH: Hash = Hash::SHA2_256; +} + +#[cfg(feature = "sha2")] +impl AssociatedHash for sha2::Sha384 { + const HASH: Hash = Hash::SHA2_384; +} + +#[cfg(feature = "sha2")] +impl AssociatedHash for sha2::Sha512 { + const HASH: Hash = Hash::SHA2_512; +} + +/* +#[cfg(feature = "sha3")] +impl AssociatedHash for sha3::Sha3_224 { + const HASH: Hash = Hash::SHA3_224; +} +*/ + +#[cfg(feature = "sha3")] +impl AssociatedHash for sha3::Sha3_256 { + const HASH: Hash = Hash::SHA3_256; +} + +#[cfg(feature = "sha3")] +impl AssociatedHash for sha3::Sha3_384 { + const HASH: Hash = Hash::SHA3_384; +} + +#[cfg(feature = "sha3")] +impl AssociatedHash for sha3::Sha3_512 { + const HASH: Hash = Hash::SHA3_512; +} diff --git a/src/lib.rs b/src/lib.rs index 33256f98..029849a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,29 +47,32 @@ //! assert_eq!(&data[..], &dec_data[..]); //! ``` //! -//! Using PKCS1v15 signatures -//! ``` -//! use rsa::{Hash, RsaPrivateKey}; -//! use rsa::pkcs1v15::{SigningKey, VerifyingKey}; -//! use sha2::{Digest, Sha256}; -//! use signature::{RandomizedSigner, Signature, Verifier}; -//! -//! let mut rng = rand::thread_rng(); -//! -//! let bits = 2048; -//! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); -//! let signing_key = SigningKey::new_with_hash(private_key, Hash::SHA2_256); -//! let verifying_key: VerifyingKey = (&signing_key).into(); -//! -//! // Sign -//! let data = b"hello world"; -//! let digest = Sha256::digest(data).to_vec(); -//! let signature = signing_key.sign_with_rng(&mut rng, &digest); -//! assert_ne!(signature.as_bytes(), data); -//! -//! // Verify -//! verifying_key.verify(&digest, &signature).expect("failed to verify"); -//! ``` +#![cfg_attr( + feature = "sha2", + doc = r#" +Using PKCS1v15 signatures +``` +use rsa::{Hash, RsaPrivateKey}; +use rsa::pkcs1v15::{SigningKey, VerifyingKey}; +use sha2::{Digest, Sha256}; +use signature::{RandomizedSigner, Signature, Verifier}; + +let mut rng = rand::thread_rng(); + +let bits = 2048; +let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); +let signing_key = SigningKey::::new_with_prefix(private_key); +let verifying_key: VerifyingKey<_> = (&signing_key).into(); + +// Sign +let data = b"hello world"; +let signature = signing_key.sign_with_rng(&mut rng, data); +assert_ne!(signature.as_bytes(), data); + +// Verify +verifying_key.verify(data, &signature).expect("failed to verify"); +```"# +)] //! //! Using PSS signatures //! ``` @@ -82,17 +85,16 @@ //! //! let bits = 2048; //! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); -//! let signing_key = BlindedSigningKey::new(private_key, Box::new(Sha256::new())); -//! let verifying_key: VerifyingKey = (&signing_key).into(); +//! let signing_key = BlindedSigningKey::::new(private_key); +//! let verifying_key: VerifyingKey<_> = (&signing_key).into(); //! //! // Sign //! let data = b"hello world"; -//! let digest = Sha256::digest(data).to_vec(); -//! let signature = signing_key.sign_with_rng(&mut rng, &digest); +//! let signature = signing_key.sign_with_rng(&mut rng, data); //! assert_ne!(signature.as_bytes(), data); //! //! // Verify -//! verifying_key.verify(&digest, &signature).expect("failed to verify"); +//! verifying_key.verify(data, &signature).expect("failed to verify"); //! ``` //! //! ## PKCS#1 RSA Key Encoding diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index 9e0ee594..a5c9775c 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -1,14 +1,19 @@ use alloc::vec; use alloc::vec::Vec; use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex}; +use core::marker::PhantomData; +use digest::Digest; use rand_core::{CryptoRng, RngCore}; -use signature::{RandomizedSigner, Signature as SignSignature, Signer, Verifier}; +use signature::{ + DigestSigner, DigestVerifier, RandomizedDigestSigner, RandomizedSigner, + Signature as SignSignature, Signer, Verifier, +}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use zeroize::Zeroizing; use crate::dummy_rng::DummyRng; use crate::errors::{Error, Result}; -use crate::hash::Hash; +use crate::hash::{AssociatedHash, Hash}; use crate::key::{self, PrivateKey, PublicKey}; use crate::{RsaPrivateKey, RsaPublicKey}; @@ -285,12 +290,19 @@ fn non_zero_random_bytes(rng: &mut R, data: &mut [u8]) { } } -pub struct SigningKey { +pub struct SigningKey +where + D: Digest, +{ inner: RsaPrivateKey, hash: Option, + phantom: PhantomData, } -impl SigningKey { +impl SigningKey +where + D: Digest, +{ pub(crate) fn key(&self) -> &RsaPrivateKey { &self.inner } @@ -303,79 +315,174 @@ impl SigningKey { Self { inner: key, hash: None, + phantom: Default::default(), } } +} - pub fn new_with_hash(key: RsaPrivateKey, hash: Hash) -> Self { +impl SigningKey +where + D: Digest + AssociatedHash, +{ + pub fn new_with_prefix(key: RsaPrivateKey) -> Self { Self { inner: key, - hash: Some(hash), + hash: Some(D::HASH), + phantom: Default::default(), } } } -impl Signer for SigningKey { - fn try_sign(&self, digest: &[u8]) -> signature::Result { - sign::(None, &self.inner, self.hash.as_ref(), digest) +impl Signer for SigningKey +where + D: Digest, +{ + fn try_sign(&self, msg: &[u8]) -> signature::Result { + sign::(None, &self.inner, self.hash.as_ref(), &D::digest(msg)) .map(|v| v.into()) .map_err(|e| e.into()) } } -impl RandomizedSigner for SigningKey { +impl RandomizedSigner for SigningKey +where + D: Digest, +{ fn try_sign_with_rng( &self, mut rng: impl CryptoRng + RngCore, - digest: &[u8], + msg: &[u8], ) -> signature::Result { - sign(Some(&mut rng), &self.inner, self.hash.as_ref(), digest) + sign( + Some(&mut rng), + &self.inner, + self.hash.as_ref(), + &D::digest(msg), + ) + .map(|v| v.into()) + .map_err(|e| e.into()) + } +} + +impl DigestSigner for SigningKey +where + D: Digest, +{ + fn try_sign_digest(&self, digest: D) -> signature::Result { + sign::(None, &self.inner, self.hash.as_ref(), &digest.finalize()) .map(|v| v.into()) .map_err(|e| e.into()) } } -pub struct VerifyingKey { +impl RandomizedDigestSigner for SigningKey +where + D: Digest, +{ + fn try_sign_digest_with_rng( + &self, + mut rng: impl CryptoRng + RngCore, + digest: D, + ) -> signature::Result { + sign( + Some(&mut rng), + &self.inner, + self.hash.as_ref(), + &digest.finalize(), + ) + .map(|v| v.into()) + .map_err(|e| e.into()) + } +} + +pub struct VerifyingKey +where + D: Digest, +{ inner: RsaPublicKey, hash: Option, + phantom: PhantomData, } -impl VerifyingKey { +impl VerifyingKey +where + D: Digest, +{ pub fn new(key: RsaPublicKey) -> Self { Self { inner: key, hash: None, + phantom: Default::default(), } } +} - pub fn new_with_hash(key: RsaPublicKey, hash: Hash) -> Self { +impl VerifyingKey +where + D: Digest + AssociatedHash, +{ + pub fn new_with_prefix(key: RsaPublicKey) -> Self { Self { inner: key, - hash: Some(hash), + hash: Some(D::HASH), + phantom: Default::default(), } } } -impl From for VerifyingKey { - fn from(key: SigningKey) -> Self { +impl From> for VerifyingKey +where + D: Digest, +{ + fn from(key: SigningKey) -> Self { Self { inner: key.key().into(), hash: key.hash(), + phantom: Default::default(), } } } -impl From<&SigningKey> for VerifyingKey { - fn from(key: &SigningKey) -> Self { +impl From<&SigningKey> for VerifyingKey +where + D: Digest, +{ + fn from(key: &SigningKey) -> Self { Self { inner: key.key().into(), hash: key.hash(), + phantom: Default::default(), } } } -impl Verifier for VerifyingKey { +impl Verifier for VerifyingKey +where + D: Digest, +{ fn verify(&self, msg: &[u8], signature: &Signature) -> signature::Result<()> { - verify(&self.inner, self.hash.as_ref(), msg, signature.as_ref()).map_err(|e| e.into()) + verify( + &self.inner, + self.hash.as_ref(), + &D::digest(msg), + signature.as_ref(), + ) + .map_err(|e| e.into()) + } +} + +impl DigestVerifier for VerifyingKey +where + D: Digest, +{ + fn verify_digest(&self, digest: D, signature: &Signature) -> signature::Result<()> { + verify( + &self.inner, + self.hash.as_ref(), + &digest.finalize(), + signature.as_ref(), + ) + .map_err(|e| e.into()) } } @@ -389,6 +496,8 @@ mod tests { use num_traits::Num; use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; use sha1::{Digest, Sha1}; + #[cfg(feature = "sha2")] + use sha2::Sha256; use signature::{RandomizedSigner, Signature, Signer, Verifier}; use crate::{Hash, PaddingScheme, PublicKey, PublicKeyParts, RsaPrivateKey, RsaPublicKey}; @@ -514,6 +623,7 @@ mod tests { } } + #[cfg(feature = "sha2")] #[test] fn test_sign_pkcs1v15_signer() { let priv_key = get_private_key(); @@ -521,22 +631,52 @@ mod tests { let tests = [( "Test.\n", hex!( - "a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33" - "6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae" + "2ffae3f3e130287b3a1dcb320e46f52e8f3f7969b646932273a7e3a6f2a182ea" + "02d42875a7ffa4a148aa311f9e4b562e4e13a2223fb15f4e5bf5f2b206d9451b" ), )]; - let signing_key = SigningKey::new_with_hash(priv_key, Hash::SHA1); + let signing_key = SigningKey::::new_with_prefix(priv_key); for (text, expected) in &tests { - let digest = Sha1::digest(text.as_bytes()).to_vec(); + let out = signing_key.sign(text.as_bytes()); + assert_ne!(out.as_ref(), text.as_bytes()); + assert_ne!(out.as_ref(), &Sha1::digest(text.as_bytes()).to_vec()); + assert_eq!(out.as_ref(), expected); - let out = signing_key.sign(&digest); - assert_ne!(out.as_ref(), digest); + let mut rng = ChaCha8Rng::from_seed([42; 32]); + let out2 = signing_key.sign_with_rng(&mut rng, text.as_bytes()); + assert_eq!(out2.as_ref(), expected); + } + } + + #[cfg(feature = "sha2")] + #[test] + fn test_sign_pkcs1v15_digest_signer() { + let priv_key = get_private_key(); + + let tests = [( + "Test.\n", + hex!( + "2ffae3f3e130287b3a1dcb320e46f52e8f3f7969b646932273a7e3a6f2a182ea" + "02d42875a7ffa4a148aa311f9e4b562e4e13a2223fb15f4e5bf5f2b206d9451b" + ), + )]; + + let signing_key = SigningKey::new_with_prefix(priv_key); + + for (text, expected) in &tests { + let mut digest = Sha256::new(); + digest.update(text.as_bytes()); + let out = signing_key.sign_digest(digest); + assert_ne!(out.as_ref(), text.as_bytes()); + assert_ne!(out.as_ref(), &Sha1::digest(text.as_bytes()).to_vec()); assert_eq!(out.as_ref(), expected); let mut rng = ChaCha8Rng::from_seed([42; 32]); - let out2 = signing_key.sign_with_rng(&mut rng, &digest); + let mut digest = Sha256::new(); + digest.update(text.as_bytes()); + let out2 = signing_key.sign_digest_with_rng(&mut rng, digest); assert_eq!(out2.as_ref(), expected); } } @@ -583,6 +723,7 @@ mod tests { } } + #[cfg(feature = "sha2")] #[test] fn test_verify_pkcs1v15_signer() { let priv_key = get_private_key(); @@ -591,27 +732,26 @@ mod tests { ( "Test.\n", hex!( - "a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33" - "6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae" + "2ffae3f3e130287b3a1dcb320e46f52e8f3f7969b646932273a7e3a6f2a182ea" + "02d42875a7ffa4a148aa311f9e4b562e4e13a2223fb15f4e5bf5f2b206d9451b" ), true, ), ( "Test.\n", hex!( - "a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33" - "6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362af" + "2ffae3f3e130287b3a1dcb320e46f52e8f3f7969b646932273a7e3a6f2a182ea" + "02d42875a7ffa4a148aa311f9e4b562e4e13a2223fb15f4e5bf5f2b206d9451c" ), false, ), ]; let pub_key: RsaPublicKey = priv_key.into(); - let verifying_key = VerifyingKey::new_with_hash(pub_key, Hash::SHA1); + let verifying_key = VerifyingKey::::new_with_prefix(pub_key); for (text, sig, expected) in &tests { - let digest = Sha1::digest(text.as_bytes()).to_vec(); - - let result = verifying_key.verify(&digest, &Signature::from_bytes(sig).unwrap()); + let result = + verifying_key.verify(text.as_bytes(), &Signature::from_bytes(sig).unwrap()); match expected { true => result.expect("failed to verify"), false => { @@ -622,6 +762,45 @@ mod tests { } } + #[cfg(feature = "sha2")] + #[test] + fn test_verify_pkcs1v15_digest_signer() { + let priv_key = get_private_key(); + + let tests = [ + ( + "Test.\n", + hex!( + "2ffae3f3e130287b3a1dcb320e46f52e8f3f7969b646932273a7e3a6f2a182ea" + "02d42875a7ffa4a148aa311f9e4b562e4e13a2223fb15f4e5bf5f2b206d9451b" + ), + true, + ), + ( + "Test.\n", + hex!( + "2ffae3f3e130287b3a1dcb320e46f52e8f3f7969b646932273a7e3a6f2a182ea" + "02d42875a7ffa4a148aa311f9e4b562e4e13a2223fb15f4e5bf5f2b206d9451c" + ), + false, + ), + ]; + let pub_key: RsaPublicKey = priv_key.into(); + let verifying_key = VerifyingKey::new_with_prefix(pub_key); + + for (text, sig, expected) in &tests { + let mut digest = Sha256::new(); + digest.update(text.as_bytes()); + let result = verifying_key.verify_digest(digest, &Signature::from_bytes(sig).unwrap()); + match expected { + true => result.expect("failed to verify"), + false => { + result.expect_err("expected verifying error"); + () + } + } + } + } #[test] fn test_unpadded_signature() { let msg = b"Thu Dec 19 18:06:16 EST 2013\n"; @@ -642,14 +821,14 @@ mod tests { #[test] fn test_unpadded_signature_signer() { let msg = b"Thu Dec 19 18:06:16 EST 2013\n"; - let expected_sig = Base64::decode_vec("pX4DR8azytjdQ1rtUiC040FjkepuQut5q2ZFX1pTjBrOVKNjgsCDyiJDGZTCNoh9qpXYbhl7iEym30BWWwuiZg==").unwrap(); + let expected_sig = Base64::decode_vec("F8rxGUnrRLYr9nTWrYMZYk3Y0msVzfl9daWt32AZHJNCVENOWUS17OwcFawgmYhyJZDG3leTT6S5QZLaozun/A==").unwrap(); let priv_key = get_private_key(); - let signing_key = SigningKey::new(priv_key); + let signing_key = SigningKey::::new(priv_key); let sig = signing_key.sign(msg); assert_eq!(sig.as_ref(), expected_sig); - let verifying_key: VerifyingKey = (&signing_key).into(); + let verifying_key: VerifyingKey<_> = (&signing_key).into(); verifying_key .verify(msg, &Signature::from_bytes(&expected_sig).unwrap()) .expect("failed to verify"); diff --git a/src/pss.rs b/src/pss.rs index fe498920..38a56234 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -1,14 +1,16 @@ -use alloc::boxed::Box; use alloc::vec; use alloc::vec::Vec; use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex}; -use digest::DynDigest; +use core::marker::PhantomData; +use digest::{Digest, DynDigest, FixedOutputReset}; use rand_core::{CryptoRng, RngCore}; -use signature::{RandomizedSigner, Signature as SignSignature, Verifier}; +use signature::{ + DigestVerifier, RandomizedDigestSigner, RandomizedSigner, Signature as SignSignature, Verifier, +}; use subtle::ConstantTimeEq; -use crate::algorithms::mgf1_xor; +use crate::algorithms::{mgf1_xor, mgf1_xor_digest}; use crate::errors::{Error, Result}; use crate::key::{PrivateKey, PublicKey}; use crate::{RsaPrivateKey, RsaPublicKey}; @@ -97,6 +99,22 @@ pub(crate) fn verify( emsa_pss_verify(hashed, &mut em, em_bits, None, digest) } +pub(crate) fn verify_digest(pub_key: &PK, hashed: &[u8], sig: &[u8]) -> Result<()> +where + PK: PublicKey, + D: Digest + FixedOutputReset, +{ + if sig.len() != pub_key.size() { + return Err(Error::Verification); + } + + let em_bits = pub_key.n().bits() - 1; + let em_len = (em_bits + 7) / 8; + let mut em = pub_key.raw_encryption_primitive(sig, em_len)?; + + emsa_pss_verify_digest::(hashed, &mut em, em_bits, None) +} + /// 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 @@ -110,18 +128,30 @@ pub(crate) fn sign( salt_len: Option, digest: &mut dyn DynDigest, ) -> Result> { - let salt = generate_salt(rng, priv_key, salt_len, digest); + let salt = generate_salt(rng, priv_key, salt_len, digest.output_size()); sign_pss_with_salt(blind.then(|| rng), priv_key, hashed, &salt, digest) } +pub(crate) fn sign_digest( + rng: &mut T, + blind: bool, + priv_key: &SK, + hashed: &[u8], + salt_len: Option, +) -> Result> { + let salt = generate_salt(rng, priv_key, salt_len, ::output_size()); + + sign_pss_with_salt_digest::<_, _, D>(blind.then(|| rng), priv_key, hashed, &salt) +} + fn generate_salt( rng: &mut T, priv_key: &SK, salt_len: Option, - digest: &mut dyn DynDigest, + digest_size: usize, ) -> Vec { - let salt_len = salt_len.unwrap_or_else(|| priv_key.size() - 2 - digest.output_size()); + let salt_len = salt_len.unwrap_or_else(|| priv_key.size() - 2 - digest_size); let mut salt = vec![0; salt_len]; rng.fill_bytes(&mut salt[..]); @@ -146,6 +176,22 @@ fn sign_pss_with_salt( priv_key.raw_decryption_primitive(blind_rng, &em, priv_key.size()) } +fn sign_pss_with_salt_digest< + T: CryptoRng + RngCore, + SK: PrivateKey, + D: Digest + FixedOutputReset, +>( + blind_rng: Option<&mut T>, + priv_key: &SK, + hashed: &[u8], + salt: &[u8], +) -> Result> { + let em_bits = priv_key.n().bits() - 1; + let em = emsa_pss_encode_digest::(hashed, em_bits, salt)?; + + priv_key.raw_decryption_primitive(blind_rng, &em, priv_key.size()) +} + fn emsa_pss_encode( m_hash: &[u8], em_bits: usize, @@ -219,19 +265,91 @@ fn emsa_pss_encode( Ok(em) } -fn emsa_pss_verify( +fn emsa_pss_encode_digest(m_hash: &[u8], em_bits: usize, salt: &[u8]) -> Result> +where + D: Digest + FixedOutputReset, +{ + // See [1], section 9.1.1 + let h_len = ::output_size(); + let s_len = salt.len(); + let em_len = (em_bits + 7) / 8; + + // 1. If the length of M is greater than the input limitation for the + // hash function (2^61 - 1 octets for SHA-1), output "message too + // long" and stop. + // + // 2. Let mHash = Hash(M), an octet string of length hLen. + if m_hash.len() != h_len { + return Err(Error::InputNotHashed); + } + + // 3. If em_len < h_len + s_len + 2, output "encoding error" and stop. + if em_len < h_len + s_len + 2 { + // TODO: Key size too small + return Err(Error::Internal); + } + + let mut em = vec![0; em_len]; + + let (db, h) = em.split_at_mut(em_len - h_len - 1); + let h = &mut h[..(em_len - 1) - db.len()]; + + // 4. Generate a random octet string salt of length s_len; if s_len = 0, + // then salt is the empty string. + // + // 5. Let + // M' = (0x)00 00 00 00 00 00 00 00 || m_hash || salt; + // + // M' is an octet string of length 8 + h_len + s_len with eight + // initial zero octets. + // + // 6. Let H = Hash(M'), an octet string of length h_len. + let prefix = [0u8; 8]; + + let mut hash = D::new(); + + Digest::update(&mut hash, &prefix); + Digest::update(&mut hash, m_hash); + Digest::update(&mut hash, salt); + + let hashed = hash.finalize_reset(); + h.copy_from_slice(&hashed); + + // 7. Generate an octet string PS consisting of em_len - s_len - h_len - 2 + // zero octets. The length of PS may be 0. + // + // 8. Let DB = PS || 0x01 || salt; DB is an octet string of length + // emLen - hLen - 1. + db[em_len - s_len - h_len - 2] = 0x01; + db[em_len - s_len - h_len - 1..].copy_from_slice(salt); + + // 9. Let dbMask = MGF(H, emLen - hLen - 1). + // + // 10. Let maskedDB = DB \xor dbMask. + mgf1_xor_digest(db, &mut hash, &h); + + // 11. Set the leftmost 8 * em_len - em_bits bits of the leftmost octet in + // maskedDB to zero. + db[0] &= 0xFF >> (8 * em_len - em_bits); + + // 12. Let EM = maskedDB || H || 0xbc. + em[em_len - 1] = 0xBC; + + Ok(em) +} + +fn emsa_pss_verify_pre<'a>( m_hash: &[u8], - em: &mut [u8], + em: &'a mut [u8], em_bits: usize, s_len: Option, - hash: &mut dyn DynDigest, -) -> Result<()> { + h_len: usize, +) -> Result<(&'a mut [u8], &'a mut [u8])> { // 1. If the length of M is greater than the input limitation for the // hash function (2^61 - 1 octets for SHA-1), output "inconsistent" // and stop. // // 2. Let mHash = Hash(M), an octet string of length hLen - let h_len = hash.output_size(); if m_hash.len() != h_len { return Err(Error::Verification); } @@ -260,15 +378,15 @@ fn emsa_pss_verify( return Err(Error::Verification); } - // 7. Let dbMask = MGF(H, em_len - h_len - 1) - // - // 8. Let DB = maskedDB \xor dbMask - mgf1_xor(db, hash, &*h); - - // 9. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in DB - // to zero. - db[0] &= 0xFF >> /*uint*/(8 * em_len - em_bits); + Ok((db, h)) +} +fn emsa_pss_get_salt<'a>( + db: &'a [u8], + em_len: usize, + s_len: Option, + h_len: usize, +) -> Result<&'a [u8]> { let s_len = match s_len { None => (0..=em_len - (h_len + 2)) .rev() @@ -296,6 +414,32 @@ fn emsa_pss_verify( // 11. Let salt be the last s_len octets of DB. let salt = &db[db.len() - s_len..]; + Ok(salt) +} + +fn emsa_pss_verify( + m_hash: &[u8], + em: &mut [u8], + em_bits: usize, + s_len: Option, + hash: &mut dyn DynDigest, +) -> Result<()> { + let em_len = em.len(); //(em_bits + 7) / 8; + let h_len = hash.output_size(); + + let (db, h) = emsa_pss_verify_pre(m_hash, em, em_bits, s_len, h_len)?; + + // 7. Let dbMask = MGF(H, em_len - h_len - 1) + // + // 8. Let DB = maskedDB \xor dbMask + mgf1_xor(db, hash, &*h); + + // 9. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in DB + // to zero. + db[0] &= 0xFF >> /*uint*/(8 * em_len - em_bits); + + let salt = emsa_pss_get_salt(db, em_len, s_len, h_len)?; + // 12. Let // M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt ; // M' is an octet string of length 8 + hLen + sLen with eight @@ -317,151 +461,279 @@ fn emsa_pss_verify( } } -pub struct SigningKey { +fn emsa_pss_verify_digest( + m_hash: &[u8], + em: &mut [u8], + em_bits: usize, + s_len: Option, +) -> Result<()> +where + D: Digest + FixedOutputReset, +{ + let em_len = em.len(); //(em_bits + 7) / 8; + let h_len = ::output_size(); + + let (db, h) = emsa_pss_verify_pre(m_hash, em, em_bits, s_len, h_len)?; + + let mut hash = D::new(); + + // 7. Let dbMask = MGF(H, em_len - h_len - 1) + // + // 8. Let DB = maskedDB \xor dbMask + mgf1_xor_digest::(db, &mut hash, &*h); + + // 9. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in DB + // to zero. + db[0] &= 0xFF >> /*uint*/(8 * em_len - em_bits); + + let salt = emsa_pss_get_salt(db, em_len, s_len, h_len)?; + + // 12. Let + // M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt ; + // M' is an octet string of length 8 + hLen + sLen with eight + // initial zero octets. + // + // 13. Let H' = Hash(M'), an octet string of length hLen. + let prefix = [0u8; 8]; + + Digest::update(&mut hash, &prefix[..]); + Digest::update(&mut hash, m_hash); + Digest::update(&mut hash, salt); + let h0 = hash.finalize_reset(); + + // 14. If H = H', output "consistent." Otherwise, output "inconsistent." + if h0.ct_eq(h).into() { + Ok(()) + } else { + Err(Error::Verification) + } +} + +pub struct SigningKey +where + D: Digest, +{ inner: RsaPrivateKey, salt_len: Option, - digest: Box, + phantom: PhantomData, } -impl SigningKey { +impl SigningKey +where + D: Digest, +{ pub(crate) fn key(&self) -> &RsaPrivateKey { &self.inner } - pub(crate) fn digest(&self) -> Box { - self.digest.box_clone() + pub fn new(key: RsaPrivateKey) -> Self { + Self { + inner: key, + salt_len: None, + phantom: Default::default(), + } } - pub fn new(key: RsaPrivateKey, digest: Box) -> Self { + pub fn new_with_salt_len(key: RsaPrivateKey, salt_len: usize) -> Self { Self { inner: key, - salt_len: None, - digest: digest, + salt_len: Some(salt_len), + phantom: Default::default(), } } } -impl RandomizedSigner for SigningKey { +impl RandomizedSigner for SigningKey +where + D: Digest + FixedOutputReset, +{ fn try_sign_with_rng( &self, mut rng: impl CryptoRng + RngCore, - digest: &[u8], + msg: &[u8], + ) -> signature::Result { + sign_digest::<_, _, D>(&mut rng, false, &self.inner, &D::digest(msg), self.salt_len) + .map(|v| v.into()) + .map_err(|e| e.into()) + } +} + +impl RandomizedDigestSigner for SigningKey +where + D: Digest + FixedOutputReset, +{ + fn try_sign_digest_with_rng( + &self, + mut rng: impl CryptoRng + RngCore, + digest: D, ) -> signature::Result { - sign( + sign_digest::<_, _, D>( &mut rng, false, &self.inner, - digest, + &digest.finalize(), self.salt_len, - self.digest.box_clone().as_mut(), ) .map(|v| v.into()) .map_err(|e| e.into()) } } -pub struct BlindedSigningKey { +pub struct BlindedSigningKey +where + D: Digest, +{ inner: RsaPrivateKey, salt_len: Option, - digest: Box, + phantom: PhantomData, } -impl BlindedSigningKey { +impl BlindedSigningKey +where + D: Digest, +{ pub(crate) fn key(&self) -> &RsaPrivateKey { &self.inner } - pub(crate) fn digest(&self) -> Box { - self.digest.box_clone() + pub fn new(key: RsaPrivateKey) -> Self { + Self { + inner: key, + salt_len: None, + phantom: Default::default(), + } } - pub fn new(key: RsaPrivateKey, digest: Box) -> Self { + pub fn new_with_salt_len(key: RsaPrivateKey, salt_len: usize) -> Self { Self { inner: key, - salt_len: None, - digest: digest, + salt_len: Some(salt_len), + phantom: Default::default(), } } } -impl RandomizedSigner for BlindedSigningKey { +impl RandomizedSigner for BlindedSigningKey +where + D: Digest + FixedOutputReset, +{ fn try_sign_with_rng( &self, mut rng: impl CryptoRng + RngCore, - digest: &[u8], + msg: &[u8], + ) -> signature::Result { + sign_digest::<_, _, D>(&mut rng, true, &self.inner, &D::digest(msg), self.salt_len) + .map(|v| v.into()) + .map_err(|e| e.into()) + } +} + +impl RandomizedDigestSigner for BlindedSigningKey +where + D: Digest + FixedOutputReset, +{ + fn try_sign_digest_with_rng( + &self, + mut rng: impl CryptoRng + RngCore, + digest: D, ) -> signature::Result { - sign( + sign_digest::<_, _, D>( &mut rng, true, &self.inner, - digest, + &digest.finalize(), self.salt_len, - self.digest.box_clone().as_mut(), ) .map(|v| v.into()) .map_err(|e| e.into()) } } -pub struct VerifyingKey { +pub struct VerifyingKey +where + D: Digest, +{ inner: RsaPublicKey, - digest: Box, + phantom: PhantomData, } -impl VerifyingKey { - pub fn new(key: RsaPublicKey, digest: Box) -> Self { +impl VerifyingKey +where + D: Digest, +{ + pub fn new(key: RsaPublicKey) -> Self { Self { inner: key, - digest: digest, + phantom: Default::default(), } } } -impl From for VerifyingKey { - fn from(key: SigningKey) -> Self { +impl From> for VerifyingKey +where + D: Digest, +{ + fn from(key: SigningKey) -> Self { Self { inner: key.key().into(), - digest: key.digest(), + phantom: Default::default(), } } } -impl From<&SigningKey> for VerifyingKey { - fn from(key: &SigningKey) -> Self { +impl From<&SigningKey> for VerifyingKey +where + D: Digest, +{ + fn from(key: &SigningKey) -> Self { Self { inner: key.key().into(), - digest: key.digest(), + phantom: Default::default(), } } } -impl From for VerifyingKey { - fn from(key: BlindedSigningKey) -> Self { +impl From> for VerifyingKey +where + D: Digest, +{ + fn from(key: BlindedSigningKey) -> Self { Self { inner: key.key().into(), - digest: key.digest(), + phantom: Default::default(), } } } -impl From<&BlindedSigningKey> for VerifyingKey { - fn from(key: &BlindedSigningKey) -> Self { +impl From<&BlindedSigningKey> for VerifyingKey +where + D: Digest, +{ + fn from(key: &BlindedSigningKey) -> Self { Self { inner: key.key().into(), - digest: key.digest(), + phantom: Default::default(), } } } -impl Verifier for VerifyingKey { +impl Verifier for VerifyingKey +where + D: Digest + FixedOutputReset, +{ fn verify(&self, msg: &[u8], signature: &Signature) -> signature::Result<()> { - verify( - &self.inner, - msg, - signature.as_ref(), - self.digest.box_clone().as_mut(), - ) - .map_err(|e| e.into()) + verify_digest::<_, D>(&self.inner, &D::digest(msg), signature.as_ref()) + .map_err(|e| e.into()) + } +} + +impl DigestVerifier for VerifyingKey +where + D: Digest + FixedOutputReset, +{ + fn verify_digest(&self, digest: D, signature: &Signature) -> signature::Result<()> { + verify_digest::<_, D>(&self.inner, &digest.finalize(), signature.as_ref()) + .map_err(|e| e.into()) } } @@ -470,13 +742,14 @@ mod test { use crate::pss::{BlindedSigningKey, SigningKey, VerifyingKey}; use crate::{PaddingScheme, PublicKey, RsaPrivateKey, RsaPublicKey}; - use alloc::boxed::Box; 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::{RandomizedSigner, Signature, Verifier}; + use signature::{ + DigestVerifier, RandomizedDigestSigner, RandomizedSigner, Signature, Verifier, + }; fn get_private_key() -> RsaPrivateKey { // In order to generate new test vectors you'll need the PEM form of this key: @@ -561,11 +834,50 @@ mod test { ), ]; let pub_key: RsaPublicKey = priv_key.into(); - let verifying_key: VerifyingKey = VerifyingKey::new(pub_key, Box::new(Sha1::new())); + let verifying_key: VerifyingKey = VerifyingKey::new(pub_key); for (text, sig, expected) in &tests { - let digest = Sha1::digest(text.as_bytes()).to_vec(); - let result = verifying_key.verify(&digest, &Signature::from_bytes(sig).unwrap()); + let result = + verifying_key.verify(text.as_bytes(), &Signature::from_bytes(sig).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::from_bytes(sig).unwrap()); match expected { true => result.expect("failed to verify"), false => { @@ -620,14 +932,13 @@ mod test { let tests = ["test\n"]; let mut rng = ChaCha8Rng::from_seed([42; 32]); - let signing_key = SigningKey::new(priv_key, Box::new(Sha1::new())); - let verifying_key: VerifyingKey = (&signing_key).into(); + let signing_key = SigningKey::::new(priv_key); + let verifying_key = VerifyingKey::from(&signing_key); for test in &tests { - let digest = Sha1::digest(test.as_bytes()).to_vec(); - let sig = signing_key.sign_with_rng(&mut rng, &digest); + let sig = signing_key.sign_with_rng(&mut rng, test.as_bytes()); verifying_key - .verify(&digest, &sig) + .verify(test.as_bytes(), &sig) .expect("failed to verify"); } } @@ -638,14 +949,57 @@ mod test { let tests = ["test\n"]; let mut rng = ChaCha8Rng::from_seed([42; 32]); - let signing_key = BlindedSigningKey::new(priv_key, Box::new(Sha1::new())); - let verifying_key: VerifyingKey = (&signing_key).into(); + let signing_key = BlindedSigningKey::::new(priv_key); + let verifying_key = VerifyingKey::from(&signing_key); for test in &tests { - let digest = Sha1::digest(test.as_bytes()).to_vec(); - let sig = signing_key.sign_with_rng(&mut rng, &digest); + 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 = VerifyingKey::from(&signing_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::::new(priv_key); + let verifying_key = VerifyingKey::from(&signing_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, &sig) + .verify_digest(digest, &sig) .expect("failed to verify"); } }