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