1 | Private DNS |
---|
2 | |
---|
3 | Summary |
---|
4 | |
---|
5 | Private DNS is an extension to standard Wide Area Bonjour that allows |
---|
6 | for secure, encrypted, and authorized communications. Private data sent |
---|
7 | from a client to a DNS server is encrypted using Transport Layer |
---|
8 | Security (TLS), ensuring that the data is hidden from prying eyes, and |
---|
9 | contains Transaction Signatures (TSIG), so the server can authorize the |
---|
10 | request. TSIGs are typically associated with Dynamic Updates; we are |
---|
11 | using them for standard and long-lived queries as well. Private DNS also |
---|
12 | protects Dynamic Updates from eavesdropping, by wrapping the update in a |
---|
13 | TLS communication channel if the server has been configured appropriately. |
---|
14 | |
---|
15 | Architectural Overview |
---|
16 | |
---|
17 | mDNSResponder has been modified to automatically issue a private query |
---|
18 | when necessary. After receiving an NXDOMAIN error, mDNSResponder checks |
---|
19 | in the system keychain to see if the user has a DNS query key (TSIG key) |
---|
20 | for the name in question, or for a parent of that name. If a suitable |
---|
21 | key is found, mDNSResponder looks up the zone data associated with the |
---|
22 | name of the question. After determining the correct name server, |
---|
23 | mDNSResponder looks up an additional SRV record "_dns-private._tcp". If |
---|
24 | it finds this record, mDNSResponder will re-issue the query privately. |
---|
25 | If either there is no _dns-private._tcp record, or there is no secret |
---|
26 | key, the call fails as it initially did, with an NXDOMAIN error. |
---|
27 | |
---|
28 | Once the secret key is found and the SRV record is looked up, mDNSResponder |
---|
29 | opens a TLS connection to the server on the port specified in the SRV |
---|
30 | record just looked up. After the connection succeeds, mDNSResponder |
---|
31 | can proceed to use that communication channel to make requests of |
---|
32 | the server. Every private packet must also have a TSIG record; |
---|
33 | the DNS server uses this TSIG record to allow access to its data. |
---|
34 | |
---|
35 | When setting up a long-lived query over TCP (with or without TLS) |
---|
36 | TCP's standard three-way handshake makes the full four-packet LLQ setup |
---|
37 | exchange described in <http://files.dns-sd.org/draft-sekar-dns-llq.txt> |
---|
38 | unnecessary. Instead, when connecting over TCP, the client simply sends |
---|
39 | a setup message and expects to receive ACK + Answers. The setup message |
---|
40 | sent is formatted as described in the LLQ document, however there is |
---|
41 | an additional TSIG' resource record added to the end of it. The TSIG |
---|
42 | resource records looks and acts exactly as it does in a secure update. |
---|
43 | So when the server receives an LLQ (or a standard query), it looks to |
---|
44 | see if the zone that is being referenced is public or private. If it's |
---|
45 | private, then it makes sure that the client is authorized to query that |
---|
46 | zone (by using the TSIG signature) and returns the appropriate data. |
---|
47 | When a zone is configured as private, the server will do this type of |
---|
48 | authorization checking for every query except those queries that are |
---|
49 | looking for SOA and NS records. |
---|
50 | |
---|
51 | Implementation Issues |
---|
52 | |
---|
53 | dnsextd |
---|
54 | |
---|
55 | dnsextd has been modified to behave much like a DNS firewall. The "real" |
---|
56 | DNS server is configured to listen on non-standard ports on the loopback |
---|
57 | interface. dnsextd then listens on the standard DNS ports (TCP/UDP port |
---|
58 | 53) and intercepts all DNS traffic. It is responsible for determining |
---|
59 | what zone a DNS request is associated with, determining whether the |
---|
60 | client is allowed access to that zone, and returning the appropriate |
---|
61 | information back to the caller. If the packet is allowed access, dnsextd |
---|
62 | forwards the request to the "real" nameserver, and returns the result to |
---|
63 | the caller. |
---|
64 | |
---|
65 | It was tempting to use BIND9's facility for configuring TSIG enabled |
---|
66 | queries while doing this work. However after proceeding down that path, |
---|
67 | enough subtle interaction problems were found that it was not practical |
---|
68 | to pursue this direction, so instead dnsextd does all TSIG processing |
---|
69 | for queries itself. It does continue to use BIND9 for processing TSIG |
---|
70 | enabled dynamic updates, though one minor downside with this is that |
---|
71 | there are two configuration files (named.conf or dnsextd.conf) that have |
---|
72 | the same secret key information. That seems redundant and error-prone, |
---|
73 | and moving all TSIG processing for both queries and updates into dnsextd |
---|
74 | would fix this. |
---|
75 | |
---|
76 | All private LLQ operations are TSIG-enabled and sent over a secure |
---|
77 | encrypted TLS channel. To accommodate service providers who don't want |
---|
78 | to have to keep open a large number of TLS connections to a large number |
---|
79 | of client machines, the server has the option of dropping the TLS |
---|
80 | connection after initial LLQ setup and sending subsequent events and |
---|
81 | refreshes using unencrypted UDP packets. This results in less load on |
---|
82 | the server, at the cost of slightly lower security (LLQs can only be set |
---|
83 | up by an authorized client, but once set up, subsequent change event |
---|
84 | packets sent over unencrypted UDP could be observed by an eavesdropper). |
---|
85 | A potential solution to this deficiency might be in using DTLS, which is |
---|
86 | a protocol based on TLS that is capable of securing datagram traffic. |
---|
87 | More investigation needs to be done to see if DTLS is suitable for |
---|
88 | private DNS. |
---|
89 | |
---|
90 | It was necessary to relax one of the checks that dnsextd performs during |
---|
91 | processing of an LLQ refresh. Prior to these changes, dnsextd would |
---|
92 | verify that the refresh request came from the same entity that setup the |
---|
93 | LLQ by comparing both the IP Address and port number of the request |
---|
94 | packet with the IP Address and port number of the setup packet. Because |
---|
95 | of the preceding issue, a refresh request might be sent over two |
---|
96 | different sockets. While their IP addresses would be the same, their |
---|
97 | port numbers could potentially differ. This check has been modified to |
---|
98 | only check that the IP addresses match. |
---|
99 | |
---|
100 | When setting up a semi-private LLQ (where the request and initial answer |
---|
101 | set is sent over TLS/TCP, but subsequent change events are sent over |
---|
102 | unencrypted UDP), dnsextd uses the port number of the client's TCP |
---|
103 | socket to determine the UDP event port number. While this eliminates the |
---|
104 | need to pass the UDP event port number in the LLQ setup request |
---|
105 | (obviating a potential data mismatch error), I think it does more harm |
---|
106 | than good, for three reasons: |
---|
107 | |
---|
108 | 1) We are relying that all the routers out there implement the Port |
---|
109 | Mapping Protocol spec correctly. |
---|
110 | |
---|
111 | 2) Upon setup every LLQ must NAT map two ports. Upon tear down every LLQ |
---|
112 | must tear down two NAT mappings. |
---|
113 | |
---|
114 | 3) Every LLQ opens up two sockets (TCP and UDP), rather than just the |
---|
115 | one TCP socket. |
---|
116 | |
---|
117 | All of this just to avoid sending two bytes in the LLQ setup packet |
---|
118 | doesn't seem logical. The approach also necessitates creating an |
---|
119 | additional UDP socket for every private LLQ, port mapping both the TCP |
---|
120 | socket as well as the UDP socket, and moderately increasing the |
---|
121 | complexity and efficiency of the code. Because of this we plan to allow |
---|
122 | the LLQ setup packet to specify a different UDP port for change event |
---|
123 | packets. This will allow mDNSResponder to receive all UDP change event |
---|
124 | packets on a single UDP port, instead of a different one for each LLQ. |
---|
125 | |
---|
126 | Currently, dnsextd is buggy on multi-homed hosts. If it receives a |
---|
127 | packet on interface 2, it will reply on interface 1 causing an error in |
---|
128 | the client program. |
---|
129 | |
---|
130 | dnsextd doesn't fully process all of its option parameters. |
---|
131 | Specifically, it doesn't process the keywords: "listen-on", |
---|
132 | "nameserver", "private", and "llq". It defaults to expecting the "real" |
---|
133 | nameserver to be listening on 127.0.0.1:5030. |
---|
134 | |
---|
135 | |
---|
136 | mDNSResponder |
---|
137 | |
---|
138 | Currently, mDNSResponder attempts to issue private queries for all |
---|
139 | queries that initially result in an NXDOMAIN error. This behavior might |
---|
140 | be modified in future versions, however it seems patently incorrect to |
---|
141 | do this for reverse name lookups. The code that attempts to get the zone |
---|
142 | data associated with the name will never find the zone for a reverse |
---|
143 | name lookup, and so will issue a number of wasteful DNS queries. |
---|
144 | |
---|
145 | mDNSResponder doesn't handle SERV_FULL or STATIC return codes after |
---|
146 | setting up an LLQ over TCP. This isn't a terrible problem right now, |
---|
147 | because dnsextd doesn't ever return them, but this should be fixed so |
---|
148 | that mDNSResponder will work when talking to other servers that do |
---|
149 | return these error codes. |
---|
150 | |
---|
151 | |
---|
152 | Configuration: |
---|
153 | |
---|
154 | Sample named.conf: |
---|
155 | |
---|
156 | // |
---|
157 | // Include keys file |
---|
158 | // |
---|
159 | include "/etc/rndc.key"; |
---|
160 | // Declares control channels to be used by the rndc utility. |
---|
161 | // |
---|
162 | // It is recommended that 127.0.0.1 be the only address used. |
---|
163 | // This also allows non-privileged users on the local host to manage |
---|
164 | // your name server. |
---|
165 | |
---|
166 | // |
---|
167 | // Default controls |
---|
168 | // |
---|
169 | controls |
---|
170 | { |
---|
171 | inet 127.0.0.1 port 54 allow { any; } keys { "rndc-key"; }; |
---|
172 | }; |
---|
173 | |
---|
174 | options |
---|
175 | { |
---|
176 | directory "/var/named"; |
---|
177 | /* |
---|
178 | * If there is a firewall between you and nameservers you want |
---|
179 | * to talk to, you might need to uncomment the query-source |
---|
180 | * directive below. Previous versions of BIND always asked |
---|
181 | * questions using port 53, but BIND 8.1 uses an unprivileged |
---|
182 | * port by default. |
---|
183 | */ |
---|
184 | |
---|
185 | forwarders |
---|
186 | { |
---|
187 | 65.23.128.2; |
---|
188 | 65.23.128.3; |
---|
189 | }; |
---|
190 | |
---|
191 | listen-on port 5030 { 127.0.0.1; }; |
---|
192 | recursion true; |
---|
193 | }; |
---|
194 | |
---|
195 | // |
---|
196 | // a caching only nameserver config |
---|
197 | // |
---|
198 | zone "." IN |
---|
199 | { |
---|
200 | type hint; |
---|
201 | file "named.ca"; |
---|
202 | }; |
---|
203 | |
---|
204 | zone "localhost" IN |
---|
205 | { |
---|
206 | type master; |
---|
207 | file "localhost.zone"; |
---|
208 | allow-update { none; }; |
---|
209 | }; |
---|
210 | |
---|
211 | zone "0.0.127.in-addr.arpa" IN |
---|
212 | { |
---|
213 | type master; |
---|
214 | file "named.local"; |
---|
215 | allow-update { none; }; |
---|
216 | }; |
---|
217 | |
---|
218 | zone "hungrywolf.org." in |
---|
219 | { |
---|
220 | type master; |
---|
221 | file "db.hungrywolf.org"; |
---|
222 | allow-update { key hungrywolf.org.; }; |
---|
223 | }; |
---|
224 | |
---|
225 | zone "157.23.65.in-addr.arpa" IN |
---|
226 | { |
---|
227 | file "db.65.23.157"; |
---|
228 | type master; |
---|
229 | }; |
---|
230 | |
---|
231 | zone "100.255.17.in-addr.arpa" IN |
---|
232 | { |
---|
233 | file "db.17.255.100"; |
---|
234 | type master; |
---|
235 | }; |
---|
236 | |
---|
237 | zone "66.6.24.in-addr.arpa" IN |
---|
238 | { |
---|
239 | file "db.24.6.66"; |
---|
240 | type master; |
---|
241 | }; |
---|
242 | |
---|
243 | key hungrywolf.org. |
---|
244 | { |
---|
245 | algorithm hmac-md5; |
---|
246 | secret "c8LWr16K6ju6KMO5zT6Tyg=="; |
---|
247 | }; |
---|
248 | |
---|
249 | logging |
---|
250 | { |
---|
251 | category default { _default_log; }; |
---|
252 | |
---|
253 | channel _default_log |
---|
254 | { |
---|
255 | file "/Library/Logs/named.log"; |
---|
256 | severity info; |
---|
257 | print-time yes; |
---|
258 | }; |
---|
259 | }; |
---|
260 | |
---|
261 | |
---|
262 | Sample dnsextd.conf: |
---|
263 | |
---|
264 | options { }; |
---|
265 | |
---|
266 | key "hungrywolf.org." |
---|
267 | { |
---|
268 | secret "c8LWr16K6ju6KMO5zT6Tyg=="; |
---|
269 | }; |
---|
270 | |
---|
271 | zone "hungrywolf.org." |
---|
272 | { |
---|
273 | type private; |
---|
274 | allow-query { key hungrywolf.org.; }; |
---|
275 | }; |
---|