2019-06-01 13:58:20 +00:00
// Copyright 2018 The mkcert Authors. All rights reserved.
2018-06-25 05:32:41 +00:00
// 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"
2019-04-12 02:59:44 +00:00
"net/mail"
2019-07-05 04:16:19 +00:00
"net/url"
2018-06-25 05:32:41 +00:00
"os"
2019-06-01 13:55:58 +00:00
"os/exec"
2019-08-16 21:47:28 +00:00
"os/user"
2018-06-25 05:32:41 +00:00
"path/filepath"
2018-06-26 03:47:28 +00:00
"regexp"
2018-06-25 05:32:41 +00:00
"runtime"
2020-03-22 02:04:37 +00:00
"runtime/debug"
2019-02-02 21:03:48 +00:00
"strings"
2019-08-16 21:47:28 +00:00
"sync"
2018-06-28 05:44:13 +00:00
"golang.org/x/net/idna"
2018-06-25 05:32:41 +00:00
)
2019-02-02 19:55:55 +00:00
const shortUsage = ` Usage of mkcert :
2018-07-07 04:07:41 +00:00
$ 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" .
2019-02-02 19:55:55 +00:00
$ mkcert "*.example.it"
Generate "_wildcard.example.it.pem" and "_wildcard.example.it-key.pem" .
2018-08-13 03:21:46 +00:00
2018-07-07 04:07:41 +00:00
$ mkcert - uninstall
Uninstall the local CA ( but do not delete it ) .
2019-02-02 19:55:55 +00:00
`
const advancedUsage = ` Advanced options :
- cert - file FILE , - key - file FILE , - p12 - file FILE
Customize the output paths .
2019-02-02 20:44:12 +00:00
- client
Generate a certificate for client authentication .
2019-02-02 20:26:03 +00:00
- ecdsa
Generate a certificate with an ECDSA key .
2019-02-02 19:55:55 +00:00
- pkcs12
Generate a ".p12" PKCS # 12 file , also know as a ".pfx" file ,
containing certificate and key for legacy applications .
2019-02-02 23:51:24 +00:00
- csr CSR
Generate a certificate based on the supplied CSR . Conflicts with
all other flags and arguments except - install and - cert - file .
2019-02-02 19:55:55 +00:00
- CAROOT
Print the CA certificate and key storage location .
$ CAROOT ( environment variable )
Set the CA certificate and key storage location . ( This allows
maintaining multiple local CAs in parallel . )
2019-01-06 23:55:49 +00:00
2019-02-02 21:03:48 +00:00
$ TRUST_STORES ( environment variable )
A comma - separated list of trust stores to install the local
root CA into . Options are : "system" , "java" and "nss" ( includes
Firefox ) . Autodetected by default .
2018-07-07 04:07:41 +00:00
`
2020-03-22 02:04:37 +00:00
// Version can be set at link time to override debug.BuildInfo.Main.Version,
// which is "(devel)" when building from within the module. See
// golang.org/issue/29814 and golang.org/issue/29228.
var Version string
2019-11-09 21:06:19 +00:00
2018-06-25 05:32:41 +00:00
func main ( ) {
log . SetFlags ( 0 )
2019-02-02 19:55:55 +00:00
var (
installFlag = flag . Bool ( "install" , false , "" )
uninstallFlag = flag . Bool ( "uninstall" , false , "" )
pkcs12Flag = flag . Bool ( "pkcs12" , false , "" )
2019-02-02 20:26:03 +00:00
ecdsaFlag = flag . Bool ( "ecdsa" , false , "" )
2019-02-02 20:44:12 +00:00
clientFlag = flag . Bool ( "client" , false , "" )
2019-02-02 19:55:55 +00:00
helpFlag = flag . Bool ( "help" , false , "" )
carootFlag = flag . Bool ( "CAROOT" , false , "" )
2019-02-02 23:51:24 +00:00
csrFlag = flag . String ( "csr" , "" , "" )
2019-02-02 19:55:55 +00:00
certFileFlag = flag . String ( "cert-file" , "" , "" )
keyFileFlag = flag . String ( "key-file" , "" , "" )
p12FileFlag = flag . String ( "p12-file" , "" , "" )
2019-11-09 21:06:19 +00:00
versionFlag = flag . Bool ( "version" , false , "" )
2019-02-02 19:55:55 +00:00
)
flag . Usage = func ( ) {
2019-07-05 04:41:23 +00:00
fmt . Fprint ( flag . CommandLine . Output ( ) , shortUsage )
2019-02-02 19:55:55 +00:00
fmt . Fprintln ( flag . CommandLine . Output ( ) , ` For more options, run "mkcert -help". ` )
}
2018-06-25 05:32:41 +00:00
flag . Parse ( )
2019-02-02 19:55:55 +00:00
if * helpFlag {
2019-07-05 04:41:23 +00:00
fmt . Fprint ( flag . CommandLine . Output ( ) , shortUsage )
fmt . Fprint ( flag . CommandLine . Output ( ) , advancedUsage )
2019-02-02 19:55:55 +00:00
return
}
2019-11-09 21:06:19 +00:00
if * versionFlag {
2020-03-22 02:04:37 +00:00
if Version != "" {
fmt . Println ( Version )
2020-03-22 02:10:16 +00:00
return
2020-03-22 02:04:37 +00:00
}
if buildInfo , ok := debug . ReadBuildInfo ( ) ; ok {
fmt . Println ( buildInfo . Main . Version )
2020-03-22 02:10:16 +00:00
return
2020-03-22 02:04:37 +00:00
}
fmt . Println ( "(unknown)" )
2019-11-09 21:06:19 +00:00
return
}
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" )
}
2019-02-02 23:51:24 +00:00
if * csrFlag != "" && ( * pkcs12Flag || * ecdsaFlag || * clientFlag ) {
log . Fatalln ( "ERROR: can only combine -csr with -install and -cert-file" )
}
if * csrFlag != "" && flag . NArg ( ) != 0 {
log . Fatalln ( "ERROR: can't specify extra arguments when using -csr" )
}
2018-06-26 03:47:28 +00:00
( & mkcert {
2019-02-02 23:51:24 +00:00
installMode : * installFlag , uninstallMode : * uninstallFlag , csrPath : * csrFlag ,
2019-02-02 20:44:12 +00:00
pkcs12 : * pkcs12Flag , ecdsa : * ecdsaFlag , client : * clientFlag ,
2019-01-06 23:55:49 +00:00
certFile : * certFileFlag , keyFile : * keyFileFlag , p12File : * p12FileFlag ,
2018-06-26 03:47:28 +00:00
} ) . Run ( flag . Args ( ) )
2018-06-25 05:32:41 +00:00
}
const rootName = "rootCA.pem"
2019-01-06 23:55:49 +00:00
const rootKeyName = "rootCA-key.pem"
2018-06-25 05:32:41 +00:00
type mkcert struct {
2019-01-06 23:55:49 +00:00
installMode , uninstallMode bool
2019-02-02 20:44:12 +00:00
pkcs12 , ecdsa , client bool
2019-01-06 23:55:49 +00:00
keyFile , certFile , p12File string
2019-02-02 23:51:24 +00:00
csrPath string
2018-06-26 03:47:28 +00:00
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
2019-02-02 21:03:48 +00:00
if storeEnabled ( "system" ) && ! m . checkPlatform ( ) {
2018-06-28 05:29:20 +00:00
warning = true
log . Println ( "Warning: the local CA is not installed in the system trust store! ⚠️" )
}
2019-02-02 21:03:48 +00:00
if storeEnabled ( "nss" ) && 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
}
2019-02-02 21:03:48 +00:00
if storeEnabled ( "java" ) && hasJava && ! m . checkJava ( ) {
2018-07-30 02:22:27 +00:00
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
2019-02-02 23:51:24 +00:00
if m . csrPath != "" {
m . makeCertFromCSR ( )
return
}
2018-06-26 03:47:28 +00:00
if len ( args ) == 0 {
2019-02-02 19:55:55 +00:00
flag . 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
}
2019-04-12 02:59:44 +00:00
if email , err := mail . ParseAddress ( name ) ; err == nil && email . Address == name {
continue
}
2019-07-05 04:16:19 +00:00
if uriName , err := url . Parse ( name ) ; err == nil && uriName . Scheme != "" && uriName . Host != "" {
continue
}
2018-06-28 05:44:13 +00:00
punycode , err := idna . ToASCII ( name )
if err != nil {
2019-07-05 04:16:19 +00:00
log . Fatalf ( "ERROR: %q is not a valid hostname, IP, URL or email: %s" , name , err )
2018-06-28 05:44:13 +00:00
}
args [ i ] = punycode
2018-07-03 20:55:12 +00:00
if ! hostnameRegexp . MatchString ( punycode ) {
2019-07-05 04:16:19 +00:00
log . Fatalf ( "ERROR: %q is not a valid hostname, IP, URL or email" , 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 ( ) {
2019-08-16 22:04:46 +00:00
if storeEnabled ( "system" ) {
if m . checkPlatform ( ) {
log . Print ( "The local CA is already installed in the system trust store! 👍" )
} else {
if m . installPlatform ( ) {
log . Print ( "The local CA is now installed in the system trust store! ⚡️" )
}
m . ignoreCheckFailure = true // TODO: replace with a check for a successful install
2018-06-29 20:02:23 +00:00
}
2018-06-25 05:32:41 +00:00
}
2019-08-16 22:04:46 +00:00
if storeEnabled ( "nss" ) && hasNSS {
if m . checkNSS ( ) {
log . Printf ( "The local CA is already installed in the %s trust store! 👍" , NSSBrowsers )
} else {
if hasCertutil && m . installNSS ( ) {
log . Printf ( "The local CA is now installed in the %s trust store (requires browser restart)! 🦊" , NSSBrowsers )
} else if CertutilInstallHelp == "" {
log . Printf ( ` Note: %s support is not available on your platform. ℹ ️ ` , NSSBrowsers )
} else if ! hasCertutil {
log . Printf ( ` Warning: "certutil" is not available, so the CA can't be automatically installed in %s! ⚠️ ` , NSSBrowsers )
log . Printf ( ` Install "certutil" with "%s" and re-run "mkcert -install" 👈 ` , CertutilInstallHelp )
}
2018-06-28 05:29:20 +00:00
}
}
2019-08-16 22:04:46 +00:00
if storeEnabled ( "java" ) && hasJava {
if m . checkJava ( ) {
log . Println ( "The local CA is already installed in Java's trust store! 👍" )
2018-07-30 02:22:27 +00:00
} else {
2019-08-16 22:04:46 +00:00
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! ⚠️ ` )
}
2018-07-30 02:22:27 +00:00
}
2018-06-25 05:32:41 +00:00
}
2019-08-16 22:04:46 +00:00
log . Print ( "" )
2018-06-26 03:47:28 +00:00
}
func ( m * mkcert ) uninstall ( ) {
2019-02-02 21:03:48 +00:00
if storeEnabled ( "nss" ) && 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 ( "" )
}
}
2019-02-02 21:03:48 +00:00
if storeEnabled ( "java" ) && hasJava {
2018-07-30 02:22:27 +00:00
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 ( "" )
}
}
2019-02-02 21:03:48 +00:00
if storeEnabled ( "system" ) && m . uninstallPlatform ( ) {
2018-07-04 04:06:50 +00:00
log . Print ( "The local CA is now uninstalled from the system trust store(s)! 👋" )
log . Print ( "" )
2019-02-02 21:03:48 +00:00
} else if storeEnabled ( "nss" ) && hasCertutil {
2018-07-04 04:06:50 +00:00
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
}
2019-02-02 21:03:48 +00:00
func storeEnabled ( name string ) bool {
stores := os . Getenv ( "TRUST_STORES" )
if stores == "" {
return true
}
for _ , store := range strings . Split ( stores , "," ) {
if store == name {
return true
}
}
return false
}
2018-06-25 05:32:41 +00:00
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 )
}
}
2019-06-01 13:55:58 +00:00
func pathExists ( path string ) bool {
_ , err := os . Stat ( path )
return err == nil
}
func binaryExists ( name string ) bool {
_ , err := exec . LookPath ( name )
return err == nil
}
2019-08-16 21:47:28 +00:00
var sudoWarningOnce sync . Once
func commandWithSudo ( cmd ... string ) * exec . Cmd {
if u , err := user . Current ( ) ; err == nil && u . Uid == "0" {
return exec . Command ( cmd [ 0 ] , cmd [ 1 : ] ... )
}
if ! binaryExists ( "sudo" ) {
sudoWarningOnce . Do ( func ( ) {
log . Println ( ` Warning: "sudo" is not available, and mkcert is not running as root. The (un)install operation might fail. ⚠️ ` )
} )
return exec . Command ( cmd [ 0 ] , cmd [ 1 : ] ... )
}
return exec . Command ( "sudo" , append ( [ ] string { "--prompt=Sudo password:" , "--" } , cmd ... ) ... )
}