Skip to main content

saorsa_pqc/api/
hmac.rs

1//! HMAC (Hash-based Message Authentication Code) implementations
2//!
3//! Provides quantum-resistant HMAC implementations:
4//! - HMAC-SHA3-256
5//! - HMAC-SHA3-512
6//! - HMAC-BLAKE3
7
8use crate::api::errors::{PqcError, PqcResult};
9use crate::api::traits::Mac;
10use hmac::{Hmac, Mac as HmacMac};
11use sha3::{Sha3_256, Sha3_512};
12use subtle::ConstantTimeEq;
13use zeroize::Zeroizing;
14
15/// HMAC-SHA3-256
16pub struct HmacSha3_256 {
17    mac: Hmac<Sha3_256>,
18}
19
20/// HMAC-SHA3-256 output
21#[derive(Clone)]
22pub struct HmacSha3_256Output([u8; 32]);
23
24impl AsRef<[u8]> for HmacSha3_256Output {
25    fn as_ref(&self) -> &[u8] {
26        &self.0
27    }
28}
29
30impl Mac for HmacSha3_256 {
31    type Output = HmacSha3_256Output;
32
33    fn new(key: &[u8]) -> PqcResult<Self> {
34        use hmac::Mac as HmacMac;
35        Ok(Self {
36            mac: <Hmac<Sha3_256> as HmacMac>::new_from_slice(key)
37                .map_err(|_| PqcError::InvalidKeyLength)?,
38        })
39    }
40
41    fn update(&mut self, data: &[u8]) {
42        self.mac.update(data);
43    }
44
45    fn finalize(self) -> Self::Output {
46        let result = self.mac.finalize();
47        let mut output = [0u8; 32];
48        output.copy_from_slice(&result.into_bytes());
49        HmacSha3_256Output(output)
50    }
51
52    fn verify(&self, tag: &[u8]) -> PqcResult<()> {
53        if tag.len() != 32 {
54            return Err(PqcError::InvalidSignature);
55        }
56
57        // Clone to get final MAC without consuming self
58        let mac_clone = self.mac.clone();
59        let result = mac_clone.finalize();
60
61        // Constant-time comparison
62        if result.into_bytes().ct_eq(tag).into() {
63            Ok(())
64        } else {
65            Err(PqcError::InvalidSignature)
66        }
67    }
68
69    fn output_size() -> usize {
70        32
71    }
72
73    fn name() -> &'static str {
74        "HMAC-SHA3-256"
75    }
76}
77
78/// HMAC-SHA3-512
79pub struct HmacSha3_512 {
80    mac: Hmac<Sha3_512>,
81}
82
83/// HMAC-SHA3-512 output
84#[derive(Clone)]
85pub struct HmacSha3_512Output([u8; 64]);
86
87impl AsRef<[u8]> for HmacSha3_512Output {
88    fn as_ref(&self) -> &[u8] {
89        &self.0
90    }
91}
92
93impl Mac for HmacSha3_512 {
94    type Output = HmacSha3_512Output;
95
96    fn new(key: &[u8]) -> PqcResult<Self> {
97        use hmac::Mac as HmacMac;
98        Ok(Self {
99            mac: <Hmac<Sha3_512> as HmacMac>::new_from_slice(key)
100                .map_err(|_| PqcError::InvalidKeyLength)?,
101        })
102    }
103
104    fn update(&mut self, data: &[u8]) {
105        self.mac.update(data);
106    }
107
108    fn finalize(self) -> Self::Output {
109        let result = self.mac.finalize();
110        let mut output = [0u8; 64];
111        output.copy_from_slice(&result.into_bytes());
112        HmacSha3_512Output(output)
113    }
114
115    fn verify(&self, tag: &[u8]) -> PqcResult<()> {
116        if tag.len() != 64 {
117            return Err(PqcError::InvalidSignature);
118        }
119
120        let mac_clone = self.mac.clone();
121        let result = mac_clone.finalize();
122
123        if result.into_bytes().ct_eq(tag).into() {
124            Ok(())
125        } else {
126            Err(PqcError::InvalidSignature)
127        }
128    }
129
130    fn output_size() -> usize {
131        64
132    }
133
134    fn name() -> &'static str {
135        "HMAC-SHA3-512"
136    }
137}
138
139/// High-level HMAC selector
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum HmacAlgorithm {
142    /// HMAC with SHA3-256
143    HmacSha3_256,
144    /// HMAC with SHA3-512
145    HmacSha3_512,
146}
147
148impl HmacAlgorithm {
149    /// Compute HMAC of data
150    ///
151    /// # Errors
152    ///
153    /// Returns an error if the key has invalid length or HMAC computation fails
154    pub fn mac(&self, key: &[u8], data: &[u8]) -> PqcResult<Vec<u8>> {
155        match self {
156            Self::HmacSha3_256 => {
157                let tag = HmacSha3_256::mac(key, data)?;
158                Ok(tag.as_ref().to_vec())
159            }
160            Self::HmacSha3_512 => {
161                let tag = HmacSha3_512::mac(key, data)?;
162                Ok(tag.as_ref().to_vec())
163            }
164        }
165    }
166
167    /// Verify HMAC tag (constant-time)
168    ///
169    /// # Errors
170    ///
171    /// Returns an error if verification fails or the tag is invalid
172    pub fn verify(&self, key: &[u8], data: &[u8], tag: &[u8]) -> PqcResult<()> {
173        let computed = self.mac(key, data)?;
174
175        if computed.len() != tag.len() {
176            return Err(PqcError::InvalidSignature);
177        }
178
179        if computed.ct_eq(tag).into() {
180            Ok(())
181        } else {
182            Err(PqcError::InvalidSignature)
183        }
184    }
185
186    /// Get the output size in bytes
187    #[must_use]
188    pub fn output_size(&self) -> usize {
189        match self {
190            Self::HmacSha3_256 => HmacSha3_256::output_size(),
191            Self::HmacSha3_512 => HmacSha3_512::output_size(),
192        }
193    }
194
195    /// Get the algorithm name
196    #[must_use]
197    pub fn name(&self) -> &'static str {
198        match self {
199            Self::HmacSha3_256 => HmacSha3_256::name(),
200            Self::HmacSha3_512 => HmacSha3_512::name(),
201        }
202    }
203}
204
205/// Helper functions for common HMAC operations
206pub mod helpers {
207    use super::{HmacAlgorithm, HmacSha3_256, HmacSha3_512, Mac, PqcResult, Zeroizing};
208
209    /// Compute HMAC-SHA3-256
210    ///
211    /// # Errors
212    ///
213    /// Returns an error if the key has invalid length or HMAC computation fails
214    pub fn hmac_sha3_256(key: &[u8], data: &[u8]) -> PqcResult<[u8; 32]> {
215        let tag = HmacSha3_256::mac(key, data)?;
216        let mut result = [0u8; 32];
217        result.copy_from_slice(tag.as_ref());
218        Ok(result)
219    }
220
221    /// Compute HMAC-SHA3-512
222    ///
223    /// # Errors
224    ///
225    /// Returns an error if the key has invalid length or HMAC computation fails
226    pub fn hmac_sha3_512(key: &[u8], data: &[u8]) -> PqcResult<[u8; 64]> {
227        let tag = HmacSha3_512::mac(key, data)?;
228        let mut result = [0u8; 64];
229        result.copy_from_slice(tag.as_ref());
230        Ok(result)
231    }
232
233    /// Verify HMAC-SHA3-256 (constant-time)
234    ///
235    /// # Errors
236    ///
237    /// Returns an error if verification fails or the tag is invalid
238    pub fn verify_hmac_sha3_256(key: &[u8], data: &[u8], tag: &[u8; 32]) -> PqcResult<()> {
239        HmacAlgorithm::HmacSha3_256.verify(key, data, tag)
240    }
241
242    /// Verify HMAC-SHA3-512 (constant-time)
243    ///
244    /// # Errors
245    ///
246    /// Returns an error if verification fails or the tag is invalid
247    pub fn verify_hmac_sha3_512(key: &[u8], data: &[u8], tag: &[u8; 64]) -> PqcResult<()> {
248        HmacAlgorithm::HmacSha3_512.verify(key, data, tag)
249    }
250
251    /// Generate a MAC key from key material
252    ///
253    /// # Errors
254    ///
255    /// Returns an error if key derivation fails
256    pub fn derive_mac_key(key_material: &[u8], context: &[u8]) -> PqcResult<Zeroizing<[u8; 32]>> {
257        use crate::api::kdf::HkdfSha3_256;
258        use crate::api::traits::Kdf;
259
260        let mut mac_key = Zeroizing::new([0u8; 32]);
261        HkdfSha3_256::derive(key_material, None, context, &mut mac_key[..])?;
262        Ok(mac_key)
263    }
264
265    /// Create an HMAC-based key confirmation value
266    ///
267    /// # Errors
268    ///
269    /// Returns an error if HMAC computation fails
270    pub fn key_confirmation(
271        shared_secret: &[u8],
272        initiator_data: &[u8],
273        responder_data: &[u8],
274    ) -> PqcResult<[u8; 32]> {
275        let mut combined = Vec::new();
276        combined.extend_from_slice(initiator_data);
277        combined.extend_from_slice(responder_data);
278
279        hmac_sha3_256(shared_secret, &combined)
280    }
281}
282
283#[cfg(test)]
284#[allow(clippy::indexing_slicing)]
285#[allow(clippy::unwrap_used, clippy::expect_used)]
286mod tests {
287    use super::*;
288
289    #[test]
290    fn test_hmac_sha3_256_basic() {
291        let key = b"test key";
292        let data = b"test data";
293
294        let tag1 = HmacSha3_256::mac(key, data).unwrap();
295        let tag2 = helpers::hmac_sha3_256(key, data).unwrap();
296
297        assert_eq!(tag1.as_ref(), &tag2);
298        assert_eq!(HmacSha3_256::output_size(), 32);
299        assert_eq!(HmacSha3_256::name(), "HMAC-SHA3-256");
300    }
301
302    #[test]
303    fn test_hmac_sha3_512_basic() {
304        let key = b"test key";
305        let data = b"test data";
306
307        let tag1 = HmacSha3_512::mac(key, data).unwrap();
308        let tag2 = helpers::hmac_sha3_512(key, data).unwrap();
309
310        assert_eq!(tag1.as_ref(), &tag2);
311        assert_eq!(HmacSha3_512::output_size(), 64);
312        assert_eq!(HmacSha3_512::name(), "HMAC-SHA3-512");
313    }
314
315    #[test]
316    fn test_hmac_incremental() {
317        let key = b"test key";
318        let data1 = b"first part";
319        let data2 = b"second part";
320        let combined = b"first partsecond part";
321
322        // SHA3-256 incremental
323        let mut mac = HmacSha3_256::new(key).unwrap();
324        mac.update(data1);
325        mac.update(data2);
326        let incremental_tag = mac.finalize();
327
328        let direct_tag = HmacSha3_256::mac(key, combined).unwrap();
329        assert_eq!(incremental_tag.as_ref(), direct_tag.as_ref());
330
331        // SHA3-512 incremental
332        let mut mac = HmacSha3_512::new(key).unwrap();
333        mac.update(data1);
334        mac.update(data2);
335        let incremental_tag = mac.finalize();
336
337        let direct_tag = HmacSha3_512::mac(key, combined).unwrap();
338        assert_eq!(incremental_tag.as_ref(), direct_tag.as_ref());
339    }
340
341    #[test]
342    fn test_hmac_verification_success() {
343        let key = b"test key";
344        let data = b"test data";
345
346        // SHA3-256
347        let tag = helpers::hmac_sha3_256(key, data).unwrap();
348        assert!(helpers::verify_hmac_sha3_256(key, data, &tag).is_ok());
349
350        // SHA3-512
351        let tag = helpers::hmac_sha3_512(key, data).unwrap();
352        assert!(helpers::verify_hmac_sha3_512(key, data, &tag).is_ok());
353    }
354
355    #[test]
356    fn test_hmac_verification_failure() {
357        let key = b"test key";
358        let data = b"test data";
359
360        // SHA3-256 with wrong tag
361        let mut wrong_tag = helpers::hmac_sha3_256(key, data).unwrap();
362        wrong_tag[0] ^= 0x01; // Flip a bit
363        assert!(helpers::verify_hmac_sha3_256(key, data, &wrong_tag).is_err());
364
365        // SHA3-512 with wrong tag
366        let mut wrong_tag = helpers::hmac_sha3_512(key, data).unwrap();
367        wrong_tag[0] ^= 0x01;
368        assert!(helpers::verify_hmac_sha3_512(key, data, &wrong_tag).is_err());
369
370        // Wrong data
371        let tag = helpers::hmac_sha3_256(key, data).unwrap();
372        assert!(helpers::verify_hmac_sha3_256(key, b"wrong data", &tag).is_err());
373
374        // Wrong key
375        let tag = helpers::hmac_sha3_256(key, data).unwrap();
376        assert!(helpers::verify_hmac_sha3_256(b"wrong key", data, &tag).is_err());
377    }
378
379    #[test]
380    fn test_hmac_algorithm_enum() {
381        let key = b"test key";
382        let data = b"test data";
383
384        let tag1 = HmacAlgorithm::HmacSha3_256.mac(key, data).unwrap();
385        assert_eq!(tag1.len(), 32);
386        assert_eq!(HmacAlgorithm::HmacSha3_256.output_size(), 32);
387        assert_eq!(HmacAlgorithm::HmacSha3_256.name(), "HMAC-SHA3-256");
388
389        let tag2 = HmacAlgorithm::HmacSha3_512.mac(key, data).unwrap();
390        assert_eq!(tag2.len(), 64);
391
392        // Verify
393        assert!(HmacAlgorithm::HmacSha3_256.verify(key, data, &tag1).is_ok());
394        assert!(HmacAlgorithm::HmacSha3_512.verify(key, data, &tag2).is_ok());
395    }
396
397    #[test]
398    fn test_derive_mac_key() {
399        let key_material = b"key material";
400        let context = b"MAC key derivation";
401
402        let mac_key1 = helpers::derive_mac_key(key_material, context).unwrap();
403        assert_eq!(mac_key1.len(), 32);
404
405        // Should be deterministic
406        let mac_key2 = helpers::derive_mac_key(key_material, context).unwrap();
407        assert_eq!(&mac_key1[..], &mac_key2[..]);
408
409        // Different context should give different key
410        let mac_key3 = helpers::derive_mac_key(key_material, b"different context").unwrap();
411        assert_ne!(&mac_key1[..], &mac_key3[..]);
412    }
413
414    #[test]
415    fn test_key_confirmation() {
416        let shared_secret = b"shared secret from key exchange";
417        let party_a = b"Alice's public data";
418        let party_b = b"Bob's public data";
419
420        let confirmation1 = helpers::key_confirmation(shared_secret, party_a, party_b).unwrap();
421        assert_eq!(confirmation1.len(), 32);
422
423        // Should be deterministic
424        let confirmation2 = helpers::key_confirmation(shared_secret, party_a, party_b).unwrap();
425        assert_eq!(confirmation1, confirmation2);
426
427        // Order matters
428        let confirmation3 = helpers::key_confirmation(shared_secret, party_b, party_a).unwrap();
429        assert_ne!(confirmation1, confirmation3);
430    }
431}