I was investigating the use of Hardware Security Modules (HSMs) to better secure some stuff at work. Our choice was a Nitrokey HSM 2 for its convenient price, features and open approach, including hardware. Unfortunately Nitrokeys's documentation is sparse at best and there is not much available documentation online to guide new users to get HSMs to work with GnuPG (GPG): it's even the opposite with some forum posts indicating that the Nitrokey HSM 2 is not compatible with GPG.
From what seems to be the current state of things, GPG works out of the box with OpenPGP cards (which are not HSMs, they are intended for personal use and are limited to one or a few keys at best), but not other hardware solutions like HSMs (of any brand).
Note: I was not interested in reading all the abundant FOSS debate about GnuPG and PKCS11 support which is part of why we even need this doc to somewhat explain the convoluted way of getting GPG working with PKCS11-backed HSMs. This mailing list message from over 10 years ago is still one of the top 3 search results for "gpg pkcs11".
Since we are already using GPG I was very interested in getting GPG to work with the HSM: I really didn't want to change our existing tooling at work because of some hardware-sofware-ideological issue.
Warning: this doc does not cover key backups and disaster recovery. Please see OpenSC doc on DKEK.
- List of useful packages (naming can vary depending on your Linux distro, those are Arch-based ones):
- opensc
- libp11
- gnupg-pkcs11-scd
- ccid
- pcscd
- pcsc-tools
You likely want to enable pcscd.socket
systemd target as well.
This part is relatively well documented on OpenSC's doc itself linked from the Nitrokey documentation. Please note that the default PINs are needed to change them with custom ones.
The regular PIN code is the one you will need to do day-to-day operation with secret keys. The Security Officer PIN is needed for sensitive operations like unlocking the HSM after failed PIN attempts, changing the PIN, etc.
TL;DR:
pkcs11-tool --module /usr/local/lib/opensc-pkcs11.so --login --login-type so --so-pin 3537363231383830 --change-pin --new-pin 012345678901234 pkcs11-tool --module /usr/local/lib/opensc-pkcs11.so --login --pin 648219 --change-pin --new-pin 012345
Use the --slot
option if you have mutliple HSMs.
Note
If you choose to generate RSA4096, expect it to take a few minutes.
pkcs11-tool --module /usr/lib/opensc-pkcs11.so -l --pin 012345 --keypairgen --key-type rsa:2048 --id 10 pkcs11-tool --module /usr/lib/opensc-pkcs11.so -l --pin 012345 --list-objects
GPG needs a 'provider' for pkcs11 backends in the form of a shared library implementing the correct API.
In our case with the Nitrokey HSM 2 we are using opensc and the opensc-pkcs11.so
provider (itself provided
by the opensc package).
A few config files need to be tweaked. Please note that library paths can vary depending on the Linux distro.
~/.gnupg/gnupg-pkcs11-scd.conf
debug-all providers nitrokey provider-nitrokey-library /usr/lib64/opensc-pkcs11.so
~/.gnupg/gpg-agent.conf
daemon verbose log-file ~/.gnupg/gpg-agent.log scdaemon-program /usr/bin/gnupg-pkcs11-scd pinentry-program /usr/bin/pinentry-qt
Note
pinentry-qt
is for graphical envrironments, non-qt pinentry also works.
You might need to kill / reload your gpg agent for things to take effect (ex. killall gpg-agent
) or
gpg-agent --server gpg-connect-agent << EOF RELOADAGENT EOF
A gpg --card-status
should output a few lines of informations. Note that this seemingly innocent gpg command
does some kind of discovery / init as I've encountered a weird case where in a build pipeline where adding this
call made gpg detect the HSM, otherwise it tried to open the local fs-based keyring instead with a misleading
"No such file or directory."
Reader ...........: [none] Application ID ...: 123456789123456789123456789 Application type .: OpenPGP Version ..........: 11.50 Manufacturer .....: ? Serial number ....: 5439A23E Name of cardholder: [not set] Language prefs ...: [not set] Salutation .......: URL of public key : [not set] Login data .......: [not set] Signature PIN ....: forced Key attributes ...: rsa48 rsa48 rsa48 Max. PIN lengths .: 0 0 0 PIN retry counter : 0 0 0 Signature counter : 0 Signature key ....: [none] Encryption key....: [none] Authentication key: [none] General key info..: [none]
Create a hsm.conf
openssl configuration file with the following content:
# PKCS11 engine config openssl_conf = openssl_def [openssl_def] engines = engine_section [req] distinguished_name = req_distinguished_name [req_distinguished_name] # empty. [engine_section] pkcs11 = pkcs11_section [pkcs11_section] engine_id = pkcs11 dynamic_path = /usr/lib/engines-3/pkcs11.so MODULE_PATH = /usr/lib/opensc-pkcs11.so PIN = 012345 init = 0
You can then use the pkcs11 engine to create a certificate from the key pair. 0:10
means slot 0 (will always be 0 if
you have only one HSM) key id 10.
OPENSSL_CONF=./hsm.conf openssl req -engine pkcs11 -new -key 0:10 -keyform engine -out cert.pem -text -x509 -days 3640 -subj "/C=FR/ST=IDF/L=Paris/O=SomeOrg/OU=SomeOrgUnit/CN=A Common Name/"
You now have a cert.pem
self signed certificate that you can import back to the HSM. This step is required
for GPG to interact with the keys.
pkcs11-tool --module /usr/lib/opensc-pkcs11.so -l --pin 012345 --write-object cert.pem --type cert --id 10 --label 'My cert'
You should be able to query the GPG agent to see the key:
gpg-agent --server gpg-connect-agent << EOF SCD LEARN EOF
You should see KEY-FRIENDLY
, CERTINFO
and KEYPAIRINFO
responses.
gpg --expert --full-generate-key
Choose option "Existing key from card" and fill out the informations. This step cannot be automated but once you have the GPG public key generated once you can export it as a generic GPG key file and import it instead.
gpg --list-secret-keys
Note: while the terminology says "importing" and "generating" the key, the key stays on the HSM and is not stored in a local GPG keyring. You will need to have the HSM plugged and to enter the PIN for each operation with the private key. However GPG seems to cache the PIN and likely will only ask the PIN once.
gpgsm
can also be used:
gpgsm --import < cert.pem gpgsm --learn-card gpgsm --list-secret-keys