Security Adventures 1

How to get yubikey+gpg+ssh+gitbhub working on MacOS

November 4, 2020 - 25 minute read -
Yubikey Security GitHub

I’ve spent the day trying to get this setup working with GitHub and given the number of gotcha’s I encountered, it seemed like a good idea to document how I finally got this working with as few hacks as possible. There’s a lot of documentation out there (some of it old and misleading) and committing here for posterity will help me remember this when I inevitably need to do this again.

Rationale

Passwords are simply not enough these days. Regardless of the company, breaches (and the associated Personally Identifiable Information harvested) are a matter of not if, but when. There are a number of things you can do to protect yourself, but being on the tin-foil-hat side of paranoia, means there are a few Commandents that I adhere to (and recommend for other folks)[Insert link to Fight Club Rules for the Secure Internet].

That being said, if you use 2-factor authentication and have committed to using a hardware token such as the Yubikey, then you’re already ahead of the curve. The problem is that while this has been broadly adopted in enterprise, it still feels like the bleeding edge. It doesn’t help that the Yubikey documentation and tools are out-of-date and possibly not fully supported on MacOS. This doc is to address this gap and bring awareness to the somewhat-mainstream.

The Yubikey 5

The Yubikey 5

The instructions below are for the Yubikey 5 (which is what I have). If you have an older version of the key, the instructions below may not work for you and you may want to consider upgrading to a newer version.

Relevant Features

  • There are USB-C and USB-A versions of this key which make it compatible with most devices. CAVEAT: I don’t have an NFC version, so have not tested to see if these instructions work on these keys as well. If you have one and try these instructions (with or without success), post your experiences below in the comments.
  • Supports FIDO / U2F / PIV-compatible Smart Card / OpenPGP. It’s these last two that we’ll focus on here as these are relevant to getting the key to provide ssh authentication credentials to a service like GitHub.
  • Support for the following type of cryptographic keys - RSA 2048, RSA 4096 (PGP), ECC p256, ECC p384.
  • Out-of-the box support for a lot of services which can use one-time-passwords (OTP) generated by the key.

OpenSSH Keys vs. OpenPGP Keys

Given that both systems use keys, why can’t you use one with the other?

TL;DR - OpenSSH was designed to secure a stream of network communication vs. OpenPGP which was designed to encrypt the contents of a message.

Usually, when you first configure OpenSSH, you generate a keypair (a private key + a public key). This keypair is then used to access remote systems by transferring the public key to the remote machine and disabling PasswordAuthentication in the /etc/ssh/sshd_config file. Keys are usually stored in the local or remote machine’s ~/.ssh directory. The ~/.ssh/authorized_keys file on the remote machine contains the public key of the generated key pair which is used for authentication purposes.

OpenPGP on the other hand is designed as a general purpose decentralized public key infrastructure (PKI) for communicating encrypted messages which may be sent over a different network protocol (such as SMTP for mail, FTP, or even SSH). Instead of a single keypair, OpenPGP also allows for the generation of subkeys which can have specific capabilities enabled/disabled. This allows for the generation of purpose specific keys which are associated with a master key. We’ll use this technique to generate subkey pairs for authentication, encryption, and signing.

The trick for bridging this gap is to allow OpenPGP to act as an authentication secrets provider for OpenSSH.

OK. So where do I start?

Protonmail

We begin by creating an OpenPGP keypair. On of the key features of this keypair is the associated email address. In other words, every key MUST be associated with a valid email address*. I use Protonmail for this purpose as it provides encryption at for all of my email messages and it supports generating OpenPGP keys of various strengths (RSA2048, RSA4096, EdDSA256). Previously, Protonmail only supported RSA2048 keys, but now includes newer/stronger ciphers which have various security/performance trade-offs.

I began generating an RSA4096 key using these instructions - Key management.

If you have a different email address you would like to create a keypair for, you can also generate a key using the gpg command line (which you can install with Homebrew or alternatively, install GPGSuite).

NOTE: The generated key must be protected by a password. You’ll need this later for when you import the key into your OpenPGP keychain.

Why choose RSA4096 for the OpenPGP key cipher?

It seems that there are flaws within elliptic curve algorithms that suggest it’s better to go with the slower, but proven RSA cipher. For more background see - ECDSA: Handle with Care

Also, RSA is well supported by the Yubikey 5 as well as Github ;-)

Why don’t we generate an OpenPGP key on the Yubikey instead of creating one externally?

You can interact directly with the OpenPGP application on the Yubikey 5 to generate an OpenPGP keypair, but there are a number of implications when doing so:

  1. The private key remains solely on the device which means not being able to back up the private key (as only a stub is exported).
  2. In the event you lose of your physical key, you’re pretty much shit out of luck :( . If you’ve seen the size of the Yubikey 5c, you’ll know how real of a possibility it actually is…

Since I’m re-purposing my Protonmail OpenPGP key, I’ll be showing how to import this into the Yubikey.

The Yubikey

There are a number of articles in the dev/support sections of the Yubico site which would lead you to suspect that the tools they provide support configuration of OpenPGP on the Yubikey 5, but having put them to the test, I would say that the docs are woefully inadequate. To save you time (and future me from wasting more of it), I’ve documented the limitations here.

Yubikey’s default state of insecurity

Strangely, when I started down this rabbit hole, I expected that a company focused on security solutions would focus their user experience around, you know - “security”. Furthermore, the defaults for configuring the PIV functionality provide defaults which are easily found on the interwebs :(

For the record, the default PINs associated with the PIV functionality are:

  • default PIN: 123456
  • admin PIN: 12345678 (this should really be referred to as the Pin Unlock Key)
  • management key:010203040506070801020304050607080102030405060708

What’s missing is a walk-through which should do the following:

  1. Provide installation instructions to download the latest version of the Yubikey Manager. Bonus points if previous versions are aware they are out-of-date and provide rolling updates.
  2. Provide a warning when you first insert your Yubikey and the Manager application recognizes it to provide new/secure PINs to override these known defaults.
  3. Provide a means to configuring the retries counts for the various PINs (preferrably without wiping any already configured PINs)
  4. Provide a means for verifying that the PIN changes have been persisted to the device.

WARNING* By default, the number of retries for PIN entry is 3. If you fail to provide the correct PIN, then that type of PIN is locked. You will need to use the admin PIN/PUK to unlock the device. In the PUK is entered incorrectly more than the number of configured retries, the device is locked and must be reset back to the factory defaults (resulting in the loss of all data/keys on the device)..

Here are some background resources provided by Yubico regarding these PINs/keys:

NOTE: As we are not generating the OpenPGP keys on the device, but importing our external keys generated previously, we will NOT be using the PIV Attestation feature.

Yubikey Manager

I started out with an older version of this tool which did not have support for the Personal Identity Verification (PIV) functionality required to enable certain Smart Card functionality to import the various OpenPGP keys we need for our use case. Unfortunately, documentation using this tools is sparse and the terminology used for the various certificate management stores differs from how gpg refers to it. YMMV.

NOTE: I was able to configure full functionality without using this application, but I noticed that although my key is configured with the certificates, this tool does NOT recognize it as such.

WARNING: When configuring another Yubikey, I thought to give this application a spin. I set the PINs and generated a new Management Key, but when attempting to use these settings with ykman the PINs were unrecognized. YMMV, but I would recommend setting them both ways to be sure. Also see the warning regarding Management Key generation below. Bad Yubico.

ykman

Searching the interwebs for how to enable certificates on the Yubikey brings up a lot of documents which refer to this CLI utility. As of writing, this tool was unstable, resulting in subsequent commands failing with the following error:

Usage: ykman [OPTIONS] COMMAND [ARGS]...
Try "ykman -h" for help.
Error: Failed connecting to YubiKey 5 [OTP+FIDO+CCID]. Make sure the application have the required permissions.

Not exactly informative… For example, attempting to use the openpgp functionality would repeatedly result in the above message, despite various info commands returing successfully :(

NOTE: This failure may be due to locking of the scdaemon resposible for communicating with the Yubikey’s Smart Card functionality, but I didn’t try to get to the bottom of it either…

UPDATE 18.10.2020: The response from Yubico Support - The overall issue I believe you are running into is one we've seen on macOS specifically with YubiKey functions that use its CCID (OpenPGP, PIV, and OATH). Essentially, intermittently on macOS, when you attempt to access these functions, they will not respond, and the YubiKey must be reinserted (sometimes multiple times) until things start working again. The intermittent nature has made it difficult for us to resolve, but from what we have uncovered, it doesn't seem to be an issue with our products. Not sure I agree with the last statement though.

Here’s the version info reported for the tool (which was installed with Homebrew):

% ykman --version
YubiKey Manager (ykman) version: 3.1.1
Libraries:
    libykpers 1.20.0
    libusb 1.0.23

Resetting the PINs using ykman piv

Here’s the man page for this sub-command:

% ykman piv 
Usage: ykman piv [OPTIONS] COMMAND [ARGS]...

  Manage PIV Application.

  Examples:

    Generate an ECC P-256 private key and a self-signed certificate in
    slot 9a:
    $ ykman piv generate-key --algorithm ECCP256 9a pubkey.pem
    $ ykman piv generate-certificate --subject "yubico" 9a pubkey.pem

    Change the PIN from 123456 to 654321:
    $ ykman piv change-pin --pin 123456 --new-pin 654321

    Reset all PIV data and restore default settings:
    $ ykman piv reset

Options:
  -h, --help  Show this message and exit.

Commands:
  attest                 Generate a attestation certificate for a key.
  change-management-key  Change the management key.
  change-pin             Change the PIN code.
  change-puk             Change the PUK code.
  delete-certificate     Delete a certificate.
  export-certificate     Export a X.509 certificate.
  generate-certificate   Generate a self-signed X.509 certificate.
  generate-csr           Generate a Certificate Signing Request (CSR).
  generate-key           Generate an asymmetric key pair.
  import-certificate     Import a X.509 certificate.
  import-key             Import a private key.
  info                   Display status of PIV application.
  read-object            Read arbitrary PIV object.
  reset                  Reset all PIV data.
  set-ccc                Generate and set a CCC on the YubiKey.
  set-chuid              Generate and set a CHUID on the YubiKey.
  set-pin-retries        Set the number of PIN and PUK retries.
  unblock-pin            Unblock the PIN.
  write-object           Write an arbitrary PIV object.

NOTE: Notice that there are only two change commands - one for the PUK/admin PIN, and a “PIN”. This man page is unhelpful with regards to specifying which of the PINs can be changed. For the record, change-pin command changes the Default PIN. See below.

Order is important. Set the number of PIN retries first!

For some reason, if you reset the number of retries, you will inadvertently reset the Default PIN and the PUK PIN (probably due to how these are stored on the device). You should do this first in order to prevent you from locking yourself out/disabling the device. By default you are allow 3 retries for either PIN (which doesn’t provide much of a margin of error). I set mine to 5 (NOTE: I use the Default PIN / Management Key from above):

% ykman piv set-pin-retries --help
Usage: ykman piv set-pin-retries [OPTIONS] PIN-RETRIES PUK-RETRIES

  Set the number of PIN and PUK retries. NOTE: This will reset the PIN and PUK to their factory defaults.

Options:
  -m, --management-key TEXT  The management key.
  -P, --pin TEXT             PIN code.
  -f, --force                Confirm the action without prompting.
  -h, --help                 Show this message and exit.
mel@zed protonmail % ykman piv set-pin-retries 5 5
Enter PIN: 
Enter a management key [blank to use default key]: 
WARNING: This will reset the PIN and PUK to the factory defaults!
Set PIN and PUK retry counters to: 5 5? [y/N]: y
Default PINs are set.
PIN:    123456
PUK:    12345678

Setting the Admin/PUK PIN

First order of business is to set a PUK so that you can unlock the device should should accidentally exceed the PIN retries. It’s unhelpful that the Yubico documentation refers to this PIN using two different terms (PUK/admin). The are one and the same :( . Also note the inconsistent use of the -p/-P option for specifying the PIN argument for the commands. (We use the default admin PIN here (12345678) for the device and NNNNNNNN as the new PUK PIN):

% ykman piv change-puk -p 12345678 -n NNNNNNNN
New PUK set.

Unfortunately, the tool provides no mechanism for verifying that the change has been applied :(

Setting the Default PIN

You can also change the Default PIN using ykman. (replace NNNNNNNN a the new Default PIN):

% ykman piv change-pin -P 123456 -n NNNNNNNN
New PIN set.

PIN Constraints

It may not be clear that PINs can be comprised of the following characteristics:

  • shoud not contain all or part of the user’s account name.
  • can contain characters from three of the following four categories:
    • English uppercase characters (A through Z)
    • English lowercase characters (a through z)
    • Base 10 digits (0 through 9)
    • Nonalphanumeric characters (e.g., !, $, #, %)

There are additional Considerations when using a PIN for PIV management, most notably:

There are certain security and usability considerations which should be taken into account when using the PIN for PIV management, instead of a Management Key. The way this feature works, is that a Management Key is still used, but it is cryptographically derived from your PIN by the YubiKey PIV Manager, behind the scenes. One implication of this is that the Management Key changes whenever you change your PIN, and it is therefore crucial that you ONLY change your PIN using the YubiKey PIN Manager. Changing it using an external tool will render the YubiKey PIV Manager unable to derive the Management Key.

Setting the Management Key

You can change the Management Key using the following commands (where a new key is automatically generated and protected with the PIN. (replace XXXXXXXX with the Default PIN).

WARNING: As per the previous section, DO NOT attempt to specify your own Management Key as it is cryptographically derived from your PIN).

% ykman piv change-management-key -P XXXXXXXX -g -p 

WARNING: This command for some reason does not entirely work and WILL NOT DISPLAY THE NEWLY GENERATED MANAGEMENT KEY. You may need to use the Yubikey Manager application to set this correctly.

Enabling touch for OpenPGP on the Yubikey

After fiddling with the ykman tool, I was able to enable specific openpgp functionality by using the following commands. A few things to note:

  • Interactive mode blocks on entry of the carriage return (it won’t accept it for some strange reason)
  • The sub-command you need is set-touch NOT touch as some older documentation refers to.
% ykman openpgp set-touch --help
Usage: ykman openpgp set-touch [OPTIONS] KEY POLICY

  Set touch policy for OpenPGP keys.

  KEY     Key slot to set (sig, enc, aut or att).
  POLICY  Touch policy to set (on, off, fixed, cached or cached-fixed).

  The touch policy is used to require user interaction for all operations using the private key on the YubiKey. The touch policy is set indivdually for each key slot. To see the current touch policy, run

      $ ykman openpgp info

  Touch policies:

  Off (default)   No touch required
  On              Touch required
  Fixed           Touch required, can't be disabled without a full reset
  Cached          Touch required, cached for 15s after use
  Cached-Fixed    Touch required, cached for 15s after use, can't be disabled
                  without a full reset

Options:
  -a, --admin-pin TEXT  Admin PIN for OpenPGP.
  -f, --force           Confirm the action without prompting.
  -h, --help            Show this message and exit.  

By default, the OpenPGP touch policies are disabled. You can see that by running the following command:

% ykman openpgp info            
OpenPGP version: 2.1
Application version: 5.1.0

PIN tries remaining: 3
Reset code tries remaining: 3
Admin PIN tries remaining: 3

Touch policies
Signature key           Off
Encryption key          Off
Authentication key      Off

WARNING: Be aware of the number of retries which can potentially lock the device if you fail to provide the correct PIN.

You can enable each of these functions non-interactively by using the following commands (replace XXXXXXXX with your admin/PUK PIN for the device):

% ykman openpgp set-touch enc on -a XXXXXXXX -f
% ykman openpgp set-touch aut on -a XXXXXXXX -f
% ykman openpgp set-touch sig on -a XXXXXXXX -f

You can verify that these touch settings have been enabled by running the ykman openpgp info again:

% ykman openpgp info                           
OpenPGP version: 2.1
Application version: 5.1.0

PIN tries remaining: 3
Reset code tries remaining: 3
Admin PIN tries remaining: 3

Touch policies
Signature key           On
Encryption key          On
Authentication key      On

yubico-priv-tool

I found additional references to this utility which looked like it would solve my initial configuration issues. Like ykman above, permission errors are reported when attempting to use this tool.

Here’s the version information from the version of this tool I attempted to use:

% yubico-piv-tool --version
yubico-piv-tool 2.1.1

Verifying PIN retry changes

One useful application of yubico-priv-tool was to verify the change in PIN retries.

% yubico-piv-tool -astatus                                                    
Version:	5.1.0
Serial Number:	086XXXXX
CHUID:	XXXX
CCC:	XXXX
PIN tries left:	5

The Secret Sauce - gpg

There are a number of articles which I used as a guide for determining what actually works, here are a few of them:

The rest of this document goes into the OpenPGP configuration and how to configure and load the keys onto the Yubikey.

Generating the individual subkeys

Recognizing the Yubikey as a Smart Card

We begin by looking at whether or not gpg can see your Yubikey as a Smart Card:

% % gpg-connect-agent --hex "scd apdu 00 f1 00 00" /bye
D[0000]  05 01 00 90 00                                     .....           
OK

The 05 01 00 in the response tells us that this Yubikey is using version 5.1.0.

In addition, you can check the Yubikey’s Smart Card status using the following command. This is what mine looked like prior to configuration:

%  gpg --card-status
Reader ...........: Yubico YubiKey OTP FIDO CCID
Application ID ...: D276XXXXXXXXXXXXXXXXXXXXXXXXXXXX
Application type .: OpenPGP
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: 086XXXXX
Name of cardholder: [not set]
Language prefs ...: [not set]
Salutation .......: 
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: rsa4096 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 0
Signature counter : 0
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

Generating an OpenPGP key (if you already don’t have one)

I’ll be using my Protonmail key for the purposes of this example, but for those not using Protonmail or who want to start with a fresh keypair, you can generate a key using the instructions here - Generate a Key

As mentioned previously, I use RSA4096 as my cipher for various reasons, but feel free to modify this based on your own use case.

Assumptions

The following instructions assume that you’ve generated your OpenPGP keypair and that it has been imported into your keychain and granted Ultimate Ownertrust.

Finding your OpenPGP Key ID

For example, in the following output:

 % gpg --fingerprint
/Users/mel/.gnupg/pubring.kbx
-----------------------------
pub   dsa2048 2010-08-19 [SC] [expires: 2024-05-11]
      85E3 8F69 046B 44C1 EC9F  B07B 76D7 8F05 00D0 26C4
uid           [ unknown] GPGTools Team <team@gpgtools.org>
sub   rsa4096 2014-04-08 [S] [expires: 2024-05-11]
sub   rsa4096 2020-05-11 [E] [expires: 2024-05-11]
  • 00D0 26C4 represents the Short Key ID
  • 76D7 8F05 00D0 26C4 represents the Long Key ID

WARNING: It is recommended that Long Key IDs are used as the collision space for Short Key IDs may result in duplicate identifiers.

Editing your OpenPGP key

We’ll be using gpg’s interactive mode to add subkeys to our primary OpenPGP key. (**replace XXXXXXXX with the associated Key ID for your generated keypair):

% gpg --expert --edit-key XXXXXXXXXXXXXXXX
gpg (GnuPG/MacGPG2) 2.2.20; Copyright (C) 2020 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/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: E   
[ultimate] (1). mel.llaguno@protonmail.com <mel.llaguno@protonmail.com>

You’ll note that there are two components to the key:

  • sec - the secret part
  • ssb - the subkey part. By default, we already have an encryption subkey (specified by usage: E)

Create the Authentication Subkey

Since we’re using RSA4096 as our base cipher, we’ll be creating a similar Authenticataion subkey.

NOTE: Be default, subkeys have Sign and Encrypt allowed actions enabled. You need to toggle their states to disable them. Current subkey capabilities are enumerated in the Current allowed actions: field.

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
  (14) Existing key from card
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? (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) 0
Key does not expire at all
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.

sec  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: E   
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: A 

One the operation completes, you should see a new ssb or subkey listed with your other keys.

[Optional] Create the Signing Subkey

Proceed as before in the previous section, but ensure that the subkey generated has on the sign capability. Again, completing this operation should result in another ssb/subkey entry for gpg:

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/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: E   
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: A 
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: S

Save your configuration and export your modified private key for safe keeping

Quit out of the gpg utility. This should prompt you to save your changes.

gpg> quit
Save changes? (y/N) y

% gpg --export-secret-key --armor XXXXXXXXXXXXXXXX > private_with_subkeys.key

WARNING: It’s important not to underestimate the value of your secret key. Anyone who can potentially copy/read it can impersonate you. It is recommended that you store it securely (and potentially offline).

Moving the subkeys to the Yubikey

Once the appropriate subkeys are generated, we’ll move them to be exclusively be accessible through the Yubikey Smart Card interface. This means that after this operation, removing the Yubikey from the system will also remove access to the associated OpenPGP key.

% gpg --expert --edit-key XXXXXXXXXXXXXXXX

gpg (GnuPG/MacGPG2) 2.2.20; Copyright (C) 2020 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/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: E   
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: A   
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: S   
[ultimate] (1). mel.llaguno@protonmail.com <mel.llaguno@protonmail.com>

gpg> toggle

sec  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: E   
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: A   
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: S   
[ultimate] (1). mel.llaguno@protonmail.com <mel.llaguno@protonmail.com>

gpg> keytocard
Really move the primary key? (y/N) y
Please select where to store the key:
   (1) Signature key
   (3) Authentication key
Your selection? 1

After moving you primary key, you will need to select the individual subkeys for migration. Unfortunately, this is somewhat obtuse in the gpg interactive mode. You can select keys by specifying their ssb ordinal. The usage determines where the key will be stored on the Yubikey/Smart Card.

NOTE: The selected subkey can be identified by the * appended to the ssb identifier.

WARNING: Key selection, like subkey generation, works with toggles. To disable a selected key, you MUST reissue the selection command AGAIN.

gpg> key 1

sec  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb* rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: E   
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: A   
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: S   
[ultimate] (1). mel.llaguno@protonmail.com <mel.llaguno@protonmail.com>

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

gpg> key 1

sec  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: E   
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: A   
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: S   
[ultimate] (1). mel.llaguno@protonmail.com <mel.llaguno@protonmail.com>

gpg> key 2

sec  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: E   
ssb* rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: A   
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: S   
[ultimate] (1). mel.llaguno@protonmail.com <mel.llaguno@protonmail.com>

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

sec  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: E   
ssb* rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: A   
ssb  rsa4096/XXXXXXXXXXXXXXXX
     created: 2020-10-14  expires: never       usage: S   
[ultimate] (1). mel.llaguno@protonmail.com <mel.llaguno@protonmail.com>

gpg> quit
Save changes? (y/N) y

There are only 3 available slots for certificates on the Yubikey/Smart Card. Note that we did not end up transferring our generated Signing subkey. This is OK given the that private key uploaded has the Signing capability enabled (usage: SC).

Verify that the OpenPGP subkeys have been associated with the Yubikey

You can list -K or --list-secret-key argument for gpg to verify that the OpenPGP keys have been migrated to the Yubikey. In particular, you should be able to the the associated secret (sec), the three subkeys (ssb) and a reference to the Yubikey Smart Card (Card serial no.):

% gpg -K                
/Users/mel/.gnupg/pubring.kbx
-----------------------------

... [REDACTED]

sec>  rsa4096 2020-10-14 [SC]
      XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
      Card serial no. = 0006 086XXXXX
uid           [ultimate] mel.llaguno@protonmail.com <mel.llaguno@protonmail.com>
ssb>  rsa4096 2020-10-14 [E]
ssb>  rsa4096 2020-10-14 [A]
ssb   rsa4096 2020-10-14 [S]

... [REDACTED]

Alternatively, you can use gpg --card-status.

Configure gpg-agent to act as an authentication source for ssh-agent

This section assumes that you already have gpg-agent running on your system (which should be true if you installed the GPG Suite mentioned earlier). The documentation I previously found described a bunch of hacks/workarounds. I used that material to describe a process that worked for me using the default functionality of gpg and ssh.

Enable ssh support for the gpg-agent

Append the following line to your ~/.gnupg/gpg-agent.conf file:

enable-ssh-support

Restart your gpg-agent by sending the process the appropiate signal via kill.

Identify the keygrip associated with your OpenPGP key

This is required to identify which secrets can be passed through to the ssh-agent.

WARNING: For the purposes of integration, we will be using ONLY the Authentication subkey.

% gpg2 --with-keygrip -k mel.llaguno@protonmail.com
... [REDACTED]

pub   rsa4096 2020-10-14 [SC]
      XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
      Keygrip = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
uid           [ultimate] mel.llaguno@protonmail.com <mel.llaguno@protonmail.com>
sub   rsa4096 2020-10-14 [E]
      Keygrip = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
sub   rsa4096 2020-10-14 [A]
      Keygrip = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX <<<< We will need this one.
sub   rsa4096 2020-10-14 [S]
      Keygrip = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

... [REDACTED]

Configure the Authentication Subkey Keygrip

Add the keygrip to the following file (which may not exist) - ~/.gnupg/sshcontrol.

Confgure your shell to export the SSH_AUTH_SOCKET environment variable

Add the following line to the appropriate start up file for your shell:

export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)

Verfiy that this environment variable is present:

% env | grep SSH
SSH_AUTH_SOCK=/Users/mel/.gnupg/S.gpg-agent.ssh

If noting is listed, then the environment variable has not been properly exported.

Test the visibility of your Encryption OpenPGP subkey

If everything is working correctly, your Authentication subkey should now be visible to ssh. You can verify using the following commands:

% ssh-add -l            
4096 SHA256:aLsYjSwRYVXc9WU6hrW0NjVIi4axQlN17DOhw5c2R7w cardno:0006086XXXXX (RSA)

NOTE: The cardno should be the same as the one associated with your Yubikey Smart Card in gpg.

To generate the public portion of this new Authentication ssh key, use the following command:

% ssh-add -L    
ssh-rsa AAAA...[REDACTED]...spqw== cardno:0006086XXXXX

This is the public key that you can upload to remote servers for authentication purposes.

Last but not least - Configure GitHub with the public key of the OpenPGP Authentication Subkey.

GitHub Octocat

Instructions

To complete this last step, you will need to be logged into your GitHub account.

  1. Navigate to your https://github.com/settings/keys
  2. Create an New SSH Key
  3. Paste in the output of the ssh-add -L command above. Label the key and save.
  4. Attempt to pull/checkout a repository with your github account. When you first attempt to use the Yubikey, you should be prompted for the default PIN. Enter it to continue (and be mindful of the number of retries you have before the Yubikey is locked).
  5. If you’ve provided your default PIN correctly, you should notice the LED on your Yubikey flashing. Simply touch it to proceed.

Voila! You’re finally done.

Addendum - Code Signing

Now that we have OpenPGP support for GitHub, we can use the generated Signing subkey with GitHub commits. The official instructions are here - Telling Git about your signing key .

Be sure to use the separate Signing subkey for this purpose.

NOTE: This only works if you’ve enabled public visibility to your primary email address associated with your GitHub account and that address is the same as the one associated with the certificate we used for this process.

TODO

  • code signing
  • revocation