]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/dnssec_v2/dnssec_v2.c
mDNSResponder-1310.40.42.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / dnssec_v2 / dnssec_v2.c
1 //
2 // dnssec_v2.c
3 // mDNSResponder
4 //
5 // Copyright (c) 2020 Apple Inc. All rights reserved.
6 //
7
8 #include <AssertMacros.h> // for require_* macro
9 #include <os/feature_private.h> // for feature flag
10 #include "mDNSEmbeddedAPI.h"
11 #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
12 #include "uds_daemon.h"
13 #include "DNSCommon.h"
14 #include "dnssec_v2.h"
15 #include "dnssec_v2_helper.h"
16 #include "dnssec_v2_validation.h"
17 #include "dnssec_v2_trust_anchor.h"
18 #include "dnssec_v2_client.h"
19
20 // MARK: - Macros
21
22 #define DNSSEC_OK_BIT 0x8000
23
24 // MARK: - External Functions
25
26 mDNSexport mDNSBool
27 enables_dnssec_validation(const DNSQuestion * _Nonnull q) {
28 return q->DNSSECStatus.enable_dnssec;
29 }
30
31 //======================================================================================================================
32
33 // Check if the question could be validated with DNSSEC.
34 mDNSexport mDNSBool
35 is_eligible_for_dnssec(const domainname * const _Nonnull name, mDNSu16 question_type) {
36 mDNSBool is_eligible = mDNSfalse;
37
38 require_quiet(!IsLocalDomain(name), exit);
39 require_quiet(question_type != kDNSServiceType_RRSIG, exit);
40 require_quiet(question_type != kDNSServiceType_ANY, exit);
41
42 is_eligible = mDNStrue;
43 exit:
44 return is_eligible;
45 }
46
47 //======================================================================================================================
48
49 mDNSexport void
50 get_denial_records_from_negative_cache_to_dnssec_context(
51 const mDNSBool enable_dnssec,
52 dnssec_context_t * const _Nonnull context,
53 CacheRecord * const _Nonnull rr) {
54
55 if (enable_dnssec) {
56 context->denial_of_existence_records = rr->denial_of_existence_records;
57 }
58 }
59
60 //======================================================================================================================
61
62 mDNSexport void
63 set_denial_records_in_cache_record(
64 CacheRecord * const _Nonnull cache_record,
65 denial_of_existence_records_t * _Nullable * _Nonnull denial_records_ptr) {
66
67 cache_record->denial_of_existence_records = *denial_records_ptr;
68 *denial_records_ptr = mDNSNULL;
69 }
70
71 //======================================================================================================================
72
73 mDNSexport void
74 release_denial_records_in_cache_record(CacheRecord * const _Nonnull cache_record) {
75 if (cache_record->denial_of_existence_records != mDNSNULL) {
76 destroy_denial_of_existence_records_t(cache_record->denial_of_existence_records);
77 cache_record->denial_of_existence_records = mDNSNULL;
78 }
79 }
80
81 //======================================================================================================================
82
83 mDNSexport void
84 update_denial_records_in_cache_record(
85 CacheRecord * const _Nonnull cache_record,
86 denial_of_existence_records_t * _Nullable * _Nonnull denial_records_ptr) {
87
88 if (cache_record->denial_of_existence_records != mDNSNULL) {
89 destroy_denial_of_existence_records_t(cache_record->denial_of_existence_records);
90 }
91 cache_record->denial_of_existence_records = *denial_records_ptr;
92 *denial_records_ptr = mDNSNULL;
93 }
94
95 //======================================================================================================================
96
97 mDNSexport mDNSBool
98 adds_denial_records_in_cache_record(
99 const ResourceRecord * _Nonnull const rr,
100 const mDNSBool enable_dnssec,
101 denial_of_existence_records_t * _Nullable * _Nonnull denials_ptr) {
102
103 mDNSBool not_answer_but_required_for_dnssec = mDNSfalse;
104 mStatus error = mStatus_NoError;
105
106 require_quiet(enable_dnssec, exit);
107 require_quiet(record_denies_existence_of_dnssec_question(rr), exit);
108
109 if (*denials_ptr == mDNSNULL) {
110 *denials_ptr = create_denial_of_existence_records_t();
111 require_quiet(*denials_ptr != mDNSNULL, exit);
112 }
113
114 error = add_to_denial_of_existence_records_t(*denials_ptr, rr);
115 require_quiet(error == mStatus_NoError, exit);
116 not_answer_but_required_for_dnssec = mDNStrue;
117
118 exit:
119 if (error != mStatus_NoError) {
120 if (*denials_ptr != mDNSNULL) destroy_denial_of_existence_records_t(*denials_ptr);
121 *denials_ptr = mDNSNULL;
122 }
123 return not_answer_but_required_for_dnssec;
124 }
125
126 //======================================================================================================================
127
128 mDNSexport mDNSBool
129 are_records_in_the_same_cache_set_for_dnssec(
130 const ResourceRecord * const _Nonnull left,
131 const ResourceRecord * const _Nonnull right) {
132
133 if (left->rrtype != kDNSType_RRSIG) {
134 return mDNStrue;
135 }
136
137 return rrsig_records_cover_the_same_record_type(left, right);
138 }
139
140 //======================================================================================================================
141
142 // Check if the current record type belongs to the question that enables DNSSEC.
143 mDNSexport mDNSBool
144 record_type_answers_dnssec_question(const ResourceRecord * const _Nonnull record, const mDNSu16 qtype) {
145 mDNSBool result = mDNSfalse;
146
147 switch (record->rrtype) {
148 case kDNSType_CNAME:
149 result = mDNStrue;
150 break;
151 case kDNSType_RRSIG: {
152 mDNSu16 type_covered = get_covered_type_of_dns_type_rrsig_t(record->rdata->u.data);
153
154 if (qtype == kDNSType_RRSIG
155 || type_covered == qtype
156 || type_covered == kDNSType_CNAME) { // Returned RRSIG covers a CNAME for the current question.
157 // RRSIG that covers NSEC/NSEC3 also answers question, but it provides non-existence proof.
158 result = mDNStrue;
159 }
160 }
161 break;
162 // kDNSType_DS and kDNSType_DNSKEY also applies to the default case here.
163 default:
164 if (record->rrtype == qtype) {
165 result = mDNStrue;
166 }
167 // NSEC/NSEC3 or RRSIG that covers NSEC/NSEC3 also answers question, but they provides non-existence proof,
168 // so they do not answer the question positively, and the function should return false.
169 break;
170 }
171
172 return result;
173 }
174
175 //======================================================================================================================
176
177 mDNSexport mDNSBool
178 rrsig_records_cover_the_same_record_type(const ResourceRecord * const _Nonnull left, const ResourceRecord * const _Nonnull right) {
179 mDNSu16 type_covered_left = get_covered_type_of_dns_type_rrsig_t(left->rdata->u.data);
180 mDNSu16 type_covered_right = get_covered_type_of_dns_type_rrsig_t(right->rdata->u.data);
181
182 return type_covered_left == type_covered_right;
183 }
184
185 //======================================================================================================================
186
187 // Used by mDNSCoreReceiveResponse, to check if the current NSEC/NSEC3 record belongs to the question that enables DNSSEC
188 mDNSexport mDNSBool
189 record_denies_existence_of_dnssec_question(const ResourceRecord * const _Nonnull record) {
190 const mDNSu16 rr_type = record->rrtype;
191 mDNSu16 type_covered;
192 mDNSBool acceptable_denial_of_existence = mDNSfalse;
193
194 // Temporarily disbale NSEC validation, it should also check if it is NSEC(or the corresponding RRSIG covers NSEC)
195 if (rr_type == kDNSType_NSEC3) {
196 acceptable_denial_of_existence = mDNStrue;
197 } else if (rr_type == kDNSType_RRSIG) {
198 type_covered = get_covered_type_of_dns_type_rrsig_t(record->rdata->u.data);
199 // Same here, temporarily disbale NSEC validation.
200 if (type_covered == kDNSType_NSEC3) {
201 acceptable_denial_of_existence = mDNStrue;
202 }
203 }
204
205 return acceptable_denial_of_existence;
206 }
207
208 //======================================================================================================================
209
210 // The main DNSSEC callback function, it replaces the original user callback function, and becomes a middle layer
211 // between the mDNSCore and user callback function, all the DNSSEC related operation happens here:
212 // 1. Records retrieval
213 // 2. Records validation
214 mDNSexport void
215 query_record_result_reply_with_dnssec(
216 mDNS *const _Null_unspecified m,
217 DNSQuestion * _Null_unspecified question,
218 const ResourceRecord * const _Null_unspecified const_answer,
219 QC_result add_record,
220 DNSServiceErrorType dns_result_error,
221 void * _Null_unspecified context) {
222
223 dnssec_context_t * dnssec_context = (dnssec_context_t *)context;
224 QueryRecordClientRequest * primary_request = GET_PRIMARY_REQUEST(dnssec_context);
225 ResourceRecord * const answer = (ResourceRecord *)const_answer;
226 mDNSBool anchor_reached = mDNSfalse;
227 mDNSBool stop_process = mDNSfalse;
228 dnssec_retrieval_result_t retrieval_result = dnssec_retrieval_no_error;
229 dnssec_validation_result_t validation_result;
230 mDNSu32 request_id = primary_request->op.reqID;
231 returned_answers_t * const returned_answers = &dnssec_context->returned_answers;
232
233 switch (add_record) {
234 case QC_add:
235 case QC_rmv:
236 case QC_suppressed:
237 break;
238 // QC_addnocache and QC_forceresponse are all cases where the returned resource record is not in the cache.
239 // We temporarily ignore those two cases.
240 case QC_addnocache:
241 case QC_forceresponse:
242 default:
243 log_error("[R%u] QC_result other than add, remove, suppressed is returned; add_record=%d",
244 request_id, add_record);
245 return;
246 }
247
248 if (dns_result_error == kDNSServiceErr_NoError) {
249 retrieval_result = add_no_error_records(m, question, answer, add_record, dns_result_error, dnssec_context);
250 } else {
251 retrieval_result = add_denial_of_existence_records(m, question, answer, add_record, dns_result_error, dnssec_context);
252 }
253
254 do {
255 // handle any error case when addign records
256 stop_process = handle_retrieval_result(question, context, retrieval_result, dns_result_error, m);
257 // WARNING: If stop_process is set to true here, we should not touch anything including dnssec_context, because
258 // we might free the object related to the current dnssec request, and we would get memory fault if using it.
259
260 // If we have error when adding record, then we should not continue.
261 if (stop_process) {
262 break;
263 }
264
265 // check if we could reach the trust anchor with the records we have currently
266 anchor_reached = trust_anchor_can_be_reached(dnssec_context);
267 if (anchor_reached) {
268 // if so, validate the from the leaf to the root(trust anchor)
269 validation_result = validate_dnssec(dnssec_context);
270
271 // handle the validation result such as returning DNSSEC-secure answer to user, return error code to user
272 stop_process = handle_validation_result(question, context, validation_result, dns_result_error, m);
273
274 // If we already returned the answer/error to user, there is no more to do.
275 if (stop_process) {
276 break;
277 }
278 } else if (returned_answers->error != kDNSServiceErr_Invalid) {
279 // previous verified record set cannot establish trust chain, deliver rmv event for all returned records
280 if (returned_answers->type != cname_response) { // Do not deliver RMV for CNAME records.
281 // Since here we are returning the records on the behave of the primary request, the question being
282 // returned should be the question from the primary request instead of the possible CNAME question that
283 // is started by the DNSSEC handler itself.
284 stop_process = deliver_remove_to_callback_with_all_returned_answers(dnssec_context, returned_answers, m,
285 GET_PRIMARY_QUESTION(dnssec_context), question);
286 require_quiet(!stop_process, exit);
287 }
288 uninitialize_returned_answers_t(returned_answers);
289 initialize_returned_answers_t(returned_answers, dnssec_indeterminate, kDNSServiceErr_Invalid);
290 }
291
292 // If the records we have currently is not enough to form a chian of trust, keep querying for more records until
293 // the trust anchor
294 retrieval_result = fetch_necessary_dnssec_records(dnssec_context, anchor_reached);
295
296 // handle the result of fetch_necessary_dnssec_records such as returning error to user if some error occurs when
297 // querying for more records
298 stop_process = handle_retrieval_result(question, context, retrieval_result, dns_result_error, m);
299 if (stop_process) {
300 break;
301 }
302 } while (retrieval_result == dnssec_retrieval_validate_again);
303
304 exit:
305 return;
306 }
307
308 //======================================================================================================================
309
310 mDNSexport void
311 stop_dnssec_if_enable_dnssec(QueryRecordClientRequest * const _Nonnull request) {
312 DNSQuestion * const q = &request->op.q;
313 if (!q->DNSSECStatus.enable_dnssec) {
314 return;
315 }
316 stop_dnssec(request);
317 }
318
319
320 //======================================================================================================================
321
322 mDNSexport void
323 stop_dnssec(QueryRecordClientRequest * const _Nonnull request) {
324 DNSQuestion * const q = &request->op.q;
325 mDNSu32 request_id = request->op.reqID;
326 if (!q->DNSSECStatus.enable_dnssec) {
327 goto exit;
328 }
329
330 dnssec_context_t * const dnssec_context = (dnssec_context_t *)q->DNSSECStatus.context;
331 list_t * const zones = &dnssec_context->zone_chain;
332 original_t * const original = &dnssec_context->original;
333 const original_request_parameters_t * const param = &original->original_parameters;
334
335 log_default("[R%u] Stopping " PUB_S "DNSSEC request -- hostname: " PRI_DM_NAME ", type: " PUB_S, request_id,
336 dnssec_context->primary_dnssec_context == mDNSNULL ? "primary " : "sub-",
337 DM_NAME_PARAM(&param->question_name), DNSTypeName(param->question_type));
338
339 // stop and clean zone, dnssec_zone_t node will be deleted in destroy_dnssec_context_t
340 for (list_node_t * zone_node = list_get_first(zones); !list_has_ended(zones, zone_node); zone_node = list_next(zone_node)) {
341 dnssec_zone_t * zone = (dnssec_zone_t *)zone_node->data;
342 stop_and_clean_dnssec_zone_t(zone);
343 }
344
345 // Stop the sub CNAME request.
346 if (dnssec_context->subtask_dnssec_context != mDNSNULL) {
347 // Since we will not deliver RMV so there is no need to check if we should stop the request immediately because
348 // the client cancels the request in the callback.
349 stop_sub_cname_request_and_dnssec(q, dnssec_context, mDNSfalse, mDNSNULL);
350 }
351
352 // leave original->original_request to be released by QueryRecordClientRequestStop
353 uninitialize_originals_with_rrsig_t(&original->original_result_with_rrsig);
354
355 // undo create_dnssec_context_t
356 destroy_dnssec_context_t(dnssec_context);
357
358 exit:
359 return;
360 }
361
362 //======================================================================================================================
363
364 mDNSexport mDNSBool
365 stop_sub_cname_request_and_dnssec(DNSQuestion * const question, dnssec_context_t * const _Nonnull dnssec_context,
366 const mDNSBool deliver_remove, mDNS * const _Nullable m) {
367
368 dnssec_context_t * const cname_dnssec_context = dnssec_context->subtask_dnssec_context;
369 DNSQuestion * const primary_question = GET_PRIMARY_QUESTION(dnssec_context);
370 mDNSBool stop_immediately = mDNSfalse;
371 require_quiet(cname_dnssec_context != mDNSNULL, exit);
372
373 // If we call this function because the CNAME reference chain has changed, all the answers that
374 // are returned to the client needs to be removed first.
375 if (deliver_remove) {
376 for (dnssec_context_t * context_i = cname_dnssec_context; context_i != mDNSNULL; context_i = context_i->subtask_dnssec_context) {
377 // if it is not kDNSServiceErr_Invalid, it means that we have returned something.
378 if (context_i->returned_answers.error == kDNSServiceErr_Invalid) {
379 continue;
380 }
381 // Do not deliver RMV event for CNAME answer, since mDNSResponder never rewinds the CNAME chain, DNSSEC API
382 // needs to follow the same behavior.
383 if (context_i->returned_answers.type == cname_response) {
384 continue;
385 }
386 stop_immediately = deliver_remove_to_callback_with_all_returned_answers(context_i,
387 &context_i->returned_answers, m, primary_question, question);
388 require_quiet(!stop_immediately, exit);
389 }
390 }
391
392 QueryRecordClientRequest * cname_request = cname_dnssec_context->me;
393 require_action_quiet(cname_request == &dnssec_context->request_to_follow_cname, exit, stop_immediately = mDNStrue;
394 log_debug("cname request does not points back to the request_to_follow_cname"));
395 QueryRecordOpStopForClientRequest(&cname_request->op);
396 stop_dnssec(cname_request);
397 dnssec_context->subtask_dnssec_context = mDNSNULL;
398
399 exit:
400 return stop_immediately;
401 }
402
403 #endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)