use crate::error::{Error, Result};
use crate::hash::HashFunction;
use dcrypt_common::security::{SecretBuffer, SecureZeroingType};
use subtle::ConstantTimeEq;
use zeroize::{Zeroize, ZeroizeOnDrop};
const MAX_BLOCK: usize = 144;
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct Hmac<H: HashFunction + Clone + Zeroize> {
hash: H,
ipad: SecretBuffer<MAX_BLOCK>,
opad: SecretBuffer<MAX_BLOCK>,
block_size: usize,
is_finalized: bool,
}
impl<H> Hmac<H>
where
H: HashFunction + Clone + Zeroize,
H::Output: AsRef<[u8]> + Clone + Zeroize,
{
const IPAD_BYTE: u8 = 0x36;
const OPAD_BYTE: u8 = 0x5c;
pub fn new(key: &[u8]) -> Result<Self> {
let bs = H::block_size();
debug_assert!(bs <= MAX_BLOCK);
let mut hk = H::new();
if let Err(error) = hk.update(key) {
hk.zeroize();
return Err(error);
}
let mut hashed = match hk.finalize() {
Ok(output) => output,
Err(error) => {
hk.zeroize();
return Err(error);
}
}; hk.zeroize();
let mut k_prime = SecretBuffer::<MAX_BLOCK>::zeroed();
let long = (key.len() > bs) as u8; let mask = long.wrapping_neg(); #[allow(clippy::needless_range_loop)] for i in 0..bs {
let k = *key.get(i).unwrap_or(&0);
let hk = hashed.as_ref().get(i).copied().unwrap_or(0);
k_prime.as_mut()[i] = (hk & mask) | (k & !mask);
}
hashed.zeroize();
let mut ipad = SecretBuffer::<MAX_BLOCK>::zeroed();
let mut opad = SecretBuffer::<MAX_BLOCK>::zeroed();
#[allow(clippy::needless_range_loop)] for i in 0..bs {
ipad.as_mut()[i] = k_prime.as_ref()[i] ^ Self::IPAD_BYTE;
opad.as_mut()[i] = k_prime.as_ref()[i] ^ Self::OPAD_BYTE;
}
k_prime.zeroize();
let mut hash = H::new();
if let Err(error) = hash.update(&ipad.as_ref()[..bs]) {
hash.zeroize();
return Err(error);
}
Ok(Self {
hash,
ipad,
opad,
block_size: bs,
is_finalized: false,
})
}
pub fn update(&mut self, data: &[u8]) -> Result<()> {
if self.is_finalized {
return Err(Error::param(
"hmac_state",
"Cannot update after finalization",
));
}
self.hash.update(data).map(|_| ())
}
pub fn finalize(&mut self) -> Result<Vec<u8>> {
if self.is_finalized {
return Err(Error::param("hmac_state", "HMAC already finalized"));
}
self.is_finalized = true;
let mut inner_hash = match self.hash.finalize() {
Ok(output) => {
self.hash.zeroize();
output
}
Err(error) => {
self.hash.zeroize();
return Err(error);
}
};
let mut outer = H::new();
if let Err(error) = outer.update(&self.opad.as_ref()[..self.block_size]) {
inner_hash.zeroize();
outer.zeroize();
return Err(error);
}
if let Err(error) = outer.update(inner_hash.as_ref()) {
inner_hash.zeroize();
outer.zeroize();
return Err(error);
}
inner_hash.zeroize();
let mut output = match outer.finalize() {
Ok(output) => output,
Err(error) => {
outer.zeroize();
return Err(error);
}
};
outer.zeroize();
let tag = output.as_ref().to_vec();
output.zeroize();
Ok(tag)
}
pub fn mac(key: &[u8], data: &[u8]) -> Result<Vec<u8>> {
let mut h = Self::new(key)?;
h.update(data)?;
h.finalize()
}
pub fn verify(key: &[u8], data: &[u8], tag: &[u8]) -> Result<bool> {
let expected = Self::mac(key, data)?;
let mut diff = 0u8;
#[allow(clippy::needless_range_loop)] for i in 0..H::output_size() {
let a = expected.get(i).copied().unwrap_or(0);
let b = tag.get(i).copied().unwrap_or(0);
diff |= a ^ b;
}
diff |= tag.len().ct_eq(&H::output_size()).unwrap_u8() ^ 1;
Ok(diff.ct_eq(&0u8).unwrap_u8() == 1)
}
}
impl<H> SecureZeroingType for Hmac<H>
where
H: HashFunction + Default + Clone + Zeroize,
{
fn zeroed() -> Self {
Self {
hash: H::default(),
ipad: SecretBuffer::zeroed(),
opad: SecretBuffer::zeroed(),
block_size: 0,
is_finalized: false,
}
}
fn secure_clone(&self) -> Self {
Self {
hash: self.hash.clone(),
ipad: self.ipad.secure_clone(),
opad: self.opad.secure_clone(),
block_size: self.block_size,
is_finalized: self.is_finalized,
}
}
}
#[cfg(test)]
mod tests;