How to get OpenSSH to see DNSSEC AD flags on SSHFP lookups with glibc


Update: If you just want to know how to use OpenSSH with SSHFP DNS records, read this.

1. Create the SSHFP record, repeat with DSA key if necessary:

ssh-keygen -r foohost.example.com -f /etc/ssh/ssh_host_rsa_key.pub

2. Put the record into the DNS zone:

foohost.example.com.    A     192.168.1.1
                        SSHFP 1 1 1cee8dde082a7d50b8f46440f4c12a7bcd7c2741

3. In /etc/ssh/ssh_config or ~/.ssh/config, set:

Host *
  VerifyHostKeyDNS yes

4. Test:

ssh -v foohost.example.com
[...]
debug1: found 1 secure fingerprints in DNS

Done.


Note: This article is obsolete. Follow this link for backported RES_USE_DNSSEC patches from glibc 2.11.


OpenSSH can match host keys against fingerprints stored in SSHFP records in DNS and even check for the “Authenticated Data” (AD) flag in DNSSEC-secured lookups. While users of OpenBSD and derivative run-time libraries already benefit from this, glibc doesn’t support it yet. With a single line of inofficial patchery, we can get this bit set, though.

This is a patch for the DNS security-aware user. It does not make your application any more secure than it is now.

The compatible way

The most compatible method is to set the AD flag in the query header. That way, the response won’t contain all the additional DNSSEC-related records that could otherwise confuse an application which does not expect them. Neither the stub resolver nor OpenSSH validate RRSIGs on their own yet, anyway. Also, this method doesn’t need EDNS0 support in the resolver library. The validating recursor simply sets AD in its response header if the answer passed signature validation. OpenSSH tests for AD and can optionally use the result to trust a fingerprint without user interaction.

The RFCs define AD only for DNS responses at this time but IETF work is underway to standardize its use in queries, too. Both BIND and Unbound already implement it.

The downside is that incompatible DNS proxies and packet filters could strip AD flags or drop the packets altogether. If you’re stuck behind such an appliance and cannot run your own recursor, you’re probably out of luck. For further information on DNSSEC compatibility in popular broadband routers and SOHO firewalls, read the results of a study conducted by Nominet and Core Competence: DNSSEC-CPE-Report.pdf (pg. 12, Request Flag Compatibility)

So, my current quick and ugly hack is to unconditionally set AD in all queries sent by the stub resolver by hard-wiring “hp->ad = 1” in  res_nmkquery(). Ideally, the library would allow AD to be set through the use of a resolver option.

This patch “works for me” with Ubuntu Jaunty glibc-2.9-4ubuntu6 and also applies against stock glibc 2.10.1:

[download patch]

--- glibc-2.9/resolv/res_mkquery.c
+++ glibc-2.9-ad/resolv/res_mkquery.c
@@ -141,6 +141,7 @@
    hp->opcode = op;
    hp->rd = (statp->options & RES_RECURSE) != 0;
    hp->rcode = NOERROR;
+   hp->ad = 1;
    cp = buf + HFIXEDSZ;
    buflen -= HFIXEDSZ;
    dpp = dnptrs;

With this patch and “VerifyHostKeyDNS=yes” enabled in SSH config, OpenSSH can trust a host key fingerprint found in DNS:

debug1: found 1 secure fingerprints in DNS
debug1: matching host key fingerprint found in DNS

The True Way

The preferred method would be to use an EDNS0 OPT record and set its DNSSEC OK (DO) bit. This allows the resolver to negotiate a larger maximum response size and also returns all DNSSEC-related records, so that the resolver or application could validate the responses themselves, closing the insecure gap between a stub resolver and its validating recursor.

Unfortunately, glibc’s resolver codes still needs some work before it can make good use of EDNS0 for DNSSEC.

Since glibc 2.6, EDNS0 can be enabled by “options edns0” in /etc/resolv.conf or the RES_OPTIONS environment variable (see manpage) and also by adding RES_USE_EDNS0 to the resolver API options.  But this alone won’t let OpenSSH see the AD flag in DNS responses.

Note: A problem can occur in the interaction of (portable) OpenSSH and glibc, resulting in queries with an invalid UDPsize=0. See this post on the openssh-unix-dev mailing list with a proposed workaround (committed). Also reported to Ubuntu and the glibc bug tracker (fixed).

Also, RES_USE_DNSSEC is not yet defined in resolv.h or honored by the resolver code.

Additionally, the resolver’s default buffer size of 1024 byte is too small if RES_USE_DNSSEC is enabled for all queries. DNSSEC RFCs requires a minimum EDNS buffer size of 1220 bytes (although there is ongoing discourse on this limitation) and should be much larger in practice.  DNSSEC records increase the size of an answer by a sizable amount. Too small a buffer leads to truncated responses that need to be retried over TCP, placing a high burden on the server’s resources.

I addressed the issues with a different patch.

If you think you know what you’re doing and still want to experiment with DO=1 in stub resolver queries right now, add NS_OPT_DNSSEC_OK to the flags in __res_nopt():

[download patch]

--- glibc-2.9/resolv/res_mkquery.c
+++ glibc-2.9-do/resolv/res_mkquery.c
@@ -248,6 +248,7 @@
    *cp++ = NOERROR;    /* extended RCODE */
    *cp++ = 0;        /* EDNS version */
    /* XXX Once we support DNSSEC we change the flag value here.  */
+   flags |= NS_OPT_DNSSEC_OK;
    NS_PUT16(flags, cp);
    NS_PUT16(0, cp);    /* RDLEN */
    hp->arcount = htons(ntohs(hp->arcount) + 1);

Leave a Reply