1 /***************************** -*- Java -*- ********************************\
3 * Copyright (c) 2009 VeriSign, Inc. All rights reserved. *
5 * This software is provided solely in connection with the terms of the *
6 * license agreement. Any other use without the prior express written *
7 * permission of VeriSign is completely prohibited. The software and *
8 * documentation are "Commercial Items", as that term is defined in 48 *
9 * C.F.R. section 2.101, consisting of "Commercial Computer Software" and *
10 * "Commercial Computer Software Documentation" as such terms are defined *
11 * in 48 C.F.R. section 252.227-7014(a)(5) and 48 C.F.R. section *
12 * 252.227-7014(a)(1), and used in 48 C.F.R. section 12.212 and 48 C.F.R. *
13 * section 227.7202, as applicable. Pursuant to the above and other *
14 * relevant sections of the Code of Federal Regulations, as applicable, *
15 * VeriSign's publications, commercial computer software, and commercial *
16 * computer software documentation are distributed and licensed to United *
17 * States Government end users with only those rights as granted to all *
18 * other end users, according to the terms and conditions contained in the *
19 * license agreement(s) that accompany the products and software *
22 \***************************************************************************/
24 package com.verisign.tat.dnssec;
26 import org.apache.log4j.Logger;
28 import org.xbill.DNS.*;
29 import org.xbill.DNS.security.*;
33 import java.security.*;
38 * A class for performing basic DNSSEC verification. The DNSJAVA
39 * package contains a similar class. This is a re-implementation that
40 * allows us to have finer control over the validation process.
42 public class DnsSecVerifier {
43 public static final int UNKNOWN = 0;
44 public static final int RSA = 1;
45 public static final int DSA = 2;
46 private Logger log = Logger.getLogger(this.getClass());
49 * This is a mapping of DNSSEC algorithm numbers to JCA algorithm
52 private HashMap<Integer, AlgEntry> mAlgorithmMap;
55 * This is a mapping of DNSSEC private (DNS name) identifiers to
56 * JCA algorithm identifiers.
58 private HashMap<Name, AlgEntry> mPrivateAlgorithmMap;
60 public DnsSecVerifier() {
61 mAlgorithmMap = new HashMap<Integer, AlgEntry>();
62 mPrivateAlgorithmMap = new HashMap<Name, AlgEntry>();
64 // set the default algorithm map.
65 mAlgorithmMap.put(Integer.valueOf(DNSSEC.Algorithm.RSAMD5), new AlgEntry(
66 "MD5withRSA", DNSSEC.Algorithm.RSAMD5, false));
67 mAlgorithmMap.put(Integer.valueOf(DNSSEC.Algorithm.DSA), new AlgEntry("SHA1withDSA",
68 DNSSEC.Algorithm.DSA, true));
69 mAlgorithmMap.put(Integer.valueOf(DNSSEC.Algorithm.RSASHA1), new AlgEntry(
70 "SHA1withRSA", DNSSEC.Algorithm.RSASHA1, false));
71 mAlgorithmMap.put(Integer.valueOf(DNSSEC.Algorithm.DSA_NSEC3_SHA1), new AlgEntry(
72 "SHA1withDSA", DNSSEC.Algorithm.DSA, true));
73 mAlgorithmMap.put(Integer.valueOf(DNSSEC.Algorithm.RSA_NSEC3_SHA1), new AlgEntry(
74 "SHA1withRSA", DNSSEC.Algorithm.RSASHA1, false));
75 mAlgorithmMap.put(Integer.valueOf(DNSSEC.Algorithm.RSASHA256), new AlgEntry(
76 "SHA256withRSA", DNSSEC.Algorithm.RSASHA256, false));
77 mAlgorithmMap.put(Integer.valueOf(DNSSEC.Algorithm.RSASHA512), new AlgEntry(
78 "SHA512withRSA", DNSSEC.Algorithm.RSASHA512, false));
81 private boolean isDSA(int algorithm) {
82 // shortcut the standard algorithms
83 if (algorithm == DNSSEC.Algorithm.DSA) {
87 if (algorithm == DNSSEC.Algorithm.RSASHA1) {
91 if (algorithm == DNSSEC.Algorithm.RSAMD5) {
95 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(Integer.valueOf(algorithm));
104 public void init(Properties config) {
105 if (config == null) {
109 // Algorithm configuration
111 // For now, we just accept new identifiers for existing algorithms.
112 // FIXME: handle private identifiers.
113 List<Util.ConfigEntry> aliases = Util.parseConfigPrefix(config, "dns.algorithm.");
115 for (Util.ConfigEntry entry : aliases) {
116 Integer alg_alias = Integer.valueOf(Util.parseInt(entry.key, -1));
117 Integer alg_orig = Integer.valueOf(Util.parseInt(entry.value, -1));
119 if (!mAlgorithmMap.containsKey(alg_orig)) {
120 log.warn("Unable to alias " + alg_alias + " to unknown algorithm " + alg_orig);
124 if (mAlgorithmMap.containsKey(alg_alias)) {
125 log.warn("Algorithm alias " + alg_alias + " is already defined and cannot be redefined");
129 mAlgorithmMap.put(alg_alias, mAlgorithmMap.get(alg_orig));
132 // for debugging purposes, log the entire algorithm map table.
133 for (Integer alg : mAlgorithmMap.keySet()) {
134 AlgEntry entry = mAlgorithmMap.get(alg);
137 log.warn("DNSSEC alg " + alg + " has a null entry!");
139 log.debug("DNSSEC alg " + alg + " maps to " + entry.jcaName
140 + " (" + entry.dnssecAlg + ")");
146 * Find the matching DNSKEY(s) to an RRSIG within a DNSKEY
147 * rrset. Normally this will only return one DNSKEY. It can return
148 * more than one, since KeyID/Footprints are not guaranteed to be
151 * @param dnskey_rrset
152 * The DNSKEY rrset to search.
154 * The RRSIG to match against.
155 * @return A List contains a one or more DNSKEYRecord objects, or null if a
156 * matching DNSKEY could not be found.
158 @SuppressWarnings("rawtypes")
159 private List<DNSKEYRecord> findKey(RRset dnskey_rrset, RRSIGRecord signature) {
160 if (!signature.getSigner().equals(dnskey_rrset.getName())) {
161 log.trace("findKey: could not find appropriate key because "
162 + "incorrect keyset was supplied. Wanted: "
163 + signature.getSigner() + ", got: "
164 + dnskey_rrset.getName());
169 int keyid = signature.getFootprint();
170 int alg = signature.getAlgorithm();
172 List<DNSKEYRecord> res = new ArrayList<DNSKEYRecord>(dnskey_rrset.size());
174 for (Iterator i = dnskey_rrset.rrs(); i.hasNext();) {
175 DNSKEYRecord r = (DNSKEYRecord) i.next();
177 if ((r.getAlgorithm() == alg) && (r.getFootprint() == keyid)) {
182 if (res.size() == 0) {
183 log.trace("findKey: could not find a key matching " +
184 "the algorithm and footprint in supplied keyset.");
193 * Check to see if a signature looks valid (i.e., matches the
194 * rrset in question, in the validity period, etc.)
197 * The rrset that the signature belongs to.
199 * The signature record to check.
200 * @return A value of SecurityStatus.SECURE if it looks OK,
201 * SecurityStatus.BOGUS if it looks bad.
203 private byte checkSignature(RRset rrset, RRSIGRecord sigrec) {
204 if ((rrset == null) || (sigrec == null)) {
205 return SecurityStatus.BOGUS;
208 if (!rrset.getName().equals(sigrec.getName())) {
209 log.debug("Signature name does not match RRset name");
211 return SecurityStatus.BOGUS;
214 if (rrset.getType() != sigrec.getTypeCovered()) {
215 log.debug("Signature type does not match RRset type");
217 return SecurityStatus.BOGUS;
220 Date now = new Date();
221 Date start = sigrec.getTimeSigned();
222 Date expire = sigrec.getExpire();
224 if (now.before(start)) {
225 log.debug("Signature is not yet valid");
227 return SecurityStatus.BOGUS;
230 if (now.after(expire)) {
231 log.debug("Signature has expired (now = " + now +
232 ", sig expires = " + expire);
234 return SecurityStatus.BOGUS;
237 return SecurityStatus.SECURE;
240 public PublicKey parseDNSKEY(DNSKEYRecord key) {
241 AlgEntry ae = (AlgEntry) mAlgorithmMap.get(Integer.valueOf(key.getAlgorithm()));
243 if (key.getAlgorithm() != ae.dnssecAlg) {
244 // Recast the DNSKEYRecord in question as one using the offical
245 // algorithm, to work around the lack of alias support in the
247 // KEYConverter class from DNSjava
248 key = new DNSKEYRecord(key.getName(), key.getDClass(),
249 key.getTTL(), key.getFlags(), key.getProtocol(),
250 ae.dnssecAlg, key.getKey());
253 return KEYConverter.parseRecord(key);
257 * Actually cryptographically verify a signature over the
258 * rrset. The RRSIG record must match the rrset being verified
259 * (see checkSignature).
262 * The rrset to verify.
264 * The signature to verify with.
266 * The (public) key associated with the RRSIG record.
267 * @return A security status code: SECURE if it worked, BOGUS if not,
268 * UNCHECKED if we just couldn't actually do the function.
270 public byte verifySignature(RRset rrset, RRSIGRecord sigrec,
273 PublicKey pk = parseDNSKEY(key);
276 log.warn("Could not convert DNSKEY record to a JCA public key: " + key);
277 return SecurityStatus.UNCHECKED;
280 byte[] data = SignUtils.generateSigData(rrset, sigrec);
282 Signature signer = getSignature(sigrec.getAlgorithm());
284 if (signer == null) {
285 return SecurityStatus.BOGUS;
288 signer.initVerify(pk);
291 byte[] sig = sigrec.getSignature();
293 if (isDSA(sigrec.getAlgorithm())) {
294 sig = SignUtils.convertDSASignature(sig);
297 if (!signer.verify(sig)) {
298 log.info("Signature failed to verify cryptographically");
299 log.debug("Failed signature: " + sigrec);
301 return SecurityStatus.BOGUS;
304 log.trace("Signature verified: " + sigrec);
306 return SecurityStatus.SECURE;
307 } catch (IOException e) {
308 log.error("I/O error", e);
309 } 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;
320 * Verify an RRset against a particular signature.
322 * @return DNSSEC.Secure if the signature verfied, DNSSEC.Failed
323 * if it did not verify (for any reason), and
324 * DNSSEC.Insecure if verification could not be completed
325 * (usually because the public key was not available).
327 public byte verifySignature(RRset rrset, RRSIGRecord sigrec, RRset key_rrset) {
328 byte result = checkSignature(rrset, sigrec);
330 if (result != SecurityStatus.SECURE) {
334 List<DNSKEYRecord> keys = findKey(key_rrset, sigrec);
337 log.trace("could not find appropriate key");
338 return SecurityStatus.BOGUS;
341 byte status = SecurityStatus.UNCHECKED;
343 for (DNSKEYRecord key : keys) {
344 status = verifySignature(rrset, sigrec, key);
346 if (status == SecurityStatus.SECURE) {
355 * Verifies an RRset. This routine does not modify the RRset. This
356 * RRset is presumed to be verifiable, and the correct DNSKEY
357 * rrset is presumed to have been found.
359 * @return SecurityStatus.SECURE if the rrest verified positively,
360 * SecurityStatus.BOGUS otherwise.
362 @SuppressWarnings("rawtypes")
363 public byte verify(RRset rrset, RRset key_rrset) {
364 Iterator i = rrset.sigs();
367 log.info("RRset failed to verify due to lack of signatures");
369 return SecurityStatus.BOGUS;
372 while (i.hasNext()) {
373 RRSIGRecord sigrec = (RRSIGRecord) i.next();
375 byte res = verifySignature(rrset, sigrec, key_rrset);
377 if (res == SecurityStatus.SECURE) {
382 log.info("RRset failed to verify: all signatures were BOGUS");
384 return SecurityStatus.BOGUS;
388 * Verify an RRset against a single DNSKEY. Use this when you must
389 * be certain that an RRset signed and verifies with a particular
390 * DNSKEY (as opposed to a particular DNSKEY rrset).
393 * The rrset to verify.
395 * The DNSKEY to verify with.
396 * @return SecurityStatus.SECURE if the rrset verified, BOGUS otherwise.
398 @SuppressWarnings("rawtypes")
399 public byte verify(RRset rrset, DNSKEYRecord dnskey) {
400 // Iterate over RRSIGS
401 Iterator i = rrset.sigs();
404 log.info("RRset failed to verify due to lack of signatures");
406 return SecurityStatus.BOGUS;
409 while (i.hasNext()) {
410 RRSIGRecord sigrec = (RRSIGRecord) i.next();
412 // Skip RRSIGs that do not match our given key's footprint.
413 if (sigrec.getFootprint() != dnskey.getFootprint()) {
417 byte res = verifySignature(rrset, sigrec, dnskey);
419 if (res == SecurityStatus.SECURE) {
424 log.info("RRset failed to verify: all signatures were BOGUS");
426 return SecurityStatus.BOGUS;
429 public boolean supportsAlgorithm(int algorithm) {
430 return mAlgorithmMap.containsKey(Integer.valueOf(algorithm));
433 public boolean supportsAlgorithm(Name private_id) {
434 return mPrivateAlgorithmMap.containsKey(private_id);
437 public int baseAlgorithm(int algorithm) {
439 case DNSSEC.Algorithm.RSAMD5:
440 case DNSSEC.Algorithm.RSASHA1:
443 case DNSSEC.Algorithm.DSA:
447 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(Integer.valueOf(algorithm));
460 /** @return the appropriate Signature object for this keypair. */
461 private Signature getSignature(int algorithm) {
465 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(Integer.valueOf(algorithm));
468 log.info("DNSSEC algorithm " + algorithm + " not recognized.");
472 // TODO: should we cache the instance?
473 s = Signature.getInstance(entry.jcaName);
474 } catch (NoSuchAlgorithmException e) {
475 log.error("error getting Signature object", e);
481 private static class AlgEntry {
482 public String jcaName;
483 public boolean isDSA;
484 public int dnssecAlg;
486 public AlgEntry(String name, int dnssecAlg, boolean isDSA) {
488 this.dnssecAlg = dnssecAlg;