Bringing E-Mail Server In-House - Part 2:
DNSCrypt

The second part in the series bringing my e-mail in-house. Getting DNSCrypt functioning on the LAN will be the main focus of this article.

In Part 1 (Introduction and DNS) I started work on modifying my DNS systems. One thing I forgot to do was to actually "map out" what I want to do. I did say what I needed my DNS resolving cache to do and support, but I didn't think about how I wanted to things.

DNS Resolution Map

I'm not a visual thinker, so there are probably going to be lots of words and then a (nested) list in this section.

My Home Server hosts my DNS Caching Server. That is currently pdnsd and will be replaced by Unbound.

In order to have new and old operating simultaneously, no LAN DNS resolver is listening on IPv4 and isc-dhcp-server is no longer giving out the IP(s) of DNS servers through DHCP. wide-dhcpv6-server is, however, still giving out the IPv6 ULA IP of the pdnsd caching server.

At present, pdnsd resolves addresses by using the following upstream IPs in order, and always using the first unless there is a failure (resolution over IPv4 should be very rare):

  1. Connecting to a DNS caching server on my VPS using CJDNS.
  2. Connecting to a DNS caching server on my VPS using an unencrypted ip6ip6 (IPv6 over IPv6) tunnel over a sit (IPv6 over IPv4) tunnel at one endpoint and another sit (IPv6 over IPv4) tunnel at the other endpoint.
  3. Connecting to Hurricane Electric's Anycast IPv6 DNS caching server.
  4. Connecting to Google's IPv4 DNS caching server.

The CJDNS connection mentioned above is using IPv4 because of (a) speed, and (b) lack of IPv6 support in CJDNS/Hyperboria when I last looked at it.

The ip6ip6 tunnel above was previously decided upon because as well as my ISP unlikely doing deep packet inspection (DPI) on IPv6 (protocol 41) traffic, the tunnel can be encrypted at a later time.

DNS lookups on the LAN are not encrypted, so LAN spying and possible interception/spoofing/interference is possible. DNSCurve/CurveDNS/DNSCrypt might be an option now or at a later time, although I need to investigate it.

DNS lookups through Hurricane Electric are not secure, as both HE and my ISP (and Government) can theoretically interfere with the traffic. Likewise Google DNS and the intermediaries there (including my ISP, Government, and the US Government).

DNS lookups from my VPS are not secure, as my VPS provider and Government can theoretically interfere with the traffic.

I want my VPS to do lookups using both DNSSEC and DNSCurve where available, I want my Home Server to assume that my VPS isn't being interfered with, and I want to secure DNS lookups between home devices (where possible) and my Home Server.

The Map

  • Device
    • DNScrypt utility ✓
      • DNScrypt wrapper on Home Server ✓
        • Unbound (caching resolver) ✓
          • Encrypted tunnel to VPS
            • Unbound (caching resolver)
              • CurveDNS resolver
                • Authoritative DNS Server
              • VPS Authoritative DNS Server
              • Namecoin Resolution?
          • LAN Authoritative DNS Server

DNSSEC validation should be passed down through the layers where applicable.

DNSCurve/DNSCrypt

A wrapper tends to take time to get setup correctly, so it is the obvious choice for the next part of this project.

The benefit of working from the middle is that I can secure LAN DNS traffic during testing before reaching the point of testing DNSSEC validation or encrypting my VPS tunnel (well, backup tunnel).

If this works, and doesn't cause milliseconds of lag, OpenDNS deserve from gratitude - not only did dnscrypt-wrapper not exist the last time I was looking at doing what I want, but DNSCrypt was only for OpenDNS back then as well. In fact, were it not for OpenDNS going with CurveDNS in the first place, they may never have created DNSCrypt, so thank you OpenDNS.

dnscrypt-wrapper

Following the "On Linux:" instructions on dnscrypt-wrapper's GitHub page, and modifying it for my purposes, I have to type the following terminal commands:

sudo apt-get install libevent-dev
cd /opt
sudo mkdir dnscrypt-wrapper libsodium
sudo chown thejc:thejc dnscrypt-wrapper libsodium
git clone --recursive https://github.com/jedisct1/libsodium.git
cd libsodium
./autogen.sh
./configure
make -j2 && make check -j2
===================
All 56 tests passed
===================
sudo make install
cd /opt
git clone --recursive git://github.com/Cofyc/dnscrypt-wrapper.git
cd dnscrypt-wrapper
make configure -j2
./configure
    Support for event library: yes
    Support for sodium library: yes
sudo make install

I should now have dnscrypt-wrapper installed. A quick dnscrypt-wrapper in the terminal gives an [error] message, confirming it is installed. Other than finding libevent2 in the Debian repository using apt-cache search libevent dev and realising I needed to use ./autogen.sh to create the libsodium configure file, installation of dnscrypt-wrapper was much simpler than other things I have tried installing in the past.

Now that I've said that, I have probably cursed myself and will have extreme difficulty configuring it. Onwards.

Configuration of dnscrypt-wrapper

dnscrypt-wrapper stores generated files in the current directory. /var or /etc for keys? The Filesystem Hierarchy Standard Wikipedia article suggests that the correct place would be /etc/opt/ ergo I need to create /etc/opt/dnscrypt-wrapper/.

sudo mkdir /etc/opt/dnscrypt-wrapper
sudo chown thejc:root /etc/opt/dnscrypt-wrapper
cd /etc/opt/dnscrypt-wrapper
dnscrypt-wrapper --gen-provider-keypair
Public key fingerprint: DCB0:F5B5:A62D:499F:BC2F:8EC6:C310:E99E:7B6F:85A4:3913:A5E2:929E:5F58:0382:E657
dnscrypt-wrapper --gen-crypt-keypair
dnscrypt-wrapper --crypt-secretkey-file crypt_secret.key --crypt-publickey-file=crypt_public.key --provider-publickey-file=public.key --provider-secretkey-file=secret.key --gen-cert-file
sudo dnscrypt-wrapper -r [fdd7:5938:e2e6:1::c:53]:53 -a [fdd7:5938:e2e6:1::c:53]:54 --crypt-secretkey-file=/etc/opt/dnscrypt-wrapper/crypt_secret.key --crypt-publickey-file=/etc/opt/dnscrypt-wrapper/crypt_public.key --provider-cert-file=/etc/opt/dnscrypt-wrapper/dnscrypt.cert --provider-name=2.dnscrypt-cert.ula.home.thejc.me.uk -VV
^Z
bg
sudo netstat -antpu | grep dnscrypt
tcp6       0      0 fdd7:5938:e2e6:1::c::54 :::*                    LISTEN      22528/dnscrypt-wrap
udp6       0      0 fdd7:5938:e2e6:1::c::54 :::*                                22528/dnscrypt-wrap

Now, to test it. A slight problem - I don't have a DNSCrypt client available. The following is what I have so far determined to be a workable (although not yet automatically functional) setup.

DNSCrypt on Windows

The first thing that is needed is a DNSCrypt proxy on Windows. I have called this DNScrypt utility in The Map. This will be the bridge between the Windows stub resolver (built-in) and the DNScrypt wrapper on Home Server.

For this I have used the latest dnscrypt-proxy-win32-full - download the zip file, extract it.

Open an elevated command prompt, and cd to the dnscrypt-proxy-win32\bin directory. I need the Public key fingerprint from when I setup dnscrypt-wrapper above, as well as the --provider-name and -a (listening address).

The first thing to do is to edit the dnscrypt-resolvers.csv file, to add my Home Server. It is a comma seperated file, and the first line are the column headers. Thus:

Name,Full name,Description,Location,Coordinates,URL,Version,DNSSEC validation,No logs,Namecoin,Resolver address,Provider name,Provider public key,Provider public key TXT record
home-ula-johncook,"John Cook LAN","JohnCook.co.UK LAN DNSSEC-enabled caching recursive DNS server","Watford, Hertfordshire, GB",,https://web.johncook.co.uk,1.0,no,no,no,[fdd7:5938:e2e6:1::c:53]:54,2.dnscrypt-cert.home.ula.thejc.me.uk, DCB0:F5B5:A62D:499F:BC2F:8EC6:C310:E99E:7B6F:85A4:3913:A5E2:929E:5F58:0382:E657,

I have left the Coordinates and Provider public key TXT record blank (empty comma-delimited fields - that is, nothing between the commas) which is why the line finished with a comma. DNSSEC validation and Namecoin are currently no because I am still testing.

Save the dnscrypt-resolvers.csv file, and then run the following command in that elevated command prompt:

dnscrypt-proxy.exe -R home-ula-johncook -a [::1]:53
[NOTICE] Starting dnscrypt-proxy 1.4.0
[INFO] Initializing libsodium for optimal performance
[INFO] Generating a new key pair
[INFO] Done
[INFO] Server certificate #808464433 received
[INFO] This certificate looks valid
[INFO] Server key fingerprint is 3D36:0BBE:CEC5:F810:74C9:2882:5E8F:B197:7B01:6921:6046:0607:4E4F:6B44:1790:0846
[NOTICE] Proxying from [::1]:53 to [fdd7:5938:e2e6:1::c:53]:54

If you get an [ERROR] Resolvers list error it is most likely the CSV fields are in the wrong order, a missing comma, or something similar. If you receive an [ERROR] Unable to retrieve server certificates error, it is possibly because you copied the wrong public key fingerprint from the server. As no other machines/devices are relying on the current server configuration, it might be simpler just to repeat the steps to setting up dnscrypt-wrapper again rather than looking back through the scrollback.

Change your (IPv6) DNS server settings for your LAN connection to ::1, and try visiting a page in your browser. You should see activity in both the command prompt (dnscrypt-proxy) output, and the terminal (dnscrypt-wrapper) output. If your browser works (assuming the A/AAAA record for the site you're visiting hasn't previously been cached by the browser) then DNS lookups from all applications on your machine that do DNS resolution through the OS are now encrypted between the OS and the Home Server.

dnscrypt-proxy as a Windows Service

The next thing that needs to be tackled is automation. First, I am going to see if dnscrypt-wrapper is capable of doing non-DNSCrypt/-DNSCurve/-CurveDNS resolution. This is so that DHCPv6 can hand out the IPv6 ULA IP and I can replace pdnsd with dnscrypt-wrapper.

Why would I want to do this? To simplify things. If using a DNSCrypt client, the server IP will be exactly the same as that already obtained through DHCPv6. Not all devices on the network will be using DNSCrypt, but if all DNS lookups go through dnscrypt-wrapper then a failure will be evident on all devices using LAN DNS resolvers.

Open a command prompt window.

nslookup
server fdd7:5938:e2e6:1::c:53
set port=54
thejc.me.uk
Server:  [fdd7:5938:e2e6:1::c:53]
Address:  fdd7:5938:e2e6:1::c:53

Non-authoritative answer:
Name:    thejc.me.uk
Addresses:  2001:470:1f09:38d::80:2
          149.255.99.49

The output indicates that dnscrypt-wrapper is capable of handling unencrypted lookups, so what I want to do should work.

Switch back to the terminal, and close the dnscrypt-wrapper process and restart it daemonised with no verbosity.

fg
^C
sudo dnscrypt-wrapper -r [fdd7:5938:e2e6:1::c:53]: -a [fdd7:5938:e2e6:1::c:53]:54 --crypt-secretkey-file=/etc/opt/dnscrypt-wrapper/crypt_secret.key --crypt-publickey-file=/etc/opt/dnscrypt-wrapper/crypt_public.key --provider-cert-file=/etc/opt/dnscrypt-wrapper/dnscrypt.cert --provider-name=2.dnscrypt-cert.home.ula.thejc.me.uk -d

Check that everything is still working in Windows (Web browser, command prompt dnscrypt-proxy activity output).

Install the dnscrypt-proxy settings in the registry and create a service by switching back to the elevated command prompt window and entering the commands:

^C
dnscrypt-proxy.exe -R home-ula-johncook -a [::1]:53 --install
net start dnscrypt-proxy
The dnscrypt-proxy service is starting.
The dnscrypt-proxy service could not be started.

A system error has occurred.

System error 1067 has occurred.

The process terminated unexpectedly.
dnscrypt-proxy.exe -R home-ula-johncook -a [::1]:53

I couldn't start the service, so I have restarted dnscrypt-proxy (not running as a service) so I can access Google without changing DNS settings again.

Now, I can't find anything relevant for 'dnscrypt-proxy system error 1067' in Google, so it is time to debug. The dnscrypt-proxy Windows README says the mandatory parameters are ResolversList and ResolverName. A look in the registry and dnscrypt-resolvers.csv is not the full path which has to be set so this is the probably issue.

Back in the elevated command prompt:

^C
dnscrypt-proxy.exe --uninstall
dnscrypt-proxy.exe -R home-ula-johncook -a [::1]:53 -L "s:\John\dnscrypt-proxy-win32-full-1.4.1\dnscrypt-proxy-win32\bin\dnscrypt-resolvers.csv" --install
[INFO] The dnscrypt-proxy service has been installed and started
[INFO] The registry key used for this service is SYSTEM\CurrentControlSet\Services\dnscrypt-proxy\Parameters
[INFO] Now, change your resolver settings to [::1]:53
net start dnscrypt-proxy
The requested service has already been started.

More help is available by typing NET HELPMSG 2182.

A quick visit to test-ipv6.com and it appears everything is working. It took a few attempts to get --install to work, possibly because I had regedit open. I clicked a different folder (Dnscache) and then clicked the dnscrypt-proxy folder, which caused an error. After that, installation worked fine and the service even started automatically.

dnscrypt-wrapper as a Debian service

init.d or init script? That is the question. On Debian it isn't much of a question though, because all my IPv6 scripts are init.d and there is probably a forgotten reason for that.

Actually, let me do a bit of Googling for 'dnscrypt-wrapper init script'... and I find a dnscrypt-proxy Debian init script on Github. I need to modify it, however.

sudo nano /etc/init.d/dnscrypt-wrapper
#! /bin/sh
### BEGIN INIT INFO
# Provides:          dnscrypt-wrapper
# Required-Start:    $local_fs $remote_fs $syslog $network $time IPv4tables IPv6tables networking
# Required-Stop:     $local_fs $remote_fs $syslog $network IPv4tables IPv6tables networking
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: DNSCrypt Proxy
# Description:       Secures DNS traffic between LAN devices and LAN DNS resolver
#                    by adding a wrapper around the DNS resolver adding
#                    DNSCrypt support.
### END INIT INFO

# Author: John Cook

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="DNS Encryption Proxy"
NAME=dnscrypt-wrapper
DAEMON=/usr/local/bin/$NAME
DAEMON_ARGS="--listen-address=[fdd7:5938:e2e6:1::c:53]:54 --resolver-address=[fdd7:5938:e2e6:1::c:53]:53 --crypt-secretkey-file=/etc/opt/dnscrypt-wrapper/crypt_secret.key --crypt-publickey-file=/etc/opt/dnscrypt-wrapper/crypt_public.key --provider-cert-file=/etc/opt/dnscrypt-wrapper/dnscrypt.cert --provider-name=2.dnscrypt-cert.home.ula.thejc.me.uk --daemonize"
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
	# Return
	#   0 if daemon has been started
	#   1 if daemon was already running
	#   2 if daemon could not be started
	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
		|| return 1
	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
		$DAEMON_ARGS \
		|| return 2
	# Add code here, if necessary, that waits for the process to be ready
	# to handle requests from services started subsequently which depend
	# on this one.  As a last resort, sleep for some time.
}

#
# Function that stops the daemon/service
#
do_stop()
{
	# Return
	#   0 if daemon has been stopped
	#   1 if daemon was already stopped
	#   2 if daemon could not be stopped
	#   other if a failure occurred
	start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --exec $DAEMON
	RETVAL="$?"
	[ "$RETVAL" = 2 ] && return 2
	# Wait for children to finish too if this is a daemon that forks
	# and if the daemon is only ever run from this initscript.
	# If the above conditions are not satisfied then add some other code
	# that waits for the process to drop all resources that could be
	# needed by services started subsequently.  A last resort is to
	# sleep for some time.
	start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
	[ "$?" = 2 ] && return 2
	# Many daemons don't delete their pidfiles when they exit.
	rm -f $PIDFILE
	return "$RETVAL"
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
	#
	# If the daemon can reload its configuration without
	# restarting (for example, when it is sent a SIGHUP),
	# then implement that here.
	#
	start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --exec $DAEMON
	return 0
}

case "$1" in
  start)
	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
	do_start
	case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
	;;
  stop)
	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
	do_stop
	case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
	;;
  status)
       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
       ;;
  #reload|force-reload)
	#
	# If do_reload() is not implemented then leave this commented out
	# and leave 'force-reload' as an alias for 'restart'.
	#
	#log_daemon_msg "Reloading $DESC" "$NAME"
	#do_reload
	#log_end_msg $?
	#;;
  restart|force-reload)
	#
	# If the "reload" option is implemented then remove the
	# 'force-reload' alias
	#
	log_daemon_msg "Restarting $DESC" "$NAME"
	do_stop
	case "$?" in
	  0|1)
		do_start
		case "$?" in
			0) log_end_msg 0 ;;
			1) log_end_msg 1 ;; # Old process is still running
			*) log_end_msg 1 ;; # Failed to start
		esac
		;;
	  *)
	  	# Failed to stop
		log_end_msg 1
		;;
	esac
	;;
  *)
	#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
	echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
	exit 3
	;;
esac

Note that I made several changes from that script available on Github. dnscrypt-proxy became dnscrypt-wrapper, sbin became bin, and --name $NAME became --exec $DAEMON (because of a 15 character limit in the start-stop-daemon).

Now I just need to kill the running dnscrypt-wrapper process and try to run the init script.

chmod +x /etc/init.d/dnscrypt-wrapper
sudo killall dnscrypt-wrapper
sudo /etc/init.d/dnscrypt-wrapper start
netstat -antpu | grep dnscrypt
tcp6       0      0 fdd7:5938:e2e6:1::c::54 :::*                    LISTEN      24947/dnscrypt-wrap
udp6       0      0 fdd7:5938:e2e6:1::c::54 :::*                                24947/dnscrypt-wrap

Snooping on my laptop's DNS while I'm on the LAN is now no longer possible, and both the dnscrypt-proxy service on my laptop and the dnscrypt-wrapper service on my Home Server should auto-run on boot-up.

Something that should be noted about this article is that the script(s), CSV file(s) and registry setting(s) are subject to change in later articles. IP addresses and ports, for instance, will change after thorough testing and when I retire pdnsd. Still a way to go before reaching that point.

I'm going to end this part of the series of articles here, as it feels like the logical place to stick a horizontal rule.

Although everything is now automated, Device in The Map does not yet deserve a check mark because not all devices have secure DNS. My mobile phone doesn't (unless I root it) nor does my laptop if I use my Fon spot (although it would have an issue because dnscrypt-proxy wouldn't work).

As I said before, bringing e-mail in-house means doing everything correctly. To solve the Fonspot and cellular issue, two more facets will need to be considered - getting Fon traffic from Fonspot to LAN (currently not routed) and setting up a VPN that portable devices can connect to both locally and remotely.

In the next article, I am finally going to deal with the (sometimes existent) encryption between my Home Server and my VPS. This is what IPSEC was created for. The first question I'll be asking is "IPv4 or IPv6?"