SSHFP records: A different way to check host keys

The JHU ACM has many machines that users might want to SSH into. The JHU ACM also occasionally reinstalls those machines for various reasons, which changes their SSH keys. And I’m usually not near the office anymore to verify for myself that the keys I’m seeing are correct.

The ACM happens to do DNSSEC on their domain, though, and I run a validating resolver, so I can use SSHFP records as a trusted source of SSH fingerprints.

Using SSHFP records

On my end I added this line to ~/.ssh/config:

VerifyHostKeyDNS yes

And ssh checks the key it sees against SSHFP records (falling back to known_hosts if there aren’t any), giving the usual loud warning about any discrepancies it may come across.

(It even knows the difference between a DNSSEC-authenticated response and the other kind, and it will still ask you whether the key is OK if the matching SSHFP record is unauthenticated and the key isn’t in known_hosts. You can make it ask even on authenticated SSHFP records by setting VerifyHostKeyDNS to ask instead.)

That’s all well and good… what about the other end?

ssh-keygen can generate SSHFP records for a given key file when run with the -r option. We wrapped it in a script, which you can run on the machine in question to get a zonefile snippet (and which only looks at public key files, and so doesn’t need to be run as root):

#!/bin/sh
# Generate a zone file snippet for SSHFP records for the host this is run on.

set -e

# This is in increasing order of algorithm ID in the SSHFP records.
for algo in rsa dsa ecdsa ed25519; do
  keyfile="/etc/ssh/ssh_host_${algo}_key.pub"
  if test -f "$keyfile"; then
    if ! ssh-keygen -r "`hostname`" -f "$keyfile" | awk '
    {
      # Do not bother with SHA1 fingerprints - only process SHA256 ones.
      # Reformat the lines so the whitespace matches common zone file layout.
      if ($5 == "2")
        print $1 (length($1) < 8 ? "\t" : "") "\t" $2 "\t" $3 "\t" $4, $5, $6
    }' | grep SSHFP  # the grep is just to check whether there was any output
    then
      if test x"$algo" = xed25519; then
        echo "; placeholder for SSHFP record for `hostname` ed25519 key"
        # Complain on stderr about ed25519 SSHFP records not yet being supported
        # so the output of this script can be sensibly directed into a zone file.
        cat >&2 <<"EOF"

There is an ed25519 key, but ssh-keygen did not turn it into an SSHFP
record. It is probably not a new enough version to support doing so -
if this is the case, remember to regenerate the records once it is.
EOF
      else
        echo "unable to generate SSHFP record for $keyfile" >&2
        # ssh-keygen puts some error messages on stdout (shame!), so re-run
        # the failing invocation to get that output and throw away stderr.
        ssh-keygen -r "`hostname`" -f "$keyfile" 2>/dev/null
        exit 1
      fi
    fi
  fi
done

(Remove the if ($5 == "2") if you also want SHA1 SSHFPs, which older versions of ssh might need. The ugly conditionals involving ed25519 are because at the time the script was written, Debian stable’s OpenSSH didn’t support SSHFP for ed25519, Debian testing’s did, we had machines running both, and the OpenSSH tools could stand to do much better when it comes to error exit codes and to what goes to stdout and what goes to stderr. The script is also available as /afs/acm.jhu.edu/group/admins.pub.ro/scripts/gen-sshfp-records.)

Since we would occasionally forget to update the SSHFP records when reinstalling a machine (or when the key changed for some other reason), I wrote a Nagios plugin for checking SSHFP records, which the ACM now uses. It checks that all key types offered by the server have SSHFP records, that every SSHFP record goes to a key type the server offers, and that every SSHFP record is correct.

Leave a Reply

Your email address will not be published. Required fields are marked *