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