mkcert/cert.go

241 lines
7.2 KiB
Go
Raw Normal View History

2018-07-04 04:06:50 +00:00
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
2018-06-28 03:43:51 +00:00
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
2018-06-28 03:43:51 +00:00
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
2018-06-28 03:43:51 +00:00
"encoding/pem"
"io/ioutil"
"log"
"math/big"
"net"
"os"
"os/exec"
"os/user"
2018-06-28 03:43:51 +00:00
"path/filepath"
"regexp"
2018-06-28 03:43:51 +00:00
"strconv"
"strings"
"time"
"software.sslmate.com/src/go-pkcs12"
2018-06-28 03:43:51 +00:00
)
var userAndHostname string
func init() {
u, _ := user.Current()
if u != nil {
userAndHostname = u.Username + "@"
}
out, _ := exec.Command("hostname").Output()
userAndHostname += strings.TrimSpace(string(out))
2018-06-28 03:43:51 +00:00
}
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"},
OrganizationalUnit: []string{userAndHostname},
2018-06-28 03:43:51 +00:00
},
NotAfter: time.Now().AddDate(10, 0, 0),
NotBefore: time.Now(),
2018-06-28 03:43:51 +00:00
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")
2019-01-06 23:55:49 +00:00
certFile, keyFile, p12File := m.fileNames(hosts)
if !m.pkcs12 {
privDER, err := x509.MarshalPKCS8PrivateKey(priv)
fatalIfErr(err, "failed to encode certificate key")
2019-01-06 23:55:49 +00:00
err = ioutil.WriteFile(keyFile, pem.EncodeToMemory(
&pem.Block{Type: "PRIVATE KEY", Bytes: privDER}), 0600)
fatalIfErr(err, "failed to save certificate key")
2018-06-28 03:43:51 +00:00
2019-01-06 23:55:49 +00:00
err = ioutil.WriteFile(certFile, pem.EncodeToMemory(
&pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0644)
fatalIfErr(err, "failed to save certificate key")
} else {
domainCert, _ := x509.ParseCertificate(cert)
pfxData, err := pkcs12.Encode(rand.Reader, priv, domainCert, []*x509.Certificate{m.caCert}, "changeit")
fatalIfErr(err, "failed to generate PKCS#12")
2019-01-06 23:55:49 +00:00
err = ioutil.WriteFile(p12File, pfxData, 0644)
fatalIfErr(err, "failed to save PKCS#12")
}
secondLvlWildcardRegexp := regexp.MustCompile(`(?i)^\*\.[0-9a-z_-]+$`)
2018-06-28 03:43:51 +00:00
log.Printf("\nCreated a new certificate valid for the following names 📜")
for _, h := range hosts {
log.Printf(" - %q", h)
if secondLvlWildcardRegexp.MatchString(h) {
log.Printf(" Warning: many browsers don't support second-level wildcards like %q ⚠️", h)
}
2018-06-28 03:43:51 +00:00
}
for _, h := range hosts {
if strings.HasPrefix(h, "*.") {
log.Printf("\nReminder: X.509 wildcards only go one level deep, so this won't match a.b.%s ", h[2:])
break
}
}
if !m.pkcs12 {
2019-01-06 23:55:49 +00:00
log.Printf("\nThe certificate is at \"%s\" and the key at \"%s\" ✅\n\n", certFile, keyFile)
} else {
2019-01-06 23:55:49 +00:00
log.Printf("\nThe PKCS#12 bundle is at \"%s\" ✅\n", p12File)
log.Printf("\nThe legacy PKCS#12 encryption password is the often hardcoded default \"changeit\" \n\n")
}
2018-06-28 03:43:51 +00:00
}
2019-01-06 23:55:49 +00:00
func (m *mkcert) fileNames(hosts []string) (certFile, keyFile, p12File string) {
defaultName := strings.Replace(hosts[0], ":", "_", -1)
defaultName = strings.Replace(defaultName, "*", "_wildcard", -1)
if len(hosts) > 1 {
defaultName += "+" + strconv.Itoa(len(hosts)-1)
}
certFile = "./" + defaultName + ".pem"
if m.certFile != "" {
certFile = m.certFile
}
keyFile = "./" + defaultName + "-key.pem"
if m.keyFile != "" {
keyFile = m.keyFile
}
p12File = "./" + defaultName + ".p12"
if m.p12File != "" {
p12File = m.p12File
}
return
}
2018-06-28 03:43:51 +00:00
// 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")
2019-01-06 23:55:49 +00:00
if _, err := os.Stat(filepath.Join(m.CAROOT, rootKeyName)); os.IsNotExist(err) {
2018-06-28 03:43:51 +00:00
return // keyless mode, where only -install works
}
2019-01-06 23:55:49 +00:00
keyPEMBlock, err := ioutil.ReadFile(filepath.Join(m.CAROOT, rootKeyName))
2018-06-28 03:43:51 +00:00
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")
pub := priv.PublicKey
2018-06-28 03:43:51 +00:00
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
fatalIfErr(err, "failed to generate serial number")
spkiASN1, err := x509.MarshalPKIXPublicKey(&pub)
fatalIfErr(err, "failed to encode public key")
var spki struct {
Algorithm pkix.AlgorithmIdentifier
SubjectPublicKey asn1.BitString
}
_, err = asn1.Unmarshal(spkiASN1, &spki)
fatalIfErr(err, "failed to decode public key")
skid := sha1.Sum(spki.SubjectPublicKey.Bytes)
2018-06-28 03:43:51 +00:00
tpl := &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"mkcert development CA"},
OrganizationalUnit: []string{userAndHostname},
// The CommonName is required by iOS to show the certificate in the
// "Certificate Trust Settings" menu.
// https://github.com/FiloSottile/mkcert/issues/47
CommonName: "mkcert " + userAndHostname,
},
SubjectKeyId: skid[:],
2018-06-28 03:43:51 +00:00
NotAfter: time.Now().AddDate(10, 0, 0),
NotBefore: time.Now(),
2018-06-28 03:43:51 +00:00
KeyUsage: x509.KeyUsageCertSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLenZero: true,
2018-06-28 03:43:51 +00:00
}
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")
2019-01-06 23:55:49 +00:00
err = ioutil.WriteFile(filepath.Join(m.CAROOT, rootKeyName), pem.EncodeToMemory(
2018-06-28 03:43:51 +00:00
&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)
}
2018-06-28 05:29:20 +00:00
func (m *mkcert) caUniqueName() string {
return "mkcert development CA " + m.caCert.SerialNumber.String()
}