1/* -*- Mode: C; tab-width: 4 -*- 2 * 3 * Copyright (c) 2012 Apple Computer, Inc. All rights reserved. 4 * 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 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 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. 16 */ 17 18// *************************************************************************** 19// DNSSECSupport.c: Platform specific support for DNSSEC like fetching root 20// trust anchor and dnssec probes etc. 21// *************************************************************************** 22 23#include "mDNSEmbeddedAPI.h" 24#include "DNSCommon.h" // For mDNS_Lock, mDNS_Random 25#include "dnssec.h" 26#include "DNSSECSupport.h" 27 28#include <CommonCrypto/CommonDigest.h> // For Hash algorithms SHA1 etc. 29 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> 35#include <notify.h> 36 37// 30 days 38#define ROOT_TA_UPDATE_INTERVAL (30 * 24 * 3600) // seconds 39 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) 43 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 48#if !TARGET_OS_IPHONE 49DNSQuestion DNSSECProbeQuestion; 50#endif 51 52mDNSlocal int RegisterNotification(mDNS *const m, unsigned int interval); 53 54mDNSlocal void LinkTrustAnchor(mDNS *const m, TrustAnchor *ta) 55{ 56 int length = 0; 57 int i; 58 mDNSu8 *p; 59 TrustAnchor **t = &m->TrustAnchors; 60 char buffer[256]; 61 62 while (*t) 63 t = &((*t)->next); 64 *t = ta; 65 66 buffer[0] = 0; 67 p = ta->rds.digest; 68 for (i = 0; i < ta->digestLen; i++) 69 { 70 length += mDNS_snprintf(buffer+length, sizeof(buffer)-1-length, "%x", p[i]); 71 } 72 LogInfo("LinkTrustAnchor: Zone %##s, keytag %d, alg %d, digestType %d, digestLen %d, digest %s", ta->zone.c, ta->rds.keyTag, 73 ta->rds.alg, ta->rds.digestType, ta->digestLen, buffer); 74} 75 76mDNSlocal void DelTrustAnchor(mDNS *const m, const domainname *zone) 77{ 78 TrustAnchor **ta = &m->TrustAnchors; 79 TrustAnchor *tmp; 80 81 while (*ta && !SameDomainName(&(*ta)->zone, zone)) 82 ta = &(*ta)->next; 83 84 // First time, we won't find the TrustAnchor in the list as it has 85 // not been added. 86 if (!(*ta)) 87 return; 88 89 tmp = *ta; 90 *ta = (*ta)->next; // Cut this record from the list 91 tmp->next = mDNSNULL; 92 if (tmp->rds.digest) 93 mDNSPlatformMemFree(tmp->rds.digest); 94 mDNSPlatformMemFree(tmp); 95} 96 97mDNSlocal void AddTrustAnchor(mDNS *const m, const domainname *zone, mDNSu16 keytag, mDNSu8 alg, mDNSu8 digestType, int diglen, 98 mDNSu8 *digest) 99{ 100 TrustAnchor *ta, *tmp; 101 mDNSu32 t = (mDNSu32) time(NULL); 102 103 // Check for duplicates 104 tmp = m->TrustAnchors; 105 while (tmp) 106 { 107 if (SameDomainName(zone, &tmp->zone) && tmp->rds.keyTag == keytag && tmp->rds.alg == alg && tmp->rds.digestType == digestType && 108 !memcmp(tmp->rds.digest, digest, diglen)) 109 { 110 LogMsg("AddTrustAnchors: Found a duplicate"); 111 return; 112 } 113 tmp = tmp->next; 114 } 115 116 ta = (TrustAnchor *)mDNSPlatformMemAllocate(sizeof(TrustAnchor)); 117 if (!ta) 118 { 119 LogMsg("AddTrustAnchor: malloc failure ta"); 120 return; 121 } 122 ta->rds.keyTag = keytag; 123 ta->rds.alg = alg; 124 ta->rds.digestType = digestType; 125 ta->rds.digest = digest; 126 ta->digestLen = diglen; 127 ta->validFrom = t; 128 ta->validUntil = t + TEST_TA_EXPIRE_INTERVAL; 129 AssignDomainName(&ta->zone, zone); 130 ta->next = mDNSNULL; 131 132 LinkTrustAnchor(m, ta); 133} 134 135#define HexVal(X) ( ((X) >= '0' && (X) <= '9') ? ((X) - '0' ) : \ 136 ((X) >= 'A' && (X) <= 'F') ? ((X) - 'A' + 10) : \ 137 ((X) >= 'a' && (X) <= 'f') ? ((X) - 'a' + 10) : -1) 138 139mDNSlocal mDNSu8 *ConvertDigest(char *digest, int digestType, int *diglen) 140{ 141 int i, j; 142 mDNSu8 *dig; 143 144 switch (digestType) 145 { 146 case SHA1_DIGEST_TYPE: 147 *diglen = CC_SHA1_DIGEST_LENGTH; 148 break; 149 case SHA256_DIGEST_TYPE: 150 *diglen = CC_SHA256_DIGEST_LENGTH; 151 break; 152 default: 153 LogMsg("ConvertDigest: digest type %d not supported", digestType); 154 return mDNSNULL; 155 } 156 dig = mDNSPlatformMemAllocate(*diglen); 157 if (!dig) 158 { 159 LogMsg("ConvertDigest: malloc failure"); 160 return mDNSNULL; 161 } 162 163 for (j=0,i=0; i<*diglen*2; i+=2) 164 { 165 int l, h; 166 l = HexVal(digest[i]); 167 h = HexVal(digest[i+1]); 168 if (l<0 || h<0) { LogMsg("ConvertDigest: Cannot convert digest"); return NULL;} 169 dig[j++] = (mDNSu8)((l << 4) | h); 170 } 171 return dig; 172} 173 174// All the children are in a linked list 175// 176// <TrustAnchor> has two children: <Zone> and <KeyDigest> 177// <KeyDigest> has four children <KeyTag> <Algorithm> <DigestType> <Digest> 178// 179// Returns false if failed to parse the element i.e., malformed xml document. 180// Validity of the actual values itself is done outside the function. 181mDNSlocal mDNSBool ParseElementChildren(xmlDocPtr tadoc, xmlNode *node, TrustAnchor *ta) 182{ 183 xmlNode *cur_node; 184 xmlChar *val1, *val2, *val; 185 char *invalid = NULL; 186 187 val = val1 = val2 = NULL; 188 189 for (cur_node = node; cur_node; cur_node = cur_node->next) 190 { 191 invalid = NULL; 192 val1 = val2 = NULL; 193 194 val = xmlNodeListGetString(tadoc, cur_node->xmlChildrenNode, 1); 195 if (!val) 196 { 197 LogInfo("ParseElementChildren: NULL value for %s", cur_node->name); 198 continue; 199 } 200 if (!xmlStrcmp(cur_node->name, (const xmlChar *)"Zone")) 201 { 202 // MaeDomainNameFromDNSNameString does not work for "." 203 if (!xmlStrcmp(val, (const xmlChar *)".")) 204 { 205 ta->zone.c[0] = 0; 206 } 207 else if (!MakeDomainNameFromDNSNameString(&ta->zone, (char *)val)) 208 { 209 LogMsg("ParseElementChildren: Cannot parse Zone %s", val); 210 goto error; 211 } 212 else 213 { 214 LogInfo("ParseElementChildren: Element %s, value %##s", cur_node->name, ta->zone.c); 215 } 216 } 217 else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"KeyTag")) 218 { 219 ta->rds.keyTag = strtol((const char *)val, &invalid, 10); 220 if (*invalid != '\0') 221 { 222 LogMsg("ParseElementChildren: KeyTag invalid character %d", *invalid); 223 goto error; 224 } 225 else 226 { 227 LogInfo("ParseElementChildren: Element %s, value %d", cur_node->name, ta->rds.keyTag); 228 } 229 } 230 else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"Algorithm")) 231 { 232 ta->rds.alg = strtol((const char *)val, &invalid, 10); 233 if (*invalid != '\0') 234 { 235 LogMsg("ParseElementChildren: Algorithm invalid character %c", *invalid); 236 goto error; 237 } 238 else 239 { 240 LogInfo("ParseElementChildren: Element %s, value %d", cur_node->name, ta->rds.alg); 241 } 242 } 243 else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"DigestType")) 244 { 245 ta->rds.digestType = strtol((const char *)val, &invalid, 10); 246 if (*invalid != '\0') 247 { 248 LogMsg("ParseElementChildren: Algorithm invalid character %c", *invalid); 249 goto error; 250 } 251 else 252 { 253 LogInfo("ParseElementChildren: Element %s, value %d", cur_node->name, ta->rds.digestType); 254 } 255 } 256 else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"Digest")) 257 { 258 int diglen; 259 mDNSu8 *dig = ConvertDigest((char *)val, ta->rds.digestType, &diglen); 260 if (dig) 261 { 262 LogInfo("ParseElementChildren: Element %s, digest %s", cur_node->name, val); 263 ta->digestLen = diglen; 264 ta->rds.digest = dig; 265 } 266 else 267 { 268 LogMsg("ParseElementChildren: Element %s, NULL digest", cur_node->name); 269 goto error; 270 } 271 } 272 else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"KeyDigest")) 273 { 274 struct tm tm; 275 val1 = xmlGetProp(cur_node, (const xmlChar *)"validFrom"); 276 if (val1) 277 { 278 char *s = strptime((const char *)val1, "%Y-%m-%dT%H:%M:%S", &tm); 279 if (!s) 280 { 281 LogMsg("ParseElementChildren: Parsing ValidFrom failed %s", val1); 282 goto error; 283 } 284 else 285 { 286 ta->validFrom = (mDNSu32)timegm(&tm); 287 } 288 } 289 val2 = xmlGetProp(cur_node, (const xmlChar *)"validUntil"); 290 if (val2) 291 { 292 char *s = strptime((const char *)val2, "%Y-%m-%dT%H:%M:%S", &tm); 293 if (!s) 294 { 295 LogMsg("ParseElementChildren: Parsing ValidFrom failed %s", val2); 296 goto error; 297 } 298 else 299 { 300 ta->validUntil = (mDNSu32)timegm(&tm); 301 } 302 } 303 else 304 { 305 // If there is no validUntil, set it to the next probing interval 306 mDNSu32 t = (mDNSu32) time(NULL); 307 ta->validUntil = t + ROOT_TA_UPDATE_INTERVAL; 308 } 309 LogInfo("ParseElementChildren: ValidFrom time %u, validUntil %u", (unsigned)ta->validFrom, (unsigned)ta->validUntil); 310 } 311 if (val1) 312 xmlFree(val1); 313 if (val2) 314 xmlFree(val2); 315 if (val) 316 xmlFree(val); 317 } 318 return mDNStrue; 319error: 320 if (val1) 321 xmlFree(val1); 322 if (val2) 323 xmlFree(val2); 324 if (val) 325 xmlFree(val); 326 return mDNSfalse; 327} 328 329mDNSlocal mDNSBool ValidateTrustAnchor(TrustAnchor *ta) 330{ 331 time_t currTime = time(NULL); 332 333 // Currently only support trust anchor for root. 334 if (!SameDomainName(&ta->zone, (const domainname *)"\000")) 335 { 336 LogInfo("ParseElementChildren: Zone %##s not root", ta->zone.c); 337 return mDNSfalse; 338 } 339 340 switch (ta->rds.digestType) 341 { 342 case SHA1_DIGEST_TYPE: 343 if (ta->digestLen != CC_SHA1_DIGEST_LENGTH) 344 { 345 LogMsg("ValidateTrustAnchor: Invalid digest len %d for SHA1", ta->digestLen); 346 return mDNSfalse; 347 } 348 break; 349 case SHA256_DIGEST_TYPE: 350 if (ta->digestLen != CC_SHA256_DIGEST_LENGTH) 351 { 352 LogMsg("ValidateTrustAnchor: Invalid digest len %d for SHA256", ta->digestLen); 353 return mDNSfalse; 354 } 355 break; 356 default: 357 LogMsg("ValidateTrustAnchor: digest type %d not supported", ta->rds.digestType); 358 return mDNSfalse; 359 } 360 if (!ta->rds.digest) 361 { 362 LogMsg("ValidateTrustAnchor: digest NULL for %d", ta->rds.digestType); 363 return mDNSfalse; 364 } 365 switch (ta->rds.alg) 366 { 367 case CRYPTO_RSA_SHA512: 368 case CRYPTO_RSA_SHA256: 369 case CRYPTO_RSA_NSEC3_SHA1: 370 case CRYPTO_RSA_SHA1: 371 break; 372 default: 373 LogMsg("ValidateTrustAnchor: Algorithm %d not supported", ta->rds.alg); 374 return mDNSfalse; 375 } 376 377 if (DNS_SERIAL_LT(currTime, ta->validFrom)) 378 { 379 LogMsg("ValidateTrustAnchor: Invalid ValidFrom time %u, currtime %u", (unsigned)ta->validFrom, (unsigned)currTime); 380 return mDNSfalse; 381 } 382 if (DNS_SERIAL_LT(ta->validUntil, currTime)) 383 { 384 LogMsg("ValidateTrustAnchor: Invalid ValidUntil time %u, currtime %u", (unsigned)ta->validUntil, (unsigned)currTime); 385 return mDNSfalse; 386 } 387 return mDNStrue; 388} 389 390mDNSlocal mDNSBool ParseElement(xmlDocPtr tadoc, xmlNode * a_node, TrustAnchor *ta) 391{ 392 xmlNode *cur_node = NULL; 393 394 for (cur_node = a_node; cur_node; cur_node = cur_node->next) 395 { 396 if (cur_node->type == XML_ELEMENT_NODE) 397 { 398 // There could be multiple KeyDigests per TrustAnchor. We keep parsing till we 399 // reach the last one or we encounter an error in parsing the document. 400 if (!xmlStrcmp(cur_node->name, (const xmlChar *)"KeyDigest")) 401 { 402 if (ta->rds.digest) 403 mDNSPlatformMemFree(ta->rds.digest); 404 ta->rds.digestType = 0; 405 ta->digestLen = 0; 406 } 407 if (!ParseElementChildren(tadoc, cur_node->children, ta)) 408 return mDNSfalse; 409 if (!ParseElement(tadoc, cur_node->children, ta)) 410 return mDNSfalse; 411 } 412 } 413 return mDNStrue; 414} 415 416mDNSlocal void TAComplete(mDNS *const m, void *context) 417{ 418 TrustAnchor *ta = (TrustAnchor *)context; 419 420 DelTrustAnchor(m, &ta->zone); 421 LinkTrustAnchor(m, ta); 422} 423 424mDNSlocal void FetchRootTA(mDNS *const m) 425{ 426 CFStringRef urlString = CFSTR("https://data.iana.org/root-anchors/root-anchors.xml"); 427 CFDataRef xmlData; 428 CFStringRef fileRef = NULL; 429 const char *xmlFileName = NULL; 430 char buf[512]; 431 CFURLRef url = NULL; 432 static unsigned int RootTAFetchInterval = InitialTAFetchInterval; 433 434 (void) m; 435 436 TrustAnchor *ta = (TrustAnchor *)mDNSPlatformMemAllocate(sizeof(TrustAnchor)); 437 if (!ta) 438 { 439 LogMsg("FetchRootTA: TrustAnchor alloc failed"); 440 return; 441 } 442 memset(ta, 0, sizeof(TrustAnchor)); 443 444 url = CFURLCreateWithString(NULL, urlString, NULL); 445 if (!url) 446 { 447 LogMsg("FetchRootTA: CFURLCreateWithString error"); 448 mDNSPlatformMemFree(ta); 449 return; 450 } 451 452 // If we can't fetch the XML file e.g., network problems, trigger a timer. All other failures 453 // should hardly happen in practice for which schedule the normal interval to refetch the TA. 454 if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, url, &xmlData, NULL, NULL, NULL)) 455 { 456 LogInfo("FetchRootTA: CFURLCreateDataAndPropertiesFromResource error"); 457 CFRelease(url); 458 mDNSPlatformMemFree(ta); 459 RegisterNotification(m, RootTAFetchInterval); 460 RootTAFetchInterval *= 2 + 1; 461 return; 462 } 463 464 // get the name of the last component from the url, libxml will use it if 465 // it has to report an error 466 fileRef = CFURLCopyLastPathComponent(url); 467 if (fileRef) 468 { 469 xmlFileName = CFStringGetCStringPtr(fileRef, kCFStringEncodingUTF8); 470 if (!xmlFileName) 471 { 472 if (!CFStringGetCString(fileRef, buf, sizeof(buf), kCFStringEncodingUTF8) ) 473 strlcpy(buf, "nofile.xml", sizeof(buf)); 474 xmlFileName = (const char *)buf; 475 } 476 } 477 478 // Parse the XML and get the CFXMLTree. 479 xmlDocPtr tadoc = xmlReadMemory((const char*)CFDataGetBytePtr(xmlData), 480 (int)CFDataGetLength(xmlData), xmlFileName, NULL, 0); 481 482 CFRelease(fileRef); 483 CFRelease(url); 484 CFRelease(xmlData); 485 486 if (!tadoc) 487 { 488 LogMsg("FetchRootTA: xmlReadMemory failed"); 489 goto done; 490 } 491 492 xmlNodePtr root = xmlDocGetRootElement(tadoc); 493 if (!root) 494 { 495 LogMsg("FetchRootTA: Cannot get root element"); 496 goto done; 497 } 498 499 if (ParseElement(tadoc, root, ta) && ValidateTrustAnchor(ta)) 500 { 501 // Do the actual addition of TA on the main queue. 502 mDNSPlatformDispatchAsync(m, ta, TAComplete); 503 } 504 else 505 { 506 if (ta->rds.digest) 507 mDNSPlatformMemFree(ta->rds.digest); 508 mDNSPlatformMemFree(ta); 509 } 510done: 511 if (tadoc) 512 xmlFreeDoc(tadoc); 513 RegisterNotification(m, ROOT_TA_UPDATE_INTERVAL); 514 RootTAFetchInterval = InitialTAFetchInterval; 515 return; 516} 517 518 519#if APPLE_OSX_mDNSResponder && !TARGET_OS_IPHONE 520mDNSlocal void DNSSECProbeCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) 521{ 522 if (!AddRecord) 523 return; 524 525 mDNS_Lock(m); 526 if ((m->timenow - question->StopTime) >= 0) 527 { 528 mDNS_Unlock(m); 529 LogDNSSEC("DNSSECProbeCallback: Question %##s (%s) timed out", question->qname.c, DNSTypeName(question->qtype)); 530 mDNS_StopQuery(m, question); 531 return; 532 } 533 mDNS_Unlock(m); 534 535 // Wait till we get the DNSSEC results. If we get a negative response e.g., no DNS servers, the 536 // question will be restarted by the core and we should have the DNSSEC results eventually. 537 if (AddRecord != QC_dnssec) 538 { 539 LogDNSSEC("DNSSECProbeCallback: Question %##s (%s)", question->qname.c, DNSTypeName(question->qtype), RRDisplayString(m, answer)); 540 return; 541 } 542 543 LogDNSSEC("DNSSECProbeCallback: Question %##s (%s), DNSSEC status %s", question->qname.c, DNSTypeName(question->qtype), 544 DNSSECStatusName(question->ValidationStatus)); 545 546 mDNS_StopQuery(m, question); 547} 548 549// Send a DNSSEC probe just for the sake of collecting DNSSEC statistics. 550mDNSexport void DNSSECProbe(mDNS *const m) 551{ 552 mDNSu32 rand; 553 554 if (DNSSECProbeQuestion.ThisQInterval != -1) 555 return; 556 557 rand = mDNSRandom(0x3FFFFFFF) % 100; 558 // Probe 5% of the time 559 if (rand > 5) 560 return; 561 562 mDNS_DropLockBeforeCallback(); 563 InitializeQuestion(m, &DNSSECProbeQuestion, mDNSInterface_Any, (const domainname *)"\003com", kDNSType_DS, DNSSECProbeCallback, mDNSNULL); 564 DNSSECProbeQuestion.ValidatingResponse = 0; 565 DNSSECProbeQuestion.ValidationRequired = DNSSEC_VALIDATION_SECURE; 566 567 BumpDNSSECStats(m, kStatsActionIncrement, kStatsTypeProbe, 1); 568 mDNS_StartQuery(m, &DNSSECProbeQuestion); 569 mDNS_ReclaimLockAfterCallback(); 570} 571#endif // APPLE_OSX_mDNSResponder && !TARGET_OS_IPHONE 572 573// For now we fetch the root trust anchor and update the local copy 574mDNSexport void UpdateTrustAnchors(mDNS *const m) 575{ 576 // Register for a notification to fire immediately which in turn will update 577 // the trust anchor 578 if (RegisterNotification(m, 1)) 579 { 580 LogMsg("UpdateTrustAnchors: ERROR!! failed to register for notification"); 581 } 582} 583 584mDNSlocal int RegisterNotification(mDNS *const m, unsigned int interval) 585{ 586 int len = strlen("com.apple.system.notify.service.timer:+") + 21; // 21 bytes to accomodate the interval 587 char buffer[len]; 588 unsigned int blen; 589 int status; 590 591 // Starting "interval" second from now (+ below indicates relative) register for a notification 592 blen = mDNS_snprintf(buffer, sizeof(buffer), "com.apple.system.notify.service.timer:+%us", interval); 593 if (blen >= sizeof(buffer)) 594 { 595 LogMsg("RegisterNotification: Buffer too small blen %d, buffer size %d", blen, sizeof(buffer)); 596 return -1; 597 } 598 LogInfo("RegisterNotification: buffer %s", buffer); 599 if (m->notifyToken) 600 { 601 notify_cancel(m->notifyToken); 602 m->notifyToken = 0; 603 } 604 status = notify_register_dispatch(buffer, &m->notifyToken, 605 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 606 ^(int t) { (void) t; FetchRootTA(m); }); 607 608 if (status != NOTIFY_STATUS_OK) 609 { 610 LogMsg("RegisterNotification: notify_register_dispatch failed"); 611 return -1; 612 } 613 return 0; 614} 615 616mDNSexport mStatus DNSSECPlatformInit(mDNS *const m) 617{ 618 int diglen; 619 620 m->TrustAnchors = mDNSNULL; 621 m->notifyToken = 0; 622 623 // Add a couple of trust anchors for testing purposes. 624 mDNSlocal const domainname *testZone = (const domainname*)"\007example"; 625 626 char *digest = "F122E47B5B7D2B6A5CC0A21EADA11D96BB9CC927"; 627 mDNSu8 *dig = ConvertDigest(digest, 1, &diglen); 628 AddTrustAnchor(m, testZone, 23044, 5, 1, diglen, dig); 629 630 char *digest1 = "D795AE5E1AFB200C6139474199B70EAD3F3484553FD97BE5A43704B8A4791F21"; 631 dig = ConvertDigest(digest1, 2, &diglen); 632 AddTrustAnchor(m, testZone, 23044, 5, 2, diglen, dig); 633 634 // Add the TA for root zone manually here. We will dynamically fetch the root TA and 635 // update it shortly. If that fails e.g., disconnected from the network, we still 636 // have something to work with. 637 char *digest2 = "49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5"; 638 dig = ConvertDigest(digest2, 2, &diglen); 639 AddTrustAnchor(m, (const domainname *)"\000", 19036, 8, 2, diglen, dig); 640 641#if !TARGET_OS_IPHONE 642 DNSSECProbeQuestion.ThisQInterval = -1; 643#endif 644 return mStatus_NoError; 645} 646