use rsa::traits::PaddingScheme;
use super::content::{Cek, cek_len};
use super::key::RecipientKey;
use super::keymgmt::{KeyOutput, RecipientOptions};
use super::random::RandomSource;
use crate::json::Object;
use crate::jwa::{ContentEncAlg, KeyMgmtAlg};
use crate::{Error, Result};
#[cfg(test)]
const SEED_LEN: usize = 32;
const MIN_MODULUS_BITS: u32 = 2048;
const CEK_SIZE: &str = "CEK is missing or not the size enc requires";
const MODULUS_SIZE: &str = "RSA modulus must be at least 2048 bits";
const MEMBER_SIZE: &str = "RSA key member is empty or too long";
const KEY_INVALID: &str = "RSA public key is not valid for RSA-OAEP-256";
pub(super) fn encrypt_key(
alg: &KeyMgmtAlg,
key: &RecipientKey,
enc: &ContentEncAlg,
cek: Option<&Cek>,
_options: &RecipientOptions,
rng: &mut dyn RandomSource,
) -> Result<KeyOutput> {
if *alg != KeyMgmtAlg::RsaOaep256 {
return Err(Error::UnsupportedAlgorithm);
}
let RecipientKey::RsaPublic { n, e } = key else {
return Err(Error::KeyAlgorithmMismatch);
};
let cek_len = cek_len(enc)?;
let cek = cek
.filter(|cek| cek.len() == cek_len)
.ok_or(Error::InvalidKey(CEK_SIZE))?;
let public = public_key(n, e)?;
let mut seed_rng = rng.crypto_rng();
let encrypted_key = rsa::Oaep::<sha2::Sha256>::new()
.encrypt(&mut seed_rng, &public, cek.as_bytes())
.map_err(|err| match err {
rsa::errors::Error::Rng => Error::RandomnessUnavailable,
_ => Error::InvalidKey(KEY_INVALID),
})?;
Ok(KeyOutput {
encrypted_key,
header_params: Object::new(),
cek: None,
})
}
fn public_key(n: &[u8], e: &[u8]) -> Result<rsa::RsaPublicKey> {
let (n, e) = (uint(n)?, uint(e)?);
if n.bits() < MIN_MODULUS_BITS {
return Err(Error::InvalidKey(MODULUS_SIZE));
}
rsa::RsaPublicKey::new(n, e).map_err(|_| Error::InvalidKey(KEY_INVALID))
}
fn uint(bytes: &[u8]) -> Result<rsa::BoxedUint> {
let bits = u32::try_from(bytes.len())
.ok()
.and_then(|len| len.checked_mul(8))
.filter(|&bits| bits > 0)
.ok_or(Error::InvalidKey(MEMBER_SIZE))?;
rsa::BoxedUint::from_be_slice(bytes, bits).map_err(|_| Error::InvalidKey(MEMBER_SIZE))
}
#[cfg(test)]
mod tests {
use super::super::random::OsRandom;
use super::*;
use crate::rng::insecure_test_vectors::FixedRng;
const F4: &[u8] = &[1, 0, 1];
fn modulus(bits: usize) -> Vec<u8> {
let mut n = vec![0xff; bits.div_ceil(8)];
n[0] >>= (8 - bits % 8) % 8;
n
}
fn key(bits: usize) -> RecipientKey {
RecipientKey::RsaPublic {
n: modulus(bits),
e: F4.to_vec(),
}
}
fn cek(enc: &ContentEncAlg) -> Cek {
let len = super::super::content::cek_len(enc).unwrap();
Cek::from_slice(&vec![0x5a; len])
}
fn seed() -> FixedRng {
FixedRng::new([0x42u8; SEED_LEN])
}
fn run(
key: &RecipientKey,
enc: &ContentEncAlg,
cek: Option<&Cek>,
rng: &mut dyn RandomSource,
) -> Result<KeyOutput> {
encrypt_key(
&KeyMgmtAlg::RsaOaep256,
key,
enc,
cek,
&RecipientOptions::default(),
rng,
)
}
#[test]
fn the_encrypted_key_is_k_octets_and_defines_no_header_parameter() {
for bits in [2048, 3072, 4096, 4097, 8192] {
for enc in ContentEncAlg::KNOWN {
let cek = cek(enc);
let mut rng = seed();
let out = run(&key(bits), enc, Some(&cek), &mut rng).unwrap();
assert_eq!(out.encrypted_key.len(), bits.div_ceil(8), "{bits} {enc}");
assert!(out.header_params.is_empty(), "{bits} {enc}");
assert!(out.cek.is_none(), "{bits} {enc}: the random CEK is used");
}
}
}
#[test]
fn the_oaep_seed_is_one_draw_of_hlen_octets() {
let enc = ContentEncAlg::A128GCM;
let cek = cek(&enc);
let mut rng = FixedRng::new([0x42u8; SEED_LEN + 3]);
run(&key(2048), &enc, Some(&cek), &mut rng).unwrap();
assert_eq!(rng.remaining(), 3);
}
#[test]
fn the_same_seed_gives_the_same_encrypted_key_and_another_seed_another() {
let enc = ContentEncAlg::A256CbcHs512;
let cek = cek(&enc);
let first = run(&key(2048), &enc, Some(&cek), &mut seed()).unwrap();
let again = run(&key(2048), &enc, Some(&cek), &mut seed()).unwrap();
assert_eq!(first.encrypted_key, again.encrypted_key);
let mut other = [0x42u8; SEED_LEN];
other[SEED_LEN - 1] ^= 1;
let other = run(&key(2048), &enc, Some(&cek), &mut FixedRng::new(other)).unwrap();
assert_ne!(first.encrypted_key, other.encrypted_key);
}
#[test]
fn the_os_source_gives_a_fresh_seed_each_time() {
let enc = ContentEncAlg::A128GCM;
let cek = cek(&enc);
let a = run(&key(2048), &enc, Some(&cek), &mut OsRandom).unwrap();
let b = run(&key(2048), &enc, Some(&cek), &mut OsRandom).unwrap();
assert_eq!(a.encrypted_key.len(), 256);
assert_ne!(a.encrypted_key, b.encrypted_key);
}
#[test]
fn a_short_source_fails_closed() {
let enc = ContentEncAlg::A128GCM;
let cek = cek(&enc);
let mut rng = FixedRng::new([0x42u8; SEED_LEN - 1]);
let out = run(&key(2048), &enc, Some(&cek), &mut rng);
assert_eq!(out.err(), Some(Error::RandomnessUnavailable));
}
#[test]
fn a_modulus_under_2048_bits_is_an_invalid_key() {
let enc = ContentEncAlg::A128GCM;
let cek = cek(&enc);
for bits in [512, 1024, 2047] {
let mut rng = seed();
let out = run(&key(bits), &enc, Some(&cek), &mut rng);
assert!(matches!(out, Err(Error::InvalidKey(_))), "{bits}");
assert_eq!(rng.remaining(), SEED_LEN, "{bits}");
}
}
#[test]
fn a_malformed_public_key_is_an_invalid_key() {
let enc = ContentEncAlg::A128GCM;
let cek = cek(&enc);
let mut even = modulus(2048);
*even.last_mut().unwrap() &= 0xfe;
let cases = [
(even, F4.to_vec()),
(modulus(2048), vec![2]),
(modulus(2048), vec![1]),
(modulus(2048), Vec::new()),
(Vec::new(), F4.to_vec()),
];
for (n, e) in cases {
let key = RecipientKey::RsaPublic { n, e };
let out = run(&key, &enc, Some(&cek), &mut seed());
assert!(matches!(out, Err(Error::InvalidKey(_))), "{key:?}");
}
}
#[test]
fn a_missing_or_wrong_size_cek_is_an_invalid_key() {
let enc = ContentEncAlg::A128GCM;
assert!(matches!(
run(&key(2048), &enc, None, &mut seed()),
Err(Error::InvalidKey(_))
));
let long = Cek::from_slice(&[0x5a; 32]);
assert!(matches!(
run(&key(2048), &enc, Some(&long), &mut seed()),
Err(Error::InvalidKey(_))
));
}
#[test]
fn only_an_rsa_public_key_serves_and_only_rsa_oaep_256() {
let enc = ContentEncAlg::A128GCM;
let cek = cek(&enc);
let oct = RecipientKey::Symmetric(zeroize::Zeroizing::new(vec![0u8; 16]));
assert_eq!(
run(&oct, &enc, Some(&cek), &mut seed()).err(),
Some(Error::KeyAlgorithmMismatch)
);
for alg in [KeyMgmtAlg::from("RSA-OAEP"), KeyMgmtAlg::A128KW] {
let out = encrypt_key(
&alg,
&key(2048),
&enc,
Some(&cek),
&RecipientOptions::default(),
&mut seed(),
);
assert_eq!(out.err(), Some(Error::UnsupportedAlgorithm), "{alg}");
}
let unknown = ContentEncAlg::from("A128CTR");
assert_eq!(
run(&key(2048), &unknown, Some(&cek), &mut seed()).err(),
Some(Error::UnsupportedAlgorithm)
);
}
}