1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#![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, Deserialize};
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<T> = std::result::Result<T, Error>;

/// 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<H> = Token<H, ()>;

/// Main struct representing a JSON Web Token, composed of a header and a set of claims.
#[derive(Debug, Default)]
pub struct Token<H, C>
    where H: Serialize + Deserialize + PartialEq,
          C: Serialize + Deserialize + PartialEq
{
    raw: Option<String>,
    pub header: Header<H>,
    pub payload: Payload<C>,
}

/// Provide the ability to parse a token, verify it and sign/serialize it.
impl<H, C> Token<H, C>
    where H: Serialize + Deserialize + PartialEq,
          C: Serialize + Deserialize + PartialEq
{
    pub fn new(header: Header<H>, payload: Payload<C>) -> Token<H, C> {
        Token {
            raw: None,
            header: header,
            payload: payload,
        }
    }

    /// Parse a token from a string.
    pub fn parse(raw: &str) -> Result<Token<H, C>> {
        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<bool> {
        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<String> {
        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<H, C> PartialEq for Token<H, C>
    where H: Serialize + Deserialize + PartialEq,
          C: Serialize + Deserialize + PartialEq
{
    fn eq(&self, other: &Token<H, C>) -> 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)
    }
}