2020-02-08 - A few days ago I updated this page with information about how to set this up on Catalina. While I was thinking about it I happened across this article which accomplishes the same overall goal, but instead of disabling the macOS com.openssh.ssh-agent
LaunchAgent, it creates a symlink with whatever name $SSH_AUTH_SOCK
contains, pointing to $HOME/.gnupg/S.gpg-agent.ssh
. This is a LOT easier to set up, so this is what I'm doing with my own machines as I upgrade them to Catalina.
If you're standing at the console of a CentOS 7 machine and need to use your YubiKey to authenticate outbound SSH connections...
sudo yum install gnupg2-smime pcsc-lite
sudo systemctl start pcscd
eval $( gpg-agent --daemon --enable-ssh-support )
export SSH_AUTH_SOCK="$( gpgconf --list-dirs agent-ssh-socket )"
Now you should be good to go.
The two obvious dependencies are an SSH client, and gnupg
. One or both of these are usually installed on most Linux and macOS machines.
Most Linux distros come with openssh
already installed, however some distros may split the client and server bits into separate packages. Some distros may install gnupg
as well - if not, you should be able to use yum
, apt-get
, or a similar command, to install the necessary packages. Search
yum install openssh-clients gnupg2 gnupg2-smime
apt-get install (names?)
I don't have the exact commands for every other distro out there. For gnupg
you should search for packages with names like gnupg
, gpg2
, or maybe just gpg
.
For macOS, the openssh
client is installed as a basic part of the OS, however gnupg
is not. You should visit https://gpgtools.org/ and install the current version of GPG Suite. Not only will this give you the gpg
command line tools, but it also includes Mail.app support for signing and encrypting email, and a System Preferences widget to control some aspects of how gpg
and gpg-agent
work.
To make the current shell use gpg-agent
(and therefore the YubiKey) instead of the normal ssh-agent
...
Make sure the GPG_TTY
variable is set.
export GPG_TTY=$(tty)
Make sure that the SSH_AUTH_SOCK
variable points to the S.gpg-agent.ssh
socket.
unset SSH_AGENT_PID
export SSH_AUTH_SOCK="$( gpgconf --list-dirs agent-ssh-socket )"
Any commands executed in this shell will use gpg-agent
as the SSH agent.
To make sure that your shell always sets the GPG_TTY
and SSH_AUTH_SOCK
variables correctly, add the following to your .bash_profile
(or the appropriate file, if your login shell is not bash
)
########################################
# Set things up for using gpg-agent
export GPG_TTY=$(tty)
function use-gpg-agent-for-ssh {
SOCK="$( gpgconf --list-dirs agent-ssh-socket )"
if [[ -n "${SOCK:-}" ]]
then
unset SSH_AGENT_PID
export SSH_AUTH_SOCK="$SOCK"
fi
}
use-gpg-agent-for-ssh
This creates a function to "do the work", and then calls that function. This way if you decide you don't want this all the time, you can comment out just the function call (the last line), and then type
use-gpg-agent-for-ssh
in a shell to easily "activate" the change within that shell.
Once you have added this, every new interactive shell will use the changes. A quick way to test it is to open a new terminal window, which will contain a new shell. Once you have verified that it's working, you can either close the shell you're working in and open a new window, or you can run "source ~/.bash_profile
" to read the updated profile into the current shell.
Note that setting the variables in this way will only affect shells and any processes started from those shells. In particular, it will NOT affect processes started by something other than your shell, such as cron jobs.
The process is the same as the "shell, per-user" process above, except that instead of editing your ~/.bash_profile
file...
You will edit /etc/profile
, so that all users see it.
On some systems you may be able to create an /etc/profile.d/use-gpg-agent-for-ssh.sh
file instead.
If your system has multiple users, and some of them may want to use the normal ssh-agent
, you may want to not include calling the function (i.e. the final use-gpg-agent-for-ssh
line) in what you add to the system-wide profile. In this case, users who do want to use gpg-agent
by default can add a user-gpg-agent-for-ssh
line to their ~/.bash_profile
, and anybody on the system can manually type that command to use gpg-agent
within that shell.
NOTE: macOS 10.15 "Catalina" changes a lot of things under the covers. Most of the changes are good things from a security standpoint, however they go out of their way to make it impossible to prevent the standard ssh-agent
LaunchAgent from starting, or more specifically, from overriding the SSH_AUTH_SOCK
variable before the desktop environment starts. This means that under Catalina, you cannot use SSH keys stored on a Yubikey with other programs (such as BBEdit), unless those apps have ways to override environment variables (like a .bashrc
file, but that only works for shells.)
$ launchctl stop com.openssh.ssh-agent
$ launchctl disable gui/$(id -u)/com.openssh.ssh-agent
$ launchctl unload /System/Library/LaunchAgents/com.openssh.ssh-agent.plist
/System/Library/LaunchAgents/com.openssh.ssh-agent.plist: Operation not permitted while System Integrity Protection is engaged
macOS comes with a LaunchAgent which does two things:
Creates a UNIX socket with a dynamic name, and sets things up so that ssh-agent
is listening on on that socket.
Exports an SSH_AUTH_SOCK
environment variable whose value is the path to that dynamically generated socket.
We are going to create our own LaunchAgent which replaces that socket with a symbolic link pointing to the socket where gpg-agent
's SSH agent listens. By doing this, any client which uses the $SSH_AUTH_SOCK
value to connect to an SSH agent, will end up talking to gpg-agent
.
NOTE: I'll finish writing this at some point over the next few days. For now I'll just include the relevant part of the list of commands I run on a machine after upgrading it to Catalina.
cd ~/Library/LaunchAgents
curl -O https://jms1.net/yubikey/net.jms1.gpg-agent.plist
curl -O https://jms1.net/yubikey/net.jms1.gpg-agent-symlink.plist
reboot
Download links:
net.jms1.gpg-agent.plist
- starts gpg-agent
net.jms1.gpg-agent-symlink.plist
- replaces the UNIX socket with a symlinkssh-agent
I no longer use the process described in this section, however I'm leaving it here in case anybody is interested, or if something changes in the future and I end up needing to go back to this process.
The first thing we need to do is disable the ssh-agent
that Apple installs with macOS. However, before we can do this, we need to disable SIP (System Integrity Protection).
Utilities → Terminal
csrutil disable
exit
Press ⌘-Q to exit the Terminal and return to the menu.
→ Restart
ssh-agent
LaunchAgentOpen a command line window (Terminal, iTerm2, etc.)
Run csrutil status
and make sure it says "disabled".
Disable, unload, and remove the LaunchAgent.
launchctl disable gui/$(id -u)/com.openssh.ssh-agent
launchctl unload /System/Library/LaunchAgents/com.openssh.ssh-agent.plist
sudo mv /System/Library/LaunchAgents/com.openssh.ssh-agent.plist /var/root/
If you're running Catalina and the last command fails, re-mount the root filesystem in read-write mode (this can only be done while SIP is disabled) and try it again.
sudo mount -uw /
sudo mv /System/Library/LaunchAgents/com.openssh.ssh-agent.plist /var/root/
Utilities → Terminal
csrutil enable
exit
Press ⌘-Q to exit the Terminal and return to the menu.
→ Restart
After the machine restarts normally, make sure it worked.
$ launchctl list com.openssh.ssh-agent
This should say that it can't find the servce.
$ env | grep SSH
You should not see an SSH_AUTH_SOCK
variable (and probably won't see any output at all, just another command prompt.)
gpg-agent
Quick version:
cd ~/Library/LaunchAgents
curl -O https://jms1.net/yubikey/net.jms1.gpg-agent.plist
curl -O https://jms1.net/yubikey/net.jms1.gpg-agent-env.plist
launchctl load net.jms1.gpg-agent.plist
launchctl load net.jms1.gpg-agent-env.plist
gpg-agent
start automaticallyCreate $HOME/Library/LaunchAgents/net.jms1.gpg-agent.plist
with the following contents: (adjust the path to gpg-connect-agent
as needed)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>net.jms1.gpg-agent</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<false/>
<key>ProgramArguments</key>
<array>
<string>/usr/local/MacGPG2/bin/gpg-connect-agent</string>
<string>/bye</string>
</array>
</dict>
</plist>
Tell launchd
to use it.
launchctl load net.jms1.gpg-agent.plist
SSH_AUTH_SOCK
variable automaticallyCreate $HOME/Library/LaunchAgents/net.jms1.gpg-agent-env.plist
with the following contents: (adjust the path to the socket file as needed)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>net.jms1.gpg-agent-env</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<false/>
<key>ProgramArguments</key>
<array>
<string>/bin/launchctl</string>
<string>setenv</string>
<string>SSH_AUTH_SOCK</string>
<string>/Users/jms1/.gnupg/S.gpg-agent.ssh</string>
</array>
</dict>
</plist>
Tell launchd
to use it.
launchctl load net.jms1.gpg-agent-env.plist
You will need to either reboot, or log out and log back in, in order to activate these changes.
After the machine restarts normally, make sure it worked.
Make sure the variable exists, with the correct value.
$ env | grep SSH
SSH_AUTH_SOCK=/Users/jms1/.gnupg/S.gpg-agent.ssh
You should see an SSH_AUTH_SOCK
variable, pointing to the path listed in the net.jms1.gpg-agent-env.plist
file.
Make sure the agent is reachable.
$ gpg-connect-agent -v /bye
gpg-connect-agent: closing connection to agent
You should just see the message shown above.
gpg-agent
macOS 10.13 uses OpenSSH_7.5p1, LibreSSL 2.5.4
. Either SSH support is enabled by default, or Apple has configured it to always run by default. Your OS may have done the same, you should probably start by checking whether SSH support is already enabled. To do so:
ps auxww | grep gpg-agent
, find the PIDlsof -nP -p PID
~/.gnupg/S.gpg-agent.ssh
If you need to enable SSH support, or want to go through the exercise to ensure that SSH support is explicitly enabled:
Add this line to ~/.gnupg/gpg-agent.conf
enable-ssh-support
If you changed the file, you should restart the running gpg-agent
process:
gpg-connect-agent killagent /bye
gpg-connect-agent /bye
sshcontrol
fileRef: http://karl.kornel.us/2016/04/sshcontrol/
THIS STEP IS NOT NECESSARY, at least not for gpg-agent (GnuPG/MacGPG2) 2.2.0
.
Find the "keygrip" for the authentication key. (A "keygrip" is another form of fingerprint on the public key plus options, see http://lists.gnupg.org/pipermail/gnupg-users/2012-July/045115.html for details.)
$ gpg --list-keys --with-keygrip 6353320118E1DEA2F38EAE806B2EDC90B5C6DC30
...
pub rsa4096/0x6B2EDC90B5C6DC30 2017-05-27 [SC]
6353320118E1DEA2F38EAE806B2EDC90B5C6DC30
Keygrip = 05D34BB61EA379C3B05817A753FD21E2DA6231E1
uid [ultimate] John M. Simpson <jms1@voalte.com>
uid [ultimate] John M. Simpson <jms1@jms1.net>
sub rsa4096/0x297E5961AB566594 2017-05-27 [E]
Keygrip = 4530A5E8BBB0F6A604E847AB21FBC29D1E44B293
sub rsa4096/0xBA6C2A169C6C0F60 2017-11-10 [A]
Keygrip = CD8EEE908C11D30706FC1BB704066180F2F0196E
The value we want is the keygrip of the authentication sub-key, since that was uploaded to the YubiKey.
Add a line to ~/.gnupg/sshcontrol
containing that value, with "0
" after it (this is a TTL value, not really sure how this works). Comments can be added using "#".
# YubiKey 2017-11-10
CD8EEE908C11D30706FC1BB704066180F2F0196E 0
If you've gone through the setup process above, and the SSH_AUTH_SOCK
variable points to the S.gpg-agent.ssh
socket, you don't really need to do anything differently - just use ssh
, scp
, sftp
, or whatever, the same way you already do. As long as your SSH client works with an agent, it should "just work".
If you haven't gone through the steps above ... do so.
authorized_keys
Getting the public key line needed for authorized_keys
files is very easy:
Make sure gpg-agent
is running, and the current shell is configured to use it.
$ export GPG_TTY=$(tty)
If you have NOT done the setup procedure to ensure that ssh
always uses gpg-agent
...
$ unset SSH_AGENT_PID
$ export SSH_AUTH_SOCK="$( gpgconf --list-dirs agent-ssh-socket )"
Insert the YubiKey, wait a few seconds, and run ssh-add -L
. The card's key will have cardno:
with the YubiKey's serial number as the comment.
$ ssh-add -L
ssh-rsa AAAAB3NzaC1yc...9toFRmxejrbw== cardno:000606940447
http://www.dowdandassociates.com/blog/content/howto-set-an-environment-variable-in-mac-os-x-launchd-plist/ - shows how to create a .plist
which runs launchctl setenv
during login (since launchctl setenv
doesn't appear to persist across reboots.)
Login Script - this script is executed as root, during every user's login process, before the user is able to interact with their account.
gpg-agent
, I just thought it was interesting and decided to make a note of it.The gpg-agent
automatically "contains" the Authentication Keys stored on the YubiKeys (or other OpenPGP cards) present on the system. When gpg-agent
receives an authentication request, it passes it along to the YubiKey, which does the work of signing the request without sending the secret key anywhere.
Other keys can be added to the agent using ssh-add
as expected. When you do this, a copy of the secret key will be written to a file in the ~/.gnupg/private-keys-v1.d/
directory, named after the "key grip" (another kind of fingerprint, which includes the options rather than just the public key). These files are encrypted using a passphrase which may or may not be the same as the key's normal passphrase. When you add a key, you will be prompted first for the existing passphrase (to read the secret key), and then for a new passphrase (to encrypt the secret key in this new file).
However, ssh-add -d
(or -D
) will not remove them (at least, it won't make them stop appearing in ssh-add -l
output.) gpg-agent
adds the key grips (similar to a Key ID) to a file called ~/.gnupg/sshcontrol
. Removing the keygrip from this file makes the key no loger appear in the "ssh-add -l
" output, and no longer be available for SSH authentication. (Removing the ~/.gnupg/sshcontrol
file will make ALL keys no longer appear in the "ssh-add -l
" output.) Note that editing or removing this file does not remove the private-keys-v1.d
file - you will need to remove those by hand.