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