summaryrefslogtreecommitdiffstats
path: root/lib
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 /lib
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.
Diffstat (limited to 'lib')
-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
5 files changed, 102 insertions, 26 deletions
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 {