Issue #2. Handle CNAME responses
[captive-validator.git] / src / com / verisign / tat / dnssec / ValUtils.java
1 /***************************** -*- Java -*- ********************************\
2  *                                                                         *
3  *   Copyright (c) 2009 VeriSign, Inc. All rights reserved.                *
4  *                                                                         *
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           *
20  * documentation.                                                          *
21  *                                                                         *
22 \***************************************************************************/
23
24 package com.verisign.tat.dnssec;
25
26 import org.apache.log4j.Logger;
27
28 import org.xbill.DNS.*;
29
30 import java.security.MessageDigest;
31 import java.security.NoSuchAlgorithmException;
32
33 import java.util.Iterator;
34
35 /**
36  * This is a collection of routines encompassing the logic of validating
37  * different message types.
38  */
39 public class ValUtils {
40     private static Logger st_log = Logger.getLogger(ValUtils.class);
41     private Logger        log    = Logger.getLogger(this.getClass());
42
43     /** A local copy of the verifier object. */
44     private DnsSecVerifier mVerifier;
45
46     public ValUtils(DnsSecVerifier verifier) {
47         mVerifier = verifier;
48     }
49
50     /**
51      * Given a response, classify ANSWER responses into a subtype.
52      *
53      * @param m
54      *            The response to classify.
55      *
56      * @return A subtype ranging from UNKNOWN to NAMEERROR.
57      */
58     public static ResponseType classifyResponse(SMessage m, Name zone) {
59         SRRset[] rrsets;
60
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) && (m.getCount(Section.ANSWER) == 0)) {
64             return ResponseType.NAMEERROR;
65         }
66
67         // If rcode isn't NXDOMAIN or NOERROR, it is a throwaway response.
68         // E.g., SERVFAIL, FORMERR, REFUSED
69         if (m.getRcode() != Rcode.NOERROR && m.getRcode() != Rcode.NXDOMAIN) {
70             return ResponseType.THROWAWAY;
71         }
72
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
80             rrsets = m.getSectionRRsets(Section.AUTHORITY);
81
82             for (int i = 0; i < rrsets.length; ++i) {
83                 if ((rrsets[i].getType() == Type.NS)
84                         && strictSubdomain(rrsets[i].getName(), zone)) {
85                     return ResponseType.REFERRAL;
86                 }
87             }
88         }
89
90         // Next is NODATA
91         if (m.getCount(Section.ANSWER) == 0) {
92             return ResponseType.NODATA;
93         }
94
95         // We distinguish between CNAME response and other positive/negative
96         // responses because CNAME answers require extra processing.
97         int qtype = m.getQuestion().getType();
98
99         // We distinguish between ANY and CNAME or POSITIVE because ANY
100         // responses are validated differently.
101         if (qtype == Type.ANY) {
102             return ResponseType.ANY;
103         }
104
105         rrsets = m.getSectionRRsets(Section.ANSWER);
106
107         // Note that DNAMEs will be ignored here, unless qtype=DNAME. Unless
108         // qtype=CNAME, this will yield a CNAME response.
109         for (int i = 0; i < rrsets.length; i++) {
110             if (rrsets[i].getType() == qtype) {
111                 return ResponseType.POSITIVE;
112             }
113
114             if (rrsets[i].getType() == Type.CNAME) {
115                 return ResponseType.CNAME;
116             }
117         }
118
119         st_log.warn("Failed to classify response message:\n" + m);
120
121         return ResponseType.UNKNOWN;
122     }
123
124     /**
125      * Given a response, determine the name of the "signer". This is
126      * primarily to determine if the response is, in fact, signed at
127      * all, and, if so, what is the name of the most pertinent keyset.
128      *
129      * @param m
130      *            The response to analyze.
131      * @return a signer name, if the response is signed (even partially), or
132      *         null if the response isn't signed.
133      */
134     public Name findSigner(SMessage m) {
135         // This used to classify the message, then look in the pertinent
136         // section. Now we just find the first RRSIG in the ANSWER and AUTHORIY
137         // sections.
138         for (int section = Section.ANSWER; section < Section.ADDITIONAL; ++section) {
139             SRRset[] rrsets = m.getSectionRRsets(section);
140
141             for (int i = 0; i < rrsets.length; ++i) {
142                 Name signerName = rrsets[i].getSignerName();
143
144                 if (signerName != null) {
145                     return signerName;
146                 }
147             }
148         }
149
150         return null;
151     }
152
153     /**
154      * Given a DNSKEY record, generate the DS record from it.
155      *
156      * @param keyrec
157      *            the DNSKEY record in question.
158      * @param ds_alg
159      *            The DS digest algorithm in use.
160      * @return the corresponding {@link org.xbill.DNS.DSRecord}
161      */
162     public static byte[] calculateDSHash(DNSKEYRecord keyrec, int ds_alg) {
163         DNSOutput os = new DNSOutput();
164
165         os.writeByteArray(keyrec.getName().toWireCanonical());
166         os.writeByteArray(keyrec.rdataToWireCanonical());
167
168         try {
169             MessageDigest md = null;
170
171             switch (ds_alg) {
172             case DSRecord.SHA1_DIGEST_ID:
173                 md = MessageDigest.getInstance("SHA");
174
175                 return md.digest(os.toByteArray());
176
177             case DSRecord.SHA256_DIGEST_ID:
178                 md = MessageDigest.getInstance("SHA256");
179
180                 return md.digest(os.toByteArray());
181
182             default:
183                 st_log.warn("Unknown DS algorithm: " + ds_alg);
184
185                 return null;
186             }
187         } catch (NoSuchAlgorithmException e) {
188             st_log.error("Error using DS algorithm: " + ds_alg, e);
189
190             return null;
191         }
192     }
193
194     public static boolean supportsDigestID(int digest_id) {
195         if (digest_id == DSRecord.SHA1_DIGEST_ID) {
196             return true;
197         }
198
199         if (digest_id == DSRecord.SHA256_DIGEST_ID) {
200             return true;
201         }
202
203         return false;
204     }
205
206     /**
207      * Check to see if a type is a special DNSSEC type.
208      *
209      * @param type
210      *            The type.
211      *
212      * @return true if the type is one of the special DNSSEC types.
213      */
214     public static boolean isDNSSECType(int type) {
215         switch (type) {
216         case Type.DNSKEY:
217         case Type.NSEC:
218         case Type.DS:
219         case Type.RRSIG:
220         case Type.NSEC3:
221             return true;
222
223         default:
224             return false;
225         }
226     }
227
228     /**
229      * Set the security status of a particular RRset. This will only
230      * upgrade the security status.
231      *
232      * @param rrset
233      *            The SRRset to update.
234      * @param security
235      *            The security status.
236      */
237     public static void setRRsetSecurity(SRRset rrset, byte security) {
238         if (rrset == null) {
239             return;
240         }
241
242         int cur_sec = rrset.getSecurityStatus();
243
244         if ((cur_sec == SecurityStatus.UNCHECKED) || (security > cur_sec)) {
245             rrset.setSecurityStatus(security);
246         }
247     }
248
249     /**
250      * Set the security status of a message and all of its
251      * RRsets. This will only upgrade the status of the message (i.e.,
252      * set to more secure, not less) and all of the RRsets.
253      */
254     public static void setMessageSecurity(SMessage m, byte security) {
255         if (m == null) {
256             return;
257         }
258
259         int cur_sec = m.getStatus();
260
261         if ((cur_sec == SecurityStatus.UNCHECKED) || (security > cur_sec)) {
262             m.setStatus(security);
263         }
264
265         for (int section = Section.ANSWER; section <= Section.ADDITIONAL; section++) {
266             SRRset[] rrsets = m.getSectionRRsets(section);
267
268             for (int i = 0; i < rrsets.length; i++) {
269                 setRRsetSecurity(rrsets[i], security);
270             }
271         }
272     }
273
274     /**
275      * Given an SRRset that is signed by a DNSKEY found in the
276      * key_rrset, verify it. This will return the status (either BOGUS
277      * or SECURE) and set that status in rrset.
278      *
279      * @param rrset
280      *            The SRRset to verify.
281      * @param key_rrset
282      *            The set of keys to verify against.
283      * @return The status (BOGUS or SECURE).
284      */
285     public byte verifySRRset(SRRset rrset, SRRset key_rrset) {
286         String rrset_name = rrset.getName() + "/" + Type.string(rrset.getType()) + "/" +
287             DClass.string(rrset.getDClass());
288
289         if (rrset.getSecurityStatus() == SecurityStatus.SECURE) {
290             log.trace("verifySRRset: rrset <" + rrset_name +
291                       "> previously found to be SECURE");
292
293             return SecurityStatus.SECURE;
294         }
295
296         byte status = mVerifier.verify(rrset, key_rrset);
297
298         if (status != SecurityStatus.SECURE) {
299             log.debug("verifySRRset: rrset <" + rrset_name + "> found to be BAD");
300             status = SecurityStatus.BOGUS;
301         } else {
302             log.trace("verifySRRset: rrset <" + rrset_name + "> found to be SECURE");
303         }
304
305         rrset.setSecurityStatus(status);
306
307         return status;
308     }
309
310     /**
311      * Determine if a given type map has a given type.
312      *
313      * @param types
314      *            The type map from the NSEC record.
315      * @param type
316      *            The type to look for.
317      * @return true if the type is present in the type map, false otherwise.
318      */
319     public static boolean typeMapHasType(int[] types, int type) {
320         for (int i = 0; i < types.length; i++) {
321             if (types[i] == type) {
322                 return true;
323             }
324         }
325
326         return false;
327     }
328
329     @SuppressWarnings("rawtypes")
330     public static RRSIGRecord rrsetFirstSig(RRset rrset) {
331         for (Iterator i = rrset.sigs(); i.hasNext();) {
332             return (RRSIGRecord) i.next();
333         }
334
335         return null;
336     }
337
338     /**
339      * Finds the longest common name between two domain names.
340      *
341      * @param domain1
342      * @param domain2
343      * @return
344      */
345     public static Name longestCommonName(Name domain1, Name domain2) {
346         if ((domain1 == null) || (domain2 == null)) {
347             return null;
348         }
349
350         // for now, do this in a a fairly brute force way
351         // FIXME: convert this to direct operations on the byte[]
352         int d1_labels = domain1.labels();
353         int d2_labels = domain2.labels();
354
355         int l = (d1_labels < d2_labels) ? d1_labels : d2_labels;
356
357         for (int i = l; i > 0; i--) {
358             Name n1 = new Name(domain1, d1_labels - i);
359             Name n2 = new Name(domain2, d2_labels - i);
360
361             if (n1.equals(n2)) {
362                 return n1;
363             }
364         }
365
366         return Name.root;
367     }
368
369     public static boolean strictSubdomain(Name child, Name parent) {
370         int clabels = child.labels();
371         int plabels = parent.labels();
372
373         if (plabels >= clabels) {
374             return false;
375         }
376
377         Name n = new Name(child, clabels - plabels);
378
379         return parent.equals(n);
380     }
381
382     /**
383      * Determine by looking at a signed RRset whether or not the rrset name was
384      * the result of a wildcard expansion.
385      *
386      * @param rrset
387      *            The rrset to examine.
388      * @return true if the rrset is a wildcard expansion. This will return false
389      *         for all unsigned rrsets.
390      */
391     public static boolean rrsetIsWildcardExpansion(RRset rrset) {
392         if (rrset == null) {
393             return false;
394         }
395
396         RRSIGRecord rrsig = rrsetFirstSig(rrset);
397
398         if ((rrset.getName().labels() - 1) > rrsig.getLabels()) {
399             return true;
400         }
401
402         return false;
403     }
404
405     /**
406      * Determine by looking at a signed RRset whether or not the RRset name was
407      * the result of a wildcard expansion. If so, return the name of the
408      * generating wildcard.
409      *
410      * @param rrset
411      *            The rrset to check.
412      * @return the wildcard name, if the rrset was synthesized from a wildcard.
413      *         null if not.
414      */
415     public static Name rrsetWildcard(RRset rrset) {
416         if (rrset == null) {
417             return null;
418         }
419
420         RRSIGRecord rrsig = rrsetFirstSig(rrset);
421
422         // if the RRSIG label count is shorter than the number of actual labels,
423         // then this rrset was synthesized from a wildcard.
424         // Note that the RRSIG label count doesn't count the root label.
425         int label_diff = (rrset.getName().labels() - 1) - rrsig.getLabels();
426
427         if (label_diff > 0) {
428             Name wc = rrset.getName().wild(label_diff);
429             // if the name was the wildcard itself, this isn't actually a
430             // wildcard expansion.
431             if (wc.equals(rrset.getName())) {
432                 return null;
433             }
434             return wc;
435         }
436
437         return null;
438     }
439
440     public static Name closestEncloser(Name domain, NSECRecord nsec) {
441         Name n1 = longestCommonName(domain, nsec.getName());
442         Name n2 = longestCommonName(domain, nsec.getNext());
443
444         return (n1.labels() > n2.labels()) ? n1 : n2;
445     }
446
447     public static Name nsecWildcard(Name domain, NSECRecord nsec) {
448         try {
449             return new Name("*", closestEncloser(domain, nsec));
450         } catch (TextParseException e) {
451             // this should never happen.
452             return null;
453         }
454     }
455
456     /**
457      * Determine if the given NSEC proves a NameError (NXDOMAIN) for a
458      * given qname.
459      *
460      * @param nsec
461      *            The NSEC to check.
462      * @param qname
463      *            The qname to check against.
464      * @param signerName
465      *            The signer name of the NSEC record, which is used as the zone
466      *            name, for a more precise (but perhaps more brittle) check for
467      *            the last NSEC in a zone.
468      * @return true if the NSEC proves the condition.
469      */
470     public static boolean nsecProvesNameError(NSECRecord nsec, Name qname,
471             Name signerName) {
472         Name owner = nsec.getName();
473         Name next  = nsec.getNext();
474
475         // If NSEC owner == qname, then this NSEC proves that qname exists.
476         if (qname.equals(owner)) {
477             return false;
478         }
479
480         // If NSEC is a parent of qname, we need to check the type map
481         // If the parent name has a DNAME or is a delegation point, then this
482         // NSEC is being misused.
483         boolean hasBadType = typeMapHasType(nsec.getTypes(), Type.DNAME) ||
484             (typeMapHasType(nsec.getTypes(), Type.NS) && !typeMapHasType(nsec.getTypes(), Type.SOA));
485
486         if (qname.subdomain(owner) && hasBadType) {
487             return false;
488         }
489
490         if (((qname.compareTo(owner) > 0) && (qname.compareTo(next) < 0)) || signerName.equals(next)) {
491             return true;
492         }
493
494         return false;
495     }
496
497     /**
498      * Determine if a NSEC record proves the non-existence of a
499      * wildcard that could have produced qname.
500      *
501      * @param nsec
502      *            The nsec to check.
503      * @param qname
504      *            The qname to check against.
505      * @param signerName
506      *            The signer name for the NSEC rrset, used as the zone name.
507      * @return true if the NSEC proves the condition.
508      */
509     public static boolean nsecProvesNoWC(NSECRecord nsec, Name qname, Name signerName) {
510         Name owner = nsec.getName();
511         Name next  = nsec.getNext();
512
513         int qname_labels  = qname.labels();
514         int signer_labels = signerName.labels();
515
516         for (int i = qname_labels - signer_labels; i > 0; i--) {
517             Name wc_name = qname.wild(i);
518
519             if ((wc_name.compareTo(owner) > 0) &&
520                 ((wc_name.compareTo(next) < 0) || signerName.equals(next))) {
521                 return true;
522             }
523         }
524
525         return false;
526     }
527
528     /**
529      * Determine if a NSEC proves the NOERROR/NODATA conditions. This
530      * will also handle the empty non-terminal (ENT) case and
531      * partially handle the wildcard case. If the ownername of 'nsec'
532      * is a wildcard, the validator must still be provided proof that
533      * qname did not directly exist and that the wildcard is, in fact,
534      * *.closest_encloser.
535      *
536      * @param nsec
537      *            The NSEC to check
538      * @param qname
539      *            The query name to check against.
540      * @param qtype
541      *            The query type to check against.
542      * @return true if the NSEC proves the condition.
543      */
544     public static boolean nsecProvesNodata(NSECRecord nsec, Name qname, int qtype) {
545         if (!nsec.getName().equals(qname)) {
546             // wildcard checking.
547
548             // If this is a wildcard NSEC, make sure that a) it was
549             // possible to have generated qname from the wildcard and
550             // b) the type map does not contain qtype. Note that this
551             // does NOT prove that this wildcard was the applicable
552             // wildcard.
553             if (nsec.getName().isWild()) {
554                 // the is the purported closest encloser.
555                 Name ce = new Name(nsec.getName(), 1);
556
557                 // The qname must be a strict subdomain of the closest
558                 // encloser, and the qtype must be absent from the
559                 // type map.
560                 if (!strictSubdomain(qname, ce) || typeMapHasType(nsec.getTypes(), qtype)) {
561                     return false;
562                 }
563
564                 return true;
565             }
566
567             // empty-non-terminal checking.
568
569             // If the nsec is proving that qname is an ENT, the nsec
570             // owner will be less than qname, and the next name will
571             // be a child domain of the qname.
572             if (strictSubdomain(nsec.getNext(), qname)
573                     && (qname.compareTo(nsec.getName()) > 0)) {
574                 return true;
575             }
576
577             // Otherwise, this NSEC does not prove ENT, so it does not
578             // prove NODATA.
579             return false;
580         }
581
582         // If the qtype exists, then we should have gotten it.
583         if (typeMapHasType(nsec.getTypes(), qtype)) {
584             return false;
585         }
586
587         // if the name is a CNAME node, then we should have gotten the
588         // CNAME
589         if (typeMapHasType(nsec.getTypes(), Type.CNAME)) {
590             return false;
591         }
592
593         // If an NS set exists at this name, and NOT a SOA (so this is
594         // a zone cut, not a zone apex), then we should have gotten a
595         // referral (or we just got the wrong NSEC).
596         if (typeMapHasType(nsec.getTypes(), Type.NS) &&
597             !typeMapHasType(nsec.getTypes(), Type.SOA)) {
598
599             return false;
600         }
601
602         return true;
603     }
604
605     public static byte nsecProvesNoDS(NSECRecord nsec, Name qname) {
606         // Could check to make sure the qname is a subdomain of nsec
607         int[] types = nsec.getTypes();
608
609         if (typeMapHasType(types, Type.SOA) || typeMapHasType(types, Type.DS)) {
610             // SOA present means that this is the NSEC from the child,
611             // not the parent (so it is the wrong one) DS present
612             // means that there should have been a positive response
613             // to the DS query, so there is something wrong.
614             return SecurityStatus.BOGUS;
615         }
616
617         if (!typeMapHasType(types, Type.NS)) {
618             // If there is no NS at this point at all, then this
619             // doesn't prove anything one way or the other.
620             return SecurityStatus.INSECURE;
621         }
622
623         // Otherwise, this proves no DS.
624         return SecurityStatus.SECURE;
625     }
626
627     // These are response subtypes. They are necessary for determining
628     // the validation strategy. They have no bearing on the iterative
629     // resolution algorithm, so they are confined here.
630     public enum ResponseType {
631         UNTYPED, UNKNOWN, POSITIVE, CNAME, NODATA, NAMEERROR, ANY, REFERRAL, // a referral response
632         THROWAWAY; // a throwaway response (i.e., an error)
633     }
634 }