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