Skip to main content

sockudo_http/
token.rs

1use crate::util;
2use hmac::{Hmac, Mac};
3use sha2::Sha256;
4use std::fmt;
5use zeroize::Zeroize;
6
7type HmacSha256 = Hmac<Sha256>;
8
9/// Token for signing and verifying data against the app key and secret
10#[derive(Clone)]
11pub struct Token {
12    pub key: String,
13    secret: SecretString,
14}
15
16/// Wrapper for secret that ensures it's zeroed on drop
17#[derive(Clone, Zeroize, ZeroizeOnDrop)]
18struct SecretString(String);
19
20impl Token {
21    /// Creates a new token with the given key and secret
22    pub fn new(key: impl Into<String>, secret: impl Into<String>) -> Self {
23        Self {
24            key: key.into(),
25            secret: SecretString(secret.into()),
26        }
27    }
28
29    /// Signs the string using HMAC-SHA256
30    pub fn sign(&self, data: &str) -> String {
31        let mut mac = HmacSha256::new_from_slice(self.secret.0.as_bytes())
32            .expect("HMAC can take key of any size");
33        mac.update(data.as_bytes());
34
35        // Use hex formatting directly for better performance
36        format!("{:x}", mac.finalize().into_bytes())
37    }
38
39    /// Verifies the signature against the data
40    pub fn verify(&self, data: &str, signature: &str) -> bool {
41        let expected = self.sign(data);
42        util::secure_compare(&expected, signature)
43    }
44
45    /// Gets the secret as a string (for internal use only)
46    pub(crate) fn secret_string(&self) -> String {
47        self.secret.0.clone()
48    }
49}
50
51impl fmt::Debug for Token {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        f.debug_struct("Token")
54            .field("key", &self.key)
55            .field("secret", &"[REDACTED]")
56            .finish()
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn test_sign_and_verify() {
66        let token = Token::new("test_key", "test_secret");
67        let data = "test_data";
68        let signature = token.sign(data);
69
70        assert!(token.verify(data, &signature));
71        assert!(!token.verify("other_data", &signature));
72        assert!(!token.verify(data, "wrong_signature"));
73    }
74
75    #[test]
76    fn test_hmac_consistency() {
77        let token = Token::new("key", "secret");
78        let data = "some data to sign";
79
80        let sig1 = token.sign(data);
81        let sig2 = token.sign(data);
82
83        assert_eq!(sig1, sig2, "HMAC should be deterministic");
84    }
85
86    #[test]
87    fn test_debug_redaction() {
88        let token = Token::new("public_key", "secret_key");
89        let debug_str = format!("{:?}", token);
90
91        assert!(debug_str.contains("public_key"));
92        assert!(debug_str.contains("[REDACTED]"));
93        assert!(!debug_str.contains("secret_key"));
94    }
95}