/* -*- Mode: C; tab-width: 4 -*- * * Copyright (c) 2011-2013 Apple Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "mDNSEmbeddedAPI.h" #include "DNSSECSupport.h" #include "DNSCommon.h" #include "dnssec.h" #include "CryptoAlg.h" #include "nsec.h" #include "nsec3.h" // Define DNSSEC_DISABLED to remove all the DNSSEC functionality // and use the stub functions implemented later in this file. #ifndef DNSSEC_DISABLED //#define DNSSEC_DEBUG #ifdef DNSSEC_DEBUG #define debugdnssec LogMsg #else #define debugdnssec debug_noop #endif // // Implementation Notes // // The entry point to DNSSEC Verification is VerifySignature. This function is called from the "core" when // the answer delivered to the application needs DNSSEC validation. If a question needs DNSSEC // validation, "ValidationRequired" would be set. As we need to issue more queries to validate the // original question, we create another question as part of the verification process (question is part of // DNSSECVerifier). This question sets "ValidatingResponse" to distinguish itself from the original // question. Without this, it will be a duplicate and never sent out. The "core" almost treats both the // types identically (like adding EDNS0 option with DO bit etc.) except for a few differences. When RRSIGs // are added to the cache, "ValidatingResponse" question gets called back as long as the typeCovered matches // the question's qtype. See the comment in DNSSECRecordAnswersQuestion for the details. The other big // difference is that "ValidationRequired" question kicks off the verification process by calling into // "VerifySignature" whereas ValidationResponse don't do that as it gets callback for its questions. // // VerifySignature does not retain the original question that started the verification process. It just // remembers the name and the type. It takes a snapshot of the cache at that instance which will be // verified using DNSSEC. If the cache changes subsequently e.g., network change etc., it will be detected // when the validation is completed. If there is a change, it will be revalidated. // // The verification flow looks like this: // // VerifySignature -> StartDNSSECVerification - GetAllRRSetsForVerification -> FinishDNSSECVerification -> VerifySignature // // Verification is a recursive process. It stops when we find a trust anchor or if we have recursed too deep. // // If the original question resulted in NODATA/NXDOMAIN error, there should have been NSECs as part of the response. // These nsecs are cached along with the negative cache record. These are validated using ValidateWithNSECS called // from Verifysignature. // // The flow in this case looks like this: // // VerifySignature -> ValidateWithNSECS -> {NoDataProof, NameErrorProof} -> VerifyNSECS -> StartDNSSECVerification // // Once the DNSSEC verification is started, it is similar to the previous flow described above. When the verification // is done, DNSSECPositiveValidationCB or DNSSECNegativeValidationCB will be called which will then deliver the // validation results to the original question that started the validation. // // Insecure proofs are done when the verification ends up bogus. The flow would look like this // // VerifySignature -> StartDNSSECVerification - GetAllRRSetsForVerification -> FinishDNSSECVerification -> DNSSECValidationCB // {DNSSECPositiveValidationCB, DNSSECNegativeValidationCB} -> ProveInsecure -> VerifySignaure -> // // ProveInsecure finds the break in trust in a top-down fashion. // // Forward declaration mDNSlocal void VerifySigCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord); mDNSlocal mStatus TrustedKey(mDNS *const m, DNSSECVerifier *dv); mDNSlocal mDNSBool TrustedKeyPresent(mDNS *const m, DNSSECVerifier *dv); mDNSlocal mStatus ValidateDS(DNSSECVerifier *dv); mDNSlocal void DNSSECNegativeValidationCB(mDNS *const m, DNSSECVerifier *dv, CacheGroup *cg, ResourceRecord *answer, DNSSECStatus status); mDNSlocal RRVerifier* CopyRRVerifier(RRVerifier *from); mDNSlocal void FreeDNSSECAuthChainInfo(AuthChain *ac); // Currently we use this to convert a RRVerifier to resource record so that we can // use the standard DNS utility functions LargeCacheRecord largerec; // Verification is a recursive process. We arbitrarily limit to 10 just to be cautious which should be // removed in the future. #define MAX_RECURSE_COUNT 10 // TTL (in seconds) when the DNSSEC status is Bogus #define RR_BOGUS_TTL 60 // RFC 4034 Appendix B: Get the keyid of a DNS KEY. It is not transmitted // explicitly on the wire. // // Note: This just helps narrow down the list of keys to look at. It is possible // for two DNS keys to have the same ID i.e., key ID is not a unqiue tag // // 1st argument - the RDATA part of the DNSKEY RR // 2nd argument - the RDLENGTH // mDNSlocal mDNSu32 keytag(mDNSu8 *key, mDNSu32 keysize) { unsigned long ac; unsigned int i; // DST_ALG_RSAMD5 will be rejected automatically as the keytag // is calculated wrongly for (ac = 0, i = 0; i < keysize; ++i) ac += (i & 1) ? key[i] : key[i] << 8; ac += (ac >> 16) & 0xFFFF; return ac & 0xFFFF; } mDNSexport int DNSMemCmp(const mDNSu8 *const m1, const mDNSu8 *const m2, int len) { int res; res = mDNSPlatformMemCmp(m1, m2, len); if (res != 0) return (res < 0 ? -1 : 1); return 0; } // RFC 4034: // // Section 6.1: // // For the purposes of DNS security, owner names are ordered by treating // individual labels as unsigned left-justified octet strings. The // absence of a octet sorts before a zero value octet, and uppercase // US-ASCII letters are treated as if they were lowercase US-ASCII // letters. // // To compute the canonical ordering of a set of DNS names, start by // sorting the names according to their most significant (rightmost) // labels. For names in which the most significant label is identical, // continue sorting according to their next most significant label, and // so forth. // // Returns 0 if the names are same // Returns -1 if d1 < d2 // Returns 1 if d1 > d2 // // subdomain is set if there is at least one label match (starting from the end) // and d1 has more labels than d2 e.g., a.b.com is a subdomain of b.com // mDNSexport int DNSSECCanonicalOrder(const domainname *const d1, const domainname *const d2, int *subdomain) { int count, c1, c2; int i, skip1, skip2; c1 = CountLabels(d1); skip1 = c1 - 1; c2 = CountLabels(d2); skip2 = c2 - 1; if (subdomain) *subdomain = 0; // Compare as many labels as possible starting from the rightmost count = c1 < c2 ? c1 : c2; for (i = count; i > 0; i--) { mDNSu8 *a, *b; int j, len, lena, lenb; a = (mDNSu8 *)SkipLeadingLabels(d1, skip1); b = (mDNSu8 *)SkipLeadingLabels(d2, skip2); lena = *a; lenb = *b; // Compare label by label. Note that "z" > "yak" because z > y, but z < za // (lena - lenb check below) because 'za' has two characters. Hence compare the // letters first and then compare the length of the label at the end. len = lena < lenb ? lena : lenb; a++; b++; for (j = 0; j < len; j++) { mDNSu8 ac = *a++; mDNSu8 bc = *b++; if (mDNSIsUpperCase(ac)) ac += 'a' - 'A'; if (mDNSIsUpperCase(bc)) bc += 'a' - 'A'; if (ac != bc) { verbosedebugf("DNSSECCanonicalOrder: returning ac %c, bc %c", ac, bc); return ((ac < bc) ? -1 : 1); } } if ((lena - lenb) != 0) { verbosedebugf("DNSSECCanonicalOrder: returning lena %d lenb %d", lena, lenb); return ((lena < lenb) ? -1 : 1); } // Continue with the next label skip1--; skip2--; } // We have compared label by label. Both of them are same if we are here. // // Two possibilities. // // 1) Both names have same number of labels. In that case, return zero. // 2) The number of labels is not same. As zero label sorts before, names // with more number of labels is greater. // a.b.com is a subdomain of b.com if ((c1 > c2) && subdomain) *subdomain = 1; verbosedebugf("DNSSECCanonicalOrder: returning c1 %d c2 %d\n", c1, c2); if (c1 != c2) return ((c1 < c2) ? -1 : 1); else return 0; } // Initialize the question enough so that it can be answered from the cache using SameNameRecordAnswersQuestion or // ResourceRecordAnswersQuestion. mDNSexport void InitializeQuestion(mDNS *const m, DNSQuestion *question, mDNSInterfaceID InterfaceID, const domainname *qname, mDNSu16 qtype, mDNSQuestionCallback *callback, void *context) { debugf("InitializeQuestion: Called for %##s (%s)", qname->c, DNSTypeName(qtype)); if (question->ThisQInterval != -1) mDNS_StopQuery(m, question); mDNS_SetupQuestion(question, InterfaceID, qname, qtype, callback, context); question->qnamehash = DomainNameHashValue(qname); question->ValidatingResponse = mDNStrue; // Need to hold the lock, as GetServerForQuestion (its callers) references m->timenow. mDNS_Lock(m); // We need to set the DNS server appropriately to match the question against the cache record. // Though not all callers of this function need it, we always do it to keep it simple. SetValidDNSServers(m, question); question->qDNSServer = GetServerForQuestion(m, question); mDNS_Unlock(m); // Make it look like unicast question->TargetQID = onesID; question->TimeoutQuestion = 1; question->ReturnIntermed = 1; // SetupQuestion sets LongLived if qtype == PTR question->LongLived = 0; } mDNSexport DNSSECVerifier *AllocateDNSSECVerifier(mDNS *const m, const domainname *name, mDNSu16 rrtype, mDNSInterfaceID InterfaceID, mDNSu8 ValidationRequired, DNSSECVerifierCallback dvcallback, mDNSQuestionCallback qcallback) { DNSSECVerifier *dv; dv = (DNSSECVerifier *)mDNSPlatformMemAllocate(sizeof(DNSSECVerifier)); if (!dv) { LogMsg("AllocateDNSSECVerifier: ERROR!! memory alloc failed"); return mDNSNULL; } mDNSPlatformMemZero(dv, sizeof(*dv)); LogDNSSEC("AllocateDNSSECVerifier called %p", dv); // Remember the question's name and type so that when we are done processing all // the verifications, we can trace the original question back AssignDomainName(&dv->origName, name); dv->origType = rrtype; dv->InterfaceID = InterfaceID; dv->DVCallback = dvcallback; dv->q.ThisQInterval = -1; ResetAuthChain(dv); // These two are used for Insecure proof if we end up doing it. // -Value of ValidationRequired so that we know whether this is a secure or insecure validation // -InsecureProofDone tells us whether the proof has been done or not dv->ValidationRequired = ValidationRequired; dv->InsecureProofDone = 0; dv->NumPackets = 0; mDNS_Lock(m); dv->StartTime = m->timenow; mDNS_Unlock(m); // The verifier's question has to be initialized as some of the callers assume it InitializeQuestion(m, &dv->q, InterfaceID, name, rrtype, qcallback, dv); return dv; } mDNSlocal AuthChain *AuthChainCopy(AuthChain *ae) { RRVerifier *rvfrom, **rvto; AuthChain **prev = mDNSNULL; AuthChain *retac = mDNSNULL; AuthChain *ac; while (ae) { ac = mDNSPlatformMemAllocate(sizeof(AuthChain)); if (!ac) { LogMsg("AuthChainCopy: AuthChain alloc failure"); if (retac) FreeDNSSECAuthChainInfo(retac); return mDNSfalse; } ac->next = mDNSNULL; if (!retac) retac = ac; rvfrom = ae->rrset; rvto = &ac->rrset; while (rvfrom && rvto) { *rvto = CopyRRVerifier(rvfrom); rvfrom = rvfrom->next; rvto = &((*rvto)->next); } rvfrom = ae->rrsig; rvto = &ac->rrsig; while (rvfrom && rvto) { *rvto = CopyRRVerifier(rvfrom); rvfrom = rvfrom->next; rvto = &((*rvto)->next); } rvfrom = ae->key; rvto = &ac->key; while (rvfrom && rvto) { *rvto = CopyRRVerifier(rvfrom); rvfrom = rvfrom->next; rvto = &((*rvto)->next); } if (prev) { *prev = ac; } prev = &(ac->next); ae = ae->next; } return retac; } mDNSlocal void FreeDNSSECAuthChainInfo(AuthChain *ac) { RRVerifier *rrset; RRVerifier *next; AuthChain *acnext; LogDNSSEC("FreeDNSSECAuthChainInfo: called"); while (ac) { acnext = ac->next; rrset = ac->rrset; while (rrset) { next = rrset->next; mDNSPlatformMemFree(rrset); rrset = next; } ac->rrset = mDNSNULL; rrset = ac->rrsig; while (rrset) { next = rrset->next; mDNSPlatformMemFree(rrset); rrset = next; } ac->rrsig = mDNSNULL; rrset = ac->key; while (rrset) { next = rrset->next; mDNSPlatformMemFree(rrset); rrset = next; } ac->key = mDNSNULL; mDNSPlatformMemFree(ac); ac = acnext; } } mDNSlocal void FreeDNSSECAuthChain(DNSSECVerifier *dv) { if (dv->ac) { FreeDNSSECAuthChainInfo(dv->ac); // if someone reuses the "dv", it will be initialized properly ResetAuthChain(dv); } if (dv->saveac) { FreeDNSSECAuthChainInfo(dv->saveac); dv->saveac = mDNSNULL; } } mDNSlocal void FreeAuthChain(mDNS *const m, void *context) { AuthChain *ac = (AuthChain *)context; (void) m; // unused FreeDNSSECAuthChainInfo(ac); } mDNSlocal void FreeDNSSECVerifierRRSets(DNSSECVerifier *dv) { RRVerifier *rrset; RRVerifier *next; //debugdnssec("FreeDNSSECVerifierRRSets called %p", dv); rrset = dv->rrset; while (rrset) { next = rrset->next; mDNSPlatformMemFree(rrset); rrset = next; } dv->rrset = mDNSNULL; rrset = dv->rrsig; while (rrset) { next = rrset->next; mDNSPlatformMemFree(rrset); rrset = next; } dv->rrsig = mDNSNULL; rrset = dv->key; while (rrset) { next = rrset->next; mDNSPlatformMemFree(rrset); rrset = next; } dv->key = mDNSNULL; rrset = dv->rrsigKey; while (rrset) { next = rrset->next; mDNSPlatformMemFree(rrset); rrset = next; } dv->rrsigKey = mDNSNULL; rrset = dv->ds; while (rrset) { next = rrset->next; mDNSPlatformMemFree(rrset); rrset = next; } dv->ds = mDNSNULL; rrset = dv->pendingNSEC; while (rrset) { next = rrset->next; mDNSPlatformMemFree(rrset); rrset = next; } dv->pendingNSEC = mDNSNULL; } mDNSexport void FreeDNSSECVerifier(mDNS *const m, DNSSECVerifier *dv) { LogDNSSEC("FreeDNSSECVerifier called %p", dv); if (dv->q.ThisQInterval != -1) mDNS_StopQuery(m, &dv->q); FreeDNSSECVerifierRRSets(dv); if (dv->ctx) AlgDestroy(dv->ctx); if (dv->ac || dv->saveac) FreeDNSSECAuthChain(dv); if (dv->parent) { LogDNSSEC("FreeDNSSECVerifier freeing parent %p", dv->parent); FreeDNSSECVerifier(m, dv->parent); } mDNSPlatformMemFree(dv); } mDNSlocal RRVerifier* CopyRRVerifier(RRVerifier *from) { RRVerifier *r; r = mDNSPlatformMemAllocate(sizeof (RRVerifier) + from->rdlength); if (!r) { LogMsg("CopyRRVerifier: memory failure"); return mDNSNULL; } mDNSPlatformMemCopy(r, from, sizeof(RRVerifier)); r->next = mDNSNULL; r->rdata = (mDNSu8*) ((mDNSu8 *)r + sizeof(RRVerifier)); mDNSPlatformMemCopy(r->rdata, from->rdata, r->rdlength); return r; } mDNSexport RRVerifier* AllocateRRVerifier(const ResourceRecord *const rr, mStatus *status) { RRVerifier *r; r = mDNSPlatformMemAllocate(sizeof (RRVerifier) + rr->rdlength); if (!r) { LogMsg("AllocateRRVerifier: memory failure"); *status = mStatus_NoMemoryErr; return mDNSNULL; } r->next = mDNSNULL; r->rrtype = rr->rrtype; r->rrclass = rr->rrclass; r->rroriginalttl = rr->rroriginalttl; r->rdlength = rr->rdlength; r->namehash = rr->namehash; r->rdatahash = rr->rdatahash; AssignDomainName(&r->name, rr->name); r->rdata = (mDNSu8*) ((mDNSu8 *)r + sizeof(RRVerifier)); // When we parsed the DNS response in GeLargeResourceRecord, for some records, we parse them into // host order so that the rest of the code does not have to bother with converting from network order // to host order. For signature verification, we need them back in network order. For DNSSEC records // like DNSKEY and DS, we just copy over the data both in GetLargeResourceRecord and putRData. if (!putRData(mDNSNULL, r->rdata, r->rdata + rr->rdlength, rr)) { LogMsg("AllocateRRVerifier: putRData failed"); *status = mStatus_BadParamErr; return mDNSNULL; } *status = mStatus_NoError; return r; } mDNSexport mStatus AddRRSetToVerifier(DNSSECVerifier *dv, const ResourceRecord *const rr, RRVerifier *rv, RRVerifierSet set) { RRVerifier *r; RRVerifier **v; mStatus status; if (!rv) { r = AllocateRRVerifier(rr, &status); if (!r) return status; } else r = rv; switch (set) { case RRVS_rr: v = &dv->rrset; break; case RRVS_rrsig: v = &dv->rrsig; break; case RRVS_key: v = &dv->key; break; case RRVS_rrsig_key: v = &dv->rrsigKey; break; case RRVS_ds: v = &dv->ds; break; default: LogMsg("AddRRSetToVerifier: ERROR!! default case %d", set); return mStatus_BadParamErr; } while (*v) v = &(*v)->next; *v = r; return mStatus_NoError; } // Validate the RRSIG. "type" tells which RRSIG that we are supposed to validate. We fetch RRSIG for // the rrset (type is RRVS_rrsig) and RRSIG for the key (type is RRVS_rrsig_key). mDNSexport void ValidateRRSIG(DNSSECVerifier *dv, RRVerifierSet type, const ResourceRecord *const rr) { RRVerifier *rv; mDNSu32 currentTime; rdataRRSig *rrsigRData = (rdataRRSig *)((mDNSu8 *)rr->rdata + sizeofRDataHeader); if (type == RRVS_rrsig) { rv = dv->rrset; } else if (type == RRVS_rrsig_key) { rv = dv->key; } else { LogMsg("ValidateRRSIG: ERROR!! type not valid %d", type); return; } // RFC 4035: // For each authoritative RRset in a signed zone, there MUST be at least // one RRSIG record that meets the following requirements: // // RRSet is defined by same name, class and type // // 1. The RRSIG RR and the RRset MUST have the same owner name and the same class. if (!SameDomainName(&rv->name, rr->name) || (rr->rrclass != rv->rrclass)) { debugdnssec("ValidateRRSIG: name mismatch or class mismatch"); return; } // 2. The RRSIG RR's Type Covered field MUST equal the RRset's type. if ((swap16(rrsigRData->typeCovered)) != rv->rrtype) { debugdnssec("ValidateRRSIG: typeCovered mismatch rrsig %d, rr type %d", swap16(rrsigRData->typeCovered), rv->rrtype); return; } // 3. The number of labels in the RRset owner name MUST be greater than or equal // to the value in the RRSIG RR's Labels field. if (rrsigRData->labels > CountLabels(&rv->name)) { debugdnssec("ValidateRRSIG: labels count problem rrsig %d, rr %d", rrsigRData->labels, CountLabels(&rv->name)); return; } // 4. The RRSIG RR's Signer's Name field MUST be the name of the zone that contains // the RRset. For a stub resolver, this can't be done in a secure way. Hence we // do it this way (discussed in dnsext mailing list) switch (rv->rrtype) { case kDNSType_NS: case kDNSType_SOA: case kDNSType_DNSKEY: //Signed by the owner if (!SameDomainName(&rv->name, (domainname *)&rrsigRData->signerName)) { debugdnssec("ValidateRRSIG: Signer Name does not match the record name for %s", DNSTypeName(rv->rrtype)); return; } break; case kDNSType_DS: // Should be signed by the parent if (SameDomainName(&rv->name, (domainname *)&rrsigRData->signerName)) { debugdnssec("ValidateRRSIG: Signer Name matches the record name for %s", DNSTypeName(rv->rrtype)); return; } // FALLTHROUGH default: { int c1 = CountLabels(&rv->name); int c2 = CountLabels((domainname *)&rrsigRData->signerName); if (c1 < c2) { debugdnssec("ValidateRRSIG: Signer Name not a subdomain label count %d < %d ", c1, c2); return; } domainname *d = (domainname *)SkipLeadingLabels(&rv->name, c1 - c2); if (!SameDomainName(d, (domainname *)&rrsigRData->signerName)) { debugdnssec("ValidateRRSIG: Signer Name not a subdomain"); return; } break; } } // 5. The validator's notion of the current time MUST be less than or equal to the // time listed in the RRSIG RR's Expiration field. // // 6. The validator's notion of the current time MUST be greater than or equal to the // time listed in the RRSIG RR's Inception field. currentTime = mDNSPlatformUTC(); if (DNS_SERIAL_LT(swap32(rrsigRData->sigExpireTime), currentTime)) { LogDNSSEC("ValidateRRSIG: Expired: currentTime %d, ExpireTime %d", (int)currentTime, swap32((int)rrsigRData->sigExpireTime)); return; } if (DNS_SERIAL_LT(currentTime, swap32(rrsigRData->sigInceptTime))) { LogDNSSEC("ValidateRRSIG: Future: currentTime %d, InceptTime %d", (int)currentTime, swap32((int)rrsigRData->sigInceptTime)); return; } if (AddRRSetToVerifier(dv, rr, mDNSNULL, type) != mStatus_NoError) { LogMsg("ValidateRRSIG: ERROR!! cannot allocate RRSet"); return; } } mDNSlocal mStatus CheckRRSIGForRRSet(mDNS *const m, DNSSECVerifier *dv, CacheRecord **negcr) { CacheGroup *cg; CacheRecord *cr; RRVerifier *rv; mDNSBool expectRRSIG = mDNSfalse; *negcr = mDNSNULL; if (!dv->rrset) { LogMsg("CheckRRSIGForRRSet: ERROR!! rrset NULL for origName %##s (%s)", dv->origName.c, DNSTypeName(dv->origType)); return mStatus_BadParamErr; } rv = dv->rrset; cg = CacheGroupForName(m, rv->namehash, &rv->name); if (!cg) { debugdnssec("CheckRRSIGForRRSet: cg null"); return mStatus_NoSuchRecord; } for (cr=cg->members; cr; cr=cr->next) { debugdnssec("CheckRRSIGForRRSet: checking the validity of rrsig"); if (cr->resrec.rrtype != kDNSType_RRSIG) { // Check to see if we should expect RRSIGs for the type that we are looking for. // We would expect RRSIGs, if we had previously issued the question with the // EDNS0/DOK bit set. if (cr->resrec.rrtype == dv->rrset->rrtype) { expectRRSIG = cr->CRDNSSECQuestion; LogDNSSEC("CheckRRSIGForRRSet: %s RRSIG for %s", (expectRRSIG ? "Expecting" : "Not Expecting"), CRDisplayString(m, cr)); } continue; } if (cr->resrec.RecordType == kDNSRecordTypePacketNegative) { if (!(*negcr)) { LogDNSSEC("CheckRRSIGForRRSet: Negative cache record %s encountered for %##s (%s)", CRDisplayString(m, cr), rv->name.c, DNSTypeName(rv->rrtype)); *negcr = cr; } else { LogMsg("CheckRRSIGForRRSet: ERROR!! Negative cache record %s already set for %##s (%s)", CRDisplayString(m, cr), rv->name.c, DNSTypeName(rv->rrtype)); } continue; } ValidateRRSIG(dv, RRVS_rrsig, &cr->resrec); } if (*negcr && dv->rrsig) { // Encountered both RRSIG and negative CR LogMsg("CheckRRSIGForRRSet: ERROR!! Encountered negative cache record %s and RRSIG for %##s (%s)", CRDisplayString(m, *negcr), rv->name.c, DNSTypeName(rv->rrtype)); return mStatus_BadParamErr; } // If we can't find RRSIGs, but we find a negative response then we need to validate that // which the caller will do it. Otherwise, if we should be expecting RRSIGs to be in the // cache already, then return error. if (dv->rrsig || *negcr) return mStatus_NoError; else if (expectRRSIG) return mStatus_BadParamErr; else return mStatus_NoSuchRecord; } mDNSlocal void CheckOneKeyForRRSIG(DNSSECVerifier *dv, const ResourceRecord *const rr) { rdataRRSig *rrsig; if (!dv->rrsig) { LogMsg("CheckOneKeyForRRSIG: ERROR!! rrsig NULL"); return; } rrsig = (rdataRRSig *)dv->rrsig->rdata; if (!SameDomainName((domainname *)&rrsig->signerName, rr->name)) { debugdnssec("CheckOneKeyForRRSIG: name mismatch"); return; } // We store all the keys including the ZSK and KSK and use them appropriately // later if (AddRRSetToVerifier(dv, rr, mDNSNULL, RRVS_key) != mStatus_NoError) { LogMsg("CheckOneKeyForRRSIG: ERROR!! cannot allocate RRSet"); return; } } mDNSlocal mStatus CheckKeyForRRSIG(mDNS *const m, DNSSECVerifier *dv, CacheRecord **negcr) { mDNSu32 namehash; CacheGroup *cg; CacheRecord *cr; rdataRRSig *rrsig; domainname *name; *negcr = mDNSNULL; if (!dv->rrsig) { LogMsg("CheckKeyForRRSIG: ERROR!! rrsig NULL"); return mStatus_BadParamErr; } // Signer name should be the same on all rrsig ?? rrsig = (rdataRRSig *)dv->rrsig->rdata; name = (domainname *)&rrsig->signerName; namehash = DomainNameHashValue(name); cg = CacheGroupForName(m, namehash, name); if (!cg) { debugdnssec("CheckKeyForRRSIG: cg null for %##s", name->c); return mStatus_NoSuchRecord; } for (cr=cg->members; cr; cr=cr->next) { if (cr->resrec.rrtype != kDNSType_DNSKEY) continue; if (cr->resrec.RecordType == kDNSRecordTypePacketNegative) { if (!(*negcr)) { LogDNSSEC("CheckKeyForRRSIG: Negative cache record %s encountered for %##s (DNSKEY)", CRDisplayString(m, cr), name->c); *negcr = cr; } else { LogMsg("CheckKeyForRRSIG: ERROR!! Negative cache record %s already set for %##s (DNSKEY)", CRDisplayString(m, cr), name->c); } continue; } debugdnssec("CheckKeyForRRSIG: checking the validity of key record"); CheckOneKeyForRRSIG(dv, &cr->resrec); } if (*negcr && dv->key) { // Encountered both RRSIG and negative CR LogMsg("CheckKeyForRRSIG: ERROR!! Encountered negative cache record %s and DNSKEY for %##s", CRDisplayString(m, *negcr), name->c); return mStatus_BadParamErr; } if (dv->key || *negcr) return mStatus_NoError; else return mStatus_NoSuchRecord; } mDNSlocal void CheckOneRRSIGForKey(DNSSECVerifier *dv, const ResourceRecord *const rr) { rdataRRSig *rrsig; if (!dv->rrsig) { LogMsg("CheckOneRRSIGForKey: ERROR!! rrsig NULL"); return; } rrsig = (rdataRRSig *)dv->rrsig->rdata; if (!SameDomainName((domainname *)&rrsig->signerName, rr->name)) { debugdnssec("CheckOneRRSIGForKey: name mismatch"); return; } ValidateRRSIG(dv, RRVS_rrsig_key, rr); } mDNSlocal mStatus CheckRRSIGForKey(mDNS *const m, DNSSECVerifier *dv, CacheRecord **negcr) { mDNSu32 namehash; CacheGroup *cg; CacheRecord *cr; rdataRRSig *rrsig; domainname *name; mDNSBool expectRRSIG = mDNSfalse; *negcr = mDNSNULL; if (!dv->rrsig) { LogMsg("CheckRRSIGForKey: ERROR!! rrsig NULL"); return mStatus_BadParamErr; } if (!dv->key) { LogMsg("CheckRRSIGForKey: ERROR!! key NULL"); return mStatus_BadParamErr; } rrsig = (rdataRRSig *)dv->rrsig->rdata; name = (domainname *)&rrsig->signerName; namehash = DomainNameHashValue(name); cg = CacheGroupForName(m, namehash, name); if (!cg) { debugdnssec("CheckRRSIGForKey: cg null %##s", name->c); return mStatus_NoSuchRecord; } for (cr=cg->members; cr; cr=cr->next) { if (cr->resrec.rrtype != kDNSType_RRSIG) { // Check to see if we should expect RRSIGs for the DNSKEY record that we are // looking for. We would expect RRSIGs, if we had previously issued the question // with the EDNS0/DOK bit set. if (cr->resrec.rrtype == kDNSType_DNSKEY) { expectRRSIG = cr->CRDNSSECQuestion; LogDNSSEC("CheckRRSIGForKey: %s RRSIG for %s", (expectRRSIG ? "Expecting" : "Not Expecting"), CRDisplayString(m, cr)); } continue; } if (cr->resrec.RecordType == kDNSRecordTypePacketNegative) { if (!(*negcr)) { LogDNSSEC("CheckRRSIGForKey: Negative cache record %s encountered for %##s (RRSIG)", CRDisplayString(m, cr), name->c); *negcr = cr; } else { LogMsg("CheckRRSIGForKey: ERROR!! Negative cache record %s already set for %##s (RRSIG)", CRDisplayString(m, cr), name->c); } continue; } debugdnssec("CheckRRSIGForKey: checking the validity of rrsig"); CheckOneRRSIGForKey(dv, &cr->resrec); } if (*negcr && dv->rrsigKey) { // Encountered both RRSIG and negative CR LogMsg("CheckRRSIGForKey: ERROR!! Encountered negative cache record %s and DNSKEY for %##s", CRDisplayString(m, *negcr), name->c); return mStatus_BadParamErr; } // If we can't find RRSIGs, but we find a negative response then we need to validate that // which the caller will do it. Finally, make sure that we are not expecting RRSIGS. if (dv->rrsigKey || *negcr) return mStatus_NoError; else if (expectRRSIG) return mStatus_BadParamErr; else return mStatus_NoSuchRecord; } mDNSlocal void CheckOneDSForKey(DNSSECVerifier *dv, const ResourceRecord *const rr) { mDNSu16 tag; rdataDS *DS; RRVerifier *keyv; rdataDNSKey *key; rdataRRSig *rrsig; if (!dv->rrsig) { LogMsg("CheckOneDSForKey: ERROR!! rrsig NULL"); return; } rrsig = (rdataRRSig *)dv->rrsig->rdata; DS = (rdataDS *)((mDNSu8 *)rr->rdata + sizeofRDataHeader); if (!SameDomainName((domainname *)&rrsig->signerName, rr->name)) { debugdnssec("CheckOneDSForKey: name mismatch"); return; } for (keyv = dv->key; keyv; keyv = keyv->next) { key = (rdataDNSKey *)keyv->rdata; tag = (mDNSu16)keytag((mDNSu8 *)key, keyv->rdlength); if (tag != swap16(DS->keyTag)) { debugdnssec("CheckOneDSForKey: keyTag mismatch keyTag %d, DStag %d", tag, swap16(DS->keyTag)); continue; } if (key->alg != DS->alg) { debugdnssec("CheckOneDSForKey: alg mismatch key alg%d, DS alg %d", key->alg, swap16(DS->alg)); continue; } if (AddRRSetToVerifier(dv, rr, mDNSNULL, RRVS_ds) != mStatus_NoError) { debugdnssec("CheckOneDSForKey: cannot allocate RRSet"); } } } mDNSlocal mStatus CheckDSForKey(mDNS *const m, DNSSECVerifier *dv, CacheRecord **negcr) { mDNSu32 namehash; CacheGroup *cg; CacheRecord *cr; rdataRRSig *rrsig; domainname *name; *negcr = mDNSNULL; if (!dv->rrsig) { LogMsg("CheckDSForKey: ERROR!! rrsig NULL"); return mStatus_BadParamErr; } if (!dv->key) { LogMsg("CheckDSForKey: ERROR!! key NULL"); return mStatus_BadParamErr; } rrsig = (rdataRRSig *)dv->rrsig->rdata; name = (domainname *)&rrsig->signerName; namehash = DomainNameHashValue(name); cg = CacheGroupForName(m, namehash, name); if (!cg) { debugdnssec("CheckDSForKey: cg null for %s", name->c); return mStatus_NoSuchRecord; } for (cr=cg->members; cr; cr=cr->next) { if (cr->resrec.rrtype != kDNSType_DS) continue; if (cr->resrec.RecordType == kDNSRecordTypePacketNegative) { if (!(*negcr)) { LogDNSSEC("CheckDSForKey: Negative cache record %s encountered for %##s (DS)", CRDisplayString(m, cr), name->c); *negcr = cr; } else { LogMsg("CheckDSForKey: ERROR!! Negative cache record %s already set for %##s (DS)", CRDisplayString(m, cr), name->c); } continue; } CheckOneDSForKey(dv, &cr->resrec); } if (*negcr && dv->ds) { // Encountered both RRSIG and negative CR LogMsg("CheckDSForKey: ERROR!! Encountered negative cache record %s and DS for %##s", CRDisplayString(m, *negcr), name->c); return mStatus_BadParamErr; } if (dv->ds || *negcr) return mStatus_NoError; else return mStatus_NoSuchRecord; } // It returns mDNStrue if we have all the rrsets for verification and mDNSfalse otherwise. mDNSlocal mDNSBool GetAllRRSetsForVerification(mDNS *const m, DNSSECVerifier *dv) { mStatus err; CacheRecord *negcr; rdataRRSig *rrsig; if (!dv->rrset) { LogMsg("GetAllRRSetsForVerification: ERROR!! rrset NULL"); dv->DVCallback(m, dv, DNSSEC_Bogus); return mDNSfalse; } if (dv->next == RRVS_done) return mDNStrue; debugdnssec("GetAllRRSetsForVerification: next %d", dv->next); switch (dv->next) { case RRVS_rrsig: // If we can't find the RRSIG for the rrset, re-issue the query. // // NOTE: It is possible that the cache might answer partially e.g., RRSIGs match qtype but the // whole set is not there. In that case the validation will fail. Ideally we should flush the // cache and reissue the query (TBD). err = CheckRRSIGForRRSet(m, dv, &negcr); if (err != mStatus_NoSuchRecord && err != mStatus_NoError) { dv->DVCallback(m, dv, DNSSEC_Bogus); return mDNSfalse; } // Need to initialize the question as if we end up in ValidateWithNSECS below, the nsec proofs // looks in "dv->q" for the proof. Note that we have to use currQtype as the response could be // a CNAME and dv->rrset->rrtype would be set to CNAME and not the original question type that // resulted in CNAME. InitializeQuestion(m, &dv->q, dv->InterfaceID, &dv->rrset->name, dv->currQtype, VerifySigCallback, dv); // We may not have the NSECS if the previous query was a non-DNSSEC query if (negcr && negcr->nsec) { ValidateWithNSECS(m, dv, negcr); return mDNSfalse; } dv->next = RRVS_key; if (!dv->rrsig) { // We already found the rrset to verify. Ideally we should just issue the query for the RRSIG. Unfortunately, // that does not work well as the response may not contain the RRSIG whose typeCovered matches the // rrset->rrtype (recursive server returns what is in its cache). Hence, we send the original query with the // DO bit set again to get the RRSIG. Normally this would happen if there was question which did not require // DNSSEC validation (ValidationRequied = 0) populated the cache and later when the ValidationRequired question // comes along, we need to get the RRSIGs. If we started off with ValidationRequired question we would have // already set the DO bit and not able to get RRSIGs e.g., bad CPE device, we would reissue the query here // again once more. // // Also, if it is a wildcard expanded answer, we need to issue the query with the original type for it to // elicit the right NSEC records. Just querying for RRSIG alone is not sufficient. // // Note: For this to work, the core needs to deliver RRSIGs when they are added to the cache even if the // "qtype" is not RRSIG. debugdnssec("GetAllRRSetsForVerification: Fetching RRSIGS for RRSET"); dv->NumPackets++; mDNS_StartQuery(m, &dv->q); return mDNSfalse; } // if we found the RRSIG, then fall through to find the DNSKEY case RRVS_key: err = CheckKeyForRRSIG(m, dv, &negcr); if (err != mStatus_NoSuchRecord && err != mStatus_NoError) { dv->DVCallback(m, dv, DNSSEC_Bogus); return mDNSfalse; } // Need to initialize the question as if we end up in ValidateWithNSECS below, the nsec proofs // looks in "dv->q" for the proof. rrsig = (rdataRRSig *)dv->rrsig->rdata; InitializeQuestion(m, &dv->q, dv->InterfaceID, (domainname *)&rrsig->signerName, kDNSType_DNSKEY, VerifySigCallback, dv); // We may not have the NSECS if the previous query was a non-DNSSEC query if (negcr && negcr->nsec) { ValidateWithNSECS(m, dv, negcr); return mDNSfalse; } dv->next = RRVS_rrsig_key; if (!dv->key) { debugdnssec("GetAllRRSetsForVerification: Fetching DNSKEY for RRSET"); dv->NumPackets++; mDNS_StartQuery(m, &dv->q); return mDNSfalse; } // if we found the DNSKEY, then fall through to find the RRSIG for the DNSKEY case RRVS_rrsig_key: err = CheckRRSIGForKey(m, dv, &negcr); // if we are falling through, then it is okay if we don't find the record if (err != mStatus_NoSuchRecord && err != mStatus_NoError) { dv->DVCallback(m, dv, DNSSEC_Bogus); return mDNSfalse; } // Need to initialize the question as if we end up in ValidateWithNSECS below, the nsec proofs // looks in "dv->q" for the proof. rrsig = (rdataRRSig *)dv->rrsig->rdata; InitializeQuestion(m, &dv->q, dv->InterfaceID, (domainname *)&rrsig->signerName, kDNSType_DNSKEY, VerifySigCallback, dv); // We may not have the NSECS if the previous query was a non-DNSSEC query if (negcr && negcr->nsec) { ValidateWithNSECS(m, dv, negcr); return mDNSfalse; } dv->next = RRVS_ds; debugdnssec("GetAllRRSetsForVerification: RRVS_rrsig_key %p", dv->rrsigKey); if (!dv->rrsigKey) { debugdnssec("GetAllRRSetsForVerification: Fetching RRSIGS for DNSKEY"); dv->NumPackets++; mDNS_StartQuery(m, &dv->q); return mDNSfalse; } // if we found RRSIG for the DNSKEY, then fall through to find the DS case RRVS_ds: { domainname *qname; rrsig = (rdataRRSig *)dv->rrsig->rdata; qname = (domainname *)&rrsig->signerName; err = CheckDSForKey(m, dv, &negcr); if (err != mStatus_NoSuchRecord && err != mStatus_NoError) { dv->DVCallback(m, dv, DNSSEC_Bogus); return mDNSfalse; } // Need to initialize the question as if we end up in ValidateWithNSECS below, the nsec proofs // looks in "dv->q" for the proof. InitializeQuestion(m, &dv->q, dv->InterfaceID, qname, kDNSType_DS, VerifySigCallback, dv); // We may not have the NSECS if the previous query was a non-DNSSEC query if (negcr && negcr->nsec) { ValidateWithNSECS(m, dv, negcr); return mDNSfalse; } dv->next = RRVS_done; // If we have a trust anchor, then don't bother looking up the DS record if (!dv->ds && !TrustedKeyPresent(m, dv)) { // There is no DS for the root. Hence, if we don't have the trust // anchor for root, just fail. if (SameDomainName(qname, (const domainname *)"\000")) { LogDNSSEC("GetAllRRSetsForVerification: Reached root"); dv->DVCallback(m, dv, DNSSEC_Bogus); return mDNSfalse; } debugdnssec("GetAllRRSetsForVerification: Fetching DS"); dv->NumPackets++; mDNS_StartQuery(m, &dv->q); return mDNSfalse; } else { debugdnssec("GetAllRRSetsForVerification: Skipped fetching the DS"); return mDNStrue; } } default: LogMsg("GetAllRRSetsForVerification: ERROR!! unknown next %d", dv->next); dv->DVCallback(m, dv, DNSSEC_Bogus); return mDNSfalse; } } #ifdef DNSSEC_DEBUG mDNSlocal void PrintFixedSignInfo(rdataRRSig *rrsig, domainname *signerName, int sigNameLen, mDNSu8 *fixedPart, int fixedPartLen) { int j; char buf[RRSIG_FIXED_SIZE *3 + 1]; // 3 bytes count for %2x + 1 and the one byte for null at the end char sig[sigNameLen * 3 + 1]; char fp[fixedPartLen * 3 + 1]; int length; length = 0; for (j = 0; j < RRSIG_FIXED_SIZE; j++) length += mDNS_snprintf(buf+length, sizeof(buf) - length - 1, "%2x ", ((mDNSu8 *)rrsig)[j]); LogMsg("RRSIG(%d) %s", RRSIG_FIXED_SIZE, buf); length = 0; for (j = 0; j < sigNameLen; j++) length += mDNS_snprintf(sig+length, sizeof(sig) - length - 1, "%2x ", signerName->c[j]); LogMsg("SIGNAME(%d) %s", sigNameLen, sig); length = 0; for (j = 0; j < fixedPartLen; j++) length += mDNS_snprintf(fp+length, sizeof(fp) - length - 1, "%2x ", fixedPart[j]); LogMsg("fixedPart(%d) %s", fixedPartLen, fp); } mDNSlocal void PrintVarSignInfo(mDNSu16 rdlen, mDNSu8 *rdata) { unsigned int j; mDNSu8 *r; unsigned int blen = swap16(rdlen); char buf[blen * 3 + 1]; // 3 bytes count for %2x + 1 and the one byte for null at the end int length; length = 0; r = (mDNSu8 *)&rdlen; for (j = 0; j < sizeof(mDNSu16); j++) length += mDNS_snprintf(buf+length, sizeof(buf) - length - 1, "%2x ", r[j]); LogMsg("RDLENGTH(%d) %s", sizeof(mDNSu16), buf); length = 0; for (j = 0; j < blen; j++) length += mDNS_snprintf(buf+length, sizeof(buf) - length - 1, "%2x ", rdata[j]); LogMsg("RDATA(%d) %s", blen, buf); } #else mDNSlocal void PrintVarSignInfo(mDNSu16 rdlen, mDNSu8 *rdata) { (void)rdlen; (void)rdata; } mDNSlocal void PrintFixedSignInfo(rdataRRSig *rrsig, domainname *signerName, int sigNameLen, mDNSu8 *fixedPart, int fixedPartLen) { (void)rrsig; (void)signerName; (void)sigNameLen; (void)fixedPart; (void)fixedPartLen; } #endif // Used for RDATA comparison typedef struct { mDNSu16 rdlength; mDNSu16 rrtype; mDNSu8 *rdata; } rdataComp; mDNSlocal int rdata_compare(mDNSu8 *const rdata1, mDNSu8 *const rdata2, int rdlen1, int rdlen2) { int len; int ret; len = (rdlen1 < rdlen2) ? rdlen1 : rdlen2; ret = DNSMemCmp(rdata1, rdata2, len); if (ret != 0) return ret; // RDATA is same at this stage. Consider them equal if they are of same length. Otherwise // decide based on their lengths. return ((rdlen1 == rdlen2) ? 0 : (rdlen1 < rdlen2) ? -1 : 1); } mDNSlocal int name_compare(mDNSu8 *const rdata1, mDNSu8 *const rdata2, int rdlen1, int rdlen2) { domainname *n1 = (domainname *)rdata1; domainname *n2 = (domainname *)rdata2; mDNSu8 *a = n1->c; mDNSu8 *b = n2->c; int count, c1, c2; int i, j, len; c1 = CountLabels(n1); c2 = CountLabels(n2); count = c1 < c2 ? c1 : c2; // We can't use SameDomainName as we need to know exactly which is greater/smaller // for sorting purposes. Hence, we need to compare label by label for (i = 0; i < count; i++) { // Are the lengths same ? if (*a != *b) { debugdnssec("compare_name: returning c1 %d, c2 %d", *a, *b); return ((*a < *b) ? -1 : 1); } len = *a; rdlen1 -= (len + 1); rdlen2 -= (len + 1); if (rdlen1 < 0 || rdlen2 < 0) { LogMsg("name_compare: ERROR!! not enough data rdlen1 %d, rdlen2 %d", rdlen1, rdlen2); return -1; } a++; b++; for (j = 0; j < len; j++) { mDNSu8 ac = *a++; mDNSu8 bc = *b++; if (mDNSIsUpperCase(ac)) ac += 'a' - 'A'; if (mDNSIsUpperCase(bc)) bc += 'a' - 'A'; if (ac != bc) { debugdnssec("compare_name: returning ac %c, bc %c", ac, bc); return ((ac < bc) ? -1 : 1); } } } return 0; } mDNSlocal int srv_compare(rdataComp *const r1, rdataComp *const r2) { int res; int length1, length2; length1 = r1->rdlength; length2 = r2->rdlength; // We should have at least priority, weight, port plus 1 byte if (length1 < 7 || length2 < 7) { LogMsg("srv_compare: ERROR!! Length smaller than 7 bytes"); return -1; } // Compare priority, weight and port res = DNSMemCmp(r1->rdata, r2->rdata, 6); if (res != 0) return res; length1 -= 6; length2 -= 6; return (name_compare(r1->rdata + 6, r2->rdata + 6, length1, length2)); } mDNSlocal int tsig_compare(rdataComp *const r1, rdataComp *const r2) { int offset1, offset2; int length1, length2; int res, dlen; offset1 = offset2 = 0; length1 = r1->rdlength; length2 = r2->rdlength; // we should have at least one byte to start with if (length1 < 1 || length2 < 1) { LogMsg("sig_compare: Length smaller than 18 bytes"); return -1; } res = name_compare(r1->rdata, r2->rdata, length1, length2); if (res != 0) return res; dlen = DomainNameLength((domainname *)r1->rdata); offset1 += dlen; offset2 += dlen; length1 -= dlen; length2 -= dlen; if (length1 <= 1 || length2 <= 1) { LogMsg("tsig_compare: data too small to compare length1 %d, length2 %d", length1, length2); return -1; } return (rdata_compare(r1->rdata + offset1, r2->rdata + offset2, length1, length2)); } // Compares types that conform to : mDNSlocal int lenval_compare(mDNSu8 *d1, mDNSu8 *d2, int *len1, int *len2, int rem1, int rem2) { int len; int res; if (rem1 <= 1 || rem2 <= 1) { LogMsg("lenval_compare: data too small to compare length1 %d, length2 %d", rem1, rem2); return -1; } *len1 = (int)d1[0]; *len2 = (int)d2[0]; len = (*len1 < *len2 ? *len1 : *len2); res = DNSMemCmp(d1, d2, len + 1); return res; } // RFC 2915: Order (2) Preference(2) and variable length: Flags Service Regexp Replacement mDNSlocal int naptr_compare(rdataComp *const r1, rdataComp *const r2) { mDNSu8 *d1 = r1->rdata; mDNSu8 *d2 = r2->rdata; int len1, len2, res; int length1, length2; length1 = r1->rdlength; length2 = r2->rdlength; // Order, Preference plus at least 1 byte if (length1 < 5 || length2 < 5) { LogMsg("naptr_compare: Length smaller than 18 bytes"); return -1; } // Compare order and preference res = DNSMemCmp(d1, d2, 4); if (res != 0) return res; d1 += 4; d2 += 4; length1 -= 4; length2 -= 4; // Compare Flags (including the length byte) res = lenval_compare(d1, d2, &len1, &len2, length1, length2); if (res != 0) return res; d1 += (len1 + 1); d2 += (len2 + 1); length1 -= (len1 + 1); length2 -= (len2 + 1); // Compare Service (including the length byte) res = lenval_compare(d1, d2, &len1, &len2, length1, length2); if (res != 0) return res; d1 += (len1 + 1); d2 += (len2 + 1); length1 -= (len1 + 1); length2 -= (len2 + 1); // Compare regexp (including the length byte) res = lenval_compare(d1, d2, &len1, &len2, length1, length2); if (res != 0) return res; d1 += (len1 + 1); d2 += (len2 + 1); length1 -= (len1 + 1); length2 -= (len2 + 1); // Compare Replacement return name_compare(d1, d2, length1, length2); } // RFC 1035: MINFO: Two domain names // RFC 1183: RP: Two domain names mDNSlocal int dom2_compare(mDNSu8 *d1, mDNSu8 *d2, int length1, int length2) { int res, dlen; // We need at least one byte to start with if (length1 < 1 || length2 < 1) { LogMsg("dom2_compare:1: data too small length1 %d, length2 %d", length1, length2); return -1; } res = name_compare(d1, d2, length1, length2); if (res != 0) return res; dlen = DomainNameLength((domainname *)d1); length1 -= dlen; length2 -= dlen; // We need at least one byte to start with if (length1 < 1 || length2 < 1) { LogMsg("dom2_compare:2: data too small length1 %d, length2 %d", length1, length2); return -1; } d1 += dlen; d2 += dlen; return name_compare(d1, d2, length1, length2); } // MX : preference (2 bytes), domainname mDNSlocal int mx_compare(rdataComp *const r1, rdataComp *const r2) { int res; int length1, length2; length1 = r1->rdlength; length2 = r2->rdlength; // We need at least two bytes + 1 extra byte for the domainname to start with if (length1 < 3 || length2 < 3) { LogMsg("mx_compare: data too small length1 %d, length2 %d", length1, length2); return -1; } res = DNSMemCmp(r1->rdata, r2->rdata, 2); if (res != 0) return res; length1 -= 2; length2 -= 2; return name_compare(r1->rdata + 2, r2->rdata + 2, length1, length2); } // RFC 2163 (PX) : preference (2 bytes), map822. mapx400 (domainnames) mDNSlocal int px_compare(rdataComp *const r1, rdataComp *const r2) { int res; // We need at least two bytes + 1 extra byte for the domainname to start with if (r1->rdlength < 3 || r2->rdlength < 3) { LogMsg("px_compare: data too small length1 %d, length2 %d", r1->rdlength, r2->rdlength); return -1; } res = DNSMemCmp(r1->rdata, r2->rdata, 2); if (res != 0) return res; return dom2_compare(r1->rdata + 2, r2->rdata + 2, r1->rdlength - 2, r2->rdlength - 2); } mDNSlocal int soa_compare(rdataComp *r1, rdataComp *r2) { int res, dlen; int offset1, offset2; int length1, length2; length1 = r1->rdlength; length2 = r2->rdlength; offset1 = offset2 = 0; // We need at least 20 bytes plus 1 byte for each domainname if (length1 < 22 || length2 < 22) { LogMsg("soa_compare:1: data too small length1 %d, length2 %d", length1, length2); return -1; } // There are two domainnames followed by 20 bytes of serial, refresh, retry, expire and min // Compare the names and then the rest of the bytes res = name_compare(r1->rdata, r2->rdata, length1, length2); if (res != 0) return res; dlen = DomainNameLength((domainname *)r1->rdata); length1 -= dlen; length2 -= dlen; if (length1 < 1 || length2 < 1) { LogMsg("soa_compare:2: data too small length1 %d, length2 %d", length1, length2); return -1; } offset1 += dlen; offset2 += dlen; res = name_compare(r1->rdata + offset1, r2->rdata + offset2, length1, length2); if (res != 0) return res; dlen = DomainNameLength((domainname *)r1->rdata); length1 -= dlen; length2 -= dlen; if (length1 < 20 || length2 < 20) { LogMsg("soa_compare:3: data too small length1 %d, length2 %d", length1, length2); return -1; } offset1 += dlen; offset2 += dlen; return (rdata_compare(r1->rdata + offset1, r2->rdata + offset2, length1, length2)); } // RFC 4034 Section 6.0 states that: // // A canonical RR form and ordering within an RRset are required in order to // construct and verify RRSIG RRs. // // This function is called to order within an RRset. We can't just do a memcmp as // as stated in 6.3. This function is responsible for the third bullet in 6.2, where // the RDATA has to be converted to lower case if it has domain names. mDNSlocal int RDATACompare(const void *rdata1, const void *rdata2) { rdataComp *r1 = (rdataComp *)rdata1; rdataComp *r2 = (rdataComp *)rdata2; if (r1->rrtype != r2->rrtype) { LogMsg("RDATACompare: ERROR!! comparing rdata of wrong types type1: %d, type2: %d", r1->rrtype, r2->rrtype); return -1; } switch (r1->rrtype) { case kDNSType_A: // 1. Address Record case kDNSType_NULL: // 10 NULL RR case kDNSType_WKS: // 11 Well-known-service case kDNSType_HINFO: // 13 Host information case kDNSType_TXT: // 16 Arbitrary text string case kDNSType_X25: // 19 X_25 calling address case kDNSType_ISDN: // 20 ISDN calling address case kDNSType_NSAP: // 22 NSAP address case kDNSType_KEY: // 25 Security key case kDNSType_GPOS: // 27 Geographical position (withdrawn) case kDNSType_AAAA: // 28 IPv6 Address case kDNSType_LOC: // 29 Location Information case kDNSType_EID: // 31 Endpoint identifier case kDNSType_NIMLOC: // 32 Nimrod Locator case kDNSType_ATMA: // 34 ATM Address case kDNSType_CERT: // 37 Certification record case kDNSType_A6: // 38 IPv6 Address (deprecated) case kDNSType_SINK: // 40 Kitchen sink (experimental) case kDNSType_OPT: // 41 EDNS0 option (meta-RR) case kDNSType_APL: // 42 Address Prefix List case kDNSType_DS: // 43 Delegation Signer case kDNSType_SSHFP: // 44 SSH Key Fingerprint case kDNSType_IPSECKEY: // 45 IPSECKEY case kDNSType_RRSIG: // 46 RRSIG case kDNSType_NSEC: // 47 Denial of Existence case kDNSType_DNSKEY: // 48 DNSKEY case kDNSType_DHCID: // 49 DHCP Client Identifier case kDNSType_NSEC3: // 50 Hashed Authenticated Denial of Existence case kDNSType_NSEC3PARAM: // 51 Hashed Authenticated Denial of Existence case kDNSType_HIP: // 55 Host Identity Protocol case kDNSType_SPF: // 99 Sender Policy Framework for E-Mail default: return rdata_compare(r1->rdata, r2->rdata, r1->rdlength, r2->rdlength); case kDNSType_NS: // 2 Name Server case kDNSType_MD: // 3 Mail Destination case kDNSType_MF: // 4 Mail Forwarder case kDNSType_CNAME: // 5 Canonical Name case kDNSType_MB: // 7 Mailbox case kDNSType_MG: // 8 Mail Group case kDNSType_MR: // 9 Mail Rename case kDNSType_PTR: // 12 Domain name pointer case kDNSType_NSAP_PTR: // 23 Reverse NSAP lookup (deprecated) case kDNSType_DNAME: // 39 Non-terminal DNAME (for IPv6) return name_compare(r1->rdata, r2->rdata, r1->rdlength, r2->rdlength); case kDNSType_SRV: // 33 Service record return srv_compare(r1, r2); case kDNSType_SOA: // 6 Start of Authority return soa_compare(r1, r2); case kDNSType_RP: // 17 Responsible person case kDNSType_MINFO: // 14 Mailbox information return dom2_compare(r1->rdata, r2->rdata, r1->rdlength, r2->rdlength); case kDNSType_MX: // 15 Mail Exchanger case kDNSType_AFSDB: // 18 AFS cell database case kDNSType_RT: // 21 Router case kDNSType_KX: // 36 Key Exchange return mx_compare(r1, r2); case kDNSType_PX: // 26 X.400 mail mapping return px_compare(r1, r2); case kDNSType_NAPTR: // 35 Naming Authority PoinTeR return naptr_compare(r1, r2); case kDNSType_TKEY: // 249 Transaction key case kDNSType_TSIG: // 250 Transaction signature // TSIG and TKEY have a domainname followed by data return tsig_compare(r1, r2); // TBD: We are comparing them as opaque types, perhaps not right case kDNSType_SIG: // 24 Security signature case kDNSType_NXT: // 30 Next domain (security) LogMsg("RDATACompare: WARNING!! explicit support has not been added, using default"); return rdata_compare(r1->rdata, r2->rdata, r1->rdlength, r2->rdlength); } } // RFC 4034 section 6.2 requirement for verifying signature. // // 3. if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR, // HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX, // SRV, DNAME, A6, RRSIG, or NSEC, all uppercase US-ASCII letters in // the DNS names contained within the RDATA are replaced by the // corresponding lowercase US-ASCII letters; // // NSEC and HINFO is not needed as per dnssec-bis update. RRSIG is done elsewhere // as part of signature verification mDNSlocal void ConvertRDATAToCanonical(mDNSu16 rrtype, mDNSu16 rdlength, mDNSu8 *rdata) { domainname name; int len; mDNSu8 *origRdata = rdata; // Ensure that we have at least one byte of data to examine and modify. if (!rdlength) { LogMsg("ConvertRDATAToCanonical: rdlength zero for rrtype %s", DNSTypeName(rrtype)); return; } switch (rrtype) { // Not adding suppot for A6 as it is deprecated case kDNSType_A6: // 38 IPv6 Address (deprecated) default: debugdnssec("ConvertRDATAToCanonical: returning from default %s", DNSTypeName(rrtype)); return; case kDNSType_NS: // 2 Name Server case kDNSType_MD: // 3 Mail Destination case kDNSType_MF: // 4 Mail Forwarder case kDNSType_CNAME: // 5 Canonical Name case kDNSType_MB: // 7 Mailbox case kDNSType_MG: // 8 Mail Group case kDNSType_MR: // 9 Mail Rename case kDNSType_PTR: // 12 Domain name pointer case kDNSType_DNAME: // 39 Non-terminal DNAME (for IPv6) case kDNSType_NXT: // 30 Next domain (security) // TSIG and TKEY are not mentioned in RFC 4034, but we just leave it here case kDNSType_TSIG: // 250 Transaction signature case kDNSType_TKEY: // 249 Transaction key if (DNSNameToLowerCase((domainname *)rdata, &name) != mStatus_NoError) { LogMsg("ConvertRDATAToCanonical: ERROR!! DNSNameToLowerCase failed"); return; } AssignDomainName((domainname *)rdata, &name); return; case kDNSType_MX: // 15 Mail Exchanger case kDNSType_AFSDB: // 18 AFS cell database case kDNSType_RT: // 21 Router case kDNSType_KX: // 36 Key Exchange // format: preference - 2 bytes, followed by name // Ensure that we have at least 3 bytes (preference + 1 byte for the domain name) if (rdlength <= 3) { LogMsg("ConvertRDATAToCanonical:MX: rdlength %d for rrtype %s too small", rdlength, DNSTypeName(rrtype)); return; } if (DNSNameToLowerCase((domainname *)(rdata + 2), &name) != mStatus_NoError) { LogMsg("ConvertRDATAToCanonical: MX: ERROR!! DNSNameToLowerCase failed"); return; } AssignDomainName((domainname *)(rdata + 2), &name); return; case kDNSType_SRV: // 33 Service record // format : priority, weight and port - 6 bytes, followed by name if (rdlength <= 7) { LogMsg("ConvertRDATAToCanonical:SRV: rdlength %d for rrtype %s too small", rdlength, DNSTypeName(rrtype)); return; } if (DNSNameToLowerCase((domainname *)(rdata + 6), &name) != mStatus_NoError) { LogMsg("ConvertRDATAToCanonical: SRV: ERROR!! DNSNameToLowerCase failed"); return; } AssignDomainName((domainname *)(rdata + 6), &name); return; case kDNSType_PX: // 26 X.400 mail mapping if (rdlength <= 3) { LogMsg("ConvertRDATAToCanonical:PX: rdlength %d for rrtype %s too small", rdlength, DNSTypeName(rrtype)); return; } // Preference followed by two domain names rdata += 2; /* FALLTHROUGH */ case kDNSType_RP: // 17 Responsible person case kDNSType_SOA: // 6 Start of Authority case kDNSType_MINFO: // 14 Mailbox information if (DNSNameToLowerCase((domainname *)rdata, &name) != mStatus_NoError) { LogMsg("ConvertRDATAToCanonical: SOA1: ERROR!! DNSNameToLowerCase failed"); return; } AssignDomainName((domainname *)rdata, &name); len = DomainNameLength((domainname *)rdata); if (rdlength <= len + 1) { LogMsg("ConvertRDATAToCanonical:RP: rdlength %d for rrtype %s too small", rdlength, DNSTypeName(rrtype)); return; } rdata += len; if (DNSNameToLowerCase((domainname *)rdata, &name) != mStatus_NoError) { LogMsg("ConvertRDATAToCanonical: SOA2: ERROR!! DNSNameToLowerCase failed"); return; } AssignDomainName((domainname *)rdata, &name); return; case kDNSType_NAPTR: // 35 Naming Authority Pointer // order and preference rdata += 4; // Flags (including the length byte) rdata += (((int) rdata[0]) + 1); // Service (including the length byte) rdata += (((int) rdata[0]) + 1); // regexp (including the length byte) rdata += (((int) rdata[0]) + 1); // Replacement field is a domainname. If we have at least one more byte, then we are okay. if ((origRdata + rdlength) < rdata + 1) { LogMsg("ConvertRDATAToCanonical:NAPTR: origRdata %p, rdlength %d, rdata %p for rrtype %s too small", origRdata, rdlength, rdata, DNSTypeName(rrtype)); return; } if (DNSNameToLowerCase((domainname *)rdata, &name) != mStatus_NoError) { LogMsg("ConvertRDATAToCanonical: NAPTR2: ERROR!! DNSNameToLowerCase failed"); return; } AssignDomainName((domainname *)rdata, &name); case kDNSType_SIG: // 24 Security signature // format: <18 bytes> if (rdlength <= 19) { LogMsg("ConvertRDATAToCanonical:SIG: rdlength %d for rrtype %s too small", rdlength, DNSTypeName(rrtype)); return; } // Preference followed by two domain names rdata += 18; if (DNSNameToLowerCase((domainname *)rdata, &name) != mStatus_NoError) { LogMsg("ConvertRDATAToCanonical: SIG: ERROR!! DNSNameToLowerCase failed"); return; } AssignDomainName((domainname *)rdata, &name); return; } } mDNSlocal mDNSBool ValidateSignatureWithKey(DNSSECVerifier *dv, RRVerifier *rrset, RRVerifier *keyv, RRVerifier *sig) { domainname name; domainname signerName; int labels; mDNSu8 fixedPart[MAX_DOMAIN_NAME + 8]; // domainname + type + class + ttl int fixedPartLen; RRVerifier *tmp; int nrrsets; rdataComp *ptr, *start, *p; rdataRRSig *rrsig; rdataDNSKey *key; int i; int sigNameLen; mDNSu16 temp; mStatus algRet; key = (rdataDNSKey *)keyv->rdata; rrsig = (rdataRRSig *)sig->rdata; LogDNSSEC("ValidateSignatureWithKey: Validating signature with key with tag %d", (mDNSu16)keytag((mDNSu8 *)key, keyv->rdlength)); if (DNSNameToLowerCase((domainname *)&rrsig->signerName, &signerName) != mStatus_NoError) { LogMsg("ValidateSignatureWithKey: ERROR!! cannot convert signer name to lower case"); return mDNSfalse; } if (DNSNameToLowerCase((domainname *)&rrset->name, &name) != mStatus_NoError) { LogMsg("ValidateSignatureWithKey: ERROR!! cannot convert rrset name to lower case"); return mDNSfalse; } sigNameLen = DomainNameLength(&signerName); labels = CountLabels(&name); // RFC 4034: RRSIG validation // // signature = sign(RRSIG_RDATA | RR(1) | RR(2)... ) // // where RRSIG_RDATA excludes the signature and signer name in canonical form if (dv->ctx) AlgDestroy(dv->ctx); dv->ctx = AlgCreate(CRYPTO_ALG, rrsig->alg); if (!dv->ctx) { LogDNSSEC("ValidateSignatureWithKey: ERROR!! No algorithm support for %d", rrsig->alg); return mDNSfalse; } AlgAdd(dv->ctx, (const mDNSu8 *)rrsig, RRSIG_FIXED_SIZE); AlgAdd(dv->ctx, signerName.c, sigNameLen); if (labels - rrsig->labels > 0) { domainname *d; LogDNSSEC("ValidateSignatureWithKey: ====splitting labels %d, rrsig->labels %d====", labels,rrsig->labels); d = (domainname *)SkipLeadingLabels(&name, labels - rrsig->labels); fixedPart[0] = 1; fixedPart[1] = '*'; AssignDomainName((domainname *)(fixedPart + 2), d); fixedPartLen = DomainNameLength(d) + 2; // See RFC 4034 section 3.1.3. If you are looking up *.example.com, // the labels count in the RRSIG is 2, but this is not considered as // a wildcard answer if (name.c[0] != 1 || name.c[1] != '*') { LogDNSSEC("ValidateSignatureWithKey: Wildcard exapnded answer for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType)); dv->flags |= WILDCARD_PROVES_ANSWER_EXPANDED; dv->wildcardName = (domainname *)SkipLeadingLabels(&dv->origName, labels - rrsig->labels); if (!dv->wildcardName) return mDNSfalse; } } else { debugdnssec("ValidateSignatureWithKey: assigning domainname"); AssignDomainName((domainname *)fixedPart, &name); fixedPartLen = DomainNameLength(&name); } temp = swap16(rrset->rrtype); mDNSPlatformMemCopy(fixedPart + fixedPartLen, (mDNSu8 *)&temp, sizeof(rrset->rrtype)); fixedPartLen += sizeof(rrset->rrtype); temp = swap16(rrset->rrclass); mDNSPlatformMemCopy(fixedPart + fixedPartLen, (mDNSu8 *)&temp, sizeof(rrset->rrclass)); fixedPartLen += sizeof(rrset->rrclass); mDNSPlatformMemCopy(fixedPart + fixedPartLen, (mDNSu8 *)&rrsig->origTTL, sizeof(rrsig->origTTL)); fixedPartLen += sizeof(rrsig->origTTL); for (tmp = rrset, nrrsets = 0; tmp; tmp = tmp->next) nrrsets++; tmp = rrset; start = ptr = mDNSPlatformMemAllocate(nrrsets * sizeof (rdataComp)); debugdnssec("ValidateSignatureWithKey: start %p, nrrsets %d", start, nrrsets); if (ptr) { // Need to initialize for failure case below mDNSPlatformMemZero(ptr, nrrsets * (sizeof (rdataComp))); while (tmp) { ptr->rdlength = tmp->rdlength; ptr->rrtype = tmp->rrtype; if (ptr->rdlength) { ptr->rdata = mDNSPlatformMemAllocate(ptr->rdlength); if (ptr->rdata) { mDNSPlatformMemCopy(ptr->rdata, tmp->rdata, tmp->rdlength); } else { for (i = 0; i < nrrsets; i++) if (start[i].rdata) mDNSPlatformMemFree(start[i].rdata); mDNSPlatformMemFree(start); LogMsg("ValidateSignatureWithKey:1: ERROR!! RDATA memory alloation failure"); return mDNSfalse; } } ptr++; tmp = tmp->next; } } else { LogMsg("ValidateSignatureWithKey:2: ERROR!! RDATA memory alloation failure"); return mDNSfalse; } PrintFixedSignInfo(rrsig, &signerName, sigNameLen, fixedPart, fixedPartLen); mDNSPlatformQsort(start, nrrsets, sizeof(rdataComp), RDATACompare); for (p = start, i = 0; i < nrrsets; p++, i++) { int rdlen; // The array is sorted and hence checking adjacent entries for duplicate is sufficient if (i > 0) { rdataComp *q = p - 1; if (!RDATACompare((void *)p, (void *)q)) continue; } // Add the fixed part AlgAdd(dv->ctx, (const mDNSu8 *)fixedPart, fixedPartLen); // Add the rdlength rdlen = swap16(p->rdlength); AlgAdd(dv->ctx, (const mDNSu8 *)&rdlen, sizeof(mDNSu16)); ConvertRDATAToCanonical(p->rrtype, p->rdlength, p->rdata); PrintVarSignInfo(rdlen, p->rdata); AlgAdd(dv->ctx, (const mDNSu8 *)p->rdata, p->rdlength); } // free the memory as we don't need it anymore for (i = 0; i < nrrsets; i++) if (start[i].rdata) mDNSPlatformMemFree(start[i].rdata); mDNSPlatformMemFree(start); algRet = AlgVerify(dv->ctx, (mDNSu8 *)&key->data, keyv->rdlength - DNSKEY_FIXED_SIZE, (mDNSu8 *)(sig->rdata + sigNameLen + RRSIG_FIXED_SIZE), sig->rdlength - RRSIG_FIXED_SIZE - sigNameLen); AlgDestroy(dv->ctx); dv->ctx = mDNSNULL; if (algRet != mStatus_NoError) { LogDNSSEC("ValidateSignatureWithKey: AlgVerify failed for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType)); // Reset the state if we set any above. if (dv->flags & WILDCARD_PROVES_ANSWER_EXPANDED) { dv->flags &= ~WILDCARD_PROVES_ANSWER_EXPANDED; dv->wildcardName = mDNSNULL; } return mDNSfalse; } return mDNStrue; } // Walk all the keys and for each key walk all the RRSIGS that signs the original rrset mDNSlocal mStatus ValidateSignature(DNSSECVerifier *dv, RRVerifier **resultKey, RRVerifier **resultRRSIG) { RRVerifier *rrset; RRVerifier *keyv; RRVerifier *rrsigv; RRVerifier *sig; rdataDNSKey *key; rdataRRSig *rrsig; mDNSu16 tag; rrset = dv->rrset; sig = dv->rrsig; for (keyv = dv->key; keyv; keyv = keyv->next) { key = (rdataDNSKey *)keyv->rdata; tag = (mDNSu16)keytag((mDNSu8 *)key, keyv->rdlength); for (rrsigv = sig; rrsigv; rrsigv = rrsigv->next) { rrsig = (rdataRRSig *)rrsigv->rdata; // 7. The RRSIG RR's Signer's Name, Algorithm, and Key Tag fields MUST match the owner // name, algorithm, and key tag for some DNSKEY RR in the zone's apex DNSKEY RRset. if (!SameDomainName((domainname *)&rrsig->signerName, &keyv->name)) { debugdnssec("ValidateSignature: name mismatch"); continue; } if (key->alg != rrsig->alg) { debugdnssec("ValidateSignature: alg mismatch"); continue; } if (tag != swap16(rrsig->keyTag)) { debugdnssec("ValidateSignature: keyTag mismatch rrsig tag %d(0x%x), keyTag %d(0x%x)", swap16(rrsig->keyTag), swap16(rrsig->keyTag), tag, tag); continue; } // 8. The matching DNSKEY RR MUST be present in the zone's apex DNSKEY RRset, and MUST // have the Zone Flag bit (DNSKEY RDATA Flag bit 7) set. if (!((swap16(key->flags)) & DNSKEY_ZONE_SIGN_KEY)) { debugdnssec("ValidateSignature: ZONE flag bit not set"); continue; } debugdnssec("ValidateSignature:Found a key and RRSIG tag: %d", tag); if (ValidateSignatureWithKey(dv, rrset, keyv, rrsigv)) { LogDNSSEC("ValidateSignature: Validated successfully with key tag %d", tag); *resultKey = keyv; *resultRRSIG = rrsigv; return mStatus_NoError; } } } *resultKey = mDNSNULL; *resultRRSIG = mDNSNULL; return mStatus_NoSuchRecord; } mDNSlocal mDNSBool ValidateSignatureWithKeyForAllRRSigs(DNSSECVerifier *dv, RRVerifier *rrset, RRVerifier *keyv, RRVerifier *sig) { rdataRRSig *rrsig; mDNSu16 tag; while (sig) { rrsig = (rdataRRSig *)sig->rdata; tag = (mDNSu16)keytag(keyv->rdata, keyv->rdlength); if (tag == swap16(rrsig->keyTag)) { if (ValidateSignatureWithKey(dv, rrset, keyv, sig)) { LogDNSSEC("ValidateSignatureWithKeyForAllRRSigs: Validated"); return mDNStrue; } } sig = sig->next; } return mDNSfalse; } mDNSlocal mStatus ValidateDS(DNSSECVerifier *dv) { mDNSu8 *digest; int digestLen; domainname name; rdataRRSig *rrsig; rdataDS *ds; rdataDNSKey *key; RRVerifier *keyv; RRVerifier *dsv; mStatus algRet; rrsig = (rdataRRSig *)dv->rrsig->rdata; // Walk all the DS Records to see if we have a matching DNS KEY record that verifies // the hash. If we find one, verify that this key was used to sign the KEY rrsets in // this zone. Loop till we find one. for (dsv = dv->ds; dsv; dsv = dsv->next) { ds = (rdataDS *)dsv->rdata; if ((ds->digestType != SHA1_DIGEST_TYPE) && (ds->digestType != SHA256_DIGEST_TYPE)) { LogDNSSEC("ValidateDS: Unsupported digest %d", ds->digestType); return mStatus_BadParamErr; } else debugdnssec("ValidateDS: digest type %d", ds->digestType); for (keyv = dv->key; keyv; keyv = keyv->next) { key = (rdataDNSKey *)keyv->rdata; mDNSu16 tag = (mDNSu16)keytag((mDNSu8 *)key, keyv->rdlength); if (tag != swap16(ds->keyTag)) { debugdnssec("ValidateDS:Not a valid keytag %d", tag); continue; } if (DNSNameToLowerCase((domainname *)&rrsig->signerName, &name) != mStatus_NoError) { LogMsg("ValidateDS: ERROR!! cannot convert to lower case"); continue; } if (dv->ctx) AlgDestroy(dv->ctx); dv->ctx = AlgCreate(DIGEST_ALG, ds->digestType); if (!dv->ctx) { LogMsg("ValidateDS: ERROR!! Cannot allocate context"); continue; } digest = (mDNSu8 *)&ds->digest; digestLen = dsv->rdlength - DS_FIXED_SIZE; AlgAdd(dv->ctx, name.c, DomainNameLength(&name)); AlgAdd(dv->ctx, (const mDNSu8 *)key, keyv->rdlength); algRet = AlgVerify(dv->ctx, mDNSNULL, 0, digest, digestLen); AlgDestroy(dv->ctx); dv->ctx = mDNSNULL; if (algRet == mStatus_NoError) { LogDNSSEC("ValidateDS: DS Validated Successfully, need to verify the key %d", tag); // We found the DNS KEY that is authenticated by the DS in our parent zone. Check to see if this key // was used to sign the DNS KEY RRSET. If so, then the keys in our DNS KEY RRSET are valid if (ValidateSignatureWithKeyForAllRRSigs(dv, dv->key, keyv, dv->rrsigKey)) { LogDNSSEC("ValidateDS: DS Validated Successfully %d", tag); return mStatus_NoError; } } } } return mStatus_NoSuchRecord; } mDNSlocal mDNSBool UnlinkRRVerifier(DNSSECVerifier *dv, RRVerifier *elem, RRVerifierSet set) { RRVerifier **v; switch (set) { case RRVS_rr: v = &dv->rrset; break; case RRVS_rrsig: v = &dv->rrsig; break; case RRVS_key: v = &dv->key; break; case RRVS_rrsig_key: v = &dv->rrsigKey; break; case RRVS_ds: v = &dv->ds; break; default: LogMsg("UnlinkRRVerifier: ERROR!! default case %d", set); return mDNSfalse; } while (*v && *v != elem) v = &(*v)->next; if (!(*v)) { LogMsg("UnlinkRRVerifier: ERROR!! cannot find element in set %d", set); return mDNSfalse; } *v = elem->next; // Cut this record from the list elem->next = mDNSNULL; return mDNStrue; } // This can link a single AuthChain element or a list of AuthChain elements to // DNSSECVerifier. The latter happens when we have multiple NSEC proofs and // we gather up all the proofs in one place. mDNSexport void AuthChainLink(DNSSECVerifier *dv, AuthChain *ae) { AuthChain *head; LogDNSSEC("AuthChainLink: called"); head = ae; // Get to the last element while (ae->next) ae = ae->next; *(dv->actail) = head; // Append this record to tail of auth chain dv->actail = &(ae->next); // Advance tail pointer } mDNSlocal mDNSBool AuthChainAdd(DNSSECVerifier *dv, RRVerifier *resultKey, RRVerifier *resultRRSig) { AuthChain *ae; rdataDNSKey *key; mDNSu16 tag; if (!dv->rrset || !resultKey || !resultRRSig) { LogMsg("AuthChainAdd: ERROR!! input argument NULL"); return mDNSfalse; } // Unlink resultKey and resultRRSig and store as part of AuthChain if (!UnlinkRRVerifier(dv, resultKey, RRVS_key)) { LogMsg("AuthChainAdd: ERROR!! cannot unlink key"); return mDNSfalse; } if (!UnlinkRRVerifier(dv, resultRRSig, RRVS_rrsig)) { LogMsg("AuthChainAdd: ERROR!! cannot unlink rrsig"); return mDNSfalse; } ae = mDNSPlatformMemAllocate(sizeof(AuthChain)); if (!ae) { LogMsg("AuthChainAdd: AuthChain alloc failure"); return mDNSfalse; } ae->next = mDNSNULL; ae->rrset = dv->rrset; dv->rrset = mDNSNULL; ae->rrsig = resultRRSig; ae->key = resultKey; key = (rdataDNSKey *)resultKey->rdata; tag = (mDNSu16)keytag((mDNSu8 *)key, resultKey->rdlength); LogDNSSEC("AuthChainAdd: inserting AuthChain element with rrset %##s (%s), DNSKEY tag %d", ae->rrset->name.c, DNSTypeName(ae->rrset->rrtype), tag); AuthChainLink(dv, ae); return mDNStrue; } // RFC 4035: Section 5.3.3 // // If the resolver accepts the RRset as authentic, the validator MUST set the TTL of // the RRSIG RR and each RR in the authenticated RRset to a value no greater than the // minimum of: // // o the RRset's TTL as received in the response; // // o the RRSIG RR's TTL as received in the response; // // o the value in the RRSIG RR's Original TTL field; and // // o the difference of the RRSIG RR's Signature Expiration time and the // current time. mDNSlocal void SetTTLRRSet(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status) { DNSQuestion question; CacheRecord *rr; RRVerifier *rrsigv; rdataRRSig *rrsig; CacheGroup *cg; mDNSu32 rrTTL, rrsigTTL, rrsigOrigTTL, rrsigTimeTTL; domainname *qname; mDNSu16 qtype; CacheRecord *rrsigRR; mDNSs32 now; debugdnssec("SetTTLRRSet called"); if (status == DNSSEC_Insecure || status == DNSSEC_Indeterminate) { LogDNSSEC("SetTTLRRSET: not setting ttl for status %s", DNSSECStatusName(status)); return; } mDNS_Lock(m); now = m->timenow; mDNS_Unlock(m); mDNSPlatformMemZero(&question, sizeof(DNSQuestion)); rrTTL = rrsigTTL = rrsigOrigTTL = rrsigTimeTTL = 0; // 1. Locate the rrset name and get its TTL (take the first one as a representative // of the rrset). Ideally, we should set the TTL on the first validation. Instead, // we do it whenever we validate which happens whenever a ValidationRequired question // finishes validation. qname = &dv->origName; qtype = dv->origType; question.ThisQInterval = -1; InitializeQuestion(m, &question, dv->InterfaceID, qname, qtype, mDNSNULL, mDNSNULL); cg = CacheGroupForName(m, question.qnamehash, &question.qname); if (!cg) { LogMsg("SetTTLRRSet cg NULL for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType)); return; } for (rr = cg->members; rr; rr = rr->next) if (SameNameRecordAnswersQuestion(&rr->resrec, &question)) { // originalttl is never touched. The actual TTL is derived based on when it was // received. rrTTL = rr->resrec.rroriginalttl - (now - rr->TimeRcvd)/mDNSPlatformOneSecond; break; } // Should we check to see if it matches the record in dv->ac->rrset ? if (!rr) { LogMsg("SetTTLRRSet: ERROR!! cannot locate main rrset for %##s (%s)", qname->c, DNSTypeName(qtype)); return; } // 2. Get the RRSIG ttl. For NSEC records we need to get the NSEC record's TTL as // the negative cache record that we created may not be right. if (dv->ac && dv->ac->rrsig) { rrsigv = dv->ac->rrsig; rrsig = (rdataRRSig *)rrsigv->rdata; } else { rrsigv = mDNSNULL; rrsig = mDNSNULL; } rrsigRR = mDNSNULL; if (rr->resrec.RecordType == kDNSRecordTypePacketNegative && status == DNSSEC_Secure) { CacheRecord *ncr; rrTTL = 0; for (ncr = rr->nsec; ncr; ncr = ncr->next) { if (ncr->resrec.rrtype == kDNSType_NSEC || ncr->resrec.rrtype == kDNSType_NSEC3) { rrTTL = ncr->resrec.rroriginalttl - (now - ncr->TimeRcvd)/mDNSPlatformOneSecond; debugdnssec("SetTTLRRSet: NSEC TTL %u", rrTTL); } // Note: we can't use dv->origName here as the NSEC record's RRSIG may not match // the original name if (rrsigv && ncr->resrec.rrtype == kDNSType_RRSIG && SameDomainName(ncr->resrec.name, &rrsigv->name)) { RDataBody2 *rdb = (RDataBody2 *)ncr->resrec.rdata->u.data; rdataRRSig *sig = (rdataRRSig *)rdb->data; if (rrsigv->rdlength != ncr->resrec.rdlength) { debugdnssec("SetTTLRRSet length mismatch"); continue; } if (mDNSPlatformMemSame(sig, rrsig, rrsigv->rdlength)) { mDNSu32 remain = (now - ncr->TimeRcvd)/mDNSPlatformOneSecond; rrsigTTL = ncr->resrec.rroriginalttl - remain; rrsigOrigTTL = swap32(rrsig->origTTL) - remain; rrsigTimeTTL = swap32(rrsig->sigExpireTime) - swap32(rrsig->sigInceptTime); } } if (rrTTL && (!rrsigv || rrsigTTL)) break; } } else if (rrsigv) { // Look for the matching RRSIG so that we can get its TTL for (rr = cg ? cg->members : mDNSNULL; rr; rr=rr->next) if (rr->resrec.rrtype == kDNSType_RRSIG && SameDomainName(rr->resrec.name, &rrsigv->name)) { RDataBody2 *rdb = (RDataBody2 *)rr->resrec.rdata->u.data; rdataRRSig *sig = (rdataRRSig *)rdb->data; if (rrsigv->rdlength != rr->resrec.rdlength) { debugdnssec("SetTTLRRSet length mismatch"); continue; } if (mDNSPlatformMemSame(sig, rrsig, rrsigv->rdlength)) { mDNSu32 remain = (now - rr->TimeRcvd)/mDNSPlatformOneSecond; rrsigTTL = rr->resrec.rroriginalttl - remain; rrsigOrigTTL = swap32(rrsig->origTTL) - remain; rrsigTimeTTL = swap32(rrsig->sigExpireTime) - swap32(rrsig->sigInceptTime); rrsigRR = rr; break; } } } // It is possible that there are no RRSIGs and in that case it is not an error // to find the rrsigTTL. if (!rrTTL || (rrsigv && (!rrsigTTL || !rrsigOrigTTL || !rrsigTimeTTL))) { LogDNSSEC("SetTTLRRSet: ERROR!! Bad TTL rrtl %u, rrsigTTL %u, rrsigOrigTTL %u, rrsigTimeTTL %u for %##s (%s)", rrTTL, rrsigTTL, rrsigOrigTTL, rrsigTimeTTL, qname->c, DNSTypeName(qtype)); return; } LogDNSSEC("SetTTLRRSet: TTL rrtl %u, rrsigTTL %u, rrsigOrigTTL %u, rrsigTimeTTL %u for %##s (%s)", rrTTL, rrsigTTL, rrsigOrigTTL, rrsigTimeTTL, qname->c, DNSTypeName(qtype)); if (status == DNSSEC_Bogus) { rrTTL = RR_BOGUS_TTL; LogDNSSEC("SetTTLRRSet: setting to bogus TTL %d", rrTTL); } if (rrsigv) { if (rrsigTTL < rrTTL) rrTTL = rrsigTTL; if (rrsigOrigTTL < rrTTL) rrTTL = rrsigOrigTTL; if (rrsigTimeTTL < rrTTL) rrTTL = rrsigTimeTTL; } // Set the rrsig's TTL. For NSEC records, rrsigRR is NULL which means it expires when // the negative cache record expires. if (rrsigRR) { rrsigRR->resrec.rroriginalttl = rrTTL; rrsigRR->TimeRcvd = now; rrsigRR->UnansweredQueries = 0; } // Find the RRset and set its TTL for (rr = cg ? cg->members : mDNSNULL; rr; rr=rr->next) { if (SameNameRecordAnswersQuestion(&rr->resrec, &question)) { LogDNSSEC("SetTTLRRSet: Setting the TTL %d for %s, question %##s (%s)", rrTTL, CRDisplayString(m, rr), question.qname.c, DNSTypeName(rr->resrec.rrtype)); rr->resrec.rroriginalttl = rrTTL; rr->TimeRcvd = now; rr->UnansweredQueries = 0; SetNextCacheCheckTimeForRecord(m, rr); } } } mDNSlocal void FinishDNSSECVerification(mDNS *const m, DNSSECVerifier *dv) { RRVerifier *resultKey; RRVerifier *resultRRSig; LogDNSSEC("FinishDNSSECVerification: all rdata sets available for sig verification for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType)); // Stop outstanding query if one exists if (dv->q.ThisQInterval != -1) mDNS_StopQuery(m, &dv->q); if (ValidateSignature(dv, &resultKey, &resultRRSig) == mStatus_NoError) { rdataDNSKey *key; mDNSu16 tag; key = (rdataDNSKey *)resultKey->rdata; tag = (mDNSu16)keytag((mDNSu8 *)key, resultKey->rdlength); LogDNSSEC("FinishDNSSECVerification: RRSIG validated by DNSKEY tag %d, %##s (%s)", tag, dv->rrset->name.c, DNSTypeName(dv->rrset->rrtype)); if (TrustedKey(m, dv) == mStatus_NoError) { // Need to call this after we called TrustedKey, as AuthChainAdd // unlinks the resultKey and resultRRSig if (!AuthChainAdd(dv, resultKey, resultRRSig)) { dv->DVCallback(m, dv, DNSSEC_Bogus); return; } // The callback will be called when NSEC verification is done. if ((dv->flags & WILDCARD_PROVES_ANSWER_EXPANDED)) { WildcardAnswerProof(m, dv); return; } else { dv->DVCallback(m, dv, DNSSEC_Secure); return; } } if (!ValidateDS(dv)) { // Need to call this after we called ValidateDS, as AuthChainAdd // unlinks the resultKey and resultRRSig if (!AuthChainAdd(dv, resultKey, resultRRSig)) { dv->DVCallback(m, dv, DNSSEC_Bogus); return; } FreeDNSSECVerifierRRSets(dv); dv->recursed++; if (dv->recursed < MAX_RECURSE_COUNT) { LogDNSSEC("FinishDNSSECVerification: Recursion level %d for %##s (%s)", dv->recursed, dv->origName.c, DNSTypeName(dv->origType)); VerifySignature(m, dv, &dv->q); return; } } else { LogDNSSEC("FinishDNSSECVerification: ValidateDS failed %##s (%s)", dv->rrset->name.c, DNSTypeName(dv->rrset->rrtype)); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } } else { LogDNSSEC("FinishDNSSECVerification: Could not validate the rrset %##s (%s)", dv->origName.c, DNSTypeName(dv->origType)); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } } mDNSexport void StartDNSSECVerification(mDNS *const m, void *context) { mDNSBool done; DNSSECVerifier *dv = (DNSSECVerifier *)context; done = GetAllRRSetsForVerification(m, dv); if (done) { if (dv->next != RRVS_done) LogMsg("StartDNSSECVerification: ERROR!! dv->next is not done"); else LogDNSSEC("StartDNSSECVerification: all rdata sets available for sig verification"); FinishDNSSECVerification(m, dv); return; } else debugdnssec("StartDNSSECVerification: all rdata sets not available for sig verification next %d", dv->next); } mDNSexport char *DNSSECStatusName(DNSSECStatus status) { switch (status) { case DNSSEC_Secure: return "Secure"; case DNSSEC_Insecure: return "Insecure"; case DNSSEC_Indeterminate: return "Indeterminate"; case DNSSEC_Bogus: return "Bogus"; default: return "Invalid"; } } // We could not use GenerateNegativeResponse as it assumes m->CurrentQuestion to be set. Even if // we change that, we needs to fix its callers and so on. It is much simpler to call the callback. mDNSlocal void DeliverDNSSECStatus(mDNS *const m, DNSSECVerifier *dv, ResourceRecord *answer, DNSSECStatus status) { // Can't use m->CurrentQuestion as it may already be in use if (m->ValidationQuestion) LogMsg("DeliverDNSSECStatus: ERROR!! m->ValidationQuestion already set: %##s (%s)", m->ValidationQuestion->qname.c, DNSTypeName(m->ValidationQuestion->qtype)); BumpDNSSECStats(m, kStatsActionSet, kStatsTypeStatus, status); BumpDNSSECStats(m, kStatsActionSet, kStatsTypeExtraPackets, dv->NumPackets); mDNS_Lock(m); BumpDNSSECStats(m, kStatsActionSet, kStatsTypeLatency, m->timenow - dv->StartTime); mDNS_Unlock(m); m->ValidationQuestion = m->Questions; while (m->ValidationQuestion && m->ValidationQuestion != m->NewQuestions) { DNSQuestion *q = m->ValidationQuestion; if (q->ValidatingResponse || !q->ValidationRequired || (q->ValidationState != DNSSECValInProgress) || !ResourceRecordAnswersQuestion(answer, q)) { m->ValidationQuestion = q->next; continue; } q->ValidationState = DNSSECValDone; q->ValidationStatus = status; MakeNegativeCacheRecord(m, &largerec.r, &q->qname, q->qnamehash, q->qtype, q->qclass, 60, mDNSInterface_Any, mDNSNULL); if (q->qtype == answer->rrtype || status != DNSSEC_Secure) { LogDNSSEC("DeliverDNSSECStatus: Generating dnssec status %s for %##s (%s)", DNSSECStatusName(status), q->qname.c, DNSTypeName(q->qtype)); if (q->QuestionCallback) { if (q->DNSSECAuthInfo) FreeDNSSECAuthChainInfo((AuthChain *)q->DNSSECAuthInfo); q->DNSSECAuthInfo = AuthChainCopy(dv->ac); q->DAIFreeCallback = FreeAuthChain; q->QuestionCallback(m, q, &largerec.r.resrec, QC_dnssec); } } else if (FollowCNAME(q, answer, QC_add)) { LogDNSSEC("DeliverDNSSECStatus: Following CNAME dnssec status %s for %##s (%s)", DNSSECStatusName(status), q->qname.c, DNSTypeName(q->qtype)); mDNS_Lock(m); AnswerQuestionByFollowingCNAME(m, q, answer); mDNS_Unlock(m); } if (m->ValidationQuestion == q) // If m->ValidationQuestion was not auto-advanced, do it ourselves now m->ValidationQuestion = q->next; } m->ValidationQuestion = mDNSNULL; } // There is no work to be done if we could not validate DNSSEC (as the actual response for // the query has already been delivered) except in the case of CNAMEs where we did not follow // CNAMEs until we finished the DNSSEC processing. mDNSlocal void DNSSECNoResponse(mDNS *const m, DNSSECVerifier *dv) { CacheGroup *cg; CacheRecord *cr; mDNSu32 namehash; ResourceRecord *answer = mDNSNULL; LogDNSSEC("DNSSECNoResponse: called"); if (dv->ValidationRequired != DNSSEC_VALIDATION_SECURE_OPTIONAL) { LogMsg("DNSSECNoResponse: ERROR!! ValidationRequired incorrect %d", dv->ValidationRequired); return; } BumpDNSSECStats(m, kStatsActionSet, kStatsTypeStatus, DNSSEC_NoResponse); namehash = DomainNameHashValue(&dv->origName); cg = CacheGroupForName(m, namehash, &dv->origName); if (!cg) { LogDNSSEC("DNSSECNoResponse: cg NULL for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType)); goto done; } InitializeQuestion(m, &dv->q, dv->InterfaceID, &dv->origName, dv->origType, mDNSNULL, mDNSNULL); // We don't have to reset ValidatingResponse (unlike in DeliverDNSSECStatus) as there are no // RRSIGs that can match the original question for (cr = cg->members; cr; cr = cr->next) { if (SameNameRecordAnswersQuestion(&cr->resrec, &dv->q)) { answer = &cr->resrec; break; } } // It is not an error for things to disappear underneath if (!answer) { LogDNSSEC("DNSSECNoResponse: answer NULL for %##s, %s", dv->origName.c, DNSTypeName(dv->origType)); goto done; } if (answer->rrtype == kDNSType_RRSIG) { LogDNSSEC("DNSSECNoResponse: RRSIG present for %##s, %s", dv->origName.c, DNSTypeName(dv->origType)); goto done; } // Can't use m->CurrentQuestion as it may already be in use if (m->ValidationQuestion) LogMsg("DNSSECNoResponse: ERROR!! m->ValidationQuestion already set: %##s (%s)", m->ValidationQuestion->qname.c, DNSTypeName(m->ValidationQuestion->qtype)); m->ValidationQuestion = m->Questions; while (m->ValidationQuestion && m->ValidationQuestion != m->NewQuestions) { DNSQuestion *q = m->ValidationQuestion; if (q->ValidatingResponse || !q->ValidationRequired || (q->ValidationState != DNSSECValInProgress) || !ResourceRecordAnswersQuestion(answer, q)) { m->ValidationQuestion = q->next; continue; } // If we could not validate e.g., zone was not signed or bad delegation etc., // disable validation. Ideally, for long outstanding questions, we should try again when // we switch networks. But for now, keep it simple. // // Note: If we followed a CNAME with no dnssec protection, it is even more important that // we disable validation as we don't want to deliver a "secure" dnssec response later e.g., // it is possible that the CNAME is not secure but the address records are secure. In this // case, we don't want to deliver the secure response later as we followed a CNAME that was // not protected with DNSSEC. q->ValidationRequired = 0; q->ValidationState = DNSSECValNotRequired; if (FollowCNAME(q, answer, QC_add)) { LogDNSSEC("DNSSECNoResponse: Following CNAME for %##s (%s)", q->qname.c, DNSTypeName(q->qtype)); mDNS_Lock(m); AnswerQuestionByFollowingCNAME(m, q, answer); mDNS_Unlock(m); } if (m->ValidationQuestion == q) // If m->ValidationQuestion was not auto-advanced, do it ourselves now m->ValidationQuestion = q->next; } m->ValidationQuestion = mDNSNULL; done: FreeDNSSECVerifier(m, dv); } mDNSlocal void DNSSECPositiveValidationCB(mDNS *const m, DNSSECVerifier *dv, CacheGroup *cg, ResourceRecord *answer, DNSSECStatus status) { RRVerifier *rrset; RRVerifier *rv; CacheRecord *cr; mDNSu16 rrtype, rrclass; CacheRecord *const lrr = &largerec.r; LogDNSSEC("DNSSECPositiveValidationCB: called %s for %##s (%s)", DNSSECStatusName(status), dv->origName.c, DNSTypeName(dv->origType)); // // 1. Check to see if the rrset that was validated is the same as in cache. If they are not same, // this validation result is not valid. When the rrset changed while the validation was in // progress, the act of delivering the changed rrset again should have kicked off another // verification. // // 2. Walk the question list to find the matching question. The original question that started // the DNSSEC verification may or may not be there. As long as there is a matching question // and waiting for the response, deliver the response. // // 3. If we are answering with CNAME, it is time to follow the CNAME if the response is secure if (!dv->ac || status == DNSSEC_Insecure) { // For Insecure status, the auth chain contains information about the trust // chain starting from the known trust anchor. The rrsets are not related to // the origName like in Bogus or Secure. if (!answer) LogMsg("DNSSECPositiveValidationCB: ERROR: answer NULL"); } else { if (!dv->ac->rrset) { LogMsg("DNSSECPositiveValidationCB: ERROR!! Validated RRSET NULL"); goto done; } rrset = dv->ac->rrset; rrtype = rrset->rrtype; rrclass = rrset->rrclass; lrr->resrec.name = &largerec.namestorage; for (rv = dv->ac->rrset; rv; rv = rv->next) rv->found = 0; // Check to see if we can find all the elements in the rrset for (cr = cg ? cg->members : mDNSNULL; cr; cr = cr->next) { if (cr->resrec.rrtype == rrtype && cr->resrec.rrclass == rrclass) { for (rv = dv->ac->rrset; rv; rv = rv->next) { if (rv->rdlength == cr->resrec.rdlength && rv->rdatahash == cr->resrec.rdatahash) { lrr->resrec.namehash = rv->namehash; lrr->resrec.rrtype = rv->rrtype; lrr->resrec.rrclass = rv->rrclass; lrr->resrec.rdata = (RData*)&lrr->smallrdatastorage; lrr->resrec.rdata->MaxRDLength = MaximumRDSize; // Convert the "rdata" to a suitable form before we can call SameRDataBody which expects // some of the resource records in host order and also domainnames fully expanded. We // converted the resource records into network order for verification purpose and hence // need to convert them back again before comparing them. if (!SetRData(mDNSNULL, rv->rdata, rv->rdata + rv->rdlength, &largerec, rv->rdlength)) { LogMsg("DNSSECPositiveValidationCB: SetRData failed for %##s (%s)", rv->name.c, DNSTypeName(rv->rrtype)); } else if (SameRDataBody(&cr->resrec, &lrr->resrec.rdata->u, SameDomainName)) { answer = &cr->resrec; rv->found = 1; break; } } } if (!rv) { // The validated rrset does not have the element in the cache, re-validate LogDNSSEC("DNSSECPositiveValidationCB: CacheRecord %s, not found in the validated set", CRDisplayString(m, cr)); goto done; } } } // Check to see if we have elements that were not in the cache for (rv = dv->ac->rrset; rv; rv = rv->next) { if (!rv->found) { // We had more elements in the validated set, re-validate LogDNSSEC("DNSSECPositiveValidationCB: Record %##s (%s) not found in the cache", rv->name.c, DNSTypeName(rv->rrtype)); goto done; } } } // It is not an error for things to disappear underneath if (!answer) { LogDNSSEC("DNSSECPositiveValidationCB: answer NULL for %##s, %s", dv->origName.c, DNSTypeName(dv->origType)); goto done; } DeliverDNSSECStatus(m, dv, answer, status); SetTTLRRSet(m, dv, status); done: FreeDNSSECVerifier(m, dv); } mDNSlocal void DNSSECNegativeValidationCB(mDNS *const m, DNSSECVerifier *dv, CacheGroup *cg, ResourceRecord *answer, DNSSECStatus status) { RRVerifier *rv; CacheRecord *cr; mDNSu16 rrtype, rrclass; AuthChain *ac; LogDNSSEC("DNSSECNegativeValidationCB: called %s for %##s (%s)", DNSSECStatusName(status), dv->origName.c, DNSTypeName(dv->origType)); if (dv->parent) { // When NSEC/NSEC3s validation is completed, it calls the parent's DVCallback with the // parent DNSSECVerifier which is the original one that started the verification. It itself // should not have a parent. If the NSEC/NSEC3 validation results in another NSEC/NSEC3 // validation, it should chain up via the dv->parent all the way to the top. LogMsg("DNSSECNegativeValidationCB: ERROR!! dv->parent is set for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType)); goto done; } // 1. Locate the negative cache record and check the cached NSEC/NSEC3 records to see if it matches the // NSEC/NSEC3s that were valiated. If the cached NSEC/NSEC3s changed while the validation was in progress, // we ignore the validation results. // // 2. Walk the question list to find the matching question. The original question that started // the DNSSEC verification may or may not be there. As long as there is a matching question // and waiting for the response, deliver the response. // if (!dv->ac || status == DNSSEC_Insecure) { // For Insecure status, the auth chain contains information about the trust // chain starting from the known trust anchor. The rrsets are not related to // the origName like in Bogus or Secure. if (!answer) LogMsg("DNSSECNegativeValidationCB: ERROR: answer NULL"); } else { if (!dv->ac->rrset) { LogMsg("DNSSECNegativeValidationCB: ERROR!! Validated RRSET NULL"); goto done; } rrtype = dv->origType; rrclass = dv->ac->rrset->rrclass; for (ac = dv->ac; ac; ac = ac->next) { for (rv = ac->rrset; rv; rv = rv->next) { if (rv->rrtype == kDNSType_NSEC || rv->rrtype == kDNSType_NSEC3) { LogDNSSEC("DNSSECNegativeValidationCB: Record %p %##s (%s) marking zero", rv, rv->name.c, DNSTypeName(rv->rrtype)); rv->found = 0; } } } // Check to see if we can find all the elements in the rrset for (cr = cg->members; cr; cr = cr->next) { if (cr->resrec.RecordType == kDNSRecordTypePacketNegative && cr->resrec.rrtype == rrtype && cr->resrec.rrclass == rrclass) { CacheRecord *ncr; for (ncr = cr->nsec; ncr; ncr = ncr->next) { // We have RRSIGs for the NSECs cached there too if (ncr->resrec.rrtype != kDNSType_NSEC && ncr->resrec.rrtype != kDNSType_NSEC3) continue; for (ac = dv->ac; ac; ac = ac->next) { for (rv = ac->rrset; rv; rv = rv->next) { if ((rv->rrtype == kDNSType_NSEC || rv->rrtype == kDNSType_NSEC3) && rv->rdlength == ncr->resrec.rdlength && rv->rdatahash == ncr->resrec.rdatahash) { if (SameDomainName(ncr->resrec.name, &rv->name) && SameRDataBody(&ncr->resrec, (const RDataBody *)rv->rdata, SameDomainName)) { LogDNSSEC("DNSSECNegativeValidationCB: Record %p %##s (%s) marking one", rv, rv->name.c, DNSTypeName(rv->rrtype)); answer = &cr->resrec; rv->found = 1; break; } } } if (rv) break; } } if (!rv) { // The validated rrset does not have the element in the cache, re-validate LogDNSSEC("DNSSECNegativeValidationCB: CacheRecord %s, not found in the validated set", CRDisplayString(m, cr)); goto done; } } } // Check to see if we have elements that were not in the cache for (ac = dv->ac; ac; ac = ac->next) { for (rv = ac->rrset; rv; rv = rv->next) { if (rv->rrtype == kDNSType_NSEC || rv->rrtype == kDNSType_NSEC3) { if (!rv->found) { // We had more elements in the validated set, re-validate LogDNSSEC("DNSSECNegativeValidationCB: Record %p %##s (%s) not found in the cache", rv, rv->name.c, DNSTypeName(rv->rrtype)); goto done; } rv->found = 0; } } } } // It is not an error for things to disappear underneath if (!answer) { LogDNSSEC("DNSSECNegativeValidationCB: answer NULL for %##s, %s", dv->origName.c, DNSTypeName(dv->origType)); goto done; } DeliverDNSSECStatus(m, dv, answer, status); SetTTLRRSet(m, dv, status); done: FreeDNSSECVerifier(m, dv); } mDNSlocal void DNSSECValidationCB(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status) { mDNSu32 namehash; CacheGroup *cg; CacheRecord *cr; LogDNSSEC("DNSSECValidationCB: called %s for %##s (%s)", DNSSECStatusName(status), dv->origName.c, DNSTypeName(dv->origType)); // Currently, if we receive anything other than secure, we abort DNSSEC validation for // the optional case. if (dv->ValidationRequired == DNSSEC_VALIDATION_SECURE_OPTIONAL && status != DNSSEC_Secure) { DNSSECNoResponse(m, dv); return; } if (dv->ValidationRequired == DNSSEC_VALIDATION_SECURE && !dv->InsecureProofDone && status == DNSSEC_Bogus) { dv->InsecureProofDone = 1; ProveInsecure(m, dv, mDNSNULL, mDNSNULL); return; } namehash = DomainNameHashValue(&dv->origName); cg = CacheGroupForName(m, namehash, &dv->origName); if (!cg) { LogDNSSEC("DNSSECValidationCB: cg NULL for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType)); FreeDNSSECVerifier(m, dv); return; } InitializeQuestion(m, &dv->q, dv->InterfaceID, &dv->origName, dv->origType, mDNSNULL, mDNSNULL); // Need to be reset ValidatingResponse as we are looking for the cache record that would answer // the original question dv->q.ValidatingResponse = mDNSfalse; for (cr = cg->members; cr; cr = cr->next) { if (SameNameRecordAnswersQuestion(&cr->resrec, &dv->q)) { if (cr->resrec.RecordType == kDNSRecordTypePacketNegative) DNSSECNegativeValidationCB(m, dv, cg, &cr->resrec, status); else DNSSECPositiveValidationCB(m, dv, cg, &cr->resrec, status); return; } } } mDNSexport void VerifySignature(mDNS *const m, DNSSECVerifier *dv, DNSQuestion *q) { CacheGroup *const cg = CacheGroupForName(m, q->qnamehash, &q->qname); CacheRecord *rr; mDNSBool first = mDNSfalse; static mDNSBool TrustAnchorsUpdated = mDNSfalse; LogDNSSEC("VerifySignature called for %##s (%s)", q->qname.c, DNSTypeName(q->qtype)); if (!TrustAnchorsUpdated) { TrustAnchorsUpdated = mDNStrue; UpdateTrustAnchors(m); } if (!dv) { first = mDNStrue; if (!q->qDNSServer || q->qDNSServer->cellIntf) { LogDNSSEC("VerifySignature: Disabled"); return; } // We assume that the verifier's question has been initialized here so that ValidateWithNSECS below // knows what it has prove the non-existence of. dv = AllocateDNSSECVerifier(m, &q->qname, q->qtype, q->InterfaceID, q->ValidationRequired, DNSSECValidationCB, VerifySigCallback); if (!dv) { LogMsg("VerifySignature: ERROR!! memory alloc failed"); return; } } // If we find a CNAME response to the question, remember what qtype // caused the CNAME response. origType is not sufficient as we // recursively validate the response and origType is initialized above // the first time this function is called. dv->currQtype = q->qtype; // Walk the cache and get all the rrsets for verification. for (rr = cg ? cg->members : mDNSNULL; rr; rr=rr->next) if (SameNameRecordAnswersQuestion(&rr->resrec, q)) { // We also get called for RRSIGs which matches qtype. We don't need that here as we are // building rrset for matching q->qname. Checking for RRSIG type is important as otherwise // we would miss the CNAME answering any qtype. if (rr->resrec.rrtype == kDNSType_RRSIG && rr->resrec.rrtype != q->qtype) { LogDNSSEC("VerifySignature: Question %##s (%s) answered with RRSIG record %s, not using it", q->qname.c, DNSTypeName(q->qtype), CRDisplayString(m, rr)); continue; } // See DNSSECRecordAnswersQuestion: This should never happen. NSEC records are // answered directly only when the qtype is NSEC. Otherwise, NSEC records are // used only for denial of existence and hence should go through negative cache // entry. if (rr->resrec.rrtype == kDNSType_NSEC && q->qtype != kDNSType_NSEC) { LogMsg("VerifySignature: ERROR!! Question %##s (%s) answered using NSEC record %s", q->qname.c, DNSTypeName(q->qtype), CRDisplayString(m, rr)); continue; } // We might get a NSEC response when we first send the query out from the "core" for ValidationRequired // questions. Later as part of validating the response, we might get a NSEC response. if (rr->resrec.RecordType == kDNSRecordTypePacketNegative && DNSSECQuestion(q)) { // If we can't find the NSEC, we can't validate. This can happens if we are // behind a non-DNSSEC aware CPE/server. if (!rr->nsec) { LogDNSSEC("VerifySignature: No nsecs found for %s", CRDisplayString(m, rr)); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } ValidateWithNSECS(m, dv, rr); return; } if (AddRRSetToVerifier(dv, &rr->resrec, mDNSNULL, RRVS_rr) != mStatus_NoError) { dv->DVCallback(m, dv, DNSSEC_Bogus); return; } } if (!dv->rrset) { LogMsg("VerifySignature: rrset mDNSNULL for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType)); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } dv->next = RRVS_rrsig; // Delay this so that the mDNS "core" can deliver all the results before // we can deliver the dnssec result if (first) { mDNSPlatformDispatchAsync(m, dv, StartDNSSECVerification); } else { StartDNSSECVerification(m, dv); } } mDNSlocal mDNSBool TrustedKeyPresent(mDNS *const m, DNSSECVerifier *dv) { rdataDS *ds; rdataDNSKey *key; TrustAnchor *ta; RRVerifier *keyv; // Walk all our trusted DS Records to see if we have a matching DNS KEY record that verifies // the hash. If we find one, verify that this key was used to sign the KEY rrsets in // this zone. Loop till we find one. for (ta = m->TrustAnchors; ta; ta = ta->next) { ds = (rdataDS *)&ta->rds; if ((ds->digestType != SHA1_DIGEST_TYPE) && (ds->digestType != SHA256_DIGEST_TYPE)) { LogMsg("TrustedKeyPresent: Unsupported digest %d", ds->digestType); continue; } else { debugdnssec("TrustedKeyPresent: digest type %d", ds->digestType); } for (keyv = dv->key; keyv; keyv = keyv->next) { key = (rdataDNSKey *)keyv->rdata; mDNSu16 tag = (mDNSu16)keytag((mDNSu8 *)key, keyv->rdlength); if (tag != ds->keyTag) { debugdnssec("TrustedKeyPresent:Not a valid keytag %d", tag); continue; } if (!SameDomainName(&keyv->name, &ta->zone)) { debugdnssec("TrustedKeyPresent: domainame mismatch key %##s, ta %##s", keyv->name.c, ta->zone.c); continue; } return mDNStrue; } } return mDNSfalse; } mDNSlocal mStatus TrustedKey(mDNS *const m, DNSSECVerifier *dv) { mDNSu8 *digest; int digestLen; domainname name; rdataRRSig *rrsig; rdataDS *ds; rdataDNSKey *key; TrustAnchor *ta; RRVerifier *keyv; mStatus algRet; mDNSu32 currTime = mDNSPlatformUTC(); rrsig = (rdataRRSig *)dv->rrsig->rdata; // Walk all our trusted DS Records to see if we have a matching DNS KEY record that verifies // the hash. If we find one, verify that this key was used to sign the KEY rrsets in // this zone. Loop till we find one. for (ta = m->TrustAnchors; ta; ta = ta->next) { ds = (rdataDS *)&ta->rds; if ((ds->digestType != SHA1_DIGEST_TYPE) && (ds->digestType != SHA256_DIGEST_TYPE)) { LogMsg("TrustedKey: Unsupported digest %d", ds->digestType); continue; } else { debugdnssec("TrustedKey: Zone %##s, digest type %d, tag %d", ta->zone.c, ds->digestType, ds->keyTag); } for (keyv = dv->key; keyv; keyv = keyv->next) { key = (rdataDNSKey *)keyv->rdata; mDNSu16 tag = (mDNSu16)keytag((mDNSu8 *)key, keyv->rdlength); if (tag != ds->keyTag) { debugdnssec("TrustedKey:Not a valid keytag %d", tag); continue; } if (!SameDomainName(&keyv->name, &ta->zone)) { debugdnssec("TrustedKey: domainame mismatch key %##s, ta %##s", keyv->name.c, ta->zone.c); continue; } if (DNS_SERIAL_LT(ta->validUntil, currTime)) { LogDNSSEC("TrustedKey: Expired: currentTime %d, ExpireTime %d", (int)currTime, ta->validUntil); continue; } if (DNS_SERIAL_LT(currTime, ta->validFrom)) { LogDNSSEC("TrustedKey: Future: currentTime %d, InceptTime %d", (int)currTime, ta->validFrom); continue; } if (DNSNameToLowerCase((domainname *)&rrsig->signerName, &name) != mStatus_NoError) { LogMsg("TrustedKey: ERROR!! cannot convert to lower case"); continue; } if (dv->ctx) AlgDestroy(dv->ctx); dv->ctx = AlgCreate(DIGEST_ALG, ds->digestType); if (!dv->ctx) { LogMsg("TrustedKey: ERROR!! No digest support"); continue; } digest = ds->digest; digestLen = ta->digestLen; AlgAdd(dv->ctx, name.c, DomainNameLength(&name)); AlgAdd(dv->ctx, (const mDNSu8 *)key, keyv->rdlength); algRet = AlgVerify(dv->ctx, mDNSNULL, 0, digest, digestLen); AlgDestroy(dv->ctx); dv->ctx = mDNSNULL; if (algRet == mStatus_NoError) { LogDNSSEC("TrustedKey: DS Validated Successfully, need to verify the key %d", tag); // We found the DNS KEY that is authenticated by the DS in our parent zone. Check to see if this key // was used to sign the DNS KEY RRSET. If so, then the keys in our DNS KEY RRSET are valid if (ValidateSignatureWithKeyForAllRRSigs(dv, dv->key, keyv, dv->rrsigKey)) { LogDNSSEC("TrustedKey: DS Validated Successfully %d", tag); return mStatus_NoError; } } } } return mStatus_NoSuchRecord; } mDNSlocal CacheRecord* NegativeCacheRecordForRR(mDNS *const m, const ResourceRecord *const rr) { mDNSu32 namehash; CacheGroup *cg; CacheRecord *cr; namehash = DomainNameHashValue(rr->name); cg = CacheGroupForName(m, namehash, rr->name); if (!cg) { LogMsg("NegativeCacheRecordForRR: cg null %##s", rr->name->c); return mDNSNULL; } for (cr=cg->members; cr; cr=cr->next) { if (cr->resrec.RecordType == kDNSRecordTypePacketNegative && (&cr->resrec == rr)) return cr; } return mDNSNULL; } mDNSlocal void VerifySigCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) { DNSSECVerifier *dv = (DNSSECVerifier *)question->QuestionContext; mDNSu16 rrtype; CacheRecord *negcr; debugdnssec("VerifySigCallback: AddRecord %d, dv %p", AddRecord, dv); if (!AddRecord) return; // After the first ADD event, we should ideally stop the question. If we don't stop // the question, we might get more callbacks and that can cause problems. For example, // in the first callback, we could start a insecure proof and while that is in progress, // if we get more callbacks, we will try to start another insecure proof. As we already // started an insecure proof, we won't start another but terminate the verification // process where we free the current DNSSECVerifier while the first insecure proof is // still referencing it. // // But there are cases below which might return if we have not received the right answer // yet e.g., no RRSIGs. In that case if the question is stopped, we will never get any // callbacks again and also we leak "dv". Hence it is important that we either process // the result or wait for more results. Note that the question eventually times out // and cleans up the "dv" i.e., we don't wait forever. if (!answer) { LogDNSSEC("VerifySigCallback: Question %##s (%s) no dnssec response", question->qname.c, DNSTypeName(question->qtype)); mDNS_StopQuery(m, question); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } LogDNSSEC("VerifySigCallback(%p): Called with record %s for question %##s (%s)", dv, RRDisplayString(m, answer), question->qname.c, DNSTypeName(question->qtype)); mDNS_Lock(m); if ((m->timenow - question->StopTime) >= 0) { mDNS_Unlock(m); LogDNSSEC("VerifySigCallback: Question %##s (%s) timed out", question->qname.c, DNSTypeName(question->qtype)); mDNS_StopQuery(m, question); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } mDNS_Unlock(m); if (answer->RecordType == kDNSRecordTypePacketNegative) { CacheRecord *cr; LogDNSSEC("VerifySigCallback: Received a negative answer with record %s, AddRecord %d", RRDisplayString(m, answer), AddRecord); mDNS_StopQuery(m, question); cr = NegativeCacheRecordForRR(m, answer); if (cr && cr->nsec) { ValidateWithNSECS(m, dv, cr); } else { LogDNSSEC("VerifySigCallback: Missing record (%s) Negative Cache Record %p", RRDisplayString(m, answer), cr); dv->DVCallback(m, dv, DNSSEC_Bogus); } return; } if (!dv->rrset) { LogMsg("VerifySigCallback: ERROR!! rrset NULL"); mDNS_StopQuery(m, question); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } rrtype = answer->rrtype; // Check whether we got any answers for the question. If there are no answers, we // can't do the verification. // // We need to look at the whole rrset for verifying the signatures. This callback gets // called back for each record in the rrset sequentially and we won't know when to start the // verification. Hence, we look for all the records in the rrset ourselves using the // CheckXXX function below. The caller has to ensure that all the records in the rrset are // added to the cache before calling this callback which happens naturally because all // unicast records are marked for DelayDelivery and hence added to the cache before the // callback is done. // // We also need the RRSIGs for the rrset to do the validation. It is possible that the // cache contains RRSIG records but it may not be a valid record when we filter them // in CheckXXX function. For example, some application can query for RRSIG records which // might come back with a partial set of RRSIG records from the recursive server and // they may not be the right ones for the current validation. In this case, we still // need to send the query out to get the right RRSIGs but the "core" should not answer // this query with the same records that we checked and found them to be unusable. // // We handle this in two ways: // // 1) AnswerNewQuestion always sends the "ValidatingResponse" query out bypassing the cache. // // 2) DNSSECRecordAnswersQuestion does not answer a question with RRSIGs matching the // same name as the query until the typeCovered also matches the query's type. // // NOTE: We use "next - 1" as next always points to what we are going to fetch next and not the one // we are fetching currently switch(dv->next - 1) { case RRVS_rr: // Verification always starts at RRVS_rrsig (which means dv->next points at RRVS_key) as verification does // not begin until we have the main rrset. LogDNSSEC("VerifySigCallback: ERROR!! rrset %##s dv->next is RRVS_rr", dv->rrset->name.c); return; case RRVS_rrsig: // We can get called back with rrtype matching qtype as new records are added to the cache // triggered by other questions. This could potentially mean that the rrset that is being // validated by this "dv" whose rrsets were initialized at the beginning of the verification // may not be the right one. If this case happens, we will detect this at the end of validation // and throw away the validation results. This should not be a common case. if (rrtype != kDNSType_RRSIG) { LogDNSSEC("VerifySigCallback: RRVS_rrsig called with %s", RRDisplayString(m, answer)); return; } mDNS_StopQuery(m, question); if (CheckRRSIGForRRSet(m, dv, &negcr) != mStatus_NoError) { LogDNSSEC("VerifySigCallback: Unable to find RRSIG for %##s (%s), question %##s", dv->rrset->name.c, DNSTypeName(dv->rrset->rrtype), question->qname.c); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } break; case RRVS_key: // We are waiting for the DNSKEY record and hence dv->key should be NULL. If RRSIGs are being // returned first, ignore them for now. if (dv->key) LogDNSSEC("VerifySigCallback: ERROR!! RRVS_key dv->key non-NULL for %##s", question->qname.c); if (rrtype == kDNSType_RRSIG) { LogDNSSEC("VerifySigCallback: RRVS_key rrset type %s, %##s received before DNSKEY", DNSTypeName(rrtype), question->qname.c); return; } if (rrtype != question->qtype) { LogDNSSEC("VerifySigCallback: ERROR!! RRVS_key rrset type %s, %##s not matching qtype %d", DNSTypeName(rrtype), question->qname.c, question->qtype); return; } mDNS_StopQuery(m, question); if (CheckKeyForRRSIG(m, dv, &negcr) != mStatus_NoError) { LogDNSSEC("VerifySigCallback: Unable to find DNSKEY for %##s (%s), question %##s", dv->rrset->name.c, DNSTypeName(dv->rrset->rrtype), question->qname.c); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } break; case RRVS_rrsig_key: // If we are in RRVS_rrsig_key, it means that we already found the relevant DNSKEYs (dv->key should be non-NULL). // If DNSKEY record is being returned i.e., it means it is being added to the cache, then it can't be in our // list. if (!dv->key) LogDNSSEC("VerifySigCallback: ERROR!! RRVS_rrsig_key dv->key NULL for %##s", question->qname.c); if (rrtype == question->qtype) { LogDNSSEC("VerifySigCallback: RRVS_rrsig_key rrset type %s, %##s", DNSTypeName(rrtype), question->qname.c); CheckOneKeyForRRSIG(dv, answer); return; } if (rrtype != kDNSType_RRSIG) { LogDNSSEC("VerifySigCallback: RRVS_rrsig_key rrset type %s, %##s not matching qtype %d", DNSTypeName(rrtype), question->qname.c, question->qtype); return; } mDNS_StopQuery(m, question); if (CheckRRSIGForKey(m, dv, &negcr) != mStatus_NoError) { LogDNSSEC("VerifySigCallback: Unable to find RRSIG for %##s (%s), question %##s", dv->rrset->name.c, DNSTypeName(dv->rrset->rrtype), question->qname.c); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } break; case RRVS_ds: if (rrtype == question->qtype) { LogDNSSEC("VerifySigCallback: RRVS_ds rrset type %s, %##s", DNSTypeName(rrtype), question->qname.c); } else { LogDNSSEC("VerifySigCallback: RRVS_ds rrset type %s, %##s received before DS", DNSTypeName(rrtype), question->qname.c); } mDNS_StopQuery(m, question); // It is not an error if we don't find the DS record as we could have // a trusted key. Or this is not a secure delegation which will be handled // below. if (CheckDSForKey(m, dv, &negcr) != mStatus_NoError) { LogDNSSEC("VerifySigCallback: Unable find DS for %##s (%s), question %##s", dv->rrset->name.c, DNSTypeName(dv->rrset->rrtype), question->qname.c); } // dv->next is already at RRVS_done, so if we "break" from here, we will end up // in FinishDNSSECVerification. We should not do that if we receive a negative // response. For all other cases above, GetAllRRSetsForVerification handles // negative cache record if (negcr) { if (!negcr->nsec) { LogDNSSEC("VerifySigCallback: No nsec records for %##s (DS)", dv->ds->name.c); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } ValidateWithNSECS(m, dv, negcr); return; } break; default: LogDNSSEC("VerifySigCallback: ERROR!! default case rrset %##s question %##s", dv->rrset->name.c, question->qname.c); mDNS_StopQuery(m, question); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } if (dv->next != RRVS_done) { mDNSBool done = GetAllRRSetsForVerification(m, dv); if (done) { if (dv->next != RRVS_done) LogMsg("VerifySigCallback ERROR!! dv->next is not done"); else LogDNSSEC("VerifySigCallback: all rdata sets available for sig verification"); } else { LogDNSSEC("VerifySigCallback: all rdata sets not available for sig verification"); return; } } FinishDNSSECVerification(m, dv); } mDNSlocal TrustAnchor *FindTrustAnchor(mDNS *const m, const domainname *const name) { TrustAnchor *ta; TrustAnchor *matchTA = mDNSNULL; TrustAnchor *rootTA = mDNSNULL; int currmatch = 0; int match; mDNSu32 currTime = mDNSPlatformUTC(); for (ta = m->TrustAnchors; ta; ta = ta->next) { if (DNS_SERIAL_LT(ta->validUntil, currTime)) { LogDNSSEC("FindTrustAnchor: Expired: currentTime %d, ExpireTime %d", (int)currTime, ta->validUntil); continue; } if (DNS_SERIAL_LT(currTime, ta->validFrom)) { LogDNSSEC("FindTrustAnchor: Future: currentTime %d, InceptTime %d", (int)currTime, ta->validFrom); continue; } if (SameDomainName((const domainname *)"\000", &ta->zone)) rootTA = ta; match = CountLabelsMatch(&ta->zone, name); if (match > currmatch) { currmatch = match; matchTA = ta; } } if (matchTA) { LogDNSSEC("FindTrustAnhcor: matched %##s", matchTA->zone.c); return matchTA; } else if (rootTA) { LogDNSSEC("FindTrustAnhcor: matched rootTA %##s", rootTA->zone.c); return rootTA; } else { LogDNSSEC("FindTrustAnhcor: No Trust Anchor"); return mDNSNULL; } } mDNSlocal void DeliverInsecureProofResultAsync(mDNS *const m, void *context) { InsecureContext *ic = (InsecureContext *)context; ic->dv->DVCallback(m, ic->dv, ic->status); if (ic->q.ThisQInterval != -1) { LogMsg("DeliverInsecureProofResultAsync: ERROR!! Question %##s (%s) not stopped already", ic->q.qname.c, DNSTypeName(ic->q.qtype)); mDNS_StopQuery(m, &ic->q); } mDNSPlatformMemFree(ic); } mDNSlocal void DeliverInsecureProofResult(mDNS *const m, InsecureContext *ic, DNSSECStatus status) { // If the status is Bogus, restore the original auth chain before the insecure // proof. if (status == DNSSEC_Bogus) { LogDNSSEC("DeliverInsecureProofResult: Restoring the auth chain"); if (ic->dv->ac) { FreeDNSSECAuthChainInfo(ic->dv->ac); } ResetAuthChain(ic->dv); ic->dv->ac = ic->dv->saveac; if (ic->dv->ac) { AuthChain *tmp = ic->dv->ac; AuthChain **tail = &tmp->next; while (tmp->next) { tail = &tmp->next; tmp = tmp->next; } ic->dv->actail = tail; } ic->dv->saveac = mDNSNULL; } else if (ic->dv->saveac) { FreeDNSSECAuthChainInfo(ic->dv->saveac); ic->dv->saveac = mDNSNULL; } ic->status = status; // Stop the question before we schedule the block so that we don't receive additional // callbacks again. Once the block runs, it will free the "ic" and you can't // have another block queued up. This can happen if we receive a callback after we // queue the block below. if (ic->q.ThisQInterval != -1) mDNS_StopQuery(m, &ic->q); mDNSPlatformDispatchAsync(m, ic, DeliverInsecureProofResultAsync); } mDNSlocal mDNSBool AlgorithmSupported(rdataDS *ds) { switch(ds->digestType) { case SHA1_DIGEST_TYPE: case SHA256_DIGEST_TYPE: break; default: LogDNSSEC("AlgorithmSupported: Unsupported digest %d", ds->digestType); return mDNSfalse; } switch(ds->alg) { case CRYPTO_RSA_NSEC3_SHA1: case CRYPTO_RSA_SHA1: case CRYPTO_RSA_SHA256: case CRYPTO_RSA_SHA512: return mDNStrue; default: LogDNSSEC("AlgorithmSupported: Unsupported algorithm %d", ds->alg); return mDNSfalse; } } // Note: This function is called when DNSSEC results are delivered (from DeliverDNSSECStatus) and we can't deliver DNSSEC result // again within this function as "m->ValidationQuestion" is already in use. Hence we should dispatch off the delivery of insecure // results asynchronously. // // Insecure proof callback can deliver either insecure or bogus, but never secure result. mDNSlocal void ProveInsecureCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) { InsecureContext *ic = (InsecureContext *)question->QuestionContext; DNSSECVerifier *pdv = ic->dv; AuthChain *ac; (void) answer; if (!AddRecord) return; mDNS_Lock(m); if ((m->timenow - question->StopTime) >= 0) { mDNS_Unlock(m); LogDNSSEC("ProveInsecureCallback: Question %##s (%s) timed out", question->qname.c, DNSTypeName(question->qtype)); DeliverInsecureProofResult(m, ic, DNSSEC_Bogus); return; } mDNS_Unlock(m); // We only need to handle the actual DNSSEC results and the ones that are secure. Anything else results in // bogus. if (AddRecord != QC_dnssec) { LogDNSSEC("ProveInsecureCallback: Question %##s (%s), AddRecord %d, answer %s", question->qname.c, DNSTypeName(question->qtype), AddRecord, RRDisplayString(m, answer)); return; } LogDNSSEC("ProveInsecureCallback: ic %p Question %##s (%s), DNSSEC status %s", ic, question->qname.c, DNSTypeName(question->qtype), DNSSECStatusName(question->ValidationStatus)); // Insecure is delivered for NSEC3 OptOut if (question->ValidationStatus != DNSSEC_Secure && question->ValidationStatus != DNSSEC_Insecure) { LogDNSSEC("ProveInsecureCallback: Question %##s (%s) returned DNSSEC status %s", question->qname.c, DNSTypeName(question->qtype), DNSSECStatusName(question->ValidationStatus)); goto done; } ac = (AuthChain *)question->DNSSECAuthInfo; if (!ac) { LogDNSSEC("ProveInsecureCallback: ac NULL for question %##s, %s", question->qname.c, DNSTypeName(question->qtype)); goto done; } if (!ac->rrset) { LogDNSSEC("ProveInsecureCallback: ac->rrset NULL for question %##s, %s", question->qname.c, DNSTypeName(question->qtype)); goto done; } if (ac->rrset->rrtype != kDNSType_DS && ac->rrset->rrtype != kDNSType_NSEC && ac->rrset->rrtype != kDNSType_NSEC3) { LogDNSSEC("ProveInsecureCallback: ac->rrset->rrtype %##s (%s) not handled", ac->rrset->name.c, DNSTypeName(ac->rrset->rrtype)); goto done; } AuthChainLink(pdv, ac); question->DNSSECAuthInfo = mDNSNULL; if (ac->rrset->rrtype == kDNSType_DS) { rdataDS *ds = (rdataDS *)ac->rrset->rdata; // If the delegation is secure, but the underlying zone is signed with an unsupported // algorithm, then we can't verify it. Deliver insecure in that case. if (!AlgorithmSupported(ds)) { LogDNSSEC("ProveInsecureCallback: Unsupported algorithm %d or digest %d", ds->alg, ds->digestType); DeliverInsecureProofResult(m, ic, DNSSEC_Insecure); return; } // If the delegation is secure and the name that we queried for is same as the original // name that started the insecure proof, then something is not right. We started the // insecure proof e.g., the zone is not signed, but we are able to validate a DS for // the same name which implies that the zone is signed (whose algorithm we support) and // we should not have started the insecurity proof in the first place. if (SameDomainName(&question->qname, &pdv->origName)) { LogDNSSEC("ProveInsecureCallback: Insecure proof reached original name %##s, error", question->qname.c); DeliverInsecureProofResult(m, ic, DNSSEC_Bogus); return; } LogDNSSEC("ProveInsecureCallback: Trying one more level down"); ProveInsecure(m, pdv, ic, mDNSNULL); } else if (ac->rrset->rrtype == kDNSType_NSEC || ac->rrset->rrtype == kDNSType_NSEC3) { CacheRecord *cr; if (ac->rrset->rrtype == kDNSType_NSEC) cr = NSECRecordIsDelegation(m, &question->qname, question->qtype); else cr = NSEC3RecordIsDelegation(m, &question->qname, question->qtype); if (cr) { LogDNSSEC("ProveInsecureCallback: Non-existence proved and %s is a delegation for %##s (%s)", CRDisplayString(m, cr), question->qname.c, DNSTypeName(question->qtype)); DeliverInsecureProofResult(m, ic, DNSSEC_Insecure); return; } // Could be a ENT. Go one more level down to see whether it is a secure delegation or not. if (!SameDomainName(&question->qname, &pdv->origName)) { LogDNSSEC("ProveInsecureCallback: Not a delegation %##s (%s), go one more level down", question->qname.c, DNSTypeName(question->qtype)); ProveInsecure(m, pdv, ic, mDNSNULL); } else { // Secure denial of existence and the name matches the original query. This means we should have // received an NSEC (if the type does not exist) or signed records (if the name and type exists) // and verified it successfully instead of starting the insecure proof. This could happen e.g., // Wildcard expanded answer received without NSEC/NSEC3s etc. Also, is it possible that the // zone went from unsigned to signed in a short time ? For now, we return bogus. LogDNSSEC("ProveInsecureCallback: Not a delegation %##s (%s), but reached original name", question->qname.c, DNSTypeName(question->qtype)); DeliverInsecureProofResult(m, ic, DNSSEC_Bogus); } } return; done: DeliverInsecureProofResult(m, ic, DNSSEC_Bogus); } // We return Insecure if we don't have a trust anchor or we have a trust anchor and // can prove that the delegation is not secure (and hence can't establish the trust // chain) or the delegation is possibly secure but we don't have the algorithm support // to prove that. mDNSexport void ProveInsecure(mDNS *const m, DNSSECVerifier *dv, InsecureContext *ic, domainname *trigger) { TrustAnchor *ta; domainname *sname; if (ic == mDNSNULL) { ic = (InsecureContext *)mDNSPlatformMemAllocate(sizeof(InsecureContext)); if (!ic) { LogMsg("mDNSPlatformMemAllocate: ERROR!! memory alloc failed for ic"); return; } // Save the AuthInfo while we are proving insecure. We don't want to mix up // the auth chain for Bogus and Insecure. If we prove it to be insecure, we // will add the chain corresponding to the insecure proof. Otherwise, we will // restore this chain. if (dv->ac) { if (!dv->saveac) { LogDNSSEC("ProveInsecure: saving authinfo"); } else { LogDNSSEC("ProveInsecure: ERROR!! authinfo already set"); FreeDNSSECAuthChainInfo(dv->saveac); } dv->saveac = dv->ac; ResetAuthChain(dv); } ic->dv = dv; ic->q.ThisQInterval = -1; if (trigger) { LogDNSSEC("ProveInsecure: Setting Trigger %##s", trigger->c); ic->triggerLabelCount = CountLabels(trigger); } else { LogDNSSEC("ProveInsecure: No Trigger"); ic->triggerLabelCount = CountLabels(&dv->origName); } ta = FindTrustAnchor(m, &dv->origName); if (!ta) { LogDNSSEC("ProveInsecure: TrustAnchor NULL"); DeliverInsecureProofResult(m, ic, DNSSEC_Insecure); return; } // We want to skip the labels that is already matched by the trust anchor so // that the first query starts just below the trust anchor ic->skip = CountLabels(&dv->origName) - CountLabels(&ta->zone); if (!ic->skip) { LogDNSSEC("ProveInsecure: origName %##s, skip is zero", dv->origName.c); DeliverInsecureProofResult(m, ic, DNSSEC_Bogus); return; } } // Look for the DS record starting just below the trust anchor. // // 1. If we find an NSEC record, then see if it is a delegation. If it is, then // we are done. Otherwise, go down one more level. // // 2. If we find a DS record and no algorithm support, return "insecure". Otherwise, go // down one more level. // sname = (domainname *)SkipLeadingLabels(&dv->origName, (ic->skip ? ic->skip - 1 : 0)); if (!sname) { LogDNSSEC("ProveInsecure: sname NULL, origName %##s, skip %d", dv->origName.c, ic->skip); DeliverInsecureProofResult(m, ic, DNSSEC_Bogus); return; } // Insecurity proof is started during the normal bottom-up validation when we have a break in the trust // chain e.g., we get NSEC/NSEC3s when looking up a DS record. Insecurity proof is top-down looking // for a break in the trust chain. If we have already tried the validation (before the insecurity // proof started) for this "sname", then don't bother with the proof. This happens sometimes, when // we can't prove whether a zone is insecurely delegated or not. For example, if we are looking up // host1.secure-nods.secure.example and when we encounter secure-nods, there is no DS record in the // parent. We start the insecurity proof remembering that "secure-nods.secure.example" is the trigger // point. As part of the proof we reach "secure-nods.secure.example". Even though secure.example // prove that the name "secure-nods.secure.example/DS" does not exist, it can't prove that it is a // delegation. So, we continue one more level down to host1.secure-nods.secure.example and we // realize that we already tried the validation and hence abort here. if (CountLabels(sname) > ic->triggerLabelCount) { LogDNSSEC("ProveInsecure: Beyond the trigger current name %##s, origName %##s", sname->c, dv->origName.c); DeliverInsecureProofResult(m, ic, DNSSEC_Bogus); return; } LogDNSSEC("ProveInsecure: OrigName %##s (%s), Current %##s", dv->origName.c, DNSTypeName(dv->origType), sname->c); ic->skip--; InitializeQuestion(m, &ic->q, dv->InterfaceID, sname, kDNSType_DS, ProveInsecureCallback, ic); ic->q.ValidationRequired = DNSSEC_VALIDATION_INSECURE; ic->q.ValidatingResponse = 0; ic->q.DNSSECAuthInfo = mDNSNULL; mDNS_StartQuery(m, &ic->q); } mDNSexport void BumpDNSSECStats(mDNS *const m, DNSSECStatsAction action, DNSSECStatsType type, mDNSu32 value) { switch (type) { case kStatsTypeMemoryUsage: if (action == kStatsActionIncrement) { m->DNSSECStats.TotalMemUsed += value; } else if (action == kStatsActionDecrement) { m->DNSSECStats.TotalMemUsed -= value; } break; case kStatsTypeLatency: if (action == kStatsActionSet) { if (value <= 4) { m->DNSSECStats.Latency0++; } else if (value <= 9) { m->DNSSECStats.Latency5++; } else if (value <= 19) { m->DNSSECStats.Latency10++; } else if (value <= 49) { m->DNSSECStats.Latency20++; } else if (value <= 99) { m->DNSSECStats.Latency50++; } else { m->DNSSECStats.Latency100++; } } break; case kStatsTypeExtraPackets: if (action == kStatsActionSet) { if (value <= 2) { m->DNSSECStats.ExtraPackets0++; } else if (value <= 6) { m->DNSSECStats.ExtraPackets3++; } else if (value <= 9) { m->DNSSECStats.ExtraPackets7++; } else { m->DNSSECStats.ExtraPackets10++; } } break; case kStatsTypeStatus: if (action == kStatsActionSet) { switch(value) { case DNSSEC_Secure: m->DNSSECStats.SecureStatus++; break; case DNSSEC_Insecure: m->DNSSECStats.InsecureStatus++; break; case DNSSEC_Indeterminate: m->DNSSECStats.IndeterminateStatus++; break; case DNSSEC_Bogus: m->DNSSECStats.BogusStatus++; break; case DNSSEC_NoResponse: m->DNSSECStats.NoResponseStatus++; break; default: LogMsg("BumpDNSSECStats: unknown status %d", value); } } break; case kStatsTypeMsgSize: if (action == kStatsActionSet) { if (value <= 1024) { m->DNSSECStats.MsgSize0++; } else if (value <= 2048) { m->DNSSECStats.MsgSize1++; } else { m->DNSSECStats.MsgSize2++; } } break; case kStatsTypeProbe: if (action == kStatsActionIncrement) { m->DNSSECStats.NumProbesSent += value; } break; default: LogMsg("BumpDNSSECStats: unknown type %d", type); } return; } #else // !DNSSEC_DISABLED mDNSexport void VerifySignature(mDNS *const m, DNSSECVerifier *dv, DNSQuestion *q) { (void)m; (void)dv; (void)q; } mDNSexport void BumpDNSSECStats(mDNS *const m, DNSSECStatsAction action, DNSSECStatsType type, mDNSu32 value) { (void)m; (void)action; (void)type; (void)value; } mDNSexport void InitializeQuestion(mDNS *const m, DNSQuestion *question, mDNSInterfaceID InterfaceID, const domainname *qname, mDNSu16 qtype, mDNSQuestionCallback *callback, void *context) { (void) m; (void) question; (void) InterfaceID; (void) qname; (void) qtype; (void) callback; (void) context; } mDNSexport char *DNSSECStatusName(DNSSECStatus status) { (void) status; return mDNSNULL; } #endif // !DNSSEC_DISABLED