use subtle::ConstantTimeEq;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CmacError {
TagMismatch,
}
macro_rules! kalyna_cmac_variant {
($name:ident, $expanded:ident, $key_bytes:literal, $block_bytes:literal) => {
#[doc = concat!(
"CMAC over [`super::kalyna::", stringify!($expanded), "`] - see the module doc ",
"comment for the citation and the misuse warning."
)]
pub struct $name;
impl $name {
#[must_use]
pub fn mac(key: &[u8; $key_bytes], message: &[u8]) -> [u8; 16] {
let cipher = super::kalyna::$expanded::new(key);
Self::mac_with_cipher(&cipher, message)
}
#[must_use]
pub fn mac_with_cipher(cipher: &super::kalyna::$expanded, message: &[u8]) -> [u8; 16] {
let len = message.len();
let chain_len = if len == 0 {
0
} else if len % $block_bytes == 0 {
len - $block_bytes
} else {
(len / $block_bytes) * $block_bytes
};
let tail = &message[chain_len..];
let mut state = [0u8; $block_bytes];
for block in message[..chain_len].chunks_exact($block_bytes) {
for (s, b) in state.iter_mut().zip(block) {
*s ^= *b;
}
state = cipher.encrypt_block(&state);
}
let mut last_block = [0u8; $block_bytes];
last_block[..tail.len()].copy_from_slice(tail);
if tail.len() % $block_bytes != 0 {
last_block[tail.len()] = 0x80;
}
let mut rkey = [0u8; $block_bytes];
rkey[0] = u8::from(tail.len() % $block_bytes != 0);
let rkey = cipher.encrypt_block(&rkey);
for (s, (l, r)) in state.iter_mut().zip(last_block.iter().zip(rkey.iter())) {
*s ^= *l ^ *r;
}
let tag_block = cipher.encrypt_block(&state);
let mut tag = [0u8; 16];
tag.copy_from_slice(&tag_block[..16]);
tag
}
pub fn verify(
key: &[u8; $key_bytes],
message: &[u8],
expected: &[u8; 16],
) -> Result<(), CmacError> {
let cipher = super::kalyna::$expanded::new(key);
Self::verify_with_cipher(&cipher, message, expected)
}
pub fn verify_with_cipher(
cipher: &super::kalyna::$expanded,
message: &[u8],
expected: &[u8; 16],
) -> Result<(), CmacError> {
let tag = Self::mac_with_cipher(cipher, message);
if tag.ct_eq(expected).into() {
Ok(())
} else {
Err(CmacError::TagMismatch)
}
}
}
};
}
kalyna_cmac_variant!(Kalyna128_128Cmac, Kalyna128_128ExpandedKey, 16, 16);
kalyna_cmac_variant!(Kalyna128_256Cmac, Kalyna128_256ExpandedKey, 32, 16);
kalyna_cmac_variant!(Kalyna256_256Cmac, Kalyna256_256ExpandedKey, 32, 32);
kalyna_cmac_variant!(Kalyna256_512Cmac, Kalyna256_512ExpandedKey, 64, 32);
kalyna_cmac_variant!(Kalyna512_512Cmac, Kalyna512_512ExpandedKey, 64, 64);