use super::selection::select_al;
use crate::{
algorithms::pad::i2osp_zeroizing,
errors::{Error, Result},
};
use alloc::vec::Vec;
use crypto_bigint::BoxedUint;
use digest::Digest;
use hmac::{Hmac, KeyInit, Mac};
use sha2::Sha256;
use zeroize::Zeroizing;
pub(crate) struct KeyDerivationKey(Zeroizing<[u8; 32]>);
impl KeyDerivationKey {
pub(crate) fn derive(d: &BoxedUint, k: usize, ciphertext: &[u8]) -> Result<Self> {
if k < 11 || ciphertext.len() != k {
return Err(Error::Decryption);
}
let d = i2osp_zeroizing(d.clone(), k);
let dh: Zeroizing<[u8; 32]> = Zeroizing::new(Sha256::digest(&*d).into());
let mut mac = Hmac::<Sha256>::new_from_slice(dh.as_ref()).map_err(|_| Error::Internal)?;
mac.update(ciphertext);
Ok(Self(Zeroizing::new(mac.finalize().into_bytes().into())))
}
pub(crate) fn alternative_message(&self, k: usize) -> Result<(Zeroizing<Vec<u8>>, u16)> {
let max_message_len = k
.checked_sub(11)
.and_then(|len| u16::try_from(len).ok())
.ok_or(Error::Decryption)?;
let cl = self.irprf(b"length", 256)?;
let am = self.irprf(b"message", k)?;
let al = select_al(&cl, max_message_len);
Ok((am, al))
}
fn irprf(&self, label: &[u8], length: usize) -> Result<Zeroizing<Vec<u8>>> {
let bit_length = length
.checked_mul(8)
.and_then(|bits| u16::try_from(bits).ok())
.ok_or(Error::Decryption)?
.to_be_bytes();
let mut output = Zeroizing::new(vec![0u8; length]);
for (i, chunk) in (0u16..).zip(output.chunks_mut(32)) {
let mut mac =
Hmac::<Sha256>::new_from_slice(self.0.as_ref()).map_err(|_| Error::Internal)?;
mac.update(&i.to_be_bytes());
mac.update(label);
mac.update(&bit_length);
let block: Zeroizing<[u8; 32]> = Zeroizing::new(mac.finalize().into_bytes().into());
chunk.copy_from_slice(&block[..chunk.len()]);
}
Ok(output)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn length_mask_boundary() {
let mut key = [0u8; 32];
key[31] = 2;
let kdk = KeyDerivationKey(Zeroizing::new(key));
assert_eq!(kdk.alternative_message(138).unwrap().1, 105);
assert_eq!(kdk.alternative_message(266).unwrap().1, 233);
}
#[test]
fn public_length_errors() {
let d = BoxedUint::from(1u64);
assert!(KeyDerivationKey::derive(&d, 10, &[0; 10]).is_err());
assert!(KeyDerivationKey::derive(&d, 11, &[0; 10]).is_err());
assert!(KeyDerivationKey::derive(&d, 11, &[0; 12]).is_err());
let kdk = KeyDerivationKey::derive(&d, 11, &[0; 11]).unwrap();
assert!(kdk.alternative_message(10).is_err());
assert_eq!(kdk.alternative_message(11).unwrap().1, 0);
assert!(kdk.irprf(b"message", 0).unwrap().is_empty());
assert_eq!(kdk.irprf(b"message", 8191).unwrap().len(), 8191);
assert!(kdk.irprf(b"message", 8192).is_err());
assert!(kdk.irprf(b"message", usize::MAX).is_err());
}
#[cfg(feature = "encoding")]
mod vectors {
use super::*;
use crate::{
RsaPrivateKey,
algorithms::{pad::os2ip, pkcs1v15::eme_pkcs1v15_decode, rsa::rsa_decrypt_and_check},
pkcs1v15::{DecryptingKey, Pkcs1v15Encrypt},
traits::{Decryptor, PaddingScheme, PublicKeyParts, RandomizedDecryptor},
};
use alloc::{collections::BTreeMap, string::String};
use pkcs8::DecodePrivateKey;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde::Deserialize;
#[derive(Deserialize)]
struct Section {
key_pem: String,
cases: Vec<Case>,
}
#[derive(Deserialize)]
struct Case {
id: String,
valid: bool,
ciphertext_hex: String,
expect: Expected,
}
#[derive(Deserialize)]
struct Expected {
#[serde(default)]
empty: bool,
ascii: Option<String>,
hex: Option<String>,
}
macro_rules! fixture {
($name:literal) => {
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/examples/pkcs1v15_cfrg_rsa_guidance/",
$name
))
};
}
#[rstest::rstest]
#[case("B.1")]
#[case("B.2")]
#[case("B.3")]
#[case("B.4")]
fn draft_10_vectors(#[case] section: &str) {
let data: BTreeMap<String, Section> =
serde_json::from_str(fixture!("test_cases.json")).unwrap();
let section = &data[section];
let pem = match section.key_pem.as_str() {
"k2048.pem" => fixture!("k2048.pem"),
"k2049.pem" => fixture!("k2049.pem"),
"k3072.pem" => fixture!("k3072.pem"),
"k4096.pem" => fixture!("k4096.pem"),
_ => panic!("unknown key fixture"),
};
let key = RsaPrivateKey::from_pkcs8_pem(pem).unwrap();
let k = key.size();
let typed_key = DecryptingKey::new(key.clone());
let mut rng = ChaCha8Rng::from_seed([42; 32]);
assert_eq!(section.cases.len(), 12);
for case in §ion.cases {
let ciphertext = hex::decode(&case.ciphertext_hex).unwrap();
assert_eq!(ciphertext.len(), k, "{}", case.id);
let c = os2ip(&ciphertext, key.n_bits_precision()).unwrap();
let m = rsa_decrypt_and_check::<ChaCha8Rng>(&key, None, &c).unwrap();
let em = i2osp_zeroizing(m, k);
let (check, _) = eme_pkcs1v15_decode(&em, k);
assert_eq!(!check.to_bool(), case.valid, "{}", case.id);
let expected = if case.expect.empty {
Vec::new()
} else if let Some(ascii) = &case.expect.ascii {
ascii.as_bytes().to_vec()
} else {
hex::decode(case.expect.hex.as_ref().unwrap()).unwrap()
};
let outputs = [
Pkcs1v15Encrypt
.decrypt::<ChaCha8Rng>(None, &key, &ciphertext)
.unwrap(),
key.decrypt(Pkcs1v15Encrypt, &ciphertext).unwrap(),
key.decrypt_blinded(&mut rng, Pkcs1v15Encrypt, &ciphertext)
.unwrap(),
typed_key.decrypt(&ciphertext).unwrap(),
typed_key.decrypt_with_rng(&mut rng, &ciphertext).unwrap(),
];
for got in outputs {
assert_eq!(got, expected, "{}", case.id);
}
}
}
}
}