]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
add support for EDE code 1 and 2
authorColin Vidal <colin@isc.org>
Mon, 13 Jan 2025 13:50:01 +0000 (14:50 +0100)
committerColin Vidal <colin@isc.org>
Fri, 24 Jan 2025 12:26:30 +0000 (12:26 +0000)
Add support for EDE codes 1 (Unsupported DNSKEY Algorithm) and 2
(Unsupported DS Digest Type) which might occurs during DNSSEC
validation in case of unsupported DNSKEY algorithm or DS digest type.

Because DNSSEC internally kicks off various fetches, we need to copy
all encountered extended errors from fetch responses to the fetch
context. Upon an event, the errors from the fetch context are copied
to the client response.

lib/dns/include/dns/resolver.h
lib/dns/include/dns/validator.h
lib/dns/resolver.c
lib/dns/validator.c
lib/ns/query.c

index 6a8f715afeafc5c74feb13d901b711b8adecb805..e9d0a537e70199a583e622a978b58ff6b97bc472 100644 (file)
@@ -262,6 +262,8 @@ ISC_REFCOUNT_TRACE_DECL(dns_resolver);
 ISC_REFCOUNT_DECL(dns_resolver);
 #endif
 
+typedef struct fetchctx fetchctx_t;
+
 isc_result_t
 dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
                         dns_rdatatype_t type, const dns_name_t *domain,
@@ -631,4 +633,32 @@ dns_resolver_freefresp(dns_fetchresponse_t **frespp);
  *
  * Requires:
  * \li 'frespp' is valid. No-op if *frespp == NULL
- */
\ No newline at end of file
+ */
+
+void
+dns_resolver_edeappend(fetchctx_t *fctx, uint16_t info_code, const char *what,
+                      const dns_name_t *name, dns_rdatatype_t type);
+/*%<
+ * Helper for EDE message creation in resolver context. Creates message
+ * containing the "what" context message as well as the "name"/"type" being
+ * resolved
+ *
+ * Requires:
+ * \li "fctx" is valid
+ * \li "what" is valid
+ * \li "info_code" is within the range of defined EDE codes
+ * \li "name" is valid
+ */
+
+void
+dns_resolver_copyede(dns_fetch_t *from, fetchctx_t *to);
+/*%<
+ * Copy all EDE messages from the fetchctx_t "from->private" to the fetchctx_t
+ * "to". The fetchctx_t from "from" is not locked. This is reponsability of the
+ * caller to lock it if this function is called in a context needing "from"
+ * synchronization.
+ *
+ * Requires:
+ * \li "from" is valid
+ * \li "to" is valid
+ */
index e097c9861155192f701f913d7cefdc228bba2781..a0c87268ee493e85fe91077dbc80055e510a4aef 100644 (file)
@@ -146,13 +146,20 @@ struct dns_validator {
        isc_stdtime_t start;
 
        bool           digest_sha1;
-       bool           supported_algorithm;
+       uint8_t        unsupported_algorithm;
+       uint8_t        unsupported_digest;
        dns_rdata_t    rdata;
        bool           resume;
        uint32_t      *nvalidations;
        uint32_t      *nfails;
        isc_counter_t *qc;
        isc_counter_t *gqc;
+
+       /*
+        * opaque type here, used to send EDE errors during DNSSEC valiration
+        * to the fetch context.
+        */
+       fetchctx_t *fctx;
 };
 
 /*%
@@ -169,7 +176,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
                     dns_message_t *message, unsigned int options,
                     isc_loop_t *loop, isc_job_cb cb, void *arg,
                     uint32_t *nvalidations, uint32_t *nfails,
-                    isc_counter_t *qc, isc_counter_t *gqc,
+                    isc_counter_t *qc, isc_counter_t *gqc, fetchctx_t *fctx,
                     dns_validator_t **validatorp);
 /*%<
  * Start a DNSSEC validation.
index 80b6cbe388a80b7d244981209d501496f3f85a98..13fbed4215ad29d9963fce068b12aaddf76f55c9 100644 (file)
@@ -977,7 +977,7 @@ valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo,
        result = dns_validator_create(
                fctx->res->view, name, type, rdataset, sigrdataset, message,
                valoptions, fctx->loop, validated, valarg, &fctx->nvalidations,
-               &fctx->nfails, fctx->qc, fctx->gqc, &validator);
+               &fctx->nfails, fctx->qc, fctx->gqc, fctx, &validator);
        RUNTIME_CHECK(result == ISC_R_SUCCESS);
        inc_stats(fctx->res, dns_resstatscounter_val);
        ISC_LIST_APPEND(fctx->validators, validator, link);
@@ -1322,8 +1322,6 @@ fctx_cleanup(fetchctx_t *fctx) {
                ISC_LIST_UNLINK(fctx->altaddrs, addr, publink);
                dns_adb_freeaddrinfo(fctx->adb, &addr);
        }
-
-       dns_ede_unlinkall(fctx->mctx, &fctx->edelist);
 }
 
 static void
@@ -1588,7 +1586,7 @@ fctx_sendevents(fetchctx_t *fctx, isc_result_t result) {
 
                /*
                 * Copy EDE that occured during the resolution to all
-                * clients
+                * clients.
                 */
                for (dns_ede_t *ede = ISC_LIST_HEAD(fctx->edelist); ede != NULL;
                     ede = ISC_LIST_NEXT(ede, link))
@@ -4083,7 +4081,6 @@ fctx_try(fetchctx_t *fctx, bool retrying) {
                                        sizeof(namebuf));
                        dns_rdatatype_format(fctx->qmintype, typebuf,
                                             sizeof(typebuf));
-
                        isc_log_write(DNS_LOGCATEGORY_RESOLVER,
                                      DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR,
                                      "fctx %p(%s): attempting QNAME "
@@ -4200,6 +4197,7 @@ resume_qmin(void *arg) {
        }
        UNLOCK(&fctx->lock);
 
+       dns_resolver_copyede(fctx->qminfetch, fctx);
        dns_resolver_destroyfetch(&fctx->qminfetch);
 
        /*
@@ -4338,7 +4336,6 @@ fctx_destroy(fetchctx_t *fctx) {
        REQUIRE(ISC_LIST_EMPTY(fctx->queries));
        REQUIRE(ISC_LIST_EMPTY(fctx->finds));
        REQUIRE(ISC_LIST_EMPTY(fctx->altfinds));
-       REQUIRE(ISC_LIST_EMPTY(fctx->edelist));
        REQUIRE(atomic_load_acquire(&fctx->pending) == 0);
        REQUIRE(ISC_LIST_EMPTY(fctx->validators));
        REQUIRE(fctx->state != fetchstate_active);
@@ -4374,6 +4371,8 @@ fctx_destroy(fetchctx_t *fctx) {
                isc_mem_put(fctx->mctx, sa, sizeof(*sa));
        }
 
+       dns_ede_unlinkall(fctx->mctx, &fctx->edelist);
+
        isc_counter_detach(&fctx->qc);
        if (fctx->gqc != NULL) {
                isc_counter_detach(&fctx->gqc);
@@ -7201,6 +7200,7 @@ resume_dslookup(void *arg) {
        }
 
 cleanup:
+       dns_resolver_copyede(fetch, fctx);
        dns_resolver_destroyfetch(&fetch);
 
        if (result != ISC_R_SUCCESS) {
@@ -11077,4 +11077,45 @@ dns_resolver_freefresp(dns_fetchresponse_t **frespp) {
        *frespp = NULL;
        dns_ede_unlinkall(fresp->mctx, &fresp->edelist);
        isc_mem_putanddetach(&fresp->mctx, fresp, sizeof(*fresp));
-}
\ No newline at end of file
+}
+
+void
+dns_resolver_edeappend(fetchctx_t *fctx, uint16_t info_code, const char *what,
+                      const dns_name_t *name, dns_rdatatype_t type) {
+       REQUIRE(VALID_FCTX(fctx));
+       REQUIRE(what);
+       REQUIRE(name);
+
+       char extra[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE +
+                  DNS_EDE_EXTRATEXT_LEN];
+       size_t offset = 0;
+
+       /*
+        * -2 to leave room for separator "/" and NULL terminator
+        */
+       snprintf(extra, DNS_EDE_EXTRATEXT_LEN - 2, "%s ", what);
+       offset += strlen(extra);
+       dns_name_format(name, extra + offset, DNS_NAME_FORMATSIZE);
+       offset = strlcat(extra, "/", sizeof(extra));
+       dns_rdatatype_format(type, extra + offset,
+                            DNS_RDATATYPE_FORMATSIZE + 1);
+
+       LOCK(&fctx->lock);
+       dns_ede_append(fctx->mctx, &fctx->edelist, info_code, extra);
+       UNLOCK(&fctx->lock)
+}
+
+void
+dns_resolver_copyede(dns_fetch_t *from, fetchctx_t *to) {
+       REQUIRE(DNS_FETCH_VALID(from));
+       REQUIRE(VALID_FCTX(to));
+
+       LOCK(&to->lock);
+       for (dns_ede_t *ede = ISC_LIST_HEAD(from->private->edelist);
+            ede != NULL; ede = ISC_LIST_NEXT(ede, link))
+       {
+               dns_ede_append(to->mctx, &to->edelist, ede->info_code,
+                              ede->extra_text);
+       }
+       UNLOCK(&to->lock);
+}
index 4dc4edbd7166872a294234e4b3ef7e2ac9fe00b1..9cecd6a781e148f3fa2afe2d2b33305296dc318d 100644 (file)
@@ -174,6 +174,9 @@ expire_rdatasets(dns_validator_t *val) {
        }
 }
 
+static void
+validate_extendederror(dns_validator_t *val);
+
 /*%
  * Ensure the validator's rdatasets are disassociated.
  */
@@ -402,6 +405,7 @@ fetch_callback_dnskey(void *arg) {
        }
 
        validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_dnskey");
+       dns_resolver_copyede(val->fetch, val->fctx);
        dns_resolver_destroyfetch(&val->fetch);
 
        if (CANCELED(val) || CANCELING(val)) {
@@ -476,6 +480,7 @@ fetch_callback_ds(void *arg) {
        }
 
        validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_ds");
+       dns_resolver_copyede(val->fetch, val->fctx);
        dns_resolver_destroyfetch(&val->fetch);
 
        if (CANCELED(val) || CANCELING(val)) {
@@ -965,7 +970,7 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
        result = dns_validator_create(val->view, name, type, rdataset, sig,
                                      NULL, vopts, val->loop, cb, val,
                                      val->nvalidations, val->nfails, val->qc,
-                                     val->gqc, &val->subvalidator);
+                                     val->gqc, val->fctx, &val->subvalidator);
        if (result == ISC_R_SUCCESS) {
                dns_validator_attach(val, &val->subvalidator->parent);
                val->subvalidator->depth = val->depth + 1;
@@ -1525,6 +1530,8 @@ cleanup:
                return;
        }
 
+       val->unsupported_algorithm = 0;
+       val->unsupported_digest = 0;
        result = validate_async_run(val, validate_answer_process);
        INSIST(result == DNS_R_WAIT);
 }
@@ -1645,6 +1652,9 @@ validate_answer_process(void *arg) {
        if (!dns_resolver_algorithm_supported(val->view->resolver, val->name,
                                              val->siginfo->algorithm))
        {
+               if (val->unsupported_algorithm == 0) {
+                       val->unsupported_algorithm = val->siginfo->algorithm;
+               }
                goto next_key;
        }
 
@@ -1768,6 +1778,10 @@ validate_answer_iter_done(dns_validator_t *val, isc_result_t result) {
                return;
        }
 
+       if (result != ISC_R_SUCCESS && result != DNS_R_WAIT) {
+               validate_extendederror(val);
+       }
+
        validator_log(val, ISC_LOG_INFO, "no valid signature found");
        validate_async_done(val, val->result);
 }
@@ -1968,10 +1982,13 @@ validate_dnskey_dsset_done(dns_validator_t *val, isc_result_t result) {
                validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)");
                break;
        case ISC_R_NOMORE:
-               if (!val->supported_algorithm) {
+               if (val->unsupported_algorithm != 0 ||
+                   val->unsupported_digest != 0)
+               {
                        validator_log(val, ISC_LOG_DEBUG(3),
                                      "no supported algorithm/digest (DS)");
                        result = markanswer(val, "validate_dnskey (3)");
+                       validate_extendederror(val);
                        break;
                }
                FALLTHROUGH;
@@ -2008,17 +2025,21 @@ validate_dnskey_dsset(dns_validator_t *val) {
        if (!dns_resolver_ds_digest_supported(val->view->resolver, val->name,
                                              ds.digest_type))
        {
+               if (val->unsupported_digest == 0) {
+                       val->unsupported_digest = ds.digest_type;
+               }
                return DNS_R_BADALG;
        }
 
        if (!dns_resolver_algorithm_supported(val->view->resolver, val->name,
                                              ds.algorithm))
        {
+               if (val->unsupported_algorithm == 0) {
+                       val->unsupported_algorithm = ds.algorithm;
+               }
                return DNS_R_BADALG;
        }
 
-       val->supported_algorithm = true;
-
        /*
         * Find the DNSKEY matching the DS...
         */
@@ -2190,8 +2211,8 @@ validate_dnskey(void *arg) {
         * key set and the matching signature.  For each such key, attempt
         * verification.
         */
-
-       val->supported_algorithm = false;
+       val->unsupported_algorithm = 0;
+       val->unsupported_digest = 0;
 
        /*
         * If DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present we
@@ -2929,6 +2950,13 @@ check_ds_algs(dns_validator_t *val, dns_name_t *name,
                }
                dns_rdata_reset(&dsrdata);
        }
+
+       /*
+        * No unsupported alg/digest EDE error is raised here because the prove
+        * unsecure flow always runs after a validate/validatenx flow. So if an
+        * unsupported alg/digest was found while building the chain of trust,
+        * it would be raised already.
+        */
        return false;
 }
 
@@ -3372,7 +3400,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
                     dns_message_t *message, unsigned int options,
                     isc_loop_t *loop, isc_job_cb cb, void *arg,
                     uint32_t *nvalidations, uint32_t *nfails,
-                    isc_counter_t *qc, isc_counter_t *gqc,
+                    isc_counter_t *qc, isc_counter_t *gqc, fetchctx_t *fctx,
                     dns_validator_t **validatorp) {
        isc_result_t result = ISC_R_FAILURE;
        dns_validator_t *val = NULL;
@@ -3405,6 +3433,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
                .rdata = DNS_RDATA_INIT,
                .nvalidations = nvalidations,
                .nfails = nfails,
+               .fctx = fctx,
        };
 
        isc_refcount_init(&val->references, 1);
@@ -3511,8 +3540,10 @@ destroy_validator(dns_validator_t *val) {
        if (val->gqc != NULL) {
                isc_counter_detach(&val->gqc);
        }
+
        dns_view_detach(&val->view);
        isc_loop_detach(&val->loop);
+
        isc_mem_put(mctx, val, sizeof(*val));
 }
 
@@ -3610,6 +3641,25 @@ validator_logcreate(dns_validator_t *val, dns_name_t *name,
                      caller, operation, namestr, typestr);
 }
 
+static void
+validate_extendederror(dns_validator_t *val) {
+       char txt[32];
+
+       REQUIRE(VALID_VALIDATOR(val));
+
+       if (val->unsupported_algorithm != 0) {
+               dns_secalg_format(val->unsupported_algorithm, txt, sizeof(txt));
+               dns_resolver_edeappend(val->fctx, DNS_EDE_DNSKEYALG, txt,
+                                      val->name, val->type);
+       }
+
+       if (val->unsupported_digest != 0) {
+               dns_dsdigest_format(val->unsupported_digest, txt, sizeof(txt));
+               dns_resolver_edeappend(val->fctx, DNS_EDE_DSDIGESTTYPE, txt,
+                                      val->name, val->type);
+       }
+}
+
 #if DNS_VALIDATOR_TRACE
 ISC_REFCOUNT_TRACE_IMPL(dns_validator, destroy_validator);
 #else
index 4584be6e26732a9792d02ddd974225aab3e470b6..486e667e641906e11d84aad1013c80248fd7a594 100644 (file)
@@ -2492,6 +2492,18 @@ validate(ns_client_t *client, dns_db_t *db, dns_name_t *name,
                if (!dns_resolver_algorithm_supported(client->view->resolver,
                                                      name, rrsig.algorithm))
                {
+                       char txt[DNS_NAME_FORMATSIZE + 32];
+                       isc_buffer_t buffer;
+
+                       isc_buffer_init(&buffer, txt, sizeof(txt));
+                       dns_secalg_totext(rrsig.algorithm, &buffer);
+                       isc_buffer_putstr(&buffer, " ");
+                       dns_name_totext(name, DNS_NAME_OMITFINALDOT, &buffer);
+                       isc_buffer_putstr(&buffer, " (cached)");
+                       isc_buffer_putuint8(&buffer, 0);
+
+                       ns_client_extendederror(client, DNS_EDE_DNSKEYALG,
+                                               isc_buffer_base(&buffer));
                        continue;
                }
                if (!dns_name_issubdomain(name, &rrsig.signer)) {