a4316dd8ee8ae7d518895529d944dfa53924ec69
[captive-validator.git] / src / com / versign / tat / dnssec / NSEC3ValUtils.java
1 /*
2  * $Id$
3  * 
4  * Copyright (c) 2006 VeriSign. All rights reserved.
5  * 
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  * 
9  * 1. Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer. 2. Redistributions in
11  * binary form must reproduce the above copyright notice, this list of
12  * conditions and the following disclaimer in the documentation and/or other
13  * materials provided with the distribution. 3. The name of the author may not
14  * be used to endorse or promote products derived from this software without
15  * specific prior written permission.
16  * 
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
20  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
22  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *  
28  */
29
30 package com.versign.tat.dnssec;
31
32 import java.security.NoSuchAlgorithmException;
33 import java.util.*;
34
35 import org.xbill.DNS.*;
36 import org.xbill.DNS.utils.base32;
37
38 import com.versign.tat.dnssec.SignUtils.ByteArrayComparator;
39
40 public class NSEC3ValUtils {
41
42     // FIXME: should probably refactor to handle different NSEC3 parameters more
43     // efficiently.
44     // Given a list of NSEC3 RRs, they should be grouped according to
45     // parameters. The idea is to hash and compare for each group independently,
46     // instead of having to skip NSEC3 RRs with the wrong parameters.
47
48     private static Name asterisk_label = Name.fromConstantString("*");
49
50     /**
51      * This is a class to encapsulate a unique set of NSEC3 parameters:
52      * algorithm, iterations, and salt.
53      */
54     private static class NSEC3Parameters {
55         public byte   alg;
56         public byte[] salt;
57         public int    iterations;
58
59         public NSEC3Parameters(NSEC3Record r) {
60             alg = r.getHashAlgorithm();
61             salt = r.getSalt();
62             iterations = r.getIterations();
63         }
64
65         public boolean match(NSEC3Record r, ByteArrayComparator bac) {
66             if (r.getHashAlgorithm() != alg) return false;
67             if (r.getIterations() != iterations) return false;
68
69             if (salt == null && r.getSalt() != null) return false;
70
71             if (bac == null) bac = new ByteArrayComparator();
72             return bac.compare(r.getSalt(), salt) == 0;
73         }
74     }
75
76     /**
77      * This is just a simple class to encapsulate the response to a closest
78      * encloser proof.
79      */
80     private static class CEResponse {
81         public Name        closestEncloser;
82         public NSEC3Record ce_nsec3;
83         public NSEC3Record nc_nsec3;
84
85         public CEResponse(Name ce, NSEC3Record nsec3) {
86             this.closestEncloser = ce;
87             this.ce_nsec3 = nsec3;
88         }
89     }
90
91     public static boolean supportsHashAlgorithm(int alg) {
92         if (alg == NSEC3Record.SHA1_DIGEST_ID) return true;
93         return false;
94     }
95
96     public static void stripUnknownAlgNSEC3s(List<NSEC3Record> nsec3s) {
97         if (nsec3s == null) return;
98         for (ListIterator<NSEC3Record> i = nsec3s.listIterator(); i.hasNext();) {
99             NSEC3Record nsec3 = i.next();
100             if (!supportsHashAlgorithm(nsec3.getHashAlgorithm())) {
101                 i.remove();
102             }
103         }
104     }
105
106     /**
107      * Given a list of NSEC3Records that are part of a message, determine the
108      * NSEC3 parameters (hash algorithm, iterations, and salt) present. If there
109      * is more than one distinct grouping, return null;
110      * 
111      * @param nsec3s
112      *            A list of NSEC3Record object.
113      * @return A set containing a number of objects (NSEC3Parameter objects)
114      *         that correspond to each distinct set of parameters, or null if
115      *         the nsec3s list was empty.
116      */
117     public static NSEC3Parameters nsec3Parameters(List<NSEC3Record> nsec3s) {
118         if (nsec3s == null || nsec3s.size() == 0) return null;
119
120         NSEC3Parameters params = new NSEC3Parameters(
121                 (NSEC3Record) nsec3s.get(0));
122         ByteArrayComparator bac = new ByteArrayComparator();
123
124         for (NSEC3Record nsec3 : nsec3s) {
125             if (!params.match(nsec3, bac)) return null;
126         }
127
128         return params;
129     }
130
131
132     /**
133      * Given a hash and an a zone name, construct an NSEC3 ownername.
134      * 
135      * @param hash
136      *            The hash of an original name.
137      * @param zonename
138      *            The zone to use in constructing the NSEC3 name.
139      * @return The NSEC3 name.
140      */
141     private static Name hashName(byte[] hash, Name zonename) {
142         try {
143             return new Name(base32.toString(hash).toLowerCase(), zonename);
144         } catch (TextParseException e) {
145             // Note, this should never happen.
146             return null;
147         }
148     }
149
150     /**
151      * Given a set of NSEC3 parameters, hash a name.
152      * 
153      * @param name
154      *            The name to hash.
155      * @param params
156      *            The parameters to hash with.
157      * @return The hash.
158      */
159     private static byte[] hash(Name name, NSEC3Parameters params) {
160         try {
161             return NSEC3Record.hash(name, params.alg, params.iterations,
162                                     params.salt);
163         } catch (NoSuchAlgorithmException e) {
164             // st_log.debug("Did not recognize hash algorithm: " + params.alg);
165             return null;
166         }
167     }
168
169     /**
170      * Given the name of a closest encloser, return the name *.closest_encloser.
171      * 
172      * @param closestEncloser
173      *            The name to start with.
174      * @return The wildcard name.
175      */
176     private static Name ceWildcard(Name closestEncloser) {
177         try {
178             Name wc = Name.concatenate(asterisk_label, closestEncloser);
179             return wc;
180         } catch (NameTooLongException e) {
181             return null;
182         }
183     }
184
185     /**
186      * Given a qname and its proven closest encloser, calculate the "next
187      * closest" name. Basically, this is the name that is one label longer than
188      * the closest encloser that is still a subdomain of qname.
189      * 
190      * @param qname
191      *            The qname.
192      * @param closestEncloser
193      *            The closest encloser name.
194      * @return The next closer name.
195      */
196     private static Name nextClosest(Name qname, Name closestEncloser) {
197         int strip = qname.labels() - closestEncloser.labels() - 1;
198         return (strip > 0) ? new Name(qname, strip) : qname;
199     }
200
201     /**
202      * Find the NSEC3Record that matches a hash of a name.
203      * 
204      * @param hash
205      *            The pre-calculated hash of a name.
206      * @param zonename
207      *            The name of the zone that the NSEC3s are from.
208      * @param nsec3s
209      *            A list of NSEC3Records from a given message.
210      * @param params
211      *            The parameters used for calculating the hash.
212      * @param bac
213      *            An already allocated ByteArrayComparator, for reuse. This may
214      *            be null.
215      * 
216      * @return The matching NSEC3Record, if one is present.
217      */
218     private static NSEC3Record findMatchingNSEC3(byte[] hash, Name zonename,
219                                                  List<NSEC3Record> nsec3s,
220                                                  NSEC3Parameters params,
221                                                  ByteArrayComparator bac) {
222         Name n = hashName(hash, zonename);
223
224         for (NSEC3Record nsec3 : nsec3s) {
225             // Skip nsec3 records that are using different parameters.
226             if (!params.match(nsec3, bac)) continue;
227             if (n.equals(nsec3.getName())) return nsec3;
228         }
229         return null;
230     }
231
232     /**
233      * Given a hash and a candidate NSEC3Record, determine if that NSEC3Record
234      * covers the hash. Covers specifically means that the hash is in between
235      * the owner and next hashes and does not equal either.
236      * 
237      * @param nsec3
238      *            The candidate NSEC3Record.
239      * @param hash
240      *            The precalculated hash.
241      * @param bac
242      *            An already allocated comparator. This may be null.
243      * @return True if the NSEC3Record covers the hash.
244      */
245     private static boolean nsec3Covers(NSEC3Record nsec3, byte[] hash,
246                                        ByteArrayComparator bac) {
247         byte[] owner = nsec3.getOwner();
248         byte[] next = nsec3.getNext();
249
250         // This is the "normal case: owner < next and owner < hash < next
251         if (bac.compare(owner, hash) < 0 && bac.compare(hash, next) < 0)
252             return true;
253
254         // this is the end of zone case: next < owner && hash > owner || hash <
255         // next
256         if (bac.compare(next, owner) <= 0
257             && (bac.compare(hash, next) < 0 || bac.compare(owner, hash) < 0))
258             return true;
259
260         // Otherwise, the NSEC3 does not cover the hash.
261         return false;
262     }
263
264     /**
265      * Given a pre-hashed name, find a covering NSEC3 from among a list of
266      * NSEC3s.
267      * 
268      * @param hash
269      *            The hash to consider.
270      * @param zonename
271      *            The name of the zone.
272      * @param nsec3s
273      *            The list of NSEC3s present in a message.
274      * @param params
275      *            The NSEC3 parameters used to generate the hash -- NSEC3s that
276      *            do not use those parameters will be skipped.
277      * 
278      * @return A covering NSEC3 if one is present, null otherwise.
279      */
280     private static NSEC3Record findCoveringNSEC3(byte[] hash, Name zonename,
281                                                  List<NSEC3Record> nsec3s,
282                                                  NSEC3Parameters params,
283                                                  ByteArrayComparator bac) {
284         ByteArrayComparator comparator = new ByteArrayComparator();
285
286         for (NSEC3Record nsec3 : nsec3s) {
287             if (!params.match(nsec3, bac)) continue;
288             if (nsec3Covers(nsec3, hash, comparator)) return nsec3;
289         }
290
291         return null;
292     }
293
294     /**
295      * Given a name and a list of NSEC3s, find the candidate closest encloser.
296      * This will be the first ancestor of 'name' (including itself) to have a
297      * matching NSEC3 RR.
298      * 
299      * @param name
300      *            The name the start with.
301      * @param zonename
302      *            The name of the zone that the NSEC3s came from.
303      * @param nsec3s
304      *            The list of NSEC3s.
305      * @param nsec3params
306      *            The NSEC3 parameters.
307      * @param bac
308      *            A pre-allocated comparator. May be null.
309      * 
310      * @return A CEResponse containing the closest encloser name and the NSEC3
311      *         RR that matched it, or null if there wasn't one.
312      */
313     private static CEResponse findClosestEncloser(Name name, Name zonename,
314                                                   List<NSEC3Record> nsec3s,
315                                                   NSEC3Parameters params,
316                                                   ByteArrayComparator bac) {
317         Name n = name;
318
319         NSEC3Record nsec3;
320
321         // This scans from longest name to shortest, so the first match we find
322         // is
323         // the only viable candidate.
324         // FIXME: modify so that the NSEC3 matching the zone apex need not be
325         // present.
326         while (n.labels() >= zonename.labels()) {
327             nsec3 = findMatchingNSEC3(hash(n, params), zonename, nsec3s,
328                                       params, bac);
329             if (nsec3 != null) return new CEResponse(n, nsec3);
330             n = new Name(n, 1);
331         }
332
333         return null;
334     }
335
336     /**
337      * Given a List of nsec3 RRs, find and prove the closest encloser to qname.
338      * 
339      * @param qname
340      *            The qname in question.
341      * @param zonename
342      *            The name of the zone that the NSEC3 RRs come from.
343      * @param nsec3s
344      *            The list of NSEC3s found the this response (already verified).
345      * @param params
346      *            The NSEC3 parameters found in the response.
347      * @param bac
348      *            A pre-allocated comparator. May be null.
349      * @param proveDoesNotExist
350      *            If true, then if the closest encloser turns out to be qname,
351      *            then null is returned.
352      * @return null if the proof isn't completed. Otherwise, return a CEResponse
353      *         object which contains the closest encloser name and the NSEC3
354      *         that matches it.
355      */
356     private static CEResponse proveClosestEncloser(Name qname, Name zonename,
357                                                    List<NSEC3Record> nsec3s,
358                                                    NSEC3Parameters params,
359                                                    ByteArrayComparator bac,
360                                                    boolean proveDoesNotExist) {
361         CEResponse candidate = findClosestEncloser(qname, zonename, nsec3s,
362                                                    params, bac);
363
364         if (candidate == null) {
365             // st_log.debug("proveClosestEncloser: could not find a "
366             // + "candidate for the closest encloser.");
367             return null;
368         }
369
370         if (candidate.closestEncloser.equals(qname)) {
371             if (proveDoesNotExist) {
372                 // st_log.debug("proveClosestEncloser: proved that qname existed!");
373                 return null;
374             }
375             // otherwise, we need to nothing else to prove that qname is its own
376             // closest encloser.
377             return candidate;
378         }
379
380         // If the closest encloser is actually a delegation, then the response
381         // should have been a referral. If it is a DNAME, then it should have
382         // been
383         // a DNAME response.
384         if (candidate.ce_nsec3.hasType(Type.NS)
385             && !candidate.ce_nsec3.hasType(Type.SOA)) {
386             // st_log.debug("proveClosestEncloser: closest encloser "
387             // + "was a delegation!");
388             return null;
389         }
390         if (candidate.ce_nsec3.hasType(Type.DNAME)) {
391             // st_log.debug("proveClosestEncloser: closest encloser was a DNAME!");
392             return null;
393         }
394
395         // Otherwise, we need to show that the next closer name is covered.
396         Name nextClosest = nextClosest(qname, candidate.closestEncloser);
397
398         byte[] nc_hash = hash(nextClosest, params);
399         candidate.nc_nsec3 = findCoveringNSEC3(nc_hash, zonename, nsec3s,
400                                                params, bac);
401         if (candidate.nc_nsec3 == null) {
402             // st_log.debug("Could not find proof that the "
403             // + "closest encloser was the closest encloser");
404             return null;
405         }
406
407         return candidate;
408     }
409
410     private static int maxIterations(int baseAlg, int keysize) {
411         switch (baseAlg) {
412         case DnsSecVerifier.RSA:
413             if (keysize == 0) return 2500; // the max at 4096
414             if (keysize > 2048) return 2500;
415             if (keysize > 1024) return 500;
416             if (keysize > 0) return 150;
417             break;
418         case DnsSecVerifier.DSA:
419             if (keysize == 0) return 5000; // the max at 2048;
420             if (keysize > 1024) return 5000;
421             if (keysize > 0) return 1500;
422             break;
423         }
424         return -1;
425     }
426
427     @SuppressWarnings("unchecked")
428     private static boolean validIterations(NSEC3Parameters nsec3params,
429                                            RRset dnskey_rrset,
430                                            DnsSecVerifier verifier) {
431         // for now, we return the maximum iterations based simply on the key
432         // algorithms that may have been used to sign the NSEC3 RRsets.
433
434         int max_iterations = 0;
435         for (Iterator i = dnskey_rrset.rrs(); i.hasNext();) {
436             DNSKEYRecord dnskey = (DNSKEYRecord) i.next();
437             int baseAlg = verifier.baseAlgorithm(dnskey.getAlgorithm());
438             int iters = maxIterations(baseAlg, 0);
439             max_iterations = max_iterations < iters ? iters : max_iterations;
440         }
441
442         if (nsec3params.iterations > max_iterations) return false;
443
444         return true;
445     }
446
447     /**
448      * Determine if all of the NSEC3s in a response are legally ignoreable
449      * (i.e., their presence should lead to an INSECURE result). Currently, this
450      * is solely based on iterations.
451      * 
452      * @param nsec3s
453      *            The list of NSEC3s. If there is more than one set of NSEC3
454      *            parameters present, this test will not be performed.
455      * @param dnskey_rrset
456      *            The set of validating DNSKEYs.
457      * @param verifier
458      *            The verifier used to verify the NSEC3 RRsets. This is solely
459      *            used to map algorithm aliases.
460      * @return true if all of the NSEC3s can be legally ignored, false if not.
461      */
462     public static boolean allNSEC3sIgnoreable(List<NSEC3Record> nsec3s,
463                                               RRset dnskey_rrset,
464                                               DnsSecVerifier verifier) {
465         NSEC3Parameters params = nsec3Parameters(nsec3s);
466         if (params == null) return false;
467
468         return !validIterations(params, dnskey_rrset, verifier);
469     }
470
471     /**
472      * Determine if the set of NSEC3 records provided with a response prove NAME
473      * ERROR. This means that the NSEC3s prove a) the closest encloser exists,
474      * b) the direct child of the closest encloser towards qname doesn't exist,
475      * and c) *.closest encloser does not exist.
476      * 
477      * @param nsec3s
478      *            The list of NSEC3s.
479      * @param qname
480      *            The query name to check against.
481      * @param zonename
482      *            This is the name of the zone that the NSEC3s belong to. This
483      *            may be discovered in any number of ways. A good one is to use
484      *            the signerName from the NSEC3 record's RRSIG.
485      * @return SecurityStatus.SECURE of the Name Error is proven by the NSEC3
486      *         RRs, BOGUS if not, INSECURE if all of the NSEC3s could be validly
487      *         ignored.
488      */
489     public static boolean proveNameError(List<NSEC3Record> nsec3s, Name qname,
490                                          Name zonename) {
491         if (nsec3s == null || nsec3s.size() == 0) return false;
492
493         NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
494         if (nsec3params == null) {
495             // st_log.debug("Could not find a single set of " +
496             // "NSEC3 parameters (multiple parameters present).");
497             return false;
498         }
499
500         ByteArrayComparator bac = new ByteArrayComparator();
501
502         // First locate and prove the closest encloser to qname. We will use the
503         // variant that fails if the closest encloser turns out to be qname.
504         CEResponse ce = proveClosestEncloser(qname, zonename, nsec3s,
505                                              nsec3params, bac, true);
506
507         if (ce == null) {
508             // st_log.debug("proveNameError: failed to prove a closest encloser.");
509             return false;
510         }
511
512         // At this point, we know that qname does not exist. Now we need to
513         // prove
514         // that the wildcard does not exist.
515         Name wc = ceWildcard(ce.closestEncloser);
516         byte[] wc_hash = hash(wc, nsec3params);
517         NSEC3Record nsec3 = findCoveringNSEC3(wc_hash, zonename, nsec3s,
518                                               nsec3params, bac);
519         if (nsec3 == null) {
520             // st_log.debug("proveNameError: could not prove that the "
521             // + "applicable wildcard did not exist.");
522             return false;
523         }
524
525         return true;
526     }
527
528  
529     /**
530      * Determine if the NSEC3s provided in a response prove the NOERROR/NODATA
531      * status. There are a number of different variants to this:
532      * 
533      * 1) Normal NODATA -- qname is matched to an NSEC3 record, type is not
534      * present.
535      * 
536      * 2) ENT NODATA -- because there must be NSEC3 record for
537      * empty-non-terminals, this is the same as #1.
538      * 
539      * 3) NSEC3 ownername NODATA -- qname matched an existing, lone NSEC3
540      * ownername, but qtype was not NSEC3. NOTE: as of nsec-05, this case no
541      * longer exists.
542      * 
543      * 4) Wildcard NODATA -- A wildcard matched the name, but not the type.
544      * 
545      * 5) Opt-In DS NODATA -- the qname is covered by an opt-in span and qtype
546      * == DS. (or maybe some future record with the same parent-side-only
547      * property)
548      * 
549      * @param nsec3s
550      *            The NSEC3Records to consider.
551      * @param qname
552      *            The qname in question.
553      * @param qtype
554      *            The qtype in question.
555      * @param zonename
556      *            The name of the zone that the NSEC3s came from.
557      * @return true if the NSEC3s prove the proposition.
558      */
559     public static boolean proveNodata(List<NSEC3Record> nsec3s, Name qname,
560                                       int qtype, Name zonename) {
561         if (nsec3s == null || nsec3s.size() == 0) return false;
562
563         NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
564         if (nsec3params == null) {
565             // st_log.debug("could not find a single set of "
566             // + "NSEC3 parameters (multiple parameters present)");
567             return false;
568         }
569         ByteArrayComparator bac = new ByteArrayComparator();
570
571         NSEC3Record nsec3 = findMatchingNSEC3(hash(qname, nsec3params),
572                                               zonename, nsec3s, nsec3params,
573                                               bac);
574         // Cases 1 & 2.
575         if (nsec3 != null) {
576             if (nsec3.hasType(qtype)) {
577                 // st_log.debug("proveNodata: Matching NSEC3 proved that type existed!");
578                 return false;
579             }
580             if (nsec3.hasType(Type.CNAME)) {
581                 // st_log.debug("proveNodata: Matching NSEC3 proved "
582                 // + "that a CNAME existed!");
583                 return false;
584             }
585             return true;
586         }
587
588         // For cases 3 - 5, we need the proven closest encloser, and it can't
589         // match qname. Although, at this point, we know that it won't since we
590         // just checked that.
591         CEResponse ce = proveClosestEncloser(qname, zonename, nsec3s,
592                                              nsec3params, bac, true);
593
594         // At this point, not finding a match or a proven closest encloser is a
595         // problem.
596         if (ce == null) {
597             // st_log.debug("proveNodata: did not match qname, "
598             // + "nor found a proven closest encloser.");
599             return false;
600         }
601
602         // Case 3: REMOVED
603
604         // Case 4:
605         Name wc = ceWildcard(ce.closestEncloser);
606         nsec3 = findMatchingNSEC3(hash(wc, nsec3params), zonename, nsec3s,
607                                   nsec3params, bac);
608
609         if (nsec3 != null) {
610             if (nsec3.hasType(qtype)) {
611                 // st_log.debug("proveNodata: matching wildcard had qtype!");
612                 return false;
613             }
614             return true;
615         }
616
617         // Case 5.
618         if (qtype != Type.DS) {
619             // st_log.debug("proveNodata: could not find matching NSEC3, "
620             // +
621             // "nor matching wildcard, and qtype is not DS -- no more options.");
622             return false;
623         }
624
625         // We need to make sure that the covering NSEC3 is opt-in.
626         if (!ce.nc_nsec3.getOptInFlag()) {
627             // st_log.debug("proveNodata: covering NSEC3 was not "
628             // + "opt-in in an opt-in DS NOERROR/NODATA case.");
629             return false;
630         }
631
632         return true;
633     }
634
635     /**
636      * Prove that a positive wildcard match was appropriate (no direct match
637      * RRset).
638      * 
639      * @param nsec3s
640      *            The NSEC3 records to work with.
641      * @param qname
642      *            The qname that was matched to the wildcard
643      * @param zonename
644      *            The name of the zone that the NSEC3s come from.
645      * @param wildcard
646      *            The purported wildcard that matched.
647      * @return true if the NSEC3 records prove this case.
648      */
649     public static boolean proveWildcard(List<NSEC3Record> nsec3s, Name qname,
650                                         Name zonename, Name wildcard) {
651         if (nsec3s == null || nsec3s.size() == 0) return false;
652         if (qname == null || wildcard == null) return false;
653
654         NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
655         if (nsec3params == null) {
656             // st_log.debug("couldn't find a single set of NSEC3 parameters (multiple parameters present).");
657             return false;
658         }
659
660         ByteArrayComparator bac = new ByteArrayComparator();
661
662         // We know what the (purported) closest encloser is by just looking at
663         // the
664         // supposed generating wildcard.
665         CEResponse candidate = new CEResponse(new Name(wildcard, 1), null);
666
667         // Now we still need to prove that the original data did not exist.
668         // Otherwise, we need to show that the next closer name is covered.
669         Name nextClosest = nextClosest(qname, candidate.closestEncloser);
670         candidate.nc_nsec3 = findCoveringNSEC3(hash(nextClosest, nsec3params),
671                                                zonename, nsec3s, nsec3params,
672                                                bac);
673
674         if (candidate.nc_nsec3 == null) {
675             // st_log.debug("proveWildcard: did not find a covering NSEC3 "
676             // + "that covered the next closer name to " + qname + " from "
677             // + candidate.closestEncloser + " (derived from wildcard " +
678             // wildcard
679             // + ")");
680             return false;
681         }
682
683         return true;
684     }
685
686     /**
687      * Prove that a DS response either had no DS, or wasn't a delegation point.
688      * 
689      * Fundamentally there are two cases here: normal NODATA and Opt-In NODATA.
690      * 
691      * @param nsec3s
692      *            The NSEC3 RRs to examine.
693      * @param qname
694      *            The name of the DS in question.
695      * @param zonename
696      *            The name of the zone that the NSEC3 RRs come from.
697      * 
698      * @return SecurityStatus.SECURE if it was proven that there is no DS in a
699      *         secure (i.e., not opt-in) way, SecurityStatus.INSECURE if there
700      *         was no DS in an insecure (i.e., opt-in) way,
701      *         SecurityStatus.INDETERMINATE if it was clear that this wasn't a
702      *         delegation point, and SecurityStatus.BOGUS if the proofs don't
703      *         work out.
704      */
705     public static byte proveNoDS(List<NSEC3Record> nsec3s, Name qname,
706                                 Name zonename) {
707         if (nsec3s == null || nsec3s.size() == 0) return SecurityStatus.BOGUS;
708
709         NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
710         if (nsec3params == null) {
711             // st_log.debug("couldn't find a single set of " +
712             // "NSEC3 parameters (multiple parameters present).");
713             return SecurityStatus.BOGUS;
714         }
715         ByteArrayComparator bac = new ByteArrayComparator();
716
717         // Look for a matching NSEC3 to qname -- this is the normal NODATA case.
718         NSEC3Record nsec3 = findMatchingNSEC3(hash(qname, nsec3params),
719                                               zonename, nsec3s, nsec3params,
720                                               bac);
721
722         if (nsec3 != null) {
723             // If the matching NSEC3 has the SOA bit set, it is from the wrong
724             // zone
725             // (the child instead of the parent). If it has the DS bit set, then
726             // we
727             // were lied to.
728             if (nsec3.hasType(Type.SOA) || nsec3.hasType(Type.DS)) {
729                 return SecurityStatus.BOGUS;
730             }
731             // If the NSEC3 RR doesn't have the NS bit set, then this wasn't a
732             // delegation point.
733             if (!nsec3.hasType(Type.NS)) return SecurityStatus.INDETERMINATE;
734
735             // Otherwise, this proves no DS.
736             return SecurityStatus.SECURE;
737         }
738
739         // Otherwise, we are probably in the opt-in case.
740         CEResponse ce = proveClosestEncloser(qname, zonename, nsec3s,
741                                              nsec3params, bac, true);
742         if (ce == null) {
743             return SecurityStatus.BOGUS;
744         }
745
746         // If we had the closest encloser proof, then we need to check that the
747         // covering NSEC3 was opt-in -- the proveClosestEncloser step already
748         // checked to see if the closest encloser was a delegation or DNAME.
749         if (ce.nc_nsec3.getOptInFlag()) {
750             return SecurityStatus.SECURE;
751         }
752
753         return SecurityStatus.BOGUS;
754     }
755
756 }