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