Skip to main content

ipfrs_network/
message_authenticator.rs

1//! Message authentication with HMAC-like construction and replay attack prevention.
2//!
3//! Provides production-quality message signing and verification using pure-Rust
4//! crypto primitives (FNV-1a based HMAC, xorshift64 PRNG) without any external
5//! crypto crates.
6//!
7//! # Design
8//! * `MessageAuthenticator` manages a keystore of named `AuthKey` entries.
9//! * Each key carries an `AuthAlgorithm` variant that controls how HMAC is computed.
10//! * A `ReplayWindow` tracks recently seen nonces to block replay attacks.
11//! * Policy flags (`AuthPolicy`) let callers enforce sequential nonces or key rotation.
12//! * All significant events (sign, verify, error) are recorded in an internal audit log
13//!   drainable via `drain_events()`.
14//!
15//! # Collision notes (lib.rs re-export)
16//! * `fnv1a_64` / `xorshift64` are already exported under `ntm_` / `smx_` / etc.
17//!   aliases.  The helpers in this module are re-exported as `mau_fnv1a_64` and
18//!   `mau_xorshift64` to avoid collisions.
19//! * No other types in this module collide with existing crate-root exports as of
20//!   the time of writing (confirmed by grepping lib.rs for each name before
21//!   creating this file).
22
23use std::collections::HashMap;
24use std::collections::VecDeque;
25
26// ─────────────────────────────────────────────────────────────────────────────
27// Pure-Rust crypto primitives (no external crates)
28// ─────────────────────────────────────────────────────────────────────────────
29
30/// FNV-1a 64-bit hash over `data`.
31///
32/// Uses the standard FNV offset basis (`14695981039346656037`) and prime
33/// (`1099511628211`).
34#[inline]
35pub fn fnv1a_64(data: &[u8]) -> u64 {
36    let mut h: u64 = 14_695_981_039_346_656_037;
37    for &b in data {
38        h ^= b as u64;
39        h = h.wrapping_mul(1_099_511_628_211);
40    }
41    h
42}
43
44/// HMAC-FNV64 simplified construction.
45///
46/// `H(K XOR opad || H(K XOR ipad || message))` where `opad = 0x5c` and
47/// `ipad = 0x36`.  The key is normalised to 8 bytes via `fnv1a_64` before
48/// use, giving a consistent key-length regardless of input size.
49#[inline]
50pub fn hmac_fnv64(key: &[u8], message: &[u8]) -> u64 {
51    let key_hash = fnv1a_64(key);
52    let key_bytes = key_hash.to_le_bytes();
53    // inner = fnv1a_64(key_bytes XOR ipad || message)
54    let ipad: Vec<u8> = key_bytes
55        .iter()
56        .cycle()
57        .zip(message.iter())
58        .map(|(k, m)| k ^ 0x36 ^ m)
59        .collect();
60    let inner = fnv1a_64(&ipad);
61    // outer = fnv1a_64(key_bytes XOR opad || inner)
62    let opad: Vec<u8> = key_bytes.iter().map(|k| k ^ 0x5c).collect();
63    let mut outer = opad;
64    outer.extend_from_slice(&inner.to_le_bytes());
65    fnv1a_64(&outer)
66}
67
68/// Xorshift-64 PRNG.
69///
70/// Caller must supply a **non-zero** `state`; the function updates it in-place
71/// and returns the next pseudo-random value.
72#[inline]
73pub fn xorshift64(state: &mut u64) -> u64 {
74    let mut x = *state;
75    x ^= x << 13;
76    x ^= x >> 7;
77    x ^= x << 17;
78    *state = x;
79    x
80}
81
82// ─────────────────────────────────────────────────────────────────────────────
83// Algorithm
84// ─────────────────────────────────────────────────────────────────────────────
85
86/// Authentication algorithm selection.
87///
88/// * `HmacFnv64` — single-round HMAC-FNV64 over the payload.
89/// * `HmacFnv64WithNonce` — HMAC-FNV64 over `nonce || payload`.
90/// * `ChainedHash(rounds)` — apply HMAC-FNV64 `rounds` times, feeding the
91///   output of each round as the key for the next.
92#[derive(Debug, Clone, PartialEq, Eq, Default)]
93pub enum AuthAlgorithm {
94    /// Standard HMAC-FNV64 (payload only).
95    #[default]
96    HmacFnv64,
97    /// HMAC-FNV64 with nonce prepended to the payload.
98    HmacFnv64WithNonce,
99    /// Multi-round chained HMAC-FNV64.  `rounds` must be ≥ 1.
100    ChainedHash(u8),
101}
102
103// ─────────────────────────────────────────────────────────────────────────────
104// Key material
105// ─────────────────────────────────────────────────────────────────────────────
106
107/// A named secret key used for signing and verifying messages.
108///
109/// `expires_at` is an optional Unix-microsecond timestamp.  When set, the key
110/// is considered invalid once `current_ts >= expires_at`.
111#[derive(Debug, Clone)]
112pub struct AuthKey {
113    /// Human-readable key identifier.
114    pub id: String,
115    /// Raw secret bytes.
116    pub secret: Vec<u8>,
117    /// Creation time (Unix microseconds).
118    pub created_at: u64,
119    /// Optional expiry time (Unix microseconds).
120    pub expires_at: Option<u64>,
121    /// Algorithm used with this key.
122    pub algorithm: AuthAlgorithm,
123}
124
125impl AuthKey {
126    /// Construct a new `AuthKey`.
127    pub fn new(
128        id: impl Into<String>,
129        secret: Vec<u8>,
130        created_at: u64,
131        expires_at: Option<u64>,
132        algorithm: AuthAlgorithm,
133    ) -> Self {
134        Self {
135            id: id.into(),
136            secret,
137            created_at,
138            expires_at,
139            algorithm,
140        }
141    }
142
143    /// Return `true` if the key has not yet expired at `current_ts`.
144    pub fn is_valid_at(&self, current_ts: u64) -> bool {
145        match self.expires_at {
146            Some(exp) => current_ts < exp,
147            None => true,
148        }
149    }
150}
151
152// ─────────────────────────────────────────────────────────────────────────────
153// Signed message
154// ─────────────────────────────────────────────────────────────────────────────
155
156/// A payload together with its authentication tag and metadata.
157#[derive(Debug, Clone)]
158pub struct SignedMessage {
159    /// The original payload bytes.
160    pub payload: Vec<u8>,
161    /// HMAC-FNV64 signature.
162    pub signature: u64,
163    /// ID of the key used to produce `signature`.
164    pub key_id: String,
165    /// Random nonce for replay prevention.
166    pub nonce: u64,
167    /// Wall-clock timestamp at signing time (Unix microseconds).
168    pub timestamp: u64,
169    /// Monotonically increasing per-key sequence number.
170    pub sequence_num: u64,
171}
172
173// ─────────────────────────────────────────────────────────────────────────────
174// Policy
175// ─────────────────────────────────────────────────────────────────────────────
176
177/// Authentication enforcement policy.
178///
179/// Multiple policies can be held in a `Vec<AuthPolicy>` and evaluated together.
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub enum AuthPolicy {
182    /// Every message must be signed; unsigned messages are rejected.
183    RequireAll,
184    /// Signing is optional; unsigned messages pass through without error.
185    OptionalSign,
186    /// Keys older than `max_key_age_us` microseconds must be rotated before
187    /// new messages can be signed.
188    KeyRotationRequired(u64),
189    /// The `sequence_num` field must increment by exactly 1 each message.
190    SequentialNonce,
191}
192
193// ─────────────────────────────────────────────────────────────────────────────
194// Replay window
195// ─────────────────────────────────────────────────────────────────────────────
196
197/// Sliding window of recently observed nonces used to detect replay attacks.
198///
199/// Operates as a bounded FIFO: when the window is full, the oldest nonce is
200/// evicted to make room for the newcomer.
201#[derive(Debug, Clone)]
202pub struct ReplayWindow {
203    /// Maximum number of nonces retained at once.
204    pub window_size: usize,
205    /// Ordered queue of recently seen nonces (oldest first).
206    pub seen_nonces: VecDeque<u64>,
207    /// Highest `sequence_num` seen so far (used for sequential nonce policy).
208    pub last_sequence: u64,
209}
210
211impl ReplayWindow {
212    /// Create a new `ReplayWindow` with the given capacity.
213    pub fn new(window_size: usize) -> Self {
214        Self {
215            window_size,
216            seen_nonces: VecDeque::with_capacity(window_size),
217            last_sequence: 0,
218        }
219    }
220
221    /// Return `true` if `nonce` was already seen within the window.
222    pub fn contains(&self, nonce: u64) -> bool {
223        self.seen_nonces.contains(&nonce)
224    }
225
226    /// Record `nonce`; evict the oldest entry if the window is at capacity.
227    ///
228    /// When `window_size == 0` the nonce is discarded — no replay detection
229    /// is performed.
230    pub fn record(&mut self, nonce: u64) {
231        if self.window_size == 0 {
232            return;
233        }
234        if self.seen_nonces.len() >= self.window_size {
235            self.seen_nonces.pop_front();
236        }
237        self.seen_nonces.push_back(nonce);
238    }
239}
240
241// ─────────────────────────────────────────────────────────────────────────────
242// Statistics
243// ─────────────────────────────────────────────────────────────────────────────
244
245/// Cumulative counters collected by `MessageAuthenticator`.
246#[derive(Debug, Clone, Default)]
247pub struct AuthStats {
248    /// Total messages successfully signed.
249    pub messages_signed: u64,
250    /// Total messages successfully verified.
251    pub messages_verified: u64,
252    /// Number of messages blocked due to replay detection.
253    pub replay_attacks_blocked: u64,
254    /// Number of verification failures caused by an expired key.
255    pub expired_key_rejections: u64,
256    /// Number of verification failures caused by a bad signature.
257    pub invalid_signature_rejections: u64,
258}
259
260// ─────────────────────────────────────────────────────────────────────────────
261// Error type
262// ─────────────────────────────────────────────────────────────────────────────
263
264/// All errors that can be returned by `MessageAuthenticator`.
265#[derive(Debug, thiserror::Error)]
266pub enum AuthError {
267    /// A key with the given ID does not exist in the keystore.
268    #[error("key not found: {0}")]
269    KeyNotFound(String),
270
271    /// The recomputed signature did not match the one supplied in the message.
272    #[error("invalid signature for key '{key_id}': expected {expected:#018x}, got {got:#018x}")]
273    InvalidSignature {
274        /// Key ID associated with the failing verification.
275        key_id: String,
276        /// The signature that the verifier computed.
277        expected: u64,
278        /// The signature that was present in the message.
279        got: u64,
280    },
281
282    /// The nonce in a message was found in the replay window.
283    #[error("replay detected for nonce {0:#018x}")]
284    ReplayDetected(u64),
285
286    /// The key has passed its expiry timestamp.
287    #[error("key expired: {0}")]
288    KeyExpired(String),
289
290    /// The PRNG state has been exhausted (state reached zero).
291    #[error("nonce generator exhausted — reseed required")]
292    NonceExhausted,
293
294    /// A `SequentialNonce` policy violation: the sequence number jumped or
295    /// regressed unexpectedly.
296    #[error("invalid sequence number: expected {expected}, got {got}")]
297    InvalidSequence {
298        /// The next expected sequence number.
299        expected: u64,
300        /// The sequence number that was actually present.
301        got: u64,
302    },
303}
304
305// ─────────────────────────────────────────────────────────────────────────────
306// Internal per-key state
307// ─────────────────────────────────────────────────────────────────────────────
308
309/// Internal bookkeeping for a single key entry.
310#[derive(Debug)]
311struct KeyEntry {
312    key: AuthKey,
313    /// Next sequence number to assign when signing with this key.
314    next_seq: u64,
315}
316
317// ─────────────────────────────────────────────────────────────────────────────
318// MessageAuthenticator
319// ─────────────────────────────────────────────────────────────────────────────
320
321/// Production-quality message authenticator with:
322/// * HMAC-FNV64 signing / verification (three algorithm variants).
323/// * Replay attack prevention via a bounded nonce window.
324/// * Key expiry and atomic key rotation.
325/// * Configurable `AuthPolicy` enforcement.
326/// * Monotonic per-key sequence numbers.
327/// * Cumulative statistics and drainable audit log.
328///
329/// # Example
330///
331/// ```
332/// use ipfrs_network::message_authenticator::{
333///     AuthAlgorithm, AuthKey, AuthPolicy, MessageAuthenticator,
334/// };
335///
336/// let mut auth = MessageAuthenticator::new(vec![AuthPolicy::RequireAll], 256);
337/// let key = AuthKey::new("k1", b"super-secret".to_vec(), 0, None, AuthAlgorithm::HmacFnv64);
338/// auth.add_key(key).unwrap();
339/// let msg = auth.sign(b"hello world".to_vec(), "k1", 1_000_000).unwrap();
340/// auth.verify(&msg, 1_000_000).unwrap();
341/// ```
342#[derive(Debug)]
343pub struct MessageAuthenticator {
344    /// Active key store: key_id → entry.
345    keys: HashMap<String, KeyEntry>,
346    /// Replay-prevention window shared across all keys.
347    replay_window: ReplayWindow,
348    /// Active policies to enforce.
349    policies: Vec<AuthPolicy>,
350    /// Xorshift64 PRNG state (must remain non-zero).
351    prng_state: u64,
352    /// Cumulative statistics.
353    stats: AuthStats,
354    /// Audit log — drained on demand by `drain_events`.
355    events: Vec<String>,
356}
357
358impl MessageAuthenticator {
359    /// Create a new `MessageAuthenticator`.
360    ///
361    /// * `policies` — list of `AuthPolicy` variants to enforce.
362    /// * `replay_window_size` — maximum number of nonces retained for replay
363    ///   detection.
364    ///
365    /// # Panics
366    ///
367    /// Does not panic; `replay_window_size == 0` is accepted but provides no
368    /// replay protection.
369    pub fn new(policies: Vec<AuthPolicy>, replay_window_size: usize) -> Self {
370        // Seed PRNG with a compile-time constant mixed with a runtime value.
371        // Using the address of a local as an entropy source avoids any external
372        // dep while still producing a non-zero, varied starting value.
373        let seed_base: u64 = 0x517c_c1b7_2722_0a95;
374        let runtime_mix: u64 = {
375            let local: u64 = replay_window_size as u64;
376            seed_base
377                .wrapping_add(local)
378                .wrapping_mul(6_364_136_223_846_793_005)
379                .wrapping_add(1_442_695_040_888_963_407)
380        };
381        // Ensure the initial PRNG state is non-zero.
382        let prng_state = if runtime_mix == 0 {
383            seed_base
384        } else {
385            runtime_mix
386        };
387
388        Self {
389            keys: HashMap::new(),
390            replay_window: ReplayWindow::new(replay_window_size),
391            policies,
392            prng_state,
393            stats: AuthStats::default(),
394            events: Vec::new(),
395        }
396    }
397
398    // ─────────────────────────────────────────────────────────────────────────
399    // Key management
400    // ─────────────────────────────────────────────────────────────────────────
401
402    /// Add a new key to the keystore.
403    ///
404    /// Fails with `AuthError::KeyNotFound` if a key with the same ID already
405    /// exists (use `rotate_key` for atomic replacement).
406    pub fn add_key(&mut self, key: AuthKey) -> Result<(), AuthError> {
407        let id = key.id.clone();
408        if self.keys.contains_key(&id) {
409            // Treat duplicate-add as an error so callers don't silently
410            // overwrite a key; they should use rotate_key instead.
411            return Err(AuthError::KeyNotFound(format!(
412                "key '{id}' already exists — use rotate_key for replacement"
413            )));
414        }
415        self.events
416            .push(format!("key_added id={id} algo={:?}", key.algorithm));
417        self.keys.insert(id, KeyEntry { key, next_seq: 1 });
418        Ok(())
419    }
420
421    /// Remove a key from the keystore.
422    ///
423    /// Returns `AuthError::KeyNotFound` if no such key exists.
424    pub fn remove_key(&mut self, key_id: &str) -> Result<(), AuthError> {
425        if self.keys.remove(key_id).is_none() {
426            return Err(AuthError::KeyNotFound(key_id.to_string()));
427        }
428        self.events.push(format!("key_removed id={key_id}"));
429        Ok(())
430    }
431
432    /// Atomically replace the key identified by `old_id` with `new_key`.
433    ///
434    /// The sequence counter is reset to 1 for the new key.
435    /// Returns `AuthError::KeyNotFound` if `old_id` is not in the keystore.
436    pub fn rotate_key(&mut self, old_id: &str, new_key: AuthKey) -> Result<(), AuthError> {
437        if !self.keys.contains_key(old_id) {
438            return Err(AuthError::KeyNotFound(old_id.to_string()));
439        }
440        let new_id = new_key.id.clone();
441        self.keys.remove(old_id);
442        self.events.push(format!(
443            "key_rotated old_id={old_id} new_id={new_id} algo={:?}",
444            new_key.algorithm
445        ));
446        self.keys.insert(
447            new_id,
448            KeyEntry {
449                key: new_key,
450                next_seq: 1,
451            },
452        );
453        Ok(())
454    }
455
456    // ─────────────────────────────────────────────────────────────────────────
457    // Signing
458    // ─────────────────────────────────────────────────────────────────────────
459
460    /// Sign `payload` using the key identified by `key_id`.
461    ///
462    /// * A fresh nonce is generated via `xorshift64`.
463    /// * The signature is computed per the key's `AuthAlgorithm`.
464    /// * The per-key sequence counter is incremented atomically.
465    ///
466    /// # Errors
467    ///
468    /// * `AuthError::KeyNotFound` — unknown key.
469    /// * `AuthError::KeyExpired` — key has passed its expiry.
470    /// * `AuthError::NonceExhausted` — PRNG state wrapped to zero (extremely
471    ///   unlikely in practice).
472    /// * `AuthError::KeyRotationRequired` — (via policy check) key age exceeds
473    ///   the rotation threshold.
474    pub fn sign(
475        &mut self,
476        payload: Vec<u8>,
477        key_id: &str,
478        current_ts: u64,
479    ) -> Result<SignedMessage, AuthError> {
480        // Retrieve key entry (mutable borrow released before building msg).
481        let entry = self
482            .keys
483            .get_mut(key_id)
484            .ok_or_else(|| AuthError::KeyNotFound(key_id.to_string()))?;
485
486        // Check expiry.
487        if !entry.key.is_valid_at(current_ts) {
488            self.stats.expired_key_rejections += 1;
489            self.events
490                .push(format!("sign_rejected_expired id={key_id}"));
491            return Err(AuthError::KeyExpired(key_id.to_string()));
492        }
493
494        // Enforce key-rotation policy if present.
495        for policy in &self.policies {
496            if let AuthPolicy::KeyRotationRequired(max_age) = policy {
497                let age = current_ts.saturating_sub(entry.key.created_at);
498                if age > *max_age {
499                    self.events.push(format!(
500                        "sign_rejected_rotation_required id={key_id} age={age} max={max_age}"
501                    ));
502                    return Err(AuthError::KeyExpired(format!(
503                        "rotation required — key '{key_id}' age {age} > max {max_age}"
504                    )));
505                }
506            }
507        }
508
509        // Generate nonce.
510        let nonce = xorshift64(&mut self.prng_state);
511        if nonce == 0 {
512            return Err(AuthError::NonceExhausted);
513        }
514
515        // Sequence number.
516        let sequence_num = entry.next_seq;
517        entry.next_seq = entry.next_seq.wrapping_add(1);
518
519        // Compute signature.
520        let algorithm = entry.key.algorithm.clone();
521        let secret = entry.key.secret.clone();
522        let signature = compute_signature(&algorithm, &secret, &payload, nonce);
523
524        self.stats.messages_signed += 1;
525        self.events.push(format!(
526            "signed key_id={key_id} seq={sequence_num} nonce={nonce:#018x}"
527        ));
528
529        Ok(SignedMessage {
530            payload,
531            signature,
532            key_id: key_id.to_string(),
533            nonce,
534            timestamp: current_ts,
535            sequence_num,
536        })
537    }
538
539    // ─────────────────────────────────────────────────────────────────────────
540    // Verification
541    // ─────────────────────────────────────────────────────────────────────────
542
543    /// Verify the authenticity of `msg` at time `current_ts`.
544    ///
545    /// Checks (in order):
546    /// 1. Key exists in the keystore.
547    /// 2. Key has not expired.
548    /// 3. Nonce has not been replayed.
549    /// 4. Sequence number is valid (when `SequentialNonce` policy is active).
550    /// 5. Signature matches the recomputed value.
551    ///
552    /// On success the nonce is recorded and statistics are updated.
553    pub fn verify(&mut self, msg: &SignedMessage, current_ts: u64) -> Result<(), AuthError> {
554        // 1. Key lookup.
555        let entry = self
556            .keys
557            .get(&msg.key_id)
558            .ok_or_else(|| AuthError::KeyNotFound(msg.key_id.clone()))?;
559
560        // 2. Expiry check.
561        if !entry.key.is_valid_at(current_ts) {
562            self.stats.expired_key_rejections += 1;
563            self.events
564                .push(format!("verify_rejected_expired key_id={}", msg.key_id));
565            return Err(AuthError::KeyExpired(msg.key_id.clone()));
566        }
567
568        // 3. Replay check.
569        if self.check_replay(msg.nonce) {
570            self.stats.replay_attacks_blocked += 1;
571            self.events.push(format!(
572                "replay_blocked key_id={} nonce={:#018x}",
573                msg.key_id, msg.nonce
574            ));
575            return Err(AuthError::ReplayDetected(msg.nonce));
576        }
577
578        // 4. Sequential nonce policy.
579        for policy in &self.policies {
580            if *policy == AuthPolicy::SequentialNonce {
581                let expected = self.replay_window.last_sequence + 1;
582                if msg.sequence_num != expected && self.replay_window.last_sequence != 0 {
583                    self.events.push(format!(
584                        "verify_rejected_sequence key_id={} expected={} got={}",
585                        msg.key_id, expected, msg.sequence_num
586                    ));
587                    return Err(AuthError::InvalidSequence {
588                        expected,
589                        got: msg.sequence_num,
590                    });
591                }
592            }
593        }
594
595        // 5. Recompute and compare signature.
596        let algorithm = entry.key.algorithm.clone();
597        let secret = entry.key.secret.clone();
598        let expected_sig = compute_signature(&algorithm, &secret, &msg.payload, msg.nonce);
599        if expected_sig != msg.signature {
600            self.stats.invalid_signature_rejections += 1;
601            self.events.push(format!(
602                "verify_rejected_bad_sig key_id={} expected={:#018x} got={:#018x}",
603                msg.key_id, expected_sig, msg.signature
604            ));
605            return Err(AuthError::InvalidSignature {
606                key_id: msg.key_id.clone(),
607                expected: expected_sig,
608                got: msg.signature,
609            });
610        }
611
612        // Record the nonce and update sequence tracking.
613        self.record_nonce(msg.nonce);
614        self.replay_window.last_sequence = msg.sequence_num;
615        self.stats.messages_verified += 1;
616        self.events.push(format!(
617            "verified key_id={} seq={} nonce={:#018x}",
618            msg.key_id, msg.sequence_num, msg.nonce
619        ));
620        Ok(())
621    }
622
623    // ─────────────────────────────────────────────────────────────────────────
624    // Replay window helpers
625    // ─────────────────────────────────────────────────────────────────────────
626
627    /// Return `true` if `nonce` is present in the current replay window.
628    pub fn check_replay(&self, nonce: u64) -> bool {
629        self.replay_window.contains(nonce)
630    }
631
632    /// Add `nonce` to the replay window, evicting the oldest entry if full.
633    pub fn record_nonce(&mut self, nonce: u64) {
634        self.replay_window.record(nonce);
635    }
636
637    // ─────────────────────────────────────────────────────────────────────────
638    // Key lifecycle
639    // ─────────────────────────────────────────────────────────────────────────
640
641    /// Remove all keys whose `expires_at` is ≤ `current_ts`.
642    ///
643    /// Returns the IDs of all removed keys.
644    pub fn expire_keys(&mut self, current_ts: u64) -> Vec<String> {
645        let expired: Vec<String> = self
646            .keys
647            .iter()
648            .filter_map(|(id, entry)| match entry.key.expires_at {
649                Some(exp) if current_ts >= exp => Some(id.clone()),
650                _ => None,
651            })
652            .collect();
653
654        for id in &expired {
655            self.keys.remove(id);
656            self.events
657                .push(format!("key_expired_evicted id={id} ts={current_ts}"));
658        }
659        expired
660    }
661
662    // ─────────────────────────────────────────────────────────────────────────
663    // Observability
664    // ─────────────────────────────────────────────────────────────────────────
665
666    /// Return a snapshot of the current cumulative statistics.
667    pub fn stats(&self) -> AuthStats {
668        self.stats.clone()
669    }
670
671    /// Drain the internal audit log, returning all accumulated events.
672    ///
673    /// The log is cleared after this call.
674    pub fn drain_events(&mut self) -> Vec<String> {
675        std::mem::take(&mut self.events)
676    }
677
678    /// Return the number of keys currently in the keystore.
679    pub fn key_count(&self) -> usize {
680        self.keys.len()
681    }
682
683    /// Return `true` if a key with `key_id` is currently registered.
684    pub fn has_key(&self, key_id: &str) -> bool {
685        self.keys.contains_key(key_id)
686    }
687}
688
689// ─────────────────────────────────────────────────────────────────────────────
690// Internal signature computation
691// ─────────────────────────────────────────────────────────────────────────────
692
693/// Compute the authentication tag for `payload` and `nonce` using `algorithm`
694/// and `secret`.
695fn compute_signature(algorithm: &AuthAlgorithm, secret: &[u8], payload: &[u8], nonce: u64) -> u64 {
696    match algorithm {
697        AuthAlgorithm::HmacFnv64 => hmac_fnv64(secret, payload),
698
699        AuthAlgorithm::HmacFnv64WithNonce => {
700            let mut msg = nonce.to_le_bytes().to_vec();
701            msg.extend_from_slice(payload);
702            hmac_fnv64(secret, &msg)
703        }
704
705        AuthAlgorithm::ChainedHash(rounds) => {
706            let rounds = (*rounds).max(1) as usize;
707            // First round: HMAC over payload.
708            let mut current = hmac_fnv64(secret, payload);
709            for _ in 1..rounds {
710                // Subsequent rounds: use previous output as the key.
711                current = hmac_fnv64(&current.to_le_bytes(), payload);
712            }
713            // Final round always folds in the nonce.
714            let nonce_bytes = nonce.to_le_bytes();
715            let mut final_msg = nonce_bytes.to_vec();
716            final_msg.extend_from_slice(&current.to_le_bytes());
717            hmac_fnv64(secret, &final_msg)
718        }
719    }
720}
721
722// ─────────────────────────────────────────────────────────────────────────────
723// Tests
724// ─────────────────────────────────────────────────────────────────────────────
725
726#[cfg(test)]
727mod tests {
728    use super::*;
729
730    // ── Helper constructors ──────────────────────────────────────────────────
731
732    fn make_auth(window: usize) -> MessageAuthenticator {
733        MessageAuthenticator::new(vec![AuthPolicy::RequireAll], window)
734    }
735
736    fn simple_key(id: &str) -> AuthKey {
737        AuthKey::new(
738            id,
739            b"secret_key_bytes".to_vec(),
740            0,
741            None,
742            AuthAlgorithm::HmacFnv64,
743        )
744    }
745
746    fn key_with_expiry(id: &str, expires_at: u64) -> AuthKey {
747        AuthKey::new(
748            id,
749            b"secret".to_vec(),
750            0,
751            Some(expires_at),
752            AuthAlgorithm::HmacFnv64,
753        )
754    }
755
756    fn key_with_algo(id: &str, algo: AuthAlgorithm) -> AuthKey {
757        AuthKey::new(id, b"algo_key".to_vec(), 1_000, None, algo)
758    }
759
760    // ── FNV-1a primitive tests ───────────────────────────────────────────────
761
762    #[test]
763    fn test_fnv1a_empty() {
764        // Empty input should return the FNV offset basis.
765        assert_eq!(fnv1a_64(b""), 14_695_981_039_346_656_037u64);
766    }
767
768    #[test]
769    fn test_fnv1a_known_vector() {
770        // "hello" → known FNV-1a 64-bit output.
771        let h = fnv1a_64(b"hello");
772        assert_ne!(h, 0);
773        // Stability: same input must always give same output.
774        assert_eq!(h, fnv1a_64(b"hello"));
775    }
776
777    #[test]
778    fn test_fnv1a_different_inputs_differ() {
779        assert_ne!(fnv1a_64(b"foo"), fnv1a_64(b"bar"));
780        assert_ne!(fnv1a_64(b"abc"), fnv1a_64(b"abd"));
781    }
782
783    #[test]
784    fn test_fnv1a_single_byte_difference() {
785        let a = fnv1a_64(&[0x00]);
786        let b = fnv1a_64(&[0x01]);
787        assert_ne!(a, b);
788    }
789
790    // ── HMAC-FNV64 primitive tests ───────────────────────────────────────────
791
792    #[test]
793    fn test_hmac_fnv64_deterministic() {
794        let h1 = hmac_fnv64(b"key", b"message");
795        let h2 = hmac_fnv64(b"key", b"message");
796        assert_eq!(h1, h2);
797    }
798
799    #[test]
800    fn test_hmac_fnv64_key_sensitivity() {
801        let h1 = hmac_fnv64(b"key1", b"message");
802        let h2 = hmac_fnv64(b"key2", b"message");
803        assert_ne!(h1, h2);
804    }
805
806    #[test]
807    fn test_hmac_fnv64_message_sensitivity() {
808        let h1 = hmac_fnv64(b"key", b"msg1");
809        let h2 = hmac_fnv64(b"key", b"msg2");
810        assert_ne!(h1, h2);
811    }
812
813    #[test]
814    fn test_hmac_fnv64_empty_message() {
815        let h = hmac_fnv64(b"key", b"");
816        assert_ne!(h, 0);
817    }
818
819    #[test]
820    fn test_hmac_fnv64_empty_key() {
821        let h = hmac_fnv64(b"", b"message");
822        assert_ne!(h, 0);
823    }
824
825    // ── Xorshift64 PRNG tests ────────────────────────────────────────────────
826
827    #[test]
828    fn test_xorshift64_non_zero_output() {
829        let mut state = 12345u64;
830        for _ in 0..100 {
831            let v = xorshift64(&mut state);
832            assert_ne!(v, 0);
833        }
834    }
835
836    #[test]
837    fn test_xorshift64_state_changes() {
838        let mut state = 1u64;
839        let v1 = xorshift64(&mut state);
840        let v2 = xorshift64(&mut state);
841        assert_ne!(v1, v2);
842    }
843
844    #[test]
845    fn test_xorshift64_period_varies() {
846        let mut state = 0xdead_beef_cafe_babe_u64;
847        let first = xorshift64(&mut state);
848        // Verify a second call doesn't just repeat.
849        let second = xorshift64(&mut state);
850        assert_ne!(first, second);
851    }
852
853    // ── Key management tests ─────────────────────────────────────────────────
854
855    #[test]
856    fn test_add_key_success() {
857        let mut auth = make_auth(32);
858        assert!(auth.add_key(simple_key("k1")).is_ok());
859        assert!(auth.has_key("k1"));
860        assert_eq!(auth.key_count(), 1);
861    }
862
863    #[test]
864    fn test_add_key_duplicate_fails() {
865        let mut auth = make_auth(32);
866        auth.add_key(simple_key("k1")).expect("test: add_key k1");
867        let res = auth.add_key(simple_key("k1"));
868        assert!(matches!(res, Err(AuthError::KeyNotFound(_))));
869    }
870
871    #[test]
872    fn test_remove_key_success() {
873        let mut auth = make_auth(32);
874        auth.add_key(simple_key("k1")).expect("test: add_key k1");
875        assert!(auth.remove_key("k1").is_ok());
876        assert!(!auth.has_key("k1"));
877    }
878
879    #[test]
880    fn test_remove_key_missing_fails() {
881        let mut auth = make_auth(32);
882        assert!(matches!(
883            auth.remove_key("ghost"),
884            Err(AuthError::KeyNotFound(_))
885        ));
886    }
887
888    #[test]
889    fn test_rotate_key_replaces() {
890        let mut auth = make_auth(32);
891        auth.add_key(simple_key("k1")).expect("test: add_key k1");
892        let new_key = AuthKey::new(
893            "k2",
894            b"new_secret".to_vec(),
895            0,
896            None,
897            AuthAlgorithm::HmacFnv64,
898        );
899        assert!(auth.rotate_key("k1", new_key).is_ok());
900        assert!(!auth.has_key("k1"), "old key should be removed");
901        assert!(auth.has_key("k2"), "new key should be present");
902    }
903
904    #[test]
905    fn test_rotate_key_missing_fails() {
906        let mut auth = make_auth(32);
907        let new_key = simple_key("k_new");
908        assert!(matches!(
909            auth.rotate_key("ghost", new_key),
910            Err(AuthError::KeyNotFound(_))
911        ));
912    }
913
914    #[test]
915    fn test_add_multiple_keys() {
916        let mut auth = make_auth(64);
917        for i in 0..5u32 {
918            let key = AuthKey::new(
919                format!("key_{i}"),
920                b"secret".to_vec(),
921                0,
922                None,
923                AuthAlgorithm::HmacFnv64,
924            );
925            auth.add_key(key).expect("test: add_key");
926        }
927        assert_eq!(auth.key_count(), 5);
928    }
929
930    // ── Sign / verify round-trip ─────────────────────────────────────────────
931
932    #[test]
933    fn test_sign_and_verify_basic() {
934        let mut auth = make_auth(64);
935        auth.add_key(simple_key("k1")).expect("test: add_key k1");
936        let msg = auth
937            .sign(b"payload".to_vec(), "k1", 1_000)
938            .expect("test: sign payload");
939        assert!(auth.verify(&msg, 1_000).is_ok());
940    }
941
942    #[test]
943    fn test_sign_missing_key() {
944        let mut auth = make_auth(64);
945        assert!(matches!(
946            auth.sign(b"data".to_vec(), "ghost", 0),
947            Err(AuthError::KeyNotFound(_))
948        ));
949    }
950
951    #[test]
952    fn test_verify_missing_key() {
953        let mut auth = make_auth(64);
954        auth.add_key(simple_key("k1")).expect("test: add_key k1");
955        let msg = auth
956            .sign(b"data".to_vec(), "k1", 0)
957            .expect("test: sign data");
958        auth.remove_key("k1").expect("test: remove_key k1");
959        assert!(matches!(
960            auth.verify(&msg, 0),
961            Err(AuthError::KeyNotFound(_))
962        ));
963    }
964
965    #[test]
966    fn test_verify_tampered_payload() {
967        let mut auth = make_auth(64);
968        auth.add_key(simple_key("k1")).expect("test: add_key k1");
969        let mut msg = auth
970            .sign(b"original".to_vec(), "k1", 0)
971            .expect("test: sign original");
972        msg.payload = b"tampered".to_vec();
973        assert!(matches!(
974            auth.verify(&msg, 0),
975            Err(AuthError::InvalidSignature { .. })
976        ));
977    }
978
979    #[test]
980    fn test_verify_tampered_signature() {
981        let mut auth = make_auth(64);
982        auth.add_key(simple_key("k1")).expect("test: add_key k1");
983        let mut msg = auth
984            .sign(b"data".to_vec(), "k1", 0)
985            .expect("test: sign data");
986        msg.signature ^= 0xFF;
987        assert!(matches!(
988            auth.verify(&msg, 0),
989            Err(AuthError::InvalidSignature { .. })
990        ));
991    }
992
993    #[test]
994    fn test_verify_tampered_nonce() {
995        let mut auth = make_auth(64);
996        auth.add_key(simple_key("k1")).expect("test: add_key k1");
997        // HmacFnv64WithNonce embeds nonce in the hash — tampering must fail.
998        let key = key_with_algo("k2", AuthAlgorithm::HmacFnv64WithNonce);
999        auth.add_key(key).expect("test: add_key k2 with nonce algo");
1000        let mut msg = auth
1001            .sign(b"data".to_vec(), "k2", 0)
1002            .expect("test: sign data k2");
1003        msg.nonce ^= 0xABCD;
1004        assert!(matches!(
1005            auth.verify(&msg, 0),
1006            Err(AuthError::InvalidSignature { .. })
1007        ));
1008    }
1009
1010    // ── Key expiry tests ─────────────────────────────────────────────────────
1011
1012    #[test]
1013    fn test_sign_with_expired_key_fails() {
1014        let mut auth = make_auth(64);
1015        auth.add_key(key_with_expiry("k1", 100))
1016            .expect("test: add_key k1 with expiry");
1017        // ts=200 >= expires_at=100
1018        assert!(matches!(
1019            auth.sign(b"data".to_vec(), "k1", 200),
1020            Err(AuthError::KeyExpired(_))
1021        ));
1022    }
1023
1024    #[test]
1025    fn test_sign_with_valid_expiry_succeeds() {
1026        let mut auth = make_auth(64);
1027        auth.add_key(key_with_expiry("k1", 1_000))
1028            .expect("test: add_key k1 with expiry");
1029        // ts=500 < expires_at=1000 → should succeed.
1030        assert!(auth.sign(b"data".to_vec(), "k1", 500).is_ok());
1031    }
1032
1033    #[test]
1034    fn test_verify_with_expired_key_fails() {
1035        let mut auth = make_auth(64);
1036        auth.add_key(key_with_expiry("k1", 1_000))
1037            .expect("test: add_key k1 with expiry");
1038        // Sign while valid.
1039        let msg = auth
1040            .sign(b"data".to_vec(), "k1", 500)
1041            .expect("test: sign data while valid");
1042        // Verify after expiry.
1043        assert!(matches!(
1044            auth.verify(&msg, 1_001),
1045            Err(AuthError::KeyExpired(_))
1046        ));
1047    }
1048
1049    #[test]
1050    fn test_expire_keys_removes_expired() {
1051        let mut auth = make_auth(64);
1052        auth.add_key(key_with_expiry("k_old", 100))
1053            .expect("test: add_key k_old with expiry");
1054        auth.add_key(simple_key("k_live"))
1055            .expect("test: add_key k_live");
1056        let removed = auth.expire_keys(200);
1057        assert_eq!(removed, vec!["k_old".to_string()]);
1058        assert!(!auth.has_key("k_old"));
1059        assert!(auth.has_key("k_live"));
1060    }
1061
1062    #[test]
1063    fn test_expire_keys_none_expired() {
1064        let mut auth = make_auth(64);
1065        auth.add_key(key_with_expiry("k1", 1_000))
1066            .expect("test: add_key k1 with expiry");
1067        let removed = auth.expire_keys(500);
1068        assert!(removed.is_empty());
1069        assert!(auth.has_key("k1"));
1070    }
1071
1072    #[test]
1073    fn test_expire_keys_no_expiry_set() {
1074        let mut auth = make_auth(64);
1075        auth.add_key(simple_key("k_permanent"))
1076            .expect("test: add_key k_permanent");
1077        let removed = auth.expire_keys(u64::MAX);
1078        assert!(removed.is_empty());
1079    }
1080
1081    #[test]
1082    fn test_expire_multiple_keys() {
1083        let mut auth = make_auth(64);
1084        for i in 0..4u64 {
1085            let key = AuthKey::new(
1086                format!("k{i}"),
1087                b"s".to_vec(),
1088                0,
1089                Some(i * 100 + 50), // expire at 50, 150, 250, 350
1090                AuthAlgorithm::HmacFnv64,
1091            );
1092            auth.add_key(key).expect("test: add_key in loop");
1093        }
1094        // Expire keys whose expiry ≤ 200.
1095        let removed = auth.expire_keys(200);
1096        // k0 (exp=50) and k1 (exp=150) should be gone; k2 (exp=250), k3 (exp=350) survive.
1097        assert_eq!(removed.len(), 2);
1098        assert!(!auth.has_key("k0"));
1099        assert!(!auth.has_key("k1"));
1100        assert!(auth.has_key("k2"));
1101        assert!(auth.has_key("k3"));
1102    }
1103
1104    // ── Replay prevention tests ──────────────────────────────────────────────
1105
1106    #[test]
1107    fn test_replay_blocked_on_second_verify() {
1108        let mut auth = make_auth(64);
1109        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1110        let msg = auth
1111            .sign(b"hello".to_vec(), "k1", 0)
1112            .expect("test: sign hello");
1113        // First verify succeeds.
1114        assert!(auth.verify(&msg, 0).is_ok());
1115        // Second verify with same nonce must fail.
1116        assert!(matches!(
1117            auth.verify(&msg, 0),
1118            Err(AuthError::ReplayDetected(_))
1119        ));
1120    }
1121
1122    #[test]
1123    fn test_replay_window_eviction() {
1124        let window = 4;
1125        let mut auth = MessageAuthenticator::new(vec![], window);
1126        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1127
1128        // Fill and overflow the window (5 messages into a window of 4).
1129        let mut msgs = Vec::new();
1130        for _ in 0..5 {
1131            let m = auth.sign(b"p".to_vec(), "k1", 0).expect("test: sign p");
1132            msgs.push(m);
1133        }
1134
1135        // Verify all 5.
1136        for m in &msgs {
1137            // The first-signed message's nonce will be evicted; its re-verify
1138            // should succeed (evicted from window ⇒ not detected as replay).
1139            let _ = auth.verify(m, 0);
1140        }
1141        // After verification the window holds the last 4 nonces.
1142        assert_eq!(auth.replay_window.seen_nonces.len(), window);
1143    }
1144
1145    #[test]
1146    fn test_check_replay_returns_true_when_seen() {
1147        let mut auth = make_auth(32);
1148        auth.record_nonce(0xABCD_u64);
1149        assert!(auth.check_replay(0xABCD_u64));
1150    }
1151
1152    #[test]
1153    fn test_check_replay_returns_false_when_unseen() {
1154        let auth = make_auth(32);
1155        assert!(!auth.check_replay(0xDEAD_BEEF_u64));
1156    }
1157
1158    #[test]
1159    fn test_record_nonce_evicts_oldest_when_full() {
1160        let mut auth = make_auth(3);
1161        auth.record_nonce(1);
1162        auth.record_nonce(2);
1163        auth.record_nonce(3);
1164        // Window full; inserting 4 should evict 1.
1165        auth.record_nonce(4);
1166        assert!(!auth.check_replay(1), "oldest should be evicted");
1167        assert!(auth.check_replay(2));
1168        assert!(auth.check_replay(3));
1169        assert!(auth.check_replay(4));
1170    }
1171
1172    // ── Algorithm-specific tests ─────────────────────────────────────────────
1173
1174    #[test]
1175    fn test_algorithm_hmac_fnv64_roundtrip() {
1176        let mut auth = make_auth(64);
1177        auth.add_key(key_with_algo("k", AuthAlgorithm::HmacFnv64))
1178            .expect("test: add_key with HmacFnv64 algo");
1179        let msg = auth
1180            .sign(b"test".to_vec(), "k", 0)
1181            .expect("test: sign test");
1182        assert!(auth.verify(&msg, 0).is_ok());
1183    }
1184
1185    #[test]
1186    fn test_algorithm_hmac_fnv64_with_nonce_roundtrip() {
1187        let mut auth = make_auth(64);
1188        auth.add_key(key_with_algo("k", AuthAlgorithm::HmacFnv64WithNonce))
1189            .expect("test: add_key with HmacFnv64WithNonce algo");
1190        let msg = auth
1191            .sign(b"test".to_vec(), "k", 0)
1192            .expect("test: sign test");
1193        assert!(auth.verify(&msg, 0).is_ok());
1194    }
1195
1196    #[test]
1197    fn test_algorithm_chained_hash_roundtrip_1_round() {
1198        let mut auth = make_auth(64);
1199        auth.add_key(key_with_algo("k", AuthAlgorithm::ChainedHash(1)))
1200            .expect("test: add_key with ChainedHash(1) algo");
1201        let msg = auth
1202            .sign(b"test".to_vec(), "k", 0)
1203            .expect("test: sign test");
1204        assert!(auth.verify(&msg, 0).is_ok());
1205    }
1206
1207    #[test]
1208    fn test_algorithm_chained_hash_roundtrip_3_rounds() {
1209        let mut auth = make_auth(64);
1210        auth.add_key(key_with_algo("k", AuthAlgorithm::ChainedHash(3)))
1211            .expect("test: add_key with ChainedHash(3) algo");
1212        let msg = auth
1213            .sign(b"test".to_vec(), "k", 0)
1214            .expect("test: sign test");
1215        assert!(auth.verify(&msg, 0).is_ok());
1216    }
1217
1218    #[test]
1219    fn test_algorithm_chained_hash_max_rounds() {
1220        let mut auth = make_auth(64);
1221        auth.add_key(key_with_algo("k", AuthAlgorithm::ChainedHash(255)))
1222            .expect("test: add_key with ChainedHash(255) algo");
1223        let msg = auth
1224            .sign(b"rounds".to_vec(), "k", 0)
1225            .expect("test: sign rounds");
1226        assert!(auth.verify(&msg, 0).is_ok());
1227    }
1228
1229    #[test]
1230    fn test_algorithms_produce_different_signatures() {
1231        // Same key material, same payload — different algorithms must differ.
1232        let secret = b"shared_secret".to_vec();
1233        let payload = b"same payload".to_vec();
1234        let nonce = 0x1234_5678_9ABC_DEF0_u64;
1235
1236        let s1 = compute_signature(&AuthAlgorithm::HmacFnv64, &secret, &payload, nonce);
1237        let s2 = compute_signature(&AuthAlgorithm::HmacFnv64WithNonce, &secret, &payload, nonce);
1238        let s3 = compute_signature(&AuthAlgorithm::ChainedHash(2), &secret, &payload, nonce);
1239
1240        assert_ne!(s1, s2);
1241        assert_ne!(s1, s3);
1242        assert_ne!(s2, s3);
1243    }
1244
1245    #[test]
1246    fn test_chained_hash_rounds_differ() {
1247        let secret = b"secret".to_vec();
1248        let payload = b"payload".to_vec();
1249        let nonce = 1u64;
1250
1251        let s1 = compute_signature(&AuthAlgorithm::ChainedHash(1), &secret, &payload, nonce);
1252        let s2 = compute_signature(&AuthAlgorithm::ChainedHash(2), &secret, &payload, nonce);
1253        let s3 = compute_signature(&AuthAlgorithm::ChainedHash(3), &secret, &payload, nonce);
1254
1255        assert_ne!(s1, s2);
1256        assert_ne!(s2, s3);
1257    }
1258
1259    // ── Sequential nonce policy tests ────────────────────────────────────────
1260
1261    #[test]
1262    fn test_sequential_nonce_policy_first_message_ok() {
1263        let mut auth = MessageAuthenticator::new(vec![AuthPolicy::SequentialNonce], 64);
1264        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1265        let msg = auth
1266            .sign(b"first".to_vec(), "k1", 0)
1267            .expect("test: sign first");
1268        // First message: last_sequence == 0, so no check fires.
1269        assert!(auth.verify(&msg, 0).is_ok());
1270    }
1271
1272    #[test]
1273    fn test_sequential_nonce_policy_ordered() {
1274        let mut auth = MessageAuthenticator::new(vec![AuthPolicy::SequentialNonce], 64);
1275        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1276        // Sign two messages in order.
1277        let m1 = auth.sign(b"one".to_vec(), "k1", 0).expect("test: sign one");
1278        let m2 = auth.sign(b"two".to_vec(), "k1", 0).expect("test: sign two");
1279        // Verify in order — both should pass.
1280        assert!(auth.verify(&m1, 0).is_ok());
1281        assert!(auth.verify(&m2, 0).is_ok());
1282    }
1283
1284    #[test]
1285    fn test_sequential_nonce_policy_out_of_order_rejected() {
1286        let mut auth = MessageAuthenticator::new(vec![AuthPolicy::SequentialNonce], 64);
1287        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1288        let m1 = auth.sign(b"one".to_vec(), "k1", 0).expect("test: sign one");
1289        let m2 = auth.sign(b"two".to_vec(), "k1", 0).expect("test: sign two");
1290        // Verify m1 first to set last_sequence = 1.
1291        assert!(auth.verify(&m1, 0).is_ok());
1292        // Now craft a message with a sequence number far in the future.
1293        let mut m_bad = m2.clone();
1294        m_bad.sequence_num = 999;
1295        // Should be caught by the SequentialNonce policy.
1296        assert!(matches!(
1297            auth.verify(&m_bad, 0),
1298            Err(AuthError::InvalidSequence { .. }) | Err(AuthError::InvalidSignature { .. })
1299        ));
1300    }
1301
1302    // ── Key-rotation policy tests ────────────────────────────────────────────
1303
1304    #[test]
1305    fn test_key_rotation_required_policy_new_key_ok() {
1306        let max_age = 10_000u64;
1307        let mut auth =
1308            MessageAuthenticator::new(vec![AuthPolicy::KeyRotationRequired(max_age)], 64);
1309        // Key created at ts=0, signing at ts=5000 → age=5000 < max=10000.
1310        let key = AuthKey::new("k1", b"sec".to_vec(), 0, None, AuthAlgorithm::HmacFnv64);
1311        auth.add_key(key).expect("test: add_key k1");
1312        assert!(auth.sign(b"data".to_vec(), "k1", 5_000).is_ok());
1313    }
1314
1315    #[test]
1316    fn test_key_rotation_required_policy_old_key_rejected() {
1317        let max_age = 10_000u64;
1318        let mut auth =
1319            MessageAuthenticator::new(vec![AuthPolicy::KeyRotationRequired(max_age)], 64);
1320        // Key created at ts=0, signing at ts=20000 → age=20000 > max=10000.
1321        let key = AuthKey::new("k1", b"sec".to_vec(), 0, None, AuthAlgorithm::HmacFnv64);
1322        auth.add_key(key).expect("test: add_key k1");
1323        assert!(matches!(
1324            auth.sign(b"data".to_vec(), "k1", 20_000),
1325            Err(AuthError::KeyExpired(_))
1326        ));
1327    }
1328
1329    // ── Statistics tests ─────────────────────────────────────────────────────
1330
1331    #[test]
1332    fn test_stats_initial_zeroed() {
1333        let auth = make_auth(32);
1334        let s = auth.stats();
1335        assert_eq!(s.messages_signed, 0);
1336        assert_eq!(s.messages_verified, 0);
1337        assert_eq!(s.replay_attacks_blocked, 0);
1338        assert_eq!(s.expired_key_rejections, 0);
1339        assert_eq!(s.invalid_signature_rejections, 0);
1340    }
1341
1342    #[test]
1343    fn test_stats_sign_increments() {
1344        let mut auth = make_auth(32);
1345        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1346        auth.sign(b"a".to_vec(), "k1", 0).expect("test: sign a");
1347        auth.sign(b"b".to_vec(), "k1", 0).expect("test: sign b");
1348        assert_eq!(auth.stats().messages_signed, 2);
1349    }
1350
1351    #[test]
1352    fn test_stats_verify_increments() {
1353        let mut auth = make_auth(64);
1354        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1355        let m1 = auth.sign(b"a".to_vec(), "k1", 0).expect("test: sign a");
1356        let m2 = auth.sign(b"b".to_vec(), "k1", 0).expect("test: sign b");
1357        auth.verify(&m1, 0).expect("test: verify m1");
1358        auth.verify(&m2, 0).expect("test: verify m2");
1359        assert_eq!(auth.stats().messages_verified, 2);
1360    }
1361
1362    #[test]
1363    fn test_stats_replay_blocked_increments() {
1364        let mut auth = make_auth(64);
1365        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1366        let msg = auth
1367            .sign(b"hello".to_vec(), "k1", 0)
1368            .expect("test: sign hello");
1369        auth.verify(&msg, 0).expect("test: verify msg");
1370        let _ = auth.verify(&msg, 0); // replay
1371        assert_eq!(auth.stats().replay_attacks_blocked, 1);
1372    }
1373
1374    #[test]
1375    fn test_stats_expired_key_rejections_increments() {
1376        let mut auth = make_auth(64);
1377        auth.add_key(key_with_expiry("k1", 100))
1378            .expect("test: add_key k1 with expiry");
1379        let _ = auth.sign(b"d".to_vec(), "k1", 200); // expired
1380        assert_eq!(auth.stats().expired_key_rejections, 1);
1381    }
1382
1383    #[test]
1384    fn test_stats_invalid_signature_increments() {
1385        let mut auth = make_auth(64);
1386        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1387        let mut msg = auth
1388            .sign(b"data".to_vec(), "k1", 0)
1389            .expect("test: sign data");
1390        msg.signature ^= 1;
1391        let _ = auth.verify(&msg, 0);
1392        assert_eq!(auth.stats().invalid_signature_rejections, 1);
1393    }
1394
1395    // ── Audit log tests ──────────────────────────────────────────────────────
1396
1397    #[test]
1398    fn test_drain_events_clears_log() {
1399        let mut auth = make_auth(32);
1400        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1401        let events = auth.drain_events();
1402        assert!(!events.is_empty(), "add_key should have logged an event");
1403        // Second drain should return nothing.
1404        let events2 = auth.drain_events();
1405        assert!(events2.is_empty());
1406    }
1407
1408    #[test]
1409    fn test_drain_events_captures_sign_and_verify() {
1410        let mut auth = make_auth(64);
1411        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1412        let msg = auth.sign(b"hi".to_vec(), "k1", 0).expect("test: sign hi");
1413        auth.verify(&msg, 0).expect("test: verify msg");
1414        let events = auth.drain_events();
1415        let log = events.join("\n");
1416        assert!(log.contains("key_added"), "should log key_added");
1417        assert!(log.contains("signed"), "should log signed");
1418        assert!(log.contains("verified"), "should log verified");
1419    }
1420
1421    #[test]
1422    fn test_drain_events_captures_replay_blocked() {
1423        let mut auth = make_auth(64);
1424        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1425        let msg = auth.sign(b"x".to_vec(), "k1", 0).expect("test: sign x");
1426        auth.verify(&msg, 0).expect("test: verify msg first time");
1427        let _ = auth.verify(&msg, 0); // replay
1428        let events = auth.drain_events();
1429        let log = events.join("\n");
1430        assert!(log.contains("replay_blocked"));
1431    }
1432
1433    #[test]
1434    fn test_drain_events_captures_key_expired_eviction() {
1435        let mut auth = make_auth(64);
1436        auth.add_key(key_with_expiry("k_expire", 50))
1437            .expect("test: add_key k_expire with expiry");
1438        auth.expire_keys(100);
1439        let events = auth.drain_events();
1440        let log = events.join("\n");
1441        assert!(log.contains("key_expired_evicted"));
1442    }
1443
1444    #[test]
1445    fn test_drain_events_captures_key_rotation() {
1446        let mut auth = make_auth(32);
1447        auth.add_key(simple_key("k_old"))
1448            .expect("test: add_key k_old");
1449        auth.rotate_key("k_old", simple_key("k_new"))
1450            .expect("test: rotate k_old to k_new");
1451        let events = auth.drain_events();
1452        let log = events.join("\n");
1453        assert!(log.contains("key_rotated"));
1454    }
1455
1456    // ── Error-case coverage ──────────────────────────────────────────────────
1457
1458    #[test]
1459    fn test_error_key_not_found_message() {
1460        let err = AuthError::KeyNotFound("missing".to_string());
1461        assert!(err.to_string().contains("missing"));
1462    }
1463
1464    #[test]
1465    fn test_error_invalid_signature_message() {
1466        let err = AuthError::InvalidSignature {
1467            key_id: "k1".to_string(),
1468            expected: 0xABCD,
1469            got: 0x1234,
1470        };
1471        let s = err.to_string();
1472        assert!(s.contains("k1"));
1473        assert!(s.contains("0x000000000000abcd"));
1474        assert!(s.contains("0x0000000000001234"));
1475    }
1476
1477    #[test]
1478    fn test_error_replay_detected_message() {
1479        let err = AuthError::ReplayDetected(0xDEAD);
1480        assert!(err.to_string().contains("replay"));
1481    }
1482
1483    #[test]
1484    fn test_error_key_expired_message() {
1485        let err = AuthError::KeyExpired("old_key".to_string());
1486        assert!(err.to_string().contains("old_key"));
1487    }
1488
1489    #[test]
1490    fn test_error_nonce_exhausted_message() {
1491        let err = AuthError::NonceExhausted;
1492        assert!(!err.to_string().is_empty());
1493    }
1494
1495    #[test]
1496    fn test_error_invalid_sequence_message() {
1497        let err = AuthError::InvalidSequence {
1498            expected: 5,
1499            got: 3,
1500        };
1501        let s = err.to_string();
1502        assert!(s.contains("5"));
1503        assert!(s.contains("3"));
1504    }
1505
1506    // ── Additional edge cases ────────────────────────────────────────────────
1507
1508    #[test]
1509    fn test_sign_verify_empty_payload() {
1510        let mut auth = make_auth(64);
1511        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1512        let msg = auth
1513            .sign(vec![], "k1", 0)
1514            .expect("test: sign empty payload");
1515        assert!(auth.verify(&msg, 0).is_ok());
1516    }
1517
1518    #[test]
1519    fn test_sign_verify_large_payload() {
1520        let mut auth = make_auth(128);
1521        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1522        let payload = vec![0xABu8; 64 * 1024];
1523        let msg = auth
1524            .sign(payload, "k1", 0)
1525            .expect("test: sign large payload");
1526        assert!(auth.verify(&msg, 0).is_ok());
1527    }
1528
1529    #[test]
1530    fn test_sequence_numbers_increment_per_key() {
1531        let mut auth = make_auth(128);
1532        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1533        let m1 = auth.sign(b"a".to_vec(), "k1", 0).expect("test: sign a");
1534        let m2 = auth.sign(b"b".to_vec(), "k1", 0).expect("test: sign b");
1535        let m3 = auth.sign(b"c".to_vec(), "k1", 0).expect("test: sign c");
1536        assert_eq!(m1.sequence_num, 1);
1537        assert_eq!(m2.sequence_num, 2);
1538        assert_eq!(m3.sequence_num, 3);
1539    }
1540
1541    #[test]
1542    fn test_sequence_numbers_independent_per_key() {
1543        let mut auth = make_auth(128);
1544        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1545        auth.add_key(simple_key("k2")).expect("test: add_key k2");
1546        let ma = auth
1547            .sign(b"a".to_vec(), "k1", 0)
1548            .expect("test: sign a with k1");
1549        let mb = auth
1550            .sign(b"a".to_vec(), "k2", 0)
1551            .expect("test: sign a with k2");
1552        // Both start at seq=1.
1553        assert_eq!(ma.sequence_num, 1);
1554        assert_eq!(mb.sequence_num, 1);
1555    }
1556
1557    #[test]
1558    fn test_verify_wrong_key_id_in_message() {
1559        let mut auth = make_auth(64);
1560        // Use different secrets so k1 and k2 produce different signatures.
1561        let k1 = AuthKey::new(
1562            "k1",
1563            b"secret_for_k1".to_vec(),
1564            0,
1565            None,
1566            AuthAlgorithm::HmacFnv64,
1567        );
1568        let k2 = AuthKey::new(
1569            "k2",
1570            b"different_secret_k2".to_vec(),
1571            0,
1572            None,
1573            AuthAlgorithm::HmacFnv64,
1574        );
1575        auth.add_key(k1).expect("test: add_key k1");
1576        auth.add_key(k2).expect("test: add_key k2");
1577        let mut msg = auth
1578            .sign(b"data".to_vec(), "k1", 0)
1579            .expect("test: sign data k1");
1580        // Point the message at k2 — signature was computed under k1's secret,
1581        // so it cannot match k2's recomputed value.
1582        msg.key_id = "k2".to_string();
1583        assert!(matches!(
1584            auth.verify(&msg, 0),
1585            Err(AuthError::InvalidSignature { .. })
1586        ));
1587    }
1588
1589    #[test]
1590    fn test_many_sign_verify_cycles() {
1591        let mut auth = make_auth(1000);
1592        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1593        let mut msgs = Vec::new();
1594        for i in 0..50u64 {
1595            let payload = format!("message-{i}").into_bytes();
1596            let m = auth
1597                .sign(payload, "k1", i)
1598                .expect("test: sign payload in loop");
1599            msgs.push(m);
1600        }
1601        for m in &msgs {
1602            assert!(auth.verify(m, 0).is_ok());
1603        }
1604        let s = auth.stats();
1605        assert_eq!(s.messages_signed, 50);
1606        assert_eq!(s.messages_verified, 50);
1607        assert_eq!(s.replay_attacks_blocked, 0);
1608    }
1609
1610    #[test]
1611    fn test_optional_sign_policy_does_not_block() {
1612        // OptionalSign — just check we can create the authenticator with it.
1613        let mut auth = MessageAuthenticator::new(vec![AuthPolicy::OptionalSign], 32);
1614        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1615        let msg = auth.sign(b"x".to_vec(), "k1", 0).expect("test: sign x");
1616        assert!(auth.verify(&msg, 0).is_ok());
1617    }
1618
1619    #[test]
1620    fn test_replay_window_size_zero_records_nothing() {
1621        let mut auth = make_auth(0);
1622        auth.record_nonce(42);
1623        // Window of size 0 never stores anything.
1624        assert!(!auth.check_replay(42));
1625    }
1626
1627    #[test]
1628    fn test_auth_key_is_valid_at_boundary() {
1629        let key = key_with_expiry("k", 500);
1630        // Strictly before expiry → valid.
1631        assert!(key.is_valid_at(499));
1632        // Exactly at expiry → invalid.
1633        assert!(!key.is_valid_at(500));
1634        // After expiry → invalid.
1635        assert!(!key.is_valid_at(501));
1636    }
1637
1638    #[test]
1639    fn test_auth_key_no_expiry_always_valid() {
1640        let key = simple_key("k");
1641        assert!(key.is_valid_at(0));
1642        assert!(key.is_valid_at(u64::MAX));
1643    }
1644
1645    #[test]
1646    fn test_drain_events_multiple_rounds() {
1647        let mut auth = make_auth(32);
1648        auth.add_key(simple_key("k1")).expect("test: add_key k1");
1649        let round1 = auth.drain_events();
1650        assert!(!round1.is_empty());
1651
1652        auth.add_key(simple_key("k2")).expect("test: add_key k2");
1653        let round2 = auth.drain_events();
1654        assert!(!round2.is_empty());
1655        // round1 events should not appear again.
1656        let combined = round2.join("\n");
1657        let count_k1_add = combined.matches("key_added id=k1").count();
1658        assert_eq!(count_k1_add, 0, "k1 add event should not re-appear");
1659    }
1660}