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 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 verify_token(token: &str, key_bytes: &[u8]) -> Result<TokenData<Claims>> {
54 verify_token_internal(token, key_bytes, None, None)
55}
56
57pub fn verify_access_token(token: &str, key_bytes: &[u8]) -> Result<TokenData<Claims>> {
58 verify_token_internal(
59 token,
60 key_bytes,
61 Some(TOKEN_TYPE_ACCESS),
62 Some(&[SCOPE_ACCESS, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED]),
63 )
64}
65
66pub fn verify_refresh_token(token: &str, key_bytes: &[u8]) -> Result<TokenData<Claims>> {
67 verify_token_internal(
68 token,
69 key_bytes,
70 Some(TOKEN_TYPE_REFRESH),
71 Some(&[SCOPE_REFRESH]),
72 )
73}
74
75pub fn verify_access_token_hs256(token: &str, secret: &[u8]) -> Result<TokenData<Claims>> {
76 verify_token_hs256_internal(
77 token,
78 secret,
79 Some(TOKEN_TYPE_ACCESS),
80 Some(&[SCOPE_ACCESS, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED]),
81 )
82}
83
84pub fn verify_refresh_token_hs256(token: &str, secret: &[u8]) -> Result<TokenData<Claims>> {
85 verify_token_hs256_internal(
86 token,
87 secret,
88 Some(TOKEN_TYPE_REFRESH),
89 Some(&[SCOPE_REFRESH]),
90 )
91}
92
93fn verify_token_internal(
94 token: &str,
95 key_bytes: &[u8],
96 expected_typ: Option<&str>,
97 allowed_scopes: Option<&[&str]>,
98) -> Result<TokenData<Claims>> {
99 let parts: Vec<&str> = token.split('.').collect();
100 if parts.len() != 3 {
101 return Err(anyhow!("Invalid token format"));
102 }
103
104 let header_b64 = parts[0];
105 let claims_b64 = parts[1];
106 let signature_b64 = parts[2];
107
108 let header_bytes = URL_SAFE_NO_PAD
109 .decode(header_b64)
110 .context("Base64 decode of header failed")?;
111
112 let header: Header =
113 serde_json::from_slice(&header_bytes).context("JSON decode of header failed")?;
114
115 if let Some(expected) = expected_typ
116 && header.typ != expected
117 {
118 return Err(anyhow!(
119 "Invalid token type: expected {}, got {}",
120 expected,
121 header.typ
122 ));
123 }
124
125 let signature_bytes = URL_SAFE_NO_PAD
126 .decode(signature_b64)
127 .context("Base64 decode of signature failed")?;
128
129 let signature = Signature::from_slice(&signature_bytes)
130 .map_err(|e| anyhow!("Invalid signature format: {}", e))?;
131
132 let signing_key = SigningKey::from_slice(key_bytes)?;
133 let verifying_key = VerifyingKey::from(&signing_key);
134
135 let message = format!("{}.{}", header_b64, claims_b64);
136 verifying_key
137 .verify(message.as_bytes(), &signature)
138 .map_err(|e| anyhow!("Signature verification failed: {}", e))?;
139
140 let claims_bytes = URL_SAFE_NO_PAD
141 .decode(claims_b64)
142 .context("Base64 decode of claims failed")?;
143
144 let claims: Claims =
145 serde_json::from_slice(&claims_bytes).context("JSON decode of claims failed")?;
146
147 let now = Utc::now().timestamp() as usize;
148 if claims.exp < now {
149 return Err(anyhow!("Token expired"));
150 }
151
152 if let Some(scopes) = allowed_scopes {
153 let token_scope = claims.scope.as_deref().unwrap_or("");
154 if !scopes.contains(&token_scope) {
155 return Err(anyhow!("Invalid token scope: {}", token_scope));
156 }
157 }
158
159 Ok(TokenData { claims })
160}
161
162fn verify_token_hs256_internal(
163 token: &str,
164 secret: &[u8],
165 expected_typ: Option<&str>,
166 allowed_scopes: Option<&[&str]>,
167) -> Result<TokenData<Claims>> {
168 let parts: Vec<&str> = token.split('.').collect();
169 if parts.len() != 3 {
170 return Err(anyhow!("Invalid token format"));
171 }
172
173 let header_b64 = parts[0];
174 let claims_b64 = parts[1];
175 let signature_b64 = parts[2];
176
177 let header_bytes = URL_SAFE_NO_PAD
178 .decode(header_b64)
179 .context("Base64 decode of header failed")?;
180
181 let header: Header =
182 serde_json::from_slice(&header_bytes).context("JSON decode of header failed")?;
183
184 if header.alg != "HS256" {
185 return Err(anyhow!("Expected HS256 algorithm, got {}", header.alg));
186 }
187
188 if let Some(expected) = expected_typ
189 && header.typ != expected
190 {
191 return Err(anyhow!(
192 "Invalid token type: expected {}, got {}",
193 expected,
194 header.typ
195 ));
196 }
197
198 let signature_bytes = URL_SAFE_NO_PAD
199 .decode(signature_b64)
200 .context("Base64 decode of signature failed")?;
201
202 let message = format!("{}.{}", header_b64, claims_b64);
203
204 let mut mac =
205 HmacSha256::new_from_slice(secret).map_err(|e| anyhow!("Invalid secret: {}", e))?;
206 mac.update(message.as_bytes());
207
208 let expected_signature = mac.finalize().into_bytes();
209 let is_valid: bool = signature_bytes.ct_eq(&expected_signature).into();
210
211 if !is_valid {
212 return Err(anyhow!("Signature verification failed"));
213 }
214
215 let claims_bytes = URL_SAFE_NO_PAD
216 .decode(claims_b64)
217 .context("Base64 decode of claims failed")?;
218
219 let claims: Claims =
220 serde_json::from_slice(&claims_bytes).context("JSON decode of claims failed")?;
221
222 let now = Utc::now().timestamp() as usize;
223 if claims.exp < now {
224 return Err(anyhow!("Token expired"));
225 }
226
227 if let Some(scopes) = allowed_scopes {
228 let token_scope = claims.scope.as_deref().unwrap_or("");
229 if !scopes.contains(&token_scope) {
230 return Err(anyhow!("Invalid token scope: {}", token_scope));
231 }
232 }
233
234 Ok(TokenData { claims })
235}
236
237pub fn get_algorithm_from_token(token: &str) -> Result<String, String> {
238 let parts: Vec<&str> = token.split('.').collect();
239 if parts.len() != 3 {
240 return Err("Invalid token format".to_string());
241 }
242
243 let header_bytes = URL_SAFE_NO_PAD
244 .decode(parts[0])
245 .map_err(|e| format!("Base64 decode failed: {}", e))?;
246
247 let header: Header =
248 serde_json::from_slice(&header_bytes).map_err(|e| format!("JSON decode failed: {}", e))?;
249
250 Ok(header.alg)
251}