summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormenakite <29005531+menakite@users.noreply.github.com>2024-08-11 05:44:21 +0200
committerVladimír Čunát <vladimir.cunat@nic.cz>2024-09-06 12:26:40 +0200
commit48f57424c33496fd354300300af0a183f0f50e87 (patch)
treef747b0a5c292a5828db1e9b84f6d3600fd8c3025
parentlib/utils: generalize kr_strcatdup() for mempools (diff)
downloadknot-resolver-48f57424c33496fd354300300af0a183f0f50e87.tar.xz
knot-resolver-48f57424c33496fd354300300af0a183f0f50e87.zip
resolver,validator: provide more EDE codes.
dnssec: * Provide a way to retrieve whether a DNSKEY has the Zone Key bit set, and add bindings for Lua modules (kr_dnssec_key_zonekey_flag), like kr_dnssec_key_sep_flag. * In kr_ds_algo_support() provide a way to retrieve what is wrong with the keys. * Check if a RRSIG RR has the signature expired already before inception time. validator: * Set EDE "Unsupported NSEC3 Iterations Value" when downgrading. * Set EDE "Signature Expired before Valid" when checking RRSIGs. * Set EDE "No Zone Key Bit Set" when a DNSKEY with the Zone Key Bit set to 0 is discarded. * Instead of the generic "Other Error" with extra text "unsupported digest/key", set appropriate EDEs "Unsupported DNSKEY Algorithm" and "Unsupported DS Digest Type". resolver: * Set EDE "No Reachable Authority" when it is decided that all authoritative servers are unreachable or misbehaving. Some parts adjusted by vcunat, in particular construction of EDE messages.
-rw-r--r--daemon/lua/kres-gen-33.lua1
-rwxr-xr-xdaemon/lua/kres-gen.sh1
-rw-r--r--lib/dnssec.c26
-rw-r--r--lib/dnssec.h32
-rw-r--r--lib/layer/validate.c47
-rw-r--r--lib/resolve-produce.c12
-rw-r--r--lib/resolve.c11
7 files changed, 104 insertions, 26 deletions
diff --git a/daemon/lua/kres-gen-33.lua b/daemon/lua/kres-gen-33.lua
index 1a085741..ecbb6330 100644
--- a/daemon/lua/kres-gen-33.lua
+++ b/daemon/lua/kres-gen-33.lua
@@ -490,6 +490,7 @@ int kr_ta_add(trie_t *, const knot_dname_t *, uint16_t, uint32_t, const uint8_t
int kr_ta_del(trie_t *, const knot_dname_t *);
void kr_ta_clear(trie_t *);
_Bool kr_dnssec_key_sep_flag(const uint8_t *);
+_Bool kr_dnssec_key_zonekey_flag(const uint8_t *);
_Bool kr_dnssec_key_revoked(const uint8_t *);
int kr_dnssec_key_tag(uint16_t, const uint8_t *, size_t);
int kr_dnssec_key_match(const uint8_t *, size_t, const uint8_t *, size_t);
diff --git a/daemon/lua/kres-gen.sh b/daemon/lua/kres-gen.sh
index 8d0b91c0..8fe72035 100755
--- a/daemon/lua/kres-gen.sh
+++ b/daemon/lua/kres-gen.sh
@@ -285,6 +285,7 @@ ${CDEFS} ${LIBKRES} functions <<-EOF
kr_ta_clear
# DNSSEC
kr_dnssec_key_sep_flag
+ kr_dnssec_key_zonekey_flag
kr_dnssec_key_revoked
kr_dnssec_key_tag
kr_dnssec_key_match
diff --git a/lib/dnssec.c b/lib/dnssec.c
index 77cec796..169ce2bf 100644
--- a/lib/dnssec.c
+++ b/lib/dnssec.c
@@ -63,6 +63,10 @@ static int validate_rrsig_rr(int *flags, int cov_labels,
if (kr_fails_assert(flags && rrsigs && vctx && vctx->zone_name)) {
return kr_error(EINVAL);
}
+ if (knot_rrsig_sig_expiration(rrsigs) < knot_rrsig_sig_inception(rrsigs)) {
+ vctx->rrs_counters.expired_before_inception++;
+ return kr_error(EINVAL);
+ }
/* bullet 5 */
if (knot_rrsig_sig_expiration(rrsigs) < vctx->timestamp) {
vctx->rrs_counters.expired++;
@@ -435,26 +439,32 @@ finish:
return vctx->result;
}
-bool kr_ds_algo_support(const knot_rrset_t *ta)
+int kr_ds_algo_support(const knot_rrset_t *ta)
{
if (kr_fails_assert(ta && ta->type == KNOT_RRTYPE_DS && ta->rclass == KNOT_CLASS_IN))
- return false;
+ return kr_error(EINVAL);
/* Check if at least one DS has a usable algorithm pair. */
+ int ret = kr_error(ENOENT);
knot_rdata_t *rdata_i = ta->rrs.rdata;
for (uint16_t i = 0; i < ta->rrs.count;
++i, rdata_i = knot_rdataset_next(rdata_i)) {
- if (dnssec_algorithm_digest_support(knot_ds_digest_type(rdata_i))
- && dnssec_algorithm_key_support(knot_ds_alg(rdata_i))) {
- return true;
- }
+ if (dnssec_algorithm_digest_support(knot_ds_digest_type(rdata_i))) {
+ if (dnssec_algorithm_key_support(knot_ds_alg(rdata_i)))
+ return kr_ok();
+ else
+ ret = DNSSEC_INVALID_KEY_ALGORITHM;
+ } else
+ ret = DNSSEC_INVALID_DIGEST_ALGORITHM;
}
- return false;
+ return ret;
}
-// Now we instantiate these two as non-inline externally linkable code here (for lua).
+// Now we instantiate these three as non-inline externally linkable code here (for lua).
KR_EXPORT extern inline KR_PURE
bool kr_dnssec_key_sep_flag(const uint8_t *dnskey_rdata);
KR_EXPORT extern inline KR_PURE
+bool kr_dnssec_key_zonekey_flag(const uint8_t *dnskey_rdata);
+KR_EXPORT extern inline KR_PURE
bool kr_dnssec_key_revoked(const uint8_t *dnskey_rdata);
int kr_dnskeys_trusted(kr_rrset_validation_ctx_t *vctx, const knot_rdataset_t *sigs,
diff --git a/lib/dnssec.h b/lib/dnssec.h
index 52465042..b9f854d0 100644
--- a/lib/dnssec.h
+++ b/lib/dnssec.h
@@ -56,8 +56,9 @@ struct kr_rrset_validation_ctx {
const struct kr_query *log_qry; /*!< The query; just for logging purposes. */
struct {
unsigned int matching_name_type; /*!< Name + type matches */
- unsigned int expired;
- unsigned int notyet;
+ unsigned int expired; /*!< Number of expired signatures */
+ unsigned int notyet; /*!< Number of signatures not yet valid (inception > now) */
+ unsigned int expired_before_inception; /*!< Number of signatures already expired before inception time */
unsigned int signer_invalid; /*!< Signer is not zone apex */
unsigned int labels_invalid; /*!< Number of labels in RRSIG */
unsigned int key_invalid; /*!< Algorithm/keytag/key owner */
@@ -78,10 +79,17 @@ typedef struct kr_rrset_validation_ctx kr_rrset_validation_ctx_t;
int kr_rrset_validate(kr_rrset_validation_ctx_t *vctx, knot_rrset_t *covered);
/**
- * Return true iff the RRset contains at least one usable DS. See RFC6840 5.2.
+ * Check whether the RRset contains at least one usable DS.
+ *
+ * See RFC6840 5.2.
+ * @param ta Pointer to TA RRSet.
+ * @return kr_ok() if at least one DS is supported
+ * DNSSEC_INVALID_KEY_ALGORITHM if all DSes are not supported, because of their key algorithm
+ * DNSSEC_INVALID_DIGEST_ALGORITHM if all DSes are not supported, because of their digest algorithm
+ * @note Given that entries are iterated until a supported DS is found, the error refers to the last one.
*/
KR_EXPORT KR_PURE
-bool kr_ds_algo_support(const knot_rrset_t *ta);
+int kr_ds_algo_support(const knot_rrset_t *ta);
/**
* Check whether the DNSKEY rrset matches the supplied trust anchor RRSet.
@@ -97,13 +105,20 @@ int kr_dnskeys_trusted(kr_rrset_validation_ctx_t *vctx, const knot_rdataset_t *s
// flags: https://www.iana.org/assignments/dnskey-flags/dnskey-flags.xhtml
// https://datatracker.ietf.org/doc/html/rfc4034#section-2.1
-/** Return true if the DNSKEY has the SEP flag (normally ignored). */
+/** Return true if the DNSKEY has the SEP flag/bit set (normally ignored). */
KR_EXPORT inline KR_PURE
bool kr_dnssec_key_sep_flag(const uint8_t *dnskey_rdata)
{
return dnskey_rdata[1] & 0x01;
}
+/** Return true if the DNSKEY has the Zone Key flag/bit set. */
+KR_EXPORT inline KR_PURE
+bool kr_dnssec_key_zonekey_flag(const uint8_t *dnskey_rdata)
+{
+ return dnskey_rdata[0] & 0x01;
+}
+
/** Return true if the DNSKEY is revoked. */
KR_EXPORT inline KR_PURE
bool kr_dnssec_key_revoked(const uint8_t *dnskey_rdata)
@@ -111,11 +126,14 @@ bool kr_dnssec_key_revoked(const uint8_t *dnskey_rdata)
return dnskey_rdata[1] & 0x80;
}
-/** Return true if the DNSKEY could be used to validate zone records. */
+/**
+ * Return true if the DNSKEY could be used to validate zone records, meaning
+ * it correctly has the Zone Key flag/bit set to 1 and it is not revoked.
+ */
static inline KR_PURE
bool kr_dnssec_key_usable(const uint8_t *dnskey_rdata)
{
- return (dnskey_rdata[0] & 0x01) && !kr_dnssec_key_revoked(dnskey_rdata);
+ return kr_dnssec_key_zonekey_flag(dnskey_rdata) && !kr_dnssec_key_revoked(dnskey_rdata);
}
/** Return DNSKEY tag.
diff --git a/lib/layer/validate.c b/lib/layer/validate.c
index 395640cc..d6840a52 100644
--- a/lib/layer/validate.c
+++ b/lib/layer/validate.c
@@ -137,6 +137,7 @@ do_downgrade: // we do this deep inside calls because of having signer name avai
VERBOSE_MSG(qry,
"<= DNSSEC downgraded due to expensive NSEC3: %d iterations, %d salt length\n",
(int)knot_nsec3_iters(rd), (int)knot_nsec3_salt_len(rd));
+ kr_request_set_extended_error(qry->request, KNOT_EDNS_EDE_NSEC3_ITERS, "AUO2");
qry->flags.DNSSEC_WANT = false;
qry->flags.DNSSEC_INSECURE = true;
rank_records(qry, true, KR_RANK_INSECURE, vctx->zone_name);
@@ -242,7 +243,9 @@ static int validate_section(kr_rrset_validation_ctx_t *vctx, struct kr_query *qr
} else {
kr_rank_set(&entry->rank, KR_RANK_BOGUS);
vctx->err_cnt += 1;
- if (vctx->rrs_counters.expired > 0)
+ if (vctx->rrs_counters.expired_before_inception > 0)
+ kr_request_set_extended_error(req, KNOT_EDNS_EDE_EXPIRED_INV, "XXAP");
+ else if (vctx->rrs_counters.expired > 0)
kr_request_set_extended_error(req, KNOT_EDNS_EDE_SIG_EXPIRED, "YFJ2");
else if (vctx->rrs_counters.notyet > 0)
kr_request_set_extended_error(req, KNOT_EDNS_EDE_SIG_NOTYET, "UBBS");
@@ -368,7 +371,12 @@ static int validate_keyset(struct kr_request *req, knot_pkt_t *answer, bool has_
}
}
if (sig_index < 0) {
- kr_request_set_extended_error(req, KNOT_EDNS_EDE_RRSIG_MISS, "EZDC");
+ if (!kr_dnssec_key_zonekey_flag(qry->zone_cut.key->rrs.rdata->data)) {
+ kr_request_set_extended_error(req, KNOT_EDNS_EDE_DNSKEY_BIT, "YQEH");
+ } else {
+ kr_request_set_extended_error(req, KNOT_EDNS_EDE_RRSIG_MISS,
+ "EZDC: no valid RRSIGs for DNSKEY");
+ }
return kr_error(ENOENT);
}
const knot_rdataset_t *sig_rds = &req->answ_selected.at[sig_index]->rr->rrs;
@@ -395,15 +403,20 @@ static int validate_keyset(struct kr_request *req, knot_pkt_t *answer, bool has_
ret == 0 ? KR_RANK_SECURE : KR_RANK_BOGUS);
if (ret != 0) {
- log_bogus_rrsig(&vctx, qry->zone_cut.key, "bogus key");
- knot_rrset_free(qry->zone_cut.key, qry->zone_cut.pool);
- qry->zone_cut.key = NULL;
- if (vctx.rrs_counters.expired > 0)
+ if (!kr_dnssec_key_zonekey_flag(qry->zone_cut.key->rrs.rdata->data))
+ kr_request_set_extended_error(req, KNOT_EDNS_EDE_DNSKEY_BIT, "CYNG");
+ else if (vctx.rrs_counters.expired_before_inception > 0)
+ kr_request_set_extended_error(req, KNOT_EDNS_EDE_EXPIRED_INV, "4UBF");
+ else if (vctx.rrs_counters.expired > 0)
kr_request_set_extended_error(req, KNOT_EDNS_EDE_SIG_EXPIRED, "6GJV");
else if (vctx.rrs_counters.notyet > 0)
kr_request_set_extended_error(req, KNOT_EDNS_EDE_SIG_NOTYET, "4DJQ");
else
kr_request_set_extended_error(req, KNOT_EDNS_EDE_BOGUS, "EXRU");
+
+ log_bogus_rrsig(&vctx, qry->zone_cut.key, "bogus key");
+ knot_rrset_free(qry->zone_cut.key, qry->zone_cut.pool);
+ qry->zone_cut.key = NULL;
return ret;
}
@@ -1147,10 +1160,19 @@ static int validate(kr_layer_t *ctx, knot_pkt_t *pkt)
if (knot_wire_get_aa(pkt->wire) && qtype == KNOT_RRTYPE_DNSKEY) {
const knot_rrset_t *ds = qry->zone_cut.trust_anchor;
- if (ds && !kr_ds_algo_support(ds)) {
- VERBOSE_MSG(qry, ">< all DS entries use unsupported algorithm pairs, going insecure\n");
- /* ^ the message is a bit imprecise to avoid being too verbose */
- kr_request_set_extended_error(req, KNOT_EDNS_EDE_OTHER, "LSLC: unsupported digest/key");
+ ret = ds ? kr_ds_algo_support(ds) : kr_ok();
+ if (ret != kr_ok()) {
+ char *reason = "???";
+ if (ret == DNSSEC_INVALID_KEY_ALGORITHM) {
+ reason = "key";
+ kr_request_set_extended_error(req, KNOT_EDNS_EDE_DNSKEY_ALG, "PBAO");
+ } else if (ret == DNSSEC_INVALID_DIGEST_ALGORITHM) {
+ reason = "digest";
+ kr_request_set_extended_error(req, KNOT_EDNS_EDE_DS_DIGEST, "DDDV");
+ }
+ VERBOSE_MSG(qry,
+ ">< all DS entries are unsupported (last error: %s algorithm), going insecure\n",
+ reason);
qry->flags.DNSSEC_WANT = false;
qry->flags.DNSSEC_INSECURE = true;
rank_records(qry, true, KR_RANK_INSECURE, qry->zone_cut.name);
@@ -1367,11 +1389,14 @@ static int validate_finalize(kr_layer_t *ctx) {
case KNOT_EDNS_EDE_NSEC_MISS:
case KNOT_EDNS_EDE_RRSIG_MISS:
case KNOT_EDNS_EDE_SIG_EXPIRED:
+ case KNOT_EDNS_EDE_EXPIRED_INV:
case KNOT_EDNS_EDE_SIG_NOTYET:
+ case KNOT_EDNS_EDE_DNSKEY_BIT:
+ case KNOT_EDNS_EDE_DNSKEY_ALG:
+ case KNOT_EDNS_EDE_DS_DIGEST:
kr_request_set_extended_error(ctx->req, KNOT_EDNS_EDE_NONE, NULL);
break;
case KNOT_EDNS_EDE_DNSKEY_MISS:
- case KNOT_EDNS_EDE_DNSKEY_BIT:
kr_assert(false); /* These EDE codes aren't used. */
break;
default: break; /* Remaining codes don't indicate hard DNSSEC failure. */
diff --git a/lib/resolve-produce.c b/lib/resolve-produce.c
index 563a2ca2..a3a2401e 100644
--- a/lib/resolve-produce.c
+++ b/lib/resolve-produce.c
@@ -697,6 +697,18 @@ int kr_resolve_produce(struct kr_request *request, struct kr_transport **transpo
if (qry->flags.NO_NS_FOUND) {
ITERATE_LAYERS(request, qry, reset);
kr_rplan_pop(rplan, qry);
+
+ /* Construct EDE message. We need it on mempool. */
+ char cut_buf[KR_DNAME_STR_MAXLEN];
+ char *msg = knot_dname_to_str(cut_buf, qry->zone_cut.name, sizeof(cut_buf));
+ if (!kr_fails_assert(msg)) {
+ if (*qry->zone_cut.name != '\0') /* Strip trailing dot. */
+ cut_buf[strlen(cut_buf) - 1] = '\0';
+ msg = kr_strcatdup_pool(&request->pool, 2,
+ "P3CD: delegation ", cut_buf);
+ }
+ kr_request_set_extended_error(request, KNOT_EDNS_EDE_NREACH_AUTH, msg);
+
return KR_STATE_FAIL;
} else {
/* FIXME: This is probably quite inefficient:
diff --git a/lib/resolve.c b/lib/resolve.c
index 4b4827f2..bc00471b 100644
--- a/lib/resolve.c
+++ b/lib/resolve.c
@@ -738,6 +738,17 @@ int kr_resolve_consume(struct kr_request *request, struct kr_transport **transpo
qry->flags.NO_NS_FOUND = true;
return KR_STATE_PRODUCE;
}
+
+ /* Construct EDE message. We need it on mempool. */
+ char cut_buf[KR_DNAME_STR_MAXLEN];
+ char *msg = knot_dname_to_str(cut_buf, qry->zone_cut.name, sizeof(cut_buf));
+ if (!kr_fails_assert(msg)) {
+ if (*qry->zone_cut.name != '\0') /* Strip trailing dot. */
+ cut_buf[strlen(cut_buf) - 1] = '\0';
+ msg = kr_strcatdup_pool(&request->pool, 2,
+ "OLX2: delegation ", cut_buf);
+ }
+ kr_request_set_extended_error(request, KNOT_EDNS_EDE_NREACH_AUTH, msg);
return KR_STATE_FAIL;
}
} else {