diff --git a/src/mitm/cert/install.ts b/src/mitm/cert/install.ts index 4f6a100aa1..d2e422a264 100644 --- a/src/mitm/cert/install.ts +++ b/src/mitm/cert/install.ts @@ -1,5 +1,6 @@ import fs from "fs"; import crypto from "crypto"; +import { exec } from "child_process"; import { execFileText, execFileWithPassword, @@ -12,8 +13,78 @@ const IS_WIN = process.platform === "win32"; const IS_MAC = process.platform === "darwin"; const LINUX_CERT_NAME = "omniroute-mitm.crt"; -const LINUX_CA_DIR = "/usr/local/share/ca-certificates"; -const LINUX_CERT_DEST = `${LINUX_CA_DIR}/${LINUX_CERT_NAME}`; + +interface LinuxCertConfig { + dir: string; + cmd: string; +} + +const LINUX_CERT_PATHS: LinuxCertConfig[] = [ + // Debian / Ubuntu + { dir: "/usr/local/share/ca-certificates", cmd: "update-ca-certificates" }, + // Arch Linux / CachyOS / Manjaro + { dir: "/etc/ca-certificates/trust-source/anchors", cmd: "update-ca-trust" }, + // Fedora / RHEL / CentOS + { dir: "/etc/pki/ca-trust/source/anchors", cmd: "update-ca-trust" }, + // openSUSE + { dir: "/etc/pki/trust/anchors", cmd: "update-ca-certificates" }, +]; + +function getLinuxCertConfig(): LinuxCertConfig { + for (const config of LINUX_CERT_PATHS) { + if (fs.existsSync(config.dir)) { + return config; + } + } + return LINUX_CERT_PATHS[0]; +} + +async function updateNssDatabases( + certPath: string | null, + action: "add" | "delete" = "add" +): Promise { + const certName = "OmniRoute MITM Root CA"; + + const script = ` + if ! command -v certutil &> /dev/null; then + exit 0 + fi + + DIRS="$HOME/.pki/nssdb $HOME/snap/chromium/current/.pki/nssdb" + + if [ -d "$HOME/.mozilla/firefox" ]; then + for profile in "$HOME"/.mozilla/firefox/*/; do + if [ -f "\${profile}cert9.db" ] || [ -f "\${profile}cert8.db" ]; then + DIRS="$DIRS $profile" + fi + done + fi + + if [ -d "$HOME/snap/firefox/common/.mozilla/firefox" ]; then + for profile in "$HOME"/snap/firefox/common/.mozilla/firefox/*/; do + if [ -f "\${profile}cert9.db" ] || [ -f "\${profile}cert8.db" ]; then + DIRS="$DIRS $profile" + fi + done + fi + + for db in $DIRS; do + if [ -d "$db" ]; then + if [ "${action}" = "add" ]; then + certutil -d sql:"$db" -A -t "C,," -n "${certName}" -i "${certPath}" 2>/dev/null || \\ + certutil -d "$db" -A -t "C,," -n "${certName}" -i "${certPath}" 2>/dev/null || true + else + certutil -d sql:"$db" -D -n "${certName}" 2>/dev/null || \\ + certutil -d "$db" -D -n "${certName}" 2>/dev/null || true + fi + fi + done + `; + + return new Promise((resolve) => { + exec(script, { shell: "/bin/bash" }, () => resolve()); + }); +} // Get SHA1 fingerprint from cert file using Node.js crypto function getCertFingerprint(certPath: string): string { @@ -52,8 +123,10 @@ async function checkCertInstalledMac(certPath: string): Promise { async function checkCertInstalledLinux(certPath: string): Promise { try { - if (!fs.existsSync(LINUX_CERT_DEST)) return false; - return getCertFingerprint(certPath) === getCertFingerprint(LINUX_CERT_DEST); + const config = getLinuxCertConfig(); + const destFile = `${config.dir}/${LINUX_CERT_NAME}`; + if (!fs.existsSync(destFile)) return false; + return getCertFingerprint(certPath) === getCertFingerprint(destFile); } catch { return false; } @@ -120,9 +193,14 @@ async function installCertMac(sudoPassword: string, certPath: string): Promise { try { - await execFileWithPassword("sudo", ["-S", "mkdir", "-p", LINUX_CA_DIR], sudoPassword); - await execFileWithPassword("sudo", ["-S", "cp", certPath, LINUX_CERT_DEST], sudoPassword); - await execFileWithPassword("sudo", ["-S", "update-ca-certificates"], sudoPassword); + const config = getLinuxCertConfig(); + const destFile = `${config.dir}/${LINUX_CERT_NAME}`; + + await execFileWithPassword("sudo", ["-S", "mkdir", "-p", config.dir], sudoPassword); + await execFileWithPassword("sudo", ["-S", "cp", certPath, destFile], sudoPassword); + await execFileWithPassword("sudo", ["-S", config.cmd], sudoPassword); + + await updateNssDatabases(certPath, "add"); } catch (error) { const message = getErrorMessage(error); const msg = message.includes("canceled") @@ -183,10 +261,20 @@ async function uninstallCertMac(sudoPassword: string, certPath: string): Promise async function uninstallCertLinux(sudoPassword: string, certPath: string): Promise { try { - if (fs.existsSync(LINUX_CERT_DEST)) { - await execFileWithPassword("sudo", ["-S", "rm", "-f", LINUX_CERT_DEST], sudoPassword); + await updateNssDatabases(null, "delete"); + + const config = getLinuxCertConfig(); + const destFile = `${config.dir}/${LINUX_CERT_NAME}`; + + if (fs.existsSync(destFile)) { + await execFileWithPassword("sudo", ["-S", "rm", "-f", destFile], sudoPassword); + } + + try { + await execFileWithPassword("sudo", ["-S", config.cmd, "--fresh"], sudoPassword); + } catch { + await execFileWithPassword("sudo", ["-S", config.cmd], sudoPassword); } - await execFileWithPassword("sudo", ["-S", "update-ca-certificates", "--fresh"], sudoPassword); } catch (err) { throw new Error("Failed to uninstall certificate"); }