commit
e89fa41b7a
|
@ -0,0 +1,37 @@
|
|||
name: ci
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
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
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,5 +1,15 @@
|
|||
# Changelog
|
||||
|
||||
# 7.0.0 (2020-01-28)
|
||||
|
||||
- Add support for PS256, PS384 and PS512
|
||||
- Add support for verifying with modulus/exponent components for RSA
|
||||
- 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
|
||||
- Add EncodingKey/DecodingKey API to improve performance and UX
|
||||
|
||||
## 6.0.1 (2019-05-10)
|
||||
|
||||
- Fix Algorithm mapping in FromStr for RSA
|
||||
|
|
30
Cargo.toml
30
Cargo.toml
|
@ -1,19 +1,27 @@
|
|||
[package]
|
||||
name = "jsonwebtoken"
|
||||
version = "6.0.1"
|
||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||
version = "7.0.0"
|
||||
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", "jwk"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde = "1.0"
|
||||
ring = "0.14.4"
|
||||
base64 = "0.10"
|
||||
untrusted = "0.6"
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
ring = { version = "0.16.5", features = ["std"] }
|
||||
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-maintained" }
|
||||
|
|
147
README.md
147
README.md
|
@ -4,22 +4,32 @@
|
|||
|
||||
[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 JSON Web Tokens are.
|
||||
|
||||
## Installation
|
||||
Add the following to Cargo.toml:
|
||||
|
||||
```toml
|
||||
jsonwebtoken = "6"
|
||||
serde_derive = "1"
|
||||
serde = "1"
|
||||
jsonwebtoken = "7"
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
```
|
||||
|
||||
## Help wanted for v7
|
||||
The minimum required Rust version is 1.36.
|
||||
|
||||
v6 was released as a stopgap version to update Ring and add a couple of features like ES256/384.
|
||||
The results are not very ergonomic once we factor in all the possible ways to load a RSA key for example.
|
||||
A possible solution is to have decoder types as described in https://github.com/Keats/jsonwebtoken/issues/76
|
||||
but I currently do not have the time to implement it myself.
|
||||
I will take any better idea as well of course!
|
||||
## Algorithms
|
||||
This library currently supports the following:
|
||||
|
||||
- HS256
|
||||
- HS384
|
||||
- HS512
|
||||
- RS256
|
||||
- RS384
|
||||
- RS512
|
||||
- PS256
|
||||
- PS384
|
||||
- PS512
|
||||
- ES256
|
||||
- ES384
|
||||
|
||||
|
||||
## How to use
|
||||
|
@ -27,11 +37,8 @@ Complete examples are available in the examples directory: a basic one and one w
|
|||
|
||||
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, EncodingKey, DecodingKey};
|
||||
|
||||
/// Our claims struct, it needs to derive `Serialize` and/or `Deserialize`
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -42,11 +49,11 @@ 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())?;
|
||||
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref()))?;
|
||||
```
|
||||
|
||||
#### Custom headers & changing algorithm
|
||||
|
@ -54,38 +61,90 @@ 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())?;
|
||||
let token = encode(&header, &my_claims, &EncodingKey::from_secret("secret".as_ref()))?;
|
||||
```
|
||||
Look at `examples/custom_header.rs` for a full working example.
|
||||
|
||||
### Decoding
|
||||
### Encoding
|
||||
|
||||
```rust
|
||||
let token = decode::<Claims>(&token, "secret".as_ref(), &Validation::default())?;
|
||||
// token is a struct with 2 params: header and claims
|
||||
// HS256
|
||||
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref()))?;
|
||||
// RSA
|
||||
let token = encode(&Header::new(Algorithm::RS256), &my_claims, &EncodingKey::from_rsa_pem(include_bytes!("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 or DER format.
|
||||
|
||||
If your key is in PEM format, it is better performance wise to generate the `EncodingKey` once in a `lazy_static` or
|
||||
something similar and reuse it.
|
||||
|
||||
### Decoding
|
||||
|
||||
```rust
|
||||
// `token` is a struct with 2 fields: `header` and `claims` where `claims` is your own struct.
|
||||
let token = decode::<Claims>(&token, &DecodingKey::from_secret("secret".as_ref()), &Validation::default())?;
|
||||
```
|
||||
`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 or DER 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` and `nbf` claims 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::<Claims>(&token, &DecodingKey::from_rsa_components(jwk["n"], jwk["e"]), &Validation::new(Algorithm::RS256))?;
|
||||
```
|
||||
|
||||
If your key is in PEM format, it is better performance wise to generate the `DecodingKey` once in a `lazy_static` or
|
||||
something similar and reuse it.
|
||||
|
||||
### 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.
|
||||
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 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`.
|
||||
|
||||
|
@ -106,30 +165,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
|
||||
- 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.
|
||||
|
|
|
@ -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, Validation};
|
||||
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
|
@ -15,12 +13,15 @@ struct Claims {
|
|||
#[bench]
|
||||
fn bench_encode(b: &mut test::Bencher) {
|
||||
let claim = Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned() };
|
||||
let key = EncodingKey::from_secret("secret".as_ref());
|
||||
|
||||
b.iter(|| encode(&Header::default(), &claim, "secret".as_ref()));
|
||||
b.iter(|| encode(&Header::default(), &claim, &key));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_decode(b: &mut test::Bencher) {
|
||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
|
||||
b.iter(|| decode::<Claims>(token, "secret".as_ref(), &Validation::default()));
|
||||
let key = DecodingKey::from_secret("secret".as_ref());
|
||||
|
||||
b.iter(|| decode::<Claims>(token, &key, &Validation::default()));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
use chrono::prelude::*;
|
||||
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const SECRET: &str = "some-secret";
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
sub: String,
|
||||
#[serde(with = "jwt_numeric_date")]
|
||||
iat: DateTime<Utc>,
|
||||
#[serde(with = "jwt_numeric_date")]
|
||||
exp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
mod jwt_numeric_date {
|
||||
//! Custom serialization of DateTime<Utc> to conform with the JWT spec (RFC 7519 section 2, "Numeric Date")
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||
|
||||
/// Serializes a DateTime<Utc> to a Unix timestamp (milliseconds since 1970/1/1T00:00:00T)
|
||||
pub fn serialize<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let timestamp = date.timestamp();
|
||||
serializer.serialize_i64(timestamp)
|
||||
}
|
||||
|
||||
/// Attempts to deserialize an i64 and use as a Unix timestamp
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Utc.timestamp_opt(i64::deserialize(deserializer)?, 0)
|
||||
.single() // If there are multiple or no valid DateTimes from timestamp, return None
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid Unix timestamp value"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
const EXPECTED_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gRGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjMyNTAzNjgwMDAwfQ.RTgha0S53MjPC2pMA4e2oMzaBxSY3DMjiYR2qFfV55A";
|
||||
|
||||
use super::super::{Claims, SECRET};
|
||||
|
||||
#[test]
|
||||
fn round_trip() {
|
||||
let sub = "Custom DateTime ser/de".to_string();
|
||||
let iat = Utc.timestamp(0, 0);
|
||||
let exp = Utc.timestamp(32503680000, 0);
|
||||
|
||||
let claims = Claims { sub: sub.clone(), iat, exp };
|
||||
|
||||
let token =
|
||||
encode(&Header::default(), &claims, &EncodingKey::from_secret(SECRET.as_ref()))
|
||||
.expect("Failed to encode claims");
|
||||
|
||||
assert_eq!(&token, EXPECTED_TOKEN);
|
||||
|
||||
let decoded = decode::<Claims>(
|
||||
&token,
|
||||
&DecodingKey::from_secret(SECRET.as_ref()),
|
||||
&Validation::default(),
|
||||
)
|
||||
.expect("Failed to decode token");
|
||||
|
||||
assert_eq!(decoded.claims, claims);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail_on_invalid_timestamp() {
|
||||
// A token with the expiry of i64::MAX + 1
|
||||
let overflow_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gRGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjkyMjMzNzIwMzY4NTQ3NzYwMDB9.G2PKreA27U8_xOwuIeCYXacFYeR46f9FyENIZfCrvEc";
|
||||
|
||||
let decode_result =
|
||||
decode::<Claims>(&overflow_token, SECRET.as_ref(), &Validation::default());
|
||||
|
||||
assert!(decode_result.is_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let sub = "Custom DateTime ser/de".to_string();
|
||||
let iat = Utc::now();
|
||||
let exp = iat + chrono::Duration::days(1);
|
||||
|
||||
let claims = Claims { sub: sub.clone(), iat, exp };
|
||||
|
||||
let token = jsonwebtoken::encode(
|
||||
&Header::default(),
|
||||
&claims,
|
||||
&EncodingKey::from_secret(SECRET.as_ref()),
|
||||
)?;
|
||||
|
||||
println!("serialized token: {}", &token);
|
||||
|
||||
let token_data = jsonwebtoken::decode::<Claims>(
|
||||
&token,
|
||||
&DecodingKey::from_secret(SECRET.as_ref()),
|
||||
&Validation::default(),
|
||||
)?;
|
||||
|
||||
println!("token data:\n{:#?}", &token_data);
|
||||
Ok(())
|
||||
}
|
|
@ -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, Validation};
|
||||
use jsonwebtoken::errors::ErrorKind;
|
||||
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
|
@ -15,26 +13,29 @@ struct Claims {
|
|||
fn main() {
|
||||
let my_claims =
|
||||
Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned(), exp: 10000000000 };
|
||||
let key = "secret";
|
||||
let key = b"secret";
|
||||
|
||||
let mut header = Header::default();
|
||||
header.kid = Some("signing_key".to_owned());
|
||||
header.alg = Algorithm::HS512;
|
||||
|
||||
let token = match encode(&header, &my_claims, key.as_ref()) {
|
||||
let token = match encode(&header, &my_claims, &EncodingKey::from_secret(key)) {
|
||||
Ok(t) => t,
|
||||
Err(_) => panic!(), // in practice you would return the error
|
||||
};
|
||||
println!("{:?}", token);
|
||||
|
||||
let token_data =
|
||||
match decode::<Claims>(&token, key.as_ref(), &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,
|
||||
&DecodingKey::from_secret(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);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
extern crate jsonwebtoken as jwt;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use jwt::errors::ErrorKind;
|
||||
use jwt::{decode, encode, Header, Validation};
|
||||
use jsonwebtoken::errors::ErrorKind;
|
||||
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
|
@ -13,16 +10,16 @@ struct Claims {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let key = b"secret";
|
||||
let my_claims =
|
||||
Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned(), exp: 10000000000 };
|
||||
let key = "secret";
|
||||
let token = match encode(&Header::default(), &my_claims, key.as_ref()) {
|
||||
let token = match encode(&Header::default(), &my_claims, &EncodingKey::from_secret(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.as_ref(), &validation) {
|
||||
let token_data = match decode::<Claims>(&token, &DecodingKey::from_secret(key), &validation) {
|
||||
Ok(c) => c,
|
||||
Err(err) => match *err.kind() {
|
||||
ErrorKind::InvalidToken => panic!("Token is invalid"), // Example on how to handle a specific error
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
use crate::errors::{Error, ErrorKind, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub(crate) enum AlgorithmFamily {
|
||||
Hmac,
|
||||
Rsa,
|
||||
Ec,
|
||||
}
|
||||
|
||||
/// The algorithms supported for signing/verifying JWTs
|
||||
#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum Algorithm {
|
||||
/// HMAC using SHA-256
|
||||
HS256,
|
||||
/// HMAC using SHA-384
|
||||
HS384,
|
||||
/// HMAC using SHA-512
|
||||
HS512,
|
||||
|
||||
/// ECDSA using SHA-256
|
||||
ES256,
|
||||
/// ECDSA using SHA-384
|
||||
ES384,
|
||||
|
||||
/// RSASSA-PKCS1-v1_5 using SHA-256
|
||||
RS256,
|
||||
/// RSASSA-PKCS1-v1_5 using SHA-384
|
||||
RS384,
|
||||
/// RSASSA-PKCS1-v1_5 using SHA-512
|
||||
RS512,
|
||||
|
||||
/// RSASSA-PSS using SHA-256
|
||||
PS256,
|
||||
/// RSASSA-PSS using SHA-384
|
||||
PS384,
|
||||
/// RSASSA-PSS using SHA-512
|
||||
PS512,
|
||||
}
|
||||
|
||||
impl Default for Algorithm {
|
||||
fn default() -> Self {
|
||||
Algorithm::HS256
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Algorithm {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
"HS256" => Ok(Algorithm::HS256),
|
||||
"HS384" => Ok(Algorithm::HS384),
|
||||
"HS512" => Ok(Algorithm::HS512),
|
||||
"ES256" => Ok(Algorithm::ES256),
|
||||
"ES384" => Ok(Algorithm::ES384),
|
||||
"RS256" => Ok(Algorithm::RS256),
|
||||
"RS384" => Ok(Algorithm::RS384),
|
||||
"PS256" => Ok(Algorithm::PS256),
|
||||
"PS384" => Ok(Algorithm::PS384),
|
||||
"PS512" => Ok(Algorithm::PS512),
|
||||
"RS512" => Ok(Algorithm::RS512),
|
||||
_ => Err(ErrorKind::InvalidAlgorithmName.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Algorithm {
|
||||
pub(crate) fn family(self) -> AlgorithmFamily {
|
||||
match self {
|
||||
Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => AlgorithmFamily::Hmac,
|
||||
Algorithm::RS256
|
||||
| Algorithm::RS384
|
||||
| Algorithm::RS512
|
||||
| Algorithm::PS256
|
||||
| Algorithm::PS384
|
||||
| Algorithm::PS512 => AlgorithmFamily::Rsa,
|
||||
Algorithm::ES256 | Algorithm::ES384 => AlgorithmFamily::Ec,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
}
|
162
src/crypto.rs
162
src/crypto.rs
|
@ -1,162 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use base64;
|
||||
use ring::constant_time::verify_slices_are_equal;
|
||||
use ring::{digest, hmac, rand, signature};
|
||||
use std::str::FromStr;
|
||||
use untrusted;
|
||||
|
||||
use errors::{new_error, Error, ErrorKind, Result};
|
||||
|
||||
/// The algorithms supported for signing/verifying
|
||||
#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum Algorithm {
|
||||
/// HMAC using SHA-256
|
||||
HS256,
|
||||
/// HMAC using SHA-384
|
||||
HS384,
|
||||
/// HMAC using SHA-512
|
||||
HS512,
|
||||
|
||||
/// ECDSA using SHA-256
|
||||
ES256,
|
||||
|
||||
/// ECDSA using SHA-384
|
||||
ES384,
|
||||
|
||||
/// RSASSA-PKCS1-v1_5 using SHA-256
|
||||
RS256,
|
||||
/// RSASSA-PKCS1-v1_5 using SHA-384
|
||||
RS384,
|
||||
/// RSASSA-PKCS1-v1_5 using SHA-512
|
||||
RS512,
|
||||
}
|
||||
|
||||
impl Default for Algorithm {
|
||||
fn default() -> Self {
|
||||
Algorithm::HS256
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Algorithm {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
"HS256" => Ok(Algorithm::HS256),
|
||||
"HS384" => Ok(Algorithm::HS384),
|
||||
"HS512" => Ok(Algorithm::HS512),
|
||||
"ES256" => Ok(Algorithm::ES256),
|
||||
"ES384" => Ok(Algorithm::ES384),
|
||||
"RS256" => Ok(Algorithm::RS256),
|
||||
"RS384" => Ok(Algorithm::RS384),
|
||||
"RS512" => Ok(Algorithm::RS512),
|
||||
_ => Err(new_error(ErrorKind::InvalidAlgorithmName)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The actual HS signing + encoding
|
||||
fn sign_hmac(alg: &'static digest::Algorithm, key: &[u8], signing_input: &str) -> Result<String> {
|
||||
let signing_key = hmac::SigningKey::new(alg, key);
|
||||
let digest = hmac::sign(&signing_key, signing_input.as_bytes());
|
||||
|
||||
Ok(base64::encode_config::<hmac::Signature>(&digest, base64::URL_SAFE_NO_PAD))
|
||||
}
|
||||
|
||||
/// The actual ECDSA signing + encoding
|
||||
fn sign_ecdsa(alg: &'static signature::EcdsaSigningAlgorithm, key: &[u8], signing_input: &str) -> Result<String> {
|
||||
let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, untrusted::Input::from(key))?;
|
||||
let rng = rand::SystemRandom::new();
|
||||
let sig = signing_key.sign(&rng, untrusted::Input::from(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 signature::RsaEncoding, key: &[u8], signing_input: &str) -> Result<String> {
|
||||
let key_pair = Arc::new(
|
||||
signature::RsaKeyPair::from_der(untrusted::Input::from(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, 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: &[u8], algorithm: Algorithm) -> Result<String> {
|
||||
match algorithm {
|
||||
Algorithm::HS256 => sign_hmac(&digest::SHA256, key, signing_input),
|
||||
Algorithm::HS384 => sign_hmac(&digest::SHA384, key, signing_input),
|
||||
Algorithm::HS512 => sign_hmac(&digest::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),
|
||||
}
|
||||
}
|
||||
|
||||
/// See Ring docs for more details
|
||||
fn verify_ring(
|
||||
alg: &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_der = untrusted::Input::from(key);
|
||||
let message = untrusted::Input::from(signing_input.as_bytes());
|
||||
let expected_signature = untrusted::Input::from(signature_bytes.as_slice());
|
||||
|
||||
let res = signature::verify(alg, public_key_der, message, expected_signature);
|
||||
|
||||
Ok(res.is_ok())
|
||||
}
|
||||
|
||||
/// 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: &[u8],
|
||||
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(&signature::ECDSA_P256_SHA256_FIXED, signature, signing_input, key)
|
||||
}
|
||||
Algorithm::ES384 => {
|
||||
verify_ring(&signature::ECDSA_P384_SHA384_FIXED, signature, signing_input, key)
|
||||
}
|
||||
Algorithm::RS256 => {
|
||||
verify_ring(&signature::RSA_PKCS1_2048_8192_SHA256, signature, signing_input, key)
|
||||
}
|
||||
Algorithm::RS384 => {
|
||||
verify_ring(&signature::RSA_PKCS1_2048_8192_SHA384, signature, signing_input, key)
|
||||
}
|
||||
Algorithm::RS512 => {
|
||||
verify_ring(&signature::RSA_PKCS1_2048_8192_SHA512, signature, signing_input, key)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
use ring::{rand, signature};
|
||||
|
||||
use crate::algorithms::Algorithm;
|
||||
use crate::errors::Result;
|
||||
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
|
||||
/// The key needs to be in PKCS8 format
|
||||
pub fn sign(
|
||||
alg: &'static signature::EcdsaSigningAlgorithm,
|
||||
key: &[u8],
|
||||
message: &str,
|
||||
) -> Result<String> {
|
||||
let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, key)?;
|
||||
let rng = rand::SystemRandom::new();
|
||||
let out = signing_key.sign(&rng, message.as_bytes())?;
|
||||
Ok(b64_encode(out.as_ref()))
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
use ring::constant_time::verify_slices_are_equal;
|
||||
use ring::{hmac, signature};
|
||||
|
||||
use crate::algorithms::Algorithm;
|
||||
use crate::decoding::{DecodingKey, DecodingKeyKind};
|
||||
use crate::encoding::EncodingKey;
|
||||
use crate::errors::Result;
|
||||
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.
|
||||
pub fn sign(message: &str, key: &EncodingKey, algorithm: Algorithm) -> Result<String> {
|
||||
match algorithm {
|
||||
Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key.inner(), message),
|
||||
Algorithm::HS384 => sign_hmac(hmac::HMAC_SHA384, key.inner(), message),
|
||||
Algorithm::HS512 => sign_hmac(hmac::HMAC_SHA512, key.inner(), message),
|
||||
|
||||
Algorithm::ES256 | Algorithm::ES384 => {
|
||||
ecdsa::sign(ecdsa::alg_to_ec_signing(algorithm), key.inner(), message)
|
||||
}
|
||||
|
||||
Algorithm::RS256
|
||||
| Algorithm::RS384
|
||||
| Algorithm::RS512
|
||||
| Algorithm::PS256
|
||||
| Algorithm::PS384
|
||||
| Algorithm::PS512 => rsa::sign(rsa::alg_to_rsa_signing(algorithm), key.inner(), 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)
|
||||
pub fn verify(
|
||||
signature: &str,
|
||||
message: &str,
|
||||
key: &DecodingKey,
|
||||
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, &EncodingKey::from_secret(key.as_bytes()), algorithm)?;
|
||||
Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok())
|
||||
}
|
||||
Algorithm::ES256 | Algorithm::ES384 => verify_ring(
|
||||
ecdsa::alg_to_ec_verification(algorithm),
|
||||
signature,
|
||||
message,
|
||||
key.as_bytes(),
|
||||
),
|
||||
Algorithm::RS256
|
||||
| Algorithm::RS384
|
||||
| Algorithm::RS512
|
||||
| Algorithm::PS256
|
||||
| Algorithm::PS384
|
||||
| Algorithm::PS512 => {
|
||||
let alg = rsa::alg_to_rsa_parameters(algorithm);
|
||||
match &key.kind {
|
||||
DecodingKeyKind::SecretOrDer(bytes) => verify_ring(alg, signature, message, bytes),
|
||||
DecodingKeyKind::RsaModulusExponent { n, e } => {
|
||||
rsa::verify_from_components(alg, signature, message, (n, e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
use ring::{rand, signature};
|
||||
use simple_asn1::BigUint;
|
||||
|
||||
use crate::algorithms::Algorithm;
|
||||
use crate::errors::{ErrorKind, Result};
|
||||
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
|
||||
/// The key needs to be in PKCS8 format
|
||||
/// 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 key_pair = signature::RsaKeyPair::from_der(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))
|
||||
}
|
||||
|
||||
/// Checks that a signature is valid based on the (n, e) RSA pubkey components
|
||||
pub(crate) fn verify_from_components(
|
||||
alg: &'static signature::RsaParameters,
|
||||
signature: &str,
|
||||
message: &str,
|
||||
components: (&str, &str),
|
||||
) -> Result<bool> {
|
||||
let signature_bytes = b64_decode(signature)?;
|
||||
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())
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::algorithms::AlgorithmFamily;
|
||||
use crate::crypto::verify;
|
||||
use crate::errors::{new_error, ErrorKind, Result};
|
||||
use crate::header::Header;
|
||||
use crate::pem::decoder::PemEncodedKey;
|
||||
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)),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) enum DecodingKeyKind<'a> {
|
||||
SecretOrDer(Cow<'a, [u8]>),
|
||||
RsaModulusExponent { n: &'a str, e: &'a str },
|
||||
}
|
||||
|
||||
/// All the different kind of keys we can use to decode a JWT
|
||||
/// This key can be re-used so make sure you only initialize it once if you can for better performance
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DecodingKey<'a> {
|
||||
pub(crate) family: AlgorithmFamily,
|
||||
pub(crate) kind: DecodingKeyKind<'a>,
|
||||
}
|
||||
|
||||
impl<'a> DecodingKey<'a> {
|
||||
/// If you're using HMAC, use this.
|
||||
pub fn from_secret(secret: &'a [u8]) -> Self {
|
||||
DecodingKey {
|
||||
family: AlgorithmFamily::Hmac,
|
||||
kind: DecodingKeyKind::SecretOrDer(Cow::Borrowed(secret)),
|
||||
}
|
||||
}
|
||||
|
||||
/// If you're using HMAC with a base64 encoded, use this.
|
||||
pub fn from_base64_secret(secret: &str) -> Result<Self> {
|
||||
let out = base64::decode(&secret)?;
|
||||
Ok(DecodingKey {
|
||||
family: AlgorithmFamily::Hmac,
|
||||
kind: DecodingKeyKind::SecretOrDer(Cow::Owned(out)),
|
||||
})
|
||||
}
|
||||
|
||||
/// If you are loading a public RSA key in a PEM format, use this.
|
||||
pub fn from_rsa_pem(key: &'a [u8]) -> Result<Self> {
|
||||
let pem_key = PemEncodedKey::new(key)?;
|
||||
let content = pem_key.as_rsa_key()?;
|
||||
Ok(DecodingKey {
|
||||
family: AlgorithmFamily::Rsa,
|
||||
kind: DecodingKeyKind::SecretOrDer(Cow::Owned(content.to_vec())),
|
||||
})
|
||||
}
|
||||
|
||||
/// If you have (n, e) RSA public key components, use this.
|
||||
pub fn from_rsa_components(modulus: &'a str, exponent: &'a str) -> Self {
|
||||
DecodingKey {
|
||||
family: AlgorithmFamily::Rsa,
|
||||
kind: DecodingKeyKind::RsaModulusExponent { n: modulus, e: exponent },
|
||||
}
|
||||
}
|
||||
|
||||
/// If you have a ECDSA public key in PEM format, use this.
|
||||
pub fn from_ec_pem(key: &'a [u8]) -> Result<Self> {
|
||||
let pem_key = PemEncodedKey::new(key)?;
|
||||
let content = pem_key.as_ec_public_key()?;
|
||||
Ok(DecodingKey {
|
||||
family: AlgorithmFamily::Ec,
|
||||
kind: DecodingKeyKind::SecretOrDer(Cow::Owned(content.to_vec())),
|
||||
})
|
||||
}
|
||||
|
||||
/// If you know what you're doing and have a RSA DER encoded public key, use this.
|
||||
pub fn from_rsa_der(der: &'a [u8]) -> Self {
|
||||
DecodingKey {
|
||||
family: AlgorithmFamily::Rsa,
|
||||
kind: DecodingKeyKind::SecretOrDer(Cow::Borrowed(der)),
|
||||
}
|
||||
}
|
||||
|
||||
/// If you know what you're doing and have a RSA EC encoded public key, use this.
|
||||
pub fn from_ec_der(der: &'a [u8]) -> Self {
|
||||
DecodingKey {
|
||||
family: AlgorithmFamily::Ec,
|
||||
kind: DecodingKeyKind::SecretOrDer(Cow::Borrowed(der)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_bytes(&self) -> &[u8] {
|
||||
match &self.kind {
|
||||
DecodingKeyKind::SecretOrDer(b) => &b,
|
||||
DecodingKeyKind::RsaModulusExponent { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode and validate a JWT
|
||||
///
|
||||
/// 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, DecodingKey, 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, &DecodingKey::from_secret("secret".as_ref()), &Validation::new(Algorithm::HS256));
|
||||
/// ```
|
||||
pub fn decode<T: DeserializeOwned>(
|
||||
token: &str,
|
||||
key: &DecodingKey,
|
||||
validation: &Validation,
|
||||
) -> Result<TokenData<T>> {
|
||||
for alg in &validation.algorithms {
|
||||
if key.family != alg.family() {
|
||||
return Err(new_error(ErrorKind::InvalidAlgorithm));
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
if !verify(signature, message, key, header.alg)? {
|
||||
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 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)
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
use serde::ser::Serialize;
|
||||
|
||||
use crate::algorithms::AlgorithmFamily;
|
||||
use crate::crypto;
|
||||
use crate::errors::{new_error, ErrorKind, Result};
|
||||
use crate::header::Header;
|
||||
use crate::pem::decoder::PemEncodedKey;
|
||||
use crate::serialization::b64_encode_part;
|
||||
|
||||
/// A key to encode a JWT with. Can be a secret, a PEM-encoded key or a DER-encoded key.
|
||||
/// This key can be re-used so make sure you only initialize it once if you can for better performance
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct EncodingKey {
|
||||
pub(crate) family: AlgorithmFamily,
|
||||
content: Vec<u8>,
|
||||
}
|
||||
|
||||
impl EncodingKey {
|
||||
/// If you're using a HMAC secret that is not base64, use that.
|
||||
pub fn from_secret(secret: &[u8]) -> Self {
|
||||
EncodingKey { family: AlgorithmFamily::Hmac, content: secret.to_vec() }
|
||||
}
|
||||
|
||||
/// If you have a base64 HMAC secret, use that.
|
||||
pub fn from_base64_secret(secret: &str) -> Result<Self> {
|
||||
let out = base64::decode(&secret)?;
|
||||
Ok(EncodingKey { family: AlgorithmFamily::Hmac, content: out })
|
||||
}
|
||||
|
||||
/// If you are loading a RSA key from a .pem file.
|
||||
/// This errors if the key is not a valid RSA key.
|
||||
pub fn from_rsa_pem(key: &[u8]) -> Result<Self> {
|
||||
let pem_key = PemEncodedKey::new(key)?;
|
||||
let content = pem_key.as_rsa_key()?;
|
||||
Ok(EncodingKey { family: AlgorithmFamily::Rsa, content: content.to_vec() })
|
||||
}
|
||||
|
||||
/// If you are loading a ECDSA key from a .pem file
|
||||
/// This errors if the key is not a valid private EC key
|
||||
pub fn from_ec_pem(key: &[u8]) -> Result<Self> {
|
||||
let pem_key = PemEncodedKey::new(key)?;
|
||||
let content = pem_key.as_ec_private_key()?;
|
||||
Ok(EncodingKey { family: AlgorithmFamily::Ec, content: content.to_vec() })
|
||||
}
|
||||
|
||||
/// If you know what you're doing and have the DER-encoded key, for RSA only
|
||||
pub fn from_rsa_der(der: &[u8]) -> Self {
|
||||
EncodingKey { family: AlgorithmFamily::Rsa, content: der.to_vec() }
|
||||
}
|
||||
|
||||
/// If you know what you're doing and have the DER-encoded key, for ECDSA
|
||||
pub fn from_ec_der(der: &[u8]) -> Self {
|
||||
EncodingKey { family: AlgorithmFamily::Ec, content: der.to_vec() }
|
||||
}
|
||||
|
||||
pub(crate) fn inner(&self) -> &[u8] {
|
||||
&self.content
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// use serde::{Deserialize, Serialize};
|
||||
/// use jsonwebtoken::{encode, Algorithm, Header, EncodingKey};
|
||||
///
|
||||
/// #[derive(Debug, Serialize, Deserialize)]
|
||||
/// struct Claims {
|
||||
/// sub: String,
|
||||
/// company: String
|
||||
/// }
|
||||
///
|
||||
/// let my_claims = Claims {
|
||||
/// sub: "b@b.com".to_owned(),
|
||||
/// company: "ACME".to_owned()
|
||||
/// };
|
||||
///
|
||||
/// // my_claims is a struct that implements Serialize
|
||||
/// // This will create a JWT using HS256 as algorithm
|
||||
/// let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref())).unwrap();
|
||||
/// ```
|
||||
pub fn encode<T: Serialize>(header: &Header, claims: &T, key: &EncodingKey) -> Result<String> {
|
||||
if key.family != header.alg.family() {
|
||||
return Err(new_error(ErrorKind::InvalidAlgorithm));
|
||||
}
|
||||
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([message, signature].join("."))
|
||||
}
|
|
@ -42,8 +42,10 @@ pub enum ErrorKind {
|
|||
InvalidRsaKey,
|
||||
/// When the algorithm from string doesn't match the one passed to `from_str`
|
||||
InvalidAlgorithmName,
|
||||
/// When a key is provided with an invalid format
|
||||
InvalidKeyFormat,
|
||||
|
||||
// validation error
|
||||
// Validation errors
|
||||
/// When a token’s `exp` claim indicates that it has expired
|
||||
ExpiredSignature,
|
||||
/// When a token’s `iss` claim does not match the expected issuer
|
||||
|
@ -54,7 +56,8 @@ pub enum ErrorKind {
|
|||
InvalidSubject,
|
||||
/// When a token’s nbf claim represents a time in the future
|
||||
ImmatureSignature,
|
||||
/// When the algorithm in the header doesn't match the one passed to `decode`
|
||||
/// When the algorithm in the header doesn't match the one passed to `decode` or the encoding/decoding key
|
||||
/// used doesn't match the alg requested
|
||||
InvalidAlgorithm,
|
||||
|
||||
// 3rd party errors
|
||||
|
@ -77,27 +80,7 @@ pub enum ErrorKind {
|
|||
}
|
||||
|
||||
impl StdError for Error {
|
||||
fn description(&self) -> &str {
|
||||
match *self.0 {
|
||||
ErrorKind::InvalidToken => "invalid token",
|
||||
ErrorKind::InvalidSignature => "invalid signature",
|
||||
ErrorKind::InvalidEcdsaKey => "invalid ECDSA key",
|
||||
ErrorKind::InvalidRsaKey => "invalid RSA key",
|
||||
ErrorKind::ExpiredSignature => "expired signature",
|
||||
ErrorKind::InvalidIssuer => "invalid issuer",
|
||||
ErrorKind::InvalidAudience => "invalid audience",
|
||||
ErrorKind::InvalidSubject => "invalid subject",
|
||||
ErrorKind::ImmatureSignature => "immature signature",
|
||||
ErrorKind::InvalidAlgorithm => "algorithms don't match",
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&StdError> {
|
||||
fn cause(&self) -> Option<&dyn StdError> {
|
||||
match *self.0 {
|
||||
ErrorKind::InvalidToken => None,
|
||||
ErrorKind::InvalidSignature => None,
|
||||
|
@ -109,11 +92,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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,21 +106,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),
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
use crypto::Algorithm;
|
||||
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 {
|
||||
|
|
162
src/lib.rs
162
src/lib.rs
|
@ -1,164 +1,22 @@
|
|||
//! Create and parses JWT (JSON Web Tokens)
|
||||
//!
|
||||
//! Documentation: [stable](https://docs.rs/jsonwebtoken/)
|
||||
#![recursion_limit = "300"]
|
||||
#![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;
|
||||
extern crate untrusted;
|
||||
|
||||
mod crypto;
|
||||
/// All the errors, generated using error-chain
|
||||
mod algorithms;
|
||||
/// Lower level functions, if you want to do something other than JWTs
|
||||
pub mod crypto;
|
||||
mod decoding;
|
||||
mod encoding;
|
||||
/// All the errors that can be encountered while encoding/decoding JWTs
|
||||
pub mod errors;
|
||||
mod header;
|
||||
mod pem;
|
||||
mod serialization;
|
||||
mod validation;
|
||||
|
||||
pub use crypto::{sign, verify, Algorithm};
|
||||
pub use algorithms::Algorithm;
|
||||
pub use decoding::{dangerous_unsafe_decode, decode, decode_header, DecodingKey, TokenData};
|
||||
pub use encoding::{encode, EncodingKey};
|
||||
pub use header::Header;
|
||||
pub use serialization::TokenData;
|
||||
pub use validation::Validation;
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::ser::Serialize;
|
||||
|
||||
use errors::{new_error, ErrorKind, Result};
|
||||
use serialization::{from_jwt_part, from_jwt_part_claims, to_jwt_part};
|
||||
use validation::validate;
|
||||
|
||||
/// Encode the header and claims given and sign the payload using the algorithm from the header and the key
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// #[macro_use]
|
||||
/// extern crate serde_derive;
|
||||
/// use jsonwebtoken::{encode, Algorithm, Header};
|
||||
///
|
||||
/// /// #[derive(Debug, Serialize, Deserialize)]
|
||||
/// struct Claims {
|
||||
/// sub: String,
|
||||
/// company: String
|
||||
/// }
|
||||
///
|
||||
/// let my_claims = Claims {
|
||||
/// sub: "b@b.com".to_owned(),
|
||||
/// company: "ACME".to_owned()
|
||||
/// };
|
||||
///
|
||||
/// // my_claims is a struct that implements Serialize
|
||||
/// // This will create a JWT using HS256 as algorithm
|
||||
/// let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap();
|
||||
/// ```
|
||||
pub fn encode<T: Serialize>(header: &Header, claims: &T, key: &[u8]) -> 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)?;
|
||||
|
||||
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, "secret", &Validation::new(Algorithm::HS256));
|
||||
/// ```
|
||||
pub fn decode<T: DeserializeOwned>(
|
||||
token: &str,
|
||||
key: &[u8],
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
pub(crate) mod decoder;
|
|
@ -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 errors::Result;
|
||||
use header::Header;
|
||||
use crate::errors::Result;
|
||||
|
||||
/// 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))
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use chrono::Utc;
|
||||
use serde::ser::Serialize;
|
||||
use std::collections::HashSet;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use serde_json::map::Map;
|
||||
use serde_json::{from_value, to_value, Value};
|
||||
use serde_json::{from_value, Value};
|
||||
|
||||
use crypto::Algorithm;
|
||||
use errors::{new_error, ErrorKind, Result};
|
||||
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;
|
||||
|
@ -21,7 +22,7 @@ use errors::{new_error, ErrorKind, Result};
|
|||
///
|
||||
/// // Setting audience
|
||||
/// let mut validation = Validation::default();
|
||||
/// validation.set_audience(&"Me"); // string
|
||||
/// validation.set_audience(&["Me"]); // a single string
|
||||
/// validation.set_audience(&["Me", "You"]); // array of strings
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
@ -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.
|
||||
|
@ -43,13 +44,11 @@ pub struct Validation {
|
|||
///
|
||||
/// Defaults to `false`.
|
||||
pub validate_nbf: bool,
|
||||
/// If it contains a value, the validation will check that the `aud` field is the same as the
|
||||
/// one provided and will error otherwise.
|
||||
/// Since `aud` can be either a String or a Vec<String> in the JWT spec, you will need to use
|
||||
/// the [set_audience](struct.Validation.html#method.set_audience) method to set it.
|
||||
/// If it contains a value, the validation will check that the `aud` field is a member of the
|
||||
/// audience provided and will error otherwise.
|
||||
///
|
||||
/// Defaults to `None`.
|
||||
pub aud: Option<Value>,
|
||||
pub aud: Option<HashSet<String>>,
|
||||
/// If it contains a value, the validation will check that the `iss` field is the same as the
|
||||
/// one provided and will error otherwise.
|
||||
///
|
||||
|
@ -75,10 +74,9 @@ impl Validation {
|
|||
validation
|
||||
}
|
||||
|
||||
/// Since `aud` can be either a String or an array of String in the JWT spec, this method will take
|
||||
/// care of serializing the value.
|
||||
pub fn set_audience<T: Serialize>(&mut self, audience: &T) {
|
||||
self.aud = Some(to_value(audience).unwrap());
|
||||
/// `aud` is a collection of one or more acceptable audience members
|
||||
pub fn set_audience<T: ToString>(&mut self, items: &[T]) {
|
||||
self.aud = Some(items.iter().map(|x| x.to_string()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,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 {
|
||||
|
@ -114,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 {
|
||||
|
@ -144,9 +147,20 @@ 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") {
|
||||
if aud != correct_aud {
|
||||
return Err(new_error(ErrorKind::InvalidAudience));
|
||||
}
|
||||
match aud {
|
||||
Value::String(aud_found) => {
|
||||
if !correct_aud.contains(aud_found) {
|
||||
return Err(new_error(ErrorKind::InvalidAudience));
|
||||
}
|
||||
}
|
||||
Value::Array(_) => {
|
||||
let provided_aud: HashSet<String> = from_value(aud.clone())?;
|
||||
if provided_aud.intersection(correct_aud).count() == 0 {
|
||||
return Err(new_error(ErrorKind::InvalidAudience));
|
||||
}
|
||||
}
|
||||
_ => return Err(new_error(ErrorKind::InvalidAudience)),
|
||||
};
|
||||
} else {
|
||||
return Err(new_error(ErrorKind::InvalidAudience));
|
||||
}
|
||||
|
@ -157,18 +171,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 errors::ErrorKind;
|
||||
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());
|
||||
}
|
||||
|
@ -176,7 +189,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());
|
||||
|
||||
|
@ -189,7 +202,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());
|
||||
|
@ -210,7 +223,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);
|
||||
|
@ -220,7 +233,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);
|
||||
|
@ -235,7 +248,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,
|
||||
|
@ -345,9 +358,9 @@ mod tests {
|
|||
#[test]
|
||||
fn aud_string_ok() {
|
||||
let mut claims = Map::new();
|
||||
claims.insert("aud".to_string(), to_value("Everyone").unwrap());
|
||||
claims.insert("aud".to_string(), to_value(["Everyone"]).unwrap());
|
||||
let mut validation = Validation { validate_exp: false, ..Validation::default() };
|
||||
validation.set_audience(&"Everyone");
|
||||
validation.set_audience(&["Everyone"]);
|
||||
let res = validate(&claims, &validation);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
@ -365,7 +378,7 @@ mod tests {
|
|||
#[test]
|
||||
fn aud_type_mismatch_fails() {
|
||||
let mut claims = Map::new();
|
||||
claims.insert("aud".to_string(), to_value("Everyone").unwrap());
|
||||
claims.insert("aud".to_string(), to_value(["Everyone"]).unwrap());
|
||||
let mut validation = Validation { validate_exp: false, ..Validation::default() };
|
||||
validation.set_audience(&["UserA", "UserB"]);
|
||||
let res = validate(&claims, &validation);
|
||||
|
@ -380,9 +393,9 @@ mod tests {
|
|||
#[test]
|
||||
fn aud_correct_type_not_matching_fails() {
|
||||
let mut claims = Map::new();
|
||||
claims.insert("aud".to_string(), to_value("Everyone").unwrap());
|
||||
claims.insert("aud".to_string(), to_value(["Everyone"]).unwrap());
|
||||
let mut validation = Validation { validate_exp: false, ..Validation::default() };
|
||||
validation.set_audience(&"None");
|
||||
validation.set_audience(&["None"]);
|
||||
let res = validate(&claims, &validation);
|
||||
assert!(res.is_err());
|
||||
|
||||
|
@ -396,7 +409,7 @@ mod tests {
|
|||
fn aud_missing_fails() {
|
||||
let claims = Map::new();
|
||||
let mut validation = Validation { validate_exp: false, ..Validation::default() };
|
||||
validation.set_audience(&"None");
|
||||
validation.set_audience(&["None"]);
|
||||
let res = validate(&claims, &validation);
|
||||
assert!(res.is_err());
|
||||
|
||||
|
@ -405,4 +418,48 @@ 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)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// https://github.com/Keats/jsonwebtoken/issues/110
|
||||
#[test]
|
||||
fn aud_use_validation_struct() {
|
||||
let mut claims = Map::new();
|
||||
claims.insert(
|
||||
"aud".to_string(),
|
||||
to_value("my-googleclientid1234.apps.googleusercontent.com").unwrap(),
|
||||
);
|
||||
|
||||
let aud = "my-googleclientid1234.apps.googleusercontent.com".to_string();
|
||||
let mut aud_hashset = std::collections::HashSet::new();
|
||||
aud_hashset.insert(aud);
|
||||
|
||||
let validation =
|
||||
Validation { aud: Some(aud_hashset), validate_exp: false, ..Validation::default() };
|
||||
let res = validate(&claims, &validation);
|
||||
println!("{:?}", res);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +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, Validation};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
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", 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_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, privkey).unwrap();
|
||||
let pubkey = include_bytes!("public_ecdsa_key.pk8");
|
||||
let token_data = decode::<Claims>(&token, pubkey, &Validation::new(Algorithm::ES256)).unwrap();
|
||||
assert_eq!(my_claims, token_data.claims);
|
||||
assert!(token_data.header.kid.is_none());
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
use chrono::Utc;
|
||||
use jsonwebtoken::{
|
||||
crypto::{sign, verify},
|
||||
decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
sub: String,
|
||||
company: String,
|
||||
exp: i64,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_sign_verification_pk8() {
|
||||
let privkey = include_bytes!("private_ecdsa_key.pk8");
|
||||
let pubkey = include_bytes!("public_ecdsa_key.pk8");
|
||||
|
||||
let encrypted =
|
||||
sign("hello world", &EncodingKey::from_ec_der(privkey), Algorithm::ES256).unwrap();
|
||||
let is_valid =
|
||||
verify(&encrypted, "hello world", &DecodingKey::from_ec_der(pubkey), Algorithm::ES256)
|
||||
.unwrap();
|
||||
assert!(is_valid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_sign_verification_pem() {
|
||||
let privkey_pem = include_bytes!("private_ecdsa_key.pem");
|
||||
let pubkey_pem = include_bytes!("public_ecdsa_key.pem");
|
||||
let encrypted =
|
||||
sign("hello world", &EncodingKey::from_ec_pem(privkey_pem).unwrap(), Algorithm::ES256)
|
||||
.unwrap();
|
||||
let is_valid = verify(
|
||||
&encrypted,
|
||||
"hello world",
|
||||
&DecodingKey::from_ec_pem(pubkey_pem).unwrap(),
|
||||
Algorithm::ES256,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(is_valid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_claim() {
|
||||
let privkey_pem = include_bytes!("private_ecdsa_key.pem");
|
||||
let pubkey_pem = 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,
|
||||
&EncodingKey::from_ec_pem(privkey_pem).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let token_data = decode::<Claims>(
|
||||
&token,
|
||||
&DecodingKey::from_ec_pem(pubkey_pem).unwrap(),
|
||||
&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_pem = include_bytes!("private_jwtio_pkcs8.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,
|
||||
};
|
||||
let token = encode(
|
||||
&Header::new(Algorithm::ES384),
|
||||
&my_claims,
|
||||
&EncodingKey::from_ec_pem(privkey_pem).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let token_data = decode::<Claims>(
|
||||
&token,
|
||||
&DecodingKey::from_ec_pem(pubkey_pem).unwrap(),
|
||||
&Validation::new(Algorithm::ES384),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(my_claims, token_data.claims);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWTFfCGljY6aw3Hrt
|
||||
kHmPRiazukxPLb6ilpRAewjW8nihRANCAATDskChT+Altkm9X7MI69T3IUmrQU0L
|
||||
950IxEzvw/x5BMEINRMrXLBJhqzO9Bm+d6JbqA21YQmd1Kt4RzLJR1W+
|
||||
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,6 @@
|
|||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIGkAgEBBDCAHpFQ62QnGCEvYh/pE9QmR1C9aLcDItRbslbmhen/h1tt8AyMhske
|
||||
enT+rAyyPhGgBwYFK4EEACKhZANiAAQLW5ZJePZzMIPAxMtZXkEWbDF0zo9f2n4+
|
||||
T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw8lE5IPUWpgu553SteKigiKLU
|
||||
PeNpbqmYZUkWGh3MLfVzLmx85ii2vMU=
|
||||
-----END EC PRIVATE KEY-----
|
|
@ -0,0 +1,6 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCAHpFQ62QnGCEvYh/p
|
||||
E9QmR1C9aLcDItRbslbmhen/h1tt8AyMhskeenT+rAyyPhGhZANiAAQLW5ZJePZz
|
||||
MIPAxMtZXkEWbDF0zo9f2n4+T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw
|
||||
8lE5IPUWpgu553SteKigiKLUPeNpbqmYZUkWGh3MLfVzLmx85ii2vMU=
|
||||
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,4 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEw7JAoU/gJbZJvV+zCOvU9yFJq0FN
|
||||
C/edCMRM78P8eQTBCDUTK1ywSYaszvQZvneiW6gNtWEJndSreEcyyUdVvg==
|
||||
-----END PUBLIC KEY-----
|
|
@ -0,0 +1,5 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+
|
||||
Pk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii
|
||||
1D3jaW6pmGVJFhodzC31cy5sfOYotrzF
|
||||
-----END PUBLIC KEY-----
|
|
@ -0,0 +1,160 @@
|
|||
use chrono::Utc;
|
||||
use jsonwebtoken::{
|
||||
crypto::{sign, verify},
|
||||
dangerous_unsafe_decode, decode, decode_header, encode, Algorithm, DecodingKey, EncodingKey,
|
||||
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", &EncodingKey::from_secret(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", &DecodingKey::from_secret(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, &EncodingKey::from_secret(b"secret")).unwrap();
|
||||
let token_data =
|
||||
decode::<Claims>(&token, &DecodingKey::from_secret(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, &EncodingKey::from_secret(b"secret")).unwrap();
|
||||
let token_data =
|
||||
decode::<Claims>(&token, &DecodingKey::from_secret(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, &DecodingKey::from_secret(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, &DecodingKey::from_secret(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, &DecodingKey::from_secret(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,
|
||||
&DecodingKey::from_secret(b"secret"),
|
||||
&Validation::new(Algorithm::RS512),
|
||||
);
|
||||
claims.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "InvalidAlgorithm")]
|
||||
fn encode_wrong_alg_family() {
|
||||
let my_claims = Claims {
|
||||
sub: "b@b.com".to_string(),
|
||||
company: "ACME".to_string(),
|
||||
exp: Utc::now().timestamp() + 10000,
|
||||
};
|
||||
let claims = encode(&Header::default(), &my_claims, &EncodingKey::from_rsa_der(b"secret"));
|
||||
claims.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_token_with_bytes_secret() {
|
||||
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks";
|
||||
let claims =
|
||||
decode::<Claims>(token, &DecodingKey::from_secret(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();
|
||||
}
|
172
tests/lib.rs
172
tests/lib.rs
|
@ -1,170 +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,
|
||||
Validation,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
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, "secret".as_ref()).unwrap();
|
||||
let token_data = decode::<Claims>(&token, "secret".as_ref(), &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, "secret".as_ref()).unwrap();
|
||||
let token_data = decode::<Claims>(&token, "secret".as_ref(), &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, "secret".as_ref(), &Validation::default());
|
||||
println!("{:?}", claims);
|
||||
claims.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "InvalidToken")]
|
||||
fn decode_token_missing_parts() {
|
||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
||||
let claims = decode::<Claims>(token, "secret".as_ref(), &Validation::default());
|
||||
claims.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "InvalidSignature")]
|
||||
fn decode_token_invalid_signature() {
|
||||
let token =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong";
|
||||
let claims = decode::<Claims>(token, "secret".as_ref(), &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, "secret".as_ref(), &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();
|
||||
}
|
||||
|
||||
// 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, "secret".as_ref()).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, "secret".as_ref(), &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("").is_err());
|
||||
}
|
||||
mod ecdsa;
|
||||
mod rsa;
|
||||
|
|
44
tests/rsa.rs
44
tests/rsa.rs
|
@ -1,44 +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, Validation};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
sub: String,
|
||||
company: String,
|
||||
exp: i64,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_sign_verification() {
|
||||
let encrypted =
|
||||
sign("hello world", include_bytes!("private_rsa_key.der"), Algorithm::RS256).unwrap();
|
||||
let is_valid =
|
||||
verify(&encrypted, "hello world", include_bytes!("public_rsa_key.der"), Algorithm::RS256)
|
||||
.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 token =
|
||||
encode(&Header::new(Algorithm::RS256), &my_claims, include_bytes!("private_rsa_key.der"))
|
||||
.unwrap();
|
||||
let token_data = decode::<Claims>(
|
||||
&token,
|
||||
include_bytes!("public_rsa_key.der"),
|
||||
&Validation::new(Algorithm::RS256),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(my_claims, token_data.claims);
|
||||
assert!(token_data.header.kid.is_none());
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
use chrono::Utc;
|
||||
use jsonwebtoken::{
|
||||
crypto::{sign, verify},
|
||||
decode, encode, Algorithm, DecodingKey, EncodingKey, 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", &EncodingKey::from_rsa_pem(privkey_pem).unwrap(), alg).unwrap();
|
||||
let is_valid =
|
||||
verify(&encrypted, "hello world", &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), 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", &EncodingKey::from_rsa_pem(privkey_pem).unwrap(), alg).unwrap();
|
||||
let is_valid =
|
||||
verify(&encrypted, "hello world", &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), alg)
|
||||
.unwrap();
|
||||
assert!(is_valid);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_sign_verification_der() {
|
||||
let privkey_der = include_bytes!("private_rsa_key.der");
|
||||
let pubkey_der = include_bytes!("public_rsa_key.der");
|
||||
|
||||
for &alg in RSA_ALGORITHMS {
|
||||
let encrypted = sign("hello world", &EncodingKey::from_rsa_der(privkey_der), alg).unwrap();
|
||||
let is_valid =
|
||||
verify(&encrypted, "hello world", &DecodingKey::from_rsa_der(pubkey_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_pem = include_bytes!("private_rsa_key_pkcs1.pem");
|
||||
let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem");
|
||||
|
||||
for &alg in RSA_ALGORITHMS {
|
||||
let token =
|
||||
encode(&Header::new(alg), &my_claims, &EncodingKey::from_rsa_pem(privkey_pem).unwrap())
|
||||
.unwrap();
|
||||
let token_data = decode::<Claims>(
|
||||
&token,
|
||||
&DecodingKey::from_rsa_pem(pubkey_pem).unwrap(),
|
||||
&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,
|
||||
&EncodingKey::from_rsa_pem(privkey.as_ref()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let res = decode::<Claims>(
|
||||
&encrypted,
|
||||
&DecodingKey::from_rsa_components(n, e),
|
||||
&Validation::new(Algorithm::RS256),
|
||||
);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
// 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, &EncodingKey::from_rsa_pem(privkey_pem).unwrap())
|
||||
.unwrap();
|
||||
let token_data = decode::<Claims>(
|
||||
&token,
|
||||
&DecodingKey::from_rsa_pem(pubkey_pem).unwrap(),
|
||||
&Validation::new(alg),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(my_claims, token_data.claims);
|
||||
}
|
||||
}
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
Loading…
Reference in New Issue