diff --git a/Cargo.toml b/Cargo.toml index 441cc736..0d9ed026 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 4bfe424c..0c4dd5ba 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 6d1fc42f..2c02a4df 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)?; @@ -317,29 +334,6 @@ pub fn insecure_decode( Ok(TokenData { header, claims }) } -/// Return the correct [`JwtVerifier`] based on the `algorithm`. -pub fn jwt_verifier_factory( - algorithm: &Algorithm, - key: &DecodingKey, -) -> 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;