/* * dnssecjava - a DNSSEC validating stub resolver for Java * Copyright (c) 2013-2015 Ingo Bauersachs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * This file is based on work under the following copyright and permission * notice: * * Copyright (c) 2005 VeriSign. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.jitsi.dnssec.validator; import java.security.NoSuchAlgorithmException; import java.security.interfaces.DSAPublicKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPublicKey; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Properties; import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.jitsi.dnssec.SRRset; import org.jitsi.dnssec.SecurityStatus; import org.xbill.DNS.DNSKEYRecord; import org.xbill.DNS.DNSSEC.Algorithm; import org.xbill.DNS.DNSSEC.DNSSECException; import org.xbill.DNS.NSEC3Record; import org.xbill.DNS.NSEC3Record.Flags; import org.xbill.DNS.Name; import org.xbill.DNS.NameTooLongException; import org.xbill.DNS.TextParseException; import org.xbill.DNS.Type; import org.xbill.DNS.utils.base32; /** * NSEC3 non-existence proof utilities. */ final class NSEC3ValUtils { // The logger to use in static methods. private static final Logger logger = LoggerFactory.getLogger(NSEC3ValUtils.class); private static final Name ASTERISK_LABEL = Name.fromConstantString("*"); private static final int MAX_ITERATION_COUNT = 65536; private TreeMap<Integer, Integer> maxIterations; /** * Creates a new instance of this class. */ NSEC3ValUtils() { // see RFC5155#10.3 for the max iteration count // CHECKSTYLE:OFF this.maxIterations = new TreeMap<Integer, Integer>(); this.maxIterations.put(1024, 150); this.maxIterations.put(2048, 500); this.maxIterations.put(4096, 2500); // CHECKSTYLE:ON } /** * Loads the configuration data. Supported properties are: * <ul> * <li>org.jitsi.dnssec.nsec3.iterations.M=N</li> * </ul> * * @param config The configuration data. */ void init(Properties config) { boolean first = true; for (Map.Entry<?, ?> s : config.entrySet()) { String key = s.getKey().toString(); if (key.startsWith("org.jitsi.dnssec.nsec3.iterations")) { int keySize = Integer.parseInt(key.substring(key.lastIndexOf(".") + 1)); int iters = Integer.parseInt(s.getValue().toString()); if (iters > MAX_ITERATION_COUNT) { throw new IllegalArgumentException("Iteration count too high."); } if (first) { first = false; this.maxIterations.clear(); } this.maxIterations.put(keySize, iters); } } } /** * This is just a simple class to encapsulate the response to a closest * encloser proof. */ private final class CEResponse { private Name closestEncloser; private NSEC3Record ceNsec3; private NSEC3Record ncNsec3; /** * <ul> * <li>bogus if no closest encloser could be proven.</li> * <li>secure if a closest encloser could be proven, ce is set.</li> * <li>insecure if the closest-encloser candidate turns out to prove * that an insecure delegation exists above the qname.</li> * </ul> */ private SecurityStatus status = SecurityStatus.UNCHECKED; private CEResponse(Name ce, NSEC3Record nsec3) { this.closestEncloser = ce; this.ceNsec3 = nsec3; } } private boolean supportsHashAlgorithm(int alg) { return alg == NSEC3Record.SHA1_DIGEST_ID; } /** * Remove all records whose algorithm is unknown. * * @param nsec3s List of NSEC3 records to check. The list is modified by * this method. */ public void stripUnknownAlgNSEC3s(List<SRRset> nsec3s) { for (ListIterator<SRRset> i = nsec3s.listIterator(); i.hasNext();) { NSEC3Record nsec3 = (NSEC3Record)i.next().first(); if (!this.supportsHashAlgorithm(nsec3.getHashAlgorithm())) { i.remove(); } } } /** * Given the name of a closest encloser, return the name *.closest_encloser. * * @param closestEncloser The name to start with. * @return The wildcard name. */ private Name ceWildcard(Name closestEncloser) { try { return Name.concatenate(ASTERISK_LABEL, closestEncloser); } catch (NameTooLongException e) { return null; } } /** * Given a qname and its proven closest encloser, calculate the "next * closest" name. Basically, this is the name that is one label longer than * the closest encloser that is still a subdomain of qname. * * @param qname The qname. * @param closestEncloser The closest encloser name. * @return The next closer name. */ private Name nextClosest(Name qname, Name closestEncloser) { int strip = qname.labels() - closestEncloser.labels() - 1; return (strip > 0) ? new Name(qname, strip) : qname; } /** * Find the NSEC3Record that matches a hash of a name. * * @param name The name to find. * @param zonename The name of the zone that the NSEC3s are from. * @param nsec3s A list of NSEC3Records from a given message. * * @return The matching NSEC3Record if one is present, null otherwise. */ private NSEC3Record findMatchingNSEC3(Name name, Name zonename, List<SRRset> nsec3s) { base32 b32 = new base32(base32.Alphabet.BASE32HEX, false, false); for (SRRset set : nsec3s) { try { NSEC3Record nsec3 = (NSEC3Record)set.first(); byte[] hash = nsec3.hashName(name); Name complete = new Name(b32.toString(hash), zonename); if (complete.equals(nsec3.getName())) { return nsec3; } } catch (NoSuchAlgorithmException e) { logger.debug("Unrecognized NSEC3 in set:" + set, e); } catch (TextParseException e) { logger.debug("Unrecognized NSEC3 in set:" + set, e); } } return null; } /** * Given a hash and a candidate NSEC3Record, determine if that NSEC3Record * covers the hash. Covers specifically means that the hash is in between * the owner and next hashes and does not equal either. * * @param nsec3 The candidate NSEC3Record. * @param zonename The zone name. * @param hash The precalculated hash. * @return True if the NSEC3Record covers the hash. */ private boolean nsec3Covers(NSEC3Record nsec3, Name zonename, byte[] hash) { if (!new Name(nsec3.getName(), 1).equals(zonename)) { return false; } byte[] owner = new base32(base32.Alphabet.BASE32HEX, false, false).fromString(nsec3.getName().getLabelString(0)); byte[] next = nsec3.getNext(); // This is the "normal case: owner < next and owner < hash < next ByteArrayComparator bac = new ByteArrayComparator(); if (bac.compare(owner, hash) < 0 && bac.compare(hash, next) < 0) { return true; } // this is the end of zone case: // next <= owner && (hash > owner || hash < next) if (bac.compare(next, owner) <= 0 && (bac.compare(hash, owner) > 0 || bac.compare(hash, next) < 0)) { return true; } // Otherwise, the NSEC3 does not cover the hash. return false; } /** * Given a pre-hashed name, find a covering NSEC3 from among a list of * NSEC3s. * * @param name The name to consider. * @param zonename The name of the zone. * @param nsec3s The list of NSEC3s present in a message. * @return A covering NSEC3 if one is present, null otherwise. */ private NSEC3Record findCoveringNSEC3(Name name, Name zonename, List<SRRset> nsec3s) { for (SRRset set : nsec3s) { try { NSEC3Record nsec3 = (NSEC3Record)set.first(); byte[] hash = nsec3.hashName(name); if (this.nsec3Covers(nsec3, zonename, hash)) { return nsec3; } } catch (NoSuchAlgorithmException e) { logger.debug("Unrecognized NSEC3 in set:" + set, e); } } return null; } /** * Given a name and a list of NSEC3s, find the candidate closest encloser. * This will be the first ancestor of 'name' (including itself) to have a * matching NSEC3 RR. * * @param name The name the start with. * @param zonename The name of the zone that the NSEC3s came from. * @param nsec3s The list of NSEC3s. * * @return A CEResponse containing the closest encloser name and the NSEC3 * RR that matched it, or null if there wasn't one. */ private CEResponse findClosestEncloser(Name name, Name zonename, List<SRRset> nsec3s) { // This scans from longest name to shortest, so the first match we find // is the only viable candidate. // FIXME: modify so that the NSEC3 matching the zone apex need not be // present. while (name.labels() >= zonename.labels()) { NSEC3Record nsec3 = this.findMatchingNSEC3(name, zonename, nsec3s); if (nsec3 != null) { return new CEResponse(name, nsec3); } name = new Name(name, 1); } return null; } /** * Given a List of nsec3 RRs, find and prove the closest encloser to qname. * * @param qname The qname in question. * @param zonename The name of the zone that the NSEC3 RRs come from. * @param nsec3s The list of NSEC3s found the this response (already * verified). * @return A CEResponse object which contains the closest encloser name and * the NSEC3 that matches it. */ private CEResponse proveClosestEncloser(Name qname, Name zonename, List<SRRset> nsec3s) { CEResponse candidate = this.findClosestEncloser(qname, zonename, nsec3s); if (candidate == null) { logger.debug("proveClosestEncloser: could not find a candidate for the closest encloser."); candidate = new CEResponse(Name.empty, null); candidate.status = SecurityStatus.BOGUS; return candidate; } if (candidate.closestEncloser.equals(qname)) { logger.debug("proveClosestEncloser: proved that qname existed!"); candidate.status = SecurityStatus.BOGUS; return candidate; } // If the closest encloser is actually a delegation, then the response // should have been a referral. If it is a DNAME, then it should have // been a DNAME response. if (candidate.ceNsec3.hasType(Type.NS) && !candidate.ceNsec3.hasType(Type.SOA)) { if (!candidate.ceNsec3.hasType(Type.DS)) { candidate.status = SecurityStatus.INSECURE; return candidate; } logger.debug("proveClosestEncloser: closest encloser was a delegation!"); candidate.status = SecurityStatus.BOGUS; return candidate; } if (candidate.ceNsec3.hasType(Type.DNAME)) { logger.debug("proveClosestEncloser: closest encloser was a DNAME!"); candidate.status = SecurityStatus.BOGUS; return candidate; } // Otherwise, we need to show that the next closer name is covered. Name nextClosest = this.nextClosest(qname, candidate.closestEncloser); candidate.ncNsec3 = this.findCoveringNSEC3(nextClosest, zonename, nsec3s); if (candidate.ncNsec3 == null) { logger.debug("Could not find proof that the closest encloser was the closest encloser"); candidate.status = SecurityStatus.BOGUS; return candidate; } candidate.status = SecurityStatus.SECURE; return candidate; } private boolean validIterations(SRRset nsec, KeyCache keyCache) { SRRset dnskeyRrset = keyCache.find(nsec.getSignerName(), nsec.getDClass()).getRRset(); // for now, we return the maximum iterations based simply on the key // algorithms that may have been used to sign the NSEC3 RRsets. try { for (Iterator<?> i = dnskeyRrset.rrs(); i.hasNext();) { DNSKEYRecord dnskey = (DNSKEYRecord)i.next(); int keysize; switch (dnskey.getAlgorithm()) { case Algorithm.RSAMD5: return false; // obsoleted by rfc6944 case Algorithm.RSASHA1: case Algorithm.RSASHA256: case Algorithm.RSASHA512: case Algorithm.RSA_NSEC3_SHA1: keysize = ((RSAPublicKey)dnskey.getPublicKey()).getModulus().bitLength(); break; case Algorithm.DSA: case Algorithm.DSA_NSEC3_SHA1: keysize = ((DSAPublicKey)dnskey.getPublicKey()).getParams().getP().bitLength(); break; case Algorithm.ECDSAP256SHA256: case Algorithm.ECDSAP384SHA384: keysize = ((ECPublicKey)dnskey.getPublicKey()).getParams().getCurve().getField().getFieldSize(); break; default: return false; } Integer keyIters = this.maxIterations.floorKey(keysize); if (keyIters == null) { keyIters = this.maxIterations.firstKey(); } keyIters = this.maxIterations.get(keyIters); if (((NSEC3Record)nsec.first()).getIterations() > keyIters) { return false; } } return true; } catch (DNSSECException e) { logger.error("Could not get public key from NSEC3 record", e); return false; } } /** * Determine if all of the NSEC3s in a response are legally ignoreable * (i.e., their presence should lead to an INSECURE result). Currently, this * is solely based on iterations. * * @param nsec3s The list of NSEC3s. If there is more than one set of NSEC3 * parameters present, this test will not be performed. * @param dnskeyRrset The set of validating DNSKEYs. * @return true if all of the NSEC3s can be legally ignored, false if not. */ public boolean allNSEC3sIgnoreable(List<SRRset> nsec3s, KeyCache dnskeyRrset) { Map<Name, NSEC3Record> foundNsecs = new HashMap<Name, NSEC3Record>(); ByteArrayComparator comp = new ByteArrayComparator(); for (SRRset set : nsec3s) { @SuppressWarnings("unchecked") Iterator<NSEC3Record> it = (Iterator<NSEC3Record>)set.rrs(); while (it.hasNext()) { NSEC3Record current = it.next(); Name key = new Name(current.getName(), 1); NSEC3Record previous = foundNsecs.get(key); if (previous != null) { if (current.getHashAlgorithm() != previous.getHashAlgorithm()) { return true; } if (current.getIterations() != previous.getIterations()) { return true; } if (current.getSalt() == null ^ previous.getSalt() == null) { return true; } if (current.getSalt() != null && comp.compare(current.getSalt(), previous.getSalt()) != 0) { return true; } } else { foundNsecs.put(key, current); } } } for (SRRset set : nsec3s) { if (this.validIterations(set, dnskeyRrset)) { return false; } } return true; } /** * Determine if the set of NSEC3 records provided with a response prove NAME * ERROR. This means that the NSEC3s prove a) the closest encloser exists, * b) the direct child of the closest encloser towards qname doesn't exist, * and c) *.closest encloser does not exist. * * @param nsec3s The list of NSEC3s. * @param qname The query name to check against. * @param zonename This is the name of the zone that the NSEC3s belong to. * This may be discovered in any number of ways. A good one is to * use the signerName from the NSEC3 record's RRSIG. * @return {@link SecurityStatus#SECURE} of the Name Error is proven by the * NSEC3 RRs, {@link SecurityStatus#BOGUS} if not, * {@link SecurityStatus#INSECURE} if all of the NSEC3s could be * validly ignored. */ public SecurityStatus proveNameError(List<SRRset> nsec3s, Name qname, Name zonename) { if (nsec3s == null || nsec3s.size() == 0) { return SecurityStatus.BOGUS; } // First locate and prove the closest encloser to qname. We will use the // variant that fails if the closest encloser turns out to be qname. CEResponse ce = this.proveClosestEncloser(qname, zonename, nsec3s); if (ce.status != SecurityStatus.SECURE) { logger.debug("proveNameError: failed to prove a closest encloser."); return ce.status; } // At this point, we know that qname does not exist. Now we need to // prove // that the wildcard does not exist. Name wc = this.ceWildcard(ce.closestEncloser); NSEC3Record nsec3 = this.findCoveringNSEC3(wc, zonename, nsec3s); if (nsec3 == null) { logger.debug("proveNameError: could not prove that the applicable wildcard did not exist."); return SecurityStatus.BOGUS; } if ((ce.ncNsec3.getFlags() & Flags.OPT_OUT) == Flags.OPT_OUT) { logger.debug("nsec3 nameerror proof: nc has optout"); return SecurityStatus.INSECURE; } return SecurityStatus.SECURE; } /** * Determine if the NSEC3s provided in a response prove the NOERROR/NODATA * status. There are a number of different variants to this: * * 1) Normal NODATA -- qname is matched to an NSEC3 record, type is not * present. * * 2) ENT NODATA -- because there must be NSEC3 record for * empty-non-terminals, this is the same as #1. * * 3) NSEC3 ownername NODATA -- qname matched an existing, lone NSEC3 * ownername, but qtype was not NSEC3. NOTE: as of nsec-05, this case no * longer exists. * * 4) Wildcard NODATA -- A wildcard matched the name, but not the type. * * 5) Opt-In DS NODATA -- the qname is covered by an opt-in span and qtype * == DS. (or maybe some future record with the same parent-side-only * property) * * @param nsec3s The NSEC3Records to consider. * @param qname The qname in question. * @param qtype The qtype in question. * @param zonename The name of the zone that the NSEC3s came from. * @return {@link SecurityStatus#SECURE} if the NSEC3s prove the * proposition, {@link SecurityStatus#INSECURE} if qname is under * opt-out, {@link SecurityStatus#BOGUS} otherwise. */ public SecurityStatus proveNodata(List<SRRset> nsec3s, Name qname, int qtype, Name zonename) { if (nsec3s == null || nsec3s.size() == 0) { return SecurityStatus.BOGUS; } NSEC3Record nsec3 = this.findMatchingNSEC3(qname, zonename, nsec3s); // Cases 1 & 2. if (nsec3 != null) { if (nsec3.hasType(qtype)) { logger.debug("proveNodata: Matching NSEC3 proved that type existed!"); return SecurityStatus.BOGUS; } if (nsec3.hasType(Type.CNAME)) { logger.debug("proveNodata: Matching NSEC3 proved that a CNAME existed!"); return SecurityStatus.BOGUS; } if (qtype == Type.DS && nsec3.hasType(Type.SOA) && !Name.root.equals(qname)) { logger.debug("proveNodata: apex NSEC3 abused for no DS proof, bogus"); return SecurityStatus.BOGUS; } else if (qtype != Type.DS && nsec3.hasType(Type.NS) && !nsec3.hasType(Type.SOA)) { if (!nsec3.hasType(Type.DS)) { logger.debug("proveNodata: matching NSEC3 is insecure delegation"); return SecurityStatus.INSECURE; } logger.debug("proveNodata: matching NSEC3 is a delegation, bogus"); return SecurityStatus.BOGUS; } return SecurityStatus.SECURE; } // For cases 3 - 5, we need the proven closest encloser, and it can't // match qname. Although, at this point, we know that it won't since we // just checked that. CEResponse ce = this.proveClosestEncloser(qname, zonename, nsec3s); // At this point, not finding a match or a proven closest encloser is a // problem. if (ce.status == SecurityStatus.BOGUS) { logger.debug("proveNodata: did not match qname, nor found a proven closest encloser."); return SecurityStatus.BOGUS; } else if (ce.status == SecurityStatus.INSECURE && qtype != Type.DS) { logger.debug("proveNodata: closest nsec3 is insecure delegation."); return SecurityStatus.INSECURE; } // Case 3: REMOVED // Case 4: Name wc = this.ceWildcard(ce.closestEncloser); nsec3 = this.findMatchingNSEC3(wc, zonename, nsec3s); if (nsec3 != null) { if (nsec3.hasType(qtype)) { logger.debug("proveNodata: matching wildcard had qtype!"); return SecurityStatus.BOGUS; } else if (nsec3.hasType(Type.CNAME)) { logger.debug("nsec3 nodata proof: matching wildcard had a CNAME, bogus"); return SecurityStatus.BOGUS; } if (qtype == Type.DS && qname.labels() != 1 && nsec3.hasType(Type.SOA)) { logger.debug("nsec3 nodata proof: matching wildcard for no DS proof has a SOA, bogus"); return SecurityStatus.BOGUS; } else if (qtype != Type.DS && nsec3.hasType(Type.NS) && !nsec3.hasType(Type.SOA)) { logger.debug("nsec3 nodata proof: matching wilcard is a delegation, bogus"); return SecurityStatus.BOGUS; } if (ce.ncNsec3 != null && (ce.ncNsec3.getFlags() & Flags.OPT_OUT) == Flags.OPT_OUT) { logger.debug("nsec3 nodata proof: matching wildcard is in optout range, insecure"); return SecurityStatus.INSECURE; } return SecurityStatus.SECURE; } // Case 5. // Due to forwarders, cnames, and other collating effects, we // can see the ordinary unsigned data from a zone beneath an // insecure delegation under an optout here */ if (ce.ncNsec3 == null) { logger.debug("nsec3 nodata proof: no next closer nsec3"); return SecurityStatus.BOGUS; } // We need to make sure that the covering NSEC3 is opt-out. if ((ce.ncNsec3.getFlags() & Flags.OPT_OUT) == 0) { if (qtype != Type.DS) { logger.debug("proveNodata: covering NSEC3 was not opt-out in an opt-out DS NOERROR/NODATA case."); } else { logger.debug("proveNodata: could not find matching NSEC3, nor matching wildcard, and qtype is not DS -- no more options."); } return SecurityStatus.BOGUS; } // RFC5155 section 9.2: if nc has optout then no AD flag set return SecurityStatus.INSECURE; } /** * Prove that a positive wildcard match was appropriate (no direct match * RRset). * * @param nsec3s The NSEC3 records to work with. * @param qname The qname that was matched to the wildard * @param zonename The name of the zone that the NSEC3s come from. * @param wildcard The purported wildcard that matched. * @return true if the NSEC3 records prove this case. */ public SecurityStatus proveWildcard(List<SRRset> nsec3s, Name qname, Name zonename, Name wildcard) { if (nsec3s == null || nsec3s.size() == 0 || qname == null || wildcard == null) { return SecurityStatus.BOGUS; } // We know what the (purported) closest encloser is by just looking at // the supposed generating wildcard. CEResponse candidate = new CEResponse(new Name(wildcard, 1), null); // Now we still need to prove that the original data did not exist. // Otherwise, we need to show that the next closer name is covered. Name nextClosest = this.nextClosest(qname, candidate.closestEncloser); candidate.ncNsec3 = this.findCoveringNSEC3(nextClosest, zonename, nsec3s); if (candidate.ncNsec3 == null) { logger.debug("proveWildcard: did not find a covering NSEC3 that covered the next closer name to " + qname + " from " + candidate.closestEncloser + " (derived from wildcard " + wildcard + ")"); return SecurityStatus.BOGUS; } if ((candidate.ncNsec3.getFlags() & Flags.OPT_OUT) == Flags.OPT_OUT) { return SecurityStatus.INSECURE; } return SecurityStatus.SECURE; } /** * Prove that a DS response either had no DS, or wasn't a delegation point. * * Fundamentally there are two cases here: normal NODATA and Opt-In NODATA. * * @param nsec3s The NSEC3 RRs to examine. * @param qname The name of the DS in question. * @param zonename The name of the zone that the NSEC3 RRs come from. * * @return SecurityStatus.SECURE if it was proven that there is no DS in a * secure (i.e., not opt-in) way, SecurityStatus.INSECURE if there * was no DS in an insecure (i.e., opt-in) way, * SecurityStatus.INDETERMINATE if it was clear that this wasn't a * delegation point, and SecurityStatus.BOGUS if the proofs don't * work out. */ public SecurityStatus proveNoDS(List<SRRset> nsec3s, Name qname, Name zonename) { if (nsec3s == null || nsec3s.size() == 0) { return SecurityStatus.BOGUS; } // Look for a matching NSEC3 to qname -- this is the normal NODATA case. NSEC3Record nsec3 = this.findMatchingNSEC3(qname, zonename, nsec3s); if (nsec3 != null) { // If the matching NSEC3 has the SOA bit set, it is from the wrong // zone (the child instead of the parent). If it has the DS bit set, // then we were lied to. if (nsec3.hasType(Type.SOA) || nsec3.hasType(Type.DS)) { return SecurityStatus.BOGUS; } // If the NSEC3 RR doesn't have the NS bit set, then this wasn't a // delegation point. if (!nsec3.hasType(Type.NS)) { return SecurityStatus.INDETERMINATE; } // Otherwise, this proves no DS. return SecurityStatus.SECURE; } // Otherwise, we are probably in the opt-in case. CEResponse ce = this.proveClosestEncloser(qname, zonename, nsec3s); if (ce.status != SecurityStatus.SECURE) { return SecurityStatus.BOGUS; } // If we had the closest encloser proof, then we need to check that the // covering NSEC3 was opt-in -- the proveClosestEncloser step already // checked to see if the closest encloser was a delegation or DNAME. if (ce.ncNsec3.getFlags() == 1) { return SecurityStatus.SECURE; } return SecurityStatus.BOGUS; } }