Sign a Yubikey certificate with optional attestation verification

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.8
  • 1.3.6.1.4.1.41482.3.7: Serial number (DER-encoded), here it is 0x02345678 which is 36984440 in decimal
  • 1.3.6.1.4.1.41482.3.8: PIN and touch policy, respectively 03 and 01 for always and never:
    • PIN policy is 01 for never, 02 for once per session and 03 for always
    • Touch policy is 01 for never, 02 for always and 03 for cached for 15 seconds
  • 1.3.6.1.4.1.41482.3.9: Form factor which is 03 for USB-C keychain
    • 01: USB-A keychain
    • 02: USB-A nano
    • 03: USB-C keychain
    • 04: USB-C nano
    • 05: 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

links