package ca import ( "fmt" "os/exec" "path/filepath" "strings" ) // commandExists reports whether name is on PATH. Var, not a plain func // call, so tests can substitute a fake lookup without touching the real // PATH - trust-store tooling varies enough across distros that testing // the actual detection logic (which command wins, in what order) matters // more than testing against whatever happens to be installed on the // machine running `go test`. var commandExists = func(name string) bool { _, err := exec.LookPath(name) return err == nil } // InstallInstructions returns copy-pasteable, OS-specific steps for // trusting caPath as a root CA. It only ever prints commands - it never // runs anything itself. Installing a root CA is a genuinely sensitive, // system-wide trust change (and system trust-store tooling varies enough // across distros that guessing wrong and auto-running the wrong command // is worse than asking); the user running the printed command themselves // keeps them in control of a change that affects every TLS connection on // the machine, not just mitmux's own traffic. func InstallInstructions(goos, caPath string) string { switch goos { case "linux": return linuxInstructions(caPath) case "darwin": return darwinInstructions(caPath) case "windows": return windowsInstructions(caPath) default: return genericInstructions(caPath) } } func linuxInstructions(caPath string) string { var b strings.Builder fmt.Fprintf(&b, "System trust store (curl, most CLI tools, Chrome/Chromium):\n") switch { case commandExists("trust"): fmt.Fprintf(&b, " sudo trust anchor --store %s\n", caPath) case commandExists("update-ca-trust"): fmt.Fprintf(&b, " sudo cp %s /etc/pki/ca-trust/source/anchors/mitmux-ca.pem\n", caPath) fmt.Fprintf(&b, " sudo update-ca-trust\n") case commandExists("update-ca-certificates"): fmt.Fprintf(&b, " sudo cp %s /usr/local/share/ca-certificates/mitmux-ca.crt\n", caPath) fmt.Fprintf(&b, " sudo update-ca-certificates\n") default: fmt.Fprintf(&b, " No known trust-store tool (trust / update-ca-trust /\n") fmt.Fprintf(&b, " update-ca-certificates) found on PATH. Check your distro's\n") fmt.Fprintf(&b, " docs for how it manages /etc/ssl/certs.\n") } fmt.Fprintf(&b, "\nFirefox (and Chrome/Chromium's own NSS store, which doesn't\n") fmt.Fprintf(&b, "always follow the system trust store on Linux):\n") if commandExists("certutil") { fmt.Fprintf(&b, " certutil -d sql:$HOME/.mozilla/firefox/ -A -n mitmux -t \"C,,\" -i %s\n", caPath) fmt.Fprintf(&b, " (find with: ls ~/.mozilla/firefox | grep default)\n") } else { fmt.Fprintf(&b, " Import manually: Settings -> Privacy & Security -> Certificates\n") fmt.Fprintf(&b, " -> View Certificates -> Authorities -> Import, select %s\n", caPath) fmt.Fprintf(&b, " (or install nss-tools/libnss3-tools for certutil, which can\n") fmt.Fprintf(&b, " script this instead)\n") } return b.String() } func darwinInstructions(caPath string) string { var b strings.Builder fmt.Fprintf(&b, "System-wide (Keychain Access -> System, or via Terminal):\n") fmt.Fprintf(&b, " sudo security add-trusted-cert -d -r trustRoot \\\n") fmt.Fprintf(&b, " -k /Library/Keychains/System.keychain %s\n", caPath) fmt.Fprintf(&b, "\nCurrent user only (no sudo, login keychain):\n") fmt.Fprintf(&b, " security add-trusted-cert -d -r trustRoot \\\n") fmt.Fprintf(&b, " -k ~/Library/Keychains/login.keychain-db %s\n", caPath) fmt.Fprintf(&b, "\nFirefox uses its own certificate store, not the macOS Keychain -\n") fmt.Fprintf(&b, "import %s manually via Settings -> Privacy & Security ->\n", caPath) fmt.Fprintf(&b, "Certificates -> View Certificates -> Authorities -> Import.\n") return b.String() } func windowsInstructions(caPath string) string { var b strings.Builder fmt.Fprintf(&b, "From an elevated (Administrator) command prompt:\n") fmt.Fprintf(&b, " certutil -addstore -f \"ROOT\" %s\n", caPath) fmt.Fprintf(&b, "\nOr from an elevated PowerShell:\n") fmt.Fprintf(&b, " Import-Certificate -FilePath %s -CertStoreLocation Cert:\\LocalMachine\\Root\n", caPath) fmt.Fprintf(&b, "\nFirefox uses its own certificate store, not the Windows store -\n") fmt.Fprintf(&b, "import %s manually via Settings -> Privacy & Security ->\n", caPath) fmt.Fprintf(&b, "Certificates -> View Certificates -> Authorities -> Import.\n") return b.String() } func genericInstructions(caPath string) string { return fmt.Sprintf("No install steps known for this OS - import %s into your\n"+ "client's trust store manually (browser certificate settings, or\n"+ "whatever --cacert / equivalent flag your TLS client offers).\n", caPath) } // CertPath returns the path to the CA certificate PEM file inside dir // (see EnsureCA), for callers that just need to point a user or a tool // at it. func CertPath(dir string) string { return filepath.Join(dir, certFileName) }