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 se.rfc.unbound.validator;
35 import java.security.*;
37 import org.apache.log4j.Logger;
38 import org.xbill.DNS.*;
39 import org.xbill.DNS.security.*;
41 import se.rfc.unbound.SecurityStatus;
42 import se.rfc.unbound.Util;
45 * A class for performing basic DNSSEC verification. The DNSJAVA package
46 * contains a similar class. This is a reimplementation that allows us to have
47 * finer control over the validation process.
52 public class DnsSecVerifier
54 public static final int UNKNOWN = 0;
55 public static final int RSA = 1;
56 public static final int DSA = 2;
59 * This is a mapping of DNSSEC algorithm numbers/private identifiers to JCA
60 * algorithm identifiers.
62 private HashMap mAlgorithmMap;
64 private Logger log = Logger.getLogger(this.getClass());
66 private static class AlgEntry
68 public String jcaName;
72 public AlgEntry(String name, int dnssecAlg, boolean isDSA)
75 this.dnssecAlg = dnssecAlg;
80 public DnsSecVerifier()
82 mAlgorithmMap = new HashMap();
84 // set the default algorithm map.
85 mAlgorithmMap.put(new Integer(DNSSEC.RSAMD5), new AlgEntry("MD5withRSA",
86 DNSSEC.RSAMD5, false));
87 mAlgorithmMap.put(new Integer(DNSSEC.DSA), new AlgEntry("SHA1withDSA", DNSSEC.DSA,
89 mAlgorithmMap.put(new Integer(DNSSEC.RSASHA1), new AlgEntry(
90 "SHA1withRSA", DNSSEC.RSASHA1, false));
93 private boolean isDSA(int algorithm)
95 // shortcut the standard algorithms
96 if (algorithm == DNSSEC.DSA) return true;
97 if (algorithm == DNSSEC.RSASHA1) return false;
98 if (algorithm == DNSSEC.RSAMD5) return false;
100 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
101 if (entry != null) return entry.isDSA;
105 public void init(Properties config)
107 if (config == null) return;
109 // Algorithm configuration
111 // For now, we just accept new identifiers for existing algoirthms.
112 // FIXME: handle private identifiers.
113 List aliases = Util.parseConfigPrefix(config, "dns.algorithm.");
115 for (Iterator i = aliases.iterator(); i.hasNext();)
117 Util.ConfigEntry entry = (Util.ConfigEntry) i.next();
119 Integer alg_alias = new Integer(Util.parseInt(entry.key, -1));
120 Integer alg_orig = new Integer(Util.parseInt(entry.value, -1));
122 if (!mAlgorithmMap.containsKey(alg_orig))
124 log.warn("Unable to alias " + alg_alias + " to unknown algorithm "
129 if (mAlgorithmMap.containsKey(alg_alias))
131 log.warn("Algorithm alias " + alg_alias
132 + " is already defined and cannot be redefined");
136 mAlgorithmMap.put(alg_alias, mAlgorithmMap.get(alg_orig));
139 // for debugging purposes, log the entire algorithm map table.
140 for (Iterator i = mAlgorithmMap.keySet().iterator(); i.hasNext(); )
142 Integer alg = (Integer) i.next();
143 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(alg);
145 log.warn("DNSSEC alg " + alg + " has a null entry!");
147 log.debug("DNSSEC alg " + alg + " maps to " + entry.jcaName
148 + " (" + entry.dnssecAlg + ")");
153 * Find the matching DNSKEY(s) to an RRSIG within a DNSKEY rrset. Normally
154 * this will only return one DNSKEY. It can return more than one, since
155 * KeyID/Footprints are not guaranteed to be unique.
157 * @param dnskey_rrset The DNSKEY rrset to search.
158 * @param signature The RRSIG to match against.
159 * @return A List contains a one or more DNSKEYRecord objects, or null if a
160 * matching DNSKEY could not be found.
162 private List findKey(RRset dnskey_rrset, RRSIGRecord signature)
164 if (!signature.getSigner().equals(dnskey_rrset.getName()))
166 log.trace("findKey: could not find appropriate key because "
167 + "incorrect keyset was supplied. Wanted: " + signature.getSigner()
168 + ", got: " + dnskey_rrset.getName());
172 int keyid = signature.getFootprint();
173 int alg = signature.getAlgorithm();
175 List res = new ArrayList(dnskey_rrset.size());
177 for (Iterator i = dnskey_rrset.rrs(); i.hasNext();)
179 DNSKEYRecord r = (DNSKEYRecord) i.next();
180 if (r.getAlgorithm() == alg && r.getFootprint() == keyid)
188 log.trace("findKey: could not find a key matching "
189 + "the algorithm and footprint in supplied keyset. ");
196 * Check to see if a signature looks valid (i.e., matches the rrset in
197 * question, in the validity period, etc.)
199 * @param rrset The rrset that the signature belongs to.
200 * @param sigrec The signature record to check.
201 * @return A value of DNSSEC.Secure if it looks OK, DNSSEC.Faile if it looks
204 private byte checkSignature(RRset rrset, RRSIGRecord sigrec)
206 if (rrset == null || sigrec == null) return DNSSEC.Failed;
207 if (!rrset.getName().equals(sigrec.getName()))
209 log.debug("Signature name does not match RRset name");
210 return SecurityStatus.BOGUS;
212 if (rrset.getType() != sigrec.getTypeCovered())
214 log.debug("Signature type does not match RRset type");
215 return SecurityStatus.BOGUS;
218 Date now = new Date();
219 Date start = sigrec.getTimeSigned();
220 Date expire = sigrec.getExpire();
221 if (now.before(start))
223 log.debug("Signature is not yet valid");
224 return SecurityStatus.BOGUS;
227 if (now.after(expire))
229 log.debug("Signature has expired (now = " + now + ", sig expires = "
231 return SecurityStatus.BOGUS;
234 return SecurityStatus.SECURE;
237 public PublicKey parseDNSKEY(DNSKEYRecord key)
239 AlgEntry ae = (AlgEntry) mAlgorithmMap
240 .get(new Integer(key.getAlgorithm()));
241 if (key.getAlgorithm() != ae.dnssecAlg)
243 // Recast the DNSKEYRecord in question as one using the offical
244 // algorithm, to work around the lack of alias support in the underlying
245 // KEYConverter class from DNSjava
247 key = new DNSKEYRecord(key.getName(), key.getDClass(), key.getTTL(),
248 key.getFlags(), key.getProtocol(), ae.dnssecAlg, key.getKey());
251 return KEYConverter.parseRecord(key);
256 * Actually cryptographically verify a signature over the rrset. The RRSIG
257 * record must match the rrset being verified (see checkSignature).
259 * @param rrset The rrset to verify.
260 * @param sigrec The signature to verify with.
261 * @param key The (public) key associated with the RRSIG record.
262 * @return A security status code: SECURE if it worked, BOGUS if not,
263 * UNCHECKED if we just couldn't actually do the function.
265 public byte verifySignature(RRset rrset, RRSIGRecord sigrec,
270 PublicKey pk = parseDNSKEY(key);
274 log.warn("Could not convert DNSKEY record to a JCA public key: "
276 return SecurityStatus.UNCHECKED;
279 byte[] data = SignUtils.generateSigData(rrset, sigrec);
281 Signature signer = getSignature(sigrec.getAlgorithm());
284 return SecurityStatus.BOGUS;
287 signer.initVerify(pk);
290 byte[] sig = sigrec.getSignature();
291 if (isDSA(sigrec.getAlgorithm()))
293 sig = SignUtils.convertDSASignature(sig);
295 if (!signer.verify(sig))
297 log.info("Signature failed to verify cryptographically");
298 log.debug("Failed signature: " + sigrec);
299 return SecurityStatus.BOGUS;
301 log.trace("Signature verified: " + sigrec);
302 return SecurityStatus.SECURE;
304 catch (IOException e)
306 log.error("I/O error", e);
308 catch (GeneralSecurityException e)
310 log.error("Security error", e);
313 // FIXME: Since I'm not sure what would cause an exception here (failure
314 // to have the required crypto?)
315 // We default to UNCHECKED instead of BOGUS. This could be wrong.
316 return SecurityStatus.UNCHECKED;
321 * Verify an RRset against a particular signature.
323 * @return DNSSEC.Secure if the signature verfied, DNSSEC.Failed if it did
324 * not verify (for any reason), and DNSSEC.Insecure if verification
325 * could not be completed (usually because the public key was not
328 public byte verifySignature(RRset rrset, RRSIGRecord sigrec, RRset key_rrset)
330 byte result = checkSignature(rrset, sigrec);
331 if (result != SecurityStatus.SECURE) return result;
333 List keys = findKey(key_rrset, sigrec);
337 log.trace("could not find appropriate key");
338 return SecurityStatus.BOGUS;
341 byte status = SecurityStatus.UNCHECKED;
343 for (Iterator i = keys.iterator(); i.hasNext();)
345 DNSKEYRecord key = (DNSKEYRecord) i.next();
346 status = verifySignature(rrset, sigrec, key);
348 if (status == SecurityStatus.SECURE) break;
355 * Verifies an RRset. This routine does not modify the RRset. This RRset is
356 * presumed to be verifiable, and the correct DNSKEY rrset is presumed to
359 * @return SecurityStatus.SECURE if the rrest verified positively,
360 * SecurityStatus.BOGUS otherwise.
362 public byte verify(RRset rrset, RRset key_rrset)
364 Iterator i = rrset.sigs();
368 log.info("RRset failed to verify due to lack of signatures");
369 return SecurityStatus.BOGUS;
374 RRSIGRecord sigrec = (RRSIGRecord) i.next();
376 byte res = verifySignature(rrset, sigrec, key_rrset);
378 if (res == SecurityStatus.SECURE) return res;
381 log.info("RRset failed to verify: all signatures were BOGUS");
382 return SecurityStatus.BOGUS;
386 * Verify an RRset against a single DNSKEY. Use this when you must be
387 * certain that an RRset signed and verifies with a particular DNSKEY (as
388 * opposed to a particular DNSKEY rrset).
390 * @param rrset The rrset to verify.
391 * @param dnskey The DNSKEY to verify with.
392 * @return SecurityStatus.SECURE if the rrset verified, BOGUS otherwise.
394 public byte verify(RRset rrset, DNSKEYRecord dnskey)
396 // Iterate over RRSIGS
398 Iterator i = rrset.sigs();
401 log.info("RRset failed to verify due to lack of signatures");
402 return SecurityStatus.BOGUS;
407 RRSIGRecord sigrec = (RRSIGRecord) i.next();
409 // Skip RRSIGs that do not match our given key's footprint.
410 if (sigrec.getFootprint() != dnskey.getFootprint()) continue;
412 byte res = verifySignature(rrset, sigrec, dnskey);
414 if (res == SecurityStatus.SECURE) return res;
417 log.info("RRset failed to verify: all signatures were BOGUS");
418 return SecurityStatus.BOGUS;
421 public boolean supportsAlgorithm(int algorithm)
423 return mAlgorithmMap.containsKey(new Integer(algorithm));
426 public boolean supportsAlgorithm(Name private_id)
428 return mAlgorithmMap.containsKey(private_id);
431 public int baseAlgorithm(int algorithm)
441 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
442 if (entry == null) return UNKNOWN;
443 if (entry.isDSA) return DSA;
447 /** @return the appropriate Signature object for this keypair. */
448 private Signature getSignature(int algorithm)
455 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
458 log.info("DNSSEC algorithm " + algorithm + " not recognized.");
461 // TODO: should we cache the instance?
462 s = Signature.getInstance(entry.jcaName);
464 catch (NoSuchAlgorithmException e)
466 log.error("error getting Signature object", e);
472 // TODO: enable private algorithm support in dnsjava.
473 // Right now, this cannot be used because the DNSKEYRecord object doesn't
475 // the private key name.
476 // private Signature getSignature(Name private_alg)
478 // Signature s = null;
482 // String alg_id = (String) mAlgorithmMap.get(private_alg);
483 // if (alg_id == null)
485 // log.debug("DNSSEC private algorithm '" + private_alg
486 // + "' not recognized.");
490 // s = Signature.getInstance(alg_id);
492 // catch (NoSuchAlgorithmException e)
494 // log.error("error getting Signature object", e);