pub(crate) mod httpsig;
pub(crate) mod pageant;
pub(crate) mod ssh_agent;
pub(crate) mod ssh_sign;
use hmac::{Hmac, Mac};
use sha2::Sha256;
use subtle::ConstantTimeEq;
type HmacSha256 = Hmac<Sha256>;
pub fn verify_webhook_signature(
secret: &str,
expected: &str,
payload: &[u8],
) -> crate::Result<bool> {
let expected_bytes = hex::decode(expected)
.map_err(|_| crate::Error::Validation("invalid hex signature".into()))?;
let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
.map_err(|_| crate::Error::Validation("HMAC key error".into()))?;
mac.update(payload);
let computed = mac.finalize().into_bytes();
Ok(computed.ct_eq(&expected_bytes).into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_webhook_valid() {
let secret = "my-secret";
let payload = b"test-payload";
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
mac.update(payload);
let expected_hex = hex::encode(mac.finalize().into_bytes());
assert!(verify_webhook_signature(secret, &expected_hex, payload).unwrap());
}
#[test]
fn test_webhook_invalid() {
let secret = "my-secret";
let payload = b"test-payload";
let wrong_sig = "0000000000000000000000000000000000000000000000000000000000000000";
assert!(!verify_webhook_signature(secret, wrong_sig, payload).unwrap());
}
#[test]
fn test_webhook_bad_hex() {
let result = verify_webhook_signature("secret", "not-hex-at-all!", b"payload");
assert!(result.is_err());
}
#[test]
fn test_webhook_wrong_length() {
let secret = "secret";
let payload = b"payload";
let wrong_sig = "abcd";
assert!(!verify_webhook_signature(secret, wrong_sig, payload).unwrap());
}
#[test]
fn test_webhook_empty_secret() {
let secret = "";
let payload = b"test";
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
mac.update(payload);
let expected_hex = hex::encode(mac.finalize().into_bytes());
assert!(verify_webhook_signature("", &expected_hex, payload).unwrap());
}
}