diff --git a/lib/dnsjava-2.0.8-vrsn-2.jar b/lib/dnsjava-2.0.8-vrsn-2.jar new file mode 100644 index 0000000..276b156 Binary files /dev/null and b/lib/dnsjava-2.0.8-vrsn-2.jar differ diff --git a/lib/log4j-1.2.15.jar b/lib/log4j-1.2.15.jar new file mode 100644 index 0000000..c930a6a Binary files /dev/null and b/lib/log4j-1.2.15.jar differ diff --git a/src/com/verisign/cl/DNSSECReconciler.java b/src/com/verisign/cl/DNSSECReconciler.java new file mode 100644 index 0000000..2b35d69 --- /dev/null +++ b/src/com/verisign/cl/DNSSECReconciler.java @@ -0,0 +1,104 @@ +package com.verisign.cl; + +import java.util.*; + +import org.xbill.DNS.*; +import com.verisign.tat.dnssec.CaptiveValidator; + +public class DNSSECReconciler { + + /** + * Invoke with java -jar dnssecreconciler.jar server=127.0.0.1 \ + * query_file=queries.txt dnskey_query=net dnskey_query=edu + */ + private CaptiveValidator validator; + + // Options + public String server; + public String query; + public String queryFile; + public String dnskeyFile; + public List dnskeyNames; + + DNSSECReconciler() { + validator = new CaptiveValidator(); + } + + public void execute() { + + } + + private static void usage() { + System.err.println("usage: java -jar dnssecreconiler.jar [..options..]"); + System.err.println(" server: the DNS server to query."); + System.err.println(" query: a name [type [flags]] string."); + System.err.println(" query_file: a list of queries, one query per line."); + System.err.println(" dnskey_file: a file containing DNSKEY RRs to trust."); + System.err.println(" dnskey_query: query 'server' for DNSKEY at given name to trust, may repeat"); + } + + public static int main(String[] argv) { + + DNSSECReconciler dr = new DNSSECReconciler(); + + try { + // Parse the command line options + for (String arg : argv) { + + if (arg.indexOf('=') < 0) { + System.err.println("Unrecognized option: " + arg); + usage(); + return 1; + } + + String[] split_arg = arg.split("[ \t]*=[ \t]*", 2); + String opt = split_arg[0]; + String optarg = split_arg[1]; + + if (opt.equals("server")) { + dr.server = optarg; + } else if (opt.equals("query_file")) { + dr.queryFile = optarg; + } else if (opt.equals("dnskey_file")) { + dr.dnskeyFile = optarg; + } else if (opt.equals("dnskey_query")) { + if (dr.dnskeyNames == null) { + dr.dnskeyNames = new ArrayList(); + } + dr.dnskeyNames.add(optarg); + } else { + System.err.println("Unrecognized option: " + opt); + usage(); + return 1; + } + } + + // Check for minimum usage + if (dr.server == null) { + System.err.println("'server' must be specified"); + usage(); + return 1; + } + if (dr.query == null && dr.queryFile == null) { + System.err.println("Either 'query' or 'query_file' must be specified"); + usage(); + return 1; + } + if (dr.dnskeyFile == null && dr.dnskeyNames == null) { + System.err.println("Either 'dnskey_file' or 'dnskey_query' must be specified"); + usage(); + return 1; + } + + + // Execute the job + dr.execute(); + + } catch (Exception e) { + e.printStackTrace(); + return 1; + } + + return 0; + } +} diff --git a/src/com/verisign/tat/dnssec/CaptiveValidator.java b/src/com/verisign/tat/dnssec/CaptiveValidator.java index 4ada2aa..07311ce 100644 --- a/src/com/verisign/tat/dnssec/CaptiveValidator.java +++ b/src/com/verisign/tat/dnssec/CaptiveValidator.java @@ -49,10 +49,13 @@ public class CaptiveValidator { private DnsSecVerifier mVerifier; private Logger log = Logger.getLogger(this.getClass()); + private List mErrorList; + public CaptiveValidator() { - mVerifier = new DnsSecVerifier(); - mValUtils = new ValUtils(mVerifier); - mTrustedKeys = new TrustAnchorStore(); + mVerifier = new DnsSecVerifier(); + mValUtils = new ValUtils(mVerifier); + mTrustedKeys = new TrustAnchorStore(); + mErrorList = new ArrayList(); } // ---------------- Module Initialization ------------------- @@ -205,7 +208,7 @@ public class CaptiveValidator { // Follow the CNAME chain. if (type == Type.CNAME) { if (rrset.size() > 1) { - log.debug("Found CNAME rrset with size > 1: " + rrset); + mErrorList.add("Found CNAME rrset with size > 1: " + rrset); m.setStatus(SecurityStatus.INVALID); return m; @@ -363,8 +366,8 @@ public class CaptiveValidator { // If the (answer) rrset failed to validate, then this message is // BAD. if (status != SecurityStatus.SECURE) { - log.debug("Positive response has failed ANSWER rrset: " - + rrsets[i]); + mErrorList.add("Positive response has failed ANSWER rrset: " + + rrsets[i]); m.setStatus(SecurityStatus.BOGUS); return; @@ -389,11 +392,10 @@ public class CaptiveValidator { int status = mValUtils.verifySRRset(rrsets[i], key_rrset); // If anything in the authority section fails to be secure, we have - // a - // bad message. + // a bad message. if (status != SecurityStatus.SECURE) { - log.debug("Positive response has failed AUTHORITY rrset: " - + rrsets[i]); + mErrorList.add("Positive response has failed AUTHORITY rrset: " + + rrsets[i]); m.setStatus(SecurityStatus.BOGUS); return; @@ -410,8 +412,7 @@ public class CaptiveValidator { Name nsec_wc = ValUtils.nsecWildcard(qname, nsec); if (!wc.equals(nsec_wc)) { - // log.debug("Positive wildcard response wasn't generated " - // + "by the correct wildcard"); + mErrorList.add("Positive wildcard response wasn't generated by the correct wildcard"); m.setStatus(SecurityStatus.BOGUS); return; @@ -437,7 +438,7 @@ public class CaptiveValidator { // records. if ((wc != null) && !wcNSEC_ok && (nsec3s != null)) { if (NSEC3ValUtils.proveWildcard(nsec3s, qname, key_rrset.getName(), - wc)) { + wc, mErrorList)) { wcNSEC_ok = true; } } @@ -489,8 +490,8 @@ public class CaptiveValidator { // have // a bad message. if (status != SecurityStatus.SECURE) { - log.debug("Positive response has failed AUTHORITY rrset: " - + rrsets[i]); + mErrorList.add("Positive response has failed AUTHORITY rrset: " + + rrsets[i]); m.setStatus(SecurityStatus.BOGUS); return; @@ -538,6 +539,7 @@ public class CaptiveValidator { // Now to check to see if we have a valid combination of things. if (delegation == null) { // somehow we have a referral without an NS rrset. + mErrorList.add("Apparent referral does not contain NS RRset"); m.setStatus(SecurityStatus.BOGUS); return; @@ -546,6 +548,7 @@ public class CaptiveValidator { if (secure_delegation) { if ((nsec != null) || ((nsec3s != null) && (nsec3s.size() > 0))) { // we found both a DS rrset *and* NSEC/NSEC3 rrsets! + mErrorList.add("Referral contains both DS and NSEC/NSEC3 RRsets"); m.setStatus(SecurityStatus.BOGUS); return; @@ -564,6 +567,7 @@ public class CaptiveValidator { if (status != SecurityStatus.SECURE) { // The NSEC *must* prove that there was no DS record. The // INSECURE state here is still bogus. + mErrorList.add("Referral does not contain a NSEC record proving no DS"); m.setStatus(SecurityStatus.BOGUS); return; @@ -575,12 +579,12 @@ public class CaptiveValidator { } if (nsec3s.size() > 0) { - byte status = NSEC3ValUtils - .proveNoDS(nsec3s, delegation, nsec3zone); + byte status = NSEC3ValUtils.proveNoDS(nsec3s, delegation, nsec3zone, mErrorList); if (status != SecurityStatus.SECURE) { // the NSEC3 RRs MUST prove no DS, so the INDETERMINATE state is // actually bogus + mErrorList.add("Referral does not contain NSEC3 record(s) proving no DS"); m.setStatus(SecurityStatus.BOGUS); return; @@ -592,11 +596,12 @@ public class CaptiveValidator { } // failed to find proof either way. + mErrorList.add("Referral does not contain proof of no DS"); m.setStatus(SecurityStatus.BOGUS); } - private void validateCNAMEResponse(SMessage message, SRRset key_rrset) { - } + // FIXME: write CNAME validation code. + private void validateCNAMEResponse(SMessage message, SRRset key_rrset) {} /** * Given an "ANY" response -- a response that contains an answer to a @@ -640,8 +645,8 @@ public class CaptiveValidator { // If the (answer) rrset failed to validate, then this message is // BAD. if (status != SecurityStatus.SECURE) { - log.debug("Positive response has failed ANSWER rrset: " - + rrsets[i]); + mErrorList.add("Positive response has failed ANSWER rrset: " + + rrsets[i]); m.setStatus(SecurityStatus.BOGUS); return; @@ -656,11 +661,10 @@ public class CaptiveValidator { int status = mValUtils.verifySRRset(rrsets[i], key_rrset); // If anything in the authority section fails to be secure, we have - // a - // bad message. + // a bad message. if (status != SecurityStatus.SECURE) { - log.debug("Positive response has failed AUTHORITY rrset: " - + rrsets[i]); + mErrorList.add("Positive response has failed AUTHORITY rrset: " + + rrsets[i]); m.setStatus(SecurityStatus.BOGUS); return; @@ -689,9 +693,9 @@ public class CaptiveValidator { * @param key_rrset * The trusted DNSKEY rrset that signs this response. */ - private void validateNodataResponse(SMessage message, SRRset key_rrset) { - Name qname = message.getQName(); - int qtype = message.getQType(); + private void validateNodataResponse(SMessage message, SRRset key_rrset, List errorList) { + Name qname = message.getQName(); + int qtype = message.getQType(); SMessage m = message; @@ -722,8 +726,8 @@ public class CaptiveValidator { int status = mValUtils.verifySRRset(rrsets[i], key_rrset); if (status != SecurityStatus.SECURE) { - log.debug("NODATA response has failed AUTHORITY rrset: " - + rrsets[i]); + mErrorList.add("NODATA response has failed AUTHORITY rrset: " + + rrsets[i]); m.setStatus(SecurityStatus.BOGUS); return; @@ -779,13 +783,14 @@ public class CaptiveValidator { if (!hasValidNSEC && (nsec3s != null) && (nsec3s.size() > 0)) { // try to prove NODATA with our NSEC3 record(s) hasValidNSEC = NSEC3ValUtils.proveNodata(nsec3s, qname, qtype, - nsec3Signer); + nsec3Signer, errorList); } if (!hasValidNSEC) { log.debug("NODATA response failed to prove NODATA " + "status with NSEC/NSEC3"); log.trace("Failed NODATA:\n" + m); + mErrorList.add("NODATA response failed to prove NODATA status with NSEC/NSEC3"); m.setStatus(SecurityStatus.BOGUS); return; @@ -818,8 +823,9 @@ public class CaptiveValidator { SMessage m = message; if (message.getCount(Section.ANSWER) > 0) { - log - .warn("NAME ERROR response contained records in the ANSWER SECTION"); + log.warn( + "NameError response contained records in the ANSWER SECTION"); + mErrorList.add("NameError response contained records in the ANSWER SECTION"); message.setStatus(SecurityStatus.INVALID); return; @@ -838,8 +844,8 @@ public class CaptiveValidator { int status = mValUtils.verifySRRset(rrsets[i], key_rrset); if (status != SecurityStatus.SECURE) { - log.debug("NameError response has failed AUTHORITY rrset: " - + rrsets[i]); + mErrorList.add("NameError response has failed AUTHORITY rrset: " + + rrsets[i]); m.setStatus(SecurityStatus.BOGUS); return; @@ -882,8 +888,8 @@ public class CaptiveValidator { return; } - hasValidNSEC = NSEC3ValUtils.proveNameError(nsec3s, qname, - nsec3Signer); + hasValidNSEC = NSEC3ValUtils.proveNameError(nsec3s, qname, + nsec3Signer, mErrorList); // Note that we assume that the NSEC3ValUtils proofs encompass the // wildcard part of the proof. @@ -892,16 +898,14 @@ public class CaptiveValidator { // If the message fails to prove either condition, it is bogus. if (!hasValidNSEC) { - log.debug("NameError response has failed to prove: " - + "qname does not exist"); + mErrorList.add("NameError response has failed to prove qname does not exist"); m.setStatus(SecurityStatus.BOGUS); return; } if (!hasValidWCNSEC) { - log.debug("NameError response has failed to prove: " - + "covering wildcard does not exist"); + mErrorList.add("NameError response has failed to prove covering wildcard does not exist"); m.setStatus(SecurityStatus.BOGUS); return; @@ -913,6 +917,7 @@ public class CaptiveValidator { } public byte validateMessage(SMessage message, Name zone) { + mErrorList.clear(); if (!zone.isAbsolute()) { try { zone = Name.concatenate(zone, Name.root); @@ -934,6 +939,7 @@ public class CaptiveValidator { SRRset key_rrset = findKeys(message); if (key_rrset == null) { + mErrorList.add("Failed to find matching DNSKEYs for the response"); return SecurityStatus.BOGUS; } @@ -952,9 +958,9 @@ public class CaptiveValidator { break; - case NODATA: - log.trace("Validating a NODATA response"); - validateNodataResponse(message, key_rrset); + case NODATA: + log.trace("Validating a NODATA response"); + validateNodataResponse(message, key_rrset, mErrorList); break; @@ -994,4 +1000,8 @@ public class CaptiveValidator { public List listTrustedKeys() { return mTrustedKeys.listTrustAnchors(); } -} + + public List getErrorList() { + return mErrorList; + } + } diff --git a/src/com/verisign/tat/dnssec/NSEC3ValUtils.java b/src/com/verisign/tat/dnssec/NSEC3ValUtils.java index f0485b5..b7f5386 100644 --- a/src/com/verisign/tat/dnssec/NSEC3ValUtils.java +++ b/src/com/verisign/tat/dnssec/NSEC3ValUtils.java @@ -231,16 +231,16 @@ public class NSEC3ValUtils { * 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 = hash(nsec3.getName(), nsec3); - byte[] next = nsec3.getNext(); + private static boolean nsec3Covers(NSEC3Record nsec3, byte [] hash, + ByteArrayComparator bac) { + Name ownerName = nsec3.getName(); + byte [] owner = b32.fromString(ownerName.getLabelString(0)); + 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) @@ -317,8 +317,8 @@ public class NSEC3ValUtils { // 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); + nsec3 = findMatchingNSEC3(hash(n, params), zonename, + nsec3s, params, bac); if (nsec3 != null) { return new CEResponse(n, nsec3); @@ -351,22 +351,23 @@ public class NSEC3ValUtils { * that matches it. */ private static CEResponse proveClosestEncloser(Name qname, Name zonename, - List nsec3s, NSEC3Parameters params, - ByteArrayComparator bac, boolean proveDoesNotExist) { + List nsec3s, NSEC3Parameters params, + ByteArrayComparator bac, boolean proveDoesNotExist, List errorList) { CEResponse candidate = findClosestEncloser(qname, zonename, nsec3s, params, bac); if (candidate == null) { - st_log.debug("proveClosestEncloser: could not find a " - + "candidate for the closest encloser."); + errorList.add("Could not find a candidate for the closest encloser"); + 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!"); + errorList.add("Proven closest encloser proved that the qname existed and should not have"); + st_log.debug("proveClosestEncloser: proved that qname existed!"); return null; } @@ -380,15 +381,17 @@ public class NSEC3ValUtils { // 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!"); + if (candidate.ce_nsec3.hasType(Type.NS) && + !candidate.ce_nsec3.hasType(Type.SOA)) { + errorList.add("Proven closest encloser was a delegation"); + st_log.debug("proveClosestEncloser: closest encloser " + + "was a delegation!"); return null; } if (candidate.ce_nsec3.hasType(Type.DNAME)) { + errorList.add("Proven closest encloser was a DNAME"); st_log.debug("proveClosestEncloser: closest encloser was a DNAME!"); return null; @@ -402,8 +405,10 @@ public class NSEC3ValUtils { params, bac); if (candidate.nc_nsec3 == null) { - st_log.debug("Could not find proof that the " - + "closest encloser was the closest encloser"); + errorList.add("Could not find proof that the closest encloser was the closest encloser"); + errorList.add("hash " + hashName(nc_hash, zonename) + " is not covered by any NSEC3 RRs"); + st_log.debug("Could not find proof that the " + + "closest encloser was the closest encloser"); return null; } @@ -519,7 +524,7 @@ public class NSEC3ValUtils { * ignored. */ public static boolean proveNameError(List nsec3s, Name qname, - Name zonename) { + Name zonename, List errorList) { if ((nsec3s == null) || (nsec3s.size() == 0)) { return false; } @@ -527,8 +532,9 @@ public class NSEC3ValUtils { NSEC3Parameters nsec3params = nsec3Parameters(nsec3s); if (nsec3params == null) { - st_log.debug("Could not find a single set of " - + "NSEC3 parameters (multiple parameters present)."); + errorList.add("Could not find a single set of NSEC3 parameters (multiple parameters present"); + st_log.debug("Could not find a single set of " + + "NSEC3 parameters (multiple parameters present)."); return false; } @@ -538,9 +544,10 @@ public class NSEC3ValUtils { // 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); + nsec3params, bac, true, errorList); if (ce == null) { + errorList.add("Failed to find the closest encloser as part of the NSEC3 proof"); st_log.debug("proveNameError: failed to prove a closest encloser."); return false; @@ -555,8 +562,9 @@ public class NSEC3ValUtils { nsec3params, bac); if (nsec3 == null) { - st_log.debug("proveNameError: could not prove that the " - + "applicable wildcard did not exist."); + errorList.add("Failed to prove that the applicable wildcard did not exist"); + st_log.debug("proveNameError: could not prove that the " + + "applicable wildcard did not exist."); return false; } @@ -595,7 +603,7 @@ public class NSEC3ValUtils { * @return true if the NSEC3s prove the proposition. */ public static boolean proveNodata(List nsec3s, Name qname, - int qtype, Name zonename) { + int qtype, Name zonename, List errorList) { if ((nsec3s == null) || (nsec3s.size() == 0)) { return false; } @@ -637,7 +645,7 @@ public class NSEC3ValUtils { // 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); + nsec3params, bac, true, errorList); // At this point, not finding a match or a proven closest encloser is a // problem. @@ -700,7 +708,7 @@ public class NSEC3ValUtils { * @return true if the NSEC3 records prove this case. */ public static boolean proveWildcard(List nsec3s, Name qname, - Name zonename, Name wildcard) { + Name zonename, Name wildcard, List errorList) { if ((nsec3s == null) || (nsec3s.size() == 0)) { return false; } @@ -712,8 +720,9 @@ public class NSEC3ValUtils { NSEC3Parameters nsec3params = nsec3Parameters(nsec3s); if (nsec3params == null) { - st_log - .debug("couldn't find a single set of NSEC3 parameters (multiple parameters present)."); + errorList.add("Could not find a single set of NSEC3 parameters (multiple parameters present)"); + st_log.debug( + "couldn't find a single set of NSEC3 parameters (multiple parameters present)."); return false; } @@ -732,10 +741,13 @@ public class NSEC3ValUtils { 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 + ")"); + errorList.add("Did not find a NSEC3 that covered the next closer name to '" + + qname + "' from '" + candidate.closestEncloser + "' (derived from the wildcard: " + + wildcard + ")"); + 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; } @@ -763,7 +775,7 @@ public class NSEC3ValUtils { * work out. */ public static byte proveNoDS(List nsec3s, Name qname, - Name zonename) { + Name zonename, List errorList) { if ((nsec3s == null) || (nsec3s.size() == 0)) { return SecurityStatus.BOGUS; } @@ -771,8 +783,9 @@ public class NSEC3ValUtils { NSEC3Parameters nsec3params = nsec3Parameters(nsec3s); if (nsec3params == null) { - st_log.debug("couldn't find a single set of " - + "NSEC3 parameters (multiple parameters present)."); + errorList.add("Could not find a single set of NSEC3 parameters (multiple parameters present)"); + st_log.debug("couldn't find a single set of " + + "NSEC3 parameters (multiple parameters present)."); return SecurityStatus.BOGUS; } @@ -788,6 +801,7 @@ public class NSEC3ValUtils { // 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)) { + errorList.add("Matching NSEC3 is incorrectly from the child instead of the parent (SOA or DS bit set)"); return SecurityStatus.BOGUS; } @@ -803,9 +817,10 @@ public class NSEC3ValUtils { // Otherwise, we are probably in the opt-in case. CEResponse ce = proveClosestEncloser(qname, zonename, nsec3s, - nsec3params, bac, true); + nsec3params, bac, true, errorList); if (ce == null) { + errorList.add("Failed to prove the closest encloser as part of a 'No DS' proof"); return SecurityStatus.BOGUS; } @@ -816,6 +831,7 @@ public class NSEC3ValUtils { return SecurityStatus.SECURE; } + errorList.add("Failed to find a covering NSEC3 for 'No DS' proof"); return SecurityStatus.BOGUS; }