If you are developing services which need to authenticate each other by mTLS you need a triplet of x509 certificate files: A root CA, client certificates and servers certificates and corresponding key files. One way to generate these files locally is to use openssl, but this is rather tedious as you must configure a lot to use this tool. A more elegant solution is to use the CloudFlare PKI/TLS toolkit.
In this post I am going to explain how to create all files you need to test a mTLS connection.
Setup
To install CFSSL you can either use brew or install it directly with the go tool chain. For the later you need a working go installation.
Use brew:
brew install cfssl
For installing CFSSL with go tool chain see the offical documentation.
Root CA
First you need a Root CA (Certificate Authority) from which all other certificates are derived from.
CFSSL supports configuring variables for certificates in a JSON format. So first we create a JSON file with variables for our CA:
Filename: configs/ca.json
{
"CN": "www.radile.net",
"key": {
"algo": "rsa",
"size": 4096
},
"names": [
{
"C": "DE",
"L": "Cologne",
"O": "Martin Radile",
"OU": "cfssldemo",
"ST": "NRW"
}
]
}
To create the CA files run the following command:
mkdir -p certs
cfssl gencert -initca \
configs/ca.json | cfssljson -bare certs/ca
This will create a new CA with the variables configured in configs/ca.json. To show the contents of the CA certificate run:
openssl x509 -in certs/ca.pem -text
The output will be somewhat like this:
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
01:02:c7:ec:cc:d9:20:af:c5:f9:7c:34:79:eb:f6:6c:74:30:12:ca
Signature Algorithm: sha512WithRSAEncryption
Issuer: C=DE, ST=NRW, L=Cologne, O=Martin Radile, OU=cfssldemo, CN=www.radile.net
Validity
Not Before: Jan 31 20:42:00 2022 GMT
Not After : Jan 30 20:42:00 2027 GMT
Subject: C=DE, ST=NRW, L=Cologne, O=Martin Radile, OU=cfssldemo, CN=www.radile.net
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)
...
Profiles Configuration
Now that we have a new CA, we can create the certificates for our client and server applications. We create again a new configuration file to define profiles for certificate generation. These profiles are later referenced when creating the client and server files.
Filename: configs/ca-config.json
{
"signing": {
"profiles": {
"client": {
"usages": ["client auth"],
"expiry": "8760h"
},
"server": {
"usages": ["server auth"],
"expiry": "8760h"
}
}
}
}
The configuration contains two profiles named client and server. You can choose any name you like. The client profile tells CFSSL to create certificates which can be used for authentication as a client. The server profile lets CFSS generate certificates for a server application. Both profiles are configured to create certificates with of lifespan of one year.
We are now ready to create our server and client certificates.
Client Certificates
We need again a configuration file with parameters for our certificates:
Filename: configs/client.json
{
"key": {
"algo": "rsa",
"size": 4096
},
"names": [
{
"C": "DE",
"L": "Cologne",
"O": "Martin Radile",
"OU": "client cfssl demo",
"ST": "NRW"
}
]
}
To generate the client certificate run the following command:
cfssl gencert \
-ca=certs/ca.pem \
-ca-key=certs/ca-key.pem \
-config=configs/ca-config.json \
-profile=client \
configs/client.json | cfssljson -bare certs/client
We reference the CA files and our previously created client
profile. The last parameter for cfssljson certs/client
is the output directory and the filename prefix for the certificates.
Server Certificates
The configuration for the server certificates is looking nearly identical. The only difference is the hosts array. Here you can add hosts for which the generated certificate will be valid.
If you enable strict host checking in your client application, and you do not specify the correct hosts here the connection will fail.
Filename: configs/server.json
{
"hosts": [
"server.cfssldemo.radile.net",
"127.0.0.1",
"localhost"
],
"key": {
"algo": "rsa",
"size": 4096
},
"names": [
{
"C": "DE",
"L": "Cologne",
"O": "Martin Radile",
"OU": "server cfssl demo",
"ST": "NRW"
}
]
}
To generate the server certificates run:
cfssl gencert \
-ca=certs/ca.pem \
-ca-key=certs/ca-key.pem \
-config=configs/ca-config.json \
-profile=server \
configs/server.json | cfssljson -bare certs/server
The host names will be included in the SAN (Subject Alternative Name) field in the certificate:
openssl x509 -in certs/server.pem -text
...
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:server.cfssldemo.radile.net, DNS:localhost, IP Address:127.0.0.1
...
Files overview
If you followed all steps you should have the following file structure:
├── certs
│ ├── ca-key.pem
│ ├── ca.csr
│ ├── ca.pem
│ ├── client-key.pem
│ ├── client.csr
│ ├── client.pem
│ ├── server-key.pem
│ ├── server.csr
│ └── server.pem
└── configs
├── ca-config.json
├── ca.json
├── client.json
└── server.json
Distribute these files as follows to your client and server applications:
- client
- certs/ca.pem
- certs/client.pem
- certs/client-key.pem
- server
- certs/ca.pem
- certs/server.pem
- certs/server-key.pem
Full Code
You can find the full code with a Makefile here github.com/mradile/cfssl-mtls-demo.