Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ default = ["use_pem"]
use_pem = ["pem", "simple_asn1"]
rust_crypto = ["ed25519-dalek", "hmac", "p256", "p384", "rand", "rsa", "sha2"]
aws_lc_rs = ["aws-lc-rs"]
custom-provider = []

[[bench]]
name = "jwt"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jsonwebtoken = { version = "10", features = ["aws_lc_rs"] }
serde = {version = "1.0", features = ["derive"] }
```

Two crypto backends are available via features, `aws_lc_rs` and `rust_crypto`, exactly one of which must be enabled.
Two crypto backends are available via features, `aws_lc_rs` and `rust_crypto`, at most one of which must be enabled. If you wish to use a custom crypto provider, enable the `custom-provider` feature instead.

The minimum required Rust version (MSRV) is specified in the `rust-version` field in this project's [Cargo.toml](Cargo.toml).

Expand Down
107 changes: 107 additions & 0 deletions examples/custom_provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use jsonwebtoken::{
Algorithm, DecodingKey, EncodingKey, Header, Validation,
crypto::{CryptoProvider, JwkUtils, JwtSigner, JwtVerifier},
decode, encode,
errors::Error,
};
use serde::{Deserialize, Serialize};
use signature::{Signer, Verifier};

fn new_signer(algorithm: &Algorithm, key: &EncodingKey) -> Result<Box<dyn JwtSigner>, Error> {
let jwt_signer = match algorithm {
Algorithm::EdDSA => Box::new(CustomEdDSASigner::new(key)?) as Box<dyn JwtSigner>,
_ => unimplemented!(),
};

Ok(jwt_signer)
}

fn new_verifier(algorithm: &Algorithm, key: &DecodingKey) -> Result<Box<dyn JwtVerifier>, Error> {
let jwt_verifier = match algorithm {
Algorithm::EdDSA => Box::new(CustomEdDSAVerifier::new(key)?) as Box<dyn JwtVerifier>,
_ => unimplemented!(),
};

Ok(jwt_verifier)
}

pub struct CustomEdDSASigner;

impl CustomEdDSASigner {
fn new(_: &EncodingKey) -> Result<Self, Error> {
Ok(CustomEdDSASigner)
}
}

// WARNING: This is obviously not secure at all and should NEVER be done in practice!
impl Signer<Vec<u8>> for CustomEdDSASigner {
fn try_sign(&self, _: &[u8]) -> Result<Vec<u8>, signature::Error> {
Ok(vec![0; 16])
}
}

impl JwtSigner for CustomEdDSASigner {
fn algorithm(&self) -> Algorithm {
Algorithm::EdDSA
}
}

pub struct CustomEdDSAVerifier;

impl CustomEdDSAVerifier {
fn new(_: &DecodingKey) -> Result<Self, Error> {
Ok(CustomEdDSAVerifier)
}
}

impl Verifier<Vec<u8>> for CustomEdDSAVerifier {
fn verify(&self, _: &[u8], signature: &Vec<u8>) -> Result<(), signature::Error> {
if signature == &vec![0; 16] { Ok(()) } else { Err(signature::Error::new()) }
}
}

impl JwtVerifier for CustomEdDSAVerifier {
fn algorithm(&self) -> Algorithm {
Algorithm::EdDSA
}
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Claims {
sub: String,
exp: u64,
}

fn main() {
// create and install our custom provider
let my_crypto_provider = CryptoProvider {
signer_factory: new_signer,
verifier_factory: new_verifier,
// the default impl uses dummy functions that panic, but we don't need them here
jwk_utils: JwkUtils::default(),
};
my_crypto_provider.install_default().unwrap();

// for an actual EdDSA implementation, this would be some private key
let key = b"secret";
let my_claims = Claims { sub: "me".to_owned(), exp: 10000000000 };

// our crypto provider only supports EdDSA
let header = Header::new(Algorithm::EdDSA);

let token = match encode(&header, &my_claims, &EncodingKey::from_ed_der(key)) {
Ok(t) => t,
Err(_) => panic!(), // in practice you would return an error
};

let claims = match decode::<Claims>(
token,
&DecodingKey::from_ed_der(key),
&Validation::new(Algorithm::EdDSA),
) {
Ok(c) => c.claims,
Err(_) => panic!(),
};

assert_eq!(my_claims, claims);
}
1 change: 1 addition & 0 deletions src/algorithms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use crate::errors::{Error, ErrorKind, Result};

#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
#[allow(missing_docs)]
pub enum AlgorithmFamily {
Hmac,
Rsa,
Expand Down
69 changes: 65 additions & 4 deletions src/crypto/aws_lc/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,65 @@
pub(crate) mod ecdsa;
pub(crate) mod eddsa;
pub(crate) mod hmac;
pub(crate) mod rsa;
use aws_lc_rs::{
digest,
signature::{self as aws_sig, KeyPair},
};

use crate::{
Algorithm, DecodingKey, EncodingKey,
crypto::{CryptoProvider, JwkUtils, JwtSigner, JwtVerifier},
errors::{self, Error, ErrorKind},
jwk::{EllipticCurve, ThumbprintHash},
};

mod ecdsa;
mod eddsa;
mod hmac;
mod rsa;

/// Given a DER encoded private key, extract the RSA public key components (n, e)
pub fn extract_rsa_public_key_components(key_content: &[u8]) -> errors::Result<(Vec<u8>, Vec<u8>)> {
let key_pair = aws_sig::RsaKeyPair::from_der(key_content)
.map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?;
let public = key_pair.public_key();
let components = aws_sig::RsaPublicKeyComponents::<Vec<u8>>::from(public);
Ok((components.n, components.e))
}

/// Given a DER encoded private key and an algorithm, extract the associated curve
/// and the EC public key components (x, y)
pub fn extract_ec_public_key_coordinates(
key_content: &[u8],
alg: Algorithm,
) -> errors::Result<(EllipticCurve, Vec<u8>, Vec<u8>)> {
use aws_lc_rs::signature::{
ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING, EcdsaKeyPair,
};

let (signing_alg, curve, pub_elem_bytes) = match alg {
Algorithm::ES256 => (&ECDSA_P256_SHA256_FIXED_SIGNING, EllipticCurve::P256, 32),
Algorithm::ES384 => (&ECDSA_P384_SHA384_FIXED_SIGNING, EllipticCurve::P384, 48),
_ => return Err(ErrorKind::InvalidEcdsaKey.into()),
};

let key_pair = EcdsaKeyPair::from_pkcs8(signing_alg, key_content)
.map_err(|_| ErrorKind::InvalidEcdsaKey)?;

let pub_bytes = key_pair.public_key().as_ref();
if pub_bytes[0] != 4 {
return Err(ErrorKind::InvalidEcdsaKey.into());
}

let (x, y) = pub_bytes[1..].split_at(pub_elem_bytes);
Ok((curve, x.to_vec(), y.to_vec()))
}

/// Given some data and a name of a hash function, compute hash_function(data)
pub fn compute_digest(data: &[u8], hash_function: ThumbprintHash) -> Vec<u8> {
let algorithm = match hash_function {
ThumbprintHash::SHA256 => &digest::SHA256,
ThumbprintHash::SHA384 => &digest::SHA384,
ThumbprintHash::SHA512 => &digest::SHA512,
};
digest::digest(algorithm, data).as_ref().to_vec()
}

define_default_provider!("aws_lc_rs", "https://github.com/aws/aws-lc-rs");
90 changes: 90 additions & 0 deletions src/crypto/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#[cfg(any(feature = "rust_crypto", feature = "aws_lc_rs"))]
macro_rules! define_default_provider {
($name:literal, $link:literal) => {
#[doc = "The default [`CryptoProvider`] backed by [`"]
#[doc = $name]
#[doc = "`]"]
#[doc = concat!("The default [`CryptoProvider`] backed by [`", $name, "`]")]
#[doc = ""]
#[doc = concat!("[`", $name, "`]: ", $link)]
pub const DEFAULT_PROVIDER: CryptoProvider = CryptoProvider {
signer_factory: new_signer,
verifier_factory: new_verifier,
jwk_utils: JwkUtils {
extract_rsa_public_key_components,
extract_ec_public_key_coordinates,
compute_digest,
},
};

#[doc = "Create a new [`JwtSigner`] for a given [`Algorithm`]."]
pub fn new_signer(
algorithm: &Algorithm,
key: &EncodingKey,
) -> Result<Box<dyn JwtSigner>, Error> {
let jwt_signer = match algorithm {
Algorithm::HS256 => Box::new(hmac::Hs256Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::HS384 => Box::new(hmac::Hs384Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::HS512 => Box::new(hmac::Hs512Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::ES256 => Box::new(ecdsa::Es256Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::ES384 => Box::new(ecdsa::Es384Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::RS256 => Box::new(rsa::Rsa256Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::RS384 => Box::new(rsa::Rsa384Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::RS512 => Box::new(rsa::Rsa512Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::PS256 => Box::new(rsa::RsaPss256Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::PS384 => Box::new(rsa::RsaPss384Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::PS512 => Box::new(rsa::RsaPss512Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::EdDSA => Box::new(eddsa::EdDSASigner::new(key)?) as Box<dyn JwtSigner>,
};

Ok(jwt_signer)
}

#[doc = "Create a new [`JwtVerifier`] for a given [`Algorithm`]."]
pub fn new_verifier(
algorithm: &Algorithm,
key: &DecodingKey,
) -> Result<Box<dyn super::JwtVerifier>, Error> {
let jwt_encoder = match algorithm {
Algorithm::HS256 => {
Box::new(hmac::Hs256Verifier::new(key)?) as Box<dyn JwtVerifier>
}
Algorithm::HS384 => {
Box::new(hmac::Hs384Verifier::new(key)?) as Box<dyn JwtVerifier>
}
Algorithm::HS512 => {
Box::new(hmac::Hs512Verifier::new(key)?) as Box<dyn JwtVerifier>
}
Algorithm::ES256 => {
Box::new(ecdsa::Es256Verifier::new(key)?) as Box<dyn JwtVerifier>
}
Algorithm::ES384 => {
Box::new(ecdsa::Es384Verifier::new(key)?) as Box<dyn JwtVerifier>
}
Algorithm::RS256 => {
Box::new(rsa::Rsa256Verifier::new(key)?) as Box<dyn JwtVerifier>
}
Algorithm::RS384 => {
Box::new(rsa::Rsa384Verifier::new(key)?) as Box<dyn JwtVerifier>
}
Algorithm::RS512 => {
Box::new(rsa::Rsa512Verifier::new(key)?) as Box<dyn JwtVerifier>
}
Algorithm::PS256 => {
Box::new(rsa::RsaPss256Verifier::new(key)?) as Box<dyn JwtVerifier>
}
Algorithm::PS384 => {
Box::new(rsa::RsaPss384Verifier::new(key)?) as Box<dyn JwtVerifier>
}
Algorithm::PS512 => {
Box::new(rsa::RsaPss512Verifier::new(key)?) as Box<dyn JwtVerifier>
}
Algorithm::EdDSA => {
Box::new(eddsa::EdDSAVerifier::new(key)?) as Box<dyn JwtVerifier>
}
};

Ok(jwt_encoder)
}
};
}
Loading