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
194 lines
6.5 KiB
Rust
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());
|
|
}
|
|
}
|