2017-01-19

The DIY CurveDNS Proxy: Privacy for Your Public Domain Data

At some point someone noticed that I put together a recursive resolver howto and it got linked to dnscurve.io, a good resource for all your DNSCurve query-encrypting needs.

I ended that howto by saying "this is only the beginning," which is an understatement if ever I've made one. DNSCurve is an underdog in the Internet security community. By my surveys, fewer than 50 domains in the .com zone are using DNSCurve to protect their service. How big is .com? Over 126 million domains in total. Can we fix that? Yes, we can. Can we do it alone? No, not yet. But there's still hope.

We can still control our DNS queries though. DNSCurve encryption is per-query encryption: if you have to ask six questions to six different machines to get a valid answer, all six of those questions need to be encrypted or you will leak information to a passive observer. To simplify this problem, consider how DNS works:

  1. I have a question: "How do I get to www.google.com?"
  2. I ask the root servers: "What's the IP address for www.google.com?"
  3. The root servers say: "I don't know, but you can ask these .com servers," and give me a list to check.
  4. I ask the com servers: "What's the IP adddress for www.google.com?"
  5. The com servers say: "I don't know, but you can ask the google.com servers," and give me a list to check.
  6. I ask the google.com servers: "What's the IP address for www.google.com?"
  7. The google.com servers say: "It's 173.194.202.147, 173.194.202.105, and a few more you can use."

In many cases there can be more questions, and detours, and circular dependencies, and all sorts of fun convolutions, but this is the basic version of how your DNS queries get answered. These are each individual questions, sent to different parts of the world, and answered by different software. If you aren't using DNSCurve for each of those queries, you're not getting the full protection that DNSCurve can provide. This isn't your fault, since in order to get this end-to-end DNSCurve support, every level — each server that is going to either give you an answer or else say "I don't know, but you can ask..." — needs to support DNSCurve.

Right now, there are 50 .com domains that are doing their part. That's not good enough:

  1. The root servers don't support DNSCurve.
  2. No major top level domain service supports DNSCurve. You really want .com, .net, and .org to get on board with this.
  3. Only a handful of domains support DNSCurve.

By yourself, you can only control that last one. You can set up your authoritative DNS server with a CurveDNS proxy. This is a good thing! For starters, CurveDNS does not replace your DNS server, it only acts as a forwarder to it. If you use BIND, you can keep using BIND. If you use MaraDNS, you can keep using MaraDNS. If you want, you can put CurveDNS on a different IP and provide both encrypted and non-encrypted DNS service for backwards compatibility. CurveDNS will forward non-DNSCurve queries, so this isn't necessary, but you can do it that way if you really like how your DNS server handles its sockets.

The following tutorial is NOT for the casual internet user. This is strictly for domain owners who want to provide DNSCurve-enabled service to the general public. Bump that 50 up.

Are you ready to make your DNS server awesome? Great!

What do I need to get started?

You should be familiar with my earlier recursive resolver howto. You do not need to use a DNSCurve-enabled recursive resolver to set up a DNSCurve-enabled authoritative resolver, but the principles will be the same: start with a Unix or Linux system and prepare to fetch, compile, and install some software. If compiling your own software makes you nervous, stop. Take a deep breath, bookmark this page, and come back when you're feeling brave.

  1. Get yourself a copy of CurveDNS. CurveDNS was developed by Harm van Tilborg and is currently the only serious DNSCurve proxy for authoritative DNS servers I've yet found. It uses NaCl, the Networking and Cryptography library. You may remember this from the recurvsive resolver howto. If you do, you can reuse that version here by linking it into CurveDNS, but this is a little more advanced and is not strictly necessary. CurveDNS includes its own copy of NaCl that it will use by default. For the sake of simplicity, the included version of NaCl will be the one we use.

    The most recent version of curvedns-0.87.tar.gz has the following checksums:

    MD5 = 693c8b0e96642dbc3bc2013e5c71fc4f

    SHA1 = 5941e53de5836d2cef037b8e45f585762bd6e8df

    SHA256 = 9f45d0324d2917dd93546b0af74428ff50e06293fd4c273b7e5f6b62f88d9e6a

    SHA512 = c8cf96340c3db1d9061f9f138df40f52254eb53fef8bcb65cfde57d02fd45977200e003e7dbaa4d51328cf5fedb14c860793a7e89050d2202205d43c5bd93264

    If you want an updated version that may be released at a later date, it also has a github project you can clone.

    wget -N https://github.com/curvedns/curvedns/archive/curvedns-0.87.tar.gz
    openssl dgst -sha256 ./curvedns-0.87.tar.gz
    # SHA256(./curvedns-0.87.tar.gz)= 9f45d0324d2917dd93546b0af74428ff50e06293fd4c273b7e5f6b62f88d9e6a
    [ -d ./src ] || mkdir -m02755 ./src.new && mv ./src.new ./src
    gzip -d < ./curvedns-0.87.tar.gz | (cd ./src; tar -xf -)
    cd ./src/curvedns-curvedns-0.87
    time ./configure.nacl

    NaCl can take a while to compile, so I like to do this in a tmux or GNU screen window and let it run while I do other things.

    While that compiles, here's a side note about NaCl: there are a few different forks of the NaCl library you may want to explore. While I am going to use the included NaCl libraries in this tutorial, you can consider finding one NaCl library that's right for you and using it in your software. NaCl has grown into an ecosystem of libraries that include:

    • The original NaCl software, whose purpose is to simplify and enhance online encryption, signing, and authentication into a single easy-to-use API.
    • The libsodium project, which aims for portability and redistributability.
    • The tinynacl fork used by Jan Mojžíš for dqcache. More on this later.
    • tweetnacl, a port of NaCl that can be expressed in 100 140-character-or-fewer tweets. If you ever enjoyed the idea of "RSA encryption in x lines of Perl", this is a fun read.
  2. CurveDNS requires libev and the libev dev resources to compile. This is a common package that is very likely supported by your operating system's package manager. In FreeBSD, you can use "pkg install libev" and "pkg install libevdev". In OpenBSD, use "pkg_add libev", and in Debian/Ubuntu, "apt-get install libev-dev". Your actual mileage may vary. Don't forget this step!

  3. When NaCl is finished compiling, I suggest adding the chroot patch for CurveDNS. The curve-chroot.diff file has the following checksums:

    MD5 = 079f9a82c0bc2c2c8c8f9c5a3fbfb8bb

    SHA1 = 1d6162364211861d74f9baaa91f7e01faecc1dd4

    SHA256 = 3edcfd551eceace7231ac0f6c84812e29c0f4d594277b639507f29f56c3e3dd0

    SHA512 = f3278cd70b8d57ed06a13262e10af9ecbf3934c132010aa6d90e6c6eb7a8f4c5ef085c1c0981f982af9e8e54e4b742a8f60e4c6e40978623f7adc85a0d0673a9

    cd ./src/curvedns-curvedns-0.87
    wget -N https://www.blinkenlights.ch/misc/curve-chroot.diff
    openssl dgst -sha256 ./curve-chroot.diff
    # SHA256(./curve-chroot.diff) = 3edcfd551eceace7231ac0f6c84812e29c0f4d594277b639507f29f56c3e3dd0
    patch -p1 < ./curve-chroot.diff
  4. (Optional, based on platform) Additionally, there's a patch for CurveDNS so it will compile on OpenBSD. Use it if you are building CurveDNS on that platform.

  5. (Optional) A few years ago I hit a problem with CurveDNS's source IP socket binding code, so I'm providing a patch for ip.c if you plan to use the CURVEDNS_SOURCE_IP environment variable.

    cd ./src/curvedns-curvedns-0.87
    wget -N https://su.bze.ro/software/curvedns_sourceip_bindfix.patch
    openssl dgst -sha256 ./curvedns_sourceip_bindfix.patch
    # SHA256(./curvedns_sourceip_bindfix.patch) = 6e8d073e3ae1f97dd6eb6ca60f2c6f383d766179df133ac7950c59193dd34e02
    patch -p0 < ./curvedns_sourceip_bindfix.patch
    
  6. Compile CurveDNS:

    ./configure.curvedns
    make

  7. Assuming all goes as expected, CurveDNS will compile. Confusingly, it does not include an installer, so put the files where you need them. I suggest /usr/local/bin:

    sudo install -m0755 curvedns /usr/local/bin/
    sudo install -m0755 curvedns-keygen /usr/local/bin/

    Now comes the fun part, setting up your CurveDNS service:

  8. Create a new group and two users.

    sudo groupadd _curvedns
    sudo useradd -d /dev/null -s /bin/false -g _curvedns _curvedns
    sudo useradd -d /dev/null -s /bin/false -g _curvedns _curvednslog

  9. Set up a "curvedns" service directory. This is assuming you use daemontools:

    cd
    [ -d ./curvedns ] || mkdir -m02755 ./curvedns.new && mv ./curvedns.new ./curvedns
    mkdir -m02755 ./curvedns/env
    mkdir -m02755 -p ./curvedns/log/main
    sudo chown _curvednslog._curvedns ./curvedns/log/main

  10. Set up your service and log run files:

    Create ./curvedns/run:

    echo '#!/bin/sh' > ./run.new
    echo 'exec 2>&1' >> ./run.new
    echo "exec envdir ./env sh -c '" >> ./run.new
    echo '  exec envuidgid _curvedns softlimit -d$DATALIMIT /usr/local/bin/curvedns $IP $PORT $FORWARDIP $FORWARDPORT' >> ./run.new
    echo "'" >> ./run.new
    mv ./run.new ./curvedns/run
    chmod 0755 ./curvedns/run

    Create ./curvedns/log/run:

    echo '#!/bin/sh' > ./run.new
    echo 'exec setuidgid _curvednslog multilog t ./main' >> ./run.new
    mv ./run.new ./curvedns/log/run
    chmod 0755 ./curvedns/log/run

  11. Create your service environment variables. You will need to know where you plan to run CurveDNS and where your authoritative DNS server lives. The variables you will need are:

    • CURVEDNS_DEBUG - (Optional) How much information will CurveDNS write to the log. Make this an integer from 1 to 5. 1 is fatal messages only, 5 gives a lot of additional debugging data. For this tutorial, we are going to set this to 5. You can change this later if you like.
    • CURVEDNS_SOURCE_IP - (Optional) If you applied the source IP patch, you can force an output IP for CurveDNS to use. This is handy when you are doing IP aliases and NAT.
    • DATALIMIT - How big you will let CurveDNS get. Too low will crash the service, too high can cause system thrashing. You will need to tune this value to find what works for you.
    • FORWARDIP - The IP address where your authoritative DNS server lives.
    • FORWARDPORT - The port where your authoritative DNS server lives. Almost always "53".
    • IP - The IP that CurveDNS will use.
    • PORT - The port that CurveDNS will use. Almost always "53".

    Bear in mind that you cannot run CurveDNS on the same port and IP as your authoritative DNS server, though you can run CurveDNS and your DNS service on the same machine. For example, our DNS server runs on the IP address 10.0.0.6. I am going to run CurveDNS on 10.0.0.12, so my environment variables will be:

    echo 5 > ./curvedns/env/CURVEDNS_DEBUG
    echo 20000000 > ./curvedns/env/DATALIMIT
    echo 10.0.0.6 > ./curvedns/env/FORWARDIP
    echo 53 > ./curvedns/env/FORWARDPORT
    echo 10.0.0.12 > ./curvedns/env/IP
    echo 53 > ./curvedns/env/PORT
  12. The best part, now you create your CurveDNS encryption key. CurveDNS does not use a separate key exchange mechanism. A CurveDNS server's hostname is that server's public key. The keygen utility prints out secret information, so this part needs to be kept safe at all times. If your CurveDNS private key is readable, your data isn't secured.

    This is where we put on our tinfoil hats and fiddle with our file masks.

    umask
    # Remember this value. It's usually 0022.
    umask 077
    /usr/local/bin/curvedns-keygen > ./curvedns/private.key
    # Use the value from your first umask check here:
    umask 0022
    ls -l ./curvedns/private.key
    # Check that the permissions on private.key are -rw-------

    Check the contents of ./private.key. It should look something like this, with different values for each of the three lines:

    DNS public key: uz5p19xmspsm75ntr3bgrm92b964qhmc8kkhutsrbjpnc5bb4822pq
    Hex public key: 35a43e71c5f3149aef50eece245432c4beb9508c4f677c15ac7415a50812a25a
    Hex secret key: f3f16c27f2ff6f94d52b0aa6b4011444be707f5e05b96feb31890096496f7075

    You can ignore the hex public key, it isn't used directly. Rather, it is base-32 encoded in a special way. That is the "DNS public key" string, in this case, "uz5p19xmspsm75ntr3bgrm92b964qhmc8kkhutsrbjpnc5bb4822pq". Write it down, because that is the value you will give to your domain registrar so that people can reach your CurveDNS proxy.

    The hex secret key is the other important portion. This value needs to be put into an environment variable called CURVEDNS_PRIVATE_KEY:

    umask
    # Remember this value. It's usually 0022.
    umask 077
    tail -1 ./curvedns/private.key | cut -f2 > ./curvedns/env/CURVEDNS_PRIVATE_KEY
    # Use the value from your first umask check here:
    umask 0022
    chmod -w ./curvedns/env/CURVEDNS_PRIVATE_KEY
    ls -l ./curvedns/env/CURVEDNS_PRIVATE_KEY
    # Check that the permissions on CURVEDNS_PRIVATE_KEY are -r--------

  13. Put your curvedns service directory in its permanent location and symlink it to /service:

    sudo mv ./curvedns /etc
    sudo ln -s /etc/curvedns /service/curvedns

  14. Now tail /service/curvedns/log/main/current and check that your service is running. You can test that CurveDNS is forwarding correctly with the usual DNS tools (dig, dnsq), and you can verify that the DNSCurve portion is working with dq, a remarkable DNS tool that Jan Mojžíš wrote.

    Normal traffic should work as expected. Assuming your authoritative DNS server controls myvanitydomain.dom, you can query it through the CurveDNS proxy:

    dig @`cat /service/curvedns/env/IP` ns myvanitydomain.dom

  15. If normal DNS traffic is passing through CurveDNS correctly, use dq to test the encryption. Use the DNS public key string you got from running curvedns-keygen:

    dq -a -k uz5p19xmspsm75ntr3bgrm92b964qhmc8kkhutsrbjpnc5bb4822pq ns myvanitydomain.dom `cat /service/curvedns/env/IP`

    If this is working, you can put your CurveDNS proxy online. If you are using NAT or a firewall to forward external DNS lookups to 10.0.0.6:53, you can (after making a backup of your configs), change your setup to forward DNS to 10.0.0.12:53 and do the above validation checks from an external IP address.

  16. Last step: update your DNS records on your authoritative DNS server and then update your registrar. If you were previously using "ns1.myvanitydomain.dom" as the name you provided for your authoritative DNS server, change this to
    "uz5p19xmspsm75ntr3bgrm92b964qhmc8kkhutsrbjpnc5bb4822pq.myvanitydomain.dom". Much longer, true, but this is the public key for your CurveDNS proxy. If it were shorter, it wouldn't be as secure. When a DNSCurve-enabled recursive resolver sees this nameserver hostname, it will know (a) your domain supports DNSCurve and (b) how to encrypt all the queries it will send to that host.

    If you're using tinydns, you would change your DNS lines like this (assuming your DNS servers are 1.2.3.4 and 5.6.7.8):

    - .myvanitydomain.dom:1.2.3.4:ns1.myvanitydomain.dom
      .myvanitydomain.dom:5.6.7.8:ns2.myvanitydomain.dom
    
    + .myvanitydomain.dom:1.2.3.4:uz5p19xmspsm75ntr3bgrm92b964qhmc8kkhutsrbjpnc5bb4822pq.myvanitydomain.dom
      .myvanitydomain.dom:5.6.7.8:ns2.myvanitydomain.dom

    See How to install a DNSCurve forwarder for more examples.

When you are confident your CurveDNS proxy is working well for your DNS traffic, you can repeat this process with your remaining nameservers. This slightly changes the questions your DNSCurve-enabled resolver asks:

  1. I have a question: "How do I get to www.myvanitydomain.dom?"
  2. I ask the root servers: "What's the IP address for www.myvanitydomain.dom?"
  3. The root servers say: "I don't know, but you can ask these .dom servers," and give me a list to check.
  4. I ask the dom servers: "What's the IP adddress for www.myvanitydomain.dom?"
  5. The dom servers say: "I don't know, but you can ask uz5p19xmspsm75ntr3bgrm92b964qhmc8kkhutsrbjpnc5bb4822pq.myvanitydomain.dom." (This isn't just a strange hostname. It's also the public key to use when encrypting communications to that host.)
  6. I ask uz5p19xmspsm75ntr3bgrm92b964qhmc8kkhutsrbjpnc5bb4822pq.myvanitydomain.dom: "*whisper whisper whisper*" (It's secret. No one knows what I'm asking.)
  7. The uz5p19xmspsm75ntr3bgrm92b964qhmc8kkhutsrbjpnc5bb4822pq.myvanitydomain.dom says: "*whisper whisper whisper*" (It's secret. No one can see what the response is.)

Unlike a DNSSEC-signed zone, which is a publicly-viewable signature scheme for publicly-viewable queries and responses, the entire conversation with the CurveDNS proxy is encrypted and authenticated. I encrypt my initial question to uz5p19xmspsm75ntr3bgrm92b964qhmc8kkhutsrbjpnc5bb4822pq.myvanitydomain.dom, and all subsequent queries to and responses from that nameserver are secure. In my sequence of resolver questions, the last two are private, and only I and the nameserver know what was asked and what was answered. This is a good start, but it doesn't give us the end-to-end encryption we need to make a safe, reliable, and functional Internet. You're still leaking your question to the root servers and to the intermediate nameservers that handle .dom. And one-third protection is simply no protection at all. We still have a long way to go.

Earlier in this tutorial, I mentioned that the root servers don't support DNSCurve and you can only enable DNSCurve on your own domain. This isn't entirely true: after securing your own domains, there's also a way for you to secure the first part of your DNS lookups, too. There's still hope.

Next time: Getting to the "root" of the problem and any other awful puns that may occur to me.

1 comment:

rajani said...
This comment has been removed by a blog administrator.