parent
126ee4b1a3
commit
109978ab6b
|
@ -1,7 +1,14 @@
|
|||
# Changelog
|
||||
|
||||
## 5.0.0 (unreleased)
|
||||
|
||||
- Update ring
|
||||
- Change error handling to be based on simple struct/enum rather than error-chain
|
||||
|
||||
## 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"
|
||||
|
|
|
@ -48,14 +48,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,7 +73,7 @@ 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
|
||||
// TODO: if PKCS1 is made public, 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),
|
||||
|
|
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)
|
||||
}
|
||||
}
|
||||
|
|
10
src/lib.rs
10
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,11 +106,11 @@ 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)?;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
@ -114,26 +114,26 @@ pub fn validate(claims: &Map<String, Value>, options: &Validation) -> Result<()>
|
|||
|
||||
if let Some(iat) = claims.get("iat") {
|
||||
if options.validate_iat && from_value::<i64>(iat.clone())? > now + options.leeway {
|
||||
return Err(ErrorKind::InvalidIssuedAt.into());
|
||||
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());
|
||||
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());
|
||||
return Err(new_error(ErrorKind::ImmatureSignature));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(iss) = claims.get("iss") {
|
||||
if let Some(ref correct_iss) = options.iss {
|
||||
if from_value::<String>(iss.clone())? != *correct_iss {
|
||||
return Err(ErrorKind::InvalidIssuer.into());
|
||||
return Err(new_error(ErrorKind::InvalidIssuer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ pub fn validate(claims: &Map<String, Value>, options: &Validation) -> Result<()>
|
|||
if let Some(sub) = claims.get("sub") {
|
||||
if let Some(ref correct_sub) = options.sub {
|
||||
if from_value::<String>(sub.clone())? != *correct_sub {
|
||||
return Err(ErrorKind::InvalidSubject.into());
|
||||
return Err(new_error(ErrorKind::InvalidSubject));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ pub fn validate(claims: &Map<String, Value>, options: &Validation) -> Result<()>
|
|||
if let Some(aud) = claims.get("aud") {
|
||||
if let Some(ref correct_aud) = options.aud {
|
||||
if aud != correct_aud {
|
||||
return Err(ErrorKind::InvalidAudience.into());
|
||||
return Err(new_error(ErrorKind::InvalidAudience));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue