Goal: to remove my ssh key entirely from disk and use my yubikey instead.
Why? Hopefully better security: it will be much more difficult to steal my SSH key and access all the things.
What I use my SSH key for:
Update 2022: added notes to ensure you have scdaemon installed, or gpg won’t detect your smartcard.
Update 2022 Q4: Keys expired, so I geuss time for the annual refresh. Added a section for extending the expiry. Next year maybe I’ll try creating new keys.
Update 2024: New install on Ubuntu 23.04 - added some new stuff to get the agent to place nice with gnome (IdentityAgent), also better to use .profile
instead of .bashrc
.
Thanks to: https://mlohr.com/gpg-agent-for-ssh-authentication-update/
YubiKey offers two different methods that can be used for SSH: PIV or OpenPGP. So which one should I use? Luckily yubico already provides a good discussion on this topic.
https://developers.yubico.com/PIV/Guides/Securing_SSH_with_OpenPGP_or_PIV.html
Because the SSH communication is done through the GnuPG agent, the OpenPGP option is more complicated than PIV to set up. However, developers who use Git to sign their commits typically choose the OpenPGP path because they already have it up and running.
I’m a developer, so interested in signing my commits too. Guess I should go the hard route. While rare, sometimes I might also want to perform some openpgp stuff too.
Before I rm .ssh/id_rsa*
, I brainstormed on what would happen if I were to lose or have my yubikey stolen.
If I lose the SSH key:
authorized_keys
. If I want to be extra diligent most of my servers can be rebuilt from scratch as they are mostly stateless.If I lose a signing key:
Depending on how it’s set up, the yubikey itself will also ask for local authentication before doing any signing or authentication. This should also harden against adversaries that manage to steal the yubikey (which I would think is extremely unlikely in my case).
I don’t see a need to keep a backup of the key, as revocation and resetting should be fairly easy. I will only keep a backup of the revocation certificate.
Reference: https://developers.yubico.com/yubikey-manager/
sudo apt-add-repository ppa:yubico/stable
sudo apt update
sudo apt install yubikey-manager
Once installed, can see some info on the plugged in key:
tethik@mimikyu:~$ ykman info
Device type: YubiKey 4
Serial number: <snip>
Firmware version: 4.3.7
Enabled USB interfaces: OTP+FIDO+CCID
Applications
OTP Enabled
FIDO U2F Enabled
OpenPGP Enabled
PIV Enabled
OATH Enabled
FIDO2 Not available
As good practice, it’s probably a good idea to reset the key before use. In my case I had experimented with the yubikey previously and wanted to start from a clean slate.
ykman openpgp reset
WARNING! This will delete all stored OpenPGP keys and data and restore factory settings? [y/N]: y
Resetting OpenPGP data, don't remove your YubiKey...
Success! All data has been cleared and default PINs are set.
PIN: 123456
Reset code: NOT SET
Admin PIN: 12345678
Reference: https://developers.yubico.com/PGP/Importing_keys.html
2022 Update: Ensure you have scdaemon installed, or gpg won’t detect your smartcard. If you get this error message when connecting to the card:
$ gpg-connect-agent --hex "scd apdu 00 f1 00 00" /bye
ERR 67108983 No SmartCard daemon <GPG Agent>
Installing scdaemon seems to resolve it.
$ sudo apt install scdaemon
What follows is my own notes from doing the same and may be useful in the future if the above link no longer works.
The basic procedure here is to generate the keypair on your own computer first, then import it to the yubikey. The yubikey itself does not support generating the PGP key. The key itself is also has two subkeys attached, one for signing and one for authentication, as per recommendations in the above reference. If needed, a further one could be added for encryption.
My yubikey (version 4) only supports 2048 bit rsa keys, so make sure to pick a cipher that your version of yubikey supports. Otherwise it will fail when you import the key to the yubikey.
I generate key with…
gpg --full-gen-key
From the output, grab the revocation certificate and store it somewhere safe.
gpg: key 79A936FAEE8C3B4A marked as ultimately trusted
gpg: revocation certificate stored as '/home/tethik/.gnupg/openpgp-revocs.d/644892D6680B2FABB541FA5779A936FAEE8C3B4A.rev'
public and secret key created and signed.
Using gpg --expert --edit-key <key-id>
we will add two subkeys. One for authentication
and one for signing. The authentication key will be used for SSH, and the signing key for commit signing.
gpg --expert --edit-key 13AFCE85
gpg (GnuPG) 2.0.22; Copyright (C) 2013 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Secret key is available.
pub 2048R/13AFCE85 created: 2014-03-07 expires: 2014-06-15 usage: SC
trust: ultimate validity: ultimate
sub 2048R/D7421CDF created: 2014-03-07 expires: 2014-06-15 usage: E
[ultimate] (1). Foo Bar <foo@example.com>
gpg> addkey
2048-bit RSA key, ID 13AFCE85, created 2014-03-07
Please select what kind of key you want:
(3) DSA (sign only)
(4) RSA (sign only)
(5) Elgamal (encrypt only)
(6) RSA (encrypt only)
(7) DSA (set your own capabilities)
(8) RSA (set your own capabilities)
Your selection?
Finally, the key is imported to the yubikey. Note the keytocard
command.
gpg --edit-key 13AFCE85
gpg (GnuPG) 2.0.22; Copyright (C) 2013 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Secret key is available.
pub 2048R/13AFCE85 created: 2014-03-07 expires: 2014-06-15 usage: SC
trust: ultimate validity: ultimate
sub 2048R/D7421CDF created: 2014-03-07 expires: 2014-06-15 usage: E
sub 2048R/B4000C55 created: 2014-03-07 expires: 2014-06-15 usage: A
[ultimate] (1). Foo Bar <foo@example.com>
gpg> toggle
sec 2048R/13AFCE85 created: 2014-03-07 expires: 2014-06-15
ssb 2048R/D7421CDF created: 2014-03-07 expires: never
ssb 2048R/B4000C55 created: 2014-03-07 expires: never
(1) Foo Bar <foo@example.com>
gpg> keytocard
Really move the primary key? (y/N) y
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
Generate the public key via the following. The string it returns can be used for the
authorized_keys
file as well as GitHub auth.
gpg --export-ssh-key 4B4C120067AC0E52 (authentication key)
Switch from OpenSSH ssh-agent to GnuPG as ssh-agent.
First find path to the unix socket created by gnupg
gpgconf --list-dirs | grep ssh
Then set the SSH_AUTH_SOCK
variable with that path. E.g.
export SSH_AUTH_SOCK=/run/user/1000/gnupg/S.gpg-agent.ssh
Consider adding it to your .profile
to automatically apply it for new shells7.
You may also want to inform ssh that it should use gpg-agent, you can do so via:
echo "IdentityAgent /run/user/1000/gnupg/S.gpg-agent.ssh" >> ~/.ssh/config
https://developers.yubico.com/PGP/Git_signing.html
Enable commit signing in your git config:
git config --global user.signingkey 79A936FAEE8C3B4A (signing key)
git config --global commit.gpgSign true
You should ensure also that the email you configured for git is the same as the one you generated the gpg key for, otherwise github will show your commits as unverified.
To verify that your commits are signed, you should see them show up if
you use the --show-signature
option.
git log --show-signature
To get the public key, for e.g import into github
gpg --export -a 79A936FAEE8C3B4A > yubikey.pub
When using the key now, it should ask you for a PIN. There are two pins to keep track of: the Normal PIN which is the one used for the daily operations of signing/encrypting using the card, and an Admin PIN which authorizes you to change settings on the card (e.g. force signature) as well as reset the Normal PIN.
By default, the Admin PIN’s value is 12345678. The normal PIN is 123456. The Admin PIN seems to require at least 8 digits, while the normal PIN can be shorter.
You can and should change these via the gpg --change-pin
command. This will
provide some protection if your key from being used if it is stolen
tethik@mimikyu:~$ gpg --change-pin
gpg: OpenPGP card no. <snip> detected
1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit
Option 2) allows you to change the normal PIN using the admin PIN, which is why it’s important to change both.
If you set your key to expire after some time, you’ll eventually need to refresh it.
You can either refresh the expiry date, or if you are more paranoid create new keys altogether. Here I show the former, how to refresh the expiry date.
$ gpg --expert --edit-key 644892D6680B2FABB541FA5779A936FAEE8C3B4A
> expire # sets a new expiry date on the primary key
> key 1 # selects subkey 1
> key 2 # selects subkey 1
> expire # expires both key 1 and 2
> save # saves the changes you made
Here is a log of me doing it for my own key:
$ gpg --expert --edit-key 644892D6680B2FABB541FA5779A936FAEE8C3B4A
gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Secret key is available.
sec rsa2048/79A936FAEE8C3B4A
created: 2020-11-05 expired: 2022-11-05 usage: SC
card-no: 0006 06917271
trust: ultimate validity: expired
ssb rsa2048/0641BA0FD747AB91
created: 2020-11-05 expired: 2022-11-05 usage: E
ssb rsa2048/4B4C120067AC0E52
created: 2020-11-05 expired: 2022-11-05 usage: A
card-no: 0006 06917271
[ expired] (1). Joakim Uddholm <tethik@blacknode.se>
gpg> expire
Changing expiration time for the primary key.
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) 1y
gpg> save
gpg> key 1
sec rsa2048/79A936FAEE8C3B4A
created: 2020-11-05 expires: 2023-11-06 usage: SC
card-no: 0006 06917271
trust: ultimate validity: ultimate
ssb* rsa2048/0641BA0FD747AB91
created: 2020-11-05 expires: 2023-11-06 usage: E
ssb rsa2048/4B4C120067AC0E52
created: 2020-11-05 expires: 2023-11-06 usage: A
card-no: 0006 06917271
[ultimate] (1). Joakim Uddholm <tethik@blacknode.se>
gpg> key 2
sec rsa2048/79A936FAEE8C3B4A
created: 2020-11-05 expires: 2023-11-06 usage: SC
card-no: 0006 06917271
trust: ultimate validity: ultimate
ssb* rsa2048/0641BA0FD747AB91
created: 2020-11-05 expires: 2023-11-06 usage: E
ssb* rsa2048/4B4C120067AC0E52
created: 2020-11-05 expires: 2023-11-06 usage: A
card-no: 0006 06917271
[ultimate] (1). Joakim Uddholm <tethik@blacknode.se>
gpg> expire
Are you sure you want to change the expiration time for multiple subkeys? (y/N) y
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) 1y
Key expires at mån 6 nov 2023 14:19:21 CET
Is this correct? (y/N) y
sec rsa2048/79A936FAEE8C3B4A
created: 2020-11-05 expires: 2023-11-06 usage: SC
card-no: 0006 06917271
trust: ultimate validity: ultimate
ssb* rsa2048/0641BA0FD747AB91
created: 2020-11-05 expires: 2023-11-06 usage: E
ssb* rsa2048/4B4C120067AC0E52
created: 2020-11-05 expires: 2023-11-06 usage: A
card-no: 0006 06917271
[ultimate] (1). Joakim Uddholm <tethik@blacknode.se>
For SSH, your public key should be the same as before (I guess it doesn’t contain an expiry date?), so you don’t need to send out an updated id_yubikey.pub
to github or your authorized_keys-files.
For signed commits and github, you’ll need to generate a new public key again.
gpg --export -a 79A936FAEE8C3B4A > yubikey.pub