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