What SSH identities will be offered to a remote server and when

January 29, 2016

I've already written an entry on what SSH keys in your .ssh/config will be offered to servers, but it wasn't quite complete and I still managed to confuse myself about this recently. So today I'm going to try to write down in one place more or less everything I know about this.

Assuming that you're using ssh-agent and you don't have IdentitiesOnly set anywhere, the following is what keys will be offered to the remote server:

  1. All keys from ssh-agent, in the order they were loaded into ssh-agent.
  2. The key from a -i argument, if any.
  3. Any key(s) from matching Host or Match stanzas in .ssh/config, in the order they are listed (and matched) in the file. Yes, all keys from all matching stanzas; IdentityFile directives are cumulative, which can be a bit surprising.

    (If there are no IdentityFile matches in .ssh/config, OpenSSH will fall back to the default .ssh/id_* keys if they exist.)

(If you aren't using ssh-agent, only #2 and #3 are applicable and you can pretty much ignore the rest of this entry.)

If there is a 'IdentitiesOnly yes' directive in any matching .ssh/config stanza (whether it is in a 'Host *' or a specific 'Host <whatever>'), the only keys from ssh-agent that will be offered in #1 are the keys that would otherwise be offered in both #2 and #3. Unfortunately IdentitiesOnly doesn't change the order that keys are offered in; keys in ssh-agent are still offered first (in #1) and in the order they were loaded into ssh-agent, not the order that they would be offered in if ssh-agent wasn't running.

Where the 'IdentitiesOnly yes' directive comes from makes no difference, as you'd pretty much expect. The only difference between having it in eg 'Hosts *' versus only (some) specific 'Host <whatever>' entries is how many connections it applies to. This leads to an important observation:

The main effect of a universal IdentitiesOnly directive is to make it harmless to load a ton of keys into your ssh-agent.

OpenSSH servers have a relatively low limit on how many public keys they will let you offer to them; usually it's six or less (technically it's a limit on total authentication 'attempts', which can wind up including eg a regular password). Since OpenSSH normally offers all keys from your ssh-agent, loading too many keys into it can cause authentication problems (how many problems you have depends on how many places you can authenticate to with the first five or six keys loaded). Setting a universal 'IdentitiesOnly yes' means that you can safely load even host-specific keys into ssh-agent and still have everything usable.

(This is the sshd configuration directive MaxAuthTries.)

Note that specifying -i does not help if you're offering too many keys through ssh-agent, because the ssh-agent keys are offered first. You must enable IdentitiesOnly as well, either in .ssh/config or as a command line option. Even this may not be a complete cure if your .ssh/config enables too many IdentityFile directives and those keys are loaded into ssh-agent so that they get offered first.

If the key for -i is loaded into your ssh-agent, OpenSSH will use the ssh-agent version for authentication. This will cause a confirmation check if the key was loaded with 'ssh-add -c' (and yes, this still happens even if the -i key is unencrypted).

(ssh-agent confirmation checks only happen when the key is about to be used to authenticate you, not when it is initially offered to the server.)

PS: you can see what keys you're going to be offering in what order with 'ssh -v -v ...'. Look for the 'debug2: key: ...' lines, and also 'debug1: Offering ...' lines. Note that keys with paths and marked 'explicit' may still come from ssh-agent; that explicit path just means that they're known through an IdentityFile directive.

Sidebar: the drawback of a universal IdentitiesOnly

The short version is 'agent forwarding from elsewhere'. Suppose that you are on machine A, with a ssh-agent collection of keys, and you log into machine B with agent forwarding (for whatever reason). If machine B is set up with up with universal IdentitiesOnly, you will be totally unable to use any ssh-agent keys that machine B doesn't know about. This can sort of defeat the purpose of agent forwarding.

There is a potential half way around this, which is that IdentityFile can be used without the private key file. Given a stanza:

Host *
  IdentitiesOnly yes
  IdentityFile /u/cks/.ssh/ids/key-ed2

If you have a key-ed2.pub file but no key-ed2 private key file, this key will still be offered to servers. If you have key-ed2 loaded into your ssh-agent through some alternate path, SSH can authenticate you to the remote server; otherwise ssh will offer the key, have it accepted by the server, and then discover that it can't authenticate with it because there is no private key. SSH will continue to try any remaining authentication methods, including more identities.

(This is the inverse of how SSH only needs you to decrypt private keys when it's about to use them.)

However, this causes SSH to offer the key all the time, using up some of your MaxAuthTries even in situations where the key is not usable. Unfortunately, as far as I can tell there is no way to tell SSH 'offer this key only if ssh-agent supports it', which is what we really want here.

Written on 29 January 2016.
« Modern Django makes me repeat myself in the name of something
Some good practices for handling OpenSSH keypairs »

Page tools: View Source, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Fri Jan 29 02:24:59 2016
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.