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))); + } }