use oxicrypto_core::Mac;
use oxicrypto_mac::HmacSha256;
use rand_chacha::ChaCha20Rng;
use rand_core::{Rng, SeedableRng};
#[test]
fn prop_mac_verify_consistent() {
for i in 0u8..=50 {
let mut rng = ChaCha20Rng::from_seed([i; 32]);
let mut key = [0u8; 32];
rng.fill_bytes(&mut key);
let mut msg = [0u8; 64];
rng.fill_bytes(&mut msg);
let mut tag = [0u8; 32];
HmacSha256.mac(&key, &msg, &mut tag).expect("mac");
HmacSha256
.verify(&key, &msg, &tag)
.expect("verify must pass for correct tag");
tag[0] ^= 1;
assert!(
HmacSha256.verify(&key, &msg, &tag).is_err(),
"tampered tag (seed={i}) must be rejected"
);
}
}
#[test]
fn prop_mac_key_sensitivity() {
for i in 0u8..=20 {
let mut rng = ChaCha20Rng::from_seed([i; 32]);
let mut key1 = [0u8; 32];
rng.fill_bytes(&mut key1);
let mut key2 = key1;
key2[0] ^= 1;
let msg = b"test message for key sensitivity";
let mut tag1 = [0u8; 32];
let mut tag2 = [0u8; 32];
HmacSha256.mac(&key1, msg, &mut tag1).expect("mac1");
HmacSha256.mac(&key2, msg, &mut tag2).expect("mac2");
assert_ne!(
tag1, tag2,
"different keys (seed={i}) must produce different tags"
);
}
}
#[test]
fn test_mac_zero_length_message() {
let key = b"some-key-for-zero-len-test";
let mut tag1 = [0u8; 32];
let mut tag2 = [0u8; 32];
HmacSha256
.mac(key, b"", &mut tag1)
.expect("mac of empty message must succeed");
HmacSha256
.mac(key, b"", &mut tag2)
.expect("mac of empty message must succeed (2nd call)");
assert_eq!(tag1, tag2, "MAC of empty message must be deterministic");
HmacSha256
.verify(key, b"", &tag1)
.expect("verify of empty-message MAC must succeed");
}
#[test]
fn test_mac_long_key() {
let long_key = vec![0xab_u8; 200]; let msg = b"test message with long key";
let mut tag1 = [0u8; 32];
let mut tag2 = [0u8; 32];
HmacSha256
.mac(&long_key, msg, &mut tag1)
.expect("mac with 200-byte key must succeed");
HmacSha256
.mac(&long_key, msg, &mut tag2)
.expect("mac with 200-byte key must be deterministic");
assert_eq!(tag1, tag2, "long-key MAC must be deterministic");
HmacSha256
.verify(&long_key, msg, &tag1)
.expect("verify with 200-byte key must succeed");
}
#[test]
fn fuzz_verify_no_panic() {
let mut rng = ChaCha20Rng::from_seed([0xde; 32]);
for _ in 0..1000 {
let mut key = [0u8; 32];
rng.fill_bytes(&mut key);
let mut msg = [0u8; 32];
rng.fill_bytes(&mut msg);
let mut random_tag = [0u8; 32];
rng.fill_bytes(&mut random_tag);
let _result = HmacSha256.verify(&key, &msg, &random_tag);
}
}
#[test]
fn prop_mac_ct_timing_sanity() {
let key = b"timing-sanity-key";
let msg = b"timing-sanity-msg";
let mut correct_tag = [0u8; 32];
HmacSha256.mac(key, msg, &mut correct_tag).expect("mac");
assert!(
HmacSha256.verify(key, msg, &correct_tag).is_ok(),
"correct tag must verify"
);
for byte_pos in 0..32 {
let mut bad_tag = correct_tag;
bad_tag[byte_pos] ^= 0x01;
assert!(
HmacSha256.verify(key, msg, &bad_tag).is_err(),
"tag with bit flip at byte {byte_pos} must be rejected"
);
}
}