I develop stuff and have opinions on things -
About me/Contact

Articles:

DNS-Based Authentication of Named Entities

NOTE: This article is not in any way a complete walkthrough/tutorial of NSD deployment or DNSSEC best practices, and should not be used as such. It is more a testimony of my small experience doing those things, and using your head is required (e.g. reading the relevant RFC sections, restarting the DNS server at the right times, or clicking through the registrar web UI, and searching the web to do what you want).

DANE is, I believe, the least harmful way of handing certificates, but sadly it is restricted to a fraction of the domains which have DNSSEC enabled. The idea is to publish information about the certificates into the DNS zone, and use DNSSEC to certify the information. Sadly, DNSSEC itself isn’t widely deployed or supported on the software side, so DANE is kind of a marginal thing that is waiting to get traction.

Firefox has an addon, as well as prosody, XMPP.net has a dedicated section, and I am looking into dnspython to see how I could integrate this into poezio or sleekxmpp in the future, as part of the XMPP encryption manifesto.

I have been able to successfully and easily (systemd nonwithstanding) run NSD with DNSSEC & DANE on my domains in very little time.

First, you will probably have to run the DNS server yourself, as many registrars do not want to handle the signing themselves, leaving only a form to submit the public keys used for the signature.

If you already have a working DNS server, skip to the DNSSEC section.

If you already have a DNSSEC-enabled zone, skip to the DANE section.

Get a working NSD

I chose NSD as a nameserver instead of bind because it is lighter and less cumbersome, but bind also works, if that is what you are into.

The NSD configuration file is very simple, and does not need comments (if you need them, every option is detailed in the manpage):

server:
        port: 53
        zonesdir: "/etc/nsd"
        logfile: "/var/log/nsd.log"

remote-control:
        control-enable: "yes"

zone:
        name: "example.net"
        zonefile: "example.net.zone"

Next, make sure you have a correct (working) zone file by taking the zonefile from your previous DNS hosting and adding $ORIGIN and $TTL variables, along with a valid SOA record. This should look like this:

$ORIGIN example.net.
$TTL 3600


@ IN SOA ns.example.net. admin.example.net. ( ; nameserver and contact e-mail
           2014022701  ; serial number, tradition is YYYYMMDDnn
           28800       ; Refresh
           7200        ; Retry
           864000      ; Expire
           86400       ; Min TTL
           )

@ 10800 IN A 10.0.0.1
www 10800 IN A 10.0.0.1
@ 600 IN AAAA 2a01::f00
www 600 IN AAAA 2a01::f00
_xmpp-client._tcp 3600 IN SRV 5 0 5222 example.net.
_xmpp-server._tcp 3600 IN SRV 5 0 5269 example.net.

Use dig or drill to check if your nameserver is replying correctly (dig record @nameserver_ip host).

Enabling DNSSEC

Enabling DNSSEC on a domain is fairly easy.

First, generate a KSK (Key Signing Key) and a ZSK (Zone Signing Key). I chose to use the ldns tools, but there are many more.

The difference between the two is purely cosmetical, the KSK will be used to sign the ZSK, and the ZSK will be used to sign the rest of the zone. As the KSK will sign less data, less often, it can be kept longer than the ZSK, and in a more secure place.

The KSK:

$ ldns-keygen -a RSASHA512 -b 4096 -k example.net

The ZSK:

$ ldns-keygen -a RSASHA512 -b 2048 example.net

Both should output three files, a .key, a .ds, and a .private, prefixed with K<domain name>+<alg id, here 0007>+<5-digit id>.

The update frequency should be something like zone > ZSK > KSK, with several orders of magnitude inbetween.

Your registrar has probably an online form where you can submit the public part of your KSK.

dnssec_form

Now you only have to sign the zone, and tell NSD to serve it.

Sign it with both keys (it will detect which key is a KSK and which is a ZSK and act accordingly):

$ ldns-signzone -n example.net.zone Kexample.net+010+12345 Kexample.net+010+67890

This command will produce a new example.net.zone.signed file, so you have to tell NSD to use it, which is done by changing the configured filename, that is all.

You can check the validity of your deployment using the VeriSign DNSSEC debugger (tip: use +/- to add or remove details to the results).

The default expiration date is around one month, and there are many tools to automatize the process, or send you emails when a signature is about to expire. I also strongly encourage you to read the RFC 4641 (DNSSEC Operational Practices).

Enabling DANE

Ok, so here is the easiest part.

A TLSA record (DANE is the name of the RFC) looks like this:

_443._tcp.jeproteste.info.      3600    IN      TLSA    2 0 2 a37405a646c994cd594bf79ad4cbdb22e2576797340fc2806e16692f4c82d112905846f01493b59e2da89d1f93cd2d8b194756e8f8c8bec2d915c19dcdd0c196

You can see here the port, the host, the TTL, the record, and its fields (2, 0, 2, and a hash). The first field is the certificate usage field :

  • Usage 0: Pin an authorized public CA (PKIX-TA).
  • Usage 1: Pin a certificate, which must still be valid PKIX-wise (PKIX-EE).
  • Usage 2: Pin a non-public CA (has to be sent by the server). Mirrors usage 0 (DANE-TA).
  • Usage 3: Pin a certificate, which is not necessarily valid PKIX-wise. Mirrors usage 1 (DANE-EE).

The second field is the selector field:

  • Selector 0: match the certificate binary structure.
  • Selector 1: match the SubjectPublicKeyInfo field.

You probably only need the selector type 0.

The third field is the matching type:

  • Type 0: match the raw content
  • Type 1: match a SHA256 of the raw content
  • Type 2: match a SHA512 of the raw content

The fourth field is naturally the matching data, the hash or content.

Fortunately, ldns integrates a ldns-dane utility that allows you to generate a TLSA record painlessy.

$ ldns-dane create mathieui.net 443 3 0 2
_443._tcp.mathieui.net. 3600    IN      TLSA    3 0 2 34c34b1efbc4789ffffc5c1d132d7304a57797a3bef94b961c43bcf393f617b863f0ac9d18b5045d1f028e4f3b5cf0bbe09e030168e4a0107e2975b5db53e7c4

I don’t know how many other protocols it supports, but I know that it does not support XMPP, so you will have to generate the hash and write the record by hand.

The following script takes the filename of the certfile as a parameter and outputs a sha512 hash (change according to what you want):

#!/usr/bin/env python3

import hashlib
import ssl
import sys

with open(sys.argv[1]) as fd:
    key = fd.read()

key = key[key.index('-----BEGIN CERTIFICATE'):]
key = ssl.PEM_cert_to_DER_cert(key)
hashed = hashlib.sha512(key)
print(hashed.hexdigest())

Once you are done signing the records and reloading your dns server, you can check the results with ldns-dane.

$ ldns-dane verify example.net 443

There are few options that might be needed (like -d if your default resolver doesn’t do DNSSEC, or -S -k trusted.key if you have the public keys of the root, etc).

Addendum

I added the -n option to the ldns-signzone command, because otherwise you could enumerate the records in the zone; and while putting private data inside a zone file has never been recommended, if you can prevent zone walking for free, do it.

Also, the semantics of DANE integration into XMPP haven’t been finalized yet, but it will probably be better to have

_xmpp-client._tcp.mathieui.net. 3600    IN      TLSA    3 0 2 34c34b1efbc4789ffffc5c1d132d7304a57797a3bef94b961c43bcf393f617b863f0ac9d18b5045d1f028e4f3b5cf0bbe09e030168e4a0107e2975b5db53e7c4

rather than

_5222._tcp.mathieui.net. 3600    IN      TLSA    3 0 2 34c34b1efbc4789ffffc5c1d132d7304a57797a3bef94b961c43bcf393f617b863f0ac9d18b5045d1f028e4f3b5cf0bbe09e030168e4a0107e2975b5db53e7c4

as it is nicer to read, write, and remember.

If you have remarks or suggestions concerning this article, please by all means contact me.