mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
feat(mitm): implement dynamic linux cert resolution and NSS db injection in TS
- Replaced hardcoded LINUX_CA_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.
This commit is contained in:
@@ -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<void> {
|
||||
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<boolean> {
|
||||
|
||||
async function checkCertInstalledLinux(certPath: string): Promise<boolean> {
|
||||
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<v
|
||||
|
||||
async function installCertLinux(sudoPassword: string, certPath: string): Promise<void> {
|
||||
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<void> {
|
||||
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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user