resolved: make sure the NSEC proof-of-non-existance check also looks for wildcard...
authorLennart Poettering <lennart@poettering.net>
Thu, 14 Jan 2016 20:05:57 +0000 (21:05 +0100)
committerLennart Poettering <lennart@poettering.net>
Sun, 17 Jan 2016 19:47:46 +0000 (20:47 +0100)
src/resolve/resolved-dns-dnssec.c

index 69ed7af..ac27484 100644 (file)
@@ -1617,7 +1617,7 @@ found_closest_encloser:
         return 0;
 }
 
-static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) {
+static int dnssec_nsec_test_in_path(DnsResourceRecord *rr, const char *name) {
         const char *nn, *common_suffix;
         int r;
 
@@ -1653,9 +1653,38 @@ static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) {
         return dns_name_endswith(name, common_suffix);
 }
 
+static int dns_dnssec_test_wildcard_at_closest_encloser(DnsResourceRecord *rr, const char *name) {
+        const char *common_suffix, *wc;
+        int r;
+
+        assert(rr);
+        assert(rr->key->type == DNS_TYPE_NSEC);
+
+        /* Checks whether the "Wildcard at the Closest Encloser" is within the space covered by the specified
+         * RR. Specifically, checks whether 'name' has the common suffix of the NSEC RR's owner and next names as
+         * suffix, and whether the NSEC covers the name generated by that suffix prepended with an asterisk label.
+         *
+         *     NSEC             bar →   waldo.foo.bar: indicates that *.bar and *.foo.bar do not exist
+         *     NSEC   waldo.foo.bar → yyy.zzz.xoo.bar: indicates that *.xoo.bar and *.zzz.xoo.bar do not exist (and more ...)
+         *     NSEC yyy.zzz.xoo.bar →             bar: indicates that a number of wildcards don#t exist either...
+         */
+
+        r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix);
+        if (r < 0)
+                return r;
+
+        /* If the common suffix is not shared by the name we are interested in, it has nothing to say for us. */
+        r = dns_name_endswith(name, common_suffix);
+        if (r <= 0)
+                return r;
+
+        wc = strjoina("*.", common_suffix, NULL);
+        return dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), wc, rr->nsec.next_domain_name);
+}
+
 int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
-        bool have_nsec3 = false;
-        DnsResourceRecord *rr;
+        bool have_nsec3 = false, covering_rr_authenticated = false, wildcard_rr_authenticated = false;
+        DnsResourceRecord *rr, *covering_rr = NULL, *wildcard_rr = NULL;
         DnsAnswerFlags flags;
         const char *name;
         int r;
@@ -1708,9 +1737,13 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
                                 return 0;
                         }
 
+                        /* The following three checks only make sense for NSEC RRs that are not expanded from a wildcard */
+                        if (rr->n_skip_labels_source != 0)
+                                continue;
+
                         /* Check if the name we are looking for is an empty non-terminal within the owner or next name
                          * of the NSEC RR. */
-                        r = dnssec_nsec_in_path(rr, name);
+                        r = dnssec_nsec_test_in_path(rr, name);
                         if (r < 0)
                                 return r;
                         if (r > 0) {
@@ -1724,18 +1757,25 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
                                 return 0;
                         }
 
+                        /* Check if this NSEC RR proves the absence of an explicit RR under this name */
                         r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), name, rr->nsec.next_domain_name);
                         if (r < 0)
                                 return r;
-                        if (r > 0)
-                                *result = DNSSEC_NSEC_NXDOMAIN;
+                        if (r > 0 && (!covering_rr || !covering_rr_authenticated)) {
+                                covering_rr = rr;
+                                covering_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED;
+                        }
 
-                        if (authenticated)
-                                *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
-                        if (ttl)
-                                *ttl = rr->ttl;
+                        /* Check if this NSEC RR proves the absence of a wildcard RR under this name */
+                        r = dns_dnssec_test_wildcard_at_closest_encloser(rr, name);
+                        if (r < 0)
+                                return r;
+                        if (r > 0 && (!wildcard_rr || !wildcard_rr_authenticated)) {
+                                wildcard_rr = rr;
+                                wildcard_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED;
+                        }
 
-                        return 0;
+                        break;
 
                 case DNS_TYPE_NSEC3:
                         have_nsec3 = true;
@@ -1743,6 +1783,19 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
                 }
         }
 
+        if (covering_rr && wildcard_rr) {
+                /* If we could prove that neither the name itself, nor the wildcard at the closest encloser exists, we
+                 * proved the NXDOMAIN case. */
+                *result = DNSSEC_NSEC_NXDOMAIN;
+
+                if (authenticated)
+                        *authenticated = covering_rr_authenticated && wildcard_rr_authenticated;
+                if (ttl)
+                        *ttl = MIN(covering_rr->ttl, wildcard_rr->ttl);
+
+                return 0;
+        }
+
         /* OK, this was not sufficient. Let's see if NSEC3 can help. */
         if (have_nsec3)
                 return dnssec_test_nsec3(answer, key, result, authenticated, ttl);