2 * Copyright (c) 2012-2019 Apple Inc. All rights reserved.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 // ***************************************************************************
18 // DNSSECSupport.c: Platform specific support for DNSSEC like fetching root
19 // trust anchor and dnssec probes etc.
20 // ***************************************************************************
22 #include "mDNSEmbeddedAPI.h"
23 #include "DNSCommon.h" // For mDNS_Lock, mDNS_Random
25 #include "DNSSECSupport.h"
27 #include <CommonCrypto/CommonDigest.h> // For Hash algorithms SHA1 etc.
29 // Following are needed for fetching the root trust anchor dynamically
30 #include <CoreFoundation/CoreFoundation.h>
31 #include <libxml2/libxml/parser.h>
32 #include <libxml2/libxml/tree.h>
33 #include <libxml2/libxml/xmlmemory.h>
37 #define ROOT_TA_UPDATE_INTERVAL (30 * 24 * 3600) // seconds
39 // After 100 days, the test anchors are not valid. Just an arbitrary number
40 // to configure validUntil.
41 #define TEST_TA_EXPIRE_INTERVAL (100 * 24 * 4600)
43 // When we can't fetch the root TA due to network errors etc., we start off a timer
44 // to fire at 60 seconds and then keep doubling it till we fetch it
45 #define InitialTAFetchInterval 60
46 #define DNSSECProbePercentage 1
50 DNSQuestion DNSSECProbeQuestion
;
53 mDNSlocal
int RegisterNotification(mDNS
*const m
, unsigned int interval
);
55 mDNSlocal
void LinkTrustAnchor(mDNS
*const m
, TrustAnchor
*ta
)
60 TrustAnchor
**t
= &m
->TrustAnchors
;
69 for (i
= 0; i
< ta
->digestLen
; i
++)
71 length
+= mDNS_snprintf(buffer
+length
, sizeof(buffer
)-1-length
, "%x", p
[i
]);
73 LogInfo("LinkTrustAnchor: Zone %##s, keytag %d, alg %d, digestType %d, digestLen %d, digest %s", ta
->zone
.c
, ta
->rds
.keyTag
,
74 ta
->rds
.alg
, ta
->rds
.digestType
, ta
->digestLen
, buffer
);
77 mDNSlocal
void DelTrustAnchor(mDNS
*const m
, const domainname
*zone
)
79 TrustAnchor
**ta
= &m
->TrustAnchors
;
82 while (*ta
&& !SameDomainName(&(*ta
)->zone
, zone
))
85 // First time, we won't find the TrustAnchor in the list as it has
91 *ta
= (*ta
)->next
; // Cut this record from the list
94 mDNSPlatformMemFree(tmp
->rds
.digest
);
95 mDNSPlatformMemFree(tmp
);
98 mDNSlocal
void AddTrustAnchor(mDNS
*const m
, const domainname
*zone
, mDNSu16 keytag
, mDNSu8 alg
, mDNSu8 digestType
, int diglen
,
101 TrustAnchor
*ta
, *tmp
;
102 mDNSu32 t
= (mDNSu32
) time(NULL
);
104 // Check for duplicates
105 tmp
= m
->TrustAnchors
;
108 if (SameDomainName(zone
, &tmp
->zone
) && tmp
->rds
.keyTag
== keytag
&& tmp
->rds
.alg
== alg
&& tmp
->rds
.digestType
== digestType
&&
109 !memcmp(tmp
->rds
.digest
, digest
, diglen
))
111 LogMsg("AddTrustAnchors: Found a duplicate");
117 ta
= (TrustAnchor
*) mDNSPlatformMemAllocateClear(sizeof(*ta
));
120 LogMsg("AddTrustAnchor: malloc failure ta");
123 ta
->rds
.keyTag
= keytag
;
125 ta
->rds
.digestType
= digestType
;
126 ta
->rds
.digest
= digest
;
127 ta
->digestLen
= diglen
;
129 ta
->validUntil
= t
+ TEST_TA_EXPIRE_INTERVAL
;
130 AssignDomainName(&ta
->zone
, zone
);
133 LinkTrustAnchor(m
, ta
);
136 #define HexVal(X) ( ((X) >= '0' && (X) <= '9') ? ((X) - '0' ) : \
137 ((X) >= 'A' && (X) <= 'F') ? ((X) - 'A' + 10) : \
138 ((X) >= 'a' && (X) <= 'f') ? ((X) - 'a' + 10) : -1)
140 mDNSlocal mDNSu8
*ConvertDigest(char *digest
, int digestType
, int *diglen
)
147 case SHA1_DIGEST_TYPE
:
148 *diglen
= CC_SHA1_DIGEST_LENGTH
;
150 case SHA256_DIGEST_TYPE
:
151 *diglen
= CC_SHA256_DIGEST_LENGTH
;
154 LogMsg("ConvertDigest: digest type %d not supported", digestType
);
157 dig
= (mDNSu8
*) mDNSPlatformMemAllocate(*diglen
);
160 LogMsg("ConvertDigest: malloc failure");
164 for (j
=0,i
=0; i
<*diglen
*2; i
+=2)
167 l
= HexVal(digest
[i
]);
168 h
= HexVal(digest
[i
+1]);
169 if (l
<0 || h
<0) { LogMsg("ConvertDigest: Cannot convert digest"); mDNSPlatformMemFree(dig
); return NULL
;}
170 dig
[j
++] = (mDNSu8
)((l
<< 4) | h
);
175 // All the children are in a linked list
177 // <TrustAnchor> has two children: <Zone> and <KeyDigest>
178 // <KeyDigest> has four children <KeyTag> <Algorithm> <DigestType> <Digest>
180 // Returns false if failed to parse the element i.e., malformed xml document.
181 // Validity of the actual values itself is done outside the function.
182 mDNSlocal mDNSBool
ParseElementChildren(xmlDocPtr tadoc
, xmlNode
*node
, TrustAnchor
*ta
)
185 xmlChar
*val1
, *val2
, *val
;
186 char *invalid
= NULL
;
188 val
= val1
= val2
= NULL
;
190 for (cur_node
= node
; cur_node
; cur_node
= cur_node
->next
)
195 val
= xmlNodeListGetString(tadoc
, cur_node
->xmlChildrenNode
, 1);
198 LogInfo("ParseElementChildren: NULL value for %s", cur_node
->name
);
201 if (!xmlStrcmp(cur_node
->name
, (const xmlChar
*)"Zone"))
203 // MaeDomainNameFromDNSNameString does not work for "."
204 if (!xmlStrcmp(val
, (const xmlChar
*)"."))
208 else if (!MakeDomainNameFromDNSNameString(&ta
->zone
, (char *)val
))
210 LogMsg("ParseElementChildren: Cannot parse Zone %s", val
);
215 LogInfo("ParseElementChildren: Element %s, value %##s", cur_node
->name
, ta
->zone
.c
);
218 else if (!xmlStrcmp(cur_node
->name
, (const xmlChar
*)"KeyTag"))
220 ta
->rds
.keyTag
= strtol((const char *)val
, &invalid
, 10);
221 if (*invalid
!= '\0')
223 LogMsg("ParseElementChildren: KeyTag invalid character %d", *invalid
);
228 LogInfo("ParseElementChildren: Element %s, value %d", cur_node
->name
, ta
->rds
.keyTag
);
231 else if (!xmlStrcmp(cur_node
->name
, (const xmlChar
*)"Algorithm"))
233 ta
->rds
.alg
= strtol((const char *)val
, &invalid
, 10);
234 if (*invalid
!= '\0')
236 LogMsg("ParseElementChildren: Algorithm invalid character %c", *invalid
);
241 LogInfo("ParseElementChildren: Element %s, value %d", cur_node
->name
, ta
->rds
.alg
);
244 else if (!xmlStrcmp(cur_node
->name
, (const xmlChar
*)"DigestType"))
246 ta
->rds
.digestType
= strtol((const char *)val
, &invalid
, 10);
247 if (*invalid
!= '\0')
249 LogMsg("ParseElementChildren: Algorithm invalid character %c", *invalid
);
254 LogInfo("ParseElementChildren: Element %s, value %d", cur_node
->name
, ta
->rds
.digestType
);
257 else if (!xmlStrcmp(cur_node
->name
, (const xmlChar
*)"Digest"))
260 mDNSu8
*dig
= ConvertDigest((char *)val
, ta
->rds
.digestType
, &diglen
);
263 LogInfo("ParseElementChildren: Element %s, digest %s", cur_node
->name
, val
);
264 ta
->digestLen
= diglen
;
265 ta
->rds
.digest
= dig
;
269 LogMsg("ParseElementChildren: Element %s, NULL digest", cur_node
->name
);
273 else if (!xmlStrcmp(cur_node
->name
, (const xmlChar
*)"KeyDigest"))
276 val1
= xmlGetProp(cur_node
, (const xmlChar
*)"validFrom");
279 char *s
= strptime((const char *)val1
, "%Y-%m-%dT%H:%M:%S", &tm
);
282 LogMsg("ParseElementChildren: Parsing ValidFrom failed %s", val1
);
287 ta
->validFrom
= (mDNSu32
)timegm(&tm
);
290 val2
= xmlGetProp(cur_node
, (const xmlChar
*)"validUntil");
293 char *s
= strptime((const char *)val2
, "%Y-%m-%dT%H:%M:%S", &tm
);
296 LogMsg("ParseElementChildren: Parsing ValidFrom failed %s", val2
);
301 ta
->validUntil
= (mDNSu32
)timegm(&tm
);
306 // If there is no validUntil, set it to the next probing interval
307 mDNSu32 t
= (mDNSu32
) time(NULL
);
308 ta
->validUntil
= t
+ ROOT_TA_UPDATE_INTERVAL
;
310 LogInfo("ParseElementChildren: ValidFrom time %u, validUntil %u", (unsigned)ta
->validFrom
, (unsigned)ta
->validUntil
);
330 mDNSlocal mDNSBool
ValidateTrustAnchor(TrustAnchor
*ta
)
332 time_t currTime
= time(NULL
);
334 // Currently only support trust anchor for root.
335 if (!SameDomainName(&ta
->zone
, (const domainname
*)"\000"))
337 LogInfo("ParseElementChildren: Zone %##s not root", ta
->zone
.c
);
341 switch (ta
->rds
.digestType
)
343 case SHA1_DIGEST_TYPE
:
344 if (ta
->digestLen
!= CC_SHA1_DIGEST_LENGTH
)
346 LogMsg("ValidateTrustAnchor: Invalid digest len %d for SHA1", ta
->digestLen
);
350 case SHA256_DIGEST_TYPE
:
351 if (ta
->digestLen
!= CC_SHA256_DIGEST_LENGTH
)
353 LogMsg("ValidateTrustAnchor: Invalid digest len %d for SHA256", ta
->digestLen
);
358 LogMsg("ValidateTrustAnchor: digest type %d not supported", ta
->rds
.digestType
);
363 LogMsg("ValidateTrustAnchor: digest NULL for %d", ta
->rds
.digestType
);
368 case CRYPTO_RSA_SHA512
:
369 case CRYPTO_RSA_SHA256
:
370 case CRYPTO_RSA_NSEC3_SHA1
:
371 case CRYPTO_RSA_SHA1
:
374 LogMsg("ValidateTrustAnchor: Algorithm %d not supported", ta
->rds
.alg
);
378 if (DNS_SERIAL_LT(currTime
, ta
->validFrom
))
380 LogMsg("ValidateTrustAnchor: Invalid ValidFrom time %u, currtime %u", (unsigned)ta
->validFrom
, (unsigned)currTime
);
383 if (DNS_SERIAL_LT(ta
->validUntil
, currTime
))
385 LogMsg("ValidateTrustAnchor: Invalid ValidUntil time %u, currtime %u", (unsigned)ta
->validUntil
, (unsigned)currTime
);
391 mDNSlocal mDNSBool
ParseElement(xmlDocPtr tadoc
, xmlNode
* a_node
, TrustAnchor
*ta
)
393 xmlNode
*cur_node
= NULL
;
395 for (cur_node
= a_node
; cur_node
; cur_node
= cur_node
->next
)
397 if (cur_node
->type
== XML_ELEMENT_NODE
)
399 // There could be multiple KeyDigests per TrustAnchor. We keep parsing till we
400 // reach the last one or we encounter an error in parsing the document.
401 if (!xmlStrcmp(cur_node
->name
, (const xmlChar
*)"KeyDigest"))
404 mDNSPlatformMemFree(ta
->rds
.digest
);
405 ta
->rds
.digestType
= 0;
408 if (!ParseElementChildren(tadoc
, cur_node
->children
, ta
))
410 if (!ParseElement(tadoc
, cur_node
->children
, ta
))
417 mDNSlocal
void TAComplete(mDNS
*const m
, void *context
)
419 TrustAnchor
*ta
= (TrustAnchor
*)context
;
421 DelTrustAnchor(m
, &ta
->zone
);
422 LinkTrustAnchor(m
, ta
);
425 mDNSlocal
void FetchRootTA(mDNS
*const m
)
427 CFStringRef urlString
= CFSTR("https://data.iana.org/root-anchors/root-anchors.xml");
429 CFStringRef fileRef
= NULL
;
430 const char *xmlFileName
= NULL
;
433 static unsigned int RootTAFetchInterval
= InitialTAFetchInterval
;
437 TrustAnchor
*ta
= (TrustAnchor
*) mDNSPlatformMemAllocateClear(sizeof(*ta
));
440 LogMsg("FetchRootTA: TrustAnchor alloc failed");
444 url
= CFURLCreateWithString(NULL
, urlString
, NULL
);
447 LogMsg("FetchRootTA: CFURLCreateWithString error");
448 mDNSPlatformMemFree(ta
);
452 #pragma clang diagnostic push
453 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
454 // If we can't fetch the XML file e.g., network problems, trigger a timer. All other failures
455 // should hardly happen in practice for which schedule the normal interval to refetch the TA.
456 Boolean success
= CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault
, url
, &xmlData
, NULL
, NULL
, NULL
);
457 #pragma clang diagnostic pop
460 LogInfo("FetchRootTA: CFURLCreateDataAndPropertiesFromResource error");
462 mDNSPlatformMemFree(ta
);
463 RegisterNotification(m
, RootTAFetchInterval
);
464 RootTAFetchInterval
*= 2 + 1;
468 // get the name of the last component from the url, libxml will use it if
469 // it has to report an error
470 fileRef
= CFURLCopyLastPathComponent(url
);
473 xmlFileName
= CFStringGetCStringPtr(fileRef
, kCFStringEncodingUTF8
);
476 if (!CFStringGetCString(fileRef
, buf
, sizeof(buf
), kCFStringEncodingUTF8
) )
477 strlcpy(buf
, "nofile.xml", sizeof(buf
));
478 xmlFileName
= (const char *)buf
;
482 // Parse the XML and get the CFXMLTree.
483 xmlDocPtr tadoc
= xmlReadMemory((const char*)CFDataGetBytePtr(xmlData
),
484 (int)CFDataGetLength(xmlData
), xmlFileName
, NULL
, 0);
493 LogMsg("FetchRootTA: xmlReadMemory failed");
497 xmlNodePtr root
= xmlDocGetRootElement(tadoc
);
500 LogMsg("FetchRootTA: Cannot get root element");
504 if (ParseElement(tadoc
, root
, ta
) && ValidateTrustAnchor(ta
))
506 // Do the actual addition of TA on the main queue.
507 mDNSPlatformDispatchAsync(m
, ta
, TAComplete
);
512 mDNSPlatformMemFree(ta
->rds
.digest
);
513 mDNSPlatformMemFree(ta
);
518 RegisterNotification(m
, ROOT_TA_UPDATE_INTERVAL
);
519 RootTAFetchInterval
= InitialTAFetchInterval
;
524 #if APPLE_OSX_mDNSResponder && !TARGET_OS_IPHONE
525 mDNSlocal
void DNSSECProbeCallback(mDNS
*const m
, DNSQuestion
*question
, const ResourceRecord
*const answer
, QC_result AddRecord
)
531 if ((m
->timenow
- question
->StopTime
) >= 0)
534 LogDNSSEC("DNSSECProbeCallback: Question %##s (%s) timed out", question
->qname
.c
, DNSTypeName(question
->qtype
));
535 mDNS_StopQuery(m
, question
);
540 // Wait till we get the DNSSEC results. If we get a negative response e.g., no DNS servers, the
541 // question will be restarted by the core and we should have the DNSSEC results eventually.
542 if (AddRecord
!= QC_dnssec
)
544 LogDNSSEC("DNSSECProbeCallback: Question %##s (%s)", question
->qname
.c
, DNSTypeName(question
->qtype
), RRDisplayString(m
, answer
));
548 LogDNSSEC("DNSSECProbeCallback: Question %##s (%s), DNSSEC status %s", question
->qname
.c
, DNSTypeName(question
->qtype
),
549 DNSSECStatusName(question
->ValidationStatus
));
551 mDNS_StopQuery(m
, question
);
554 // Send a DNSSEC probe just for the sake of collecting DNSSEC statistics.
555 mDNSexport
void DNSSECProbe(mDNS
*const m
)
559 if (DNSSECProbeQuestion
.ThisQInterval
!= -1)
562 rand
= mDNSRandom(FutureTime
) % 100;
563 // Probe 1% of the time
564 if (rand
>= DNSSECProbePercentage
)
567 mDNS_DropLockBeforeCallback();
568 InitializeQuestion(m
, &DNSSECProbeQuestion
, mDNSInterface_Any
, (const domainname
*)"\003com", kDNSType_DS
, DNSSECProbeCallback
, mDNSNULL
);
569 DNSSECProbeQuestion
.ValidatingResponse
= 0;
570 DNSSECProbeQuestion
.ValidationRequired
= DNSSEC_VALIDATION_SECURE
;
572 BumpDNSSECStats(m
, kStatsActionIncrement
, kStatsTypeProbe
, 1);
573 mDNS_StartQuery(m
, &DNSSECProbeQuestion
);
574 mDNS_ReclaimLockAfterCallback();
576 #endif // APPLE_OSX_mDNSResponder && !TARGET_OS_IPHONE
578 // For now we fetch the root trust anchor and update the local copy
579 mDNSexport
void UpdateTrustAnchors(mDNS
*const m
)
581 // Register for a notification to fire immediately which in turn will update
583 if (RegisterNotification(m
, 1))
585 LogMsg("UpdateTrustAnchors: ERROR!! failed to register for notification");
589 mDNSlocal
int RegisterNotification(mDNS
*const m
, unsigned int interval
)
591 int len
= strlen("com.apple.system.notify.service.timer:+") + 21; // 21 bytes to accomodate the interval
596 // Starting "interval" second from now (+ below indicates relative) register for a notification
597 blen
= mDNS_snprintf(buffer
, sizeof(buffer
), "com.apple.system.notify.service.timer:+%us", interval
);
598 if (blen
>= sizeof(buffer
))
600 LogMsg("RegisterNotification: Buffer too small blen %d, buffer size %d", blen
, sizeof(buffer
));
603 LogInfo("RegisterNotification: buffer %s", buffer
);
606 notify_cancel(m
->notifyToken
);
609 status
= notify_register_dispatch(buffer
, &m
->notifyToken
,
610 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0),
611 ^(int t
) { (void) t
; FetchRootTA(m
); });
613 if (status
!= NOTIFY_STATUS_OK
)
615 LogMsg("RegisterNotification: notify_register_dispatch failed");
621 mDNSexport mStatus
DNSSECPlatformInit(mDNS
*const m
)
625 m
->TrustAnchors
= mDNSNULL
;
628 // Add a couple of trust anchors for testing purposes.
629 mDNSlocal
const domainname
*testZone
= (const domainname
*)"\007example";
631 char *digest
= "F122E47B5B7D2B6A5CC0A21EADA11D96BB9CC927";
632 mDNSu8
*dig
= ConvertDigest(digest
, 1, &diglen
);
633 if (dig
) AddTrustAnchor(m
, testZone
, 23044, 5, 1, diglen
, dig
);
635 char *digest1
= "D795AE5E1AFB200C6139474199B70EAD3F3484553FD97BE5A43704B8A4791F21";
636 dig
= ConvertDigest(digest1
, 2, &diglen
);
637 if (dig
) AddTrustAnchor(m
, testZone
, 23044, 5, 2, diglen
, dig
);
639 // Add the TA for root zone manually here. We will dynamically fetch the root TA and
640 // update it shortly. If that fails e.g., disconnected from the network, we still
641 // have something to work with.
642 char *digest2
= "49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5";
643 dig
= ConvertDigest(digest2
, 2, &diglen
);
644 if (dig
) AddTrustAnchor(m
, (const domainname
*)"\000", 19036, 8, 2, diglen
, dig
);
646 #if !TARGET_OS_IPHONE
647 DNSSECProbeQuestion
.ThisQInterval
= -1;
649 return mStatus_NoError
;