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
97impl Token {
98    pub(crate) fn build<AuthenticationOrSignatureFn, CustomClaims: Serialize + DeserializeOwned>(
99        jwt_header: &JWTHeader,
100        claims: JWTClaims<CustomClaims>,
101        authentication_or_signature_fn: AuthenticationOrSignatureFn,
102    ) -> Result<String, Error>
103    where
104        AuthenticationOrSignatureFn: FnOnce(&str) -> Result<Vec<u8>, Error>,
105    {
106        let jwt_header_json = serde_json::to_string(&jwt_header)?;
107        let claims_json = serde_json::to_string(&claims)?;
108        let authenticated = format!(
109            "{}.{}",
110            Base64UrlSafeNoPadding::encode_to_string(jwt_header_json)?,
111            Base64UrlSafeNoPadding::encode_to_string(claims_json)?
112        );
113        let authentication_tag_or_signature = authentication_or_signature_fn(&authenticated)?;
114        let mut token = authenticated;
115        token.push('.');
116        token.push_str(&Base64UrlSafeNoPadding::encode_to_string(
117            authentication_tag_or_signature,
118        )?);
119        Ok(token)
120    }
121
122    pub(crate) fn verify<AuthenticationOrSignatureFn, CustomClaims: Serialize + DeserializeOwned>(
123        jwt_alg_name: &'static str,
124        token: &str,
125        options: Option<VerificationOptions>,
126        authentication_or_signature_fn: AuthenticationOrSignatureFn,
127    ) -> Result<JWTClaims<CustomClaims>, Error>
128    where
129        AuthenticationOrSignatureFn: FnOnce(&str, &[u8]) -> Result<(), Error>,
130    {
131        let options = options.unwrap_or_default();
132
133        if let Some(max_token_length) = options.max_token_length {
134            ensure!(token.len() <= max_token_length, JWTError::TokenTooLong);
135        }
136
137        let mut parts = token.split('.');
138        let jwt_header_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
139        ensure!(
140            jwt_header_b64.len() <= options.max_header_length.unwrap_or(MAX_HEADER_LENGTH),
141            JWTError::HeaderTooLarge
142        );
143        let claims_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
144        let authentication_tag_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
145        ensure!(parts.next().is_none(), JWTError::CompactEncodingError);
146        let jwt_header: JWTHeader = serde_json::from_slice(
147            &Base64UrlSafeNoPadding::decode_to_vec(jwt_header_b64, None)?,
148        )?;
149        if let Some(signature_type) = &jwt_header.signature_type {
150            let signature_type_uc = signature_type.to_uppercase();
151            ensure!(
152                signature_type_uc == "JWT" || signature_type_uc.ends_with("+JWT"),
153                JWTError::NotJWT
154            );
155        }
156        ensure!(
157            jwt_header.algorithm == jwt_alg_name,
158            JWTError::AlgorithmMismatch
159        );
160        if let Some(required_key_id) = &options.required_key_id {
161            if let Some(key_id) = &jwt_header.key_id {
162                ensure!(key_id == required_key_id, JWTError::KeyIdentifierMismatch);
163            } else {
164                bail!(JWTError::MissingJWTKeyIdentifier)
165            }
166        }
167        let authentication_tag =
168            Base64UrlSafeNoPadding::decode_to_vec(authentication_tag_b64, None)?;
169        let authenticated = &token[..jwt_header_b64.len() + 1 + claims_b64.len()];
170        authentication_or_signature_fn(authenticated, &authentication_tag)?;
171        let claims: JWTClaims<CustomClaims> =
172            serde_json::from_slice(&Base64UrlSafeNoPadding::decode_to_vec(claims_b64, None)?)?;
173        claims.validate(&options)?;
174        Ok(claims)
175    }
176
177    /// Decode token information that can be usedful prior to signature/tag
178    /// verification
179    pub fn decode_metadata(token: &str) -> Result<TokenMetadata, Error> {
180        let mut parts = token.split('.');
181        let jwt_header_b64 = parts.next().ok_or(JWTError::CompactEncodingError)?;
182        ensure!(
183            jwt_header_b64.len() <= MAX_HEADER_LENGTH,
184            JWTError::HeaderTooLarge
185        );
186        let jwt_header: JWTHeader = serde_json::from_slice(
187            &Base64UrlSafeNoPadding::decode_to_vec(jwt_header_b64, None)?,
188        )?;
189        Ok(TokenMetadata { jwt_header })
190    }
191}
192
193#[test]
194fn should_verify_token() {
195    use crate::prelude::*;
196
197    let key = HS256Key::generate();
198
199    let issuer = "issuer";
200    let audience = "recipient";
201    let mut claims = Claims::create(Duration::from_mins(10))
202        .with_issuer(issuer)
203        .with_audience(audience);
204    let nonce = claims.create_nonce();
205    let token = key.authenticate(claims).unwrap();
206
207    let options = VerificationOptions {
208        required_nonce: Some(nonce),
209        allowed_issuers: Some(HashSet::from_strings(&[issuer])),
210        allowed_audiences: Some(HashSet::from_strings(&[audience])),
211        ..Default::default()
212    };
213    key.verify_token::<NoCustomClaims>(&token, Some(options))
214        .unwrap();
215}
216
217#[test]
218fn multiple_audiences() {
219    use std::collections::HashSet;
220
221    use crate::prelude::*;
222
223    let key = HS256Key::generate();
224
225    let mut audiences = HashSet::new();
226    audiences.insert("audience 1");
227    audiences.insert("audience 2");
228    audiences.insert("audience 3");
229    let claims = Claims::create(Duration::from_mins(10)).with_audiences(audiences);
230    let token = key.authenticate(claims).unwrap();
231
232    let options = VerificationOptions {
233        allowed_audiences: Some(HashSet::from_strings(&["audience 1"])),
234        ..Default::default()
235    };
236    key.verify_token::<NoCustomClaims>(&token, Some(options))
237        .unwrap();
238}
239
240#[test]
241fn explicitly_empty_audiences() {
242    use std::collections::HashSet;
243
244    use crate::prelude::*;
245
246    let key = HS256Key::generate();
247
248    let audiences: HashSet<&str> = HashSet::new();
249    let claims = Claims::create(Duration::from_mins(10)).with_audiences(audiences);
250    let token = key.authenticate(claims).unwrap();
251    let decoded = key.verify_token::<NoCustomClaims>(&token, None).unwrap();
252    assert!(decoded.audiences.is_some());
253
254    let claims = Claims::create(Duration::from_mins(10)).with_audience("");
255    let token = key.authenticate(claims).unwrap();
256    let decoded = key.verify_token::<NoCustomClaims>(&token, None).unwrap();
257    assert!(decoded.audiences.is_some());
258
259    let claims = Claims::create(Duration::from_mins(10));
260    let token = key.authenticate(claims).unwrap();
261    let decoded = key.verify_token::<NoCustomClaims>(&token, None).unwrap();
262    assert!(decoded.audiences.is_none());
263}