TL;DR: jump to the Instructions for creating an airgapped key set with two yubikeys
- Overview
- What to expect
- Requirements
- Usage
- Scenario 1: Instructions for creating an airgapped key set with two yubikeys
- Scenario 2: Instructions for creating an airgapped soft key set
- Key practices followed in this repository
- Using ZFS and EncFS
- Expired Key Renewals
- To do
- Known issues
There are numerous well written guides that describe how to manually generate a GPG key set, both for soft keys and for Yubikeys or other PGP/GPG SmartCards. The aim of this work is to automate the GPG commands so you can generate and create your key set without having to become a GPG veteran.
There are a couple of scenarios currently supported:
- An offline soft master key, and subkeys stored on one or more smartcards or yubikeys.
- An offline soft master key, and soft subkeys that can be imported into your main devices.
- An offline soft master key, and several sets of soft subkeys, with a common encryption key to be used across devices.
You can, of course, choose not to keep your master key offline or airgapped.
Additionally you can keep your master key on a smartcard to aide with sub key renewals. If you have a spare smartcard or are willing to purchase one, this is highly recommended as it massively simplifies renewals and certification of other keys. See the instructions on the wiki for how to do this.
The goal is to have a structure as follows for your day to day use. The master secret key is:
- generated on an airgapped device
- only stored in cold storage
- only capable of certifying subkeys
All the private sub keys are either soft keys in your regular ~/.gnupg keyring, or are stored on a Yubikey or other PGP/GPG smartcard.
sec# rsa4096 2017-06-26 [C] [expires: 2022-06-25]
D49147668E74D68C108A83DB53E708995F9835CD
uid [ultimate] Real Name (https://keybase.io/userid) <email@addr.ess>
ssb> rsa2048 2017-06-26 [E] [expires: 2019-06-26]
ssb> rsa4096 2017-06-26 [S] [expires: 2019-06-26]
ssb> rsa4096 2017-06-26 [A] [expires: 2019-06-26]
ssb# rsa2048 2017-09-09 [S] [expires: 2019-06-26]
ssb# rsa2048 2017-09-09 [A] [expires: 2019-06-26]
In the above example we can make the following observations:
#
insec#
indicates the private key part of the master key is not available locally>
inssb>
indicates the private key for each subkey is stored on a PGP/GPG SmartCard or Yubikey.#
inssb#
indicates the public key for these subkeys is available. This would usually be the case if you have the public keys for an additional smartcard in your keychain.[C]
indicates the key is only capable of certification- signing (
[S]
), encryption ([E]
) and authentication ([A]
) are all achieved by the corresponding individual subkeys - The encryption key is 2048 bit so that it can be shared between multiple smartcards or yubikeys, among which the lowest common support is 2048 bit keys. If your smartcards support higher sizes you can use the size you need.
- The signing and authentication keys are 4096 bit as they're generated on the card at the maximum supported bits for that specific card.
- GnuPG >= 2.1
- GNU Make
- Paperkey
- Air gapped device. Either a system that is not online, or make use of a live system image, with all connectivity disabled, possibly after downloading further requirements.
- One or two PGP/GPG smartcards, such as Yubikeys.
- At least two USB thumb drives to keep your master key safe. You may want to consider using SLC sticks, and/or using ZFSonLinux / O3X to keep the keys safe from bitrot and device failure. For a live OS image, GhostBSD works well, though it didn't boot on my 2013 Macbook Pro, so I used another old PC laptop.
- An additional USB thumb drive to transfer subkey shadows if using a SmartCard, or private subkeys if not using a smartcard, and public keys to your regular device.
- Access to a printer for creating your hard copies of your master key.
Use the built-in help for additional targets and usage.
gmake help
These steps will take you through an example scenario. Adjust as needed.
- Two Yubikeys, one is a Neo (max 2048bit keys), and the other a Nano (max 4096bit keys).
- Four USB sticks:
- First a USB stick for the live OS image.
- Two that will store your secrets offline and should never be used in a networked device. Ideally these are industrial quality, possibly SLC.
- One for transferring data across the air gap.
- GhostBSD live image. At the time of writing, the GhostBSD 11 beta images worked well, supports ZFS, and has up to date GPG packages. You could also use any linux live system, such as Tails, however be aware you won't get ZFS with that. See the Cold storage of the secret keys and revocation certificates section below for why ZFS would be of value.
- Passwords you'll be using to protect your keys, and if you choose to use EncFS, then also for the encrypted mount.
- Printer for the paper keys.
-
Prepare the USB drives for the OS, storing secrets, and for transferring keys. If your primary workstation is Linux or Mac and you want to use ZFS, prepare the sticks before moving to the air gapped system.
-
First, prepare your live OS image following the instructions for the live system you've chosen.
-
At least two USB thumb drives for storing your secrets. We'll refer to these as the secure USB sticks as they should ideally only be used on an air-gapped device. See the section below on Creating ZFS pools if that's how you'll be proceeding.
-
One USB thumb drive for transferring data between workstation and the air-gapped device. We'll refer to this as the transfer USB stick. It will contain a copy of this repository, GPG public keys and shadow keys. No secrets will be stored on this, and any data on here can be recreated from the secure USB sticks. See the section below on Creating ZFS pools if that's how you'll be proceeding.
-
-
Clone this repository to the transfer USB stick.
These steps will prepare two SmartCards with secret subkeys, paperkey backups that should be printed, and a revocation certificate in case the master key is damaged or compromised.
-
Boot up the air-gapped system.
-
Get the live OS network connected, then get all the packages you'll need to complete this task, and finally disable networking. For GhostBSD these are the steps:
-
Establish a network connection, whether wired or wireless, using the GUI.
-
In a terminal, get the necessary packages:
sudo pkg install gnupg paperkey fusefs-encfs gmake
-
Turn off networking:
sudo service netif stop
-
-
Insert the transfer USB stick, mount it, and copy the gpg-smartcard-automation repository off the USB stick. I recommend not working directly from the stick as I've experienced odd behaviours with the filesystems when doing this.
mkdir ~ghostbsd/zpools sudo zpool import -R ~ghostbsd/zpools airgap-transfers sudo zpool scrub airgap-transfers cp -a ~/zpools/airgap-transfers/gpg-smartcard-automation ~/ sudo zpool export airgap-transfers
-
Create your keyset:
-
Prepare your working environment
setenv GPGHOME $HOME/gpg-20170903 cd ~/gpg-smartcard-automation
-
Insert the first yubikey. If you've not used the key before, or if you're recently reset it (try
gmake reset-yubikey
), then the default admin PIN is12345678
, and regular PIN is123456
. Remember to change the default PIN as soon as possible (hint:gpg --card-edit
).gmake default
You'll be prompted for your password many times. Follow the instructions in the output to complete all the parts of this step.
-
If you'll be using a second yubikey, insert it now. The tooling will prepare another GPG homedir and keychain to work around a GnuPG limitation that will be fixed by T2291.
gmake import-secrets sckey
You can skip this step if you're only preparing a single Yubikey.
-
Copy data to USB sticks. A series of helper scripts make this easy for you.
-
Insert first secure USB stick, then
./copy-secrets-to-usb.sh $GPGHOME secure-1
In this case,
secure-1
is the pool name for this USB stick. This scripts assume you're using ZFS and EncFS. -
Repeat step 1 for additional secure USB sticks, adjusting the pool name as appropriate.
-
Insert the transfer USB stick to copy the data destined for your main device. As before, the second argument is the ZFS pool name. In this case there's no EncFS as there's nothing to be hidden here.
./copy-to-transfer-usb.sh $GPGHOME airgap-transfers
-
-
Print the paper master key. Ideally this will be via a USB connected printer rather than something wireless or networked. The file to print is:
$(GPGHOME)/DATA-airgapped/paperkey-master.txt
At this point you're done with the air gapped live OS work!
-
-
Import your new keys to your main devices.
-
Insert the transfer USB stick into your main device and copy the
$GPGHOME
path across to your system. For examplecp -a /Volumes/airgap-transfers/gpg-20170903 ~/ownCloud/
-
If you prepared multiple Yubikeys, some files will be suffixed with the Yubikey serial number, for example
$GPGHOME/DATA-transferred/subkey-shadows-03821903.asc
. Choose the one you want to import and pull it in.gpg --import < ~/ownCloud/gpg-20170903/DATA-transferred/subkey-shadows-03821903.asc
-
Create the link between the imported shadows keys and your Yubikey:
gpg --card-status
-
If you want to use your other Yubikey with your mobile you'll need to import the other
subkey-shadows-*.asc
file into something like OpenKeyChain (for Android), then register your Yubikey with the app.
-
You may want to do a few further things to strengthen your key and web of trust:
-
Sign your new key with your old key.
gpg --local-user 0xOLD_KEY_ID --sign-key NEW_KEY_ID
-
Pull in the public keys for your second set of keys.
gpg --import /path/to/DATA-transferred/key-master.pub gpg --import /path/to/DATA-transferred/subkey*.pub
-
Upload your new public key to https://keybase.io
If you don't have a smartcard, you can generate only soft keys, with the master key kept in cold storage, and your subkeys available to your every day devices. The benefit of this is that if your subkeys are compromised for any reason, your web of trust doesn't break down due to the trust being anchored to your master key.
The main difference here is that in step 4.2 of the first
scenario, rather than running gmake default
, you need to gmake softkeys
. There are some further considerations for copying your secrets
across to the your main device, and those steps will be detailed soon. Short
explanation is that you'll likely want to use an EncFS on your transfer USB
stick to keep those secrets less exposed.
The master key is a soft key only capable of certification and should not be stored on a device used daily. It's a good idea to keep this safe on an encrypted USB device, and even better to have a print out of it, as is prepared by paperkey.
The generated master key backups are stored at:
- $(GPGHOME)/DATA-airgapped/key-master.asc
- $(GPGHOME)/DATA-airgapped/paperkey-master.txt
When using Yubikeys or PGP/GPG smartcards, we want to have different signing and authentication keys on each smartcard so that in the event one is lost we only need to revoke that single key set of that token, plus the shared encryption key.
The use of a shared encryption key is primarily for convenience: to have the same encryption key used on both smartcards making it easy for others to send us encrypted messages and not require them to have any knoweldge of our PGP/GPG key structure.
For these reasons we generate the encryption key as a soft key that is transferred to the smartcard, while we generate the signing and authentication keys directly on the cards.
If you don't have a smartcard then you can generate all the keys as soft keys using scenario 2.
Storing and keeping the soft keys requires a more prudence than when using a smartcard.
Ideally all your key material will be generated on an air gapped device, and even better on a live OS so there's no persistence. If you use an ephemeral system for this, such as Tails or GhostBSD, you'll need to keep your master key and revocation certificates on a couple of USB thumb drives.
You should consider using SLC (Single Layer Cell) USB sticks as they should be more resilient, and using a filesystem capable of bitrot detection and recovery, such as ZFS.
You should also print out the paperkey representations of all private key material as paper generally lasts a lot longer than USB thumb drives under normal conditions.
Using ZFS offers some benefits:
-
Interoperability between unix and macOs. On Linux this is availabe from the ZFSonLinux project, and on macOS use O3X. BSDs generally come with ZFS by default.
-
Support for multiple copies of the data on a single drive. If your USB thumbdrive were to experience bitflips/bitrot ZFS will return the correct data silently, though there are some caveates. Details below.
Using EncFS on top of ZFS has the benefit of interoperability between unix and macOS.
This section isn't comprehensive as there's enough documentation online, however here's a quick start to creating a pool with multipe data copies on a single stick:
-
Determine the device for your USB stick
diskutil list
Look for the device that corresponds to the characteristics of your stick and take node of the
/dev/diskX
title. -
Create the new zpool. This is a destructive operation on the USB stick, so be sure you know what you're doing.
sudo zpool create -f -o ashift=12 -O copies=3 -O normalization=formD airgap-transfers /dev/diskX
This will create a volume with 3 copies of your data. To detect bitrot you'll need to perform scrubs of the disk as ZFS doesn't do this during normal operation. See ZFSonLinux issue 1256 for more details.
On macOS, if you have O3X installed, when you insert a ZFS USB stick it should get mounted automatically. If not, the follwing is what you need:
sudo zpool import POOL_NAME
You can get a list of available pools by omiting the POOL_NAME
.
Before you remove a USB stick from macOS you must export it:
sudo zpool export POOL_NAME
If you're managing multiple smartcards you'll need to follow these steps for each smartcard.
-
Ensure you're working from a clean slate
gpgconf --kill gpg-agent scdaemon ps -ef | grep gpg
-
Set up your working environment
export GNUPGHOME=/path/to/gpg/conf/path
-
Get the primary key ID
gpg --card-status
-
Start GPG key editing
gpg --edit-key $PRIMARY_KEY
-
Select the keys you want to update
gpg> key 1 gpg> key 4 gpg> key 5
-
Update expiry on the keys
gpg> expire ...
-
Save and exit
gpg> save
-
Export the new subkey shadows
for key in $subkey1 $subkey2 $subkey3; do gpg --export-secret-subkeys --export-options export-minimal --armor $key > $key.shadow done
Note that the inspected exports will appear to contain the primary key, however GnuPG essentially supports this
--export-secret-subkeys
as an extension to the PGP standard, and the primary key in these exports has been rendered unusable. -
Export the new public keys
gpg --export --export-options export-minimal --armor $PRIMARY_KEY > $GNUPGHOME/keys.pub
-
Transfer the exported
*.shadow
files to your main systems andgpg --import
the ones relevant to the smartcard you use on that device.
- Manage key expiry extention. As of GPG 2.1 only master key expiry can be easily manipulated with --quick-set-expire.
- Add tests to validate all resulting artefacts are as expected, for example the exported ASCII armored private key and subkeys are well structured and encrypted, and the SSH public key is exported correctly.
- When GnuPG supports automation of card key generation this will need to be implemented.
- For some reason a Makefile seemed a good idea at the outset due to the fact we're creating many files and it should handle idempotence for us. The code has probably grown beyond that and would benefit from a rewrite in something more suitable.
With a Raspberry Pi I wasn't able to get a working ZFS set up, probably because the Pi I'm using is 32bit, but it could also be due to limited memory.
-
Raspberry Pi (tested on a Pi Zero)
-
Raspbian (tested with Debian Stretch)
-
Install the base system to an SD card
-
Install essential packages
apt-get update apt-get install paperkey scdaemon
-
You may want to use LUKS encrypted volume, in which case you'll need:
apt-get install cryptsetup
-
Update & upgrade the system
apt-get update apt-get upgrade
-
Copy this repository on to the Raspberry Pi
GPG has a hard max key size of 4096bit. Apparently the brew version has upped this to 8192bit. You can try that if you desire, however keep in mind that most SmartCards only support 2048bit, while a few now support 4096bit.
As of GnuPG 2.1 the key handling logic has been entirely moved out of the old
gpg
binary and into gpg-agent
. As a result gpg-agent
will be invoked
automatically as needed, and therefore the gpg-agent
socket will always be
used for communication between the gpg
command line interface and the
running gpg-agent
. Unfortunately sockets on Unix systems have a hard limit
of 108 characters in their path, including macOS. This is not a limitation of
GnuPG but rather of the underlying OS, and gpg
may fail slightly cryptically
if the path is longer than 108 characters.
This is a GnuPG limitation that will be fixed by T2291.
Unfortunately this didn't make it into the GnuPG-2.2. We work around this limitation
by generating seaprate $GNUPGHOME folders for each smartcard being used. You then
only import the main one you would use day to day on a given device, but if the
need arises you're still able to switch to using the other smartcard by deleting
your key shadows/stubs (hint: gpg --delete-secret-and-public-keys 0xKEY_ID
), then
re-importing the other shadows/stubs and registering them with the gpg --card-status
command.