From 76f3d4b74e79c0e9c3a7cfcbadfee016fbd66b9b Mon Sep 17 00:00:00 2001 From: FlyingMongoose <399379+flyingmongoose@users.noreply.github.com> Date: Mon, 11 May 2026 05:05:34 -0400 Subject: [PATCH] feat(mitm): implement dynamic linux cert resolution and NSS db injection (#1010) - Replaced hardcoded LINUX_CERT_DIR with dynamic filesystem probing to support Debian, Arch, Fedora, and openSUSE system trust stores. - Added updateNssDatabases helper to seamlessly inject root certificates directly into browser NSS databases (e.g., ~/.pki/nssdb, ~/.mozilla/firefox). - Supported standard and snap-based Chrome/Chromium and Firefox installations. - Made browser cert injection resilient, executing under the current user to prevent file ownership issues, and safely falling back if certutil is absent. --- src/mitm/cert/install.js | 97 +++++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 10 deletions(-) diff --git a/src/mitm/cert/install.js b/src/mitm/cert/install.js index bf986e0..7c8fe61 100644 --- a/src/mitm/cert/install.js +++ b/src/mitm/cert/install.js @@ -7,7 +7,26 @@ const { log, err } = require("../logger"); const IS_WIN = process.platform === "win32"; const IS_MAC = process.platform === "darwin"; -const LINUX_CERT_DIR = "/usr/local/share/ca-certificates"; +const LINUX_CERT_PATHS = [ + // 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() { + for (const config of LINUX_CERT_PATHS) { + if (fs.existsSync(config.dir)) { + return config; + } + } + // Fallback to Debian default if none exist + return LINUX_CERT_PATHS[0]; +} const ROOT_CA_CN = "9Router MITM Root CA"; // Get SHA1 fingerprint from cert file using Node.js crypto @@ -153,35 +172,93 @@ async function uninstallCertWindows() { } function checkCertInstalledLinux() { - const certFile = `${LINUX_CERT_DIR}/9router-root-ca.crt`; + const config = getLinuxCertConfig(); + const certFile = `${config.dir}/9router-root-ca.crt`; return Promise.resolve(fs.existsSync(certFile)); } +async function updateNssDatabases(certPath, action = 'add') { + const certName = "9Router 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()); + }); +} + async function installCertLinux(sudoPassword, certPath) { if (!isSudoAvailable()) { log(`🔐 Cert: cannot install to system store without sudo — trust this file on clients: ${certPath}`); + // Still try to update user NSS DBs even if no sudo! + await updateNssDatabases(certPath, 'add'); return; } - const destFile = `${LINUX_CERT_DIR}/9router-root-ca.crt`; - // Try update-ca-certificates (Debian/Ubuntu), fallback to update-ca-trust (Fedora/RHEL) - const cmd = `cp "${certPath}" "${destFile}" && (update-ca-certificates 2>/dev/null || update-ca-trust 2>/dev/null || true)`; + + const config = getLinuxCertConfig(); + const destFile = `${config.dir}/9router-root-ca.crt`; + + // Copy to the discovered directory and execute the specific update command + const cmd = `cp "${certPath}" "${destFile}" && (${config.cmd} 2>/dev/null || true)`; + try { await execWithPassword(cmd, sudoPassword); - log("🔐 Cert: ✅ installed to Linux trust store"); + await updateNssDatabases(certPath, 'add'); + log(`🔐 Cert: ✅ installed to Linux trust store (${config.dir}) and user browser databases`); } catch (error) { - throw new Error("Certificate install failed"); + throw new Error(`Certificate install failed: ${error.message}`); } } async function uninstallCertLinux(sudoPassword) { + // Always try to uninstall from user DBs even without sudo + await updateNssDatabases(null, 'delete'); + if (!isSudoAvailable()) { return; } - const destFile = `${LINUX_CERT_DIR}/9router-root-ca.crt`; - const cmd = `rm -f "${destFile}" && (update-ca-certificates 2>/dev/null || update-ca-trust 2>/dev/null || true)`; + + const config = getLinuxCertConfig(); + const destFile = `${config.dir}/9router-root-ca.crt`; + const cmd = `rm -f "${destFile}" && (${config.cmd} 2>/dev/null || true)`; + try { await execWithPassword(cmd, sudoPassword); - log("🔐 Cert: ✅ uninstalled from Linux trust store"); + log("🔐 Cert: ✅ uninstalled from Linux trust store and user browser databases"); } catch (error) { throw new Error("Failed to uninstall certificate"); }