We now compile, although we probably don't really work

This commit is contained in:
David Blacka 2009-04-19 15:42:05 -04:00
parent a852ce1790
commit c43169bc24

View File

@ -1,7 +1,5 @@
/* /*
* $Id$ * Copyright (c) 2009 VeriSign, Inc. All rights reserved.
*
* Copyright (c) 2005 VeriSign, Inc. All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
@ -39,12 +37,9 @@ import org.xbill.DNS.*;
/** /**
* This is a collection of routines encompassing the logic of validating * This is a collection of routines encompassing the logic of validating
* different message types. * different message types.
* */
* @author davidb
* @version $Revision$ public class ValUtils {
*/
public class ValUtils
{
// These are response subtypes. They are necessary for determining the // These are response subtypes. They are necessary for determining the
// validation strategy. They have no bearing on the iterative resolution // validation strategy. They have no bearing on the iterative resolution
@ -74,33 +69,29 @@ public class ValUtils
/** A local copy of the verifier object. */ /** A local copy of the verifier object. */
private DnsSecVerifier mVerifier; private DnsSecVerifier mVerifier;
public ValUtils(DnsSecVerifier verifier) public ValUtils(DnsSecVerifier verifier) {
{
mVerifier = verifier; mVerifier = verifier;
} }
/** /**
* Given a response, classify ANSWER responses into a subtype. * Given a response, classify ANSWER responses into a subtype.
* *
* @param m The response to classify. * @param m
* The response to classify.
* *
* @return A subtype ranging from UNKNOWN to NAMEERROR. * @return A subtype ranging from UNKNOWN to NAMEERROR.
*/ */
public static int classifyResponse(SMessage m) public static int classifyResponse(SMessage m) {
{
// Normal Name Error's are easy to detect -- but don't mistake a CNAME // Normal Name Error's are easy to detect -- but don't mistake a CNAME
// chain ending in NXDOMAIN. // chain ending in NXDOMAIN.
if (m.getRcode() == Rcode.NXDOMAIN if (m.getRcode() == Rcode.NXDOMAIN && m.getCount(Section.ANSWER) == 0) {
&& m.getCount(Section.ANSWER) == 0)
{
return NAMEERROR; return NAMEERROR;
} }
// Next is NODATA // Next is NODATA
// st_log.debug("classifyResponse: ancount = " + // st_log.debug("classifyResponse: ancount = " +
// m.getCount(Section.ANSWER)); // m.getCount(Section.ANSWER));
if (m.getCount(Section.ANSWER) == 0) if (m.getCount(Section.ANSWER) == 0) {
{
return NODATA; return NODATA;
} }
@ -108,10 +99,10 @@ public class ValUtils
// responses because CNAME answers require extra processing. // responses because CNAME answers require extra processing.
int qtype = m.getQuestion().getType(); int qtype = m.getQuestion().getType();
// We distinguish between ANY and CNAME or POSITIVE because ANY responses // We distinguish between ANY and CNAME or POSITIVE because ANY
// responses
// are validated differently. // are validated differently.
if (qtype == Type.ANY) if (qtype == Type.ANY) {
{
return ANY; return ANY;
} }
@ -119,13 +110,12 @@ public class ValUtils
// Note that DNAMEs will be ignored here, unless qtype=DNAME. Unless // Note that DNAMEs will be ignored here, unless qtype=DNAME. Unless
// qtype=CNAME, this will yield a CNAME response. // qtype=CNAME, this will yield a CNAME response.
for (int i = 0; i < rrsets.length; i++) for (int i = 0; i < rrsets.length; i++) {
{
if (rrsets[i].getType() == qtype) return POSITIVE; if (rrsets[i].getType() == qtype) return POSITIVE;
if (rrsets[i].getType() == Type.CNAME) return CNAME; if (rrsets[i].getType() == Type.CNAME) return CNAME;
} }
// st_log.warn("Failed to classify response message:\n" + m); // st_log.warn("Failed to classify response message:\n" + m);
return UNKNOWN; return UNKNOWN;
} }
@ -134,62 +124,55 @@ public class ValUtils
* to determine if the response is, in fact, signed at all, and, if so, what * to determine if the response is, in fact, signed at all, and, if so, what
* is the name of the most pertinent keyset. * is the name of the most pertinent keyset.
* *
* @param m The response to analyze. * @param m
* @param request The request that generated the response. * The response to analyze.
* @param request
* The request that generated the response.
* @return a signer name, if the response is signed (even partially), or * @return a signer name, if the response is signed (even partially), or
* null if the response isn't signed. * null if the response isn't signed.
*/ */
public Name findSigner(SMessage m) public Name findSigner(SMessage m) {
{
int subtype = classifyResponse(m); int subtype = classifyResponse(m);
Name qname = m.getQName(); Name qname = m.getQName();
SRRset[] rrsets; SRRset[] rrsets;
switch (subtype) switch (subtype) {
{ case POSITIVE:
case POSITIVE : case CNAME:
case CNAME : case ANY:
case ANY :
// Check to see if the ANSWER section RRset // Check to see if the ANSWER section RRset
rrsets = m.getSectionRRsets(Section.ANSWER); rrsets = m.getSectionRRsets(Section.ANSWER);
for (int i = 0; i < rrsets.length; i++) for (int i = 0; i < rrsets.length; i++) {
{ if (rrsets[i].getName().equals(qname)) {
if (rrsets[i].getName().equals(qname))
{
return rrsets[i].getSignerName(); return rrsets[i].getSignerName();
} }
} }
return null; return null;
case NAMEERROR : case NAMEERROR:
case NODATA : case NODATA:
// Check to see if the AUTH section NSEC record(s) have rrsigs // Check to see if the AUTH section NSEC record(s) have rrsigs
rrsets = m.getSectionRRsets(Section.AUTHORITY); rrsets = m.getSectionRRsets(Section.AUTHORITY);
for (int i = 0; i < rrsets.length; i++) for (int i = 0; i < rrsets.length; i++) {
{
if (rrsets[i].getType() == Type.NSEC if (rrsets[i].getType() == Type.NSEC
|| rrsets[i].getType() == Type.NSEC3) || rrsets[i].getType() == Type.NSEC3) {
{
return rrsets[i].getSignerName(); return rrsets[i].getSignerName();
} }
} }
return null; return null;
default : default:
// log.debug("findSigner: could not find signer name " // log.debug("findSigner: could not find signer name "
// + "for unknown type response."); // + "for unknown type response.");
return null; return null;
} }
} }
public boolean dssetIsUsable(SRRset ds_rrset) public boolean dssetIsUsable(SRRset ds_rrset) {
{ for (Iterator i = ds_rrset.rrs(); i.hasNext();) {
for (Iterator i = ds_rrset.rrs(); i.hasNext();)
{
DSRecord ds = (DSRecord) i.next(); DSRecord ds = (DSRecord) i.next();
if (supportsDigestID(ds.getDigestID()) if (supportsDigestID(ds.getDigestID())
&& mVerifier.supportsAlgorithm(ds.getAlgorithm())) && mVerifier.supportsAlgorithm(ds.getAlgorithm())) {
{
return true; return true;
} }
} }
@ -201,10 +184,11 @@ public class ValUtils
* Given a DS rrset and a DNSKEY rrset, match the DS to a DNSKEY and verify * Given a DS rrset and a DNSKEY rrset, match the DS to a DNSKEY and verify
* the DNSKEY rrset with that key. * the DNSKEY rrset with that key.
* *
* @param dnskey_rrset The DNSKEY rrset to match against. The security * @param dnskey_rrset
* status of this rrset will be updated on a successful * The DNSKEY rrset to match against. The security status of this
* verification. * rrset will be updated on a successful verification.
* @param ds_rrset The DS rrset to match with. This rrset must already be * @param ds_rrset
* The DS rrset to match with. This rrset must already be
* trusted. * trusted.
* *
* @return a KeyEntry. This will either contain the now trusted * @return a KeyEntry. This will either contain the now trusted
@ -216,128 +200,124 @@ public class ValUtils
* this sort of thing is checked before fetching the matching DNSKEY * this sort of thing is checked before fetching the matching DNSKEY
* rrset. * rrset.
*/ */
// public KeyEntry verifyNewDNSKEYs(SRRset dnskey_rrset, SRRset ds_rrset) // public KeyEntry verifyNewDNSKEYs(SRRset dnskey_rrset, SRRset ds_rrset)
// { // {
// if (!dnskey_rrset.getName().equals(ds_rrset.getName())) // if (!dnskey_rrset.getName().equals(ds_rrset.getName()))
// { // {
//// log.debug("DNSKEY RRset did not match DS RRset by name!"); // // log.debug("DNSKEY RRset did not match DS RRset by name!");
// return KeyEntry // return KeyEntry
// .newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass()); // .newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass());
// } // }
// //
// // as long as this is false, we can consider this DS rrset to be // // as long as this is false, we can consider this DS rrset to be
// // equivalent to no DS rrset. // // equivalent to no DS rrset.
// boolean hasUsefulDS = false; // boolean hasUsefulDS = false;
// //
// for (Iterator i = ds_rrset.rrs(); i.hasNext();) // for (Iterator i = ds_rrset.rrs(); i.hasNext();)
// { // {
// DSRecord ds = (DSRecord) i.next(); // DSRecord ds = (DSRecord) i.next();
// //
// // Check to see if we can understand this DS. // // Check to see if we can understand this DS.
// if (!supportsDigestID(ds.getDigestID()) // if (!supportsDigestID(ds.getDigestID())
// || !mVerifier.supportsAlgorithm(ds.getAlgorithm())) // || !mVerifier.supportsAlgorithm(ds.getAlgorithm()))
// { // {
// continue; // continue;
// } // }
// //
// // Once we see a single DS with a known digestID and algorithm, we // // Once we see a single DS with a known digestID and algorithm, we
// // cannot return INSECURE (with a "null" KeyEntry). // // cannot return INSECURE (with a "null" KeyEntry).
// hasUsefulDS = true; // hasUsefulDS = true;
// //
// DNSKEY : for (Iterator j = dnskey_rrset.rrs(); j.hasNext();) // DNSKEY : for (Iterator j = dnskey_rrset.rrs(); j.hasNext();)
// { // {
// DNSKEYRecord dnskey = (DNSKEYRecord) j.next(); // DNSKEYRecord dnskey = (DNSKEYRecord) j.next();
// //
// // Skip DNSKEYs that don't match the basic criteria. // // Skip DNSKEYs that don't match the basic criteria.
// if (ds.getFootprint() != dnskey.getFootprint() // if (ds.getFootprint() != dnskey.getFootprint()
// || ds.getAlgorithm() != dnskey.getAlgorithm()) // || ds.getAlgorithm() != dnskey.getAlgorithm())
// { // {
// continue; // continue;
// } // }
// //
// // Convert the candidate DNSKEY into a hash using the same DS hash // // Convert the candidate DNSKEY into a hash using the same DS hash
// // algorithm. // // algorithm.
// byte[] key_hash = calculateDSHash(dnskey, ds.getDigestID()); // byte[] key_hash = calculateDSHash(dnskey, ds.getDigestID());
// byte[] ds_hash = ds.getDigest(); // byte[] ds_hash = ds.getDigest();
// //
// // see if there is a length mismatch (unlikely) // // see if there is a length mismatch (unlikely)
// if (key_hash.length != ds_hash.length) // if (key_hash.length != ds_hash.length)
// { // {
// continue DNSKEY; // continue DNSKEY;
// } // }
// //
// for (int k = 0; k < key_hash.length; k++) // for (int k = 0; k < key_hash.length; k++)
// { // {
// if (key_hash[k] != ds_hash[k]) continue DNSKEY; // if (key_hash[k] != ds_hash[k]) continue DNSKEY;
// } // }
// //
// // Otherwise, we have a match! Make sure that the DNSKEY verifies // // Otherwise, we have a match! Make sure that the DNSKEY verifies
// // *with this key*. // // *with this key*.
// byte res = mVerifier.verify(dnskey_rrset, dnskey); // byte res = mVerifier.verify(dnskey_rrset, dnskey);
// if (res == SecurityStatus.SECURE) // if (res == SecurityStatus.SECURE)
// { // {
//// log.trace("DS matched DNSKEY."); // // log.trace("DS matched DNSKEY.");
// dnskey_rrset.setSecurityStatus(SecurityStatus.SECURE); // dnskey_rrset.setSecurityStatus(SecurityStatus.SECURE);
// return KeyEntry.newKeyEntry(dnskey_rrset); // return KeyEntry.newKeyEntry(dnskey_rrset);
// } // }
// // If it didn't validate with the DNSKEY, try the next one! // // If it didn't validate with the DNSKEY, try the next one!
// } // }
// } // }
// //
// // None of the DS's worked out. // // None of the DS's worked out.
// //
// // If no DSs were understandable, then this is OK. // // If no DSs were understandable, then this is OK.
// if (!hasUsefulDS) // if (!hasUsefulDS)
// { // {
//// log.debug("No usuable DS records were found -- treating as insecure."); // //
// return KeyEntry.newNullKeyEntry(ds_rrset.getName(), ds_rrset // log.debug("No usuable DS records were found -- treating as insecure.");
// .getDClass(), ds_rrset.getTTL()); // return KeyEntry.newNullKeyEntry(ds_rrset.getName(), ds_rrset
// } // .getDClass(), ds_rrset.getTTL());
// // If any were understandable, then it is bad. // }
//// log.debug("Failed to match any usable DS to a DNSKEY."); // // If any were understandable, then it is bad.
// return KeyEntry.newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass()); // // log.debug("Failed to match any usable DS to a DNSKEY.");
// } // return KeyEntry.newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass());
// }
/** /**
* Given a DNSKEY record, generate the DS record from it. * Given a DNSKEY record, generate the DS record from it.
* *
* @param keyrec the DNSKEY record in question. * @param keyrec
* @param ds_alg The DS digest algorithm in use. * the DNSKEY record in question.
* @param ds_alg
* The DS digest algorithm in use.
* @return the corresponding {@link org.xbill.DNS.DSRecord} * @return the corresponding {@link org.xbill.DNS.DSRecord}
*/ */
public static byte[] calculateDSHash(DNSKEYRecord keyrec, int ds_alg) public static byte[] calculateDSHash(DNSKEYRecord keyrec, int ds_alg) {
{
DNSOutput os = new DNSOutput(); DNSOutput os = new DNSOutput();
os.writeByteArray(keyrec.getName().toWireCanonical()); os.writeByteArray(keyrec.getName().toWireCanonical());
os.writeByteArray(keyrec.rdataToWireCanonical()); os.writeByteArray(keyrec.rdataToWireCanonical());
try try {
{
MessageDigest md = null; MessageDigest md = null;
switch (ds_alg) switch (ds_alg) {
{ case DSRecord.SHA1_DIGEST_ID:
case DSRecord.SHA1_DIGEST_ID :
md = MessageDigest.getInstance("SHA"); md = MessageDigest.getInstance("SHA");
return md.digest(os.toByteArray()); return md.digest(os.toByteArray());
case DSRecord.SHA256_DIGEST_ID: case DSRecord.SHA256_DIGEST_ID:
md = MessageDigest.getInstance("SHA256"); md = MessageDigest.getInstance("SHA256");
return md.digest(os.toByteArray()); return md.digest(os.toByteArray());
default : default:
// st_log.warn("Unknown DS algorithm: " + ds_alg); // st_log.warn("Unknown DS algorithm: " + ds_alg);
return null; return null;
} }
} } catch (NoSuchAlgorithmException e) {
catch (NoSuchAlgorithmException e) // st_log.error("Error using DS algorithm: " + ds_alg, e);
{
// st_log.error("Error using DS algorithm: " + ds_alg, e);
return null; return null;
} }
} }
public static boolean supportsDigestID(int digest_id) public static boolean supportsDigestID(int digest_id) {
{
if (digest_id == DSRecord.SHA1_DIGEST_ID) return true; if (digest_id == DSRecord.SHA1_DIGEST_ID) return true;
if (digest_id == DSRecord.SHA256_DIGEST_ID) return true; if (digest_id == DSRecord.SHA256_DIGEST_ID) return true;
return false; return false;
@ -346,21 +326,20 @@ public class ValUtils
/** /**
* Check to see if a type is a special DNSSEC type. * Check to see if a type is a special DNSSEC type.
* *
* @param type The type. * @param type
* The type.
* *
* @return true if the type is one of the special DNSSEC types. * @return true if the type is one of the special DNSSEC types.
*/ */
public static boolean isDNSSECType(int type) public static boolean isDNSSECType(int type) {
{ switch (type) {
switch (type) case Type.DNSKEY:
{ case Type.NSEC:
case Type.DNSKEY : case Type.DS:
case Type.NSEC : case Type.RRSIG:
case Type.DS : case Type.NSEC3:
case Type.RRSIG :
case Type.NSEC3 :
return true; return true;
default : default:
return false; return false;
} }
} }
@ -369,16 +348,16 @@ public class ValUtils
* Set the security status of a particular RRset. This will only upgrade the * Set the security status of a particular RRset. This will only upgrade the
* security status. * security status.
* *
* @param rrset The SRRset to update. * @param rrset
* @param security The security status. * The SRRset to update.
* @param security
* The security status.
*/ */
public static void setRRsetSecurity(SRRset rrset, byte security) public static void setRRsetSecurity(SRRset rrset, byte security) {
{
if (rrset == null) return; if (rrset == null) return;
int cur_sec = rrset.getSecurityStatus(); int cur_sec = rrset.getSecurityStatus();
if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec) if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec) {
{
rrset.setSecurityStatus(security); rrset.setSecurityStatus(security);
} }
} }
@ -389,30 +368,27 @@ public class ValUtils
* less) and all of the RRsets. * less) and all of the RRsets.
* *
* @param m * @param m
* @param security KeyEntry ke; * @param security
* KeyEntry ke;
* *
* SMessage m = response.getSMessage(); SRRset ans_rrset = * SMessage m = response.getSMessage(); SRRset ans_rrset =
* m.findAnswerRRset(qname, qtype, qclass); * m.findAnswerRRset(qname, qtype, qclass);
* *
* ke = verifySRRset(ans_rrset, key_rrset); if * ke = verifySRRset(ans_rrset, key_rrset); if
* (ans_rrset.getSecurityStatus() != SecurityStatus.SECURE) { return; } * (ans_rrset.getSecurityStatus() != SecurityStatus.SECURE) {
* key_rrset = ke.getRRset(); * return; } key_rrset = ke.getRRset();
*/ */
public static void setMessageSecurity(SMessage m, byte security) public static void setMessageSecurity(SMessage m, byte security) {
{
if (m == null) return; if (m == null) return;
int cur_sec = m.getStatus(); int cur_sec = m.getStatus();
if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec) if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec) {
{
m.setStatus(security); m.setStatus(security);
} }
for (int section = Section.ANSWER; section <= Section.ADDITIONAL; section++) for (int section = Section.ANSWER; section <= Section.ADDITIONAL; section++) {
{
SRRset[] rrsets = m.getSectionRRsets(section); SRRset[] rrsets = m.getSectionRRsets(section);
for (int i = 0; i < rrsets.length; i++) for (int i = 0; i < rrsets.length; i++) {
{
setRRsetSecurity(rrsets[i], security); setRRsetSecurity(rrsets[i], security);
} }
} }
@ -423,32 +399,34 @@ public class ValUtils
* it. This will return the status (either BOGUS or SECURE) and set that * it. This will return the status (either BOGUS or SECURE) and set that
* status in rrset. * status in rrset.
* *
* @param rrset The SRRset to verify. * @param rrset
* @param key_rrset The set of keys to verify against. * The SRRset to verify.
* @param key_rrset
* The set of keys to verify against.
* @return The status (BOGUS or SECURE). * @return The status (BOGUS or SECURE).
*/ */
public byte verifySRRset(SRRset rrset, SRRset key_rrset) public byte verifySRRset(SRRset rrset, SRRset key_rrset) {
{ String rrset_name = rrset.getName() + "/"
String rrset_name = rrset.getName() + "/" + Type.string(rrset.getType()) + Type.string(rrset.getType()) + "/"
+ "/" + DClass.string(rrset.getDClass()); + DClass.string(rrset.getDClass());
if (rrset.getSecurityStatus() == SecurityStatus.SECURE) if (rrset.getSecurityStatus() == SecurityStatus.SECURE) {
{ // log.trace("verifySRRset: rrset <" + rrset_name
// log.trace("verifySRRset: rrset <" + rrset_name // + "> previously found to be SECURE");
// + "> previously found to be SECURE");
return SecurityStatus.SECURE; return SecurityStatus.SECURE;
} }
byte status = mVerifier.verify(rrset, key_rrset); byte status = mVerifier.verify(rrset, key_rrset);
if (status != SecurityStatus.SECURE) if (status != SecurityStatus.SECURE) {
{ // log.debug("verifySRRset: rrset <" + rrset_name +
// log.debug("verifySRRset: rrset <" + rrset_name + "> found to be BAD"); // "> found to be BAD");
status = SecurityStatus.BOGUS; status = SecurityStatus.BOGUS;
} }
// else // else
// { // {
// log.trace("verifySRRset: rrset <" + rrset_name + "> found to be SECURE"); // log.trace("verifySRRset: rrset <" + rrset_name +
// } // "> found to be SECURE");
// }
rrset.setSecurityStatus(status); rrset.setSecurityStatus(status);
return status; return status;
@ -457,61 +435,76 @@ public class ValUtils
/** /**
* Determine if a given type map has a given typ. * Determine if a given type map has a given typ.
* *
* @param types The type map from the NSEC record. * @param types
* @param type The type to look for. * The type map from the NSEC record.
* @param type
* The type to look for.
* @return true if the type is present in the type map, false otherwise. * @return true if the type is present in the type map, false otherwise.
*/ */
public static boolean typeMapHasType(int[] types, int type) public static boolean typeMapHasType(int[] types, int type) {
{ for (int i = 0; i < types.length; i++) {
for (int i = 0; i < types.length; i++)
{
if (types[i] == type) return true; if (types[i] == type) return true;
} }
return false; return false;
} }
public static RRSIGRecord rrsetFirstSig(RRset rrset) { public static RRSIGRecord rrsetFirstSig(RRset rrset) {
for (Iterator i = rrset.sigs(); i.hasNext(); ) { for (Iterator i = rrset.sigs(); i.hasNext();) {
return (RRSIGRecord) i.next(); return (RRSIGRecord) i.next();
} }
return null; return null;
} }
/**
* Finds the longest common name between two domain names.
*
* @param domain1
* @param domain2
* @return
*/
public static Name longestCommonName(Name domain1, Name domain2) { public static Name longestCommonName(Name domain1, Name domain2) {
if (domain1 == null || domain2 == null) return null; if (domain1 == null || domain2 == null) return null;
// for now, do this in a a fairly brute force way // for now, do this in a a fairly brute force way
// FIXME: convert this to direct operations on the byte[] // FIXME: convert this to direct operations on the byte[]
int this_labels = domain1.labels(); int d1_labels = domain1.labels();
int name_labels = domain2.labels(); int d2_labels = domain2.labels();
int l = (this_labels < name_labels) ? this_labels : name_labels; int l = (d1_labels < d2_labels) ? d1_labels : d2_labels;
for (int i = l; i > 0; i--) for (int i = l; i > 0; i--) {
{ Name n1 = new Name(domain1, d1_labels - i);
Name n = new Name(domain2, name_labels - i); Name n2 = new Name(domain2, d2_labels - i);
if (n.equals(name, offset(this_labels - i))) if (n1.equals(n2)) {
{ return n1;
return n;
} }
} }
return root; return Name.root;
} }
public static boolean strictSubdomain(Name child, Name parent) {
int clabels = child.labels();
int plabels = parent.labels();
if (plabels >= clabels) return false;
Name n = new Name(child, clabels - plabels);
return parent.equals(n);
}
/** /**
* Determine by looking at a signed RRset whether or not the rrset name was * Determine by looking at a signed RRset whether or not the rrset name was
* the result of a wildcard expansion. * the result of a wildcard expansion.
* *
* @param rrset The rrset to examine. * @param rrset
* The rrset to examine.
* @return true if the rrset is a wildcard expansion. This will return false * @return true if the rrset is a wildcard expansion. This will return false
* for all unsigned rrsets. * for all unsigned rrsets.
*/ */
public static boolean rrsetIsWildcardExpansion(RRset rrset) public static boolean rrsetIsWildcardExpansion(RRset rrset) {
{
if (rrset == null) return false; if (rrset == null) return false;
RRSIGRecord rrsig = rrsetFirstSig(rrset); RRSIGRecord rrsig = rrsetFirstSig(rrset);
if (rrset.getName().labels() - 1 > rrsig.getLabels()) if (rrset.getName().labels() - 1 > rrsig.getLabels()) {
{
return true; return true;
} }
@ -523,12 +516,12 @@ public class ValUtils
* the result of a wildcard expansion. If so, return the name of the * the result of a wildcard expansion. If so, return the name of the
* generating wildcard. * generating wildcard.
* *
* @param rrset The rrset to chedck. * @param rrset
* The rrset to chedck.
* @return the wildcard name, if the rrset was synthesized from a wildcard. * @return the wildcard name, if the rrset was synthesized from a wildcard.
* null if not. * null if not.
*/ */
public static Name rrsetWildcard(RRset rrset) public static Name rrsetWildcard(RRset rrset) {
{
if (rrset == null) return null; if (rrset == null) return null;
RRSIGRecord rrsig = rrsetFirstSig(rrset); RRSIGRecord rrsig = rrsetFirstSig(rrset);
@ -536,29 +529,23 @@ public class ValUtils
// then this rrset was synthesized from a wildcard. // then this rrset was synthesized from a wildcard.
// Note that the RRSIG label count doesn't count the root label. // Note that the RRSIG label count doesn't count the root label.
int label_diff = (rrset.getName().labels() - 1) - rrsig.getLabels(); int label_diff = (rrset.getName().labels() - 1) - rrsig.getLabels();
if (label_diff > 0) if (label_diff > 0) {
{
return rrset.getName().wild(label_diff); return rrset.getName().wild(label_diff);
} }
return null; return null;
} }
public static Name closestEncloser(Name domain, NSECRecord nsec) public static Name closestEncloser(Name domain, NSECRecord nsec) {
{ Name n1 = longestCommonName(domain, nsec.getName());
Name n1 = domain.longestCommonName(nsec.getName()); Name n2 = longestCommonName(domain, nsec.getNext());
Name n2 = domain.longestCommonName(nsec.getNext());
return (n1.labels() > n2.labels()) ? n1 : n2; return (n1.labels() > n2.labels()) ? n1 : n2;
} }
public static Name nsecWildcard(Name domain, NSECRecord nsec) public static Name nsecWildcard(Name domain, NSECRecord nsec) {
{ try {
try
{
return new Name("*", closestEncloser(domain, nsec)); return new Name("*", closestEncloser(domain, nsec));
} } catch (TextParseException e) {
catch (TextParseException e)
{
// this should never happen. // this should never happen.
return null; return null;
} }
@ -568,39 +555,41 @@ public class ValUtils
* Determine if the given NSEC proves a NameError (NXDOMAIN) for a given * Determine if the given NSEC proves a NameError (NXDOMAIN) for a given
* qname. * qname.
* *
* @param nsec The NSEC to check. * @param nsec
* @param qname The qname to check against. * The NSEC to check.
* @param signerName The signer name of the NSEC record, which is used as * @param qname
* the zone name, for a more precise (but perhaps more brittle) * The qname to check against.
* check for the last NSEC in a zone. * @param signerName
* The signer name of the NSEC record, which is used as the zone
* name, for a more precise (but perhaps more brittle) check for
* the last NSEC in a zone.
* @return true if the NSEC proves the condition. * @return true if the NSEC proves the condition.
*/ */
public static boolean nsecProvesNameError(NSECRecord nsec, Name qname, public static boolean nsecProvesNameError(NSECRecord nsec, Name qname,
Name signerName) Name signerName) {
{
Name owner = nsec.getName(); Name owner = nsec.getName();
Name next = nsec.getNext(); Name next = nsec.getNext();
// If NSEC owner == qname, then this NSEC proves that qname exists. // If NSEC owner == qname, then this NSEC proves that qname exists.
if (qname.equals(owner)) if (qname.equals(owner)) {
{
return false; return false;
} }
// If NSEC is a parent of qname, we need to check the type map // If NSEC is a parent of qname, we need to check the type map
// If the parent name has a DNAME or is a delegation point, then this NSEC // If the parent name has a DNAME or is a delegation point, then this
// NSEC
// is being misused. // is being misused.
if (qname.subdomain(owner) if (qname.subdomain(owner)
&& (typeMapHasType(nsec.getTypes(), Type.DNAME) || (typeMapHasType(nsec && (typeMapHasType(nsec.getTypes(), Type.DNAME) || (typeMapHasType(
.getTypes(), nsec.getTypes(),
Type.NS) && !typeMapHasType(nsec.getTypes(), Type.SOA)))) Type.NS) && !typeMapHasType(
{ nsec.getTypes(),
Type.SOA)))) {
return false; return false;
} }
if (qname.compareTo(owner) > 0 && (qname.compareTo(next) < 0) if (qname.compareTo(owner) > 0 && (qname.compareTo(next) < 0)
|| signerName.equals(next)) || signerName.equals(next)) {
{
return true; return true;
} }
return false; return false;
@ -610,27 +599,26 @@ public class ValUtils
* Determine if a NSEC record proves the non-existence of a wildcard that * Determine if a NSEC record proves the non-existence of a wildcard that
* could have produced qname. * could have produced qname.
* *
* @param nsec The nsec to check. * @param nsec
* @param qname The qname to check against. * The nsec to check.
* @param signerName The signer name for the NSEC rrset, used as the zone * @param qname
* name. * The qname to check against.
* @param signerName
* The signer name for the NSEC rrset, used as the zone name.
* @return true if the NSEC proves the condition. * @return true if the NSEC proves the condition.
*/ */
public static boolean nsecProvesNoWC(NSECRecord nsec, Name qname, public static boolean nsecProvesNoWC(NSECRecord nsec, Name qname,
Name signerName) Name signerName) {
{
Name owner = nsec.getName(); Name owner = nsec.getName();
Name next = nsec.getNext(); Name next = nsec.getNext();
int qname_labels = qname.labels(); int qname_labels = qname.labels();
int signer_labels = signerName.labels(); int signer_labels = signerName.labels();
for (int i = qname_labels - signer_labels; i > 0; i--) for (int i = qname_labels - signer_labels; i > 0; i--) {
{
Name wc_name = qname.wild(i); Name wc_name = qname.wild(i);
if (wc_name.compareTo(owner) > 0 if (wc_name.compareTo(owner) > 0
&& (wc_name.compareTo(next) < 0 || signerName.equals(next))) && (wc_name.compareTo(next) < 0 || signerName.equals(next))) {
{
return true; return true;
} }
} }
@ -645,31 +633,34 @@ public class ValUtils
* must still be provided proof that qname did not directly exist and that * must still be provided proof that qname did not directly exist and that
* the wildcard is, in fact, *.closest_encloser. * the wildcard is, in fact, *.closest_encloser.
* *
* @param nsec The NSEC to check * @param nsec
* @param qname The query name to check against. * The NSEC to check
* @param qtype The query type to check against. * @param qname
* The query name to check against.
* @param qtype
* The query type to check against.
* @return true if the NSEC proves the condition. * @return true if the NSEC proves the condition.
*/ */
public static boolean nsecProvesNodata(NSECRecord nsec, Name qname, public static boolean nsecProvesNodata(NSECRecord nsec, Name qname,
int qtype) int qtype) {
{ if (!nsec.getName().equals(qname)) {
if (!nsec.getName().equals(qname))
{
// wildcard checking. // wildcard checking.
// If this is a wildcard NSEC, make sure that a) it was possible to have // If this is a wildcard NSEC, make sure that a) it was possible to
// have
// generated qname from the wildcard and b) the type map does not // generated qname from the wildcard and b) the type map does not
// contain qtype. Note that this does NOT prove that this wildcard was // contain qtype. Note that this does NOT prove that this wildcard
// was
// the applicable wildcard. // the applicable wildcard.
if (nsec.getName().isWild()) if (nsec.getName().isWild()) {
{
// the is the purported closest encloser. // the is the purported closest encloser.
Name ce = new Name(nsec.getName(), 1); Name ce = new Name(nsec.getName(), 1);
// The qname must be a strict subdomain of the closest encloser, and // The qname must be a strict subdomain of the closest encloser,
// and
// the qtype must be absent from the type map. // the qtype must be absent from the type map.
if (!qname.strictSubdomain(ce) || typeMapHasType(nsec.getTypes(), qtype)) if (!strictSubdomain(qname, ce)
{ || typeMapHasType(nsec.getTypes(), qtype)) {
return false; return false;
} }
return true; return true;
@ -677,57 +668,55 @@ public class ValUtils
// empty-non-terminal checking. // empty-non-terminal checking.
// If the nsec is proving that qname is an ENT, the nsec owner will be // If the nsec is proving that qname is an ENT, the nsec owner will
// be
// less than qname, and the next name will be a child domain of the // less than qname, and the next name will be a child domain of the
// qname. // qname.
if (nsec.getNext().strictSubdomain(qname) if (strictSubdomain(nsec.getNext(), qname)
&& qname.compareTo(nsec.getName()) > 0) && qname.compareTo(nsec.getName()) > 0) {
{
return true; return true;
} }
// Otherwise, this NSEC does not prove ENT, so it does not prove NODATA. // Otherwise, this NSEC does not prove ENT, so it does not prove
// NODATA.
return false; return false;
} }
// If the qtype exists, then we should have gotten it. // If the qtype exists, then we should have gotten it.
if (typeMapHasType(nsec.getTypes(), qtype)) if (typeMapHasType(nsec.getTypes(), qtype)) {
{
return false; return false;
} }
// if the name is a CNAME node, then we should have gotten the CNAME // if the name is a CNAME node, then we should have gotten the CNAME
if (typeMapHasType(nsec.getTypes(), Type.CNAME)) if (typeMapHasType(nsec.getTypes(), Type.CNAME)) {
{
return false; return false;
} }
// If an NS set exists at this name, and NOT a SOA (so this is a zone cut, // If an NS set exists at this name, and NOT a SOA (so this is a zone
// not a zone apex), then we should have gotten a referral (or we just got // cut,
// not a zone apex), then we should have gotten a referral (or we just
// got
// the wrong NSEC). // the wrong NSEC).
if (typeMapHasType(nsec.getTypes(), Type.NS) if (typeMapHasType(nsec.getTypes(), Type.NS)
&& !typeMapHasType(nsec.getTypes(), Type.SOA)) && !typeMapHasType(nsec.getTypes(), Type.SOA)) {
{
return false; return false;
} }
return true; return true;
} }
public static int nsecProvesNoDS(NSECRecord nsec, Name qname) public static int nsecProvesNoDS(NSECRecord nsec, Name qname) {
{
// Could check to make sure the qname is a subdomain of nsec // Could check to make sure the qname is a subdomain of nsec
int[] types = nsec.getTypes(); int[] types = nsec.getTypes();
if (typeMapHasType(types, Type.SOA) || typeMapHasType(types, Type.DS)) if (typeMapHasType(types, Type.SOA) || typeMapHasType(types, Type.DS)) {
{
// SOA present means that this is the NSEC from the child, not the // SOA present means that this is the NSEC from the child, not the
// parent (so it is the wrong one) // parent (so it is the wrong one)
// DS present means that there should have been a positive response to // DS present means that there should have been a positive response
// to
// the DS query, so there is something wrong. // the DS query, so there is something wrong.
return SecurityStatus.BOGUS; return SecurityStatus.BOGUS;
} }
if (!typeMapHasType(types, Type.NS)) if (!typeMapHasType(types, Type.NS)) {
{
// If there is no NS at this point at all, then this doesn't prove // If there is no NS at this point at all, then this doesn't prove
// anything one way or the other. // anything one way or the other.
return SecurityStatus.INSECURE; return SecurityStatus.INSECURE;