diff --git a/Cargo.toml b/Cargo.toml index d740dbe..cdf3337 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ subtle = { version = "2.1.1", default-features = false } 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"] } zeroize = { version = "1", features = ["alloc"] } # Temporary workaround until https://github.com/dignifiedquire/num-bigint/pull/42 lands @@ -55,7 +57,7 @@ default = ["std", "pem"] nightly = ["num-bigint/nightly"] serde = ["num-bigint/serde", "serde_crate"] expose-internals = [] -std = ["digest/std", "pkcs1/std", "pkcs8/std", "rand_core/std"] +std = ["digest/std", "pkcs1/std", "pkcs8/std", "rand_core/std", "signature/std"] pem = ["pkcs1/pem", "pkcs8/pem"] pkcs5 = ["pkcs8/encryption"] getrandom = ["rand_core/getrandom"] diff --git a/src/dummy_rng.rs b/src/dummy_rng.rs new file mode 100644 index 0000000..87beaa9 --- /dev/null +++ b/src/dummy_rng.rs @@ -0,0 +1,26 @@ +use rand_core::{CryptoRng, RngCore}; + +/// This is a dummy RNG for cases when we need a concrete RNG type +/// which does not get used. +#[derive(Copy, Clone)] +pub(crate) struct DummyRng; + +impl RngCore for DummyRng { + fn next_u32(&mut self) -> u32 { + unimplemented!(); + } + + fn next_u64(&mut self) -> u64 { + unimplemented!(); + } + + fn fill_bytes(&mut self, _: &mut [u8]) { + unimplemented!(); + } + + fn try_fill_bytes(&mut self, _: &mut [u8]) -> core::result::Result<(), rand_core::Error> { + unimplemented!(); + } +} + +impl CryptoRng for DummyRng {} diff --git a/src/errors.rs b/src/errors.rs index 666ada0..5333995 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -64,3 +64,17 @@ impl From for Error { Error::Pkcs8(err) } } + +#[cfg(feature = "std")] +impl From for signature::Error { + fn from(err: Error) -> Self { + Self::from_source(err) + } +} + +#[cfg(not(feature = "std"))] +impl From for signature::Error { + fn from(_err: Error) -> Self { + Self::new() + } +} diff --git a/src/key.rs b/src/key.rs index 987f0a6..248f2f8 100644 --- a/src/key.rs +++ b/src/key.rs @@ -10,6 +10,7 @@ use serde_crate::{Deserialize, Serialize}; use zeroize::Zeroize; use crate::algorithms::{generate_multi_prime_key, generate_multi_prime_key_with_exp}; +use crate::dummy_rng::DummyRng; use crate::errors::{Error, Result}; use crate::padding::PaddingScheme; @@ -586,31 +587,6 @@ fn check_public_with_max_size(public_key: &impl PublicKeyParts, max_size: usize) Ok(()) } -/// This is a dummy RNG for cases when we need a concrete RNG type -/// which does not get used. -#[derive(Copy, Clone)] -struct DummyRng; - -impl RngCore for DummyRng { - fn next_u32(&mut self) -> u32 { - unimplemented!(); - } - - fn next_u64(&mut self) -> u64 { - unimplemented!(); - } - - fn fill_bytes(&mut self, _: &mut [u8]) { - unimplemented!(); - } - - fn try_fill_bytes(&mut self, _: &mut [u8]) -> core::result::Result<(), rand_core::Error> { - unimplemented!(); - } -} - -impl CryptoRng for DummyRng {} - #[cfg(test)] mod tests { use super::*; diff --git a/src/lib.rs b/src/lib.rs index 706a504..33256f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,6 +47,54 @@ //! 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"); +//! ``` +//! +//! Using PSS signatures +//! ``` +//! use rsa::{Hash, RsaPrivateKey}; +//! use rsa::pss::{BlindedSigningKey, 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 = BlindedSigningKey::new(private_key, Box::new(Sha256::new())); +//! 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"); +//! ``` +//! //! ## PKCS#1 RSA Key Encoding //! //! PKCS#1 is a legacy format for encoding RSA keys as binary (DER) or text @@ -158,12 +206,15 @@ pub mod errors; pub mod hash; /// Supported padding schemes. pub mod padding; +/// RSASSA-PKCS1-v1_5 Signature support +pub mod pkcs1v15; +/// RSASSA-PSS Signature support +pub mod pss; +mod dummy_rng; mod encoding; mod key; mod oaep; -mod pkcs1v15; -mod pss; mod raw; pub use pkcs1; diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index 232e739..9e0ee59 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -1,18 +1,89 @@ use alloc::vec; use alloc::vec::Vec; +use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex}; use rand_core::{CryptoRng, RngCore}; +use signature::{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::key::{self, PrivateKey, PublicKey}; +use crate::{RsaPrivateKey, RsaPublicKey}; + +#[derive(Clone)] +pub struct Signature { + bytes: Vec, +} + +impl signature::Signature for Signature { + fn from_bytes(bytes: &[u8]) -> signature::Result { + Ok(Signature { + bytes: bytes.into(), + }) + } + + fn as_bytes(&self) -> &[u8] { + &self.bytes.as_slice() + } +} + +impl From> for Signature { + fn from(bytes: Vec) -> Self { + Self { bytes } + } +} + +impl PartialEq for Signature { + fn eq(&self, other: &Self) -> bool { + self.as_bytes() == other.as_bytes() + } +} + +impl Eq for Signature {} + +impl Debug for Signature { + fn fmt(&self, fmt: &mut Formatter<'_>) -> core::result::Result<(), core::fmt::Error> { + fmt.debug_list().entries(self.as_bytes().iter()).finish() + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.as_bytes() + } +} + +impl LowerHex for Signature { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + for byte in self.as_bytes() { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } +} + +impl UpperHex for Signature { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + for byte in self.as_bytes() { + write!(f, "{:02X}", byte)?; + } + Ok(()) + } +} + +impl Display for Signature { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{:X}", self) + } +} // Encrypts the given message with RSA and the padding // scheme from PKCS#1 v1.5. The message must be no longer than the // length of the public modulus minus 11 bytes. #[inline] -pub fn encrypt( +pub(crate) fn encrypt( rng: &mut R, pub_key: &PK, msg: &[u8], @@ -43,7 +114,7 @@ pub fn encrypt( // forge signatures as if they had the private key. See // `decrypt_session_key` for a way of solving this problem. #[inline] -pub fn decrypt( +pub(crate) fn decrypt( rng: Option<&mut R>, priv_key: &SK, ciphertext: &[u8], @@ -72,7 +143,7 @@ pub fn decrypt( // messages to signatures and identify the signed messages. As ever, // signatures provide authenticity, not confidentiality. #[inline] -pub fn sign( +pub(crate) fn sign( rng: Option<&mut R>, priv_key: &SK, hash: Option<&Hash>, @@ -99,7 +170,7 @@ pub fn sign( /// Verifies an RSA PKCS#1 v1.5 signature. #[inline] -pub fn verify( +pub(crate) fn verify( pub_key: &PK, hash: Option<&Hash>, hashed: &[u8], @@ -214,6 +285,100 @@ fn non_zero_random_bytes(rng: &mut R, data: &mut [u8]) { } } +pub struct SigningKey { + inner: RsaPrivateKey, + hash: Option, +} + +impl SigningKey { + pub(crate) fn key(&self) -> &RsaPrivateKey { + &self.inner + } + + pub(crate) fn hash(&self) -> Option { + self.hash + } + + pub fn new(key: RsaPrivateKey) -> Self { + Self { + inner: key, + hash: None, + } + } + + pub fn new_with_hash(key: RsaPrivateKey, hash: Hash) -> Self { + Self { + inner: key, + hash: Some(hash), + } + } +} + +impl Signer for SigningKey { + fn try_sign(&self, digest: &[u8]) -> signature::Result { + sign::(None, &self.inner, self.hash.as_ref(), digest) + .map(|v| v.into()) + .map_err(|e| e.into()) + } +} + +impl RandomizedSigner for SigningKey { + fn try_sign_with_rng( + &self, + mut rng: impl CryptoRng + RngCore, + digest: &[u8], + ) -> signature::Result { + sign(Some(&mut rng), &self.inner, self.hash.as_ref(), digest) + .map(|v| v.into()) + .map_err(|e| e.into()) + } +} + +pub struct VerifyingKey { + inner: RsaPublicKey, + hash: Option, +} + +impl VerifyingKey { + pub fn new(key: RsaPublicKey) -> Self { + Self { + inner: key, + hash: None, + } + } + + pub fn new_with_hash(key: RsaPublicKey, hash: Hash) -> Self { + Self { + inner: key, + hash: Some(hash), + } + } +} + +impl From for VerifyingKey { + fn from(key: SigningKey) -> Self { + Self { + inner: key.key().into(), + hash: key.hash(), + } + } +} + +impl From<&SigningKey> for VerifyingKey { + fn from(key: &SigningKey) -> Self { + Self { + inner: key.key().into(), + hash: key.hash(), + } + } +} + +impl Verifier for VerifyingKey { + 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()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -224,6 +389,7 @@ mod tests { use num_traits::Num; use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; use sha1::{Digest, Sha1}; + use signature::{RandomizedSigner, Signature, Signer, Verifier}; use crate::{Hash, PaddingScheme, PublicKey, PublicKeyParts, RsaPrivateKey, RsaPublicKey}; @@ -349,7 +515,7 @@ mod tests { } #[test] - fn test_verify_pkcs1v15() { + fn test_sign_pkcs1v15_signer() { let priv_key = get_private_key(); let tests = [( @@ -359,18 +525,100 @@ mod tests { "6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae" ), )]; + + let signing_key = SigningKey::new_with_hash(priv_key, Hash::SHA1); + + for (text, expected) in &tests { + let digest = Sha1::digest(text.as_bytes()).to_vec(); + + let out = signing_key.sign(&digest); + assert_ne!(out.as_ref(), digest); + assert_eq!(out.as_ref(), expected); + + let mut rng = ChaCha8Rng::from_seed([42; 32]); + let out2 = signing_key.sign_with_rng(&mut rng, &digest); + assert_eq!(out2.as_ref(), expected); + } + } + + #[test] + fn test_verify_pkcs1v15() { + let priv_key = get_private_key(); + + let tests = [ + ( + "Test.\n", + hex!( + "a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33" + "6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae" + ), + true, + ), + ( + "Test.\n", + hex!( + "a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33" + "6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362af" + ), + false, + ), + ]; let pub_key: RsaPublicKey = priv_key.into(); - for (text, sig) in &tests { + for (text, sig, expected) in &tests { let digest = Sha1::digest(text.as_bytes()).to_vec(); - pub_key - .verify( - PaddingScheme::new_pkcs1v15_sign(Some(Hash::SHA1)), - &digest, - sig, - ) - .expect("failed to verify"); + let result = pub_key.verify( + PaddingScheme::new_pkcs1v15_sign(Some(Hash::SHA1)), + &digest, + sig, + ); + match expected { + true => result.expect("failed to verify"), + false => { + result.expect_err("expected verifying error"); + () + } + } + } + } + + #[test] + fn test_verify_pkcs1v15_signer() { + let priv_key = get_private_key(); + + let tests = [ + ( + "Test.\n", + hex!( + "a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33" + "6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae" + ), + true, + ), + ( + "Test.\n", + hex!( + "a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33" + "6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362af" + ), + false, + ), + ]; + let pub_key: RsaPublicKey = priv_key.into(); + let verifying_key = VerifyingKey::new_with_hash(pub_key, Hash::SHA1); + + 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()); + match expected { + true => result.expect("failed to verify"), + false => { + result.expect_err("expected verifying error"); + () + } + } } } @@ -390,4 +638,26 @@ mod tests { .verify(PaddingScheme::new_pkcs1v15_sign(None), msg, &sig) .expect("failed to verify"); } + + #[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 priv_key = get_private_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(); + verifying_key + .verify(msg, &Signature::from_bytes(&expected_sig).unwrap()) + .expect("failed to verify"); + + let mut rng = ChaCha8Rng::from_seed([42; 32]); + let sig = signing_key.sign_with_rng(&mut rng, msg); + assert_eq!(sig.as_ref(), expected_sig); + + verifying_key.verify(msg, &sig).expect("failed to verify"); + } } diff --git a/src/pss.rs b/src/pss.rs index 41d2f14..d577be1 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -1,15 +1,86 @@ +use alloc::boxed::Box; use alloc::vec; use alloc::vec::Vec; +use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex}; use digest::DynDigest; use rand_core::{CryptoRng, RngCore}; +use signature::{RandomizedSigner, Signature as SignSignature, Verifier}; use subtle::ConstantTimeEq; use crate::algorithms::mgf1_xor; use crate::errors::{Error, Result}; use crate::key::{PrivateKey, PublicKey}; +use crate::{RsaPrivateKey, RsaPublicKey}; -pub fn verify( +#[derive(Clone)] +pub struct Signature { + bytes: Vec, +} + +impl signature::Signature for Signature { + fn from_bytes(bytes: &[u8]) -> signature::Result { + Ok(Signature { + bytes: bytes.into(), + }) + } + + fn as_bytes(&self) -> &[u8] { + &self.bytes.as_slice() + } +} + +impl From> for Signature { + fn from(bytes: Vec) -> Self { + Self { bytes } + } +} + +impl PartialEq for Signature { + fn eq(&self, other: &Self) -> bool { + self.as_bytes() == other.as_bytes() + } +} + +impl Eq for Signature {} + +impl Debug for Signature { + fn fmt(&self, fmt: &mut Formatter<'_>) -> core::result::Result<(), core::fmt::Error> { + fmt.debug_list().entries(self.as_bytes().iter()).finish() + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.as_bytes() + } +} + +impl LowerHex for Signature { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + for byte in self.as_bytes() { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } +} + +impl UpperHex for Signature { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + for byte in self.as_bytes() { + write!(f, "{:02X}", byte)?; + } + Ok(()) + } +} + +impl Display for Signature { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{:X}", self) + } +} + +pub(crate) fn verify( pub_key: &PK, hashed: &[u8], sig: &[u8], @@ -26,12 +97,12 @@ pub fn verify( emsa_pss_verify(hashed, &mut em, em_bits, None, digest) } -/// SignPSS calculates the signature of hashed using RSASSA-PSS [1]. +/// 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. // TODO: bind T with the CryptoRng trait -pub fn sign( +pub(crate) fn sign( rng: &mut T, blind_rng: Option<&mut S>, priv_key: &SK, @@ -39,15 +110,39 @@ pub fn sign( salt_len: Option, digest: &mut dyn DynDigest, ) -> Result> { + let salt = generate_salt(rng, priv_key, salt_len, digest); + + sign_pss_with_salt(blind_rng, priv_key, hashed, &salt, digest) +} + +fn sign_int( + rng: &mut T, + blind: bool, + priv_key: &SK, + hashed: &[u8], + salt_len: Option, + digest: &mut dyn DynDigest, +) -> Result> { + let salt = generate_salt(rng, priv_key, salt_len, digest); + + sign_pss_with_salt(blind.then(|| rng), priv_key, hashed, &salt, digest) +} + +fn generate_salt( + rng: &mut T, + priv_key: &SK, + salt_len: Option, + digest: &mut dyn DynDigest, +) -> Vec { let salt_len = salt_len.unwrap_or_else(|| priv_key.size() - 2 - digest.output_size()); let mut salt = vec![0; salt_len]; rng.fill_bytes(&mut salt[..]); - sign_pss_with_salt(blind_rng, priv_key, hashed, &salt, digest) + salt } -/// signPSSWithSalt calculates the signature of hashed using PSS [1] with specified 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. @@ -235,15 +330,166 @@ fn emsa_pss_verify( } } +pub struct SigningKey { + inner: RsaPrivateKey, + salt_len: Option, + digest: Box, +} + +impl SigningKey { + pub(crate) fn key(&self) -> &RsaPrivateKey { + &self.inner + } + + pub(crate) fn digest(&self) -> Box { + self.digest.box_clone() + } + + pub fn new(key: RsaPrivateKey, digest: Box) -> Self { + Self { + inner: key, + salt_len: None, + digest: digest, + } + } +} + +impl RandomizedSigner for SigningKey { + fn try_sign_with_rng( + &self, + mut rng: impl CryptoRng + RngCore, + digest: &[u8], + ) -> signature::Result { + sign_int( + &mut rng, + false, + &self.inner, + digest, + self.salt_len, + self.digest.box_clone().as_mut(), + ) + .map(|v| v.into()) + .map_err(|e| e.into()) + } +} + +pub struct BlindedSigningKey { + inner: RsaPrivateKey, + salt_len: Option, + digest: Box, +} + +impl BlindedSigningKey { + pub(crate) fn key(&self) -> &RsaPrivateKey { + &self.inner + } + + pub(crate) fn digest(&self) -> Box { + self.digest.box_clone() + } + + pub fn new(key: RsaPrivateKey, digest: Box) -> Self { + Self { + inner: key, + salt_len: None, + digest: digest, + } + } +} + +impl RandomizedSigner for BlindedSigningKey { + fn try_sign_with_rng( + &self, + mut rng: impl CryptoRng + RngCore, + digest: &[u8], + ) -> signature::Result { + sign_int( + &mut rng, + true, + &self.inner, + digest, + self.salt_len, + self.digest.box_clone().as_mut(), + ) + .map(|v| v.into()) + .map_err(|e| e.into()) + } +} + +pub struct VerifyingKey { + inner: RsaPublicKey, + digest: Box, +} + +impl VerifyingKey { + pub fn new(key: RsaPublicKey, digest: Box) -> Self { + Self { + inner: key, + digest: digest, + } + } +} + +impl From for VerifyingKey { + fn from(key: SigningKey) -> Self { + Self { + inner: key.key().into(), + digest: key.digest(), + } + } +} + +impl From<&SigningKey> for VerifyingKey { + fn from(key: &SigningKey) -> Self { + Self { + inner: key.key().into(), + digest: key.digest(), + } + } +} + +impl From for VerifyingKey { + fn from(key: BlindedSigningKey) -> Self { + Self { + inner: key.key().into(), + digest: key.digest(), + } + } +} + +impl From<&BlindedSigningKey> for VerifyingKey { + fn from(key: &BlindedSigningKey) -> Self { + Self { + inner: key.key().into(), + digest: key.digest(), + } + } +} + +impl Verifier for VerifyingKey { + 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()) + } +} + #[cfg(test)] 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}; fn get_private_key() -> RsaPrivateKey { // In order to generate new test vectors you'll need the PEM form of this key: @@ -272,21 +518,75 @@ mod test { fn test_verify_pss() { let priv_key = get_private_key(); - let tests = [( - "test\n", - hex!( - "6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae" - "30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f" + let tests = [ + ( + "test\n", + hex!( + "6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae" + "30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f" + ), + true, ), - )]; + ( + "test\n", + hex!( + "6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae" + "30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962e" + ), + false, + ), + ]; let pub_key: RsaPublicKey = priv_key.into(); - for (text, sig) in &tests { + for (text, sig, expected) in &tests { let digest = Sha1::digest(text.as_bytes()).to_vec(); let rng = ChaCha8Rng::from_seed([42; 32]); - pub_key - .verify(PaddingScheme::new_pss::(rng), &digest, sig) - .expect("failed to verify"); + let result = pub_key.verify(PaddingScheme::new_pss::(rng), &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 = VerifyingKey::new(pub_key, Box::new(Sha1::new())); + + 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()); + match expected { + true => result.expect("failed to verify"), + false => { + result.expect_err("expected verifying error"); + () + } + } } } @@ -316,4 +616,40 @@ mod test { .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::new(priv_key, Box::new(Sha1::new())); + let verifying_key: VerifyingKey = (&signing_key).into(); + + for test in &tests { + let digest = Sha1::digest(test.as_bytes()).to_vec(); + let sig = signing_key.sign_with_rng(&mut rng, &digest); + verifying_key + .verify(&digest, &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::new(priv_key, Box::new(Sha1::new())); + let verifying_key: VerifyingKey = (&signing_key).into(); + + for test in &tests { + let digest = Sha1::digest(test.as_bytes()).to_vec(); + let sig = signing_key.sign_with_rng(&mut rng, &digest); + verifying_key + .verify(&digest, &sig) + .expect("failed to verify"); + } + } }