Skip to content

Rust TLS: block the compromised eMudhra intermediates by SPKI hash in our reqwest/rustls verifier #1301

Open

Description

@tobyhede

Background

On 2026-10-17 Mozilla received a report that the private keys of eight eMudhra intermediate CAs had been compromised (dev-security-policy announcement, disclosure site, affected-certificates.json). Each intermediate has a Sectigo cross-sign as well as its emSign original, so dropping the emSign roots doesn't stop a chain from validating. The intermediates themselves have to be blocked. Node is doing exactly this with a compiled-in SPKI-SHA256 blocklist (nodejs/node#66871), and Go did it in crypto/x509 (1.27.3 / 1.26.10).

Why we are exposed

Our Rust HTTP clients use reqwest 0.13 with rustls-platform-verifier (root Cargo.toml, packages/eql/Cargo.lock, languages/typescript/packages/protect-ffi/Cargo.lock):

  • macOS / Windows: the OS verifier is used, so Apple's and Microsoft's blocks cover us.
  • Linux (most production deployments of protect-ffi, stack-auth and stack-kms): the verifier reads the OS bundle via rustls-native-certs and doesn't check revocation. The Sectigo cross-signs chain to a Sectigo root that stays in distro bundles, so these intermediates are still trusted. Anyone holding the keys could MITM ZeroKMS and auth traffic.
  • wasm32: reqwest goes through the runtime's fetch, so the runtime's TLS applies and this issue doesn't cover it.

Task

  1. Find every place in this tree that builds a reqwest client or rustls ClientConfig (start with packages/stack-auth/src/transport.rs and packages/stack-kms/src/connection/http.rs). Also check protect-ffi, which may get its client from the external cipherstash-client crate. If a client is built outside this repo, say so in the PR and list it as a follow-up rather than forking the dependency.
  2. Wrap rustls_platform_verifier::Verifier in a ServerCertVerifier that calls the inner verifier and then rejects the connection when any certificate in the presented chain has a SubjectPublicKeyInfo SHA-256 hash on a compiled-in blocklist. Report it as a revoked-certificate error. Checking only the presented intermediates is acceptable, since Node does the same. Delegate signature checks to the inner verifier.
  3. Seed the blocklist with the SPKI hashes of all 16 certificates (8 originals + 8 Sectigo cross-signs) from affected-certificates.json. Note that the file's fingerprints are whole-certificate SHA-256 hashes, not SPKI hashes. Use the SPKI hash field if the file has one; otherwise derive and document how you derived it. Record the source URL and date next to the list.
  4. Put the verifier in one shared place that the crates above can depend on, without adding a dependency cycle or a new published crate unless that's clearly required.
  5. Tests: a blocked SPKI in the chain is rejected, an unrelated chain is accepted, and the list has exactly the expected entries.
  6. Add changesets/version bumps as the repo's rules require (@cipherstash/auth changeset if stack-auth changes; follow AGENTS.md "Working on the Rust crates"). Keep cargo test --locked, clippy and fmt green.

Out of scope

The Go toolchain pin, which has its own issue.

Activity

  1. tobyhede commented on Oct 20, 2026

    @tobyhede
    ContributorAuthor

    Tracking the remaining gap (@cipherstash/stack via protect-ffi / cipherstash-client) in #1305. This issue stays open until that ships.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    SDKready-for-agentFully specified and ready for an AFK agent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions