remove some warnings by using java 5 features
[captive-validator.git] / src / com / versign / tat / dnssec / DnsSecVerifier.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.util.*;
32 import java.io.*;
33 import java.security.*;
34
35 import org.xbill.DNS.*;
36 import org.xbill.DNS.security.*;
37
38 import com.versign.tat.dnssec.SecurityStatus;
39 import com.versign.tat.dnssec.Util;
40
41
42 /**
43  * A class for performing basic DNSSEC verification. The DNSJAVA package
44  * contains a similar class. This is a re-implementation that allows us to have
45  * finer control over the validation process.
46  */
47 public class DnsSecVerifier
48 {
49   public static final int UNKNOWN = 0;
50   public static final int RSA = 1;
51   public static final int DSA = 2;
52
53   /**
54    * This is a mapping of DNSSEC algorithm numbers/private identifiers to JCA
55    * algorithm identifiers.
56    */
57   private HashMap<Integer, AlgEntry> mAlgorithmMap;
58
59   private static class AlgEntry
60   {
61     public String    jcaName;
62     public boolean   isDSA;
63     public int       dnssecAlg;
64
65     public AlgEntry(String name, int dnssecAlg, boolean isDSA)
66     {
67       jcaName = name;
68       this.dnssecAlg = dnssecAlg;
69       this.isDSA = isDSA;
70     }
71   }
72
73   public DnsSecVerifier()
74   {
75     mAlgorithmMap = new HashMap<Integer, AlgEntry>();
76
77     // set the default algorithm map.
78     mAlgorithmMap.put(new Integer(DNSSEC.RSAMD5), new AlgEntry("MD5withRSA",
79         DNSSEC.RSAMD5, false));
80     mAlgorithmMap.put(new Integer(DNSSEC.DSA), new AlgEntry("SHA1withDSA", DNSSEC.DSA,
81         true));
82     mAlgorithmMap.put(new Integer(DNSSEC.RSASHA1), new AlgEntry(
83         "SHA1withRSA", DNSSEC.RSASHA1, false));
84   }
85
86   private boolean isDSA(int algorithm)
87   {
88     // shortcut the standard algorithms
89     if (algorithm == DNSSEC.DSA) return true;
90     if (algorithm == DNSSEC.RSASHA1) return false;
91     if (algorithm == DNSSEC.RSAMD5) return false;
92     
93     AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
94     if (entry != null) return entry.isDSA;
95     return false;
96   }
97
98   public void init(Properties config)
99   {
100     if (config == null) return;
101
102     // Algorithm configuration
103
104     // For now, we just accept new identifiers for existing algoirthms.
105     // FIXME: handle private identifiers.
106     List<Util.ConfigEntry> aliases = Util.parseConfigPrefix(config, "dns.algorithm.");
107
108     for (Util.ConfigEntry entry : aliases) {
109       Integer alg_alias = new Integer(Util.parseInt(entry.key, -1));
110       Integer alg_orig = new Integer(Util.parseInt(entry.value, -1));
111
112       if (!mAlgorithmMap.containsKey(alg_orig))
113       {
114 //        log.warn("Unable to alias " + alg_alias + " to unknown algorithm "
115 //            + alg_orig);
116         continue;
117       }
118
119       if (mAlgorithmMap.containsKey(alg_alias))
120       {
121 //        log.warn("Algorithm alias " + alg_alias
122 //            + " is already defined and cannot be redefined");
123         continue;
124       }
125
126       mAlgorithmMap.put(alg_alias, mAlgorithmMap.get(alg_orig));
127     }
128
129     // for debugging purposes, log the entire algorithm map table.
130 //    for (Integer alg : mAlgorithmMap.keySet()) {
131 //      AlgEntry entry =  mAlgorithmMap.get(alg);
132 //      if (entry == null) 
133 //        log.warn("DNSSEC alg " + alg + " has a null entry!");
134 //      else
135 //        log.debug("DNSSEC alg " + alg + " maps to " + entry.jcaName
136 //            + " (" + entry.dnssecAlg + ")");
137 //    }
138   }
139
140   /**
141    * Find the matching DNSKEY(s) to an RRSIG within a DNSKEY rrset. Normally
142    * this will only return one DNSKEY. It can return more than one, since
143    * KeyID/Footprints are not guaranteed to be unique.
144    * 
145    * @param dnskey_rrset The DNSKEY rrset to search.
146    * @param signature The RRSIG to match against.
147    * @return A List contains a one or more DNSKEYRecord objects, or null if a
148    *         matching DNSKEY could not be found.
149    */
150   @SuppressWarnings("unchecked")
151 private List<DNSKEYRecord> findKey(RRset dnskey_rrset, RRSIGRecord signature)
152   {
153     if (!signature.getSigner().equals(dnskey_rrset.getName()))
154     {
155 //      log.trace("findKey: could not find appropriate key because "
156 //          + "incorrect keyset was supplied. Wanted: " + signature.getSigner()
157 //          + ", got: " + dnskey_rrset.getName());
158       return null;
159     }
160
161     int keyid = signature.getFootprint();
162     int alg = signature.getAlgorithm();
163
164     List<DNSKEYRecord> res = new ArrayList<DNSKEYRecord>(dnskey_rrset.size());
165
166     for (Iterator i = dnskey_rrset.rrs(); i.hasNext();)
167     {
168       DNSKEYRecord r = (DNSKEYRecord) i.next();
169       if (r.getAlgorithm() == alg && r.getFootprint() == keyid)
170       {
171         res.add(r);
172       }
173     }
174
175     if (res.size() == 0)
176     {
177 //      log.trace("findKey: could not find a key matching "
178 //          + "the algorithm and footprint in supplied keyset. ");
179       return null;
180     }
181     return res;
182   }
183
184   /**
185    * Check to see if a signature looks valid (i.e., matches the rrset in
186    * question, in the validity period, etc.)
187    * 
188    * @param rrset The rrset that the signature belongs to.
189    * @param sigrec The signature record to check.
190    * @return A value of DNSSEC.Secure if it looks OK, DNSSEC.Faile if it looks
191    *         bad.
192    */
193   private byte checkSignature(RRset rrset, RRSIGRecord sigrec)
194   {
195     if (rrset == null || sigrec == null) return DNSSEC.Failed;
196     if (!rrset.getName().equals(sigrec.getName()))
197     {
198 //      log.debug("Signature name does not match RRset name");
199       return SecurityStatus.BOGUS;
200     }
201     if (rrset.getType() != sigrec.getTypeCovered())
202     {
203 //      log.debug("Signature type does not match RRset type");
204       return SecurityStatus.BOGUS;
205     }
206
207     Date now = new Date();
208     Date start = sigrec.getTimeSigned();
209     Date expire = sigrec.getExpire();
210     if (now.before(start))
211     {
212 //      log.debug("Signature is not yet valid");
213       return SecurityStatus.BOGUS;
214     }
215
216     if (now.after(expire))
217     {
218 //      log.debug("Signature has expired (now = " + now + ", sig expires = "
219 //          + expire);
220       return SecurityStatus.BOGUS;
221     }
222
223     return SecurityStatus.SECURE;
224   }
225
226   public PublicKey parseDNSKEY(DNSKEYRecord key)
227   {
228     AlgEntry ae = (AlgEntry) mAlgorithmMap
229         .get(new Integer(key.getAlgorithm()));
230     if (key.getAlgorithm() != ae.dnssecAlg)
231     {
232       // Recast the DNSKEYRecord in question as one using the offical
233       // algorithm, to work around the lack of alias support in the underlying
234       // KEYConverter class from DNSjava
235
236       key = new DNSKEYRecord(key.getName(), key.getDClass(), key.getTTL(),
237           key.getFlags(), key.getProtocol(), ae.dnssecAlg, key.getKey());
238     }
239
240     return KEYConverter.parseRecord(key);
241   }
242   
243   
244   /**
245    * Actually cryptographically verify a signature over the rrset. The RRSIG
246    * record must match the rrset being verified (see checkSignature).
247    * 
248    * @param rrset The rrset to verify.
249    * @param sigrec The signature to verify with.
250    * @param key The (public) key associated with the RRSIG record.
251    * @return A security status code: SECURE if it worked, BOGUS if not,
252    *         UNCHECKED if we just couldn't actually do the function.
253    */
254   public byte verifySignature(RRset rrset, RRSIGRecord sigrec,
255       DNSKEYRecord key)
256   {
257     try
258     {
259       PublicKey pk = parseDNSKEY(key);
260
261       if (pk == null)
262       {
263 //        log.warn("Could not convert DNSKEY record to a JCA public key: "
264 //            + key);
265         return SecurityStatus.UNCHECKED;
266       }
267
268       byte[] data = SignUtils.generateSigData(rrset, sigrec);
269
270       Signature signer = getSignature(sigrec.getAlgorithm());
271       if (signer == null)
272       {
273         return SecurityStatus.BOGUS;
274       }
275       
276       signer.initVerify(pk);
277       signer.update(data);
278
279       byte[] sig = sigrec.getSignature();
280       if (isDSA(sigrec.getAlgorithm()))
281       {
282         sig = SignUtils.convertDSASignature(sig);
283       }
284       if (!signer.verify(sig))
285       {
286 //        log.info("Signature failed to verify cryptographically");
287 //        log.debug("Failed signature: " + sigrec);
288         return SecurityStatus.BOGUS;
289       }
290 //      log.trace("Signature verified: " + sigrec);
291       return SecurityStatus.SECURE;
292     }
293     catch (IOException e)
294     {
295 //      log.error("I/O error", e);
296     }
297     catch (GeneralSecurityException e)
298     {
299 //      log.error("Security error", e);
300     }
301
302     // FIXME: Since I'm not sure what would cause an exception here (failure
303     // to have the required crypto?)
304     // We default to UNCHECKED instead of BOGUS. This could be wrong.
305     return SecurityStatus.UNCHECKED;
306
307   }
308
309   /**
310    * Verify an RRset against a particular signature.
311    * 
312    * @return DNSSEC.Secure if the signature verfied, DNSSEC.Failed if it did
313    *         not verify (for any reason), and DNSSEC.Insecure if verification
314    *         could not be completed (usually because the public key was not
315    *         available).
316    */
317   public byte verifySignature(RRset rrset, RRSIGRecord sigrec, RRset key_rrset)
318   {
319     byte result = checkSignature(rrset, sigrec);
320     if (result != SecurityStatus.SECURE) return result;
321
322     List<DNSKEYRecord> keys = findKey(key_rrset, sigrec);
323
324     if (keys == null)
325     {
326 //      log.trace("could not find appropriate key");
327       return SecurityStatus.BOGUS;
328     }
329
330     byte status = SecurityStatus.UNCHECKED;
331
332     for (DNSKEYRecord key : keys) {
333       status = verifySignature(rrset, sigrec, key);
334
335       if (status == SecurityStatus.SECURE) break;
336     }
337
338     return status;
339   }
340
341   /**
342    * Verifies an RRset. This routine does not modify the RRset. This RRset is
343    * presumed to be verifiable, and the correct DNSKEY rrset is presumed to
344    * have been found.
345    * 
346    * @return SecurityStatus.SECURE if the rrest verified positively,
347    *         SecurityStatus.BOGUS otherwise.
348    */
349   @SuppressWarnings("unchecked")
350 public byte verify(RRset rrset, RRset key_rrset)
351   {
352     Iterator i = rrset.sigs();
353
354     if (!i.hasNext())
355     {
356 //      log.info("RRset failed to verify due to lack of signatures");
357       return SecurityStatus.BOGUS;
358     }
359
360     while (i.hasNext())
361     {
362       RRSIGRecord sigrec = (RRSIGRecord) i.next();
363
364       byte res = verifySignature(rrset, sigrec, key_rrset);
365
366       if (res == SecurityStatus.SECURE) return res;
367     }
368
369 //    log.info("RRset failed to verify: all signatures were BOGUS");
370     return SecurityStatus.BOGUS;
371   }
372
373   /**
374    * Verify an RRset against a single DNSKEY. Use this when you must be
375    * certain that an RRset signed and verifies with a particular DNSKEY (as
376    * opposed to a particular DNSKEY rrset).
377    * 
378    * @param rrset The rrset to verify.
379    * @param dnskey The DNSKEY to verify with.
380    * @return SecurityStatus.SECURE if the rrset verified, BOGUS otherwise.
381    */
382   @SuppressWarnings("unchecked")
383 public byte verify(RRset rrset, DNSKEYRecord dnskey)
384   {
385     // Iterate over RRSIGS
386
387     Iterator i = rrset.sigs();
388     if (!i.hasNext())
389     {
390 //      log.info("RRset failed to verify due to lack of signatures");
391       return SecurityStatus.BOGUS;
392     }
393
394     while (i.hasNext())
395     {
396       RRSIGRecord sigrec = (RRSIGRecord) i.next();
397
398       // Skip RRSIGs that do not match our given key's footprint.
399       if (sigrec.getFootprint() != dnskey.getFootprint()) continue;
400
401       byte res = verifySignature(rrset, sigrec, dnskey);
402
403       if (res == SecurityStatus.SECURE) return res;
404     }
405
406 //    log.info("RRset failed to verify: all signatures were BOGUS");
407     return SecurityStatus.BOGUS;
408   }
409
410   public boolean supportsAlgorithm(int algorithm)
411   {
412     return mAlgorithmMap.containsKey(new Integer(algorithm));
413   }
414
415   public boolean supportsAlgorithm(Name private_id)
416   {
417     return mAlgorithmMap.containsKey(private_id);
418   }
419
420   public int baseAlgorithm(int algorithm)
421   {
422     switch (algorithm)
423     {
424       case DNSSEC.RSAMD5:
425       case DNSSEC.RSASHA1:
426         return RSA;
427       case DNSSEC.DSA:
428         return DSA;
429     }
430     AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
431     if (entry == null) return UNKNOWN;
432     if (entry.isDSA) return DSA;
433     return RSA;
434   }
435   
436   /** @return the appropriate Signature object for this keypair. */
437   private Signature getSignature(int algorithm)
438   {
439     Signature s = null;
440
441
442     try
443     {
444       AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
445       if (entry == null)
446       {
447 //        log.info("DNSSEC algorithm " + algorithm + " not recognized.");
448         return null;
449       }
450       // TODO: should we cache the instance?
451       s = Signature.getInstance(entry.jcaName);
452     }
453     catch (NoSuchAlgorithmException e)
454     {
455 //      log.error("error getting Signature object", e);
456     }
457
458     return s;
459   }
460
461   // TODO: enable private algorithm support in dnsjava.
462   // Right now, this cannot be used because the DNSKEYRecord object doesn't
463   // give us
464   // the private key name.
465   // private Signature getSignature(Name private_alg)
466   // {
467   // Signature s = null;
468   //
469   // try
470   // {
471   // String alg_id = (String) mAlgorithmMap.get(private_alg);
472   // if (alg_id == null)
473   // {
474   // log.debug("DNSSEC private algorithm '" + private_alg
475   // + "' not recognized.");
476   // return null;
477   // }
478   //
479   // s = Signature.getInstance(alg_id);
480   // }
481   // catch (NoSuchAlgorithmException e)
482   // {
483   // log.error("error getting Signature object", e);
484   // }
485   //
486   // return s;
487   // }
488 }