+/*
+ * $Id$
+ *
+ * Copyright (c) 2006 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 se.rfc.unbound;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+
+import org.apache.log4j.Logger;
+import org.xbill.DNS.*;
+import org.xbill.DNS.utils.base32;
+
+import se.rfc.unbound.validator.DnsSecVerifier;
+import se.rfc.unbound.validator.SignUtils;
+import se.rfc.unbound.validator.SignUtils.ByteArrayComparator;
+
+public class NSEC3ValUtils
+{
+
+ // FIXME: should probably refactor to handle different NSEC3 parameters more
+ // efficiently.
+ // Given a list of NSEC3 RRs, they should be grouped according to
+ // parameters. The idea is to hash and compare for each group independently,
+ // instead of having to skip NSEC3 RRs with the wrong parameters.
+
+ // The logger to use in static methods.
+ private static Logger st_log = Logger.getLogger(NSEC3ValUtils.class);
+
+ private static Name asterisk_label = Name.fromConstantString("*");
+
+ /**
+ * This is a class to encapsulate a unique set of NSEC3 parameters:
+ * algorithm, iterations, and salt.
+ */
+ private static class NSEC3Parameters
+ {
+ public byte alg;
+ public byte[] salt;
+ public int iterations;
+
+ public NSEC3Parameters(NSEC3Record r)
+ {
+ alg = r.getHashAlgorithm();
+ salt = r.getSalt();
+ iterations = r.getIterations();
+ }
+
+ public boolean match(NSEC3Record r, ByteArrayComparator bac)
+ {
+ if (r.getHashAlgorithm() != alg) return false;
+ if (r.getIterations() != iterations) return false;
+
+ if (salt == null && r.getSalt() != null) return false;
+
+ if (bac == null) bac = new ByteArrayComparator();
+ return bac.compare(r.getSalt(), salt) == 0;
+ }
+ }
+
+ /**
+ * This is just a simple class to enapsulate the response to a closest
+ * encloser proof.
+ */
+ private static class CEResponse
+ {
+ public Name closestEncloser;
+ public NSEC3Record ce_nsec3;
+ public NSEC3Record nc_nsec3;
+
+ public CEResponse(Name ce, NSEC3Record nsec3)
+ {
+ this.closestEncloser = ce;
+ this.ce_nsec3 = nsec3;
+ }
+ }
+
+ public static boolean supportsHashAlgorithm(int alg)
+ {
+ if (alg == NSEC3Record.SHA1_DIGEST_ID) return true;
+ return false;
+ }
+
+ public static void stripUnknownAlgNSEC3s(List nsec3s)
+ {
+ if (nsec3s == null) return;
+ for (ListIterator i = nsec3s.listIterator(); i.hasNext(); )
+ {
+ NSEC3Record nsec3 = (NSEC3Record) i.next();
+ if (!supportsHashAlgorithm(nsec3.getHashAlgorithm()))
+ {
+ i.remove();
+ }
+ }
+ }
+
+ /**
+ * Given a list of NSEC3Records that are part of a message, determine the
+ * NSEC3 parameters (hash algorithm, iterations, and salt) present. If there
+ * is more than one distinct grouping, return null;
+ *
+ * @param nsec3s A list of NSEC3Record object.
+ * @return A set containing a number of objects (NSEC3Parameter objects)
+ * that correspond to each distinct set of parameters, or null if
+ * the nsec3s list was empty.
+ */
+ public static NSEC3Parameters nsec3Parameters(List nsec3s)
+ {
+ if (nsec3s == null || nsec3s.size() == 0) return null;
+
+ NSEC3Parameters params = new NSEC3Parameters((NSEC3Record) nsec3s.get(0));
+ ByteArrayComparator bac = new ByteArrayComparator();
+
+ for (Iterator i = nsec3s.iterator(); i.hasNext();)
+ {
+ if (! params.match((NSEC3Record) i.next(), bac))
+ {
+ return null;
+ }
+ }
+ return params;
+ }
+
+ /**
+ * In a list of NSEC3Record object pulled from a given message, find the
+ * NSEC3 that directly matches a given name, without hashing.
+ *
+ * @param n The name in question.
+ * @param nsec3s A list of NSEC3Records from a given message.
+ * @return The matching NSEC3Record, or null if there wasn't one.
+ */
+ // private static NSEC3Record findDirectMatchingNSEC3(Name n, List nsec3s)
+ // {
+ // if (n == null || nsec3s == null) return null;
+ //
+ // for (Iterator i = nsec3s.iterator(); i.hasNext();)
+ // {
+ // NSEC3Record nsec3 = (NSEC3Record) i.next();
+ // if (n.equals(nsec3.getName())) return nsec3;
+ // }
+ //
+ // return null;
+ // }
+ /**
+ * Given a hash and an a zone name, construct an NSEC3 ownername.
+ *
+ * @param hash The hash of an original name.
+ * @param zonename The zone to use in constructing the NSEC3 name.
+ * @return The NSEC3 name.
+ */
+ private static Name hashName(byte[] hash, Name zonename)
+ {
+ try
+ {
+ return new Name(base32.toString(hash).toLowerCase(), zonename);
+ }
+ catch (TextParseException e)
+ {
+ // Note, this should never happen.
+ return null;
+ }
+ }
+
+ /**
+ * Given a set of NSEC3 parameters, hash a name.
+ *
+ * @param name The name to hash.
+ * @param params The parameters to hash with.
+ * @return The hash.
+ */
+ private static byte[] hash(Name name, NSEC3Parameters params)
+ {
+ try
+ {
+ return NSEC3Record.hash(name,
+ params.alg,
+ params.iterations,
+ params.salt);
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ st_log.debug("Did not recognize hash algorithm: " + params.alg);
+ return null;
+ }
+ }
+
+ /**
+ * Given the name of a closest encloser, return the name *.closest_encloser.
+ *
+ * @param closestEncloser The name to start with.
+ * @return The wildcard name.
+ */
+ private static Name ceWildcard(Name closestEncloser)
+ {
+ try
+ {
+ Name wc = Name.concatenate(asterisk_label, closestEncloser);
+ return wc;
+ }
+ 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 static 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 hash The pre-calculated hash of a name.
+ * @param zonename The name of the zone that the NSEC3s are from.
+ * @param nsec3s A list of NSEC3Records from a given message.
+ * @param params The parameters used for calculating the hash.
+ * @param bac An already allocated ByteArrayComparator, for reuse. This may
+ * be null.
+ *
+ * @return The matching NSEC3Record, if one is present.
+ */
+ private static NSEC3Record findMatchingNSEC3(byte[] hash, Name zonename,
+ List nsec3s, NSEC3Parameters params, ByteArrayComparator bac)
+ {
+ Name n = hashName(hash, zonename);
+
+ for (Iterator i = nsec3s.iterator(); i.hasNext();)
+ {
+ NSEC3Record nsec3 = (NSEC3Record) i.next();
+ // Skip nsec3 records that are using different parameters.
+ if (!params.match(nsec3, bac)) continue;
+ if (n.equals(nsec3.getName())) return nsec3;
+ }
+ 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 hash The precalculated hash.
+ * @param bac An already allocated comparator. This may be null.
+ * @return True if the NSEC3Record covers the hash.
+ */
+ private static boolean nsec3Covers(NSEC3Record nsec3, byte[] hash,
+ ByteArrayComparator bac)
+ {
+ byte[] owner = nsec3.getOwner();
+ byte[] next = nsec3.getNext();
+
+ // This is the "normal case: owner < next and owner < hash < next
+ 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, next) < 0 || bac.compare(owner, hash) < 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 hash The hash to consider.
+ * @param zonename The name of the zone.
+ * @param nsec3s The list of NSEC3s present in a message.
+ * @param params The NSEC3 parameters used to generate the hash -- NSEC3s
+ * that do not use those parameters will be skipped.
+ *
+ * @return A covering NSEC3 if one is present, null otherwise.
+ */
+ private static NSEC3Record findCoveringNSEC3(byte[] hash, Name zonename,
+ List nsec3s, NSEC3Parameters params, ByteArrayComparator bac)
+ {
+ ByteArrayComparator comparator = new ByteArrayComparator();
+
+ for (Iterator i = nsec3s.iterator(); i.hasNext();)
+ {
+ NSEC3Record nsec3 = (NSEC3Record) i.next();
+ if (!params.match(nsec3, bac)) continue;
+
+ if (nsec3Covers(nsec3, hash, comparator)) return nsec3;
+ }
+
+ 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.
+ * @param nsec3params The NSEC3 parameters.
+ * @param bac A pre-allocated comparator. May be null.
+ *
+ * @return A CEResponse containing the closest encloser name and the NSEC3
+ * RR that matched it, or null if there wasn't one.
+ */
+ private static CEResponse findClosestEncloser(Name name, Name zonename,
+ List nsec3s, NSEC3Parameters params, ByteArrayComparator bac)
+ {
+ Name n = name;
+
+ NSEC3Record nsec3;
+
+ // 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 (n.labels() >= zonename.labels())
+ {
+ nsec3 = findMatchingNSEC3(hash(n, params), zonename, nsec3s, params, bac);
+ if (nsec3 != null) return new CEResponse(n, nsec3);
+ n = new Name(n, 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).
+ * @param params The NSEC3 parameters found in the response.
+ * @param bac A pre-allocated comparator. May be null.
+ * @param proveDoesNotExist If true, then if the closest encloser turns out
+ * to be qname, then null is returned.
+ * @return null if the proof isn't completed. Otherwise, return a CEResponse
+ * object which contains the closest encloser name and the NSEC3
+ * that matches it.
+ */
+ private static CEResponse proveClosestEncloser(Name qname, Name zonename,
+ List nsec3s, NSEC3Parameters params, ByteArrayComparator bac,
+ boolean proveDoesNotExist)
+ {
+ CEResponse candidate = findClosestEncloser(qname,
+ zonename,
+ nsec3s,
+ params,
+ bac);
+
+ if (candidate == null)
+ {
+ st_log.debug("proveClosestEncloser: could not find a "
+ + "candidate for the closest encloser.");
+ return null;
+ }
+
+ if (candidate.closestEncloser.equals(qname))
+ {
+ if (proveDoesNotExist)
+ {
+ st_log.debug("proveClosestEncloser: proved that qname existed!");
+ return null;
+ }
+ // otherwise, we need to nothing else to prove that qname is its own
+ // closest encloser.
+ 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.ce_nsec3.hasType(Type.NS)
+ && !candidate.ce_nsec3.hasType(Type.SOA))
+ {
+ st_log.debug("proveClosestEncloser: closest encloser "
+ + "was a delegation!");
+ return null;
+ }
+ if (candidate.ce_nsec3.hasType(Type.DNAME))
+ {
+ st_log.debug("proveClosestEncloser: closest encloser was a DNAME!");
+ return null;
+ }
+
+ // Otherwise, we need to show that the next closer name is covered.
+ Name nextClosest = nextClosest(qname, candidate.closestEncloser);
+
+ byte[] nc_hash = hash(nextClosest, params);
+ candidate.nc_nsec3 = findCoveringNSEC3(nc_hash,
+ zonename,
+ nsec3s,
+ params,
+ bac);
+ if (candidate.nc_nsec3 == null)
+ {
+ st_log.debug("Could not find proof that the "
+ + "closest encloser was the closest encloser");
+ return null;
+ }
+
+ return candidate;
+ }
+
+ private static int maxIterations(int baseAlg, int keysize)
+ {
+ switch (baseAlg)
+ {
+ case DnsSecVerifier.RSA:
+ if (keysize == 0) return 2500; // the max at 4096
+ if (keysize > 2048) return 2500;
+ if (keysize > 1024) return 500;
+ if (keysize > 0) return 150;
+ break;
+ case DnsSecVerifier.DSA:
+ if (keysize == 0) return 5000; // the max at 2048;
+ if (keysize > 1024) return 5000;
+ if (keysize > 0) return 1500;
+ break;
+ }
+ return -1;
+ }
+
+ private static boolean validIterations(NSEC3Parameters nsec3params,
+ RRset dnskey_rrset, DnsSecVerifier verifier)
+ {
+ // for now, we return the maximum iterations based simply on the key
+ // algorithms that may have been used to sign the NSEC3 RRsets.
+
+ int max_iterations = 0;
+ for (Iterator i = dnskey_rrset.rrs(); i.hasNext();)
+ {
+ DNSKEYRecord dnskey = (DNSKEYRecord) i.next();
+ int baseAlg = verifier.baseAlgorithm(dnskey.getAlgorithm());
+ int iters = maxIterations(baseAlg, 0);
+ max_iterations = max_iterations < iters ? iters : max_iterations;
+ }
+
+ if (nsec3params.iterations > max_iterations) return false;
+
+ return true;
+ }
+
+ /**
+ * 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 dnskey_rrset The set of validating DNSKEYs.
+ * @param verifier The verifier used to verify the NSEC3 RRsets. This is
+ * solely used to map algorithm aliases.
+ * @return true if all of the NSEC3s can be legally ignored, false if not.
+ */
+ public static boolean allNSEC3sIgnoreable(List nsec3s, RRset dnskey_rrset, DnsSecVerifier verifier)
+ {
+ NSEC3Parameters params = nsec3Parameters(nsec3s);
+ if (params == null) return false;
+
+ return !validIterations(params, dnskey_rrset, verifier);
+ }
+
+ /**
+ * 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 SecurityStatus.SECURE of the Name Error is proven by the NSEC3
+ * RRs, BOGUS if not, INSECURE if all of the NSEC3s could be validly
+ * ignored.
+ */
+ public static boolean proveNameError(List nsec3s, Name qname, Name zonename)
+ {
+ if (nsec3s == null || nsec3s.size() == 0) return false;
+
+ NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
+ if (nsec3params == null)
+ {
+ st_log.debug("Could not find a single set of " +
+ "NSEC3 parameters (multiple parameters present).");
+ return false;
+ }
+
+ ByteArrayComparator bac = new ByteArrayComparator();
+
+ // 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 = proveClosestEncloser(qname,
+ zonename,
+ nsec3s,
+ nsec3params,
+ bac,
+ true);
+
+ if (ce == null)
+ {
+ st_log.debug("proveNameError: failed to prove a closest encloser.");
+ return false;
+ }
+
+ // At this point, we know that qname does not exist. Now we need to prove
+ // that the wildcard does not exist.
+ Name wc = ceWildcard(ce.closestEncloser);
+ byte[] wc_hash = hash(wc, nsec3params);
+ NSEC3Record nsec3 = findCoveringNSEC3(wc_hash,
+ zonename,
+ nsec3s,
+ nsec3params,
+ bac);
+ if (nsec3 == null)
+ {
+ st_log.debug("proveNameError: could not prove that the "
+ + "applicable wildcard did not exist.");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Determine if the set of NSEC3 records provided with a response prove NAME
+ * ERROR when qtype = NSEC3. This is a special case, and (currently anyway)
+ * it suffices to simply prove that the NSEC3 RRset itself does not exist,
+ * without proving that no wildcard could have generated it, etc..
+ *
+ * @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 true of the Name Error is proven by the NSEC3 RRs, false if not.
+ */
+ // public static boolean proveNSEC3NameError(List nsec3s, Name qname,
+ // Name zonename)
+ // {
+ // if (nsec3s == null || nsec3s.size() == 0) return false;
+ //
+ // for (Iterator i = nsec3s.iterator(); i.hasNext(); )
+ // {
+ // NSEC3Record nsec3 = (NSEC3Record) i.next();
+ //
+ // // Convert owner and next into Names.
+ // Name owner = nsec3.getName();
+ // Name next = null;
+ // try
+ // {
+ // next = new Name(base32.toString(nsec3.getNext()), zonename);
+ // }
+ // catch (TextParseException e)
+ // {
+ // continue;
+ // }
+ //
+ // // Now see if qname is covered by the NSEC3.
+ //
+ // // normal case, owner < qname < next.
+ // if (owner.compareTo(next) < 0 && owner.compareTo(qname) < 0 &&
+ // next.compareTo(qname) > 0)
+ // {
+ // st_log.debug("proveNSEC3NameError: found a covering NSEC3: " + nsec3);
+ // return true;
+ // }
+ // // end-of-zone case: next < owner and qname > owner || qname < next.
+ // if (owner.compareTo(next) > 0 && (owner.compareTo(qname) < 0 ||
+ // next.compareTo(qname) > 0))
+ // {
+ // st_log.debug("proveNSEC3NameError: found a covering NSEC3: " + nsec3);
+ // return true;
+ // }
+ // }
+ //
+ // st_log.debug("proveNSEC3NameError: did not find a covering NSEC3");
+ // return false;
+ // }
+ /**
+ * 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 true if the NSEC3s prove the proposition.
+ */
+ public static boolean proveNodata(List nsec3s, Name qname, int qtype,
+ Name zonename)
+ {
+ if (nsec3s == null || nsec3s.size() == 0) return false;
+
+ NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
+ if (nsec3params == null)
+ {
+ st_log.debug("could not find a single set of "
+ + "NSEC3 parameters (multiple parameters present)");
+ return false;
+ }
+ ByteArrayComparator bac = new ByteArrayComparator();
+
+ NSEC3Record nsec3 = findMatchingNSEC3(hash(qname, nsec3params),
+ zonename,
+ nsec3s,
+ nsec3params,
+ bac);
+ // Cases 1 & 2.
+ if (nsec3 != null)
+ {
+ if (nsec3.hasType(qtype))
+ {
+ st_log.debug("proveNodata: Matching NSEC3 proved that type existed!");
+ return false;
+ }
+ if (nsec3.hasType(Type.CNAME))
+ {
+ st_log.debug("proveNodata: Matching NSEC3 proved "
+ + "that a CNAME existed!");
+ return false;
+ }
+ return true;
+ }
+
+ // 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 = proveClosestEncloser(qname,
+ zonename,
+ nsec3s,
+ nsec3params,
+ bac,
+ true);
+
+ // At this point, not finding a match or a proven closest encloser is a
+ // problem.
+ if (ce == null)
+ {
+ st_log.debug("proveNodata: did not match qname, "
+ + "nor found a proven closest encloser.");
+ return false;
+ }
+
+ // Case 3: REMOVED
+
+ // Case 4:
+ Name wc = ceWildcard(ce.closestEncloser);
+ nsec3 = findMatchingNSEC3(hash(wc, nsec3params),
+ zonename,
+ nsec3s,
+ nsec3params,
+ bac);
+
+ if (nsec3 != null)
+ {
+ if (nsec3.hasType(qtype))
+ {
+ st_log.debug("proveNodata: matching wildcard had qtype!");
+ return false;
+ }
+ return true;
+ }
+
+ // Case 5.
+ if (qtype != Type.DS)
+ {
+ st_log.debug("proveNodata: could not find matching NSEC3, "
+ + "nor matching wildcard, and qtype is not DS -- no more options.");
+ return false;
+ }
+
+ // We need to make sure that the covering NSEC3 is opt-in.
+ if (!ce.nc_nsec3.getOptInFlag())
+ {
+ st_log.debug("proveNodata: covering NSEC3 was not "
+ + "opt-in in an opt-in DS NOERROR/NODATA case.");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 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 static boolean proveWildcard(List nsec3s, Name qname, Name zonename,
+ Name wildcard)
+ {
+ if (nsec3s == null || nsec3s.size() == 0) return false;
+ if (qname == null || wildcard == null) return false;
+
+ NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
+ if (nsec3params == null)
+ {
+ st_log.debug("couldn't find a single set of NSEC3 parameters (multiple parameters present).");
+ return false;
+ }
+
+ ByteArrayComparator bac = new ByteArrayComparator();
+
+ // 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 = nextClosest(qname, candidate.closestEncloser);
+ candidate.nc_nsec3 = findCoveringNSEC3(hash(nextClosest, nsec3params),
+ zonename,
+ nsec3s,
+ nsec3params,
+ bac);
+
+ if (candidate.nc_nsec3 == null)
+ {
+ st_log.debug("proveWildcard: did not find a covering NSEC3 "
+ + "that covered the next closer name to " + qname + " from "
+ + candidate.closestEncloser + " (derived from wildcard " + wildcard
+ + ")");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 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 static int proveNoDS(List nsec3s, Name qname, Name zonename)
+ {
+ if (nsec3s == null || nsec3s.size() == 0) return SecurityStatus.BOGUS;
+
+ NSEC3Parameters nsec3params = nsec3Parameters(nsec3s);
+ if (nsec3params == null)
+ {
+ st_log.debug("couldn't find a single set of " +
+ "NSEC3 parameters (multiple parameters present).");
+ return SecurityStatus.BOGUS;
+ }
+ ByteArrayComparator bac = new ByteArrayComparator();
+
+ // Look for a matching NSEC3 to qname -- this is the normal NODATA case.
+ NSEC3Record nsec3 = findMatchingNSEC3(hash(qname, nsec3params),
+ zonename,
+ nsec3s,
+ nsec3params,
+ bac);
+
+ 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 = proveClosestEncloser(qname,
+ zonename,
+ nsec3s,
+ nsec3params,
+ bac,
+ true);
+ if (ce == null)
+ {
+ 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.nc_nsec3.getOptInFlag())
+ {
+ return SecurityStatus.SECURE;
+ }
+
+ return SecurityStatus.BOGUS;
+ }
+
+}