diff --git a/src/se/rfc/unbound/DnsSecVerifier.java b/src/se/rfc/unbound/DnsSecVerifier.java index 239316e..d03b4fc 100644 --- a/src/se/rfc/unbound/DnsSecVerifier.java +++ b/src/se/rfc/unbound/DnsSecVerifier.java @@ -28,13 +28,12 @@ * */ -package se.rfc.unbound.validator; +package se.rfc.unbound; import java.util.*; import java.io.*; import java.security.*; -import org.apache.log4j.Logger; import org.xbill.DNS.*; import org.xbill.DNS.security.*; @@ -43,11 +42,8 @@ import se.rfc.unbound.Util; /** * A class for performing basic DNSSEC verification. The DNSJAVA package - * contains a similar class. This is a reimplementation that allows us to have + * contains a similar class. This is a re-implementation that allows us to have * finer control over the validation process. - * - * @author davidb - * @version $Revision$ */ public class DnsSecVerifier { @@ -61,8 +57,6 @@ public class DnsSecVerifier */ private HashMap mAlgorithmMap; - private Logger log = Logger.getLogger(this.getClass()); - private static class AlgEntry { public String jcaName; @@ -121,15 +115,15 @@ public class DnsSecVerifier if (!mAlgorithmMap.containsKey(alg_orig)) { - log.warn("Unable to alias " + alg_alias + " to unknown algorithm " - + alg_orig); +// log.warn("Unable to alias " + alg_alias + " to unknown algorithm " +// + alg_orig); continue; } if (mAlgorithmMap.containsKey(alg_alias)) { - log.warn("Algorithm alias " + alg_alias - + " is already defined and cannot be redefined"); +// log.warn("Algorithm alias " + alg_alias +// + " is already defined and cannot be redefined"); continue; } @@ -141,11 +135,11 @@ public class DnsSecVerifier { Integer alg = (Integer) i.next(); AlgEntry entry = (AlgEntry) mAlgorithmMap.get(alg); - if (entry == null) - log.warn("DNSSEC alg " + alg + " has a null entry!"); - else - log.debug("DNSSEC alg " + alg + " maps to " + entry.jcaName - + " (" + entry.dnssecAlg + ")"); +// if (entry == null) +// log.warn("DNSSEC alg " + alg + " has a null entry!"); +// else +// log.debug("DNSSEC alg " + alg + " maps to " + entry.jcaName +// + " (" + entry.dnssecAlg + ")"); } } @@ -163,9 +157,9 @@ public class DnsSecVerifier { if (!signature.getSigner().equals(dnskey_rrset.getName())) { - log.trace("findKey: could not find appropriate key because " - + "incorrect keyset was supplied. Wanted: " + signature.getSigner() - + ", got: " + dnskey_rrset.getName()); +// log.trace("findKey: could not find appropriate key because " +// + "incorrect keyset was supplied. Wanted: " + signature.getSigner() +// + ", got: " + dnskey_rrset.getName()); return null; } @@ -185,8 +179,8 @@ public class DnsSecVerifier if (res.size() == 0) { - log.trace("findKey: could not find a key matching " - + "the algorithm and footprint in supplied keyset. "); +// log.trace("findKey: could not find a key matching " +// + "the algorithm and footprint in supplied keyset. "); return null; } return res; @@ -206,12 +200,12 @@ public class DnsSecVerifier if (rrset == null || sigrec == null) return DNSSEC.Failed; if (!rrset.getName().equals(sigrec.getName())) { - log.debug("Signature name does not match RRset name"); +// log.debug("Signature name does not match RRset name"); return SecurityStatus.BOGUS; } if (rrset.getType() != sigrec.getTypeCovered()) { - log.debug("Signature type does not match RRset type"); +// log.debug("Signature type does not match RRset type"); return SecurityStatus.BOGUS; } @@ -220,14 +214,14 @@ public class DnsSecVerifier Date expire = sigrec.getExpire(); if (now.before(start)) { - log.debug("Signature is not yet valid"); +// log.debug("Signature is not yet valid"); return SecurityStatus.BOGUS; } if (now.after(expire)) { - log.debug("Signature has expired (now = " + now + ", sig expires = " - + expire); +// log.debug("Signature has expired (now = " + now + ", sig expires = " +// + expire); return SecurityStatus.BOGUS; } @@ -271,8 +265,8 @@ public class DnsSecVerifier if (pk == null) { - log.warn("Could not convert DNSKEY record to a JCA public key: " - + key); +// log.warn("Could not convert DNSKEY record to a JCA public key: " +// + key); return SecurityStatus.UNCHECKED; } @@ -294,20 +288,20 @@ public class DnsSecVerifier } if (!signer.verify(sig)) { - log.info("Signature failed to verify cryptographically"); - log.debug("Failed signature: " + sigrec); +// log.info("Signature failed to verify cryptographically"); +// log.debug("Failed signature: " + sigrec); return SecurityStatus.BOGUS; } - log.trace("Signature verified: " + sigrec); +// log.trace("Signature verified: " + sigrec); return SecurityStatus.SECURE; } catch (IOException e) { - log.error("I/O error", e); +// log.error("I/O error", e); } catch (GeneralSecurityException e) { - log.error("Security error", e); +// log.error("Security error", e); } // FIXME: Since I'm not sure what would cause an exception here (failure @@ -334,7 +328,7 @@ public class DnsSecVerifier if (keys == null) { - log.trace("could not find appropriate key"); +// log.trace("could not find appropriate key"); return SecurityStatus.BOGUS; } @@ -365,7 +359,7 @@ public class DnsSecVerifier if (!i.hasNext()) { - log.info("RRset failed to verify due to lack of signatures"); +// log.info("RRset failed to verify due to lack of signatures"); return SecurityStatus.BOGUS; } @@ -378,7 +372,7 @@ public class DnsSecVerifier if (res == SecurityStatus.SECURE) return res; } - log.info("RRset failed to verify: all signatures were BOGUS"); +// log.info("RRset failed to verify: all signatures were BOGUS"); return SecurityStatus.BOGUS; } @@ -398,7 +392,7 @@ public class DnsSecVerifier Iterator i = rrset.sigs(); if (!i.hasNext()) { - log.info("RRset failed to verify due to lack of signatures"); +// log.info("RRset failed to verify due to lack of signatures"); return SecurityStatus.BOGUS; } @@ -414,7 +408,7 @@ public class DnsSecVerifier if (res == SecurityStatus.SECURE) return res; } - log.info("RRset failed to verify: all signatures were BOGUS"); +// log.info("RRset failed to verify: all signatures were BOGUS"); return SecurityStatus.BOGUS; } @@ -455,7 +449,7 @@ public class DnsSecVerifier AlgEntry entry = (AlgEntry) mAlgorithmMap.get(new Integer(algorithm)); if (entry == null) { - log.info("DNSSEC algorithm " + algorithm + " not recognized."); +// log.info("DNSSEC algorithm " + algorithm + " not recognized."); return null; } // TODO: should we cache the instance? @@ -463,7 +457,7 @@ public class DnsSecVerifier } catch (NoSuchAlgorithmException e) { - log.error("error getting Signature object", e); +// log.error("error getting Signature object", e); } return s; diff --git a/src/se/rfc/unbound/NSEC3ValUtils.java b/src/se/rfc/unbound/NSEC3ValUtils.java index 73bd0f8..ddcd62d 100644 --- a/src/se/rfc/unbound/NSEC3ValUtils.java +++ b/src/se/rfc/unbound/NSEC3ValUtils.java @@ -32,13 +32,11 @@ package se.rfc.unbound; import java.security.NoSuchAlgorithmException; import java.util.*; -import org.apache.log4j.Logger; import org.xbill.DNS.*; import org.xbill.DNS.utils.base32; -import se.rfc.unbound.validator.DnsSecVerifier; -import se.rfc.unbound.validator.SignUtils; -import se.rfc.unbound.validator.SignUtils.ByteArrayComparator; +import se.rfc.unbound.SignUtils.ByteArrayComparator; + public class NSEC3ValUtils { @@ -49,8 +47,6 @@ public class NSEC3ValUtils // parameters. The idea is to hash and compare for each group independently, // instead of having to skip NSEC3 RRs with the wrong parameters. - // The logger to use in static methods. - private static Logger st_log = Logger.getLogger(NSEC3ValUtils.class); private static Name asterisk_label = Name.fromConstantString("*"); @@ -204,7 +200,7 @@ public class NSEC3ValUtils } catch (NoSuchAlgorithmException e) { - st_log.debug("Did not recognize hash algorithm: " + params.alg); +// st_log.debug("Did not recognize hash algorithm: " + params.alg); return null; } } @@ -391,8 +387,8 @@ public class NSEC3ValUtils if (candidate == null) { - st_log.debug("proveClosestEncloser: could not find a " - + "candidate for the closest encloser."); +// st_log.debug("proveClosestEncloser: could not find a " +// + "candidate for the closest encloser."); return null; } @@ -400,7 +396,7 @@ public class NSEC3ValUtils { if (proveDoesNotExist) { - st_log.debug("proveClosestEncloser: proved that qname existed!"); +// st_log.debug("proveClosestEncloser: proved that qname existed!"); return null; } // otherwise, we need to nothing else to prove that qname is its own @@ -414,13 +410,13 @@ public class NSEC3ValUtils if (candidate.ce_nsec3.hasType(Type.NS) && !candidate.ce_nsec3.hasType(Type.SOA)) { - st_log.debug("proveClosestEncloser: closest encloser " - + "was a delegation!"); +// st_log.debug("proveClosestEncloser: closest encloser " +// + "was a delegation!"); return null; } if (candidate.ce_nsec3.hasType(Type.DNAME)) { - st_log.debug("proveClosestEncloser: closest encloser was a DNAME!"); +// st_log.debug("proveClosestEncloser: closest encloser was a DNAME!"); return null; } @@ -435,8 +431,8 @@ public class NSEC3ValUtils bac); if (candidate.nc_nsec3 == null) { - st_log.debug("Could not find proof that the " - + "closest encloser was the closest encloser"); +// st_log.debug("Could not find proof that the " +// + "closest encloser was the closest encloser"); return null; } @@ -524,8 +520,8 @@ public class NSEC3ValUtils NSEC3Parameters nsec3params = nsec3Parameters(nsec3s); if (nsec3params == null) { - st_log.debug("Could not find a single set of " + - "NSEC3 parameters (multiple parameters present)."); +// st_log.debug("Could not find a single set of " + +// "NSEC3 parameters (multiple parameters present)."); return false; } @@ -542,7 +538,7 @@ public class NSEC3ValUtils if (ce == null) { - st_log.debug("proveNameError: failed to prove a closest encloser."); +// st_log.debug("proveNameError: failed to prove a closest encloser."); return false; } @@ -557,8 +553,8 @@ public class NSEC3ValUtils bac); if (nsec3 == null) { - st_log.debug("proveNameError: could not prove that the " - + "applicable wildcard did not exist."); +// st_log.debug("proveNameError: could not prove that the " +// + "applicable wildcard did not exist."); return false; } @@ -653,8 +649,8 @@ public class NSEC3ValUtils NSEC3Parameters nsec3params = nsec3Parameters(nsec3s); if (nsec3params == null) { - st_log.debug("could not find a single set of " - + "NSEC3 parameters (multiple parameters present)"); +// st_log.debug("could not find a single set of " +// + "NSEC3 parameters (multiple parameters present)"); return false; } ByteArrayComparator bac = new ByteArrayComparator(); @@ -669,13 +665,13 @@ public class NSEC3ValUtils { if (nsec3.hasType(qtype)) { - st_log.debug("proveNodata: Matching NSEC3 proved that type existed!"); +// st_log.debug("proveNodata: Matching NSEC3 proved that type existed!"); return false; } if (nsec3.hasType(Type.CNAME)) { - st_log.debug("proveNodata: Matching NSEC3 proved " - + "that a CNAME existed!"); +// st_log.debug("proveNodata: Matching NSEC3 proved " +// + "that a CNAME existed!"); return false; } return true; @@ -695,8 +691,8 @@ public class NSEC3ValUtils // problem. if (ce == null) { - st_log.debug("proveNodata: did not match qname, " - + "nor found a proven closest encloser."); +// st_log.debug("proveNodata: did not match qname, " +// + "nor found a proven closest encloser."); return false; } @@ -714,7 +710,7 @@ public class NSEC3ValUtils { if (nsec3.hasType(qtype)) { - st_log.debug("proveNodata: matching wildcard had qtype!"); +// st_log.debug("proveNodata: matching wildcard had qtype!"); return false; } return true; @@ -723,16 +719,16 @@ public class NSEC3ValUtils // Case 5. if (qtype != Type.DS) { - st_log.debug("proveNodata: could not find matching NSEC3, " - + "nor matching wildcard, and qtype is not DS -- no more options."); +// st_log.debug("proveNodata: could not find matching NSEC3, " +// + "nor matching wildcard, and qtype is not DS -- no more options."); return false; } // We need to make sure that the covering NSEC3 is opt-in. if (!ce.nc_nsec3.getOptInFlag()) { - st_log.debug("proveNodata: covering NSEC3 was not " - + "opt-in in an opt-in DS NOERROR/NODATA case."); +// st_log.debug("proveNodata: covering NSEC3 was not " +// + "opt-in in an opt-in DS NOERROR/NODATA case."); return false; } @@ -758,7 +754,7 @@ public class NSEC3ValUtils NSEC3Parameters nsec3params = nsec3Parameters(nsec3s); if (nsec3params == null) { - st_log.debug("couldn't find a single set of NSEC3 parameters (multiple parameters present)."); +// st_log.debug("couldn't find a single set of NSEC3 parameters (multiple parameters present)."); return false; } @@ -779,10 +775,10 @@ public class NSEC3ValUtils if (candidate.nc_nsec3 == null) { - st_log.debug("proveWildcard: did not find a covering NSEC3 " - + "that covered the next closer name to " + qname + " from " - + candidate.closestEncloser + " (derived from wildcard " + wildcard - + ")"); +// st_log.debug("proveWildcard: did not find a covering NSEC3 " +// + "that covered the next closer name to " + qname + " from " +// + candidate.closestEncloser + " (derived from wildcard " + wildcard +// + ")"); return false; } @@ -812,8 +808,8 @@ public class NSEC3ValUtils NSEC3Parameters nsec3params = nsec3Parameters(nsec3s); if (nsec3params == null) { - st_log.debug("couldn't find a single set of " + - "NSEC3 parameters (multiple parameters present)."); +// st_log.debug("couldn't find a single set of " + +// "NSEC3 parameters (multiple parameters present)."); return SecurityStatus.BOGUS; } ByteArrayComparator bac = new ByteArrayComparator(); diff --git a/src/se/rfc/unbound/SMessage.java b/src/se/rfc/unbound/SMessage.java index df29613..d361b52 100644 --- a/src/se/rfc/unbound/SMessage.java +++ b/src/se/rfc/unbound/SMessage.java @@ -271,7 +271,13 @@ public class SMessage h.setRcode(mHeader.getRcode()); for (int i = 0; i < 16; i++) { - if (Flags.isFlag(i)) h.setFlag(i, mHeader.getFlag(i)); + if (Flags.isFlag(i)) { + if (mHeader.getFlag(i)) { + h.setFlag(i); + } else { + h.unsetFlag(i); + } + } } // Add all the records. -- this will set the counts correctly in the diff --git a/src/se/rfc/unbound/SRRset.java b/src/se/rfc/unbound/SRRset.java index 8c6cb7e..02503e4 100644 --- a/src/se/rfc/unbound/SRRset.java +++ b/src/se/rfc/unbound/SRRset.java @@ -37,7 +37,7 @@ import org.xbill.DNS.*; public class SRRset extends RRset { private SecurityStatus mSecurityStatus; - + /** Create a new, blank SRRset. */ public SRRset() { @@ -73,23 +73,23 @@ public SRRset(RRset r) * contained RRsig records as well. * @return The cloned SRRset. */ - public SRRset cloneSRRset(long withNewTTL) - { - SRRset nr = new SRRset(); - - for (Iterator i = rrs(); i.hasNext();) - { - nr.addRR(((Record) i.next()).withTTL(withNewTTL)); - } - for (Iterator i = sigs(); i.hasNext();) - { - nr.addRR(((Record) i.next()).withTTL(withNewTTL)); - } - - nr.mSecurityStatus = mSecurityStatus; - - return nr; - } +// public SRRset cloneSRRset(long withNewTTL) +// { +// SRRset nr = new SRRset(); +// +// for (Iterator i = rrs(); i.hasNext();) +// { +// nr.addRR(((Record) i.next()).withTTL(withNewTTL)); +// } +// for (Iterator i = sigs(); i.hasNext();) +// { +// nr.addRR(((Record) i.next()).withTTL(withNewTTL)); +// } +// +// nr.mSecurityStatus = mSecurityStatus; +// +// return nr; +// } public SRRset cloneSRRsetNoSigs() { @@ -103,6 +103,8 @@ public SRRset(RRset r) return nr; } + + /** * Return the current security status (generally: UNCHECKED, BOGUS, or * SECURE). @@ -125,11 +127,19 @@ public SRRset(RRset r) * Set the current security status for this SRRset. This status will be * shared amongst all copies of this SRRset (created with cloneSRRset()) */ - public void setSecurityStatus(int status) + public void setSecurityStatus(byte status) { mSecurityStatus.setStatus(status); } + public int totalSize() { + int num_sigs = 0; + for (Iterator i = sigs(); i.hasNext(); ) { + num_sigs++; + } + return size() + num_sigs; + } + /** * @return The total number of records (data + sigs) in the SRRset. */ @@ -138,6 +148,12 @@ public SRRset(RRset r) return totalSize(); } + public RRSIGRecord firstSig() { + for (Iterator i = sigs(); i.hasNext(); ) { + return (RRSIGRecord) i.next(); + } + return null; + } /** * @return true if this RRset has RRSIG records that cover data records. * (i.e., RRSIG SRRsets return false) @@ -158,12 +174,12 @@ public SRRset(RRset r) return sig.getSigner(); } - public void setTTL(long ttl) - { - if (ttl < 0) - { - throw new IllegalArgumentException("ttl can't be less than zero, stupid! was " + ttl); - } - super.setTTL(ttl); - } +// public void setTTL(long ttl) +// { +// if (ttl < 0) +// { +// throw new IllegalArgumentException("ttl can't be less than zero, stupid! was " + ttl); +// } +// super.setTTL(ttl); +// } } diff --git a/src/se/rfc/unbound/SignUtils.java b/src/se/rfc/unbound/SignUtils.java new file mode 100644 index 0000000..a34f244 --- /dev/null +++ b/src/se/rfc/unbound/SignUtils.java @@ -0,0 +1,474 @@ +/* + * $Id$ + * + * Copyright (c) 2005 VeriSign, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package se.rfc.unbound; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.SignatureException; +import java.security.interfaces.DSAParams; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.Iterator; + +import org.xbill.DNS.DNSKEYRecord; +import org.xbill.DNS.DNSOutput; +import org.xbill.DNS.Name; +import org.xbill.DNS.RRSIGRecord; +import org.xbill.DNS.RRset; +import org.xbill.DNS.Record; +import org.xbill.DNS.utils.base64; + +/** + * This class contains a bunch of utility methods that are generally useful in + * signing and verifying rrsets. + * + * @author David Blacka (original) + * @author $Author$ + * @version $Revision$ + */ + +public class SignUtils +{ + + /** + * This class implements a basic comparitor for byte arrays. It is primarily + * useful for comparing RDATA portions of DNS records in doing DNSSEC + * canonical ordering. + * + * @author David Blacka (original) + */ + public static class ByteArrayComparator implements Comparator + { + private int mOffset = 0; + private boolean mDebug = false; + + public ByteArrayComparator() + { + } + + public ByteArrayComparator(int offset, boolean debug) + { + mOffset = offset; + mDebug = debug; + } + + public int compare(Object o1, Object o2) throws ClassCastException + { + byte[] b1 = (byte[]) o1; + byte[] b2 = (byte[]) o2; + + for (int i = mOffset; i < b1.length && i < b2.length; i++) + { + if (b1[i] != b2[i]) + { + if (mDebug) + { + System.out.println("offset " + i + " differs (this is " + + (i - mOffset) + " bytes in from our offset.)"); + } + return (b1[i] & 0xFF) - (b2[i] & 0xFF); + } + } + + return b1.length - b2.length; + } + } + + // private static final int DSA_SIGNATURE_LENGTH = 20; + private static final int ASN1_INT = 0x02; + private static final int ASN1_SEQ = 0x30; + + public static final int RR_NORMAL = 0; + public static final int RR_DELEGATION = 1; + public static final int RR_GLUE = 2; + public static final int RR_INVALID = 3; + + /** + * Generate from some basic information a prototype SIG RR containing + * everything but the actual signature itself. + * + * @param rrset the RRset being signed. + * @param signer the name of the signing key + * @param alg the algorithm of the signing key + * @param keyid the keyid (or footprint) of the signing key + * @param start the SIG inception time. + * @param expire the SIG expiration time. + * @param sig_ttl the TTL of the resulting SIG record. + * @return a prototype signature based on the RRset and key information. + */ + public static RRSIGRecord generatePreRRSIG(RRset rrset, Name signer, + int alg, int keyid, Date start, Date expire, long sig_ttl) + { + return new RRSIGRecord(rrset.getName(), rrset.getDClass(), sig_ttl, rrset + .getType(), alg, rrset.getTTL(), expire, start, keyid, signer, null); + } + + /** + * Generate from some basic information a prototype SIG RR containing + * everything but the actual signature itself. + * + * @param rrset the RRset being signed. + * @param key the public KEY RR counterpart to the key being used to sign + * the RRset + * @param start the SIG inception time. + * @param expire the SIG expiration time. + * @param sig_ttl the TTL of the resulting SIG record. + * @return a prototype signature based on the RRset and key information. + */ + public static RRSIGRecord generatePreRRSIG(RRset rrset, DNSKEYRecord key, + Date start, Date expire, long sig_ttl) + { + return generatePreRRSIG(rrset, key.getName(), key.getAlgorithm(), key + .getFootprint(), start, expire, sig_ttl); + } + + /** + * Generate from some basic information a prototype SIG RR containing + * everything but the actual signature itself. + * + * @param rec the DNS record being signed (forming an entire RRset). + * @param key the public KEY RR counterpart to the key signing the record. + * @param start the SIG inception time. + * @param expire the SIG expiration time. + * @param sig_ttl the TTL of the result SIG record. + * @return a prototype signature based on the Record and key information. + */ + public static RRSIGRecord generatePreRRSIG(Record rec, DNSKEYRecord key, + Date start, Date expire, long sig_ttl) + { + return new RRSIGRecord(rec.getName(), rec.getDClass(), sig_ttl, rec + .getType(), key.getAlgorithm(), rec.getTTL(), expire, start, key + .getFootprint(), key.getName(), null); + } + + /** + * Generate the binary image of the prototype SIG RR. + * + * @param presig the SIG RR prototype. + * @return the RDATA portion of the prototype SIG record. This forms the + * first part of the data to be signed. + */ + private static byte[] generatePreSigRdata(RRSIGRecord presig) + { + // Generate the binary image; + DNSOutput image = new DNSOutput(); + + // precalc some things + int start_time = (int) (presig.getTimeSigned().getTime() / 1000); + int expire_time = (int) (presig.getExpire().getTime() / 1000); + Name signer = presig.getSigner(); + + // first write out the partial SIG record (this is the SIG RDATA + // minus the actual signature. + image.writeU16(presig.getTypeCovered()); + image.writeU8(presig.getAlgorithm()); + image.writeU8(presig.getLabels()); + image.writeU32((int) presig.getOrigTTL()); + image.writeU32(expire_time); + image.writeU32(start_time); + image.writeU16(presig.getFootprint()); + image.writeByteArray(signer.toWireCanonical()); + + return image.toByteArray(); + } + + /** + * Calculate the canonical wire line format of the RRset. + * + * @param rrset the RRset to convert. + * @param ttl the TTL to use when canonicalizing -- this is generally the + * TTL of the signature if there is a pre-existing signature. If + * not it is just the ttl of the rrset itself. + * @param labels the labels field of the signature, or 0. + * @return the canonical wire line format of the rrset. This is the second + * part of data to be signed. + */ + public static byte[] generateCanonicalRRsetData(RRset rrset, long ttl, + int labels) + { + DNSOutput image = new DNSOutput(); + + if (ttl == 0) ttl = rrset.getTTL(); + Name n = rrset.getName(); + if (labels == 0) + { + labels = n.labels(); + } + else + { + // correct for Name()'s conception of label count. + labels++; + } + boolean wildcardName = false; + if (n.labels() != labels) + { + n = n.wild(n.labels() - labels); + wildcardName = true; +// log.trace("Detected wildcard expansion: " + rrset.getName() + " changed to " + n); + } + + // now convert load the wire format records in the RRset into a + // list of byte arrays. + ArrayList canonical_rrs = new ArrayList(); + for (Iterator i = rrset.rrs(); i.hasNext();) + { + Record r = (Record) i.next(); + if (r.getTTL() != ttl || wildcardName) + { + // If necessary, we need to create a new record with a new ttl or ownername. + // In the TTL case, this avoids changing the ttl in the response. + r = Record.newRecord(n, r.getType(), r.getDClass(), ttl, r + .rdataToWireCanonical()); + } + byte[] wire_fmt = r.toWireCanonical(); + canonical_rrs.add(wire_fmt); + } + + // put the records into the correct ordering. + // Caculate the offset where the RDATA begins (we have to skip + // past the length byte) + + int offset = rrset.getName().toWireCanonical().length + 10; + ByteArrayComparator bac = new ByteArrayComparator(offset, false); + + Collections.sort(canonical_rrs, bac); + + for (Iterator i = canonical_rrs.iterator(); i.hasNext();) + { + byte[] wire_fmt_rec = (byte[]) i.next(); + image.writeByteArray(wire_fmt_rec); + } + + return image.toByteArray(); + } + + /** + * Given an RRset and the prototype signature, generate the canonical data + * that is to be signed. + * + * @param rrset the RRset to be signed. + * @param presig a prototype SIG RR created using the same RRset. + * @return a block of data ready to be signed. + */ + public static byte[] generateSigData(RRset rrset, RRSIGRecord presig) + throws IOException + { + byte[] rrset_data = generateCanonicalRRsetData(rrset, presig.getOrigTTL(), presig.getLabels()); + + return generateSigData(rrset_data, presig); + } + + /** + * Given an RRset and the prototype signature, generate the canonical data + * that is to be signed. + * + * @param rrset_data the RRset converted into canonical wire line format (as + * per the canonicalization rules in RFC 2535). + * @param presig the prototype signature based on the same RRset represented + * in rrset_data. + * @return a block of data ready to be signed. + */ + public static byte[] generateSigData(byte[] rrset_data, RRSIGRecord presig) + throws IOException + { + byte[] sig_rdata = generatePreSigRdata(presig); + + ByteArrayOutputStream image = new ByteArrayOutputStream(sig_rdata.length + + rrset_data.length); + + image.write(sig_rdata); + image.write(rrset_data); + + return image.toByteArray(); + } + + /** + * Given the acutal signature an the prototype signature, combine them and + * return the fully formed SIGRecord. + * + * @param signature the cryptographic signature, in DNSSEC format. + * @param presig the prototype SIG RR to add the signature to. + * @return the fully formed SIG RR. + */ + public static RRSIGRecord generateRRSIG(byte[] signature, RRSIGRecord presig) + { + return new RRSIGRecord(presig.getName(), presig.getDClass(), presig + .getTTL(), presig.getTypeCovered(), presig.getAlgorithm(), presig + .getOrigTTL(), presig.getExpire(), presig.getTimeSigned(), presig + .getFootprint(), presig.getSigner(), signature); + } + + /** + * Converts from a RFC 2536 formatted DSA signature to a JCE (ASN.1) + * formatted signature. + * + *

+ * ASN.1 format = ASN1_SEQ . seq_length . ASN1_INT . Rlength . R . ANS1_INT . + * Slength . S + *

+ * + * The integers R and S may have a leading null byte to force the integer + * positive. + * + * @param signature the RFC 2536 formatted DSA signature. + * @return The ASN.1 formatted DSA signature. + * @throws SignatureException if there was something wrong with the RFC 2536 + * formatted signature. + */ + public static byte[] convertDSASignature(byte[] signature) + throws SignatureException + { + if (signature.length != 41) + throw new SignatureException("RFC 2536 signature not expected length."); + + byte r_pad = 0; + byte s_pad = 0; + + // handle initial null byte padding. + if (signature[1] < 0) r_pad++; + if (signature[21] < 0) s_pad++; + + // ASN.1 length = R length + S length + (2 + 2 + 2), where each 2 + // is for a ASN.1 type-length byte pair of which there are three + // (SEQ, INT, INT). + byte sig_length = (byte) (40 + r_pad + s_pad + 6); + + byte sig[] = new byte[sig_length]; + byte pos = 0; + + sig[pos++] = ASN1_SEQ; + sig[pos++] = (byte) (sig_length - 2); // all but the SEQ type+length. + sig[pos++] = ASN1_INT; + sig[pos++] = (byte) (20 + r_pad); + + // copy the value of R, leaving a null byte if necessary + if (r_pad == 1) sig[pos++] = 0; + + System.arraycopy(signature, 1, sig, pos, 20); + pos += 20; + + sig[pos++] = ASN1_INT; + sig[pos++] = (byte) (20 + s_pad); + + // copy the value of S, leaving a null byte if necessary + if (s_pad == 1) sig[pos++] = 0; + + System.arraycopy(signature, 21, sig, pos, 20); + + return sig; + } + + /** + * Converts from a JCE (ASN.1) formatted DSA signature to a RFC 2536 + * compliant signature. + * + *

+ * rfc2536 format = T . R . S + *

+ * + * where T is a number between 0 and 8, which is based on the DSA key + * length, and R & S are formatted to be exactly 20 bytes each (no leading + * null bytes). + * + * @param params the DSA parameters associated with the DSA key used to + * generate the signature. + * @param signature the ASN.1 formatted DSA signature. + * @return a RFC 2536 formatted DSA signature. + * @throws SignatureException if something is wrong with the ASN.1 format. + */ + public static byte[] convertDSASignature(DSAParams params, byte[] signature) + throws SignatureException + { + if (signature[0] != ASN1_SEQ || signature[2] != ASN1_INT) + { + throw new SignatureException( + "Invalid ASN.1 signature format: expected SEQ, INT"); + } + + byte r_pad = (byte) (signature[3] - 20); + + if (signature[24 + r_pad] != ASN1_INT) + { + throw new SignatureException( + "Invalid ASN.1 signature format: expected SEQ, INT, INT"); + } + +// log.trace("(start) ASN.1 DSA Sig:\n" + base64.toString(signature)); + + byte s_pad = (byte) (signature[25 + r_pad] - 20); + + byte[] sig = new byte[41]; // all rfc2536 signatures are 41 bytes. + + // Calculate T: + sig[0] = (byte) ((params.getP().bitLength() - 512) / 64); + + // copy R value + if (r_pad >= 0) + { + System.arraycopy(signature, 4 + r_pad, sig, 1, 20); + } + else + { + // R is shorter than 20 bytes, so right justify the number + // (r_pad is negative here, remember?). + Arrays.fill(sig, 1, 1 - r_pad, (byte) 0); + System.arraycopy(signature, 4, sig, 1 - r_pad, 20 + r_pad); + } + + // copy S value + if (s_pad >= 0) + { + System.arraycopy(signature, 26 + r_pad + s_pad, sig, 21, 20); + } + else + { + // S is shorter than 20 bytes, so right justify the number + // (s_pad is negative here). + Arrays.fill(sig, 21, 21 - s_pad, (byte) 0); + System.arraycopy(signature, 26 + r_pad, sig, 21 - s_pad, 20 + s_pad); + } + +// if (r_pad < 0 || s_pad < 0) +// { +// log.trace("(finish ***) RFC 2536 DSA Sig:\n" + base64.toString(sig)); +// +// } +// else +// { +// log.trace("(finish) RFC 2536 DSA Sig:\n" + base64.toString(sig)); +// } + + return sig; + } +} diff --git a/src/se/rfc/unbound/ValUtils.java b/src/se/rfc/unbound/ValUtils.java index 14570d5..6778b3c 100644 --- a/src/se/rfc/unbound/ValUtils.java +++ b/src/se/rfc/unbound/ValUtils.java @@ -28,17 +28,14 @@ * */ -package se.rfc.unbound.validator; +package se.rfc.unbound; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Iterator; -import org.apache.log4j.Logger; import org.xbill.DNS.*; -import se.rfc.unbound.*; - /** * This is a collection of routines encompassing the logic of validating * different message types. @@ -74,9 +71,6 @@ public class ValUtils /** A response to a qtype=ANY query. */ public static final int ANY = 6; - private Logger log = Logger.getLogger(this.getClass()); - private static Logger st_log = Logger.getLogger(ValUtils.class); - /** A local copy of the verifier object. */ private DnsSecVerifier mVerifier; @@ -131,7 +125,7 @@ public class ValUtils if (rrsets[i].getType() == Type.CNAME) return CNAME; } - st_log.warn("Failed to classify response message:\n" + m); +// st_log.warn("Failed to classify response message:\n" + m); return UNKNOWN; } @@ -145,10 +139,10 @@ public class ValUtils * @return a signer name, if the response is signed (even partially), or * null if the response isn't signed. */ - public Name findSigner(SMessage m, Request request) + public Name findSigner(SMessage m) { int subtype = classifyResponse(m); - Name qname = request.getQName(); + Name qname = m.getQName(); SRRset[] rrsets; @@ -182,8 +176,8 @@ public class ValUtils } return null; default : - log.debug("findSigner: could not find signer name " - + "for unknown type response."); +// log.debug("findSigner: could not find signer name " +// + "for unknown type response."); return null; } } @@ -222,87 +216,87 @@ public class ValUtils * this sort of thing is checked before fetching the matching DNSKEY * rrset. */ - public KeyEntry verifyNewDNSKEYs(SRRset dnskey_rrset, SRRset ds_rrset) - { - if (!dnskey_rrset.getName().equals(ds_rrset.getName())) - { - log.debug("DNSKEY RRset did not match DS RRset by name!"); - return KeyEntry - .newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass()); - } - - // as long as this is false, we can consider this DS rrset to be - // equivalent to no DS rrset. - boolean hasUsefulDS = false; - - for (Iterator i = ds_rrset.rrs(); i.hasNext();) - { - DSRecord ds = (DSRecord) i.next(); - - // Check to see if we can understand this DS. - if (!supportsDigestID(ds.getDigestID()) - || !mVerifier.supportsAlgorithm(ds.getAlgorithm())) - { - continue; - } - - // Once we see a single DS with a known digestID and algorithm, we - // cannot return INSECURE (with a "null" KeyEntry). - hasUsefulDS = true; - - DNSKEY : for (Iterator j = dnskey_rrset.rrs(); j.hasNext();) - { - DNSKEYRecord dnskey = (DNSKEYRecord) j.next(); - - // Skip DNSKEYs that don't match the basic criteria. - if (ds.getFootprint() != dnskey.getFootprint() - || ds.getAlgorithm() != dnskey.getAlgorithm()) - { - continue; - } - - // Convert the candidate DNSKEY into a hash using the same DS hash - // algorithm. - byte[] key_hash = calculateDSHash(dnskey, ds.getDigestID()); - byte[] ds_hash = ds.getDigest(); - - // see if there is a length mismatch (unlikely) - if (key_hash.length != ds_hash.length) - { - continue DNSKEY; - } - - for (int k = 0; k < key_hash.length; k++) - { - if (key_hash[k] != ds_hash[k]) continue DNSKEY; - } - - // Otherwise, we have a match! Make sure that the DNSKEY verifies - // *with this key*. - byte res = mVerifier.verify(dnskey_rrset, dnskey); - if (res == SecurityStatus.SECURE) - { - log.trace("DS matched DNSKEY."); - dnskey_rrset.setSecurityStatus(SecurityStatus.SECURE); - return KeyEntry.newKeyEntry(dnskey_rrset); - } - // If it didn't validate with the DNSKEY, try the next one! - } - } - - // None of the DS's worked out. - - // If no DSs were understandable, then this is OK. - if (!hasUsefulDS) - { - log.debug("No usuable DS records were found -- treating as insecure."); - return KeyEntry.newNullKeyEntry(ds_rrset.getName(), ds_rrset - .getDClass(), ds_rrset.getTTL()); - } - // If any were understandable, then it is bad. - log.debug("Failed to match any usable DS to a DNSKEY."); - return KeyEntry.newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass()); - } +// public KeyEntry verifyNewDNSKEYs(SRRset dnskey_rrset, SRRset ds_rrset) +// { +// if (!dnskey_rrset.getName().equals(ds_rrset.getName())) +// { +//// log.debug("DNSKEY RRset did not match DS RRset by name!"); +// return KeyEntry +// .newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass()); +// } +// +// // as long as this is false, we can consider this DS rrset to be +// // equivalent to no DS rrset. +// boolean hasUsefulDS = false; +// +// for (Iterator i = ds_rrset.rrs(); i.hasNext();) +// { +// DSRecord ds = (DSRecord) i.next(); +// +// // Check to see if we can understand this DS. +// if (!supportsDigestID(ds.getDigestID()) +// || !mVerifier.supportsAlgorithm(ds.getAlgorithm())) +// { +// continue; +// } +// +// // Once we see a single DS with a known digestID and algorithm, we +// // cannot return INSECURE (with a "null" KeyEntry). +// hasUsefulDS = true; +// +// DNSKEY : for (Iterator j = dnskey_rrset.rrs(); j.hasNext();) +// { +// DNSKEYRecord dnskey = (DNSKEYRecord) j.next(); +// +// // Skip DNSKEYs that don't match the basic criteria. +// if (ds.getFootprint() != dnskey.getFootprint() +// || ds.getAlgorithm() != dnskey.getAlgorithm()) +// { +// continue; +// } +// +// // Convert the candidate DNSKEY into a hash using the same DS hash +// // algorithm. +// byte[] key_hash = calculateDSHash(dnskey, ds.getDigestID()); +// byte[] ds_hash = ds.getDigest(); +// +// // see if there is a length mismatch (unlikely) +// if (key_hash.length != ds_hash.length) +// { +// continue DNSKEY; +// } +// +// for (int k = 0; k < key_hash.length; k++) +// { +// if (key_hash[k] != ds_hash[k]) continue DNSKEY; +// } +// +// // Otherwise, we have a match! Make sure that the DNSKEY verifies +// // *with this key*. +// byte res = mVerifier.verify(dnskey_rrset, dnskey); +// if (res == SecurityStatus.SECURE) +// { +//// log.trace("DS matched DNSKEY."); +// dnskey_rrset.setSecurityStatus(SecurityStatus.SECURE); +// return KeyEntry.newKeyEntry(dnskey_rrset); +// } +// // If it didn't validate with the DNSKEY, try the next one! +// } +// } +// +// // None of the DS's worked out. +// +// // If no DSs were understandable, then this is OK. +// if (!hasUsefulDS) +// { +//// log.debug("No usuable DS records were found -- treating as insecure."); +// return KeyEntry.newNullKeyEntry(ds_rrset.getName(), ds_rrset +// .getDClass(), ds_rrset.getTTL()); +// } +// // If any were understandable, then it is bad. +//// log.debug("Failed to match any usable DS to a DNSKEY."); +// return KeyEntry.newBadKeyEntry(ds_rrset.getName(), ds_rrset.getDClass()); +// } /** * Given a DNSKEY record, generate the DS record from it. @@ -327,18 +321,17 @@ public class ValUtils md = MessageDigest.getInstance("SHA"); return md.digest(os.toByteArray()); case DSRecord.SHA256_DIGEST_ID: - SHA256 sha = new SHA256(); - sha.setData(os.toByteArray()); - return sha.getDigest(); + md = MessageDigest.getInstance("SHA256"); + return md.digest(os.toByteArray()); default : - st_log.warn("Unknown DS algorithm: " + ds_alg); +// st_log.warn("Unknown DS algorithm: " + ds_alg); return null; } } catch (NoSuchAlgorithmException e) { - st_log.error("Error using DS algorithm: " + ds_alg, e); +// st_log.error("Error using DS algorithm: " + ds_alg, e); return null; } } @@ -379,7 +372,7 @@ public class ValUtils * @param rrset The SRRset to update. * @param security The security status. */ - public static void setRRsetSecurity(SRRset rrset, int security) + public static void setRRsetSecurity(SRRset rrset, byte security) { if (rrset == null) return; @@ -405,7 +398,7 @@ public class ValUtils * (ans_rrset.getSecurityStatus() != SecurityStatus.SECURE) { return; } * key_rrset = ke.getRRset(); */ - public static void setMessageSecurity(SMessage m, int security) + public static void setMessageSecurity(SMessage m, byte security) { if (m == null) return; @@ -441,21 +434,21 @@ public class ValUtils if (rrset.getSecurityStatus() == SecurityStatus.SECURE) { - log.trace("verifySRRset: rrset <" + rrset_name - + "> previously found to be SECURE"); +// log.trace("verifySRRset: rrset <" + rrset_name +// + "> previously found to be SECURE"); return SecurityStatus.SECURE; } byte status = mVerifier.verify(rrset, key_rrset); if (status != SecurityStatus.SECURE) { - log.debug("verifySRRset: rrset <" + rrset_name + "> found to be BAD"); +// log.debug("verifySRRset: rrset <" + rrset_name + "> found to be BAD"); status = SecurityStatus.BOGUS; } - else - { - log.trace("verifySRRset: rrset <" + rrset_name + "> found to be SECURE"); - } +// else +// { +// log.trace("verifySRRset: rrset <" + rrset_name + "> found to be SECURE"); +// } rrset.setSecurityStatus(status); return status; @@ -477,6 +470,12 @@ public class ValUtils return false; } + private static RRSIGRecord rrsetFirstSig(RRset rrset) { + for (Iterator i = rrset.sigs(); i.hasNext(); ) { + return (RRSIGRecord) i.next(); + } + return null; + } /** * Determine by looking at a signed RRset whether or not the rrset name was * the result of a wildcard expansion. @@ -488,7 +487,7 @@ public class ValUtils public static boolean rrsetIsWildcardExpansion(RRset rrset) { if (rrset == null) return false; - RRSIGRecord rrsig = (RRSIGRecord) rrset.firstSig(); + RRSIGRecord rrsig = rrsetFirstSig(rrset); if (rrset.getName().labels() - 1 > rrsig.getLabels()) { @@ -510,7 +509,7 @@ public class ValUtils public static Name rrsetWildcard(RRset rrset) { if (rrset == null) return null; - RRSIGRecord rrsig = (RRSIGRecord) rrset.firstSig(); + RRSIGRecord rrsig = rrsetFirstSig(rrset); // if the RRSIG label count is shorter than the number of actual labels, // then this rrset was synthesized from a wildcard.