1 /* -*- Mode: C; tab-width: 4 -*-
3 * Copyright (c) 2012-2013 Apple Inc. All rights reserved.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 // ***************************************************************************
19 // DNSSECSupport.c: Platform specific support for DNSSEC like fetching root
20 // trust anchor and dnssec probes etc.
21 // ***************************************************************************
23 #include "mDNSEmbeddedAPI.h"
24 #include "DNSCommon.h" // For mDNS_Lock, mDNS_Random
26 #include "DNSSECSupport.h"
28 #include <CommonCrypto/CommonDigest.h> // For Hash algorithms SHA1 etc.
30 // Following are needed for fetching the root trust anchor dynamically
31 #include <CoreFoundation/CoreFoundation.h>
32 #include <libxml2/libxml/parser.h>
33 #include <libxml2/libxml/tree.h>
34 #include <libxml2/libxml/xmlmemory.h>
38 #define ROOT_TA_UPDATE_INTERVAL (30 * 24 * 3600) // seconds
40 // After 100 days, the test anchors are not valid. Just an arbitrary number
41 // to configure validUntil.
42 #define TEST_TA_EXPIRE_INTERVAL (100 * 24 * 4600)
44 // When we can't fetch the root TA due to network errors etc., we start off a timer
45 // to fire at 60 seconds and then keep doubling it till we fetch it
46 #define InitialTAFetchInterval 60
47 #define DNSSECProbePercentage 1
51 DNSQuestion DNSSECProbeQuestion;
54 mDNSlocal int RegisterNotification(mDNS *const m, unsigned int interval);
56 mDNSlocal void LinkTrustAnchor(mDNS *const m, TrustAnchor *ta)
61 TrustAnchor **t = &m->TrustAnchors;
70 for (i = 0; i < ta->digestLen; i++)
72 length += mDNS_snprintf(buffer+length, sizeof(buffer)-1-length, "%x", p[i]);
74 LogInfo("LinkTrustAnchor: Zone %##s, keytag %d, alg %d, digestType %d, digestLen %d, digest %s", ta->zone.c, ta->rds.keyTag,
75 ta->rds.alg, ta->rds.digestType, ta->digestLen, buffer);
78 mDNSlocal void DelTrustAnchor(mDNS *const m, const domainname *zone)
80 TrustAnchor **ta = &m->TrustAnchors;
83 while (*ta && !SameDomainName(&(*ta)->zone, zone))
86 // First time, we won't find the TrustAnchor in the list as it has
92 *ta = (*ta)->next; // Cut this record from the list
95 mDNSPlatformMemFree(tmp->rds.digest);
96 mDNSPlatformMemFree(tmp);
99 mDNSlocal void AddTrustAnchor(mDNS *const m, const domainname *zone, mDNSu16 keytag, mDNSu8 alg, mDNSu8 digestType, int diglen,
102 TrustAnchor *ta, *tmp;
103 mDNSu32 t = (mDNSu32) time(NULL);
105 // Check for duplicates
106 tmp = m->TrustAnchors;
109 if (SameDomainName(zone, &tmp->zone) && tmp->rds.keyTag == keytag && tmp->rds.alg == alg && tmp->rds.digestType == digestType &&
110 !memcmp(tmp->rds.digest, digest, diglen))
112 LogMsg("AddTrustAnchors: Found a duplicate");
118 ta = (TrustAnchor *)mDNSPlatformMemAllocate(sizeof(TrustAnchor));
121 LogMsg("AddTrustAnchor: malloc failure ta");
124 ta->rds.keyTag = keytag;
126 ta->rds.digestType = digestType;
127 ta->rds.digest = digest;
128 ta->digestLen = diglen;
130 ta->validUntil = t + TEST_TA_EXPIRE_INTERVAL;
131 AssignDomainName(&ta->zone, zone);
134 LinkTrustAnchor(m, ta);
137 #define HexVal(X) ( ((X) >= '0' && (X) <= '9') ? ((X) - '0' ) : \
138 ((X) >= 'A' && (X) <= 'F') ? ((X) - 'A' + 10) : \
139 ((X) >= 'a' && (X) <= 'f') ? ((X) - 'a' + 10) : -1)
141 mDNSlocal mDNSu8 *ConvertDigest(char *digest, int digestType, int *diglen)
148 case SHA1_DIGEST_TYPE:
149 *diglen = CC_SHA1_DIGEST_LENGTH;
151 case SHA256_DIGEST_TYPE:
152 *diglen = CC_SHA256_DIGEST_LENGTH;
155 LogMsg("ConvertDigest: digest type %d not supported", digestType);
158 dig = mDNSPlatformMemAllocate(*diglen);
161 LogMsg("ConvertDigest: malloc failure");
165 for (j=0,i=0; i<*diglen*2; i+=2)
168 l = HexVal(digest[i]);
169 h = HexVal(digest[i+1]);
170 if (l<0 || h<0) { LogMsg("ConvertDigest: Cannot convert digest"); mDNSPlatformMemFree(dig); return NULL;}
171 dig[j++] = (mDNSu8)((l << 4) | h);
176 // All the children are in a linked list
178 // <TrustAnchor> has two children: <Zone> and <KeyDigest>
179 // <KeyDigest> has four children <KeyTag> <Algorithm> <DigestType> <Digest>
181 // Returns false if failed to parse the element i.e., malformed xml document.
182 // Validity of the actual values itself is done outside the function.
183 mDNSlocal mDNSBool ParseElementChildren(xmlDocPtr tadoc, xmlNode *node, TrustAnchor *ta)
186 xmlChar *val1, *val2, *val;
187 char *invalid = NULL;
189 val = val1 = val2 = NULL;
191 for (cur_node = node; cur_node; cur_node = cur_node->next)
196 val = xmlNodeListGetString(tadoc, cur_node->xmlChildrenNode, 1);
199 LogInfo("ParseElementChildren: NULL value for %s", cur_node->name);
202 if (!xmlStrcmp(cur_node->name, (const xmlChar *)"Zone"))
204 // MaeDomainNameFromDNSNameString does not work for "."
205 if (!xmlStrcmp(val, (const xmlChar *)"."))
209 else if (!MakeDomainNameFromDNSNameString(&ta->zone, (char *)val))
211 LogMsg("ParseElementChildren: Cannot parse Zone %s", val);
216 LogInfo("ParseElementChildren: Element %s, value %##s", cur_node->name, ta->zone.c);
219 else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"KeyTag"))
221 ta->rds.keyTag = strtol((const char *)val, &invalid, 10);
222 if (*invalid != '\0')
224 LogMsg("ParseElementChildren: KeyTag invalid character %d", *invalid);
229 LogInfo("ParseElementChildren: Element %s, value %d", cur_node->name, ta->rds.keyTag);
232 else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"Algorithm"))
234 ta->rds.alg = strtol((const char *)val, &invalid, 10);
235 if (*invalid != '\0')
237 LogMsg("ParseElementChildren: Algorithm invalid character %c", *invalid);
242 LogInfo("ParseElementChildren: Element %s, value %d", cur_node->name, ta->rds.alg);
245 else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"DigestType"))
247 ta->rds.digestType = strtol((const char *)val, &invalid, 10);
248 if (*invalid != '\0')
250 LogMsg("ParseElementChildren: Algorithm invalid character %c", *invalid);
255 LogInfo("ParseElementChildren: Element %s, value %d", cur_node->name, ta->rds.digestType);
258 else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"Digest"))
261 mDNSu8 *dig = ConvertDigest((char *)val, ta->rds.digestType, &diglen);
264 LogInfo("ParseElementChildren: Element %s, digest %s", cur_node->name, val);
265 ta->digestLen = diglen;
266 ta->rds.digest = dig;
270 LogMsg("ParseElementChildren: Element %s, NULL digest", cur_node->name);
274 else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"KeyDigest"))
277 val1 = xmlGetProp(cur_node, (const xmlChar *)"validFrom");
280 char *s = strptime((const char *)val1, "%Y-%m-%dT%H:%M:%S", &tm);
283 LogMsg("ParseElementChildren: Parsing ValidFrom failed %s", val1);
288 ta->validFrom = (mDNSu32)timegm(&tm);
291 val2 = xmlGetProp(cur_node, (const xmlChar *)"validUntil");
294 char *s = strptime((const char *)val2, "%Y-%m-%dT%H:%M:%S", &tm);
297 LogMsg("ParseElementChildren: Parsing ValidFrom failed %s", val2);
302 ta->validUntil = (mDNSu32)timegm(&tm);
307 // If there is no validUntil, set it to the next probing interval
308 mDNSu32 t = (mDNSu32) time(NULL);
309 ta->validUntil = t + ROOT_TA_UPDATE_INTERVAL;
311 LogInfo("ParseElementChildren: ValidFrom time %u, validUntil %u", (unsigned)ta->validFrom, (unsigned)ta->validUntil);
331 mDNSlocal mDNSBool ValidateTrustAnchor(TrustAnchor *ta)
333 time_t currTime = time(NULL);
335 // Currently only support trust anchor for root.
336 if (!SameDomainName(&ta->zone, (const domainname *)"\000"))
338 LogInfo("ParseElementChildren: Zone %##s not root", ta->zone.c);
342 switch (ta->rds.digestType)
344 case SHA1_DIGEST_TYPE:
345 if (ta->digestLen != CC_SHA1_DIGEST_LENGTH)
347 LogMsg("ValidateTrustAnchor: Invalid digest len %d for SHA1", ta->digestLen);
351 case SHA256_DIGEST_TYPE:
352 if (ta->digestLen != CC_SHA256_DIGEST_LENGTH)
354 LogMsg("ValidateTrustAnchor: Invalid digest len %d for SHA256", ta->digestLen);
359 LogMsg("ValidateTrustAnchor: digest type %d not supported", ta->rds.digestType);
364 LogMsg("ValidateTrustAnchor: digest NULL for %d", ta->rds.digestType);
369 case CRYPTO_RSA_SHA512:
370 case CRYPTO_RSA_SHA256:
371 case CRYPTO_RSA_NSEC3_SHA1:
372 case CRYPTO_RSA_SHA1:
375 LogMsg("ValidateTrustAnchor: Algorithm %d not supported", ta->rds.alg);
379 if (DNS_SERIAL_LT(currTime, ta->validFrom))
381 LogMsg("ValidateTrustAnchor: Invalid ValidFrom time %u, currtime %u", (unsigned)ta->validFrom, (unsigned)currTime);
384 if (DNS_SERIAL_LT(ta->validUntil, currTime))
386 LogMsg("ValidateTrustAnchor: Invalid ValidUntil time %u, currtime %u", (unsigned)ta->validUntil, (unsigned)currTime);
392 mDNSlocal mDNSBool ParseElement(xmlDocPtr tadoc, xmlNode * a_node, TrustAnchor *ta)
394 xmlNode *cur_node = NULL;
396 for (cur_node = a_node; cur_node; cur_node = cur_node->next)
398 if (cur_node->type == XML_ELEMENT_NODE)
400 // There could be multiple KeyDigests per TrustAnchor. We keep parsing till we
401 // reach the last one or we encounter an error in parsing the document.
402 if (!xmlStrcmp(cur_node->name, (const xmlChar *)"KeyDigest"))
405 mDNSPlatformMemFree(ta->rds.digest);
406 ta->rds.digestType = 0;
409 if (!ParseElementChildren(tadoc, cur_node->children, ta))
411 if (!ParseElement(tadoc, cur_node->children, ta))
418 mDNSlocal void TAComplete(mDNS *const m, void *context)
420 TrustAnchor *ta = (TrustAnchor *)context;
422 DelTrustAnchor(m, &ta->zone);
423 LinkTrustAnchor(m, ta);
426 mDNSlocal void FetchRootTA(mDNS *const m)
428 CFStringRef urlString = CFSTR("https://data.iana.org/root-anchors/root-anchors.xml");
430 CFStringRef fileRef = NULL;
431 const char *xmlFileName = NULL;
434 static unsigned int RootTAFetchInterval = InitialTAFetchInterval;
438 TrustAnchor *ta = (TrustAnchor *)mDNSPlatformMemAllocate(sizeof(TrustAnchor));
441 LogMsg("FetchRootTA: TrustAnchor alloc failed");
444 memset(ta, 0, sizeof(TrustAnchor));
446 url = CFURLCreateWithString(NULL, urlString, NULL);
449 LogMsg("FetchRootTA: CFURLCreateWithString error");
450 mDNSPlatformMemFree(ta);
454 #pragma clang diagnostic push
455 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
456 // If we can't fetch the XML file e.g., network problems, trigger a timer. All other failures
457 // should hardly happen in practice for which schedule the normal interval to refetch the TA.
458 Boolean success = CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, url, &xmlData, NULL, NULL, NULL);
459 #pragma clang diagnostic pop
462 LogInfo("FetchRootTA: CFURLCreateDataAndPropertiesFromResource error");
464 mDNSPlatformMemFree(ta);
465 RegisterNotification(m, RootTAFetchInterval);
466 RootTAFetchInterval *= 2 + 1;
470 // get the name of the last component from the url, libxml will use it if
471 // it has to report an error
472 fileRef = CFURLCopyLastPathComponent(url);
475 xmlFileName = CFStringGetCStringPtr(fileRef, kCFStringEncodingUTF8);
478 if (!CFStringGetCString(fileRef, buf, sizeof(buf), kCFStringEncodingUTF8) )
479 strlcpy(buf, "nofile.xml", sizeof(buf));
480 xmlFileName = (const char *)buf;
484 // Parse the XML and get the CFXMLTree.
485 xmlDocPtr tadoc = xmlReadMemory((const char*)CFDataGetBytePtr(xmlData),
486 (int)CFDataGetLength(xmlData), xmlFileName, NULL, 0);
495 LogMsg("FetchRootTA: xmlReadMemory failed");
499 xmlNodePtr root = xmlDocGetRootElement(tadoc);
502 LogMsg("FetchRootTA: Cannot get root element");
506 if (ParseElement(tadoc, root, ta) && ValidateTrustAnchor(ta))
508 // Do the actual addition of TA on the main queue.
509 mDNSPlatformDispatchAsync(m, ta, TAComplete);
514 mDNSPlatformMemFree(ta->rds.digest);
515 mDNSPlatformMemFree(ta);
520 RegisterNotification(m, ROOT_TA_UPDATE_INTERVAL);
521 RootTAFetchInterval = InitialTAFetchInterval;
526 #if APPLE_OSX_mDNSResponder && !TARGET_OS_IPHONE
527 mDNSlocal void DNSSECProbeCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
533 if ((m->timenow - question->StopTime) >= 0)
536 LogDNSSEC("DNSSECProbeCallback: Question %##s (%s) timed out", question->qname.c, DNSTypeName(question->qtype));
537 mDNS_StopQuery(m, question);
542 // Wait till we get the DNSSEC results. If we get a negative response e.g., no DNS servers, the
543 // question will be restarted by the core and we should have the DNSSEC results eventually.
544 if (AddRecord != QC_dnssec)
546 LogDNSSEC("DNSSECProbeCallback: Question %##s (%s)", question->qname.c, DNSTypeName(question->qtype), RRDisplayString(m, answer));
550 LogDNSSEC("DNSSECProbeCallback: Question %##s (%s), DNSSEC status %s", question->qname.c, DNSTypeName(question->qtype),
551 DNSSECStatusName(question->ValidationStatus));
553 mDNS_StopQuery(m, question);
556 // Send a DNSSEC probe just for the sake of collecting DNSSEC statistics.
557 mDNSexport void DNSSECProbe(mDNS *const m)
561 if (DNSSECProbeQuestion.ThisQInterval != -1)
564 rand = mDNSRandom(FutureTime) % 100;
565 // Probe 1% of the time
566 if (rand >= DNSSECProbePercentage)
569 mDNS_DropLockBeforeCallback();
570 InitializeQuestion(m, &DNSSECProbeQuestion, mDNSInterface_Any, (const domainname *)"\003com", kDNSType_DS, DNSSECProbeCallback, mDNSNULL);
571 DNSSECProbeQuestion.ValidatingResponse = 0;
572 DNSSECProbeQuestion.ValidationRequired = DNSSEC_VALIDATION_SECURE;
574 BumpDNSSECStats(m, kStatsActionIncrement, kStatsTypeProbe, 1);
575 mDNS_StartQuery(m, &DNSSECProbeQuestion);
576 mDNS_ReclaimLockAfterCallback();
578 #endif // APPLE_OSX_mDNSResponder && !TARGET_OS_IPHONE
580 // For now we fetch the root trust anchor and update the local copy
581 mDNSexport void UpdateTrustAnchors(mDNS *const m)
583 // Register for a notification to fire immediately which in turn will update
585 if (RegisterNotification(m, 1))
587 LogMsg("UpdateTrustAnchors: ERROR!! failed to register for notification");
591 mDNSlocal int RegisterNotification(mDNS *const m, unsigned int interval)
593 int len = strlen("com.apple.system.notify.service.timer:+") + 21; // 21 bytes to accomodate the interval
598 // Starting "interval" second from now (+ below indicates relative) register for a notification
599 blen = mDNS_snprintf(buffer, sizeof(buffer), "com.apple.system.notify.service.timer:+%us", interval);
600 if (blen >= sizeof(buffer))
602 LogMsg("RegisterNotification: Buffer too small blen %d, buffer size %d", blen, sizeof(buffer));
605 LogInfo("RegisterNotification: buffer %s", buffer);
608 notify_cancel(m->notifyToken);
611 status = notify_register_dispatch(buffer, &m->notifyToken,
612 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
613 ^(int t) { (void) t; FetchRootTA(m); });
615 if (status != NOTIFY_STATUS_OK)
617 LogMsg("RegisterNotification: notify_register_dispatch failed");
623 mDNSexport mStatus DNSSECPlatformInit(mDNS *const m)
627 m->TrustAnchors = mDNSNULL;
630 // Add a couple of trust anchors for testing purposes.
631 mDNSlocal const domainname *testZone = (const domainname*)"\007example";
633 char *digest = "F122E47B5B7D2B6A5CC0A21EADA11D96BB9CC927";
634 mDNSu8 *dig = ConvertDigest(digest, 1, &diglen);
635 AddTrustAnchor(m, testZone, 23044, 5, 1, diglen, dig);
637 char *digest1 = "D795AE5E1AFB200C6139474199B70EAD3F3484553FD97BE5A43704B8A4791F21";
638 dig = ConvertDigest(digest1, 2, &diglen);
639 AddTrustAnchor(m, testZone, 23044, 5, 2, diglen, dig);
641 // Add the TA for root zone manually here. We will dynamically fetch the root TA and
642 // update it shortly. If that fails e.g., disconnected from the network, we still
643 // have something to work with.
644 char *digest2 = "49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5";
645 dig = ConvertDigest(digest2, 2, &diglen);
646 AddTrustAnchor(m, (const domainname *)"\000", 19036, 8, 2, diglen, dig);
648 #if !TARGET_OS_IPHONE
649 DNSSECProbeQuestion.ThisQInterval = -1;
651 return mStatus_NoError;