1use std::collections::HashSet;
2use std::convert::TryInto;
3
4use coarsetime::{Clock, Duration, UnixTimeStamp};
5use ct_codecs::{Base64UrlSafeNoPadding, Encoder};
6use rand::RngCore;
7use serde::{de::DeserializeOwned, Deserialize, Serialize};
8
9use crate::common::VerificationOptions;
10use crate::error::*;
11use crate::serde_additions;
12
13pub const DEFAULT_TIME_TOLERANCE_SECS: u64 = 900;
14
15#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)]
17pub struct NoCustomClaims {}
18
19#[derive(Debug, Clone, Eq, PartialEq)]
22pub enum Audiences {
23 AsSet(HashSet<String>),
24 AsString(String),
25}
26
27impl Audiences {
28 pub fn is_set(&self) -> bool {
30 matches!(self, Audiences::AsSet(_))
31 }
32
33 pub fn is_string(&self) -> bool {
35 matches!(self, Audiences::AsString(_))
36 }
37
38 pub fn contains(&self, allowed_audiences: &HashSet<String>) -> bool {
41 match self {
42 Audiences::AsString(audience) => allowed_audiences.contains(audience),
43 Audiences::AsSet(audiences) => {
44 audiences.intersection(allowed_audiences).next().is_some()
45 }
46 }
47 }
48
49 pub fn into_set(self) -> HashSet<String> {
51 match self {
52 Audiences::AsSet(audiences_set) => audiences_set,
53 Audiences::AsString(audiences) => {
54 let mut audiences_set = HashSet::new();
55 if !audiences.is_empty() {
56 audiences_set.insert(audiences);
57 }
58 audiences_set
59 }
60 }
61 }
62
63 pub fn into_string(self) -> Result<String, Error> {
67 match self {
68 Audiences::AsString(audiences_str) => Ok(audiences_str),
69 Audiences::AsSet(audiences) => {
70 if audiences.len() > 1 {
71 bail!(JWTError::TooManyAudiences);
72 }
73 Ok(audiences
74 .iter()
75 .next()
76 .map(|x| x.to_string())
77 .unwrap_or_default())
78 }
79 }
80 }
81}
82
83impl TryInto<String> for Audiences {
84 type Error = Error;
85
86 fn try_into(self) -> Result<String, Error> {
87 self.into_string()
88 }
89}
90
91impl From<Audiences> for HashSet<String> {
92 fn from(audiences: Audiences) -> HashSet<String> {
93 audiences.into_set()
94 }
95}
96
97impl<T: ToString> From<T> for Audiences {
98 fn from(audience: T) -> Self {
99 Audiences::AsString(audience.to_string())
100 }
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct JWTClaims<CustomClaims> {
110 #[serde(
112 rename = "iat",
113 default,
114 skip_serializing_if = "Option::is_none",
115 with = "self::serde_additions::unix_timestamp"
116 )]
117 pub issued_at: Option<UnixTimeStamp>,
118
119 #[serde(
121 rename = "exp",
122 default,
123 skip_serializing_if = "Option::is_none",
124 with = "self::serde_additions::unix_timestamp"
125 )]
126 pub expires_at: Option<UnixTimeStamp>,
127
128 #[serde(
130 rename = "nbf",
131 default,
132 skip_serializing_if = "Option::is_none",
133 with = "self::serde_additions::unix_timestamp"
134 )]
135 pub invalid_before: Option<UnixTimeStamp>,
136
137 #[serde(rename = "iss", default, skip_serializing_if = "Option::is_none")]
139 pub issuer: Option<String>,
140
141 #[serde(rename = "sub", default, skip_serializing_if = "Option::is_none")]
143 pub subject: Option<String>,
144
145 #[serde(
147 rename = "aud",
148 default,
149 skip_serializing_if = "Option::is_none",
150 with = "self::serde_additions::audiences"
151 )]
152 pub audiences: Option<Audiences>,
153
154 #[serde(rename = "jti", default, skip_serializing_if = "Option::is_none")]
163 pub jwt_id: Option<String>,
164
165 #[serde(rename = "nonce", default, skip_serializing_if = "Option::is_none")]
167 pub nonce: Option<String>,
168
169 #[serde(flatten)]
171 pub custom: CustomClaims,
172}
173
174impl<CustomClaims> JWTClaims<CustomClaims> {
175 pub(crate) fn validate(&self, options: &VerificationOptions) -> Result<(), Error> {
176 let now = Clock::now_since_epoch();
177 let time_tolerance = options.time_tolerance.unwrap_or_default();
178
179 if let Some(reject_before) = options.reject_before {
180 ensure!(now <= reject_before, JWTError::OldTokenReused);
181 }
182 if let Some(time_issued) = self.issued_at {
183 ensure!(time_issued <= now + time_tolerance, JWTError::ClockDrift);
184 if let Some(max_validity) = options.max_validity {
185 ensure!(
186 now <= time_issued || now - time_issued <= max_validity,
187 JWTError::TokenIsTooOld
188 );
189 }
190 }
191 if !options.accept_future {
192 if let Some(invalid_before) = self.invalid_before {
193 ensure!(
194 now + time_tolerance >= invalid_before,
195 JWTError::TokenNotValidYet
196 );
197 }
198 }
199 if let Some(expires_at) = self.expires_at {
200 ensure!(
201 now - time_tolerance <= expires_at,
202 JWTError::TokenHasExpired
203 );
204 }
205 if let Some(allowed_issuers) = &options.allowed_issuers {
206 if let Some(issuer) = &self.issuer {
207 ensure!(
208 allowed_issuers.contains(issuer),
209 JWTError::RequiredIssuerMismatch
210 );
211 } else {
212 bail!(JWTError::RequiredIssuerMissing);
213 }
214 }
215 if let Some(required_subject) = &options.required_subject {
216 if let Some(subject) = &self.subject {
217 ensure!(
218 subject == required_subject,
219 JWTError::RequiredSubjectMismatch
220 );
221 } else {
222 bail!(JWTError::RequiredSubjectMissing);
223 }
224 }
225 if let Some(required_nonce) = &options.required_nonce {
226 if let Some(nonce) = &self.nonce {
227 ensure!(nonce == required_nonce, JWTError::RequiredNonceMismatch);
228 } else {
229 bail!(JWTError::RequiredNonceMissing);
230 }
231 }
232 if let Some(allowed_audiences) = &options.allowed_audiences {
233 if let Some(audiences) = &self.audiences {
234 ensure!(
235 audiences.contains(allowed_audiences),
236 JWTError::RequiredAudienceMismatch
237 );
238 } else {
239 bail!(JWTError::RequiredAudienceMissing);
240 }
241 }
242 Ok(())
243 }
244
245 pub fn invalid_before(mut self, unix_timestamp: UnixTimeStamp) -> Self {
247 self.invalid_before = Some(unix_timestamp);
248 self
249 }
250
251 pub fn with_issuer(mut self, issuer: impl ToString) -> Self {
253 self.issuer = Some(issuer.to_string());
254 self
255 }
256
257 pub fn with_subject(mut self, subject: impl ToString) -> Self {
259 self.subject = Some(subject.to_string());
260 self
261 }
262
263 pub fn with_audiences(mut self, audiences: HashSet<impl ToString>) -> Self {
266 self.audiences = Some(Audiences::AsSet(
267 audiences.iter().map(|x| x.to_string()).collect(),
268 ));
269 self
270 }
271
272 pub fn with_audience(mut self, audience: impl ToString) -> Self {
274 self.audiences = Some(Audiences::AsString(audience.to_string()));
275 self
276 }
277
278 pub fn with_jwt_id(mut self, jwt_id: impl ToString) -> Self {
280 self.jwt_id = Some(jwt_id.to_string());
281 self
282 }
283
284 pub fn with_nonce(mut self, nonce: impl ToString) -> Self {
286 self.nonce = Some(nonce.to_string());
287 self
288 }
289
290 pub fn create_nonce(&mut self) -> String {
292 let mut raw_nonce = [0u8; 24];
293 let mut rng = rand::thread_rng();
294 rng.fill_bytes(&mut raw_nonce);
295 let nonce = Base64UrlSafeNoPadding::encode_to_string(raw_nonce).unwrap();
296 self.nonce = Some(nonce);
297 self.nonce.as_deref().unwrap().to_string()
298 }
299}
300
301pub struct Claims;
302
303impl Claims {
304 pub fn create(valid_for: Duration) -> JWTClaims<NoCustomClaims> {
307 let now = Some(Clock::now_since_epoch());
308 JWTClaims {
309 issued_at: now,
310 expires_at: Some(now.unwrap() + valid_for),
311 invalid_before: now,
312 audiences: None,
313 issuer: None,
314 jwt_id: None,
315 subject: None,
316 nonce: None,
317 custom: NoCustomClaims {},
318 }
319 }
320
321 pub fn with_custom_claims<CustomClaims: Serialize + DeserializeOwned>(
323 custom_claims: CustomClaims,
324 valid_for: Duration,
325 ) -> JWTClaims<CustomClaims> {
326 let now = Some(Clock::now_since_epoch());
327 JWTClaims {
328 issued_at: now,
329 expires_at: Some(now.unwrap() + valid_for),
330 invalid_before: now,
331 audiences: None,
332 issuer: None,
333 jwt_id: None,
334 subject: None,
335 nonce: None,
336 custom: custom_claims,
337 }
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344
345 #[test]
346 fn should_set_standard_claims() {
347 let exp = Duration::from_mins(10);
348 let mut audiences = HashSet::new();
349 audiences.insert("audience1".to_string());
350 audiences.insert("audience2".to_string());
351 let claims = Claims::create(exp)
352 .with_audiences(audiences.clone())
353 .with_issuer("issuer")
354 .with_jwt_id("jwt_id")
355 .with_nonce("nonce")
356 .with_subject("subject");
357
358 assert_eq!(claims.audiences, Some(Audiences::AsSet(audiences)));
359 assert_eq!(claims.issuer, Some("issuer".to_owned()));
360 assert_eq!(claims.jwt_id, Some("jwt_id".to_owned()));
361 assert_eq!(claims.nonce, Some("nonce".to_owned()));
362 assert_eq!(claims.subject, Some("subject".to_owned()));
363 }
364
365 #[test]
366 fn parse_floating_point_unix_time() {
367 let claims: JWTClaims<()> = serde_json::from_str(r#"{"exp":1617757825.8}"#).unwrap();
368 assert_eq!(
369 claims.expires_at,
370 Some(UnixTimeStamp::from_secs(1617757825))
371 );
372 }
373}