GnuPG (PGP) SmartCard over SSH to a VM with a Yubikey
Published: 2022-04-07 09:14:10

Agent Forwarding (GNU Privacy Guard (GPG) & SSH) over SSH.

Most all the blog posts I found for this topic were five or more years old, and were referencing a time prior to GnuPG 2.1 which was where some real changes landed, which made this process possible, and safe. These days even LTS distributions such as Ubuntu 20.04 include GnuPG 2.2 which is yet simplier, and requires even fewer hoops be jumped through.

My objective seemed simple:

- From my laptop (macOS Monterey)..
- Forward my EC-DSA key with an SSH agent...
- ..and a GPG Key (actually a smartcard, but that doesn't matter) ..
- into a Linux VM over SSH so that I can work there, sign commits, and clone `git+ssh://` repositories

It's possible to use a GPG key (and smartcard) as an SSH authentication token, but I'm not interested in that, I have separate SSH and GPG keys, and I'm happy with that set-up.

# Check you have new enough software

```
# Workstation:
$ gpg --version
gpg (GnuPG) 2.3.4    # anything over 2.1 is fine

# VM (ssh target)
$ gpg --version
gpg (GnuPG) 2.2.19
```

# Generate or configure relevant keys:

### Smart-card

Out of scope, but it's pretty doable. Follow this guide:

- https://support.yubico.com/hc/en-us/articles/360013790259-Using-Your-YubiKey-with-OpenPGP

### Generate a GPG Key

```
$ gpg --gen-key
```

It is an interactive program which will ask for your real name, and your user email address, complete those, and then show you some output, the important part is the long key ID:

```
Real name: Example User
Email address: user@example.com
You selected this USER-ID:
    "Example User <user@example.com>"

..... snip .....

pub   ed25519 2022-04-06 [SC] [expires: 2024-04-05]
      7B5CB440DA3A316537466897128986B90599B1B1
uid                      Example User <user@example.com>
sub   cv25519 2022-04-06 [E] [expires: 2024-04-05]
```

In this case `7B5CB440DA3A3...` is the key ID, copy it to the clipboard, or export it to an environment variable, we'll need this a lot.

### Generate an SSH key

Run this and follow the prompt...

```
$ ssh-keygen 
```

This will generate something like an `~/.ssh/id_rsa` or `~/.ssh/id_ecdsa` or something depending what you configure.


## Getting Relevant Socket Addresses

```
# On your local machine:

$ gpgconf --list-dirs agent-ssh-socket
/Users/<your username>/.gnupg/S.gpg-agent.ssh
$ gpgconf --list-dir agent-socket
/Users/leehambley/.gnupg/S.gpg-agent
$ gpgconf --list-dirs agent-extra-socket
/Users/<your username>/.gnupg/S.gpg-agent.extra


% On the remote machine:
$ gpgconf --list-dirs agent-ssh-socket
/run/user/<your numeric user id, probably>/gnupg/S.gpg-agent.ssh
$ gpgconf --list-dirs agent-socket
/run/user/<your numeric user id, probably>/gnupg/S.gpg-agent
```

The GPG Agent and SSH Agent sockets should be self-explanatory enough, however the "extra" socket is peculiar, see this from the docs:

> Also listen on native gpg-agent connections on the given socket. The intended use for this extra socket is to setup a Unix domain socket forwarding from a remote machine to this socket on the local machine. A gpg running on the remote machine may then connect to the local gpg-agent and use its private keys. This enables decrypting or signing data on a remote machine without exposing the private keys to the remote machine.

The extra socket then is a slightly less privilidged socket which safely allows forwarding to a remote machine without giving that remote machine full control over your local GPG agent (as a the normal socket would have)

## Local Configuration

```
# ~/.ssh/config
Host thevmweworkin
  # this is standard SSH config, mostly
  HostName 192.168.64.11
  User vagrant
  Port 22
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentitiesOnly yes
  LogLevel FATAL

  # This is the GPG/SSH forwarding 
  RemoteForward /run/user/<your numeric user id, probably>/gnupg/S.gpg-agent /Users/<your username>/.gnupg/S.gpg-agent.extra
  RemoteForward /run/user/<your numeric user id, probably>/gnupg/S.gpg-agent.ssh /Users/<your username>/.gnupg/S.gpg-agent.ssh
  ForwardAgent yes
  ExitOnForwardFailure yes

# ~/.gnupg/agent-config.conf
cat ~/.gnupg/gpg-agent.conf
default-cache-ttl 600
max-cache-ttl 7200
pinentry-program /opt/homebrew/bin/pinentry-mac            # brew install gnupg for this, or don't specify pin entry
extra-socket /Users/<your username>/.gnupg/S.gpg-agent.extra  
enable-ssh-support
keep-display
default-cache-ttl 600
max-cache-ttl 7200
keep-tty
keep-display

# Your ~/.zshrc or ~/.bash_profile, etc
eval $(gpg-agent --daemon)
```

Run this to make sure your agent is running/restarted with the correct config:

```
$ gpg-connect-agent reloadagent /bye   # will start an agent if you didn't have one running
OK
```

## Remote Configuration

Configure Git to require signing commits:

```
$ git config [--global] commit.gpgsign true
$ git config --global user.signingkey 34EC1A4D011E7FDFFD6E3722A4F823DC30FA9DA7!  # the exclamation mark makes Git use this key, and not try and detect a subkey to use
```

Configure SSH to remove local sockets of already running daemons, and allow you to overbind them:

```
/etc/ssh/sshd_conf

# add the following:
# https://superuser.com/questions/161973/how-can-i-forward-a-gpg-key-via-ssh-agent
StreamLocalBindUnlink yes
```

## Upload the key someplace (Github, Gitlab), and into the VM public keychain

```
# From the host machine
gpg --output public.pgp --armor --export 34EC1A4D011E7FDFFD6E3722A4F823DC30FA9DA7
gpg --armor --export 34EC1A4D011E7FDFFD6E3722A4F823DC30FA9DA7 | pbcopy

scp public.pgp thevm:~/public.gpg
```

Then go to your profile and paste the new GPG key into your profile.

## Check Everything Works