[wip] cli capability parity
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
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
This commit is contained in:
193
crates/common/src/auth/crypto_provider.rs
Normal file
193
crates/common/src/auth/crypto_provider.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
//! 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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user