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 package
39 * contains a similar class. This is a re-implementation that allows us to have
40 * 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/private identifiers to JCA
50 * algorithm identifiers.
52 private HashMap<Integer, AlgEntry> mAlgorithmMap;
54 public DnsSecVerifier() {
55 mAlgorithmMap = new HashMap<Integer, AlgEntry>();
57 // set the default algorithm map.
58 mAlgorithmMap.put(new Integer(DNSSEC.RSAMD5), new AlgEntry(
59 "MD5withRSA", DNSSEC.RSAMD5, false));
60 mAlgorithmMap.put(new Integer(DNSSEC.DSA), new AlgEntry("SHA1withDSA",
62 mAlgorithmMap.put(new Integer(DNSSEC.RSASHA1), new AlgEntry(
63 "SHA1withRSA", DNSSEC.RSASHA1, false));
64 mAlgorithmMap.put(new Integer(DNSSEC.DSA_NSEC3_SHA1), new AlgEntry(
65 "SHA1withDSA", DNSSEC.DSA, true));
66 mAlgorithmMap.put(new Integer(DNSSEC.RSA_NSEC3_SHA1), new AlgEntry(
67 "SHA1withRSA", DNSSEC.RSASHA1, false));
68 mAlgorithmMap.put(new Integer(DNSSEC.RSASHA256), new AlgEntry(
69 "SHA256withRSA", DNSSEC.RSASHA256, false));
70 mAlgorithmMap.put(new Integer(DNSSEC.RSASHA512), new AlgEntry(
71 "SHA512withRSA", DNSSEC.RSASHA512, false));
74 private boolean isDSA(int algorithm) {
75 // shortcut the standard algorithms
76 if (algorithm == DNSSEC.DSA) {
80 if (algorithm == DNSSEC.RSASHA1) {
84 if (algorithm == DNSSEC.RSAMD5) {
88 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
97 public void init(Properties config) {
102 // Algorithm configuration
104 // For now, we just accept new identifiers for existing algorithms.
105 // FIXME: handle private identifiers.
106 List<Util.ConfigEntry> aliases = Util.parseConfigPrefix(config,
109 for (Util.ConfigEntry entry : aliases) {
110 Integer alg_alias = new Integer(Util.parseInt(entry.key, -1));
111 Integer alg_orig = new Integer(Util.parseInt(entry.value, -1));
113 if (!mAlgorithmMap.containsKey(alg_orig)) {
114 log.warn("Unable to alias " + alg_alias
115 + " to unknown algorithm " + alg_orig);
120 if (mAlgorithmMap.containsKey(alg_alias)) {
121 log.warn("Algorithm alias " + alg_alias
122 + " is already defined and cannot be redefined");
127 mAlgorithmMap.put(alg_alias, mAlgorithmMap.get(alg_orig));
130 // for debugging purposes, log the entire algorithm map table.
131 for (Integer alg : mAlgorithmMap.keySet()) {
132 AlgEntry entry = mAlgorithmMap.get(alg);
135 log.warn("DNSSEC alg " + alg + " has a null entry!");
137 log.debug("DNSSEC alg " + alg + " maps to " + entry.jcaName
138 + " (" + entry.dnssecAlg + ")");
144 * Find the matching DNSKEY(s) to an RRSIG within a DNSKEY rrset. Normally
145 * this will only return one DNSKEY. It can return more than one, since
146 * KeyID/Footprints are not guaranteed to be unique.
148 * @param dnskey_rrset
149 * The DNSKEY rrset to search.
151 * The RRSIG to match against.
152 * @return A List contains a one or more DNSKEYRecord objects, or null if a
153 * matching DNSKEY could not be found.
155 @SuppressWarnings("unchecked")
156 private List<DNSKEYRecord> findKey(RRset dnskey_rrset, RRSIGRecord signature) {
157 if (!signature.getSigner().equals(dnskey_rrset.getName())) {
158 log.trace("findKey: could not find appropriate key because "
159 + "incorrect keyset was supplied. Wanted: "
160 + signature.getSigner() + ", got: "
161 + dnskey_rrset.getName());
166 int keyid = signature.getFootprint();
167 int alg = signature.getAlgorithm();
169 List<DNSKEYRecord> res = new ArrayList<DNSKEYRecord>(dnskey_rrset
172 for (Iterator i = dnskey_rrset.rrs(); i.hasNext();) {
173 DNSKEYRecord r = (DNSKEYRecord) i.next();
175 if ((r.getAlgorithm() == alg) && (r.getFootprint() == keyid)) {
180 if (res.size() == 0) {
181 log.trace("findKey: could not find a key matching "
182 + "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.)
195 * The rrset that the signature belongs to.
197 * The signature record to check.
198 * @return A value of DNSSEC.Secure if it looks OK, DNSSEC.Faile if it looks
201 private byte checkSignature(RRset rrset, RRSIGRecord sigrec) {
202 if ((rrset == null) || (sigrec == null)) {
203 return DNSSEC.Failed;
206 if (!rrset.getName().equals(sigrec.getName())) {
207 log.debug("Signature name does not match RRset name");
209 return SecurityStatus.BOGUS;
212 if (rrset.getType() != sigrec.getTypeCovered()) {
213 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();
222 if (now.before(start)) {
223 log.debug("Signature is not yet valid");
225 return SecurityStatus.BOGUS;
228 if (now.after(expire)) {
229 log.debug("Signature has expired (now = " + now
230 + ", sig expires = " + expire);
232 return SecurityStatus.BOGUS;
235 return SecurityStatus.SECURE;
238 public PublicKey parseDNSKEY(DNSKEYRecord key) {
239 AlgEntry ae = (AlgEntry) mAlgorithmMap.get(new Integer(key
242 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
246 // KEYConverter class from DNSjava
247 key = new DNSKEYRecord(key.getName(), key.getDClass(),
248 key.getTTL(), key.getFlags(), key.getProtocol(),
249 ae.dnssecAlg, key.getKey());
252 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).
260 * The rrset to verify.
262 * The signature to verify with.
264 * The (public) key associated with the RRSIG record.
265 * @return A security status code: SECURE if it worked, BOGUS if not,
266 * UNCHECKED if we just couldn't actually do the function.
268 public byte verifySignature(RRset rrset, RRSIGRecord sigrec,
271 PublicKey pk = parseDNSKEY(key);
275 .warn("Could not convert DNSKEY record to a JCA public key: "
278 return SecurityStatus.UNCHECKED;
281 byte[] data = SignUtils.generateSigData(rrset, sigrec);
283 Signature signer = getSignature(sigrec.getAlgorithm());
285 if (signer == null) {
286 return SecurityStatus.BOGUS;
289 signer.initVerify(pk);
292 byte[] sig = sigrec.getSignature();
294 if (isDSA(sigrec.getAlgorithm())) {
295 sig = SignUtils.convertDSASignature(sig);
298 if (!signer.verify(sig)) {
299 log.info("Signature failed to verify cryptographically");
300 log.debug("Failed signature: " + sigrec);
302 return SecurityStatus.BOGUS;
305 log.trace("Signature verified: " + sigrec);
307 return SecurityStatus.SECURE;
308 } catch (IOException e) {
309 log.error("I/O error", e);
310 } catch (GeneralSecurityException e) {
311 log.error("Security error", e);
314 // FIXME: Since I'm not sure what would cause an exception here (failure
315 // to have the required crypto?)
316 // We default to UNCHECKED instead of BOGUS. This could be wrong.
317 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) {
329 byte result = checkSignature(rrset, sigrec);
331 if (result != SecurityStatus.SECURE) {
335 List<DNSKEYRecord> keys = findKey(key_rrset, sigrec);
338 log.trace("could not find appropriate key");
340 return SecurityStatus.BOGUS;
343 byte status = SecurityStatus.UNCHECKED;
345 for (DNSKEYRecord key : keys) {
346 status = verifySignature(rrset, sigrec, key);
348 if (status == SecurityStatus.SECURE) {
357 * Verifies an RRset. This routine does not modify the RRset. This RRset is
358 * presumed to be verifiable, and the correct DNSKEY rrset is presumed to
361 * @return SecurityStatus.SECURE if the rrest verified positively,
362 * SecurityStatus.BOGUS otherwise.
364 @SuppressWarnings("unchecked")
365 public byte verify(RRset rrset, RRset key_rrset) {
366 Iterator i = rrset.sigs();
369 log.info("RRset failed to verify due to lack of signatures");
371 return SecurityStatus.BOGUS;
374 while (i.hasNext()) {
375 RRSIGRecord sigrec = (RRSIGRecord) i.next();
377 byte res = verifySignature(rrset, sigrec, key_rrset);
379 if (res == SecurityStatus.SECURE) {
384 log.info("RRset failed to verify: all signatures were BOGUS");
386 return SecurityStatus.BOGUS;
390 * Verify an RRset against a single DNSKEY. Use this when you must be
391 * certain that an RRset signed and verifies with a particular DNSKEY (as
392 * opposed to a particular DNSKEY rrset).
395 * The rrset to verify.
397 * The DNSKEY to verify with.
398 * @return SecurityStatus.SECURE if the rrset verified, BOGUS otherwise.
400 @SuppressWarnings("unchecked")
401 public byte verify(RRset rrset, DNSKEYRecord dnskey) {
402 // Iterate over RRSIGS
403 Iterator i = rrset.sigs();
406 log.info("RRset failed to verify due to lack of signatures");
408 return SecurityStatus.BOGUS;
411 while (i.hasNext()) {
412 RRSIGRecord sigrec = (RRSIGRecord) i.next();
414 // Skip RRSIGs that do not match our given key's footprint.
415 if (sigrec.getFootprint() != dnskey.getFootprint()) {
419 byte res = verifySignature(rrset, sigrec, dnskey);
421 if (res == SecurityStatus.SECURE) {
426 log.info("RRset failed to verify: all signatures were BOGUS");
428 return SecurityStatus.BOGUS;
431 public boolean supportsAlgorithm(int algorithm) {
432 return mAlgorithmMap.containsKey(new Integer(algorithm));
435 public boolean supportsAlgorithm(Name private_id) {
436 return mAlgorithmMap.containsKey(private_id);
439 public int baseAlgorithm(int algorithm) {
449 AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm));
462 /** @return the appropriate Signature object for this keypair. */
463 private Signature getSignature(int algorithm) {
467 AlgEntry entry = (AlgEntry) mAlgorithmMap
468 .get(new Integer(algorithm));
471 log.info("DNSSEC algorithm " + algorithm + " not recognized.");
476 // TODO: should we cache the instance?
477 s = Signature.getInstance(entry.jcaName);
478 } catch (NoSuchAlgorithmException e) {
479 log.error("error getting Signature object", e);
485 private static class AlgEntry {
486 public String jcaName;
487 public boolean isDSA;
488 public int dnssecAlg;
490 public AlgEntry(String name, int dnssecAlg, boolean isDSA) {
492 this.dnssecAlg = dnssecAlg;
497 // TODO: enable private algorithm support in dnsjava.
498 // Right now, this cannot be used because the DNSKEYRecord object doesn't
500 // the private key name.
501 // private Signature getSignature(Name private_alg)
503 // Signature s = null;
507 // String alg_id = (String) mAlgorithmMap.get(private_alg);
508 // if (alg_id == null)
510 // log.debug("DNSSEC private algorithm '" + private_alg
511 // + "' not recognized.");
515 // s = Signature.getInstance(alg_id);
517 // catch (NoSuchAlgorithmException e)
519 // log.error("error getting Signature object", e);