chie_crypto/
hmac.rs

1//! HMAC-based authentication for message integrity.
2//!
3//! This module provides HMAC (Hash-based Message Authentication Code) functionality
4//! for ensuring message integrity and authenticity in the CHIE protocol.
5//!
6//! # Features
7//!
8//! - HMAC-SHA256 and HMAC-BLAKE3 support
9//! - Constant-time MAC verification
10//! - Key derivation for HMAC keys
11//! - Tagged authentication for different message types
12//!
13//! # Examples
14//!
15//! ```
16//! use chie_crypto::hmac::{HmacKey, HmacTag, compute_hmac, verify_hmac};
17//!
18//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
19//! // Generate a random HMAC key
20//! let key = HmacKey::generate();
21//!
22//! // Compute HMAC for a message
23//! let message = b"Hello, CHIE!";
24//! let tag = compute_hmac(&key, message);
25//!
26//! // Verify the HMAC tag
27//! assert!(verify_hmac(&key, message, &tag));
28//!
29//! // Verification fails for wrong message
30//! assert!(!verify_hmac(&key, b"Wrong message", &tag));
31//! # Ok(())
32//! # }
33//! ```
34//!
35//! # Security Considerations
36//!
37//! - HMAC keys should be at least 32 bytes (256 bits)
38//! - Use constant-time comparison for MAC verification
39//! - Never reuse HMAC keys across different protocols
40//! - Rotate HMAC keys periodically
41
42use blake3;
43use serde::{Deserialize, Serialize};
44use sha2::Sha256;
45use zeroize::{Zeroize, ZeroizeOnDrop};
46
47use crate::ct::ct_eq;
48
49// Serde helper for Vec<u8>
50mod serde_bytes {
51    use serde::{Deserialize, Deserializer, Serializer};
52
53    pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
54    where
55        S: Serializer,
56    {
57        serializer.serialize_bytes(bytes)
58    }
59
60    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
61    where
62        D: Deserializer<'de>,
63    {
64        <Vec<u8>>::deserialize(deserializer)
65    }
66}
67
68/// HMAC errors.
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum HmacError {
71    /// Invalid key size
72    InvalidKeySize,
73    /// Invalid tag size
74    InvalidTagSize,
75    /// Verification failed
76    VerificationFailed,
77    /// Serialization error
78    SerializationError(String),
79}
80
81impl std::fmt::Display for HmacError {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        match self {
84            Self::InvalidKeySize => write!(f, "Invalid HMAC key size"),
85            Self::InvalidTagSize => write!(f, "Invalid HMAC tag size"),
86            Self::VerificationFailed => write!(f, "HMAC verification failed"),
87            Self::SerializationError(e) => write!(f, "Serialization error: {}", e),
88        }
89    }
90}
91
92impl std::error::Error for HmacError {}
93
94/// HMAC result type.
95pub type HmacResult<T> = Result<T, HmacError>;
96
97/// HMAC key size in bytes.
98pub const HMAC_KEY_SIZE: usize = 32;
99
100/// HMAC tag size for SHA256 in bytes.
101pub const HMAC_SHA256_TAG_SIZE: usize = 32;
102
103/// HMAC tag size for BLAKE3 in bytes.
104pub const HMAC_BLAKE3_TAG_SIZE: usize = 32;
105
106/// HMAC key for message authentication.
107#[derive(Clone, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
108pub struct HmacKey {
109    #[serde(with = "serde_bytes")]
110    key: Vec<u8>,
111}
112
113impl HmacKey {
114    /// Generate a random HMAC key.
115    pub fn generate() -> Self {
116        use rand::RngCore;
117        let mut rng = rand::thread_rng();
118        let mut key = vec![0u8; HMAC_KEY_SIZE];
119        rng.fill_bytes(&mut key[..]);
120        Self { key }
121    }
122
123    /// Create an HMAC key from bytes.
124    ///
125    /// # Errors
126    ///
127    /// Returns `HmacError::InvalidKeySize` if the key is not exactly 32 bytes.
128    pub fn from_bytes(bytes: &[u8]) -> HmacResult<Self> {
129        if bytes.len() != HMAC_KEY_SIZE {
130            return Err(HmacError::InvalidKeySize);
131        }
132        Ok(Self {
133            key: bytes.to_vec(),
134        })
135    }
136
137    /// Get the key bytes.
138    pub fn as_bytes(&self) -> &[u8] {
139        &self.key
140    }
141
142    /// Convert to bytes.
143    pub fn to_bytes(&self) -> Vec<u8> {
144        self.key.clone()
145    }
146
147    /// Derive an HMAC key from a password using PBKDF2.
148    pub fn derive_from_password(password: &[u8], salt: &[u8], iterations: u32) -> Self {
149        use pbkdf2::pbkdf2_hmac;
150        let mut key = vec![0u8; HMAC_KEY_SIZE];
151        pbkdf2_hmac::<Sha256>(password, salt, iterations, &mut key);
152        Self { key }
153    }
154}
155
156impl std::fmt::Debug for HmacKey {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        f.debug_struct("HmacKey")
159            .field("key", &"[REDACTED]")
160            .finish()
161    }
162}
163
164/// HMAC tag (authentication code).
165#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
166pub struct HmacTag {
167    #[serde(with = "serde_bytes")]
168    tag: Vec<u8>,
169}
170
171impl HmacTag {
172    /// Create an HMAC tag from bytes.
173    pub fn from_bytes(bytes: &[u8]) -> Self {
174        Self {
175            tag: bytes.to_vec(),
176        }
177    }
178
179    /// Get the tag bytes.
180    pub fn as_bytes(&self) -> &[u8] {
181        &self.tag
182    }
183
184    /// Convert to bytes.
185    pub fn to_bytes(&self) -> Vec<u8> {
186        self.tag.clone()
187    }
188
189    /// Verify this tag against another tag in constant time.
190    pub fn verify(&self, other: &Self) -> bool {
191        ct_eq(&self.tag, &other.tag)
192    }
193}
194
195/// Compute HMAC-SHA256 for a message.
196pub fn compute_hmac_sha256(key: &HmacKey, message: &[u8]) -> HmacTag {
197    use hmac::{Hmac, Mac};
198    type HmacSha256 = Hmac<Sha256>;
199
200    let mut mac =
201        HmacSha256::new_from_slice(key.as_bytes()).expect("HMAC can take key of any size");
202    mac.update(message);
203    let result = mac.finalize();
204    HmacTag::from_bytes(&result.into_bytes())
205}
206
207/// Compute HMAC-BLAKE3 for a message (using keyed BLAKE3).
208pub fn compute_hmac_blake3(key: &HmacKey, message: &[u8]) -> HmacTag {
209    // BLAKE3 has native keyed hashing support
210    let key_array: [u8; 32] = key.as_bytes().try_into().expect("key is 32 bytes");
211    let hash = blake3::keyed_hash(&key_array, message);
212    HmacTag::from_bytes(hash.as_bytes())
213}
214
215/// Compute HMAC for a message (defaults to BLAKE3 for performance).
216pub fn compute_hmac(key: &HmacKey, message: &[u8]) -> HmacTag {
217    compute_hmac_blake3(key, message)
218}
219
220/// Verify HMAC tag in constant time.
221pub fn verify_hmac(key: &HmacKey, message: &[u8], tag: &HmacTag) -> bool {
222    let computed = compute_hmac(key, message);
223    computed.verify(tag)
224}
225
226/// Verify HMAC-SHA256 tag in constant time.
227pub fn verify_hmac_sha256(key: &HmacKey, message: &[u8], tag: &HmacTag) -> bool {
228    let computed = compute_hmac_sha256(key, message);
229    computed.verify(tag)
230}
231
232/// Verify HMAC-BLAKE3 tag in constant time.
233pub fn verify_hmac_blake3(key: &HmacKey, message: &[u8], tag: &HmacTag) -> bool {
234    let computed = compute_hmac_blake3(key, message);
235    computed.verify(tag)
236}
237
238/// Tagged HMAC for domain separation.
239///
240/// This adds a context tag to the message before computing HMAC,
241/// preventing HMAC values from being reused across different contexts.
242pub fn compute_tagged_hmac(key: &HmacKey, context: &[u8], message: &[u8]) -> HmacTag {
243    let key_array: [u8; 32] = key.as_bytes().try_into().expect("key is 32 bytes");
244    let mut hasher = blake3::Hasher::new_keyed(&key_array);
245    hasher.update(context);
246    hasher.update(message);
247    HmacTag::from_bytes(hasher.finalize().as_bytes())
248}
249
250/// Verify tagged HMAC in constant time.
251pub fn verify_tagged_hmac(key: &HmacKey, context: &[u8], message: &[u8], tag: &HmacTag) -> bool {
252    let computed = compute_tagged_hmac(key, context, message);
253    computed.verify(tag)
254}
255
256/// Authenticated message containing both data and HMAC tag.
257#[derive(Clone, Debug, Serialize, Deserialize)]
258pub struct AuthenticatedMessage {
259    #[serde(with = "serde_bytes")]
260    data: Vec<u8>,
261    tag: HmacTag,
262}
263
264impl AuthenticatedMessage {
265    /// Create a new authenticated message.
266    pub fn new(key: &HmacKey, data: Vec<u8>) -> Self {
267        let tag = compute_hmac(key, &data);
268        Self { data, tag }
269    }
270
271    /// Create a new tagged authenticated message.
272    pub fn new_tagged(key: &HmacKey, context: &[u8], data: Vec<u8>) -> Self {
273        let tag = compute_tagged_hmac(key, context, &data);
274        Self { data, tag }
275    }
276
277    /// Verify and extract the data.
278    ///
279    /// # Errors
280    ///
281    /// Returns `HmacError::VerificationFailed` if the HMAC tag is invalid.
282    pub fn verify(self, key: &HmacKey) -> HmacResult<Vec<u8>> {
283        if verify_hmac(key, &self.data, &self.tag) {
284            Ok(self.data)
285        } else {
286            Err(HmacError::VerificationFailed)
287        }
288    }
289
290    /// Verify and extract the data (tagged version).
291    ///
292    /// # Errors
293    ///
294    /// Returns `HmacError::VerificationFailed` if the HMAC tag is invalid.
295    pub fn verify_tagged(self, key: &HmacKey, context: &[u8]) -> HmacResult<Vec<u8>> {
296        if verify_tagged_hmac(key, context, &self.data, &self.tag) {
297            Ok(self.data)
298        } else {
299            Err(HmacError::VerificationFailed)
300        }
301    }
302
303    /// Get the data without verification (unsafe).
304    pub fn data(&self) -> &[u8] {
305        &self.data
306    }
307
308    /// Get the HMAC tag.
309    pub fn tag(&self) -> &HmacTag {
310        &self.tag
311    }
312
313    /// Serialize to bytes.
314    pub fn to_bytes(&self) -> HmacResult<Vec<u8>> {
315        crate::codec::encode(self).map_err(|e| HmacError::SerializationError(e.to_string()))
316    }
317
318    /// Deserialize from bytes.
319    pub fn from_bytes(bytes: &[u8]) -> HmacResult<Self> {
320        crate::codec::decode(bytes).map_err(|e| HmacError::SerializationError(e.to_string()))
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn test_hmac_basic() {
330        let key = HmacKey::generate();
331        let message = b"Hello, CHIE!";
332
333        let tag = compute_hmac(&key, message);
334        assert!(verify_hmac(&key, message, &tag));
335
336        // Wrong message should fail
337        assert!(!verify_hmac(&key, b"Wrong message", &tag));
338    }
339
340    #[test]
341    fn test_hmac_sha256() {
342        let key = HmacKey::generate();
343        let message = b"Test message";
344
345        let tag = compute_hmac_sha256(&key, message);
346        assert!(verify_hmac_sha256(&key, message, &tag));
347        assert_eq!(tag.as_bytes().len(), HMAC_SHA256_TAG_SIZE);
348    }
349
350    #[test]
351    fn test_hmac_blake3() {
352        let key = HmacKey::generate();
353        let message = b"Test message";
354
355        let tag = compute_hmac_blake3(&key, message);
356        assert!(verify_hmac_blake3(&key, message, &tag));
357        assert_eq!(tag.as_bytes().len(), HMAC_BLAKE3_TAG_SIZE);
358    }
359
360    #[test]
361    fn test_tagged_hmac() {
362        let key = HmacKey::generate();
363        let context = b"CHIE:BandwidthProof";
364        let message = b"1234567890";
365
366        let tag = compute_tagged_hmac(&key, context, message);
367        assert!(verify_tagged_hmac(&key, context, message, &tag));
368
369        // Wrong context should fail
370        assert!(!verify_tagged_hmac(&key, b"wrong", message, &tag));
371    }
372
373    #[test]
374    fn test_authenticated_message() {
375        let key = HmacKey::generate();
376        let data = b"Secret data".to_vec();
377
378        let msg = AuthenticatedMessage::new(&key, data.clone());
379        let verified = msg.verify(&key).unwrap();
380        assert_eq!(verified, data);
381    }
382
383    #[test]
384    fn test_authenticated_message_fails() {
385        let key1 = HmacKey::generate();
386        let key2 = HmacKey::generate();
387        let data = b"Secret data".to_vec();
388
389        let msg = AuthenticatedMessage::new(&key1, data);
390        assert!(msg.verify(&key2).is_err());
391    }
392
393    #[test]
394    fn test_tagged_authenticated_message() {
395        let key = HmacKey::generate();
396        let context = b"CHIE:Chunk";
397        let data = b"Chunk data".to_vec();
398
399        let msg = AuthenticatedMessage::new_tagged(&key, context, data.clone());
400        let verified = msg.verify_tagged(&key, context).unwrap();
401        assert_eq!(verified, data);
402    }
403
404    #[test]
405    fn test_hmac_key_from_bytes() {
406        let bytes = [42u8; HMAC_KEY_SIZE];
407        let key = HmacKey::from_bytes(&bytes).unwrap();
408        assert_eq!(key.as_bytes(), &bytes);
409    }
410
411    #[test]
412    fn test_hmac_key_invalid_size() {
413        let bytes = [42u8; 16]; // Wrong size
414        assert!(HmacKey::from_bytes(&bytes).is_err());
415    }
416
417    #[test]
418    fn test_hmac_key_derive_from_password() {
419        let password = b"my secret password";
420        let salt = b"unique salt";
421        let key1 = HmacKey::derive_from_password(password, salt, 10000);
422        let key2 = HmacKey::derive_from_password(password, salt, 10000);
423
424        // Same password and salt should give same key
425        assert_eq!(key1.as_bytes(), key2.as_bytes());
426
427        // Different salt should give different key
428        let key3 = HmacKey::derive_from_password(password, b"different salt", 10000);
429        assert_ne!(key1.as_bytes(), key3.as_bytes());
430    }
431
432    #[test]
433    fn test_serialization() {
434        let key = HmacKey::generate();
435        let data = b"Test data".to_vec();
436        let msg = AuthenticatedMessage::new(&key, data);
437
438        let bytes = msg.to_bytes().unwrap();
439        let deserialized = AuthenticatedMessage::from_bytes(&bytes).unwrap();
440
441        assert_eq!(msg.data, deserialized.data);
442        assert_eq!(msg.tag, deserialized.tag);
443    }
444
445    #[test]
446    fn test_constant_time_verification() {
447        let key = HmacKey::generate();
448        let message = b"Test message";
449        let tag1 = compute_hmac(&key, message);
450        let tag2 = compute_hmac(&key, message);
451
452        assert!(tag1.verify(&tag2));
453
454        // Create a modified tag
455        let mut wrong_tag = tag1.clone();
456        wrong_tag.tag[0] ^= 1;
457
458        assert!(!tag1.verify(&wrong_tag));
459    }
460}