]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
resolver,validator: provide more EDE codes.
authormenakite <29005531+menakite@users.noreply.github.com>
Sun, 11 Aug 2024 03:44:21 +0000 (05:44 +0200)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Fri, 6 Sep 2024 10:26:40 +0000 (12:26 +0200)
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.

daemon/lua/kres-gen-33.lua
daemon/lua/kres-gen.sh
lib/dnssec.c
lib/dnssec.h
lib/layer/validate.c
lib/resolve-produce.c
lib/resolve.c

index 1a0857418885da16e186ee62038ccbd4ec86e078..ecbb6330fbe97e7d1209e087ba66d6fc84e758ed 100644 (file)
@@ -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);
index 8d0b91c041247c1cca4302e3e05308a78fb22c32..8fe72035b607e59dd626e580218b8cabdcc19765 100755 (executable)
@@ -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
index 77cec796d67b8e6eaf74fc89d999ef62f795586e..169ce2bf7046d8357ccf787506101c7444984572 100644 (file)
@@ -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,
index 52465042bc9858e73a017c63c61e88b9e1a6b61b..b9f854d0619168596ede95b0e49c61f6519735a0 100644 (file)
@@ -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.
index 395640cc3754db11d8c215335b48e700d351c75f..d6840a5252886a84b105e6e75b3bc6dd98ae1ef6 100644 (file)
@@ -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. */
index 563a2ca21774015f4ab0ab1991d5b20a3d964a6d..a3a2401e41dd5487ee6cf79af55734c877dddd59 100644 (file)
@@ -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:
index 4b4827f285d7d6521c2f282684c2107bbc361766..bc00471bc746a2e1167e519b06fb88d2a1f0a88a 100644 (file)
@@ -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 {