mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 22:32:12 +03:00
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.
75 lines
2.5 KiB
TypeScript
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 });
|
|
}
|
|
});
|