tls: reject chains containing blocked public keys · nodejs/node@4342503 · GitHub
Skip to content

Commit 4342503

Browse files
UlisesGasconaduh95
authored andcommitted
tls: reject chains containing blocked public keys
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 and QUIC. 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> PR-URL: #66871 Backport-PR-URL: nodejs-private/node-private#1014 Reviewed-By: Filip Skokan <panva.ip@gmail.com>
1 parent 68fa0df commit 4342503

5 files changed

Lines changed: 510 additions & 1 deletion

File tree

‎src/crypto/crypto_context.cc‎

Lines changed: 232 additions & 1 deletion

‎src/crypto/crypto_context.h‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ X509_STORE* NewRootCertStore();
2323

2424
X509_STORE* GetOrCreateRootCertStore();
2525

26+
// Installs the certificate verification callback that applies the SPKI
27+
// blocklist (and, on OpenSSL 4.1+, the CN name-constraint handling) to every
28+
// chain verified through `ctx`. Must be called for every SSL_CTX Node.js
29+
// creates, since SSL_CTX_set_cert_verify_callback replaces any earlier hook.
30+
void InstallCertVerifyCallback(SSL_CTX* ctx);
31+
2632
ncrypto::BIOPointer LoadBIO(Environment* env, v8::Local<v8::Value> v);
2733

2834
class SecureContext final : public BaseObject {
@@ -126,6 +132,15 @@ class SecureContext final : public BaseObject {
126132
template <bool primary>
127133
static void GetCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);
128134

135+
// Test-only hooks for the SPKI blocklist that every SSL_CTX applies
136+
// during certificate verification. Reachable through
137+
// internalBinding('crypto') with --expose-internals. The argument is the
138+
// SHA-256 of a DER-encoded SubjectPublicKeyInfo.
139+
static void AddBlockedSPKIForTesting(
140+
const v8::FunctionCallbackInfo<v8::Value>& args);
141+
static void ClearBlockedSPKIsForTesting(
142+
const v8::FunctionCallbackInfo<v8::Value>& args);
143+
129144
static int TicketKeyCallback(SSL* ssl,
130145
unsigned char* name,
131146
unsigned char* iv,

‎src/crypto/crypto_tls.cc‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,9 @@ int TLSExtStatusCallback(SSL* s, void* arg) {
366366
void ConfigureSecureContext(SecureContext* sc) {
367367
// OCSP stapling
368368
sc->ctx().setStatusCallback(TLSExtStatusCallback);
369+
// The cert-verify callback (SPKI blocklist) is installed once per SSL_CTX
370+
// in SecureContext::Init, so that it also covers contexts created without
371+
// a TLSWrap.
369372
}
370373

371374
inline bool Set(

‎src/quic/tlscontext.cc‎

Lines changed: 5 additions & 0 deletions

0 commit comments

Comments
 (0)