March 4, 2024

gpg-agent as ssh-agent

Any reasonably recent systemd based desktop distro will have a gpg-agent that is launched in supervised mode on login. Supervised means that it will be killed on logout. This implies that passphrases will have to entered again after logout.

The setup below works on Chrom{e,ium}OS too.

Agent Forwarding

On trusted boxen, you can enable agent forwarding with the following stanza in ~/.ssh/config. Forwarding the extra socket allows pinentry to behave differently, one hopes more securely.

Host *.alephnull.site *.local *.home
     StreamLocalBindUnlink yes
     RemoteForward /run/user/1000/gnupg/S.gpg-agent /run/user/1000/gnupg/S.gpg-agent.extra
     RemoteForward /run/user/1000/gnupg/S.gpg-agent.ssh.remote /run/user/1000/gnupg/S.gpg-agent.ssh
     RemoteForward /run/user/1000/emacs/remote /run/user/1000/emacs/server

Your gpg public keys are required on the remote box for this to work. Private keys are not. If the key you are using remotely has not been used in a while, the local gpg agent might not have it and something might have also gone wrong in the chain to call pinentry. In this case, it might help to seed the key on your local gpg-agent with,

gpg -o /dev/null --sign -u key_id /dev/null

Remote boxen

On remote boxen, you do not want the systemd managed gpg-agent starting up. On Raspbian, gpg-agent is started in the user slice, so disable it with,

scu mask gpg-agent.service gpg-agent.socket gpg-agent-ssh.socket gpg-agent-extra.socket gpg-agent-browser.socket
killall gpg-agent

pinentry

The terminal where you set export GPG_TTY=$(tty); echo updatestartuptty | gpg-connect-agent is where pinentry will ask for the passphrase.

  • If you happen to be running some curses thing when pinentry decides to use the pts, not only will pinentry not be able to read your passphrase, it will leave your pts in a state where you can’t even call tput reset to fix it.
  • If something caused pinentry to popup somewhere and you were on some other pts and thought, “Oh, I’ve probably not set GPG_TTY”, and run the incantation, not only will pinentry not be able to read your passphrase, it will leave your pts in a state where you can’t even call tput reset to fix it.

gpg-agent

Defaults to caching passphrases for 30 mins. It does not enable ssh-agent support by default. Add the following to ~/.gnupg/gpg-agent.conf,

enable-ssh-support
disable-scdaemon
grab
max-cache-ttl 432000
default-cache-ttl 86400
max-cache-ttl-ssh 864000
default-cache-ttl-ssh 864000

grab is the bit that messes up the pts if used incorrectly. It is a trade off between security and UX.

gpg-connect-agent reloadagent /bye should reload the agent after editing its config file.

Your shell setup should call the below somewhere

remote_ssh_sock="/run/user/1000/gnupg/S.gpg-agent.ssh.remote"
[[ ! "${SSH_AUTH_SOCK}" && -S $remote_ssh_sock ]] && export SSH_AUTH_SOCK=$remote_ssh_sock

kitty ssh

Since kitty takes a radical approach to terminals, it requires its terminfo config to be present on remote hosts. If you can, run apt install kitty-terminfo on long lived hosts. If you cannot, there is an ssh kitten that works for basic usage. You might still need export TERM=xterm for some programs with the kitten.

Now, if you have a keybinding like below to open tabs to remote boxen,

map kitty_mod+g>r	launch --type=tab --title=rpi5 ssh -t rpi5.local tmux -u new-session -ADs rpi5

kitty will not be able to find the ssh-agent as SSH_AUTH_SOCK is set by the shell and kitty will not have the variable when it tries to launch a process. There are many ways to set environment variable per user session but the simplest is to create a file ~/.config/environment.d/ssh-agent.conf with contents

SSH_AUTH_SOCK=/run/user/1000/gnupg/S.gpg-agent.ssh

The rhs needs to be static.

In 1995, when I was getting started on Linux, I used to laugh at Windows asking me to restart my PC to set an environment variable. After all these years of pam, policykit, DBus, and systemd, you could run dbus-update-activation-environment --systemd --all and then restart your machine to have everything working properly.

emacs

Just install pinentry-gtk2 or pinentry-gnome3 as appropriate. Attempting to use emacs or the shell for pinentry is an exercise in yak-shaving.

The .ssh/config stanza above forwards the emacs server socket to open files in your home emacs from the remote terminal. Note that this works well using the unix socket. The emacs TCP socket appears to be a fallback on systems where unix sockets are not available. Attempting to use the TCP edit-server for this feature does not work.

The runtime scratch /run/user/$UID/emacs directory is created by edit-server and is available on home box. This directory is not available on the remote box and attempting to forward sockets to this directory will silently fail. Arrange for this directory to be available before ssh.

auth-sources.el1 is a framework that support multiple backends (authinfo(.gpg), netrc, pass, etc.) After Emacs comes up, it should all be fine but during startup, file-handlers are disabled to help speed up launch. auth-sources relies on epa setting up a file-handler to transparently decrypt .gpg files. gpg-agent is required to decrypt.


  1. part of Emacs 28 ↩︎

Powered by Hugo & Kiss.