more progress -- still not compiling
authorDavid Blacka <david@blacka.com>
Sun, 19 Apr 2009 19:24:49 +0000 (15:24 -0400)
committerDavid Blacka <david@blacka.com>
Sun, 19 Apr 2009 19:24:49 +0000 (15:24 -0400)
src/se/rfc/unbound/DnsSecVerifier.java
src/se/rfc/unbound/NSEC3ValUtils.java
src/se/rfc/unbound/SMessage.java
src/se/rfc/unbound/SRRset.java
src/se/rfc/unbound/SignUtils.java [new file with mode: 0644]
src/se/rfc/unbound/ValUtils.java

index 239316e..d03b4fc 100644 (file)
  *
  */
 
-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;
index 73bd0f8..ddcd62d 100644 (file)
@@ -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();
index df29613..d361b52 100644 (file)
@@ -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
index 8c6cb7e..02503e4 100644 (file)
@@ -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 (file)
index 0000000..a34f244
--- /dev/null
@@ -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 <code>rrset_data</code>.
+   * @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.
+   * 
+   * <p>
+   * ASN.1 format = ASN1_SEQ . seq_length . ASN1_INT . Rlength . R . ANS1_INT .
+   * Slength . S
+   * </p>
+   * 
+   * 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.
+   * 
+   * <p>
+   * rfc2536 format = T . R . S
+   * </p>
+   * 
+   * 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;
+  }
+}
index 14570d5..6778b3c 100644 (file)
  *
  */
 
-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.