6.3. SSH AgentsAn SSH agent is a program that caches private keys and responds to authentication-related queries from SSH clients. [Section 2.5, "The SSH Agent"] They are terrific labor-saving devices, handling all key-related operations and eliminating the need to retype your passphrase. The programs related to agents are ssh-agent and ssh-add. ssh-agent runs an agent, and ssh-add inserts and removes keys from the agent's key cache. A typical use might look like this:
By typing your passphrase a single time, you decrypt the private key which is then stored in memory by the agent. From now on, until you terminate the agent or log out, SSH clients automatically contact the agent for all key-related operations. You needn't type your passphrase again. We now briefly discuss how agents work. After that we get practical and illustrate the two ways to start an agent, various configuration options, and several techniques for automatically loading your keys into the agent. Finally, we cover agent security, agent forwarding, and compatibility between SSH-1 and SSH-2 agents.# Start the agent $ ssh-agent $SHELL # Load your default identity $ ssh-add Need passphrase for /home/barrett/.ssh/identity (email@example.com). Enter passphrase: ********
6.3.1. Agents Don't Expose KeysAgents perform two tasks:
Figure 6-4. How an SSH agent works with its clientsFor example, if ssh needs to sign an authenticator, it sends the agent a signing request containing the authenticator data and an indication of which key to use. The agent performs the cryptographic operation itself and returns the signature. In this manner, SSH clients use the agent without ever seeing the agent's private keys. This technique is more secure than handing out keys to clients. The fewer places that private keys get stored or sent, the harder it is to steal them.
This design also fits well with token-based key storage, in which your keys are kept on a smart card carried with you. Examples are the U.S. government-standard Fortezza card or RSA Security's Keon system. Like agents, smart cards respond to key-related requests but don't give out keys, so integration with SSH would be straightforward. Though adoption of tokens has been slow, we believe it will be commonplace in the future.
6.3.2. Starting an AgentThere are two ways to invoke an agent in your login account:
WARNING: Don't invoke an agent with the "obvious" but wrong command:Although the agent runs without complaint, SSH clients can't contact it, and the termination command (ssh-agent -k) doesn't kill it, because some environment variables aren't properly set.$ ssh-agent
18.104.22.168. Single-shell methodThe single-shell method runs an agent in your current login shell. This is most convenient if you're running a login shell on a single terminal, as opposed to a Unix window system such as X. Type:
and an ssh-agent process is forked in the background. The process detaches itself from your terminal, returning a prompt to you, so you needn't run it in the background manually (i.e., with an ampersand on the end). Note that the quotes around ssh-agent are backquotes, not apostrophes. What purpose does the eval serve? Well, when ssh-agent runs, it not only forks itself in the background, it also outputs some shell commands to set several environment variables necessary for using the agent. The variables are SSH_AUTH_SOCK (for SSH1 and OpenSSH) or SSH2_AUTH_SOCK (SSH2), and SSH_AGENT_PID (SSH1, OpenSSH) or SSH2_AGENT_PID (SSH2). The eval command causes the current shell to interpret the commands output by ssh-agent, setting the environment variables. If you omit the eval, these commands are printed on standard output as ssh-agent is invoked. For example:# SSH1, SSH2, OpenSSH $ eval `ssh-agent`
Older versions of SSH1 use SSH_AUTHENTICATION_SOCKET instead of SSH_AUTH_SOCK. If this applies to you, we recommend setting SSH_AUTH_SOCK yourself, for example (in C shell):
Now you've got an agent running but inaccessible to the shell. You can either kill it using the pid printed in the previous output:$ ssh-agent SSH_AUTH_SOCK=/tmp/ssh-barrett/ssh-22841-agent; export SSH_AUTH_SOCK; SSH_AGENT_PID=22842; export SSH_AGENT_PID; echo Agent pid 22842;
or connect your shell manually by setting the environment variables exactly as given:$ kill 22842
Nevertheless, it's easier to use the single-shell form of the command so everything is set up for you.$ SSH_AUTH_SOCK=/tmp/ssh-barrett/ssh-22841-agent; export SSH_AUTH_SOCK; $ SSH_AGENT_PID=22842; export SSH_AGENT_PID;
Why can't ssh-agent set its environment variables without all this trickery? Because under Unix, a program can't set environment variables in its parent shell.To terminate the agent, kill its pid:
and unset the environment variables:# SSH1, SSH2, OpenSSH $ kill 22842
Or for SSH1 and OpenSSH, use the more convenient -k command-line option:$ unset SSH_AUTH_SOCK # SSH2 uses SSH2_AUTH_SOCK instead $ unset SSH_AGENT_PID
This prints termination commands on standard output so the eval can invoke them. If you eliminate the eval, the agent is still killed, but your environment variables don't unset automatically:# SSH1, OpenSSH $ eval `ssh-agent -k`
Running an agent in a single shell, as opposed to the method we cover next (spawning a subshell), has one problem. When your login session ends, the ssh-agent process doesn't die. After several logins, you see many agents running, serving no purpose.# SSH1, OpenSSH $ ssh-agent1 -k unset SSH_AUTH_SOCK # This won't get unset, unset SSH_AGENT_PID # and neither will this, echo Agent pid 22848 killed # but the agent gets killed.
Actually, you can reconnect to an agent launched in a previous login by modifying your SSH_AUTH_SOCK variable to point to the old socket, but this is gross.
You can get around this problem by running ssh-agent -k automatically when you log out. In Bourne style shells (sh, ksh, bash), this may be done with a trap of Unix signal at the top of ~/.profile :$ /usr/ucb/ps uax | grep ssh-agent barrett 7833 0.4 0.4 828 608 pts/1 S 21:06:10 0:00 grep agent barrett 4189 0.0 0.6 1460 844 ? S Feb 21 0:06 ssh-agent barrett 6134 0.0 0.6 1448 828 ? S 23:11:41 0:00 ssh-agent barrett 6167 0.0 0.6 1448 828 ? S 23:24:19 0:00 ssh-agent barrett 7719 0.0 0.6 1456 840 ? S 20:42:25 0:02 ssh-agent
For C shell and tcsh, terminate the agent in your ~/.logout file:# ~/.profile trap ' test -n "$SSH_AGENT_PID" && eval `ssh-agent1 -k` ; test -n "$SSH2_AGENT_PID" && kill $SSH2_AGENT_PID ' 0
Once this trap is set, your ssh-agent process is killed automatically when you log out, printing a message like:# ~/.logout if ( "$SSH_AGENT_PID" != "" ) then eval `ssh-agent -k` endif if ( "$SSH2_AGENT_PID" != "" ) then kill $SSH2_AGENT_PID endif
22.214.171.124. Subshell methodThe second way to invoke an agent spawns a subshell. You provide an argument to ssh-agent, which is a path to a shell or shell script. Examples are:
This time, instead of forking a background process, ssh-agent runs in the foreground, spawning a subshell and setting the aforementioned environment variables automatically. The rest of your login session runs within this subshell, and when you terminate it, ssh-agent terminates as well. This method, as you will see later, is most convenient if you run a window system such as X and invoke the agent in your initialization file (e.g., ~/.xsession). However, the method is also perfectly reasonable for single-terminal logins. When using the subshell method, invoke it at an appropriate time. We recommend the last line of your login initialization file (e.g., ~/.profile or ~/.login) or the first typed command after you log in. Otherwise, if you first run some background processes in your shell, and then invoke the agent, those initial background processes become inaccessible until you terminate the agent's subshell. For example, if you run the vi editor, suspend it, and then run the agent, you lose access to the editor session until you terminate the agent.$ ssh-agent /bin/sh $ ssh-agent /bin/csh $ ssh-agent $SHELL $ ssh-agent my-shell-script # Run a shell script instead of a shell
The advantages and disadvantages of the two methods are shown in Table 6-1.$ vi myfile # Run your editor ^Z # Suspend it $ jobs # View your background processes  + Stopped (SIGTSTP) vi $ ssh-agent $SHELL # Run a subshell $ jobs # No jobs here! They're in the parent shell $ exit # Terminate the agent's subshell $ jobs # Now we can see our processes again  + Stopped (SIGTSTP) vi
Table 6-1. Pros and Cons of Invoking an Agent
126.96.36.199. Format of environment variable commandsAs we've said, ssh-agent prints a sequence of shell commands to set several environment variables. The syntax of these commands differs depending on which shell is being used. You can force the commands to use Bourne-style or C shell-style syntax with the -s and -c options, respectively:
Normally ssh-agent detects your login shell and prints the appropriate lines, so you don't need -c or -s. One situation where you need these options is if you invoke ssh-agent within a shell script, but the script's shell is not the same type as your login shell. For example, if your login shell is /bin/csh, and you invoke this script:# Bourne-shell style commands $ ssh-agent -s SSH_AUTH_SOCK=/tmp/ssh-barrett/ssh-3654-agent; export SSH_AUTH_SOCK; SSH_AGENT_PID=3655; export SSH_AGENT_PID; echo Agent pid 3655; # C-shell style commands $ ssh-agent -c setenv SSH_AUTH_SOCK /tmp/ssh-barrett/ssh-3654-agent; setenv SSH_AGENT_PID 3655; echo Agent pid 3655;
ssh-agent outputs C shell-style commands, which will fail. So you should use:#!/bin/sh `ssh-agent`
This is particularly important if you run an agent under X, and your ~/.xsession file (or other startup file) is executed by a shell different from your login shell.#!/bin/sh `ssh-agent -s`
188.8.131.52. SSH-1 and SSH-2 agent compatibilityAn SSH-1 agent can't service requests from SSH-2 clients. The reverse, however, is supported by SSH2. If ssh-agent2 is invoked with the -1 option (that's a numeral one, not a lowercase L), the agent services requests from SSH-1 clients, even from ssh-add1. This works only for SSH-2 implementations that support RSA, because SSH-1 uses RSA keys. At press time, only F-Secure SSH2 Server is capable of SSH-1 agent compatibility.
# Invoke an SSH2 agent in SSH1 compatibility mode $ eval `ssh-agent2 -1` # Add an SSH1 key $ ssh-add1 Need passphrase for /home/smith/.ssh/identity (smith SSH1 key). Enter passphrase: **** Identity added (smith SSH1 key). # Add an SSH2 key $ ssh-add2 Adding identity: /home/smith/.ssh2/id_dsa_1024_a.pub Need passphrase for /home/smith/.ssh2/id_dsa_1024_a (1024-bit dsa, smith SSH2 key, Thu Dec 02 1999 22:25:09-0500). Enter passphrase: ******** # ssh-add1 lists only the SSH1 key $ ssh-add1 -l 1024 37 1425047358166328978851045774063877571270... and so forth
Now an SSH-1 client contacts ssh-agent2 transparently, believing it to be an SSH-1 agent:# ssh-add2 lists both keys # F-Secure SSH Server only $ ssh-add2 -l Listing identities. The authorization agent has 2 keys: id_dsa_1024_a: 1024-bit dsa, smith SSH2 key, Thu Dec 02 1999 22:25:09-0500 smith SSH1 key
ssh-agent2 achieves compatibility by setting the same environment variables normally set by ssh-agent1: SSH_AUTH_SOCK and SSH_AGENT_PID. Therefore, any SSH-1 agent requests are directed to ssh-agent2.$ ssh1 server.example.com [no passphrase prompt appears]
WARNING: If you have an ssh-agent1 process running, and you invoke ssh-agent2 -1, your old ssh-agent1 process becomes inaccessible as ssh-agent2 overwrites its environment variables.Agent compatibility works only if the SSH2 distribution is compiled with the flag -- with-ssh-agent1-compat. [Section 184.108.40.206, "SSH-1/SSH-2 agent compatibility"] It also depends on the value of the client configuration keyword Ssh1AgentCompatibility. [Section 7.4.14, "SSH1/SSH2 Compatibility"]
6.3.3. Loading Keys with ssh-addThe program ssh-add is your personal communication channel to an ssh-agent process. (Again, this command may be ssh-add1 under SSH1 and ssh-add2 under SSH2, with ssh-add a link to one program or the other.) When you first invoke an SSH agent, it contains no keys. ssh-add, as you might guess from its name, can add private keys to an SSH agent. But the name is misleading, because it also controls the agent in other ways, such as listing keys, deleting keys, and locking the agent from accepting further keys. If you invoke ssh-add with no arguments, your default SSH key is loaded into the agent, once you have typed its passphrase. For example:
Normally, ssh-add reads the passphrase from the user's terminal. If the standard input isn't a terminal, however, and the DISPLAY environment variable is set, ssh-add instead invokes an X window graphical program called ssh-askpass that pops up a window to read your passphrase. This is especially convenient in xdm start-up scripts.$ ssh-add1 Need passphrase for /home/smith/.ssh/identity (smith@client). Enter passphrase: ******** Identity added: /home/smith/.ssh/identity (smith@client). $ ssh-add2 Adding identity: /home/smith/.ssh2/id_dsa_1024_a.pub Need passphrase for /home/smith/.ssh2/id_dsa_1024_a (1024-bit dsa, smith@client, Thu Dec 02 1999 22:25:09-0500). Enter passphrase: ********
X has its own security problems, of course. If someone can connect to your X server, they can monitor all your keystrokes, including your passphrase. Whether this is an issue in using ssh-askpass depends on your system and security needs.Both ssh-add1 and ssh-add2 support the following command-line options for listing and deleting keys, and for reading the passphrase:
220.127.116.11. Automatic agent loading (single-shell method)It's a pain to invoke ssh-agent and/or ssh-add manually each time you log in. With some clever lines in your login initialization file, you can automatically invoke an agent and load your default identity. We demonstrate this with both methods of agent invocation, single-shell and subshell. With the single-shell method, here are the major steps:
For the C shell and tcsh, the following lines can be placed into ~/.login:# Make sure ssh-agent1 and ssh-agent2 die on logout trap ' test -n "$SSH_AGENT_PID" && eval `ssh-agent1 -k` ; test -n "$SSH2_AGENT_PID" && kill $SSH2_AGENT_PID ' 0 # If no agent is running and we have a terminal, run ssh-agent and ssh-add. # (For SSH2, change this to use SSH2_AUTH_SOCK, ssh-agent2 and ssh-add2.) if [ "$SSH_AUTH_SOCK" = "" ] then eval `ssh-agent` /usr/bin/tty > /dev/null && ssh-add fi
and termination code in ~/.logout :# Use SSH2_AUTH_SOCK instead for SSH2 if ( ! $?SSH_AUTH_SOCK ) then eval `ssh-agent` /usr/bin/tty > /dev/null && ssh-add endif
# ~/.logout if ( "$SSH_AGENT_PID" != "" ) eval `ssh-agent -k` if ( "$SSH2_AGENT_PID" != "" ) kill $SSH2_AGENT_PID
18.104.22.168. Automatic agent loading (subshell method)The second way to load an agent on login uses the subshell method to invoke the agent. This time, you need to add lines to both your login initialization file (~/.profile or ~/.login), an optional second file of your choice, and your shell initialization file (~/.cshrc, ~/.bashrc, etc.). This method doesn't work for the Bourne shell, which has no shell initialization file.
This runs the agent, spawning a subshell. If you want to tailor the environment of the subshell, create a script (say, ~/.profile2) to do so, and use this instead:test -n "$SSH_AUTH_SOCK" && exec ssh-agent $SHELL
Next, in your shell initialization file ($ENV for ksh, or ~/.bashrc for bash), place the following lines to load your default identity only if it's not loaded already:test -n "$SSH_AUTH_SOCK" && exec ssh-agent $SHELL $HOME/.profile2
# Make sure we are attached to a tty if /usr/bin/tty > /dev/null then # Check the output of "ssh-add -l" for identities. # For SSH2, use the line: # ssh-add2 -l | grep 'no keys' > /dev/null # ssh-add1 -l | grep 'no identities' > /dev/null if [ $? -eq 0 ] then # Load your default identity. Use ssh-add2 for SSH2. ssh-add1 fi fi
22.214.171.124. Automatic agent loading (X Window System)If you're using X and want to run an agent and load your default identity automatically, it's simple. Just use the single-shell method. For example, in your X startup file, usually ~/.xsession, you can use these two lines:
eval `ssh-agent` ssh-add
6.3.4. Agents and SecurityAs we mentioned earlier, agents don't expose private keys to SSH clients. Instead, they answer requests from clients about the keys. This approach is more secure than passing keys around, but it still has some security concerns. It is important to understand these concerns before completely trusting the agent model:
126.96.36.199. Access controlWhen your agent is loaded with private keys, a potential security issue arises. How does your agent distinguish between legitimate requests from your SSH clients and illegitimate requests from unauthorized sources? Surprisingly, the agent does not distinguish at all. Agents don't authenticate their clients. They will respond to any well-formed request received over their IPC channel, which is a Unix domain socket. How is agent security maintained then? The host operating system is responsible for protecting the IPC channel from unauthorized access. For Unix, this protection is accomplished by the file permissions on the socket. SSH1 and SSH2 keep your agent sockets in a protected directory, /tmp/ssh-USERNAME, where USENRAME is your login name, while OpenSSH names the directory /tmp/ssh-STRING, where STRING is random text based on the agent's pid. In either case, the directory is protected from all other users (mode 700) and owned by you:
In this case, user smith has several agent-related sockets in this directory. The two sockets owned by smith were created by agents run and owned by smith. The third, which is world-writable and owned by root, was created by the SSH server to effect an agent forwarding. [Section 6.3.5, "Agent Forwarding"]$ ls -la /tmp/ssh-smith/ drwx------ 2 smith smith 1024 Feb 17 18:18 . drwxrwxrwt 9 root root 1024 Feb 17 18:01 .. srwx------ 1 smith smith 0 May 14 1999 agent-socket-328 s-w--w--w- 1 root root 0 Feb 14 14:30 ssh-24649-agent srw------- 1 smith smith 0 Dec 3 00:34 ssh2-29614-agent
Even though this socket is world-writable, only user smith can access it due to the permissions on the parent directory, /tmp/ssh-smith.This organization of a user's sockets into a single directory is not only for neatness but also for security and portability, because different operating systems treat socket permissions in different ways. For example, Solaris appears to ignore them completely; even a socket with permission 000 (no access for anyone) accepts all connections. Linux respects socket permissions, but a write-only socket permits both reading and writing. To deal with such diverse implementations, SSH keeps your sockets in a directory owned by you, with directory permissions that forbid anyone else to access the sockets inside. Using a subdirectory of /tmp, rather than /tmp itself, also prevents a class of attacks called temp races. A temp-race attack takes advantage of race conditions inherent in the common setting of the "sticky" mode bit on the Unix /tmp directory, allowing anyone to create a file there, but only allowing deletion of files owned by the same uid as the deleting process.
188.8.131.52. Cracking an agentIf the machine running your agent is compromised, an attacker can easily gain access to the IPC channel and thus to your agent. This permits the interloper to make requests of the agent, at least for a time. Once you log out or unload your keys from the agent, the security hole is closed. Therefore, you should run agents only on trusted machines, perhaps unloading your keys (ssh-agent -D) if you're away from the computer for an extended time, such as overnight. Since agents don't give out keys, your keys would seem safe from theft if the machine is compromised. Alas, that's not the case. An enterprising cracker, once logged into the machine, has other means for getting your keys, such as:
6.3.5. Agent ForwardingSo far, our SSH clients have conversed with an SSH agent on the same machine. Using a feature called agent forwarding, clients can also communicate with agents on remote machines. This is both a convenience feature -- permitting your clients on multiple machines to work with a single agent -- and a means for avoiding some firewall-related problems.
184.108.40.206. A firewall exampleSuppose you want to connect from your home computer, H, to a computer at work, W. Like many corporate computers, W is behind a network firewall and not directly accessible from the Internet, so you can't create an SSH connection from H to W. Hmm... what can you do? You call technical support and for once, they have good news. They say that your company maintains a gateway or "bastion" host, B, that is accessible from the Internet and runs an SSH server. This means you should be able to reach W by opening an SSH connection from H to B, and then from B to W, since the firewall permits SSH traffic. Tech support gives you an account on the bastion host B, and the problem seems to be solved... or is it? For security reasons, the company permits access to its computers only by public-key authentication. So, using your private key on home machine H, you successfully connect to bastion host B. And now you run into a roadblock: also for security reasons, the company prohibits users from storing SSH keys on the exposed bastion host B, since they can be stolen if B were hacked. That's bad news, since the SSH client on B needs a key to connect to your work account on W. Your key is at home on H. (Figure 6-5 illustrates the problem.) What now?
Figure 6-5. Bastion host scenarioNotice that this problem doesn't arise with telnet or rsh. You'd simply type your password to reach W (insecurely, of course). For a solution, we turn to SSH agents and agent forwarding.
This key-distribution problem can also be solved with network file-sharing protocols, such as NFS, SMB, or AFP, but these aren't usually available in the remote-access situation we're discussing.SSH agent forwarding allows a program running on a remote host, such as B, to access your ssh-agent on H transparently, as if the agent were running on B. Thus, a remote SSH client running on B can now sign and decrypt data using your key on H as shown in Figure 6-6. As a result, you can invoke an SSH session from B to your work machine W, solving the problem.
Figure 6-6. Solution with SSH agent forwarding
220.127.116.11. How agent forwarding worksAgent forwarding, like all SSH forwarding (Chapter 9, "Port Forwarding and X Forwarding"), works "behind the scenes." In this case, an SSH client has its agent requests forwarded across a separate, previously established SSH session, to an agent holding the needed keys, shown in Figure 6-7. The transmission takes place over a secure SSH connection, of course. Let's examine, in detail, the steps that occur.
Figure 6-7. How agent forwarding works
You will see all keys that are loaded in your agent on machine X. It's worth noting that the agent-forwarding relationship is transitive: if you repeat this process, making a chain of SSH connections from machine to machine, then clients on the final host will still have access to your keys on the first host (X). (This assumes, of course, that agent forwarding is permitted by sshd on each intermediate host.)# On machine Y: $ ssh-agent -l
18.104.22.168. Enabling agent forwardingBefore an SSH client can take advantage of agent forwarding, the feature must be turned on. SSH implementations vary in their default settings of this feature, and of course the system administrator can change it. If necessary, you can turn it on manually with the configuration keyword ForwardAgent in the client configuration file ~/.ssh/config, giving a value of yes (the default) or no:
Likewise, you can use command-line options. In addition to the -o command-line option, which accepts any configuration keyword and its value:# SSH1, SSH2, OpenSSH ForwardAgent yes
The ssh option -a turns off agent forwarding:# SSH1, SSH2, OpenSSH $ ssh -o "ForwardAgent yes" ...
In addition, ssh2 and OpenSSH's ssh accept options to turn on agent forwarding, even though it's on by default:# SSH1, SSH2, OpenSSH $ ssh -a ...
# SSH2 only $ ssh2 +a ... # OpenSSH only $ ssh -A ...
6.3.6. Agent CPU UsageBefore we leave our discussion of agents, we'll make one final note about performance. Agents carry out all cryptographic work that would otherwise be done by SSH clients. This means an agent can accumulate substantial CPU time. In one case we saw, some friends of ours were using SSH1 for a great deal of automation, running hundreds of short-lived SSH sessions in a row. Our friends were quite puzzled to find that the single ssh-agent used by all these processes was eating the lion's share of CPU on that machine.
Copyright © 2002 O'Reilly & Associates. All rights reserved.