mirror of
https://github.com/systemd/systemd.git
synced 2025-03-31 14:50:15 +03:00
resolved: tighten search for NSEC3 RRs a bit
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.
This commit is contained in:
parent
13b78323ba
commit
db5b0e92b3
@ -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)
|
||||
r = nsec3_is_good(suffix_rr, flags, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 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;
|
||||
|
||||
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 (;;) {
|
||||
_cleanup_free_ char *hashed_domain = NULL, *label = NULL;
|
||||
|
||||
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;
|
||||
|
||||
label = base32hexmem(hashed, hashed_size, false);
|
||||
if (!label)
|
||||
return -ENOMEM;
|
||||
|
||||
hashed_domain = strjoin(label, ".", suffix, NULL);
|
||||
if (!hashed_domain)
|
||||
return -ENOMEM;
|
||||
|
||||
DNS_ANSWER_FOREACH_FLAGS(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))
|
||||
continue;
|
||||
|
||||
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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user