Support for WASM
This commit is contained in:
parent
d8a33def00
commit
86ba5e1d6c
10
Cargo.toml
10
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"
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue