One of the great promises of DNSSEC is to provide a new public key infrastructure for authenticating Internet services. If you are a relatively technical person you can try out this brave new future now with ssh.
There are a couple of advantages of getting SSHFP host authentication to work. Firstly you get easier-to-use security, since you longer need to rely on manual host authentication, or better security for the brave or foolhardy who trust leap-of-faith authentication. Secondly, it becomes feasible to do host key rollovers, since you only need to update the DNS - the host's key is no longer wired into thousands of known_hosts files. (You can probably also get the latter benefit using ssh certificate authentication, but why set up another PKI if you already have one?)
In principle it should be easy to get this working but there are a surprising number of traps and pitfalls. So in this article I am going to try to explain all the whys and wherefores, which unfortunately means it is going to be long, but I will try to make it easy to navigate. In the initial version of this article I am just going to describe what the software does by default, but I am happy to add specific details and changes made by particular operating systems.
The outline of what you need to do on the server is:
- Sign your DNS zone. I will not cover that in this article.
- Publish SSHFP records in the DNS
The client side is more involved. There are two versions, depending on whether ssh has been compiled to use ldns or not. Run ldd $(which ssh) to see if it is linked with libldns.
- Without ldns:
- Install a validating resolver (BIND or Unbound)
- Configure the stub resolver /etc/resolv.conf
- Configure ssh
- With ldns:
- Install unbound-anchor
- Configure the stub resolver /etc/resolv.conf
- Configure ssh
Publish SSHFP records in the DNS
Generating SSHFP records is quite straightforward:
demo:~# cd /etc/ssh demo:/etc/ssh# ssh-keygen -r $(hostname) demo IN SSHFP 1 1 21da0404294d07b940a1df0e2d7c07116f1494f9 demo IN SSHFP 1 2 3293d4c839bfbea1f2d79ab1b22f0c9e0adbdaeec80fa1c0879dcf084b72e206 demo IN SSHFP 2 1 af673b7beddd724d68ce6b2bb8be733a4d073cc0 demo IN SSHFP 2 2 953f24d775f64ff21f52f9cbcbad9e981303c7987a1474df59cbbc4a9af83f6b demo IN SSHFP 3 1 f8539cfa09247eb6821c645970b2aee2c5506a61 demo IN SSHFP 3 2 9cf9ace240c8f8052f0a6a5df1dea4ed003c0f5ecb441fa2c863034fddd37dc9
Put these records in your zone file, or you can convert them into an nsupdate script with a bit of seddery:
ssh-keygen -r $(hostname -f) | sed 's/^/update add /;s/ IN / 3600 IN /;/ SSHFP . 1 /d;'
The output of ssh-keygen -r includes hashes in both SHA1 and SHA256 format (the shorter and longer hashes). You can discard the SHA1 hashes.
It includes hashes for the different host key authentication algorithms:
- 1: ssh-rsa
- 2: ssh-dss
- 3: ecdsa
I believe ecdsa covers all three key sizes which OpenSSH gives separate algorithm names: ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521
OpenSSH supports other host key authentication algorithms, but unfortunately they cannot be authenticated using SSHFP records because they do not have algorithm numbers allocated.
The problem is actually worse than that, because most of the extra algorithms are the same as the three listed above, but with added support for certificate authentication. The ssh client is able to convert certificate to plain key authentication, but a bug in this fallback logic breaks SSHFP authentication.
So I recommend that if you want to use SSHFP authentication your server should only have host keys of the three basic algorithms listed above.
NOTE (added 26-Nov-2014): if you are running OpenSSH-5, it does not support ECDSA SSHFP records. So if your servers need to support older clients, you might want to stick to just RSA and DSA host keys.
You are likely to have an SSHFP algorithm compatibility problem if you get the message: "Error calculating host key fingerprint."
NOTE (added 12-Mar-2015): if you are running OpenSSH-6.7 or later, it has support for Ed25519 SSHFP records which use algorithm number 4.
Install a validating resolver
To be safe against active network interception attacks you need to do DNSSEC validation on the same machine as your ssh client. If you don't do this, you can still use SSHFP records to provide a marginal safety improvement for leap-of-faith users. In this case I recommend using VerifyHostKeyDNS=ask to reinforce to the user that they ought to be doing proper manual host authentication.
If ssh is not compiled to use ldns then you need to run a local validating resolver, either BIND or unbound.
If ssh is compiled to use ldns, it can do its own validation, and you do not need to install BIND or Unbound.
Run ldd $(which ssh) to see if it is linked with libldns.
Install a validating resolver - BIND
The following configuration will make named run as a local validating recursive server. It just takes the defaults for everything, apart from turning on validation. It automatically uses BIND's built-in copy of the root trust anchor.
/etc/named/named.conf
options { dnssec-validation auto; dnssec-lookaside auto; };
Install a validating resolver - Unbound
Unbound comes with a utility unbound-anchor which sets up the root trust anchor for use by the unbound daemon. You can then configure unbound as follows, which takes the defaults for everything apart from turning on validation using the trust anchor managed by unbound-anchor.
/etc/unbound/unbound.conf
server: auto-trust-anchor-file: "/var/lib/unbound/root.key"
Install a validating resolver - dnssec-trigger
If your machine moves around a lot to dodgy WiFi hot spots and hotel Internet connections, you may find that the nasty middleboxes break your ability to validate DNSSEC. In that case you can use dnssec-trigger, which is a wrapper around Unbound which knows how to update its configuration when you connect to different networks, and which can work around braindamaged DNS proxies.
Configure the stub resolver - without ldns
If ssh is compiled without ldns, you need to add the following line to /etc/resolv.conf; beware your system's automatic resolver configuration software, which might be difficult to persuade to leave resolv.conf alone.
options edns0
For testing purposes you can add RES_OPTIONS=edns0 to ssh's environment.
On some systems (including Debian and Ubuntu), ssh is patched to force EDNS0 on, so that you do not need to set this option. See the section on RRSET_FORCE_EDNS0 below for further discussion.
Configure the stub resolver - with ldns
If ssh is compiled with ldns, you need to run unbound-anchor to maintain a root trust anchor, and add something like the following line to /etc/resolv.conf
anchor /var/lib/unbound/root.key
Run ldd $(which ssh) to see if it is linked with libldns.
Configure ssh
After you have done all of the above, you can add the following to your ssh configuration, either /etc/ssh/ssh_config or ~/.ssh/config
VerifyHostKeyDNS yes
Then when you connect to a host for the first time, it should go straight to the Password: prompt, without asking for manual host authtentication.
If you are not using certificate authentication, you might also want to disable that. This is because ssh prefers the certificate authentication algorithms, and if you connect to a host that offers a more preferred algorithm, ssh will try that and ignore the DNS. This is not very satisfactory; hopefully it will improve when the bug is fixed.
HostKeyAlgorithms ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-rsa,ssh-dss
Troubleshooting
Check that your resolver is validating and getting a secure result for your host. Run the following command and check for "ad" in the flags. If it is not there then either your resolver is not validating, or /etc/resolv.conf is not pointing at the validating resolver.
$ dig +dnssec <hostname> sshfp | grep flags ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1 ; EDNS: version: 0, flags: do; udp: 4096
See if ssh is seeing the AD bit. Use ssh -v and look for messages about secure or insecure fingerprints in the DNS. If you are getting secure answers via dig but ssh is not, perhaps you are missing "options edns0" from /etc/resolv.conf.
debug1: found 6 secure fingerprints in DNS debug1: matching host key fingerprint found in DNS
Try using specific host key algorithms, to see if ssh is trying to authenticate a key which does not have an SSHFP record of the corresponding algorithm.
$ ssh -o HostKeyAlgorithms=ssh-rsa <hostname>
Summary
What I use is essentially:
/etc/named/named.conf
options { dnssec-validation auto; dnssec-lookaside auto; };
/etc/resolv.conf
nameserver 127.0.0.1 options edns0
/etc/ssh/ssh_config
VerifyHostKeyDNS yes
Background on DNSSEC and non-validating stub resolvers
When ssh is not compiled to use ldns, it has to trust the recursive DNS server to validate SSHFP records, and it trusts that the connection to the recursive server is secure. To find out if an SSHFP record is securely validated, ssh looks at the AD bit in the DNS response header - AD stands for "authenticated data".
A resolver will not set the AD bit based on the security status of the answer unless the client asks for it. There are two ways to do that. The simple way (from the perspective of the DNS protocol) is to set the AD bit in the query, which gets you the AD bit in the reply without other side-effects. Unfortunately the standard resolver API makes it very hard to do this, so it is only simple in theory.
The other way is to add an EDNS0 OPT record to the query with the DO bit set - DO stands for "DNSSEC OK". This has a number of side-effects: EDNS0 allows large UDP packets which provide the extra space needed by DNSSEC, and DO makes the server send back the extra records required by DNSSEC such as RRSIGs.
Adding "options edns0" to /etc/resolv.conf only tells it to add the EDNS0 OPT record - it does not enable DNSSEC. However ssh itself observes whether EDNS0 is turned on, and if so also turns on the DO bit.
Regarding "options edns0" vs RRSET_FORCE_EDNS0
At first it might seem annoying that ssh makes you add "options edns0" to /etc/resolv.conf before it will ask for DNSSEC results. In fact on some systems, ssh is patched to add a DNS API flag called RRSET_FORCE_EDNS0 which forces EDNS0 and DO on, so that you do not need to explicitly configure the stub resolver. However although this seems more convenient, it is less safe.
If you are using the standard portable OpenSSH then you can safely set VerifyHostKeyDNS=yes, provided your stub resolver is configured correctly. The rule you must follow is to only add "options edns0" if /etc/resolv.conf is pointing at a local validating resolver. SSH is effectively treating "options edns0" as a signal that it can trust the resolver. If you keep this rule you can change your resolver configuration without having to reconfigure ssh too; it will automatically fall back to VerifyHostKeyDNS=ask when appropriate.
If you are using a version of ssh with the RRSET_FORCE_EDNS0 patch (such as Debian and Ubuntu) then it is sometimes NOT SAFE to set VerifyHostKeyDNS=yes. With this patch ssh has no way to tell if the resolver is trustworthy or if it should fall back to VerifyHostKeyDNS=ask; it will blindly trust a remote validating resolver, which leaves you vulnerable to MitM attacks. On these systems, if you reconfigure your resolver, you may also have to reconfigure ssh in order to remain safe.
Towards the end of February there was a discussion on an IETF list about stub resolvers and DNSSEC which revolved around exactly this question of how an app can tell if it is safe to trust the AD bit from the recursive DNS server.
One proposal was for the stub resolver to strip the AD bit in replies from untrusted servers, which (if it were implemented) would allow ssh to use the RRSET_FORCE_EDNS0 patch safely. However this proposal means you have to tell the resolver if the server is trusted, which might undo the patch's improved convenience. There are ways to avoid that, such as automatically trusting resolvers running on the local host, and perhaps having a separate configuration file listing trusted resolvers, e.g. those reachable over IPSEC.