2 * Copyright (c) 2009 VeriSign, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 package com.versign.tat.dnssec;
33 import java.security.*;
35 import org.xbill.DNS.*;
36 import org.xbill.DNS.security.*;
38 import com.versign.tat.dnssec.SecurityStatus;
39 import com.versign.tat.dnssec.Util;
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.
47 public class DnsSecVerifier
49 public static final int UNKNOWN = 0;
50 public static final int RSA = 1;
51 public static final int DSA = 2;
54 * This is a mapping of DNSSEC algorithm numbers/private identifiers to JCA
55 * algorithm identifiers.
57 private HashMap<Integer, AlgEntry> mAlgorithmMap;
59 private static class AlgEntry
61 public String jcaName;
65 public AlgEntry(String name, int dnssecAlg, boolean isDSA)
68 this.dnssecAlg = dnssecAlg;
73 public DnsSecVerifier()
75 mAlgorithmMap = new HashMap<Integer, AlgEntry>();
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,
82 mAlgorithmMap.put(new Integer(DNSSEC.RSASHA1), new AlgEntry(
83 "SHA1withRSA", DNSSEC.RSASHA1, false));
86 private boolean isDSA(int algorithm)
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;
93 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
94 if (entry != null) return entry.isDSA;
98 public void init(Properties config)
100 if (config == null) return;
102 // Algorithm configuration
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.");
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));
112 if (!mAlgorithmMap.containsKey(alg_orig))
114 // log.warn("Unable to alias " + alg_alias + " to unknown algorithm "
119 if (mAlgorithmMap.containsKey(alg_alias))
121 // log.warn("Algorithm alias " + alg_alias
122 // + " is already defined and cannot be redefined");
126 mAlgorithmMap.put(alg_alias, mAlgorithmMap.get(alg_orig));
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!");
135 // log.debug("DNSSEC alg " + alg + " maps to " + entry.jcaName
136 // + " (" + entry.dnssecAlg + ")");
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.
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.
150 @SuppressWarnings("unchecked")
151 private List<DNSKEYRecord> findKey(RRset dnskey_rrset, RRSIGRecord signature)
153 if (!signature.getSigner().equals(dnskey_rrset.getName()))
155 // log.trace("findKey: could not find appropriate key because "
156 // + "incorrect keyset was supplied. Wanted: " + signature.getSigner()
157 // + ", got: " + dnskey_rrset.getName());
161 int keyid = signature.getFootprint();
162 int alg = signature.getAlgorithm();
164 List<DNSKEYRecord> res = new ArrayList<DNSKEYRecord>(dnskey_rrset.size());
166 for (Iterator i = dnskey_rrset.rrs(); i.hasNext();)
168 DNSKEYRecord r = (DNSKEYRecord) i.next();
169 if (r.getAlgorithm() == alg && r.getFootprint() == keyid)
177 // log.trace("findKey: could not find a key matching "
178 // + "the algorithm and footprint in supplied keyset. ");
185 * Check to see if a signature looks valid (i.e., matches the rrset in
186 * question, in the validity period, etc.)
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
193 private byte checkSignature(RRset rrset, RRSIGRecord sigrec)
195 if (rrset == null || sigrec == null) return DNSSEC.Failed;
196 if (!rrset.getName().equals(sigrec.getName()))
198 // log.debug("Signature name does not match RRset name");
199 return SecurityStatus.BOGUS;
201 if (rrset.getType() != sigrec.getTypeCovered())
203 // log.debug("Signature type does not match RRset type");
204 return SecurityStatus.BOGUS;
207 Date now = new Date();
208 Date start = sigrec.getTimeSigned();
209 Date expire = sigrec.getExpire();
210 if (now.before(start))
212 // log.debug("Signature is not yet valid");
213 return SecurityStatus.BOGUS;
216 if (now.after(expire))
218 // log.debug("Signature has expired (now = " + now + ", sig expires = "
220 return SecurityStatus.BOGUS;
223 return SecurityStatus.SECURE;
226 public PublicKey parseDNSKEY(DNSKEYRecord key)
228 AlgEntry ae = (AlgEntry) mAlgorithmMap
229 .get(new Integer(key.getAlgorithm()));
230 if (key.getAlgorithm() != ae.dnssecAlg)
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
236 key = new DNSKEYRecord(key.getName(), key.getDClass(), key.getTTL(),
237 key.getFlags(), key.getProtocol(), ae.dnssecAlg, key.getKey());
240 return KEYConverter.parseRecord(key);
245 * Actually cryptographically verify a signature over the rrset. The RRSIG
246 * record must match the rrset being verified (see checkSignature).
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.
254 public byte verifySignature(RRset rrset, RRSIGRecord sigrec,
259 PublicKey pk = parseDNSKEY(key);
263 // log.warn("Could not convert DNSKEY record to a JCA public key: "
265 return SecurityStatus.UNCHECKED;
268 byte[] data = SignUtils.generateSigData(rrset, sigrec);
270 Signature signer = getSignature(sigrec.getAlgorithm());
273 return SecurityStatus.BOGUS;
276 signer.initVerify(pk);
279 byte[] sig = sigrec.getSignature();
280 if (isDSA(sigrec.getAlgorithm()))
282 sig = SignUtils.convertDSASignature(sig);
284 if (!signer.verify(sig))
286 // log.info("Signature failed to verify cryptographically");
287 // log.debug("Failed signature: " + sigrec);
288 return SecurityStatus.BOGUS;
290 // log.trace("Signature verified: " + sigrec);
291 return SecurityStatus.SECURE;
293 catch (IOException e)
295 // log.error("I/O error", e);
297 catch (GeneralSecurityException e)
299 // log.error("Security error", e);
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;
310 * Verify an RRset against a particular signature.
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
317 public byte verifySignature(RRset rrset, RRSIGRecord sigrec, RRset key_rrset)
319 byte result = checkSignature(rrset, sigrec);
320 if (result != SecurityStatus.SECURE) return result;
322 List<DNSKEYRecord> keys = findKey(key_rrset, sigrec);
326 // log.trace("could not find appropriate key");
327 return SecurityStatus.BOGUS;
330 byte status = SecurityStatus.UNCHECKED;
332 for (DNSKEYRecord key : keys) {
333 status = verifySignature(rrset, sigrec, key);
335 if (status == SecurityStatus.SECURE) break;
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
346 * @return SecurityStatus.SECURE if the rrest verified positively,
347 * SecurityStatus.BOGUS otherwise.
349 @SuppressWarnings("unchecked")
350 public byte verify(RRset rrset, RRset key_rrset)
352 Iterator i = rrset.sigs();
356 // log.info("RRset failed to verify due to lack of signatures");
357 return SecurityStatus.BOGUS;
362 RRSIGRecord sigrec = (RRSIGRecord) i.next();
364 byte res = verifySignature(rrset, sigrec, key_rrset);
366 if (res == SecurityStatus.SECURE) return res;
369 // log.info("RRset failed to verify: all signatures were BOGUS");
370 return SecurityStatus.BOGUS;
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).
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.
382 @SuppressWarnings("unchecked")
383 public byte verify(RRset rrset, DNSKEYRecord dnskey)
385 // Iterate over RRSIGS
387 Iterator i = rrset.sigs();
390 // log.info("RRset failed to verify due to lack of signatures");
391 return SecurityStatus.BOGUS;
396 RRSIGRecord sigrec = (RRSIGRecord) i.next();
398 // Skip RRSIGs that do not match our given key's footprint.
399 if (sigrec.getFootprint() != dnskey.getFootprint()) continue;
401 byte res = verifySignature(rrset, sigrec, dnskey);
403 if (res == SecurityStatus.SECURE) return res;
406 // log.info("RRset failed to verify: all signatures were BOGUS");
407 return SecurityStatus.BOGUS;
410 public boolean supportsAlgorithm(int algorithm)
412 return mAlgorithmMap.containsKey(new Integer(algorithm));
415 public boolean supportsAlgorithm(Name private_id)
417 return mAlgorithmMap.containsKey(private_id);
420 public int baseAlgorithm(int algorithm)
430 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
431 if (entry == null) return UNKNOWN;
432 if (entry.isDSA) return DSA;
436 /** @return the appropriate Signature object for this keypair. */
437 private Signature getSignature(int algorithm)
444 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
447 // log.info("DNSSEC algorithm " + algorithm + " not recognized.");
450 // TODO: should we cache the instance?
451 s = Signature.getInstance(entry.jcaName);
453 catch (NoSuchAlgorithmException e)
455 // log.error("error getting Signature object", e);
461 // TODO: enable private algorithm support in dnsjava.
462 // Right now, this cannot be used because the DNSKEYRecord object doesn't
464 // the private key name.
465 // private Signature getSignature(Name private_alg)
467 // Signature s = null;
471 // String alg_id = (String) mAlgorithmMap.get(private_alg);
472 // if (alg_id == null)
474 // log.debug("DNSSEC private algorithm '" + private_alg
475 // + "' not recognized.");
479 // s = Signature.getInstance(alg_id);
481 // catch (NoSuchAlgorithmException e)
483 // log.error("error getting Signature object", e);