From ac04adbeb9d0b19e77a715715be24779f7dcf1b2 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Mon, 28 Dec 2015 18:03:34 +0100 Subject: [PATCH 1/8] resolved: dnssec - fix off-by-one in RSA key parsing If the first byte of the key is zero, the key-length is stored in the second and third byte (not first and second). --- src/resolve/resolved-dns-dnssec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index e4b32c7e4b..6a6aabc18f 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -238,8 +238,8 @@ static int dnssec_rsa_verify( exponent = (uint8_t*) dnskey->dnskey.key + 3; exponent_size = - ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) | - ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]); + ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) | + ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]); if (exponent_size < 256) return -EINVAL; From 935a999f7d6881af2e888316be7165801420dc5f Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Mon, 28 Dec 2015 19:05:59 +0100 Subject: [PATCH 2/8] resoled: dnssec - don't refuse to verify answer due to too many unrelated RRs Let VERIFY_RRS_MAX be about the max number of RRs in an RRSet that we actually try to verify, not about the total number of RRs in the RRSet. --- src/resolve/resolved-dns-dnssec.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 6a6aabc18f..552fd48fba 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -525,9 +525,6 @@ int dnssec_verify_rrset( if (md_algorithm < 0) return md_algorithm; - if (a->n_rrs > VERIFY_RRS_MAX) - return -E2BIG; - r = dnssec_rrsig_expired(rrsig, realtime); if (r < 0) return r; @@ -552,6 +549,9 @@ int dnssec_verify_rrset( return r; list[n++] = rr; + + if (n > VERIFY_RRS_MAX) + return -E2BIG; } if (n <= 0) From d1511b3338f431de3c95a50a9c1aca297e0c0734 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Fri, 1 Jan 2016 22:10:55 +0100 Subject: [PATCH 3/8] resolved: dnssec - rename some variables Makes the NSEC3 proof somewhat simpler to follow. --- src/resolve/resolved-dns-dnssec.c | 40 +++++++++++++++++-------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 552fd48fba..564436551c 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -1195,8 +1195,8 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourc static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) { _cleanup_free_ char *next_closer_domain = NULL, *l = NULL; uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; - const char *suffix, *p, *pp = NULL; - DnsResourceRecord *rr, *suffix_rr; + const char *zone, *p, *pp = NULL; + DnsResourceRecord *rr, *enclosure_rr, *suffix_rr; DnsAnswerFlags flags; int hashed_size, r; bool a; @@ -1205,8 +1205,12 @@ static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecR assert(result); assert(authenticated); - /* First step, look for the longest common suffix we find with any NSEC3 RR in the response. */ - suffix = DNS_RESOURCE_KEY_NAME(key); + /* First step, find the zone name and the NSEC3 parameters of the zone. + * it is sufficient to look for the longest common suffix we find with + * any NSEC3 RR in the response. Any NSEC3 record will do as all NSEC3 + * records from a given zone in a response must use the same + * parameters. */ + zone = DNS_RESOURCE_KEY_NAME(key); for (;;) { DNS_ANSWER_FOREACH_FLAGS(suffix_rr, flags, answer) { r = nsec3_is_good(suffix_rr, flags, NULL); @@ -1215,15 +1219,15 @@ static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecR if (r == 0) continue; - r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(suffix_rr->key), 1, suffix); + r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(suffix_rr->key), 1, zone); if (r < 0) return r; if (r > 0) - goto found_suffix; + goto found_zone; } /* Strip one label from the front */ - r = dns_name_parent(&suffix); + r = dns_name_parent(&zone); if (r < 0) return r; if (r == 0) @@ -1233,7 +1237,7 @@ static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecR *result = DNSSEC_NSEC_NO_RR; return 0; -found_suffix: +found_zone: /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */ p = DNS_RESOURCE_KEY_NAME(key); for (;;) { @@ -1251,22 +1255,22 @@ found_suffix: if (!label) return -ENOMEM; - hashed_domain = strjoin(label, ".", suffix, NULL); + hashed_domain = strjoin(label, ".", zone, NULL); if (!hashed_domain) return -ENOMEM; - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) { - r = nsec3_is_good(rr, flags, suffix_rr); + r = nsec3_is_good(enclosure_rr, flags, suffix_rr); if (r < 0) return r; if (r == 0) continue; - if (rr->nsec3.next_hashed_name_size != (size_t) hashed_size) + if (enclosure_rr->nsec3.next_hashed_name_size != (size_t) hashed_size) continue; - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain); + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(enclosure_rr->key), hashed_domain); if (r < 0) return r; if (r > 0) { @@ -1296,25 +1300,25 @@ found_closest_encloser: /* We found a closest encloser in 'p'; next closer is 'pp' */ /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */ - if (bitmap_isset(rr->nsec3.types, DNS_TYPE_DNAME)) + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_DNAME)) return -EBADMSG; /* Ensure that this data is from the delegated domain * (i.e. originates from the "lower" DNS server), and isn't * just glue records (i.e. doesn't originate from the "upper" * DNS server). */ - if (bitmap_isset(rr->nsec3.types, DNS_TYPE_NS) && - !bitmap_isset(rr->nsec3.types, DNS_TYPE_SOA)) + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && + !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) return -EBADMSG; if (!pp) { /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */ - *result = bitmap_isset(rr->nsec3.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA; + *result = bitmap_isset(enclosure_rr->nsec3.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA; *authenticated = a; return 0; } - r = dnssec_nsec3_hash(rr, pp, hashed); + r = dnssec_nsec3_hash(enclosure_rr, pp, hashed); if (r < 0) return r; if (r != hashed_size) From b2c2a1b95dcdd5aaa3e4b7c36c94a8ba8912a983 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Fri, 1 Jan 2016 22:18:24 +0100 Subject: [PATCH 4/8] resolved: dnssec - prepend hashed labels to zone name All hashed names consist of the hashed label prepended to the zone name, not to the closest enclosure. --- src/resolve/resolved-dns-dnssec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 564436551c..deb5e7384f 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -1328,7 +1328,7 @@ found_closest_encloser: if (!l) return -ENOMEM; - next_closer_domain = strjoin(l, ".", p, NULL); + next_closer_domain = strjoin(l, ".", zone, NULL); if (!next_closer_domain) return -ENOMEM; @@ -1345,7 +1345,7 @@ found_closest_encloser: if (!label) return -ENOMEM; - next_hashed_domain = strjoin(label, ".", p, NULL); + next_hashed_domain = strjoin(label, ".", zone, NULL); if (!next_hashed_domain) return -ENOMEM; From 964067666fbf7f4a5edc06d3eb4845c35457c858 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Fri, 1 Jan 2016 16:48:35 +0100 Subject: [PATCH 5/8] resolved: dnssec - add reference to the algorithm we implement --- src/resolve/resolved-dns-dnssec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index deb5e7384f..1ffa98ecb3 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -1192,6 +1192,7 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourc return dns_name_equal(a, b); } +/* See RFC 5155, Section 8 */ static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) { _cleanup_free_ char *next_closer_domain = NULL, *l = NULL; uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; From 146035b3bb2e9a60d82c8816de67c83691d6cbc4 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Fri, 1 Jan 2016 23:07:34 +0100 Subject: [PATCH 6/8] resolved: don't conclude NODATA if CNAME exists Instead introduce the new return-code DNSSEC_NSEC_CNAME to indicate this condition. See RFC 6840, Section 4.3. --- src/resolve/resolved-dns-dnssec.c | 16 ++++++++++++++-- src/resolve/resolved-dns-dnssec.h | 1 + src/resolve/resolved-dns-transaction.c | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 1ffa98ecb3..98d1c6f353 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -1314,8 +1314,15 @@ found_closest_encloser: if (!pp) { /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */ - *result = bitmap_isset(enclosure_rr->nsec3.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA; + if (bitmap_isset(enclosure_rr->nsec3.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + *authenticated = a; + return 0; } @@ -1393,7 +1400,12 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r if (r < 0) return r; if (r > 0) { - *result = bitmap_isset(rr->nsec.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA; + 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; *authenticated = flags & DNS_ANSWER_AUTHENTICATED; return 0; } diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h index d7aecbce13..57858d0c15 100644 --- a/src/resolve/resolved-dns-dnssec.h +++ b/src/resolve/resolved-dns-dnssec.h @@ -91,6 +91,7 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret); typedef enum DnssecNsecResult { DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */ + DNSSEC_NSEC_CNAME, /* Would be NODATA, but for the existence of a CNAME RR */ DNSSEC_NSEC_UNSUPPORTED_ALGORITHM, DNSSEC_NSEC_NXDOMAIN, DNSSEC_NSEC_NODATA, diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index fb95554db3..993db0dc69 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -2300,6 +2300,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { break; case DNSSEC_NSEC_FOUND: + case DNSSEC_NSEC_CNAME: /* NSEC says it needs to be there, but we couldn't find it? Bummer! */ t->answer_dnssec_result = DNSSEC_NSEC_MISMATCH; break; From 6f76ec5a7b174bea43ab16af2dc4f91314940bd5 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Sun, 3 Jan 2016 09:49:58 +0100 Subject: [PATCH 7/8] resolved: dnssec - factor out hashed domain generation --- src/resolve/resolved-dns-dnssec.c | 55 ++++++++++++++++++------------- src/resolve/resolved-dns-dnssec.h | 2 +- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 98d1c6f353..46dcae68ba 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -1071,7 +1071,7 @@ static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) { } } -int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { +int dnssec_nsec3_hash(const DnsResourceRecord *nsec3, const char *name, void *ret) { uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX]; gcry_md_hd_t md = NULL; size_t hash_size; @@ -1192,10 +1192,37 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourc return dns_name_equal(a, b); } +static int nsec3_hashed_domain(const DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) { + _cleanup_free_ char *l = NULL, *hashed_domain = NULL; + uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; + int hashed_size; + + assert(nsec3); + assert(domain); + assert(zone); + assert(ret); + + hashed_size = dnssec_nsec3_hash(nsec3, domain, hashed); + if (hashed_size < 0) + return hashed_size; + + l = base32hexmem(hashed, hashed_size, false); + if (!l) + return -ENOMEM; + + hashed_domain = strjoin(l, ".", zone, NULL); + if (!hashed_domain) + return -ENOMEM; + + *ret = hashed_domain; + hashed_domain = NULL; + + return hashed_size; +} + /* See RFC 5155, Section 8 */ static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) { - _cleanup_free_ char *next_closer_domain = NULL, *l = NULL; - uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; + _cleanup_free_ char *next_closer_domain = NULL; const char *zone, *p, *pp = NULL; DnsResourceRecord *rr, *enclosure_rr, *suffix_rr; DnsAnswerFlags flags; @@ -1242,9 +1269,9 @@ found_zone: /* 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; + _cleanup_free_ char *hashed_domain = NULL; - hashed_size = dnssec_nsec3_hash(suffix_rr, p, hashed); + hashed_size = nsec3_hashed_domain(suffix_rr, p, zone, &hashed_domain); if (hashed_size == -EOPNOTSUPP) { *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM; return 0; @@ -1252,14 +1279,6 @@ found_zone: if (hashed_size < 0) return hashed_size; - label = base32hexmem(hashed, hashed_size, false); - if (!label) - return -ENOMEM; - - hashed_domain = strjoin(label, ".", zone, NULL); - if (!hashed_domain) - return -ENOMEM; - DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) { r = nsec3_is_good(enclosure_rr, flags, suffix_rr); @@ -1326,20 +1345,12 @@ found_closest_encloser: return 0; } - r = dnssec_nsec3_hash(enclosure_rr, pp, hashed); + r = nsec3_hashed_domain(enclosure_rr, pp, zone, &next_closer_domain); if (r < 0) return r; if (r != hashed_size) return -EBADMSG; - l = base32hexmem(hashed, hashed_size, false); - if (!l) - return -ENOMEM; - - next_closer_domain = strjoin(l, ".", zone, NULL); - if (!next_closer_domain) - return -ENOMEM; - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { _cleanup_free_ char *label = NULL, *next_hashed_domain = NULL; diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h index 57858d0c15..f106875027 100644 --- a/src/resolve/resolved-dns-dnssec.h +++ b/src/resolve/resolved-dns-dnssec.h @@ -87,7 +87,7 @@ uint16_t dnssec_keytag(DnsResourceRecord *dnskey); int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max); -int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret); +int dnssec_nsec3_hash(const DnsResourceRecord *nsec3, const char *name, void *ret); typedef enum DnssecNsecResult { DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */ From 35ad41d361a2d9e766f2d7689b92cfbc4304ddbd Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Fri, 1 Jan 2016 23:39:07 +0100 Subject: [PATCH 8/8] resolved: dnssec - properly take wildcards into account in NESC3 proof For NXDOMAIN, it is not sufficient to prove that the next-closest enclosure does not exist, we must also prove that there is no wildcard domain directly below the closest enclosure which would synthesise the name that has been requested. For positive responses, in addition to exact matches, we should accept wildcard ones. In that case we must first prove that there is no precise match (i.e., that the closest encounter is not the record itself) and secondly that the source of synthesis exists. --- src/resolve/resolved-dns-dnssec.c | 107 +++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 10 deletions(-) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 46dcae68ba..2ede907e25 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -1220,14 +1220,23 @@ static int nsec3_hashed_domain(const DnsResourceRecord *nsec3, const char *domai return hashed_size; } -/* See RFC 5155, Section 8 */ +/* See RFC 5155, Section 8 + * First try to find a NSEC3 record that matches our query precisely, if that fails, find the closest + * enclosure. Secondly, find a proof that there is no closer enclosure and either a proof that there + * is no wildcard domain as a direct descendant of the closest enclosure, or find an NSEC3 record that + * matches the wildcard domain. + * + * Based on this we can prove either the existence of the record in @key, or NXDOMAIN or NODATA, or + * that there is no proof either way. The latter is the case if a the proof of non-existence of a given + * name uses an NSEC3 record with the opt-out bit set. Lastly, if we are given insufficient NSEC3 records + * to conclude anything we indicate this by returning NO_RR. */ static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) { - _cleanup_free_ char *next_closer_domain = NULL; + _cleanup_free_ char *next_closer_domain = NULL, *wildcard = NULL, *wildcard_domain = NULL; const char *zone, *p, *pp = NULL; - DnsResourceRecord *rr, *enclosure_rr, *suffix_rr; + DnsResourceRecord *rr, *enclosure_rr, *suffix_rr, *wildcard_rr = NULL; DnsAnswerFlags flags; int hashed_size, r; - bool a; + bool a, no_closer = false, no_wildcard = false, optout = false; assert(key); assert(result); @@ -1345,6 +1354,18 @@ found_closest_encloser: return 0; } + /* Prove that there is no next closer and whether or not there is a wildcard domain. */ + + wildcard = strappend("*.", p); + if (!wildcard) + return -ENOMEM; + + r = nsec3_hashed_domain(enclosure_rr, wildcard, zone, &wildcard_domain); + if (r < 0) + return r; + if (r != hashed_size) + return -EBADMSG; + r = nsec3_hashed_domain(enclosure_rr, pp, zone, &next_closer_domain); if (r < 0) return r; @@ -1373,16 +1394,82 @@ found_closest_encloser: return r; if (r > 0) { if (rr->nsec3.flags & 1) - *result = DNSSEC_NSEC_OPTOUT; - else - *result = DNSSEC_NSEC_NXDOMAIN; + optout = true; - *authenticated = a && (flags & DNS_ANSWER_AUTHENTICATED); - return 1; + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + no_closer = true; + } + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain); + if (r < 0) + return r; + if (r > 0) { + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + wildcard_rr = rr; + } + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain, next_hashed_domain); + if (r < 0) + return r; + if (r > 0) { + if (rr->nsec3.flags & 1) + /* This only makes sense if we have a wildcard delegation, which is + * very unlikely, see RFC 4592, Section 4.2, but we cannot rely on + * this not happening, so hence cannot simply conclude NXDOMAIN as + * we would wish */ + optout = true; + + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + no_wildcard = true; } } - *result = DNSSEC_NSEC_NO_RR; + if (wildcard_rr && no_wildcard) + return -EBADMSG; + + if (!no_closer) { + *result = DNSSEC_NSEC_NO_RR; + + return 0; + } + + if (wildcard_rr) { + /* A wildcard exists that matches our query. */ + if (optout) + /* This is not specified in any RFC to the best of my knowledge, but + * if the next closer enclosure is covered by an opt-out NSEC3 RR + * it means that we cannot prove that the source of synthesis is + * correct, as there may be a closer match. */ + *result = DNSSEC_NSEC_OPTOUT; + else if (bitmap_isset(wildcard_rr->nsec3.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(wildcard_rr->nsec3.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + } else { + if (optout) + /* The RFC only specifies that we have to care for optout for NODATA for + * DS records. However, children of an insecure opt-out delegation should + * also be considered opt-out, rather than verified NXDOMAIN. + * Note that we do not require a proof of wildcard non-existence if the + * next closer domain is covered by an opt-out, as that would not provide + * any additional information. */ + *result = DNSSEC_NSEC_OPTOUT; + else if (no_wildcard) + *result = DNSSEC_NSEC_NXDOMAIN; + else { + *result = DNSSEC_NSEC_NO_RR; + + return 0; + } + } + + *authenticated = a; + return 0; }