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
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ wasm-bindgen-test = "0.3.1"
ed25519-dalek = { version = "2.1.1", features = ["pkcs8", "rand_core"] }
rand = { version = "0.8.5", features = ["std"], default-features = false }
rand_core = "0.6.4"
# for the custom provider example
botan = { version = "0.12.0", features = ["vendored"] }
[target.'cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))'.dev-dependencies]
# For the custom time example
time = "0.3"
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 select neither feature, you need to provide your own `CryptoProvider`.

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

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

fn new_signer(algorithm: &Algorithm, key: &EncodingKey) -> Result<Box<dyn JwtSigner>, Error> {
let jwt_signer = match algorithm {
Algorithm::EdDSA => Box::new(EdDSASigner::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(EdDSAVerifier::new(key)?) as Box<dyn JwtVerifier>,
_ => unimplemented!(),
};

Ok(jwt_verifier)
}

struct EdDSASigner(botan::Privkey);

impl EdDSASigner {
fn new(encoding_key: &EncodingKey) -> Result<Self, Error> {
if encoding_key.family() != AlgorithmFamily::Ed {
return Err(ErrorKind::InvalidKeyFormat.into());
}

Ok(Self(
botan::Privkey::load_der(encoding_key.inner())
.map_err(|_| ErrorKind::InvalidEddsaKey)?,
))
}
}

impl Signer<Vec<u8>> for EdDSASigner {
fn try_sign(&self, msg: &[u8]) -> std::result::Result<Vec<u8>, SigError> {
let mut rng = botan::RandomNumberGenerator::new_system().map_err(SigError::from_source)?;
let mut signer = botan::Signer::new(&self.0, "Pure").map_err(SigError::from_source)?;
signer.update(msg).map_err(SigError::from_source)?;
signer.finish(&mut rng).map_err(SigError::from_source)
}
}

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

struct EdDSAVerifier(botan::Pubkey);

impl EdDSAVerifier {
fn new(decoding_key: &DecodingKey) -> Result<Self, Error> {
if decoding_key.family() != AlgorithmFamily::Ed {
return Err(ErrorKind::InvalidKeyFormat.into());
}

Ok(Self(
botan::Pubkey::load_ed25519(decoding_key.as_bytes())
.map_err(|_| ErrorKind::InvalidEddsaKey)?,
))
}
}

impl Verifier<Vec<u8>> for EdDSAVerifier {
fn verify(&self, msg: &[u8], signature: &Vec<u8>) -> std::result::Result<(), SigError> {
let mut verifier = botan::Verifier::new(&self.0, "Pure").map_err(SigError::from_source)?;
verifier.update(msg).map_err(SigError::from_source)?;
verifier
.finish(signature)
.map_err(SigError::from_source)?
.then_some(())
.ok_or(SigError::new())
}
}

impl JwtVerifier for EdDSAVerifier {
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();

// generate a new key
let (privkey, pubkey) = {
let key = botan::Privkey::create(
"Ed25519",
"",
&mut botan::RandomNumberGenerator::new_system().unwrap(),
)
.unwrap();
(key.pem_encode().unwrap(), key.pubkey().unwrap().pem_encode().unwrap())
};
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_pem(privkey.as_bytes()).unwrap()) {
Ok(t) => t,
Err(_) => panic!(), // in practice you would return an error
};

let claims = match decode::<Claims>(
token,
&DecodingKey::from_ed_pem(pubkey.as_bytes()).unwrap(),
&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
4 changes: 2 additions & 2 deletions src/crypto/aws_lc/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ macro_rules! define_ecdsa_signer {

impl $name {
pub(crate) fn new(encoding_key: &EncodingKey) -> Result<Self> {
if encoding_key.family != AlgorithmFamily::Ec {
if encoding_key.family() != AlgorithmFamily::Ec {
return Err(new_error(ErrorKind::InvalidKeyFormat));
}

Expand Down Expand Up @@ -51,7 +51,7 @@ macro_rules! define_ecdsa_verifier {

impl $name {
pub(crate) fn new(decoding_key: &DecodingKey) -> Result<Self> {
if decoding_key.family != AlgorithmFamily::Ec {
if decoding_key.family() != AlgorithmFamily::Ec {
return Err(new_error(ErrorKind::InvalidKeyFormat));
}

Expand Down
4 changes: 2 additions & 2 deletions src/crypto/aws_lc/eddsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub struct EdDSASigner(Ed25519KeyPair);

impl EdDSASigner {
pub(crate) fn new(encoding_key: &EncodingKey) -> Result<Self> {
if encoding_key.family != AlgorithmFamily::Ed {
if encoding_key.family() != AlgorithmFamily::Ed {
return Err(new_error(ErrorKind::InvalidKeyFormat));
}

Expand All @@ -38,7 +38,7 @@ pub struct EdDSAVerifier(DecodingKey);

impl EdDSAVerifier {
pub(crate) fn new(decoding_key: &DecodingKey) -> Result<Self> {
if decoding_key.family != AlgorithmFamily::Ed {
if decoding_key.family() != AlgorithmFamily::Ed {
return Err(new_error(ErrorKind::InvalidKeyFormat));
}

Expand Down
65 changes: 61 additions & 4 deletions src/crypto/aws_lc/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,61 @@
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;

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))
}

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()))
}

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");
6 changes: 3 additions & 3 deletions src/crypto/aws_lc/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fn verify_rsa(
msg: &[u8],
signature: &[u8],
) -> std::result::Result<(), signature::Error> {
match &decoding_key.kind {
match decoding_key.kind() {
DecodingKeyKind::SecretOrDer(bytes) => {
let public_key = crypto_sig::UnparsedPublicKey::new(algorithm, bytes);
public_key.verify(msg, signature).map_err(signature::Error::from_source)?;
Expand All @@ -57,7 +57,7 @@ macro_rules! define_rsa_signer {

impl $name {
pub(crate) fn new(encoding_key: &EncodingKey) -> Result<Self> {
if encoding_key.family != AlgorithmFamily::Rsa {
if encoding_key.family() != AlgorithmFamily::Rsa {
return Err(new_error(ErrorKind::InvalidKeyFormat));
}

Expand Down Expand Up @@ -85,7 +85,7 @@ macro_rules! define_rsa_verifier {

impl $name {
pub(crate) fn new(decoding_key: &DecodingKey) -> Result<Self> {
if decoding_key.family != AlgorithmFamily::Rsa {
if decoding_key.family() != AlgorithmFamily::Rsa {
return Err(new_error(ErrorKind::InvalidKeyFormat));
}

Expand Down
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>,
};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that will get more complex if we start supporting some alg in some backend like #461


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