From 5d28baf32a30173ebef0ab0c5d9cd2e98c522074 Mon Sep 17 00:00:00 2001 From: roblabla Date: Wed, 21 Aug 2019 09:33:50 +0000 Subject: [PATCH 01/20] Implement PSS --- Cargo.toml | 5 +- src/key.rs | 33 +++-- src/lib.rs | 1 + src/pkcs1v15.rs | 4 +- src/pss.rs | 380 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 403 insertions(+), 20 deletions(-) create mode 100644 src/pss.rs diff --git a/Cargo.toml b/Cargo.toml index a9415be0..272b6360 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,9 @@ rand = "0.6.5" byteorder = "1.3.1" failure = "0.1.5" subtle = "2.0.0" +signature = { version = "0.2.0" } +sha2 = { version = "0.8" } +sha-1 = { version = "0.8.1" } [dependencies.zeroize] version = "0.6" @@ -34,8 +37,6 @@ features = ["std", "derive"] [dev-dependencies] base64 = "0.10.1" -sha-1 = "0.8.1" -sha2 = "0.8.0" hex = "0.3.2" serde_test = "1.0.89" diff --git a/src/key.rs b/src/key.rs index 27164af9..645e0330 100644 --- a/src/key.rs +++ b/src/key.rs @@ -9,9 +9,10 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; use crate::algorithms::generate_multi_prime_key; use crate::errors::{Error, Result}; -use crate::hash::Hash; +use crate::hash::{Hash, Hashes}; use crate::padding::PaddingScheme; use crate::pkcs1v15; +use crate::pss; lazy_static! { static ref MIN_PUB_EXPONENT: BigUint = BigUint::from_u64(2).unwrap(); @@ -132,10 +133,10 @@ pub trait PublicKey { /// `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( + fn verify( &self, padding: PaddingScheme, - hash: Option<&H>, + hash: Option<&Hashes>, hashed: &[u8], sig: &[u8], ) -> Result<()>; @@ -157,16 +158,16 @@ impl PublicKey for RSAPublicKey { } } - fn verify( + fn verify( &self, padding: PaddingScheme, - hash: Option<&H>, + hash: Option<&Hashes>, hashed: &[u8], sig: &[u8], ) -> Result<()> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::verify(self, hash, hashed, sig), - PaddingScheme::PSS => unimplemented!("not yet implemented"), + PaddingScheme::PSS => pss::verify(self, hash.unwrap(), hashed, sig), _ => Err(Error::InvalidPaddingScheme), } } @@ -195,10 +196,10 @@ impl<'a> PublicKey for &'a RSAPublicKey { (*self).encrypt(rng, padding, msg) } - fn verify( + fn verify( &self, padding: PaddingScheme, - hash: Option<&H>, + hash: Option<&Hashes>, hashed: &[u8], sig: &[u8], ) -> Result<()> { @@ -223,16 +224,16 @@ impl PublicKey for RSAPrivateKey { } } - fn verify( + fn verify( &self, padding: PaddingScheme, - hash: Option<&H>, + hash: Option<&Hashes>, hashed: &[u8], sig: &[u8], ) -> Result<()> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::verify(self, hash, hashed, sig), - PaddingScheme::PSS => unimplemented!("not yet implemented"), + PaddingScheme::PSS => pss::verify(self, hash.unwrap(), hashed, sig), _ => Err(Error::InvalidPaddingScheme), } } @@ -251,10 +252,10 @@ impl<'a> PublicKey for &'a RSAPrivateKey { (*self).encrypt(rng, padding, msg) } - fn verify( + fn verify( &self, padding: PaddingScheme, - hash: Option<&H>, + hash: Option<&Hashes>, hashed: &[u8], sig: &[u8], ) -> Result<()> { @@ -428,16 +429,16 @@ impl RSAPrivateKey { /// Sign the given digest. /// Use `rng` for blinding. - pub fn sign_blinded( + pub fn sign_blinded( &self, rng: &mut R, padding: PaddingScheme, - hash: Option<&H>, + hash: Option<&Hashes>, digest: &[u8], ) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::sign(Some(rng), self, hash, digest), - PaddingScheme::PSS => unimplemented!("not yet implemented"), + PaddingScheme::PSS => pss::sign(rng, self, hash.expect("Can't use None hash with PSS"), digest, None), _ => Err(Error::InvalidPaddingScheme), } } diff --git a/src/lib.rs b/src/lib.rs index 42799cd5..5ebdb43c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,6 +62,7 @@ pub mod padding; mod key; mod pkcs1v15; +mod pss; pub use self::key::{PublicKey, RSAPrivateKey, RSAPublicKey}; pub use self::padding::PaddingScheme; diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index aa753384..82d9304c 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -171,7 +171,7 @@ fn hash_info(hash: Option<&H>, digest_len: usize) -> Result<(usize, Vec } #[inline] -fn copy_with_left_pad(dest: &mut [u8], src: &[u8]) { +pub fn copy_with_left_pad(dest: &mut [u8], src: &[u8]) { // left pad with zeros let padding_bytes = dest.len() - src.len(); for el in dest.iter_mut().take(padding_bytes) { @@ -414,7 +414,7 @@ mod tests { let pub_key: RSAPublicKey = priv_key.into(); pub_key - .verify::(PaddingScheme::PKCS1v15, None, msg, &sig) + .verify(PaddingScheme::PKCS1v15, None, msg, &sig) .expect("failed to verify"); } } diff --git a/src/pss.rs b/src/pss.rs new file mode 100644 index 00000000..bcbf5682 --- /dev/null +++ b/src/pss.rs @@ -0,0 +1,380 @@ +use crate::pkcs1v15::copy_with_left_pad; +use crate::internals; +use crate::hash::{Hashes, Hash}; +use crate::key::{RSAPrivateKey, PublicKey}; +use crate::errors::{Error, Result}; + +use std::vec::Vec; +use num_bigint::BigUint; +use subtle::ConstantTimeEq; +use sha2::{Digest, Sha256}; +use sha1::Sha1; +use rand::Rng; + +pub fn verify( + pub_key: &K, + hash: &Hashes, + hashed: &[u8], + sig: &[u8]) -> Result<()> +{ + let n_bits = pub_key.n().bits(); + if sig.len() != (n_bits + 7) / 8 { + return Err(Error::Verification); + } + let s = BigUint::from_bytes_be(sig); + let m = internals::encrypt(pub_key, &s).to_bytes_be(); + let em_bits = n_bits - 1; + let em_len = (em_bits + 7) / 8; + + if em_len < m.len() { + return Err(Error::Verification); + } + + let mut em = vec![0; em_len]; + copy_with_left_pad(&mut em, &m); + + match hash { + Hashes::SHA1 => { + emsa_pss_verify(hashed, &mut em, em_bits, None, Sha1::new()) + }, + Hashes::SHA2_256 => { + emsa_pss_verify(hashed, &mut em, em_bits, None, Sha256::new()) + }, + _ => unimplemented!() + } +} + + +/// SignPSS calculates the signature of hashed using RSASSA-PSS [1]. +/// 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 fn sign(rng: &mut T, priv_key: &RSAPrivateKey, hash: &Hashes, hashed: &[u8], salt_len: Option) -> Result> { + let salt_len = salt_len.unwrap_or_else(|| { + (priv_key.n().bits() + 7) / 8 - 2 - hash.size() + }); + + let mut salt = vec![0; salt_len]; + rng.fill(&mut salt[..]); + + return sign_pss_with_salt(rng, priv_key, hash, hashed, &salt) +} + + +// signPSSWithSalt calculates the signature of hashed using PSS [1] 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. +fn sign_pss_with_salt(rng: &mut T, priv_key: &RSAPrivateKey, hash: &Hashes, hashed: &[u8], salt: &[u8]) -> Result> { + let n_bits = priv_key.n().bits(); + let mut em = vec![0; ((n_bits - 1) + 7) / 8]; + match hash { + Hashes::SHA1 => { + emsa_pss_encode(&mut em, hashed, n_bits - 1, salt, Sha1::new())?; + }, + Hashes::SHA2_256 => { + emsa_pss_encode(&mut em, hashed, n_bits - 1, salt, Sha256::new())?; + }, + _ => unimplemented!() + } + + let mut m = BigUint::from_bytes_be(&em); + let mut c = internals::decrypt_and_check(Some(rng), priv_key, &m)?.to_bytes_be(); + + let mut s = vec![0; (n_bits + 7) / 8]; + copy_with_left_pad(&mut s, &c); + return Ok(s) +} + +fn emsa_pss_encode(em: &mut [u8], m_hash: &[u8], em_bits: usize, salt: &[u8], mut hash: H) -> Result<()> { + // See [1], section 9.1.1 + let h_len = H::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); + } + + if em.len() != em_len { + return Err(Error::Internal); + } + + let (db, h) = em.split_at_mut(em_len - s_len - h_len - 2 + 1 + s_len); + 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.input(&prefix); + hash.input(m_hash); + hash.input(salt); + + let hashed = hash.result_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, &mut hash, &h); + + // 11. Set the leftmost 8 * em_len - em_bits bits of the leftmost octet in + // maskedDB to zero. + db[0] &= 0xFF >> (8 * em_len - em_bits); + + // 12. Let EM = maskedDB || H || 0xbc. + em[em_len-1] = 0xBC; + + return Ok(()) +} + +fn emsa_pss_verify(m_hash: &[u8], em: &mut [u8], em_bits: usize, s_len: Option, mut hash: H) -> Result<()> { + // 1. If the length of M is greater than the input limitation for the + // hash function (2^61 - 1 octets for SHA-1), output "inconsistent" + // and stop. + // + // 2. Let mHash = Hash(M), an octet string of length hLen + let h_len = H::output_size(); + if m_hash.len() != h_len { + return Err(Error::Verification); + } + + // 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. + let em_len = em.len();//(em_bits + 7) / 8; + if em_len < h_len + 2 { + return Err(Error::Verification) + } + + // 4. If the rightmost octet of EM does not have hexadecimal value + // 0xbc, output "inconsistent" and stop. + if em[em.len() - 1] != 0xBC { + return Err(Error::Verification) + } + + // 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and + // let H be the next hLen octets. + let (db, h) = em.split_at_mut(em_len - h_len - 1); + let h = &mut h[..(em_len - 1) - (em_len - h_len - 1)]; + + // 6. If the leftmost 8 * em_len - em_bits bits of the leftmost octet in + // maskedDB are not all equal to zero, output "inconsistent" and + // stop. + if db[0] & (0xFF << /*uint*/(8 - (8 * em_len - em_bits))) != 0 { + return Err(Error::Verification) + } + + // 7. Let dbMask = MGF(H, em_len - h_len - 1) + // + // 8. Let DB = maskedDB \xor dbMask + mgf1_xor(db, &mut hash, &*h); + + + // 9. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in DB + // to zero. + db[0] &= 0xFF >> /*uint*/(8 * em_len - em_bits); + + let s_len = match s_len { + None => (0..=em_len - (h_len + 2)).rev().try_fold(None, |state, i| { + match (state, db[em_len - h_len - i - 2]) { + (Some(i), _) => Ok(Some(i)), + (_, 1) => Ok(Some(i)), + (_, 0) => Ok(None), + _ => Err(Error::Verification) + } + })?.ok_or(Error::Verification)?, + Some(s_len) => { + // 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero + // or if the octet at position emLen - hLen - sLen - 1 (the leftmost + // position is "position 1") does not have hexadecimal value 0x01, + // output "inconsistent" and stop. + for e in &db[..em_len - h_len - s_len - 2] { + if *e != 0x00 { + return Err(Error::Verification); + } + } + if db[em_len - h_len - s_len - 2] != 0x01 { + return Err(Error::Verification) + } + s_len + } + }; + + // 11. Let salt be the last s_len octets of DB. + let salt = &db[db.len() - s_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.input(prefix); + hash.input(m_hash); + hash.input(salt); + let h0 = hash.result(); + + // 14. If H = H', output "consistent." Otherwise, output "inconsistent." + if Into::::into(h0.ct_eq(h)) { + Ok(()) + } else { + Err(Error::Verification) + } +} + +fn inc_counter(counter: &mut [u8]) { + if counter[3] == u8::max_value() { + counter[3] = 0; + } else { + counter[3] += 1; + return; + } + + if counter[2] == u8::max_value() { + counter[2] = 0; + } else { + counter[2] += 1; + return; + } + + if counter[1] == u8::max_value() { + counter[1] = 0; + } else { + counter[1] += 1; + return; + } + + if counter[0] == u8::max_value() { + counter[0] = 0u8; + counter[1] = 0u8; + counter[2] = 0u8; + counter[3] = 0u8; + } else { + counter[0] += 1; + } +} + +/// Mask generation function +fn mgf1_xor(out: &mut [u8], digest: &mut T, seed: &[u8]) { + let mut counter = vec![0u8; 4]; + let mut i = 0; + + 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.input(digest_input.as_slice()); + let digest_output = &*digest.result_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(counter.as_mut_slice()); + } +} + +#[cfg(test)] +mod test { + use crate::{PaddingScheme, RSAPrivateKey, RSAPublicKey, PublicKey}; + use crate::hash::Hashes; + + use num_bigint::BigUint; + use num_traits::{FromPrimitive, Num}; + use sha1::{Digest, Sha1}; + use rand::thread_rng; + + fn get_private_key() -> RSAPrivateKey { + // In order to generate new test vectors you'll need the PEM form of this key: + // -----BEGIN RSA PRIVATE KEY----- + // MIIBOgIBAAJBALKZD0nEffqM1ACuak0bijtqE2QrI/KLADv7l3kK3ppMyCuLKoF0 + // fd7Ai2KW5ToIwzFofvJcS/STa6HA5gQenRUCAwEAAQJBAIq9amn00aS0h/CrjXqu + // /ThglAXJmZhOMPVn4eiu7/ROixi9sex436MaVeMqSNf7Ex9a8fRNfWss7Sqd9eWu + // RTUCIQDasvGASLqmjeffBNLTXV2A5g4t+kLVCpsEIZAycV5GswIhANEPLmax0ME/ + // EO+ZJ79TJKN5yiGBRsv5yvx5UiHxajEXAiAhAol5N4EUyq6I9w1rYdhPMGpLfk7A + // IU2snfRJ6Nq2CQIgFrPsWRCkV+gOYcajD17rEqmuLrdIRexpg8N1DOSXoJ8CIGlS + // tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V + // -----END RSA PRIVATE KEY----- + + RSAPrivateKey::from_components( + BigUint::from_str_radix("9353930466774385905609975137998169297361893554149986716853295022578535724979677252958524466350471210367835187480748268864277464700638583474144061408845077", 10).unwrap(), + BigUint::from_u64(65537).unwrap(), + BigUint::from_str_radix("7266398431328116344057699379749222532279343923819063639497049039389899328538543087657733766554155839834519529439851673014800261285757759040931985506583861", 10).unwrap(), + vec![ + BigUint::from_str_radix("98920366548084643601728869055592650835572950932266967461790948584315647051443",10).unwrap(), + BigUint::from_str_radix("94560208308847015747498523884063394671606671904944666360068158221458669711639", 10).unwrap() + ], + ) + } + + #[test] + fn test_verify_pss() { + let priv_key = get_private_key(); + + let tests = [[ + "test\n", "6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f" + ]]; + let pub_key: RSAPublicKey = priv_key.into(); + + for test in &tests { + let digest = Sha1::digest(test[0].as_bytes()).to_vec(); + let sig = hex::decode(test[1]).unwrap(); + + pub_key + .verify(PaddingScheme::PSS, Some(&Hashes::SHA1), &digest, &sig) + .expect("failed to verify"); + } + } + + #[test] + fn test_sign_and_verify_roundtrip() { + let priv_key = get_private_key(); + + let tests = ["test\n"]; + + for test in &tests { + let digest = Sha1::digest(test.as_bytes()).to_vec(); + let sig = priv_key + .sign_blinded(&mut thread_rng(), PaddingScheme::PSS, Some(&Hashes::SHA1), &digest) + .expect("failed to sign"); + + priv_key + .verify(PaddingScheme::PSS, Some(&Hashes::SHA1), &digest, &sig) + .expect("failed to verify"); + } + } +} \ No newline at end of file From 2462f1dd56f79deb211efe7e298a9d8c2b2b8d61 Mon Sep 17 00:00:00 2001 From: roblabla Date: Thu, 26 Sep 2019 12:40:43 +0000 Subject: [PATCH 02/20] Remove PublicKey trait, use deref. Removes the PublicKey trait, using Deref to provide the RSAPublicKey methods to RSAPrivateKey. --- src/internals.rs | 8 +-- src/key.rs | 166 ++++++++++++----------------------------------- src/lib.rs | 2 +- src/pkcs1v15.rs | 8 +-- 4 files changed, 50 insertions(+), 134 deletions(-) diff --git a/src/internals.rs b/src/internals.rs index 5c034f59..ecc12198 100644 --- a/src/internals.rs +++ b/src/internals.rs @@ -5,11 +5,11 @@ use std::borrow::Cow; use zeroize::Zeroize; use crate::errors::{Error, Result}; -use crate::key::{PublicKey, RSAPrivateKey}; +use crate::key::{RSAPublicKey, RSAPrivateKey}; /// Raw RSA encryption of m with the public key. No padding is performed. #[inline] -pub fn encrypt(key: &K, m: &BigUint) -> BigUint { +pub fn encrypt(key: &RSAPublicKey, m: &BigUint) -> BigUint { m.modpow(key.e(), key.n()) } @@ -125,7 +125,7 @@ pub fn decrypt_and_check( } /// Returns the blinded c, along with the unblinding factor. -pub fn blind(rng: &mut R, key: &K, c: &BigUint) -> (BigUint, BigUint) { +pub fn blind(rng: &mut R, key: &RSAPublicKey, c: &BigUint) -> (BigUint, BigUint) { // Blinding involves multiplying c by r^e. // Then the decryption operation performs (m^e * r^e)^d mod n // which equals mr mod n. The factor of r can then be removed @@ -162,7 +162,7 @@ pub fn blind(rng: &mut R, key: &K, c: &BigUint) -> (BigUin } /// Given an m and and unblinding factor, unblind the m. -pub fn unblind(key: impl PublicKey, m: &BigUint, unblinder: &BigUint) -> BigUint { +pub fn unblind(key: &RSAPublicKey, m: &BigUint, unblinder: &BigUint) -> BigUint { (m * unblinder) % key.n() } diff --git a/src/key.rs b/src/key.rs index 645e0330..4c2290c7 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,3 +1,4 @@ +use std::ops::Deref; use num_bigint::traits::ModInverse; use num_bigint::Sign::Plus; use num_bigint::{BigInt, BigUint}; @@ -31,10 +32,8 @@ pub struct RSAPublicKey { #[derive(Debug, Clone, ZeroizeOnDrop)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct RSAPrivateKey { - /// Modulus - n: BigUint, - /// Public exponent - e: BigUint, + /// Public components of the private key. + pubkey_components: RSAPublicKey, /// Private exponent d: BigUint, /// Prime factors of N, contains >= 2 elements. @@ -47,7 +46,7 @@ pub struct RSAPrivateKey { impl PartialEq for RSAPrivateKey { #[inline] fn eq(&self, other: &RSAPrivateKey) -> bool { - self.n == other.n && self.e == other.e && self.d == other.d && self.primes == other.primes + self.pubkey_components == other.pubkey_components && self.d == other.d && self.primes == other.primes } } @@ -66,6 +65,13 @@ impl Zeroize for RSAPrivateKey { } } +impl Deref for RSAPrivateKey { + type Target = RSAPublicKey; + fn deref(&self) -> &RSAPublicKey { + &self.pubkey_components + } +} + #[derive(Debug, Clone, ZeroizeOnDrop)] pub(crate) struct PrecomputedValues { /// D mod (P-1) @@ -106,70 +112,16 @@ pub(crate) struct CRTValue { } impl From for RSAPublicKey { - fn from(private_key: RSAPrivateKey) -> Self { - let n = private_key.n.clone(); - let e = private_key.e.clone(); - - RSAPublicKey { n, e } - } -} - -/// Generic trait for operations on a public key. -pub trait PublicKey { - /// Returns the modulus of the key. - fn n(&self) -> &BigUint; - /// Returns the public exponent of the key. - fn e(&self) -> &BigUint; - /// Returns the modulus size in bytes. Raw signatures and ciphertexts for - /// or by this public key will have the same size. - fn size(&self) -> usize { - (self.n().bits() + 7) / 8 - } - - /// Encrypt the given message. - fn encrypt(&self, rng: &mut R, 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, - hash: Option<&Hashes>, - hashed: &[u8], - sig: &[u8], - ) -> Result<()>; -} - -impl PublicKey for RSAPublicKey { - fn n(&self) -> &BigUint { - &self.n - } - - fn e(&self) -> &BigUint { - &self.e - } - fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { - match padding { - PaddingScheme::PKCS1v15 => pkcs1v15::encrypt(rng, self, msg), - PaddingScheme::OAEP => unimplemented!("not yet implemented"), - _ => Err(Error::InvalidPaddingScheme), - } - } - - fn verify( - &self, - padding: PaddingScheme, - hash: Option<&Hashes>, - hashed: &[u8], - sig: &[u8], - ) -> Result<()> { - match padding { - PaddingScheme::PKCS1v15 => pkcs1v15::verify(self, hash, hashed, sig), - PaddingScheme::PSS => pss::verify(self, hash.unwrap(), hashed, sig), - _ => Err(Error::InvalidPaddingScheme), - } + fn from(mut private_key: RSAPrivateKey) -> Self { + let mut broken_key = RSAPublicKey { + // Fast, no-allocation creation of a biguint. + n: BigUint::new_native(Default::default()), + e: BigUint::new_native(Default::default()) + }; + // The private key is going to get dropped after this, so temporarily + // making it invalid is fine. + let pubkey = core::mem::replace(&mut private_key.pubkey_components, broken_key); + pubkey } } @@ -181,42 +133,25 @@ impl RSAPublicKey { Ok(k) } -} -impl<'a> PublicKey for &'a RSAPublicKey { - fn n(&self) -> &BigUint { + /// Returns the modulus of the key. + pub fn n(&self) -> &BigUint { &self.n } - fn e(&self) -> &BigUint { + /// Returns the public exponent of the key. + pub fn e(&self) -> &BigUint { &self.e } - fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { - (*self).encrypt(rng, padding, msg) - } - - fn verify( - &self, - padding: PaddingScheme, - hash: Option<&Hashes>, - hashed: &[u8], - sig: &[u8], - ) -> Result<()> { - (*self).verify(padding, hash, hashed, sig) - } -} - -impl PublicKey for RSAPrivateKey { - fn n(&self) -> &BigUint { - &self.n - } - - fn e(&self) -> &BigUint { - &self.e + /// Returns the modulus size in bytes. Raw signatures and ciphertexts for + /// or by this public key will have the same size. + pub fn size(&self) -> usize { + (self.n().bits() + 7) / 8 } - fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { + /// Encrypt the given message + pub fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::encrypt(rng, self, msg), PaddingScheme::OAEP => unimplemented!("not yet implemented"), @@ -224,10 +159,14 @@ impl PublicKey for RSAPrivateKey { } } - fn verify( + /// Verify a signed message. + /// `hashed` must be the result of hashing the input using the hashing function + /// identified using the ASN1 prefix in `hash_asn1_prefix`. + /// If the message is valid `Ok(())` is returned, otherwiese an `Err` indicating failure. + pub fn verify( &self, padding: PaddingScheme, - hash: Option<&Hashes>, + hash: Option<&H>, hashed: &[u8], sig: &[u8], ) -> Result<()> { @@ -239,30 +178,6 @@ impl PublicKey for RSAPrivateKey { } } -impl<'a> PublicKey for &'a RSAPrivateKey { - fn n(&self) -> &BigUint { - &self.n - } - - fn e(&self) -> &BigUint { - &self.e - } - - fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { - (*self).encrypt(rng, padding, msg) - } - - fn verify( - &self, - padding: PaddingScheme, - hash: Option<&Hashes>, - hashed: &[u8], - sig: &[u8], - ) -> Result<()> { - (*self).verify(padding, hash, hashed, sig) - } -} - impl RSAPrivateKey { /// Generate a new RSA key pair of the given bit size using the passed in `rng`. pub fn new(rng: &mut R, bit_size: usize) -> Result { @@ -277,8 +192,9 @@ impl RSAPrivateKey { primes: Vec, ) -> RSAPrivateKey { let mut k = RSAPrivateKey { - n, - e, + pubkey_components: RSAPublicKey { + n, e + }, d, primes, precomputed: None, @@ -446,7 +362,7 @@ impl RSAPrivateKey { /// Check that the public key is well formed and has an exponent within acceptable bounds. #[inline] -pub fn check_public(public_key: &impl PublicKey) -> Result<()> { +pub fn check_public(public_key: &RSAPublicKey) -> RsaResult<()> { if public_key.e() < &*MIN_PUB_EXPONENT { return Err(Error::PublicExponentTooSmall); } diff --git a/src/lib.rs b/src/lib.rs index 5ebdb43c..d292f257 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,7 @@ mod key; mod pkcs1v15; mod pss; -pub use self::key::{PublicKey, RSAPrivateKey, RSAPublicKey}; +pub use self::key::{RSAPrivateKey, RSAPublicKey}; pub use self::padding::PaddingScheme; // Optionally expose internals if requested via feature-flag. diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index 82d9304c..3a8d2c27 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -6,13 +6,13 @@ use zeroize::Zeroize; use crate::errors::{Error, Result}; use crate::hash::Hash; use crate::internals; -use crate::key::{self, PublicKey, RSAPrivateKey}; +use crate::key::{self, RSAPublicKey, RSAPrivateKey}; // 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(rng: &mut R, pub_key: &K, msg: &[u8]) -> Result> { +pub fn encrypt(rng: &mut R, pub_key: &RSAPublicKey, msg: &[u8]) -> Result> { key::check_public(pub_key)?; let k = pub_key.size(); @@ -116,8 +116,8 @@ pub fn sign( /// Verifies an RSA PKCS#1 v1.5 signature. #[inline] -pub fn verify( - pub_key: &K, +pub fn verify( + pub_key: &RSAPublicKey, hash: Option<&H>, hashed: &[u8], sig: &[u8], From 071f07f6c8288efea12f557fe226749450e3f5bd Mon Sep 17 00:00:00 2001 From: roblabla Date: Thu, 26 Sep 2019 12:42:54 +0000 Subject: [PATCH 03/20] Avoid using vectors to store the asn1 prefixes --- src/hash.rs | 26 +++++++++++++------------- src/pkcs1v15.rs | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/hash.rs b/src/hash.rs index 6af2ae5b..71dbc63c 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -5,7 +5,7 @@ pub trait Hash { fn size(&self) -> usize; /// Returns the ASN1 DER prefix for the the hash function. - fn asn1_prefix(&self) -> Vec; + fn asn1_prefix(&self) -> &'static [u8]; } /// A list of provided hashes, implementing `Hash`. @@ -41,50 +41,50 @@ impl Hash for Hashes { } } - fn asn1_prefix(&self) -> Vec { + fn asn1_prefix(&self) -> &'static [u8] { match *self { - Hashes::MD5 => vec![ + Hashes::MD5 => &[ 0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10, ], - Hashes::SHA1 => vec![ + Hashes::SHA1 => &[ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14, ], - Hashes::SHA2_224 => vec![ + Hashes::SHA2_224 => &[ 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c, ], - Hashes::SHA2_256 => vec![ + Hashes::SHA2_256 => &[ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20, ], - Hashes::SHA2_384 => vec![ + Hashes::SHA2_384 => &[ 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30, ], - Hashes::SHA2_512 => vec![ + Hashes::SHA2_512 => &[ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40, ], // A special TLS case which doesn't use an ASN1 prefix - Hashes::MD5SHA1 => Vec::new(), - Hashes::RIPEMD160 => vec![ + Hashes::MD5SHA1 => &[], + Hashes::RIPEMD160 => &[ 0x30, 0x20, 0x30, 0x08, 0x06, 0x06, 0x28, 0xcf, 0x06, 0x03, 0x00, 0x31, 0x04, 0x14, ], - Hashes::SHA3_256 => vec![ + Hashes::SHA3_256 => &[ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08, 0x05, 0x00, 0x04, 0x20, ], - Hashes::SHA3_384 => vec![ + Hashes::SHA3_384 => &[ 30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08, 0x05, 0x00, 0x04, 0x20, ], - Hashes::SHA3_512 => vec![ + Hashes::SHA3_512 => &[ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0a, 0x05, 0x00, 0x04, 0x40, ], diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index 3a8d2c27..02dd2939 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -155,7 +155,7 @@ pub fn verify( } #[inline] -fn hash_info(hash: Option<&H>, digest_len: usize) -> Result<(usize, Vec)> { +fn hash_info(hash: Option<&H>, digest_len: usize) -> Result<(usize, &'static [u8])> { match hash { Some(hash) => { let hash_len = hash.size(); @@ -166,7 +166,7 @@ fn hash_info(hash: Option<&H>, digest_len: usize) -> Result<(usize, Vec Ok((hash_len, hash.asn1_prefix())) } // this means the data is signed directly - None => Ok((digest_len, Vec::new())), + None => Ok((digest_len, &[])), } } From 527acfca3156ddb7eaec79f502c472b97222d948 Mon Sep 17 00:00:00 2001 From: roblabla Date: Thu, 26 Sep 2019 12:43:56 +0000 Subject: [PATCH 04/20] Use separate functions for different padding schemes --- src/key.rs | 85 +++++++++++++++++++----------------------------------- 1 file changed, 29 insertions(+), 56 deletions(-) diff --git a/src/key.rs b/src/key.rs index 4c2290c7..0dd67cd6 100644 --- a/src/key.rs +++ b/src/key.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; use crate::algorithms::generate_multi_prime_key; -use crate::errors::{Error, Result}; +use crate::errors::{Error, Result as RsaResult}; use crate::hash::{Hash, Hashes}; use crate::padding::PaddingScheme; use crate::pkcs1v15; @@ -127,7 +127,7 @@ impl From for RSAPublicKey { impl RSAPublicKey { /// Create a new key from its components. - pub fn new(n: BigUint, e: BigUint) -> Result { + pub fn new(n: BigUint, e: BigUint) -> RsaResult { let k = RSAPublicKey { n, e }; check_public(&k)?; @@ -150,37 +150,28 @@ impl RSAPublicKey { (self.n().bits() + 7) / 8 } - /// Encrypt the given message - pub fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { - match padding { - PaddingScheme::PKCS1v15 => pkcs1v15::encrypt(rng, self, msg), - PaddingScheme::OAEP => unimplemented!("not yet implemented"), - _ => Err(Error::InvalidPaddingScheme), - } + /// Encrypt the given message, using the PKCS1v15 padding scheme. + pub fn encrypt_pkcs1v15(&self, rng: &mut R, msg: &[u8]) -> RsaResult> { + pkcs1v15::encrypt(rng, self, msg) } - /// Verify a signed message. + /// Verify a message signed with the PKCS1v15 padding scheme. /// `hashed` must be the result of hashing the input using the hashing function /// identified using the ASN1 prefix in `hash_asn1_prefix`. /// If the message is valid `Ok(())` is returned, otherwiese an `Err` indicating failure. - pub fn verify( + pub fn verify_pkcs1v15( &self, - padding: PaddingScheme, hash: Option<&H>, hashed: &[u8], sig: &[u8], - ) -> Result<()> { - match padding { - PaddingScheme::PKCS1v15 => pkcs1v15::verify(self, hash, hashed, sig), - PaddingScheme::PSS => pss::verify(self, hash.unwrap(), hashed, sig), - _ => Err(Error::InvalidPaddingScheme), - } + ) -> RsaResult<()> { + pkcs1v15::verify(self, hash, hashed, sig) } } impl RSAPrivateKey { /// Generate a new RSA key pair of the given bit size using the passed in `rng`. - pub fn new(rng: &mut R, bit_size: usize) -> Result { + pub fn new(rng: &mut R, bit_size: usize) -> RsaResult { generate_multi_prime_key(rng, 2, bit_size) } @@ -271,7 +262,7 @@ impl RSAPrivateKey { /// Performs basic sanity checks on the key. /// Returns `Ok(())` if everything is good, otherwise an approriate error. - pub fn validate(&self) -> Result<()> { + pub fn validate(&self) -> RsaResult<()> { check_public(self)?; // Check that Πprimes == n. @@ -304,59 +295,41 @@ 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::PKCS1v15 => pkcs1v15::decrypt::(None, self, ciphertext), - PaddingScheme::OAEP => unimplemented!("not yet implemented"), - _ => Err(Error::InvalidPaddingScheme), - } + /// Decrypt the given message, using the PKCS1v15 padding scheme. + pub fn decrypt_pkcs1v15(&self, ciphertext: &[u8]) -> RsaResult> { + pkcs1v15::decrypt::(None, self, ciphertext) } - /// Decrypt the given message. + /// Decrypt the given message, using the PKCS1v15 padding scheme. + /// /// Uses `rng` to blind the decryption process. - pub fn decrypt_blinded( + pub fn decrypt_pkcs1v15_blinded( &self, rng: &mut R, - padding: PaddingScheme, ciphertext: &[u8], - ) -> Result> { - match padding { - PaddingScheme::PKCS1v15 => pkcs1v15::decrypt(Some(rng), self, ciphertext), - PaddingScheme::OAEP => unimplemented!("not yet implemented"), - _ => Err(Error::InvalidPaddingScheme), - } + ) -> RsaResult> { + pkcs1v15::decrypt(Some(rng), self, ciphertext) } - /// Sign the given digest. - pub fn sign( + /// Sign the given digest using the PKCS1v15 padding scheme. + pub fn sign_pkcs1v15( &self, - padding: PaddingScheme, hash: Option<&H>, digest: &[u8], - ) -> Result> { - match padding { - PaddingScheme::PKCS1v15 => pkcs1v15::sign::(None, self, hash, digest), - PaddingScheme::PSS => unimplemented!("not yet implemented"), - _ => Err(Error::InvalidPaddingScheme), - } + ) -> RsaResult> { + pkcs1v15::sign::(None, self, hash, digest) } - /// Sign the given digest. + /// Sign the given digest using the PKCS1v15 padding scheme. + /// /// Use `rng` for blinding. - pub fn sign_blinded( + pub fn sign_pkcs1v15_blinded( &self, rng: &mut R, - padding: PaddingScheme, - hash: Option<&Hashes>, + hash: Option<&H>, digest: &[u8], - ) -> Result> { - match padding { - PaddingScheme::PKCS1v15 => pkcs1v15::sign(Some(rng), self, hash, digest), - PaddingScheme::PSS => pss::sign(rng, self, hash.expect("Can't use None hash with PSS"), digest, None), - _ => Err(Error::InvalidPaddingScheme), - } + ) -> RsaResult> { + pkcs1v15::sign(Some(rng), self, hash, digest) } } From b821cc6cc2adb7fd94c367ef330f18076c8ea5d6 Mon Sep 17 00:00:00 2001 From: roblabla Date: Thu, 26 Sep 2019 13:15:04 +0000 Subject: [PATCH 05/20] Add new sign_pss/verify_pss --- src/key.rs | 39 +++++++++++++++++++++++++--- src/pss.rs | 74 ++++++++++++++++++++++++------------------------------ 2 files changed, 69 insertions(+), 44 deletions(-) diff --git a/src/key.rs b/src/key.rs index 0dd67cd6..128733e9 100644 --- a/src/key.rs +++ b/src/key.rs @@ -7,11 +7,11 @@ use rand::{rngs::ThreadRng, Rng}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; +use digest::Digest; use crate::algorithms::generate_multi_prime_key; use crate::errors::{Error, Result as RsaResult}; -use crate::hash::{Hash, Hashes}; -use crate::padding::PaddingScheme; +use crate::hash::Hash; use crate::pkcs1v15; use crate::pss; @@ -113,7 +113,7 @@ pub(crate) struct CRTValue { impl From for RSAPublicKey { fn from(mut private_key: RSAPrivateKey) -> Self { - let mut broken_key = RSAPublicKey { + let broken_key = RSAPublicKey { // Fast, no-allocation creation of a biguint. n: BigUint::new_native(Default::default()), e: BigUint::new_native(Default::default()) @@ -167,6 +167,20 @@ impl RSAPublicKey { ) -> RsaResult<()> { pkcs1v15::verify(self, hash, hashed, sig) } + + /// Verify that the given signature is valid using the PSS padding scheme. + /// + /// The first parameter should be a pre-hashed message, using D as the + /// hashing scheme. + /// + /// The salt length is auto-detected. + pub fn verify_pss( + &self, + hashed: &[u8], + sig: &[u8] + ) -> RsaResult<()> { + pss::verify::(self, hashed, sig) + } } impl RSAPrivateKey { @@ -331,6 +345,25 @@ impl RSAPrivateKey { ) -> RsaResult> { pkcs1v15::sign(Some(rng), self, hash, digest) } + + /// Sign the given pre-hashed message using the PSS padding scheme. The + /// message should be hashed using the Digest algorithm passed as a generic + /// argument. + /// + /// RNG is used for PSS salt generation, and if `blind` is true, it will + /// also be used to blind the RSA encryption. + /// + /// The length of the salt can be controlled with the salt_len parameter. If + /// it is None, then it will be calculated to be as large as possible. + pub fn sign_pss( + &self, + rng: &mut R, + digest: &[u8], + salt_len: Option, + blind: bool + ) -> RsaResult> { + pss::sign::(rng, self, digest, salt_len, blind) + } } /// Check that the public key is well formed and has an exponent within acceptable bounds. diff --git a/src/pss.rs b/src/pss.rs index bcbf5682..e3fe1ba8 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -1,19 +1,16 @@ use crate::pkcs1v15::copy_with_left_pad; use crate::internals; -use crate::hash::{Hashes, Hash}; -use crate::key::{RSAPrivateKey, PublicKey}; +use crate::key::{RSAPrivateKey, RSAPublicKey}; use crate::errors::{Error, Result}; use std::vec::Vec; use num_bigint::BigUint; use subtle::ConstantTimeEq; -use sha2::{Digest, Sha256}; -use sha1::Sha1; +use digest::Digest; use rand::Rng; -pub fn verify( - pub_key: &K, - hash: &Hashes, +pub fn verify( + pub_key: &RSAPublicKey, hashed: &[u8], sig: &[u8]) -> Result<()> { @@ -33,15 +30,7 @@ pub fn verify( let mut em = vec![0; em_len]; copy_with_left_pad(&mut em, &m); - match hash { - Hashes::SHA1 => { - emsa_pss_verify(hashed, &mut em, em_bits, None, Sha1::new()) - }, - Hashes::SHA2_256 => { - emsa_pss_verify(hashed, &mut em, em_bits, None, Sha256::new()) - }, - _ => unimplemented!() - } + emsa_pss_verify::(hashed, &mut em, em_bits, None) } @@ -49,15 +38,15 @@ pub fn verify( /// 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 fn sign(rng: &mut T, priv_key: &RSAPrivateKey, hash: &Hashes, hashed: &[u8], salt_len: Option) -> Result> { +pub fn sign(rng: &mut T, priv_key: &RSAPrivateKey, hashed: &[u8], salt_len: Option, blind: bool) -> Result> { let salt_len = salt_len.unwrap_or_else(|| { - (priv_key.n().bits() + 7) / 8 - 2 - hash.size() + (priv_key.n().bits() + 7) / 8 - 2 - H::output_size() }); let mut salt = vec![0; salt_len]; rng.fill(&mut salt[..]); - return sign_pss_with_salt(rng, priv_key, hash, hashed, &salt) + return sign_pss_with_salt::<_, H>(rng, priv_key, hashed, &salt, blind) } @@ -65,28 +54,27 @@ pub fn sign(rng: &mut T, priv_key: &RSAPrivateKey, hash: &Hashes, hashed // 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(rng: &mut T, priv_key: &RSAPrivateKey, hash: &Hashes, hashed: &[u8], salt: &[u8]) -> Result> { +fn sign_pss_with_salt(rng: &mut T, priv_key: &RSAPrivateKey, hashed: &[u8], salt: &[u8], blind: bool) -> Result> { let n_bits = priv_key.n().bits(); let mut em = vec![0; ((n_bits - 1) + 7) / 8]; - match hash { - Hashes::SHA1 => { - emsa_pss_encode(&mut em, hashed, n_bits - 1, salt, Sha1::new())?; - }, - Hashes::SHA2_256 => { - emsa_pss_encode(&mut em, hashed, n_bits - 1, salt, Sha256::new())?; - }, - _ => unimplemented!() - } + emsa_pss_encode::(&mut em, hashed, n_bits - 1, salt)?; - let mut m = BigUint::from_bytes_be(&em); - let mut c = internals::decrypt_and_check(Some(rng), priv_key, &m)?.to_bytes_be(); + let m = BigUint::from_bytes_be(&em); + + let blind_rng = if blind { + Some(rng) + } else { + None + }; + + let c = internals::decrypt_and_check(blind_rng, priv_key, &m)?.to_bytes_be(); let mut s = vec![0; (n_bits + 7) / 8]; copy_with_left_pad(&mut s, &c); return Ok(s) } -fn emsa_pss_encode(em: &mut [u8], m_hash: &[u8], em_bits: usize, salt: &[u8], mut hash: H) -> Result<()> { +fn emsa_pss_encode(em: &mut [u8], m_hash: &[u8], em_bits: usize, salt: &[u8]) -> Result<()> { // See [1], section 9.1.1 let h_len = H::output_size(); let s_len = salt.len(); @@ -125,11 +113,13 @@ fn emsa_pss_encode(em: &mut [u8], m_hash: &[u8], em_bits: usize, salt // // 6. Let H = Hash(M'), an octet string of length h_len. let prefix = [0u8; 8]; + let mut hash = H::new(); + hash.input(&prefix); hash.input(m_hash); hash.input(salt); - let hashed = hash.result_reset(); + let hashed = hash.result(); h.copy_from_slice(&hashed); // 7. Generate an octet string PS consisting of em_len - s_len - h_len - 2 @@ -143,7 +133,7 @@ fn emsa_pss_encode(em: &mut [u8], m_hash: &[u8], em_bits: usize, salt // 9. Let dbMask = MGF(H, emLen - hLen - 1). // // 10. Let maskedDB = DB \xor dbMask. - mgf1_xor(db, &mut hash, &h); + mgf1_xor(db, &mut H::new(), &h); // 11. Set the leftmost 8 * em_len - em_bits bits of the leftmost octet in // maskedDB to zero. @@ -155,7 +145,7 @@ fn emsa_pss_encode(em: &mut [u8], m_hash: &[u8], em_bits: usize, salt return Ok(()) } -fn emsa_pss_verify(m_hash: &[u8], em: &mut [u8], em_bits: usize, s_len: Option, mut hash: H) -> Result<()> { +fn emsa_pss_verify(m_hash: &[u8], em: &mut [u8], em_bits: usize, s_len: Option) -> Result<()> { // 1. If the length of M is greater than the input limitation for the // hash function (2^61 - 1 octets for SHA-1), output "inconsistent" // and stop. @@ -193,7 +183,7 @@ fn emsa_pss_verify(m_hash: &[u8], em: &mut [u8], em_bits: usize, s_le // 7. Let dbMask = MGF(H, em_len - h_len - 1) // // 8. Let DB = maskedDB \xor dbMask - mgf1_xor(db, &mut hash, &*h); + mgf1_xor(db, &mut H::new(), &*h); // 9. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in DB @@ -237,6 +227,7 @@ fn emsa_pss_verify(m_hash: &[u8], em: &mut [u8], em_bits: usize, s_le // 13. Let H' = Hash(M'), an octet string of length hLen. let prefix = [0u8; 8]; + let mut hash = H::new(); hash.input(prefix); hash.input(m_hash); hash.input(salt); @@ -283,6 +274,8 @@ fn inc_counter(counter: &mut [u8]) { } /// Mask generation function +/// +/// Will reset the Digest before returning. fn mgf1_xor(out: &mut [u8], digest: &mut T, seed: &[u8]) { let mut counter = vec![0u8; 4]; let mut i = 0; @@ -310,8 +303,7 @@ fn mgf1_xor(out: &mut [u8], digest: &mut T, seed: &[u8]) { #[cfg(test)] mod test { - use crate::{PaddingScheme, RSAPrivateKey, RSAPublicKey, PublicKey}; - use crate::hash::Hashes; + use crate::{RSAPrivateKey, RSAPublicKey}; use num_bigint::BigUint; use num_traits::{FromPrimitive, Num}; @@ -355,7 +347,7 @@ mod test { let sig = hex::decode(test[1]).unwrap(); pub_key - .verify(PaddingScheme::PSS, Some(&Hashes::SHA1), &digest, &sig) + .verify_pss::(&digest, &sig) .expect("failed to verify"); } } @@ -369,11 +361,11 @@ mod test { for test in &tests { let digest = Sha1::digest(test.as_bytes()).to_vec(); let sig = priv_key - .sign_blinded(&mut thread_rng(), PaddingScheme::PSS, Some(&Hashes::SHA1), &digest) + .sign_pss::(&mut thread_rng(), &digest, None, true) .expect("failed to sign"); priv_key - .verify(PaddingScheme::PSS, Some(&Hashes::SHA1), &digest, &sig) + .verify_pss::(&digest, &sig) .expect("failed to verify"); } } From 30220cfd4484ec14e27c21cefb1652d401e3ce67 Mon Sep 17 00:00:00 2001 From: roblabla Date: Thu, 26 Sep 2019 13:16:25 +0000 Subject: [PATCH 06/20] Fix the tests --- Cargo.toml | 5 ++--- src/key.rs | 6 ++++-- src/lib.rs | 10 +++------- src/padding.rs | 7 ------- src/pkcs1v15.rs | 14 ++++++-------- 5 files changed, 15 insertions(+), 27 deletions(-) delete mode 100644 src/padding.rs diff --git a/Cargo.toml b/Cargo.toml index 272b6360..2e77bb5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,7 @@ rand = "0.6.5" byteorder = "1.3.1" failure = "0.1.5" subtle = "2.0.0" -signature = { version = "0.2.0" } -sha2 = { version = "0.8" } -sha-1 = { version = "0.8.1" } +digest = "0.8" [dependencies.zeroize] version = "0.6" @@ -39,6 +37,7 @@ features = ["std", "derive"] base64 = "0.10.1" hex = "0.3.2" serde_test = "1.0.89" +sha-1 = "0.8.1" [[bench]] diff --git a/src/key.rs b/src/key.rs index 128733e9..e7c3b1c1 100644 --- a/src/key.rs +++ b/src/key.rs @@ -390,8 +390,10 @@ mod tests { #[test] fn test_from_into() { let private_key = RSAPrivateKey { - n: BigUint::from_u64(100).unwrap(), - e: BigUint::from_u64(200).unwrap(), + pubkey_components: RSAPublicKey { + n: BigUint::from_u64(100).unwrap(), + e: BigUint::from_u64(200).unwrap(), + }, d: BigUint::from_u64(123).unwrap(), primes: vec![], precomputed: None, diff --git a/src/lib.rs b/src/lib.rs index d292f257..ddb75e86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ //! extern crate rsa; //! extern crate rand; //! -//! use rsa::{PublicKey, RSAPrivateKey, PaddingScheme}; +//! use rsa::RSAPrivateKey; //! use rand::rngs::OsRng; //! //! let mut rng = OsRng::new().expect("no secure randomness available"); @@ -17,11 +17,11 @@ //! //! // Encrypt //! let data = b"hello world"; -//! let enc_data = key.encrypt(&mut rng, PaddingScheme::PKCS1v15, &data[..]).expect("failed to encrypt"); +//! let enc_data = key.encrypt_pkcs1v15(&mut rng, &data[..]).expect("failed to encrypt"); //! assert_ne!(&data[..], &enc_data[..]); //! //! // Decrypt -//! let dec_data = key.decrypt(PaddingScheme::PKCS1v15, &enc_data).expect("failed to decrypt"); +//! let dec_data = key.decrypt_pkcs1v15(&enc_data).expect("failed to decrypt"); //! assert_eq!(&data[..], &dec_data[..]); //! ``` //! @@ -57,15 +57,11 @@ pub mod errors; /// Supported hash functions. pub mod hash; -/// Supported padding schemes. -pub mod padding; - mod key; mod pkcs1v15; mod pss; pub use self::key::{RSAPrivateKey, RSAPublicKey}; -pub use self::padding::PaddingScheme; // Optionally expose internals if requested via feature-flag. diff --git a/src/padding.rs b/src/padding.rs deleted file mode 100644 index 4525ecb2..00000000 --- a/src/padding.rs +++ /dev/null @@ -1,7 +0,0 @@ -/// Available padding schemes. -#[derive(Debug, Clone, Copy)] -pub enum PaddingScheme { - PKCS1v15, - OAEP, - PSS, -} diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index 02dd2939..d3dc1771 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -266,7 +266,6 @@ mod tests { use crate::hash::Hashes; use crate::key::RSAPublicKey; - use crate::padding::PaddingScheme; #[test] fn test_non_zero_bytes() { @@ -323,7 +322,7 @@ mod tests { for test in &tests { let out = priv_key - .decrypt(PaddingScheme::PKCS1v15, &base64::decode(test[0]).unwrap()) + .decrypt_pkcs1v15(&base64::decode(test[0]).unwrap()) .unwrap(); assert_eq!(out, test[1].as_bytes()); } @@ -364,16 +363,15 @@ mod tests { let expected = hex::decode(test[1]).unwrap(); let out = priv_key - .sign(PaddingScheme::PKCS1v15, Some(&Hashes::SHA1), &digest) + .sign_pkcs1v15(Some(&Hashes::SHA1), &digest) .unwrap(); assert_ne!(out, digest); assert_eq!(out, expected); let mut rng = thread_rng(); let out2 = priv_key - .sign_blinded( + .sign_pkcs1v15_blinded( &mut rng, - PaddingScheme::PKCS1v15, Some(&Hashes::SHA1), &digest, ) @@ -396,7 +394,7 @@ mod tests { let sig = hex::decode(test[1]).unwrap(); pub_key - .verify(PaddingScheme::PKCS1v15, Some(&Hashes::SHA1), &digest, &sig) + .verify_pkcs1v15(Some(&Hashes::SHA1), &digest, &sig) .expect("failed to verify"); } } @@ -408,13 +406,13 @@ mod tests { let priv_key = get_private_key(); let sig = priv_key - .sign::(PaddingScheme::PKCS1v15, None, msg) + .sign_pkcs1v15::(None, msg) .unwrap(); assert_eq!(expected_sig, sig); let pub_key: RSAPublicKey = priv_key.into(); pub_key - .verify(PaddingScheme::PKCS1v15, None, msg, &sig) + .verify_pkcs1v15::(None, msg, &sig) .expect("failed to verify"); } } From 610c4cc95f614dad40651e585756858cca4f6e20 Mon Sep 17 00:00:00 2001 From: roblabla Date: Thu, 26 Sep 2019 13:22:46 +0000 Subject: [PATCH 07/20] Move copy_with_left_pad to algorithms --- src/algorithms.rs | 10 ++++++++++ src/pkcs1v15.rs | 11 +---------- src/pss.rs | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index a02e884b..5b6d3850 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -110,3 +110,13 @@ pub fn generate_multi_prime_key( primes, )) } + +#[inline] +pub fn copy_with_left_pad(dest: &mut [u8], src: &[u8]) { + // left pad with zeros + let padding_bytes = dest.len() - src.len(); + for el in dest.iter_mut().take(padding_bytes) { + *el = 0; + } + dest[padding_bytes..].copy_from_slice(src); +} \ No newline at end of file diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index d3dc1771..f85cd76d 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -3,6 +3,7 @@ use rand::Rng; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use zeroize::Zeroize; +use crate::algorithms::copy_with_left_pad; use crate::errors::{Error, Result}; use crate::hash::Hash; use crate::internals; @@ -170,16 +171,6 @@ fn hash_info(hash: Option<&H>, digest_len: usize) -> Result<(usize, &'s } } -#[inline] -pub fn copy_with_left_pad(dest: &mut [u8], src: &[u8]) { - // left pad with zeros - let padding_bytes = dest.len() - src.len(); - for el in dest.iter_mut().take(padding_bytes) { - *el = 0; - } - dest[padding_bytes..].copy_from_slice(src); -} - /// Decrypts ciphertext using `priv_key` and blinds the operation if /// `rng` is given. It returns one or zero in valid that indicates whether the /// plaintext was correctly structured. In either case, the plaintext is diff --git a/src/pss.rs b/src/pss.rs index e3fe1ba8..f136f303 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -1,4 +1,4 @@ -use crate::pkcs1v15::copy_with_left_pad; +use crate::algorithms::copy_with_left_pad; use crate::internals; use crate::key::{RSAPrivateKey, RSAPublicKey}; use crate::errors::{Error, Result}; From 79439a004e8dc5e23d240f124c406c74730395a8 Mon Sep 17 00:00:00 2001 From: lucdew Date: Mon, 1 Apr 2019 18:11:40 +0200 Subject: [PATCH 08/20] Add oaep encrypt and only with sha1 digest Test assertion is manual and done with openssl --- Cargo.toml | 6 +- src/hash.rs | 1 + src/internals.rs | 11 ++ src/lib.rs | 3 + src/oaep.rs | 354 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 373 insertions(+), 2 deletions(-) create mode 100644 src/oaep.rs diff --git a/Cargo.toml b/Cargo.toml index 661cd8e9..38adeb23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ thiserror = "1.0.11" subtle = "2.0.0" simple_asn1 = "0.4" pem = { version = "0.7", optional = true } +digest = { version = "0.8.0", features = ["std"] } [dependencies.zeroize] version = "1.1.0" @@ -37,12 +38,13 @@ features = ["std", "derive"] [dev-dependencies] base64 = "0.11.0" -sha-1 = "0.8.1" -sha2 = "0.8.0" hex = "0.4.0" serde_test = "1.0.89" rand_xorshift = "0.2.0" pem = "0.7" +sha-1 = "0.8.1" +sha2 = "0.8.0" +sha3 = "0.8.1" [[bench]] name = "key" diff --git a/src/hash.rs b/src/hash.rs index 6af2ae5b..6adc39b6 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -6,6 +6,7 @@ pub trait Hash { /// Returns the ASN1 DER prefix for the the hash function. fn asn1_prefix(&self) -> Vec; + } /// A list of provided hashes, implementing `Hash`. diff --git a/src/internals.rs b/src/internals.rs index eb291288..767d4c37 100644 --- a/src/internals.rs +++ b/src/internals.rs @@ -179,3 +179,14 @@ pub fn left_pad(input: &[u8], size: usize) -> Vec { out[size - n..].copy_from_slice(input); out } + +#[inline] +pub fn copy_with_left_pad(dest: &mut [u8], src: &[u8]) { + // left pad with zeros + let padding_bytes = dest.len() - src.len(); + for el in dest.iter_mut().take(padding_bytes) { + *el = 0; + } + dest[padding_bytes..].copy_from_slice(src); +} + diff --git a/src/lib.rs b/src/lib.rs index 395fb0a5..dab803ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,9 +64,12 @@ pub use pem; mod key; mod parse; + mod pkcs1v15; mod raw; +pub mod oaep; + pub use self::key::{PublicKey, RSAPrivateKey, RSAPublicKey}; pub use self::padding::PaddingScheme; diff --git a/src/oaep.rs b/src/oaep.rs new file mode 100644 index 00000000..21d2faa8 --- /dev/null +++ b/src/oaep.rs @@ -0,0 +1,354 @@ +use rand::Rng; + +use digest::DynDigest; + +use num_bigint::BigUint; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; +use zeroize::Zeroize; + +use crate::errors::{Error, Result}; +use crate::internals; +use crate::key::{self, PublicKey, RSAPrivateKey}; + +fn inc_counter(counter: &mut [u8]) { + if counter[3] == u8::max_value() { + counter[3] = 0; + } else { + counter[3] += 1; + return; + } + + if counter[2] == u8::max_value() { + counter[2] = 0; + } else { + counter[2] += 1; + return; + } + + if counter[1] == u8::max_value() { + counter[1] = 0; + } else { + counter[1] += 1; + return; + } + + if counter[0] == u8::max_value() { + counter[0] = 0u8; + counter[1] = 0u8; + counter[2] = 0u8; + counter[3] = 0u8; + } else { + counter[0] += 1; + } +} + +/// Mask generation function +fn mgf1_xor(out: &mut [u8], digest: &mut impl DynDigest, seed: &[u8]) { + let mut counter = vec![0u8; 4]; + let mut i = 0; + + 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.input(digest_input.as_slice()); + let digest_output = &*digest.result_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(counter.as_mut_slice()); + } +} + +// Encrypts the given message with RSA and the padding +// scheme from PKCS#1 OAEP. The message must be no longer than the +// length of the public modulus minus (2+ 2*hash.size()). +#[inline] +pub fn encrypt( + rng: &mut R, + pub_key: &K, + msg: &[u8], + digest: &mut impl DynDigest, + label: Option, +) -> Result> { + key::check_public(pub_key)?; + + let k = pub_key.size(); + + let h_size = digest.output_size(); + + if msg.len() + 2 * h_size + 2 > k { + return Err(Error::MessageTooLong); + } + + let label = match label { + Some(l) => l, + None => "".to_owned(), + }; + + let mut em = vec![0u8; k]; + + let (_, payload) = em.split_at_mut(1); + let (seed, db) = payload.split_at_mut(h_size); + rng.fill(seed); + + // Data block DB = pHash || PS || 01 || M + let db_len = k - h_size - 1; + + digest.input(label.as_bytes()); + let p_hash = digest.result_reset(); + 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, digest, seed); + mgf1_xor(seed, digest, db); + + { + let mut m = BigUint::from_bytes_be(&em); + let mut c = internals::encrypt(pub_key, &m).to_bytes_be(); + internals::copy_with_left_pad(&mut em, &c); + + // clear out tmp values + m.zeroize(); + c.zeroize(); + } + + Ok(em) +} + +/// Decrypts a plaintext using RSA and the padding scheme from pkcs1# OAEP +// If an `rng` is passed, it uses RSA blinding to avoid timing side-channel attacks. +// +// Note that whether this function returns an error or not discloses secret +// information. If an attacker can cause this function to run repeatedly and +// learn whether each instance returned an error then they can decrypt and +// forge signatures as if they had the private key. See +// `decrypt_session_key` for a way of solving this problem. +#[inline] +pub fn decrypt( + rng: Option<&mut R>, + priv_key: &RSAPrivateKey, + ciphertext: &[u8], + digest: &mut impl DynDigest, + label: Option, +) -> Result> { + key::check_public(priv_key)?; + + let (valid, out, index) = decrypt_inner(rng, priv_key, ciphertext, digest, label)?; + if valid == 0 { + return Err(Error::Decryption); + } + + Ok(out[index as usize..].to_vec()) +} + +/// Decrypts ciphertext using `priv_key` and blinds the operation if +/// `rng` is given. It returns one or zero in valid that indicates whether the +/// plaintext was correctly structured. In either case, the plaintext is +/// returned in em so that it may be read independently of whether it was valid +/// 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( + rng: Option<&mut R>, + priv_key: &RSAPrivateKey, + ciphertext: &[u8], + digest: &mut impl DynDigest, + label: Option, +) -> Result<(u8, Vec, u32)> { + let k = priv_key.size(); + if k < 11 { + return Err(Error::Decryption); + } + + let h_size = digest.output_size(); + + if ciphertext.len() > k || k < h_size * 2 + 2 { + return Err(Error::Decryption); + } + + let mut em = { + let mut c = BigUint::from_bytes_be(ciphertext); + let mut m = internals::decrypt(rng, priv_key, &c)?; + let em = internals::left_pad(&m.to_bytes_be(), k); + + c.zeroize(); + m.zeroize(); + + em + }; + + let label = match label { + Some(l) => l, + None => "".to_owned(), + }; + + digest.input(label.as_bytes()); + + let expected_p_hash = &*digest.result_reset(); + + 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, digest, db); + mgf1_xor(db, digest, seed); + + let hash_are_equal = db[0..h_size].ct_eq(expected_p_hash); + + // The remainder of the plaintext must be zero or more 0x00, followed + // by 0x01, followed by the message. + // looking_for_index: 1 if we are still looking for the 0x01 + // index: the offset of the first 0x01 byte + // zero_before_one: 1 if we saw a non-zero byte before the 1 + let mut looking_for_index = 1u8; + let mut index = 0u32; + let mut zero_before_one = 0u8; + + for (i, el) in db.iter().skip(h_size).enumerate() { + let equals0 = el.ct_eq(&0u8); + let equals1 = el.ct_eq(&1u8); + index.conditional_assign(&(i as u32), Choice::from(looking_for_index) & equals1); + looking_for_index.conditional_assign(&0u8, equals1); + zero_before_one.conditional_assign(&1u8, Choice::from(looking_for_index) & !equals0); + } + + let valid = first_byte_is_zero + & hash_are_equal + & !Choice::from(zero_before_one) + & !Choice::from(looking_for_index); + index = u32::conditional_select(&0, &(index + 2 + (h_size * 2) as u32), valid); + + Ok((valid.unwrap_u8(), em, index)) +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::key::RSAPublicKey; + use num_traits::FromPrimitive; + use rand::distributions::Alphanumeric; + use rand::thread_rng; + + use sha1::Sha1; + use sha2::{Sha224, Sha256, Sha384, Sha512}; + use sha3::{Sha3_256, Sha3_384, Sha3_512}; + + fn get_private_key() -> RSAPrivateKey { + // -----BEGIN RSA PRIVATE KEY----- + // MIIEpAIBAAKCAQEA05e4TZikwmE47RtpWoEG6tkdVTvwYEG2LT/cUKBB4iK49FKW + // icG4LF5xVU9d1p+i9LYVjPDb61eBGg/DJ+HyjnT+dNO8Fmweq9wbi1e5NMqL5bAL + // TymXW8yZrK9BW1m7KKZ4K7QaLDwpdrPBjbre9i8AxrsiZkAJUJbAzGDSL+fvmH11 + // xqgbENlr8pICivEQ3HzBu8Q9Iq2rN5oM1dgHjMeA/1zWIJ3qNMkiz3hPdxfkKNdb + // WuyP8w5fAUFRB2bi4KuNRzyE6HELK5gifD2wlTN600UvGeK5v7zN2BSKv2d2+lUn + // debnWVbkUimuWpxGlJurHmIvDkj1ZSSoTtNIOwIDAQABAoIBAQDE5wxokWLJTGYI + // KBkbUrTYOSEV30hqmtvoMeRY1zlYMg3Bt1VFbpNwHpcC12+wuS+Q4B0f4kgVMoH+ + // eaqXY6kvrmnY1+zRRN4p+hNb0U+Vc+NJ5FAx47dpgvWDADgmxVLomjl8Gga9IWNI + // hjDZLowrtkPXq+9wDaldaFyUFImkb1S1MW9itdLDp/G70TTLNzU6RGg/3J2V02RY + // 3iL2xEBX/nSgpDbEMI9z9NpC81xHrBanE41IOvyR5B3DoRJzguDA9RGbAiG0/GOd + // a5w4F3pt6bUm69iMONeYLAf5ig79h31Qiq4nW5RpFcAuLhEG0XXXTsZ3f16A0SwF + // PZx74eNBAoGBAPgnu/OkGHfHzFmuv0LtSynDLe/LjtloY9WwkKBaiTDdYkohydz5 + // g4Vo/foN9luEYqXyrJE9bFb5dVMr2OePsHvUBcqZpIS89Z8Bm73cs5M/K85wYwC0 + // 97EQEgxd+QGBWQZ8NdowYaVshjWlK1QnOzEnG0MR8Hld9gIeY1XhpC5hAoGBANpI + // F84Aid028q3mo/9BDHPsNL8bT2vaOEMb/t4RzvH39u+nDl+AY6Ox9uFylv+xX+76 + // CRKgMluNH9ZaVZ5xe1uWHsNFBy4OxSA9A0QdKa9NZAVKBFB0EM8dp457YRnZCexm + // 5q1iW/mVsnmks8W+fYlc18W5xMSX/ecwkW/NtOQbAoGAHabpz4AhKFbodSLrWbzv + // CUt4NroVFKdjnoodjfujfwJFF2SYMV5jN9LG3lVCxca43ulzc1tqka33Nfv8TBcg + // WHuKQZ5ASVgm5VwU1wgDMSoQOve07MWy/yZTccTc1zA0ihDXgn3bfR/NnaVh2wlh + // CkuI92eyW1494hztc7qlmqECgYEA1zenyOQ9ChDIW/ABGIahaZamNxsNRrDFMl3j + // AD+cxHSRU59qC32CQH8ShRy/huHzTaPX2DZ9EEln76fnrS4Ey7uLH0rrFl1XvT6K + // /timJgLvMEvXTx/xBtUdRN2fUqXtI9odbSyCtOYFL+zVl44HJq2UzY4pVRDrNcxs + // SUkQJqsCgYBSaNfPBzR5rrstLtTdZrjImRW1LRQeDEky9WsMDtCTYUGJTsTSfVO8 + // hkU82MpbRVBFIYx+GWIJwcZRcC7OCQoV48vMJllxMAAjqG/p00rVJ+nvA7et/nNu + // BoB0er/UmDm4Ly/97EO9A0PKMOE5YbMq9s3t3RlWcsdrU7dvw+p2+A== + // -----END RSA PRIVATE KEY----- + + RSAPrivateKey::from_components( + BigUint::parse_bytes(b"00d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b", 16).unwrap(), + BigUint::from_u64(65537).unwrap(), + BigUint::parse_bytes(b"00c4e70c689162c94c660828191b52b4d8392115df486a9adbe831e458d73958320dc1b755456e93701e9702d76fb0b92f90e01d1fe248153281fe79aa9763a92fae69d8d7ecd144de29fa135bd14f9573e349e45031e3b76982f583003826c552e89a397c1a06bd2163488630d92e8c2bb643d7abef700da95d685c941489a46f54b5316f62b5d2c3a7f1bbd134cb37353a44683fdc9d95d36458de22f6c44057fe74a0a436c4308f73f4da42f35c47ac16a7138d483afc91e41dc3a1127382e0c0f5119b0221b4fc639d6b9c38177a6de9b526ebd88c38d7982c07f98a0efd877d508aae275b946915c02e2e1106d175d74ec6777f5e80d12c053d9c7be1e341", 16).unwrap(), + vec![ + BigUint::parse_bytes(b"00f827bbf3a41877c7cc59aebf42ed4b29c32defcb8ed96863d5b090a05a8930dd624a21c9dcf9838568fdfa0df65b8462a5f2ac913d6c56f975532bd8e78fb07bd405ca99a484bcf59f019bbddcb3933f2bce706300b4f7b110120c5df9018159067c35da3061a56c8635a52b54273b31271b4311f0795df6021e6355e1a42e61",16).unwrap(), + BigUint::parse_bytes(b"00da4817ce0089dd36f2ade6a3ff410c73ec34bf1b4f6bda38431bfede11cef1f7f6efa70e5f8063a3b1f6e17296ffb15feefa0912a0325b8d1fd65a559e717b5b961ec345072e0ec5203d03441d29af4d64054a04507410cf1da78e7b6119d909ec66e6ad625bf995b279a4b3c5be7d895cd7c5b9c4c497fde730916fcdb4e41b", 16).unwrap() + ], + ) + } + + #[test] + fn test_encrypt_decrypt_oaep() { + let priv_key = get_private_key(); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha1::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha224::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha256::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha384::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha512::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha3_256::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha3_384::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha3_512::default()); + } + + fn do_test_encrypt_decrypt_oaep(prk: &RSAPrivateKey, digest: &mut D) { + let mut rng = thread_rng(); + + let k = prk.size(); + + for i in 1..8 { + let mut input: Vec = (0..i * 8).map(|_| rng.gen()).collect(); + if input.len() > k - 11 { + input = input[0..k - 11].to_vec(); + } + let has_label: bool = rng.gen(); + let label = if has_label { + Some(rng.sample_iter(&Alphanumeric).take(30).collect()) + } else { + None + }; + + let pub_key: RSAPublicKey = prk.clone().into(); + let ciphertext = encrypt(&mut rng, &pub_key, &input, digest, label.clone()).unwrap(); + assert_ne!(input, ciphertext); + let blind: bool = rng.gen(); + let blinder = if blind { Some(&mut rng) } else { None }; + let plaintext = decrypt(blinder, &prk, &ciphertext, digest, label).unwrap(); + assert_eq!(input, plaintext); + } + } + + #[test] + fn test_decrypt_oaep_invalid_hash() { + let mut rng = thread_rng(); + let priv_key = get_private_key(); + let pub_key: RSAPublicKey = priv_key.clone().into(); + let mut digest = Sha1::default(); + let ciphertext = encrypt( + &mut rng, + &pub_key, + "a_plain_text".as_bytes(), + &mut digest, + None, + ) + .unwrap(); + assert!( + decrypt( + Some(&mut rng), + &priv_key, + &ciphertext, + &mut digest, + Some("label".to_owned()) + ) + .is_err(), + "decrypt should have failed on hash verification" + ); + } +} From b50fa5238e8a775e63ab179fa60f025c8e5a424b Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 6 Mar 2020 20:05:24 +0100 Subject: [PATCH 09/20] refactor: bring oaep implementation into the regular interface --- Cargo.toml | 2 +- README.md | 9 +-- src/internals.rs | 11 --- src/key.rs | 135 +++++++++++++++++++++++++++++++++-- src/lib.rs | 40 +++++++---- src/oaep.rs | 181 +++++------------------------------------------ src/padding.rs | 48 ++++++++++++- 7 files changed, 227 insertions(+), 199 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 38adeb23..fb30556f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ subtle = "2.0.0" simple_asn1 = "0.4" pem = { version = "0.7", optional = true } digest = { version = "0.8.0", features = ["std"] } +sha2 = "0.8.0" [dependencies.zeroize] version = "1.1.0" @@ -43,7 +44,6 @@ serde_test = "1.0.89" rand_xorshift = "0.2.0" pem = "0.7" sha-1 = "0.8.1" -sha2 = "0.8.0" sha3 = "0.8.1" [[bench]] diff --git a/README.md b/README.md index c88eb99d..b44d9a4e 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,16 @@ use rand::rngs::OsRng; let mut rng = OsRng; let bits = 2048; -let key = RSAPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); +let priv_key = RSAPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); +let pub_key = RSAPublicKey::from(&private_key); // Encrypt let data = b"hello world"; -let enc_data = key.encrypt(&mut rng, PaddingScheme::PKCS1v15, &data[..]).expect("failed to encrypt"); +let enc_data = pub_key.encrypt(&mut rng, PaddingScheme::PKCS1v15, &data[..]).expect("failed to encrypt"); assert_ne!(&data[..], &enc_data[..]); // Decrypt -let dec_data = key.decrypt(PaddingScheme::PKCS1v15, &enc_data).expect("failed to decrypt"); +let dec_data = priv_key.decrypt(PaddingScheme::PKCS1v15, &enc_data).expect("failed to decrypt"); assert_eq!(&data[..], &dec_data[..]); ``` @@ -41,7 +42,7 @@ There will be three phases before `1.0` :ship: can be released. - [x] PKCS1v1.5: Encryption & Decryption :white_check_mark: - [x] PKCS1v1.5: Sign & Verify :white_check_mark: - [ ] PKCS1v1.5 (session key): Encryption & Decryption - - [ ] OAEP: Encryption & Decryption + - [x] OAEP: Encryption & Decryption - [ ] PSS: Sign & Verify - [x] Key import & export 2. :rocket: Make it fast diff --git a/src/internals.rs b/src/internals.rs index 767d4c37..eb291288 100644 --- a/src/internals.rs +++ b/src/internals.rs @@ -179,14 +179,3 @@ pub fn left_pad(input: &[u8], size: usize) -> Vec { out[size - n..].copy_from_slice(input); out } - -#[inline] -pub fn copy_with_left_pad(dest: &mut [u8], src: &[u8]) { - // left pad with zeros - let padding_bytes = dest.len() - src.len(); - for el in dest.iter_mut().take(padding_bytes) { - *el = 0; - } - dest[padding_bytes..].copy_from_slice(src); -} - diff --git a/src/key.rs b/src/key.rs index 8718829e..029285e2 100644 --- a/src/key.rs +++ b/src/key.rs @@ -11,8 +11,8 @@ use crate::algorithms::generate_multi_prime_key; use crate::errors::{Error, Result}; use crate::hash::Hash; use crate::padding::PaddingScheme; -use crate::pkcs1v15; use crate::raw::{DecryptionPrimitive, EncryptionPrimitive}; +use crate::{oaep, pkcs1v15}; lazy_static! { static ref MIN_PUB_EXPONENT: BigUint = BigUint::from_u64(2).unwrap(); @@ -186,7 +186,9 @@ impl PublicKey for RSAPublicKey { fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::encrypt(rng, self, msg), - PaddingScheme::OAEP => unimplemented!("not yet implemented"), + PaddingScheme::OAEP { mut digest, label } => { + oaep::encrypt(rng, self, msg, &mut *digest, label) + } _ => Err(Error::InvalidPaddingScheme), } } @@ -545,7 +547,9 @@ impl RSAPrivateKey { 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::PKCS1v15 => pkcs1v15::decrypt::(None, self, ciphertext), - PaddingScheme::OAEP => unimplemented!("not yet implemented"), + PaddingScheme::OAEP { mut digest, label } => { + oaep::decrypt::(None, self, ciphertext, &mut *digest, label) + } _ => Err(Error::InvalidPaddingScheme), } } @@ -560,7 +564,9 @@ impl RSAPrivateKey { ) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::decrypt(Some(rng), self, ciphertext), - PaddingScheme::OAEP => unimplemented!("not yet implemented"), + PaddingScheme::OAEP { mut digest, label } => { + oaep::decrypt(Some(rng), self, ciphertext, &mut *digest, label) + } _ => Err(Error::InvalidPaddingScheme), } } @@ -614,8 +620,13 @@ pub fn check_public(public_key: &impl PublicKeyParts) -> Result<()> { mod tests { use super::*; use crate::internals; + + use digest::{Digest, DynDigest}; use num_traits::{FromPrimitive, ToPrimitive}; - use rand::{rngs::ThreadRng, thread_rng}; + use rand::{distributions::Alphanumeric, rngs::ThreadRng, thread_rng}; + use sha1::Sha1; + use sha2::{Sha224, Sha256, Sha384, Sha512}; + use sha3::{Sha3_256, Sha3_384, Sha3_512}; #[test] fn test_from_into() { @@ -797,4 +808,118 @@ mod tests { primes.iter().map(|p| BigUint::from_bytes_be(p)).collect(), ); } + + fn get_private_key() -> RSAPrivateKey { + // -----BEGIN RSA PRIVATE KEY----- + // MIIEpAIBAAKCAQEA05e4TZikwmE47RtpWoEG6tkdVTvwYEG2LT/cUKBB4iK49FKW + // icG4LF5xVU9d1p+i9LYVjPDb61eBGg/DJ+HyjnT+dNO8Fmweq9wbi1e5NMqL5bAL + // TymXW8yZrK9BW1m7KKZ4K7QaLDwpdrPBjbre9i8AxrsiZkAJUJbAzGDSL+fvmH11 + // xqgbENlr8pICivEQ3HzBu8Q9Iq2rN5oM1dgHjMeA/1zWIJ3qNMkiz3hPdxfkKNdb + // WuyP8w5fAUFRB2bi4KuNRzyE6HELK5gifD2wlTN600UvGeK5v7zN2BSKv2d2+lUn + // debnWVbkUimuWpxGlJurHmIvDkj1ZSSoTtNIOwIDAQABAoIBAQDE5wxokWLJTGYI + // KBkbUrTYOSEV30hqmtvoMeRY1zlYMg3Bt1VFbpNwHpcC12+wuS+Q4B0f4kgVMoH+ + // eaqXY6kvrmnY1+zRRN4p+hNb0U+Vc+NJ5FAx47dpgvWDADgmxVLomjl8Gga9IWNI + // hjDZLowrtkPXq+9wDaldaFyUFImkb1S1MW9itdLDp/G70TTLNzU6RGg/3J2V02RY + // 3iL2xEBX/nSgpDbEMI9z9NpC81xHrBanE41IOvyR5B3DoRJzguDA9RGbAiG0/GOd + // a5w4F3pt6bUm69iMONeYLAf5ig79h31Qiq4nW5RpFcAuLhEG0XXXTsZ3f16A0SwF + // PZx74eNBAoGBAPgnu/OkGHfHzFmuv0LtSynDLe/LjtloY9WwkKBaiTDdYkohydz5 + // g4Vo/foN9luEYqXyrJE9bFb5dVMr2OePsHvUBcqZpIS89Z8Bm73cs5M/K85wYwC0 + // 97EQEgxd+QGBWQZ8NdowYaVshjWlK1QnOzEnG0MR8Hld9gIeY1XhpC5hAoGBANpI + // F84Aid028q3mo/9BDHPsNL8bT2vaOEMb/t4RzvH39u+nDl+AY6Ox9uFylv+xX+76 + // CRKgMluNH9ZaVZ5xe1uWHsNFBy4OxSA9A0QdKa9NZAVKBFB0EM8dp457YRnZCexm + // 5q1iW/mVsnmks8W+fYlc18W5xMSX/ecwkW/NtOQbAoGAHabpz4AhKFbodSLrWbzv + // CUt4NroVFKdjnoodjfujfwJFF2SYMV5jN9LG3lVCxca43ulzc1tqka33Nfv8TBcg + // WHuKQZ5ASVgm5VwU1wgDMSoQOve07MWy/yZTccTc1zA0ihDXgn3bfR/NnaVh2wlh + // CkuI92eyW1494hztc7qlmqECgYEA1zenyOQ9ChDIW/ABGIahaZamNxsNRrDFMl3j + // AD+cxHSRU59qC32CQH8ShRy/huHzTaPX2DZ9EEln76fnrS4Ey7uLH0rrFl1XvT6K + // /timJgLvMEvXTx/xBtUdRN2fUqXtI9odbSyCtOYFL+zVl44HJq2UzY4pVRDrNcxs + // SUkQJqsCgYBSaNfPBzR5rrstLtTdZrjImRW1LRQeDEky9WsMDtCTYUGJTsTSfVO8 + // hkU82MpbRVBFIYx+GWIJwcZRcC7OCQoV48vMJllxMAAjqG/p00rVJ+nvA7et/nNu + // BoB0er/UmDm4Ly/97EO9A0PKMOE5YbMq9s3t3RlWcsdrU7dvw+p2+A== + // -----END RSA PRIVATE KEY----- + + RSAPrivateKey::from_components( + BigUint::parse_bytes(b"00d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b", 16).unwrap(), + BigUint::from_u64(65537).unwrap(), + BigUint::parse_bytes(b"00c4e70c689162c94c660828191b52b4d8392115df486a9adbe831e458d73958320dc1b755456e93701e9702d76fb0b92f90e01d1fe248153281fe79aa9763a92fae69d8d7ecd144de29fa135bd14f9573e349e45031e3b76982f583003826c552e89a397c1a06bd2163488630d92e8c2bb643d7abef700da95d685c941489a46f54b5316f62b5d2c3a7f1bbd134cb37353a44683fdc9d95d36458de22f6c44057fe74a0a436c4308f73f4da42f35c47ac16a7138d483afc91e41dc3a1127382e0c0f5119b0221b4fc639d6b9c38177a6de9b526ebd88c38d7982c07f98a0efd877d508aae275b946915c02e2e1106d175d74ec6777f5e80d12c053d9c7be1e341", 16).unwrap(), + vec![ + BigUint::parse_bytes(b"00f827bbf3a41877c7cc59aebf42ed4b29c32defcb8ed96863d5b090a05a8930dd624a21c9dcf9838568fdfa0df65b8462a5f2ac913d6c56f975532bd8e78fb07bd405ca99a484bcf59f019bbddcb3933f2bce706300b4f7b110120c5df9018159067c35da3061a56c8635a52b54273b31271b4311f0795df6021e6355e1a42e61",16).unwrap(), + BigUint::parse_bytes(b"00da4817ce0089dd36f2ade6a3ff410c73ec34bf1b4f6bda38431bfede11cef1f7f6efa70e5f8063a3b1f6e17296ffb15feefa0912a0325b8d1fd65a559e717b5b961ec345072e0ec5203d03441d29af4d64054a04507410cf1da78e7b6119d909ec66e6ad625bf995b279a4b3c5be7d895cd7c5b9c4c497fde730916fcdb4e41b", 16).unwrap() + ], + ) + } + + #[test] + fn test_encrypt_decrypt_oaep() { + let priv_key = get_private_key(); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + } + + fn do_test_encrypt_decrypt_oaep(prk: &RSAPrivateKey) { + let mut rng = thread_rng(); + + let k = prk.size(); + + for i in 1..8 { + let mut input: Vec = (0..i * 8).map(|_| rng.gen()).collect(); + if input.len() > k - 11 { + input = input[0..k - 11].to_vec(); + } + let has_label: bool = rng.gen(); + let padding_scheme = if has_label { + let label: String = rng.sample_iter(&Alphanumeric).take(30).collect(); + PaddingScheme::new_oaep_with_label::(label) + } else { + PaddingScheme::new_oaep::() + }; + + let pub_key: RSAPublicKey = prk.into(); + let ciphertext = pub_key + .encrypt(&mut rng, padding_scheme.clone(), &input) + .unwrap(); + + assert_ne!(input, ciphertext); + let blind: bool = rng.gen(); + + let plaintext = if blind { + prk.decrypt(padding_scheme, &ciphertext).unwrap() + } else { + prk.decrypt_blinded(&mut rng, padding_scheme, &ciphertext) + .unwrap() + }; + + assert_eq!(input, plaintext); + } + } + + #[test] + fn test_decrypt_oaep_invalid_hash() { + let mut rng = thread_rng(); + 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(), + ) + .unwrap(); + assert!( + priv_key + .decrypt_blinded( + &mut rng, + PaddingScheme::new_oaep_with_label::("label"), + &ciphertext, + ) + .is_err(), + "decrypt should have failed on hash verification" + ); + } } diff --git a/src/lib.rs b/src/lib.rs index dab803ff..3bdddd02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,10 +4,8 @@ //! //! # Usage //! +//! Using PKCS1v15. //! ``` -//! extern crate rsa; -//! extern crate rand; -//! //! use rsa::{PublicKey, RSAPrivateKey, RSAPublicKey, PaddingScheme}; //! use rand::rngs::OsRng; //! @@ -18,23 +16,39 @@ //! //! // Encrypt //! let data = b"hello world"; -//! let enc_data = public_key.encrypt(&mut rng, PaddingScheme::PKCS1v15, &data[..]).expect("failed to encrypt"); +//! let padding = PaddingScheme::new_pkcs1v15(); +//! let enc_data = public_key.encrypt(&mut rng, padding.clone(), &data[..]).expect("failed to encrypt"); //! assert_ne!(&data[..], &enc_data[..]); //! //! // Decrypt -//! let dec_data = private_key.decrypt(PaddingScheme::PKCS1v15, &enc_data).expect("failed to decrypt"); +//! let dec_data = private_key.decrypt(padding, &enc_data).expect("failed to decrypt"); //! assert_eq!(&data[..], &dec_data[..]); //! ``` //! +//! Using OAEP. +//! ``` +//! use rsa::{PublicKey, RSAPrivateKey, RSAPublicKey, PaddingScheme}; +//! use rand::rngs::OsRng; +//! +//! let mut rng = OsRng; +//! let bits = 2048; +//! let private_key = RSAPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); +//! let public_key = RSAPublicKey::from(&private_key); +//! +//! // Encrypt +//! let data = b"hello world"; +//! let padding = PaddingScheme::new_oaep::(); +//! let enc_data = public_key.encrypt(&mut rng, padding.clone(), &data[..]).expect("failed to encrypt"); +//! assert_ne!(&data[..], &enc_data[..]); +//! +//! // Decrypt +//! let dec_data = private_key.decrypt(padding, &enc_data).expect("failed to decrypt"); +//! assert_eq!(&data[..], &dec_data[..]); +//! ``` #[macro_use] extern crate lazy_static; -extern crate num_iter; -extern crate rand; -extern crate subtle; -extern crate zeroize; - #[cfg(feature = "serde")] extern crate serde_crate; @@ -63,14 +77,12 @@ pub mod padding; pub use pem; mod key; +mod oaep; mod parse; - mod pkcs1v15; mod raw; -pub mod oaep; - -pub use self::key::{PublicKey, RSAPrivateKey, RSAPublicKey}; +pub use self::key::{PublicKey, PublicKeyParts, RSAPrivateKey, RSAPublicKey}; pub use self::padding::PaddingScheme; // Optionally expose internals if requested via feature-flag. diff --git a/src/oaep.rs b/src/oaep.rs index 21d2faa8..703817cd 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -1,14 +1,11 @@ use rand::Rng; use digest::DynDigest; - -use num_bigint::BigUint; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; -use zeroize::Zeroize; use crate::errors::{Error, Result}; -use crate::internals; -use crate::key::{self, PublicKey, RSAPrivateKey}; +use crate::key::{self, PublicKey, PublicKeyParts, RSAPrivateKey}; +use crate::raw::DecryptionPrimitive; fn inc_counter(counter: &mut [u8]) { if counter[3] == u8::max_value() { @@ -42,8 +39,8 @@ fn inc_counter(counter: &mut [u8]) { } } -/// Mask generation function -fn mgf1_xor(out: &mut [u8], digest: &mut impl DynDigest, seed: &[u8]) { +/// Mask generation function. +fn mgf1_xor(out: &mut [u8], digest: &mut dyn DynDigest, seed: &[u8]) { let mut counter = vec![0u8; 4]; let mut i = 0; @@ -68,15 +65,15 @@ fn mgf1_xor(out: &mut [u8], digest: &mut impl DynDigest, seed: &[u8]) { } } -// Encrypts the given message with RSA and the padding -// scheme from PKCS#1 OAEP. The message must be no longer than the -// length of the public modulus minus (2+ 2*hash.size()). +/// Encrypts the given message with RSA and the padding +/// scheme from PKCS#1 OAEP. The message must be no longer than the +/// length of the public modulus minus (2+ 2*hash.size()). #[inline] pub fn encrypt( rng: &mut R, pub_key: &K, msg: &[u8], - digest: &mut impl DynDigest, + digest: &mut dyn DynDigest, label: Option, ) -> Result> { key::check_public(pub_key)?; @@ -112,33 +109,23 @@ pub fn encrypt( mgf1_xor(db, digest, seed); mgf1_xor(seed, digest, db); - { - let mut m = BigUint::from_bytes_be(&em); - let mut c = internals::encrypt(pub_key, &m).to_bytes_be(); - internals::copy_with_left_pad(&mut em, &c); - - // clear out tmp values - m.zeroize(); - c.zeroize(); - } - - Ok(em) + pub_key.raw_encryption_primitive(&em) } /// Decrypts a plaintext using RSA and the padding scheme from pkcs1# OAEP -// If an `rng` is passed, it uses RSA blinding to avoid timing side-channel attacks. -// -// Note that whether this function returns an error or not discloses secret -// information. If an attacker can cause this function to run repeatedly and -// learn whether each instance returned an error then they can decrypt and -// forge signatures as if they had the private key. See -// `decrypt_session_key` for a way of solving this problem. +/// If an `rng` is passed, it uses RSA blinding to avoid timing side-channel attacks. +/// +/// Note that whether this function returns an error or not discloses secret +/// information. If an attacker can cause this function to run repeatedly and +/// learn whether each instance returned an error then they can decrypt and +/// forge signatures as if they had the private key. See +/// `decrypt_session_key` for a way of solving this problem. #[inline] pub fn decrypt( rng: Option<&mut R>, priv_key: &RSAPrivateKey, ciphertext: &[u8], - digest: &mut impl DynDigest, + digest: &mut dyn DynDigest, label: Option, ) -> Result> { key::check_public(priv_key)?; @@ -162,7 +149,7 @@ fn decrypt_inner( rng: Option<&mut R>, priv_key: &RSAPrivateKey, ciphertext: &[u8], - digest: &mut impl DynDigest, + digest: &mut dyn DynDigest, label: Option, ) -> Result<(u8, Vec, u32)> { let k = priv_key.size(); @@ -176,16 +163,7 @@ fn decrypt_inner( return Err(Error::Decryption); } - let mut em = { - let mut c = BigUint::from_bytes_be(ciphertext); - let mut m = internals::decrypt(rng, priv_key, &c)?; - let em = internals::left_pad(&m.to_bytes_be(), k); - - c.zeroize(); - m.zeroize(); - - em - }; + let mut em = priv_key.raw_decryption_primitive(rng, ciphertext)?; let label = match label { Some(l) => l, @@ -231,124 +209,3 @@ fn decrypt_inner( Ok((valid.unwrap_u8(), em, index)) } - -#[cfg(test)] -mod tests { - - use super::*; - use crate::key::RSAPublicKey; - use num_traits::FromPrimitive; - use rand::distributions::Alphanumeric; - use rand::thread_rng; - - use sha1::Sha1; - use sha2::{Sha224, Sha256, Sha384, Sha512}; - use sha3::{Sha3_256, Sha3_384, Sha3_512}; - - fn get_private_key() -> RSAPrivateKey { - // -----BEGIN RSA PRIVATE KEY----- - // MIIEpAIBAAKCAQEA05e4TZikwmE47RtpWoEG6tkdVTvwYEG2LT/cUKBB4iK49FKW - // icG4LF5xVU9d1p+i9LYVjPDb61eBGg/DJ+HyjnT+dNO8Fmweq9wbi1e5NMqL5bAL - // TymXW8yZrK9BW1m7KKZ4K7QaLDwpdrPBjbre9i8AxrsiZkAJUJbAzGDSL+fvmH11 - // xqgbENlr8pICivEQ3HzBu8Q9Iq2rN5oM1dgHjMeA/1zWIJ3qNMkiz3hPdxfkKNdb - // WuyP8w5fAUFRB2bi4KuNRzyE6HELK5gifD2wlTN600UvGeK5v7zN2BSKv2d2+lUn - // debnWVbkUimuWpxGlJurHmIvDkj1ZSSoTtNIOwIDAQABAoIBAQDE5wxokWLJTGYI - // KBkbUrTYOSEV30hqmtvoMeRY1zlYMg3Bt1VFbpNwHpcC12+wuS+Q4B0f4kgVMoH+ - // eaqXY6kvrmnY1+zRRN4p+hNb0U+Vc+NJ5FAx47dpgvWDADgmxVLomjl8Gga9IWNI - // hjDZLowrtkPXq+9wDaldaFyUFImkb1S1MW9itdLDp/G70TTLNzU6RGg/3J2V02RY - // 3iL2xEBX/nSgpDbEMI9z9NpC81xHrBanE41IOvyR5B3DoRJzguDA9RGbAiG0/GOd - // a5w4F3pt6bUm69iMONeYLAf5ig79h31Qiq4nW5RpFcAuLhEG0XXXTsZ3f16A0SwF - // PZx74eNBAoGBAPgnu/OkGHfHzFmuv0LtSynDLe/LjtloY9WwkKBaiTDdYkohydz5 - // g4Vo/foN9luEYqXyrJE9bFb5dVMr2OePsHvUBcqZpIS89Z8Bm73cs5M/K85wYwC0 - // 97EQEgxd+QGBWQZ8NdowYaVshjWlK1QnOzEnG0MR8Hld9gIeY1XhpC5hAoGBANpI - // F84Aid028q3mo/9BDHPsNL8bT2vaOEMb/t4RzvH39u+nDl+AY6Ox9uFylv+xX+76 - // CRKgMluNH9ZaVZ5xe1uWHsNFBy4OxSA9A0QdKa9NZAVKBFB0EM8dp457YRnZCexm - // 5q1iW/mVsnmks8W+fYlc18W5xMSX/ecwkW/NtOQbAoGAHabpz4AhKFbodSLrWbzv - // CUt4NroVFKdjnoodjfujfwJFF2SYMV5jN9LG3lVCxca43ulzc1tqka33Nfv8TBcg - // WHuKQZ5ASVgm5VwU1wgDMSoQOve07MWy/yZTccTc1zA0ihDXgn3bfR/NnaVh2wlh - // CkuI92eyW1494hztc7qlmqECgYEA1zenyOQ9ChDIW/ABGIahaZamNxsNRrDFMl3j - // AD+cxHSRU59qC32CQH8ShRy/huHzTaPX2DZ9EEln76fnrS4Ey7uLH0rrFl1XvT6K - // /timJgLvMEvXTx/xBtUdRN2fUqXtI9odbSyCtOYFL+zVl44HJq2UzY4pVRDrNcxs - // SUkQJqsCgYBSaNfPBzR5rrstLtTdZrjImRW1LRQeDEky9WsMDtCTYUGJTsTSfVO8 - // hkU82MpbRVBFIYx+GWIJwcZRcC7OCQoV48vMJllxMAAjqG/p00rVJ+nvA7et/nNu - // BoB0er/UmDm4Ly/97EO9A0PKMOE5YbMq9s3t3RlWcsdrU7dvw+p2+A== - // -----END RSA PRIVATE KEY----- - - RSAPrivateKey::from_components( - BigUint::parse_bytes(b"00d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b", 16).unwrap(), - BigUint::from_u64(65537).unwrap(), - BigUint::parse_bytes(b"00c4e70c689162c94c660828191b52b4d8392115df486a9adbe831e458d73958320dc1b755456e93701e9702d76fb0b92f90e01d1fe248153281fe79aa9763a92fae69d8d7ecd144de29fa135bd14f9573e349e45031e3b76982f583003826c552e89a397c1a06bd2163488630d92e8c2bb643d7abef700da95d685c941489a46f54b5316f62b5d2c3a7f1bbd134cb37353a44683fdc9d95d36458de22f6c44057fe74a0a436c4308f73f4da42f35c47ac16a7138d483afc91e41dc3a1127382e0c0f5119b0221b4fc639d6b9c38177a6de9b526ebd88c38d7982c07f98a0efd877d508aae275b946915c02e2e1106d175d74ec6777f5e80d12c053d9c7be1e341", 16).unwrap(), - vec![ - BigUint::parse_bytes(b"00f827bbf3a41877c7cc59aebf42ed4b29c32defcb8ed96863d5b090a05a8930dd624a21c9dcf9838568fdfa0df65b8462a5f2ac913d6c56f975532bd8e78fb07bd405ca99a484bcf59f019bbddcb3933f2bce706300b4f7b110120c5df9018159067c35da3061a56c8635a52b54273b31271b4311f0795df6021e6355e1a42e61",16).unwrap(), - BigUint::parse_bytes(b"00da4817ce0089dd36f2ade6a3ff410c73ec34bf1b4f6bda38431bfede11cef1f7f6efa70e5f8063a3b1f6e17296ffb15feefa0912a0325b8d1fd65a559e717b5b961ec345072e0ec5203d03441d29af4d64054a04507410cf1da78e7b6119d909ec66e6ad625bf995b279a4b3c5be7d895cd7c5b9c4c497fde730916fcdb4e41b", 16).unwrap() - ], - ) - } - - #[test] - fn test_encrypt_decrypt_oaep() { - let priv_key = get_private_key(); - do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha1::default()); - do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha224::default()); - do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha256::default()); - do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha384::default()); - do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha512::default()); - do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha3_256::default()); - do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha3_384::default()); - do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha3_512::default()); - } - - fn do_test_encrypt_decrypt_oaep(prk: &RSAPrivateKey, digest: &mut D) { - let mut rng = thread_rng(); - - let k = prk.size(); - - for i in 1..8 { - let mut input: Vec = (0..i * 8).map(|_| rng.gen()).collect(); - if input.len() > k - 11 { - input = input[0..k - 11].to_vec(); - } - let has_label: bool = rng.gen(); - let label = if has_label { - Some(rng.sample_iter(&Alphanumeric).take(30).collect()) - } else { - None - }; - - let pub_key: RSAPublicKey = prk.clone().into(); - let ciphertext = encrypt(&mut rng, &pub_key, &input, digest, label.clone()).unwrap(); - assert_ne!(input, ciphertext); - let blind: bool = rng.gen(); - let blinder = if blind { Some(&mut rng) } else { None }; - let plaintext = decrypt(blinder, &prk, &ciphertext, digest, label).unwrap(); - assert_eq!(input, plaintext); - } - } - - #[test] - fn test_decrypt_oaep_invalid_hash() { - let mut rng = thread_rng(); - let priv_key = get_private_key(); - let pub_key: RSAPublicKey = priv_key.clone().into(); - let mut digest = Sha1::default(); - let ciphertext = encrypt( - &mut rng, - &pub_key, - "a_plain_text".as_bytes(), - &mut digest, - None, - ) - .unwrap(); - assert!( - decrypt( - Some(&mut rng), - &priv_key, - &ciphertext, - &mut digest, - Some("label".to_owned()) - ) - .is_err(), - "decrypt should have failed on hash verification" - ); - } -} diff --git a/src/padding.rs b/src/padding.rs index 4525ecb2..8a15fc22 100644 --- a/src/padding.rs +++ b/src/padding.rs @@ -1,7 +1,51 @@ +use std::fmt; + +use digest::{Digest, DynDigest}; + /// Available padding schemes. -#[derive(Debug, Clone, Copy)] +#[derive(Clone)] pub enum PaddingScheme { PKCS1v15, - OAEP, + OAEP { + digest: Box, + label: Option, + }, PSS, } + +impl fmt::Debug for PaddingScheme { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PaddingScheme::PKCS1v15 => write!(f, "PaddingScheme::PKCS1v15"), + PaddingScheme::OAEP { ref label, .. } => { + // TODO: How to print the digest name? + write!(f, "PaddingScheme::OAEP({:?})", label) + } + PaddingScheme::PSS => write!(f, "PaddingScheme::PSS"), + } + } +} + +impl PaddingScheme { + pub fn new_pkcs1v15() -> Self { + PaddingScheme::PKCS1v15 + } + + pub fn new_oaep() -> Self { + PaddingScheme::OAEP { + digest: Box::new(T::new()), + label: None, + } + } + + pub fn new_oaep_with_label>(label: S) -> Self { + PaddingScheme::OAEP { + digest: Box::new(T::new()), + label: Some(label.as_ref().to_string()), + } + } + + pub fn new_pss() -> Self { + PaddingScheme::PSS + } +} From 0b9564cbe08b5f613cc6f9ffb2d3933f17489e77 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 6 Mar 2020 21:11:24 +0100 Subject: [PATCH 10/20] update readme --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b44d9a4e..e3ab26bc 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,6 @@ A portable RSA implementation in pure Rust. ## Example ```rust -extern crate rsa; -extern crate rand; - use rsa::{PublicKey, RSAPrivateKey, PaddingScheme}; use rand::rngs::OsRng; @@ -22,11 +19,11 @@ let pub_key = RSAPublicKey::from(&private_key); // Encrypt let data = b"hello world"; -let enc_data = pub_key.encrypt(&mut rng, PaddingScheme::PKCS1v15, &data[..]).expect("failed to encrypt"); +let enc_data = pub_key.encrypt(&mut rng, PaddingScheme::new_pkcs1v15(), &data[..]).expect("failed to encrypt"); assert_ne!(&data[..], &enc_data[..]); // Decrypt -let dec_data = priv_key.decrypt(PaddingScheme::PKCS1v15, &enc_data).expect("failed to decrypt"); +let dec_data = priv_key.decrypt(PaddingScheme::new_pkcs1v15(), &enc_data).expect("failed to decrypt"); assert_eq!(&data[..], &dec_data[..]); ``` @@ -43,7 +40,7 @@ There will be three phases before `1.0` :ship: can be released. - [x] PKCS1v1.5: Sign & Verify :white_check_mark: - [ ] PKCS1v1.5 (session key): Encryption & Decryption - [x] OAEP: Encryption & Decryption - - [ ] PSS: Sign & Verify + - [x] PSS: Sign & Verify - [x] Key import & export 2. :rocket: Make it fast - [x] Benchmarks :white_check_mark: From 062ab54739d68ab9b99caaed5771dc35cc62ab2c Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 6 Mar 2020 21:24:09 +0100 Subject: [PATCH 11/20] fix benches --- benches/key.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/benches/key.rs b/benches/key.rs index 5afde12d..74977fbe 100644 --- a/benches/key.rs +++ b/benches/key.rs @@ -6,9 +6,7 @@ use base64; use num_bigint::BigUint; use num_traits::{FromPrimitive, Num}; use rand::{rngs::StdRng, SeedableRng}; -use rsa::hash::Hashes; -use rsa::padding::PaddingScheme; -use rsa::RSAPrivateKey; +use rsa::{Hash, PaddingScheme, RSAPrivateKey}; use sha2::{Digest, Sha256}; use test::Bencher; @@ -33,7 +31,7 @@ fn bench_rsa_2048_pkcsv1_decrypt(b: &mut Bencher) { let x = base64::decode(DECRYPT_VAL).unwrap(); b.iter(|| { - let res = priv_key.decrypt(PaddingScheme::PKCS1v15, &x).unwrap(); + let res = priv_key.decrypt(PaddingScheme::new_pkcs1v15(), &x).unwrap(); test::black_box(res); }); } @@ -48,8 +46,7 @@ fn bench_rsa_2048_pkcsv1_sign_blinded(b: &mut Bencher) { let res = priv_key .sign_blinded( &mut rng, - PaddingScheme::PKCS1v15, - Some(&Hashes::SHA2_256), + PaddingScheme::new_pkcs1v15_with_hash(Hash::SHA2_256), &digest, ) .unwrap(); From bc9749ea3d241c0383d28f05056c5ad90b030eaa Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 6 Mar 2020 21:42:25 +0100 Subject: [PATCH 12/20] update serde test --- src/key.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/key.rs b/src/key.rs index 914d5b84..4f27a103 100644 --- a/src/key.rs +++ b/src/key.rs @@ -760,7 +760,12 @@ mod tests { let priv_tokens = [ Token::Struct { name: "RSAPrivateKey", - len: 4, + len: 3, + }, + Token::Str("pubkey_components"), + Token::Struct { + name: "RSAPublicKey", + len: 2, }, Token::Str("n"), Token::Seq { len: Some(2) }, @@ -771,6 +776,7 @@ mod tests { Token::Seq { len: Some(1) }, Token::U32(65537), Token::SeqEnd, + Token::StructEnd, Token::Str("d"), Token::Seq { len: Some(2) }, Token::U32(298985985), From 11500ed5e920b916a147107b89eaeced6dba0617 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 6 Mar 2020 23:10:55 +0100 Subject: [PATCH 13/20] make oaep and pss generic over keys --- src/algorithms.rs | 10 ---------- src/key.rs | 15 ++++++++++----- src/oaep.rs | 15 +++++++-------- src/pkcs1v15.rs | 19 +++++++++---------- src/pss.rs | 37 ++++++++++--------------------------- src/raw.rs | 25 ++++++++++++++++--------- 6 files changed, 52 insertions(+), 69 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index 5b6d3850..a02e884b 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -110,13 +110,3 @@ pub fn generate_multi_prime_key( primes, )) } - -#[inline] -pub fn copy_with_left_pad(dest: &mut [u8], src: &[u8]) { - // left pad with zeros - let padding_bytes = dest.len() - src.len(); - for el in dest.iter_mut().take(padding_bytes) { - *el = 0; - } - dest[padding_bytes..].copy_from_slice(src); -} \ No newline at end of file diff --git a/src/key.rs b/src/key.rs index 6d1678cb..49e315e1 100644 --- a/src/key.rs +++ b/src/key.rs @@ -549,7 +549,7 @@ impl RSAPrivateKey { pkcs1v15::decrypt::(None, self, ciphertext) } PaddingScheme::OAEP { mut digest, label } => { - oaep::decrypt::(None, self, ciphertext, &mut *digest, label) + oaep::decrypt::(None, self, ciphertext, &mut *digest, label) } _ => Err(Error::InvalidPaddingScheme), } @@ -583,9 +583,14 @@ impl RSAPrivateKey { mut salt_rng, mut digest, salt_len, - } => { - pss::sign::<_, ThreadRng>(&mut *salt_rng, None, self, input, salt_len, &mut *digest) - } + } => pss::sign::<_, ThreadRng, _>( + &mut *salt_rng, + None, + self, + input, + salt_len, + &mut *digest, + ), _ => Err(Error::InvalidPaddingScheme), } } @@ -607,7 +612,7 @@ impl RSAPrivateKey { mut salt_rng, mut digest, salt_len, - } => pss::sign::<_, R>( + } => pss::sign::<_, R, _>( &mut *salt_rng, Some(rng), self, diff --git a/src/oaep.rs b/src/oaep.rs index 703817cd..7335a036 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -4,8 +4,7 @@ use digest::DynDigest; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use crate::errors::{Error, Result}; -use crate::key::{self, PublicKey, PublicKeyParts, RSAPrivateKey}; -use crate::raw::DecryptionPrimitive; +use crate::key::{self, PrivateKey, PublicKey}; fn inc_counter(counter: &mut [u8]) { if counter[3] == u8::max_value() { @@ -109,7 +108,7 @@ pub fn encrypt( mgf1_xor(db, digest, seed); mgf1_xor(seed, digest, db); - pub_key.raw_encryption_primitive(&em) + pub_key.raw_encryption_primitive(&em, pub_key.size()) } /// Decrypts a plaintext using RSA and the padding scheme from pkcs1# OAEP @@ -121,9 +120,9 @@ 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 fn decrypt( rng: Option<&mut R>, - priv_key: &RSAPrivateKey, + priv_key: &SK, ciphertext: &[u8], digest: &mut dyn DynDigest, label: Option, @@ -145,9 +144,9 @@ pub fn decrypt( /// 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: &RSAPrivateKey, + priv_key: &SK, ciphertext: &[u8], digest: &mut dyn DynDigest, label: Option, @@ -163,7 +162,7 @@ fn decrypt_inner( return Err(Error::Decryption); } - let mut em = priv_key.raw_decryption_primitive(rng, ciphertext)?; + let mut em = priv_key.raw_decryption_primitive(rng, ciphertext, priv_key.size())?; let label = match label { Some(l) => l, diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index 563cf797..629acc75 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -3,14 +3,13 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use crate::errors::{Error, Result}; use crate::hash::Hash; -use crate::raw::{DecryptionPrimitive, EncryptionPrimitive}; use crate::key::{self, PrivateKey, PublicKey}; // 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(rng: &mut R, pub_key: &RSAPublicKey, msg: &[u8]) -> Result> { +pub fn encrypt(rng: &mut R, pub_key: &PK, msg: &[u8]) -> Result> { key::check_public(pub_key)?; let k = pub_key.size(); @@ -25,7 +24,7 @@ pub fn encrypt(rng: &mut R, pub_key: &RSAPublicKey, msg: &[u8]) -> Resul em[k - msg.len() - 1] = 0; em[k - msg.len()..].copy_from_slice(msg); - pub_key.raw_encryption_primitive(&em) + pub_key.raw_encryption_primitive(&em, pub_key.size()) } /// Decrypts a plaintext using RSA and the padding scheme from PKCS#1 v1.5. @@ -88,13 +87,13 @@ pub fn sign( em[k - t_len..k - hash_len].copy_from_slice(&prefix); em[k - hash_len..k].copy_from_slice(hashed); - priv_key.raw_decryption_primitive(rng, &em) + priv_key.raw_decryption_primitive(rng, &em, priv_key.size()) } /// Verifies an RSA PKCS#1 v1.5 signature. #[inline] -pub fn verify( - pub_key: &RSAPublicKey, +pub fn verify( + pub_key: &PK, hash: Option<&Hash>, hashed: &[u8], sig: &[u8], @@ -107,7 +106,7 @@ pub fn verify( return Err(Error::Verification); } - let em = pub_key.raw_encryption_primitive(sig)?; + let em = pub_key.raw_encryption_primitive(sig, pub_key.size())?; // EM = 0x00 || 0x01 || PS || 0x00 || T let mut ok = em[0].ct_eq(&0u8); @@ -160,7 +159,7 @@ fn decrypt_inner( return Err(Error::Decryption); } - let em = priv_key.raw_decryption_primitive(rng, ciphertext)?; + let em = priv_key.raw_decryption_primitive(rng, ciphertext, priv_key.size())?; let first_byte_is_zero = em[0].ct_eq(&0u8); let second_byte_is_two = em[1].ct_eq(&2u8); @@ -219,7 +218,7 @@ mod tests { use rand::thread_rng; use sha1::{Digest, Sha1}; - use crate::{Hash, PaddingScheme, PublicKey, RSAPublicKey}; + use crate::{Hash, PaddingScheme, PublicKey, PublicKeyParts, RSAPrivateKey, RSAPublicKey}; #[test] fn test_non_zero_bytes() { @@ -374,4 +373,4 @@ mod tests { .verify(PaddingScheme::new_pkcs1v15(), msg, &sig) .expect("failed to verify"); } - +} diff --git a/src/pss.rs b/src/pss.rs index d7e80dd0..71291d1c 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -1,17 +1,14 @@ use std::vec::Vec; use digest::DynDigest; -use num_bigint::BigUint; use rand::{Rng, RngCore}; use subtle::ConstantTimeEq; -use crate::algorithms::copy_with_left_pad; use crate::errors::{Error, Result}; -use crate::internals; -use crate::key::{PublicKeyParts, RSAPrivateKey, RSAPublicKey}; +use crate::key::{PrivateKey, PublicKey}; -pub fn verify( - pub_key: &RSAPublicKey, +pub fn verify( + pub_key: &PK, hashed: &[u8], sig: &[u8], digest: &mut dyn DynDigest, @@ -20,17 +17,10 @@ pub fn verify( if sig.len() != (n_bits + 7) / 8 { return Err(Error::Verification); } - let s = BigUint::from_bytes_be(sig); - let m = internals::encrypt(pub_key, &s).to_bytes_be(); + let em_bits = n_bits - 1; let em_len = (em_bits + 7) / 8; - - if em_len < m.len() { - return Err(Error::Verification); - } - - let mut em = vec![0; em_len]; - copy_with_left_pad(&mut em, &m); + let mut em = pub_key.raw_encryption_primitive(sig, em_len)?; emsa_pss_verify(hashed, &mut em, em_bits, None, digest) } @@ -39,10 +29,10 @@ pub fn verify( /// 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 fn sign( +pub fn sign( rng: &mut T, blind_rng: Option<&mut S>, - priv_key: &RSAPrivateKey, + priv_key: &SK, hashed: &[u8], salt_len: Option, digest: &mut dyn DynDigest, @@ -60,9 +50,9 @@ pub fn sign( /// 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( +fn sign_pss_with_salt( blind_rng: Option<&mut T>, - priv_key: &RSAPrivateKey, + priv_key: &SK, hashed: &[u8], salt: &[u8], digest: &mut dyn DynDigest, @@ -71,14 +61,7 @@ fn sign_pss_with_salt( let mut em = vec![0; ((n_bits - 1) + 7) / 8]; emsa_pss_encode(&mut em, hashed, n_bits - 1, salt, digest)?; - let m = BigUint::from_bytes_be(&em); - - let c = internals::decrypt_and_check(blind_rng, priv_key, &m)?.to_bytes_be(); - - let mut s = vec![0; (n_bits + 7) / 8]; - copy_with_left_pad(&mut s, &c); - - Ok(s) + priv_key.raw_decryption_primitive(blind_rng, &em, (n_bits + 7) / 8) } fn emsa_pss_encode( diff --git a/src/raw.rs b/src/raw.rs index 496b7a7c..9bf55e96 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -2,13 +2,13 @@ use num_bigint::BigUint; use rand::Rng; use zeroize::Zeroize; -use crate::errors::Result; +use crate::errors::{Error, Result}; use crate::internals; -use crate::key::{PublicKeyParts, RSAPrivateKey, RSAPublicKey}; +use crate::key::{RSAPrivateKey, RSAPublicKey}; pub trait EncryptionPrimitive { /// Do NOT use directly! Only for implementors. - fn raw_encryption_primitive(&self, plaintext: &[u8]) -> Result>; + fn raw_encryption_primitive(&self, plaintext: &[u8], pad_size: usize) -> Result>; } pub trait DecryptionPrimitive { @@ -17,15 +17,20 @@ pub trait DecryptionPrimitive { &self, rng: Option<&mut R>, ciphertext: &[u8], + pad_size: usize, ) -> Result>; } impl EncryptionPrimitive for RSAPublicKey { - fn raw_encryption_primitive(&self, plaintext: &[u8]) -> Result> { + fn raw_encryption_primitive(&self, plaintext: &[u8], pad_size: usize) -> Result> { let mut m = BigUint::from_bytes_be(plaintext); let mut c = internals::encrypt(self, &m); let mut c_bytes = c.to_bytes_be(); - let ciphertext = internals::left_pad(&c_bytes, self.size()); + let ciphertext = internals::left_pad(&c_bytes, pad_size); + + if pad_size < ciphertext.len() { + return Err(Error::Verification); + } // clear out tmp values m.zeroize(); @@ -37,8 +42,8 @@ impl EncryptionPrimitive for RSAPublicKey { } impl<'a> EncryptionPrimitive for &'a RSAPublicKey { - fn raw_encryption_primitive(&self, plaintext: &[u8]) -> Result> { - (*self).raw_encryption_primitive(plaintext) + fn raw_encryption_primitive(&self, plaintext: &[u8], pad_size: usize) -> Result> { + (*self).raw_encryption_primitive(plaintext, pad_size) } } @@ -47,11 +52,12 @@ impl DecryptionPrimitive for RSAPrivateKey { &self, rng: Option<&mut R>, ciphertext: &[u8], + pad_size: usize, ) -> Result> { let mut c = BigUint::from_bytes_be(ciphertext); let mut m = internals::decrypt_and_check(rng, self, &c)?; let mut m_bytes = m.to_bytes_be(); - let plaintext = internals::left_pad(&m_bytes, self.size()); + let plaintext = internals::left_pad(&m_bytes, pad_size); // clear tmp values c.zeroize(); @@ -67,7 +73,8 @@ impl<'a> DecryptionPrimitive for &'a RSAPrivateKey { &self, rng: Option<&mut R>, ciphertext: &[u8], + pad_size: usize, ) -> Result> { - (*self).raw_decryption_primitive(rng, ciphertext) + (*self).raw_decryption_primitive(rng, ciphertext, pad_size) } } From 668e9ddfe4a535c5f59c8ad889add365f0b65dc7 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 10 Apr 2020 14:34:25 +0200 Subject: [PATCH 14/20] apply CR --- src/algorithms.rs | 42 ++++++++++++++++++++++++++ src/key.rs | 28 ++++++++--------- src/lib.rs | 4 +-- src/oaep.rs | 59 +----------------------------------- src/padding.rs | 16 +++++----- src/parse.rs | 8 +++-- src/pkcs1v15.rs | 14 +++++---- src/pss.rs | 77 +++++------------------------------------------ 8 files changed, 88 insertions(+), 160 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index a02e884b..6998abc0 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -1,3 +1,4 @@ +use digest::DynDigest; use num_bigint::traits::ModInverse; use num_bigint::{BigUint, RandPrime}; use num_traits::{FromPrimitive, One, Zero}; @@ -110,3 +111,44 @@ pub fn generate_multi_prime_key( 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 = 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.input(digest_input.as_slice()); + let digest_output = &*digest.result_reset(); + let mut j = 0; + loop { + if j >= digest_output.len() || i >= out.len() { + break; + } + + out[i] ^= digest_output[j]; + j += 1; + i += 1; + } + inc_counter(&mut counter); + } +} + +fn inc_counter(counter: &mut [u8; 4]) { + for i in (0..4).rev() { + counter[i] = counter[i].wrapping_add(1); + if counter[i] != 0 { + // No overflow + return; + } + } +} diff --git a/src/key.rs b/src/key.rs index 49e315e1..91a9da4e 100644 --- a/src/key.rs +++ b/src/key.rs @@ -189,7 +189,7 @@ impl PublicKeyParts for RSAPublicKey { impl PublicKey for RSAPublicKey { fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { match padding { - PaddingScheme::PKCS1v15 { .. } => pkcs1v15::encrypt(rng, self, msg), + PaddingScheme::PKCS1v15Encrypt => pkcs1v15::encrypt(rng, self, msg), PaddingScheme::OAEP { mut digest, label } => { oaep::encrypt(rng, self, msg, &mut *digest, label) } @@ -199,7 +199,7 @@ impl PublicKey for RSAPublicKey { fn verify(&self, padding: PaddingScheme, hashed: &[u8], sig: &[u8]) -> Result<()> { match padding { - PaddingScheme::PKCS1v15 { ref hash } => { + PaddingScheme::PKCS1v15Sign { ref hash } => { pkcs1v15::verify(self, hash.as_ref(), hashed, sig) } PaddingScheme::PSS { mut digest, .. } => pss::verify(self, hashed, sig, &mut *digest), @@ -328,10 +328,6 @@ impl<'a> PublicKeyParts for &'a RSAPrivateKey { fn e(&self) -> &BigUint { &self.e } - - fn size(&self) -> usize { - (self.n().bits() + 7) / 8 - } } impl<'a> PrivateKey for &'a RSAPrivateKey {} @@ -545,7 +541,7 @@ impl RSAPrivateKey { 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::PKCS1v15 { .. } => { + PaddingScheme::PKCS1v15Encrypt => { pkcs1v15::decrypt::(None, self, ciphertext) } PaddingScheme::OAEP { mut digest, label } => { @@ -565,7 +561,7 @@ impl RSAPrivateKey { ciphertext: &[u8], ) -> Result> { match padding { - PaddingScheme::PKCS1v15 { .. } => pkcs1v15::decrypt(Some(rng), self, ciphertext), + PaddingScheme::PKCS1v15Encrypt => pkcs1v15::decrypt(Some(rng), self, ciphertext), PaddingScheme::OAEP { mut digest, label } => { oaep::decrypt(Some(rng), self, ciphertext, &mut *digest, label) } @@ -574,10 +570,10 @@ impl RSAPrivateKey { } /// Sign the given digest. - pub fn sign(&self, padding: PaddingScheme, input: &[u8]) -> Result> { + pub fn sign(&self, padding: PaddingScheme, digest_in: &[u8]) -> Result> { match padding { - PaddingScheme::PKCS1v15 { ref hash } => { - pkcs1v15::sign::(None, self, hash.as_ref(), input) + PaddingScheme::PKCS1v15Sign { ref hash } => { + pkcs1v15::sign::(None, self, hash.as_ref(), digest_in) } PaddingScheme::PSS { mut salt_rng, @@ -587,7 +583,7 @@ impl RSAPrivateKey { &mut *salt_rng, None, self, - input, + digest_in, salt_len, &mut *digest, ), @@ -602,11 +598,11 @@ impl RSAPrivateKey { &self, rng: &mut R, padding: PaddingScheme, - input: &[u8], + digest_in: &[u8], ) -> Result> { match padding { - PaddingScheme::PKCS1v15 { ref hash } => { - pkcs1v15::sign(Some(rng), self, hash.as_ref(), input) + PaddingScheme::PKCS1v15Sign { ref hash } => { + pkcs1v15::sign(Some(rng), self, hash.as_ref(), digest_in) } PaddingScheme::PSS { mut salt_rng, @@ -616,7 +612,7 @@ impl RSAPrivateKey { &mut *salt_rng, Some(rng), self, - input, + digest_in, salt_len, &mut *digest, ), diff --git a/src/lib.rs b/src/lib.rs index 8b0948a3..dc3c8484 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,12 +16,12 @@ //! //! // Encrypt //! let data = b"hello world"; -//! let padding = PaddingScheme::new_pkcs1v15(); +//! let padding = PaddingScheme::new_pkcs1v15_encrypt(); //! let enc_data = public_key.encrypt(&mut rng, padding, &data[..]).expect("failed to encrypt"); //! assert_ne!(&data[..], &enc_data[..]); //! //! // Decrypt -//! let padding = PaddingScheme::new_pkcs1v15(); +//! let padding = PaddingScheme::new_pkcs1v15_encrypt(); //! let dec_data = private_key.decrypt(padding, &enc_data).expect("failed to decrypt"); //! assert_eq!(&data[..], &dec_data[..]); //! ``` diff --git a/src/oaep.rs b/src/oaep.rs index 7335a036..8b2c7dbc 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -3,67 +3,10 @@ use rand::Rng; use digest::DynDigest; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; +use crate::algorithms::mgf1_xor; use crate::errors::{Error, Result}; use crate::key::{self, PrivateKey, PublicKey}; -fn inc_counter(counter: &mut [u8]) { - if counter[3] == u8::max_value() { - counter[3] = 0; - } else { - counter[3] += 1; - return; - } - - if counter[2] == u8::max_value() { - counter[2] = 0; - } else { - counter[2] += 1; - return; - } - - if counter[1] == u8::max_value() { - counter[1] = 0; - } else { - counter[1] += 1; - return; - } - - if counter[0] == u8::max_value() { - counter[0] = 0u8; - counter[1] = 0u8; - counter[2] = 0u8; - counter[3] = 0u8; - } else { - counter[0] += 1; - } -} - -/// Mask generation function. -fn mgf1_xor(out: &mut [u8], digest: &mut dyn DynDigest, seed: &[u8]) { - let mut counter = vec![0u8; 4]; - let mut i = 0; - - 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.input(digest_input.as_slice()); - let digest_output = &*digest.result_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(counter.as_mut_slice()); - } -} - /// Encrypts the given message with RSA and the padding /// scheme from PKCS#1 OAEP. The message must be no longer than the /// length of the public modulus minus (2+ 2*hash.size()). diff --git a/src/padding.rs b/src/padding.rs index b45d234b..08b94643 100644 --- a/src/padding.rs +++ b/src/padding.rs @@ -7,7 +7,8 @@ use crate::hash::Hash; /// Available padding schemes. pub enum PaddingScheme { - PKCS1v15 { + PKCS1v15Encrypt, + PKCS1v15Sign { hash: Option, }, OAEP { @@ -24,8 +25,9 @@ pub enum PaddingScheme { impl fmt::Debug for PaddingScheme { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - PaddingScheme::PKCS1v15 { ref hash } => { - write!(f, "PaddingScheme::PKCS1v15({:?})", hash) + PaddingScheme::PKCS1v15Encrypt => write!(f, "PaddingScheme::PKCS1v15Encrypt"), + PaddingScheme::PKCS1v15Sign { ref hash } => { + write!(f, "PaddingScheme::PKCS1v15Sign({:?})", hash) } PaddingScheme::OAEP { ref label, .. } => { // TODO: How to print the digest name? @@ -40,12 +42,12 @@ impl fmt::Debug for PaddingScheme { } impl PaddingScheme { - pub fn new_pkcs1v15() -> Self { - PaddingScheme::PKCS1v15 { hash: None } + pub fn new_pkcs1v15_encrypt() -> Self { + PaddingScheme::PKCS1v15Encrypt } - pub fn new_pkcs1v15_with_hash(hash: Hash) -> Self { - PaddingScheme::PKCS1v15 { hash: Some(hash) } + pub fn new_pkcs1v15_sign(hash: Option) -> Self { + PaddingScheme::PKCS1v15Sign { hash } } pub fn new_oaep() -> Self { diff --git a/src/parse.rs b/src/parse.rs index f548db07..e7db888e 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -404,11 +404,15 @@ VY8J0wvbOtL9NjCMy6zz1zQ+N7oJ9mdhNwIDAQAB let clear_text = "Hello, World!"; let encrypted = public_key - .encrypt(rng, PaddingScheme::new_pkcs1v15(), clear_text.as_bytes()) + .encrypt( + rng, + PaddingScheme::new_pkcs1v15_encrypt(), + clear_text.as_bytes(), + ) .expect("encrypt failed"); let decrypted = private_key - .decrypt(PaddingScheme::new_pkcs1v15(), &encrypted) + .decrypt(PaddingScheme::new_pkcs1v15_encrypt(), &encrypted) .expect("decrypt failed"); assert_eq!( diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index 629acc75..5e4ad496 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -276,7 +276,7 @@ mod tests { for test in &tests { let out = priv_key .decrypt( - PaddingScheme::new_pkcs1v15(), + PaddingScheme::new_pkcs1v15_encrypt(), &base64::decode(test[0]).unwrap(), ) .unwrap(); @@ -319,7 +319,7 @@ mod tests { let expected = hex::decode(test[1]).unwrap(); let out = priv_key - .sign(PaddingScheme::new_pkcs1v15_with_hash(Hash::SHA1), &digest) + .sign(PaddingScheme::new_pkcs1v15_sign(Some(Hash::SHA1)), &digest) .unwrap(); assert_ne!(out, digest); assert_eq!(out, expected); @@ -328,7 +328,7 @@ mod tests { let out2 = priv_key .sign_blinded( &mut rng, - PaddingScheme::new_pkcs1v15_with_hash(Hash::SHA1), + PaddingScheme::new_pkcs1v15_sign(Some(Hash::SHA1)), &digest, ) .unwrap(); @@ -351,7 +351,7 @@ mod tests { pub_key .verify( - PaddingScheme::new_pkcs1v15_with_hash(Hash::SHA1), + PaddingScheme::new_pkcs1v15_sign(Some(Hash::SHA1)), &digest, &sig, ) @@ -365,12 +365,14 @@ mod tests { let expected_sig = base64::decode("pX4DR8azytjdQ1rtUiC040FjkepuQut5q2ZFX1pTjBrOVKNjgsCDyiJDGZTCNoh9qpXYbhl7iEym30BWWwuiZg==").unwrap(); let priv_key = get_private_key(); - let sig = priv_key.sign(PaddingScheme::new_pkcs1v15(), msg).unwrap(); + let sig = priv_key + .sign(PaddingScheme::new_pkcs1v15_sign(None), msg) + .unwrap(); assert_eq!(expected_sig, sig); let pub_key: RSAPublicKey = priv_key.into(); pub_key - .verify(PaddingScheme::new_pkcs1v15(), msg, &sig) + .verify(PaddingScheme::new_pkcs1v15_sign(None), msg, &sig) .expect("failed to verify"); } } diff --git a/src/pss.rs b/src/pss.rs index 71291d1c..2b086be1 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -4,6 +4,7 @@ use digest::DynDigest; use rand::{Rng, RngCore}; use subtle::ConstantTimeEq; +use crate::algorithms::mgf1_xor; use crate::errors::{Error, Result}; use crate::key::{PrivateKey, PublicKey}; @@ -13,12 +14,11 @@ pub fn verify( sig: &[u8], digest: &mut dyn DynDigest, ) -> Result<()> { - let n_bits = pub_key.n().bits(); - if sig.len() != (n_bits + 7) / 8 { + if sig.len() != pub_key.size() { return Err(Error::Verification); } - let em_bits = n_bits - 1; + 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)?; @@ -37,8 +37,7 @@ pub fn sign( salt_len: Option, digest: &mut dyn DynDigest, ) -> Result> { - let salt_len = - salt_len.unwrap_or_else(|| (priv_key.n().bits() + 7) / 8 - 2 - digest.output_size()); + let salt_len = salt_len.unwrap_or_else(|| priv_key.size() - 2 - digest.output_size()); let mut salt = vec![0; salt_len]; rng.fill(&mut salt[..]); @@ -57,11 +56,11 @@ fn sign_pss_with_salt( salt: &[u8], digest: &mut dyn DynDigest, ) -> Result> { - let n_bits = priv_key.n().bits(); - let mut em = vec![0; ((n_bits - 1) + 7) / 8]; - emsa_pss_encode(&mut em, hashed, n_bits - 1, salt, digest)?; + let em_bits = priv_key.n().bits() - 1; + let mut em = vec![0; (em_bits + 7) / 8]; + emsa_pss_encode(&mut em, hashed, em_bits, salt, digest)?; - priv_key.raw_decryption_primitive(blind_rng, &em, (n_bits + 7) / 8) + priv_key.raw_decryption_primitive(blind_rng, &em, priv_key.size()) } fn emsa_pss_encode( @@ -241,66 +240,6 @@ fn emsa_pss_verify( } } -fn inc_counter(counter: &mut [u8]) { - if counter[3] == u8::max_value() { - counter[3] = 0; - } else { - counter[3] += 1; - return; - } - - if counter[2] == u8::max_value() { - counter[2] = 0; - } else { - counter[2] += 1; - return; - } - - if counter[1] == u8::max_value() { - counter[1] = 0; - } else { - counter[1] += 1; - return; - } - - if counter[0] == u8::max_value() { - counter[0] = 0u8; - counter[1] = 0u8; - counter[2] = 0u8; - counter[3] = 0u8; - } else { - counter[0] += 1; - } -} - -/// Mask generation function -/// -/// Will reset the Digest before returning. -fn mgf1_xor(out: &mut [u8], digest: &mut dyn DynDigest, seed: &[u8]) { - let mut counter = vec![0u8; 4]; - let mut i = 0; - - 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.input(digest_input.as_slice()); - let digest_output = &*digest.result_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(counter.as_mut_slice()); - } -} - #[cfg(test)] mod test { use crate::{PaddingScheme, PublicKey, RSAPrivateKey, RSAPublicKey}; From 40d7be0223627b94224c3e11721fc584e198e220 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 10 Apr 2020 14:36:10 +0200 Subject: [PATCH 15/20] document padding schemes --- src/padding.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/padding.rs b/src/padding.rs index 08b94643..6b5e2ccf 100644 --- a/src/padding.rs +++ b/src/padding.rs @@ -7,14 +7,16 @@ use crate::hash::Hash; /// Available padding schemes. pub enum PaddingScheme { + /// Encryption and Decryption using PKCS1v15 padding. PKCS1v15Encrypt, - PKCS1v15Sign { - hash: Option, - }, + /// Sign and Verify using PKCS1v15 padding. + PKCS1v15Sign { hash: Option }, + /// Encryption and Decryption using OAEP padding. OAEP { digest: Box, label: Option, }, + /// Sign and Verify using PSS padding. PSS { salt_rng: Box, digest: Box, From 2ae5f331b45db9ec4cf62ca364ce9728ca0d9ae9 Mon Sep 17 00:00:00 2001 From: Friedel Ziegelmayer Date: Sat, 11 Apr 2020 14:35:24 +0200 Subject: [PATCH 16/20] Update src/algorithms.rs Co-Authored-By: str4d --- src/algorithms.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index 6998abc0..2cd4df86 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -119,7 +119,7 @@ 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 = u32::MAX as u64 + 1; + const MAX_LEN: u64 = std::u32::MAX as u64 + 1; assert!(out.len() as u64 <= MAX_LEN); while i < out.len() { From f3f794d44b6a68dc99162aad1d3a6d8c1a1e809a Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 11 Apr 2020 14:55:03 +0200 Subject: [PATCH 17/20] CR for oaep --- src/errors.rs | 2 ++ src/oaep.rs | 55 +++++++++++++++++++++++++-------------------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 949e39bb..18d66a2c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -35,4 +35,6 @@ pub enum Error { ParseError { reason: String }, #[error("internal error")] Internal, + #[error("label too long")] + LabelTooLong, } diff --git a/src/oaep.rs b/src/oaep.rs index 8b2c7dbc..ac3b604c 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -1,12 +1,16 @@ use rand::Rng; use digest::DynDigest; -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; use crate::algorithms::mgf1_xor; use crate::errors::{Error, Result}; use crate::key::{self, PrivateKey, PublicKey}; +// 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. +const MAX_LABEL_LEN: u64 = 2_305_843_009_213_693_951; + /// Encrypts the given message with RSA and the padding /// scheme from PKCS#1 OAEP. The message must be no longer than the /// length of the public modulus minus (2+ 2*hash.size()). @@ -28,10 +32,10 @@ pub fn encrypt( return Err(Error::MessageTooLong); } - let label = match label { - Some(l) => l, - None => "".to_owned(), - }; + let label = label.unwrap_or_default(); + if label.len() as u64 > MAX_LABEL_LEN { + return Err(Error::LabelTooLong); + } let mut em = vec![0u8; k]; @@ -72,20 +76,19 @@ pub fn decrypt( ) -> Result> { key::check_public(priv_key)?; - let (valid, out, index) = decrypt_inner(rng, priv_key, ciphertext, digest, label)?; - if valid == 0 { + let res = decrypt_inner(rng, priv_key, ciphertext, digest, label)?; + if res.is_none().into() { return Err(Error::Decryption); } + let (out, index) = res.unwrap(); + Ok(out[index as usize..].to_vec()) } /// Decrypts ciphertext using `priv_key` and blinds the operation if /// `rng` is given. It returns one or zero in valid that indicates whether the -/// plaintext was correctly structured. In either case, the plaintext is -/// returned in em so that it may be read independently of whether it was valid -/// in order to maintain constant memory access patterns. If the plaintext was -/// valid then index contains the index of the original message in em. +/// plaintext was correctly structured. #[inline] fn decrypt_inner( rng: Option<&mut R>, @@ -93,7 +96,7 @@ fn decrypt_inner( ciphertext: &[u8], digest: &mut dyn DynDigest, label: Option, -) -> Result<(u8, Vec, u32)> { +) -> Result, u32)>> { let k = priv_key.size(); if k < 11 { return Err(Error::Decryption); @@ -101,16 +104,16 @@ fn decrypt_inner( let h_size = digest.output_size(); - if ciphertext.len() > k || k < h_size * 2 + 2 { + if ciphertext.len() != k || k < h_size * 2 + 2 { return Err(Error::Decryption); } let mut em = priv_key.raw_decryption_primitive(rng, ciphertext, priv_key.size())?; - let label = match label { - Some(l) => l, - None => "".to_owned(), - }; + let label = label.unwrap_or_default(); + if label.len() as u64 > MAX_LABEL_LEN { + return Err(Error::LabelTooLong); + } digest.input(label.as_bytes()); @@ -131,23 +134,19 @@ fn decrypt_inner( // looking_for_index: 1 if we are still looking for the 0x01 // index: the offset of the first 0x01 byte // zero_before_one: 1 if we saw a non-zero byte before the 1 - let mut looking_for_index = 1u8; + let mut looking_for_index = Choice::from(1u8); let mut index = 0u32; - let mut zero_before_one = 0u8; + let mut nonzero_before_one = Choice::from(0u8); for (i, el) in db.iter().skip(h_size).enumerate() { let equals0 = el.ct_eq(&0u8); let equals1 = el.ct_eq(&1u8); - index.conditional_assign(&(i as u32), Choice::from(looking_for_index) & equals1); - looking_for_index.conditional_assign(&0u8, equals1); - zero_before_one.conditional_assign(&1u8, Choice::from(looking_for_index) & !equals0); + index.conditional_assign(&(i as u32), looking_for_index & equals1); + looking_for_index &= !equals1; + nonzero_before_one |= looking_for_index & !equals0; } - let valid = first_byte_is_zero - & hash_are_equal - & !Choice::from(zero_before_one) - & !Choice::from(looking_for_index); - index = u32::conditional_select(&0, &(index + 2 + (h_size * 2) as u32), valid); + let valid = first_byte_is_zero & hash_are_equal & !nonzero_before_one & !looking_for_index; - Ok((valid.unwrap_u8(), em, index)) + Ok(CtOption::new((em, index + 2 + (h_size * 2) as u32), valid)) } From 84bfd0d9eb5e36285cdec18beaea08298297a419 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Thu, 11 Jun 2020 13:20:51 +0200 Subject: [PATCH 18/20] fix benchmarks --- benches/key.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/benches/key.rs b/benches/key.rs index 74977fbe..68271aa8 100644 --- a/benches/key.rs +++ b/benches/key.rs @@ -31,7 +31,9 @@ fn bench_rsa_2048_pkcsv1_decrypt(b: &mut Bencher) { let x = base64::decode(DECRYPT_VAL).unwrap(); b.iter(|| { - let res = priv_key.decrypt(PaddingScheme::new_pkcs1v15(), &x).unwrap(); + let res = priv_key + .decrypt(PaddingScheme::new_pkcs1v15_encrypt(), &x) + .unwrap(); test::black_box(res); }); } @@ -46,7 +48,7 @@ fn bench_rsa_2048_pkcsv1_sign_blinded(b: &mut Bencher) { let res = priv_key .sign_blinded( &mut rng, - PaddingScheme::new_pkcs1v15_with_hash(Hash::SHA2_256), + PaddingScheme::new_pkcs1v15_sign(Some(Hash::SHA2_256)), &digest, ) .unwrap(); From f3e99b434d30dd65fd94d085db32b4136b6b515a Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Thu, 11 Jun 2020 13:24:03 +0200 Subject: [PATCH 19/20] feat: update dependencies to rustcrypto 0.9 releases --- Cargo.toml | 14 +++++++------- src/algorithms.rs | 4 ++-- src/oaep.rs | 8 ++++---- src/pss.rs | 16 ++++++++-------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fb30556f..6ad34e8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,9 @@ byteorder = "1.3.1" thiserror = "1.0.11" subtle = "2.0.0" simple_asn1 = "0.4" -pem = { version = "0.7", optional = true } -digest = { version = "0.8.0", features = ["std"] } -sha2 = "0.8.0" +pem = { version = "0.8", optional = true } +digest = { version = "0.9.0", features = ["std"] } +sha2 = "0.9.0" [dependencies.zeroize] version = "1.1.0" @@ -38,13 +38,13 @@ default-features = false features = ["std", "derive"] [dev-dependencies] -base64 = "0.11.0" +base64 = "0.12.0" hex = "0.4.0" serde_test = "1.0.89" rand_xorshift = "0.2.0" -pem = "0.7" -sha-1 = "0.8.1" -sha3 = "0.8.1" +pem = "0.8" +sha-1 = "0.9.0" +sha3 = "0.9.0" [[bench]] name = "key" diff --git a/src/algorithms.rs b/src/algorithms.rs index 2cd4df86..02b4d846 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -127,8 +127,8 @@ pub fn mgf1_xor(out: &mut [u8], digest: &mut dyn DynDigest, seed: &[u8]) { digest_input[0..seed.len()].copy_from_slice(seed); digest_input[seed.len()..].copy_from_slice(&counter); - digest.input(digest_input.as_slice()); - let digest_output = &*digest.result_reset(); + 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() { diff --git a/src/oaep.rs b/src/oaep.rs index ac3b604c..00fe6392 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -46,8 +46,8 @@ pub fn encrypt( // Data block DB = pHash || PS || 01 || M let db_len = k - h_size - 1; - digest.input(label.as_bytes()); - let p_hash = digest.result_reset(); + digest.update(label.as_bytes()); + let p_hash = digest.finalize_reset(); 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); @@ -115,9 +115,9 @@ fn decrypt_inner( return Err(Error::LabelTooLong); } - digest.input(label.as_bytes()); + digest.update(label.as_bytes()); - let expected_p_hash = &*digest.result_reset(); + let expected_p_hash = &*digest.finalize_reset(); let first_byte_is_zero = em[0].ct_eq(&0u8); diff --git a/src/pss.rs b/src/pss.rs index 2b086be1..4487e404 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -109,11 +109,11 @@ fn emsa_pss_encode( // 6. Let H = Hash(M'), an octet string of length h_len. let prefix = [0u8; 8]; - hash.input(&prefix); - hash.input(m_hash); - hash.input(salt); + hash.update(&prefix); + hash.update(m_hash); + hash.update(salt); - let hashed = hash.result_reset(); + 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 @@ -227,10 +227,10 @@ fn emsa_pss_verify( // 13. Let H' = Hash(M'), an octet string of length hLen. let prefix = [0u8; 8]; - hash.input(&prefix[..]); - hash.input(m_hash); - hash.input(salt); - let h0 = hash.result_reset(); + 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 Into::::into(h0.ct_eq(h)) { From a2bafc05a863031ebf7b2290645cbeb3f276b430 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Thu, 11 Jun 2020 13:37:34 +0200 Subject: [PATCH 20/20] apply CR for pss implementation --- src/pss.rs | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/pss.rs b/src/pss.rs index 4487e404..76bbcc9d 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -57,19 +57,17 @@ fn sign_pss_with_salt( digest: &mut dyn DynDigest, ) -> Result> { let em_bits = priv_key.n().bits() - 1; - let mut em = vec![0; (em_bits + 7) / 8]; - emsa_pss_encode(&mut em, hashed, em_bits, salt, digest)?; + let em = emsa_pss_encode(hashed, em_bits, salt, digest)?; priv_key.raw_decryption_primitive(blind_rng, &em, priv_key.size()) } fn emsa_pss_encode( - em: &mut [u8], m_hash: &[u8], em_bits: usize, salt: &[u8], hash: &mut dyn DynDigest, -) -> Result<()> { +) -> Result> { // See [1], section 9.1.1 let h_len = hash.output_size(); let s_len = salt.len(); @@ -90,11 +88,9 @@ fn emsa_pss_encode( return Err(Error::Internal); } - if em.len() != em_len { - return Err(Error::Internal); - } + let mut em = vec![0; em_len]; - let (db, h) = em.split_at_mut(em_len - s_len - h_len - 2 + 1 + s_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, @@ -136,7 +132,7 @@ fn emsa_pss_encode( // 12. Let EM = maskedDB || H || 0xbc. em[em_len - 1] = 0xBC; - return Ok(()); + Ok(em) } fn emsa_pss_verify( @@ -158,7 +154,7 @@ fn emsa_pss_verify( // 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. let em_len = em.len(); //(em_bits + 7) / 8; - if em_len < h_len + 2 { + if em_len < h_len + s_len.unwrap_or_default() + 2 { return Err(Error::Verification); } @@ -171,7 +167,7 @@ fn emsa_pss_verify( // 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and // let H be the next hLen octets. let (db, h) = em.split_at_mut(em_len - h_len - 1); - let h = &mut h[..(em_len - 1) - (em_len - h_len - 1)]; + let h = &mut h[..h_len]; // 6. If the leftmost 8 * em_len - em_bits bits of the leftmost octet in // maskedDB are not all equal to zero, output "inconsistent" and @@ -204,14 +200,11 @@ fn emsa_pss_verify( // or if the octet at position emLen - hLen - sLen - 1 (the leftmost // position is "position 1") does not have hexadecimal value 0x01, // output "inconsistent" and stop. - for e in &db[..em_len - h_len - s_len - 2] { - if *e != 0x00 { - return Err(Error::Verification); - } - } - if db[em_len - h_len - s_len - 2] != 0x01 { + let (zeroes, rest) = db.split_at(em_len - h_len - s_len - 2); + if zeroes.iter().any(|e| *e != 0x00) || rest[0] != 0x01 { return Err(Error::Verification); } + s_len } }; @@ -233,7 +226,7 @@ fn emsa_pss_verify( let h0 = hash.finalize_reset(); // 14. If H = H', output "consistent." Otherwise, output "inconsistent." - if Into::::into(h0.ct_eq(h)) { + if h0.ct_eq(h).into() { Ok(()) } else { Err(Error::Verification)