/*
- * $Id$
- *
- * Copyright (c) 2005 VeriSign, Inc. All rights reserved.
+ * 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
/**
* This is a collection of routines encompassing the logic of validating
* different message types.
- *
- * @author davidb
- * @version $Revision$
- */
-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.
-
- /** Not subtyped yet. */
- public static final int UNTYPED = 0;
-
- /** Not a recognized subtype. */
- public static final int UNKNOWN = 1;
-
- /** A postive, direct, response. */
- public static final int POSITIVE = 2;
-
- /** A postive response, with a CNAME/DNAME chain. */
- public static final int CNAME = 3;
-
- /** A NOERROR/NODATA response. */
- public static final int NODATA = 4;
-
- /** A NXDOMAIN response. */
- public static final int NAMEERROR = 5;
-
- /** A response to a qtype=ANY query. */
- public static final int ANY = 6;
-
- /** A local copy of the verifier object. */
- private DnsSecVerifier mVerifier;
-
- public ValUtils(DnsSecVerifier verifier)
- {
- mVerifier = verifier;
- }
-
- /**
- * 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 int classifyResponse(SMessage m)
- {
- // 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)
- {
- return NAMEERROR;
- }
+*/
- // Next is NODATA
- // st_log.debug("classifyResponse: ancount = " +
- // m.getCount(Section.ANSWER));
- if (m.getCount(Section.ANSWER) == 0)
- {
- return NODATA;
- }
+public class ValUtils {
- // We distinguish between CNAME response and other positive/negative
- // responses because CNAME answers require extra processing.
- int qtype = m.getQuestion().getType();
+ // 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.
- // We distinguish between ANY and CNAME or POSITIVE because ANY responses
- // are validated differently.
- if (qtype == Type.ANY)
- {
- return ANY;
- }
+ /** Not subtyped yet. */
+ public static final int UNTYPED = 0;
+
+ /** Not a recognized subtype. */
+ public static final int UNKNOWN = 1;
+
+ /** A postive, direct, response. */
+ public static final int POSITIVE = 2;
+
+ /** A postive response, with a CNAME/DNAME chain. */
+ public static final int CNAME = 3;
+
+ /** A NOERROR/NODATA response. */
+ public static final int NODATA = 4;
+
+ /** A NXDOMAIN response. */
+ public static final int NAMEERROR = 5;
+
+ /** A response to a qtype=ANY query. */
+ public static final int ANY = 6;
- SRRset[] rrsets = m.getSectionRRsets(Section.ANSWER);
+ /** A local copy of the verifier object. */
+ private DnsSecVerifier mVerifier;
- // 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 POSITIVE;
- if (rrsets[i].getType() == Type.CNAME) return CNAME;
+ public ValUtils(DnsSecVerifier verifier) {
+ mVerifier = verifier;
}
-// st_log.warn("Failed to classify response message:\n" + m);
- return UNKNOWN;
- }
-
- /**
- * 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.
- * @param request The request that generated the response.
- * @return a signer name, if the response is signed (even partially), or
- * null if the response isn't signed.
- */
- public Name findSigner(SMessage m)
- {
- int subtype = classifyResponse(m);
- Name qname = m.getQName();
-
- SRRset[] rrsets;
-
- switch (subtype)
- {
- case POSITIVE :
- case CNAME :
- case ANY :
- // Check to see if the ANSWER section RRset
- rrsets = m.getSectionRRsets(Section.ANSWER);
- for (int i = 0; i < rrsets.length; i++)
- {
- if (rrsets[i].getName().equals(qname))
- {
- return rrsets[i].getSignerName();
- }
+ /**
+ * 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 int classifyResponse(SMessage m) {
+ // 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) {
+ return NAMEERROR;
}
- return null;
- case NAMEERROR :
- case NODATA :
- // Check to see if the AUTH section NSEC record(s) have rrsigs
- rrsets = m.getSectionRRsets(Section.AUTHORITY);
- for (int i = 0; i < rrsets.length; i++)
- {
- if (rrsets[i].getType() == Type.NSEC
- || rrsets[i].getType() == Type.NSEC3)
- {
- return rrsets[i].getSignerName();
- }
+ // Next is NODATA
+ // st_log.debug("classifyResponse: ancount = " +
+ // m.getCount(Section.ANSWER));
+ if (m.getCount(Section.ANSWER) == 0) {
+ return NODATA;
}
- return null;
- default :
-// log.debug("findSigner: could not find signer name "
-// + "for unknown type response.");
- return null;
- }
- }
-
- public boolean dssetIsUsable(SRRset ds_rrset)
- {
- for (Iterator i = ds_rrset.rrs(); i.hasNext();)
- {
- DSRecord ds = (DSRecord) i.next();
- if (supportsDigestID(ds.getDigestID())
- && mVerifier.supportsAlgorithm(ds.getAlgorithm()))
- {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Given a DS rrset and a DNSKEY rrset, match the DS to a DNSKEY and verify
- * the DNSKEY rrset with that key.
- *
- * @param dnskey_rrset The DNSKEY rrset to match against. The security
- * status of this rrset will be updated on a successful
- * verification.
- * @param ds_rrset The DS rrset to match with. This rrset must already be
- * trusted.
- *
- * @return a KeyEntry. This will either contain the now trusted
- * dnskey_rrset, a "null" key entry indicating that this DS
- * rrset/DNSKEY pair indicate an secure end to the island of trust
- * (i.e., unknown algorithms), or a "bad" KeyEntry if the dnskey
- * rrset fails to verify. Note that the "null" response should
- * generally only occur in a private algorithm scenario: normally
- * this sort of thing is checked before fetching the matching DNSKEY
- * rrset.
- */
-// public KeyEntry verifyNewDNSKEYs(SRRset dnskey_rrset, SRRset ds_rrset)
-// {
-// if (!dnskey_rrset.getName().equals(ds_rrset.getName()))
-// {
-//// log.debug("DNSKEY RRset did not match DS RRset by name!");
-// return KeyEntry
-// .newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass());
-// }
-//
-// // as long as this is false, we can consider this DS rrset to be
-// // equivalent to no DS rrset.
-// boolean hasUsefulDS = false;
-//
-// for (Iterator i = ds_rrset.rrs(); i.hasNext();)
-// {
-// DSRecord ds = (DSRecord) i.next();
-//
-// // Check to see if we can understand this DS.
-// if (!supportsDigestID(ds.getDigestID())
-// || !mVerifier.supportsAlgorithm(ds.getAlgorithm()))
-// {
-// continue;
-// }
-//
-// // Once we see a single DS with a known digestID and algorithm, we
-// // cannot return INSECURE (with a "null" KeyEntry).
-// hasUsefulDS = true;
-//
-// DNSKEY : for (Iterator j = dnskey_rrset.rrs(); j.hasNext();)
-// {
-// DNSKEYRecord dnskey = (DNSKEYRecord) j.next();
-//
-// // Skip DNSKEYs that don't match the basic criteria.
-// if (ds.getFootprint() != dnskey.getFootprint()
-// || ds.getAlgorithm() != dnskey.getAlgorithm())
-// {
-// continue;
-// }
-//
-// // Convert the candidate DNSKEY into a hash using the same DS hash
-// // algorithm.
-// byte[] key_hash = calculateDSHash(dnskey, ds.getDigestID());
-// byte[] ds_hash = ds.getDigest();
-//
-// // see if there is a length mismatch (unlikely)
-// if (key_hash.length != ds_hash.length)
-// {
-// continue DNSKEY;
-// }
-//
-// for (int k = 0; k < key_hash.length; k++)
-// {
-// if (key_hash[k] != ds_hash[k]) continue DNSKEY;
-// }
-//
-// // Otherwise, we have a match! Make sure that the DNSKEY verifies
-// // *with this key*.
-// byte res = mVerifier.verify(dnskey_rrset, dnskey);
-// if (res == SecurityStatus.SECURE)
-// {
-//// log.trace("DS matched DNSKEY.");
-// dnskey_rrset.setSecurityStatus(SecurityStatus.SECURE);
-// return KeyEntry.newKeyEntry(dnskey_rrset);
-// }
-// // If it didn't validate with the DNSKEY, try the next one!
-// }
-// }
-//
-// // None of the DS's worked out.
-//
-// // If no DSs were understandable, then this is OK.
-// if (!hasUsefulDS)
-// {
-//// log.debug("No usuable DS records were found -- treating as insecure.");
-// return KeyEntry.newNullKeyEntry(ds_rrset.getName(), ds_rrset
-// .getDClass(), ds_rrset.getTTL());
-// }
-// // If any were understandable, then it is bad.
-//// log.debug("Failed to match any usable DS to a DNSKEY.");
-// return KeyEntry.newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass());
-// }
-
- /**
- * 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)
- {
- DNSOutput os = new DNSOutput();
-
- os.writeByteArray(keyrec.getName().toWireCanonical());
- os.writeByteArray(keyrec.rdataToWireCanonical());
-
- 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;
- }
+ // We distinguish between CNAME response and other positive/negative
+ // responses because CNAME answers require extra processing.
+ int qtype = m.getQuestion().getType();
+
+ // We distinguish between ANY and CNAME or POSITIVE because ANY
+ // responses
+ // are validated differently.
+ if (qtype == Type.ANY) {
+ return ANY;
+ }
+
+ SRRset[] rrsets = m.getSectionRRsets(Section.ANSWER);
+
+ // 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 POSITIVE;
+ if (rrsets[i].getType() == Type.CNAME) return CNAME;
+ }
+ // st_log.warn("Failed to classify response message:\n" + m);
+ return UNKNOWN;
}
- catch (NoSuchAlgorithmException e)
- {
-// st_log.error("Error using DS algorithm: " + ds_alg, e);
- return null;
+
+ /**
+ * 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.
+ * @param request
+ * The request that generated the response.
+ * @return a signer name, if the response is signed (even partially), or
+ * null if the response isn't signed.
+ */
+ public Name findSigner(SMessage m) {
+ int subtype = classifyResponse(m);
+ Name qname = m.getQName();
+
+ SRRset[] rrsets;
+
+ switch (subtype) {
+ case POSITIVE:
+ case CNAME:
+ case ANY:
+ // Check to see if the ANSWER section RRset
+ rrsets = m.getSectionRRsets(Section.ANSWER);
+ for (int i = 0; i < rrsets.length; i++) {
+ if (rrsets[i].getName().equals(qname)) {
+ return rrsets[i].getSignerName();
+ }
+ }
+ return null;
+
+ case NAMEERROR:
+ case NODATA:
+ // Check to see if the AUTH section NSEC record(s) have rrsigs
+ rrsets = m.getSectionRRsets(Section.AUTHORITY);
+ for (int i = 0; i < rrsets.length; i++) {
+ if (rrsets[i].getType() == Type.NSEC
+ || rrsets[i].getType() == Type.NSEC3) {
+ return rrsets[i].getSignerName();
+ }
+ }
+ return null;
+ default:
+ // log.debug("findSigner: could not find signer name "
+ // + "for unknown type response.");
+ 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;
- 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 :
+
+ public boolean dssetIsUsable(SRRset ds_rrset) {
+ for (Iterator i = ds_rrset.rrs(); i.hasNext();) {
+ DSRecord ds = (DSRecord) i.next();
+ if (supportsDigestID(ds.getDigestID())
+ && mVerifier.supportsAlgorithm(ds.getAlgorithm())) {
+ return true;
+ }
+ }
+
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;
-
- int cur_sec = rrset.getSecurityStatus();
- if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec)
- {
- rrset.setSecurityStatus(security);
- }
- }
-
- /**
- * 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;
-
- int cur_sec = m.getStatus();
- 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 i = 0; i < rrsets.length; i++)
- {
- setRRsetSecurity(rrsets[i], security);
- }
+ /**
+ * Given a DS rrset and a DNSKEY rrset, match the DS to a DNSKEY and verify
+ * the DNSKEY rrset with that key.
+ *
+ * @param dnskey_rrset
+ * The DNSKEY rrset to match against. The security status of this
+ * rrset will be updated on a successful verification.
+ * @param ds_rrset
+ * The DS rrset to match with. This rrset must already be
+ * trusted.
+ *
+ * @return a KeyEntry. This will either contain the now trusted
+ * dnskey_rrset, a "null" key entry indicating that this DS
+ * rrset/DNSKEY pair indicate an secure end to the island of trust
+ * (i.e., unknown algorithms), or a "bad" KeyEntry if the dnskey
+ * rrset fails to verify. Note that the "null" response should
+ * generally only occur in a private algorithm scenario: normally
+ * this sort of thing is checked before fetching the matching DNSKEY
+ * rrset.
+ */
+ // public KeyEntry verifyNewDNSKEYs(SRRset dnskey_rrset, SRRset ds_rrset)
+ // {
+ // if (!dnskey_rrset.getName().equals(ds_rrset.getName()))
+ // {
+ // // log.debug("DNSKEY RRset did not match DS RRset by name!");
+ // return KeyEntry
+ // .newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass());
+ // }
+ //
+ // // as long as this is false, we can consider this DS rrset to be
+ // // equivalent to no DS rrset.
+ // boolean hasUsefulDS = false;
+ //
+ // for (Iterator i = ds_rrset.rrs(); i.hasNext();)
+ // {
+ // DSRecord ds = (DSRecord) i.next();
+ //
+ // // Check to see if we can understand this DS.
+ // if (!supportsDigestID(ds.getDigestID())
+ // || !mVerifier.supportsAlgorithm(ds.getAlgorithm()))
+ // {
+ // continue;
+ // }
+ //
+ // // Once we see a single DS with a known digestID and algorithm, we
+ // // cannot return INSECURE (with a "null" KeyEntry).
+ // hasUsefulDS = true;
+ //
+ // DNSKEY : for (Iterator j = dnskey_rrset.rrs(); j.hasNext();)
+ // {
+ // DNSKEYRecord dnskey = (DNSKEYRecord) j.next();
+ //
+ // // Skip DNSKEYs that don't match the basic criteria.
+ // if (ds.getFootprint() != dnskey.getFootprint()
+ // || ds.getAlgorithm() != dnskey.getAlgorithm())
+ // {
+ // continue;
+ // }
+ //
+ // // Convert the candidate DNSKEY into a hash using the same DS hash
+ // // algorithm.
+ // byte[] key_hash = calculateDSHash(dnskey, ds.getDigestID());
+ // byte[] ds_hash = ds.getDigest();
+ //
+ // // see if there is a length mismatch (unlikely)
+ // if (key_hash.length != ds_hash.length)
+ // {
+ // continue DNSKEY;
+ // }
+ //
+ // for (int k = 0; k < key_hash.length; k++)
+ // {
+ // if (key_hash[k] != ds_hash[k]) continue DNSKEY;
+ // }
+ //
+ // // Otherwise, we have a match! Make sure that the DNSKEY verifies
+ // // *with this key*.
+ // byte res = mVerifier.verify(dnskey_rrset, dnskey);
+ // if (res == SecurityStatus.SECURE)
+ // {
+ // // log.trace("DS matched DNSKEY.");
+ // dnskey_rrset.setSecurityStatus(SecurityStatus.SECURE);
+ // return KeyEntry.newKeyEntry(dnskey_rrset);
+ // }
+ // // If it didn't validate with the DNSKEY, try the next one!
+ // }
+ // }
+ //
+ // // None of the DS's worked out.
+ //
+ // // If no DSs were understandable, then this is OK.
+ // if (!hasUsefulDS)
+ // {
+ // //
+ // log.debug("No usuable DS records were found -- treating as insecure.");
+ // return KeyEntry.newNullKeyEntry(ds_rrset.getName(), ds_rrset
+ // .getDClass(), ds_rrset.getTTL());
+ // }
+ // // If any were understandable, then it is bad.
+ // // log.debug("Failed to match any usable DS to a DNSKEY.");
+ // return KeyEntry.newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass());
+ // }
+ /**
+ * 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) {
+ DNSOutput os = new DNSOutput();
+
+ os.writeByteArray(keyrec.getName().toWireCanonical());
+ os.writeByteArray(keyrec.rdataToWireCanonical());
+
+ 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;
+ }
+
+ } catch (NoSuchAlgorithmException e) {
+ // st_log.error("Error using DS algorithm: " + ds_alg, e);
+ return null;
+ }
}
- }
-
- /**
- * 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 The set of keys to verify against.
- * @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());
-
- if (rrset.getSecurityStatus() == SecurityStatus.SECURE)
- {
-// log.trace("verifySRRset: rrset <" + rrset_name
-// + "> previously found to be SECURE");
- return SecurityStatus.SECURE;
+
+ 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;
+ return false;
}
- byte status = mVerifier.verify(rrset, key_rrset);
- if (status != SecurityStatus.SECURE)
- {
-// log.debug("verifySRRset: rrset <" + rrset_name + "> found to be BAD");
- status = SecurityStatus.BOGUS;
+ /**
+ * 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;
+ }
}
-// 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 typ.
- *
- * @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)
- {
- for (int i = 0; i < types.length; i++)
- {
- if (types[i] == type) return true;
+
+ /**
+ * 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;
+
+ int cur_sec = rrset.getSecurityStatus();
+ if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec) {
+ rrset.setSecurityStatus(security);
+ }
}
- return false;
- }
-
- public static RRSIGRecord rrsetFirstSig(RRset rrset) {
- for (Iterator i = rrset.sigs(); i.hasNext(); ) {
- return (RRSIGRecord) i.next();
- }
- return null;
- }
-
- public static Name longestCommonName(Name domain1, Name domain2) {
- 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 this_labels = domain1.labels();
- int name_labels = domain2.labels();
-
- int l = (this_labels < name_labels) ? this_labels : name_labels;
- for (int i = l; i > 0; i--)
- {
- Name n = new Name(domain2, name_labels - i);
- if (n.equals(name, offset(this_labels - i)))
- {
- return n;
- }
+
+ /**
+ * 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;
+
+ int cur_sec = m.getStatus();
+ 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 i = 0; i < rrsets.length; i++) {
+ setRRsetSecurity(rrsets[i], security);
+ }
+ }
}
-
- return root;
- }
- /**
- * 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;
- RRSIGRecord rrsig = rrsetFirstSig(rrset);
-
- if (rrset.getName().labels() - 1 > rrsig.getLabels())
- {
- return true;
+
+ /**
+ * 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
+ * The set of keys to verify against.
+ * @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());
+
+ if (rrset.getSecurityStatus() == SecurityStatus.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");
+ status = SecurityStatus.BOGUS;
+ }
+ // else
+ // {
+ // log.trace("verifySRRset: rrset <" + rrset_name +
+ // "> found to be SECURE");
+ // }
+
+ rrset.setSecurityStatus(status);
+ return status;
}
- return false;
- }
-
- /**
- * 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 chedck.
- * @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;
- 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);
+ /**
+ * Determine if a given type map has a given typ.
+ *
+ * @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) {
+ for (int i = 0; i < types.length; i++) {
+ if (types[i] == type) return true;
+ }
+ return false;
}
- return null;
- }
-
- public static Name closestEncloser(Name domain, NSECRecord nsec)
- {
- Name n1 = domain.longestCommonName(nsec.getName());
- Name n2 = domain.longestCommonName(nsec.getNext());
-
- return (n1.labels() > n2.labels()) ? n1 : n2;
- }
-
- public static Name nsecWildcard(Name domain, NSECRecord nsec)
- {
- try
- {
- return new Name("*", closestEncloser(domain, nsec));
+
+ public static RRSIGRecord rrsetFirstSig(RRset rrset) {
+ for (Iterator i = rrset.sigs(); i.hasNext();) {
+ return (RRSIGRecord) i.next();
+ }
+ return null;
}
- catch (TextParseException e)
- {
- // this should never happen.
- 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;
+ // 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;
+ 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;
+ }
+ }
+
+ return Name.root;
}
- }
-
- /**
- * Determine if the given NSEC proves a NameError (NXDOMAIN) for a given
- * qname.
- *
- * @param nsec The NSEC to check.
- * @param qname The qname to check against.
- * @param signerName The signer name of the NSEC record, which is used as
- * the zone name, for a more precise (but perhaps more brittle)
- * check for the last NSEC in a zone.
- * @return true if the NSEC proves the condition.
- */
- public static boolean nsecProvesNameError(NSECRecord nsec, Name qname,
- Name signerName)
- {
- Name owner = nsec.getName();
- Name next = nsec.getNext();
-
- // If NSEC owner == qname, then this NSEC proves that qname exists.
- if (qname.equals(owner))
- {
- return false;
+
+ public static boolean strictSubdomain(Name child, Name parent) {
+ int clabels = child.labels();
+ int plabels = parent.labels();
+ if (plabels >= clabels) return false;
+
+ Name n = new Name(child, clabels - plabels);
+ return parent.equals(n);
}
- // 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.
- if (qname.subdomain(owner)
- && (typeMapHasType(nsec.getTypes(), Type.DNAME) || (typeMapHasType(nsec
- .getTypes(),
- Type.NS) && !typeMapHasType(nsec.getTypes(), Type.SOA))))
- {
- return false;
+ /**
+ * 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;
+ RRSIGRecord rrsig = rrsetFirstSig(rrset);
+
+ if (rrset.getName().labels() - 1 > rrsig.getLabels()) {
+ return true;
+ }
+
+ return false;
}
- if (qname.compareTo(owner) > 0 && (qname.compareTo(next) < 0)
- || signerName.equals(next))
- {
- return true;
+ /**
+ * 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 chedck.
+ * @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;
+ 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;
}
- 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 The qname to check against.
- * @param signerName The signer name for the NSEC rrset, used as the zone
- * name.
- * @return true if the NSEC proves the condition.
- */
- public static boolean nsecProvesNoWC(NSECRecord nsec, Name qname,
- Name signerName)
- {
- Name owner = nsec.getName();
- Name next = nsec.getNext();
-
- 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)))
- {
- return true;
- }
+
+ public static Name closestEncloser(Name domain, NSECRecord nsec) {
+ Name n1 = longestCommonName(domain, nsec.getName());
+ Name n2 = longestCommonName(domain, nsec.getNext());
+
+ return (n1.labels() > n2.labels()) ? n1 : n2;
}
- return false;
- }
-
- /**
- * Determine if a NSEC proves the NOERROR/NODATA conditions. This will also
- * handle the empty non-terminal (ENT) case and partially handle the
- * 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 The query name to check against.
- * @param qtype The query type to check against.
- * @return true if the NSEC proves the condition.
- */
- public static boolean nsecProvesNodata(NSECRecord nsec, Name qname,
- int qtype)
- {
- if (!nsec.getName().equals(qname))
- {
- // wildcard checking.
-
- // If this is a wildcard NSEC, make sure that a) it was possible to have
- // generated qname from the wildcard and b) the type map does not
- // contain qtype. Note that this does NOT prove that this wildcard was
- // the applicable wildcard.
- if (nsec.getName().isWild())
- {
- // the is the purported closest encloser.
- Name ce = new Name(nsec.getName(), 1);
-
- // The qname must be a strict subdomain of the closest encloser, and
- // the qtype must be absent from the type map.
- if (!qname.strictSubdomain(ce) || typeMapHasType(nsec.getTypes(), qtype))
- {
- return false;
+ public static Name nsecWildcard(Name domain, NSECRecord nsec) {
+ try {
+ return new Name("*", closestEncloser(domain, nsec));
+ } catch (TextParseException e) {
+ // this should never happen.
+ return null;
}
- return true;
- }
-
- // empty-non-terminal checking.
-
- // If the nsec is proving that qname is an ENT, the nsec owner will be
- // less than qname, and the next name will be a child domain of the
- // qname.
- if (nsec.getNext().strictSubdomain(qname)
- && qname.compareTo(nsec.getName()) > 0)
- {
- return true;
- }
- // Otherwise, this NSEC does not prove ENT, so it does not prove NODATA.
- return false;
}
- // If the qtype exists, then we should have gotten it.
- if (typeMapHasType(nsec.getTypes(), qtype))
- {
- return false;
- }
+ /**
+ * Determine if the given NSEC proves a NameError (NXDOMAIN) for a given
+ * qname.
+ *
+ * @param nsec
+ * The NSEC to check.
+ * @param qname
+ * The qname to check against.
+ * @param signerName
+ * The signer name of the NSEC record, which is used as the zone
+ * name, for a more precise (but perhaps more brittle) check for
+ * the last NSEC in a zone.
+ * @return true if the NSEC proves the condition.
+ */
+ public static boolean nsecProvesNameError(NSECRecord nsec, Name qname,
+ Name signerName) {
+ Name owner = nsec.getName();
+ Name next = nsec.getNext();
+
+ // If NSEC owner == qname, then this NSEC proves that qname exists.
+ if (qname.equals(owner)) {
+ return false;
+ }
+
+ // 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.
+ if (qname.subdomain(owner)
+ && (typeMapHasType(nsec.getTypes(), Type.DNAME) || (typeMapHasType(
+ nsec.getTypes(),
+ Type.NS) && !typeMapHasType(
+ nsec.getTypes(),
+ Type.SOA)))) {
+ return false;
+ }
- // if the name is a CNAME node, then we should have gotten the CNAME
- if (typeMapHasType(nsec.getTypes(), Type.CNAME))
- {
- return false;
+ if (qname.compareTo(owner) > 0 && (qname.compareTo(next) < 0)
+ || signerName.equals(next)) {
+ return true;
+ }
+ return false;
}
-
- // If an NS set exists at this name, and NOT a SOA (so this is a zone cut,
- // 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))
- {
- 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
+ * The qname to check against.
+ * @param signerName
+ * The signer name for the NSEC rrset, used as the zone name.
+ * @return true if the NSEC proves the condition.
+ */
+ public static boolean nsecProvesNoWC(NSECRecord nsec, Name qname,
+ Name signerName) {
+ Name owner = nsec.getName();
+ Name next = nsec.getNext();
+
+ 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))) {
+ return true;
+ }
+ }
+
+ return false;
}
- return true;
- }
-
- public static int nsecProvesNoDS(NSECRecord nsec, Name qname)
- {
- // Could check to make sure the qname is a subdomain of nsec
- 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)
- // DS present means that there should have been a positive response to
- // the DS query, so there is something wrong.
- return SecurityStatus.BOGUS;
+ /**
+ * Determine if a NSEC proves the NOERROR/NODATA conditions. This will also
+ * handle the empty non-terminal (ENT) case and partially handle the
+ * 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
+ * The query name to check against.
+ * @param qtype
+ * The query type to check against.
+ * @return true if the NSEC proves the condition.
+ */
+ public static boolean nsecProvesNodata(NSECRecord nsec, Name qname,
+ int qtype) {
+ if (!nsec.getName().equals(qname)) {
+ // wildcard checking.
+
+ // If this is a wildcard NSEC, make sure that a) it was possible to
+ // have
+ // generated qname from the wildcard and b) the type map does not
+ // contain qtype. Note that this does NOT prove that this wildcard
+ // was
+ // the applicable wildcard.
+ if (nsec.getName().isWild()) {
+ // the is the purported closest encloser.
+ Name ce = new Name(nsec.getName(), 1);
+
+ // 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)) {
+ return false;
+ }
+ return true;
+ }
+
+ // empty-non-terminal checking.
+
+ // If the nsec is proving that qname is an ENT, the nsec owner will
+ // be
+ // less than qname, and the next name will be a child domain of the
+ // qname.
+ 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;
+ }
+
+ // If the qtype exists, then we should have gotten it.
+ if (typeMapHasType(nsec.getTypes(), qtype)) {
+ return false;
+ }
+
+ // if the name is a CNAME node, then we should have gotten the CNAME
+ if (typeMapHasType(nsec.getTypes(), Type.CNAME)) {
+ return false;
+ }
+
+ // If an NS set exists at this name, and NOT a SOA (so this is a zone
+ // cut,
+ // 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)) {
+ return false;
+ }
+
+ return true;
}
- if (!typeMapHasType(types, Type.NS))
- {
- // If there is no NS at this point at all, then this doesn't prove
- // anything one way or the other.
- return SecurityStatus.INSECURE;
+ public static int nsecProvesNoDS(NSECRecord nsec, Name qname) {
+ // Could check to make sure the qname is a subdomain of nsec
+ 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)
+ // DS present means that there should have been a positive response
+ // to
+ // the DS query, so there is something wrong.
+ return SecurityStatus.BOGUS;
+ }
+
+ if (!typeMapHasType(types, Type.NS)) {
+ // If there is no NS at this point at all, then this doesn't prove
+ // anything one way or the other.
+ return SecurityStatus.INSECURE;
+ }
+ // Otherwise, this proves no DS.
+ return SecurityStatus.SECURE;
}
- // Otherwise, this proves no DS.
- return SecurityStatus.SECURE;
- }
}