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#256") 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        match self.jwt_header.salt {
99            Some(ref salt) => {
100                let salt = Base64UrlSafeNoPadding::decode_to_vec(salt, None).unwrap();
101                Some(salt)
102            }
103            None => None,
104        }
105    }
106}
107
108impl Token {
109    pub(crate) fn build<AuthenticationOrSignatureFn, CustomClaims: Serialize + DeserializeOwned>(
110        jwt_header: &JWTHeader,
111        claims: JWTClaims<CustomClaims>,
112        authentication_or_signature_fn: AuthenticationOrSignatureFn,
113    ) -> Result<String, Error>
114    where
115        AuthenticationOrSignatureFn: FnOnce(&str) -> Result<Vec<u8>, Error>,
116    {
117        let jwt_header_json = serde_json::to_string(&jwt_header)?;
118        let claims_json = serde_json::to_string(&claims)?;
119        let authenticated = format!(
120            "{}.{}",
121            Base64UrlSafeNoPadding::encode_to_string(jwt_header_json)?,
122            Base64UrlSafeNoPadding::encode_to_string(claims_json)?
123        );
124        let authentication_tag_or_signature = authentication_or_signature_fn(&authenticated)?;
125        let mut token = authenticated;
126        token.push('.');
127        token.push_str(&Base64UrlSafeNoPadding::encode_to_string(
128            authentication_tag_or_signature,
129        )?);
130        Ok(token)
131    }
132
133    pub(crate) fn verify<
134        AuthenticationOrSignatureFn,
135        SaltCheckFn,
136        CustomClaims: Serialize + DeserializeOwned,
137    >(
138        jwt_alg_name: &'static str,
139        token: &str,
140        options: Option<VerificationOptions>,
141        authentication_or_signature_fn: AuthenticationOrSignatureFn,
142        salt_check_fn: SaltCheckFn,
143    ) -> Result<JWTClaims<CustomClaims>, Error>
144    where
145        AuthenticationOrSignatureFn: FnOnce(&str, &[u8]) -> Result<(), Error>,
146        SaltCheckFn: FnOnce(Option<&[u8]>) -> Result<(), Error>,
147    {
148        let options = options.unwrap_or_default();
149
150        if let Some(max_token_length) = options.max_token_length {
151            ensure!(token.len() <= max_token_length, JWTError::TokenTooLong);
152        }
153
154        let mut parts = token.split('.');
155        let jwt_header_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
156        ensure!(
157            jwt_header_b64.len() <= options.max_header_length.unwrap_or(MAX_HEADER_LENGTH),
158            JWTError::HeaderTooLarge
159        );
160        let claims_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
161        let authentication_tag_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
162        ensure!(parts.next().is_none(), JWTError::CompactEncodingError);
163        let jwt_header: JWTHeader = serde_json::from_slice(
164            &Base64UrlSafeNoPadding::decode_to_vec(jwt_header_b64, None)?,
165        )?;
166
167        if let Some(expected_signature_type) = &options.required_signature_type {
168            let expected_signature_type_uc = expected_signature_type.to_uppercase();
169            let signature_type_uc = jwt_header
170                .signature_type
171                .ok_or(JWTError::RequiredSignatureTypeMismatch)?
172                .to_uppercase();
173            ensure!(
174                signature_type_uc == expected_signature_type_uc,
175                JWTError::RequiredSignatureTypeMismatch
176            )
177        } else if let Some(signature_type) = &jwt_header.signature_type {
178            let signature_type_uc = signature_type.to_uppercase();
179            ensure!(
180                signature_type_uc == "JWT" || signature_type_uc.ends_with("+JWT"),
181                JWTError::NotJWT
182            );
183        }
184
185        if let Some(expected_content_type) = &options.required_content_type {
186            let expected_content_type_uc = expected_content_type.to_uppercase();
187            let content_type_uc = jwt_header
188                .content_type
189                .ok_or(JWTError::RequiredContentTypeMismatch)?
190                .to_uppercase();
191            ensure!(
192                content_type_uc == expected_content_type_uc,
193                JWTError::RequiredContentTypeMismatch
194            );
195        }
196
197        ensure!(
198            jwt_header.algorithm == jwt_alg_name,
199            JWTError::AlgorithmMismatch
200        );
201        if let Some(required_key_id) = &options.required_key_id {
202            if let Some(key_id) = &jwt_header.key_id {
203                ensure!(key_id == required_key_id, JWTError::KeyIdentifierMismatch);
204            } else {
205                bail!(JWTError::MissingJWTKeyIdentifier)
206            }
207        }
208        if let Some(salt) = &jwt_header.salt {
209            let salt = Base64UrlSafeNoPadding::decode_to_vec(salt, None)?;
210            salt_check_fn(Some(&salt))?;
211        } else {
212            salt_check_fn(None)?;
213        }
214        let authentication_tag =
215            Base64UrlSafeNoPadding::decode_to_vec(authentication_tag_b64, None)?;
216        let authenticated = &token[..jwt_header_b64.len() + 1 + claims_b64.len()];
217        authentication_or_signature_fn(authenticated, &authentication_tag)?;
218        let claims: JWTClaims<CustomClaims> =
219            serde_json::from_slice(&Base64UrlSafeNoPadding::decode_to_vec(claims_b64, None)?)?;
220        claims.validate(&options)?;
221        Ok(claims)
222    }
223
224    /// Decode token information that can be useful prior to signature/tag
225    /// verification
226    pub fn decode_metadata(token: &str) -> Result<TokenMetadata, Error> {
227        let mut parts = token.split('.');
228        let jwt_header_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
229        ensure!(
230            jwt_header_b64.len() <= MAX_HEADER_LENGTH,
231            JWTError::HeaderTooLarge
232        );
233        let jwt_header: JWTHeader = serde_json::from_slice(
234            &Base64UrlSafeNoPadding::decode_to_vec(jwt_header_b64, None)?,
235        )?;
236        Ok(TokenMetadata { jwt_header })
237    }
238}
239
240#[test]
241fn should_verify_token() {
242    use crate::prelude::*;
243
244    let key = HS256Key::generate();
245
246    let issuer = "issuer";
247    let audience = "recipient";
248    let mut claims = Claims::create(Duration::from_mins(10))
249        .with_issuer(issuer)
250        .with_audience(audience);
251    let nonce = claims.create_nonce();
252    let token = key.authenticate(claims).unwrap();
253
254    let options = VerificationOptions {
255        required_nonce: Some(nonce),
256        allowed_issuers: Some(HashSet::from_strings(&[issuer])),
257        allowed_audiences: Some(HashSet::from_strings(&[audience])),
258        ..Default::default()
259    };
260    key.verify_token::<NoCustomClaims>(&token, Some(options))
261        .unwrap();
262}
263
264#[test]
265fn multiple_audiences() {
266    use std::collections::HashSet;
267
268    use crate::prelude::*;
269
270    let key = HS256Key::generate();
271
272    let mut audiences = HashSet::new();
273    audiences.insert("audience 1");
274    audiences.insert("audience 2");
275    audiences.insert("audience 3");
276    let claims = Claims::create(Duration::from_mins(10)).with_audiences(audiences);
277    let token = key.authenticate(claims).unwrap();
278
279    let options = VerificationOptions {
280        allowed_audiences: Some(HashSet::from_strings(&["audience 1"])),
281        ..Default::default()
282    };
283    key.verify_token::<NoCustomClaims>(&token, Some(options))
284        .unwrap();
285}
286
287#[test]
288fn explicitly_empty_audiences() {
289    use std::collections::HashSet;
290
291    use crate::prelude::*;
292
293    let key = HS256Key::generate();
294
295    let audiences: HashSet<&str> = HashSet::new();
296    let claims = Claims::create(Duration::from_mins(10)).with_audiences(audiences);
297    let token = key.authenticate(claims).unwrap();
298    let decoded = key.verify_token::<NoCustomClaims>(&token, None).unwrap();
299    assert!(decoded.audiences.is_some());
300
301    let claims = Claims::create(Duration::from_mins(10)).with_audience("");
302    let token = key.authenticate(claims).unwrap();
303    let decoded = key.verify_token::<NoCustomClaims>(&token, None).unwrap();
304    assert!(decoded.audiences.is_some());
305
306    let claims = Claims::create(Duration::from_mins(10));
307    let token = key.authenticate(claims).unwrap();
308    let decoded = key.verify_token::<NoCustomClaims>(&token, None).unwrap();
309    assert!(decoded.audiences.is_none());
310}
311
312#[test]
313fn very_old_artificial_time() {
314    use crate::prelude::*;
315    let key = RS256PublicKey::from_pem(
316        r#"-----BEGIN PUBLIC KEY-----
317MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt5N44H1mpb5Wlx/0e7Cd
318oKTY8xt+3yMby8BgNdagVNkeCkZ4pRbmQXRWNC7qn//Zaxx9dnzHbzGCul5W0RLf
319d3oB3PESwsrQh+oiXVEPTYhvUPQkX0vBfCXJtg/zY2mY1DxKOIiXnZ8PaK/7Sx0a
320MmvR//0Yy2a5dIAWCmjPsxn+PcGZOkVUm+D5bH1+ZStcA/68r4ZSPix7Szhgl1Ro
321Hb9Q6JSekyZqM0Qfwhgb7srZVXC/9/m5PEx9wMVNYpYJBrXhD5IQm9RzE9oJS8T+
322Ai+4/5mNTNXI8f1rrYgffWS4wf9cvsEihrvEg9867B2f98L7ux9Llle7jsHCtwgV
3231wIDAQAB
324-----END PUBLIC KEY-----"#,
325    )
326    .unwrap();
327    let jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJuYW1lIjoiQWRhIExvdmVsYWNlIiwiaXNzIjoiaHR0cHM6Ly9jaHJvbm9nZWFycy5jb20vdGVzdCIsImF1ZCI6InRlc3QiLCJhdXRoX3RpbWUiOjEwMCwidXNlcl9pZCI6InVpZDEyMyIsInN1YiI6InNidTEyMyIsImlhdCI6MjAwLCJleHAiOjUwMCwibmJmIjozMDAsImVtYWlsIjoiYWxvdmVsYWNlQGNocm9ub2dlYXJzLmNvbSJ9.eTQnwXrri_uY55fS4IygseBzzbosDM1hP153EZXzNlLH5s29kdlGt2mL_KIjYmQa8hmptt9RwKJHBtw6l4KFHvIcuif86Ix-iI2fCpqNnKyGZfgERV51NXk1THkgWj0GQB6X5cvOoFIdHa9XvgPl_rVmzXSUYDgkhd2t01FOjQeeT6OL2d9KdlQHJqAsvvKVc3wnaYYoSqv2z0IluvK93Tk1dUBU2yWXH34nX3GAVGvIoFoNRiiFfZwFlnz78G0b2fQV7B5g5F8XlNRdD1xmVZXU8X2-xh9LqRpnEakdhecciFHg0u6AyC4c00rlo_HBb69wlXajQ3R4y26Kpxn7HA";
328
329    let mut options = VerificationOptions::default();
330    options.artificial_time = Some(UnixTimeStamp::from_secs(400));
331    let res = key.verify_token::<NoCustomClaims>(jwt, Some(options.clone()));
332    assert!(res.is_err());
333
334    options.time_tolerance = Some(Duration::from_secs(100));
335    key.verify_token::<NoCustomClaims>(jwt, Some(options))
336        .unwrap();
337}
338
339#[test]
340fn content_type() {
341    use crate::prelude::*;
342    let key = HS256Key::generate();
343    let options = VerificationOptions {
344        required_content_type: Some("JWT".into()),
345        ..VerificationOptions::default()
346    };
347    let token = key
348        .authenticate(Claims::create(Duration::from_secs(86400)))
349        .unwrap();
350    let res = key.verify_token::<NoCustomClaims>(&token, Some(options.clone()));
351    assert!(res.is_err());
352
353    let token = key
354        .authenticate_with_options(
355            Claims::create(Duration::from_secs(86400)),
356            &HeaderOptions {
357                content_type: Some("jwt".into()),
358                ..Default::default()
359            },
360        )
361        .unwrap();
362    key.verify_token::<NoCustomClaims>(&token, Some(options.clone()))
363        .unwrap();
364}
365
366#[test]
367fn signature_type() {
368    use crate::prelude::*;
369    let key = ES256KeyPair::generate();
370    let options = VerificationOptions {
371        required_signature_type: Some("dpop+jwt".into()),
372        ..VerificationOptions::default()
373    };
374    let token = key
375        .sign(Claims::create(Duration::from_secs(86400)))
376        .unwrap();
377    let res = key
378        .public_key()
379        .verify_token::<NoCustomClaims>(&token, Some(options.clone()));
380    assert!(res.is_err());
381
382    let token = key
383        .sign_with_options(
384            Claims::create(Duration::from_secs(86400)),
385            &HeaderOptions {
386                signature_type: Some("dpop+jwt".into()),
387                ..Default::default()
388            },
389        )
390        .unwrap();
391    key.public_key()
392        .verify_token::<NoCustomClaims>(&token, Some(options.clone()))
393        .unwrap();
394}