Skip to content

Instantly share code, notes, and snippets.

@Patazerty
Created July 3, 2023 09:16
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Patazerty/48b50e82cbfbc033a836797c8d0a47f2 to your computer and use it in GitHub Desktop.
Save Patazerty/48b50e82cbfbc033a836797c8d0a47f2 to your computer and use it in GitHub Desktop.
Nitrokey HSM 2 GPG setup

Nitrokey HSM GPG setup

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.

Software requirements

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.

Initializing the HSM

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.

Creating a new key pair

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 Setup

GPG config files

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]

Generating a certificate

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.

Importing the key

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment