yubikey and ssh authentication

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

  1. 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 uses scdaemon when interacting with a smart card.

  2. 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)

  3. 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!

  1. 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>
    
  2. Create a text backup of the master key:

    $ gpg2 -a --export-secret-keys 07D1E784D2ED9B73 > masterkeys.txt
    
  3. 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!
    
  4. 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
    
  5. Export your secret subkeys for backup.

    $ gpg2 -a --export-secret-subkeys 07D1E784D2ED9B73 > subkeys.txt
    

Configure YubiKey

  1. 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]
    
  2. 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

  1. 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
    
  2. 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.

  1. 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
    
  2. 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)
    
  3. 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
    
  4. 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).

  5. 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.

  1. Use the -v verbose flag with ssh 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.