Przeglądaj źródła

Add Firefox support

Fixes #6
Filippo Valsorda 6 lat temu
rodzic
commit
e4c5c312a7
6 zmienionych plików z 159 dodań i 23 usunięć
  1. 3 2
      README.md
  2. 4 0
      cert.go
  3. 50 15
      main.go
  4. 6 6
      truststore_darwin.go
  5. 89 0
      truststore_firefox.go
  6. 7 0
      truststore_linux.go

+ 3 - 2
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
 

+ 4 - 0
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()
+}

+ 50 - 15
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)
+	}
+}

+ 6 - 6
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)
-	}
-}

+ 89 - 0
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
+}

+ 7 - 0
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))
 }