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