Cover V12, I01

Article
Figure 1
Figure 2
Figure 3
Listing 1
Listing 2
Listing 3
Listing 4
Listing 5
Listing 6
Sidebar
Table 1

jan2003.tar

Improving SSH with Keychain

Todd A. Jacobs

Keychain is a wrapper for OpenSSH that simplifies key management by allowing use of a single authentication agent across virtual consoles and through multiple logins. Initially released in August 2001 for the Gentoo Linux distribution, keychain has matured into an essential tool for anyone who uses RSA or DSA keys for SSH authentication.

To reap the full benefits of this relatively new tool, a basic understanding of SSH is required. SSH (Secure Shell) is a replacement for services that transmit unencrypted data across the network. While it is most often used as a secure alternative to telnet, it can also replace other services such as rlogin, rsh, rexec, and ftp.

In its basic configuration, SSH creates an encrypted session before prompting the user to enter his system password (as defined on the remote system). If a user often connects to a server, this can become repetitive because he will be prompted for a password each time a connection is made. The problem becomes more pronounced if he opens sessions to multiple systems, each with a different password. Using the same password on multiple systems is a security risk, but keeping track of many passwords, and having to constantly re-enter them, is a real bother. SSH addresses these issues through the use of public key cryptography and an authentication agent named ssh-agent.

Introducing ssh-agent

With ssh-agent, a public/private key pair is generated using the ssh-keygen utility. The private key is encrypted with a password that, for security reasons, should differ from the password used for the user's system account. The public key is then uploaded to all the SSH servers with which the client may want to connect, while the private key is retained on the client system and is not shared with anyone.

If server-side support for public key authentication has been enabled, the SSH client will prompt the user for the password to the private key when initiating a connection. A correct password will unlock the secret key, which the SSH client then uses to complete the authentication process.

So far, this doesn't appear to be any different from authenticating with the user's regular password. The connection is initiated, the user is prompted for a password, authentication is performed, and a connection is then established. Even if things are left there, the ssh-agent approach offers two primary benefits:

1. Two-Factor Authentication -- To authenticate, the user needs to possess a token (the private half of the key pair) in addition to a password. This limits the potential for strictly password-based attacks.

2. Uniformity -- A user can safely use the same password to connect to multiple systems. Because of public key cryptography, the token (private key) and password are stored locally, and are never transmitted to the remote system.

However, the benefits don't stop there. Using ssh-agent can also simplify the login process by reducing repetition. It allows the user to cache the decrypted private key on the client side, so that future authentications can take place without prompting the user for a password. When the SSH client attempts a new connection, it will use the credentials provided by ssh-agent to complete the connection. See the sidebar, "A Few Ways to Launch ssh-agent".

Why Keychain

When ssh-agent launches a new process (or if the output of ssh-agent is evaluated in the current shell), it also creates two environment variables: SSH_AUTH_SOCK and SSH_AGENT_PID. The name of the UNIX-domain socket for the authentication agent is stored in SSH_AUTH_SOCK, while the process ID of the agent is held in SSH_AGENT_PID.

The various SSH tools use these two environment variables to communicate with the authentication agent. The ssh-add utility loads secret keys into the authentication agent using the defined socket, and the ssh client uses it again when connecting to a remote host.

The main drawback to using ssh-agent, especially under X, is that the information used to connect to an ssh-agent is stored in the current environment. If you open a new xterm that doesn't inherit its environment from the shell that originally spawned the agent process, an SSH client process won't know how to connect to the appropriate socket. You would need to launch another instance of ssh-agent just for that xterm, and then load your private keys yet again. Even if you run ssh-agent as part of your XDM/KDM/GDM login (see the sidebar), the authentication agent will exit when you log out.

Collectively, these are the problems that keychain addresses. By sharing information about the ssh-agent process with each login, keychain allows all your other processes access to the same authentication agent. You now only need one running agent per user on each machine, rather than one agent per login shell. And because keychain keeps your authentication agent running and accessible even after you log out, you can also use SSH tools from a crontab or at command without having to resort to dangerous tricks, such as creating secret keys with no defined password.

Installing Keychain

If you're ready to try keychain, you must first download the latest source code. At the time of this writing, the most recent version is 2.0.2. The source code can be found on the project's home page at:

http://www.gentoo.org/projects/keychain.html#doc_chap3
Next, extract the files:

bzcat keychain-2.0.2.tar.bz2 | tar xv
and copy the keychain script to an appropriate location in your PATH, such as /usr/local/bin or your home directory.

Keychain is now installed and ready to use. However, to make the best use of keychain, you should modify at least one of your shell configuration files. The name of this file will depend on your shell (see Table 1) and how your shell environment has been configured. In general, the first file for each shell listed is most likely the correct one.

For Bourne-compatible (sh) shells, add the contents of Listing 1 to the file that controls your environment variables (e.g., .bash_profile). C-type shells, such as csh or tcsh, will look almost the same, except for the command and filename used to import the environment variables into the current shell. For C shells, use Listing 2 instead.

These commands run the keychain script, optionally load a list of secret keys into the authentication agent, and then import a set of shared environment variables into the current shell. Keychain supports a number of other command-line arguments, which can be found in the keychain documentation.

Note that there are currently no man or info pages distributed with keychain. The keychain documentation is contained in the script itself, and can be displayed with the --help command-line argument. The documentation is currently 73 lines long, so it's a good idea to pipe the output to a pager such as less or more. For example:

keychain --help | less
How Keychain Works

Now that your login script has been modified, keychain will be invoked whenever a new interactive shell is started. Keychain will first check the process list to see whether an authentication agent is already running under the current user ID. If not, a new agent is started, and the commands to add SSH_AUTH_SOCK and SSH_AGENT_PID to the environment are written to a pair of shell scripts in the .keychain directory -- one for Bourne shells, and one for csh-compatible shells -- so that this information can be shared with other sessions.

If ssh-agent is already running, then the appropriate shell script is sourced to import the existing values for SSH_AUTH_SOCK and SSH_AGENT_PID into the current environment. Remember, this is the secret to keychain: by sharing the values for the process ID and socket for ssh-agent, all login sessions belonging to that user have access to the same authentication agent and so do not require independent copies of the private keys to be reloaded and their passwords to be re-entered.

After having verified that an authentication agent is running for the current user, keychain runs attempts to load any private keys passed as arguments into ssh-agent (see Figure 1). You will be prompted for a password for each secret key in turn until all requested keys have been loaded or the script is aborted with CTRL-C.

Once your keys have been loaded into ssh-agent, keychain returns control to the shell. You can now connect automatically, without password prompting, to any host that has been configured with your public key(s).

At this point, I have covered the essential topics to get you started with keychain. However, there are some additional topics worth mentioning.

Keychain and NFS

The .keychain directory, which contains information about running agents, is located in the user's home directory by default (this can be overridden with the --dir option). In this directory, the filenames generated by keychain include the hostname of the machine to which the user is currently logged in (see Figure 2). Since each host will create its own pair of files in the .keychain directory, keychain is safe to use with NFS-mounted directories.

Key Management

As seen in Listings 1 and 2, keychain can be run with no private keys specified. This is a feature. While keychain certainly makes it easy to load multiple keys when starting an authentication agent, keys can also be added from the command-line after the login process has been completed; this is what keychain itself is doing during the key-loading phase anyway.

Once the socket has been shared, keys can be added to, or removed from, the authentication agent at any time (see Listing 3). The change will instantly affect all keychain-enabled sessions without any need to reinitialize the agent or the login session.

In some environments, it may not be appropriate to leave decrypted keys cached in memory for long periods of time. However, if you kill the authentication agent (see Listing 4), you will have to reinitialize each session in order to share a new agent. The solution is to remove the keys from the agent's cache, while leaving ssh-agent itself running (see Listing 5).

Security and Non-Interactive Key Access

As you can see, since keychain is simply a wrapper around the ssh-agent and ssh-add utilities, most key management tasks can be done either with keychain, or by calling ssh-add directly. However, there is one more value-added benefit to keychain worth mentioning -- interactive login protection.

Historically, if you had a crontab or at command defined that relied on public keys, you had to create a key with no password so that it could be run non-interactively. Keychain allows you to run such non-interactive processes without creating such a vulnerable key.

Since the authentication agent started by keychain continues running after logout, your crontab and at commands have access to any keys you have loaded into ssh-agent. Since keys cached by ssh-agent do not require a password, these non-interactive processes will not hang while waiting for a password. However, the keyfile on disk will remain encrypted, adding a measure of safety.

One vulnerability in this scenario is the potential for abuse by someone who knows your system password. This person could log onto the local system as you, and immediately have access to your authentication agent and, by extension, access to any system for which your agent will authenticate. This is obviously undesirable, so enter the --clear option. By adding --clear to the keychain statement in your login script as follows:

keychain --clear ~/.ssh/key1 ~/.ssh/key2 ~/.ssh/key3
keychain will remove all keys from the running agent, and then attempt to reload the listed keys. This forces all interactive logins to authenticate, while non-interactive logins are unaffected. Use of this option will definitely involve a trade-off in convenience/productivity vs. security and warrants careful consideration.

Problems Under Cygwin

Keychain uses the nohup command to ensure that ssh-agent continues to run even after a login session terminates. However, this does not seem to work properly under Cygwin. While testing keychain for this article, I discovered that attempting to close the first session to call keychain would always hang. Other keychain-enabled Cygwin windows can open and close without problems, but the original window cannot close because of the ssh-agent process running under nohup in the background.

To resolve this problem, I wrote a .bash_logout shell script (see Listing 6) for Cygwin, which prompts the user to stop keychain before logging out, and prints a useful message to the screen that will tell the user what's going on in case the window does hang (see Figure 3).

Conclusion

Keychain adds a layer of convenience and control to the core SSH utilities by building on their existing UNIX-domain socket method of communication. I have found it to be a huge time-saver and an essential tool in my own work, and I hope that this article encourages others to try it.

Todd A. Jacobs, CISSP (articles@codegnome.org) is president of CodeGnome Consulting, LTD (http://codegnome.org). In addition to spending way too much of his free time researching obscure technical topics, he likes to ride his Honda Shadow and practice Tai Chi.