Refactor PaddingScheme into a trait (#244)

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}`
This commit is contained in:
Tony Arcieri 2023-01-10 13:59:31 -07:00 committed by GitHub
parent 35a32093f0
commit 35372d9516
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 446 additions and 378 deletions

View File

@ -6,7 +6,7 @@ use base64ct::{Base64, Encoding};
use num_bigint::BigUint;
use num_traits::{FromPrimitive, Num};
use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
use rsa::{PaddingScheme, RsaPrivateKey};
use rsa::{Pkcs1v15Encrypt, Pkcs1v15Sign, RsaPrivateKey};
use sha2::{Digest, Sha256};
use test::Bencher;
@ -31,9 +31,7 @@ fn bench_rsa_2048_pkcsv1_decrypt(b: &mut Bencher) {
let x = Base64::decode_vec(DECRYPT_VAL).unwrap();
b.iter(|| {
let res = priv_key
.decrypt(PaddingScheme::new_pkcs1v15_encrypt(), &x)
.unwrap();
let res = priv_key.decrypt(Pkcs1v15Encrypt, &x).unwrap();
test::black_box(res);
});
}
@ -46,11 +44,7 @@ fn bench_rsa_2048_pkcsv1_sign_blinded(b: &mut Bencher) {
b.iter(|| {
let res = priv_key
.sign_blinded(
&mut rng,
PaddingScheme::new_pkcs1v15_sign::<Sha256>(),
&digest,
)
.sign_with_rng(&mut rng, Pkcs1v15Sign::new::<Sha256>(), &digest)
.unwrap();
test::black_box(res);
});

View File

@ -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<R: CryptoRngCore>(
fn encrypt<R: CryptoRngCore, P: PaddingScheme>(
&self,
rng: &mut R,
padding: PaddingScheme,
padding: P,
msg: &[u8],
) -> Result<Vec<u8>>;
/// 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<S: SignatureScheme>(&self, scheme: S, hashed: &[u8], sig: &[u8]) -> Result<()>;
}
impl PublicKeyParts for RsaPublicKey {
@ -198,36 +199,17 @@ impl PublicKeyParts for RsaPublicKey {
}
impl PublicKey for RsaPublicKey {
fn encrypt<R: CryptoRngCore>(
fn encrypt<R: CryptoRngCore, P: PaddingScheme>(
&self,
rng: &mut R,
padding: PaddingScheme,
padding: P,
msg: &[u8],
) -> Result<Vec<u8>> {
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<S: SignatureScheme>(&self, scheme: S, hashed: &[u8], sig: &[u8]) -> Result<()> {
scheme.verify(self, hashed, sig)
}
}
@ -448,113 +430,44 @@ impl RsaPrivateKey {
}
/// Decrypt the given message.
pub fn decrypt(&self, padding: PaddingScheme, ciphertext: &[u8]) -> Result<Vec<u8>> {
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::<DummyRng, _>(None, self, ciphertext)
}
PaddingScheme::OAEP {
mut digest,
mut mgf_digest,
label,
} => oaep::decrypt::<DummyRng, _>(
None,
self,
ciphertext,
&mut *digest,
&mut *mgf_digest,
label,
),
_ => Err(Error::InvalidPaddingScheme),
}
pub fn decrypt<P: PaddingScheme>(&self, padding: P, ciphertext: &[u8]) -> Result<Vec<u8>> {
padding.decrypt(Option::<&mut DummyRng>::None, self, ciphertext)
}
/// Decrypt the given message.
///
/// Uses `rng` to blind the decryption process.
pub fn decrypt_blinded<R: CryptoRngCore>(
pub fn decrypt_blinded<R: CryptoRngCore, P: PaddingScheme>(
&self,
rng: &mut R,
padding: PaddingScheme,
padding: P,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
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<Vec<u8>> {
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::<DummyRng, _>(None, self, prefix.as_ref(), digest_in)
}
_ => Err(Error::InvalidPaddingScheme),
}
pub fn sign<S: SignatureScheme>(&self, padding: S, digest_in: &[u8]) -> Result<Vec<u8>> {
padding.sign(Option::<&mut DummyRng>::None, self, digest_in)
}
/// Sign the given digest using the provided rng
/// Sign the given digest using the provided `rng`, which is used in the
/// following ways depending on the [`SignatureScheme`]:
///
/// Use `rng` for signature process.
pub fn sign_with_rng<R: CryptoRngCore>(
/// - [`Pkcs1v15Sign`][`crate::Pkcs1v15Sign`] padding: uses the RNG
/// to mask the private key operation with random blinding, which helps
/// mitigate sidechannel attacks.
/// - [`Pss`][`crate::Pss`] always requires randomness. Use
/// [`Pss::new`][`crate::Pss::new`] for a standard RSASSA-PSS signature, or
/// [`Pss::new_blinded`][`crate::Pss::new_blinded`] for RSA-BSSA blind
/// signatures.
pub fn sign_with_rng<R: CryptoRngCore, S: SignatureScheme>(
&self,
rng: &mut R,
padding: PaddingScheme,
padding: S,
digest_in: &[u8],
) -> Result<Vec<u8>> {
match padding {
PaddingScheme::PSS {
mut digest,
salt_len,
} => pss::sign::<R, _>(rng, false, self, digest_in, salt_len, &mut *digest),
_ => Err(Error::InvalidPaddingScheme),
}
}
/// Sign the given digest.
///
/// Use `rng` for blinding.
pub fn sign_blinded<R: CryptoRngCore>(
&self,
rng: &mut R,
padding: PaddingScheme,
digest_in: &[u8],
) -> Result<Vec<u8>> {
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::<R, _>(rng, true, self, digest_in, salt_len, &mut *digest),
_ => Err(Error::InvalidPaddingScheme),
}
padding.sign(Some(rng), self, digest_in)
}
}
@ -591,6 +504,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 +879,10 @@ mod tests {
let pub_key: RsaPublicKey = prk.into();
let ciphertext = if let Some(ref label) = label {
let padding = PaddingScheme::new_oaep_with_label::<D, _>(label);
let padding = Oaep::new_with_label::<D, _>(label);
pub_key.encrypt(&mut rng, padding, &input).unwrap()
} else {
let padding = PaddingScheme::new_oaep::<D>();
let padding = Oaep::new::<D>();
pub_key.encrypt(&mut rng, padding, &input).unwrap()
};
@ -976,9 +890,9 @@ mod tests {
let blind: bool = rng.next_u32() < (1 << 31);
let padding = if let Some(ref label) = label {
PaddingScheme::new_oaep_with_label::<D, _>(label)
Oaep::new_with_label::<D, _>(label)
} else {
PaddingScheme::new_oaep::<D>()
Oaep::new::<D>()
};
let plaintext = if blind {
@ -1013,10 +927,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::<D, U, _>(label);
let padding = Oaep::new_with_mgf_hash_and_label::<D, U, _>(label);
pub_key.encrypt(&mut rng, padding, &input).unwrap()
} else {
let padding = PaddingScheme::new_oaep_with_mgf_hash::<D, U>();
let padding = Oaep::new_with_mgf_hash::<D, U>();
pub_key.encrypt(&mut rng, padding, &input).unwrap()
};
@ -1024,9 +938,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::<D, U, _>(label)
Oaep::new_with_mgf_hash_and_label::<D, U, _>(label)
} else {
PaddingScheme::new_oaep_with_mgf_hash::<D, U>()
Oaep::new_with_mgf_hash::<D, U>()
};
let plaintext = if blind {
@ -1044,17 +958,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::<Sha1>(),
"a_plain_text".as_bytes(),
)
.encrypt(&mut rng, Oaep::new::<Sha1>(), "a_plain_text".as_bytes())
.unwrap();
assert!(
priv_key
.decrypt_blinded(
&mut rng,
PaddingScheme::new_oaep_with_label::<Sha1, _>("label"),
Oaep::new_with_label::<Sha1, _>("label"),
&ciphertext,
)
.is_err(),

View File

@ -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::<sha2::Sha256>();
//! let padding = Oaep::new::<sha2::Sha256>();
//! 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::<sha2::Sha256>();
//! let padding = Oaep::new::<sha2::Sha256>();
//! 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"))]

View File

@ -1,20 +1,171 @@
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<dyn DynDigest + Send + Sync>,
/// Digest to use for Mask Generation Function (MGF).
pub mgf_digest: Box<dyn DynDigest + Send + Sync>,
/// Optional label.
pub label: Option<String>,
}
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::<Sha256>();
/// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap();
/// ```
pub fn new<T: 'static + Digest + DynDigest + Send + Sync>() -> 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<T: 'static + Digest + DynDigest + Send + Sync, S: AsRef<str>>(
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::<Sha256, Sha1>();
/// 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<str>,
>(
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<Rng: CryptoRngCore, Priv: PrivateKey>(
mut self,
rng: Option<&mut Rng>,
priv_key: &Priv,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
decrypt(
rng,
priv_key,
ciphertext,
&mut *self.digest,
&mut *self.mgf_digest,
self.label,
)
}
fn encrypt<Rng: CryptoRngCore, Pub: PublicKey>(
mut self,
rng: &mut Rng,
pub_key: &Pub,
msg: &[u8],
) -> Result<Vec<u8>> {
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("digest", &"...")
.field("mgf_digest", &"...")
.field("label", &self.label)
.finish()
}
}
/// Encrypts the given message with RSA and the padding scheme from
/// [PKCS#1 OAEP].
///
@ -57,7 +208,7 @@ pub fn encrypt<R: CryptoRngCore, K: PublicKey>(
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);

View File

@ -1,202 +1,49 @@
//! Supported padding schemes.
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use core::fmt;
use alloc::vec::Vec;
use digest::{Digest, DynDigest};
use pkcs8::AssociatedOid;
use rand_core::CryptoRngCore;
use crate::pkcs1v15;
use crate::errors::Result;
use crate::key::{PrivateKey, PublicKey};
/// 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<usize>,
/// Prefix.
prefix: Box<[u8]>,
},
/// Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1).
/// Padding scheme used for encryption.
pub trait PaddingScheme {
/// Decrypt the given message using the given private key.
///
/// - `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<dyn DynDigest + Send + Sync>,
/// If an `rng` is passed, it uses RSA blinding to help mitigate timing
/// side-channel attacks.
fn decrypt<Rng: CryptoRngCore, Priv: PrivateKey>(
self,
rng: Option<&mut Rng>,
priv_key: &Priv,
ciphertext: &[u8],
) -> Result<Vec<u8>>;
/// Digest to use for Mask Generation Function (MGF).
mgf_digest: Box<dyn DynDigest + Send + Sync>,
/// Optional label.
label: Option<String>,
},
/// Sign and Verify using PSS padding.
PSS {
/// Digest type to use.
digest: Box<dyn DynDigest + Send + Sync>,
/// Salt length.
salt_len: Option<usize>,
},
/// Encrypt the given message using the given public key.
fn encrypt<Rng: CryptoRngCore, Pub: PublicKey>(
self,
rng: &mut Rng,
pub_key: &Pub,
msg: &[u8],
) -> Result<Vec<u8>>;
}
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)
}
}
}
}
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<D>() -> Self
where
D: Digest + AssociatedOid,
{
PaddingScheme::PKCS1v15Sign {
hash_len: Some(<D as Digest>::output_size()),
prefix: pkcs1v15::generate_prefix::<D>().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::<Sha256, Sha1>();
/// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap();
/// ```
pub fn new_oaep_with_mgf_hash<
T: 'static + Digest + DynDigest + Send + Sync,
U: 'static + Digest + DynDigest + Send + Sync,
>() -> Self {
PaddingScheme::OAEP {
digest: Box::new(T::new()),
mgf_digest: Box::new(U::new()),
label: None,
}
}
/// Create a new OAEP `PaddingScheme`, using `T` as the hash function for both the default (empty) label and for MGF1.
///
/// # Example
/// ```
/// use sha1::Sha1;
/// use sha2::Sha256;
/// use rsa::{BigUint, RsaPublicKey, PaddingScheme, PublicKey};
/// use base64ct::{Base64, Encoding};
///
/// let n = Base64::decode_vec("ALHgDoZmBQIx+jTmgeeHW6KsPOrj11f6CvWsiRleJlQpW77AwSZhd21ZDmlTKfaIHBSUxRUsuYNh7E2SHx8rkFVCQA2/gXkZ5GK2IUbzSTio9qXA25MWHvVxjMfKSL8ZAxZyKbrG94FLLszFAFOaiLLY8ECs7g+dXOriYtBwLUJK+lppbd+El+8ZA/zH0bk7vbqph5pIoiWggxwdq3mEz4LnrUln7r6dagSQzYErKewY8GADVpXcq5mfHC1xF2DFBub7bFjMVM5fHq7RK+pG5xjNDiYITbhLYrbVv3X0z75OvN0dY49ITWjM7xyvMWJXVJS7sJlgmCCL6RwWgP8PhcE=").unwrap();
/// let e = Base64::decode_vec("AQAB").unwrap();
///
/// let mut rng = rand::thread_rng();
/// let key = RsaPublicKey::new(BigUint::from_bytes_be(&n), BigUint::from_bytes_be(&e)).unwrap();
/// let padding = PaddingScheme::new_oaep::<Sha256>();
/// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap();
/// ```
pub fn new_oaep<T: 'static + Digest + DynDigest + Send + Sync>() -> 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<str>,
>(
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<T: 'static + Digest + DynDigest + Send + Sync, S: AsRef<str>>(
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<T: 'static + Digest + DynDigest + Send + Sync>() -> 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<T: 'static + Digest + DynDigest + Send + Sync>(len: usize) -> Self {
PaddingScheme::PSS {
digest: Box::new(T::new()),
salt_len: Some(len),
}
}
/// Digital signature scheme.
pub trait SignatureScheme {
/// Sign the given digest.
fn sign<Rng: CryptoRngCore, Priv: PrivateKey>(
self,
rng: Option<&mut Rng>,
priv_key: &Priv,
hashed: &[u8],
) -> Result<Vec<u8>>;
/// 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, otherwise an `Err` indicating failure.
fn verify<Pub: PublicKey>(self, pub_key: &Pub, hashed: &[u8], sig: &[u8]) -> Result<()>;
}

View File

@ -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<Rng: CryptoRngCore, Priv: PrivateKey>(
self,
rng: Option<&mut Rng>,
priv_key: &Priv,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
decrypt(rng, priv_key, ciphertext)
}
fn encrypt<Rng: CryptoRngCore, Pub: PublicKey>(
self,
rng: &mut Rng,
pub_key: &Pub,
msg: &[u8],
) -> Result<Vec<u8>> {
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<usize>,
/// 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<D>() -> Self
where
D: Digest + AssociatedOid,
{
Self {
hash_len: Some(<D as Digest>::output_size()),
prefix: generate_prefix::<D>().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<Rng: CryptoRngCore, Priv: PrivateKey>(
self,
rng: Option<&mut Rng>,
priv_key: &Priv,
hashed: &[u8],
) -> Result<Vec<u8>> {
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<Pub: PublicKey>(self, pub_key: &Pub, 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<Box<[u8]>> 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<Self> {
fn try_from(bytes: &[u8]) -> signature::Result<Self> {
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::<Sha1>(), &digest)
.unwrap();
let out = priv_key.sign(Pkcs1v15Sign::new::<Sha1>(), &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::<Sha1>(),
&digest,
)
.sign_with_rng(&mut rng, Pkcs1v15Sign::new::<Sha1>(), &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::<Sha1>(), &digest, sig);
let result = pub_key.verify(Pkcs1v15Sign::new::<Sha1>(), &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");
}

View File

@ -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<dyn DynDigest + Send + Sync>,
/// Salt length.
pub salt_len: Option<usize>,
}
impl Pss {
/// New PSS padding for the given digest.
pub fn new<T: 'static + Digest + DynDigest + Send + Sync>() -> 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<T: 'static + Digest + DynDigest + Send + Sync>(len: usize) -> Self {
Self {
blinded: false,
digest: Box::new(T::new()),
salt_len: Some(len),
}
}
/// New PSS padding for blinded signatures (RSA-BSSA) for the given digest.
pub fn new_blinded<T: 'static + Digest + DynDigest + Send + Sync>() -> Self {
Self {
blinded: true,
digest: Box::new(T::new()),
salt_len: None,
}
}
/// New PSS padding for blinded signatures (RSA-BSSA) for the given digest
/// with a salt value of the given length.
pub fn new_blinded_with_salt<T: 'static + Digest + DynDigest + Send + Sync>(
len: usize,
) -> Self {
Self {
blinded: true,
digest: Box::new(T::new()),
salt_len: Some(len),
}
}
}
impl SignatureScheme for Pss {
fn sign<Rng: CryptoRngCore, Priv: PrivateKey>(
mut self,
rng: Option<&mut Rng>,
priv_key: &Priv,
hashed: &[u8],
) -> Result<Vec<u8>> {
sign(
rng.ok_or(Error::InvalidPaddingScheme)?,
self.blinded,
priv_key,
hashed,
self.salt_len,
&mut *self.digest,
)
}
fn verify<Pub: PublicKey>(mut self, pub_key: &Pub, 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("blinded", &self.blinded)
.field("digest", &"...")
.field("salt_len", &self.salt_len)
.finish()
}
}
/// 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<Box<[u8]>> 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<Self> {
fn try_from(bytes: &[u8]) -> signature::Result<Self> {
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::<Sha1>(), &digest, sig);
let result = pub_key.verify(Pss::new::<Sha1>(), &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::<Sha1>(), &digest)
.sign_with_rng(&mut rng.clone(), Pss::new::<Sha1>(), &digest)
.expect("failed to sign");
priv_key
.verify(PaddingScheme::new_pss::<Sha1>(), &digest, &sig)
.verify(Pss::new::<Sha1>(), &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::<Sha1>(), &digest)
.sign_with_rng(&mut rng.clone(), Pss::new_blinded::<Sha1>(), &digest)
.expect("failed to sign");
priv_key
.verify(PaddingScheme::new_pss::<Sha1>(), &digest, &sig)
.verify(Pss::new::<Sha1>(), &digest, &sig)
.expect("failed to verify");
}
}