Using YubiKeys with X.509 certificates on macOS
This tutorial will show you how to manage X.509 certificates with a YubiKey on macOS using OpenSSL to privision your own Public Key Infrastructure with a root certificate authority, intermediate certificate authority, and end entity certificate signing.
Tools of the trade
yubico-piv-tool to generate the keys on the YubiKey and edit the configuration, we’ll use
ykman to reset the PIV data (optional), and then OpenSC and engine-pkcs11 to talk to the key, as well as OpenSSL to drive the whole thing and manipulate certificates.
You can install these with Homebrew using the following command:
brew install ykman yubico-piv-tool openssl opensc engine-pkcs11
We’ll use our own OpenSSL from Homebrew to make sure it plays nicely with the other packages.
Clear your YubiKey PIV data (optional)
Note: This will completely erase the Personal Identification Verification (PIV) data on your key.
If you want to clear the X.509 part of your YubiKey, you can issue the following command to reset it:
ykman piv reset
This will set the management key, PUK, and PIN to the default values.
Note: If you don’t clear your PIV data, you’ll have to enter the management key or PIN for commands. Using
yubico-piv-tool, you can make it ask for a PIN by appending
-a verify-pin after the command, or make it ask for the management key with the
-k flag. Generally using a certificate (for signing, and so forth) requires the PIN, whereas generating keys or creating, and importing certificates requires the management key.
Enable the Reserved Key Management certificate slots (0x82-0x95)
The new YubiKeys have an extra 20 slots that can be used to fully store and use certificates. These are stored in the “Retired Key Management” slots which are really intended for storing old keys that you’ve stopped using, so that you can still decrypt old email, and so forth. It seems that the firmware abuses these slots and doesn’t adhere to the standards, by not expose them in the expected way. This means that you won’t by default be able to use them from OpenSSL. It turns out, however, that you can enable these by running the following command:
echo -n C10114C20100FE00 | yubico-piv-tool -a write-object --id 0x5FC10C -i -
The keys should now be available in slots 0x82 to 0x95 on
yubico-piv-tool, and slots 0x5 through to 0x19 when accessing through OpenSSL.
Thanks to Jonathan Rudenberg on GitHub for this helpful tip.
Building your own Public Key Infrastructure
YubiKeys only support RSA keys of sizes 1024 and 2048, so don’t try to import something larger than that.
Depending on your use case, you’ll want to either generate the keys on an air-gapped computer, or on your YubiKey. If you generate them on your computer, you can back them up safely, or duplicate them onto several hardware devices. On the other hand, if you generate them on the YubiKey, you’ll be sure the private key has never left the device, and it’s extremely unlikely that there exists another copy of the key: this is the whole point of the FIPS 140-2 standard, especially levels 3 and 4 (you can even now get FIPS 140-2 validated YubiKeys)! You can also perform attestation, to verify that a private key never left the device.
On your computer
Here we’ll generate one 4096-bit RSA key for the Root Certificate Authority that will be generated and kept offline, on an air-gapped computer, and AES-256 encrypted with a strong password. We’ll also generate a 2048-bit RSA key for the intermediate Certificate Authority that will be signed by the root CA and then imported onto the YubiKey and destroyed off the computer.
Paste the following into
root-ca.conf in your working directory:
[ req ] x509_extensions = x509v3 distinguished_name = dn prompt = no [ dn ] CN = Phony Root CA O = Phony, Inc. [ x509v3 ] keyUsage = critical, keyCertSign, cRLSign, digitalSignature basicConstraints = critical, CA:true subjectKeyIdentifier = hash authorityKeyIdentifier = keyid, issuer
Customize your Common Name (
CN) and Organization (
O) as you wish. The
x509v3 section contains the version 3 extensions for the certificate.
keyUsage outlines what operations are allowed, in this case,
keyCertSign indicating that the certificate can be used to sign other certrificates,
cRLSign indicating that the certificate can be used to sign Certificate Revocation Lists, and
digitalSignature indicating that the certificate can be used for other types of signatures. The
basicConstraints value just says that the certificate is a Certificate Authority (and that it can be used as the root of an unlimited length chain). The
authorityKeyIdentifier extensions contain certain hashes of the public keys used and make it easier to index and find certificates corresponding to a given key during certificate verification.
For more information on the various possible fields, see RFC5280 which describes the x.509 v3 format in full detail.
Now generate the key, then generate a certificate signing request and sign it with the following commands. We’ll make the certificate valid for 25 years, and give it a random serial number. You’ll be asked for a password:
/usr/local/opt/openssl/bin/openssl genrsa -aes256 -out root-ca-private.pem 4096 /usr/local/opt/openssl/bin/openssl req -new -sha256 -x509 -set_serial 0x$(/usr/local/opt/openssl/bin/openssl rand -hex 8) -days 9131 -config root-ca.conf -key root-ca-private.pem -out root-ca-certificate.pem
That’s it! You now have a certificate and a corresponding private key for your root CA. To inspect the newly generated certificate, run:
/usr/local/opt/openssl/bin/openssl x509 -text < root-ca-certificate.pem
This should output something like:
Certificate: Data: Version: 3 (0x2) Serial Number: ae:96:ce:a2:bb:17:dc:9e Signature Algorithm: sha256WithRSAEncryption Issuer: CN=Phony Root CA, O=Phony, Inc. Validity Not Before: Sep 1 13:24:54 2018 GMT Not After : Sep 1 13:24:54 2043 GMT Subject: CN=Phony Root CA, O=Phony, Inc. Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (4096 bit) Modulus: 00:bd:... Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Certificate Sign, CRL Sign X509v3 Basic Constraints: critical CA:TRUE X509v3 Subject Key Identifier: 7B:4D:67:B8:76:29:9A:A2:20:17:F2:C4:F0:F6:8F:EA:6D:06:87:3D X509v3 Authority Key Identifier: keyid:7B:4D:67:B8:76:29:9A:A2:20:17:F2:C4:F0:F6:8F:EA:6D:06:87:3D Signature Algorithm: sha256WithRSAEncryption 2a:67:... -----BEGIN CERTIFICATE----- MIIF... -----END CERTIFICATE-----
The stuff at the end is a
base64 encoded binary blob encoded itself using Abstract Syntax Notation 1 (ASN.1), an ugly, old binary serialization protocol. The object contains a bunch of parameters, identified by a unique Object Identifier (OID).
Now we’ll generate an Intermediate Certificate Authority and import it onto the YubiKey. We’ll start off like before, but this time we’ll divide up the configuration options into those to be attached in the Certificate Signing Request, and those to be attached by the signing Certificate Authority (our Root CA).
Paste the following settings into
intermediate-ca-csr.conf, which will be used to generate the signing request:
[ req ] distinguished_name = dn prompt = no [ dn ] CN = Phony Intermediate CA O = Phony, Inc.
Then generate a new 2048-bit RSA private key (as the YubiKey cannot handle larger keys at this time), as well as a Certificate Signing Request:
/usr/local/opt/openssl/bin/openssl genrsa -out intermediate-ca-private.pem 2048 /usr/local/opt/openssl/bin/openssl req -sha256 -new -config intermediate-ca-csr.conf -key intermediate-ca-private.pem -out intermediate-ca-csr.pem
Now create a
intermediate-ca.conf and add the certificate data to be inserted by the signing Certificate Authority:
keyUsage = critical, keyCertSign basicConstraints = critical, CA:true, pathlen:0 subjectKeyIdentifier = hash authorityKeyIdentifier = keyid, issuer
This time, we’ll only allow this Certificate Authority to sign certificates. You could also add
cRLSign if you want to allow it to sign Certificate Revocation Lists. This will also be a Certificate Authority, but note that we’ve added a
pathlen:0, which means that this certificate cannot be used to create sub-certificate authorities, that is, it can only be used to sign End Entity Certificates.
Now we’ll need to sign this certificate request with the Root private key in order to produce a certificate. We’ll make this certificate valid for 10 years, and again, we’ll give it a random serial number by adding a random value to the serial number file (
/usr/local/opt/openssl/bin/openssl rand -hex 8 > root-ca-certificate.srl /usr/local/opt/openssl/bin/openssl x509 -sha256 -CA root-ca-certificate.pem -CAkey root-ca-private.pem -req -days 3653 -in intermediate-ca-csr.pem -extfile intermediate-ca.conf -out intermediate-ca-certificate.pem
Done! We now have an Intermediate Certificate Authority, we can again inspect it with:
/usr/local/opt/openssl/bin/openssl x509 -text < intermediate-ca-certificate.pem
You should see something like this:
Certificate: Data: Version: 3 (0x2) Serial Number: 188428493704131080 (0x29d6eb778cbf208) Signature Algorithm: sha256WithRSAEncryption Issuer: CN=Phony Root CA, O=Phony, Inc. Validity Not Before: Sep 1 13:25:19 2018 GMT Not After : Sep 1 13:25:19 2028 GMT Subject: CN=Phony Intermediate CA, O=Phony, Inc. Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:a5:... Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Key Usage: critical Certificate Sign X509v3 Basic Constraints: critical CA:TRUE, pathlen:0 X509v3 Subject Key Identifier: E5:AC:86:65:60:4B:F6:68:A5:A3:FE:2C:2D:5E:2E:E3:89:29:74:11 X509v3 Authority Key Identifier: keyid:7B:4D:67:B8:76:29:9A:A2:20:17:F2:C4:F0:F6:8F:EA:6D:06:87:3D Signature Algorithm: sha256WithRSAEncryption 3b:ec:... -----BEGIN CERTIFICATE----- MIIE... -----END CERTIFICATE-----
Importing the key onto a YubiKey
To import the key and certificate into slot 82 of the YubiKey (the first Retired Key Management slot), run:
yubico-piv-tool -s 82 -a import-key -i intermediate-ca-private.pem yubico-piv-tool -s 82 -a import-certificate -i intermediate-ca-certificate.pem
Now delete the private key (
intermediate-ca-private.pem) off your computer.
Using the certificates to sign End Entity Certificates
In order to sign End Entity Certificates, such as for websites, or clients; you need to first generate a certificate and a certificate signing request.
The idea of a certificate signing request is that one is able to generate a request on a server, then send that request to the certificate authority which can sign the request without ever needing access to the private key.
Generating a certificate signing request
On the server requiring the certificate, create a file called
example_com-csr.conf with the following parameters for the certificate signing request:
[ req ] distinguished_name = dn prompt = no [ dn ] CN = example.com
We can use a shortcut to generate both a certificate signing request as well as a private key at the same time, by passing the
-newkey parameter to OpenSSL. Note that since this key need not be imported onto a YubiKey, you can use any key type or size you wish. In this example, however, I’ll again use a 2048-bit RSA key:
/usr/local/opt/openssl/bin/openssl req -new -newkey rsa:2048 -nodes -out example_com-csr.pem -keyout example_com-private.pem -config example_com-csr.conf
This generates the file
example_com-private.pem which contains the private key, and the file
example_com-csr.pem containing the actual signing request. You will not normally need to secure the private key with a password on a production server, so I have added the
-nodes (no Data Encryption Standard encryption) to stop OpenSSL from encrypting the private key.
Now send the certificate signing request over to the machine containing the intermediate CA certificate.
On the signing machine
Create a file for the certificate parameters called
example_com.conf with the following attributes:
[ req ] prompt = no [ x509v3 ] keyUsage = critical,digitalSignature,keyEncipherment extendedKeyUsage = serverAuth basicConstraints = critical,CA:false subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer subjectAltName = @san [ san ] DNS.0 = example.com DNS.1 = *.example.com
This configuration makes the certificate valid for
example.com and any subdomain of this domain. If you’d like to make it valid for other domains, you can add more of them under the
Now to download a copy of the intermediate CA certificate (the key cannot obviously be downloaded) from the YubiKey, and to again create a random serial number, issue the following commands:
yubico-piv-tool -s 82 -a read-certificate -o ca-certificate.pem /usr/local/opt/openssl/bin/openssl rand -hex 8 > ca-certificate.srl
Finally, to sign the certificate signing request, use the following command:
/usr/local/opt/openssl/bin/openssl << EOF engine -t dynamic \ -pre SO_PATH:/usr/local/lib/engines/engine_pkcs11.so \ -pre ID:pkcs11 \ -pre LIST_ADD:1 \ -pre LOAD \ -pre MODULE_PATH:/usr/local/lib/opensc-pkcs11.so x509 -engine pkcs11 -CAkeyform engine -CAkey 0:5 -sha256 -CA ca-certificate.pem -req -days 731 -in example_com-csr.pem -out example_com-certificate.pem -extfile example_com.conf -extensions x509v3 EOF
This will prompt you for the YubiKey PIV password, which by is by default
If these commands complete successfully, you should now have a certificate stored in
example_com-certificate.pem, which we can once again inspect with the following command:
/usr/local/opt/openssl/bin/openssl x509 -text < example_com-certificate.pem
You should see something like this:
Certificate: Data: Version: 3 (0x2) Serial Number: 5998200100684798003 (0x533de33635079833) Signature Algorithm: sha256WithRSAEncryption Issuer: CN=Phony Intermediate CA, O=Phony, Inc. Validity Not Before: Sep 1 13:26:07 2018 GMT Not After : Sep 1 13:26:07 2018 GMT Subject: CN=example.com Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:c0:... Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server Authentication X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Key Identifier: 4C:5B:68:48:7C:AF:A8:33:0F:D8:E2:D0:F8:D2:A1:58:55:70:45:42 X509v3 Authority Key Identifier: keyid:E5:AC:86:65:60:4B:F6:68:A5:A3:FE:2C:2D:5E:2E:E3:89:29:74:11 X509v3 Subject Alternative Name: DNS:example.com, DNS:*.example.com Signature Algorithm: sha256WithRSAEncryption 5d:63:... -----BEGIN CERTIFICATE----- MIID... -----END CERTIFICATE-----
Now send this certificate to your server, and install it!