Repository navigation
Move every TLS path onto rustls (eMudhra distrust) and clear cargo audit - #236
Merged
Merged
Conversation
bdk_esplora's `async-https` feature turns on reqwest 0.12 `default-tls`,
which is native-tls (OpenSSL with the system root store on Linux). Eight
eMudhra intermediate CA keys are compromised and chain to roots that
system stores still ship ("AAA Certificate Services", "emSign ECC Root
CA - G3"), so anyone can mint a certificate the esplora client accepts
and feed a node fake chain state.
Switch to `async-https-rustls` and bump the verification crates:
- rustls-webpki 0.103.16 rejects paths through the distrusted SPKIs
regardless of root store.
- webpki-roots / webpki-root-certs 1.0.10 drop the affected trust.
- rustls 0.23.46 fixes RUSTSEC-2026-0346..0349, now on the esplora path.
Downstream crates cannot undo this themselves: features are additive,
and reqwest 0.12 picks native-tls whenever `default-tls` is on, even
with `rustls-tls` also enabled.
postgresql_embedded's default features use native-tls, which was the last path to OpenSSL in the workspace (reqwest 0.12 via postgresql_archive). Use its `rustls` feature instead. The lockfile no longer contains native-tls, openssl, openssl-sys or hyper-tls.
- bitcoind / electrsd 0.41 -> 0.43.2: bitcoind's `download` build script no longer depends on zip 0.6.6 (RUSTSEC-2026-0367, -0368, -0369). - negentropy 0.5.0 -> 0.5.2 (RUSTSEC-2026-0365), via nostr.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
eMudhra: we have a problem (2026-10-20): the private keys of eight eMudhra intermediate CAs (16 certificates, counting their Sectigo cross-signs) are recoverable. Anyone can mint a TLS certificate for any host that chains to "AAA Certificate Services" (Comodo) or "emSign ECC Root CA - G3".
ddk's esplora client verified TLS with native-tls, which on Linux means OpenSSL with the system root store. Debian 12 based images, including
gcr.io/distroless/cc-debian12, still ship both roots. OpenSSL has no distrust list for these intermediates. A MITM on esplora traffic can serve a forged certificate for the esplora host and feed a DLC node fake chain state: heights, confirmations, transactions, and fee estimates.Fixed versions:
rustls-webpki0.103.16 rejects any path through the distrusted SPKIs, whatever the root store, andwebpki-roots/webpki-root-certs1.0.10 drop the affected trust.Why downstream crates can't fix this themselves
bdk_esplora/async-https→esplora-client/async-https→reqwest 0.12/default-tls→ native-tls.default-tlsback off.default-tlsis on, even ifrustls-tlsis also on (TlsBackend::default()inreqwest/src/tls.rs).EsploraClient::newbuilds its ownreqwest::Client, so there's no way to inject a different client either.Change
ddk/Cargo.toml:bdk_esplorafeatureasync-https→async-https-rustls. reqwest 0.12 now uses rustls 0.23 with the bundledwebpki-roots.Cargo.lock:rustls-webpki0.103.15 → 0.103.16,webpki-roots1.0.8 → 1.0.10,webpki-root-certs1.0.8 → 1.0.10, andrustls0.23.45 → 0.23.46. The rustls bump fixes RUSTSEC-2026-0346/0347/0348/0349, which are now on the esplora path.testenv/Cargo.toml:postgresql_embedded(still 0.19, because of the MSRV pin) uses itsrustlsfeature instead of the defaultnative-tls. This was the last path to OpenSSL in the workspace. The lockfile no longer containsnative-tls,openssl,openssl-sys,hyper-tlsortokio-native-tls.cargo auditpassing:bitcoind/electrsd0.41 → 0.43.2. bitcoind'sdownloadbuild script no longer useszip0.6.6 (RUSTSEC-2026-0367/0368/0369). testenv's use of their API is unchanged.negentropy0.5.0 → 0.5.2 (RUSTSEC-2026-0365), via nostr.The feature switch alone is not enough. With rustls but the old lockfile (
rustls-webpki0.103.15,webpki-roots1.0.8), the G3B host is still accepted, becausewebpki-roots1.0.8 bundles "emSign ECC Root CA - G3":Important
A library's
Cargo.lockdoesn't bind its consumers. Every downstream binary must also runcargo update -p rustls-webpki -p webpki-roots -p webpki-root-certs -p rustlsin its own lockfile.Verification
Each run uses ddk's own
EsploraClient::new, the production config, from a scratch crate outside this workspace that depends onddkby path. Running it outside the workspace keeps ddk's dev-dependencies out of feature unification, so the build matches what a consumer gets. Whenget_height()fails, the check repeats the GET with the samereqwest::Clientto show whether the TLS handshake succeeded.The Linux runs use a Debian bookworm container (OpenSSL 3.0.22) with the CA bundle copied out of
gcr.io/distroless/cc-debian12. That bundle contains both affected roots. The currentrust:bookwormimage (ca-certificates20250419~deb12u2) has already dropped them, so it would not be representative.Before:
master@ 1ee82da (native-tls)Linux, OpenSSL, distroless debian12 roots. Both forged certificates are accepted:
The macOS run (native-tls → Security.framework) gives the same result: both handshakes succeed and return HTTP 200.
After: this branch (rustls)
Linux, distroless debian12 roots. Both are rejected as distrusted, and mempool.space still works:
macOS gives identical output.
Dependency graph
native-tls,openssl,openssl-sys,hyper-tlsandtokio-native-tlsare gone fromCargo.lock.cargo tree -ifor each, with--all-features --target all -e all, reports no match.openssl-probe. It doesn't use OpenSSL: it's a pure-Rust crate thatrustls-native-certsuses to find the system CA bundle path on Linux. It comes in throughrustls-platform-verifier, which reqwest 0.13 always uses in rustls mode (the kormir/p2pderivatives oracle clients), and throughpostgresql_archive's rustls native roots. Paths verified that way still go throughrustls-webpki0.103.16, so the distrusted intermediates are rejected even with the system root store.cargo tree -i rustls-webpki: a single version, 0.103.16. The other rustls users (reqwest 0.13 for the kormir/p2pderivatives oracle clients, nostr's tungstenite,rustls-platform-verifier) all resolve to it.webpki-roots0.26.11 is still in the lockfile (via nostr'stokio-tungstenite), but it's the compat shim that re-exportswebpki-roots1.0.10.Longer term
esplora-client 0.13 moved to
bitreqwith rustls by default. No bdk_esplora release uses it yet (0.22.3 is still on esplora-client 0.12). Once one does, moving to it removes reqwest 0.12 and this feature trap from the esplora path entirely.Tests
cargo fmt --check,cargo clippy -- -D warnings, andcargo +1.88 check --all-features(MSRV): pass.cargo audit --ignore RUSTSEC-2023-0071: passes, exit 0 (mastercurrently fails with 16 advisories). Only the existing "unmaintained"/unsound warnings remain (fxhash,instant,nostr-relay-builder,nostr-relay-pool,lru), and they don't fail the job.CertDistrusted, and mempool.space still succeeds.linux/amd64Debian container (the same OS family as the CI runners):cargo test --all-features --no-fail-fast: every target passes exceptddk --lib, where 17 postgres storage tests fail.masterfails the same 17, test for test. The current theseus postgres 18.6.1 Linux build ships a partialshare/timezonewith noUTC, so the server rejects sqlx'sTimeZone=UTC. CI is shielded by its~/.theseuscache. The embedded postgres download itself now runs over rustls, and the server installs and starts. The tests that run against bitcoind/electrs 0.43 (wallet::tests,balance,chain,nostr) pass.--ignored, featuresddk-manager/parallel,ddk-manager/use-serde):manager_execution_tests86/86 andstateless_execution47/47 pass against bitcoind/electrs 0.43.manager_tests(7) andcontract_updater(1) have no ignored tests; they ran and passed in the unit step.