diff --git a/bin/tests/system/dnssec/ns2/example.db.in b/bin/tests/system/dnssec/ns2/example.db.in index 015be5fd1d..72e3c1fffa 100644 --- a/bin/tests/system/dnssec/ns2/example.db.in +++ b/bin/tests/system/dnssec/ns2/example.db.in @@ -106,6 +106,12 @@ 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 + +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 680d296ac5..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 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/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..293ff2dda8 100644 --- a/bin/tests/system/dnssec/ns3/named.conf.in +++ b/bin/tests/system/dnssec/ns3/named.conf.in @@ -195,6 +195,18 @@ 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"; + 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..5689979cf1 100644 --- a/bin/tests/system/dnssec/ns3/sign.sh +++ b/bin/tests/system/dnssec/ns3/sign.sh @@ -298,6 +298,48 @@ awk '$4 == "DNSKEY" { $7 = 255 } $4 == "RRSIG" { $6 = 255 } { print }' ${zonefil DSFILE="dsset-${zone}." $DSFROMKEY -A -f ${zonefile}.signed "$zone" >"$DSFILE" +# +# 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. +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..be904a53a2 100644 --- a/bin/tests/system/dnssec/ns4/named1.conf.in +++ b/bin/tests/system/dnssec/ns4/named1.conf.in @@ -28,9 +28,13 @@ options { nta-lifetime 12s; 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; }; + # 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..7f1188830b 100644 --- a/bin/tests/system/dnssec/ns4/named2.conf.in +++ b/bin/tests/system/dnssec/ns4/named2.conf.in @@ -25,6 +25,10 @@ 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; }; }; 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..d90ffb0531 100644 --- a/bin/tests/system/dnssec/ns4/named3.conf.in +++ b/bin/tests/system/dnssec/ns4/named3.conf.in @@ -26,6 +26,10 @@ 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; }; }; 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..1a8d917ca8 100644 --- a/bin/tests/system/dnssec/ns4/named4.conf.in +++ b/bin/tests/system/dnssec/ns4/named4.conf.in @@ -21,6 +21,10 @@ 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; }; }; key rndc_key { diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index 37fc64de29..ea2eafcb56 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -3677,6 +3677,35 @@ 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" +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" @@ -3974,6 +4003,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 +4015,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 +4030,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 +4042,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..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", @@ -131,6 +132,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", 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)) {