276 lines
9.6 KiB
Plaintext
276 lines
9.6 KiB
Plaintext
Private DNS
|
|
|
|
Summary
|
|
|
|
Private DNS is an extension to standard Wide Area Bonjour that allows
|
|
for secure, encrypted, and authorized communications. Private data sent
|
|
from a client to a DNS server is encrypted using Transport Layer
|
|
Security (TLS), ensuring that the data is hidden from prying eyes, and
|
|
contains Transaction Signatures (TSIG), so the server can authorize the
|
|
request. TSIGs are typically associated with Dynamic Updates; we are
|
|
using them for standard and long-lived queries as well. Private DNS also
|
|
protects Dynamic Updates from eavesdropping, by wrapping the update in a
|
|
TLS communication channel if the server has been configured appropriately.
|
|
|
|
Architectural Overview
|
|
|
|
mDNSResponder has been modified to automatically issue a private query
|
|
when necessary. After receiving an NXDOMAIN error, mDNSResponder checks
|
|
in the system keychain to see if the user has a DNS query key (TSIG key)
|
|
for the name in question, or for a parent of that name. If a suitable
|
|
key is found, mDNSResponder looks up the zone data associated with the
|
|
name of the question. After determining the correct name server,
|
|
mDNSResponder looks up an additional SRV record "_dns-private._tcp". If
|
|
it finds this record, mDNSResponder will re-issue the query privately.
|
|
If either there is no _dns-private._tcp record, or there is no secret
|
|
key, the call fails as it initially did, with an NXDOMAIN error.
|
|
|
|
Once the secret key is found and the SRV record is looked up, mDNSResponder
|
|
opens a TLS connection to the server on the port specified in the SRV
|
|
record just looked up. After the connection succeeds, mDNSResponder
|
|
can proceed to use that communication channel to make requests of
|
|
the server. Every private packet must also have a TSIG record;
|
|
the DNS server uses this TSIG record to allow access to its data.
|
|
|
|
When setting up a long-lived query over TCP (with or without TLS)
|
|
TCP's standard three-way handshake makes the full four-packet LLQ setup
|
|
exchange described in <http://files.dns-sd.org/draft-sekar-dns-llq.txt>
|
|
unnecessary. Instead, when connecting over TCP, the client simply sends
|
|
a setup message and expects to receive ACK + Answers. The setup message
|
|
sent is formatted as described in the LLQ document, however there is
|
|
an additional TSIG' resource record added to the end of it. The TSIG
|
|
resource records looks and acts exactly as it does in a secure update.
|
|
So when the server receives an LLQ (or a standard query), it looks to
|
|
see if the zone that is being referenced is public or private. If it's
|
|
private, then it makes sure that the client is authorized to query that
|
|
zone (by using the TSIG signature) and returns the appropriate data.
|
|
When a zone is configured as private, the server will do this type of
|
|
authorization checking for every query except those queries that are
|
|
looking for SOA and NS records.
|
|
|
|
Implementation Issues
|
|
|
|
dnsextd
|
|
|
|
dnsextd has been modified to behave much like a DNS firewall. The "real"
|
|
DNS server is configured to listen on non-standard ports on the loopback
|
|
interface. dnsextd then listens on the standard DNS ports (TCP/UDP port
|
|
53) and intercepts all DNS traffic. It is responsible for determining
|
|
what zone a DNS request is associated with, determining whether the
|
|
client is allowed access to that zone, and returning the appropriate
|
|
information back to the caller. If the packet is allowed access, dnsextd
|
|
forwards the request to the "real" nameserver, and returns the result to
|
|
the caller.
|
|
|
|
It was tempting to use BIND9's facility for configuring TSIG enabled
|
|
queries while doing this work. However after proceeding down that path,
|
|
enough subtle interaction problems were found that it was not practical
|
|
to pursue this direction, so instead dnsextd does all TSIG processing
|
|
for queries itself. It does continue to use BIND9 for processing TSIG
|
|
enabled dynamic updates, though one minor downside with this is that
|
|
there are two configuration files (named.conf or dnsextd.conf) that have
|
|
the same secret key information. That seems redundant and error-prone,
|
|
and moving all TSIG processing for both queries and updates into dnsextd
|
|
would fix this.
|
|
|
|
All private LLQ operations are TSIG-enabled and sent over a secure
|
|
encrypted TLS channel. To accommodate service providers who don't want
|
|
to have to keep open a large number of TLS connections to a large number
|
|
of client machines, the server has the option of dropping the TLS
|
|
connection after initial LLQ setup and sending subsequent events and
|
|
refreshes using unencrypted UDP packets. This results in less load on
|
|
the server, at the cost of slightly lower security (LLQs can only be set
|
|
up by an authorized client, but once set up, subsequent change event
|
|
packets sent over unencrypted UDP could be observed by an eavesdropper).
|
|
A potential solution to this deficiency might be in using DTLS, which is
|
|
a protocol based on TLS that is capable of securing datagram traffic.
|
|
More investigation needs to be done to see if DTLS is suitable for
|
|
private DNS.
|
|
|
|
It was necessary to relax one of the checks that dnsextd performs during
|
|
processing of an LLQ refresh. Prior to these changes, dnsextd would
|
|
verify that the refresh request came from the same entity that setup the
|
|
LLQ by comparing both the IP Address and port number of the request
|
|
packet with the IP Address and port number of the setup packet. Because
|
|
of the preceding issue, a refresh request might be sent over two
|
|
different sockets. While their IP addresses would be the same, their
|
|
port numbers could potentially differ. This check has been modified to
|
|
only check that the IP addresses match.
|
|
|
|
When setting up a semi-private LLQ (where the request and initial answer
|
|
set is sent over TLS/TCP, but subsequent change events are sent over
|
|
unencrypted UDP), dnsextd uses the port number of the client's TCP
|
|
socket to determine the UDP event port number. While this eliminates the
|
|
need to pass the UDP event port number in the LLQ setup request
|
|
(obviating a potential data mismatch error), I think it does more harm
|
|
than good, for three reasons:
|
|
|
|
1) We are relying that all the routers out there implement the Port
|
|
Mapping Protocol spec correctly.
|
|
|
|
2) Upon setup every LLQ must NAT map two ports. Upon tear down every LLQ
|
|
must tear down two NAT mappings.
|
|
|
|
3) Every LLQ opens up two sockets (TCP and UDP), rather than just the
|
|
one TCP socket.
|
|
|
|
All of this just to avoid sending two bytes in the LLQ setup packet
|
|
doesn't seem logical. The approach also necessitates creating an
|
|
additional UDP socket for every private LLQ, port mapping both the TCP
|
|
socket as well as the UDP socket, and moderately increasing the
|
|
complexity and efficiency of the code. Because of this we plan to allow
|
|
the LLQ setup packet to specify a different UDP port for change event
|
|
packets. This will allow mDNSResponder to receive all UDP change event
|
|
packets on a single UDP port, instead of a different one for each LLQ.
|
|
|
|
Currently, dnsextd is buggy on multi-homed hosts. If it receives a
|
|
packet on interface 2, it will reply on interface 1 causing an error in
|
|
the client program.
|
|
|
|
dnsextd doesn't fully process all of its option parameters.
|
|
Specifically, it doesn't process the keywords: "listen-on",
|
|
"nameserver", "private", and "llq". It defaults to expecting the "real"
|
|
nameserver to be listening on 127.0.0.1:5030.
|
|
|
|
|
|
mDNSResponder
|
|
|
|
Currently, mDNSResponder attempts to issue private queries for all
|
|
queries that initially result in an NXDOMAIN error. This behavior might
|
|
be modified in future versions, however it seems patently incorrect to
|
|
do this for reverse name lookups. The code that attempts to get the zone
|
|
data associated with the name will never find the zone for a reverse
|
|
name lookup, and so will issue a number of wasteful DNS queries.
|
|
|
|
mDNSResponder doesn't handle SERV_FULL or STATIC return codes after
|
|
setting up an LLQ over TCP. This isn't a terrible problem right now,
|
|
because dnsextd doesn't ever return them, but this should be fixed so
|
|
that mDNSResponder will work when talking to other servers that do
|
|
return these error codes.
|
|
|
|
|
|
Configuration:
|
|
|
|
Sample named.conf:
|
|
|
|
//
|
|
// Include keys file
|
|
//
|
|
include "/etc/rndc.key";
|
|
// Declares control channels to be used by the rndc utility.
|
|
//
|
|
// It is recommended that 127.0.0.1 be the only address used.
|
|
// This also allows non-privileged users on the local host to manage
|
|
// your name server.
|
|
|
|
//
|
|
// Default controls
|
|
//
|
|
controls
|
|
{
|
|
inet 127.0.0.1 port 54 allow { any; } keys { "rndc-key"; };
|
|
};
|
|
|
|
options
|
|
{
|
|
directory "/var/named";
|
|
/*
|
|
* If there is a firewall between you and nameservers you want
|
|
* to talk to, you might need to uncomment the query-source
|
|
* directive below. Previous versions of BIND always asked
|
|
* questions using port 53, but BIND 8.1 uses an unprivileged
|
|
* port by default.
|
|
*/
|
|
|
|
forwarders
|
|
{
|
|
65.23.128.2;
|
|
65.23.128.3;
|
|
};
|
|
|
|
listen-on port 5030 { 127.0.0.1; };
|
|
recursion true;
|
|
};
|
|
|
|
//
|
|
// a caching only nameserver config
|
|
//
|
|
zone "." IN
|
|
{
|
|
type hint;
|
|
file "named.ca";
|
|
};
|
|
|
|
zone "localhost" IN
|
|
{
|
|
type master;
|
|
file "localhost.zone";
|
|
allow-update { none; };
|
|
};
|
|
|
|
zone "0.0.127.in-addr.arpa" IN
|
|
{
|
|
type master;
|
|
file "named.local";
|
|
allow-update { none; };
|
|
};
|
|
|
|
zone "hungrywolf.org." in
|
|
{
|
|
type master;
|
|
file "db.hungrywolf.org";
|
|
allow-update { key hungrywolf.org.; };
|
|
};
|
|
|
|
zone "157.23.65.in-addr.arpa" IN
|
|
{
|
|
file "db.65.23.157";
|
|
type master;
|
|
};
|
|
|
|
zone "100.255.17.in-addr.arpa" IN
|
|
{
|
|
file "db.17.255.100";
|
|
type master;
|
|
};
|
|
|
|
zone "66.6.24.in-addr.arpa" IN
|
|
{
|
|
file "db.24.6.66";
|
|
type master;
|
|
};
|
|
|
|
key hungrywolf.org.
|
|
{
|
|
algorithm hmac-md5;
|
|
secret "c8LWr16K6ju6KMO5zT6Tyg==";
|
|
};
|
|
|
|
logging
|
|
{
|
|
category default { _default_log; };
|
|
|
|
channel _default_log
|
|
{
|
|
file "/Library/Logs/named.log";
|
|
severity info;
|
|
print-time yes;
|
|
};
|
|
};
|
|
|
|
|
|
Sample dnsextd.conf:
|
|
|
|
options { };
|
|
|
|
key "hungrywolf.org."
|
|
{
|
|
secret "c8LWr16K6ju6KMO5zT6Tyg==";
|
|
};
|
|
|
|
zone "hungrywolf.org."
|
|
{
|
|
type private;
|
|
allow-query { key hungrywolf.org.; };
|
|
};
|