Repository navigation
tls: reject chains containing blocked public keys - #66871
Conversation
|
Review requested:
|
577a4a9 to
db7cfbb
Compare
Node.js embeds the Mozilla root store but has no equivalent of the browsers' CRLSet/OneCRL and no default online revocation checking, so a certificate chain through a compromised intermediate still verifies as long as it reaches a trusted root. Removing the affected roots would over-block, since other legitimate intermediates chain to them. Add a small compiled-in blocklist of SubjectPublicKeyInfo SHA-256 hashes and check every verified chain against it from a single SSL_CTX_set_cert_verify_callback hook. A hit is reported as X509_V_ERR_CERT_REVOKED through the normal verification path, so it honours rejectUnauthorized and surfaces as authorizationError on both client and server sockets. The hook is installed for every SSL_CTX Node.js creates: SecureContext, QUIC and DTLS. The OpenSSL 4.1 CN name constraint handling previously registered from crypto_tls.cc moves into the same hook, since OpenSSL allows only one. The initial entries are the eight eMudhra/emSign intermediates whose private keys were reported compromised in October 2026. The same list is shipped by Go (CVE-2026-111943) and Electron. Sessions resumed from before this change are not re-verified, as is standard for TLS resumption; the exposure ends with the session timeout. Test-only hooks on the crypto binding, reachable with --expose-internals, allow the test to block a fixture key and exercise the full handshake. Refs: electron/electron#55126 Refs: golang/go#82345 Assisted-by: Claude, Codex Signed-off-by: Ulises Gascon <ulisesgascongonzalez@gmail.com>
db7cfbb to
71af0a9
Compare
Use only X509_STORE_CTX APIs that BoringSSL also provides. BoringSSL lacks X509_STORE_CTX_set_error_depth, X509_STORE_CTX_set_current_cert and X509_STORE_CTX_get_verify_cb, which broke the aarch64 shared BoringSSL build. Fetch the per-certificate callback from the SSL object instead and skip the two setters on BoringSSL, where Node.js' own callbacks never read them. Assisted-by: Claude Signed-off-by: Ulises Gascon <ulisesgascongonzalez@gmail.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #66871 +/- ##
========================================
Coverage 90.49% 90.50%
========================================
Files 797 797
Lines 278221 278315 +94
Branches 53462 53484 +22
========================================
+ Hits 251786 251888 +102
+ Misses 16809 16808 -1
+ Partials 9626 9619 -7
🚀 New features to boost your workflow:
|
|
Fast-track has been requested by @aduh95. Please 👍 to approve. |
| } | ||
|
|
||
| bool IsBlockedSPKIForTesting(const SpkiHash& digest) { | ||
| Mutex::ScopedLock lock(BlockedSPKIsForTestingMutex()); |
There was a problem hiding this comment.
This puts a mutex into every TLS handshake, to check a list which is guaranteed empty anywhere except our test suite. It's not a huge cost compared to a whole TLS handshake, but it's not free, especially if there's any actual contention (worker threads doing TLS).
Doesn't need to block this, but there might be nicer ways to test this without this list and mutex and extra internal APIs. We could just generate a fresh intermediate for our test suite for example and include its SPKI in the real revoked list. That'd be cheaper, no locking, and we could drop the internal test APIs etc that this adds. Having a revoked example we can test with is probably quite useful generally.
There was a problem hiding this comment.
I will work on a separate PR for this 👍
…ermediates The webapp Docker image no longer trusts the roots behind the eight eMudhra intermediate CAs whose private keys were compromised ([background](https://emudhra.we-have-a-problem.com/)): emSign ECC Root CA - G3, emSign Root CA - C1, emSign ECC Root CA - C3, and Sectigo's AAA Certificate Services, which cross-signs the same intermediates. The system CA bundle drops them for OpenSSL, goose and Prisma's engines. A small preload removes them from Node's bundled roots, since no Node release has done that yet ([nodejs/node#66871](nodejs/node#66871)). The image loads it through `NODE_OPTIONS`, and the entrypoint keeps it when `NODE_OPTIONS` is overridden at deploy time. Certificates added with `NODE_EXTRA_CA_CERTS` are kept. Mono-RevId: 28fb41176a5984be06fe3755f5fa4447e05d399f
|
Landed in d9a3f3d |
…mediates The private keys of eight eMudhra intermediate CAs were stolen (Pasta-Devs#7900). Browsers block them, but Node.js has no release with the block yet, so the server would accept a certificate an attacker signs with one of them. Wrap tls.checkServerIdentity at startup and refuse any chain that contains one of the eight public keys (their Sectigo cross-signs share them). Stopgap until the bundled Node includes nodejs/node#66871. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Data point for anyone landing here, plus a userland stopgap until a release containing this PR ships. Unpatched v24 releases still accept these chains. On v24.22.0 and v24.14.0, against the test hosts from https://emudhra.we-have-a-problem.com, plain Stopgap, using the same SPKI-hash approach as this PR. Wrap Caveats, each observed:
|
* ci: patch runner CA trust for eMudhra compromise Upgrade Ubuntu ca-certificates as the first step of every job and fail closed below 20260816, the first patched release. Set NODE_OPTIONS to --use-openssl-ca with SSL_CERT_FILE at job level so JavaScript actions trust the patched system store instead of the runner's bundled Node CA snapshot, whose fix is still pending (nodejs/node#66871). Advisory: https://emudhra.we-have-a-problem.com/ * ci: require Ubuntu-patched ca-certificates revision Plain 20260816 is the unpatched upstream baseline. Require 20260816ubuntu0.22.04.1 or later so the gate fails closed on it.
Node.js embeds the Mozilla root store but has no equivalent of the
browsers' CRLSet/OneCRL and no default online revocation checking. When
an intermediate CA key is compromised, a chain through it still verifies
as long as it reaches a trusted root. Removing the affected roots would
over-block, since other legitimate intermediates chain to them.
This adds a small compiled-in blocklist of SubjectPublicKeyInfo SHA-256
hashes, checked from a single
SSL_CTX_set_cert_verify_callbackhook onevery SSL_CTX Node.js creates (SecureContext, QUIC, DTLS). A hit is
reported as
X509_V_ERR_CERT_REVOKEDthrough the normal verificationpath, so it honours
rejectUnauthorizedand surfaces asauthorizationErroron client and server sockets. The OpenSSL 4.1 CNname-constraint handling from #65957 moves into the same hook, since
OpenSSL allows only one.
The initial entries are the eight eMudhra/emSign intermediates whose
private keys were reported compromised in October 2026. The hashes are
byte-identical to Go's fix (CVE-2026-111943) and Electron's patch.
Known limitation
Sessions resumed from before the upgrade are not re-verified. This is
standard TLS resumption behaviour and ends with the session timeout.
Points for reviewers
deliberately narrow; whether it should be configurable or updatable
outside a release is open.
internalBinding('crypto')behind--expose-internals. Atest/addonsC++ addon is the alternative.AI disclosure
Assisted by Claude Code and Codex; reviewed, built and tested by me.
Refs: electron/electron#55126
Refs: golang/go#82345 (technical details: golang/go@go1.27.2...go1.27.3)