sorenpoulsen.com header

Securing GnuPG keys on a Nitrokey Pro

A GnuPG key can be secured by storing it on a USB device with OpenPGP Card support such as the Nitrokey Pro.

Once safely stored on the device we can send commands to the device to decrypt, sign and authenticate with the GnuPG key, but there is no way to extract a copy of the key from the device.

The use of the device is protected by a PIN. The device locks if we fail to enter the correct PIN 3 times in a row. There's an Admin PIN that can reset the first PIN, but if we also fail to enter the correct Admin PIN 3 times in a row then the device self-destructs with smoke coming out of it.

Actually I'm not sure about the last bit about the smoke, but OpenPGP Card does have PIN retry counters that must be taken seriously.

Moving a key to the device has little impact on the daily use of GnuPG. Most GnuPG commands work as if the key was still stored in the keyring on the hard drive. In fact if we list the keys in the keyring, it appears the key is still there:

bob@home:~$ gpg -K 
/home/bob/.gnupg/secring.gpg
----------------------------
sec#  2048R/675E7FA7 2015-03-26
uid                  Bob Bobmeister <bob@test.com>
ssb>  2048R/7AC1F9A2 2015-03-26
ssb>  2048R/1CA3EBA3 2016-02-06

It is just a proxy though. Any GnuPG command we issue that requires the private key is redirected transparently to the device through the OpenPGP Card interface.

Prerequisites

I'm going to assume, that you have already created a personal key, as described in this previous post. This is the key we will move to the card.

The OpenPGP Card USB device described in this post is a Nitrokey Pro, which is built on Open Source hardware - no hidden backdoors. The OS is Ubuntu 12.04 with a rather old GnuPG v1.4.11 but even Ubuntu 16.04 still uses the v1.4.x series by default.

Enter subkeys

If you have only recently wrapped your head around Public Key Cryptography and the idea that keys are split into a public and private key, then unfortunately we now have to complicate matters further by introducing the concept of subkeys.

A freshly created GnuPG key, like the one from the previous post, looks something like this:

bob@home:~$ gpg -k
/home/bob/.gnupg/pubring.gpg
----------------------------
pub   2048R/675E7FA7 2015-03-26
uid                  Bob Bobmeister <bob@test.com>
sub   2048R/7AC1F9A2 2015-03-26

Although we strictly remember creating just one key, the keyring clearly shows two. One with ID 675E7FA7 marked "pub" and another with ID 7AC1F9A2 marked "sub".

What are these two keys?

The first key is the Primary Signing Key and the latter is a subkey for encryption.

So why do we have a subkey?

Imagine that one day we want to replace the key. Maybe the private key was compromised or the key size became inadequate with time. If we simply discard the old key and create a new one, we loose all the history of the old key. All the signatures from friends on the public key that are proof of trust, gone. We have to convince our friends to import the new public key into their keyrings and sign it again, a logistical mess.

With subkeys we don't have to throw everything overboard. Our friend's signatures on the key are bound to the Primary Signing Key, as is our user ID. Subkeys are specialized for either encryption, signing or authentication, but they don't carry trust directly. We can replace subkeys individually and push those changes to friends. The changes are automatically merged into our friends keyrings as an update to the existing key, because the subkeys are signed with our Primary Signing Key that our friends already trust.

What exactly are we storing in the Nitrokey Pro?

There are three types of subkey: one for encryption, one for signing and one for authentication.  The Nitrokey Pro conveniently has a slot for each type of subkey. To be more precise it is the private part of the subkeys that are stored on the Nitrokey Pro.

The Primary Signing Key is not stored on the Nitrokey Pro but will be moved to a safe backup. Using the private Primary Signing Key from its backup media is discussed in a separate post.

In this post we go through the following steps to move our subkeys to the Nitrokey Pro.

  • Compliment the subkey for encryption that we already have, with a subkey for signing (I'm leaving the authentication subkey out to keep the post shorter).
  • Configure the subkeys to have a fixed expiration period, while never letting the Primary Signing Key expire.
  • Move all private subkeys off the hard drive onto the OpenPGP Card USB device.
  • Move the private Primary Signing Key off the hard drive onto a safe backup medium.

The public keys stay put in the keyring on our hard drive, only the private subkeys are replaced with proxies in the keyring.

So let's get started.

Install CCID smartcard device driver

Check if you have a file named /etc/libccid_Info.plist? If it exists, then you already have the CCID smartcard device driver installed and can skip ahead to the next section. If not then follow the steps below.

Check in /etc/apt/sources.list if the "universe" repository is commented out. If so then add it with this command:

sudo add-apt-repository universe

Now install the CCID smartcard device driver.

sudo apt-get install libccid

Add Nitrokey Pro to the device list

If you are running Ubuntu 16.04 LTS then the Nitrokey Pro is already registered in the device list and you can skip this section.

The file /etc/libccid_Info.plist has three arrays named ifdVendorID, ifdProductID and ifdFriendlyName. Add the Nitrokey Pro vendor, productID and friendlyname to the arrays. Although the information is split into three arrays its important they go into the same index of these arrays.

sudo vi /etc/libccid_Info.plist

Press i to enter Vi's edit mode. Add the array entries shown below.

        <key>ifdVendorID</key>
        <array>
                ...
                <string>0x20A0</string>
        <array>

        <key>ifdProductID</key>
        <array>
                ...
                <string>0x4108</string>
        <array>

        <key>ifdFriendlyName</key>
        <array>
                ...
                <string>Nitrokey Pro</string>
        <array>

The press Esc to exit edit mode and type ":wq" to write the file and quit.

Configure UDEV for the Nitrokey Pro

Udev is a core linux service that manages device nodes under the /dev file hierarchy. Configure the permissions that will be required to access the Nitrokey Pro device node by adding a file named /etc/udev/rules.d/40-nitrokey-rules and add the content below.

sudo vi /etc/udev/rules.d/40-nitrokey.rules

Press i to enter edit mode. Then paste this content.

# Nitrokey U2F
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="plugdev", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="f1d0"

SUBSYSTEM!="usb", GOTO="gnupg_rules_end"
ACTION!="add", GOTO="gnupg_rules_end"

# USB SmartCard Readers
## Crypto Stick 1.2
ATTR{idVendor}=="20a0", ATTR{idProduct}=="4107", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", GROUP+="plugdev", TAG+="uaccess"
## Nitrokey Pro
ATTR{idVendor}=="20a0", ATTR{idProduct}=="4108", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", GROUP+="plugdev", TAG+="uaccess"
## Nitrokey Storage
ATTR{idVendor}=="20a0", ATTR{idProduct}=="4109", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", GROUP+="plugdev", TAG+="uaccess"
## Nitrokey Start
ATTR{idVendor}=="20a0", ATTR{idProduct}=="4211", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", GROUP+="plugdev", TAG+="uaccess"
## Nitrokey HSM
ATTR{idVendor}=="20a0", ATTR{idProduct}=="4230", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", GROUP+="plugdev", TAG+="uaccess"

LABEL="gnupg_rules_end"

Press Esc to exit edit mode then type ":wq" to write and quit.

Give the file the same permissions as other files in this folder:

sudo chmod go+r /etc/udev/rules.d/40-nitrokey.rules

Check the USB device works

Insert the USB device and run a OpenPGP Card status. If the output doesn't show like this or you find that doing a card status only works with root access then revisit the previous section on setting up Udev:

bob@home:~$ gpg --card-status
Application ID ...: D2760001240102010005000039000000
Version ..........: 2.1
Manufacturer .....: ZeitControl
Serial number ....: 00003900
Name of cardholder: [not set]
Language prefs ...: de
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Private DO 1 .....: [not set]
Private DO 2 .....: [not set]
Signature PIN ....: forced
Key attributes ...: 2048R 2048R 2048R
Max. PIN lengths .: 32 32 32
PIN retry counter : 3 0 3
Signature counter : 0
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

Initialize the USB device

First order of the day is to change the default PIN and Admin PIN. The PIN is required when we decrypt, sign and authenticate. The Admin PIN is used to administrate the card and can reset the PIN retry counter.

There is also a "Reset Code" that can be used to reset the PIN retry counter. It is needed if the OpenPGP Card was issued by an organization that didn't provide us with the Admin PIN. We don't need the Reset Code and leave it uninitialized for the Nitrokey Pro.

The default PIN is 123456 and the default Admin PIN is 12345678.

gpg --card-edit

gpg/card> admin
Admin commands are allowed

gpg/card> passwd
1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 1
Please enter the PIN: 123456
New PIN
Enter New PIN: manThisIsALongPost
Repeat New PIN: manThisIsALongPost
Pin changed.

Your selection? 3
Please enter the Admin PIN
Enter Admin PIN: 12345678
New Admin PIN: wishICouldMakeItShorter
Repeat Admin PIN: wishICouldMakeItShorter
PIN changed.

Your selection? Q

gpg/card> name
Cardholder's surname: Bobmeister
Cardholder's given name: Bob

gpg/card> lang
Language preferences: en

gpg/card> quit

Create a subkey for signing

Remember we already have a Primary Signing Key and a subkey for encryption. Now let's create a subkey for signing. Although it is possible to create keys directly on the devices, we will create it in the keyring on the hard drive, then back up the keyring before we finally move the private keys to the device.

First we need the ID of the Primary Signing Key

bob@home:~$ gpg -k
/home/bob/.gnupg/pubring.gpg
----------------------------
pub   2048R/675E7FA7 2015-03-26
uid                  Bob Bobmeister <bob@test.com>
sub   2048R/7AC1F9A2 2015-03-26

From the output above we find the key ID to be 675E7FA7. Edit this key in GnuPG's interactive mode to add a RSA subkey for signing only and give it a expiration period of 2 years:

bob@home:~$ gpg --edit-key 675E7FA7

gpg> addkey
Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
Your selection? 4

RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 2048

Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 2y

GnuPG lists the Primary Signing Key and all subkeys. The last one says "usage: S" for signing. The other key is for "usage: E" encryption.

pub  2048R/675E7FA7  created: 2015-03-26  expires: never       usage: SC  
                     trust: ultimate      validity: ultimate
sub  2048R/7AC1F9A2  created: 2015-03-26  expires: never       usage: E   
sub  2048R/1CA3EBA3  created: 2016-02-06  expires: 2018-02-05  usage: S   
[ultimate] (1). Bob Bobmeister <bob@test.com>

Notice the original encryption subkey doesn't have an expiration period yet. Let's fix that but first don't forget to save:

gpg> save

Change the expiration period of the encryption subkey

We already have the key ID of the encryption subkey from the previous output. Let's edit the key:

bob@home:~$ gpg --edit-key 7AC1F9A2

While we selected the key ID of the subkey in the command above, we need to select it again in the interactive mode. First list the keys:

gpg> list

pub  2048R/675E7FA7  created: 2015-03-26  expires: never       usage: SC  
                     trust: ultimate      validity: ultimate
sub  2048R/7AC1F9A2  created: 2015-03-26  expires: never       usage: E   
sub  2048R/1CA3EBA3  created: 2016-02-06  expires: 2018-02-05  usage: S   
[ultimate] (1). Bob Bobmeister <bob@test.com>

The subkey we want to change is at index 1 (starting from 0). Choose the key this way:

gpg> key 1

pub  2048R/675E7FA7  created: 2015-03-26  expires: never       usage: SC  
                     trust: ultimate      validity: ultimate
sub* 2048R/7AC1F9A2  created: 2015-03-26  expires: never       usage: E   
sub  2048R/1CA3EBA3  created: 2016-02-06  expires: 2018-02-05  usage: S   
[ultimate] (1). Bob Bobmeister <bob@test.com>

Notice the selected subkey is marked with a star. Now we can change its expiration period:

gpg> expire
Changing expiration time for a subkey.
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 2y
Key expires at man 05 feb 2018 21:34:08 CET

pub  2048R/675E7FA7  created: 2015-03-26  expires: never       usage: SC  
                     trust: ultimate      validity: ultimate
sub* 2048R/7AC1F9A2  created: 2015-03-26  expires: 2018-02-05  usage: E   
sub  2048R/1CA3EBA3  created: 2016-02-06  expires: 2018-02-05  usage: S   
[ultimate] (1). Bob Bobmeister <bob@test.com>

gpg> save

Again don't forget to save.

Backup

Backup the files under ~/.gnupg to an encrypted USB flash drive.

This is the last chance to backup the private keys before they are removed from the hard drive.

Move private subkeys to the USB device

To move a subkey to the USB device we enter GnuPG edit mode again, select the subkey and run the command keytocard.

Open edit mode with the Primary Signing Key ID 675E7FA7.

bob@home:~$ gpg --edit-key 675E7FA7
pub  2048R/675E7FA7  created: 2015-03-26  expires: never       usage: SC  
                     trust: ultimate      validity: ultimate
sub  2048R/7AC1F9A2  created: 2015-03-26  expires: 2018-02-05  usage: E   
sub  2048R/1CA3EBA3  created: 2016-02-06  expires: 2018-02-05  usage: S   
[ultimate] (1). Bob Bobmeister <bob@test.com>

Toggle key mode to see private keys.

gpg> toggle

sec  2048R/675E7FA7  created: 2015-03-26  expires: never     
ssb  2048R/7AC1F9A2  created: 2015-03-26  expires: never     
ssb  2048R/1CA3EBA3  created: 2016-02-06  expires: never     
(1)  Bob Bobmeister <bob@test.com>

The subkey for decryption is at index 1 (beginning from 0):

gpg> key 1

sec  2048R/675E7FA7  created: 2015-03-26  expires: never     
ssb* 2048R/7AC1F9A2  created: 2015-03-26  expires: never     
ssb  2048R/1CA3EBA3  created: 2016-02-06  expires: never
(1)  Bob Bobmeister <bob@test.com>

Finally run keytocard and select the encryption key slot.

gpg> keytocard
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]

Please select where to store the key:
   (2) Encryption key
Your selection? 2

gpg: writing new key
                 
sec  2048R/675E7FA7  created: 2015-03-26  expires: never     
ssb* 2048R/7AC1F9A2  created: 2015-03-26  expires: never     
                     card-no: 0005 00003900
ssb  2048R/1CA3EBA3  created: 2016-02-06  expires: never     
(1)  Bob Bobmeister <bob@test.com>

The subkey is now safely stored on the USB device.

Moving the private signing subkey is pretty much the same, but first we must deselect the current subkey by running the command key 1 again.

gpg> list

sec  2048R/675E7FA7  created: 2015-03-26  expires: never     
ssb* 2048R/7AC1F9A2  created: 2015-03-26  expires: never     
                     card-no: 0005 00003900
ssb  2048R/1CA3EBA3  created: 2016-02-06  expires: never     
(1)  Bob Bobmeister <bob@test.com>

gpg> key 1

sec  2048R/675E7FA7  created: 2015-03-26  expires: never     
ssb  2048R/7AC1F9A2  created: 2015-03-26  expires: never     
                     card-no: 0005 00003900
ssb  2048R/1CA3EBA3  created: 2016-02-06  expires: never     
(1)  Bob Bobmeister <bob@test.com>

gpg> key 2

sec  2048R/675E7FA7  created: 2015-03-26  expires: never     
ssb  2048R/7AC1F9A2  created: 2015-03-26  expires: never     
                     card-no: 0005 00003900
ssb* 2048R/1CA3EBA3  created: 2016-02-06  expires: never     
(1)  Bob Bobmeister <bob@test.com>

gpg> keytocard
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]

Please select where to store the key:
   (1) Signature key
   (3) Authentication key
Your selection? 1

gpg: writing new key

sec  2048R/675E7FA7  created: 2015-03-26  expires: never     
ssb  2048R/7AC1F9A2  created: 2015-03-26  expires: never     
                     card-no: 0005 00003900
ssb* 2048R/1CA3EBA3  created: 2016-02-06  expires: never     
                     card-no: 0005 00003900
(1)  Bob Bobmeister <bob@test.com>

Now save the changes:

gpg> save

If we do a card status now, we should see the decryption and signing slots initialized.

bob@home:~$ sudo gpg --card-status
Application ID ...: D2760001240102010005000039000000
Version ..........: 2.1
Manufacturer .....: ZeitControl
Serial number ....: 00003900
Name of cardholder: Bob Bobmeister
Language prefs ...: en
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Private DO 1 .....: [not set]
Private DO 2 .....: [not set]
Signature PIN ....: forced
Key attributes ...: 2048R 2048R 2048R
Max. PIN lengths .: 32 32 32
PIN retry counter : 3 0 3
Signature counter : 0
Signature key ....: 8AB7 D354 B481 B283 D2B6  731D 4E76 A255 1CA3 EBA3
      created ....: 2016-02-06 15:27:55
Encryption key....: 86B8 307E 90D5 1478 1B66  9266 A3EC 32EA 7AC1 F9A2
      created ....: 2015-03-26 20:32:30
Authentication key: [none]
General key info..: pub  2048R/1CA3EBA3 2016-02-06 Bob Bobmeister <bob@test.com>
sec   2048R/675E7FA7  created: 2015-03-26  expires: never     
ssb>  2048R/7AC1F9A2  created: 2015-03-26  expires: 2018-02-05
                      card-no: 0005 00003900
ssb>  2048R/1CA3EBA3  created: 2016-02-06  expires: 2018-02-05
                      card-no: 0005 00003900

Test run

Let's do a little test run.

Insert the USB device. Then run the following commands to encrypt a file with our own public encryption key, still present in our keyring on the hard drive. Finally decrypt it with the private key that was moved to the USB device:

bob@home:~$ echo 'hello world' > test.txt
bob@home:~$ gpg -o test.txt.gpg -r bob -e test.txt
bob@home:~$ gpg -o test2.txt -d test.txt.gpg
bob@home:~$ cat test2.txt
hello world

Remove the Primary Signing Key from the keyring

Finally we are ready to remove the Primary Signing Key from the keyring on the hard drive.

Don't remove the Primary Signing key before you have backed up the keyring as described in a previous section!

We are going to use the gpg command "--delete-secret-keys" which takes the Primary Signing Key as argument, but also deletes its private subkey proxies. Because we only want to delete the private Primary Signing Key from the keyring, we export the private subkeys before deleting the private Primary Signing Key and import them back afterwards.

First export the subkeys in a file. Append an exclamation mark to the subkey IDs.

bob@home:~$ gpg -o subkeys --export-secret-subkeys 7ac1f9a2! 1ca3eba3!

Then delete the private Primary Signing Key including the private subkeys:

bob@home:~$ gpg --delete-secret-keys 675e7fa7

Now import the private subkeys back into the keyring:

bob@home:~$ gpg --import subkeys

When we list the private keys, the Primary Signing Key now shows a # sign to indicate that the key is not actually available in the keyring.

bob@home:~$ gpg -K
sec#  2048R/675E7FA7 2015-03-26
uid                  Bob Bobmeister <bob@test.com>
ssb>  2048R/7AC1F9A2 2015-03-26
ssb>  2048R/1CA3EBA3 2016-02-06

Finally clean up the file with the exported subkeys.

bob@home:~$ shred --remove --zero subkeys

And we are done.

{{model.usr.name}}
{{cmt.user.name}}
{{cmt.user.name}}
{{childcmt.user.name}}
{{childcmt.user.name}}