From 314622cbb38aa7bf9338d0d250b3d69c8f084651 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Mon, 25 Jun 2018 23:47:28 -0400 Subject: [PATCH] Add certificate generation and -uninstall --- main.go | 110 ++++++++++++++++++++++++++++++++++++++++--- truststore_darwin.go | 6 +++ 2 files changed, 109 insertions(+), 7 deletions(-) diff --git a/main.go b/main.go index 3f88955..27435d7 100644 --- a/main.go +++ b/main.go @@ -16,18 +16,27 @@ import ( "io/ioutil" "log" "math/big" + "net" "os" "path/filepath" + "regexp" "runtime" + "strconv" + "strings" "time" ) -var installFlag = flag.Bool("install", false, "install the local root CA in the system trust store") - func main() { log.SetFlags(0) + var installFlag = flag.Bool("install", false, "install the local root CA in the system trust store") + var uninstallFlag = flag.Bool("uninstall", false, "uninstall the local root CA from the system trust store") flag.Parse() - (&mkcert{}).Run() + if *installFlag && *uninstallFlag { + log.Fatalln("ERROR: you can't set -install and -uninstall at the same time") + } + (&mkcert{ + installMode: *installFlag, uninstallMode: *uninstallFlag, + }).Run(flag.Args()) } const rootName = "rootCA.pem" @@ -38,6 +47,8 @@ var rootSubject = pkix.Name{ } type mkcert struct { + installMode, uninstallMode bool + CAROOT string caCert *x509.Certificate caKey crypto.PrivateKey @@ -48,19 +59,99 @@ type mkcert struct { ignoreCheckFailure bool } -func (m *mkcert) Run() { +func (m *mkcert) Run(args []string) { m.CAROOT = getCAROOT() if m.CAROOT == "" { log.Fatalln("ERROR: failed to find the default CA location, set one as the CAROOT env var") } fatalIfErr(os.MkdirAll(m.CAROOT, 0755), "failed to create the CAROOT") m.loadCA() - if *installFlag { + + if m.installMode { m.install() + if len(args) == 0 { + return + } + } 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 ‼️") } + + if len(args) == 0 { + log.Println("Usage: TODO") + return + } + + re := regexp.MustCompile(`^[0-9A-Za-z._-]+$`) + for _, name := range args { + if ip := net.ParseIP(name); ip != nil { + continue + } + if re.MatchString(name) { + continue + } + log.Fatalf("ERROR: %q is not a valid hostname or IP", name) + } + + m.makeCert(args) +} + +func (m *mkcert) makeCert(hosts []string) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + fatalIfErr(err, "failed to generate certificate key") + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + fatalIfErr(err, "failed to generate serial number") + + tpl := &x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"mkcert development certificate"}, + }, + + NotAfter: time.Now().AddDate(10, 0, 0), + NotBefore: time.Now().AddDate(0, 0, -1), + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + tpl.IPAddresses = append(tpl.IPAddresses, ip) + } else { + tpl.DNSNames = append(tpl.DNSNames, h) + } + } + + pub := priv.PublicKey + cert, err := x509.CreateCertificate(rand.Reader, tpl, m.caCert, &pub, m.caKey) + fatalIfErr(err, "failed to generate certificate") + + filename := strings.Replace(hosts[0], ":", ".", -1) + if len(hosts) > 1 { + filename += "+" + strconv.Itoa(len(hosts)-1) + } + + privDER, err := x509.MarshalPKCS8PrivateKey(priv) + fatalIfErr(err, "failed to encode certificate key") + err = ioutil.WriteFile(filename+"-key.pem", pem.EncodeToMemory( + &pem.Block{Type: "PRIVATE KEY", Bytes: privDER}), 0644) + fatalIfErr(err, "failed to save certificate key") + + err = ioutil.WriteFile(filename+".pem", pem.EncodeToMemory( + &pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0600) + fatalIfErr(err, "failed to save certificate key") + + log.Printf("\nCreated a new certificate valid for the following names 📜") + for _, h := range hosts { + log.Printf(" - %q", h) + } + log.Printf("\nThe certificate is at \"./%s.pem\" and the key at \"./%s-key.pem\" ✅\n\n", filename, filename) } // loadCA will load or create the CA at CAROOT. @@ -176,12 +267,17 @@ func (m *mkcert) install() { */ if m.check() { // useless, see comment on ignoreCheckFailure - log.Println("The local CA is now installed in the system trust store! ⚡️") + log.Print("The local CA is now installed in the system trust store! ⚡️\n\n") } else { - log.Fatalln("Installing failed. Please report the issue with details about your environment at https://github.com/FiloSottile/mkcert/issues/new 👎") + log.Fatal("Installing failed. Please report the issue with details about your environment at https://github.com/FiloSottile/mkcert/issues/new 👎\n\n") } } +func (m *mkcert) uninstall() { + m.uninstallPlatform() + log.Print("The local CA is now uninstalled from the system trust store! 👋\n\n") +} + func (m *mkcert) check() bool { if m.ignoreCheckFailure { return true diff --git a/truststore_darwin.go b/truststore_darwin.go index c35d8b6..e52033b 100644 --- a/truststore_darwin.go +++ b/truststore_darwin.go @@ -95,6 +95,12 @@ func (m *mkcert) installPlatform() { fatalIfCmdErr(err, "security trust-settings-import", out) } +func (m *mkcert) uninstallPlatform() { + cmd := exec.Command("sudo", "security", "remove-trusted-cert", "-d", filepath.Join(m.CAROOT, rootName)) + 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)