diff --git a/cert.go b/cert.go new file mode 100644 index 0000000..af034d9 --- /dev/null +++ b/cert.go @@ -0,0 +1,152 @@ +package main + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "io/ioutil" + "log" + "math/big" + "net" + "os" + "path/filepath" + "strconv" + "strings" + "time" +) + +var rootSubject = pkix.Name{ + Organization: []string{"mkcert development CA"}, +} + +func (m *mkcert) makeCert(hosts []string) { + if m.caKey == nil { + log.Fatalln("ERROR: can't create new certificates because the CA key (rootCA-key.pem) is missing") + } + + 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) + filename = strings.Replace(filename, "*", "_wildcard", -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}), 0400) + fatalIfErr(err, "failed to save certificate key") + + err = ioutil.WriteFile(filename+".pem", pem.EncodeToMemory( + &pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0644) + 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. +func (m *mkcert) loadCA() { + if _, err := os.Stat(filepath.Join(m.CAROOT, rootName)); os.IsNotExist(err) { + m.newCA() + } else { + log.Printf("Using the local CA at \"%s\" ✨\n", m.CAROOT) + } + + certPEMBlock, err := ioutil.ReadFile(filepath.Join(m.CAROOT, rootName)) + fatalIfErr(err, "failed to read the CA certificate") + certDERBlock, _ := pem.Decode(certPEMBlock) + if certDERBlock == nil || certDERBlock.Type != "CERTIFICATE" { + log.Fatalln("ERROR: failed to read the CA certificate: unexpected content") + } + m.caCert, err = x509.ParseCertificate(certDERBlock.Bytes) + fatalIfErr(err, "failed to parse the CA certificate") + + if _, err := os.Stat(filepath.Join(m.CAROOT, keyName)); os.IsNotExist(err) { + return // keyless mode, where only -install works + } + + keyPEMBlock, err := ioutil.ReadFile(filepath.Join(m.CAROOT, keyName)) + fatalIfErr(err, "failed to read the CA key") + keyDERBlock, _ := pem.Decode(keyPEMBlock) + if keyDERBlock == nil || keyDERBlock.Type != "PRIVATE KEY" { + log.Fatalln("ERROR: failed to read the CA key: unexpected content") + } + m.caKey, err = x509.ParsePKCS8PrivateKey(keyDERBlock.Bytes) + fatalIfErr(err, "failed to parse the CA key") +} + +func (m *mkcert) newCA() { + priv, err := rsa.GenerateKey(rand.Reader, 3072) + fatalIfErr(err, "failed to generate the CA 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: rootSubject, + + NotAfter: time.Now().AddDate(10, 0, 0), + NotBefore: time.Now().AddDate(0, 0, -1), + + KeyUsage: x509.KeyUsageCertSign, + + BasicConstraintsValid: true, + IsCA: true, + MaxPathLenZero: true, + } + + pub := priv.PublicKey + cert, err := x509.CreateCertificate(rand.Reader, tpl, tpl, &pub, priv) + fatalIfErr(err, "failed to generate CA certificate") + + privDER, err := x509.MarshalPKCS8PrivateKey(priv) + fatalIfErr(err, "failed to encode CA key") + err = ioutil.WriteFile(filepath.Join(m.CAROOT, keyName), pem.EncodeToMemory( + &pem.Block{Type: "PRIVATE KEY", Bytes: privDER}), 0400) + fatalIfErr(err, "failed to save CA key") + + err = ioutil.WriteFile(filepath.Join(m.CAROOT, rootName), pem.EncodeToMemory( + &pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0644) + fatalIfErr(err, "failed to save CA key") + + log.Printf("Created a new local CA at \"%s\" 💥\n", m.CAROOT) +} diff --git a/main.go b/main.go index d17a7c0..a82543c 100644 --- a/main.go +++ b/main.go @@ -7,23 +7,14 @@ package main import ( "crypto" - "crypto/rand" - "crypto/rsa" "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "flag" - "io/ioutil" "log" - "math/big" "net" "os" "path/filepath" "regexp" "runtime" - "strconv" - "strings" - "time" ) func main() { @@ -42,10 +33,6 @@ func main() { const rootName = "rootCA.pem" const keyName = "rootCA-key.pem" -var rootSubject = pkix.Name{ - Organization: []string{"mkcert development CA"}, -} - type mkcert struct { installMode, uninstallMode bool @@ -118,136 +105,6 @@ Change the CA certificate and key storage location by setting $CAROOT. m.makeCert(args) } -func (m *mkcert) makeCert(hosts []string) { - if m.caKey == nil { - log.Fatalln("ERROR: can't create new certificates because the CA key (rootCA-key.pem) is missing") - } - - 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) - filename = strings.Replace(filename, "*", "_wildcard", -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}), 0400) - fatalIfErr(err, "failed to save certificate key") - - err = ioutil.WriteFile(filename+".pem", pem.EncodeToMemory( - &pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0644) - 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. -func (m *mkcert) loadCA() { - if _, err := os.Stat(filepath.Join(m.CAROOT, rootName)); os.IsNotExist(err) { - m.newCA() - } else { - log.Printf("Using the local CA at \"%s\" ✨\n", m.CAROOT) - } - - certPEMBlock, err := ioutil.ReadFile(filepath.Join(m.CAROOT, rootName)) - fatalIfErr(err, "failed to read the CA certificate") - certDERBlock, _ := pem.Decode(certPEMBlock) - if certDERBlock == nil || certDERBlock.Type != "CERTIFICATE" { - log.Fatalln("ERROR: failed to read the CA certificate: unexpected content") - } - m.caCert, err = x509.ParseCertificate(certDERBlock.Bytes) - fatalIfErr(err, "failed to parse the CA certificate") - - if _, err := os.Stat(filepath.Join(m.CAROOT, keyName)); os.IsNotExist(err) { - return // keyless mode, where only -install works - } - - keyPEMBlock, err := ioutil.ReadFile(filepath.Join(m.CAROOT, keyName)) - fatalIfErr(err, "failed to read the CA key") - keyDERBlock, _ := pem.Decode(keyPEMBlock) - if keyDERBlock == nil || keyDERBlock.Type != "PRIVATE KEY" { - log.Fatalln("ERROR: failed to read the CA key: unexpected content") - } - m.caKey, err = x509.ParsePKCS8PrivateKey(keyDERBlock.Bytes) - fatalIfErr(err, "failed to parse the CA key") -} - -func (m *mkcert) newCA() { - priv, err := rsa.GenerateKey(rand.Reader, 3072) - fatalIfErr(err, "failed to generate the CA 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: rootSubject, - - NotAfter: time.Now().AddDate(10, 0, 0), - NotBefore: time.Now().AddDate(0, 0, -1), - - KeyUsage: x509.KeyUsageCertSign, - - BasicConstraintsValid: true, - IsCA: true, - MaxPathLenZero: true, - } - - pub := priv.PublicKey - cert, err := x509.CreateCertificate(rand.Reader, tpl, tpl, &pub, priv) - fatalIfErr(err, "failed to generate CA certificate") - - privDER, err := x509.MarshalPKCS8PrivateKey(priv) - fatalIfErr(err, "failed to encode CA key") - err = ioutil.WriteFile(filepath.Join(m.CAROOT, keyName), pem.EncodeToMemory( - &pem.Block{Type: "PRIVATE KEY", Bytes: privDER}), 0400) - fatalIfErr(err, "failed to save CA key") - - err = ioutil.WriteFile(filepath.Join(m.CAROOT, rootName), pem.EncodeToMemory( - &pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0644) - fatalIfErr(err, "failed to save CA key") - - log.Printf("Created a new local CA at \"%s\" 💥\n", m.CAROOT) -} - func getCAROOT() string { if env := os.Getenv("CAROOT"); env != "" { return env