174 lines
6.5 KiB
Rust
174 lines
6.5 KiB
Rust
//! MAC (Message Authentication Code) implementations via wolfCrypt.
|
||
//!
|
||
//! Provides HMAC-SHA256, HMAC-SHA384, HMAC-SHA512, and BLAKE2b-MAC.
|
||
//! All `verify_mac` implementations use constant-time comparison to prevent
|
||
//! timing side-channels.
|
||
|
||
use ccc_crypto_core::{algorithms::MacAlgorithm, error::CryptoError};
|
||
|
||
// wolfCrypt hash type constants matching `enum wc_HashType` in hash.h.
|
||
const WC_HASH_TYPE_SHA256: i32 = 8;
|
||
const WC_HASH_TYPE_SHA384: i32 = 9;
|
||
const WC_HASH_TYPE_SHA512: i32 = 10;
|
||
|
||
// ──────────────────────────────────────────────────────────────────────────────
|
||
// Public entry points
|
||
// ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
/// Compute a MAC tag over `data` using the specified algorithm and `key`.
|
||
pub fn compute_mac(
|
||
algo: MacAlgorithm,
|
||
key: &[u8],
|
||
data: &[u8],
|
||
) -> Result<Vec<u8>, CryptoError> {
|
||
match algo {
|
||
MacAlgorithm::HmacSha256 => hmac(WC_HASH_TYPE_SHA256, key, data, 32),
|
||
MacAlgorithm::HmacSha384 => hmac(WC_HASH_TYPE_SHA384, key, data, 48),
|
||
MacAlgorithm::HmacSha512 => hmac(WC_HASH_TYPE_SHA512, key, data, 64),
|
||
MacAlgorithm::Blake2bMac => blake2b_mac(key, data),
|
||
MacAlgorithm::Poly1305 =>
|
||
Err(CryptoError::UnsupportedAlgorithm(
|
||
"Poly1305 standalone MAC is not exposed in this Phase".into()
|
||
)),
|
||
}
|
||
}
|
||
|
||
/// Verify a MAC tag in constant time.
|
||
///
|
||
/// Returns `Ok(true)` if the tag matches, `Ok(false)` if it does not.
|
||
/// Never returns `Err` for a tag mismatch — only for structural errors.
|
||
pub fn verify_mac(
|
||
algo: MacAlgorithm,
|
||
key: &[u8],
|
||
data: &[u8],
|
||
mac: &[u8],
|
||
) -> Result<bool, CryptoError> {
|
||
let expected = compute_mac(algo, key, data)?;
|
||
Ok(constant_time_eq(&expected, mac))
|
||
}
|
||
|
||
// ──────────────────────────────────────────────────────────────────────────────
|
||
// HMAC
|
||
// ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
/// HMAC using wolfCrypt's `wc_HmacSetKey` / `wc_HmacUpdate` / `wc_HmacFinal`.
|
||
fn hmac(
|
||
hash_type: i32,
|
||
key: &[u8],
|
||
data: &[u8],
|
||
out_len: usize,
|
||
) -> Result<Vec<u8>, CryptoError> {
|
||
let mut out = vec![0u8; out_len];
|
||
|
||
unsafe {
|
||
let mut hmac_ctx: crate::sys::Hmac = std::mem::zeroed();
|
||
|
||
let ret = crate::sys::wc_HmacSetKey(
|
||
&mut hmac_ctx,
|
||
hash_type,
|
||
key.as_ptr(),
|
||
key.len() as u32,
|
||
);
|
||
if ret != 0 {
|
||
return Err(CryptoError::InvalidKey(format!("wc_HmacSetKey returned {}", ret)));
|
||
}
|
||
|
||
let ret = crate::sys::wc_HmacUpdate(
|
||
&mut hmac_ctx,
|
||
data.as_ptr(),
|
||
data.len() as u32,
|
||
);
|
||
if ret != 0 {
|
||
return Err(CryptoError::InternalError(format!("wc_HmacUpdate returned {}", ret)));
|
||
}
|
||
|
||
let ret = crate::sys::wc_HmacFinal(
|
||
&mut hmac_ctx,
|
||
out.as_mut_ptr(),
|
||
);
|
||
if ret != 0 {
|
||
return Err(CryptoError::InternalError(format!("wc_HmacFinal returned {}", ret)));
|
||
}
|
||
|
||
crate::sys::wc_HmacFree(&mut hmac_ctx);
|
||
}
|
||
|
||
Ok(out)
|
||
}
|
||
|
||
// ──────────────────────────────────────────────────────────────────────────────
|
||
// BLAKE2b-MAC (keyed BLAKE2b)
|
||
// ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
/// Keyed BLAKE2b-512 used as a MAC.
|
||
///
|
||
/// Key length must be 1–64 bytes (wolfCrypt limitation).
|
||
/// Output is always 64 bytes (BLAKE2b-512 full digest length).
|
||
fn blake2b_mac(key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||
if key.is_empty() || key.len() > 64 {
|
||
return Err(CryptoError::InvalidKey(
|
||
"BLAKE2b-MAC: key must be 1–64 bytes".into()
|
||
));
|
||
}
|
||
|
||
let mut out = vec![0u8; 64];
|
||
|
||
unsafe {
|
||
// wolfCrypt: wc_Blake2b(blake2b, out, outLen, key, keyLen, data, dataSz)
|
||
// Using the simplified one-shot Blake2bHash wrapper.
|
||
let ret = crate::sys::wc_Blake2bHash(
|
||
out.as_mut_ptr(),
|
||
64u32,
|
||
key.as_ptr(),
|
||
key.len() as u32,
|
||
data.as_ptr(),
|
||
data.len() as u32,
|
||
);
|
||
if ret != 0 {
|
||
return Err(CryptoError::InternalError(
|
||
format!("wc_Blake2bHash returned {}", ret)
|
||
));
|
||
}
|
||
}
|
||
|
||
Ok(out)
|
||
}
|
||
|
||
// ──────────────────────────────────────────────────────────────────────────────
|
||
// Constant-time comparison
|
||
// ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
/// Constant-time byte-slice equality comparison.
|
||
///
|
||
/// Returns `false` immediately if lengths differ (safe — length is not secret
|
||
/// for fixed-size HMAC tags).
|
||
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
|
||
if a.len() != b.len() {
|
||
return false;
|
||
}
|
||
// Accumulate XOR differences; the branch is on the final accumulated value
|
||
// only, not on any individual byte.
|
||
let diff: u8 = a.iter().zip(b.iter()).fold(0u8, |acc, (x, y)| acc | (x ^ y));
|
||
diff == 0
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::constant_time_eq;
|
||
|
||
#[test]
|
||
fn ct_eq_same() {
|
||
assert!(constant_time_eq(b"hello", b"hello"));
|
||
}
|
||
|
||
#[test]
|
||
fn ct_eq_different() {
|
||
assert!(!constant_time_eq(b"hello", b"Hello"));
|
||
}
|
||
|
||
#[test]
|
||
fn ct_eq_different_lengths() {
|
||
assert!(!constant_time_eq(b"hi", b"hello"));
|
||
}
|
||
}
|