44 changed files with 1360 additions and 1971 deletions
File diff suppressed because it is too large
@ -1,20 +0,0 @@ |
|||||
[package] |
|
||||
name = "jsonwebtoken" |
|
||||
version = "4.0.1" |
|
||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"] |
|
||||
license = "MIT" |
|
||||
readme = "README.md" |
|
||||
description = "Create and parse JWT in a strongly typed way." |
|
||||
homepage = "https://github.com/Keats/rust-jwt" |
|
||||
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" |
|
||||
ring = { version = "0.11.0", features = ["rsa_signing", "dev_urandom_fallback"] } |
|
||||
base64 = "0.9" |
|
||||
untrusted = "0.5" |
|
||||
chrono = "0.4" |
|
@ -1,21 +0,0 @@ |
|||||
The MIT License (MIT) |
|
||||
|
|
||||
Copyright (c) 2015 Vincent Prouillet |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||
of this software and associated documentation files (the "Software"), to deal |
|
||||
in the Software without restriction, including without limitation the rights |
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||
copies of the Software, and to permit persons to whom the Software is |
|
||||
furnished to do so, subject to the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be included in all |
|
||||
copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||
SOFTWARE. |
|
@ -1,120 +0,0 @@ |
|||||
use std::sync::Arc; |
|
||||
|
|
||||
use base64; |
|
||||
use ring::{rand, digest, hmac, signature}; |
|
||||
use ring::constant_time::verify_slices_are_equal; |
|
||||
use untrusted; |
|
||||
|
|
||||
use errors::{Result, ErrorKind}; |
|
||||
|
|
||||
|
|
||||
/// The algorithms supported for signing/verifying
|
|
||||
#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)] |
|
||||
pub enum Algorithm { |
|
||||
/// HMAC using SHA-256
|
|
||||
HS256, |
|
||||
/// HMAC using SHA-384
|
|
||||
HS384, |
|
||||
/// HMAC using SHA-512
|
|
||||
HS512, |
|
||||
|
|
||||
/// RSASSA-PKCS1-v1_5 using SHA-256
|
|
||||
RS256, |
|
||||
/// RSASSA-PKCS1-v1_5 using SHA-384
|
|
||||
RS384, |
|
||||
/// RSASSA-PKCS1-v1_5 using SHA-512
|
|
||||
RS512, |
|
||||
} |
|
||||
|
|
||||
/// 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); |
|
||||
let digest = hmac::sign(&signing_key, signing_input.as_bytes()); |
|
||||
|
|
||||
Ok( |
|
||||
base64::encode_config::<hmac::Signature>(&digest, base64::URL_SAFE_NO_PAD) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
/// The actual RSA signing + encoding
|
|
||||
/// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html
|
|
||||
fn sign_rsa(alg: Algorithm, key: &[u8], signing_input: &str) -> Result<String> { |
|
||||
let ring_alg = match alg { |
|
||||
Algorithm::RS256 => &signature::RSA_PKCS1_SHA256, |
|
||||
Algorithm::RS384 => &signature::RSA_PKCS1_SHA384, |
|
||||
Algorithm::RS512 => &signature::RSA_PKCS1_SHA512, |
|
||||
_ => unreachable!(), |
|
||||
}; |
|
||||
|
|
||||
let key_pair = Arc::new( |
|
||||
signature::RSAKeyPair::from_der(untrusted::Input::from(key)) |
|
||||
.map_err(|_| ErrorKind::InvalidKey)? |
|
||||
); |
|
||||
let mut signing_state = signature::RSASigningState::new(key_pair) |
|
||||
.map_err(|_| ErrorKind::InvalidKey)?; |
|
||||
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)?; |
|
||||
|
|
||||
Ok( |
|
||||
base64::encode_config::<[u8]>(&signature, base64::URL_SAFE_NO_PAD) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
/// Take the payload of a JWT, sign it using the algorithm given and return
|
|
||||
/// the base64 url safe encoded of the result.
|
|
||||
///
|
|
||||
/// Only use this function if you want to do something other than JWT.
|
|
||||
pub fn sign(signing_input: &str, key: &[u8], algorithm: Algorithm) -> Result<String> { |
|
||||
match algorithm { |
|
||||
Algorithm::HS256 => sign_hmac(&digest::SHA256, key, signing_input), |
|
||||
Algorithm::HS384 => sign_hmac(&digest::SHA384, key, signing_input), |
|
||||
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),
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// See Ring RSA docs for more details
|
|
||||
fn verify_rsa(alg: &signature::RSAParameters, signature: &str, signing_input: &str, key: &[u8]) -> Result<bool> { |
|
||||
let signature_bytes = base64::decode_config(signature, base64::URL_SAFE_NO_PAD)?; |
|
||||
let public_key_der = untrusted::Input::from(key); |
|
||||
let message = untrusted::Input::from(signing_input.as_bytes()); |
|
||||
let expected_signature = untrusted::Input::from(signature_bytes.as_slice()); |
|
||||
|
|
||||
let res = signature::verify(alg, public_key_der, message, expected_signature); |
|
||||
|
|
||||
Ok(res.is_ok()) |
|
||||
} |
|
||||
|
|
||||
/// Compares the signature given with a re-computed signature for HMAC or using the public key
|
|
||||
/// for RSA.
|
|
||||
///
|
|
||||
/// Only use this function if you want to do something other than JWT.
|
|
||||
///
|
|
||||
/// `signature` is the signature part of a jwt (text after the second '.')
|
|
||||
///
|
|
||||
/// `signing_input` is base64(header) + "." + base64(claims)
|
|
||||
pub fn verify(signature: &str, signing_input: &str, key: &[u8], algorithm: Algorithm) -> Result<bool> { |
|
||||
match algorithm { |
|
||||
Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => { |
|
||||
// we just re-sign the data with the key and compare if they are equal
|
|
||||
let signed = sign(signing_input, key, algorithm)?; |
|
||||
Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok()) |
|
||||
}, |
|
||||
Algorithm::RS256 => verify_rsa(&signature::RSA_PKCS1_2048_8192_SHA256, signature, signing_input, key), |
|
||||
Algorithm::RS384 => verify_rsa(&signature::RSA_PKCS1_2048_8192_SHA384, signature, signing_input, key), |
|
||||
Algorithm::RS512 => verify_rsa(&signature::RSA_PKCS1_2048_8192_SHA512, signature, signing_input, key), |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
impl Default for Algorithm { |
|
||||
fn default() -> Self { |
|
||||
Algorithm::HS256 |
|
||||
} |
|
||||
} |
|
@ -1,68 +0,0 @@ |
|||||
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") |
|
||||
} |
|
||||
|
|
||||
// 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") |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
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"]; |
|
||||
} |
|
||||
} |
|
@ -1,64 +0,0 @@ |
|||||
use crypto::Algorithm; |
|
||||
|
|
||||
|
|
||||
/// A basic JWT header, the alg defaults to HS256 and typ is automatically
|
|
||||
/// set to `JWT`. All the other fields are optional.
|
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] |
|
||||
pub struct Header { |
|
||||
/// The type of JWS: it can only be "JWT" here
|
|
||||
///
|
|
||||
/// Defined in [RFC7515#4.1.9](https://tools.ietf.org/html/rfc7515#section-4.1.9).
|
|
||||
#[serde(skip_serializing_if = "Option::is_none")] |
|
||||
pub typ: Option<String>, |
|
||||
/// The algorithm used
|
|
||||
///
|
|
||||
/// Defined in [RFC7515#4.1.1](https://tools.ietf.org/html/rfc7515#section-4.1.1).
|
|
||||
pub alg: Algorithm, |
|
||||
/// Content type
|
|
||||
///
|
|
||||
/// Defined in [RFC7519#5.2](https://tools.ietf.org/html/rfc7519#section-5.2).
|
|
||||
#[serde(skip_serializing_if = "Option::is_none")] |
|
||||
pub cty: Option<String>, |
|
||||
/// JSON Key URL
|
|
||||
///
|
|
||||
/// Defined in [RFC7515#4.1.2](https://tools.ietf.org/html/rfc7515#section-4.1.2).
|
|
||||
#[serde(skip_serializing_if = "Option::is_none")] |
|
||||
pub jku: Option<String>, |
|
||||
/// Key ID
|
|
||||
///
|
|
||||
/// Defined in [RFC7515#4.1.4](https://tools.ietf.org/html/rfc7515#section-4.1.4).
|
|
||||
#[serde(skip_serializing_if = "Option::is_none")] |
|
||||
pub kid: Option<String>, |
|
||||
/// X.509 URL
|
|
||||
///
|
|
||||
/// Defined in [RFC7515#4.1.5](https://tools.ietf.org/html/rfc7515#section-4.1.5).
|
|
||||
#[serde(skip_serializing_if = "Option::is_none")] |
|
||||
pub x5u: Option<String>, |
|
||||
/// X.509 certificate thumbprint
|
|
||||
///
|
|
||||
/// Defined in [RFC7515#4.1.7](https://tools.ietf.org/html/rfc7515#section-4.1.7).
|
|
||||
#[serde(skip_serializing_if = "Option::is_none")] |
|
||||
pub x5t: Option<String>, |
|
||||
} |
|
||||
|
|
||||
impl Header { |
|
||||
/// Returns a JWT header with the algorithm given
|
|
||||
pub fn new(algorithm: Algorithm) -> Header { |
|
||||
Header { |
|
||||
typ: Some("JWT".to_string()), |
|
||||
alg: algorithm, |
|
||||
cty: None, |
|
||||
jku: None, |
|
||||
kid: None, |
|
||||
x5u: None, |
|
||||
x5t: None, |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
impl Default for Header { |
|
||||
/// Returns a JWT header using the default Algorithm, HS256
|
|
||||
fn default() -> Self { |
|
||||
Header::new(Algorithm::default()) |
|
||||
} |
|
||||
} |
|
@ -1,142 +0,0 @@ |
|||||
//! Create and parses JWT (JSON Web Tokens)
|
|
||||
//!
|
|
||||
//! Documentation: [stable](https://docs.rs/jsonwebtoken/)
|
|
||||
#![recursion_limit = "300"] |
|
||||
#![deny(missing_docs)] |
|
||||
#![allow(unused_doc_comments)] |
|
||||
#![allow(renamed_and_removed_lints)] |
|
||||
|
|
||||
#[macro_use] |
|
||||
extern crate error_chain; |
|
||||
#[macro_use] |
|
||||
extern crate serde_derive; |
|
||||
extern crate serde_json; |
|
||||
extern crate serde; |
|
||||
extern crate base64; |
|
||||
extern crate ring; |
|
||||
extern crate untrusted; |
|
||||
extern crate chrono; |
|
||||
|
|
||||
/// All the errors, generated using error-chain
|
|
||||
pub mod errors; |
|
||||
mod header; |
|
||||
mod crypto; |
|
||||
mod serialization; |
|
||||
mod validation; |
|
||||
|
|
||||
pub use header::Header; |
|
||||
pub use crypto::{ |
|
||||
Algorithm, |
|
||||
sign, |
|
||||
verify, |
|
||||
}; |
|
||||
pub use validation::Validation; |
|
||||
pub use serialization::TokenData; |
|
||||
|
|
||||
|
|
||||
use serde::de::DeserializeOwned; |
|
||||
use serde::ser::Serialize; |
|
||||
|
|
||||
use errors::{Result, ErrorKind}; |
|
||||
use serialization::{from_jwt_part, from_jwt_part_claims, to_jwt_part}; |
|
||||
use validation::{validate}; |
|
||||
|
|
||||
|
|
||||
/// Encode the header and claims given and sign the payload using the algorithm from the header and the key
|
|
||||
///
|
|
||||
/// ```rust,ignore
|
|
||||
/// #[macro_use]
|
|
||||
/// extern crate serde_derive;
|
|
||||
/// use jsonwebtoken::{encode, Algorithm, Header};
|
|
||||
///
|
|
||||
/// /// #[derive(Debug, Serialize, Deserialize)]
|
|
||||
/// struct Claims {
|
|
||||
/// sub: String,
|
|
||||
/// company: String
|
|
||||
/// }
|
|
||||
///
|
|
||||
/// let my_claims = Claims {
|
|
||||
/// sub: "b@b.com".to_owned(),
|
|
||||
/// company: "ACME".to_owned()
|
|
||||
/// };
|
|
||||
///
|
|
||||
/// // my_claims is a struct that implements Serialize
|
|
||||
/// // This will create a JWT using HS256 as algorithm
|
|
||||
/// let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap();
|
|
||||
/// ```
|
|
||||
pub fn encode<T: Serialize>(header: &Header, claims: &T, key: &[u8]) -> Result<String> { |
|
||||
let encoded_header = to_jwt_part(&header)?; |
|
||||
let encoded_claims = to_jwt_part(&claims)?; |
|
||||
let signing_input = [encoded_header.as_ref(), encoded_claims.as_ref()].join("."); |
|
||||
let signature = sign(&*signing_input, key.as_ref(), header.alg)?; |
|
||||
|
|
||||
Ok([signing_input, signature].join(".")) |
|
||||
} |
|
||||
|
|
||||
/// Used in decode: takes the result of a rsplit and ensure we only get 2 parts
|
|
||||
/// Errors if we don't
|
|
||||
macro_rules! expect_two { |
|
||||
($iter:expr) => {{ |
|
||||
let mut i = $iter; |
|
||||
match (i.next(), i.next(), i.next()) { |
|
||||
(Some(first), Some(second), None) => (first, second), |
|
||||
_ => return Err(ErrorKind::InvalidToken.into()) |
|
||||
} |
|
||||
}} |
|
||||
} |
|
||||
|
|
||||
/// Decode a token into a struct containing 2 fields: `claims` and `header`.
|
|
||||
///
|
|
||||
/// If the token or its signature is invalid or the claims fail validation, it will return an error.
|
|
||||
///
|
|
||||
/// ```rust,ignore
|
|
||||
/// #[macro_use]
|
|
||||
/// extern crate serde_derive;
|
|
||||
/// use jsonwebtoken::{decode, Validation, Algorithm};
|
|
||||
///
|
|
||||
/// #[derive(Debug, Serialize, Deserialize)]
|
|
||||
/// struct Claims {
|
|
||||
/// sub: String,
|
|
||||
/// company: String
|
|
||||
/// }
|
|
||||
///
|
|
||||
/// let token = "a.jwt.token".to_string();
|
|
||||
/// // Claims is a struct that implements Deserialize
|
|
||||
/// let token_data = decode::<Claims>(&token, "secret", &Validation::new(Algorithm::HS256));
|
|
||||
/// ```
|
|
||||
pub fn decode<T: DeserializeOwned>(token: &str, key: &[u8], validation: &Validation) -> Result<TokenData<T>> { |
|
||||
let (signature, signing_input) = expect_two!(token.rsplitn(2, '.')); |
|
||||
let (claims, header) = expect_two!(signing_input.rsplitn(2, '.')); |
|
||||
let header: Header = from_jwt_part(header)?; |
|
||||
|
|
||||
if !verify(signature, signing_input, key, header.alg)? { |
|
||||
return Err(ErrorKind::InvalidSignature.into()); |
|
||||
} |
|
||||
|
|
||||
if !validation.algorithms.contains(&header.alg) { |
|
||||
return Err(ErrorKind::InvalidAlgorithm.into()); |
|
||||
} |
|
||||
|
|
||||
let (decoded_claims, claims_map): (T, _) = from_jwt_part_claims(claims)?; |
|
||||
|
|
||||
validate(&claims_map, validation)?; |
|
||||
|
|
||||
Ok(TokenData { header: header, claims: decoded_claims }) |
|
||||
} |
|
||||
|
|
||||
/// Decode a token and return the Header. This is not doing any kind of validation: it is meant to be
|
|
||||
/// used when you don't know which `alg` the token is using and want to find out.
|
|
||||
///
|
|
||||
/// If the token has an invalid format, it will return an error.
|
|
||||
///
|
|
||||
/// ```rust,ignore
|
|
||||
/// use jsonwebtoken::decode_header;
|
|
||||
///
|
|
||||
/// let token = "a.jwt.token".to_string();
|
|
||||
/// let header = decode_header(&token);
|
|
||||
/// ```
|
|
||||
pub fn decode_header(token: &str) -> Result<Header> { |
|
||||
let (_, signing_input) = expect_two!(token.rsplitn(2, '.')); |
|
||||
let (_, header) = expect_two!(signing_input.rsplitn(2, '.')); |
|
||||
from_jwt_part(header) |
|
||||
} |
|
@ -1,42 +0,0 @@ |
|||||
use base64; |
|
||||
use serde::de::DeserializeOwned; |
|
||||
use serde::ser::Serialize; |
|
||||
use serde_json::{from_str, to_string, Value}; |
|
||||
use serde_json::map::Map; |
|
||||
|
|
||||
use errors::{Result}; |
|
||||
use header::Header; |
|
||||
|
|
||||
|
|
||||
/// The return type of a successful call to decode
|
|
||||
#[derive(Debug)] |
|
||||
pub struct TokenData<T> { |
|
||||
/// The decoded JWT header
|
|
||||
pub header: Header, |
|
||||
/// The decoded JWT claims
|
|
||||
pub claims: T |
|
||||
} |
|
||||
|
|
||||
/// Serializes to JSON and encodes to base64
|
|
||||
pub fn to_jwt_part<T: Serialize>(input: &T) -> Result<String> { |
|
||||
let encoded = to_string(input)?; |
|
||||
Ok(base64::encode_config(encoded.as_bytes(), base64::URL_SAFE_NO_PAD)) |
|
||||
} |
|
||||
|
|
||||
/// Decodes from base64 and deserializes from JSON to a struct
|
|
||||
pub fn from_jwt_part<B: AsRef<str>, T: DeserializeOwned>(encoded: B) -> Result<T> { |
|
||||
let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?; |
|
||||
let s = String::from_utf8(decoded)?; |
|
||||
|
|
||||
Ok(from_str(&s)?) |
|
||||
} |
|
||||
|
|
||||
/// Decodes from base64 and deserializes from JSON to a struct AND a hashmap
|
|
||||
pub fn from_jwt_part_claims<B: AsRef<str>, T: DeserializeOwned>(encoded: B) -> Result<(T, Map<String, Value>)> { |
|
||||
let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?; |
|
||||
let s = String::from_utf8(decoded)?; |
|
||||
|
|
||||
let claims: T = from_str(&s)?; |
|
||||
let map: Map<_,_> = from_str(&s)?; |
|
||||
Ok((claims, map)) |
|
||||
} |
|
@ -1,377 +0,0 @@ |
|||||
use chrono::Utc; |
|
||||
use serde::ser::Serialize; |
|
||||
use serde_json::{Value, from_value, to_value}; |
|
||||
use serde_json::map::Map; |
|
||||
|
|
||||
use errors::{Result, ErrorKind}; |
|
||||
use crypto::Algorithm; |
|
||||
|
|
||||
|
|
||||
/// Contains the various validations that are applied after decoding a token.
|
|
||||
///
|
|
||||
/// All time validation happen on UTC timestamps.
|
|
||||
///
|
|
||||
/// ```rust
|
|
||||
/// use jsonwebtoken::Validation;
|
|
||||
///
|
|
||||
/// // Default value
|
|
||||
/// let validation = Validation::default();
|
|
||||
///
|
|
||||
/// // Changing one parameter
|
|
||||
/// let mut validation = Validation {leeway: 60, ..Default::default()};
|
|
||||
///
|
|
||||
/// // Setting audience
|
|
||||
/// let mut validation = Validation::default();
|
|
||||
/// validation.set_audience(&"Me"); // string
|
|
||||
/// validation.set_audience(&["Me", "You"]); // array of strings
|
|
||||
/// ```
|
|
||||
#[derive(Debug, Clone, PartialEq)] |
|
||||
pub struct Validation { |
|
||||
/// Add some leeway (in seconds) to the `exp`, `iat` and `nbf` validation to
|
|
||||
/// account for clock skew.
|
|
||||
///
|
|
||||
/// Defaults to `0`.
|
|
||||
pub leeway: i64, |
|
||||
/// Whether to validate the `exp` field.
|
|
||||
///
|
|
||||
/// It will return an error if the time in the `exp` field is past.
|
|
||||
///
|
|
||||
/// Defaults to `true`.
|
|
||||
pub validate_exp: bool, |
|
||||
/// Whether to validate the `iat` field.
|
|
||||
///
|
|
||||
/// It will return an error if the time in the `iat` field is in the future.
|
|
||||
///
|
|
||||
/// Defaults to `true`.
|
|
||||
pub validate_iat: bool, |
|
||||
/// Whether to validate the `nbf` field.
|
|
||||
///
|
|
||||
/// It will return an error if the current timestamp is before the time in the `nbf` field.
|
|
||||
///
|
|
||||
/// Defaults to `true`.
|
|
||||
pub validate_nbf: bool, |
|
||||
/// If it contains a value, the validation will check that the `aud` field is the same as the
|
|
||||
/// one provided and will error otherwise.
|
|
||||
/// Since `aud` can be either a String or a Vec<String> in the JWT spec, you will need to use
|
|
||||
/// the [set_audience](struct.Validation.html#method.set_audience) method to set it.
|
|
||||
///
|
|
||||
/// Defaults to `None`.
|
|
||||
pub aud: Option<Value>, |
|
||||
/// If it contains a value, the validation will check that the `iss` field is the same as the
|
|
||||
/// one provided and will error otherwise.
|
|
||||
///
|
|
||||
/// Defaults to `None`.
|
|
||||
pub iss: Option<String>, |
|
||||
/// If it contains a value, the validation will check that the `sub` field is the same as the
|
|
||||
/// one provided and will error otherwise.
|
|
||||
///
|
|
||||
/// Defaults to `None`.
|
|
||||
pub sub: Option<String>, |
|
||||
/// If it contains a value, the validation will check that the `alg` of the header is contained
|
|
||||
/// in the ones provided and will error otherwise.
|
|
||||
///
|
|
||||
/// Defaults to `vec![Algorithm::HS256]`.
|
|
||||
pub algorithms: Vec<Algorithm>, |
|
||||
} |
|
||||
|
|
||||
impl Validation { |
|
||||
/// Create a default validation setup allowing the given alg
|
|
||||
pub fn new(alg: Algorithm) -> Validation { |
|
||||
let mut validation = Validation::default(); |
|
||||
validation.algorithms = vec![alg]; |
|
||||
validation |
|
||||
} |
|
||||
|
|
||||
/// Since `aud` can be either a String or an array of String in the JWT spec, this method will take
|
|
||||
/// care of serializing the value.
|
|
||||
pub fn set_audience<T: Serialize>(&mut self, audience: &T) { |
|
||||
self.aud = Some(to_value(audience).unwrap()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
impl Default for Validation { |
|
||||
fn default() -> Validation { |
|
||||
Validation { |
|
||||
leeway: 0, |
|
||||
|
|
||||
validate_exp: true, |
|
||||
validate_iat: true, |
|
||||
validate_nbf: true, |
|
||||
|
|
||||
iss: None, |
|
||||
sub: None, |
|
||||
aud: None, |
|
||||
|
|
||||
algorithms: vec![Algorithm::HS256], |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
|
|
||||
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 let Some(exp) = claims.get("exp") { |
|
||||
if options.validate_exp && from_value::<i64>(exp.clone())? < now - options.leeway { |
|
||||
return Err(ErrorKind::ExpiredSignature.into()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
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 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()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
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()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if let Some(aud) = claims.get("aud") { |
|
||||
if let Some(ref correct_aud) = options.aud { |
|
||||
if aud != correct_aud { |
|
||||
return Err(ErrorKind::InvalidAudience.into()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
|
|
||||
#[cfg(test)] |
|
||||
mod tests { |
|
||||
use serde_json::{to_value}; |
|
||||
use serde_json::map::Map; |
|
||||
use chrono::Utc; |
|
||||
|
|
||||
use super::{validate, Validation}; |
|
||||
|
|
||||
use errors::ErrorKind; |
|
||||
|
|
||||
#[test] |
|
||||
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()); |
|
||||
assert!(res.is_ok()); |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
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()); |
|
||||
assert!(res.is_err()); |
|
||||
|
|
||||
match res.unwrap_err().kind() { |
|
||||
&ErrorKind::InvalidIssuedAt => (), |
|
||||
_ => assert!(false), |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
fn iat_in_future_but_in_leeway_ok() { |
|
||||
let mut claims = Map::new(); |
|
||||
claims.insert("iat".to_string(), to_value(Utc::now().timestamp() + 50).unwrap()); |
|
||||
let validation = Validation { |
|
||||
leeway: 1000 * 60, |
|
||||
..Default::default() |
|
||||
}; |
|
||||
let res = validate(&claims, &validation); |
|
||||
assert!(res.is_ok()); |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
fn exp_in_future_ok() { |
|
||||
let mut claims = Map::new(); |
|
||||
claims.insert("exp".to_string(), to_value(Utc::now().timestamp() + 10000).unwrap()); |
|
||||
let res = validate(&claims, &Validation::default()); |
|
||||
assert!(res.is_ok()); |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
fn exp_in_past_fails() { |
|
||||
let mut claims = Map::new(); |
|
||||
claims.insert("exp".to_string(), to_value(Utc::now().timestamp() - 100000).unwrap()); |
|
||||
let res = validate(&claims, &Validation::default()); |
|
||||
assert!(res.is_err()); |
|
||||
|
|
||||
match res.unwrap_err().kind() { |
|
||||
&ErrorKind::ExpiredSignature => (), |
|
||||
_ => assert!(false), |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
fn exp_in_past_but_in_leeway_ok() { |
|
||||
let mut claims = Map::new(); |
|
||||
claims.insert("exp".to_string(), to_value(Utc::now().timestamp() - 500).unwrap()); |
|
||||
let validation = Validation { |
|
||||
leeway: 1000 * 60, |
|
||||
..Default::default() |
|
||||
}; |
|
||||
let res = validate(&claims, &validation); |
|
||||
assert!(res.is_ok()); |
|
||||
} |
|
||||
|
|
||||
#[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()); |
|
||||
assert!(res.is_ok()); |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
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()); |
|
||||
assert!(res.is_err()); |
|
||||
|
|
||||
match res.unwrap_err().kind() { |
|
||||
&ErrorKind::ImmatureSignature => (), |
|
||||
_ => assert!(false), |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
fn nbf_in_future_but_in_leeway_ok() { |
|
||||
let mut claims = Map::new(); |
|
||||
claims.insert("nbf".to_string(), to_value(Utc::now().timestamp() + 500).unwrap()); |
|
||||
let validation = Validation { |
|
||||
leeway: 1000 * 60, |
|
||||
..Default::default() |
|
||||
}; |
|
||||
let res = validate(&claims, &validation); |
|
||||
assert!(res.is_ok()); |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
fn iss_ok() { |
|
||||
let mut claims = Map::new(); |
|
||||
claims.insert("iss".to_string(), to_value("Keats").unwrap()); |
|
||||
let validation = Validation { |
|
||||
iss: Some("Keats".to_string()), |
|
||||
..Default::default() |
|
||||
}; |
|
||||
let res = validate(&claims, &validation); |
|
||||
assert!(res.is_ok()); |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
fn iss_not_matching_fails() { |
|
||||
let mut claims = Map::new(); |
|
||||
claims.insert("iss".to_string(), to_value("Hacked").unwrap()); |
|
||||
let validation = Validation { |
|
||||
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 sub_ok() { |
|
||||
let mut claims = Map::new(); |
|
||||
claims.insert("sub".to_string(), to_value("Keats").unwrap()); |
|
||||
let validation = Validation { |
|
||||
sub: Some("Keats".to_string()), |
|
||||
..Default::default() |
|
||||
}; |
|
||||
let res = validate(&claims, &validation); |
|
||||
assert!(res.is_ok()); |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
fn sub_not_matching_fails() { |
|
||||
let mut claims = Map::new(); |
|
||||
claims.insert("sub".to_string(), to_value("Hacked").unwrap()); |
|
||||
let validation = Validation { |
|
||||
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 aud_string_ok() { |
|
||||
let mut claims = Map::new(); |
|
||||
claims.insert("aud".to_string(), to_value("Everyone").unwrap()); |
|
||||
let mut validation = Validation::default(); |
|
||||
validation.set_audience(&"Everyone"); |
|
||||
let res = validate(&claims, &validation); |
|
||||
assert!(res.is_ok()); |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
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(); |
|
||||
validation.set_audience(&["UserA", "UserB"]); |
|
||||
let res = validate(&claims, &validation); |
|
||||
assert!(res.is_ok()); |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
fn aud_type_mismatch_fails() { |
|
||||
let mut claims = Map::new(); |
|
||||
claims.insert("aud".to_string(), to_value("Everyone").unwrap()); |
|
||||
let mut validation = Validation::default(); |
|
||||
validation.set_audience(&["UserA", "UserB"]); |
|
||||
let res = validate(&claims, &validation); |
|
||||
assert!(res.is_err()); |
|
||||
|
|
||||
match res.unwrap_err().kind() { |
|
||||
&ErrorKind::InvalidAudience => (), |
|
||||
_ => assert!(false), |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
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(); |
|
||||
validation.set_audience(&"None"); |
|
||||
let res = validate(&claims, &validation); |
|
||||
assert!(res.is_err()); |
|
||||
|
|
||||
match res.unwrap_err().kind() { |
|
||||
&ErrorKind::InvalidAudience => (), |
|
||||
_ => assert!(false), |
|
||||
}; |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,3 @@ |
|||||
|
ALTER TABLE attachments |
||||
|
ADD COLUMN |
||||
|
key TEXT; |
@ -1 +1 @@ |
|||||
nightly-2018-10-03 |
nightly-2018-12-01 |
||||
|
Loading…
Reference in new issue