use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
pub fn verify_signature(payload: &[u8], signature_header: &str, secret: &str) -> bool {
let mut mac = match HmacSha256::new_from_slice(secret.as_bytes()) {
Ok(m) => m,
Err(_) => return false,
};
mac.update(payload);
let expected = mac.finalize().into_bytes();
let Ok(provided) = hex::decode(signature_header) else {
return false;
};
expected.len() == provided.len()
&& expected
.iter()
.zip(provided.iter())
.fold(0u8, |acc, (a, b)| acc | (a ^ b))
== 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_signature_returns_true() {
let secret = "test-secret";
let payload = b"{\"event\":\"test\"}";
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
mac.update(payload);
let sig = hex::encode(mac.finalize().into_bytes());
assert!(verify_signature(payload, &sig, secret));
}
#[test]
fn invalid_signature_returns_false() {
assert!(!verify_signature(b"payload", "invalidhex", "secret"));
assert!(!verify_signature(b"payload", "deadbeef", "secret"));
}
#[test]
fn empty_payload() {
let secret = "s";
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
mac.update(b"");
let sig = hex::encode(mac.finalize().into_bytes());
assert!(verify_signature(b"", &sig, secret));
}
}