From ef9650b8b7522aa7e267302c7b7c67f7bb485cd0 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Thu, 5 Jan 2023 20:10:15 -0700 Subject: [PATCH] Refactor `PaddingScheme` into a trait Splits up the `PaddingScheme` enum into four structs, named after the previous variants of the struct (adopting capitalization from the Rust API guidelines): - `oaep::Oaep` - `pkcs1v15::{Pkcs1v15Encrypt, Pkcs1v15Sign}` - `pss::Pss` All of these are re-exported from the toplevel. Each of these structs impls one or more of the following traits: - `PaddingScheme`: used for encryption - `SignatureScheme`: used for signing The `PaddingScheme` constructors have been remapped as follows: - `new_oaep` => `Oaep::new` - `new_oaep_with_label` => `Oaep::new_with_label` - `new_oaep_with_mgf_hash` => `Oaep::new_with_mgf_hash` - `new_oaep_with_mgf_hash_with_label` => `Oaep::new_with_mgf_hash_and_label` - `new_pkcs1v15_encrypt` => `Pkcs1v15Encrypt` - `new_pkcs1v15_sign` => `Pkcs1v15Sign::new` - `new_pkcs1v15_sign_raw` => `Pkcs1v15Sign::new_raw` - `new_pss` => `Pss::{new, new_blinded}` - `new_pss_with_salt` => `Pss::{new_with_salt new_blinded_with_salt}` --- src/key.rs | 173 ++++++++--------------------------- src/lib.rs | 23 ++--- src/oaep.rs | 155 +++++++++++++++++++++++++++++++- src/padding.rs | 235 +++++++++--------------------------------------- src/pkcs1v15.rs | 117 +++++++++++++++++++----- src/pss.rs | 108 +++++++++++++++++++--- 6 files changed, 438 insertions(+), 373 deletions(-) diff --git a/src/key.rs b/src/key.rs index bebdeb8f..7e7379c2 100644 --- a/src/key.rs +++ b/src/key.rs @@ -13,9 +13,8 @@ use crate::algorithms::{generate_multi_prime_key, generate_multi_prime_key_with_ use crate::dummy_rng::DummyRng; use crate::errors::{Error, Result}; -use crate::padding::PaddingScheme; +use crate::padding::{PaddingScheme, SignatureScheme}; use crate::raw::{DecryptionPrimitive, EncryptionPrimitive}; -use crate::{oaep, pkcs1v15, pss}; /// Components of an RSA public key. pub trait PublicKeyParts { @@ -173,18 +172,20 @@ impl From<&RsaPrivateKey> for RsaPublicKey { /// Generic trait for operations on a public key. pub trait PublicKey: EncryptionPrimitive + PublicKeyParts { /// Encrypt the given message. - fn encrypt( + fn encrypt( &self, - rng: &mut R, - padding: PaddingScheme, + rng: &mut impl CryptoRngCore, + padding: impl PaddingScheme, msg: &[u8], ) -> Result>; /// Verify a signed message. - /// `hashed`must be the result of hashing the input using the hashing function + /// + /// `hashed` must be the result of hashing the input using the hashing function /// passed in through `hash`. - /// If the message is valid `Ok(())` is returned, otherwiese an `Err` indicating failure. - fn verify(&self, padding: PaddingScheme, hashed: &[u8], sig: &[u8]) -> Result<()>; + /// + /// If the message is valid `Ok(())` is returned, otherwise an `Err` indicating failure. + fn verify(&self, padding: impl SignatureScheme, hashed: &[u8], sig: &[u8]) -> Result<()>; } impl PublicKeyParts for RsaPublicKey { @@ -198,36 +199,17 @@ impl PublicKeyParts for RsaPublicKey { } impl PublicKey for RsaPublicKey { - fn encrypt( + fn encrypt( &self, - rng: &mut R, - padding: PaddingScheme, + rng: &mut impl CryptoRngCore, + padding: impl PaddingScheme, msg: &[u8], ) -> Result> { - match padding { - PaddingScheme::PKCS1v15Encrypt => pkcs1v15::encrypt(rng, self, msg), - PaddingScheme::OAEP { - mut digest, - mut mgf_digest, - label, - } => oaep::encrypt(rng, self, msg, &mut *digest, &mut *mgf_digest, label), - _ => Err(Error::InvalidPaddingScheme), - } + padding.encrypt(rng, self, msg) } - fn verify(&self, padding: PaddingScheme, hashed: &[u8], sig: &[u8]) -> Result<()> { - match padding { - PaddingScheme::PKCS1v15Sign { hash_len, prefix } => { - if let Some(hash_len) = hash_len { - if hashed.len() != hash_len { - return Err(Error::InputNotHashed); - } - } - pkcs1v15::verify(self, prefix.as_ref(), hashed, sig) - } - PaddingScheme::PSS { mut digest, .. } => pss::verify(self, hashed, sig, &mut *digest), - _ => Err(Error::InvalidPaddingScheme), - } + fn verify(&self, padding: impl SignatureScheme, hashed: &[u8], sig: &[u8]) -> Result<()> { + padding.verify(self, hashed, sig) } } @@ -448,113 +430,37 @@ impl RsaPrivateKey { } /// Decrypt the given message. - pub fn decrypt(&self, padding: PaddingScheme, ciphertext: &[u8]) -> Result> { - match padding { - // need to pass any Rng as the type arg, so the type checker is happy, it is not actually used for anything - PaddingScheme::PKCS1v15Encrypt => { - pkcs1v15::decrypt::(None, self, ciphertext) - } - PaddingScheme::OAEP { - mut digest, - mut mgf_digest, - label, - } => oaep::decrypt::( - None, - self, - ciphertext, - &mut *digest, - &mut *mgf_digest, - label, - ), - _ => Err(Error::InvalidPaddingScheme), - } + pub fn decrypt(&self, padding: impl PaddingScheme, ciphertext: &[u8]) -> Result> { + padding.decrypt(Option::<&mut DummyRng>::None, self, ciphertext) } /// Decrypt the given message. /// /// Uses `rng` to blind the decryption process. - pub fn decrypt_blinded( + pub fn decrypt_blinded( &self, - rng: &mut R, - padding: PaddingScheme, + rng: &mut impl CryptoRngCore, + padding: impl PaddingScheme, ciphertext: &[u8], ) -> Result> { - match padding { - PaddingScheme::PKCS1v15Encrypt => pkcs1v15::decrypt(Some(rng), self, ciphertext), - PaddingScheme::OAEP { - mut digest, - mut mgf_digest, - label, - } => oaep::decrypt( - Some(rng), - self, - ciphertext, - &mut *digest, - &mut *mgf_digest, - label, - ), - _ => Err(Error::InvalidPaddingScheme), - } + padding.decrypt(Some(rng), self, ciphertext) } /// Sign the given digest. - pub fn sign(&self, padding: PaddingScheme, digest_in: &[u8]) -> Result> { - match padding { - // need to pass any Rng as the type arg, so the type checker is happy, it is not actually used for anything - PaddingScheme::PKCS1v15Sign { hash_len, prefix } => { - if let Some(hash_len) = hash_len { - if digest_in.len() != hash_len { - return Err(Error::InputNotHashed); - } - } - pkcs1v15::sign::(None, self, prefix.as_ref(), digest_in) - } - _ => Err(Error::InvalidPaddingScheme), - } + pub fn sign(&self, padding: impl SignatureScheme, digest_in: &[u8]) -> Result> { + padding.sign(Option::<&mut DummyRng>::None, self, digest_in) } /// Sign the given digest using the provided rng /// /// Use `rng` for signature process. - pub fn sign_with_rng( - &self, - rng: &mut R, - padding: PaddingScheme, - digest_in: &[u8], - ) -> Result> { - match padding { - PaddingScheme::PSS { - mut digest, - salt_len, - } => pss::sign::(rng, false, self, digest_in, salt_len, &mut *digest), - _ => Err(Error::InvalidPaddingScheme), - } - } - - /// Sign the given digest. - /// - /// Use `rng` for blinding. - pub fn sign_blinded( + pub fn sign_with_rng( &self, - rng: &mut R, - padding: PaddingScheme, + rng: &mut impl CryptoRngCore, + padding: impl SignatureScheme, digest_in: &[u8], ) -> Result> { - match padding { - PaddingScheme::PKCS1v15Sign { hash_len, prefix } => { - if let Some(hash_len) = hash_len { - if digest_in.len() != hash_len { - return Err(Error::InputNotHashed); - } - } - pkcs1v15::sign(Some(rng), self, prefix.as_ref(), digest_in) - } - PaddingScheme::PSS { - mut digest, - salt_len, - } => pss::sign::(rng, true, self, digest_in, salt_len, &mut *digest), - _ => Err(Error::InvalidPaddingScheme), - } + padding.sign(Some(rng), self, digest_in) } } @@ -591,6 +497,7 @@ fn check_public_with_max_size(public_key: &impl PublicKeyParts, max_size: usize) mod tests { use super::*; use crate::internals; + use crate::oaep::Oaep; use alloc::string::String; use digest::{Digest, DynDigest}; @@ -965,10 +872,10 @@ mod tests { let pub_key: RsaPublicKey = prk.into(); let ciphertext = if let Some(ref label) = label { - let padding = PaddingScheme::new_oaep_with_label::(label); + let padding = Oaep::new_with_label::(label); pub_key.encrypt(&mut rng, padding, &input).unwrap() } else { - let padding = PaddingScheme::new_oaep::(); + let padding = Oaep::new::(); pub_key.encrypt(&mut rng, padding, &input).unwrap() }; @@ -976,9 +883,9 @@ mod tests { let blind: bool = rng.next_u32() < (1 << 31); let padding = if let Some(ref label) = label { - PaddingScheme::new_oaep_with_label::(label) + Oaep::new_with_label::(label) } else { - PaddingScheme::new_oaep::() + Oaep::new::() }; let plaintext = if blind { @@ -1013,10 +920,10 @@ mod tests { let pub_key: RsaPublicKey = prk.into(); let ciphertext = if let Some(ref label) = label { - let padding = PaddingScheme::new_oaep_with_mgf_hash_with_label::(label); + let padding = Oaep::new_with_mgf_hash_and_label::(label); pub_key.encrypt(&mut rng, padding, &input).unwrap() } else { - let padding = PaddingScheme::new_oaep_with_mgf_hash::(); + let padding = Oaep::new_with_mgf_hash::(); pub_key.encrypt(&mut rng, padding, &input).unwrap() }; @@ -1024,9 +931,9 @@ mod tests { let blind: bool = rng.next_u32() < (1 << 31); let padding = if let Some(ref label) = label { - PaddingScheme::new_oaep_with_mgf_hash_with_label::(label) + Oaep::new_with_mgf_hash_and_label::(label) } else { - PaddingScheme::new_oaep_with_mgf_hash::() + Oaep::new_with_mgf_hash::() }; let plaintext = if blind { @@ -1044,17 +951,13 @@ mod tests { 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(), - ) + .encrypt(&mut rng, Oaep::new::(), "a_plain_text".as_bytes()) .unwrap(); assert!( priv_key .decrypt_blinded( &mut rng, - PaddingScheme::new_oaep_with_label::("label"), + Oaep::new_with_label::("label"), &ciphertext, ) .is_err(), diff --git a/src/lib.rs b/src/lib.rs index c9e690b0..bcbe1bd7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ //! //! ## PKCS#1 v1.5 encryption //! ``` -//! use rsa::{PublicKey, RsaPrivateKey, RsaPublicKey, PaddingScheme}; +//! use rsa::{PublicKey, RsaPrivateKey, RsaPublicKey, Pkcs1v15Encrypt}; //! //! let mut rng = rand::thread_rng(); //! @@ -23,19 +23,17 @@ //! //! // Encrypt //! let data = b"hello world"; -//! let padding = PaddingScheme::new_pkcs1v15_encrypt(); -//! let enc_data = public_key.encrypt(&mut rng, padding, &data[..]).expect("failed to encrypt"); +//! let enc_data = public_key.encrypt(&mut rng, Pkcs1v15Encrypt, &data[..]).expect("failed to encrypt"); //! assert_ne!(&data[..], &enc_data[..]); //! //! // Decrypt -//! let padding = PaddingScheme::new_pkcs1v15_encrypt(); -//! let dec_data = private_key.decrypt(padding, &enc_data).expect("failed to decrypt"); +//! let dec_data = private_key.decrypt(Pkcs1v15Encrypt, &enc_data).expect("failed to decrypt"); //! assert_eq!(&data[..], &dec_data[..]); //! ``` //! //! ## OAEP encryption //! ``` -//! use rsa::{PublicKey, RsaPrivateKey, RsaPublicKey, PaddingScheme}; +//! use rsa::{PublicKey, RsaPrivateKey, RsaPublicKey, Oaep}; //! //! let mut rng = rand::thread_rng(); //! @@ -45,12 +43,12 @@ //! //! // Encrypt //! let data = b"hello world"; -//! let padding = PaddingScheme::new_oaep::(); +//! let padding = Oaep::new::(); //! let enc_data = public_key.encrypt(&mut rng, padding, &data[..]).expect("failed to encrypt"); //! assert_ne!(&data[..], &enc_data[..]); //! //! // Decrypt -//! let padding = PaddingScheme::new_oaep::(); +//! let padding = Oaep::new::(); //! let dec_data = private_key.decrypt(padding, &enc_data).expect("failed to decrypt"); //! assert_eq!(&data[..], &dec_data[..]); //! ``` @@ -227,8 +225,13 @@ mod raw; pub use pkcs1; pub use pkcs8; -pub use self::key::{PublicKey, PublicKeyParts, RsaPrivateKey, RsaPublicKey}; -pub use self::padding::PaddingScheme; +pub use crate::{ + key::{PublicKey, PublicKeyParts, RsaPrivateKey, RsaPublicKey}, + oaep::Oaep, + padding::{PaddingScheme, SignatureScheme}, + pkcs1v15::{Pkcs1v15Encrypt, Pkcs1v15Sign}, + pss::Pss, +}; /// Internal raw RSA functions. #[cfg(not(feature = "expose-internals"))] diff --git a/src/oaep.rs b/src/oaep.rs index 82d55c11..fb165378 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -1,20 +1,169 @@ -use alloc::string::String; +use alloc::boxed::Box; +use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; +use core::fmt; use rand_core::CryptoRngCore; -use digest::DynDigest; +use digest::{Digest, DynDigest}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; use zeroize::Zeroizing; use crate::algorithms::mgf1_xor; use crate::errors::{Error, Result}; use crate::key::{self, PrivateKey, PublicKey}; +use crate::padding::PaddingScheme; // 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; +/// Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1). +/// +/// - `digest` is used to hash the label. The maximum possible plaintext length is `m = k - 2 * h_len - 2`, +/// where `k` is the size of the RSA modulus. +/// - `mgf_digest` specifies the hash function that is used in the [MGF1](https://datatracker.ietf.org/doc/html/rfc8017#appendix-B.2). +/// - `label` is optional data that can be associated with the message. +/// +/// The two hash functions can, but don't need to be the same. +/// +/// A prominent example is the [`AndroidKeyStore`](https://developer.android.com/guide/topics/security/cryptography#oaep-mgf1-digest). +/// It uses SHA-1 for `mgf_digest` and a user-chosen SHA flavour for `digest`. +pub struct Oaep { + /// Digest type to use. + pub digest: Box, + + /// Digest to use for Mask Generation Function (MGF). + pub mgf_digest: Box, + + /// Optional label. + pub label: Option, +} + +impl Oaep { + /// Create a new OAEP `PaddingScheme`, using `T` as the hash function for both the default (empty) label and for MGF1. + /// + /// # Example + /// ``` + /// use sha1::Sha1; + /// use sha2::Sha256; + /// use rsa::{BigUint, RsaPublicKey, Oaep, PublicKey}; + /// use base64ct::{Base64, Encoding}; + /// + /// let n = Base64::decode_vec("ALHgDoZmBQIx+jTmgeeHW6KsPOrj11f6CvWsiRleJlQpW77AwSZhd21ZDmlTKfaIHBSUxRUsuYNh7E2SHx8rkFVCQA2/gXkZ5GK2IUbzSTio9qXA25MWHvVxjMfKSL8ZAxZyKbrG94FLLszFAFOaiLLY8ECs7g+dXOriYtBwLUJK+lppbd+El+8ZA/zH0bk7vbqph5pIoiWggxwdq3mEz4LnrUln7r6dagSQzYErKewY8GADVpXcq5mfHC1xF2DFBub7bFjMVM5fHq7RK+pG5xjNDiYITbhLYrbVv3X0z75OvN0dY49ITWjM7xyvMWJXVJS7sJlgmCCL6RwWgP8PhcE=").unwrap(); + /// let e = Base64::decode_vec("AQAB").unwrap(); + /// + /// let mut rng = rand::thread_rng(); + /// let key = RsaPublicKey::new(BigUint::from_bytes_be(&n), BigUint::from_bytes_be(&e)).unwrap(); + /// let padding = Oaep::new::(); + /// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap(); + /// ``` + pub fn new() -> Self { + Self { + digest: Box::new(T::new()), + mgf_digest: Box::new(T::new()), + label: None, + } + } + + /// Create a new OAEP `PaddingScheme` with an associated `label`, using `T` as the hash function for both the label and for MGF1. + pub fn new_with_label>( + label: S, + ) -> Self { + Self { + digest: Box::new(T::new()), + mgf_digest: Box::new(T::new()), + label: Some(label.as_ref().to_string()), + } + } + + /// Create a new OAEP `PaddingScheme`, using `T` as the hash function for the default (empty) label, and `U` as the hash function for MGF1. + /// If a label is needed use `PaddingScheme::new_oaep_with_label` or `PaddingScheme::new_oaep_with_mgf_hash_with_label`. + /// + /// # Example + /// ``` + /// use sha1::Sha1; + /// use sha2::Sha256; + /// use rsa::{BigUint, RsaPublicKey, Oaep, PublicKey}; + /// use base64ct::{Base64, Encoding}; + /// + /// let n = Base64::decode_vec("ALHgDoZmBQIx+jTmgeeHW6KsPOrj11f6CvWsiRleJlQpW77AwSZhd21ZDmlTKfaIHBSUxRUsuYNh7E2SHx8rkFVCQA2/gXkZ5GK2IUbzSTio9qXA25MWHvVxjMfKSL8ZAxZyKbrG94FLLszFAFOaiLLY8ECs7g+dXOriYtBwLUJK+lppbd+El+8ZA/zH0bk7vbqph5pIoiWggxwdq3mEz4LnrUln7r6dagSQzYErKewY8GADVpXcq5mfHC1xF2DFBub7bFjMVM5fHq7RK+pG5xjNDiYITbhLYrbVv3X0z75OvN0dY49ITWjM7xyvMWJXVJS7sJlgmCCL6RwWgP8PhcE=").unwrap(); + /// let e = Base64::decode_vec("AQAB").unwrap(); + /// + /// let mut rng = rand::thread_rng(); + /// let key = RsaPublicKey::new(BigUint::from_bytes_be(&n), BigUint::from_bytes_be(&e)).unwrap(); + /// let padding = Oaep::new_with_mgf_hash::(); + /// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap(); + /// ``` + pub fn new_with_mgf_hash< + T: 'static + Digest + DynDigest + Send + Sync, + U: 'static + Digest + DynDigest + Send + Sync, + >() -> Self { + Self { + digest: Box::new(T::new()), + mgf_digest: Box::new(U::new()), + label: None, + } + } + + /// Create a new OAEP `PaddingScheme` with an associated `label`, using `T` as the hash function for the label, and `U` as the hash function for MGF1. + pub fn new_with_mgf_hash_and_label< + T: 'static + Digest + DynDigest + Send + Sync, + U: 'static + Digest + DynDigest + Send + Sync, + S: AsRef, + >( + label: S, + ) -> Self { + Self { + digest: Box::new(T::new()), + mgf_digest: Box::new(U::new()), + label: Some(label.as_ref().to_string()), + } + } +} + +impl PaddingScheme for Oaep { + fn decrypt( + mut self, + rng: Option<&mut impl CryptoRngCore>, + priv_key: &impl PrivateKey, + ciphertext: &[u8], + ) -> Result> { + decrypt( + rng, + priv_key, + ciphertext, + &mut *self.digest, + &mut *self.mgf_digest, + self.label, + ) + } + + fn encrypt( + mut self, + rng: &mut impl CryptoRngCore, + pub_key: &impl PublicKey, + msg: &[u8], + ) -> Result> { + encrypt( + rng, + pub_key, + msg, + &mut *self.digest, + &mut *self.mgf_digest, + self.label, + ) + } +} + +impl fmt::Debug for Oaep { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OAEP") + .field("label", &self.label) + .finish_non_exhaustive() + } +} + /// Encrypts the given message with RSA and the padding scheme from /// [PKCS#1 OAEP]. /// @@ -57,7 +206,7 @@ pub fn encrypt( digest.update(label.as_bytes()); let p_hash = digest.finalize_reset(); - db[0..h_size].copy_from_slice(&*p_hash); + 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); diff --git a/src/padding.rs b/src/padding.rs index f2545143..890ccd1f 100644 --- a/src/padding.rs +++ b/src/padding.rs @@ -1,202 +1,49 @@ //! Supported padding schemes. -use alloc::boxed::Box; -use alloc::string::{String, ToString}; -use core::fmt; - -use digest::{Digest, DynDigest}; -use pkcs8::AssociatedOid; - -use crate::pkcs1v15; - -/// Available padding schemes. -pub enum PaddingScheme { - /// Encryption and Decryption using PKCS1v15 padding. - PKCS1v15Encrypt, - - /// Sign and Verify using PKCS1v15 padding. - PKCS1v15Sign { - /// Length of hash to use. - hash_len: Option, - - /// Prefix. - prefix: Box<[u8]>, - }, - - /// Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1). - /// - /// - `digest` is used to hash the label. The maximum possible plaintext length is `m = k - 2 * h_len - 2`, - /// where `k` is the size of the RSA modulus. - /// - `mgf_digest` specifies the hash function that is used in the [MGF1](https://datatracker.ietf.org/doc/html/rfc8017#appendix-B.2). - /// - `label` is optional data that can be associated with the message. - /// - /// The two hash functions can, but don't need to be the same. - /// - /// A prominent example is the [`AndroidKeyStore`](https://developer.android.com/guide/topics/security/cryptography#oaep-mgf1-digest). - /// It uses SHA-1 for `mgf_digest` and a user-chosen SHA flavour for `digest`. - OAEP { - /// Digest type to use. - digest: Box, - - /// Digest to use for Mask Generation Function (MGF). - mgf_digest: Box, - - /// Optional label. - label: Option, - }, - - /// Sign and Verify using PSS padding. - PSS { - /// Digest type to use. - digest: Box, - - /// Salt length. - salt_len: Option, - }, -} - -impl fmt::Debug for PaddingScheme { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PaddingScheme::PKCS1v15Encrypt => write!(f, "PaddingScheme::PKCS1v15Encrypt"), - PaddingScheme::PKCS1v15Sign { prefix, .. } => { - write!(f, "PaddingScheme::PKCS1v15Sign({:?})", prefix) - } - PaddingScheme::OAEP { ref label, .. } => { - // TODO: How to print the digest name? - write!(f, "PaddingScheme::OAEP({:?})", label) - } - PaddingScheme::PSS { ref salt_len, .. } => { - // TODO: How to print the digest name? - write!(f, "PaddingScheme::PSS(salt_len: {:?})", salt_len) - } - } - } +use alloc::vec::Vec; + +use rand_core::CryptoRngCore; + +use crate::errors::Result; +use crate::key::{PrivateKey, PublicKey}; + +/// Padding scheme used for encryption. +pub trait PaddingScheme { + /// Decrypt the given message using the given private key. + /// + /// If an `rng` is passed, it uses RSA blinding to help mitigate timing + /// side-channel attacks. + fn decrypt( + self, + rng: Option<&mut impl CryptoRngCore>, + priv_key: &impl PrivateKey, + ciphertext: &[u8], + ) -> Result>; + + /// Encrypt the given message using the given public key. + fn encrypt( + self, + rng: &mut impl CryptoRngCore, + pub_key: &impl PublicKey, + msg: &[u8], + ) -> Result>; } -impl PaddingScheme { - /// Create new PKCS#1 v1.5 encryption padding. - pub fn new_pkcs1v15_encrypt() -> Self { - PaddingScheme::PKCS1v15Encrypt - } - - /// Create new PKCS#1 v1.5 padding for computing a raw signature. - /// - /// This sets `hash_len` to `None` and uses an empty `prefix`. - pub fn new_pkcs1v15_sign_raw() -> Self { - PaddingScheme::PKCS1v15Sign { - hash_len: None, - prefix: Box::new([]), - } - } - - /// Create new PKCS#1 v1.5 padding for the given digest. - /// - /// The digest must have an [`AssociatedOid`]. Make sure to enable the `oid` - /// feature of the relevant digest crate. - pub fn new_pkcs1v15_sign() -> Self - where - D: Digest + AssociatedOid, - { - PaddingScheme::PKCS1v15Sign { - hash_len: Some(::output_size()), - prefix: pkcs1v15::generate_prefix::().into_boxed_slice(), - } - } - - /// Create a new OAEP `PaddingScheme`, using `T` as the hash function for the default (empty) label, and `U` as the hash function for MGF1. - /// If a label is needed use `PaddingScheme::new_oaep_with_label` or `PaddingScheme::new_oaep_with_mgf_hash_with_label`. - /// - /// # Example - /// ``` - /// use sha1::Sha1; - /// use sha2::Sha256; - /// use rsa::{BigUint, RsaPublicKey, PaddingScheme, PublicKey}; - /// use base64ct::{Base64, Encoding}; - /// - /// let n = Base64::decode_vec("ALHgDoZmBQIx+jTmgeeHW6KsPOrj11f6CvWsiRleJlQpW77AwSZhd21ZDmlTKfaIHBSUxRUsuYNh7E2SHx8rkFVCQA2/gXkZ5GK2IUbzSTio9qXA25MWHvVxjMfKSL8ZAxZyKbrG94FLLszFAFOaiLLY8ECs7g+dXOriYtBwLUJK+lppbd+El+8ZA/zH0bk7vbqph5pIoiWggxwdq3mEz4LnrUln7r6dagSQzYErKewY8GADVpXcq5mfHC1xF2DFBub7bFjMVM5fHq7RK+pG5xjNDiYITbhLYrbVv3X0z75OvN0dY49ITWjM7xyvMWJXVJS7sJlgmCCL6RwWgP8PhcE=").unwrap(); - /// let e = Base64::decode_vec("AQAB").unwrap(); - /// - /// let mut rng = rand::thread_rng(); - /// let key = RsaPublicKey::new(BigUint::from_bytes_be(&n), BigUint::from_bytes_be(&e)).unwrap(); - /// let padding = PaddingScheme::new_oaep_with_mgf_hash::(); - /// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap(); - /// ``` - pub fn new_oaep_with_mgf_hash< - T: 'static + Digest + DynDigest + Send + Sync, - U: 'static + Digest + DynDigest + Send + Sync, - >() -> Self { - PaddingScheme::OAEP { - digest: Box::new(T::new()), - mgf_digest: Box::new(U::new()), - label: None, - } - } +/// Digital signature scheme. +pub trait SignatureScheme { + /// Sign the given digest. + fn sign( + self, + rng: Option<&mut impl CryptoRngCore>, + priv_key: &impl PrivateKey, + hashed: &[u8], + ) -> Result>; - /// Create a new OAEP `PaddingScheme`, using `T` as the hash function for both the default (empty) label and for MGF1. + /// Verify a signed message. /// - /// # Example - /// ``` - /// use sha1::Sha1; - /// use sha2::Sha256; - /// use rsa::{BigUint, RsaPublicKey, PaddingScheme, PublicKey}; - /// use base64ct::{Base64, Encoding}; + /// `hashed` must be the result of hashing the input using the hashing function + /// passed in through `hash`. /// - /// let n = Base64::decode_vec("ALHgDoZmBQIx+jTmgeeHW6KsPOrj11f6CvWsiRleJlQpW77AwSZhd21ZDmlTKfaIHBSUxRUsuYNh7E2SHx8rkFVCQA2/gXkZ5GK2IUbzSTio9qXA25MWHvVxjMfKSL8ZAxZyKbrG94FLLszFAFOaiLLY8ECs7g+dXOriYtBwLUJK+lppbd+El+8ZA/zH0bk7vbqph5pIoiWggxwdq3mEz4LnrUln7r6dagSQzYErKewY8GADVpXcq5mfHC1xF2DFBub7bFjMVM5fHq7RK+pG5xjNDiYITbhLYrbVv3X0z75OvN0dY49ITWjM7xyvMWJXVJS7sJlgmCCL6RwWgP8PhcE=").unwrap(); - /// let e = Base64::decode_vec("AQAB").unwrap(); - /// - /// let mut rng = rand::thread_rng(); - /// let key = RsaPublicKey::new(BigUint::from_bytes_be(&n), BigUint::from_bytes_be(&e)).unwrap(); - /// let padding = PaddingScheme::new_oaep::(); - /// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap(); - /// ``` - pub fn new_oaep() -> Self { - PaddingScheme::OAEP { - digest: Box::new(T::new()), - mgf_digest: Box::new(T::new()), - label: None, - } - } - - /// Create a new OAEP `PaddingScheme` with an associated `label`, using `T` as the hash function for the label, and `U` as the hash function for MGF1. - pub fn new_oaep_with_mgf_hash_with_label< - T: 'static + Digest + DynDigest + Send + Sync, - U: 'static + Digest + DynDigest + Send + Sync, - S: AsRef, - >( - label: S, - ) -> Self { - PaddingScheme::OAEP { - digest: Box::new(T::new()), - mgf_digest: Box::new(U::new()), - label: Some(label.as_ref().to_string()), - } - } - - /// Create a new OAEP `PaddingScheme` with an associated `label`, using `T` as the hash function for both the label and for MGF1. - pub fn new_oaep_with_label>( - label: S, - ) -> Self { - PaddingScheme::OAEP { - digest: Box::new(T::new()), - mgf_digest: Box::new(T::new()), - label: Some(label.as_ref().to_string()), - } - } - - /// New PSS padding for the given digest. - pub fn new_pss() -> Self { - PaddingScheme::PSS { - digest: Box::new(T::new()), - salt_len: None, - } - } - - /// New PSS padding for the given digest with a salt value of the given length. - pub fn new_pss_with_salt(len: usize) -> Self { - PaddingScheme::PSS { - digest: Box::new(T::new()), - salt_len: Some(len), - } - } + /// If the message is valid `Ok(())` is returned, otherwise an `Err` indicating failure. + fn verify(self, pub_key: &impl PublicKey, hashed: &[u8], sig: &[u8]) -> Result<()>; } diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index e8fe3d78..2d129d91 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -24,8 +24,96 @@ use zeroize::Zeroizing; use crate::dummy_rng::DummyRng; use crate::errors::{Error, Result}; use crate::key::{self, PrivateKey, PublicKey}; +use crate::padding::{PaddingScheme, SignatureScheme}; use crate::{RsaPrivateKey, RsaPublicKey}; +/// Encryption using PKCS#1 v1.5 padding. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct Pkcs1v15Encrypt; + +impl PaddingScheme for Pkcs1v15Encrypt { + fn decrypt( + self, + rng: Option<&mut impl CryptoRngCore>, + priv_key: &impl PrivateKey, + ciphertext: &[u8], + ) -> Result> { + decrypt(rng, priv_key, ciphertext) + } + + fn encrypt( + self, + rng: &mut impl CryptoRngCore, + pub_key: &impl PublicKey, + msg: &[u8], + ) -> Result> { + encrypt(rng, pub_key, msg) + } +} + +/// Digital signatures using PKCS#1 v1.5 padding. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Pkcs1v15Sign { + /// Length of hash to use. + pub hash_len: Option, + + /// Prefix. + pub prefix: Box<[u8]>, +} + +impl Pkcs1v15Sign { + /// Create new PKCS#1 v1.5 padding for the given digest. + /// + /// The digest must have an [`AssociatedOid`]. Make sure to enable the `oid` + /// feature of the relevant digest crate. + pub fn new() -> Self + where + D: Digest + AssociatedOid, + { + Self { + hash_len: Some(::output_size()), + prefix: generate_prefix::().into_boxed_slice(), + } + } + + /// Create new PKCS#1 v1.5 padding for computing a raw signature. + /// + /// This sets `hash_len` to `None` and uses an empty `prefix`. + pub fn new_raw() -> Self { + Self { + hash_len: None, + prefix: Box::new([]), + } + } +} + +impl SignatureScheme for Pkcs1v15Sign { + fn sign( + self, + rng: Option<&mut impl CryptoRngCore>, + priv_key: &impl PrivateKey, + hashed: &[u8], + ) -> Result> { + if let Some(hash_len) = self.hash_len { + if hashed.len() != hash_len { + return Err(Error::InputNotHashed); + } + } + + sign(rng, priv_key, &self.prefix, hashed) + } + + fn verify(self, pub_key: &impl PublicKey, hashed: &[u8], sig: &[u8]) -> Result<()> { + if let Some(hash_len) = self.hash_len { + if hashed.len() != hash_len { + return Err(Error::InputNotHashed); + } + } + + verify(pub_key, self.prefix.as_ref(), hashed, sig) + } +} + /// PKCS#1 v1.5 signatures as described in [RFC8017 § 8.2]. /// /// [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2 @@ -44,10 +132,10 @@ impl From> for Signature { } } -impl<'a> TryFrom<&'a [u8]> for Signature { +impl TryFrom<&[u8]> for Signature { type Error = signature::Error; - fn try_from(bytes: &'a [u8]) -> signature::Result { + fn try_from(bytes: &[u8]) -> signature::Result { Ok(Self { bytes: bytes.into(), }) @@ -629,7 +717,7 @@ mod tests { use sha3::Sha3_256; use signature::{RandomizedSigner, Signer, Verifier}; - use crate::{PaddingScheme, PublicKey, PublicKeyParts, RsaPrivateKey, RsaPublicKey}; + use crate::{PublicKey, PublicKeyParts, RsaPrivateKey, RsaPublicKey}; #[test] fn test_non_zero_bytes() { @@ -686,10 +774,7 @@ mod tests { for test in &tests { let out = priv_key - .decrypt( - PaddingScheme::new_pkcs1v15_encrypt(), - &Base64::decode_vec(test[0]).unwrap(), - ) + .decrypt(Pkcs1v15Encrypt, &Base64::decode_vec(test[0]).unwrap()) .unwrap(); assert_eq!(out, test[1].as_bytes()); } @@ -734,19 +819,13 @@ mod tests { for (text, expected) in &tests { let digest = Sha1::digest(text.as_bytes()).to_vec(); - let out = priv_key - .sign(PaddingScheme::new_pkcs1v15_sign::(), &digest) - .unwrap(); + let out = priv_key.sign(Pkcs1v15Sign::new::(), &digest).unwrap(); assert_ne!(out, digest); assert_eq!(out, expected); let mut rng = ChaCha8Rng::from_seed([42; 32]); let out2 = priv_key - .sign_blinded( - &mut rng, - PaddingScheme::new_pkcs1v15_sign::(), - &digest, - ) + .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), &digest) .unwrap(); assert_eq!(out2, expected); } @@ -885,7 +964,7 @@ mod tests { for (text, sig, expected) in &tests { let digest = Sha1::digest(text.as_bytes()).to_vec(); - let result = pub_key.verify(PaddingScheme::new_pkcs1v15_sign::(), &digest, sig); + let result = pub_key.verify(Pkcs1v15Sign::new::(), &digest, sig); match expected { true => result.expect("failed to verify"), false => { @@ -978,14 +1057,12 @@ mod tests { let expected_sig = Base64::decode_vec("pX4DR8azytjdQ1rtUiC040FjkepuQut5q2ZFX1pTjBrOVKNjgsCDyiJDGZTCNoh9qpXYbhl7iEym30BWWwuiZg==").unwrap(); let priv_key = get_private_key(); - let sig = priv_key - .sign(PaddingScheme::new_pkcs1v15_sign_raw(), msg) - .unwrap(); + let sig = priv_key.sign(Pkcs1v15Sign::new_raw(), msg).unwrap(); assert_eq!(expected_sig, sig); let pub_key: RsaPublicKey = priv_key.into(); pub_key - .verify(PaddingScheme::new_pkcs1v15_sign_raw(), msg, &sig) + .verify(Pkcs1v15Sign::new_raw(), msg, &sig) .expect("failed to verify"); } diff --git a/src/pss.rs b/src/pss.rs index 104c5e0b..dff90c25 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -11,8 +11,8 @@ use alloc::boxed::Box; use alloc::vec::Vec; +use core::fmt::{self, Debug, Display, Formatter, LowerHex, UpperHex}; -use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex}; use core::marker::PhantomData; use digest::{Digest, DynDigest, FixedOutputReset}; use pkcs8::{Document, EncodePrivateKey, EncodePublicKey, SecretDocument}; @@ -26,8 +26,94 @@ use subtle::ConstantTimeEq; use crate::algorithms::{mgf1_xor, mgf1_xor_digest}; use crate::errors::{Error, Result}; use crate::key::{PrivateKey, PublicKey}; +use crate::padding::SignatureScheme; use crate::{RsaPrivateKey, RsaPublicKey}; +/// Digital signatures using PSS padding. +pub struct Pss { + /// Create blinded signatures. + pub blinded: bool, + + /// Digest type to use. + pub digest: Box, + + /// Salt length. + pub salt_len: Option, +} + +impl Pss { + /// New PSS padding for the given digest. + pub fn new() -> Self { + Self { + blinded: false, + digest: Box::new(T::new()), + salt_len: None, + } + } + + /// New PSS padding for the given digest with a salt value of the given length. + pub fn new_with_salt(len: usize) -> Self { + Self { + blinded: false, + digest: Box::new(T::new()), + salt_len: Some(len), + } + } + + /// New PSS padding for blinded signatures the given digest. + pub fn new_blinded() -> Self { + Self { + blinded: true, + digest: Box::new(T::new()), + salt_len: None, + } + } + + /// New PSS padding for blinded signatures the given digest with a salt value of the given + /// length. + pub fn new_blinded_with_salt( + len: usize, + ) -> Self { + Self { + blinded: true, + digest: Box::new(T::new()), + salt_len: Some(len), + } + } +} + +impl SignatureScheme for Pss { + fn sign( + mut self, + rng: Option<&mut impl CryptoRngCore>, + priv_key: &impl PrivateKey, + hashed: &[u8], + ) -> Result> { + let rng = rng.ok_or(Error::InvalidPaddingScheme)?; + sign( + rng, + self.blinded, + priv_key, + hashed, + self.salt_len, + &mut *self.digest, + ) + } + + fn verify(mut self, pub_key: &impl PublicKey, hashed: &[u8], sig: &[u8]) -> Result<()> { + verify(pub_key, hashed, sig, &mut *self.digest) + } +} + +impl Debug for Pss { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PSS") + .field("blind", &self.blinded) + .field("salt_len", &self.salt_len) + .finish_non_exhaustive() + } +} + /// RSASSA-PSS signatures as described in [RFC8017 § 8.1]. /// /// [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1 @@ -46,10 +132,10 @@ impl From> for Signature { } } -impl<'a> TryFrom<&'a [u8]> for Signature { +impl TryFrom<&[u8]> for Signature { type Error = signature::Error; - fn try_from(bytes: &'a [u8]) -> signature::Result { + fn try_from(bytes: &[u8]) -> signature::Result { Ok(Self { bytes: bytes.into(), }) @@ -329,7 +415,7 @@ where let mut hash = D::new(); - Digest::update(&mut hash, &prefix); + Digest::update(&mut hash, prefix); Digest::update(&mut hash, m_hash); Digest::update(&mut hash, salt); @@ -920,8 +1006,8 @@ where #[cfg(test)] mod test { - use crate::pss::{BlindedSigningKey, Signature, SigningKey, VerifyingKey}; - use crate::{PaddingScheme, PublicKey, RsaPrivateKey, RsaPublicKey}; + use crate::pss::{BlindedSigningKey, Pss, Signature, SigningKey, VerifyingKey}; + use crate::{PublicKey, RsaPrivateKey, RsaPublicKey}; use hex_literal::hex; use num_bigint::BigUint; @@ -980,7 +1066,7 @@ mod test { for (text, sig, expected) in &tests { let digest = Sha1::digest(text.as_bytes()).to_vec(); - let result = pub_key.verify(PaddingScheme::new_pss::(), &digest, sig); + let result = pub_key.verify(Pss::new::(), &digest, sig); match expected { true => result.expect("failed to verify"), false => { @@ -1078,11 +1164,11 @@ mod test { for test in &tests { let digest = Sha1::digest(test.as_bytes()).to_vec(); let sig = priv_key - .sign_with_rng(&mut rng.clone(), PaddingScheme::new_pss::(), &digest) + .sign_with_rng(&mut rng.clone(), Pss::new::(), &digest) .expect("failed to sign"); priv_key - .verify(PaddingScheme::new_pss::(), &digest, &sig) + .verify(Pss::new::(), &digest, &sig) .expect("failed to verify"); } } @@ -1097,11 +1183,11 @@ mod test { for test in &tests { let digest = Sha1::digest(test.as_bytes()).to_vec(); let sig = priv_key - .sign_blinded(&mut rng.clone(), PaddingScheme::new_pss::(), &digest) + .sign_with_rng(&mut rng.clone(), Pss::new_blinded::(), &digest) .expect("failed to sign"); priv_key - .verify(PaddingScheme::new_pss::(), &digest, &sig) + .verify(Pss::new::(), &digest, &sig) .expect("failed to verify"); } }