Files
OmniRoute/tests/unit/mitm-cert-migration-6684.test.ts
Diego Rodrigues de Sa e Souza a2004060c5 feat(mitm): root-CA + per-host leaf certs for AgentBridge static server (#6684) (#7731)
Replace the AgentBridge static server's single self-signed leaf cert
(scoped only to the 4 antigravity hosts) with a persisted local root CA
+ per-SNI leaf certs, reusing the CA/leaf crypto already proven for the
TPROXY capture mode (tproxy/dynamicCert.ts). server.cjs switches from a
static key/cert to an SNICallback so every host in MITM_TOOL_HOSTS gets
a matching leaf, not just antigravity.

- src/mitm/cert/rootCa.ts: load-or-generate-once CA persistence
  (ca.key/ca.crt under <DATA_DIR>/mitm/), private key chmod 0o600.
- src/mitm/cert/migration.ts: pure migration gate — an already-trusted
  legacy leaf install stays on the old leaf until the operator opts in
  via MITM_ROOT_CA_ENABLED=true; a fresh install gets the CA model
  automatically. A CA that can sign a leaf for any host is materially
  more powerful than the old fixed-SAN leaf, so the switch is never
  silent for an already-trusted install.
- src/mitm/cert/install.ts: installCaCert() — thin wrapper over the
  existing cert-path-agnostic installCertResult(), same
  omniroute-mitm.crt trust-store slot the old leaf used (supersedes it,
  no dual-trust cleanup needed).
- src/mitm/manager.ts: wires the migration gate + CA load/install into
  the bridge-start sequence, passes the resolved MITM_CERT_MODE to the
  spawned server.cjs child so it can't drift from manager.ts's decision.
- src/mitm/server.cjs: async-bootstraps server creation behind the same
  MITM_CERT_MODE gate; default ("legacy") reproduces the exact prior
  synchronous behavior. The CJS/ESM boundary (server.cjs is spawned via
  plain `node`, no TS loader) is crossed via a new
  _internal/rootCaShim.cjs CJS twin of the CA/leaf crypto, matching the
  established pattern of the sibling _internal/*.cjs shims in this file.

Validated: 14 new unit tests (CA generate-once, 0o600 key perms, CA
basicConstraints, leaf issuance across every MITM_TOOL_HOSTS host, SAN
match, chain validation against the CA, leaf caching, migration-gate
branches) plus a manual live smoke test spawning server.cjs in both
legacy and root-ca mode (confirmed a real TLS handshake with SNI
api.githubcopilot.com returns a CA-issued leaf for that host).

Deferred to VPS live validation (OS-trust-store mutation is not
unit-testable): actual OS trust-store install of the CA cert via
installCaCert() on Linux/macOS/Windows.
2026-07-19 14:37:27 -03:00

75 lines
2.5 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { decideCertMigration } from "../../src/mitm/cert/migration.ts";
// #6684: migration gate between the legacy single self-signed leaf and the
// new persisted root-CA model. A trusted MITM CA that can sign a cert for
// ANY host is materially more powerful than the old fixed-SAN leaf, so an
// already-trusted install must never be silently upgraded.
function tmpCertDir(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), "mitm-migration-test-"));
}
function touch(filePath: string): void {
fs.writeFileSync(filePath, "test");
}
test("decideCertMigration: existing legacy leaf, no CA pair, flag off → stay on legacy leaf", () => {
const certDir = tmpCertDir();
try {
touch(path.join(certDir, "server.crt"));
touch(path.join(certDir, "server.key"));
assert.equal(decideCertMigration(certDir, false), "use-legacy-leaf");
} finally {
fs.rmSync(certDir, { recursive: true, force: true });
}
});
test("decideCertMigration: no legacy leaf and no CA pair (fresh install) → use root CA", () => {
const certDir = tmpCertDir();
try {
assert.equal(decideCertMigration(certDir, false), "use-root-ca");
} finally {
fs.rmSync(certDir, { recursive: true, force: true });
}
});
test("decideCertMigration: legacy leaf present but explicit opt-in flag on → use root CA", () => {
const certDir = tmpCertDir();
try {
touch(path.join(certDir, "server.crt"));
touch(path.join(certDir, "server.key"));
assert.equal(decideCertMigration(certDir, true), "use-root-ca");
} finally {
fs.rmSync(certDir, { recursive: true, force: true });
}
});
test("decideCertMigration: CA pair already persisted → use root CA even without the flag", () => {
const certDir = tmpCertDir();
try {
touch(path.join(certDir, "server.crt"));
touch(path.join(certDir, "server.key"));
touch(path.join(certDir, "ca.crt"));
touch(path.join(certDir, "ca.key"));
assert.equal(decideCertMigration(certDir, false), "use-root-ca");
} finally {
fs.rmSync(certDir, { recursive: true, force: true });
}
});
test("decideCertMigration: partial legacy pair (only server.crt) is treated as no legacy install", () => {
const certDir = tmpCertDir();
try {
touch(path.join(certDir, "server.crt"));
assert.equal(decideCertMigration(certDir, false), "use-root-ca");
} finally {
fs.rmSync(certDir, { recursive: true, force: true });
}
});