Files
opaque-lattice/src/mac.rs
2026-01-06 12:49:26 -07:00

152 lines
3.5 KiB
Rust

use hmac::{Hmac, Mac};
use sha2::Sha512;
use subtle::ConstantTimeEq;
use crate::error::{OpaqueError, Result};
pub const MAC_LEN: usize = 64;
type HmacSha512 = Hmac<Sha512>;
pub fn compute(key: &[u8], data: &[u8]) -> [u8; MAC_LEN] {
let mut mac = HmacSha512::new_from_slice(key).expect("HMAC accepts any key length");
mac.update(data);
mac.finalize().into_bytes().into()
}
pub fn verify(key: &[u8], data: &[u8], expected: &[u8]) -> Result<()> {
if expected.len() != MAC_LEN {
return Err(OpaqueError::MacVerificationFailed);
}
let computed = compute(key, data);
if computed.ct_eq(expected).into() {
Ok(())
} else {
Err(OpaqueError::MacVerificationFailed)
}
}
pub fn compute_multi(key: &[u8], parts: &[&[u8]]) -> [u8; MAC_LEN] {
let mut mac = HmacSha512::new_from_slice(key).expect("HMAC accepts any key length");
for part in parts {
mac.update(part);
}
mac.finalize().into_bytes().into()
}
pub struct HmacContext {
mac: HmacSha512,
}
impl HmacContext {
#[must_use]
pub fn new(key: &[u8]) -> Self {
let mac = HmacSha512::new_from_slice(key).expect("HMAC accepts any key length");
Self { mac }
}
pub fn update(&mut self, data: &[u8]) {
self.mac.update(data);
}
#[must_use]
pub fn finalize(self) -> [u8; MAC_LEN] {
self.mac.finalize().into_bytes().into()
}
pub fn verify(self, expected: &[u8]) -> Result<()> {
if expected.len() != MAC_LEN {
return Err(OpaqueError::MacVerificationFailed);
}
let computed = self.finalize();
if computed.ct_eq(expected).into() {
Ok(())
} else {
Err(OpaqueError::MacVerificationFailed)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_and_verify() {
let key = b"secret key";
let data = b"message to authenticate";
let tag = compute(key, data);
assert_eq!(tag.len(), MAC_LEN);
assert!(verify(key, data, &tag).is_ok());
}
#[test]
fn test_verify_wrong_tag() {
let key = b"secret key";
let data = b"message";
let wrong_tag = [0u8; MAC_LEN];
assert!(verify(key, data, &wrong_tag).is_err());
}
#[test]
fn test_verify_wrong_length() {
let key = b"secret key";
let data = b"message";
let short_tag = [0u8; 32];
assert!(verify(key, data, &short_tag).is_err());
}
#[test]
fn test_compute_multi() {
let key = b"secret key";
let part1 = b"hello ";
let part2 = b"world";
let tag_multi = compute_multi(key, &[part1, part2]);
let tag_single = compute(key, b"hello world");
assert_eq!(tag_multi, tag_single);
}
#[test]
fn test_hmac_context() {
let key = b"secret key";
let data = b"message to authenticate";
let mut ctx = HmacContext::new(key);
ctx.update(data);
let tag1 = ctx.finalize();
let tag2 = compute(key, data);
assert_eq!(tag1, tag2);
}
#[test]
fn test_hmac_context_verify() {
let key = b"secret key";
let data = b"message";
let tag = compute(key, data);
let mut ctx = HmacContext::new(key);
ctx.update(data);
assert!(ctx.verify(&tag).is_ok());
}
#[test]
fn test_different_keys_different_tags() {
let data = b"message";
let tag1 = compute(b"key1", data);
let tag2 = compute(b"key2", data);
assert_ne!(tag1, tag2);
}
}