Merge pull request #59 from Keats/next

Next
This commit is contained in:
Vincent Prouillet 2018-08-13 16:02:04 +02:00 committed by GitHub
commit af18ff9230
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 354 additions and 131 deletions

View File

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

View File

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

View File

@ -8,7 +8,7 @@
Add the following to Cargo.toml:
```toml
jsonwebtoken = "4"
jsonwebtoken = "5"
serde_derive = "1"
serde = "1"
```

View File

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

View File

@ -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 tokens `exp` claim indicates that it has expired
ExpiredSignature {
description("expired signature")
display("Expired Signature")
}
/// When a tokens `iss` claim does not match the expected issuer
InvalidIssuer {
description("invalid issuer")
display("Invalid Issuer")
}
/// When a tokens `aud` claim does not match one of the expected audience values
InvalidAudience {
description("invalid audience")
display("Invalid Audience")
}
/// When a tokens `aud` claim does not match one of the expected audience values
InvalidSubject {
description("invalid subject")
display("Invalid Subject")
}
/// When a tokens `iat` claim is in the future
InvalidIssuedAt {
description("invalid issued at")
display("Invalid Issued At")
}
/// When a tokens 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 tokens `exp` claim indicates that it has expired
ExpiredSignature,
/// When a tokens `iss` claim does not match the expected issuer
InvalidIssuer,
/// When a tokens `aud` claim does not match one of the expected audience values
InvalidAudience,
/// When a tokens `aud` claim does not match one of the expected audience values
InvalidSubject,
/// When a tokens `iat` claim is in the future
InvalidIssuedAt,
/// When a tokens 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)
}
}

View File

@ -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`.

View File

@ -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());

View File

@ -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());
}

View File

@ -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();