Skip to main content

jwt_simple/
token.rs

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
11/// Utilities to get information about a JWT token
12pub struct Token;
13
14/// JWT token information useful before signature/tag verification
15#[derive(Debug, Clone, Default)]
16pub struct TokenMetadata {
17    pub(crate) jwt_header: JWTHeader,
18}
19
20impl TokenMetadata {
21    /// The JWT algorithm for this token ("alg")
22    /// This information should not be trusted: it is unprotected and can be
23    /// freely modified by a third party. Clients should ignore it and use
24    /// the correct type of key directly.
25    pub fn algorithm(&self) -> &str {
26        &self.jwt_header.algorithm
27    }
28
29    /// The content type for this token ("cty")
30    pub fn content_type(&self) -> Option<&str> {
31        self.jwt_header.content_type.as_deref()
32    }
33
34    /// The key, or public key identifier for this token ("kid")
35    pub fn key_id(&self) -> Option<&str> {
36        self.jwt_header.key_id.as_deref()
37    }
38
39    /// The signature type for this token ("typ")
40    pub fn signature_type(&self) -> Option<&str> {
41        self.jwt_header.signature_type.as_deref()
42    }
43
44    /// The set of raw critical properties for this token ("crit")
45    pub fn critical(&self) -> Option<&[String]> {
46        self.jwt_header.critical.as_deref()
47    }
48
49    /// The certificate chain for this token ("x5c")
50    /// This information should not be trusted: it is unprotected and can be
51    /// freely modified by a third party.
52    pub fn certificate_chain(&self) -> Option<&[String]> {
53        self.jwt_header.certificate_chain.as_deref()
54    }
55
56    /// The key set URL for this token ("jku")
57    /// This information should not be trusted: it is unprotected and can be
58    /// freely modified by a third party. At the bare minimum, you should
59    /// check that the URL belongs to the domain you expect.
60    pub fn key_set_url(&self) -> Option<&str> {
61        self.jwt_header.key_set_url.as_deref()
62    }
63
64    /// The public key for this token ("jwk")
65    /// This information should not be trusted: it is unprotected and can be
66    /// freely modified by a third party. At the bare minimum, you should
67    /// check that it's in a set of public keys you already trust.
68    pub fn public_key(&self) -> Option<&str> {
69        self.jwt_header.public_key.as_deref()
70    }
71
72    /// The certificate URL for this token ("x5u")
73    /// This information should not be trusted: it is unprotected and can be
74    /// freely modified by a third party. At the bare minimum, you should
75    /// check that the URL belongs to the domain you expect.
76    pub fn certificate_url(&self) -> Option<&str> {
77        self.jwt_header.certificate_url.as_deref()
78    }
79
80    /// URLsafe-base64-encoded SHA1 hash of the X.509 certificate for this token
81    /// ("x5t") In practice, it can also be any string representing the
82    /// public key. This information should not be trusted: it is
83    /// unprotected and can be freely modified by a third party.
84    pub fn certificate_sha1_thumbprint(&self) -> Option<&str> {
85        self.jwt_header.certificate_sha1_thumbprint.as_deref()
86    }
87
88    /// URLsafe-base64-encoded SHA256 hash of the X.509 certificate for this
89    /// token ("x5t#S256") In practice, it can also be any string
90    /// representing the public key. This information should not be trusted:
91    /// it is unprotected and can be freely modified by a third party.
92    pub fn certificate_sha256_thumbprint(&self) -> Option<&str> {
93        self.jwt_header.certificate_sha256_thumbprint.as_deref()
94    }
95
96    /// Salt
97    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    /// Accepts alternate names for one algorithm, such as `EdDSA` and `Ed25519`.
151    /// All names must use the same verification method.
152    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        // Reject unsupported critical header extensions before accepting the token.
187        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    /// Decode token information that can be useful prior to signature/tag
251    /// verification
252    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}