Using a second OpenPGP card for my primary key

Using a second OpenPGP card for my primary key

Personal blog: One more OpenPGP card #

This is a writeup about my OpenPGP card setup.

It’s a mix of general observations about OpenPGP card devices and a report about setting up a secondary OpenPGP card for myself.

Disclaimer #

This article describes a relatively involved setup consisting of two OpenPGP card hardware security devices.

I’m describing this setup merely because it is what I currently use, not because I think anyone should mimic it. If you’d rather not use hardware cryptographic devices, then more power to you!1

OpenPGP card and me #

My current OpenPGP card experiment started when I created the key 23da7c0eaa711f0170013595b518d342eb2d4805 around two years ago. At the time, I decided to keep its private key material “offline”.

That is, that I won’t directly handle its private key material on a network-connected computer. Instead, I’d only use the keys in two ways:

  1. On special purpose devices (specifically: USB-connected OpenPGP cards), or
  2. On an “offline/airgapped” stateless Linux system, on rare occasions (with the key material mounted from a LUKS-encrypted block device).

Until now, I only had my three subkeys (“data signing”, “decryption”, “authentication”) readily available on an OpenPGP card device, while I kept the primary key “offline”. Only on rare occasions did I boot into a separate system to use the primary key.

Some code #

Over the past years, I’ve worked on the openpgp-card client library and a set of applications, including:

These tools aim for simplicity, being easy to reason about, and a convenient user experience. For the most part, I genuinely enjoy using OpenPGP card devices with them.

The quest for convenience #

While I’m not particularly interested in defending against powerful, motivated attackers, I do appreciate the ergonomics of my setup: I never have to enter any passwords or PINs to use my OpenPGP key.

My setup stores the card’s “User PIN” on the host computer, so that I never need to manually enter it. For convenience while using git (and issuing signatures for it), I have not enabled “touch confirmation” for data signatures4 (more on “touch confirmation” later).

However, until this week I did not have my primary key readily available. In part because my primary OpenPGP card device doesn’t have enough key slots, and in part because I wasn’t sure if I’d actually want to use the primary key a lot.

So for the past two years, I kept the private key material of my primary key “offline”.

This was inconvenient for at least two workflows:

  • Extending the expiration of my certificate (which requires issuing new self-signatures with the primary key).
  • Issuing certifications (“third-party signatures”) for other people’s OpenPGP user identities.

I did indeed not want to use my primary key a lot. But each time was an annoyance, and I did not enjoy this setup. I am very happy to move on and to stop doing the “offline primary” dance.

On OpenPGP card key slots and OpenPGP component keys #

OpenPGP card key slots #

For better or worse, the OpenPGP card standard specifies devices that have exactly three slots for private key material. Each slot is named for a specific purpose:

  • “data signature”,
  • “decryption” and
  • “authentication”,

respectively.

There are historical reasons why these devices were specified to have exactly these three key slots. But from today’s perspective, it would be nice if the standard mandated a few more slots (for example, at least one additional slot for the primary key).

However, the existing OpenPGP card specification with three key slots is what we currently have. I’m not holding my breath for an update with more key slots, let alone one that gets traction with hardware vendors and software projects anytime soon.

Note that even though the card slots have very descriptive names, they aren’t functionally tied as strictly to very specific usages5.

OpenPGP component keys #

An OpenPGP “key” actually consists of a number of distinct cryptographic keys, which are logically bound together into one composite object. The individual component keys serve different purposes (for more context, see the “Component keys” section of “openpgp.dev”).

A common modern best practice in OpenPGP is to have a composite key that internally consists of four component keys:

  • A “primary” key that serves two functions:
    • Life cycle operations for the OpenPGP certificate (such as extending expiration, or adding identities to the certificate)
    • Certifying identities on OpenPGP certificates of third parties (aka: “issuing third-party signatures”)
  • A set of “subkeys”, each serving a distinct purpose. It is common to have three subkeys:
    • One for issuing/verifying data signatures
    • One for encryption/decryption of data (e.g. email content)
    • One for “authentication” (typically used for ssh authentication)

Many modern OpenPGP keys (including mine) have this shape.

Note that alternative OpenPGP key layouts exist (some of them consist of only three component keys6).

Going forward: An easy-to-use card-based primary key #

To have one more key slot available on a hardware security device, I recently acquired a second OpenPGP card. I will use that device exclusively for my OpenPGP primary key.

Since I use my primary key much less often than the subkeys on my other card, I can leave this second device unplugged most of the time. Also, I can configure it to require touch-confirmation for cryptographic operations, without being inconvenienced much.

Configuring the card’s access controls #

Before uploading the key, I configured two access control mechanisms on the card:

First, the User and Admin PINs (changing them away from their factory-defaults of “123456” and “12345678”, respectively):

$ oct pin --card 000F:ABCDE123 set-user
Enter User PIN:
PIN was accepted by the card.

Enter new User PIN: 
Repeat the new User PIN: 

User PIN has been set.

$ oct pin --card 000F:ABCDE123 set-admin
Enter Admin PIN:
PIN was accepted by the card.

Enter new Admin PIN: 
Repeat the new Admin PIN: 

Admin PIN has been set.

Then I enabled the “touch confirmation” requirement for the “signing” key slot of the device. This means that the hardware device will require a physical touch (with a finger), on a designated surface for each signature the card issues. Without this local physical confirmation, the card won’t produce signatures7:

$ oct admin --card 000F:ABCDE123 touch --key SIG --policy On
Enter Admin PIN:

Since I’m not planning to sign with this primary key very often, requiring touch is a good tradeoff. An additional bit of defense in depth without a big usability cost for me.

Importing key material #

To import the private key material of my primary key into the new OpenPGP card, I booted into the “offline” system one final time, and mounted the LUKS-encrypted storage on which my OpenPGP private key material lives (most of the time unplugged and stashed away):

$ oct admin --card 000F:ABCDE123 import -s 23da7c0eaa711f0170013595b518d342eb2d4805 heiko.tsk 
Enter Admin PIN:
Uploading 23DA7C0EAA711F0170013595B518D342EB2D4805 as signing key

Nice, that was easy8. Now the new card has my primary key in its signing slot:

$ oct status
OpenPGP card 000F:ABCDE123

Signature key:
  Fingerprint: 23da7c0eaa711f0170013595b518d342eb2d4805
  Creation Time: 2023-04-23 03:04:56 UTC
  Algorithm: EdDSA (Ed25519)
  Signatures made: 0

Decryption key:
  Fingerprint: [unset]
  Algorithm: RSA 2048

Authentication key:
  Fingerprint: [unset]
  Algorithm: RSA 2048

Remaining PIN attempts: User: 3, Admin: 3, Reset Code: 0

Note that (as planned) two of the card’s three key slots are unused.

Next steps: More rsop-oct #

One point of setting up this second card was to give myself the opportunity to dog-food the full range of OpenPGP workflows with keys on hardware devices.

In particular, I want to implement (and use) more commands for the rsop-oct CLI tool. Having the primary key available on a card enables use of the following SOP commands:

I plan to implement more of these.

Right now, only certify-userid is available (I implemented it after setting up this card). See below for a usage walkthrough.

Keys split over two cards and other OpenPGP software #

I don’t know which OpenPGP software can conveniently use the two-card setup I set up in this article.

In particular, I don’t know if (and how) such a setup might be used with the GnuPG software, which has dominated the OpenPGP ecosystem for some time. I’m happy to admit I’ve never used GnuPG with any proficiency, and all of my attempts at doing anything non-trivial with it have not sparked joy.

Issuing a third-party certification with rsop-oct #

Ok, let’s see the new card in action, and issue a third-party certification with it.

We can use rsop to generate a test-certificate (with a single user id “alice”) like this:

$ rsop generate-key alice | rsop extract-cert > alice.cert

Issuing a certification for the identity alice in this test-certificate is now a simple task.

With my new OpenPGP card plugged in, running rsop-oct as follows produces an updated copy of Alice’s certificate that comprises an added third-party certification by me10:

$ cat alice.cert | rsoct certify-userid heiko.cert --userid alice --with-key-password pin
Touch confirmation required for certification with rsop-oct
-----BEGIN PGP PUBLIC KEY BLOCK-----

xjMEaHVsohYJKwYBBAHaRw8BAQdAar0ttWTb7e9/xNd6ANdArQBmiP6bzXl1vXid
YISPaEHNBWFsaWNlwpoEEBYIAEIFAmh1bKIWIQRGKhyzZ5lnF/+KkqI/TWxKnVLO
EgIbAwIeAQQLCQgHBhUOCgkMCAEWDScJAggCBwIJAQgBBwECGQEACgkQP01sSp1S
zhJ+CgEA+/e9Mgbf+Tx03zi3izqaPw33Z3XNF+FS6GTonYr10bMA/RO4fnqfgx+d
myC441TKsyoCr9uO+k16dOtOxijN76oHwnUEEBYIAB0WIQQj2nwOqnEfAXABNZW1
GNNC6y1IBQUCaHVs/AAKCRC1GNNC6y1IBYzKAP4sgk1bYRYea3ZROAVM5jQnzbCP
cMiQ2oGN6gYt6pAnHwEAkyMy7Bt60LzMLWYaYue+3AL23nv+P1R1c5d2jzoljgHO
OARodWyiEgorBgEEAZdVAQUBAQdAzXYgcYaL1Wvq6UdIK6bkDAiqw0uYNKAnaeol
lT76QnIDAQgHwngEGBYIACAFAmh1bKICGwwWIQRGKhyzZ5lnF/+KkqI/TWxKnVLO
EgAKCRA/TWxKnVLOEiPUAP9QG0WUlIHjqHF0DKleIhPTYx87ebE0h5vQm0Plnx8f
tAEAoOBZqe0jct2gzVOefkUgjUKjW0ltkJe17O5PYu+2agE=
=7RmM
-----END PGP PUBLIC KEY BLOCK-----

The parameters in this call are heiko.cert, which point to my certificate (aka public key) file, and pin which is a text file that contains the User PIN for my OpenPGP card.

This is so much nicer than rebooting into a separate system and futzing around with tools and key material from a set of USB-connected storage devices. I’m very excited about this new (dual-card) setup and look forward to test-driving it over the coming months and years.


  1. Using hardware security devices (like OpenPGP cards) defends against exfiltration of private key material. Depending on their configuration, they can also provide some defense against covert use of private keys by an attacker (by requiring physical confirmation of each cryptographic operation on the device).
    I don’t believe that the risks hardware security devices defend against are particularly relevant to me. I use them mostly out of curiosity, and to witness the user experience of relying on them.
    Depending on your goals and threat model, you quite likely don’t need to use hardware-backed keys. For must applications of OpenPGP, key material directly on the host computer (aka “software keys”) is just fine.
    Still, I hope this exploration of the option space for OpenPGP card usage is of interest to some readers (even if only to clarify to themselves that they want to stick with “software keys” 😅). ↩︎

  2. Keys that use the RSA algorithm are an outlier: An RSA key can serve both of these functions. All other algorithms used in OpenPGP can be used only for either one or the other. ↩︎

  3. More specifically, the “decryption” key can be used to decrypt “session keys”. The actual payload of an encrypted OpenPGP message gets decrypted on the host computer, using the session key. ↩︎

  4. With this setup, if an attacker gains control of my user environment (including remotely), they can generate data signatures with my key at will.
    However, an attacker who gets access to only my computer or the OpenPGP card can’t perform any OpenPGP private key operations, without the other device. ↩︎

  5. In OpenPGP, there are technically two types of key material2: a. the kind that can handle digital signatures, and b. the kind that can handle encryption/decryption of messages3.
    In these terms, an OpenPGP card device has two slots for cryptographic keys that can issue digital signatures (the “signing” and “authentication” slots), and one slot for a cryptographic key that can decrypt data. ↩︎

  6. Various OpenPGP key shapes with only three component keys exist, for example:
    a. The primary key is used directly for data signatures, instead of a separate data signature subkey (this has historically been a common setup). This shape of key fits neatly into the slots of an OpenPGP card device (however, it has potential security drawbacks).
    b. Not having an authentication subkey (the authentication subkey is only needed for SSH logins based on OpenPGP, users who don’t want to use their OpenPGP key for SSH authentication can forego that subkey). This key shape doesn’t fit onto an OpenPGP card device very neatly, but it’s possible to e.g. put the primary key into the key slot for “authentication” (the name doesn’t match, but in terms of functionality, the slot is entirely serviceable). ↩︎

  7. So even if an attacker gained full remote control of my host computer system, and the OpenPGP card was permanently plugged in, and the attacker knew its User PIN, they could still not make signatures at will - someone would need to touch the USB device for confirmation. ↩︎

  8. Actually … importing the key should have been easy, but was in fact not.
    Turned out that the current version of oct had a bug that broke uploading of Curve 25519 keys onto my new card, specifically with the very new firmware version on that device (and annoyingly, my CI setup hadn’t surfaced this bug).
    So my first attempt to import the key ended with an obscure error message from the card, followed by me reading the source for the card’s firmware. After fixing the issue and releasing a new oct version 0.11.10, importing my primary key onto the card was indeed easy. ↩︎

  9. This is of particular interest to me, since my certificate had been in an invalid state for a while, and I wanted to take this as an occasion for more dog-fooding, and fixing the certificate with rsop-oct↩︎

  10. The actual cryptographic signature in the certification is produced on my OpenPGP card hardware device, using the private key material that we imported into the card, above. ↩︎