1use std::collections::HashMap;
24use std::collections::VecDeque;
25
26#[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#[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 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 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#[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#[derive(Debug, Clone, PartialEq, Eq, Default)]
93pub enum AuthAlgorithm {
94 #[default]
96 HmacFnv64,
97 HmacFnv64WithNonce,
99 ChainedHash(u8),
101}
102
103#[derive(Debug, Clone)]
112pub struct AuthKey {
113 pub id: String,
115 pub secret: Vec<u8>,
117 pub created_at: u64,
119 pub expires_at: Option<u64>,
121 pub algorithm: AuthAlgorithm,
123}
124
125impl AuthKey {
126 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 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#[derive(Debug, Clone)]
158pub struct SignedMessage {
159 pub payload: Vec<u8>,
161 pub signature: u64,
163 pub key_id: String,
165 pub nonce: u64,
167 pub timestamp: u64,
169 pub sequence_num: u64,
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
181pub enum AuthPolicy {
182 RequireAll,
184 OptionalSign,
186 KeyRotationRequired(u64),
189 SequentialNonce,
191}
192
193#[derive(Debug, Clone)]
202pub struct ReplayWindow {
203 pub window_size: usize,
205 pub seen_nonces: VecDeque<u64>,
207 pub last_sequence: u64,
209}
210
211impl ReplayWindow {
212 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 pub fn contains(&self, nonce: u64) -> bool {
223 self.seen_nonces.contains(&nonce)
224 }
225
226 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#[derive(Debug, Clone, Default)]
247pub struct AuthStats {
248 pub messages_signed: u64,
250 pub messages_verified: u64,
252 pub replay_attacks_blocked: u64,
254 pub expired_key_rejections: u64,
256 pub invalid_signature_rejections: u64,
258}
259
260#[derive(Debug, thiserror::Error)]
266pub enum AuthError {
267 #[error("key not found: {0}")]
269 KeyNotFound(String),
270
271 #[error("invalid signature for key '{key_id}': expected {expected:#018x}, got {got:#018x}")]
273 InvalidSignature {
274 key_id: String,
276 expected: u64,
278 got: u64,
280 },
281
282 #[error("replay detected for nonce {0:#018x}")]
284 ReplayDetected(u64),
285
286 #[error("key expired: {0}")]
288 KeyExpired(String),
289
290 #[error("nonce generator exhausted — reseed required")]
292 NonceExhausted,
293
294 #[error("invalid sequence number: expected {expected}, got {got}")]
297 InvalidSequence {
298 expected: u64,
300 got: u64,
302 },
303}
304
305#[derive(Debug)]
311struct KeyEntry {
312 key: AuthKey,
313 next_seq: u64,
315}
316
317#[derive(Debug)]
343pub struct MessageAuthenticator {
344 keys: HashMap<String, KeyEntry>,
346 replay_window: ReplayWindow,
348 policies: Vec<AuthPolicy>,
350 prng_state: u64,
352 stats: AuthStats,
354 events: Vec<String>,
356}
357
358impl MessageAuthenticator {
359 pub fn new(policies: Vec<AuthPolicy>, replay_window_size: usize) -> Self {
370 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 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 pub fn add_key(&mut self, key: AuthKey) -> Result<(), AuthError> {
407 let id = key.id.clone();
408 if self.keys.contains_key(&id) {
409 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 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 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 pub fn sign(
475 &mut self,
476 payload: Vec<u8>,
477 key_id: &str,
478 current_ts: u64,
479 ) -> Result<SignedMessage, AuthError> {
480 let entry = self
482 .keys
483 .get_mut(key_id)
484 .ok_or_else(|| AuthError::KeyNotFound(key_id.to_string()))?;
485
486 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 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 let nonce = xorshift64(&mut self.prng_state);
511 if nonce == 0 {
512 return Err(AuthError::NonceExhausted);
513 }
514
515 let sequence_num = entry.next_seq;
517 entry.next_seq = entry.next_seq.wrapping_add(1);
518
519 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 pub fn verify(&mut self, msg: &SignedMessage, current_ts: u64) -> Result<(), AuthError> {
554 let entry = self
556 .keys
557 .get(&msg.key_id)
558 .ok_or_else(|| AuthError::KeyNotFound(msg.key_id.clone()))?;
559
560 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 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 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 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 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 pub fn check_replay(&self, nonce: u64) -> bool {
629 self.replay_window.contains(nonce)
630 }
631
632 pub fn record_nonce(&mut self, nonce: u64) {
634 self.replay_window.record(nonce);
635 }
636
637 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 pub fn stats(&self) -> AuthStats {
668 self.stats.clone()
669 }
670
671 pub fn drain_events(&mut self) -> Vec<String> {
675 std::mem::take(&mut self.events)
676 }
677
678 pub fn key_count(&self) -> usize {
680 self.keys.len()
681 }
682
683 pub fn has_key(&self, key_id: &str) -> bool {
685 self.keys.contains_key(key_id)
686 }
687}
688
689fn 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 let mut current = hmac_fnv64(secret, payload);
709 for _ in 1..rounds {
710 current = hmac_fnv64(¤t.to_le_bytes(), payload);
712 }
713 let nonce_bytes = nonce.to_le_bytes();
715 let mut final_msg = nonce_bytes.to_vec();
716 final_msg.extend_from_slice(¤t.to_le_bytes());
717 hmac_fnv64(secret, &final_msg)
718 }
719 }
720}
721
722#[cfg(test)]
727mod tests {
728 use super::*;
729
730 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 #[test]
763 fn test_fnv1a_empty() {
764 assert_eq!(fnv1a_64(b""), 14_695_981_039_346_656_037u64);
766 }
767
768 #[test]
769 fn test_fnv1a_known_vector() {
770 let h = fnv1a_64(b"hello");
772 assert_ne!(h, 0);
773 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 #[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 #[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 let second = xorshift64(&mut state);
850 assert_ne!(first, second);
851 }
852
853 #[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 #[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 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 #[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 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 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 let msg = auth
1040 .sign(b"data".to_vec(), "k1", 500)
1041 .expect("test: sign data while valid");
1042 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), AuthAlgorithm::HmacFnv64,
1091 );
1092 auth.add_key(key).expect("test: add_key in loop");
1093 }
1094 let removed = auth.expire_keys(200);
1096 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 #[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 assert!(auth.verify(&msg, 0).is_ok());
1115 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 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 for m in &msgs {
1137 let _ = auth.verify(m, 0);
1140 }
1141 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 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 #[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 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 #[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 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 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 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 assert!(auth.verify(&m1, 0).is_ok());
1292 let mut m_bad = m2.clone();
1294 m_bad.sequence_num = 999;
1295 assert!(matches!(
1297 auth.verify(&m_bad, 0),
1298 Err(AuthError::InvalidSequence { .. }) | Err(AuthError::InvalidSignature { .. })
1299 ));
1300 }
1301
1302 #[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 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 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 #[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); 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); 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 #[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 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); 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 #[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 #[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 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 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 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 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 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 assert!(key.is_valid_at(499));
1632 assert!(!key.is_valid_at(500));
1634 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 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}