diff --git a/README.md b/README.md index 4ce4663..9aabb6c 100644 --- a/README.md +++ b/README.md @@ -33,16 +33,17 @@ On macOS, use Homebrew. ``` brew install --HEAD https://github.com/FiloSottile/mkcert/raw/master/HomebrewFormula/mkcert.rb +brew install nss # if you use Firefox ``` -On Linux (install support coming soon!), use [the pre-built binaries](https://github.com/FiloSottile/mkcert/releases), or build from source. +On Linux (`-install` support coming soon!), use [the pre-built binaries (again, coming soon)](https://github.com/FiloSottile/mkcert/releases), or build from source. ``` $ git clone https://github.com/FiloSottile/mkcert $ cd mkcert && make ``` -Windows will be supported soon. +Windows will be supported next. ## Advanced topics diff --git a/cert.go b/cert.go index 44c06b0..d8ef898 100644 --- a/cert.go +++ b/cert.go @@ -150,3 +150,7 @@ func (m *mkcert) newCA() { log.Printf("Created a new local CA at \"%s\" 💥\n", m.CAROOT) } + +func (m *mkcert) caUniqueName() string { + return "mkcert development CA " + m.caCert.SerialNumber.String() +} diff --git a/main.go b/main.go index a82543c..b6d3dc1 100644 --- a/main.go +++ b/main.go @@ -62,9 +62,19 @@ func (m *mkcert) Run(args []string) { } else if m.uninstallMode { m.uninstall() return - } else if !m.check() { - log.Println("Warning: the local CA is not installed in the system trust store! ⚠️") - log.Println("Run \"mkcert -install\" to avoid verification errors ‼️") + } else { + var warning bool + if !m.checkPlatform() { + warning = true + log.Println("Warning: the local CA is not installed in the system trust store! ⚠️") + } + if hasFirefox && !m.checkFirefox() { + warning = true + log.Println("Warning: the local CA is not installed in the Firefox trust store! ⚠️") + } + if warning { + log.Println("Run \"mkcert -install\" to avoid verification errors ‼️") + } } if len(args) == 0 { @@ -134,26 +144,45 @@ func getCAROOT() string { } func (m *mkcert) install() { - if m.check() { - return + var printed bool + if !m.checkPlatform() { + m.installPlatform() + m.ignoreCheckFailure = true // TODO: replace with a check for a successful install + log.Print("The local CA is now installed in the system trust store! ⚡️") + printed = true } - - m.installPlatform() - m.ignoreCheckFailure = true - - if m.check() { // useless, see comment on ignoreCheckFailure - log.Print("The local CA is now installed in the system trust store! ⚡️\n\n") - } else { - log.Fatal("Installing failed. Please report the issue with details about your environment at https://github.com/FiloSottile/mkcert/issues/new 👎\n\n") + if hasFirefox && !m.checkFirefox() { + if hasCertutil { + m.installFirefox() + log.Print("The local CA is now installed in the Firefox trust store (requires restart)! 🦊") + } else { + log.Println(`Warning: "certutil" is not available, so the CA can't be automatically installed in Firefox! ⚠️`) + log.Printf(`Install "certutil" with "%s" and re-run "mkcert -install" 👈`, CertutilInstallHelp) + } + printed = true + } + if printed { + log.Print("") } } func (m *mkcert) uninstall() { m.uninstallPlatform() - log.Print("The local CA is now uninstalled from the system trust store! 👋\n\n") + if hasFirefox { + if hasCertutil { + m.uninstallFirefox() + } else { + log.Print("") + log.Println(`Warning: "certutil" is not available, so the CA can't be automatically uninstalled from Firefox (if it was ever installed)! ⚠️`) + log.Printf(`You can install "certutil" with "%s" and re-run "mkcert -uninstall" 👈`, CertutilInstallHelp) + log.Print("") + } + } + log.Print("The local CA is now uninstalled from the system trust store(s)! 👋") + log.Print("") } -func (m *mkcert) check() bool { +func (m *mkcert) checkPlatform() bool { if m.ignoreCheckFailure { return true } @@ -167,3 +196,9 @@ func fatalIfErr(err error, msg string) { log.Fatalf("ERROR: %s: %s", msg, err) } } + +func fatalIfCmdErr(err error, cmd string, out []byte) { + if err != nil { + log.Fatalf("ERROR: failed to execute \"%s\": %s\n\n%s\n", cmd, err, out) + } +} diff --git a/truststore_darwin.go b/truststore_darwin.go index e52033b..9022158 100644 --- a/truststore_darwin.go +++ b/truststore_darwin.go @@ -16,6 +16,12 @@ import ( "github.com/DHowett/go-plist" ) +var ( + FirefoxPath = "/Applications/Firefox.app" + FirefoxProfile = os.Getenv("HOME") + "/Library/Application Support/Firefox/Profiles/*" + CertutilInstallHelp = "brew install nss" +) + // https://github.com/golang/go/issues/24652#issuecomment-399826583 var trustSettings []interface{} var _, _ = plist.Unmarshal(trustSettingsData, &trustSettings) @@ -100,9 +106,3 @@ func (m *mkcert) uninstallPlatform() { out, err := cmd.CombinedOutput() fatalIfCmdErr(err, "security remove-trusted-cert", out) } - -func fatalIfCmdErr(err error, cmd string, out []byte) { - if err != nil { - log.Fatalf("ERROR: failed to execute \"%s\": %s\n\n%s\n", cmd, err, out) - } -} diff --git a/truststore_firefox.go b/truststore_firefox.go new file mode 100644 index 0000000..60d521e --- /dev/null +++ b/truststore_firefox.go @@ -0,0 +1,89 @@ +package main + +import ( + "log" + "os" + "os/exec" + "path/filepath" + "strings" +) + +var ( + hasFirefox bool + hasCertutil bool + certutilPath string +) + +func init() { + _, err := os.Stat(FirefoxPath) + hasFirefox = !os.IsNotExist(err) + + out, err := exec.Command("brew", "--prefix", "nss").Output() + if err != nil { + return + } + certutilPath = filepath.Join(strings.TrimSpace(string(out)), "bin", "certutil") + + _, err = os.Stat(certutilPath) + hasCertutil = !os.IsNotExist(err) +} + +func (m *mkcert) checkFirefox() bool { + if !hasCertutil { + return false + } + success := true + if m.forEachFirefoxProfile(func(profile string) { + err := exec.Command(certutilPath, "-V", "-d", profile, "-u", "L", "-n", m.caUniqueName()).Run() + if err != nil { + success = false + } + }) == 0 { + success = false + } + return success +} + +func (m *mkcert) installFirefox() { + if m.forEachFirefoxProfile(func(profile string) { + cmd := exec.Command(certutilPath, "-A", "-d", profile, "-t", "C,,", "-n", m.caUniqueName(), "-i", filepath.Join(m.CAROOT, rootName)) + out, err := cmd.CombinedOutput() + fatalIfCmdErr(err, "certutil -A", out) + }) == 0 { + log.Println("ERROR: no Firefox security databases found") + } + if !m.checkFirefox() { + log.Println("Installing in Firefox failed. Please report the issue with details about your environment at https://github.com/FiloSottile/mkcert/issues/new 👎") + log.Println("Note that if you never started Firefox, you need to do that at least once.") + } +} + +func (m *mkcert) uninstallFirefox() { + m.forEachFirefoxProfile(func(profile string) { + err := exec.Command(certutilPath, "-V", "-d", profile, "-u", "L", "-n", m.caUniqueName()).Run() + if err != nil { + return + } + cmd := exec.Command(certutilPath, "-D", "-d", profile, "-n", m.caUniqueName()) + out, err := cmd.CombinedOutput() + fatalIfCmdErr(err, "certutil -D", out) + }) +} + +func (m *mkcert) forEachFirefoxProfile(f func(profile string)) (found int) { + profiles, _ := filepath.Glob(FirefoxProfile) + if len(profiles) == 0 { + return + } + for _, profile := range profiles { + if _, err := os.Stat(filepath.Join(profile, "cert8.db")); !os.IsNotExist(err) { + f(profile) + found++ + } + if _, err := os.Stat(filepath.Join(profile, "cert9.db")); !os.IsNotExist(err) { + f("sql:" + profile) + found++ + } + } + return +} diff --git a/truststore_linux.go b/truststore_linux.go index fcbfbdf..7ba6760 100644 --- a/truststore_linux.go +++ b/truststore_linux.go @@ -6,9 +6,16 @@ package main import ( "log" + "os" "path/filepath" ) +var ( + FirefoxPath = "/usr/bin/firefox" + FirefoxProfile = os.Getenv("HOME") + "/.mozilla/firefox/*" + CertutilInstallHelp = "apt install libnss3-tools" +) + func (m *mkcert) installPlatform() { log.Fatalf("-install is not yet supported on Linux 😣\nYou can manually install the root certificate at %q in the meantime.", filepath.Join(m.CAROOT, rootName)) }