1use ct_codecs::{Base64UrlSafeNoPadding, Decoder, Encoder};
2use serde::{de::DeserializeOwned, Serialize};
3
4use crate::claims::*;
5use crate::common::*;
6use crate::error::*;
7use crate::jwt_header::*;
8
9pub const MAX_HEADER_LENGTH: usize = 8192;
10
11pub struct Token;
13
14#[derive(Debug, Clone, Default)]
16pub struct TokenMetadata {
17 pub(crate) jwt_header: JWTHeader,
18}
19
20impl TokenMetadata {
21 pub fn algorithm(&self) -> &str {
26 &self.jwt_header.algorithm
27 }
28
29 pub fn content_type(&self) -> Option<&str> {
31 self.jwt_header.content_type.as_deref()
32 }
33
34 pub fn key_id(&self) -> Option<&str> {
36 self.jwt_header.key_id.as_deref()
37 }
38
39 pub fn signature_type(&self) -> Option<&str> {
41 self.jwt_header.signature_type.as_deref()
42 }
43
44 pub fn critical(&self) -> Option<&[String]> {
46 self.jwt_header.critical.as_deref()
47 }
48
49 pub fn certificate_chain(&self) -> Option<&[String]> {
53 self.jwt_header.certificate_chain.as_deref()
54 }
55
56 pub fn key_set_url(&self) -> Option<&str> {
61 self.jwt_header.key_set_url.as_deref()
62 }
63
64 pub fn public_key(&self) -> Option<&str> {
69 self.jwt_header.public_key.as_deref()
70 }
71
72 pub fn certificate_url(&self) -> Option<&str> {
77 self.jwt_header.certificate_url.as_deref()
78 }
79
80 pub fn certificate_sha1_thumbprint(&self) -> Option<&str> {
85 self.jwt_header.certificate_sha1_thumbprint.as_deref()
86 }
87
88 pub fn certificate_sha256_thumbprint(&self) -> Option<&str> {
93 self.jwt_header.certificate_sha256_thumbprint.as_deref()
94 }
95
96 pub fn salt(&self) -> Option<Vec<u8>> {
98 self.jwt_header
99 .salt
100 .as_ref()
101 .and_then(|salt| Base64UrlSafeNoPadding::decode_to_vec(salt, None).ok())
102 }
103}
104
105impl Token {
106 pub(crate) fn build<AuthenticationOrSignatureFn, CustomClaims: Serialize>(
107 jwt_header: &JWTHeader,
108 claims: JWTClaims<CustomClaims>,
109 authentication_or_signature_fn: AuthenticationOrSignatureFn,
110 ) -> Result<String, Error>
111 where
112 AuthenticationOrSignatureFn: FnOnce(&str) -> Result<Vec<u8>, Error>,
113 {
114 let jwt_header_json = serde_json::to_string(&jwt_header)?;
115 let claims_json = serde_json::to_string(&claims)?;
116 let authenticated = format!(
117 "{}.{}",
118 Base64UrlSafeNoPadding::encode_to_string(jwt_header_json)?,
119 Base64UrlSafeNoPadding::encode_to_string(claims_json)?
120 );
121 let authentication_tag_or_signature = authentication_or_signature_fn(&authenticated)?;
122 let mut token = authenticated;
123 token.push('.');
124 token.push_str(&Base64UrlSafeNoPadding::encode_to_string(
125 authentication_tag_or_signature,
126 )?);
127 Ok(token)
128 }
129
130 pub(crate) fn verify<AuthenticationOrSignatureFn, SaltCheckFn, CustomClaims: DeserializeOwned>(
131 jwt_alg_name: &'static str,
132 token: &str,
133 options: Option<VerificationOptions>,
134 authentication_or_signature_fn: AuthenticationOrSignatureFn,
135 salt_check_fn: SaltCheckFn,
136 ) -> Result<JWTClaims<CustomClaims>, Error>
137 where
138 AuthenticationOrSignatureFn: FnOnce(&str, &[u8]) -> Result<(), Error>,
139 SaltCheckFn: FnOnce(Option<&[u8]>) -> Result<(), Error>,
140 {
141 let options = options.unwrap_or_default();
142
143 if let Some(max_token_length) = options.max_token_length {
144 ensure!(token.len() <= max_token_length, JWTError::TokenTooLong);
145 }
146
147 let mut parts = token.split('.');
148 let jwt_header_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
149 ensure!(
150 jwt_header_b64.len() <= options.max_header_length.unwrap_or(MAX_HEADER_LENGTH),
151 JWTError::HeaderTooLarge
152 );
153 let claims_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
154 let authentication_tag_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
155 ensure!(parts.next().is_none(), JWTError::CompactEncodingError);
156 let jwt_header: JWTHeader = serde_json::from_slice(
157 &Base64UrlSafeNoPadding::decode_to_vec(jwt_header_b64, None)?,
158 )?;
159
160 if let Some(expected_signature_type) = &options.required_signature_type {
161 let expected_signature_type_uc = expected_signature_type.to_uppercase();
162 let signature_type_uc = jwt_header
163 .signature_type
164 .ok_or(JWTError::RequiredSignatureTypeMismatch)?
165 .to_uppercase();
166 ensure!(
167 signature_type_uc == expected_signature_type_uc,
168 JWTError::RequiredSignatureTypeMismatch
169 )
170 } else if let Some(signature_type) = &jwt_header.signature_type {
171 let signature_type_uc = signature_type.to_uppercase();
172 ensure!(
173 signature_type_uc == "JWT" || signature_type_uc.ends_with("+JWT"),
174 JWTError::NotJWT
175 );
176 }
177
178 if let Some(expected_content_type) = &options.required_content_type {
179 let expected_content_type_uc = expected_content_type.to_uppercase();
180 let content_type_uc = jwt_header
181 .content_type
182 .ok_or(JWTError::RequiredContentTypeMismatch)?
183 .to_uppercase();
184 ensure!(
185 content_type_uc == expected_content_type_uc,
186 JWTError::RequiredContentTypeMismatch
187 );
188 }
189
190 ensure!(
191 jwt_header.algorithm == jwt_alg_name,
192 JWTError::AlgorithmMismatch
193 );
194 if let Some(required_key_id) = &options.required_key_id {
195 if let Some(key_id) = &jwt_header.key_id {
196 ensure!(key_id == required_key_id, JWTError::KeyIdentifierMismatch);
197 } else {
198 bail!(JWTError::MissingJWTKeyIdentifier)
199 }
200 }
201 if let Some(salt) = &jwt_header.salt {
202 let salt = Base64UrlSafeNoPadding::decode_to_vec(salt, None)?;
203 salt_check_fn(Some(&salt))?;
204 } else {
205 salt_check_fn(None)?;
206 }
207 let authentication_tag =
208 Base64UrlSafeNoPadding::decode_to_vec(authentication_tag_b64, None)?;
209 let authenticated = &token[..jwt_header_b64.len() + 1 + claims_b64.len()];
210 authentication_or_signature_fn(authenticated, &authentication_tag)?;
211 let claims: JWTClaims<CustomClaims> =
212 serde_json::from_slice(&Base64UrlSafeNoPadding::decode_to_vec(claims_b64, None)?)?;
213 claims.validate(&options)?;
214 Ok(claims)
215 }
216
217 pub fn decode_metadata(token: &str) -> Result<TokenMetadata, Error> {
220 let mut parts = token.split('.');
221 let jwt_header_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
222 ensure!(
223 jwt_header_b64.len() <= MAX_HEADER_LENGTH,
224 JWTError::HeaderTooLarge
225 );
226 let jwt_header: JWTHeader = serde_json::from_slice(
227 &Base64UrlSafeNoPadding::decode_to_vec(jwt_header_b64, None)?,
228 )?;
229 Ok(TokenMetadata { jwt_header })
230 }
231}
232
233#[test]
234fn should_verify_token() {
235 use crate::prelude::*;
236
237 let key = HS256Key::generate();
238
239 let issuer = "issuer";
240 let audience = "recipient";
241 let mut claims = Claims::create(Duration::from_mins(10))
242 .with_issuer(issuer)
243 .with_audience(audience);
244 let nonce = claims.create_nonce();
245 let token = key.authenticate(claims).unwrap();
246
247 let options = VerificationOptions {
248 required_nonce: Some(nonce),
249 allowed_issuers: Some(HashSet::from_strings(&[issuer])),
250 allowed_audiences: Some(HashSet::from_strings(&[audience])),
251 ..Default::default()
252 };
253 key.verify_token::<NoCustomClaims>(&token, Some(options))
254 .unwrap();
255}
256
257#[test]
258fn multiple_audiences() {
259 use std::collections::HashSet;
260
261 use crate::prelude::*;
262
263 let key = HS256Key::generate();
264
265 let mut audiences = HashSet::new();
266 audiences.insert("audience 1");
267 audiences.insert("audience 2");
268 audiences.insert("audience 3");
269 let claims = Claims::create(Duration::from_mins(10)).with_audiences(audiences);
270 let token = key.authenticate(claims).unwrap();
271
272 let options = VerificationOptions {
273 allowed_audiences: Some(HashSet::from_strings(&["audience 1"])),
274 ..Default::default()
275 };
276 key.verify_token::<NoCustomClaims>(&token, Some(options))
277 .unwrap();
278}
279
280#[test]
281fn explicitly_empty_audiences() {
282 use std::collections::HashSet;
283
284 use crate::prelude::*;
285
286 let key = HS256Key::generate();
287
288 let audiences: HashSet<&str> = HashSet::new();
289 let claims = Claims::create(Duration::from_mins(10)).with_audiences(audiences);
290 let token = key.authenticate(claims).unwrap();
291 let decoded = key.verify_token::<NoCustomClaims>(&token, None).unwrap();
292 assert!(decoded.audiences.is_some());
293
294 let claims = Claims::create(Duration::from_mins(10)).with_audience("");
295 let token = key.authenticate(claims).unwrap();
296 let decoded = key.verify_token::<NoCustomClaims>(&token, None).unwrap();
297 assert!(decoded.audiences.is_some());
298
299 let claims = Claims::create(Duration::from_mins(10));
300 let token = key.authenticate(claims).unwrap();
301 let decoded = key.verify_token::<NoCustomClaims>(&token, None).unwrap();
302 assert!(decoded.audiences.is_none());
303}
304
305#[test]
306fn very_old_artificial_time() {
307 use crate::prelude::*;
308 let key = RS256PublicKey::from_pem(
309 r#"-----BEGIN PUBLIC KEY-----
310MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt5N44H1mpb5Wlx/0e7Cd
311oKTY8xt+3yMby8BgNdagVNkeCkZ4pRbmQXRWNC7qn//Zaxx9dnzHbzGCul5W0RLf
312d3oB3PESwsrQh+oiXVEPTYhvUPQkX0vBfCXJtg/zY2mY1DxKOIiXnZ8PaK/7Sx0a
313MmvR//0Yy2a5dIAWCmjPsxn+PcGZOkVUm+D5bH1+ZStcA/68r4ZSPix7Szhgl1Ro
314Hb9Q6JSekyZqM0Qfwhgb7srZVXC/9/m5PEx9wMVNYpYJBrXhD5IQm9RzE9oJS8T+
315Ai+4/5mNTNXI8f1rrYgffWS4wf9cvsEihrvEg9867B2f98L7ux9Llle7jsHCtwgV
3161wIDAQAB
317-----END PUBLIC KEY-----"#,
318 )
319 .unwrap();
320 let jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJuYW1lIjoiQWRhIExvdmVsYWNlIiwiaXNzIjoiaHR0cHM6Ly9jaHJvbm9nZWFycy5jb20vdGVzdCIsImF1ZCI6InRlc3QiLCJhdXRoX3RpbWUiOjEwMCwidXNlcl9pZCI6InVpZDEyMyIsInN1YiI6InNidTEyMyIsImlhdCI6MjAwLCJleHAiOjUwMCwibmJmIjozMDAsImVtYWlsIjoiYWxvdmVsYWNlQGNocm9ub2dlYXJzLmNvbSJ9.eTQnwXrri_uY55fS4IygseBzzbosDM1hP153EZXzNlLH5s29kdlGt2mL_KIjYmQa8hmptt9RwKJHBtw6l4KFHvIcuif86Ix-iI2fCpqNnKyGZfgERV51NXk1THkgWj0GQB6X5cvOoFIdHa9XvgPl_rVmzXSUYDgkhd2t01FOjQeeT6OL2d9KdlQHJqAsvvKVc3wnaYYoSqv2z0IluvK93Tk1dUBU2yWXH34nX3GAVGvIoFoNRiiFfZwFlnz78G0b2fQV7B5g5F8XlNRdD1xmVZXU8X2-xh9LqRpnEakdhecciFHg0u6AyC4c00rlo_HBb69wlXajQ3R4y26Kpxn7HA";
321
322 let mut options = VerificationOptions::default();
323 options.artificial_time = Some(UnixTimeStamp::from_secs(400));
324 let res = key.verify_token::<NoCustomClaims>(jwt, Some(options.clone()));
325 assert!(res.is_err());
326
327 options.time_tolerance = Some(Duration::from_secs(100));
328 key.verify_token::<NoCustomClaims>(jwt, Some(options))
329 .unwrap();
330}
331
332#[test]
333fn content_type() {
334 use crate::prelude::*;
335 let key = HS256Key::generate();
336 let options = VerificationOptions {
337 required_content_type: Some("JWT".into()),
338 ..VerificationOptions::default()
339 };
340 let token = key
341 .authenticate(Claims::create(Duration::from_secs(86400)))
342 .unwrap();
343 let res = key.verify_token::<NoCustomClaims>(&token, Some(options.clone()));
344 assert!(res.is_err());
345
346 let token = key
347 .authenticate_with_options(
348 Claims::create(Duration::from_secs(86400)),
349 &HeaderOptions {
350 content_type: Some("jwt".into()),
351 ..Default::default()
352 },
353 )
354 .unwrap();
355 key.verify_token::<NoCustomClaims>(&token, Some(options.clone()))
356 .unwrap();
357}
358
359#[test]
360fn signature_type() {
361 use crate::prelude::*;
362 let key = ES256KeyPair::generate();
363 let options = VerificationOptions {
364 required_signature_type: Some("dpop+jwt".into()),
365 ..VerificationOptions::default()
366 };
367 let token = key
368 .sign(Claims::create(Duration::from_secs(86400)))
369 .unwrap();
370 let res = key
371 .public_key()
372 .verify_token::<NoCustomClaims>(&token, Some(options.clone()));
373 assert!(res.is_err());
374
375 let token = key
376 .sign_with_options(
377 Claims::create(Duration::from_secs(86400)),
378 &HeaderOptions {
379 signature_type: Some("dpop+jwt".into()),
380 ..Default::default()
381 },
382 )
383 .unwrap();
384 key.public_key()
385 .verify_token::<NoCustomClaims>(&token, Some(options.clone()))
386 .unwrap();
387}
388
389#[test]
390fn reject_before_uses_issued_at() {
391 use crate::{prelude::*, JWTError};
392
393 let key = HS256Key::generate();
394 let base_time = Clock::now_since_epoch();
395
396 let mut stale_claims = Claims::create(Duration::from_mins(10));
397 let stale_issued_at = base_time - Duration::from_secs(30);
398 stale_claims.issued_at = Some(stale_issued_at);
399 stale_claims.invalid_before = Some(stale_issued_at);
400 stale_claims.expires_at = Some(base_time + Duration::from_mins(10));
401 let stale_token = key.authenticate(stale_claims).unwrap();
402
403 let mut options = VerificationOptions::default();
404 options.reject_before = Some(base_time);
405 options.artificial_time = Some(base_time);
406
407 let err = key
408 .verify_token::<NoCustomClaims>(&stale_token, Some(options.clone()))
409 .unwrap_err();
410 assert!(matches!(
411 err.downcast_ref::<JWTError>(),
412 Some(JWTError::OldTokenReused)
413 ));
414
415 let mut fresh_claims = Claims::create(Duration::from_mins(10));
416 let fresh_issued_at = base_time + Duration::from_secs(1);
417 fresh_claims.issued_at = Some(fresh_issued_at);
418 fresh_claims.invalid_before = Some(fresh_issued_at);
419 fresh_claims.expires_at = Some(base_time + Duration::from_mins(10));
420 let fresh_token = key.authenticate(fresh_claims).unwrap();
421
422 key.verify_token::<NoCustomClaims>(&fresh_token, Some(options))
423 .unwrap();
424}
425
426#[test]
427fn token_metadata_salt_handles_invalid_input() {
428 use crate::jwt_header::JWTHeader;
429
430 let metadata = TokenMetadata {
431 jwt_header: JWTHeader {
432 algorithm: "HS256".into(),
433 salt: Some("%%%not_base64%%%".into()),
434 ..Default::default()
435 },
436 };
437 assert!(metadata.salt().is_none());
438
439 let salt_bytes = b"salty";
440 let salt_b64 = Base64UrlSafeNoPadding::encode_to_string(salt_bytes).unwrap();
441 let metadata = TokenMetadata {
442 jwt_header: JWTHeader {
443 algorithm: "HS256".into(),
444 salt: Some(salt_b64),
445 ..Default::default()
446 },
447 };
448 assert_eq!(metadata.salt(), Some(salt_bytes.to_vec()));
449}