cert.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. package main
  2. import (
  3. "crypto/rand"
  4. "crypto/rsa"
  5. "crypto/x509"
  6. "crypto/x509/pkix"
  7. "encoding/pem"
  8. "io/ioutil"
  9. "log"
  10. "math/big"
  11. "net"
  12. "os"
  13. "path/filepath"
  14. "regexp"
  15. "strconv"
  16. "strings"
  17. "time"
  18. )
  19. var rootSubject = pkix.Name{
  20. Organization: []string{"mkcert development CA"},
  21. }
  22. func (m *mkcert) makeCert(hosts []string) {
  23. if m.caKey == nil {
  24. log.Fatalln("ERROR: can't create new certificates because the CA key (rootCA-key.pem) is missing")
  25. }
  26. priv, err := rsa.GenerateKey(rand.Reader, 2048)
  27. fatalIfErr(err, "failed to generate certificate key")
  28. serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
  29. serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
  30. fatalIfErr(err, "failed to generate serial number")
  31. tpl := &x509.Certificate{
  32. SerialNumber: serialNumber,
  33. Subject: pkix.Name{
  34. Organization: []string{"mkcert development certificate"},
  35. },
  36. NotAfter: time.Now().AddDate(10, 0, 0),
  37. NotBefore: time.Now().AddDate(0, 0, -1),
  38. KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
  39. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
  40. BasicConstraintsValid: true,
  41. }
  42. for _, h := range hosts {
  43. if ip := net.ParseIP(h); ip != nil {
  44. tpl.IPAddresses = append(tpl.IPAddresses, ip)
  45. } else {
  46. tpl.DNSNames = append(tpl.DNSNames, h)
  47. }
  48. }
  49. pub := priv.PublicKey
  50. cert, err := x509.CreateCertificate(rand.Reader, tpl, m.caCert, &pub, m.caKey)
  51. fatalIfErr(err, "failed to generate certificate")
  52. filename := strings.Replace(hosts[0], ":", "_", -1)
  53. filename = strings.Replace(filename, "*", "_wildcard", -1)
  54. if len(hosts) > 1 {
  55. filename += "+" + strconv.Itoa(len(hosts)-1)
  56. }
  57. privDER, err := x509.MarshalPKCS8PrivateKey(priv)
  58. fatalIfErr(err, "failed to encode certificate key")
  59. err = ioutil.WriteFile(filename+"-key.pem", pem.EncodeToMemory(
  60. &pem.Block{Type: "PRIVATE KEY", Bytes: privDER}), 0600)
  61. fatalIfErr(err, "failed to save certificate key")
  62. err = ioutil.WriteFile(filename+".pem", pem.EncodeToMemory(
  63. &pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0644)
  64. fatalIfErr(err, "failed to save certificate key")
  65. secondLvlWildcardRegexp := regexp.MustCompile(`(?i)^\*\.[0-9a-z_-]+$`)
  66. log.Printf("\nCreated a new certificate valid for the following names 📜")
  67. for _, h := range hosts {
  68. log.Printf(" - %q", h)
  69. if secondLvlWildcardRegexp.MatchString(h) {
  70. log.Printf(" Warning: many browsers don't support second-level wildcards like %q ⚠️", h)
  71. }
  72. }
  73. log.Printf("\nThe certificate is at \"./%s.pem\" and the key at \"./%s-key.pem\" ✅\n\n", filename, filename)
  74. }
  75. // loadCA will load or create the CA at CAROOT.
  76. func (m *mkcert) loadCA() {
  77. if _, err := os.Stat(filepath.Join(m.CAROOT, rootName)); os.IsNotExist(err) {
  78. m.newCA()
  79. } else {
  80. log.Printf("Using the local CA at \"%s\" ✨\n", m.CAROOT)
  81. }
  82. certPEMBlock, err := ioutil.ReadFile(filepath.Join(m.CAROOT, rootName))
  83. fatalIfErr(err, "failed to read the CA certificate")
  84. certDERBlock, _ := pem.Decode(certPEMBlock)
  85. if certDERBlock == nil || certDERBlock.Type != "CERTIFICATE" {
  86. log.Fatalln("ERROR: failed to read the CA certificate: unexpected content")
  87. }
  88. m.caCert, err = x509.ParseCertificate(certDERBlock.Bytes)
  89. fatalIfErr(err, "failed to parse the CA certificate")
  90. if _, err := os.Stat(filepath.Join(m.CAROOT, keyName)); os.IsNotExist(err) {
  91. return // keyless mode, where only -install works
  92. }
  93. keyPEMBlock, err := ioutil.ReadFile(filepath.Join(m.CAROOT, keyName))
  94. fatalIfErr(err, "failed to read the CA key")
  95. keyDERBlock, _ := pem.Decode(keyPEMBlock)
  96. if keyDERBlock == nil || keyDERBlock.Type != "PRIVATE KEY" {
  97. log.Fatalln("ERROR: failed to read the CA key: unexpected content")
  98. }
  99. m.caKey, err = x509.ParsePKCS8PrivateKey(keyDERBlock.Bytes)
  100. fatalIfErr(err, "failed to parse the CA key")
  101. }
  102. func (m *mkcert) newCA() {
  103. priv, err := rsa.GenerateKey(rand.Reader, 3072)
  104. fatalIfErr(err, "failed to generate the CA key")
  105. serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
  106. serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
  107. fatalIfErr(err, "failed to generate serial number")
  108. tpl := &x509.Certificate{
  109. SerialNumber: serialNumber,
  110. Subject: rootSubject,
  111. NotAfter: time.Now().AddDate(10, 0, 0),
  112. NotBefore: time.Now().AddDate(0, 0, -1),
  113. KeyUsage: x509.KeyUsageCertSign,
  114. BasicConstraintsValid: true,
  115. IsCA: true,
  116. MaxPathLenZero: true,
  117. }
  118. pub := priv.PublicKey
  119. cert, err := x509.CreateCertificate(rand.Reader, tpl, tpl, &pub, priv)
  120. fatalIfErr(err, "failed to generate CA certificate")
  121. privDER, err := x509.MarshalPKCS8PrivateKey(priv)
  122. fatalIfErr(err, "failed to encode CA key")
  123. err = ioutil.WriteFile(filepath.Join(m.CAROOT, keyName), pem.EncodeToMemory(
  124. &pem.Block{Type: "PRIVATE KEY", Bytes: privDER}), 0400)
  125. fatalIfErr(err, "failed to save CA key")
  126. err = ioutil.WriteFile(filepath.Join(m.CAROOT, rootName), pem.EncodeToMemory(
  127. &pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0644)
  128. fatalIfErr(err, "failed to save CA key")
  129. log.Printf("Created a new local CA at \"%s\" 💥\n", m.CAROOT)
  130. }
  131. func (m *mkcert) caUniqueName() string {
  132. return "mkcert development CA " + m.caCert.SerialNumber.String()
  133. }