Files
attune/crates/common/src/auth/crypto_provider.rs
David Culbreth 87d830f952
Some checks failed
CI / Rustfmt (push) Successful in 23s
CI / Cargo Audit & Deny (push) Successful in 30s
CI / Web Blocking Checks (push) Successful in 48s
CI / Security Blocking Checks (push) Successful in 8s
CI / Clippy (push) Failing after 1m55s
CI / Web Advisory Checks (push) Successful in 35s
CI / Security Advisory Checks (push) Successful in 37s
CI / Tests (push) Successful in 8m5s
[wip] cli capability parity
2026-03-06 16:58:50 -06:00

194 lines
6.5 KiB
Rust

//! 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<Sha256>;
type HmacSha384 = Hmac<Sha384>;
type HmacSha512 = Hmac<Sha512>;
// ---------------------------------------------------------------------------
// 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<Self> {
let inner = <$hmac_type>::new_from_slice(key.try_get_hmac_secret()?)
.map_err(|_| jsonwebtoken::errors::ErrorKind::InvalidKeyFormat)?;
Ok(Self(inner))
}
}
impl Signer<Vec<u8>> for $name {
fn try_sign(&self, msg: &[u8]) -> std::result::Result<Vec<u8>, 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<Self> {
let inner = <$hmac_type>::new_from_slice(key.try_get_hmac_secret()?)
.map_err(|_| jsonwebtoken::errors::ErrorKind::InvalidKeyFormat)?;
Ok(Self(inner))
}
}
impl Verifier<Vec<u8>> for $name {
fn verify(
&self,
msg: &[u8],
sig: &Vec<u8>,
) -> 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<Box<dyn JwtSigner>> {
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<Box<dyn JwtVerifier>> {
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());
}
}