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