From: Evan Hunt Date: Wed, 15 Jul 2026 06:02:11 +0000 (-0700) Subject: Treat unusable NSEC3PARAM as a verification failure X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=8a14413aae7a065ce05cc4df91dada781240555c;p=thirdparty%2Fbind9.git Treat unusable NSEC3PARAM as a verification failure dns_zoneverify_dnssec() could previously return success if the NSEC3PARAM record was unusable, but no other NSEC/NSEC3 chains were available. This has been fixed. Co-Authored-By: Mark Andrews --- diff --git a/bin/tests/system/verify/tests_verify.py b/bin/tests/system/verify/tests_verify.py index 2fe4d30b2fd..609b5b60019 100644 --- a/bin/tests/system/verify/tests_verify.py +++ b/bin/tests/system/verify/tests_verify.py @@ -9,13 +9,28 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +from pathlib import Path from re import compile as Re import os import re - +import time + +from cryptography.hazmat.primitives.asymmetric import rsa +from dns.dnssectypes import NSEC3Hash +from dns.rdtypes.dnskeybase import Flag + +import dns.dnssec +import dns.name +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.zone import pytest +from isctest.template import NO_NS, zones +from isctest.zone import Zone + import isctest pytestmark = pytest.mark.extra_artifacts( @@ -28,12 +43,114 @@ pytestmark = pytest.mark.extra_artifacts( "zones/*.out*", "zones/*.tmp", "zones/updated*", + "zones/bad-nsec3param-hash*", + "zones/nsec-bad-nsec3param-hash*", + "zones/good-bad-nsec3param-hash*", + "zones/nsec+non-zero-nsec3param-flags*", + "zones/no-nsec+non-zero-nsec3param-flags*", + "zones/nseconly-nsec3param*", ] ) VERIFY = os.environ.get("VERIFY") +def bootstrap(): + + def generate_keys(): + algorithm = dns.dnssec.Algorithm.RSASHA256 + ksk_private_key = rsa.generate_private_key(public_exponent=65537, key_size=1024) + try: + ksk_dnskey = dns.dnssec.make_dnskey( + public_key=ksk_private_key.public_key(), + algorithm=algorithm, + flags=Flag.ZONE | Flag.SEP, + ) + except ImportError as exc: + # if the cryptography package is too old, the make_dnskey() function + # will raise ImportError at runtime + pytest.skip(f"{exc}") + return ksk_private_key, ksk_dnskey + + def gen_and_sign(name, nsec3=False): + zone = Zone(name, NO_NS, signed=True) + ksk_private_key, ksk_dnskey = generate_keys() + keys = [(ksk_private_key, ksk_dnskey)] + zone.render() + + # read the rendered zone + unsigned_path = str(Path(zone.ns.name) / zone.filepath_unsigned) + signed_path = str(Path(zone.ns.name) / zone.filepath_signed) + zoneobj = dns.zone.from_file(unsigned_path, origin=f"{name}.") + lifetime = 30 * 86400 + + # sign the zone + with zoneobj.writer() as txn: + dns.dnssec.sign_zone( + zone=zoneobj, + txn=txn, + keys=keys, + lifetime=lifetime, + add_dnskey=True, + deterministic=False, # for OpenSSL<3.2.0 compat + ) + + # This generates an NSEC3 record for the apex, so that we can + # verify a zone with both a valid and invalid NSEC3PARAM record. + if nsec3: + origin = dns.name.from_text(f"{name}.") + hashname = dns.dnssec.nsec3_hash( + origin, salt=b"", iterations=0, algorithm=NSEC3Hash.SHA1 + ) + nsec3rdata = dns.rdata.from_text( + rdclass=dns.rdataclass.IN, + rdtype=dns.rdatatype.NSEC3, + tok=f"1 0 0 - {hashname} SOA NS A NSEC3PARAM DNSKEY RRSIG", + ) + nsec3_owner = dns.name.from_text(f"{hashname}.{name}.") + node = zoneobj.find_node(nsec3_owner, create=True) + nsec3_rrset = node.find_rdataset( + rdclass=dns.rdataclass.IN, rdtype=dns.rdatatype.NSEC3, create=True + ) + nsec3_rrset.ttl = 300 + nsec3_rrset.add(nsec3rdata) + + inception = int(time.time()) - 30 + nsec3_sigdata = dns.dnssec.sign( + rrset=(nsec3_owner, nsec3_rrset), + private_key=ksk_private_key, + signer=origin, + dnskey=ksk_dnskey, + inception=inception, + lifetime=lifetime, + ) + nsec3_rrsig = node.find_rdataset( + rdclass=dns.rdataclass.IN, + rdtype=dns.rdatatype.RRSIG, + covers=dns.rdatatype.NSEC3, + create=True, + ) + nsec3_rrsig.ttl = 300 + nsec3_rrsig.add(nsec3_sigdata) + + zoneobj.to_file(signed_path) + + return zone + + return { + "zones": zones( + [ + gen_and_sign("bad-nsec3param-hash"), + gen_and_sign("nsec-bad-nsec3param-hash"), + gen_and_sign("good-bad-nsec3param-hash", True), + gen_and_sign("nsec+non-zero-nsec3param-flags"), + gen_and_sign("no-nsec+non-zero-nsec3param-flags"), + gen_and_sign("nseconly-nsec3param", True), + ] + ), + } + + @pytest.mark.parametrize( "zone", [ @@ -176,3 +293,86 @@ def test_verify_j_reads_journal_file(): ] ) assert "Loading zone 'updated' from file 'zones/updated.other'" in cmd.out + + +# checking that unknown hash is detected +def test_verify_rejects_bad_nsec3param_hash(): + cmd = isctest.run.cmd( + [ + VERIFY, + "-z", + "-o", + "bad-nsec3param-hash", + "zones/bad-nsec3param-hash.db.signed", + ], + raise_on_exception=False, + ) + assert cmd.rc != 0 + assert "No usable NSEC/NSEC3 chain for testing" in cmd.err + + +def test_verify_ignores_bad_nsec3param_hash_with_nsec(): + isctest.run.cmd( + [ + VERIFY, + "-z", + "-o", + "nsec-bad-nsec3param-hash", + "zones/nsec-bad-nsec3param-hash.db.signed", + ] + ) + + +def test_verify_ignores_bad_nsec3param_hash_with_good_nsec3param(): + isctest.run.cmd( + [ + VERIFY, + "-z", + "-o", + "good-bad-nsec3param-hash", + "zones/good-bad-nsec3param-hash.db.signed", + ] + ) + + +def test_verify_rejects_no_nsec_and_all_nsec3param_with_non_zero_flags(): + cmd = isctest.run.cmd( + [ + VERIFY, + "-z", + "-o", + "no-nsec+non-zero-nsec3param-flags", + "zones/no-nsec+non-zero-nsec3param-flags.db.signed", + ], + raise_on_exception=False, + ) + assert cmd.rc != 0 + assert "No usable NSEC/NSEC3 chain for testing" in cmd.err + + +def test_verify_ignores_nsec3param_non_zero_flags(): + isctest.run.cmd( + [ + VERIFY, + "-z", + "-o", + "nsec+non-zero-nsec3param-flags", + "zones/nsec+non-zero-nsec3param-flags.db.signed", + ], + ) + + +def test_verify_rejects_nsec3param_with_nsec_only_key(): + cmd = isctest.run.cmd( + [ + VERIFY, + "-z", + "-o", + "nseconly-nsec3param", + "zones/nseconly-nsec3param.db.signed", + ], + raise_on_exception=False, + ) + assert cmd.rc != 0 + isctest.log.debug(cmd.err) + assert "cannot use NSEC3 with key algorithm" in cmd.out diff --git a/bin/tests/system/verify/zones/bad-nsec3param-hash.db.j2.manual b/bin/tests/system/verify/zones/bad-nsec3param-hash.db.j2.manual new file mode 100644 index 00000000000..f3052a98c94 --- /dev/null +++ b/bin/tests/system/verify/zones/bad-nsec3param-hash.db.j2.manual @@ -0,0 +1,7 @@ +{% include '_common/zones/soa.partial.db.j2' %} +{% include '_common/zones/ns.partial.db.j2' %} + +; This NSEC3PARAM record is unusable and should cause verification failure. +{% raw %} +@ NSEC3PARAM 2 0 0 - +{% endraw %} diff --git a/bin/tests/system/verify/zones/good-bad-nsec3param-hash.db.j2.manual b/bin/tests/system/verify/zones/good-bad-nsec3param-hash.db.j2.manual new file mode 100644 index 00000000000..354ae4de15a --- /dev/null +++ b/bin/tests/system/verify/zones/good-bad-nsec3param-hash.db.j2.manual @@ -0,0 +1,8 @@ +{% include '_common/zones/soa.partial.db.j2' %} +{% include '_common/zones/ns.partial.db.j2' %} + +; This NSEC3PARAM record is unusable and should cause verification failure. +{% raw %} +@ NSEC3PARAM 1 0 0 - +@ NSEC3PARAM 2 0 0 - +{% endraw %} diff --git a/bin/tests/system/verify/zones/no-nsec+non-zero-nsec3param-flags.db.j2.manual b/bin/tests/system/verify/zones/no-nsec+non-zero-nsec3param-flags.db.j2.manual new file mode 100644 index 00000000000..08517538551 --- /dev/null +++ b/bin/tests/system/verify/zones/no-nsec+non-zero-nsec3param-flags.db.j2.manual @@ -0,0 +1,10 @@ +{% include '_common/zones/soa.partial.db.j2' %} +{% include '_common/zones/ns.partial.db.j2' %} + +; This NSEC3PARAM record should be ignored. It does not +; indicate that there is a valid NSEC3 chain. As there +; isn't another NSEC3PARAM record with zero flags nor a +; NSEC record this zone should be rejected. +{% raw %} +@ NSEC3PARAM 1 1 0 - +{% endraw %} diff --git a/bin/tests/system/verify/zones/nsec+non-zero-nsec3param-flags.db.j2.manual b/bin/tests/system/verify/zones/nsec+non-zero-nsec3param-flags.db.j2.manual new file mode 100644 index 00000000000..e66995b33d9 --- /dev/null +++ b/bin/tests/system/verify/zones/nsec+non-zero-nsec3param-flags.db.j2.manual @@ -0,0 +1,9 @@ +{% include '_common/zones/soa.partial.db.j2' %} +{% include '_common/zones/ns.partial.db.j2' %} + +; This NSEC3PARAM record should be ignored. It does not +; indicate that there is a valid NSEC3 chain. +{% raw %} +@ NSEC @ SOA NS A NSEC3PARAM DNSKEY NSEC RRSIG +@ NSEC3PARAM 1 1 0 - +{% endraw %} diff --git a/bin/tests/system/verify/zones/nsec-badnsec3param-hash.db.j2.manual b/bin/tests/system/verify/zones/nsec-badnsec3param-hash.db.j2.manual new file mode 100644 index 00000000000..d0b976b3674 --- /dev/null +++ b/bin/tests/system/verify/zones/nsec-badnsec3param-hash.db.j2.manual @@ -0,0 +1,8 @@ +{% include '_common/zones/soa.partial.db.j2' %} +{% include '_common/zones/ns.partial.db.j2' %} + +; This NSEC3PARAM record is unusable but should be ignored since NSEC works. +{% raw %} +@ NSEC @ SOA NS A NSEC3PARAM DNSKEY NSEC RRSIG +@ NSEC3PARAM 2 0 0 - +{% endraw %} diff --git a/bin/tests/system/verify/zones/nseconly-nsec3param.db.j2.manual b/bin/tests/system/verify/zones/nseconly-nsec3param.db.j2.manual new file mode 100644 index 00000000000..c44af87125c --- /dev/null +++ b/bin/tests/system/verify/zones/nseconly-nsec3param.db.j2.manual @@ -0,0 +1,8 @@ +{% include '_common/zones/soa.partial.db.j2' %} +{% include '_common/zones/ns.partial.db.j2' %} + +; This NSEC3PARAM record is valid but the DNSKEY has an NSEC-only algorithm +{% raw %} +@ NSEC3PARAM 1 0 0 - +@ DNSKEY 257 3 5 BEAAAAOlYGw53D+f01yCL5JsP0SB6EjYrnd0JYRBooAaGPT+Q0kpiN+7GviFh+nIazoB8e2Yv7mupgqkmIjObdcbGstYpUltdECdNpNmBvASKB9SBdtGeRvXXpORi3Qyxb9kHGG7SpzyYbc+KDVKnzYHB94pvqu3ZZpPFPBFtCibp/mkhw== +{% endraw %} diff --git a/lib/dns/zoneverify.c b/lib/dns/zoneverify.c index aeb9b1e5b3e..2050f275dd5 100644 --- a/lib/dns/zoneverify.c +++ b/lib/dns/zoneverify.c @@ -60,6 +60,7 @@ typedef struct vctx { dns_keytable_t *secroots; bool goodksk; bool goodzsk; + bool nseconly; dns_rdataset_t keyset; dns_rdataset_t keysigs; dns_rdataset_t soaset; @@ -605,17 +606,16 @@ cleanup: static isc_result_t isoptout(const vctx_t *vctx, const dns_rdata_nsec3param_t *nsec3param, bool *optout) { - dns_rdataset_t rdataset; + dns_rdataset_t rdataset = DNS_RDATASET_INIT; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_nsec3_t nsec3; dns_fixedname_t fixed; - dns_name_t *hashname; + dns_name_t *hashname = dns_fixedname_initname(&fixed); isc_result_t result; dns_dbnode_t *node = NULL; unsigned char rawhash[NSEC3_MAX_HASH_LENGTH]; size_t rhsize = sizeof(rawhash); - dns_fixedname_init(&fixed); result = dns_nsec3_hashname( &fixed, rawhash, &rhsize, vctx->origin, vctx->origin, nsec3param->hash, nsec3param->iterations, nsec3param->salt.base, @@ -626,27 +626,21 @@ isoptout(const vctx_t *vctx, const dns_rdata_nsec3param_t *nsec3param, return result; } - dns_rdataset_init(&rdataset); - hashname = dns_fixedname_name(&fixed); result = dns_db_findnsec3node(vctx->db, hashname, false, &node); if (result == ISC_R_SUCCESS) { result = dns_db_findrdataset(vctx->db, node, vctx->ver, dns_rdatatype_nsec3, 0, 0, &rdataset, NULL); } + if (result == ISC_R_SUCCESS) { + result = dns_rdataset_first(&rdataset); + } if (result != ISC_R_SUCCESS) { *optout = false; result = ISC_R_SUCCESS; goto done; } - result = dns_rdataset_first(&rdataset); - if (result != ISC_R_SUCCESS) { - zoneverify_log_error(vctx, "dns_rdataset_first(): %s", - isc_result_totext(result)); - goto done; - } - dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &nsec3, NULL); @@ -664,49 +658,36 @@ done: static isc_result_t verifynsec3(const vctx_t *vctx, const dns_name_t *name, - const dns_rdata_t *rdata, bool delegation, bool empty, - const unsigned char types[8192], unsigned int maxtype, + const dns_rdata_nsec3param_t *nsec3param, bool delegation, + bool empty, const unsigned char types[8192], unsigned int maxtype, isc_result_t *vresult) { char namebuf[DNS_NAME_FORMATSIZE]; char hashbuf[DNS_NAME_FORMATSIZE]; - dns_rdataset_t rdataset; - dns_rdata_nsec3param_t nsec3param; + dns_rdataset_t rdataset = DNS_RDATASET_INIT; dns_fixedname_t fixed; - dns_name_t *hashname; - isc_result_t result, tvresult = ISC_R_UNSET; + dns_name_t *hashname = dns_fixedname_initname(&fixed); + isc_result_t result; dns_dbnode_t *node = NULL; unsigned char rawhash[NSEC3_MAX_HASH_LENGTH]; size_t rhsize = sizeof(rawhash); bool optout = false; - result = dns_rdata_tostruct(rdata, &nsec3param, NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - - if (nsec3param.flags != 0) { - return ISC_R_SUCCESS; - } - - if (!dns_nsec3_supportedhash(nsec3param.hash)) { - return ISC_R_SUCCESS; - } + INSIST(nsec3param->flags == 0); - if (nsec3param.iterations > DNS_NSEC3_MAXITERATIONS) { - result = DNS_R_NSEC3ITERRANGE; - zoneverify_log_error(vctx, "verifynsec3: %s", - isc_result_totext(result)); + result = isoptout(vctx, nsec3param, &optout); + if (result != ISC_R_SUCCESS) { + *vresult = result; return result; } - RETERR(isoptout(vctx, &nsec3param, &optout)); - - dns_fixedname_init(&fixed); - result = dns_nsec3_hashname(&fixed, rawhash, &rhsize, name, - vctx->origin, nsec3param.hash, - nsec3param.iterations, nsec3param.salt.base, - nsec3param.salt.length); + result = dns_nsec3_hashname( + &fixed, rawhash, &rhsize, name, vctx->origin, nsec3param->hash, + nsec3param->iterations, nsec3param->salt.base, + nsec3param->salt.length); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "dns_nsec3_hashname(): %s", isc_result_totext(result)); + *vresult = result; return result; } @@ -716,8 +697,6 @@ verifynsec3(const vctx_t *vctx, const dns_name_t *name, * from dnssec-signzone so the secure status of the zone may not * be up to date. */ - dns_rdataset_init(&rdataset); - hashname = dns_fixedname_name(&fixed); result = dns_db_findnsec3node(vctx->db, hashname, false, &node); if (result == ISC_R_SUCCESS) { result = dns_db_findrdataset(vctx->db, node, vctx->ver, @@ -736,9 +715,11 @@ verifynsec3(const vctx_t *vctx, const dns_name_t *name, { result = ISC_R_SUCCESS; } else if (result == ISC_R_SUCCESS) { - result = match_nsec3(vctx, name, &nsec3param, &rdataset, types, + isc_result_t tvresult = ISC_R_UNSET; + result = match_nsec3(vctx, name, nsec3param, &rdataset, types, maxtype, rawhash, rhsize, &tvresult); if (result != ISC_R_SUCCESS) { + *vresult = tvresult; goto done; } result = tvresult; @@ -762,11 +743,37 @@ verifynsec3s(const vctx_t *vctx, const dns_name_t *name, const unsigned char types[8192], unsigned int maxtype, isc_result_t *vresult) { DNS_RDATASET_FOREACH(nsec3paramset) { + isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec3param_t nsec3param; dns_rdataset_current(nsec3paramset, &rdata); - RETERR(verifynsec3(vctx, name, &rdata, delegation, empty, types, - maxtype, vresult)); + + result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* Skip unusable NSEC3PARAM records. */ + if (nsec3param.flags != 0) { + continue; + } + + /* + * If an NSEC-only algorithm is in the DNSKEY set, + * any NSEC3PARAM with flags == 0 is an error. + */ + if (vctx->nseconly) { + *vresult = DNS_R_NSEC3BADALG; + break; + } + + if (nsec3param.iterations > DNS_NSEC3_MAXITERATIONS || + !dns_nsec3_supportedhash(nsec3param.hash)) + { + continue; + } + + RETERR(verifynsec3(vctx, name, &nsec3param, delegation, empty, + types, maxtype, vresult)); if (*vresult != ISC_R_SUCCESS) { break; } @@ -1276,21 +1283,17 @@ static isc_result_t check_apex_rrsets(vctx_t *vctx) { dns_dbnode_t *node = NULL; isc_result_t result; + bool nsec3param_ok = false; - result = dns_db_findnode(vctx->db, vctx->origin, false, &node); - if (result != ISC_R_SUCCESS) { - zoneverify_log_error(vctx, - "failed to find the zone's origin: %s", - isc_result_totext(result)); - return result; + result = dns_db_getoriginnode(vctx->db, &node); + if (result == ISC_R_SUCCESS) { + result = dns_db_findrdataset(vctx->db, node, vctx->ver, + dns_rdatatype_dnskey, 0, 0, + &vctx->keyset, &vctx->keysigs); } - - result = dns_db_findrdataset(vctx->db, node, vctx->ver, - dns_rdatatype_dnskey, 0, 0, &vctx->keyset, - &vctx->keysigs); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "Zone contains no DNSSEC keys"); - goto done; + CLEANUP(result); } result = dns_db_findrdataset(vctx->db, node, vctx->ver, @@ -1298,7 +1301,7 @@ check_apex_rrsets(vctx_t *vctx) { &vctx->soasigs); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "Zone contains no SOA record"); - goto done; + CLEANUP(result) } result = dns_db_findrdataset(vctx->db, node, vctx->ver, @@ -1306,7 +1309,7 @@ check_apex_rrsets(vctx_t *vctx) { &vctx->nsecsigs); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { zoneverify_log_error(vctx, "NSEC lookup failed"); - goto done; + CLEANUP(result); } result = dns_db_findrdataset( @@ -1314,21 +1317,19 @@ check_apex_rrsets(vctx_t *vctx) { &vctx->nsec3paramset, &vctx->nsec3paramsigs); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { zoneverify_log_error(vctx, "NSEC3PARAM lookup failed"); - goto done; + CLEANUP(result); } if (!dns_rdataset_isassociated(&vctx->keysigs)) { zoneverify_log_error(vctx, "DNSKEY is not signed " "(keys offline or inactive?)"); - result = ISC_R_FAILURE; - goto done; + CLEANUP(ISC_R_FAILURE); } if (!dns_rdataset_isassociated(&vctx->soasigs)) { zoneverify_log_error(vctx, "SOA is not signed " "(keys offline or inactive?)"); - result = ISC_R_FAILURE; - goto done; + CLEANUP(ISC_R_FAILURE); } if (dns_rdataset_isassociated(&vctx->nsecset) && @@ -1336,8 +1337,7 @@ check_apex_rrsets(vctx_t *vctx) { { zoneverify_log_error(vctx, "NSEC is not signed " "(keys offline or inactive?)"); - result = ISC_R_FAILURE; - goto done; + CLEANUP(ISC_R_FAILURE); } if (dns_rdataset_isassociated(&vctx->nsec3paramset) && @@ -1345,22 +1345,41 @@ check_apex_rrsets(vctx_t *vctx) { { zoneverify_log_error(vctx, "NSEC3PARAM is not signed " "(keys offline or inactive?)"); - result = ISC_R_FAILURE; - goto done; + CLEANUP(ISC_R_FAILURE); } - if (!dns_rdataset_isassociated(&vctx->nsecset) && - !dns_rdataset_isassociated(&vctx->nsec3paramset)) - { - zoneverify_log_error(vctx, "No valid NSEC/NSEC3 chain for " + /* + * Do we have a NSEC3PARAM record that indicates a complete + * chain? A forged NSEC3PARAM set will be detected later. + */ + if (dns_rdataset_isassociated(&vctx->nsec3paramset)) { + DNS_RDATASET_FOREACH(&vctx->nsec3paramset) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec3param_t nsec3param; + + dns_rdataset_current(&vctx->nsec3paramset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (nsec3param.flags != 0 || + nsec3param.iterations > DNS_NSEC3_MAXITERATIONS || + !dns_nsec3_supportedhash(nsec3param.hash)) + { + continue; + } + nsec3param_ok = true; + break; + } + } + + if (!dns_rdataset_isassociated(&vctx->nsecset) && !nsec3param_ok) { + zoneverify_log_error(vctx, "No usable NSEC/NSEC3 chain for " "testing"); - result = ISC_R_FAILURE; - goto done; + CLEANUP(ISC_R_FAILURE); } result = ISC_R_SUCCESS; -done: +cleanup: dns_db_detachnode(&node); return result; @@ -1768,10 +1787,7 @@ verify_nodes(vctx_t *vctx, isc_result_t *vresult) { dns_db_detachnode(&node); goto done; } - if (*vresult == ISC_R_UNSET) { - *vresult = ISC_R_SUCCESS; - } - if (*vresult == ISC_R_SUCCESS) { + if (*vresult == ISC_R_UNSET || *vresult == ISC_R_SUCCESS) { *vresult = tvresult; } if (prevname != NULL) { @@ -1926,6 +1942,10 @@ dns_zoneverify_dnssec(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, goto done; } + /* Record whether NSEC3 is supported by the DNSKEY RRset */ + result = dns_nsec_nseconly(vctx.db, vctx.ver, NULL, &vctx.nseconly); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + determine_active_algorithms(&vctx, ignore_kskflag, keyset_kskonly, report); @@ -1935,10 +1955,9 @@ dns_zoneverify_dnssec(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, } result = verify_nsec3_chains(&vctx, mctx); - if (vresult == ISC_R_UNSET) { - vresult = ISC_R_SUCCESS; - } - if (result != ISC_R_SUCCESS && vresult == ISC_R_SUCCESS) { + if (result != ISC_R_SUCCESS && + (vresult == ISC_R_SUCCESS || vresult == ISC_R_UNSET)) + { vresult = result; }