resolved: tighten search for NSEC3 RRs a bit
authorLennart Poettering <lennart@poettering.net>
Mon, 21 Dec 2015 20:06:29 +0000 (21:06 +0100)
committerLennart Poettering <lennart@poettering.net>
Sat, 26 Dec 2015 18:09:10 +0000 (19:09 +0100)
Be stricter when searching suitable NSEC3 RRs for proof: generalize the
check we use to find suitable NSEC3 RRs, in nsec3_is_good(), and add
additional checks, such as checking whether all NSEC3 RRs use the same
parameters, have the same suffix and so on.

src/resolve/resolved-dns-dnssec.c
src/resolve/resolved-dns-transaction.c
src/shared/dns-domain.c
src/shared/dns-domain.h
src/test/test-dns-domain.c

index dbda71b..edd1efc 100644 (file)
@@ -909,11 +909,63 @@ finish:
         return r;
 }
 
+static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourceRecord *nsec3) {
+        const char *a, *b;
+        int r;
+
+        assert(rr);
+
+        if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
+                return 0;
+
+        if (rr->key->type != DNS_TYPE_NSEC3)
+                return 0;
+
+        /* RFC  5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
+        if (!IN_SET(rr->nsec3.flags, 0, 1))
+                return 0;
+
+        if (!nsec3)
+                return 1;
+
+        /* If a second NSEC3 RR is specified, also check if they are from the same zone. */
+
+        if (nsec3 == rr) /* Shortcut */
+                return 1;
+
+        if (rr->key->class != nsec3->key->class)
+                return 0;
+        if (rr->nsec3.algorithm != nsec3->nsec3.algorithm)
+                return 0;
+        if (rr->nsec3.iterations != nsec3->nsec3.iterations)
+                return 0;
+        if (rr->nsec3.salt_size != nsec3->nsec3.salt_size)
+                return 0;
+        if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0)
+                return 0;
+
+        a = DNS_RESOURCE_KEY_NAME(rr->key);
+        r = dns_name_parent(&a); /* strip off hash */
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 0;
+
+        b = DNS_RESOURCE_KEY_NAME(nsec3->key);
+        r = dns_name_parent(&b); /* strip off hash */
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 0;
+
+        return dns_name_equal(a, b);
+}
+
 static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result) {
         _cleanup_free_ char *next_closer_domain = NULL, *l = NULL;
         uint8_t hashed[DNSSEC_HASH_SIZE_MAX];
         const char *suffix, *p, *pp = NULL;
-        DnsResourceRecord *rr;
+        DnsResourceRecord *rr, *suffix_rr;
         DnsAnswerFlags flags;
         int hashed_size, r;
 
@@ -923,20 +975,16 @@ static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecR
         /* First step, look for the longest common suffix we find with any NSEC3 RR in the response. */
         suffix = DNS_RESOURCE_KEY_NAME(key);
         for (;;) {
-                DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
+                DNS_ANSWER_FOREACH_FLAGS(suffix_rr, flags, answer) {
                         _cleanup_free_ char *hashed_domain = NULL, *label = NULL;
 
-                        if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
-                                continue;
-
-                        if (rr->key->type != DNS_TYPE_NSEC3)
-                                continue;
-
-                        /* RFC  5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
-                        if (!IN_SET(rr->nsec3.flags, 0, 1))
+                        r = nsec3_is_good(suffix_rr, flags, NULL);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
                                 continue;
 
-                        r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), suffix);
+                        r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(suffix_rr->key), 1, suffix);
                         if (r < 0)
                                 return r;
                         if (r > 0)
@@ -958,42 +1006,34 @@ found_suffix:
         /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */
         p = DNS_RESOURCE_KEY_NAME(key);
         for (;;) {
-                DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
-                        _cleanup_free_ char *hashed_domain = NULL, *label = NULL;
+                _cleanup_free_ char *hashed_domain = NULL, *label = NULL;
 
-                        if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
-                                continue;
+                hashed_size = dnssec_nsec3_hash(suffix_rr, p, hashed);
+                if (hashed_size == -EOPNOTSUPP) {
+                        *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
+                        return 0;
+                }
+                if (hashed_size < 0)
+                        return hashed_size;
 
-                        if (rr->key->type != DNS_TYPE_NSEC3)
-                                continue;
+                label = base32hexmem(hashed, hashed_size, false);
+                if (!label)
+                        return -ENOMEM;
 
-                        /* RFC  5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
-                        if (!IN_SET(rr->nsec3.flags, 0, 1))
-                                continue;
+                hashed_domain = strjoin(label, ".", suffix, NULL);
+                if (!hashed_domain)
+                        return -ENOMEM;
+
+                DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
 
-                        r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), suffix);
+                        r = nsec3_is_good(rr, flags, suffix_rr);
                         if (r < 0)
                                 return r;
                         if (r == 0)
                                 continue;
 
-                        hashed_size = dnssec_nsec3_hash(rr, p, hashed);
-                        if (hashed_size == -EOPNOTSUPP) {
-                                *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
-                                return 0;
-                        }
-                        if (hashed_size < 0)
-                                return hashed_size;
                         if (rr->nsec3.next_hashed_name_size != (size_t) hashed_size)
-                                return -EBADMSG;
-
-                        label = base32hexmem(hashed, hashed_size, false);
-                        if (!label)
-                                return -ENOMEM;
-
-                        hashed_domain = strjoin(label, ".", suffix, NULL);
-                        if (!hashed_domain)
-                                return -ENOMEM;
+                                continue;
 
                         r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain);
                         if (r < 0)
@@ -1056,26 +1096,8 @@ found_closest_encloser:
 
         DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
                 _cleanup_free_ char *label = NULL, *next_hashed_domain = NULL;
-                const char *nsec3_parent;
-
-                if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
-                        continue;
-
-                if (rr->key->type != DNS_TYPE_NSEC3)
-                        continue;
-
-                /* RFC  5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
-                if (!IN_SET(rr->nsec3.flags, 0, 1))
-                        continue;
-
-                nsec3_parent = DNS_RESOURCE_KEY_NAME(rr->key);
-                r = dns_name_parent(&nsec3_parent);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        continue;
 
-                r = dns_name_equal(p, nsec3_parent);
+                r = nsec3_is_good(rr, flags, suffix_rr);
                 if (r < 0)
                         return r;
                 if (r == 0)
index d2aec13..1d02d7e 100644 (file)
@@ -1516,7 +1516,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
                         if (!soa)
                                 return -ENOMEM;
 
-                        log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key));
+                        log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dns_resource_record_to_string(rr));
                         r = dns_transaction_request_dnssec_rr(t, soa);
                         if (r < 0)
                                 return r;
index f3dbf60..f44e80c 100644 (file)
@@ -1215,3 +1215,21 @@ int dns_name_count_labels(const char *name) {
 
         return (int) n;
 }
+
+int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) {
+        int r;
+
+        assert(a);
+        assert(b);
+
+        while (n_labels > 0) {
+
+                r = dns_name_parent(&a);
+                if (r <= 0)
+                        return r;
+
+                n_labels --;
+        }
+
+        return dns_name_equal(a, b);
+}
index 7b50972..dd8ae3a 100644 (file)
@@ -102,3 +102,5 @@ int dns_service_split(const char *joined, char **name, char **type, char **domai
 
 int dns_name_suffix(const char *name, unsigned n_labels, const char **ret);
 int dns_name_count_labels(const char *name);
+
+int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b);
index 3a15ea5..78c5631 100644 (file)
@@ -515,6 +515,37 @@ static void test_dns_name_count_labels(void) {
         test_dns_name_count_labels_one("..", -EINVAL);
 }
 
+static void test_dns_name_equal_skip_one(const char *a, unsigned n_labels, const char *b, int ret) {
+        assert_se(dns_name_equal_skip(a, n_labels, b) == ret);
+}
+
+static void test_dns_name_equal_skip(void) {
+        test_dns_name_equal_skip_one("foo", 0, "bar", 0);
+        test_dns_name_equal_skip_one("foo", 0, "foo", 1);
+        test_dns_name_equal_skip_one("foo", 1, "foo", 0);
+        test_dns_name_equal_skip_one("foo", 2, "foo", 0);
+
+        test_dns_name_equal_skip_one("foo.bar", 0, "foo.bar", 1);
+        test_dns_name_equal_skip_one("foo.bar", 1, "foo.bar", 0);
+        test_dns_name_equal_skip_one("foo.bar", 2, "foo.bar", 0);
+        test_dns_name_equal_skip_one("foo.bar", 3, "foo.bar", 0);
+
+        test_dns_name_equal_skip_one("foo.bar", 0, "bar", 0);
+        test_dns_name_equal_skip_one("foo.bar", 1, "bar", 1);
+        test_dns_name_equal_skip_one("foo.bar", 2, "bar", 0);
+        test_dns_name_equal_skip_one("foo.bar", 3, "bar", 0);
+
+        test_dns_name_equal_skip_one("foo.bar", 0, "", 0);
+        test_dns_name_equal_skip_one("foo.bar", 1, "", 0);
+        test_dns_name_equal_skip_one("foo.bar", 2, "", 1);
+        test_dns_name_equal_skip_one("foo.bar", 3, "", 0);
+
+        test_dns_name_equal_skip_one("", 0, "", 1);
+        test_dns_name_equal_skip_one("", 1, "", 0);
+        test_dns_name_equal_skip_one("", 1, "foo", 0);
+        test_dns_name_equal_skip_one("", 2, "foo", 0);
+}
+
 int main(int argc, char *argv[]) {
 
         test_dns_label_unescape();
@@ -537,6 +568,7 @@ int main(int argc, char *argv[]) {
         test_dns_name_change_suffix();
         test_dns_name_suffix();
         test_dns_name_count_labels();
+        test_dns_name_equal_skip();
 
         return 0;
 }