lum_ccc_rust/crates/ccc-crypto-wolfssl/src/mac.rs

174 lines
6.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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 164 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 164 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"));
}
}