From 6c65d70ce57cb695027f255994d3c5ea80725011 Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Mon, 13 Jan 2025 14:50:01 +0100 Subject: [PATCH 1/3] add support for EDE code 1 and 2 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. (cherry picked from commit 46a58acdf511e0ce8ab3af001f248688afc24998) --- lib/dns/include/dns/resolver.h | 30 ++++++++++++++++ lib/dns/include/dns/validator.h | 11 ++++-- lib/dns/resolver.c | 52 ++++++++++++++++++++++++--- lib/dns/validator.c | 64 +++++++++++++++++++++++++++++---- lib/ns/query.c | 12 +++++++ 5 files changed, 155 insertions(+), 14 deletions(-) diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h index a9aa1ffa97..155e35b8de 100644 --- a/lib/dns/include/dns/resolver.h +++ b/lib/dns/include/dns/resolver.h @@ -265,6 +265,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, @@ -646,4 +648,32 @@ dns_resolver_freefresp(dns_fetchresponse_t **frespp); * \li 'frespp' is valid. No-op if *frespp == NULL */ +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 + */ + ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h index 09dc3a13a3..973adbc510 100644 --- a/lib/dns/include/dns/validator.h +++ b/lib/dns/include/dns/validator.h @@ -148,13 +148,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; }; /*% @@ -173,7 +180,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. diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 75d8e88bac..31d3c4b3e1 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -988,7 +988,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); if ((valoptions & DNS_VALIDATOR_DEFER) == 0) { @@ -1337,8 +1337,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 @@ -1603,7 +1601,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)) @@ -4201,6 +4199,7 @@ resume_qmin(void *arg) { } UNLOCK(&fctx->lock); + dns_resolver_copyede(fctx->qminfetch, fctx); dns_resolver_destroyfetch(&fctx->qminfetch); /* @@ -4339,7 +4338,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); @@ -4375,6 +4373,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); @@ -7208,6 +7208,7 @@ resume_dslookup(void *arg) { } cleanup: + dns_resolver_copyede(fetch, fctx); dns_resolver_destroyfetch(&fetch); if (result != ISC_R_SUCCESS) { @@ -11114,3 +11115,44 @@ dns_resolver_freefresp(dns_fetchresponse_t **frespp) { dns_ede_unlinkall(fresp->mctx, &fresp->edelist); isc_mem_putanddetach(&fresp->mctx, fresp, sizeof(*fresp)); } + +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); +} diff --git a/lib/dns/validator.c b/lib/dns/validator.c index 7a4789229a..30a8b8de51 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -173,6 +173,9 @@ expire_rdatasets(dns_validator_t *val) { } } +static void +validate_extendederror(dns_validator_t *val); + /*% * Ensure the validator's rdatasets are disassociated. */ @@ -410,6 +413,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)) { @@ -484,6 +488,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)) { @@ -976,7 +981,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; @@ -1536,6 +1541,8 @@ cleanup: return; } + val->unsupported_algorithm = 0; + val->unsupported_digest = 0; result = validate_async_run(val, validate_answer_process); INSIST(result == DNS_R_WAIT); } @@ -1656,6 +1663,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; } @@ -1779,6 +1789,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); } @@ -1979,12 +1993,15 @@ 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)", "no supported algorithm/digest (DS)"); + validate_extendederror(val); break; } FALLTHROUGH; @@ -2021,17 +2038,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... */ @@ -2203,8 +2224,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 @@ -2942,6 +2963,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; } @@ -3392,7 +3420,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; @@ -3425,6 +3453,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); @@ -3532,8 +3561,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)); } @@ -3632,6 +3663,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 diff --git a/lib/ns/query.c b/lib/ns/query.c index 714c8d6c93..e7372f87a8 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -2499,6 +2499,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)) { From e1334114516e768e1ac9067cfbb2ba6098e146e4 Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Mon, 13 Jan 2025 14:50:58 +0100 Subject: [PATCH 2/3] tests for support for EDE 1 & 2 (cherry picked from commit 8b50d63fe195ccefa3821b45d1449b9f5019c296) --- bin/tests/system/dnssec/ns2/example.db.in | 3 +++ bin/tests/system/dnssec/ns2/sign.sh | 2 +- .../dnssec/ns3/ds-unsupported.example.db.in | 22 ++++++++++++++++++ bin/tests/system/dnssec/ns3/named.conf.in | 6 +++++ .../system/dnssec/ns3/secure.example.db.in | 1 + bin/tests/system/dnssec/ns3/sign.sh | 18 +++++++++++++++ bin/tests/system/dnssec/ns4/named1.conf.in | 4 +++- bin/tests/system/dnssec/ns4/named2.conf.in | 2 ++ bin/tests/system/dnssec/ns4/named3.conf.in | 2 ++ bin/tests/system/dnssec/ns4/named4.conf.in | 2 ++ bin/tests/system/dnssec/tests.sh | 23 +++++++++++++++++++ bin/tests/system/dnssec/tests_sh_dnssec.py | 1 + 12 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 bin/tests/system/dnssec/ns3/ds-unsupported.example.db.in diff --git a/bin/tests/system/dnssec/ns2/example.db.in b/bin/tests/system/dnssec/ns2/example.db.in index 015be5fd1d..dd0c568bad 100644 --- a/bin/tests/system/dnssec/ns2/example.db.in +++ b/bin/tests/system/dnssec/ns2/example.db.in @@ -106,6 +106,9 @@ ns.dnskey-unknown A 10.53.0.3 dnskey-unsupported NS ns.dnskey-unsupported ns.dnskey-unsupported A 10.53.0.3 +ds-unsupported NS ns.ds-unsupported +ns.ds-unsupported A 10.53.0.3 + dnskey-nsec3-unknown NS ns.dnskey-nsec3-unknown ns.dnskey-nsec3-unknown A 10.53.0.3 diff --git a/bin/tests/system/dnssec/ns2/sign.sh b/bin/tests/system/dnssec/ns2/sign.sh index 680d296ac5..5d326c23c5 100644 --- a/bin/tests/system/dnssec/ns2/sign.sh +++ b/bin/tests/system/dnssec/ns2/sign.sh @@ -56,7 +56,7 @@ infile=example.db.in zonefile=example.db # Get the DS records for the "example." zone. -for subdomain in secure badds bogus dynamic keyless nsec3 optout \ +for subdomain in ds-unsupported secure badds bogus dynamic keyless nsec3 optout \ nsec3-unknown optout-unknown multiple rsasha256 rsasha512 \ kskonly update-nsec3 auto-nsec auto-nsec3 secure.below-cname \ ttlpatch split-dnssec split-smart expired expiring upper lower \ diff --git a/bin/tests/system/dnssec/ns3/ds-unsupported.example.db.in b/bin/tests/system/dnssec/ns3/ds-unsupported.example.db.in new file mode 100644 index 0000000000..94fa465980 --- /dev/null +++ b/bin/tests/system/dnssec/ns3/ds-unsupported.example.db.in @@ -0,0 +1,22 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns +ns A 10.53.0.3 +a A 10.0.0.1 diff --git a/bin/tests/system/dnssec/ns3/named.conf.in b/bin/tests/system/dnssec/ns3/named.conf.in index d8f45a825a..1d7157b93b 100644 --- a/bin/tests/system/dnssec/ns3/named.conf.in +++ b/bin/tests/system/dnssec/ns3/named.conf.in @@ -195,6 +195,12 @@ zone "dnskey-unknown.example" { file "dnskey-unknown.example.db.signed"; }; +zone "ds-unsupported.example" { + type primary; + file "ds-unsupported.example.db.signed"; + allow-update { any; }; +}; + zone "dnskey-unsupported.example" { type primary; file "dnskey-unsupported.example.db.signed"; diff --git a/bin/tests/system/dnssec/ns3/secure.example.db.in b/bin/tests/system/dnssec/ns3/secure.example.db.in index b82b280219..9aebd98007 100644 --- a/bin/tests/system/dnssec/ns3/secure.example.db.in +++ b/bin/tests/system/dnssec/ns3/secure.example.db.in @@ -30,6 +30,7 @@ g A 10.0.0.7 z A 10.0.0.26 a.a.a.a.a.a.a.a.a.a.e A 10.0.0.27 x CNAME a +badalg A 10.53.0.4 private NS ns.private ns.private A 10.53.0.2 diff --git a/bin/tests/system/dnssec/ns3/sign.sh b/bin/tests/system/dnssec/ns3/sign.sh index 958c7e64c6..cdf06f45d4 100644 --- a/bin/tests/system/dnssec/ns3/sign.sh +++ b/bin/tests/system/dnssec/ns3/sign.sh @@ -298,6 +298,24 @@ awk '$4 == "DNSKEY" { $7 = 255 } $4 == "RRSIG" { $6 = 255 } { print }' ${zonefil DSFILE="dsset-${zone}." $DSFROMKEY -A -f ${zonefile}.signed "$zone" >"$DSFILE" +# +# A zone which is fime by itself (supported alg and digest) but that is used +# to mimic unsupported DS digest (see ns8). +# +zone=ds-unsupported.example. +infile=ds-unsupported.example.db.in +zonefile=ds-unsupported.example.db + +cnameandkey=$("$KEYGEN" -T KEY -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n host "cnameandkey.$zone") +dnameandkey=$("$KEYGEN" -T KEY -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n host "dnameandkey.$zone") +keyname=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone "$zone") + +cat "$infile" "$cnameandkey.key" "$dnameandkey.key" "$keyname.key" >"$zonefile" + +"$SIGNER" -z -D -o "$zone" "$zonefile" >/dev/null +cat "$zonefile" "$zonefile".signed >"$zonefile".tmp +mv "$zonefile".tmp "$zonefile".signed + # # A zone with a published unsupported DNSKEY algorithm (Reserved). # Different from above because this key is not intended for signing. diff --git a/bin/tests/system/dnssec/ns4/named1.conf.in b/bin/tests/system/dnssec/ns4/named1.conf.in index 113e3bbe1d..2cb4b4ffd9 100644 --- a/bin/tests/system/dnssec/ns4/named1.conf.in +++ b/bin/tests/system/dnssec/ns4/named1.conf.in @@ -28,9 +28,11 @@ options { nta-lifetime 12s; nta-recheck 9s; - validate-except { corp; }; + disable-ds-digests "ds-unsupported.example." {"SHA1"; "SHA-1"; "SHA256"; "SHA-256"; "SHA384"; "SHA-384"; }; + disable-algorithms "badalg.secure.example." { ECDSAP256SHA256; }; + # Note: We only reference the bind.keys file here to confirm that it # is *not* being used. It contains the real root key, and we're # using a local toy root zone for the tests, so it wouldn't work. diff --git a/bin/tests/system/dnssec/ns4/named2.conf.in b/bin/tests/system/dnssec/ns4/named2.conf.in index 56bdfe4235..82c8ecfffc 100644 --- a/bin/tests/system/dnssec/ns4/named2.conf.in +++ b/bin/tests/system/dnssec/ns4/named2.conf.in @@ -25,6 +25,8 @@ options { dnssec-validation auto; bindkeys-file "managed.conf"; minimal-responses no; + disable-ds-digests "ds-unsupported.example." {"SHA1"; "SHA-1"; "SHA256"; "SHA-256"; "SHA384"; "SHA-384"; }; + disable-algorithms "badalg.secure.example." { ECDSAP256SHA256; }; }; key rndc_key { diff --git a/bin/tests/system/dnssec/ns4/named3.conf.in b/bin/tests/system/dnssec/ns4/named3.conf.in index e160d37433..4b9e93cc5a 100644 --- a/bin/tests/system/dnssec/ns4/named3.conf.in +++ b/bin/tests/system/dnssec/ns4/named3.conf.in @@ -26,6 +26,8 @@ options { bindkeys-file "managed.conf"; dnssec-accept-expired yes; minimal-responses no; + disable-ds-digests "ds-unsupported.example." {"SHA1"; "SHA-1"; "SHA256"; "SHA-256"; "SHA384"; "SHA-384"; }; + disable-algorithms "badalg.secure.example." { ECDSAP256SHA256; }; }; key rndc_key { diff --git a/bin/tests/system/dnssec/ns4/named4.conf.in b/bin/tests/system/dnssec/ns4/named4.conf.in index 941211993d..9cda7eb456 100644 --- a/bin/tests/system/dnssec/ns4/named4.conf.in +++ b/bin/tests/system/dnssec/ns4/named4.conf.in @@ -21,6 +21,8 @@ options { pid-file "named.pid"; listen-on { 10.53.0.4; }; listen-on-v6 { none; }; + disable-ds-digests "ds-unsupported.example." {"SHA1"; "SHA-1"; "SHA256"; "SHA-256"; "SHA384"; "SHA-384"; }; + disable-algorithms "badalg.secure.example." { ECDSAP256SHA256; }; }; key rndc_key { diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index 37fc64de29..9d6440a763 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -3677,6 +3677,25 @@ dig_with_opts +noauth +noadd +nodnssec +adflag @10.53.0.3 dnskey-unsupported.exa dig_with_opts +noauth +noadd +nodnssec +adflag @10.53.0.4 dnskey-unsupported.example A >dig.out.ns4.test$n grep "status: NOERROR," dig.out.ns3.test$n >/dev/null || ret=1 grep "status: NOERROR," dig.out.ns4.test$n >/dev/null || ret=1 +grep "; EDE: 1 (Unsupported DNSKEY Algorithm): (255 dnskey-unsupported.example/SOA)" dig.out.ns4.test$n >/dev/null || ret=1 +grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null && ret=1 +n=$((n + 1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status + ret)) + +echo_i "checking EDE code 2 for unsupported DS digest ($n)" +ret=0 +dig_with_opts @10.53.0.4 a.ds-unsupported.example >dig.out.ns4.test$n || ret=1 +grep "; EDE: 2 (Unsupported DS Digest Type): (SHA-256 ds-unsupported.example/DNSKEY)" dig.out.ns4.test$n >/dev/null || ret=1 +grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null && ret=1 +n=$((n + 1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status + ret)) + +echo_i "checking EDE code 1 for bad alg mnemonic ($n)" +ret=0 +dig_with_opts @10.53.0.4 badalg.secure.example >dig.out.ns4.test$n || ret=1 +grep "; EDE: 1 (Unsupported DNSKEY Algorithm): (ECDSAP256SHA256 badalg.secure.example/A)" dig.out.ns4.test$n >/dev/null || ret=1 grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null && ret=1 n=$((n + 1)) test "$ret" -eq 0 || echo_i "failed" @@ -3974,6 +3993,7 @@ dig_with_opts @10.53.0.8 a.secure.trusted A >dig.out.ns8.test$n grep "status: NOERROR," dig.out.ns3.test$n >/dev/null || ret=1 grep "status: NOERROR," dig.out.ns8.test$n >/dev/null || ret=1 grep "flags:.*ad.*QUERY" dig.out.ns8.test$n >/dev/null || ret=1 +grep "; EDE: " dig.out.ns8.test$n >/dev/null && ret=1 n=$((n + 1)) test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) @@ -3985,6 +4005,7 @@ dig_with_opts @10.53.0.8 a.secure.managed A >dig.out.ns8.test$n grep "status: NOERROR," dig.out.ns3.test$n >/dev/null || ret=1 grep "status: NOERROR," dig.out.ns8.test$n >/dev/null || ret=1 grep "flags:.*ad.*QUERY" dig.out.ns8.test$n >/dev/null || ret=1 +grep "; EDE: " dig.out.ns8.test$n >/dev/null && ret=1 n=$((n + 1)) test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) @@ -3999,6 +4020,7 @@ dig_with_opts @10.53.0.3 a.unsupported.trusted A >dig.out.ns3.test$n dig_with_opts @10.53.0.8 a.unsupported.trusted A >dig.out.ns8.test$n grep "status: NOERROR," dig.out.ns3.test$n >/dev/null || ret=1 grep "status: NOERROR," dig.out.ns8.test$n >/dev/null || ret=1 +grep "; EDE: 1 (Unsupported DNSKEY Algorithm): (255 ns3.unsupported.trusted (cached))" dig.out.ns8.test$n >/dev/null || ret=1 grep "flags:.*ad.*QUERY" dig.out.ns8.test$n >/dev/null && ret=1 n=$((n + 1)) test "$ret" -eq 0 || echo_i "failed" @@ -4010,6 +4032,7 @@ dig_with_opts @10.53.0.3 a.unsupported.managed A >dig.out.ns3.test$n dig_with_opts @10.53.0.8 a.unsupported.managed A >dig.out.ns8.test$n grep "status: NOERROR," dig.out.ns3.test$n >/dev/null || ret=1 grep "status: NOERROR," dig.out.ns8.test$n >/dev/null || ret=1 +grep "; EDE: 1 (Unsupported DNSKEY Algorithm): (255 ns3.unsupported.managed (cached))" dig.out.ns8.test$n >/dev/null || ret=1 grep "flags:.*ad.*QUERY" dig.out.ns8.test$n >/dev/null && ret=1 n=$((n + 1)) test "$ret" -eq 0 || echo_i "failed" diff --git a/bin/tests/system/dnssec/tests_sh_dnssec.py b/bin/tests/system/dnssec/tests_sh_dnssec.py index 91817bd0b7..bd7cd111c2 100644 --- a/bin/tests/system/dnssec/tests_sh_dnssec.py +++ b/bin/tests/system/dnssec/tests_sh_dnssec.py @@ -131,6 +131,7 @@ pytestmark = pytest.mark.extra_artifacts( "ns3/update-nsec3.example.db.signed", "ns3/upper.example.db", "ns3/upper.example.db.lower", + "ns3/ds-unsupported.example.db", "ns4/managed.conf", "ns4/managed-keys.bind", "ns4/named.secroots", From b61e1a5bcf6cc49abf3b5edd461849daf56c70a8 Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Mon, 20 Jan 2025 20:59:23 +0100 Subject: [PATCH 3/3] add DNSSEC EDE test for unsupported digest and alg A DNSSEC validation can fail in the case where multiple DNSKEY are available for a zone and none of them are supported, but for different reasons: one has a DS record in the parent zone using an unsupported digest while the other one uses an unsupported encryption algorithm. Add a specific test case covering this flow and making sure that two extended DNS error are provided: code 1 and 2, each of them highlighting unsupported algorithm and digest. (cherry picked from commit 244923b9dc841bbfb8d9e0758733d991df4658b1) --- bin/tests/system/dnssec/ns2/example.db.in | 3 +++ bin/tests/system/dnssec/ns2/sign.sh | 3 ++- .../ns3/digest-alg-unsupported.example.db.in | 22 ++++++++++++++++ bin/tests/system/dnssec/ns3/named.conf.in | 6 +++++ bin/tests/system/dnssec/ns3/sign.sh | 26 ++++++++++++++++++- bin/tests/system/dnssec/ns4/named1.conf.in | 2 ++ bin/tests/system/dnssec/ns4/named2.conf.in | 2 ++ bin/tests/system/dnssec/ns4/named3.conf.in | 2 ++ bin/tests/system/dnssec/ns4/named4.conf.in | 2 ++ bin/tests/system/dnssec/tests.sh | 10 +++++++ bin/tests/system/dnssec/tests_sh_dnssec.py | 1 + 11 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 bin/tests/system/dnssec/ns3/digest-alg-unsupported.example.db.in diff --git a/bin/tests/system/dnssec/ns2/example.db.in b/bin/tests/system/dnssec/ns2/example.db.in index dd0c568bad..72e3c1fffa 100644 --- a/bin/tests/system/dnssec/ns2/example.db.in +++ b/bin/tests/system/dnssec/ns2/example.db.in @@ -109,6 +109,9 @@ ns.dnskey-unsupported A 10.53.0.3 ds-unsupported NS ns.ds-unsupported ns.ds-unsupported A 10.53.0.3 +digest-alg-unsupported NS ns.digest-alg-unsupported +ns.digest-alg-unsupported A 10.53.0.3 + dnskey-nsec3-unknown NS ns.dnskey-nsec3-unknown ns.dnskey-nsec3-unknown A 10.53.0.3 diff --git a/bin/tests/system/dnssec/ns2/sign.sh b/bin/tests/system/dnssec/ns2/sign.sh index 5d326c23c5..9ac57f776c 100644 --- a/bin/tests/system/dnssec/ns2/sign.sh +++ b/bin/tests/system/dnssec/ns2/sign.sh @@ -56,7 +56,8 @@ infile=example.db.in zonefile=example.db # Get the DS records for the "example." zone. -for subdomain in ds-unsupported secure badds bogus dynamic keyless nsec3 optout \ +for subdomain in digest-alg-unsupported ds-unsupported secure badds \ + bogus dynamic keyless nsec3 optout \ nsec3-unknown optout-unknown multiple rsasha256 rsasha512 \ kskonly update-nsec3 auto-nsec auto-nsec3 secure.below-cname \ ttlpatch split-dnssec split-smart expired expiring upper lower \ diff --git a/bin/tests/system/dnssec/ns3/digest-alg-unsupported.example.db.in b/bin/tests/system/dnssec/ns3/digest-alg-unsupported.example.db.in new file mode 100644 index 0000000000..94fa465980 --- /dev/null +++ b/bin/tests/system/dnssec/ns3/digest-alg-unsupported.example.db.in @@ -0,0 +1,22 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns +ns A 10.53.0.3 +a A 10.0.0.1 diff --git a/bin/tests/system/dnssec/ns3/named.conf.in b/bin/tests/system/dnssec/ns3/named.conf.in index 1d7157b93b..293ff2dda8 100644 --- a/bin/tests/system/dnssec/ns3/named.conf.in +++ b/bin/tests/system/dnssec/ns3/named.conf.in @@ -195,6 +195,12 @@ zone "dnskey-unknown.example" { file "dnskey-unknown.example.db.signed"; }; +zone "digest-alg-unsupported.example" { + type primary; + file "digest-alg-unsupported.example.db.signed"; + allow-update { any; }; +}; + zone "ds-unsupported.example" { type primary; file "ds-unsupported.example.db.signed"; diff --git a/bin/tests/system/dnssec/ns3/sign.sh b/bin/tests/system/dnssec/ns3/sign.sh index cdf06f45d4..5689979cf1 100644 --- a/bin/tests/system/dnssec/ns3/sign.sh +++ b/bin/tests/system/dnssec/ns3/sign.sh @@ -299,7 +299,31 @@ DSFILE="dsset-${zone}." $DSFROMKEY -A -f ${zonefile}.signed "$zone" >"$DSFILE" # -# A zone which is fime by itself (supported alg and digest) but that is used +# A zone which uses an unsupported algorithm for a DNSKEY and an unsupported +# digest for another DNSKEY +# +zone=digest-alg-unsupported.example. +infile=digest-alg-unsupported.example.db.in +zonefile=digest-alg-unsupported.example.db + +cnameandkey=$("$KEYGEN" -T KEY -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n host "cnameandkey.$zone") +dnameandkey=$("$KEYGEN" -T KEY -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n host "dnameandkey.$zone") +keyname=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone "$zone") +keyname2=$("$KEYGEN" -q -a ED448 -b "$DEFAULT_BITS" -n zone "$zone") + +cat "$infile" "$cnameandkey.key" "$dnameandkey.key" "$keyname.key" "$keyname2.key" >"$zonefile" + +"$SIGNER" -z -D -o "$zone" "$zonefile" >/dev/null +cat "$zonefile" "$zonefile".signed >"$zonefile".tmp +mv "$zonefile".tmp "$zonefile".signed + +# override generated DS record file so we can set different digest to each keys +DSFILE="dsset-${zone}" +$DSFROMKEY -1 -A -f ${zonefile}.signed "$zone" | head -n 1 >"$DSFILE" +$DSFROMKEY -2 -A -f ${zonefile}.signed "$zone" | tail -1 >>"$DSFILE" + +# +# A zone which is fine by itself (supported algorithm) but that is used # to mimic unsupported DS digest (see ns8). # zone=ds-unsupported.example. diff --git a/bin/tests/system/dnssec/ns4/named1.conf.in b/bin/tests/system/dnssec/ns4/named1.conf.in index 2cb4b4ffd9..be904a53a2 100644 --- a/bin/tests/system/dnssec/ns4/named1.conf.in +++ b/bin/tests/system/dnssec/ns4/named1.conf.in @@ -30,6 +30,8 @@ options { nta-recheck 9s; validate-except { corp; }; + disable-algorithms "digest-alg-unsupported.example." { ED448; }; + disable-ds-digests "digest-alg-unsupported.example." { "SHA1"; "SHA-1"; }; disable-ds-digests "ds-unsupported.example." {"SHA1"; "SHA-1"; "SHA256"; "SHA-256"; "SHA384"; "SHA-384"; }; disable-algorithms "badalg.secure.example." { ECDSAP256SHA256; }; diff --git a/bin/tests/system/dnssec/ns4/named2.conf.in b/bin/tests/system/dnssec/ns4/named2.conf.in index 82c8ecfffc..7f1188830b 100644 --- a/bin/tests/system/dnssec/ns4/named2.conf.in +++ b/bin/tests/system/dnssec/ns4/named2.conf.in @@ -25,6 +25,8 @@ options { dnssec-validation auto; bindkeys-file "managed.conf"; minimal-responses no; + disable-algorithms "digest-alg-unsupported.example." { ED448; }; + disable-ds-digests "digest-alg-unsupported.example." { "SHA1"; "SHA-1"; }; disable-ds-digests "ds-unsupported.example." {"SHA1"; "SHA-1"; "SHA256"; "SHA-256"; "SHA384"; "SHA-384"; }; disable-algorithms "badalg.secure.example." { ECDSAP256SHA256; }; }; diff --git a/bin/tests/system/dnssec/ns4/named3.conf.in b/bin/tests/system/dnssec/ns4/named3.conf.in index 4b9e93cc5a..d90ffb0531 100644 --- a/bin/tests/system/dnssec/ns4/named3.conf.in +++ b/bin/tests/system/dnssec/ns4/named3.conf.in @@ -26,6 +26,8 @@ options { bindkeys-file "managed.conf"; dnssec-accept-expired yes; minimal-responses no; + disable-algorithms "digest-alg-unsupported.example." { ED448; }; + disable-ds-digests "digest-alg-unsupported.example." { "SHA1"; "SHA-1"; }; disable-ds-digests "ds-unsupported.example." {"SHA1"; "SHA-1"; "SHA256"; "SHA-256"; "SHA384"; "SHA-384"; }; disable-algorithms "badalg.secure.example." { ECDSAP256SHA256; }; }; diff --git a/bin/tests/system/dnssec/ns4/named4.conf.in b/bin/tests/system/dnssec/ns4/named4.conf.in index 9cda7eb456..1a8d917ca8 100644 --- a/bin/tests/system/dnssec/ns4/named4.conf.in +++ b/bin/tests/system/dnssec/ns4/named4.conf.in @@ -21,6 +21,8 @@ options { pid-file "named.pid"; listen-on { 10.53.0.4; }; listen-on-v6 { none; }; + disable-algorithms "digest-alg-unsupported.example." { ED448; }; + disable-ds-digests "digest-alg-unsupported.example." { "SHA1"; "SHA-1"; }; disable-ds-digests "ds-unsupported.example." {"SHA1"; "SHA-1"; "SHA256"; "SHA-256"; "SHA384"; "SHA-384"; }; disable-algorithms "badalg.secure.example." { ECDSAP256SHA256; }; }; diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index 9d6440a763..ea2eafcb56 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -3701,6 +3701,16 @@ n=$((n + 1)) test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) +echo_i "checking both EDE code 1 and 2 for unsupported digest on one DNSKEY and alg on the other ($n)" +ret=0 +dig_with_opts @10.53.0.4 a.digest-alg-unsupported.example >dig.out.ns4.test$n || ret=1 +grep "; EDE: 1 (Unsupported DNSKEY Algorithm): (ED448 digest-alg-unsupported.example/DNSKEY)" dig.out.ns4.test$n >/dev/null || ret=1 +grep "; EDE: 2 (Unsupported DS Digest Type): (SHA-1 digest-alg-unsupported.example/DNSKEY)" dig.out.ns4.test$n >/dev/null || ret=1 +grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null && ret=1 +n=$((n + 1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status + ret)) + echo_i "checking that unsupported DNSKEY algorithm is in DNSKEY RRset ($n)" ret=0 dig_with_opts +noauth +noadd +nodnssec +adflag @10.53.0.3 dnskey-unsupported-2.example DNSKEY >dig.out.test$n diff --git a/bin/tests/system/dnssec/tests_sh_dnssec.py b/bin/tests/system/dnssec/tests_sh_dnssec.py index bd7cd111c2..c7e4d9df7e 100644 --- a/bin/tests/system/dnssec/tests_sh_dnssec.py +++ b/bin/tests/system/dnssec/tests_sh_dnssec.py @@ -83,6 +83,7 @@ pytestmark = pytest.mark.extra_artifacts( "ns3/dnskey-unsupported.example.db", "ns3/dnskey-unsupported.example.db.tmp", "ns3/dynamic.example.db", + "ns3/digest-alg-unsupported.example.db", "ns3/enabled.managed.db", "ns3/enabled.trusted.db", "ns3/example.bk",