148 lines
4.2 KiB
JavaScript
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 });
|
|
}
|