2017-02-15 20:42:06 +00:00
|
|
|
#![crate_name = "medallion"]
|
|
|
|
#![crate_type = "lib"]
|
|
|
|
#![doc(html_root_url = "https://commandline.github.io/medallion/")]
|
2017-02-13 23:40:07 +00:00
|
|
|
extern crate base64;
|
|
|
|
extern crate openssl;
|
|
|
|
extern crate serde;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate serde_derive;
|
|
|
|
extern crate serde_json;
|
|
|
|
|
2017-02-14 18:36:45 +00:00
|
|
|
use base64::{decode_config, encode_config, URL_SAFE};
|
2017-02-13 23:40:07 +00:00
|
|
|
use serde::{Serialize, Deserialize};
|
|
|
|
pub use error::Error;
|
|
|
|
pub use header::DefaultHeader;
|
|
|
|
pub use header::Algorithm;
|
|
|
|
pub use claims::Claims;
|
|
|
|
pub use claims::Registered;
|
|
|
|
|
|
|
|
pub mod error;
|
|
|
|
pub mod header;
|
|
|
|
pub mod claims;
|
|
|
|
mod crypt;
|
|
|
|
|
2017-02-17 16:53:12 +00:00
|
|
|
pub type Result<T> = std::result::Result<T, Error>;
|
|
|
|
|
2017-02-15 20:41:52 +00:00
|
|
|
/// Main struct representing a JSON Web Token, composed of a header and a set of claims.
|
2017-02-13 23:40:07 +00:00
|
|
|
#[derive(Debug, Default)]
|
|
|
|
pub struct Token<H, C>
|
|
|
|
where H: Component, C: Component {
|
|
|
|
raw: Option<String>,
|
|
|
|
pub header: H,
|
|
|
|
pub claims: C,
|
|
|
|
}
|
|
|
|
|
2017-02-15 20:41:52 +00:00
|
|
|
/// Any header type must implement this trait so that signing and verification work.
|
2017-02-13 23:40:07 +00:00
|
|
|
pub trait Header {
|
|
|
|
fn alg(&self) -> &header::Algorithm;
|
|
|
|
}
|
|
|
|
|
2017-02-15 20:41:52 +00:00
|
|
|
/// Any header or claims type must implement this trait in order to serialize and deserialize
|
|
|
|
/// correctly.
|
2017-02-13 23:40:07 +00:00
|
|
|
pub trait Component: Sized {
|
2017-02-17 16:53:12 +00:00
|
|
|
fn from_base64(raw: &str) -> Result<Self>;
|
|
|
|
fn to_base64(&self) -> Result<String>;
|
2017-02-13 23:40:07 +00:00
|
|
|
}
|
|
|
|
|
2017-02-15 20:41:52 +00:00
|
|
|
/// Provide a default implementation that should work in almost all cases.
|
2017-02-13 23:40:07 +00:00
|
|
|
impl<T> Component for T
|
|
|
|
where T: Serialize + Deserialize + Sized {
|
|
|
|
|
|
|
|
/// Parse from a string.
|
2017-02-17 16:53:12 +00:00
|
|
|
fn from_base64(raw: &str) -> Result<T> {
|
2017-02-17 17:39:28 +00:00
|
|
|
let data = decode_config(raw, URL_SAFE)?;
|
|
|
|
let s = String::from_utf8(data)?;
|
|
|
|
Ok(serde_json::from_str(&*s)?)
|
2017-02-13 23:40:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Encode to a string.
|
2017-02-17 16:53:12 +00:00
|
|
|
fn to_base64(&self) -> Result<String> {
|
2017-02-17 17:39:28 +00:00
|
|
|
let s = serde_json::to_string(&self)?;
|
2017-02-13 23:40:07 +00:00
|
|
|
let enc = encode_config((&*s).as_bytes(), URL_SAFE);
|
|
|
|
Ok(enc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-15 20:41:52 +00:00
|
|
|
/// Provide the ability to parse a token, verify it and sign/serialize it.
|
2017-02-13 23:40:07 +00:00
|
|
|
impl<H, C> Token<H, C>
|
|
|
|
where H: Component + Header, C: Component {
|
|
|
|
pub fn new(header: H, claims: C) -> Token<H, C> {
|
|
|
|
Token {
|
|
|
|
raw: None,
|
|
|
|
header: header,
|
|
|
|
claims: claims,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse a token from a string.
|
2017-02-17 16:53:12 +00:00
|
|
|
pub fn parse(raw: &str) -> Result<Token<H, C>> {
|
2017-02-13 23:40:07 +00:00
|
|
|
let pieces: Vec<_> = raw.split('.').collect();
|
|
|
|
|
|
|
|
Ok(Token {
|
|
|
|
raw: Some(raw.into()),
|
2017-02-17 17:39:28 +00:00
|
|
|
header: Component::from_base64(pieces[0])?,
|
|
|
|
claims: Component::from_base64(pieces[1])?,
|
2017-02-13 23:40:07 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-02-15 20:41:52 +00:00
|
|
|
/// Verify a token with a key and the token's specific algorithm.
|
2017-02-17 16:53:12 +00:00
|
|
|
pub fn verify(&self, key: &[u8]) -> Result<bool> {
|
2017-02-13 23:40:07 +00:00
|
|
|
let raw = match self.raw {
|
|
|
|
Some(ref s) => s,
|
2017-02-17 16:53:12 +00:00
|
|
|
None => return Ok(false),
|
2017-02-13 23:40:07 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let pieces: Vec<_> = raw.rsplitn(2, '.').collect();
|
|
|
|
let sig = pieces[0];
|
|
|
|
let data = pieces[1];
|
|
|
|
|
2017-02-17 16:53:12 +00:00
|
|
|
Ok(crypt::verify(sig, data, key, &self.header.alg())?)
|
2017-02-13 23:40:07 +00:00
|
|
|
}
|
|
|
|
|
2017-02-15 20:41:52 +00:00
|
|
|
/// Generate the signed token from a key with the specific algorithm as a url-safe, base64
|
|
|
|
/// string.
|
2017-02-17 16:53:12 +00:00
|
|
|
pub fn signed(&self, key: &[u8]) -> Result<String> {
|
2017-02-17 17:39:28 +00:00
|
|
|
let header = Component::to_base64(&self.header)?;
|
|
|
|
let claims = self.claims.to_base64()?;
|
2017-02-13 23:40:07 +00:00
|
|
|
let data = format!("{}.{}", header, claims);
|
|
|
|
|
2017-02-17 16:53:12 +00:00
|
|
|
let sig = crypt::sign(&*data, key, &self.header.alg())?;
|
2017-02-13 23:40:07 +00:00
|
|
|
Ok(format!("{}.{}", data, sig))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<H, C> PartialEq for Token<H, C>
|
|
|
|
where H: Component + PartialEq, C: Component + PartialEq{
|
|
|
|
fn eq(&self, other: &Token<H, C>) -> bool {
|
|
|
|
self.header == other.header &&
|
|
|
|
self.claims == other.claims
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use Claims;
|
|
|
|
use Token;
|
2017-02-17 17:54:50 +00:00
|
|
|
use crypt::tests::load_pem;
|
2017-02-13 23:40:07 +00:00
|
|
|
use header::Algorithm::{HS256,RS512};
|
|
|
|
use header::DefaultHeader;
|
|
|
|
|
2017-02-14 18:33:00 +00:00
|
|
|
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
|
|
|
struct EmptyClaim { }
|
|
|
|
|
2017-02-13 23:40:07 +00:00
|
|
|
#[test]
|
|
|
|
pub fn raw_data() {
|
|
|
|
let raw = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
|
2017-02-14 18:33:00 +00:00
|
|
|
let token = Token::<DefaultHeader, Claims<EmptyClaim>>::parse(raw).unwrap();
|
2017-02-13 23:40:07 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
assert_eq!(token.header.alg, HS256);
|
|
|
|
}
|
2017-02-17 16:53:12 +00:00
|
|
|
assert!(token.verify("secret".as_bytes()).unwrap());
|
2017-02-13 23:40:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2017-02-15 18:50:12 +00:00
|
|
|
pub fn roundtrip_hmac() {
|
2017-02-14 18:33:00 +00:00
|
|
|
let token: Token<DefaultHeader, Claims<EmptyClaim>> = Default::default();
|
2017-02-13 23:40:07 +00:00
|
|
|
let key = "secret".as_bytes();
|
|
|
|
let raw = token.signed(key).unwrap();
|
|
|
|
let same = Token::parse(&*raw).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(token, same);
|
2017-02-17 16:53:12 +00:00
|
|
|
assert!(same.verify(key).unwrap());
|
2017-02-13 23:40:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn roundtrip_rsa() {
|
2017-02-14 18:33:00 +00:00
|
|
|
let token: Token<DefaultHeader, Claims<EmptyClaim>> = Token {
|
2017-02-13 23:40:07 +00:00
|
|
|
header: DefaultHeader {
|
|
|
|
alg: RS512,
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
..Default::default()
|
|
|
|
};
|
2017-02-17 17:54:50 +00:00
|
|
|
let private_key = load_pem("./examples/privateKey.pem").unwrap();
|
2017-02-13 23:40:07 +00:00
|
|
|
let raw = token.signed(private_key.as_bytes()).unwrap();
|
|
|
|
let same = Token::parse(&*raw).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(token, same);
|
2017-02-17 17:54:50 +00:00
|
|
|
let public_key = load_pem("./examples/publicKey.pub").unwrap();
|
2017-02-17 16:53:12 +00:00
|
|
|
assert!(same.verify(public_key.as_bytes()).unwrap());
|
2017-02-13 23:40:07 +00:00
|
|
|
}
|
|
|
|
}
|