Skip to main content

modo/auth/session/jwt/
signer.rs

1use std::sync::Arc;
2
3use hmac::{Hmac, KeyInit, Mac};
4use sha2::Sha256;
5
6use crate::{Error, Result};
7
8use super::error::JwtError;
9
10type HmacSha256 = Hmac<Sha256>;
11
12/// Object-safe trait for JWT signature verification.
13///
14/// Implemented by `HmacSigner`. Can be wrapped in `Arc<dyn TokenVerifier>`
15/// for use inside `JwtDecoder`.
16pub trait TokenVerifier: Send + Sync {
17    /// Verifies that `signature` was produced by signing `header_payload`
18    /// with the same key.
19    ///
20    /// # Errors
21    ///
22    /// Returns `Error::unauthorized` with `jwt:invalid_signature` when the
23    /// signature does not match.
24    fn verify(&self, header_payload: &[u8], signature: &[u8]) -> Result<()>;
25    /// Returns the JWT algorithm name used in the token header (e.g., `"HS256"`).
26    fn algorithm_name(&self) -> &str;
27}
28
29/// Extends `TokenVerifier` with signing capability.
30///
31/// Implemented by `HmacSigner`. Can be wrapped in `Arc<dyn TokenSigner>`
32/// for use inside `JwtEncoder`.
33pub trait TokenSigner: TokenVerifier {
34    /// Signs `header_payload` and returns the raw signature bytes.
35    ///
36    /// # Errors
37    ///
38    /// Returns `Error::internal` with `jwt:signing_failed` if the HMAC key is
39    /// invalid.
40    fn sign(&self, header_payload: &[u8]) -> Result<Vec<u8>>;
41}
42
43/// HMAC-SHA256 (HS256) implementation of [`TokenSigner`] and [`TokenVerifier`].
44///
45/// Cloning is cheap — the secret is stored behind `Arc`.
46pub struct HmacSigner {
47    inner: Arc<HmacSignerInner>,
48}
49
50struct HmacSignerInner {
51    secret: Vec<u8>,
52}
53
54impl HmacSigner {
55    /// Creates a new `HmacSigner` with the given secret.
56    pub fn new(secret: impl AsRef<[u8]>) -> Self {
57        Self {
58            inner: Arc::new(HmacSignerInner {
59                secret: secret.as_ref().to_vec(),
60            }),
61        }
62    }
63}
64
65impl Clone for HmacSigner {
66    fn clone(&self) -> Self {
67        Self {
68            inner: self.inner.clone(),
69        }
70    }
71}
72
73impl TokenVerifier for HmacSigner {
74    fn verify(&self, header_payload: &[u8], signature: &[u8]) -> Result<()> {
75        let mut mac = HmacSha256::new_from_slice(&self.inner.secret)
76            .map_err(|_| Error::internal("invalid HMAC key").chain(JwtError::InvalidSignature))?;
77        mac.update(header_payload);
78        mac.verify_slice(signature).map_err(|_| {
79            Error::unauthorized("unauthorized")
80                .chain(JwtError::InvalidSignature)
81                .with_code(JwtError::InvalidSignature.code())
82        })
83    }
84
85    fn algorithm_name(&self) -> &str {
86        "HS256"
87    }
88}
89
90impl TokenSigner for HmacSigner {
91    fn sign(&self, header_payload: &[u8]) -> Result<Vec<u8>> {
92        let mut mac = HmacSha256::new_from_slice(&self.inner.secret)
93            .map_err(|_| Error::internal("invalid HMAC key").chain(JwtError::SigningFailed))?;
94        mac.update(header_payload);
95        Ok(mac.finalize().into_bytes().to_vec())
96    }
97}
98
99impl From<HmacSigner> for Arc<dyn TokenSigner> {
100    fn from(signer: HmacSigner) -> Self {
101        Arc::new(signer)
102    }
103}
104
105impl From<HmacSigner> for Arc<dyn TokenVerifier> {
106    fn from(signer: HmacSigner) -> Self {
107        Arc::new(signer)
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn sign_verify_roundtrip() {
117        let signer = HmacSigner::new(b"secret-key");
118        let data = b"header.payload";
119        let sig = signer.sign(data).unwrap();
120        assert!(signer.verify(data, &sig).is_ok());
121    }
122
123    #[test]
124    fn verify_rejects_tampered_payload() {
125        let signer = HmacSigner::new(b"secret-key");
126        let sig = signer.sign(b"header.payload").unwrap();
127        let result = signer.verify(b"header.tampered", &sig);
128        assert!(result.is_err());
129        let err = result.unwrap_err();
130        assert_eq!(err.status(), http::StatusCode::UNAUTHORIZED);
131    }
132
133    #[test]
134    fn verify_rejects_wrong_secret() {
135        let signer1 = HmacSigner::new(b"secret-one");
136        let signer2 = HmacSigner::new(b"secret-two");
137        let sig = signer1.sign(b"data").unwrap();
138        assert!(signer2.verify(b"data", &sig).is_err());
139    }
140
141    #[test]
142    fn algorithm_name_returns_hs256() {
143        let signer = HmacSigner::new(b"key");
144        assert_eq!(signer.algorithm_name(), "HS256");
145    }
146
147    #[test]
148    fn clone_shares_inner() {
149        let signer = HmacSigner::new(b"key");
150        let cloned = signer.clone();
151        let sig = signer.sign(b"data").unwrap();
152        assert!(cloned.verify(b"data", &sig).is_ok());
153    }
154
155    #[test]
156    fn into_arc_dyn_token_signer() {
157        let signer = HmacSigner::new(b"key");
158        let arc_signer: Arc<dyn TokenSigner> = signer.into();
159        assert_eq!(arc_signer.algorithm_name(), "HS256");
160    }
161
162    #[test]
163    fn into_arc_dyn_token_verifier() {
164        let signer = HmacSigner::new(b"key");
165        let arc_verifier: Arc<dyn TokenVerifier> = signer.into();
166        assert_eq!(arc_verifier.algorithm_name(), "HS256");
167    }
168}