use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
pub fn sign(timestamp: i64, secret: &str) -> String {
use base64::engine::Engine;
let content = format!("{timestamp}\n{secret}");
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).expect(
"HMAC can accept keys of any size up to 128GB, which is impossible for webhook secrets",
);
mac.update(content.as_bytes());
base64::engine::general_purpose::STANDARD.encode(mac.finalize().into_bytes())
}
pub fn current_timestamp() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("System time should never be before 1970-01-01 unless the system clock is maliciously modified")
.as_secs() as i64
}
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut result = 0u8;
for (x, y) in a.iter().zip(b.iter()) {
result |= x ^ y;
}
result == 0
}
pub fn verify_signature(timestamp: i64, secret: &str, signature: &str) -> bool {
let computed = sign(timestamp, secret);
constant_time_eq(computed.as_bytes(), signature.as_bytes())
}
#[cfg(test)]
#[allow(unused_imports)]
mod tests {
use super::*;
#[test]
fn test_sign_known_input() {
let timestamp = 1599360473i64;
let secret = "test-secret";
let signature = sign(timestamp, secret);
assert!(!signature.is_empty());
}
#[test]
fn test_sign_different_secrets() {
let timestamp = 1599360473i64;
let sig1 = sign(timestamp, "secret1");
let sig2 = sign(timestamp, "secret2");
assert_ne!(sig1, sig2);
}
#[test]
fn test_sign_different_timestamps() {
let secret = "test-secret";
let sig1 = sign(1599360473, secret);
let sig2 = sign(1599360474, secret);
assert_ne!(sig1, sig2);
}
#[test]
fn test_verify_signature_valid() {
let timestamp = 1599360473i64;
let secret = "test-secret";
let signature = sign(timestamp, secret);
assert!(verify_signature(timestamp, secret, &signature));
}
#[test]
fn test_verify_signature_invalid() {
let timestamp = 1599360473i64;
let secret = "test-secret";
assert!(!verify_signature(timestamp, secret, "invalid-signature"));
}
#[test]
fn test_verify_signature_wrong_secret() {
let timestamp = 1599360473i64;
let secret = "test-secret";
let signature = sign(timestamp, secret);
assert!(!verify_signature(timestamp, "wrong-secret", &signature));
}
#[test]
fn test_current_timestamp() {
let ts = current_timestamp();
assert!(ts > 1577836800); assert!(ts < 2000000000); }
#[test]
fn test_constant_time_eq_equal() {
assert!(constant_time_eq(b"abc", b"abc"));
}
#[test]
fn test_constant_time_eq_not_equal() {
assert!(!constant_time_eq(b"abc", b"abd"));
}
#[test]
fn test_constant_time_eq_different_lengths() {
assert!(!constant_time_eq(b"abc", b"abcd"));
}
}