Files

148 lines
4.2 KiB
JavaScript

import { spawn, spawnSync } from "node:child_process";
import { createServer } from "node:https";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const tmp = mkdtempSync(join(tmpdir(), "openwork-custom-ca-"));
const caKey = join(tmp, "ca.key");
const caCert = join(tmp, "ca.crt");
const serverKey = join(tmp, "server.key");
const serverCsr = join(tmp, "server.csr");
const serverCert = join(tmp, "server.crt");
const opensslConfig = join(tmp, "openssl.cnf");
function runOpenSsl(args) {
const result = spawnSync("openssl", args, { encoding: "utf8" });
if (result.status !== 0) {
throw new Error(`openssl ${args.join(" ")} failed\n${result.stderr}`);
}
}
function clientEnvironment(caPath) {
const env = { ...process.env };
delete env.NODE_EXTRA_CA_CERTS;
delete env.NODE_TLS_REJECT_UNAUTHORIZED;
if (caPath) {
env.NODE_EXTRA_CA_CERTS = caPath;
}
return env;
}
function runClient(url, caPath) {
const clientSource = `
const https = require("node:https");
https.get(process.argv[1], (response) => {
let body = "";
response.setEncoding("utf8");
response.on("data", (chunk) => { body += chunk; });
response.on("end", () => {
if (response.statusCode === 200 && body === "openwork-custom-ca-ok") {
console.log(body);
process.exit(0);
}
console.error(\`unexpected response: \${response.statusCode} \${body}\`);
process.exit(1);
});
}).on("error", (error) => {
console.error(error.code || error.message);
process.exit(2);
});
`;
return new Promise((resolve) => {
const child = spawn(process.execPath, ["-e", clientSource, url], {
env: clientEnvironment(caPath),
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.on("close", (status) => {
resolve({ status, stdout, stderr });
});
});
}
function listen(server) {
return new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", () => {
server.off("error", reject);
resolve();
});
});
}
function close(server) {
return new Promise((resolve, reject) => {
server.close((error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
try {
writeFileSync(
opensslConfig,
`[req]
distinguished_name = dn
prompt = no
[dn]
CN = localhost
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
IP.1 = 127.0.0.1
`,
);
runOpenSsl(["req", "-x509", "-newkey", "rsa:2048", "-nodes", "-days", "1", "-subj", "/CN=OpenWork Test CA", "-keyout", caKey, "-out", caCert]);
runOpenSsl(["req", "-newkey", "rsa:2048", "-nodes", "-keyout", serverKey, "-out", serverCsr, "-config", opensslConfig]);
runOpenSsl(["x509", "-req", "-in", serverCsr, "-CA", caCert, "-CAkey", caKey, "-CAcreateserial", "-out", serverCert, "-days", "1", "-extensions", "v3_req", "-extfile", opensslConfig]);
const server = createServer(
{
key: readFileSync(serverKey),
cert: readFileSync(serverCert),
},
(_request, response) => {
response.end("openwork-custom-ca-ok");
},
);
await listen(server);
const address = server.address();
const url = `https://127.0.0.1:${address.port}/`;
try {
const withoutCustomCa = await runClient(url);
if (withoutCustomCa.status === 0) {
throw new Error("Strict TLS unexpectedly trusted the private CA without NODE_EXTRA_CA_CERTS");
}
const withCustomCa = await runClient(url, caCert);
if (withCustomCa.status !== 0) {
throw new Error(`Strict TLS did not trust NODE_EXTRA_CA_CERTS=${caCert}\n${withCustomCa.stderr}`);
}
console.log(`Strict TLS without NODE_EXTRA_CA_CERTS: rejected (${withoutCustomCa.stderr.trim()})`);
console.log(`Strict TLS with NODE_EXTRA_CA_CERTS=${caCert}: accepted (${withCustomCa.stdout.trim()})`);
console.log("Focused runtime trust check passed; this verifies Node's strict TLS trust path without starting MySQL.");
} finally {
await close(server);
}
} finally {
rmSync(tmp, { recursive: true, force: true });
}