Security Adventures 1. How to get yubikey+gpg+ssh+gitbhub working on MacOS

Home   »   Security Adventures 1. How to get yubikey+gpg+ssh+gitbhub working on MacOS

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](https://support.yubico.com/hc/en-us/articles/360013708340-YubiKey-5-Nano)

![The Yubikey 5](https://resources.yubico.com/53ZDUYE6/as/qd4ajv-3kfhzk-fqbarm/YubiKey_5-Series-Family-Overhead.auto?width=400&height=326&position=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.
   - As of writing, [FIDO/U2F support for OpenSSH is from 8.2+](https://www.openssh.com/txt/release-8.2). MacOS currently ships with `OpenSSH_8.1p1, LibreSSL 2.7.3`, so won't be covering this. 
- Support for the following type of cryptographic keys - `RSA 2048, RSA 4096 (PGP), ECC p256, ECC p384`. 
   - Older versions of the key may not have full support for the strongest/newest ciphers. For example the neo 4 was limited to `RSA 2048` and was affected by this bug - [YSA-2017-01  – Infineon weak RSA key generation](https://www.yubico.com/support/security-advisories/ysa-2017-01/)
- 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](https://protonmail.com/images/media/logos/protonmail-logo-dark.png)

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](https://protonmail.com) 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](https://protonmail.com/support/knowledge-base/pgp-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](https://formulae.brew.sh/formula/gnupg) or alternatively, install [GPGSuite](https://gpgtools.org)).

**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](https://blog.trailofbits.com/2020/06/11/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).
1. 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.
1. 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.
1. Provide a means to configuring the retries counts for the various PINs (preferrably without wiping any already configured PINs)
1. 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:

- [PIN and Management Key](https://developers.yubico.com/yubikey-piv-manager/PIN_and_Management_Key.html)
- [Accessing administrative functions](https://developers.yubico.com/PIV/Introduction/Admin_access.html) <- Note that this matrix is the ONLY place which documents that changing the retries count will RESET THE PINS TO THE DEFAULTS. Why dear God?
- [PIV-enabled YubiKeys](https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html) <- Mentions the PIVv default PINs/keys.
- [pkirkovsky/yubikey-reset.sh](https://gist.github.com/pkirkovsky/c3d703633effbdfcb48c) <- User supplied way of resetting the Yubikey to the factory defaults using gpg. Not included in the official Yubico documentation.

> **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](https://developers.yubico.com/yubikey-manager/)

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:

```sh
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):

```sh
% 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:

```sh
% 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):

```sh
% 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.
[email protected] 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***):

```sh
% 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***):

```sh
% 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](https://developers.yubico.com/yubikey-piv-manager/PIN_and_Management_Key.html), 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).

```sh
% 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.

```sh
% 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:

```sh
% 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***):

```sh
% 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:

```sh
% 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](https://developers.yubico.com/yubico-piv-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:

```sh
% 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.

```sh
% 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:

- [OpenPGP SSH access with Yubikey and GnuPG](https://gist.github.com/artizirk/d09ce3570021b0f65469cb450bee5e29)
- [Using Your YubiKey with OpenPGP](https://support.yubico.com/hc/en-us/articles/360013790259-Using-Your-YubiKey-with-OpenPGP)
- [PGP - Introduction](https://developers.yubico.com/PGP/Importing_keys.html)
- [Easy multifactor authentication for SSH using YubiKey NEO tokens](https://florin.myip.org/blog/easy-multifactor-authentication-ssh-using-yubikey-neo-tokens)
- [Using GnuPG (2.1) for SSH authentication](https://incenp.org/notes/2015/gnupg-for-ssh-authentication.html)
- [Setting up ssh public key authentication on macOS using a YubiKey 4](https://gist.github.com/ixdy/6fdd1ecea5d17479a6b4dab4fe1c17eb)

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:

```sh
% % 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:

```sh
%  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](https://developers.yubico.com/PGP/Importing_keys.html)

> 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:

```sh
 % 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 
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):

```sh
% 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). [email protected] 
```

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.

```sh
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
        = 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) 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`: 

```sh
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. 

```sh
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.

```sh
% 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). [email protected] 

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). [email protected] 

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

```sh
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). [email protected] 

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). [email protected] 

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). [email protected] 

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). [email protected] 

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.`):

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

... [REDACTED]

sec>  rsa4096 2020-10-14 [SC]
      XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
      Card serial no. = 0006 086XXXXX
uid           [ultimate] [email protected] 
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. 

```sh
% gpg2 --with-keygrip -k [email protected]
... [REDACTED]

pub   rsa4096 2020-10-14 [SC]
      XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
      Keygrip = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
uid           [ultimate] [email protected] 
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:

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

Verfiy that this environment variable is present:

```sh
% 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: 

```sh
% 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:

```sh
% 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](https://github.githubassets.com/images/modules/logos_page/Octocat.png)

## 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
1. Create an `New SSH Key`
1. Paste in the output of the `ssh-add -L` command above. Label the key and save.
1. 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).
1. 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
](https://docs.github.com/en/[email protected]/github/authenticating-to-github/telling-git-about-your-signing-key#telling-git-about-your-x509-key-1). 

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

- [x] code signing
- [ ] revocation


Leave a Reply

Your email address will not be published. Required fields are marked *