Initial re-factor

This commit is contained in:
Thomas Gideon 2017-02-13 18:40:07 -05:00
commit 29ad0d721d
14 changed files with 743 additions and 0 deletions

97
src/claims.rs Normal file
View file

@ -0,0 +1,97 @@
use base64::{decode, encode_config, URL_SAFE};
use Component;
use error::Error;
use serde::{Deserialize, Serialize};
use serde_json;
use serde_json::value::{from_value, to_value, Map, Value};
#[derive(Debug, Default, PartialEq)]
pub struct Claims {
pub reg: Registered,
pub private: Value
}
#[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Registered {
pub iss: Option<String>,
pub sub: Option<String>,
pub aud: Option<String>,
pub exp: Option<u64>,
pub nbf: Option<u64>,
pub iat: Option<u64>,
pub jti: Option<String>,
}
/// JWT Claims. Registered claims are directly accessible via the `Registered`
/// struct embedded, while private fields are a map that contains `Json`
/// values.
impl Claims {
pub fn new(reg: Registered, private: Value) -> Claims {
Claims {
reg: reg,
private: private
}
}
}
impl Component for Claims {
fn from_base64(raw: &str) -> Result<Claims, Error> {
let data = try!(decode(raw));
let reg_claims: Registered = try!(serde_json::from_slice(&data));
let pri_claims: Value = try!(serde_json::from_slice(&data));
Ok(Claims{
reg: reg_claims,
private: pri_claims
})
}
fn to_base64(&self) -> Result<String, Error> {
let mut value = try!(serde_json::to_value(&self.reg));
let mut obj_value = &value.as_object_mut().unwrap();
// TODO iterate private claims and add to JSON Map
//let mut pri_value = self.private.as_object_mut().unwrap();
//obj_value.extend(pri_value.into_iter());
let s = try!(serde_json::to_string(&obj_value));
let enc = encode_config((&*s).as_bytes(), URL_SAFE);
Ok(enc)
}
}
#[cfg(test)]
mod tests {
use std::default::Default;
use claims::{Claims, Registered};
use Component;
#[test]
fn from_base64() {
let enc = "ew0KICAiaXNzIjogIm1pa2t5YW5nLmNvbSIsDQogICJleHAiOiAxMzAyMzE5MTAwLA0KICAibmFtZSI6ICJNaWNoYWVsIFlhbmciLA0KICAiYWRtaW4iOiB0cnVlDQp9";
let claims = Claims::from_base64(enc).unwrap();
assert_eq!(claims.reg.iss.unwrap(), "mikkyang.com");
assert_eq!(claims.reg.exp.unwrap(), 1302319100);
}
#[test]
fn multiple_types() {
let enc = "ew0KICAiaXNzIjogIm1pa2t5YW5nLmNvbSIsDQogICJleHAiOiAxMzAyMzE5MTAwLA0KICAibmFtZSI6ICJNaWNoYWVsIFlhbmciLA0KICAiYWRtaW4iOiB0cnVlDQp9";
let claims = Registered::from_base64(enc).unwrap();
assert_eq!(claims.iss.unwrap(), "mikkyang.com");
assert_eq!(claims.exp.unwrap(), 1302319100);
}
#[test]
fn roundtrip() {
let mut claims: Claims = Default::default();
claims.reg.iss = Some("mikkyang.com".into());
claims.reg.exp = Some(1302319100);
let enc = claims.to_base64().unwrap();
assert_eq!(claims, Claims::from_base64(&*enc).unwrap());
}
}

47
src/crypt.rs Normal file
View file

@ -0,0 +1,47 @@
use base64::{decode, encode_config, URL_SAFE};
use openssl::hash::MessageDigest;
use openssl::memcmp;
use openssl::pkey::PKey;
use openssl::rsa::Rsa;
use openssl::sign::{Signer, Verifier};
pub fn sign(data: &str, key: &[u8], digest: MessageDigest) -> String {
let secret_key = PKey::hmac(key).unwrap();
let mut signer = Signer::new(digest, &secret_key).unwrap();
signer.update(data.as_bytes()).unwrap();
let mac = signer.finish().unwrap();
encode_config(&mac, URL_SAFE)
}
pub fn sign_rsa(data: &str, key: &[u8], digest: MessageDigest) -> String {
let private_key = Rsa::private_key_from_pem(key).unwrap();
let pkey = PKey::from_rsa(private_key).unwrap();
let mut signer = Signer::new(digest, &pkey).unwrap();
signer.update(data.as_bytes()).unwrap();
let sig = signer.finish().unwrap();
encode_config(&sig, URL_SAFE)
}
pub fn verify(target: &str, data: &str, key: &[u8], digest: MessageDigest) -> bool {
let target_bytes: Vec<u8> = decode(target).unwrap();
let secret_key = PKey::hmac(key).unwrap();
let mut signer = Signer::new(digest, &secret_key).unwrap();
signer.update(data.as_bytes()).unwrap();
let mac = signer.finish().unwrap();
memcmp::eq(&mac, &target_bytes)
}
pub fn verify_rsa(signature: &str, data: &str, key: &[u8], digest: MessageDigest) -> bool {
let signature_bytes: Vec<u8> = decode(signature).unwrap();
let public_key = Rsa::public_key_from_pem(key).unwrap();
let pkey = PKey::from_rsa(public_key).unwrap();
let mut verifier = Verifier::new(digest, &pkey).unwrap();
verifier.update(data.as_bytes()).unwrap();
verifier.finish(&signature_bytes).unwrap()
}

23
src/error.rs Normal file
View file

@ -0,0 +1,23 @@
use base64::Base64Error;
use serde_json;
use std::string::FromUtf8Error;
#[derive(Debug)]
pub enum Error {
Format,
Utf8(FromUtf8Error),
Base64(Base64Error),
JSON(serde_json::Error),
}
macro_rules! error_wrap {
($f: ty, $e: expr) => {
impl From<$f> for Error {
fn from(f: $f) -> Error { $e(f) }
}
}
}
error_wrap!(FromUtf8Error, Error::Utf8);
error_wrap!(Base64Error, Error::Base64);
error_wrap!(serde_json::Error, Error::JSON);

74
src/header.rs Normal file
View file

@ -0,0 +1,74 @@
use std::default::Default;
use Header;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct DefaultHeader {
pub typ: Option<HeaderType>,
pub kid: Option<String>,
pub alg: Algorithm,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum HeaderType {
JWT,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum Algorithm {
HS256,
HS384,
HS512,
RS256,
RS384,
RS512
}
impl Default for DefaultHeader {
fn default() -> DefaultHeader {
DefaultHeader {
typ: Some(HeaderType::JWT),
kid: None,
alg: Algorithm::HS256,
}
}
}
impl Header for DefaultHeader {
fn alg(&self) -> &Algorithm {
&(self.alg)
}
}
#[cfg(test)]
mod tests {
use Component;
use header::{
Algorithm,
DefaultHeader,
HeaderType,
};
#[test]
fn from_base64() {
let enc = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
let header = DefaultHeader::from_base64(enc).unwrap();
assert_eq!(header.typ.unwrap(), HeaderType::JWT);
assert_eq!(header.alg, Algorithm::HS256);
let enc = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFLU0YzZyJ9";
let header = DefaultHeader::from_base64(enc).unwrap();
assert_eq!(header.kid.unwrap(), "1KSF3g".to_string());
assert_eq!(header.alg, Algorithm::RS256);
}
#[test]
fn roundtrip() {
let header: DefaultHeader = Default::default();
let enc = Component::to_base64(&header).unwrap();
assert_eq!(header, DefaultHeader::from_base64(&*enc).unwrap());
}
}

264
src/lib.rs Normal file
View file

@ -0,0 +1,264 @@
extern crate base64;
extern crate openssl;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
use base64::{decode, encode_config, URL_SAFE};
use openssl::hash::MessageDigest;
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;
#[derive(Debug, Default)]
pub struct Token<H, C>
where H: Component, C: Component {
raw: Option<String>,
pub header: H,
pub claims: C,
}
pub trait Header {
fn alg(&self) -> &header::Algorithm;
}
pub trait Component: Sized {
fn from_base64(raw: &str) -> Result<Self, Error>;
fn to_base64(&self) -> Result<String, Error>;
}
impl<T> Component for T
where T: Serialize + Deserialize + Sized {
/// Parse from a string.
fn from_base64(raw: &str) -> Result<T, Error> {
let data = try!(decode(raw));
let s = try!(String::from_utf8(data));
Ok(try!(serde_json::from_str(&*s)))
}
/// Encode to a string.
fn to_base64(&self) -> Result<String, Error> {
let s = try!(serde_json::to_string(&self));
let enc = encode_config((&*s).as_bytes(), URL_SAFE);
Ok(enc)
}
}
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.
pub fn parse(raw: &str) -> Result<Token<H, C>, Error> {
let pieces: Vec<_> = raw.split('.').collect();
Ok(Token {
raw: Some(raw.into()),
header: try!(Component::from_base64(pieces[0])),
claims: try!(Component::from_base64(pieces[1])),
})
}
/// Verify a from_base64 token with a key and the token's specific algorithm
pub fn verify(&self, key: &[u8]) -> bool {
match self.header.alg() {
&Algorithm::HS256 => self.verify_hmac(key, MessageDigest::sha256()),
&Algorithm::HS384 => self.verify_hmac(key, MessageDigest::sha384()),
&Algorithm::HS512 => self.verify_hmac(key, MessageDigest::sha512()),
&Algorithm::RS256 => self.verify_rsa(key, MessageDigest::sha256()),
&Algorithm::RS384 => self.verify_rsa(key, MessageDigest::sha384()),
&Algorithm::RS512 => self.verify_rsa(key, MessageDigest::sha512()),
}
}
fn verify_hmac(&self, key: &[u8], digest: MessageDigest) -> bool {
let raw = match self.raw {
Some(ref s) => s,
None => return false,
};
let pieces: Vec<_> = raw.rsplitn(2, '.').collect();
let sig = pieces[0];
let data = pieces[1];
crypt::verify(sig, data, key, digest)
}
fn verify_rsa(&self, key: &[u8], digest: MessageDigest) -> bool {
let raw = match self.raw {
Some(ref s) => s,
None => return false,
};
let pieces: Vec<_> = raw.rsplitn(2, '.').collect();
let sig = pieces[0];
let data = pieces[1];
crypt::verify_rsa(sig, data, key, digest)
}
/// Generate the signed token from a key and the specific algorithm
pub fn signed(&self, key: &[u8]) -> Result<String, Error> {
match self.header.alg() {
&Algorithm::HS256 => self.signed_hmac(key, MessageDigest::sha256()),
&Algorithm::HS384 => self.signed_hmac(key, MessageDigest::sha384()),
&Algorithm::HS512 => self.signed_hmac(key, MessageDigest::sha512()),
&Algorithm::RS256 => self.signed_rsa(key, MessageDigest::sha256()),
&Algorithm::RS384 => self.signed_rsa(key, MessageDigest::sha384()),
&Algorithm::RS512 => self.signed_rsa(key, MessageDigest::sha512()),
}
}
fn signed_hmac(&self, key: &[u8], digest: MessageDigest) -> Result<String, Error> {
let header = try!(Component::to_base64(&self.header));
let claims = try!(self.claims.to_base64());
let data = format!("{}.{}", header, claims);
let sig = crypt::sign(&*data, key, digest);
Ok(format!("{}.{}", data, sig))
}
fn signed_rsa(&self, key: &[u8], digest: MessageDigest) -> Result<String, Error> {
let header = try!(Component::to_base64(&self.header));
let claims = try!(self.claims.to_base64());
let data = format!("{}.{}", header, claims);
let sig = crypt::sign_rsa(&*data, key, digest);
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 crypt::{
sign,
sign_rsa,
verify,
verify_rsa
};
use Claims;
use Token;
use header::Algorithm::{HS256,RS512};
use header::DefaultHeader;
use std::io::{Error, Read};
use std::fs::File;
use openssl::hash::MessageDigest;
#[test]
pub fn sign_data() {
let header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
let claims = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9";
let real_sig = "TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
let data = format!("{}.{}", header, claims);
let sig = sign(&*data, "secret".as_bytes(), MessageDigest::sha256());
assert_eq!(sig, real_sig);
}
#[test]
pub fn sign_data_rsa() {
let header = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9";
let claims = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9";
let real_sig = "nXdpIkFQYZXZ0VlJjHmAc5/aewHCCJpT5jP1fpexUCF/9m3NxlC7uYNXAl6NKno520oh9wVT4VV/vmPeEin7BnnoIJNPcImWcUzkYpLTrDBntiF9HCuqFaniuEVzlf8dVlRJgo8QxhmUZEjyDFjPZXZxPlPV1LD6hrtItxMKZbh1qoNY3OL7Mwo+WuSRQ0mmKj+/y3weAmx/9EaTLY639uD8+o5iZxIIf85U4e55Wdp+C9FJ4RxyHpjgoG8p87IbChfleSdWcZL3NZuxjRCHVWgS1uYG0I+LqBWpWyXnJ1zk6+w4tfxOYpZFMOIyq4tY2mxJQ78Kvcu8bTO7UdI7iA";
let data = format!("{}.{}", header, claims);
let key = load_key("./examples/privateKey.pem").unwrap();
let sig = sign_rsa(&*data, key.as_bytes(), MessageDigest::sha256());
assert_eq!(sig.trim(), real_sig);
}
#[test]
pub fn verify_data() {
let header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
let claims = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9";
let target = "TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
let data = format!("{}.{}", header, claims);
assert!(verify(target, &*data, "secret".as_bytes(), MessageDigest::sha256()));
}
#[test]
pub fn verify_data_rsa() {
let header = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9";
let claims = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9";
let real_sig = "nXdpIkFQYZXZ0VlJjHmAc5/aewHCCJpT5jP1fpexUCF/9m3NxlC7uYNXAl6NKno520oh9wVT4VV/vmPeEin7BnnoIJNPcImWcUzkYpLTrDBntiF9HCuqFaniuEVzlf8dVlRJgo8QxhmUZEjyDFjPZXZxPlPV1LD6hrtItxMKZbh1qoNY3OL7Mwo+WuSRQ0mmKj+/y3weAmx/9EaTLY639uD8+o5iZxIIf85U4e55Wdp+C9FJ4RxyHpjgoG8p87IbChfleSdWcZL3NZuxjRCHVWgS1uYG0I+LqBWpWyXnJ1zk6+w4tfxOYpZFMOIyq4tY2mxJQ78Kvcu8bTO7UdI7iA";
let data = format!("{}.{}", header, claims);
let key = load_key("./examples/publicKey.pub").unwrap();
assert!(verify_rsa(&real_sig, &*data, key.as_bytes(), MessageDigest::sha256()));
}
#[test]
pub fn raw_data() {
let raw = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
let token = Token::<DefaultHeader, Claims>::parse(raw).unwrap();
{
assert_eq!(token.header.alg, HS256);
}
assert!(token.verify("secret".as_bytes()));
}
#[test]
pub fn roundtrip() {
let token: Token<DefaultHeader, Claims> = Default::default();
let key = "secret".as_bytes();
let raw = token.signed(key).unwrap();
let same = Token::parse(&*raw).unwrap();
assert_eq!(token, same);
assert!(same.verify(key));
}
#[test]
pub fn roundtrip_rsa() {
let token: Token<DefaultHeader, Claims> = Token {
header: DefaultHeader {
alg: RS512,
..Default::default()
},
..Default::default()
};
let private_key = load_key("./examples/privateKey.pem").unwrap();
let raw = token.signed(private_key.as_bytes()).unwrap();
let same = Token::parse(&*raw).unwrap();
assert_eq!(token, same);
let public_key = load_key("./examples/publicKey.pub").unwrap();
assert!(same.verify(public_key.as_bytes()));
}
fn load_key(keypath: &str) -> Result<String, Error> {
let mut key_file = try!(File::open(keypath));
let mut key = String::new();
try!(key_file.read_to_string(&mut key));
Ok(key)
}
}