snork.ca ... making kittens cry since 2001
homeabouttoscanaryrssmansvalidatecontact

Host A DNSCrypt Server On Jessie 2017-04-02


Intro

Let's begin with a bit of an explanation of DNS. Say you want to visit a web site called snork.ca. You open up your browser, type that in, and hit enter on your keyboard. Within a few seconds you'll be looking at the desired web page, but a lot happens in those few seconds. One of the things that happens, is that the device you are using has to convert the name snork.ca to an IP address so that it can contact it. If your device can't figure out the IP address with information it has locally, it asks its DNS server (often a caching server on the local network). If that device doesn't know, it will ask an upstream DNS server (often your ISP's DNS server). If that server doesn't know, it may ask the root servers, which in turn will tell it about the servers responsible for the .ca TLD. It will pick one of those servers, and it'll ask who is responsible for snork.ca, which happens to be he.net (because that's where my DNS is hosted right now). Finally, one of Hurricane Electric's servers will provide the answer, which will make its way back to your device, giving it the ability to now contact the web site.

woof Mishka eating lumber.

Seriously, all that happens in typically a second or less. In fact, many web sites will force your browser to make DNS requests for numerous host names. Many DNS servers will also cache the information that passes through them so that they can short-circuit the process and provide your answer quickly. Unfortunately, almost all of the traffic that involves DNS is clear text, and there is really no need for that. Whether or not anyone is recording your DNS queries may be debateable, but DNS is so common, so ignored, and so unencrypted... that as a protocol, it is an excellent candidate for information disclosure.

The Server

So in my quest to encrypt my DNS queries, I of course came across DNSCrypt. It has two parts... the dnscrypt-wrapper which runs on the server and the dnscrypt-proxy which runs on clients that wish to connect to the server. In fact, it seems neither of these really knows much about DNS. The wrapper just takes in queries and passes them on to a (probably not encrypted) resolver, whicle the proxy just takes the unencrypted queries and wraps them in encryption before sending them on. Don't get me wrong, I like it... I just thought it was cute when I discovered how bare bones it is. Anyways, I like Debian for whatever reason, and tried to setup a Jessie box with BIND, but it was an ugly solution. There's no reason BIND has to be like that. So I settled for just using the proxy, and connecting to one of the resolvers that comes with the list provided in the proxy package. I liked okturtles and dnscrypt.eu-nl but had some minor trouble with them. For example, at one point, okturtles would not resolve snork.ca for me but all other domains seemed fine.

So I recently had another hack at my own server. This time I used unbound, which was significantly less painful than BIND, and supports DNSSEC out of the box. Here's a pretty accurate description of what I had to do to make it work.

The Part You Came For

Start by installing unbound from the repos

# apt-get install unbound

And then configure it

/etc/unbound/unbound.conf interface: 127.0.0.1
do-daemonize: yes
username: "unbound"
logfile: "/var/log/unbound.log"
hide-identity: yes
hide-version: yes
harden-glue: yes
harden-dnssec-stripped: yes
access-control: 127.0.0.1 allow
root-hints: "/etc/unbound/named.cache"

Download the current root-hints

# wget -O /etc/unbound/named.cache ftp://ftp.internic.net/domain/named.cache

Restart unbound

# systemctl restart unbound

Now test unbound to make sure it is working

# nslookup snork.ca 127.0.0.1

That should give you the IP address of snork.ca, if it doesn't you might want to check to make sure you do not have an old [unworking] install of another DNS server already using port 53. Now that you have unbound resolving for you, it is time to setup the encrypted dnscrypt-wrapper, starting with the dependencies. Then download and install dnscrypt itself.

# apt-get install build-essential automake libsodium-dev libevent-dev unzip
# mkdir ~/src && cd ~/src
# wget -O dnscrypt-master.zip https://github.com/cofyc/dnscrypt-wrapper/archive/master.zip
# unzip dnscrypt-master.zip
# cd dnscrypt-wrapper-master/
# make configure
# ./configure
# make install

Now you need to generate your provider key pair

# mkdir /etc/dnscrypt && cd /etc/dnscrypt
# dnscrypt-wrapper --gen-provider-keypair
# dnscrypt-wrapper --show-provider-publickey --provider-publickey-file public.key > public.asc

Next, generate your initial daily secret key & cert

# dnscrypt-wrapper --gen-crypt-keypair --crypt-secretkey-file=1.key
# dnscrypt-wrapper --gen-cert-file --crypt-secretkey-file=1.key --provider-cert-file=1.cert --provider-publickey-file=public.key --provider-secretkey-file=secret.key --cert-file-expire-days=1

Finally, it is time to run the wrapper... you'll need to change this to match your system! Note that provider-name can be anything, but must start with "2.dnscrypt-cert".

# dnscrypt-wrapper --resolver-address=192.168.1.104:53 --listen-address=0.0.0.0:443 --provider-name=2.dnscrypt-cert.snork.ca --crypt-secretkey-file=/etc/dnscrypt/1.key --provider-cert-file=/etc/dnscrypt/1.cert &

At this point you should have a working dnscrypt'ed server that is ready to take on your queries. To test it you'll want to grab the dnscrypt-proxy and use it on a client to do some queries. I used a Windows [XP] box to test mine, but the idea is generally the same on whatever OS you're using. Since your resolver is not listed in dnscrypt's public list, you'll have to connect to it like this:

> dnscrypt-proxy --local-address=127.0.0.1:53 --resolver-address=YOURSERVER:443 --provider-name=2.dnscrypt-cert.snork.ca --provider-key=1111:2222:3333:4444:5555:6666:7777:8888:9999:0000:AAAA:BBBB:CCCC:DDDD:EEEE:FFFF

"YOURSERVER" is the IP address you want to bind the wrapper to
"provider-name" comes from the name you used when you started the wrapper above and
"provider-key" is the fingerprint you should find in public.asc from the key generation above

There should be some information that tells you about your dnscrypt'ed connection to your server and you should now be able to open another command prompt and use nslookup to test a few queries:

> nslookup
> server 127.0.0.1
> snork.ca

Now, to make sure that you really are going through the proxy and wrapper, you should kill the wrapper process on the server and try your nslookups again on your test proxy machine.

# killall -v dnscrypt-wrapper

If the queries fail, then you know the wrapper is being used for them. Unfortunately, this is not the end of the story. In the commands above, when you created your initial query key/cert, they were set to expire after one day. Set them any longer, the proxy will barf up an error message when it connects to your server. It won't stop it from working, it'll just barf up the error message which looks bad. If you are planning to be the only one using this server, you can ignore the error message, but if you want to get rid of the error message (and you should), you'll have to make a script that generates new keys and kills/starts wrappers to use the new keys. Then you'll have to cron the script to run every now and then. The guy who runs dnscrypt.pl has this post explaining a bit more about the error message, and how he setup his key rotation... but I kind of went my own way. Here's what I made:

/etc/dnscrypt/keyrotate.sh #!/bin/bash
#####################################################################
# Snorkasaurus: 2017-03-28
# Script to rotate dnscrypt-wrapper keys.
# Should be cron'ed a couple times a day.
# Modify these lines to suit your system.
# providername can be anything but must start with "2.dnscrypt-cert"
# extresolver is your upstream resolver (hopefully a recursive server
# that you control).
# bindto is where the wrapper will listen.
# keydir is where to store the key files.
#####################################################################

logfile=/dev/null
datefmt=$(date +"%Y-%m-%d %H:%M:%S")
wrapperbin=/usr/local/sbin/dnscrypt-wrapper
providername=2.dnscrypt-cert.snork.ca
extresolver=192.168.1.104:53
bindto=0.0.0.0:443
keydir=/etc/dnscrypt

# You probably do not need to modify below this line.
# ===================================================================

# Functions: ########################################################

getpids ()
{ # Grabs PIDs of existing dnscrypt-wrapper so they can be killed
   pids=`ps ax|egrep "dnscrypt-wrapper.*provider-name" | grep -v grep | awk ' { print $1 }'`
}
killpids ()
{ # Kills the pids found by getpids
   if [ "$pids" != "" ]; then
      kill -9 $pids
   fi
}

# End of functions ##################################################

cd $keydir
### Here's what happens if there's two cert files present ###
if [ -f 1.cert ] && [ -f 2.cert ]; then
   echo $datefmt [ERROR] Something went wrong - found two .cert files >> $logfile

   if [ 1.cert -ot 2.cert ]; then
      echo $datefmt [INFO] 2.cert is newer >> $logfile
# Two cert files - 2.cert is newer
      if [ -f 2.key ]; then
         echo $datefmt [INFO] 2.key also exists - starting wrapper with 2.key and 2.cert >> $logfile
         getpids
         $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=2.key --provider-cert-file=2.cert &
         echo $datefmt [INFO] Dumping unused 1.key and 1.cert - and killing any leftover old wrappers >> $logfile
         # Get rid of stray files
         if [ -f 1.key ]; then rm 1.key; fi
         rm 1.cert
         killpids
# Exit after starting up wrapper with 2.cert and 2.key
         exit
      else
         echo $datefmt [ERROR] 2.cert is newest cert, but 2.key does not exist - Starting over with fresh key and cert >> $logfile
         echo $datefmt [ERROR] Clients will not be able to resolve until they refetch the new key >> $logfile
         killall dnscrypt-wrapper
         # Get rid of stray files
         if [ -f 1.key ]; then rm 1.key; fi
         rm 1.cert
         rm 2.cert
         $wrapperbin --gen-crypt-keypair --crypt-secretkey-file=1.key
         $wrapperbin --gen-cert-file --crypt-secretkey-file=1.key --provider-cert-file=1.cert --provider-publickey-file=public.key --provider-secretkey-file=secret.key --cert-file-expire-days=1
         $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=1.key --provider-cert-file=1.cert &
      fi
# Exit after starting over with 1.cert and 1.key
         exit

   else
      echo $datefmt [INFO] 1.cert is newer >> $logfile
# Two cert files - 1.cert is newer
      if [ -f 1.key ]; then
         echo $datefmt [INFO] 1.key also exists - starting wrapper with 1.key and 1.cert >> $logfile
         getpids
         $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=1.key --provider-cert-file=1.cert &
         echo $datefmt [INFO] Dumping unused 2.key and 2.cert - and killing any leftover old wrappers >> $logfile
         # Get rid of stray files
         if [ -f 2.key ]; then rm 2.key; fi
         rm 2.cert
         killpids
# Exit after starting up wrapper with 1.cert and 1.key
         exit
      else
         echo $datefmt [ERROR] 1.cert is newest cert, but 1.key does not exist - Starting over with fresh key and cert >> $logfile
         echo $datefmt [ERROR] Clients will not be able to resolve until they refetch the new key >> $logfile
         killall dnscrypt-wrapper
         # Get rid of stray files
         if [ -f 2.key ]; then rm 2.key; fi
         rm 1.cert
         rm 2.cert
         $wrapperbin --gen-crypt-keypair --crypt-secretkey-file=1.key
         $wrapperbin --gen-cert-file --crypt-secretkey-file=1.key --provider-cert-file=1.cert --provider-publickey-file=public.key --provider-secretkey-file=secret.key --cert-file-expire-days=1
         $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=1.key --provider-cert-file=1.cert &
      fi
# Exit after starting over with 1.cert and 1.key
         exit
   fi
### Here's what happens if there's a 1.cert file ###
elif [ -f 1.cert ]; then
   echo $datefmt [INFO] Found existing 1.cert file. >> $logfile
# 1.cert and two keys
   if [ -f 1.key ] && [ -f 2.key ]; then
      echo $datefmt [ERROR] Also found two keys, last script run must have failed for some reason >> $logfile
      echo $datefmt [INFO] Starting wrapper with 1.cert and both keys >> $logfile
      getpids
      $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=1.key,2.key --provider-cert-file=1.cert &
      echo $datefmt [INFO] Killing any wrappers that had been running >> $logfile
      killpids
      echo $datefmt [INFO] Waiting 70 minutes >> $logfile
      sleep 4200
      echo $datefmt [INFO] Generate 2.cert and 2.key >> $logfile
      getpids
      $wrapperbin --gen-crypt-keypair --crypt-secretkey-file=2.key
      $wrapperbin --gen-cert-file --crypt-secretkey-file=2.key --provider-cert-file=2.cert --provider-publickey-file=public.key --provider-secretkey-file=secret.key --cert-file-expire-days=1
      echo $datefmt [INFO] Start a wrapper for 2.cert with both keys >> $logfile
      $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=2.key,1.key --provider-cert-file=2.cert &
      echo $datefmt [INFO] Kill off old wrappers >> $logfile
      killpids
      # Get rid of stray files
      rm 1.cert
      echo $datefmt [INFO] Waiting 70 minutes again >> $logfile
      sleep 4200
      echo $datefmt [INFO] Make new wrapper for just 2.cert and 2.key >> $logfile
      getpids
      $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=2.key --provider-cert-file=2.cert &
      echo $datefmt [INFO] Killing any wrappers that had been running >> $logfile
      killpids
      # Get rid of stray files
      rm 1.key
# Exit after running on both keys for 70 mins, generating 2.cert and 2.key, waiting another 70 mins, and then just using 2.cert and 2.key
      exit
   elif [ -f 1.key ]; then
# 1.cert and 1.key (normal run)
      echo $datefmt [INFO] Also found 1.key file - time to add a 2.cert and 2.key - drop 1.cert >> $logfile
      getpids
      $wrapperbin --gen-crypt-keypair --crypt-secretkey-file=2.key
      $wrapperbin --gen-cert-file --crypt-secretkey-file=2.key --provider-cert-file=2.cert --provider-publickey-file=public.key --provider-secretkey-file=secret.key --cert-file-expire-days=1
      $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=2.key,1.key --provider-cert-file=2.cert &
      echo $datefmt [INFO] Killing any wrappers that had been running. >> $logfile
      killpids
      # Get rid of stray files
      rm 1.cert
      echo $datefmt [INFO] Waiting 70 minutes >> $logfile
      sleep 4200
      echo $datefmt [INFO] Now it is time to just use 2.key and 2.cert - drop 1.key >> $logfile
      getpids
      $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=2.key --provider-cert-file=2.cert &
      echo $datefmt [INFO] Killing any wrappers that had been running. >> $logfile
      killpids
      rm 1.key
# Exit after switching to 2.cert and 2.key
      exit
# 1.cert exists but no 1.key
   else
   echo $datefmt [ERROR] 1.cert exists - but there is no 1.key - Starting over with fresh key and cert >> $logfile
   echo $datefmt [ERROR] Clients will not be able to resolve until they refetch this new key >> $logfile
   killall dnscrypt-wrapper
   if [ -f 2.key ]; then rm 2.key; fi
   rm 1.cert
   $wrapperbin --gen-crypt-keypair --crypt-secretkey-file=1.key
   $wrapperbin --gen-cert-file --crypt-secretkey-file=1.key --provider-cert-file=1.cert --provider-publickey-file=public.key --provider-secretkey-file=secret.key --cert-file-expire-days=1
   $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=1.key --provider-cert-file=1.cert &
# Exit after dumping everything and starting fresh
      exit
   fi

### Here's what happens if there's a 2.cert file ### (same as 1.cert but opposite files)
elif [ -f 2.cert ]; then
   echo $datefmt [INFO] Found existing 2.cert file. >> $logfile
# 2.cert and two keys
   if [ -f 1.key ] && [ -f 2.key ]; then
      echo $datefmt [ERROR] Also found two keys, last script run must have failed for some reason >> $logfile
      echo $datefmt [INFO] Starting wrapper with 2.cert and both keys >> $logfile
      getpids
      $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=2.key,1.key --provider-cert-file=2.cert &
      echo $datefmt [INFO] Killing any wrappers that had been running >> $logfile
      killpids
      echo $datefmt [INFO] Waiting 70 minutes >> $logfile
      sleep 4200
      echo $datefmt [INFO] Generate 1.cert and 1.key >> $logfile
      getpids
      $wrapperbin --gen-crypt-keypair --crypt-secretkey-file=1.key
      $wrapperbin --gen-cert-file --crypt-secretkey-file=1.key --provider-cert-file=1.cert --provider-publickey-file=public.key --provider-secretkey-file=secret.key --cert-file-expire-days=1
      echo $datefmt [INFO] Start a wrapper for 1.cert with both keys >> $logfile
      $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=1.key,2.key --provider-cert-file=1.cert &
      echo $datefmt [INFO] Kill off old wrappers >> $logfile
      killpids
      # Get rid of stray files
      rm 2.cert
      echo $datefmt [INFO] Waiting 70 minutes again >> $logfile
      sleep 4200
      echo $datefmt [INFO] Make new wrapper for just 1.cert and 1.key >> $logfile
      getpids
      $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=1.key --provider-cert-file=1.cert & br />       echo $datefmt [INFO] Killing any wrappers that had been running >> $logfile
      killpids
      # Get rid of stray files
      rm 2.key
# Exit after running on both keys for 70 mins, generating 1.cert and 1.key, waiting another 70 mins, and then just using 1.cert and 1.key
      exit
   elif [ -f 2.key ]; then
# 2.cert and 2.key (normal run)
      echo $datefmt [INFO] Also found 2.key file - time to add a 1.cert and 1.key - drop 2.cert >> $logfile
      getpids
      $wrapperbin --gen-crypt-keypair --crypt-secretkey-file=1.key
      $wrapperbin --gen-cert-file --crypt-secretkey-file=1.key --provider-cert-file=1.cert --provider-publickey-file=public.key --provider-secretkey-file=secret.key --cert-file-expire-days=1
      $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=1.key,2.key --provider-cert-file=1.cert &
      echo $datefmt [INFO] Killing any wrappers that had been running. >> $logfile
      killpids
      # Get rid of stray files
      rm 2.cert
      echo $datefmt [INFO] Waiting 70 minutes >> $logfile
      sleep 4200
      echo $datefmt [INFO] Now it is time to just use 1.key and 1.cert - drop 2.key >> $logfile
      getpids
      $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=1.key --provider-cert-file=1.cert &
      echo $datefmt [INFO] Killing any wrappers that had been running. >> $logfile
      killpids
      rm 2.key
# Exit after switching to 1.cert and 1.key
      exit
# 2.cert exists but no 2.key
   else
      echo $datefmt [ERROR] 2.cert exists - but there is no 2.key - Starting over with fresh key and cert >> $logfile
      echo $datefmt [ERROR] Clients will not be able to resolve until they refetch this new key >> $logfile
      killall dnscrypt-wrapper
      if [ -f 1.key ]; then rm 1.key; fi
      rm 2.cert
      $wrapperbin --gen-crypt-keypair --crypt-secretkey-file=1.key
      $wrapperbin --gen-cert-file --crypt-secretkey-file=1.key --provider-cert-file=1.cert --provider-publickey-file=public.key --provider-secretkey-file=secret.key --cert-file-expire-days=1
      $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=1.key --provider-cert-file=1.cert &
# Exit after dumping everything and starting fresh
      exit
   fi

# This is what happened when there are no certs at all
else
   echo $datefmt [ERROR] There are no .cert files - Starting over with fresh key and cert >> $logfile
   echo $datefmt [ERROR] Clients will not be able to resolve until they refetch this new key >> $logfile
   killall dnscrypt-wrapper
   if [ -f 1.key ]; then rm 1.key; fi
   if [ -f 2.key ]; then rm 2.key; fi
   $wrapperbin --gen-crypt-keypair --crypt-secretkey-file=1.key
   $wrapperbin --gen-cert-file --crypt-secretkey-file=1.key --provider-cert-file=1.cert --provider-publickey-file=public.key --provider-secretkey-file=secret.key --cert-file-expire-days=1
   $wrapperbin --resolver-address=$extresolver --listen-address=$bindto --provider-name=$providername --crypt-secretkey-file=1.key --provider-cert-file=1.cert &
# Exit after dumping everything and starting fresh
   exit
fi

Okay, so the general idea is that your dnscrypt server starts by using 1.key and 1.cert. Then, to switch to a new set, it has to:

  1. Generate 2.key and 2.cert
  2. Start a new wrapper that uses 1.key, 2.key, and 2.cert
  3. Kill the old wrapper that was using just 1.key and 1.cert
  4. Run that way for an hour (70 mins actually)
  5. Start a new wrapper with just 2.key and 2.cert
  6. Kill the old wrapper that was using 1.key, 2.key, and 2.cert

The reason for waiting an hour is because each running dnscrypt-proxy machine will check in every hour to see if its current cert is the right one. During the hour wait period, the server is running on the new key/cert but will still accept queries using the old key. When each connected proxy gets to its "check time" it'll download the new cert and switch to 2.key. After an hour it is safe to stop accepting queries using the old key. I set my wait to 70 minutes just in case any proxy clients have a minor delay in their check in period. Basically the script reverses the process to generate and revert to 1.key/1.cert the next time it is run.

This script also tries to determine if the previous script had failed for any reason, and attempts to recover by picking up where the previous run had failed. Finally, it also looks for impossible situations like two or zero cert files. I was thinking of making a [similar] script that would run at boot, and would attempt to pick up where the last script left off too. Anyways, I chmod'ed the script with

# chmod +x /etc/dnscrypt/keyrotate.sh

...and added it to my crontab to run every four hours.

# crontab -e 15 1,7,13,19 * * * /etc/dnscrypt/keyrotate.sh

You Must Make Your Server Public

The reason you really would want to run the stupid script, is because you want your server to at least seem mildly professional to clients who may be connecting to it. I guess some people wouldn't notice or perhaps wouldn't care about the minor error message they get when connedcting to your server... but they should. Now, you might say that you are only making a dnscrypt'ed server for your own use and that you do not plan to make your server publicly accessible. Perhaps you just don't trust any of the public dnscrypt resolvers and you want to make sure that you have control of the logging being done on your dnscrypt'ed server. Well, that would be a waste. Frankly, if anyone has the ability to record your DNS queries when you use your ISP's DNS servers, then they have the digital intelligence to figure out where your dnscrypted server is, and will know that all DNS traffic is yours anyways. Using a dnscrypt'ed server is really only effective if you have numerous clients making queries to it, because even if the upstream queries were recorded, the person recording doesn't know which client is doing the requesting.

Some might agrue that [on a heavily used public dnscrypt'ed server] it could be possible to trap the outbound queries from it, as well as the traffic from your PC/network/VPN and be able to piece together who is making which queries. In all reality, that is kind of a stretch, because it doesn't take in to account the fact that many queries would be cached and would never have to go upstream. It would also be hard to essentially trap every bit of Internet traffic continuously in order to compare the packet times against. Don't get me wrong, if someone could trap just your traffic and the outbound traffic from a dnscrypt'ed server, then maybe they could piece together which queries are yours, but that is a lot of trouble to go to for DNS queries, and if someone was that iterested in your traffic, they'd be doing a lot more than just some DNS query sniffing.

Made using Notepad++ & FastStone. Hosted on Debian with nginx & php. Powered by North Korean mushrooms.