Trezor GPG Setup with Forwarding Over SSH

I recently got a Trezor One hardware wallet. For the uninitiated, this is a small hardware device that securely stores the cryptographic data needed to manage a cryptocurrency wallet. Check out their wiki for more information. There are other similar devices on the market, the Trezor just happened to be a good fit for me and my needs. Along with cryptocurrency transactions, this device is also able to perform a number of other cryptographic operations including SSH and GPG key operations. I use GPG with git to sign my commits. I’ve recently come back to git and coding after an extended hiatus and realized that I let my previous GPG key expire and honestly, forgot the passphrase. Yes, I know, I am a garbage person, I can’t help it. So, as I was getting ready to shamefully generate a new GPG key, I started considering algorithms and trying to decide the best way to set up the new key. That’s when I remembered the Trezor and its GPG capabilities. I ran into a lot of hang-ups along the way since this process is not overly documented and honestly a bit obscure. I also needed to be able to forward my GPG key over SSH to a remote computer for signing remotely. Since the key resides permanently on the Trezor device there’s no way of just copying the private key to the remote machine. I’ve outlined my process below to maybe save someone else the headache I experienced.

I’m using Arch Linux on all my machines and running the latest versions of all the packages as of the date of this post:

  • GPG 2.2.29
  • OpenSSH 8.8

Trezor GPG Setup

I started off at Trezor’s wiki post for GPG support and the trezor_agent install instructions on GitHub. I had all the dependencies listed installed, so I just had to install the agent and bridge.

sudo pip3 install trezor_agent

You can install the Trezor daemon (bridge) from the AUR or your distro’s repos as well.

yay -S trezor-bridge-bin

I opted to install the Trezor daemon manually because I apparently like to do things the hard way. I blame this on you, Fabio.

Build

GO111MODULE=auto go get github.com/trezor/trezord-go
GO111MODULE=auto go build github.com/trezor/trezord-go
./trezord-go -h

Install executable and udev rules

sudo mv ./trezord-go /usr/local/bin/trezord
sudo chown root:root /usr/local/bin/trezord
sudo curl https://data.trezor.io/udev/51-trezor.rules \
    -o /etc/udev/rules.d/51-trezor.rules

Create systemd service

/usr/lib/systemd/system/trezord.service

[Unit]
Description=Trezor Daemon
After=network.target

[Service]
Type=simple
GuessMainPID=no
ExecStart=/usr/local/bin/trezord
Restart=always

[Install]
WantedBy=multi-user.target

Start and enable the service:

sudo systemctl start trezord.service
sudo systemctl enable trezord.service

Create an update script (because I’m lazy)

Save this script somewhere handy and run it as root when you want to update the bridge.

#!/usr/bin/env bash
go clean
GO111MODULE=auto go get -u github.com/trezor/trezord-go
GO111MODULE=auto go build -a github.com/trezor/trezord-go
mv trezord-go /usr/local/bin/trezord

Generate key

Now we can finally create a key.

trezor-gpg init "John Doe <john@email.com>"
...
gpg -K
/home/john/.gnupg/trezor/pubring.kbx
-----------------------------------------
sec   nistp256 1970-01-01 [SC]
      ###[PUBLIC KEY SIG]###
uid           [ultimate] John Doe <john@email.com>
ssb   nistp256 1970-01-01 [E]

Set the GPG context and add it to your ~/.bashrc or equivalent for persistence:

export GNUPGHOME=~/.gnupg/trezor
echo "export GNUPGHOME=~/.gnupg/trezor" >> ~/.bashrc

Be aware that this will override your current GPG keychain because apparently hardware keys and local keys can’t live in harmony under this setup. You can change contexts back and forth though. See the docs for more info. If you want to add other identities or keys to the one that’s generated, see the docs for those advanced use cases ’cause ain’t nobody got time for that.

Local git commit signing

To sign git commits locally you’ll want to configure a few settings:

git config --global commit.gpgsign true
git config --global user.signingkey ###[PUBLIC KEY SIG]###
git config --global user.name "John Doe"
git config --global user.email john@email.com

Test a commit to see if it works. Or not. You do you.

Forwarding GPG agent over SSH (the hard part)

This is where things started to fall apart for me. I looked for answers and got a lot of confusing and conflicting information because GPG forwarding has changed a lot over the versions, we’re dealing with a custom GPG agent, and we have a non-standard GNUPGHOME directory. I mostly pieced this together from the GnuPG wiki and the trezor-agent repo docs.

The TLDR; version is that a GPG agent listens on a local socket(s) for encryption requests. In the process of signing commits, git calls to GPG which in turn calls the socket to process the request. Our GPG agent has to run on the local machine since that’s where the hardware device is attached with the private key. So, we use SSH to make a magical portal from the socket file on the remote machine to the socket file on the local machine so that GPG calls on the remote machine will be handled by the GPG agent on the local machine.

GPG Contexts

To start, add a new GPG context on the remote machine, I chose trezor since it seemed appropriate:

mkdir ~/.gnupg/trezor
chmod 700 ~/.gnupg/trezor
export GNUPGHOME=~/.gnupg/trezor

~/.bashrc

...
export GNUPGHOME=~/.gnupg/trezor
gpgconf --create-socketdir

Then look up and take note of the unique socket directory for the new GPG context on the remote machine:

gpgconf --list-dir socketdir
/run/user/1000/gnupg/d.[REMOTEUID]

Do the same on the local machine:

gpgconf --list-dir socketdir
/run/user/1000/gnupg/d.[LOCALUID]

You’ll substitute these unique paths in the following configurations.

Systemd Files

Next, configure the Trezor GPG Agent socket on the local machine with some systemd black magic.

~/.config/systemd/user/trezor-gpg-agent.service

[Unit]
Description=Trezor GPG agent
Requires=trezor-gpg-agent.socket

[Service]
Type=simple
Environment="GNUPGHOME=%h/.gnupg/trezor"
Environment="PATH=/bin:/usr/bin:/usr/local/bin:%h/.local/bin"
ExecStart=/usr/bin/trezor-gpg-agent -vv

~/.config/systemd/user/trezor-gpg-agent.socket

[Unit]
Description=Trezor GPG agent socket

[Socket]
ListenStream=%t/gnupg/d.[LOCALUID]/S.gpg-agent
FileDescriptorName=std
SocketMode=0600
DirectoryMode=0700

[Install]
WantedBy=sockets.target

Start and enable your shiny new systemd files and bask in their glory:

systemctl --user start trezor-gpg-agent.{service,socket}
systemctl --user enable trezor-gpg-agent.socket

SSH config

Next, open ~/.ssh/config on the local machine and add/modify the configuration block for the remote host.

~/.ssh/config

Host example.com
    RemoteForward /run/user/1000/gnupg/d.[REMOTEUID]/S.gpg-agent /run/user/1000/gnupg/d.[LOCALUID]/S.gpg-agent

Watch out for the user id values and unique folder names here when configuring. Both remote and local user ids were 1000 for me, but your system may differ.

Add the following line to sshd_config on the remote machine telling it to dispose of the remote socket file upon termination of the connection:

/etc/ssh/sshd_config

...
StreamLocalBindUnlink yes

(Re)connect to the remote machine via SSH.

ssh john@example.com

If you got things right you should see a normal login. If you see SSH bellyache like this:

Warning: remote port forwarding failed for listen path ...

Check your paths and configuration file to make sure the remote directory exists, the remote socket file does not exist, and the local socket file exists. Check permissions too while you’re in there. SSH is basically saying that it can’t create the remote socket file.

Push public key

GPG needs a copy of the public key in order to use the private key through the agent. Push the local public keyring to the remote machine:

Local:

scp ~/.gnupg/trezor/pubkey.asc ~/.gnupg/trezor/ownertrust.txt john@example.com:~/.gnupg/trezor/

If these files do not exist:

gpg --output pubkey.asc --armor --export ###[PUBLIC KEY SIG]### 
gpg --export-ownertrust > ownertrust.txt

Remote:

cd ~/.gnupg/trezor
gpg --import pubkey.asc
gpg --import-ownertrust ownertrust.txt

Test to make sure it made it loaded correctly and you can access the agent:

gpg -K
/home/john/.gnupg/trezor/pubring.kbx
-----------------------------------------
sec   nistp256 1970-01-01 [SC]
      ###[PUBLIC KEY SIG]###
uid           [ultimate] John Doe <john@email.com>
ssb   nistp256 1970-01-01 [E]

Remote git commit signing

Use the same settings as you did locally:

git config --global commit.gpgsign true
git config --global user.signingkey ###[PUBLIC KEY SIG]###
git config --global user.name "John Doe"
git config --global user.email john@email.com

Conclusion

Hopefully, this walkthrough will help some poor struggling soul out there. I learned a lot about GnuPG, the Trezor, SSH forwarding, and systemd in the process of this struggle.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.