summaryrefslogtreecommitdiffstats
path: root/mDNSResponder/mDNSCore/nsec3.c
blob: 43cd37a8c102bed2c108f91960f80f58fd5fac09 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2011-2013 Apple Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// ***************************************************************************
// nsec3.c: This file contains support functions to validate NSEC3 records for
// NODATA and NXDOMAIN error.
// ***************************************************************************

#include "mDNSEmbeddedAPI.h"
#include "DNSCommon.h"
#include "CryptoAlg.h"
#include "nsec3.h"
#include "nsec.h"

// Define DNSSEC_DISABLED to remove all the DNSSEC functionality
// and use the stub functions implemented later in this file.

#ifndef DNSSEC_DISABLED

typedef enum 
{
    NSEC3ClosestEncloser,
    NSEC3Covers,
    NSEC3CEProof
} NSEC3FindValues;

//#define NSEC3_DEBUG 1

#if NSEC3_DEBUG
mDNSlocal void PrintHash(mDNSu8 *digest, int digestlen, char *buffer, int buflen)
{
    int length = 0;
    for (int j = 0; j < digestlen; j++)
    {
        length += mDNS_snprintf(buffer+length, buflen-length-1, "%x", digest[j]);
    }
}
#endif

mDNSlocal mDNSBool NSEC3OptOut(CacheRecord *cr)
{
    const RDataBody2 *const rdb = (RDataBody2 *)cr->resrec.rdata->u.data;
    rdataNSEC3 *nsec3 = (rdataNSEC3 *)rdb->data;
    return (nsec3->flags & NSEC3_FLAGS_OPTOUT);
}

mDNSlocal int NSEC3SameName(const mDNSu8 *name, int namelen, const mDNSu8 *nsecName, int nsecLen)
{
    int i;

    // Note: With NSEC3, the lengths should always be same. 
    if (namelen != nsecLen)
    {
        LogMsg("NSEC3SameName: ERROR!! namelen %d, nsecLen %d", namelen, nsecLen);
        return ((namelen < nsecLen) ? -1 : 1);
    }

    for (i = 0; i < namelen; i++)
    {
        mDNSu8 ac = *name++;
        mDNSu8 bc = *nsecName++;
        if (mDNSIsUpperCase(ac)) ac += 'a' - 'A';
        if (mDNSIsUpperCase(bc)) bc += 'a' - 'A';
        if (ac != bc)
        {
            verbosedebugf("NSEC3SameName: returning ac %c, bc %c", ac, bc);
            return ((ac < bc) ? -1 : 1);
        }
    }
    return 0;
}

// Does the NSEC3 in "ncr" covers the "name" ?
// hashName is hash of the "name" and b32Name is the base32 encoded equivalent.
mDNSlocal mDNSBool NSEC3CoversName(mDNS *const m, CacheRecord *ncr, const mDNSu8 *hashName, int hashLen, const mDNSu8 *b32Name,
	int b32len)
{
    mDNSu8 *nxtName;
    int nxtLength;
    int ret, ret1, ret2;
    const mDNSu8 b32nxtname[NSEC3_MAX_B32_LEN+1];
    int b32nxtlen;

    NSEC3Parse(&ncr->resrec, mDNSNULL, &nxtLength, &nxtName, mDNSNULL, mDNSNULL);

    if (nxtLength != hashLen || ncr->resrec.name->c[0] != b32len)
        return mDNSfalse;

    // Compare the owner names and the "nxt" names.
    //
    // Owner name is base32 encoded and hence use the base32 encoded name b32name.
    // nxt name is binary and hence use the binary value in hashName. 
    ret1 = NSEC3SameName(&ncr->resrec.name->c[1], ncr->resrec.name->c[0], b32Name, b32len);
    ret2 = DNSMemCmp(nxtName, hashName, hashLen);

#if NSEC3_DEBUG
    {
        char nxtbuf1[50];
        char nxtbuf2[50];

        PrintHash(nxtName, nxtLength, nxtbuf1, sizeof(nxtbuf1));
        PrintHash((mDNSu8 *)hashName, hashLen, nxtbuf2, sizeof(nxtbuf2));
        LogMsg("NSEC3CoversName: Owner name %s, name %s", &ncr->resrec.name->c[1], b32Name);
        LogMsg("NSEC3CoversName: Nxt hash name %s, name %s", nxtbuf1, nxtbuf2);
    }
#endif

    // "name" is greater than the owner name and smaller than nxtName. This also implies
	// that nxtName > owner name implying that it is normal NSEC3.
    if (ret1 < 0 && ret2 > 0)
    {
        LogDNSSEC("NSEC3CoversName: NSEC3 %s covers %s (Normal)", CRDisplayString(m, ncr), b32Name);
        return mDNStrue;
    }
    // Need to compare the owner name and "nxt" to see if this is the last
    // NSEC3 in the zone. Only the owner name is in base32 and hence we need to
    // convert the nxtName to base32.
    b32nxtlen = baseEncode((char *)b32nxtname, sizeof(b32nxtname), nxtName, nxtLength, ENC_BASE32);
    if (!b32nxtlen)
    {
        LogDNSSEC("NSEC3CoversName: baseEncode of nxtName of %s failed", CRDisplayString(m, ncr));
        return mDNSfalse;
    }
    if (b32len != b32nxtlen)
    {
        LogDNSSEC("NSEC3CoversName: baseEncode of nxtName for %s resulted in wrong length b32nxtlen %d, b32len %d",
            CRDisplayString(m, ncr), b32len, b32nxtlen);
        return mDNSfalse;
    }
    LogDNSSEC("NSEC3CoversName: Owner name %s, b32nxtname %s, ret1 %d, ret2 %d", &ncr->resrec.name->c[1], b32nxtname, ret1, ret2);

    // If it is the last NSEC3 in the zone nxt < "name" and NSEC3SameName returns -1.
    //
    // - ret1 < 0 means "name > owner"
    // - ret2 > 0 means "name < nxt"
    // 
    // Note: We also handle the case of only NSEC3 in the zone where NSEC3SameName returns zero.
    ret = NSEC3SameName(b32nxtname, b32nxtlen, &ncr->resrec.name->c[1], ncr->resrec.name->c[0]);
    if (ret <= 0 &&
        (ret1 < 0 || ret2 > 0))
    {
        LogDNSSEC("NSEC3CoversName: NSEC3 %s covers %s (Last), ret1 %d, ret2 %d", CRDisplayString(m, ncr), b32Name, ret1, ret2);
        return mDNStrue;
    }

    return mDNSfalse;
}

// This function can be called with NSEC3ClosestEncloser, NSEC3Covers and NSEC3CEProof
//
// Passing in NSEC3ClosestEncloser means "find an exact match for the origName".
// Passing in NSEC3Covers means "find an NSEC3 that covers the origName".
//
// i.e., in both cases the nsec3 records are iterated to find the best match and returned.
// With NSEC3ClosestEncloser, as we are just looking for a name match, extra checks for
// the types being present or absent will not be checked.
//
// If NSEC3CEProof is passed, the name is tried as such first by iterating through all NSEC3s
// finding a ClosestEncloser or CloserEncloser and then one label skipped from the left and
// retried again till both the closest and closer encloser is found.
//
// ncr is the negative cache record that has the NSEC3 chain
// origName is the name for which we are trying to find the ClosestEncloser etc.
// closestEncloser and closerEncloser are the return values of the function
// ce is the closest encloser that will be returned if we find one
mDNSlocal mDNSBool NSEC3Find(mDNS *const m, NSEC3FindValues val, CacheRecord *ncr, domainname *origName, CacheRecord **closestEncloser,
	CacheRecord **closerEncloser, const domainname **ce, mDNSu16 qtype)
{
    int i;
    int labelCount = CountLabels(origName);
    CacheRecord *cr;
    rdataNSEC3 *nsec3;

    (void) qtype; // unused
    // Pick the first NSEC for the iterations, salt etc.
    for (cr = ncr->nsec; cr; cr = cr->next)
    {
        if (cr->resrec.rrtype == kDNSType_NSEC3)
        {
            const RDataBody2 *const rdb = (RDataBody2 *)cr->resrec.rdata->u.data;
            nsec3 = (rdataNSEC3 *)rdb->data;
            break;
        }
    }
    if (!cr)
    {
        LogMsg("NSEC3Find: cr NULL");
        return mDNSfalse;
    }

    // Note: The steps defined in this function are for "NSEC3CEProof". As part of NSEC3CEProof,
    // we need to find both the closestEncloser and closerEncloser which can also be found
    // by passing NSEC3ClosestEncloser and NSEC3Covers respectively.
    //
    // Section 8.3 of RFC 5155.
    // 1.  Set SNAME=QNAME.  Clear the flag.
    //
    // closerEncloser is the "flag". "name" below is SNAME.

    if (closestEncloser)
    {
        *ce = mDNSNULL;
        *closestEncloser = mDNSNULL;
    }
    if (closerEncloser)
        *closerEncloser = mDNSNULL;

    // If we are looking for a closestEncloser or a covering NSEC3, we don't have
    // to truncate the name. For the give name, try to find the closest or closer
    // encloser.
    if (val != NSEC3CEProof)
    {
        labelCount = 0;
    }

    for (i = 0; i < labelCount + 1; i++)
    { 
        int hlen;
        const mDNSu8 hashName[NSEC3_MAX_HASH_LEN];
        const domainname *name;
        const mDNSu8 b32Name[NSEC3_MAX_B32_LEN+1];
        int b32len;

        name = SkipLeadingLabels(origName, i);
        if (!NSEC3HashName(name, nsec3, mDNSNULL, 0, hashName, &hlen))
        {
            LogMsg("NSEC3Find: NSEC3HashName failed for %##s", name->c);
            continue;
        }

        b32len = baseEncode((char *)b32Name, sizeof(b32Name), (mDNSu8 *)hashName, hlen, ENC_BASE32);
        if (!b32len)
        {
            LogMsg("NSEC3Find: baseEncode of name %##s failed", name->c);
            continue;
        }


        for (cr = ncr->nsec; cr; cr = cr->next)
        {
            const domainname *nsecZone;
            int result, subdomain;

            if (cr->resrec.rrtype != kDNSType_NSEC3)
                continue;

            nsecZone = SkipLeadingLabels(cr->resrec.name, 1);
            if (!nsecZone)
            {
                LogMsg("NSEC3Find: SkipLeadingLabel failed for %s, current name %##s",
                    CRDisplayString(m, cr), name->c);
                continue;
            }

            // NSEC3 owner names are formed by hashing the owner name and then appending
            // the zone name to it. If we skip the first label, the rest should be
            // the zone name. See whether it is the subdomain of the name we are looking
            // for. 
            result = DNSSECCanonicalOrder(origName, nsecZone, &subdomain);
            
            // The check can't be a strict subdomain check. When NSEC3ClosestEncloser is
            // passed in, there can be an exact match. If it is a subdomain or an exact
            // match, we should continue with the proof.
            if (!(subdomain || !result))
            {
                LogMsg("NSEC3Find: NSEC3 %s not a subdomain of %##s, result %d", CRDisplayString(m, cr),
                    origName->c, result);
                continue;
            }

            // 2.1) If there is no NSEC3 RR in the response that matches SNAME
            // (i.e., an NSEC3 RR whose owner name is the same as the hash of
            // SNAME, prepended as a single label to the zone name), clear
            // the flag.
            //
            // Note: We don't try to determine the actual zone name. We know that
            // the labels following the hash (nsecZone) is the ancestor and we don't
            // know where the zone cut is. Hence, we verify just the hash to be
            // the same.

            if (val == NSEC3ClosestEncloser || val == NSEC3CEProof)
            {
                if (!NSEC3SameName(&cr->resrec.name->c[1], cr->resrec.name->c[0], (const mDNSu8 *)b32Name, b32len))
                {
                    int bmaplen;
                    mDNSu8 *bmap;

                    // For NSEC3ClosestEncloser, we are finding an exact match and
                    // "type" specific checks should be done by the caller.
                    if (val != NSEC3ClosestEncloser)
                    {
                        // DNAME bit must not be set and NS bit may be set only if SOA bit is set
                        NSEC3Parse(&cr->resrec, mDNSNULL, mDNSNULL, mDNSNULL, &bmaplen, &bmap);
                        if (BitmapTypeCheck(bmap, bmaplen, kDNSType_DNAME))
                        {
                            LogDNSSEC("NSEC3Find: DNAME bit set in %s, ignoring", CRDisplayString(m, cr));
                            return mDNSfalse;
                        }
                        // This is the closest encloser and should come from the right zone.
                        if (BitmapTypeCheck(bmap, bmaplen, kDNSType_NS) &&
                            !BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA))
                        {
                            LogDNSSEC("NSEC3Find: NS bit set without SOA bit in %s, ignoring", CRDisplayString(m, cr));
                            return mDNSfalse;
                        }
                    }

                    LogDNSSEC("NSEC3Find: ClosestEncloser %s found for name %##s", CRDisplayString(m, cr), name->c);
                    if (closestEncloser)
                    {
                        *ce = name;
                        *closestEncloser = cr;
                    }
                    if (val == NSEC3ClosestEncloser)
                        return mDNStrue;
                    else
                        break;
                }
            }

            if ((val == NSEC3Covers || val == NSEC3CEProof) && (!closerEncloser || !(*closerEncloser)))
            {
                if (NSEC3CoversName(m, cr, hashName, hlen, b32Name, b32len))
                {
                    // 2.2) If there is an NSEC3 RR in the response that covers SNAME, set the flag.
                    if (closerEncloser)
                        *closerEncloser = cr;
                    if (val == NSEC3Covers)
                        return mDNStrue;
                    else
                        break;
                }
            }
        }
        // 2.3) If there is a matching NSEC3 RR in the response and the flag
        // was set, then the proof is complete, and SNAME is the closest
        // encloser.
        if (val == NSEC3CEProof && closestEncloser && *closestEncloser)
        {
            if (closerEncloser && *closerEncloser)
            {
                LogDNSSEC("NSEC3Find: Found closest and closer encloser");
                return mDNStrue;
            }
            else
            {
                // 2.4) If there is a matching NSEC3 RR in the response, but the flag
                // is not set, then the response is bogus.
                //
                // Note: We don't have to wait till we finish trying all the names. If the matchName
                // happens, we found the closest encloser which means we should have found the closer
                // encloser before.

                LogDNSSEC("NSEC3Find: Found closest, but not closer encloser");
                return mDNSfalse;
            }
        }
        // 3.  Truncate SNAME by one label from the left, go to step 2.
    }
    LogDNSSEC("NSEC3Find: Cannot find name %##s (%s)", origName->c, DNSTypeName(qtype));
    return mDNSfalse;
}

mDNSlocal mDNSBool NSEC3ClosestEncloserProof(mDNS *const m, CacheRecord *ncr, domainname *name, CacheRecord **closestEncloser, CacheRecord **closerEncloser,
	const domainname **ce, mDNSu16 qtype)
{
    if (!NSEC3Find(m, NSEC3CEProof, ncr, name, closestEncloser, closerEncloser, ce, qtype))
    {
        LogDNSSEC("NSEC3ClosestEncloserProof: ERROR!! Cannot do closest encloser proof");
        return mDNSfalse;
    }

    // Note: It is possible that closestEncloser and closerEncloser are the same.
    if (!closestEncloser || !closerEncloser || !ce)
    {
        LogMsg("NSEC3ClosestEncloserProof: ClosestEncloser %p or CloserEncloser %p ce %p, something is NULL", closestEncloser, closerEncloser, ce);
        return mDNSfalse;
    }

    // If the name exists, we should not have gotten the name error
    if (SameDomainName((*ce), name))
    {
        LogMsg("NSEC3ClosestEncloserProof: ClosestEncloser %s same as origName %##s", CRDisplayString(m, *closestEncloser),
            (*ce)->c);
        return mDNSfalse;
    }
    return mDNStrue;
}

mDNSlocal mDNSBool VerifyNSEC3(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr, CacheRecord *closestEncloser,
    CacheRecord *closerEncloser, CacheRecord *wildcard, DNSSECVerifierCallback callback)
{
    mStatus status;
    RRVerifier *r;

    // We have three NSEC3s. If any of two are same, we should just prove one of them.
    // This is just not an optimization; DNSSECNegativeValidationCB does not handle
    // identical NSEC3s very well.

    if (closestEncloser == closerEncloser)
        closestEncloser = mDNSNULL;
    if (closerEncloser == wildcard)
        closerEncloser = mDNSNULL;
    if (closestEncloser == wildcard)
        closestEncloser = mDNSNULL;

    dv->pendingNSEC = mDNSNULL;
    if (closestEncloser)
    {
        r = AllocateRRVerifier(&closestEncloser->resrec, &status);
        if (!r)
            return mDNSfalse;
        r->next = dv->pendingNSEC;
        dv->pendingNSEC = r;
    }
    if (closerEncloser)
    {
        r = AllocateRRVerifier(&closerEncloser->resrec, &status);
        if (!r)
            return mDNSfalse;
        r->next = dv->pendingNSEC;
        dv->pendingNSEC = r;
    }
    if (wildcard)
    {
        r = AllocateRRVerifier(&wildcard->resrec, &status);
        if (!r)
            return mDNSfalse;
        r->next = dv->pendingNSEC;
        dv->pendingNSEC = r;
    }
    if (!dv->pendingNSEC)
    {
        LogMsg("VerifyNSEC3: ERROR!! pending NSEC null");
        return mDNSfalse;
    }
    r = dv->pendingNSEC;
    dv->pendingNSEC = r->next;
    r->next = mDNSNULL;

    LogDNSSEC("VerifyNSEC3: Verifying %##s (%s)", r->name.c, DNSTypeName(r->rrtype));
    if (!dv->pendingNSEC)
        VerifyNSEC(m, mDNSNULL, r, dv, ncr, mDNSNULL);
    else
        VerifyNSEC(m, mDNSNULL, r, dv, ncr, callback);
    return mDNStrue;
}

mDNSexport void NSEC3NameErrorProof(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr)
{
    CacheRecord *closerEncloser;
    CacheRecord *closestEncloser;
    CacheRecord *wildcard;
    const domainname *ce = mDNSNULL;
    domainname wild;

    if (!NSEC3ClosestEncloserProof(m, ncr, &dv->q.qname, &closestEncloser, &closerEncloser, &ce, dv->q.qtype))
    {
        goto error;
    }
    LogDNSSEC("NSEC3NameErrorProof: ClosestEncloser %s, ce %##s", CRDisplayString(m, closestEncloser), ce->c);
    LogDNSSEC("NSEC3NameErrorProof: CloserEncloser %s", CRDisplayString(m, closerEncloser));

    // *.closestEncloser should be covered by some nsec3 which would then prove
    // that the wildcard does not exist
    wild.c[0] = 1;
    wild.c[1] = '*';
    wild.c[2] = 0;
    if (!AppendDomainName(&wild, ce))
    {
        LogMsg("NSEC3NameErrorProof: Can't append domainname to closest encloser name %##s", ce->c);
        goto error;
    }
    if (!NSEC3Find(m, NSEC3Covers, ncr, &wild, mDNSNULL, &wildcard, mDNSNULL, dv->q.qtype))
    {
        LogMsg("NSEC3NameErrorProof: Cannot find encloser for wildcard");
        goto error;
    }
    else
    {
        LogDNSSEC("NSEC3NameErrorProof: Wildcard %##s covered by %s", wild.c, CRDisplayString(m, wildcard));
        if (wildcard == closestEncloser)
        {
            LogDNSSEC("NSEC3NameErrorProof: ClosestEncloser matching Wildcard %s", CRDisplayString(m, wildcard));
        }
    }
    if (NSEC3OptOut(closerEncloser))
    {
        dv->flags |= NSEC3_OPT_OUT;
    }
    if (!VerifyNSEC3(m, dv, ncr, closestEncloser, closerEncloser, wildcard, NameErrorNSECCallback))
        goto error;
    else
        return;

error:
    dv->DVCallback(m, dv, DNSSEC_Bogus);
}

// Section 8.5, 8.6 of RFC 5155 first paragraph
mDNSlocal mDNSBool NSEC3NoDataError(mDNS *const m, CacheRecord *ncr, domainname *name, mDNSu16 qtype, CacheRecord **closestEncloser)
{
    const domainname *ce = mDNSNULL;

    *closestEncloser = mDNSNULL;
    // Note: This also covers ENT in which case the bitmap is empty
    if (NSEC3Find(m, NSEC3ClosestEncloser, ncr, name, closestEncloser, mDNSNULL, &ce, qtype))
    {
        int bmaplen;
        mDNSu8 *bmap;
        mDNSBool ns, soa;

        NSEC3Parse(&(*closestEncloser)->resrec, mDNSNULL, mDNSNULL, mDNSNULL, &bmaplen, &bmap);
        if (BitmapTypeCheck(bmap, bmaplen, qtype) || BitmapTypeCheck(bmap, bmaplen, kDNSType_CNAME))
        {
            LogMsg("NSEC3NoDataError: qtype %s exists in %s", DNSTypeName(qtype), CRDisplayString(m, *closestEncloser));
            return mDNSfalse;
        }
        ns = BitmapTypeCheck(bmap, bmaplen, kDNSType_NS);
        soa = BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA);
        if (qtype != kDNSType_DS)
        {
            // For non-DS type questions, we don't want to use the parent side records to
            // answer it
            if (ns && !soa)
            {
                LogDNSSEC("NSEC3NoDataError: Parent side NSEC %s, can't use for child qname %##s (%s)",
                    CRDisplayString(m, *closestEncloser), name->c, DNSTypeName(qtype));
                return mDNSfalse;
            }
        }
        else
        {
            if (soa)
            {
                LogDNSSEC("NSEC3NoDataError: Child side NSEC %s, can't use for parent qname %##s (%s)",
                    CRDisplayString(m, *closestEncloser), name->c, DNSTypeName(qtype));
                return mDNSfalse;
            }
        }
        LogDNSSEC("NSEC3NoDataError: Name -%##s- exists, but qtype %s does not exist in %s", name->c, DNSTypeName(qtype), CRDisplayString(m, *closestEncloser));
        return mDNStrue;
    }
    return mDNSfalse;
}

mDNSexport void NSEC3NoDataProof(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr)
{
    CacheRecord *closerEncloser = mDNSNULL;
    CacheRecord *closestEncloser = mDNSNULL;
    CacheRecord *wildcard = mDNSNULL;
    const domainname *ce = mDNSNULL;
    domainname wild;

    // Section 8.5, 8.6 of RFC 5155
    if (NSEC3NoDataError(m, ncr, &dv->q.qname, dv->q.qtype, &closestEncloser))
    {
        goto verify;
    }
    // Section 8.6, 8.7: if we can't find the NSEC3 RR, verify the closest encloser proof
    // for QNAME and the "next closer" should have the opt out
    if (!NSEC3ClosestEncloserProof(m, ncr, &dv->q.qname, &closestEncloser, &closerEncloser, &ce, dv->q.qtype))
    {
        goto error;
    }

    // Section 8.7: find a matching NSEC3 for *.closestEncloser
    wild.c[0] = 1;
    wild.c[1] = '*';
    wild.c[2] = 0;
    if (!AppendDomainName(&wild, ce))
    {
        LogMsg("NSEC3NameErrorProof: Can't append domainname to closest encloser name %##s", ce->c);
        goto error;
    }
    if (!NSEC3Find(m, NSEC3ClosestEncloser, ncr, &wild, &wildcard, mDNSNULL, &ce, dv->q.qtype))
    {
        // Not a wild card case. Section 8.6 second para applies.
        LogDNSSEC("NSEC3NoDataProof: Cannot find encloser for wildcard, perhaps not a wildcard case");
        if (!NSEC3OptOut(closerEncloser))
        {
            LogDNSSEC("NSEC3DataProof: opt out not set for %##s (%s), bogus", dv->q.qname.c, DNSTypeName(dv->q.qtype));
            goto error;
        }
        LogDNSSEC("NSEC3DataProof: opt out set, proof complete for %##s (%s)", dv->q.qname.c, DNSTypeName(dv->q.qtype));
        dv->flags |= NSEC3_OPT_OUT;
    }
    else
    {
        int bmaplen;
        mDNSu8 *bmap;
        NSEC3Parse(&wildcard->resrec, mDNSNULL, mDNSNULL, mDNSNULL, &bmaplen, &bmap);
        if (BitmapTypeCheck(bmap, bmaplen, dv->q.qtype) || BitmapTypeCheck(bmap, bmaplen, kDNSType_CNAME))
        {
            LogDNSSEC("NSEC3NoDataProof: qtype %s exists in %s", DNSTypeName(dv->q.qtype), CRDisplayString(m, wildcard));
            goto error;
        }
        if (dv->q.qtype == kDNSType_DS && BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA))
        {
            LogDNSSEC("NSEC3NoDataProof: Child side wildcard NSEC3 %s, can't use for parent qname %##s (%s)",
                CRDisplayString(m, wildcard), dv->q.qname.c, DNSTypeName(dv->q.qtype));
            goto error;
        }
        else if (dv->q.qtype != kDNSType_DS && !BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA) &&
            BitmapTypeCheck(bmap, bmaplen, kDNSType_NS))
        {
            // Don't use the parent side record for this
            LogDNSSEC("NSEC3NoDataProof: Parent side wildcard NSEC3 %s, can't use for child qname %##s (%s)",
                CRDisplayString(m, wildcard), dv->q.qname.c, DNSTypeName(dv->q.qtype));
            goto error;
        }
        LogDNSSEC("NSEC3NoDataProof: Wildcard %##s matched by %s", wild.c, CRDisplayString(m, wildcard));
    }
verify:

    if (!VerifyNSEC3(m, dv, ncr, closestEncloser, closerEncloser, wildcard, NoDataNSECCallback))
        goto error;
    else
        return;
error:
    dv->DVCallback(m, dv, DNSSEC_Bogus);
}

mDNSexport mDNSBool NSEC3WildcardAnswerProof(mDNS *const m, CacheRecord *ncr, DNSSECVerifier *dv)
{
    int skip;
    const domainname *nc;
    CacheRecord *closerEncloser;

    (void) m;

    // Find the next closer name and prove that it is covered by the NSEC3
    skip = CountLabels(&dv->origName) - CountLabels(dv->wildcardName) - 1;
    if (skip)
        nc = SkipLeadingLabels(&dv->origName, skip);
    else
        nc = &dv->origName;

    LogDNSSEC("NSEC3WildcardAnswerProof: wildcard name %##s", nc->c);

    if (!NSEC3Find(m, NSEC3Covers, ncr, (domainname *)nc, mDNSNULL, &closerEncloser, mDNSNULL, dv->q.qtype))
    {
        LogMsg("NSEC3WildcardAnswerProof: Cannot find closer encloser");
        return mDNSfalse;
    }
    if (!closerEncloser)
    {
        LogMsg("NSEC3WildcardAnswerProof: closerEncloser NULL");
        return mDNSfalse;
    }
    if (NSEC3OptOut(closerEncloser))
    {
        dv->flags |= NSEC3_OPT_OUT;
    }
    // NSEC3 Verification is done by the caller
    return mDNStrue;
}

mDNSexport CacheRecord *NSEC3RecordIsDelegation(mDNS *const m, domainname *name, mDNSu16 qtype)
{
    CacheGroup *cg;
    CacheRecord *cr;
    CacheRecord *ncr;
    mDNSu32 namehash;

    namehash = DomainNameHashValue(name);

    cg = CacheGroupForName(m, namehash, name);
    if (!cg)
    {
        LogDNSSEC("NSEC3RecordForName: cg NULL for %##s", name);
        return mDNSNULL;
    }
    for (ncr = cg->members; ncr; ncr = ncr->next)
    {
        if (ncr->resrec.RecordType != kDNSRecordTypePacketNegative ||
            ncr->resrec.rrtype != qtype)
        {
            continue;
        }
        for (cr = ncr->nsec; cr; cr = cr->next)
        {
            int hlen, b32len;
            const mDNSu8 hashName[NSEC3_MAX_HASH_LEN];
            const mDNSu8 b32Name[NSEC3_MAX_B32_LEN+1];
            const RDataBody2 *const rdb = (RDataBody2 *)cr->resrec.rdata->u.data;
            rdataNSEC3 *nsec3;

            if (cr->resrec.rrtype != kDNSType_NSEC3)
                continue;

            nsec3 = (rdataNSEC3 *)rdb->data;

            if (!NSEC3HashName(name, nsec3, mDNSNULL, 0, hashName, &hlen))
            {
                LogMsg("NSEC3RecordIsDelegation: NSEC3HashName failed for %##s", name->c);
                return mDNSNULL;
            }

            b32len = baseEncode((char *)b32Name, sizeof(b32Name), (mDNSu8 *)hashName, hlen, ENC_BASE32);
            if (!b32len)
            {
                LogMsg("NSEC3RecordIsDelegation: baseEncode of name %##s failed", name->c);
                return mDNSNULL;
            }
            // Section 2.3 of RFC 4035 states that:
            //
            // Each owner name in the zone that has authoritative data or a delegation point NS RRset MUST
            // have an NSEC resource record. 
            //
            // This applies to NSEC3 record. So, if we have an NSEC3 record matching the question name with the
            // NS bit set, then this is a delegation.
            //
            if (!NSEC3SameName(&cr->resrec.name->c[1], cr->resrec.name->c[0], (const mDNSu8 *)b32Name, b32len))
            {
                int bmaplen;
                mDNSu8 *bmap;
                
                LogDNSSEC("NSEC3RecordIsDelegation: CacheRecord %s matches name %##s, b32name %s", CRDisplayString(m, cr), name->c, b32Name);
                NSEC3Parse(&cr->resrec, mDNSNULL, mDNSNULL, mDNSNULL, &bmaplen, &bmap);

                // See the Insecure Delegation Proof section in dnssec-bis: DS bit and SOA bit
                // should be absent
                if (BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA) ||
                    BitmapTypeCheck(bmap, bmaplen, kDNSType_DS))
                {
                    LogDNSSEC("NSEC3RecordIsDelegation: CacheRecord %s has DS or SOA bit set, ignoring", CRDisplayString(m, cr));
                    return mDNSNULL;
                }
                if (BitmapTypeCheck(bmap, bmaplen, kDNSType_NS))
                    return cr;
                else
                    return mDNSNULL;
            }
            // If opt-out is not set, then it does not cover any delegations
            if (!(nsec3->flags & NSEC3_FLAGS_OPTOUT))
                continue;
            // Opt-out allows insecure delegations to exist without the NSEC3 RR at the
            // hashed owner name (see RFC 5155 section 6.0).
            if (NSEC3CoversName(m, cr, hashName, hlen, b32Name, b32len))
            {
                LogDNSSEC("NSEC3RecordIsDelegation: CacheRecord %s covers name %##s with optout", CRDisplayString(m, cr), name->c);
                return cr;
            }
        }
    }
    return mDNSNULL;
}

#else // !DNSSEC_DISABLED

#endif // !DNSSEC_DISABLED