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