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 (barrett@example.com). Enter passphrase: ********
[80]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.
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
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).[81] 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`
[81]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.[82]$ SSH_AUTH_SOCK=/tmp/ssh-barrett/ssh-22841-agent; export SSH_AUTH_SOCK; $ SSH_AGENT_PID=22842; export SSH_AGENT_PID;
[82]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.[83]# 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.
[83]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
Agent pid 8090 killed
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 [1] + 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 [1] + Stopped (SIGTSTP) vi
Method | Pros | Cons |
---|---|---|
eval `ssh-agent` | Simple, intuitive | Must be terminated manually |
ssh-agent $SHELL | Agent's environment variables are propagated automatically; terminates on logout | Your login shell becomes dependent on the agent's health; if the agent dies, your login shell may die |
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`
# 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 4.1.5.13, "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"]
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.[84]$ 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: ********
[84]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:
For OpenSSH, the -l option operates differently, printing the key's fingerprint rather than the public key (see the sidebar "Key Fingerprints" earlier for more detail):$ ssh-add1 -l 1024 35 1604921766775161379181745950571099412502846... and so forth 1024 37 1236194621955474376584658921922152150472844... and so forth $ ssh-add2 -l Listing identities. The authorization agent has one key: id_dsa_1024_a: 1024-bit dsa, smith@client, Thu Dec 02 1999 22:25:09-0500
To print the public key with OpenSSH, use -L instead:# OpenSSH only $ ssh-add -l 1024 1c:3d:cc:1a:db:74:f8:e6:46:6f:55:57:9e:ec:d5:fc smith@client
# OpenSSH only $ ssh-add -L 1024 35 1604921766775161379181745950571099412502846... and so forth 1024 37 1236194621955474376584658921922152150472844... and so forth
If you don't specify a key file, ssh-add1 deletes your default identity from the agent:$ ssh-add -d ~/.ssh/second_id Identity removed: /home/smith/.ssh/second_id (my alternative key) $ ssh-add2 -d ~/.ssh2/id_dsa_1024_a Deleting identity: id_dsa_1024_a.pub
ssh-add2, on the other hand, requires you to specify a key file:$ ssh-add -d Identity removed: /home/smith/.ssh/identity (smith@client)
$ ssh-add2 -d (nothing happens)
$ ssh-add -D All identities removed. $ ssh-add2 -D Deleting all identities.
open(SSHADD,"|ssh-add -p") || die "can't start ssh-add"; print SSHADD $passphrase; close(SSHADD);
The requested operation was denied.
The authorization agent has no keys.
and to unlock:$ ssh-add2 -L Enter lock password: **** Again: ****
Locking is a convenient way to protect the agent if you step away from your computer but leave yourself logged in. You can unload all your keys with ssh-add -D, but then you'd have to reload them again when you return. If you have only one key, there's no difference, but if you use several, it's a pain. Unfortunately, the locking mechanism isn't tremendously secure. ssh-agent2 simply stores the lock password in memory, refusing to honor any more requests until it receives an unlock message containing the same password. The locked agent is still vulnerable to attack: if an intruder gains access to your account (or the root account), he can dump the agent's process address space and extract your keys. The lock feature certainly deters casual misuse, but the potential for an attack is real. If you're seriously concerned about key disclosure, think twice before relying on locking. We prefer to see this feature implemented by encrypting all the agent's loaded keys with the lock password. This gives the same user convenience and provides better protection.$ ssh-add2 -U Enter lock password: ****
# Unload this key after 30 minutes $ ssh-add2 -t 30 mykey
The -F option lets you limit the set of hosts that may make requests relating to this key. It takes as an argument a set of hostnames, domains, and IP addresses that may make or forward requests. The argument is a comma-separated list of wildcard patterns, as for the serverwide configuration keywords AllowHosts and DenyHosts. [Section 5.5.2.3, "Hostname access control"]# Load a key that may be used only locally $ ssh-agent2 -f 0 mykey # Load a key and accept requests from up to 3 hops away $ ssh-agent2 -f 3 mykey
# Permit request forwarding for a key only in the example.com domain $ ssh-agent2 -F '*.example.com' mykey # Permit forwarding from server.example.com and the harvard.edu domain $ ssh-agent2 -F 'server.example.com,*.harvard.edu' mykey # Same as the preceding command, but limit forwarding to 2 hops $ ssh-agent2 -F 'server.example.com,*.harvard.edu' -f 2 mykey
WARNING: SSH1 agents don't support this feature. If you use an SSH2 agent in SSH1 compatibility mode, these forwarding features won't necessarily work.
$ ssh-keygen2 -t rsa my-rsa-key
$ eval `ssh-agent2 -1`
$ ssh-add2 my-rsa-key Enter passphrase: ********
and SSH2 clients:$ ssh-add1 -l 1023 33 753030143250178784431763590... my-rsa-key ...
Now let's unload the key and repeat the experiment:$ ssh-add2 -l Listing identities. The authorization agent has one key: my-rsa-key: 1024-bit rsa, smith@client, Mon Jun 05 2000 23:37:19 -040
This time, load the key using the -1 option, so SSH1 clients don't see it:$ ssh-add2 -D Deleting all identities.
Notice that the key is still visible to SSH2 clients:$ ssh-add2 -1 my-rsa-key Enter passphrase: ********
But SSH1 clients can't see it:$ ssh-add2 -l Listing identities. The authorization agent has one key: my-rsa-key: 1024-bit rsa, smith@client, Mon Jun 05 2000 23:37:19 -040
$ ssh-add1 -l The agent has no identities.
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
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
eval `ssh-agent` ssh-add
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.[85] [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
[85]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.
[86]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.
# On machine X: $ ssh Y
# On machine Y: $ ssh Z
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
[87]SSH2 supports the keyword AllowAgentForwarding as a synonym for ForwardAgent.
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.2. Creating an Identity | 6.4. Multiple Identities |
Copyright © 2002 O'Reilly & Associates. All rights reserved.