diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 0000000..128c905 --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,14 @@ +# cargo-audit configuration. +# Run: cargo audit +# +# RUSTSEC-2023-0071 — "Marvin Attack" timing sidechannel in `rsa` 0.9.x +# (rsa -> jsonwebtoken rust_crypto backend -> structured-proxy). +# +# Ignored deliberately; mirror of the rationale in deny.toml. +# See https://github.com/structured-world/structured-proxy/issues/48 +# - No fixed `rsa` release exists (latest 0.10.0-rc; advisory patched:[]). +# - We keep the pure-Rust `rust_crypto` backend over `aws_lc_rs` (C FFI). +# - RSA is used for JWT verification (public key) only; Marvin targets +# private-key timing, not exploitable on the verify path. +[advisories] +ignore = ["RUSTSEC-2023-0071"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4096c97..feab851 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,21 @@ env: jobs: quality-checks: - name: Quality Checks + name: Quality Checks (${{ matrix.backend.name }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + backend: + # The jsonwebtoken crypto backends are mutually exclusive (enabling + # both panics at runtime), so each is exercised on its own leg instead + # of via `--all-features`. + - name: rust_crypto + # Pure-Rust default backend (RustCrypto / rsa). + flags: "--features redis" + - name: aws_lc_rs + # Opt-in constant-time backend (aws-lc, C FFI); disables the default. + flags: "--no-default-features --features aws_lc_rs,redis" steps: - uses: actions/checkout@v6 with: @@ -30,16 +43,32 @@ jobs: - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: Format + if: matrix.backend.name == 'rust_crypto' run: cargo fmt --all -- --check - name: Clippy - run: cargo clippy --all-targets --all-features + run: cargo clippy --all-targets ${{ matrix.backend.flags }} - name: Build - run: cargo build --release + run: cargo build --release ${{ matrix.backend.flags }} - name: Test - run: cargo test --all-features + run: cargo test ${{ matrix.backend.flags }} - name: Publish dry-run + if: matrix.backend.name == 'rust_crypto' run: cargo publish --dry-run + + security-audit: + name: Security Audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + # Scans Cargo.lock for RustSec advisories; honors the ignore list in + # deny.toml. Third-party action: pinned to a commit SHA (dependabot + # keeps it fresh). + - uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe # v2.0.20 + with: + command: check advisories diff --git a/Cargo.toml b/Cargo.toml index 0e44084..af4a81c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,8 +64,11 @@ ipnet = "2" # Optional shared rate-limit store for multi-instance deployments. redis = { version = "0.27", features = ["tokio-comp"], optional = true } -# Auth (JWT validation, route policies) -jsonwebtoken = { version = "10", features = ["rust_crypto"] } +# Auth (JWT validation, route policies). +# Crypto backend is selected via this crate's `rust_crypto` / `aws_lc_rs` +# features (see [features] below); `use_pem` is always required for PEM key +# parsing and is therefore enabled unconditionally. +jsonwebtoken = { version = "10", default-features = false, features = ["use_pem"] } reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } globset = "0.4" base64 = "0.22" @@ -78,6 +81,21 @@ clap = { version = "4", features = ["derive"] } envoy-types = "0.7" [features] +default = ["rust_crypto"] + +# JWT crypto backend. Mutually exclusive: enable exactly one. jsonwebtoken +# picks its provider from these features and panics at runtime if both (or +# neither) are on, so do NOT build with `--all-features`. +# +# `rust_crypto` (default): pure-Rust RustCrypto backend. Pulls in `rsa`, which +# carries RUSTSEC-2023-0071 (Marvin Attack). Not exploitable on our verify-only +# path (public key, no private-key ops); see deny.toml / issue #48. +rust_crypto = ["jsonwebtoken/rust_crypto"] +# `aws_lc_rs`: constant-time / FIPS-capable backend via aws-lc (C FFI), +# advisory-free. Opt-in for consumers that allow FFI; enable with +# `default-features = false, features = ["aws_lc_rs"]`. +aws_lc_rs = ["jsonwebtoken/aws_lc_rs"] + # Shared Redis-backed rate-limit store for multi-instance deployments. redis = ["dep:redis"] diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..9dba480 --- /dev/null +++ b/deny.toml @@ -0,0 +1,21 @@ +# cargo-deny configuration. +# Run: cargo deny check advisories +# +# Only the advisories section is configured here; license/bans/sources checks +# fall back to cargo-deny defaults. + +[advisories] +# RUSTSEC-2023-0071 — "Marvin Attack" timing sidechannel in `rsa` 0.9.x. +# Chain: rsa -> jsonwebtoken (rust_crypto backend) -> structured-proxy. +# +# Ignored deliberately. See https://github.com/structured-world/structured-proxy/issues/48 +# 1. No fix exists: latest `rsa` is 0.10.0-rc and the advisory has patched:[] / +# unaffected:[] in the RustSec DB. Nothing to upgrade to. +# 2. We keep jsonwebtoken's pure-Rust `rust_crypto` backend over `aws_lc_rs` +# (C FFI), required by no-FFI consumers (e.g. CoordiNode ADR-013). +# 3. RSA here is used for JWT *verification* (public key) only. Marvin targets +# private-key timing (decrypt/sign), which never runs on the verify path. +# Revisit when RustCrypto ships a constant-time `rsa` stable release. +ignore = [ + { id = "RUSTSEC-2023-0071", reason = "No fixed `rsa` release exists (latest 0.10.0-rc; advisory patched:[]). We deliberately keep jsonwebtoken's pure-Rust `rust_crypto` backend over `aws_lc_rs` (C FFI). RSA is used for JWT verification (public key) only; Marvin targets private-key timing, not exploitable on the verify path. Revisit when RustCrypto ships a constant-time `rsa` stable. Tracking: structured-world/structured-proxy#48" }, +] diff --git a/src/lib.rs b/src/lib.rs index 1b176cc..d981eb7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,21 @@ //! structured-proxy --config sid-proxy.yaml //! structured-proxy --config sflow-proxy.yaml //! ``` +//! +//! ## JWT crypto backend +//! +//! Exactly one crypto backend feature must be enabled (they are mutually +//! exclusive): `rust_crypto` (default, pure Rust) or `aws_lc_rs` (opt-in, +//! constant-time / FIPS-capable, links aws-lc via C FFI). Enabling both or +//! neither is rejected at compile time by the guards below. + +// jsonwebtoken selects its provider from these features and would otherwise +// panic at runtime on an invalid combination; turn that into a build error. +#[cfg(all(feature = "rust_crypto", feature = "aws_lc_rs"))] +compile_error!("features `rust_crypto` and `aws_lc_rs` are mutually exclusive; enable exactly one"); + +#[cfg(not(any(feature = "rust_crypto", feature = "aws_lc_rs")))] +compile_error!("exactly one JWT crypto backend must be enabled: `rust_crypto` or `aws_lc_rs`"); pub mod auth; pub mod config;