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