truthlinked_sdk/
signing.rs1use hmac::{Hmac, Mac};
2use sha2::Sha256;
3use std::time::{SystemTime, UNIX_EPOCH};
4use base64::Engine;
5
6type HmacSha256 = Hmac<Sha256>;
7
8pub struct RequestSigner {
10 signing_key: [u8; 32],
11}
12
13impl RequestSigner {
14 pub fn new(license_key: &str) -> Self {
16 let mut mac = HmacSha256::new_from_slice(b"truthlinked-request-signing-v1")
18 .expect("HMAC can take key of any size");
19 mac.update(license_key.as_bytes());
20 let result = mac.finalize();
21
22 let mut signing_key = [0u8; 32];
23 signing_key.copy_from_slice(&result.into_bytes());
24
25 Self { signing_key }
26 }
27
28 pub fn sign_request(
30 &self,
31 method: &str,
32 path: &str,
33 body: &[u8],
34 timestamp: u64,
35 ) -> String {
36 let mut mac = HmacSha256::new_from_slice(&self.signing_key)
37 .expect("Valid key length");
38
39 mac.update(method.as_bytes());
41 mac.update(b"\n");
42 mac.update(path.as_bytes());
43 mac.update(b"\n");
44 mac.update(timestamp.to_string().as_bytes());
45 mac.update(b"\n");
46 mac.update(body);
47
48 let signature = mac.finalize();
49 base64::engine::general_purpose::STANDARD.encode(signature.into_bytes())
50 }
51
52 pub fn current_timestamp() -> u64 {
54 SystemTime::now()
55 .duration_since(UNIX_EPOCH)
56 .unwrap()
57 .as_secs()
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn test_request_signing() {
67 let signer = RequestSigner::new("test_key");
68 let signature = signer.sign_request("GET", "/health", b"", 1234567890);
69
70 let signature2 = signer.sign_request("GET", "/health", b"", 1234567890);
72 assert_eq!(signature, signature2);
73
74 let signature3 = signer.sign_request("GET", "/health", b"", 1234567891);
76 assert_ne!(signature, signature3);
77 }
78}