14

Problem: I have some 20-30 ssh-agent identities. Most servers refuse authentication with Too many failed authentications, as SSH usually won't let me try 20 different keys to log in.

At the moment, I am specifying the identity file for every host manually, using the IdentityFile and the IdentitiesOnly directive, so that SSH will only try one key file, which works.

Unfortunately, this stops working as soon as the original keys aren't available anymore. ssh-add -l shows me the correct paths for every key file, and they match with the paths in .ssh/config, but it doesn't work. Apparently, SSH selects the indentity by public key signature and not by file name, which means that the original files have to be available so that SSH can extract the public key.

There are two problems with this:

  • it stops working as soon as I unplug the flash drive holding the keys
  • it renders agent forwarding useless as the key files aren't available on the remote host

Of course, I could extract the public keys from my identity files and store them on my computer, and on every remote computer I usually log into. This doesn't looks like a desirable solution, though.

What I need is a possibility to select an identity from ssh-agent by file name, so that I can easily select the right key using .ssh/config or by passing -i /path/to/original/key, even on a remote host I SSH'd into. It would be even better if I could "nickname" the keys so that I don't even have to specify the full path.

3
  • 1
    Why do you need many ssh identities? If it is to avoid one compromised private key giving access to all your accounts, then why do you hold all of them on a single flash drive? It is not the first time I hear of problems related to managing multiple ssh identities but never had a chance to ask why they are needed. Jun 24, 2012 at 12:07
  • Never said all of them are on one flash drive.
    – leoluk
    Jun 24, 2012 at 13:14
  • 3
    @DmitriChubarov One possible application for multiple ssh identities is a authorized_keys file that, depending on the key used, executes different commands, without ever permitting direct shell access. Oct 17, 2012 at 17:47

5 Answers 5

11

Guess I'll have to answer my own question, as there doesn't seem to be any way to request an identity by file name.

I wrote a quick-and-dirty Python scripts which creates a public key file in .ssh/fingerprints for every key the agent holds. I can then specify this file, which contains no secret key, using IdentityFile and SSH will pick the right identity from the SSH agent. Works perfectly fine, and allows me to use the agent for as many private keys I wish.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Dumps all public keys held by ssh-agent and stores them in ~/.ssh/fingerprints/, so that
they can be identified using the IdentityFile directive.

"""

import sys, os
import stat
import re
import envoy

RE_MATCH_FILENAME = re.compile(r'([^\\/:*?"<>|\r\n]+)\.\w{2,}$', re.IGNORECASE)

if os.getuid() == 0:
    USERNAME = os.environ['SUDO_USER']
else:
    USERNAME = os.environ['USER']

def error(message):
    print "Error:", message
    sys.exit(1)

def main():
    keylist = envoy.run('ssh-add -L').std_out.strip('\n').split('\n')

    if len(keylist) < 1:
        error("SSH-Agent holds no indentities")

    for key in keylist:
        crypto, ckey, name = key.split(' ')
        filename = os.path.join(os.environ['HOME'], '.ssh/fingerprints',
                  RE_MATCH_FILENAME.search(name).group(1)+'.pub')

        with open(filename, 'w') as f:
            print "Writing %s ..." % filename
            f.write(key)

        envoy.run('chmod 600 %s' % filename)
        envoy.run('chown %s %s' % (USERNAME, filename))


if __name__ == '__main__':
    main()
1
  • 1
    Wow a ten year old answer saved my live... thanks leoluk, if you haven't retired yet.
    – DrD
    Dec 21, 2022 at 15:41
4

Run

ssh-add -L | gawk ' { print $2 > $3 ".pub" } '

on the remote machine to automatically generate all the public key files (assuming the public keys in your .ssh/config are named privateKeyFileName.pub and no inconsitent paths are involved). Call chown $USER .ssh/* for your sudo case.

2

Picking up from the accepted solution, and assuming you just want to reuse the identity used to gain access to the initial server, then something like:

Host github.com
    IdentitiesOnly yes
    IdentityFile ~/.ssh/authorized_keys

is sufficent.

1
  • 1
    this doesn't work for me. When identity file has only one public key - it works, but not when multiple.
    – ygrek
    May 3, 2019 at 16:02
0

One option is to create a small wrapper script for SSH that can forward the correct public key from the SSH agent. Here's a working proof-of-concept:

#!/usr/bin/env bash

# SSH with specific identity that has been previously added to the ssh-agent.
# Public key file need not exist.
# Usage: First argument is the ssh key fingerprint of the identity you want to
#     use. All further arguments will be forwarded to ssh directly.
#     
# Ex: ssh-with-key SHA256:VrzNKaV7VcU7jT7hLZJMxxAo6whPc+VVXKaH0exqXiM user@host

set -euo pipefail

fingerprint="$1"
shift

# List ssh-agent fingerprints and public keys, get public key that corresponds
# to the line number of the fingerprint.
pub_key="$(awk 'line == null && "'"$fingerprint"'" {line=FNR; nextfile}; line == FNR' <(ssh-add -l) <(ssh-add -L))"

# Save public key to temporary file.
# NB: Can't use process substitution here. Bash passes substituted argument as
# a /dev/fd file, and ssh closes all /dev/fd files other than stdin, stdout,
# and stderr before processing CLI options.
pub_key_file=$(mktemp)
echo $pub_key > $pub_key_file

cleanup() {
    rm -f $pub_key_file
}
trap cleanup EXIT

ssh -o IdentitiesOnly=yes -i $pub_key_file "$@"

You could create a separate config file to associate the SSH key fingerprints with one or more hostnames or nicknames for ease of use.

The advantage of this technique is that you don't have to do any upfront or periodic handling of public keys outside of adding them to the SSH agent. Public key files are written as needed and deleted as soon as the session is over.

0

python3 version of the code in leoluks excellent answer:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Dumps all public keys held by ssh-agent and stores them in ~/.ssh/fingerprints/, so that
they can be identified using the IdentityFile directive.

"""

import sys, os
import stat
import re
import subprocess

RE_MATCH_FILENAME = re.compile(r'([^\\/:*?"<>|\r\n]+)(\.\w{2,})?$', re.IGNORECASE)

if os.getuid() == 0:
    USERNAME = os.environ['SUDO_USER']
else:
    USERNAME = os.environ['USER']

def error(message):
    print("Error:", message)
    sys.exit(1)

def main():
    keylist = subprocess.check_output(['ssh-add','-L']).decode().strip('\n').split('\n')

    if len(keylist) < 1:
        error("SSH-Agent holds no indentities")

    for key in keylist:
        crypto, ckey, name = key.split(' ')
        print('name', name)
        filename = os.path.join(os.environ['HOME'], '.ssh/fingerprints',
                  RE_MATCH_FILENAME.search(name).group(1)+'.pub')

        with open(filename, 'w') as f:
            print("Writing %s ..." % filename)
            f.write(key)

        subprocess.call(['chmod', '600', filename])
        subprocess.call(['chown',USERNAME, filename])


if __name__ == '__main__':
    main()

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .