123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- // Copyright 2018 The mkcert Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package main
- import (
- "crypto"
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/rand"
- "crypto/rsa"
- "crypto/sha1"
- "crypto/x509"
- "crypto/x509/pkix"
- "encoding/asn1"
- "encoding/pem"
- "io/ioutil"
- "log"
- "math/big"
- "net"
- "net/mail"
- "net/url"
- "os"
- "os/user"
- "path/filepath"
- "regexp"
- "strconv"
- "strings"
- "time"
- pkcs12 "software.sslmate.com/src/go-pkcs12"
- )
- var userAndHostname string
- func init() {
- u, err := user.Current()
- if err == nil {
- userAndHostname = u.Username + "@"
- }
- if h, err := os.Hostname(); err == nil {
- userAndHostname += h
- }
- if err == nil && u.Name != "" && u.Name != u.Username {
- userAndHostname += " (" + u.Name + ")"
- }
- }
- 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 := m.generateKey(false)
- fatalIfErr(err, "failed to generate certificate key")
- pub := priv.(crypto.Signer).Public()
- tpl := &x509.Certificate{
- SerialNumber: randomSerialNumber(),
- Subject: pkix.Name{
- Organization: []string{"mkcert development certificate"},
- OrganizationalUnit: []string{userAndHostname},
- },
- NotAfter: time.Now().AddDate(10, 0, 0),
- // Fix the notBefore to temporarily bypass macOS Catalina's limit on
- // certificate lifespan. Once mkcert provides an ACME server, automation
- // will be the recommended way to guarantee uninterrupted functionality,
- // and the lifespan will be shortened to 825 days. See issue 174 and
- // https://support.apple.com/en-us/HT210176.
- NotBefore: time.Date(2019, time.June, 1, 0, 0, 0, 0, time.UTC),
- KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
- BasicConstraintsValid: true,
- }
- for _, h := range hosts {
- if ip := net.ParseIP(h); ip != nil {
- tpl.IPAddresses = append(tpl.IPAddresses, ip)
- } else if email, err := mail.ParseAddress(h); err == nil && email.Address == h {
- tpl.EmailAddresses = append(tpl.EmailAddresses, h)
- } else if uriName, err := url.Parse(h); err == nil && uriName.Scheme != "" && uriName.Host != "" {
- tpl.URIs = append(tpl.URIs, uriName)
- } else {
- tpl.DNSNames = append(tpl.DNSNames, h)
- }
- }
- if m.client {
- tpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}
- } else if len(tpl.IPAddresses) > 0 || len(tpl.DNSNames) > 0 {
- tpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
- }
- if len(tpl.EmailAddresses) > 0 {
- tpl.ExtKeyUsage = append(tpl.ExtKeyUsage, x509.ExtKeyUsageCodeSigning, x509.ExtKeyUsageEmailProtection)
- }
- // IIS (the main target of PKCS #12 files), only shows the deprecated
- // Common Name in the UI. See issue #115.
- if m.pkcs12 {
- tpl.Subject.CommonName = hosts[0]
- }
- cert, err := x509.CreateCertificate(rand.Reader, tpl, m.caCert, pub, m.caKey)
- fatalIfErr(err, "failed to generate certificate")
- certFile, keyFile, p12File := m.fileNames(hosts)
- if !m.pkcs12 {
- privDER, err := x509.MarshalPKCS8PrivateKey(priv)
- fatalIfErr(err, "failed to encode certificate key")
- err = ioutil.WriteFile(keyFile, pem.EncodeToMemory(
- &pem.Block{Type: "PRIVATE KEY", Bytes: privDER}), 0600)
- fatalIfErr(err, "failed to save certificate key")
- err = ioutil.WriteFile(certFile, pem.EncodeToMemory(
- &pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0644)
- fatalIfErr(err, "failed to save certificate")
- } 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")
- err = ioutil.WriteFile(p12File, pfxData, 0644)
- fatalIfErr(err, "failed to save PKCS#12")
- }
- m.printHosts(hosts)
- if !m.pkcs12 {
- log.Printf("\nThe certificate is at \"%s\" and the key at \"%s\" ✅\n\n", certFile, keyFile)
- } else {
- 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")
- }
- }
- func (m *mkcert) printHosts(hosts []string) {
- secondLvlWildcardRegexp := regexp.MustCompile(`(?i)^\*\.[0-9a-z_-]+$`)
- 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)
- }
- }
- 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
- }
- }
- }
- func (m *mkcert) generateKey(rootCA bool) (crypto.PrivateKey, error) {
- if m.ecdsa {
- return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- }
- if rootCA {
- return rsa.GenerateKey(rand.Reader, 3072)
- }
- return rsa.GenerateKey(rand.Reader, 2048)
- }
- 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)
- }
- if m.client {
- defaultName += "-client"
- }
- 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
- }
- func randomSerialNumber() *big.Int {
- serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
- serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
- fatalIfErr(err, "failed to generate serial number")
- return serialNumber
- }
- func (m *mkcert) makeCertFromCSR() {
- if m.caKey == nil {
- log.Fatalln("ERROR: can't create new certificates because the CA key (rootCA-key.pem) is missing")
- }
- csrPEMBytes, err := ioutil.ReadFile(m.csrPath)
- fatalIfErr(err, "failed to read the CSR")
- csrPEM, _ := pem.Decode(csrPEMBytes)
- if csrPEM == nil {
- log.Fatalln("ERROR: failed to read the CSR: unexpected content")
- }
- if csrPEM.Type != "CERTIFICATE REQUEST" &&
- csrPEM.Type != "NEW CERTIFICATE REQUEST" {
- log.Fatalln("ERROR: failed to read the CSR: expected CERTIFICATE REQUEST, got " + csrPEM.Type)
- }
- csr, err := x509.ParseCertificateRequest(csrPEM.Bytes)
- fatalIfErr(err, "failed to parse the CSR")
- fatalIfErr(csr.CheckSignature(), "invalid CSR signature")
- tpl := &x509.Certificate{
- SerialNumber: randomSerialNumber(),
- Subject: csr.Subject,
- ExtraExtensions: csr.Extensions, // includes requested SANs
- NotAfter: time.Now().AddDate(10, 0, 0),
- NotBefore: time.Now(),
- KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
- ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
- BasicConstraintsValid: true,
- // If the CSR does not request a SAN extension, fix it up for them as
- // the Common Name field does not work in modern browsers. Otherwise,
- // this will get overridden.
- DNSNames: []string{csr.Subject.CommonName},
- }
- cert, err := x509.CreateCertificate(rand.Reader, tpl, m.caCert, csr.PublicKey, m.caKey)
- fatalIfErr(err, "failed to generate certificate")
- var hosts []string
- hosts = append(hosts, csr.DNSNames...)
- hosts = append(hosts, csr.EmailAddresses...)
- for _, ip := range csr.IPAddresses {
- hosts = append(hosts, ip.String())
- }
- if len(hosts) == 0 {
- hosts = []string{csr.Subject.CommonName}
- }
- certFile, _, _ := m.fileNames(hosts)
- err = ioutil.WriteFile(certFile, pem.EncodeToMemory(
- &pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0644)
- fatalIfErr(err, "failed to save certificate")
- m.printHosts(hosts)
- log.Printf("\nThe certificate is at \"%s\" ✅\n\n", certFile)
- }
- // loadCA will load or create the CA at CAROOT.
- func (m *mkcert) loadCA() {
- if !pathExists(filepath.Join(m.CAROOT, rootName)) {
- 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 !pathExists(filepath.Join(m.CAROOT, rootKeyName)) {
- return // keyless mode, where only -install works
- }
- keyPEMBlock, err := ioutil.ReadFile(filepath.Join(m.CAROOT, rootKeyName))
- 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 := m.generateKey(true)
- fatalIfErr(err, "failed to generate the CA key")
- pub := priv.(crypto.Signer).Public()
- 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)
- tpl := &x509.Certificate{
- SerialNumber: randomSerialNumber(),
- 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[:],
- NotAfter: time.Now().AddDate(10, 0, 0),
- NotBefore: time.Now(),
- KeyUsage: x509.KeyUsageCertSign,
- BasicConstraintsValid: true,
- IsCA: true,
- MaxPathLenZero: true,
- }
- 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, rootKeyName), 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 (m *mkcert) caUniqueName() string {
- return "mkcert development CA " + m.caCert.SerialNumber.String()
- }
|