jwtiny/algorithm/
hmac.rs

1use crate::algorithm::Algorithm;
2use crate::error::{Error, Result};
3use crate::keys::Key;
4use crate::utils::base64url;
5
6use constant_time_eq::constant_time_eq;
7use hmac::{Hmac, Mac};
8use sha2::{Sha256, Sha384, Sha512};
9
10/// HS256 algorithm (HMAC with SHA-256)
11pub struct HS256;
12
13/// HS384 algorithm (HMAC with SHA-384)
14pub struct HS384;
15
16/// HS512 algorithm (HMAC with SHA-512)
17pub struct HS512;
18
19impl Algorithm for HS256 {
20    fn name(&self) -> &'static str {
21        "HS256"
22    }
23
24    fn verify(&self, signing_input: &str, signature: &str, key: &Key) -> Result<()> {
25        let symmetric_key = key.as_symmetric()?;
26        verify_hs256(signing_input, signature, symmetric_key.as_bytes())
27    }
28}
29
30impl Algorithm for HS384 {
31    fn name(&self) -> &'static str {
32        "HS384"
33    }
34
35    fn verify(&self, signing_input: &str, signature: &str, key: &Key) -> Result<()> {
36        let symmetric_key = key.as_symmetric()?;
37        verify_hs384(signing_input, signature, symmetric_key.as_bytes())
38    }
39}
40
41impl Algorithm for HS512 {
42    fn name(&self) -> &'static str {
43        "HS512"
44    }
45
46    fn verify(&self, signing_input: &str, signature: &str, key: &Key) -> Result<()> {
47        let symmetric_key = key.as_symmetric()?;
48        verify_hs512(signing_input, signature, symmetric_key.as_bytes())
49    }
50}
51
52/// Verify HS256 signature with constant-time comparison
53fn verify_hs256(signing_input: &str, signature: &str, secret: &[u8]) -> Result<()> {
54    let provided_signature = base64url::decode_bytes(signature)?;
55
56    let mut mac = Hmac::<Sha256>::new_from_slice(secret).map_err(|_| Error::SignatureInvalid)?;
57    mac.update(signing_input.as_bytes());
58    let expected_signature = mac.finalize().into_bytes();
59
60    if provided_signature.len() != expected_signature.len() {
61        return Err(Error::SignatureInvalid);
62    }
63
64    if constant_time_eq(&provided_signature, &expected_signature) {
65        Ok(())
66    } else {
67        Err(Error::SignatureInvalid)
68    }
69}
70
71/// Verify HS384 signature with constant-time comparison
72fn verify_hs384(signing_input: &str, signature: &str, secret: &[u8]) -> Result<()> {
73    let provided_signature = base64url::decode_bytes(signature)?;
74
75    let mut mac = Hmac::<Sha384>::new_from_slice(secret).map_err(|_| Error::SignatureInvalid)?;
76    mac.update(signing_input.as_bytes());
77    let expected_signature = mac.finalize().into_bytes();
78
79    if provided_signature.len() != expected_signature.len() {
80        return Err(Error::SignatureInvalid);
81    }
82
83    if constant_time_eq(&provided_signature, &expected_signature) {
84        Ok(())
85    } else {
86        Err(Error::SignatureInvalid)
87    }
88}
89
90/// Verify HS512 signature with constant-time comparison
91fn verify_hs512(signing_input: &str, signature: &str, secret: &[u8]) -> Result<()> {
92    let provided_signature = base64url::decode_bytes(signature)?;
93
94    let mut mac = Hmac::<Sha512>::new_from_slice(secret).map_err(|_| Error::SignatureInvalid)?;
95    mac.update(signing_input.as_bytes());
96    let expected_signature = mac.finalize().into_bytes();
97
98    if provided_signature.len() != expected_signature.len() {
99        return Err(Error::SignatureInvalid);
100    }
101
102    if constant_time_eq(&provided_signature, &expected_signature) {
103        Ok(())
104    } else {
105        Err(Error::SignatureInvalid)
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    fn compute_hs256_signature(signing_input: &str, secret: &[u8]) -> String {
114        let mut mac = Hmac::<Sha256>::new_from_slice(secret).unwrap();
115        mac.update(signing_input.as_bytes());
116        let signature_bytes = mac.finalize().into_bytes();
117        base64url::encode_bytes(&signature_bytes)
118    }
119
120    fn compute_hs384_signature(signing_input: &str, secret: &[u8]) -> String {
121        let mut mac = Hmac::<Sha384>::new_from_slice(secret).unwrap();
122        mac.update(signing_input.as_bytes());
123        let signature_bytes = mac.finalize().into_bytes();
124        base64url::encode_bytes(&signature_bytes)
125    }
126
127    fn compute_hs512_signature(signing_input: &str, secret: &[u8]) -> String {
128        let mut mac = Hmac::<Sha512>::new_from_slice(secret).unwrap();
129        mac.update(signing_input.as_bytes());
130        let signature_bytes = mac.finalize().into_bytes();
131        base64url::encode_bytes(&signature_bytes)
132    }
133
134    #[test]
135    fn test_hs256_valid_signature() {
136        let signing_input = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0";
137        let secret = b"your-256-bit-secret";
138        let signature = compute_hs256_signature(signing_input, secret);
139
140        let key = Key::symmetric(secret.to_vec());
141        let result = HS256.verify(signing_input, &signature, &key);
142        assert!(result.is_ok());
143    }
144
145    #[test]
146    fn test_hs256_invalid_signature() {
147        let signing_input = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0";
148        let secret = b"your-256-bit-secret";
149        let key = Key::symmetric(secret.to_vec());
150
151        let wrong_signature = base64url::encode("wrong");
152        let result = HS256.verify(signing_input, &wrong_signature, &key);
153        assert!(matches!(result, Err(Error::SignatureInvalid)));
154    }
155
156    #[test]
157    fn test_hs256_wrong_secret() {
158        let signing_input = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0";
159        let secret = b"your-256-bit-secret";
160        let wrong_secret = b"wrong-secret";
161
162        let signature = compute_hs256_signature(signing_input, secret);
163        let key = Key::symmetric(wrong_secret.to_vec());
164
165        let result = HS256.verify(signing_input, &signature, &key);
166        assert!(matches!(result, Err(Error::SignatureInvalid)));
167    }
168
169    #[test]
170    fn test_hs384_valid_signature() {
171        let signing_input = "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0";
172        let secret = b"your-384-bit-secret-needs-to-be-longer";
173        let signature = compute_hs384_signature(signing_input, secret);
174
175        let key = Key::symmetric(secret.to_vec());
176        let result = HS384.verify(signing_input, &signature, &key);
177        assert!(result.is_ok());
178    }
179
180    #[test]
181    fn test_hs512_valid_signature() {
182        let signing_input = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0";
183        let secret = b"your-512-bit-secret-needs-to-be-even-longer-than-384-bit";
184        let signature = compute_hs512_signature(signing_input, secret);
185
186        let key = Key::symmetric(secret.to_vec());
187        let result = HS512.verify(signing_input, &signature, &key);
188        assert!(result.is_ok());
189    }
190
191    #[test]
192    #[allow(unused_variables)]
193    fn test_wrong_key_type() {
194        let signing_input = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0";
195        let signature = "signature";
196
197        // Try with RSA key (wrong type)
198        #[cfg(feature = "rsa")]
199        {
200            let rsa_key = Key::rsa_public(vec![1, 2, 3]);
201            let result = HS256.verify(signing_input, signature, &rsa_key);
202            assert!(matches!(result, Err(Error::KeyTypeMismatch { .. })));
203        }
204    }
205}