use hmac::{Hmac, Mac};
use sha2::Sha256;
use thiserror::Error;
type HmacSha256 = Hmac<Sha256>;
#[derive(Error, Debug)]
pub enum HmacError {
#[error("Invalid key length")]
InvalidKeyLength,
#[error("HMAC verification failed")]
VerificationFailed,
}
pub struct HmacAuthenticator {
secret: [u8; 32],
}
impl HmacAuthenticator {
pub fn new(secret: [u8; 32]) -> Self {
Self { secret }
}
pub fn compute(&self, data: &[u8]) -> [u8; 32] {
let mut mac =
HmacSha256::new_from_slice(&self.secret).expect("HMAC can take key of any size");
mac.update(data);
mac.finalize().into_bytes().into()
}
pub fn compute_with_timestamp(&self, data: &[u8], timestamp: u64) -> [u8; 32] {
let mut mac =
HmacSha256::new_from_slice(&self.secret).expect("HMAC can take key of any size");
mac.update(data);
mac.update(×tamp.to_le_bytes());
mac.finalize().into_bytes().into()
}
pub fn verify(&self, data: &[u8], expected: &[u8; 32]) -> Result<(), HmacError> {
let computed = self.compute(data);
if constant_time_compare(&computed, expected) {
Ok(())
} else {
Err(HmacError::VerificationFailed)
}
}
pub fn verify_with_timestamp(
&self,
data: &[u8],
timestamp: u64,
expected: &[u8; 32],
) -> Result<(), HmacError> {
let computed = self.compute_with_timestamp(data, timestamp);
if constant_time_compare(&computed, expected) {
Ok(())
} else {
Err(HmacError::VerificationFailed)
}
}
}
#[inline]
fn constant_time_compare(a: &[u8; 32], b: &[u8; 32]) -> bool {
let mut result = 0u8;
for i in 0..32 {
result |= a[i] ^ b[i];
}
result == 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hmac_compute_verify() {
let secret = [42u8; 32];
let auth = HmacAuthenticator::new(secret);
let data = b"Hello, APFSDS!";
let mac = auth.compute(data);
assert!(auth.verify(data, &mac).is_ok());
}
#[test]
fn test_hmac_wrong_data() {
let secret = [42u8; 32];
let auth = HmacAuthenticator::new(secret);
let data = b"Hello, APFSDS!";
let wrong_data = b"Wrong data!";
let mac = auth.compute(data);
assert!(auth.verify(wrong_data, &mac).is_err());
}
#[test]
fn test_hmac_with_timestamp() {
let secret = [42u8; 32];
let auth = HmacAuthenticator::new(secret);
let data = b"Hello, APFSDS!";
let timestamp = 1234567890u64;
let mac = auth.compute_with_timestamp(data, timestamp);
assert!(auth.verify_with_timestamp(data, timestamp, &mac).is_ok());
assert!(
auth.verify_with_timestamp(data, timestamp + 1, &mac)
.is_err()
);
}
#[test]
fn test_constant_time_compare() {
let a = [1u8; 32];
let b = [1u8; 32];
let c = [2u8; 32];
assert!(constant_time_compare(&a, &b));
assert!(!constant_time_compare(&a, &c));
}
}