use subtle::ConstantTimeEq;
use super::gf2m_wide::{Gf2m128, Gf2m256, Gf2m512};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GmacError {
InvalidLength,
TagMismatch,
}
macro_rules! kalyna_gmac_variant {
($name:ident, $expanded:ident, $key_bytes:literal, $block_bytes:literal, $gf:ty) => {
#[doc = concat!(
"GMAC over [`super::kalyna::", stringify!($expanded), "`] - see the module doc ",
"comment for the citation, the found reference bug, and the misuse warning."
)]
pub struct $name;
impl $name {
#[must_use]
pub fn mac(key: &[u8; $key_bytes], message: &[u8]) -> [u8; $block_bytes] {
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; $block_bytes] {
let h_key = <$gf>::from_le_bytes(&cipher.encrypt_block(&[0u8; $block_bytes]));
let msg_len = message.len();
let rem = msg_len % $block_bytes;
let padded_len = if rem == 0 {
msg_len
} else {
msg_len + ($block_bytes - rem)
};
let mut acc = <$gf>::ZERO;
let mut off = 0usize;
while off < padded_len {
let end = (off + $block_bytes).min(msg_len);
let mut block = [0u8; $block_bytes];
if end > off {
block[..end - off].copy_from_slice(&message[off..end]);
}
if rem != 0 && msg_len >= off && msg_len < off + $block_bytes {
block[msg_len - off] = 0x80;
}
acc = acc.add(<$gf>::from_le_bytes(&block)).multiply(h_key);
off += $block_bytes;
}
let mut length_block = [0u8; $block_bytes];
#[allow(clippy::cast_possible_truncation)] let padded_len_bits = (padded_len as u64) * 8;
length_block[..8].copy_from_slice(&padded_len_bits.to_le_bytes());
let acc_bytes = acc.to_le_bytes();
let mut combined = [0u8; $block_bytes];
for i in 0..$block_bytes {
combined[i] = length_block[i] ^ acc_bytes[i];
}
cipher.encrypt_block(&combined)
}
pub fn verify(
key: &[u8; $key_bytes],
message: &[u8],
tag: &[u8],
) -> Result<(), GmacError> {
let cipher = super::kalyna::$expanded::new(key);
Self::verify_with_cipher(&cipher, message, tag)
}
pub fn verify_with_cipher(
cipher: &super::kalyna::$expanded,
message: &[u8],
tag: &[u8],
) -> Result<(), GmacError> {
if !(8..=$block_bytes).contains(&tag.len()) {
return Err(GmacError::InvalidLength);
}
let expected = Self::mac_with_cipher(cipher, message);
if bool::from(expected[..tag.len()].ct_eq(tag)) {
Ok(())
} else {
Err(GmacError::TagMismatch)
}
}
}
};
}
kalyna_gmac_variant!(Kalyna128_128Gmac, Kalyna128_128ExpandedKey, 16, 16, Gf2m128);
kalyna_gmac_variant!(Kalyna128_256Gmac, Kalyna128_256ExpandedKey, 32, 16, Gf2m128);
kalyna_gmac_variant!(Kalyna256_256Gmac, Kalyna256_256ExpandedKey, 32, 32, Gf2m256);
kalyna_gmac_variant!(Kalyna256_512Gmac, Kalyna256_512ExpandedKey, 64, 32, Gf2m256);
kalyna_gmac_variant!(Kalyna512_512Gmac, Kalyna512_512ExpandedKey, 64, 64, Gf2m512);