use crate::{Cmac, Error, Nonce, Tag, TagSize};
use aead::consts::U16;
use cipher::{
BlockCipherEncrypt, BlockSizeUser, Key, KeyInit, KeyIvInit, StreamCipher, array::Array,
typenum::Unsigned,
};
use cmac::Mac;
use core::{fmt, marker::PhantomData};
pub use Eax as EaxOnline;
pub trait CipherOp {}
#[derive(Clone, Copy, Debug)]
pub struct Encrypt;
impl CipherOp for Encrypt {}
#[derive(Clone, Copy, Debug)]
pub struct Decrypt;
impl CipherOp for Decrypt {}
pub struct Eax<Cipher, Op, M = U16>
where
Cipher: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + Clone + KeyInit,
Op: CipherOp,
M: TagSize,
{
imp: EaxImpl<Cipher, M>,
marker: PhantomData<Op>,
}
impl<Cipher, Op, M> Eax<Cipher, Op, M>
where
Cipher: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + Clone + KeyInit,
Op: CipherOp,
M: TagSize,
{
pub fn with_key_and_nonce(key: &Key<Cipher>, nonce: &Nonce<Cipher::BlockSize>) -> Self {
let imp = EaxImpl::<Cipher, M>::with_key_and_nonce(key, nonce);
Self {
imp,
marker: PhantomData,
}
}
#[inline]
pub fn update_assoc(&mut self, aad: &[u8]) {
self.imp.update_assoc(aad);
}
#[inline]
pub fn tag_clone(&self) -> Tag<M> {
self.imp.tag_clone()
}
}
impl<Cipher, M> Eax<Cipher, Encrypt, M>
where
Cipher: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + Clone + KeyInit,
M: TagSize,
{
#[inline]
pub fn encrypt(&mut self, msg: &mut [u8]) {
self.imp.encrypt(msg);
}
#[must_use = "tag must be saved to later verify decrypted data"]
#[inline]
pub fn finish(self) -> Tag<M> {
self.imp.tag()
}
}
impl<Cipher, M> Eax<Cipher, Decrypt, M>
where
Cipher: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + Clone + KeyInit,
M: TagSize,
{
#[inline]
pub fn decrypt_unauthenticated_hazmat(&mut self, msg: &mut [u8]) {
self.imp.decrypt(msg);
}
#[must_use = "decrypted data stream must be verified for authenticity"]
pub fn finish(self, expected: &Tag<M>) -> Result<(), Error> {
self.imp.verify_ct(expected)
}
}
impl<Cipher, Op, M> fmt::Debug for Eax<Cipher, Op, M>
where
Cipher: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + Clone + KeyInit,
Op: CipherOp,
M: TagSize,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("Eax").finish_non_exhaustive()
}
}
#[doc(hidden)]
struct EaxImpl<Cipher, M>
where
Cipher: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + Clone + KeyInit,
M: TagSize,
{
nonce: Nonce<Cipher::BlockSize>,
data: Cmac<Cipher>,
message: Cmac<Cipher>,
ctr: ctr::Ctr128BE<Cipher>,
_tag_size: PhantomData<M>,
}
impl<Cipher, M> EaxImpl<Cipher, M>
where
Cipher: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + Clone + KeyInit,
M: TagSize,
{
fn with_key_and_nonce(key: &Key<Cipher>, nonce: &Nonce<Cipher::BlockSize>) -> Self {
let prepend_cmac = |key, init_val, data| {
let mut cmac = <Cmac<Cipher> as KeyInit>::new(key);
cmac.update(&[0; 15]);
cmac.update(&[init_val]);
cmac.update(data);
cmac
};
let n = prepend_cmac(key, 0, nonce);
let n = n.finalize().into_bytes();
let h = prepend_cmac(key, 1, &[]);
let c = prepend_cmac(key, 2, &[]);
let cipher = ctr::Ctr128BE::<Cipher>::new(key, &n);
Self {
nonce: n,
data: h,
message: c,
ctr: cipher,
_tag_size: Default::default(),
}
}
#[inline]
pub fn update_assoc(&mut self, aad: &[u8]) {
self.data.update(aad);
}
#[inline]
fn encrypt(&mut self, msg: &mut [u8]) {
self.ctr.apply_keystream(msg);
self.message.update(msg);
}
#[inline]
fn decrypt(&mut self, msg: &mut [u8]) {
self.message.update(msg);
self.ctr.apply_keystream(msg);
}
#[inline]
fn tag(self) -> Tag<M> {
let h = self.data.finalize().into_bytes();
let c = self.message.finalize().into_bytes();
let full_tag: Array<_, Cipher::BlockSize> = self
.nonce
.into_iter()
.zip(h)
.map(|(a, b)| a ^ b)
.zip(c)
.map(|(a, b)| a ^ b)
.take(Cipher::BlockSize::to_usize())
.collect();
Tag::<M>::try_from(&full_tag[..M::to_usize()]).expect("tag size mismatch")
}
#[inline]
fn tag_clone(&self) -> Tag<M> {
let h = self.data.clone().finalize().into_bytes();
let c = self.message.clone().finalize().into_bytes();
let full_tag: Array<_, Cipher::BlockSize> = self
.nonce
.into_iter()
.zip(h)
.map(|(a, b)| a ^ b)
.zip(c)
.map(|(a, b)| a ^ b)
.take(Cipher::BlockSize::to_usize())
.collect();
Tag::<M>::try_from(&full_tag[..M::to_usize()]).expect("tag size mismatch")
}
fn verify_ct(self, expected: &Tag<M>) -> Result<(), Error> {
use subtle::ConstantTimeEq;
let resulting_tag = &self.tag()[..expected.len()];
if resulting_tag.ct_eq(expected).into() {
Ok(())
} else {
Err(Error)
}
}
}