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 /** Not subtyped yet. */
49 public static final int UNTYPED = 0;
51 /** Not a recognized subtype. */
52 public static final int UNKNOWN = 1;
54 /** A postive, direct, response. */
55 public static final int POSITIVE = 2;
57 /** A postive response, with a CNAME/DNAME chain. */
58 public static final int CNAME = 3;
60 /** A NOERROR/NODATA response. */
61 public static final int NODATA = 4;
63 /** A NXDOMAIN response. */
64 public static final int NAMEERROR = 5;
66 /** A response to a qtype=ANY query. */
67 public static final int ANY = 6;
69 /** A local copy of the verifier object. */
70 private DnsSecVerifier mVerifier;
72 public ValUtils(DnsSecVerifier verifier) {
77 * Given a response, classify ANSWER responses into a subtype.
80 * The response to classify.
82 * @return A subtype ranging from UNKNOWN to NAMEERROR.
84 public static int classifyResponse(SMessage m) {
85 // Normal Name Error's are easy to detect -- but don't mistake a CNAME
86 // chain ending in NXDOMAIN.
87 if (m.getRcode() == Rcode.NXDOMAIN && m.getCount(Section.ANSWER) == 0) {
92 // st_log.debug("classifyResponse: ancount = " +
93 // m.getCount(Section.ANSWER));
94 if (m.getCount(Section.ANSWER) == 0) {
98 // We distinguish between CNAME response and other positive/negative
99 // responses because CNAME answers require extra processing.
100 int qtype = m.getQuestion().getType();
102 // We distinguish between ANY and CNAME or POSITIVE because ANY
104 // are validated differently.
105 if (qtype == Type.ANY) {
109 SRRset[] rrsets = m.getSectionRRsets(Section.ANSWER);
111 // Note that DNAMEs will be ignored here, unless qtype=DNAME. Unless
112 // qtype=CNAME, this will yield a CNAME response.
113 for (int i = 0; i < rrsets.length; i++) {
114 if (rrsets[i].getType() == qtype) return POSITIVE;
115 if (rrsets[i].getType() == Type.CNAME) return CNAME;
118 // st_log.warn("Failed to classify response message:\n" + m);
123 * Given a response, determine the name of the "signer". This is primarily
124 * to determine if the response is, in fact, signed at all, and, if so, what
125 * is the name of the most pertinent keyset.
128 * The response to analyze.
130 * The request that generated the response.
131 * @return a signer name, if the response is signed (even partially), or
132 * null if the response isn't signed.
134 public Name findSigner(SMessage m) {
135 int subtype = classifyResponse(m);
136 Name qname = m.getQName();
144 // Check to see if the ANSWER section RRset
145 rrsets = m.getSectionRRsets(Section.ANSWER);
146 for (int i = 0; i < rrsets.length; i++) {
147 if (rrsets[i].getName().equals(qname)) {
148 return rrsets[i].getSignerName();
155 // Check to see if the AUTH section NSEC record(s) have rrsigs
156 rrsets = m.getSectionRRsets(Section.AUTHORITY);
157 for (int i = 0; i < rrsets.length; i++) {
158 if (rrsets[i].getType() == Type.NSEC
159 || rrsets[i].getType() == Type.NSEC3) {
160 return rrsets[i].getSignerName();
165 // log.debug("findSigner: could not find signer name "
166 // + "for unknown type response.");
171 public boolean dssetIsUsable(SRRset ds_rrset) {
172 for (Iterator i = ds_rrset.rrs(); i.hasNext();) {
173 DSRecord ds = (DSRecord) i.next();
174 if (supportsDigestID(ds.getDigestID())
175 && mVerifier.supportsAlgorithm(ds.getAlgorithm())) {
184 * Given a DS rrset and a DNSKEY rrset, match the DS to a DNSKEY and verify
185 * the DNSKEY rrset with that key.
187 * @param dnskey_rrset
188 * The DNSKEY rrset to match against. The security status of this
189 * rrset will be updated on a successful verification.
191 * The DS rrset to match with. This rrset must already be
194 * @return a KeyEntry. This will either contain the now trusted
195 * dnskey_rrset, a "null" key entry indicating that this DS
196 * rrset/DNSKEY pair indicate an secure end to the island of trust
197 * (i.e., unknown algorithms), or a "bad" KeyEntry if the dnskey
198 * rrset fails to verify. Note that the "null" response should
199 * generally only occur in a private algorithm scenario: normally
200 * this sort of thing is checked before fetching the matching DNSKEY
203 // public KeyEntry verifyNewDNSKEYs(SRRset dnskey_rrset, SRRset ds_rrset)
205 // if (!dnskey_rrset.getName().equals(ds_rrset.getName()))
207 // // log.debug("DNSKEY RRset did not match DS RRset by name!");
209 // .newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass());
212 // // as long as this is false, we can consider this DS rrset to be
213 // // equivalent to no DS rrset.
214 // boolean hasUsefulDS = false;
216 // for (Iterator i = ds_rrset.rrs(); i.hasNext();)
218 // DSRecord ds = (DSRecord) i.next();
220 // // Check to see if we can understand this DS.
221 // if (!supportsDigestID(ds.getDigestID())
222 // || !mVerifier.supportsAlgorithm(ds.getAlgorithm()))
227 // // Once we see a single DS with a known digestID and algorithm, we
228 // // cannot return INSECURE (with a "null" KeyEntry).
229 // hasUsefulDS = true;
231 // DNSKEY : for (Iterator j = dnskey_rrset.rrs(); j.hasNext();)
233 // DNSKEYRecord dnskey = (DNSKEYRecord) j.next();
235 // // Skip DNSKEYs that don't match the basic criteria.
236 // if (ds.getFootprint() != dnskey.getFootprint()
237 // || ds.getAlgorithm() != dnskey.getAlgorithm())
242 // // Convert the candidate DNSKEY into a hash using the same DS hash
244 // byte[] key_hash = calculateDSHash(dnskey, ds.getDigestID());
245 // byte[] ds_hash = ds.getDigest();
247 // // see if there is a length mismatch (unlikely)
248 // if (key_hash.length != ds_hash.length)
253 // for (int k = 0; k < key_hash.length; k++)
255 // if (key_hash[k] != ds_hash[k]) continue DNSKEY;
258 // // Otherwise, we have a match! Make sure that the DNSKEY verifies
259 // // *with this key*.
260 // byte res = mVerifier.verify(dnskey_rrset, dnskey);
261 // if (res == SecurityStatus.SECURE)
263 // // log.trace("DS matched DNSKEY.");
264 // dnskey_rrset.setSecurityStatus(SecurityStatus.SECURE);
265 // return KeyEntry.newKeyEntry(dnskey_rrset);
267 // // If it didn't validate with the DNSKEY, try the next one!
271 // // None of the DS's worked out.
273 // // If no DSs were understandable, then this is OK.
277 // log.debug("No usuable DS records were found -- treating as insecure.");
278 // return KeyEntry.newNullKeyEntry(ds_rrset.getName(), ds_rrset
279 // .getDClass(), ds_rrset.getTTL());
281 // // If any were understandable, then it is bad.
282 // // log.debug("Failed to match any usable DS to a DNSKEY.");
283 // return KeyEntry.newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass());
286 * Given a DNSKEY record, generate the DS record from it.
289 * the DNSKEY record in question.
291 * The DS digest algorithm in use.
292 * @return the corresponding {@link org.xbill.DNS.DSRecord}
294 public static byte[] calculateDSHash(DNSKEYRecord keyrec, int ds_alg) {
295 DNSOutput os = new DNSOutput();
297 os.writeByteArray(keyrec.getName().toWireCanonical());
298 os.writeByteArray(keyrec.rdataToWireCanonical());
301 MessageDigest md = null;
303 case DSRecord.SHA1_DIGEST_ID:
304 md = MessageDigest.getInstance("SHA");
305 return md.digest(os.toByteArray());
306 case DSRecord.SHA256_DIGEST_ID:
307 md = MessageDigest.getInstance("SHA256");
308 return md.digest(os.toByteArray());
310 // st_log.warn("Unknown DS algorithm: " + ds_alg);
314 } catch (NoSuchAlgorithmException e) {
315 // st_log.error("Error using DS algorithm: " + ds_alg, e);
320 public static boolean supportsDigestID(int digest_id) {
321 if (digest_id == DSRecord.SHA1_DIGEST_ID) return true;
322 if (digest_id == DSRecord.SHA256_DIGEST_ID) return true;
327 * Check to see if a type is a special DNSSEC type.
332 * @return true if the type is one of the special DNSSEC types.
334 public static boolean isDNSSECType(int type) {
348 * Set the security status of a particular RRset. This will only upgrade the
352 * The SRRset to update.
354 * The security status.
356 public static void setRRsetSecurity(SRRset rrset, byte security) {
357 if (rrset == null) return;
359 int cur_sec = rrset.getSecurityStatus();
360 if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec) {
361 rrset.setSecurityStatus(security);
366 * Set the security status of a message and all of its RRsets. This will
367 * only upgrade the status of the message (i.e., set to more secure, not
368 * less) and all of the RRsets.
374 * SMessage m = response.getSMessage(); SRRset ans_rrset =
375 * m.findAnswerRRset(qname, qtype, qclass);
377 * ke = verifySRRset(ans_rrset, key_rrset); if
378 * (ans_rrset.getSecurityStatus() != SecurityStatus.SECURE) {
379 * return; } key_rrset = ke.getRRset();
381 public static void setMessageSecurity(SMessage m, byte security) {
382 if (m == null) return;
384 int cur_sec = m.getStatus();
385 if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec) {
386 m.setStatus(security);
389 for (int section = Section.ANSWER; section <= Section.ADDITIONAL; section++) {
390 SRRset[] rrsets = m.getSectionRRsets(section);
391 for (int i = 0; i < rrsets.length; i++) {
392 setRRsetSecurity(rrsets[i], security);
398 * Given an SRRset that is signed by a DNSKEY found in the key_rrset, verify
399 * it. This will return the status (either BOGUS or SECURE) and set that
403 * The SRRset to verify.
405 * The set of keys to verify against.
406 * @return The status (BOGUS or SECURE).
408 public byte verifySRRset(SRRset rrset, SRRset key_rrset) {
409 String rrset_name = rrset.getName() + "/"
410 + Type.string(rrset.getType()) + "/"
411 + DClass.string(rrset.getDClass());
413 if (rrset.getSecurityStatus() == SecurityStatus.SECURE) {
414 // log.trace("verifySRRset: rrset <" + rrset_name
415 // + "> previously found to be SECURE");
416 return SecurityStatus.SECURE;
419 byte status = mVerifier.verify(rrset, key_rrset);
420 if (status != SecurityStatus.SECURE) {
421 // log.debug("verifySRRset: rrset <" + rrset_name +
422 // "> found to be BAD");
423 status = SecurityStatus.BOGUS;
427 // log.trace("verifySRRset: rrset <" + rrset_name +
428 // "> found to be SECURE");
431 rrset.setSecurityStatus(status);
436 * Determine if a given type map has a given typ.
439 * The type map from the NSEC record.
441 * The type to look for.
442 * @return true if the type is present in the type map, false otherwise.
444 public static boolean typeMapHasType(int[] types, int type) {
445 for (int i = 0; i < types.length; i++) {
446 if (types[i] == type) return true;
451 public static RRSIGRecord rrsetFirstSig(RRset rrset) {
452 for (Iterator i = rrset.sigs(); i.hasNext();) {
453 return (RRSIGRecord) i.next();
459 * Finds the longest common name between two domain names.
465 public static Name longestCommonName(Name domain1, Name domain2) {
466 if (domain1 == null || domain2 == null) return null;
467 // for now, do this in a a fairly brute force way
468 // FIXME: convert this to direct operations on the byte[]
470 int d1_labels = domain1.labels();
471 int d2_labels = domain2.labels();
473 int l = (d1_labels < d2_labels) ? d1_labels : d2_labels;
474 for (int i = l; i > 0; i--) {
475 Name n1 = new Name(domain1, d1_labels - i);
476 Name n2 = new Name(domain2, d2_labels - i);
485 public static boolean strictSubdomain(Name child, Name parent) {
486 int clabels = child.labels();
487 int plabels = parent.labels();
488 if (plabels >= clabels) return false;
490 Name n = new Name(child, clabels - plabels);
491 return parent.equals(n);
495 * Determine by looking at a signed RRset whether or not the rrset name was
496 * the result of a wildcard expansion.
499 * The rrset to examine.
500 * @return true if the rrset is a wildcard expansion. This will return false
501 * for all unsigned rrsets.
503 public static boolean rrsetIsWildcardExpansion(RRset rrset) {
504 if (rrset == null) return false;
505 RRSIGRecord rrsig = rrsetFirstSig(rrset);
507 if (rrset.getName().labels() - 1 > rrsig.getLabels()) {
515 * Determine by looking at a signed RRset whether or not the RRset name was
516 * the result of a wildcard expansion. If so, return the name of the
517 * generating wildcard.
520 * The rrset to chedck.
521 * @return the wildcard name, if the rrset was synthesized from a wildcard.
524 public static Name rrsetWildcard(RRset rrset) {
525 if (rrset == null) return null;
526 RRSIGRecord rrsig = rrsetFirstSig(rrset);
528 // if the RRSIG label count is shorter than the number of actual labels,
529 // then this rrset was synthesized from a wildcard.
530 // Note that the RRSIG label count doesn't count the root label.
531 int label_diff = (rrset.getName().labels() - 1) - rrsig.getLabels();
532 if (label_diff > 0) {
533 return rrset.getName().wild(label_diff);
538 public static Name closestEncloser(Name domain, NSECRecord nsec) {
539 Name n1 = longestCommonName(domain, nsec.getName());
540 Name n2 = longestCommonName(domain, nsec.getNext());
542 return (n1.labels() > n2.labels()) ? n1 : n2;
545 public static Name nsecWildcard(Name domain, NSECRecord nsec) {
547 return new Name("*", closestEncloser(domain, nsec));
548 } catch (TextParseException e) {
549 // this should never happen.
555 * Determine if the given NSEC proves a NameError (NXDOMAIN) for a given
561 * The qname to check against.
563 * The signer name of the NSEC record, which is used as the zone
564 * name, for a more precise (but perhaps more brittle) check for
565 * the last NSEC in a zone.
566 * @return true if the NSEC proves the condition.
568 public static boolean nsecProvesNameError(NSECRecord nsec, Name qname,
570 Name owner = nsec.getName();
571 Name next = nsec.getNext();
573 // If NSEC owner == qname, then this NSEC proves that qname exists.
574 if (qname.equals(owner)) {
578 // If NSEC is a parent of qname, we need to check the type map
579 // If the parent name has a DNAME or is a delegation point, then this
582 if (qname.subdomain(owner)
583 && (typeMapHasType(nsec.getTypes(), Type.DNAME) || (typeMapHasType(
585 Type.NS) && !typeMapHasType(
591 if (qname.compareTo(owner) > 0 && (qname.compareTo(next) < 0)
592 || signerName.equals(next)) {
599 * Determine if a NSEC record proves the non-existence of a wildcard that
600 * could have produced qname.
605 * The qname to check against.
607 * The signer name for the NSEC rrset, used as the zone name.
608 * @return true if the NSEC proves the condition.
610 public static boolean nsecProvesNoWC(NSECRecord nsec, Name qname,
612 Name owner = nsec.getName();
613 Name next = nsec.getNext();
615 int qname_labels = qname.labels();
616 int signer_labels = signerName.labels();
618 for (int i = qname_labels - signer_labels; i > 0; i--) {
619 Name wc_name = qname.wild(i);
620 if (wc_name.compareTo(owner) > 0
621 && (wc_name.compareTo(next) < 0 || signerName.equals(next))) {
630 * Determine if a NSEC proves the NOERROR/NODATA conditions. This will also
631 * handle the empty non-terminal (ENT) case and partially handle the
632 * wildcard case. If the ownername of 'nsec' is a wildcard, the validator
633 * must still be provided proof that qname did not directly exist and that
634 * the wildcard is, in fact, *.closest_encloser.
639 * The query name to check against.
641 * The query type to check against.
642 * @return true if the NSEC proves the condition.
644 public static boolean nsecProvesNodata(NSECRecord nsec, Name qname,
646 if (!nsec.getName().equals(qname)) {
647 // wildcard checking.
649 // If this is a wildcard NSEC, make sure that a) it was possible to
651 // generated qname from the wildcard and b) the type map does not
652 // contain qtype. Note that this does NOT prove that this wildcard
654 // the applicable wildcard.
655 if (nsec.getName().isWild()) {
656 // the is the purported closest encloser.
657 Name ce = new Name(nsec.getName(), 1);
659 // The qname must be a strict subdomain of the closest encloser,
661 // the qtype must be absent from the type map.
662 if (!strictSubdomain(qname, ce)
663 || typeMapHasType(nsec.getTypes(), qtype)) {
669 // empty-non-terminal checking.
671 // If the nsec is proving that qname is an ENT, the nsec owner will
673 // less than qname, and the next name will be a child domain of the
675 if (strictSubdomain(nsec.getNext(), qname)
676 && qname.compareTo(nsec.getName()) > 0) {
679 // Otherwise, this NSEC does not prove ENT, so it does not prove
684 // If the qtype exists, then we should have gotten it.
685 if (typeMapHasType(nsec.getTypes(), qtype)) {
689 // if the name is a CNAME node, then we should have gotten the CNAME
690 if (typeMapHasType(nsec.getTypes(), Type.CNAME)) {
694 // If an NS set exists at this name, and NOT a SOA (so this is a zone
696 // not a zone apex), then we should have gotten a referral (or we just
699 if (typeMapHasType(nsec.getTypes(), Type.NS)
700 && !typeMapHasType(nsec.getTypes(), Type.SOA)) {
707 public static int nsecProvesNoDS(NSECRecord nsec, Name qname) {
708 // Could check to make sure the qname is a subdomain of nsec
709 int[] types = nsec.getTypes();
710 if (typeMapHasType(types, Type.SOA) || typeMapHasType(types, Type.DS)) {
711 // SOA present means that this is the NSEC from the child, not the
712 // parent (so it is the wrong one)
713 // DS present means that there should have been a positive response
715 // the DS query, so there is something wrong.
716 return SecurityStatus.BOGUS;
719 if (!typeMapHasType(types, Type.NS)) {
720 // If there is no NS at this point at all, then this doesn't prove
721 // anything one way or the other.
722 return SecurityStatus.INSECURE;
724 // Otherwise, this proves no DS.
725 return SecurityStatus.SECURE;