Problem: We want to authenticate to remote machines using SSH keys that are not stored on the client (local or remote) machine.
Why? Attackers can copy your private keys if the keys are kept on disk on the client. Although private keys are protected with a passphrase, if the keys are copied, attackers only need to acquire the passphrase (via bruteforce or keylogging).
Solution: Store the private key onto a hardware token. The private key cannot be copied from the token, and attackers need to steal your physical token in order to use it (ignoring any computer hardware compromise).
The YubiKey can’t store SSH keys, but can store GPG keys.
We can then utilize OpenPGP key pairs to operate as SSH key pairs, and
gpg-agent
to cache the passphrase (in lieu of ssh-agent
).
We do this by specifically creating an authentication subkey and loading that subkey
into the YubiKey.
Please send any comments, bugs, or fixes to cardi@acm.org.
2018-12-22 My good friend Raymond Cheng has an updated guide: Signing Git Commits and SSH Authentication with Yubikey.
2016-03-16 some minor updates.
2015-10-27 Thanks to Dan M. for general feedback and improvements.
2015-09-29 Thanks to Tyler B. for the feedback on gpg-agent.
2015-06-29 Thanks to Eric E. for the question on signed SSH keys.
Install and configure software
GNU/Linux: install
gnupg2
,gpg-agent
, and YubiKey NEO Manager. Mac OS X: install GPG Suite (Beta) and YubiKey NEO Manager.OS X and GPG
Getting GPG to work with OS X can be a frustrating exercise. It's recommended to use GPG Suite, but you can also install it using Homebrew, MacPorts, or from source. You will have to figure out the best configuration for yourself.
GnuPG 2.x
We need GnuPG 2.x (
gpg2
) as opposed to 1.x as recommended by Yubico. 2.x supports the OpenPGP card 2.0 specification and usesscdaemon
when interacting with a smart card.All OS: Run the YubiKey NEO Manager, enable “CCID”. (The NEO supports running all modes at the same time, but is not discussed here).
GNU/Linux: additionally, make the YubiKey accessible to the user (TODO)Modify
~/.gnupg/gpg.conf
to set your preferences. An example configuration:use-agent personal-cipher-preferences AES256 AES192 AES CAST5 personal-digest-preferences SHA512 SHA384 SHA256 SHA224 cert-digest-algo SHA512 default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
OpenPGP Best Practices
When using GPG to create and manage OpenPGP/GPG keys, it's in your best interest to configure it properly and understand why each configuration setting is applied.
Read the OpenPGP Best Practices guide for more information.
Generate PGP Keys
Generate your PGP keys in a secure environment. Use a LiveCD or LiveUSB distribution of your favorite Linux, and generate the keys directly onto a USB flash drive. The Tails distribution is a good choice.
Although creating the text backups in the tutorial is optional, it’s recommended that you store these backups in a safe place. An encrypted USB drive or CD stored in a safe or safety deposit box are good locations (you can even print them out).
Master Key and Subkeys
Your PGP key consists of a master key and one or many subkeys. The master key will be used to certify (or sign) your subkeys. The subkeys can be configured for one or multiple actions: encrypt, authenticate, or sign. (A signing subkey means to sign data, as opposed to other keys).
Passphrase
If you forget your passphrase, your PGP key cannot be used and any data encrypted using that key will be lost forever!
Generate a new master key:
$ gpg2 --expert --gen-key gpg (GnuPG/MacGPG2) 2.0.26; 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. Please select what kind of key you want: (1) RSA and RSA (default) (2) DSA and Elgamal (3) DSA (sign only) (4) RSA (sign only) (7) DSA (set your own capabilities) (8) RSA (set your own capabilities) Your selection? 8 Possible actions for a RSA key: Sign Certify Encrypt Authenticate Current allowed actions: Sign Certify Encrypt (S) Toggle the sign capability (E) Toggle the encrypt capability (A) Toggle the authenticate capability (Q) Finished Your selection? s Possible actions for a RSA key: Sign Certify Encrypt Authenticate Current allowed actions: Certify Encrypt (S) Toggle the sign capability (E) Toggle the encrypt capability (A) Toggle the authenticate capability (Q) Finished Your selection? e Possible actions for a RSA key: Sign Certify Encrypt Authenticate Current allowed actions: Certify (S) Toggle the sign capability (E) Toggle the encrypt capability (A) Toggle the authenticate capability (Q) Finished Your selection? q RSA keys may be between 1024 and 8192 bits long. What keysize do you want? (2048) 4096 Requested keysize is 4096 bits 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 Sat Mar 4 20:42:38 2017 PST Is this correct? (y/N) y GnuPG needs to construct a user ID to identify your key. Real name: john doe Email address: johndoe@localhost Comment: You selected this USER-ID: "john doe <johndoe@localhost>" Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o You need a Passphrase to protect your secret key. We need to generate a lot of random bytes. It is a good idea to perform some other action (type on the keyboard, move the mouse, utilize the disks) during the prime generation; this gives the random number generator a better chance to gain enough entropy. gpg: key 0x07D1E784D2ED9B73 marked as ultimately trusted public and secret key created and signed. gpg: checking the trustdb gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model gpg: depth: 0 valid: 4 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 4u gpg: next trustdb check due at 2017-03-04 pub 4096R/0x07D1E784D2ED9B73 2015-03-06 [expires: 2017-03-05] Key fingerprint = EE50 A92F 7A25 E286 5AF3 A8D4 07D1 E784 D2ED 9B73 uid [ultimate] john doe <johndoe@localhost>
Create a text backup of the master key:
$ gpg2 -a --export-secret-keys 07D1E784D2ED9B73 > masterkeys.txt
Generate the revocation certificate for the master key:
$ gpg2 --gen-revoke 07D1E784D2ED9B73 > 07D1E784D2ED9B73-revoke-cert.asc sec 4096R/0x07D1E784D2ED9B73 2015-03-06 john doe <johndoe@localhost> Create a revocation certificate for this key? (y/N) y Please select the reason for the revocation: 0 = No reason specified 1 = Key has been compromised 2 = Key is superseded 3 = Key is no longer used Q = Cancel (Probably you want to select 1 here) Your decision? 3 Enter an optional description; end it with an empty line: > Reason for revocation: Key is no longer used (No description given) Is this okay? (y/N) y You need a passphrase to unlock the secret key for user: "john doe <johndoe@localhost>" 4096-bit RSA key, ID 0x07D1E784D2ED9B73, created 2015-03-06 ASCII armored output forced. Revocation certificate created. Please move it to a medium which you can hide away; if Mallory gets access to this certificate he can use it to make your key unusable. It is smart to print this certificate and store it away, just in case your media become unreadable. But have some caution: The print system of your machine might store the data and make it available to others!
Generate the authentication subkey:
Subkey Size and YubiKey
The NEO limits subkey size to 2048 bits or less. Thus, we recommend that the size of your subkeys to be used with the NEO are 2048 bits.
$ gpg2 --expert --edit-key 07D1E784D2ED9B73 gpg (GnuPG/MacGPG2) 2.0.26; 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 4096R/0x07D1E784D2ED9B73 created: 2015-03-06 expires: 2017-03-05 usage: C trust: ultimate validity: ultimate [ultimate] (1). john doe <johndoe@localhost> gpg> addkey Key is protected. You need a passphrase to unlock the secret key for user: "john doe <johndoe@localhost>" 4096-bit RSA key, ID 0x07D1E784D2ED9B73, created 2015-03-06 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? 8 Possible actions for a RSA key: Sign Encrypt Authenticate Current allowed actions: Sign Encrypt (S) Toggle the sign capability (E) Toggle the encrypt capability (A) Toggle the authenticate capability (Q) Finished Your selection? s Possible actions for a RSA key: Sign Encrypt Authenticate Current allowed actions: Encrypt (S) Toggle the sign capability (E) Toggle the encrypt capability (A) Toggle the authenticate capability (Q) Finished Your selection? e Possible actions for a RSA key: Sign Encrypt Authenticate Current allowed actions: (S) Toggle the sign capability (E) Toggle the encrypt capability (A) Toggle the authenticate capability (Q) Finished Your selection? a Possible actions for a RSA key: Sign Encrypt Authenticate Current allowed actions: Authenticate (S) Toggle the sign capability (E) Toggle the encrypt capability (A) Toggle the authenticate capability (Q) Finished Your selection? q RSA keys may be between 1024 and 8192 bits long. What keysize do you want? (2048) 2048 Requested keysize is 2048 bits 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 Sat Mar 4 20:47:50 2017 PST Is this correct? (y/N) y Really create? (y/N) y We need to generate a lot of random bytes. It is a good idea to perform some other action (type on the keyboard, move the mouse, utilize the disks) during the prime generation; this gives the random number generator a better chance to gain enough entropy. pub 4096R/0x07D1E784D2ED9B73 created: 2015-03-06 expires: 2017-03-05 usage: C trust: ultimate validity: ultimate sub 2048R/0xAED9256FF8CEC558 created: 2015-03-06 expires: 2017-03-05 usage: A [ultimate] (1). john doe <johndoe@localhost> gpg> save
Export your secret subkeys for backup.
$ gpg2 -a --export-secret-subkeys 07D1E784D2ED9B73 > subkeys.txt
Configure YubiKey
Make sure your YubiKey is plugged in and check if
gpg2
can read it:$ gpg2 --card-status Application ID ...: XXXXXX Version ..........: 2.0 Manufacturer .....: Yubico Serial number ....: XXXXXX Name of cardholder: [not set] Language prefs ...: [not set] Sex ..............: unspecified URL of public key : [not set] Login data .......: [not set] Signature PIN ....: forced Key attributes ...: 2048R 2048R 2048R Max. PIN lengths .: 127 127 127 PIN retry counter : 3 3 3 Signature counter : 0 Signature key ....: [none] Encryption key....: [none] Authentication key: [none] General key info..: [none]
Change the PIN and Admin PIN from its defaults (123456 and 12345678, respectively):
Admin PIN Lockout
If you incorrectly enter your Admin PIN three (3) times, you will be locked out of your YubiKey and it will be useless.
You can completely reset your Yubikey if locked out with ResetApplet.
$ gpg2 --card-edit gpg/card> admin Admin commands are allowed gpg/card> passwd gpg: OpenPGP card no. XXXXXX detected 1 - change PIN 2 - unblock PIN 3 - change Admin PIN 4 - set the Reset Code Q - quit Your selection? 3 PIN changed. ... Your selection? 1 PIN changed. ... Your selection? q gpg/card> quit
Load PGP keys onto Yubikey
Move the authentication subkey to your YubiKey:
$ gpg2 --edit-key 07D1E784D2ED9B73 gpg (GnuPG/MacGPG2) 2.0.26; 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 4096R/0x07D1E784D2ED9B73 created: 2015-03-06 expires: 2017-03-05 usage: C trust: ultimate validity: ultimate sub 2048R/0xAED9256FF8CEC558 created: 2015-03-06 expires: 2017-03-05 usage: A [ultimate] (1). john doe <johndoe@localhost> gpg> toggle sec 4096R/0x07D1E784D2ED9B73 created: 2015-03-06 expires: 2017-03-05 ssb 2048R/0xAED9256FF8CEC558 created: 2015-03-06 expires: never (1) john doe <johndoe@localhost> gpg> key 1 sec 4096R/0x07D1E784D2ED9B73 created: 2015-03-06 expires: 2017-03-05 ssb* 2048R/0xAED9256FF8CEC558 created: 2015-03-06 expires: never (1) john doe <johndoe@localhost> gpg> keytocard Signature key ....: [none] Encryption key....: [none] Authentication key: [none] Please select where to store the key: (3) Authentication key Your selection? 3 You need a passphrase to unlock the secret key for user: "john doe <johndoe@localhost>" 2048-bit RSA key, ID 0xAED9256FF8CEC558, created 2015-03-06 sec 4096R/0x07D1E784D2ED9B73 created: 2015-03-06 expires: 2017-03-05 ssb* 2048R/0xAED9256FF8CEC558 created: 2015-03-06 expires: never card-no: 0006 03366476 (1) john doe <johndoe@localhost> gpg> save
Verify that the key is on the card:
$ gpg2 --card-status Application ID ...: XXXXXX Version ..........: 2.0 Manufacturer .....: Yubico Serial number ....: XXXXXX Name of cardholder: [not set] Language prefs ...: [not set] Sex ..............: unspecified URL of public key : [not set] Login data .......: [not set] Signature PIN ....: forced Key attributes ...: 2048R 2048R 2048R Max. PIN lengths .: 127 127 127 PIN retry counter : 3 3 3 Signature counter : 0 Signature key ....: [none] Encryption key....: [none] Authentication key: 4FB8 96F3 32D7 BE1E C178 0CC5 AED9 256F F8CE C558 created ....: 2015-03-06 04:49:15 General key info..: pub 2048R/0xAED9256FF8CEC558 2015-03-06 john doe <johndoe@localhost> sec 4096R/0x07D1E784D2ED9B73 created: 2015-03-06 expires: 2017-03-05 ssb> 2048R/0xAED9256FF8CEC558 created: 2015-03-06 expires: 2017-03-05 card-no: XXXX XXXXXX
Once we move the master key from the local machine and onto offline storage,
the master key will appear as sec#
in the output of gpg2 --card-status
;
the #
means that the corresponding private key is not present.
We are now ready to use our YubiKey for SSH authentication.
Configure gpg-agent
and add your SSH keys
gpg-agent
needs to be configured for SSH support.
gpg-agent
will take over the functionality of ssh-agent
.
On OS X, gpg-agent will be launched automatically at startup if you installed GPG Suite.
TODO
Guidance for GNOME Keyring (Seahorse), or other Linux utilities.
Modify
~/.gnupg/gpg-agent.conf
:# if on Mac OS X and GPG Suite is installed # otherwise, look for `pinentry' on your system pinentry-program /usr/local/MacGPG2/libexec/pinentry-mac.app/Contents/MacOS/pinentry-mac # enables SSH support (ssh-agent) enable-ssh-support # writes environment information to ~/.gpg-agent-info write-env-file use-standard-socket # default cache timeout of 600 seconds default-cache-ttl 600 max-cache-ttl 7200
Append to your
~/.bashrc
(or your favorite shell config):if [ -f "${HOME}/.gpg-agent-info" ]; then . "${HOME}/.gpg-agent-info" export GPG_AGENT_INFO export SSH_AUTH_SOCK fi export GPG_TTY=$(tty)
If you’ve created your GPG keys on a separate machine (e.g., A) you’ll need to make sure that the machine you’ll be using the Yubikey on (e.g., B) has a copy of the generated public key.
One way to do this is to upload your public key to a keyserver. Another way is to export the key as an ASCII file and import it manually.
On machine A:
$ gpg2 --armor --export AED9256FF8CEC558 > AED9256FF8CEC558.asc
Then on machine B:
$ gpg2 --import < AED9256FF8CEC558.asc
Convert your authentication public subkey to an SSH key (specify the authentication subkey ID):
$ gpgkey2ssh AED9256FF8CEC558 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... COMMENT
Copy the output into
~/.ssh/authorized_keys
on the remote machine (e.g.,example.com
).You can also check if your YubiKey is working with
ssh-add -L
.$ ssh-add -L ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... cardno:XXXXXXXXXXXX
Test the SSH connection
We can now test an SSH connection to the remote machine. We will be asked for the PIN to unlock the key; if successful, we will be able to SSH successfully.
Use the
-v
verbose flag withssh
to see which key is being used:$ ssh -v example.com OpenSSH_6.7p1, OpenSSL 1.0.2 22 Jan 2015 debug1: Connecting to example.com [127.0.0.1] port 22. debug1: Connection established. debug1: Enabling compatibility mode for protocol 2.0 debug1: Local version string SSH-2.0-OpenSSH_6.7 debug1: Remote protocol version 2.0, remote software version OpenSSH_6.4 debug1: match: OpenSSH_6.4 pat OpenSSH* compat 0x04000000 debug1: SSH2_MSG_KEXINIT sent debug1: SSH2_MSG_KEXINIT received debug1: kex: server->client aes128-ctr umac-64-etm@openssh.com none debug1: kex: client->server aes128-ctr umac-64-etm@openssh.com none debug1: sending SSH2_MSG_KEX_ECDH_INIT debug1: expecting SSH2_MSG_KEX_ECDH_REPLY debug1: Server host key: RSA xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx debug1: Host 'example.com' is known and matches the RSA host key. debug1: SSH2_MSG_NEWKEYS sent debug1: expecting SSH2_MSG_NEWKEYS debug1: SSH2_MSG_NEWKEYS received debug1: Roaming not allowed by server debug1: SSH2_MSG_SERVICE_REQUEST sent debug1: SSH2_MSG_SERVICE_ACCEPT received debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic debug1: Next authentication method: publickey # this is where we see our YubiKey is being used debug1: Offering RSA public key: cardno:XXXXXXXXXXXX debug1: Server accepts key: pkalg ssh-rsa blen 279 debug1: Authentication succeeded (publickey). Authenticated to localhost ([127.0.0.1]:22). debug1: channel 0: new [client-session] debug1: Requesting no-more-sessions@openssh.com debug1: Entering interactive session. [user@localhost]~$
Troubleshooting
Some tips and solutions for when things don’t work out flawlessly.
Mac OS X: Could not open a connection…
When running ssh-add -L
(in Step 5.4), you might get an error:
Could not open a connection to your authentication agent.
This is likely because ssh-agent
is also running at the same time, and
the environment variables have been properly set (i.e., ssh
will look
at env variables SSH_AGENT_PID
and SSH_AGENT_SOCK
).
After killing ssh-agent
, run gpg-agent
:
eval gpg-agent --daemon --enable-ssh-support
The right environment variables will be set for that particular shell, and you should be able to successfully SSH into your server.
Once this is working, we need to make sure gpg-agent
runs at startup
by creating a new .plist
and placing it in the LaunchAgent directory.
This guide covers creating the .plist
.
2015-09-29 Thanks to Tyler B. for the feedback and troubleshooting.
Other Things
Miscellaneous things that have been or need to be figured out.
gpg2 and ~/.gnupg
gpg2
does some different things to ~/.gnupg
compared to gpg
.
In particular, what is stored in ~/.gnupg/private-keys-v1.d
?
Does it store the cached passphrases?
gpg-agent cache clearing
How do I manually clear the gpg-agent cache?
The easiest way is to probably use gpg-connect-agent reloadagent /bye
.
Even if you remove the YubiKey (the secret key supposedly never leaves it), the password is still cached (I think). One would want to destroy the cache when the screensaver starts, for example.
2016-03-15 Updated with relevant info.
gpg-agent forwarding
It’d be great if we could forward gpg-agent to remote machines. This would let you decrypt email on a remote machine, for example. Apparently it’s possible to do this with OpenSSH 6.7.
2016-03-15 We can do this now. See remote gpg-agent via ssh forwarding.
signed SSH keys
To prevent arbitrary keys being added to your authorized_keys
, it’s
possible to have your SSH Certificate Authority (CA) sign your keys and
only accept signed SSH keys for authentication.
We don’t cover the signing process here, but since your exported SSH public key (generated from your GPG auth subkey) is an OpenSSH-compatible public key, there should be no issues here. Simply send your new SSH public key to be signed by the SSH CA.
Remember that it isn’t possible to take an existing SSH keypair
(generated via ssh-keygen
) and import the private key to your NEO.
This might be an issue if you already have an existing signed SSH
keypair that you would like to reuse.
The NEO only supports loading GPG keys into the dongle and what makes all of this work is that your GPG authentication subkey can be exported as an SSH public key.
2015-06-29 Thanks to Eric E. for the question.