Honestly, I find it ugly and probably unintended,
but it's correctly signed and other vendors tend to accept it.
Example:
;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 24204
;; Flags: qr aa rd; QUERY: 1; ANSWER: 0; AUTHORITY: 4; ADDITIONAL: 1
;; EDNS PSEUDOSECTION:
;; Version: 0; flags: do; UDP size: 4096 B; ext-rcode: NOERROR
;; QUESTION SECTION:
;; _domainkey.mail.cez.cz. TXT
;; AUTHORITY SECTION:
cez.cz. 3600 SOA ns10.cez.cz. netmaster.cez.cz.
2025021801 14400 3600 604800 7200
cez.cz. 3600 RRSIG SOA 10 2 3600
20250302073317 20250223063317 45620 cez.cz. JnAonhCOi234lF2A40lYaHcuKtxACKz8X6UFILSgSaK00xyXDk6gWDWo3nmMjXxBwgfP98Gaj8nLMqRZ7ezAEUfWi+5P4YCQzax5Habu3nKB+XKocIPMCHHMhOMf410w4Taz4N2rKgi1p71QkuujISi3JZWzqG4bqzot2cGL12w=
1vk9lupeivbv7dhsb7udm5da1hkd089j.cez.cz. 7200 NSEC3 1 0 1
ACB298B834ADA5FD 1vk9lupeivbv7dhsb7udm5da1hkd089k A NS HINFO MX AAAA SRV RRSIG CAA
1vk9lupeivbv7dhsb7udm5da1hkd089j.cez.cz. 7200 RRSIG NSEC3 10 3 7200
20250303115912 20250224105912 45620 cez.cz. OBW90lof86IoVsiuKkNEf4useG3fikE+npAVkpbiVsgMZWLHRNzAAlIU9wPMH5S4CWpnwoMVTaNtWJxegsG7cvCDZrjVVNOHE9hLOG2eG9f57vx/tVFTe4/DegO9KOyColOOYt4nt/uj7LTJZbzJY3Ev8I9971LEkFf5IxVwwPU=
- manager: allow multiple instances with different rundirs (!1656)
- tests: disable problematic config.http test (#925, !1662)
- /management/unix-socket: allow path relative to rundir (!1657)
+- validator: accept a confusing NODATA proof with insecure delegation (!1659)
Knot Resolver 6.0.10 (2025-01-20)
break;
default:
/* Parent-side delegation record isn't authoritative for non-DS;
- * see RFC6840 4.1. */
+ * see RFC6840 4.1.
+ *
+ * Additionally, we signal if the NODATA would belong
+ * to an *insecure* child zone.
+ */
if (dnssec_nsec_bitmap_contains(bm, bm_size, KNOT_RRTYPE_NS)
&& !dnssec_nsec_bitmap_contains(bm, bm_size, KNOT_RRTYPE_SOA)) {
- return NO_PROOF;
+ return dnssec_nsec_bitmap_contains(bm, bm_size, KNOT_RRTYPE_DS)
+ ? NO_PROOF
+ : KNOT_EDOWNGRADED;
}
/* LATER(opt): perhaps short-circuit test if we repeat it here. */
}
&& kr_rank_test(rrrs->at[i]->rank, KR_RANK_SECURE);
if (!ok) continue;
const int covers = nsec_covers(nsec, sname);
- if (covers == abs(EEXIST)
- && no_data_response_check_rrtype(nsec, stype) == 0) {
- return PKT_NODATA; // proven NODATA by matching NSEC
+ if (covers == abs(EEXIST)) {
+ int ret = no_data_response_check_rrtype(nsec, stype);
+ if (ret == 0)
+ return PKT_NODATA; // proven NODATA by matching NSEC
+ if (ret == KNOT_EDOWNGRADED)
+ return ret;
}
if (covers != 0) continue;
#include "lib/layer/iterate.h"
+#define KNOT_EDOWNGRADED (KNOT_ERROR_MIN - 1)
+
/**
* Check bitmap that child names are contained in the same zone.
* @note see RFC6840 4.1.
* @param owner NSEC record owner.
* @note This includes special checks for zone cuts, e.g. from RFC 6840 sec. 4.
* @return 0, abs(ENOENT) (no proof), kr_error(EINVAL)
+ * KNOT_EDOWNGRADED: special case where the RR would be in an insecure child zone.
*/
int kr_nsec_bitmap_nodata_check(const uint8_t *bm, uint16_t bm_size, uint16_t type, const knot_dname_t *owner);
* @param rrrs list of RRs to search; typically kr_request::auth_selected
* @param qry_uid only consider NSECs from this packet, for better efficiency
* @return negative error code, or PKT_NXDOMAIN | PKT_NODATA (both for NXDOMAIN)
+ * KNOT_EDOWNGRADED: special case where the RR would be in an insecure child zone.
*/
int kr_nsec_negative(const ranked_rr_array_t *rrrs, uint32_t qry_uid,
const knot_dname_t *sname, uint16_t stype);
* @param sname Name to be checked.
* @param stype Type to be checked.
* @return 0 or error code.
+ * KNOT_EDOWNGRADED: special case where the RR would be in an insecure child zone.
* @note This does NOT check the opt-out case if type is DS;
* see RFC 5155 8.6.
*/
const uint8_t *bm = knot_nsec3_bitmap(nsec3->rrs.rdata);
uint16_t bm_size = knot_nsec3_bitmap_len(nsec3->rrs.rdata);
- if (kr_nsec_bitmap_nodata_check(bm, bm_size, type, nsec3->owner) == kr_ok())
- return kr_ok();
+ int ret = kr_nsec_bitmap_nodata_check(bm, bm_size, type, nsec3->owner);
+ if (ret == kr_ok() || ret == KNOT_EDOWNGRADED)
+ return ret;
}
return kr_error(ENOENT);
{
/* DS record may be also matched by an existing NSEC3 RR. */
int ret = nodata_find(pkt, section_id, sname, stype);
- if (ret == 0) {
- /* Satisfies RFC5155 8.5 and 8.6, both first paragraph. */
+ if (ret == 0 || ret == KNOT_EDOWNGRADED) {
+ /* If 0, satisfies RFC5155 8.5 and 8.6, both first paragraph. */
return ret;
}
* @return 0 or error code:
* DNSSEC_NOT_FOUND - neither ds nor nsec records
* were not found.
+ * KNOT_EDOWNGRADED - special case where
+ * the RR would be in an insecure child zone.
* KNOT_ERANGE - denial of existence can't be proven
* due to opt-out, otherwise - bogus.
*/
return true;
}
-#define KNOT_EDOWNGRADED (KNOT_ERROR_MIN - 1)
-
static int validate_section(kr_rrset_validation_ctx_t *vctx, struct kr_query *qry,
knot_mm_t *pool)
{
* we must continue, validate NSEC\NSEC3 and
* call update_parent_keys() to mark
* parent queries as insecure */
+ } else if (ret == KNOT_EDOWNGRADED) { // either NSEC3 or NSEC
+ VERBOSE_MSG(qry, "<= DNSSEC downgraded by a weird proof confusing NODATA with insecure delegation\n");
+ qry->flags.DNSSEC_WANT = false;
+ qry->flags.DNSSEC_INSECURE = true;
+ rank_records(qry, true, KR_RANK_INSECURE, qry->sname);
+ mark_insecure_parents(qry);
} else {
VERBOSE_MSG(qry, "<= bad NODATA proof\n");
kr_request_set_extended_error(req, KNOT_EDNS_EDE_NSEC_MISS, "AHXI");