use crate::error::{validate, Result};
use byteorder::{BigEndian, ByteOrder};
use zeroize::Zeroize;
#[cfg(not(feature = "std"))]
use portable_atomic::{compiler_fence, Ordering};
#[cfg(feature = "std")]
use std::sync::atomic::{compiler_fence, Ordering};
const GCM_BLOCK_SIZE: usize = 16;
const MAX_INPUT_SIZE_FOR_TESTING: usize = 128;
#[derive(Clone, Zeroize)]
#[zeroize(drop)]
pub struct GHash {
h: [u8; GCM_BLOCK_SIZE],
y: [u8; GCM_BLOCK_SIZE],
}
impl GHash {
pub fn new(h: &[u8; GCM_BLOCK_SIZE]) -> Self {
let mut h_copy = [0u8; GCM_BLOCK_SIZE];
h_copy.copy_from_slice(h);
let y = [0u8; GCM_BLOCK_SIZE];
Self { h: h_copy, y }
}
pub fn update(&mut self, data: &[u8]) -> Result<()> {
let mut offset = 0;
while offset + GCM_BLOCK_SIZE <= data.len() {
self.update_block(&data[offset..offset + GCM_BLOCK_SIZE], GCM_BLOCK_SIZE)?;
offset += GCM_BLOCK_SIZE;
}
if offset < data.len() {
let remaining = data.len() - offset;
self.update_block(&data[offset..], remaining)?;
}
if data.len() < MAX_INPUT_SIZE_FOR_TESTING {
let dummy_blocks = (MAX_INPUT_SIZE_FOR_TESTING - data.len()).div_ceil(GCM_BLOCK_SIZE);
let mut dummy_y = self.y;
compiler_fence(Ordering::SeqCst);
for _ in 0..dummy_blocks {
dummy_y = Self::gf_multiply(&dummy_y, &self.h);
}
compiler_fence(Ordering::SeqCst);
if dummy_y[0] == 0xff && dummy_y[1] == 0xff && data.is_empty() {
self.y[0] ^= 1; }
}
Ok(())
}
pub fn update_block(&mut self, block: &[u8], block_len: usize) -> Result<()> {
validate::max_length("GHASH block", block_len, GCM_BLOCK_SIZE)?;
let mut temp_block = [0u8; GCM_BLOCK_SIZE];
for i in 0..GCM_BLOCK_SIZE {
let in_range = ((block_len as isize - 1 - i as isize) >> 63) as u8;
let mask = !in_range;
let source_byte = if i < block_len { block[i] } else { 0 };
temp_block[i] = source_byte & mask;
}
compiler_fence(Ordering::SeqCst);
for (y_byte, temp_byte) in self.y.iter_mut().zip(temp_block.iter()) {
*y_byte ^= temp_byte;
}
self.y = Self::gf_multiply(&self.y, &self.h);
Ok(())
}
pub fn update_lengths(&mut self, aad_len: u64, cipher_len: u64) -> Result<()> {
let mut length_block = [0u8; GCM_BLOCK_SIZE];
BigEndian::write_u64(&mut length_block[0..8], aad_len * 8);
BigEndian::write_u64(&mut length_block[8..16], cipher_len * 8);
self.update_block(&length_block, GCM_BLOCK_SIZE)
}
pub fn finalize(&self) -> [u8; GCM_BLOCK_SIZE] {
self.y
}
fn gf_multiply(x: &[u8; 16], y: &[u8; 16]) -> [u8; 16] {
let mut z = [0u8; 16];
let mut v = *y;
for x_byte in x.iter() {
for j in 0..8 {
let bit_val = (x_byte >> (7 - j)) & 1;
let mask = 0u8.wrapping_sub(bit_val);
for (z_byte, v_byte) in z.iter_mut().zip(v.iter()) {
*z_byte ^= v_byte & mask;
}
let lsb = v[15] & 1;
let lsb_mask = 0u8.wrapping_sub(lsb);
let mut carry = 0;
for v_byte in &mut v {
let next_carry = *v_byte & 1;
*v_byte = (*v_byte >> 1) | (carry << 7);
carry = next_carry;
}
v[0] ^= 0xE1 & lsb_mask;
}
}
compiler_fence(Ordering::SeqCst);
z
}
}
pub fn process_ghash(
h: &[u8; GCM_BLOCK_SIZE],
aad: &[u8],
ciphertext: &[u8],
) -> Result<[u8; GCM_BLOCK_SIZE]> {
let mut ghash_instance = GHash::new(h);
ghash_instance.update(aad)?;
ghash_instance.update(ciphertext)?;
ghash_instance.update_lengths(aad.len() as u64, ciphertext.len() as u64)?;
Ok(ghash_instance.finalize())
}
#[cfg(test)]
mod tests;