From b419984ed19b5076fde0881e1e4f4e3774bfa688 Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Thu, 16 Oct 2025 22:49:47 +0200 Subject: [PATCH 1/4] Expose cryptography backends via CryptoProvider --- Cargo.toml | 1 + README.md | 2 +- examples/custom_provider.rs | 107 +++++++++++++++++++++++++ src/algorithms.rs | 1 + src/crypto/aws_lc/mod.rs | 69 +++++++++++++++- src/crypto/macros.rs | 90 +++++++++++++++++++++ src/crypto/mod.rs | 147 ++++++++++++++++++++++++++++++++-- src/crypto/rust_crypto/mod.rs | 74 ++++++++++++++++- src/decoding.rs | 88 ++++++++++---------- src/encoding.rs | 75 +++++++---------- src/jwk.rs | 129 ++++------------------------- src/jws.rs | 7 +- src/lib.rs | 15 ++-- 13 files changed, 575 insertions(+), 230 deletions(-) create mode 100644 examples/custom_provider.rs create mode 100644 src/crypto/macros.rs diff --git a/Cargo.toml b/Cargo.toml index f62f3a5e..5353ed28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 09b9f719..9fd74f41 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/examples/custom_provider.rs b/examples/custom_provider.rs new file mode 100644 index 00000000..713caa08 --- /dev/null +++ b/examples/custom_provider.rs @@ -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, Error> { + let jwt_signer = match algorithm { + Algorithm::EdDSA => Box::new(CustomEdDSASigner::new(key)?) as Box, + _ => unimplemented!(), + }; + + Ok(jwt_signer) +} + +fn new_verifier(algorithm: &Algorithm, key: &DecodingKey) -> Result, Error> { + let jwt_verifier = match algorithm { + Algorithm::EdDSA => Box::new(CustomEdDSAVerifier::new(key)?) as Box, + _ => unimplemented!(), + }; + + Ok(jwt_verifier) +} + +pub struct CustomEdDSASigner; + +impl CustomEdDSASigner { + fn new(_: &EncodingKey) -> Result { + Ok(CustomEdDSASigner) + } +} + +// WARNING: This is obviously not secure at all and should NEVER be done in practice! +impl Signer> for CustomEdDSASigner { + fn try_sign(&self, _: &[u8]) -> Result, 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 { + Ok(CustomEdDSAVerifier) + } +} + +impl Verifier> for CustomEdDSAVerifier { + fn verify(&self, _: &[u8], signature: &Vec) -> 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::( + token, + &DecodingKey::from_ed_der(key), + &Validation::new(Algorithm::EdDSA), + ) { + Ok(c) => c.claims, + Err(_) => panic!(), + }; + + assert_eq!(my_claims, claims); +} diff --git a/src/algorithms.rs b/src/algorithms.rs index 94eb3637..a3fb42d1 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -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, diff --git a/src/crypto/aws_lc/mod.rs b/src/crypto/aws_lc/mod.rs index 16f66b5f..687efaea 100644 --- a/src/crypto/aws_lc/mod.rs +++ b/src/crypto/aws_lc/mod.rs @@ -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, Vec)> { + 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::>::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, Vec)> { + 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 { + 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"); diff --git a/src/crypto/macros.rs b/src/crypto/macros.rs new file mode 100644 index 00000000..0b6ef710 --- /dev/null +++ b/src/crypto/macros.rs @@ -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, Error> { + let jwt_signer = match algorithm { + Algorithm::HS256 => Box::new(hmac::Hs256Signer::new(key)?) as Box, + Algorithm::HS384 => Box::new(hmac::Hs384Signer::new(key)?) as Box, + Algorithm::HS512 => Box::new(hmac::Hs512Signer::new(key)?) as Box, + Algorithm::ES256 => Box::new(ecdsa::Es256Signer::new(key)?) as Box, + Algorithm::ES384 => Box::new(ecdsa::Es384Signer::new(key)?) as Box, + Algorithm::RS256 => Box::new(rsa::Rsa256Signer::new(key)?) as Box, + Algorithm::RS384 => Box::new(rsa::Rsa384Signer::new(key)?) as Box, + Algorithm::RS512 => Box::new(rsa::Rsa512Signer::new(key)?) as Box, + Algorithm::PS256 => Box::new(rsa::RsaPss256Signer::new(key)?) as Box, + Algorithm::PS384 => Box::new(rsa::RsaPss384Signer::new(key)?) as Box, + Algorithm::PS512 => Box::new(rsa::RsaPss512Signer::new(key)?) as Box, + Algorithm::EdDSA => Box::new(eddsa::EdDSASigner::new(key)?) as Box, + }; + + Ok(jwt_signer) + } + + #[doc = "Create a new [`JwtVerifier`] for a given [`Algorithm`]."] + pub fn new_verifier( + algorithm: &Algorithm, + key: &DecodingKey, + ) -> Result, Error> { + let jwt_encoder = match algorithm { + Algorithm::HS256 => { + Box::new(hmac::Hs256Verifier::new(key)?) as Box + } + Algorithm::HS384 => { + Box::new(hmac::Hs384Verifier::new(key)?) as Box + } + Algorithm::HS512 => { + Box::new(hmac::Hs512Verifier::new(key)?) as Box + } + Algorithm::ES256 => { + Box::new(ecdsa::Es256Verifier::new(key)?) as Box + } + Algorithm::ES384 => { + Box::new(ecdsa::Es384Verifier::new(key)?) as Box + } + Algorithm::RS256 => { + Box::new(rsa::Rsa256Verifier::new(key)?) as Box + } + Algorithm::RS384 => { + Box::new(rsa::Rsa384Verifier::new(key)?) as Box + } + Algorithm::RS512 => { + Box::new(rsa::Rsa512Verifier::new(key)?) as Box + } + Algorithm::PS256 => { + Box::new(rsa::RsaPss256Verifier::new(key)?) as Box + } + Algorithm::PS384 => { + Box::new(rsa::RsaPss384Verifier::new(key)?) as Box + } + Algorithm::PS512 => { + Box::new(rsa::RsaPss512Verifier::new(key)?) as Box + } + Algorithm::EdDSA => { + Box::new(eddsa::EdDSAVerifier::new(key)?) as Box + } + }; + + Ok(jwt_encoder) + } + }; +} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index adec0d0b..a4ce717c 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,20 +1,31 @@ //! 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 std::sync::Arc; use crate::algorithms::Algorithm; use crate::errors::Result; +use crate::jwk::{EllipticCurve, ThumbprintHash}; use crate::{DecodingKey, EncodingKey}; +#[macro_use] +mod macros; + +/// `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}; @@ -40,7 +51,9 @@ pub trait JwtVerifier: Verifier> { /// /// If you just want to encode a JWT, use `encode` instead. pub fn sign(message: &[u8], key: &EncodingKey, algorithm: Algorithm) -> Result { - let provider = crate::encoding::jwt_signer_factory(&algorithm, key)?; + let provider = (CryptoProvider::get_default_or_install_from_crate_features().signer_factory)( + &algorithm, key, + )?; Ok(b64_encode(provider.sign(message))) } @@ -58,6 +71,130 @@ pub fn verify( key: &DecodingKey, algorithm: Algorithm, ) -> Result { - let provider = crate::decoding::jwt_verifier_factory(&algorithm, key)?; + let provider = (CryptoProvider::get_default_or_install_from_crate_features().verifier_factory)( + &algorithm, key, + )?; Ok(provider.verify(message, &b64_decode(signature)?).is_ok()) } + +/// Controls the cryptography used be jsonwebtoken. +/// +/// You can either install one of the built-in options: +/// - [`crypto::aws_lc::DEFAULT_PROVIDER`]: (behind the `aws-lc` 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` +/// (see the `custom_provider` example). +// 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>, + /// A function that produces a [`JwtVerifier`] for a given [`Algorithm`] + pub verifier_factory: fn(&Algorithm, &DecodingKey) -> Result>, + /// 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(self) -> std::result::Result<(), Arc> { + static_default::install_default(self) + } + + /// Get the default `CryptoProvider` for this process. + /// + /// This will be `None` if no default has been set yet. + pub fn get_default() -> Option<&'static Arc> { + static_default::get_default() + } + + /// Get the default if it has been set yet, or determine one from the crate features if possible. + pub(crate) fn get_default_or_install_from_crate_features() -> &'static Arc { + if let Some(provider) = Self::get_default() { + return provider; + } + + let provider = Self::from_crate_features() + .expect(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. + "###); + let _ = provider.install_default(); + Self::get_default().unwrap() + } + + /// Determine a `CryptoProvider` based on crate features. + pub fn from_crate_features() -> Option { + #[cfg(all( + feature = "rust_crypto", + not(feature = "aws_lc_rs"), + not(feature = "custom-provider") + ))] + { + return Some(rust_crypto::DEFAULT_PROVIDER); + } + + #[cfg(all( + feature = "aws_lc_rs", + not(feature = "rust_crypto"), + not(feature = "custom-provider") + ))] + { + return Some(aws_lc::DEFAULT_PROVIDER); + } + + #[allow(unreachable_code)] + None + } +} + +/// Holds utility functions needed for JWK processing. +/// The `Default` implementation initializes all functions to `unimplemented!()`. +#[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, Vec)>, + /// 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, Vec)>, + /// Given some data and a name of a hash function, compute hash_function(data) + pub compute_digest: fn(&[u8], ThumbprintHash) -> Vec, +} + +impl Default for JwkUtils { + fn default() -> Self { + Self { + extract_rsa_public_key_components: |_| unimplemented!(), + extract_ec_public_key_coordinates: |_, _| unimplemented!(), + compute_digest: |_, _| unimplemented!(), + } + } +} + +mod static_default { + use std::sync::{Arc, OnceLock}; + + use super::CryptoProvider; + + static PROCESS_DEFAULT_PROVIDER: OnceLock> = OnceLock::new(); + + pub(crate) fn install_default( + default_provider: CryptoProvider, + ) -> Result<(), Arc> { + PROCESS_DEFAULT_PROVIDER.set(Arc::new(default_provider)) + } + + pub(crate) fn get_default() -> Option<&'static Arc> { + PROCESS_DEFAULT_PROVIDER.get() + } +} diff --git a/src/crypto/rust_crypto/mod.rs b/src/crypto/rust_crypto/mod.rs index 16f66b5f..a2b38f63 100644 --- a/src/crypto/rust_crypto/mod.rs +++ b/src/crypto/rust_crypto/mod.rs @@ -1,4 +1,70 @@ -pub(crate) mod ecdsa; -pub(crate) mod eddsa; -pub(crate) mod hmac; -pub(crate) mod rsa; +use ::rsa::{RsaPrivateKey, pkcs1::DecodeRsaPrivateKey, traits::PublicKeyParts}; +use p256::{ecdsa::SigningKey as P256SigningKey, pkcs8::DecodePrivateKey}; +use p384::ecdsa::SigningKey as P384SigningKey; +use sha2::{Digest, Sha256, Sha384, Sha512}; + +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, Vec)> { + let private_key = RsaPrivateKey::from_pkcs1_der(key_content) + .map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?; + let public_key = private_key.to_public_key(); + Ok((public_key.n().to_bytes_be(), public_key.e().to_bytes_be())) +} + +/// 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, Vec)> { + match alg { + Algorithm::ES256 => { + let signing_key = P256SigningKey::from_pkcs8_der(key_content) + .map_err(|_| ErrorKind::InvalidEcdsaKey)?; + let public_key = signing_key.verifying_key(); + let encoded = public_key.to_encoded_point(false); + match encoded.coordinates() { + p256::elliptic_curve::sec1::Coordinates::Uncompressed { x, y } => { + Ok((EllipticCurve::P256, x.to_vec(), y.to_vec())) + } + _ => Err(ErrorKind::InvalidEcdsaKey.into()), + } + } + Algorithm::ES384 => { + let signing_key = P384SigningKey::from_pkcs8_der(key_content) + .map_err(|_| ErrorKind::InvalidEcdsaKey)?; + let public_key = signing_key.verifying_key(); + let encoded = public_key.to_encoded_point(false); + match encoded.coordinates() { + p384::elliptic_curve::sec1::Coordinates::Uncompressed { x, y } => { + Ok((EllipticCurve::P384, x.to_vec(), y.to_vec())) + } + _ => Err(ErrorKind::InvalidEcdsaKey.into()), + } + } + _ => Err(ErrorKind::InvalidEcdsaKey.into()), + } +} + +/// Given some data and a name of a hash function, compute hash_function(data) +pub fn compute_digest(data: &[u8], hash_function: ThumbprintHash) -> Vec { + match hash_function { + ThumbprintHash::SHA256 => Sha256::digest(data).to_vec(), + ThumbprintHash::SHA384 => Sha384::digest(data).to_vec(), + ThumbprintHash::SHA512 => Sha512::digest(data).to_vec(), + } +} + +define_default_provider!("rust_crypto", "https://github.com/RustCrypto"); diff --git a/src/decoding.rs b/src/decoding.rs index 51d793e7..1b32a86f 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -3,9 +3,8 @@ use std::fmt::{Debug, Formatter}; use base64::{Engine, engine::general_purpose::STANDARD}; use serde::de::DeserializeOwned; -use crate::Algorithm; use crate::algorithms::AlgorithmFamily; -use crate::crypto::JwtVerifier; +use crate::crypto::{CryptoProvider, JwtVerifier}; use crate::errors::{ErrorKind, Result, new_error}; use crate::header::Header; use crate::jwk::{AlgorithmParameters, Jwk}; @@ -13,27 +12,6 @@ use crate::jwk::{AlgorithmParameters, Jwk}; use crate::pem::decoder::PemEncodedKey; use crate::serialization::{DecodedJwtPartClaims, b64_decode}; use crate::validation::{Validation, validate}; -// Crypto -#[cfg(feature = "aws_lc_rs")] -use crate::crypto::aws_lc::{ - ecdsa::{Es256Verifier, Es384Verifier}, - eddsa::EdDSAVerifier, - hmac::{Hs256Verifier, Hs384Verifier, Hs512Verifier}, - rsa::{ - Rsa256Verifier, Rsa384Verifier, Rsa512Verifier, RsaPss256Verifier, RsaPss384Verifier, - RsaPss512Verifier, - }, -}; -#[cfg(feature = "rust_crypto")] -use crate::crypto::rust_crypto::{ - ecdsa::{Es256Verifier, Es384Verifier}, - eddsa::EdDSAVerifier, - hmac::{Hs256Verifier, Hs384Verifier, Hs512Verifier}, - rsa::{ - Rsa256Verifier, Rsa384Verifier, Rsa512Verifier, RsaPss256Verifier, RsaPss384Verifier, - RsaPss512Verifier, - }, -}; /// The return type of a successful call to [decode](fn.decode.html). #[derive(Debug)] @@ -66,11 +44,19 @@ macro_rules! expect_two { } #[derive(Clone)] +#[cfg(not(feature = "custom-provider"))] pub(crate) enum DecodingKeyKind { SecretOrDer(Vec), RsaModulusExponent { n: Vec, e: Vec }, } +#[derive(Clone)] +#[cfg(feature = "custom-provider")] +pub enum DecodingKeyKind { + SecretOrDer(Vec), + RsaModulusExponent { n: Vec, e: Vec }, +} + impl Debug for DecodingKeyKind { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { @@ -88,8 +74,16 @@ impl Debug for DecodingKeyKind { /// This key can be re-used so make sure you only initialize it once if you can for better performance. #[derive(Clone, Debug)] pub struct DecodingKey { + #[cfg(not(feature = "custom-provider"))] pub(crate) family: AlgorithmFamily, + #[cfg(feature = "custom-provider")] + #[cfg_attr(feature = "custom-provider", allow(missing_docs))] + pub family: AlgorithmFamily, + #[cfg(not(feature = "custom-provider"))] pub(crate) kind: DecodingKeyKind, + #[cfg(feature = "custom-provider")] + #[cfg_attr(feature = "custom-provider", allow(missing_docs))] + pub kind: DecodingKeyKind, } impl DecodingKey { @@ -235,14 +229,36 @@ impl DecodingKey { } } + #[cfg(not(feature = "custom-provider"))] pub(crate) fn as_bytes(&self) -> &[u8] { + self.as_bytes_impl() + } + + /// Get the value of the key. + #[cfg(feature = "custom-provider")] + pub fn as_bytes(&self) -> &[u8] { + self.as_bytes_impl() + } + + fn as_bytes_impl(&self) -> &[u8] { match &self.kind { DecodingKeyKind::SecretOrDer(b) => b, DecodingKeyKind::RsaModulusExponent { .. } => unreachable!(), } } + #[cfg(not(feature = "custom-provider"))] pub(crate) fn try_get_hmac_secret(&self) -> Result<&[u8]> { + self.try_get_hmac_secret_impl() + } + + /// Try to get the HMAC secret from a key. + #[cfg(feature = "custom-provider")] + pub fn try_get_hmac_secret(&self) -> Result<&[u8]> { + self.try_get_hmac_secret_impl() + } + + fn try_get_hmac_secret_impl(&self) -> Result<&[u8]> { if self.family == AlgorithmFamily::Hmac { Ok(self.as_bytes()) } else { @@ -289,7 +305,8 @@ pub fn decode( return Err(new_error(ErrorKind::InvalidAlgorithm)); } - let verifying_provider = jwt_verifier_factory(&header.alg, key)?; + let verifying_provider = (CryptoProvider::get_default_or_install_from_crate_features() + .verifier_factory)(&header.alg, key)?; let (header, claims) = verify_signature(token, validation, verifying_provider)?; @@ -315,29 +332,6 @@ pub fn insecure_decode(token: impl AsRef<[u8]>) -> Result Result> { - let jwt_encoder = match algorithm { - Algorithm::HS256 => Box::new(Hs256Verifier::new(key)?) as Box, - Algorithm::HS384 => Box::new(Hs384Verifier::new(key)?) as Box, - Algorithm::HS512 => Box::new(Hs512Verifier::new(key)?) as Box, - Algorithm::ES256 => Box::new(Es256Verifier::new(key)?) as Box, - Algorithm::ES384 => Box::new(Es384Verifier::new(key)?) as Box, - Algorithm::RS256 => Box::new(Rsa256Verifier::new(key)?) as Box, - Algorithm::RS384 => Box::new(Rsa384Verifier::new(key)?) as Box, - Algorithm::RS512 => Box::new(Rsa512Verifier::new(key)?) as Box, - Algorithm::PS256 => Box::new(RsaPss256Verifier::new(key)?) as Box, - Algorithm::PS384 => Box::new(RsaPss384Verifier::new(key)?) as Box, - Algorithm::PS512 => Box::new(RsaPss512Verifier::new(key)?) as Box, - Algorithm::EdDSA => Box::new(EdDSAVerifier::new(key)?) as Box, - }; - - Ok(jwt_encoder) -} - /// Decode a JWT without any signature verification/validations and return its [Header](struct.Header.html). /// /// If the token has an invalid format (ie 3 parts separated by a `.`), it will return an error. diff --git a/src/encoding.rs b/src/encoding.rs index 30a31953..9bcf1eb8 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -6,39 +6,24 @@ use base64::{ }; use serde::ser::Serialize; -use crate::Algorithm; use crate::algorithms::AlgorithmFamily; -use crate::crypto::JwtSigner; +use crate::crypto::CryptoProvider; use crate::errors::{ErrorKind, Result, new_error}; use crate::header::Header; #[cfg(feature = "use_pem")] use crate::pem::decoder::PemEncodedKey; use crate::serialization::{b64_encode, b64_encode_part}; -// Crypto -#[cfg(feature = "aws_lc_rs")] -use crate::crypto::aws_lc::{ - ecdsa::{Es256Signer, Es384Signer}, - eddsa::EdDSASigner, - hmac::{Hs256Signer, Hs384Signer, Hs512Signer}, - rsa::{ - Rsa256Signer, Rsa384Signer, Rsa512Signer, RsaPss256Signer, RsaPss384Signer, RsaPss512Signer, - }, -}; -#[cfg(feature = "rust_crypto")] -use crate::crypto::rust_crypto::{ - ecdsa::{Es256Signer, Es384Signer}, - eddsa::EdDSASigner, - hmac::{Hs256Signer, Hs384Signer, Hs512Signer}, - rsa::{ - Rsa256Signer, Rsa384Signer, Rsa512Signer, RsaPss256Signer, RsaPss384Signer, RsaPss512Signer, - }, -}; /// A key to encode a JWT with. Can be a secret, a PEM-encoded key or a DER-encoded key. /// This key can be re-used so make sure you only initialize it once if you can for better performance. #[derive(Clone)] + pub struct EncodingKey { + #[cfg(not(feature = "custom-provider"))] pub(crate) family: AlgorithmFamily, + #[cfg(feature = "custom-provider")] + #[cfg_attr(feature = "custom-provider", allow(missing_docs))] + pub family: AlgorithmFamily, pub(crate) content: Vec, } @@ -127,11 +112,33 @@ impl EncodingKey { EncodingKey { family: AlgorithmFamily::Ed, content: der.to_vec() } } + #[cfg(not(feature = "custom-provider"))] pub(crate) fn inner(&self) -> &[u8] { + self.inner_impl() + } + + /// Get the value of the key. + #[cfg(feature = "custom-provider")] + pub fn inner(&self) -> &[u8] { + self.inner_impl() + } + + fn inner_impl(&self) -> &[u8] { &self.content } + #[cfg(not(feature = "custom-provider"))] pub(crate) fn try_get_hmac_secret(&self) -> Result<&[u8]> { + self.try_get_hmac_secret_impl() + } + + /// Try to get the HMAC secret from a key. + #[cfg(feature = "custom-provider")] + pub fn try_get_hmac_secret(&self) -> Result<&[u8]> { + self.try_get_hmac_secret_impl() + } + + fn try_get_hmac_secret_impl(&self) -> Result<&[u8]> { if self.family == AlgorithmFamily::Hmac { Ok(self.inner()) } else { @@ -176,7 +183,8 @@ pub fn encode(header: &Header, claims: &T, key: &EncodingKey) -> R return Err(new_error(ErrorKind::InvalidAlgorithm)); } - let signing_provider = jwt_signer_factory(&header.alg, key)?; + let signing_provider = (CryptoProvider::get_default_or_install_from_crate_features() + .signer_factory)(&header.alg, key)?; if signing_provider.algorithm() != header.alg { return Err(new_error(ErrorKind::InvalidAlgorithm)); @@ -190,26 +198,3 @@ pub fn encode(header: &Header, claims: &T, key: &EncodingKey) -> R Ok([message, signature].join(".")) } - -/// Return the correct [`JwtSigner`] based on the `algorithm`. -pub(crate) fn jwt_signer_factory( - algorithm: &Algorithm, - key: &EncodingKey, -) -> Result> { - let jwt_signer = match algorithm { - Algorithm::HS256 => Box::new(Hs256Signer::new(key)?) as Box, - Algorithm::HS384 => Box::new(Hs384Signer::new(key)?) as Box, - Algorithm::HS512 => Box::new(Hs512Signer::new(key)?) as Box, - Algorithm::ES256 => Box::new(Es256Signer::new(key)?) as Box, - Algorithm::ES384 => Box::new(Es384Signer::new(key)?) as Box, - Algorithm::RS256 => Box::new(Rsa256Signer::new(key)?) as Box, - Algorithm::RS384 => Box::new(Rsa384Signer::new(key)?) as Box, - Algorithm::RS512 => Box::new(Rsa512Signer::new(key)?) as Box, - Algorithm::PS256 => Box::new(RsaPss256Signer::new(key)?) as Box, - Algorithm::PS384 => Box::new(RsaPss384Signer::new(key)?) as Box, - Algorithm::PS512 => Box::new(RsaPss512Signer::new(key)?) as Box, - Algorithm::EdDSA => Box::new(EdDSASigner::new(key)?) as Box, - }; - - Ok(jwt_signer) -} diff --git a/src/jwk.rs b/src/jwk.rs index 31f944d2..410274fa 100644 --- a/src/jwk.rs +++ b/src/jwk.rs @@ -8,25 +8,13 @@ use std::{fmt, str::FromStr}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; +use crate::crypto::CryptoProvider; use crate::serialization::b64_encode; use crate::{ Algorithm, EncodingKey, errors::{self, Error, ErrorKind}, }; -#[cfg(feature = "aws_lc_rs")] -use aws_lc_rs::{digest, signature as aws_sig}; -#[cfg(feature = "aws_lc_rs")] -use aws_sig::KeyPair; -#[cfg(feature = "rust_crypto")] -use p256::{ecdsa::SigningKey as P256SigningKey, pkcs8::DecodePrivateKey}; -#[cfg(feature = "rust_crypto")] -use p384::ecdsa::SigningKey as P384SigningKey; -#[cfg(feature = "rust_crypto")] -use rsa::{RsaPrivateKey, pkcs1::DecodeRsaPrivateKey, traits::PublicKeyParts}; -#[cfg(feature = "rust_crypto")] -use sha2::{Digest, Sha256, Sha384, Sha512}; - /// The intended usage of the public `KeyType`. This enum is serialized `untagged` #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum PublicKeyUse { @@ -439,103 +427,6 @@ pub struct Jwk { pub algorithm: AlgorithmParameters, } -#[cfg(feature = "aws_lc_rs")] -fn extract_rsa_public_key_components(key_content: &[u8]) -> errors::Result<(Vec, Vec)> { - 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::>::from(public); - Ok((components.n, components.e)) -} - -#[cfg(feature = "rust_crypto")] -fn extract_rsa_public_key_components(key_content: &[u8]) -> errors::Result<(Vec, Vec)> { - let private_key = RsaPrivateKey::from_pkcs1_der(key_content) - .map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?; - let public_key = private_key.to_public_key(); - Ok((public_key.n().to_bytes_be(), public_key.e().to_bytes_be())) -} - -#[cfg(feature = "aws_lc_rs")] -fn extract_ec_public_key_coordinates( - key_content: &[u8], - alg: Algorithm, -) -> errors::Result<(EllipticCurve, Vec, Vec)> { - 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())) -} - -#[cfg(feature = "rust_crypto")] -fn extract_ec_public_key_coordinates( - key_content: &[u8], - alg: Algorithm, -) -> errors::Result<(EllipticCurve, Vec, Vec)> { - match alg { - Algorithm::ES256 => { - let signing_key = P256SigningKey::from_pkcs8_der(key_content) - .map_err(|_| ErrorKind::InvalidEcdsaKey)?; - let public_key = signing_key.verifying_key(); - let encoded = public_key.to_encoded_point(false); - match encoded.coordinates() { - p256::elliptic_curve::sec1::Coordinates::Uncompressed { x, y } => { - Ok((EllipticCurve::P256, x.to_vec(), y.to_vec())) - } - _ => Err(ErrorKind::InvalidEcdsaKey.into()), - } - } - Algorithm::ES384 => { - let signing_key = P384SigningKey::from_pkcs8_der(key_content) - .map_err(|_| ErrorKind::InvalidEcdsaKey)?; - let public_key = signing_key.verifying_key(); - let encoded = public_key.to_encoded_point(false); - match encoded.coordinates() { - p384::elliptic_curve::sec1::Coordinates::Uncompressed { x, y } => { - Ok((EllipticCurve::P384, x.to_vec(), y.to_vec())) - } - _ => Err(ErrorKind::InvalidEcdsaKey.into()), - } - } - _ => Err(ErrorKind::InvalidEcdsaKey.into()), - } -} - -#[cfg(feature = "aws_lc_rs")] -fn compute_digest(data: &[u8], hash_function: ThumbprintHash) -> Vec { - 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() -} - -#[cfg(feature = "rust_crypto")] -fn compute_digest(data: &[u8], hash_function: ThumbprintHash) -> Vec { - match hash_function { - ThumbprintHash::SHA256 => Sha256::digest(data).to_vec(), - ThumbprintHash::SHA384 => Sha384::digest(data).to_vec(), - ThumbprintHash::SHA512 => Sha512::digest(data).to_vec(), - } -} - impl Jwk { /// Find whether the Algorithm is implemented and supported pub fn is_supported(&self) -> bool { @@ -571,7 +462,11 @@ impl Jwk { }) } crate::algorithms::AlgorithmFamily::Rsa => { - let (n, e) = extract_rsa_public_key_components(&key.content)?; + let (n, e) = (CryptoProvider::get_default_or_install_from_crate_features() + .jwk_utils + .extract_rsa_public_key_components)( + &key.content + )?; AlgorithmParameters::RSA(RSAKeyParameters { key_type: RSAKeyType::RSA, n: b64_encode(n), @@ -579,7 +474,12 @@ impl Jwk { }) } crate::algorithms::AlgorithmFamily::Ec => { - let (curve, x, y) = extract_ec_public_key_coordinates(&key.content, alg)?; + let (curve, x, y) = + (CryptoProvider::get_default_or_install_from_crate_features() + .jwk_utils + .extract_ec_public_key_coordinates)( + &key.content, alg + )?; AlgorithmParameters::EllipticCurve(EllipticCurveKeyParameters { key_type: EllipticCurveKeyType::EC, curve, @@ -640,7 +540,10 @@ impl Jwk { } }, }; - b64_encode(compute_digest(pre.as_bytes(), hash_function)) + + b64_encode((CryptoProvider::get_default_or_install_from_crate_features() + .jwk_utils + .compute_digest)(pre.as_bytes(), hash_function)) } } diff --git a/src/jws.rs b/src/jws.rs index 57dc02a2..b7cae4b8 100644 --- a/src/jws.rs +++ b/src/jws.rs @@ -1,13 +1,13 @@ //! JSON Web Signatures data type. use std::marker::PhantomData; -use crate::crypto::sign; +use crate::crypto::{CryptoProvider, sign}; use crate::errors::{ErrorKind, Result, new_error}; use crate::serialization::{DecodedJwtPartClaims, b64_encode_part}; use crate::validation::validate; use crate::{DecodingKey, EncodingKey, Header, TokenData, Validation}; -use crate::decoding::{jwt_verifier_factory, verify_signature_body}; +use crate::decoding::verify_signature_body; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; @@ -67,7 +67,8 @@ pub fn decode( let header = Header::from_encoded(&jws.protected)?; let message = [jws.protected.as_str(), jws.payload.as_str()].join("."); - let verifying_provider = jwt_verifier_factory(&header.alg, key)?; + let verifying_provider = (CryptoProvider::get_default_or_install_from_crate_features() + .verifier_factory)(&header.alg, key)?; verify_signature_body( message.as_bytes(), jws.signature.as_bytes(), diff --git a/src/lib.rs b/src/lib.rs index 920b996b..8bbe635f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,20 +5,19 @@ #![deny(missing_docs)] -#[cfg(all(feature = "rust_crypto", feature = "aws_lc_rs"))] -compile_error!( - "feature \"rust_crypto\" and feature \"aws_lc_rs\" cannot be enabled at the same time" -); - -#[cfg(not(any(feature = "rust_crypto", feature = "aws_lc_rs")))] -compile_error!("at least one of the features \"rust_crypto\" or \"aws_lc_rs\" must be enabled"); - pub use algorithms::Algorithm; pub use decoding::{DecodingKey, TokenData, decode, decode_header}; pub use encoding::{EncodingKey, encode}; pub use header::Header; pub use validation::{Validation, get_current_timestamp}; +/// Things needed to implement a custom crypto provider. +#[cfg(feature = "custom-provider")] +pub mod custom_provider { + pub use crate::algorithms::AlgorithmFamily; + pub use signature::{Error, Signer, Verifier}; +} + /// Dangerous decoding functions that should be audited and used with extreme care. pub mod dangerous { pub use super::decoding::insecure_decode; From 33448814c467c19b36b89623d09ebc8d45e8a9ce Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:48:43 +0100 Subject: [PATCH 2/4] remove custom-provider feature, make jwk utility functions private --- Cargo.toml | 3 +- README.md | 2 +- examples/custom_provider.rs | 89 ++++++++++++++++++++++----------- src/crypto/aws_lc/ecdsa.rs | 4 +- src/crypto/aws_lc/eddsa.rs | 4 +- src/crypto/aws_lc/mod.rs | 10 ++-- src/crypto/aws_lc/rsa.rs | 6 +-- src/crypto/mod.rs | 12 +---- src/crypto/rust_crypto/ecdsa.rs | 4 +- src/crypto/rust_crypto/eddsa.rs | 4 +- src/crypto/rust_crypto/mod.rs | 10 ++-- src/crypto/rust_crypto/rsa.rs | 6 +-- src/decoding.rs | 45 +++-------------- src/encoding.rs | 28 +---------- src/jwk.rs | 8 +-- src/jws.rs | 2 +- src/lib.rs | 10 +--- 17 files changed, 101 insertions(+), 146 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5353ed28..61862f0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" @@ -68,7 +70,6 @@ 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" diff --git a/README.md b/README.md index 9fd74f41..ec1b5a06 100644 --- a/README.md +++ b/README.md @@ -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`, at most one of which must be enabled. If you wish to use a custom crypto provider, enable the `custom-provider` feature instead. +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). diff --git a/examples/custom_provider.rs b/examples/custom_provider.rs index 713caa08..52d37b88 100644 --- a/examples/custom_provider.rs +++ b/examples/custom_provider.rs @@ -1,15 +1,15 @@ use jsonwebtoken::{ - Algorithm, DecodingKey, EncodingKey, Header, Validation, + Algorithm, AlgorithmFamily, DecodingKey, EncodingKey, Error as SigError, Header, Signer, + Validation, Verifier, crypto::{CryptoProvider, JwkUtils, JwtSigner, JwtVerifier}, decode, encode, - errors::Error, + errors::{Error, ErrorKind}, }; use serde::{Deserialize, Serialize}; -use signature::{Signer, Verifier}; fn new_signer(algorithm: &Algorithm, key: &EncodingKey) -> Result, Error> { let jwt_signer = match algorithm { - Algorithm::EdDSA => Box::new(CustomEdDSASigner::new(key)?) as Box, + Algorithm::EdDSA => Box::new(EdDSASigner::new(key)?) as Box, _ => unimplemented!(), }; @@ -18,49 +18,71 @@ fn new_signer(algorithm: &Algorithm, key: &EncodingKey) -> Result Result, Error> { let jwt_verifier = match algorithm { - Algorithm::EdDSA => Box::new(CustomEdDSAVerifier::new(key)?) as Box, + Algorithm::EdDSA => Box::new(EdDSAVerifier::new(key)?) as Box, _ => unimplemented!(), }; Ok(jwt_verifier) } -pub struct CustomEdDSASigner; +struct EdDSASigner(botan::Privkey); -impl CustomEdDSASigner { - fn new(_: &EncodingKey) -> Result { - Ok(CustomEdDSASigner) +impl EdDSASigner { + fn new(encoding_key: &EncodingKey) -> Result { + if encoding_key.family() != AlgorithmFamily::Ed { + return Err(ErrorKind::InvalidKeyFormat.into()); + } + + Ok(Self( + botan::Privkey::load_der(encoding_key.inner()) + .map_err(|_| ErrorKind::InvalidEddsaKey)?, + )) } } -// WARNING: This is obviously not secure at all and should NEVER be done in practice! -impl Signer> for CustomEdDSASigner { - fn try_sign(&self, _: &[u8]) -> Result, signature::Error> { - Ok(vec![0; 16]) +impl Signer> for EdDSASigner { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, 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 CustomEdDSASigner { +impl JwtSigner for EdDSASigner { fn algorithm(&self) -> Algorithm { Algorithm::EdDSA } } -pub struct CustomEdDSAVerifier; +struct EdDSAVerifier(botan::Pubkey); + +impl EdDSAVerifier { + fn new(decoding_key: &DecodingKey) -> Result { + if decoding_key.family() != AlgorithmFamily::Ed { + return Err(ErrorKind::InvalidKeyFormat.into()); + } -impl CustomEdDSAVerifier { - fn new(_: &DecodingKey) -> Result { - Ok(CustomEdDSAVerifier) + Ok(Self( + botan::Pubkey::load_ed25519(decoding_key.as_bytes()) + .map_err(|_| ErrorKind::InvalidEddsaKey)?, + )) } } -impl Verifier> for CustomEdDSAVerifier { - fn verify(&self, _: &[u8], signature: &Vec) -> Result<(), signature::Error> { - if signature == &vec![0; 16] { Ok(()) } else { Err(signature::Error::new()) } +impl Verifier> for EdDSAVerifier { + fn verify(&self, msg: &[u8], signature: &Vec) -> 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 CustomEdDSAVerifier { +impl JwtVerifier for EdDSAVerifier { fn algorithm(&self) -> Algorithm { Algorithm::EdDSA } @@ -82,21 +104,30 @@ fn main() { }; my_crypto_provider.install_default().unwrap(); - // for an actual EdDSA implementation, this would be some private key - let key = b"secret"; + // 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_der(key)) { - Ok(t) => t, - Err(_) => panic!(), // in practice you would return an error - }; + 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::( token, - &DecodingKey::from_ed_der(key), + &DecodingKey::from_ed_pem(pubkey.as_bytes()).unwrap(), &Validation::new(Algorithm::EdDSA), ) { Ok(c) => c.claims, diff --git a/src/crypto/aws_lc/ecdsa.rs b/src/crypto/aws_lc/ecdsa.rs index c278b934..5a5b364e 100644 --- a/src/crypto/aws_lc/ecdsa.rs +++ b/src/crypto/aws_lc/ecdsa.rs @@ -18,7 +18,7 @@ macro_rules! define_ecdsa_signer { impl $name { pub(crate) fn new(encoding_key: &EncodingKey) -> Result { - if encoding_key.family != AlgorithmFamily::Ec { + if encoding_key.family() != AlgorithmFamily::Ec { return Err(new_error(ErrorKind::InvalidKeyFormat)); } @@ -51,7 +51,7 @@ macro_rules! define_ecdsa_verifier { impl $name { pub(crate) fn new(decoding_key: &DecodingKey) -> Result { - if decoding_key.family != AlgorithmFamily::Ec { + if decoding_key.family() != AlgorithmFamily::Ec { return Err(new_error(ErrorKind::InvalidKeyFormat)); } diff --git a/src/crypto/aws_lc/eddsa.rs b/src/crypto/aws_lc/eddsa.rs index d6e60a50..085bf7c3 100644 --- a/src/crypto/aws_lc/eddsa.rs +++ b/src/crypto/aws_lc/eddsa.rs @@ -11,7 +11,7 @@ pub struct EdDSASigner(Ed25519KeyPair); impl EdDSASigner { pub(crate) fn new(encoding_key: &EncodingKey) -> Result { - if encoding_key.family != AlgorithmFamily::Ed { + if encoding_key.family() != AlgorithmFamily::Ed { return Err(new_error(ErrorKind::InvalidKeyFormat)); } @@ -38,7 +38,7 @@ pub struct EdDSAVerifier(DecodingKey); impl EdDSAVerifier { pub(crate) fn new(decoding_key: &DecodingKey) -> Result { - if decoding_key.family != AlgorithmFamily::Ed { + if decoding_key.family() != AlgorithmFamily::Ed { return Err(new_error(ErrorKind::InvalidKeyFormat)); } diff --git a/src/crypto/aws_lc/mod.rs b/src/crypto/aws_lc/mod.rs index 687efaea..d3861952 100644 --- a/src/crypto/aws_lc/mod.rs +++ b/src/crypto/aws_lc/mod.rs @@ -15,8 +15,7 @@ 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, Vec)> { +fn extract_rsa_public_key_components(key_content: &[u8]) -> errors::Result<(Vec, Vec)> { let key_pair = aws_sig::RsaKeyPair::from_der(key_content) .map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?; let public = key_pair.public_key(); @@ -24,9 +23,7 @@ pub fn extract_rsa_public_key_components(key_content: &[u8]) -> errors::Result<( 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( +fn extract_ec_public_key_coordinates( key_content: &[u8], alg: Algorithm, ) -> errors::Result<(EllipticCurve, Vec, Vec)> { @@ -52,8 +49,7 @@ pub fn extract_ec_public_key_coordinates( 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 { +fn compute_digest(data: &[u8], hash_function: ThumbprintHash) -> Vec { let algorithm = match hash_function { ThumbprintHash::SHA256 => &digest::SHA256, ThumbprintHash::SHA384 => &digest::SHA384, diff --git a/src/crypto/aws_lc/rsa.rs b/src/crypto/aws_lc/rsa.rs index 7e74eebf..d72b0680 100644 --- a/src/crypto/aws_lc/rsa.rs +++ b/src/crypto/aws_lc/rsa.rs @@ -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)?; @@ -57,7 +57,7 @@ macro_rules! define_rsa_signer { impl $name { pub(crate) fn new(encoding_key: &EncodingKey) -> Result { - if encoding_key.family != AlgorithmFamily::Rsa { + if encoding_key.family() != AlgorithmFamily::Rsa { return Err(new_error(ErrorKind::InvalidKeyFormat)); } @@ -85,7 +85,7 @@ macro_rules! define_rsa_verifier { impl $name { pub(crate) fn new(decoding_key: &DecodingKey) -> Result { - if decoding_key.family != AlgorithmFamily::Rsa { + if decoding_key.family() != AlgorithmFamily::Rsa { return Err(new_error(ErrorKind::InvalidKeyFormat)); } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index a4ce717c..14bdad22 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -132,20 +132,12 @@ See the documentation of the CryptoProvider type for more information. /// Determine a `CryptoProvider` based on crate features. pub fn from_crate_features() -> Option { - #[cfg(all( - feature = "rust_crypto", - not(feature = "aws_lc_rs"), - not(feature = "custom-provider") - ))] + #[cfg(all(feature = "rust_crypto", not(feature = "aws_lc_rs")))] { return Some(rust_crypto::DEFAULT_PROVIDER); } - #[cfg(all( - feature = "aws_lc_rs", - not(feature = "rust_crypto"), - not(feature = "custom-provider") - ))] + #[cfg(all(feature = "aws_lc_rs", not(feature = "rust_crypto")))] { return Some(aws_lc::DEFAULT_PROVIDER); } diff --git a/src/crypto/rust_crypto/ecdsa.rs b/src/crypto/rust_crypto/ecdsa.rs index f192adea..9aad882e 100644 --- a/src/crypto/rust_crypto/ecdsa.rs +++ b/src/crypto/rust_crypto/ecdsa.rs @@ -20,7 +20,7 @@ macro_rules! define_ecdsa_signer { impl $name { pub(crate) fn new(encoding_key: &EncodingKey) -> Result { - if encoding_key.family != AlgorithmFamily::Ec { + if encoding_key.family() != AlgorithmFamily::Ec { return Err(new_error(ErrorKind::InvalidKeyFormat)); } @@ -52,7 +52,7 @@ macro_rules! define_ecdsa_verifier { impl $name { pub(crate) fn new(decoding_key: &DecodingKey) -> Result { - if decoding_key.family != AlgorithmFamily::Ec { + if decoding_key.family() != AlgorithmFamily::Ec { return Err(new_error(ErrorKind::InvalidKeyFormat)); } diff --git a/src/crypto/rust_crypto/eddsa.rs b/src/crypto/rust_crypto/eddsa.rs index dc0fcecd..9b77a9f8 100644 --- a/src/crypto/rust_crypto/eddsa.rs +++ b/src/crypto/rust_crypto/eddsa.rs @@ -12,7 +12,7 @@ pub struct EdDSASigner(SigningKey); impl EdDSASigner { pub(crate) fn new(encoding_key: &EncodingKey) -> Result { - if encoding_key.family != AlgorithmFamily::Ed { + if encoding_key.family() != AlgorithmFamily::Ed { return Err(new_error(ErrorKind::InvalidKeyFormat)); } @@ -39,7 +39,7 @@ pub struct EdDSAVerifier(VerifyingKey); impl EdDSAVerifier { pub(crate) fn new(decoding_key: &DecodingKey) -> Result { - if decoding_key.family != AlgorithmFamily::Ed { + if decoding_key.family() != AlgorithmFamily::Ed { return Err(new_error(ErrorKind::InvalidKeyFormat)); } diff --git a/src/crypto/rust_crypto/mod.rs b/src/crypto/rust_crypto/mod.rs index a2b38f63..20d14e3b 100644 --- a/src/crypto/rust_crypto/mod.rs +++ b/src/crypto/rust_crypto/mod.rs @@ -15,17 +15,14 @@ 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, Vec)> { +fn extract_rsa_public_key_components(key_content: &[u8]) -> errors::Result<(Vec, Vec)> { let private_key = RsaPrivateKey::from_pkcs1_der(key_content) .map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?; let public_key = private_key.to_public_key(); Ok((public_key.n().to_bytes_be(), public_key.e().to_bytes_be())) } -/// 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( +fn extract_ec_public_key_coordinates( key_content: &[u8], alg: Algorithm, ) -> errors::Result<(EllipticCurve, Vec, Vec)> { @@ -58,8 +55,7 @@ pub fn extract_ec_public_key_coordinates( } } -/// Given some data and a name of a hash function, compute hash_function(data) -pub fn compute_digest(data: &[u8], hash_function: ThumbprintHash) -> Vec { +fn compute_digest(data: &[u8], hash_function: ThumbprintHash) -> Vec { match hash_function { ThumbprintHash::SHA256 => Sha256::digest(data).to_vec(), ThumbprintHash::SHA384 => Sha384::digest(data).to_vec(), diff --git a/src/crypto/rust_crypto/rsa.rs b/src/crypto/rust_crypto/rsa.rs index b73797e8..ba0af0fe 100644 --- a/src/crypto/rust_crypto/rsa.rs +++ b/src/crypto/rust_crypto/rsa.rs @@ -49,7 +49,7 @@ fn verify_rsa( ) -> std::result::Result<(), signature::Error> { let digest = H::digest(msg); - match &decoding_key.kind { + match decoding_key.kind() { DecodingKeyKind::SecretOrDer(bytes) => { RsaPublicKey::from_pkcs1_der(bytes) .map_err(signature::Error::from_source)? @@ -72,7 +72,7 @@ macro_rules! define_rsa_signer { impl $name { pub(crate) fn new(encoding_key: &EncodingKey) -> Result { - if encoding_key.family != AlgorithmFamily::Rsa { + if encoding_key.family() != AlgorithmFamily::Rsa { return Err(new_error(ErrorKind::InvalidKeyFormat)); } @@ -100,7 +100,7 @@ macro_rules! define_rsa_verifier { impl $name { pub(crate) fn new(decoding_key: &DecodingKey) -> Result { - if decoding_key.family != AlgorithmFamily::Rsa { + if decoding_key.family() != AlgorithmFamily::Rsa { return Err(new_error(ErrorKind::InvalidKeyFormat)); } diff --git a/src/decoding.rs b/src/decoding.rs index 1b32a86f..a041592e 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -44,14 +44,6 @@ macro_rules! expect_two { } #[derive(Clone)] -#[cfg(not(feature = "custom-provider"))] -pub(crate) enum DecodingKeyKind { - SecretOrDer(Vec), - RsaModulusExponent { n: Vec, e: Vec }, -} - -#[derive(Clone)] -#[cfg(feature = "custom-provider")] pub enum DecodingKeyKind { SecretOrDer(Vec), RsaModulusExponent { n: Vec, e: Vec }, @@ -74,16 +66,8 @@ impl Debug for DecodingKeyKind { /// This key can be re-used so make sure you only initialize it once if you can for better performance. #[derive(Clone, Debug)] pub struct DecodingKey { - #[cfg(not(feature = "custom-provider"))] - pub(crate) family: AlgorithmFamily, - #[cfg(feature = "custom-provider")] - #[cfg_attr(feature = "custom-provider", allow(missing_docs))] - pub family: AlgorithmFamily, - #[cfg(not(feature = "custom-provider"))] - pub(crate) kind: DecodingKeyKind, - #[cfg(feature = "custom-provider")] - #[cfg_attr(feature = "custom-provider", allow(missing_docs))] - pub kind: DecodingKeyKind, + family: AlgorithmFamily, + kind: DecodingKeyKind, } impl DecodingKey { @@ -92,6 +76,11 @@ impl DecodingKey { self.family } + /// The kind of decoding key + pub fn kind(&self) -> &DecodingKeyKind { + &self.kind + } + /// If you're using HMAC, use this. pub fn from_secret(secret: &[u8]) -> Self { DecodingKey { @@ -229,36 +218,16 @@ impl DecodingKey { } } - #[cfg(not(feature = "custom-provider"))] - pub(crate) fn as_bytes(&self) -> &[u8] { - self.as_bytes_impl() - } - /// Get the value of the key. - #[cfg(feature = "custom-provider")] pub fn as_bytes(&self) -> &[u8] { - self.as_bytes_impl() - } - - fn as_bytes_impl(&self) -> &[u8] { match &self.kind { DecodingKeyKind::SecretOrDer(b) => b, DecodingKeyKind::RsaModulusExponent { .. } => unreachable!(), } } - #[cfg(not(feature = "custom-provider"))] - pub(crate) fn try_get_hmac_secret(&self) -> Result<&[u8]> { - self.try_get_hmac_secret_impl() - } - /// Try to get the HMAC secret from a key. - #[cfg(feature = "custom-provider")] pub fn try_get_hmac_secret(&self) -> Result<&[u8]> { - self.try_get_hmac_secret_impl() - } - - fn try_get_hmac_secret_impl(&self) -> Result<&[u8]> { if self.family == AlgorithmFamily::Hmac { Ok(self.as_bytes()) } else { diff --git a/src/encoding.rs b/src/encoding.rs index 9bcf1eb8..3faec591 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -19,12 +19,8 @@ use crate::serialization::{b64_encode, b64_encode_part}; #[derive(Clone)] pub struct EncodingKey { - #[cfg(not(feature = "custom-provider"))] - pub(crate) family: AlgorithmFamily, - #[cfg(feature = "custom-provider")] - #[cfg_attr(feature = "custom-provider", allow(missing_docs))] - pub family: AlgorithmFamily, - pub(crate) content: Vec, + family: AlgorithmFamily, + content: Vec, } impl EncodingKey { @@ -112,33 +108,13 @@ impl EncodingKey { EncodingKey { family: AlgorithmFamily::Ed, content: der.to_vec() } } - #[cfg(not(feature = "custom-provider"))] - pub(crate) fn inner(&self) -> &[u8] { - self.inner_impl() - } - /// Get the value of the key. - #[cfg(feature = "custom-provider")] pub fn inner(&self) -> &[u8] { - self.inner_impl() - } - - fn inner_impl(&self) -> &[u8] { &self.content } - #[cfg(not(feature = "custom-provider"))] - pub(crate) fn try_get_hmac_secret(&self) -> Result<&[u8]> { - self.try_get_hmac_secret_impl() - } - /// Try to get the HMAC secret from a key. - #[cfg(feature = "custom-provider")] pub fn try_get_hmac_secret(&self) -> Result<&[u8]> { - self.try_get_hmac_secret_impl() - } - - fn try_get_hmac_secret_impl(&self) -> Result<&[u8]> { if self.family == AlgorithmFamily::Hmac { Ok(self.inner()) } else { diff --git a/src/jwk.rs b/src/jwk.rs index 410274fa..655034f1 100644 --- a/src/jwk.rs +++ b/src/jwk.rs @@ -454,18 +454,18 @@ impl Jwk { }), ..Default::default() }, - algorithm: match key.family { + algorithm: match key.family() { crate::algorithms::AlgorithmFamily::Hmac => { AlgorithmParameters::OctetKey(OctetKeyParameters { key_type: OctetKeyType::Octet, - value: b64_encode(&key.content), + value: b64_encode(key.inner()), }) } crate::algorithms::AlgorithmFamily::Rsa => { let (n, e) = (CryptoProvider::get_default_or_install_from_crate_features() .jwk_utils .extract_rsa_public_key_components)( - &key.content + key.inner() )?; AlgorithmParameters::RSA(RSAKeyParameters { key_type: RSAKeyType::RSA, @@ -478,7 +478,7 @@ impl Jwk { (CryptoProvider::get_default_or_install_from_crate_features() .jwk_utils .extract_ec_public_key_coordinates)( - &key.content, alg + key.inner(), alg )?; AlgorithmParameters::EllipticCurve(EllipticCurveKeyParameters { key_type: EllipticCurveKeyType::EC, diff --git a/src/jws.rs b/src/jws.rs index b7cae4b8..1e6e8153 100644 --- a/src/jws.rs +++ b/src/jws.rs @@ -39,7 +39,7 @@ pub fn encode( claims: Option<&T>, key: &EncodingKey, ) -> Result> { - if key.family != header.alg.family() { + if key.family() != header.alg.family() { return Err(new_error(ErrorKind::InvalidAlgorithm)); } let encoded_header = b64_encode_part(header)?; diff --git a/src/lib.rs b/src/lib.rs index 8bbe635f..e0534593 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,19 +5,13 @@ #![deny(missing_docs)] -pub use algorithms::Algorithm; +pub use algorithms::{Algorithm, AlgorithmFamily}; pub use decoding::{DecodingKey, TokenData, decode, decode_header}; pub use encoding::{EncodingKey, encode}; pub use header::Header; +pub use signature::{Error, Signer, Verifier}; pub use validation::{Validation, get_current_timestamp}; -/// Things needed to implement a custom crypto provider. -#[cfg(feature = "custom-provider")] -pub mod custom_provider { - pub use crate::algorithms::AlgorithmFamily; - pub use signature::{Error, Signer, Verifier}; -} - /// Dangerous decoding functions that should be audited and used with extreme care. pub mod dangerous { pub use super::decoding::insecure_decode; From 5940de1e41fd593bf0a54fc42729a08af7e0c84e Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:09:04 +0100 Subject: [PATCH 3/4] Export entire signature crate to prevent confusion for Error type --- examples/custom_provider.rs | 4 ++-- src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/custom_provider.rs b/examples/custom_provider.rs index 52d37b88..1f7e9754 100644 --- a/examples/custom_provider.rs +++ b/examples/custom_provider.rs @@ -1,9 +1,9 @@ use jsonwebtoken::{ - Algorithm, AlgorithmFamily, DecodingKey, EncodingKey, Error as SigError, Header, Signer, - Validation, Verifier, + 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}; diff --git a/src/lib.rs b/src/lib.rs index e0534593..f962ca11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ pub use algorithms::{Algorithm, AlgorithmFamily}; pub use decoding::{DecodingKey, TokenData, decode, decode_header}; pub use encoding::{EncodingKey, encode}; pub use header::Header; -pub use signature::{Error, Signer, Verifier}; +pub use signature; pub use validation::{Validation, get_current_timestamp}; /// Dangerous decoding functions that should be audited and used with extreme care. From a01397e2c193149a04a842ec0aebf1cbcd0901d7 Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Sun, 14 Dec 2025 00:52:14 +0100 Subject: [PATCH 4/4] make more things public --- src/algorithms.rs | 7 ++++++- src/decoding.rs | 12 ++++++++++-- src/errors.rs | 9 +++++++-- src/lib.rs | 2 +- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index a3fb42d1..2d28bebb 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -5,11 +5,15 @@ use serde::{Deserialize, Serialize}; use crate::errors::{Error, ErrorKind, Result}; #[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)] -#[allow(missing_docs)] +/// 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, } @@ -35,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] diff --git a/src/decoding.rs b/src/decoding.rs index a041592e..ef048dca 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -44,9 +44,17 @@ macro_rules! expect_two { } #[derive(Clone)] +/// Different kinds of decoding keys. pub enum DecodingKeyKind { + /// A raw public key. SecretOrDer(Vec), - RsaModulusExponent { n: Vec, e: Vec }, + /// RSA public key components. + RsaModulusExponent { + /// The modulus of the public key. + n: Vec, + /// The exponent of the public key. + e: Vec, + }, } impl Debug for DecodingKeyKind { @@ -76,7 +84,7 @@ impl DecodingKey { self.family } - /// The kind of decoding key + /// The kind of decoding key. pub fn kind(&self) -> &DecodingKeyKind { &self.kind } diff --git a/src/errors.rs b/src/errors.rs index 7ad913f3..3d4963f4 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -3,8 +3,9 @@ use std::fmt; use std::result; use std::sync::Arc; -/// A crate private constructor for `Error`. -pub(crate) fn new_error(kind: ErrorKind) -> Error { +/// A constructor for `Error`. +/// Intended for use in custom crypto providers. +pub fn new_error(kind: ErrorKind) -> Error { Error(Box::new(kind)) } @@ -78,6 +79,8 @@ pub enum ErrorKind { Json(Arc), /// Some of the text was invalid UTF-8 Utf8(::std::string::FromUtf8Error), + /// An error happened in a custom provider + Provider(String), } impl StdError for Error { @@ -102,6 +105,7 @@ impl StdError for Error { ErrorKind::Base64(err) => Some(err), ErrorKind::Json(err) => Some(err.as_ref()), ErrorKind::Utf8(err) => Some(err), + ErrorKind::Provider(_) => None, } } } @@ -128,6 +132,7 @@ impl fmt::Display for Error { ErrorKind::Json(err) => write!(f, "JSON error: {}", err), ErrorKind::Utf8(err) => write!(f, "UTF-8 error: {}", err), ErrorKind::Base64(err) => write!(f, "Base64 error: {}", err), + ErrorKind::Provider(msg) => write!(f, "Custom provider error: {}", msg), } } } diff --git a/src/lib.rs b/src/lib.rs index f962ca11..2c8ff253 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ #![deny(missing_docs)] pub use algorithms::{Algorithm, AlgorithmFamily}; -pub use decoding::{DecodingKey, TokenData, decode, decode_header}; +pub use decoding::{DecodingKey, DecodingKeyKind, TokenData, decode, decode_header}; pub use encoding::{EncodingKey, encode}; pub use header::Header; pub use signature;