use super::api::DecryptPolicy;
use super::content::{Cek, cek_len};
use super::key::RecipientKey;
use super::keymgmt::{KeyOutput, RecipientOptions};
use super::random::RandomSource;
use crate::header::Header;
use crate::json::Object;
use crate::jwa::{ContentEncAlg, KeyMgmtAlg};
use crate::{Error, Result};
const DIR_KEY_SIZE: &str = "dir key length must equal the enc key length";
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::Dir {
return Err(Error::UnsupportedAlgorithm);
}
let RecipientKey::Symmetric(k) = key else {
return Err(Error::KeyAlgorithmMismatch);
};
if k.len() != cek_len(enc)? {
return Err(Error::InvalidKey(DIR_KEY_SIZE));
}
if cek.is_some() {
return Err(Error::InvalidKey("dir takes no random CEK"));
}
Ok(KeyOutput {
encrypted_key: Vec::new(),
header_params: Object::new(),
cek: Some(Cek::from_slice(k)),
})
}
pub(super) fn decrypt_key(
alg: &KeyMgmtAlg,
key: &RecipientKey,
enc: &ContentEncAlg,
_header: &Header,
encrypted_key: &[u8],
_policy: &DecryptPolicy,
) -> Result<Cek> {
let RecipientKey::Symmetric(k) = key else {
return Err(Error::DecryptionFailed);
};
let usable = *alg == KeyMgmtAlg::Dir
&& encrypted_key.is_empty()
&& cek_len(enc).is_ok_and(|len| len == k.len());
if usable {
Ok(Cek::from_slice(k))
} else {
Err(Error::DecryptionFailed)
}
}
#[cfg(test)]
mod tests {
use zeroize::Zeroizing;
use super::*;
use crate::rng::insecure_test_vectors::FixedRng;
fn sym(len: usize) -> RecipientKey {
RecipientKey::Symmetric(Zeroizing::new((0..len).map(|i| i as u8).collect()))
}
fn policy() -> DecryptPolicy {
DecryptPolicy::new(&[KeyMgmtAlg::Dir], &[ContentEncAlg::A128GCM])
.unwrap_or_else(|e| panic!("{e}"))
}
#[test]
fn encrypt_uses_the_key_as_the_cek_with_an_empty_encrypted_key() {
let mut rng = FixedRng::new(Vec::new());
let options = RecipientOptions::default();
for enc in ContentEncAlg::KNOWN {
let len = cek_len(enc).unwrap_or_else(|e| panic!("{e}"));
let out = encrypt_key(&KeyMgmtAlg::Dir, &sym(len), enc, None, &options, &mut rng)
.unwrap_or_else(|e| panic!("{enc}: {e}"));
assert!(out.encrypted_key.is_empty(), "{enc}");
assert!(out.header_params.is_empty(), "{enc}");
let cek = out.cek.unwrap_or_else(|| panic!("{enc}: no CEK"));
assert_eq!(cek.len(), len, "{enc}");
assert_eq!(cek.as_bytes()[len - 1], (len - 1) as u8, "{enc}");
}
}
#[test]
fn encrypt_rejects_a_key_of_the_wrong_length_or_kind() {
let mut rng = FixedRng::new(Vec::new());
let options = RecipientOptions::default();
let enc = ContentEncAlg::A128GCM;
let dir = KeyMgmtAlg::Dir;
for len in [0, 15, 17, 32] {
let result = encrypt_key(&dir, &sym(len), &enc, None, &options, &mut rng);
assert_eq!(result.map(|_| ()), Err(Error::InvalidKey(DIR_KEY_SIZE)));
}
let password = RecipientKey::Password(Zeroizing::new(vec![0; 16]));
let result = encrypt_key(&dir, &password, &enc, None, &options, &mut rng);
assert_eq!(result.map(|_| ()), Err(Error::KeyAlgorithmMismatch));
let cek = Cek::from_slice(&[0; 16]);
let result = encrypt_key(&dir, &sym(16), &enc, Some(&cek), &options, &mut rng);
assert!(matches!(result, Err(Error::InvalidKey(_))));
let result = encrypt_key(
&KeyMgmtAlg::A128KW,
&sym(16),
&enc,
None,
&options,
&mut rng,
);
assert_eq!(result.map(|_| ()), Err(Error::UnsupportedAlgorithm));
let unknown = ContentEncAlg::from("A128CTR");
let result = encrypt_key(&dir, &sym(16), &unknown, None, &options, &mut rng);
assert_eq!(result.map(|_| ()), Err(Error::UnsupportedAlgorithm));
}
#[test]
fn decrypt_returns_the_key_as_the_cek() {
let header = Header::new();
for enc in ContentEncAlg::KNOWN {
let len = cek_len(enc).unwrap_or_else(|e| panic!("{e}"));
let cek = decrypt_key(&KeyMgmtAlg::Dir, &sym(len), enc, &header, &[], &policy())
.unwrap_or_else(|e| panic!("{enc}: {e}"));
assert_eq!(cek.len(), len, "{enc}");
}
}
#[test]
fn every_decrypt_failure_is_decryption_failed() {
let header = Header::new();
let p = policy();
let enc = ContentEncAlg::A128GCM;
let dir = KeyMgmtAlg::Dir;
let password = RecipientKey::Password(Zeroizing::new(vec![0; 16]));
let unknown = ContentEncAlg::from("A128CTR");
let cases: [(&KeyMgmtAlg, &RecipientKey, &ContentEncAlg, &[u8]); 6] = [
(&dir, &sym(16), &enc, &[0]), (&dir, &sym(16), &enc, &[0; 24]), (&dir, &sym(32), &enc, &[]), (&dir, &password, &enc, &[]), (&dir, &sym(16), &unknown, &[]), (&KeyMgmtAlg::A128KW, &sym(16), &enc, &[]), ];
for (alg, key, enc, encrypted_key) in cases {
let result = decrypt_key(alg, key, enc, &header, encrypted_key, &p);
assert_eq!(result.map(|_| ()), Err(Error::DecryptionFailed));
}
}
}