Merge pull request #107 from Keats/pem-handling

Add PEM decoding support (#106)
This commit is contained in:
Vincent Prouillet 2019-11-15 20:29:59 +01:00 committed by GitHub
commit 1a727f7095
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1302 additions and 817 deletions

32
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: ci
on: [push, pull_request]
jobs:
tests:
name: Tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
build: [pinned, stable, nightly]
include:
- build: pinned
os: ubuntu-18.04
rust: 1.36.0
- build: stable
os: ubuntu-18.04
rust: stable
- build: nightly
os: ubuntu-18.04
rust: nightly
steps:
- uses: actions/checkout@v1
- name: Install Rust
uses: hecrj/setup-rust-action@v1
with:
rust-version: ${{ matrix.rust }}
- name: Build System Info
run: rustc --version
- name: Run tests
run: cargo test

View File

@ -4,10 +4,10 @@
- Add support for PS256, PS384 and PS512
- Add support for verifying with modulus/exponent components for RSA
- Change API for both sign/verify to take a `Key` enum rather than bytes
- Update to 2018 edition
- Changed aud field type in Validation to `Option<HashSet<String>>`. Audience
validation now tests for "any-of-these" audience membership.
- Add support for keys in PEM format
## 6.0.1 (2019-05-10)

View File

@ -1,19 +1,27 @@
[package]
name = "jsonwebtoken"
version = "7.0.0"
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
authors = ["Vincent Prouillet <hello@vincentprouillet.com>"]
license = "MIT"
readme = "README.md"
description = "Create and parse JWT in a strongly typed way."
homepage = "https://github.com/Keats/rust-jwt"
repository = "https://github.com/Keats/rust-jwt"
keywords = ["jwt", "web", "api", "token", "json"]
description = "Create and decode JWTs in a strongly typed way."
homepage = "https://github.com/Keats/jsonwebtoken"
repository = "https://github.com/Keats/jsonwebtoken"
keywords = ["jwt", "web", "api", "token", "json", "jwk"]
edition = "2018"
[dependencies]
serde_json = "1.0"
serde_derive = "1.0"
serde = "1.0"
serde = {version = "1.0", features = ["derive"] }
ring = { version = "0.16.5", features = ["std"] }
base64 = "0.10"
base64 = "0.11"
# For PEM decoding
pem = "0.7"
simple_asn1 = "0.4"
[dev-dependencies]
# For the custom chrono example
chrono = "0.4"
[badges]
maintenance = { status = "passively-developed" }

139
README.md
View File

@ -4,25 +4,41 @@
[API documentation on docs.rs](https://docs.rs/jsonwebtoken/)
See [JSON Web Tokens](https://en.wikipedia.org/wiki/JSON_Web_Token) for more information on what are JSON Web Tokens.
## Installation
Add the following to Cargo.toml:
```toml
jsonwebtoken = "7"
serde_derive = "1"
serde = "1"
serde = {version = "1.0", features = ["derive"] }
```
The minimum required Rust version is 1.36.
## Algorithms
This library currently supports the following:
- HS256
- HS384
- HS512
- RS256
- RS384
- RS512
- PS256
- PS384
- PS512
- ES256
- ES384
## How to use
Complete examples are available in the examples directory: a basic one and one with a custom header.
In terms of imports and structs:
```rust
extern crate jsonwebtoken as jwt;
#[macro_use]
extern crate serde_derive;
use jwt::{encode, decode, Header, Algorithm, Validation};
use serde::{Serialize, Deserialize};
use jsonwebtoken::{encode, decode, Header, Algorithm, Validation};
/// Our claims struct, it needs to derive `Serialize` and/or `Deserialize`
#[derive(Debug, Serialize, Deserialize)]
@ -33,8 +49,8 @@ struct Claims {
}
```
### Encoding
The default algorithm is HS256.
### Header
The default algorithm is HS256, which uses a shared secret.
```rust
let token = encode(&Header::default(), &my_claims, "secret".as_ref())?;
@ -45,38 +61,92 @@ All the parameters from the RFC are supported but the default header only has `t
If you want to set the `kid` parameter or change the algorithm for example:
```rust
let mut header = Header::default();
let mut header = Header::new(Algorithm::HS512);
header.kid = Some("blabla".to_owned());
header.alg = Algorithm::HS512;
let token = encode(&header, &my_claims, "secret".as_ref())?;
```
Look at `examples/custom_header.rs` for a full working example.
### Decoding
### Encoding
```rust
// HS256
let token = encode(&Header::default(), &my_claims, "secret".as_ref())?;
// RSA
let token = encode(&Header::new(Algorithm::RS256), &my_claims, include_str!("privkey.pem"))?;
```
Encoding a JWT takes 3 parameters:
- a header: the `Header` struct
- some claims: your own struct
- a key/secret
When using HS256, HS2384 or HS512, the key is always a shared secret like in the example above. When using
RSA/EC, the key should always be the content of the private key in the PEM format.
### Decoding
```rust
// `token` is a struct with 2 fields: `header` and `claims` where `claims` is your own struct.
let token = decode::<Claims>(&token, "secret".as_ref(), &Validation::default())?;
// token is a struct with 2 params: header and claims
```
`decode` can error for a variety of reasons:
- the token or its signature is invalid
- error while decoding base64 or the result of decoding base64 is not valid UTF-8
- the token had invalid base64
- validation of at least one reserved claim failed
In some cases, for example if you don't know the algorithm used, you will want to only decode the header:
As with encoding, when using HS256, HS2384 or HS512, the key is always a shared secret like in the example above. When using
RSA/EC, the key should always be the content of the public key in the PEM format.
In some cases, for example if you don't know the algorithm used or need to grab the `kid`, you can choose to decode only the header:
```rust
let header = decode_header(&token)?;
```
This does not perform any validation on the token.
This does not perform any signature verification or validate the token claims.
#### Validation
This library validates automatically the `exp` claim. `nbf` is also validated if present. You can also validate the `sub`, `iss` and `aud` but
You can also decode a token using the public key components of a RSA key in base64 format.
The main use-case is for JWK where your public key is in a JSON format like so:
```json
{
"kty":"RSA",
"e":"AQAB",
"kid":"6a7a119f-0876-4f7e-8d0f-bf3ea1391dd8",
"n":"yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ"
}
```
```rust
// `token` is a struct with 2 fields: `header` and `claims` where `claims` is your own struct.
let token = decode_rsa_components::<Claims>(&token, jwk["n"], jwk["e"], &Validation::new(Algorithm::RS256))?;
```
### Converting .der to .pem
You can use openssl for that:
```bash
openssl rsa -inform DER -outform PEM -in mykey.der -out mykey.pem
```
### Convert SEC1 private key to PKCS8
`jsonwebtoken` currently only supports PKCS8 format for private EC keys. If your key has `BEGIN EC PRIVATE KEY` at the top,
this is a SEC1 type and can be converted to PKCS8 like so:
```bash
openssl pkcs8 -topk8 -nocrypt -in sec1.pem -out pkcs8.pem
```
## Validation
This library validates automatically the `exp` claim and `nbf` is validated if present. You can also validate the `sub`, `iss` and `aud` but
those require setting the expected value in the `Validation` struct.
Since validating time fields is always a bit tricky due to clock skew,
you can add some leeway to the `iat`, `exp` and `nbf` validation by setting a `leeway` parameter.
you can add some leeway to the `iat`, `exp` and `nbf` validation by setting the `leeway` field.
Last but not least, you will need to set the algorithm(s) allowed for this token if you are not using `HS256`.
@ -97,33 +167,4 @@ validation.set_audience(&"Me"); // string
validation.set_audience(&["Me", "You"]); // array of strings
```
## Algorithms
This library currently supports the following:
- HS256
- HS384
- HS512
- RS256
- RS384
- RS512
- PS256
- PS384
- PS512
- ES256
- ES384
### RSA
`jsonwebtoken` can only read DER encoded keys currently. If you have openssl installed,
you can run the following commands to obtain the DER keys from PKCS#1 (ie with `BEGIN RSA PUBLIC KEY`) .pem.
If you have a PKCS#8 pem file (ie starting with `BEGIN PUBLIC KEY`), you will need to first convert it to PKCS#1:
`openssl rsa -pubin -in <filename> -RSAPublicKey_out -out <filename>`.
```bash
// private key
$ openssl rsa -in private_rsa_key.pem -outform DER -out private_rsa_key.der
// public key
$ openssl rsa -in private_rsa_key.der -inform DER -RSAPublicKey_out -outform DER -out public_key.der
```
If you are getting an error with your public key, make sure you get it by using the command above to ensure
it is in the right format.
Look at `examples/validation.rs` for a full working example.

View File

@ -1,10 +1,8 @@
#![feature(test)]
extern crate jsonwebtoken as jwt;
extern crate test;
#[macro_use]
extern crate serde_derive;
use jwt::{decode, encode, Header, Hmac, Validation};
use jsonwebtoken::{decode, encode, Header, Validation};
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
struct Claims {
@ -16,7 +14,7 @@ struct Claims {
fn bench_encode(b: &mut test::Bencher) {
let claim = Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned() };
b.iter(|| encode(&Header::default(), &claim, Hmac::from(b"secret")));
b.iter(|| encode(&Header::default(), &claim, "secret".as_ref()));
}
#[bench]

View File

@ -1,11 +1,6 @@
extern crate jsonwebtoken as jwt;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate chrono;
use chrono::prelude::*;
use jwt::{Header, Key, Validation};
use jsonwebtoken::{Header, Validation};
use serde::{Deserialize, Serialize};
const SECRET: &str = "some-secret";
@ -44,9 +39,6 @@ mod jwt_numeric_date {
#[cfg(test)]
mod tests {
use super::*;
use jwt::{Header, Validation};
const EXPECTED_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gRGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjMyNTAzNjgwMDAwfQ.RTgha0S53MjPC2pMA4e2oMzaBxSY3DMjiYR2qFfV55A";
use super::super::{Claims, SECRET};
@ -59,14 +51,13 @@ mod jwt_numeric_date {
let claims = Claims { sub: sub.clone(), iat, exp };
let token = jwt::encode(&Header::default(), &claims, Key::Hmac(SECRET.as_ref()))
let token = encode(&Header::default(), &claims, SECRET.as_ref())
.expect("Failed to encode claims");
assert_eq!(&token, EXPECTED_TOKEN);
let decoded =
jwt::decode::<Claims>(&token, Key::Hmac(SECRET.as_ref()), &Validation::default())
.expect("Failed to decode token");
let decoded = decode::<Claims>(&token, SECRET.as_ref(), &Validation::default())
.expect("Failed to decode token");
assert_eq!(decoded.claims, claims);
}
@ -77,7 +68,7 @@ mod jwt_numeric_date {
let overflow_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gRGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjkyMjMzNzIwMzY4NTQ3NzYwMDB9.G2PKreA27U8_xOwuIeCYXacFYeR46f9FyENIZfCrvEc";
let decode_result =
jwt::decode::<Claims>(&overflow_token, SECRET.as_ref(), &Validation::default());
decode::<Claims>(&overflow_token, SECRET.as_ref(), &Validation::default());
assert!(decode_result.is_err());
}
@ -91,12 +82,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let claims = Claims { sub: sub.clone(), iat, exp };
let token = jwt::encode(&Header::default(), &claims, Key::Hmac(SECRET.as_ref()))?;
let token = jsonwebtoken::encode(&Header::default(), &claims, SECRET.as_ref())?;
println!("serialized token: {}", &token);
let token_data =
jwt::decode::<Claims>(&token, Key::Hmac(SECRET.as_ref()), &Validation::default())?;
jsonwebtoken::decode::<Claims>(&token, SECRET.as_ref(), &Validation::default())?;
println!("token data:\n{:#?}", &token_data);
Ok(())

View File

@ -1,9 +1,7 @@
extern crate jsonwebtoken as jwt;
#[macro_use]
extern crate serde_derive;
use serde::{Deserialize, Serialize};
use jwt::errors::ErrorKind;
use jwt::{decode, encode, Algorithm, Header, Key, Validation};
use jsonwebtoken::errors::ErrorKind;
use jsonwebtoken::{decode, encode, Algorithm, Header, Validation};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
@ -21,20 +19,19 @@ fn main() {
header.kid = Some("signing_key".to_owned());
header.alg = Algorithm::HS512;
let token = match encode(&header, &my_claims, Key::Hmac(key)) {
let token = match encode(&header, &my_claims, key) {
Ok(t) => t,
Err(_) => panic!(), // in practice you would return the error
};
println!("{:?}", token);
let token_data =
match decode::<Claims>(&token, Key::Hmac(key), &Validation::new(Algorithm::HS512)) {
Ok(c) => c,
Err(err) => match *err.kind() {
ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error
_ => panic!(),
},
};
let token_data = match decode::<Claims>(&token, key, &Validation::new(Algorithm::HS512)) {
Ok(c) => c,
Err(err) => match *err.kind() {
ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error
_ => panic!(),
},
};
println!("{:?}", token_data.claims);
println!("{:?}", token_data.header);
}

View File

@ -1,9 +1,6 @@
extern crate jsonwebtoken as jwt;
#[macro_use]
extern crate serde_derive;
use jwt::errors::ErrorKind;
use jwt::{decode, encode, Header, Key, Validation};
use jsonwebtoken::errors::ErrorKind;
use jsonwebtoken::{decode, encode, Header, Validation};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
@ -16,13 +13,13 @@ fn main() {
let my_claims =
Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned(), exp: 10000000000 };
let key = b"secret";
let token = match encode(&Header::default(), &my_claims, Key::Hmac(key)) {
let token = match encode(&Header::default(), &my_claims, key) {
Ok(t) => t,
Err(_) => panic!(), // in practice you would return the error
};
let validation = Validation { sub: Some("b@b.com".to_string()), ..Validation::default() };
let token_data = match decode::<Claims>(&token, Key::Hmac(key), &validation) {
let token_data = match decode::<Claims>(&token, key, &validation) {
Ok(c) => c,
Err(err) => match *err.kind() {
ErrorKind::InvalidToken => panic!("Token is invalid"), // Example on how to handle a specific error

View File

@ -1,7 +1,8 @@
use crate::errors::{new_error, Error, ErrorKind, Result};
use crate::errors::{Error, ErrorKind, Result};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
/// The algorithms supported for signing/verifying
/// The algorithms supported for signing/verifying JWTs
#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum Algorithm {
/// HMAC using SHA-256
@ -52,7 +53,26 @@ impl FromStr for Algorithm {
"PS384" => Ok(Algorithm::PS384),
"PS512" => Ok(Algorithm::PS512),
"RS512" => Ok(Algorithm::RS512),
_ => Err(new_error(ErrorKind::InvalidAlgorithmName)),
_ => Err(ErrorKind::InvalidAlgorithmName.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_algorithm_enum_from_str() {
assert!(Algorithm::from_str("HS256").is_ok());
assert!(Algorithm::from_str("HS384").is_ok());
assert!(Algorithm::from_str("HS512").is_ok());
assert!(Algorithm::from_str("RS256").is_ok());
assert!(Algorithm::from_str("RS384").is_ok());
assert!(Algorithm::from_str("RS512").is_ok());
assert!(Algorithm::from_str("PS256").is_ok());
assert!(Algorithm::from_str("PS384").is_ok());
assert!(Algorithm::from_str("PS512").is_ok());
assert!(Algorithm::from_str("").is_err());
}
}

View File

@ -1,187 +0,0 @@
use base64;
use ring::constant_time::verify_slices_are_equal;
use ring::{hmac, rand, signature};
use crate::algorithms::Algorithm;
use crate::errors::{new_error, ErrorKind, Result};
use crate::keys::Key;
/// The actual HS signing + encoding
fn sign_hmac(alg: hmac::Algorithm, key: Key, signing_input: &str) -> Result<String> {
let signing_key = match key {
Key::Hmac(bytes) => hmac::Key::new(alg, bytes),
_ => return Err(ErrorKind::InvalidKeyFormat)?,
};
let digest = hmac::sign(&signing_key, signing_input.as_bytes());
Ok(base64::encode_config::<hmac::Tag>(&digest, base64::URL_SAFE_NO_PAD))
}
/// The actual ECDSA signing + encoding
fn sign_ecdsa(
alg: &'static signature::EcdsaSigningAlgorithm,
key: Key,
signing_input: &str,
) -> Result<String> {
let signing_key = match key {
Key::Pkcs8(bytes) => signature::EcdsaKeyPair::from_pkcs8(alg, bytes)?,
_ => {
return Err(new_error(ErrorKind::InvalidKeyFormat));
}
};
let rng = rand::SystemRandom::new();
let sig = signing_key.sign(&rng, signing_input.as_bytes())?;
Ok(base64::encode_config(&sig, base64::URL_SAFE_NO_PAD))
}
/// The actual RSA signing + encoding
/// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html
fn sign_rsa(
alg: &'static dyn signature::RsaEncoding,
key: Key,
signing_input: &str,
) -> Result<String> {
let key_pair = match key {
Key::Der(bytes) => {
signature::RsaKeyPair::from_der(bytes).map_err(|_| ErrorKind::InvalidRsaKey)?
}
Key::Pkcs8(bytes) => {
signature::RsaKeyPair::from_pkcs8(bytes).map_err(|_| ErrorKind::InvalidRsaKey)?
}
_ => {
return Err(ErrorKind::InvalidKeyFormat)?;
}
};
let mut signature = vec![0; key_pair.public_modulus_len()];
let rng = rand::SystemRandom::new();
key_pair
.sign(alg, &rng, signing_input.as_bytes(), &mut signature)
.map_err(|_| ErrorKind::InvalidRsaKey)?;
Ok(base64::encode_config::<[u8]>(&signature, base64::URL_SAFE_NO_PAD))
}
/// Take the payload of a JWT, sign it using the algorithm given and return
/// the base64 url safe encoded of the result.
///
/// Only use this function if you want to do something other than JWT.
pub fn sign(signing_input: &str, key: Key, algorithm: Algorithm) -> Result<String> {
match algorithm {
Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key, signing_input),
Algorithm::HS384 => sign_hmac(hmac::HMAC_SHA384, key, signing_input),
Algorithm::HS512 => sign_hmac(hmac::HMAC_SHA512, key, signing_input),
Algorithm::ES256 => {
sign_ecdsa(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, key, signing_input)
}
Algorithm::ES384 => {
sign_ecdsa(&signature::ECDSA_P384_SHA384_FIXED_SIGNING, key, signing_input)
}
Algorithm::RS256 => sign_rsa(&signature::RSA_PKCS1_SHA256, key, signing_input),
Algorithm::RS384 => sign_rsa(&signature::RSA_PKCS1_SHA384, key, signing_input),
Algorithm::RS512 => sign_rsa(&signature::RSA_PKCS1_SHA512, key, signing_input),
Algorithm::PS256 => sign_rsa(&signature::RSA_PSS_SHA256, key, signing_input),
Algorithm::PS384 => sign_rsa(&signature::RSA_PSS_SHA384, key, signing_input),
Algorithm::PS512 => sign_rsa(&signature::RSA_PSS_SHA512, key, signing_input),
}
}
/// See Ring docs for more details
fn verify_ring(
alg: &'static dyn signature::VerificationAlgorithm,
signature: &str,
signing_input: &str,
key: &[u8],
) -> Result<bool> {
let signature_bytes = base64::decode_config(signature, base64::URL_SAFE_NO_PAD)?;
let public_key = signature::UnparsedPublicKey::new(alg, key);
let res = public_key.verify(signing_input.as_bytes(), &signature_bytes);
Ok(res.is_ok())
}
fn verify_ring_es(
alg: &'static dyn signature::VerificationAlgorithm,
signature: &str,
signing_input: &str,
key: Key,
) -> Result<bool> {
let bytes = match key {
Key::Pkcs8(bytes) => bytes,
_ => {
return Err(ErrorKind::InvalidKeyFormat)?;
}
};
verify_ring(alg, signature, signing_input, bytes)
}
fn verify_ring_rsa(
alg: &'static signature::RsaParameters,
signature: &str,
signing_input: &str,
key: Key,
) -> Result<bool> {
match key {
Key::Der(bytes) | Key::Pkcs8(bytes) => verify_ring(alg, signature, signing_input, bytes),
Key::ModulusExponent(n, e) => {
let public_key = signature::RsaPublicKeyComponents { n, e };
let signature_bytes = base64::decode_config(signature, base64::URL_SAFE_NO_PAD)?;
let res = public_key.verify(alg, signing_input.as_bytes(), &signature_bytes);
Ok(res.is_ok())
}
_ => Err(ErrorKind::InvalidKeyFormat)?,
}
}
/// Compares the signature given with a re-computed signature for HMAC or using the public key
/// for RSA.
///
/// Only use this function if you want to do something other than JWT.
///
/// `signature` is the signature part of a jwt (text after the second '.')
///
/// `signing_input` is base64(header) + "." + base64(claims)
pub fn verify(
signature: &str,
signing_input: &str,
key: Key,
algorithm: Algorithm,
) -> Result<bool> {
match algorithm {
Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
// we just re-sign the data with the key and compare if they are equal
let signed = sign(signing_input, key, algorithm)?;
Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok())
}
Algorithm::ES256 => {
verify_ring_es(&signature::ECDSA_P256_SHA256_FIXED, signature, signing_input, key)
}
Algorithm::ES384 => {
verify_ring_es(&signature::ECDSA_P384_SHA384_FIXED, signature, signing_input, key)
}
Algorithm::RS256 => {
verify_ring_rsa(&signature::RSA_PKCS1_2048_8192_SHA256, signature, signing_input, key)
}
Algorithm::RS384 => {
verify_ring_rsa(&signature::RSA_PKCS1_2048_8192_SHA384, signature, signing_input, key)
}
Algorithm::RS512 => {
verify_ring_rsa(&signature::RSA_PKCS1_2048_8192_SHA512, signature, signing_input, key)
}
Algorithm::PS256 => {
verify_ring_rsa(&signature::RSA_PSS_2048_8192_SHA256, signature, signing_input, key)
}
Algorithm::PS384 => {
verify_ring_rsa(&signature::RSA_PSS_2048_8192_SHA384, signature, signing_input, key)
}
Algorithm::PS512 => {
verify_ring_rsa(&signature::RSA_PSS_2048_8192_SHA512, signature, signing_input, key)
}
}
}

39
src/crypto/ecdsa.rs Normal file
View File

@ -0,0 +1,39 @@
use ring::{rand, signature};
use crate::algorithms::Algorithm;
use crate::errors::Result;
use crate::pem::decoder::PemEncodedKey;
use crate::serialization::b64_encode;
/// Only used internally when validating EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs.
pub(crate) fn alg_to_ec_verification(
alg: Algorithm,
) -> &'static signature::EcdsaVerificationAlgorithm {
match alg {
Algorithm::ES256 => &signature::ECDSA_P256_SHA256_FIXED,
Algorithm::ES384 => &signature::ECDSA_P384_SHA384_FIXED,
_ => unreachable!("Tried to get EC alg for a non-EC algorithm"),
}
}
/// Only used internally when signing EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs.
pub(crate) fn alg_to_ec_signing(alg: Algorithm) -> &'static signature::EcdsaSigningAlgorithm {
match alg {
Algorithm::ES256 => &signature::ECDSA_P256_SHA256_FIXED_SIGNING,
Algorithm::ES384 => &signature::ECDSA_P384_SHA384_FIXED_SIGNING,
_ => unreachable!("Tried to get EC alg for a non-EC algorithm"),
}
}
/// The actual ECDSA signing + encoding
pub fn sign(
alg: &'static signature::EcdsaSigningAlgorithm,
key: &[u8],
message: &str,
) -> Result<String> {
let pem_key = PemEncodedKey::new(key)?;
let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, pem_key.as_ec_private_key()?)?;
let rng = rand::SystemRandom::new();
let out = signing_key.sign(&rng, message.as_bytes())?;
Ok(b64_encode(out.as_ref()))
}

119
src/crypto/mod.rs Normal file
View File

@ -0,0 +1,119 @@
use ring::constant_time::verify_slices_are_equal;
use ring::{hmac, signature};
use crate::algorithms::Algorithm;
use crate::errors::Result;
use crate::pem::decoder::PemEncodedKey;
use crate::serialization::{b64_decode, b64_encode};
pub(crate) mod ecdsa;
pub(crate) mod rsa;
/// The actual HS signing + encoding
/// Could be in its own file to match RSA/EC but it's 2 lines...
pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], message: &str) -> Result<String> {
let digest = hmac::sign(&hmac::Key::new(alg, key), message.as_bytes());
Ok(b64_encode(digest.as_ref()))
}
/// Take the payload of a JWT, sign it using the algorithm given and return
/// the base64 url safe encoded of the result.
///
/// If you just want to encode a JWT, use `encode` instead.
///
/// `key` is the secret for HMAC and a pem encoded string otherwise
pub fn sign(message: &str, key: &[u8], algorithm: Algorithm) -> Result<String> {
match algorithm {
Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key, message),
Algorithm::HS384 => sign_hmac(hmac::HMAC_SHA384, key, message),
Algorithm::HS512 => sign_hmac(hmac::HMAC_SHA512, key, message),
Algorithm::ES256 | Algorithm::ES384 => {
ecdsa::sign(ecdsa::alg_to_ec_signing(algorithm), key, message)
}
Algorithm::RS256
| Algorithm::RS384
| Algorithm::RS512
| Algorithm::PS256
| Algorithm::PS384
| Algorithm::PS512 => rsa::sign(rsa::alg_to_rsa_signing(algorithm), key, message),
}
}
/// See Ring docs for more details
fn verify_ring(
alg: &'static dyn signature::VerificationAlgorithm,
signature: &str,
message: &str,
key: &[u8],
) -> Result<bool> {
let signature_bytes = b64_decode(signature)?;
let public_key = signature::UnparsedPublicKey::new(alg, key);
let res = public_key.verify(message.as_bytes(), &signature_bytes);
Ok(res.is_ok())
}
/// Compares the signature given with a re-computed signature for HMAC or using the public key
/// for RSA/EC.
///
/// If you just want to decode a JWT, use `decode` instead.
///
/// `signature` is the signature part of a jwt (text after the second '.')
///
/// `message` is base64(header) + "." + base64(claims)
/// For ECDSA/RSA, the `key` is the pem public key. If you want to verify using the public key
/// components (modulus/exponent), use `verify_rsa_components` instead.
pub fn verify(signature: &str, message: &str, key: &[u8], algorithm: Algorithm) -> Result<bool> {
match algorithm {
Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
// we just re-sign the message with the key and compare if they are equal
let signed = sign(message, key, algorithm)?;
Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok())
}
Algorithm::ES256 | Algorithm::ES384 => {
let pem_key = PemEncodedKey::new(key)?;
verify_ring(
ecdsa::alg_to_ec_verification(algorithm),
signature,
message,
pem_key.as_ec_public_key()?,
)
}
Algorithm::RS256
| Algorithm::RS384
| Algorithm::RS512
| Algorithm::PS256
| Algorithm::PS384
| Algorithm::PS512 => {
let pem_key = PemEncodedKey::new(key)?;
verify_ring(
rsa::alg_to_rsa_parameters(algorithm),
signature,
message,
pem_key.as_rsa_key()?,
)
}
}
}
/// Verify the signature given using the (n, e) components of a RSA public key.
///
/// `signature` is the signature part of a jwt (text after the second '.')
///
/// `message` is base64(header) + "." + base64(claims)
pub fn verify_rsa_components(
signature: &str,
message: &str,
components: (&str, &str),
alg: Algorithm,
) -> Result<bool> {
let signature_bytes = b64_decode(signature)?;
rsa::verify_from_components(
rsa::alg_to_rsa_parameters(alg),
&signature_bytes,
message,
components,
)
}

66
src/crypto/rsa.rs Normal file
View File

@ -0,0 +1,66 @@
use ring::{rand, signature};
use simple_asn1::BigUint;
use crate::algorithms::Algorithm;
use crate::errors::{ErrorKind, Result};
use crate::pem::decoder::PemEncodedKey;
use crate::serialization::{b64_decode, b64_encode};
/// Only used internally when validating RSA, to map from our enum to the Ring param structs.
pub(crate) fn alg_to_rsa_parameters(alg: Algorithm) -> &'static signature::RsaParameters {
match alg {
Algorithm::RS256 => &signature::RSA_PKCS1_2048_8192_SHA256,
Algorithm::RS384 => &signature::RSA_PKCS1_2048_8192_SHA384,
Algorithm::RS512 => &signature::RSA_PKCS1_2048_8192_SHA512,
Algorithm::PS256 => &signature::RSA_PSS_2048_8192_SHA256,
Algorithm::PS384 => &signature::RSA_PSS_2048_8192_SHA384,
Algorithm::PS512 => &signature::RSA_PSS_2048_8192_SHA512,
_ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"),
}
}
/// Only used internally when signing with RSA, to map from our enum to the Ring signing structs.
pub(crate) fn alg_to_rsa_signing(alg: Algorithm) -> &'static dyn signature::RsaEncoding {
match alg {
Algorithm::RS256 => &signature::RSA_PKCS1_SHA256,
Algorithm::RS384 => &signature::RSA_PKCS1_SHA384,
Algorithm::RS512 => &signature::RSA_PKCS1_SHA512,
Algorithm::PS256 => &signature::RSA_PSS_SHA256,
Algorithm::PS384 => &signature::RSA_PSS_SHA384,
Algorithm::PS512 => &signature::RSA_PSS_SHA512,
_ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"),
}
}
/// The actual RSA signing + encoding
/// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html
pub(crate) fn sign(
alg: &'static dyn signature::RsaEncoding,
key: &[u8],
message: &str,
) -> Result<String> {
let pem_key = PemEncodedKey::new(key)?;
let key_pair = signature::RsaKeyPair::from_der(pem_key.as_rsa_key()?)
.map_err(|_| ErrorKind::InvalidRsaKey)?;
let mut signature = vec![0; key_pair.public_modulus_len()];
let rng = rand::SystemRandom::new();
key_pair
.sign(alg, &rng, message.as_bytes(), &mut signature)
.map_err(|_| ErrorKind::InvalidRsaKey)?;
Ok(b64_encode(&signature))
}
pub(crate) fn verify_from_components(
alg: &'static signature::RsaParameters,
signature_bytes: &[u8],
message: &str,
components: (&str, &str),
) -> Result<bool> {
let n = BigUint::from_bytes_be(&b64_decode(components.0)?).to_bytes_be();
let e = BigUint::from_bytes_be(&b64_decode(components.1)?).to_bytes_be();
let pubkey = signature::RsaPublicKeyComponents { n, e };
let res = pubkey.verify(alg, message.as_ref(), &signature_bytes);
Ok(res.is_ok())
}

163
src/decoding.rs Normal file
View File

@ -0,0 +1,163 @@
use serde::de::DeserializeOwned;
use crate::crypto::{verify, verify_rsa_components};
use crate::errors::{new_error, ErrorKind, Result};
use crate::header::Header;
use crate::serialization::from_jwt_part_claims;
use crate::validation::{validate, Validation};
/// The return type of a successful call to [decode](fn.decode.html).
#[derive(Debug)]
pub struct TokenData<T> {
/// The decoded JWT header
pub header: Header,
/// The decoded JWT claims
pub claims: T,
}
/// 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(new_error(ErrorKind::InvalidToken)),
}
}};
}
/// Internal way to differentiate between public key types
enum DecodingKey<'a> {
SecretOrPem(&'a [u8]),
RsaModulusExponent { n: &'a str, e: &'a str },
}
fn _decode<T: DeserializeOwned>(
token: &str,
key: DecodingKey,
validation: &Validation,
) -> Result<TokenData<T>> {
let (signature, message) = expect_two!(token.rsplitn(2, '.'));
let (claims, header) = expect_two!(message.rsplitn(2, '.'));
let header = Header::from_encoded(header)?;
if !validation.algorithms.contains(&header.alg) {
return Err(new_error(ErrorKind::InvalidAlgorithm));
}
let is_valid = match key {
DecodingKey::SecretOrPem(k) => verify(signature, message, k, header.alg),
DecodingKey::RsaModulusExponent { n, e } => {
verify_rsa_components(signature, message, (n, e), header.alg)
}
}?;
if !is_valid {
return Err(new_error(ErrorKind::InvalidSignature));
}
let (decoded_claims, claims_map): (T, _) = from_jwt_part_claims(claims)?;
validate(&claims_map, validation)?;
Ok(TokenData { header, claims: decoded_claims })
}
/// Decode and validate a JWT using a secret for HS and a public PEM format for RSA/EC
///
/// If the token or its signature is invalid or the claims fail validation, it will return an error.
///
/// ```rust
/// use serde::{Deserialize, Serialize};
/// use jsonwebtoken::{decode, Validation, Algorithm};
///
/// #[derive(Debug, Serialize, Deserialize)]
/// struct Claims {
/// sub: String,
/// company: String
/// }
///
/// let token = "a.jwt.token".to_string();
/// // Claims is a struct that implements Deserialize
/// let token_message = decode::<Claims>(&token, "secret".as_ref(), &Validation::new(Algorithm::HS256));
/// ```
pub fn decode<T: DeserializeOwned>(
token: &str,
key: &[u8],
validation: &Validation,
) -> Result<TokenData<T>> {
_decode(token, DecodingKey::SecretOrPem(key), validation)
}
/// Decode and validate a JWT using (n, e) base64 encoded public key components for RSA
///
/// If the token or its signature is invalid or the claims fail validation, it will return an error.
///
/// ```rust
/// use serde::{Deserialize, Serialize};
/// use jsonwebtoken::{decode_rsa_components, Validation, Algorithm};
///
/// #[derive(Debug, Serialize, Deserialize)]
/// struct Claims {
/// sub: String,
/// company: String
/// }
///
/// let modulus = "some-base64-data";
/// let exponent = "some-base64-data";
/// let token = "a.jwt.token".to_string();
/// // Claims is a struct that implements Deserialize
/// let token_message = decode_rsa_components::<Claims>(&token, &modulus, &exponent, &Validation::new(Algorithm::HS256));
/// ```
pub fn decode_rsa_components<T: DeserializeOwned>(
token: &str,
modulus: &str,
exponent: &str,
validation: &Validation,
) -> Result<TokenData<T>> {
_decode(token, DecodingKey::RsaModulusExponent { n: modulus, e: exponent }, validation)
}
/// Decode a JWT without any signature verification/validations.
///
/// NOTE: Do not use this unless you know what you are doing! If the token's signature is invalid, it will *not* return an error.
///
/// ```rust
/// use serde::{Deserialize, Serialize};
/// use jsonwebtoken::{dangerous_unsafe_decode, Validation, Algorithm};
///
/// #[derive(Debug, Serialize, Deserialize)]
/// struct Claims {
/// sub: String,
/// company: String
/// }
///
/// let token = "a.jwt.token".to_string();
/// // Claims is a struct that implements Deserialize
/// let token_message = dangerous_unsafe_decode::<Claims>(&token);
/// ```
pub fn dangerous_unsafe_decode<T: DeserializeOwned>(token: &str) -> Result<TokenData<T>> {
let (_, message) = expect_two!(token.rsplitn(2, '.'));
let (claims, header) = expect_two!(message.rsplitn(2, '.'));
let header = Header::from_encoded(header)?;
let (decoded_claims, _): (T, _) = from_jwt_part_claims(claims)?;
Ok(TokenData { header, claims: decoded_claims })
}
/// Decode a JWT without any signature verification/validations and return its [Header](struct.Header.html).
///
/// If the token has an invalid format (ie 3 parts separated by a `.`), it will return an error.
///
/// ```rust
/// use jsonwebtoken::decode_header;
///
/// let token = "a.jwt.token".to_string();
/// let header = decode_header(&token);
/// ```
pub fn decode_header(token: &str) -> Result<Header> {
let (_, message) = expect_two!(token.rsplitn(2, '.'));
let (_, header) = expect_two!(message.rsplitn(2, '.'));
Header::from_encoded(header)
}

View File

@ -45,7 +45,7 @@ pub enum ErrorKind {
/// When a key is provided with an invalid format
InvalidKeyFormat,
// validation error
// Validation errors
/// When a tokens `exp` claim indicates that it has expired
ExpiredSignature,
/// When a tokens `iss` claim does not match the expected issuer
@ -91,11 +91,13 @@ impl StdError for Error {
ErrorKind::InvalidSubject => "invalid subject",
ErrorKind::ImmatureSignature => "immature signature",
ErrorKind::InvalidAlgorithm => "algorithms don't match",
ErrorKind::InvalidAlgorithmName => "not a known algorithm",
ErrorKind::InvalidKeyFormat => "invalid key format",
ErrorKind::Base64(ref err) => err.description(),
ErrorKind::Json(ref err) => err.description(),
ErrorKind::Utf8(ref err) => err.description(),
ErrorKind::Crypto(ref err) => err.description(),
_ => unreachable!(),
ErrorKind::__Nonexhaustive => "unknown error",
}
}
@ -111,11 +113,13 @@ impl StdError for Error {
ErrorKind::InvalidSubject => None,
ErrorKind::ImmatureSignature => None,
ErrorKind::InvalidAlgorithm => None,
ErrorKind::InvalidAlgorithmName => None,
ErrorKind::InvalidKeyFormat => None,
ErrorKind::Base64(ref err) => Some(err),
ErrorKind::Json(ref err) => Some(err),
ErrorKind::Utf8(ref err) => Some(err),
ErrorKind::Crypto(ref err) => Some(err),
_ => unreachable!(),
ErrorKind::__Nonexhaustive => None,
}
}
}
@ -123,21 +127,23 @@ impl StdError for Error {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self.0 {
ErrorKind::InvalidToken => write!(f, "invalid token"),
ErrorKind::InvalidSignature => write!(f, "invalid signature"),
ErrorKind::InvalidEcdsaKey => write!(f, "invalid ECDSA key"),
ErrorKind::InvalidRsaKey => write!(f, "invalid RSA key"),
ErrorKind::ExpiredSignature => write!(f, "expired signature"),
ErrorKind::InvalidIssuer => write!(f, "invalid issuer"),
ErrorKind::InvalidAudience => write!(f, "invalid audience"),
ErrorKind::InvalidSubject => write!(f, "invalid subject"),
ErrorKind::ImmatureSignature => write!(f, "immature signature"),
ErrorKind::InvalidAlgorithm => write!(f, "algorithms don't match"),
ErrorKind::Base64(ref err) => write!(f, "base64 error: {}", err),
ErrorKind::InvalidToken
| ErrorKind::InvalidSignature
| ErrorKind::InvalidEcdsaKey
| ErrorKind::InvalidRsaKey
| ErrorKind::ExpiredSignature
| ErrorKind::InvalidIssuer
| ErrorKind::InvalidAudience
| ErrorKind::InvalidSubject
| ErrorKind::ImmatureSignature
| ErrorKind::InvalidAlgorithm
| ErrorKind::InvalidKeyFormat
| ErrorKind::InvalidAlgorithmName => write!(f, "{}", self.description()),
ErrorKind::Json(ref err) => write!(f, "JSON error: {}", err),
ErrorKind::Utf8(ref err) => write!(f, "UTF-8 error: {}", err),
ErrorKind::Crypto(ref err) => write!(f, "Crypto error: {}", err),
_ => unreachable!(),
ErrorKind::Base64(ref err) => write!(f, "Base64 error: {}", err),
ErrorKind::__Nonexhaustive => write!(f, "Unknown error"),
}
}
}

View File

@ -1,4 +1,8 @@
use serde::{Deserialize, Serialize};
use crate::algorithms::Algorithm;
use crate::errors::Result;
use crate::serialization::b64_decode;
/// A basic JWT header, the alg defaults to HS256 and typ is automatically
/// set to `JWT`. All the other fields are optional.
@ -42,7 +46,7 @@ pub struct Header {
impl Header {
/// Returns a JWT header with the algorithm given
pub fn new(algorithm: Algorithm) -> Header {
pub fn new(algorithm: Algorithm) -> Self {
Header {
typ: Some("JWT".to_string()),
alg: algorithm,
@ -53,6 +57,14 @@ impl Header {
x5t: None,
}
}
/// Converts an encoded part into the Header struct if possible
pub(crate) fn from_encoded(encoded_part: &str) -> Result<Self> {
let decoded = b64_decode(encoded_part)?;
let s = String::from_utf8(decoded)?;
Ok(serde_json::from_str(&s)?)
}
}
impl Default for Header {

View File

@ -1,15 +0,0 @@
/// The supported RSA key formats, see the documentation for ring::signature::RsaKeyPair
/// for more information
pub enum Key<'a> {
/// An unencrypted PKCS#8-encoded key. Can be used with both ECDSA and RSA
/// algorithms when signing. See ring for information.
Pkcs8(&'a [u8]),
/// A binary DER-encoded ASN.1 key. Can only be used with RSA algorithms
/// when signing. See ring for more information
Der(&'a [u8]),
/// This is not a key format, but provided for convenience since HMAC is
/// a supported signing algorithm.
Hmac(&'a [u8]),
/// A Modulus/exponent for a RSA public key
ModulusExponent(&'a [u8], &'a [u8]),
}

View File

@ -3,45 +3,37 @@
//! Documentation: [stable](https://docs.rs/jsonwebtoken/)
#![deny(missing_docs)]
#[macro_use]
extern crate serde_derive;
extern crate base64;
extern crate chrono;
extern crate ring;
extern crate serde;
extern crate serde_json;
mod algorithms;
mod crypto;
/// All the errors
/// Lower level functions, if you want to do something other than JWTs
pub mod crypto;
mod decoding;
/// All the errors that can be encountered while encoding/decoding JWTs
pub mod errors;
mod header;
mod keys;
mod pem;
mod serialization;
mod validation;
pub use algorithms::Algorithm;
pub use crypto::{sign, verify};
pub use decoding::{
dangerous_unsafe_decode, decode, decode_header, decode_rsa_components, TokenData,
};
pub use header::Header;
pub use keys::Key;
pub use serialization::TokenData;
pub use validation::Validation;
use serde::de::DeserializeOwned;
use serde::ser::Serialize;
use crate::errors::{new_error, ErrorKind, Result};
use crate::serialization::{from_jwt_part, from_jwt_part_claims, to_jwt_part};
use crate::validation::validate;
use crate::errors::Result;
use crate::serialization::b64_encode_part;
/// Encode the header and claims given and sign the payload using the algorithm from the header and the key
/// Encode the header and claims given and sign the payload using the algorithm from the header and the key.
/// If the algorithm given is RSA or EC, the key needs to be in the PEM format.
///
/// ```rust,ignore
/// #[macro_use]
/// extern crate serde_derive;
/// ```rust
/// use serde::{Deserialize, Serialize};
/// use jsonwebtoken::{encode, Algorithm, Header};
///
/// /// #[derive(Debug, Serialize, Deserialize)]
/// #[derive(Debug, Serialize, Deserialize)]
/// struct Claims {
/// sub: String,
/// company: String
@ -54,113 +46,13 @@ use crate::validation::validate;
///
/// // my_claims is a struct that implements Serialize
/// // This will create a JWT using HS256 as algorithm
/// let token = encode(&Header::default(), &my_claims, Key::Hmac("secret".as_ref())).unwrap();
/// let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap();
/// ```
pub fn encode<T: Serialize>(header: &Header, claims: &T, key: Key) -> Result<String> {
let encoded_header = to_jwt_part(&header)?;
let encoded_claims = to_jwt_part(&claims)?;
let signing_input = [encoded_header.as_ref(), encoded_claims.as_ref()].join(".");
let signature = sign(&*signing_input, key, header.alg)?;
pub fn encode<T: Serialize>(header: &Header, claims: &T, key: &[u8]) -> Result<String> {
let encoded_header = b64_encode_part(&header)?;
let encoded_claims = b64_encode_part(&claims)?;
let message = [encoded_header.as_ref(), encoded_claims.as_ref()].join(".");
let signature = crypto::sign(&*message, key, header.alg)?;
Ok([signing_input, 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(new_error(ErrorKind::InvalidToken)),
}
}};
}
/// Decode a token into a struct containing 2 fields: `claims` and `header`.
///
/// If the token or its signature is invalid or the claims fail validation, it will return an error.
///
/// ```rust,ignore
/// #[macro_use]
/// extern crate serde_derive;
/// use jsonwebtoken::{decode, Validation, Algorithm};
///
/// #[derive(Debug, Serialize, Deserialize)]
/// struct Claims {
/// sub: String,
/// company: String
/// }
///
/// let token = "a.jwt.token".to_string();
/// // Claims is a struct that implements Deserialize
/// let token_data = decode::<Claims>(&token, Key::Hmac("secret"), &Validation::new(Algorithm::HS256));
/// ```
pub fn decode<T: DeserializeOwned>(
token: &str,
key: Key,
validation: &Validation,
) -> Result<TokenData<T>> {
let (signature, signing_input) = expect_two!(token.rsplitn(2, '.'));
let (claims, header) = expect_two!(signing_input.rsplitn(2, '.'));
let header: Header = from_jwt_part(header)?;
if !verify(signature, signing_input, key, header.alg)? {
return Err(new_error(ErrorKind::InvalidSignature));
}
if !validation.algorithms.contains(&header.alg) {
return Err(new_error(ErrorKind::InvalidAlgorithm));
}
let (decoded_claims, claims_map): (T, _) = from_jwt_part_claims(claims)?;
validate(&claims_map, validation)?;
Ok(TokenData { header, claims: decoded_claims })
}
/// Decode a token without any signature validation into a struct containing 2 fields: `claims` and `header`.
///
/// NOTE: Do not use this unless you know what you are doing! If the token's signature is invalid, it will *not* return an error.
///
/// ```rust,ignore
/// #[macro_use]
/// extern crate serde_derive;
/// use jsonwebtoken::{dangerous_unsafe_decode, Validation, Algorithm};
///
/// #[derive(Debug, Serialize, Deserialize)]
/// struct Claims {
/// sub: String,
/// company: String
/// }
///
/// let token = "a.jwt.token".to_string();
/// // Claims is a struct that implements Deserialize
/// let token_data = dangerous_unsafe_decode::<Claims>(&token, &Validation::new(Algorithm::HS256));
/// ```
pub fn dangerous_unsafe_decode<T: DeserializeOwned>(token: &str) -> Result<TokenData<T>> {
let (_, signing_input) = expect_two!(token.rsplitn(2, '.'));
let (claims, header) = expect_two!(signing_input.rsplitn(2, '.'));
let header: Header = from_jwt_part(header)?;
let (decoded_claims, _): (T, _) = from_jwt_part_claims(claims)?;
Ok(TokenData { header, claims: decoded_claims })
}
/// Decode a token and return the Header. This is not doing any kind of validation: it is meant to be
/// used when you don't know which `alg` the token is using and want to find out.
///
/// If the token has an invalid format, it will return an error.
///
/// ```rust,ignore
/// use jsonwebtoken::decode_header;
///
/// let token = "a.jwt.token".to_string();
/// let header = decode_header(&token);
/// ```
pub fn decode_header(token: &str) -> Result<Header> {
let (_, signing_input) = expect_two!(token.rsplitn(2, '.'));
let (_, header) = expect_two!(signing_input.rsplitn(2, '.'));
from_jwt_part(header)
Ok([message, signature].join("."))
}

205
src/pem/decoder.rs Normal file
View File

@ -0,0 +1,205 @@
use crate::errors::{ErrorKind, Result};
use simple_asn1::{BigUint, OID};
/// Supported PEM files for EC and RSA Public and Private Keys
#[derive(Debug, PartialEq)]
enum PemType {
EcPublic,
EcPrivate,
RsaPublic,
RsaPrivate,
}
#[derive(Debug, PartialEq)]
enum Standard {
// Only for RSA
Pkcs1,
// RSA/EC
Pkcs8,
}
#[derive(Debug, PartialEq)]
enum Classification {
Ec,
Rsa,
}
/// The return type of a successful PEM encoded key with `decode_pem`
///
/// This struct gives a way to parse a string to a key for use in jsonwebtoken.
/// A struct is necessary as it provides the lifetime of the key
///
/// PEM public private keys are encoded PKCS#1 or PKCS#8
/// You will find that with PKCS#8 RSA keys that the PKCS#1 content
/// is embedded inside. This is what is provided to ring via `Key::Der`
/// For EC keys, they are always PKCS#8 on the outside but like RSA keys
/// EC keys contain a section within that ultimately has the configuration
/// that ring uses.
/// Documentation about these formats is at
/// PKCS#1: https://tools.ietf.org/html/rfc8017
/// PKCS#8: https://tools.ietf.org/html/rfc5958
#[derive(Debug)]
pub(crate) struct PemEncodedKey {
content: Vec<u8>,
asn1: Vec<simple_asn1::ASN1Block>,
pem_type: PemType,
standard: Standard,
}
impl PemEncodedKey {
/// Read the PEM file for later key use
pub fn new(input: &[u8]) -> Result<PemEncodedKey> {
match pem::parse(input) {
Ok(content) => {
let pem_contents = content.contents;
let asn1_content = match simple_asn1::from_der(pem_contents.as_slice()) {
Ok(asn1) => asn1,
Err(_) => return Err(ErrorKind::InvalidKeyFormat.into()),
};
match content.tag.as_ref() {
// This handles a PKCS#1 RSA Private key
"RSA PRIVATE KEY" => Ok(PemEncodedKey {
content: pem_contents,
asn1: asn1_content,
pem_type: PemType::RsaPrivate,
standard: Standard::Pkcs1,
}),
"RSA PUBLIC KEY" => Ok(PemEncodedKey {
content: pem_contents,
asn1: asn1_content,
pem_type: PemType::RsaPublic,
standard: Standard::Pkcs1,
}),
// No "EC PRIVATE KEY"
// https://security.stackexchange.com/questions/84327/converting-ecc-private-key-to-pkcs1-format
// "there is no such thing as a "PKCS#1 format" for elliptic curve (EC) keys"
// This handles PKCS#8 public & private keys
tag @ "PRIVATE KEY" | tag @ "PUBLIC KEY" => match classify_pem(&asn1_content) {
Some(c) => {
let is_private = tag == "PRIVATE KEY";
let pem_type = match c {
Classification::Ec => {
if is_private {
PemType::EcPrivate
} else {
PemType::EcPublic
}
}
Classification::Rsa => {
if is_private {
PemType::RsaPrivate
} else {
PemType::RsaPublic
}
}
};
Ok(PemEncodedKey {
content: pem_contents,
asn1: asn1_content,
pem_type,
standard: Standard::Pkcs8,
})
}
None => Err(ErrorKind::InvalidKeyFormat.into()),
},
// Unknown/unsupported type
_ => Err(ErrorKind::InvalidKeyFormat.into()),
}
}
Err(_) => Err(ErrorKind::InvalidKeyFormat.into()),
}
}
/// Can only be PKCS8
pub fn as_ec_private_key(&self) -> Result<&[u8]> {
match self.standard {
Standard::Pkcs1 => Err(ErrorKind::InvalidKeyFormat.into()),
Standard::Pkcs8 => match self.pem_type {
PemType::EcPrivate => Ok(self.content.as_slice()),
_ => Err(ErrorKind::InvalidKeyFormat.into()),
},
}
}
/// Can only be PKCS8
pub fn as_ec_public_key(&self) -> Result<&[u8]> {
match self.standard {
Standard::Pkcs1 => Err(ErrorKind::InvalidKeyFormat.into()),
Standard::Pkcs8 => match self.pem_type {
PemType::EcPublic => extract_first_bitstring(&self.asn1),
_ => Err(ErrorKind::InvalidKeyFormat.into()),
},
}
}
/// Can be PKCS1 or PKCS8
pub fn as_rsa_key(&self) -> Result<&[u8]> {
match self.standard {
Standard::Pkcs1 => Ok(self.content.as_slice()),
Standard::Pkcs8 => match self.pem_type {
PemType::RsaPrivate => extract_first_bitstring(&self.asn1),
PemType::RsaPublic => extract_first_bitstring(&self.asn1),
_ => Err(ErrorKind::InvalidKeyFormat.into()),
},
}
}
}
// This really just finds and returns the first bitstring or octet string
// Which is the x coordinate for EC public keys
// And the DER contents of an RSA key
// Though PKCS#11 keys shouldn't have anything else.
// It will get confusing with certificates.
fn extract_first_bitstring(asn1: &[simple_asn1::ASN1Block]) -> Result<&[u8]> {
for asn1_entry in asn1.iter() {
match asn1_entry {
simple_asn1::ASN1Block::Sequence(_, entries) => {
if let Ok(result) = extract_first_bitstring(entries) {
return Ok(result);
}
}
simple_asn1::ASN1Block::BitString(_, _, value) => {
return Ok(value.as_ref());
}
simple_asn1::ASN1Block::OctetString(_, value) => {
return Ok(value.as_ref());
}
_ => (),
}
}
Err(ErrorKind::InvalidEcdsaKey.into())
}
/// Find whether this is EC or RSA
fn classify_pem(asn1: &[simple_asn1::ASN1Block]) -> Option<Classification> {
// These should be constant but the macro requires
// #![feature(const_vec_new)]
let ec_public_key_oid = simple_asn1::oid!(1, 2, 840, 10_045, 2, 1);
let rsa_public_key_oid = simple_asn1::oid!(1, 2, 840, 113_549, 1, 1, 1);
for asn1_entry in asn1.iter() {
match asn1_entry {
simple_asn1::ASN1Block::Sequence(_, entries) => {
if let Some(classification) = classify_pem(entries) {
return Some(classification);
}
}
simple_asn1::ASN1Block::ObjectIdentifier(_, oid) => {
if oid == ec_public_key_oid {
return Some(Classification::Ec);
}
if oid == rsa_public_key_oid {
return Some(Classification::Rsa);
}
}
_ => {}
}
}
None
}

1
src/pem/mod.rs Normal file
View File

@ -0,0 +1 @@
pub(crate) mod decoder;

View File

@ -1,43 +1,32 @@
use base64;
use serde::de::DeserializeOwned;
use serde::ser::Serialize;
use serde_json::map::Map;
use serde_json::{from_str, to_string, Value};
use crate::errors::Result;
use crate::header::Header;
/// The return type of a successful call to decode
#[derive(Debug)]
pub struct TokenData<T> {
/// The decoded JWT header
pub header: Header,
/// The decoded JWT claims
pub claims: T,
pub(crate) fn b64_encode(input: &[u8]) -> String {
base64::encode_config(input, base64::URL_SAFE_NO_PAD)
}
/// Serializes to JSON and encodes to base64
pub fn to_jwt_part<T: Serialize>(input: &T) -> Result<String> {
let encoded = to_string(input)?;
Ok(base64::encode_config(encoded.as_bytes(), base64::URL_SAFE_NO_PAD))
pub(crate) fn b64_decode(input: &str) -> Result<Vec<u8>> {
base64::decode_config(input, base64::URL_SAFE_NO_PAD).map_err(|e| e.into())
}
/// Decodes from base64 and deserializes from JSON to a struct
pub fn from_jwt_part<B: AsRef<str>, T: DeserializeOwned>(encoded: B) -> Result<T> {
let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?;
let s = String::from_utf8(decoded)?;
Ok(from_str(&s)?)
/// Serializes a struct to JSON and encodes it in base64
pub(crate) fn b64_encode_part<T: Serialize>(input: &T) -> Result<String> {
let json = to_string(input)?;
Ok(b64_encode(json.as_bytes()))
}
/// Decodes from base64 and deserializes from JSON to a struct AND a hashmap
pub fn from_jwt_part_claims<B: AsRef<str>, T: DeserializeOwned>(
/// Decodes from base64 and deserializes from JSON to a struct AND a hashmap of Value so we can
/// run validation on it
pub(crate) fn from_jwt_part_claims<B: AsRef<str>, T: DeserializeOwned>(
encoded: B,
) -> Result<(T, Map<String, Value>)> {
let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?;
let s = String::from_utf8(decoded)?;
let s = String::from_utf8(b64_decode(encoded.as_ref())?)?;
let claims: T = from_str(&s)?;
let map: Map<_, _> = from_str(&s)?;
Ok((claims, map))
let validation_map: Map<_, _> = from_str(&s)?;
Ok((claims, validation_map))
}

View File

@ -1,14 +1,15 @@
use std::collections::HashSet;
use chrono::Utc;
use std::time::{SystemTime, UNIX_EPOCH};
use serde_json::map::Map;
use serde_json::{from_value, Value};
use crate::algorithms::Algorithm;
use crate::errors::{new_error, ErrorKind, Result};
/// Contains the various validations that are applied after decoding a token.
/// Contains the various validations that are applied after decoding a JWT.
///
/// All time validation happen on UTC timestamps.
/// All time validation happen on UTC timestamps as seconds.
///
/// ```rust
/// use jsonwebtoken::Validation;
@ -30,7 +31,7 @@ pub struct Validation {
/// account for clock skew.
///
/// Defaults to `0`.
pub leeway: i64,
pub leeway: u64,
/// Whether to validate the `exp` field.
///
/// It will return an error if the time in the `exp` field is past.
@ -96,12 +97,17 @@ impl Default for Validation {
}
}
fn get_current_timestamp() -> u64 {
let start = SystemTime::now();
start.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs()
}
pub fn validate(claims: &Map<String, Value>, options: &Validation) -> Result<()> {
let now = Utc::now().timestamp();
let now = get_current_timestamp();
if options.validate_exp {
if let Some(exp) = claims.get("exp") {
if from_value::<i64>(exp.clone())? < now - options.leeway {
if from_value::<u64>(exp.clone())? < now - options.leeway {
return Err(new_error(ErrorKind::ExpiredSignature));
}
} else {
@ -111,7 +117,7 @@ pub fn validate(claims: &Map<String, Value>, options: &Validation) -> Result<()>
if options.validate_nbf {
if let Some(nbf) = claims.get("nbf") {
if from_value::<i64>(nbf.clone())? > now + options.leeway {
if from_value::<u64>(nbf.clone())? > now + options.leeway {
return Err(new_error(ErrorKind::ImmatureSignature));
}
} else {
@ -141,8 +147,8 @@ pub fn validate(claims: &Map<String, Value>, options: &Validation) -> Result<()>
if let Some(ref correct_aud) = options.aud {
if let Some(aud) = claims.get("aud") {
let provided_aud: HashSet<String> = from_value(aud.clone())?;
if provided_aud.intersection(correct_aud).count() == 0 {
let provided_aud: HashSet<String> = from_value(aud.clone())?;
if provided_aud.intersection(correct_aud).count() == 0 {
return Err(new_error(ErrorKind::InvalidAudience));
}
} else {
@ -155,18 +161,17 @@ pub fn validate(claims: &Map<String, Value>, options: &Validation) -> Result<()>
#[cfg(test)]
mod tests {
use chrono::Utc;
use serde_json::map::Map;
use serde_json::to_value;
use super::{validate, Validation};
use super::{get_current_timestamp, validate, Validation};
use crate::errors::ErrorKind;
#[test]
fn exp_in_future_ok() {
let mut claims = Map::new();
claims.insert("exp".to_string(), to_value(Utc::now().timestamp() + 10000).unwrap());
claims.insert("exp".to_string(), to_value(get_current_timestamp() + 10000).unwrap());
let res = validate(&claims, &Validation::default());
assert!(res.is_ok());
}
@ -174,7 +179,7 @@ mod tests {
#[test]
fn exp_in_past_fails() {
let mut claims = Map::new();
claims.insert("exp".to_string(), to_value(Utc::now().timestamp() - 100000).unwrap());
claims.insert("exp".to_string(), to_value(get_current_timestamp() - 100000).unwrap());
let res = validate(&claims, &Validation::default());
assert!(res.is_err());
@ -187,7 +192,7 @@ mod tests {
#[test]
fn exp_in_past_but_in_leeway_ok() {
let mut claims = Map::new();
claims.insert("exp".to_string(), to_value(Utc::now().timestamp() - 500).unwrap());
claims.insert("exp".to_string(), to_value(get_current_timestamp() - 500).unwrap());
let validation = Validation { leeway: 1000 * 60, ..Default::default() };
let res = validate(&claims, &validation);
assert!(res.is_ok());
@ -208,7 +213,7 @@ mod tests {
#[test]
fn nbf_in_past_ok() {
let mut claims = Map::new();
claims.insert("nbf".to_string(), to_value(Utc::now().timestamp() - 10000).unwrap());
claims.insert("nbf".to_string(), to_value(get_current_timestamp() - 10000).unwrap());
let validation =
Validation { validate_exp: false, validate_nbf: true, ..Validation::default() };
let res = validate(&claims, &validation);
@ -218,7 +223,7 @@ mod tests {
#[test]
fn nbf_in_future_fails() {
let mut claims = Map::new();
claims.insert("nbf".to_string(), to_value(Utc::now().timestamp() + 100000).unwrap());
claims.insert("nbf".to_string(), to_value(get_current_timestamp() + 100000).unwrap());
let validation =
Validation { validate_exp: false, validate_nbf: true, ..Validation::default() };
let res = validate(&claims, &validation);
@ -233,7 +238,7 @@ mod tests {
#[test]
fn nbf_in_future_but_in_leeway_ok() {
let mut claims = Map::new();
claims.insert("nbf".to_string(), to_value(Utc::now().timestamp() + 500).unwrap());
claims.insert("nbf".to_string(), to_value(get_current_timestamp() + 500).unwrap());
let validation = Validation {
leeway: 1000 * 60,
validate_nbf: true,
@ -403,4 +408,28 @@ mod tests {
_ => assert!(false),
};
}
// https://github.com/Keats/jsonwebtoken/issues/51
#[test]
fn does_validation_in_right_order() {
let mut claims = Map::new();
claims.insert("exp".to_string(), to_value(get_current_timestamp() + 10000).unwrap());
let v = Validation {
leeway: 5,
validate_exp: true,
iss: Some("iss no check".to_string()),
sub: Some("sub no check".to_string()),
..Validation::default()
};
let res = validate(&claims, &v);
// It errors because it needs to validate iss/sub which are missing
assert!(res.is_err());
match res.unwrap_err().kind() {
&ErrorKind::InvalidIssuer => (),
t @ _ => {
println!("{:?}", t);
assert!(false)
}
};
}
}

View File

@ -1,47 +0,0 @@
extern crate jsonwebtoken;
#[macro_use]
extern crate serde_derive;
extern crate chrono;
use chrono::Utc;
use jsonwebtoken::{decode, encode, sign, verify, Algorithm, Header, Key, Validation};
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Claims {
sub: String,
company: String,
exp: i64,
}
#[test]
fn round_trip_sign_verification() {
let privkey = include_bytes!("private_ecdsa_key.pk8");
let encrypted = sign("hello world", Key::Pkcs8(&privkey[..]), Algorithm::ES256).unwrap();
let pubkey = include_bytes!("public_ecdsa_key.pk8");
let is_valid = verify(&encrypted, "hello world", Key::Pkcs8(pubkey), Algorithm::ES256).unwrap();
assert!(is_valid);
}
#[test]
fn round_trip_claim() {
let my_claims = Claims {
sub: "b@b.com".to_string(),
company: "ACME".to_string(),
exp: Utc::now().timestamp() + 10000,
};
let privkey = include_bytes!("private_ecdsa_key.pk8");
let token =
encode(&Header::new(Algorithm::ES256), &my_claims, Key::Pkcs8(&privkey[..])).unwrap();
let pubkey = include_bytes!("public_ecdsa_key.pk8");
let token_data =
decode::<Claims>(&token, Key::Pkcs8(pubkey), &Validation::new(Algorithm::ES256)).unwrap();
assert_eq!(my_claims, token_data.claims);
assert!(token_data.header.kid.is_none());
}
#[test]
#[should_panic(expected = "InvalidKeyFormat")]
fn fails_with_non_pkcs8_key_format() {
let privkey = include_bytes!("private_rsa_key.der");
let _encrypted = sign("hello world", Key::Der(&privkey[..]), Algorithm::ES256).unwrap();
}

62
tests/ecdsa/mod.rs Normal file
View File

@ -0,0 +1,62 @@
use chrono::Utc;
use jsonwebtoken::{
crypto::{sign, verify},
decode, encode, Algorithm, Header, Validation,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Claims {
sub: String,
company: String,
exp: i64,
}
// TODO: remove completely?
//#[test]
//fn round_trip_sign_verification_pk8() {
// let privkey = include_bytes!("private_ecdsa_key.pk8");
// let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap();
// let pubkey = include_bytes!("public_ecdsa_key.pk8");
// let is_valid = verify(&encrypted, "hello world", pubkey, Algorithm::ES256).unwrap();
// assert!(is_valid);
//}
#[test]
fn round_trip_sign_verification_pem() {
let privkey = include_bytes!("private_ecdsa_key.pem");
let pubkey = include_bytes!("public_ecdsa_key.pem");
let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap();
let is_valid = verify(&encrypted, "hello world", pubkey, Algorithm::ES256).unwrap();
assert!(is_valid);
}
#[test]
fn round_trip_claim() {
let privkey = include_bytes!("private_ecdsa_key.pem");
let pubkey = include_bytes!("public_ecdsa_key.pem");
let my_claims = Claims {
sub: "b@b.com".to_string(),
company: "ACME".to_string(),
exp: Utc::now().timestamp() + 10000,
};
let token = encode(&Header::new(Algorithm::ES256), &my_claims, privkey).unwrap();
let token_data = decode::<Claims>(&token, pubkey, &Validation::new(Algorithm::ES256)).unwrap();
assert_eq!(my_claims, token_data.claims);
}
// https://jwt.io/ is often used for examples so ensure their example works with jsonwebtoken
#[test]
fn roundtrip_with_jwtio_example() {
// We currently do not support SEC1 so we use the converted PKCS8 formatted
let privkey = include_bytes!("private_jwtio_pkcs8.pem");
let pubkey = include_bytes!("public_jwtio.pem");
let my_claims = Claims {
sub: "b@b.com".to_string(),
company: "ACME".to_string(),
exp: Utc::now().timestamp() + 10000,
};
let token = encode(&Header::new(Algorithm::ES384), &my_claims, privkey).unwrap();
let token_data = decode::<Claims>(&token, pubkey, &Validation::new(Algorithm::ES384)).unwrap();
assert_eq!(my_claims, token_data.claims);
}

View File

@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWTFfCGljY6aw3Hrt
kHmPRiazukxPLb6ilpRAewjW8nihRANCAATDskChT+Altkm9X7MI69T3IUmrQU0L
950IxEzvw/x5BMEINRMrXLBJhqzO9Bm+d6JbqA21YQmd1Kt4RzLJR1W+
-----END PRIVATE KEY-----

View File

@ -0,0 +1,6 @@
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDCAHpFQ62QnGCEvYh/pE9QmR1C9aLcDItRbslbmhen/h1tt8AyMhske
enT+rAyyPhGgBwYFK4EEACKhZANiAAQLW5ZJePZzMIPAxMtZXkEWbDF0zo9f2n4+
T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw8lE5IPUWpgu553SteKigiKLU
PeNpbqmYZUkWGh3MLfVzLmx85ii2vMU=
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,6 @@
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCAHpFQ62QnGCEvYh/p
E9QmR1C9aLcDItRbslbmhen/h1tt8AyMhskeenT+rAyyPhGhZANiAAQLW5ZJePZz
MIPAxMtZXkEWbDF0zo9f2n4+T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw
8lE5IPUWpgu553SteKigiKLUPeNpbqmYZUkWGh3MLfVzLmx85ii2vMU=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEw7JAoU/gJbZJvV+zCOvU9yFJq0FN
C/edCMRM78P8eQTBCDUTK1ywSYaszvQZvneiW6gNtWEJndSreEcyyUdVvg==
-----END PUBLIC KEY-----

View File

@ -0,0 +1,5 @@
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+
Pk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii
1D3jaW6pmGVJFhodzC31cy5sfOYotrzF
-----END PUBLIC KEY-----

132
tests/hmac.rs Normal file
View File

@ -0,0 +1,132 @@
use chrono::Utc;
use jsonwebtoken::{
crypto::{sign, verify},
dangerous_unsafe_decode, decode, decode_header, encode, Algorithm, Header, Validation,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Claims {
sub: String,
company: String,
exp: i64,
}
#[test]
fn sign_hs256() {
let result = sign("hello world", b"secret", Algorithm::HS256).unwrap();
let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo";
assert_eq!(result, expected);
}
#[test]
fn verify_hs256() {
let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo";
let valid = verify(sig, "hello world", b"secret", Algorithm::HS256).unwrap();
assert!(valid);
}
#[test]
fn encode_with_custom_header() {
let my_claims = Claims {
sub: "b@b.com".to_string(),
company: "ACME".to_string(),
exp: Utc::now().timestamp() + 10000,
};
let mut header = Header::default();
header.kid = Some("kid".to_string());
let token = encode(&header, &my_claims, b"secret").unwrap();
let token_data = decode::<Claims>(&token, b"secret", &Validation::default()).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(),
exp: Utc::now().timestamp() + 10000,
};
let token = encode(&Header::default(), &my_claims, b"secret").unwrap();
let token_data = decode::<Claims>(&token, b"secret", &Validation::default()).unwrap();
assert_eq!(my_claims, token_data.claims);
assert!(token_data.header.kid.is_none());
}
#[test]
fn decode_token() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98";
let claims = decode::<Claims>(token, b"secret", &Validation::default());
println!("{:?}", claims);
claims.unwrap();
}
#[test]
#[should_panic(expected = "InvalidToken")]
fn decode_token_missing_parts() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
let claims = decode::<Claims>(token, b"secret", &Validation::default());
claims.unwrap();
}
#[test]
#[should_panic(expected = "InvalidSignature")]
fn decode_token_invalid_signature() {
let token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong";
let claims = decode::<Claims>(token, b"secret", &Validation::default());
claims.unwrap();
}
#[test]
#[should_panic(expected = "InvalidAlgorithm")]
fn decode_token_wrong_algorithm() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY";
let claims = decode::<Claims>(token, b"secret", &Validation::new(Algorithm::RS512));
claims.unwrap();
}
#[test]
fn decode_token_with_bytes_secret() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks";
let claims = decode::<Claims>(token, b"\x01\x02\x03", &Validation::default());
assert!(claims.is_ok());
}
#[test]
fn decode_header_only() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.S";
let header = decode_header(token).unwrap();
assert_eq!(header.alg, Algorithm::HS256);
assert_eq!(header.typ, Some("JWT".to_string()));
}
#[test]
fn dangerous_unsafe_decode_token() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98";
let claims = dangerous_unsafe_decode::<Claims>(token);
claims.unwrap();
}
#[test]
#[should_panic(expected = "InvalidToken")]
fn dangerous_unsafe_decode_token_missing_parts() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
let claims = dangerous_unsafe_decode::<Claims>(token);
claims.unwrap();
}
#[test]
fn dangerous_unsafe_decode_token_invalid_signature() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.wrong";
let claims = dangerous_unsafe_decode::<Claims>(token);
claims.unwrap();
}
#[test]
fn dangerous_unsafe_decode_token_wrong_algorithm() {
let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.fLxey-hxAKX5rNHHIx1_Ch0KmrbiuoakDVbsJjLWrx8fbjKjrPuWMYEJzTU3SBnYgnZokC-wqSdqckXUOunC-g";
let claims = dangerous_unsafe_decode::<Claims>(token);
claims.unwrap();
}

View File

@ -1,175 +1,2 @@
extern crate jsonwebtoken;
#[macro_use]
extern crate serde_derive;
extern crate chrono;
use chrono::Utc;
use jsonwebtoken::{
dangerous_unsafe_decode, decode, decode_header, encode, sign, verify, Algorithm, Header, Key,
Validation,
};
use std::str::FromStr;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Claims {
sub: String,
company: String,
exp: i64,
}
#[test]
fn sign_hs256() {
let result = sign("hello world", Key::Hmac(b"secret"), Algorithm::HS256).unwrap();
let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo";
assert_eq!(result, expected);
}
#[test]
fn verify_hs256() {
let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo";
let valid = verify(sig, "hello world", Key::Hmac(b"secret"), Algorithm::HS256).unwrap();
assert!(valid);
}
#[test]
fn encode_with_custom_header() {
let my_claims = Claims {
sub: "b@b.com".to_string(),
company: "ACME".to_string(),
exp: Utc::now().timestamp() + 10000,
};
let mut header = Header::default();
header.kid = Some("kid".to_string());
let token = encode(&header, &my_claims, Key::Hmac(b"secret")).unwrap();
let token_data =
decode::<Claims>(&token, Key::Hmac(b"secret"), &Validation::default()).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(),
exp: Utc::now().timestamp() + 10000,
};
let token = encode(&Header::default(), &my_claims, Key::Hmac(b"secret")).unwrap();
let token_data =
decode::<Claims>(&token, Key::Hmac(b"secret"), &Validation::default()).unwrap();
assert_eq!(my_claims, token_data.claims);
assert!(token_data.header.kid.is_none());
}
#[test]
fn decode_token() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98";
let claims = decode::<Claims>(token, Key::Hmac(b"secret"), &Validation::default());
println!("{:?}", claims);
claims.unwrap();
}
#[test]
#[should_panic(expected = "InvalidToken")]
fn decode_token_missing_parts() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
let claims = decode::<Claims>(token, Key::Hmac(b"secret"), &Validation::default());
claims.unwrap();
}
#[test]
#[should_panic(expected = "InvalidSignature")]
fn decode_token_invalid_signature() {
let token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong";
let claims = decode::<Claims>(token, Key::Hmac(b"secret"), &Validation::default());
claims.unwrap();
}
#[test]
#[should_panic(expected = "InvalidAlgorithm")]
fn decode_token_wrong_algorithm() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY";
let claims = decode::<Claims>(token, Key::Hmac(b"secret"), &Validation::new(Algorithm::RS512));
claims.unwrap();
}
#[test]
fn decode_token_with_bytes_secret() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks";
let claims = decode::<Claims>(token, Key::Hmac(b"\x01\x02\x03"), &Validation::default());
assert!(claims.is_ok());
}
#[test]
fn decode_header_only() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.S";
let header = decode_header(token).unwrap();
assert_eq!(header.alg, Algorithm::HS256);
assert_eq!(header.typ, Some("JWT".to_string()));
}
#[test]
fn dangerous_unsafe_decode_token() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98";
let claims = dangerous_unsafe_decode::<Claims>(token);
claims.unwrap();
}
#[test]
#[should_panic(expected = "InvalidToken")]
fn dangerous_unsafe_decode_token_missing_parts() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
let claims = dangerous_unsafe_decode::<Claims>(token);
claims.unwrap();
}
#[test]
fn dangerous_unsafe_decode_token_invalid_signature() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.wrong";
let claims = dangerous_unsafe_decode::<Claims>(token);
claims.unwrap();
}
#[test]
fn dangerous_unsafe_decode_token_wrong_algorithm() {
let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.fLxey-hxAKX5rNHHIx1_Ch0KmrbiuoakDVbsJjLWrx8fbjKjrPuWMYEJzTU3SBnYgnZokC-wqSdqckXUOunC-g";
let claims = dangerous_unsafe_decode::<Claims>(token);
claims.unwrap();
}
// https://github.com/Keats/jsonwebtoken/issues/51
#[test]
fn does_validation_in_right_order() {
let my_claims = Claims {
sub: "b@b.com".to_string(),
company: "ACME".to_string(),
exp: Utc::now().timestamp() + 10000,
};
let token = encode(&Header::default(), &my_claims, Key::Hmac(b"secret")).unwrap();
let v = Validation {
leeway: 5,
validate_exp: true,
iss: Some("iss no check".to_string()),
sub: Some("sub no check".to_string()),
..Validation::default()
};
let res = decode::<Claims>(&token, Key::Hmac(b"secret"), &v);
assert!(res.is_err());
println!("{:?}", res);
//assert!(res.is_ok());
}
#[test]
fn generate_algorithm_enum_from_str() {
assert!(Algorithm::from_str("HS256").is_ok());
assert!(Algorithm::from_str("HS384").is_ok());
assert!(Algorithm::from_str("HS512").is_ok());
assert!(Algorithm::from_str("RS256").is_ok());
assert!(Algorithm::from_str("RS384").is_ok());
assert!(Algorithm::from_str("RS512").is_ok());
assert!(Algorithm::from_str("PS256").is_ok());
assert!(Algorithm::from_str("PS384").is_ok());
assert!(Algorithm::from_str("PS512").is_ok());
assert!(Algorithm::from_str("").is_err());
}
mod ecdsa;
mod rsa;

View File

@ -1,100 +0,0 @@
extern crate jsonwebtoken;
#[macro_use]
extern crate serde_derive;
extern crate chrono;
use chrono::Utc;
use jsonwebtoken::{decode, encode, sign, verify, Algorithm, Header, Key, Validation};
const RSA_ALGORITHMS: &[Algorithm] = &[
Algorithm::RS256,
Algorithm::RS384,
Algorithm::RS512,
Algorithm::PS256,
Algorithm::PS384,
Algorithm::PS512,
];
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Claims {
sub: String,
company: String,
exp: i64,
}
#[test]
fn round_trip_sign_verification() {
let privkey = include_bytes!("private_rsa_key.der");
for &alg in RSA_ALGORITHMS {
let encrypted = sign("hello world", Key::Der(&privkey[..]), alg).unwrap();
let is_valid =
verify(&encrypted, "hello world", Key::Der(include_bytes!("public_rsa_key.der")), alg)
.unwrap();
assert!(is_valid);
}
}
#[test]
fn round_trip_claim() {
let my_claims = Claims {
sub: "b@b.com".to_string(),
company: "ACME".to_string(),
exp: Utc::now().timestamp() + 10000,
};
let privkey = include_bytes!("private_rsa_key.der");
for &alg in RSA_ALGORITHMS {
let token = encode(&Header::new(alg), &my_claims, Key::Der(&privkey[..])).unwrap();
let token_data = decode::<Claims>(
&token,
Key::Der(include_bytes!("public_rsa_key.der")),
&Validation::new(alg),
)
.unwrap();
assert_eq!(my_claims, token_data.claims);
assert!(token_data.header.kid.is_none());
}
}
#[test]
#[should_panic(expected = "InvalidRsaKey")]
fn fails_with_different_key_format() {
let privkey = include_bytes!("private_rsa_key.der");
sign("hello world", Key::Pkcs8(&privkey[..]), Algorithm::RS256).unwrap();
}
#[test]
fn rsa_modulus_exponent() {
let modulus: Vec<u8> = vec![
0xc9, 0x11, 0x3a, 0xac, 0x7b, 0x8d, 0x47, 0x44, 0x1b, 0x1c, 0xed, 0xc7, 0xdc, 0xab, 0x76,
0xa4, 0xe2, 0x86, 0x56, 0x14, 0x2a, 0x19, 0x95, 0xc8, 0x9c, 0xe7, 0x6e, 0x40, 0xdc, 0x57,
0xce, 0xe2, 0xa5, 0xbd, 0x04, 0xcb, 0x51, 0x3b, 0xf8, 0x97, 0x8b, 0x20, 0x82, 0x1e, 0x7f,
0x09, 0x86, 0x22, 0xfd, 0xcb, 0xc8, 0xf9, 0x25, 0xd5, 0x4f, 0xd9, 0x0f, 0x59, 0x22, 0x97,
0xc4, 0x95, 0xc1, 0x5d, 0xdf, 0xf8, 0x2e, 0x4b, 0xdc, 0x3e, 0xe5, 0x1a, 0x90, 0x1a, 0x00,
0x91, 0xf8, 0x7e, 0x7a, 0x21, 0x55, 0x32, 0x1d, 0x95, 0xad, 0x4c, 0x96, 0xca, 0x3d, 0xcc,
0x16, 0x5d, 0x07, 0x4d, 0x51, 0x7d, 0x2b, 0x04, 0x57, 0x2c, 0x07, 0x30, 0x91, 0x11, 0x22,
0x4b, 0x79, 0xe9, 0x4e, 0x11, 0xd1, 0xc8, 0x8c, 0x6e, 0xcb, 0x46, 0x4c, 0x79, 0x97, 0xf1,
0x54, 0xbe, 0x5a, 0xac, 0xc8, 0x70, 0xd5, 0x24, 0x44, 0x2c, 0x1f, 0x07, 0xa0, 0x67, 0xc6,
0xfc, 0x0b, 0x47, 0xf3, 0xd0, 0x48, 0x13, 0xd8, 0xc3, 0x04, 0x76, 0x7d, 0x74, 0xb7, 0xa5,
0x2b, 0xd6, 0xb5, 0xf3, 0x8c, 0xc0, 0x7f, 0xc2, 0xf0, 0xa0, 0xf2, 0xf1, 0xbc, 0x96, 0xf7,
0x22, 0x5e, 0x67, 0x9d, 0xca, 0x8f, 0x71, 0x27, 0xca, 0x0c, 0x3a, 0x1d, 0x30, 0x50, 0x48,
0x31, 0xce, 0x25, 0x43, 0x30, 0xca, 0x2f, 0x98, 0x2f, 0x9a, 0x25, 0xcb, 0x5c, 0x1d, 0x40,
0x18, 0xb9, 0xbc, 0x28, 0x18, 0xdf, 0x13, 0xcb, 0x37, 0x2f, 0x9c, 0x6a, 0x8b, 0xec, 0x94,
0xa1, 0xdf, 0xa3, 0xf0, 0xcb, 0x6f, 0x22, 0x3f, 0x35, 0xd9, 0xd9, 0x12, 0xe1, 0x03, 0x22,
0x45, 0x53, 0x7f, 0x6f, 0x2d, 0xa1, 0xdd, 0x96, 0x3c, 0x2d, 0x85, 0x46, 0xae, 0xa6, 0x57,
0x65, 0x37, 0x20, 0x9f, 0x6b, 0xa3, 0x9f, 0xcb, 0x8a, 0x8d, 0x72, 0xd9, 0x54, 0x3e, 0x53,
0x75,
];
let exponent: Vec<u8> = vec![0x01, 0x00, 0x01];
let privkey = include_bytes!("private_rsa_key.der");
let encrypted = sign("hello world", Key::Der(&privkey[..]), Algorithm::RS256).unwrap();
let is_valid = verify(
&encrypted,
"hello world",
Key::ModulusExponent(&modulus, &exponent),
Algorithm::RS256,
)
.unwrap();
assert!(is_valid);
}

110
tests/rsa/mod.rs Normal file
View File

@ -0,0 +1,110 @@
use chrono::Utc;
use jsonwebtoken::{
crypto::{sign, verify},
decode, decode_rsa_components, encode, Algorithm, Header, Validation,
};
use serde::{Deserialize, Serialize};
const RSA_ALGORITHMS: &[Algorithm] = &[
Algorithm::RS256,
Algorithm::RS384,
Algorithm::RS512,
Algorithm::PS256,
Algorithm::PS384,
Algorithm::PS512,
];
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Claims {
sub: String,
company: String,
exp: i64,
}
#[test]
fn round_trip_sign_verification_pem_pkcs1() {
let privkey_pem = include_bytes!("private_rsa_key_pkcs1.pem");
let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem");
for &alg in RSA_ALGORITHMS {
let encrypted = sign("hello world", privkey_pem, alg).unwrap();
let is_valid = verify(&encrypted, "hello world", pubkey_pem, alg).unwrap();
assert!(is_valid);
}
}
#[test]
fn round_trip_sign_verification_pem_pkcs8() {
let privkey_pem = include_bytes!("private_rsa_key_pkcs8.pem");
let pubkey_pem = include_bytes!("public_rsa_key_pkcs8.pem");
for &alg in RSA_ALGORITHMS {
let encrypted = sign("hello world", privkey_pem, alg).unwrap();
let is_valid = verify(&encrypted, "hello world", pubkey_pem, alg).unwrap();
assert!(is_valid);
}
}
#[test]
fn round_trip_claim() {
let my_claims = Claims {
sub: "b@b.com".to_string(),
company: "ACME".to_string(),
exp: Utc::now().timestamp() + 10000,
};
let privkey = include_bytes!("private_rsa_key_pkcs1.pem");
for &alg in RSA_ALGORITHMS {
let token = encode(&Header::new(alg), &my_claims, privkey).unwrap();
let token_data = decode::<Claims>(
&token,
include_bytes!("public_rsa_key_pkcs1.pem"),
&Validation::new(alg),
)
.unwrap();
assert_eq!(my_claims, token_data.claims);
assert!(token_data.header.kid.is_none());
}
}
#[test]
fn rsa_modulus_exponent() {
let privkey = include_str!("private_rsa_key_pkcs1.pem");
let my_claims = Claims {
sub: "b@b.com".to_string(),
company: "ACME".to_string(),
exp: Utc::now().timestamp() + 10000,
};
let n = "yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ";
let e = "AQAB";
let encrypted = encode(&Header::new(Algorithm::RS256), &my_claims, privkey.as_ref()).unwrap();
let res = decode_rsa_components::<Claims>(&encrypted, n, e, &Validation::new(Algorithm::RS256));
assert!(res.is_ok());
}
#[test]
#[should_panic(expected = "InvalidKeyFormat")]
fn fails_with_non_pkcs8_key_format() {
let _encrypted =
sign("hello world", include_bytes!("private_rsa_key_pkcs1.pem"), Algorithm::ES256).unwrap();
}
// https://jwt.io/ is often used for examples so ensure their example works with jsonwebtoken
#[test]
fn roundtrip_with_jwtio_example_jey() {
let privkey_pem = include_bytes!("private_jwtio.pem");
let pubkey_pem = include_bytes!("public_jwtio.pem");
let my_claims = Claims {
sub: "b@b.com".to_string(),
company: "ACME".to_string(),
exp: Utc::now().timestamp() + 10000,
};
for &alg in RSA_ALGORITHMS {
let token = encode(&Header::new(alg), &my_claims, privkey_pem).unwrap();
let token_data = decode::<Claims>(&token, pubkey_pem, &Validation::new(alg)).unwrap();
assert_eq!(my_claims, token_data.claims);
}
}

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw
kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr
m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi
NQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV
3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2
QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs
kFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go
amMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM
+bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9
D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC
0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y
lSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+
hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp
bU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X
+jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B
BwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC
2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx
QYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz
5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9
Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0
NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j
8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma
3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K
y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB
jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJETqse41HRBsc
7cfcq3ak4oZWFCoZlcic525A3FfO4qW9BMtRO/iXiyCCHn8JhiL9y8j5JdVP2Q9Z
IpfElcFd3/guS9w+5RqQGgCR+H56IVUyHZWtTJbKPcwWXQdNUX0rBFcsBzCRESJL
eelOEdHIjG7LRkx5l/FUvlqsyHDVJEQsHwegZ8b8C0fz0EgT2MMEdn10t6Ur1rXz
jMB/wvCg8vG8lvciXmedyo9xJ8oMOh0wUEgxziVDMMovmC+aJctcHUAYubwoGN8T
yzcvnGqL7JSh36Pwy28iPzXZ2RLhAyJFU39vLaHdljwthUaupldlNyCfa6Ofy4qN
ctlUPlN1AgMBAAECggEAdESTQjQ70O8QIp1ZSkCYXeZjuhj081CK7jhhp/4ChK7J
GlFQZMwiBze7d6K84TwAtfQGZhQ7km25E1kOm+3hIDCoKdVSKch/oL54f/BK6sKl
qlIzQEAenho4DuKCm3I4yAw9gEc0DV70DuMTR0LEpYyXcNJY3KNBOTjN5EYQAR9s
2MeurpgK2MdJlIuZaIbzSGd+diiz2E6vkmcufJLtmYUT/k/ddWvEtz+1DnO6bRHh
xuuDMeJA/lGB/EYloSLtdyCF6sII6C6slJJtgfb0bPy7l8VtL5iDyz46IKyzdyzW
tKAn394dm7MYR1RlUBEfqFUyNK7C+pVMVoTwCC2V4QKBgQD64syfiQ2oeUlLYDm4
CcKSP3RnES02bcTyEDFSuGyyS1jldI4A8GXHJ/lG5EYgiYa1RUivge4lJrlNfjyf
dV230xgKms7+JiXqag1FI+3mqjAgg4mYiNjaao8N8O3/PD59wMPeWYImsWXNyeHS
55rUKiHERtCcvdzKl4u35ZtTqQKBgQDNKnX2bVqOJ4WSqCgHRhOm386ugPHfy+8j
m6cicmUR46ND6ggBB03bCnEG9OtGisxTo/TuYVRu3WP4KjoJs2LD5fwdwJqpgtHl
yVsk45Y1Hfo+7M6lAuR8rzCi6kHHNb0HyBmZjysHWZsn79ZM+sQnLpgaYgQGRbKV
DZWlbw7g7QKBgQCl1u+98UGXAP1jFutwbPsx40IVszP4y5ypCe0gqgon3UiY/G+1
zTLp79GGe/SjI2VpQ7AlW7TI2A0bXXvDSDi3/5Dfya9ULnFXv9yfvH1QwWToySpW
Kvd1gYSoiX84/WCtjZOr0e0HmLIb0vw0hqZA4szJSqoxQgvF22EfIWaIaQKBgQCf
34+OmMYw8fEvSCPxDxVvOwW2i7pvV14hFEDYIeZKW2W1HWBhVMzBfFB5SE8yaCQy
pRfOzj9aKOCm2FjjiErVNpkQoi6jGtLvScnhZAt/lr2TXTrl8OwVkPrIaN0bG/AS
aUYxmBPCpXu3UjhfQiWqFq/mFyzlqlgvuCc9g95HPQKBgAscKP8mLxdKwOgX8yFW
GcZ0izY/30012ajdHY+/QK5lsMoxTnn0skdS+spLxaS5ZEO4qvPVb8RAoCkWMMal
2pOhmquJQVDPDLuZHdrIiKiDM20dy9sMfHygWcZjQ4WSxf/J7T9canLZIXFhHAZT
3wc9h4G8BBCtWN2TN/LsGZdB
-----END PRIVATE KEY-----

View File

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
MwIDAQAB
-----END PUBLIC KEY-----

View File

@ -0,0 +1,8 @@
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAyRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4
l4sggh5/CYYi/cvI+SXVT9kPWSKXxJXBXd/4LkvcPuUakBoAkfh+eiFVMh2VrUyW
yj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG
/AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4l
QzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi+yUod+j8MtvIj812dkS4QMiRVN/by2h
3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQIDAQAB
-----END RSA PUBLIC KEY-----