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);
}
6 changes: 6 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 All @@ -34,6 +39,7 @@ impl AlgorithmFamily {
/// The algorithms supported for signing/verifying JWTs
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Default, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Algorithm {
/// HMAC using SHA-256
#[default]
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
Loading
Loading