8000 SECURITY: node, node-lts: backport CVE-2026-111943 eMudhra fixes (nodejs/node#66871, #66877) by bryan-minimal · Pull Request #946 · gominimal/pkgs · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions packages/node-lts/build.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ let zstd = import "../zstd/build.ncl" in
let gtest = import "../gtest/build.ncl" in
let glibc = import "../glibc/build.ncl" in
let gcc = import "../gcc/build.ncl" in
let patch = import "../patch/build.ncl" in

let version = "24.19.0" in
{
Expand All @@ -28,9 +29,17 @@ let version = "24.19.0" in
url = "https://nodejs.org/dist/v%{version}/node-v%{version}.tar.gz",
sha256 = "16fe258006a6e86844fbe05b3b5e1e5623ca8d3da54e32d98d9e83234bf25b01",
} | Source,
# CVE-2026-111943 (eMudhra key compromise): backports of nodejs/node#66871
# (SPKI blocklist in TLS chain verification) and nodejs/node#66877 (drop the
# emSign roots removed in NSS 3.131.1), both merged to main 2026-10-20 and
# unreleased. Build-time only; see the patch headers. Drop both on the first
# release whose changelog lists them.
{ file = "cve-2026-111943-tls-spki-blocklist.patch" } | Local,
{ file = "cve-2026-111943-drop-emsign-roots.patch" } | Local,
base,
make,
pkgconf,
patch,
python,
toolchain,
],
Expand Down Expand Up @@ -113,5 +122,143 @@ let version = "24.19.0" in
["/bin/bash", "-c", "npm config get prefix | grep -q '/.local$'"],
],
} | Test,

# CVE-2026-111943: the emSign roots NSS 3.131.1 removed must be gone from
# the BUNDLED store (what `tls` trusts by default). FAILS on stock node
# (checked against node 26.9.0: all three are present) and
# passes only with cve-2026-111943-drop-emsign-roots.patch.
emsign_roots_removed =
{
class = 'Standalone,
test_deps = [base],
cmds = [
[
"/bin/bash",
"-c",
m%"
cat > roots.js <<'JSEOF'
const tls = require('tls');
const { X509Certificate } = require('crypto');
const removed = {
'86:A1:EC:BA:08:9C:4A:8D:3B:BE:27:34:C6:12:BA:34:1D:81:3E:04:3C:F9:E8:A8:62:CD:5C:57:A3:6B:BE:6B': 'emSign ECC Root CA - G3',
'12:56:09:AA:30:1D:A0:A2:49:B9:7A:82:39:CB:6A:34:21:6F:44:DC:AC:9F:39:54:B1:42:92:F2:E8:C8:60:8F': 'emSign Root CA - C1',
'BC:4D:80:9B:15:18:9D:78:DB:3E:1D:8C:F4:F9:72:6A:79:5D:A1:64:3C:A5:F1:35:8E:1D:DB:0E:DC:0D:7E:B3': 'emSign ECC Root CA - C3',
};
const fps = tls.rootCertificates.map((p) => new X509Certificate(p).fingerprint256);
// Guard against a vacuous pass: the bundled store must still be populated.
if (fps.length < 100) { console.error('only ' + fps.length + ' bundled roots'); process.exit(2); }
const hits = fps.filter((f) => f in removed).map((f) => removed[f]);
if (hits.length) { console.error('still bundled: ' + hits.join(', ')); process.exit(1); }
console.log('ok: ' + fps.length + ' bundled roots, none of the 3 removed emSign roots');
JSEOF
node roots.js
"%
],
],
} | Test,

# CVE-2026-111943: the compiled-in SPKI blocklist (nodejs/node#66871),
# exercised with a REAL compromised eMudhra intermediate and no network.
# A local server presents a throwaway self-signed leaf plus one extra cert.
# The leaf is untrusted, so verification fails and the patched callback
# scans every peer-sent cert: with the compromised intermediate attached,
# the error must become CERT_REVOKED; with an unrelated cert attached
# (control) it must not. Stock node reports DEPTH_ZERO_SELF_SIGNED_CERT for
# both (checked against node 26.9.0), so this fails without the patch. The
# intermediate's private key is not needed or used.
spki_blocklist_rejects_emudhra_intermediate =
{
class = 'Standalone,
test_deps = [base],
cmds = [
[
"/bin/bash",
"-c",
m%"
cat > blocklist.js <<'JSEOF'
'use strict';
const tls = require('tls');
// Throwaway self-signed localhost key/cert, generated for this test only.
const leafKey = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgBntishQTKnOQ5BYq
YMl6DN2832k3mPEuBBivBy/pZhuhRANCAARH7UBOAcealWpyFbYPHWsN7KwkGjbh
XaFy2MaYD4iRky5fXTP4oLh30Y4WGuqnidQo8NH3rLPA/eHByaZK7nSo
-----END PRIVATE KEY-----`;
const leafCert = `-----BEGIN CERTIFICATE-----
MIIBljCCATugAwIBAgIUDse2MObwU7yD+AVlIg+O5FY8oRowCgYIKoZIzj0EAwIw
FDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTI2MTAyMDIyMzQxNVoYDzIxMjYwOTI2
MjIzNDE1WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAARH7UBOAcealWpyFbYPHWsN7KwkGjbhXaFy2MaYD4iRky5fXTP4oLh3
0Y4WGuqnidQo8NH3rLPA/eHByaZK7nSoo2kwZzAdBgNVHQ4EFgQUJW3YJUswqwV2
eQeQjD699UlVE94wHwYDVR0jBBgwFoAUJW3YJUswqwV2eQeQjD699UlVE94wDwYD
VR0TAQH/BAUwAwEB/zAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwID
SQAwRgIhAN5h2+ujZZh9fAX3pbs36JbQm12AgxmBq5OdAxWrz0MvAiEArtKXS5Ev
uVh2ceJcjY/zb7ExiHzJlcxeUO5NAB7xCTw=
-----END CERTIFICATE-----`;
// REAL compromised intermediate: C=US, O=eMudhra Inc, CN=EM OV TLS CA - G3C
// (sha256 CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F,
// SPKI sha256 63ecefee...9cf8, 4th entry of kBlockedSPKIs). Public cert only.
const blocked = `-----BEGIN CERTIFICATE-----
MIIDaDCCAu6gAwIBAgIPKWkmwiejoVJjkrGUgqUEMAoGCCqGSM49BAMDMFYxCzAJ
BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk
MSAwHgYDVQQDDBdlbVNpZ24gUm9vdCBUTFMgQ0EgLSBHMzAeFw0yNTA0MzAxNTM3
NDFaFw00MDA0MjgxODMwMDBaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVk
aHJhIEluYzEbMBkGA1UEAwwSRU0gT1YgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0C
AQYFK4EEACIDYgAEE4JRzVKskpjByKrZdzId65fnCb0LTKCspV3IrVHc/J0ViaFZ
fjpRIOHv1jHGPhg1ysrimGmmLhYx6KKBgatWYW3EXZGKvAnzqw5jz3kqpNztc4e+
N7ulaVSfHAKycO1no4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L
WUIgkFQwHQYDVR0OBBYEFDTq6gzryQra+dDF4u2beMmHnhtsMB0GA1UdJQQWMBQG
CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow
KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB
BAGDjiEBAm4wCAYGZ4EMAQICMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY
aHR0cDovL29jc3AtYy5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw
b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNT1ZUTFNDQUczQy5wN2MwNwYDVR0f
BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j
cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E
AwMDaAAwZQIxAMSZt2xsPeumRdeSMSQ3CbzIZeQ/q7GDCWfeE1Pq+mhgA6gRM44c
NVF3xJ+wsxJk+wIwFJbaDMokD8Ei6eNOiA8yUMYvfzQyngmrhZQMdPCK1SewrR79
4rSb9vGqxZzelQth
-----END CERTIFICATE-----`;
// Unrelated self-signed cert (public part only) for the control probe.
const unrelated = `-----BEGIN CERTIFICATE-----
MIIBfzCCASWgAwIBAgIUMlp45Gz2s2WZcjmuScXJvcNL9wIwCgYIKoZIzj0EAwIw
FDESMBAGA1UEAwwJdW5yZWxhdGVkMCAXDTI2MTAyMDIyMzQxNVoYDzIxMjYwOTI2
MjIzNDE1WjAUMRIwEAYDVQQDDAl1bnJlbGF0ZWQwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAAQOzAYjCJw8zYxjnTgcx5mXxHW1AUQjRwPDgYzTXCC5hvEc/8/eWnyx
xTBtol+ZJEfe5E1/E18HPhsQzio03buco1MwUTAdBgNVHQ4EFgQUUKKd1zdu9Swr
C/u/vFyyF3KcX24wHwYDVR0jBBgwFoAUUKKd1zdu9SwrC/u/vFyyF3KcX24wDwYD
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAhRYCZ2DlkB58K8S2So8z
o17WxT6iVqn1rkr/6mOHNlcCIDzD/YRYVMZFDSWv0mpgj8ZC1jkR+tq5tfc1dWaq
6zcr
-----END CERTIFICATE-----`;
function probe(extra) {
return new Promise((resolve, reject) => {
const server = tls.createServer({ key: leafKey, cert: leafCert + '\n' + extra },
(s) => s.end());
server.listen(0, '127.0.0.1', () => {
const c = tls.connect({ port: server.address().port, host: '127.0.0.1',
rejectUnauthorized: false, checkServerIdentity() {} },
() => {
const e = c.authorizationError;
c.destroy();
server.close();
resolve(String(e));
});
c.on('error', (e) => { server.close(); reject(e); });
});
});
}
(async () => {
const control = await probe(unrelated);
const hit = await probe(blocked);
console.log('control=' + control + ' blocked=' + hit);
if (control === 'CERT_REVOKED' || hit !== 'CERT_REVOKED') process.exit(1);
})().catch((e) => { console.error(e); process.exit(2); });
JSEOF
node blocklist.js
"%
],
],
} | Test,
},
} | BuildSpec
28 changes: 28 additions & 0 deletions packages/node-lts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,34 @@ export CC=gcc
tar -xof "node-v${MINIMAL_ARG_VERSION}.tar.gz"
cd "node-v${MINIMAL_ARG_VERSION}"

# Applied by name, never a glob, so a patch that stops being fetched fails the
# build instead of being silently skipped (same convention as packages/node).
# CVE-2026-111943 (compromised eMudhra intermediate keys, Mozilla bug 2083449).
# Backports of nodejs/node#66871 (SPKI blocklist in TLS chain verification) and
# the root-store half of nodejs/node#66877 (drop the emSign roots NSS 3.131.1
# removed). Both merged to node main on 2026-10-20 and are NOT in a release yet;
# see each patch header for the deviations from upstream and when to drop them.
#
# The bundled root store is what `tls` trusts by default (we do not configure
# --use-openssl-ca), so the ca-certificates fix does not reach it; and the
# blocklist is needed even with the roots gone, because a chain through a
# compromised key also verifies against any OTHER store that still carries a
# path to it (system store, a caller's `ca`, the Sectigo cross-sign).
patch -Np1 -i "../cve-2026-111943-tls-spki-blocklist.patch"
patch -Np1 -i "../cve-2026-111943-drop-emsign-roots.patch"

# Prove both landed (patch can exit 0 on an already-applied hunk): the
# blocklist table, its install on every SecureContext, and the root gone.
grep -q "constexpr SpkiHash kBlockedSPKIs" src/crypto/crypto_context.cc &&
grep -q "InstallCertVerifyCallback(sc->ctx_.get());" src/crypto/crypto_context.cc || {
echo "ERROR: CVE-2026-111943 SPKI blocklist patch did not land in src/crypto/crypto_context.cc" >&2
exit 1
}
if grep -q "emSign ECC Root CA - G3" src/node_root_certs.h; then
echo "ERROR: CVE-2026-111943 root removal did not land in src/node_root_certs.h" >&2
exit 1
fi

case $(uname -m) in
x86_64) MARCH="-march=x86-64-v3" ;;
aarch64) MARCH="-march=armv8-a" ;;
Expand Down
81 changes: 81 additions & 0 deletions packages/node-lts/cve-2026-111943-drop-emsign-roots.patch
< 8BD0 tr data-hunk="7541334fdbaec3949b49b1dc872219a03940522c4e21f2c779ce061c3ba3cd29" class="show-top-border">
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
CVE-2026-111943: drop the three emSign roots removed in NSS 3.131.1.

Backport of the src/node_root_certs.h half of nodejs/node#66877 ("crypto:
update root certificates to NSS 3.131.1", merged to main 2026-10-20 as
ae223417d964, not yet in any release). Removes from node's BUNDLED root store
(what `tls` trusts by default, and what tls.rootCertificates returns):

emSign ECC Root CA - G3 sha256 86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B
emSign Root CA - C1 sha256 125609AA301DA0A249B97A8239CB6A34216F44DCAC9F3954B14292F2E8C8608F
emSign ECC Root CA - C3 sha256 BC4D809B15189D78DB3E1D8CF4F9726A795DA1643CA5F1358E1DDB0EDC0D7EB3

emSign ECC Root CA - G3 is the Mozilla-store anchor for the compromised
intermediates (it cross-signs emSign Root TLS CA - G3). The upstream PR also
deletes the matching tools/certdata.txt records; that file is an input to
node's root-cert generator, not to the build, so that hunk is omitted.

Applies unchanged to node 24.19.0 and 26.10.0. Drop on the first node release
whose changelog lists nodejs/node#66877 (or an NSS >= 3.131.1 root update).

diff --git a/src/node_root_certs.h b/src/node_root_certs.h
index 1d3af484161..bad9cb8d984 100644
--- a/src/node_root_certs.h
+++ b/src/node_root_certs.h
@@ -1500,57 +1500,6 @@
"fko=\n"
"-----END CERTIFICATE-----",

-/* emSign ECC Root CA - G3 */
-"-----BEGIN CERTIFICATE-----\n"
-"MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjET\n"
-"MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGlt\n"
-"aXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAw\n"
-"WhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTEl\n"
-"MCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWdu\n"
-"IEVDQyBSb290IENBIC0gRzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3i\n"
-"AhKAnjlfSU2fySU0WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4P\n"
-"PwT01xySfvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB\n"
-"zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQD\n"
-"AwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHt\n"
-"RwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj\n"
-"-----END CERTIFICATE-----",
-
-/* emSign Root CA - C1 */
-"-----BEGIN CERTIFICATE-----\n"
-"MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMC\n"
-"VVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD\n"
-"ExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFow\n"
-"VjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg\n"
-"SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOC\n"
-"AQ8AMIIBCgKCAQEAz+upufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4\n"
-"cI7gklL35CX5VIPZHdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQ\n"
-"FTdrHCZH3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH\n"
-"GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwh\n"
-"bL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwID\n"
-"AQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEG\n"
-"MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAMJKVvoVIXsoounlHfv4LcQ5\n"
-"lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1p\n"
-"M/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT\n"
-"5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcx\n"
-"HpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo\n"
-"WXzhriKi4gp6D/piq1JM4fHfyr6DDUI=\n"
-"-----END CERTIFICATE-----",
-
-/* emSign ECC Root CA - C3 */
-"-----BEGIN CERTIFICATE-----\n"
-"MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzET\n"
-"MBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2Vt\n"
-"U2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFow\n"
-"WjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg\n"
-"SW5jMSAwHgYDVQQDExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuB\n"
-"BAAiA2IABP2lYa57JhAd6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+\n"
-"yAdBPLtVb4OjavtisIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAw\n"
-"HQYDVR0OBBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB\n"
-"Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaP\n"
-"WEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw\n"
-"0Md3f76BB1UwUCAU9Vc4CqgxUQ==\n"
-"-----END CERTIFICATE-----",
-
/* Hongkong Post Root CA 3 */
"-----BEGIN CERTIFICATE-----\n"
"MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzEL\n"
Loading
Loading
0