1use sha2::{Digest, Sha256};
16
17pub const SIGNATURE_HEADER: &str = "X-Webhook-Signature";
19
20const BLOCK_SIZE: usize = 64;
21
22pub fn verify_signature(payload: &[u8], signature: &str, secret: &str) -> bool {
25 if secret.is_empty() || signature.is_empty() {
26 return false;
27 }
28 constant_time_eq(
29 compute_signature(payload, secret).as_bytes(),
30 signature.trim().as_bytes(),
31 )
32}
33
34pub fn compute_signature(payload: &[u8], secret: &str) -> String {
36 hex(&hmac_sha256(secret.as_bytes(), payload))
37}
38
39fn hmac_sha256(key: &[u8], message: &[u8]) -> [u8; 32] {
43 let mut block = [0u8; BLOCK_SIZE];
44 if key.len() > BLOCK_SIZE {
45 block[..32].copy_from_slice(&Sha256::digest(key));
46 } else {
47 block[..key.len()].copy_from_slice(key);
48 }
49 let mut inner = Sha256::new();
50 inner.update(block.map(|byte| byte ^ 0x36));
51 inner.update(message);
52 let inner = inner.finalize();
53 let mut outer = Sha256::new();
54 outer.update(block.map(|byte| byte ^ 0x5c));
55 outer.update(inner);
56 outer.finalize().into()
57}
58
59fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
63 let mut difference = a.len() ^ b.len();
64 for index in 0..a.len().max(b.len()) {
65 let left = a.get(index).copied().unwrap_or(0);
66 let right = b.get(index).copied().unwrap_or(0);
67 difference |= usize::from(left ^ right);
68 }
69 difference == 0
70}
71
72fn hex(bytes: &[u8]) -> String {
73 use std::fmt::Write;
74 bytes.iter().fold(String::new(), |mut out, byte| {
75 let _ = write!(out, "{byte:02x}");
76 out
77 })
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
86 fn hmac_matches_the_rfc_4231_vector() {
87 assert_eq!(
88 compute_signature(b"what do ya want for nothing?", "Jefe"),
89 "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
90 );
91 }
92
93 #[test]
95 fn a_long_key_is_hashed_before_use() {
96 let key = "\u{aa}".repeat(131).into_bytes();
97 let key: Vec<u8> = key.iter().filter(|byte| **byte == 0xaa).copied().collect();
98 assert_eq!(key.len(), 131);
99 let message = b"Test Using Larger Than Block-Size Key - Hash Key First";
100 assert_eq!(
101 hex(&hmac_sha256(&key, message)),
102 "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
103 );
104 }
105
106 #[test]
107 fn verification_needs_a_secret_and_a_signature_and_the_same_bytes() {
108 let signature = compute_signature(b"payload", "secret");
109 assert!(verify_signature(b"payload", &signature, "secret"));
110 assert!(verify_signature(
111 b"payload",
112 &format!(" {signature}\n"),
113 "secret"
114 ));
115 assert!(!verify_signature(b"payload", &signature, "other"));
116 assert!(!verify_signature(b"other", &signature, "secret"));
117 assert!(!verify_signature(b"payload", "", "secret"));
118 assert!(!verify_signature(b"payload", &signature, ""));
119 assert!(!verify_signature(b"payload", &signature[..10], "secret"));
120 }
121
122 #[test]
123 fn constant_time_comparison_is_exact() {
124 assert!(constant_time_eq(b"abc", b"abc"));
125 assert!(!constant_time_eq(b"abc", b"abd"));
126 assert!(!constant_time_eq(b"abc", b"ab"));
127 assert!(!constant_time_eq(b"", b"a"));
128 assert!(constant_time_eq(b"", b""));
129 }
130}