This guide will show how to set up your own client authentication CA.
Client authentication using client certificates allows to access websites without entering passwords, just by selecting the right certificate as prompted by the browser.
This could be used to protect any self-hosted service you might have without relying on their built-in authentication and the flaws it might have.
We will set up a root CA which will be used to sign the client certificates. For personal use it is typically fine to store the root CA keys on a computer, but it is possible to use a hardware token for this purpose if you set up OpenSSL for it as in the Set up OpenSSL for PKCS#11 tokens article, for instance backing it with a Yubikey.
It is also possible to store the client certificate on a Yubikey for authenticating from anywhere, as we will see in the Use a Yubikey for authentication section.
Make the root CA
We will make a simple CA architecture with one root CA that can only sign client certificates made for client authentication.
The attribute basicConstraints = critical, CA:TRUE, pathlen:0 will ensure that
only client certificates can be signed by the root CA.
The attribute extendedKeyUsage = clientAuth will ensure that only client
authentication certificates can be signed by the CA.
Generate the root CA with a P-384 key:
openssl req -new -x509 \
-newkey EC -pkeyopt ec_paramgen_curve:P-384 -sha384 \
-subj '/CN=Client authentication root CA' \
-addext basicConstraints=critical,CA:TRUE,pathlen:0 \
-addext keyUsage=critical,keyCertSign \
-addext extendedKeyUsage=clientAuth \
-keyout ca.key -out ca.pem \
-days +3650
You will be prompted for a passphrase to protect the private key with.
The passphrase should be strong enough, and the private key needs to be stored in a safe location. It is also possible to use a Yubikey to back the CA, then follow the Sign a Yubikey certificate article to create the certificate.
Optional: sign a CRL
Having a CRL will allow you to revoke certificates before they reach their expiration date, for instance after a private key compromise.
Get my tool make-crl from PyPI, either with pip from within a virtual environment
or with pipx:
# Install for the current user
pipx install make-crl
# Or install in a virtual environment (install the package python3-venv on Debian or similar)
python3 -m venv venv-make-crl
source venv-make-crl/activate # run the command deactivate to exit the virtual environment
pip install make-crl
You can also install with pip as the current user, but if you are running Debian or
similar then pip will complain about it.
Sign an empty (for now!) CRL:
make-crl make-empty ca.pem ca.key ca.crl
And convert it to PEM format for use with nginx:
openssl crl -outform PEM < ca.crl > ca-crl.pem
See instructions in the section Revoke a certificate for instructions on how to revoke the certificate.
Make a client certificate
Now we will make the first client certificate and store the private key on the target device. For using a Yubikey instead see the section Use a Yubikey for authentication.
You can of course customize the common name in the subject to fit the device that will hold the certificate; as well as change the validity time.
openssl req -new -x509 \
-newkey EC -pkeyopt ec_paramgen_curve:P-384 -sha384 -noenc \
-CA ca.pem -CAkey ca.key \
-subj '/CN=My client authentication certificate' \
-addext basicConstraints=critical,CA:FALSE \
-addext keyUsage=critical,digitalSignature \
-addext extendedKeyUsage=clientAuth \
-out client-auth.pem -keyout client-auth.key \
-days +365
Then make the .pfx file that will be imported into the device, and shred the
unprotected private key:
openssl pkcs12 -export \
-in client-auth.pem -inkey client-auth.key \
-chain -CAfile ca.pem \
-out client-auth.pfx
shred -u client-auth.key
You will be prompted for an export password that you will use on the target device
when importing the .pfx file.
Keep the certificate client-auth.pem next to the CA certificate and key for when
the need for revocation arises
Now you can import the .pfx file, for instance in Google Chrome or in Firefox,
in the Security section of the settings.
Set up nginx for client authentication
Upload ca.pem (and ca-crl.pem if you made one) to the server for instance
in /etc/nginx/certs.
Then in the server { } block that you want to protect, add the following:
server {
# ...
ssl_client_certificate /etc/nginx/certs/ca.pem;
ssl_crl /etc/nginx/certs/ca-crl.pem; # if you made a CRL
ssl_verify_client on;
# ...
}
The clients will be prompted to select a certificate when accessing the website, and will be allowed access only if the certificate is signed by the CA and the client has the corresponding private key material.
Optional: use a Yubikey for authentication
To generate the key and the certificate signing request you will need the
yubikey-manager tool:
sudo apt install yubikey-manager
Initial setup
If you did not set up the PIN, PUK and management key yet, the defaults are:
- PIN:
123456 - PUK:
12345678 - Management key:
010203040506070801020304050607080102030405060708
It is going to be most convenient if the new management key is stored on the Yubikey itsef, protected by the PIN:
# Follow the prompts, touch your key when prompted
ykman piv access --touch --generate --protect --algorithm aes256
Then change the PIN:
ykman piv access change-pin
And the PUK:
ykman piv access change-puk
Key generation and certificate signing
Generate a P-384 key:
ykman piv keys generate \
--algorithm eccp384 \
--format pem \
--pin-policy once \
--touch-policy never \
9A pubkey.pem
Now sign a certificate signing request:
ykman piv certificates request --hash-algorithm sha384 -s 'CSR' 9A pubkey.pem yubikey.csr
Once you have the certificate signing request you can sign it as such:
openssl req -x509 \
-in yubikey.csr \
-copy_extensions none \
-CA ca.pem -CAkey ca.key \
-subj '/CN=My client authentication certificate' \
-addext basicConstraints=critical,CA:FALSE \
-addext keyUsage=critical,digitalSignature \
-addext extendedKeyUsage=clientAuth \
-out client-auth.pem \
-days +365
Then import the certificate onto the card:
ykman piv certificates import --verify --compress 9A client-auth.pem
Now to use the card on Linux you need to install the package opensc-pkcs11.
Windows and MacOS should not need any driver.
Optional: revoke a certificate
This operation needs to be performed when a device private key is lost, or when the device is decommissioned.
Take the previous .crl file that is either empty (generated in section
Sign a CRL) or one that contains previously revoked certificates,
as well as the certificate you want to revoke, then run:
make-crl revoke --reason keyCompromise ca.pem ca.key ca.crl client-auth.pem
openssl crl -outform PEM < ca.crl > ca-crl.pem
You will be prompted for the CA private key passphrase.
You can specify another reason than keyCompromise, for instance cessationOfOperation
or just leave out the reason entirely (which will then default to unspecified).
Then upload ca-crl.pem to the nginx server in the configured place and reload it:
sudo systemctl reload nginx