2018-06-25 05:32:41 +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.
// Command mkcert is a simple zero-config tool to make development certificates.
package main
import (
"crypto"
"crypto/x509"
"flag"
2018-07-03 21:33:14 +00:00
"fmt"
2018-06-25 05:32:41 +00:00
"log"
2018-06-26 03:47:28 +00:00
"net"
2018-06-25 05:32:41 +00:00
"os"
"path/filepath"
2018-06-26 03:47:28 +00:00
"regexp"
2018-06-25 05:32:41 +00:00
"runtime"
2018-06-28 05:44:13 +00:00
"golang.org/x/net/idna"
2018-06-25 05:32:41 +00:00
)
2018-07-07 04:07:41 +00:00
const usage = ` Usage of mkcert :
$ mkcert - install
Install the local CA in the system trust store .
$ mkcert example . org
Generate "example.org.pem" and "example.org-key.pem" .
$ mkcert example . com myapp . dev localhost 127.0 .0 .1 : : 1
Generate "example.com+4.pem" and "example.com+4-key.pem" .
$ mkcert ' * . example . com '
Generate "_wildcard.example.com.pem" and "_wildcard.example.com-key.pem" .
$ mkcert - uninstall
Uninstall the local CA ( but do not delete it ) .
Change the CA certificate and key storage location by setting $ CAROOT ,
print it with "mkcert -CAROOT" .
`
2018-06-25 05:32:41 +00:00
func main ( ) {
log . SetFlags ( 0 )
2018-06-26 03:47:28 +00:00
var installFlag = flag . Bool ( "install" , false , "install the local root CA in the system trust store" )
var uninstallFlag = flag . Bool ( "uninstall" , false , "uninstall the local root CA from the system trust store" )
2018-07-03 21:33:14 +00:00
var carootFlag = flag . Bool ( "CAROOT" , false , "print the CAROOT path" )
2018-07-07 04:07:41 +00:00
flag . Usage = func ( ) { fmt . Fprintf ( flag . CommandLine . Output ( ) , usage ) }
2018-06-25 05:32:41 +00:00
flag . Parse ( )
2018-07-03 21:33:14 +00:00
if * carootFlag {
if * installFlag || * uninstallFlag {
log . Fatalln ( "ERROR: you can't set -[un]install and -CAROOT at the same time" )
}
fmt . Println ( getCAROOT ( ) )
return
}
2018-06-26 03:47:28 +00:00
if * installFlag && * uninstallFlag {
log . Fatalln ( "ERROR: you can't set -install and -uninstall at the same time" )
}
( & mkcert {
installMode : * installFlag , uninstallMode : * uninstallFlag ,
} ) . Run ( flag . Args ( ) )
2018-06-25 05:32:41 +00:00
}
const rootName = "rootCA.pem"
const keyName = "rootCA-key.pem"
type mkcert struct {
2018-06-26 03:47:28 +00:00
installMode , uninstallMode bool
2018-06-25 05:32:41 +00:00
CAROOT string
caCert * x509 . Certificate
caKey crypto . PrivateKey
// The system cert pool is only loaded once. After installing the root, checks
// will keep failing until the next execution. TODO: maybe execve?
// https://github.com/golang/go/issues/24540 (thanks, myself)
ignoreCheckFailure bool
}
2018-06-26 03:47:28 +00:00
func ( m * mkcert ) Run ( args [ ] string ) {
2018-06-25 05:32:41 +00:00
m . CAROOT = getCAROOT ( )
if m . CAROOT == "" {
log . Fatalln ( "ERROR: failed to find the default CA location, set one as the CAROOT env var" )
}
fatalIfErr ( os . MkdirAll ( m . CAROOT , 0755 ) , "failed to create the CAROOT" )
m . loadCA ( )
2018-06-26 03:47:28 +00:00
if m . installMode {
2018-06-25 05:32:41 +00:00
m . install ( )
2018-06-26 03:47:28 +00:00
if len ( args ) == 0 {
return
}
} else if m . uninstallMode {
m . uninstall ( )
return
2018-06-28 05:29:20 +00:00
} else {
var warning bool
if ! m . checkPlatform ( ) {
warning = true
log . Println ( "Warning: the local CA is not installed in the system trust store! ⚠️" )
}
2018-08-13 01:44:02 +00:00
if hasNSS && CertutilInstallHelp != "" && ! m . checkNSS ( ) {
2018-06-28 05:29:20 +00:00
warning = true
2018-07-04 02:26:37 +00:00
log . Printf ( "Warning: the local CA is not installed in the %s trust store! ⚠️" , NSSBrowsers )
2018-06-28 05:29:20 +00:00
}
2018-07-30 02:22:27 +00:00
if hasJava && ! m . checkJava ( ) {
warning = true
log . Println ( "Warning: the local CA is not installed in the Java trust store! ⚠️" )
}
2018-06-28 05:29:20 +00:00
if warning {
log . Println ( "Run \"mkcert -install\" to avoid verification errors ‼️" )
}
2018-06-25 05:32:41 +00:00
}
2018-06-26 03:47:28 +00:00
if len ( args ) == 0 {
2018-07-07 04:07:41 +00:00
log . Printf ( "\n%s" , usage )
2018-06-26 03:47:28 +00:00
return
}
2018-06-28 02:38:48 +00:00
hostnameRegexp := regexp . MustCompile ( ` (?i)^(\*\.)?[0-9a-z_-]([0-9a-z._-]*[0-9a-z_-])?$ ` )
2018-06-28 05:44:13 +00:00
for i , name := range args {
2018-06-26 03:47:28 +00:00
if ip := net . ParseIP ( name ) ; ip != nil {
continue
}
2018-06-28 05:44:13 +00:00
punycode , err := idna . ToASCII ( name )
if err != nil {
log . Fatalf ( "ERROR: %q is not a valid hostname or IP: %s" , name , err )
}
args [ i ] = punycode
2018-07-03 20:55:12 +00:00
if ! hostnameRegexp . MatchString ( punycode ) {
log . Fatalf ( "ERROR: %q is not a valid hostname or IP" , name )
2018-06-26 03:47:28 +00:00
}
}
m . makeCert ( args )
}
2018-06-25 05:32:41 +00:00
func getCAROOT ( ) string {
if env := os . Getenv ( "CAROOT" ) ; env != "" {
return env
}
var dir string
2018-07-07 00:09:58 +00:00
switch {
case runtime . GOOS == "windows" :
2018-06-25 05:32:41 +00:00
dir = os . Getenv ( "LocalAppData" )
2018-07-07 00:09:58 +00:00
case os . Getenv ( "XDG_DATA_HOME" ) != "" :
dir = os . Getenv ( "XDG_DATA_HOME" )
case runtime . GOOS == "darwin" :
2018-06-25 05:32:41 +00:00
dir = os . Getenv ( "HOME" )
if dir == "" {
return ""
}
dir = filepath . Join ( dir , "Library" , "Application Support" )
default : // Unix
2018-07-07 00:02:49 +00:00
dir = os . Getenv ( "HOME" )
2018-06-25 05:32:41 +00:00
if dir == "" {
2018-07-07 00:02:49 +00:00
return ""
2018-06-25 05:32:41 +00:00
}
2018-07-07 00:02:49 +00:00
dir = filepath . Join ( dir , ".local" , "share" )
2018-06-25 05:32:41 +00:00
}
return filepath . Join ( dir , "mkcert" )
}
func ( m * mkcert ) install ( ) {
2018-06-28 05:29:20 +00:00
var printed bool
if ! m . checkPlatform ( ) {
2018-07-04 04:06:50 +00:00
if m . installPlatform ( ) {
2018-06-29 20:02:23 +00:00
log . Print ( "The local CA is now installed in the system trust store! ⚡️" )
}
2018-07-04 04:06:50 +00:00
m . ignoreCheckFailure = true // TODO: replace with a check for a successful install
2018-06-28 05:29:20 +00:00
printed = true
2018-06-25 05:32:41 +00:00
}
2018-07-04 02:26:37 +00:00
if hasNSS && ! m . checkNSS ( ) {
2018-08-13 00:32:34 +00:00
if hasCertutil && m . installNSS ( ) {
2018-07-04 02:26:37 +00:00
log . Printf ( "The local CA is now installed in the %s trust store (requires browser restart)! 🦊" , NSSBrowsers )
2018-08-13 01:44:02 +00:00
} else if CertutilInstallHelp == "" {
log . Printf ( ` Note: %s support is not available on your platform. ℹ ️ ` , NSSBrowsers )
2018-08-13 00:32:34 +00:00
} else if ! hasCertutil {
2018-07-04 02:26:37 +00:00
log . Printf ( ` Warning: "certutil" is not available, so the CA can't be automatically installed in %s! ⚠️ ` , NSSBrowsers )
2018-06-28 05:29:20 +00:00
log . Printf ( ` Install "certutil" with "%s" and re-run "mkcert -install" 👈 ` , CertutilInstallHelp )
}
printed = true
}
2018-07-30 02:22:27 +00:00
if hasJava && ! m . checkJava ( ) {
if hasKeytool {
m . installJava ( )
log . Println ( "The local CA is now installed in Java's trust store! ☕️" )
} else {
log . Println ( ` Warning: "keytool" is not available, so the CA can't be automatically installed in Java's trust store! ⚠️ ` )
}
printed = true
}
2018-06-28 05:29:20 +00:00
if printed {
log . Print ( "" )
2018-06-25 05:32:41 +00:00
}
2018-06-26 03:47:28 +00:00
}
func ( m * mkcert ) uninstall ( ) {
2018-07-04 02:26:37 +00:00
if hasNSS {
2018-06-28 05:29:20 +00:00
if hasCertutil {
2018-07-04 02:26:37 +00:00
m . uninstallNSS ( )
2018-08-13 01:44:02 +00:00
} else if CertutilInstallHelp != "" {
2018-06-28 05:29:20 +00:00
log . Print ( "" )
2018-07-04 02:26:37 +00:00
log . Printf ( ` Warning: "certutil" is not available, so the CA can't be automatically uninstalled from %s (if it was ever installed)! ⚠️ ` , NSSBrowsers )
2018-06-28 05:29:20 +00:00
log . Printf ( ` You can install "certutil" with "%s" and re-run "mkcert -uninstall" 👈 ` , CertutilInstallHelp )
log . Print ( "" )
}
}
2018-07-30 02:22:27 +00:00
if hasJava {
if hasKeytool {
m . uninstallJava ( )
} else {
log . Print ( "" )
log . Println ( ` Warning: "keytool" is not available, so the CA can't be automatically uninstalled from Java's trust store (if it was ever installed)! ⚠️ ` )
log . Print ( "" )
}
}
2018-07-04 04:06:50 +00:00
if m . uninstallPlatform ( ) {
log . Print ( "The local CA is now uninstalled from the system trust store(s)! 👋" )
log . Print ( "" )
} else if hasCertutil {
log . Printf ( "The local CA is now uninstalled from the %s trust store(s)! 👋" , NSSBrowsers )
log . Print ( "" )
}
2018-06-25 05:32:41 +00:00
}
2018-06-28 05:29:20 +00:00
func ( m * mkcert ) checkPlatform ( ) bool {
2018-06-25 05:32:41 +00:00
if m . ignoreCheckFailure {
return true
}
_ , err := m . caCert . Verify ( x509 . VerifyOptions { } )
return err == nil
}
func fatalIfErr ( err error , msg string ) {
if err != nil {
log . Fatalf ( "ERROR: %s: %s" , msg , err )
}
}
2018-06-28 05:29:20 +00:00
func fatalIfCmdErr ( err error , cmd string , out [ ] byte ) {
if err != nil {
log . Fatalf ( "ERROR: failed to execute \"%s\": %s\n\n%s\n" , cmd , err , out )
}
}