1 /***************************** -*- Java -*- ********************************\
3 * Copyright (c) 2009 VeriSign, Inc. All rights reserved. *
5 * This software is provided solely in connection with the terms of the *
6 * license agreement. Any other use without the prior express written *
7 * permission of VeriSign is completely prohibited. The software and *
8 * documentation are "Commercial Items", as that term is defined in 48 *
9 * C.F.R. section 2.101, consisting of "Commercial Computer Software" and *
10 * "Commercial Computer Software Documentation" as such terms are defined *
11 * in 48 C.F.R. section 252.227-7014(a)(5) and 48 C.F.R. section *
12 * 252.227-7014(a)(1), and used in 48 C.F.R. section 12.212 and 48 C.F.R. *
13 * section 227.7202, as applicable. Pursuant to the above and other *
14 * relevant sections of the Code of Federal Regulations, as applicable, *
15 * VeriSign's publications, commercial computer software, and commercial *
16 * computer software documentation are distributed and licensed to United *
17 * States Government end users with only those rights as granted to all *
18 * other end users, according to the terms and conditions contained in the *
19 * license agreement(s) that accompany the products and software *
22 \***************************************************************************/
24 package com.verisign.tat.dnssec;
26 import org.apache.log4j.Logger;
28 import org.xbill.DNS.*;
30 import java.security.MessageDigest;
31 import java.security.NoSuchAlgorithmException;
33 import java.util.Iterator;
36 * This is a collection of routines encompassing the logic of validating
37 * different message types.
39 public class ValUtils {
40 private static Logger st_log = Logger.getLogger(ValUtils.class);
41 private Logger log = Logger.getLogger(this.getClass());
43 /** A local copy of the verifier object. */
44 private DnsSecVerifier mVerifier;
46 public ValUtils(DnsSecVerifier verifier) {
51 * Given a response, classify ANSWER responses into a subtype.
54 * The response to classify.
56 * @return A subtype ranging from UNKNOWN to NAMEERROR.
58 public static ResponseType classifyResponse(SMessage m, Name zone) {
61 // Normal Name Error's are easy to detect -- but don't mistake a CNAME
62 // chain ending in NXDOMAIN.
63 if ((m.getRcode() == Rcode.NXDOMAIN)
64 && (m.getCount(Section.ANSWER) == 0)) {
65 return ResponseType.NAMEERROR;
68 // If rcode isn't NXDOMAIN or NOERROR, it is a throwaway response.
69 if (m.getRcode() != Rcode.NOERROR) {
70 return ResponseType.THROWAWAY;
73 // Next is REFERRAL. These are distinguished by having:
74 // 1) nothing in the ANSWER section
75 // 2) an NS RRset in the AUTHORITY section that is a strict subdomain of
76 // 'zone' (the presumed queried zone).
77 if ((zone != null) && (m.getCount(Section.ANSWER) == 0)
78 && (m.getCount(Section.AUTHORITY) > 0)) {
79 rrsets = m.getSectionRRsets(Section.AUTHORITY);
81 for (int i = 0; i < rrsets.length; ++i) {
82 if ((rrsets[i].getType() == Type.NS)
83 && strictSubdomain(rrsets[i].getName(), zone)) {
84 return ResponseType.REFERRAL;
90 if (m.getCount(Section.ANSWER) == 0) {
91 return ResponseType.NODATA;
94 // We distinguish between CNAME response and other positive/negative
95 // responses because CNAME answers require extra processing.
96 int qtype = m.getQuestion().getType();
98 // We distinguish between ANY and CNAME or POSITIVE because ANY
99 // responses are validated differently.
100 if (qtype == Type.ANY) {
101 return ResponseType.ANY;
104 rrsets = m.getSectionRRsets(Section.ANSWER);
106 // Note that DNAMEs will be ignored here, unless qtype=DNAME. Unless
107 // qtype=CNAME, this will yield a CNAME response.
108 for (int i = 0; i < rrsets.length; i++) {
109 if (rrsets[i].getType() == qtype) {
110 return ResponseType.POSITIVE;
113 if (rrsets[i].getType() == Type.CNAME) {
114 return ResponseType.CNAME;
118 st_log.warn("Failed to classify response message:\n" + m);
120 return ResponseType.UNKNOWN;
124 * Given a response, determine the name of the "signer". This is primarily
125 * to determine if the response is, in fact, signed at all, and, if so, what
126 * is the name of the most pertinent keyset.
129 * The response to analyze.
130 * @return a signer name, if the response is signed (even partially), or
131 * null if the response isn't signed.
133 public Name findSigner(SMessage m) {
134 // FIXME: this used to classify the message, then look in the pertinent
135 // section. Now we just find the first RRSIG in the ANSWER and AUTHORIY
137 for (int section = Section.ANSWER; section < Section.ADDITIONAL; ++section) {
138 SRRset[] rrsets = m.getSectionRRsets(section);
140 for (int i = 0; i < rrsets.length; ++i) {
141 Name signerName = rrsets[i].getSignerName();
143 if (signerName != null) {
153 * Given a DNSKEY record, generate the DS record from it.
156 * the DNSKEY record in question.
158 * The DS digest algorithm in use.
159 * @return the corresponding {@link org.xbill.DNS.DSRecord}
161 public static byte[] calculateDSHash(DNSKEYRecord keyrec, int ds_alg) {
162 DNSOutput os = new DNSOutput();
164 os.writeByteArray(keyrec.getName().toWireCanonical());
165 os.writeByteArray(keyrec.rdataToWireCanonical());
168 MessageDigest md = null;
171 case DSRecord.SHA1_DIGEST_ID:
172 md = MessageDigest.getInstance("SHA");
174 return md.digest(os.toByteArray());
176 case DSRecord.SHA256_DIGEST_ID:
177 md = MessageDigest.getInstance("SHA256");
179 return md.digest(os.toByteArray());
182 st_log.warn("Unknown DS algorithm: " + ds_alg);
186 } catch (NoSuchAlgorithmException e) {
187 st_log.error("Error using DS algorithm: " + ds_alg, e);
193 public static boolean supportsDigestID(int digest_id) {
194 if (digest_id == DSRecord.SHA1_DIGEST_ID) {
198 if (digest_id == DSRecord.SHA256_DIGEST_ID) {
206 * Check to see if a type is a special DNSSEC type.
211 * @return true if the type is one of the special DNSSEC types.
213 public static boolean isDNSSECType(int type) {
228 * Set the security status of a particular RRset. This will only upgrade the
232 * The SRRset to update.
234 * The security status.
236 public static void setRRsetSecurity(SRRset rrset, byte security) {
241 int cur_sec = rrset.getSecurityStatus();
243 if ((cur_sec == SecurityStatus.UNCHECKED) || (security > cur_sec)) {
244 rrset.setSecurityStatus(security);
249 * Set the security status of a message and all of its RRsets. This will
250 * only upgrade the status of the message (i.e., set to more secure, not
251 * less) and all of the RRsets.
257 * SMessage m = response.getSMessage(); SRRset ans_rrset =
258 * m.findAnswerRRset(qname, qtype, qclass);
260 * ke = verifySRRset(ans_rrset, key_rrset); if
261 * (ans_rrset.getSecurityStatus() != SecurityStatus.SECURE) {
262 * return; } key_rrset = ke.getRRset();
264 public static void setMessageSecurity(SMessage m, byte security) {
269 int cur_sec = m.getStatus();
271 if ((cur_sec == SecurityStatus.UNCHECKED) || (security > cur_sec)) {
272 m.setStatus(security);
275 for (int section = Section.ANSWER; section <= Section.ADDITIONAL; section++) {
276 SRRset[] rrsets = m.getSectionRRsets(section);
278 for (int i = 0; i < rrsets.length; i++) {
279 setRRsetSecurity(rrsets[i], security);
285 * Given an SRRset that is signed by a DNSKEY found in the key_rrset, verify
286 * it. This will return the status (either BOGUS or SECURE) and set that
290 * The SRRset to verify.
292 * The set of keys to verify against.
293 * @return The status (BOGUS or SECURE).
295 public byte verifySRRset(SRRset rrset, SRRset key_rrset) {
296 String rrset_name = rrset.getName() + "/"
297 + Type.string(rrset.getType()) + "/"
298 + DClass.string(rrset.getDClass());
300 if (rrset.getSecurityStatus() == SecurityStatus.SECURE) {
301 log.trace("verifySRRset: rrset <" + rrset_name
302 + "> previously found to be SECURE");
304 return SecurityStatus.SECURE;
307 byte status = mVerifier.verify(rrset, key_rrset);
309 if (status != SecurityStatus.SECURE) {
310 log.debug("verifySRRset: rrset <" + rrset_name
311 + "> found to be BAD");
312 status = SecurityStatus.BOGUS;
314 log.trace("verifySRRset: rrset <" + rrset_name
315 + "> found to be SECURE");
318 rrset.setSecurityStatus(status);
324 * Determine if a given type map has a given type.
327 * The type map from the NSEC record.
329 * The type to look for.
330 * @return true if the type is present in the type map, false otherwise.
332 public static boolean typeMapHasType(int[] types, int type) {
333 for (int i = 0; i < types.length; i++) {
334 if (types[i] == type) {
342 @SuppressWarnings("unchecked")
343 public static RRSIGRecord rrsetFirstSig(RRset rrset) {
344 for (Iterator i = rrset.sigs(); i.hasNext();) {
345 return (RRSIGRecord) i.next();
352 * Finds the longest common name between two domain names.
358 public static Name longestCommonName(Name domain1, Name domain2) {
359 if ((domain1 == null) || (domain2 == null)) {
363 // for now, do this in a a fairly brute force way
364 // FIXME: convert this to direct operations on the byte[]
365 int d1_labels = domain1.labels();
366 int d2_labels = domain2.labels();
368 int l = (d1_labels < d2_labels) ? d1_labels : d2_labels;
370 for (int i = l; i > 0; i--) {
371 Name n1 = new Name(domain1, d1_labels - i);
372 Name n2 = new Name(domain2, d2_labels - i);
382 public static boolean strictSubdomain(Name child, Name parent) {
383 int clabels = child.labels();
384 int plabels = parent.labels();
386 if (plabels >= clabels) {
390 Name n = new Name(child, clabels - plabels);
392 return parent.equals(n);
396 * Determine by looking at a signed RRset whether or not the rrset name was
397 * the result of a wildcard expansion.
400 * The rrset to examine.
401 * @return true if the rrset is a wildcard expansion. This will return false
402 * for all unsigned rrsets.
404 public static boolean rrsetIsWildcardExpansion(RRset rrset) {
409 RRSIGRecord rrsig = rrsetFirstSig(rrset);
411 if ((rrset.getName().labels() - 1) > rrsig.getLabels()) {
419 * Determine by looking at a signed RRset whether or not the RRset name was
420 * the result of a wildcard expansion. If so, return the name of the
421 * generating wildcard.
424 * The rrset to check.
425 * @return the wildcard name, if the rrset was synthesized from a wildcard.
428 public static Name rrsetWildcard(RRset rrset) {
433 RRSIGRecord rrsig = rrsetFirstSig(rrset);
435 // if the RRSIG label count is shorter than the number of actual labels,
436 // then this rrset was synthesized from a wildcard.
437 // Note that the RRSIG label count doesn't count the root label.
438 int label_diff = (rrset.getName().labels() - 1) - rrsig.getLabels();
440 if (label_diff > 0) {
441 return rrset.getName().wild(label_diff);
447 public static Name closestEncloser(Name domain, NSECRecord nsec) {
448 Name n1 = longestCommonName(domain, nsec.getName());
449 Name n2 = longestCommonName(domain, nsec.getNext());
451 return (n1.labels() > n2.labels()) ? n1 : n2;
454 public static Name nsecWildcard(Name domain, NSECRecord nsec) {
456 return new Name("*", closestEncloser(domain, nsec));
457 } catch (TextParseException e) {
458 // this should never happen.
464 * Determine if the given NSEC proves a NameError (NXDOMAIN) for a given
470 * The qname to check against.
472 * The signer name of the NSEC record, which is used as the zone
473 * name, for a more precise (but perhaps more brittle) check for
474 * the last NSEC in a zone.
475 * @return true if the NSEC proves the condition.
477 public static boolean nsecProvesNameError(NSECRecord nsec, Name qname,
479 Name owner = nsec.getName();
480 Name next = nsec.getNext();
482 // If NSEC owner == qname, then this NSEC proves that qname exists.
483 if (qname.equals(owner)) {
487 // If NSEC is a parent of qname, we need to check the type map
488 // If the parent name has a DNAME or is a delegation point, then this
489 // NSEC is being misused.
490 boolean hasBadType = typeMapHasType(nsec.getTypes(), Type.DNAME)
491 || (typeMapHasType(nsec.getTypes(), Type.NS) && !typeMapHasType(
492 nsec.getTypes(), Type.SOA));
494 if (qname.subdomain(owner) && hasBadType) {
498 if (((qname.compareTo(owner) > 0) && (qname.compareTo(next) < 0))
499 || signerName.equals(next)) {
507 * Determine if a NSEC record proves the non-existence of a wildcard that
508 * could have produced qname.
513 * The qname to check against.
515 * The signer name for the NSEC rrset, used as the zone name.
516 * @return true if the NSEC proves the condition.
518 public static boolean nsecProvesNoWC(NSECRecord nsec, Name qname,
520 Name owner = nsec.getName();
521 Name next = nsec.getNext();
523 int qname_labels = qname.labels();
524 int signer_labels = signerName.labels();
526 for (int i = qname_labels - signer_labels; i > 0; i--) {
527 Name wc_name = qname.wild(i);
529 if ((wc_name.compareTo(owner) > 0)
530 && ((wc_name.compareTo(next) < 0) || signerName
540 * Determine if a NSEC proves the NOERROR/NODATA conditions. This will also
541 * handle the empty non-terminal (ENT) case and partially handle the
542 * wildcard case. If the ownername of 'nsec' is a wildcard, the validator
543 * must still be provided proof that qname did not directly exist and that
544 * the wildcard is, in fact, *.closest_encloser.
549 * The query name to check against.
551 * The query type to check against.
552 * @return true if the NSEC proves the condition.
554 public static boolean nsecProvesNodata(NSECRecord nsec, Name qname,
556 if (!nsec.getName().equals(qname)) {
557 // wildcard checking.
559 // If this is a wildcard NSEC, make sure that a) it was possible to
561 // generated qname from the wildcard and b) the type map does not
562 // contain qtype. Note that this does NOT prove that this wildcard
564 // the applicable wildcard.
565 if (nsec.getName().isWild()) {
566 // the is the purported closest encloser.
567 Name ce = new Name(nsec.getName(), 1);
569 // The qname must be a strict subdomain of the closest encloser,
571 // the qtype must be absent from the type map.
572 if (!strictSubdomain(qname, ce)
573 || typeMapHasType(nsec.getTypes(), qtype)) {
580 // empty-non-terminal checking.
582 // If the nsec is proving that qname is an ENT, the nsec owner will
584 // less than qname, and the next name will be a child domain of the
586 if (strictSubdomain(nsec.getNext(), qname)
587 && (qname.compareTo(nsec.getName()) > 0)) {
591 // Otherwise, this NSEC does not prove ENT, so it does not prove
596 // If the qtype exists, then we should have gotten it.
597 if (typeMapHasType(nsec.getTypes(), qtype)) {
601 // if the name is a CNAME node, then we should have gotten the CNAME
602 if (typeMapHasType(nsec.getTypes(), Type.CNAME)) {
606 // If an NS set exists at this name, and NOT a SOA (so this is a zone
608 // not a zone apex), then we should have gotten a referral (or we just
611 if (typeMapHasType(nsec.getTypes(), Type.NS)
612 && !typeMapHasType(nsec.getTypes(), Type.SOA)) {
619 public static byte nsecProvesNoDS(NSECRecord nsec, Name qname) {
620 // Could check to make sure the qname is a subdomain of nsec
621 int[] types = nsec.getTypes();
623 if (typeMapHasType(types, Type.SOA) || typeMapHasType(types, Type.DS)) {
624 // SOA present means that this is the NSEC from the child, not the
625 // parent (so it is the wrong one)
626 // DS present means that there should have been a positive response
628 // the DS query, so there is something wrong.
629 return SecurityStatus.BOGUS;
632 if (!typeMapHasType(types, Type.NS)) {
633 // If there is no NS at this point at all, then this doesn't prove
634 // anything one way or the other.
635 return SecurityStatus.INSECURE;
638 // Otherwise, this proves no DS.
639 return SecurityStatus.SECURE;
642 // These are response subtypes. They are necessary for determining the
643 // validation strategy. They have no bearing on the iterative resolution
644 // algorithm, so they are confined here.
645 public enum ResponseType {
646 UNTYPED, UNKNOWN, POSITIVE, CNAME, NODATA, NAMEERROR, ANY, REFERRAL,
647 // a referral response
649 // a throwaway response (i.e., an error)