PKCS#11 tools for OpenPGP

Table of Contents

This crate allows using PIV tokens (e.g. Yubikeys) through PKCS#11 interface for OpenPGP operations. It is like OpenPGP Card that GnuPG natively supports but using a different set of tools.

1 Why?

PKCS#11 is widely deployed and more popular than OpenPGP Card. Using this crate one can use their keys stored in a PIV applet for signing and decryption.

2 Status

This crate is currently a Proof of Concept showing that it can be done. Do not use this in production!

3 Usage

Currently this crate assumes you have an OpenPGP primary key and will use PKCS#11 keys as OpenPGP subkeys.

Examples use software hardware token (softhsm2) so that they can be automatically executed as part of end-to-end test suite.

set -eu
set -o pipefail

export SOFTHSM2_CONF=$(mktemp)
DIR=$(mktemp --directory)

echo "directories.tokendir = $DIR" > $SOFTHSM2_CONF

If you want to use a different PKCS#11 library subsitute the MODULE to your own (e.g. for Yubikey this is


Note that for brevity we assume the tools as on the PATH:

export PATH=$PATH:./target/debug

First, we initialize the token (reset it) and set user PIN and use pkcs11-tool to generate an RSA key with id 3:

echo "===> Initializing token..."
pkcs11-tool --init-token --module $MODULE --slot-index 0 --label TestToken --so-pin 123456

echo "===> Initializing user PIN..."
pkcs11-tool --init-pin --login --so-pin 123456 --pin 123456 --slot-index 0 --module $MODULE

# This will be the serial number of the newly initialized card
SN=$(./target/debug/list-tokens --module $MODULE)

echo "===> Using token with serial number [$SN]"

list-objects --module $MODULE --serial-number $SN --pin 123456

echo "===> Generating RSA keys on card..."

pkcs11-tool --module $MODULE --slot-index 0 --login --pin 123456 --keypairgen --key-type rsa:2048 --id 3

Other vendors usually provide GUI tools for key creation. E.g. use ykman-gui tool (or any other) to generate keys on the Yubikey card. ykman-gui requires creating certificates that as a side-effect also generates keys. This is OK for testing purposes.

Create primary key and extracting public parts:

sq key generate --cannot-encrypt --cannot-sign --userid "<>" --export primary.sec.asc
sq key extract-cert < primary.sec.asc | sq dearmor >

3.1 Binding on-card keys

List objects on the card:

list-objects --module $MODULE --serial-number $SN --pin 123456

And then export the subkey in OpenPGP format:

export-subkey --module $MODULE --serial-number $SN --pin 123456 --id 3 >

If your subkey is signing capable you also need to generate a back signature:

create-backsig --module $MODULE --serial-number $SN --pin 123456 --id 3 < > backsig.pgp

Now, bind the subkey to the primary key:

bind-subkey --backsig backsig.pgp --subkey < primary.sec.asc > subkey-binding.pgp

If there is no backsig (e.g. encryption capable subkey) just omit the `–backsig` parameter.

Now, concatenate all parts of the key:

cat subkey-binding.pgp > complete-key.pgp

`complete-key` can now be imported, uploaded to keyserver or shared as usual.

3.2 Signing data

First, create a file to be signed:

echo dummy test > file.txt

Then sign it via PKCS#11 interface:

detach-sign --module $MODULE --serial-number $SN --pin 123456 --id 3 < file.txt > file.txt.sig

And that's it! File is ready to be verified:

sq verify --detached file.txt.sig --signer-cert complete-key.pgp < file.txt

``` $ gpg –verify file.txt.sig gpg: assuming signed data in 'file.txt' gpg: Signature made Wed, 17 May 2021, 13:13:41 CET gpg: using RSA key EBA81FA1C3D96433EC91F31F83643509205B12FE gpg: Good signature from "<>" [unknown] ```

3.3 Decrypting data

First, add encryption subkey using key with a different ID. In our case it's `3` for the `Key Management` key:

export-subkey --module $MODULE --serial-number $SN --pin 123456 --id 3 >

Not creating a backsig, since this is not signing-capable key and binding it directly:

bind-subkey --subkey < primary.sec.asc > subkey-binding.pgp

And assembling all parts together:

cat subkey-binding.pgp > complete-key.pgp

Now, you can import complete-key.pgp.

As for the actual encryption. First, encrypt the data:

echo dummy test | sq encrypt --recipient-cert complete-key.pgp > encrypted.pgp

$ echo dummy test | gpg -ear > encrypted.pgp

Now, pass the decrypted file to decrypt:

decrypt --module $MODULE --serial-number $SN --pin 123456 --id 3 < encrypted.pgp
dummy test

Cleaning up the temporary directories:

rm --recursive --force $DIR $SOFTHSM2_CONF
rm --force *.asc *.pgp *.rev

4 Caveats

Currently only RSA 2048 and secp256r1 is supported.

Author: root

Created: 2021-07-08 Thu 08:20