use aes_kw::{AesKw, KeyInit};
use zeroize::Zeroizing;
use crate::algorithm::{AesKeySize, KeyWrapAlgorithm};
use crate::backend::{require_supported, Operation};
use crate::error::{Error, Result};
pub fn wrap(algorithm: KeyWrapAlgorithm, kek: &[u8], key_data: &[u8]) -> Result<Vec<u8>> {
require_supported(Operation::Wrap(algorithm))?;
match algorithm {
KeyWrapAlgorithm::AesKw(size) => aes_kw_wrap(size, kek, key_data),
#[cfg(feature = "legacy")]
KeyWrapAlgorithm::TripleDesKw => tdes_kw_wrap(kek, key_data),
}
}
pub fn unwrap(algorithm: KeyWrapAlgorithm, kek: &[u8], wrapped: &[u8]) -> Result<Vec<u8>> {
require_supported(Operation::Unwrap(algorithm))?;
match algorithm {
KeyWrapAlgorithm::AesKw(size) => aes_kw_unwrap(size, kek, wrapped),
#[cfg(feature = "legacy")]
KeyWrapAlgorithm::TripleDesKw => tdes_kw_unwrap(kek, wrapped),
}
}
fn aes_kw_wrap(size: AesKeySize, kek_bytes: &[u8], key_data: &[u8]) -> Result<Vec<u8>> {
let expected = size.key_len();
if kek_bytes.len() != expected {
return Err(Error::Crypto(format!(
"expected {expected} byte KEK, got {}",
kek_bytes.len()
)));
}
if key_data.len() < 16 || !key_data.len().is_multiple_of(8) {
return Err(Error::Crypto("invalid AES-KW input length".into()));
}
let mut out = vec![0u8; key_data.len() + 8];
macro_rules! do_wrap {
($aes:ty) => {{
let kek = AesKw::<$aes>::new_from_slice(kek_bytes)
.map_err(|e| Error::Crypto(format!("AES-KW init: {e}")))?;
kek.wrap_key(key_data, &mut out)
.map_err(|e| Error::Crypto(format!("AES-KW wrap: {e}")))?;
}};
}
match size {
AesKeySize::Aes128 => do_wrap!(aes::Aes128),
AesKeySize::Aes192 => do_wrap!(aes::Aes192),
AesKeySize::Aes256 => do_wrap!(aes::Aes256),
}
Ok(out)
}
fn aes_kw_unwrap(size: AesKeySize, kek_bytes: &[u8], wrapped: &[u8]) -> Result<Vec<u8>> {
let expected = size.key_len();
if kek_bytes.len() != expected {
return Err(Error::Crypto(format!(
"expected {expected} byte KEK, got {}",
kek_bytes.len()
)));
}
if wrapped.len() < 24 || !wrapped.len().is_multiple_of(8) {
return Err(Error::Crypto("invalid AES-KW input length".into()));
}
let mut out = Zeroizing::new(vec![0u8; wrapped.len() - 8]);
macro_rules! do_unwrap {
($aes:ty) => {{
let kek = AesKw::<$aes>::new_from_slice(kek_bytes)
.map_err(|e| Error::Crypto(format!("AES-KW init: {e}")))?;
kek.unwrap_key(wrapped, &mut out)
.map_err(|e| Error::Crypto(format!("AES-KW unwrap: {e}")))?;
}};
}
match size {
AesKeySize::Aes128 => do_unwrap!(aes::Aes128),
AesKeySize::Aes192 => do_unwrap!(aes::Aes192),
AesKeySize::Aes256 => do_unwrap!(aes::Aes256),
}
Ok(std::mem::take(&mut *out))
}
#[cfg(feature = "legacy")]
const TDES_KW_IV: [u8; 8] = [0x4a, 0xdd, 0xa2, 0x2c, 0x79, 0xe8, 0x21, 0x05];
#[cfg(feature = "legacy")]
fn tdes_kw_wrap(kek: &[u8], key_data: &[u8]) -> Result<Vec<u8>> {
if kek.len() != 24 {
return Err(Error::Crypto(format!(
"expected 24 byte 3DES KEK, got {}",
kek.len()
)));
}
use sha1::Digest;
let mut hasher = sha1::Sha1::new();
hasher.update(key_data);
let hash = hasher.finalize();
let checksum = &hash[..8];
let mut wkcks = Vec::with_capacity(key_data.len() + 8);
wkcks.extend_from_slice(key_data);
wkcks.extend_from_slice(checksum);
let mut iv = [0u8; 8];
crate::backend::fill_random(&mut iv)?;
let temp1 = tdes_cbc_encrypt(kek, &iv, &wkcks)?;
let mut temp2 = Vec::with_capacity(8 + temp1.len());
temp2.extend_from_slice(&iv);
temp2.extend_from_slice(&temp1);
temp2.reverse();
tdes_cbc_encrypt(kek, &TDES_KW_IV, &temp2)
}
#[cfg(feature = "legacy")]
fn tdes_kw_unwrap(kek: &[u8], wrapped: &[u8]) -> Result<Vec<u8>> {
if kek.len() != 24 {
return Err(Error::Crypto(format!(
"expected 24 byte 3DES KEK, got {}",
kek.len()
)));
}
if wrapped.len() < 16 {
return Err(Error::Crypto("3DES-KW wrapped data too short".into()));
}
let mut temp2 = Zeroizing::new(tdes_cbc_decrypt(kek, &TDES_KW_IV, wrapped)?);
temp2.reverse();
if temp2.len() < 8 {
return Err(Error::Crypto("3DES-KW unwrapped data too short".into()));
}
let iv: [u8; 8] = temp2[..8]
.try_into()
.map_err(|_| Error::Crypto("invalid IV length".into()))?;
let enc_data = &temp2[8..];
let wkcks = Zeroizing::new(tdes_cbc_decrypt(kek, &iv, enc_data)?);
if wkcks.len() < 8 {
return Err(Error::Crypto(
"3DES-KW: decrypted data too short for checksum".into(),
));
}
let key_data = &wkcks[..wkcks.len() - 8];
let checksum = &wkcks[wkcks.len() - 8..];
use sha1::Digest;
let mut hasher = sha1::Sha1::new();
hasher.update(key_data);
let hash = hasher.finalize();
if !crate::digest::constant_time_eq(checksum, &hash[..8]) {
return Err(Error::Crypto(
"3DES-KW: key checksum verification failed".into(),
));
}
Ok(key_data.to_vec())
}
#[cfg(feature = "legacy")]
fn tdes_cbc_encrypt(key: &[u8], iv: &[u8; 8], data: &[u8]) -> Result<Vec<u8>> {
use cbc::cipher::{BlockModeEncrypt, KeyIvInit};
type TdesCbcEnc = cbc::Encryptor<des::TdesEde3>;
let encryptor = TdesCbcEnc::new_from_slices(key, iv)
.map_err(|e| Error::Crypto(format!("3DES-CBC init: {e}")))?;
if !data.len().is_multiple_of(8) {
return Err(Error::Crypto("3DES-KW: data not block-aligned".into()));
}
let mut buf = data.to_vec();
encryptor
.encrypt_padded::<cbc::cipher::block_padding::NoPadding>(&mut buf, data.len())
.map_err(|e| Error::Crypto(format!("3DES-CBC encrypt: {e}")))?;
Ok(buf)
}
#[cfg(feature = "legacy")]
fn tdes_cbc_decrypt(key: &[u8], iv: &[u8; 8], data: &[u8]) -> Result<Vec<u8>> {
use cbc::cipher::{BlockModeDecrypt, KeyIvInit};
type TdesCbcDec = cbc::Decryptor<des::TdesEde3>;
let decryptor = TdesCbcDec::new_from_slices(key, iv)
.map_err(|e| Error::Crypto(format!("3DES-CBC init: {e}")))?;
let mut buf = data.to_vec();
decryptor
.decrypt_padded::<cbc::cipher::block_padding::NoPadding>(&mut buf)
.map_err(|e| Error::Crypto(format!("3DES-CBC decrypt: {e}")))?;
Ok(buf)
}
#[cfg(test)]
mod tests {
use super::*;
fn nist_aes_kw_vector(
size: AesKeySize,
plaintext: &[u8],
kek_bytes: &[u8],
expected_ct: &[u8],
) {
let algo = KeyWrapAlgorithm::AesKw(size);
let wrapped = wrap(algo, kek_bytes, plaintext).expect("wrap failed");
assert_eq!(wrapped, expected_ct, "wrap ciphertext mismatch");
let unwrapped = unwrap(algo, kek_bytes, expected_ct).expect("unwrap failed");
assert_eq!(unwrapped, plaintext, "unwrap plaintext mismatch");
}
#[test]
fn test_nist_aes128_kw_128bit_data() {
let kek = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap();
let pt = hex::decode("00112233445566778899AABBCCDDEEFF").unwrap();
let ct = hex::decode("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5").unwrap();
nist_aes_kw_vector(AesKeySize::Aes128, &pt, &kek, &ct);
}
#[test]
fn test_nist_aes192_kw_128bit_data() {
let kek = hex::decode("000102030405060708090A0B0C0D0E0F1011121314151617").unwrap();
let pt = hex::decode("00112233445566778899AABBCCDDEEFF").unwrap();
let ct = hex::decode("96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D").unwrap();
nist_aes_kw_vector(AesKeySize::Aes192, &pt, &kek, &ct);
}
#[test]
fn test_nist_aes256_kw_128bit_data() {
let kek = hex::decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")
.unwrap();
let pt = hex::decode("00112233445566778899AABBCCDDEEFF").unwrap();
let ct = hex::decode("64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7").unwrap();
nist_aes_kw_vector(AesKeySize::Aes256, &pt, &kek, &ct);
}
#[test]
fn test_nist_aes192_kw_192bit_data() {
let kek = hex::decode("000102030405060708090A0B0C0D0E0F1011121314151617").unwrap();
let pt = hex::decode("00112233445566778899AABBCCDDEEFF0001020304050607").unwrap();
let ct = hex::decode("031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2")
.unwrap();
nist_aes_kw_vector(AesKeySize::Aes192, &pt, &kek, &ct);
}
#[test]
fn test_nist_aes256_kw_256bit_data() {
let kek = hex::decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")
.unwrap();
let pt = hex::decode("00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F")
.unwrap();
let ct = hex::decode(
"28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21",
)
.unwrap();
nist_aes_kw_vector(AesKeySize::Aes256, &pt, &kek, &ct);
}
#[test]
fn test_aes_kw_wrong_kek_size() {
let result = wrap(
KeyWrapAlgorithm::AesKw(AesKeySize::Aes128),
&[0u8; 15],
&[0u8; 16],
);
assert!(result.is_err());
}
#[test]
fn test_aes_kw_rejects_short_or_unaligned_input() {
let kek = [0x42u8; 16];
let algo = KeyWrapAlgorithm::AesKw(AesKeySize::Aes128);
for len in [0, 8, 15, 17, 23] {
let err = wrap(algo, &kek, &vec![0u8; len]).unwrap_err();
assert!(
err.to_string().contains("invalid AES-KW input length"),
"wrap {len}: {err}"
);
}
for len in [0, 8, 16, 23, 25] {
let err = unwrap(algo, &kek, &vec![0u8; len]).unwrap_err();
assert!(
err.to_string().contains("invalid AES-KW input length"),
"unwrap {len}: {err}"
);
}
}
#[test]
fn test_aes_kw_integrity_check_failure() {
let kek = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap();
let pt = hex::decode("00112233445566778899AABBCCDDEEFF").unwrap();
let algo = KeyWrapAlgorithm::AesKw(AesKeySize::Aes128);
let mut wrapped = wrap(algo, &kek, &pt).unwrap();
wrapped[0] ^= 0xFF;
let result = unwrap(algo, &kek, &wrapped);
assert!(
result.is_err(),
"unwrap should fail for corrupted ciphertext"
);
}
#[test]
fn test_aes_kw_roundtrip_all_sizes() {
let kek_sizes: &[(AesKeySize, usize)] = &[
(AesKeySize::Aes128, 16),
(AesKeySize::Aes192, 24),
(AesKeySize::Aes256, 32),
];
let data_sizes = [16, 24, 32, 40, 48, 64, 128];
for &(size, kek_len) in kek_sizes {
let algo = KeyWrapAlgorithm::AesKw(size);
for &data_size in &data_sizes {
let kek: Vec<u8> = (0..kek_len).map(|i| (i * 7 + 3) as u8).collect();
let data: Vec<u8> = (0..data_size).map(|i| (i * 13 + 5) as u8).collect();
let wrapped = wrap(algo, &kek, &data).unwrap();
assert_eq!(
wrapped.len(),
data.len() + 8,
"ciphertext should be 8 bytes longer"
);
let unwrapped = unwrap(algo, &kek, &wrapped).unwrap();
assert_eq!(
unwrapped, data,
"roundtrip failed for kek={kek_len}, data={data_size}"
);
}
}
}
#[cfg(feature = "legacy")]
#[test]
fn test_tdes_key_wrap_roundtrip() {
let kek = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18";
let key_data = b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8";
let algo = KeyWrapAlgorithm::TripleDesKw;
let wrapped = wrap(algo, kek, key_data).expect("wrap");
let unwrapped = unwrap(algo, kek, &wrapped).expect("unwrap");
assert_eq!(unwrapped, key_data);
}
}