From e3a4294e85ed5e170c56993e96206e12ddf35770 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Mon, 9 Jan 2017 14:50:51 +0900 Subject: [PATCH] Move to serde + base64 Remove Part trait Move tests to tests directory Reorganise code --- Cargo.toml | 5 + benches/jwt.rs | 5 +- examples/claims.rs | 10 +- examples/custom_header.rs | 11 +- src/crypto.rs | 103 +++++++++++++ src/errors.rs | 79 +++------- src/header.rs | 33 +++++ src/lib.rs | 295 ++------------------------------------ tests/lib.rs | 97 +++++++++++++ 9 files changed, 281 insertions(+), 357 deletions(-) create mode 100644 src/crypto.rs create mode 100644 src/header.rs create mode 100644 tests/lib.rs diff --git a/Cargo.toml b/Cargo.toml index bbf2eb5..aa04685 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,8 @@ keywords = ["jwt", "web", "api", "token", "json"] [dependencies] rustc-serialize = "^0.3" ring = "^0.7" +error-chain = "0.9" +serde_json = "0.9" +serde_derive = "0.9" +serde = "0.9" +base64 = "0.4" diff --git a/benches/jwt.rs b/benches/jwt.rs index 5813bc9..3f8c692 100644 --- a/benches/jwt.rs +++ b/benches/jwt.rs @@ -1,11 +1,12 @@ #![feature(test)] extern crate test; extern crate jsonwebtoken as jwt; -extern crate rustc_serialize; +#[macro_use] +extern crate serde_derive; use jwt::{encode, decode, Algorithm, Header}; -#[derive(Debug, PartialEq, Clone, RustcEncodable, RustcDecodable)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] struct Claims { sub: String, company: String diff --git a/examples/claims.rs b/examples/claims.rs index 1c138ad..850b828 100644 --- a/examples/claims.rs +++ b/examples/claims.rs @@ -1,11 +1,11 @@ extern crate jsonwebtoken as jwt; -extern crate rustc_serialize; +#[macro_use] extern crate serde_derive; use jwt::{encode, decode, Header, Algorithm}; -use jwt::errors::{Error}; +use jwt::errors::{ErrorKind}; -#[derive(Debug, RustcEncodable, RustcDecodable)] +#[derive(Debug, Serialize, Deserialize)] struct Claims { sub: String, company: String @@ -38,8 +38,8 @@ fn main() { let token_data = match decode::(&token, key.as_ref(), Algorithm::HS256) { Ok(c) => c, - Err(err) => match err { - Error::InvalidToken => panic!(), // Example on how to handle a specific error + Err(err) => match *err.kind() { + ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error _ => panic!() } }; diff --git a/examples/custom_header.rs b/examples/custom_header.rs index 1b7e686..b09def8 100644 --- a/examples/custom_header.rs +++ b/examples/custom_header.rs @@ -1,11 +1,12 @@ extern crate jsonwebtoken as jwt; -extern crate rustc_serialize; +#[macro_use] +extern crate serde_derive; use jwt::{encode, decode, Header, Algorithm}; -use jwt::errors::{Error}; +use jwt::errors::{ErrorKind}; -#[derive(Debug, RustcEncodable, RustcDecodable)] +#[derive(Debug, Serialize, Deserialize)] struct Claims { sub: String, company: String @@ -29,8 +30,8 @@ fn main() { let token_data = match decode::(&token, key.as_ref(), Algorithm::HS512) { Ok(c) => c, - Err(err) => match err { - Error::InvalidToken => panic!(), // Example on how to handle a specific error + Err(err) => match *err.kind() { + ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error _ => panic!() } }; diff --git a/src/crypto.rs b/src/crypto.rs new file mode 100644 index 0000000..3063b17 --- /dev/null +++ b/src/crypto.rs @@ -0,0 +1,103 @@ +use base64; +use ring::{digest, hmac}; +use ring::constant_time::verify_slices_are_equal; +use serde::de::Deserialize; +use serde::ser::Serialize; +use serde_json; + + +use errors::{Result, ErrorKind}; +use header::Header; + + +/// The algorithms supported for signing/verifying +#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)] +pub enum Algorithm { + HS256, + HS384, + HS512 +} + +/// Serializes and encodes to base64 +fn to_jwt_part(input: &T) -> Result { + let encoded = serde_json::to_string(input)?; + Ok(base64::encode_config(encoded.as_bytes(), base64::URL_SAFE_NO_PAD)) +} + +/// Decodes from base64 and deserializes +fn from_jwt_part, T: Deserialize>(encoded: B) -> Result { + let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?; + let s = String::from_utf8(decoded)?; + + Ok(serde_json::from_str(&s)?) +} + + +/// The return type of a successful call to decode(...) +#[derive(Debug)] +pub struct TokenData { + pub header: Header, + pub claims: T +} + +/// Take the payload of a JWT and sign it using the algorithm given. +/// Returns the base64 url safe encoded of the hmac result +pub fn sign(data: &str, secret: &[u8], algorithm: Algorithm) -> String { + let digest = match algorithm { + Algorithm::HS256 => &digest::SHA256, + Algorithm::HS384 => &digest::SHA384, + Algorithm::HS512 => &digest::SHA512, + }; + let key = hmac::SigningKey::new(digest, secret); + base64::encode_config( + hmac::sign(&key, data.as_bytes()).as_ref(), + base64::URL_SAFE_NO_PAD + ) +} + +/// Compares the signature given with a re-computed signature +pub fn verify(signature: &str, data: &str, secret: &[u8], algorithm: Algorithm) -> bool { + verify_slices_are_equal(signature.as_ref(), sign(data, secret, algorithm).as_ref()).is_ok() +} + +/// Encode the claims passed and sign the payload using the algorithm from the header and the secret +pub fn encode(header: Header, claims: &T, secret: &[u8]) -> Result { + let encoded_header = to_jwt_part(&header)?; + let encoded_claims = to_jwt_part(&claims)?; + let payload = [encoded_header.as_ref(), encoded_claims.as_ref()].join("."); + let signature = sign(&*payload, secret.as_ref(), header.alg); + + Ok([payload, signature].join(".")) +} + +/// Used in decode: takes the result of a rsplit and ensure we only get 2 parts +/// Errors if we don't +macro_rules! expect_two { + ($iter:expr) => {{ + let mut i = $iter; + match (i.next(), i.next(), i.next()) { + (Some(first), Some(second), None) => (first, second), + _ => return Err(ErrorKind::InvalidToken.into()) + } + }} +} + +/// Decode a token into a Claims struct +/// If the token or its signature is invalid, it will return an error +pub fn decode(token: &str, secret: &[u8], algorithm: Algorithm) -> Result> { + let (signature, payload) = expect_two!(token.rsplitn(2, '.')); + + if !verify(signature, payload, secret, algorithm) { + return Err(ErrorKind::InvalidSignature.into()); + } + + let (claims, header) = expect_two!(payload.rsplitn(2, '.')); + + let header: Header = from_jwt_part(header)?; + if header.alg != algorithm { + return Err(ErrorKind::WrongAlgorithmHeader.into()); + } + let decoded_claims: T = from_jwt_part(claims)?; + + Ok(TokenData { header: header, claims: decoded_claims }) +} diff --git a/src/errors.rs b/src/errors.rs index 1d78005..5215c54 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,68 +1,25 @@ -use std::{string, fmt, error}; -use rustc_serialize::{json, base64}; +use base64; +use serde_json; -#[derive(Debug)] -/// All the errors we can encounter while signing/verifying tokens -/// and a couple of custom one for when the token we are trying -/// to verify is invalid -pub enum Error { - EncodeJSON(json::EncoderError), - DecodeBase64(base64::FromBase64Error), - DecodeJSON(json::DecoderError), - Utf8(string::FromUtf8Error), - - InvalidToken, - InvalidSignature, - WrongAlgorithmHeader -} - -macro_rules! impl_from_error { - ($f: ty, $e: expr) => { - impl From<$f> for Error { - fn from(f: $f) -> Error { $e(f) } +error_chain! { + errors { + InvalidToken { + description("invalid token") + display("Invalid token") } - } -} - -impl_from_error!(json::EncoderError, Error::EncodeJSON); -impl_from_error!(base64::FromBase64Error, Error::DecodeBase64); -impl_from_error!(json::DecoderError, Error::DecodeJSON); -impl_from_error!(string::FromUtf8Error, Error::Utf8); - -impl error::Error for Error { - fn description(&self) -> &str { - match *self { - Error::EncodeJSON(ref err) => err.description(), - Error::DecodeBase64(ref err) => err.description(), - Error::DecodeJSON(ref err) => err.description(), - Error::Utf8(ref err) => err.description(), - Error::InvalidToken => "Invalid Token", - Error::InvalidSignature => "Invalid Signature", - Error::WrongAlgorithmHeader => "Wrong Algorithm Header", + InvalidSignature { + description("invalid signature") + display("Invalid signature") + } + WrongAlgorithmHeader { + description("Wrong Algorithm Header") + display("Wrong Algorithm Header") } } - fn cause(&self) -> Option<&error::Error> { - Some(match *self { - Error::EncodeJSON(ref err) => err as &error::Error, - Error::DecodeBase64(ref err) => err as &error::Error, - Error::DecodeJSON(ref err) => err as &error::Error, - Error::Utf8(ref err) => err as &error::Error, - ref e => e as &error::Error, - }) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::EncodeJSON(ref err) => fmt::Display::fmt(err, f), - Error::DecodeBase64(ref err) => fmt::Display::fmt(err, f), - Error::DecodeJSON(ref err) => fmt::Display::fmt(err, f), - Error::Utf8(ref err) => fmt::Display::fmt(err, f), - Error::InvalidToken => write!(f, "{}", error::Error::description(self)), - Error::InvalidSignature => write!(f, "{}", error::Error::description(self)), - Error::WrongAlgorithmHeader => write!(f, "{}", error::Error::description(self)), - } + foreign_links { + Base64(base64::Base64Error); + Json(serde_json::Error); + Utf8(::std::string::FromUtf8Error); } } diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 0000000..16084c6 --- /dev/null +++ b/src/header.rs @@ -0,0 +1,33 @@ +use crypto::Algorithm; + + +/// A basic JWT header, the alg defaults to HS256 and typ is automatically +/// set to `JWT`. All the other fields are optional +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Header { + typ: String, + pub alg: Algorithm, + pub jku: Option, + pub kid: Option, + pub x5u: Option, + pub x5t: Option +} + +impl Header { + pub fn new(algorithm: Algorithm) -> Header { + Header { + typ: "JWT".to_string(), + alg: algorithm, + jku: None, + kid: None, + x5u: None, + x5t: None + } + } +} + +impl Default for Header { + fn default() -> Header { + Header::new(Algorithm::HS256) + } +} diff --git a/src/lib.rs b/src/lib.rs index 52283aa..c22e51d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,292 +1,19 @@ //! Create and parses JWT (JSON Web Tokens) //! -#![cfg_attr(feature = "dev", allow(unstable_features))] -#![cfg_attr(feature = "dev", feature(plugin))] -#![cfg_attr(feature = "dev", plugin(clippy))] - -extern crate rustc_serialize; +#[macro_use] +extern crate error_chain; +#[macro_use] +extern crate serde_derive; +extern crate serde_json; +extern crate serde; +extern crate base64; extern crate ring; -use ring::{digest, hmac}; -use ring::constant_time::verify_slices_are_equal; - -use rustc_serialize::{json, Encodable, Decodable}; -use rustc_serialize::base64::{self, ToBase64, FromBase64}; -use rustc_serialize::json::{ToJson, Json}; - pub mod errors; -use errors::Error; -use std::collections::BTreeMap; +mod header; +mod crypto; -#[derive(Debug, PartialEq, Copy, Clone, RustcDecodable, RustcEncodable)] -/// The algorithms supported for signing/verifying -pub enum Algorithm { - HS256, - HS384, - HS512 -} +pub use header::{Header}; +pub use crypto::{Algorithm, sign, verify, encode, decode}; -impl ToJson for Algorithm { - fn to_json(&self) -> Json { - match *self { - Algorithm::HS256 => Json::String("HS256".to_string()), - Algorithm::HS384 => Json::String("HS384".to_string()), - Algorithm::HS512 => Json::String("HS512".to_string()), - } - } -} - -/// A part of the JWT: header and claims specifically -/// Allows converting from/to struct with base64 -pub trait Part { - type Encoded: AsRef; - - fn from_base64>(encoded: B) -> Result where Self: Sized; - fn to_base64(&self) -> Result; -} - -impl Part for T where T: Encodable + Decodable { - type Encoded = String; - - fn to_base64(&self) -> Result { - let encoded = try!(json::encode(&self)); - Ok(encoded.as_bytes().to_base64(base64::URL_SAFE)) - } - - fn from_base64>(encoded: B) -> Result { - let decoded = try!(encoded.as_ref().from_base64()); - let s = try!(String::from_utf8(decoded)); - Ok(try!(json::decode(&s))) - } -} - -#[derive(Debug, PartialEq, RustcDecodable)] -/// A basic JWT header part, the alg defaults to HS256 and typ is automatically -/// set to `JWT`. All the other fields are optional -pub struct Header { - typ: String, - pub alg: Algorithm, - pub jku: Option, - pub kid: Option, - pub x5u: Option, - pub x5t: Option -} - -impl Header { - pub fn new(algorithm: Algorithm) -> Header { - Header { - typ: "JWT".to_string(), - alg: algorithm, - jku: None, - kid: None, - x5u: None, - x5t: None - } - } -} - -impl Default for Header { - fn default() -> Header { - Header::new(Algorithm::HS256) - } -} - -impl Encodable for Header { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - self.to_json().encode(s) - } -} - -impl ToJson for Header { - fn to_json(&self) -> Json { - let mut d = BTreeMap::new(); - d.insert("typ".to_string(), self.typ.to_json()); - d.insert("alg".to_string(), self.alg.to_json()); - - // Define a macro to reduce boilerplate. - macro_rules! optional { - ($field_name:ident) => ( - if let Some(ref value) = self.$field_name { - d.insert(stringify!($field_name).to_string(), value.to_json()); - } - ) - } - optional!(jku); - optional!(kid); - optional!(x5u); - optional!(x5t); - Json::Object(d) - } -} - -#[derive(Debug)] -/// The return type of a successful call to decode(...) -pub struct TokenData { - pub header: Header, - pub claims: T -} - -/// Take the payload of a JWT and sign it using the algorithm given. -/// Returns the base64 url safe encoded of the hmac result -pub fn sign(data: &str, secret: &[u8], algorithm: Algorithm) -> String { - let digest = match algorithm { - Algorithm::HS256 => &digest::SHA256, - Algorithm::HS384 => &digest::SHA384, - Algorithm::HS512 => &digest::SHA512, - }; - let key = hmac::SigningKey::new(digest, secret); - hmac::sign(&key, data.as_bytes()).as_ref().to_base64(base64::URL_SAFE) -} - -/// Compares the signature given with a re-computed signature -pub fn verify(signature: &str, data: &str, secret: &[u8], algorithm: Algorithm) -> bool { - verify_slices_are_equal(signature.as_ref(), sign(data, secret, algorithm).as_ref()).is_ok() -} - -/// Encode the claims passed and sign the payload using the algorithm from the header and the secret -pub fn encode(header: Header, claims: &T, secret: &[u8]) -> Result { - let encoded_header = try!(header.to_base64()); - let encoded_claims = try!(claims.to_base64()); - // seems to be a tiny bit faster than format!("{}.{}", x, y) - let payload = [encoded_header.as_ref(), encoded_claims.as_ref()].join("."); - let signature = sign(&*payload, secret.as_ref(), header.alg); - - Ok([payload, signature].join(".")) -} - -/// Used in decode: takes the result of a rsplit and ensure we only get 2 parts -/// Errors if we don't -macro_rules! expect_two { - ($iter:expr) => {{ - let mut i = $iter; // evaluate the expr - match (i.next(), i.next(), i.next()) { - (Some(first), Some(second), None) => (first, second), - _ => return Err(Error::InvalidToken) - } - }} -} - -/// Decode a token into a Claims struct -/// If the token or its signature is invalid, it will return an error -pub fn decode(token: &str, secret: &[u8], algorithm: Algorithm) -> Result, Error> { - let (signature, payload) = expect_two!(token.rsplitn(2, '.')); - - let is_valid = verify( - signature, - payload, - secret, - algorithm - ); - - if !is_valid { - return Err(Error::InvalidSignature); - } - - let (claims, header) = expect_two!(payload.rsplitn(2, '.')); - - let header = try!(Header::from_base64(header)); - if header.alg != algorithm { - return Err(Error::WrongAlgorithmHeader); - } - let decoded_claims = try!(T::from_base64(claims)); - - Ok(TokenData { header: header, claims: decoded_claims}) -} - -#[cfg(test)] -mod tests { - use super::{encode, decode, Algorithm, Header, sign, verify}; - - #[derive(Debug, PartialEq, Clone, RustcEncodable, RustcDecodable)] - struct Claims { - sub: String, - company: String - } - - #[test] - fn sign_hs256() { - let result = sign("hello world", b"secret", Algorithm::HS256); - let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; - assert_eq!(result, expected); - } - - #[test] - fn verify_hs256() { - let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; - let valid = verify(sig, "hello world", b"secret", Algorithm::HS256); - assert!(valid); - } - - #[test] - fn encode_with_custom_header() { - // TODO: test decode value - let my_claims = Claims { - sub: "b@b.com".to_string(), - company: "ACME".to_string() - }; - let mut header = Header::default(); - header.kid = Some("kid".to_string()); - let token = encode(header, &my_claims, "secret".as_ref()).unwrap(); - let token_data = decode::(&token, "secret".as_ref(), Algorithm::HS256).unwrap(); - assert_eq!(my_claims, token_data.claims); - assert_eq!("kid", token_data.header.kid.unwrap()); - } - - #[test] - fn round_trip_claim() { - let my_claims = Claims { - sub: "b@b.com".to_string(), - company: "ACME".to_string() - }; - let token = encode(Header::default(), &my_claims, "secret".as_ref()).unwrap(); - let token_data = decode::(&token, "secret".as_ref(), Algorithm::HS256).unwrap(); - assert_eq!(my_claims, token_data.claims); - assert!(token_data.header.kid.is_none()); - } - - #[test] - fn decode_token() { - let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; - let claims = decode::(token, "secret".as_ref(), Algorithm::HS256); - claims.unwrap(); - } - - #[test] - #[should_panic(expected = "InvalidToken")] - fn decode_token_missing_parts() { - let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; - let claims = decode::(token, "secret".as_ref(), Algorithm::HS256); - claims.unwrap(); - } - - #[test] - #[should_panic(expected = "InvalidSignature")] - fn decode_token_invalid_signature() { - let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong"; - let claims = decode::(token, "secret".as_ref(), Algorithm::HS256); - claims.unwrap(); - } - - #[test] - #[should_panic(expected = "WrongAlgorithmHeader")] - fn decode_token_wrong_algorithm() { - let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.pKscJVk7-aHxfmQKlaZxh5uhuKhGMAa-1F5IX5mfUwI"; - let claims = decode::(token, "secret".as_ref(), Algorithm::HS256); - claims.unwrap(); - } - - #[test] - fn decode_token_with_bytes_secret() { - let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiY29tcGFueSI6Ikdvb2dvbCJ9.27QxgG96vpX4akKNpD1YdRGHE3_u2X35wR3EHA2eCrs"; - let claims = decode::(token, b"\x01\x02\x03", Algorithm::HS256); - assert!(claims.is_ok()); - } - - #[test] - fn decode_token_with_shuffled_header_fields() { - let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.SEIZ4Jg46VGhquuwPYDLY5qHF8AkQczF14aXM3a2c28"; - let claims = decode::(token, "secret".as_ref(), Algorithm::HS256); - assert!(claims.is_ok()); - } -} diff --git a/tests/lib.rs b/tests/lib.rs new file mode 100644 index 0000000..24ebfea --- /dev/null +++ b/tests/lib.rs @@ -0,0 +1,97 @@ +extern crate jsonwebtoken; +#[macro_use] +extern crate serde_derive; + +use jsonwebtoken::{encode, decode, Algorithm, Header, sign, verify}; + + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +struct Claims { + sub: String, + company: String +} + +#[test] +fn sign_hs256() { + let result = sign("hello world", b"secret", Algorithm::HS256); + let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; + assert_eq!(result, expected); +} + +#[test] +fn verify_hs256() { + let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; + let valid = verify(sig, "hello world", b"secret", Algorithm::HS256); + assert!(valid); +} + +#[test] +fn encode_with_custom_header() { + let my_claims = Claims { + sub: "b@b.com".to_string(), + company: "ACME".to_string() + }; + let mut header = Header::default(); + header.kid = Some("kid".to_string()); + let token = encode(header, &my_claims, "secret".as_ref()).unwrap(); + let token_data = decode::(&token, "secret".as_ref(), Algorithm::HS256).unwrap(); + assert_eq!(my_claims, token_data.claims); + assert_eq!("kid", token_data.header.kid.unwrap()); +} + +#[test] +fn round_trip_claim() { + let my_claims = Claims { + sub: "b@b.com".to_string(), + company: "ACME".to_string() + }; + let token = encode(Header::default(), &my_claims, "secret".as_ref()).unwrap(); + let token_data = decode::(&token, "secret".as_ref(), Algorithm::HS256).unwrap(); + assert_eq!(my_claims, token_data.claims); + assert!(token_data.header.kid.is_none()); +} + +#[test] +fn decode_token() { + let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; + let claims = decode::(token, "secret".as_ref(), Algorithm::HS256); + claims.unwrap(); +} + +#[test] +#[should_panic(expected = "InvalidToken")] +fn decode_token_missing_parts() { + let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; + let claims = decode::(token, "secret".as_ref(), Algorithm::HS256); + claims.unwrap(); +} + +#[test] +#[should_panic(expected = "InvalidSignature")] +fn decode_token_invalid_signature() { + let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong"; + let claims = decode::(token, "secret".as_ref(), Algorithm::HS256); + claims.unwrap(); +} + +#[test] +#[should_panic(expected = "WrongAlgorithmHeader")] +fn decode_token_wrong_algorithm() { + let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.pKscJVk7-aHxfmQKlaZxh5uhuKhGMAa-1F5IX5mfUwI"; + let claims = decode::(token, "secret".as_ref(), Algorithm::HS256); + claims.unwrap(); +} + +#[test] +fn decode_token_with_bytes_secret() { + let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiY29tcGFueSI6Ikdvb2dvbCJ9.27QxgG96vpX4akKNpD1YdRGHE3_u2X35wR3EHA2eCrs"; + let claims = decode::(token, b"\x01\x02\x03", Algorithm::HS256); + assert!(claims.is_ok()); +} + +#[test] +fn decode_token_with_shuffled_header_fields() { + let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.SEIZ4Jg46VGhquuwPYDLY5qHF8AkQczF14aXM3a2c28"; + let claims = decode::(token, "secret".as_ref(), Algorithm::HS256); + assert!(claims.is_ok()); +}