| /* -*- Mode: C; tab-width: 4 -*- |
| * |
| * Copyright (c) 2011 Apple Computer, 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. |
| */ |
| |
| // *************************************************************************** |
| // nsec.c: This file contains support functions to validate NSEC records for |
| // NODATA and NXDOMAIN error. |
| // *************************************************************************** |
| |
| #include "mDNSEmbeddedAPI.h" |
| #include "DNSCommon.h" |
| #include "nsec.h" |
| |
| // Implementation Notes |
| // |
| // NSEC records in DNSSEC are used for authenticated denial of existence i.e., if the response to a query |
| // results in NXDOMAIN or NODATA error, the response also contains NSEC records in the additional section |
| // to prove the non-existence of the original name. In most of the cases, NSEC records don't have any |
| // relationship to the original name queried i.e, if they are cached based on the name like other records, |
| // it can't be located to prove the non-existence of the original name. Hence, we create a negative cache |
| // record like we do for the NXDOMAIN/NODATA error and then cache the NSEC records as part of that. Sometimes, |
| // NSEC records are also used for wildcard expanded answer in which case they are cached with the cache record |
| // that is created for the original name. NSEC records are freed when the parent cache (the record that they |
| // are attached to is expired). |
| // |
| // NSEC records also can be queried like any other record and hence can exist independent of the negative |
| // cache record. It exists as part of negative cache record only when we get a NXDOMAIN/NODATA error with |
| // NSEC records. When a query results in NXDOMAIN/NODATA error and needs to be validated, the NSEC |
| // records (and its RRSIGS) are cached as part of the negative cache record. The NSEC records that |
| // exist separately from the negative cache record should not be used to answer ValidationRequired/ |
| // ValidatingResponse questions as it may not be sufficient to prove the non-existence of the name. |
| // The exception is when the NSEC record is looked up explicitly. See DNSSECRecordAnswersQuestion |
| // for more details. |
| // |
| |
| mDNSlocal CacheRecord *NSECParentForQuestion(mDNS *const m, DNSQuestion *q) |
| { |
| CacheGroup *cg; |
| CacheRecord *cr; |
| mDNSu32 slot; |
| mDNSu32 namehash; |
| |
| slot = HashSlot(&q->qname); |
| namehash = DomainNameHashValue(&q->qname); |
| cg = CacheGroupForName(m, slot, namehash, &q->qname); |
| if (!cg) |
| { |
| LogDNSSEC("NSECParentForQuestion: Cannot find cg for %##s (%s)", q->qname.c, DNSTypeName(q->qtype)); |
| return mDNSNULL; |
| } |
| for (cr = cg->members; cr; cr = cr->next) |
| if (SameNameRecordAnswersQuestion(&cr->resrec, q)) |
| return cr; |
| return mDNSNULL; |
| } |
| |
| // Note: This should just call the parent callback which will free the DNSSECVerifier. |
| mDNSlocal void VerifyNSECCallback(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status) |
| { |
| if (!dv->parent) |
| { |
| LogMsg("VerifyNSECCCallback: ERROR!! no parent DV\n"); |
| FreeDNSSECVerifier(m, dv); |
| return; |
| } |
| if (dv->ac) |
| { |
| // Before we call the callback, we need to update the |
| // parent with our AuthChain information |
| AuthChainLink(dv->parent, dv->ac); |
| dv->ac = mDNSNULL; |
| dv->actail = &dv->ac; |
| } |
| dv->parent->DVCallback(m, dv->parent, status); |
| // The callback we called in the previous line should recursively |
| // free all the DNSSECVerifiers starting from dv->parent and above. |
| // So, set that to NULL and free the "dv" itself here. |
| dv->parent = mDNSNULL; |
| FreeDNSSECVerifier(m, dv); |
| } |
| |
| // If the caller provides a callback, it takes the responsibility of calling the original callback |
| // in "pdv" when it is done. |
| // |
| // INPUT: |
| // |
| // rr: The NSEC record that should be verified |
| // rv: The NSEC record can also be provided like this |
| // pdv: Parent DNSSECVerifier which will be called when the verification is done. |
| // callback: As part of the proof, we need multiple NSEC verifications before we call the "pdv" callback in |
| // which case a intermediate "callback" is provided which can be used to do multiple verifications. |
| // ncr: The cache record where the RRSIGS are cached |
| // |
| // NSEC records and signatures are cached along with the cache record so that we can expire them all together. We can't cache |
| // them based on the name hash like other records as in most cases the returned NSECs has a different name than we asked for |
| // (except for NODATA error where the name exists but type does not exist). |
| // |
| mDNSlocal void VerifyNSEC(mDNS *const m, ResourceRecord *rr, RRVerifier *rv, DNSSECVerifier *pdv, CacheRecord *ncr, |
| DNSSECVerifierCallback callback) |
| { |
| DNSSECVerifier *dv = mDNSNULL; |
| CacheRecord **rp; |
| const domainname *name; |
| mDNSu16 rrtype; |
| |
| if (!rv && !rr) |
| { |
| LogDNSSEC("VerifyNSEC: Both rr and rv are NULL"); |
| goto error; |
| } |
| if (!pdv) |
| { |
| LogDNSSEC("VerifyNSEC: ERROR!! pdv is NULL"); |
| return; |
| } |
| // Remember the name and type for which we are verifying, so that when we are done processing all |
| // the verifications, we can trace it back. |
| // |
| // Note: Currently it is not used because when the verification completes as we just |
| // call the "pdv" callback which has its origName and origType. |
| if (rr) |
| { |
| name = rr->name; |
| rrtype = rr->rrtype; |
| } |
| else |
| { |
| name = &rv->name; |
| rrtype = rv->rrtype; |
| } |
| |
| dv = AllocateDNSSECVerifier(m, name, rrtype, pdv->q.InterfaceID, (callback ? callback : VerifyNSECCallback), mDNSNULL); |
| if (!dv) { LogMsg("VerifyNSEC: mDNSPlatformMemAlloc failed"); return; } |
| |
| dv->parent = pdv; |
| |
| if (AddRRSetToVerifier(dv, rr, rv, RRVS_rr) != mStatus_NoError) |
| { |
| LogMsg("VerifyNSEC: ERROR!! AddRRSetToVerifier failed to add NSEC"); |
| goto error; |
| } |
| |
| // Add the signatures after validating them |
| rp = &(ncr->nsec); |
| while (*rp) |
| { |
| if ((*rp)->resrec.rrtype == kDNSType_RRSIG) |
| { |
| ValidateRRSIG(dv, RRVS_rrsig, &(*rp)->resrec); |
| } |
| rp=&(*rp)->next; |
| } |
| |
| if (!dv->rrset || !dv->rrsig) |
| { |
| LogMsg("VerifyNSEC: ERROR!! AddRRSetToVerifier missing rrset %p, rrsig %p", dv->rrset, dv->rrsig); |
| goto error; |
| } |
| |
| // Next step is to fetch the keys |
| dv->next = RRVS_key; |
| |
| StartDNSSECVerification(m, dv); |
| return; |
| error: |
| pdv->DVCallback(m, pdv, DNSSEC_Indeterminate); |
| if (dv) |
| { |
| dv->parent = mDNSNULL; |
| FreeDNSSECVerifier(m, dv); |
| } |
| return; |
| } |
| |
| mDNSlocal void DeleteCachedNSECS(mDNS *const m, CacheRecord *cr) |
| { |
| CacheRecord *rp, *next; |
| |
| if (cr->nsec) LogDNSSEC("DeleteCachedNSECS: Deleting NSEC Records\n"); |
| for (rp = cr->nsec; rp; rp = next) |
| { |
| next = rp->next; |
| ReleaseCacheRecord(m, rp); |
| } |
| cr->nsec = mDNSNULL; |
| } |
| |
| // Returns success if it adds the nsecs and the rrsigs to the cache record. Otherwise, it returns |
| // failure (mDNSfalse) |
| mDNSexport mDNSBool AddNSECSForCacheRecord(mDNS *const m, CacheRecord *crlist, CacheRecord *negcr, mDNSu8 rcode) |
| { |
| CacheRecord *cr, *next; |
| |
| if (rcode != kDNSFlag1_RC_NoErr && rcode != kDNSFlag1_RC_NXDomain) |
| { |
| LogMsg("AddNSECSForCacheRecord: Addings nsecs for rcode %d", rcode); |
| return mDNSfalse; |
| } |
| |
| // Sanity check the list to see if we have anything else other than |
| // NSECs and its RRSIGs |
| for (cr = crlist; cr; cr = cr->next) |
| { |
| next = cr->next; |
| if (cr->resrec.rrtype != kDNSType_NSEC && cr->resrec.rrtype != kDNSType_RRSIG) |
| { |
| LogMsg("AddNSECSForCacheRecord: ERROR!! Adding Wrong record %s", CRDisplayString(m, cr)); |
| return mDNSfalse; |
| } |
| if (cr->resrec.rrtype == kDNSType_RRSIG) |
| { |
| RDataBody2 *const rdb = (RDataBody2 *)cr->smallrdatastorage.data; |
| rdataRRSig *rrsig = &rdb->rrsig; |
| if (swap16(rrsig->typeCovered) != kDNSType_NSEC) |
| { |
| LogMsg("AddNSECSForCacheRecord:ERROR!! Adding RRSIG with Wrong type %s", CRDisplayString(m, cr)); |
| return mDNSfalse; |
| } |
| } |
| LogDNSSEC("AddNSECSForCacheRecord: Found a valid record %s", CRDisplayString(m, cr)); |
| } |
| DeleteCachedNSECS(m, negcr); |
| LogDNSSEC("AddNSECSForCacheRecord: Adding NSEC Records for %s", CRDisplayString(m, negcr)); |
| negcr->nsec = crlist; |
| negcr->rcode = rcode; |
| return mDNStrue; |
| } |
| |
| // Return the number of labels that matches starting from the right (excluding the |
| // root label) |
| mDNSlocal int CountLabelsMatch(const domainname *const d1, const domainname *const d2) |
| { |
| int count, c1, c2; |
| int match, i, skip1, skip2; |
| |
| c1 = CountLabels(d1); |
| skip1 = c1 - 1; |
| c2 = CountLabels(d2); |
| skip2 = c2 - 1; |
| |
| // Root label always matches. And we don't include it here to |
| // match CountLabels |
| match = 0; |
| |
| // Compare as many labels as possible starting from the rightmost |
| count = c1 < c2 ? c1 : c2; |
| for (i = count; i > 0; i--) |
| { |
| const domainname *da, *db; |
| |
| da = SkipLeadingLabels(d1, skip1); |
| db = SkipLeadingLabels(d2, skip2); |
| if (!SameDomainName(da, db)) return match; |
| skip1--; |
| skip2--; |
| match++; |
| } |
| return match; |
| } |
| |
| // 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 |
| // |
| mDNSlocal 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; |
| } |
| |
| // Empty Non-Terminal (ENT): if the qname is bigger than nsec owner's name and a |
| // subdomain of the nsec's nxt field, then the qname is a empty non-terminal. For |
| // example, if you are looking for (in RFC 4035 example zone) "y.w.example A" |
| // record, if it is a ENT, then it would return |
| // |
| // x.w.example. 3600 NSEC x.y.w.example. MX RRSIG NSEC |
| // |
| // This function is normally called before checking for wildcard matches. If you |
| // find this NSEC, there is no need to look for a wildcard record |
| // that could possibly answer the question. |
| mDNSexport mDNSBool NSECAnswersENT(const ResourceRecord *const rr, domainname *qname) |
| { |
| const domainname *oname = rr->name; |
| const RDataBody2 *const rdb = (RDataBody2 *)rr->rdata->u.data; |
| const domainname *nxt = (const domainname *)&rdb->data; |
| int ret; |
| int subdomain; |
| |
| // Is the owner name smaller than qname? |
| ret = DNSSECCanonicalOrder(oname, qname, mDNSNULL); |
| if (ret < 0) |
| { |
| // Is the next domain field a subdomain of qname ? |
| ret = DNSSECCanonicalOrder(nxt, qname, &subdomain); |
| if (subdomain) |
| { |
| if (ret <= 0) |
| { |
| LogMsg("NSECAnswersENT: ERROR!! DNSSECCanonicalOrder subdomain set " |
| " qname %##s, NSEC %##s", qname->c, rr->name->c); |
| } |
| return mDNStrue; |
| } |
| } |
| return mDNSfalse; |
| } |
| |
| mDNSlocal const domainname *NSECClosestEncloser(ResourceRecord *rr, domainname *qname) |
| { |
| const domainname *oname = rr->name; |
| const RDataBody2 *const rdb = (RDataBody2 *)rr->rdata->u.data; |
| const domainname *nxt = (const domainname *)&rdb->data; |
| int match1, match2; |
| |
| match1 = CountLabelsMatch(oname, qname); |
| match2 = CountLabelsMatch(nxt, qname); |
| // Return the closest i.e the one that matches more labels |
| if (match1 > match2) |
| return SkipLeadingLabels(oname, CountLabels(oname) - match1); |
| else |
| return SkipLeadingLabels(nxt, CountLabels(nxt) - match2); |
| } |
| |
| // Assumption: NSEC has been validated outside of this function |
| // |
| // Does the name exist given the name and NSEC rr ? |
| // |
| // Returns -1 if it is an inappropriate nsec |
| // Returns 1 if the name exists |
| // Returns 0 if the name does not exist |
| // |
| mDNSlocal int NSECNameExists(mDNS *const m, ResourceRecord *rr, domainname *name, mDNSu16 qtype) |
| { |
| const RDataBody2 *const rdb = (RDataBody2 *)rr->rdata->u.data; |
| const domainname *nxt = (const domainname *)&rdb->data; |
| const domainname *oname = rr->name; // owner name |
| int ret1, subdomain1; |
| int ret2, subdomain2; |
| int ret3, subdomain3; |
| |
| ret1 = DNSSECCanonicalOrder(oname, name, &subdomain1); |
| if (ret1 > 0) |
| { |
| LogDNSSEC("NSECNameExists: owner name %##s is bigger than name %##s", oname->c, name->c); |
| return -1; |
| } |
| |
| // Section 4.1 of draft-ietf-dnsext-dnssec-bis-updates-14: |
| // |
| // Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume non- |
| // existence of any RRs below that zone cut, which include all RRs at |
| // that (original) owner name other than DS RRs, and all RRs below that |
| // owner name regardless of type. |
| // |
| // This also implies that we can't use the child side NSEC for DS question. |
| |
| if (!ret1) |
| { |
| mDNSBool soa = RRAssertsExistence(rr, kDNSType_SOA); |
| mDNSBool ns = RRAssertsExistence(rr, kDNSType_NS); |
| |
| // We are here because the owner name is the same as "name". Make sure the |
| // NSEC has the right NS and SOA bits set. |
| if (ns && !soa && qtype != kDNSType_DS) |
| { |
| LogDNSSEC("NSECNameExists: Parent side NSEC %s can't be used for question %##s (%s)", |
| RRDisplayString(m, rr), name->c, DNSTypeName(qtype)); |
| return -1; |
| } |
| else if (ns && soa && qtype == kDNSType_DS) |
| { |
| LogDNSSEC("NSECNameExists: Child side NSEC %s can't be used for question %##s (%s)", |
| RRDisplayString(m, rr), name->c, DNSTypeName(qtype)); |
| return -1; |
| } |
| LogDNSSEC("NSECNameExists: owner name %##s is same as name %##s", oname->c, name->c); |
| return 1; |
| } |
| |
| // If the name is a.b.com and NSEC's owner name is b.com i.e., a subdomain |
| // and nsec comes from the parent (NS is set and SOA is not set), then this |
| // NSEC can't be used for names below the owner name. |
| // |
| // Similarly if DNAME is set, we can't use it here. See RFC2672-bis-dname |
| // appendix. |
| if (subdomain1 && (RRAssertsExistence(rr, kDNSType_DNAME) || |
| (RRAssertsNonexistence(rr, kDNSType_SOA) && RRAssertsExistence(rr, kDNSType_NS)))) |
| { |
| LogDNSSEC("NSECNameExists: NSEC %s comes from the parent, can't use it here", |
| RRDisplayString(m, rr)); |
| return -1; |
| } |
| |
| // At this stage, we know that name is greater than the owner name and |
| // the nsec is not from the parent side. |
| // |
| // Compare with the next field in the nsec. |
| // |
| ret2 = DNSSECCanonicalOrder(name, nxt, &subdomain2); |
| |
| // Exact match with the nsec next name |
| if (!ret2) |
| { |
| LogDNSSEC("NSECNameExists: name %##s is same as nxt name %##s", name->c, nxt->c); |
| return 1; |
| } |
| |
| ret3 = DNSSECCanonicalOrder(oname, nxt, &subdomain3); |
| |
| if (!ret3) |
| { |
| // Pathological case of a single name in the domain. This means only the |
| // apex of the zone itself exists. Nothing below it. "subdomain2" indicates |
| // that name is a subdmain of "next" and hence below the zone. |
| if (subdomain2) |
| { |
| LogDNSSEC("NSECNameExists: owner name %##s subdomain of nxt name %##s", oname->c, nxt->c); |
| return 0; |
| } |
| else |
| { |
| LogDNSSEC("NSECNameExists: Single name in zone, owner name %##s is same as nxt name %##s", oname->c, nxt->c); |
| return -1; |
| } |
| } |
| |
| if (ret3 < 0) |
| { |
| // Regular NSEC in the zone. Make sure that the "name" lies within |
| // oname and next. oname < name and name < next |
| if (ret1 < 0 && ret2 < 0) |
| { |
| LogDNSSEC("NSECNameExists: Normal NSEC name %##s lies within owner %##s and nxt name %##s", |
| name->c, oname->c, nxt->c); |
| return 0; |
| } |
| else |
| { |
| LogDNSSEC("NSECNameExists: Normal NSEC name %##s does not lie within owner %##s and nxt name %##s", |
| name->c, oname->c, nxt->c); |
| return -1; |
| } |
| } |
| else |
| { |
| // Last NSEC in the zone. The "next" is pointing to the apex. All names |
| // should be a subdomain of that and the name should be bigger than |
| // oname |
| if (ret1 < 0 && subdomain2) |
| { |
| LogDNSSEC("NSECNameExists: Last NSEC name %##s lies within owner %##s and nxt name %##s", |
| name->c, oname->c, nxt->c); |
| return 0; |
| } |
| else |
| { |
| LogDNSSEC("NSECNameExists: Last NSEC name %##s does not lie within owner %##s and nxt name %##s", |
| name->c, oname->c, nxt->c); |
| return -1; |
| } |
| } |
| |
| LogDNSSEC("NSECNameExists: NSEC %s did not match any case", RRDisplayString(m, rr)); |
| return -1; |
| } |
| |
| // If the answer was result of a wildcard match, then this function proves |
| // that a proper wildcard was used to answer the question and that the |
| // original name does not exist |
| mDNSexport void WildcardAnswerProof(mDNS *const m, DNSSECVerifier *dv) |
| { |
| CacheRecord *ncr; |
| CacheRecord **rp; |
| const domainname *ce; |
| DNSQuestion q; |
| |
| LogDNSSEC("WildcardAnswerProof: Question %##s (%s)", dv->origName.c, DNSTypeName(dv->origType)); |
| // |
| // RFC 4035: Section 3.1.3.3 |
| // |
| // 1) We used a wildcard because the qname does not exist, so verify |
| // that the qname does not exist |
| // |
| // 2) Is the wildcard the right one ? |
| // |
| // Unfortunately, this is not well explained in that section. Refer to |
| // RFC 5155 section 7.2.6. |
| |
| // Walk the list of nsecs we received and see if they prove that |
| // the name does not exist |
| |
| mDNSPlatformMemZero(&q, sizeof(DNSQuestion)); |
| q.ThisQInterval = -1; |
| InitializeQuestion(m, &q, dv->InterfaceID, &dv->origName, dv->origType, mDNSNULL, mDNSNULL); |
| |
| ncr = NSECParentForQuestion(m, &q); |
| if (!ncr) |
| { |
| LogMsg("NSECWildCardProof: Can't find NSEC Parent for %##s (%s)", q.qname.c, DNSTypeName(q.qtype)); |
| goto error; |
| } |
| rp = &(ncr->nsec); |
| while (*rp) |
| { |
| if ((*rp)->resrec.rrtype == kDNSType_NSEC) |
| { |
| CacheRecord *cr = *rp; |
| if (!NSECNameExists(m, &cr->resrec, &dv->origName, dv->origType)) |
| break; |
| } |
| rp=&(*rp)->next; |
| } |
| if (!(*rp)) |
| { |
| LogMsg("NSECWildCardProof: ERROR!! No NSECs found for %##s (%s)", q.qname.c, DNSTypeName(q.qtype)); |
| goto error; |
| } |
| ce = NSECClosestEncloser(&((*rp)->resrec), &dv->origName); |
| if (!ce) |
| { |
| LogMsg("NSECWildCardProof: ERROR!! Closest Encloser NULL for %##s (%s)", q.qname.c, DNSTypeName(q.qtype)); |
| goto error; |
| } |
| if (!SameDomainName(ce, dv->wildcardName)) |
| { |
| LogMsg("NSECWildCardProof: ERROR!! Closest Encloser %##s does not match wildcard name %##s", q.qname.c, dv->wildcardName->c); |
| goto error; |
| } |
| |
| VerifyNSEC(m, &((*rp)->resrec), mDNSNULL, dv, ncr, mDNSNULL); |
| return; |
| error: |
| dv->DVCallback(m, dv, DNSSEC_Insecure); |
| } |
| |
| // We have a NSEC. Need to see if it proves that NODATA exists for the given name. Note that this |
| // function does not prove anything as proof may require more than one NSEC and this function |
| // processes only one NSEC at a time. |
| // |
| // Returns mDNSfalse if the NSEC does not prove the NODATA error |
| // Returns mDNStrue if the NSEC proves the NODATA error |
| // |
| mDNSlocal mDNSBool NSECNoDataError(mDNS *const m, ResourceRecord *rr, domainname *name, mDNSu16 qtype, domainname **wildcard) |
| { |
| const domainname *oname = rr->name; // owner name |
| |
| if (wildcard) *wildcard = mDNSNULL; |
| // RFC 4035 |
| // |
| // section 3.1.3.1 : Name matches. Prove that the type does not exist and also CNAME is |
| // not set as in that case CNAME should have been returned ( CNAME part is mentioned in |
| // section 4.3 of dnssec-bis-updates.) Without the CNAME check, a positive response can |
| // be converted to a NODATA/NOERROR response. |
| // |
| // section 3.1.3.4 : No exact match for the name but there is a wildcard that could match |
| // the name but not the type. There are two NSECs in this case. One of them is a wildcard |
| // NSEC and another NSEC proving that the qname does not exist. We are called with one |
| // NSEC at a time. We return what we matched and the caller should decide whether all |
| // conditions are met for the proof. |
| if (SameDomainName(oname, name)) |
| { |
| mDNSBool soa = RRAssertsExistence(rr, kDNSType_SOA); |
| mDNSBool ns = RRAssertsExistence(rr, kDNSType_NS); |
| if (qtype != kDNSType_DS) |
| { |
| // For non-DS type questions, we don't want to use the parent side records to |
| // answer it |
| if (ns && !soa) |
| { |
| LogDNSSEC("NSECNoDataError: Parent side NSEC %s, can't use for child qname %##s (%s)", |
| RRDisplayString(m, rr), name->c, DNSTypeName(qtype)); |
| return mDNSfalse; |
| } |
| } |
| else |
| { |
| if (ns && soa) |
| { |
| LogDNSSEC("NSECNoDataError: Child side NSEC %s, can't use for parent qname %##s (%s)", |
| RRDisplayString(m, rr), name->c, DNSTypeName(qtype)); |
| return mDNSfalse; |
| } |
| } |
| if (RRAssertsExistence(rr, qtype) || RRAssertsExistence(rr, kDNSType_CNAME)) |
| { |
| LogMsg("NSECNoDataError: ERROR!! qtype %s exists in %s", DNSTypeName(qtype), RRDisplayString(m, rr)); |
| return mDNSfalse; |
| } |
| LogDNSSEC("NSECNoDataError: qype %s does not exist in %s", DNSTypeName(qtype), RRDisplayString(m, rr)); |
| return mDNStrue; |
| } |
| else |
| { |
| // Name does not exist. Before we check for a wildcard match, make sure that |
| // this is not an ENT. |
| // |
| if (NSECAnswersENT(rr, name)) |
| { |
| LogDNSSEC("NSECNoDataError: ERROR!! name %##s exists %s", name->c, RRDisplayString(m, rr)); |
| return mDNSfalse; |
| } |
| |
| // Wildcard check. If this is a wildcard NSEC, then check to see if we could |
| // have answered the question using this wildcard and it should not have the |
| // "qtype" passed in with its bitmap. |
| // |
| // See RFC 4592, on how wildcards are used to synthesize answers. Find the |
| // closest encloser and the qname should be a subdomain i.e if the wildcard |
| // is *.x.example, x.example is the closest encloser and the qname should be |
| // a subdomain e.g., y.x.example or z.y.x.example and so on. |
| if (oname->c[0] == 1 && oname->c[1] == '*') |
| { |
| int r, s; |
| const domainname *ce = SkipLeadingLabels(oname, 1); |
| |
| r = DNSSECCanonicalOrder(name, ce, &s); |
| if (s) |
| { |
| if (RRAssertsExistence(rr, qtype) || RRAssertsExistence(rr, kDNSType_CNAME)) |
| { |
| LogMsg("NSECNoDataError: ERROR!! qtype %s exists in wildcard %s", DNSTypeName(qtype), RRDisplayString(m, rr)); |
| return mDNSfalse; |
| } |
| // It is odd for a wildcard to match when we are looking up DS |
| // See RFC 4592 |
| if (qtype == kDNSType_DS) |
| { |
| LogMsg("NSECNoDataError: ERROR!! DS qtype exists in wildcard %s", RRDisplayString(m, rr)); |
| return mDNSfalse; |
| } |
| // Don't use the parent side record for this |
| if (RRAssertsNonexistence(rr, kDNSType_SOA) && |
| RRAssertsExistence(rr, kDNSType_NS)) |
| { |
| LogDNSSEC("NSECNoDataError: Parent side wildcard NSEC %s, can't use for child qname %##s (%s)", |
| RRDisplayString(m, rr), name->c, DNSTypeName(qtype)); |
| return mDNSfalse; |
| } |
| *wildcard = (domainname *)ce; |
| LogDNSSEC("NSECNoDataError: qtype %s does not exist in wildcard %s", DNSTypeName(qtype), RRDisplayString(m, rr)); |
| return mDNStrue; |
| } |
| } |
| return mDNSfalse; |
| } |
| } |
| |
| mDNSlocal void NoDataNSECCallback(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status) |
| { |
| RRVerifier *rv; |
| DNSSECVerifier *pdv; |
| CacheRecord *ncr; |
| |
| LogDNSSEC("NoDataNSECCallback: called"); |
| if (!dv->parent) |
| { |
| LogMsg("NoDataNSECCCallback: no parent DV"); |
| FreeDNSSECVerifier(m, dv); |
| return; |
| } |
| |
| if (dv->ac) |
| { |
| // Before we free the "dv", we need to update the |
| // parent with our AuthChain information |
| AuthChainLink(dv->parent, dv->ac); |
| dv->ac = mDNSNULL; |
| dv->actail = &dv->ac; |
| } |
| |
| pdv = dv->parent; |
| if (status != DNSSEC_Secure) |
| { |
| goto error; |
| } |
| if (!(pdv->flags & NSEC_PROVES_NONAME_EXISTS)) |
| { |
| LogMsg("NoDataNSECCCallback: ERROR!! NSEC_PROVES_NONAME_EXISTS not set"); |
| goto error; |
| } |
| if (!(pdv->flags & WILDCARD_PROVES_NONAME_EXISTS)) |
| { |
| LogMsg("NoDataNSECCCallback: ERROR!! WILDCARD_PROVES_NONAME_EXISTS not set"); |
| goto error; |
| } |
| |
| // We don't care about the "dv" that was allocated in VerifyNSEC. |
| // Get the original verifier and verify the other NSEC like we did |
| // the first time. |
| dv->parent = mDNSNULL; |
| FreeDNSSECVerifier(m, dv); |
| |
| ncr = NSECParentForQuestion(m, &pdv->q); |
| if (!ncr) |
| { |
| LogMsg("NoDataNSECCallback: Can't find NSEC Parent for %##s (%s)", pdv->q.qname.c, DNSTypeName(pdv->q.qtype)); |
| goto error; |
| } |
| |
| rv = pdv->pendingNSEC; |
| pdv->pendingNSEC = mDNSNULL; |
| // Verify the pendingNSEC and we don't need to come back here. Let the regular |
| // NSECCallback call the original callback. |
| VerifyNSEC(m, mDNSNULL, rv, pdv, ncr, mDNSNULL); |
| return; |
| |
| error: |
| dv->parent->DVCallback(m, dv->parent, status); |
| dv->parent = mDNSNULL; |
| FreeDNSSECVerifier(m, dv); |
| } |
| |
| mDNSlocal void NameErrorNSECCallback(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status) |
| { |
| RRVerifier *rv; |
| DNSSECVerifier *pdv; |
| CacheRecord *ncr; |
| |
| LogDNSSEC("NameErrorNSECCallback: called"); |
| if (!dv->parent) |
| { |
| LogMsg("NameErrorNSECCCallback: no parent DV"); |
| FreeDNSSECVerifier(m, dv); |
| return; |
| } |
| |
| if (dv->ac) |
| { |
| // Before we free the "dv", we need to update the |
| // parent with our AuthChain information |
| AuthChainLink(dv->parent, dv->ac); |
| dv->ac = mDNSNULL; |
| dv->actail = &dv->ac; |
| } |
| |
| pdv = dv->parent; |
| if (status != DNSSEC_Secure) |
| { |
| goto error; |
| } |
| // We don't care about the "dv" that was allocated in VerifyNSEC. |
| // Get the original verifier and verify the other NSEC like we did |
| // the first time. |
| dv->parent = mDNSNULL; |
| FreeDNSSECVerifier(m, dv); |
| |
| ncr = NSECParentForQuestion(m, &pdv->q); |
| if (!ncr) |
| { |
| LogMsg("NameErrorNSECCallback: Can't find NSEC Parent for %##s (%s)", pdv->q.qname.c, DNSTypeName(pdv->q.qtype)); |
| goto error; |
| } |
| rv = pdv->pendingNSEC; |
| pdv->pendingNSEC = mDNSNULL; |
| // Verify the pendingNSEC and we don't need to come back here. Let the regular |
| // NSECCallback call the original callback. |
| VerifyNSEC(m, mDNSNULL, rv, pdv, ncr, mDNSNULL); |
| return; |
| |
| error: |
| dv->parent->DVCallback(m, dv->parent, status); |
| dv->parent = mDNSNULL; |
| FreeDNSSECVerifier(m, dv); |
| } |
| |
| // We get a NODATA error with no records in answer section. This proves |
| // that qname does not exist. |
| mDNSlocal void NoDataProof(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr) |
| { |
| CacheRecord **rp; |
| domainname *wildcard = mDNSNULL; |
| const domainname *ce = mDNSNULL; |
| ResourceRecord *nsec_wild = mDNSNULL; |
| ResourceRecord *nsec_noname = mDNSNULL; |
| |
| // NODATA Error could mean two things. The name exists with no type or there is a |
| // wildcard that matches the name but no type. This is done by NSECNoDataError. |
| // |
| // If it is the case of wildcard, there are two NSECs. One is the wildcard NSEC and |
| // the other NSEC to prove that there is no other closer match. |
| |
| wildcard = mDNSNULL; |
| rp = &(ncr->nsec); |
| while (*rp) |
| { |
| if ((*rp)->resrec.rrtype == kDNSType_NSEC) |
| { |
| CacheRecord *cr = *rp; |
| if (NSECNoDataError(m, &cr->resrec, &dv->q.qname, dv->q.qtype, &wildcard)) |
| { |
| if (wildcard) |
| { |
| dv->flags |= WILDCARD_PROVES_NONAME_EXISTS; |
| LogDNSSEC("NoDataProof: NSEC %s proves NODATA error for %##s (%s)", |
| RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype)); |
| } |
| else |
| { |
| dv->flags |= NSEC_PROVES_NOTYPE_EXISTS; |
| LogDNSSEC("NoDataProof: NSEC %s proves NOTYPE error for %##s (%s)", |
| RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype)); |
| } |
| nsec_wild = &cr->resrec; |
| } |
| if (!NSECNameExists(m, &cr->resrec, &dv->q.qname, dv->q.qtype)) |
| { |
| LogDNSSEC("NoDataProof: NSEC %s proves that name %##s (%s) does not exist", |
| RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype)); |
| // If we have a wildcard, then we should check to see if the closest |
| // encloser is the same as the wildcard. |
| ce = NSECClosestEncloser(&cr->resrec, &dv->q.qname); |
| dv->flags |= NSEC_PROVES_NONAME_EXISTS; |
| nsec_noname = &cr->resrec; |
| } |
| } |
| rp=&(*rp)->next; |
| } |
| // If the type exists, then we have to verify just that NSEC |
| if (!(dv->flags & NSEC_PROVES_NOTYPE_EXISTS)) |
| { |
| // If we have a wildcard, then we should have a "ce" which matches the wildcard |
| // If we don't have a wildcard, then we should have proven that the name does not |
| // exist which means we would have set the "ce". |
| if (wildcard && !ce) |
| { |
| LogMsg("NoDataProof: Cannot prove that the name %##s (%s) does not exist", dv->q.qname.c, DNSTypeName(dv->q.qtype)); |
| goto error; |
| } |
| if (wildcard && !SameDomainName(wildcard, ce)) |
| { |
| LogMsg("NoDataProof: wildcard %##s does not match closest encloser %##s", wildcard->c, ce->c); |
| goto error; |
| } |
| } |
| |
| if ((dv->flags & (WILDCARD_PROVES_NONAME_EXISTS|NSEC_PROVES_NONAME_EXISTS)) == |
| (WILDCARD_PROVES_NONAME_EXISTS|NSEC_PROVES_NONAME_EXISTS)) |
| { |
| mStatus status; |
| RRVerifier *r = AllocateRRVerifier(nsec_noname, &status); |
| if (!r) goto error; |
| // First verify wildcard NSEC and then when we are done, we |
| // will verify the noname nsec |
| dv->pendingNSEC = r; |
| VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, NoDataNSECCallback); |
| } |
| else if ((dv->flags & WILDCARD_PROVES_NONAME_EXISTS) || |
| (dv->flags & NSEC_PROVES_NOTYPE_EXISTS)) |
| { |
| VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, mDNSNULL); |
| } |
| else if (dv->flags & NSEC_PROVES_NONAME_EXISTS) |
| { |
| VerifyNSEC(m, nsec_noname, mDNSNULL, dv, ncr, mDNSNULL); |
| } |
| return; |
| error: |
| LogDNSSEC("NoDataProof: Error return"); |
| dv->DVCallback(m, dv, DNSSEC_Insecure); |
| } |
| |
| mDNSlocal mDNSBool NSECNoWildcard(mDNS *const m, ResourceRecord *rr, domainname *qname, mDNSu16 qtype) |
| { |
| const domainname *ce; |
| domainname wild; |
| |
| // If the query name is c.x.w.example and if the name does not exist, we should get |
| // get a nsec back that looks something like this: |
| // |
| // w.example NSEC a.w.example |
| // |
| // First, we need to get the closest encloser which in this case is w.example. Wild |
| // card synthesis works by finding the closest encloser first and then look for |
| // a "*" label (assuming * label does not appear in the question). If it does not |
| // exists, it would return the NSEC at that name. And the wildcard name at the |
| // closest encloser "*.w.example" would be covered by such an NSEC. (Appending "*" |
| // makes it bigger than w.example and "* is smaller than "a" for the above NSEC) |
| // |
| ce = NSECClosestEncloser(rr, qname); |
| if (!ce) { LogMsg("NSECNoWildcard: No closest encloser for rr %s, qname %##s (%s)", qname->c, DNSTypeName(qtype)); return mDNSfalse; } |
| |
| wild.c[0] = 1; |
| wild.c[1] = '*'; |
| wild.c[2] = 0; |
| if (!AppendDomainName(&wild, ce)) |
| { |
| LogMsg("NSECNoWildcard: ERROR!! Can't append domainname closest encloser name %##s, qname %##s (%s)", ce->c, qname->c, DNSTypeName(qtype)); |
| return mDNSfalse; |
| } |
| if (NSECNameExists(m, rr, &wild, qtype) != 0) |
| { |
| LogDNSSEC("NSECNoWildcard: Wildcard name %##s exists or not valid qname %##s (%s)", wild.c, qname->c, DNSTypeName(qtype)); |
| return mDNSfalse; |
| } |
| LogDNSSEC("NSECNoWildcard: Wildcard name %##s does not exist for record %s, qname %##s (%s)", wild.c, |
| RRDisplayString(m, rr), qname->c, DNSTypeName(qtype)); |
| return mDNStrue; |
| } |
| |
| // We get a NXDOMAIN error with no records in answer section. This proves |
| // that qname does not exist. |
| mDNSlocal void NameErrorProof(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr) |
| { |
| CacheRecord **rp; |
| ResourceRecord *nsec_wild = mDNSNULL; |
| ResourceRecord *nsec_noname = mDNSNULL; |
| mStatus status; |
| |
| // NXDOMAIN Error. We need to prove that the qname does not exist and there |
| // is no wildcard that can be used to answer the question. |
| |
| rp = &(ncr->nsec); |
| while (*rp) |
| { |
| if ((*rp)->resrec.rrtype == kDNSType_NSEC) |
| { |
| CacheRecord *cr = *rp; |
| if (!NSECNameExists(m, &cr->resrec, &dv->q.qname, dv->q.qtype)) |
| { |
| LogDNSSEC("NameErrorProof: NSEC %s proves name does not exist for %##s (%s)", |
| RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype)); |
| // If we have a wildcard, then we should check to see if the closest |
| // encloser is the same as the wildcard. |
| dv->flags |= NSEC_PROVES_NONAME_EXISTS; |
| nsec_noname = &cr->resrec; |
| } |
| if (NSECNoWildcard(m, &cr->resrec, &dv->q.qname, dv->q.qtype)) |
| { |
| dv->flags |= WILDCARD_PROVES_NONAME_EXISTS; |
| nsec_wild = &cr->resrec; |
| LogDNSSEC("NameErrorProof: NSEC %s proves wildcard cannot answer question for %##s (%s)", |
| RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype)); |
| } |
| } |
| rp=&(*rp)->next; |
| } |
| if (!nsec_noname || !nsec_wild) |
| { |
| LogMsg("NameErrorProof: Proof failed for %##s (%s) noname %p, wild %p", dv->q.qname.c, DNSTypeName(dv->q.qtype), nsec_noname, nsec_wild); |
| goto error; |
| } |
| |
| // First verify wildcard NSEC and then when we are done, we will verify the noname nsec. |
| // Sometimes a single NSEC can prove both that the "qname" does not exist and a wildcard |
| // could not have produced qname. These are a few examples where this can happen. |
| // |
| // 1. If the zone is example.com and you look up *.example.com and if there are no wildcards, |
| // you will get a NSEC back "example.com NSEC a.example.com". This proves that both the |
| // name does not exist and *.example.com also does not exist |
| // |
| // 2. If the zone is example.com and it has a record like this: |
| // |
| // example.com NSEC d.example.com |
| // |
| // any name you lookup in between like a.example.com,b.example.com etc. you will get a single |
| // NSEC back. In that case we just have to verify only once. |
| // |
| if (nsec_wild != nsec_noname) |
| { |
| RRVerifier *r = AllocateRRVerifier(nsec_noname, &status); |
| if (!r) goto error; |
| dv->pendingNSEC = r; |
| VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, NameErrorNSECCallback); |
| } |
| else |
| { |
| VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, mDNSNULL); |
| } |
| return; |
| error: |
| dv->DVCallback(m, dv, DNSSEC_Insecure); |
| } |
| |
| mDNSexport void ValidateWithNSECS(mDNS *const m, DNSSECVerifier *dv, CacheRecord *cr) |
| { |
| LogDNSSEC("ValidateWithNSECS: called for %s", CRDisplayString(m, cr)); |
| // "parent" is set when we are validating a NSEC. In the process of validating that |
| // nsec, we encountered another NSEC. For example, we are looking up the A record for |
| // www.example.com, we got an NSEC at some stage. We come here to validate the NSEC |
| // the first time. While validating the NSEC we remember the original validation result |
| // in the parent. But while validating the NSEC, we got another NSEC back e.g., not |
| // a secure delegation i.e., we got an NSEC proving that DS does not exist. We prove |
| // that again. But if we receive more NSECs after this, we stop. |
| // |
| if (dv->parent) |
| { |
| if (dv->parent->parent) |
| { |
| LogMsg("ValidateWithNSECS: ERROR!! dv parent is set already"); |
| dv->DVCallback(m, dv, DNSSEC_Indeterminate); |
| return; |
| } |
| else |
| { |
| DNSSECVerifier *pdv = dv; |
| dv = AllocateDNSSECVerifier(m, &pdv->q.qname, pdv->q.qtype, pdv->q.InterfaceID, VerifyNSECCallback, mDNSNULL); |
| if (!dv) |
| { |
| LogMsg("VerifyNSEC: mDNSPlatformMemAlloc failed"); |
| pdv->DVCallback(m, pdv, DNSSEC_Indeterminate); |
| return; |
| } |
| LogDNSSEC("ValidateWithNSECS: Parent set, Verifying dv %p %##s (%s)", dv, pdv->q.qname.c, DNSTypeName(pdv->q.qtype)); |
| dv->parent = pdv; |
| } |
| } |
| if (cr->resrec.RecordType == kDNSRecordTypePacketNegative) |
| { |
| CacheRecord *neg = cr->nsec; |
| while (neg) |
| { |
| LogDNSSEC("ValidateWithNSECS: NSECCached Record %s", CRDisplayString(m, neg)); |
| neg = neg->next; |
| } |
| |
| if (cr->rcode == kDNSFlag1_RC_NoErr) |
| { |
| NoDataProof(m, dv, cr); |
| } |
| else if (cr->rcode == kDNSFlag1_RC_NXDomain) |
| { |
| NameErrorProof(m, dv, cr); |
| } |
| else |
| { |
| LogDNSSEC("ValidateWithNSECS: Rcode %d invalid", cr->rcode); |
| dv->DVCallback(m, dv, DNSSEC_Insecure); |
| } |
| } |
| else |
| { |
| LogMsg("ValidateWithNSECS: Not a valid cache record %s for NSEC proofs", CRDisplayString(m, cr)); |
| dv->DVCallback(m, dv, DNSSEC_Insecure); |
| return; |
| } |
| } |