this repo has no description
1use super::token::{
2 SCOPE_ACCESS, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED, SCOPE_REFRESH, TOKEN_TYPE_ACCESS,
3 TOKEN_TYPE_REFRESH,
4};
5use super::types::{Claims, Header, TokenData, TokenVerifyError, UnsafeClaims};
6use anyhow::{Context, Result, anyhow};
7use base64::Engine as _;
8use base64::engine::general_purpose::URL_SAFE_NO_PAD;
9use chrono::Utc;
10use hmac::{Hmac, Mac};
11use k256::ecdsa::{Signature, SigningKey, VerifyingKey, signature::Verifier};
12use sha2::Sha256;
13use subtle::ConstantTimeEq;
14
15type HmacSha256 = Hmac<Sha256>;
16
17pub fn get_did_from_token(token: &str) -> Result<String, String> {
18 let parts: Vec<&str> = token.split('.').collect();
19 if parts.len() != 3 {
20 return Err("Invalid token format".to_string());
21 }
22
23 let payload_bytes = URL_SAFE_NO_PAD
24 .decode(parts[1])
25 .map_err(|e| format!("Base64 decode failed: {}", e))?;
26
27 let claims: UnsafeClaims =
28 serde_json::from_slice(&payload_bytes).map_err(|e| format!("JSON decode failed: {}", e))?;
29
30 Ok(claims.sub.unwrap_or(claims.iss))
31}
32
33pub fn get_jti_from_token(token: &str) -> Result<String, String> {
34 let parts: Vec<&str> = token.split('.').collect();
35 if parts.len() != 3 {
36 return Err("Invalid token format".to_string());
37 }
38
39 let payload_bytes = URL_SAFE_NO_PAD
40 .decode(parts[1])
41 .map_err(|e| format!("Base64 decode failed: {}", e))?;
42
43 let claims: serde_json::Value =
44 serde_json::from_slice(&payload_bytes).map_err(|e| format!("JSON decode failed: {}", e))?;
45
46 claims
47 .get("jti")
48 .and_then(|j| j.as_str())
49 .map(|s| s.to_string())
50 .ok_or_else(|| "No jti claim in token".to_string())
51}
52
53pub fn get_algorithm_from_token(token: &str) -> Result<String, String> {
54 let parts: Vec<&str> = token.split('.').collect();
55 if parts.len() != 3 {
56 return Err("Invalid token format".to_string());
57 }
58
59 let header_bytes = URL_SAFE_NO_PAD
60 .decode(parts[0])
61 .map_err(|e| format!("Base64 decode failed: {}", e))?;
62
63 let header: Header =
64 serde_json::from_slice(&header_bytes).map_err(|e| format!("JSON decode failed: {}", e))?;
65
66 Ok(header.alg)
67}
68
69pub fn verify_token(token: &str, key_bytes: &[u8]) -> Result<TokenData<Claims>> {
70 verify_token_internal(token, key_bytes, None, None)
71}
72
73pub fn verify_access_token(token: &str, key_bytes: &[u8]) -> Result<TokenData<Claims>> {
74 verify_token_internal(
75 token,
76 key_bytes,
77 Some(TOKEN_TYPE_ACCESS),
78 Some(&[SCOPE_ACCESS, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED]),
79 )
80}
81
82pub fn verify_refresh_token(token: &str, key_bytes: &[u8]) -> Result<TokenData<Claims>> {
83 verify_token_internal(
84 token,
85 key_bytes,
86 Some(TOKEN_TYPE_REFRESH),
87 Some(&[SCOPE_REFRESH]),
88 )
89}
90
91pub fn verify_access_token_hs256(token: &str, secret: &[u8]) -> Result<TokenData<Claims>> {
92 verify_token_hs256_internal(
93 token,
94 secret,
95 Some(TOKEN_TYPE_ACCESS),
96 Some(&[SCOPE_ACCESS, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED]),
97 )
98}
99
100pub fn verify_refresh_token_hs256(token: &str, secret: &[u8]) -> Result<TokenData<Claims>> {
101 verify_token_hs256_internal(
102 token,
103 secret,
104 Some(TOKEN_TYPE_REFRESH),
105 Some(&[SCOPE_REFRESH]),
106 )
107}
108
109fn verify_token_internal(
110 token: &str,
111 key_bytes: &[u8],
112 expected_typ: Option<&str>,
113 allowed_scopes: Option<&[&str]>,
114) -> Result<TokenData<Claims>> {
115 let parts: Vec<&str> = token.split('.').collect();
116 if parts.len() != 3 {
117 return Err(anyhow!("Invalid token format"));
118 }
119
120 let header_b64 = parts[0];
121 let claims_b64 = parts[1];
122 let signature_b64 = parts[2];
123
124 let header_bytes = URL_SAFE_NO_PAD
125 .decode(header_b64)
126 .context("Base64 decode of header failed")?;
127
128 let header: Header =
129 serde_json::from_slice(&header_bytes).context("JSON decode of header failed")?;
130
131 if let Some(expected) = expected_typ
132 && header.typ != expected
133 {
134 return Err(anyhow!(
135 "Invalid token type: expected {}, got {}",
136 expected,
137 header.typ
138 ));
139 }
140
141 let signature_bytes = URL_SAFE_NO_PAD
142 .decode(signature_b64)
143 .context("Base64 decode of signature failed")?;
144
145 let signature = Signature::from_slice(&signature_bytes)
146 .map_err(|e| anyhow!("Invalid signature format: {}", e))?;
147
148 let signing_key = SigningKey::from_slice(key_bytes)?;
149 let verifying_key = VerifyingKey::from(&signing_key);
150
151 let message = format!("{}.{}", header_b64, claims_b64);
152 verifying_key
153 .verify(message.as_bytes(), &signature)
154 .map_err(|e| anyhow!("Signature verification failed: {}", e))?;
155
156 let claims_bytes = URL_SAFE_NO_PAD
157 .decode(claims_b64)
158 .context("Base64 decode of claims failed")?;
159
160 let claims: Claims =
161 serde_json::from_slice(&claims_bytes).context("JSON decode of claims failed")?;
162
163 let now = Utc::now().timestamp() as usize;
164 if claims.exp < now {
165 return Err(anyhow!("Token expired"));
166 }
167
168 if let Some(scopes) = allowed_scopes {
169 let token_scope = claims.scope.as_deref().unwrap_or("");
170 if !scopes.contains(&token_scope) {
171 return Err(anyhow!("Invalid token scope: {}", token_scope));
172 }
173 }
174
175 Ok(TokenData { claims })
176}
177
178fn verify_token_hs256_internal(
179 token: &str,
180 secret: &[u8],
181 expected_typ: Option<&str>,
182 allowed_scopes: Option<&[&str]>,
183) -> Result<TokenData<Claims>> {
184 let parts: Vec<&str> = token.split('.').collect();
185 if parts.len() != 3 {
186 return Err(anyhow!("Invalid token format"));
187 }
188
189 let header_b64 = parts[0];
190 let claims_b64 = parts[1];
191 let signature_b64 = parts[2];
192
193 let header_bytes = URL_SAFE_NO_PAD
194 .decode(header_b64)
195 .context("Base64 decode of header failed")?;
196
197 let header: Header =
198 serde_json::from_slice(&header_bytes).context("JSON decode of header failed")?;
199
200 if header.alg != "HS256" {
201 return Err(anyhow!("Expected HS256 algorithm, got {}", header.alg));
202 }
203
204 if let Some(expected) = expected_typ
205 && header.typ != expected
206 {
207 return Err(anyhow!(
208 "Invalid token type: expected {}, got {}",
209 expected,
210 header.typ
211 ));
212 }
213
214 let signature_bytes = URL_SAFE_NO_PAD
215 .decode(signature_b64)
216 .context("Base64 decode of signature failed")?;
217
218 let message = format!("{}.{}", header_b64, claims_b64);
219
220 let mut mac =
221 HmacSha256::new_from_slice(secret).map_err(|e| anyhow!("Invalid secret: {}", e))?;
222 mac.update(message.as_bytes());
223
224 let expected_signature = mac.finalize().into_bytes();
225 let is_valid: bool = signature_bytes.ct_eq(&expected_signature).into();
226
227 if !is_valid {
228 return Err(anyhow!("Signature verification failed"));
229 }
230
231 let claims_bytes = URL_SAFE_NO_PAD
232 .decode(claims_b64)
233 .context("Base64 decode of claims failed")?;
234
235 let claims: Claims =
236 serde_json::from_slice(&claims_bytes).context("JSON decode of claims failed")?;
237
238 let now = Utc::now().timestamp() as usize;
239 if claims.exp < now {
240 return Err(anyhow!("Token expired"));
241 }
242
243 if let Some(scopes) = allowed_scopes {
244 let token_scope = claims.scope.as_deref().unwrap_or("");
245 if !scopes.contains(&token_scope) {
246 return Err(anyhow!("Invalid token scope: {}", token_scope));
247 }
248 }
249
250 Ok(TokenData { claims })
251}
252
253pub fn verify_access_token_typed(
254 token: &str,
255 key_bytes: &[u8],
256) -> Result<TokenData<Claims>, TokenVerifyError> {
257 verify_token_typed_internal(token, key_bytes, Some(TOKEN_TYPE_ACCESS), None)
258}
259
260fn verify_token_typed_internal(
261 token: &str,
262 key_bytes: &[u8],
263 expected_typ: Option<&str>,
264 allowed_scopes: Option<&[&str]>,
265) -> Result<TokenData<Claims>, TokenVerifyError> {
266 let parts: Vec<&str> = token.split('.').collect();
267 if parts.len() != 3 {
268 return Err(TokenVerifyError::Invalid);
269 }
270
271 let header_b64 = parts[0];
272 let claims_b64 = parts[1];
273 let signature_b64 = parts[2];
274
275 let Ok(header_bytes) = URL_SAFE_NO_PAD.decode(header_b64) else {
276 return Err(TokenVerifyError::Invalid);
277 };
278
279 let Ok(header) = serde_json::from_slice::<Header>(&header_bytes) else {
280 return Err(TokenVerifyError::Invalid);
281 };
282
283 if let Some(expected) = expected_typ
284 && header.typ != expected
285 {
286 return Err(TokenVerifyError::Invalid);
287 }
288
289 let Ok(signature_bytes) = URL_SAFE_NO_PAD.decode(signature_b64) else {
290 return Err(TokenVerifyError::Invalid);
291 };
292
293 let Ok(signature) = Signature::from_slice(&signature_bytes) else {
294 return Err(TokenVerifyError::Invalid);
295 };
296
297 let Ok(signing_key) = SigningKey::from_slice(key_bytes) else {
298 return Err(TokenVerifyError::Invalid);
299 };
300 let verifying_key = VerifyingKey::from(&signing_key);
301
302 let message = format!("{}.{}", header_b64, claims_b64);
303 if verifying_key
304 .verify(message.as_bytes(), &signature)
305 .is_err()
306 {
307 return Err(TokenVerifyError::Invalid);
308 }
309
310 let Ok(claims_bytes) = URL_SAFE_NO_PAD.decode(claims_b64) else {
311 return Err(TokenVerifyError::Invalid);
312 };
313
314 let Ok(claims) = serde_json::from_slice::<Claims>(&claims_bytes) else {
315 return Err(TokenVerifyError::Invalid);
316 };
317
318 let now = Utc::now().timestamp() as usize;
319 if claims.exp < now {
320 return Err(TokenVerifyError::Expired);
321 }
322
323 if let Some(scopes) = allowed_scopes {
324 let token_scope = claims.scope.as_deref().unwrap_or("");
325 if !scopes.contains(&token_scope) {
326 return Err(TokenVerifyError::Invalid);
327 }
328 }
329
330 Ok(TokenData { claims })
331}