1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
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/<your-profile> -A -n mitmux -t \"C,,\" -i %s\n", caPath)
fmt.Fprintf(&b, " (find <your-profile> 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)
}
|