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.