Support for WASM

This commit is contained in:
Michael Pfaff 2022-02-27 23:24:26 -05:00
parent d8a33def00
commit 86ba5e1d6c
Signed by: michael
GPG Key ID: F1A27427218FCA77
4 changed files with 62 additions and 42 deletions

View File

@ -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"

View File

@ -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<T: DeserializeOwned>(
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 })
}

View File

@ -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),
}
}
}

View File

@ -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<String>, given: &HashSet<BorrowedCowIfPossible<
}
}
pub(crate) fn validate(claims: ClaimsForValidation, options: &Validation) -> 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()));
}
}