DEV Community

Lee Hambley
Lee Hambley

Posted on

GnuPG (PGP) SmartCard over SSH to a VM with a Yubikey

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
Enter fullscreen mode Exit fullscreen mode

Generate or configure relevant keys:


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

Generate a GPG Key

$ gpg --gen-key
Enter fullscreen mode Exit fullscreen mode

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:
You selected this USER-ID:
    "Example User <>"

..... snip .....

pub   ed25519 2022-04-06 [SC] [expires: 2024-04-05]
uid                      Example User <>
sub   cv25519 2022-04-06 [E] [expires: 2024-04-05]
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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
$ 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
Enter fullscreen mode Exit fullscreen mode

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
  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  
default-cache-ttl 600
max-cache-ttl 7200

# Your ~/.zshrc or ~/.bash_profile, etc
eval $(gpg-agent --daemon)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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


# add the following:
StreamLocalBindUnlink yes
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Check Everything Works

Top comments (0)