telemetry_kit/sync/
auth.rs1use hmac::{Hmac, Mac};
4use sha2::Sha256;
5
6type HmacSha256 = Hmac<Sha256>;
7
8pub struct HmacAuth {
10 secret: String,
11}
12
13impl HmacAuth {
14 pub fn new(secret: impl Into<String>) -> Self {
16 Self {
17 secret: secret.into(),
18 }
19 }
20
21 pub fn sign(&self, timestamp: &str, nonce: &str, body: &str) -> String {
33 let message = format!("{}:{}:{}", timestamp, nonce, body);
34
35 let mut mac = HmacSha256::new_from_slice(self.secret.as_bytes())
36 .expect("HMAC can take key of any size");
37
38 mac.update(message.as_bytes());
39
40 let result = mac.finalize();
41 hex::encode(result.into_bytes())
42 }
43
44 pub fn verify(&self, timestamp: &str, nonce: &str, body: &str, signature: &str) -> bool {
55 let expected = self.sign(timestamp, nonce, body);
56 constant_time_eq(signature.as_bytes(), expected.as_bytes())
57 }
58}
59
60fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
62 if a.len() != b.len() {
63 return false;
64 }
65
66 let mut diff = 0u8;
67 for (a_byte, b_byte) in a.iter().zip(b.iter()) {
68 diff |= a_byte ^ b_byte;
69 }
70
71 diff == 0
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn test_hmac_signing() {
80 let auth = HmacAuth::new("test_secret_key");
81 let timestamp = "1732003200";
82 let nonce = "550e8400-e29b-41d4-a716-446655440000";
83 let body = r#"{"events":[]}"#;
84
85 let signature = auth.sign(timestamp, nonce, body);
86
87 assert_eq!(signature.len(), 64);
89 assert!(signature.chars().all(|c| c.is_ascii_hexdigit()));
90 }
91
92 #[test]
93 fn test_hmac_verification() {
94 let auth = HmacAuth::new("test_secret_key");
95 let timestamp = "1732003200";
96 let nonce = "550e8400-e29b-41d4-a716-446655440000";
97 let body = r#"{"events":[]}"#;
98
99 let signature = auth.sign(timestamp, nonce, body);
100
101 assert!(auth.verify(timestamp, nonce, body, &signature));
103
104 let tampered_body = r#"{"events":[{"tampered":true}]}"#;
106 assert!(!auth.verify(timestamp, nonce, tampered_body, &signature));
107 }
108
109 #[test]
110 fn test_signature_determinism() {
111 let auth = HmacAuth::new("test_secret_key");
112 let timestamp = "1732003200";
113 let nonce = "550e8400-e29b-41d4-a716-446655440000";
114 let body = r#"{"events":[]}"#;
115
116 let sig1 = auth.sign(timestamp, nonce, body);
117 let sig2 = auth.sign(timestamp, nonce, body);
118
119 assert_eq!(sig1, sig2);
121 }
122
123 #[test]
124 fn test_constant_time_eq() {
125 assert!(constant_time_eq(b"hello", b"hello"));
126 assert!(!constant_time_eq(b"hello", b"world"));
127 assert!(!constant_time_eq(b"hello", b"hello!"));
128 }
129}