This guide will show you how to generate a key with its corresponding attestation certificate on a Yubikey PIV applet, verify the attestation up to the Yubico root CA and then sign a certificate corresponding to the key.
The attestation certifies that the key was generated on a given Yubikey and not imported, as well as that a specific PIN or touch policy was used.
The openssl, yubikey-manager, ykcs11 and pkcs11-provider packages
need to be installed.
It is assumed that your Yubikey PIV applet is already personalized
with a PIN, PUK and management key; otherwise see the command
ykman piv access to change them, with the following defaults:
- PIN:
123456 - PUK:
12345678 - Management key:
010203040506070801020304050607080102030405060708
Additionally it is assumed that OpenSSL is already configured to find the ykcs11 PKCS#11 module; otherwise follow the Set up OpenSSL for PKCS#11 tokens guide or adapt the command from the Extra: specify the provider on the command-line section if you do not want to install p11-kit.
Generate the key
In this example we are going to use slot 82 which is the Retired key
slot 1, but any slot can be used.
A P-384 key will be generated, but this works for any type of key (with a caveat for X25519 keys which cannot be used to emit a certificate signing request, but the attestation can be verified in the same way).
ykman piv keys generate --algorithm eccp384 --format pem --pin-policy always --touch-policy never 82 82-pubkey.pem
Emit the attestation
The attestation can be obtained either with openssl through the correct
PKCS#11 URI, or with ykman as follows:
ykman piv keys attest --format pem 82 82-attestation.pem
Then get the attestation of the Yubikey itself, which is stored in slot F9:
ykman piv certificates export --format pem F9 attestation.pem
Verify the attestation
For firmware 5.7.3 or earlier
Get the Yubico root CA:
curl https://developers.yubico.com/PKI/yubico-piv-ca-1.pem -o yubico-root.pem
Verify the attestation:
openssl verify -show_chain -CAfile yubico-root.pem -untrusted attestation.pem 82-attestation.pem
For firmware 5.7.4 or later
Get the Yubico root CA:
curl https://developers.yubico.com/PKI/yubico-ca-1.pem -o yubico-root.pem
Get the intermediate CA certificates:
curl https://developers.yubico.com/PKI/yubico-intermediate.pem -o yubico-intermediate.pem
Verify the attestation:
openssl verify -show_chain -CAfile yubico-root.pem -untrusted yubico-intermediate.pem -untrusted attestation.pem 82-attestation.pem
Verify the serial number and PIN policy
Show the attestation certificate with the Yubico-specific extensions shown as a hex dump:
openssl x509 -in 82-attestation.pem -noout -text -certopt ext_dump
And look at the X509v3 extensions section:
1.3.6.1.4.1.41482.3.3:
0000 - 05 08 00 ...
1.3.6.1.4.1.41482.3.7:
0000 - 02 04 02 34 56 78 ......
1.3.6.1.4.1.41482.3.8:
0000 - 03 01 ..
1.3.6.1.4.1.41482.3.9:
0000 - 03 .
You can see the different extensions:
1.3.6.1.4.1.41482.3.3: Firmware version, here it is 5.81.3.6.1.4.1.41482.3.7: Serial number (DER-encoded), here it is0x02345678which is36984440in decimal1.3.6.1.4.1.41482.3.8: PIN and touch policy, respectively03and01for always and never:- PIN policy is
01for never,02for once per session and03for always - Touch policy is
01for never,02for always and03for cached for 15 seconds
- PIN policy is
1.3.6.1.4.1.41482.3.9: Form factor which is03for USB-C keychain01: USB-A keychain02: USB-A nano03: USB-C keychain04: USB-C nano05: Lightning and USB-C
Make a certificate
Now for ECC and RSA keys you can either emit a certificate signing request that will be signed by your CA, or make a self-signed certificate.
For X25519 keys signing is not possible and the usual openssl req command will not work (as of libykcs11 version 2.7.3)
so we will have to make a dummy certificate and swap out the public key.
If you did not use slot 82 you first need to find the PKCS#11 URI to use for the key.
If you followed Set up OpenSSL for PKCS#11 tokens you can run:
p11-kit list-objects --login 'pkcs11:model=YubiKey%20YK5'
And you will get among other things:
Object: #21
uri: pkcs11:model=YubiKey%20YK5;manufacturer=Yubico%20%28www.yubico.com%29;serial=36984440;token=YubiKey%20PIV%20%2336984440;id=%05;object=Private%20key%20for%20Retired%20Key%201;type=private
class: private-key
key-type: ec
label: Private key for Retired Key 1
id: 05
flags:
local
token
private
destroyable
Alternatively if you did not set up p11-kit, assuming you installed opensc you can run:
pkcs11-tool --module /usr/lib/x86_64-linux-gnu/libykcs11.so -O -l -y privkey
And get among other things:
Private Key Object; EC
label: Private key for Retired Key 1
ID: 5 (0x05)
Usage: decrypt, sign
Access: always authenticate, sensitive, always sensitive, never extractable, local
uri: pkcs11:model=YubiKey%20YK5;manufacturer=Yubico%20%28www.yubico.com%29;serial=36984440;token=YubiKey%20PIV%20%2336984440;id=%05;object=Private%20key%20for%20Retired%20Key%201;type=private
The URI of a private key can be abbreviated as long as it contains either model or manufacturer and id and type.
The URI for a certificate must additionally contains object as otherwise it is ambiguous whether we are
referring to the attestation certificate or the actual certificate.
Make a self-signed certificate
For this you would run a command such as:
openssl req -new -x509 \
-key 'pkcs11:model=YubiKey%20YK5;id=%05;type=private' \
-subj '/CN=My certificate' \
-out yubikey.pem
You can omit the -subj '...' argument if you want OpenSSL to prompt you for the various fields.
Then skip to Import the certificate into the slot.
Sign a certificate signing request
openssl req -new \
-key 'pkcs11:model=YubiKey%20YK5;id=%05;type=private' \
-subj '/' \
-out yubikey.csr
Then you would sign it with your CA (which could potentially be on a hardware token as well, then replace
ca.pem and ca.key with the corresponding PKCS#11 URIs), for instance here for a client certificate for TLS:
openssl req \
-in yubikey.csr \
-CA ca.pem -CAkey ca.key \
-subj '/CN=My authentication certificate' \
-addext basicConstraints=critical,CA:FALSE \
-addext keyUsage=critical,digitalSignature \
-addext extendedKeyUsage=clientAuth \
-copy_extensions none \
-out yubikey.pem
Then skip to Import the certificate into the slot.
Make a X25519 certificate
The following command is supposed to work, but as of ykcs11 version 2.7.3 it doesn't:
openssl req -new -x509 \
-key 'pkcs11:model=YubiKey%20YK5;id=%05;type=private' \
-CA ca.pem -CAkey ca.key \
-subj '/CN=Firstname Lastname/emailAddress=firstname.lastname@gmail.com' \
-addext basicConstraints=critical,CA:FALSE \
-addext keyUsage=critical,keyAgreement \
-addext extendedKeyUsage=emailProtection \
-addext subjectAltName=email:copy \
-out yubikey.pem
So we're going to create the certificate directly and force the public key to be the
82-pubkey.pem that you've obtained at the Generate the key step:
cat > extensions.cnf <<'EOF'
authorityKeyIdentifier = keyid:always
subjectKeyIdentifier = hash
basicConstraints = critical, CA:FALSE
keyUsage = critical, keyAgreement
extendedKeyUsage = emailProtection
subjectAltName = email:copy
EOF
openssl x509 -new \
-set_subject '/CN=Firstname Lastname/emailAddress=firstname.lastname@gmail.com' \
-force_pubkey 82-pubkey.pem \
-extfile extensions.cnf \
-CA ca.pem -CAkey ca.key \
-out yubikey.pem
Then go on to the next section.
Import the certificate into the slot
Now import the certificate into the corresponding key slot:
ykman piv certificates import --compress 82 yubikey.pem
Now the certificate is stored on the Yubikey and can be addressed by its PKCS#11 URI, which would be here:
pkcs11:model=YubiKey%20YK5;id=%05;object=X.509%20Certificate%20for%20Retired%20Key%201;type=cert
You can see it either with (if you have set up p11-kit):
p11-kit list-objects 'pkcs11:model=YubiKey%20YK5'
Or with:
pkcs11-tool --module /usr/lib/x86_64-linux-gnu/libykcs11.so -O -y cert