4 * Copyright (c) 2005 VeriSign, Inc. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 package com.versign.tat.dnssec;
33 import java.security.MessageDigest;
34 import java.security.NoSuchAlgorithmException;
35 import java.util.Iterator;
37 import org.xbill.DNS.*;
40 * This is a collection of routines encompassing the logic of validating
41 * different message types.
49 // These are response subtypes. They are necessary for determining the
50 // validation strategy. They have no bearing on the iterative resolution
51 // algorithm, so they are confined here.
53 /** Not subtyped yet. */
54 public static final int UNTYPED = 0;
56 /** Not a recognized subtype. */
57 public static final int UNKNOWN = 1;
59 /** A postive, direct, response. */
60 public static final int POSITIVE = 2;
62 /** A postive response, with a CNAME/DNAME chain. */
63 public static final int CNAME = 3;
65 /** A NOERROR/NODATA response. */
66 public static final int NODATA = 4;
68 /** A NXDOMAIN response. */
69 public static final int NAMEERROR = 5;
71 /** A response to a qtype=ANY query. */
72 public static final int ANY = 6;
74 /** A local copy of the verifier object. */
75 private DnsSecVerifier mVerifier;
77 public ValUtils(DnsSecVerifier verifier)
83 * Given a response, classify ANSWER responses into a subtype.
85 * @param m The response to classify.
87 * @return A subtype ranging from UNKNOWN to NAMEERROR.
89 public static int classifyResponse(SMessage m)
91 // Normal Name Error's are easy to detect -- but don't mistake a CNAME
92 // chain ending in NXDOMAIN.
93 if (m.getRcode() == Rcode.NXDOMAIN
94 && m.getCount(Section.ANSWER) == 0)
100 // st_log.debug("classifyResponse: ancount = " +
101 // m.getCount(Section.ANSWER));
102 if (m.getCount(Section.ANSWER) == 0)
107 // We distinguish between CNAME response and other positive/negative
108 // responses because CNAME answers require extra processing.
109 int qtype = m.getQuestion().getType();
111 // We distinguish between ANY and CNAME or POSITIVE because ANY responses
112 // are validated differently.
113 if (qtype == Type.ANY)
118 SRRset[] rrsets = m.getSectionRRsets(Section.ANSWER);
120 // Note that DNAMEs will be ignored here, unless qtype=DNAME. Unless
121 // qtype=CNAME, this will yield a CNAME response.
122 for (int i = 0; i < rrsets.length; i++)
124 if (rrsets[i].getType() == qtype) return POSITIVE;
125 if (rrsets[i].getType() == Type.CNAME) return CNAME;
128 // st_log.warn("Failed to classify response message:\n" + m);
133 * Given a response, determine the name of the "signer". This is primarily
134 * to determine if the response is, in fact, signed at all, and, if so, what
135 * is the name of the most pertinent keyset.
137 * @param m The response to analyze.
138 * @param request The request that generated the response.
139 * @return a signer name, if the response is signed (even partially), or
140 * null if the response isn't signed.
142 public Name findSigner(SMessage m)
144 int subtype = classifyResponse(m);
145 Name qname = m.getQName();
154 // Check to see if the ANSWER section RRset
155 rrsets = m.getSectionRRsets(Section.ANSWER);
156 for (int i = 0; i < rrsets.length; i++)
158 if (rrsets[i].getName().equals(qname))
160 return rrsets[i].getSignerName();
167 // Check to see if the AUTH section NSEC record(s) have rrsigs
168 rrsets = m.getSectionRRsets(Section.AUTHORITY);
169 for (int i = 0; i < rrsets.length; i++)
171 if (rrsets[i].getType() == Type.NSEC
172 || rrsets[i].getType() == Type.NSEC3)
174 return rrsets[i].getSignerName();
179 // log.debug("findSigner: could not find signer name "
180 // + "for unknown type response.");
185 public boolean dssetIsUsable(SRRset ds_rrset)
187 for (Iterator i = ds_rrset.rrs(); i.hasNext();)
189 DSRecord ds = (DSRecord) i.next();
190 if (supportsDigestID(ds.getDigestID())
191 && mVerifier.supportsAlgorithm(ds.getAlgorithm()))
201 * Given a DS rrset and a DNSKEY rrset, match the DS to a DNSKEY and verify
202 * the DNSKEY rrset with that key.
204 * @param dnskey_rrset The DNSKEY rrset to match against. The security
205 * status of this rrset will be updated on a successful
207 * @param ds_rrset The DS rrset to match with. This rrset must already be
210 * @return a KeyEntry. This will either contain the now trusted
211 * dnskey_rrset, a "null" key entry indicating that this DS
212 * rrset/DNSKEY pair indicate an secure end to the island of trust
213 * (i.e., unknown algorithms), or a "bad" KeyEntry if the dnskey
214 * rrset fails to verify. Note that the "null" response should
215 * generally only occur in a private algorithm scenario: normally
216 * this sort of thing is checked before fetching the matching DNSKEY
219 // public KeyEntry verifyNewDNSKEYs(SRRset dnskey_rrset, SRRset ds_rrset)
221 // if (!dnskey_rrset.getName().equals(ds_rrset.getName()))
223 //// log.debug("DNSKEY RRset did not match DS RRset by name!");
225 // .newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass());
228 // // as long as this is false, we can consider this DS rrset to be
229 // // equivalent to no DS rrset.
230 // boolean hasUsefulDS = false;
232 // for (Iterator i = ds_rrset.rrs(); i.hasNext();)
234 // DSRecord ds = (DSRecord) i.next();
236 // // Check to see if we can understand this DS.
237 // if (!supportsDigestID(ds.getDigestID())
238 // || !mVerifier.supportsAlgorithm(ds.getAlgorithm()))
243 // // Once we see a single DS with a known digestID and algorithm, we
244 // // cannot return INSECURE (with a "null" KeyEntry).
245 // hasUsefulDS = true;
247 // DNSKEY : for (Iterator j = dnskey_rrset.rrs(); j.hasNext();)
249 // DNSKEYRecord dnskey = (DNSKEYRecord) j.next();
251 // // Skip DNSKEYs that don't match the basic criteria.
252 // if (ds.getFootprint() != dnskey.getFootprint()
253 // || ds.getAlgorithm() != dnskey.getAlgorithm())
258 // // Convert the candidate DNSKEY into a hash using the same DS hash
260 // byte[] key_hash = calculateDSHash(dnskey, ds.getDigestID());
261 // byte[] ds_hash = ds.getDigest();
263 // // see if there is a length mismatch (unlikely)
264 // if (key_hash.length != ds_hash.length)
269 // for (int k = 0; k < key_hash.length; k++)
271 // if (key_hash[k] != ds_hash[k]) continue DNSKEY;
274 // // Otherwise, we have a match! Make sure that the DNSKEY verifies
275 // // *with this key*.
276 // byte res = mVerifier.verify(dnskey_rrset, dnskey);
277 // if (res == SecurityStatus.SECURE)
279 //// log.trace("DS matched DNSKEY.");
280 // dnskey_rrset.setSecurityStatus(SecurityStatus.SECURE);
281 // return KeyEntry.newKeyEntry(dnskey_rrset);
283 // // If it didn't validate with the DNSKEY, try the next one!
287 // // None of the DS's worked out.
289 // // If no DSs were understandable, then this is OK.
292 //// log.debug("No usuable DS records were found -- treating as insecure.");
293 // return KeyEntry.newNullKeyEntry(ds_rrset.getName(), ds_rrset
294 // .getDClass(), ds_rrset.getTTL());
296 // // If any were understandable, then it is bad.
297 //// log.debug("Failed to match any usable DS to a DNSKEY.");
298 // return KeyEntry.newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass());
302 * Given a DNSKEY record, generate the DS record from it.
304 * @param keyrec the DNSKEY record in question.
305 * @param ds_alg The DS digest algorithm in use.
306 * @return the corresponding {@link org.xbill.DNS.DSRecord}
308 public static byte[] calculateDSHash(DNSKEYRecord keyrec, int ds_alg)
310 DNSOutput os = new DNSOutput();
312 os.writeByteArray(keyrec.getName().toWireCanonical());
313 os.writeByteArray(keyrec.rdataToWireCanonical());
317 MessageDigest md = null;
320 case DSRecord.SHA1_DIGEST_ID :
321 md = MessageDigest.getInstance("SHA");
322 return md.digest(os.toByteArray());
323 case DSRecord.SHA256_DIGEST_ID:
324 md = MessageDigest.getInstance("SHA256");
325 return md.digest(os.toByteArray());
327 // st_log.warn("Unknown DS algorithm: " + ds_alg);
332 catch (NoSuchAlgorithmException e)
334 // st_log.error("Error using DS algorithm: " + ds_alg, e);
339 public static boolean supportsDigestID(int digest_id)
341 if (digest_id == DSRecord.SHA1_DIGEST_ID) return true;
342 if (digest_id == DSRecord.SHA256_DIGEST_ID) return true;
347 * Check to see if a type is a special DNSSEC type.
349 * @param type The type.
351 * @return true if the type is one of the special DNSSEC types.
353 public static boolean isDNSSECType(int type)
369 * Set the security status of a particular RRset. This will only upgrade the
372 * @param rrset The SRRset to update.
373 * @param security The security status.
375 public static void setRRsetSecurity(SRRset rrset, byte security)
377 if (rrset == null) return;
379 int cur_sec = rrset.getSecurityStatus();
380 if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec)
382 rrset.setSecurityStatus(security);
387 * Set the security status of a message and all of its RRsets. This will
388 * only upgrade the status of the message (i.e., set to more secure, not
389 * less) and all of the RRsets.
392 * @param security KeyEntry ke;
394 * SMessage m = response.getSMessage(); SRRset ans_rrset =
395 * m.findAnswerRRset(qname, qtype, qclass);
397 * ke = verifySRRset(ans_rrset, key_rrset); if
398 * (ans_rrset.getSecurityStatus() != SecurityStatus.SECURE) { return; }
399 * key_rrset = ke.getRRset();
401 public static void setMessageSecurity(SMessage m, byte security)
403 if (m == null) return;
405 int cur_sec = m.getStatus();
406 if (cur_sec == SecurityStatus.UNCHECKED || security > cur_sec)
408 m.setStatus(security);
411 for (int section = Section.ANSWER; section <= Section.ADDITIONAL; section++)
413 SRRset[] rrsets = m.getSectionRRsets(section);
414 for (int i = 0; i < rrsets.length; i++)
416 setRRsetSecurity(rrsets[i], security);
422 * Given an SRRset that is signed by a DNSKEY found in the key_rrset, verify
423 * it. This will return the status (either BOGUS or SECURE) and set that
426 * @param rrset The SRRset to verify.
427 * @param key_rrset The set of keys to verify against.
428 * @return The status (BOGUS or SECURE).
430 public byte verifySRRset(SRRset rrset, SRRset key_rrset)
432 String rrset_name = rrset.getName() + "/" + Type.string(rrset.getType())
433 + "/" + DClass.string(rrset.getDClass());
435 if (rrset.getSecurityStatus() == SecurityStatus.SECURE)
437 // log.trace("verifySRRset: rrset <" + rrset_name
438 // + "> previously found to be SECURE");
439 return SecurityStatus.SECURE;
442 byte status = mVerifier.verify(rrset, key_rrset);
443 if (status != SecurityStatus.SECURE)
445 // log.debug("verifySRRset: rrset <" + rrset_name + "> found to be BAD");
446 status = SecurityStatus.BOGUS;
450 // log.trace("verifySRRset: rrset <" + rrset_name + "> found to be SECURE");
453 rrset.setSecurityStatus(status);
458 * Determine if a given type map has a given typ.
460 * @param types The type map from the NSEC record.
461 * @param type The type to look for.
462 * @return true if the type is present in the type map, false otherwise.
464 public static boolean typeMapHasType(int[] types, int type)
466 for (int i = 0; i < types.length; i++)
468 if (types[i] == type) return true;
473 public static RRSIGRecord rrsetFirstSig(RRset rrset) {
474 for (Iterator i = rrset.sigs(); i.hasNext(); ) {
475 return (RRSIGRecord) i.next();
480 public static Name longestCommonName(Name domain1, Name domain2) {
481 if (domain1 == null || domain2 == null) return null;
482 // for now, do this in a a fairly brute force way
483 // FIXME: convert this to direct operations on the byte[]
485 int this_labels = domain1.labels();
486 int name_labels = domain2.labels();
488 int l = (this_labels < name_labels) ? this_labels : name_labels;
489 for (int i = l; i > 0; i--)
491 Name n = new Name(domain2, name_labels - i);
492 if (n.equals(name, offset(this_labels - i)))
501 * Determine by looking at a signed RRset whether or not the rrset name was
502 * the result of a wildcard expansion.
504 * @param rrset The rrset to examine.
505 * @return true if the rrset is a wildcard expansion. This will return false
506 * for all unsigned rrsets.
508 public static boolean rrsetIsWildcardExpansion(RRset rrset)
510 if (rrset == null) return false;
511 RRSIGRecord rrsig = rrsetFirstSig(rrset);
513 if (rrset.getName().labels() - 1 > rrsig.getLabels())
522 * Determine by looking at a signed RRset whether or not the RRset name was
523 * the result of a wildcard expansion. If so, return the name of the
524 * generating wildcard.
526 * @param rrset The rrset to chedck.
527 * @return the wildcard name, if the rrset was synthesized from a wildcard.
530 public static Name rrsetWildcard(RRset rrset)
532 if (rrset == null) return null;
533 RRSIGRecord rrsig = rrsetFirstSig(rrset);
535 // if the RRSIG label count is shorter than the number of actual labels,
536 // then this rrset was synthesized from a wildcard.
537 // Note that the RRSIG label count doesn't count the root label.
538 int label_diff = (rrset.getName().labels() - 1) - rrsig.getLabels();
541 return rrset.getName().wild(label_diff);
546 public static Name closestEncloser(Name domain, NSECRecord nsec)
548 Name n1 = domain.longestCommonName(nsec.getName());
549 Name n2 = domain.longestCommonName(nsec.getNext());
551 return (n1.labels() > n2.labels()) ? n1 : n2;
554 public static Name nsecWildcard(Name domain, NSECRecord nsec)
558 return new Name("*", closestEncloser(domain, nsec));
560 catch (TextParseException e)
562 // this should never happen.
568 * Determine if the given NSEC proves a NameError (NXDOMAIN) for a given
571 * @param nsec The NSEC to check.
572 * @param qname The qname to check against.
573 * @param signerName The signer name of the NSEC record, which is used as
574 * the zone name, for a more precise (but perhaps more brittle)
575 * check for the last NSEC in a zone.
576 * @return true if the NSEC proves the condition.
578 public static boolean nsecProvesNameError(NSECRecord nsec, Name qname,
581 Name owner = nsec.getName();
582 Name next = nsec.getNext();
584 // If NSEC owner == qname, then this NSEC proves that qname exists.
585 if (qname.equals(owner))
590 // If NSEC is a parent of qname, we need to check the type map
591 // If the parent name has a DNAME or is a delegation point, then this NSEC
593 if (qname.subdomain(owner)
594 && (typeMapHasType(nsec.getTypes(), Type.DNAME) || (typeMapHasType(nsec
596 Type.NS) && !typeMapHasType(nsec.getTypes(), Type.SOA))))
601 if (qname.compareTo(owner) > 0 && (qname.compareTo(next) < 0)
602 || signerName.equals(next))
610 * Determine if a NSEC record proves the non-existence of a wildcard that
611 * could have produced qname.
613 * @param nsec The nsec to check.
614 * @param qname The qname to check against.
615 * @param signerName The signer name for the NSEC rrset, used as the zone
617 * @return true if the NSEC proves the condition.
619 public static boolean nsecProvesNoWC(NSECRecord nsec, Name qname,
622 Name owner = nsec.getName();
623 Name next = nsec.getNext();
625 int qname_labels = qname.labels();
626 int signer_labels = signerName.labels();
628 for (int i = qname_labels - signer_labels; i > 0; i--)
630 Name wc_name = qname.wild(i);
631 if (wc_name.compareTo(owner) > 0
632 && (wc_name.compareTo(next) < 0 || signerName.equals(next)))
642 * Determine if a NSEC proves the NOERROR/NODATA conditions. This will also
643 * handle the empty non-terminal (ENT) case and partially handle the
644 * wildcard case. If the ownername of 'nsec' is a wildcard, the validator
645 * must still be provided proof that qname did not directly exist and that
646 * the wildcard is, in fact, *.closest_encloser.
648 * @param nsec The NSEC to check
649 * @param qname The query name to check against.
650 * @param qtype The query type to check against.
651 * @return true if the NSEC proves the condition.
653 public static boolean nsecProvesNodata(NSECRecord nsec, Name qname,
656 if (!nsec.getName().equals(qname))
658 // wildcard checking.
660 // If this is a wildcard NSEC, make sure that a) it was possible to have
661 // generated qname from the wildcard and b) the type map does not
662 // contain qtype. Note that this does NOT prove that this wildcard was
663 // the applicable wildcard.
664 if (nsec.getName().isWild())
666 // the is the purported closest encloser.
667 Name ce = new Name(nsec.getName(), 1);
669 // The qname must be a strict subdomain of the closest encloser, and
670 // the qtype must be absent from the type map.
671 if (!qname.strictSubdomain(ce) || typeMapHasType(nsec.getTypes(), qtype))
678 // empty-non-terminal checking.
680 // If the nsec is proving that qname is an ENT, the nsec owner will be
681 // less than qname, and the next name will be a child domain of the
683 if (nsec.getNext().strictSubdomain(qname)
684 && qname.compareTo(nsec.getName()) > 0)
688 // Otherwise, this NSEC does not prove ENT, so it does not prove NODATA.
692 // If the qtype exists, then we should have gotten it.
693 if (typeMapHasType(nsec.getTypes(), qtype))
698 // if the name is a CNAME node, then we should have gotten the CNAME
699 if (typeMapHasType(nsec.getTypes(), Type.CNAME))
704 // If an NS set exists at this name, and NOT a SOA (so this is a zone cut,
705 // not a zone apex), then we should have gotten a referral (or we just got
707 if (typeMapHasType(nsec.getTypes(), Type.NS)
708 && !typeMapHasType(nsec.getTypes(), Type.SOA))
716 public static int nsecProvesNoDS(NSECRecord nsec, Name qname)
718 // Could check to make sure the qname is a subdomain of nsec
719 int[] types = nsec.getTypes();
720 if (typeMapHasType(types, Type.SOA) || typeMapHasType(types, Type.DS))
722 // SOA present means that this is the NSEC from the child, not the
723 // parent (so it is the wrong one)
724 // DS present means that there should have been a positive response to
725 // the DS query, so there is something wrong.
726 return SecurityStatus.BOGUS;
729 if (!typeMapHasType(types, Type.NS))
731 // If there is no NS at this point at all, then this doesn't prove
732 // anything one way or the other.
733 return SecurityStatus.INSECURE;
735 // Otherwise, this proves no DS.
736 return SecurityStatus.SECURE;