From 74c8b85becad15f112ae5a7438784f0422e3a8e5 Mon Sep 17 00:00:00 2001 From: MrBBot Date: Fri, 26 May 2023 10:45:42 +0100 Subject: [PATCH] Trust CA root certificates on Windows and `NODE_EXTRA_CA_CERTS` (#587) * Use Node's root certificates on Windows `workerd`'s `trustBrowserCas` uses `SSL_CTX_set_default_verify_paths()` to enable the system trust store. Unfortunately, this doesn't work on Windows, meaning any HTTPS `fetch()` would fail, with an `unable to get local issuer certificate` error. This change passes the root certificates from Node's bundled CA store to `workerd` as `trustedCertificates` on Windows. Closes cloudflare/workers-sdk#3264 * Read extra trusted certificates from `NODE_EXTRA_CA_CERTS` Wrangler passes the Cloudflare root certificate using the `NODE_EXTRA_CA_CERTS` environment variable. This change loads CA certs from this variable, fixing HTTPS `fetch()`s with WARP enabled. This can also be used for trusting self-signed certificates. Closes cloudflare/workers-sdk#3218 --- packages/miniflare/src/plugins/core/index.ts | 29 ++++++++++++++++++-- packages/miniflare/test/index.spec.ts | 13 +++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/miniflare/src/plugins/core/index.ts b/packages/miniflare/src/plugins/core/index.ts index 0637cf2c6f6b..e67948808041 100644 --- a/packages/miniflare/src/plugins/core/index.ts +++ b/packages/miniflare/src/plugins/core/index.ts @@ -1,6 +1,7 @@ import assert from "assert"; import { readFileSync } from "fs"; import fs from "fs/promises"; +import tls from "tls"; import { TextEncoder } from "util"; import { bold } from "kleur/colors"; import SCRIPT_ENTRY from "worker:core/entry"; @@ -44,6 +45,25 @@ import { } from "./modules"; import { ServiceDesignatorSchema } from "./services"; +// `workerd`'s `trustBrowserCas` should probably be named `trustSystemCas`. +// Rather than using a bundled CA store like Node, it uses +// `SSL_CTX_set_default_verify_paths()` to use the system CA store: +// https://github.com/capnproto/capnproto/blob/6e26d260d1d91e0465ca12bbb5230a1dfa28f00d/c%2B%2B/src/kj/compat/tls.c%2B%2B#L745 +// Unfortunately, this doesn't work on Windows. Luckily, Node exposes its own +// bundled CA store's certificates, so we just use those. +const trustedCertificates = + process.platform === "win32" ? Array.from(tls.rootCertificates) : []; +if (process.env.NODE_EXTRA_CA_CERTS !== undefined) { + // Try load extra CA certs if defined, ignoring errors. Node will log a + // warning if it fails to load this anyway. Note, this we only load this once + // at process startup to match Node's behaviour: + // https://nodejs.org/api/cli.html#node_extra_ca_certsfile + try { + const extra = readFileSync(process.env.NODE_EXTRA_CA_CERTS, "utf8"); + trustedCertificates.push(extra); + } catch {} +} + const encoder = new TextEncoder(); const numericCompare = new Intl.Collator(undefined, { numeric: true }).compare; @@ -360,14 +380,17 @@ export function getGlobalServices({ bindings: serviceEntryBindings, }, }, - // Allow access to private/public addresses: - // https://github.com/cloudflare/miniflare/issues/412 { name: "internet", network: { + // Allow access to private/public addresses: + // https://github.com/cloudflare/miniflare/issues/412 allow: ["public", "private"], deny: [], - tlsOptions: { trustBrowserCas: true }, + tlsOptions: { + trustBrowserCas: true, + trustedCertificates, + }, }, }, ]; diff --git a/packages/miniflare/test/index.spec.ts b/packages/miniflare/test/index.spec.ts index 621dc589e0b9..b3c60064e5b1 100644 --- a/packages/miniflare/test/index.spec.ts +++ b/packages/miniflare/test/index.spec.ts @@ -294,3 +294,16 @@ test("Miniflare: modules in sub-directories", async (t) => { const res = await mf.dispatchFetch("http://localhost"); t.is(await res.text(), "123"); }); + +test("Miniflare: HTTPS fetches using browser CA certificates", async (t) => { + const mf = new Miniflare({ + modules: true, + script: `export default { + fetch() { + return fetch("https://workers.cloudflare.com/cf.json"); + } + }`, + }); + const res = await mf.dispatchFetch("http://localhost"); + t.true(res.ok); +});