Unlocking SSH keys using pass

2020-05-29


I use pass as my password manager. It stores passwords and other secrets as GPG-encrypted files in a Git repository. I host that repo on GitHub and have clones of it on my work and personal computers. I store nearly all of my personal passwords in pass, including passwords for my GitHub SSH keys. I’ve gone through three ways of unlocking these SSH keys.

I started by running pass -c SSH/Ubuntu to copy my SSH key’s password to the keyboard, running a Git command like git push, then pasting my password into the prompt. I wasn’t satisfied with how much typing this process required. Plus, sometimes I’d forget to copy the password to my clipboard before running the Git command. I’d have to Ctrl+C that command and rerun it after copying the password. Even though I used ssh-agent and wasn’t unlocking my SSH key that often, I decided to automate this process.

My second method used a script I called sshpass. Here it is in its entirety:

#!/usr/bin/expect -f
set password [exec pass SSH/Ubuntu]

spawn ssh-add
expect "passphrase"
send -- "$password\r"
interact

This is an expect script. According to its man page, expect is “a program that ‘talks’ to other interactive programs according to a script”. In this case, the script instructs expect to store the output of pass SSH/Ubuntu in the variable password, then run ssh-add, wait for it to prompt for a password, and write the password to ssh-add’s stdin. For reasons I don’t totally understand, the interact command, which attaches the terminal’s standard streams to ssh-add’s, is necessary to let ssh-add finish running.

Now, all I had to do was run sshpass before running a Git command, but I’d still sometimes forget to run sshpass. Finally, this week, I came across a third solution that addresses this issue on Linux, mainly based on this blog post.

First, I added AddKeysToAgent yes to my SSH configuration at ~/.ssh/config. This causes ssh to add keys to ssh-agent when they’re unlocked. This is important because we’ll no longer be explicitly calling ssh-add.

Then, I added the following to my .zshrc:

eval $(ssh-agent) > /dev/null
export SSH_ASKPASS=~/ssh-pass-passphrase.bash
alias ssh="setsid -w ssh"
alias git="setsid -w git"

This starts an instance of ssh-agent, then sets the SSH_ASKPASS environment variable. If this variable is set, when ssh needs a password to unlock an SSH key, it runs the script pointed at by the variable and uses the output as the password. Note that this only works if ssh isn’t run in a terminal. That’s the purpose of the aliases for ssh and git: setsid runs these programs in a new shell session that isn’t associated with the terminal in which I’m running ssh or git. The -w flag causes setsid to wait until ssh or git exits before exiting.

~/ssh-pass-passphrase.bash is a script that writes my SSH key’s password to stdout:

#!/bin/bash
pass SSH/Ubuntu

Now, I can run Git commands without worrying if my SSH key is unlocked beforehand. If it isn’t, ssh automatically calls pass to get my SSH key’s password and unlocks the key before Git runs the command.

So far, this setup only works on Ubuntu on my personal computer. My next step is to get it working on Mac OS, so I can use it on my work computer too. After that, I’d like to look into ways to avoid spawning an instance of ssh-agent in every terminal I run this command in.