Start of CNAME support; add unit test framework and initial tests
[captive-validator.git] / src / com / verisign / tat / dnssec / CaptiveValidator.java
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