From 657c073cc51933fbd84c7161712a6206ac706689 Mon Sep 17 00:00:00 2001 From: davidb Date: Sun, 30 May 2010 18:34:04 -0400 Subject: [PATCH] sync with external work. --- .../versign/tat/dnssec/CaptiveValidator.java | 668 ++++++++----- .../versign/tat/dnssec/DnsSecVerifier.java | 894 +++++++++--------- src/com/versign/tat/dnssec/NSEC3ValUtils.java | 626 +++++++----- src/com/versign/tat/dnssec/SMessage.java | 185 ++-- src/com/versign/tat/dnssec/SRRset.java | 80 +- .../versign/tat/dnssec/SecurityStatus.java | 196 ++-- src/com/versign/tat/dnssec/SignUtils.java | 352 +++---- .../versign/tat/dnssec/TrustAnchorStore.java | 90 +- src/com/versign/tat/dnssec/Util.java | 192 ++-- src/com/versign/tat/dnssec/ValUtils.java | 358 ++++--- 10 files changed, 2027 insertions(+), 1614 deletions(-) diff --git a/src/com/versign/tat/dnssec/CaptiveValidator.java b/src/com/versign/tat/dnssec/CaptiveValidator.java index f32b863..9bfba09 100644 --- a/src/com/versign/tat/dnssec/CaptiveValidator.java +++ b/src/com/versign/tat/dnssec/CaptiveValidator.java @@ -1,38 +1,37 @@ -/* - * Copyright (c) 2009 VeriSign, Inc. 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. - * - */ +/***************************** -*- Java -*- ********************************\ + * * + * Copyright (c) 2009 VeriSign, Inc. All rights reserved. * + * * + * This software is provided solely in connection with the terms of the * + * license agreement. Any other use without the prior express written * + * permission of VeriSign is completely prohibited. The software and * + * documentation are "Commercial Items", as that term is defined in 48 * + * C.F.R. section 2.101, consisting of "Commercial Computer Software" and * + * "Commercial Computer Software Documentation" as such terms are defined * + * in 48 C.F.R. section 252.227-7014(a)(5) and 48 C.F.R. section * + * 252.227-7014(a)(1), and used in 48 C.F.R. section 12.212 and 48 C.F.R. * + * section 227.7202, as applicable. Pursuant to the above and other * + * relevant sections of the Code of Federal Regulations, as applicable, * + * VeriSign's publications, commercial computer software, and commercial * + * computer software documentation are distributed and licensed to United * + * States Government end users with only those rights as granted to all * + * other end users, according to the terms and conditions contained in the * + * license agreement(s) that accompany the products and software * + * documentation. * + * * +\***************************************************************************/ -package com.versign.tat.dnssec; +package com.verisign.tat.dnssec; -import java.io.IOException; -import java.util.*; +import org.apache.log4j.Logger; import org.xbill.DNS.*; +import java.io.IOException; + +import java.util.*; + + /** * This resolver module implements a "captive" DNSSEC validator. The captive * validator does not have direct access to the Internet and DNS system -- @@ -40,22 +39,21 @@ import org.xbill.DNS.*; * This is useful for determining if responses coming from a given authoritative * server will validate independent of the normal chain of trust. */ - public class CaptiveValidator { - // A data structure holding all all of our trusted keys. private TrustAnchorStore mTrustedKeys; // The local validation utilities. - private ValUtils mValUtils; + private ValUtils mValUtils; // The local verification utility. - private DnsSecVerifier mVerifier; + private DnsSecVerifier mVerifier; + private Logger log = Logger.getLogger(this.getClass()); public CaptiveValidator() { - mVerifier = new DnsSecVerifier(); - mValUtils = new ValUtils(mVerifier); - mTrustedKeys = new TrustAnchorStore(); + mVerifier = new DnsSecVerifier(); + mValUtils = new ValUtils(mVerifier); + mTrustedKeys = new TrustAnchorStore(); } // ---------------- Module Initialization ------------------- @@ -63,17 +61,18 @@ public class CaptiveValidator { /** * Add a set of trusted keys from a file. The file should be in DNS master * zone file format. Only DNSKEY records will be added. - * + * * @param filename * The file contains the trusted keys. * @throws IOException */ @SuppressWarnings("unchecked") - public void addTrustedKeysFromFile(String filename) throws IOException { + public void addTrustedKeysFromFile(String filename) + throws IOException { // First read in the whole trust anchor file. - Master master = new Master(filename, Name.root, 0); + Master master = new Master(filename, Name.root, 0); ArrayList records = new ArrayList(); - Record r = null; + Record r = null; while ((r = master.nextRecord()) != null) { records.add(r); @@ -85,21 +84,27 @@ public class CaptiveValidator { Collections.sort(records); SRRset cur_rrset = new SRRset(); + for (Record rec : records) { // Skip RR types that cannot be used as trusted keys. I.e., // everything not a key :) - if (rec.getType() != Type.DNSKEY) continue; + if (rec.getType() != Type.DNSKEY) { + continue; + } // If our cur_rrset is empty, we can just add it. if (cur_rrset.size() == 0) { cur_rrset.addRR(rec); + continue; } + // If this record matches our current RRset, we can just add it. - if (cur_rrset.getName().equals(rec.getName()) - && cur_rrset.getType() == rec.getType() - && cur_rrset.getDClass() == rec.getDClass()) { + if (cur_rrset.getName().equals(rec.getName()) && + (cur_rrset.getType() == rec.getType()) && + (cur_rrset.getDClass() == rec.getDClass())) { cur_rrset.addRR(rec); + continue; } @@ -114,9 +119,10 @@ public class CaptiveValidator { mTrustedKeys.store(cur_rrset); } } - + public void addTrustedKeysFromResponse(Message m) { - RRset[] rrsets = m.getSectionRRsets(Section.ANSWER); + RRset [] rrsets = m.getSectionRRsets(Section.ANSWER); + for (int i = 0; i < rrsets.length; ++i) { if (rrsets[i].getType() == Type.DNSKEY) { SRRset srrset = new SRRset(rrsets[i]); @@ -131,82 +137,92 @@ public class CaptiveValidator { * This routine normalizes a response. This includes removing "irrelevant" * records from the answer and additional sections and (re)synthesizing * CNAMEs from DNAMEs, if present. - * + * * @param response */ private SMessage normalize(SMessage m) { - if (m == null) return m; - - if (m.getRcode() != Rcode.NOERROR && m.getRcode() != Rcode.NXDOMAIN) { + if (m == null) { return m; } - Name qname = m.getQuestion().getName(); - int qtype = m.getQuestion().getType(); + if ((m.getRcode() != Rcode.NOERROR) && + (m.getRcode() != Rcode.NXDOMAIN)) { + return m; + } - Name sname = qname; + Name qname = m.getQuestion().getName(); + int qtype = m.getQuestion().getType(); + + Name sname = qname; // For the ANSWER section, remove all "irrelevant" records and add // synthesized CNAMEs from DNAMEs // This will strip out-of-order CNAMEs as well. - List rrset_list = m.getSectionList(Section.ANSWER); - Set additional_names = new HashSet(); + List rrset_list = m.getSectionList(Section.ANSWER); + Set additional_names = new HashSet(); for (ListIterator i = rrset_list.listIterator(); i.hasNext();) { SRRset rrset = i.next(); - int type = rrset.getType(); - Name n = rrset.getName(); + int type = rrset.getType(); + Name n = rrset.getName(); // Handle DNAME synthesis; DNAME synthesis does not occur at the // DNAME name itself. - if (type == Type.DNAME && ValUtils.strictSubdomain(sname, n)) { + if ((type == Type.DNAME) && ValUtils.strictSubdomain(sname, n)) { if (rrset.size() > 1) { - // log.debug("Found DNAME rrset with size > 1: " + rrset); - // return Util.errorMessage(m, Rcode.SERVFAIL); - return null; // FIXME + log.debug("Found DNAME rrset with size > 1: " + rrset); + m.setStatus(SecurityStatus.INVALID); + + return m; } + DNAMERecord dname = (DNAMERecord) rrset.first(); + try { Name cname_alias = sname.fromDNAME(dname); - // Note that synthesized CNAMEs should have a TTL of zero. - CNAMERecord cname = new CNAMERecord(sname, + // Note that synthesized CNAMEs should have a TTL of zero. + CNAMERecord cname = new CNAMERecord(sname, dname.getDClass(), 0, cname_alias); - SRRset cname_rrset = new SRRset(); + SRRset cname_rrset = new SRRset(); cname_rrset.addRR(cname); i.add(cname_rrset); sname = cname_alias; } catch (NameTooLongException e) { -// log.debug("not adding synthesized CNAME -- " -// + "generated name is too long", e); + log.debug("not adding synthesized CNAME -- " + + "generated name is too long", e); } + continue; } // The only records in the ANSWER section not allowed to if (!n.equals(sname)) { -// log.debug("normalize: removing irrelevant rrset: " + rrset); + log.debug("normalize: removing irrelevant rrset: " + rrset); i.remove(); + continue; } // Follow the CNAME chain. if (type == Type.CNAME) { if (rrset.size() > 1) { -// log.debug("Found CNAME rrset with size > 1: " + rrset); -// return Util.errorMessage(m, Rcode.SERVFAIL); - return null; // FIXME + log.debug("Found CNAME rrset with size > 1: " + rrset); + m.setStatus(SecurityStatus.INVALID); + + return m; } CNAMERecord cname = (CNAMERecord) rrset.first(); sname = cname.getAlias(); + continue; } // Otherwise, make sure that the RRset matches the qtype. - if (qtype != Type.ANY && qtype != type) { -// log.debug("normalize: removing irrelevant rrset: " + rrset); + if ((qtype != Type.ANY) && (qtype != type)) { + log.debug("normalize: removing irrelevant rrset: " + rrset); i.remove(); } @@ -216,6 +232,7 @@ public class CaptiveValidator { // Get additional names from AUTHORITY rrset_list = m.getSectionList(Section.AUTHORITY); + for (SRRset rrset : rrset_list) { rrsetAdditionalNames(additional_names, rrset); } @@ -224,42 +241,46 @@ public class CaptiveValidator { // address record and not in the collection of additional names found in // ANSWER and AUTHORITY. rrset_list = m.getSectionList(Section.ADDITIONAL); + for (Iterator i = rrset_list.iterator(); i.hasNext();) { SRRset rrset = i.next(); - int type = rrset.getType(); - if ((type == Type.A || type == Type.AAAA) - && !additional_names.contains(rrset.getName())) { + int type = rrset.getType(); + + if (((type == Type.A) || (type == Type.AAAA)) && + !additional_names.contains(rrset.getName())) { i.remove(); } - // FIXME: what about other types? } return m; } - + /** * Extract additional names from the records in an rrset. - * + * * @param additional_names * The set to add the additional names to, if any. * @param rrset * The rrset to extract from. */ private void rrsetAdditionalNames(Set additional_names, SRRset rrset) { - if (rrset == null) return; + if (rrset == null) { + return; + } for (Iterator i = rrset.rrs(); i.hasNext();) { - Record r = i.next(); - Name add_name = r.getAdditionalName(); + Record r = i.next(); + Name add_name = r.getAdditionalName(); + if (add_name != null) { additional_names.add(add_name); } } } - + private SRRset findKeys(SMessage message) { - Name qname = message.getQName(); - int qclass = message.getQClass(); + Name qname = message.getQName(); + int qclass = message.getQClass(); return mTrustedKeys.find(qname, qclass); } @@ -270,27 +291,30 @@ public class CaptiveValidator { * on in the original request, the response was already validated, or the * response is a kind of message that is unvalidatable (i.e., SERVFAIL, * REFUSED, etc.) - * + * * @param message * The message to check. * @param origRequest * The original request received from the client. - * + * * @return true if the response could use validation (although this does not * mean we can actually validate this response). */ private boolean needsValidation(SMessage message) { int rcode = message.getRcode(); - if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) { - // log.debug("cannot validate non-answer."); - // log.trace("non-answer: " + response); + if ((rcode != Rcode.NOERROR) && (rcode != Rcode.NXDOMAIN)) { + log.debug("cannot validate non-answer."); + log.trace("non-answer: " + message); + return false; } - - if (!mTrustedKeys.isBelowTrustAnchor(message.getQName(), message.getQClass())) { + + if (!mTrustedKeys.isBelowTrustAnchor(message.getQName(), + message.getQClass())) { return false; } + return true; } @@ -298,11 +322,11 @@ public class CaptiveValidator { * Given a "positive" response -- a response that contains an answer to the * question, and no CNAME chain, validate this response. This generally * consists of verifying the answer RRset and the authority RRsets. - * + * * Note that by the time this method is called, the process of finding the * trusted DNSKEY rrset that signs this response must already have been * completed. - * + * * @param response * The response to validate. * @param request @@ -312,46 +336,50 @@ public class CaptiveValidator { * answer. */ private void validatePositiveResponse(SMessage message, SRRset key_rrset) { - Name qname = message.getQName(); - int qtype = message.getQType(); + Name qname = message.getQName(); + int qtype = message.getQType(); - SMessage m = message; + SMessage m = message; // validate the ANSWER section - this will be the answer itself - SRRset[] rrsets = m.getSectionRRsets(Section.ANSWER); + SRRset [] rrsets = m.getSectionRRsets(Section.ANSWER); - Name wc = null; - boolean wcNSEC_ok = false; - boolean dname = false; - List nsec3s = null; + Name wc = null; + boolean wcNSEC_ok = false; + boolean dname = false; + List nsec3s = null; for (int i = 0; i < rrsets.length; i++) { // Skip the CNAME following a (validated) DNAME. // Because of the normalization routines in NameserverClient, there // will always be an unsigned CNAME following a DNAME (unless // qtype=DNAME). - if (dname && rrsets[i].getType() == Type.CNAME) { + if (dname && (rrsets[i].getType() == Type.CNAME)) { dname = false; + continue; } // Verify the answer rrset. int status = mValUtils.verifySRRset(rrsets[i], key_rrset); + // 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]); + log.debug("Positive response has failed ANSWER rrset: " + + rrsets[i]); m.setStatus(SecurityStatus.BOGUS); + return; } + // Check to see if the rrset is the result of a wildcard expansion. // If so, an additional check will need to be made in the authority // section. wc = ValUtils.rrsetWildcard(rrsets[i]); // Notice a DNAME that should be followed by an unsigned CNAME. - if (qtype != Type.DNAME && rrsets[i].getType() == Type.DNAME) { + if ((qtype != Type.DNAME) && (rrsets[i].getType() == Type.DNAME)) { dname = true; } } @@ -359,41 +387,50 @@ public class CaptiveValidator { // validate the AUTHORITY section as well - this will generally be the // NS rrset (which could be missing, no problem) rrsets = m.getSectionRRsets(Section.AUTHORITY); + for (int i = 0; i < rrsets.length; i++) { int status = mValUtils.verifySRRset(rrsets[i], key_rrset); + // If anything in the authority section fails to be secure, we have // a // bad message. if (status != SecurityStatus.SECURE) { - // log.debug("Positive response has failed AUTHORITY rrset: " - // + rrsets[i]); + log.debug("Positive response has failed AUTHORITY rrset: " + + rrsets[i]); m.setStatus(SecurityStatus.BOGUS); + return; } // If this is a positive wildcard response, and we have a (just // verified) NSEC record, try to use it to 1) prove that qname // doesn't exist and 2) that the correct wildcard was used. - if (wc != null && rrsets[i].getType() == Type.NSEC) { + if ((wc != null) && (rrsets[i].getType() == Type.NSEC)) { NSECRecord nsec = (NSECRecord) rrsets[i].first(); if (ValUtils.nsecProvesNameError(nsec, qname, - key_rrset.getName())) { + key_rrset.getName())) { Name nsec_wc = ValUtils.nsecWildcard(qname, nsec); + if (!wc.equals(nsec_wc)) { // log.debug("Positive wildcard response wasn't generated " // + "by the correct wildcard"); m.setStatus(SecurityStatus.BOGUS); + return; } + wcNSEC_ok = true; } } // Otherwise, if this is a positive wildcard response and we have // NSEC3 records, collect them. - if (wc != null && rrsets[i].getType() == Type.NSEC3) { - if (nsec3s == null) nsec3s = new ArrayList(); + if ((wc != null) && (rrsets[i].getType() == Type.NSEC3)) { + if (nsec3s == null) { + nsec3s = new ArrayList(); + } + nsec3s.add((NSEC3Record) rrsets[i].first()); } } @@ -401,23 +438,24 @@ public class CaptiveValidator { // If this was a positive wildcard response that we haven't already // proven, and we have NSEC3 records, try to prove it using the NSEC3 // records. - if (wc != null && !wcNSEC_ok && nsec3s != null) { + if ((wc != null) && !wcNSEC_ok && (nsec3s != null)) { if (NSEC3ValUtils.proveWildcard(nsec3s, qname, key_rrset.getName(), - wc)) { + wc)) { wcNSEC_ok = true; } } // If after all this, we still haven't proven the positive wildcard // response, fail. - if (wc != null && !wcNSEC_ok) { + if ((wc != null) && !wcNSEC_ok) { // log.debug("positive response was wildcard expansion and " // + "did not prove original data did not exist"); m.setStatus(SecurityStatus.BOGUS); + return; } - // log.trace("Successfully validated positive response"); + log.trace("Successfully validated positive response"); m.setStatus(SecurityStatus.SECURE); } @@ -425,122 +463,160 @@ public class CaptiveValidator { SMessage m = message; if (m.getCount(Section.ANSWER) > 0) { - // FIXME: fail somehow. + m.setStatus(SecurityStatus.INVALID); + + return; } // validate the AUTHORITY section. - SRRset[] rrsets = m.getSectionRRsets(Section.AUTHORITY); + SRRset [] rrsets = m.getSectionRRsets(Section.AUTHORITY); - boolean secure_delegation = false; - Name delegation = null; - Name nsec3zone = null; - NSECRecord nsec = null; - List nsec3s = null; + boolean secure_delegation = false; + Name delegation = null; + Name nsec3zone = null; + NSECRecord nsec = null; + List nsec3s = null; // validate the AUTHORITY section as well - this will generally be the // NS rrset, plus proof of a secure delegation or not rrsets = m.getSectionRRsets(Section.AUTHORITY); + for (int i = 0; i < rrsets.length; i++) { - int status = mValUtils.verifySRRset(rrsets[i], key_rrset); - // If anything in the authority section fails to be secure, we have - // a bad message. - if (status != SecurityStatus.SECURE) { - // log.debug("Positive response has failed AUTHORITY rrset: " - // + rrsets[i]); - m.setStatus(SecurityStatus.BOGUS); - return; + int type = rrsets[i].getType(); + + // The NS RRset won't be signed, but everything else should be. + if (type != Type.NS) { + int status = mValUtils.verifySRRset(rrsets[i], key_rrset); + + // If anything in the authority section fails to be secure, we + // have + // a bad message. + if (status != SecurityStatus.SECURE) { + log.debug("Positive response has failed AUTHORITY rrset: " + + rrsets[i]); + m.setStatus(SecurityStatus.BOGUS); + + return; + } } - int type = rrsets[i].getType(); switch (type) { - case Type.DS: - secure_delegation = true; - break; - case Type.NS: - delegation = rrsets[i].getName(); - break; - case Type.NSEC: - nsec = (NSECRecord) rrsets[i].first(); - break; - case Type.NSEC3: - if (nsec3s == null) nsec3s = new ArrayList(); - NSEC3Record nsec3 = (NSEC3Record) rrsets[i].first(); - nsec3s.add(nsec3); - nsec3zone = rrsets[i].getSignerName(); // this is a hack of sorts. - break; - default: - // FIXME: should probably whine if we see something else. - break; + case Type.DS: + secure_delegation = true; + + break; + + case Type.NS: + delegation = rrsets[i].getName(); + + break; + + case Type.NSEC: + nsec = (NSECRecord) rrsets[i].first(); + + break; + + case Type.NSEC3: + + if (nsec3s == null) { + nsec3s = new ArrayList(); + } + + NSEC3Record nsec3 = (NSEC3Record) rrsets[i].first(); + nsec3s.add(nsec3); + nsec3zone = rrsets[i].getSignerName(); // this is a hack of + // sorts. + + break; + + default: + log.warn( + "Encountered unexpected type in a REFERRAL response: " + + Type.string(type)); + + break; } } - + // At this point, all validatable RRsets have been validated. // 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. m.setStatus(SecurityStatus.BOGUS); + return; } - + if (secure_delegation) { - if (nsec != null || nsec3s.size() > 0) { - // we found both a DS rrset *and* NSEC/NSEC3 rrsets! + if ((nsec != null) || ((nsec3s != null) && (nsec3s.size() > 0))) { + // we found both a DS rrset *and* NSEC/NSEC3 rrsets! m.setStatus(SecurityStatus.BOGUS); + return; } + // otherwise, we are done. m.setStatus(SecurityStatus.SECURE); + return; } // Note: not going to care if both NSEC and NSEC3 rrsets were present. if (nsec != null) { byte status = ValUtils.nsecProvesNoDS(nsec, delegation); + if (status != SecurityStatus.SECURE) { - // The NSEC *must* prove that there was no DS record. The INSECURE state here is still bogus. + // The NSEC *must* prove that there was no DS record. The + // INSECURE state here is still bogus. m.setStatus(SecurityStatus.BOGUS); + return; } + m.setStatus(SecurityStatus.SECURE); + return; } - + if (nsec3s.size() > 0) { byte status = NSEC3ValUtils.proveNoDS(nsec3s, delegation, nsec3zone); + if (status != SecurityStatus.SECURE) { - // the NSEC3 RRs MUST prove no DS, so the INDETERMINATE state is actually bogus + // the NSEC3 RRs MUST prove no DS, so the INDETERMINATE state is + // actually bogus m.setStatus(SecurityStatus.BOGUS); + return; } + m.setStatus(SecurityStatus.SECURE); - return; + + return; } // failed to find proof either way. m.setStatus(SecurityStatus.BOGUS); } - private void validateCNAMEResponse(SMessage message, SRRset key_rrset) { - - } + private void validateCNAMEResponse(SMessage message, SRRset key_rrset) {} /** * Given an "ANY" response -- a response that contains an answer to a * qtype==ANY question, with answers. This consists of simply verifying all * present answer/auth RRsets, with no checking that all types are present. - * + * * NOTE: it may be possible to get parent-side delegation point records * here, which won't all be signed. Right now, this routine relies on the * upstream iterative resolver to not return these responses -- instead * treating them as referrals. - * + * * NOTE: RFC 4035 is silent on this issue, so this may change upon * clarification. - * + * * Note that by the time this method is called, the process of finding the * trusted DNSKEY rrset that signs this response must already have been * completed. - * + * * @param message * The response to validate. * @param key_rrset @@ -550,22 +626,26 @@ public class CaptiveValidator { private void validateAnyResponse(SMessage message, SRRset key_rrset) { int qtype = message.getQType(); - if (qtype != Type.ANY) + if (qtype != Type.ANY) { throw new IllegalArgumentException( - "ANY validation called on non-ANY response."); + "ANY validation called on non-ANY response."); + } SMessage m = message; // validate the ANSWER section. - SRRset[] rrsets = m.getSectionRRsets(Section.ANSWER); + SRRset [] rrsets = m.getSectionRRsets(Section.ANSWER); + for (int i = 0; i < rrsets.length; i++) { int status = mValUtils.verifySRRset(rrsets[i], key_rrset); + // 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]); + log.debug("Positive response has failed ANSWER rrset: " + + rrsets[i]); m.setStatus(SecurityStatus.BOGUS); + return; } } @@ -573,20 +653,23 @@ public class CaptiveValidator { // validate the AUTHORITY section as well - this will be the NS rrset // (which could be missing, no problem) rrsets = m.getSectionRRsets(Section.AUTHORITY); + for (int i = 0; i < rrsets.length; i++) { int status = mValUtils.verifySRRset(rrsets[i], key_rrset); + // If anything in the authority section fails to be secure, we have // a // bad message. if (status != SecurityStatus.SECURE) { - // log.debug("Positive response has failed AUTHORITY rrset: " - // + rrsets[i]); + log.debug("Positive response has failed AUTHORITY rrset: " + + rrsets[i]); m.setStatus(SecurityStatus.BOGUS); + return; } } - // log.trace("Successfully validated positive ANY response"); + log.trace("Successfully validated positive ANY response"); m.setStatus(SecurityStatus.SECURE); } @@ -596,11 +679,11 @@ public class CaptiveValidator { * the authority section rrsets and making certain that the authority * section NSEC/NSEC3s proves that the qname does exist and the qtype * doesn't. - * + * * Note that by the time this method is called, the process of finding the * trusted DNSKEY rrset that signs this response must already have been * completed. - * + * * @param response * The response to validate. * @param request @@ -609,10 +692,10 @@ public class CaptiveValidator { * The trusted DNSKEY rrset that signs this response. */ private void validateNodataResponse(SMessage message, SRRset key_rrset) { - Name qname = message.getQName(); - int qtype = message.getQType(); + Name qname = message.getQName(); + int qtype = message.getQType(); - SMessage m = message; + SMessage m = message; // Since we are here, there must be nothing in the ANSWER section to // validate. (Note: CNAME/DNAME responses will not directly get here -- @@ -620,24 +703,31 @@ public class CaptiveValidator { // responses.) // validate the AUTHORITY section - SRRset[] rrsets = m.getSectionRRsets(Section.AUTHORITY); + SRRset [] rrsets = m.getSectionRRsets(Section.AUTHORITY); - boolean hasValidNSEC = false; // If true, then the NODATA has been - // proven. - Name ce = null; // for wildcard NODATA responses. This is the proven - // closest encloser. - NSECRecord wc = null; // for wildcard NODATA responses. This is the - // wildcard NSEC. - List nsec3s = null; // A collection of NSEC3 RRs found in the authority - // section. - Name nsec3Signer = null; // The RRSIG signer field for the NSEC3 RRs. + boolean hasValidNSEC = false; // If true, then the NODATA has been + // proven. + + Name ce = null; // for wildcard NODATA responses. This is the proven + // closest encloser. + + NSECRecord wc = null; // for wildcard NODATA responses. This is the + // wildcard NSEC. + + List nsec3s = null; // A collection of NSEC3 RRs found in + // the authority + // section. + + Name nsec3Signer = null; // The RRSIG signer field for the NSEC3 RRs. for (int i = 0; i < rrsets.length; i++) { int status = mValUtils.verifySRRset(rrsets[i], key_rrset); + if (status != SecurityStatus.SECURE) { - // log.debug("NODATA response has failed AUTHORITY rrset: " - // + rrsets[i]); + log.debug("NODATA response has failed AUTHORITY rrset: " + + rrsets[i]); m.setStatus(SecurityStatus.BOGUS); + return; } @@ -645,20 +735,25 @@ public class CaptiveValidator { // This needs to handle the ENT NODATA case. if (rrsets[i].getType() == Type.NSEC) { NSECRecord nsec = (NSECRecord) rrsets[i].first(); + if (ValUtils.nsecProvesNodata(nsec, qname, qtype)) { hasValidNSEC = true; - if (nsec.getName().isWild()) wc = nsec; - } else if (ValUtils.nsecProvesNameError( - nsec, - qname, - rrsets[i].getSignerName())) { + + if (nsec.getName().isWild()) { + wc = nsec; + } + } else if (ValUtils.nsecProvesNameError(nsec, qname, + rrsets[i].getSignerName())) { ce = ValUtils.closestEncloser(qname, nsec); } } // Collect any NSEC3 records present. if (rrsets[i].getType() == Type.NSEC3) { - if (nsec3s == null) nsec3s = new ArrayList(); + if (nsec3s == null) { + nsec3s = new ArrayList(); + } + nsec3s.add((NSEC3Record) rrsets[i].first()); nsec3Signer = rrsets[i].getSignerName(); } @@ -669,33 +764,36 @@ public class CaptiveValidator { // The wildcard NODATA is 1 NSEC proving that qname does not exists (and // also proving what the closest encloser is), and 1 NSEC showing the // matching wildcard, which must be *.closest_encloser. - if (ce != null || wc != null) { + if ((ce != null) || (wc != null)) { try { Name wc_name = new Name("*", ce); + if (!wc_name.equals(wc.getName())) { hasValidNSEC = false; } } catch (TextParseException e) { - // log.error(e); + log.error(e); } } NSEC3ValUtils.stripUnknownAlgNSEC3s(nsec3s); - if (!hasValidNSEC && nsec3s != null && nsec3s.size() > 0) { + if (!hasValidNSEC && (nsec3s != null) && (nsec3s.size() > 0)) { // try to prove NODATA with our NSEC3 record(s) hasValidNSEC = NSEC3ValUtils.proveNodata(nsec3s, qname, qtype, - nsec3Signer); + nsec3Signer); } if (!hasValidNSEC) { - // log.debug("NODATA response failed to prove NODATA " - // + "status with NSEC/NSEC3"); - // log.trace("Failed NODATA:\n" + m); + log.debug("NODATA response failed to prove NODATA " + + "status with NSEC/NSEC3"); + log.trace("Failed NODATA:\n" + m); m.setStatus(SecurityStatus.BOGUS); + return; } - // log.trace("successfully validated NODATA response."); + + log.trace("successfully validated NODATA response."); m.setStatus(SecurityStatus.SECURE); } @@ -704,11 +802,11 @@ public class CaptiveValidator { * Rcode. This consists of verifying the authority section rrsets and making * certain that the authority section NSEC proves that the qname doesn't * exist and the covering wildcard also doesn't exist.. - * + * * Note that by the time this method is called, the process of finding the * trusted DNSKEY rrset that signs this response must already have been * completed. - * + * * @param response * The response to validate. * @param request @@ -717,45 +815,57 @@ public class CaptiveValidator { * The trusted DNSKEY rrset that signs this response. */ private void validateNameErrorResponse(SMessage message, SRRset key_rrset) { - Name qname = message.getQName(); + Name qname = message.getQName(); - SMessage m = message; + SMessage m = message; - // FIXME: should we check to see if there is anything in the answer - // section? if so, what should the result be? + if (message.getCount(Section.ANSWER) > 0) { + log.warn( + "NAME ERROR response contained records in the ANSWER SECTION"); + message.setStatus(SecurityStatus.INVALID); + + return; + } // Validate the authority section -- all RRsets in the authority section // must be signed and valid. // In addition, the NSEC record(s) must prove the NXDOMAIN condition. - - boolean hasValidNSEC = false; - boolean hasValidWCNSEC = false; - SRRset[] rrsets = m.getSectionRRsets(Section.AUTHORITY); - List nsec3s = null; - Name nsec3Signer = null; + boolean hasValidNSEC = false; + boolean hasValidWCNSEC = false; + SRRset [] rrsets = m.getSectionRRsets(Section.AUTHORITY); + List nsec3s = null; + Name nsec3Signer = null; for (int i = 0; i < rrsets.length; i++) { int status = mValUtils.verifySRRset(rrsets[i], key_rrset); + if (status != SecurityStatus.SECURE) { - // log.debug("NameError response has failed AUTHORITY rrset: " - // + rrsets[i]); + log.debug("NameError response has failed AUTHORITY rrset: " + + rrsets[i]); m.setStatus(SecurityStatus.BOGUS); + return; } + if (rrsets[i].getType() == Type.NSEC) { NSECRecord nsec = (NSECRecord) rrsets[i].first(); if (ValUtils.nsecProvesNameError(nsec, qname, - rrsets[i].getSignerName())) { + rrsets[i].getSignerName())) { hasValidNSEC = true; } + if (ValUtils.nsecProvesNoWC(nsec, qname, - rrsets[i].getSignerName())) { + rrsets[i].getSignerName())) { hasValidWCNSEC = true; } } + if (rrsets[i].getType() == Type.NSEC3) { - if (nsec3s == null) nsec3s = new ArrayList(); + if (nsec3s == null) { + nsec3s = new ArrayList(); + } + nsec3s.add((NSEC3Record) rrsets[i].first()); nsec3Signer = rrsets[i].getSignerName(); } @@ -763,55 +873,68 @@ public class CaptiveValidator { NSEC3ValUtils.stripUnknownAlgNSEC3s(nsec3s); - if (nsec3s != null && nsec3s.size() > 0) { - // log.debug("Validating nxdomain: using NSEC3 records"); - // Attempt to prove name error with nsec3 records. + if ((nsec3s != null) && (nsec3s.size() > 0)) { + log.debug("Validating nxdomain: using NSEC3 records"); + // Attempt to prove name error with nsec3 records. if (NSEC3ValUtils.allNSEC3sIgnoreable(nsec3s, key_rrset, mVerifier)) { // log.debug("all NSEC3s were validated but ignored."); m.setStatus(SecurityStatus.INSECURE); + return; } - hasValidNSEC = NSEC3ValUtils.proveNameError(nsec3s, qname, - nsec3Signer); + hasValidNSEC = NSEC3ValUtils.proveNameError(nsec3s, qname, + nsec3Signer); // Note that we assume that the NSEC3ValUtils proofs encompass the // wildcard part of the proof. - hasValidWCNSEC = hasValidNSEC; + hasValidWCNSEC = hasValidNSEC; } // 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"); + log.debug("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"); + log.debug("NameError response has failed to prove: " + + "covering wildcard does not exist"); m.setStatus(SecurityStatus.BOGUS); + return; } // Otherwise, we consider the message secure. - // log.trace("successfully validated NAME ERROR response."); + log.trace("successfully validated NAME ERROR response."); m.setStatus(SecurityStatus.SECURE); } public byte validateMessage(SMessage message, Name zone) { + if (!zone.isAbsolute()) { + try { + zone = Name.concatenate(zone, Name.root); + } catch (NameTooLongException e) { + log.error(e); + + return SecurityStatus.UNCHECKED; + } + } // FIXME: it is unclear if we should actually normalize our responses // Instead, maybe we should just fail if they are not normal? message = normalize(message); - - if (! needsValidation(message)) { + + if (!needsValidation(message)) { return SecurityStatus.UNCHECKED; } - + SRRset key_rrset = findKeys(message); + if (key_rrset == null) { return SecurityStatus.BOGUS; } @@ -819,36 +942,57 @@ public class CaptiveValidator { ValUtils.ResponseType subtype = ValUtils.classifyResponse(message, zone); switch (subtype) { - case POSITIVE: - // log.trace("Validating a positive response"); - validatePositiveResponse(message, key_rrset); - break; - case REFERRAL: - validateReferral(message, key_rrset); - break; - case NODATA: - // log.trace("Validating a NODATA response"); - validateNodataResponse(message, key_rrset); - break; - case NAMEERROR: - // log.trace("Validating a NXDOMAIN response"); - validateNameErrorResponse(message, key_rrset); - break; - case CNAME: - // log.trace("Validating a CNAME response"); - // forward on to the special CNAME state for this. - // state.state = ValEventState.CNAME_STATE; - validateCNAMEResponse(message, key_rrset); - break; - case ANY: - // log.trace("Validating a positive ANY response"); - validateAnyResponse(message, key_rrset); - break; - default: - // log.error("unhandled response subtype: " + subtype); + case POSITIVE: + log.trace("Validating a positive response"); + validatePositiveResponse(message, key_rrset); + + break; + + case REFERRAL: + validateReferral(message, key_rrset); + + break; + + case NODATA: + log.trace("Validating a NODATA response"); + validateNodataResponse(message, key_rrset); + + break; + + case NAMEERROR: + log.trace("Validating a NXDOMAIN response"); + validateNameErrorResponse(message, key_rrset); + + break; + + case CNAME: + log.trace("Validating a CNAME response"); + validateCNAMEResponse(message, key_rrset); + + break; + + case ANY: + log.trace("Validating a positive ANY response"); + validateAnyResponse(message, key_rrset); + + break; + + default: + log.error("unhandled response subtype: " + subtype); } return message.getSecurityStatus().getStatus(); + } + public byte validateMessage(Message message, String zone) + throws TextParseException { + SMessage sm = new SMessage(message); + Name z = Name.fromString(zone); + + return validateMessage(sm, z); + } + + public List listTrustedKeys() { + return mTrustedKeys.listTrustAnchors(); } } diff --git a/src/com/versign/tat/dnssec/DnsSecVerifier.java b/src/com/versign/tat/dnssec/DnsSecVerifier.java index e43b987..dac34cb 100644 --- a/src/com/versign/tat/dnssec/DnsSecVerifier.java +++ b/src/com/versign/tat/dnssec/DnsSecVerifier.java @@ -1,42 +1,38 @@ -/* - * Copyright (c) 2009 VeriSign, Inc. 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. - * - */ +/***************************** -*- Java -*- ********************************\ + * * + * Copyright (c) 2009 VeriSign, Inc. All rights reserved. * + * * + * This software is provided solely in connection with the terms of the * + * license agreement. Any other use without the prior express written * + * permission of VeriSign is completely prohibited. The software and * + * documentation are "Commercial Items", as that term is defined in 48 * + * C.F.R. section 2.101, consisting of "Commercial Computer Software" and * + * "Commercial Computer Software Documentation" as such terms are defined * + * in 48 C.F.R. section 252.227-7014(a)(5) and 48 C.F.R. section * + * 252.227-7014(a)(1), and used in 48 C.F.R. section 12.212 and 48 C.F.R. * + * section 227.7202, as applicable. Pursuant to the above and other * + * relevant sections of the Code of Federal Regulations, as applicable, * + * VeriSign's publications, commercial computer software, and commercial * + * computer software documentation are distributed and licensed to United * + * States Government end users with only those rights as granted to all * + * other end users, according to the terms and conditions contained in the * + * license agreement(s) that accompany the products and software * + * documentation. * + * * +\***************************************************************************/ -package com.versign.tat.dnssec; +package com.verisign.tat.dnssec; -import java.util.*; -import java.io.*; -import java.security.*; +import org.apache.log4j.Logger; import org.xbill.DNS.*; import org.xbill.DNS.security.*; -import com.versign.tat.dnssec.SecurityStatus; -import com.versign.tat.dnssec.Util; +import java.io.*; + +import java.security.*; + +import java.util.*; /** @@ -44,445 +40,483 @@ import com.versign.tat.dnssec.Util; * contains a similar class. This is a re-implementation that allows us to have * finer control over the validation process. */ -public class DnsSecVerifier -{ - public static final int UNKNOWN = 0; - public static final int RSA = 1; - public static final int DSA = 2; +public class DnsSecVerifier { + public static final int UNKNOWN = 0; + public static final int RSA = 1; + public static final int DSA = 2; + private Logger log = Logger.getLogger(this.getClass()); - /** - * This is a mapping of DNSSEC algorithm numbers/private identifiers to JCA - * algorithm identifiers. - */ - private HashMap mAlgorithmMap; + /** + * This is a mapping of DNSSEC algorithm numbers/private identifiers to JCA + * algorithm identifiers. + */ + private HashMap mAlgorithmMap; - private static class AlgEntry - { - public String jcaName; - public boolean isDSA; - public int dnssecAlg; + public DnsSecVerifier() { + mAlgorithmMap = new HashMap(); - public AlgEntry(String name, int dnssecAlg, boolean isDSA) - { - jcaName = name; - this.dnssecAlg = dnssecAlg; - this.isDSA = isDSA; - } - } - - public DnsSecVerifier() - { - mAlgorithmMap = new HashMap(); - - // set the default algorithm map. - mAlgorithmMap.put(new Integer(DNSSEC.RSAMD5), new AlgEntry("MD5withRSA", - DNSSEC.RSAMD5, false)); - mAlgorithmMap.put(new Integer(DNSSEC.DSA), new AlgEntry("SHA1withDSA", DNSSEC.DSA, - true)); - mAlgorithmMap.put(new Integer(DNSSEC.RSASHA1), new AlgEntry( - "SHA1withRSA", DNSSEC.RSASHA1, false)); - } - - private boolean isDSA(int algorithm) - { - // shortcut the standard algorithms - if (algorithm == DNSSEC.DSA) return true; - if (algorithm == DNSSEC.RSASHA1) return false; - if (algorithm == DNSSEC.RSAMD5) return false; - - AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm)); - if (entry != null) return entry.isDSA; - return false; - } - - public void init(Properties config) - { - if (config == null) return; - - // Algorithm configuration - - // For now, we just accept new identifiers for existing algoirthms. - // FIXME: handle private identifiers. - List aliases = Util.parseConfigPrefix(config, "dns.algorithm."); - - for (Util.ConfigEntry entry : aliases) { - Integer alg_alias = new Integer(Util.parseInt(entry.key, -1)); - Integer alg_orig = new Integer(Util.parseInt(entry.value, -1)); - - if (!mAlgorithmMap.containsKey(alg_orig)) - { -// log.warn("Unable to alias " + alg_alias + " to unknown algorithm " -// + alg_orig); - continue; - } - - if (mAlgorithmMap.containsKey(alg_alias)) - { -// log.warn("Algorithm alias " + alg_alias -// + " is already defined and cannot be redefined"); - continue; - } - - mAlgorithmMap.put(alg_alias, mAlgorithmMap.get(alg_orig)); + // set the default algorithm map. + mAlgorithmMap.put(new Integer(DNSSEC.RSAMD5), + new AlgEntry("MD5withRSA", DNSSEC.RSAMD5, false)); + mAlgorithmMap.put(new Integer(DNSSEC.DSA), + new AlgEntry("SHA1withDSA", DNSSEC.DSA, true)); + mAlgorithmMap.put(new Integer(DNSSEC.RSASHA1), + new AlgEntry("SHA1withRSA", DNSSEC.RSASHA1, false)); + mAlgorithmMap.put(new Integer(DNSSEC.DSA_NSEC3_SHA1), + new AlgEntry("SHA1withDSA", DNSSEC.DSA, true)); + mAlgorithmMap.put(new Integer(DNSSEC.RSA_NSEC3_SHA1), + new AlgEntry("SHA1withRSA", DNSSEC.RSASHA1, false)); + mAlgorithmMap.put(new Integer(DNSSEC.RSASHA256), + new AlgEntry("SHA256withRSA", DNSSEC.RSASHA256, false)); + mAlgorithmMap.put(new Integer(DNSSEC.RSASHA512), + new AlgEntry("SHA512withRSA", DNSSEC.RSASHA512, false)); } - // for debugging purposes, log the entire algorithm map table. -// for (Integer alg : mAlgorithmMap.keySet()) { -// AlgEntry entry = mAlgorithmMap.get(alg); -// if (entry == null) -// log.warn("DNSSEC alg " + alg + " has a null entry!"); -// else -// log.debug("DNSSEC alg " + alg + " maps to " + entry.jcaName -// + " (" + entry.dnssecAlg + ")"); -// } - } + private boolean isDSA(int algorithm) { + // shortcut the standard algorithms + if (algorithm == DNSSEC.DSA) { + return true; + } - /** - * Find the matching DNSKEY(s) to an RRSIG within a DNSKEY rrset. Normally - * this will only return one DNSKEY. It can return more than one, since - * KeyID/Footprints are not guaranteed to be unique. - * - * @param dnskey_rrset The DNSKEY rrset to search. - * @param signature The RRSIG to match against. - * @return A List contains a one or more DNSKEYRecord objects, or null if a - * matching DNSKEY could not be found. - */ - @SuppressWarnings("unchecked") -private List findKey(RRset dnskey_rrset, RRSIGRecord signature) - { - if (!signature.getSigner().equals(dnskey_rrset.getName())) - { -// log.trace("findKey: could not find appropriate key because " -// + "incorrect keyset was supplied. Wanted: " + signature.getSigner() -// + ", got: " + dnskey_rrset.getName()); - return null; + if (algorithm == DNSSEC.RSASHA1) { + return false; + } + + if (algorithm == DNSSEC.RSAMD5) { + return false; + } + + AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm)); + + if (entry != null) { + return entry.isDSA; + } + + return false; } - int keyid = signature.getFootprint(); - int alg = signature.getAlgorithm(); + public void init(Properties config) { + if (config == null) { + return; + } - List res = new ArrayList(dnskey_rrset.size()); + // Algorithm configuration - for (Iterator i = dnskey_rrset.rrs(); i.hasNext();) - { - DNSKEYRecord r = (DNSKEYRecord) i.next(); - if (r.getAlgorithm() == alg && r.getFootprint() == keyid) - { - res.add(r); - } + // For now, we just accept new identifiers for existing algorithms. + // FIXME: handle private identifiers. + List aliases = Util.parseConfigPrefix(config, + "dns.algorithm."); + + for (Util.ConfigEntry entry : aliases) { + Integer alg_alias = new Integer(Util.parseInt(entry.key, -1)); + Integer alg_orig = new Integer(Util.parseInt(entry.value, -1)); + + if (!mAlgorithmMap.containsKey(alg_orig)) { + log.warn("Unable to alias " + alg_alias + + " to unknown algorithm " + alg_orig); + + continue; + } + + if (mAlgorithmMap.containsKey(alg_alias)) { + log.warn("Algorithm alias " + alg_alias + + " is already defined and cannot be redefined"); + + continue; + } + + mAlgorithmMap.put(alg_alias, mAlgorithmMap.get(alg_orig)); + } + + // for debugging purposes, log the entire algorithm map table. + for (Integer alg : mAlgorithmMap.keySet()) { + AlgEntry entry = mAlgorithmMap.get(alg); + + if (entry == null) { + log.warn("DNSSEC alg " + alg + " has a null entry!"); + } else { + log.debug("DNSSEC alg " + alg + " maps to " + entry.jcaName + + " (" + entry.dnssecAlg + ")"); + } + } } - if (res.size() == 0) - { -// log.trace("findKey: could not find a key matching " -// + "the algorithm and footprint in supplied keyset. "); - return null; - } - return res; - } + /** + * Find the matching DNSKEY(s) to an RRSIG within a DNSKEY rrset. Normally + * this will only return one DNSKEY. It can return more than one, since + * KeyID/Footprints are not guaranteed to be unique. + * + * @param dnskey_rrset + * The DNSKEY rrset to search. + * @param signature + * The RRSIG to match against. + * @return A List contains a one or more DNSKEYRecord objects, or null if a + * matching DNSKEY could not be found. + */ + @SuppressWarnings("unchecked") + private List findKey(RRset dnskey_rrset, RRSIGRecord signature) { + if (!signature.getSigner().equals(dnskey_rrset.getName())) { + log.trace("findKey: could not find appropriate key because " + + "incorrect keyset was supplied. Wanted: " + + signature.getSigner() + ", got: " + dnskey_rrset.getName()); - /** - * Check to see if a signature looks valid (i.e., matches the rrset in - * question, in the validity period, etc.) - * - * @param rrset The rrset that the signature belongs to. - * @param sigrec The signature record to check. - * @return A value of DNSSEC.Secure if it looks OK, DNSSEC.Faile if it looks - * bad. - */ - private byte checkSignature(RRset rrset, RRSIGRecord sigrec) - { - if (rrset == null || sigrec == null) return DNSSEC.Failed; - if (!rrset.getName().equals(sigrec.getName())) - { -// log.debug("Signature name does not match RRset name"); - return SecurityStatus.BOGUS; - } - if (rrset.getType() != sigrec.getTypeCovered()) - { -// log.debug("Signature type does not match RRset type"); - return SecurityStatus.BOGUS; + return null; + } + + int keyid = signature.getFootprint(); + int alg = signature.getAlgorithm(); + + List res = new ArrayList(dnskey_rrset.size()); + + for (Iterator i = dnskey_rrset.rrs(); i.hasNext();) { + DNSKEYRecord r = (DNSKEYRecord) i.next(); + + if ((r.getAlgorithm() == alg) && (r.getFootprint() == keyid)) { + res.add(r); + } + } + + if (res.size() == 0) { + log.trace("findKey: could not find a key matching " + + "the algorithm and footprint in supplied keyset. "); + + return null; + } + + return res; } - Date now = new Date(); - Date start = sigrec.getTimeSigned(); - Date expire = sigrec.getExpire(); - if (now.before(start)) - { -// log.debug("Signature is not yet valid"); - return SecurityStatus.BOGUS; + /** + * Check to see if a signature looks valid (i.e., matches the rrset in + * question, in the validity period, etc.) + * + * @param rrset + * The rrset that the signature belongs to. + * @param sigrec + * The signature record to check. + * @return A value of DNSSEC.Secure if it looks OK, DNSSEC.Faile if it looks + * bad. + */ + private byte checkSignature(RRset rrset, RRSIGRecord sigrec) { + if ((rrset == null) || (sigrec == null)) { + return DNSSEC.Failed; + } + + if (!rrset.getName().equals(sigrec.getName())) { + log.debug("Signature name does not match RRset name"); + + return SecurityStatus.BOGUS; + } + + if (rrset.getType() != sigrec.getTypeCovered()) { + log.debug("Signature type does not match RRset type"); + + return SecurityStatus.BOGUS; + } + + Date now = new Date(); + Date start = sigrec.getTimeSigned(); + Date expire = sigrec.getExpire(); + + if (now.before(start)) { + log.debug("Signature is not yet valid"); + + return SecurityStatus.BOGUS; + } + + if (now.after(expire)) { + log.debug("Signature has expired (now = " + now + + ", sig expires = " + expire); + + return SecurityStatus.BOGUS; + } + + return SecurityStatus.SECURE; } - if (now.after(expire)) - { -// log.debug("Signature has expired (now = " + now + ", sig expires = " -// + expire); - return SecurityStatus.BOGUS; + public PublicKey parseDNSKEY(DNSKEYRecord key) { + AlgEntry ae = (AlgEntry) mAlgorithmMap.get(new Integer( + key.getAlgorithm())); + + if (key.getAlgorithm() != ae.dnssecAlg) { + // Recast the DNSKEYRecord in question as one using the offical + // algorithm, to work around the lack of alias support in the + // underlying + // KEYConverter class from DNSjava + key = new DNSKEYRecord(key.getName(), key.getDClass(), + key.getTTL(), key.getFlags(), key.getProtocol(), + ae.dnssecAlg, key.getKey()); + } + + return KEYConverter.parseRecord(key); } - return SecurityStatus.SECURE; - } + /** + * Actually cryptographically verify a signature over the rrset. The RRSIG + * record must match the rrset being verified (see checkSignature). + * + * @param rrset + * The rrset to verify. + * @param sigrec + * The signature to verify with. + * @param key + * The (public) key associated with the RRSIG record. + * @return A security status code: SECURE if it worked, BOGUS if not, + * UNCHECKED if we just couldn't actually do the function. + */ + public byte verifySignature(RRset rrset, RRSIGRecord sigrec, + DNSKEYRecord key) { + try { + PublicKey pk = parseDNSKEY(key); - public PublicKey parseDNSKEY(DNSKEYRecord key) - { - AlgEntry ae = (AlgEntry) mAlgorithmMap - .get(new Integer(key.getAlgorithm())); - if (key.getAlgorithm() != ae.dnssecAlg) - { - // Recast the DNSKEYRecord in question as one using the offical - // algorithm, to work around the lack of alias support in the underlying - // KEYConverter class from DNSjava + if (pk == null) { + log.warn( + "Could not convert DNSKEY record to a JCA public key: " + + key); - key = new DNSKEYRecord(key.getName(), key.getDClass(), key.getTTL(), - key.getFlags(), key.getProtocol(), ae.dnssecAlg, key.getKey()); - } + return SecurityStatus.UNCHECKED; + } - return KEYConverter.parseRecord(key); - } - - - /** - * Actually cryptographically verify a signature over the rrset. The RRSIG - * record must match the rrset being verified (see checkSignature). - * - * @param rrset The rrset to verify. - * @param sigrec The signature to verify with. - * @param key The (public) key associated with the RRSIG record. - * @return A security status code: SECURE if it worked, BOGUS if not, - * UNCHECKED if we just couldn't actually do the function. - */ - public byte verifySignature(RRset rrset, RRSIGRecord sigrec, - DNSKEYRecord key) - { - try - { - PublicKey pk = parseDNSKEY(key); + byte [] data = SignUtils.generateSigData(rrset, sigrec); - if (pk == null) - { -// log.warn("Could not convert DNSKEY record to a JCA public key: " -// + key); + Signature signer = getSignature(sigrec.getAlgorithm()); + + if (signer == null) { + return SecurityStatus.BOGUS; + } + + signer.initVerify(pk); + signer.update(data); + + byte [] sig = sigrec.getSignature(); + + if (isDSA(sigrec.getAlgorithm())) { + sig = SignUtils.convertDSASignature(sig); + } + + if (!signer.verify(sig)) { + log.info("Signature failed to verify cryptographically"); + log.debug("Failed signature: " + sigrec); + + return SecurityStatus.BOGUS; + } + + log.trace("Signature verified: " + sigrec); + + return SecurityStatus.SECURE; + } catch (IOException e) { + log.error("I/O error", e); + } catch (GeneralSecurityException e) { + log.error("Security error", e); + } + + // FIXME: Since I'm not sure what would cause an exception here (failure + // to have the required crypto?) + // We default to UNCHECKED instead of BOGUS. This could be wrong. return SecurityStatus.UNCHECKED; - } + } - byte[] data = SignUtils.generateSigData(rrset, sigrec); + /** + * Verify an RRset against a particular signature. + * + * @return DNSSEC.Secure if the signature verfied, DNSSEC.Failed if it did + * not verify (for any reason), and DNSSEC.Insecure if verification + * could not be completed (usually because the public key was not + * available). + */ + public byte verifySignature(RRset rrset, RRSIGRecord sigrec, RRset key_rrset) { + byte result = checkSignature(rrset, sigrec); + + if (result != SecurityStatus.SECURE) { + return result; + } + + List keys = findKey(key_rrset, sigrec); + + if (keys == null) { + log.trace("could not find appropriate key"); + + return SecurityStatus.BOGUS; + } + + byte status = SecurityStatus.UNCHECKED; + + for (DNSKEYRecord key : keys) { + status = verifySignature(rrset, sigrec, key); + + if (status == SecurityStatus.SECURE) { + break; + } + } + + return status; + } + + /** + * Verifies an RRset. This routine does not modify the RRset. This RRset is + * presumed to be verifiable, and the correct DNSKEY rrset is presumed to + * have been found. + * + * @return SecurityStatus.SECURE if the rrest verified positively, + * SecurityStatus.BOGUS otherwise. + */ + @SuppressWarnings("unchecked") + public byte verify(RRset rrset, RRset key_rrset) { + Iterator i = rrset.sigs(); + + if (!i.hasNext()) { + log.info("RRset failed to verify due to lack of signatures"); + + return SecurityStatus.BOGUS; + } + + while (i.hasNext()) { + RRSIGRecord sigrec = (RRSIGRecord) i.next(); + + byte res = verifySignature(rrset, sigrec, key_rrset); + + if (res == SecurityStatus.SECURE) { + return res; + } + } + + log.info("RRset failed to verify: all signatures were BOGUS"); - Signature signer = getSignature(sigrec.getAlgorithm()); - if (signer == null) - { return SecurityStatus.BOGUS; - } - - signer.initVerify(pk); - signer.update(data); + } + + /** + * Verify an RRset against a single DNSKEY. Use this when you must be + * certain that an RRset signed and verifies with a particular DNSKEY (as + * opposed to a particular DNSKEY rrset). + * + * @param rrset + * The rrset to verify. + * @param dnskey + * The DNSKEY to verify with. + * @return SecurityStatus.SECURE if the rrset verified, BOGUS otherwise. + */ + @SuppressWarnings("unchecked") + public byte verify(RRset rrset, DNSKEYRecord dnskey) { + // Iterate over RRSIGS + Iterator i = rrset.sigs(); + + if (!i.hasNext()) { + log.info("RRset failed to verify due to lack of signatures"); + + return SecurityStatus.BOGUS; + } + + while (i.hasNext()) { + RRSIGRecord sigrec = (RRSIGRecord) i.next(); + + // Skip RRSIGs that do not match our given key's footprint. + if (sigrec.getFootprint() != dnskey.getFootprint()) { + continue; + } + + byte res = verifySignature(rrset, sigrec, dnskey); + + if (res == SecurityStatus.SECURE) { + return res; + } + } + + log.info("RRset failed to verify: all signatures were BOGUS"); - byte[] sig = sigrec.getSignature(); - if (isDSA(sigrec.getAlgorithm())) - { - sig = SignUtils.convertDSASignature(sig); - } - if (!signer.verify(sig)) - { -// log.info("Signature failed to verify cryptographically"); -// log.debug("Failed signature: " + sigrec); return SecurityStatus.BOGUS; - } -// log.trace("Signature verified: " + sigrec); - return SecurityStatus.SECURE; - } - catch (IOException e) - { -// log.error("I/O error", e); - } - catch (GeneralSecurityException e) - { -// log.error("Security error", e); } - // FIXME: Since I'm not sure what would cause an exception here (failure - // to have the required crypto?) - // We default to UNCHECKED instead of BOGUS. This could be wrong. - return SecurityStatus.UNCHECKED; - - } - - /** - * Verify an RRset against a particular signature. - * - * @return DNSSEC.Secure if the signature verfied, DNSSEC.Failed if it did - * not verify (for any reason), and DNSSEC.Insecure if verification - * could not be completed (usually because the public key was not - * available). - */ - public byte verifySignature(RRset rrset, RRSIGRecord sigrec, RRset key_rrset) - { - byte result = checkSignature(rrset, sigrec); - if (result != SecurityStatus.SECURE) return result; - - List keys = findKey(key_rrset, sigrec); - - if (keys == null) - { -// log.trace("could not find appropriate key"); - return SecurityStatus.BOGUS; + public boolean supportsAlgorithm(int algorithm) { + return mAlgorithmMap.containsKey(new Integer(algorithm)); } - byte status = SecurityStatus.UNCHECKED; - - for (DNSKEYRecord key : keys) { - status = verifySignature(rrset, sigrec, key); - - if (status == SecurityStatus.SECURE) break; + public boolean supportsAlgorithm(Name private_id) { + return mAlgorithmMap.containsKey(private_id); } - return status; - } + public int baseAlgorithm(int algorithm) { + switch (algorithm) { + case DNSSEC.RSAMD5: + case DNSSEC.RSASHA1: + return RSA; - /** - * Verifies an RRset. This routine does not modify the RRset. This RRset is - * presumed to be verifiable, and the correct DNSKEY rrset is presumed to - * have been found. - * - * @return SecurityStatus.SECURE if the rrest verified positively, - * SecurityStatus.BOGUS otherwise. - */ - @SuppressWarnings("unchecked") -public byte verify(RRset rrset, RRset key_rrset) - { - Iterator i = rrset.sigs(); + case DNSSEC.DSA: + return DSA; + } - if (!i.hasNext()) - { -// log.info("RRset failed to verify due to lack of signatures"); - return SecurityStatus.BOGUS; - } + AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm)); - while (i.hasNext()) - { - RRSIGRecord sigrec = (RRSIGRecord) i.next(); + if (entry == null) { + return UNKNOWN; + } - byte res = verifySignature(rrset, sigrec, key_rrset); + if (entry.isDSA) { + return DSA; + } - if (res == SecurityStatus.SECURE) return res; - } - -// log.info("RRset failed to verify: all signatures were BOGUS"); - return SecurityStatus.BOGUS; - } - - /** - * Verify an RRset against a single DNSKEY. Use this when you must be - * certain that an RRset signed and verifies with a particular DNSKEY (as - * opposed to a particular DNSKEY rrset). - * - * @param rrset The rrset to verify. - * @param dnskey The DNSKEY to verify with. - * @return SecurityStatus.SECURE if the rrset verified, BOGUS otherwise. - */ - @SuppressWarnings("unchecked") -public byte verify(RRset rrset, DNSKEYRecord dnskey) - { - // Iterate over RRSIGS - - Iterator i = rrset.sigs(); - if (!i.hasNext()) - { -// log.info("RRset failed to verify due to lack of signatures"); - return SecurityStatus.BOGUS; - } - - while (i.hasNext()) - { - RRSIGRecord sigrec = (RRSIGRecord) i.next(); - - // Skip RRSIGs that do not match our given key's footprint. - if (sigrec.getFootprint() != dnskey.getFootprint()) continue; - - byte res = verifySignature(rrset, sigrec, dnskey); - - if (res == SecurityStatus.SECURE) return res; - } - -// log.info("RRset failed to verify: all signatures were BOGUS"); - return SecurityStatus.BOGUS; - } - - public boolean supportsAlgorithm(int algorithm) - { - return mAlgorithmMap.containsKey(new Integer(algorithm)); - } - - public boolean supportsAlgorithm(Name private_id) - { - return mAlgorithmMap.containsKey(private_id); - } - - public int baseAlgorithm(int algorithm) - { - switch (algorithm) - { - case DNSSEC.RSAMD5: - case DNSSEC.RSASHA1: return RSA; - case DNSSEC.DSA: - return DSA; - } - AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm)); - if (entry == null) return UNKNOWN; - if (entry.isDSA) return DSA; - return RSA; - } - - /** @return the appropriate Signature object for this keypair. */ - private Signature getSignature(int algorithm) - { - Signature s = null; - - - try - { - AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm)); - if (entry == null) - { -// log.info("DNSSEC algorithm " + algorithm + " not recognized."); - return null; - } - // TODO: should we cache the instance? - s = Signature.getInstance(entry.jcaName); - } - catch (NoSuchAlgorithmException e) - { -// log.error("error getting Signature object", e); } - return s; - } + /** @return the appropriate Signature object for this keypair. */ + private Signature getSignature(int algorithm) { + Signature s = null; - // TODO: enable private algorithm support in dnsjava. - // Right now, this cannot be used because the DNSKEYRecord object doesn't - // give us - // the private key name. - // private Signature getSignature(Name private_alg) - // { - // Signature s = null; - // - // try - // { - // String alg_id = (String) mAlgorithmMap.get(private_alg); - // if (alg_id == null) - // { - // log.debug("DNSSEC private algorithm '" + private_alg - // + "' not recognized."); - // return null; - // } - // - // s = Signature.getInstance(alg_id); - // } - // catch (NoSuchAlgorithmException e) - // { - // log.error("error getting Signature object", e); - // } - // - // return s; - // } + try { + AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm)); + + if (entry == null) { + log.info("DNSSEC algorithm " + algorithm + " not recognized."); + + return null; + } + + // TODO: should we cache the instance? + s = Signature.getInstance(entry.jcaName); + } catch (NoSuchAlgorithmException e) { + log.error("error getting Signature object", e); + } + + return s; + } + + private static class AlgEntry { + public String jcaName; + public boolean isDSA; + public int dnssecAlg; + + public AlgEntry(String name, int dnssecAlg, boolean isDSA) { + jcaName = name; + this.dnssecAlg = dnssecAlg; + this.isDSA = isDSA; + } + } + + // TODO: enable private algorithm support in dnsjava. + // Right now, this cannot be used because the DNSKEYRecord object doesn't + // give us + // the private key name. + // private Signature getSignature(Name private_alg) + // { + // Signature s = null; + // + // try + // { + // String alg_id = (String) mAlgorithmMap.get(private_alg); + // if (alg_id == null) + // { + // log.debug("DNSSEC private algorithm '" + private_alg + // + "' not recognized."); + // return null; + // } + // + // s = Signature.getInstance(alg_id); + // } + // catch (NoSuchAlgorithmException e) + // { + // log.error("error getting Signature object", e); + // } + // + // return s; + // } } diff --git a/src/com/versign/tat/dnssec/NSEC3ValUtils.java b/src/com/versign/tat/dnssec/NSEC3ValUtils.java index a4316dd..9eb6698 100644 --- a/src/com/versign/tat/dnssec/NSEC3ValUtils.java +++ b/src/com/versign/tat/dnssec/NSEC3ValUtils.java @@ -1,113 +1,82 @@ -/* - * $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. - * - */ +/***************************** -*- Java -*- ********************************\ + * * + * Copyright (c) 2009 VeriSign, Inc. All rights reserved. * + * * + * This software is provided solely in connection with the terms of the * + * license agreement. Any other use without the prior express written * + * permission of VeriSign is completely prohibited. The software and * + * documentation are "Commercial Items", as that term is defined in 48 * + * C.F.R. section 2.101, consisting of "Commercial Computer Software" and * + * "Commercial Computer Software Documentation" as such terms are defined * + * in 48 C.F.R. section 252.227-7014(a)(5) and 48 C.F.R. section * + * 252.227-7014(a)(1), and used in 48 C.F.R. section 12.212 and 48 C.F.R. * + * section 227.7202, as applicable. Pursuant to the above and other * + * relevant sections of the Code of Federal Regulations, as applicable, * + * VeriSign's publications, commercial computer software, and commercial * + * computer software documentation are distributed and licensed to United * + * States Government end users with only those rights as granted to all * + * other end users, according to the terms and conditions contained in the * + * license agreement(s) that accompany the products and software * + * documentation. * + * * +\***************************************************************************/ -package com.versign.tat.dnssec; +package com.verisign.tat.dnssec; -import java.security.NoSuchAlgorithmException; -import java.util.*; +import com.verisign.tat.dnssec.SignUtils.ByteArrayComparator; + +import org.apache.log4j.Logger; import org.xbill.DNS.*; import org.xbill.DNS.utils.base32; -import com.versign.tat.dnssec.SignUtils.ByteArrayComparator; +import java.security.NoSuchAlgorithmException; + +import java.util.*; + 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. - - 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 encapsulate 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; - } - } + private static Name asterisk_label = Name.fromConstantString("*"); + private static Logger st_log = Logger.getLogger(NSEC3ValUtils.class); + private static final base32 b32 = new base32(base32.Alphabet.BASE32HEX, + false, false); public static boolean supportsHashAlgorithm(int alg) { - if (alg == NSEC3Record.SHA1_DIGEST_ID) return true; + if (alg == NSEC3Record.SHA1_DIGEST_ID) { + return true; + } + return false; } public static void stripUnknownAlgNSEC3s(List nsec3s) { - if (nsec3s == null) return; + if (nsec3s == null) { + return; + } + for (ListIterator i = nsec3s.listIterator(); i.hasNext();) { NSEC3Record nsec3 = i.next(); + if (!supportsHashAlgorithm(nsec3.getHashAlgorithm())) { i.remove(); } } } + public static boolean isOptOut(NSEC3Record nsec3) { + return (nsec3.getFlags() & NSEC3Record.Flags.OPT_OUT) == NSEC3Record.Flags.OPT_OUT; + } + /** * 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) @@ -115,32 +84,35 @@ public class NSEC3ValUtils { * the nsec3s list was empty. */ public static NSEC3Parameters nsec3Parameters(List nsec3s) { - if (nsec3s == null || nsec3s.size() == 0) return null; + if ((nsec3s == null) || (nsec3s.size() == 0)) { + return null; + } - NSEC3Parameters params = new NSEC3Parameters( - (NSEC3Record) nsec3s.get(0)); - ByteArrayComparator bac = new ByteArrayComparator(); + NSEC3Parameters params = new NSEC3Parameters((NSEC3Record) nsec3s.get( + 0)); + ByteArrayComparator bac = new ByteArrayComparator(); for (NSEC3Record nsec3 : nsec3s) { - if (!params.match(nsec3, bac)) return null; + if (!params.match(nsec3, bac)) { + return null; + } } return params; } - /** * 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) { + private static Name hashName(byte [] hash, Name zonename) { try { - return new Name(base32.toString(hash).toLowerCase(), zonename); + return new Name(b32.toString(hash).toLowerCase(), zonename); } catch (TextParseException e) { // Note, this should never happen. return null; @@ -149,26 +121,36 @@ public class NSEC3ValUtils { /** * 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) { + private static byte [] hash(Name name, NSEC3Parameters params) { try { - return NSEC3Record.hash(name, params.alg, params.iterations, - params.salt); + return params.hash(name); } catch (NoSuchAlgorithmException e) { - // st_log.debug("Did not recognize hash algorithm: " + params.alg); + st_log.warn("Did not recognize hash algorithm: " + params.alg); + + return null; + } + } + + private static byte[] hash(Name name, NSEC3Record nsec3) { + try { + return nsec3.hashName(name); + } catch (NoSuchAlgorithmException e) { + st_log.warn("Did not recognize hash algorithm: " + nsec3.getHashAlgorithm()); + 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. @@ -176,6 +158,7 @@ public class NSEC3ValUtils { private static Name ceWildcard(Name closestEncloser) { try { Name wc = Name.concatenate(asterisk_label, closestEncloser); + return wc; } catch (NameTooLongException e) { return null; @@ -186,7 +169,7 @@ public class NSEC3ValUtils { * 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 @@ -195,12 +178,13 @@ public class NSEC3ValUtils { */ 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 @@ -212,20 +196,25 @@ public class NSEC3ValUtils { * @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) { + private static NSEC3Record findMatchingNSEC3(byte [] hash, Name zonename, + List nsec3s, NSEC3Parameters params, + ByteArrayComparator bac) { Name n = hashName(hash, zonename); for (NSEC3Record nsec3 : nsec3s) { // Skip nsec3 records that are using different parameters. - if (!params.match(nsec3, bac)) continue; - if (n.equals(nsec3.getName())) return nsec3; + if (!params.match(nsec3, bac)) { + continue; + } + + if (n.equals(nsec3.getName())) { + return nsec3; + } } + return null; } @@ -233,7 +222,7 @@ public class NSEC3ValUtils { * 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 @@ -242,20 +231,23 @@ 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 = nsec3.getOwner(); - byte[] next = nsec3.getNext(); + private static boolean nsec3Covers(NSEC3Record nsec3, byte [] hash, + ByteArrayComparator bac) { + byte [] owner = hash(nsec3.getName(), nsec3); + 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) + 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)) + 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; @@ -264,7 +256,7 @@ public class NSEC3ValUtils { /** * Given a pre-hashed name, find a covering NSEC3 from among a list of * NSEC3s. - * + * * @param hash * The hash to consider. * @param zonename @@ -274,18 +266,22 @@ public class NSEC3ValUtils { * @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) { + private static NSEC3Record findCoveringNSEC3(byte [] hash, Name zonename, + List nsec3s, NSEC3Parameters params, + ByteArrayComparator bac) { ByteArrayComparator comparator = new ByteArrayComparator(); for (NSEC3Record nsec3 : nsec3s) { - if (!params.match(nsec3, bac)) continue; - if (nsec3Covers(nsec3, hash, comparator)) return nsec3; + if (!params.match(nsec3, bac)) { + continue; + } + + if (nsec3Covers(nsec3, hash, comparator)) { + return nsec3; + } } return null; @@ -295,7 +291,7 @@ public class NSEC3ValUtils { * 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 @@ -306,27 +302,29 @@ public class NSEC3ValUtils { * 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; + 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. + // 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); + nsec3 = findMatchingNSEC3(hash(n, params), zonename, + nsec3s, params, bac); + + if (nsec3 != null) { + return new CEResponse(n, nsec3); + } + n = new Name(n, 1); } @@ -335,7 +333,7 @@ public class NSEC3ValUtils { /** * Given a List of nsec3 RRs, find and prove the closest encloser to qname. - * + * * @param qname * The qname in question. * @param zonename @@ -354,24 +352,25 @@ 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) { CEResponse candidate = findClosestEncloser(qname, zonename, nsec3s, - params, bac); + params, bac); if (candidate == null) { - // st_log.debug("proveClosestEncloser: 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!"); + 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; @@ -381,26 +380,31 @@ 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)) { + 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!"); + 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); + Name nextClosest = nextClosest(qname, candidate.closestEncloser); + + byte [] nc_hash = hash(nextClosest, params); + candidate.nc_nsec3 = findCoveringNSEC3(nc_hash, zonename, nsec3s, + params, bac); - 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"); + st_log.debug("Could not find proof that the " + + "closest encloser was the closest encloser"); + return null; } @@ -409,37 +413,64 @@ public class NSEC3ValUtils { 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; + 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; } @SuppressWarnings("unchecked") private static boolean validIterations(NSEC3Parameters nsec3params, - RRset dnskey_rrset, - DnsSecVerifier verifier) { + 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; + 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; + if (nsec3params.iterations > max_iterations) { + return false; + } return true; } @@ -448,7 +479,7 @@ public class NSEC3ValUtils { * 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. @@ -460,10 +491,12 @@ public class NSEC3ValUtils { * @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) { + RRset dnskey_rrset, DnsSecVerifier verifier) { NSEC3Parameters params = nsec3Parameters(nsec3s); - if (params == null) return false; + + if (params == null) { + return false; + } return !validIterations(params, dnskey_rrset, verifier); } @@ -473,7 +506,7 @@ public class NSEC3ValUtils { * 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 @@ -487,13 +520,17 @@ public class NSEC3ValUtils { * ignored. */ public static boolean proveNameError(List nsec3s, Name qname, - Name zonename) { - if (nsec3s == null || nsec3s.size() == 0) return false; + 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)."); + st_log.debug("Could not find a single set of " + + "NSEC3 parameters (multiple parameters present)."); + return false; } @@ -502,50 +539,52 @@ 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); if (ce == null) { - // st_log.debug("proveNameError: failed to prove a closest encloser."); + 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); + 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."); + st_log.debug("proveNameError: could not prove that the " + + "applicable wildcard did not exist."); + return false; } return true; } - /** * 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 @@ -557,31 +596,41 @@ public class NSEC3ValUtils { * @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)"); + int qtype, Name zonename) { + if ((nsec3s == null) || (nsec3s.size() == 0)) { return false; } - ByteArrayComparator bac = new ByteArrayComparator(); - NSEC3Record nsec3 = findMatchingNSEC3(hash(qname, nsec3params), - zonename, nsec3s, nsec3params, - bac); + 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!"); + 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!"); + st_log.debug("proveNodata: Matching NSEC3 proved " + + "that a CNAME existed!"); + return false; } + return true; } @@ -589,13 +638,14 @@ 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); // 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."); + st_log.debug("proveNodata: did not match qname, " + + "nor found a proven closest encloser."); + return false; } @@ -604,28 +654,31 @@ public class NSEC3ValUtils { // Case 4: Name wc = ceWildcard(ce.closestEncloser); nsec3 = findMatchingNSEC3(hash(wc, nsec3params), zonename, nsec3s, - nsec3params, bac); + nsec3params, bac); if (nsec3 != null) { if (nsec3.hasType(qtype)) { - // st_log.debug("proveNodata: matching wildcard had 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."); + 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."); + if (!isOptOut(ce.nc_nsec3)) { + st_log.debug("proveNodata: covering NSEC3 was not " + + "opt-in in an opt-in DS NOERROR/NODATA case."); + return false; } @@ -635,7 +688,7 @@ public class NSEC3ValUtils { /** * Prove that a positive wildcard match was appropriate (no direct match * RRset). - * + * * @param nsec3s * The NSEC3 records to work with. * @param qname @@ -647,13 +700,21 @@ public class NSEC3ValUtils { * @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; + 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)."); + st_log.debug( + "couldn't find a single set of NSEC3 parameters (multiple parameters present)."); + return false; } @@ -668,15 +729,14 @@ public class NSEC3ValUtils { // 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); + 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 - // + ")"); + 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; } @@ -685,16 +745,16 @@ public class NSEC3ValUtils { /** * 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, @@ -703,34 +763,39 @@ public class NSEC3ValUtils { * work out. */ public static byte 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)."); + 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); + 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. + // 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; + if (!nsec3.hasType(Type.NS)) { + return SecurityStatus.INDETERMINATE; + } // Otherwise, this proves no DS. return SecurityStatus.SECURE; @@ -738,7 +803,8 @@ public class NSEC3ValUtils { // Otherwise, we are probably in the opt-in case. CEResponse ce = proveClosestEncloser(qname, zonename, nsec3s, - nsec3params, bac, true); + nsec3params, bac, true); + if (ce == null) { return SecurityStatus.BOGUS; } @@ -746,11 +812,73 @@ public class NSEC3ValUtils { // 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()) { + if (isOptOut(ce.nc_nsec3)) { return SecurityStatus.SECURE; } return SecurityStatus.BOGUS; } + /** + * This is a class to encapsulate a unique set of NSEC3 parameters: + * algorithm, iterations, and salt. + */ + private static class NSEC3Parameters { + public int alg; + public byte [] salt; + public int iterations; + private NSEC3PARAMRecord nsec3paramrec; + + public NSEC3Parameters(NSEC3Record r) { + alg = r.getHashAlgorithm(); + salt = r.getSalt(); + iterations = r.getIterations(); + + nsec3paramrec = new NSEC3PARAMRecord(Name.root, DClass.IN, 0, + alg, 0, iterations, salt); + } + + 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 (salt == null) { + return true; + } + + if (bac == null) { + bac = new ByteArrayComparator(); + } + + return bac.compare(r.getSalt(), salt) == 0; + } + + public byte[] hash(Name name) throws NoSuchAlgorithmException { + return nsec3paramrec.hashName(name); + } + } + + /** + * This is just a simple class to encapsulate 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; + } + } } diff --git a/src/com/versign/tat/dnssec/SMessage.java b/src/com/versign/tat/dnssec/SMessage.java index aca257b..d1e9a98 100644 --- a/src/com/versign/tat/dnssec/SMessage.java +++ b/src/com/versign/tat/dnssec/SMessage.java @@ -1,56 +1,49 @@ -/* - * $Id$ - * - * 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. - * - */ +/***************************** -*- Java -*- ********************************\ + * * + * Copyright (c) 2009 VeriSign, Inc. All rights reserved. * + * * + * This software is provided solely in connection with the terms of the * + * license agreement. Any other use without the prior express written * + * permission of VeriSign is completely prohibited. The software and * + * documentation are "Commercial Items", as that term is defined in 48 * + * C.F.R. section 2.101, consisting of "Commercial Computer Software" and * + * "Commercial Computer Software Documentation" as such terms are defined * + * in 48 C.F.R. section 252.227-7014(a)(5) and 48 C.F.R. section * + * 252.227-7014(a)(1), and used in 48 C.F.R. section 12.212 and 48 C.F.R. * + * section 227.7202, as applicable. Pursuant to the above and other * + * relevant sections of the Code of Federal Regulations, as applicable, * + * VeriSign's publications, commercial computer software, and commercial * + * computer software documentation are distributed and licensed to United * + * States Government end users with only those rights as granted to all * + * other end users, according to the terms and conditions contained in the * + * license agreement(s) that accompany the products and software * + * documentation. * + * * +\***************************************************************************/ -package com.versign.tat.dnssec; +package com.verisign.tat.dnssec; + +import org.xbill.DNS.*; import java.util.*; -import org.xbill.DNS.*; /** * This class represents a DNS message with resolver/validator state. */ public class SMessage { - private Header mHeader; - - private Record mQuestion; - private OPTRecord mOPTRecord; - private List[] mSection; - private SecurityStatus mSecurityStatus; - - private static SRRset[] empty_srrset_array = new SRRset[0]; + private static SRRset [] empty_srrset_array = new SRRset[0]; + private Header mHeader; + private Record mQuestion; + private OPTRecord mOPTRecord; + private List [] mSection; + private SecurityStatus mSecurityStatus; @SuppressWarnings("unchecked") public SMessage(Header h) { - mSection = (List[]) new List[3]; - mHeader = h; - mSecurityStatus = new SecurityStatus(); + mSection = (List []) new List[3]; + mHeader = h; + mSecurityStatus = new SecurityStatus(); } public SMessage(int id) { @@ -63,11 +56,11 @@ public class SMessage { public SMessage(Message m) { this(m.getHeader()); - mQuestion = m.getQuestion(); - mOPTRecord = m.getOPT(); + mQuestion = m.getQuestion(); + mOPTRecord = m.getOPT(); for (int i = Section.ANSWER; i <= Section.ADDITIONAL; i++) { - RRset[] rrsets = m.getSectionRRsets(i); + RRset [] rrsets = m.getSectionRRsets(i); for (int j = 0; j < rrsets.length; j++) { addRRset(rrsets[j], i); @@ -112,8 +105,9 @@ public class SMessage { } public List getSectionList(int section) { - if (section <= Section.QUESTION || section > Section.ADDITIONAL) + if ((section <= Section.QUESTION) || (section > Section.ADDITIONAL)) { throw new IllegalArgumentException("Invalid section."); + } if (mSection[section - 1] == null) { mSection[section - 1] = new LinkedList(); @@ -123,11 +117,13 @@ public class SMessage { } public void addRRset(SRRset srrset, int section) { - if (section <= Section.QUESTION || section > Section.ADDITIONAL) + if ((section <= Section.QUESTION) || (section > Section.ADDITIONAL)) { throw new IllegalArgumentException("Invalid section"); + } if (srrset.getType() == Type.OPT) { mOPTRecord = (OPTRecord) srrset.first(); + return; } @@ -138,6 +134,7 @@ public class SMessage { public void addRRset(RRset rrset, int section) { if (rrset instanceof SRRset) { addRRset((SRRset) rrset, section); + return; } @@ -146,48 +143,59 @@ public class SMessage { } public void prependRRsets(List rrsets, int section) { - if (section <= Section.QUESTION || section > Section.ADDITIONAL) + if ((section <= Section.QUESTION) || (section > Section.ADDITIONAL)) { throw new IllegalArgumentException("Invalid section"); + } List sectionList = getSectionList(section); sectionList.addAll(0, rrsets); } - public SRRset[] getSectionRRsets(int section) { + public SRRset [] getSectionRRsets(int section) { List slist = getSectionList(section); - return (SRRset[]) slist.toArray(empty_srrset_array); + return (SRRset []) slist.toArray(empty_srrset_array); } - public SRRset[] getSectionRRsets(int section, int qtype) { + public SRRset [] getSectionRRsets(int section, int qtype) { List slist = getSectionList(section); - if (slist.size() == 0) return new SRRset[0]; - - ArrayList result = new ArrayList(slist.size()); - for (SRRset rrset : slist) { - if (rrset.getType() == qtype) result.add(rrset); + if (slist.size() == 0) { + return new SRRset[0]; } - return (SRRset[]) result.toArray(empty_srrset_array); + ArrayList result = new ArrayList(slist.size()); + + for (SRRset rrset : slist) { + if (rrset.getType() == qtype) { + result.add(rrset); + } + } + + return (SRRset []) result.toArray(empty_srrset_array); } public void deleteRRset(SRRset rrset, int section) { List slist = getSectionList(section); - if (slist.size() == 0) return; + if (slist.size() == 0) { + return; + } slist.remove(rrset); } public void clear(int section) { - if (section < Section.QUESTION || section > Section.ADDITIONAL) + if ((section < Section.QUESTION) || (section > Section.ADDITIONAL)) { throw new IllegalArgumentException("Invalid section."); + } if (section == Section.QUESTION) { mQuestion = null; + return; } + if (section == Section.ADDITIONAL) { mOPTRecord = null; } @@ -219,7 +227,10 @@ public class SMessage { } public void setSecurityStatus(SecurityStatus s) { - if (s == null) return; + if (s == null) { + return; + } + mSecurityStatus = s; } @@ -235,6 +246,7 @@ public class SMessage { Header h = m.getHeader(); h.setOpcode(mHeader.getOpcode()); h.setRcode(mHeader.getRcode()); + for (int i = 0; i < 16; i++) { if (Flags.isFlag(i)) { if (mHeader.getFlag(i)) { @@ -247,18 +259,19 @@ public class SMessage { // Add all the records. -- this will set the counts correctly in the // message header. - if (mQuestion != null) { m.addRecord(mQuestion, Section.QUESTION); } for (int sec = Section.ANSWER; sec <= Section.ADDITIONAL; sec++) { List slist = getSectionList(sec); + for (SRRset rrset : slist) { - for (Iterator j = rrset.rrs(); j.hasNext(); ) { + for (Iterator j = rrset.rrs(); j.hasNext();) { m.addRecord(j.next(), sec); } - for (Iterator j = rrset.sigs(); j.hasNext(); ) { + + for (Iterator j = rrset.sigs(); j.hasNext();) { m.addRecord(j.next(), sec); } } @@ -273,13 +286,21 @@ public class SMessage { public int getCount(int section) { if (section == Section.QUESTION) { - return mQuestion == null ? 0 : 1; + return (mQuestion == null) ? 0 : 1; } + List sectionList = getSectionList(section); - if (sectionList == null) return 0; - if (sectionList.size() == 0) return 0; + + if (sectionList == null) { + return 0; + } + + if (sectionList.size() == 0) { + return 0; + } int count = 0; + for (SRRset sr : sectionList) { count += sr.totalSize(); } @@ -293,7 +314,7 @@ public class SMessage { /** * Find a specific (S)RRset in a given section. - * + * * @param name * the name of the RRset. * @param type @@ -302,18 +323,20 @@ public class SMessage { * the class of the RRset. * @param section * the section to look in (ANSWER -> ADDITIONAL) - * + * * @return The SRRset if found, null otherwise. */ public SRRset findRRset(Name name, int type, int dclass, int section) { - if (section <= Section.QUESTION || section > Section.ADDITIONAL) + if ((section <= Section.QUESTION) || (section > Section.ADDITIONAL)) { throw new IllegalArgumentException("Invalid section."); + } - SRRset[] rrsets = getSectionRRsets(section); + SRRset [] rrsets = getSectionRRsets(section); for (int i = 0; i < rrsets.length; i++) { - if (rrsets[i].getName().equals(name) && rrsets[i].getType() == type - && rrsets[i].getDClass() == dclass) { + if (rrsets[i].getName().equals(name) && + (rrsets[i].getType() == type) && + (rrsets[i].getDClass() == dclass)) { return rrsets[i]; } } @@ -324,36 +347,36 @@ public class SMessage { /** * Find an "answer" RRset. This will look for RRsets in the ANSWER section * that match the , taking into consideration CNAMEs. - * + * * @param qname * The starting search name. * @param qtype * The search type. * @param qclass * The search class. - * + * * @return a SRRset matching the query. This SRRset may have a different * name from qname, due to following a CNAME chain. */ public SRRset findAnswerRRset(Name qname, int qtype, int qclass) { - SRRset[] srrsets = getSectionRRsets(Section.ANSWER); + SRRset [] srrsets = getSectionRRsets(Section.ANSWER); for (int i = 0; i < srrsets.length; i++) { - if (srrsets[i].getName().equals(qname) - && srrsets[i].getType() == Type.CNAME) { + if (srrsets[i].getName().equals(qname) && + (srrsets[i].getType() == Type.CNAME)) { CNAMERecord cname = (CNAMERecord) srrsets[i].first(); qname = cname.getTarget(); + continue; } - if (srrsets[i].getName().equals(qname) - && srrsets[i].getType() == qtype - && srrsets[i].getDClass() == qclass) { + if (srrsets[i].getName().equals(qname) && + (srrsets[i].getType() == qtype) && + (srrsets[i].getDClass() == qclass)) { return srrsets[i]; } } return null; } - -} \ No newline at end of file +} diff --git a/src/com/versign/tat/dnssec/SRRset.java b/src/com/versign/tat/dnssec/SRRset.java index 71880a0..ab1c66e 100644 --- a/src/com/versign/tat/dnssec/SRRset.java +++ b/src/com/versign/tat/dnssec/SRRset.java @@ -1,35 +1,32 @@ -/* - * Copyright (c) 2009 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. - * - */ +/***************************** -*- Java -*- ********************************\ + * * + * Copyright (c) 2009 VeriSign, Inc. All rights reserved. * + * * + * This software is provided solely in connection with the terms of the * + * license agreement. Any other use without the prior express written * + * permission of VeriSign is completely prohibited. The software and * + * documentation are "Commercial Items", as that term is defined in 48 * + * C.F.R. section 2.101, consisting of "Commercial Computer Software" and * + * "Commercial Computer Software Documentation" as such terms are defined * + * in 48 C.F.R. section 252.227-7014(a)(5) and 48 C.F.R. section * + * 252.227-7014(a)(1), and used in 48 C.F.R. section 12.212 and 48 C.F.R. * + * section 227.7202, as applicable. Pursuant to the above and other * + * relevant sections of the Code of Federal Regulations, as applicable, * + * VeriSign's publications, commercial computer software, and commercial * + * computer software documentation are distributed and licensed to United * + * States Government end users with only those rights as granted to all * + * other end users, according to the terms and conditions contained in the * + * license agreement(s) that accompany the products and software * + * documentation. * + * * +\***************************************************************************/ -package com.versign.tat.dnssec; +package com.verisign.tat.dnssec; + +import org.xbill.DNS.*; import java.util.*; -import org.xbill.DNS.*; /** * A version of the RRset class overrides the standard security status. @@ -43,13 +40,11 @@ public class SRRset extends RRset { mSecurityStatus = new SecurityStatus(); } - /** * Create a new SRRset from an existing RRset. This SRRset will contain that * same internal Record objects as the original RRset. */ @SuppressWarnings("unchecked") - // org.xbill.DNS.RRset isn't typesafe-aware. public SRRset(RRset r) { this(); @@ -86,19 +81,24 @@ public class SRRset extends RRset { mSecurityStatus.setStatus(status); } + @SuppressWarnings("unchecked") public Iterator rrs() { - return (Iterator) rrs(); + return (Iterator) super.rrs(); } - + + @SuppressWarnings("unchecked") public Iterator sigs() { - return (Iterator) sigs(); + return (Iterator) super.sigs(); } - + public int totalSize() { int num_sigs = 0; + for (Iterator i = sigs(); i.hasNext();) { num_sigs++; + i.next(); } + return size() + num_sigs; } @@ -113,6 +113,7 @@ public class SRRset extends RRset { for (Iterator i = sigs(); i.hasNext();) { return i.next(); } + return null; } @@ -121,7 +122,10 @@ public class SRRset extends RRset { * (i.e., RRSIG SRRsets return false) */ public boolean isSigned() { - if (getType() == Type.RRSIG) return false; + if (getType() == Type.RRSIG) { + return false; + } + return firstSig() != null; } @@ -130,7 +134,11 @@ public class SRRset extends RRset { */ public Name getSignerName() { RRSIGRecord sig = (RRSIGRecord) firstSig(); - if (sig == null) return null; + + if (sig == null) { + return null; + } + return sig.getSigner(); } } diff --git a/src/com/versign/tat/dnssec/SecurityStatus.java b/src/com/versign/tat/dnssec/SecurityStatus.java index ee59058..08617b2 100644 --- a/src/com/versign/tat/dnssec/SecurityStatus.java +++ b/src/com/versign/tat/dnssec/SecurityStatus.java @@ -1,112 +1,112 @@ -/* - * $Id$ - * - * 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. - * - */ +/***************************** -*- Java -*- ********************************\ + * * + * Copyright (c) 2009 VeriSign, Inc. All rights reserved. * + * * + * This software is provided solely in connection with the terms of the * + * license agreement. Any other use without the prior express written * + * permission of VeriSign is completely prohibited. The software and * + * documentation are "Commercial Items", as that term is defined in 48 * + * C.F.R. section 2.101, consisting of "Commercial Computer Software" and * + * "Commercial Computer Software Documentation" as such terms are defined * + * in 48 C.F.R. section 252.227-7014(a)(5) and 48 C.F.R. section * + * 252.227-7014(a)(1), and used in 48 C.F.R. section 12.212 and 48 C.F.R. * + * section 227.7202, as applicable. Pursuant to the above and other * + * relevant sections of the Code of Federal Regulations, as applicable, * + * VeriSign's publications, commercial computer software, and commercial * + * computer software documentation are distributed and licensed to United * + * States Government end users with only those rights as granted to all * + * other end users, according to the terms and conditions contained in the * + * license agreement(s) that accompany the products and software * + * documentation. * + * * +\***************************************************************************/ + +package com.verisign.tat.dnssec; -package com.versign.tat.dnssec; /** * Codes for DNSSEC security statuses. - * + * * @author davidb */ -public class SecurityStatus -{ +public class SecurityStatus { + public static final byte INVALID = -1; - /** - * UNCHECKED means that object has yet to be validated. - */ - public static final byte UNCHECKED = 0; - /** - * BOGUS means that the object (RRset or message) failed to validate - * (according to local policy), but should have validated. - */ - public static final byte BOGUS = 1; - /** - * BAD is a synonym for BOGUS. - */ - public static final byte BAD = BOGUS; - /** - * INDTERMINATE means that the object is insecure, but not authoritatively - * so. Generally this means that the RRset is not below a configured trust - * anchor. - */ - public static final byte INDETERMINATE = 2; - /** - * INSECURE means that the object is authoritatively known to be insecure. - * Generally this means that this RRset is below a trust anchor, but also - * below a verified, insecure delegation. - */ - public static final byte INSECURE = 3; - /** - * SECURE means that the object (RRset or message) validated according to - * local policy. - */ - public static final byte SECURE = 4; + /** + * UNCHECKED means that object has yet to be validated. + */ + public static final byte UNCHECKED = 0; - private byte status; + /** + * BOGUS means that the object (RRset or message) failed to validate + * (according to local policy), but should have validated. + */ + public static final byte BOGUS = 1; - public static String string(int status) - { - switch (status) - { - case BOGUS : - return "Bogus"; - case SECURE : - return "Secure"; - case INSECURE : - return "Insecure"; - case INDETERMINATE : - return "Indeterminate"; - case UNCHECKED : - return "Unchecked"; - default : - return "UNKNOWN"; + /** + * BAD is a synonym for BOGUS. + */ + public static final byte BAD = BOGUS; + + /** + * INDTERMINATE means that the object is insecure, but not authoritatively + * so. Generally this means that the RRset is not below a configured trust + * anchor. + */ + public static final byte INDETERMINATE = 2; + + /** + * INSECURE means that the object is authoritatively known to be insecure. + * Generally this means that this RRset is below a trust anchor, but also + * below a verified, insecure delegation. + */ + public static final byte INSECURE = 3; + + /** + * SECURE means that the object (RRset or message) validated according to + * local policy. + */ + public static final byte SECURE = 4; + private byte status; + + public SecurityStatus() { + status = UNCHECKED; } - } - public SecurityStatus() - { - status = UNCHECKED; - } - - public SecurityStatus(byte status) - { - setStatus(status); - } - - public byte getStatus() - { - return status; - } + public SecurityStatus(byte status) { + setStatus(status); + } - public void setStatus(byte status) - { - this.status = status; - } + public static String string(int status) { + switch (status) { + case INVALID: + return "Invalid"; + case BOGUS: + return "Bogus"; + + case SECURE: + return "Secure"; + + case INSECURE: + return "Insecure"; + + case INDETERMINATE: + return "Indeterminate"; + + case UNCHECKED: + return "Unchecked"; + + default: + return "UNKNOWN"; + } + } + + public byte getStatus() { + return status; + } + + public void setStatus(byte status) { + this.status = status; + } } diff --git a/src/com/versign/tat/dnssec/SignUtils.java b/src/com/versign/tat/dnssec/SignUtils.java index 8316407..eb2cfdd 100644 --- a/src/com/versign/tat/dnssec/SignUtils.java +++ b/src/com/versign/tat/dnssec/SignUtils.java @@ -1,45 +1,29 @@ -/* - * $Id$ - * - * Copyright (c) 2005 VeriSign, Inc. 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. - * - */ +/***************************** -*- Java -*- ********************************\ + * * + * Copyright (c) 2009 VeriSign, Inc. All rights reserved. * + * * + * This software is provided solely in connection with the terms of the * + * license agreement. Any other use without the prior express written * + * permission of VeriSign is completely prohibited. The software and * + * documentation are "Commercial Items", as that term is defined in 48 * + * C.F.R. section 2.101, consisting of "Commercial Computer Software" and * + * "Commercial Computer Software Documentation" as such terms are defined * + * in 48 C.F.R. section 252.227-7014(a)(5) and 48 C.F.R. section * + * 252.227-7014(a)(1), and used in 48 C.F.R. section 12.212 and 48 C.F.R. * + * section 227.7202, as applicable. Pursuant to the above and other * + * relevant sections of the Code of Federal Regulations, as applicable, * + * VeriSign's publications, commercial computer software, and commercial * + * computer software documentation are distributed and licensed to United * + * States Government end users with only those rights as granted to all * + * other end users, according to the terms and conditions contained in the * + * license agreement(s) that accompany the products and software * + * documentation. * + * * +\***************************************************************************/ -package com.versign.tat.dnssec; +package com.verisign.tat.dnssec; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.security.SignatureException; -import java.security.interfaces.DSAParams; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.Iterator; +import org.apache.log4j.Logger; import org.xbill.DNS.DNSKEYRecord; import org.xbill.DNS.DNSOutput; @@ -47,60 +31,40 @@ import org.xbill.DNS.Name; import org.xbill.DNS.RRSIGRecord; import org.xbill.DNS.RRset; import org.xbill.DNS.Record; +import org.xbill.DNS.utils.base64; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import java.security.SignatureException; +import java.security.interfaces.DSAParams; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.Iterator; + /** * This class contains a bunch of utility methods that are generally useful in * signing and verifying rrsets. */ - public class SignUtils { - - /** - * This class implements a basic comparator for byte arrays. It is primarily - * useful for comparing RDATA portions of DNS records in doing DNSSEC - * canonical ordering. - */ - public static class ByteArrayComparator implements Comparator { - private int mOffset = 0; - private boolean mDebug = false; - - public ByteArrayComparator() { - } - - public ByteArrayComparator(int offset, boolean debug) { - mOffset = offset; - mDebug = debug; - } - - public int compare(byte[] b1, byte[] b2) throws ClassCastException { - for (int i = mOffset; i < b1.length && i < b2.length; i++) { - if (b1[i] != b2[i]) { - if (mDebug) { - System.out.println("offset " + i + " differs (this is " - + (i - mOffset) - + " bytes in from our offset.)"); - } - return (b1[i] & 0xFF) - (b2[i] & 0xFF); - } - } - - return b1.length - b2.length; - } - } - // private static final int DSA_SIGNATURE_LENGTH = 20; private static final int ASN1_INT = 0x02; private static final int ASN1_SEQ = 0x30; - public static final int RR_NORMAL = 0; public static final int RR_DELEGATION = 1; public static final int RR_GLUE = 2; public static final int RR_INVALID = 3; + private static Logger log = Logger.getLogger(SignUtils.class); /** * Generate from some basic information a prototype SIG RR containing * everything but the actual signature itself. - * + * * @param rrset * the RRset being signed. * @param signer @@ -118,17 +82,16 @@ public class SignUtils { * @return a prototype signature based on the RRset and key information. */ public static RRSIGRecord generatePreRRSIG(RRset rrset, Name signer, - int alg, int keyid, Date start, - Date expire, long sig_ttl) { + int alg, int keyid, Date start, Date expire, long sig_ttl) { return new RRSIGRecord(rrset.getName(), rrset.getDClass(), sig_ttl, - rrset.getType(), alg, rrset.getTTL(), expire, start, keyid, - signer, null); + rrset.getType(), alg, rrset.getTTL(), expire, start, keyid, signer, + null); } /** * Generate from some basic information a prototype SIG RR containing * everything but the actual signature itself. - * + * * @param rrset * the RRset being signed. * @param key @@ -143,16 +106,15 @@ public class SignUtils { * @return a prototype signature based on the RRset and key information. */ public static RRSIGRecord generatePreRRSIG(RRset rrset, DNSKEYRecord key, - Date start, Date expire, - long sig_ttl) { + Date start, Date expire, long sig_ttl) { return generatePreRRSIG(rrset, key.getName(), key.getAlgorithm(), - key.getFootprint(), start, expire, sig_ttl); + key.getFootprint(), start, expire, sig_ttl); } /** * Generate from some basic information a prototype SIG RR containing * everything but the actual signature itself. - * + * * @param rec * the DNS record being signed (forming an entire RRset). * @param key @@ -166,29 +128,28 @@ public class SignUtils { * @return a prototype signature based on the Record and key information. */ public static RRSIGRecord generatePreRRSIG(Record rec, DNSKEYRecord key, - Date start, Date expire, - long sig_ttl) { + Date start, Date expire, long sig_ttl) { return new RRSIGRecord(rec.getName(), rec.getDClass(), sig_ttl, - rec.getType(), key.getAlgorithm(), rec.getTTL(), expire, start, - key.getFootprint(), key.getName(), null); + rec.getType(), key.getAlgorithm(), rec.getTTL(), expire, start, + key.getFootprint(), key.getName(), null); } /** * Generate the binary image of the prototype SIG RR. - * + * * @param presig * the SIG RR prototype. * @return the RDATA portion of the prototype SIG record. This forms the * first part of the data to be signed. */ - private static byte[] generatePreSigRdata(RRSIGRecord presig) { + private static byte [] generatePreSigRdata(RRSIGRecord presig) { // Generate the binary image; DNSOutput image = new DNSOutput(); // precalculate some things - int start_time = (int) (presig.getTimeSigned().getTime() / 1000); - int expire_time = (int) (presig.getExpire().getTime() / 1000); - Name signer = presig.getSigner(); + int start_time = (int) (presig.getTimeSigned().getTime() / 1000); + int expire_time = (int) (presig.getExpire().getTime() / 1000); + Name signer = presig.getSigner(); // first write out the partial SIG record (this is the SIG RDATA // minus the actual signature. @@ -206,7 +167,7 @@ public class SignUtils { /** * Calculate the canonical wire line format of the RRset. - * + * * @param rrset * the RRset to convert. * @param ttl @@ -219,54 +180,63 @@ public class SignUtils { * part of data to be signed. */ @SuppressWarnings("unchecked") - public static byte[] generateCanonicalRRsetData(RRset rrset, long ttl, - int labels) { + public static byte [] generateCanonicalRRsetData(RRset rrset, long ttl, + int labels) { DNSOutput image = new DNSOutput(); - if (ttl == 0) ttl = rrset.getTTL(); + if (ttl == 0) { + ttl = rrset.getTTL(); + } + Name n = rrset.getName(); + if (labels == 0) { labels = n.labels(); } else { // correct for Name()'s conception of label count. labels++; } + boolean wildcardName = false; + if (n.labels() != labels) { - n = n.wild(n.labels() - labels); - wildcardName = true; - // log.trace("Detected wildcard expansion: " + rrset.getName() + - // " changed to " + n); + n = n.wild(n.labels() - labels); + wildcardName = true; + log.trace("Detected wildcard expansion: " + rrset.getName() + + " changed to " + n); } // now convert the wire format records in the RRset into a // list of byte arrays. - ArrayList canonical_rrs = new ArrayList(); + ArrayList canonical_rrs = new ArrayList(); + for (Iterator i = rrset.rrs(); i.hasNext();) { Record r = (Record) i.next(); - if (r.getTTL() != ttl || wildcardName) { + + if ((r.getTTL() != ttl) || wildcardName) { // If necessary, we need to create a new record with a new ttl // or ownername. // In the TTL case, this avoids changing the ttl in the // response. r = Record.newRecord(n, r.getType(), r.getDClass(), ttl, - r.rdataToWireCanonical()); + r.rdataToWireCanonical()); } - byte[] wire_fmt = r.toWireCanonical(); + + byte [] wire_fmt = r.toWireCanonical(); canonical_rrs.add(wire_fmt); } // put the records into the correct ordering. // Calculate the offset where the RDATA begins (we have to skip // past the length byte) - - int offset = rrset.getName().toWireCanonical().length + 10; - ByteArrayComparator bac = new ByteArrayComparator(offset, false); + int offset = rrset.getName().toWireCanonical().length + + 10; + ByteArrayComparator bac = new ByteArrayComparator(offset, false); Collections.sort(canonical_rrs, bac); - for (Iterator i = canonical_rrs.iterator(); i.hasNext();) { - byte[] wire_fmt_rec = i.next(); + for (Iterator i = canonical_rrs.iterator(); i.hasNext();) { + byte [] wire_fmt_rec = i.next(); image.writeByteArray(wire_fmt_rec); } @@ -276,18 +246,17 @@ public class SignUtils { /** * Given an RRset and the prototype signature, generate the canonical data * that is to be signed. - * + * * @param rrset * the RRset to be signed. * @param presig * a prototype SIG RR created using the same RRset. * @return a block of data ready to be signed. */ - public static byte[] generateSigData(RRset rrset, RRSIGRecord presig) - throws IOException { - byte[] rrset_data = generateCanonicalRRsetData(rrset, - presig.getOrigTTL(), - presig.getLabels()); + public static byte [] generateSigData(RRset rrset, RRSIGRecord presig) + throws IOException { + byte [] rrset_data = generateCanonicalRRsetData(rrset, + presig.getOrigTTL(), presig.getLabels()); return generateSigData(rrset_data, presig); } @@ -295,7 +264,7 @@ public class SignUtils { /** * Given an RRset and the prototype signature, generate the canonical data * that is to be signed. - * + * * @param rrset_data * the RRset converted into canonical wire line format (as per * the canonicalization rules in RFC 2535). @@ -304,12 +273,12 @@ public class SignUtils { * rrset_data. * @return a block of data ready to be signed. */ - public static byte[] generateSigData(byte[] rrset_data, RRSIGRecord presig) - throws IOException { - byte[] sig_rdata = generatePreSigRdata(presig); + public static byte [] generateSigData(byte [] rrset_data, RRSIGRecord presig) + throws IOException { + byte [] sig_rdata = generatePreSigRdata(presig); - ByteArrayOutputStream image = new ByteArrayOutputStream( - sig_rdata.length + rrset_data.length); + ByteArrayOutputStream image = new ByteArrayOutputStream(sig_rdata.length + + rrset_data.length); image.write(sig_rdata); image.write(rrset_data); @@ -320,33 +289,33 @@ public class SignUtils { /** * Given the actual signature and the prototype signature, combine them and * return the fully formed RRSIGRecord. - * + * * @param signature * the cryptographic signature, in DNSSEC format. * @param presig * the prototype RRSIG RR to add the signature to. * @return the fully formed RRSIG RR. */ - public static RRSIGRecord generateRRSIG(byte[] signature, RRSIGRecord presig) { + public static RRSIGRecord generateRRSIG(byte [] signature, + RRSIGRecord presig) { return new RRSIGRecord(presig.getName(), presig.getDClass(), - presig.getTTL(), presig.getTypeCovered(), - presig.getAlgorithm(), presig.getOrigTTL(), presig.getExpire(), - presig.getTimeSigned(), presig.getFootprint(), - presig.getSigner(), signature); + presig.getTTL(), presig.getTypeCovered(), presig.getAlgorithm(), + presig.getOrigTTL(), presig.getExpire(), presig.getTimeSigned(), + presig.getFootprint(), presig.getSigner(), signature); } /** * Converts from a RFC 2536 formatted DSA signature to a JCE (ASN.1) * formatted signature. - * + * *

* ASN.1 format = ASN1_SEQ . seq_length . ASN1_INT . Rlength . R . ANS1_INT * . Slength . S *

- * + * * The integers R and S may have a leading null byte to force the integer * positive. - * + * * @param signature * the RFC 2536 formatted DSA signature. * @return The ASN.1 formatted DSA signature. @@ -354,43 +323,53 @@ public class SignUtils { * if there was something wrong with the RFC 2536 formatted * signature. */ - public static byte[] convertDSASignature(byte[] signature) - throws SignatureException { - if (signature.length != 41) + public static byte [] convertDSASignature(byte [] signature) + throws SignatureException { + if (signature.length != 41) { throw new SignatureException( - "RFC 2536 signature not expected length."); + "RFC 2536 signature not expected length."); + } byte r_pad = 0; byte s_pad = 0; // handle initial null byte padding. - if (signature[1] < 0) r_pad++; - if (signature[21] < 0) s_pad++; + if (signature[1] < 0) { + r_pad++; + } + + if (signature[21] < 0) { + s_pad++; + } // ASN.1 length = R length + S length + (2 + 2 + 2), where each 2 // is for a ASN.1 type-length byte pair of which there are three // (SEQ, INT, INT). - byte sig_length = (byte) (40 + r_pad + s_pad + 6); + byte sig_length = (byte) (40 + r_pad + s_pad + 6); - byte sig[] = new byte[sig_length]; - byte pos = 0; + byte [] sig = new byte[sig_length]; + byte pos = 0; - sig[pos++] = ASN1_SEQ; - sig[pos++] = (byte) (sig_length - 2); // all but the SEQ type+length. - sig[pos++] = ASN1_INT; - sig[pos++] = (byte) (20 + r_pad); + sig[pos++] = ASN1_SEQ; + sig[pos++] = (byte) (sig_length - 2); // all but the SEQ type+length. + sig[pos++] = ASN1_INT; + sig[pos++] = (byte) (20 + r_pad); // copy the value of R, leaving a null byte if necessary - if (r_pad == 1) sig[pos++] = 0; + if (r_pad == 1) { + sig[pos++] = 0; + } System.arraycopy(signature, 1, sig, pos, 20); pos += 20; - sig[pos++] = ASN1_INT; - sig[pos++] = (byte) (20 + s_pad); + sig[pos++] = ASN1_INT; + sig[pos++] = (byte) (20 + s_pad); // copy the value of S, leaving a null byte if necessary - if (s_pad == 1) sig[pos++] = 0; + if (s_pad == 1) { + sig[pos++] = 0; + } System.arraycopy(signature, 21, sig, pos, 20); @@ -400,15 +379,15 @@ public class SignUtils { /** * Converts from a JCE (ASN.1) formatted DSA signature to a RFC 2536 * compliant signature. - * + * *

* rfc2536 format = T . R . S *

- * + * * where T is a number between 0 and 8, which is based on the DSA key * length, and R & S are formatted to be exactly 20 bytes each (no leading * null bytes). - * + * * @param params * the DSA parameters associated with the DSA key used to * generate the signature. @@ -418,25 +397,25 @@ public class SignUtils { * @throws SignatureException * if something is wrong with the ASN.1 format. */ - public static byte[] convertDSASignature(DSAParams params, byte[] signature) - throws SignatureException { - if (signature[0] != ASN1_SEQ || signature[2] != ASN1_INT) { + public static byte [] convertDSASignature(DSAParams params, + byte [] signature) throws SignatureException { + if ((signature[0] != ASN1_SEQ) || (signature[2] != ASN1_INT)) { throw new SignatureException( - "Invalid ASN.1 signature format: expected SEQ, INT"); + "Invalid ASN.1 signature format: expected SEQ, INT"); } byte r_pad = (byte) (signature[3] - 20); if (signature[24 + r_pad] != ASN1_INT) { throw new SignatureException( - "Invalid ASN.1 signature format: expected SEQ, INT, INT"); + "Invalid ASN.1 signature format: expected SEQ, INT, INT"); } - // log.trace("(start) ASN.1 DSA Sig:\n" + base64.toString(signature)); + log.trace("(start) ASN.1 DSA Sig:\n" + base64.toString(signature)); - byte s_pad = (byte) (signature[25 + r_pad] - 20); + byte s_pad = (byte) (signature[25 + r_pad] - 20); - byte[] sig = new byte[41]; // all rfc2536 signatures are 41 bytes. + byte [] sig = new byte[41]; // all rfc2536 signatures are 41 bytes. // Calculate T: sig[0] = (byte) ((params.getP().bitLength() - 512) / 64); @@ -458,19 +437,50 @@ public class SignUtils { // S is shorter than 20 bytes, so right justify the number // (s_pad is negative here). Arrays.fill(sig, 21, 21 - s_pad, (byte) 0); - System.arraycopy(signature, 26 + r_pad, sig, 21 - s_pad, 20 + s_pad); + System.arraycopy(signature, 26 + r_pad, sig, 21 - s_pad, 20 + + s_pad); } - // if (r_pad < 0 || s_pad < 0) - // { - // log.trace("(finish ***) RFC 2536 DSA Sig:\n" + base64.toString(sig)); - // - // } - // else - // { - // log.trace("(finish) RFC 2536 DSA Sig:\n" + base64.toString(sig)); - // } + if ((r_pad < 0) || (s_pad < 0)) { + log.trace("(finish ***) RFC 2536 DSA Sig:\n" + + base64.toString(sig)); + } else { + log.trace("(finish) RFC 2536 DSA Sig:\n" + base64.toString(sig)); + } return sig; } + + /** + * This class implements a basic comparator for byte arrays. It is primarily + * useful for comparing RDATA portions of DNS records in doing DNSSEC + * canonical ordering. + */ + public static class ByteArrayComparator implements Comparator { + private int mOffset = 0; + private boolean mDebug = false; + + public ByteArrayComparator() {} + + public ByteArrayComparator(int offset, boolean debug) { + mOffset = offset; + mDebug = debug; + } + + public int compare(byte [] b1, byte [] b2) throws ClassCastException { + for (int i = mOffset; (i < b1.length) && (i < b2.length); i++) { + if (b1[i] != b2[i]) { + if (mDebug) { + System.out.println("offset " + i + + " differs (this is " + (i - mOffset) + + " bytes in from our offset.)"); + } + + return (b1[i] & 0xFF) - (b2[i] & 0xFF); + } + } + + return b1.length - b2.length; + } + } } diff --git a/src/com/versign/tat/dnssec/TrustAnchorStore.java b/src/com/versign/tat/dnssec/TrustAnchorStore.java index eac4553..a635f5c 100644 --- a/src/com/versign/tat/dnssec/TrustAnchorStore.java +++ b/src/com/versign/tat/dnssec/TrustAnchorStore.java @@ -1,37 +1,32 @@ -/* - * Copyright (c) 2009 VeriSign, Inc. 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. - * - */ +/***************************** -*- Java -*- ********************************\ + * * + * Copyright (c) 2009 VeriSign, Inc. All rights reserved. * + * * + * This software is provided solely in connection with the terms of the * + * license agreement. Any other use without the prior express written * + * permission of VeriSign is completely prohibited. The software and * + * documentation are "Commercial Items", as that term is defined in 48 * + * C.F.R. section 2.101, consisting of "Commercial Computer Software" and * + * "Commercial Computer Software Documentation" as such terms are defined * + * in 48 C.F.R. section 252.227-7014(a)(5) and 48 C.F.R. section * + * 252.227-7014(a)(1), and used in 48 C.F.R. section 12.212 and 48 C.F.R. * + * section 227.7202, as applicable. Pursuant to the above and other * + * relevant sections of the Code of Federal Regulations, as applicable, * + * VeriSign's publications, commercial computer software, and commercial * + * computer software documentation are distributed and licensed to United * + * States Government end users with only those rights as granted to all * + * other end users, according to the terms and conditions contained in the * + * license agreement(s) that accompany the products and software * + * documentation. * + * * +\***************************************************************************/ -package com.versign.tat.dnssec; +package com.verisign.tat.dnssec; -import java.util.HashMap; -import java.util.Map; +import org.xbill.DNS.*; + +import java.util.*; -import org.xbill.DNS.Name; /** * @@ -51,6 +46,7 @@ public class TrustAnchorStore { if (mMap == null) { mMap = new HashMap(); } + String k = key(rrset.getName(), rrset.getDClass()); rrset.setSecurityStatus(SecurityStatus.SECURE); @@ -58,25 +54,49 @@ public class TrustAnchorStore { } private SRRset lookup(String key) { - if (mMap == null) return null; + if (mMap == null) { + return null; + } + return mMap.get(key); } public SRRset find(Name n, int dclass) { - if (mMap == null) return null; + if (mMap == null) { + return null; + } while (n.labels() > 0) { String k = key(n, dclass); SRRset r = lookup(k); - if (r != null) return r; + + if (r != null) { + return r; + } + n = new Name(n, 1); } return null; } - + public boolean isBelowTrustAnchor(Name n, int dclass) { return find(n, dclass) != null; } + public List listTrustAnchors() { + List res = new ArrayList(); + + for (Map.Entry entry : mMap.entrySet()) { + for (Iterator i = entry.getValue().rrs(); i.hasNext();) { + DNSKEYRecord r = (DNSKEYRecord) i.next(); + String key_desc = r.getName().toString() + "/" + + DNSSEC.Algorithm.string(r.getAlgorithm()) + "/" + + r.getFootprint(); + res.add(key_desc); + } + } + + return res; + } } diff --git a/src/com/versign/tat/dnssec/Util.java b/src/com/versign/tat/dnssec/Util.java index 967be77..40890fb 100644 --- a/src/com/versign/tat/dnssec/Util.java +++ b/src/com/versign/tat/dnssec/Util.java @@ -1,115 +1,109 @@ -/* - * $Id$ - * - * 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. - * - */ +/***************************** -*- Java -*- ********************************\ + * * + * Copyright (c) 2009 VeriSign, Inc. All rights reserved. * + * * + * This software is provided solely in connection with the terms of the * + * license agreement. Any other use without the prior express written * + * permission of VeriSign is completely prohibited. The software and * + * documentation are "Commercial Items", as that term is defined in 48 * + * C.F.R. section 2.101, consisting of "Commercial Computer Software" and * + * "Commercial Computer Software Documentation" as such terms are defined * + * in 48 C.F.R. section 252.227-7014(a)(5) and 48 C.F.R. section * + * 252.227-7014(a)(1), and used in 48 C.F.R. section 12.212 and 48 C.F.R. * + * section 227.7202, as applicable. Pursuant to the above and other * + * relevant sections of the Code of Federal Regulations, as applicable, * + * VeriSign's publications, commercial computer software, and commercial * + * computer software documentation are distributed and licensed to United * + * States Government end users with only those rights as granted to all * + * other end users, according to the terms and conditions contained in the * + * license agreement(s) that accompany the products and software * + * documentation. * + * * +\***************************************************************************/ -package com.versign.tat.dnssec; +package com.verisign.tat.dnssec; + +import org.xbill.DNS.Name; import java.util.*; -import org.xbill.DNS.Name; /** * Some basic utility functions. */ -public class Util -{ +public class Util { + /** + * Convert a DNS name into a string suitable for use as a cache key. + * + * @param name The name to convert. + * @return A string representing the name. This isn't ever meant to be + * converted back into a DNS name. + */ + public static String nameToString(Name name) { + if (name.equals(Name.root)) { + return "."; + } - /** - * Convert a DNS name into a string suitable for use as a cache key. - * - * @param name The name to convert. - * @return A string representing the name. This isn't ever meant to be - * converted back into a DNS name. - */ - public static String nameToString(Name name) - { - if (name.equals(Name.root)) return "."; + String n = name.toString().toLowerCase(); - String n = name.toString().toLowerCase(); - if (n.endsWith(".")) n = n.substring(0, n.length() - 1); + if (n.endsWith(".")) { + n = n.substring(0, n.length() - 1); + } - return n; - } + return n; + } - public static int parseInt(String s, int def) - { - if (s == null) return def; - try - { - return Integer.parseInt(s); - } - catch (NumberFormatException e) - { - return def; - } - } + public static int parseInt(String s, int def) { + if (s == null) { + return def; + } - public static long parseLong(String s, long def) - { - if (s == null) return def; - try - { - return Long.parseLong(s); - } - catch (NumberFormatException e) - { - return def; - } - } - - public static class ConfigEntry - { - public String key; - public String value; - - public ConfigEntry(String key, String value) - { - this.key = key; this.value = value; - } - } - - public static List parseConfigPrefix(Properties config, String prefix) - { - if (! prefix.endsWith(".")) - { - prefix = prefix + "."; - } - - List res = new ArrayList(); - - for (Map.Entry entry : config.entrySet()) { - String key = (String) entry.getKey(); - if (key.startsWith(prefix)) { - key = key.substring(prefix.length()); - res.add(new ConfigEntry(key, (String) entry.getValue())); + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + return def; + } + } + + public static long parseLong(String s, long def) { + if (s == null) { + return def; + } + + try { + return Long.parseLong(s); + } catch (NumberFormatException e) { + return def; + } + } + + public static List parseConfigPrefix(Properties config, + String prefix) { + if (!prefix.endsWith(".")) { + prefix = prefix + "."; + } + + List res = new ArrayList(); + + for (Map.Entry entry : config.entrySet()) { + String key = (String) entry.getKey(); + + if (key.startsWith(prefix)) { + key = key.substring(prefix.length()); + res.add(new ConfigEntry(key, (String) entry.getValue())); + } + } + + return res; + } + + public static class ConfigEntry { + public String key; + public String value; + + public ConfigEntry(String key, String value) { + this.key = key; + this.value = value; } } - - return res; - } } diff --git a/src/com/versign/tat/dnssec/ValUtils.java b/src/com/versign/tat/dnssec/ValUtils.java index 873733f..5d05e98 100644 --- a/src/com/versign/tat/dnssec/ValUtils.java +++ b/src/com/versign/tat/dnssec/ValUtils.java @@ -1,66 +1,48 @@ -/* - * Copyright (c) 2009 VeriSign, Inc. 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. - * - */ +/***************************** -*- Java -*- ********************************\ + * * + * Copyright (c) 2009 VeriSign, Inc. All rights reserved. * + * * + * This software is provided solely in connection with the terms of the * + * license agreement. Any other use without the prior express written * + * permission of VeriSign is completely prohibited. The software and * + * documentation are "Commercial Items", as that term is defined in 48 * + * C.F.R. section 2.101, consisting of "Commercial Computer Software" and * + * "Commercial Computer Software Documentation" as such terms are defined * + * in 48 C.F.R. section 252.227-7014(a)(5) and 48 C.F.R. section * + * 252.227-7014(a)(1), and used in 48 C.F.R. section 12.212 and 48 C.F.R. * + * section 227.7202, as applicable. Pursuant to the above and other * + * relevant sections of the Code of Federal Regulations, as applicable, * + * VeriSign's publications, commercial computer software, and commercial * + * computer software documentation are distributed and licensed to United * + * States Government end users with only those rights as granted to all * + * other end users, according to the terms and conditions contained in the * + * license agreement(s) that accompany the products and software * + * documentation. * + * * +\***************************************************************************/ -package com.versign.tat.dnssec; +package com.verisign.tat.dnssec; + +import org.apache.log4j.Logger; + +import org.xbill.DNS.*; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; + import java.util.Iterator; -import org.xbill.DNS.*; /** * This is a collection of routines encompassing the logic of validating * different message types. -*/ - + */ public class ValUtils { - - // These are response subtypes. They are necessary for determining the - // validation strategy. They have no bearing on the iterative resolution - // algorithm, so they are confined here. - - public enum ResponseType { - UNTYPED, // not sub typed yet - UNKNOWN, // not a recognized sub type - POSITIVE, // a positive response (no CNAME/DNAME chain) - CNAME, // a positive response with a CNAME/DNAME chain. - NODATA, // a NOERROR/NODATA response - NAMEERROR, // a NXDOMAIN response - ANY, // a response to a qtype=ANY query - REFERRAL, - // a referral response - THROWAWAY - // a throwaway response (i.e., an error) - } + private static Logger st_log = Logger.getLogger(ValUtils.class); + private Logger log = Logger.getLogger(this.getClass()); /** A local copy of the verifier object. */ - private DnsSecVerifier mVerifier; + private DnsSecVerifier mVerifier; public ValUtils(DnsSecVerifier verifier) { mVerifier = verifier; @@ -68,18 +50,19 @@ public class ValUtils { /** * Given a response, classify ANSWER responses into a subtype. - * + * * @param m * The response to classify. - * + * * @return A subtype ranging from UNKNOWN to NAMEERROR. */ public static ResponseType classifyResponse(SMessage m, Name zone) { + SRRset [] rrsets; - SRRset[] rrsets; // Normal Name Error's are easy to detect -- but don't mistake a CNAME // chain ending in NXDOMAIN. - if (m.getRcode() == Rcode.NXDOMAIN && m.getCount(Section.ANSWER) == 0) { + if ((m.getRcode() == Rcode.NXDOMAIN) && + (m.getCount(Section.ANSWER) == 0)) { return ResponseType.NAMEERROR; } @@ -92,12 +75,13 @@ public class ValUtils { // 1) nothing in the ANSWER section // 2) an NS RRset in the AUTHORITY section that is a strict subdomain of // 'zone' (the presumed queried zone). - if (zone != null && m.getCount(Section.ANSWER) == 0 - && m.getCount(Section.AUTHORITY) > 0) { + if ((zone != null) && (m.getCount(Section.ANSWER) == 0) && + (m.getCount(Section.AUTHORITY) > 0)) { rrsets = m.getSectionRRsets(Section.AUTHORITY); + for (int i = 0; i < rrsets.length; ++i) { - if (rrsets[i].getType() == Type.NS - && strictSubdomain(rrsets[i].getName(), zone)) { + if ((rrsets[i].getType() == Type.NS) && + strictSubdomain(rrsets[i].getName(), zone)) { return ResponseType.REFERRAL; } } @@ -123,11 +107,17 @@ public class ValUtils { // Note that DNAMEs will be ignored here, unless qtype=DNAME. Unless // qtype=CNAME, this will yield a CNAME response. for (int i = 0; i < rrsets.length; i++) { - if (rrsets[i].getType() == qtype) return ResponseType.POSITIVE; - if (rrsets[i].getType() == Type.CNAME) return ResponseType.CNAME; + if (rrsets[i].getType() == qtype) { + return ResponseType.POSITIVE; + } + + if (rrsets[i].getType() == Type.CNAME) { + return ResponseType.CNAME; + } } - // st_log.warn("Failed to classify response message:\n" + m); + st_log.warn("Failed to classify response message:\n" + m); + return ResponseType.UNKNOWN; } @@ -135,7 +125,7 @@ public class ValUtils { * Given a response, determine the name of the "signer". This is primarily * to determine if the response is, in fact, signed at all, and, if so, what * is the name of the most pertinent keyset. - * + * * @param m * The response to analyze. * @return a signer name, if the response is signed (even partially), or @@ -145,28 +135,32 @@ public class ValUtils { // FIXME: this used to classify the message, then look in the pertinent // section. Now we just find the first RRSIG in the ANSWER and AUTHORIY // sections. + for (int section = Section.ANSWER; section < Section.ADDITIONAL; + ++section) { + SRRset [] rrsets = m.getSectionRRsets(section); - for (int section = Section.ANSWER; section < Section.ADDITIONAL; ++section) { - SRRset[] rrsets = m.getSectionRRsets(section); for (int i = 0; i < rrsets.length; ++i) { Name signerName = rrsets[i].getSignerName(); - if (signerName != null) return signerName; + + if (signerName != null) { + return signerName; + } } } + return null; } - /** * Given a DNSKEY record, generate the DS record from it. - * + * * @param keyrec * the DNSKEY record in question. * @param ds_alg * The DS digest algorithm in use. * @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(); os.writeByteArray(keyrec.getName().toWireCanonical()); @@ -174,65 +168,81 @@ public class ValUtils { try { MessageDigest md = null; - switch (ds_alg) { - case DSRecord.SHA1_DIGEST_ID: - md = MessageDigest.getInstance("SHA"); - return md.digest(os.toByteArray()); - case DSRecord.SHA256_DIGEST_ID: - md = MessageDigest.getInstance("SHA256"); - return md.digest(os.toByteArray()); - default: - // st_log.warn("Unknown DS algorithm: " + ds_alg); - return null; - } + switch (ds_alg) { + case DSRecord.SHA1_DIGEST_ID: + md = MessageDigest.getInstance("SHA"); + + return md.digest(os.toByteArray()); + + case DSRecord.SHA256_DIGEST_ID: + md = MessageDigest.getInstance("SHA256"); + + return md.digest(os.toByteArray()); + + default: + st_log.warn("Unknown DS algorithm: " + ds_alg); + + return null; + } } 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; } } public static boolean supportsDigestID(int digest_id) { - if (digest_id == DSRecord.SHA1_DIGEST_ID) return true; - if (digest_id == DSRecord.SHA256_DIGEST_ID) return true; + if (digest_id == DSRecord.SHA1_DIGEST_ID) { + return true; + } + + if (digest_id == DSRecord.SHA256_DIGEST_ID) { + return true; + } + return false; } /** * Check to see if a type is a special DNSSEC type. - * + * * @param type * The type. - * + * * @return true if the type is one of the special DNSSEC types. */ public static boolean isDNSSECType(int type) { switch (type) { - case Type.DNSKEY: - case Type.NSEC: - case Type.DS: - case Type.RRSIG: - case Type.NSEC3: - return true; - default: - return false; + case Type.DNSKEY: + case Type.NSEC: + case Type.DS: + case Type.RRSIG: + case Type.NSEC3: + return true; + + default: + return false; } } /** * Set the security status of a particular RRset. This will only upgrade the * security status. - * + * * @param rrset * The SRRset to update. * @param security * The security status. */ public static void setRRsetSecurity(SRRset rrset, byte security) { - if (rrset == null) return; + if (rrset == null) { + return; + } int cur_sec = rrset.getSecurityStatus(); - if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec) { + + if ((cur_sec == SecurityStatus.UNCHECKED) || (security > cur_sec)) { rrset.setSecurityStatus(security); } } @@ -241,28 +251,33 @@ public class ValUtils { * Set the security status of a message and all of its RRsets. This will * only upgrade the status of the message (i.e., set to more secure, not * less) and all of the RRsets. - * + * * @param m * @param security * KeyEntry ke; - * + * * SMessage m = response.getSMessage(); SRRset ans_rrset = * m.findAnswerRRset(qname, qtype, qclass); - * + * * ke = verifySRRset(ans_rrset, key_rrset); if * (ans_rrset.getSecurityStatus() != SecurityStatus.SECURE) { * return; } key_rrset = ke.getRRset(); */ public static void setMessageSecurity(SMessage m, byte security) { - if (m == null) return; + if (m == null) { + return; + } int cur_sec = m.getStatus(); - if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec) { + + if ((cur_sec == SecurityStatus.UNCHECKED) || (security > cur_sec)) { m.setStatus(security); } - for (int section = Section.ANSWER; section <= Section.ADDITIONAL; section++) { - SRRset[] rrsets = m.getSectionRRsets(section); + for (int section = Section.ANSWER; section <= Section.ADDITIONAL; + section++) { + SRRset [] rrsets = m.getSectionRRsets(section); + for (int i = 0; i < rrsets.length; i++) { setRRsetSecurity(rrsets[i], security); } @@ -273,7 +288,7 @@ public class ValUtils { * Given an SRRset that is signed by a DNSKEY found in the key_rrset, verify * it. This will return the status (either BOGUS or SECURE) and set that * status in rrset. - * + * * @param rrset * The SRRset to verify. * @param key_rrset @@ -281,45 +296,49 @@ public class ValUtils { * @return The status (BOGUS or SECURE). */ public byte verifySRRset(SRRset rrset, SRRset key_rrset) { -// String rrset_name = rrset.getName() + "/" -// + Type.string(rrset.getType()) + "/" -// + DClass.string(rrset.getDClass()); + String rrset_name = rrset.getName() + "/" + + Type.string(rrset.getType()) + "/" + + DClass.string(rrset.getDClass()); if (rrset.getSecurityStatus() == SecurityStatus.SECURE) { - // log.trace("verifySRRset: rrset <" + rrset_name - // + "> previously found to be SECURE"); + log.trace("verifySRRset: rrset <" + rrset_name + + "> previously found to be SECURE"); + return SecurityStatus.SECURE; } byte status = mVerifier.verify(rrset, key_rrset); + if (status != SecurityStatus.SECURE) { - // log.debug("verifySRRset: rrset <" + rrset_name + - // "> found to be BAD"); + log.debug("verifySRRset: rrset <" + rrset_name + + "> found to be BAD"); status = SecurityStatus.BOGUS; + } else { + log.trace("verifySRRset: rrset <" + rrset_name + + "> found to be SECURE"); } - // else - // { - // log.trace("verifySRRset: rrset <" + rrset_name + - // "> found to be SECURE"); - // } rrset.setSecurityStatus(status); + return status; } /** * Determine if a given type map has a given type. - * + * * @param types * 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. */ - public static boolean typeMapHasType(int[] types, int type) { + public static boolean typeMapHasType(int [] types, int type) { for (int i = 0; i < types.length; i++) { - if (types[i] == type) return true; + if (types[i] == type) { + return true; + } } + return false; } @@ -328,28 +347,33 @@ public class ValUtils { for (Iterator i = rrset.sigs(); i.hasNext();) { return (RRSIGRecord) i.next(); } + return null; } /** * Finds the longest common name between two domain names. - * + * * @param domain1 * @param domain2 * @return */ 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 // FIXME: convert this to direct operations on the byte[] - int d1_labels = domain1.labels(); int d2_labels = domain2.labels(); - int l = (d1_labels < d2_labels) ? d1_labels : d2_labels; + int l = (d1_labels < d2_labels) ? d1_labels : d2_labels; + for (int i = l; i > 0; i--) { Name n1 = new Name(domain1, d1_labels - i); Name n2 = new Name(domain2, d2_labels - i); + if (n1.equals(n2)) { return n1; } @@ -361,26 +385,33 @@ public class ValUtils { public static boolean strictSubdomain(Name child, Name parent) { int clabels = child.labels(); int plabels = parent.labels(); - if (plabels >= clabels) return false; + + 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 * the result of a wildcard expansion. - * + * * @param rrset * The rrset to examine. * @return true if the rrset is a wildcard expansion. This will return false * for all unsigned rrsets. */ public static boolean rrsetIsWildcardExpansion(RRset rrset) { - if (rrset == null) return false; + if (rrset == null) { + return false; + } + RRSIGRecord rrsig = rrsetFirstSig(rrset); - if (rrset.getName().labels() - 1 > rrsig.getLabels()) { + if ((rrset.getName().labels() - 1) > rrsig.getLabels()) { return true; } @@ -391,23 +422,28 @@ public class ValUtils { * Determine by looking at a signed RRset whether or not the RRset name was * the result of a wildcard expansion. If so, return the name of the * generating wildcard. - * + * * @param rrset * The rrset to check. * @return the wildcard name, if the rrset was synthesized from a wildcard. * null if not. */ public static Name rrsetWildcard(RRset rrset) { - if (rrset == null) return null; + if (rrset == null) { + return null; + } + RRSIGRecord rrsig = rrsetFirstSig(rrset); // if the RRSIG label count is shorter than the number of actual labels, // then this rrset was synthesized from a wildcard. // Note that the RRSIG label count doesn't count the root label. int label_diff = (rrset.getName().labels() - 1) - rrsig.getLabels(); + if (label_diff > 0) { return rrset.getName().wild(label_diff); } + return null; } @@ -430,7 +466,7 @@ public class ValUtils { /** * Determine if the given NSEC proves a NameError (NXDOMAIN) for a given * qname. - * + * * @param nsec * The NSEC to check. * @param qname @@ -442,9 +478,9 @@ public class ValUtils { * @return true if the NSEC proves the condition. */ public static boolean nsecProvesNameError(NSECRecord nsec, Name qname, - Name signerName) { + Name signerName) { Name owner = nsec.getName(); - Name next = nsec.getNext(); + Name next = nsec.getNext(); // If NSEC owner == qname, then this NSEC proves that qname exists. if (qname.equals(owner)) { @@ -454,24 +490,26 @@ public class ValUtils { // 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 is being misused. - boolean hasBadType = typeMapHasType(nsec.getTypes(), Type.DNAME) - || (typeMapHasType(nsec.getTypes(), Type.NS) && !typeMapHasType(nsec.getTypes(), - Type.SOA)); + boolean hasBadType = typeMapHasType(nsec.getTypes(), Type.DNAME) || + (typeMapHasType(nsec.getTypes(), Type.NS) && + !typeMapHasType(nsec.getTypes(), Type.SOA)); + if (qname.subdomain(owner) && hasBadType) { return false; } - if (qname.compareTo(owner) > 0 && (qname.compareTo(next) < 0) - || signerName.equals(next)) { + if (((qname.compareTo(owner) > 0) && (qname.compareTo(next) < 0)) || + signerName.equals(next)) { return true; } + return false; } /** * Determine if a NSEC record proves the non-existence of a wildcard that * could have produced qname. - * + * * @param nsec * The nsec to check. * @param qname @@ -481,17 +519,18 @@ public class ValUtils { * @return true if the NSEC proves the condition. */ public static boolean nsecProvesNoWC(NSECRecord nsec, Name qname, - Name signerName) { - Name owner = nsec.getName(); - Name next = nsec.getNext(); + Name signerName) { + Name owner = nsec.getName(); + Name next = nsec.getNext(); - int qname_labels = qname.labels(); - int signer_labels = signerName.labels(); + int qname_labels = qname.labels(); + int signer_labels = signerName.labels(); for (int i = qname_labels - signer_labels; i > 0; i--) { Name wc_name = qname.wild(i); - if (wc_name.compareTo(owner) > 0 - && (wc_name.compareTo(next) < 0 || signerName.equals(next))) { + + if ((wc_name.compareTo(owner) > 0) && + ((wc_name.compareTo(next) < 0) || signerName.equals(next))) { return true; } } @@ -505,7 +544,7 @@ public class ValUtils { * wildcard case. If the ownername of 'nsec' is a wildcard, the validator * must still be provided proof that qname did not directly exist and that * the wildcard is, in fact, *.closest_encloser. - * + * * @param nsec * The NSEC to check * @param qname @@ -515,7 +554,7 @@ public class ValUtils { * @return true if the NSEC proves the condition. */ public static boolean nsecProvesNodata(NSECRecord nsec, Name qname, - int qtype) { + int qtype) { if (!nsec.getName().equals(qname)) { // wildcard checking. @@ -532,10 +571,11 @@ public class ValUtils { // The qname must be a strict subdomain of the closest encloser, // and // the qtype must be absent from the type map. - if (!strictSubdomain(qname, ce) - || typeMapHasType(nsec.getTypes(), qtype)) { + if (!strictSubdomain(qname, ce) || + typeMapHasType(nsec.getTypes(), qtype)) { return false; } + return true; } @@ -545,10 +585,11 @@ public class ValUtils { // be // less than qname, and the next name will be a child domain of the // qname. - if (strictSubdomain(nsec.getNext(), qname) - && qname.compareTo(nsec.getName()) > 0) { + if (strictSubdomain(nsec.getNext(), qname) && + (qname.compareTo(nsec.getName()) > 0)) { return true; } + // Otherwise, this NSEC does not prove ENT, so it does not prove // NODATA. return false; @@ -569,8 +610,8 @@ public class ValUtils { // not a zone apex), then we should have gotten a referral (or we just // got // the wrong NSEC). - if (typeMapHasType(nsec.getTypes(), Type.NS) - && !typeMapHasType(nsec.getTypes(), Type.SOA)) { + if (typeMapHasType(nsec.getTypes(), Type.NS) && + !typeMapHasType(nsec.getTypes(), Type.SOA)) { return false; } @@ -579,7 +620,8 @@ public class ValUtils { public static byte nsecProvesNoDS(NSECRecord nsec, Name qname) { // 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)) { // SOA present means that this is the NSEC from the child, not the // parent (so it is the wrong one) @@ -594,8 +636,18 @@ public class ValUtils { // anything one way or the other. return SecurityStatus.INSECURE; } + // Otherwise, this proves no DS. return SecurityStatus.SECURE; } + // These are response subtypes. They are necessary for determining the + // validation strategy. They have no bearing on the iterative resolution + // algorithm, so they are confined here. + public enum ResponseType {UNTYPED, UNKNOWN, POSITIVE, CNAME, NODATA, + NAMEERROR, ANY, REFERRAL, + // a referral response + THROWAWAY; + // a throwaway response (i.e., an error) + } }