Unlock SSH keys with the 1Password CLI
Like any self-respecting, somewhat security conscious nerd I make sure my SSH keys have a stupidly-long passphrase. Obviously, I use a password manager to store my passwords. But instead of copying and pasting the passphrase from 1Password into the terminal like some kind of 12th-century peasant, let’s walk through how to grab the passphrase out of 1Password automatically.
Note: If you’re running the desktop application, 1Password can actually serve as an SSH Agent. If that setup works for you, then I recommend going that direction instead and stop reading immediately. I spent precious time I’ll never get back figuring out how to do this because I can’t run the desktop application as an SSH agent due to reasons so unimportant you’d instantly die of boredom if I told you.
How SSH_ASKPASS works
Normally ssh-add reads your passphrase straight from the terminal. But pipe something into it and it thinks there’s no terminal, and if DISPLAY and SSH_ASKPASS are set it’ll run whatever program SSH_ASKPASS points to, using the output as the passphrase. This was designed for GUI password dialogs, but nothing stops us from abusing it in the terminal.
The trick is an empty pipe:
echo | SSH_ASKPASS=op_ask_pass ssh-add
ssh-add calls op_ask_pass, reads its output as the passphrase, and we never have to touch the clipboard.
I use two helper scripts for the workflow. op_ask_pass calls op_get_pass with --categories "SSH Key":
#!/usr/bin/env bash
#
# Use 1Password to unlock SSH keys
#
# Usage:
#
# echo | SSH_ASKPASS=op_ask_pass ssh-add
set -euo pipefail
IFS=$'\n\t'
main() {
op_get_pass --categories "SSH Key"
}
main "${@}"
op_get_pass handles signing in to 1Password, uses fzf so you can fuzzy-search through your items, then outputs the password:
#!/usr/bin/env bash
#
# Fuzzy find a password from 1Password and output to stdout
set -euo pipefail
IFS=$'\n\t'
main() {
local -r user_id="$(op account list --format json | jq -r '.[].user_uuid')"
local -r variable_name="OP_SESSION_${user_id}"
local op_session
op_session="$(tmux show-environment -g "${variable_name}" | cut -d= -f2-)"
if ! op whoami --session "${op_session}" &> /dev/null; then
echo "Sign in to 1password first"
op_session="$(op signin --raw)"
tmux set-environment -g "${variable_name}" "${op_session}"
fi
# Select item via FZF and output password
# shellcheck disable=SC2068
op item list --session "${op_session}" ${@} \
| fzf --no-multi --header-lines=1 --nth=1.. --with-nth=2.. --exit-0 \
| cut -d' ' -f1 \
| xargs --no-run-if-empty \
op item get --fields=password --session "${op_session}"
}
main "${@}"
The 1password session token gets added to the tmux global environment, so you only have to sign in once per tmux session.
Copy any password within tmux
op_get_pass isn’t just for SSH keys, I have <prefix> . open a popup to put a password into the tmux paste buffer:
# Copy password from 1Password to tmux paste buffer
if-shell 'command -v op' {
bind-key -N "Copy password to paste buffer" . {
display-popup -T "#[fg=cyan] 1Password" -w 80% -h 80% -E -E \
'bash -c "set -o pipefail; \
op_get_pass | tmux load-buffer - \
&& tmux display-message -N \"Copied to paste buffer\""'
}
}
Yet another super-brittle hacky workflow with obvious security risks all just to avoid using a GUI, success!