mDNSlocal mDNSBool ValidateTrustAnchor(TrustAnchor *ta) { time_t currTime = time(NULL); // Currently only support trust anchor for root. if (!SameDomainName(&ta->zone, (const domainname *)"\000")) { LogInfo("ParseElementChildren: Zone %##s not root", ta->zone.c); return mDNSfalse; } switch (ta->rds.digestType) { case SHA1_DIGEST_TYPE: if (ta->digestLen != CC_SHA1_DIGEST_LENGTH) { LogMsg("ValidateTrustAnchor: Invalid digest len %d for SHA1", ta->digestLen); return mDNSfalse; } break; case SHA256_DIGEST_TYPE: if (ta->digestLen != CC_SHA256_DIGEST_LENGTH) { LogMsg("ValidateTrustAnchor: Invalid digest len %d for SHA256", ta->digestLen); return mDNSfalse; } break; default: LogMsg("ValidateTrustAnchor: digest type %d not supported", ta->rds.digestType); return mDNSfalse; } if (!ta->rds.digest) { LogMsg("ValidateTrustAnchor: digest NULL for %d", ta->rds.digestType); return mDNSfalse; } switch (ta->rds.alg) { case CRYPTO_RSA_SHA512: case CRYPTO_RSA_SHA256: case CRYPTO_RSA_NSEC3_SHA1: case CRYPTO_RSA_SHA1: break; default: LogMsg("ValidateTrustAnchor: Algorithm %d not supported", ta->rds.alg); return mDNSfalse; } if (DNS_SERIAL_LT(currTime, ta->validFrom)) { LogMsg("ValidateTrustAnchor: Invalid ValidFrom time %u, currtime %u", (unsigned)ta->validFrom, (unsigned)currTime); return mDNSfalse; } if (DNS_SERIAL_LT(ta->validUntil, currTime)) { LogMsg("ValidateTrustAnchor: Invalid ValidUntil time %u, currtime %u", (unsigned)ta->validUntil, (unsigned)currTime); return mDNSfalse; } return mDNStrue; }
// Return the number of labels that matches starting from the right (excluding the // root label) mDNSexport 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; }
mDNSlocal mDNSBool NSEC3ClosestEncloserProof(mDNS *const m, CacheRecord *ncr, domainname *name, CacheRecord **closestEncloser, CacheRecord **closerEncloser, const domainname **ce, mDNSu16 qtype) { if (!NSEC3Find(m, NSEC3CEProof, ncr, name, closestEncloser, closerEncloser, ce, qtype)) { LogDNSSEC("NSEC3ClosestEncloserProof: ERROR!! Cannot do closest encloser proof"); return mDNSfalse; } // Note: It is possible that closestEncloser and closerEncloser are the same. if (!closestEncloser || !closerEncloser || !ce) { LogMsg("NSEC3ClosestEncloserProof: ClosestEncloser %p or CloserEncloser %p ce %p, something is NULL", *closestEncloser, *closerEncloser, *ce); return mDNSfalse; } // If the name exists, we should not have gotten the name error if (SameDomainName((*ce), name)) { LogMsg("NSEC3ClosestEncloserProof: ClosestEncloser %s same as origName %##s", CRDisplayString(m, *closestEncloser), (*ce)->c); return mDNSfalse; } return mDNStrue; }
mDNSexport CacheRecord *NSECRecordIsDelegation(mDNS *const m, domainname *name, mDNSu16 qtype) { CacheGroup *cg; CacheRecord *cr; mDNSu32 slot, namehash; slot = HashSlot(name); namehash = DomainNameHashValue(name); cg = CacheGroupForName(m, (const mDNSu32)slot, namehash, name); if (!cg) { LogDNSSEC("NSECRecordForName: cg NULL for %##s", name); return mDNSNULL; } for (cr = cg->members; cr; cr = cr->next) { if (cr->resrec.RecordType == kDNSRecordTypePacketNegative && cr->resrec.rrtype == qtype) { CacheRecord *ncr; for (ncr = cr->nsec; ncr; ncr = ncr->next) { if (ncr->resrec.rrtype == kDNSType_NSEC && SameDomainName(ncr->resrec.name, name)) { // See the Insecure Delegation Proof section in dnssec-bis: DS bit and SOA bit // should be absent if (RRAssertsExistence(&ncr->resrec, kDNSType_SOA) || RRAssertsExistence(&ncr->resrec, kDNSType_DS)) { LogDNSSEC("NSECRecordForName: found record %s for %##s (%s), but DS or SOA bit set", CRDisplayString(m, ncr), name, DNSTypeName(qtype)); return mDNSNULL; } // Section 2.3 of RFC 4035 states that: // // Each owner name in the zone that has authoritative data or a delegation point NS RRset MUST // have an NSEC resource record. // // So, if we have an NSEC record matching the question name with the NS bit set, // then this is a delegation. // if (RRAssertsExistence(&ncr->resrec, kDNSType_NS)) { LogDNSSEC("NSECRecordForName: found record %s for %##s (%s)", CRDisplayString(m, ncr), name, DNSTypeName(qtype)); return ncr; } else { LogDNSSEC("NSECRecordForName: found record %s for %##s (%s), but NS bit is not set", CRDisplayString(m, ncr), name, DNSTypeName(qtype)); return mDNSNULL; } } } } } return mDNSNULL; }
mDNSlocal void DelTrustAnchor(mDNS *const m, const domainname *zone) { TrustAnchor **ta = &m->TrustAnchors; TrustAnchor *tmp; while (*ta && !SameDomainName(&(*ta)->zone, zone)) ta = &(*ta)->next; // First time, we won't find the TrustAnchor in the list as it has // not been added. if (!(*ta)) return; tmp = *ta; *ta = (*ta)->next; // Cut this record from the list tmp->next = mDNSNULL; if (tmp->rds.digest) mDNSPlatformMemFree(tmp->rds.digest); mDNSPlatformMemFree(tmp); }
// Find a matching NSEC3 record for the name. We parse the questions and the records in the packet in order. // Similarly we also parse the NSEC3 records in order and this mapping to the questions and records // respectively. mDNSlocal CacheRecord *FindMatchingNSEC3ForName(mDNS *const m, CacheRecord **nsec3, const domainname *name) { CacheRecord *cr; CacheRecord **prev = nsec3; (void) m; for (cr = *nsec3; cr; cr = cr->next) { if (SameDomainName(cr->resrec.name, name)) { debugf("FindMatchingNSEC3ForName: NSEC3 record %s matched %##s", CRDisplayString(m, cr), name->c); *prev = cr->next; cr->next = mDNSNULL; return cr; } prev = &cr->next; } return mDNSNULL; }
mDNSlocal void AddTrustAnchor(mDNS *const m, const domainname *zone, mDNSu16 keytag, mDNSu8 alg, mDNSu8 digestType, int diglen, mDNSu8 *digest) { TrustAnchor *ta, *tmp; mDNSu32 t = (mDNSu32) time(NULL); // Check for duplicates tmp = m->TrustAnchors; while (tmp) { if (SameDomainName(zone, &tmp->zone) && tmp->rds.keyTag == keytag && tmp->rds.alg == alg && tmp->rds.digestType == digestType && !memcmp(tmp->rds.digest, digest, diglen)) { LogMsg("AddTrustAnchors: Found a duplicate"); return; } tmp = tmp->next; } ta = (TrustAnchor *)mDNSPlatformMemAllocate(sizeof(TrustAnchor)); if (!ta) { LogMsg("AddTrustAnchor: malloc failure ta"); return; } ta->rds.keyTag = keytag; ta->rds.alg = alg; ta->rds.digestType = digestType; ta->rds.digest = digest; ta->digestLen = diglen; ta->validFrom = t; ta->validUntil = t + TEST_TA_EXPIRE_INTERVAL; AssignDomainName(&ta->zone, zone); ta->next = mDNSNULL; LinkTrustAnchor(m, ta); }
// 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 (!nsec_noname && !nsec_wild) { LogDNSSEC("NoDataProof: No valid NSECs for %##s (%s)", dv->q.qname.c, DNSTypeName(dv->q.qtype)); goto error; } // 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 a single NSEC can prove both, then we just have validate that one NSEC. if (nsec_wild == nsec_noname) { nsec_noname = mDNSNULL; dv->flags &= ~NSEC_PROVES_NONAME_EXISTS; } } 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; LogDNSSEC("NoDataProof: Verifying wild and noname %s", RRDisplayString(m, nsec_wild)); VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, NoDataNSECCallback); } else if ((dv->flags & WILDCARD_PROVES_NONAME_EXISTS) || (dv->flags & NSEC_PROVES_NOTYPE_EXISTS)) { LogDNSSEC("NoDataProof: Verifying wild %s", RRDisplayString(m, nsec_wild)); VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, mDNSNULL); } else if (dv->flags & NSEC_PROVES_NONAME_EXISTS) { LogDNSSEC("NoDataProof: Verifying noname %s", RRDisplayString(m, nsec_noname)); VerifyNSEC(m, nsec_noname, mDNSNULL, dv, ncr, mDNSNULL); } return; error: LogDNSSEC("NoDataProof: Error return"); dv->DVCallback(m, dv, DNSSEC_Bogus); }
// 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 (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: 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; } if (qtype == kDNSType_DS && RRAssertsExistence(rr, kDNSType_SOA)) { LogDNSSEC("NSECNoDataError: Child side wildcard NSEC %s, can't use for parent qname %##s (%s)", RRDisplayString(m, rr), name->c, DNSTypeName(qtype)); return mDNSfalse; } else if (qtype != kDNSType_DS && RRAssertsNonexistence(rr, kDNSType_SOA) && RRAssertsExistence(rr, kDNSType_NS)) { // Don't use the parent side record for this 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; } }
// 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; CacheRecord **nsec3 = mDNSNULL; 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("WildcardAnswerProof: Can't find NSEC Parent for %##s (%s)", q.qname.c, DNSTypeName(q.qtype)); goto error; } else { LogDNSSEC("WildcardAnswerProof: found %s", CRDisplayString(m, ncr)); } rp = &(ncr->nsec); while (*rp) { if ((*rp)->resrec.rrtype == kDNSType_NSEC) { CacheRecord *cr = *rp; if (!NSECNameExists(m, &cr->resrec, &dv->origName, dv->origType)) break; } else if ((*rp)->resrec.rrtype == kDNSType_NSEC3) { nsec3 = rp; } rp=&(*rp)->next; } if (!(*rp)) { mDNSBool ret = mDNSfalse; if (nsec3) { ret = NSEC3WildcardAnswerProof(m, ncr, dv); } if (!ret) { LogDNSSEC("WildcardAnswerProof: NSEC3 wildcard proof failed for %##s (%s)", q.qname.c, DNSTypeName(q.qtype)); goto error; } rp = nsec3; } else { ce = NSECClosestEncloser(&((*rp)->resrec), &dv->origName); if (!ce) { LogMsg("WildcardAnswerProof: ERROR!! Closest Encloser NULL for %##s (%s)", q.qname.c, DNSTypeName(q.qtype)); goto error; } if (!SameDomainName(ce, dv->wildcardName)) { LogMsg("WildcardAnswerProof: 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_Bogus); }