mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-01-11 05:17:44 +03:00
resolved: complete NSEC non-existance proofs
This fills in the last few gaps: - When checking if a domain is non-existing, also check that no wildcard for it exists - Ensure we don't base "covering" tests on NSEC RRs from a parent zone - Refuse to accept expanded wildcard NSEC RRs for absence proofs.
This commit is contained in:
parent
d86c982a34
commit
ab481675f9
@ -1617,7 +1617,30 @@ found_closest_encloser:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dnssec_nsec_test_in_path(DnsResourceRecord *rr, const char *name) {
|
||||
static int dnssec_nsec_wildcard_equal(DnsResourceRecord *rr, const char *name) {
|
||||
char label[DNS_LABEL_MAX];
|
||||
const char *n;
|
||||
int r;
|
||||
|
||||
assert(rr);
|
||||
assert(rr->key->type == DNS_TYPE_NSEC);
|
||||
|
||||
/* Checks whether the specified RR has a name beginning in "*.", and if the rest is a suffix of our name */
|
||||
|
||||
if (rr->n_skip_labels_source != 1)
|
||||
return 0;
|
||||
|
||||
n = DNS_RESOURCE_KEY_NAME(rr->key);
|
||||
r = dns_label_unescape(&n, label, sizeof(label));
|
||||
if (r <= 0)
|
||||
return r;
|
||||
if (r != 1 || label[0] != '*')
|
||||
return 0;
|
||||
|
||||
return dns_name_endswith(name, n);
|
||||
}
|
||||
|
||||
static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) {
|
||||
const char *nn, *common_suffix;
|
||||
int r;
|
||||
|
||||
@ -1653,7 +1676,66 @@ static int dnssec_nsec_test_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) {
|
||||
static int dnssec_nsec_from_parent_zone(DnsResourceRecord *rr, const char *name) {
|
||||
int r;
|
||||
|
||||
assert(rr);
|
||||
assert(rr->key->type == DNS_TYPE_NSEC);
|
||||
|
||||
/* Checks whether this NSEC originates to the parent zone or the child zone. */
|
||||
|
||||
r = dns_name_parent(&name);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
r = dns_name_equal(name, DNS_RESOURCE_KEY_NAME(rr->key));
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
/* DNAME, and NS without SOA is an indication for a delegation. */
|
||||
if (bitmap_isset(rr->nsec.types, DNS_TYPE_DNAME))
|
||||
return 1;
|
||||
|
||||
if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dnssec_nsec_covers(DnsResourceRecord *rr, const char *name) {
|
||||
const char *common_suffix, *p;
|
||||
int r;
|
||||
|
||||
assert(rr);
|
||||
assert(rr->key->type == DNS_TYPE_NSEC);
|
||||
|
||||
/* Checks whether the "Next Closer" is witin the space covered by the specified RR. */
|
||||
|
||||
r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (;;) {
|
||||
p = name;
|
||||
r = dns_name_parent(&name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 0;
|
||||
|
||||
r = dns_name_equal(name, common_suffix);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/* p is now the "Next Closer". */
|
||||
|
||||
return dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), p, rr->nsec.next_domain_name);
|
||||
}
|
||||
|
||||
static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name) {
|
||||
const char *common_suffix, *wc;
|
||||
int r;
|
||||
|
||||
@ -1701,85 +1783,96 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
|
||||
if (rr->key->class != key->class)
|
||||
continue;
|
||||
|
||||
switch (rr->key->type) {
|
||||
have_nsec3 = have_nsec3 || (rr->key->type == DNS_TYPE_NSEC3);
|
||||
|
||||
case DNS_TYPE_NSEC:
|
||||
if (rr->key->type != DNS_TYPE_NSEC)
|
||||
continue;
|
||||
|
||||
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), name);
|
||||
/* The following checks only make sense for NSEC RRs that are not expanded from a wildcard */
|
||||
r = dns_resource_record_is_synthetic(rr);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
continue;
|
||||
|
||||
/* Check if this is a direct match. If so, we have encountered a NODATA case */
|
||||
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
/* If it's not a direct match, maybe it's a wild card match? */
|
||||
r = dnssec_nsec_wildcard_equal(rr, name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) {
|
||||
if (key->type == DNS_TYPE_DS) {
|
||||
/* If we look for a DS RR and the server sent us the NSEC RR of the child zone
|
||||
* we have a problem. For DS RRs we want the NSEC RR from the parent */
|
||||
if (bitmap_isset(rr->nsec.types, DNS_TYPE_SOA))
|
||||
continue;
|
||||
} else {
|
||||
/* For all RR types, ensure that if NS is set SOA is set too, so that we know
|
||||
* we got the child's NSEC. */
|
||||
if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) &&
|
||||
!bitmap_isset(rr->nsec.types, DNS_TYPE_SOA))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bitmap_isset(rr->nsec.types, key->type))
|
||||
*result = DNSSEC_NSEC_FOUND;
|
||||
else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME))
|
||||
*result = DNSSEC_NSEC_CNAME;
|
||||
else
|
||||
*result = DNSSEC_NSEC_NODATA;
|
||||
|
||||
if (authenticated)
|
||||
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
|
||||
if (ttl)
|
||||
*ttl = rr->ttl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
if (r > 0) {
|
||||
if (key->type == DNS_TYPE_DS) {
|
||||
/* If we look for a DS RR and the server sent us the NSEC RR of the child zone
|
||||
* we have a problem. For DS RRs we want the NSEC RR from the parent */
|
||||
if (bitmap_isset(rr->nsec.types, DNS_TYPE_SOA))
|
||||
continue;
|
||||
} else {
|
||||
/* For all RR types, ensure that if NS is set SOA is set too, so that we know
|
||||
* we got the child's NSEC. */
|
||||
if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) &&
|
||||
!bitmap_isset(rr->nsec.types, DNS_TYPE_SOA))
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 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_test_in_path(rr, name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) {
|
||||
if (bitmap_isset(rr->nsec.types, key->type))
|
||||
*result = DNSSEC_NSEC_FOUND;
|
||||
else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME))
|
||||
*result = DNSSEC_NSEC_CNAME;
|
||||
else
|
||||
*result = DNSSEC_NSEC_NODATA;
|
||||
|
||||
if (authenticated)
|
||||
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
|
||||
if (ttl)
|
||||
*ttl = rr->ttl;
|
||||
if (authenticated)
|
||||
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
|
||||
if (ttl)
|
||||
*ttl = rr->ttl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
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 && (!covering_rr || !covering_rr_authenticated)) {
|
||||
covering_rr = rr;
|
||||
covering_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED;
|
||||
}
|
||||
/* 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);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) {
|
||||
*result = DNSSEC_NSEC_NODATA;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
if (authenticated)
|
||||
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
|
||||
if (ttl)
|
||||
*ttl = rr->ttl;
|
||||
|
||||
break;
|
||||
return 0;
|
||||
}
|
||||
|
||||
case DNS_TYPE_NSEC3:
|
||||
have_nsec3 = true;
|
||||
break;
|
||||
/* The following two "covering" checks, are not useful if the NSEC is from the parent */
|
||||
r = dnssec_nsec_from_parent_zone(rr, name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
continue;
|
||||
|
||||
/* Check if this NSEC RR proves the absence of an explicit RR under this name */
|
||||
r = dnssec_nsec_covers(rr, name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0 && (!covering_rr || !covering_rr_authenticated)) {
|
||||
covering_rr = rr;
|
||||
covering_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED;
|
||||
}
|
||||
|
||||
/* Check if this NSEC RR proves the absence of a wildcard RR under this name */
|
||||
r = dnssec_nsec_covers_wildcard(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1136,6 +1136,8 @@ int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) {
|
||||
const char *signer;
|
||||
int r;
|
||||
|
||||
assert(rr);
|
||||
|
||||
r = dns_resource_record_signer(rr, &signer);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -1143,6 +1145,29 @@ int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) {
|
||||
return dns_name_equal(zone, signer);
|
||||
}
|
||||
|
||||
int dns_resource_record_is_synthetic(DnsResourceRecord *rr) {
|
||||
int r;
|
||||
|
||||
assert(rr);
|
||||
|
||||
/* Returns > 0 if the RR is generated from a wildcard, and is not the asterisk name itself */
|
||||
|
||||
if (rr->n_skip_labels_source == (unsigned) -1)
|
||||
return -ENODATA;
|
||||
|
||||
if (rr->n_skip_labels_source == 0)
|
||||
return 0;
|
||||
|
||||
if (rr->n_skip_labels_source > 1)
|
||||
return 1;
|
||||
|
||||
r = dns_name_startswith(DNS_RESOURCE_KEY_NAME(rr->key), "*");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return !r;
|
||||
}
|
||||
|
||||
static void dns_resource_record_hash_func(const void *i, struct siphash *state) {
|
||||
const DnsResourceRecord *rr = i;
|
||||
|
||||
|
@ -309,6 +309,7 @@ int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical);
|
||||
int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret);
|
||||
int dns_resource_record_source(DnsResourceRecord *rr, const char **ret);
|
||||
int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone);
|
||||
int dns_resource_record_is_synthetic(DnsResourceRecord *rr);
|
||||
|
||||
DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i);
|
||||
bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b);
|
||||
|
Loading…
Reference in New Issue
Block a user