use aes::Aes128;
use aes_gcm::aead::AeadInPlace;
use aes_gcm::Aes128Gcm;
use ccm::consts::{U11, U8};
use ccm::Ccm;
use chacha20poly1305::ChaCha20Poly1305;
use cipher::generic_array::GenericArray;
use cipher::KeyInit;
use crate::{CrafterError, Result};
const AES128_KEY_LEN: usize = 16;
const CHACHA20_KEY_LEN: usize = 32;
const SALT_LEN_4: usize = 4;
const SALT_LEN_3: usize = 3;
const IV_LEN: usize = 8;
const NONCE_LEN_12: usize = SALT_LEN_4 + IV_LEN;
const NONCE_LEN_11: usize = SALT_LEN_3 + IV_LEN;
const ICV_LEN_16: usize = 16;
const ICV_LEN_8: usize = 8;
type AesCcm8 = Ccm<Aes128, U8, U11>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AeadTransform {
AesGcm16,
AesCcm8,
ChaCha20Poly1305,
}
impl AeadTransform {
pub const fn key_len(self) -> usize {
match self {
Self::AesGcm16 => AES128_KEY_LEN, Self::AesCcm8 => AES128_KEY_LEN, Self::ChaCha20Poly1305 => CHACHA20_KEY_LEN, }
}
pub const fn salt_len(self) -> usize {
match self {
Self::AesGcm16 => SALT_LEN_4, Self::AesCcm8 => SALT_LEN_3, Self::ChaCha20Poly1305 => SALT_LEN_4, }
}
pub const fn iv_len(self) -> usize {
IV_LEN
}
pub const fn nonce_len(self) -> usize {
match self {
Self::AesGcm16 => NONCE_LEN_12,
Self::AesCcm8 => NONCE_LEN_11,
Self::ChaCha20Poly1305 => NONCE_LEN_12,
}
}
pub const fn icv_len(self) -> usize {
match self {
Self::AesGcm16 => ICV_LEN_16, Self::AesCcm8 => ICV_LEN_8, Self::ChaCha20Poly1305 => ICV_LEN_16, }
}
pub fn seal(
self,
key: &[u8],
nonce: &[u8],
aad: &[u8],
plaintext: &[u8],
) -> Result<(Vec<u8>, Vec<u8>)> {
self.check_shapes(key, nonce)?;
match self {
Self::AesGcm16 => {
let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Self::key_err(self))?;
let mut buf = plaintext.to_vec();
let tag = cipher
.encrypt_in_place_detached(GenericArray::from_slice(nonce), aad, &mut buf)
.map_err(|_| Self::seal_err(self))?;
Ok((buf, tag.to_vec()))
}
Self::AesCcm8 => {
let cipher = AesCcm8::new_from_slice(key).map_err(|_| Self::key_err(self))?;
let mut buf = plaintext.to_vec();
let tag = cipher
.encrypt_in_place_detached(GenericArray::from_slice(nonce), aad, &mut buf)
.map_err(|_| Self::seal_err(self))?;
Ok((buf, tag.to_vec()))
}
Self::ChaCha20Poly1305 => {
let cipher =
ChaCha20Poly1305::new_from_slice(key).map_err(|_| Self::key_err(self))?;
let mut buf = plaintext.to_vec();
let tag = cipher
.encrypt_in_place_detached(GenericArray::from_slice(nonce), aad, &mut buf)
.map_err(|_| Self::seal_err(self))?;
Ok((buf, tag.to_vec()))
}
}
}
pub fn open(
self,
key: &[u8],
nonce: &[u8],
aad: &[u8],
ciphertext: &[u8],
tag: &[u8],
) -> Result<Vec<u8>> {
self.check_shapes(key, nonce)?;
if tag.len() != self.icv_len() {
return Err(Self::tag_len_err(self));
}
match self {
Self::AesGcm16 => {
let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Self::key_err(self))?;
let mut buf = ciphertext.to_vec();
cipher
.decrypt_in_place_detached(
GenericArray::from_slice(nonce),
aad,
&mut buf,
GenericArray::from_slice(tag),
)
.map_err(|_| Self::integrity_err(self))?;
Ok(buf)
}
Self::AesCcm8 => {
let cipher = AesCcm8::new_from_slice(key).map_err(|_| Self::key_err(self))?;
let mut buf = ciphertext.to_vec();
cipher
.decrypt_in_place_detached(
GenericArray::from_slice(nonce),
aad,
&mut buf,
GenericArray::from_slice(tag),
)
.map_err(|_| Self::integrity_err(self))?;
Ok(buf)
}
Self::ChaCha20Poly1305 => {
let cipher =
ChaCha20Poly1305::new_from_slice(key).map_err(|_| Self::key_err(self))?;
let mut buf = ciphertext.to_vec();
cipher
.decrypt_in_place_detached(
GenericArray::from_slice(nonce),
aad,
&mut buf,
GenericArray::from_slice(tag),
)
.map_err(|_| Self::integrity_err(self))?;
Ok(buf)
}
}
}
fn check_shapes(self, key: &[u8], nonce: &[u8]) -> Result<()> {
if key.len() != self.key_len() {
return Err(Self::key_err(self));
}
if nonce.len() != self.nonce_len() {
return Err(Self::nonce_err(self));
}
Ok(())
}
const fn key_err(self) -> CrafterError {
CrafterError::invalid_field_value(
"ipsec.aead.key",
"AEAD key length does not match the transform",
)
}
const fn nonce_err(self) -> CrafterError {
CrafterError::invalid_field_value(
"ipsec.aead.nonce",
"AEAD nonce length does not match salt||IV for the transform",
)
}
const fn tag_len_err(self) -> CrafterError {
CrafterError::invalid_field_value(
"ipsec.aead.tag",
"AEAD tag length does not match the transform ICV",
)
}
const fn seal_err(self) -> CrafterError {
CrafterError::invalid_field_value("ipsec.aead.seal", "AEAD encryption failed")
}
const fn integrity_err(self) -> CrafterError {
CrafterError::invalid_field_value(
"ipsec.aead.icv",
"AEAD integrity check failed: tag did not verify",
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hex(s: &str) -> Vec<u8> {
let s: String = s.chars().filter(|c| !c.is_whitespace()).collect();
assert!(s.len() % 2 == 0, "hex string must have even length");
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).expect("valid hex"))
.collect()
}
#[test]
fn aes_gcm16_gcm_appendix_b_case3_no_aad() {
let key = hex("feffe9928665731c6d6a8f9467308308");
let nonce = hex("cafebabefacedbaddecaf888"); let plaintext = hex("d9313225f88406e5a55909c5aff5269a\
86a7a9531534f7da2e4c303d8a318a72\
1c3c0c95956809532fcf0e2449a6b525\
b16aedf5aa0de657ba637b391aafd255");
let expected_ct = hex("42831ec2217774244b7221b784d0d49c\
e3aa212f2c02a4e035c17e2329aca12e\
21d514b25466931c7d8f6a5aac84aa05\
1ba30b396a0aac973d58e091473f5985");
let expected_tag = hex("4d5c2af327cd64a62cf35abd2ba6fab4");
let t = AeadTransform::AesGcm16;
assert_eq!(t.key_len(), 16);
assert_eq!(t.salt_len(), 4);
assert_eq!(t.iv_len(), 8);
assert_eq!(t.nonce_len(), 12);
assert_eq!(t.icv_len(), 16);
let (ct, tag) = t.seal(&key, &nonce, &[], &plaintext).unwrap();
assert_eq!(ct, expected_ct);
assert_eq!(tag, expected_tag);
let pt = t.open(&key, &nonce, &[], &ct, &tag).unwrap();
assert_eq!(pt, plaintext);
}
#[test]
fn aes_gcm16_gcm_appendix_b_case4_with_aad() {
let key = hex("feffe9928665731c6d6a8f9467308308");
let nonce = hex("cafebabefacedbaddecaf888");
let aad = hex("feedfacedeadbeeffeedfacedeadbeefabaddad2");
let plaintext = hex("d9313225f88406e5a55909c5aff5269a\
86a7a9531534f7da2e4c303d8a318a72\
1c3c0c95956809532fcf0e2449a6b525\
b16aedf5aa0de657ba637b39");
let expected_ct = hex("42831ec2217774244b7221b784d0d49c\
e3aa212f2c02a4e035c17e2329aca12e\
21d514b25466931c7d8f6a5aac84aa05\
1ba30b396a0aac973d58e091");
let expected_tag = hex("5bc94fbc3221a5db94fae95ae7121a47");
let t = AeadTransform::AesGcm16;
let (ct, tag) = t.seal(&key, &nonce, &aad, &plaintext).unwrap();
assert_eq!(ct, expected_ct);
assert_eq!(tag, expected_tag);
let pt = t.open(&key, &nonce, &aad, &ct, &tag).unwrap();
assert_eq!(pt, plaintext);
let mut bad_aad = aad.clone();
bad_aad[0] ^= 0x01;
assert!(t.open(&key, &nonce, &bad_aad, &ct, &tag).is_err());
}
#[test]
fn aes_gcm16_tag_tamper_fails_open() {
let key = hex("feffe9928665731c6d6a8f9467308308");
let nonce = hex("cafebabefacedbaddecaf888");
let plaintext = b"ESP AEAD plaintext block";
let t = AeadTransform::AesGcm16;
let (ct, mut tag) = t.seal(&key, &nonce, &[], plaintext).unwrap();
assert!(t.open(&key, &nonce, &[], &ct, &tag).is_ok());
tag[0] ^= 0x01;
let err = t.open(&key, &nonce, &[], &ct, &tag).unwrap_err();
match err {
CrafterError::InvalidFieldValue { field, .. } => {
assert_eq!(field, "ipsec.aead.icv");
}
other => panic!("expected integrity error, got {other:?}"),
}
}
#[test]
fn chacha20poly1305_rfc7634_appendix_a() {
let key = hex("808182838485868788898a8b8c8d8e8f\
909192939495969798999a9b9c9d9e9f");
let salt = hex("a0a1a2a3");
let iv = hex("1011121314151617");
let mut nonce = salt.clone();
nonce.extend_from_slice(&iv);
let aad = hex("0102030400000005");
let plaintext = hex("45000054a6f2000040 01e778c63364 05c0000205 08005b7a\
3a0800005 53bec1000073627\
08090a0b0c0d0e0f1011121314151617\
18191a1b1c1d1e1f2021222324252627\
28292a2b2c2d2e2f3031323334353637\
0102020 4");
let expected_ct = hex("2403942 8b97f417e3c13753a4f05087b\
67c352e6a7fab1b982d466ef407ae5c6\
14ee8099d52844eb61aa95dfab4c02f7\
2aa71e7c4c4f64c9befe2facc638e8f3\
cbec163fac469b502773f6fb94e664da\
9165b82829f641e0");
let expected_tag = hex("76aaa8266b7fb0f7b11b369907e1ad43");
let t = AeadTransform::ChaCha20Poly1305;
assert_eq!(t.key_len(), 32);
assert_eq!(t.salt_len(), 4);
assert_eq!(t.iv_len(), 8);
assert_eq!(t.nonce_len(), 12);
assert_eq!(t.icv_len(), 16);
assert_eq!(nonce.len(), 12);
assert_eq!(plaintext.len(), 88);
let (ct, tag) = t.seal(&key, &nonce, &aad, &plaintext).unwrap();
assert_eq!(ct, expected_ct);
assert_eq!(tag, expected_tag);
let pt = t.open(&key, &nonce, &aad, &ct, &tag).unwrap();
assert_eq!(pt, plaintext);
let mut bad_tag = tag.clone();
bad_tag[15] ^= 0x80;
assert!(t.open(&key, &nonce, &aad, &ct, &bad_tag).is_err());
let mut bad_ct = ct.clone();
bad_ct[0] ^= 0x01;
assert!(t.open(&key, &nonce, &aad, &bad_ct, &tag).is_err());
}
#[test]
fn aes_ccm8_rfc3610_packet_vector1_via_ccm_crate() {
use ccm::aead::AeadInPlace as CcmAeadInPlace;
use ccm::consts::{U13, U8 as CcmU8};
use ccm::{Ccm, KeyInit as CcmKeyInit};
type AesCcm8L2 = Ccm<Aes128, CcmU8, U13>;
let key = hex("c0c1c2c3c4c5c6c7c8c9cacbcccdcecf");
let nonce = hex("00000003020100a0a1a2a3a4a5");
let aad = hex("0001020304050607");
let plaintext = hex("08090a0b0c0d0e0f101112131415161718191a1b1c1d1e");
let expected_ct = hex("588c979a61c663d2f066d0c2c0f989806d5f6b61dac384");
let expected_tag = hex("17e8d12cfdf926e0");
let cipher = <AesCcm8L2 as CcmKeyInit>::new_from_slice(&key).unwrap();
let mut buf = plaintext.clone();
let tag = cipher
.encrypt_in_place_detached(GenericArray::from_slice(&nonce), &aad, &mut buf)
.unwrap();
assert_eq!(buf, expected_ct);
assert_eq!(tag.to_vec(), expected_tag);
let mut dec = expected_ct.clone();
cipher
.decrypt_in_place_detached(
GenericArray::from_slice(&nonce),
&aad,
&mut dec,
GenericArray::from_slice(&expected_tag),
)
.unwrap();
assert_eq!(dec, plaintext);
}
#[test]
fn aes_ccm8_rfc4309_roundtrip_and_tamper() {
let t = AeadTransform::AesCcm8;
assert_eq!(t.key_len(), 16);
assert_eq!(t.salt_len(), 3); assert_eq!(t.iv_len(), 8);
assert_eq!(t.nonce_len(), 11); assert_eq!(t.icv_len(), 8);
let key = hex("c0c1c2c3c4c5c6c7c8c9cacbcccdcecf");
let nonce = hex("a0a1a20011223344556677");
assert_eq!(nonce.len(), t.nonce_len());
let aad = hex("0102030400000007"); let plaintext = b"ESP AES-CCM-8 payload, RFC 4309";
let (ct, tag) = t.seal(&key, &nonce, &aad, plaintext).unwrap();
assert_eq!(tag.len(), 8);
assert_eq!(ct.len(), plaintext.len());
let pt = t.open(&key, &nonce, &aad, &ct, &tag).unwrap();
assert_eq!(pt, plaintext);
let mut bad_tag = tag.clone();
bad_tag[0] ^= 0x01;
let err = t.open(&key, &nonce, &aad, &ct, &bad_tag).unwrap_err();
match err {
CrafterError::InvalidFieldValue { field, .. } => {
assert_eq!(field, "ipsec.aead.icv");
}
other => panic!("expected integrity error, got {other:?}"),
}
let mut bad_ct = ct.clone();
bad_ct[0] ^= 0x01;
assert!(t.open(&key, &nonce, &aad, &bad_ct, &tag).is_err());
}
#[test]
fn aead_shape_guards() {
let t = AeadTransform::AesGcm16;
let key = hex("feffe9928665731c6d6a8f9467308308");
let nonce = hex("cafebabefacedbaddecaf888");
assert!(t.seal(&hex("0011223344"), &nonce, &[], b"x").is_err());
assert!(t.seal(&key, &hex("00112233"), &[], b"x").is_err());
let (ct, _tag) = t.seal(&key, &nonce, &[], b"x").unwrap();
assert!(t.open(&key, &nonce, &[], &ct, &hex("0011")).is_err());
}
}