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 com.versign.tat.dnssec;
32 import java.security.NoSuchAlgorithmException;
35 import org.xbill.DNS.*;
36 import org.xbill.DNS.utils.base32;
38 import com.versign.tat.dnssec.SignUtils.ByteArrayComparator;
42 public class NSEC3ValUtils
45 // FIXME: should probably refactor to handle different NSEC3 parameters more
47 // Given a list of NSEC3 RRs, they should be grouped according to
48 // parameters. The idea is to hash and compare for each group independently,
49 // instead of having to skip NSEC3 RRs with the wrong parameters.
52 private static Name asterisk_label = Name.fromConstantString("*");
55 * This is a class to encapsulate a unique set of NSEC3 parameters:
56 * algorithm, iterations, and salt.
58 private static class NSEC3Parameters
62 public int iterations;
64 public NSEC3Parameters(NSEC3Record r)
66 alg = r.getHashAlgorithm();
68 iterations = r.getIterations();
71 public boolean match(NSEC3Record r, ByteArrayComparator bac)
73 if (r.getHashAlgorithm() != alg) return false;
74 if (r.getIterations() != iterations) return false;
76 if (salt == null && r.getSalt() != null) return false;
78 if (bac == null) bac = new ByteArrayComparator();
79 return bac.compare(r.getSalt(), salt) == 0;
84 * This is just a simple class to enapsulate the response to a closest
87 private static class CEResponse
89 public Name closestEncloser;
90 public NSEC3Record ce_nsec3;
91 public NSEC3Record nc_nsec3;
93 public CEResponse(Name ce, NSEC3Record nsec3)
95 this.closestEncloser = ce;
96 this.ce_nsec3 = nsec3;
100 public static boolean supportsHashAlgorithm(int alg)
102 if (alg == NSEC3Record.SHA1_DIGEST_ID) return true;
106 public static void stripUnknownAlgNSEC3s(List nsec3s)
108 if (nsec3s == null) return;
109 for (ListIterator i = nsec3s.listIterator(); i.hasNext(); )
111 NSEC3Record nsec3 = (NSEC3Record) i.next();
112 if (!supportsHashAlgorithm(nsec3.getHashAlgorithm()))
120 * Given a list of NSEC3Records that are part of a message, determine the
121 * NSEC3 parameters (hash algorithm, iterations, and salt) present. If there
122 * is more than one distinct grouping, return null;
124 * @param nsec3s A list of NSEC3Record object.
125 * @return A set containing a number of objects (NSEC3Parameter objects)
126 * that correspond to each distinct set of parameters, or null if
127 * the nsec3s list was empty.
129 public static NSEC3Parameters nsec3Parameters(List nsec3s)
131 if (nsec3s == null || nsec3s.size() == 0) return null;
133 NSEC3Parameters params = new NSEC3Parameters((NSEC3Record) nsec3s.get(0));
134 ByteArrayComparator bac = new ByteArrayComparator();
136 for (Iterator i = nsec3s.iterator(); i.hasNext();)
138 if (! params.match((NSEC3Record) i.next(), bac))
147 * In a list of NSEC3Record object pulled from a given message, find the
148 * NSEC3 that directly matches a given name, without hashing.
150 * @param n The name in question.
151 * @param nsec3s A list of NSEC3Records from a given message.
152 * @return The matching NSEC3Record, or null if there wasn't one.
154 // private static NSEC3Record findDirectMatchingNSEC3(Name n, List nsec3s)
156 // if (n == null || nsec3s == null) return null;
158 // for (Iterator i = nsec3s.iterator(); i.hasNext();)
160 // NSEC3Record nsec3 = (NSEC3Record) i.next();
161 // if (n.equals(nsec3.getName())) return nsec3;
167 * Given a hash and an a zone name, construct an NSEC3 ownername.
169 * @param hash The hash of an original name.
170 * @param zonename The zone to use in constructing the NSEC3 name.
171 * @return The NSEC3 name.
173 private static Name hashName(byte[] hash, Name zonename)
177 return new Name(base32.toString(hash).toLowerCase(), zonename);
179 catch (TextParseException e)
181 // Note, this should never happen.
187 * Given a set of NSEC3 parameters, hash a name.
189 * @param name The name to hash.
190 * @param params The parameters to hash with.
193 private static byte[] hash(Name name, NSEC3Parameters params)
197 return NSEC3Record.hash(name,
202 catch (NoSuchAlgorithmException e)
204 // st_log.debug("Did not recognize hash algorithm: " + params.alg);
210 * Given the name of a closest encloser, return the name *.closest_encloser.
212 * @param closestEncloser The name to start with.
213 * @return The wildcard name.
215 private static Name ceWildcard(Name closestEncloser)
219 Name wc = Name.concatenate(asterisk_label, closestEncloser);
222 catch (NameTooLongException e)
229 * Given a qname and its proven closest encloser, calculate the "next
230 * closest" name. Basically, this is the name that is one label longer than
231 * the closest encloser that is still a subdomain of qname.
233 * @param qname The qname.
234 * @param closestEncloser The closest encloser name.
235 * @return The next closer name.
237 private static Name nextClosest(Name qname, Name closestEncloser)
239 int strip = qname.labels() - closestEncloser.labels() - 1;
240 return (strip > 0) ? new Name(qname, strip) : qname;
244 * Find the NSEC3Record that matches a hash of a name.
246 * @param hash The pre-calculated hash of a name.
247 * @param zonename The name of the zone that the NSEC3s are from.
248 * @param nsec3s A list of NSEC3Records from a given message.
249 * @param params The parameters used for calculating the hash.
250 * @param bac An already allocated ByteArrayComparator, for reuse. This may
253 * @return The matching NSEC3Record, if one is present.
255 private static NSEC3Record findMatchingNSEC3(byte[] hash, Name zonename,
256 List nsec3s, NSEC3Parameters params, ByteArrayComparator bac)
258 Name n = hashName(hash, zonename);
260 for (Iterator i = nsec3s.iterator(); i.hasNext();)
262 NSEC3Record nsec3 = (NSEC3Record) i.next();
263 // Skip nsec3 records that are using different parameters.
264 if (!params.match(nsec3, bac)) continue;
265 if (n.equals(nsec3.getName())) return nsec3;
271 * Given a hash and a candidate NSEC3Record, determine if that NSEC3Record
272 * covers the hash. Covers specifically means that the hash is in between
273 * the owner and next hashes and does not equal either.
275 * @param nsec3 The candidate NSEC3Record.
276 * @param hash The precalculated hash.
277 * @param bac An already allocated comparator. This may be null.
278 * @return True if the NSEC3Record covers the hash.
280 private static boolean nsec3Covers(NSEC3Record nsec3, byte[] hash,
281 ByteArrayComparator bac)
283 byte[] owner = nsec3.getOwner();
284 byte[] next = nsec3.getNext();
286 // This is the "normal case: owner < next and owner < hash < next
287 if (bac.compare(owner, hash) < 0 && bac.compare(hash, next) < 0)
290 // this is the end of zone case: next < owner && hash > owner || hash <
292 if (bac.compare(next, owner) <= 0
293 && (bac.compare(hash, next) < 0 || bac.compare(owner, hash) < 0))
296 // Otherwise, the NSEC3 does not cover the hash.
301 * Given a pre-hashed name, find a covering NSEC3 from among a list of
304 * @param hash The hash to consider.
305 * @param zonename The name of the zone.
306 * @param nsec3s The list of NSEC3s present in a message.
307 * @param params The NSEC3 parameters used to generate the hash -- NSEC3s
308 * that do not use those parameters will be skipped.
310 * @return A covering NSEC3 if one is present, null otherwise.
312 private static NSEC3Record findCoveringNSEC3(byte[] hash, Name zonename,
313 List nsec3s, NSEC3Parameters params, ByteArrayComparator bac)
315 ByteArrayComparator comparator = new ByteArrayComparator();
317 for (Iterator i = nsec3s.iterator(); i.hasNext();)
319 NSEC3Record nsec3 = (NSEC3Record) i.next();
320 if (!params.match(nsec3, bac)) continue;
322 if (nsec3Covers(nsec3, hash, comparator)) return nsec3;
330 * Given a name and a list of NSEC3s, find the candidate closest encloser.
331 * This will be the first ancestor of 'name' (including itself) to have a
334 * @param name The name the start with.
335 * @param zonename The name of the zone that the NSEC3s came from.
336 * @param nsec3s The list of NSEC3s.
337 * @param nsec3params The NSEC3 parameters.
338 * @param bac A pre-allocated comparator. May be null.
340 * @return A CEResponse containing the closest encloser name and the NSEC3
341 * RR that matched it, or null if there wasn't one.
343 private static CEResponse findClosestEncloser(Name name, Name zonename,
344 List nsec3s, NSEC3Parameters params, ByteArrayComparator bac)
350 // This scans from longest name to shortest, so the first match we find is
351 // the only viable candidate.
352 // FIXME: modify so that the NSEC3 matching the zone apex need not be
354 while (n.labels() >= zonename.labels())
356 nsec3 = findMatchingNSEC3(hash(n, params), zonename, nsec3s, params, bac);
357 if (nsec3 != null) return new CEResponse(n, nsec3);
365 * Given a List of nsec3 RRs, find and prove the closest encloser to qname.
367 * @param qname The qname in question.
368 * @param zonename The name of the zone that the NSEC3 RRs come from.
369 * @param nsec3s The list of NSEC3s found the this response (already
371 * @param params The NSEC3 parameters found in the response.
372 * @param bac A pre-allocated comparator. May be null.
373 * @param proveDoesNotExist If true, then if the closest encloser turns out
374 * to be qname, then null is returned.
375 * @return null if the proof isn't completed. Otherwise, return a CEResponse
376 * object which contains the closest encloser name and the NSEC3
379 private static CEResponse proveClosestEncloser(Name qname, Name zonename,
380 List nsec3s, NSEC3Parameters params, ByteArrayComparator bac,
381 boolean proveDoesNotExist)
383 CEResponse candidate = findClosestEncloser(qname,
389 if (candidate == null)
391 // st_log.debug("proveClosestEncloser: could not find a "
392 // + "candidate for the closest encloser.");
396 if (candidate.closestEncloser.equals(qname))
398 if (proveDoesNotExist)
400 // st_log.debug("proveClosestEncloser: proved that qname existed!");
403 // otherwise, we need to nothing else to prove that qname is its own
408 // If the closest encloser is actually a delegation, then the response
409 // should have been a referral. If it is a DNAME, then it should have been
411 if (candidate.ce_nsec3.hasType(Type.NS)
412 && !candidate.ce_nsec3.hasType(Type.SOA))
414 // st_log.debug("proveClosestEncloser: closest encloser "
415 // + "was a delegation!");
418 if (candidate.ce_nsec3.hasType(Type.DNAME))
420 // st_log.debug("proveClosestEncloser: closest encloser was a DNAME!");
424 // Otherwise, we need to show that the next closer name is covered.
425 Name nextClosest = nextClosest(qname, candidate.closestEncloser);
427 byte[] nc_hash = hash(nextClosest, params);
428 candidate.nc_nsec3 = findCoveringNSEC3(nc_hash,
433 if (candidate.nc_nsec3 == null)
435 // st_log.debug("Could not find proof that the "
436 // + "closest encloser was the closest encloser");
443 private static int maxIterations(int baseAlg, int keysize)
447 case DnsSecVerifier.RSA:
448 if (keysize == 0) return 2500; // the max at 4096
449 if (keysize > 2048) return 2500;
450 if (keysize > 1024) return 500;
451 if (keysize > 0) return 150;
453 case DnsSecVerifier.DSA:
454 if (keysize == 0) return 5000; // the max at 2048;
455 if (keysize > 1024) return 5000;
456 if (keysize > 0) return 1500;
462 private static boolean validIterations(NSEC3Parameters nsec3params,
463 RRset dnskey_rrset, DnsSecVerifier verifier)
465 // for now, we return the maximum iterations based simply on the key
466 // algorithms that may have been used to sign the NSEC3 RRsets.
468 int max_iterations = 0;
469 for (Iterator i = dnskey_rrset.rrs(); i.hasNext();)
471 DNSKEYRecord dnskey = (DNSKEYRecord) i.next();
472 int baseAlg = verifier.baseAlgorithm(dnskey.getAlgorithm());
473 int iters = maxIterations(baseAlg, 0);
474 max_iterations = max_iterations < iters ? iters : max_iterations;
477 if (nsec3params.iterations > max_iterations) return false;
483 * Determine if all of the NSEC3s in a response are legally ignoreable
484 * (i.e., their presence should lead to an INSECURE result). Currently, this
485 * is solely based on iterations.
487 * @param nsec3s The list of NSEC3s. If there is more than one set of NSEC3
488 * parameters present, this test will not be performed.
489 * @param dnskey_rrset The set of validating DNSKEYs.
490 * @param verifier The verifier used to verify the NSEC3 RRsets. This is
491 * solely used to map algorithm aliases.
492 * @return true if all of the NSEC3s can be legally ignored, false if not.
494 public static boolean allNSEC3sIgnoreable(List nsec3s, RRset dnskey_rrset, DnsSecVerifier verifier)
496 NSEC3Parameters params = nsec3Parameters(nsec3s);
497 if (params == null) return false;
499 return !validIterations(params, dnskey_rrset, verifier);
503 * Determine if the set of NSEC3 records provided with a response prove NAME
504 * ERROR. This means that the NSEC3s prove a) the closest encloser exists,
505 * b) the direct child of the closest encloser towards qname doesn't exist,
506 * and c) *.closest encloser does not exist.
508 * @param nsec3s The list of NSEC3s.
509 * @param qname The query name to check against.
510 * @param zonename This is the name of the zone that the NSEC3s belong to.
511 * This may be discovered in any number of ways. A good one is to
512 * use the signerName from the NSEC3 record's RRSIG.
513 * @return SecurityStatus.SECURE of the Name Error is proven by the NSEC3
514 * RRs, BOGUS if not, INSECURE if all of the NSEC3s could be validly
517 public static boolean proveNameError(List nsec3s, Name qname, Name zonename)
519 if (nsec3s == null || nsec3s.size() == 0) return false;
521 NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
522 if (nsec3params == null)
524 // st_log.debug("Could not find a single set of " +
525 // "NSEC3 parameters (multiple parameters present).");
529 ByteArrayComparator bac = new ByteArrayComparator();
531 // First locate and prove the closest encloser to qname. We will use the
532 // variant that fails if the closest encloser turns out to be qname.
533 CEResponse ce = proveClosestEncloser(qname,
542 // st_log.debug("proveNameError: failed to prove a closest encloser.");
546 // At this point, we know that qname does not exist. Now we need to prove
547 // that the wildcard does not exist.
548 Name wc = ceWildcard(ce.closestEncloser);
549 byte[] wc_hash = hash(wc, nsec3params);
550 NSEC3Record nsec3 = findCoveringNSEC3(wc_hash,
557 // st_log.debug("proveNameError: could not prove that the "
558 // + "applicable wildcard did not exist.");
566 * Determine if the set of NSEC3 records provided with a response prove NAME
567 * ERROR when qtype = NSEC3. This is a special case, and (currently anyway)
568 * it suffices to simply prove that the NSEC3 RRset itself does not exist,
569 * without proving that no wildcard could have generated it, etc..
571 * @param nsec3s The list of NSEC3s.
572 * @param qname The query name to check against.
573 * @param zonename This is the name of the zone that the NSEC3s belong to.
574 * This may be discovered in any number of ways. A good one is to
575 * use the signerName from the NSEC3 record's RRSIG.
576 * @return true of the Name Error is proven by the NSEC3 RRs, false if not.
578 // public static boolean proveNSEC3NameError(List nsec3s, Name qname,
581 // if (nsec3s == null || nsec3s.size() == 0) return false;
583 // for (Iterator i = nsec3s.iterator(); i.hasNext(); )
585 // NSEC3Record nsec3 = (NSEC3Record) i.next();
587 // // Convert owner and next into Names.
588 // Name owner = nsec3.getName();
592 // next = new Name(base32.toString(nsec3.getNext()), zonename);
594 // catch (TextParseException e)
599 // // Now see if qname is covered by the NSEC3.
601 // // normal case, owner < qname < next.
602 // if (owner.compareTo(next) < 0 && owner.compareTo(qname) < 0 &&
603 // next.compareTo(qname) > 0)
605 // st_log.debug("proveNSEC3NameError: found a covering NSEC3: " + nsec3);
608 // // end-of-zone case: next < owner and qname > owner || qname < next.
609 // if (owner.compareTo(next) > 0 && (owner.compareTo(qname) < 0 ||
610 // next.compareTo(qname) > 0))
612 // st_log.debug("proveNSEC3NameError: found a covering NSEC3: " + nsec3);
617 // st_log.debug("proveNSEC3NameError: did not find a covering NSEC3");
621 * Determine if the NSEC3s provided in a response prove the NOERROR/NODATA
622 * status. There are a number of different variants to this:
624 * 1) Normal NODATA -- qname is matched to an NSEC3 record, type is not
627 * 2) ENT NODATA -- because there must be NSEC3 record for
628 * empty-non-terminals, this is the same as #1.
630 * 3) NSEC3 ownername NODATA -- qname matched an existing, lone NSEC3
631 * ownername, but qtype was not NSEC3. NOTE: as of nsec-05, this case no
634 * 4) Wildcard NODATA -- A wildcard matched the name, but not the type.
636 * 5) Opt-In DS NODATA -- the qname is covered by an opt-in span and qtype ==
637 * DS. (or maybe some future record with the same parent-side-only property)
639 * @param nsec3s The NSEC3Records to consider.
640 * @param qname The qname in question.
641 * @param qtype The qtype in question.
642 * @param zonename The name of the zone that the NSEC3s came from.
643 * @return true if the NSEC3s prove the proposition.
645 public static boolean proveNodata(List nsec3s, Name qname, int qtype,
648 if (nsec3s == null || nsec3s.size() == 0) return false;
650 NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
651 if (nsec3params == null)
653 // st_log.debug("could not find a single set of "
654 // + "NSEC3 parameters (multiple parameters present)");
657 ByteArrayComparator bac = new ByteArrayComparator();
659 NSEC3Record nsec3 = findMatchingNSEC3(hash(qname, nsec3params),
667 if (nsec3.hasType(qtype))
669 // st_log.debug("proveNodata: Matching NSEC3 proved that type existed!");
672 if (nsec3.hasType(Type.CNAME))
674 // st_log.debug("proveNodata: Matching NSEC3 proved "
675 // + "that a CNAME existed!");
681 // For cases 3 - 5, we need the proven closest encloser, and it can't
682 // match qname. Although, at this point, we know that it won't since we
683 // just checked that.
684 CEResponse ce = proveClosestEncloser(qname,
691 // At this point, not finding a match or a proven closest encloser is a
695 // st_log.debug("proveNodata: did not match qname, "
696 // + "nor found a proven closest encloser.");
703 Name wc = ceWildcard(ce.closestEncloser);
704 nsec3 = findMatchingNSEC3(hash(wc, nsec3params),
712 if (nsec3.hasType(qtype))
714 // st_log.debug("proveNodata: matching wildcard had qtype!");
721 if (qtype != Type.DS)
723 // st_log.debug("proveNodata: could not find matching NSEC3, "
724 // + "nor matching wildcard, and qtype is not DS -- no more options.");
728 // We need to make sure that the covering NSEC3 is opt-in.
729 if (!ce.nc_nsec3.getOptInFlag())
731 // st_log.debug("proveNodata: covering NSEC3 was not "
732 // + "opt-in in an opt-in DS NOERROR/NODATA case.");
740 * Prove that a positive wildcard match was appropriate (no direct match
743 * @param nsec3s The NSEC3 records to work with.
744 * @param qname The qname that was matched to the wildard
745 * @param zonename The name of the zone that the NSEC3s come from.
746 * @param wildcard The purported wildcard that matched.
747 * @return true if the NSEC3 records prove this case.
749 public static boolean proveWildcard(List nsec3s, Name qname, Name zonename,
752 if (nsec3s == null || nsec3s.size() == 0) return false;
753 if (qname == null || wildcard == null) return false;
755 NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
756 if (nsec3params == null)
758 // st_log.debug("couldn't find a single set of NSEC3 parameters (multiple parameters present).");
762 ByteArrayComparator bac = new ByteArrayComparator();
764 // We know what the (purported) closest encloser is by just looking at the
765 // supposed generating wildcard.
766 CEResponse candidate = new CEResponse(new Name(wildcard, 1), null);
768 // Now we still need to prove that the original data did not exist.
769 // Otherwise, we need to show that the next closer name is covered.
770 Name nextClosest = nextClosest(qname, candidate.closestEncloser);
771 candidate.nc_nsec3 = findCoveringNSEC3(hash(nextClosest, nsec3params),
777 if (candidate.nc_nsec3 == null)
779 // st_log.debug("proveWildcard: did not find a covering NSEC3 "
780 // + "that covered the next closer name to " + qname + " from "
781 // + candidate.closestEncloser + " (derived from wildcard " + wildcard
790 * Prove that a DS response either had no DS, or wasn't a delegation point.
792 * Fundamentally there are two cases here: normal NODATA and Opt-In NODATA.
794 * @param nsec3s The NSEC3 RRs to examine.
795 * @param qname The name of the DS in question.
796 * @param zonename The name of the zone that the NSEC3 RRs come from.
798 * @return SecurityStatus.SECURE if it was proven that there is no DS in a
799 * secure (i.e., not opt-in) way, SecurityStatus.INSECURE if there
800 * was no DS in an insecure (i.e., opt-in) way,
801 * SecurityStatus.INDETERMINATE if it was clear that this wasn't a
802 * delegation point, and SecurityStatus.BOGUS if the proofs don't
805 public static int proveNoDS(List nsec3s, Name qname, Name zonename)
807 if (nsec3s == null || nsec3s.size() == 0) return SecurityStatus.BOGUS;
809 NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
810 if (nsec3params == null)
812 // st_log.debug("couldn't find a single set of " +
813 // "NSEC3 parameters (multiple parameters present).");
814 return SecurityStatus.BOGUS;
816 ByteArrayComparator bac = new ByteArrayComparator();
818 // Look for a matching NSEC3 to qname -- this is the normal NODATA case.
819 NSEC3Record nsec3 = findMatchingNSEC3(hash(qname, nsec3params),
827 // If the matching NSEC3 has the SOA bit set, it is from the wrong zone
828 // (the child instead of the parent). If it has the DS bit set, then we
830 if (nsec3.hasType(Type.SOA) || nsec3.hasType(Type.DS))
832 return SecurityStatus.BOGUS;
834 // If the NSEC3 RR doesn't have the NS bit set, then this wasn't a
836 if (!nsec3.hasType(Type.NS)) return SecurityStatus.INDETERMINATE;
838 // Otherwise, this proves no DS.
839 return SecurityStatus.SECURE;
842 // Otherwise, we are probably in the opt-in case.
843 CEResponse ce = proveClosestEncloser(qname,
851 return SecurityStatus.BOGUS;
854 // If we had the closest encloser proof, then we need to check that the
855 // covering NSEC3 was opt-in -- the proveClosestEncloser step already
856 // checked to see if the closest encloser was a delegation or DNAME.
857 if (ce.nc_nsec3.getOptInFlag())
859 return SecurityStatus.SECURE;
862 return SecurityStatus.BOGUS;