# Make SSH use gpg-agent

Last updated 2019-09-01 jms1

- Simplifying the macOS setup process, only one reboot is needed now.

- Using unicode entity codes instead of just embedding the raw character into the document and hoping for the best. These include:

    |    Code    |   Char   | Description                   |
    |:----------:|:--------:|:------------------------------|
    | `&#xF8FF;` | &#xF8FF; | Apple logo                    |
    | `&#x2318;` | &#x2318; | Cloverleaf (Apple's CMD key)  |
    | `&#x279E;` | &#x279E; | Arrow pointing right          |

- Updated other parts of the text, to try and explain things more clearly.

**This version is newer than [`https://jms1.net/yubikey/make-ssh-use-gpg-agent.md`](https://jms1.net/yubikey/make-ssh-use-gpg-agent.md)** ... I haven't decided yet if I want to upload this version to my normal web site, or just replace that page on the web site with a redirect to the Keybase public folder where you're reading this.

# Background

In order to make `ssh` use the YubiKey when authenticating, you need to do two things:

* Run "`gpg-agent`" with SSH agent support enabled

* Make the `ssh`, `scp`, and `sftp` programs use the SSH agent provided by `gpg-agent`. This is done by setting the `SSH_AUTH_SOCK` environment variable to point to the named pipe provided by `gpg-agent`.

This document will cover how to do this for both Linux (specifically, CentOS 7) and macOS.

# Quick Setup

If you're standing at the console of a machine and need to use your YubiKey to authenticate outbound SSH connections...

### Linux - CentOS 7

**Ahead of time:** The following packages/services need to be installed/running on each machine.

```
sudo yum install gnupg2-smime pcsc-lite
sudo systemctl start pcscd
sudo systemctl enable pcscd
```

The "`pcscd`" process is what actually talks to the YubiKey.

**When you need it:**

```
eval $( gpg-agent --daemon --enable-ssh-support )
export SSH_AUTH_SOCK="$( gpgconf --list-dirs agent-ssh-socket )"
```

This should make all SSH processes running *under the shell where you do this*, use the SSH agent supplied by the `gpg-agent` process. 

### macOS

I need to do some checking to see what is and is not already installed on macOS. All of my machines have [GPG Suite](https://gpgtools.org/) installed via direct download, or via [Homebrew](https://brew.sh/), like so ...

```
brew cask install gpg-suite-no-mail
```

... so first I need to figure out what, if anything, would need to be installed on a machine where GPG Suite is not already installed.

Assuming the GPG software is installed, the same two commands listed above for "When you need it", *should* work (but again, I haven't tried this myself, fair warning).

# Pre-requisites

The two obvious dependencies are an SSH client, and `gnupg`. One or both of these are usually installed on most Linux and macOS machines.

### Linux

Almost all Linux distros come with `openssh` already installed, however some distros may split the client and server bits into separate packages. *Some* distros may also install `gnupg` as well - if not, you should be able to use `yum`, `apt-get`, or a similar command, to install the necessary packages.

#### CentOS, Fedora, RedHat, etc.

```
yum install openssh-clients gnupg2 gnupg2-smime
```

#### Debian, Ubuntu, etc.

```
apt-get install (names?)
```

#### Others

I don't have the exact commands for every other distro out there.  For `gnupg` you should search for packages with names like `gnupg`, `gpg2`, or maybe just `gpg`.

### macOS

For macOS, the `openssh` client is installed as a basic part of the OS, however `gnupg` is not. There are two easy ways to install the necessary software:

* Visit [`https://gpgtools.org/`](https://gpgtools.org/) and install the current version of GPG Suite. This will give you the `gpg` command line tools, a System Preferences widget to control some aspects of how `gpg` and `gpg-agent` work, and a `Mail.app` plug-in to support signing and encrypting email.

    Most of the software is free, however the `Mail.app` plug-in requires a payment (which I'm okay with), and limits the number of machines where you can use it (which I'm NOT okay with). Plus they're using "Paddle" to track activations, and there doesn't seem to be any way to "de-activate" a machine so the same license can be used on a different machine.

    I don't really *need* the the Mail plug-in, I'm happy to stick with [Thunderbird](https://www.thunderbird.net/) and [Enigmail](https://enigmail.net/).

* Use [Homebrew](https://brew.sh/) to install the same GPG Suite software.

    * If you want the full GPG Suite package, with the `Mail.app` plug-in...

        ```
        brew cask install gpg-suite
        ```

    * If you want the GPG Suite package *without* the `Mail.app` plug-in...

        ```
        brew cask install gpg-suite-no-mail
        ```

# Setup - Linux


### Manual process

To make *the current shell* use `gpg-agent` (and therefore the YubiKey) instead of the normal `ssh-agent` ...


* Make sure the `GPG_TTY` variable is set.

    ```
    export GPG_TTY=$(tty)
    ```

* Make sure that the `SSH_AUTH_SOCK` variable points to the `S.gpg-agent.ssh` socket.

    ```
    unset SSH_AGENT_PID
    export SSH_AUTH_SOCK="$( gpgconf --list-dirs agent-ssh-socket )"
    ```

* Any commands executed in this shell will use `gpg-agent` as the SSH agent.

### Automatic process (shell, per-user)

To make sure that your shell always sets the `GPG_TTY` and `SSH_AUTH_SOCK` variables correctly, add the following to your `.bash_profile` (or the appropriate file, if your login shell is not `bash`)

```
########################################
# Set things up for using gpg-agent

export GPG_TTY=$(tty)

function use-gpg-agent-for-ssh {
    SOCK="$( gpgconf --list-dirs agent-ssh-socket )"
    if [[ -n "${SOCK:-}" ]]
    then
        unset SSH_AGENT_PID
        export SSH_AUTH_SOCK="$SOCK"
    fi
}

use-gpg-agent-for-ssh
```

> This creates a function to "do the work", and then calls that function. This way if you decide you don't want this all the time, you can comment out just the function call (the last line), and then type `use-gpg-agent-for-ssh` in a shell to easily "activate" the change within that shell.

Once you have added this, every new interactive shell will use the changes. A quick way to test it is to open a new terminal window, which will contain a new shell. Once you have verified that it's working, you can either close the shell you're working in and open a new window, or you can run "`source ~/.bash_profile`" to read the updated profile into the current shell.

Note that setting the variables in this way will only affect shells and any processes started from those shells. In particular, it will NOT affect processes started by something other than your shell, such as cron jobs.

### Automatic process (all users)

The process is the same as the "shell, per-user" process above, except that instead of editing your `~/.bash_profile` file...

* You will edit `/etc/profile`, so that all users' shells will see it.

* On some systems you may be able to create an `/etc/profile.d/use-gpg-agent-for-ssh.sh` file instead.

If your system *has* multiple users, and some of them may want to use the normal `ssh-agent`, you may want to not include calling the function (i.e. the final `use-gpg-agent-for-ssh` line) in what you add to the system-wide profile. In this case, users who *do* want to use `gpg-agent` by default can add a `user-gpg-agent-for-ssh` line to their `~/.bash_profile`, and *anybody* on the system can manually type that command to use `gpg-agent` within that shell.

# Setup - macOS

The basic idea is similar for macOS - we need to set an `SSH_AUTH_SOCK` environment variable to point to the named pipe where `gpg-agent` (with SSH agent support) is running. The big difference is, when a user logs into a macOS machine, there is not a single shell from which all other processes inherit their environment variables.

Luckily, there is a way to set an environment variable so that all of that users' processes will inherit it... it's just a pain to set up, especially since SIP (System Integrity Protection) was added in OS X 10.11 "El Capitan".


## Disable `ssh-agent`

The first thing you need to do is disable the `ssh-agent` that Apple installs with macOS. However, if you're running OSX/macOS 10.11 or later, you will need to disable SIP before you can do this.

### Disable SIP

* &#xF8FF; &#x279E; Restart…

* When the boot chime plays, press and hold **&#x2318;-R** to enter recovery mode

* Utilities → Terminal

    ```
    csrutil disable
    exit
    ```

* Press **&#x2318;-Q** to exit the Terminal and return to the menu.

* &#xF8FF; &#x279E; Restart

### Remove the `ssh-agent` LaunchAgent

* Open a command line window (Terminal, iTerm2, etc.)

* Run "`csrutil status`" and make sure it says "disabled".

* Disable and unload the LaunchAgent.

    Note that if the machine has multiple users, you will need to log into each user's account and run this command, *as that user*. (Users cannot access each others' "`gui/xxx/`" trees, even if you have the other user's numeric UID.)

    ```
    launchctl disable gui/$(id -u)/com.openssh.ssh-agent
    launchctl unload /System/Library/LaunchAgents/com.openssh.ssh-agent.plist
    ```

* Remove the LaunchAgent system-wide.

    ```
    sudo mv /System/Library/LaunchAgents/com.openssh.ssh-agent.plist /var/root/
    ```

### Re-enable SIP

If you had to disable SIP, you should now re-enable it.

* &#xF8FF; &#x279E; Restart…

* When the boot chime plays, press and hold **&#x2318;-R** to enter recovery mode

* Utilities → Terminal

    ```
    csrutil enable
    exit
    ```

* Press **&#x2318;-Q** to exit the Terminal and return to the menu.

* &#xF8FF; &#x279E; Restart

### Make sure it worked

After the machine restarts normally, make sure it worked.

```
$ launchctl list com.openssh.ssh-agent
```

This should say that it can't find the servce.

```
$ env | grep SSH
```

You should not see an `SSH_AUTH_SOCK` variable (and probably won't see any output at all, just another command prompt.)

## Set up `gpg-agent`

### Make `gpg-agent` start automatically

Create `$HOME/Library/LaunchAgents/net.jms1.gpg-agent.plist`. You can download it from my web server like so ...

```
cd ~/Library/LaunchAgents
curl -O https://jms1.net/yubikey/net.jms1.gpg-agent.plist
```

... or you can create it by hand. The contents look like this: (I've wrapped the `<!DOCTYPE` line here to make this document easier to read)

```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>net.jms1.gpg-agent</string>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <false/>
    <key>ProgramArguments</key>
    <array>
      <string>/usr/local/MacGPG2/bin/gpg-connect-agent</string>
      <string>/bye</string>
    </array>
  </dict>
</plist>
```

Depending on how you installed the GPG software, the path to `gpg-connect-agent` (five lines up from the bottom of the file) may not be correct. You should verify the path and edit this file accordingly, before you continue.

Once the file is correct, tell `launchd` to use it.

```
launchctl load net.jms1.gpg-agent.plist
```

This makes macOS automatically run the command "`gpg-connect-agent /bye`" behind the scenes whenever the user logs in. This will start `gpg-agent` if it isn't already running.

### Set the `SSH_AUTH_SOCK` variable automatically

Create `$HOME/Library/LaunchAgents/net.jms1.gpg-agent-env.plist`. You can download it from my web server like so ...

```
cd ~/Library/LaunchAgents
curl -O https://jms1.net/yubikey/net.jms1.gpg-agent-env.plist
```

... or you can create it by hand. The contents look like this: (Again, I've wrapped the `<!DOCTYPE` line here to make this document easier to read)

```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>net.jms1.gpg-agent-env</string>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <false/>
    <key>ProgramArguments</key>
    <array>
      <string>/bin/launchctl</string>
      <string>setenv</string>
      <string>SSH_AUTH_SOCK</string>
      <string>/Users/jms1/.gnupg/S.gpg-agent.ssh</string>
    </array>
  </dict>
</plist>
```

This makes macOS automatically run the command "`/bin/launchctl setenv SSH_AUTH_SOCK /Users/jms1/.gnupg/S.gpg-agent.ssh`" behind the scenes whenever the user logs in. This makes `launchd` include that envirnoment variable, with that value, in the environment block of every new process it starts.

**You WILL need to edit this file.** The value that the `SSH_AUTH_SOCK` variable (the fourth line up from the bottom) starts with "`/Users/jms1`", this will need to be changed to your user's home directory.

Once the file is correct, tell `launchd` to use it.

```
launchctl load net.jms1.gpg-agent-env.plist
```

### Restart

You will need to either log out and log back in, or reboot the machine, in order to activate these changes.

### Make sure it worked

After you have logged out/in or rebooted, make sure it worked.

* Make sure the environment variable exists, with the correct value.

    ```
    $ env | grep SSH
    SSH_AUTH_SOCK=/Users/jms1/.gnupg/S.gpg-agent.ssh
    ```

    You should see an `SSH_AUTH_SOCK` variable, pointing to the path listed in the `net.jms1.gpg-agent-env.plist` file.

* Make sure the agent is reachable.

    ```
    $ gpg-connect-agent -v /bye
    gpg-connect-agent: closing connection to agent
    ```

    You should just see the message shown above.

## Configure `gpg-agent`

### Check gpg-agent SSH support

To check whether or not the currently running `gpg-agent` process supports the SSH agent protocol, and verify the filename of the named pipe where it's listening:

* Find the PID of the `gpg-agent` process.

    ```
    $ ps acx | grep gpg-agent
     5430   ??  Ss     0:03.65 gpg-agent
    ```

    In this case, `5430` is the PID (Process ID).

* Make sure the process has at least one file handle open to the SSH agent named pipe.

    ```
    $ lsof -nPp 5430 | grep ssh
    gpg-agent 5430 jms1    6u  unix 0x914e502e3097527b      0t0             /Users/jms1/.gnupg/S.gpg-agent.ssh
    gpg-agent 5430 jms1    7u  unix 0x914e502e40a7ecab      0t0             /Users/jms1/.gnupg/S.gpg-agent.ssh
    ```

    In this case, there are two handles. I *think* one of them is listening, and one is an active connection from an SSH process which happens to be open in another window... but the important part is, they're the same filename that the `SSH_AUTH_SOCK` variable is pointing to.

### Enable SSH support

If you need to enable SSH support, or want to go through the exercise to ensure that SSH support is explicitly enabled:

* Add this line to `~/.gnupg/gpg-agent.conf`

    ```
    enable-ssh-support
    ```

* If you changed the file, you should restart the running `gpg-agent` process:

    ```
    gpg-connect-agent killagent /bye
    gpg-connect-agent /bye
    ```

## The `sshcontrol` file

I found a web page from 2016-04-20 whose author ran into a problem where he had to manually add the "keygrip" of his authentication key to his "`~/.gnupg/sshcontrol`" file. **I haven't had this problem**, but I'm including some information about it here, just in case somebody else runs into whatever problem he was having.

**Ref:** [`http://karl.kornel.us/2016/04/sshcontrol/`](http://karl.kornel.us/2016/04/sshcontrol/)

Find the "keygrip" for the authentication key. (A "keygrip" is a kind of fingerprint on the public key plus options, see [http://lists.gnupg.org/pipermail/gnupg-users/2012-July/045115.html](http://lists.gnupg.org/pipermail/gnupg-users/2012-July/045115.html) for details.)

```
$ gpg --list-keys --with-keygrip 6353320118E1DEA2F38EAE806B2EDC90B5C6DC30
...
pub   rsa4096/0x6B2EDC90B5C6DC30 2017-05-27 [SC]
      6353320118E1DEA2F38EAE806B2EDC90B5C6DC30
      Keygrip = 05D34BB61EA379C3B05817A753FD21E2DA6231E1
uid                   [ultimate] John M. Simpson <jms1@voalte.com>
uid                   [ultimate] John M. Simpson <jms1@jms1.net>
sub   rsa4096/0x297E5961AB566594 2017-05-27 [E]
      Keygrip = 4530A5E8BBB0F6A604E847AB21FBC29D1E44B293
sub   rsa4096/0xBA6C2A169C6C0F60 2017-11-10 [A]
      Keygrip = CD8EEE908C11D30706FC1BB704066180F2F0196E
```

The value we want is the keygrip of the authentication sub-key, since that was uploaded to the YubiKey.

Add a line to `~/.gnupg/sshcontrol` containing that value, with "` 0`" after it (this is a TTL value, not really sure how this works). Comments can be added using "#".

```
# YubiKey 2017-11-10
CD8EEE908C11D30706FC1BB704066180F2F0196E 0
```

Again, I haven't run into this problem, so I've never actually edited this file, and I've never seen anything other than ten lines of comments in this file.

## Usage

If you've gone through the setup process above, and the `SSH_AUTH_SOCK` variable points to the `S.gpg-agent.ssh` socket, you don't really need to *do* anything differently - just use `ssh`, `scp`, `sftp`, or whatever, the same way you already do. As long as your SSH client works with an agent, it should "just work".

If you haven't gone through the steps above ... do so.

## `authorized_keys`

Getting the public key line needed for `authorized_keys` files is very easy:

* Make sure `gpg-agent` is running, and the current shell is configured to use it.

    ```
    $ export GPG_TTY=$(tty)
    ```

* If you have NOT done the setup procedure to ensure that `ssh` always uses `gpg-agent`...

    ```
    $ unset SSH_AGENT_PID
    $ export SSH_AUTH_SOCK="$( gpgconf --list-dirs agent-ssh-socket )"
    ```

Insert the YubiKey, wait a few seconds, and **run "`ssh-add -L`"**. The card's key will have `cardno:` with the YubiKey's serial number as the comment.

```
$ ssh-add -L
ssh-rsa AAAAB3NzaC1y...vwOVnvi1eTUAkjIPw== cardno:000606940447
```

This line can be added as-is to the `~/.ssh/authorized_keys` file on any machine to which this key should grant access. If agent forwarding is active, you could even do this, on that machine:

```
$ ssh-add -L >> ~/.ssh/authorized_keys
```

However, I find it useful to change the comments on these lines to something more descriptive, like the date and my email address.

```
$ cat ~/.ssh/authorized_keys
ssh-rsa AAAAB3NzaC1y...vwOVnvi1eTUAkjIPw== jms1@jms1.net 2019-03-21 hardware token
```

# References

* [http://www.dowdandassociates.com/blog/content/howto-set-an-environment-variable-in-mac-os-x-launchd-plist/](http://www.dowdandassociates.com/blog/content/howto-set-an-environment-variable-in-mac-os-x-launchd-plist/) - shows how to create a `.plist` file which runs a "`launchctl setenv`" command during login, since "`launchctl setenv`" doesn't persist across reboots.

* [Login Script](https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CustomLogin.html) (Apple) - this script is executed *as root*, during every user's login process, before the user is able to interact with their account.

    One thing to be aware of, is that there can only be one login script, shared by all users.

    This is not directly related to SSH using `gpg-agent`, I just thought it was interesting and decided to make a note of it.

# Notes

### SSH secret keys and `gpg-agent`

Normal SSH keys *can* be added to `gpg-agent` using the `ssh-add` command. When you do this, `gpg-agent` writes a copy of the secret key to a file in the `~/.gnupg/private-keys-v1.d/` directory, named after the "key grip" (another kind of fingerprint, which includes the options rather than just the public key). These files are encrypted using a passphrase which may or may not be the same as the key's normal passphrase. When you add a key, you will be prompted first for the existing passphrase (to read the secret key), and then for a new passphrase (to write an encrypted copy of the secret key in this new file).

However, `ssh-add -d` (or `-D`) will not remove them (at least, it won't make them stop appearing in `ssh-add -l` output.) `gpg-agent` adds the key grips (similar to a Key ID) to a file called `~/.gnupg/sshcontrol`. Removing the keygrip from this file makes the key no loger appear in the "`ssh-add -l`" output, and no longer be available for SSH authentication. (Removing the `~/.gnupg/sshcontrol` file will make ALL keys no longer appear in the "`ssh-add -l`" output.) Note that editing or removing this file does not remove the `private-keys-v1.d` file - you will need to remove those by hand.

In addition, `gpg-agent` writes files under the `~/.gnupg/private-keys-v1.d/` directory which correspond to the keys stored on the YubiKey. However, these files don't contain the actual secret keys - they can't, because **the secret keys never leave the YubiKey**. Instead, these files contain "shadow keys", which contain the *serial number* of the YubiKey contianing the secret key. When `gpg-agent` receives a request which needs one of these keys, if that YubiKey isn't connected it will ask the user to plug it in, and then it will send the request to the YubiKey in order to be encrypted, decrypted, or signed.
