use subtle::ConstantTimeEq;
use super::gf2m_wide::{Gf2m128, Gf2m256, Gf2m512};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GcmError {
InvalidLength,
TagMismatch,
}
macro_rules! kalyna_gcm_variant {
($name:ident, $expanded:ident, $key_bytes:literal, $block_bytes:literal, $half_bytes:literal, $gf:ty) => {
#[doc = concat!(
"GCM mode over [`super::kalyna::", stringify!($expanded), "`] - see the module doc ",
"comment for the citation, the three AES-GCM divergences, and the misuse warning."
)]
pub struct $name {
key: super::kalyna::$expanded,
}
impl $name {
#[must_use]
pub fn new(key: &[u8; $key_bytes]) -> Self {
Self {
key: super::kalyna::$expanded::new(key),
}
}
fn apply_keystream(&self, iv: &[u8; $block_bytes], data: &[u8], out: &mut [u8]) {
let mut gamma_old = self.key.encrypt_block(iv);
let mut off = 0usize;
while off < data.len() {
let mut low = u64::from_le_bytes(gamma_old[..8].try_into().unwrap());
low = low.wrapping_add(1);
gamma_old[..8].copy_from_slice(&low.to_le_bytes());
let gamma = self.key.encrypt_block(&gamma_old);
let end = (off + $block_bytes).min(data.len());
for i in off..end {
out[i] = data[i] ^ gamma[i - off];
}
off += $block_bytes;
}
}
fn compute_tag(&self, aad: &[u8], ciphertext: &[u8]) -> [u8; $block_bytes] {
let h_key = <$gf>::from_le_bytes(&self.key.encrypt_block(&[0u8; $block_bytes]));
let mut acc = <$gf>::ZERO;
let mut off = 0usize;
while off < aad.len() {
let end = (off + $block_bytes).min(aad.len());
let mut block = [0u8; $block_bytes];
block[..end - off].copy_from_slice(&aad[off..end]);
acc = acc.add(<$gf>::from_le_bytes(&block)).multiply(h_key);
off += $block_bytes;
}
let ct_len = ciphertext.len();
let rem = ct_len % $block_bytes;
let padded_ct_len = if rem == 0 { ct_len } else { ct_len + ($block_bytes - rem) };
let mut off = 0usize;
while off < padded_ct_len {
let end = (off + $block_bytes).min(ct_len);
let mut block = [0u8; $block_bytes];
if end > off {
block[..end - off].copy_from_slice(&ciphertext[off..end]);
}
if rem != 0 && ct_len >= off && ct_len < off + $block_bytes {
block[ct_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 auth_len_bits = (aad.len() as u64) * 8;
#[allow(clippy::cast_possible_truncation)]
let padded_ct_len_bits = (padded_ct_len as u64) * 8;
length_block[..8].copy_from_slice(&auth_len_bits.to_le_bytes());
length_block[$half_bytes..$half_bytes + 8]
.copy_from_slice(&padded_ct_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];
}
self.key.encrypt_block(&combined)
}
pub fn encrypt(
&self,
iv: &[u8; $block_bytes],
aad: &[u8],
plaintext: &[u8],
ciphertext_out: &mut [u8],
) -> Result<[u8; $block_bytes], GcmError> {
if ciphertext_out.len() != plaintext.len() {
return Err(GcmError::InvalidLength);
}
self.apply_keystream(iv, plaintext, ciphertext_out);
Ok(self.compute_tag(aad, ciphertext_out))
}
pub fn decrypt(
&self,
iv: &[u8; $block_bytes],
aad: &[u8],
ciphertext: &[u8],
tag: &[u8],
plaintext_out: &mut [u8],
) -> Result<(), GcmError> {
if !(8..=$block_bytes).contains(&tag.len()) || plaintext_out.len() != ciphertext.len()
{
return Err(GcmError::InvalidLength);
}
let expected = self.compute_tag(aad, ciphertext);
if !bool::from(expected[..tag.len()].ct_eq(tag)) {
plaintext_out.fill(0);
return Err(GcmError::TagMismatch);
}
self.apply_keystream(iv, ciphertext, plaintext_out);
Ok(())
}
}
};
}
kalyna_gcm_variant!(
Kalyna128_128Gcm,
Kalyna128_128ExpandedKey,
16,
16,
8,
Gf2m128
);
kalyna_gcm_variant!(
Kalyna128_256Gcm,
Kalyna128_256ExpandedKey,
32,
16,
8,
Gf2m128
);
kalyna_gcm_variant!(
Kalyna256_256Gcm,
Kalyna256_256ExpandedKey,
32,
32,
16,
Gf2m256
);
kalyna_gcm_variant!(
Kalyna256_512Gcm,
Kalyna256_512ExpandedKey,
64,
32,
16,
Gf2m256
);
kalyna_gcm_variant!(
Kalyna512_512Gcm,
Kalyna512_512ExpandedKey,
64,
64,
32,
Gf2m512
);