Merge pull request #113 from Keats/keys_struct
Add EncodingKey & DecodingKey
This commit is contained in:
commit
e46f8f9d58
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "jsonwebtoken"
|
name = "jsonwebtoken"
|
||||||
version = "7.0.0-alpha.2"
|
version = "7.0.0-alpha.3"
|
||||||
authors = ["Vincent Prouillet <hello@vincentprouillet.com>"]
|
authors = ["Vincent Prouillet <hello@vincentprouillet.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
28
README.md
28
README.md
|
@ -38,7 +38,7 @@ Complete examples are available in the examples directory: a basic one and one w
|
||||||
In terms of imports and structs:
|
In terms of imports and structs:
|
||||||
```rust
|
```rust
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use jsonwebtoken::{encode, decode, Header, Algorithm, Validation};
|
use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey};
|
||||||
|
|
||||||
/// Our claims struct, it needs to derive `Serialize` and/or `Deserialize`
|
/// Our claims struct, it needs to derive `Serialize` and/or `Deserialize`
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -53,7 +53,7 @@ struct Claims {
|
||||||
The default algorithm is HS256, which uses a shared secret.
|
The default algorithm is HS256, which uses a shared secret.
|
||||||
|
|
||||||
```rust
|
```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
|
#### Custom headers & changing algorithm
|
||||||
|
@ -63,7 +63,7 @@ If you want to set the `kid` parameter or change the algorithm for example:
|
||||||
```rust
|
```rust
|
||||||
let mut header = Header::new(Algorithm::HS512);
|
let mut header = Header::new(Algorithm::HS512);
|
||||||
header.kid = Some("blabla".to_owned());
|
header.kid = Some("blabla".to_owned());
|
||||||
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.
|
Look at `examples/custom_header.rs` for a full working example.
|
||||||
|
|
||||||
|
@ -71,9 +71,9 @@ Look at `examples/custom_header.rs` for a full working example.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// HS256
|
// HS256
|
||||||
let token = encode(&Header::default(), &my_claims, "secret".as_ref())?;
|
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref()))?;
|
||||||
// RSA
|
// RSA
|
||||||
let token = encode(&Header::new(Algorithm::RS256), &my_claims, include_str!("privkey.pem"))?;
|
let token = encode(&Header::new(Algorithm::RS256), &my_claims, &EncodingKey::from_rsa_pem(include_bytes!("privkey.pem"))?)?;
|
||||||
```
|
```
|
||||||
Encoding a JWT takes 3 parameters:
|
Encoding a JWT takes 3 parameters:
|
||||||
|
|
||||||
|
@ -82,13 +82,13 @@ Encoding a JWT takes 3 parameters:
|
||||||
- a key/secret
|
- a key/secret
|
||||||
|
|
||||||
When using HS256, HS2384 or HS512, the key is always a shared secret like in the example above. When using
|
When using HS256, HS2384 or HS512, the key is always a shared secret like in the example above. When using
|
||||||
RSA/EC, the key should always be the content of the private key in the PEM format.
|
RSA/EC, the key should always be the content of the private key in the PEM or DER format.
|
||||||
|
|
||||||
### Decoding
|
### Decoding
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// `token` is a struct with 2 fields: `header` and `claims` where `claims` is your own struct.
|
// `token` is a struct with 2 fields: `header` and `claims` where `claims` is your own struct.
|
||||||
let token = decode::<Claims>(&token, "secret".as_ref(), &Validation::default())?;
|
let token = decode::<Claims>(&token, &DecodingKey::from_secret("secret".as_ref()), &Validation::default())?;
|
||||||
```
|
```
|
||||||
`decode` can error for a variety of reasons:
|
`decode` can error for a variety of reasons:
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ let token = decode::<Claims>(&token, "secret".as_ref(), &Validation::default())?
|
||||||
- validation of at least one reserved claim failed
|
- validation of at least one reserved claim failed
|
||||||
|
|
||||||
As with encoding, when using HS256, HS2384 or HS512, the key is always a shared secret like in the example above. When using
|
As with encoding, when using HS256, HS2384 or HS512, the key is always a shared secret like in the example above. When using
|
||||||
RSA/EC, the key should always be the content of the public key in the PEM format.
|
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:
|
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:
|
||||||
|
|
||||||
|
@ -121,15 +121,7 @@ The main use-case is for JWK where your public key is in a JSON format like so:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// `token` is a struct with 2 fields: `header` and `claims` where `claims` is your own struct.
|
// `token` is a struct with 2 fields: `header` and `claims` where `claims` is your own struct.
|
||||||
let token = decode_rsa_components::<Claims>(&token, jwk["n"], jwk["e"], &Validation::new(Algorithm::RS256))?;
|
let token = decode::<Claims>(&token, &EncodingKey::from_rsa_components(jwk["n"], jwk["e"]), &Validation::new(Algorithm::RS256))?;
|
||||||
```
|
|
||||||
|
|
||||||
### Converting .der to .pem
|
|
||||||
|
|
||||||
You can use openssl for that:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
openssl rsa -inform DER -outform PEM -in mykey.der -out mykey.pem
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Convert SEC1 private key to PKCS8
|
### Convert SEC1 private key to PKCS8
|
||||||
|
@ -145,7 +137,7 @@ openssl pkcs8 -topk8 -nocrypt -in sec1.pem -out pkcs8.pem
|
||||||
This library validates automatically the `exp` claim and `nbf` is validated if present. You can also validate the `sub`, `iss` and `aud` but
|
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.
|
those require setting the expected value in the `Validation` struct.
|
||||||
|
|
||||||
Since validating time fields is always a bit tricky due to clock skew,
|
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.
|
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`.
|
Last but not least, you will need to set the algorithm(s) allowed for this token if you are not using `HS256`.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#![feature(test)]
|
#![feature(test)]
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use jsonwebtoken::{decode, encode, Header, Validation};
|
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
@ -13,12 +13,15 @@ struct Claims {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_encode(b: &mut test::Bencher) {
|
fn bench_encode(b: &mut test::Bencher) {
|
||||||
let claim = Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned() };
|
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]
|
#[bench]
|
||||||
fn bench_decode(b: &mut test::Bencher) {
|
fn bench_decode(b: &mut test::Bencher) {
|
||||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
|
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()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use jsonwebtoken::{Header, Validation};
|
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
const SECRET: &str = "some-secret";
|
const SECRET: &str = "some-secret";
|
||||||
|
@ -51,13 +51,18 @@ mod jwt_numeric_date {
|
||||||
|
|
||||||
let claims = Claims { sub: sub.clone(), iat, exp };
|
let claims = Claims { sub: sub.clone(), iat, exp };
|
||||||
|
|
||||||
let token = encode(&Header::default(), &claims, SECRET.as_ref())
|
let token =
|
||||||
.expect("Failed to encode claims");
|
encode(&Header::default(), &claims, &EncodingKey::from_secret(SECRET.as_ref()))
|
||||||
|
.expect("Failed to encode claims");
|
||||||
|
|
||||||
assert_eq!(&token, EXPECTED_TOKEN);
|
assert_eq!(&token, EXPECTED_TOKEN);
|
||||||
|
|
||||||
let decoded = decode::<Claims>(&token, SECRET.as_ref(), &Validation::default())
|
let decoded = decode::<Claims>(
|
||||||
.expect("Failed to decode token");
|
&token,
|
||||||
|
&DecodingKey::from_secret(SECRET.as_ref()),
|
||||||
|
&Validation::default(),
|
||||||
|
)
|
||||||
|
.expect("Failed to decode token");
|
||||||
|
|
||||||
assert_eq!(decoded.claims, claims);
|
assert_eq!(decoded.claims, claims);
|
||||||
}
|
}
|
||||||
|
@ -82,12 +87,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
let claims = Claims { sub: sub.clone(), iat, exp };
|
let claims = Claims { sub: sub.clone(), iat, exp };
|
||||||
|
|
||||||
let token = jsonwebtoken::encode(&Header::default(), &claims, SECRET.as_ref())?;
|
let token = jsonwebtoken::encode(
|
||||||
|
&Header::default(),
|
||||||
|
&claims,
|
||||||
|
&EncodingKey::from_secret(SECRET.as_ref()),
|
||||||
|
)?;
|
||||||
|
|
||||||
println!("serialized token: {}", &token);
|
println!("serialized token: {}", &token);
|
||||||
|
|
||||||
let token_data =
|
let token_data = jsonwebtoken::decode::<Claims>(
|
||||||
jsonwebtoken::decode::<Claims>(&token, SECRET.as_ref(), &Validation::default())?;
|
&token,
|
||||||
|
&DecodingKey::from_secret(SECRET.as_ref()),
|
||||||
|
&Validation::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
println!("token data:\n{:#?}", &token_data);
|
println!("token data:\n{:#?}", &token_data);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use jsonwebtoken::errors::ErrorKind;
|
use jsonwebtoken::errors::ErrorKind;
|
||||||
use jsonwebtoken::{decode, encode, Algorithm, Header, Validation};
|
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct Claims {
|
struct Claims {
|
||||||
|
@ -19,13 +19,17 @@ fn main() {
|
||||||
header.kid = Some("signing_key".to_owned());
|
header.kid = Some("signing_key".to_owned());
|
||||||
header.alg = Algorithm::HS512;
|
header.alg = Algorithm::HS512;
|
||||||
|
|
||||||
let token = match encode(&header, &my_claims, key) {
|
let token = match encode(&header, &my_claims, &EncodingKey::from_secret(key)) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(_) => panic!(), // in practice you would return the error
|
Err(_) => panic!(), // in practice you would return the error
|
||||||
};
|
};
|
||||||
println!("{:?}", token);
|
println!("{:?}", token);
|
||||||
|
|
||||||
let token_data = match decode::<Claims>(&token, key, &Validation::new(Algorithm::HS512)) {
|
let token_data = match decode::<Claims>(
|
||||||
|
&token,
|
||||||
|
&DecodingKey::from_secret(key),
|
||||||
|
&Validation::new(Algorithm::HS512),
|
||||||
|
) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(err) => match *err.kind() {
|
Err(err) => match *err.kind() {
|
||||||
ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error
|
ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use jsonwebtoken::errors::ErrorKind;
|
use jsonwebtoken::errors::ErrorKind;
|
||||||
use jsonwebtoken::{decode, encode, Header, Validation};
|
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -10,16 +10,16 @@ struct Claims {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let key = b"secret";
|
||||||
let my_claims =
|
let my_claims =
|
||||||
Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned(), exp: 10000000000 };
|
Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned(), exp: 10000000000 };
|
||||||
let key = b"secret";
|
let token = match encode(&Header::default(), &my_claims, &EncodingKey::from_secret(key)) {
|
||||||
let token = match encode(&Header::default(), &my_claims, key) {
|
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(_) => panic!(), // in practice you would return the error
|
Err(_) => panic!(), // in practice you would return the error
|
||||||
};
|
};
|
||||||
|
|
||||||
let validation = Validation { sub: Some("b@b.com".to_string()), ..Validation::default() };
|
let validation = Validation { sub: Some("b@b.com".to_string()), ..Validation::default() };
|
||||||
let token_data = match decode::<Claims>(&token, key, &validation) {
|
let token_data = match decode::<Claims>(&token, &DecodingKey::from_secret(key), &validation) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(err) => match *err.kind() {
|
Err(err) => match *err.kind() {
|
||||||
ErrorKind::InvalidToken => panic!("Token is invalid"), // Example on how to handle a specific error
|
ErrorKind::InvalidToken => panic!("Token is invalid"), // Example on how to handle a specific error
|
||||||
|
|
|
@ -2,6 +2,13 @@ use crate::errors::{Error, ErrorKind, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
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
|
/// The algorithms supported for signing/verifying JWTs
|
||||||
#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||||
pub enum Algorithm {
|
pub enum Algorithm {
|
||||||
|
@ -58,6 +65,21 @@ impl FromStr for Algorithm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -2,7 +2,6 @@ use ring::{rand, signature};
|
||||||
|
|
||||||
use crate::algorithms::Algorithm;
|
use crate::algorithms::Algorithm;
|
||||||
use crate::errors::Result;
|
use crate::errors::Result;
|
||||||
use crate::pem::decoder::PemEncodedKey;
|
|
||||||
use crate::serialization::b64_encode;
|
use crate::serialization::b64_encode;
|
||||||
|
|
||||||
/// Only used internally when validating EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs.
|
/// Only used internally when validating EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs.
|
||||||
|
@ -26,13 +25,13 @@ pub(crate) fn alg_to_ec_signing(alg: Algorithm) -> &'static signature::EcdsaSign
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The actual ECDSA signing + encoding
|
/// The actual ECDSA signing + encoding
|
||||||
|
/// The key needs to be in PKCS8 format
|
||||||
pub fn sign(
|
pub fn sign(
|
||||||
alg: &'static signature::EcdsaSigningAlgorithm,
|
alg: &'static signature::EcdsaSigningAlgorithm,
|
||||||
key: &[u8],
|
key: &[u8],
|
||||||
message: &str,
|
message: &str,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let pem_key = PemEncodedKey::new(key)?;
|
let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, key)?;
|
||||||
let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, pem_key.as_ec_private_key()?)?;
|
|
||||||
let rng = rand::SystemRandom::new();
|
let rng = rand::SystemRandom::new();
|
||||||
let out = signing_key.sign(&rng, message.as_bytes())?;
|
let out = signing_key.sign(&rng, message.as_bytes())?;
|
||||||
Ok(b64_encode(out.as_ref()))
|
Ok(b64_encode(out.as_ref()))
|
||||||
|
|
|
@ -2,8 +2,9 @@ use ring::constant_time::verify_slices_are_equal;
|
||||||
use ring::{hmac, signature};
|
use ring::{hmac, signature};
|
||||||
|
|
||||||
use crate::algorithms::Algorithm;
|
use crate::algorithms::Algorithm;
|
||||||
|
use crate::decoding::{DecodingKey, DecodingKeyKind};
|
||||||
|
use crate::encoding::EncodingKey;
|
||||||
use crate::errors::Result;
|
use crate::errors::Result;
|
||||||
use crate::pem::decoder::PemEncodedKey;
|
|
||||||
use crate::serialization::{b64_decode, b64_encode};
|
use crate::serialization::{b64_decode, b64_encode};
|
||||||
|
|
||||||
pub(crate) mod ecdsa;
|
pub(crate) mod ecdsa;
|
||||||
|
@ -20,16 +21,14 @@ pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], message: &str) -> Resu
|
||||||
/// the base64 url safe encoded of the result.
|
/// the base64 url safe encoded of the result.
|
||||||
///
|
///
|
||||||
/// If you just want to encode a JWT, use `encode` instead.
|
/// If you just want to encode a JWT, use `encode` instead.
|
||||||
///
|
pub fn sign(message: &str, key: &EncodingKey, algorithm: Algorithm) -> Result<String> {
|
||||||
/// `key` is the secret for HMAC and a pem encoded string otherwise
|
|
||||||
pub fn sign(message: &str, key: &[u8], algorithm: Algorithm) -> Result<String> {
|
|
||||||
match algorithm {
|
match algorithm {
|
||||||
Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key, message),
|
Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key.inner(), message),
|
||||||
Algorithm::HS384 => sign_hmac(hmac::HMAC_SHA384, key, message),
|
Algorithm::HS384 => sign_hmac(hmac::HMAC_SHA384, key.inner(), message),
|
||||||
Algorithm::HS512 => sign_hmac(hmac::HMAC_SHA512, key, message),
|
Algorithm::HS512 => sign_hmac(hmac::HMAC_SHA512, key.inner(), message),
|
||||||
|
|
||||||
Algorithm::ES256 | Algorithm::ES384 => {
|
Algorithm::ES256 | Algorithm::ES384 => {
|
||||||
ecdsa::sign(ecdsa::alg_to_ec_signing(algorithm), key, message)
|
ecdsa::sign(ecdsa::alg_to_ec_signing(algorithm), key.inner(), message)
|
||||||
}
|
}
|
||||||
|
|
||||||
Algorithm::RS256
|
Algorithm::RS256
|
||||||
|
@ -37,7 +36,7 @@ pub fn sign(message: &str, key: &[u8], algorithm: Algorithm) -> Result<String> {
|
||||||
| Algorithm::RS512
|
| Algorithm::RS512
|
||||||
| Algorithm::PS256
|
| Algorithm::PS256
|
||||||
| Algorithm::PS384
|
| Algorithm::PS384
|
||||||
| Algorithm::PS512 => rsa::sign(rsa::alg_to_rsa_signing(algorithm), key, message),
|
| Algorithm::PS512 => rsa::sign(rsa::alg_to_rsa_signing(algorithm), key.inner(), message),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,57 +62,37 @@ fn verify_ring(
|
||||||
/// `signature` is the signature part of a jwt (text after the second '.')
|
/// `signature` is the signature part of a jwt (text after the second '.')
|
||||||
///
|
///
|
||||||
/// `message` is base64(header) + "." + base64(claims)
|
/// `message` is base64(header) + "." + base64(claims)
|
||||||
/// For ECDSA/RSA, the `key` is the pem public key. If you want to verify using the public key
|
pub fn verify(
|
||||||
/// components (modulus/exponent), use `verify_rsa_components` instead.
|
signature: &str,
|
||||||
pub fn verify(signature: &str, message: &str, key: &[u8], algorithm: Algorithm) -> Result<bool> {
|
message: &str,
|
||||||
|
key: &DecodingKey,
|
||||||
|
algorithm: Algorithm,
|
||||||
|
) -> Result<bool> {
|
||||||
match algorithm {
|
match algorithm {
|
||||||
Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
|
Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
|
||||||
// we just re-sign the message with the key and compare if they are equal
|
// we just re-sign the message with the key and compare if they are equal
|
||||||
let signed = sign(message, key, algorithm)?;
|
let signed = sign(message, &EncodingKey::from_secret(key.as_bytes()), algorithm)?;
|
||||||
Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok())
|
Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok())
|
||||||
}
|
}
|
||||||
Algorithm::ES256 | Algorithm::ES384 => {
|
Algorithm::ES256 | Algorithm::ES384 => verify_ring(
|
||||||
let pem_key = PemEncodedKey::new(key)?;
|
ecdsa::alg_to_ec_verification(algorithm),
|
||||||
verify_ring(
|
signature,
|
||||||
ecdsa::alg_to_ec_verification(algorithm),
|
message,
|
||||||
signature,
|
key.as_bytes(),
|
||||||
message,
|
),
|
||||||
pem_key.as_ec_public_key()?,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Algorithm::RS256
|
Algorithm::RS256
|
||||||
| Algorithm::RS384
|
| Algorithm::RS384
|
||||||
| Algorithm::RS512
|
| Algorithm::RS512
|
||||||
| Algorithm::PS256
|
| Algorithm::PS256
|
||||||
| Algorithm::PS384
|
| Algorithm::PS384
|
||||||
| Algorithm::PS512 => {
|
| Algorithm::PS512 => {
|
||||||
let pem_key = PemEncodedKey::new(key)?;
|
let alg = rsa::alg_to_rsa_parameters(algorithm);
|
||||||
verify_ring(
|
match &key.kind {
|
||||||
rsa::alg_to_rsa_parameters(algorithm),
|
DecodingKeyKind::SecretOrDer(bytes) => verify_ring(alg, signature, message, bytes),
|
||||||
signature,
|
DecodingKeyKind::RsaModulusExponent { n, e } => {
|
||||||
message,
|
rsa::verify_from_components(alg, signature, message, (n, e))
|
||||||
pem_key.as_rsa_key()?,
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify the signature given using the (n, e) components of a RSA public key.
|
|
||||||
///
|
|
||||||
/// `signature` is the signature part of a jwt (text after the second '.')
|
|
||||||
///
|
|
||||||
/// `message` is base64(header) + "." + base64(claims)
|
|
||||||
pub fn verify_rsa_components(
|
|
||||||
signature: &str,
|
|
||||||
message: &str,
|
|
||||||
components: (&str, &str),
|
|
||||||
alg: Algorithm,
|
|
||||||
) -> Result<bool> {
|
|
||||||
let signature_bytes = b64_decode(signature)?;
|
|
||||||
rsa::verify_from_components(
|
|
||||||
rsa::alg_to_rsa_parameters(alg),
|
|
||||||
&signature_bytes,
|
|
||||||
message,
|
|
||||||
components,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ use simple_asn1::BigUint;
|
||||||
|
|
||||||
use crate::algorithms::Algorithm;
|
use crate::algorithms::Algorithm;
|
||||||
use crate::errors::{ErrorKind, Result};
|
use crate::errors::{ErrorKind, Result};
|
||||||
use crate::pem::decoder::PemEncodedKey;
|
|
||||||
use crate::serialization::{b64_decode, b64_encode};
|
use crate::serialization::{b64_decode, b64_encode};
|
||||||
|
|
||||||
/// Only used internally when validating RSA, to map from our enum to the Ring param structs.
|
/// Only used internally when validating RSA, to map from our enum to the Ring param structs.
|
||||||
|
@ -33,15 +32,14 @@ pub(crate) fn alg_to_rsa_signing(alg: Algorithm) -> &'static dyn signature::RsaE
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The actual RSA signing + encoding
|
/// 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
|
/// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html
|
||||||
pub(crate) fn sign(
|
pub(crate) fn sign(
|
||||||
alg: &'static dyn signature::RsaEncoding,
|
alg: &'static dyn signature::RsaEncoding,
|
||||||
key: &[u8],
|
key: &[u8],
|
||||||
message: &str,
|
message: &str,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let pem_key = PemEncodedKey::new(key)?;
|
let key_pair = signature::RsaKeyPair::from_der(key).map_err(|_| ErrorKind::InvalidRsaKey)?;
|
||||||
let key_pair = signature::RsaKeyPair::from_der(pem_key.as_rsa_key()?)
|
|
||||||
.map_err(|_| ErrorKind::InvalidRsaKey)?;
|
|
||||||
|
|
||||||
let mut signature = vec![0; key_pair.public_modulus_len()];
|
let mut signature = vec![0; key_pair.public_modulus_len()];
|
||||||
let rng = rand::SystemRandom::new();
|
let rng = rand::SystemRandom::new();
|
||||||
|
@ -52,12 +50,14 @@ pub(crate) fn sign(
|
||||||
Ok(b64_encode(&signature))
|
Ok(b64_encode(&signature))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks that a signature is valid based on the (n, e) RSA pubkey components
|
||||||
pub(crate) fn verify_from_components(
|
pub(crate) fn verify_from_components(
|
||||||
alg: &'static signature::RsaParameters,
|
alg: &'static signature::RsaParameters,
|
||||||
signature_bytes: &[u8],
|
signature: &str,
|
||||||
message: &str,
|
message: &str,
|
||||||
components: (&str, &str),
|
components: (&str, &str),
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
|
let signature_bytes = b64_decode(signature)?;
|
||||||
let n = BigUint::from_bytes_be(&b64_decode(components.0)?).to_bytes_be();
|
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 e = BigUint::from_bytes_be(&b64_decode(components.1)?).to_bytes_be();
|
||||||
let pubkey = signature::RsaPublicKeyComponents { n, e };
|
let pubkey = signature::RsaPublicKeyComponents { n, e };
|
||||||
|
|
182
src/decoding.rs
182
src/decoding.rs
|
@ -1,8 +1,12 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
use crate::crypto::{verify, verify_rsa_components};
|
use crate::algorithms::AlgorithmFamily;
|
||||||
|
use crate::crypto::verify;
|
||||||
use crate::errors::{new_error, ErrorKind, Result};
|
use crate::errors::{new_error, ErrorKind, Result};
|
||||||
use crate::header::Header;
|
use crate::header::Header;
|
||||||
|
use crate::pem::decoder::PemEncodedKey;
|
||||||
use crate::serialization::from_jwt_part_claims;
|
use crate::serialization::from_jwt_part_claims;
|
||||||
use crate::validation::{validate, Validation};
|
use crate::validation::{validate, Validation};
|
||||||
|
|
||||||
|
@ -27,17 +31,119 @@ macro_rules! expect_two {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal way to differentiate between public key types
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
enum DecodingKey<'a> {
|
pub(crate) enum DecodingKeyKind<'a> {
|
||||||
SecretOrPem(&'a [u8]),
|
SecretOrDer(Cow<'a, [u8]>),
|
||||||
RsaModulusExponent { n: &'a str, e: &'a str },
|
RsaModulusExponent { n: &'a str, e: &'a str },
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _decode<T: DeserializeOwned>(
|
/// 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,
|
token: &str,
|
||||||
key: DecodingKey,
|
key: &DecodingKey,
|
||||||
validation: &Validation,
|
validation: &Validation,
|
||||||
) -> Result<TokenData<T>> {
|
) -> 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 (signature, message) = expect_two!(token.rsplitn(2, '.'));
|
||||||
let (claims, header) = expect_two!(message.rsplitn(2, '.'));
|
let (claims, header) = expect_two!(message.rsplitn(2, '.'));
|
||||||
let header = Header::from_encoded(header)?;
|
let header = Header::from_encoded(header)?;
|
||||||
|
@ -46,14 +152,7 @@ fn _decode<T: DeserializeOwned>(
|
||||||
return Err(new_error(ErrorKind::InvalidAlgorithm));
|
return Err(new_error(ErrorKind::InvalidAlgorithm));
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_valid = match key {
|
if !verify(signature, message, key, header.alg)? {
|
||||||
DecodingKey::SecretOrPem(k) => verify(signature, message, k, header.alg),
|
|
||||||
DecodingKey::RsaModulusExponent { n, e } => {
|
|
||||||
verify_rsa_components(signature, message, (n, e), header.alg)
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
|
|
||||||
if !is_valid {
|
|
||||||
return Err(new_error(ErrorKind::InvalidSignature));
|
return Err(new_error(ErrorKind::InvalidSignature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,61 +162,6 @@ fn _decode<T: DeserializeOwned>(
|
||||||
Ok(TokenData { header, claims: decoded_claims })
|
Ok(TokenData { header, claims: decoded_claims })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode and validate a JWT using a secret for HS and a public PEM format for RSA/EC
|
|
||||||
///
|
|
||||||
/// If the token or its signature is invalid or the claims fail validation, it will return an error.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use serde::{Deserialize, Serialize};
|
|
||||||
/// use jsonwebtoken::{decode, Validation, Algorithm};
|
|
||||||
///
|
|
||||||
/// #[derive(Debug, Serialize, Deserialize)]
|
|
||||||
/// struct Claims {
|
|
||||||
/// sub: String,
|
|
||||||
/// company: String
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// let token = "a.jwt.token".to_string();
|
|
||||||
/// // Claims is a struct that implements Deserialize
|
|
||||||
/// let token_message = decode::<Claims>(&token, "secret".as_ref(), &Validation::new(Algorithm::HS256));
|
|
||||||
/// ```
|
|
||||||
pub fn decode<T: DeserializeOwned>(
|
|
||||||
token: &str,
|
|
||||||
key: &[u8],
|
|
||||||
validation: &Validation,
|
|
||||||
) -> Result<TokenData<T>> {
|
|
||||||
_decode(token, DecodingKey::SecretOrPem(key), validation)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decode and validate a JWT using (n, e) base64 encoded public key components for RSA
|
|
||||||
///
|
|
||||||
/// If the token or its signature is invalid or the claims fail validation, it will return an error.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use serde::{Deserialize, Serialize};
|
|
||||||
/// use jsonwebtoken::{decode_rsa_components, Validation, Algorithm};
|
|
||||||
///
|
|
||||||
/// #[derive(Debug, Serialize, Deserialize)]
|
|
||||||
/// struct Claims {
|
|
||||||
/// sub: String,
|
|
||||||
/// company: String
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// let modulus = "some-base64-data";
|
|
||||||
/// let exponent = "some-base64-data";
|
|
||||||
/// let token = "a.jwt.token".to_string();
|
|
||||||
/// // Claims is a struct that implements Deserialize
|
|
||||||
/// let token_message = decode_rsa_components::<Claims>(&token, &modulus, &exponent, &Validation::new(Algorithm::HS256));
|
|
||||||
/// ```
|
|
||||||
pub fn decode_rsa_components<T: DeserializeOwned>(
|
|
||||||
token: &str,
|
|
||||||
modulus: &str,
|
|
||||||
exponent: &str,
|
|
||||||
validation: &Validation,
|
|
||||||
) -> Result<TokenData<T>> {
|
|
||||||
_decode(token, DecodingKey::RsaModulusExponent { n: modulus, e: exponent }, validation)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decode a JWT without any signature verification/validations.
|
/// 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.
|
/// 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.
|
||||||
|
|
|
@ -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("."))
|
||||||
|
}
|
|
@ -56,7 +56,8 @@ pub enum ErrorKind {
|
||||||
InvalidSubject,
|
InvalidSubject,
|
||||||
/// When a token’s nbf claim represents a time in the future
|
/// When a token’s nbf claim represents a time in the future
|
||||||
ImmatureSignature,
|
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,
|
InvalidAlgorithm,
|
||||||
|
|
||||||
// 3rd party errors
|
// 3rd party errors
|
||||||
|
|
42
src/lib.rs
42
src/lib.rs
|
@ -7,6 +7,7 @@ mod algorithms;
|
||||||
/// Lower level functions, if you want to do something other than JWTs
|
/// Lower level functions, if you want to do something other than JWTs
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
mod decoding;
|
mod decoding;
|
||||||
|
mod encoding;
|
||||||
/// All the errors that can be encountered while encoding/decoding JWTs
|
/// All the errors that can be encountered while encoding/decoding JWTs
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
mod header;
|
mod header;
|
||||||
|
@ -15,44 +16,7 @@ mod serialization;
|
||||||
mod validation;
|
mod validation;
|
||||||
|
|
||||||
pub use algorithms::Algorithm;
|
pub use algorithms::Algorithm;
|
||||||
pub use decoding::{
|
pub use decoding::{dangerous_unsafe_decode, decode, decode_header, DecodingKey, TokenData};
|
||||||
dangerous_unsafe_decode, decode, decode_header, decode_rsa_components, TokenData,
|
pub use encoding::{encode, EncodingKey};
|
||||||
};
|
|
||||||
pub use header::Header;
|
pub use header::Header;
|
||||||
pub use validation::Validation;
|
pub use validation::Validation;
|
||||||
|
|
||||||
use serde::ser::Serialize;
|
|
||||||
|
|
||||||
use crate::errors::Result;
|
|
||||||
use crate::serialization::b64_encode_part;
|
|
||||||
|
|
||||||
/// Encode the header and claims given and sign the payload using the algorithm from the header and the key.
|
|
||||||
/// 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};
|
|
||||||
///
|
|
||||||
/// #[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 = 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("."))
|
|
||||||
}
|
|
||||||
|
|
|
@ -159,7 +159,7 @@ pub fn validate(claims: &Map<String, Value>, options: &Validation) -> Result<()>
|
||||||
return Err(new_error(ErrorKind::InvalidAudience));
|
return Err(new_error(ErrorKind::InvalidAudience));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return Err(new_error(ErrorKind::InvalidAudience))
|
_ => return Err(new_error(ErrorKind::InvalidAudience)),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return Err(new_error(ErrorKind::InvalidAudience));
|
return Err(new_error(ErrorKind::InvalidAudience));
|
||||||
|
@ -447,17 +447,17 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn aud_use_validation_struct() {
|
fn aud_use_validation_struct() {
|
||||||
let mut claims = Map::new();
|
let mut claims = Map::new();
|
||||||
claims.insert("aud".to_string(), to_value("my-googleclientid1234.apps.googleusercontent.com").unwrap());
|
claims.insert(
|
||||||
|
"aud".to_string(),
|
||||||
|
to_value("my-googleclientid1234.apps.googleusercontent.com").unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
let aud = "my-googleclientid1234.apps.googleusercontent.com".to_string();
|
let aud = "my-googleclientid1234.apps.googleusercontent.com".to_string();
|
||||||
let mut aud_hashset = std::collections::HashSet::new();
|
let mut aud_hashset = std::collections::HashSet::new();
|
||||||
aud_hashset.insert(aud);
|
aud_hashset.insert(aud);
|
||||||
|
|
||||||
let validation = Validation {
|
let validation =
|
||||||
aud: Some(aud_hashset),
|
Validation { aud: Some(aud_hashset), validate_exp: false, ..Validation::default() };
|
||||||
validate_exp: false,
|
|
||||||
..Validation::default()
|
|
||||||
};
|
|
||||||
let res = validate(&claims, &validation);
|
let res = validate(&claims, &validation);
|
||||||
println!("{:?}", res);
|
println!("{:?}", res);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use jsonwebtoken::{
|
use jsonwebtoken::{
|
||||||
crypto::{sign, verify},
|
crypto::{sign, verify},
|
||||||
decode, encode, Algorithm, Header, Validation,
|
decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -12,36 +12,57 @@ pub struct Claims {
|
||||||
exp: i64,
|
exp: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove completely?
|
#[test]
|
||||||
//#[test]
|
fn round_trip_sign_verification_pk8() {
|
||||||
//fn round_trip_sign_verification_pk8() {
|
let privkey = include_bytes!("private_ecdsa_key.pk8");
|
||||||
// let privkey = include_bytes!("private_ecdsa_key.pk8");
|
let pubkey = include_bytes!("public_ecdsa_key.pk8");
|
||||||
// let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap();
|
|
||||||
// let pubkey = include_bytes!("public_ecdsa_key.pk8");
|
let encrypted =
|
||||||
// let is_valid = verify(&encrypted, "hello world", pubkey, Algorithm::ES256).unwrap();
|
sign("hello world", &EncodingKey::from_ec_der(privkey), Algorithm::ES256).unwrap();
|
||||||
// assert!(is_valid);
|
let is_valid =
|
||||||
//}
|
verify(&encrypted, "hello world", &DecodingKey::from_ec_der(pubkey), Algorithm::ES256)
|
||||||
|
.unwrap();
|
||||||
|
assert!(is_valid);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn round_trip_sign_verification_pem() {
|
fn round_trip_sign_verification_pem() {
|
||||||
let privkey = include_bytes!("private_ecdsa_key.pem");
|
let privkey_pem = include_bytes!("private_ecdsa_key.pem");
|
||||||
let pubkey = include_bytes!("public_ecdsa_key.pem");
|
let pubkey_pem = include_bytes!("public_ecdsa_key.pem");
|
||||||
let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap();
|
let encrypted =
|
||||||
let is_valid = verify(&encrypted, "hello world", pubkey, Algorithm::ES256).unwrap();
|
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);
|
assert!(is_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn round_trip_claim() {
|
fn round_trip_claim() {
|
||||||
let privkey = include_bytes!("private_ecdsa_key.pem");
|
let privkey_pem = include_bytes!("private_ecdsa_key.pem");
|
||||||
let pubkey = include_bytes!("public_ecdsa_key.pem");
|
let pubkey_pem = include_bytes!("public_ecdsa_key.pem");
|
||||||
let my_claims = Claims {
|
let my_claims = Claims {
|
||||||
sub: "b@b.com".to_string(),
|
sub: "b@b.com".to_string(),
|
||||||
company: "ACME".to_string(),
|
company: "ACME".to_string(),
|
||||||
exp: Utc::now().timestamp() + 10000,
|
exp: Utc::now().timestamp() + 10000,
|
||||||
};
|
};
|
||||||
let token = encode(&Header::new(Algorithm::ES256), &my_claims, privkey).unwrap();
|
let token = encode(
|
||||||
let token_data = decode::<Claims>(&token, pubkey, &Validation::new(Algorithm::ES256)).unwrap();
|
&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);
|
assert_eq!(my_claims, token_data.claims);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,14 +70,24 @@ fn round_trip_claim() {
|
||||||
#[test]
|
#[test]
|
||||||
fn roundtrip_with_jwtio_example() {
|
fn roundtrip_with_jwtio_example() {
|
||||||
// We currently do not support SEC1 so we use the converted PKCS8 formatted
|
// We currently do not support SEC1 so we use the converted PKCS8 formatted
|
||||||
let privkey = include_bytes!("private_jwtio_pkcs8.pem");
|
let privkey_pem = include_bytes!("private_jwtio_pkcs8.pem");
|
||||||
let pubkey = include_bytes!("public_jwtio.pem");
|
let pubkey_pem = include_bytes!("public_jwtio.pem");
|
||||||
let my_claims = Claims {
|
let my_claims = Claims {
|
||||||
sub: "b@b.com".to_string(),
|
sub: "b@b.com".to_string(),
|
||||||
company: "ACME".to_string(),
|
company: "ACME".to_string(),
|
||||||
exp: Utc::now().timestamp() + 10000,
|
exp: Utc::now().timestamp() + 10000,
|
||||||
};
|
};
|
||||||
let token = encode(&Header::new(Algorithm::ES384), &my_claims, privkey).unwrap();
|
let token = encode(
|
||||||
let token_data = decode::<Claims>(&token, pubkey, &Validation::new(Algorithm::ES384)).unwrap();
|
&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);
|
assert_eq!(my_claims, token_data.claims);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use jsonwebtoken::{
|
use jsonwebtoken::{
|
||||||
crypto::{sign, verify},
|
crypto::{sign, verify},
|
||||||
dangerous_unsafe_decode, decode, decode_header, encode, Algorithm, Header, Validation,
|
dangerous_unsafe_decode, decode, decode_header, encode, Algorithm, DecodingKey, EncodingKey,
|
||||||
|
Header, Validation,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -14,7 +15,8 @@ pub struct Claims {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sign_hs256() {
|
fn sign_hs256() {
|
||||||
let result = sign("hello world", b"secret", Algorithm::HS256).unwrap();
|
let result =
|
||||||
|
sign("hello world", &EncodingKey::from_secret(b"secret"), Algorithm::HS256).unwrap();
|
||||||
let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo";
|
let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo";
|
||||||
assert_eq!(result, expected);
|
assert_eq!(result, expected);
|
||||||
}
|
}
|
||||||
|
@ -22,7 +24,8 @@ fn sign_hs256() {
|
||||||
#[test]
|
#[test]
|
||||||
fn verify_hs256() {
|
fn verify_hs256() {
|
||||||
let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo";
|
let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo";
|
||||||
let valid = verify(sig, "hello world", b"secret", Algorithm::HS256).unwrap();
|
let valid =
|
||||||
|
verify(sig, "hello world", &DecodingKey::from_secret(b"secret"), Algorithm::HS256).unwrap();
|
||||||
assert!(valid);
|
assert!(valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,8 +38,10 @@ fn encode_with_custom_header() {
|
||||||
};
|
};
|
||||||
let mut header = Header::default();
|
let mut header = Header::default();
|
||||||
header.kid = Some("kid".to_string());
|
header.kid = Some("kid".to_string());
|
||||||
let token = encode(&header, &my_claims, b"secret").unwrap();
|
let token = encode(&header, &my_claims, &EncodingKey::from_secret(b"secret")).unwrap();
|
||||||
let token_data = decode::<Claims>(&token, b"secret", &Validation::default()).unwrap();
|
let token_data =
|
||||||
|
decode::<Claims>(&token, &DecodingKey::from_secret(b"secret"), &Validation::default())
|
||||||
|
.unwrap();
|
||||||
assert_eq!(my_claims, token_data.claims);
|
assert_eq!(my_claims, token_data.claims);
|
||||||
assert_eq!("kid", token_data.header.kid.unwrap());
|
assert_eq!("kid", token_data.header.kid.unwrap());
|
||||||
}
|
}
|
||||||
|
@ -48,8 +53,11 @@ fn round_trip_claim() {
|
||||||
company: "ACME".to_string(),
|
company: "ACME".to_string(),
|
||||||
exp: Utc::now().timestamp() + 10000,
|
exp: Utc::now().timestamp() + 10000,
|
||||||
};
|
};
|
||||||
let token = encode(&Header::default(), &my_claims, b"secret").unwrap();
|
let token =
|
||||||
let token_data = decode::<Claims>(&token, b"secret", &Validation::default()).unwrap();
|
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_eq!(my_claims, token_data.claims);
|
||||||
assert!(token_data.header.kid.is_none());
|
assert!(token_data.header.kid.is_none());
|
||||||
}
|
}
|
||||||
|
@ -57,7 +65,8 @@ fn round_trip_claim() {
|
||||||
#[test]
|
#[test]
|
||||||
fn decode_token() {
|
fn decode_token() {
|
||||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98";
|
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98";
|
||||||
let claims = decode::<Claims>(token, b"secret", &Validation::default());
|
let claims =
|
||||||
|
decode::<Claims>(token, &DecodingKey::from_secret(b"secret"), &Validation::default());
|
||||||
println!("{:?}", claims);
|
println!("{:?}", claims);
|
||||||
claims.unwrap();
|
claims.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -66,7 +75,8 @@ fn decode_token() {
|
||||||
#[should_panic(expected = "InvalidToken")]
|
#[should_panic(expected = "InvalidToken")]
|
||||||
fn decode_token_missing_parts() {
|
fn decode_token_missing_parts() {
|
||||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
||||||
let claims = decode::<Claims>(token, b"secret", &Validation::default());
|
let claims =
|
||||||
|
decode::<Claims>(token, &DecodingKey::from_secret(b"secret"), &Validation::default());
|
||||||
claims.unwrap();
|
claims.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +85,8 @@ fn decode_token_missing_parts() {
|
||||||
fn decode_token_invalid_signature() {
|
fn decode_token_invalid_signature() {
|
||||||
let token =
|
let token =
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong";
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong";
|
||||||
let claims = decode::<Claims>(token, b"secret", &Validation::default());
|
let claims =
|
||||||
|
decode::<Claims>(token, &DecodingKey::from_secret(b"secret"), &Validation::default());
|
||||||
claims.unwrap();
|
claims.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,14 +94,31 @@ fn decode_token_invalid_signature() {
|
||||||
#[should_panic(expected = "InvalidAlgorithm")]
|
#[should_panic(expected = "InvalidAlgorithm")]
|
||||||
fn decode_token_wrong_algorithm() {
|
fn decode_token_wrong_algorithm() {
|
||||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY";
|
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY";
|
||||||
let claims = decode::<Claims>(token, b"secret", &Validation::new(Algorithm::RS512));
|
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();
|
claims.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decode_token_with_bytes_secret() {
|
fn decode_token_with_bytes_secret() {
|
||||||
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks";
|
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks";
|
||||||
let claims = decode::<Claims>(token, b"\x01\x02\x03", &Validation::default());
|
let claims =
|
||||||
|
decode::<Claims>(token, &DecodingKey::from_secret(b"\x01\x02\x03"), &Validation::default());
|
||||||
assert!(claims.is_ok());
|
assert!(claims.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use jsonwebtoken::{
|
use jsonwebtoken::{
|
||||||
crypto::{sign, verify},
|
crypto::{sign, verify},
|
||||||
decode, decode_rsa_components, encode, Algorithm, Header, Validation,
|
decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -27,8 +27,11 @@ fn round_trip_sign_verification_pem_pkcs1() {
|
||||||
let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem");
|
let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem");
|
||||||
|
|
||||||
for &alg in RSA_ALGORITHMS {
|
for &alg in RSA_ALGORITHMS {
|
||||||
let encrypted = sign("hello world", privkey_pem, alg).unwrap();
|
let encrypted =
|
||||||
let is_valid = verify(&encrypted, "hello world", pubkey_pem, alg).unwrap();
|
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);
|
assert!(is_valid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,8 +42,24 @@ fn round_trip_sign_verification_pem_pkcs8() {
|
||||||
let pubkey_pem = include_bytes!("public_rsa_key_pkcs8.pem");
|
let pubkey_pem = include_bytes!("public_rsa_key_pkcs8.pem");
|
||||||
|
|
||||||
for &alg in RSA_ALGORITHMS {
|
for &alg in RSA_ALGORITHMS {
|
||||||
let encrypted = sign("hello world", privkey_pem, alg).unwrap();
|
let encrypted =
|
||||||
let is_valid = verify(&encrypted, "hello world", pubkey_pem, alg).unwrap();
|
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);
|
assert!(is_valid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,13 +71,16 @@ fn round_trip_claim() {
|
||||||
company: "ACME".to_string(),
|
company: "ACME".to_string(),
|
||||||
exp: Utc::now().timestamp() + 10000,
|
exp: Utc::now().timestamp() + 10000,
|
||||||
};
|
};
|
||||||
let privkey = include_bytes!("private_rsa_key_pkcs1.pem");
|
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 {
|
for &alg in RSA_ALGORITHMS {
|
||||||
let token = encode(&Header::new(alg), &my_claims, privkey).unwrap();
|
let token =
|
||||||
|
encode(&Header::new(alg), &my_claims, &EncodingKey::from_rsa_pem(privkey_pem).unwrap())
|
||||||
|
.unwrap();
|
||||||
let token_data = decode::<Claims>(
|
let token_data = decode::<Claims>(
|
||||||
&token,
|
&token,
|
||||||
include_bytes!("public_rsa_key_pkcs1.pem"),
|
&DecodingKey::from_rsa_pem(pubkey_pem).unwrap(),
|
||||||
&Validation::new(alg),
|
&Validation::new(alg),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -78,18 +100,20 @@ fn rsa_modulus_exponent() {
|
||||||
let n = "yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ";
|
let n = "yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ";
|
||||||
let e = "AQAB";
|
let e = "AQAB";
|
||||||
|
|
||||||
let encrypted = encode(&Header::new(Algorithm::RS256), &my_claims, privkey.as_ref()).unwrap();
|
let encrypted = encode(
|
||||||
let res = decode_rsa_components::<Claims>(&encrypted, n, e, &Validation::new(Algorithm::RS256));
|
&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());
|
assert!(res.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic(expected = "InvalidKeyFormat")]
|
|
||||||
fn fails_with_non_pkcs8_key_format() {
|
|
||||||
let _encrypted =
|
|
||||||
sign("hello world", include_bytes!("private_rsa_key_pkcs1.pem"), Algorithm::ES256).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://jwt.io/ is often used for examples so ensure their example works with jsonwebtoken
|
// https://jwt.io/ is often used for examples so ensure their example works with jsonwebtoken
|
||||||
#[test]
|
#[test]
|
||||||
fn roundtrip_with_jwtio_example_jey() {
|
fn roundtrip_with_jwtio_example_jey() {
|
||||||
|
@ -103,8 +127,15 @@ fn roundtrip_with_jwtio_example_jey() {
|
||||||
};
|
};
|
||||||
|
|
||||||
for &alg in RSA_ALGORITHMS {
|
for &alg in RSA_ALGORITHMS {
|
||||||
let token = encode(&Header::new(alg), &my_claims, privkey_pem).unwrap();
|
let token =
|
||||||
let token_data = decode::<Claims>(&token, pubkey_pem, &Validation::new(alg)).unwrap();
|
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_eq!(my_claims, token_data.claims);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue