Daniel García
6 years ago
31 changed files with 712 additions and 1481 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), |
|||
}; |
|||
} |
|||
} |
@ -1 +1 @@ |
|||
nightly-2018-10-03 |
|||
nightly-2018-10-31 |
|||
|
Loading…
Reference in new issue