//! Property-based tests for Mac. #![cfg(feature = "hmac")] use bolero::check; use evidence::{ codec::Identity, mac::{Mac, MacUnchecked, hmac::HmacSha256}, }; use hmac::Hmac; /// Helper to create a key from seed bytes (64 bytes for HMAC-SHA256 block size). fn key_from_seed(seed: [u8; 64]) -> hmac::digest::Key> { *hmac::digest::Key::>::from_slice(&seed) } #[test] #[allow(clippy::expect_used)] // test should panic if verification fails fn mac_verify_roundtrip() { check!() .with_type::<([u8; 64], Vec)>() .for_each(|(seed, payload)| { let key = key_from_seed(*seed); let mac: Mac, HmacSha256, Identity> = Mac::tag(&key, payload); let verified = mac .try_verify(&key) .expect("verification should succeed for just-tagged payload"); assert_eq!(&verified, payload, "verified payload must match original"); }); } #[test] fn mac_deterministic() { check!() .with_type::<([u8; 64], Vec)>() .for_each(|(seed, payload)| { let key = key_from_seed(*seed); let mac1: Mac, HmacSha256, Identity> = Mac::tag(&key, payload); let mac2: Mac, HmacSha256, Identity> = Mac::tag(&key, payload); assert_eq!(mac1, mac2, "same input must produce same MAC"); }); } #[test] fn different_payloads_different_macs() { check!() .with_type::<([u8; 64], Vec, Vec)>() .filter(|(_, a, b)| a != b) .for_each(|(seed, payload_a, payload_b)| { let key = key_from_seed(*seed); let mac_a: Mac, HmacSha256, Identity> = Mac::tag(&key, payload_a); let mac_b: Mac, HmacSha256, Identity> = Mac::tag(&key, payload_b); assert_ne!( mac_a.mac_tag(), mac_b.mac_tag(), "different payloads should produce different MACs" ); }); } #[test] fn wrong_key_fails_verification() { check!() .with_type::<([u8; 64], [u8; 64], Vec)>() .filter(|(a, b, _)| a != b) .for_each(|(seed_correct, seed_wrong, payload)| { let correct_key = key_from_seed(*seed_correct); let wrong_key = key_from_seed(*seed_wrong); let mac: Mac, HmacSha256, Identity> = Mac::tag(&correct_key, payload); assert!( mac.try_verify(&wrong_key).is_err(), "verification must fail with wrong key" ); }); } #[test] #[allow(clippy::indexing_slicing)] // payload is non-empty (filter guarantees) fn tampered_payload_fails_verification() { check!() .with_type::<([u8; 64], Vec)>() .cloned() .filter(|(_, payload)| !payload.is_empty()) .for_each(|(seed, payload)| { let key = key_from_seed(seed); let mac: Mac, HmacSha256, Identity> = Mac::tag(&key, &payload); let mut tampered_bytes = mac.encoded_payload().to_vec(); tampered_bytes[0] = tampered_bytes[0].wrapping_add(1); let tampered: Mac, HmacSha256, Identity> = Mac::from_unchecked_parts(*mac.mac_tag(), tampered_bytes); assert!( tampered.try_verify(&key).is_err(), "verification must fail with tampered payload" ); }); } #[test] fn tampered_tag_fails_verification() { check!() .with_type::<([u8; 64], Vec)>() .cloned() .for_each(|(seed, payload)| { let key = key_from_seed(seed); let mac: Mac, HmacSha256, Identity> = Mac::tag(&key, &payload); let mut tampered_tag = *mac.mac_tag(); tampered_tag[0] = tampered_tag[0].wrapping_add(1); let tampered: Mac, HmacSha256, Identity> = Mac::from_unchecked_parts(tampered_tag, mac.encoded_payload().to_vec()); assert!( tampered.try_verify(&key).is_err(), "verification must fail with tampered tag" ); }); }