use aes::Aes128;
use ccm::{
aead::{Aead, AeadInPlace, KeyInit, Payload},
consts::{U13, U16},
Ccm, Key, Nonce,
};
use crate::error::{Error, Result};
type Aes128Ccm = Ccm<Aes128, U16, U13>;
pub const AEAD_KEY_LEN: usize = 16;
pub const AEAD_NONCE_LEN: usize = 13;
pub const AEAD_TAG_LEN: usize = 16;
pub fn encrypt(
key: &[u8; AEAD_KEY_LEN],
nonce: &[u8; AEAD_NONCE_LEN],
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>> {
SessionAead::new(key).encrypt(nonce, aad, plaintext)
}
pub fn decrypt(
key: &[u8; AEAD_KEY_LEN],
nonce: &[u8; AEAD_NONCE_LEN],
aad: &[u8],
ciphertext: &[u8],
) -> Result<Vec<u8>> {
SessionAead::new(key).decrypt(nonce, aad, ciphertext)
}
pub fn ctr_apply(
key: &[u8; AEAD_KEY_LEN],
nonce: &[u8; AEAD_NONCE_LEN],
data: &[u8],
) -> Result<Vec<u8>> {
SessionAead::new(key).ctr_apply(nonce, data)
}
pub struct SessionAead(Aes128Ccm);
impl SessionAead {
pub fn new(key: &[u8; AEAD_KEY_LEN]) -> Self {
let key_arr: Key<Aes128Ccm> = (*key).into();
Self(Aes128Ccm::new(&key_arr))
}
pub fn encrypt(
&self,
nonce: &[u8; AEAD_NONCE_LEN],
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>> {
let nonce_arr: Nonce<U13> = (*nonce).into();
self.0
.encrypt(
&nonce_arr,
Payload {
msg: plaintext,
aad,
},
)
.map_err(|_| Error::EncryptionFailed)
}
pub fn decrypt(
&self,
nonce: &[u8; AEAD_NONCE_LEN],
aad: &[u8],
ciphertext: &[u8],
) -> Result<Vec<u8>> {
let nonce_arr: Nonce<U13> = (*nonce).into();
self.0
.decrypt(
&nonce_arr,
Payload {
msg: ciphertext,
aad,
},
)
.map_err(|_| Error::EncryptedBlobDecryptionFailed)
}
pub fn encrypt_in_place(
&self,
nonce: &[u8; AEAD_NONCE_LEN],
aad: &[u8],
buf: &mut Vec<u8>,
) -> Result<()> {
let nonce_arr: Nonce<U13> = (*nonce).into();
self.0
.encrypt_in_place(&nonce_arr, aad, buf)
.map_err(|_| Error::EncryptionFailed)
}
pub fn decrypt_in_place(
&self,
nonce: &[u8; AEAD_NONCE_LEN],
aad: &[u8],
buf: &mut Vec<u8>,
) -> Result<()> {
let nonce_arr: Nonce<U13> = (*nonce).into();
self.0
.decrypt_in_place(&nonce_arr, aad, buf)
.map_err(|_| Error::EncryptedBlobDecryptionFailed)
}
pub fn ctr_apply(&self, nonce: &[u8; AEAD_NONCE_LEN], data: &[u8]) -> Result<Vec<u8>> {
let mut out = self.encrypt(nonce, &[], data)?;
out.truncate(data.len()); Ok(out)
}
}
impl core::fmt::Debug for SessionAead {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("SessionAead(<aes-128-ccm>)")
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
#[test]
fn ctr_apply_is_an_involution() {
let key = [0x42u8; AEAD_KEY_LEN];
let nonce = [0x17u8; AEAD_NONCE_LEN];
let data = b"obfuscate me please";
let once = ctr_apply(&key, &nonce, data).unwrap();
assert_ne!(&once[..], &data[..]);
let twice = ctr_apply(&key, &nonce, &once).unwrap();
assert_eq!(&twice[..], &data[..]);
}
#[test]
fn encrypt_decrypt_roundtrip() {
let key = [0x42u8; AEAD_KEY_LEN];
let nonce = [0x17u8; AEAD_NONCE_LEN];
let aad = b"matter aad";
let plaintext = b"the quick brown fox jumps over the lazy dog";
let ciphertext = encrypt(&key, &nonce, aad, plaintext).unwrap();
assert_eq!(ciphertext.len(), plaintext.len() + AEAD_TAG_LEN);
let decrypted = decrypt(&key, &nonce, aad, &ciphertext).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn tampered_ciphertext_rejected() {
let key = [0x42u8; AEAD_KEY_LEN];
let nonce = [0x17u8; AEAD_NONCE_LEN];
let mut ciphertext = encrypt(&key, &nonce, b"", b"payload").unwrap();
ciphertext[0] ^= 1;
assert!(decrypt(&key, &nonce, b"", &ciphertext).is_err());
}
#[test]
fn wrong_key_rejected() {
let key = [0x42u8; AEAD_KEY_LEN];
let bad_key = [0x43u8; AEAD_KEY_LEN];
let nonce = [0x17u8; AEAD_NONCE_LEN];
let ciphertext = encrypt(&key, &nonce, b"", b"payload").unwrap();
assert!(decrypt(&bad_key, &nonce, b"", &ciphertext).is_err());
}
#[test]
fn wrong_aad_rejected() {
let key = [0x42u8; AEAD_KEY_LEN];
let nonce = [0x17u8; AEAD_NONCE_LEN];
let ciphertext = encrypt(&key, &nonce, b"good aad", b"payload").unwrap();
assert!(decrypt(&key, &nonce, b"bad aad", &ciphertext).is_err());
}
#[test]
fn session_aead_matches_free_functions() {
let key = [0x42u8; AEAD_KEY_LEN];
let nonce = [0x17u8; AEAD_NONCE_LEN];
let aad = b"matter aad";
let plaintext = b"the quick brown fox jumps over the lazy dog";
let handle = SessionAead::new(&key);
let via_handle = handle.encrypt(&nonce, aad, plaintext).unwrap();
let via_free_fn = encrypt(&key, &nonce, aad, plaintext).unwrap();
assert_eq!(via_handle, via_free_fn);
let decrypted_by_handle = handle.decrypt(&nonce, aad, &via_free_fn).unwrap();
let decrypted_by_free_fn = decrypt(&key, &nonce, aad, &via_handle).unwrap();
assert_eq!(decrypted_by_handle, plaintext);
assert_eq!(decrypted_by_free_fn, plaintext);
let keystream_by_handle = handle.ctr_apply(&nonce, plaintext).unwrap();
let keystream_by_free_fn = ctr_apply(&key, &nonce, plaintext).unwrap();
assert_eq!(keystream_by_handle, keystream_by_free_fn);
}
#[test]
fn in_place_matches_vec_api() {
let key = [0x42u8; AEAD_KEY_LEN];
let nonce = [0x17u8; AEAD_NONCE_LEN];
let aad = b"matter aad";
let plaintext = b"the quick brown fox jumps over the lazy dog".to_vec();
let session = SessionAead::new(&key);
let expected_ct = session.encrypt(&nonce, aad, &plaintext).unwrap();
let mut buf = plaintext.clone();
session.encrypt_in_place(&nonce, aad, &mut buf).unwrap();
assert_eq!(buf, expected_ct);
session.decrypt_in_place(&nonce, aad, &mut buf).unwrap();
assert_eq!(buf, plaintext);
let mut tampered = expected_ct.clone();
tampered[0] ^= 1;
assert!(session
.decrypt_in_place(&nonce, aad, &mut tampered)
.is_err());
}
#[test]
fn decrypt_in_place_rejects_buffer_shorter_than_tag() {
let key = [0x42u8; AEAD_KEY_LEN];
let nonce = [0x17u8; AEAD_NONCE_LEN];
let session = SessionAead::new(&key);
let mut too_short = vec![0xAAu8; 4]; assert!(session
.decrypt_in_place(&nonce, b"aad", &mut too_short)
.is_err());
let mut one_short = vec![0xAAu8; AEAD_TAG_LEN - 1];
assert!(session
.decrypt_in_place(&nonce, b"aad", &mut one_short)
.is_err());
}
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn session_aead_is_send_and_sync() {
assert_send_sync::<SessionAead>();
}
#[test]
fn session_aead_debug_is_opaque() {
let key = [0x42u8; AEAD_KEY_LEN];
let session = SessionAead::new(&key);
assert_eq!(format!("{session:?}"), "SessionAead(<aes-128-ccm>)");
}
}