5 // Copyright (c) 2020 Apple Inc. All rights reserved.
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"
22 #define DNSSEC_OK_BIT 0x8000
24 // MARK: - External Functions
27 enables_dnssec_validation(const DNSQuestion
* _Nonnull q
) {
28 return q
->DNSSECStatus
.enable_dnssec
;
31 //======================================================================================================================
33 // Check if the question could be validated with DNSSEC.
35 is_eligible_for_dnssec(const domainname
* const _Nonnull name
, mDNSu16 question_type
) {
36 mDNSBool is_eligible
= mDNSfalse
;
38 require_quiet(!IsLocalDomain(name
), exit
);
39 require_quiet(question_type
!= kDNSServiceType_RRSIG
, exit
);
40 require_quiet(question_type
!= kDNSServiceType_ANY
, exit
);
42 is_eligible
= mDNStrue
;
47 //======================================================================================================================
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
) {
56 context
->denial_of_existence_records
= rr
->denial_of_existence_records
;
60 //======================================================================================================================
63 set_denial_records_in_cache_record(
64 CacheRecord
* const _Nonnull cache_record
,
65 denial_of_existence_records_t
* _Nullable
* _Nonnull denial_records_ptr
) {
67 cache_record
->denial_of_existence_records
= *denial_records_ptr
;
68 *denial_records_ptr
= mDNSNULL
;
71 //======================================================================================================================
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
;
81 //======================================================================================================================
84 update_denial_records_in_cache_record(
85 CacheRecord
* const _Nonnull cache_record
,
86 denial_of_existence_records_t
* _Nullable
* _Nonnull denial_records_ptr
) {
88 if (cache_record
->denial_of_existence_records
!= mDNSNULL
) {
89 destroy_denial_of_existence_records_t(cache_record
->denial_of_existence_records
);
91 cache_record
->denial_of_existence_records
= *denial_records_ptr
;
92 *denial_records_ptr
= mDNSNULL
;
95 //======================================================================================================================
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
) {
103 mDNSBool not_answer_but_required_for_dnssec
= mDNSfalse
;
104 mStatus error
= mStatus_NoError
;
106 require_quiet(enable_dnssec
, exit
);
107 require_quiet(record_denies_existence_of_dnssec_question(rr
), exit
);
109 if (*denials_ptr
== mDNSNULL
) {
110 *denials_ptr
= create_denial_of_existence_records_t();
111 require_quiet(*denials_ptr
!= mDNSNULL
, exit
);
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
;
119 if (error
!= mStatus_NoError
) {
120 if (*denials_ptr
!= mDNSNULL
) destroy_denial_of_existence_records_t(*denials_ptr
);
121 *denials_ptr
= mDNSNULL
;
123 return not_answer_but_required_for_dnssec
;
126 //======================================================================================================================
129 are_records_in_the_same_cache_set_for_dnssec(
130 const ResourceRecord
* const _Nonnull left
,
131 const ResourceRecord
* const _Nonnull right
) {
133 if (left
->rrtype
!= kDNSType_RRSIG
) {
137 return rrsig_records_cover_the_same_record_type(left
, right
);
140 //======================================================================================================================
142 // Check if the current record type belongs to the question that enables DNSSEC.
144 record_type_answers_dnssec_question(const ResourceRecord
* const _Nonnull record
, const mDNSu16 qtype
) {
145 mDNSBool result
= mDNSfalse
;
147 switch (record
->rrtype
) {
151 case kDNSType_RRSIG
: {
152 mDNSu16 type_covered
= get_covered_type_of_dns_type_rrsig_t(record
->rdata
->u
.data
);
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.
162 // kDNSType_DS and kDNSType_DNSKEY also applies to the default case here.
164 if (record
->rrtype
== qtype
) {
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.
175 //======================================================================================================================
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
);
182 return type_covered_left
== type_covered_right
;
185 //======================================================================================================================
187 // Used by mDNSCoreReceiveResponse, to check if the current NSEC/NSEC3 record belongs to the question that enables DNSSEC
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
;
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
;
205 return acceptable_denial_of_existence
;
208 //======================================================================================================================
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
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
) {
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
;
233 switch (add_record
) {
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.
241 case QC_forceresponse
:
243 log_error("[R%u] QC_result other than add, remove, suppressed is returned; add_record=%d",
244 request_id
, add_record
);
248 if (dns_result_error
== kDNSServiceErr_NoError
) {
249 retrieval_result
= add_no_error_records(m
, question
, answer
, add_record
, dns_result_error
, dnssec_context
);
251 retrieval_result
= add_denial_of_existence_records(m
, question
, answer
, add_record
, dns_result_error
, dnssec_context
);
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.
260 // If we have error when adding record, then we should not continue.
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
);
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
);
274 // If we already returned the answer/error to user, there is no more to do.
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
);
288 uninitialize_returned_answers_t(returned_answers
);
289 initialize_returned_answers_t(returned_answers
, dnssec_indeterminate
, kDNSServiceErr_Invalid
);
292 // If the records we have currently is not enough to form a chian of trust, keep querying for more records until
294 retrieval_result
= fetch_necessary_dnssec_records(dnssec_context
, anchor_reached
);
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
);
302 } while (retrieval_result
== dnssec_retrieval_validate_again
);
308 //======================================================================================================================
311 stop_dnssec_if_enable_dnssec(QueryRecordClientRequest
* const _Nonnull request
) {
312 DNSQuestion
* const q
= &request
->op
.q
;
313 if (!q
->DNSSECStatus
.enable_dnssec
) {
316 stop_dnssec(request
);
320 //======================================================================================================================
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
) {
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
;
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(¶m
->question_name
), DNSTypeName(param
->question_type
));
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
);
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
);
352 // leave original->original_request to be released by QueryRecordClientRequestStop
353 uninitialize_originals_with_rrsig_t(&original
->original_result_with_rrsig
);
355 // undo create_dnssec_context_t
356 destroy_dnssec_context_t(dnssec_context
);
362 //======================================================================================================================
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
) {
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
);
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
) {
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
) {
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
);
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
;
400 return stop_immediately
;
403 #endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)