diff options
author | menakite <29005531+menakite@users.noreply.github.com> | 2024-08-11 05:44:21 +0200 |
---|---|---|
committer | Vladimír Čunát <vladimir.cunat@nic.cz> | 2024-09-06 12:26:40 +0200 |
commit | 48f57424c33496fd354300300af0a183f0f50e87 (patch) | |
tree | f747b0a5c292a5828db1e9b84f6d3600fd8c3025 | |
parent | lib/utils: generalize kr_strcatdup() for mempools (diff) | |
download | knot-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.lua | 1 | ||||
-rwxr-xr-x | daemon/lua/kres-gen.sh | 1 | ||||
-rw-r--r-- | lib/dnssec.c | 26 | ||||
-rw-r--r-- | lib/dnssec.h | 32 | ||||
-rw-r--r-- | lib/layer/validate.c | 47 | ||||
-rw-r--r-- | lib/resolve-produce.c | 12 | ||||
-rw-r--r-- | lib/resolve.c | 11 |
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 { |