diff --git a/src/mitm/cert/generate.js b/src/mitm/cert/generate.js index 4fcc2de4..e85f9dc6 100644 --- a/src/mitm/cert/generate.js +++ b/src/mitm/cert/generate.js @@ -7,8 +7,8 @@ const { generateRootCA, loadRootCA, generateLeafCert } = require("./rootCA"); * Generate Root CA certificate (one-time setup) * This replaces the old static wildcard cert approach */ -async function generateCert() { - return await generateRootCA(); +function generateCert() { + return generateRootCA(); } /** diff --git a/src/mitm/cert/rootCA.js b/src/mitm/cert/rootCA.js index d5e73d89..3f033f0a 100644 --- a/src/mitm/cert/rootCA.js +++ b/src/mitm/cert/rootCA.js @@ -23,7 +23,7 @@ function isCertExpired(certPath) { * Generate Root CA certificate (only once, auto-regenerate if expired) * This Root CA will sign all dynamic leaf certificates */ -async function generateRootCA() { +function generateRootCA() { const exists = fs.existsSync(ROOT_CA_KEY_PATH) && fs.existsSync(ROOT_CA_CERT_PATH); if (exists && !isCertExpired(ROOT_CA_CERT_PATH)) { console.log("✅ Root CA already exists"); diff --git a/src/mitm/server.js b/src/mitm/server.js index 2c5c876a..d675e6a3 100644 --- a/src/mitm/server.js +++ b/src/mitm/server.js @@ -9,7 +9,7 @@ const { execSync } = require("child_process"); const { log, err, dumpRequest, createResponseDumper, clearDumpDir } = require("./logger"); const { IS_DEV, LSOF_BIN, TARGET_HOSTS, URL_PATTERNS, MODEL_SYNONYMS, MODEL_PATTERNS, MODEL_NO_MAP, getToolForHost } = require("./config"); const { DATA_DIR, MITM_DIR } = require("./paths"); -const { getCertForDomain } = require("./cert/generate"); +const { generateCert, getCertForDomain } = require("./cert/generate"); const { getMitmAlias } = require("./dbReader"); const { applyAntigravityIdeVersionOverride } = require("./antigravityIdeVersion"); const LOCAL_PORT = 443; @@ -57,6 +57,11 @@ function sniCallback(servername, cb) { let sslOptions; try { + if (!fs.existsSync(path.join(MITM_DIR, "rootCA.key")) || !fs.existsSync(path.join(MITM_DIR, "rootCA.crt"))) { + log("Root CA missing, generating..."); + generateCert(); + } + const rootKey = fs.readFileSync(path.join(MITM_DIR, "rootCA.key")); const rootCert = fs.readFileSync(path.join(MITM_DIR, "rootCA.crt")); rootCAPem = rootCert.toString("utf8"); diff --git a/tests/unit/mitm-root-ca.test.js b/tests/unit/mitm-root-ca.test.js new file mode 100644 index 00000000..f3e3f0a4 --- /dev/null +++ b/tests/unit/mitm-root-ca.test.js @@ -0,0 +1,35 @@ +import { describe, expect, it } from "vitest"; +import { createRequire } from "module"; +import fs from "fs"; +import os from "os"; +import path from "path"; + +const require = createRequire(import.meta.url); + +function loadRootCAWithDataDir(dataDir) { + const rootCAPath = require.resolve("../../src/mitm/cert/rootCA.js"); + const pathsPath = require.resolve("../../src/mitm/paths.js"); + delete require.cache[rootCAPath]; + delete require.cache[pathsPath]; + + const oldDataDir = process.env.DATA_DIR; + process.env.DATA_DIR = dataDir; + try { + return require("../../src/mitm/cert/rootCA.js"); + } finally { + if (oldDataDir === undefined) delete process.env.DATA_DIR; + else process.env.DATA_DIR = oldDataDir; + } +} + +describe("MITM Root CA generation", () => { + it("creates Root CA files synchronously for direct server startup", () => { + const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "9router-mitm-ca-")); + const { generateRootCA } = loadRootCAWithDataDir(dataDir); + + generateRootCA(); + + expect(fs.existsSync(path.join(dataDir, "mitm", "rootCA.key"))).toBe(true); + expect(fs.existsSync(path.join(dataDir, "mitm", "rootCA.crt"))).toBe(true); + }); +});