use super::BlockCipher;
use crate::ct::ConstantTimeEq;
const RB: u8 = 0x87;
fn dbl(block: [u8; 16]) -> [u8; 16] {
let msb = block[0] >> 7;
let mut out = [0u8; 16];
let mut carry = 0u8;
for i in (0..16).rev() {
out[i] = (block[i] << 1) | carry;
carry = block[i] >> 7;
}
out[15] ^= RB & 0u8.wrapping_sub(msb);
out
}
#[derive(Clone)]
pub struct Cmac<C: BlockCipher> {
cipher: C,
k1: [u8; 16],
k2: [u8; 16],
state: [u8; 16],
pending: [u8; 16],
pending_len: usize,
}
impl<C: BlockCipher> Cmac<C> {
pub fn new(cipher: C) -> Self {
let mut l = [0u8; 16];
cipher.encrypt_block(&mut l);
let k1 = dbl(l);
let k2 = dbl(k1);
Cmac {
cipher,
k1,
k2,
state: [0u8; 16],
pending: [0u8; 16],
pending_len: 0,
}
}
fn absorb(&mut self, block: &[u8; 16]) {
for (s, b) in self.state.iter_mut().zip(block.iter()) {
*s ^= *b;
}
self.cipher.encrypt_block(&mut self.state);
}
pub fn update(&mut self, mut data: &[u8]) {
while !data.is_empty() {
if self.pending_len == 16 {
let block = self.pending;
self.absorb(&block);
self.pending_len = 0;
}
let n = core::cmp::min(16 - self.pending_len, data.len());
self.pending[self.pending_len..self.pending_len + n].copy_from_slice(&data[..n]);
self.pending_len += n;
data = &data[n..];
}
}
pub fn finalize(mut self) -> [u8; 16] {
let mut last = self.pending;
if self.pending_len == 16 {
for (l, k) in last.iter_mut().zip(self.k1.iter()) {
*l ^= *k;
}
} else {
last[self.pending_len] = 0x80;
for b in last.iter_mut().skip(self.pending_len + 1) {
*b = 0;
}
for (l, k) in last.iter_mut().zip(self.k2.iter()) {
*l ^= *k;
}
}
self.absorb(&last);
self.state
}
pub fn finalize_into(self, out: &mut [u8]) {
let tag = self.finalize();
let n = out.len().min(16);
out[..n].copy_from_slice(&tag[..n]);
}
pub fn verify(self, expected: &[u8]) -> bool {
if expected.len() > 16 {
return false;
}
let tag = self.finalize();
bool::from(tag[..expected.len()].ct_eq(expected))
}
}
impl<C: BlockCipher> Drop for Cmac<C> {
fn drop(&mut self) {
self.k1 = [0u8; 16];
self.k2 = [0u8; 16];
self.state = [0u8; 16];
self.pending = [0u8; 16];
let _ = core::hint::black_box(&self.k1);
let _ = core::hint::black_box(&self.k2);
let _ = core::hint::black_box(&self.state);
let _ = core::hint::black_box(&self.pending);
}
}
#[cfg(feature = "hash")]
impl<C: BlockCipher + Clone> crate::hash::Mac for Cmac<C> {
const OUTPUT_LEN: Option<usize> = Some(16);
fn update(&mut self, data: &[u8]) {
Cmac::update(self, data);
}
fn finalize_into(self, out: &mut [u8]) {
Cmac::finalize_into(self, out);
}
}
pub type AesCmac128 = Cmac<super::Aes128>;
pub type AesCmac256 = Cmac<super::Aes256>;
#[cfg(test)]
mod tests {
use super::*;
use crate::cipher::{Aes128, Aes256};
use crate::test_util::from_hex;
fn cmac128(key_hex: &str, msg: &[u8]) -> [u8; 16] {
let mut m = AesCmac128::new(Aes128::new(&from_hex::<16>(key_hex)));
m.update(msg);
m.finalize()
}
#[test]
fn rfc4493_subkeys() {
let key = from_hex::<16>("2b7e151628aed2a6abf7158809cf4f3c");
let m = AesCmac128::new(Aes128::new(&key));
assert_eq!(m.k1, from_hex::<16>("fbeed618357133667c85e08f7236a8de"));
assert_eq!(m.k2, from_hex::<16>("f7ddac306ae266ccf90bc11ee46d513b"));
}
#[test]
fn rfc4493_example1_empty() {
let tag = cmac128("2b7e151628aed2a6abf7158809cf4f3c", &[]);
assert_eq!(tag, from_hex::<16>("bb1d6929e95937287fa37d129b756746"));
}
#[test]
fn rfc4493_example2_one_block() {
let msg = from_hex::<16>("6bc1bee22e409f96e93d7e117393172a");
let tag = cmac128("2b7e151628aed2a6abf7158809cf4f3c", &msg);
assert_eq!(tag, from_hex::<16>("070a16b46b4d4144f79bdd9dd04a287c"));
}
#[test]
fn rfc4493_example3_partial() {
let msg = from_hex::<40>(
"6bc1bee22e409f96e93d7e117393172a\
ae2d8a571e03ac9c9eb76fac45af8e51\
30c81c46a35ce411",
);
let tag = cmac128("2b7e151628aed2a6abf7158809cf4f3c", &msg);
assert_eq!(tag, from_hex::<16>("dfa66747de9ae63030ca32611497c827"));
}
#[test]
fn rfc4493_example4_full() {
let msg = from_hex::<64>(
"6bc1bee22e409f96e93d7e117393172a\
ae2d8a571e03ac9c9eb76fac45af8e51\
30c81c46a35ce411e5fbc1191a0a52ef\
f69f2445df4f9b17ad2b417be66c3710",
);
let tag = cmac128("2b7e151628aed2a6abf7158809cf4f3c", &msg);
assert_eq!(tag, from_hex::<16>("51f0bebf7e3b9d92fc49741779363cfe"));
}
#[test]
fn nist_38b_aes256_empty() {
let key = from_hex::<32>(
"603deb1015ca71be2b73aef0857d7781\
1f352c073b6108d72d9810a30914dff4",
);
let mut m = AesCmac256::new(Aes256::new(&key));
m.update(&[]);
assert_eq!(
m.finalize(),
from_hex::<16>("028962f61b7bf89efc6b551f4667d983")
);
}
#[test]
fn nist_38b_aes256_partial() {
let key = from_hex::<32>(
"603deb1015ca71be2b73aef0857d7781\
1f352c073b6108d72d9810a30914dff4",
);
let msg = from_hex::<40>(
"6bc1bee22e409f96e93d7e117393172a\
ae2d8a571e03ac9c9eb76fac45af8e51\
30c81c46a35ce411",
);
let mut m = AesCmac256::new(Aes256::new(&key));
m.update(&msg);
assert_eq!(
m.finalize(),
from_hex::<16>("aaf3d8f1de5640c232f5b169b9c911e6")
);
}
#[test]
fn streaming_matches_oneshot() {
let key = "2b7e151628aed2a6abf7158809cf4f3c";
let msg = from_hex::<64>(
"6bc1bee22e409f96e93d7e117393172a\
ae2d8a571e03ac9c9eb76fac45af8e51\
30c81c46a35ce411e5fbc1191a0a52ef\
f69f2445df4f9b17ad2b417be66c3710",
);
let oneshot = cmac128(key, &msg);
let mut m = AesCmac128::new(Aes128::new(&from_hex::<16>(key)));
let mut start = 0;
for len in [1usize, 15, 16, 1, 31] {
m.update(&msg[start..start + len]);
start += len;
}
assert_eq!(m.finalize(), oneshot);
}
#[test]
fn verify_roundtrip() {
let key = from_hex::<16>("2b7e151628aed2a6abf7158809cf4f3c");
let msg = from_hex::<16>("6bc1bee22e409f96e93d7e117393172a");
let make = || {
let mut m = AesCmac128::new(Aes128::new(&key));
m.update(&msg);
m
};
let tag = make().finalize();
assert!(make().verify(&tag));
assert!(make().verify(&tag[..8]));
let mut bad = tag;
bad[0] ^= 1;
assert!(!make().verify(&bad));
assert!(!make().verify(&[0u8; 17]));
}
#[test]
fn mac_trait_verify() {
use crate::hash::Mac;
let key = from_hex::<16>("2b7e151628aed2a6abf7158809cf4f3c");
let msg = from_hex::<16>("6bc1bee22e409f96e93d7e117393172a");
let mut m = AesCmac128::new(Aes128::new(&key));
Mac::update(&mut m, &msg);
let mut tag = [0u8; 16];
Mac::finalize_into(m.clone(), &mut tag);
assert_eq!(tag, from_hex::<16>("070a16b46b4d4144f79bdd9dd04a287c"));
assert!(bool::from(Mac::verify(m.clone(), &tag)));
assert!(!bool::from(Mac::verify(m.clone(), &tag[..8])));
assert!(!bool::from(Mac::verify(m.clone(), &[])));
let mut long = [0u8; 17];
long[..16].copy_from_slice(&tag);
assert!(!bool::from(Mac::verify(m, &long)));
}
}