-
Notifications
You must be signed in to change notification settings - Fork 326
Expose cryptography backends via CryptoProvider #452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,110 @@ | ||
| pub(crate) mod ecdsa; | ||
| pub(crate) mod eddsa; | ||
| pub(crate) mod hmac; | ||
| pub(crate) mod rsa; | ||
| use aws_lc_rs::{ | ||
| digest, | ||
| signature::{ | ||
| self as aws_sig, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING, | ||
| EcdsaKeyPair, KeyPair, | ||
| }, | ||
| }; | ||
|
|
||
| use crate::{ | ||
| Algorithm, DecodingKey, EncodingKey, | ||
| crypto::{CryptoProvider, JwkUtils, JwtSigner, JwtVerifier}, | ||
| errors::{self, Error, ErrorKind}, | ||
| jwk::{EllipticCurve, ThumbprintHash}, | ||
| }; | ||
|
|
||
| mod ecdsa; | ||
| mod eddsa; | ||
| mod hmac; | ||
| mod rsa; | ||
|
|
||
| fn extract_rsa_public_key_components(key_content: &[u8]) -> errors::Result<(Vec<u8>, Vec<u8>)> { | ||
| let key_pair = aws_sig::RsaKeyPair::from_der(key_content) | ||
| .map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?; | ||
| let public = key_pair.public_key(); | ||
| let components = aws_sig::RsaPublicKeyComponents::<Vec<u8>>::from(public); | ||
| Ok((components.n, components.e)) | ||
| } | ||
|
|
||
| fn extract_ec_public_key_coordinates( | ||
| key_content: &[u8], | ||
| alg: Algorithm, | ||
| ) -> errors::Result<(EllipticCurve, Vec<u8>, Vec<u8>)> { | ||
| let (signing_alg, curve, pub_elem_bytes) = match alg { | ||
| Algorithm::ES256 => (&ECDSA_P256_SHA256_FIXED_SIGNING, EllipticCurve::P256, 32), | ||
| Algorithm::ES384 => (&ECDSA_P384_SHA384_FIXED_SIGNING, EllipticCurve::P384, 48), | ||
| _ => return Err(ErrorKind::InvalidEcdsaKey.into()), | ||
| }; | ||
|
|
||
| let key_pair = EcdsaKeyPair::from_pkcs8(signing_alg, key_content) | ||
| .map_err(|_| ErrorKind::InvalidEcdsaKey)?; | ||
|
|
||
| let pub_bytes = key_pair.public_key().as_ref(); | ||
| if pub_bytes[0] != 4 { | ||
| return Err(ErrorKind::InvalidEcdsaKey.into()); | ||
| } | ||
|
|
||
| let (x, y) = pub_bytes[1..].split_at(pub_elem_bytes); | ||
| Ok((curve, x.to_vec(), y.to_vec())) | ||
| } | ||
|
|
||
| fn compute_digest(data: &[u8], hash_function: ThumbprintHash) -> Vec<u8> { | ||
| let algorithm = match hash_function { | ||
| ThumbprintHash::SHA256 => &digest::SHA256, | ||
| ThumbprintHash::SHA384 => &digest::SHA384, | ||
| ThumbprintHash::SHA512 => &digest::SHA512, | ||
| }; | ||
| digest::digest(algorithm, data).as_ref().to_vec() | ||
| } | ||
|
|
||
| fn new_signer(algorithm: &Algorithm, key: &EncodingKey) -> Result<Box<dyn JwtSigner>, Error> { | ||
| let jwt_signer = match algorithm { | ||
| Algorithm::HS256 => Box::new(hmac::Hs256Signer::new(key)?) as Box<dyn JwtSigner>, | ||
| Algorithm::HS384 => Box::new(hmac::Hs384Signer::new(key)?) as Box<dyn JwtSigner>, | ||
| Algorithm::HS512 => Box::new(hmac::Hs512Signer::new(key)?) as Box<dyn JwtSigner>, | ||
| Algorithm::ES256 => Box::new(ecdsa::Es256Signer::new(key)?) as Box<dyn JwtSigner>, | ||
| Algorithm::ES384 => Box::new(ecdsa::Es384Signer::new(key)?) as Box<dyn JwtSigner>, | ||
| Algorithm::RS256 => Box::new(rsa::Rsa256Signer::new(key)?) as Box<dyn JwtSigner>, | ||
| Algorithm::RS384 => Box::new(rsa::Rsa384Signer::new(key)?) as Box<dyn JwtSigner>, | ||
| Algorithm::RS512 => Box::new(rsa::Rsa512Signer::new(key)?) as Box<dyn JwtSigner>, | ||
| Algorithm::PS256 => Box::new(rsa::RsaPss256Signer::new(key)?) as Box<dyn JwtSigner>, | ||
| Algorithm::PS384 => Box::new(rsa::RsaPss384Signer::new(key)?) as Box<dyn JwtSigner>, | ||
| Algorithm::PS512 => Box::new(rsa::RsaPss512Signer::new(key)?) as Box<dyn JwtSigner>, | ||
| Algorithm::EdDSA => Box::new(eddsa::EdDSASigner::new(key)?) as Box<dyn JwtSigner>, | ||
| }; | ||
|
|
||
| Ok(jwt_signer) | ||
| } | ||
|
|
||
| fn new_verifier( | ||
| algorithm: &Algorithm, | ||
| key: &DecodingKey, | ||
| ) -> Result<Box<dyn super::JwtVerifier>, Error> { | ||
| let jwt_verifier = match algorithm { | ||
| Algorithm::HS256 => Box::new(hmac::Hs256Verifier::new(key)?) as Box<dyn JwtVerifier>, | ||
| Algorithm::HS384 => Box::new(hmac::Hs384Verifier::new(key)?) as Box<dyn JwtVerifier>, | ||
| Algorithm::HS512 => Box::new(hmac::Hs512Verifier::new(key)?) as Box<dyn JwtVerifier>, | ||
| Algorithm::ES256 => Box::new(ecdsa::Es256Verifier::new(key)?) as Box<dyn JwtVerifier>, | ||
| Algorithm::ES384 => Box::new(ecdsa::Es384Verifier::new(key)?) as Box<dyn JwtVerifier>, | ||
| Algorithm::RS256 => Box::new(rsa::Rsa256Verifier::new(key)?) as Box<dyn JwtVerifier>, | ||
| Algorithm::RS384 => Box::new(rsa::Rsa384Verifier::new(key)?) as Box<dyn JwtVerifier>, | ||
| Algorithm::RS512 => Box::new(rsa::Rsa512Verifier::new(key)?) as Box<dyn JwtVerifier>, | ||
| Algorithm::PS256 => Box::new(rsa::RsaPss256Verifier::new(key)?) as Box<dyn JwtVerifier>, | ||
| Algorithm::PS384 => Box::new(rsa::RsaPss384Verifier::new(key)?) as Box<dyn JwtVerifier>, | ||
| Algorithm::PS512 => Box::new(rsa::RsaPss512Verifier::new(key)?) as Box<dyn JwtVerifier>, | ||
| Algorithm::EdDSA => Box::new(eddsa::EdDSAVerifier::new(key)?) as Box<dyn JwtVerifier>, | ||
| }; | ||
|
|
||
| Ok(jwt_verifier) | ||
| } | ||
|
|
||
| /// The default [`CryptoProvider`] backed by [`aws_lc_rs`]. | ||
| pub static DEFAULT_PROVIDER: CryptoProvider = CryptoProvider { | ||
| signer_factory: new_signer, | ||
| verifier_factory: new_verifier, | ||
| jwk_utils: JwkUtils { | ||
| extract_rsa_public_key_components, | ||
| extract_ec_public_key_coordinates, | ||
| compute_digest, | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,26 @@ | ||
| //! The cryptography of the `jsonwebtoken` crate is decoupled behind | ||
| //! [`JwtSigner`] and [`JwtVerifier`] traits. These make use of `RustCrypto`'s | ||
| //! [`JwtSigner`] and [`JwtVerifier`] traits. These make use of `signature`'s | ||
| //! [`Signer`] and [`Verifier`] traits respectively. | ||
| //! Crypto provider selection is handled by [`CryptoProvider`]. | ||
| //! | ||
| //! [`JwtSigner`]: crate::crypto::JwtSigner | ||
| //! [`JwtVerifier`]: crate::crypto::JwtVerifier | ||
| //! [`Signer`]: signature::Signer | ||
| //! [`Verifier`]: signature::Verifier | ||
| //! [`CryptoProvider`]: crate::crypto::CryptoProvider | ||
|
|
||
| use crate::algorithms::Algorithm; | ||
| use crate::errors::Result; | ||
| use crate::jwk::{EllipticCurve, ThumbprintHash}; | ||
| use crate::{DecodingKey, EncodingKey}; | ||
|
|
||
| /// `aws_lc_rs` based CryptoProvider. | ||
| #[cfg(feature = "aws_lc_rs")] | ||
| pub(crate) mod aws_lc; | ||
| pub mod aws_lc; | ||
|
|
||
| /// `RustCrypto` based CryptoProvider. | ||
| #[cfg(feature = "rust_crypto")] | ||
| pub(crate) mod rust_crypto; | ||
| pub mod rust_crypto; | ||
|
|
||
| use crate::serialization::{b64_decode, b64_encode}; | ||
| use signature::{Signer, Verifier}; | ||
|
|
@@ -40,7 +46,7 @@ pub trait JwtVerifier: Verifier<Vec<u8>> { | |
| /// | ||
| /// If you just want to encode a JWT, use `encode` instead. | ||
| pub fn sign(message: &[u8], key: &EncodingKey, algorithm: Algorithm) -> Result<String> { | ||
| let provider = crate::encoding::jwt_signer_factory(&algorithm, key)?; | ||
| let provider = (CryptoProvider::get_default().signer_factory)(&algorithm, key)?; | ||
| Ok(b64_encode(provider.sign(message))) | ||
| } | ||
|
|
||
|
|
@@ -58,6 +64,124 @@ pub fn verify( | |
| key: &DecodingKey, | ||
| algorithm: Algorithm, | ||
| ) -> Result<bool> { | ||
| let provider = crate::decoding::jwt_verifier_factory(&algorithm, key)?; | ||
| let provider = (CryptoProvider::get_default().verifier_factory)(&algorithm, key)?; | ||
| Ok(provider.verify(message, &b64_decode(signature)?).is_ok()) | ||
| } | ||
|
|
||
| /// Controls the cryptography used by jsonwebtoken. | ||
| /// | ||
| /// You can either install one of the built-in options: | ||
| /// - [`crypto::aws_lc::DEFAULT_PROVIDER`]: (behind the `aws_lc_rs` crate feature). | ||
| /// This provider uses the [aws-lc-rs](https://github.com/aws/aws-lc-rs) crate. | ||
| /// - [`crypto::rust_crypto::DEFAULT_PROVIDER`]: (behind the `rust_crypto` crate feature) | ||
| /// This provider uses crates from the [Rust Crypto](https://github.com/RustCrypto) project. | ||
| /// | ||
| /// or provide your own custom custom implementation of `CryptoProvider`. | ||
| // This implementation appropriates a good chunk of code from the `rustls` CryptoProvider, | ||
| // and is very much inspired by it. | ||
| #[derive(Clone, Debug)] | ||
| pub struct CryptoProvider { | ||
| /// A function that produces a [`JwtSigner`] for a given [`Algorithm`] | ||
| pub signer_factory: fn(&Algorithm, &EncodingKey) -> Result<Box<dyn JwtSigner>>, | ||
| /// A function that produces a [`JwtVerifier`] for a given [`Algorithm`] | ||
| pub verifier_factory: fn(&Algorithm, &DecodingKey) -> Result<Box<dyn JwtVerifier>>, | ||
| /// Struct with utility functions for JWK processing. | ||
| pub jwk_utils: JwkUtils, | ||
| } | ||
|
|
||
| impl CryptoProvider { | ||
| /// Set this `CryptoProvider` as the default for this process. | ||
| /// | ||
| /// This can be called successfully at most once in any process execution. | ||
| pub fn install_default(&'static self) -> std::result::Result<(), &'static Self> { | ||
| static_default::install_default(self) | ||
| } | ||
|
|
||
| pub(crate) fn get_default() -> &'static Self { | ||
| static_default::get_default() | ||
| } | ||
|
|
||
| fn from_crate_features() -> &'static Self { | ||
| #[cfg(all(feature = "rust_crypto", not(feature = "aws_lc_rs")))] | ||
| { | ||
| return &rust_crypto::DEFAULT_PROVIDER; | ||
| } | ||
|
|
||
| #[cfg(all(feature = "aws_lc_rs", not(feature = "rust_crypto")))] | ||
| { | ||
| return &aws_lc::DEFAULT_PROVIDER; | ||
| } | ||
|
|
||
| #[allow(unreachable_code)] | ||
| { | ||
| const NOT_INSTALLED_ERROR: &str = r###" | ||
| Could not automatically determine the process-level CryptoProvider from jsonwebtoken crate features. | ||
| Call CryptoProvider::install_default() before this point to select a provider manually, or make sure exactly one of the 'rust_crypto' and 'aws_lc_rs' features is enabled. | ||
| See the documentation of the CryptoProvider type for more information. | ||
| "###; | ||
|
|
||
| static INSTANCE: CryptoProvider = CryptoProvider { | ||
| signer_factory: |_, _| panic!("{}", NOT_INSTALLED_ERROR), | ||
| verifier_factory: |_, _| panic!("{}", NOT_INSTALLED_ERROR), | ||
| jwk_utils: JwkUtils::new_unimplemented(), | ||
| }; | ||
|
|
||
| &INSTANCE | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Holds utility functions required for JWK processing. | ||
| /// Use the [`JwkUtils::new_unimplemented`] function to initialize all values to dummies. | ||
| #[derive(Clone, Debug)] | ||
| pub struct JwkUtils { | ||
| /// Given a DER encoded private key, extract the RSA public key components (n, e) | ||
| #[allow(clippy::type_complexity)] | ||
| pub extract_rsa_public_key_components: fn(&[u8]) -> Result<(Vec<u8>, Vec<u8>)>, | ||
| /// Given a DER encoded private key and an algorithm, extract the associated curve | ||
| /// and the EC public key components (x, y) | ||
| #[allow(clippy::type_complexity)] | ||
| pub extract_ec_public_key_coordinates: | ||
| fn(&[u8], Algorithm) -> Result<(EllipticCurve, Vec<u8>, Vec<u8>)>, | ||
| /// Given some data and a name of a hash function, compute hash_function(data) | ||
| pub compute_digest: fn(&[u8], ThumbprintHash) -> Vec<u8>, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems I forgot to bring this up before, but it would really be great if we could do a breaking change to turn this into
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you create an issue for it? We can change that for the next major version? |
||
| } | ||
|
|
||
| impl JwkUtils { | ||
| /// Initialises all values to dummies. | ||
| /// Will lead to a panic when JWKs are required, so only use it if you don't want to support JWKs. | ||
| pub const fn new_unimplemented() -> Self { | ||
| const NOT_INSTALLED_OR_UNIMPLEMENTED_ERROR: &str = r###" | ||
| Could not automatically determine the process-level CryptoProvider from jsonwebtoken crate features, or your CryptoProvider does not support JWKs. | ||
| Call CryptoProvider::install_default() before this point to select a provider manually, or make sure exactly one of the 'rust_crypto' and 'aws_lc_rs' features is enabled. | ||
| See the documentation of the CryptoProvider type for more information. | ||
| "###; | ||
| Self { | ||
| extract_rsa_public_key_components: |_| { | ||
| panic!("{}", NOT_INSTALLED_OR_UNIMPLEMENTED_ERROR) | ||
| }, | ||
| extract_ec_public_key_coordinates: |_, _| { | ||
| panic!("{}", NOT_INSTALLED_OR_UNIMPLEMENTED_ERROR) | ||
| }, | ||
| compute_digest: |_, _| panic!("{}", NOT_INSTALLED_OR_UNIMPLEMENTED_ERROR), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| mod static_default { | ||
| use std::sync::OnceLock; | ||
|
|
||
| use super::CryptoProvider; | ||
|
|
||
| static PROCESS_DEFAULT_PROVIDER: OnceLock<&'static CryptoProvider> = OnceLock::new(); | ||
|
|
||
| pub(crate) fn install_default( | ||
| default_provider: &'static CryptoProvider, | ||
| ) -> Result<(), &'static CryptoProvider> { | ||
| PROCESS_DEFAULT_PROVIDER.set(default_provider) | ||
| } | ||
|
|
||
| pub(crate) fn get_default() -> &'static CryptoProvider { | ||
| PROCESS_DEFAULT_PROVIDER.get_or_init(CryptoProvider::from_crate_features) | ||
| } | ||
| } | ||
arckoor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Uh oh!
There was an error while loading. Please reload this page.