//! HMAC-only CryptoProvider for jsonwebtoken v10. //! //! The `jsonwebtoken` crate v10 requires a `CryptoProvider` to be installed //! before any signing/verification operations. The built-in `rust_crypto` //! feature pulls in the `rsa` crate, which has an unpatched advisory //! (RUSTSEC-2023-0071 — Marvin Attack timing sidechannel). //! //! Since Attune only uses HMAC-SHA2 (HS256/HS384/HS512) for JWT signing, //! this module provides a minimal CryptoProvider that supports only those //! algorithms, avoiding the `rsa` dependency entirely. //! //! Call [`install()`] once at process startup (before any JWT operations). use hmac::{Hmac, Mac}; use jsonwebtoken::crypto::{CryptoProvider, JwkUtils, JwtSigner, JwtVerifier}; use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey}; use sha2::{Sha256, Sha384, Sha512}; use signature::{Signer, Verifier}; use std::sync::Once; type HmacSha256 = Hmac; type HmacSha384 = Hmac; type HmacSha512 = Hmac; // --------------------------------------------------------------------------- // Signers // --------------------------------------------------------------------------- macro_rules! define_hmac_signer { ($name:ident, $alg:expr, $hmac_type:ty) => { struct $name($hmac_type); impl $name { fn new(key: &EncodingKey) -> jsonwebtoken::errors::Result { let inner = <$hmac_type>::new_from_slice(key.try_get_hmac_secret()?) .map_err(|_| jsonwebtoken::errors::ErrorKind::InvalidKeyFormat)?; Ok(Self(inner)) } } impl Signer> for $name { fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { let mut mac = self.0.clone(); mac.reset(); mac.update(msg); Ok(mac.finalize().into_bytes().to_vec()) } } impl JwtSigner for $name { fn algorithm(&self) -> Algorithm { $alg } } }; } define_hmac_signer!(Hs256Signer, Algorithm::HS256, HmacSha256); define_hmac_signer!(Hs384Signer, Algorithm::HS384, HmacSha384); define_hmac_signer!(Hs512Signer, Algorithm::HS512, HmacSha512); // --------------------------------------------------------------------------- // Verifiers // --------------------------------------------------------------------------- macro_rules! define_hmac_verifier { ($name:ident, $alg:expr, $hmac_type:ty) => { struct $name($hmac_type); impl $name { fn new(key: &DecodingKey) -> jsonwebtoken::errors::Result { let inner = <$hmac_type>::new_from_slice(key.try_get_hmac_secret()?) .map_err(|_| jsonwebtoken::errors::ErrorKind::InvalidKeyFormat)?; Ok(Self(inner)) } } impl Verifier> for $name { fn verify( &self, msg: &[u8], sig: &Vec, ) -> std::result::Result<(), signature::Error> { let mut mac = self.0.clone(); mac.reset(); mac.update(msg); mac.verify_slice(sig).map_err(signature::Error::from_source) } } impl JwtVerifier for $name { fn algorithm(&self) -> Algorithm { $alg } } }; } define_hmac_verifier!(Hs256Verifier, Algorithm::HS256, HmacSha256); define_hmac_verifier!(Hs384Verifier, Algorithm::HS384, HmacSha384); define_hmac_verifier!(Hs512Verifier, Algorithm::HS512, HmacSha512); // --------------------------------------------------------------------------- // Provider // --------------------------------------------------------------------------- fn hmac_signer_factory( algorithm: &Algorithm, key: &EncodingKey, ) -> jsonwebtoken::errors::Result> { match algorithm { Algorithm::HS256 => Ok(Box::new(Hs256Signer::new(key)?)), Algorithm::HS384 => Ok(Box::new(Hs384Signer::new(key)?)), Algorithm::HS512 => Ok(Box::new(Hs512Signer::new(key)?)), _other => Err(jsonwebtoken::errors::ErrorKind::InvalidAlgorithm.into()), } } fn hmac_verifier_factory( algorithm: &Algorithm, key: &DecodingKey, ) -> jsonwebtoken::errors::Result> { match algorithm { Algorithm::HS256 => Ok(Box::new(Hs256Verifier::new(key)?)), Algorithm::HS384 => Ok(Box::new(Hs384Verifier::new(key)?)), Algorithm::HS512 => Ok(Box::new(Hs512Verifier::new(key)?)), _other => Err(jsonwebtoken::errors::ErrorKind::InvalidAlgorithm.into()), } } /// HMAC-only [`CryptoProvider`]. Supports HS256, HS384, HS512 only. /// JWK utility functions (RSA/EC key extraction) are stubbed out since /// Attune never uses asymmetric JWKs. static HMAC_PROVIDER: CryptoProvider = CryptoProvider { signer_factory: hmac_signer_factory, verifier_factory: hmac_verifier_factory, jwk_utils: JwkUtils::new_unimplemented(), }; static INIT: Once = Once::new(); /// Install the HMAC-only crypto provider for jsonwebtoken. /// /// Safe to call multiple times — only the first call takes effect. /// Must be called before any JWT encode/decode operations. pub fn install() { INIT.call_once(|| { // install_default returns Err if already installed (e.g., by a feature-based // provider). That's fine — we only care that *some* provider is present. let _ = HMAC_PROVIDER.install_default(); }); } #[cfg(test)] mod tests { use super::*; #[test] fn test_install_idempotent() { install(); install(); // second call should not panic } #[test] fn test_hmac_sign_and_verify() { install(); let secret = b"test-secret-key"; let encoding_key = EncodingKey::from_secret(secret); let decoding_key = DecodingKey::from_secret(secret); let message = b"hello world"; let signer = hmac_signer_factory(&Algorithm::HS256, &encoding_key).expect("should create signer"); let sig = signer.try_sign(message).expect("should sign"); let verifier = hmac_verifier_factory(&Algorithm::HS256, &decoding_key) .expect("should create verifier"); verifier .verify(message, &sig) .expect("signature should verify"); } #[test] fn test_unsupported_algorithm_rejected() { install(); let key = EncodingKey::from_secret(b"key"); let result = hmac_signer_factory(&Algorithm::RS256, &key); assert!(result.is_err()); } }