{"id":200,"date":"2015-11-12T22:06:59","date_gmt":"2015-11-13T03:06:59","guid":{"rendered":"https:\/\/stump.io\/blog\/?p=200"},"modified":"2015-11-12T22:06:59","modified_gmt":"2015-11-13T03:06:59","slug":"sshfp-records-a-different-way-to-check-host-keys","status":"publish","type":"post","link":"https:\/\/stump.io\/blog\/2015\/11\/12\/sshfp-records-a-different-way-to-check-host-keys\/","title":{"rendered":"SSHFP records: A different way to check host keys"},"content":{"rendered":"<p>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&#8217;m usually not near the office anymore to verify for myself that the keys I&#8217;m seeing are correct.<\/p>\n<p>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.<\/p>\n<h1>Using SSHFP records<\/h1>\n<p>On my end I added this line to <code>~\/.ssh\/config<\/code>:<\/p>\n<pre>VerifyHostKeyDNS yes<\/pre>\n<p>And <code>ssh<\/code> checks the key it sees against SSHFP records (falling back to <code>known_hosts<\/code> if there aren&#8217;t any), giving\u00a0the usual loud warning about any discrepancies it may come across.<\/p>\n<p>(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&#8217;t in <code>known_hosts<\/code>. You can make it ask even on authenticated SSHFP records by setting <code>VerifyHostKeyDNS<\/code> to <code>ask<\/code> instead.)<\/p>\n<h1>That&#8217;s all well and good&#8230; what about the other end?<\/h1>\n<p><code>ssh-keygen<\/code> can generate SSHFP records for a given key file when run with the <code>-r<\/code> 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&#8217;t need to be run as root):<\/p>\n<pre>#!\/bin\/sh\r\n# Generate a zone file snippet for SSHFP records for the host this is run on.\r\n\r\nset -e\r\n\r\n# This is in increasing order of algorithm ID in the SSHFP records.\r\nfor algo in rsa dsa ecdsa ed25519; do\r\n  keyfile=\"\/etc\/ssh\/ssh_host_${algo}_key.pub\"\r\n  if test -f \"$keyfile\"; then\r\n    if ! ssh-keygen -r \"`hostname`\" -f \"$keyfile\" | awk '\r\n    {\r\n      # Do not bother with SHA1 fingerprints - only process SHA256 ones.\r\n      # Reformat the lines so the whitespace matches common zone file layout.\r\n      if ($5 == \"2\")\r\n        print $1 (length($1) &lt; 8 ? \"\\t\" : \"\") \"\\t\" $2 \"\\t\" $3 \"\\t\" $4, $5, $6\r\n    }' | grep SSHFP  # the grep is just to check whether there was any output\r\n    then\r\n      if test x\"$algo\" = xed25519; then\r\n        echo \"; placeholder for SSHFP record for `hostname` ed25519 key\"\r\n        # Complain on stderr about ed25519 SSHFP records not yet being supported\r\n        # so the output of this script can be sensibly directed into a zone file.\r\n        cat &gt;&amp;2 &lt;&lt;\"EOF\"\r\n\r\nThere is an ed25519 key, but ssh-keygen did not turn it into an SSHFP\r\nrecord. It is probably not a new enough version to support doing so -\r\nif this is the case, remember to regenerate the records once it is.\r\nEOF\r\n      else\r\n        echo \"unable to generate SSHFP record for $keyfile\" &gt;&amp;2\r\n        # ssh-keygen puts some error messages on stdout (shame!), so re-run\r\n        # the failing invocation to get that output and throw away stderr.\r\n        ssh-keygen -r \"`hostname`\" -f \"$keyfile\" 2&gt;\/dev\/null\r\n        exit 1\r\n      fi\r\n    fi\r\n  fi\r\ndone<\/pre>\n<p>(Remove the <code>if ($5 == \"2\")<\/code> if you also want SHA1 SSHFPs, which older versions of <code>ssh<\/code> might need. The ugly conditionals involving ed25519 are because at the time the script was written, Debian stable&#8217;s OpenSSH didn&#8217;t support SSHFP for ed25519, Debian testing&#8217;s did, we had machines running both, and the OpenSSH tools could stand to do <em>much<\/em> 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\u00a0<code>\/afs\/acm.jhu.edu\/group\/admins.pub.ro\/scripts\/gen-sshfp-records<\/code>.)<\/p>\n<p>Since we\u00a0would occasionally forget to update the SSHFP records when reinstalling a machine (or when the key changed for some other reason), I wrote <a href=\"https:\/\/github.com\/stump\/check_sshfp\" target=\"_blank\">a Nagios plugin for checking SSHFP records<\/a>, 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.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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&#8217;m usually not near the office anymore to verify for myself that the keys I&#8217;m seeing are correct. The ACM happens to do DNSSEC on [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[7],"tags":[],"_links":{"self":[{"href":"https:\/\/stump.io\/blog\/wp-json\/wp\/v2\/posts\/200"}],"collection":[{"href":"https:\/\/stump.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/stump.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/stump.io\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/stump.io\/blog\/wp-json\/wp\/v2\/comments?post=200"}],"version-history":[{"count":11,"href":"https:\/\/stump.io\/blog\/wp-json\/wp\/v2\/posts\/200\/revisions"}],"predecessor-version":[{"id":220,"href":"https:\/\/stump.io\/blog\/wp-json\/wp\/v2\/posts\/200\/revisions\/220"}],"wp:attachment":[{"href":"https:\/\/stump.io\/blog\/wp-json\/wp\/v2\/media?parent=200"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/stump.io\/blog\/wp-json\/wp\/v2\/categories?post=200"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/stump.io\/blog\/wp-json\/wp\/v2\/tags?post=200"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}