Ubuntu

Yubikey SSH authentication

The YubiKey is a hardware authentication device manufactured by Yubico that supports one-time passwords, public-key encryption and authentication, and the Universal 2nd Factor (U2F) and FIDO2 protocols developed by the FIDO Alliance.

It allows users to securely log into their accounts by emitting one-time passwords or using a FIDO-based public/private key pair generated by the device. YubiKey also allows for storing static passwords for use at sites that do not support one-time passwords.

Facebook uses YubiKey for employee credentials, and Google supports it for both employees and users. Some password managers support YubiKey. Yubico also manufactures the Security Key, a device similar to the YubiKey, but focused on public-key authentication.

Today I will show you how to prepare the Yubikey key to connect to the server.

Step 1: Install and configure software

  1. GNU/Linux: install gnupg, gpg-agent, and YubiKey NEO Manager.
  2. 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

Step 2: Generate PGP Keys

Generate a new master key:

gpg --expert --full-generate-key
gpg (GnuPG) 2.2.12; Copyright (C) 2018 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.

gpg: keybox '/home/vasilij/.gnupg/pubring.kbx' created
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)
   (9) ECC and ECC
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (13) Existing key
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 4096 bits long.
What keysize do you want? (3072) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
        = key expires in n days
      w = key expires in n weeks
      m = key expires in n months
      y = key expires in n years
Key is valid for? (0) 2y
Key expires at 2021 m. birželio 01 d. 10:06:08 EEST
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
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: /home/vasilij/.gnupg/trustdb.gpg: trustdb created
gpg: key 04D23D2F098D895F marked as ultimately trusted
gpg: directory '/home/vasilij/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/home/vasilij/.gnupg/openpgp-revocs.d/7848D7F318181F4AA62CD70104D23D2F098D895F.rev'
public and secret key created and signed.

pub   rsa4096 2019-06-02 [C] [expires: 2021-06-01]
      7848D7F318181F4AA62CD70104D23D2F098D895F
uid                      John Doe <johndoe@localhost>

Create a text backup of the master key:

gpg -a --export-secret-keys 04D23D2F098D895F > masterkeys.txt

Generate the revocation certificate for the master key:

gpg --gen-revoke 04D23D2F098D895F > 04D23D2F098D895F-revoke-cert.asc

sec  rsa4096/04D23D2F098D895F 2019-06-02 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
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:

gpg --expert --edit-key 04D23D2F098D895F
gpg (GnuPG) 2.2.12; Copyright (C) 2018 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.

gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2021-06-01
sec  rsa4096/04D23D2F098D895F
     created: 2019-06-02  expires: 2021-06-01  usage: C   
     trust: ultimate      validity: ultimate
[ultimate] (1). John Doe <johndoe@localhost>

gpg> addkey
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)
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (12) ECC (encrypt only)
  (13) Existing key
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 4096 bits long.
What keysize do you want? (3072) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
        = key expires in n days
      w = key expires in n weeks
      m = key expires in n months
      y = key expires in n years
Key is valid for? (0) 2y
Key expires at 2021 m. birželio 01 d. 10:17:02 EEST
Is this correct? (y/N) 
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.

sec  rsa4096/04D23D2F098D895F
     created: 2019-06-02  expires: 2021-06-01  usage: C   
     trust: ultimate      validity: ultimate
ssb  rsa4096/8FB8B60D9095F628
     created: 2019-06-02  expires: 2021-06-01  usage: A   
[ultimate] (1). John Doe <johndoe@localhost>
gpg> save

Export your secret subkeys for backup.

gpg -a --export-secret-subkeys 04D23D2F098D895F > subkeys.txt

Step 3: Configure YubiKey

1. Make sure your YubiKey is plugged in and check if gpg can read it:

gpg --card-status
Reader ...........: 1050:0407:X:0
Application ID ...: XXXXXXXX
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: XXXXXXXX
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 1 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):

gpg --card-edit

gpg/card> admin
Admin commands are allowed

gpg/card> passwd
gpg: OpenPGP card no. XXXXXXXX detected

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 3
PIN changed.

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 1
Error changing the PIN: Bad PIN

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? q

gpg/card> quit

Step 4: Load PGP keys onto Yubikey

Move the authentication subkey to your YubiKey:

gpg --edit-key 04D23D2F098D895F
gpg (GnuPG) 2.2.12; Copyright (C) 2018 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  rsa4096/04D23D2F098D895F
     created: 2019-06-02  expires: 2021-06-01  usage: C   
     trust: ultimate      validity: ultimate
ssb  rsa4096/8FB8B60D9095F628
     created: 2019-06-02  expires: 2021-06-01  usage: A   
[ultimate] (1). John Doe <johndoe@localhost>

gpg> toggle

sec  rsa4096/04D23D2F098D895F
     created: 2019-06-02  expires: 2021-06-01  usage: C   
     trust: ultimate      validity: ultimate
ssb  rsa4096/8FB8B60D9095F628
     created: 2019-06-02  expires: 2021-06-01  usage: A   
[ultimate] (1). John Doe <johndoe@localhost>

gpg> key 1

sec  rsa4096/04D23D2F098D895F
     created: 2019-06-02  expires: 2021-06-01  usage: C   
     trust: ultimate      validity: ultimate
ssb* rsa4096/8FB8B60D9095F628
     created: 2019-06-02  expires: 2021-06-01  usage: A   
[ultimate] (1). John Doe <johndoe@localhost>

gpg> keytocard
Please select where to store the key:
   (3) Authentication key
Your selection? 3

sec  rsa4096/04D23D2F098D895F
     created: 2019-06-02  expires: 2021-06-01  usage: C   
     trust: ultimate      validity: ultimate
ssb* rsa4096/8FB8B60D9095F628
     created: 2019-06-02  expires: 2021-06-01  usage: A   
[ultimate] (1). John Doe <johndoe@localhost>

gpg> save

Verify that the key is on the card:

gpg --card-status
Reader ...........: 1050:0407:X:0
Application ID ...: XXXXXXXX
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: XXXXXXXX
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: rsa4096 rsa2048 rsa4096
Max. PIN lengths .: 127 127 127
PIN retry counter : 0 3 3
Signature counter : 0
Signature key ....: 7848 D7F3 1818 1F4A A62C  D701 04D2 3D2F 098D 895F
      created ....: 2019-06-02 07:06:18
Encryption key....: [none]
Authentication key: D629 1EF3 DDEA 0BD8 C0E8  A911 8FB8 B60D 9095 F628
      created ....: 2019-06-02 07:16:31
General key info..: pub  rsa4096/04D23D2F098D895F 2019-06-02 John Doe <johndoe@localhost>
sec>  rsa4096/04D23D2F098D895F  created: 2019-06-02  expires: 2021-06-01
                                card-no: 0006 06955733
ssb>  rsa4096/8FB8B60D9095F628  created: 2019-06-02  expires: 2021-06-01
                                card-no: 0006 06955733

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

gpg --card-status

The # means that the corresponding private key is not present.

We are now ready to use our YubiKey for SSH authentication.

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

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:

gpg --armor --export 04D23D2F098D895F > 04D23D2F098D895F.asc

Then on machine B:

gpg --import < 04D23D2F098D895F.asc

You can also check if your YubiKey is working with ssh-add -L

ssh-rsa AAAAB3NzaC1yc2EAAAA... cardno:XXXXXXXX

Step 6: 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 with ssh to see which key is being used:

ssh -v [email protected]
OpenSSH_7.9p1 Ubuntu-10, OpenSSL 1.1.1b  26 Feb 2019
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 19: Applying options for *
debug1: Connecting to XXX.XXX.XXX.XXX [XXX.XXX.XXX.XXX] port 22.
debug1: Connection established.
debug1: Local version string SSH-2.0-OpenSSH_7.9p1 Ubuntu-10
debug1: Remote protocol version 2.0, remote software version OpenSSH_7.4
debug1: match: OpenSSH_7.4 pat OpenSSH_7.0*,OpenSSH_7.1*,OpenSSH_7.2*,OpenSSH_7.3*,OpenSSH_7.4*,OpenSSH_7.5*,OpenSSH_7.6*,OpenSSH_7.7* compat 0x04000002
debug1: Authenticating to XXX.XXX.XXX.XXX:22 as 'root'
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: curve25519-sha256
debug1: kex: host key algorithm: ecdsa-sha2-nistp256
debug1: kex: server->client cipher: [email protected] MAC:  compression: none
debug1: kex: client->server cipher: [email protected] MAC:  compression: none
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug1: Server host key: ecdsa-sha2-nistp256 SHA256:0YB3eFixKKjO+0ybqG/JwpHUHYPGyDN1hIUu0SfqH6I
debug1: Host 'XXX.XXX.XXX.XXX' is known and matches the ECDSA host key.
debug1: rekey after 134217728 blocks
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug1: SSH2_MSG_NEWKEYS received
debug1: rekey after 134217728 blocks
debug1: Will attempt key: cardno:XXXXXXXXXXXX RSA SHA256:7M02qveHErgtrX4iwPJnaa0NyJrTmtByv1oTGZd7enU agent
debug1: SSH2_MSG_EXT_INFO received
debug1: kex_input_ext_info: server-sig-algs=
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password
debug1: Next authentication method: gssapi-keyex
debug1: No valid Key exchange context
debug1: Next authentication method: gssapi-with-mic
debug1: Unspecified GSS failure.  Minor code may provide more information
No Kerberos credentials available (default cache: FILE:/tmp/krb5cc_1000)

debug1: Unspecified GSS failure.  Minor code may provide more information
No Kerberos credentials available (default cache: FILE:/tmp/krb5cc_1000)
# this is where we see our YubiKey is being used
debug1: Next authentication method: publickey
debug1: Offering public key: cardno:XXXXXXXXXXXX RSA SHA256:7M02qveHErgtrX4iwPJnaa0NyJrTmtByv1oTGZd7enU agent
debug1: Server accepts key: cardno:XXXXXXXXXXXX RSA SHA256:7M02qveHErgtrX4iwPJnaa0NyJrTmtByv1oTGZd7enU agent
debug1: Authentication succeeded (publickey).
Authenticated to XXX.XXX.XXX.XXX ([XXX.XXX.XXX.XXX]:22).
debug1: channel 0: new [client-session]
debug1: Requesting [email protected]
debug1: Entering interactive session.
debug1: pledge: network
debug1: client_input_global_request: rtype [email protected] want_reply 0
debug1: Sending environment.
debug1: Sending env LC_ADDRESS = lt_LT.UTF-8
debug1: Sending env LC_NAME = lt_LT.UTF-8
debug1: Sending env LC_MONETARY = lt_LT.UTF-8
debug1: Sending env LC_PAPER = lt_LT.UTF-8
debug1: Sending env LANG = en_US.UTF-8
debug1: Sending env LC_IDENTIFICATION = lt_LT.UTF-8
debug1: Sending env LC_TELEPHONE = lt_LT.UTF-8
debug1: Sending env LC_MEASUREMENT = lt_LT.UTF-8
debug1: Sending env LC_TIME = lt_LT.UTF-8
debug1: Sending env LC_NUMERIC = lt_LT.UTF-8