fix(servo): gate vulnerable RSA private operations

This commit is contained in:
2026-07-10 01:06:48 -04:00
parent 670f7abe60
commit e085174b89
67 changed files with 10535 additions and 21 deletions
+10
View File
@@ -0,0 +1,10 @@
//! Useful algorithms related to RSA.
mod mgf;
pub(crate) mod generate;
pub(crate) mod oaep;
pub(crate) mod pad;
pub(crate) mod pkcs1v15;
pub(crate) mod pss;
pub(crate) mod rsa;
+183
View File
@@ -0,0 +1,183 @@
//! Generate prime components for the RSA Private Key
use alloc::vec::Vec;
use crypto_bigint::{BoxedUint, Odd};
use crypto_primes::{
hazmat::{SetBits, SmallFactorsSieveFactory},
is_prime, sieve_and_find, Flavor,
};
use rand_core::CryptoRng;
use crate::{
algorithms::rsa::{compute_modulus, compute_private_exponent_euler_totient},
errors::{Error, Result},
};
pub struct RsaPrivateKeyComponents {
pub n: Odd<BoxedUint>,
pub e: BoxedUint,
pub d: BoxedUint,
pub primes: Vec<BoxedUint>,
}
/// Generates a multi-prime RSA keypair of the given bit size, public exponent,
/// and the given random source, as suggested in [1]. Although the public
/// keys are compatible (actually, indistinguishable) from the 2-prime case,
/// the private keys are not. Thus it may not be possible to export multi-prime
/// private keys in certain formats or to subsequently import them into other
/// code.
///
/// Table 1 in [2] suggests maximum numbers of primes for a given size.
///
/// [1]: https://patents.google.com/patent/US4405829A/en
/// [2]: http://www.cacr.math.uwaterloo.ca/techreports/2006/cacr2006-16.pdf
pub(crate) fn generate_multi_prime_key_with_exp<R: CryptoRng + ?Sized>(
rng: &mut R,
nprimes: usize,
bit_size: usize,
exp: BoxedUint,
) -> Result<RsaPrivateKeyComponents> {
if nprimes < 2 {
return Err(Error::NprimesTooSmall);
}
if bit_size < 64 {
let prime_limit = (1u64 << (bit_size / nprimes) as u64) as f64;
// pi approximates the number of primes less than prime_limit
// Calculate `log(prime_limit)` as `log(x) = log2(x) / log2(e) = log2(x) * log(2)`.
let mut pi = prime_limit / ((bit_size / nprimes) as f64 * core::f64::consts::LN_2 - 1.);
// Generated primes start with 0b11, so we can only use a quarter of them.
pi /= 4f64;
// Use a factor of two to ensure that key generation terminates in a
// reasonable amount of time.
pi /= 2f64;
if pi < nprimes as f64 {
return Err(Error::TooFewPrimes);
}
}
let mut primes = vec![BoxedUint::zero(); nprimes];
let n_final: Odd<BoxedUint>;
let d_final: BoxedUint;
'next: loop {
let mut todo = bit_size;
// `generate_prime_with_rng` should set the top two bits in each prime.
// Thus each prime has the form
// p_i = 2^bitlen(p_i) × 0.11... (in base 2).
// And the product is:
// P = 2^todo × α
// where α is the product of nprimes numbers of the form 0.11...
//
// If α < 1/2 (which can happen for nprimes > 2), we need to
// shift todo to compensate for lost bits: the mean value of 0.11...
// is 7/8, so todo + shift - nprimes * log2(7/8) ~= bits - 1/2
// will give good results.
if nprimes >= 7 {
todo += (nprimes - 2) / 5;
}
for (i, prime) in primes.iter_mut().enumerate() {
let bits = (todo / (nprimes - i)) as u32;
*prime = generate_prime_with_rng(rng, bits);
todo -= prime.bits() as usize;
}
// Makes sure that primes is pairwise unequal.
for (i, prime1) in primes.iter().enumerate() {
for prime2 in primes.iter().take(i) {
if prime1 == prime2 {
continue 'next;
}
}
}
let n = compute_modulus(&primes);
if n.bits() as usize != bit_size {
// This should never happen for nprimes == 2 because
// generate_prime_with_rng should set the top two bits in each prime.
// For nprimes > 2 we hope it does not happen often.
continue 'next;
}
if let Ok(d) = compute_private_exponent_euler_totient(&primes, &exp) {
n_final = n;
d_final = d;
break;
}
}
Ok(RsaPrivateKeyComponents {
n: n_final,
e: exp,
d: d_final,
primes,
})
}
fn generate_prime_with_rng<R: CryptoRng + ?Sized>(rng: &mut R, bit_length: u32) -> BoxedUint {
let factory = SmallFactorsSieveFactory::new(Flavor::Any, bit_length, SetBits::TwoMsb)
.unwrap_or_else(|err| panic!("Error creating the sieve: {err}"));
sieve_and_find(rng, factory, |_rng, candidate| {
is_prime(Flavor::Any, candidate)
})
.unwrap_or_else(|err| panic!("Error generating random candidates: {}", err))
.expect("will produce a result eventually")
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
const EXP: u64 = 65537;
#[test]
fn test_impossible_keys() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let exp = BoxedUint::from(EXP);
for i in 0..32 {
let _ = generate_multi_prime_key_with_exp(&mut rng, 2, i, exp.clone());
let _ = generate_multi_prime_key_with_exp(&mut rng, 3, i, exp.clone());
let _ = generate_multi_prime_key_with_exp(&mut rng, 4, i, exp.clone());
let _ = generate_multi_prime_key_with_exp(&mut rng, 5, i, exp.clone());
}
}
macro_rules! key_generation {
($name:ident, $multi:expr, $size:expr) => {
#[test]
fn $name() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let exp = BoxedUint::from(EXP);
for _ in 0..10 {
let components =
generate_multi_prime_key_with_exp(&mut rng, $multi, $size, exp.clone())
.unwrap();
assert_eq!(components.n.bits(), $size);
assert_eq!(components.primes.len(), $multi);
}
}
};
}
key_generation!(key_generation_128, 2, 128);
key_generation!(key_generation_1024, 2, 1024);
key_generation!(key_generation_multi_3_256, 3, 256);
key_generation!(key_generation_multi_4_64, 4, 64);
key_generation!(key_generation_multi_5_64, 5, 64);
key_generation!(key_generation_multi_8_576, 8, 576);
// TODO: reenable, currently slow
// key_generation!(key_generation_multi_16_1024, 16, 1024);
}
+78
View File
@@ -0,0 +1,78 @@
//! Mask generation function common to both PSS and OAEP padding
use digest::{Digest, FixedOutputReset};
/// Mask generation function.
///
/// Panics if out is larger than 2**32. This is in accordance with RFC 8017 - PKCS #1 B.2.1
pub(crate) fn mgf1_xor<D>(out: &mut [u8], digest: &mut D, seed: &[u8])
where
D: Digest + FixedOutputReset,
{
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::update(digest, digest_input.as_slice());
let digest_output = &*digest.finalize_reset();
let mut j = 0;
loop {
if j >= digest_output.len() || i >= out.len() {
break;
}
out[i] ^= digest_output[j];
j += 1;
i += 1;
}
inc_counter(&mut counter);
}
}
/// Mask generation function.
///
/// Panics if out is larger than 2**32. This is in accordance with RFC 8017 - PKCS #1 B.2.1
pub(crate) fn mgf1_xor_digest<D>(out: &mut [u8], digest: &mut D, seed: &[u8])
where
D: Digest + FixedOutputReset,
{
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() {
Digest::update(digest, seed);
Digest::update(digest, counter);
let digest_output = digest.finalize_reset();
let mut j = 0;
loop {
if j >= digest_output.len() || i >= out.len() {
break;
}
out[i] ^= digest_output[j];
j += 1;
i += 1;
}
inc_counter(&mut counter);
}
}
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;
}
}
}
+260
View File
@@ -0,0 +1,260 @@
//! Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1).
//!
use alloc::boxed::Box;
use alloc::vec::Vec;
use crypto_bigint::{Choice, CtAssign, CtEq, CtOption};
use digest::{Digest, FixedOutputReset};
use rand_core::TryCryptoRng;
use zeroize::Zeroizing;
use super::mgf::{mgf1_xor, mgf1_xor_digest};
use crate::errors::{Error, Result};
/// Maximum label size (2^64 bits) for SHA-1 and SHA-256 hash functions.
///
/// In theory, other hash functions (e.g. SHA-512 and SHA-3) can process longer labels,
/// but such huge inputs are practically impossible on one machine, so we use this limit
/// for all hash functions.
const MAX_LABEL_LEN: u64 = 1 << 61;
#[inline]
fn encrypt_internal<R: TryCryptoRng + ?Sized, MGF: FnMut(&mut [u8], &mut [u8])>(
rng: &mut R,
msg: &[u8],
p_hash: &[u8],
h_size: usize,
k: usize,
mut mgf: MGF,
) -> Result<Zeroizing<Vec<u8>>> {
if msg.len() + 2 * h_size + 2 > k {
return Err(Error::MessageTooLong);
}
let mut em = Zeroizing::new(vec![0u8; k]);
let (_, payload) = em.split_at_mut(1);
let (seed, db) = payload.split_at_mut(h_size);
rng.try_fill_bytes(seed).map_err(|_| Error::Rng)?;
// Data block DB = pHash || PS || 01 || M
let db_len = k - h_size - 1;
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);
mgf(seed, db);
Ok(em)
}
/// 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())`.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
pub(crate) fn oaep_encrypt<R, D, MGD>(
rng: &mut R,
msg: &[u8],
digest: &mut D,
mgf_digest: &mut MGD,
label: Option<Box<[u8]>>,
k: usize,
) -> Result<Zeroizing<Vec<u8>>>
where
R: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
MGD: Digest + FixedOutputReset,
{
let h_size = <D as Digest>::output_size();
let label = label.unwrap_or_default();
if label.len() as u64 >= MAX_LABEL_LEN {
return Err(Error::LabelTooLong);
}
Digest::update(digest, &label);
let p_hash = digest.finalize_reset();
encrypt_internal(rng, msg, &p_hash, h_size, k, |seed, db| {
mgf1_xor(db, mgf_digest, seed);
mgf1_xor(seed, mgf_digest, db);
})
}
/// 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())`.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
pub(crate) fn oaep_encrypt_digest<R, D, MGD>(
rng: &mut R,
msg: &[u8],
label: Option<Box<[u8]>>,
k: usize,
) -> Result<Zeroizing<Vec<u8>>>
where
R: TryCryptoRng + ?Sized,
D: Digest,
MGD: Digest + FixedOutputReset,
{
let h_size = <D as Digest>::output_size();
let label = label.unwrap_or_default();
if label.len() as u64 >= MAX_LABEL_LEN {
return Err(Error::LabelTooLong);
}
let p_hash = D::digest(&label);
encrypt_internal(rng, msg, &p_hash, h_size, k, |seed, db| {
let mut mgf_digest = MGD::new();
mgf1_xor_digest(db, &mut mgf_digest, seed);
mgf1_xor_digest(seed, &mut mgf_digest, db);
})
}
///Decrypts OAEP padding.
///
/// 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.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
pub(crate) fn oaep_decrypt<D, MGD>(
em: &mut [u8],
digest: &mut D,
mgf_digest: &mut MGD,
label: Option<Box<[u8]>>,
k: usize,
) -> Result<Vec<u8>>
where
D: Digest + FixedOutputReset,
MGD: Digest + FixedOutputReset,
{
let h_size = <D as Digest>::output_size();
let label = label.unwrap_or_default();
if label.len() as u64 >= MAX_LABEL_LEN {
return Err(Error::Decryption);
}
Digest::update(digest, &label);
let expected_p_hash = digest.finalize_reset();
let res = decrypt_inner(em, h_size, &expected_p_hash, k, |seed, db| {
mgf1_xor(seed, mgf_digest, db);
mgf1_xor(db, mgf_digest, seed);
})?;
if res.is_none().into() {
return Err(Error::Decryption);
}
let index = res.unwrap();
Ok(em[index as usize..].to_vec())
}
///Decrypts OAEP padding.
///
/// 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.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
pub(crate) fn oaep_decrypt_digest<D, MGD>(
em: &mut [u8],
label: Option<Box<[u8]>>,
k: usize,
) -> Result<Vec<u8>>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
let h_size = <D as Digest>::output_size();
let label = label.unwrap_or_default();
if label.len() as u64 >= MAX_LABEL_LEN {
return Err(Error::LabelTooLong);
}
let expected_p_hash = D::digest(&label);
let res = decrypt_inner(em, h_size, &expected_p_hash, k, |seed, db| {
let mut mgf_digest = MGD::new();
mgf1_xor_digest(seed, &mut mgf_digest, db);
mgf1_xor_digest(db, &mut mgf_digest, seed);
})?;
if res.is_none().into() {
return Err(Error::Decryption);
}
let index = res.unwrap();
Ok(em[index as usize..].to_vec())
}
/// Decrypts OAEP padding. It returns one or zero in valid that indicates whether the
/// plaintext was correctly structured.
#[inline]
fn decrypt_inner<MGF: FnMut(&mut [u8], &mut [u8])>(
em: &mut [u8],
h_size: usize,
expected_p_hash: &[u8],
k: usize,
mut mgf: MGF,
) -> Result<CtOption<u32>> {
if k < 11 {
return Err(Error::Decryption);
}
if k < h_size * 2 + 2 {
return Err(Error::Decryption);
}
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);
mgf(seed, db);
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 = Choice::TRUE;
let mut index = 0u32;
let mut nonzero_before_one = Choice::FALSE;
for (i, el) in db.iter().skip(h_size).enumerate() {
let equals0 = el.ct_eq(&0u8);
let equals1 = el.ct_eq(&1u8);
index.ct_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 & !nonzero_before_one & !looking_for_index;
Ok(CtOption::new(index + 2 + (h_size * 2) as u32, valid))
}
+63
View File
@@ -0,0 +1,63 @@
//! Special handling for converting the BigUint to u8 vectors
use alloc::vec::Vec;
use crypto_bigint::BoxedUint;
use zeroize::Zeroizing;
use crate::errors::{Error, Result};
/// Returns a new vector of the given length, with 0s left padded.
#[inline]
fn left_pad(input: &[u8], padded_len: usize) -> Result<Vec<u8>> {
if input.len() > padded_len {
return Err(Error::InvalidPadLen);
}
let mut out = vec![0u8; padded_len];
out[padded_len - input.len()..].copy_from_slice(input);
Ok(out)
}
/// Converts input to the new vector of the given length, using BE and with 0s left padded.
/// In some cases BoxedUint might already have leading zeroes, this function removes them
/// before padding again.
#[inline]
pub(crate) fn uint_to_be_pad(input: BoxedUint, padded_len: usize) -> Result<Vec<u8>> {
let leading_zeros = input.leading_zeros() as usize / 8;
left_pad(&input.to_be_bytes()[leading_zeros..], padded_len)
}
/// Converts input to the new vector of the given length, using BE and with 0s left padded.
/// In some cases BoxedUint might already have leading zeroes, this function removes them
/// before padding again.
#[inline]
pub(crate) fn uint_to_zeroizing_be_pad(input: BoxedUint, padded_len: usize) -> Result<Vec<u8>> {
let leading_zeros = input.leading_zeros() as usize / 8;
let m = Zeroizing::new(input);
let m = Zeroizing::new(m.to_be_bytes());
left_pad(&m[leading_zeros..], padded_len)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_left_pad() {
const INPUT_LEN: usize = 3;
let input = vec![0u8; INPUT_LEN];
// input len < padded len
let padded = left_pad(&input, INPUT_LEN + 1).unwrap();
assert_eq!(padded.len(), INPUT_LEN + 1);
// input len == padded len
let padded = left_pad(&input, INPUT_LEN).unwrap();
assert_eq!(padded.len(), INPUT_LEN);
// input len > padded len
let padded = left_pad(&input, INPUT_LEN - 1);
assert!(padded.is_err());
}
}
+213
View File
@@ -0,0 +1,213 @@
//! PKCS#1 v1.5 support as described in [RFC8017 § 8.2].
//!
//! # Usage
//!
//! See [code example in the toplevel rustdoc](../index.html#pkcs1-v15-signatures).
//!
//! [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2
use alloc::vec::Vec;
use const_oid::AssociatedOid;
use crypto_bigint::{Choice, CtAssign, CtEq, CtSelect};
use digest::Digest;
use rand_core::TryCryptoRng;
use zeroize::Zeroizing;
use crate::errors::{Error, Result};
/// Fills the provided slice with random values, which are guaranteed
/// to not be zero.
#[inline]
fn non_zero_random_bytes<R: TryCryptoRng + ?Sized>(
rng: &mut R,
data: &mut [u8],
) -> core::result::Result<(), R::Error> {
rng.try_fill_bytes(data)?;
for el in data {
if *el == 0u8 {
// TODO: break after a certain amount of time
while *el == 0u8 {
rng.try_fill_bytes(core::slice::from_mut(el))?;
}
}
}
Ok(())
}
/// Applied the padding scheme from PKCS#1 v1.5 for encryption. The message must be no longer than
/// the length of the public modulus minus 11 bytes.
pub(crate) fn pkcs1v15_encrypt_pad<R>(
rng: &mut R,
msg: &[u8],
k: usize,
) -> Result<Zeroizing<Vec<u8>>>
where
R: TryCryptoRng + ?Sized,
{
if msg.len() + 11 > k {
return Err(Error::MessageTooLong);
}
// EM = 0x00 || 0x02 || PS || 0x00 || M
let mut em = Zeroizing::new(vec![0u8; k]);
em[1] = 2;
non_zero_random_bytes(rng, &mut em[2..k - msg.len() - 1]).map_err(|_: R::Error| Error::Rng)?;
em[k - msg.len() - 1] = 0;
em[k - msg.len()..].copy_from_slice(msg);
Ok(em)
}
/// Removes the encryption padding scheme from PKCS#1 v1.5.
///
/// 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(crate) fn pkcs1v15_encrypt_unpad(em: Vec<u8>, k: usize) -> Result<Vec<u8>> {
let (valid, out, index) = decrypt_inner(em, k)?;
if valid == 0 {
return Err(Error::Decryption);
}
Ok(out[index as usize..].to_vec())
}
/// Removes the PKCS1v15 padding 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(em: Vec<u8>, k: usize) -> Result<(u8, Vec<u8>, u32)> {
if k < 11 {
return Err(Error::Decryption);
}
let first_byte_is_zero = em[0].ct_eq(&0u8);
let second_byte_is_two = em[1].ct_eq(&2u8);
// The remainder of the plaintext must be a string of non-zero random
// octets, followed by a 0, followed by the message.
// looking_for_index: 1 iff we are still looking for the zero.
// index: the offset of the first zero byte.
let mut looking_for_index = Choice::TRUE;
let mut index = 0u32;
for (i, el) in em.iter().enumerate().skip(2) {
let equals0 = el.ct_eq(&0u8);
index.ct_assign(&(i as u32), looking_for_index & equals0);
looking_for_index &= !equals0;
}
// The PS padding must be at least 8 bytes long, and it starts two
// bytes into em.
// TODO: WARNING: THIS MUST BE CONSTANT TIME CHECK:
// Ref: https://github.com/dalek-cryptography/subtle/issues/20
// This is currently copy & paste from the constant time impl in
// go, but very likely not sufficient.
let valid_ps = Choice::from_u8_lsb((((2i32 + 8i32 - index as i32 - 1i32) >> 31) & 1) as u8);
let valid = first_byte_is_zero & second_byte_is_two & !looking_for_index & valid_ps;
index = u32::ct_select(&0, &(index + 1), valid);
Ok((valid.to_u8(), em, index))
}
#[inline]
pub(crate) fn pkcs1v15_sign_pad(prefix: &[u8], hashed: &[u8], k: usize) -> Result<Vec<u8>> {
let hash_len = hashed.len();
let t_len = prefix.len() + hashed.len();
if k < t_len + 11 {
return Err(Error::MessageTooLong);
}
// EM = 0x00 || 0x01 || PS || 0x00 || T
let mut em = vec![0xff; k];
em[0] = 0;
em[1] = 1;
em[k - t_len - 1] = 0;
em[k - t_len..k - hash_len].copy_from_slice(prefix);
em[k - hash_len..k].copy_from_slice(hashed);
Ok(em)
}
#[inline]
pub(crate) fn pkcs1v15_sign_unpad(prefix: &[u8], hashed: &[u8], em: &[u8], k: usize) -> Result<()> {
let hash_len = hashed.len();
let t_len = prefix.len() + hashed.len();
if k < t_len + 11 {
return Err(Error::Verification);
}
// EM = 0x00 || 0x01 || PS || 0x00 || T
let mut ok = em[0].ct_eq(&0u8);
ok &= em[1].ct_eq(&1u8);
ok &= em[k - hash_len..k].ct_eq(hashed);
ok &= em[k - t_len..k - hash_len].ct_eq(prefix);
ok &= em[k - t_len - 1].ct_eq(&0u8);
for el in em.iter().skip(2).take(k - t_len - 3) {
ok &= el.ct_eq(&0xff)
}
// TODO(tarcieri): avoid branching here by e.g. using a pseudorandom rejection symbol
if !ok.to_bool() {
return Err(Error::Verification);
}
Ok(())
}
/// prefix = 0x30 <oid_len + 8 + digest_len> 0x30 <oid_len + 4> 0x06 <oid_len> oid 0x05 0x00 0x04 <digest_len>
#[inline]
pub(crate) fn pkcs1v15_generate_prefix<D>() -> Vec<u8>
where
D: Digest + AssociatedOid,
{
let oid = D::OID.as_bytes();
let oid_len = oid.len() as u8;
let digest_len = <D as Digest>::output_size() as u8;
let mut v = vec![
0x30,
oid_len + 8 + digest_len,
0x30,
oid_len + 4,
0x6,
oid_len,
];
v.extend_from_slice(oid);
v.extend_from_slice(&[0x05, 0x00, 0x04, digest_len]);
v
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
#[test]
fn test_non_zero_bytes() {
for _ in 0..10 {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let mut b = vec![0u8; 512];
non_zero_random_bytes(&mut rng, &mut b).unwrap();
for el in &b {
assert_ne!(*el, 0u8);
}
}
}
#[test]
fn test_encrypt_tiny_no_crash() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let k = 8;
let message = vec![1u8; 4];
let res = pkcs1v15_encrypt_pad(&mut rng, &message, k);
assert_eq!(res, Err(Error::MessageTooLong));
}
}
+383
View File
@@ -0,0 +1,383 @@
//! Support for the [Probabilistic Signature Scheme] (PSS) a.k.a. RSASSA-PSS.
//!
//! Designed by Mihir Bellare and Phillip Rogaway. Specified in [RFC8017 § 8.1].
//!
//! # Usage
//!
//! See [code example in the toplevel rustdoc](../index.html#pss-signatures).
//!
//! [Probabilistic Signature Scheme]: https://en.wikipedia.org/wiki/Probabilistic_signature_scheme
//! [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1
use alloc::vec::Vec;
use crypto_bigint::{Choice, CtEq, CtSelect};
use digest::{Digest, FixedOutputReset};
use super::mgf::{mgf1_xor, mgf1_xor_digest};
use crate::errors::{Error, Result};
pub(crate) fn emsa_pss_encode<D>(
m_hash: &[u8],
em_bits: usize,
salt: &[u8],
hash: &mut D,
) -> Result<Vec<u8>>
where
D: Digest + FixedOutputReset,
{
// See [1], section 9.1.1
let h_len = <D as Digest>::output_size();
let s_len = salt.len();
let em_len = em_bits.div_ceil(8);
// 1. If the length of M is greater than the input limitation for the
// hash function (2^61 - 1 octets for SHA-1), output "message too
// long" and stop.
//
// 2. Let mHash = Hash(M), an octet string of length hLen.
if m_hash.len() != h_len {
return Err(Error::InputNotHashed);
}
// 3. If em_len < h_len + s_len + 2, output "encoding error" and stop.
if em_len < h_len + s_len + 2 {
// TODO: Key size too small
return Err(Error::Internal);
}
let mut em = vec![0; em_len];
let (db, h) = em.split_at_mut(em_len - h_len - 1);
let h = &mut h[..(em_len - 1) - db.len()];
// 4. Generate a random octet string salt of length s_len; if s_len = 0,
// then salt is the empty string.
//
// 5. Let
// M' = (0x)00 00 00 00 00 00 00 00 || m_hash || salt;
//
// M' is an octet string of length 8 + h_len + s_len with eight
// initial zero octets.
//
// 6. Let H = Hash(M'), an octet string of length h_len.
let prefix = [0u8; 8];
Digest::update(hash, prefix);
Digest::update(hash, m_hash);
Digest::update(hash, salt);
let hashed = hash.finalize_reset();
h.copy_from_slice(&hashed);
// 7. Generate an octet string PS consisting of em_len - s_len - h_len - 2
// zero octets. The length of PS may be 0.
//
// 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
// emLen - hLen - 1.
db[em_len - s_len - h_len - 2] = 0x01;
db[em_len - s_len - h_len - 1..].copy_from_slice(salt);
// 9. Let dbMask = MGF(H, emLen - hLen - 1).
//
// 10. Let maskedDB = DB \xor dbMask.
mgf1_xor(db, hash, h);
// 11. Set the leftmost 8 * em_len - em_bits bits of the leftmost octet in
// maskedDB to zero.
db[0] &= 0xFF >> (8 * em_len - em_bits);
// 12. Let EM = maskedDB || H || 0xbc.
em[em_len - 1] = 0xBC;
Ok(em)
}
pub(crate) fn emsa_pss_encode_digest<D>(
m_hash: &[u8],
em_bits: usize,
salt: &[u8],
) -> Result<Vec<u8>>
where
D: Digest + FixedOutputReset,
{
// See [1], section 9.1.1
let h_len = <D as Digest>::output_size();
let s_len = salt.len();
let em_len = em_bits.div_ceil(8);
// 1. If the length of M is greater than the input limitation for the
// hash function (2^61 - 1 octets for SHA-1), output "message too
// long" and stop.
//
// 2. Let mHash = Hash(M), an octet string of length hLen.
if m_hash.len() != h_len {
return Err(Error::InputNotHashed);
}
// 3. If em_len < h_len + s_len + 2, output "encoding error" and stop.
if em_len < h_len + s_len + 2 {
// TODO: Key size too small
return Err(Error::Internal);
}
let mut em = vec![0; em_len];
let (db, h) = em.split_at_mut(em_len - h_len - 1);
let h = &mut h[..(em_len - 1) - db.len()];
// 4. Generate a random octet string salt of length s_len; if s_len = 0,
// then salt is the empty string.
//
// 5. Let
// M' = (0x)00 00 00 00 00 00 00 00 || m_hash || salt;
//
// M' is an octet string of length 8 + h_len + s_len with eight
// initial zero octets.
//
// 6. Let H = Hash(M'), an octet string of length h_len.
let prefix = [0u8; 8];
let mut hash = D::new();
Digest::update(&mut hash, prefix);
Digest::update(&mut hash, m_hash);
Digest::update(&mut hash, salt);
let hashed = hash.finalize_reset();
h.copy_from_slice(&hashed);
// 7. Generate an octet string PS consisting of em_len - s_len - h_len - 2
// zero octets. The length of PS may be 0.
//
// 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
// emLen - hLen - 1.
db[em_len - s_len - h_len - 2] = 0x01;
db[em_len - s_len - h_len - 1..].copy_from_slice(salt);
// 9. Let dbMask = MGF(H, emLen - hLen - 1).
//
// 10. Let maskedDB = DB \xor dbMask.
mgf1_xor_digest(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;
Ok(em)
}
fn emsa_pss_verify_pre<'a>(
m_hash: &[u8],
em: &'a mut [u8],
em_bits: usize,
s_len: Option<usize>,
h_len: usize,
) -> Result<(&'a mut [u8], &'a mut [u8])> {
// 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
if m_hash.len() != h_len {
return Err(Error::Verification);
}
let em_len = em.len(); //(em_bits + 7) / 8;
if let Some(s_len) = s_len {
// 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop.
if em_len < h_len + s_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[..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
// stop.
if db[0]
& (0xFF_u8
.checked_shl(8 - (8 * em_len - em_bits) as u32)
.unwrap_or(0))
!= 0
{
return Err(Error::Verification);
}
Ok((db, h))
}
fn emsa_pss_verify_salt(db: &[u8], em_len: usize, s_len: usize, h_len: usize) -> Choice {
// 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.
let (zeroes, rest) = db.split_at(em_len - h_len - s_len - 2);
let valid: Choice = zeroes.iter().fold(Choice::TRUE, |a, e| a & e.ct_eq(&0x00));
valid & rest[0].ct_eq(&0x01)
}
/// Detect salt length by scanning DB for the 0x01 separator byte.
/// Returns (s_len, valid) where s_len is 0 on failure.
fn emsa_pss_get_salt_len(db: &[u8], em_len: usize, h_len: usize) -> (usize, Choice) {
let em_len = em_len as u32;
let h_len = h_len as u32;
let max_scan_len = em_len - h_len - 2;
let mut separator_pos = 0u32;
let mut found_separator = Choice::FALSE;
let mut padding_valid = Choice::TRUE;
// Single forward scan to find separator and validate padding
for i in 0..=max_scan_len {
let byte_val = db[i as usize];
let is_zero = byte_val.ct_eq(&0x00);
let is_separator = byte_val.ct_eq(&0x01);
let is_invalid = !(is_zero | is_separator);
// Update separator position if we found one and haven't found one before
let should_update_pos = is_separator & !found_separator;
separator_pos = u32::ct_select(&separator_pos, &i, should_update_pos);
found_separator = Choice::ct_select(&found_separator, &Choice::TRUE, should_update_pos);
// Padding is invalid if we see a non-zero, non-separator byte before finding separator
let corrupts_padding = is_invalid & !found_separator;
padding_valid &= !corrupts_padding;
}
let salt_len = max_scan_len.wrapping_sub(separator_pos);
let final_valid = found_separator & padding_valid;
// Return 0 length on failure
let result_len = u32::ct_select(&0u32, &salt_len, final_valid);
(result_len as usize, final_valid)
}
pub(crate) fn emsa_pss_verify<D>(
m_hash: &[u8],
em: &mut [u8],
s_len: Option<usize>,
hash: &mut D,
key_bits: usize,
) -> Result<()>
where
D: Digest + FixedOutputReset,
{
let em_bits = key_bits - 1;
let em_len = em_bits.div_ceil(8);
let key_len = key_bits.div_ceil(8);
let h_len = <D as Digest>::output_size();
let em = &mut em[key_len - em_len..];
let (db, h) = emsa_pss_verify_pre(m_hash, em, em_bits, s_len, h_len)?;
// 7. Let dbMask = MGF(H, em_len - h_len - 1)
//
// 8. Let DB = maskedDB \xor dbMask
mgf1_xor(db, hash, &*h);
// 9. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in DB
// to zero.
db[0] &= 0xFF >> /*uint*/(8 * em_len - em_bits);
let (s_len, salt_valid) = match s_len {
Some(s_len) => (s_len, emsa_pss_verify_salt(db, em_len, s_len, h_len)),
None => emsa_pss_get_salt_len(db, em_len, h_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];
Digest::update(hash, &prefix[..]);
Digest::update(hash, m_hash);
Digest::update(hash, salt);
let h0 = hash.finalize_reset();
// 14. If H = H', output "consistent." Otherwise, output "inconsistent."
if (salt_valid & h0.as_slice().ct_eq(h)).into() {
Ok(())
} else {
Err(Error::Verification)
}
}
pub(crate) fn emsa_pss_verify_digest<D>(
m_hash: &[u8],
em: &mut [u8],
s_len: Option<usize>,
key_bits: usize,
) -> Result<()>
where
D: Digest + FixedOutputReset,
{
let em_bits = key_bits - 1;
let em_len = em_bits.div_ceil(8);
let key_len = key_bits.div_ceil(8);
let h_len = <D as Digest>::output_size();
let em = &mut em[key_len - em_len..];
let (db, h) = emsa_pss_verify_pre(m_hash, em, em_bits, s_len, h_len)?;
let mut hash = D::new();
// 7. Let dbMask = MGF(H, em_len - h_len - 1)
//
// 8. Let DB = maskedDB \xor dbMask
mgf1_xor_digest::<D>(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, salt_valid) = match s_len {
Some(s_len) => (s_len, emsa_pss_verify_salt(db, em_len, s_len, h_len)),
None => emsa_pss_get_salt_len(db, em_len, h_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];
Digest::update(&mut hash, &prefix[..]);
Digest::update(&mut hash, m_hash);
Digest::update(&mut hash, salt);
let h0 = hash.finalize_reset();
// 14. If H = H', output "consistent." Otherwise, output "inconsistent."
if (salt_valid & h0.as_slice().ct_eq(h)).into() {
Ok(())
} else {
Err(Error::Verification)
}
}
+484
View File
@@ -0,0 +1,484 @@
//! Generic RSA implementation
use core::cmp::Ordering;
use crypto_bigint::{
modular::{BoxedMontyForm, BoxedMontyParams},
BoxedUint, ConcatenatingMul, ConcatenatingSquare, Gcd, NonZero, Odd, RandomMod, Resize,
};
use rand_core::TryCryptoRng;
use zeroize::Zeroize;
use crate::errors::{Error, Result};
use crate::traits::keys::{PrivateKeyParts, PublicKeyParts};
/// ⚠️ Raw RSA encryption of m with the public key. No padding is performed.
///
/// # ☢️ WARNING: HAZARDOUS API ☢️
///
/// Use this function with great care! Raw RSA should never be used without an appropriate padding
/// or signature scheme. See the [module-level documentation][crate::hazmat] for more information.
#[inline]
pub fn rsa_encrypt<K: PublicKeyParts>(key: &K, m: &BoxedUint) -> Result<BoxedUint> {
let e = key.e();
let res = pow_mod_params_vartime_exp_bits(m, e, e.bits(), key.n_params());
Ok(res)
}
/// ⚠️ Performs raw RSA decryption with no padding or error checking.
///
/// Returns a plaintext `BoxedUint`. Performs RSA blinding if an `Rng` is passed.
///
/// # ☢️ WARNING: HAZARDOUS API ☢️
///
/// Use this function with great care! Raw RSA should never be used without an appropriate padding
/// or signature scheme. See the [module-level documentation][crate::hazmat] for more information.
#[inline]
pub fn rsa_decrypt<R: TryCryptoRng + ?Sized>(
rng: Option<&mut R>,
priv_key: &impl PrivateKeyParts,
c: &BoxedUint,
) -> Result<BoxedUint> {
// ELY gates every private exponent operation until RUSTSEC-2023-0071 has a patched release.
if cfg!(feature = "private-key-operations-disabled") {
return Err(Error::Decryption);
}
let n = priv_key.n();
let d = priv_key.d();
if c.bits_precision() != n.as_ref().bits_precision() {
return Err(Error::Decryption);
}
if c >= n.as_ref() {
return Err(Error::Decryption);
}
let mut ir = None;
let n_params = priv_key.n_params();
let bits = d.bits_precision();
let c = if let Some(rng) = rng {
let (blinded, unblinder) = blind(rng, priv_key, c, n_params)?;
ir = Some(unblinder);
blinded.try_resize(bits).ok_or(Error::Internal)?
} else {
c.try_resize(bits).ok_or(Error::Internal)?
};
let is_multiprime = priv_key.primes().len() > 2;
let m = match (
priv_key.dp(),
priv_key.dq(),
priv_key.qinv(),
priv_key.p_params(),
priv_key.q_params(),
) {
(Some(dp), Some(dq), Some(qinv), Some(p_params), Some(q_params)) if !is_multiprime => {
// We have the precalculated values needed for the CRT.
let p = &priv_key.primes()[0];
let q = &priv_key.primes()[1];
// precomputed: dP = (1/e) mod (p-1) = d mod (p-1)
// precomputed: dQ = (1/e) mod (q-1) = d mod (q-1)
// TODO: it may be faster to convert to and from Montgomery with prepared parameters
// (modulo `p` and `q`) rather than calculating the remainder directly.
// m1 = c^dP mod p
let p_wide = p_params.modulus().resize_unchecked(c.bits_precision());
let c_mod_dp = (&c % p_wide.as_nz_ref()).resize_unchecked(dp.bits_precision());
let cp = BoxedMontyForm::new(c_mod_dp, p_params);
let mut m1 = cp.pow(dp);
// m2 = c^dQ mod q
let q_wide = q_params.modulus().resize_unchecked(c.bits_precision());
let c_mod_dq = (&c % q_wide.as_nz_ref()).resize_unchecked(dq.bits_precision());
let cq = BoxedMontyForm::new(c_mod_dq, q_params);
let m2 = cq.pow(dq).retrieve();
// Note that since `p` and `q` may have different `bits_precision`,
// it may be different for `m1` and `m2` as well.
// (m1 - m2) mod p = (m1 mod p) - (m2 mod p) mod p
let m2_mod_p = match p_params.bits_precision().cmp(&q_params.bits_precision()) {
Ordering::Less => {
let p_wide = NonZero::new(p.clone())
.expect("`p` is non-zero")
.resize_unchecked(q_params.bits_precision());
(&m2 % p_wide).resize_unchecked(p_params.bits_precision())
}
Ordering::Greater => (&m2).resize_unchecked(p_params.bits_precision()),
Ordering::Equal => m2.clone(),
};
let m2r = BoxedMontyForm::new(m2_mod_p, p_params);
m1 -= &m2r;
// precomputed: qInv = (1/q) mod p
// h = qInv.(m1 - m2) mod p
let h = (qinv * m1).retrieve();
// m = m2 + h.q
let m2 = m2.try_resize(n.bits_precision()).ok_or(Error::Internal)?;
let hq = h
.concatenating_mul(&q)
.try_resize(n.bits_precision())
.ok_or(Error::Internal)?;
m2.wrapping_add(&hq)
}
_ => {
// c^d (mod n)
pow_mod_params(&c, d, n_params)
}
};
match ir {
Some(ref ir) => {
// unblind
let res = unblind(&m, ir, n_params);
Ok(res)
}
None => Ok(m),
}
}
/// ⚠️ Performs raw RSA decryption with no padding.
///
/// Returns a plaintext `BoxedUint`. Performs RSA blinding if an `Rng` is passed. This will also
/// check for errors in the CRT computation.
///
/// `c` must have the same `bits_precision` as the RSA key modulus.
///
/// # ☢️ WARNING: HAZARDOUS API ☢️
///
/// Use this function with great care! Raw RSA should never be used without an appropriate padding
/// or signature scheme. See the [module-level documentation][crate::hazmat] for more information.
#[inline]
pub fn rsa_decrypt_and_check<R: TryCryptoRng + ?Sized>(
priv_key: &impl PrivateKeyParts,
rng: Option<&mut R>,
c: &BoxedUint,
) -> Result<BoxedUint> {
let m = rsa_decrypt(rng, priv_key, c)?;
// In order to defend against errors in the CRT computation, m^e is
// calculated, which should match the original ciphertext.
let check = rsa_encrypt(priv_key, &m)?;
if c != &check {
return Err(Error::Internal);
}
Ok(m)
}
/// Returns the blinded c, along with the unblinding factor.
fn blind<R: TryCryptoRng + ?Sized, K: PublicKeyParts>(
rng: &mut R,
key: &K,
c: &BoxedUint,
n_params: &BoxedMontyParams,
) -> Result<(BoxedUint, BoxedUint)> {
// 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
// by multiplying by the multiplicative inverse of r.
debug_assert_eq!(&key.n().clone().get(), n_params.modulus());
let bits = key.n_bits_precision();
let mut r: BoxedUint = BoxedUint::zero_with_precision(bits);
let mut ir: Option<BoxedUint> = None;
while ir.is_none() {
r = BoxedUint::try_random_mod_vartime(rng, key.n()).map_err(|_| Error::Rng)?;
// r^-1 (mod n)
ir = r.invert_mod(key.n()).into();
}
let blinded = {
// r^e (mod n)
let e = key.e();
let mut rpowe = pow_mod_params_vartime_exp_bits(&r, e, e.bits(), n_params);
// c * r^e (mod n)
let c = c.mul_mod(&rpowe, n_params.modulus().as_nz_ref());
rpowe.zeroize();
c
};
let ir = ir.expect("loop exited");
debug_assert_eq!(blinded.bits_precision(), bits);
debug_assert_eq!(ir.bits_precision(), bits);
Ok((blinded, ir))
}
/// Given an m and unblinding factor, unblind the m.
fn unblind(m: &BoxedUint, unblinder: &BoxedUint, n_params: &BoxedMontyParams) -> BoxedUint {
// m * r^-1 (mod n)
debug_assert_eq!(
m.bits_precision(),
unblinder.bits_precision(),
"invalid unblinder"
);
debug_assert_eq!(
m.bits_precision(),
n_params.bits_precision(),
"invalid n_params"
);
m.mul_mod(unblinder, n_params.modulus().as_nz_ref())
}
/// Computes `base.pow_mod(exp, n)` with precomputed `n_params`.
fn pow_mod_params(base: &BoxedUint, exp: &BoxedUint, n_params: &BoxedMontyParams) -> BoxedUint {
let base = reduce_vartime(base, n_params);
base.pow(exp).retrieve()
}
/// Computes `base.pow_mod(exp, n)` with a bounded exponent and precomputed `n_params`.
///
/// The exponent bit length `exp_bits` may be leaked in the time pattern.
fn pow_mod_params_vartime_exp_bits(
base: &BoxedUint,
exp: &BoxedUint,
exp_bits: u32,
n_params: &BoxedMontyParams,
) -> BoxedUint {
let base = reduce_vartime(base, n_params);
base.pow_bounded_exp(exp, exp_bits).retrieve()
}
fn reduce_vartime(n: &BoxedUint, p: &BoxedMontyParams) -> BoxedMontyForm {
let modulus = p.modulus().as_nz_ref().clone();
let n_reduced = n.rem_vartime(&modulus).resize_unchecked(p.bits_precision());
BoxedMontyForm::new(n_reduced, p)
}
/// The following (deterministic) algorithm also recovers the prime factors `p` and `q` of a modulus `n`, given the
/// public exponent `e` and private exponent `d` using the method described in
/// [NIST 800-56B Appendix C.2](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf).
pub fn recover_primes(
n: &NonZero<BoxedUint>,
e: &BoxedUint,
d: &BoxedUint,
) -> Result<(BoxedUint, BoxedUint)> {
// Check precondition
// Note: because e is at most u64::MAX, it is already
// known to be < 2**256
if e <= &BoxedUint::from(2u64.pow(16)) {
return Err(Error::InvalidArguments);
}
// 1. Let a = (de 1) × GCD(n 1, de 1).
let bits = d.bits_precision() * 2;
let one = BoxedUint::one_with_precision(bits);
let e = e.resize_unchecked(d.bits_precision());
let d = d.resize_unchecked(d.bits_precision());
let n = n.resize_unchecked(bits);
let a1 = d.concatenating_mul(&e) - &one;
let a2 = (n.as_ref() - &one).gcd(&a1);
let a = a1.concatenating_mul(&a2);
let n = n.resize_unchecked(a.bits_precision());
// 2. Let m = floor(a /n) and r = a m n, so that a = m n + r and 0 ≤ r < n.
let m = &a / &n;
let r = a - m.concatenating_mul(&*n);
let n = n.get();
// 3. Let b = ( (n r)/(m + 1) ) + 1; if b is not an integer or b^2 ≤ 4n, then output an error indicator,
// and exit without further processing.
let modulus_check = (&n - &r) % NonZero::new(&m + &one).expect("adding 1");
if (!modulus_check.is_zero()).into() {
return Err(Error::InvalidArguments);
}
let b = ((&n - &r) / NonZero::new(&m + &one).expect("adding one")) + one;
let four = BoxedUint::from(4u32);
let four_n = n.concatenating_mul(&four);
let b_squared = b.concatenating_square();
if b_squared <= four_n {
return Err(Error::InvalidArguments);
}
let b_squared_minus_four_n = b_squared - four_n;
// 4. Let ϒ be the positive square root of b^2 4n; if ϒ is not an integer,
// then output an error indicator, and exit without further processing.
let y = b_squared_minus_four_n.floor_sqrt();
let y_squared = y.concatenating_square();
let sqrt_is_whole_number = y_squared == b_squared_minus_four_n;
if !sqrt_is_whole_number {
return Err(Error::InvalidArguments);
}
let bits = core::cmp::max(b.bits_precision(), y.bits_precision());
let two = NonZero::new(BoxedUint::from(2u64))
.expect("2 is non zero")
.resize_unchecked(bits);
let p = (&b + &y) / &two;
let q = (b - y) / two;
Ok((p, q))
}
/// Compute the modulus of a key from its primes.
pub(crate) fn compute_modulus(primes: &[BoxedUint]) -> Odd<BoxedUint> {
let mut primes = primes.iter();
let mut out = primes.next().expect("must at least be one prime").clone();
for p in primes {
out = out.concatenating_mul(&p);
}
Odd::new(out).expect("modulus must be odd")
}
/// Compute the private exponent from its primes (p and q) and public exponent
/// This uses Euler's totient function
#[inline]
pub(crate) fn compute_private_exponent_euler_totient(
primes: &[BoxedUint],
exp: &BoxedUint,
) -> Result<BoxedUint> {
if primes.len() < 2 {
return Err(Error::InvalidPrime);
}
let bits = primes[0].bits_precision();
let mut totient = BoxedUint::one_with_precision(bits);
for prime in primes {
totient = totient.concatenating_mul(&(prime - &BoxedUint::one()));
}
let exp = exp.resize_unchecked(totient.bits_precision());
// NOTE: `mod_inverse` checks if `exp` evenly divides `totient` and returns `None` if so.
// This ensures that `exp` is not a factor of any `(prime - 1)`.
let totient = NonZero::new(totient).expect("known");
match exp.invert_mod(&totient).into_option() {
Some(res) => Ok(res),
None => Err(Error::InvalidPrime),
}
}
/// Compute the private exponent from its primes (p and q) and public exponent
///
/// This is using the method defined by
/// [NIST 800-56B Section 6.2.1](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf#page=47).
/// (Carmichael function)
///
/// FIPS 186-4 **requires** the private exponent to be less than λ(n), which would
/// make Euler's totiem unreliable.
#[inline]
pub(crate) fn compute_private_exponent_carmicheal(
p: &BoxedUint,
q: &BoxedUint,
exp: &BoxedUint,
) -> Result<BoxedUint> {
let one = BoxedUint::one();
let p1 = p - &one;
let q1 = q - &one;
// LCM inlined
let gcd = p1.gcd(&q1);
let lcm = (p1 / NonZero::new(gcd).expect("gcd is non zero")).concatenating_mul(&q1);
let exp = exp.resize_unchecked(lcm.bits_precision());
if let Some(d) = exp.invert_mod(&NonZero::new(lcm).expect("non zero")).into() {
Ok(d)
} else {
// `exp` evenly divides `lcm`
Err(Error::InvalidPrime)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "private-key-operations-disabled")]
#[test]
fn private_operations_are_disabled_before_exponentiation() {
use crate::{dummy_rng::DummyRng, pkcs8::DecodePrivateKey, RsaPrivateKey};
let private_key = RsaPrivateKey::from_pkcs8_der(include_bytes!(
"../../tests/examples/pkcs8/rsa2048-priv.der"
))
.unwrap();
let message = BoxedUint::from(42u64).resize_unchecked(private_key.n_bits_precision());
let ciphertext = rsa_encrypt(&private_key, &message).unwrap();
assert_eq!(
rsa_decrypt::<DummyRng>(None, &private_key, &ciphertext),
Err(Error::Decryption)
);
}
#[test]
fn recover_primes_works() {
let bits = 2048;
let n = BoxedUint::from_be_hex(
concat!(
"d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222",
"b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e",
"74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb",
"28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7",
"ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078c",
"c780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2",
"e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf67",
"76fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b"
),
bits,
)
.unwrap();
let e = BoxedUint::from(65_537u64);
let d = BoxedUint::from_be_hex(
concat!(
"c4e70c689162c94c660828191b52b4d8392115df486a9adbe831e458d7395832",
"0dc1b755456e93701e9702d76fb0b92f90e01d1fe248153281fe79aa9763a92f",
"ae69d8d7ecd144de29fa135bd14f9573e349e45031e3b76982f583003826c552",
"e89a397c1a06bd2163488630d92e8c2bb643d7abef700da95d685c941489a46f",
"54b5316f62b5d2c3a7f1bbd134cb37353a44683fdc9d95d36458de22f6c44057",
"fe74a0a436c4308f73f4da42f35c47ac16a7138d483afc91e41dc3a1127382e0",
"c0f5119b0221b4fc639d6b9c38177a6de9b526ebd88c38d7982c07f98a0efd87",
"7d508aae275b946915c02e2e1106d175d74ec6777f5e80d12c053d9c7be1e341"
),
bits,
)
.unwrap();
let p = BoxedUint::from_be_hex(
concat!(
"f827bbf3a41877c7cc59aebf42ed4b29c32defcb8ed96863d5b090a05a8930dd",
"624a21c9dcf9838568fdfa0df65b8462a5f2ac913d6c56f975532bd8e78fb07b",
"d405ca99a484bcf59f019bbddcb3933f2bce706300b4f7b110120c5df9018159",
"067c35da3061a56c8635a52b54273b31271b4311f0795df6021e6355e1a42e61"
),
bits / 2,
)
.unwrap();
let q = BoxedUint::from_be_hex(
concat!(
"da4817ce0089dd36f2ade6a3ff410c73ec34bf1b4f6bda38431bfede11cef1f7",
"f6efa70e5f8063a3b1f6e17296ffb15feefa0912a0325b8d1fd65a559e717b5b",
"961ec345072e0ec5203d03441d29af4d64054a04507410cf1da78e7b6119d909",
"ec66e6ad625bf995b279a4b3c5be7d895cd7c5b9c4c497fde730916fcdb4e41b"
),
bits / 2,
)
.unwrap();
let (mut p1, mut q1) = recover_primes(&NonZero::new(n).unwrap(), &e, &d).unwrap();
if p1 < q1 {
std::mem::swap(&mut p1, &mut q1);
}
assert_eq!(p, p1);
assert_eq!(q, q1);
}
}
+24
View File
@@ -0,0 +1,24 @@
use core::convert::Infallible;
use rand_core::{TryCryptoRng, TryRng};
/// This is a dummy RNG for cases when we need a concrete RNG type
/// which does not get used.
#[derive(Copy, Clone)]
pub(crate) struct DummyRng;
impl TryRng for DummyRng {
type Error = Infallible;
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
unimplemented!();
}
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
unimplemented!();
}
fn try_fill_bytes(&mut self, _: &mut [u8]) -> Result<(), Self::Error> {
unimplemented!();
}
}
impl TryCryptoRng for DummyRng {}
+240
View File
@@ -0,0 +1,240 @@
//! PKCS#1 and PKCS#8 encoding support.
//!
//! Note: PKCS#1 support is achieved through a blanket impl of the
//! `pkcs1` crate's traits for types which impl the `pkcs8` crate's traits.
#![cfg(feature = "encoding")]
use crate::{
traits::{PrivateKeyParts, PublicKeyParts},
RsaPrivateKey, RsaPublicKey,
};
use core::convert::{TryFrom, TryInto};
use crypto_bigint::{BoxedUint, NonZero, Resize};
use pkcs8::{
der::{asn1::OctetStringRef, Decode},
Document, EncodePrivateKey, EncodePublicKey, ObjectIdentifier, SecretDocument,
};
use zeroize::Zeroizing;
/// ObjectID for the RSA PSS keys
pub const ID_RSASSA_PSS: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.10");
// PKCS#1
fn uint_from_slice(data: &[u8], bits: u32) -> pkcs1::Result<BoxedUint> {
BoxedUint::from_be_slice(data, bits).map_err(|_| pkcs1::Error::KeyMalformed)
}
impl pkcs1::DecodeRsaPrivateKey for RsaPrivateKey {
fn from_pkcs1_der(bytes: &[u8]) -> pkcs1::Result<Self> {
pkcs1::RsaPrivateKey::from_der(bytes)?.try_into()
}
}
impl pkcs1::DecodeRsaPublicKey for RsaPublicKey {
fn from_pkcs1_der(bytes: &[u8]) -> pkcs1::Result<Self> {
pkcs1::RsaPublicKey::from_der(bytes)?.try_into()
}
}
impl TryFrom<pkcs1::RsaPrivateKey<'_>> for RsaPrivateKey {
type Error = pkcs1::Error;
fn try_from(pkcs1_key: pkcs1::RsaPrivateKey<'_>) -> pkcs1::Result<RsaPrivateKey> {
use pkcs1::Error::KeyMalformed;
// Multi-prime RSA keys not currently supported
if pkcs1_key.version() != pkcs1::Version::TwoPrime {
return Err(pkcs1::Error::Version);
}
let bits = u32::try_from(pkcs1_key.modulus.as_bytes().len()).map_err(|_| KeyMalformed)? * 8;
let n = uint_from_slice(pkcs1_key.modulus.as_bytes(), bits)?;
let bits_e = u32::try_from(pkcs1_key.public_exponent.as_bytes().len())
.map_err(|_| pkcs1::Error::KeyMalformed)?
* 8;
let e = uint_from_slice(pkcs1_key.public_exponent.as_bytes(), bits_e)?;
let e = Option::from(e).ok_or(KeyMalformed)?;
let d = uint_from_slice(pkcs1_key.private_exponent.as_bytes(), bits)?;
let prime1 = uint_from_slice(pkcs1_key.prime1.as_bytes(), bits)?;
let prime2 = uint_from_slice(pkcs1_key.prime2.as_bytes(), bits)?;
let primes = vec![prime1, prime2];
RsaPrivateKey::from_components(n, e, d, primes).map_err(|_| KeyMalformed)
}
}
impl TryFrom<pkcs1::RsaPublicKey<'_>> for RsaPublicKey {
type Error = pkcs1::Error;
fn try_from(pkcs1_key: pkcs1::RsaPublicKey<'_>) -> pkcs1::Result<Self> {
use pkcs1::Error::KeyMalformed;
let bits = u32::try_from(pkcs1_key.modulus.as_bytes().len()).map_err(|_| KeyMalformed)? * 8;
let n = uint_from_slice(pkcs1_key.modulus.as_bytes(), bits)?;
let bits_e = u32::try_from(pkcs1_key.public_exponent.as_bytes().len())
.map_err(|_| KeyMalformed)?
* 8;
let e = uint_from_slice(pkcs1_key.public_exponent.as_bytes(), bits_e)?;
RsaPublicKey::new(n, e).map_err(|_| KeyMalformed)
}
}
impl pkcs1::EncodeRsaPrivateKey for RsaPrivateKey {
fn to_pkcs1_der(&self) -> pkcs1::Result<SecretDocument> {
// Check if the key is multi prime
if self.primes.len() > 2 {
return Err(pkcs1::Error::Crypto);
}
let modulus = self.n().to_be_bytes();
let public_exponent = self.e().to_be_bytes();
let private_exponent = Zeroizing::new(self.d().to_be_bytes());
let prime1 = Zeroizing::new(self.primes[0].to_be_bytes());
let prime2 = Zeroizing::new(self.primes[1].to_be_bytes());
let bits = self.d().bits_precision();
debug_assert!(bits >= self.primes[0].bits_vartime());
debug_assert!(bits >= self.primes[1].bits_vartime());
let exponent1 = Zeroizing::new(
(self.d()
% NonZero::new((&self.primes[0]).resize_unchecked(bits) - &BoxedUint::one())
.unwrap())
.to_be_bytes(),
);
let exponent2 = Zeroizing::new(
(self.d()
% NonZero::new((&self.primes[1]).resize_unchecked(bits) - &BoxedUint::one())
.unwrap())
.to_be_bytes(),
);
let coefficient = Zeroizing::new(
self.crt_coefficient()
.ok_or(pkcs1::Error::Crypto)?
.to_be_bytes(),
);
Ok(SecretDocument::encode_msg(&pkcs1::RsaPrivateKey {
modulus: pkcs1::UintRef::new(&modulus)?,
public_exponent: pkcs1::UintRef::new(&public_exponent)?,
private_exponent: pkcs1::UintRef::new(&private_exponent)?,
prime1: pkcs1::UintRef::new(&prime1)?,
prime2: pkcs1::UintRef::new(&prime2)?,
exponent1: pkcs1::UintRef::new(&exponent1)?,
exponent2: pkcs1::UintRef::new(&exponent2)?,
coefficient: pkcs1::UintRef::new(&coefficient)?,
other_prime_infos: None,
})?)
}
}
impl pkcs1::EncodeRsaPublicKey for RsaPublicKey {
fn to_pkcs1_der(&self) -> pkcs1::Result<Document> {
let modulus = self.n().to_be_bytes();
let public_exponent = self.e().to_be_bytes();
Ok(Document::encode_msg(&pkcs1::RsaPublicKey {
modulus: pkcs1::UintRef::new(&modulus)?,
public_exponent: pkcs1::UintRef::new(&public_exponent)?,
})?)
}
}
// PKCS#8
/// Verify that the `AlgorithmIdentifier` for a key is correct.
pub(crate) fn verify_algorithm_id(algorithm: &spki::AlgorithmIdentifierRef) -> spki::Result<()> {
match algorithm.oid {
pkcs1::ALGORITHM_OID => {
if algorithm.parameters_any()? != pkcs8::der::asn1::Null.into() {
return Err(spki::Error::KeyMalformed);
}
}
ID_RSASSA_PSS => {
if algorithm.parameters.is_some() {
return Err(spki::Error::KeyMalformed);
}
}
_ => return Err(spki::Error::OidUnknown { oid: algorithm.oid }),
};
Ok(())
}
impl TryFrom<pkcs8::PrivateKeyInfoRef<'_>> for RsaPrivateKey {
type Error = pkcs8::Error;
fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
verify_algorithm_id(&private_key_info.algorithm)?;
pkcs1::RsaPrivateKey::try_from(private_key_info.private_key)
.and_then(TryInto::try_into)
.map_err(pkcs1_error_to_pkcs8)
}
}
impl TryFrom<pkcs8::SubjectPublicKeyInfoRef<'_>> for RsaPublicKey {
type Error = spki::Error;
fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
use spki::Error::KeyMalformed;
verify_algorithm_id(&spki.algorithm)?;
pkcs1::RsaPublicKey::try_from(spki.subject_public_key.as_bytes().ok_or(KeyMalformed)?)
.and_then(TryInto::try_into)
.map_err(pkcs1_error_to_spki)
}
}
impl EncodePrivateKey for RsaPrivateKey {
fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
let private_key =
pkcs1::EncodeRsaPrivateKey::to_pkcs1_der(self).map_err(pkcs1_error_to_pkcs8)?;
pkcs8::PrivateKeyInfoRef::new(
pkcs1::ALGORITHM_ID,
OctetStringRef::new(private_key.as_bytes())?,
)
.try_into()
}
}
impl EncodePublicKey for RsaPublicKey {
fn to_public_key_der(&self) -> spki::Result<Document> {
let subject_public_key =
pkcs1::EncodeRsaPublicKey::to_pkcs1_der(self).map_err(pkcs1_error_to_spki)?;
pkcs8::SubjectPublicKeyInfoRef {
algorithm: pkcs1::ALGORITHM_ID,
subject_public_key: pkcs8::der::asn1::BitStringRef::new(
0,
subject_public_key.as_ref(),
)?,
}
.try_into()
}
}
/// Convert `pkcs1::Result` to `pkcs8::Result`.
fn pkcs1_error_to_pkcs8(error: pkcs1::Error) -> pkcs8::Error {
match error {
pkcs1::Error::Asn1(e) => pkcs8::Error::Asn1(e),
_ => pkcs8::KeyError::Invalid.into(),
}
}
/// Convert `pkcs1::Result` to `spki::Result`.
fn pkcs1_error_to_spki(error: pkcs1::Error) -> spki::Error {
match error {
pkcs1::Error::Asn1(e) => spki::Error::Asn1(e),
_ => spki::Error::KeyMalformed,
}
}
+141
View File
@@ -0,0 +1,141 @@
//! Error types.
/// Alias for [`core::result::Result`] with the `rsa` crate's [`Error`] type.
pub type Result<T> = core::result::Result<T, Error>;
/// Error types
#[derive(Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Error {
/// Invalid padding scheme.
InvalidPaddingScheme,
/// Decryption error.
Decryption,
/// Verification error.
Verification,
/// Message too long.
MessageTooLong,
/// Input must be hashed.
InputNotHashed,
/// Number of primes must be 2 or greater.
NprimesTooSmall,
/// Too few primes of a given length to generate an RSA key.
TooFewPrimes,
/// Invalid prime value.
InvalidPrime,
/// Invalid modulus.
InvalidModulus,
/// Invalid exponent.
InvalidExponent,
/// Invalid coefficient.
InvalidCoefficient,
/// Modulus too small.
ModulusTooSmall,
/// Modulus too large.
ModulusTooLarge,
/// Public exponent too small.
PublicExponentTooSmall,
/// Public exponent too large.
PublicExponentTooLarge,
/// PKCS#1 error.
#[cfg(feature = "encoding")]
Pkcs1(pkcs1::Error),
/// PKCS#8 error.
#[cfg(feature = "encoding")]
Pkcs8(pkcs8::Error),
/// Internal error.
Internal,
/// Label too long.
LabelTooLong,
/// Invalid padding length.
InvalidPadLen,
/// Invalid arguments.
InvalidArguments,
/// Decoding error.
Decode(crypto_bigint::DecodeError),
/// Random number generator error.
Rng,
}
impl core::error::Error for Error {}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
Error::InvalidPaddingScheme => write!(f, "invalid padding scheme"),
Error::Decryption => write!(f, "decryption error"),
Error::Verification => write!(f, "verification error"),
Error::MessageTooLong => write!(f, "message too long"),
Error::InputNotHashed => write!(f, "input must be hashed"),
Error::NprimesTooSmall => write!(f, "nprimes must be >= 2"),
Error::TooFewPrimes => {
write!(f, "too few primes of given length to generate an RSA key")
}
Error::InvalidPrime => write!(f, "invalid prime value"),
Error::InvalidModulus => write!(f, "invalid modulus"),
Error::InvalidExponent => write!(f, "invalid exponent"),
Error::InvalidCoefficient => write!(f, "invalid coefficient"),
Error::ModulusTooSmall => write!(f, "modulus too small"),
Error::ModulusTooLarge => write!(f, "modulus too large"),
Error::PublicExponentTooSmall => write!(f, "public exponent too small"),
Error::PublicExponentTooLarge => write!(f, "public exponent too large"),
#[cfg(feature = "encoding")]
Error::Pkcs1(err) => write!(f, "{}", err),
#[cfg(feature = "encoding")]
Error::Pkcs8(err) => write!(f, "{}", err),
Error::Internal => write!(f, "internal error"),
Error::LabelTooLong => write!(f, "label too long"),
Error::InvalidPadLen => write!(f, "invalid padding length"),
Error::InvalidArguments => write!(f, "invalid arguments"),
Error::Decode(err) => write!(f, "{:?}", err),
Error::Rng => write!(f, "rng error"),
}
}
}
#[cfg(feature = "encoding")]
impl From<pkcs1::Error> for Error {
fn from(err: pkcs1::Error) -> Error {
Error::Pkcs1(err)
}
}
#[cfg(feature = "encoding")]
impl From<pkcs8::Error> for Error {
fn from(err: pkcs8::Error) -> Error {
Error::Pkcs8(err)
}
}
impl From<crypto_bigint::DecodeError> for Error {
fn from(err: crypto_bigint::DecodeError) -> Error {
Error::Decode(err)
}
}
impl From<Error> for signature::Error {
fn from(err: Error) -> Self {
Self::from_source(err)
}
}
+14
View File
@@ -0,0 +1,14 @@
//! ⚠️ Low-level "hazmat" RSA functions.
//!
//! # ☢️ WARNING: HAZARDOUS API ☢️
//!
//! This module holds functions that apply RSA's core encryption and decryption
//! primitives to raw data without adding or removing appropriate padding. A
//! well-reviewed padding scheme is crucial to the security of RSA, so there are
//! very few valid uses cases for this API. It's intended to be used for
//! implementing well-reviewed higher-level constructions.
//!
//! We do NOT recommend using it to implement any algorithm which has not
//! received extensive peer review by cryptographers.
pub use crate::algorithms::rsa::{rsa_decrypt, rsa_decrypt_and_check, rsa_encrypt};
+1305
View File
File diff suppressed because it is too large Load Diff
+269
View File
@@ -0,0 +1,269 @@
#![cfg_attr(not(test), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")]
#![warn(missing_docs)]
#![cfg_attr(not(test), deny(clippy::unwrap_used))]
//! # Supported algorithms
//!
//! This crate supports several schemes described in [RFC8017]:
//!
//! - [OAEP encryption scheme](#oaep-encryption)
//! - [PKCS#1 v1.5 encryption scheme](#pkcs1-v15-encryption)
//! - [PKCS#1 v1.5 signature scheme](#pkcs1-v15-signatures)
//! - [PSS signature scheme](#pss-signatures)
//!
//! These schemes are described below.
//!
//! # Usage
//!
//! ## OAEP encryption
//!
//! Note: requires `sha2` feature of `rsa` crate is enabled.
//!
#![cfg_attr(feature = "sha2", doc = "```")]
#![cfg_attr(not(feature = "sha2"), doc = "```ignore")]
//! use rsa::{RsaPrivateKey, RsaPublicKey, Oaep, sha2::Sha256};
//!
//! let mut rng = rand::rng();
//!
//! let bits = 2048;
//! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
//! let public_key = RsaPublicKey::from(&private_key);
//!
//! // Encrypt
//! let data = b"hello world";
//! let padding = Oaep::<Sha256>::new();
//! let enc_data = public_key.encrypt(&mut rng, padding, &data[..]).expect("failed to encrypt");
//! assert_ne!(&data[..], &enc_data[..]);
//!
//! // Decrypt
//! let padding = Oaep::<Sha256>::new();
//! let dec_data = private_key.decrypt(padding, &enc_data).expect("failed to decrypt");
//! assert_eq!(&data[..], &dec_data[..]);
//! ```
//!
//! ## PKCS#1 v1.5 encryption
//!
//! <div class="warning">
//! <b>Warning:</b>
//! See security notes in the <code><a href="./pkcs1v15/index.html">pkcs1v15</a></code> module.
//! </div>
//!
//! ```
//! use rsa::{RsaPrivateKey, RsaPublicKey, Pkcs1v15Encrypt};
//!
//! let mut rng = rand::rng();
//!
//! let bits = 2048;
//! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
//! let public_key = RsaPublicKey::from(&private_key);
//!
//! // Encrypt
//! let data = b"hello world";
//! let enc_data = public_key.encrypt(&mut rng, Pkcs1v15Encrypt, &data[..]).expect("failed to encrypt");
//! assert_ne!(&data[..], &enc_data[..]);
//!
//! // Decrypt
//! let dec_data = private_key.decrypt(Pkcs1v15Encrypt, &enc_data).expect("failed to decrypt");
//! assert_eq!(&data[..], &dec_data[..]);
//! ```
//!
//! ## PKCS#1 v1.5 signatures
//!
//! <div class="warning">
//! <b>Warning:</b>
//! See security notes in the <code><a href="./pkcs1v15/index.html">pkcs1v15</a></code> module.
//! </div>
//!
//! Note: requires `sha2` feature of `rsa` crate is enabled.
//!
#![cfg_attr(feature = "sha2", doc = "```")]
#![cfg_attr(not(feature = "sha2"), doc = "```ignore")]
//! use rsa::RsaPrivateKey;
//! use rsa::pkcs1v15::{SigningKey, VerifyingKey};
//! use rsa::signature::{Keypair, RandomizedSigner, SignatureEncoding, Verifier};
//! use rsa::sha2::{Digest, Sha256};
//!
//! let mut rng = rand::rng();
//!
//! let bits = 2048;
//! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
//! let signing_key = SigningKey::<Sha256>::new(private_key);
//! let verifying_key = signing_key.verifying_key();
//!
//! // Sign
//! let data = b"hello world";
//! let signature = signing_key.sign_with_rng(&mut rng, data);
//! assert_ne!(signature.to_bytes().as_ref(), data.as_slice());
//!
//! // Verify
//! verifying_key.verify(data, &signature).expect("failed to verify");
//! ```
//!
//! ## PSS signatures
//!
//! Note: requires `sha2` feature of `rsa` crate is enabled.
//!
#![cfg_attr(feature = "sha2", doc = "```")]
#![cfg_attr(not(feature = "sha2"), doc = "```ignore")]
//! use rsa::RsaPrivateKey;
//! use rsa::pss::{BlindedSigningKey, VerifyingKey};
//! use rsa::signature::{Keypair,RandomizedSigner, SignatureEncoding, Verifier};
//! use rsa::sha2::{Digest, Sha256};
//!
//! let mut rng = rand::rng();
//!
//! let bits = 2048;
//! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
//! let signing_key = BlindedSigningKey::<Sha256>::new(private_key);
//! let verifying_key = signing_key.verifying_key();
//!
//! // Sign
//! let data = b"hello world";
//! let signature = signing_key.sign_with_rng(&mut rng, data);
//! assert_ne!(signature.to_bytes().as_ref(), data);
//!
//! // Verify
//! verifying_key.verify(data, &signature).expect("failed to verify");
//! ```
//!
//! ## PKCS#1 RSA Key Encoding
//!
//! PKCS#1 supports a legacy format for encoding RSA keys as binary (DER) or
//! text (PEM) data.
//!
//! You can recognize PEM encoded PKCS#1 keys because they have "RSA * KEY" in
//! the type label, e.g.:
//!
//! ```text
//! -----BEGIN RSA PRIVATE KEY-----
//! ```
//!
//! Most modern applications use the newer PKCS#8 format instead (see below).
//!
//! The following traits can be used to decode/encode [`RsaPrivateKey`] and
//! [`RsaPublicKey`] as PKCS#1. Note that [`pkcs1`] is re-exported from the
//! toplevel of the `rsa` crate:
//!
//! - [`pkcs1::DecodeRsaPrivateKey`]: decode RSA private keys from PKCS#1
//! - [`pkcs1::EncodeRsaPrivateKey`]: encode RSA private keys to PKCS#1
//! - [`pkcs1::DecodeRsaPublicKey`]: decode RSA public keys from PKCS#1
//! - [`pkcs1::EncodeRsaPublicKey`]: encode RSA public keys to PKCS#1
//!
//! ### Example
//!
//! ```
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # #[cfg(all(feature = "encoding", feature = "std"))]
//! # {
//! use rsa::{RsaPublicKey, pkcs1::DecodeRsaPublicKey};
//!
//! let pem = "-----BEGIN RSA PUBLIC KEY-----
//! MIIBCgKCAQEAtsQsUV8QpqrygsY+2+JCQ6Fw8/omM71IM2N/R8pPbzbgOl0p78MZ
//! GsgPOQ2HSznjD0FPzsH8oO2B5Uftws04LHb2HJAYlz25+lN5cqfHAfa3fgmC38Ff
//! wBkn7l582UtPWZ/wcBOnyCgb3yLcvJrXyrt8QxHJgvWO23ITrUVYszImbXQ67YGS
//! 0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0NfFdfsZhTT8YbxBvA8FdODgEwx7u/vf3J
//! 9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejIn04APPKIjpMyQdnWlby7rNyQtE4+CV+j
//! cFjqJbE/Xilcvqxt6DirjFCvYeKYl1uHLwIDAQAB
//! -----END RSA PUBLIC KEY-----";
//!
//! let public_key = RsaPublicKey::from_pkcs1_pem(pem)?;
//! # }
//! # Ok(())
//! # }
//! ```
//!
//! ## PKCS#8 RSA Key Encoding
//!
//! PKCS#8 is a private key format with support for multiple algorithms.
//! Like PKCS#1, it can be encoded as binary (DER) or text (PEM).
//!
//! You can recognize PEM encoded PKCS#8 keys because they *don't* have
//! an algorithm name in the type label, e.g.:
//!
//! ```text
//! -----BEGIN PRIVATE KEY-----
//! ```
//!
//! The following traits can be used to decode/encode [`RsaPrivateKey`] and
//! [`RsaPublicKey`] as PKCS#8. Note that [`pkcs8`] is re-exported from the
//! toplevel of the `rsa` crate:
//!
//! - [`pkcs8::DecodePrivateKey`]: decode private keys from PKCS#8
//! - [`pkcs8::EncodePrivateKey`]: encode private keys to PKCS#8
//! - [`pkcs8::DecodePublicKey`]: decode public keys from PKCS#8
//! - [`pkcs8::EncodePublicKey`]: encode public keys to PKCS#8
//!
//! ### Example
//!
//! ```
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # #[cfg(all(feature = "encoding", feature = "std"))]
//! # {
//! use rsa::{RsaPublicKey, pkcs8::DecodePublicKey};
//!
//! let pem = "-----BEGIN PUBLIC KEY-----
//! MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtsQsUV8QpqrygsY+2+JC
//! Q6Fw8/omM71IM2N/R8pPbzbgOl0p78MZGsgPOQ2HSznjD0FPzsH8oO2B5Uftws04
//! LHb2HJAYlz25+lN5cqfHAfa3fgmC38FfwBkn7l582UtPWZ/wcBOnyCgb3yLcvJrX
//! yrt8QxHJgvWO23ITrUVYszImbXQ67YGS0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0N
//! fFdfsZhTT8YbxBvA8FdODgEwx7u/vf3J9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejI
//! n04APPKIjpMyQdnWlby7rNyQtE4+CV+jcFjqJbE/Xilcvqxt6DirjFCvYeKYl1uH
//! LwIDAQAB
//! -----END PUBLIC KEY-----";
//!
//! let public_key = RsaPublicKey::from_public_key_pem(pem)?;
//! # }
//! # Ok(())
//! # }
//! ```
//!
//! [RFC8017]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1
//!
// TODO(tarcieri): figure out why rustdoc isn't rendering these links correctly
//! [`pkcs8::DecodePublicKey`]: https://docs.rs/pkcs8/latest/pkcs8/trait.DecodePublicKey.html
//! [`pkcs8::EncodePublicKey`]: https://docs.rs/pkcs8/latest/pkcs8/trait.EncodePublicKey.html
#[cfg(doctest)]
pub struct ReadmeDoctests;
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
pub use crypto_bigint::BoxedUint;
pub use rand_core;
pub use signature;
mod algorithms;
pub mod errors;
pub mod oaep;
pub mod pkcs1v15;
pub mod pss;
pub mod traits;
mod dummy_rng;
mod encoding;
mod key;
#[cfg(feature = "encoding")]
pub use pkcs1;
#[cfg(feature = "encoding")]
pub use pkcs8;
#[cfg(feature = "sha2")]
pub use sha2;
pub use crate::{
errors::{Error, Result},
key::{RsaPrivateKey, RsaPublicKey},
oaep::Oaep,
pkcs1v15::{Pkcs1v15Encrypt, Pkcs1v15Sign},
pss::Pss,
traits::keys::CrtValue,
};
#[cfg(feature = "hazmat")]
pub mod hazmat;
+612
View File
@@ -0,0 +1,612 @@
//! Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1).
//!
//! # Usage
//!
//! See [code example in the toplevel rustdoc](../index.html#oaep-encryption).
mod decrypting_key;
mod encrypting_key;
pub use self::{decrypting_key::DecryptingKey, encrypting_key::EncryptingKey};
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::fmt;
use crypto_bigint::BoxedUint;
use digest::{Digest, FixedOutputReset};
use rand_core::TryCryptoRng;
use crate::algorithms::oaep::*;
use crate::algorithms::pad::{uint_to_be_pad, uint_to_zeroizing_be_pad};
use crate::algorithms::rsa::{rsa_decrypt_and_check, rsa_encrypt};
use crate::errors::{Error, Result};
use crate::key::{self, RsaPrivateKey, RsaPublicKey};
use crate::traits::{PaddingScheme, PublicKeyParts};
/// 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<D, MGD = D> {
/// Digest type to use.
pub digest: D,
/// Digest to use for Mask Generation Function (MGF).
pub mgf_digest: MGD,
/// Optional label.
pub label: Option<Box<[u8]>>,
}
impl<D> Default for Oaep<D>
where
D: Digest + FixedOutputReset,
{
fn default() -> Self {
Self::new()
}
}
impl<D> Oaep<D>
where
D: Digest + FixedOutputReset,
{
/// 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::{RsaPublicKey, Oaep};
/// use base64ct::{Base64, Encoding};
/// use crypto_bigint::BoxedUint;
///
/// let n_bytes = Base64::decode_vec("seAOhmYFAjH6NOaB54dboqw86uPXV/oK9ayJGV4mVClbvsDBJmF3bVkOaVMp9ogcFJTFFSy5g2HsTZIfHyuQVUJADb+BeRnkYrYhRvNJOKj2pcDbkxYe9XGMx8pIvxkDFnIpusb3gUsuzMUAU5qIstjwQKzuD51c6uJi0HAtQkr6Wmlt34SX7xkD/MfRuTu9uqmHmkiiJaCDHB2reYTPguetSWfuvp1qBJDNgSsp7BjwYANWldyrmZ8cLXEXYMUG5vtsWMxUzl8ertEr6kbnGM0OJghNuEtittW/dfTPvk683R1jj0hNaMzvHK8xYldUlLuwmWCYIIvpHBaA/w+FwQ==").unwrap();
/// let e_bytes = Base64::decode_vec("AQAB").unwrap();
/// let n = BoxedUint::from_be_slice(&n_bytes, 2048).unwrap();
/// let e = BoxedUint::from_be_slice(&e_bytes, 32).unwrap();
///
/// let mut rng = rand::rng();
/// let key = RsaPublicKey::new(n, e).unwrap();
/// let padding = Oaep::<Sha256>::new();
/// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap();
/// ```
pub fn new() -> Self {
Self {
digest: D::new(),
mgf_digest: D::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<S: Into<Box<[u8]>>>(label: S) -> Self {
Self {
digest: D::new(),
mgf_digest: D::new(),
label: Some(label.into()),
}
}
}
impl<D, MGD> Oaep<D, MGD>
where
D: Digest + FixedOutputReset,
MGD: Digest + FixedOutputReset,
{
/// 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::{RsaPublicKey, Oaep};
/// use base64ct::{Base64, Encoding};
/// use crypto_bigint::BoxedUint;
///
/// let n_bytes = Base64::decode_vec("seAOhmYFAjH6NOaB54dboqw86uPXV/oK9ayJGV4mVClbvsDBJmF3bVkOaVMp9ogcFJTFFSy5g2HsTZIfHyuQVUJADb+BeRnkYrYhRvNJOKj2pcDbkxYe9XGMx8pIvxkDFnIpusb3gUsuzMUAU5qIstjwQKzuD51c6uJi0HAtQkr6Wmlt34SX7xkD/MfRuTu9uqmHmkiiJaCDHB2reYTPguetSWfuvp1qBJDNgSsp7BjwYANWldyrmZ8cLXEXYMUG5vtsWMxUzl8ertEr6kbnGM0OJghNuEtittW/dfTPvk683R1jj0hNaMzvHK8xYldUlLuwmWCYIIvpHBaA/w+FwQ==").unwrap();
/// let e_bytes = Base64::decode_vec("AQAB").unwrap();
/// let n = BoxedUint::from_be_slice(&n_bytes, 2048).unwrap();
/// let e = BoxedUint::from_be_slice(&e_bytes, 32).unwrap();
///
/// let mut rng = rand::rng();
/// let key = RsaPublicKey::new(n, e).unwrap();
/// let padding = Oaep::<Sha256, Sha1>::new_with_mgf_hash();
/// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap();
/// ```
pub fn new_with_mgf_hash() -> Self {
Self {
digest: D::new(),
mgf_digest: MGD::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<S: Into<Box<[u8]>>>(label: S) -> Self {
Self {
digest: D::new(),
mgf_digest: MGD::new(),
label: Some(label.into()),
}
}
}
impl<D, MGD> PaddingScheme for Oaep<D, MGD>
where
D: Digest + FixedOutputReset,
MGD: Digest + FixedOutputReset,
{
fn decrypt<Rng: TryCryptoRng + ?Sized>(
mut self,
rng: Option<&mut Rng>,
priv_key: &RsaPrivateKey,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
decrypt(
rng,
priv_key,
ciphertext,
&mut self.digest,
&mut self.mgf_digest,
self.label,
)
}
fn encrypt<Rng: TryCryptoRng + ?Sized>(
mut self,
rng: &mut Rng,
pub_key: &RsaPublicKey,
msg: &[u8],
) -> Result<Vec<u8>> {
encrypt(
rng,
pub_key,
msg,
&mut self.digest,
&mut self.mgf_digest,
self.label,
)
}
}
impl<D, MGD> fmt::Debug for Oaep<D, MGD> {
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].
///
/// The message must be no longer than the length of the public modulus minus
/// `2 + (2 * hash.size())`.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
fn encrypt<R, D, MGD>(
rng: &mut R,
pub_key: &RsaPublicKey,
msg: &[u8],
digest: &mut D,
mgf_digest: &mut MGD,
label: Option<Box<[u8]>>,
) -> Result<Vec<u8>>
where
R: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
MGD: Digest + FixedOutputReset,
{
key::check_public(pub_key)?;
let em = oaep_encrypt(rng, msg, digest, mgf_digest, label, pub_key.size())?;
let int = BoxedUint::from_be_slice(&em, pub_key.n_bits_precision())?;
uint_to_be_pad(rsa_encrypt(pub_key, &int)?, pub_key.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())`.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
fn encrypt_digest<R, D, MGD>(
rng: &mut R,
pub_key: &RsaPublicKey,
msg: &[u8],
label: Option<Box<[u8]>>,
) -> Result<Vec<u8>>
where
R: TryCryptoRng + ?Sized,
D: Digest,
MGD: Digest + FixedOutputReset,
{
key::check_public(pub_key)?;
let em = oaep_encrypt_digest::<_, D, MGD>(rng, msg, label, pub_key.size())?;
let int = BoxedUint::from_be_slice(&em, pub_key.n_bits_precision())?;
uint_to_be_pad(rsa_encrypt(pub_key, &int)?, pub_key.size())
}
/// Decrypts a plaintext using RSA and the padding scheme from [PKCS#1 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.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
fn decrypt<R, D, MGD>(
rng: Option<&mut R>,
priv_key: &RsaPrivateKey,
ciphertext: &[u8],
digest: &mut D,
mgf_digest: &mut MGD,
label: Option<Box<[u8]>>,
) -> Result<Vec<u8>>
where
R: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
MGD: Digest + FixedOutputReset,
{
if ciphertext.len() != priv_key.size() {
return Err(Error::Decryption);
}
let ciphertext = BoxedUint::from_be_slice(ciphertext, priv_key.n_bits_precision())?;
let em = rsa_decrypt_and_check(priv_key, rng, &ciphertext)?;
let mut em = uint_to_zeroizing_be_pad(em, priv_key.size())?;
oaep_decrypt(&mut em, digest, mgf_digest, label, priv_key.size())
}
/// Decrypts a plaintext using RSA and the padding scheme from [PKCS#1 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.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
fn decrypt_digest<R, D, MGD>(
rng: Option<&mut R>,
priv_key: &RsaPrivateKey,
ciphertext: &[u8],
label: Option<Box<[u8]>>,
) -> Result<Vec<u8>>
where
R: TryCryptoRng + ?Sized,
D: Digest,
MGD: Digest + FixedOutputReset,
{
key::check_public(priv_key)?;
if ciphertext.len() != priv_key.size() {
return Err(Error::Decryption);
}
let ciphertext = BoxedUint::from_be_slice(ciphertext, priv_key.n_bits_precision())?;
let em = rsa_decrypt_and_check(priv_key, rng, &ciphertext)?;
let mut em = uint_to_zeroizing_be_pad(em, priv_key.size())?;
oaep_decrypt_digest::<D, MGD>(&mut em, label, priv_key.size())
}
#[cfg(test)]
mod tests {
use crate::key::{RsaPrivateKey, RsaPublicKey};
use crate::oaep::{DecryptingKey, EncryptingKey, Oaep};
use crate::traits::PublicKeyParts;
use crate::traits::{Decryptor, RandomizedDecryptor, RandomizedEncryptor};
use crypto_bigint::BoxedUint;
use digest::{Digest, FixedOutputReset};
use rand::rngs::ChaCha8Rng;
use rand_core::{Rng, SeedableRng};
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(
BoxedUint::from_be_hex("d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b", 2048).unwrap(),
BoxedUint::from(65_537u64),
BoxedUint::from_be_hex("c4e70c689162c94c660828191b52b4d8392115df486a9adbe831e458d73958320dc1b755456e93701e9702d76fb0b92f90e01d1fe248153281fe79aa9763a92fae69d8d7ecd144de29fa135bd14f9573e349e45031e3b76982f583003826c552e89a397c1a06bd2163488630d92e8c2bb643d7abef700da95d685c941489a46f54b5316f62b5d2c3a7f1bbd134cb37353a44683fdc9d95d36458de22f6c44057fe74a0a436c4308f73f4da42f35c47ac16a7138d483afc91e41dc3a1127382e0c0f5119b0221b4fc639d6b9c38177a6de9b526ebd88c38d7982c07f98a0efd877d508aae275b946915c02e2e1106d175d74ec6777f5e80d12c053d9c7be1e341", 2048).unwrap(),
vec![
BoxedUint::from_be_hex("f827bbf3a41877c7cc59aebf42ed4b29c32defcb8ed96863d5b090a05a8930dd624a21c9dcf9838568fdfa0df65b8462a5f2ac913d6c56f975532bd8e78fb07bd405ca99a484bcf59f019bbddcb3933f2bce706300b4f7b110120c5df9018159067c35da3061a56c8635a52b54273b31271b4311f0795df6021e6355e1a42e61", 1024).unwrap(),
BoxedUint::from_be_hex("da4817ce0089dd36f2ade6a3ff410c73ec34bf1b4f6bda38431bfede11cef1f7f6efa70e5f8063a3b1f6e17296ffb15feefa0912a0325b8d1fd65a559e717b5b961ec345072e0ec5203d03441d29af4d64054a04507410cf1da78e7b6119d909ec66e6ad625bf995b279a4b3c5be7d895cd7c5b9c4c497fde730916fcdb4e41b", 1024).unwrap()
],
).unwrap()
}
#[test]
fn test_encrypt_decrypt_oaep() {
let priv_key = get_private_key();
do_test_encrypt_decrypt_oaep::<Sha1>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha224>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha256>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha384>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha512>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha3_256>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha3_384>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha3_512>(&priv_key);
do_test_oaep_with_different_hashes::<Sha1, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha224, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha256, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha384, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha512, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha3_256, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha3_384, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha3_512, Sha1>(&priv_key);
}
fn get_label(rng: &mut ChaCha8Rng) -> Option<Box<[u8]>> {
let mut buf = [0u8; 32];
rng.fill_bytes(&mut buf);
if rng.next_u32() % 2 == 0 {
Some(buf.into())
} else {
None
}
}
fn do_test_encrypt_decrypt_oaep<D: Digest + FixedOutputReset>(prk: &RsaPrivateKey) {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let k = prk.size();
for i in 1..8 {
let mut input = vec![0u8; i * 8];
rng.fill_bytes(&mut input);
if input.len() > k - 11 {
input = input[0..k - 11].to_vec();
}
let label = get_label(&mut rng);
let pub_key: RsaPublicKey = prk.into();
let ciphertext = if let Some(ref label) = label {
let padding = Oaep::<D>::new_with_label(label.clone());
pub_key.encrypt(&mut rng, padding, &input).unwrap()
} else {
let padding = Oaep::<D>::new();
pub_key.encrypt(&mut rng, padding, &input).unwrap()
};
assert_ne!(input, ciphertext);
let blind: bool = rng.next_u32() < (1 << 31);
let padding = if let Some(label) = label {
Oaep::<D>::new_with_label::<Box<[u8]>>(label)
} else {
Oaep::<D>::new()
};
let plaintext = if blind {
prk.decrypt(padding, &ciphertext).unwrap()
} else {
prk.decrypt_blinded(&mut rng, padding, &ciphertext).unwrap()
};
assert_eq!(input, plaintext);
}
}
fn do_test_oaep_with_different_hashes<
D: Digest + FixedOutputReset,
U: Digest + FixedOutputReset,
>(
prk: &RsaPrivateKey,
) {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let k = prk.size();
for i in 1..8 {
let mut input = vec![0u8; i * 8];
rng.fill_bytes(&mut input);
if input.len() > k - 11 {
input = input[0..k - 11].to_vec();
}
let label = get_label(&mut rng);
let pub_key: RsaPublicKey = prk.into();
let ciphertext = if let Some(ref label) = label {
let padding = Oaep::<D, U>::new_with_mgf_hash_and_label::<_>(label.clone());
pub_key.encrypt(&mut rng, padding, &input).unwrap()
} else {
let padding = Oaep::<D, U>::new_with_mgf_hash();
pub_key.encrypt(&mut rng, padding, &input).unwrap()
};
assert_ne!(input, ciphertext);
let blind: bool = rng.next_u32() < (1 << 31);
let padding = if let Some(label) = label {
Oaep::<D, U>::new_with_mgf_hash_and_label::<_>(label)
} else {
Oaep::<D, U>::new_with_mgf_hash()
};
let plaintext = if blind {
prk.decrypt(padding, &ciphertext).unwrap()
} else {
prk.decrypt_blinded(&mut rng, padding, &ciphertext).unwrap()
};
assert_eq!(input, plaintext);
}
}
#[test]
fn test_decrypt_oaep_invalid_hash() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = get_private_key();
let pub_key: RsaPublicKey = (&priv_key).into();
let ciphertext = pub_key
.encrypt(&mut rng, Oaep::<Sha1>::new(), "a_plain_text".as_bytes())
.unwrap();
assert!(
priv_key
.decrypt_blinded(
&mut rng,
Oaep::<Sha1>::new_with_label::<_>("label".as_bytes()),
&ciphertext,
)
.is_err(),
"decrypt should have failed on hash verification"
);
}
#[test]
fn test_encrypt_decrypt_oaep_traits() {
let priv_key = get_private_key();
do_test_encrypt_decrypt_oaep_traits::<Sha1>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha224>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha256>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha384>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha512>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha3_256>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha3_384>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha3_512>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha1, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha224, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha256, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha384, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha512, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha3_256, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha3_384, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha3_512, Sha1>(&priv_key);
}
fn do_test_encrypt_decrypt_oaep_traits<D: Digest + FixedOutputReset>(prk: &RsaPrivateKey) {
do_test_oaep_with_different_hashes_traits::<D, D>(prk);
}
fn do_test_oaep_with_different_hashes_traits<D: Digest, MGD: Digest + FixedOutputReset>(
prk: &RsaPrivateKey,
) {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let k = prk.size();
for i in 1..8 {
let mut input = vec![0u8; i * 8];
rng.fill_bytes(&mut input);
if input.len() > k - 11 {
input = input[0..k - 11].to_vec();
}
let label = get_label(&mut rng);
let pub_key: RsaPublicKey = prk.into();
let ciphertext = if let Some(ref label) = label {
let encrypting_key =
EncryptingKey::<D, MGD>::new_with_label(pub_key, label.clone());
encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap()
} else {
let encrypting_key = EncryptingKey::<D, MGD>::new(pub_key);
encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap()
};
assert_ne!(input, ciphertext);
let blind: bool = rng.next_u32() < (1 << 31);
let decrypting_key = if let Some(ref label) = label {
DecryptingKey::<D, MGD>::new_with_label(prk.clone(), label.clone())
} else {
DecryptingKey::<D, MGD>::new(prk.clone())
};
let plaintext = if blind {
decrypting_key.decrypt(&ciphertext).unwrap()
} else {
decrypting_key
.decrypt_with_rng(&mut rng, &ciphertext)
.unwrap()
};
assert_eq!(input, plaintext);
}
}
#[test]
fn test_decrypt_oaep_invalid_hash_traits() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = get_private_key();
let pub_key: RsaPublicKey = (&priv_key).into();
let encrypting_key = EncryptingKey::<Sha1>::new(pub_key);
let decrypting_key = DecryptingKey::<Sha1>::new_with_label(priv_key, "label".as_bytes());
let ciphertext = encrypting_key
.encrypt_with_rng(&mut rng, "a_plain_text".as_bytes())
.unwrap();
assert!(
decrypting_key
.decrypt_with_rng(&mut rng, &ciphertext)
.is_err(),
"decrypt should have failed on hash verification"
);
}
}
+139
View File
@@ -0,0 +1,139 @@
use super::decrypt_digest;
use crate::{
dummy_rng::DummyRng,
traits::{Decryptor, RandomizedDecryptor},
Result, RsaPrivateKey,
};
use alloc::{boxed::Box, vec::Vec};
use core::marker::PhantomData;
use digest::{Digest, FixedOutputReset};
use rand_core::CryptoRng;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use zeroize::ZeroizeOnDrop;
/// Decryption key for PKCS#1 v1.5 decryption as described in [RFC8017 § 7.1].
///
/// [RFC8017 § 7.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DecryptingKey<D, MGD = D>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
inner: RsaPrivateKey,
label: Option<Box<[u8]>>,
phantom: PhantomData<D>,
mg_phantom: PhantomData<MGD>,
}
impl<D, MGD> DecryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
/// Create a new verifying key from an RSA public key.
pub fn new(key: RsaPrivateKey) -> Self {
Self {
inner: key,
label: None,
phantom: Default::default(),
mg_phantom: Default::default(),
}
}
/// Create a new verifying key from an RSA public key using provided label
pub fn new_with_label<S: Into<Box<[u8]>>>(key: RsaPrivateKey, label: S) -> Self {
Self {
inner: key,
label: Some(label.into()),
phantom: Default::default(),
mg_phantom: Default::default(),
}
}
}
impl<D, MGD> Decryptor for DecryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
decrypt_digest::<DummyRng, D, MGD>(None, &self.inner, ciphertext, self.label.clone())
}
}
impl<D, MGD> RandomizedDecryptor for DecryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
fn decrypt_with_rng<R: CryptoRng + ?Sized>(
&self,
rng: &mut R,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
decrypt_digest::<_, D, MGD>(Some(rng), &self.inner, ciphertext, self.label.clone())
}
}
impl<D, MGD> ZeroizeOnDrop for DecryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
}
impl<D, MGD> PartialEq for DecryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.label == other.label
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
use sha2::Sha256;
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let decrypting_key = DecryptingKey::<Sha256>::new(
RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key"),
);
let tokens = [
Token::Struct {
name: "DecryptingKey",
len: 4,
},
Token::Str("inner"),
Token::Str(concat!(
"3056020100300d06092a864886f70d010101050004423040020100020900ab",
"240c3361d02e370203010001020811e54a15259d22f9020500ceff5cf30205",
"00d3a7aaad020500ccaddf17020500cb529d3d020500bb526d6f"
)),
Token::Str("label"),
Token::None,
Token::Str("phantom"),
Token::UnitStruct {
name: "PhantomData",
},
Token::Str("mg_phantom"),
Token::UnitStruct {
name: "PhantomData",
},
Token::StructEnd,
];
assert_tokens(&decrypting_key.readable(), &tokens);
}
}
+111
View File
@@ -0,0 +1,111 @@
use super::encrypt_digest;
use crate::{traits::RandomizedEncryptor, Result, RsaPublicKey};
use alloc::{boxed::Box, vec::Vec};
use core::marker::PhantomData;
use digest::{Digest, FixedOutputReset};
use rand_core::CryptoRng;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// Encryption key for PKCS#1 v1.5 encryption as described in [RFC8017 § 7.1].
///
/// [RFC8017 § 7.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct EncryptingKey<D, MGD = D>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
inner: RsaPublicKey,
label: Option<Box<[u8]>>,
phantom: PhantomData<D>,
mg_phantom: PhantomData<MGD>,
}
impl<D, MGD> EncryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
/// Create a new verifying key from an RSA public key.
pub fn new(key: RsaPublicKey) -> Self {
Self {
inner: key,
label: None,
phantom: Default::default(),
mg_phantom: Default::default(),
}
}
/// Create a new verifying key from an RSA public key using provided label
pub fn new_with_label<S: Into<Box<[u8]>>>(key: RsaPublicKey, label: S) -> Self {
Self {
inner: key,
label: Some(label.into()),
phantom: Default::default(),
mg_phantom: Default::default(),
}
}
}
impl<D, MGD> RandomizedEncryptor for EncryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
fn encrypt_with_rng<R: CryptoRng + ?Sized>(&self, rng: &mut R, msg: &[u8]) -> Result<Vec<u8>> {
encrypt_digest::<_, D, MGD>(rng, &self.inner, msg, self.label.clone())
}
}
impl<D, MGD> PartialEq for EncryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.label == other.label
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key =
crate::RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key");
let encrypting_key = EncryptingKey::<sha2::Sha256>::new(priv_key.to_public_key());
let tokens = [
Token::Struct {
name: "EncryptingKey",
len: 4,
},
Token::Str("inner"),
Token::Str(
"3024300d06092a864886f70d01010105000313003010020900ab240c3361d02e370203010001",
),
Token::Str("label"),
Token::None,
Token::Str("phantom"),
Token::UnitStruct {
name: "PhantomData",
},
Token::Str("mg_phantom"),
Token::UnitStruct {
name: "PhantomData",
},
Token::StructEnd,
];
assert_tokens(&encrypting_key.readable(), &tokens);
}
}
+675
View File
@@ -0,0 +1,675 @@
//! PKCS#1 v1.5 support as described in [RFC8017 § 8.2].
//!
//! <div class="warning">
//! <b>Warning</b>
//!
//! PKCS#1 v1.5 padding has a longstanding history of issues generally classed as
//! [Bleichenbacher Attacks] which were originally discovered in 1998 but keep reappearing in
//! various forms again and again over the course of decades, including most recently in the 2023
//! [Marvin Attack], which the `rsa` crate is [still vulnerable] to.
//!
//! These attacks can result in complete plaintext recovery for encryption, or signature forgery,
//! leading to a total failure of either confidentiality or integrity.
//!
//! Unless explicitly needed for compatibility reasons, we recommend against using PKCS#1 v1.5,
//! and suggest using [PSS][`super::pss`] or [OAEP][`super::oaep`] instead (if there is a
//! requirement to use RSA).
//! </div>
//!
//! [Bleichenbacher Attacks]: https://en.wikipedia.org/wiki/Adaptive_chosen-ciphertext_attack#Practical_attacks
//! [Marvin Attack]: https://people.redhat.com/~hkario/marvin/
//! [still vulnerable]: https://github.com/RustCrypto/RSA/issues/626
//!
//! # Usage
//!
//! See [code example in the toplevel rustdoc](../index.html#pkcs1-v15-signatures).
//!
//! [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2
mod decrypting_key;
mod encrypting_key;
mod signature;
mod signing_key;
mod verifying_key;
pub use self::{
decrypting_key::DecryptingKey, encrypting_key::EncryptingKey, signature::Signature,
signing_key::SigningKey, verifying_key::VerifyingKey,
};
use alloc::{boxed::Box, vec::Vec};
use const_oid::AssociatedOid;
use core::fmt::Debug;
use crypto_bigint::BoxedUint;
use digest::Digest;
use rand_core::TryCryptoRng;
use crate::algorithms::pad::{uint_to_be_pad, uint_to_zeroizing_be_pad};
use crate::algorithms::pkcs1v15::*;
use crate::algorithms::rsa::{rsa_decrypt_and_check, rsa_encrypt};
use crate::errors::{Error, Result};
use crate::key::{self, RsaPrivateKey, RsaPublicKey};
use crate::traits::{PaddingScheme, PublicKeyParts, SignatureScheme};
/// Encryption using PKCS#1 v1.5 padding.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Pkcs1v15Encrypt;
impl PaddingScheme for Pkcs1v15Encrypt {
fn decrypt<Rng: TryCryptoRng + ?Sized>(
self,
rng: Option<&mut Rng>,
priv_key: &RsaPrivateKey,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
decrypt(rng, priv_key, ciphertext)
}
fn encrypt<Rng: TryCryptoRng + ?Sized>(
self,
rng: &mut Rng,
pub_key: &RsaPublicKey,
msg: &[u8],
) -> Result<Vec<u8>> {
encrypt(rng, pub_key, msg)
}
}
/// `RSASSA-PKCS1-v1_5`: 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: pkcs1v15_generate_prefix::<D>().into_boxed_slice(),
}
}
/// Create new PKCS#1 v1.5 padding for computing an unprefixed signature.
///
/// This sets `hash_len` to `None` and uses an empty `prefix`.
pub fn new_unprefixed() -> Self {
Self {
hash_len: None,
prefix: Box::new([]),
}
}
}
impl SignatureScheme for Pkcs1v15Sign {
fn sign<Rng: TryCryptoRng + ?Sized>(
self,
rng: Option<&mut Rng>,
priv_key: &RsaPrivateKey,
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(self, pub_key: &RsaPublicKey, 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,
&BoxedUint::from_be_slice_vartime(sig),
)
}
}
/// 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]
fn encrypt<R: TryCryptoRng + ?Sized>(
rng: &mut R,
pub_key: &RsaPublicKey,
msg: &[u8],
) -> Result<Vec<u8>> {
key::check_public(pub_key)?;
let em = pkcs1v15_encrypt_pad(rng, msg, pub_key.size())?;
let int = BoxedUint::from_be_slice(&em, pub_key.n_bits_precision())?;
uint_to_be_pad(rsa_encrypt(pub_key, &int)?, pub_key.size())
}
/// Decrypts a plaintext using RSA and the padding scheme from PKCS#1 v1.5.
///
/// 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]
fn decrypt<R: TryCryptoRng + ?Sized>(
rng: Option<&mut R>,
priv_key: &RsaPrivateKey,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
key::check_public(priv_key)?;
let ciphertext = BoxedUint::from_be_slice(ciphertext, priv_key.n_bits_precision())?;
let em = rsa_decrypt_and_check(priv_key, rng, &ciphertext)?;
let em = uint_to_zeroizing_be_pad(em, priv_key.size())?;
pkcs1v15_encrypt_unpad(em, priv_key.size())
}
/// Calculates the signature of hashed using
/// RSASSA-PKCS1-V1_5-SIGN from RSA PKCS#1 v1.5. Note that `hashed` must
/// be the result of hashing the input message using the given hash
/// function. If hash is `None`, hashed is signed directly. This isn't
/// advisable except for interoperability.
///
/// If `rng` is not `None` then RSA blinding will be used to avoid timing
/// side-channel attacks.
///
/// This function is deterministic. Thus, if the set of possible
/// messages is small, an attacker may be able to build a map from
/// messages to signatures and identify the signed messages. As ever,
/// signatures provide authenticity, not confidentiality.
#[inline]
fn sign<R: TryCryptoRng + ?Sized>(
rng: Option<&mut R>,
priv_key: &RsaPrivateKey,
prefix: &[u8],
hashed: &[u8],
) -> Result<Vec<u8>> {
let em = pkcs1v15_sign_pad(prefix, hashed, priv_key.size())?;
let em = BoxedUint::from_be_slice(&em, priv_key.n_bits_precision())?;
uint_to_zeroizing_be_pad(rsa_decrypt_and_check(priv_key, rng, &em)?, priv_key.size())
}
/// Verifies an RSA PKCS#1 v1.5 signature.
#[inline]
fn verify(pub_key: &RsaPublicKey, prefix: &[u8], hashed: &[u8], sig: &BoxedUint) -> Result<()> {
let n = pub_key.n();
if sig >= n.as_ref() || sig.bits_precision() != pub_key.n_bits_precision() {
return Err(Error::Verification);
}
let em = uint_to_be_pad(rsa_encrypt(pub_key, sig)?, pub_key.size())?;
pkcs1v15_sign_unpad(prefix, hashed, &em, pub_key.size())
}
mod oid {
use const_oid::ObjectIdentifier;
/// A trait which associates an RSA-specific OID with a type.
pub trait RsaSignatureAssociatedOid {
/// The OID associated with this type.
const OID: ObjectIdentifier;
}
#[cfg(feature = "sha1")]
impl RsaSignatureAssociatedOid for sha1::Sha1 {
const OID: ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.5");
}
#[cfg(feature = "sha2")]
impl RsaSignatureAssociatedOid for sha2::Sha224 {
const OID: ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.14");
}
#[cfg(feature = "sha2")]
impl RsaSignatureAssociatedOid for sha2::Sha256 {
const OID: ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.11");
}
#[cfg(feature = "sha2")]
impl RsaSignatureAssociatedOid for sha2::Sha384 {
const OID: ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.12");
}
#[cfg(feature = "sha2")]
impl RsaSignatureAssociatedOid for sha2::Sha512 {
const OID: ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.13");
}
}
pub use oid::RsaSignatureAssociatedOid;
#[cfg(test)]
mod tests {
use super::*;
use ::signature::{
hazmat::{PrehashSigner, PrehashVerifier},
DigestSigner, DigestVerifier, Keypair, RandomizedDigestSigner, RandomizedSigner,
SignatureEncoding, Signer, Verifier,
};
use base64ct::{Base64, Encoding};
use hex_literal::hex;
use rand::rngs::ChaCha8Rng;
use rand_core::{Rng, SeedableRng};
use rstest::rstest;
use sha1::{Digest, Sha1};
use sha2::Sha256;
use sha3::Sha3_256;
use crate::traits::{
Decryptor, EncryptingKeypair, PublicKeyParts, RandomizedDecryptor, RandomizedEncryptor,
};
use crate::{RsaPrivateKey, RsaPublicKey};
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(
BoxedUint::from_be_hex("B2990F49C47DFA8CD400AE6A4D1B8A3B6A13642B23F28B003BFB97790ADE9A4CC82B8B2A81747DDEC08B6296E53A08C331687EF25C4BF4936BA1C0E6041E9D15", 512).unwrap(),
BoxedUint::from(65_537u64),
BoxedUint::from_be_hex("8ABD6A69F4D1A4B487F0AB8D7AAEFD38609405C999984E30F567E1E8AEEFF44E8B18BDB1EC78DFA31A55E32A48D7FB131F5AF1F44D7D6B2CED2A9DF5E5AE4535", 512).unwrap(),
vec![
BoxedUint::from_be_hex("DAB2F18048BAA68DE7DF04D2D35D5D80E60E2DFA42D50A9B04219032715E46B3", 256).unwrap(),
BoxedUint::from_be_hex("D10F2E66B1D0C13F10EF9927BF5324A379CA218146CBF9CAFC795221F16A3117", 256).unwrap()
],
).unwrap()
}
#[rstest]
#[case(
"gIcUIoVkD6ATMBk/u/nlCZCCWRKdkfjCgFdo35VpRXLduiKXhNz1XupLLzTXAybEq15juc+EgY5o0DHv/nt3yg==",
"x"
)]
#[case(
"Y7TOCSqofGhkRb+jaVRLzK8xw2cSo1IVES19utzv6hwvx+M8kFsoWQm5DzBeJCZTCVDPkTpavUuEbgp8hnUGDw==",
"testing."
)]
#[case(
"arReP9DJtEVyV2Dg3dDp4c/PSk1O6lxkoJ8HcFupoRorBZG+7+1fDAwT1olNddFnQMjmkb8vxwmNMoTAT/BFjQ==",
"testing.\n"
)]
#[case(
"WtaBXIoGC54+vH0NH0CHHE+dRDOsMc/6BrfFu2lEqcKL9+uDuWaf+Xj9mrbQCjjZcpQuX733zyok/jsnqe/Ftw==",
"01234567890123456789012345678901234567890123456789012"
)]
fn test_decrypt_pkcs1v15(#[case] ciphertext: &str, #[case] plaintext: &str) {
let priv_key = get_private_key();
let out = priv_key
.decrypt(Pkcs1v15Encrypt, &Base64::decode_vec(ciphertext).unwrap())
.unwrap();
assert_eq!(out, plaintext.as_bytes());
}
#[test]
fn test_encrypt_decrypt_pkcs1v15() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = get_private_key();
let k = priv_key.size();
for i in 1..100 {
let mut input = vec![0u8; i * 8];
rng.fill_bytes(&mut input);
if input.len() > k - 11 {
input = input[0..k - 11].to_vec();
}
let pub_key: RsaPublicKey = priv_key.clone().into();
let ciphertext = encrypt(&mut rng, &pub_key, &input).unwrap();
assert_ne!(input, ciphertext);
let blind: bool = rng.next_u32() < (1u32 << 31);
let blinder = if blind { Some(&mut rng) } else { None };
let plaintext = decrypt(blinder, &priv_key, &ciphertext).unwrap();
assert_eq!(input, plaintext);
}
}
#[rstest]
#[case(
"gIcUIoVkD6ATMBk/u/nlCZCCWRKdkfjCgFdo35VpRXLduiKXhNz1XupLLzTXAybEq15juc+EgY5o0DHv/nt3yg==",
"x"
)]
#[case(
"Y7TOCSqofGhkRb+jaVRLzK8xw2cSo1IVES19utzv6hwvx+M8kFsoWQm5DzBeJCZTCVDPkTpavUuEbgp8hnUGDw==",
"testing."
)]
#[case(
"arReP9DJtEVyV2Dg3dDp4c/PSk1O6lxkoJ8HcFupoRorBZG+7+1fDAwT1olNddFnQMjmkb8vxwmNMoTAT/BFjQ==",
"testing.\n"
)]
#[case(
"WtaBXIoGC54+vH0NH0CHHE+dRDOsMc/6BrfFu2lEqcKL9+uDuWaf+Xj9mrbQCjjZcpQuX733zyok/jsnqe/Ftw==",
"01234567890123456789012345678901234567890123456789012"
)]
fn test_decrypt_pkcs1v15_traits(#[case] ciphertext: &str, #[case] plaintext: &str) {
let priv_key = get_private_key();
let decrypting_key = DecryptingKey::new(priv_key);
let out = decrypting_key
.decrypt(&Base64::decode_vec(ciphertext).unwrap())
.unwrap();
assert_eq!(out, plaintext.as_bytes());
}
#[test]
fn test_encrypt_decrypt_pkcs1v15_traits() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = get_private_key();
let k = priv_key.size();
let decrypting_key = DecryptingKey::new(priv_key);
for i in 1..100 {
let mut input = vec![0u8; i * 8];
rng.fill_bytes(&mut input);
if input.len() > k - 11 {
input = input[0..k - 11].to_vec();
}
let encrypting_key = decrypting_key.encrypting_key();
let ciphertext = encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap();
assert_ne!(input, ciphertext);
let blind: bool = rng.next_u32() < (1u32 << 31);
let plaintext = if blind {
decrypting_key
.decrypt_with_rng(&mut rng, &ciphertext)
.unwrap()
} else {
decrypting_key.decrypt(&ciphertext).unwrap()
};
assert_eq!(input, plaintext);
}
}
#[rstest]
#[case("Test.\n", hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"))
]
fn test_sign_pkcs1v15(#[case] text: &str, #[case] expected: [u8; 64]) {
let priv_key = get_private_key();
let digest = Sha1::digest(text.as_bytes()).to_vec();
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_with_rng(&mut rng, Pkcs1v15Sign::new::<Sha1>(), &digest)
.unwrap();
assert_eq!(out2, expected);
}
#[rstest]
#[case("Test.\n", hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"))
]
fn test_sign_pkcs1v15_signer(#[case] text: &str, #[case] expected: [u8; 64]) {
let priv_key = get_private_key();
let signing_key = SigningKey::<Sha1>::new(priv_key);
let out = signing_key.sign(text.as_bytes()).to_bytes();
assert_ne!(out.as_ref(), text.as_bytes());
assert_ne!(out.as_ref(), &Sha1::digest(text.as_bytes()).to_vec());
assert_eq!(out.as_ref(), expected);
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let out2 = signing_key
.sign_with_rng(&mut rng, text.as_bytes())
.to_bytes();
assert_eq!(out2.as_ref(), expected);
}
#[rstest]
#[case("Test.\n", hex!(
"2ffae3f3e130287b3a1dcb320e46f52e8f3f7969b646932273a7e3a6f2a182ea"
"02d42875a7ffa4a148aa311f9e4b562e4e13a2223fb15f4e5bf5f2b206d9451b"))
]
fn test_sign_pkcs1v15_signer_sha2_256(#[case] text: &str, #[case] expected: [u8; 64]) {
let priv_key = get_private_key();
let signing_key = SigningKey::<Sha256>::new(priv_key);
let out = signing_key.sign(text.as_bytes()).to_bytes();
assert_ne!(out.as_ref(), text.as_bytes());
assert_eq!(out.as_ref(), expected);
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let out2 = signing_key
.sign_with_rng(&mut rng, text.as_bytes())
.to_bytes();
assert_eq!(out2.as_ref(), expected);
}
#[rstest]
#[case("Test.\n", hex!(
"55e9fba3354dfb51d2c8111794ea552c86afc2cab154652c03324df8c2c51ba7"
"2ff7c14de59a6f9ba50d90c13a7537cc3011948369f1f0ec4a49d21eb7e723f9"))
]
fn test_sign_pkcs1v15_signer_sha3_256(#[case] text: &str, #[case] expected: [u8; 64]) {
let priv_key = get_private_key();
let signing_key = SigningKey::<Sha3_256>::new(priv_key);
let out = signing_key.sign(text.as_bytes()).to_bytes();
assert_ne!(out.as_ref(), text.as_bytes());
assert_eq!(out.as_ref(), expected);
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let out2 = signing_key
.sign_with_rng(&mut rng, text.as_bytes())
.to_bytes();
assert_eq!(out2.as_ref(), expected);
}
#[rstest]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"
)
)]
fn test_sign_pkcs1v15_digest_signer(#[case] text: &str, #[case] expected: [u8; 64]) {
let priv_key = get_private_key();
let signing_key = SigningKey::new(priv_key);
let mut digest = Sha1::new();
digest.update(text.as_bytes());
let out = signing_key
.sign_digest(|digest: &mut Sha1| digest.update(text.as_bytes()))
.to_bytes();
assert_ne!(out.as_ref(), text.as_bytes());
assert_ne!(out.as_ref(), &Sha1::digest(text.as_bytes()).to_vec());
assert_eq!(out.as_ref(), expected);
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let out2 = signing_key
.sign_digest_with_rng(&mut rng, |digest: &mut Sha1| digest.update(text.as_bytes()))
.to_bytes();
assert_eq!(out2.as_ref(), expected);
}
#[rstest]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"
),
true
)]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362af"
),
false
)]
fn test_verify_pkcs1v15(#[case] text: &str, #[case] sig: [u8; 64], #[case] expected: bool) {
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let digest = Sha1::digest(text.as_bytes()).to_vec();
let result = pub_key.verify(Pkcs1v15Sign::new::<Sha1>(), &digest, &sig);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[rstest]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"
),
true
)]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362af"
),
false
)]
fn test_verify_pkcs1v15_signer(
#[case] text: &str,
#[case] sig: [u8; 64],
#[case] expected: bool,
) {
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let verifying_key = VerifyingKey::<Sha1>::new(pub_key);
let result = verifying_key.verify(
text.as_bytes(),
&Signature::try_from(sig.as_slice()).unwrap(),
);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[rstest]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"
),
true
)]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362af"
),
false
)]
fn test_verify_pkcs1v15_digest_signer(
#[case] text: &str,
#[case] sig: [u8; 64],
#[case] expected: bool,
) {
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let verifying_key = VerifyingKey::new(pub_key);
let result = verifying_key.verify_digest(
|digest: &mut Sha1| {
digest.update(text.as_bytes());
Ok(())
},
&Signature::try_from(sig.as_slice()).unwrap(),
);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[test]
fn test_unpadded_signature() {
let msg = b"Thu Dec 19 18:06:16 EST 2013\n";
let expected_sig = Base64::decode_vec("pX4DR8azytjdQ1rtUiC040FjkepuQut5q2ZFX1pTjBrOVKNjgsCDyiJDGZTCNoh9qpXYbhl7iEym30BWWwuiZg==").unwrap();
let priv_key = get_private_key();
let sig = priv_key.sign(Pkcs1v15Sign::new_unprefixed(), msg).unwrap();
assert_eq!(expected_sig, sig);
let pub_key: RsaPublicKey = priv_key.into();
pub_key
.verify(Pkcs1v15Sign::new_unprefixed(), msg, &sig)
.expect("failed to verify");
}
#[test]
fn test_unpadded_signature_hazmat() {
let msg = b"Thu Dec 19 18:06:16 EST 2013\n";
let expected_sig = Base64::decode_vec("pX4DR8azytjdQ1rtUiC040FjkepuQut5q2ZFX1pTjBrOVKNjgsCDyiJDGZTCNoh9qpXYbhl7iEym30BWWwuiZg==").unwrap();
let priv_key = get_private_key();
let signing_key = SigningKey::<Sha1>::new_unprefixed(priv_key);
let sig = signing_key
.sign_prehash(msg)
.expect("Failure during sign")
.to_bytes();
assert_eq!(sig.as_ref(), expected_sig);
let verifying_key = signing_key.verifying_key();
verifying_key
.verify_prehash(msg, &Signature::try_from(expected_sig.as_slice()).unwrap())
.expect("failed to verify");
}
}
+86
View File
@@ -0,0 +1,86 @@
use super::{decrypt, EncryptingKey};
use crate::{
dummy_rng::DummyRng,
traits::{Decryptor, EncryptingKeypair, RandomizedDecryptor},
Result, RsaPrivateKey,
};
use alloc::vec::Vec;
use rand_core::CryptoRng;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use zeroize::ZeroizeOnDrop;
/// Decryption key for PKCS#1 v1.5 decryption as described in [RFC8017 § 7.2].
///
/// [RFC8017 § 7.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.2
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DecryptingKey {
inner: RsaPrivateKey,
}
impl DecryptingKey {
/// Create a new verifying key from an RSA public key.
pub fn new(key: RsaPrivateKey) -> Self {
Self { inner: key }
}
}
impl Decryptor for DecryptingKey {
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
decrypt::<DummyRng>(None, &self.inner, ciphertext)
}
}
impl RandomizedDecryptor for DecryptingKey {
fn decrypt_with_rng<R: CryptoRng + ?Sized>(
&self,
rng: &mut R,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
decrypt(Some(rng), &self.inner, ciphertext)
}
}
impl EncryptingKeypair for DecryptingKey {
type EncryptingKey = EncryptingKey;
fn encrypting_key(&self) -> EncryptingKey {
EncryptingKey {
inner: self.inner.clone().into(),
}
}
}
impl ZeroizeOnDrop for DecryptingKey {}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let decrypting_key = DecryptingKey::new(
RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key"),
);
let tokens = [
Token::Struct {
name: "DecryptingKey",
len: 1,
},
Token::Str("inner"),
Token::Str(concat!(
"3056020100300d06092a864886f70d010101050004423040020100020900ab",
"240c3361d02e370203010001020811e54a15259d22f9020500ceff5cf30205",
"00d3a7aaad020500ccaddf17020500cb529d3d020500bb526d6f"
)),
Token::StructEnd,
];
assert_tokens(&decrypting_key.readable(), &tokens);
}
}
+58
View File
@@ -0,0 +1,58 @@
use super::encrypt;
use crate::{traits::RandomizedEncryptor, Result, RsaPublicKey};
use alloc::vec::Vec;
use rand_core::CryptoRng;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// Encryption key for PKCS#1 v1.5 encryption as described in [RFC8017 § 7.2].
///
/// [RFC8017 § 7.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.2
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct EncryptingKey {
pub(super) inner: RsaPublicKey,
}
impl EncryptingKey {
/// Create a new verifying key from an RSA public key.
pub fn new(key: RsaPublicKey) -> Self {
Self { inner: key }
}
}
impl RandomizedEncryptor for EncryptingKey {
fn encrypt_with_rng<R: CryptoRng + ?Sized>(&self, rng: &mut R, msg: &[u8]) -> Result<Vec<u8>> {
encrypt(rng, &self.inner, msg)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use crate::RsaPrivateKey;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key");
let encrypting_key = EncryptingKey::new(priv_key.to_public_key());
let tokens = [
Token::Struct {
name: "EncryptingKey",
len: 1,
},
Token::Str("inner"),
Token::Str(
"3024300d06092a864886f70d01010105000313003010020900ab240c3361d02e370203010001",
),
Token::StructEnd,
];
assert_tokens(&encrypting_key.clone().readable(), &tokens);
}
}
+112
View File
@@ -0,0 +1,112 @@
//! `RSASSA-PKCS1-v1_5` signatures.
use alloc::boxed::Box;
use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex};
use crypto_bigint::BoxedUint;
use signature::SignatureEncoding;
#[cfg(feature = "serde")]
use serdect::serde::{de, Deserialize, Serialize};
#[cfg(feature = "encoding")]
use spki::{
der::{asn1::BitString, Result as DerResult},
SignatureBitStringEncoding,
};
/// `RSASSA-PKCS1-v1_5` signatures as described in [RFC8017 § 8.2].
///
/// [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature {
pub(super) inner: BoxedUint,
}
impl SignatureEncoding for Signature {
type Repr = Box<[u8]>;
}
#[cfg(feature = "encoding")]
impl SignatureBitStringEncoding for Signature {
fn to_bitstring(&self) -> DerResult<BitString> {
BitString::new(0, self.to_vec())
}
}
impl TryFrom<&[u8]> for Signature {
type Error = signature::Error;
fn try_from(bytes: &[u8]) -> signature::Result<Self> {
// TODO(tarcieri): max length restriction? (#350)
let inner = BoxedUint::from_be_slice_vartime(bytes);
Ok(Self { inner })
}
}
impl From<Signature> for Box<[u8]> {
fn from(signature: Signature) -> Box<[u8]> {
signature.inner.to_be_bytes()
}
}
impl LowerHex for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
for byte in self.to_bytes().iter() {
write!(f, "{:02x}", byte)?;
}
Ok(())
}
}
impl UpperHex for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
for byte in self.to_bytes().iter() {
write!(f, "{:02X}", byte)?;
}
Ok(())
}
}
impl Display for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{:X}", self)
}
}
#[cfg(feature = "serde")]
impl Serialize for Signature {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serdect::serde::Serializer,
{
serdect::slice::serialize_hex_lower_or_bin(&self.to_bytes(), serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Signature {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: serdect::serde::Deserializer<'de>,
{
serdect::slice::deserialize_hex_or_bin_vec(deserializer)?
.as_slice()
.try_into()
.map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "serde")]
fn test_serde() {
use super::*;
use serde_test::{assert_tokens, Configure, Token};
let signature = Signature {
inner: BoxedUint::from(42u32),
};
let tokens = [Token::Str("000000000000002a")];
assert_tokens(&signature.readable(), &tokens);
}
}
+358
View File
@@ -0,0 +1,358 @@
use super::{pkcs1v15_generate_prefix, sign, Signature, VerifyingKey};
use crate::{dummy_rng::DummyRng, Result, RsaPrivateKey};
use alloc::vec::Vec;
use const_oid::AssociatedOid;
use core::marker::PhantomData;
use digest::{Digest, FixedOutput, HashMarker, Update};
use rand_core::{CryptoRng, TryCryptoRng};
use signature::{
hazmat::PrehashSigner, DigestSigner, Keypair, MultipartSigner, RandomizedDigestSigner,
RandomizedMultipartSigner, RandomizedSigner, Signer,
};
use zeroize::ZeroizeOnDrop;
#[cfg(feature = "encoding")]
use {
super::oid,
pkcs8::{EncodePrivateKey, SecretDocument},
spki::{
der::AnyRef, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier,
SignatureAlgorithmIdentifier,
},
};
#[cfg(feature = "serde")]
use {
pkcs8::DecodePrivateKey,
serdect::serde::{de, ser, Deserialize, Serialize},
};
/// Signing key for `RSASSA-PKCS1-v1_5` signatures as described in [RFC8017 § 8.2].
///
/// [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2
#[derive(Debug, Clone)]
pub struct SigningKey<D>
where
D: Digest,
{
inner: RsaPrivateKey,
prefix: Vec<u8>,
phantom: PhantomData<D>,
}
impl<D> SigningKey<D>
where
D: Digest + AssociatedOid,
{
/// Create a new signing key with a prefix for the digest `D`.
pub fn new(key: RsaPrivateKey) -> Self {
Self {
inner: key,
prefix: pkcs1v15_generate_prefix::<D>(),
phantom: Default::default(),
}
}
/// Generate a new signing key with a prefix for the digest `D`.
pub fn random<R: CryptoRng + ?Sized>(rng: &mut R, bit_size: usize) -> Result<Self> {
Ok(Self {
inner: RsaPrivateKey::new(rng, bit_size)?,
prefix: pkcs1v15_generate_prefix::<D>(),
phantom: Default::default(),
})
}
}
impl<D> SigningKey<D>
where
D: Digest,
{
/// Create a new signing key from the give RSA private key with an empty prefix.
///
/// ## Note: unprefixed signatures are uncommon
///
/// In most cases you'll want to use [`SigningKey::new`].
pub fn new_unprefixed(key: RsaPrivateKey) -> Self {
Self {
inner: key,
prefix: Vec::new(),
phantom: Default::default(),
}
}
/// Generate a new signing key with an empty prefix.
pub fn random_unprefixed<R: CryptoRng + ?Sized>(rng: &mut R, bit_size: usize) -> Result<Self> {
Ok(Self {
inner: RsaPrivateKey::new(rng, bit_size)?,
prefix: Vec::new(),
phantom: Default::default(),
})
}
}
//
// `*Signer` trait impls
//
impl<D> DigestSigner<D, Signature> for SigningKey<D>
where
D: Default + FixedOutput + HashMarker + Update,
{
fn try_sign_digest<F: Fn(&mut D) -> signature::Result<()>>(
&self,
f: F,
) -> signature::Result<Signature> {
let mut digest = D::default();
f(&mut digest)?;
sign::<DummyRng>(None, &self.inner, &self.prefix, &digest.finalize_fixed())?
.as_slice()
.try_into()
}
}
impl<D> PrehashSigner<Signature> for SigningKey<D>
where
D: Digest,
{
fn sign_prehash(&self, prehash: &[u8]) -> signature::Result<Signature> {
sign::<DummyRng>(None, &self.inner, &self.prefix, prehash)?
.as_slice()
.try_into()
}
}
impl<D> RandomizedDigestSigner<D, Signature> for SigningKey<D>
where
D: Default + FixedOutput + HashMarker + Update,
{
fn try_sign_digest_with_rng<
R: TryCryptoRng + ?Sized,
F: Fn(&mut D) -> signature::Result<()>,
>(
&self,
rng: &mut R,
f: F,
) -> signature::Result<Signature> {
let mut digest = D::default();
f(&mut digest)?;
sign(
Some(rng),
&self.inner,
&self.prefix,
&digest.finalize_fixed(),
)?
.as_slice()
.try_into()
}
}
impl<D> RandomizedSigner<Signature> for SigningKey<D>
where
D: Digest,
{
fn try_sign_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
msg: &[u8],
) -> signature::Result<Signature> {
self.try_multipart_sign_with_rng(rng, &[msg])
}
}
impl<D> RandomizedMultipartSigner<Signature> for SigningKey<D>
where
D: Digest,
{
fn try_multipart_sign_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
msg: &[&[u8]],
) -> signature::Result<Signature> {
let mut digest = D::new();
msg.iter().for_each(|slice| digest.update(slice));
sign(Some(rng), &self.inner, &self.prefix, &digest.finalize())?
.as_slice()
.try_into()
}
}
impl<D> Signer<Signature> for SigningKey<D>
where
D: Digest,
{
fn try_sign(&self, msg: &[u8]) -> signature::Result<Signature> {
self.try_multipart_sign(&[msg])
}
}
impl<D> MultipartSigner<Signature> for SigningKey<D>
where
D: Digest,
{
fn try_multipart_sign(&self, msg: &[&[u8]]) -> signature::Result<Signature> {
let mut digest = D::new();
msg.iter().for_each(|slice| digest.update(slice));
sign::<DummyRng>(None, &self.inner, &self.prefix, &digest.finalize())?
.as_slice()
.try_into()
}
}
//
// Other trait impls
//
impl<D> AsRef<RsaPrivateKey> for SigningKey<D>
where
D: Digest,
{
fn as_ref(&self) -> &RsaPrivateKey {
&self.inner
}
}
#[cfg(feature = "encoding")]
impl<D> AssociatedAlgorithmIdentifier for SigningKey<D>
where
D: Digest,
{
type Params = AnyRef<'static>;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
}
#[cfg(feature = "encoding")]
impl<D> EncodePrivateKey for SigningKey<D>
where
D: Digest,
{
fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
self.inner.to_pkcs8_der()
}
}
impl<D> From<RsaPrivateKey> for SigningKey<D>
where
D: Digest + AssociatedOid,
{
fn from(key: RsaPrivateKey) -> Self {
Self::new(key)
}
}
impl<D> From<SigningKey<D>> for RsaPrivateKey
where
D: Digest,
{
fn from(key: SigningKey<D>) -> Self {
key.inner
}
}
impl<D> Keypair for SigningKey<D>
where
D: Digest,
{
type VerifyingKey = VerifyingKey<D>;
fn verifying_key(&self) -> Self::VerifyingKey {
VerifyingKey {
inner: self.inner.to_public_key(),
prefix: self.prefix.clone(),
phantom: Default::default(),
}
}
}
#[cfg(feature = "encoding")]
impl<D> SignatureAlgorithmIdentifier for SigningKey<D>
where
D: Digest + oid::RsaSignatureAssociatedOid,
{
type Params = AnyRef<'static>;
const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> =
AlgorithmIdentifierRef {
oid: D::OID,
parameters: Some(AnyRef::NULL),
};
}
#[cfg(feature = "encoding")]
impl<D> TryFrom<pkcs8::PrivateKeyInfoRef<'_>> for SigningKey<D>
where
D: Digest + AssociatedOid,
{
type Error = pkcs8::Error;
fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
private_key_info
.algorithm
.assert_algorithm_oid(pkcs1::ALGORITHM_OID)?;
RsaPrivateKey::try_from(private_key_info).map(Self::new)
}
}
impl<D> ZeroizeOnDrop for SigningKey<D> where D: Digest {}
impl<D> PartialEq for SigningKey<D>
where
D: Digest,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.prefix == other.prefix
}
}
#[cfg(feature = "serde")]
impl<D> Serialize for SigningKey<D>
where
D: Digest,
{
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serdect::serde::Serializer,
{
let der = self.to_pkcs8_der().map_err(ser::Error::custom)?;
serdect::slice::serialize_hex_lower_or_bin(&der.as_bytes(), serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, D> Deserialize<'de> for SigningKey<D>
where
D: Digest + AssociatedOid,
{
fn deserialize<De>(deserializer: De) -> core::result::Result<Self, De::Error>
where
De: serdect::serde::Deserializer<'de>,
{
let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::from_pkcs8_der(&der_bytes).map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use crate::RsaPrivateKey;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
use sha2::Sha256;
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key");
let signing_key = SigningKey::<Sha256>::new(priv_key);
let tokens = [Token::Str(concat!(
"3056020100300d06092a864886f70d010101050004423040020100020900ab240c",
"3361d02e370203010001020811e54a15259d22f9020500ceff5cf3020500d3a7aa",
"ad020500ccaddf17020500cb529d3d020500bb526d6f",
))];
assert_tokens(&signing_key.readable(), &tokens);
}
}
+270
View File
@@ -0,0 +1,270 @@
use super::{pkcs1v15_generate_prefix, verify, Signature};
use crate::RsaPublicKey;
use alloc::vec::Vec;
use const_oid::AssociatedOid;
use core::marker::PhantomData;
use digest::{Digest, FixedOutput, HashMarker, Update};
use signature::{hazmat::PrehashVerifier, DigestVerifier, Verifier};
#[cfg(feature = "encoding")]
use {
super::oid,
spki::{
der::AnyRef, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier, Document,
EncodePublicKey, SignatureAlgorithmIdentifier,
},
};
#[cfg(feature = "serde")]
use {
serdect::serde::{de, ser, Deserialize, Serialize},
spki::DecodePublicKey,
};
/// Verifying key for `RSASSA-PKCS1-v1_5` signatures as described in [RFC8017 § 8.2].
///
/// [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2
#[derive(Debug)]
pub struct VerifyingKey<D>
where
D: Digest,
{
pub(super) inner: RsaPublicKey,
pub(super) prefix: Vec<u8>,
pub(super) phantom: PhantomData<D>,
}
impl<D> VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
/// Create a new verifying key with a prefix for the digest `D`.
pub fn new(key: RsaPublicKey) -> Self {
Self {
inner: key,
prefix: pkcs1v15_generate_prefix::<D>(),
phantom: Default::default(),
}
}
}
impl<D> VerifyingKey<D>
where
D: Digest,
{
/// Create a new verifying key from an RSA public key with an empty prefix.
///
/// ## Note: unprefixed signatures are uncommon
///
/// In most cases you'll want to use [`VerifyingKey::new`] instead.
pub fn new_unprefixed(key: RsaPublicKey) -> Self {
Self {
inner: key,
prefix: Vec::new(),
phantom: Default::default(),
}
}
}
//
// `*Verifier` trait impls
//
impl<D> DigestVerifier<D, Signature> for VerifyingKey<D>
where
D: Default + FixedOutput + HashMarker + Update,
{
fn verify_digest<F: Fn(&mut D) -> signature::Result<()>>(
&self,
f: F,
signature: &Signature,
) -> signature::Result<()> {
let mut digest = D::default();
f(&mut digest)?;
verify(
&self.inner,
&self.prefix,
&digest.finalize_fixed(),
&signature.inner,
)
.map_err(|e| e.into())
}
}
impl<D> PrehashVerifier<Signature> for VerifyingKey<D>
where
D: Digest,
{
fn verify_prehash(&self, prehash: &[u8], signature: &Signature) -> signature::Result<()> {
verify(&self.inner, &self.prefix, prehash, &signature.inner).map_err(|e| e.into())
}
}
impl<D> Verifier<Signature> for VerifyingKey<D>
where
D: Digest,
{
fn verify(&self, msg: &[u8], signature: &Signature) -> signature::Result<()> {
verify(
&self.inner,
&self.prefix.clone(),
&D::digest(msg),
&signature.inner,
)
.map_err(|e| e.into())
}
}
//
// Other trait impls
//
impl<D> AsRef<RsaPublicKey> for VerifyingKey<D>
where
D: Digest,
{
fn as_ref(&self) -> &RsaPublicKey {
&self.inner
}
}
#[cfg(feature = "encoding")]
impl<D> AssociatedAlgorithmIdentifier for VerifyingKey<D>
where
D: Digest,
{
type Params = AnyRef<'static>;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
}
// Implemented manually so we don't have to bind D with Clone
impl<D> Clone for VerifyingKey<D>
where
D: Digest,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
prefix: self.prefix.clone(),
phantom: Default::default(),
}
}
}
#[cfg(feature = "encoding")]
impl<D> EncodePublicKey for VerifyingKey<D>
where
D: Digest,
{
fn to_public_key_der(&self) -> spki::Result<Document> {
self.inner.to_public_key_der()
}
}
impl<D> From<RsaPublicKey> for VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
fn from(key: RsaPublicKey) -> Self {
Self::new(key)
}
}
impl<D> From<VerifyingKey<D>> for RsaPublicKey
where
D: Digest,
{
fn from(key: VerifyingKey<D>) -> Self {
key.inner
}
}
#[cfg(feature = "encoding")]
impl<D> SignatureAlgorithmIdentifier for VerifyingKey<D>
where
D: Digest + oid::RsaSignatureAssociatedOid,
{
type Params = AnyRef<'static>;
const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> =
AlgorithmIdentifierRef {
oid: D::OID,
parameters: Some(AnyRef::NULL),
};
}
#[cfg(feature = "encoding")]
impl<D> TryFrom<pkcs8::SubjectPublicKeyInfoRef<'_>> for VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
type Error = spki::Error;
fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
spki.algorithm.assert_algorithm_oid(pkcs1::ALGORITHM_OID)?;
RsaPublicKey::try_from(spki).map(Self::new)
}
}
impl<D> PartialEq for VerifyingKey<D>
where
D: Digest,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.prefix == other.prefix
}
}
#[cfg(feature = "serde")]
impl<D> Serialize for VerifyingKey<D>
where
D: Digest,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let der = self.to_public_key_der().map_err(ser::Error::custom)?;
serdect::slice::serialize_hex_lower_or_bin(&der, serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, D> Deserialize<'de> for VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
fn deserialize<De>(deserializer: De) -> Result<Self, De::Error>
where
De: serde::Deserializer<'de>,
{
let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::from_public_key_der(&der_bytes).map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use crate::RsaPrivateKey;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
use sha2::Sha256;
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key");
let pub_key = priv_key.to_public_key();
let verifying_key = VerifyingKey::<Sha256>::new(pub_key);
let tokens = [Token::Str(
"3024300d06092a864886f70d01010105000313003010020900ab240c3361d02e370203010001",
)];
assert_tokens(&verifying_key.readable(), &tokens);
}
}
+675
View File
@@ -0,0 +1,675 @@
//! Support for the [Probabilistic Signature Scheme] (PSS) a.k.a. RSASSA-PSS.
//!
//! Designed by Mihir Bellare and Phillip Rogaway. Specified in [RFC8017 § 8.1].
//!
//! # Usage
//!
//! See [code example in the toplevel rustdoc](../index.html#pss-signatures).
//!
//! [Probabilistic Signature Scheme]: https://en.wikipedia.org/wiki/Probabilistic_signature_scheme
//! [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1
mod blinded_signing_key;
mod signature;
mod signing_key;
mod verifying_key;
pub use self::{
blinded_signing_key::BlindedSigningKey, signature::Signature, signing_key::SigningKey,
verifying_key::VerifyingKey,
};
use alloc::vec::Vec;
use core::fmt::{self, Debug};
use crypto_bigint::BoxedUint;
use digest::{Digest, FixedOutputReset};
use rand_core::TryCryptoRng;
use crate::algorithms::pad::{uint_to_be_pad, uint_to_zeroizing_be_pad};
use crate::algorithms::pss::*;
use crate::algorithms::rsa::{rsa_decrypt_and_check, rsa_encrypt};
use crate::errors::{Error, Result};
use crate::traits::PublicKeyParts;
use crate::traits::SignatureScheme;
use crate::{RsaPrivateKey, RsaPublicKey};
#[cfg(feature = "encoding")]
use {
crate::encoding::ID_RSASSA_PSS,
const_oid::AssociatedOid,
pkcs1::RsaPssParams,
spki::{der::Any, AlgorithmIdentifierOwned},
};
/// Digital signatures using PSS padding.
pub struct Pss<D> {
/// Create blinded signatures.
pub blinded: bool,
/// Digest type to use.
pub digest: D,
/// Salt length.
/// Required for signing, optional for verifying.
pub salt_len: Option<usize>,
}
impl<D> Default for Pss<D>
where
D: Digest,
{
fn default() -> Self {
Self::new()
}
}
impl<D> Pss<D>
where
D: Digest,
{
/// New PSS padding for the given digest.
/// Digest output size is used as a salt length.
pub fn new() -> Self {
Self::new_with_salt(<D as Digest>::output_size())
}
/// 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: D::new(),
salt_len: Some(len),
}
}
/// New PSS padding for blinded signatures (RSA-BSSA) for the given digest.
/// Digest output size is used as a salt length.
pub fn new_blinded() -> Self {
Self::new_blinded_with_salt(<D as Digest>::output_size())
}
/// 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(len: usize) -> Self {
Self {
blinded: true,
digest: D::new(),
salt_len: Some(len),
}
}
}
impl<D> SignatureScheme for Pss<D>
where
D: Digest + FixedOutputReset,
{
fn sign<Rng: TryCryptoRng + ?Sized>(
mut self,
rng: Option<&mut Rng>,
priv_key: &RsaPrivateKey,
hashed: &[u8],
) -> Result<Vec<u8>> {
sign(
rng.ok_or(Error::InvalidPaddingScheme)?,
self.blinded,
priv_key,
hashed,
self.salt_len.expect("salt_len to be Some"),
&mut self.digest,
)
}
fn verify(mut self, pub_key: &RsaPublicKey, hashed: &[u8], sig: &[u8]) -> Result<()> {
verify(
pub_key,
hashed,
&BoxedUint::from_be_slice_vartime(sig),
sig.len(),
&mut self.digest,
self.salt_len,
)
}
}
impl<D> Debug for Pss<D> {
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()
}
}
pub(crate) fn verify<D>(
pub_key: &RsaPublicKey,
hashed: &[u8],
sig: &BoxedUint,
sig_len: usize,
digest: &mut D,
salt_len: Option<usize>,
) -> Result<()>
where
D: Digest + FixedOutputReset,
{
if sig_len != pub_key.size() {
return Err(Error::Verification);
}
let raw = rsa_encrypt(pub_key, sig)?;
let mut em = uint_to_be_pad(raw, pub_key.size())?;
emsa_pss_verify(hashed, &mut em, salt_len, digest, pub_key.n().bits() as _)
}
pub(crate) fn verify_digest<D>(
pub_key: &RsaPublicKey,
hashed: &[u8],
sig: &BoxedUint,
salt_len: Option<usize>,
) -> Result<()>
where
D: Digest + FixedOutputReset,
{
let n = pub_key.n();
if sig >= n.as_ref() || sig.bits_precision() != pub_key.n_bits_precision() {
return Err(Error::Verification);
}
let mut em = uint_to_be_pad(rsa_encrypt(pub_key, sig)?, pub_key.size())?;
emsa_pss_verify_digest::<D>(hashed, &mut em, salt_len, pub_key.n().bits() as _)
}
/// SignPSS calculates the signature of hashed using RSASSA-PSS.
///
/// Note that hashed must be the result of hashing the input message using the
/// given hash function. The opts argument may be nil, in which case sensible
/// defaults are used.
pub(crate) fn sign<T, D>(
rng: &mut T,
blind: bool,
priv_key: &RsaPrivateKey,
hashed: &[u8],
salt_len: usize,
digest: &mut D,
) -> Result<Vec<u8>>
where
T: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
{
let mut salt = vec![0; salt_len];
rng.try_fill_bytes(&mut salt[..]).map_err(|_| Error::Rng)?;
sign_pss_with_salt(blind.then_some(rng), priv_key, hashed, &salt, digest)
}
pub(crate) fn sign_digest<T, D>(
rng: &mut T,
blind: bool,
priv_key: &RsaPrivateKey,
hashed: &[u8],
salt_len: usize,
) -> Result<Vec<u8>>
where
T: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
{
let mut salt = vec![0; salt_len];
rng.try_fill_bytes(&mut salt[..]).map_err(|_| Error::Rng)?;
sign_pss_with_salt_digest::<_, D>(blind.then_some(rng), priv_key, hashed, &salt)
}
/// signPSSWithSalt calculates the signature of hashed using PSS 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<T, D>(
blind_rng: Option<&mut T>,
priv_key: &RsaPrivateKey,
hashed: &[u8],
salt: &[u8],
digest: &mut D,
) -> Result<Vec<u8>>
where
T: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
{
let em_bits = priv_key.n().bits() - 1;
let em = emsa_pss_encode(hashed, em_bits as _, salt, digest)?;
let em = BoxedUint::from_be_slice(&em, priv_key.n_bits_precision())?;
let raw = rsa_decrypt_and_check(priv_key, blind_rng, &em)?;
uint_to_zeroizing_be_pad(raw, priv_key.size())
}
fn sign_pss_with_salt_digest<T, D>(
blind_rng: Option<&mut T>,
priv_key: &RsaPrivateKey,
hashed: &[u8],
salt: &[u8],
) -> Result<Vec<u8>>
where
T: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
{
let em_bits = priv_key.n().bits() - 1;
let em = emsa_pss_encode_digest::<D>(hashed, em_bits as _, salt)?;
let em = BoxedUint::from_be_slice(&em, priv_key.n_bits_precision())?;
uint_to_zeroizing_be_pad(
rsa_decrypt_and_check(priv_key, blind_rng, &em)?,
priv_key.size(),
)
}
/// Returns the [`AlgorithmIdentifierOwned`] associated with PSS signature using a given digest.
#[cfg(feature = "encoding")]
pub fn get_default_pss_signature_algo_id<D>() -> spki::Result<AlgorithmIdentifierOwned>
where
D: Digest + AssociatedOid,
{
let salt_len: u8 = <D as Digest>::output_size() as u8;
get_pss_signature_algo_id::<D>(salt_len)
}
#[cfg(feature = "encoding")]
fn get_pss_signature_algo_id<D>(salt_len: u8) -> spki::Result<AlgorithmIdentifierOwned>
where
D: Digest + AssociatedOid,
{
let pss_params = RsaPssParams::new::<D>(salt_len);
Ok(AlgorithmIdentifierOwned {
oid: ID_RSASSA_PSS,
parameters: Some(Any::encode_from(&pss_params)?),
})
}
#[cfg(all(test, feature = "encoding"))]
mod test {
use crate::pss::{BlindedSigningKey, Pss, Signature, SigningKey, VerifyingKey};
use crate::{RsaPrivateKey, RsaPublicKey};
use crate::traits::PublicKeyParts;
use hex_literal::hex;
use pkcs1::DecodeRsaPrivateKey;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use rstest::rstest;
use sha1::{Digest, Sha1};
use signature::hazmat::{PrehashVerifier, RandomizedPrehashSigner};
use signature::{DigestVerifier, Keypair, RandomizedDigestSigner, RandomizedSigner, Verifier};
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-----
let pem = r#"
-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBALKZD0nEffqM1ACuak0bijtqE2QrI/KLADv7l3kK3ppMyCuLKoF0
fd7Ai2KW5ToIwzFofvJcS/STa6HA5gQenRUCAwEAAQJBAIq9amn00aS0h/CrjXqu
/ThglAXJmZhOMPVn4eiu7/ROixi9sex436MaVeMqSNf7Ex9a8fRNfWss7Sqd9eWu
RTUCIQDasvGASLqmjeffBNLTXV2A5g4t+kLVCpsEIZAycV5GswIhANEPLmax0ME/
EO+ZJ79TJKN5yiGBRsv5yvx5UiHxajEXAiAhAol5N4EUyq6I9w1rYdhPMGpLfk7A
IU2snfRJ6Nq2CQIgFrPsWRCkV+gOYcajD17rEqmuLrdIRexpg8N1DOSXoJ8CIGlS
tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V
-----END RSA PRIVATE KEY-----"#;
RsaPrivateKey::from_pkcs1_pem(pem).unwrap()
}
#[rstest]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f"
),
true,
)]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962e"
),
false,
)]
fn test_verify_pss(#[case] text: &str, #[case] sig: [u8; 64], #[case] expected: bool) {
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let digest = Sha1::digest(text.as_bytes()).to_vec();
let result = pub_key.verify(Pss::<Sha1>::new(), &digest, &sig);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[rstest]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f"
),
true,
)]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962e"
),
false,
)]
fn test_verify_pss_signer(#[case] text: &str, #[case] sig: [u8; 64], #[case] expected: bool) {
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let verifying_key: VerifyingKey<Sha1> = VerifyingKey::new(pub_key);
let result = verifying_key.verify(
text.as_bytes(),
&Signature::try_from(sig.as_slice()).unwrap(),
);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[rstest]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f"
),
true,
)]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962e"
),
false,
)]
fn test_verify_pss_digest_signer(
#[case] text: &str,
#[case] sig: [u8; 64],
#[case] expected: bool,
) {
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let verifying_key = VerifyingKey::new(pub_key);
let result = verifying_key.verify_digest(
|digest: &mut Sha1| {
digest.update(text.as_bytes());
Ok(())
},
&Signature::try_from(sig.as_slice()).unwrap(),
);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_roundtrip(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let digest = Sha1::digest(test.as_bytes()).to_vec();
let sig = priv_key
.sign_with_rng(&mut rng, Pss::<Sha1>::new(), &digest)
.expect("failed to sign");
priv_key
.to_public_key()
.verify(Pss::<Sha1>::new(), &digest, &sig)
.expect("failed to verify");
}
#[rstest]
#[case("test\n")]
fn test_sign_blinded_and_verify_roundtrip(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let digest = Sha1::digest(test.as_bytes()).to_vec();
let sig = priv_key
.sign_with_rng(&mut rng, Pss::<Sha1>::new_blinded(), &digest)
.expect("failed to sign");
priv_key
.to_public_key()
.verify(Pss::<Sha1>::new(), &digest, &sig)
.expect("failed to verify");
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_roundtrip_signer(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = SigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
let sig = signing_key.sign_with_rng(&mut rng, test.as_bytes());
verifying_key
.verify(test.as_bytes(), &sig)
.expect("failed to verify");
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_roundtrip_blinded_signer(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = BlindedSigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
let sig = signing_key.sign_with_rng(&mut rng, test.as_bytes());
verifying_key
.verify(test.as_bytes(), &sig)
.expect("failed to verify");
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_roundtrip_digest_signer(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = SigningKey::new(priv_key);
let verifying_key = signing_key.verifying_key();
let sig = signing_key
.sign_digest_with_rng(&mut rng, |digest: &mut Sha1| digest.update(test.as_bytes()));
verifying_key
.verify_digest(
|digest: &mut Sha1| {
digest.update(test.as_bytes());
Ok(())
},
&sig,
)
.expect("failed to verify");
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_roundtrip_blinded_digest_signer(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = BlindedSigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
let sig = signing_key
.sign_digest_with_rng(&mut rng, |digest: &mut Sha1| digest.update(test.as_bytes()));
verifying_key
.verify_digest(
|digest: &mut Sha1| {
digest.update(test.as_bytes());
Ok(())
},
&sig,
)
.expect("failed to verify");
}
#[rstest]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f"
),
true
)]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962e"
),
false
)]
fn test_verify_pss_hazmat(#[case] text: &str, #[case] sig: [u8; 64], #[case] expected: bool) {
let text = Sha1::digest(text);
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let verifying_key = VerifyingKey::<Sha1>::new(pub_key);
let result = verifying_key
.verify_prehash(text.as_ref(), &Signature::try_from(sig.as_slice()).unwrap());
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_pss_hazmat(#[case] test: &str) {
let test = &Sha1::digest(test);
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = SigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
let sig = signing_key
.sign_prehash_with_rng(&mut rng, test)
.expect("failed to sign");
verifying_key
.verify_prehash(test, &sig)
.expect("failed to verify");
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_pss_blinded_hazmat(#[case] test: &str) {
let priv_key = get_private_key();
let test = &Sha1::digest(test);
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = BlindedSigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
let sig = signing_key
.sign_prehash_with_rng(&mut rng, test)
.expect("failed to sign");
verifying_key
.verify_prehash(test, &sig)
.expect("failed to verify");
}
#[test]
// Tests the corner case where the key is multiple of 8 + 1 bits long
fn test_sign_and_verify_2049bit_key() {
let plaintext = "Hello\n";
let mut rng = ChaCha8Rng::from_seed([42; 32]);
for i in 0..10 {
println!("round {i}");
let priv_key = RsaPrivateKey::new(&mut rng, 2049).unwrap();
let digest = Sha1::digest(plaintext.as_bytes()).to_vec();
let sig = priv_key
.sign_with_rng(&mut rng, Pss::<Sha1>::new(), &digest)
.expect("failed to sign");
priv_key
.to_public_key()
.verify(Pss::<Sha1>::new(), &digest, &sig)
.expect("failed to verify");
}
}
// Tests the case where the salt length used for signing differs from the default length
// while the verifier uses auto-detection.
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_pss_differing_salt_len(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
// signing keys using different salt lengths
let signing_keys = [
// default salt length
SigningKey::<Sha1>::new(priv_key.clone()),
// maximum salt length
SigningKey::<Sha1>::new_with_salt_len(
priv_key.clone(),
priv_key.size() - Sha1::output_size() - 2,
),
// unsalted
SigningKey::<Sha1>::new_with_salt_len(priv_key.clone(), 0),
];
// verifying key uses default salt length strategy
let verifying_key = VerifyingKey::<Sha1>::new_with_auto_salt_len(priv_key.to_public_key());
for signing_key in &signing_keys {
let sig = signing_key.sign_with_rng(&mut rng, test.as_bytes());
verifying_key
.verify(test.as_bytes(), &sig)
.expect("verification to succeed");
}
}
}
+307
View File
@@ -0,0 +1,307 @@
use super::{sign_digest, Signature, VerifyingKey};
use crate::{Result, RsaPrivateKey};
use core::marker::PhantomData;
use digest::{Digest, FixedOutputReset, HashMarker, Update};
use rand_core::{CryptoRng, TryCryptoRng};
use signature::{
hazmat::RandomizedPrehashSigner, Keypair, RandomizedDigestSigner, RandomizedMultipartSigner,
RandomizedSigner,
};
use zeroize::ZeroizeOnDrop;
#[cfg(feature = "encoding")]
use {
super::get_pss_signature_algo_id,
const_oid::AssociatedOid,
pkcs8::{EncodePrivateKey, SecretDocument},
spki::{
der::AnyRef, AlgorithmIdentifierOwned, AlgorithmIdentifierRef,
AssociatedAlgorithmIdentifier, DynSignatureAlgorithmIdentifier,
},
};
#[cfg(feature = "serde")]
use {
pkcs8::DecodePrivateKey,
serdect::serde::{de, ser, Deserialize, Serialize},
};
/// Signing key for producing "blinded" RSASSA-PSS signatures as described in
/// [draft-irtf-cfrg-rsa-blind-signatures](https://datatracker.ietf.org/doc/draft-irtf-cfrg-rsa-blind-signatures/).
#[derive(Debug, Clone)]
pub struct BlindedSigningKey<D>
where
D: Digest,
{
inner: RsaPrivateKey,
salt_len: usize,
phantom: PhantomData<D>,
}
impl<D> BlindedSigningKey<D>
where
D: Digest,
{
/// Create a new RSASSA-PSS signing key which produces "blinded"
/// signatures.
/// Digest output size is used as a salt length.
pub fn new(key: RsaPrivateKey) -> Self {
Self::new_with_salt_len(key, <D as Digest>::output_size())
}
/// Create a new RSASSA-PSS signing key which produces "blinded"
/// signatures with a salt of the given length.
pub fn new_with_salt_len(key: RsaPrivateKey, salt_len: usize) -> Self {
Self {
inner: key,
salt_len,
phantom: Default::default(),
}
}
/// Create a new random RSASSA-PSS signing key which produces "blinded"
/// signatures.
/// Digest output size is used as a salt length.
pub fn random<R: CryptoRng + ?Sized>(rng: &mut R, bit_size: usize) -> Result<Self> {
Self::random_with_salt_len(rng, bit_size, <D as Digest>::output_size())
}
/// Create a new random RSASSA-PSS signing key which produces "blinded"
/// signatures with a salt of the given length.
pub fn random_with_salt_len<R: CryptoRng + ?Sized>(
rng: &mut R,
bit_size: usize,
salt_len: usize,
) -> Result<Self> {
Ok(Self {
inner: RsaPrivateKey::new(rng, bit_size)?,
salt_len,
phantom: Default::default(),
})
}
/// Return specified salt length for this key
pub fn salt_len(&self) -> usize {
self.salt_len
}
}
//
// `*Signer` trait impls
//
impl<D> RandomizedSigner<Signature> for BlindedSigningKey<D>
where
D: Digest + FixedOutputReset,
{
fn try_sign_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
msg: &[u8],
) -> signature::Result<Signature> {
self.try_multipart_sign_with_rng(rng, &[msg])
}
}
impl<D> RandomizedMultipartSigner<Signature> for BlindedSigningKey<D>
where
D: Digest + FixedOutputReset,
{
fn try_multipart_sign_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
msg: &[&[u8]],
) -> signature::Result<Signature> {
let mut digest = D::new();
msg.iter()
.for_each(|slice| <D as Digest>::update(&mut digest, slice));
sign_digest::<_, D>(rng, true, &self.inner, &digest.finalize(), self.salt_len)?
.as_slice()
.try_into()
}
}
impl<D> RandomizedDigestSigner<D, Signature> for BlindedSigningKey<D>
where
D: Default + FixedOutputReset + HashMarker + Update,
{
fn try_sign_digest_with_rng<
R: TryCryptoRng + ?Sized,
F: Fn(&mut D) -> signature::Result<()>,
>(
&self,
rng: &mut R,
f: F,
) -> signature::Result<Signature> {
let mut digest = D::default();
f(&mut digest)?;
sign_digest::<_, D>(rng, true, &self.inner, &digest.finalize(), self.salt_len)?
.as_slice()
.try_into()
}
}
impl<D> RandomizedPrehashSigner<Signature> for BlindedSigningKey<D>
where
D: Digest + FixedOutputReset,
{
fn sign_prehash_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
prehash: &[u8],
) -> signature::Result<Signature> {
sign_digest::<_, D>(rng, true, &self.inner, prehash, self.salt_len)?
.as_slice()
.try_into()
}
}
//
// Other trait impls
//
impl<D> AsRef<RsaPrivateKey> for BlindedSigningKey<D>
where
D: Digest,
{
fn as_ref(&self) -> &RsaPrivateKey {
&self.inner
}
}
#[cfg(feature = "encoding")]
impl<D> AssociatedAlgorithmIdentifier for BlindedSigningKey<D>
where
D: Digest,
{
type Params = AnyRef<'static>;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
}
#[cfg(feature = "encoding")]
impl<D> DynSignatureAlgorithmIdentifier for BlindedSigningKey<D>
where
D: Digest + AssociatedOid,
{
fn signature_algorithm_identifier(&self) -> spki::Result<AlgorithmIdentifierOwned> {
get_pss_signature_algo_id::<D>(self.salt_len as u8)
}
}
#[cfg(feature = "encoding")]
impl<D> EncodePrivateKey for BlindedSigningKey<D>
where
D: Digest,
{
fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
self.inner.to_pkcs8_der()
}
}
impl<D> From<RsaPrivateKey> for BlindedSigningKey<D>
where
D: Digest,
{
fn from(key: RsaPrivateKey) -> Self {
Self::new(key)
}
}
impl<D> From<BlindedSigningKey<D>> for RsaPrivateKey
where
D: Digest,
{
fn from(key: BlindedSigningKey<D>) -> Self {
key.inner
}
}
impl<D> Keypair for BlindedSigningKey<D>
where
D: Digest,
{
type VerifyingKey = VerifyingKey<D>;
fn verifying_key(&self) -> Self::VerifyingKey {
VerifyingKey {
inner: self.inner.to_public_key(),
salt_len: Some(self.salt_len),
phantom: Default::default(),
}
}
}
#[cfg(feature = "encoding")]
impl<D> TryFrom<pkcs8::PrivateKeyInfoRef<'_>> for BlindedSigningKey<D>
where
D: Digest + AssociatedOid,
{
type Error = pkcs8::Error;
fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
RsaPrivateKey::try_from(private_key_info).map(Self::new)
}
}
impl<D> ZeroizeOnDrop for BlindedSigningKey<D> where D: Digest {}
impl<D> PartialEq for BlindedSigningKey<D>
where
D: Digest,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.salt_len == other.salt_len
}
}
#[cfg(feature = "serde")]
impl<D> Serialize for BlindedSigningKey<D>
where
D: Digest,
{
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let der = self.to_pkcs8_der().map_err(ser::Error::custom)?;
serdect::slice::serialize_hex_lower_or_bin(&der.as_bytes(), serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, D> Deserialize<'de> for BlindedSigningKey<D>
where
D: Digest + AssociatedOid,
{
fn deserialize<De>(deserializer: De) -> core::result::Result<Self, De::Error>
where
De: serde::Deserializer<'de>,
{
let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::from_pkcs8_der(&der_bytes).map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
use sha2::Sha256;
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = BlindedSigningKey::<Sha256>::new(
RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key"),
);
let tokens = [Token::Str(concat!(
"3056020100300d06092a864886f70d010101050004423040020100020900ab240c",
"3361d02e370203010001020811e54a15259d22f9020500ceff5cf3020500d3a7aa",
"ad020500ccaddf17020500cb529d3d020500bb526d6f"
))];
assert_tokens(&signing_key.readable(), &tokens);
}
}
+106
View File
@@ -0,0 +1,106 @@
//! `RSASSA-PSS` signatures.
use alloc::boxed::Box;
use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex};
use crypto_bigint::BoxedUint;
use signature::SignatureEncoding;
#[cfg(feature = "serde")]
use serdect::serde::{de, Deserialize, Serialize};
#[cfg(feature = "encoding")]
use spki::{
der::{asn1::BitString, Result as DerResult},
SignatureBitStringEncoding,
};
/// `RSASSA-PSS` signatures as described in [RFC8017 § 8.1].
///
/// [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature {
pub(super) inner: BoxedUint,
}
impl SignatureEncoding for Signature {
type Repr = Box<[u8]>;
}
#[cfg(feature = "encoding")]
impl SignatureBitStringEncoding for Signature {
fn to_bitstring(&self) -> DerResult<BitString> {
BitString::new(0, self.to_vec())
}
}
impl TryFrom<&[u8]> for Signature {
type Error = signature::Error;
fn try_from(bytes: &[u8]) -> signature::Result<Self> {
// TODO(tarcieri): max length restriction? (#350)
let inner = BoxedUint::from_be_slice_vartime(bytes);
Ok(Self { inner })
}
}
impl From<Signature> for Box<[u8]> {
fn from(signature: Signature) -> Box<[u8]> {
signature.inner.to_be_bytes()
}
}
impl LowerHex for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{:x}", &self.inner)
}
}
impl UpperHex for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{:X}", &self.inner)
}
}
impl Display for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{:X}", self)
}
}
#[cfg(feature = "serde")]
impl Serialize for Signature {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serdect::serde::Serializer,
{
serdect::slice::serialize_hex_lower_or_bin(&self.to_bytes(), serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Signature {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: serdect::serde::Deserializer<'de>,
{
serdect::slice::deserialize_hex_or_bin_vec(deserializer)?
.as_slice()
.try_into()
.map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "serde")]
fn test_serde() {
use super::*;
use serde_test::{assert_tokens, Configure, Token};
let signature = Signature {
inner: BoxedUint::from(42u32),
};
let tokens = [Token::Str("000000000000002a")];
assert_tokens(&signature.readable(), &tokens);
}
}
+346
View File
@@ -0,0 +1,346 @@
use super::{sign_digest, Signature, VerifyingKey};
use crate::{Result, RsaPrivateKey};
use core::marker::PhantomData;
use digest::{Digest, FixedOutputReset, Update};
use rand_core::{CryptoRng, TryCryptoRng};
use signature::{
hazmat::RandomizedPrehashSigner, Keypair, RandomizedDigestSigner, RandomizedMultipartSigner,
RandomizedSigner,
};
use zeroize::ZeroizeOnDrop;
#[cfg(feature = "serde")]
use {
pkcs8::DecodePrivateKey,
serdect::serde::{de, ser, Deserialize, Serialize},
};
#[cfg(feature = "encoding")]
use {
super::get_pss_signature_algo_id,
crate::encoding::verify_algorithm_id,
const_oid::AssociatedOid,
pkcs8::{EncodePrivateKey, SecretDocument},
spki::{
der::AnyRef, AlgorithmIdentifierOwned, AlgorithmIdentifierRef,
AssociatedAlgorithmIdentifier, DynSignatureAlgorithmIdentifier,
},
};
#[cfg(feature = "getrandom")]
use {
crypto_common::getrandom::SysRng,
signature::{hazmat::PrehashSigner, MultipartSigner, Signer},
};
/// Signing key for producing RSASSA-PSS signatures as described in
/// [RFC8017 § 8.1].
///
/// [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1
#[derive(Debug, Clone)]
pub struct SigningKey<D>
where
D: Digest,
{
inner: RsaPrivateKey,
salt_len: usize,
phantom: PhantomData<D>,
}
impl<D> SigningKey<D>
where
D: Digest,
{
/// Create a new RSASSA-PSS signing key.
/// Digest output size is used as a salt length.
pub fn new(key: RsaPrivateKey) -> Self {
Self::new_with_salt_len(key, <D as Digest>::output_size())
}
/// Create a new RSASSA-PSS signing key with a salt of the given length.
pub fn new_with_salt_len(key: RsaPrivateKey, salt_len: usize) -> Self {
Self {
inner: key,
salt_len,
phantom: Default::default(),
}
}
/// Generate a new random RSASSA-PSS signing key.
/// Digest output size is used as a salt length.
pub fn random<R: CryptoRng + ?Sized>(rng: &mut R, bit_size: usize) -> Result<Self> {
Self::random_with_salt_len(rng, bit_size, <D as Digest>::output_size())
}
/// Generate a new random RSASSA-PSS signing key with a salt of the given length.
pub fn random_with_salt_len<R: CryptoRng + ?Sized>(
rng: &mut R,
bit_size: usize,
salt_len: usize,
) -> Result<Self> {
Ok(Self {
inner: RsaPrivateKey::new(rng, bit_size)?,
salt_len,
phantom: Default::default(),
})
}
/// Return specified salt length for this key
pub fn salt_len(&self) -> usize {
self.salt_len
}
}
//
// `*Signer` trait impls
//
impl<D> RandomizedDigestSigner<D, Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset + Update,
{
fn try_sign_digest_with_rng<
R: TryCryptoRng + ?Sized,
F: Fn(&mut D) -> signature::Result<()>,
>(
&self,
rng: &mut R,
f: F,
) -> signature::Result<Signature> {
let mut digest = D::new();
f(&mut digest)?;
sign_digest::<_, D>(rng, false, &self.inner, &digest.finalize(), self.salt_len)?
.as_slice()
.try_into()
}
}
impl<D> RandomizedSigner<Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset + Update,
{
fn try_sign_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
msg: &[u8],
) -> signature::Result<Signature> {
self.try_sign_digest_with_rng(rng, |digest: &mut D| {
Update::update(digest, msg);
Ok(())
})
}
}
impl<D> RandomizedMultipartSigner<Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset + Update,
{
fn try_multipart_sign_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
msg: &[&[u8]],
) -> signature::Result<Signature> {
self.try_sign_digest_with_rng(rng, |digest: &mut D| {
msg.iter().for_each(|slice| Update::update(digest, slice));
Ok(())
})
}
}
impl<D> RandomizedPrehashSigner<Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset + Update,
{
fn sign_prehash_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
prehash: &[u8],
) -> signature::Result<Signature> {
sign_digest::<_, D>(rng, false, &self.inner, prehash, self.salt_len)?
.as_slice()
.try_into()
}
}
#[cfg(feature = "getrandom")]
impl<D> PrehashSigner<Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset,
{
fn sign_prehash(&self, prehash: &[u8]) -> signature::Result<Signature> {
self.sign_prehash_with_rng(&mut SysRng, prehash)
}
}
#[cfg(feature = "getrandom")]
impl<D> Signer<Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset,
{
fn try_sign(&self, msg: &[u8]) -> signature::Result<Signature> {
self.try_sign_with_rng(&mut SysRng, msg)
}
}
#[cfg(feature = "getrandom")]
impl<D> MultipartSigner<Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset,
{
fn try_multipart_sign(&self, msg: &[&[u8]]) -> signature::Result<Signature> {
self.try_multipart_sign_with_rng(&mut SysRng, msg)
}
}
//
// Other trait impls
//
impl<D> AsRef<RsaPrivateKey> for SigningKey<D>
where
D: Digest,
{
fn as_ref(&self) -> &RsaPrivateKey {
&self.inner
}
}
#[cfg(feature = "encoding")]
impl<D> AssociatedAlgorithmIdentifier for SigningKey<D>
where
D: Digest,
{
type Params = AnyRef<'static>;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
}
#[cfg(feature = "encoding")]
impl<D> DynSignatureAlgorithmIdentifier for SigningKey<D>
where
D: Digest + AssociatedOid,
{
fn signature_algorithm_identifier(&self) -> spki::Result<AlgorithmIdentifierOwned> {
get_pss_signature_algo_id::<D>(self.salt_len as u8)
}
}
#[cfg(feature = "encoding")]
impl<D> EncodePrivateKey for SigningKey<D>
where
D: Digest,
{
fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
self.inner.to_pkcs8_der()
}
}
impl<D> From<RsaPrivateKey> for SigningKey<D>
where
D: Digest,
{
fn from(key: RsaPrivateKey) -> Self {
Self::new(key)
}
}
impl<D> From<SigningKey<D>> for RsaPrivateKey
where
D: Digest,
{
fn from(key: SigningKey<D>) -> Self {
key.inner
}
}
impl<D> Keypair for SigningKey<D>
where
D: Digest,
{
type VerifyingKey = VerifyingKey<D>;
fn verifying_key(&self) -> Self::VerifyingKey {
VerifyingKey {
inner: self.inner.to_public_key(),
salt_len: Some(self.salt_len),
phantom: Default::default(),
}
}
}
#[cfg(feature = "encoding")]
impl<D> TryFrom<pkcs8::PrivateKeyInfoRef<'_>> for SigningKey<D>
where
D: Digest + AssociatedOid,
{
type Error = pkcs8::Error;
fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
verify_algorithm_id(&private_key_info.algorithm)?;
RsaPrivateKey::try_from(private_key_info).map(Self::new)
}
}
impl<D> ZeroizeOnDrop for SigningKey<D> where D: Digest {}
impl<D> PartialEq for SigningKey<D>
where
D: Digest,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.salt_len == other.salt_len
}
}
#[cfg(feature = "serde")]
impl<D> Serialize for SigningKey<D>
where
D: Digest,
{
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serdect::serde::Serializer,
{
let der = self.to_pkcs8_der().map_err(ser::Error::custom)?;
serdect::slice::serialize_hex_lower_or_bin(&der.as_bytes(), serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, D> Deserialize<'de> for SigningKey<D>
where
D: Digest + AssociatedOid,
{
fn deserialize<De>(deserializer: De) -> core::result::Result<Self, De::Error>
where
De: serdect::serde::Deserializer<'de>,
{
let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::from_pkcs8_der(&der_bytes).map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use crate::RsaPrivateKey;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
use sha2::Sha256;
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key");
let signing_key = SigningKey::<Sha256>::new(priv_key);
let tokens = [Token::Str(concat!(
"3056020100300d06092a864886f70d010101050004423040020100020900ab240c",
"3361d02e370203010001020811e54a15259d22f9020500ceff5cf3020500d3a7aa",
"ad020500ccaddf17020500cb529d3d020500bb526d6f"
))];
assert_tokens(&signing_key.readable(), &tokens);
}
}
+265
View File
@@ -0,0 +1,265 @@
use super::{verify_digest, Signature};
use crate::RsaPublicKey;
use core::marker::PhantomData;
use digest::{Digest, FixedOutputReset, Update};
use signature::{hazmat::PrehashVerifier, DigestVerifier, Verifier};
#[cfg(feature = "encoding")]
use {
crate::encoding::ID_RSASSA_PSS,
const_oid::AssociatedOid,
pkcs8::{Document, EncodePublicKey},
spki::{der::AnyRef, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier},
};
#[cfg(feature = "serde")]
use {
serdect::serde::{de, ser, Deserialize, Serialize},
spki::DecodePublicKey,
};
/// Verifying key for checking the validity of RSASSA-PSS signatures as
/// described in [RFC8017 § 8.1].
///
/// [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1
#[derive(Debug)]
pub struct VerifyingKey<D>
where
D: Digest,
{
pub(super) inner: RsaPublicKey,
pub(super) salt_len: Option<usize>,
pub(super) phantom: PhantomData<D>,
}
impl<D> VerifyingKey<D>
where
D: Digest,
{
/// Create a new RSASSA-PSS verifying key.
/// Digest output size is used as a salt length.
pub fn new(key: RsaPublicKey) -> Self {
Self::new_with_salt_len(key, <D as Digest>::output_size())
}
/// Create a new RSASSA-PSS verifying key.
pub fn new_with_salt_len(key: RsaPublicKey, salt_len: usize) -> Self {
Self {
inner: key,
salt_len: Some(salt_len),
phantom: Default::default(),
}
}
/// Create a new RSASSA-PSS verifying key.
/// Attempts to automatically detect the salt length.
pub fn new_with_auto_salt_len(key: RsaPublicKey) -> Self {
Self {
inner: key,
salt_len: None,
phantom: Default::default(),
}
}
/// Return specified salt length for this key
pub fn salt_len(&self) -> Option<usize> {
self.salt_len
}
}
//
// `*Verifier` trait impls
//
impl<D> DigestVerifier<D, Signature> for VerifyingKey<D>
where
D: Digest + FixedOutputReset + Update,
{
fn verify_digest<F: Fn(&mut D) -> signature::Result<()>>(
&self,
f: F,
signature: &Signature,
) -> signature::Result<()> {
let mut digest = D::new();
f(&mut digest)?;
verify_digest::<D>(
&self.inner,
&digest.finalize(),
&signature.inner,
self.salt_len,
)
.map_err(|e| e.into())
}
}
impl<D> PrehashVerifier<Signature> for VerifyingKey<D>
where
D: Digest + FixedOutputReset,
{
fn verify_prehash(&self, prehash: &[u8], signature: &Signature) -> signature::Result<()> {
verify_digest::<D>(&self.inner, prehash, &signature.inner, self.salt_len)
.map_err(|e| e.into())
}
}
impl<D> Verifier<Signature> for VerifyingKey<D>
where
D: Digest + FixedOutputReset,
{
fn verify(&self, msg: &[u8], signature: &Signature) -> signature::Result<()> {
verify_digest::<D>(
&self.inner,
&D::digest(msg),
&signature.inner,
self.salt_len,
)
.map_err(|e| e.into())
}
}
//
// Other trait impls
//
impl<D> AsRef<RsaPublicKey> for VerifyingKey<D>
where
D: Digest,
{
fn as_ref(&self) -> &RsaPublicKey {
&self.inner
}
}
#[cfg(feature = "encoding")]
impl<D> AssociatedAlgorithmIdentifier for VerifyingKey<D>
where
D: Digest,
{
type Params = AnyRef<'static>;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
}
// Implemented manually so we don't have to bind D with Clone
impl<D> Clone for VerifyingKey<D>
where
D: Digest,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
salt_len: self.salt_len,
phantom: Default::default(),
}
}
}
#[cfg(feature = "encoding")]
impl<D> EncodePublicKey for VerifyingKey<D>
where
D: Digest,
{
fn to_public_key_der(&self) -> spki::Result<Document> {
self.inner.to_public_key_der()
}
}
impl<D> From<RsaPublicKey> for VerifyingKey<D>
where
D: Digest,
{
fn from(key: RsaPublicKey) -> Self {
Self::new(key)
}
}
impl<D> From<VerifyingKey<D>> for RsaPublicKey
where
D: Digest,
{
fn from(key: VerifyingKey<D>) -> Self {
key.inner
}
}
#[cfg(feature = "encoding")]
impl<D> TryFrom<pkcs8::SubjectPublicKeyInfoRef<'_>> for VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
type Error = spki::Error;
fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
match spki.algorithm.oid {
ID_RSASSA_PSS | pkcs1::ALGORITHM_OID => (),
_ => {
return Err(spki::Error::OidUnknown {
oid: spki.algorithm.oid,
});
}
}
RsaPublicKey::try_from(spki).map(Self::new)
}
}
impl<D> PartialEq for VerifyingKey<D>
where
D: Digest,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.salt_len == other.salt_len
}
}
#[cfg(feature = "serde")]
impl<D> Serialize for VerifyingKey<D>
where
D: Digest,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let der = self.to_public_key_der().map_err(ser::Error::custom)?;
serdect::slice::serialize_hex_lower_or_bin(&der, serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, D> Deserialize<'de> for VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
fn deserialize<De>(deserializer: De) -> Result<Self, De::Error>
where
De: serde::Deserializer<'de>,
{
let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::from_public_key_der(&der_bytes).map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use crate::RsaPrivateKey;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
use sha2::Sha256;
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key");
let pub_key = priv_key.to_public_key();
let verifying_key = VerifyingKey::<Sha256>::new(pub_key);
let tokens = [Token::Str(
"3024300d06092a864886f70d01010105000313003010020900ab240c3361d02e370203010001",
)];
assert_tokens(&verifying_key.readable(), &tokens);
}
}
+9
View File
@@ -0,0 +1,9 @@
//! RSA-related trait definitions.
mod encryption;
pub(crate) mod keys;
mod padding;
pub use encryption::{Decryptor, EncryptingKeypair, RandomizedDecryptor, RandomizedEncryptor};
pub use keys::{PrivateKeyParts, PublicKeyParts};
pub use padding::{PaddingScheme, SignatureScheme};
+38
View File
@@ -0,0 +1,38 @@
//! Encryption-related traits.
use alloc::vec::Vec;
use rand_core::CryptoRng;
use crate::errors::Result;
/// Encrypt the message using provided random source
pub trait RandomizedEncryptor {
/// Encrypt the given message.
fn encrypt_with_rng<R: CryptoRng + ?Sized>(&self, rng: &mut R, msg: &[u8]) -> Result<Vec<u8>>;
}
/// Decrypt the given message
pub trait Decryptor {
/// Decrypt the given message.
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>>;
}
/// Decrypt the given message using provided random source
pub trait RandomizedDecryptor {
/// Decrypt the given message.
fn decrypt_with_rng<R: CryptoRng + ?Sized>(
&self,
rng: &mut R,
ciphertext: &[u8],
) -> Result<Vec<u8>>;
}
/// Encryption keypair with an associated encryption key.
pub trait EncryptingKeypair {
/// Encrypting key type for this keypair.
type EncryptingKey: Clone;
/// Get the encrypting key which can encrypt messages to be decrypted by
/// the decryption key portion of this keypair.
fn encrypting_key(&self) -> Self::EncryptingKey;
}
+93
View File
@@ -0,0 +1,93 @@
//! Traits related to the key components
use alloc::boxed::Box;
use crypto_bigint::{
modular::{BoxedMontyForm, BoxedMontyParams},
BoxedUint, NonZero,
};
use zeroize::Zeroize;
/// Components of an RSA public key.
pub trait PublicKeyParts {
/// Returns the modulus of the key.
fn n(&self) -> &NonZero<BoxedUint>;
/// Returns the public exponent of the key.
fn e(&self) -> &BoxedUint;
/// 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() as usize).div_ceil(8)
}
/// Returns the parameters for montgomery operations.
fn n_params(&self) -> &BoxedMontyParams;
/// Returns precision (in bits) of `n`.
fn n_bits_precision(&self) -> u32 {
self.n().bits_precision()
}
/// Returns the big endian serialization of the modulus of the key
fn n_bytes(&self) -> Box<[u8]> {
self.n().to_be_bytes_trimmed_vartime()
}
/// Returns the big endian serialization of the public exponent of the key
fn e_bytes(&self) -> Box<[u8]> {
self.e().to_be_bytes_trimmed_vartime()
}
}
/// Components of an RSA private key.
pub trait PrivateKeyParts: PublicKeyParts {
/// Returns the private exponent of the key.
fn d(&self) -> &BoxedUint;
/// Returns the prime factors.
fn primes(&self) -> &[BoxedUint];
/// Returns the precomputed dp value, D mod (P-1)
fn dp(&self) -> Option<&BoxedUint>;
/// Returns the precomputed dq value, D mod (Q-1)
fn dq(&self) -> Option<&BoxedUint>;
/// Returns the precomputed qinv value, Q^-1 mod P
fn qinv(&self) -> Option<&BoxedMontyForm>;
/// Returns an iterator over the CRT Values
fn crt_values(&self) -> Option<&[CrtValue]>;
/// Returns the params for `p` if precomputed.
fn p_params(&self) -> Option<&BoxedMontyParams>;
/// Returns the params for `q` if precomputed.
fn q_params(&self) -> Option<&BoxedMontyParams>;
}
/// Contains the precomputed Chinese remainder theorem values.
#[derive(Debug, Clone)]
pub struct CrtValue {
/// D mod (prime - 1)
pub(crate) exp: BoxedUint,
/// R·Coeff ≡ 1 mod Prime.
pub(crate) coeff: BoxedUint,
/// product of primes prior to this (inc p and q)
pub(crate) r: BoxedUint,
}
impl Zeroize for CrtValue {
fn zeroize(&mut self) {
self.exp.zeroize();
self.coeff.zeroize();
self.r.zeroize();
}
}
impl Drop for CrtValue {
fn drop(&mut self) {
self.zeroize();
}
}
+49
View File
@@ -0,0 +1,49 @@
//! Supported padding schemes.
use alloc::vec::Vec;
use rand_core::TryCryptoRng;
use crate::errors::Result;
use crate::key::{RsaPrivateKey, RsaPublicKey};
/// 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<Rng: TryCryptoRng + ?Sized>(
self,
rng: Option<&mut Rng>,
priv_key: &RsaPrivateKey,
ciphertext: &[u8],
) -> Result<Vec<u8>>;
/// Encrypt the given message using the given public key.
fn encrypt<Rng: TryCryptoRng + ?Sized>(
self,
rng: &mut Rng,
pub_key: &RsaPublicKey,
msg: &[u8],
) -> Result<Vec<u8>>;
}
/// Digital signature scheme.
pub trait SignatureScheme {
/// Sign the given digest.
fn sign<Rng: TryCryptoRng + ?Sized>(
self,
rng: Option<&mut Rng>,
priv_key: &RsaPrivateKey,
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(self, pub_key: &RsaPublicKey, hashed: &[u8], sig: &[u8]) -> Result<()>;
}