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