Skip to main content

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::Rng as _;
117        let mut rng = rand::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::digest::KeyInit;
198    use hmac::{Hmac, Mac};
199    type HmacSha256 = Hmac<Sha256>;
200
201    let mut mac = <HmacSha256 as KeyInit>::new_from_slice(key.as_bytes())
202        .expect("HMAC can take key of any size");
203    mac.update(message);
204    let result = mac.finalize();
205    HmacTag::from_bytes(&result.into_bytes())
206}
207
208/// Compute HMAC-BLAKE3 for a message (using keyed BLAKE3).
209pub fn compute_hmac_blake3(key: &HmacKey, message: &[u8]) -> HmacTag {
210    // BLAKE3 has native keyed hashing support
211    let key_array: [u8; 32] = key.as_bytes().try_into().expect("key is 32 bytes");
212    let hash = blake3::keyed_hash(&key_array, message);
213    HmacTag::from_bytes(hash.as_bytes())
214}
215
216/// Compute HMAC for a message (defaults to BLAKE3 for performance).
217pub fn compute_hmac(key: &HmacKey, message: &[u8]) -> HmacTag {
218    compute_hmac_blake3(key, message)
219}
220
221/// Verify HMAC tag in constant time.
222pub fn verify_hmac(key: &HmacKey, message: &[u8], tag: &HmacTag) -> bool {
223    let computed = compute_hmac(key, message);
224    computed.verify(tag)
225}
226
227/// Verify HMAC-SHA256 tag in constant time.
228pub fn verify_hmac_sha256(key: &HmacKey, message: &[u8], tag: &HmacTag) -> bool {
229    let computed = compute_hmac_sha256(key, message);
230    computed.verify(tag)
231}
232
233/// Verify HMAC-BLAKE3 tag in constant time.
234pub fn verify_hmac_blake3(key: &HmacKey, message: &[u8], tag: &HmacTag) -> bool {
235    let computed = compute_hmac_blake3(key, message);
236    computed.verify(tag)
237}
238
239/// Tagged HMAC for domain separation.
240///
241/// This adds a context tag to the message before computing HMAC,
242/// preventing HMAC values from being reused across different contexts.
243pub fn compute_tagged_hmac(key: &HmacKey, context: &[u8], message: &[u8]) -> HmacTag {
244    let key_array: [u8; 32] = key.as_bytes().try_into().expect("key is 32 bytes");
245    let mut hasher = blake3::Hasher::new_keyed(&key_array);
246    hasher.update(context);
247    hasher.update(message);
248    HmacTag::from_bytes(hasher.finalize().as_bytes())
249}
250
251/// Verify tagged HMAC in constant time.
252pub fn verify_tagged_hmac(key: &HmacKey, context: &[u8], message: &[u8], tag: &HmacTag) -> bool {
253    let computed = compute_tagged_hmac(key, context, message);
254    computed.verify(tag)
255}
256
257/// Authenticated message containing both data and HMAC tag.
258#[derive(Clone, Debug, Serialize, Deserialize)]
259pub struct AuthenticatedMessage {
260    #[serde(with = "serde_bytes")]
261    data: Vec<u8>,
262    tag: HmacTag,
263}
264
265impl AuthenticatedMessage {
266    /// Create a new authenticated message.
267    pub fn new(key: &HmacKey, data: Vec<u8>) -> Self {
268        let tag = compute_hmac(key, &data);
269        Self { data, tag }
270    }
271
272    /// Create a new tagged authenticated message.
273    pub fn new_tagged(key: &HmacKey, context: &[u8], data: Vec<u8>) -> Self {
274        let tag = compute_tagged_hmac(key, context, &data);
275        Self { data, tag }
276    }
277
278    /// Verify and extract the data.
279    ///
280    /// # Errors
281    ///
282    /// Returns `HmacError::VerificationFailed` if the HMAC tag is invalid.
283    pub fn verify(self, key: &HmacKey) -> HmacResult<Vec<u8>> {
284        if verify_hmac(key, &self.data, &self.tag) {
285            Ok(self.data)
286        } else {
287            Err(HmacError::VerificationFailed)
288        }
289    }
290
291    /// Verify and extract the data (tagged version).
292    ///
293    /// # Errors
294    ///
295    /// Returns `HmacError::VerificationFailed` if the HMAC tag is invalid.
296    pub fn verify_tagged(self, key: &HmacKey, context: &[u8]) -> HmacResult<Vec<u8>> {
297        if verify_tagged_hmac(key, context, &self.data, &self.tag) {
298            Ok(self.data)
299        } else {
300            Err(HmacError::VerificationFailed)
301        }
302    }
303
304    /// Get the data without verification (unsafe).
305    pub fn data(&self) -> &[u8] {
306        &self.data
307    }
308
309    /// Get the HMAC tag.
310    pub fn tag(&self) -> &HmacTag {
311        &self.tag
312    }
313
314    /// Serialize to bytes.
315    pub fn to_bytes(&self) -> HmacResult<Vec<u8>> {
316        crate::codec::encode(self).map_err(|e| HmacError::SerializationError(e.to_string()))
317    }
318
319    /// Deserialize from bytes.
320    pub fn from_bytes(bytes: &[u8]) -> HmacResult<Self> {
321        crate::codec::decode(bytes).map_err(|e| HmacError::SerializationError(e.to_string()))
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328
329    #[test]
330    fn test_hmac_basic() {
331        let key = HmacKey::generate();
332        let message = b"Hello, CHIE!";
333
334        let tag = compute_hmac(&key, message);
335        assert!(verify_hmac(&key, message, &tag));
336
337        // Wrong message should fail
338        assert!(!verify_hmac(&key, b"Wrong message", &tag));
339    }
340
341    #[test]
342    fn test_hmac_sha256() {
343        let key = HmacKey::generate();
344        let message = b"Test message";
345
346        let tag = compute_hmac_sha256(&key, message);
347        assert!(verify_hmac_sha256(&key, message, &tag));
348        assert_eq!(tag.as_bytes().len(), HMAC_SHA256_TAG_SIZE);
349    }
350
351    #[test]
352    fn test_hmac_blake3() {
353        let key = HmacKey::generate();
354        let message = b"Test message";
355
356        let tag = compute_hmac_blake3(&key, message);
357        assert!(verify_hmac_blake3(&key, message, &tag));
358        assert_eq!(tag.as_bytes().len(), HMAC_BLAKE3_TAG_SIZE);
359    }
360
361    #[test]
362    fn test_tagged_hmac() {
363        let key = HmacKey::generate();
364        let context = b"CHIE:BandwidthProof";
365        let message = b"1234567890";
366
367        let tag = compute_tagged_hmac(&key, context, message);
368        assert!(verify_tagged_hmac(&key, context, message, &tag));
369
370        // Wrong context should fail
371        assert!(!verify_tagged_hmac(&key, b"wrong", message, &tag));
372    }
373
374    #[test]
375    fn test_authenticated_message() {
376        let key = HmacKey::generate();
377        let data = b"Secret data".to_vec();
378
379        let msg = AuthenticatedMessage::new(&key, data.clone());
380        let verified = msg.verify(&key).unwrap();
381        assert_eq!(verified, data);
382    }
383
384    #[test]
385    fn test_authenticated_message_fails() {
386        let key1 = HmacKey::generate();
387        let key2 = HmacKey::generate();
388        let data = b"Secret data".to_vec();
389
390        let msg = AuthenticatedMessage::new(&key1, data);
391        assert!(msg.verify(&key2).is_err());
392    }
393
394    #[test]
395    fn test_tagged_authenticated_message() {
396        let key = HmacKey::generate();
397        let context = b"CHIE:Chunk";
398        let data = b"Chunk data".to_vec();
399
400        let msg = AuthenticatedMessage::new_tagged(&key, context, data.clone());
401        let verified = msg.verify_tagged(&key, context).unwrap();
402        assert_eq!(verified, data);
403    }
404
405    #[test]
406    fn test_hmac_key_from_bytes() {
407        let bytes = [42u8; HMAC_KEY_SIZE];
408        let key = HmacKey::from_bytes(&bytes).unwrap();
409        assert_eq!(key.as_bytes(), &bytes);
410    }
411
412    #[test]
413    fn test_hmac_key_invalid_size() {
414        let bytes = [42u8; 16]; // Wrong size
415        assert!(HmacKey::from_bytes(&bytes).is_err());
416    }
417
418    #[test]
419    fn test_hmac_key_derive_from_password() {
420        let password = b"my secret password";
421        let salt = b"unique salt";
422        let key1 = HmacKey::derive_from_password(password, salt, 10000);
423        let key2 = HmacKey::derive_from_password(password, salt, 10000);
424
425        // Same password and salt should give same key
426        assert_eq!(key1.as_bytes(), key2.as_bytes());
427
428        // Different salt should give different key
429        let key3 = HmacKey::derive_from_password(password, b"different salt", 10000);
430        assert_ne!(key1.as_bytes(), key3.as_bytes());
431    }
432
433    #[test]
434    fn test_serialization() {
435        let key = HmacKey::generate();
436        let data = b"Test data".to_vec();
437        let msg = AuthenticatedMessage::new(&key, data);
438
439        let bytes = msg.to_bytes().unwrap();
440        let deserialized = AuthenticatedMessage::from_bytes(&bytes).unwrap();
441
442        assert_eq!(msg.data, deserialized.data);
443        assert_eq!(msg.tag, deserialized.tag);
444    }
445
446    #[test]
447    fn test_constant_time_verification() {
448        let key = HmacKey::generate();
449        let message = b"Test message";
450        let tag1 = compute_hmac(&key, message);
451        let tag2 = compute_hmac(&key, message);
452
453        assert!(tag1.verify(&tag2));
454
455        // Create a modified tag
456        let mut wrong_tag = tag1.clone();
457        wrong_tag.tag[0] ^= 1;
458
459        assert!(!tag1.verify(&wrong_tag));
460    }
461}