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.*;
29 import org.xbill.DNS.utils.base64;
31 import java.io.IOException;
36 * This resolver module implements a "captive" DNSSEC validator. The
37 * captive validator does not have direct access to the Internet and
38 * DNS system -- instead it attempts to validate DNS messages using
39 * only configured context. This is useful for determining if
40 * responses coming from a given authoritative server will validate
41 * independent of the normal chain of trust.
43 public class CaptiveValidator {
44 // A data structure holding all all of our trusted keys.
45 private TrustAnchorStore mTrustedKeys;
47 // The local validation utilities.
48 private ValUtils mValUtils;
50 // The local verification utility.
51 private DnsSecVerifier mVerifier;
52 private Logger log = Logger.getLogger(this.getClass());
54 // The list of validation errors found.
55 private List<String> mErrorList;
57 public CaptiveValidator() {
58 mVerifier = new DnsSecVerifier();
59 mValUtils = new ValUtils(mVerifier);
60 mTrustedKeys = new TrustAnchorStore();
61 mErrorList = new ArrayList<String>();
64 // ---------------- Module Initialization -------------------
67 * Add a set of trusted keys from a file. The file should be in
68 * DNS master zone file format. Only DNSKEY records will be added.
71 * The file contains the trusted keys.
74 @SuppressWarnings("unchecked")
75 public void addTrustedKeysFromFile(String filename) throws IOException {
76 // First read in the whole trust anchor file.
77 Master master = new Master(filename, Name.root, 0);
78 ArrayList<Record> records = new ArrayList<Record>();
81 while ((r = master.nextRecord()) != null) {
85 // Record.compareTo() should sort them into DNSSEC canonical
86 // order. Don't care about canonical order per se, but do
87 // want them to be formable into RRsets.
88 Collections.sort(records);
90 SRRset cur_rrset = new SRRset();
92 for (Record rec : records) {
93 // Skip RR types that cannot be used as trusted
94 // keys. I.e., everything not a key :)
95 if (rec.getType() != Type.DNSKEY) {
99 // If our cur_rrset is empty, we can just add it.
100 if (cur_rrset.size() == 0) {
101 cur_rrset.addRR(rec);
106 // If this record matches our current RRset, we can just
108 if (cur_rrset.getName().equals(rec.getName()) &&
109 (cur_rrset.getType() == rec.getType()) && (cur_rrset.getDClass() == rec.getDClass())) {
111 cur_rrset.addRR(rec);
115 // Otherwise, we add the rrset to our set of trust anchors.
116 mTrustedKeys.store(cur_rrset);
117 cur_rrset = new SRRset();
118 cur_rrset.addRR(rec);
121 // add the last rrset (if it was not empty)
122 if (cur_rrset.size() > 0) {
123 mTrustedKeys.store(cur_rrset);
127 public void addTrustedKeysFromResponse(Message m) {
128 RRset[] rrsets = m.getSectionRRsets(Section.ANSWER);
130 for (int i = 0; i < rrsets.length; ++i) {
131 if (rrsets[i].getType() == Type.DNSKEY) {
132 SRRset srrset = new SRRset(rrsets[i]);
133 mTrustedKeys.store(srrset);
138 // ----------------- Validation Support ----------------------
141 * This routine normalizes a response. This includes removing
142 * "irrelevant" records from the answer and additional sections
143 * and (re)synthesizing CNAMEs from DNAMEs, if present.
147 private SMessage normalize(SMessage m) {
152 if ((m.getRcode() != Rcode.NOERROR) && (m.getRcode() != Rcode.NXDOMAIN)) {
156 Name qname = m.getQuestion().getName();
157 int qtype = m.getQuestion().getType();
161 // For the ANSWER section, remove all "irrelevant" records and add
162 // synthesized CNAMEs from DNAMEs
163 // This will strip out-of-order CNAMEs as well.
164 List<SRRset> rrset_list = m.getSectionList(Section.ANSWER);
165 Set<Name> additional_names = new HashSet<Name>();
167 for (ListIterator<SRRset> i = rrset_list.listIterator(); i.hasNext();) {
168 SRRset rrset = i.next();
169 int type = rrset.getType();
170 Name n = rrset.getName();
172 // Handle DNAME synthesis; DNAME synthesis does not occur
173 // at the DNAME name itself.
174 if ((type == Type.DNAME) && ValUtils.strictSubdomain(sname, n)) {
175 if (rrset.size() > 1) {
176 log.debug("Found DNAME rrset with size > 1: " + rrset);
177 m.setStatus(SecurityStatus.INVALID);
182 DNAMERecord dname = (DNAMERecord) rrset.first();
185 Name cname_alias = sname.fromDNAME(dname);
187 // Note that synthesized CNAMEs should have a TTL of zero.
188 CNAMERecord cname = new CNAMERecord(sname, dname.getDClass(), 0, cname_alias);
189 SRRset cname_rrset = new SRRset();
190 cname_rrset.addRR(cname);
194 } catch (NameTooLongException e) {
195 log.debug("not adding synthesized CNAME -- " +
196 "generated name is too long", e);
202 // The only records in the ANSWER section not allowed to
203 if (!n.equals(sname)) {
204 log.debug("normalize: removing irrelevant rrset: " + rrset);
210 // Follow the CNAME chain.
211 if (type == Type.CNAME) {
212 if (rrset.size() > 1) {
213 mErrorList.add("Found CNAME rrset with size > 1: " + rrset);
214 m.setStatus(SecurityStatus.INVALID);
219 CNAMERecord cname = (CNAMERecord) rrset.first();
220 sname = cname.getAlias();
225 // Otherwise, make sure that the RRset matches the qtype.
226 if ((qtype != Type.ANY) && (qtype != type)) {
227 log.debug("normalize: removing irrelevant rrset: " + rrset);
231 // Otherwise, fetch the additional names from the relevant rrset.
232 rrsetAdditionalNames(additional_names, rrset);
235 // Get additional names from AUTHORITY
236 rrset_list = m.getSectionList(Section.AUTHORITY);
238 for (SRRset rrset : rrset_list) {
239 rrsetAdditionalNames(additional_names, rrset);
242 // For each record in the additional section, remove it if it is an
243 // address record and not in the collection of additional names found in
244 // ANSWER and AUTHORITY.
245 rrset_list = m.getSectionList(Section.ADDITIONAL);
247 for (Iterator<SRRset> i = rrset_list.iterator(); i.hasNext();) {
248 SRRset rrset = i.next();
249 int type = rrset.getType();
251 if (((type == Type.A) || (type == Type.AAAA)) &&
252 !additional_names.contains(rrset.getName())) {
262 * Extract additional names from the records in an rrset.
264 * @param additional_names
265 * The set to add the additional names to, if any.
267 * The rrset to extract from.
269 private void rrsetAdditionalNames(Set<Name> additional_names, SRRset rrset) {
274 for (Iterator<Record> i = rrset.rrs(); i.hasNext();) {
276 Name add_name = r.getAdditionalName();
278 if (add_name != null) {
279 additional_names.add(add_name);
284 private SRRset findKeys(SMessage message) {
285 Name qname = message.getQName();
286 int qclass = message.getQClass();
288 return mTrustedKeys.find(qname, qclass);
292 * Check to see if a given response needs to go through the
293 * validation process. Typical reasons for this routine to return
294 * false are: CD bit was on in the original request, the response
295 * was already validated, or the response is a kind of message
296 * that is unvalidatable (i.e., SERVFAIL, REFUSED, etc.)
299 * The message to check.
301 * The original request received from the client.
303 * @return true if the response could use validation (although this does not
304 * mean we can actually validate this response).
306 private boolean needsValidation(SMessage message) {
307 int rcode = message.getRcode();
309 if ((rcode != Rcode.NOERROR) && (rcode != Rcode.NXDOMAIN)) {
310 log.debug("cannot validate non-answer.");
311 log.trace("non-answer: " + message);
316 if (!mTrustedKeys.isBelowTrustAnchor(message.getQName(), message.getQClass())) {
324 * Given a "positive" response -- a response that contains an
325 * answer to the question, and no CNAME chain, validate this
326 * response. This generally consists of verifying the answer RRset
327 * and the authority RRsets.
329 * Note that by the time this method is called, the process of
330 * finding the trusted DNSKEY rrset that signs this response must
331 * already have been completed.
334 * The response to validate.
336 * The request that generated this response.
338 * The trusted DNSKEY rrset that matches the signer of the
341 private void validatePositiveResponse(SMessage message, SRRset key_rrset) {
342 Name qname = message.getQName();
343 int qtype = message.getQType();
345 SMessage m = message;
347 // validate the ANSWER section - this will be the answer itself
348 SRRset[] rrsets = m.getSectionRRsets(Section.ANSWER);
351 boolean wcNSEC_ok = false;
352 boolean dname = false;
353 List<NSEC3Record> nsec3s = null;
355 for (int i = 0; i < rrsets.length; i++) {
356 // Skip the CNAME following a (validated) DNAME.
357 // Because of the normalization routines in NameserverClient, there
358 // will always be an unsigned CNAME following a DNAME (unless
360 if (dname && (rrsets[i].getType() == Type.CNAME)) {
365 // Verify the answer rrset.
366 int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
368 // If the (answer) rrset failed to validate, then this
370 if (status != SecurityStatus.SECURE) {
371 mErrorList.add("Positive response has failed ANSWER rrset: " +
373 m.setStatus(SecurityStatus.BOGUS);
378 // Check to see if the rrset is the result of a wildcard expansion.
379 // If so, an additional check will need to be made in the authority
381 wc = ValUtils.rrsetWildcard(rrsets[i]);
383 // Notice a DNAME that should be followed by an unsigned CNAME.
384 if ((qtype != Type.DNAME) && (rrsets[i].getType() == Type.DNAME)) {
389 // validate the AUTHORITY section as well - this will
390 // generally be the NS rrset (which could be missing, no
392 rrsets = m.getSectionRRsets(Section.AUTHORITY);
394 for (int i = 0; i < rrsets.length; i++) {
395 int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
397 // If anything in the authority section fails to be
398 // secure, we have a bad message.
399 if (status != SecurityStatus.SECURE) {
400 mErrorList.add("Positive response has failed AUTHORITY rrset: " +
402 m.setStatus(SecurityStatus.BOGUS);
407 // If this is a positive wildcard response, and we have a
408 // (just verified) NSEC record, try to use it to 1) prove
409 // that qname doesn't exist and 2) that the correct
410 // wildcard was used.
411 if ((wc != null) && (rrsets[i].getType() == Type.NSEC)) {
412 NSECRecord nsec = (NSECRecord) rrsets[i].first();
414 if (ValUtils.nsecProvesNameError(nsec, qname, key_rrset.getName())) {
415 Name nsec_wc = ValUtils.nsecWildcard(qname, nsec);
417 if (!wc.equals(nsec_wc)) {
418 mErrorList.add("Positive wildcard response wasn't generated by the correct wildcard");
419 m.setStatus(SecurityStatus.BOGUS);
428 // Otherwise, if this is a positive wildcard response and we have
429 // NSEC3 records, collect them.
430 if ((wc != null) && (rrsets[i].getType() == Type.NSEC3)) {
431 if (nsec3s == null) {
432 nsec3s = new ArrayList<NSEC3Record>();
435 nsec3s.add((NSEC3Record) rrsets[i].first());
439 // If this was a positive wildcard response that we haven't
440 // already proven, and we have NSEC3 records, try to prove it
441 // using the NSEC3 records.
442 if ((wc != null) && !wcNSEC_ok && (nsec3s != null)) {
443 if (NSEC3ValUtils.proveWildcard(nsec3s, qname, key_rrset.getName(),
449 // If after all this, we still haven't proven the positive
450 // wildcard response, fail.
451 if ((wc != null) && !wcNSEC_ok) {
452 // log.debug("positive response was wildcard expansion and " +
453 // "did not prove original data did not exist");
454 m.setStatus(SecurityStatus.BOGUS);
459 log.trace("Successfully validated positive response");
460 m.setStatus(SecurityStatus.SECURE);
463 /** Given a "referral" type response (RCODE=NOERROR, ANSWER=0,
464 * AUTH=NS records under the zone we thought we were talking to,
465 * etc.), validate it. This consists of validating the DS or
466 * NSEC/NSEC3 RRsets and noting that the response does indeed look
471 private void validateReferral(SMessage message, SRRset key_rrset) {
472 SMessage m = message;
474 if (m.getCount(Section.ANSWER) > 0) {
475 m.setStatus(SecurityStatus.INVALID);
480 // validate the AUTHORITY section.
481 SRRset[] rrsets = m.getSectionRRsets(Section.AUTHORITY);
483 boolean secure_delegation = false;
484 Name delegation = null;
485 Name nsec3zone = null;
486 NSECRecord nsec = null;
487 List<NSEC3Record> nsec3s = null;
489 // validate the AUTHORITY section as well - this will generally be the
490 // NS rrset, plus proof of a secure delegation or not
491 rrsets = m.getSectionRRsets(Section.AUTHORITY);
493 for (int i = 0; i < rrsets.length; i++) {
494 int type = rrsets[i].getType();
496 // The NS RRset won't be signed, but everything else
497 // should be. If we have an unexpected type here
498 // with a bad signature, we will fail when we otherwise
499 // might just have warned about the odd record. Consider
500 // checking the types first, then validating.
501 if (type != Type.NS) {
502 int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
504 // If anything in the authority section fails to be
505 // secure, we have a bad message.
506 if (status != SecurityStatus.SECURE) {
507 mErrorList.add("Referral response has failed AUTHORITY rrset: " +
509 m.setStatus(SecurityStatus.BOGUS);
517 secure_delegation = true;
521 delegation = rrsets[i].getName();
525 nsec = (NSECRecord) rrsets[i].first();
529 if (nsec3s == null) {
530 nsec3s = new ArrayList<NSEC3Record>();
533 NSEC3Record nsec3 = (NSEC3Record) rrsets[i].first();
535 // this is a hack, really.
536 nsec3zone = rrsets[i].getSignerName();
541 log.warn("Encountered unexpected type in a REFERRAL response: "
542 + Type.string(type));
548 // At this point, all validatable RRsets have been validated.
549 // Now to check to see if we have a valid combination of
551 if (delegation == null) {
552 // somehow we have a referral without an NS rrset.
553 mErrorList.add("Apparent referral does not contain NS RRset");
554 m.setStatus(SecurityStatus.BOGUS);
559 if (secure_delegation) {
560 if ((nsec != null) || ((nsec3s != null) && (nsec3s.size() > 0))) {
561 // we found both a DS rrset *and* NSEC/NSEC3 rrsets!
562 mErrorList.add("Referral contains both DS and NSEC/NSEC3 RRsets");
563 m.setStatus(SecurityStatus.BOGUS);
568 // otherwise, we are done.
569 m.setStatus(SecurityStatus.SECURE);
574 // Note: not going to care if both NSEC and NSEC3 rrsets were present.
576 byte status = ValUtils.nsecProvesNoDS(nsec, delegation);
578 if (status != SecurityStatus.SECURE) {
579 // The NSEC *must* prove that there was no DS
580 // record. The INSECURE state here is still bogus.
581 mErrorList.add("Referral does not contain a NSEC record proving no DS");
582 m.setStatus(SecurityStatus.BOGUS);
587 m.setStatus(SecurityStatus.SECURE);
592 if (nsec3s != null && nsec3s.size() > 0) {
593 byte status = NSEC3ValUtils.proveNoDS(nsec3s, delegation, nsec3zone, mErrorList);
595 if (status != SecurityStatus.SECURE) {
596 // the NSEC3 RRs MUST prove no DS, so the INDETERMINATE state is
598 mErrorList.add("Referral does not contain NSEC3 record(s) proving no DS");
599 m.setStatus(SecurityStatus.BOGUS);
604 m.setStatus(SecurityStatus.SECURE);
609 // failed to find proof either way.
610 mErrorList.add("Referral does not contain proof of no DS");
611 m.setStatus(SecurityStatus.BOGUS);
614 // When processing CNAME responses, if we have wildcard-generated CNAMEs we
615 // have to keep track of several bits of information per-cname. This small
616 // inner class is for that.
617 class CNAMEWildcardEntry {
619 public Name wildcard;
622 public CNAMEWildcardEntry(Name owner, Name wildcard, Name signer) {
624 this.wildcard = wildcard;
625 this.signer = signer;
629 // When processing CNAME responses, our final step is check the end of the
630 // chain if we ended up in zone. To that end, we generate a temporary
631 // message that removes the CNAME/DNAME chain.
632 private SMessage messageFromCNAME(SMessage source, Name sname, Name zone) {
634 SMessage m = new SMessage();
635 m.setHeader(source.getHeader());
636 Record oldQuestion = source.getQuestion();
637 Record newQuestion = Record.newRecord(sname, oldQuestion.getType(), oldQuestion.getDClass());
638 m.setQuestion(newQuestion);
639 m.setOPT(source.getOPT());
641 // Add the rrsets from the source message, stripping answers that don't
642 // belong to the end of the chain
643 RRset[] rrsets = source.getSectionRRsets(Section.ANSWER);
644 for (int i = 0; i < rrsets.length; i++) {
645 Name rname = rrsets[i].getName();
647 if (rname.equals(sname)) {
648 m.addRRset(rrsets[i], Section.ANSWER);
652 // The authority and additional sections should be about the end of the
653 // chain, plus some additional NSEC or NSEC3 records.
654 for (int i = Section.AUTHORITY; i <= Section.ADDITIONAL; i++) {
655 rrsets = source.getSectionRRsets(i);
657 for (int j = 0; j < rrsets.length; j++) {
658 m.addRRset(rrsets[j], i);
665 * Given a "CNAME" response (i.e., a response that contains at least one
666 * CNAME, and qtype != CNAME). This largely consists of validating each
667 * CNAME RRset until the CNAME chain goes "out of zone". Note that
668 * out-of-order CNAME chains will have been cleaned up via normalize(). When
669 * traversing the CNAME chain, we detect if the CNAMEs were generated from a
670 * wildcard, and we detect when the chain goes "out-of-zone". For each
671 * in-zone wildcard generated CNAME, we check for a proof that the alias
672 * (the owner of each cname) doesn't exist.
674 * If the end of the chain is still in zone, we then strip the CNAME/DNAME
675 * chain, reclassify the response, then validate the "tail message".
677 * Note that once the CNAME chain goes out of zone, any further CNAMEs are
678 * not DNSSEC validated (we would need more trusted keysets for that), so
679 * this isn't useful in all cases (i.e., for testing a nameserver, like
680 * BIND, which generates CNAME chains across zones.)
682 * Note that by the time this method is called, the process of finding the
683 * trusted DNSKEY rrset that signs this response must already have been
686 private void validateCNAMEResponse(SMessage message, SRRset key_rrset)
688 Name qname = message.getQName();
690 Name sname = qname; // this is the "current" name in the chain
691 boolean dname = false; // a flag indicating that prev iteration was a dname
692 boolean inZone = true; // a flag telling us if we ended up in zone.
693 List<CNAMEWildcardEntry> wildcards =
694 new ArrayList<CNAMEWildcardEntry>(); // The CNAMEs that were generated with wildcards.
695 Name zone = key_rrset.getName();
697 SRRset[] rrsets = message.getSectionRRsets(Section.ANSWER);
699 // Validate the ANSWER section RRsets.
700 for (int i = 0; i < rrsets.length; i++) {
702 int rtype = rrsets[i].getType();
703 Name rname = rrsets[i].getName();
705 // Follow the CNAME chain
706 if (rtype == Type.CNAME) {
707 // If we've gotten off track... Note: this should be
708 // impossible with normalization in effect.
710 if (!sname.equals(rname)) {
711 mErrorList.add("CNAME chain is broken: expected owner name of " +
712 sname + " got: " + rname);
713 message.setStatus(SecurityStatus.BOGUS);
717 sname = ((CNAMERecord) rrsets[i].first()).getAlias();
719 // Check to see if the CNAME was generated by a wildcard. We
720 // store the generated name instead of the wildcard value, as we
721 // need to prove that the wildcard wasn't blocked. For now, we
722 // only want to do that for "in zone" wildcard CNAMEs
723 Name wc = ValUtils.rrsetWildcard(rrsets[i]);
724 if (wc != null && inZone) {
725 RRSIGRecord rrsig = rrsets[i].firstSig();
726 wildcards.add(new CNAMEWildcardEntry(sname, wc, rrsig.getSigner()));
730 // Note when we see a DNAME.
731 if (rtype == Type.DNAME) {
733 Name wc = ValUtils.rrsetWildcard(rrsets[i]);
735 mErrorList.add("Illegal wildcard DNAME found: " + rrsets[i]);
739 // Skip validation of CNAMEs following DNAMEs. The
740 // normalization step will have synthesized an unsigned
742 if (dname && rtype == Type.CNAME) {
747 int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
749 if (status != SecurityStatus.SECURE) {
750 mErrorList.add("CNAME response has a failed ANSWER rrset: " +
752 message.setStatus(SecurityStatus.BOGUS);
757 // Once we've gone off the reservation, avoid further
759 if (! sname.subdomain(zone)) {
765 log.trace("processed CNAME chain and ended with: " +
766 sname + "; inZone = " + inZone);
768 // Keep track of NSEC and NSEC3 records we find in the auth section
769 // Only add verified records, though.
770 List<NSECRecord> nsecs = new ArrayList<NSECRecord>();
771 List<NSEC3Record> nsec3s = new ArrayList<NSEC3Record>();
773 // Validate the AUTHORITY section.
774 rrsets = message.getSectionRRsets(Section.ANSWER);
775 for (int i = 0; i < rrsets.length; i++) {
776 Name rname = rrsets[i].getName();
777 int rtype = rrsets[i].getType();
779 if (! rname.subdomain(zone)) {
780 // Skip auth records that are not in our zone
781 // This is a current limitation of this method
785 int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
787 // If anything in the authority section fails to be
788 // secure, we have a bad message.
789 if (status != SecurityStatus.SECURE) {
790 mErrorList.add("Positive response has failed AUTHORITY rrset: " +
792 message.setStatus(SecurityStatus.BOGUS);
797 // otherwise, collect the validated NSEC and NSEC3 RRs, if any
798 if (rtype == Type.NSEC) {
799 nsecs.add((NSECRecord) rrsets[i].first());
801 else if (rtype == Type.NSEC3) {
802 nsec3s.add((NSEC3Record) rrsets[i].first());
806 // Regardless if whether or not we left the reservation, if some of our
807 // CNAMEs were generated from wildcards we need to prove that.
808 if (wildcards.size() > 0) {
810 for (CNAMEWildcardEntry wcEntry : wildcards) {
811 boolean result = false;
812 if (nsecs.size() > 0) {
813 for (NSECRecord nsec : nsecs) {
814 result = ValUtils.nsecProvesNameError(nsec, wcEntry.owner, wcEntry.signer);
818 else if (nsec3s.size() > 0) {
819 result = NSEC3ValUtils.proveWildcard(nsec3s, wcEntry.owner, zone, wcEntry.wildcard, mErrorList);
823 mErrorList.add("CNAME response has a wildcard-generated CNAME '" +
824 wcEntry.owner + "' but does not prove that the wildcard '" +
825 wcEntry.wildcard + "' was valid via a covering NSEC or NSEC3 RR");
826 message.setStatus(SecurityStatus.BOGUS);
832 // If our CNAME chain took us out of zone, we are done.
834 log.trace("Successfully validated CNAME response up to the point where it left our zone.");
835 message.setStatus(SecurityStatus.SECURE);
839 // Otherwise, we need to do some additional proofs
840 SMessage tailMessage = messageFromCNAME(message, sname, zone);
841 ValUtils.ResponseType tailType = ValUtils.classifyResponse(tailMessage, zone);
844 log.trace("Validating the rest of the CNAME response as a positive response");
845 validatePositiveResponse(tailMessage, key_rrset);
846 message.setSecurityStatus(tailMessage.getSecurityStatus());
850 log.trace("Validating the rest of the CNAME response as a referral");
851 validateReferral(tailMessage, key_rrset);
852 message.setSecurityStatus(tailMessage.getSecurityStatus());
856 log.trace("Validating the rest of the CNAME responses as a NODATA response");
857 validateNodataResponse(tailMessage, key_rrset, mErrorList);
858 message.setSecurityStatus(tailMessage.getSecurityStatus());
862 log.trace("Validating a the rest of the CNAME responses as NXDOMAIN response");
863 validateNameErrorResponse(tailMessage, key_rrset);
864 message.setSecurityStatus(tailMessage.getSecurityStatus());
868 log.error("Reclassified the tail of a CNAME response as a CNAME");
869 log.error(tailMessage);
870 message.setStatus(SecurityStatus.BOGUS);
874 log.error("Reclassified the tail of a CNAME response as an ANY response");
875 log.error(tailMessage);
876 message.setStatus(SecurityStatus.BOGUS);
880 log.error("unhandled response subtype: " + tailType);
881 message.setStatus(SecurityStatus.BOGUS);
887 * Given an "ANY" response -- a response that contains an answer
888 * to a qtype==ANY question, with answers. This consists of simply
889 * verifying all present answer/auth RRsets, with no checking that
890 * all types are present.
892 * NOTE: it may be possible to get parent-side delegation point
893 * records here, which won't all be signed. Right now, this
894 * routine relies on the upstream iterative resolver to not return
895 * these responses -- instead treating them as referrals.
897 * NOTE: RFC 4035 is silent on this issue, so this may change upon
900 * Note that by the time this method is called, the process of
901 * finding the trusted DNSKEY rrset that signs this response must
902 * already have been completed.
905 * The response to validate.
907 * The trusted DNSKEY rrset that matches the signer of the
910 private void validateAnyResponse(SMessage message, SRRset key_rrset) {
911 int qtype = message.getQType();
913 if (qtype != Type.ANY) {
914 throw new IllegalArgumentException("ANY validation called on non-ANY response.");
917 SMessage m = message;
919 // validate the ANSWER section.
920 SRRset[] rrsets = m.getSectionRRsets(Section.ANSWER);
922 for (int i = 0; i < rrsets.length; i++) {
923 int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
925 // If the (answer) rrset failed to validate, then this message is
927 if (status != SecurityStatus.SECURE) {
928 mErrorList.add("Positive response has failed ANSWER rrset: " +
930 m.setStatus(SecurityStatus.BOGUS);
936 // validate the AUTHORITY section as well - this will be the NS rrset
937 // (which could be missing, no problem)
938 rrsets = m.getSectionRRsets(Section.AUTHORITY);
940 for (int i = 0; i < rrsets.length; i++) {
941 int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
943 // If anything in the authority section fails to be secure, we have
945 if (status != SecurityStatus.SECURE) {
946 mErrorList.add("Positive response has failed AUTHORITY rrset: " +
948 m.setStatus(SecurityStatus.BOGUS);
954 log.trace("Successfully validated positive ANY response");
955 m.setStatus(SecurityStatus.SECURE);
959 * Validate a NOERROR/NODATA signed response -- a response that
960 * has a NOERROR Rcode but no ANSWER section RRsets. This consists
961 * of verifying the authority section rrsets and making certain
962 * that the authority section NSEC/NSEC3s proves that the qname
963 * does exist and the qtype doesn't.
965 * Note that by the time this method is called, the process of
966 * finding the trusted DNSKEY rrset that signs this response must
967 * already have been completed.
970 * The response to validate.
972 * The request that generated this response.
974 * The trusted DNSKEY rrset that signs this response.
976 private void validateNodataResponse(SMessage message,
978 List<String> errorList) {
979 Name qname = message.getQName();
980 int qtype = message.getQType();
982 SMessage m = message;
984 // Since we are here, there must be nothing in the ANSWER
985 // section to validate.
987 // validate the AUTHORITY section
988 SRRset[] rrsets = m.getSectionRRsets(Section.AUTHORITY);
990 // If true, then the NODATA has been proven.
991 boolean hasValidNSEC = false;
993 // for wildcard NODATA responses. This is the proven closest
997 // for wildcard NODATA responses. This is the wildcard NSEC.
998 NSECRecord wc = null;
1000 // A collection of NSEC3 RRs found in the authority section.
1001 List<NSEC3Record> nsec3s = null;
1003 // The RRSIG signer field for the NSEC3 RRs.
1004 Name nsec3Signer = null;
1006 for (int i = 0; i < rrsets.length; i++) {
1007 int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
1009 if (status != SecurityStatus.SECURE) {
1010 mErrorList.add("NODATA response has failed AUTHORITY rrset: " +
1012 m.setStatus(SecurityStatus.BOGUS);
1017 // If we encounter an NSEC record, try to use it to prove NODATA.
1018 // This needs to handle the ENT NODATA case.
1019 if (rrsets[i].getType() == Type.NSEC) {
1020 NSECRecord nsec = (NSECRecord) rrsets[i].first();
1022 if (ValUtils.nsecProvesNodata(nsec, qname, qtype)) {
1023 hasValidNSEC = true;
1025 if (nsec.getName().isWild()) {
1028 } else if (ValUtils.nsecProvesNameError(nsec, qname, rrsets[i].getSignerName())) {
1029 ce = ValUtils.closestEncloser(qname, nsec);
1033 // Collect any NSEC3 records present.
1034 if (rrsets[i].getType() == Type.NSEC3) {
1035 if (nsec3s == null) {
1036 nsec3s = new ArrayList<NSEC3Record>();
1039 nsec3s.add((NSEC3Record) rrsets[i].first());
1040 nsec3Signer = rrsets[i].getSignerName();
1044 // check to see if we have a wildcard NODATA proof.
1046 // The wildcard NODATA is 1 NSEC proving that qname does not
1047 // exists (and also proving what the closest encloser is), and
1048 // 1 NSEC showing the matching wildcard, which must be
1049 // *.closest_encloser.
1050 if ((ce != null) || (wc != null)) {
1052 Name wc_name = new Name("*", ce);
1054 if (!wc_name.equals(wc.getName())) {
1055 hasValidNSEC = false;
1057 } catch (TextParseException e) {
1062 NSEC3ValUtils.stripUnknownAlgNSEC3s(nsec3s);
1064 if (!hasValidNSEC && (nsec3s != null) && (nsec3s.size() > 0)) {
1065 // try to prove NODATA with our NSEC3 record(s)
1066 hasValidNSEC = NSEC3ValUtils.proveNodata(nsec3s, qname, qtype,
1067 nsec3Signer, errorList);
1070 if (!hasValidNSEC) {
1071 log.debug("NODATA response failed to prove NODATA " +
1072 "status with NSEC/NSEC3");
1073 log.trace("Failed NODATA:\n" + m);
1074 mErrorList.add("NODATA response failed to prove NODATA status with NSEC/NSEC3");
1075 m.setStatus(SecurityStatus.BOGUS);
1080 log.trace("successfully validated NODATA response.");
1081 m.setStatus(SecurityStatus.SECURE);
1085 * Validate a NAMEERROR signed response -- a response that has a
1086 * NXDOMAIN Rcode. This consists of verifying the authority
1087 * section rrsets and making certain that the authority section
1088 * NSEC proves that the qname doesn't exist and the covering
1089 * wildcard also doesn't exist..
1091 * Note that by the time this method is called, the process of
1092 * finding the trusted DNSKEY rrset that signs this response must
1093 * already have been completed.
1096 * The response to validate.
1098 * The request that generated this response.
1100 * The trusted DNSKEY rrset that signs this response.
1102 private void validateNameErrorResponse(SMessage message, SRRset key_rrset) {
1103 Name qname = message.getQName();
1105 SMessage m = message;
1107 if (message.getCount(Section.ANSWER) > 0) {
1108 log.warn("NameError response contained records in the ANSWER SECTION");
1109 mErrorList.add("NameError response contained records in the ANSWER SECTION");
1110 message.setStatus(SecurityStatus.INVALID);
1115 // Validate the authority section -- all RRsets in the authority section
1116 // must be signed and valid.
1117 // In addition, the NSEC record(s) must prove the NXDOMAIN condition.
1118 boolean hasValidNSEC = false;
1119 boolean hasValidWCNSEC = false;
1120 SRRset[] rrsets = m.getSectionRRsets(Section.AUTHORITY);
1121 List<NSEC3Record> nsec3s = null;
1122 Name nsec3Signer = null;
1124 for (int i = 0; i < rrsets.length; i++) {
1125 int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
1127 if (status != SecurityStatus.SECURE) {
1128 mErrorList.add("NameError response has failed AUTHORITY rrset: " +
1130 m.setStatus(SecurityStatus.BOGUS);
1135 if (rrsets[i].getType() == Type.NSEC) {
1136 NSECRecord nsec = (NSECRecord) rrsets[i].first();
1138 if (ValUtils.nsecProvesNameError(nsec, qname, rrsets[i].getSignerName())) {
1139 hasValidNSEC = true;
1142 if (ValUtils.nsecProvesNoWC(nsec, qname, rrsets[i].getSignerName())) {
1143 hasValidWCNSEC = true;
1147 if (rrsets[i].getType() == Type.NSEC3) {
1148 if (nsec3s == null) {
1149 nsec3s = new ArrayList<NSEC3Record>();
1152 nsec3s.add((NSEC3Record) rrsets[i].first());
1153 nsec3Signer = rrsets[i].getSignerName();
1157 NSEC3ValUtils.stripUnknownAlgNSEC3s(nsec3s);
1159 if ((nsec3s != null) && (nsec3s.size() > 0)) {
1160 log.debug("Validating nxdomain: using NSEC3 records");
1162 // Attempt to prove name error with nsec3 records.
1163 if (NSEC3ValUtils.allNSEC3sIgnoreable(nsec3s, key_rrset, mVerifier)) {
1164 // log.debug("all NSEC3s were validated but ignored.");
1165 m.setStatus(SecurityStatus.INSECURE);
1170 hasValidNSEC = NSEC3ValUtils.proveNameError(nsec3s, qname, nsec3Signer, mErrorList);
1172 // Note that we assume that the NSEC3ValUtils proofs
1173 // encompass the wildcard part of the proof.
1174 hasValidWCNSEC = hasValidNSEC;
1177 // If the message fails to prove either condition, it is bogus.
1178 if (!hasValidNSEC) {
1179 mErrorList.add("NameError response has failed to prove qname does not exist");
1180 m.setStatus(SecurityStatus.BOGUS);
1185 if (!hasValidWCNSEC) {
1186 mErrorList.add("NameError response has failed to prove covering wildcard does not exist");
1187 m.setStatus(SecurityStatus.BOGUS);
1192 // Otherwise, we consider the message secure.
1193 log.trace("successfully validated NAME ERROR response.");
1194 m.setStatus(SecurityStatus.SECURE);
1197 public byte validateMessage(SMessage message, Name zone) {
1199 if (!zone.isAbsolute()) {
1201 zone = Name.concatenate(zone, Name.root);
1202 } catch (NameTooLongException e) {
1205 return SecurityStatus.UNCHECKED;
1209 // It is unclear if we should actually normalize our
1210 // responses Instead, maybe we should just fail if they are
1212 message = normalize(message);
1214 if (!needsValidation(message)) {
1215 return SecurityStatus.UNCHECKED;
1218 SRRset key_rrset = findKeys(message);
1220 if (key_rrset == null) {
1221 mErrorList.add("Failed to find matching DNSKEYs for the response");
1222 return SecurityStatus.BOGUS;
1225 ValUtils.ResponseType subtype = ValUtils.classifyResponse(message, zone);
1229 log.trace("Validating a positive response");
1230 validatePositiveResponse(message, key_rrset);
1234 validateReferral(message, key_rrset);
1238 log.trace("Validating a NODATA response");
1239 validateNodataResponse(message, key_rrset, mErrorList);
1243 log.trace("Validating a NXDOMAIN response");
1244 validateNameErrorResponse(message, key_rrset);
1248 log.trace("Validating a CNAME response");
1249 validateCNAMEResponse(message, key_rrset);
1253 log.trace("Validating a positive ANY response");
1254 validateAnyResponse(message, key_rrset);
1258 log.error("unhandled response subtype: " + subtype);
1261 return message.getSecurityStatus().getStatus();
1264 public byte validateMessage(Message message, String zone)
1265 throws TextParseException {
1266 SMessage sm = new SMessage(message);
1267 Name z = Name.fromString(zone);
1269 return validateMessage(sm, z);
1272 public byte validateMessage(byte[] messagebytes, String zone)
1273 throws IOException {
1274 Message message = new Message(messagebytes);
1275 return validateMessage(message, zone);
1278 public byte validateMessage(String b64messagebytes, String zone)
1279 throws IOException {
1280 byte[] messagebytes = base64.fromString(b64messagebytes);
1281 return validateMessage(messagebytes, zone);
1284 public List<String> listTrustedKeys() {
1285 return mTrustedKeys.listTrustAnchors();
1288 public List<String> getErrorList() {