Setting up a PKCS#11 token
This guide explains how to initialise a hardware token for storing signing keys.
PKCS#11
In this page (and subsequent pages), we will use the PKCS#11 API interface. According to Wikipedia:
PKCS#11 is a Public-Key Cryptography Standard that defines a C programming interface to create and manipulate cryptographic tokens that may contain secret cryptographic keys. It is often used to communicate with a Hardware Security Module or smart cards.
Because PKCS#11 is a standard, it allows software developers to implement cryptographic operations against the PKCS#11 API. Then the software can use every hardware token that supports the PKCS#11 API.
Before we proceed, let's look at some of the concepts and terms on PKCS#11.
Slots and tokens
The PKCS#11 standard is written with smart cards in mind. To support that use case, PKCS#11 has the concept of a slot (an empty card reader) and a token (a smart card). A slot can be empty or can contain a token. For USB security keys or network appliance HSMs there is no distinction, a slot is either not present at all or it always contains a token.
Roles
The PKCS#11 standard uses two roles: User and Security Officer (SO).
The User is the entity who uses the token for storing their private keys and signing data with them. This can be a human owning a smart card, or it can be an application such as a web server storing TLS keys on an HSM.
The Security Officer is the administrator of the PKCS#11 token.
PINs
Both the user and the SO have their own PINs, the User PIN and the SO PIN. PINs are usually 8 to 16 digits.
Yes, digits. Because hardware tokens only allow a small number of attempts before they lock, longer passphrases are generally considered unnecessary. Nevertheless, many modern hardware tokens allow alphenumeric "PINs" (in other words, normal passphrases). However, some old applications may assume that PINs are digit-only.
When the User PIN is locked, the SO PIN can be used to reset it. If the SO PIN is locked, the token becomes electronic waste.
It is recommended to use exactly 8 digits for the User PIN, and 16 digits for the SO PIN.
This avoids problems later (for example, Java's keytool expects a minimum of 6 digits,
and the SmartCard-HSM requires the SO PIN to be exactly 16 digits).
Modules
Every PKCS#11 token needs a module.
Think of the module as the "device driver".
The module exposes the PKCS#11 C API and internally communicates with the token using a hardware specific protocol.
The opensc-pkcs11 module implements this for many popular smart cards, including the Nitrokey.
Otherwise, your hardware vendor will provide you with the module in form of an .so file.
Selecting a PKCS#11 token
As explained previously, for FMD we selected the Nitrokey HSM as a hardware token. It is based on the SmartCard-HSM, packed into a USB form factor.
For testing, we recommend SoftHSM. It is a software tool that simulates a hardware token. This allows you to play around with the entire flow without having to purchase a hardware token.
There are subtle differences between every PKCS#11 token. Just because something works on SoftHSM does not mean it will work on your physical hardware token. For example, your hardware token may not implement all algorithms or key sizes. You still need to test with real hardware at some point.
Nitrokey
To use the Nitrokey, we need to initialise it.
This is the time where we need to think about how we will back up the data on the key.
DKEK and key domains
The secrets stored on the SmartCard-HSM are encrypted with a Device Key Encryption Key (DKEK). This DKEK is set when the device is initialised, and cannot be changed later without resetting the device!
The DKEK also forms a logical key domain. All SmartCard-HSMs that use the same DKEK can import/export keys wrapped under the DKEK. This allows for secure backup and transfer of keying material.
This allows every maintainer to have their own Nitrokey. Once the Nitrokeys share the same key domain, you can create the signing keys on one Nitrokey and "copy" (securely transfer) them over to the other maintainer's Nitrokey.
Multiple key domains
Using the Smart Card Shell 3 UI,
it is possible to configure multiple key domains
on a single SmartCard-HSM.
We have not explored this further, to keep it simple (one physical key == one key domain),
and because the sc-hsm-tool CLI tool does not support this.
The problem is: How do you securely initialise multiple devices with the same DKEK?
Key shares
The solution is to generate one or more DKEK key shares, and import them into the SmartCard-HSM. Together, they form the DKEK. The idea is that there are multiple key custodians, each of them holding one of the key shares.
Every key share is randomly generated and stored in a file encrypted with a password. To restore the DKEK, the custodians have to come together. Every custodian uses their encrypted file and password to load their key share onto a new SmartCard-HSM.
This can also be used in an m-of-n fashion, so that at least m of the n custodians are required to re-create the DKEK. In this case, there is only a single DKEK share that is encrypted with a randomly generated password. The password is then split with m-of-n using Shamir Secret Sharing. Every key custodian holds one password share.
Later, you can initialise a new device with the same key domain (the same DKEK). An encrypted data backup that was created with the same DKEK can be restored onto the new device.
Practical advice
The first step of initializing a Nitrokey HSM with a DKEK is crucial. It it is critical to plan ahead, to ensure:
-
Availability: You cannot change the DKEK later, nor can you extract the private signing keys.
-
Confidentiality: The DKEK is the achilles heel of the entire setup. If an attacker gets access to enough shares, they can set up their own Nitrokey with the same DKEK, allowing them to import the wrapped signing key backups.
How many key shares do you want to have? Which values of m and n do you choose? Think about how your organizational structure and number of maintainers may evolve in the future.
At FMD, we use the m-of-n scheme for sharding the DKEK. The individual shares are written down on physical paper, using this template.
However, we deliberately keep confidential what our m and n are, who holds the keys, and where they are backed up. This obscurity is part of our operational security.
For more details, see the following articles:
- https://www.smartcard-hsm.com/2014/09/25/Desaster_Recovery_for_your_SmartCard-HSM.html
- https://www.smartcard-hsm.com/2024/05/22/Use_PaperKey_for_DKEK_Share.html
- https://docs.nitrokey.com/nitrokeys/features/hsm/n-of-m-schemes
- https://github.com/OpenSC/OpenSC/wiki/SmartCardHSM#initialize-the-device
Initialise the Nitrokey
Run these steps on a dedicated, hardened computer.
-
Follow the OpenSC wiki to initialise the device with a user PIN and SO PIN. These PINs are personal to you and your device. Every maintainer should choose them for themselves and keep them confidential.
If you omit the
--pinand--so-pinoptions, you will be prompted to interactively input them. This avoids the PINs leaking to your shell history.sc-hsm-tool --initialize --label "fmd-maintainer-name" --dkek-shares 1warningPass
--dkek-sharesif you intend to import a DKEK share in a subsequent steps. Until you have imported a DKEK share, you won't be able to use the device. If you don't initialize the HSM with a DKEK, you will never be able to export wrapped keys. (You can reinitialize the device, but this wipes all data.)We set 1 share, because in the m-of-n scheme that
sc-hsm-toolsupports there is only a single DKEK share. The decryption key for that share is then Shamir-shared. -
Log in as SO and as user to confirm that the PINs work:
pkcs11-tool --module $MODULE --slot 0 --login --login-type so --list-objects
pkcs11-tool --module $MODULE --slot 0 --login --login-type user --list-objects -
Carefully read the OpenSC wiki section on key backup and restore. This explains how to initialise the key domain by loading a DKEK onto the device.
-
In particular, follow the section on the m-of-n scheme. Select values m and n for your setup. Repeat this for all n shares.
-
Create a DKEK share:
sc-hsm-tool --create-dkek-share dkek-share-1.pbe --pwd-shares-threshold 3 --pwd-shares-total 5 -
Follow the instructions given by the tool. Note down the password or share values, for example, using this template.
-
Import the DKEK share:
sc-hsm-tool --import-dkek-share dkek-share-1.pbe --pwd-shares-total 3 -
Follow the instructions and enter your share id and value.
-
-
Securely back up the
dkek-share-*.pbefiles and the passwords and PINs. For example, copy the file to a USB stick, or generate a human-readable version withopenssl base64 -in <filename>. This can be printed or written down on paper. For an additional layer for security, encrypt the USB stick with LUKS.
You should now have a Nitrokey that is initialised with PINs and a DKEK. You should also have everything backed up.
Skip the SoftHSM section and proceed below.
SoftHSM
SoftHSM is only for testing purposes, when you want to test with a PKCS#11 API but don't yet have a physical HSM. For production, use a real hardware token!
When installing from apt, most of the subsequent command will need to be done with sudo.
List the slots:
sudo softhsm2-util --show-slots
There should already be an unitialised token in slot 0. Initialise it:
sudo softhsm2-util --init-token --free --label fmd-test
You will be asked to choose both an SO PIN and a user PIN. If you list the slots again, you will see both your initialised token, and another empty, uninitialised token that SoftHSM has automatically pre-created for future use:
sudo softhsm2-util --show-slots
General PKCS#11 commands
The pkcs11-tool (provided by the opensc package) is a CLI tool for interacting with a PKCS#11 token.
Start by setting the path to the PKCS#11 module for your token:
# On Debian
export MODULE=/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so # for the Nitrokey
export MODULE=/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so # for SoftHSM
# On Arch Linux
export MODULE=/usr/lib/opensc-pkcs11.so
To list the available tokens:
sudo pkcs11-tool --module $MODULE --list-token
List the objects on the token (initially none):
sudo pkcs11-tool --module $MODULE --slot 0 --token-label fmd-test --login --list-objects
Additionally, you can set --login-type user (default) or --login-type so to explicitly log in with a role.
This is useful to verify that your (SO) PIN works.
Troubleshooting
If you run into problems, increase the log level to gain an insight into what is happening:
export OPENSC_DEBUG=9
If you accidentally lock the User PIN with too many wrong attempts, you can unlock it by resetting it. This requires the SO PIN.
sudo pkcs11-tool --module $MODULE --slot 0 --login --login-type so --init-pin
A locked SO PIN (due to too many wrong attempts) is permanent! Unlike the User PIN, the SO PIN cannot be reset. If the SO PIN is locked, you can throw the token away and buy a new one.
Next steps
At this point you should have:
- A hardened computer for the key management and signing tasks.
- A hardware token to securely store your signing keys, including:
- User and SO roles assigned to humans, and corresponding PINs.
- A backup scheme for the token and PINs.
- The token is initialised and ready to use.
You are now ready to create a signing key!