+++
title = "DNSSEC + DANE: Part 2"
date = "2013-09-15"
tags = ["security", "technical", "DANE"]
+++

#### Setting up DNSSEC + DANE ( + SSHFP )

Assuming you've been convinced that it's a good idea to set up DNSSEC and DANE,
the point of this article is to demonstrate how I did it for my own domain - 
the individual steps to get from nothing to valid DANE records weren't very
difficult; just not documented in a recipe-style guide anywhere. Hopefully,
this will help you get set up. I'm using Debian Squeeze or Wheezy throughout,
depending on host, but the instructions should be similar for most Linux
distributions.

#### DNSSEC

This is the part that provides the hierarchical trust model, enabling a random
user of your site to trust (more or less, anyway) that when they ask for a
record that tells them which certificates are valid for their site, they
get the same record that you're going to upload later.
  

##### Resolving nameserver

Firstly, the user needs to be able to make DNSSEC-validatable DNS queries to
begin with. This requires that their caching (also known as resolving) nameserver
supports DNSSEC queries. This is easy enough to test:

    lupine@den:~$ dig +dnssec mozilla.org

    ; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> +dnssec mozilla.org
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25143
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 4, ADDITIONAL: 7

    ;; OPT PSEUDOSECTION:
    ; EDNS: version: 0, flags: do; udp: 4096
    ;; QUESTION SECTION:
    ;mozilla.org.			IN	A

    ;; ANSWER SECTION:
    mozilla.org.		60	IN	A	63.245.217.105
    mozilla.org.		60	IN	RRSIG	A 7 2 60 20131013124658 20130913125405 17933 mozilla.org. k2LOpTkl35qIPmFKVQix87mItL2ycPFTymx0yoZoIt+jpsGhEbQWgiiV FXndEwOKap/RsXdHtzWWWI4vcDdQgES0X/XInAxRKTadceapQ34Nyb0w TN9CpYidxpI35MY9cseZVu9eCKXq0M7VxpSBKSHshby2A/hymJntq1lD sSI=
    mozilla.org.		60	IN	RRSIG	A 7 2 60 20131013125201 20130913125405 63920 mozilla.org. N/dNbs71T0oEAJ0ulqeVPg4ty7UwG02QKOFr3tRy0kDpnRsPvIKX8E0e lVxCU/TCEckfS8QQv3JytoOrIwKt/Y1lOI//NuxLIZT8RndMvWaROkrt Ncs3moQAsD6w0sT+Yn7wx1AimVO4udQ8dh3lyYCKHdRq8VfxyK6/5Lws tzQ=

    ;; AUTHORITY SECTION:
    mozilla.org.		60	IN	NS	ns2.mozilla.org.
    mozilla.org.		60	IN	NS	ns1.mozilla.org.
    mozilla.org.		60	IN	RRSIG	NS 7 2 60 20131013125024 20130913125405 17933 mozilla.org. MlltXDEKazn80b3mMqGSOhCCqeQhuiIsgMXI+kaAABnwXyxzHsli+BEL f1AC3Grog3p9DLtRUPbAm3RWIF6HWgd5gJJ5rcw+50ihWVEwQceWniKD Sl/13G7V8pKR0P4GZjpTg//Go4H6xYZAThhU544zjxis5ytupM+rAW0I +ho=
    mozilla.org.		60	IN	RRSIG	NS 7 2 60 20131013125355 20130913125405 63920 mozilla.org. KnOTFZRq6f3K6wbfa6YMjVROHc6kr+RzvthX531H7AQjejB0yAc6ttyI q9J3u/cDg2sdsmROJ91JXkmU7Kjq+LJKrRedQPwY0xLr57ODK/87D3Kv Z9icf5HxarvdN4FlPb7j/uI8EIN4jKXb08976KtPu7BT+6o+1b+rwUWf Ccc=

    ;; ADDITIONAL SECTION:
    ns1.mozilla.org.	60	IN	A	63.245.215.5
    ns2.mozilla.org.	60	IN	A	63.245.218.7
    ns1.mozilla.org.	60	IN	RRSIG	A 7 3 60 20131013124618 20130913125405 63920 mozilla.org. e1mdvK7ERSuaNIxSf1O+8vyFJWoGBGGPSFt20KLiF+KBU1siDlywTTBr /UT5cNBB4prqcZ0DdFagnmWE2OploEqof0Nl/IiSPwVGy8eGksGmS0Qf zK78emWv4nQmVkiVokcZqIHiAXPxG9ZafJaTo/BGtnThILmatdnk2xuI JdY=
    ns1.mozilla.org.	60	IN	RRSIG	A 7 3 60 20131013125230 20130913125405 17933 mozilla.org. 1wWdtXpmOk9oOwzl8j8Jvz2IyqfVXIMfB9kDRC0AUKQNvUDk85Xp6AfE 2i4vaupFRa5RTKKj4gBTYRqfObhdrJHLNIRx1BMb/mb/B/8IF0HuxXeU IlGU8Wu/GbDHOHrS42Z3i2w9Y+DVUI1JQQlPHapDtD20kzKnClIN9iSa FRo=
    ns2.mozilla.org.	60	IN	RRSIG	A 7 3 60 20131013125059 20130913125405 17933 mozilla.org. WcnS3dw6gQ6gM5dP6tKGK+Gwkd3u8AMco2WCU3WzLoK0ADeJo9qjYGzd pSnJLRRMfiKBeWZJvm6g89sS+gPQh1IlncPp6AaGQdAAyl+OtwIswA/n qPQLlWBdJQrfAnzLKDXbOjTH2K9vXxNSUyAL5QzUgLIAB16oTvREbL42 bIc=
    ns2.mozilla.org.	60	IN	RRSIG	A 7 3 60 20131013125237 20130913125405 63920 mozilla.org. V2xTFK6cG9v+mBKbZP7a5yXFJUaXKAt1qOP0VmHWrP1n5lNfvcOMrKLc g4vpaxdbA0M1B7xMhX4ps2IYljAUZdzkBCMXp+bYKPKXdkxKRmXsnspF 7Fii5N9q7FKyhLEbsW8G9MRTScE0ohu5s8db6hOGmkcbyvZJmk5+R1Qd aAk=

    ;; Query time: 285 msec
    ;; SERVER: 213.138.102.177#53(213.138.102.177)
    ;; WHEN: Sat Sep 14 16:54:58 2013
    ;; MSG SIZE  rcvd: 1492

    lupine@den:~$ 

If you see RRSIG records, as above, then you don't need to do anything. If you
don't, then your resolver doesn't support DNSSEC. This is fairly common. As a
first resort, ask your provider (normally your ISP) to fix it. If that doens't
bear fruit, or if you're impatient, you can install and use the
[Unbound](http://unbound.net/][Unbound) resolver.

I was in the latter situation, and my router happens to run a hacked-up version
of Debian Squeeze, so I installed Unbound on it and configured the DHCP server
to refer to it when configuring clients; so every machine on my home network
now has access to a DNSSEC-capable resolver. You can also install and use it
locally, which might look like this:

    root@den:~# apt-get install unbound # unbound-anchor # for wheezy
    root@den:~# echo "nameserver 127.0.0.1" > /etc/resolv.conf
    root@den:~# chattr +i /etc/resolv.conf

The resolv.conf file can be managed and altered in a number of ways - I can't
actually recommend altering it to point to the Unbound instance you just
installed and making it immutable. If your desktop environment manages DHCP
for you, then you should investigate options for providing the DNS manually.
Debian also has the `resolveconf` package which would allow you to specify
static fragments to go into resolv.conf. If you're old-fashioned and are
using static configuation + /etc/network/interfaces, then the dns-nameservers
directive will let you specify 127.0.0.1 - your local Unbound instance.

** Browser (and other application) support

Now that you can get DNSSEC records from your resolver, through means fair or
foul, you need client application support. Firefox has a
[plugin](https://os3sec.org/) or [two](https://www.dnssec-validator.cz/)
that also support DANE; the equivalent 
[Chrome plugin](https://chrome.google.com/webstore/detail/dnssec-validator/hpmbmjbcmglolhjdcbicfdhmgmcoeknm)
only supports DNSSEC. Internet Explorer is probably Right Out, and I have no
idea about Opera, Safari, and the rest. Another option is to install the
[Bloodhound](https://www.dnssec-tools.org/wiki/index.php/Bloodhound) browser.
Apparently.

Web browsers aren't the only applications that could make use of DNSSEC and
DANE, of course. Mail and XMPP are two other important protocols; Thunderbird
has no DNSSEC plugin at the moment, as far as I'm aware, and neither does Gajim
or Pidgin. Let me know if you're aware of any replacements that do - there's
obviously work to be done when it comes to client support. The more servers
support DNSSEC, the more pressure there is on client applications to support
it, of course. For now, open this web page on your DNSSEC-capable browser and
ensure that the DNSSEC plugin is happy.

##### Domain

Now that you've got a client environment that can handle DNSSEC records, it's
time to look at getting your own domain DNSSEC-signed. I'll be using lupine.me.uk
as an example throughout; you need to pick (or register) a domain from a
[DNSSEC-supporting registry](http://dnssec-deployment.org/), and you should
ensure that it's with a registrar that allows you to upload so-called DNSKEY
records to that registry. For me, the answers were ".me.uk" (now ".gs") and
"gandi" - they may be different for you.
  
##### Authoritative nameserver

Once you've got your domain, you need to decide how you're going to serve DNS
with it, in general. I was lazy and just set up my DNS server on the same machine
as the website - that's not generally appropriate for production, but a common
deployment is to have a DNS master on the same machine as the website, with
geographically-diverse slave servers doing zone transfers over AXFR. I'll just
look at sorting out one nameserver - a.ns.lupine.me.uk - though.

The best authoritative nameserver - by far - for DNSSEC support is
[PowerDNS](https://www.powerdns.com/][PowerDNS).
It handles all the difficult details that, if I'm quite honest, I don't really
understand. Debian Squeeze includes version 2.9, and DNSSEC support comes in
the 3.x series, so I installed the 3.3 static package available on the
[website](https://www.powerdns.com/downloads.html) and installed it.
Wheezy backports, and Debian Jessie, are both easier to deal with.

PowerDNS is fairly configurable, particularly for backends; I used its sqlite3
backend, and setting it up for that looks like this:

    root@oak:/etc/powerdns/pdns.d# cat 00-sqlite3-backend.conf 
    launch=gsqlite3
    gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
    gsqlite3-dnssec=yes
    
The pdns.sqlite3 file is autogenerated when you restart PowerDNS, but it lacks
certain schema elements that are necessary for DNSSEC. You can add them by
running the commands detailed
[here](http://doc.powerdns.com/html/gsqlite.html#idp36763616)
- for completeness, they're duplicated below.

    root@oak:~# sqlite3 /var/lib/powerdns/pdns.sqlite3
    sqlite> alter table records add ordername      VARCHAR(255);
    sqlite> alter table records add auth bool;
    sqlite> create index orderindex on records(ordername);
    sqlite> create table domainmetadata (
                id        INTEGER PRIMARY KEY,
                domain_id INT NOT NULL,
                kind      VARCHAR(16) COLLATE NOCASE,
                content   TEXT
            );
    sqlite> create index domainmetaidindex on domainmetadata(domain_id);
    sqlite> create table cryptokeys (
                id        INTEGER PRIMARY KEY,
                domain_id INT NOT NULL,
                flags     INT NOT NULL,
                active    BOOL,
                content   TEXT
            );       
    sqlite> create index domainidindex on cryptokeys(domain_id);           
    sqlite> create table tsigkeys (
                id        INTEGER PRIMARY KEY,
                name      VARCHAR(255) COLLATE NOCASE,
                algorithm VARCHAR(50) COLLATE NOCASE,
                secret    VARCHAR(255)
            );
    sqlite> create unique index namealgoindex on tsigkeys(name, algorithm);

Now add some ordinary DNS records for PowerDNS to serve:

    sqlite> insert into domains (name, type) VALUES('lupine.me.uk', 'NATIVE');
    sqlite> select id from domains where name = 'lupine.me.uk';
    1 # This may be different for you - I set domain_id below to it
    # Set your own SOA serial value according to what you prefer
    sqlite> insert into records (domain_id, name, type, content, ttl) VALUES(
                1, 'lupine.me.uk', 'SOA', 'a.ns.lupine.me.uk nick.lupine.me.uk 1378936223', 3600
            );
    sqlite> insert into records (domain_id, name, type, content, ttl) VALUES(
                1, 'lupine.me.uk', 'NS', 'a.ns.lupine.me.uk', 3600
            );
    sqlite> insert into records (domain_id, name, type, content, ttl) VALUES(
                1, 'a.ns.lupine.me.uk', 'A', '213.138.100.8', 3600
            );
    sqlite> insert into records (domain_id, name, type, content, ttl) VALUES(
                1, 'lupine.me.uk', 'MX', 'lupine.me.uk', 3600
            );
    sqlite> insert into records (domain_id, name, type, content, ttl) VALUES(
                1, 'www.lupine.me.uk', 'CNAME', 'lupine.me.uk', 3600
            );
    sqlite> insert into records (domain_id, name, type, content, ttl) VALUES(
                1, '*.chat.lupine.me.uk', 'CNAME', 'lupine.me.uk', 3600
            );
    sqlite> insert into records (domain_id, name, type, content, ttl) VALUES(
                1, '_xmpp-client._tcp.lupine.me.uk', 'SRV', '0 5222 lupine.me.uk', 3600
            );
    sqlite> insert into records (domain_id, name, type, content, ttl) VALUES(
                1, '_xmpp-server._tcp.lupine.me.uk', 'SRV', '0 5269 lupine.me.uk', 3600
            );

At this point, the PowerDNS server will respond to DNS requests, but they're
not DNSSEC-signed. Enabling DNSSEC for the domain is as simple as:

    root@oak:~# pdnssec secure-zone lupine.me.uk
    Securing zone with rsasha256 algorithm with default key size
    Zone lupine.me.uk secured
    root@oak:~# pdnssec set-nsec3 lupine.me.uk
    NSEC3 set, please rectify-zone if your backend needs it
    root@oak:~# pdnssec rectify-zone lupine.me.uk
    Adding NSEC3 hashed ordering information for 'lupine.me.uk'
    root@oak:~# pdnssec check-zone lupine.me.uk
    Checked 14 records of 'lupine.me.uk', 0 errors, 0 warnings.
    root@oak:~# pdnssec show-zone lupine.me.uk
    Zone is not presigned
    Zone has hashed NSEC3 semantics, configuration: 1 0 1 ab
    keys: 
    ID = 1 (KSK), tag = 7450, algo = 8, bits = 2048	Active: 1 ( RSASHA256 ) 
    KSK DNSKEY = lupine.me.uk IN DNSKEY 257 3 8 [...] ; ( RSASHA256 )
    DS = lupine.me.uk IN DS 7450 8 1 [...] ; ( SHA1 digest )
    DS = lupine.me.uk IN DS 7450 8 2 [...] ; ( SHA256 digest )
    DS = lupine.me.uk IN DS 7450 8 3 [...] ; ( GOST R 34.11-94 digest )
    DS = lupine.me.uk IN DS 7450 8 4 [...] ; ( SHA-384 digest )
    ID = 2 (ZSK), tag = 15433, algo = 8, bits = 1024	Active: 1 ( RSASHA256 ) 
    root@oak:~# 

Now we have a signed DNSSEC zone. If you check the SQLite3 database, you'll
see new records have been generated to match the DNSKEY and DS records displayed
by the show-zone command, and the records you've added will have had various
bits of mysterious glue added. The finer points of DNSSEC are still lost on
me, but the important thing to note is that the "KSK DNSKEY" is the important
record that allows the chain of trust to be developed; this record is given
to the upstream zone via your registry (the ".me.uk" zone for me), who sign
it with their key. It is rotated every year or so, and you need to inform
the registry whenever it changes; you can have multiple active ones at once.
PowerDNS has some documentation on key management best practices
[here](http://doc.powerdns.com/html/dnssec-operational-doctrine.html),
but I've not needed to fuss with any of this, yet.

So, take your DNSKEY record (or possibly DS record - different registrars
apparently might ask you for different things) and give it to your registrar.
Gandi has a neat "Enable DNSSEC" form you can use; others may vary. 

Once they have the record, you're ready to  change the nameservers for the
domain to point to the DNS server you've just set up. I did this in gandi's
panel, and additional hoops I needed to jump through (because the nameserver
was in the lupine.me.uk zone) included notifying Nominet of the "a.ns.lupine.me.uk"
name, as well as notifying them of the "glue" between the name and its IP
addresses. This varies quite considerably by registry and registrar, so I'll
leave it as an exercise to the reader.

#### DANE

Now that we have a DNSSEC-signed zone, we can add records to it, as defined by
RFC 6698. Unless someone is able to compromise the DNS trust anchor, your
registry's keys, or your keys, anyone looking these records up can be confident
that they are the ones you uploaded.

##### Getting a certificate

If you already have a self-signed or CA-issued certificate that you intend to
use, then great. If not, you can either buy one from a CA, or become your own
mini-CA and issue one for yourself. I'm sticking with a CA-issued one for the
next few months, because although DNSSEC has poor client support, DANE support
is entirely non-existent; so the value of a non-CA-certified certificate is
still almost nil. Using a CA-issued certificate (mine is from StartSSL, and
was free) in conjunction with DANE is OK - DANE-aware clients will detect
traditionally-MitM'd certificates from such a record - but you miss out on 
a couple of benefits. Specifically, you're still dependent on the CA to support
sensible (or new/experimental) key types, and if you let the CA generate the
private key rather than going the CSR route (don't do this, ever) then you're
trusting them not to keep a record of what it was.

I may talk about how to generate a self-signed certificate here in the future.
  
##### Generating records

Once you've got your certificate and configured your various services to use
it (HTTPS especially, but also XMPP, IMAPS, SSMTP, etc), it's time to link
it all together in the DNS. Generating the records (which are known as TLSA
records) is a pain, but there is a tool - called [swede](https://github.com/pieterlexis/swede][swede) -
to do it for you. It's Python, only works against HTTPS, and you'd get and
use it like this:

      lupine@den:~/Development$ git clone https://github.com/pieterlexis/swede
      Cloning into 'swede'...
      remote: Counting objects: 116, done.
      remote: Compressing objects: 100% (55/55), done.
      remote: Total 116 (delta 67), reused 107 (delta 59)
      Receiving objects: 100% (116/116), 21.83 KiB, done.
      Resolving deltas: 100% (67/67), done.
      lupine@den:~/Development$ cd swede
      lupine@den:~/Development/swede$ sudo apt-get install python-unbound python-argparse python-ipaddr python-m2crypto
      # [...]
      lupine@den:~/Development/swede$ ./swede create --output rfc lupine.me.uk
      No certificate specified on the commandline, attempting to retrieve it from the server lupine.me.uk.
      Attempting to get certificate from 213.138.100.8
      M2Crypto does not support SNI: services using virtual-hosting will show the wrong certificate!
      Got a certificate with Subject: /description=z3YBHiV5NCKOeIZs/C=GB/CN=www.lupine.me.uk/emailAddress=postmaster@lupine.me.uk
      _443._tcp.lupine.me.uk. IN TLSA 1 0 1 9730ccc0952f3150bc3c640aedb364bd628bc1738ada89826624d9442589eb06

That last line is the TLSA record that identfies your certificate. Even though
swede only supports HTTPS, you can change _443 to _5222 and you've got an XMPP
record - so let's add a sensible set of TLSA records for this certificate to
DNS.

    root@oak:~# sqlite3 /var/lib/powerdns/pdns.sqlite3
    sqlite> insert into records (domain_id, name, type, content, ttl) VALUES (
                1, '_443._tcp.lupine.me.uk', 'TLSA', '1 0 1 9730ccc0952f3150bc3c640aedb364bd628bc1738ada89826624d9442589eb06', 3600
            );
    sqlite> insert into records (domain_id, name, type, content, ttl) VALUES (
                1, '_993._tcp.lupine.me.uk', 'TLSA', '1 0 1 9730ccc0952f3150bc3c640aedb364bd628bc1738ada89826624d9442589eb06', 3600
            );
    sqlite> insert into records (domain_id, name, type, content, ttl) VALUES (
                1, '_5222._tcp.lupine.me.uk', 'TLSA', '1 0 1 9730ccc0952f3150bc3c640aedb364bd628bc1738ada89826624d9442589eb06', 3600
            );
    sqlite> insert into records (domain_id, name, type, content, ttl) VALUES (
                1, '_5269._tcp.lupine.me.uk', 'TLSA', '1 0 1 9730ccc0952f3150bc3c640aedb364bd628bc1738ada89826624d9442589eb06', 3600
            );
    sqlite> .exit
    root@oak:~# pdnssec increase-serial lupine.me.uk && pdnssec rectify-all-zones


Now when you visit your website in a DANE-enabled browser, you'll see the
certificate is considered valid; you could remove all CA certificates from it
or use a self-signed certificate to the same end. Success!
  

#### SSHFP

As a fillip, now that you've done all that work, you can also add SSHFP records
to smooth SSH access. That looks like this:

    root@oak:~# sshfp --scan lupine.me.uk
    WARNING: Ignoring -k option, -s was passwd
    # lupine.me.uk SSH-2.0-OpenSSH_5.5p1 Debian-6+squeeze3
    # lupine.me.uk SSH-2.0-OpenSSH_5.5p1 Debian-6+squeeze3

    lupine.me.uk IN SSHFP 1 1 08C614DAF69DA62937FEFFA025607569B54B8D08
    lupine.me.uk IN SSHFP 2 1 67B596A0A593A931DAD21C83F6E7B9F02CBFE6F5

    root@oak:~# sqlite3 /var/lib/powerdns/pdns.sqlite3
    sqlite> insert into records (domain_id, name, type, content, ttl) VALUES (
                1, 'lupine.me.uk', 'SSHFP', '1 1 08C614DAF69DA62937FEFFA025607569B54B8D08', 3600
            );
    sqlite> # ...
    sqlite> .exit
    root@oak:~# pdnssec increase-serial lupine.me.uk && pdnssec rectify-all-zones

To make use of this, you'll also need to alter your ssh_config:

    lupine@den:~$ echo "\n\nVerifyHostKeyDNS yes" >> ~/.ssh/config

The outcome is that when logging into your machines over SSH from a new
location, your SSH client can check the presented host key fingerprints
against the ones in DNS, and warn you if they don't match for any reason - 
a man-in-the-middle attack, for instance. Or a server reinstall, of course.

