diff --git a/Cargo.toml b/Cargo.toml index d740dbe..98bd260 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ 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"] } +signature = { version = "1.5", 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 +56,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/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/lib.rs b/src/lib.rs index 0a79ca7..7685b04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -163,9 +163,12 @@ mod dummy_rng; mod encoding; mod key; mod oaep; -mod pkcs1v15; -mod pss; +pub mod pkcs1v15; +pub mod pss; mod raw; +mod signature; + +pub use self::signature::Signature; pub use pkcs1; pub use pkcs8; diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index 4142dcd..ec48721 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -1,12 +1,17 @@ use alloc::vec; use alloc::vec::Vec; use rand_core::{CryptoRng, RngCore}; +use signature::{RandomizedSigner, 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::{PublicKeyParts, RsaPrivateKey, RsaPublicKey}; + +pub use crate::Signature; // Encrypts the given message with RSA and the padding // scheme from PKCS#1 v1.5. The message must be no longer than the @@ -214,6 +219,162 @@ fn non_zero_random_bytes(rng: &mut R, data: &mut [u8]) { } } +pub struct RsaPkcs1v15SigningKey { + inner: RsaPrivateKey, + hash: Option, +} + +impl<'a> RsaPkcs1v15SigningKey { + pub(crate) fn key(&'a self) -> &'a RsaPrivateKey { + &self.inner + } + + pub(crate) fn hash(&self) -> Option { + self.hash + } +} + +impl From for RsaPkcs1v15SigningKey { + fn from(key: RsaPrivateKey) -> Self { + Self { + inner: key, + hash: None, + } + } +} + +impl From<&RsaPrivateKey> for RsaPkcs1v15SigningKey { + fn from(key: &RsaPrivateKey) -> Self { + Self { + inner: RsaPrivateKey::from_components( + key.n().clone(), + key.e().clone(), + key.d().clone(), + key.primes().clone().to_vec(), + ) + .unwrap(), + hash: None, + } + } +} + +impl From<(RsaPrivateKey, Hash)> for RsaPkcs1v15SigningKey { + fn from(data: (RsaPrivateKey, Hash)) -> Self { + let key = data.0; + let hash = data.1; + Self { + inner: key, + hash: Some(hash), + } + } +} + +impl From<(&RsaPrivateKey, Hash)> for RsaPkcs1v15SigningKey { + fn from(data: (&RsaPrivateKey, Hash)) -> Self { + let key = data.0; + let hash = data.1; + Self { + inner: RsaPrivateKey::from_components( + key.n().clone(), + key.e().clone(), + key.d().clone(), + key.primes().clone().to_vec(), + ) + .unwrap(), + hash: Some(hash), + } + } +} + +impl Signer for RsaPkcs1v15SigningKey { + 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 RsaPkcs1v15SigningKey { + 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 RsaPkcs1v15VerifyingKey { + inner: RsaPublicKey, + hash: Option, +} + +impl From for RsaPkcs1v15VerifyingKey { + fn from(key: RsaPublicKey) -> Self { + Self { + inner: key, + hash: None, + } + } +} + +impl From<&RsaPublicKey> for RsaPkcs1v15VerifyingKey { + fn from(key: &RsaPublicKey) -> Self { + Self { + inner: RsaPublicKey::new(key.n().clone(), key.e().clone()).unwrap(), + hash: None, + } + } +} + +impl From<(RsaPublicKey, Hash)> for RsaPkcs1v15VerifyingKey { + fn from(data: (RsaPublicKey, Hash)) -> Self { + let key = data.0; + let hash = data.1; + Self { + inner: key, + hash: Some(hash), + } + } +} + +impl From<(&RsaPublicKey, Hash)> for RsaPkcs1v15VerifyingKey { + fn from(data: (&RsaPublicKey, Hash)) -> Self { + let key = data.0; + let hash = data.1; + Self { + inner: RsaPublicKey::new(key.n().clone(), key.e().clone()).unwrap(), + hash: Some(hash), + } + } +} + +impl From for RsaPkcs1v15VerifyingKey { + fn from(key: RsaPkcs1v15SigningKey) -> Self { + Self { + inner: key.key().into(), + hash: key.hash(), + } + } +} + +impl From<&RsaPkcs1v15SigningKey> for RsaPkcs1v15VerifyingKey { + fn from(key: &RsaPkcs1v15SigningKey) -> Self { + Self { + inner: key.key().into(), + hash: key.hash(), + } + } +} + +impl Verifier for RsaPkcs1v15VerifyingKey { + 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 +385,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}; @@ -348,6 +510,32 @@ mod tests { } } + #[test] + fn test_sign_pkcs1v15_signer() { + let priv_key = get_private_key(); + + let tests = [( + "Test.\n", + hex!( + "a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33" + "6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae" + ), + )]; + + for (text, expected) in &tests { + let digest = Sha1::digest(text.as_bytes()).to_vec(); + + let signing_key: RsaPkcs1v15SigningKey = (&priv_key, Hash::SHA1).into(); + 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(); @@ -390,6 +578,45 @@ mod tests { } } + #[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: RsaPkcs1v15VerifyingKey = (&pub_key, Hash::SHA1).into(); + + 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"); + () + } + } + } + } + #[test] fn test_unpadded_signature() { let msg = b"Thu Dec 19 18:06:16 EST 2013\n"; @@ -406,4 +633,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: RsaPkcs1v15SigningKey = (&priv_key).into(); + let sig = signing_key.sign(msg); + assert_eq!(sig.as_ref(), expected_sig); + + let verifying_key: RsaPkcs1v15VerifyingKey = (&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 3267171..2118a63 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -1,13 +1,18 @@ +use alloc::boxed::Box; use alloc::vec; use alloc::vec::Vec; use digest::DynDigest; use rand_core::{CryptoRng, RngCore}; +use signature::{RandomizedSigner, Verifier}; use subtle::ConstantTimeEq; use crate::algorithms::mgf1_xor; use crate::errors::{Error, Result}; use crate::key::{PrivateKey, PublicKey}; +use crate::{PublicKeyParts, RsaPrivateKey, RsaPublicKey}; + +pub use crate::Signature; pub fn verify( pub_key: &PK, @@ -39,12 +44,36 @@ 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 with specified salt. @@ -235,15 +264,177 @@ fn emsa_pss_verify( } } +pub struct RsaPSSSigningKey { + inner: RsaPrivateKey, + salt_len: Option, + digest: Box, + blind: bool, +} + +impl<'a> RsaPSSSigningKey { + pub(crate) fn key(&'a self) -> &'a RsaPrivateKey { + &self.inner + } + + pub(crate) fn digest(&self) -> Box { + self.digest.box_clone() + } +} + +impl From<(RsaPrivateKey, &dyn DynDigest)> for RsaPSSSigningKey { + fn from(data: (RsaPrivateKey, &dyn DynDigest)) -> Self { + let key = data.0; + let digest = data.1; + Self { + inner: key, + salt_len: None, + digest: digest.box_clone(), + blind: false, + } + } +} + +impl From<(&RsaPrivateKey, &dyn DynDigest)> for RsaPSSSigningKey { + fn from(data: (&RsaPrivateKey, &dyn DynDigest)) -> Self { + let key = data.0; + let digest = data.1; + Self { + inner: RsaPrivateKey::from_components( + key.n().clone(), + key.e().clone(), + key.d().clone(), + key.primes().clone().to_vec(), + ) + .unwrap(), + salt_len: None, + digest: digest.box_clone(), + blind: false, + } + } +} + +impl From<(RsaPrivateKey, &dyn DynDigest, bool)> for RsaPSSSigningKey { + fn from(data: (RsaPrivateKey, &dyn DynDigest, bool)) -> Self { + let key = data.0; + let digest = data.1; + let blind = data.2; + Self { + inner: key, + salt_len: None, + digest: digest.box_clone(), + blind, + } + } +} + +impl From<(&RsaPrivateKey, &dyn DynDigest, bool)> for RsaPSSSigningKey { + fn from(data: (&RsaPrivateKey, &dyn DynDigest, bool)) -> Self { + let key = data.0; + let digest = data.1; + let blind = data.2; + Self { + inner: RsaPrivateKey::from_components( + key.n().clone(), + key.e().clone(), + key.d().clone(), + key.primes().clone().to_vec(), + ) + .unwrap(), + salt_len: None, + digest: digest.box_clone(), + blind, + } + } +} + +impl RandomizedSigner for RsaPSSSigningKey { + fn try_sign_with_rng( + &self, + mut rng: impl CryptoRng + RngCore, + digest: &[u8], + ) -> signature::Result { + sign_int( + &mut rng, + self.blind, + &self.inner, + digest, + self.salt_len, + self.digest.box_clone().as_mut(), + ) + .map(|v| v.into()) + .map_err(|e| e.into()) + } +} + +pub struct RsaPSSVerifyingKey { + inner: RsaPublicKey, + digest: Box, +} + +impl From<(RsaPublicKey, &dyn DynDigest)> for RsaPSSVerifyingKey { + fn from(data: (RsaPublicKey, &dyn DynDigest)) -> Self { + let key = data.0; + let digest = data.1; + Self { + inner: key, + digest: digest.box_clone(), + } + } +} + +impl From<(&RsaPublicKey, &dyn DynDigest)> for RsaPSSVerifyingKey { + fn from(data: (&RsaPublicKey, &dyn DynDigest)) -> Self { + let key = data.0; + let digest = data.1; + Self { + inner: RsaPublicKey::new(key.n().clone(), key.e().clone()).unwrap(), + digest: digest.box_clone(), + } + } +} + +impl From for RsaPSSVerifyingKey { + fn from(key: RsaPSSSigningKey) -> Self { + Self { + inner: key.key().into(), + digest: key.digest(), + } + } +} + +impl From<&RsaPSSSigningKey> for RsaPSSVerifyingKey { + fn from(key: &RsaPSSSigningKey) -> Self { + Self { + inner: key.key().into(), + digest: key.digest(), + } + } +} + +impl Verifier for RsaPSSVerifyingKey { + 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::{RsaPSSSigningKey, RsaPSSVerifyingKey}; use crate::{PaddingScheme, PublicKey, RsaPrivateKey, RsaPublicKey}; + use digest::DynDigest; 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: @@ -306,6 +497,44 @@ mod test { } } + #[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: RsaPSSVerifyingKey = (pub_key, &Sha1::new() as &dyn DynDigest).into(); + + 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"); + () + } + } + } + } + #[test] fn test_sign_and_verify_roundtrip() { let priv_key = get_private_key(); @@ -332,4 +561,22 @@ 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: RsaPSSSigningKey = (priv_key, &Sha1::new() as &dyn DynDigest).into(); + let verifying_key: RsaPSSVerifyingKey = (&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"); + } + } } diff --git a/src/signature.rs b/src/signature.rs new file mode 100644 index 0000000..87d3859 --- /dev/null +++ b/src/signature.rs @@ -0,0 +1,70 @@ +use alloc::vec::Vec; +use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex}; +use signature::Signature as SignSignature; + +#[derive(Clone)] +pub struct Signature { + bytes: Vec, +} + +impl SignSignature 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) + } +}