2 * Copyright (c) 2009 VeriSign, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 package com.versign.tat.dnssec;
31 import java.security.MessageDigest;
32 import java.security.NoSuchAlgorithmException;
33 import java.util.Iterator;
35 import org.xbill.DNS.*;
38 * This is a collection of routines encompassing the logic of validating
39 * different message types.
42 public class ValUtils {
44 // These are response subtypes. They are necessary for determining the
45 // validation strategy. They have no bearing on the iterative resolution
46 // algorithm, so they are confined here.
48 public enum ResponseType {
49 UNTYPED, // not sub typed yet
50 UNKNOWN, // not a recognized sub type
51 POSITIVE, // a positive response (no CNAME/DNAME chain)
52 CNAME, // a positive response with a CNAME/DNAME chain.
53 NODATA, // a NOERROR/NODATA response
54 NAMEERROR, // a NXDOMAIN response
55 ANY, // a response to a qtype=ANY query
57 // a referral response
59 // a throwaway response (i.e., an error)
62 // /** Not subtyped yet. */
63 // public static final int UNTYPED = 0;
65 // /** Not a recognized subtype. */
66 // public static final int UNKNOWN = 1;
68 // /** A postive, direct, response. */
69 // public static final int POSITIVE = 2;
71 // /** A postive response, with a CNAME/DNAME chain. */
72 // public static final int CNAME = 3;
74 // /** A NOERROR/NODATA response. */
75 // public static final int NODATA = 4;
77 // /** A NXDOMAIN response. */
78 // public static final int NAMEERROR = 5;
80 // /** A response to a qtype=ANY query. */
81 // public static final int ANY = 6;
83 /** A local copy of the verifier object. */
84 private DnsSecVerifier mVerifier;
86 public ValUtils(DnsSecVerifier verifier) {
91 * Given a response, classify ANSWER responses into a subtype.
94 * The response to classify.
96 * @return A subtype ranging from UNKNOWN to NAMEERROR.
98 public static ResponseType classifyResponse(SMessage m, Name zone) {
101 // Normal Name Error's are easy to detect -- but don't mistake a CNAME
102 // chain ending in NXDOMAIN.
103 if (m.getRcode() == Rcode.NXDOMAIN && m.getCount(Section.ANSWER) == 0) {
104 return ResponseType.NAMEERROR;
107 // If rcode isn't NXDOMAIN or NOERROR, it is a throwaway response.
108 if (m.getRcode() != Rcode.NOERROR) {
109 return ResponseType.THROWAWAY;
112 // Next is REFERRAL. These are distinguished by having:
113 // 1) nothing in the ANSWER section
114 // 2) an NS RRset in the AUTHORITY section that is a strict subdomain of
115 // 'zone' (the presumed queried zone).
116 if (zone != null && m.getCount(Section.ANSWER) == 0
117 && m.getCount(Section.AUTHORITY) > 0) {
118 rrsets = m.getSectionRRsets(Section.AUTHORITY);
119 for (int i = 0; i < rrsets.length; ++i) {
120 if (rrsets[i].getType() == Type.NS
121 && strictSubdomain(rrsets[i].getName(), zone)) {
122 return ResponseType.REFERRAL;
128 if (m.getCount(Section.ANSWER) == 0) {
129 return ResponseType.NODATA;
132 // We distinguish between CNAME response and other positive/negative
133 // responses because CNAME answers require extra processing.
134 int qtype = m.getQuestion().getType();
136 // We distinguish between ANY and CNAME or POSITIVE because ANY
137 // responses are validated differently.
138 if (qtype == Type.ANY) {
139 return ResponseType.ANY;
142 rrsets = m.getSectionRRsets(Section.ANSWER);
144 // Note that DNAMEs will be ignored here, unless qtype=DNAME. Unless
145 // qtype=CNAME, this will yield a CNAME response.
146 for (int i = 0; i < rrsets.length; i++) {
147 if (rrsets[i].getType() == qtype) return ResponseType.POSITIVE;
148 if (rrsets[i].getType() == Type.CNAME) return ResponseType.CNAME;
151 // st_log.warn("Failed to classify response message:\n" + m);
152 return ResponseType.UNKNOWN;
156 * Given a response, determine the name of the "signer". This is primarily
157 * to determine if the response is, in fact, signed at all, and, if so, what
158 * is the name of the most pertinent keyset.
161 * The response to analyze.
162 * @return a signer name, if the response is signed (even partially), or
163 * null if the response isn't signed.
165 public Name findSigner(SMessage m) {
166 // FIXME: this used to classify the message, then look in the pertinent
167 // section. Now we just find the first RRSIG in the ANSWER and AUTHORIY
170 for (int section = Section.ANSWER; section < Section.ADDITIONAL; ++section) {
171 SRRset[] rrsets = m.getSectionRRsets(section);
172 for (int i = 0; i < rrsets.length; ++i) {
173 Name signerName = rrsets[i].getSignerName();
174 if (signerName != null) return signerName;
182 * Given a DNSKEY record, generate the DS record from it.
185 * the DNSKEY record in question.
187 * The DS digest algorithm in use.
188 * @return the corresponding {@link org.xbill.DNS.DSRecord}
190 public static byte[] calculateDSHash(DNSKEYRecord keyrec, int ds_alg) {
191 DNSOutput os = new DNSOutput();
193 os.writeByteArray(keyrec.getName().toWireCanonical());
194 os.writeByteArray(keyrec.rdataToWireCanonical());
197 MessageDigest md = null;
199 case DSRecord.SHA1_DIGEST_ID:
200 md = MessageDigest.getInstance("SHA");
201 return md.digest(os.toByteArray());
202 case DSRecord.SHA256_DIGEST_ID:
203 md = MessageDigest.getInstance("SHA256");
204 return md.digest(os.toByteArray());
206 // st_log.warn("Unknown DS algorithm: " + ds_alg);
210 } catch (NoSuchAlgorithmException e) {
211 // st_log.error("Error using DS algorithm: " + ds_alg, e);
216 public static boolean supportsDigestID(int digest_id) {
217 if (digest_id == DSRecord.SHA1_DIGEST_ID) return true;
218 if (digest_id == DSRecord.SHA256_DIGEST_ID) return true;
223 * Check to see if a type is a special DNSSEC type.
228 * @return true if the type is one of the special DNSSEC types.
230 public static boolean isDNSSECType(int type) {
244 * Set the security status of a particular RRset. This will only upgrade the
248 * The SRRset to update.
250 * The security status.
252 public static void setRRsetSecurity(SRRset rrset, byte security) {
253 if (rrset == null) return;
255 int cur_sec = rrset.getSecurityStatus();
256 if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec) {
257 rrset.setSecurityStatus(security);
262 * Set the security status of a message and all of its RRsets. This will
263 * only upgrade the status of the message (i.e., set to more secure, not
264 * less) and all of the RRsets.
270 * SMessage m = response.getSMessage(); SRRset ans_rrset =
271 * m.findAnswerRRset(qname, qtype, qclass);
273 * ke = verifySRRset(ans_rrset, key_rrset); if
274 * (ans_rrset.getSecurityStatus() != SecurityStatus.SECURE) {
275 * return; } key_rrset = ke.getRRset();
277 public static void setMessageSecurity(SMessage m, byte security) {
278 if (m == null) return;
280 int cur_sec = m.getStatus();
281 if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec) {
282 m.setStatus(security);
285 for (int section = Section.ANSWER; section <= Section.ADDITIONAL; section++) {
286 SRRset[] rrsets = m.getSectionRRsets(section);
287 for (int i = 0; i < rrsets.length; i++) {
288 setRRsetSecurity(rrsets[i], security);
294 * Given an SRRset that is signed by a DNSKEY found in the key_rrset, verify
295 * it. This will return the status (either BOGUS or SECURE) and set that
299 * The SRRset to verify.
301 * The set of keys to verify against.
302 * @return The status (BOGUS or SECURE).
304 public byte verifySRRset(SRRset rrset, SRRset key_rrset) {
305 // String rrset_name = rrset.getName() + "/"
306 // + Type.string(rrset.getType()) + "/"
307 // + DClass.string(rrset.getDClass());
309 if (rrset.getSecurityStatus() == SecurityStatus.SECURE) {
310 // log.trace("verifySRRset: rrset <" + rrset_name
311 // + "> previously found to be SECURE");
312 return SecurityStatus.SECURE;
315 byte status = mVerifier.verify(rrset, key_rrset);
316 if (status != SecurityStatus.SECURE) {
317 // log.debug("verifySRRset: rrset <" + rrset_name +
318 // "> found to be BAD");
319 status = SecurityStatus.BOGUS;
323 // log.trace("verifySRRset: rrset <" + rrset_name +
324 // "> found to be SECURE");
327 rrset.setSecurityStatus(status);
332 * Determine if a given type map has a given type.
335 * The type map from the NSEC record.
337 * The type to look for.
338 * @return true if the type is present in the type map, false otherwise.
340 public static boolean typeMapHasType(int[] types, int type) {
341 for (int i = 0; i < types.length; i++) {
342 if (types[i] == type) return true;
347 @SuppressWarnings("unchecked")
348 public static RRSIGRecord rrsetFirstSig(RRset rrset) {
349 for (Iterator i = rrset.sigs(); i.hasNext();) {
350 return (RRSIGRecord) i.next();
356 * Finds the longest common name between two domain names.
362 public static Name longestCommonName(Name domain1, Name domain2) {
363 if (domain1 == null || domain2 == null) return null;
364 // for now, do this in a a fairly brute force way
365 // FIXME: convert this to direct operations on the byte[]
367 int d1_labels = domain1.labels();
368 int d2_labels = domain2.labels();
370 int l = (d1_labels < d2_labels) ? d1_labels : d2_labels;
371 for (int i = l; i > 0; i--) {
372 Name n1 = new Name(domain1, d1_labels - i);
373 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();
385 if (plabels >= clabels) return false;
387 Name n = new Name(child, clabels - plabels);
388 return parent.equals(n);
392 * Determine by looking at a signed RRset whether or not the rrset name was
393 * the result of a wildcard expansion.
396 * The rrset to examine.
397 * @return true if the rrset is a wildcard expansion. This will return false
398 * for all unsigned rrsets.
400 public static boolean rrsetIsWildcardExpansion(RRset rrset) {
401 if (rrset == null) return false;
402 RRSIGRecord rrsig = rrsetFirstSig(rrset);
404 if (rrset.getName().labels() - 1 > rrsig.getLabels()) {
412 * Determine by looking at a signed RRset whether or not the RRset name was
413 * the result of a wildcard expansion. If so, return the name of the
414 * generating wildcard.
417 * The rrset to check.
418 * @return the wildcard name, if the rrset was synthesized from a wildcard.
421 public static Name rrsetWildcard(RRset rrset) {
422 if (rrset == null) return null;
423 RRSIGRecord rrsig = rrsetFirstSig(rrset);
425 // if the RRSIG label count is shorter than the number of actual labels,
426 // then this rrset was synthesized from a wildcard.
427 // Note that the RRSIG label count doesn't count the root label.
428 int label_diff = (rrset.getName().labels() - 1) - rrsig.getLabels();
429 if (label_diff > 0) {
430 return rrset.getName().wild(label_diff);
435 public static Name closestEncloser(Name domain, NSECRecord nsec) {
436 Name n1 = longestCommonName(domain, nsec.getName());
437 Name n2 = longestCommonName(domain, nsec.getNext());
439 return (n1.labels() > n2.labels()) ? n1 : n2;
442 public static Name nsecWildcard(Name domain, NSECRecord nsec) {
444 return new Name("*", closestEncloser(domain, nsec));
445 } catch (TextParseException e) {
446 // this should never happen.
452 * Determine if the given NSEC proves a NameError (NXDOMAIN) for a given
458 * The qname to check against.
460 * The signer name of the NSEC record, which is used as the zone
461 * name, for a more precise (but perhaps more brittle) check for
462 * the last NSEC in a zone.
463 * @return true if the NSEC proves the condition.
465 public static boolean nsecProvesNameError(NSECRecord nsec, Name qname,
467 Name owner = nsec.getName();
468 Name next = nsec.getNext();
470 // If NSEC owner == qname, then this NSEC proves that qname exists.
471 if (qname.equals(owner)) {
475 // If NSEC is a parent of qname, we need to check the type map
476 // If the parent name has a DNAME or is a delegation point, then this
477 // NSEC is being misused.
478 boolean hasBadType = typeMapHasType(nsec.getTypes(), Type.DNAME)
479 || (typeMapHasType(nsec.getTypes(), Type.NS) && !typeMapHasType(nsec.getTypes(),
481 if (qname.subdomain(owner) && hasBadType) {
485 if (qname.compareTo(owner) > 0 && (qname.compareTo(next) < 0)
486 || signerName.equals(next)) {
493 * Determine if a NSEC record proves the non-existence of a wildcard that
494 * could have produced qname.
499 * The qname to check against.
501 * The signer name for the NSEC rrset, used as the zone name.
502 * @return true if the NSEC proves the condition.
504 public static boolean nsecProvesNoWC(NSECRecord nsec, Name qname,
506 Name owner = nsec.getName();
507 Name next = nsec.getNext();
509 int qname_labels = qname.labels();
510 int signer_labels = signerName.labels();
512 for (int i = qname_labels - signer_labels; i > 0; i--) {
513 Name wc_name = qname.wild(i);
514 if (wc_name.compareTo(owner) > 0
515 && (wc_name.compareTo(next) < 0 || signerName.equals(next))) {
524 * Determine if a NSEC proves the NOERROR/NODATA conditions. This will also
525 * handle the empty non-terminal (ENT) case and partially handle the
526 * wildcard case. If the ownername of 'nsec' is a wildcard, the validator
527 * must still be provided proof that qname did not directly exist and that
528 * the wildcard is, in fact, *.closest_encloser.
533 * The query name to check against.
535 * The query type to check against.
536 * @return true if the NSEC proves the condition.
538 public static boolean nsecProvesNodata(NSECRecord nsec, Name qname,
540 if (!nsec.getName().equals(qname)) {
541 // wildcard checking.
543 // If this is a wildcard NSEC, make sure that a) it was possible to
545 // generated qname from the wildcard and b) the type map does not
546 // contain qtype. Note that this does NOT prove that this wildcard
548 // the applicable wildcard.
549 if (nsec.getName().isWild()) {
550 // the is the purported closest encloser.
551 Name ce = new Name(nsec.getName(), 1);
553 // The qname must be a strict subdomain of the closest encloser,
555 // the qtype must be absent from the type map.
556 if (!strictSubdomain(qname, ce)
557 || typeMapHasType(nsec.getTypes(), qtype)) {
563 // empty-non-terminal checking.
565 // If the nsec is proving that qname is an ENT, the nsec owner will
567 // less than qname, and the next name will be a child domain of the
569 if (strictSubdomain(nsec.getNext(), qname)
570 && qname.compareTo(nsec.getName()) > 0) {
573 // Otherwise, this NSEC does not prove ENT, so it does not prove
578 // If the qtype exists, then we should have gotten it.
579 if (typeMapHasType(nsec.getTypes(), qtype)) {
583 // if the name is a CNAME node, then we should have gotten the CNAME
584 if (typeMapHasType(nsec.getTypes(), Type.CNAME)) {
588 // If an NS set exists at this name, and NOT a SOA (so this is a zone
590 // not a zone apex), then we should have gotten a referral (or we just
593 if (typeMapHasType(nsec.getTypes(), Type.NS)
594 && !typeMapHasType(nsec.getTypes(), Type.SOA)) {
601 public static int nsecProvesNoDS(NSECRecord nsec, Name qname) {
602 // Could check to make sure the qname is a subdomain of nsec
603 int[] types = nsec.getTypes();
604 if (typeMapHasType(types, Type.SOA) || typeMapHasType(types, Type.DS)) {
605 // SOA present means that this is the NSEC from the child, not the
606 // parent (so it is the wrong one)
607 // DS present means that there should have been a positive response
609 // the DS query, so there is something wrong.
610 return SecurityStatus.BOGUS;
613 if (!typeMapHasType(types, Type.NS)) {
614 // If there is no NS at this point at all, then this doesn't prove
615 // anything one way or the other.
616 return SecurityStatus.INSECURE;
618 // Otherwise, this proves no DS.
619 return SecurityStatus.SECURE;