Verify not before and expiration claims, if present
This commit is contained in:
parent
179b2f8554
commit
0aa3aa6faa
3 changed files with 131 additions and 7 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "medallion"
|
name = "medallion"
|
||||||
version = "2.0.2"
|
version = "2.1.0"
|
||||||
authors = ["Thomas Gideon <cmdln@thecommandline.net>"]
|
authors = ["Thomas Gideon <cmdln@thecommandline.net>"]
|
||||||
description = "JWT library for rust using serde, serde_json and openssl"
|
description = "JWT library for rust using serde, serde_json and openssl"
|
||||||
homepage = "http://github.com/commandline/medallion"
|
homepage = "http://github.com/commandline/medallion"
|
||||||
|
@ -16,3 +16,4 @@ openssl = "0.9"
|
||||||
serde = "0.9"
|
serde = "0.9"
|
||||||
serde_json = "0.9"
|
serde_json = "0.9"
|
||||||
serde_derive = "0.9"
|
serde_derive = "0.9"
|
||||||
|
time = "0.1"
|
||||||
|
|
54
src/lib.rs
54
src/lib.rs
|
@ -7,6 +7,7 @@ extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
extern crate time;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
@ -72,7 +73,7 @@ impl<H, C> Token<H, C>
|
||||||
let sig = pieces[0];
|
let sig = pieces[0];
|
||||||
let data = pieces[1];
|
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
|
/// Generate the signed token from a key with the specific algorithm as a url-safe, base64
|
||||||
|
@ -98,8 +99,10 @@ impl<H, C> PartialEq for Token<H, C>
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use {DefaultToken, Header};
|
use {DefaultPayload, DefaultToken, Header};
|
||||||
use crypt::tests::load_pem;
|
use crypt::tests::load_pem;
|
||||||
|
use std::default::Default;
|
||||||
|
use time::{self, Duration, Tm};
|
||||||
use super::Algorithm::{HS256, RS512};
|
use super::Algorithm::{HS256, RS512};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -109,15 +112,20 @@ mod tests {
|
||||||
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
|
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
|
||||||
let token = DefaultToken::<()>::parse(raw).unwrap();
|
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());
|
assert!(token.verify("secret".as_bytes()).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn roundtrip_hmac() {
|
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 key = "secret".as_bytes();
|
||||||
let raw = token.sign(key).unwrap();
|
let raw = token.sign(key).unwrap();
|
||||||
let same = DefaultToken::parse(&*raw).unwrap();
|
let same = DefaultToken::parse(&*raw).unwrap();
|
||||||
|
@ -126,6 +134,30 @@ mod tests {
|
||||||
assert!(same.verify(key).unwrap());
|
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]
|
#[test]
|
||||||
pub fn roundtrip_rsa() {
|
pub fn roundtrip_rsa() {
|
||||||
let header: Header<()> = Header { alg: RS512, ..Default::default() };
|
let header: Header<()> = Header { alg: RS512, ..Default::default() };
|
||||||
|
@ -138,4 +170,14 @@ mod tests {
|
||||||
let public_key = load_pem("./examples/publicKey.pub").unwrap();
|
let public_key = load_pem("./examples/publicKey.pub").unwrap();
|
||||||
assert!(same.verify(public_key.as_bytes()).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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use serde_json::value::Value;
|
use serde_json::value::Value;
|
||||||
use super::Result;
|
use super::Result;
|
||||||
|
use time::{self, Timespec};
|
||||||
|
|
||||||
/// A default claim set, including the standard, or registered, claims and the ability to specify
|
/// A default claim set, including the standard, or registered, claims and the ability to specify
|
||||||
/// your own as custom claims.
|
/// your own as custom claims.
|
||||||
|
@ -80,11 +81,25 @@ impl<T: Serialize + Deserialize + PartialEq> Payload<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
use time::{self, Duration};
|
||||||
use super::{Payload, DefaultPayload};
|
use super::{Payload, DefaultPayload};
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
|
@ -141,6 +156,72 @@ mod tests {
|
||||||
assert_eq!(payload, Payload::<CustomClaims>::from_base64(&*enc).unwrap());
|
assert_eq!(payload, Payload::<CustomClaims>::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 {
|
fn create_default() -> DefaultPayload {
|
||||||
DefaultPayload {
|
DefaultPayload {
|
||||||
aud: Some("login_service".into()),
|
aud: Some("login_service".into()),
|
||||||
|
|
Loading…
Reference in a new issue