srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/internal/ca/install.go
blob: bafcea4c23d73bc2f6c270d4f6eef69e94dab080 (plain) (blame)
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)
}