4 * Copyright (c) 2005 VeriSign, Inc. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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.
31 package com.versign.tat.dnssec;
35 import java.security.*;
37 import org.xbill.DNS.*;
38 import org.xbill.DNS.security.*;
40 import com.versign.tat.dnssec.SecurityStatus;
41 import com.versign.tat.dnssec.Util;
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.
49 public class DnsSecVerifier
51 public static final int UNKNOWN = 0;
52 public static final int RSA = 1;
53 public static final int DSA = 2;
56 * This is a mapping of DNSSEC algorithm numbers/private identifiers to JCA
57 * algorithm identifiers.
59 private HashMap mAlgorithmMap;
61 private static class AlgEntry
63 public String jcaName;
67 public AlgEntry(String name, int dnssecAlg, boolean isDSA)
70 this.dnssecAlg = dnssecAlg;
75 public DnsSecVerifier()
77 mAlgorithmMap = new HashMap();
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,
84 mAlgorithmMap.put(new Integer(DNSSEC.RSASHA1), new AlgEntry(
85 "SHA1withRSA", DNSSEC.RSASHA1, false));
88 private boolean isDSA(int algorithm)
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;
95 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
96 if (entry != null) return entry.isDSA;
100 public void init(Properties config)
102 if (config == null) return;
104 // Algorithm configuration
106 // For now, we just accept new identifiers for existing algoirthms.
107 // FIXME: handle private identifiers.
108 List aliases = Util.parseConfigPrefix(config, "dns.algorithm.");
110 for (Iterator i = aliases.iterator(); i.hasNext();)
112 Util.ConfigEntry entry = (Util.ConfigEntry) i.next();
114 Integer alg_alias = new Integer(Util.parseInt(entry.key, -1));
115 Integer alg_orig = new Integer(Util.parseInt(entry.value, -1));
117 if (!mAlgorithmMap.containsKey(alg_orig))
119 // log.warn("Unable to alias " + alg_alias + " to unknown algorithm "
124 if (mAlgorithmMap.containsKey(alg_alias))
126 // log.warn("Algorithm alias " + alg_alias
127 // + " is already defined and cannot be redefined");
131 mAlgorithmMap.put(alg_alias, mAlgorithmMap.get(alg_orig));
134 // for debugging purposes, log the entire algorithm map table.
135 for (Iterator i = mAlgorithmMap.keySet().iterator(); i.hasNext(); )
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!");
142 // log.debug("DNSSEC alg " + alg + " maps to " + entry.jcaName
143 // + " (" + entry.dnssecAlg + ")");
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.
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.
157 private List findKey(RRset dnskey_rrset, RRSIGRecord signature)
159 if (!signature.getSigner().equals(dnskey_rrset.getName()))
161 // log.trace("findKey: could not find appropriate key because "
162 // + "incorrect keyset was supplied. Wanted: " + signature.getSigner()
163 // + ", got: " + dnskey_rrset.getName());
167 int keyid = signature.getFootprint();
168 int alg = signature.getAlgorithm();
170 List res = new ArrayList(dnskey_rrset.size());
172 for (Iterator i = dnskey_rrset.rrs(); i.hasNext();)
174 DNSKEYRecord r = (DNSKEYRecord) i.next();
175 if (r.getAlgorithm() == alg && r.getFootprint() == keyid)
183 // log.trace("findKey: could not find a key matching "
184 // + "the algorithm and footprint in supplied keyset. ");
191 * Check to see if a signature looks valid (i.e., matches the rrset in
192 * question, in the validity period, etc.)
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
199 private byte checkSignature(RRset rrset, RRSIGRecord sigrec)
201 if (rrset == null || sigrec == null) return DNSSEC.Failed;
202 if (!rrset.getName().equals(sigrec.getName()))
204 // log.debug("Signature name does not match RRset name");
205 return SecurityStatus.BOGUS;
207 if (rrset.getType() != sigrec.getTypeCovered())
209 // log.debug("Signature type does not match RRset type");
210 return SecurityStatus.BOGUS;
213 Date now = new Date();
214 Date start = sigrec.getTimeSigned();
215 Date expire = sigrec.getExpire();
216 if (now.before(start))
218 // log.debug("Signature is not yet valid");
219 return SecurityStatus.BOGUS;
222 if (now.after(expire))
224 // log.debug("Signature has expired (now = " + now + ", sig expires = "
226 return SecurityStatus.BOGUS;
229 return SecurityStatus.SECURE;
232 public PublicKey parseDNSKEY(DNSKEYRecord key)
234 AlgEntry ae = (AlgEntry) mAlgorithmMap
235 .get(new Integer(key.getAlgorithm()));
236 if (key.getAlgorithm() != ae.dnssecAlg)
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
242 key = new DNSKEYRecord(key.getName(), key.getDClass(), key.getTTL(),
243 key.getFlags(), key.getProtocol(), ae.dnssecAlg, key.getKey());
246 return KEYConverter.parseRecord(key);
251 * Actually cryptographically verify a signature over the rrset. The RRSIG
252 * record must match the rrset being verified (see checkSignature).
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.
260 public byte verifySignature(RRset rrset, RRSIGRecord sigrec,
265 PublicKey pk = parseDNSKEY(key);
269 // log.warn("Could not convert DNSKEY record to a JCA public key: "
271 return SecurityStatus.UNCHECKED;
274 byte[] data = SignUtils.generateSigData(rrset, sigrec);
276 Signature signer = getSignature(sigrec.getAlgorithm());
279 return SecurityStatus.BOGUS;
282 signer.initVerify(pk);
285 byte[] sig = sigrec.getSignature();
286 if (isDSA(sigrec.getAlgorithm()))
288 sig = SignUtils.convertDSASignature(sig);
290 if (!signer.verify(sig))
292 // log.info("Signature failed to verify cryptographically");
293 // log.debug("Failed signature: " + sigrec);
294 return SecurityStatus.BOGUS;
296 // log.trace("Signature verified: " + sigrec);
297 return SecurityStatus.SECURE;
299 catch (IOException e)
301 // log.error("I/O error", e);
303 catch (GeneralSecurityException e)
305 // log.error("Security error", e);
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;
316 * Verify an RRset against a particular signature.
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
323 public byte verifySignature(RRset rrset, RRSIGRecord sigrec, RRset key_rrset)
325 byte result = checkSignature(rrset, sigrec);
326 if (result != SecurityStatus.SECURE) return result;
328 List keys = findKey(key_rrset, sigrec);
332 // log.trace("could not find appropriate key");
333 return SecurityStatus.BOGUS;
336 byte status = SecurityStatus.UNCHECKED;
338 for (Iterator i = keys.iterator(); i.hasNext();)
340 DNSKEYRecord key = (DNSKEYRecord) i.next();
341 status = verifySignature(rrset, sigrec, key);
343 if (status == SecurityStatus.SECURE) break;
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
354 * @return SecurityStatus.SECURE if the rrest verified positively,
355 * SecurityStatus.BOGUS otherwise.
357 public byte verify(RRset rrset, RRset key_rrset)
359 Iterator i = rrset.sigs();
363 // log.info("RRset failed to verify due to lack of signatures");
364 return SecurityStatus.BOGUS;
369 RRSIGRecord sigrec = (RRSIGRecord) i.next();
371 byte res = verifySignature(rrset, sigrec, key_rrset);
373 if (res == SecurityStatus.SECURE) return res;
376 // log.info("RRset failed to verify: all signatures were BOGUS");
377 return SecurityStatus.BOGUS;
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).
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.
389 public byte verify(RRset rrset, DNSKEYRecord dnskey)
391 // Iterate over RRSIGS
393 Iterator i = rrset.sigs();
396 // log.info("RRset failed to verify due to lack of signatures");
397 return SecurityStatus.BOGUS;
402 RRSIGRecord sigrec = (RRSIGRecord) i.next();
404 // Skip RRSIGs that do not match our given key's footprint.
405 if (sigrec.getFootprint() != dnskey.getFootprint()) continue;
407 byte res = verifySignature(rrset, sigrec, dnskey);
409 if (res == SecurityStatus.SECURE) return res;
412 // log.info("RRset failed to verify: all signatures were BOGUS");
413 return SecurityStatus.BOGUS;
416 public boolean supportsAlgorithm(int algorithm)
418 return mAlgorithmMap.containsKey(new Integer(algorithm));
421 public boolean supportsAlgorithm(Name private_id)
423 return mAlgorithmMap.containsKey(private_id);
426 public int baseAlgorithm(int algorithm)
436 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
437 if (entry == null) return UNKNOWN;
438 if (entry.isDSA) return DSA;
442 /** @return the appropriate Signature object for this keypair. */
443 private Signature getSignature(int algorithm)
450 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
453 // log.info("DNSSEC algorithm " + algorithm + " not recognized.");
456 // TODO: should we cache the instance?
457 s = Signature.getInstance(entry.jcaName);
459 catch (NoSuchAlgorithmException e)
461 // log.error("error getting Signature object", e);
467 // TODO: enable private algorithm support in dnsjava.
468 // Right now, this cannot be used because the DNSKEYRecord object doesn't
470 // the private key name.
471 // private Signature getSignature(Name private_alg)
473 // Signature s = null;
477 // String alg_id = (String) mAlgorithmMap.get(private_alg);
478 // if (alg_id == null)
480 // log.debug("DNSSEC private algorithm '" + private_alg
481 // + "' not recognized.");
485 // s = Signature.getInstance(alg_id);
487 // catch (NoSuchAlgorithmException e)
489 // log.error("error getting Signature object", e);