A cryptographic toolchain
03 Nov 2017Strong authentication is often a requirement for working with software. It can be a hassle to manage the secrets required to adequately protect the integrity of our code and control access to its deployment. Many developers give up, treating the whole game as not worth the candle.
I’ve cobbled together a solution involving a Yubikey secure element that I like a lot, both for its security, and its convenience. The whole process takes about 30 minutes.
A Cryptographic Toolchain
What we’re going to do is set up a Yubikey not only as a universal two-factor (U2F) token, but also as a PGP smart-card. The result will be a strong security element that you can use to sign into Github, Gmail and Dropbox, sign and encrypt files (including code commits and archives) and authenticate to SSH servers (include Github for pushes.) An added value is that the Yubikey can fit in your wallet, which means you can carry your security keys on your person, much like physical keys.
Setting up the Yubikey
Install ykman (e.g. brew install ykman
)
- it’s a python package, so it’s maybe possible to get through
pip
.
Plug in your Yubikey.
First thing to do:
ykman info
Device name: YubiKey 4
Serial number: 6646987
Firmware version: 4.3.7
Enabled connection(s): OTP+U2F+CCID
Device capabilities:
OTP: Enabled
U2F: Enabled
CCID: Enabled
OPGP: Enabled
PIV: Enabled
OATH: Enabled
The really important thing here is the firmware version. Yubikeys with a firmware below 4.3.5 will need to be replaced(it’s free!)
We’d like to use the key for all of its functionality. So let’s make sure everything is turned on.
> ykman mode otp+u2f+ccid
Mode is already OTP+U2F+CCID, nothing to do...
# (you might be asked to confirm the change, and enter the admin password)
Next, we’d like to require the key to wait for us to touch it before signing anything. This prevents e.g. malware on our laptop from using a plugged in key to sign bad things as if it were us.
> ykman openpgp touch aut on
Current touch policy of AUTHENTICATE key is OFF.
Set touch policy of AUTHENTICATE key to ON? [y/N]: y
Enter admin PIN:
Touch policy successfully set.
> ykman openpgp touch sig on
Current touch policy of SIGN key is OFF.
Set touch policy of SIGN key to ON? [y/N]: y
Enter admin PIN:
Touch policy successfully set.
Touch for encryption is probably unnecessary,
but it can’t hurt.
ykman openpgp touch enc on
U2F
This is too easy not to do.
Go to sites that use U2F (Google, Github) and in your account settings, look for Two-factor authentication devices. Plug in the Yubikey, choose “add authentication device” and when prompted, touch the blinking light.
Smart Card Setup
Now we’re going to set up a PGP keypair in the Yubikey. We’ll use this to sign code, but also to authenticate our pushes to Github, and even sign in to SSH servers.
Start with:
gpg2 --card-edit
This’ll put you into an interactive mode with GPG - note the different prompt.
gpg/card> admin
gpg/card> passwd
Enter 3
to change the admin password.
You’ll be challenged with the default admin password,
which is 12345678
.
(At this phase, it won’t make it clear that it’s the default password,
although other GPG commands will remind you.)
Then for the new password,
and a confirmation repeat.
It’s my understanding that you want to limit yourself
to an 8-digit numeric code -
some environments will allow alphanumerics,
but others won’t, and you won’t be able to use
the Yubikey there.
Then enter 1
to change the user password.
Like the admin password,
you’ll need the default password,
which is 123456
.
The same advice about using
a 6-digit numeric code applies.
Now, we want to generate a keypair on the key. In my opinion, this is one of the best features of this approach. You generate a keypair in such a way that the private key (the secret part) never even leaves the smart-card - which is designed to never let that data off of the device.
gpg/card> generate
You’ll be asked for the keysize of three different keys
(signature, encryption and authentication)
which I set to 4096
out of an abundance of caution.
On modern hardware
there’s pretty much no downside.
Note that the NEO can’t handle keys that large,
so stick to the still-decent 2048
bit keys.
You may also want to specify personal details on the card itself - these don’t carry over from the identifiers embedded in the keys.
gpg/card> list
...
gpg/card> name
...
Finally,
gpg/card> quit
You can check that gpg has a hold of the key by
> gpg2 --list-keys --keyid-format 0xlong
The new key should be the last one.
Looks like
...
pub rsa4096/0x0123456789ABCDEF 2017-10-25 [SC]
9EB2E24B3D545F30732E62A112E21E4B9A3F82AA
0123456789012345678901234567890123456789
uid [ultimate] Judson Lester (General purpose key) <me@judsonlester.info>
sub rsa4096/0x456789ABCDEF0123 2017-10-25 [A]
sub rsa4096/0x789ABCDEF0123456 2017-10-25 [E]
Note that there are three keypairs above:
a master, signing key (the one marked [SC]
),
an authentication key (marked [A]
),
and an encryption key - [E]
.
There’s good reasons for this, but we mostly just need to know which is which.
Further on I’ll refer to some key ids like $AUTHKEY_ID
which in the above is 0x456789ABCDEF0123
.
Now we’re going to publish this key so that other people can use it.
> gpg2 --send-keys
Next, we’ll pull the public information back onto the Yubikey from the keyservers.
> gpg2 --card-edit
gpg/card> fetch
gpg/card> quit
SSH pubkey
Even though GPG and SSH use the same kinds of basic cryptography, SSH needs a different format for its public keys. GPG has a handy tool to convert its public keys so that SSH can use them:
gpg2 --export-ssh-key $AUTHKEY_ID > ~/.ssh/yubi-ssh.pub
Next, we’re going to set up gpg-agent so that it can act as an SSH agent. It’s run on its own and provide your Yubikey keypair to SSH for authentication and to GPG for signing.
Note: you have to spell our your home directory in the below -
replace $HOME with whatever echo $HOME
outputs.
~/.gnupg/gpg-agent.conf
:
enable-ssh-support
write-env-file /home/$USERNAME/.gpg-agent-info
default-cache-ttl 28800
max-cache-ttl 86400
default-cache-ttl-ssh 28800
max-cache-ttl-ssh 86400
To make this work, you’ll need to add this to your ~/.bash_profile
:
export GPG_TTY=$(tty)
unset SSH_AGENT_PID
if [ "${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]; then
export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
fi
That way SSH and GPG will know where to find the GPG agent. Once you’ve done that, to get the configs picked up in any shell that was already running (like the current one):
> source ~/.bash_profile
> gpgconf --kill gpg-agent
> gpg-connect-agent \bye
Other keys in agent
You very likely have other SSH keys you’d like to use.
You can use ssh-add
to put other keys into the gpg-agent.
You’ll be challenged for their passphrase once on the command line
and then again for a new passphrase for their use in gpg-agent.
Honestly, I tend to use the same passphrase for simplicity.
From then on, if SSH needs that key, gpg-agent will use its pinentry to ask for the “GPG” passphrase (i.e. the new one you enter as you ssh-add).
From now on, when SSH needs that key, GPG agent will challenge you for its passphrase, and then make it available to SSH for a set period.
Git signing
Next, we want to configure Git to use our GPG keypair.
Add this to ~/.config/git/config
(or ~/.gitconfig
)
[user]
signingkey = {last 6 hex of signing key id (e.g. ABCDEF)}
[commit]
gpgSign = true
[tag]
forceSignAnnotated = true
Now when you commit, or do annotated tags like:
git tag -m "Annotated, therefore signed" a-tag
git will use your signing key on the item.
Github settings
Copy ~/.ssh/yubi-ssh.pub
to the SSH pubkeys.
This will let you use your Yubikey to authenticate pushes.
If you need to specify a key in your ~/.ssh/config
,
use ~/.ssh/yubi-ssh.pub
.
This confused me,
since you usually identify keypairs to SSH
by the private key file,
but that “file” is intentionally unavailable.
Copy the output of gpg2 --export --armor ${SIGNING_ID}
to GPG keys.
This will get you the good feeling of “verified” badges next to your commits.
Notification
Everything up until now has been simple configuration of stock tools.
The one irritation I had was that,
while I wanted the Yubikey to require interaction before signing anything
(because otherwise any process on the computer could get access to the keys),
the quietly blinking “Y” wasn’t quite enough
to get my attention all the time.
What happens is that I’d make a git commit
and
look away, task complete,
only to have the signature part time out and fail.
So, I built a little notification engine for the Yubikey.
For now, basically, see https://github.com/nyarly/socket-notify
You’ll need Cargo (i.e. the Rust dev environment) set up on your machine to make it work, but that’s easier than you might expect.
First, run
cargo install socket-notify
Then, put this into ~/.gnupg/scdaemon.conf
:
log-file socket:///tmp/scdaemon.sock
debug 1027
debug-assuan-log-cats 511
Then run
gpg-connect-agent scd killscd
That should restart the program that interacts with the Yubikey as a smartcard.
Make sure socket-notify runs when your computer is running (I use systemd user services for this, but it depends on your OS.)
The upshot is that when a program challenges the Yubikey for a signature, you’ll get a desktop notification to remind you that you need to touch the button.
Revocation certificate
If you lose your yubikey, you want to publish the fact that you can’t use that key anymore. You want to prove that you, the owner of the key, are the one publishing that the key isn’t any good. So obviously you want to sign a message to that effect. But you won’t have the key to sign it with anymore. The way out of this paradox is to generate (and protect) “revocation certificates,” essentially pre-signed claims that the key is bad, ready to distribute when the worst happens.
Current versions of gpg2
will automatically generate a revocation certificate.
If you don’t see a message about “revocation certificate stored as …”,
(or even if you aren’t sure)
you can always
gpg2 --gen-revoke 0x0123456789ABCDEF > ~/.gnupg/openpgp-revocs.d/yubikey-ABCDEF.gpg.asc
You can also generate multiple certificates - one for each computer you use your Yubikey on, for instance.
You’ll want to protect that file carefully - some suggest backing it up to removable media. It can’t be used to impersonate you, but if it’s published to a keyserver, other users of GPG will have a hard time trusting the key again. Which is the point.
Actually revoking the key
If you lose your Yubikey, (or, say, it turns out to be theoretically vulnerable to ROCA…) you revoke the key like this:
gpg2 --import ~/.gnupg/openpgp-revocs.d/yubikey-ABCDEF.gpg.asc
gpg2 --send-keys
which adds the certificate to your local keyring, and then publishes it to the keyservers.
Then, start over with this guide.