Skip to content
Merged
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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ 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`.

For examples of how to implement a `CryptoProvider`, see
- [arckoor/jsonwebtoken-botan](https://github.com/arckoor/jsonwebtoken-botan)

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

Expand Down
5 changes: 5 additions & 0 deletions src/algorithms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ use serde::{Deserialize, Serialize};
use crate::errors::{Error, ErrorKind, Result};

#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
/// Supported families of algorithms.
pub enum AlgorithmFamily {
/// HMAC shared secret family.
Hmac,
/// RSA-based public key family.
Rsa,
/// Edwards curve public key family.
Ec,
/// Elliptic curve public key family.
Ed,
}

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
114 changes: 110 additions & 4 deletions src/crypto/aws_lc/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,110 @@
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, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
EcdsaKeyPair, 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>)> {
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()
}

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

fn new_verifier(
algorithm: &Algorithm,
key: &DecodingKey,
) -> Result<Box<dyn super::JwtVerifier>, Error> {
let jwt_verifier = 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_verifier)
}

/// The default [`CryptoProvider`] backed by [`aws_lc_rs`].
pub static 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,
},
};
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
134 changes: 129 additions & 5 deletions src/crypto/mod.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
//! The cryptography of the `jsonwebtoken` crate is decoupled behind
//! [`JwtSigner`] and [`JwtVerifier`] traits. These make use of `RustCrypto`'s
//! [`JwtSigner`] and [`JwtVerifier`] traits. These make use of `signature`'s
//! [`Signer`] and [`Verifier`] traits respectively.
//! Crypto provider selection is handled by [`CryptoProvider`].
//!
//! [`JwtSigner`]: crate::crypto::JwtSigner
//! [`JwtVerifier`]: crate::crypto::JwtVerifier
//! [`Signer`]: signature::Signer
//! [`Verifier`]: signature::Verifier
//! [`CryptoProvider`]: crate::crypto::CryptoProvider

use crate::algorithms::Algorithm;
use crate::errors::Result;
use crate::jwk::{EllipticCurve, ThumbprintHash};
use crate::{DecodingKey, EncodingKey};

/// `aws_lc_rs` based CryptoProvider.
#[cfg(feature = "aws_lc_rs")]
pub(crate) mod aws_lc;
pub mod aws_lc;

/// `RustCrypto` based CryptoProvider.
#[cfg(feature = "rust_crypto")]
pub(crate) mod rust_crypto;
pub mod rust_crypto;

use crate::serialization::{b64_decode, b64_encode};
use signature::{Signer, Verifier};
Expand All @@ -40,7 +46,7 @@ pub trait JwtVerifier: Verifier<Vec<u8>> {
///
/// If you just want to encode a JWT, use `encode` instead.
pub fn sign(message: &[u8], key: &EncodingKey, algorithm: Algorithm) -> Result<String> {
let provider = crate::encoding::jwt_signer_factory(&algorithm, key)?;
let provider = (CryptoProvider::get_default().signer_factory)(&algorithm, key)?;
Ok(b64_encode(provider.sign(message)))
}

Expand All @@ -58,6 +64,124 @@ pub fn verify(
key: &DecodingKey,
algorithm: Algorithm,
) -> Result<bool> {
let provider = crate::decoding::jwt_verifier_factory(&algorithm, key)?;
let provider = (CryptoProvider::get_default().verifier_factory)(&algorithm, key)?;
Ok(provider.verify(message, &b64_decode(signature)?).is_ok())
}

/// Controls the cryptography used by jsonwebtoken.
///
/// You can either install one of the built-in options:
/// - [`crypto::aws_lc::DEFAULT_PROVIDER`]: (behind the `aws_lc_rs` crate feature).
/// This provider uses the [aws-lc-rs](https://github.com/aws/aws-lc-rs) crate.
/// - [`crypto::rust_crypto::DEFAULT_PROVIDER`]: (behind the `rust_crypto` crate feature)
/// This provider uses crates from the [Rust Crypto](https://github.com/RustCrypto) project.
///
/// or provide your own custom custom implementation of `CryptoProvider`.
// This implementation appropriates a good chunk of code from the `rustls` CryptoProvider,
// and is very much inspired by it.
#[derive(Clone, Debug)]
pub struct CryptoProvider {
/// A function that produces a [`JwtSigner`] for a given [`Algorithm`]
pub signer_factory: fn(&Algorithm, &EncodingKey) -> Result<Box<dyn JwtSigner>>,
/// A function that produces a [`JwtVerifier`] for a given [`Algorithm`]
pub verifier_factory: fn(&Algorithm, &DecodingKey) -> Result<Box<dyn JwtVerifier>>,
/// Struct with utility functions for JWK processing.
pub jwk_utils: JwkUtils,
}

impl CryptoProvider {
/// Set this `CryptoProvider` as the default for this process.
///
/// This can be called successfully at most once in any process execution.
pub fn install_default(&'static self) -> std::result::Result<(), &'static Self> {
static_default::install_default(self)
}

pub(crate) fn get_default() -> &'static Self {
static_default::get_default()
}

fn from_crate_features() -> &'static Self {
#[cfg(all(feature = "rust_crypto", not(feature = "aws_lc_rs")))]
{
return &rust_crypto::DEFAULT_PROVIDER;
}

#[cfg(all(feature = "aws_lc_rs", not(feature = "rust_crypto")))]
{
return &aws_lc::DEFAULT_PROVIDER;
}

#[allow(unreachable_code)]
{
const NOT_INSTALLED_ERROR: &str = r###"
Could not automatically determine the process-level CryptoProvider from jsonwebtoken crate features.
Call CryptoProvider::install_default() before this point to select a provider manually, or make sure exactly one of the 'rust_crypto' and 'aws_lc_rs' features is enabled.
See the documentation of the CryptoProvider type for more information.
"###;

static INSTANCE: CryptoProvider = CryptoProvider {
signer_factory: |_, _| panic!("{}", NOT_INSTALLED_ERROR),
verifier_factory: |_, _| panic!("{}", NOT_INSTALLED_ERROR),
jwk_utils: JwkUtils::new_unimplemented(),
};

&INSTANCE
}
}
}

/// Holds utility functions required for JWK processing.
/// Use the [`JwkUtils::new_unimplemented`] function to initialize all values to dummies.
#[derive(Clone, Debug)]
pub struct JwkUtils {
/// Given a DER encoded private key, extract the RSA public key components (n, e)
#[allow(clippy::type_complexity)]
pub extract_rsa_public_key_components: fn(&[u8]) -> Result<(Vec<u8>, Vec<u8>)>,
/// Given a DER encoded private key and an algorithm, extract the associated curve
/// and the EC public key components (x, y)
#[allow(clippy::type_complexity)]
pub extract_ec_public_key_coordinates:
fn(&[u8], Algorithm) -> Result<(EllipticCurve, Vec<u8>, Vec<u8>)>,
/// Given some data and a name of a hash function, compute hash_function(data)
pub compute_digest: fn(&[u8], ThumbprintHash) -> Vec<u8>,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Seems I forgot to bring this up before, but it would really be great if we could do a breaking change to turn this into Result<Vec<u8>>. Everything else lets you return an error should it happen in your custom provider (e.g. botan can technically error here, because it does go through an FFI, and while it shouldn't error, I still need to call unwrap here)

Copy link
Owner

Choose a reason for hiding this comment

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

Can you create an issue for it? We can change that for the next major version?

}

impl JwkUtils {
/// Initialises all values to dummies.
/// Will lead to a panic when JWKs are required, so only use it if you don't want to support JWKs.
pub const fn new_unimplemented() -> Self {
const NOT_INSTALLED_OR_UNIMPLEMENTED_ERROR: &str = r###"
Could not automatically determine the process-level CryptoProvider from jsonwebtoken crate features, or your CryptoProvider does not support JWKs.
Call CryptoProvider::install_default() before this point to select a provider manually, or make sure exactly one of the 'rust_crypto' and 'aws_lc_rs' features is enabled.
See the documentation of the CryptoProvider type for more information.
"###;
Self {
extract_rsa_public_key_components: |_| {
panic!("{}", NOT_INSTALLED_OR_UNIMPLEMENTED_ERROR)
},
extract_ec_public_key_coordinates: |_, _| {
panic!("{}", NOT_INSTALLED_OR_UNIMPLEMENTED_ERROR)
},
compute_digest: |_, _| panic!("{}", NOT_INSTALLED_OR_UNIMPLEMENTED_ERROR),
}
}
}

mod static_default {
use std::sync::OnceLock;

use super::CryptoProvider;

static PROCESS_DEFAULT_PROVIDER: OnceLock<&'static CryptoProvider> = OnceLock::new();

pub(crate) fn install_default(
default_provider: &'static CryptoProvider,
) -> Result<(), &'static CryptoProvider> {
PROCESS_DEFAULT_PROVIDER.set(default_provider)
}

pub(crate) fn get_default() -> &'static CryptoProvider {
PROCESS_DEFAULT_PROVIDER.get_or_init(CryptoProvider::from_crate_features)
}
}
4 changes: 2 additions & 2 deletions src/crypto/rust_crypto/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,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 @@ -52,7 +52,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
Loading