Tag Archives: DNS

DNS-Over-HTTPS

I saw the recent announcements from Mozilla, Cloudflare and Google about running a trial to try and make DNS name resolution more secure.

The basic problem is that most users get their DNS server set via DHCP which is controlled by who ever runs the network (at home this tends to be their ISP, but when using public wifi this could be anybody). The first approach to help with this was Google’s 8.8.8.8 public DNS service (followed by the IBM’s 9.9.9.9 and Cloudflares 1.1.1.1). This helps if people are technically literate enough know how to change their OS’s DNS settings and fix them to one of these providers. Also DNS is UDP based protocol which makes it particularly easy for a bad actor on the network to spoof responses.

The approach the 3 companies are taking is to run DNS over an existing secure protocol, in this case HTTPS. From Firefox version 60 (currently in beta) it is possible to set it up to do name host name resolution via DNS-Over-HTTPS.

There are currently 2 competing specifications for how to actually implement DNS-Over-HTTPS.

DNS Wireformat

This uses exactly the same data structure as existing DNS. Requests can be made via a HTTP GET or POST. For a POST the body is the binary request and the Content-Type is set to application/dns-udpwireformat.

For GET requests the payload is BASE64 encoded and passed as the dns query parameter.

In both cases the response is the same binary payload as would be made by a normal DNS server.

This approach is currently covered by this draft RFC

JSON

For this approach the request are made as a HTTP GET request with the hostname (or IP address) being passed as the name and the query type being passed as the type query parameters.

A response looks like this:

{
    "Status": 0,
    "RA": true,
    "RD": true,
    "TC": false,
    "AD": false,
    "CD": true,
    "Additional": [],
    "Answer": [
        {
            "TTL": 86400,
            "data": "93.184.216.34",
            "name": "example.com",
            "type": 1
        }
    ],
    "Question": [
        {
            "name": "example.com",
            "type": 1
        }
    ]
}

With a Content-Type of application/dns-json

You can find the spec for this scheme from Google here and Cloudflare here.

Both of these schemes have been implemented by both Google and Cloudflare and either can be used with Firefox 60+.

Privacy Fears

There has already been a bit of a backlash against this idea, mainly around privacy fears. The idea of Google/CloudFlare being able to collect information about all the hosts your browser resolves scared some people. Mozilla has an agreement in place with CloudFlare about data retention for the initial trial.

Given these fears I wondered if people might still want to play with DNS-Over-HTTPS but not want to share data with Google/Cloudflare. With this in mind I thought I’d try and see how easy it would be to implement a DNS-Over-HTTPS server. Also people may want to try this out on closed networks (for things like performance testing or security testing).

It turned out not to be too difficult, I started with a simple ExpressJS based HTTP server and then started to add DNS support. Initially I tried a couple of different DNS NodeJS nodes to get all the require details and in the end settled on dns-packet and actually sending my own UDP packets to the DNS server.

I’ve put my code up on github here if anybody wants a play. The README.md should include details about how to set up Firefox to use an instance.

DNSSEC and Letsencrypt

A couple of tweets from a colleague over the Christmas period along with some jobs I’d been saving up made me have another look at the DNS and HTTPS set up for a couple of sites I look after.

DNSSEC

I’ve been meaning to play with DNSSEC for a while, especially since I run my own primary DNS and set up DMKIM to verify my mail server identity (yeah, I know in this day and age of cloud running all your own services is a little quaint, but I like to understand how every thing works).

This a good introduction to DNSSEC if you’re not up to speed. TL;DR DNSSEC allows you to tell when people have been messing with your DNS entries.

To set up DNSSEC you need to create 2 sets of keys, a zone signing key and a key signing key you can create them with the following commands respectively.

$ dnssec-keygen -a NSEC3RSASHA1 -b 2048 -n ZONE hardill.me.uk
Generating key pair..................+++ .............+++
Khardill.me.uk.+007+40400
$ dnssec-keygen -f KSK -a NSEC3RSASHA1 -b 4096 -n ZONE hardill.me.uk
Generating key pair....................................................................................................................................................................................................................................................++ ................................................................................++ 
Khardill.me.uk.+007+23880

Key generation requires a lot of random numbers and these are created from the /dev/random, the values for this are generated from the system entropy so can take a long time on a machine that isn’t doing very much, to help with this I can installed the haveged daemon.

Now I have the 2 sets of keys (public and private) I need to add them to the end of my zone file with the following lines:

$INCLUDE Khardill.me.uk.+007+43892.key
$INCLUDE Khardill.me.uk.+007+23880.key

Now we can use these keys to actually sign the zone with the dnssec-signzone command, the NSEC3 setup takes a salt to help with security. The $(head -c 1000 /dev/random | sha1sum | cut -b 1-16) generates a 16 character random string to act as the salt.

$ dnssec-signzone -A -3 $(head -c 1000 /dev/random | sha1sum | cut -b 1-16) -N INCREMENT -o hardill.me.uk -t hardill.me.uk.db
Verifying the zone using the following algorithms: NSEC3RSASHA1.
Zone fully signed:
Algorithm: NSEC3RSASHA1: KSKs: 1 active, 0 stand-by, 0 revoked
                         ZSKs: 1 active, 0 stand-by, 0 revoked
hardill.me.uk.db.signed
Signatures generated:                       25
Signatures retained:                         0
Signatures dropped:                          0
Signatures successfully verified:            0
Signatures unsuccessfully verified:          0
Signing time in seconds:                 1.129
Signatures per second:                  22.143
Runtime in seconds:                      1.274

This generates 2 files, the first is hardill.me.uk.db.signed which is an updated version of the zone file with the signed hashes included for each entry. The second is dsset-hardill.me.uk. which holds the DS hashes for my 2 keys. The DS entries are hosted by the layer above my domain in the DNS hierarchy so that anybody wanting to verify the data can walk from the Signed root zone up the tree checking the level above before moving on. To get the DS entries into the zone above you normally have to go through your Domain Name Registrar who would in this case ask Nominet (as the keep of the me.uk domain) to host them for me, unfortunately my registrar (I won’t name them here) claims unable to be able pass this request on to Nominet. I need to see if I can get Nominet to do it for me, but I’m not confident so I’m currently in the market for a new registrar, any recommendations welcome.

In the mean time I decided to test the rest of it out on the private TLD I run on my lan. I can get round the need for a DS record by telling Bind to trust my key explicitly using the trusted-keys directive in named.conf. To get this far I followed this set of instructions, which are the manual steps for DNSSEC, there are also instructions to get Bind to automatically sign zones, which is especially useful if you are doing Dynamic DNS updates, this page has instructions for that which I’ll be looking at once I get things sorted to have my DS records hosted properly.

Letsencrypt

The letsencrypt project has a goal to provide free SSL certificates for everybody that are signed by a CA in the collection commonly included in modern browsers. It had been in private beta most of last year, but went into public beta at the start of December so I could sign up. Letsencrypt will generate you a certificate for any domain you can prove you own, you do this using a protocol called ACME and they have written a client to help with this. ACME works over HTTP/HTTPs by placing a hash value at a known location. This can be via an existing HTTP server (e.g. Apache) or by a one built into the client. At home I run my own private CA as it allows me to issue certificates for names on my private TLD and for my IP addresses. I also issue client certificates to authenticate users and having them all with the same CA makes things a little easier. When I get some time I will probably move my domain over to a letsencrypt certificate and only use my CA for client certs. In the mean time I needed to set up access to my Dad’s work mail server so my Brother can send/receive email from his iPhone, this needed to be secure so everything needs to be protected by a certificate. Rather than mess about getting the root CA certs for my private CA on to his phone I decided to use letsencrypt. The mail server doesn’t run a webserver so I used the one built into the client.

$ letsencrypt-auto certonly --standalone  --email admin@example.com --agree-tos -d mail.example.com

The command line arguments are as follows

  • certonly – This tells the client to download the certificate (rather than download it and install it
  • –standalone – This tells the client to use it’s built in HTTP server
  • –email admin@example.com – This tells the client who to email if there is a problem (like a cert expires without being renewed)
  • –agree-tos – This stops the client showing the TOS and prompting you to agree to them
  • -d mail.example.com – This tells the client which host name to create the certificate for, you can specify multiple instances

Certificates and Keys are stored under /etc/letsencrypt/ with the current cert under live/[host name]. I configured Postfix and Dovecot to point to these so that they just need to be restarted to pick up the new certs.

Letsencrypt hand out certificates that are only valid for 90days, this is for a couple of reasons, but mainly it means that any compromised certs only expose people for a short time and they can upgrade the supported algorithms/key strength regularly to keep ahead of new vulnerabilities. The downside to this is that you need to renew the certificate regularly. The client is actually pretty good at letting you automate things using a very similar command to the original version. I’ve set up a cron job to run on the first of every second month that renews a new cert every 60ish day and then restart Postfix and Dovecot, this gives plenty of time to fix anything should there be a problem.

25 15 1 1,3,5,7,9,11 * /home/admin/renew-cert.sh
#!/bin/sh
/home/admin/letsencrypt/letsencrypt-auto certonly --standalone --renew-by-default --email admin@example.com --agree-tos -d mail.example.com
sudo service dovecot restart
sudo service postfix restart

I had to add the following to the sudoers file to get everything to work without prompting for passwords

admin ALL= NOPASSWD: /home/admin/letsencrypt/letsencrypt-auto 
admin ALL= NOPASSWD: /usr/bin/service postfix *
admin ALL= NOPASSWD: /usr/bin/service dovecot *