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 Self::verify_with_algorithms(
142 &[jwt_alg_name],
143 token,
144 options,
145 authentication_or_signature_fn,
146 salt_check_fn,
147 )
148 }
149
150 pub(crate) fn verify_with_algorithms<
153 AuthenticationOrSignatureFn,
154 SaltCheckFn,
155 CustomClaims: DeserializeOwned,
156 >(
157 jwt_alg_names: &[&'static str],
158 token: &str,
159 options: Option<VerificationOptions>,
160 authentication_or_signature_fn: AuthenticationOrSignatureFn,
161 salt_check_fn: SaltCheckFn,
162 ) -> Result<JWTClaims<CustomClaims>, Error>
163 where
164 AuthenticationOrSignatureFn: FnOnce(&str, &[u8]) -> Result<(), Error>,
165 SaltCheckFn: FnOnce(Option<&[u8]>) -> Result<(), Error>,
166 {
167 let options = options.unwrap_or_default();
168
169 if let Some(max_token_length) = options.max_token_length {
170 ensure!(token.len() <= max_token_length, JWTError::TokenTooLong);
171 }
172
173 let mut parts = token.split('.');
174 let jwt_header_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
175 ensure!(
176 jwt_header_b64.len() <= options.max_header_length.unwrap_or(MAX_HEADER_LENGTH),
177 JWTError::HeaderTooLarge
178 );
179 let claims_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
180 let authentication_tag_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
181 ensure!(parts.next().is_none(), JWTError::CompactEncodingError);
182 let jwt_header: JWTHeader = serde_json::from_slice(
183 &Base64UrlSafeNoPadding::decode_to_vec(jwt_header_b64, None)?,
184 )?;
185
186 if let Some(ref crit) = jwt_header.critical {
188 if !crit.is_empty() {
189 bail!(JWTError::UnknownCriticalExtension);
190 }
191 }
192
193 if let Some(expected_signature_type) = &options.required_signature_type {
194 let expected_signature_type_uc = expected_signature_type.to_uppercase();
195 let signature_type_uc = jwt_header
196 .signature_type
197 .ok_or(JWTError::RequiredSignatureTypeMismatch)?
198 .to_uppercase();
199 ensure!(
200 signature_type_uc == expected_signature_type_uc,
201 JWTError::RequiredSignatureTypeMismatch
202 )
203 } else if let Some(signature_type) = &jwt_header.signature_type {
204 let signature_type_uc = signature_type.to_uppercase();
205 ensure!(
206 signature_type_uc == "JWT" || signature_type_uc.ends_with("+JWT"),
207 JWTError::NotJWT
208 );
209 }
210
211 if let Some(expected_content_type) = &options.required_content_type {
212 let expected_content_type_uc = expected_content_type.to_uppercase();
213 let content_type_uc = jwt_header
214 .content_type
215 .ok_or(JWTError::RequiredContentTypeMismatch)?
216 .to_uppercase();
217 ensure!(
218 content_type_uc == expected_content_type_uc,
219 JWTError::RequiredContentTypeMismatch
220 );
221 }
222
223 ensure!(
224 jwt_alg_names.contains(&jwt_header.algorithm.as_str()),
225 JWTError::AlgorithmMismatch
226 );
227 if let Some(required_key_id) = &options.required_key_id {
228 if let Some(key_id) = &jwt_header.key_id {
229 ensure!(key_id == required_key_id, JWTError::KeyIdentifierMismatch);
230 } else {
231 bail!(JWTError::MissingJWTKeyIdentifier)
232 }
233 }
234 if let Some(salt) = &jwt_header.salt {
235 let salt = Base64UrlSafeNoPadding::decode_to_vec(salt, None)?;
236 salt_check_fn(Some(&salt))?;
237 } else {
238 salt_check_fn(None)?;
239 }
240 let authentication_tag =
241 Base64UrlSafeNoPadding::decode_to_vec(authentication_tag_b64, None)?;
242 let authenticated = &token[..jwt_header_b64.len() + 1 + claims_b64.len()];
243 authentication_or_signature_fn(authenticated, &authentication_tag)?;
244 let claims: JWTClaims<CustomClaims> =
245 serde_json::from_slice(&Base64UrlSafeNoPadding::decode_to_vec(claims_b64, None)?)?;
246 claims.validate(&options)?;
247 Ok(claims)
248 }
249
250 pub fn decode_metadata(token: &str) -> Result<TokenMetadata, Error> {
253 let mut parts = token.split('.');
254 let jwt_header_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
255 ensure!(
256 jwt_header_b64.len() <= MAX_HEADER_LENGTH,
257 JWTError::HeaderTooLarge
258 );
259 let jwt_header: JWTHeader = serde_json::from_slice(
260 &Base64UrlSafeNoPadding::decode_to_vec(jwt_header_b64, None)?,
261 )?;
262 Ok(TokenMetadata { jwt_header })
263 }
264}
265
266#[test]
267fn should_verify_token() {
268 use crate::prelude::*;
269
270 let key = HS256Key::generate();
271
272 let issuer = "issuer";
273 let audience = "recipient";
274 let mut claims = Claims::create(Duration::from_mins(10))
275 .with_issuer(issuer)
276 .with_audience(audience);
277 let nonce = claims.create_nonce();
278 let token = key.authenticate(claims).unwrap();
279
280 let options = VerificationOptions {
281 required_nonce: Some(nonce),
282 allowed_issuers: Some(HashSet::from_strings(&[issuer])),
283 allowed_audiences: Some(HashSet::from_strings(&[audience])),
284 ..Default::default()
285 };
286 key.verify_token::<NoCustomClaims>(&token, Some(options))
287 .unwrap();
288}
289
290#[test]
291fn multiple_audiences() {
292 use std::collections::HashSet;
293
294 use crate::prelude::*;
295
296 let key = HS256Key::generate();
297
298 let mut audiences = HashSet::new();
299 audiences.insert("audience 1");
300 audiences.insert("audience 2");
301 audiences.insert("audience 3");
302 let claims = Claims::create(Duration::from_mins(10)).with_audiences(audiences);
303 let token = key.authenticate(claims).unwrap();
304
305 let options = VerificationOptions {
306 allowed_audiences: Some(HashSet::from_strings(&["audience 1"])),
307 ..Default::default()
308 };
309 key.verify_token::<NoCustomClaims>(&token, Some(options))
310 .unwrap();
311}
312
313#[test]
314fn explicitly_empty_audiences() {
315 use std::collections::HashSet;
316
317 use crate::prelude::*;
318
319 let key = HS256Key::generate();
320
321 let audiences: HashSet<&str> = HashSet::new();
322 let claims = Claims::create(Duration::from_mins(10)).with_audiences(audiences);
323 let token = key.authenticate(claims).unwrap();
324 let decoded = key.verify_token::<NoCustomClaims>(&token, None).unwrap();
325 assert!(decoded.audiences.is_some());
326
327 let claims = Claims::create(Duration::from_mins(10)).with_audience("");
328 let token = key.authenticate(claims).unwrap();
329 let decoded = key.verify_token::<NoCustomClaims>(&token, None).unwrap();
330 assert!(decoded.audiences.is_some());
331
332 let claims = Claims::create(Duration::from_mins(10));
333 let token = key.authenticate(claims).unwrap();
334 let decoded = key.verify_token::<NoCustomClaims>(&token, None).unwrap();
335 assert!(decoded.audiences.is_none());
336}
337
338#[test]
339fn very_old_artificial_time() {
340 use crate::prelude::*;
341 let key = RS256PublicKey::from_pem(
342 r#"-----BEGIN PUBLIC KEY-----
343MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt5N44H1mpb5Wlx/0e7Cd
344oKTY8xt+3yMby8BgNdagVNkeCkZ4pRbmQXRWNC7qn//Zaxx9dnzHbzGCul5W0RLf
345d3oB3PESwsrQh+oiXVEPTYhvUPQkX0vBfCXJtg/zY2mY1DxKOIiXnZ8PaK/7Sx0a
346MmvR//0Yy2a5dIAWCmjPsxn+PcGZOkVUm+D5bH1+ZStcA/68r4ZSPix7Szhgl1Ro
347Hb9Q6JSekyZqM0Qfwhgb7srZVXC/9/m5PEx9wMVNYpYJBrXhD5IQm9RzE9oJS8T+
348Ai+4/5mNTNXI8f1rrYgffWS4wf9cvsEihrvEg9867B2f98L7ux9Llle7jsHCtwgV
3491wIDAQAB
350-----END PUBLIC KEY-----"#,
351 )
352 .unwrap();
353 let jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJuYW1lIjoiQWRhIExvdmVsYWNlIiwiaXNzIjoiaHR0cHM6Ly9jaHJvbm9nZWFycy5jb20vdGVzdCIsImF1ZCI6InRlc3QiLCJhdXRoX3RpbWUiOjEwMCwidXNlcl9pZCI6InVpZDEyMyIsInN1YiI6InNidTEyMyIsImlhdCI6MjAwLCJleHAiOjUwMCwibmJmIjozMDAsImVtYWlsIjoiYWxvdmVsYWNlQGNocm9ub2dlYXJzLmNvbSJ9.eTQnwXrri_uY55fS4IygseBzzbosDM1hP153EZXzNlLH5s29kdlGt2mL_KIjYmQa8hmptt9RwKJHBtw6l4KFHvIcuif86Ix-iI2fCpqNnKyGZfgERV51NXk1THkgWj0GQB6X5cvOoFIdHa9XvgPl_rVmzXSUYDgkhd2t01FOjQeeT6OL2d9KdlQHJqAsvvKVc3wnaYYoSqv2z0IluvK93Tk1dUBU2yWXH34nX3GAVGvIoFoNRiiFfZwFlnz78G0b2fQV7B5g5F8XlNRdD1xmVZXU8X2-xh9LqRpnEakdhecciFHg0u6AyC4c00rlo_HBb69wlXajQ3R4y26Kpxn7HA";
354
355 let mut options = VerificationOptions::default();
356 options.artificial_time = Some(UnixTimeStamp::from_secs(400));
357 let res = key.verify_token::<NoCustomClaims>(jwt, Some(options.clone()));
358 assert!(res.is_err());
359
360 options.time_tolerance = Some(Duration::from_secs(100));
361 key.verify_token::<NoCustomClaims>(jwt, Some(options))
362 .unwrap();
363}
364
365#[test]
366fn content_type() {
367 use crate::prelude::*;
368 let key = HS256Key::generate();
369 let options = VerificationOptions {
370 required_content_type: Some("JWT".into()),
371 ..VerificationOptions::default()
372 };
373 let token = key
374 .authenticate(Claims::create(Duration::from_secs(86400)))
375 .unwrap();
376 let res = key.verify_token::<NoCustomClaims>(&token, Some(options.clone()));
377 assert!(res.is_err());
378
379 let token = key
380 .authenticate_with_options(
381 Claims::create(Duration::from_secs(86400)),
382 &HeaderOptions {
383 content_type: Some("jwt".into()),
384 ..Default::default()
385 },
386 )
387 .unwrap();
388 key.verify_token::<NoCustomClaims>(&token, Some(options.clone()))
389 .unwrap();
390}
391
392#[test]
393fn signature_type() {
394 use crate::prelude::*;
395 let key = ES256KeyPair::generate();
396 let options = VerificationOptions {
397 required_signature_type: Some("dpop+jwt".into()),
398 ..VerificationOptions::default()
399 };
400 let token = key
401 .sign(Claims::create(Duration::from_secs(86400)))
402 .unwrap();
403 let res = key
404 .public_key()
405 .verify_token::<NoCustomClaims>(&token, Some(options.clone()));
406 assert!(res.is_err());
407
408 let token = key
409 .sign_with_options(
410 Claims::create(Duration::from_secs(86400)),
411 &HeaderOptions {
412 signature_type: Some("dpop+jwt".into()),
413 ..Default::default()
414 },
415 )
416 .unwrap();
417 key.public_key()
418 .verify_token::<NoCustomClaims>(&token, Some(options.clone()))
419 .unwrap();
420}
421
422#[test]
423fn reject_before_uses_issued_at() {
424 use crate::{prelude::*, JWTError};
425
426 let key = HS256Key::generate();
427 let base_time = Clock::now_since_epoch();
428
429 let mut stale_claims = Claims::create(Duration::from_mins(10));
430 let stale_issued_at = base_time - Duration::from_secs(30);
431 stale_claims.issued_at = Some(stale_issued_at);
432 stale_claims.invalid_before = Some(stale_issued_at);
433 stale_claims.expires_at = Some(base_time + Duration::from_mins(10));
434 let stale_token = key.authenticate(stale_claims).unwrap();
435
436 let mut options = VerificationOptions::default();
437 options.reject_before = Some(base_time);
438 options.artificial_time = Some(base_time);
439
440 let err = key
441 .verify_token::<NoCustomClaims>(&stale_token, Some(options.clone()))
442 .unwrap_err();
443 assert!(matches!(
444 err.downcast_ref::<JWTError>(),
445 Some(JWTError::OldTokenReused)
446 ));
447
448 let mut fresh_claims = Claims::create(Duration::from_mins(10));
449 let fresh_issued_at = base_time + Duration::from_secs(1);
450 fresh_claims.issued_at = Some(fresh_issued_at);
451 fresh_claims.invalid_before = Some(fresh_issued_at);
452 fresh_claims.expires_at = Some(base_time + Duration::from_mins(10));
453 let fresh_token = key.authenticate(fresh_claims).unwrap();
454
455 key.verify_token::<NoCustomClaims>(&fresh_token, Some(options))
456 .unwrap();
457}
458
459#[test]
460fn token_metadata_salt_handles_invalid_input() {
461 use crate::jwt_header::JWTHeader;
462
463 let metadata = TokenMetadata {
464 jwt_header: JWTHeader {
465 algorithm: "HS256".into(),
466 salt: Some("%%%not_base64%%%".into()),
467 ..Default::default()
468 },
469 };
470 assert!(metadata.salt().is_none());
471
472 let salt_bytes = b"salty";
473 let salt_b64 = Base64UrlSafeNoPadding::encode_to_string(salt_bytes).unwrap();
474 let metadata = TokenMetadata {
475 jwt_header: JWTHeader {
476 algorithm: "HS256".into(),
477 salt: Some(salt_b64),
478 ..Default::default()
479 },
480 };
481 assert_eq!(metadata.salt(), Some(salt_bytes.to_vec()));
482}