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        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    /// Decode token information that can be useful prior to signature/tag
218    /// verification
219    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}