Add Firefox support

Fixes #6
This commit is contained in:
Filippo Valsorda 2018-06-28 01:29:20 -04:00
parent 7544098b30
commit e4c5c312a7
6 changed files with 159 additions and 23 deletions

@ -33,16 +33,17 @@ On macOS, use Homebrew.
``` ```
brew install --HEAD https://github.com/FiloSottile/mkcert/raw/master/HomebrewFormula/mkcert.rb 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 $ git clone https://github.com/FiloSottile/mkcert
$ cd mkcert && make $ cd mkcert && make
``` ```
Windows will be supported soon. Windows will be supported next.
## Advanced topics ## Advanced topics

@ -150,3 +150,7 @@ func (m *mkcert) newCA() {
log.Printf("Created a new local CA at \"%s\" 💥\n", m.CAROOT) 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()
}

59
main.go

@ -62,10 +62,20 @@ func (m *mkcert) Run(args []string) {
} else if m.uninstallMode { } else if m.uninstallMode {
m.uninstall() m.uninstall()
return return
} else if !m.check() { } else {
var warning bool
if !m.checkPlatform() {
warning = true
log.Println("Warning: the local CA is not installed in the system trust store! ⚠️") 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 ‼️") log.Println("Run \"mkcert -install\" to avoid verification errors ‼️")
} }
}
if len(args) == 0 { if len(args) == 0 {
log.Printf(` log.Printf(`
@ -134,26 +144,45 @@ func getCAROOT() string {
} }
func (m *mkcert) install() { func (m *mkcert) install() {
if m.check() { var printed bool
return if !m.checkPlatform() {
}
m.installPlatform() m.installPlatform()
m.ignoreCheckFailure = true 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! ⚡️")
if m.check() { // useless, see comment on ignoreCheckFailure printed = true
log.Print("The local CA is now installed in the system trust store! ⚡️\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 { } else {
log.Fatal("Installing failed. Please report the issue with details about your environment at https://github.com/FiloSottile/mkcert/issues/new 👎\n\n") 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() { func (m *mkcert) uninstall() {
m.uninstallPlatform() 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 { if m.ignoreCheckFailure {
return true return true
} }
@ -167,3 +196,9 @@ func fatalIfErr(err error, msg string) {
log.Fatalf("ERROR: %s: %s", msg, err) 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)
}
}

@ -16,6 +16,12 @@ import (
"github.com/DHowett/go-plist" "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 // https://github.com/golang/go/issues/24652#issuecomment-399826583
var trustSettings []interface{} var trustSettings []interface{}
var _, _ = plist.Unmarshal(trustSettingsData, &trustSettings) var _, _ = plist.Unmarshal(trustSettingsData, &trustSettings)
@ -100,9 +106,3 @@ func (m *mkcert) uninstallPlatform() {
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
fatalIfCmdErr(err, "security remove-trusted-cert", out) 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)
}
}

89
truststore_firefox.go Normal file

@ -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
}

@ -6,9 +6,16 @@ package main
import ( import (
"log" "log"
"os"
"path/filepath" "path/filepath"
) )
var (
FirefoxPath = "/usr/bin/firefox"
FirefoxProfile = os.Getenv("HOME") + "/.mozilla/firefox/*"
CertutilInstallHelp = "apt install libnss3-tools"
)
func (m *mkcert) installPlatform() { 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)) 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))
} }