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.