fix(servo): gate vulnerable RSA private operations
This commit is contained in:
Vendored
+10
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+24
@@ -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 {}
|
||||
Vendored
+240
@@ -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,
|
||||
}
|
||||
}
|
||||
Vendored
+141
@@ -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)
|
||||
}
|
||||
}
|
||||
Vendored
+14
@@ -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};
|
||||
Vendored
+1305
File diff suppressed because it is too large
Load Diff
Vendored
+269
@@ -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;
|
||||
Vendored
+612
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+675
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+675
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+106
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+9
@@ -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
@@ -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;
|
||||
}
|
||||
Vendored
+93
@@ -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
@@ -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<()>;
|
||||
}
|
||||
Reference in New Issue
Block a user