4 * Copyright (c) 2006 VeriSign. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
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.
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.
30 package se.rfc.unbound;
32 import java.security.NoSuchAlgorithmException;
35 import org.xbill.DNS.*;
36 import org.xbill.DNS.utils.base32;
38 import se.rfc.unbound.SignUtils.ByteArrayComparator;
41 public class NSEC3ValUtils
44 // FIXME: should probably refactor to handle different NSEC3 parameters more
46 // Given a list of NSEC3 RRs, they should be grouped according to
47 // parameters. The idea is to hash and compare for each group independently,
48 // instead of having to skip NSEC3 RRs with the wrong parameters.
51 private static Name asterisk_label = Name.fromConstantString("*");
54 * This is a class to encapsulate a unique set of NSEC3 parameters:
55 * algorithm, iterations, and salt.
57 private static class NSEC3Parameters
61 public int iterations;
63 public NSEC3Parameters(NSEC3Record r)
65 alg = r.getHashAlgorithm();
67 iterations = r.getIterations();
70 public boolean match(NSEC3Record r, ByteArrayComparator bac)
72 if (r.getHashAlgorithm() != alg) return false;
73 if (r.getIterations() != iterations) return false;
75 if (salt == null && r.getSalt() != null) return false;
77 if (bac == null) bac = new ByteArrayComparator();
78 return bac.compare(r.getSalt(), salt) == 0;
83 * This is just a simple class to enapsulate the response to a closest
86 private static class CEResponse
88 public Name closestEncloser;
89 public NSEC3Record ce_nsec3;
90 public NSEC3Record nc_nsec3;
92 public CEResponse(Name ce, NSEC3Record nsec3)
94 this.closestEncloser = ce;
95 this.ce_nsec3 = nsec3;
99 public static boolean supportsHashAlgorithm(int alg)
101 if (alg == NSEC3Record.SHA1_DIGEST_ID) return true;
105 public static void stripUnknownAlgNSEC3s(List nsec3s)
107 if (nsec3s == null) return;
108 for (ListIterator i = nsec3s.listIterator(); i.hasNext(); )
110 NSEC3Record nsec3 = (NSEC3Record) i.next();
111 if (!supportsHashAlgorithm(nsec3.getHashAlgorithm()))
119 * Given a list of NSEC3Records that are part of a message, determine the
120 * NSEC3 parameters (hash algorithm, iterations, and salt) present. If there
121 * is more than one distinct grouping, return null;
123 * @param nsec3s A list of NSEC3Record object.
124 * @return A set containing a number of objects (NSEC3Parameter objects)
125 * that correspond to each distinct set of parameters, or null if
126 * the nsec3s list was empty.
128 public static NSEC3Parameters nsec3Parameters(List nsec3s)
130 if (nsec3s == null || nsec3s.size() == 0) return null;
132 NSEC3Parameters params = new NSEC3Parameters((NSEC3Record) nsec3s.get(0));
133 ByteArrayComparator bac = new ByteArrayComparator();
135 for (Iterator i = nsec3s.iterator(); i.hasNext();)
137 if (! params.match((NSEC3Record) i.next(), bac))
146 * In a list of NSEC3Record object pulled from a given message, find the
147 * NSEC3 that directly matches a given name, without hashing.
149 * @param n The name in question.
150 * @param nsec3s A list of NSEC3Records from a given message.
151 * @return The matching NSEC3Record, or null if there wasn't one.
153 // private static NSEC3Record findDirectMatchingNSEC3(Name n, List nsec3s)
155 // if (n == null || nsec3s == null) return null;
157 // for (Iterator i = nsec3s.iterator(); i.hasNext();)
159 // NSEC3Record nsec3 = (NSEC3Record) i.next();
160 // if (n.equals(nsec3.getName())) return nsec3;
166 * Given a hash and an a zone name, construct an NSEC3 ownername.
168 * @param hash The hash of an original name.
169 * @param zonename The zone to use in constructing the NSEC3 name.
170 * @return The NSEC3 name.
172 private static Name hashName(byte[] hash, Name zonename)
176 return new Name(base32.toString(hash).toLowerCase(), zonename);
178 catch (TextParseException e)
180 // Note, this should never happen.
186 * Given a set of NSEC3 parameters, hash a name.
188 * @param name The name to hash.
189 * @param params The parameters to hash with.
192 private static byte[] hash(Name name, NSEC3Parameters params)
196 return NSEC3Record.hash(name,
201 catch (NoSuchAlgorithmException e)
203 // st_log.debug("Did not recognize hash algorithm: " + params.alg);
209 * Given the name of a closest encloser, return the name *.closest_encloser.
211 * @param closestEncloser The name to start with.
212 * @return The wildcard name.
214 private static Name ceWildcard(Name closestEncloser)
218 Name wc = Name.concatenate(asterisk_label, closestEncloser);
221 catch (NameTooLongException e)
228 * Given a qname and its proven closest encloser, calculate the "next
229 * closest" name. Basically, this is the name that is one label longer than
230 * the closest encloser that is still a subdomain of qname.
232 * @param qname The qname.
233 * @param closestEncloser The closest encloser name.
234 * @return The next closer name.
236 private static Name nextClosest(Name qname, Name closestEncloser)
238 int strip = qname.labels() - closestEncloser.labels() - 1;
239 return (strip > 0) ? new Name(qname, strip) : qname;
243 * Find the NSEC3Record that matches a hash of a name.
245 * @param hash The pre-calculated hash of a name.
246 * @param zonename The name of the zone that the NSEC3s are from.
247 * @param nsec3s A list of NSEC3Records from a given message.
248 * @param params The parameters used for calculating the hash.
249 * @param bac An already allocated ByteArrayComparator, for reuse. This may
252 * @return The matching NSEC3Record, if one is present.
254 private static NSEC3Record findMatchingNSEC3(byte[] hash, Name zonename,
255 List nsec3s, NSEC3Parameters params, ByteArrayComparator bac)
257 Name n = hashName(hash, zonename);
259 for (Iterator i = nsec3s.iterator(); i.hasNext();)
261 NSEC3Record nsec3 = (NSEC3Record) i.next();
262 // Skip nsec3 records that are using different parameters.
263 if (!params.match(nsec3, bac)) continue;
264 if (n.equals(nsec3.getName())) return nsec3;
270 * Given a hash and a candidate NSEC3Record, determine if that NSEC3Record
271 * covers the hash. Covers specifically means that the hash is in between
272 * the owner and next hashes and does not equal either.
274 * @param nsec3 The candidate NSEC3Record.
275 * @param hash The precalculated hash.
276 * @param bac An already allocated comparator. This may be null.
277 * @return True if the NSEC3Record covers the hash.
279 private static boolean nsec3Covers(NSEC3Record nsec3, byte[] hash,
280 ByteArrayComparator bac)
282 byte[] owner = nsec3.getOwner();
283 byte[] next = nsec3.getNext();
285 // This is the "normal case: owner < next and owner < hash < next
286 if (bac.compare(owner, hash) < 0 && bac.compare(hash, next) < 0)
289 // this is the end of zone case: next < owner && hash > owner || hash <
291 if (bac.compare(next, owner) <= 0
292 && (bac.compare(hash, next) < 0 || bac.compare(owner, hash) < 0))
295 // Otherwise, the NSEC3 does not cover the hash.
300 * Given a pre-hashed name, find a covering NSEC3 from among a list of
303 * @param hash The hash to consider.
304 * @param zonename The name of the zone.
305 * @param nsec3s The list of NSEC3s present in a message.
306 * @param params The NSEC3 parameters used to generate the hash -- NSEC3s
307 * that do not use those parameters will be skipped.
309 * @return A covering NSEC3 if one is present, null otherwise.
311 private static NSEC3Record findCoveringNSEC3(byte[] hash, Name zonename,
312 List nsec3s, NSEC3Parameters params, ByteArrayComparator bac)
314 ByteArrayComparator comparator = new ByteArrayComparator();
316 for (Iterator i = nsec3s.iterator(); i.hasNext();)
318 NSEC3Record nsec3 = (NSEC3Record) i.next();
319 if (!params.match(nsec3, bac)) continue;
321 if (nsec3Covers(nsec3, hash, comparator)) return nsec3;
329 * Given a name and a list of NSEC3s, find the candidate closest encloser.
330 * This will be the first ancestor of 'name' (including itself) to have a
333 * @param name The name the start with.
334 * @param zonename The name of the zone that the NSEC3s came from.
335 * @param nsec3s The list of NSEC3s.
336 * @param nsec3params The NSEC3 parameters.
337 * @param bac A pre-allocated comparator. May be null.
339 * @return A CEResponse containing the closest encloser name and the NSEC3
340 * RR that matched it, or null if there wasn't one.
342 private static CEResponse findClosestEncloser(Name name, Name zonename,
343 List nsec3s, NSEC3Parameters params, ByteArrayComparator bac)
349 // This scans from longest name to shortest, so the first match we find is
350 // the only viable candidate.
351 // FIXME: modify so that the NSEC3 matching the zone apex need not be
353 while (n.labels() >= zonename.labels())
355 nsec3 = findMatchingNSEC3(hash(n, params), zonename, nsec3s, params, bac);
356 if (nsec3 != null) return new CEResponse(n, nsec3);
364 * Given a List of nsec3 RRs, find and prove the closest encloser to qname.
366 * @param qname The qname in question.
367 * @param zonename The name of the zone that the NSEC3 RRs come from.
368 * @param nsec3s The list of NSEC3s found the this response (already
370 * @param params The NSEC3 parameters found in the response.
371 * @param bac A pre-allocated comparator. May be null.
372 * @param proveDoesNotExist If true, then if the closest encloser turns out
373 * to be qname, then null is returned.
374 * @return null if the proof isn't completed. Otherwise, return a CEResponse
375 * object which contains the closest encloser name and the NSEC3
378 private static CEResponse proveClosestEncloser(Name qname, Name zonename,
379 List nsec3s, NSEC3Parameters params, ByteArrayComparator bac,
380 boolean proveDoesNotExist)
382 CEResponse candidate = findClosestEncloser(qname,
388 if (candidate == null)
390 // st_log.debug("proveClosestEncloser: could not find a "
391 // + "candidate for the closest encloser.");
395 if (candidate.closestEncloser.equals(qname))
397 if (proveDoesNotExist)
399 // st_log.debug("proveClosestEncloser: proved that qname existed!");
402 // otherwise, we need to nothing else to prove that qname is its own
407 // If the closest encloser is actually a delegation, then the response
408 // should have been a referral. If it is a DNAME, then it should have been
410 if (candidate.ce_nsec3.hasType(Type.NS)
411 && !candidate.ce_nsec3.hasType(Type.SOA))
413 // st_log.debug("proveClosestEncloser: closest encloser "
414 // + "was a delegation!");
417 if (candidate.ce_nsec3.hasType(Type.DNAME))
419 // st_log.debug("proveClosestEncloser: closest encloser was a DNAME!");
423 // Otherwise, we need to show that the next closer name is covered.
424 Name nextClosest = nextClosest(qname, candidate.closestEncloser);
426 byte[] nc_hash = hash(nextClosest, params);
427 candidate.nc_nsec3 = findCoveringNSEC3(nc_hash,
432 if (candidate.nc_nsec3 == null)
434 // st_log.debug("Could not find proof that the "
435 // + "closest encloser was the closest encloser");
442 private static int maxIterations(int baseAlg, int keysize)
446 case DnsSecVerifier.RSA:
447 if (keysize == 0) return 2500; // the max at 4096
448 if (keysize > 2048) return 2500;
449 if (keysize > 1024) return 500;
450 if (keysize > 0) return 150;
452 case DnsSecVerifier.DSA:
453 if (keysize == 0) return 5000; // the max at 2048;
454 if (keysize > 1024) return 5000;
455 if (keysize > 0) return 1500;
461 private static boolean validIterations(NSEC3Parameters nsec3params,
462 RRset dnskey_rrset, DnsSecVerifier verifier)
464 // for now, we return the maximum iterations based simply on the key
465 // algorithms that may have been used to sign the NSEC3 RRsets.
467 int max_iterations = 0;
468 for (Iterator i = dnskey_rrset.rrs(); i.hasNext();)
470 DNSKEYRecord dnskey = (DNSKEYRecord) i.next();
471 int baseAlg = verifier.baseAlgorithm(dnskey.getAlgorithm());
472 int iters = maxIterations(baseAlg, 0);
473 max_iterations = max_iterations < iters ? iters : max_iterations;
476 if (nsec3params.iterations > max_iterations) return false;
482 * Determine if all of the NSEC3s in a response are legally ignoreable
483 * (i.e., their presence should lead to an INSECURE result). Currently, this
484 * is solely based on iterations.
486 * @param nsec3s The list of NSEC3s. If there is more than one set of NSEC3
487 * parameters present, this test will not be performed.
488 * @param dnskey_rrset The set of validating DNSKEYs.
489 * @param verifier The verifier used to verify the NSEC3 RRsets. This is
490 * solely used to map algorithm aliases.
491 * @return true if all of the NSEC3s can be legally ignored, false if not.
493 public static boolean allNSEC3sIgnoreable(List nsec3s, RRset dnskey_rrset, DnsSecVerifier verifier)
495 NSEC3Parameters params = nsec3Parameters(nsec3s);
496 if (params == null) return false;
498 return !validIterations(params, dnskey_rrset, verifier);
502 * Determine if the set of NSEC3 records provided with a response prove NAME
503 * ERROR. This means that the NSEC3s prove a) the closest encloser exists,
504 * b) the direct child of the closest encloser towards qname doesn't exist,
505 * and c) *.closest encloser does not exist.
507 * @param nsec3s The list of NSEC3s.
508 * @param qname The query name to check against.
509 * @param zonename This is the name of the zone that the NSEC3s belong to.
510 * This may be discovered in any number of ways. A good one is to
511 * use the signerName from the NSEC3 record's RRSIG.
512 * @return SecurityStatus.SECURE of the Name Error is proven by the NSEC3
513 * RRs, BOGUS if not, INSECURE if all of the NSEC3s could be validly
516 public static boolean proveNameError(List nsec3s, Name qname, Name zonename)
518 if (nsec3s == null || nsec3s.size() == 0) return false;
520 NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
521 if (nsec3params == null)
523 // st_log.debug("Could not find a single set of " +
524 // "NSEC3 parameters (multiple parameters present).");
528 ByteArrayComparator bac = new ByteArrayComparator();
530 // First locate and prove the closest encloser to qname. We will use the
531 // variant that fails if the closest encloser turns out to be qname.
532 CEResponse ce = proveClosestEncloser(qname,
541 // st_log.debug("proveNameError: failed to prove a closest encloser.");
545 // At this point, we know that qname does not exist. Now we need to prove
546 // that the wildcard does not exist.
547 Name wc = ceWildcard(ce.closestEncloser);
548 byte[] wc_hash = hash(wc, nsec3params);
549 NSEC3Record nsec3 = findCoveringNSEC3(wc_hash,
556 // st_log.debug("proveNameError: could not prove that the "
557 // + "applicable wildcard did not exist.");
565 * Determine if the set of NSEC3 records provided with a response prove NAME
566 * ERROR when qtype = NSEC3. This is a special case, and (currently anyway)
567 * it suffices to simply prove that the NSEC3 RRset itself does not exist,
568 * without proving that no wildcard could have generated it, etc..
570 * @param nsec3s The list of NSEC3s.
571 * @param qname The query name to check against.
572 * @param zonename This is the name of the zone that the NSEC3s belong to.
573 * This may be discovered in any number of ways. A good one is to
574 * use the signerName from the NSEC3 record's RRSIG.
575 * @return true of the Name Error is proven by the NSEC3 RRs, false if not.
577 // public static boolean proveNSEC3NameError(List nsec3s, Name qname,
580 // if (nsec3s == null || nsec3s.size() == 0) return false;
582 // for (Iterator i = nsec3s.iterator(); i.hasNext(); )
584 // NSEC3Record nsec3 = (NSEC3Record) i.next();
586 // // Convert owner and next into Names.
587 // Name owner = nsec3.getName();
591 // next = new Name(base32.toString(nsec3.getNext()), zonename);
593 // catch (TextParseException e)
598 // // Now see if qname is covered by the NSEC3.
600 // // normal case, owner < qname < next.
601 // if (owner.compareTo(next) < 0 && owner.compareTo(qname) < 0 &&
602 // next.compareTo(qname) > 0)
604 // st_log.debug("proveNSEC3NameError: found a covering NSEC3: " + nsec3);
607 // // end-of-zone case: next < owner and qname > owner || qname < next.
608 // if (owner.compareTo(next) > 0 && (owner.compareTo(qname) < 0 ||
609 // next.compareTo(qname) > 0))
611 // st_log.debug("proveNSEC3NameError: found a covering NSEC3: " + nsec3);
616 // st_log.debug("proveNSEC3NameError: did not find a covering NSEC3");
620 * Determine if the NSEC3s provided in a response prove the NOERROR/NODATA
621 * status. There are a number of different variants to this:
623 * 1) Normal NODATA -- qname is matched to an NSEC3 record, type is not
626 * 2) ENT NODATA -- because there must be NSEC3 record for
627 * empty-non-terminals, this is the same as #1.
629 * 3) NSEC3 ownername NODATA -- qname matched an existing, lone NSEC3
630 * ownername, but qtype was not NSEC3. NOTE: as of nsec-05, this case no
633 * 4) Wildcard NODATA -- A wildcard matched the name, but not the type.
635 * 5) Opt-In DS NODATA -- the qname is covered by an opt-in span and qtype ==
636 * DS. (or maybe some future record with the same parent-side-only property)
638 * @param nsec3s The NSEC3Records to consider.
639 * @param qname The qname in question.
640 * @param qtype The qtype in question.
641 * @param zonename The name of the zone that the NSEC3s came from.
642 * @return true if the NSEC3s prove the proposition.
644 public static boolean proveNodata(List nsec3s, Name qname, int qtype,
647 if (nsec3s == null || nsec3s.size() == 0) return false;
649 NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
650 if (nsec3params == null)
652 // st_log.debug("could not find a single set of "
653 // + "NSEC3 parameters (multiple parameters present)");
656 ByteArrayComparator bac = new ByteArrayComparator();
658 NSEC3Record nsec3 = findMatchingNSEC3(hash(qname, nsec3params),
666 if (nsec3.hasType(qtype))
668 // st_log.debug("proveNodata: Matching NSEC3 proved that type existed!");
671 if (nsec3.hasType(Type.CNAME))
673 // st_log.debug("proveNodata: Matching NSEC3 proved "
674 // + "that a CNAME existed!");
680 // For cases 3 - 5, we need the proven closest encloser, and it can't
681 // match qname. Although, at this point, we know that it won't since we
682 // just checked that.
683 CEResponse ce = proveClosestEncloser(qname,
690 // At this point, not finding a match or a proven closest encloser is a
694 // st_log.debug("proveNodata: did not match qname, "
695 // + "nor found a proven closest encloser.");
702 Name wc = ceWildcard(ce.closestEncloser);
703 nsec3 = findMatchingNSEC3(hash(wc, nsec3params),
711 if (nsec3.hasType(qtype))
713 // st_log.debug("proveNodata: matching wildcard had qtype!");
720 if (qtype != Type.DS)
722 // st_log.debug("proveNodata: could not find matching NSEC3, "
723 // + "nor matching wildcard, and qtype is not DS -- no more options.");
727 // We need to make sure that the covering NSEC3 is opt-in.
728 if (!ce.nc_nsec3.getOptInFlag())
730 // st_log.debug("proveNodata: covering NSEC3 was not "
731 // + "opt-in in an opt-in DS NOERROR/NODATA case.");
739 * Prove that a positive wildcard match was appropriate (no direct match
742 * @param nsec3s The NSEC3 records to work with.
743 * @param qname The qname that was matched to the wildard
744 * @param zonename The name of the zone that the NSEC3s come from.
745 * @param wildcard The purported wildcard that matched.
746 * @return true if the NSEC3 records prove this case.
748 public static boolean proveWildcard(List nsec3s, Name qname, Name zonename,
751 if (nsec3s == null || nsec3s.size() == 0) return false;
752 if (qname == null || wildcard == null) return false;
754 NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
755 if (nsec3params == null)
757 // st_log.debug("couldn't find a single set of NSEC3 parameters (multiple parameters present).");
761 ByteArrayComparator bac = new ByteArrayComparator();
763 // We know what the (purported) closest encloser is by just looking at the
764 // supposed generating wildcard.
765 CEResponse candidate = new CEResponse(new Name(wildcard, 1), null);
767 // Now we still need to prove that the original data did not exist.
768 // Otherwise, we need to show that the next closer name is covered.
769 Name nextClosest = nextClosest(qname, candidate.closestEncloser);
770 candidate.nc_nsec3 = findCoveringNSEC3(hash(nextClosest, nsec3params),
776 if (candidate.nc_nsec3 == null)
778 // st_log.debug("proveWildcard: did not find a covering NSEC3 "
779 // + "that covered the next closer name to " + qname + " from "
780 // + candidate.closestEncloser + " (derived from wildcard " + wildcard
789 * Prove that a DS response either had no DS, or wasn't a delegation point.
791 * Fundamentally there are two cases here: normal NODATA and Opt-In NODATA.
793 * @param nsec3s The NSEC3 RRs to examine.
794 * @param qname The name of the DS in question.
795 * @param zonename The name of the zone that the NSEC3 RRs come from.
797 * @return SecurityStatus.SECURE if it was proven that there is no DS in a
798 * secure (i.e., not opt-in) way, SecurityStatus.INSECURE if there
799 * was no DS in an insecure (i.e., opt-in) way,
800 * SecurityStatus.INDETERMINATE if it was clear that this wasn't a
801 * delegation point, and SecurityStatus.BOGUS if the proofs don't
804 public static int proveNoDS(List nsec3s, Name qname, Name zonename)
806 if (nsec3s == null || nsec3s.size() == 0) return SecurityStatus.BOGUS;
808 NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
809 if (nsec3params == null)
811 // st_log.debug("couldn't find a single set of " +
812 // "NSEC3 parameters (multiple parameters present).");
813 return SecurityStatus.BOGUS;
815 ByteArrayComparator bac = new ByteArrayComparator();
817 // Look for a matching NSEC3 to qname -- this is the normal NODATA case.
818 NSEC3Record nsec3 = findMatchingNSEC3(hash(qname, nsec3params),
826 // If the matching NSEC3 has the SOA bit set, it is from the wrong zone
827 // (the child instead of the parent). If it has the DS bit set, then we
829 if (nsec3.hasType(Type.SOA) || nsec3.hasType(Type.DS))
831 return SecurityStatus.BOGUS;
833 // If the NSEC3 RR doesn't have the NS bit set, then this wasn't a
835 if (!nsec3.hasType(Type.NS)) return SecurityStatus.INDETERMINATE;
837 // Otherwise, this proves no DS.
838 return SecurityStatus.SECURE;
841 // Otherwise, we are probably in the opt-in case.
842 CEResponse ce = proveClosestEncloser(qname,
850 return SecurityStatus.BOGUS;
853 // If we had the closest encloser proof, then we need to check that the
854 // covering NSEC3 was opt-in -- the proveClosestEncloser step already
855 // checked to see if the closest encloser was a delegation or DNAME.
856 if (ce.nc_nsec3.getOptInFlag())
858 return SecurityStatus.SECURE;
861 return SecurityStatus.BOGUS;