Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 44 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

mkcert is a simple tool for making locally-trusted development certificates. It requires no configuration.

```
```sh
$ mkcert -install
Created a new local CA 💥
The local CA is now installed in the system trust store! ⚡️
Expand All @@ -21,8 +21,6 @@ Created a new certificate valid for the following names 📜
The certificate is at "./example.com+5.pem" and the key at "./example.com+5-key.pem" ✅
```

<p align="center"><img width="498" alt="Chrome and Firefox screenshot" src="https://user-images.githubusercontent.com/1225294/51066373-96d4aa80-15be-11e9-91e2-f4e44a3a4458.png"></p>

Using certificates from real certificate authorities (CAs) for development can be dangerous or impossible (for hosts like `example.test`, `localhost` or `127.0.0.1`), but self-signed certificates cause trust errors. Managing your own CA is the best solution, but usually involves arcane commands, specialized knowledge and manual steps.

mkcert automatically creates and installs a local CA in the system root store, and generates locally-trusted certificates. mkcert does not automatically configure servers to use the certificates, though, that's up to you.
Expand All @@ -35,14 +33,14 @@ mkcert automatically creates and installs a local CA in the system root store, a

On macOS, use [Homebrew](https://brew.sh/)

```
```sh
brew install mkcert
brew install nss # if you use Firefox
```

or [MacPorts](https://www.macports.org/).

```
```sh
sudo port selfupdate
sudo port install mkcert
sudo port install nss # if you use Firefox
Expand All @@ -52,7 +50,7 @@ sudo port install nss # if you use Firefox

On Linux, first install `certutil`.

```
```sh
sudo apt install libnss3-tools
-or-
sudo yum install nss-tools
Expand All @@ -64,42 +62,42 @@ sudo zypper install mozilla-nss-tools

Then you can install using [Homebrew on Linux](https://docs.brew.sh/Homebrew-on-Linux)

```
```sh
brew install mkcert
```

or build from source (requires Go 1.13+)

```
```sh
git clone https://github.com/FiloSottile/mkcert && cd mkcert
go build -ldflags "-X main.Version=$(git describe --tags)"
```

or use [the pre-built binaries](https://github.com/FiloSottile/mkcert/releases).

```
```sh
curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"
chmod +x mkcert-v*-linux-amd64
sudo cp mkcert-v*-linux-amd64 /usr/local/bin/mkcert
```

For Arch Linux users, [`mkcert`](https://archlinux.org/packages/extra/x86_64/mkcert/) is available on the official Arch Linux repository.

```
```sh
sudo pacman -Syu mkcert
```

### Windows

On Windows, use [Chocolatey](https://chocolatey.org)

```
```sh
choco install mkcert
```

or use Scoop

```
```sh
scoop bucket add extras
scoop install mkcert
```
Expand All @@ -115,9 +113,9 @@ mkcert supports the following root stores:
* macOS system store
* Windows system store
* Linux variants that provide either
* `update-ca-trust` (Fedora, RHEL, CentOS) or
* `update-ca-certificates` (Ubuntu, Debian, OpenSUSE, SLES) or
* `trust` (Arch)
* `update-ca-trust` (Fedora, RHEL, CentOS) or
* `update-ca-certificates` (Ubuntu, Debian, OpenSUSE, SLES) or
* `trust` (Arch)
* Firefox (macOS and Linux only)
* Chrome and Chromium
* Java (when `JAVA_HOME` is set)
Expand All @@ -128,38 +126,52 @@ To only install the local root CA into a subset of them, you can set the `TRUST_

### Advanced options

```
-cert-file FILE, -key-file FILE, -p12-file FILE
Customize the output paths.
```text
-cert-file FILE, -key-file FILE, -p12-file FILE
Customize the output paths.

-client
Generate a certificate for client authentication.

-ecdsa
Generate a certificate with an ECDSA key.

-client
Generate a certificate for client authentication.
-pkcs12
Generate a ".p12" PKCS #12 file, also know as a ".pfx" file,
containing certificate and key for legacy applications.

-ecdsa
Generate a certificate with an ECDSA key.
-csr CSR
Generate a certificate based on the supplied CSR. Conflicts with
all other flags and arguments except -install and -cert-file.

-pkcs12
Generate a ".p12" PKCS #12 file, also know as a ".pfx" file,
containing certificate and key for legacy applications.
-validity DURATION, -cert-validity DURATION
Set custom validity for the certificate (e.g. 2y3m, 365d, 30d, 24h).
Defaults to 2 years and 3 months.

-csr CSR
Generate a certificate based on the supplied CSR. Conflicts with
all other flags and arguments except -install and -cert-file.
-days DAYS
Set custom certificate validity in days (alias for -validity DAYSd).

-ca-validity DURATION
Set custom validity for the CA certificate when creating a new CA
(e.g. 10y, 5y, 3650d). Defaults to 10 years.
```

> **Note:** You _must_ place these options before the domain names list.

#### Example

```
```sh
mkcert -key-file key.pem -cert-file cert.pem example.com *.example.com
mkcert -validity 30d example.com
mkcert -days 90 example.com
mkcert -ca-validity 5y -install
```

### S/MIME

mkcert automatically generates an S/MIME certificate if one of the supplied names is an email address.

```
```sh
mkcert filippo@example.com
```

Expand All @@ -175,7 +187,7 @@ For Android, you will have to install the CA and then enable user roots in the d

Node does not use the system root store, so it won't accept mkcert certificates automatically. Instead, you will have to set the [`NODE_EXTRA_CA_CERTS`](https://nodejs.org/api/cli.html#cli_node_extra_ca_certs_file) environment variable.

```
```sh
export NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem"
```

Expand All @@ -194,4 +206,4 @@ Installing in the trust store does not require the CA key, so you can export the
* set `$CAROOT` to its directory
* run `mkcert -install`

Remember that mkcert is meant for development purposes, not production, so it should not be used on end users' machines, and that you should *not* export or share `rootCA-key.pem`.
Remember that mkcert is meant for development purposes, not production, so it should not be used on end users' machines, and that you should _not_ export or share `rootCA-key.pem`.
42 changes: 35 additions & 7 deletions cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ func (m *mkcert) makeCert(hosts []string) {
fatalIfErr(err, "failed to generate certificate key")
pub := priv.(crypto.Signer).Public()

// Certificates last for 2 years and 3 months, which is always less than
// Certificates last for 2 years and 3 months by default, which is always less than
// 825 days, the limit that macOS/iOS apply to all certificates,
// including custom roots. See https://support.apple.com/en-us/HT210176.
expiration := time.Now().AddDate(2, 3, 0)
notBefore := time.Now()
expiration := m.certExpiration(notBefore)

tpl := &x509.Certificate{
SerialNumber: randomSerialNumber(),
Expand All @@ -68,7 +69,7 @@ func (m *mkcert) makeCert(hosts []string) {
OrganizationalUnit: []string{userAndHostname},
},

NotBefore: time.Now(), NotAfter: expiration,
NotBefore: notBefore, NotAfter: expiration,

KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
}
Expand Down Expand Up @@ -225,13 +226,14 @@ func (m *mkcert) makeCertFromCSR() {
fatalIfErr(err, "failed to parse the CSR")
fatalIfErr(csr.CheckSignature(), "invalid CSR signature")

expiration := time.Now().AddDate(2, 3, 0)
notBefore := time.Now()
expiration := m.certExpiration(notBefore)
tpl := &x509.Certificate{
SerialNumber: randomSerialNumber(),
Subject: csr.Subject,
ExtraExtensions: csr.Extensions, // includes requested SANs, KUs and EKUs

NotBefore: time.Now(), NotAfter: expiration,
NotBefore: notBefore, NotAfter: expiration,

// 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,
Expand Down Expand Up @@ -282,6 +284,8 @@ func (m *mkcert) makeCertFromCSR() {
func (m *mkcert) loadCA() {
if !pathExists(filepath.Join(m.CAROOT, rootName)) {
m.newCA()
} else if m.caValidity != "" {
log.Println("Note: the local CA already exists, -ca-validity was ignored (to recreate the CA, delete rootCA.pem and rootCA-key.pem from CAROOT)")
}

certPEMBlock, err := ioutil.ReadFile(filepath.Join(m.CAROOT, rootName))
Expand Down Expand Up @@ -324,6 +328,9 @@ func (m *mkcert) newCA() {

skid := sha1.Sum(spki.SubjectPublicKey.Bytes)

notBefore := time.Now()
expiration := m.caExpiration(notBefore)

tpl := &x509.Certificate{
SerialNumber: randomSerialNumber(),
Subject: pkix.Name{
Expand All @@ -337,8 +344,8 @@ func (m *mkcert) newCA() {
},
SubjectKeyId: skid[:],

NotAfter: time.Now().AddDate(10, 0, 0),
NotBefore: time.Now(),
NotAfter: expiration,
NotBefore: notBefore,

KeyUsage: x509.KeyUsageCertSign,

Expand Down Expand Up @@ -366,3 +373,24 @@ func (m *mkcert) newCA() {
func (m *mkcert) caUniqueName() string {
return "mkcert development CA " + m.caCert.SerialNumber.String()
}

func (m *mkcert) certExpiration(notBefore time.Time) time.Time {
if m.certValidity != "" {
dur, err := parseValidity(m.certValidity)
fatalIfErr(err, "invalid certificate validity")
return dur.apply(notBefore)
}
// Certificates last for 2 years and 3 months, which is always less than
// 825 days, the limit that macOS/iOS apply to all certificates,
// including custom roots. See https://support.apple.com/en-us/HT210176.
return notBefore.AddDate(2, 3, 0)
}

func (m *mkcert) caExpiration(notBefore time.Time) time.Time {
if m.caValidity != "" {
dur, err := parseValidity(m.caValidity)
fatalIfErr(err, "invalid CA validity")
return dur.apply(notBefore)
}
return notBefore.AddDate(10, 0, 0)
}
69 changes: 65 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"regexp"
"runtime"
"runtime/debug"
"strconv"
"strings"
"sync"

Expand Down Expand Up @@ -65,13 +66,30 @@ const advancedUsage = `Advanced options:
Generate a certificate based on the supplied CSR. Conflicts with
all other flags and arguments except -install and -cert-file.

-validity DURATION, -cert-validity DURATION
Set custom validity for the certificate (e.g. 2y3m, 365d, 30d, 24h).
Defaults to 2 years and 3 months.

-days DAYS
Set custom certificate validity in days (alias for -validity DAYSd).

-ca-validity DURATION
Set custom validity for the CA certificate when creating a new CA
(e.g. 10y, 5y, 3650d). Defaults to 10 years.

-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.)

$CA_VALIDITY (environment variable)
Set custom validity for the CA certificate when creating a new CA.

$CERT_VALIDITY (environment variable)
Set custom validity for certificates.

$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
Expand Down Expand Up @@ -99,10 +117,14 @@ func main() {
helpFlag = flag.Bool("help", false, "")
carootFlag = flag.Bool("CAROOT", false, "")
csrFlag = flag.String("csr", "", "")
certFileFlag = flag.String("cert-file", "", "")
keyFileFlag = flag.String("key-file", "", "")
p12FileFlag = flag.String("p12-file", "", "")
versionFlag = flag.Bool("version", false, "")
certFileFlag = flag.String("cert-file", "", "")
keyFileFlag = flag.String("key-file", "", "")
p12FileFlag = flag.String("p12-file", "", "")
versionFlag = flag.Bool("version", false, "")
caValidityFlag = flag.String("ca-validity", "", "")
certValidityFlag = flag.String("cert-validity", "", "")
validityFlag = flag.String("validity", "", "")
daysFlag = flag.Int("days", 0, "")
)
flag.Usage = func() {
fmt.Fprint(flag.CommandLine.Output(), shortUsage)
Expand Down Expand Up @@ -142,10 +164,48 @@ func main() {
if *csrFlag != "" && flag.NArg() != 0 {
log.Fatalln("ERROR: can't specify extra arguments when using -csr")
}

caValidity := *caValidityFlag
if caValidity == "" {
caValidity = os.Getenv("CA_VALIDITY")
}
if caValidity != "" {
if _, err := parseValidity(caValidity); err != nil {
log.Fatalf("ERROR: invalid CA validity %q: %s\n", caValidity, err)
}
}

if *daysFlag < 0 {
log.Fatalln("ERROR: -days must be greater than zero")
}
if *daysFlag > 0 && (*certValidityFlag != "" || *validityFlag != "") {
log.Fatalln("ERROR: cannot specify both -days and -validity/-cert-validity")
}
if *certValidityFlag != "" && *validityFlag != "" && *certValidityFlag != *validityFlag {
log.Fatalln("ERROR: cannot specify differing values for -cert-validity and -validity")
}

certValidity := *certValidityFlag
if certValidity == "" {
certValidity = *validityFlag
}
if certValidity == "" && *daysFlag > 0 {
certValidity = strconv.Itoa(*daysFlag) + "d"
}
if certValidity == "" {
certValidity = os.Getenv("CERT_VALIDITY")
}
if certValidity != "" {
if _, err := parseValidity(certValidity); err != nil {
log.Fatalf("ERROR: invalid certificate validity %q: %s\n", certValidity, err)
}
}

(&mkcert{
installMode: *installFlag, uninstallMode: *uninstallFlag, csrPath: *csrFlag,
pkcs12: *pkcs12Flag, ecdsa: *ecdsaFlag, client: *clientFlag,
certFile: *certFileFlag, keyFile: *keyFileFlag, p12File: *p12FileFlag,
caValidity: caValidity, certValidity: certValidity,
}).Run(flag.Args())
}

Expand All @@ -157,6 +217,7 @@ type mkcert struct {
pkcs12, ecdsa, client bool
keyFile, certFile, p12File string
csrPath string
caValidity, certValidity string

CAROOT string
caCert *x509.Certificate
Expand Down
Loading