From 7d6a828f53858bba66412b404359556755619d14 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Fri, 6 Jan 2023 05:40:12 +0200 Subject: [PATCH 1/6] feat: drop old signing/verification API The signing and verification is now implemented using the generic Signature and *Signer / *Verifier traits. Drop old API proprietary to the RSA crate. Signed-off-by: Dmitry Baryshkov --- benches/key.rs | 23 +++-- src/key.rs | 86 +---------------- src/padding.rs | 68 -------------- src/pkcs1v15.rs | 88 +---------------- src/pss.rs | 245 +----------------------------------------------- 5 files changed, 21 insertions(+), 489 deletions(-) diff --git a/benches/key.rs b/benches/key.rs index 26c6616a..ece0887a 100644 --- a/benches/key.rs +++ b/benches/key.rs @@ -6,6 +6,7 @@ use base64ct::{Base64, Encoding}; use num_bigint::BigUint; use num_traits::{FromPrimitive, Num}; use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; +use rsa::signature::RandomizedSigner; use rsa::{PaddingScheme, RsaPrivateKey}; use sha2::{Digest, Sha256}; use test::Bencher; @@ -41,17 +42,25 @@ fn bench_rsa_2048_pkcsv1_decrypt(b: &mut Bencher) { #[bench] fn bench_rsa_2048_pkcsv1_sign_blinded(b: &mut Bencher) { let priv_key = get_key(); + let signing_key = rsa::pkcs1v15::SigningKey::::new_with_prefix(priv_key); let digest = Sha256::digest(b"testing").to_vec(); let mut rng = ChaCha8Rng::from_seed([42; 32]); b.iter(|| { - let res = priv_key - .sign_blinded( - &mut rng, - PaddingScheme::new_pkcs1v15_sign::(), - &digest, - ) - .unwrap(); + let res = signing_key.sign_with_rng(&mut rng, &digest); + test::black_box(res); + }); +} + +#[bench] +fn bench_rsa_2048_pss_sign_blinded(b: &mut Bencher) { + let priv_key = get_key(); + let signing_key = rsa::pss::SigningKey::::new(priv_key); + let digest = Sha256::digest(b"testing").to_vec(); + let mut rng = ChaCha8Rng::from_seed([42; 32]); + + b.iter(|| { + let res = signing_key.sign_with_rng(&mut rng, &digest); test::black_box(res); }); } diff --git a/src/key.rs b/src/key.rs index bebdeb8f..df706d99 100644 --- a/src/key.rs +++ b/src/key.rs @@ -15,7 +15,7 @@ use crate::errors::{Error, Result}; use crate::padding::PaddingScheme; use crate::raw::{DecryptionPrimitive, EncryptionPrimitive}; -use crate::{oaep, pkcs1v15, pss}; +use crate::{oaep, pkcs1v15}; /// Components of an RSA public key. pub trait PublicKeyParts { @@ -179,12 +179,6 @@ pub trait PublicKey: EncryptionPrimitive + PublicKeyParts { padding: PaddingScheme, msg: &[u8], ) -> Result>; - - /// Verify a signed message. - /// `hashed`must be the result of hashing the input using the hashing function - /// passed in through `hash`. - /// If the message is valid `Ok(())` is returned, otherwiese an `Err` indicating failure. - fn verify(&self, padding: PaddingScheme, hashed: &[u8], sig: &[u8]) -> Result<()>; } impl PublicKeyParts for RsaPublicKey { @@ -211,22 +205,6 @@ impl PublicKey for RsaPublicKey { mut mgf_digest, label, } => oaep::encrypt(rng, self, msg, &mut *digest, &mut *mgf_digest, label), - _ => Err(Error::InvalidPaddingScheme), - } - } - - fn verify(&self, padding: PaddingScheme, hashed: &[u8], sig: &[u8]) -> Result<()> { - match padding { - PaddingScheme::PKCS1v15Sign { hash_len, prefix } => { - if let Some(hash_len) = hash_len { - if hashed.len() != hash_len { - return Err(Error::InputNotHashed); - } - } - pkcs1v15::verify(self, prefix.as_ref(), hashed, sig) - } - PaddingScheme::PSS { mut digest, .. } => pss::verify(self, hashed, sig, &mut *digest), - _ => Err(Error::InvalidPaddingScheme), } } } @@ -466,7 +444,6 @@ impl RsaPrivateKey { &mut *mgf_digest, label, ), - _ => Err(Error::InvalidPaddingScheme), } } @@ -493,67 +470,6 @@ impl RsaPrivateKey { &mut *mgf_digest, label, ), - _ => Err(Error::InvalidPaddingScheme), - } - } - - /// Sign the given digest. - pub fn sign(&self, padding: PaddingScheme, digest_in: &[u8]) -> Result> { - match padding { - // need to pass any Rng as the type arg, so the type checker is happy, it is not actually used for anything - PaddingScheme::PKCS1v15Sign { hash_len, prefix } => { - if let Some(hash_len) = hash_len { - if digest_in.len() != hash_len { - return Err(Error::InputNotHashed); - } - } - pkcs1v15::sign::(None, self, prefix.as_ref(), digest_in) - } - _ => Err(Error::InvalidPaddingScheme), - } - } - - /// Sign the given digest using the provided rng - /// - /// Use `rng` for signature process. - pub fn sign_with_rng( - &self, - rng: &mut R, - padding: PaddingScheme, - digest_in: &[u8], - ) -> Result> { - match padding { - PaddingScheme::PSS { - mut digest, - salt_len, - } => pss::sign::(rng, false, self, digest_in, salt_len, &mut *digest), - _ => Err(Error::InvalidPaddingScheme), - } - } - - /// Sign the given digest. - /// - /// Use `rng` for blinding. - pub fn sign_blinded( - &self, - rng: &mut R, - padding: PaddingScheme, - digest_in: &[u8], - ) -> Result> { - match padding { - PaddingScheme::PKCS1v15Sign { hash_len, prefix } => { - if let Some(hash_len) = hash_len { - if digest_in.len() != hash_len { - return Err(Error::InputNotHashed); - } - } - pkcs1v15::sign(Some(rng), self, prefix.as_ref(), digest_in) - } - PaddingScheme::PSS { - mut digest, - salt_len, - } => pss::sign::(rng, true, self, digest_in, salt_len, &mut *digest), - _ => Err(Error::InvalidPaddingScheme), } } } diff --git a/src/padding.rs b/src/padding.rs index f2545143..7d632570 100644 --- a/src/padding.rs +++ b/src/padding.rs @@ -5,24 +5,12 @@ use alloc::string::{String, ToString}; use core::fmt; use digest::{Digest, DynDigest}; -use pkcs8::AssociatedOid; - -use crate::pkcs1v15; /// Available padding schemes. pub enum PaddingScheme { /// Encryption and Decryption using PKCS1v15 padding. PKCS1v15Encrypt, - /// Sign and Verify using PKCS1v15 padding. - PKCS1v15Sign { - /// Length of hash to use. - hash_len: Option, - - /// Prefix. - prefix: Box<[u8]>, - }, - /// Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1). /// /// - `digest` is used to hash the label. The maximum possible plaintext length is `m = k - 2 * h_len - 2`, @@ -44,32 +32,16 @@ pub enum PaddingScheme { /// Optional label. label: Option, }, - - /// Sign and Verify using PSS padding. - PSS { - /// Digest type to use. - digest: Box, - - /// Salt length. - salt_len: Option, - }, } impl fmt::Debug for PaddingScheme { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { PaddingScheme::PKCS1v15Encrypt => write!(f, "PaddingScheme::PKCS1v15Encrypt"), - PaddingScheme::PKCS1v15Sign { prefix, .. } => { - write!(f, "PaddingScheme::PKCS1v15Sign({:?})", prefix) - } PaddingScheme::OAEP { ref label, .. } => { // TODO: How to print the digest name? write!(f, "PaddingScheme::OAEP({:?})", label) } - PaddingScheme::PSS { ref salt_len, .. } => { - // TODO: How to print the digest name? - write!(f, "PaddingScheme::PSS(salt_len: {:?})", salt_len) - } } } } @@ -80,30 +52,6 @@ impl PaddingScheme { PaddingScheme::PKCS1v15Encrypt } - /// Create new PKCS#1 v1.5 padding for computing a raw signature. - /// - /// This sets `hash_len` to `None` and uses an empty `prefix`. - pub fn new_pkcs1v15_sign_raw() -> Self { - PaddingScheme::PKCS1v15Sign { - hash_len: None, - prefix: Box::new([]), - } - } - - /// Create new PKCS#1 v1.5 padding for the given digest. - /// - /// The digest must have an [`AssociatedOid`]. Make sure to enable the `oid` - /// feature of the relevant digest crate. - pub fn new_pkcs1v15_sign() -> Self - where - D: Digest + AssociatedOid, - { - PaddingScheme::PKCS1v15Sign { - hash_len: Some(::output_size()), - prefix: pkcs1v15::generate_prefix::().into_boxed_slice(), - } - } - /// Create a new OAEP `PaddingScheme`, using `T` as the hash function for the default (empty) label, and `U` as the hash function for MGF1. /// If a label is needed use `PaddingScheme::new_oaep_with_label` or `PaddingScheme::new_oaep_with_mgf_hash_with_label`. /// @@ -183,20 +131,4 @@ impl PaddingScheme { label: Some(label.as_ref().to_string()), } } - - /// New PSS padding for the given digest. - pub fn new_pss() -> Self { - PaddingScheme::PSS { - digest: Box::new(T::new()), - salt_len: None, - } - } - - /// New PSS padding for the given digest with a salt value of the given length. - pub fn new_pss_with_salt(len: usize) -> Self { - PaddingScheme::PSS { - digest: Box::new(T::new()), - salt_len: Some(len), - } - } } diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index e8fe3d78..3d3baee6 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -629,7 +629,7 @@ mod tests { use sha3::Sha3_256; use signature::{RandomizedSigner, Signer, Verifier}; - use crate::{PaddingScheme, PublicKey, PublicKeyParts, RsaPrivateKey, RsaPublicKey}; + use crate::{PaddingScheme, PublicKeyParts, RsaPrivateKey, RsaPublicKey}; #[test] fn test_non_zero_bytes() { @@ -719,39 +719,6 @@ mod tests { } } - #[test] - fn test_sign_pkcs1v15() { - 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 out = priv_key - .sign(PaddingScheme::new_pkcs1v15_sign::(), &digest) - .unwrap(); - assert_ne!(out, digest); - assert_eq!(out, expected); - - let mut rng = ChaCha8Rng::from_seed([42; 32]); - let out2 = priv_key - .sign_blinded( - &mut rng, - PaddingScheme::new_pkcs1v15_sign::(), - &digest, - ) - .unwrap(); - assert_eq!(out2, expected); - } - } - #[test] fn test_sign_pkcs1v15_signer() { let priv_key = get_private_key(); @@ -858,43 +825,6 @@ mod tests { } } - #[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, expected) in &tests { - let digest = Sha1::digest(text.as_bytes()).to_vec(); - - let result = pub_key.verify(PaddingScheme::new_pkcs1v15_sign::(), &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(); @@ -972,22 +902,6 @@ mod tests { } } } - #[test] - fn test_unpadded_signature() { - 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 sig = priv_key - .sign(PaddingScheme::new_pkcs1v15_sign_raw(), msg) - .unwrap(); - assert_eq!(expected_sig, sig); - - let pub_key: RsaPublicKey = priv_key.into(); - pub_key - .verify(PaddingScheme::new_pkcs1v15_sign_raw(), msg, &sig) - .expect("failed to verify"); - } #[test] fn test_unpadded_signature_hazmat() { diff --git a/src/pss.rs b/src/pss.rs index 104c5e0b..f9a54243 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -14,7 +14,7 @@ use alloc::vec::Vec; use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex}; use core::marker::PhantomData; -use digest::{Digest, DynDigest, FixedOutputReset}; +use digest::{Digest, FixedOutputReset}; use pkcs8::{Document, EncodePrivateKey, EncodePublicKey, SecretDocument}; use rand_core::CryptoRngCore; use signature::{ @@ -23,7 +23,7 @@ use signature::{ }; use subtle::ConstantTimeEq; -use crate::algorithms::{mgf1_xor, mgf1_xor_digest}; +use crate::algorithms::mgf1_xor_digest; use crate::errors::{Error, Result}; use crate::key::{PrivateKey, PublicKey}; use crate::{RsaPrivateKey, RsaPublicKey}; @@ -98,23 +98,6 @@ impl Display for Signature { } } -pub(crate) fn verify( - pub_key: &PK, - hashed: &[u8], - sig: &[u8], - digest: &mut dyn DynDigest, -) -> Result<()> { - 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(hashed, &mut em, em_bits, None, digest) -} - pub(crate) fn verify_digest(pub_key: &PK, hashed: &[u8], sig: &[u8]) -> Result<()> where PK: PublicKey, @@ -131,24 +114,6 @@ where 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 -/// defaults are used. -pub(crate) fn sign( - 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.output_size()); - - sign_pss_with_salt(blind.then(|| rng), priv_key, hashed, &salt, digest) -} - pub(crate) fn sign_digest< T: CryptoRngCore + ?Sized, SK: PrivateKey, @@ -184,19 +149,6 @@ fn generate_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( - blind_rng: Option<&mut T>, - priv_key: &SK, - hashed: &[u8], - salt: &[u8], - digest: &mut dyn DynDigest, -) -> Result> { - let em_bits = priv_key.n().bits() - 1; - let em = emsa_pss_encode(hashed, em_bits, salt, digest)?; - - priv_key.raw_decryption_primitive(blind_rng, &em, priv_key.size()) -} - fn sign_pss_with_salt_digest< T: CryptoRngCore + ?Sized, SK: PrivateKey, @@ -213,79 +165,6 @@ fn sign_pss_with_salt_digest< priv_key.raw_decryption_primitive(blind_rng, &em, priv_key.size()) } -fn emsa_pss_encode( - m_hash: &[u8], - em_bits: usize, - salt: &[u8], - hash: &mut dyn DynDigest, -) -> Result> { - // See [1], section 9.1.1 - let h_len = hash.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]; - - hash.update(&prefix); - hash.update(m_hash); - hash.update(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(db, 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_encode_digest(m_hash: &[u8], em_bits: usize, salt: &[u8]) -> Result> where D: Digest + FixedOutputReset, @@ -438,50 +317,6 @@ fn emsa_pss_get_salt( 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 - // initial zero octets. - // - // 13. Let H' = Hash(M'), an octet string of length hLen. - let prefix = [0u8; 8]; - - hash.update(&prefix[..]); - hash.update(m_hash); - hash.update(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) - } -} - fn emsa_pss_verify_digest( m_hash: &[u8], em: &mut [u8], @@ -921,7 +756,7 @@ where #[cfg(test)] mod test { use crate::pss::{BlindedSigningKey, Signature, SigningKey, VerifyingKey}; - use crate::{PaddingScheme, PublicKey, RsaPrivateKey, RsaPublicKey}; + use crate::{RsaPrivateKey, RsaPublicKey}; use hex_literal::hex; use num_bigint::BigUint; @@ -954,42 +789,6 @@ mod test { ).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(PaddingScheme::new_pss::(), &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(); @@ -1068,44 +867,6 @@ mod test { } } - #[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(), PaddingScheme::new_pss::(), &digest) - .expect("failed to sign"); - - priv_key - .verify(PaddingScheme::new_pss::(), &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_blinded(&mut rng.clone(), PaddingScheme::new_pss::(), &digest) - .expect("failed to sign"); - - priv_key - .verify(PaddingScheme::new_pss::(), &digest, &sig) - .expect("failed to verify"); - } - } - #[test] fn test_sign_and_verify_roundtrip_signer() { let priv_key = get_private_key(); From 009519b08439b6855f07ba23bcdc24a68c394440 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Fri, 6 Jan 2023 08:19:06 +0200 Subject: [PATCH 2/6] feat: relax Sized requirement for random source parameters Signed-off-by: Dmitry Baryshkov --- src/oaep.rs | 6 +++--- src/pkcs1v15.rs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/oaep.rs b/src/oaep.rs index 82d55c11..ccd909a4 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -23,7 +23,7 @@ const MAX_LABEL_LEN: u64 = 2_305_843_009_213_693_951; /// /// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 #[inline] -pub fn encrypt( +pub fn encrypt( rng: &mut R, pub_key: &K, msg: &[u8], @@ -80,7 +80,7 @@ pub fn encrypt( /// /// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 #[inline] -pub fn decrypt( +pub fn decrypt( rng: Option<&mut R>, priv_key: &SK, ciphertext: &[u8], @@ -104,7 +104,7 @@ pub fn decrypt( /// `rng` is given. It returns one or zero in valid that indicates whether the /// plaintext was correctly structured. #[inline] -fn decrypt_inner( +fn decrypt_inner( rng: Option<&mut R>, priv_key: &SK, ciphertext: &[u8], diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index 3d3baee6..fe8888df 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -100,7 +100,7 @@ impl Display for Signature { /// scheme from PKCS#1 v1.5. The message must be no longer than the /// length of the public modulus minus 11 bytes. #[inline] -pub(crate) fn encrypt( +pub(crate) fn encrypt( rng: &mut R, pub_key: &PK, msg: &[u8], @@ -132,7 +132,7 @@ pub(crate) fn encrypt( /// forge signatures as if they had the private key. See /// `decrypt_session_key` for a way of solving this problem. #[inline] -pub(crate) fn decrypt( +pub(crate) fn decrypt( rng: Option<&mut R>, priv_key: &SK, ciphertext: &[u8], @@ -249,7 +249,7 @@ where /// in order to maintain constant memory access patterns. If the plaintext was /// valid then index contains the index of the original message in em. #[inline] -fn decrypt_inner( +fn decrypt_inner( rng: Option<&mut R>, priv_key: &SK, ciphertext: &[u8], @@ -294,7 +294,7 @@ fn decrypt_inner( /// Fills the provided slice with random values, which are guaranteed /// to not be zero. #[inline] -fn non_zero_random_bytes(rng: &mut R, data: &mut [u8]) { +fn non_zero_random_bytes(rng: &mut R, data: &mut [u8]) { rng.fill_bytes(data); for el in data { From 68837941c05b5ed04f022c8a271dc13ee9213dfe Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Fri, 6 Jan 2023 08:16:30 +0200 Subject: [PATCH 3/6] feat: traits: add traits for encryption and decryption Add traits following the signature design for encryption and decryption. Signed-off-by: Dmitry Baryshkov --- src/lib.rs | 1 + src/traits.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/traits.rs diff --git a/src/lib.rs b/src/lib.rs index c9e690b0..67b98251 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -216,6 +216,7 @@ pub mod algorithms; pub mod errors; pub mod pkcs1v15; pub mod pss; +pub mod traits; mod dummy_rng; mod encoding; diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 00000000..b4a94011 --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,32 @@ +//! Generic traits for message encryption and decryption + +use alloc::vec::Vec; +use rand_core::CryptoRngCore; + +use crate::errors::Result; + +/// Encrypt the message using provided random source +pub trait RandomizedEncryptor { + /// Encrypt the given message. + fn encrypt_with_rng( + &self, + rng: &mut R, + msg: &[u8], + ) -> Result>; +} + +/// Decrypt the given message +pub trait Decryptor { + /// Decrypt the given message. + fn decrypt(&self, ciphertext: &[u8]) -> Result>; +} + +/// Decrypt the given message using provided random source +pub trait RandomizedDecryptor { + /// Decrypt the given message. + fn decrypt_with_rng( + &self, + rng: &mut R, + ciphertext: &[u8], + ) -> Result>; +} From 21ed959b4299d6e5db3f3b8813491456541215a3 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Fri, 6 Jan 2023 08:21:17 +0200 Subject: [PATCH 4/6] feat: switch to new encryption API Drop encryption/decryption from the RsaPublicKey/RsaPrivateKey structs, add new EncryptingKey and DecryptingKey structs implementing Encryptor / Decryptor traits. Signed-off-by: Dmitry Baryshkov --- benches/key.rs | 8 +- src/key.rs | 173 +++++++----------------------------------- src/lib.rs | 26 ++++--- src/oaep.rs | 195 +++++++++++++++++++++++++++++++++++++++++------- src/pkcs1v15.rs | 67 +++++++++++++++-- 5 files changed, 277 insertions(+), 192 deletions(-) diff --git a/benches/key.rs b/benches/key.rs index ece0887a..c6f3e8d8 100644 --- a/benches/key.rs +++ b/benches/key.rs @@ -7,7 +7,8 @@ use num_bigint::BigUint; use num_traits::{FromPrimitive, Num}; use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; use rsa::signature::RandomizedSigner; -use rsa::{PaddingScheme, RsaPrivateKey}; +use rsa::traits::Decryptor; +use rsa::RsaPrivateKey; use sha2::{Digest, Sha256}; use test::Bencher; @@ -29,12 +30,11 @@ fn get_key() -> RsaPrivateKey { #[bench] fn bench_rsa_2048_pkcsv1_decrypt(b: &mut Bencher) { let priv_key = get_key(); + let decryption_key = rsa::pkcs1v15::DecryptingKey::new(priv_key); let x = Base64::decode_vec(DECRYPT_VAL).unwrap(); b.iter(|| { - let res = priv_key - .decrypt(PaddingScheme::new_pkcs1v15_encrypt(), &x) - .unwrap(); + let res = decryption_key.decrypt(&x).unwrap(); test::black_box(res); }); } diff --git a/src/key.rs b/src/key.rs index df706d99..318f129c 100644 --- a/src/key.rs +++ b/src/key.rs @@ -10,12 +10,9 @@ 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; use crate::raw::{DecryptionPrimitive, EncryptionPrimitive}; -use crate::{oaep, pkcs1v15}; /// Components of an RSA public key. pub trait PublicKeyParts { @@ -171,15 +168,7 @@ impl From<&RsaPrivateKey> for RsaPublicKey { } /// Generic trait for operations on a public key. -pub trait PublicKey: EncryptionPrimitive + PublicKeyParts { - /// Encrypt the given message. - fn encrypt( - &self, - rng: &mut R, - padding: PaddingScheme, - msg: &[u8], - ) -> Result>; -} +pub trait PublicKey: EncryptionPrimitive + PublicKeyParts {} impl PublicKeyParts for RsaPublicKey { fn n(&self) -> &BigUint { @@ -191,23 +180,7 @@ impl PublicKeyParts for RsaPublicKey { } } -impl PublicKey for RsaPublicKey { - fn encrypt( - &self, - rng: &mut R, - padding: PaddingScheme, - msg: &[u8], - ) -> Result> { - match padding { - PaddingScheme::PKCS1v15Encrypt => pkcs1v15::encrypt(rng, self, msg), - PaddingScheme::OAEP { - mut digest, - mut mgf_digest, - label, - } => oaep::encrypt(rng, self, msg, &mut *digest, &mut *mgf_digest, label), - } - } -} +impl PublicKey for RsaPublicKey {} impl RsaPublicKey { /// Minimum value of the public exponent `e`. @@ -424,54 +397,6 @@ impl RsaPrivateKey { Ok(()) } - - /// Decrypt the given message. - pub fn decrypt(&self, padding: PaddingScheme, ciphertext: &[u8]) -> Result> { - match padding { - // need to pass any Rng as the type arg, so the type checker is happy, it is not actually used for anything - PaddingScheme::PKCS1v15Encrypt => { - pkcs1v15::decrypt::(None, self, ciphertext) - } - PaddingScheme::OAEP { - mut digest, - mut mgf_digest, - label, - } => oaep::decrypt::( - None, - self, - ciphertext, - &mut *digest, - &mut *mgf_digest, - label, - ), - } - } - - /// Decrypt the given message. - /// - /// Uses `rng` to blind the decryption process. - pub fn decrypt_blinded( - &self, - rng: &mut R, - padding: PaddingScheme, - ciphertext: &[u8], - ) -> Result> { - match padding { - PaddingScheme::PKCS1v15Encrypt => pkcs1v15::decrypt(Some(rng), self, ciphertext), - PaddingScheme::OAEP { - mut digest, - mut mgf_digest, - label, - } => oaep::decrypt( - Some(rng), - self, - ciphertext, - &mut *digest, - &mut *mgf_digest, - label, - ), - } - } } /// Check that the public key is well formed and has an exponent within acceptable bounds. @@ -507,9 +432,11 @@ fn check_public_with_max_size(public_key: &impl PublicKeyParts, max_size: usize) mod tests { use super::*; use crate::internals; + use crate::oaep::{DecryptingKey, EncryptingKey}; + use crate::traits::{Decryptor, RandomizedDecryptor, RandomizedEncryptor}; use alloc::string::String; - use digest::{Digest, DynDigest}; + use digest::{Digest, FixedOutputReset}; use hex_literal::hex; use num_traits::{FromPrimitive, ToPrimitive}; use rand_chacha::{ @@ -862,55 +789,11 @@ mod tests { } } - fn do_test_encrypt_decrypt_oaep( - prk: &RsaPrivateKey, - ) { - let mut rng = ChaCha8Rng::from_seed([42; 32]); - - let k = prk.size(); - - for i in 1..8 { - let mut input = vec![0u8; i * 8]; - rng.fill_bytes(&mut input); - - if input.len() > k - 11 { - input = input[0..k - 11].to_vec(); - } - let label = get_label(&mut rng); - - let pub_key: RsaPublicKey = prk.into(); - - let ciphertext = if let Some(ref label) = label { - let padding = PaddingScheme::new_oaep_with_label::(label); - pub_key.encrypt(&mut rng, padding, &input).unwrap() - } else { - let padding = PaddingScheme::new_oaep::(); - pub_key.encrypt(&mut rng, padding, &input).unwrap() - }; - - assert_ne!(input, ciphertext); - let blind: bool = rng.next_u32() < (1 << 31); - - let padding = if let Some(ref label) = label { - PaddingScheme::new_oaep_with_label::(label) - } else { - PaddingScheme::new_oaep::() - }; - - let plaintext = if blind { - prk.decrypt(padding, &ciphertext).unwrap() - } else { - prk.decrypt_blinded(&mut rng, padding, &ciphertext).unwrap() - }; - - assert_eq!(input, plaintext); - } + fn do_test_encrypt_decrypt_oaep(prk: &RsaPrivateKey) { + do_test_oaep_with_different_hashes::(prk); } - fn do_test_oaep_with_different_hashes< - D: 'static + Digest + DynDigest + Send + Sync, - U: 'static + Digest + DynDigest + Send + Sync, - >( + fn do_test_oaep_with_different_hashes( prk: &RsaPrivateKey, ) { let mut rng = ChaCha8Rng::from_seed([42; 32]); @@ -929,50 +812,48 @@ mod tests { let pub_key: RsaPublicKey = prk.into(); let ciphertext = if let Some(ref label) = label { - let padding = PaddingScheme::new_oaep_with_mgf_hash_with_label::(label); - pub_key.encrypt(&mut rng, padding, &input).unwrap() + let encrypting_key = + EncryptingKey::::new_with_label(pub_key, label.clone()); + encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap() } else { - let padding = PaddingScheme::new_oaep_with_mgf_hash::(); - pub_key.encrypt(&mut rng, padding, &input).unwrap() + let encrypting_key = EncryptingKey::::new(pub_key); + encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap() }; assert_ne!(input, ciphertext); let blind: bool = rng.next_u32() < (1 << 31); - let padding = if let Some(ref label) = label { - PaddingScheme::new_oaep_with_mgf_hash_with_label::(label) + let decrypting_key = if let Some(ref label) = label { + DecryptingKey::::new_with_label(prk.clone(), label.clone()) } else { - PaddingScheme::new_oaep_with_mgf_hash::() + DecryptingKey::::new(prk.clone()) }; let plaintext = if blind { - prk.decrypt(padding, &ciphertext).unwrap() + decrypting_key.decrypt(&ciphertext).unwrap() } else { - prk.decrypt_blinded(&mut rng, padding, &ciphertext).unwrap() + decrypting_key + .decrypt_with_rng(&mut rng, &ciphertext) + .unwrap() }; assert_eq!(input, plaintext); } } + #[test] fn test_decrypt_oaep_invalid_hash() { let mut rng = ChaCha8Rng::from_seed([42; 32]); let priv_key = get_private_key(); let pub_key: RsaPublicKey = (&priv_key).into(); - let ciphertext = pub_key - .encrypt( - &mut rng, - PaddingScheme::new_oaep::(), - "a_plain_text".as_bytes(), - ) + let encrypting_key = EncryptingKey::::new(pub_key); + let decrypting_key = DecryptingKey::::new_with_label(priv_key, "label"); + let ciphertext = encrypting_key + .encrypt_with_rng(&mut rng, "a_plain_text".as_bytes()) .unwrap(); assert!( - priv_key - .decrypt_blinded( - &mut rng, - PaddingScheme::new_oaep_with_label::("label"), - &ciphertext, - ) + decrypting_key + .decrypt_with_rng(&mut rng, &ciphertext,) .is_err(), "decrypt should have failed on hash verification" ); diff --git a/src/lib.rs b/src/lib.rs index 67b98251..e572f3d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,9 @@ //! //! ## PKCS#1 v1.5 encryption //! ``` -//! use rsa::{PublicKey, RsaPrivateKey, RsaPublicKey, PaddingScheme}; +//! use rsa::{RsaPrivateKey, RsaPublicKey}; +//! use rsa::pkcs1v15::{DecryptingKey, EncryptingKey}; +//! use rsa::traits::{RandomizedEncryptor, Decryptor}; //! //! let mut rng = rand::thread_rng(); //! @@ -23,35 +25,37 @@ //! //! // Encrypt //! let data = b"hello world"; -//! let padding = PaddingScheme::new_pkcs1v15_encrypt(); -//! let enc_data = public_key.encrypt(&mut rng, padding, &data[..]).expect("failed to encrypt"); +//! let encrypting_key = EncryptingKey::new(public_key); +//! let enc_data = encrypting_key.encrypt_with_rng(&mut rng, &data[..]).expect("failed to encrypt"); //! assert_ne!(&data[..], &enc_data[..]); //! //! // Decrypt -//! let padding = PaddingScheme::new_pkcs1v15_encrypt(); -//! let dec_data = private_key.decrypt(padding, &enc_data).expect("failed to decrypt"); +//! let decrypting_key = DecryptingKey::new(private_key); +//! let dec_data = decrypting_key.decrypt(&enc_data).expect("failed to decrypt"); //! assert_eq!(&data[..], &dec_data[..]); //! ``` //! //! ## OAEP encryption //! ``` -//! use rsa::{PublicKey, RsaPrivateKey, RsaPublicKey, PaddingScheme}; +//! use rsa::{PublicKey, RsaPrivateKey, RsaPublicKey}; +//! use rsa::oaep::{DecryptingKey, EncryptingKey}; +//! use rsa::traits::{RandomizedEncryptor, Decryptor}; //! //! let mut rng = rand::thread_rng(); //! //! let bits = 2048; //! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); //! let public_key = RsaPublicKey::from(&private_key); +//! let encrypting_key = EncryptingKey::::new(public_key); +//! let decrypting_key = DecryptingKey::::new(private_key); //! //! // Encrypt //! let data = b"hello world"; -//! let padding = PaddingScheme::new_oaep::(); -//! let enc_data = public_key.encrypt(&mut rng, padding, &data[..]).expect("failed to encrypt"); +//! let enc_data = encrypting_key.encrypt_with_rng(&mut rng, &data[..]).expect("failed to encrypt"); //! assert_ne!(&data[..], &enc_data[..]); //! //! // Decrypt -//! let padding = PaddingScheme::new_oaep::(); -//! let dec_data = private_key.decrypt(padding, &enc_data).expect("failed to decrypt"); +//! let dec_data = decrypting_key.decrypt(&enc_data).expect("failed to decrypt"); //! assert_eq!(&data[..], &dec_data[..]); //! ``` //! @@ -214,6 +218,7 @@ pub use signature; pub mod algorithms; pub mod errors; +pub mod oaep; pub mod pkcs1v15; pub mod pss; pub mod traits; @@ -221,7 +226,6 @@ pub mod traits; mod dummy_rng; mod encoding; mod key; -mod oaep; mod padding; mod raw; diff --git a/src/oaep.rs b/src/oaep.rs index ccd909a4..6310f773 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -1,15 +1,27 @@ -use alloc::string::String; +//! PKCS#1 OAEP support as described in [RFC8017 § 7.1]. +//! +//! # Usage +//! +//! See [code example in the toplevel rustdoc](../index.html#oaep-encryption). +//! +//! [RFC8017 § 7.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 + +use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; +use core::marker::PhantomData; use rand_core::CryptoRngCore; -use digest::DynDigest; +use digest::{Digest, FixedOutputReset}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; use zeroize::Zeroizing; -use crate::algorithms::mgf1_xor; +use crate::algorithms::mgf1_xor_digest; +use crate::dummy_rng::DummyRng; use crate::errors::{Error, Result}; use crate::key::{self, PrivateKey, PublicKey}; +use crate::traits::{Decryptor, RandomizedDecryptor, RandomizedEncryptor}; +use crate::{RsaPrivateKey, RsaPublicKey}; // 2**61 -1 (pow is not const yet) // TODO: This is the maximum for SHA-1, unclear from the RFC what the values are for other hashing functions. @@ -23,19 +35,22 @@ const MAX_LABEL_LEN: u64 = 2_305_843_009_213_693_951; /// /// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 #[inline] -pub fn encrypt( +pub fn encrypt< + R: CryptoRngCore + ?Sized, + K: PublicKey, + D: Digest, + MGD: Digest + FixedOutputReset, +>( rng: &mut R, pub_key: &K, msg: &[u8], - digest: &mut dyn DynDigest, - mgf_digest: &mut dyn DynDigest, label: Option, ) -> Result> { key::check_public(pub_key)?; let k = pub_key.size(); - let h_size = digest.output_size(); + let h_size = ::output_size(); if msg.len() + 2 * h_size + 2 > k { return Err(Error::MessageTooLong); @@ -55,14 +70,14 @@ pub fn encrypt( // Data block DB = pHash || PS || 01 || M let db_len = k - h_size - 1; - digest.update(label.as_bytes()); - let p_hash = digest.finalize_reset(); + let p_hash = D::digest(label.as_bytes()); db[0..h_size].copy_from_slice(&*p_hash); db[db_len - msg.len() - 1] = 1; db[db_len - msg.len()..].copy_from_slice(msg); - mgf1_xor(db, mgf_digest, seed); - mgf1_xor(seed, mgf_digest, db); + let mut mgf_digest = MGD::new(); + mgf1_xor_digest(db, &mut mgf_digest, seed); + mgf1_xor_digest(seed, &mut mgf_digest, db); pub_key.raw_encryption_primitive(&em, pub_key.size()) } @@ -80,17 +95,20 @@ pub fn encrypt( /// /// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 #[inline] -pub fn decrypt( +pub fn decrypt< + R: CryptoRngCore + ?Sized, + SK: PrivateKey, + D: Digest, + MGD: Digest + FixedOutputReset, +>( rng: Option<&mut R>, priv_key: &SK, ciphertext: &[u8], - digest: &mut dyn DynDigest, - mgf_digest: &mut dyn DynDigest, label: Option, ) -> Result> { key::check_public(priv_key)?; - let res = decrypt_inner(rng, priv_key, ciphertext, digest, mgf_digest, label)?; + let res = decrypt_inner::<_, _, D, MGD>(rng, priv_key, ciphertext, label)?; if res.is_none().into() { return Err(Error::Decryption); } @@ -104,12 +122,15 @@ pub fn decrypt( /// `rng` is given. It returns one or zero in valid that indicates whether the /// plaintext was correctly structured. #[inline] -fn decrypt_inner( +fn decrypt_inner< + R: CryptoRngCore + ?Sized, + SK: PrivateKey, + D: Digest, + MGD: Digest + FixedOutputReset, +>( rng: Option<&mut R>, priv_key: &SK, ciphertext: &[u8], - digest: &mut dyn DynDigest, - mgf_digest: &mut dyn DynDigest, label: Option, ) -> Result, u32)>> { let k = priv_key.size(); @@ -117,7 +138,7 @@ fn decrypt_inner( return Err(Error::Decryption); } - let h_size = digest.output_size(); + let h_size = ::output_size(); if ciphertext.len() != k || k < h_size * 2 + 2 { return Err(Error::Decryption); @@ -130,19 +151,18 @@ fn decrypt_inner( return Err(Error::LabelTooLong); } - digest.update(label.as_bytes()); - - let expected_p_hash = &*digest.finalize_reset(); + let expected_p_hash = D::digest(label.as_bytes()); let first_byte_is_zero = em[0].ct_eq(&0u8); let (_, payload) = em.split_at_mut(1); let (seed, db) = payload.split_at_mut(h_size); - mgf1_xor(seed, mgf_digest, db); - mgf1_xor(db, mgf_digest, seed); + let mut mgf_digest = MGD::new(); + mgf1_xor_digest(seed, &mut mgf_digest, db); + mgf1_xor_digest(db, &mut mgf_digest, seed); - let hash_are_equal = db[0..h_size].ct_eq(expected_p_hash); + let hash_are_equal = db[0..h_size].ct_eq(expected_p_hash.as_slice()); // The remainder of the plaintext must be zero or more 0x00, followed // by 0x01, followed by the message. @@ -165,3 +185,128 @@ fn decrypt_inner( Ok(CtOption::new((em, index + 2 + (h_size * 2) as u32), valid)) } + +/// Encryption key for PKCS#1 v1.5 encryption as described in [RFC8017 § 7.1]. +/// +/// [RFC8017 § 7.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 +#[derive(Debug, Clone)] +pub struct EncryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + inner: RsaPublicKey, + label: Option, + phantom: PhantomData, + mg_phantom: PhantomData, +} + +impl EncryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + /// Create a new verifying key from an RSA public key. + pub fn new(key: RsaPublicKey) -> Self { + Self { + inner: key, + label: None, + phantom: Default::default(), + mg_phantom: Default::default(), + } + } + + /// Create a new verifying key from an RSA public key using provided label + pub fn new_with_label>(key: RsaPublicKey, label: S) -> Self { + Self { + inner: key, + label: Some(label.as_ref().to_string()), + phantom: Default::default(), + mg_phantom: Default::default(), + } + } +} + +impl RandomizedEncryptor for EncryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + fn encrypt_with_rng( + &self, + rng: &mut R, + msg: &[u8], + ) -> Result> { + encrypt::<_, _, D, MGD>(rng, &self.inner, msg, self.label.as_ref().cloned()) + } +} + +/// Decryption key for PKCS#1 v1.5 decryption as described in [RFC8017 § 7.1]. +/// +/// [RFC8017 § 7.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 +#[derive(Debug, Clone)] +pub struct DecryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + inner: RsaPrivateKey, + label: Option, + phantom: PhantomData, + mg_phantom: PhantomData, +} + +impl DecryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + /// Create a new verifying key from an RSA public key. + pub fn new(key: RsaPrivateKey) -> Self { + Self { + inner: key, + label: None, + phantom: Default::default(), + mg_phantom: Default::default(), + } + } + + /// Create a new verifying key from an RSA public key using provided label + pub fn new_with_label>(key: RsaPrivateKey, label: S) -> Self { + Self { + inner: key, + label: Some(label.as_ref().to_string()), + phantom: Default::default(), + mg_phantom: Default::default(), + } + } +} + +impl Decryptor for DecryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + fn decrypt(&self, ciphertext: &[u8]) -> Result> { + decrypt::(None, &self.inner, ciphertext, self.label.as_ref().cloned()) + } +} + +impl RandomizedDecryptor for DecryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + fn decrypt_with_rng( + &self, + rng: &mut R, + ciphertext: &[u8], + ) -> Result> { + decrypt::<_, _, D, MGD>( + Some(rng), + &self.inner, + ciphertext, + self.label.as_ref().cloned(), + ) + } +} diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index fe8888df..c149310f 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -24,6 +24,7 @@ use zeroize::Zeroizing; use crate::dummy_rng::DummyRng; use crate::errors::{Error, Result}; use crate::key::{self, PrivateKey, PublicKey}; +use crate::traits::{Decryptor, RandomizedDecryptor, RandomizedEncryptor}; use crate::{RsaPrivateKey, RsaPublicKey}; /// PKCS#1 v1.5 signatures as described in [RFC8017 § 8.2]. @@ -612,6 +613,62 @@ where } } +/// Encryption key for PKCS#1 v1.5 encryption as described in [RFC8017 § 7.2]. +/// +/// [RFC8017 § 7.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.2 +#[derive(Debug, Clone)] +pub struct EncryptingKey { + inner: RsaPublicKey, +} + +impl EncryptingKey { + /// Create a new verifying key from an RSA public key. + pub fn new(key: RsaPublicKey) -> Self { + Self { inner: key } + } +} + +impl RandomizedEncryptor for EncryptingKey { + fn encrypt_with_rng( + &self, + rng: &mut R, + msg: &[u8], + ) -> Result> { + encrypt(rng, &self.inner, msg) + } +} + +/// Decryption key for PKCS#1 v1.5 decryption as described in [RFC8017 § 7.2]. +/// +/// [RFC8017 § 7.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.2 +#[derive(Debug, Clone)] +pub struct DecryptingKey { + inner: RsaPrivateKey, +} + +impl DecryptingKey { + /// Create a new verifying key from an RSA public key. + pub fn new(key: RsaPrivateKey) -> Self { + Self { inner: key } + } +} + +impl Decryptor for DecryptingKey { + fn decrypt(&self, ciphertext: &[u8]) -> Result> { + decrypt::(None, &self.inner, ciphertext) + } +} + +impl RandomizedDecryptor for DecryptingKey { + fn decrypt_with_rng( + &self, + rng: &mut R, + ciphertext: &[u8], + ) -> Result> { + decrypt(Some(rng), &self.inner, ciphertext) + } +} + #[cfg(test)] mod tests { use super::*; @@ -629,7 +686,7 @@ mod tests { use sha3::Sha3_256; use signature::{RandomizedSigner, Signer, Verifier}; - use crate::{PaddingScheme, PublicKeyParts, RsaPrivateKey, RsaPublicKey}; + use crate::{PublicKeyParts, RsaPrivateKey, RsaPublicKey}; #[test] fn test_non_zero_bytes() { @@ -669,6 +726,7 @@ mod tests { #[test] fn test_decrypt_pkcs1v15() { let priv_key = get_private_key(); + let decrypting_key = DecryptingKey::new(priv_key); let tests = [[ "gIcUIoVkD6ATMBk/u/nlCZCCWRKdkfjCgFdo35VpRXLduiKXhNz1XupLLzTXAybEq15juc+EgY5o0DHv/nt3yg==", @@ -685,11 +743,8 @@ mod tests { ]]; for test in &tests { - let out = priv_key - .decrypt( - PaddingScheme::new_pkcs1v15_encrypt(), - &Base64::decode_vec(test[0]).unwrap(), - ) + let out = decrypting_key + .decrypt(&Base64::decode_vec(test[0]).unwrap()) .unwrap(); assert_eq!(out, test[1].as_bytes()); } From cf49221f6f4938011dbdf650e15f42ee16a3ff8d Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Fri, 6 Jan 2023 08:23:31 +0200 Subject: [PATCH 5/6] feat: drop PaddingScheme The crate doesn't use anymore the PaddingScheme. Drop corresponding API. Signed-off-by: Dmitry Baryshkov --- src/lib.rs | 2 - src/padding.rs | 134 ------------------------------------------------- 2 files changed, 136 deletions(-) delete mode 100644 src/padding.rs diff --git a/src/lib.rs b/src/lib.rs index e572f3d9..8bcb728a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -226,14 +226,12 @@ pub mod traits; mod dummy_rng; mod encoding; mod key; -mod padding; mod raw; pub use pkcs1; pub use pkcs8; pub use self::key::{PublicKey, PublicKeyParts, RsaPrivateKey, RsaPublicKey}; -pub use self::padding::PaddingScheme; /// Internal raw RSA functions. #[cfg(not(feature = "expose-internals"))] diff --git a/src/padding.rs b/src/padding.rs deleted file mode 100644 index 7d632570..00000000 --- a/src/padding.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! Supported padding schemes. - -use alloc::boxed::Box; -use alloc::string::{String, ToString}; -use core::fmt; - -use digest::{Digest, DynDigest}; - -/// Available padding schemes. -pub enum PaddingScheme { - /// Encryption and Decryption using PKCS1v15 padding. - PKCS1v15Encrypt, - - /// Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1). - /// - /// - `digest` is used to hash the label. The maximum possible plaintext length is `m = k - 2 * h_len - 2`, - /// where `k` is the size of the RSA modulus. - /// - `mgf_digest` specifies the hash function that is used in the [MGF1](https://datatracker.ietf.org/doc/html/rfc8017#appendix-B.2). - /// - `label` is optional data that can be associated with the message. - /// - /// The two hash functions can, but don't need to be the same. - /// - /// A prominent example is the [`AndroidKeyStore`](https://developer.android.com/guide/topics/security/cryptography#oaep-mgf1-digest). - /// It uses SHA-1 for `mgf_digest` and a user-chosen SHA flavour for `digest`. - OAEP { - /// Digest type to use. - digest: Box, - - /// Digest to use for Mask Generation Function (MGF). - mgf_digest: Box, - - /// Optional label. - label: Option, - }, -} - -impl fmt::Debug for PaddingScheme { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PaddingScheme::PKCS1v15Encrypt => write!(f, "PaddingScheme::PKCS1v15Encrypt"), - PaddingScheme::OAEP { ref label, .. } => { - // TODO: How to print the digest name? - write!(f, "PaddingScheme::OAEP({:?})", label) - } - } - } -} - -impl PaddingScheme { - /// Create new PKCS#1 v1.5 encryption padding. - pub fn new_pkcs1v15_encrypt() -> Self { - PaddingScheme::PKCS1v15Encrypt - } - - /// Create a new OAEP `PaddingScheme`, using `T` as the hash function for the default (empty) label, and `U` as the hash function for MGF1. - /// If a label is needed use `PaddingScheme::new_oaep_with_label` or `PaddingScheme::new_oaep_with_mgf_hash_with_label`. - /// - /// # Example - /// ``` - /// use sha1::Sha1; - /// use sha2::Sha256; - /// use rsa::{BigUint, RsaPublicKey, PaddingScheme, PublicKey}; - /// use base64ct::{Base64, Encoding}; - /// - /// let n = Base64::decode_vec("ALHgDoZmBQIx+jTmgeeHW6KsPOrj11f6CvWsiRleJlQpW77AwSZhd21ZDmlTKfaIHBSUxRUsuYNh7E2SHx8rkFVCQA2/gXkZ5GK2IUbzSTio9qXA25MWHvVxjMfKSL8ZAxZyKbrG94FLLszFAFOaiLLY8ECs7g+dXOriYtBwLUJK+lppbd+El+8ZA/zH0bk7vbqph5pIoiWggxwdq3mEz4LnrUln7r6dagSQzYErKewY8GADVpXcq5mfHC1xF2DFBub7bFjMVM5fHq7RK+pG5xjNDiYITbhLYrbVv3X0z75OvN0dY49ITWjM7xyvMWJXVJS7sJlgmCCL6RwWgP8PhcE=").unwrap(); - /// let e = Base64::decode_vec("AQAB").unwrap(); - /// - /// let mut rng = rand::thread_rng(); - /// let key = RsaPublicKey::new(BigUint::from_bytes_be(&n), BigUint::from_bytes_be(&e)).unwrap(); - /// let padding = PaddingScheme::new_oaep_with_mgf_hash::(); - /// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap(); - /// ``` - pub fn new_oaep_with_mgf_hash< - T: 'static + Digest + DynDigest + Send + Sync, - U: 'static + Digest + DynDigest + Send + Sync, - >() -> Self { - PaddingScheme::OAEP { - digest: Box::new(T::new()), - mgf_digest: Box::new(U::new()), - label: None, - } - } - - /// Create a new OAEP `PaddingScheme`, using `T` as the hash function for both the default (empty) label and for MGF1. - /// - /// # Example - /// ``` - /// use sha1::Sha1; - /// use sha2::Sha256; - /// use rsa::{BigUint, RsaPublicKey, PaddingScheme, PublicKey}; - /// use base64ct::{Base64, Encoding}; - /// - /// let n = Base64::decode_vec("ALHgDoZmBQIx+jTmgeeHW6KsPOrj11f6CvWsiRleJlQpW77AwSZhd21ZDmlTKfaIHBSUxRUsuYNh7E2SHx8rkFVCQA2/gXkZ5GK2IUbzSTio9qXA25MWHvVxjMfKSL8ZAxZyKbrG94FLLszFAFOaiLLY8ECs7g+dXOriYtBwLUJK+lppbd+El+8ZA/zH0bk7vbqph5pIoiWggxwdq3mEz4LnrUln7r6dagSQzYErKewY8GADVpXcq5mfHC1xF2DFBub7bFjMVM5fHq7RK+pG5xjNDiYITbhLYrbVv3X0z75OvN0dY49ITWjM7xyvMWJXVJS7sJlgmCCL6RwWgP8PhcE=").unwrap(); - /// let e = Base64::decode_vec("AQAB").unwrap(); - /// - /// let mut rng = rand::thread_rng(); - /// let key = RsaPublicKey::new(BigUint::from_bytes_be(&n), BigUint::from_bytes_be(&e)).unwrap(); - /// let padding = PaddingScheme::new_oaep::(); - /// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap(); - /// ``` - pub fn new_oaep() -> Self { - PaddingScheme::OAEP { - digest: Box::new(T::new()), - mgf_digest: Box::new(T::new()), - label: None, - } - } - - /// Create a new OAEP `PaddingScheme` with an associated `label`, using `T` as the hash function for the label, and `U` as the hash function for MGF1. - pub fn new_oaep_with_mgf_hash_with_label< - T: 'static + Digest + DynDigest + Send + Sync, - U: 'static + Digest + DynDigest + Send + Sync, - S: AsRef, - >( - label: S, - ) -> Self { - PaddingScheme::OAEP { - digest: Box::new(T::new()), - mgf_digest: Box::new(U::new()), - label: Some(label.as_ref().to_string()), - } - } - - /// Create a new OAEP `PaddingScheme` with an associated `label`, using `T` as the hash function for both the label and for MGF1. - pub fn new_oaep_with_label>( - label: S, - ) -> Self { - PaddingScheme::OAEP { - digest: Box::new(T::new()), - mgf_digest: Box::new(T::new()), - label: Some(label.as_ref().to_string()), - } - } -} From 0e56d810edc46ccb5c0a8720c028be133054191b Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Fri, 6 Jan 2023 08:25:34 +0200 Subject: [PATCH 6/6] algorithms: drop the mgf_xor1 The function mgf_xor1() was replaced with mgf_xor1_digest(). Drop the legacy function. Signed-off-by: Dmitry Baryshkov --- src/algorithms.rs | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index 8bdc1d9a..47c8fe9b 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -1,6 +1,6 @@ //! Useful algorithms related to RSA. -use digest::{Digest, DynDigest, FixedOutputReset}; +use digest::{Digest, FixedOutputReset}; use num_bigint::traits::ModInverse; use num_bigint::{BigUint, RandPrime}; #[allow(unused_imports)] @@ -135,37 +135,6 @@ pub fn generate_multi_prime_key_with_exp( RsaPrivateKey::from_components(n_final, exp.clone(), d_final, primes) } -/// 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(out: &mut [u8], digest: &mut dyn DynDigest, seed: &[u8]) { - 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() { - let mut digest_input = vec![0u8; seed.len() + 4]; - digest_input[0..seed.len()].copy_from_slice(seed); - digest_input[seed.len()..].copy_from_slice(&counter); - - digest.update(digest_input.as_slice()); - 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); - } -} - /// Mask generation function. /// /// Panics if out is larger than 2**32. This is in accordance with RFC 8017 - PKCS #1 B.2.1