How to use cfssl to create self signed certificates

Rob Blackbourn
3 min readJul 22, 2019

--

There are man articles on CloudFlare’s PKI toolkit, but no single article had all the tasks detailed for the pattern I use, so I thought I’d put them here.

Installation on Linux

Unfortunately, at the time of writing, the latest packaged version (1.2) contains a bug that makes it impossible to create certificates with hosts, so the software must be installed with Go.

$ sudo apt install golang
$ go get -u github.com/cloudflare/cfssl/cmd/cfssl
$ sudo cp ~/go/bin/cfssl /usr/local/bin/cfssl
$ go get -u github.com/cloudflare/cfssl/cmd/cfssljson
$ sudo cp ~/go/bin/cfssljson /usr/local/bin/cfssljson

The Pattern

The root of all the certificates is a certificate authority (or “CA”) from which all other certificates are signed. Typically this is used to create one or more intermediate certificate authorities. These intermediates are used to sign certificates for clients, servers and peers (a host that can act as both a client and a server).

The Certificate Authority

To create a self signed certificate authority for a company called “Custom Widgets” based in London, England, Great Britain, create the following config file “ca.json”.

{
"CN": "Custom Widgets Root CA",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "GB",
"L": "London",
"O": "Custom Widgets",
"OU": "Custom Widgets Root CA",
"ST": "England"
}
]
}

The following command creates “ca.pem” and “ca-key.pem”

$ cfssl gencert -initca ca.json | cfssljson -bare ca

Profiles

The next steps require a profile config file. The profile describes general details about the certificate. For example it’s duration, and usages.

Create the following file “cfssl.json”.

{
"signing": {
"default": {
"expiry": "8760h"
},
"profiles": {
"intermediate_ca": {
"usages": [
"signing",
"digital signature",
"key encipherment",
"cert sign",
"crl sign",
"server auth",
"client auth"
],
"expiry": "8760h",
"ca_constraint": {
"is_ca": true,
"max_path_len": 0,
"max_path_len_zero": true
}
},
"peer": {
"usages": [
"signing",
"digital signature",
"key encipherment",
"client auth",
"server auth"
],
"expiry": "8760h"
},
"server": {
"usages": [
"signing",
"digital signing",
"key encipherment",
"server auth"
],
"expiry": "8760h"
},
"client": {
"usages": [
"signing",
"digital signature",
"key encipherment",
"client auth"
],
"expiry": "8760h"
}
}
}
}

We can see how the “client” profile specifies “client auth” in its usages, while the “server” profile specifies “server auth”.

The Intermediate CA

To create an intermediate certificate authority create the following config file “intermediate-ca.json”.

{
"CN": "Custom Widgets Intermediate CA",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "GB",
"L": "London",
"O": "Custom Widgets",
"OU": "Custom Widgets Intermediate CA",
"ST": "England"
}
],
"ca": {
"expiry": "42720h"
}
}

The following commands creates “intermediate_ca.pem”, “intermediate_ca.csr” and “intermediate_ca-key.pem” and signs the certificate.

$ cfssl gencert -initca intermediate-ca.json | cfssljson -bare intermediate_ca
$ cfssl sign -ca ca.pem -ca-key ca-key.pem -config cfssl.json -profile intermediate_ca intermediate_ca.csr | cfssljson -bare intermediate_ca

Note the second “sign” command uses the CA produced previously to sign the intermediate CA. It also uses the “cfssl.json” profile and specifies the “intermediate_ca” profile.

Host Certificates

Here is an example host certificate config file “host1.json”.

{
"CN": "host.custom-widgets.com",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "GB",
"L": "London",
"O": "Custom Widgets",
"OU": "Custom Widgets Hosts",
"ST": "England"
}
],
"hosts": [
"host1.custom-widgets.com",
"localhost"
]
}

There are some choices to be made here that depend on what the server and client software expect. Some software just checks the common name “CN” and doesn’t need a hosts section. Some software requires the IP address in the hosts section.

To generate the certificates with the above config do the following:

$ cfssl gencert -ca intermediate_ca.pem -ca-key intermediate_ca-key.pem -config cfssl.json -profile=peer host-1.json | cfssljson -bare host-1-peer
$ cfssl gencert -ca intermediate_ca.pem -ca-key intermediate_ca-key.pem -config cfssl.json -profile=server host-1.json | cfssljson -bare host-1-server
$ cfssl gencert -ca intermediate_ca.pem -ca-key intermediate_ca-key.pem -config cfssl.json -profile=client host-1.json | cfssljson -bare host-1-client

Typically we just require a server certificate. Some systems also support client authentication, and clusters often require peer certificates to allow the servers to communicate with each other.

Installing The Certificates

Typically the public certificates are stored somewhere public, while the private keys are “locked away”; in particular the CA’s. We’re creating certificates for a development environment, but it simplifies things to put the certificates in expected places.

On Ubuntu 18.04 with the “ssl-cert” package installed, I put the public certificates in “/etc/ssl/certs” and the private certificates in “/etc/ssl/private”. The “ssl-cert” package creates a group “ssl-cert” which has ownership of the private folder. Apps which need access to private keys can be added to this group.

Update: 2019–11–08

I’ve created a makefile to automate this process here.

--

--