Skip to content

Commit 72e0c7f

Browse files
authored
Expose cryptography backends via CryptoProvider (#452)
1 parent 53a3fc2 commit 72e0c7f

18 files changed

Lines changed: 443 additions & 268 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ jsonwebtoken = { version = "10", features = ["aws_lc_rs"] }
1515
serde = {version = "1.0", features = ["derive"] }
1616
```
1717

18-
Two crypto backends are available via features, `aws_lc_rs` and `rust_crypto`, exactly one of which must be enabled.
18+
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`.
19+
20+
For examples of how to implement a `CryptoProvider`, see
21+
- [arckoor/jsonwebtoken-botan](https://github.com/arckoor/jsonwebtoken-botan)
1922

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

src/algorithms.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ use serde::{Deserialize, Serialize};
55
use crate::errors::{Error, ErrorKind, Result};
66

77
#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
8+
/// Supported families of algorithms.
89
pub enum AlgorithmFamily {
10+
/// HMAC shared secret family.
911
Hmac,
12+
/// RSA-based public key family.
1013
Rsa,
14+
/// Edwards curve public key family.
1115
Ec,
16+
/// Elliptic curve public key family.
1217
Ed,
1318
}
1419

src/crypto/aws_lc/ecdsa.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ macro_rules! define_ecdsa_signer {
1818

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

@@ -51,7 +51,7 @@ macro_rules! define_ecdsa_verifier {
5151

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

src/crypto/aws_lc/eddsa.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub struct EdDSASigner(Ed25519KeyPair);
1111

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

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

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

src/crypto/aws_lc/mod.rs

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,110 @@
1-
pub(crate) mod ecdsa;
2-
pub(crate) mod eddsa;
3-
pub(crate) mod hmac;
4-
pub(crate) mod rsa;
1+
use aws_lc_rs::{
2+
digest,
3+
signature::{
4+
self as aws_sig, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
5+
EcdsaKeyPair, KeyPair,
6+
},
7+
};
8+
9+
use crate::{
10+
Algorithm, DecodingKey, EncodingKey,
11+
crypto::{CryptoProvider, JwkUtils, JwtSigner, JwtVerifier},
12+
errors::{self, Error, ErrorKind},
13+
jwk::{EllipticCurve, ThumbprintHash},
14+
};
15+
16+
mod ecdsa;
17+
mod eddsa;
18+
mod hmac;
19+
mod rsa;
20+
21+
fn extract_rsa_public_key_components(key_content: &[u8]) -> errors::Result<(Vec<u8>, Vec<u8>)> {
22+
let key_pair = aws_sig::RsaKeyPair::from_der(key_content)
23+
.map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?;
24+
let public = key_pair.public_key();
25+
let components = aws_sig::RsaPublicKeyComponents::<Vec<u8>>::from(public);
26+
Ok((components.n, components.e))
27+
}
28+
29+
fn extract_ec_public_key_coordinates(
30+
key_content: &[u8],
31+
alg: Algorithm,
32+
) -> errors::Result<(EllipticCurve, Vec<u8>, Vec<u8>)> {
33+
let (signing_alg, curve, pub_elem_bytes) = match alg {
34+
Algorithm::ES256 => (&ECDSA_P256_SHA256_FIXED_SIGNING, EllipticCurve::P256, 32),
35+
Algorithm::ES384 => (&ECDSA_P384_SHA384_FIXED_SIGNING, EllipticCurve::P384, 48),
36+
_ => return Err(ErrorKind::InvalidEcdsaKey.into()),
37+
};
38+
39+
let key_pair = EcdsaKeyPair::from_pkcs8(signing_alg, key_content)
40+
.map_err(|_| ErrorKind::InvalidEcdsaKey)?;
41+
42+
let pub_bytes = key_pair.public_key().as_ref();
43+
if pub_bytes[0] != 4 {
44+
return Err(ErrorKind::InvalidEcdsaKey.into());
45+
}
46+
47+
let (x, y) = pub_bytes[1..].split_at(pub_elem_bytes);
48+
Ok((curve, x.to_vec(), y.to_vec()))
49+
}
50+
51+
fn compute_digest(data: &[u8], hash_function: ThumbprintHash) -> Vec<u8> {
52+
let algorithm = match hash_function {
53+
ThumbprintHash::SHA256 => &digest::SHA256,
54+
ThumbprintHash::SHA384 => &digest::SHA384,
55+
ThumbprintHash::SHA512 => &digest::SHA512,
56+
};
57+
digest::digest(algorithm, data).as_ref().to_vec()
58+
}
59+
60+
fn new_signer(algorithm: &Algorithm, key: &EncodingKey) -> Result<Box<dyn JwtSigner>, Error> {
61+
let jwt_signer = match algorithm {
62+
Algorithm::HS256 => Box::new(hmac::Hs256Signer::new(key)?) as Box<dyn JwtSigner>,
63+
Algorithm::HS384 => Box::new(hmac::Hs384Signer::new(key)?) as Box<dyn JwtSigner>,
64+
Algorithm::HS512 => Box::new(hmac::Hs512Signer::new(key)?) as Box<dyn JwtSigner>,
65+
Algorithm::ES256 => Box::new(ecdsa::Es256Signer::new(key)?) as Box<dyn JwtSigner>,
66+
Algorithm::ES384 => Box::new(ecdsa::Es384Signer::new(key)?) as Box<dyn JwtSigner>,
67+
Algorithm::RS256 => Box::new(rsa::Rsa256Signer::new(key)?) as Box<dyn JwtSigner>,
68+
Algorithm::RS384 => Box::new(rsa::Rsa384Signer::new(key)?) as Box<dyn JwtSigner>,
69+
Algorithm::RS512 => Box::new(rsa::Rsa512Signer::new(key)?) as Box<dyn JwtSigner>,
70+
Algorithm::PS256 => Box::new(rsa::RsaPss256Signer::new(key)?) as Box<dyn JwtSigner>,
71+
Algorithm::PS384 => Box::new(rsa::RsaPss384Signer::new(key)?) as Box<dyn JwtSigner>,
72+
Algorithm::PS512 => Box::new(rsa::RsaPss512Signer::new(key)?) as Box<dyn JwtSigner>,
73+
Algorithm::EdDSA => Box::new(eddsa::EdDSASigner::new(key)?) as Box<dyn JwtSigner>,
74+
};
75+
76+
Ok(jwt_signer)
77+
}
78+
79+
fn new_verifier(
80+
algorithm: &Algorithm,
81+
key: &DecodingKey,
82+
) -> Result<Box<dyn super::JwtVerifier>, Error> {
83+
let jwt_verifier = match algorithm {
84+
Algorithm::HS256 => Box::new(hmac::Hs256Verifier::new(key)?) as Box<dyn JwtVerifier>,
85+
Algorithm::HS384 => Box::new(hmac::Hs384Verifier::new(key)?) as Box<dyn JwtVerifier>,
86+
Algorithm::HS512 => Box::new(hmac::Hs512Verifier::new(key)?) as Box<dyn JwtVerifier>,
87+
Algorithm::ES256 => Box::new(ecdsa::Es256Verifier::new(key)?) as Box<dyn JwtVerifier>,
88+
Algorithm::ES384 => Box::new(ecdsa::Es384Verifier::new(key)?) as Box<dyn JwtVerifier>,
89+
Algorithm::RS256 => Box::new(rsa::Rsa256Verifier::new(key)?) as Box<dyn JwtVerifier>,
90+
Algorithm::RS384 => Box::new(rsa::Rsa384Verifier::new(key)?) as Box<dyn JwtVerifier>,
91+
Algorithm::RS512 => Box::new(rsa::Rsa512Verifier::new(key)?) as Box<dyn JwtVerifier>,
92+
Algorithm::PS256 => Box::new(rsa::RsaPss256Verifier::new(key)?) as Box<dyn JwtVerifier>,
93+
Algorithm::PS384 => Box::new(rsa::RsaPss384Verifier::new(key)?) as Box<dyn JwtVerifier>,
94+
Algorithm::PS512 => Box::new(rsa::RsaPss512Verifier::new(key)?) as Box<dyn JwtVerifier>,
95+
Algorithm::EdDSA => Box::new(eddsa::EdDSAVerifier::new(key)?) as Box<dyn JwtVerifier>,
96+
};
97+
98+
Ok(jwt_verifier)
99+
}
100+
101+
/// The default [`CryptoProvider`] backed by [`aws_lc_rs`].
102+
pub static DEFAULT_PROVIDER: CryptoProvider = CryptoProvider {
103+
signer_factory: new_signer,
104+
verifier_factory: new_verifier,
105+
jwk_utils: JwkUtils {
106+
extract_rsa_public_key_components,
107+
extract_ec_public_key_coordinates,
108+
compute_digest,
109+
},
110+
};

src/crypto/aws_lc/rsa.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ fn verify_rsa(
3737
msg: &[u8],
3838
signature: &[u8],
3939
) -> std::result::Result<(), signature::Error> {
40-
match &decoding_key.kind {
40+
match decoding_key.kind() {
4141
DecodingKeyKind::SecretOrDer(bytes) => {
4242
let public_key = crypto_sig::UnparsedPublicKey::new(algorithm, bytes);
4343
public_key.verify(msg, signature).map_err(signature::Error::from_source)?;
@@ -57,7 +57,7 @@ macro_rules! define_rsa_signer {
5757

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

@@ -85,7 +85,7 @@ macro_rules! define_rsa_verifier {
8585

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

src/crypto/mod.rs

Lines changed: 129 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
//! The cryptography of the `jsonwebtoken` crate is decoupled behind
2-
//! [`JwtSigner`] and [`JwtVerifier`] traits. These make use of `RustCrypto`'s
2+
//! [`JwtSigner`] and [`JwtVerifier`] traits. These make use of `signature`'s
33
//! [`Signer`] and [`Verifier`] traits respectively.
4+
//! Crypto provider selection is handled by [`CryptoProvider`].
45
//!
56
//! [`JwtSigner`]: crate::crypto::JwtSigner
67
//! [`JwtVerifier`]: crate::crypto::JwtVerifier
78
//! [`Signer`]: signature::Signer
89
//! [`Verifier`]: signature::Verifier
10+
//! [`CryptoProvider`]: crate::crypto::CryptoProvider
911
1012
use crate::algorithms::Algorithm;
1113
use crate::errors::Result;
14+
use crate::jwk::{EllipticCurve, ThumbprintHash};
1215
use crate::{DecodingKey, EncodingKey};
1316

17+
/// `aws_lc_rs` based CryptoProvider.
1418
#[cfg(feature = "aws_lc_rs")]
15-
pub(crate) mod aws_lc;
19+
pub mod aws_lc;
20+
21+
/// `RustCrypto` based CryptoProvider.
1622
#[cfg(feature = "rust_crypto")]
17-
pub(crate) mod rust_crypto;
23+
pub mod rust_crypto;
1824

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

@@ -58,6 +64,124 @@ pub fn verify(
5864
key: &DecodingKey,
5965
algorithm: Algorithm,
6066
) -> Result<bool> {
61-
let provider = crate::decoding::jwt_verifier_factory(&algorithm, key)?;
67+
let provider = (CryptoProvider::get_default().verifier_factory)(&algorithm, key)?;
6268
Ok(provider.verify(message, &b64_decode(signature)?).is_ok())
6369
}
70+
71+
/// Controls the cryptography used by jsonwebtoken.
72+
///
73+
/// You can either install one of the built-in options:
74+
/// - [`crypto::aws_lc::DEFAULT_PROVIDER`]: (behind the `aws_lc_rs` crate feature).
75+
/// This provider uses the [aws-lc-rs](https://github.com/aws/aws-lc-rs) crate.
76+
/// - [`crypto::rust_crypto::DEFAULT_PROVIDER`]: (behind the `rust_crypto` crate feature)
77+
/// This provider uses crates from the [Rust Crypto](https://github.com/RustCrypto) project.
78+
///
79+
/// or provide your own custom custom implementation of `CryptoProvider`.
80+
// This implementation appropriates a good chunk of code from the `rustls` CryptoProvider,
81+
// and is very much inspired by it.
82+
#[derive(Clone, Debug)]
83+
pub struct CryptoProvider {
84+
/// A function that produces a [`JwtSigner`] for a given [`Algorithm`]
85+
pub signer_factory: fn(&Algorithm, &EncodingKey) -> Result<Box<dyn JwtSigner>>,
86+
/// A function that produces a [`JwtVerifier`] for a given [`Algorithm`]
87+
pub verifier_factory: fn(&Algorithm, &DecodingKey) -> Result<Box<dyn JwtVerifier>>,
88+
/// Struct with utility functions for JWK processing.
89+
pub jwk_utils: JwkUtils,
90+
}
91+
92+
impl CryptoProvider {
93+
/// Set this `CryptoProvider` as the default for this process.
94+
///
95+
/// This can be called successfully at most once in any process execution.
96+
pub fn install_default(&'static self) -> std::result::Result<(), &'static Self> {
97+
static_default::install_default(self)
98+
}
99+
100+
pub(crate) fn get_default() -> &'static Self {
101+
static_default::get_default()
102+
}
103+
104+
fn from_crate_features() -> &'static Self {
105+
#[cfg(all(feature = "rust_crypto", not(feature = "aws_lc_rs")))]
106+
{
107+
return &rust_crypto::DEFAULT_PROVIDER;
108+
}
109+
110+
#[cfg(all(feature = "aws_lc_rs", not(feature = "rust_crypto")))]
111+
{
112+
return &aws_lc::DEFAULT_PROVIDER;
113+
}
114+
115+
#[allow(unreachable_code)]
116+
{
117+
const NOT_INSTALLED_ERROR: &str = r###"
118+
Could not automatically determine the process-level CryptoProvider from jsonwebtoken crate features.
119+
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.
120+
See the documentation of the CryptoProvider type for more information.
121+
"###;
122+
123+
static INSTANCE: CryptoProvider = CryptoProvider {
124+
signer_factory: |_, _| panic!("{}", NOT_INSTALLED_ERROR),
125+
verifier_factory: |_, _| panic!("{}", NOT_INSTALLED_ERROR),
126+
jwk_utils: JwkUtils::new_unimplemented(),
127+
};
128+
129+
&INSTANCE
130+
}
131+
}
132+
}
133+
134+
/// Holds utility functions required for JWK processing.
135+
/// Use the [`JwkUtils::new_unimplemented`] function to initialize all values to dummies.
136+
#[derive(Clone, Debug)]
137+
pub struct JwkUtils {
138+
/// Given a DER encoded private key, extract the RSA public key components (n, e)
139+
#[allow(clippy::type_complexity)]
140+
pub extract_rsa_public_key_components: fn(&[u8]) -> Result<(Vec<u8>, Vec<u8>)>,
141+
/// Given a DER encoded private key and an algorithm, extract the associated curve
142+
/// and the EC public key components (x, y)
143+
#[allow(clippy::type_complexity)]
144+
pub extract_ec_public_key_coordinates:
145+
fn(&[u8], Algorithm) -> Result<(EllipticCurve, Vec<u8>, Vec<u8>)>,
146+
/// Given some data and a name of a hash function, compute hash_function(data)
147+
pub compute_digest: fn(&[u8], ThumbprintHash) -> Vec<u8>,
148+
}
149+
150+
impl JwkUtils {
151+
/// Initialises all values to dummies.
152+
/// Will lead to a panic when JWKs are required, so only use it if you don't want to support JWKs.
153+
pub const fn new_unimplemented() -> Self {
154+
const NOT_INSTALLED_OR_UNIMPLEMENTED_ERROR: &str = r###"
155+
Could not automatically determine the process-level CryptoProvider from jsonwebtoken crate features, or your CryptoProvider does not support JWKs.
156+
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.
157+
See the documentation of the CryptoProvider type for more information.
158+
"###;
159+
Self {
160+
extract_rsa_public_key_components: |_| {
161+
panic!("{}", NOT_INSTALLED_OR_UNIMPLEMENTED_ERROR)
162+
},
163+
extract_ec_public_key_coordinates: |_, _| {
164+
panic!("{}", NOT_INSTALLED_OR_UNIMPLEMENTED_ERROR)
165+
},
166+
compute_digest: |_, _| panic!("{}", NOT_INSTALLED_OR_UNIMPLEMENTED_ERROR),
167+
}
168+
}
169+
}
170+
171+
mod static_default {
172+
use std::sync::OnceLock;
173+
174+
use super::CryptoProvider;
175+
176+
static PROCESS_DEFAULT_PROVIDER: OnceLock<&'static CryptoProvider> = OnceLock::new();
177+
178+
pub(crate) fn install_default(
179+
default_provider: &'static CryptoProvider,
180+
) -> Result<(), &'static CryptoProvider> {
181+
PROCESS_DEFAULT_PROVIDER.set(default_provider)
182+
}
183+
184+
pub(crate) fn get_default() -> &'static CryptoProvider {
185+
PROCESS_DEFAULT_PROVIDER.get_or_init(CryptoProvider::from_crate_features)
186+
}
187+
}

src/crypto/rust_crypto/ecdsa.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ macro_rules! define_ecdsa_signer {
2020

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

@@ -52,7 +52,7 @@ macro_rules! define_ecdsa_verifier {
5252

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

0 commit comments

Comments
 (0)