main.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. // Copyright 2018 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // Command mkcert is a simple zero-config tool to make development certificates.
  5. package main
  6. import (
  7. "crypto"
  8. "crypto/rand"
  9. "crypto/rsa"
  10. "crypto/x509"
  11. "crypto/x509/pkix"
  12. "encoding/pem"
  13. "flag"
  14. "io/ioutil"
  15. "log"
  16. "math/big"
  17. "net"
  18. "os"
  19. "path/filepath"
  20. "regexp"
  21. "runtime"
  22. "strconv"
  23. "strings"
  24. "time"
  25. )
  26. func main() {
  27. log.SetFlags(0)
  28. var installFlag = flag.Bool("install", false, "install the local root CA in the system trust store")
  29. var uninstallFlag = flag.Bool("uninstall", false, "uninstall the local root CA from the system trust store")
  30. flag.Parse()
  31. if *installFlag && *uninstallFlag {
  32. log.Fatalln("ERROR: you can't set -install and -uninstall at the same time")
  33. }
  34. (&mkcert{
  35. installMode: *installFlag, uninstallMode: *uninstallFlag,
  36. }).Run(flag.Args())
  37. }
  38. const rootName = "rootCA.pem"
  39. const keyName = "rootCA-key.pem"
  40. var rootSubject = pkix.Name{
  41. Organization: []string{"mkcert development CA"},
  42. }
  43. type mkcert struct {
  44. installMode, uninstallMode bool
  45. CAROOT string
  46. caCert *x509.Certificate
  47. caKey crypto.PrivateKey
  48. // The system cert pool is only loaded once. After installing the root, checks
  49. // will keep failing until the next execution. TODO: maybe execve?
  50. // https://github.com/golang/go/issues/24540 (thanks, myself)
  51. ignoreCheckFailure bool
  52. }
  53. func (m *mkcert) Run(args []string) {
  54. m.CAROOT = getCAROOT()
  55. if m.CAROOT == "" {
  56. log.Fatalln("ERROR: failed to find the default CA location, set one as the CAROOT env var")
  57. }
  58. fatalIfErr(os.MkdirAll(m.CAROOT, 0755), "failed to create the CAROOT")
  59. m.loadCA()
  60. if m.installMode {
  61. m.install()
  62. if len(args) == 0 {
  63. return
  64. }
  65. } else if m.uninstallMode {
  66. m.uninstall()
  67. return
  68. } else if !m.check() {
  69. log.Println("Warning: the local CA is not installed in the system trust store! ⚠️")
  70. log.Println("Run \"mkcert -install\" to avoid verification errors ‼️")
  71. }
  72. if len(args) == 0 {
  73. log.Printf(`
  74. Usage:
  75. $ mkcert -install
  76. Install the local CA in the system trust store.
  77. $ mkcert example.org
  78. Generate "example.org.pem" and "example.org-key.pem".
  79. $ mkcert example.com myapp.dev localhost 127.0.0.1 ::1
  80. Generate "example.com+4.pem" and "example.com+4-key.pem".
  81. $ mkcert -uninstall
  82. Unnstall the local CA (but do not delete it).
  83. Change the CA certificate and key storage location by setting $CAROOT.
  84. `)
  85. return
  86. }
  87. re := regexp.MustCompile(`^[0-9A-Za-z._-]+$`)
  88. for _, name := range args {
  89. if ip := net.ParseIP(name); ip != nil {
  90. continue
  91. }
  92. if re.MatchString(name) {
  93. continue
  94. }
  95. log.Fatalf("ERROR: %q is not a valid hostname or IP", name)
  96. }
  97. m.makeCert(args)
  98. }
  99. func (m *mkcert) makeCert(hosts []string) {
  100. if m.caKey == nil {
  101. log.Fatalln("ERROR: can't create new certificates because the CA key (rootCA-key.pem) is missing")
  102. }
  103. priv, err := rsa.GenerateKey(rand.Reader, 2048)
  104. fatalIfErr(err, "failed to generate certificate 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: pkix.Name{
  111. Organization: []string{"mkcert development certificate"},
  112. },
  113. NotAfter: time.Now().AddDate(10, 0, 0),
  114. NotBefore: time.Now().AddDate(0, 0, -1),
  115. KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
  116. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
  117. BasicConstraintsValid: true,
  118. }
  119. for _, h := range hosts {
  120. if ip := net.ParseIP(h); ip != nil {
  121. tpl.IPAddresses = append(tpl.IPAddresses, ip)
  122. } else {
  123. tpl.DNSNames = append(tpl.DNSNames, h)
  124. }
  125. }
  126. pub := priv.PublicKey
  127. cert, err := x509.CreateCertificate(rand.Reader, tpl, m.caCert, &pub, m.caKey)
  128. fatalIfErr(err, "failed to generate certificate")
  129. filename := strings.Replace(hosts[0], ":", ".", -1)
  130. if len(hosts) > 1 {
  131. filename += "+" + strconv.Itoa(len(hosts)-1)
  132. }
  133. privDER, err := x509.MarshalPKCS8PrivateKey(priv)
  134. fatalIfErr(err, "failed to encode certificate key")
  135. err = ioutil.WriteFile(filename+"-key.pem", pem.EncodeToMemory(
  136. &pem.Block{Type: "PRIVATE KEY", Bytes: privDER}), 0644)
  137. fatalIfErr(err, "failed to save certificate key")
  138. err = ioutil.WriteFile(filename+".pem", pem.EncodeToMemory(
  139. &pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0600)
  140. fatalIfErr(err, "failed to save certificate key")
  141. log.Printf("\nCreated a new certificate valid for the following names 📜")
  142. for _, h := range hosts {
  143. log.Printf(" - %q", h)
  144. }
  145. log.Printf("\nThe certificate is at \"./%s.pem\" and the key at \"./%s-key.pem\" ✅\n\n", filename, filename)
  146. }
  147. // loadCA will load or create the CA at CAROOT.
  148. func (m *mkcert) loadCA() {
  149. if _, err := os.Stat(filepath.Join(m.CAROOT, rootName)); os.IsNotExist(err) {
  150. m.newCA()
  151. } else {
  152. log.Printf("Using the local CA at \"%s\" ✨\n", m.CAROOT)
  153. }
  154. certPEMBlock, err := ioutil.ReadFile(filepath.Join(m.CAROOT, rootName))
  155. fatalIfErr(err, "failed to read the CA certificate")
  156. certDERBlock, _ := pem.Decode(certPEMBlock)
  157. if certDERBlock == nil || certDERBlock.Type != "CERTIFICATE" {
  158. log.Fatalln("ERROR: failed to read the CA certificate: unexpected content")
  159. }
  160. m.caCert, err = x509.ParseCertificate(certDERBlock.Bytes)
  161. fatalIfErr(err, "failed to parse the CA certificate")
  162. if _, err := os.Stat(filepath.Join(m.CAROOT, keyName)); os.IsNotExist(err) {
  163. return // keyless mode, where only -install works
  164. }
  165. keyPEMBlock, err := ioutil.ReadFile(filepath.Join(m.CAROOT, keyName))
  166. fatalIfErr(err, "failed to read the CA key")
  167. keyDERBlock, _ := pem.Decode(keyPEMBlock)
  168. if keyDERBlock == nil || keyDERBlock.Type != "PRIVATE KEY" {
  169. log.Fatalln("ERROR: failed to read the CA key: unexpected content")
  170. }
  171. m.caKey, err = x509.ParsePKCS8PrivateKey(keyDERBlock.Bytes)
  172. fatalIfErr(err, "failed to parse the CA key")
  173. }
  174. func (m *mkcert) newCA() {
  175. priv, err := rsa.GenerateKey(rand.Reader, 3072)
  176. fatalIfErr(err, "failed to generate the CA key")
  177. serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
  178. serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
  179. fatalIfErr(err, "failed to generate serial number")
  180. tpl := &x509.Certificate{
  181. SerialNumber: serialNumber,
  182. Subject: rootSubject,
  183. NotAfter: time.Now().AddDate(10, 0, 0),
  184. NotBefore: time.Now().AddDate(0, 0, -1),
  185. KeyUsage: x509.KeyUsageCertSign,
  186. BasicConstraintsValid: true,
  187. IsCA: true,
  188. }
  189. pub := priv.PublicKey
  190. cert, err := x509.CreateCertificate(rand.Reader, tpl, tpl, &pub, priv)
  191. fatalIfErr(err, "failed to generate CA certificate")
  192. privDER, err := x509.MarshalPKCS8PrivateKey(priv)
  193. fatalIfErr(err, "failed to encode CA key")
  194. err = ioutil.WriteFile(filepath.Join(m.CAROOT, keyName), pem.EncodeToMemory(
  195. &pem.Block{Type: "PRIVATE KEY", Bytes: privDER}), 0400)
  196. fatalIfErr(err, "failed to save CA key")
  197. err = ioutil.WriteFile(filepath.Join(m.CAROOT, rootName), pem.EncodeToMemory(
  198. &pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0644)
  199. fatalIfErr(err, "failed to save CA key")
  200. log.Printf("Created a new local CA at \"%s\" 💥\n", m.CAROOT)
  201. }
  202. func getCAROOT() string {
  203. if env := os.Getenv("CAROOT"); env != "" {
  204. return env
  205. }
  206. var dir string
  207. switch runtime.GOOS {
  208. case "windows":
  209. dir = os.Getenv("LocalAppData")
  210. case "darwin":
  211. dir = os.Getenv("HOME")
  212. if dir == "" {
  213. return ""
  214. }
  215. dir = filepath.Join(dir, "Library", "Application Support")
  216. default: // Unix
  217. dir = os.Getenv("XDG_DATA_HOME")
  218. if dir == "" {
  219. dir = os.Getenv("HOME")
  220. if dir == "" {
  221. return ""
  222. }
  223. dir = filepath.Join(dir, ".local", "share")
  224. }
  225. }
  226. return filepath.Join(dir, "mkcert")
  227. }
  228. func (m *mkcert) install() {
  229. if m.check() {
  230. return
  231. }
  232. m.installPlatform()
  233. m.ignoreCheckFailure = true
  234. if m.check() { // useless, see comment on ignoreCheckFailure
  235. log.Print("The local CA is now installed in the system trust store! ⚡️\n\n")
  236. } else {
  237. log.Fatal("Installing failed. Please report the issue with details about your environment at https://github.com/FiloSottile/mkcert/issues/new 👎\n\n")
  238. }
  239. }
  240. func (m *mkcert) uninstall() {
  241. m.uninstallPlatform()
  242. log.Print("The local CA is now uninstalled from the system trust store! 👋\n\n")
  243. }
  244. func (m *mkcert) check() bool {
  245. if m.ignoreCheckFailure {
  246. return true
  247. }
  248. _, err := m.caCert.Verify(x509.VerifyOptions{})
  249. return err == nil
  250. }
  251. func fatalIfErr(err error, msg string) {
  252. if err != nil {
  253. log.Fatalf("ERROR: %s: %s", msg, err)
  254. }
  255. }