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