Skip to main content

jwt_simple/
common.rs

1use std::collections::HashSet;
2
3use coarsetime::{Duration, UnixTimeStamp};
4use ct_codecs::{Base64UrlSafeNoPadding, Decoder, Encoder, Hex};
5use rand::Rng;
6
7use crate::{claims::DEFAULT_TIME_TOLERANCE_SECS, error::*};
8
9pub const DEFAULT_MAX_TOKEN_LENGTH: usize = 1_000_000;
10
11/// Additional features to enable during verification.
12/// Signatures and token expiration are already automatically verified.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct VerificationOptions {
15    /// Reject tokens created before the given date.
16    ///
17    /// For a given user, the time of the last successful authentication can be
18    /// kept in a database, and `reject_before` can then be used to reject
19    /// older (replayed) tokens.
20    ///
21    /// Note: validation compares `reject_before` to the token’s
22    /// `issued_at` claim. Tokens without `issued_at` are rejected when
23    /// `reject_before` is set, so be sure the issuer populates it
24    /// (automatically done by constructing claims with `Claims::create()`).
25    pub reject_before: Option<UnixTimeStamp>,
26
27    /// Accept tokens created with a date in the future
28    pub accept_future: bool,
29
30    /// Require a specific subject to be present
31    pub required_subject: Option<String>,
32
33    /// Require a specific key identifier to be present
34    pub required_key_id: Option<String>,
35
36    /// Require a specific signature type
37    pub required_signature_type: Option<String>,
38
39    /// Require a specific content type
40    pub required_content_type: Option<String>,
41
42    /// Require a specific nonce to be present
43    pub required_nonce: Option<String>,
44
45    /// Require the issuer to be present in the set
46    pub allowed_issuers: Option<HashSet<String>>,
47
48    /// Require the audience to be present in the set
49    pub allowed_audiences: Option<HashSet<String>>,
50
51    /// How much clock drift to tolerate when verifying token timestamps
52    /// Default is 15 minutes, to work around common issues with clocks that are not perfectly accurate
53    pub time_tolerance: Option<Duration>,
54
55    /// Reject tokens created more than `max_validity` ago
56    pub max_validity: Option<Duration>,
57
58    /// Maximum token length to accept.
59    /// Defaults to `DEFAULT_MAX_TOKEN_LENGTH`; `None` accepts tokens of any size.
60    pub max_token_length: Option<usize>,
61
62    /// Maximum unsafe, untrusted, unverified JWT header length to accept
63    pub max_header_length: Option<usize>,
64
65    /// Change the current time. Only used for testing.
66    pub artificial_time: Option<UnixTimeStamp>,
67}
68
69impl Default for VerificationOptions {
70    fn default() -> Self {
71        Self {
72            reject_before: None,
73            accept_future: false,
74            required_subject: None,
75            required_key_id: None,
76            required_signature_type: None,
77            required_content_type: None,
78            required_nonce: None,
79            allowed_issuers: None,
80            allowed_audiences: None,
81            time_tolerance: Some(Duration::from_secs(DEFAULT_TIME_TOLERANCE_SECS)),
82            max_validity: None,
83            max_token_length: Some(DEFAULT_MAX_TOKEN_LENGTH),
84            max_header_length: None,
85            artificial_time: None,
86        }
87    }
88}
89
90/// Options for header creation when constructing a token.
91#[derive(Debug, Clone, Default)]
92pub struct HeaderOptions {
93    /// The contents of the content type (`cty`) field in the JWT header. If set
94    /// to `None`, this field is not present on the serialized JWT.
95    pub content_type: Option<String>,
96    /// The contents of the signature type (`typ`) field in the JWT header. If
97    /// set to `None`, the serialized JWT's `typ` field will contain the string
98    /// "JWT".
99    pub signature_type: Option<String>,
100}
101
102#[derive(Debug, Clone, Default)]
103pub enum Salt {
104    /// No salt. This is the default.
105    #[default]
106    None,
107    /// A salt to be used for signing tokens.
108    Signer(Vec<u8>),
109    /// A salt to be used for verifying tokens.
110    Verifier(Vec<u8>),
111}
112
113impl Salt {
114    /// Get the length of the salt.
115    pub fn len(&self) -> usize {
116        match self {
117            Salt::None => 0,
118            Salt::Signer(s) => s.len(),
119            Salt::Verifier(s) => s.len(),
120        }
121    }
122
123    /// Check if the salt is empty.
124    pub fn is_empty(&self) -> bool {
125        self.len() == 0
126    }
127
128    /// Generate a new random salt.
129    pub fn generate() -> Self {
130        let mut salt = vec![0u8; 32];
131        rand::rng().fill_bytes(&mut salt);
132        Salt::Signer(salt)
133    }
134}
135
136impl AsRef<[u8]> for Salt {
137    /// Get the salt as a byte slice.
138    fn as_ref(&self) -> &[u8] {
139        match self {
140            Salt::None => &[],
141            Salt::Signer(s) => s,
142            Salt::Verifier(s) => s,
143        }
144    }
145}
146
147/// Unsigned metadata about a key to be attached to tokens.
148/// This information can be freely tampered with by an intermediate party.
149/// Most applications should not need to use this.
150#[derive(Debug, Clone, Default)]
151pub struct KeyMetadata {
152    pub(crate) key_set_url: Option<String>,
153    pub(crate) public_key: Option<String>,
154    pub(crate) certificate_url: Option<String>,
155    pub(crate) certificate_sha1_thumbprint: Option<String>,
156    pub(crate) certificate_sha256_thumbprint: Option<String>,
157    pub(crate) salt: Salt,
158}
159
160impl KeyMetadata {
161    /// Add a salt to the metadata
162    pub fn with_salt(mut self, salt: Salt) -> Self {
163        self.salt = salt;
164        self
165    }
166
167    /// Add a key set URL to the metadata ("jku")
168    pub fn with_key_set_url(mut self, key_set_url: impl ToString) -> Self {
169        self.key_set_url = Some(key_set_url.to_string());
170        self
171    }
172
173    /// Add a public key to the metadata ("jwk")
174    pub fn with_public_key(mut self, public_key: impl ToString) -> Self {
175        self.public_key = Some(public_key.to_string());
176        self
177    }
178
179    /// Add a certificate URL to the metadata ("x5u")
180    pub fn with_certificate_url(mut self, certificate_url: impl ToString) -> Self {
181        self.certificate_url = Some(certificate_url.to_string());
182        self
183    }
184
185    /// Add a certificate SHA-1 thumbprint to the metadata ("x5t")
186    pub fn with_certificate_sha1_thumbprint(
187        mut self,
188        certificate_sha1_thumbprint: impl ToString,
189    ) -> Result<Self, Error> {
190        let thumbprint = certificate_sha1_thumbprint.to_string();
191        let mut bin = [0u8; 20];
192        if thumbprint.len() == 40 {
193            ensure!(
194                Hex::decode(&mut bin, &thumbprint, None)?.len() == bin.len(),
195                JWTError::InvalidCertThumprint
196            );
197            let thumbprint = Base64UrlSafeNoPadding::encode_to_string(bin)?;
198            self.certificate_sha1_thumbprint = Some(thumbprint);
199            return Ok(self);
200        }
201        ensure!(
202            Base64UrlSafeNoPadding::decode(&mut bin, &thumbprint, None)?.len() == bin.len(),
203            JWTError::InvalidCertThumprint
204        );
205        self.certificate_sha1_thumbprint = Some(thumbprint);
206        Ok(self)
207    }
208
209    /// Add a certificate SHA-256 thumbprint to the metadata ("x5t#S256")
210    pub fn with_certificate_sha256_thumbprint(
211        mut self,
212        certificate_sha256_thumbprint: impl ToString,
213    ) -> Result<Self, Error> {
214        let thumbprint = certificate_sha256_thumbprint.to_string();
215        let mut bin = [0u8; 32];
216        if thumbprint.len() == 64 {
217            ensure!(
218                Hex::decode(&mut bin, &thumbprint, None)?.len() == bin.len(),
219                JWTError::InvalidCertThumprint
220            );
221            let thumbprint = Base64UrlSafeNoPadding::encode_to_string(bin)?;
222            self.certificate_sha256_thumbprint = Some(thumbprint);
223            return Ok(self);
224        }
225        ensure!(
226            Base64UrlSafeNoPadding::decode(&mut bin, &thumbprint, None)?.len() == bin.len(),
227            JWTError::InvalidCertThumprint
228        );
229        self.certificate_sha256_thumbprint = Some(thumbprint);
230        Ok(self)
231    }
232}