I’ve previously written about SSH and ssh-agent on Mac OS X where I mentioned a utility named SSHKeychain that helps manage the agent daemon and your passphrases. Well, Mac OS X 10.5 (Leopard) has been released since that post, and things have changed. The long and the short of it is that ssh-agent is handled much better than before, by default. But its usage can also be a bit confusing (at least it was for me). I’ll try and explain how it all works in Leopard, so you can get the most out of it.

Why Use a Passphrase, Reprisal

I read a rebuttal or two to my previous post, where I mentioned that using a passphrase protects your private key, if someone gains access to your account. Their main argument was that if your system gets hacked, then the bad guy has complete access to your machine and you’re screwed anyways. True, if someone gets root on your machine, the game is pretty much over. As root, they can do really nasty things like steal ssh-agent identities and install key loggers to snoop your passphrase.

The problem is that root exploits are not the only way to get hacked. What if a bug in a web browser’s Javascript exposes local filesystem access? A nasty web page could read your private key file (~/.ssh/id_rsa), and post it to a website, for example. Or what if you accidentally left your laptop unattended (and unlocked) for a few minutes at the coffee shop? Someone could grab the private key file and stick it on a USB drive. If the attacker was even half way smart, they’d grab ~/.ssh/known_hosts along with your key, since that contains host names you’ve connected to.

Without a passphrase, your private key is completely usable to the bad guy. Using a passphrase encrypts your private key, so that they would have to crack your passphrase to get access to it. In summary, an empty passprhase on your private key is a bad idea. It’s just asking for trouble.

The Über-server

Okay, back to the topic at hand: ssh-agent on Leopard. One of the benefits of SSHKeychain (or one of the other ssh-agent apps for OS X) is that it starts ssh-agent at login time. It also sets the SSH_AUTH_SOCK environment variable (which points to a Unix domain socket) to be accessible by all apps (usually by modifying ~/.MacOSX/environment.plist). Leopard gives you the equivalent of this, out of the box. Open up a terminal and see:

% echo $SSH_AUTH_SOCK 
/tmp/launch-nZRFjA/Listeners

While this environment variable is automatically set for all processes, this is a little deceiving. ssh-agent does not actually get started when you log in. Go ahead and see for you self, by running this command right after you login:

% ps xa | grep ssh-agent | grep -v grep

You should get nothing back. It turns out that ssh-agent gets started on demand, the first time something tries to connect to the socket:

% ps xa | grep ssh-agent | grep -v grep
% ssh-add -l
The agent has no identities.
% ps xa | grep ssh-agent | grep -v grep
10877   ??  S      0:00.02 /usr/bin/ssh-agent -l

The ssh-add -l tried to connect to the socket, and thus caused ssh-agent to launch. Pretty nifty.

But how can the SSH_AUTH_SOCK be valid without ssh-agent? The magic sauce is launchd. launchd was introduced in Mac OS X 10.4 as a replacement for many traditional Unix daemons such as cron, xinetd, and init. Since (x)inetd is known as a super-server, and launchd replaces xinted plus a few other daemons, I like calling launchd an über-server. In this context, launchd creates the Unix domain socket and listens for connections, on behalf of ssh-agent. When something connects, it automatically launches ssh-agent. Since launchd is always running, it can listen for connections right after you login.

For the curious, this is done with the new SecureSocketWithKey plist key for launchd. From launchd.plist(5):

This optional key is a variant of SockPathName. Instead of binding to a known path, a securely generated socket is created and the path is assigned to the environment variable that is inherited by all jobs spawned by launchd.

Rock! The launchd plist file for ssh-agent is at:

/System/Library/LaunchAgents/org.openbsd.ssh-agent.plist

The benefit of this lazy launching is that ssh-agent is only run if you use ssh. If you never use ssh, the agent never gets launched, and you don’t waste any resources running it.

Keychain Integration

But the awesomeness doesn’t stop there. If you have an SSH key and try to connect to a server, you’ll get this nifty dialog box asking for your passphrase:

SSH Leopard dialog

The first benefit of this dialog box is that it uses a secure text field for your passphrase. This field is not copiable and not snoopable via universal access or low-level keyboard routines. The real benefit, though, is the second checkbox: “Remember password in my keychain.” While it does store the passphrase in your keychain, it actually does more than that. It also adds the identity to your ssh-agent for you:

% ssh-add -l
The agent has no identities.
% ssh dave@example.com
... Type passphrase and check "Remember in my keychain" ...
example.com> exit
Connection to example.com closed.
% ssh-add -l
2048 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx /Users/dave/.ssh/id_rsa (RSA)

By adding your identity to the agent, you can now log right back into the same machine, without typing any passphrase. However, ssh does not prompt for you passphrase because it gets it from the agent, not your keychain. Remove your identity from the agent, and try again:

% ssh-add -D
All identities removed.
% ssh dave@example.com

You’ll get that same GUI dialog box again. But it stores your passphrase in your keychain, right? Then why is it asking for your passphrase if it’s in your keychain? And, why would it store the passphrase in your keychain if it’s not going to actually use it? I know I was scratching my head at this point. Well, it does use the keychain, just not how you may think.

It turns at that when ssh-agent is started, it automatically adds all identities that have passphrases stored your keychain. So the normal workflow, where identities are not removed from the agent, just works. Since the GUI dialog box added your identity to the agent when it added the passphrase to your keychain, you don’t need enter your passphrase again for the rest of that login session. For all future sessions, the identity is automatically added when ssh-agent starts. And remember from above, that ssh-agent starts on demand, only when needed. Thus, you only enter your passphrase once, and from then on it grabs it from the keychain.

Manually Adding Identities

If you do want to remove your identities, you can manually add all the identities from the keychain with the Apple-specific -k option on ssh-add. From ssh-add(1):

Add identities to the agent using any passphrases stored in your keychain.

Sweet! Let’s try it out:

% ssh-add -D
All identities removed.
% ssh-add -l
The agent has no identities.
% ssh-add -k
Added keychain identities.
% ssh-add -l
2048 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx /Users/dave/.ssh/id_rsa (RSA)

Beware, the passphrase dialog box is not a GUI version of SSH_ASKPASS, though. Try adding identities without consulting the keychain, and you’ll still get prompted in the terminal:

% ssh-add -D
All identities removed.
% ssh-add 
Enter passphrase for /Users/dave/.ssh/id_rsa: 

And that covers it. I’m a little too paranoid to use this default behavior, though, as I don’t want my passphrase to be stored in my login keychain. Thus, I’ve written a follow-up article that discusses possible ways to beef up security of your passphrase.