1use hmac::{Hmac, Mac};
18use sha2::Sha256;
19
20type HmacSha256 = Hmac<Sha256>;
21
22pub fn verify_signature(payload: &[u8], signature_header: &str, secret: &str) -> bool {
28 let mut mac = match HmacSha256::new_from_slice(secret.as_bytes()) {
29 Ok(m) => m,
30 Err(_) => return false,
31 };
32 mac.update(payload);
33 let expected = mac.finalize().into_bytes();
34 let Ok(provided) = hex::decode(signature_header) else {
35 return false;
36 };
37 expected.len() == provided.len()
39 && expected
40 .iter()
41 .zip(provided.iter())
42 .fold(0u8, |acc, (a, b)| acc | (a ^ b))
43 == 0
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49
50 #[test]
51 fn valid_signature_returns_true() {
52 let secret = "test-secret";
53 let payload = b"{\"event\":\"test\"}";
54
55 let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
57 mac.update(payload);
58 let sig = hex::encode(mac.finalize().into_bytes());
59
60 assert!(verify_signature(payload, &sig, secret));
61 }
62
63 #[test]
64 fn invalid_signature_returns_false() {
65 assert!(!verify_signature(b"payload", "invalidhex", "secret"));
66 assert!(!verify_signature(b"payload", "deadbeef", "secret"));
67 }
68
69 #[test]
70 fn empty_payload() {
71 let secret = "s";
72 let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
73 mac.update(b"");
74 let sig = hex::encode(mac.finalize().into_bytes());
75 assert!(verify_signature(b"", &sig, secret));
76 }
77}