80dc0b713d4724e0acce88bb291ef413b0cff793
[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)
64                 && (m.getCount(Section.ANSWER) == 0)) {
65             return ResponseType.NAMEERROR;
66         }
67
68         // If rcode isn't NXDOMAIN or NOERROR, it is a throwaway response.
69         if (m.getRcode() != Rcode.NOERROR) {
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             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 primarily
125      * to determine if the response is, in fact, signed at all, and, if so, what
126      * 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         // FIXME: 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 upgrade the
229      * 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 RRsets. This will
250      * only upgrade the status of the message (i.e., set to more secure, not
251      * less) and all of the RRsets.
252      * 
253      * @param m
254      * @param security
255      *            KeyEntry ke;
256      * 
257      *            SMessage m = response.getSMessage(); SRRset ans_rrset =
258      *            m.findAnswerRRset(qname, qtype, qclass);
259      * 
260      *            ke = verifySRRset(ans_rrset, key_rrset); if
261      *            (ans_rrset.getSecurityStatus() != SecurityStatus.SECURE) {
262      *            return; } key_rrset = ke.getRRset();
263      */
264     public static void setMessageSecurity(SMessage m, byte security) {
265         if (m == null) {
266             return;
267         }
268
269         int cur_sec = m.getStatus();
270
271         if ((cur_sec == SecurityStatus.UNCHECKED) || (security > cur_sec)) {
272             m.setStatus(security);
273         }
274
275         for (int section = Section.ANSWER; section <= Section.ADDITIONAL; section++) {
276             SRRset[] rrsets = m.getSectionRRsets(section);
277
278             for (int i = 0; i < rrsets.length; i++) {
279                 setRRsetSecurity(rrsets[i], security);
280             }
281         }
282     }
283
284     /**
285      * Given an SRRset that is signed by a DNSKEY found in the key_rrset, verify
286      * it. This will return the status (either BOGUS or SECURE) and set that
287      * status in rrset.
288      * 
289      * @param rrset
290      *            The SRRset to verify.
291      * @param key_rrset
292      *            The set of keys to verify against.
293      * @return The status (BOGUS or SECURE).
294      */
295     public byte verifySRRset(SRRset rrset, SRRset key_rrset) {
296         String rrset_name = rrset.getName() + "/"
297                 + Type.string(rrset.getType()) + "/"
298                 + DClass.string(rrset.getDClass());
299
300         if (rrset.getSecurityStatus() == SecurityStatus.SECURE) {
301             log.trace("verifySRRset: rrset <" + rrset_name
302                     + "> previously found to be SECURE");
303
304             return SecurityStatus.SECURE;
305         }
306
307         byte status = mVerifier.verify(rrset, key_rrset);
308
309         if (status != SecurityStatus.SECURE) {
310             log.debug("verifySRRset: rrset <" + rrset_name
311                     + "> found to be BAD");
312             status = SecurityStatus.BOGUS;
313         } else {
314             log.trace("verifySRRset: rrset <" + rrset_name
315                     + "> found to be SECURE");
316         }
317
318         rrset.setSecurityStatus(status);
319
320         return status;
321     }
322
323     /**
324      * Determine if a given type map has a given type.
325      * 
326      * @param types
327      *            The type map from the NSEC record.
328      * @param type
329      *            The type to look for.
330      * @return true if the type is present in the type map, false otherwise.
331      */
332     public static boolean typeMapHasType(int[] types, int type) {
333         for (int i = 0; i < types.length; i++) {
334             if (types[i] == type) {
335                 return true;
336             }
337         }
338
339         return false;
340     }
341
342     @SuppressWarnings("unchecked")
343     public static RRSIGRecord rrsetFirstSig(RRset rrset) {
344         for (Iterator i = rrset.sigs(); i.hasNext();) {
345             return (RRSIGRecord) i.next();
346         }
347
348         return null;
349     }
350
351     /**
352      * Finds the longest common name between two domain names.
353      * 
354      * @param domain1
355      * @param domain2
356      * @return
357      */
358     public static Name longestCommonName(Name domain1, Name domain2) {
359         if ((domain1 == null) || (domain2 == null)) {
360             return null;
361         }
362
363         // for now, do this in a a fairly brute force way
364         // FIXME: convert this to direct operations on the byte[]
365         int d1_labels = domain1.labels();
366         int d2_labels = domain2.labels();
367
368         int l = (d1_labels < d2_labels) ? d1_labels : d2_labels;
369
370         for (int i = l; i > 0; i--) {
371             Name n1 = new Name(domain1, d1_labels - i);
372             Name n2 = new Name(domain2, d2_labels - i);
373
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
386         if (plabels >= clabels) {
387             return false;
388         }
389
390         Name n = new Name(child, clabels - plabels);
391
392         return parent.equals(n);
393     }
394
395     /**
396      * Determine by looking at a signed RRset whether or not the rrset name was
397      * the result of a wildcard expansion.
398      * 
399      * @param rrset
400      *            The rrset to examine.
401      * @return true if the rrset is a wildcard expansion. This will return false
402      *         for all unsigned rrsets.
403      */
404     public static boolean rrsetIsWildcardExpansion(RRset rrset) {
405         if (rrset == null) {
406             return false;
407         }
408
409         RRSIGRecord rrsig = rrsetFirstSig(rrset);
410
411         if ((rrset.getName().labels() - 1) > rrsig.getLabels()) {
412             return true;
413         }
414
415         return false;
416     }
417
418     /**
419      * Determine by looking at a signed RRset whether or not the RRset name was
420      * the result of a wildcard expansion. If so, return the name of the
421      * generating wildcard.
422      * 
423      * @param rrset
424      *            The rrset to check.
425      * @return the wildcard name, if the rrset was synthesized from a wildcard.
426      *         null if not.
427      */
428     public static Name rrsetWildcard(RRset rrset) {
429         if (rrset == null) {
430             return null;
431         }
432
433         RRSIGRecord rrsig = rrsetFirstSig(rrset);
434
435         // if the RRSIG label count is shorter than the number of actual labels,
436         // then this rrset was synthesized from a wildcard.
437         // Note that the RRSIG label count doesn't count the root label.
438         int label_diff = (rrset.getName().labels() - 1) - rrsig.getLabels();
439
440         if (label_diff > 0) {
441             return rrset.getName().wild(label_diff);
442         }
443
444         return null;
445     }
446
447     public static Name closestEncloser(Name domain, NSECRecord nsec) {
448         Name n1 = longestCommonName(domain, nsec.getName());
449         Name n2 = longestCommonName(domain, nsec.getNext());
450
451         return (n1.labels() > n2.labels()) ? n1 : n2;
452     }
453
454     public static Name nsecWildcard(Name domain, NSECRecord nsec) {
455         try {
456             return new Name("*", closestEncloser(domain, nsec));
457         } catch (TextParseException e) {
458             // this should never happen.
459             return null;
460         }
461     }
462
463     /**
464      * Determine if the given NSEC proves a NameError (NXDOMAIN) for a given
465      * qname.
466      * 
467      * @param nsec
468      *            The NSEC to check.
469      * @param qname
470      *            The qname to check against.
471      * @param signerName
472      *            The signer name of the NSEC record, which is used as the zone
473      *            name, for a more precise (but perhaps more brittle) check for
474      *            the last NSEC in a zone.
475      * @return true if the NSEC proves the condition.
476      */
477     public static boolean nsecProvesNameError(NSECRecord nsec, Name qname,
478             Name signerName) {
479         Name owner = nsec.getName();
480         Name next = nsec.getNext();
481
482         // If NSEC owner == qname, then this NSEC proves that qname exists.
483         if (qname.equals(owner)) {
484             return false;
485         }
486
487         // If NSEC is a parent of qname, we need to check the type map
488         // If the parent name has a DNAME or is a delegation point, then this
489         // NSEC is being misused.
490         boolean hasBadType = typeMapHasType(nsec.getTypes(), Type.DNAME)
491                 || (typeMapHasType(nsec.getTypes(), Type.NS) && !typeMapHasType(
492                         nsec.getTypes(), Type.SOA));
493
494         if (qname.subdomain(owner) && hasBadType) {
495             return false;
496         }
497
498         if (((qname.compareTo(owner) > 0) && (qname.compareTo(next) < 0))
499                 || signerName.equals(next)) {
500             return true;
501         }
502
503         return false;
504     }
505
506     /**
507      * Determine if a NSEC record proves the non-existence of a wildcard that
508      * could have produced qname.
509      * 
510      * @param nsec
511      *            The nsec to check.
512      * @param qname
513      *            The qname to check against.
514      * @param signerName
515      *            The signer name for the NSEC rrset, used as the zone name.
516      * @return true if the NSEC proves the condition.
517      */
518     public static boolean nsecProvesNoWC(NSECRecord nsec, Name qname,
519             Name signerName) {
520         Name owner = nsec.getName();
521         Name next = nsec.getNext();
522
523         int qname_labels = qname.labels();
524         int signer_labels = signerName.labels();
525
526         for (int i = qname_labels - signer_labels; i > 0; i--) {
527             Name wc_name = qname.wild(i);
528
529             if ((wc_name.compareTo(owner) > 0)
530                     && ((wc_name.compareTo(next) < 0) || signerName
531                             .equals(next))) {
532                 return true;
533             }
534         }
535
536         return false;
537     }
538
539     /**
540      * Determine if a NSEC proves the NOERROR/NODATA conditions. This will also
541      * handle the empty non-terminal (ENT) case and partially handle the
542      * wildcard case. If the ownername of 'nsec' is a wildcard, the validator
543      * must still be provided proof that qname did not directly exist and that
544      * the wildcard is, in fact, *.closest_encloser.
545      * 
546      * @param nsec
547      *            The NSEC to check
548      * @param qname
549      *            The query name to check against.
550      * @param qtype
551      *            The query type to check against.
552      * @return true if the NSEC proves the condition.
553      */
554     public static boolean nsecProvesNodata(NSECRecord nsec, Name qname,
555             int qtype) {
556         if (!nsec.getName().equals(qname)) {
557             // wildcard checking.
558
559             // If this is a wildcard NSEC, make sure that a) it was possible to
560             // have
561             // generated qname from the wildcard and b) the type map does not
562             // contain qtype. Note that this does NOT prove that this wildcard
563             // was
564             // the applicable wildcard.
565             if (nsec.getName().isWild()) {
566                 // the is the purported closest encloser.
567                 Name ce = new Name(nsec.getName(), 1);
568
569                 // The qname must be a strict subdomain of the closest encloser,
570                 // and
571                 // the qtype must be absent from the type map.
572                 if (!strictSubdomain(qname, ce)
573                         || typeMapHasType(nsec.getTypes(), qtype)) {
574                     return false;
575                 }
576
577                 return true;
578             }
579
580             // empty-non-terminal checking.
581
582             // If the nsec is proving that qname is an ENT, the nsec owner will
583             // be
584             // less than qname, and the next name will be a child domain of the
585             // qname.
586             if (strictSubdomain(nsec.getNext(), qname)
587                     && (qname.compareTo(nsec.getName()) > 0)) {
588                 return true;
589             }
590
591             // Otherwise, this NSEC does not prove ENT, so it does not prove
592             // NODATA.
593             return false;
594         }
595
596         // If the qtype exists, then we should have gotten it.
597         if (typeMapHasType(nsec.getTypes(), qtype)) {
598             return false;
599         }
600
601         // if the name is a CNAME node, then we should have gotten the CNAME
602         if (typeMapHasType(nsec.getTypes(), Type.CNAME)) {
603             return false;
604         }
605
606         // If an NS set exists at this name, and NOT a SOA (so this is a zone
607         // cut,
608         // not a zone apex), then we should have gotten a referral (or we just
609         // got
610         // the wrong NSEC).
611         if (typeMapHasType(nsec.getTypes(), Type.NS)
612                 && !typeMapHasType(nsec.getTypes(), Type.SOA)) {
613             return false;
614         }
615
616         return true;
617     }
618
619     public static byte nsecProvesNoDS(NSECRecord nsec, Name qname) {
620         // Could check to make sure the qname is a subdomain of nsec
621         int[] types = nsec.getTypes();
622
623         if (typeMapHasType(types, Type.SOA) || typeMapHasType(types, Type.DS)) {
624             // SOA present means that this is the NSEC from the child, not the
625             // parent (so it is the wrong one)
626             // DS present means that there should have been a positive response
627             // to
628             // the DS query, so there is something wrong.
629             return SecurityStatus.BOGUS;
630         }
631
632         if (!typeMapHasType(types, Type.NS)) {
633             // If there is no NS at this point at all, then this doesn't prove
634             // anything one way or the other.
635             return SecurityStatus.INSECURE;
636         }
637
638         // Otherwise, this proves no DS.
639         return SecurityStatus.SECURE;
640     }
641
642     // These are response subtypes. They are necessary for determining the
643     // validation strategy. They have no bearing on the iterative resolution
644     // algorithm, so they are confined here.
645     public enum ResponseType {
646         UNTYPED, UNKNOWN, POSITIVE, CNAME, NODATA, NAMEERROR, ANY, REFERRAL,
647         // a referral response
648         THROWAWAY;
649         // a throwaway response (i.e., an error)
650     }
651 }