jwt_simple/
common.rs

1use std::collections::HashSet;
2
3use coarsetime::{Duration, UnixTimeStamp};
4use ct_codecs::{Base64UrlSafeNoPadding, Decoder, Encoder, Hex};
5use rand::RngCore;
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    pub max_token_length: Option<usize>,
60
61    /// Maximum unsafe, untrusted, unverified JWT header length to accept
62    pub max_header_length: Option<usize>,
63
64    /// Change the current time. Only used for testing.
65    pub artificial_time: Option<UnixTimeStamp>,
66}
67
68impl Default for VerificationOptions {
69    fn default() -> Self {
70        Self {
71            reject_before: None,
72            accept_future: false,
73            required_subject: None,
74            required_key_id: None,
75            required_signature_type: None,
76            required_content_type: None,
77            required_nonce: None,
78            allowed_issuers: None,
79            allowed_audiences: None,
80            time_tolerance: Some(Duration::from_secs(DEFAULT_TIME_TOLERANCE_SECS)),
81            max_validity: None,
82            max_token_length: Some(DEFAULT_MAX_TOKEN_LENGTH),
83            max_header_length: None,
84            artificial_time: None,
85        }
86    }
87}
88
89/// Options for header creation when constructing a token.
90#[derive(Debug, Clone, Default)]
91pub struct HeaderOptions {
92    /// The contents of the content type (`cty`) field in the JWT header. If set
93    /// to `None`, this field is not present on the serialized JWT.
94    pub content_type: Option<String>,
95    /// The contents of the signature type (`typ`) field in the JWT header. If
96    /// set to `None`, the serialized JWT's `typ` field will contain the string
97    /// "JWT".
98    pub signature_type: Option<String>,
99}
100
101#[derive(Debug, Clone, Default)]
102pub enum Salt {
103    /// No salt. This is the default.
104    #[default]
105    None,
106    /// A salt to be used for signing tokens.
107    Signer(Vec<u8>),
108    /// A salt to be used for verifying tokens.
109    Verifier(Vec<u8>),
110}
111
112impl Salt {
113    /// Get the length of the salt.
114    pub fn len(&self) -> usize {
115        match self {
116            Salt::None => 0,
117            Salt::Signer(s) => s.len(),
118            Salt::Verifier(s) => s.len(),
119        }
120    }
121
122    /// Check if the salt is empty.
123    pub fn is_empty(&self) -> bool {
124        self.len() == 0
125    }
126
127    /// Generate a new random salt.
128    pub fn generate() -> Self {
129        let mut salt = vec![0u8; 32];
130        rand::thread_rng().fill_bytes(&mut salt);
131        Salt::Signer(salt)
132    }
133}
134
135impl AsRef<[u8]> for Salt {
136    /// Get the salt as a byte slice.
137    fn as_ref(&self) -> &[u8] {
138        match self {
139            Salt::None => &[],
140            Salt::Signer(s) => s,
141            Salt::Verifier(s) => s,
142        }
143    }
144}
145
146/// Unsigned metadata about a key to be attached to tokens.
147/// This information can be freely tampered with by an intermediate party.
148/// Most applications should not need to use this.
149#[derive(Debug, Clone, Default)]
150pub struct KeyMetadata {
151    pub(crate) key_set_url: Option<String>,
152    pub(crate) public_key: Option<String>,
153    pub(crate) certificate_url: Option<String>,
154    pub(crate) certificate_sha1_thumbprint: Option<String>,
155    pub(crate) certificate_sha256_thumbprint: Option<String>,
156    pub(crate) salt: Salt,
157}
158
159impl KeyMetadata {
160    /// Add a salt to the metadata
161    pub fn with_salt(mut self, salt: Salt) -> Self {
162        self.salt = salt;
163        self
164    }
165
166    /// Add a key set URL to the metadata ("jku")
167    pub fn with_key_set_url(mut self, key_set_url: impl ToString) -> Self {
168        self.key_set_url = Some(key_set_url.to_string());
169        self
170    }
171
172    /// Add a public key to the metadata ("jwk")
173    pub fn with_public_key(mut self, public_key: impl ToString) -> Self {
174        self.public_key = Some(public_key.to_string());
175        self
176    }
177
178    /// Add a certificate URL to the metadata ("x5u")
179    pub fn with_certificate_url(mut self, certificate_url: impl ToString) -> Self {
180        self.certificate_url = Some(certificate_url.to_string());
181        self
182    }
183
184    /// Add a certificate SHA-1 thumbprint to the metadata ("x5t")
185    pub fn with_certificate_sha1_thumbprint(
186        mut self,
187        certificate_sha1_thumbprint: impl ToString,
188    ) -> Result<Self, Error> {
189        let thumbprint = certificate_sha1_thumbprint.to_string();
190        let mut bin = [0u8; 20];
191        if thumbprint.len() == 40 {
192            ensure!(
193                Hex::decode(&mut bin, &thumbprint, None)?.len() == bin.len(),
194                JWTError::InvalidCertThumprint
195            );
196            let thumbprint = Base64UrlSafeNoPadding::encode_to_string(bin)?;
197            self.certificate_sha1_thumbprint = Some(thumbprint);
198            return Ok(self);
199        }
200        ensure!(
201            Base64UrlSafeNoPadding::decode(&mut bin, &thumbprint, None)?.len() == bin.len(),
202            JWTError::InvalidCertThumprint
203        );
204        self.certificate_sha1_thumbprint = Some(thumbprint);
205        Ok(self)
206    }
207
208    /// Add a certificate SHA-256 thumbprint to the metadata ("x5t#S256")
209    pub fn with_certificate_sha256_thumbprint(
210        mut self,
211        certificate_sha256_thumbprint: impl ToString,
212    ) -> Result<Self, Error> {
213        let thumbprint = certificate_sha256_thumbprint.to_string();
214        let mut bin = [0u8; 32];
215        if thumbprint.len() == 64 {
216            ensure!(
217                Hex::decode(&mut bin, &thumbprint, None)?.len() == bin.len(),
218                JWTError::InvalidCertThumprint
219            );
220            let thumbprint = Base64UrlSafeNoPadding::encode_to_string(bin)?;
221            self.certificate_sha256_thumbprint = Some(thumbprint);
222            return Ok(self);
223        }
224        ensure!(
225            Base64UrlSafeNoPadding::decode(&mut bin, &thumbprint, None)?.len() == bin.len(),
226            JWTError::InvalidCertThumprint
227        );
228        self.certificate_sha256_thumbprint = Some(thumbprint);
229        Ok(self)
230    }
231}