diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ec5751..d95a6b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,16 @@ # Changelog +## 5.0.0 (unreleased) + +- Update ring +- Change error handling to be based on simple struct/enum rather than error-chain +- Fix validations not being called properly in some cases +- Default validation is not checking `iat` and `nbf` anymore + ## 4.0.1 (2018-03-19) +- Add method to decode a token without signature verification + ## 4.0.0 (2017-11-22) ### Breaking changes diff --git a/Cargo.toml b/Cargo.toml index 8dc0165..257dd03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jsonwebtoken" -version = "4.0.1" +version = "5.0.0" authors = ["Vincent Prouillet "] license = "MIT" readme = "README.md" @@ -10,7 +10,6 @@ repository = "https://github.com/Keats/rust-jwt" keywords = ["jwt", "web", "api", "token", "json"] [dependencies] -error-chain = { version = "0.11", default-features = false } serde_json = "1.0" serde_derive = "1.0" serde = "1.0" diff --git a/README.md b/README.md index 975c529..c4dedbe 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Add the following to Cargo.toml: ```toml -jsonwebtoken = "4" +jsonwebtoken = "5" serde_derive = "1" serde = "1" ``` diff --git a/src/crypto.rs b/src/crypto.rs index b2516b5..110de2e 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -26,6 +26,12 @@ pub enum Algorithm { RS512, } +impl Default for Algorithm { + fn default() -> Self { + Algorithm::HS256 + } +} + /// The actual HS signing + encoding fn sign_hmac(alg: &'static digest::Algorithm, key: &[u8], signing_input: &str) -> Result { let signing_key = hmac::SigningKey::new(alg, key); @@ -48,14 +54,14 @@ fn sign_rsa(alg: Algorithm, key: &[u8], signing_input: &str) -> Result { let key_pair = Arc::new( signature::RSAKeyPair::from_der(untrusted::Input::from(key)) - .map_err(|_| ErrorKind::InvalidKey)? + .map_err(|_| ErrorKind::InvalidRsaKey)? ); let mut signing_state = signature::RSASigningState::new(key_pair) - .map_err(|_| ErrorKind::InvalidKey)?; + .map_err(|_| ErrorKind::InvalidRsaKey)?; let mut signature = vec![0; signing_state.key_pair().public_modulus_len()]; let rng = rand::SystemRandom::new(); signing_state.sign(ring_alg, &rng, signing_input.as_bytes(), &mut signature) - .map_err(|_| ErrorKind::InvalidKey)?; + .map_err(|_| ErrorKind::InvalidRsaKey)?; Ok( base64::encode_config::<[u8]>(&signature, base64::URL_SAFE_NO_PAD) @@ -73,10 +79,6 @@ pub fn sign(signing_input: &str, key: &[u8], algorithm: Algorithm) -> Result sign_hmac(&digest::SHA512, key, signing_input), Algorithm::RS256 | Algorithm::RS384 | Algorithm::RS512 => sign_rsa(algorithm, key, signing_input), -// TODO: if PKCS1 is made prublic, remove the line above and uncomment below -// Algorithm::RS256 => sign_rsa(&signature::RSA_PKCS1_SHA256, key, signing_input), -// Algorithm::RS384 => sign_rsa(&signature::RSA_PKCS1_SHA384, key, signing_input), -// Algorithm::RS512 => sign_rsa(&signature::RSA_PKCS1_SHA512, key, signing_input), } } @@ -112,9 +114,3 @@ pub fn verify(signature: &str, signing_input: &str, key: &[u8], algorithm: Algor Algorithm::RS512 => verify_rsa(&signature::RSA_PKCS1_2048_8192_SHA512, signature, signing_input, key), } } - -impl Default for Algorithm { - fn default() -> Self { - Algorithm::HS256 - } -} diff --git a/src/errors.rs b/src/errors.rs index c0af0f7..2f85b5c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,68 +1,162 @@ +use std::error::Error as StdError; +use std::fmt; +use std::result; + use base64; use serde_json; -use ring; -error_chain! { - errors { - /// When a token doesn't have a valid JWT shape - InvalidToken { - description("invalid token") - display("Invalid token") - } - /// When the signature doesn't match - InvalidSignature { - description("invalid signature") - display("Invalid signature") - } - /// When the secret given is not a valid RSA key - InvalidKey { - description("invalid key") - display("Invalid Key") - } +/// A crate private constructor for `Error`. +pub(crate) fn new_error(kind: ErrorKind) -> Error { + Error(Box::new(kind)) +} - // Validation error - /// When a token’s `exp` claim indicates that it has expired - ExpiredSignature { - description("expired signature") - display("Expired Signature") - } - /// When a token’s `iss` claim does not match the expected issuer - InvalidIssuer { - description("invalid issuer") - display("Invalid Issuer") - } - /// When a token’s `aud` claim does not match one of the expected audience values - InvalidAudience { - description("invalid audience") - display("Invalid Audience") - } - /// When a token’s `aud` claim does not match one of the expected audience values - InvalidSubject { - description("invalid subject") - display("Invalid Subject") - } - /// When a token’s `iat` claim is in the future - InvalidIssuedAt { - description("invalid issued at") - display("Invalid Issued At") - } - /// When a token’s nbf claim represents a time in the future - ImmatureSignature { - description("immature signature") - display("Immature Signature") - } - /// When the algorithm in the header doesn't match the one passed to `decode` - InvalidAlgorithm { - description("Invalid algorithm") - display("Invalid Algorithm") - } +/// A type alias for `Result`. +pub type Result = result::Result; + +/// An error that can occur when encoding/decoding JWTs +#[derive(Debug)] +pub struct Error(Box); + +impl Error { + /// Return the specific type of this error. + pub fn kind(&self) -> &ErrorKind { + &self.0 } - foreign_links { - Unspecified(ring::error::Unspecified) #[doc = "An error happened while signing/verifying a token with RSA"]; - Base64(base64::DecodeError) #[doc = "An error happened while decoding some base64 text"]; - Json(serde_json::Error) #[doc = "An error happened while serializing/deserializing JSON"]; - Utf8(::std::string::FromUtf8Error) #[doc = "An error happened while trying to convert the result of base64 decoding to a String"]; + /// Unwrap this error into its underlying type. + pub fn into_kind(self) -> ErrorKind { + *self.0 + } +} + +/// The specific type of an error. +#[derive(Debug)] +pub enum ErrorKind { + /// When a token doesn't have a valid JWT shape + InvalidToken, + /// When the signature doesn't match + InvalidSignature, + /// When the secret given is not a valid RSA key + InvalidRsaKey, + + // validation error + + /// When a token’s `exp` claim indicates that it has expired + ExpiredSignature, + /// When a token’s `iss` claim does not match the expected issuer + InvalidIssuer, + /// When a token’s `aud` claim does not match one of the expected audience values + InvalidAudience, + /// When a token’s `aud` claim does not match one of the expected audience values + InvalidSubject, + /// When a token’s `iat` claim is in the future + InvalidIssuedAt, + /// When a token’s nbf claim represents a time in the future + ImmatureSignature, + /// When the algorithm in the header doesn't match the one passed to `decode` + InvalidAlgorithm, + + // 3rd party errors + + /// An error happened when decoding some base64 text + Base64(base64::DecodeError), + /// An error happened while serializing/deserializing JSON + Json(serde_json::Error), + /// Some of the text was invalid UTF-8 + Utf8(::std::string::FromUtf8Error), + + + /// Hints that destructuring should not be exhaustive. + /// + /// This enum may grow additional variants, so this makes sure clients + /// don't count on exhaustive matching. (Otherwise, adding a new variant + /// could break existing code.) + #[doc(hidden)] + __Nonexhaustive, +} + +impl StdError for Error { + fn description(&self) -> &str { + match *self.0 { + ErrorKind::InvalidToken => "invalid token", + ErrorKind::InvalidSignature => "invalid signature", + ErrorKind::InvalidRsaKey => "invalid RSA key", + ErrorKind::ExpiredSignature => "expired signature", + ErrorKind::InvalidIssuer => "invalid issuer", + ErrorKind::InvalidAudience => "invalid audience", + ErrorKind::InvalidSubject => "invalid subject", + ErrorKind::InvalidIssuedAt => "invalid issued at", + ErrorKind::ImmatureSignature => "immature signature", + ErrorKind::InvalidAlgorithm => "algorithms don't match", + ErrorKind::Base64(ref err) => err.description(), + ErrorKind::Json(ref err) => err.description(), + ErrorKind::Utf8(ref err) => err.description(), + _ => unreachable!(), + } + } + + fn cause(&self) -> Option<&StdError> { + match *self.0 { + ErrorKind::InvalidToken => None, + ErrorKind::InvalidSignature => None, + ErrorKind::InvalidRsaKey => None, + ErrorKind::ExpiredSignature => None, + ErrorKind::InvalidIssuer => None, + ErrorKind::InvalidAudience => None, + ErrorKind::InvalidSubject => None, + ErrorKind::InvalidIssuedAt => None, + ErrorKind::ImmatureSignature => None, + ErrorKind::InvalidAlgorithm => None, + ErrorKind::Base64(ref err) => Some(err), + ErrorKind::Json(ref err) => Some(err), + ErrorKind::Utf8(ref err) => Some(err), + _ => unreachable!(), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self.0 { + ErrorKind::InvalidToken => write!(f, "invalid token"), + ErrorKind::InvalidSignature => write!(f, "invalid signature"), + ErrorKind::InvalidRsaKey => write!(f, "invalid RSA key"), + ErrorKind::ExpiredSignature => write!(f, "expired signature"), + ErrorKind::InvalidIssuer => write!(f, "invalid issuer"), + ErrorKind::InvalidAudience => write!(f, "invalid audience"), + ErrorKind::InvalidSubject => write!(f, "invalid subject"), + ErrorKind::InvalidIssuedAt => write!(f, "invalid issued at"), + ErrorKind::ImmatureSignature => write!(f, "immature signature"), + ErrorKind::InvalidAlgorithm => write!(f, "algorithms don't match"), + ErrorKind::Base64(ref err) => write!(f, "base64 error: {}", err), + ErrorKind::Json(ref err) => write!(f, "JSON error: {}", err), + ErrorKind::Utf8(ref err) => write!(f, "UTF-8 error: {}", err), + _ => unreachable!(), + } + } +} + +impl From for Error { + fn from(err: base64::DecodeError) -> Error { + new_error(ErrorKind::Base64(err)) + } +} + +impl From for Error { + fn from(err: serde_json::Error) -> Error { + new_error(ErrorKind::Json(err)) + } +} + +impl From<::std::string::FromUtf8Error> for Error { + fn from(err: ::std::string::FromUtf8Error) -> Error { + new_error(ErrorKind::Utf8(err)) + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Error { + new_error(kind) } } diff --git a/src/lib.rs b/src/lib.rs index c34e3a4..4ca961b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,6 @@ #![recursion_limit = "300"] #![deny(missing_docs)] -#[macro_use] -extern crate error_chain; #[macro_use] extern crate serde_derive; extern crate serde_json; @@ -35,7 +33,7 @@ pub use serialization::TokenData; use serde::de::DeserializeOwned; use serde::ser::Serialize; -use errors::{Result, ErrorKind}; +use errors::{Result, ErrorKind, new_error}; use serialization::{from_jwt_part, from_jwt_part_claims, to_jwt_part}; use validation::{validate}; @@ -78,7 +76,7 @@ macro_rules! expect_two { let mut i = $iter; match (i.next(), i.next(), i.next()) { (Some(first), Some(second), None) => (first, second), - _ => return Err(ErrorKind::InvalidToken.into()) + _ => return Err(new_error(ErrorKind::InvalidToken)) } }} } @@ -108,18 +106,17 @@ pub fn decode(token: &str, key: &[u8], validation: &Validat let header: Header = from_jwt_part(header)?; if !verify(signature, signing_input, key, header.alg)? { - return Err(ErrorKind::InvalidSignature.into()); + return Err(new_error(ErrorKind::InvalidSignature)); } if !validation.algorithms.contains(&header.alg) { - return Err(ErrorKind::InvalidAlgorithm.into()); + return Err(new_error(ErrorKind::InvalidAlgorithm)); } let (decoded_claims, claims_map): (T, _) = from_jwt_part_claims(claims)?; - validate(&claims_map, validation)?; - Ok(TokenData { header: header, claims: decoded_claims }) + Ok(TokenData { header, claims: decoded_claims }) } /// Decode a token without any signature validation into a struct containing 2 fields: `claims` and `header`. diff --git a/src/validation.rs b/src/validation.rs index 7025b91..dc87301 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -3,7 +3,7 @@ use serde::ser::Serialize; use serde_json::{Value, from_value, to_value}; use serde_json::map::Map; -use errors::{Result, ErrorKind}; +use errors::{Result, ErrorKind, new_error}; use crypto::Algorithm; @@ -95,8 +95,8 @@ impl Default for Validation { leeway: 0, validate_exp: true, - validate_iat: true, - validate_nbf: true, + validate_iat: false, + validate_nbf: false, iss: None, sub: None, @@ -112,45 +112,63 @@ impl Default for Validation { pub fn validate(claims: &Map, options: &Validation) -> Result<()> { let now = Utc::now().timestamp(); - if let Some(iat) = claims.get("iat") { - if options.validate_iat && from_value::(iat.clone())? > now + options.leeway { - return Err(ErrorKind::InvalidIssuedAt.into()); + if options.validate_iat { + if let Some(iat) = claims.get("iat") { + if from_value::(iat.clone())? > now + options.leeway { + return Err(new_error(ErrorKind::InvalidIssuedAt)); + } + } else { + return Err(new_error(ErrorKind::InvalidIssuedAt)); } } - if let Some(exp) = claims.get("exp") { - if options.validate_exp && from_value::(exp.clone())? < now - options.leeway { - return Err(ErrorKind::ExpiredSignature.into()); + if options.validate_exp { + if let Some(exp) = claims.get("exp") { + if from_value::(exp.clone())? < now - options.leeway { + return Err(new_error(ErrorKind::ExpiredSignature)); + } + } else { + return Err(new_error(ErrorKind::ExpiredSignature)); } } - if let Some(nbf) = claims.get("nbf") { - if options.validate_nbf && from_value::(nbf.clone())? > now + options.leeway { - return Err(ErrorKind::ImmatureSignature.into()); + if options.validate_nbf { + if let Some(nbf) = claims.get("nbf") { + if from_value::(nbf.clone())? > now + options.leeway { + return Err(new_error(ErrorKind::ImmatureSignature)); + } + } else { + return Err(new_error(ErrorKind::ImmatureSignature)); } } - if let Some(iss) = claims.get("iss") { - if let Some(ref correct_iss) = options.iss { + if let Some(ref correct_iss) = options.iss { + if let Some(iss) = claims.get("iss") { if from_value::(iss.clone())? != *correct_iss { - return Err(ErrorKind::InvalidIssuer.into()); + return Err(new_error(ErrorKind::InvalidIssuer)); } + } else { + return Err(new_error(ErrorKind::InvalidIssuer)); } } - if let Some(sub) = claims.get("sub") { - if let Some(ref correct_sub) = options.sub { + if let Some(ref correct_sub) = options.sub { + if let Some(sub) = claims.get("sub") { if from_value::(sub.clone())? != *correct_sub { - return Err(ErrorKind::InvalidSubject.into()); + return Err(new_error(ErrorKind::InvalidSubject)); } + } else { + return Err(new_error(ErrorKind::InvalidSubject)); } } - if let Some(aud) = claims.get("aud") { - if let Some(ref correct_aud) = options.aud { + if let Some(ref correct_aud) = options.aud { + if let Some(aud) = claims.get("aud") { if aud != correct_aud { - return Err(ErrorKind::InvalidAudience.into()); + return Err(new_error(ErrorKind::InvalidAudience)); } + } else { + return Err(new_error(ErrorKind::InvalidAudience)); } } @@ -172,7 +190,8 @@ mod tests { fn iat_in_past_ok() { let mut claims = Map::new(); claims.insert("iat".to_string(), to_value(Utc::now().timestamp() - 10000).unwrap()); - let res = validate(&claims, &Validation::default()); + let validation = Validation { validate_exp: false, validate_iat: true, ..Validation::default() }; + let res = validate(&claims, &validation); assert!(res.is_ok()); } @@ -180,7 +199,8 @@ mod tests { fn iat_in_future_fails() { let mut claims = Map::new(); claims.insert("iat".to_string(), to_value(Utc::now().timestamp() + 100000).unwrap()); - let res = validate(&claims, &Validation::default()); + let validation = Validation { validate_exp: false, validate_iat: true, ..Validation::default() }; + let res = validate(&claims, &validation); assert!(res.is_err()); match res.unwrap_err().kind() { @@ -195,6 +215,8 @@ mod tests { claims.insert("iat".to_string(), to_value(Utc::now().timestamp() + 50).unwrap()); let validation = Validation { leeway: 1000 * 60, + validate_iat: true, + validate_exp: false, ..Default::default() }; let res = validate(&claims, &validation); @@ -234,11 +256,24 @@ mod tests { assert!(res.is_ok()); } + // https://github.com/Keats/jsonwebtoken/issues/51 + #[test] + fn validation_called_even_if_field_is_empty() { + let claims = Map::new(); + let res = validate(&claims, &Validation::default()); + assert!(res.is_err()); + match res.unwrap_err().kind() { + &ErrorKind::ExpiredSignature => (), + _ => assert!(false), + }; + } + #[test] fn nbf_in_past_ok() { let mut claims = Map::new(); claims.insert("nbf".to_string(), to_value(Utc::now().timestamp() - 10000).unwrap()); - let res = validate(&claims, &Validation::default()); + let validation = Validation { validate_exp: false, validate_nbf: true, ..Validation::default() }; + let res = validate(&claims, &validation); assert!(res.is_ok()); } @@ -246,7 +281,8 @@ mod tests { fn nbf_in_future_fails() { let mut claims = Map::new(); claims.insert("nbf".to_string(), to_value(Utc::now().timestamp() + 100000).unwrap()); - let res = validate(&claims, &Validation::default()); + let validation = Validation { validate_exp: false, validate_nbf: true, ..Validation::default() }; + let res = validate(&claims, &validation); assert!(res.is_err()); match res.unwrap_err().kind() { @@ -261,6 +297,8 @@ mod tests { claims.insert("nbf".to_string(), to_value(Utc::now().timestamp() + 500).unwrap()); let validation = Validation { leeway: 1000 * 60, + validate_nbf: true, + validate_exp: false, ..Default::default() }; let res = validate(&claims, &validation); @@ -272,6 +310,7 @@ mod tests { let mut claims = Map::new(); claims.insert("iss".to_string(), to_value("Keats").unwrap()); let validation = Validation { + validate_exp: false, iss: Some("Keats".to_string()), ..Default::default() }; @@ -284,6 +323,24 @@ mod tests { let mut claims = Map::new(); claims.insert("iss".to_string(), to_value("Hacked").unwrap()); let validation = Validation { + validate_exp: false, + iss: Some("Keats".to_string()), + ..Default::default() + }; + let res = validate(&claims, &validation); + assert!(res.is_err()); + + match res.unwrap_err().kind() { + &ErrorKind::InvalidIssuer => (), + _ => assert!(false), + }; + } + + #[test] + fn iss_missing_fails() { + let claims = Map::new(); + let validation = Validation { + validate_exp: false, iss: Some("Keats".to_string()), ..Default::default() }; @@ -301,6 +358,7 @@ mod tests { let mut claims = Map::new(); claims.insert("sub".to_string(), to_value("Keats").unwrap()); let validation = Validation { + validate_exp: false, sub: Some("Keats".to_string()), ..Default::default() }; @@ -313,6 +371,24 @@ mod tests { let mut claims = Map::new(); claims.insert("sub".to_string(), to_value("Hacked").unwrap()); let validation = Validation { + validate_exp: false, + sub: Some("Keats".to_string()), + ..Default::default() + }; + let res = validate(&claims, &validation); + assert!(res.is_err()); + + match res.unwrap_err().kind() { + &ErrorKind::InvalidSubject => (), + _ => assert!(false), + }; + } + + #[test] + fn sub_missing_fails() { + let claims = Map::new(); + let validation = Validation { + validate_exp: false, sub: Some("Keats".to_string()), ..Default::default() }; @@ -329,7 +405,10 @@ mod tests { fn aud_string_ok() { let mut claims = Map::new(); claims.insert("aud".to_string(), to_value("Everyone").unwrap()); - let mut validation = Validation::default(); + let mut validation = Validation { + validate_exp: false, + ..Validation::default() + }; validation.set_audience(&"Everyone"); let res = validate(&claims, &validation); assert!(res.is_ok()); @@ -339,7 +418,10 @@ mod tests { fn aud_array_of_string_ok() { let mut claims = Map::new(); claims.insert("aud".to_string(), to_value(["UserA", "UserB"]).unwrap()); - let mut validation = Validation::default(); + let mut validation = Validation { + validate_exp: false, + ..Validation::default() + }; validation.set_audience(&["UserA", "UserB"]); let res = validate(&claims, &validation); assert!(res.is_ok()); @@ -349,7 +431,10 @@ mod tests { fn aud_type_mismatch_fails() { let mut claims = Map::new(); claims.insert("aud".to_string(), to_value("Everyone").unwrap()); - let mut validation = Validation::default(); + let mut validation = Validation { + validate_exp: false, + ..Validation::default() + }; validation.set_audience(&["UserA", "UserB"]); let res = validate(&claims, &validation); assert!(res.is_err()); @@ -364,7 +449,27 @@ mod tests { fn aud_correct_type_not_matching_fails() { let mut claims = Map::new(); claims.insert("aud".to_string(), to_value("Everyone").unwrap()); - let mut validation = Validation::default(); + let mut validation = Validation { + validate_exp: false, + ..Validation::default() + }; + validation.set_audience(&"None"); + let res = validate(&claims, &validation); + assert!(res.is_err()); + + match res.unwrap_err().kind() { + &ErrorKind::InvalidAudience => (), + _ => assert!(false), + }; + } + + #[test] + fn aud_missing_fails() { + let claims = Map::new(); + let mut validation = Validation { + validate_exp: false, + ..Validation::default() + }; validation.set_audience(&"None"); let res = validate(&claims, &validation); assert!(res.is_err()); diff --git a/tests/lib.rs b/tests/lib.rs index d1eb579..98b926b 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,14 +1,16 @@ extern crate jsonwebtoken; #[macro_use] extern crate serde_derive; +extern crate chrono; use jsonwebtoken::{encode, decode, decode_header, dangerous_unsafe_decode, Algorithm, Header, sign, verify, Validation}; - +use chrono::Utc; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] struct Claims { sub: String, - company: String + company: String, + exp: i64, } #[test] @@ -29,7 +31,8 @@ fn verify_hs256() { fn encode_with_custom_header() { let my_claims = Claims { sub: "b@b.com".to_string(), - company: "ACME".to_string() + company: "ACME".to_string(), + exp: Utc::now().timestamp() + 10000, }; let mut header = Header::default(); header.kid = Some("kid".to_string()); @@ -43,7 +46,8 @@ fn encode_with_custom_header() { fn round_trip_claim() { let my_claims = Claims { sub: "b@b.com".to_string(), - company: "ACME".to_string() + company: "ACME".to_string(), + exp: Utc::now().timestamp() + 10000, }; let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap(); let token_data = decode::(&token, "secret".as_ref(), &Validation::default()).unwrap(); @@ -53,8 +57,9 @@ fn round_trip_claim() { #[test] fn decode_token() { - let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; + let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; let claims = decode::(token, "secret".as_ref(), &Validation::default()); + println!("{:?}", claims); claims.unwrap(); } @@ -84,18 +89,11 @@ fn decode_token_wrong_algorithm() { #[test] fn decode_token_with_bytes_secret() { - let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiY29tcGFueSI6Ikdvb2dvbCJ9.27QxgG96vpX4akKNpD1YdRGHE3_u2X35wR3EHA2eCrs"; + let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks"; let claims = decode::(token, b"\x01\x02\x03", &Validation::default()); assert!(claims.is_ok()); } -#[test] -fn decode_token_with_shuffled_header_fields() { - let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.SEIZ4Jg46VGhquuwPYDLY5qHF8AkQczF14aXM3a2c28"; - let claims = decode::(token, "secret".as_ref(), &Validation::default()); - assert!(claims.is_ok()); -} - #[test] fn decode_header_only() { let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.S"; @@ -106,7 +104,7 @@ fn decode_header_only() { #[test] fn dangerous_unsafe_decode_token() { - let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; + let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; let claims = dangerous_unsafe_decode::(token); claims.unwrap(); } @@ -121,14 +119,36 @@ fn dangerous_unsafe_decode_token_missing_parts() { #[test] fn dangerous_unsafe_decode_token_invalid_signature() { - let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong"; + let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.wrong"; let claims = dangerous_unsafe_decode::(token); claims.unwrap(); } #[test] fn dangerous_unsafe_decode_token_wrong_algorithm() { - let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; + let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.fLxey-hxAKX5rNHHIx1_Ch0KmrbiuoakDVbsJjLWrx8fbjKjrPuWMYEJzTU3SBnYgnZokC-wqSdqckXUOunC-g"; let claims = dangerous_unsafe_decode::(token); claims.unwrap(); } + +// https://github.com/Keats/jsonwebtoken/issues/51 +#[test] +fn does_validation_in_right_order() { + let my_claims = Claims { + sub: "b@b.com".to_string(), + company: "ACME".to_string(), + exp: Utc::now().timestamp() + 10000, + }; + let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap(); + let v = Validation { + leeway: 5, + validate_exp: true, + iss: Some("iss no check".to_string()), + sub: Some("sub no check".to_string()), + ..Validation::default() + }; + let res = decode::(&token, "secret".as_ref(), &v); + assert!(res.is_err()); + println!("{:?}", res); + //assert!(res.is_ok()); +} diff --git a/tests/rsa.rs b/tests/rsa.rs index 423a5a8..23bc5cf 100644 --- a/tests/rsa.rs +++ b/tests/rsa.rs @@ -1,14 +1,16 @@ extern crate jsonwebtoken; #[macro_use] extern crate serde_derive; +extern crate chrono; use jsonwebtoken::{encode, decode, Algorithm, Header, sign, verify, Validation}; - +use chrono::Utc; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] struct Claims { sub: String, - company: String + company: String, + exp: i64, } #[test] @@ -23,7 +25,8 @@ fn round_trip_sign_verification() { fn round_trip_claim() { let my_claims = Claims { sub: "b@b.com".to_string(), - company: "ACME".to_string() + company: "ACME".to_string(), + exp: Utc::now().timestamp() + 10000, }; let token = encode(&Header::new(Algorithm::RS256), &my_claims, include_bytes!("private_rsa_key.der")).unwrap(); let token_data = decode::(&token, include_bytes!("public_rsa_key.der"), &Validation::new(Algorithm::RS256)).unwrap();