From 32c012b39e2f196cb9059e294bd8e3a0509d8a46 Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 16 Aug 2023 18:10:01 +0200 Subject: [PATCH 1/2] fix: durations --- Cargo.toml | 1 + src/lib.rs | 70 ++++++++++++++++++++++++++++++++---------------------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 836b552..a9c62b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ reqwest = { version = "0.11.4", features = ["json"], optional = true } tokio = { version = "1.9.0", features = ["sync"], optional = true } openssl-sys = "0.9.65" foreign-types = "0.3.2" +serde_with = "3.1.0" [dev-dependencies] axum = "0.1.3" diff --git a/src/lib.rs b/src/lib.rs index 0db2e2b..5568c4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,4 @@ #![doc = include_str!("../README.md")] -mod some; - -pub mod hmac; - -pub mod eddsa; - -pub mod ecdsa; - -pub mod rsa; - -pub mod jwk; use std::{ borrow::Cow, @@ -18,13 +7,27 @@ use std::{ string::FromUtf8Error, time::{Duration, SystemTime, UNIX_EPOCH}, }; - -use jwk::Jwk; use openssl::error::ErrorStack; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::{Map, Value}; +use serde_with::serde_as; use smallvec::SmallVec; +use jwk::Jwk; +pub use some::*; + +mod some; + +pub mod hmac; + +pub mod eddsa; + +pub mod ecdsa; + +pub mod rsa; + +pub mod jwk; + /// JWT header. #[non_exhaustive] #[derive(Debug, Serialize, Deserialize, Default)] @@ -65,15 +68,16 @@ impl Default for OneOrMany { } /// JWT Claims. +#[serde_as] #[non_exhaustive] #[derive(Debug, Serialize, Default, Deserialize)] pub struct Claims { - #[serde(skip_serializing_if = "Option::is_none")] - pub exp: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub nbf: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub iat: Option, + #[serde_as(as = "Option>")] + pub exp: Option, + #[serde_as(as = "Option>")] + pub nbf: Option, + #[serde_as(as = "Option>")] + pub iat: Option, #[serde(skip_serializing_if = "Option::is_none")] pub iss: Option, @@ -189,8 +193,7 @@ impl HeaderAndClaims { self.claims.iat = Some( SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(), + .unwrap(), ); self } @@ -198,7 +201,7 @@ impl HeaderAndClaims { /// Check that `iat` is present and is later than `t`. pub fn iat_is_later_than(&self, t: SystemTime) -> bool { self.claims.iat.map_or(false, |iat| { - iat > t.duration_since(UNIX_EPOCH).unwrap().as_secs() + iat > t.duration_since(UNIX_EPOCH).unwrap() }) } @@ -209,7 +212,7 @@ impl HeaderAndClaims { .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs(); - self.claims.exp = Some(t); + self.claims.exp = Some(Duration::from_secs(t)); self } @@ -220,7 +223,7 @@ impl HeaderAndClaims { .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs(); - self.claims.nbf = Some(t); + self.claims.nbf = Some(Duration::from_secs(t)); self } } @@ -238,8 +241,6 @@ fn url_safe_trailing_bits() -> base64::Config { base64::URL_SAFE_NO_PAD.decode_allow_trailing_bits(true) } -pub use some::*; - /// Encode and sign this header and claims with the signing key. /// /// The `alg` field in header is automatically set. The `kid` claim is @@ -286,13 +287,13 @@ pub fn verify( // Check exp and nbf. let now = SystemTime::now(); if let Some(exp) = claims.claims.exp { - let exp = SystemTime::UNIX_EPOCH + Duration::from_secs(exp); + let exp = SystemTime::UNIX_EPOCH + exp; if now > exp { return Err(Error::Expired); } } if let Some(nbf) = claims.claims.nbf { - let nbf = SystemTime::UNIX_EPOCH + Duration::from_secs(nbf); + let nbf = SystemTime::UNIX_EPOCH + nbf; if now < nbf { return Err(Error::Before); } @@ -496,6 +497,8 @@ pub type Result = std::result::Result; #[cfg(test)] mod tests { + use base64::{CharacterSet, Config}; + use crate::ecdsa::{EcdsaAlgorithm, EcdsaPrivateKey}; use super::*; @@ -532,4 +535,15 @@ mod tests { Ok(()) } + + #[test] + fn claim_deserialization() { + let mut json = r#"eyJpYXQiOjEuNjkyMTkwMTI1RTksImV4cCI6MS42OTIxOTM3MjVFOSwiYW50aUNzcmZUb2tlbiI6bnVsbCwic3ViIjoiYTM5ZmZjNWUtNjc5ZC00YjAzLWI5YmYtYTliZjEzNDk4NGYzIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDozOTk5L2F1dGgiLCJzZXNzaW9uSGFuZGxlIjoiNTAyMWQ2MTQtYzFmNi00ZTZkLWI1NjktZGQxN2Q0N2EyOWI0IiwicGFyZW50UmVmcmVzaFRva2VuSGFzaDEiOm51bGwsInJlZnJlc2hUb2tlbkhhc2gxIjoiNTZiMjcxZDcxNGRlMzg3M2UwMmIyZjAyYTJiZDcyYWJjZDIyZDM0NGZlZjE2YTJkMWJjYmM1NGU2YWUxN2M3OCJ9"#.as_bytes(); + + let r = base64::read::DecoderReader::new(&mut json, Config::new(CharacterSet::ImapMutf7, true)); + + let claims: Claims = serde_json::from_reader(r).unwrap(); + assert_eq!(claims.iat, Some(Duration::from_secs(1692190125))); + assert_eq!(claims.exp, Some(Duration::from_secs(1692193725))); + } } From c69ffc6612c9e12179228fae2d28dbae2978ac99 Mon Sep 17 00:00:00 2001 From: tom Date: Fri, 18 Aug 2023 09:16:30 +0200 Subject: [PATCH 2/2] fixup! fix: durations --- src/lib.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5568c4f..5e7face 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ use std::{ use openssl::error::ErrorStack; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::{Map, Value}; -use serde_with::serde_as; +use serde_with::{serde_as, skip_serializing_none}; use smallvec::SmallVec; use jwk::Jwk; @@ -69,6 +69,7 @@ impl Default for OneOrMany { /// JWT Claims. #[serde_as] +#[skip_serializing_none] #[non_exhaustive] #[derive(Debug, Serialize, Default, Deserialize)] pub struct Claims { @@ -79,13 +80,10 @@ pub struct Claims { #[serde_as(as = "Option>")] pub iat: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub iss: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub sub: Option, #[serde(default, skip_serializing_if = "OneOrMany::is_empty")] pub aud: OneOrMany, - #[serde(skip_serializing_if = "Option::is_none")] pub jti: Option, #[serde(flatten)] @@ -210,9 +208,8 @@ impl HeaderAndClaims { pub fn set_exp_from_now(&mut self, dur: Duration) -> &mut Self { let t = (SystemTime::now() + dur) .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - self.claims.exp = Some(Duration::from_secs(t)); + .unwrap(); + self.claims.exp = Some(t); self } @@ -221,9 +218,8 @@ impl HeaderAndClaims { pub fn set_nbf_from_now(&mut self, dur: Duration) -> &mut Self { let t = (SystemTime::now() + dur) .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - self.claims.nbf = Some(Duration::from_secs(t)); + .unwrap(); + self.claims.nbf = Some(t); self } } @@ -497,8 +493,6 @@ pub type Result = std::result::Result; #[cfg(test)] mod tests { - use base64::{CharacterSet, Config}; - use crate::ecdsa::{EcdsaAlgorithm, EcdsaPrivateKey}; use super::*; @@ -540,7 +534,7 @@ mod tests { fn claim_deserialization() { let mut json = r#"eyJpYXQiOjEuNjkyMTkwMTI1RTksImV4cCI6MS42OTIxOTM3MjVFOSwiYW50aUNzcmZUb2tlbiI6bnVsbCwic3ViIjoiYTM5ZmZjNWUtNjc5ZC00YjAzLWI5YmYtYTliZjEzNDk4NGYzIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDozOTk5L2F1dGgiLCJzZXNzaW9uSGFuZGxlIjoiNTAyMWQ2MTQtYzFmNi00ZTZkLWI1NjktZGQxN2Q0N2EyOWI0IiwicGFyZW50UmVmcmVzaFRva2VuSGFzaDEiOm51bGwsInJlZnJlc2hUb2tlbkhhc2gxIjoiNTZiMjcxZDcxNGRlMzg3M2UwMmIyZjAyYTJiZDcyYWJjZDIyZDM0NGZlZjE2YTJkMWJjYmM1NGU2YWUxN2M3OCJ9"#.as_bytes(); - let r = base64::read::DecoderReader::new(&mut json, Config::new(CharacterSet::ImapMutf7, true)); + let r = base64::read::DecoderReader::new(&mut json, url_safe_trailing_bits()); let claims: Claims = serde_json::from_reader(r).unwrap(); assert_eq!(claims.iat, Some(Duration::from_secs(1692190125)));