parent
1b594ff60e
commit
7fcae534f2
6 changed files with 49 additions and 118 deletions
23
Cargo.toml
23
Cargo.toml
|
@ -2,23 +2,24 @@
|
|||
authors = ["Thomas Gideon <cmdln@thecommandline.net>"]
|
||||
categories = ["cryptography", "authentication", "web-programming", "data-structures"]
|
||||
description = "JWT library for rust using serde, serde_json and openssl"
|
||||
documentation = "https://commandline.github.io/medallion/"
|
||||
homepage = "http://github.com/commandline/medallion"
|
||||
documentation = "https://cmdln.github.io/medallion/"
|
||||
homepage = "http://github.com/cmdln/medallion"
|
||||
keywords = ["JWT", "token", "web", "JSON", "RSA"]
|
||||
license = "MIT"
|
||||
name = "medallion"
|
||||
readme = "README.md"
|
||||
repository = "http://github.com/commandline/medallion"
|
||||
version = "2.2.3"
|
||||
repository = "http://github.com/cmdn/medallion"
|
||||
version = "2.3.0"
|
||||
[badges]
|
||||
[badges.travis-ci]
|
||||
branch = "master"
|
||||
repository = "https://travis-ci.org/commandline/medallion"
|
||||
repository = "https://travis-ci.org/cmdln/medallion"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.9"
|
||||
openssl = "0.10"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
time = "0.1"
|
||||
base64 = "~0.10.0"
|
||||
failure = "~0.1.3"
|
||||
openssl = "~0.10.15"
|
||||
serde = "^1.0.80"
|
||||
serde_derive = "^1.0.80"
|
||||
serde_json = "^1.0.33"
|
||||
time = "~0.1.40"
|
||||
|
|
19
src/crypt.rs
19
src/crypt.rs
|
@ -1,10 +1,13 @@
|
|||
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
|
||||
use header::Algorithm;
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::memcmp;
|
||||
use openssl::pkey::PKey;
|
||||
use openssl::rsa::Rsa;
|
||||
use openssl::sign::{Signer, Verifier};
|
||||
use openssl::{
|
||||
hash::MessageDigest,
|
||||
memcmp,
|
||||
pkey::PKey,
|
||||
rsa::Rsa,
|
||||
sign::{Signer, Verifier},
|
||||
};
|
||||
|
||||
use super::Result;
|
||||
|
||||
pub fn sign(data: &str, key: &[u8], algorithm: &Algorithm) -> Result<String> {
|
||||
|
@ -72,9 +75,9 @@ fn verify_rsa(signature: &str, data: &str, key: &[u8], digest: MessageDigest) ->
|
|||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::{sign, verify};
|
||||
use header::Algorithm;
|
||||
use openssl;
|
||||
use super::{sign, verify};
|
||||
|
||||
#[test]
|
||||
pub fn sign_data_hmac() {
|
||||
|
@ -83,7 +86,7 @@ pub mod tests {
|
|||
let real_sig = "TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
|
||||
let data = format!("{}.{}", header, claims);
|
||||
|
||||
let sig = sign(&*data, "secret".as_bytes(), &Algorithm::HS256);
|
||||
let sig = sign(&*data, b"secret", &Algorithm::HS256);
|
||||
|
||||
assert_eq!(sig.unwrap(), real_sig);
|
||||
}
|
||||
|
@ -120,6 +123,6 @@ pub mod tests {
|
|||
let target = "TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
|
||||
let data = format!("{}.{}", header, claims);
|
||||
|
||||
assert!(verify(target, &*data, "secret".as_bytes(), &Algorithm::HS256).unwrap());
|
||||
assert!(verify(target, &*data, b"secret", &Algorithm::HS256).unwrap());
|
||||
}
|
||||
}
|
||||
|
|
68
src/error.rs
68
src/error.rs
|
@ -1,67 +1,3 @@
|
|||
use base64::DecodeError;
|
||||
use serde_json;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::string::FromUtf8Error;
|
||||
use openssl::error::ErrorStack;
|
||||
use failure::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Custom, Medallion specific errors.
|
||||
Custom(String),
|
||||
/// String encoding errors.
|
||||
Utf8(FromUtf8Error),
|
||||
/// Base64 encoding or decoding errors.
|
||||
Base64(DecodeError),
|
||||
/// JSON parsing or stringifying errors.
|
||||
JSON(serde_json::Error),
|
||||
/// Errors from RSA operations.
|
||||
Crypto(ErrorStack),
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::Custom(ref message) => message,
|
||||
Error::Utf8(ref err) => err.description(),
|
||||
Error::Base64(ref err) => err.description(),
|
||||
Error::JSON(ref err) => err.description(),
|
||||
Error::Crypto(ref err) => err.description(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
match *self {
|
||||
Error::Custom(_) => None,
|
||||
Error::Utf8(ref err) => Some(err),
|
||||
Error::Base64(ref err) => Some(err),
|
||||
Error::JSON(ref err) => Some(err),
|
||||
Error::Crypto(ref err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::Custom(ref message) => f.write_str(message),
|
||||
Error::Utf8(ref err) => err.fmt(f),
|
||||
Error::Base64(ref err) => err.fmt(f),
|
||||
Error::JSON(ref err) => err.fmt(f),
|
||||
Error::Crypto(ref err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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!(DecodeError, Error::Base64);
|
||||
error_wrap!(serde_json::Error, Error::JSON);
|
||||
error_wrap!(ErrorStack, Error::Crypto);
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use serde_json::{self, Value};
|
||||
use std::default::Default;
|
||||
|
||||
use super::error::Error;
|
||||
use super::Result;
|
||||
|
||||
/// An extensible Header that provides only algorithm field and allows for additional fields to be
|
||||
|
@ -55,9 +52,7 @@ impl<T: Serialize + DeserializeOwned> Header<T> {
|
|||
let enc = encode_config((&*s).as_bytes(), URL_SAFE_NO_PAD);
|
||||
Ok(enc)
|
||||
} else {
|
||||
Err(Error::Custom(
|
||||
"Could not access additional headers.".to_owned(),
|
||||
))
|
||||
Err(format_err!("Could not access additional headers."))
|
||||
}
|
||||
}
|
||||
None => {
|
||||
|
@ -67,7 +62,7 @@ impl<T: Serialize + DeserializeOwned> Header<T> {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::Custom("Could not access default header.".to_owned()))
|
||||
Err(format_err!("Could not access default header."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +108,7 @@ mod tests {
|
|||
#[test]
|
||||
fn to_base64() {
|
||||
let enc = "eyJhbGciOiJIUzI1NiJ9";
|
||||
let header: Header<()> = Default::default();
|
||||
let header: Header<()> = Header::default();
|
||||
|
||||
assert_eq!(enc, header.to_base64().unwrap());
|
||||
}
|
||||
|
@ -126,7 +121,7 @@ mod tests {
|
|||
kid: "1KSF3g".into(),
|
||||
typ: "JWT".into(),
|
||||
}),
|
||||
..Default::default()
|
||||
..Header::default()
|
||||
};
|
||||
|
||||
assert_eq!(enc, header.to_base64().unwrap());
|
||||
|
@ -134,7 +129,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn roundtrip() {
|
||||
let header: Header<()> = Default::default();
|
||||
let header: Header<()> = Header::default();
|
||||
let enc = header.to_base64().unwrap();
|
||||
assert_eq!(header, Header::from_base64(&*enc).unwrap());
|
||||
}
|
||||
|
|
23
src/lib.rs
23
src/lib.rs
|
@ -7,6 +7,8 @@
|
|||
///! Tries to support the standard uses for JWTs while providing reasonable ways to extend,
|
||||
///! primarily by adding custom headers and claims to tokens.
|
||||
extern crate base64;
|
||||
#[macro_use]
|
||||
extern crate failure;
|
||||
extern crate openssl;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
|
@ -14,19 +16,16 @@ extern crate serde_derive;
|
|||
extern crate serde_json;
|
||||
extern crate time;
|
||||
|
||||
pub use error::Error;
|
||||
pub use header::Algorithm;
|
||||
pub use header::Header;
|
||||
pub use header::{Algorithm, Header};
|
||||
pub use payload::{DefaultPayload, Payload};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
mod crypt;
|
||||
pub mod error;
|
||||
mod error;
|
||||
mod header;
|
||||
mod payload;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub use error::Result;
|
||||
|
||||
/// A convenient type that binds 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 no
|
||||
|
@ -118,7 +117,7 @@ mod tests {
|
|||
let token = DefaultToken::<()>::parse(raw).unwrap();
|
||||
|
||||
assert_eq!(token.header.alg, HS256);
|
||||
assert!(token.verify("secret".as_bytes()).unwrap());
|
||||
assert!(token.verify(b"secret").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -131,7 +130,7 @@ mod tests {
|
|||
..DefaultPayload::default()
|
||||
};
|
||||
let token = Token::new(header, payload);
|
||||
let key = "secret".as_bytes();
|
||||
let key = b"secret";
|
||||
let raw = token.sign(key).unwrap();
|
||||
let same = Token::parse(&*raw).unwrap();
|
||||
|
||||
|
@ -143,7 +142,7 @@ mod tests {
|
|||
pub fn roundtrip_expired() {
|
||||
let now = time::now();
|
||||
let token = create_for_range(now, now + Duration::minutes(-5));
|
||||
let key = "secret".as_bytes();
|
||||
let key = b"secret";
|
||||
let raw = token.sign(key).unwrap();
|
||||
let same = Token::parse(&*raw).unwrap();
|
||||
|
||||
|
@ -155,7 +154,7 @@ mod tests {
|
|||
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 key = b"secret";
|
||||
let raw = token.sign(key).unwrap();
|
||||
let same = Token::parse(&*raw).unwrap();
|
||||
|
||||
|
@ -171,7 +170,7 @@ mod tests {
|
|||
..Header::default()
|
||||
};
|
||||
let token = DefaultToken {
|
||||
header: header,
|
||||
header,
|
||||
..Token::default()
|
||||
};
|
||||
let raw = token
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use super::Result;
|
||||
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
|
||||
use error::Error;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
|
@ -68,7 +67,7 @@ impl<T: Serialize + DeserializeOwned> Payload<T> {
|
|||
let enc = encode_config((&*s).as_bytes(), URL_SAFE_NO_PAD);
|
||||
Ok(enc)
|
||||
} else {
|
||||
Err(Error::Custom("Could not access custom claims.".to_owned()))
|
||||
Err(format_err!("Could not access custom claims."))
|
||||
}
|
||||
}
|
||||
None => {
|
||||
|
@ -78,9 +77,7 @@ impl<T: Serialize + DeserializeOwned> Payload<T> {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::Custom(
|
||||
"Could not access standard claims.".to_owned(),
|
||||
))
|
||||
Err(format_err!("Could not access standard claims.",))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,10 +231,10 @@ mod tests {
|
|||
fn create_default() -> DefaultPayload {
|
||||
DefaultPayload {
|
||||
aud: Some("login_service".into()),
|
||||
iat: Some(1302317100),
|
||||
iat: Some(1_302_317_100),
|
||||
iss: Some("example.com".into()),
|
||||
exp: Some(1302319100),
|
||||
nbf: Some(1302317100),
|
||||
exp: Some(1_302_319_100),
|
||||
nbf: Some(1_302_317_100),
|
||||
sub: Some("Random User".into()),
|
||||
..Default::default()
|
||||
}
|
||||
|
@ -246,8 +243,8 @@ mod tests {
|
|||
fn create_custom() -> Payload<CustomClaims> {
|
||||
Payload {
|
||||
iss: Some("example.com".into()),
|
||||
iat: Some(1302317100),
|
||||
exp: Some(1302319100),
|
||||
iat: Some(1_302_317_100),
|
||||
exp: Some(1_302_319_100),
|
||||
claims: Some(CustomClaims {
|
||||
user_id: "123456".into(),
|
||||
is_admin: false,
|
||||
|
|
Loading…
Reference in a new issue