/*
* 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;
}
}