Skip to main content

oxicrypto_mac/
lib.rs

1#![forbid(unsafe_code)]
2
3//! Pure Rust MAC implementations for the OxiCrypto stack.
4//!
5//! Provides [`Mac`] and [`StreamingMac`] trait wrappers for:
6//! - HMAC-SHA-256 / SHA-384 / SHA-512 (one-shot + streaming + truncated)
7//! - HMAC-SHA3-256 / SHA3-512
8//! - Poly1305 (one-time MAC)
9//! - CMAC-AES-128 / CMAC-AES-256
10//! - KMAC128 / KMAC256 (SP 800-185, via `tiny-keccak`)
11//!
12//! All MAC verifications use constant-time comparison via the `subtle` crate.
13//!
14//! ## Hash-agnostic HMAC
15//!
16//! [`hmac_streaming_hash::StreamingHashHmac`] provides a generic HMAC adapter
17//! that accepts any [`oxicrypto_core::StreamingHash`] implementation.
18//!
19//! ## Architecture: Internal Consistency with oxicrypto-kdf and oxicrypto-hash
20//!
21//! ### HKDF / PBKDF2 internal consistency
22//!
23//! `oxicrypto-kdf` (HKDF and PBKDF2) and `oxicrypto-mac` (HMAC) currently use
24//! separate call paths to the same underlying `hmac` workspace crate. This is
25//! an intentional architecture decision:
26//!
27//! - Both `oxicrypto-kdf` and `oxicrypto-mac` ultimately delegate to the same
28//!   `hmac = "0.13"` crate — Cargo deduplicates the single copy at build time.
29//!   Behavior is therefore byte-for-byte identical; there is no actual
30//!   inconsistency in outputs.
31//!
32//! - Refactoring `oxicrypto-kdf` to route HKDF/PBKDF2 calls *through*
33//!   `oxicrypto-mac`'s public `HmacSha256`/`HmacSha512` types would add a
34//!   crate dependency edge (`oxicrypto-kdf` → `oxicrypto-mac`) and require
35//!   plumbing the KDF trait bounds through the `Mac` trait boundary — a
36//!   non-trivial refactor with no output correctness benefit (the outputs are
37//!   already identical).
38//!
39//! - This is deferred as a post-1.0 ergonomic cleanup. Until then, callers
40//!   that need HKDF-then-HMAC in the same context can use `oxicrypto-kdf`
41//!   for key derivation and `oxicrypto-mac` for MAC computation independently,
42//!   relying on the fact that both use the same underlying `hmac` implementation.
43//!
44//! ### KMAC / SHA3 sponge sharing
45//!
46//! `oxicrypto-mac` KMAC128/KMAC256 use `tiny-keccak 2.0.2` (with the `kmac`
47//! feature), while `oxicrypto-hash` SHA3 uses the `sha3 0.12` crate. Both
48//! implement the same Keccak-f\[1600\] permutation internally, so there is no
49//! cryptographic inconsistency — the sponge state is not logically shared.
50//!
51//! Sharing the sponge context between crates would require either:
52//! 1. Moving KMAC into `oxicrypto-hash` and re-exporting it from `oxicrypto-mac`, or
53//! 2. Exposing `sha3` internal sponge state, which that crate deliberately does not.
54//!
55//! `tiny-keccak` is kept as the KMAC backend because it provides the correct
56//! KMAC domain separation (pad byte `0x04` vs Keccak `0x01`) and the
57//! SP 800-185-compliant `encode_string` / `bytepad` encoding. This is a
58//! correct, tested, and auditable choice. The minor code-size duplication of
59//! having two Keccak implementations is accepted as a pragmatic trade-off until
60//! a unified SP 800-185 implementation is available in the `sha3` workspace dep.
61
62extern crate alloc;
63
64pub mod hmac_streaming_hash;
65pub use hmac_streaming_hash::{
66    hmac_with_streaming_hash, StreamingHashHmac, StreamingHashHmacSession,
67};
68
69use digest::KeyInit;
70use hmac::Mac as HmacMac;
71use oxicrypto_core::{CryptoError, Mac, StreamingMac};
72use subtle::ConstantTimeEq;
73
74// ── HMAC-SHA-256 ──────────────────────────────────────────────────────────────
75
76/// HMAC-SHA-256 message authentication code (32-byte tag).
77#[derive(Debug, Default, Clone, Copy)]
78pub struct HmacSha256;
79
80impl Mac for HmacSha256 {
81    fn name(&self) -> &'static str {
82        "HMAC-SHA-256"
83    }
84    fn key_len(&self) -> usize {
85        32
86    }
87    fn output_len(&self) -> usize {
88        32
89    }
90    fn min_key_len(&self) -> usize {
91        32
92    }
93    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
94        if out.len() < 32 {
95            return Err(CryptoError::BufferTooSmall);
96        }
97        let mut mac =
98            hmac::Hmac::<sha2::Sha256>::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
99        mac.update(msg);
100        let result = mac.finalize().into_bytes();
101        out[..32].copy_from_slice(&result);
102        Ok(())
103    }
104    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
105        if tag.len() != 32 {
106            return Err(CryptoError::InvalidTag);
107        }
108        let mut expected = [0u8; 32];
109        self.mac(key, msg, &mut expected)?;
110        if expected.ct_eq(tag).into() {
111            Ok(())
112        } else {
113            Err(CryptoError::InvalidTag)
114        }
115    }
116}
117
118impl HmacSha256 {
119    /// Tag length in bytes.
120    pub const OUTPUT_LEN: usize = 32;
121
122    /// Create a pre-keyed HMAC-SHA-256 instance that implements [`StreamingMac`].
123    pub fn new_keyed(key: &[u8]) -> Result<HmacSha256Keyed, CryptoError> {
124        HmacSha256Keyed::new(key)
125    }
126
127    /// Compute a truncated HMAC-SHA-256 tag.
128    ///
129    /// Writes the first `out.len()` bytes of the full 32-byte HMAC into `out`.
130    /// Returns [`CryptoError::BadInput`] if `out.len() < 16` (minimum safe
131    /// truncation length per NIST SP 800-117).
132    pub fn mac_truncated(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
133        let n = out.len();
134        if n < 16 {
135            return Err(CryptoError::BadInput);
136        }
137        let mut full = [0u8; 32];
138        self.mac(key, msg, &mut full)?;
139        out.copy_from_slice(&full[..n]);
140        Ok(())
141    }
142
143    /// Verify a truncated HMAC-SHA-256 tag in constant time.
144    ///
145    /// Returns [`CryptoError::BadInput`] if `tag.len() < 16`, or
146    /// [`CryptoError::InvalidTag`] on mismatch.
147    pub fn verify_truncated(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
148        let n = tag.len();
149        if n < 16 {
150            return Err(CryptoError::BadInput);
151        }
152        let mut buf = [0u8; 32];
153        self.mac(key, msg, &mut buf)?;
154        if buf[..n].ct_eq(tag).into() {
155            Ok(())
156        } else {
157            Err(CryptoError::InvalidTag)
158        }
159    }
160}
161
162// ── HMAC-SHA-512 ──────────────────────────────────────────────────────────────
163
164/// HMAC-SHA-512 message authentication code (64-byte tag).
165#[derive(Debug, Default, Clone, Copy)]
166pub struct HmacSha512;
167
168impl Mac for HmacSha512 {
169    fn name(&self) -> &'static str {
170        "HMAC-SHA-512"
171    }
172    fn key_len(&self) -> usize {
173        64
174    }
175    fn output_len(&self) -> usize {
176        64
177    }
178    fn min_key_len(&self) -> usize {
179        64
180    }
181    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
182        if out.len() < 64 {
183            return Err(CryptoError::BufferTooSmall);
184        }
185        let mut mac =
186            hmac::Hmac::<sha2::Sha512>::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
187        mac.update(msg);
188        let result = mac.finalize().into_bytes();
189        out[..64].copy_from_slice(&result);
190        Ok(())
191    }
192    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
193        if tag.len() != 64 {
194            return Err(CryptoError::InvalidTag);
195        }
196        let mut expected = [0u8; 64];
197        self.mac(key, msg, &mut expected)?;
198        if expected.ct_eq(tag).into() {
199            Ok(())
200        } else {
201            Err(CryptoError::InvalidTag)
202        }
203    }
204}
205
206impl HmacSha512 {
207    /// Tag length in bytes.
208    pub const OUTPUT_LEN: usize = 64;
209
210    /// Create a pre-keyed HMAC-SHA-512 instance that implements [`StreamingMac`].
211    pub fn new_keyed(key: &[u8]) -> Result<HmacSha512Keyed, CryptoError> {
212        HmacSha512Keyed::new(key)
213    }
214
215    /// Compute a truncated HMAC-SHA-512 tag.
216    ///
217    /// Writes the first `out.len()` bytes of the full 64-byte HMAC into `out`.
218    /// Returns [`CryptoError::BadInput`] if `out.len() < 16`.
219    pub fn mac_truncated(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
220        let n = out.len();
221        if n < 16 {
222            return Err(CryptoError::BadInput);
223        }
224        let mut full = [0u8; 64];
225        self.mac(key, msg, &mut full)?;
226        out.copy_from_slice(&full[..n]);
227        Ok(())
228    }
229
230    /// Verify a truncated HMAC-SHA-512 tag in constant time.
231    ///
232    /// Returns [`CryptoError::BadInput`] if `tag.len() < 16`, or
233    /// [`CryptoError::InvalidTag`] on mismatch.
234    pub fn verify_truncated(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
235        let n = tag.len();
236        if n < 16 {
237            return Err(CryptoError::BadInput);
238        }
239        let mut buf = [0u8; 64];
240        self.mac(key, msg, &mut buf)?;
241        if buf[..n].ct_eq(tag).into() {
242            Ok(())
243        } else {
244            Err(CryptoError::InvalidTag)
245        }
246    }
247}
248
249// ── HMAC-SHA-384 ──────────────────────────────────────────────────────────────
250
251/// HMAC-SHA-384 message authentication code (48-byte tag).
252#[derive(Debug, Default, Clone, Copy)]
253pub struct HmacSha384;
254
255impl Mac for HmacSha384 {
256    fn name(&self) -> &'static str {
257        "HMAC-SHA-384"
258    }
259    fn key_len(&self) -> usize {
260        48
261    }
262    fn output_len(&self) -> usize {
263        48
264    }
265    fn min_key_len(&self) -> usize {
266        48
267    }
268    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
269        if out.len() < 48 {
270            return Err(CryptoError::BufferTooSmall);
271        }
272        let mut mac =
273            hmac::Hmac::<sha2::Sha384>::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
274        mac.update(msg);
275        let result = mac.finalize().into_bytes();
276        out[..48].copy_from_slice(&result);
277        Ok(())
278    }
279    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
280        if tag.len() != 48 {
281            return Err(CryptoError::InvalidTag);
282        }
283        let mut expected = [0u8; 48];
284        self.mac(key, msg, &mut expected)?;
285        if expected.ct_eq(tag).into() {
286            Ok(())
287        } else {
288            Err(CryptoError::InvalidTag)
289        }
290    }
291}
292
293impl HmacSha384 {
294    /// Tag length in bytes.
295    pub const OUTPUT_LEN: usize = 48;
296
297    /// Create a pre-keyed HMAC-SHA-384 instance that implements [`StreamingMac`].
298    pub fn new_keyed(key: &[u8]) -> Result<HmacSha384Keyed, CryptoError> {
299        HmacSha384Keyed::new(key)
300    }
301
302    /// Compute a truncated HMAC-SHA-384 tag.
303    ///
304    /// Writes the first `out.len()` bytes of the full 48-byte HMAC into `out`.
305    /// Returns [`CryptoError::BadInput`] if `out.len() < 16`.
306    pub fn mac_truncated(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
307        let n = out.len();
308        if n < 16 {
309            return Err(CryptoError::BadInput);
310        }
311        let mut full = [0u8; 48];
312        self.mac(key, msg, &mut full)?;
313        out.copy_from_slice(&full[..n]);
314        Ok(())
315    }
316
317    /// Verify a truncated HMAC-SHA-384 tag in constant time.
318    ///
319    /// Returns [`CryptoError::BadInput`] if `tag.len() < 16`, or
320    /// [`CryptoError::InvalidTag`] on mismatch.
321    pub fn verify_truncated(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
322        let n = tag.len();
323        if n < 16 {
324            return Err(CryptoError::BadInput);
325        }
326        let mut buf = [0u8; 48];
327        self.mac(key, msg, &mut buf)?;
328        if buf[..n].ct_eq(tag).into() {
329            Ok(())
330        } else {
331            Err(CryptoError::InvalidTag)
332        }
333    }
334}
335
336// ── StreamingMac adapter (generic HMAC) ───────────────────────────────────────
337
338/// Generic streaming MAC adapter wrapping `hmac::Hmac<D>`.
339///
340/// Implements [`StreamingMac`]: feed chunks with `update`, then consume with
341/// `finalize` or `verify`.
342pub struct HmacStreamingAdapter<D: hmac::digest::block_api::EagerHash>
343where
344    hmac::Hmac<D>: HmacMac + KeyInit,
345{
346    inner: hmac::Hmac<D>,
347}
348
349impl<D: hmac::digest::block_api::EagerHash> HmacStreamingAdapter<D>
350where
351    hmac::Hmac<D>: HmacMac + KeyInit,
352{
353    /// Create a new streaming adapter with the given key.
354    ///
355    /// Returns [`CryptoError::InvalidKey`] if the key is rejected by HMAC.
356    pub fn new(key: &[u8]) -> Result<Self, CryptoError> {
357        let inner = hmac::Hmac::<D>::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
358        Ok(Self { inner })
359    }
360}
361
362impl<D: hmac::digest::block_api::EagerHash + Send> StreamingMac for HmacStreamingAdapter<D>
363where
364    hmac::Hmac<D>: HmacMac + KeyInit + Send,
365{
366    fn update(&mut self, data: &[u8]) {
367        self.inner.update(data);
368    }
369
370    fn finalize(self, out: &mut [u8]) -> Result<(), CryptoError> {
371        let tag = self.inner.finalize().into_bytes();
372        if out.len() < tag.len() {
373            return Err(CryptoError::BufferTooSmall);
374        }
375        out[..tag.len()].copy_from_slice(&tag);
376        Ok(())
377    }
378
379    fn verify(self, expected: &[u8]) -> Result<(), CryptoError> {
380        let tag = self.inner.finalize().into_bytes();
381        if tag.len() != expected.len() {
382            return Err(CryptoError::InvalidTag);
383        }
384        if tag.as_slice().ct_eq(expected).into() {
385            Ok(())
386        } else {
387            Err(CryptoError::InvalidTag)
388        }
389    }
390}
391
392/// Streaming HMAC-SHA-256 adapter.
393pub type HmacSha256Streaming = HmacStreamingAdapter<sha2::Sha256>;
394/// Streaming HMAC-SHA-384 adapter.
395pub type HmacSha384Streaming = HmacStreamingAdapter<sha2::Sha384>;
396/// Streaming HMAC-SHA-512 adapter.
397pub type HmacSha512Streaming = HmacStreamingAdapter<sha2::Sha512>;
398
399// ── Pre-keyed HMAC types ──────────────────────────────────────────────────────
400
401/// Pre-keyed HMAC-SHA-256 instance; implements [`StreamingMac`].
402///
403/// Created via [`HmacSha256::new_keyed`].  The key is bound at construction
404/// time; subsequent calls to `update` feed message chunks, and `finalize` or
405/// `verify` consume the instance.
406pub struct HmacSha256Keyed(hmac::Hmac<sha2::Sha256>);
407
408impl HmacSha256Keyed {
409    /// Create a pre-keyed HMAC-SHA-256 instance.
410    pub fn new(key: &[u8]) -> Result<Self, CryptoError> {
411        hmac::Hmac::<sha2::Sha256>::new_from_slice(key)
412            .map(Self)
413            .map_err(|_| CryptoError::InvalidKey)
414    }
415}
416
417impl StreamingMac for HmacSha256Keyed {
418    fn update(&mut self, data: &[u8]) {
419        HmacMac::update(&mut self.0, data);
420    }
421    fn finalize(self, out: &mut [u8]) -> Result<(), CryptoError> {
422        let result = HmacMac::finalize(self.0).into_bytes();
423        if out.len() < result.len() {
424            return Err(CryptoError::BufferTooSmall);
425        }
426        out[..result.len()].copy_from_slice(&result);
427        Ok(())
428    }
429    fn verify(self, expected: &[u8]) -> Result<(), CryptoError> {
430        let tag = HmacMac::finalize(self.0).into_bytes();
431        if tag.len() != expected.len() {
432            return Err(CryptoError::InvalidTag);
433        }
434        if tag.as_slice().ct_eq(expected).into() {
435            Ok(())
436        } else {
437            Err(CryptoError::InvalidTag)
438        }
439    }
440}
441
442/// Pre-keyed HMAC-SHA-512 instance; implements [`StreamingMac`].
443///
444/// Created via [`HmacSha512::new_keyed`].  The key is bound at construction
445/// time; subsequent calls to `update` feed message chunks, and `finalize` or
446/// `verify` consume the instance.
447pub struct HmacSha512Keyed(hmac::Hmac<sha2::Sha512>);
448
449impl HmacSha512Keyed {
450    /// Create a pre-keyed HMAC-SHA-512 instance.
451    pub fn new(key: &[u8]) -> Result<Self, CryptoError> {
452        hmac::Hmac::<sha2::Sha512>::new_from_slice(key)
453            .map(Self)
454            .map_err(|_| CryptoError::InvalidKey)
455    }
456}
457
458impl StreamingMac for HmacSha512Keyed {
459    fn update(&mut self, data: &[u8]) {
460        HmacMac::update(&mut self.0, data);
461    }
462    fn finalize(self, out: &mut [u8]) -> Result<(), CryptoError> {
463        let result = HmacMac::finalize(self.0).into_bytes();
464        if out.len() < result.len() {
465            return Err(CryptoError::BufferTooSmall);
466        }
467        out[..result.len()].copy_from_slice(&result);
468        Ok(())
469    }
470    fn verify(self, expected: &[u8]) -> Result<(), CryptoError> {
471        let tag = HmacMac::finalize(self.0).into_bytes();
472        if tag.len() != expected.len() {
473            return Err(CryptoError::InvalidTag);
474        }
475        if tag.as_slice().ct_eq(expected).into() {
476            Ok(())
477        } else {
478            Err(CryptoError::InvalidTag)
479        }
480    }
481}
482
483/// Pre-keyed HMAC-SHA-384 instance; implements [`StreamingMac`].
484///
485/// Created via [`HmacSha384::new_keyed`].  The key is bound at construction
486/// time; subsequent calls to `update` feed message chunks, and `finalize` or
487/// `verify` consume the instance.
488pub struct HmacSha384Keyed(hmac::Hmac<sha2::Sha384>);
489
490impl HmacSha384Keyed {
491    /// Create a pre-keyed HMAC-SHA-384 instance.
492    pub fn new(key: &[u8]) -> Result<Self, CryptoError> {
493        hmac::Hmac::<sha2::Sha384>::new_from_slice(key)
494            .map(Self)
495            .map_err(|_| CryptoError::InvalidKey)
496    }
497}
498
499impl StreamingMac for HmacSha384Keyed {
500    fn update(&mut self, data: &[u8]) {
501        HmacMac::update(&mut self.0, data);
502    }
503    fn finalize(self, out: &mut [u8]) -> Result<(), CryptoError> {
504        let result = HmacMac::finalize(self.0).into_bytes();
505        if out.len() < result.len() {
506            return Err(CryptoError::BufferTooSmall);
507        }
508        out[..result.len()].copy_from_slice(&result);
509        Ok(())
510    }
511    fn verify(self, expected: &[u8]) -> Result<(), CryptoError> {
512        let tag = HmacMac::finalize(self.0).into_bytes();
513        if tag.len() != expected.len() {
514            return Err(CryptoError::InvalidTag);
515        }
516        if tag.as_slice().ct_eq(expected).into() {
517            Ok(())
518        } else {
519            Err(CryptoError::InvalidTag)
520        }
521    }
522}
523
524// ── HMAC-SHA3-256 ─────────────────────────────────────────────────────────────
525
526/// HMAC-SHA3-256 message authentication code (32-byte tag).
527///
528/// Uses `hmac::SimpleHmac` because sha3 0.12's types do not expose the
529/// block-level `CoreProxy` trait required by `hmac::Hmac<D>`.
530#[derive(Debug, Default, Clone, Copy)]
531pub struct HmacSha3_256;
532
533impl Mac for HmacSha3_256 {
534    fn name(&self) -> &'static str {
535        "HMAC-SHA3-256"
536    }
537    fn key_len(&self) -> usize {
538        32
539    }
540    fn output_len(&self) -> usize {
541        32
542    }
543    fn min_key_len(&self) -> usize {
544        32
545    }
546    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
547        use digest::KeyInit as _;
548
549        if out.len() < 32 {
550            return Err(CryptoError::BufferTooSmall);
551        }
552        let mut mac = hmac::SimpleHmac::<sha3::Sha3_256>::new_from_slice(key)
553            .map_err(|_| CryptoError::InvalidKey)?;
554        HmacMac::update(&mut mac, msg);
555        let result = HmacMac::finalize(mac).into_bytes();
556        out[..32].copy_from_slice(&result);
557        Ok(())
558    }
559    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
560        if tag.len() != 32 {
561            return Err(CryptoError::InvalidTag);
562        }
563        let mut expected = [0u8; 32];
564        self.mac(key, msg, &mut expected)?;
565        if expected.ct_eq(tag).into() {
566            Ok(())
567        } else {
568            Err(CryptoError::InvalidTag)
569        }
570    }
571}
572
573impl HmacSha3_256 {
574    /// Tag length in bytes.
575    pub const OUTPUT_LEN: usize = 32;
576}
577
578// ── HMAC-SHA3-512 ─────────────────────────────────────────────────────────────
579
580/// HMAC-SHA3-512 message authentication code (64-byte tag).
581///
582/// Uses `hmac::SimpleHmac` because sha3 0.12's types do not expose the
583/// block-level `CoreProxy` trait required by `hmac::Hmac<D>`.
584#[derive(Debug, Default, Clone, Copy)]
585pub struct HmacSha3_512;
586
587impl Mac for HmacSha3_512 {
588    fn name(&self) -> &'static str {
589        "HMAC-SHA3-512"
590    }
591    fn key_len(&self) -> usize {
592        64
593    }
594    fn output_len(&self) -> usize {
595        64
596    }
597    fn min_key_len(&self) -> usize {
598        64
599    }
600    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
601        use digest::KeyInit as _;
602
603        if out.len() < 64 {
604            return Err(CryptoError::BufferTooSmall);
605        }
606        let mut mac = hmac::SimpleHmac::<sha3::Sha3_512>::new_from_slice(key)
607            .map_err(|_| CryptoError::InvalidKey)?;
608        HmacMac::update(&mut mac, msg);
609        let result = HmacMac::finalize(mac).into_bytes();
610        out[..64].copy_from_slice(&result);
611        Ok(())
612    }
613    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
614        if tag.len() != 64 {
615            return Err(CryptoError::InvalidTag);
616        }
617        let mut expected = [0u8; 64];
618        self.mac(key, msg, &mut expected)?;
619        if expected.ct_eq(tag).into() {
620            Ok(())
621        } else {
622            Err(CryptoError::InvalidTag)
623        }
624    }
625}
626
627impl HmacSha3_512 {
628    /// Tag length in bytes.
629    pub const OUTPUT_LEN: usize = 64;
630}
631
632// ── Poly1305 ──────────────────────────────────────────────────────────────────
633
634/// Poly1305 one-time message authentication code (16-byte tag).
635///
636/// # Security warning
637///
638/// Poly1305 is a **one-time MAC**: the 32-byte key MUST NOT be reused for
639/// different messages.  Re-use of the same key across messages completely
640/// destroys the security guarantee.  In practice, derive a fresh per-message
641/// key from a stream cipher (e.g. ChaCha20) or a KDF.
642#[derive(Debug, Default, Clone, Copy)]
643pub struct Poly1305Mac;
644
645impl Mac for Poly1305Mac {
646    fn name(&self) -> &'static str {
647        "Poly1305"
648    }
649    fn key_len(&self) -> usize {
650        32
651    }
652    fn output_len(&self) -> usize {
653        16
654    }
655    fn min_key_len(&self) -> usize {
656        32
657    }
658    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
659        use poly1305::universal_hash::KeyInit as _;
660
661        if key.len() != 32 {
662            return Err(CryptoError::InvalidKey);
663        }
664        if out.len() < 16 {
665            return Err(CryptoError::BufferTooSmall);
666        }
667        let key_arr = poly1305::Key::try_from(key).map_err(|_| CryptoError::InvalidKey)?;
668        let mac = poly1305::Poly1305::new(&key_arr);
669        // compute_unpadded is the standard Poly1305 MAC computation:
670        // it adds a 0x01 high-bit to partial final blocks (per RFC 8439 §2.5).
671        // update_padded would zero-pad partial blocks, which is incorrect for MAC.
672        let tag = mac.compute_unpadded(msg);
673        out[..16].copy_from_slice(tag.as_slice());
674        Ok(())
675    }
676    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
677        if tag.len() != 16 {
678            return Err(CryptoError::InvalidTag);
679        }
680        let mut computed = [0u8; 16];
681        self.mac(key, msg, &mut computed)?;
682        if computed.ct_eq(tag).into() {
683            Ok(())
684        } else {
685            Err(CryptoError::InvalidTag)
686        }
687    }
688}
689
690impl Poly1305Mac {
691    /// Tag length in bytes.
692    pub const OUTPUT_LEN: usize = 16;
693}
694
695// ── CMAC-AES-128 ──────────────────────────────────────────────────────────────
696
697/// CMAC-AES-128 message authentication code (16-byte tag).
698///
699/// Uses `cmac 0.8` with `aes 0.9` (cipher 0.5 trait chain).
700#[derive(Debug, Default, Clone, Copy)]
701pub struct CmacAes128;
702
703impl Mac for CmacAes128 {
704    fn name(&self) -> &'static str {
705        "CMAC-AES-128"
706    }
707    fn key_len(&self) -> usize {
708        16
709    }
710    fn output_len(&self) -> usize {
711        16
712    }
713    fn min_key_len(&self) -> usize {
714        16
715    }
716    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
717        use cmac::Mac as _;
718        use digest::KeyInit as _;
719
720        if out.len() < 16 {
721            return Err(CryptoError::BufferTooSmall);
722        }
723        let mut mac = cmac::Cmac::<aes_cipher05::Aes128>::new_from_slice(key)
724            .map_err(|_| CryptoError::InvalidKey)?;
725        mac.update(msg);
726        let result = mac.finalize().into_bytes();
727        out[..16].copy_from_slice(&result);
728        Ok(())
729    }
730    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
731        if tag.len() != 16 {
732            return Err(CryptoError::InvalidTag);
733        }
734        let mut expected = [0u8; 16];
735        self.mac(key, msg, &mut expected)?;
736        if expected.ct_eq(tag).into() {
737            Ok(())
738        } else {
739            Err(CryptoError::InvalidTag)
740        }
741    }
742}
743
744impl CmacAes128 {
745    /// Tag length in bytes.
746    pub const OUTPUT_LEN: usize = 16;
747}
748
749// ── CMAC-AES-256 ──────────────────────────────────────────────────────────────
750
751/// CMAC-AES-256 message authentication code (16-byte tag).
752///
753/// Uses `cmac 0.8` with `aes 0.9` (cipher 0.5 trait chain).
754#[derive(Debug, Default, Clone, Copy)]
755pub struct CmacAes256;
756
757impl Mac for CmacAes256 {
758    fn name(&self) -> &'static str {
759        "CMAC-AES-256"
760    }
761    fn key_len(&self) -> usize {
762        32
763    }
764    fn output_len(&self) -> usize {
765        16
766    }
767    fn min_key_len(&self) -> usize {
768        32
769    }
770    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
771        use cmac::Mac as _;
772        use digest::KeyInit as _;
773
774        if out.len() < 16 {
775            return Err(CryptoError::BufferTooSmall);
776        }
777        let mut mac = cmac::Cmac::<aes_cipher05::Aes256>::new_from_slice(key)
778            .map_err(|_| CryptoError::InvalidKey)?;
779        mac.update(msg);
780        let result = mac.finalize().into_bytes();
781        out[..16].copy_from_slice(&result);
782        Ok(())
783    }
784    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
785        if tag.len() != 16 {
786            return Err(CryptoError::InvalidTag);
787        }
788        let mut expected = [0u8; 16];
789        self.mac(key, msg, &mut expected)?;
790        if expected.ct_eq(tag).into() {
791            Ok(())
792        } else {
793            Err(CryptoError::InvalidTag)
794        }
795    }
796}
797
798impl CmacAes256 {
799    /// Tag length in bytes.
800    pub const OUTPUT_LEN: usize = 16;
801}
802
803// ── KMAC128 ───────────────────────────────────────────────────────────────────
804
805/// KMAC128 message authentication code (SP 800-185).
806///
807/// Variable-length output; the default output length is 32 bytes.
808/// Uses a customization string (may be empty) for domain separation.
809pub struct Kmac128 {
810    /// Customization string for domain separation (SP 800-185 §3.3).
811    custom: alloc::vec::Vec<u8>,
812    /// Output tag length in bytes (minimum 1, default 32).
813    output_len: usize,
814}
815
816impl Kmac128 {
817    /// Create a new KMAC128 with the given customization string and output length.
818    ///
819    /// Returns [`CryptoError::BadInput`] if `output_len` is 0.
820    pub fn new(custom: &[u8], output_len: usize) -> Result<Self, CryptoError> {
821        if output_len == 0 {
822            return Err(CryptoError::BadInput);
823        }
824        Ok(Self {
825            custom: custom.to_vec(),
826            output_len,
827        })
828    }
829}
830
831impl core::fmt::Debug for Kmac128 {
832    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
833        f.debug_struct("Kmac128")
834            .field("output_len", &self.output_len)
835            .finish()
836    }
837}
838
839impl Mac for Kmac128 {
840    fn name(&self) -> &'static str {
841        "KMAC128"
842    }
843    fn key_len(&self) -> usize {
844        // KMAC accepts variable-length keys; recommend >= 16 bytes.
845        16
846    }
847    fn output_len(&self) -> usize {
848        self.output_len
849    }
850    fn min_key_len(&self) -> usize {
851        16
852    }
853    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
854        use tiny_keccak::Hasher as _;
855        if out.len() < self.output_len {
856            return Err(CryptoError::BufferTooSmall);
857        }
858        let mut kmac = tiny_keccak::Kmac::v128(key, &self.custom);
859        kmac.update(msg);
860        kmac.finalize(&mut out[..self.output_len]);
861        Ok(())
862    }
863    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
864        if tag.len() != self.output_len {
865            return Err(CryptoError::InvalidTag);
866        }
867        let mut computed = alloc::vec![0u8; self.output_len];
868        self.mac(key, msg, &mut computed)?;
869        if computed.ct_eq(tag).into() {
870            Ok(())
871        } else {
872            Err(CryptoError::InvalidTag)
873        }
874    }
875}
876
877// ── KMAC256 ───────────────────────────────────────────────────────────────────
878
879/// KMAC256 message authentication code (SP 800-185).
880///
881/// Variable-length output; the default output length is 64 bytes.
882/// Uses a customization string (may be empty) for domain separation.
883pub struct Kmac256 {
884    /// Customization string for domain separation.
885    custom: alloc::vec::Vec<u8>,
886    /// Output tag length in bytes (minimum 1, default 64).
887    output_len: usize,
888}
889
890impl Kmac256 {
891    /// Create a new KMAC256 with the given customization string and output length.
892    ///
893    /// Returns [`CryptoError::BadInput`] if `output_len` is 0.
894    pub fn new(custom: &[u8], output_len: usize) -> Result<Self, CryptoError> {
895        if output_len == 0 {
896            return Err(CryptoError::BadInput);
897        }
898        Ok(Self {
899            custom: custom.to_vec(),
900            output_len,
901        })
902    }
903}
904
905impl core::fmt::Debug for Kmac256 {
906    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
907        f.debug_struct("Kmac256")
908            .field("output_len", &self.output_len)
909            .finish()
910    }
911}
912
913impl Mac for Kmac256 {
914    fn name(&self) -> &'static str {
915        "KMAC256"
916    }
917    fn key_len(&self) -> usize {
918        32
919    }
920    fn output_len(&self) -> usize {
921        self.output_len
922    }
923    fn min_key_len(&self) -> usize {
924        16
925    }
926    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
927        use tiny_keccak::Hasher as _;
928        if out.len() < self.output_len {
929            return Err(CryptoError::BufferTooSmall);
930        }
931        let mut kmac = tiny_keccak::Kmac::v256(key, &self.custom);
932        kmac.update(msg);
933        kmac.finalize(&mut out[..self.output_len]);
934        Ok(())
935    }
936    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
937        if tag.len() != self.output_len {
938            return Err(CryptoError::InvalidTag);
939        }
940        let mut computed = alloc::vec![0u8; self.output_len];
941        self.mac(key, msg, &mut computed)?;
942        if computed.ct_eq(tag).into() {
943            Ok(())
944        } else {
945            Err(CryptoError::InvalidTag)
946        }
947    }
948}
949
950// ── KMAC-XOF free functions ───────────────────────────────────────────────────
951
952/// KMAC128 with variable-length output (XOF mode, SP 800-185 §4.3.1).
953///
954/// Convenience free function returning an owned `Vec<u8>`.  For structured
955/// use with the [`Mac`] trait, see [`Kmac128`] which already accepts any
956/// output length.
957///
958/// - `key`: KMAC key (recommended ≥ 16 bytes for 128-bit security)
959/// - `custom`: customization string (e.g. `b"my-app-context"`), may be empty
960/// - `msg`: message data
961/// - `output_len`: desired output length in bytes
962///
963/// Returns [`CryptoError::BadInput`] if `output_len == 0`.
964pub fn kmac128_xof(
965    key: &[u8],
966    custom: &[u8],
967    msg: &[u8],
968    output_len: usize,
969) -> Result<alloc::vec::Vec<u8>, CryptoError> {
970    use tiny_keccak::Hasher as _;
971    if output_len == 0 {
972        return Err(CryptoError::BadInput);
973    }
974    let mut k = tiny_keccak::Kmac::v128(key, custom);
975    k.update(msg);
976    let mut out = alloc::vec![0u8; output_len];
977    k.finalize(&mut out);
978    Ok(out)
979}
980
981/// KMAC256 with variable-length output (XOF mode, SP 800-185 §4.3.1).
982///
983/// Convenience free function returning an owned `Vec<u8>`.  For structured
984/// use with the [`Mac`] trait, see [`Kmac256`] which already accepts any
985/// output length.
986///
987/// - `key`: KMAC key (recommended ≥ 32 bytes for 256-bit security)
988/// - `custom`: customization string (e.g. `b"my-app-context"`), may be empty
989/// - `msg`: message data
990/// - `output_len`: desired output length in bytes
991///
992/// Returns [`CryptoError::BadInput`] if `output_len == 0`.
993pub fn kmac256_xof(
994    key: &[u8],
995    custom: &[u8],
996    msg: &[u8],
997    output_len: usize,
998) -> Result<alloc::vec::Vec<u8>, CryptoError> {
999    use tiny_keccak::Hasher as _;
1000    if output_len == 0 {
1001        return Err(CryptoError::BadInput);
1002    }
1003    let mut k = tiny_keccak::Kmac::v256(key, custom);
1004    k.update(msg);
1005    let mut out = alloc::vec![0u8; output_len];
1006    k.finalize(&mut out);
1007    Ok(out)
1008}
1009
1010// ── BLAKE3 keyed-hash MAC ─────────────────────────────────────────────────────
1011
1012/// BLAKE3 keyed-hash MAC (BLAKE3 spec §2.7).
1013///
1014/// This is BLAKE3's **native** authentication mode, **not** HMAC with BLAKE3.
1015/// The key must be exactly 32 bytes.  Output is always 32 bytes.
1016///
1017/// This is faster than [`hmac_sha256_to_vec`] at an equivalent security level
1018/// because BLAKE3 uses a single-pass tree construction rather than the
1019/// double-compression of HMAC.
1020///
1021/// Use [`blake3_keyed_mac_verify`] for constant-time verification.
1022pub fn blake3_keyed_mac(key: &[u8; 32], msg: &[u8]) -> [u8; 32] {
1023    *blake3::Hasher::new_keyed(key)
1024        .update(msg)
1025        .finalize()
1026        .as_bytes()
1027}
1028
1029/// Verify a BLAKE3 keyed-hash MAC in constant time.
1030///
1031/// Returns `Ok(())` when `expected` matches the BLAKE3 keyed-hash of `msg`
1032/// under `key`, or [`CryptoError::InvalidTag`] on mismatch.
1033pub fn blake3_keyed_mac_verify(
1034    key: &[u8; 32],
1035    msg: &[u8],
1036    expected: &[u8; 32],
1037) -> Result<(), CryptoError> {
1038    let actual = blake3_keyed_mac(key, msg);
1039    if actual.ct_eq(expected).into() {
1040        Ok(())
1041    } else {
1042        Err(CryptoError::InvalidTag)
1043    }
1044}
1045
1046// ── Standalone truncated-verify helper ───────────────────────────────────────
1047
1048/// Verify the first `truncated_tag.len()` bytes of an HMAC-SHA-256 MAC.
1049///
1050/// Useful for protocols that truncate MAC tags (e.g. to 16 bytes for bandwidth
1051/// savings).  Always performs constant-time comparison over exactly
1052/// `truncated_tag.len()` bytes.
1053///
1054/// Note: this helper accepts tags as short as 1 byte (permissive API for
1055/// protocol use).  For production use, prefer [`HmacSha256::verify_truncated`]
1056/// which enforces a 16-byte minimum per NIST SP 800-117.
1057///
1058/// Returns [`CryptoError::BadInput`] if `truncated_tag` is empty or longer
1059/// than 32 bytes.
1060pub fn hmac_sha256_verify_truncated(
1061    key: &[u8],
1062    msg: &[u8],
1063    truncated_tag: &[u8],
1064) -> Result<(), CryptoError> {
1065    if truncated_tag.is_empty() || truncated_tag.len() > 32 {
1066        return Err(CryptoError::BadInput);
1067    }
1068    let mut buf = [0u8; 32];
1069    HmacSha256.mac(key, msg, &mut buf)?;
1070    if buf[..truncated_tag.len()].ct_eq(truncated_tag).into() {
1071        Ok(())
1072    } else {
1073        Err(CryptoError::InvalidTag)
1074    }
1075}
1076
1077// ── TLS MAC negotiation (see src/tls.rs) ─────────────────────────────────────
1078
1079/// TLS cipher suite → MAC negotiation.
1080///
1081/// See [`TlsCipherSuite`], [`negotiate_mac`], and [`mac_name_for_suite`].
1082pub mod tls;
1083pub use tls::{mac_name_for_suite, negotiate_mac, TlsCipherSuite};
1084
1085// ── Convenience: mac_to_vec helpers ──────────────────────────────────────────
1086
1087/// Compute an HMAC-SHA-256 tag and return it as a 32-byte [`Vec<u8>`].
1088///
1089/// This is a convenience wrapper around [`HmacSha256::mac`] for callers that
1090/// prefer an owned return value over a pre-allocated output buffer.
1091///
1092/// # Errors
1093///
1094/// Returns [`CryptoError::InvalidKey`] if the HMAC crate rejects the key.
1095#[must_use = "MAC result must be used or verified"]
1096pub fn hmac_sha256_to_vec(key: &[u8], msg: &[u8]) -> Result<alloc::vec::Vec<u8>, CryptoError> {
1097    let mut out = alloc::vec![0u8; 32];
1098    HmacSha256.mac(key, msg, &mut out)?;
1099    Ok(out)
1100}
1101
1102/// Compute an HMAC-SHA-384 tag and return it as a 48-byte [`Vec<u8>`].
1103///
1104/// This is a convenience wrapper around [`HmacSha384::mac`] for callers that
1105/// prefer an owned return value over a pre-allocated output buffer.
1106///
1107/// # Errors
1108///
1109/// Returns [`CryptoError::InvalidKey`] if the HMAC crate rejects the key.
1110#[must_use = "MAC result must be used or verified"]
1111pub fn hmac_sha384_to_vec(key: &[u8], msg: &[u8]) -> Result<alloc::vec::Vec<u8>, CryptoError> {
1112    let mut out = alloc::vec![0u8; 48];
1113    HmacSha384.mac(key, msg, &mut out)?;
1114    Ok(out)
1115}
1116
1117/// Compute an HMAC-SHA-512 tag and return it as a 64-byte [`Vec<u8>`].
1118///
1119/// This is a convenience wrapper around [`HmacSha512::mac`] for callers that
1120/// prefer an owned return value over a pre-allocated output buffer.
1121///
1122/// # Errors
1123///
1124/// Returns [`CryptoError::InvalidKey`] if the HMAC crate rejects the key.
1125#[must_use = "MAC result must be used or verified"]
1126pub fn hmac_sha512_to_vec(key: &[u8], msg: &[u8]) -> Result<alloc::vec::Vec<u8>, CryptoError> {
1127    let mut out = alloc::vec![0u8; 64];
1128    HmacSha512.mac(key, msg, &mut out)?;
1129    Ok(out)
1130}
1131
1132// ── Tests ────────────────────────────────────────────────────────────────────
1133
1134#[cfg(test)]
1135mod tests {
1136    include!("tests_inline.rs");
1137}