| 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.; }; |
| }; |