#![crate_name = "medallion"] #![crate_type = "lib"] #![doc(html_root_url = "https://commandline.github.io/medallion/")] extern crate base64; extern crate openssl; extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_json; extern crate time; use serde::Serialize; use serde::de::DeserializeOwned; pub use error::Error; pub use header::Header; pub use header::Algorithm; pub use payload::{Payload, DefaultPayload}; pub mod error; mod header; mod payload; mod crypt; pub type Result = std::result::Result; /// A convenient type that bins the same type parameter for the custom claims, an empty tuple, as /// DefaultPayload so that the two aliases may be used together to reduce boilerplate when not /// custom claims are needed. pub type DefaultToken = Token; /// Main struct representing a JSON Web Token, composed of a header and a set of claims. #[derive(Debug, Default)] pub struct Token { raw: Option, pub header: Header, pub payload: Payload, } /// Provide the ability to parse a token, verify it and sign/serialize it. impl Token where H: Serialize + DeserializeOwned, C: Serialize + DeserializeOwned { pub fn new(header: Header, payload: Payload) -> Token { Token { raw: None, header: header, payload: payload, } } /// Parse a token from a string. pub fn parse(raw: &str) -> Result> { let pieces: Vec<_> = raw.split('.').collect(); Ok(Token { raw: Some(raw.into()), header: Header::from_base64(pieces[0])?, payload: Payload::from_base64(pieces[1])?, }) } /// Verify a token with a key and the token's specific algorithm. pub fn verify(&self, key: &[u8]) -> Result { let raw = match self.raw { Some(ref s) => s, None => return Ok(false), }; let pieces: Vec<_> = raw.rsplitn(2, '.').collect(); let sig = pieces[0]; let data = pieces[1]; 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 /// string. pub fn sign(&self, key: &[u8]) -> Result { let header = self.header.to_base64()?; let payload = self.payload.to_base64()?; let data = format!("{}.{}", header, payload); let sig = crypt::sign(&*data, key, &self.header.alg)?; Ok(format!("{}.{}", data, sig)) } } impl PartialEq for Token where H: PartialEq, C: PartialEq { fn eq(&self, other: &Token) -> bool { self.header == other.header && self.payload == other.payload } } #[cfg(test)] mod tests { use {DefaultPayload, DefaultToken, Header}; use crypt::tests::load_pem; use std::default::Default; use time::{self, Duration, Tm}; use super::Algorithm::{HS256, RS512}; #[test] pub fn raw_data() { let raw = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.\ eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.\ TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"; let token = DefaultToken::<()>::parse(raw).unwrap(); assert_eq!(token.header.alg, HS256); assert!(token.verify("secret".as_bytes()).unwrap()); } #[test] pub fn roundtrip_hmac() { 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(); assert_eq!(token, same); 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() }; let token = DefaultToken { header: header, ..Default::default() }; let private_key = load_pem("./examples/privateKey.pem").unwrap(); let raw = token.sign(private_key.as_bytes()).unwrap(); let same = DefaultToken::parse(&*raw).unwrap(); assert_eq!(token, same); 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) } }