commit
af18ff9230
|
@ -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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "jsonwebtoken"
|
||||
version = "4.0.1"
|
||||
version = "5.0.0"
|
||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||
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"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
Add the following to Cargo.toml:
|
||||
|
||||
```toml
|
||||
jsonwebtoken = "4"
|
||||
jsonwebtoken = "5"
|
||||
serde_derive = "1"
|
||||
serde = "1"
|
||||
```
|
||||
|
|
|
@ -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<String> {
|
||||
let signing_key = hmac::SigningKey::new(alg, key);
|
||||
|
@ -48,14 +54,14 @@ fn sign_rsa(alg: Algorithm, key: &[u8], signing_input: &str) -> Result<String> {
|
|||
|
||||
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<Str
|
|||
Algorithm::HS512 => 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
|
||||
}
|
||||
}
|
||||
|
|
212
src/errors.rs
212
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<T, jsonwebtoken::Error>`.
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// An error that can occur when encoding/decoding JWTs
|
||||
#[derive(Debug)]
|
||||
pub struct Error(Box<ErrorKind>);
|
||||
|
||||
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<base64::DecodeError> for Error {
|
||||
fn from(err: base64::DecodeError) -> Error {
|
||||
new_error(ErrorKind::Base64(err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> 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<ErrorKind> for Error {
|
||||
fn from(kind: ErrorKind) -> Error {
|
||||
new_error(kind)
|
||||
}
|
||||
}
|
||||
|
|
13
src/lib.rs
13
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<T: DeserializeOwned>(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`.
|
||||
|
|
|
@ -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<String, Value>, options: &Validation) -> Result<()> {
|
||||
let now = Utc::now().timestamp();
|
||||
|
||||
if let Some(iat) = claims.get("iat") {
|
||||
if options.validate_iat && from_value::<i64>(iat.clone())? > now + options.leeway {
|
||||
return Err(ErrorKind::InvalidIssuedAt.into());
|
||||
if options.validate_iat {
|
||||
if let Some(iat) = claims.get("iat") {
|
||||
if from_value::<i64>(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::<i64>(exp.clone())? < now - options.leeway {
|
||||
return Err(ErrorKind::ExpiredSignature.into());
|
||||
if options.validate_exp {
|
||||
if let Some(exp) = claims.get("exp") {
|
||||
if from_value::<i64>(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::<i64>(nbf.clone())? > now + options.leeway {
|
||||
return Err(ErrorKind::ImmatureSignature.into());
|
||||
if options.validate_nbf {
|
||||
if let Some(nbf) = claims.get("nbf") {
|
||||
if from_value::<i64>(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::<String>(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::<String>(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());
|
||||
|
|
52
tests/lib.rs
52
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::<Claims>(&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::<Claims>(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::<Claims>(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::<Claims>(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::<Claims>(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::<Claims>(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::<Claims>(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::<Claims>(&token, "secret".as_ref(), &v);
|
||||
assert!(res.is_err());
|
||||
println!("{:?}", res);
|
||||
//assert!(res.is_ok());
|
||||
}
|
||||
|
|
|
@ -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::<Claims>(&token, include_bytes!("public_rsa_key.der"), &Validation::new(Algorithm::RS256)).unwrap();
|
||||
|
|
Loading…
Reference in New Issue