dnssec.c   [plain text]


/* -*- 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.
 */
#include "mDNSEmbeddedAPI.h"
#include "DNSCommon.h"
#include "dnssec.h"
#include "CryptoAlg.h"
#include "nsec.h"

//#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.
//
// 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, DNSSECStatus status);

// 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

// 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;
}

mDNSlocal int DNSMemCmp(mDNSu8 *const m1, mDNSu8 *const m2, int len)
{
    int res;

    res = mDNSPlatformMemCmp(m1, m2, len);
    if (res != 0)
        return (res < 0 ? -1 : 1);
    return 0;
}

mDNSlocal mStatus DNSNameToLowerCase(domainname *d, domainname *result)
{
    const mDNSu8 *a = d->c;
    mDNSu8 *b = result->c;
    const mDNSu8 *const max = d->c + MAX_DOMAIN_NAME;
    int i, len;

    while (*a)
    {
        if (a + 1 + *a >= max)
        {
            LogMsg("DNSNameToLowerCase: ERROR!! Malformed Domain name");
            return mStatus_BadParamErr;
        }
        len = *a++;
        *b++ = len;
        for (i = 0; i < len; i++)
        {
            mDNSu8 ac = *a++;
            if (mDNSIsUpperCase(ac)) ac += 'a' - 'A';
            *b++ = ac;
        }
    }
    *b = 0;

    return mStatus_NoError;
}

// 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)
{
    LogOperation("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;

    // 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);

    // 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,
                                                  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));

    // 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;
    dv->ac = mDNSNULL;
    dv->actail = &dv->ac;
    // 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 void FreeDNSSECAuthChain(DNSSECVerifier *dv)
{
    RRVerifier *rrset;
    RRVerifier *next;
    AuthChain *ac, *acnext;

    LogDNSSEC("FreeDNSSECAuthChain: called");

    ac = dv->ac;

    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;
    }
    dv->ac = mDNSNULL;
}

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;
    if (dv->pendingNSEC)
    {
        mDNSPlatformMemFree(dv->pendingNSEC);
        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) FreeDNSSECAuthChain(dv);
    if (dv->parent)
    {
        LogDNSSEC("FreeDNSSECVerifier freeing parent %p", dv->parent);
        FreeDNSSECVerifier(m, dv->parent);
    }
    mDNSPlatformMemFree(dv);
}

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)
{
    mDNSu32 slot;
    CacheGroup *cg;
    CacheRecord *cr;
    RRVerifier *rv;

    *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;
    slot = HashSlot(&rv->name);
    cg = CacheGroupForName(m, slot, 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) continue;
        if (cr->resrec.RecordType == kDNSRecordTypePacketNegative)
        {
            if (!(*negcr))
            {
                LogDNSSEC("CheckRRSIGForRRSet: Negative cache record %s encountered for %##s (%s)", CRDisplayString(m, cr),
                          rv->name.c, rv->rrtype);
                *negcr = cr;
            }
            else
            {
                LogMsg("CheckRRSIGForRRSet: ERROR!! Negative cache record %s already set for %##s (%s)", CRDisplayString(m, cr),
                       rv->name.c, 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, rv->rrtype);
        return mStatus_BadParamErr;
    }
    if (dv->rrsig || *negcr)
        return mStatus_NoError;
    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 slot;
    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;

    slot = HashSlot(name);
    namehash = DomainNameHashValue(name);
    cg = CacheGroupForName(m, slot, 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 slot;
    mDNSu32 namehash;
    CacheGroup *cg;
    CacheRecord *cr;
    rdataRRSig *rrsig;
    domainname *name;

    *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;

    slot = HashSlot(name);
    namehash = DomainNameHashValue(name);
    cg = CacheGroupForName(m, slot, 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) 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 (dv->rrsigKey || *negcr)
        return mStatus_NoError;
    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 slot;
    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;
    slot = HashSlot(name);
    namehash = DomainNameHashValue(name);
    cg = CacheGroupForName(m, slot, 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;
    return (dv->ds ? mStatus_NoError : 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_Indeterminate);
        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_Indeterminate);
            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)
        {
            dv->DVCallback = DNSSECNegativeValidationCB;
            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");
            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_Indeterminate);
            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)
        {
            dv->DVCallback = DNSSECNegativeValidationCB;
            ValidateWithNSECS(m, dv, negcr);
            return mDNSfalse;
        }

        dv->next = RRVS_rrsig_key;
        if (!dv->key)
        {
            debugdnssec("GetAllRRSetsForVerification: Fetching DNSKEY for RRSET");
            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_Indeterminate);
            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)
        {
            dv->DVCallback = DNSSECNegativeValidationCB;
            ValidateWithNSECS(m, dv, negcr);
            return mDNSfalse;
        }
        dv->next = RRVS_ds;
        debugdnssec("VerifySigCallback: RRVS_rrsig_key %p", dv->rrsigKey);
        if (!dv->rrsigKey)
        {
            debugdnssec("GetAllRRSetsForVerification: Fetching RRSIGS for DNSKEY");
            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_Indeterminate);
            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)
        {
            dv->DVCallback = DNSSECNegativeValidationCB;
            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");
            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 : <length><Value>
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> <domainname> <data>
        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)
    {
        LogMsg("ValidateSignatureWithKey: ERROR!! No algorithm support for %d", rrsig->alg);
        return mDNSfalse;
    }
    AlgAdd(dv->ctx, (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, fixedPart, fixedPartLen);

        // Add the rdlength
        rdlen = swap16(p->rdlength);
        AlgAdd(dv->ctx, (mDNSu8 *)&rdlen, sizeof(mDNSu16));

        ConvertRDATAToCanonical(p->rrtype, p->rdlength, p->rdata);

        PrintVarSignInfo(rdlen, p->rdata);
        AlgAdd(dv->ctx, 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, 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 *rv;
    rdataRRSig *rrsig;
    mDNSu32 slot;
    CacheGroup *cg;
    int sigNameLen, len;
    mDNSu8 *ptr;
    mDNSu32 rrTTL, rrsigTTL, rrsigOrigTTL, rrsigTimeTTL;
    domainname *qname;
    mDNSu16 qtype;
    CacheRecord *rrsigRR;

    debugdnssec("SetTTLRRSet called");

    // TBD: Just handle secure for now
    if (status != DNSSEC_Secure) return;

    // check to make sure we built a AuthChain as part of verification
    if (!dv->ac || !dv->ac->rrset || !dv->ac->rrsig || !dv->ac->key)
    {
        LogMsg("SetTTLRRSet: ERROR!! NULL element in chain");
        FreeDNSSECVerifier(m, dv);
        return;
    }

    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).
    qname = &dv->origName;
    qtype = dv->origType;

    question.ThisQInterval = -1;
    InitializeQuestion(m, &question, dv->InterfaceID, qname, qtype, mDNSNULL, mDNSNULL);
    slot = HashSlot(&question.qname);
    cg = CacheGroupForName(m, slot, question.qnamehash, &question.qname);

    if (!cg) { LogMsg("SetTTLRRSet cg NULL"); return; }
    for (rr = cg->members; rr; rr = rr->next)
        if (SameNameRecordAnswersQuestion(&rr->resrec, &question))
        {
            rrTTL = rr->resrec.rroriginalttl;
            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.

    rv = dv->ac->rrsig;
    rrsig = (rdataRRSig *)rv->rdata;
    sigNameLen = DomainNameLength((domainname *)&rrsig->signerName);
    // pointer to signature and the length
    ptr = (mDNSu8 *)(rv->rdata + sigNameLen + RRSIG_FIXED_SIZE);
    len = rv->rdlength - RRSIG_FIXED_SIZE - sigNameLen;

    rrsigRR = mDNSNULL;
    if (rr->resrec.RecordType == kDNSRecordTypePacketNegative)
    {
        CacheRecord *ncr;
        rrTTL = 0;
        for (ncr = rr->nsec; ncr; ncr = ncr->next)
        {
            if (ncr->resrec.rrtype == kDNSType_NSEC)
            {
                rrTTL = ncr->resrec.rroriginalttl;
                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 (ncr->resrec.rrtype == kDNSType_RRSIG && SameDomainName(ncr->resrec.name, &rv->name))
            {
                RDataBody2 *rdb = (RDataBody2 *)ncr->resrec.rdata->u.data;
                rdataRRSig *sig = (rdataRRSig *)rdb->data;
                if (rv->rdlength != ncr->resrec.rdlength)
                {
                    debugdnssec("SetTTLRRSet length mismatch");
                    continue;
                }
                if (mDNSPlatformMemSame(sig, rrsig, rv->rdlength))
                {
                    rrsigTTL = ncr->resrec.rroriginalttl;
                    rrsigOrigTTL = swap32(rrsig->origTTL);
                    rrsigTimeTTL = swap32(rrsig->sigExpireTime) - swap32(rrsig->sigInceptTime);
                }
            }
            if (rrTTL && rrsigTTL) break;
        }
    }
    else
    {
        // 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, &rv->name))
            {
                RDataBody2 *rdb = (RDataBody2 *)rr->resrec.rdata->u.data;
                rdataRRSig *sig = (rdataRRSig *)rdb->data;
                if (rv->rdlength != rr->resrec.rdlength)
                {
                    debugdnssec("SetTTLRRSet length mismatch");
                    continue;
                }
                if (mDNSPlatformMemSame(sig, rrsig, rv->rdlength))
                {
                    rrsigTTL = rr->resrec.rroriginalttl;
                    rrsigOrigTTL = swap32(rrsig->origTTL);
                    rrsigTimeTTL = swap32(rrsig->sigExpireTime) - swap32(rrsig->sigInceptTime);
                    rrsigRR = rr;
                    break;
                }
            }
    }

    if (!rrTTL || !rrsigTTL || !rrsigOrigTTL || !rrsigTimeTTL)
    {
        LogMsg("SetTTLRRSet: ERROR!! Bad TTL rrtl %u, rrsigTTL %u, rrsigOrigTTL %u, rrsigTimeTTL %u for %##s (%s)",
               rrTTL, rrsigTTL, rrsigOrigTTL, rrsigTimeTTL, qname->c, DNSTypeName(qtype));
        return;
    }
    else
    {
        LogDNSSEC("SetTTLRRSet: TTL rrtl %u, rrsigTTL %u, rrsigOrigTTL %u, rrsigTimeTTL %u for %##s (%s)",
                  rrTTL, rrsigTTL, rrsigOrigTTL, rrsigTimeTTL, qname->c, DNSTypeName(qtype));
    }

    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;

    // 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;
            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));

    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_Indeterminate);
                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_Indeterminate);
                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_Insecure);
            return;
        }
    }
    else
    {
        LogDNSSEC("FinishDNSSECVerification: Could not validate the rrset %##s (%s)", dv->origName.c, DNSTypeName(dv->origType));
        dv->DVCallback(m, dv, DNSSEC_Insecure);
        return;
    }
}

mDNSexport void StartDNSSECVerification(mDNS *const m, DNSSECVerifier *dv)
{
    mDNSBool done;

    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);
}

mDNSlocal 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, 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));

    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) q->QuestionCallback(m, q, &largerec.r.resrec, QC_dnssec);
        }
        else
        {
            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;
}

mDNSlocal void DNSSECPositiveValidationCB(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status)
{
    RRVerifier *rrset;
    RRVerifier *rv;
    CacheGroup *cg;
    CacheRecord *cr;
    mDNSu32 slot, namehash;
    mDNSu16 rrtype, rrclass;
    CacheRecord *const lrr = &largerec.r;
    ResourceRecord *answer = mDNSNULL;

    LogDNSSEC("DNSSECPositiveValidationCB: called status %s", DNSSECStatusName(status));

    //
    // 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

    slot = HashSlot(&dv->origName);
    namehash = DomainNameHashValue(&dv->origName);

    cg = CacheGroupForName(m, (const mDNSu32)slot, namehash, &dv->origName);
    if (!cg)
    {
        LogDNSSEC("DNSSECPositiveValidationCB: cg NULL for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType));
        goto done;
    }
    if (!dv->ac)
    {
        // If we don't have the AuthChain, it means we could not validate the rrset. Locate the
        // original question based on dv->origName, dv->origType.
        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))
            {
                answer = &cr->resrec;
                break;
            }
        }
    }
    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");
        goto done;
    }

    DeliverDNSSECStatus(m, answer, status);
    SetTTLRRSet(m, dv, status);

done:
    FreeDNSSECVerifier(m, dv);
}

mDNSlocal void DNSSECNegativeValidationCB(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status)
{
    RRVerifier *rv;
    CacheGroup *cg;
    CacheRecord *cr;
    mDNSu32 slot, namehash;
    mDNSu16 rrtype, rrclass;
    ResourceRecord *answer = mDNSNULL;
    AuthChain *ac;

    LogDNSSEC("DNSSECNegativeValidationCB: called %s", DNSSECStatusName(status));

    // 1. Locate the negative cache record and check the cached NSEC records to see if it matches the
    //    NSECs that were valiated. If the cached NSECS 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.
    //
    slot = HashSlot(&dv->origName);
    namehash = DomainNameHashValue(&dv->origName);

    cg = CacheGroupForName(m, (const mDNSu32)slot, namehash, &dv->origName);
    if (!cg)
    {
        LogDNSSEC("DNSSECNegativeValidationCB: cg NULL for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType));
        goto done;
    }
    if (!dv->ac)
    {
        // If we don't have the AuthChain, it means we could not validate the rrset. Locate the
        // original question based on dv->origName, dv->origType.
        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))
            {
                answer = &cr->resrec;
                break;
            }
        }
    }
    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->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)
                        continue;
                    for (ac = dv->ac; ac; ac = ac->next)
                    {
                        for (rv = ac->rrset; rv; rv = rv->next)
                        {
                            if (rv->rrtype == kDNSType_NSEC && 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: setting found %s", CRDisplayString(m, ncr));
                                    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)
                {
                    if (!rv->found)
                    {
                        // We had more elements in the validated set, re-validate
                        LogDNSSEC("DNSSECNegativeValidationCB: Record %##s (%s) not found in the cache", 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");
        goto done;
    }

    DeliverDNSSECStatus(m, answer, status); 
    SetTTLRRSet(m, dv, status);

done:
    FreeDNSSECVerifier(m, dv);
}

mDNSexport void VerifySignature(mDNS *const m, DNSSECVerifier *dv, DNSQuestion *q)
{
    mDNSu32 slot = HashSlot(&q->qname);
    CacheGroup *const cg = CacheGroupForName(m, slot, q->qnamehash, &q->qname);
    CacheRecord *rr;

    LogDNSSEC("VerifySignature called for %##s (%s)", q->qname.c, DNSTypeName(q->qtype));
    if (!dv)
    {
        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, DNSSECPositiveValidationCB, 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))
            {
                dv->DVCallback = DNSSECNegativeValidationCB;
                // 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_Insecure);
                    return;
                }
                ValidateWithNSECS(m, dv, rr);
                return;
            }

            if (AddRRSetToVerifier(dv, &rr->resrec, mDNSNULL, RRVS_rr) != mStatus_NoError)
            {
                dv->DVCallback(m, dv, DNSSEC_Indeterminate);
                return;
            }
        }
    if (!dv->rrset)
    {
        LogMsg("VerifySignature: rrset mDNSNULL for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType));
        dv->DVCallback(m, dv, DNSSEC_Indeterminate);
        return;
    }
    dv->next = RRVS_rrsig;
    StartDNSSECVerification(m, dv);
}


mDNSlocal mDNSBool TrustedKeyPresent(mDNS *const m, DNSSECVerifier *dv)
{
    rdataRRSig *rrsig;
    rdataDS *ds;
    rdataDNSKey *key;
    TrustAnchor *ta;
    RRVerifier *keyv;

    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("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, 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 slot;
    mDNSu32 namehash;
    CacheGroup *cg;
    CacheRecord *cr;

    slot = HashSlot(rr->name);
    namehash = DomainNameHashValue(rr->name);
    cg = CacheGroupForName(m, slot, 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;

    LogDNSSEC("VerifySigCallback: Called with record %s", RRDisplayString(m, answer));

    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));
        dv->DVCallback(m, dv, DNSSEC_Indeterminate);
        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);
        cr = NegativeCacheRecordForRR(m, answer);
        if (cr && cr->nsec)
        {
            dv->DVCallback = DNSSECNegativeValidationCB;
            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");
        dv->DVCallback(m, dv, DNSSEC_Indeterminate);
        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;
        }
        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;
        }
        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_Indeterminate);
            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;
        }
        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);
        }
        // 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;
            }
            dv->DVCallback = DNSSECNegativeValidationCB;
            ValidateWithNSECS(m, dv, negcr);
            return;
        }
        break;
    default:
        LogDNSSEC("VerifySigCallback: ERROR!! default case rrset %##s question %##s", dv->rrset->name.c, question->qname.c);
        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);
}