From b0abeb5a166b770a0920cda4d912463531ee8b1d Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 15 Aug 2026 11:09:14 -0500 Subject: [PATCH] fix(runtime): cover Windows policy root stores --- .../ambient/scriptc-node-fallback.d.ts | 2 +- packages/compiler/test/cc-driver.test.ts | 4 +-- packages/runtime/src/scr_runtime.h | 8 +++--- packages/runtime/src/scr_tls.c | 6 ++--- packages/runtime/src/scr_tls_ca.c | 26 ++++++++++++++++--- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index a9fef823a..9f4d925ca 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -2378,7 +2378,7 @@ declare module "tls" { /** The CA-store introspection surface (scr_tls_ca.c): per-type cached * PEM string arrays. The host bundle stands in for Node's compiled-in * Mozilla roots ('bundled', and rootCertificates) AND the platform - * store ('system') — the Windows ROOT store or POSIX bundle probe; + * store ('system') — the Windows ROOT stores or POSIX bundle probe; * 'extra' reads NODE_EXTRA_CA_CERTS. An unknown type string throws * Node's ERR_INVALID_ARG_VALUE TypeError. */ export function getCACertificates(type?: string): string[]; diff --git a/packages/compiler/test/cc-driver.test.ts b/packages/compiler/test/cc-driver.test.ts index 924f7242d..19ba7e35c 100644 --- a/packages/compiler/test/cc-driver.test.ts +++ b/packages/compiler/test/cc-driver.test.ts @@ -16,7 +16,7 @@ */ import { execFile, execFileSync } from "node:child_process"; import { chmod, mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; +import { EOL, tmpdir } from "node:os"; import { join } from "node:path"; import { promisify } from "node:util"; import { describe, expect, test } from "vitest"; @@ -414,7 +414,7 @@ describe.skipIf(!zigOnPath())("zig cc builds (zig on PATH)", () => { "../../../tests/fixtures/server/certs/ca.pem", ); const { stdout } = await execFileAsync(outPath, [caPath]); - expect(stdout).toBe("windows EKU policy ok\n"); + expect(stdout).toBe(`windows EKU policy ok${EOL}`); }, 600_000, ); diff --git a/packages/runtime/src/scr_runtime.h b/packages/runtime/src/scr_runtime.h index 9c4a3a518..04ebaf5d8 100644 --- a/packages/runtime/src/scr_runtime.h +++ b/packages/runtime/src/scr_runtime.h @@ -5495,7 +5495,7 @@ long scr_secure_ctx_live_count(void); /* ── node:tls, the CA-store introspection slice (scr_tls_ca.c — its own * unit and link gate; NO mbedTLS, so a getCACertificates-only binary never * builds the archive; cc.ts also compiles it whenever scr_tls.c does). The - * HOST roots (Windows' logical ROOT store, the established bundle probe on + * HOST roots (Windows' system ROOT stores, the established bundle probe on * POSIX) stand in for both Node's compiled-in Mozilla roots ('bundled', * rootCertificates) and the platform store ('system') — the established * SEMANTICS divergence, extended to introspection; 'extra' uses the @@ -5505,9 +5505,9 @@ long scr_secure_ctx_live_count(void); void scr_tls_ca_install(void); /* snapshots NODE_EXTRA_CA_CERTS + file bytes */ bool scr_tls_ca_extra_pem(const char **pem, size_t *len); /* borrowed launch snapshot */ #ifdef _WIN32 -/* Enumerates the current user's logical ROOT store (including inherited - * machine roots), yielding only certificates whose effective Windows EKU - * policy permits TLS server authentication. DER is borrowed for the call. */ +/* Enumerates the machine, enterprise, current-user, and policy ROOT-store + * locations, yielding only certificates whose effective Windows EKU policy + * permits TLS server authentication. DER is borrowed for the call. */ typedef void (*ScrTlsCaWindowsRootFn)(void *ctx, const unsigned char *der, size_t len); bool scr_tls_ca_windows_cert_server_auth(const void *cert_context); void scr_tls_ca_windows_roots(ScrTlsCaWindowsRootFn fn, void *ctx); diff --git a/packages/runtime/src/scr_tls.c b/packages/runtime/src/scr_tls.c index 02f3fb0b7..5ed32bc02 100644 --- a/packages/runtime/src/scr_tls.c +++ b/packages/runtime/src/scr_tls.c @@ -338,9 +338,9 @@ typedef struct ScrTlsCli { /* The default trust anchors when no `ca` option is given: the host trust * store, standing in for Node's compiled-in Mozilla roots, plus - * NODE_EXTRA_CA_CERTS. Windows certificates live as DER entries in the - * current user's logical ROOT store, which includes the inherited machine - * roots; mbedTLS copies each successfully parsed entry into its own chain. + * NODE_EXTRA_CA_CERTS. Windows certificates live as DER entries across the + * machine, enterprise, current-user, and policy ROOT-store locations; + * mbedTLS copies each successfully parsed entry into its own chain. * POSIX hosts probe the established bundle spellings — /etc/ssl/cert.pem * first (macOS ships it; Alpine links it), then Debian/Ubuntu's * ca-certificates.crt, Fedora/RHEL's ca-bundle.crt, and openSUSE's diff --git a/packages/runtime/src/scr_tls_ca.c b/packages/runtime/src/scr_tls_ca.c index 8876fdc46..af42d2447 100644 --- a/packages/runtime/src/scr_tls_ca.c +++ b/packages/runtime/src/scr_tls_ca.c @@ -11,7 +11,7 @@ * * Divergences, documented: the host bundle stands in for Node's * compiled-in Mozilla roots ('bundled' and rootCertificates) AND for the - * platform store ('system') — Windows' logical ROOT store and the POSIX + * platform store ('system') — Windows' system ROOT stores and the POSIX * bundle probe are the same sources scr_tls.c's anchors use; and set-time * validation is PEM-BLOCK shaped (a well-formed block with corrupt DER * inside is kept here where Node's X509 parse would drop it — such a cert @@ -191,11 +191,11 @@ bool scr_tls_ca_windows_cert_server_auth(const void *cert_context) { return allowed; } -void scr_tls_ca_windows_roots(ScrTlsCaWindowsRootFn fn, void *ctx) { +static void scr_tls_ca_windows_roots_at( + DWORD location, ScrTlsCaWindowsRootFn fn, void *ctx) { HCERTSTORE store = CertOpenStore( CERT_STORE_PROV_SYSTEM_A, 0, 0, - CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_OPEN_EXISTING_FLAG | - CERT_STORE_READONLY_FLAG, + location | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG, "ROOT"); if (store == NULL) return; PCCERT_CONTEXT cert = NULL; @@ -209,6 +209,24 @@ void scr_tls_ca_windows_roots(ScrTlsCaWindowsRootFn fn, void *ctx) { (void)CertCloseStore(store, 0); } +void scr_tls_ca_windows_roots(ScrTlsCaWindowsRootFn fn, void *ctx) { + /* Windows keeps policy and enterprise roots in distinct store locations; + * the ordinary current-user ROOT collection does not include the + * current-user Group Policy store. Match Node's ROOT-location coverage so + * organization-managed roots reach both TLS verification and CA-store + * introspection. Duplicate entries are harmless and match Node's gather. */ + static const DWORD locations[] = { + CERT_SYSTEM_STORE_LOCAL_MACHINE, + CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, + CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, + CERT_SYSTEM_STORE_CURRENT_USER, + CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY, + }; + for (size_t i = 0; i < sizeof locations / sizeof locations[0]; i++) { + scr_tls_ca_windows_roots_at(locations[i], fn, ctx); + } +} + static void scr_ca_push_windows_root(void *ctx, const unsigned char *der, size_t len) { if (len > UINT32_MAX) return; DWORD pem_cap = 0;