diff --git a/Cargo.toml b/Cargo.toml index e6724af..ac626eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,12 +13,14 @@ include = ["src/**/*", "benches/**/*", "tests/**/*", "LICENSE", "README.md", "CH [dependencies] serde_json = "1.0" -serde = {version = "1.0", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } ring = { version = "0.16.5", features = ["std"] } base64 = "0.13" # For PEM decoding -pem = {version = "1", optional = true} -simple_asn1 = {version = "0.6", optional = true} +pem = { version = "1", optional = true } +simple_asn1 = { version = "0.6", optional = true } +time = "0.3" +wasm-time = { path = "../wasm-time" } [dev-dependencies] # For the custom time example @@ -28,6 +30,8 @@ criterion = "0.3" [features] default = ["use_pem"] use_pem = ["pem", "simple_asn1"] +no_verify = [] +wasm = ["no_verify"] [[bench]] name = "jwt" diff --git a/src/decoding.rs b/src/decoding.rs index 145a24d..aad2975 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -1,6 +1,7 @@ use serde::de::DeserializeOwned; use crate::algorithms::AlgorithmFamily; +#[cfg(not(feature = "no_verify"))] use crate::crypto::verify; use crate::errors::{new_error, ErrorKind, Result}; use crate::header::Header; @@ -172,8 +173,13 @@ fn verify_signature<'a>( return Err(new_error(ErrorKind::InvalidAlgorithm)); } - if validation.validate_signature && !verify(signature, message.as_bytes(), key, header.alg)? { - return Err(new_error(ErrorKind::InvalidSignature)); + if validation.validate_signature { + #[cfg(not(feature = "no_verify"))] + if !verify(signature, message.as_bytes(), key, header.alg)? { + return Err(new_error(ErrorKind::InvalidSignature)); + } + #[cfg(feature = "no_verify")] + return Err(new_error(ErrorKind::VerificationNotSupported)); } Ok((header, payload)) @@ -207,7 +213,7 @@ pub fn decode( Ok((header, claims)) => { let decoded_claims = DecodedJwtPartClaims::from_jwt_part_claims(claims)?; let claims = decoded_claims.deserialize()?; - validate(decoded_claims.deserialize()?, validation)?; + validate(&decoded_claims.deserialize()?, validation)?; Ok(TokenData { header, claims }) } diff --git a/src/errors.rs b/src/errors.rs index e0553e3..48e3b50 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -78,6 +78,10 @@ pub enum ErrorKind { Utf8(::std::string::FromUtf8Error), /// Something unspecified went wrong with crypto Crypto(::ring::error::Unspecified), + + /// Verification is disabled by the feature flag `no_verify`. + #[cfg(feature = "no_verify")] + VerificationNotSupported, } impl StdError for Error { @@ -102,6 +106,8 @@ impl StdError for Error { ErrorKind::Json(ref err) => Some(err.as_ref()), ErrorKind::Utf8(ref err) => Some(err), ErrorKind::Crypto(ref err) => Some(err), + #[cfg(feature = "no_verify")] + ErrorKind::VerificationNotSupported => None, } } } @@ -128,6 +134,8 @@ impl fmt::Display for Error { ErrorKind::Utf8(ref err) => write!(f, "UTF-8 error: {}", err), ErrorKind::Crypto(ref err) => write!(f, "Crypto error: {}", err), ErrorKind::Base64(ref err) => write!(f, "Base64 error: {}", err), + #[cfg(feature = "no_verify")] + ErrorKind::VerificationNotSupported => write!(f, "{:?}", self.0), } } } diff --git a/src/validation.rs b/src/validation.rs index 1a13eea..dc32189 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use std::collections::HashSet; use std::fmt; use std::marker::PhantomData; -use std::time::{SystemTime, UNIX_EPOCH}; use serde::de::{self, Visitor}; use serde::{Deserialize, Deserializer}; @@ -136,10 +135,13 @@ impl Default for Validation { } } + /// Gets the current timestamp in the format JWT expect pub fn get_current_timestamp() -> u64 { - let start = SystemTime::now(); - start.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() + let start = wasm_time::now_utc(); + let secs = (start - time::OffsetDateTime::UNIX_EPOCH).whole_seconds(); + assert!(secs >= 0, "Time went backwards."); + secs as u64 } #[derive(Deserialize)] @@ -212,7 +214,7 @@ fn is_subset(reference: &HashSet, given: &HashSet Result<()> { +pub(crate) fn validate(claims: &ClaimsForValidation, options: &Validation) -> Result<()> { let now = get_current_timestamp(); for required_claim in &options.required_spec_claims { @@ -231,27 +233,27 @@ pub(crate) fn validate(claims: ClaimsForValidation, options: &Validation) -> Res } if options.validate_exp - && !matches!(claims.exp, TryParse::Parsed(exp) if exp >= now-options.leeway) + && !matches!(claims.exp, TryParse::Parsed(ref exp) if *exp >= now-options.leeway) { return Err(new_error(ErrorKind::ExpiredSignature)); } if options.validate_nbf - && !matches!(claims.nbf, TryParse::Parsed(nbf) if nbf <= now + options.leeway) + && !matches!(claims.nbf, TryParse::Parsed(ref nbf) if *nbf <= now + options.leeway) { return Err(new_error(ErrorKind::ImmatureSignature)); } if let Some(correct_sub) = options.sub.as_deref() { - if !matches!(claims.sub, TryParse::Parsed(sub) if sub == correct_sub) { + if !matches!(claims.sub, TryParse::Parsed(ref sub) if *sub == correct_sub) { return Err(new_error(ErrorKind::InvalidSubject)); } } if let Some(ref correct_iss) = options.iss { let is_valid = match claims.iss { - TryParse::Parsed(Issuer::Single(iss)) if correct_iss.contains(&*iss) => true, - TryParse::Parsed(Issuer::Multiple(iss)) => is_subset(correct_iss, &iss), + TryParse::Parsed(Issuer::Single(ref iss)) if correct_iss.contains(&**iss) => true, + TryParse::Parsed(Issuer::Multiple(ref iss)) => is_subset(correct_iss, &iss), _ => false, }; @@ -262,8 +264,8 @@ pub(crate) fn validate(claims: ClaimsForValidation, options: &Validation) -> Res if let Some(ref correct_aud) = options.aud { let is_valid = match claims.aud { - TryParse::Parsed(Audience::Single(aud)) if correct_aud.contains(&*aud) => true, - TryParse::Parsed(Audience::Multiple(aud)) => is_subset(correct_aud, &aud), + TryParse::Parsed(Audience::Single(ref aud)) if correct_aud.contains(&**aud) => true, + TryParse::Parsed(Audience::Multiple(ref aud)) => is_subset(correct_aud, &aud), _ => false, }; @@ -330,21 +332,21 @@ mod tests { #[test] fn exp_in_future_ok() { let claims = json!({ "exp": get_current_timestamp() + 10000 }); - let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256)); + let res = validate(&deserialize_claims(&claims), &Validation::new(Algorithm::HS256)); assert!(res.is_ok()); } #[test] fn exp_float_in_future_ok() { let claims = json!({ "exp": (get_current_timestamp() as f64) + 10000.123 }); - let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256)); + let res = validate(&deserialize_claims(&claims), &Validation::new(Algorithm::HS256)); assert!(res.is_ok()); } #[test] fn exp_in_past_fails() { let claims = json!({ "exp": get_current_timestamp() - 100000 }); - let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256)); + let res = validate(&deserialize_claims(&claims), &Validation::new(Algorithm::HS256)); assert!(res.is_err()); match res.unwrap_err().kind() { @@ -356,7 +358,7 @@ mod tests { #[test] fn exp_float_in_past_fails() { let claims = json!({ "exp": (get_current_timestamp() as f64) - 100000.1234 }); - let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256)); + let res = validate(&deserialize_claims(&claims), &Validation::new(Algorithm::HS256)); assert!(res.is_err()); match res.unwrap_err().kind() { @@ -370,7 +372,7 @@ mod tests { let claims = json!({ "exp": get_current_timestamp() - 500 }); let mut validation = Validation::new(Algorithm::HS256); validation.leeway = 1000 * 60; - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_ok()); } @@ -380,7 +382,7 @@ mod tests { let claims = json!({}); let mut validation = Validation::new(Algorithm::HS256); validation.required_spec_claims = HashSet::new(); - let res = validate(deserialize_claims(&claims), &validation).unwrap_err(); + let res = validate(&deserialize_claims(&claims), &validation).unwrap_err(); assert_eq!(res.kind(), &ErrorKind::ExpiredSignature); } @@ -391,7 +393,7 @@ mod tests { validation.required_spec_claims = HashSet::new(); validation.validate_exp = false; validation.validate_nbf = true; - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_ok()); } @@ -402,7 +404,7 @@ mod tests { validation.required_spec_claims = HashSet::new(); validation.validate_exp = false; validation.validate_nbf = true; - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_ok()); } @@ -413,7 +415,7 @@ mod tests { validation.required_spec_claims = HashSet::new(); validation.validate_exp = false; validation.validate_nbf = true; - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_err()); match res.unwrap_err().kind() { @@ -430,7 +432,7 @@ mod tests { validation.validate_exp = false; validation.validate_nbf = true; validation.leeway = 1000 * 60; - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_ok()); } @@ -441,7 +443,7 @@ mod tests { validation.required_spec_claims = HashSet::new(); validation.validate_exp = false; validation.set_issuer(&["Keats"]); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_ok()); } @@ -452,7 +454,7 @@ mod tests { validation.required_spec_claims = HashSet::new(); validation.validate_exp = false; validation.set_issuer(&["UserA", "UserB"]); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_ok()); } @@ -464,7 +466,7 @@ mod tests { validation.required_spec_claims = HashSet::new(); validation.validate_exp = false; validation.set_issuer(&["Keats"]); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_err()); match res.unwrap_err().kind() { @@ -481,7 +483,7 @@ mod tests { validation.required_spec_claims = HashSet::new(); validation.validate_exp = false; validation.set_issuer(&["Keats"]); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); match res.unwrap_err().kind() { ErrorKind::InvalidIssuer => (), @@ -496,7 +498,7 @@ mod tests { validation.required_spec_claims = HashSet::new(); validation.validate_exp = false; validation.sub = Some("Keats".to_owned()); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_ok()); } @@ -507,7 +509,7 @@ mod tests { validation.required_spec_claims = HashSet::new(); validation.validate_exp = false; validation.sub = Some("Keats".to_owned()); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_err()); match res.unwrap_err().kind() { @@ -523,7 +525,7 @@ mod tests { validation.validate_exp = false; validation.required_spec_claims = HashSet::new(); validation.sub = Some("Keats".to_owned()); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_err()); match res.unwrap_err().kind() { @@ -539,7 +541,7 @@ mod tests { validation.validate_exp = false; validation.required_spec_claims = HashSet::new(); validation.set_audience(&["Everyone"]); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_ok()); } @@ -550,7 +552,7 @@ mod tests { validation.validate_exp = false; validation.required_spec_claims = HashSet::new(); validation.set_audience(&["UserA", "UserB"]); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_ok()); } @@ -561,7 +563,7 @@ mod tests { validation.validate_exp = false; validation.required_spec_claims = HashSet::new(); validation.set_audience(&["UserA", "UserB"]); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_err()); match res.unwrap_err().kind() { @@ -577,7 +579,7 @@ mod tests { validation.validate_exp = false; validation.required_spec_claims = HashSet::new(); validation.set_audience(&["None"]); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_err()); match res.unwrap_err().kind() { @@ -593,7 +595,7 @@ mod tests { validation.validate_exp = false; validation.required_spec_claims = HashSet::new(); validation.set_audience(&["None"]); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_err()); match res.unwrap_err().kind() { @@ -612,7 +614,7 @@ mod tests { validation.set_issuer(&["iss no check"]); validation.set_audience(&["iss no check"]); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); // It errors because it needs to validate iss/sub which are missing assert!(res.is_err()); match res.unwrap_err().kind() { @@ -634,14 +636,14 @@ mod tests { validation.required_spec_claims = HashSet::new(); validation.set_audience(&["my-googleclientid1234.apps.googleusercontent.com"]); - let res = validate(deserialize_claims(&claims), &validation); + let res = validate(&deserialize_claims(&claims), &validation); assert!(res.is_ok()); } #[test] fn errors_when_required_claim_is_missing() { let claims = json!({}); - let res = validate(deserialize_claims(&claims), &Validation::default()).unwrap_err(); + let res = validate(&deserialize_claims(&claims), &Validation::default()).unwrap_err(); assert_eq!(res.kind(), &ErrorKind::MissingRequiredClaim("exp".to_owned())); } }