-- Leo's gemini proxy

-- Connecting to ur.gs:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini; lang=en

DNSSEC + DANE: Part 2

Posted on 2013-09-15 by Nick Thomas



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 doesn't

bear fruit, or if you're impatient, you can install and use the Unbound resolver:


http://unbound.net/


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 or two that also support DANE:


https://os3sec.org/

https://www.dnssec-validator.cz/


The equivalent Chrome plugin only supports DNSSEC:


https://chrome.google.com/webstore/detail/dnssec-validator/hpmbmjbcmglolhjdcbicfdhmgmcoeknm


Internet Explorer is probably Right Out, and I have no

idea about Opera, Safari, and the rest. Another option is to install the

Bloodhound browser. Apparently.


https://www.dnssec-tools.org/wiki/index.php/Bloodhound


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/


You should also 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/


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 and installed it:


https://www.powerdns.com/downloads.html


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, but I've

not needed to fuss with any of this, yet.


http://doc.powerdns.com/html/dnssec-operational-doctrine.html


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 - to do it for you:


https://github.com/pieterlexis/swede


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.




Questions? Comments? Criticisms? Contact the author by email: gemini@ur.gs


mailto:gemini@ur.gs

-- Response ended

-- Page fetched on Sat May 18 11:14:38 2024