Start of CNAME support; add unit test framework and initial tests
authorDavid Blacka <davidb@verisign.com>
Tue, 12 Dec 2017 19:05:57 +0000 (19:05 +0000)
committerDavid Blacka <davidb@verisign.com>
Tue, 12 Dec 2017 19:05:57 +0000 (19:05 +0000)
build.xml
src/com/verisign/tat/dnssec/CaptiveValidator.java
tests/com/verisign/tat/dnssec/CaptiveValidatorTest.java [new file with mode: 0644]
tests/junit-3.8.1.jar [new file with mode: 0644]

index f8a9593..5fe3b8b 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -12,6 +12,9 @@
   <property name="build.lib.dest" value="${build.dir}/lib" />
   <property name="build.src" value="src" />
 
+  <property name="build.test.src" value="tests" />
+  <property name="build.test.dest" value="${build.dir}/tests/classes" />
+
   <property name="packages" value="com.verisignlabs.dnssec.*" />
   <property name="doc.dir" value="docs" />
   <property name="javadoc.dest" value="${doc.dir}/javadoc" />
   </path>
   <property name="project.classpath" refid="project.classpath" />
 
+  <!-- set the classpath for the unit tests -->
+  <path id="test.classpath">
+    <pathelement location="${build.dest}" />
+    <fileset dir="${lib.dir}" includes="*.jar,*.zip" />
+    <path location="${build.test.dest}" />
+    <path location="${build.dest}" />
+    <path location="${build.test.src}/junit-3.8.1.jar" />
+  </path>
+
   <target name="prepare-src">
     <mkdir dir="${build.dest}" />
     <mkdir dir="${build.lib.dest}" />
   </target>
 
+  <target name="prepare-test">
+    <mkdir dir="${build.test.dest}" />
+  </target>
+
   <target name="compile" depends="prepare-src" >
     <javac srcdir="${build.src}"
            destdir="${build.dest}"
     </tar>
   </target>
 
+  <target name="compile_tests" depends="prepare-test,compile">
+    <javac destdir="${build.test.dest}" debug="true"
+           classpathref="test.classpath"
+           includeantruntime="false"
+           target="1.4"
+           source="1.4">
+      <src path="${build.test.src}"/>
+    </javac>
+  </target>
+
+  <target name="test" depends="compile_tests">
+    <antcall target="_run_tests">
+      <param name="classpathref" value="tests.classpath" />
+    </antcall>
+  </target>
+
+  <target name="_run_tests">
+    <junit fork="yes" forkmode="perTest" dir="${basedir}"
+           haltonerror="on" haltonfailure="on"
+           includeantruntime="true">
+      <classpath>
+        <path location="${build.dest}" />
+        <fileset dir="${lib.dir}" includes="*.jar,*.zip" />
+        <path location="${build.test.dest}" />
+        <path location="${build.dest}" />
+        <path location="${build.test.src}/junit-3.8.1.jar" />
+      </classpath>
+
+      <formatter type="plain" usefile="no" />
+
+      <test name="${testcase}" if="testcase" />
+
+      <batchtest unless="testcase">
+        <fileset dir="${build.test.dest}">
+          <include name="**/*Test.class" />
+        </fileset>
+      </batchtest>
+    </junit>
+  </target>
+
   <target name="clean" depends="usage">
     <delete dir="${build.dest}" />
     <delete dir="${build.lib.dest}" />
   </target>
 
 </project>
-
index 6b451a3..5304967 100644 (file)
@@ -611,8 +611,126 @@ public class CaptiveValidator {
         m.setStatus(SecurityStatus.BOGUS);
     }
 
-    // FIXME: write CNAME validation code.
-    private void validateCNAMEResponse(SMessage message, SRRset key_rrset) {}
+    /**
+     * Given a "CNAME" response (i.e., a response that contains at
+     * least one CNAME, and qtype != CNAME).  This largely consists of
+     * validating each CNAME RRset until the CNAME chain goes "out of
+     * zone".  Note that out-of-order CNAME chains will have been
+     * cleaned up via normalize().  When traversing the CNAME chain,
+     * we detect if the CNAME were generated from a wildcard, and we
+     * detect when the chain goes "out-of-zone".  If the chain doesn't
+     * go out-of-zone, we then determine if the CNAME response was
+     * positive or negative (i.e., did it end with a non-CNAME
+     * RRset?).  For each in-zone wildcard generated CNAME, we check
+     * for a proof that the alias (the owner of each cname) doesn't
+     * exist.  If the response is negative (i.e., remains in-zone and
+     * results in no RRset, we do a NODATA or NXDOMAIN proof based on
+     * the actual RCODE.
+     *
+     * Note that once the CNAME chain goes out of zone, any further
+     * CNAMEs are not DNSSEC validated (we would need more trusted
+     * keysets for that), so this isn't useful in all cases (i.e., for
+     * testing a nameserver, like BIND, which generates CNAME chains
+     * across zones.)
+     *
+     * Note that by the time this method is called, the process of
+     * finding the trusted DNSKEY rrset that signs this reponse must
+     * already have been completed.
+     */
+    private void validateCNAMEResponse(SMessage message, SRRset key_rrset)
+    {
+        Name qname = message.getQName();
+        int  qtype = message.getQType();
+
+        Name       sname     = qname; // this is the "current" name in the chain
+        boolean    dname     = false; // a flag indicating that prev iteration was a dname
+        boolean    inZone    = true; // a flag telling us if we ended up in zone.
+        boolean    positive  = false; // a flag telling us if we ended with a positive answer
+        List<Name> wildcards = new Vector<Name>();
+        Name       wc        = null;
+        Name       zone      = key_rrset.getName();
+
+        SRRset[] rrsets = message.getSectionRRsets(Section.ANSWER);
+
+        // Validate the ANSWER section RRsets.
+        for (int i = 0; i < rrsets.length; i++) {
+
+            int  rtype = rrsets[i].getType();
+            Name rname = rrsets[i].getName();
+
+            // Follow the CNAME chain
+            if (rtype == Type.CNAME) {
+                // If we've gotten off track...  Note: this should be
+                // impossible with normalization in effect.
+                if (!sname.equals(rname)) {
+                    mErrorList.add("CNAME chain is broken: expected owner name of " +
+                                   sname + " got: " + rname);
+                    message.setStatus(SecurityStatus.BOGUS);
+                    return;
+                }
+
+                sname = ((CNAMERecord) rrsets[i].first()).getAlias();
+
+                // Check to see if the CNAME was generated by a
+                // wildcard.  We store the generated name instead of
+                // the wildcard value, as we need to prove that the
+                // wildcard wasn't blocked.
+                wc = ValUtils.rrsetWildcard(rrsets[i]);
+                if (wc != null) {
+                    wildcards.add(sname);
+                }
+            }
+
+            // Note when we see a DNAME.
+            if (rtype == Type.DNAME) {
+                dname = true;
+                wc = ValUtils.rrsetWildcard(rrsets[i]);
+                if (wc != null) {
+                    mErrorList.add("Illegal wildcard DNAME found: " + rrsets[i]);
+                }
+            }
+
+            // Skip validation of CNAMEs following DNAMEs.  The
+            // normalization step will have synthesized an unsigned
+            // CNAME RRset.
+            if (dname && rtype == Type.CNAME) {
+                dname = false;
+                continue;
+            }
+
+            if (rtype == qtype) {
+                positive = true;
+            }
+
+            // Once we've gone off the reservation, avoid further
+            // validation.
+            if (! rname.subdomain(zone)) {
+                inZone = false;
+                break;
+            }
+
+            int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
+
+            if (status != SecurityStatus.SECURE) {
+                mErrorList.add("CNAME response has a failed ANSWER rrset: " +
+                               rrsets[i]);
+                message.setStatus(SecurityStatus.BOGUS);
+
+                return;
+            }
+        }
+
+
+        // Validate the AUTHORITY section.
+        rrsets = message.getSectionRRsets(Section.ANSWER);
+        for (int i = 0; i < rrsets.length; i++) {
+
+
+        }
+
+        log.trace("Successfully validated CNAME response");
+        message.setStatus(SecurityStatus.SECURE);
+    }
 
     /**
      * Given an "ANY" response -- a response that contains an answer
diff --git a/tests/com/verisign/tat/dnssec/CaptiveValidatorTest.java b/tests/com/verisign/tat/dnssec/CaptiveValidatorTest.java
new file mode 100644 (file)
index 0000000..4850c44
--- /dev/null
@@ -0,0 +1,82 @@
+package com.verisign.tat.dnssec;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+
+public class CaptiveValidatorTest
+{
+
+    public static class Test_init extends TestCase
+    {
+        protected void setUp() {
+            // Nothing to do yet.
+        }
+
+        public void test_0arg()
+        {
+            CaptiveValidator v = new CaptiveValidator();
+            assertNotNull(v);
+        }
+    }
+
+    public static class Test_Validate extends TestCase
+    {
+        Message baseMessage;
+
+        // Set up a base Response message
+        protected void setUp() {
+            baseMessage = new Message();
+
+            // set up our response header; note that the captive
+            // validator code doesn't actually look at anything in the
+            // header but the RCODE, really, so the flag values
+            // probably don't matter.  But make them realistic anyway.
+            Header hdr = new Header();
+            hdr.setOpcode(DNS.Opcode.QUERY);
+            hdr.setRcode(DNS.Rcode.NOERROR);
+            hdr.setFlag(DNS.Flags.QR);
+            hdr.setFlag(DNS.Flags.AA);
+            hdr.setFlag(DNS.Flags.RD);
+            baseMessage.setHeader(hdr);
+
+
+        }
+
+        public void test_positive()
+        {
+            Message m = new Message();
+        }
+
+        public void test_referral()
+        {
+        }
+
+        public void test_nodata()
+        {
+        }
+
+        public void test_nameerror()
+        {
+        }
+
+        public void test_cname()
+        {
+        }
+
+        public void test_any()
+        {
+        }
+    }
+
+
+    public static Test suite()
+    {
+        TestSuite s = new TestSuite();
+        s.addTestSuite(Test_init.class);
+        s.addTestSuite(Test_Validate.class);
+        return s;
+    }
+
+}
diff --git a/tests/junit-3.8.1.jar b/tests/junit-3.8.1.jar
new file mode 100644 (file)
index 0000000..674d71e
Binary files /dev/null and b/tests/junit-3.8.1.jar differ