From 0aa3aa6faa12adeda909400f630756b2e2e4ed0c Mon Sep 17 00:00:00 2001 From: Thomas Gideon Date: Wed, 5 Apr 2017 16:32:41 -0400 Subject: [PATCH] Verify not before and expiration claims, if present --- Cargo.toml | 3 +- src/lib.rs | 54 +++++++++++++++++++++++++++++---- src/payload.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f13a6b5..61c320e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "medallion" -version = "2.0.2" +version = "2.1.0" authors = ["Thomas Gideon "] description = "JWT library for rust using serde, serde_json and openssl" homepage = "http://github.com/commandline/medallion" @@ -16,3 +16,4 @@ openssl = "0.9" serde = "0.9" serde_json = "0.9" serde_derive = "0.9" +time = "0.1" diff --git a/src/lib.rs b/src/lib.rs index 1ef0ae5..b67bca9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_json; +extern crate time; use serde::{Serialize, Deserialize}; pub use error::Error; @@ -72,7 +73,7 @@ impl Token let sig = pieces[0]; let data = pieces[1]; - Ok(crypt::verify(sig, data, key, &self.header.alg)?) + Ok(self.payload.verify() && crypt::verify(sig, data, key, &self.header.alg)?) } /// Generate the signed token from a key with the specific algorithm as a url-safe, base64 @@ -98,8 +99,10 @@ impl PartialEq for Token #[cfg(test)] mod tests { - use {DefaultToken, Header}; + use {DefaultPayload, DefaultToken, Header}; use crypt::tests::load_pem; + use std::default::Default; + use time::{self, Duration, Tm}; use super::Algorithm::{HS256, RS512}; #[test] @@ -109,15 +112,20 @@ mod tests { TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"; let token = DefaultToken::<()>::parse(raw).unwrap(); - { - assert_eq!(token.header.alg, HS256); - } + assert_eq!(token.header.alg, HS256); assert!(token.verify("secret".as_bytes()).unwrap()); } #[test] pub fn roundtrip_hmac() { - let token: DefaultToken<()> = Default::default(); + let now = time::now(); + let header: Header<()> = Default::default(); + let payload = DefaultPayload { + nbf: Some(now.to_timespec().sec as u64), + exp: Some((now + Duration::minutes(5)).to_timespec().sec as u64), + ..Default::default() + }; + let token = DefaultToken::new(header, payload); let key = "secret".as_bytes(); let raw = token.sign(key).unwrap(); let same = DefaultToken::parse(&*raw).unwrap(); @@ -126,6 +134,30 @@ mod tests { assert!(same.verify(key).unwrap()); } + #[test] + pub fn roundtrip_expired() { + let now = time::now(); + let token = create_for_range(now, now + Duration::minutes(-5)); + let key = "secret".as_bytes(); + let raw = token.sign(key).unwrap(); + let same = DefaultToken::parse(&*raw).unwrap(); + + assert_eq!(token, same); + assert_eq!(false, same.verify(key).unwrap()); + } + + #[test] + pub fn roundtrip_not_yet_valid() { + let now = time::now(); + let token = create_for_range(now + Duration::minutes(5), now + Duration::minutes(10)); + let key = "secret".as_bytes(); + let raw = token.sign(key).unwrap(); + let same = DefaultToken::parse(&*raw).unwrap(); + + assert_eq!(token, same); + assert_eq!(false, same.verify(key).unwrap()); + } + #[test] pub fn roundtrip_rsa() { let header: Header<()> = Header { alg: RS512, ..Default::default() }; @@ -138,4 +170,14 @@ mod tests { let public_key = load_pem("./examples/publicKey.pub").unwrap(); assert!(same.verify(public_key.as_bytes()).unwrap()); } + + fn create_for_range(nbf: Tm, exp: Tm) -> DefaultToken<()> { + let header: Header<()> = Default::default(); + let payload = DefaultPayload { + nbf: Some(nbf.to_timespec().sec as u64), + exp: Some(exp.to_timespec().sec as u64), + ..Default::default() + }; + DefaultToken::new(header, payload) + } } diff --git a/src/payload.rs b/src/payload.rs index 0b2b0e5..47b4ecf 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json; use serde_json::value::Value; use super::Result; +use time::{self, Timespec}; /// A default claim set, including the standard, or registered, claims and the ability to specify /// your own as custom claims. @@ -80,11 +81,25 @@ impl Payload { } } + + pub fn verify(&self) -> bool { + let now = time::now().to_timespec(); + let nbf_verified = match self.nbf { + Some(nbf_sec) => Timespec::new(nbf_sec as i64, 0) < now, + None => true, + }; + let exp_verified = match self.exp { + Some(exp_sec) => now < Timespec::new(exp_sec as i64, 0), + None => true, + }; + nbf_verified && exp_verified + } } #[cfg(test)] mod tests { use std::default::Default; + use time::{self, Duration}; use super::{Payload, DefaultPayload}; #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] @@ -141,6 +156,72 @@ mod tests { assert_eq!(payload, Payload::::from_base64(&*enc).unwrap()); } + #[test] + fn verify_nbf() { + let payload = create_with_nbf(5); + assert!(payload.verify()); + } + + #[test] + fn fail_nbf() { + let payload = create_with_nbf(-5); + assert_eq!(false, payload.verify()); + } + + #[test] + fn verify_exp() { + let payload = create_with_exp(5); + assert!(payload.verify()); + } + + #[test] + fn fail_exp() { + let payload = create_with_exp(-5); + assert_eq!(false, payload.verify()); + } + + #[test] + fn verify_nbf_exp() { + let payload = create_with_nbf_exp(5, 5); + assert!(payload.verify()); + } + + #[test] + fn fail_nbf_exp() { + let payload = create_with_nbf_exp(-5, -5); + assert_eq!(false, payload.verify()); + let payload = create_with_nbf_exp(5, -5); + assert_eq!(false, payload.verify()); + let payload = create_with_nbf_exp(-5, 5); + assert_eq!(false, payload.verify()); + } + + fn create_with_nbf(offset: i64) -> DefaultPayload { + let nbf = (time::now() - Duration::minutes(offset)).to_timespec().sec; + DefaultPayload { + nbf: Some(nbf as u64), + ..Default::default() + } + } + + fn create_with_exp(offset: i64) -> DefaultPayload { + let exp = (time::now() + Duration::minutes(offset)).to_timespec().sec; + DefaultPayload { + exp: Some(exp as u64), + ..Default::default() + } + } + + fn create_with_nbf_exp(nbf_offset: i64, exp_offset: i64) -> DefaultPayload { + let nbf = (time::now() - Duration::minutes(nbf_offset)).to_timespec().sec; + let exp = (time::now() + Duration::minutes(exp_offset)).to_timespec().sec; + DefaultPayload { + nbf: Some(nbf as u64), + exp: Some(exp as u64), + ..Default::default() + } + } + fn create_default() -> DefaultPayload { DefaultPayload { aud: Some("login_service".into()),