diff --git a/doc/Changelog b/doc/Changelog index 9d2d2604e..eee54dfeb 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,9 @@ +8 April 2022: Wouter + - Fix zonemd check to allow unsupported algorithms to load. + If there are only unsupported algorithms, or unsupported schemes, + and no failed or successful other ZONEMD records, or malformed + or bad ZONEMD records, the unsupported records allow the zone load. + 25 March 2022: Wouter - Fix spelling error in comment in sldns_str2wire_svcparam_key_lookup. diff --git a/services/authzone.c b/services/authzone.c index b8ce8e73b..9b20c3a7b 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -1882,6 +1882,8 @@ static int auth_zone_zonemd_check_hash(struct auth_zone* z, struct regional* region = NULL; struct sldns_buffer* buf = NULL; uint32_t soa_serial = 0; + char* unsupported_reason = NULL; + int only_unsupported = 1; region = env->scratch; regional_free_all(region); buf = env->scratch_buffer; @@ -1911,6 +1913,7 @@ static int auth_zone_zonemd_check_hash(struct auth_zone* z, &hashalgo, &hash, &hashlen)) { /* malformed RR */ *reason = "ZONEMD rdata malformed"; + only_unsupported = 0; continue; } /* check for duplicates */ @@ -1920,25 +1923,49 @@ static int auth_zone_zonemd_check_hash(struct auth_zone* z, * is not allowed. */ *reason = "ZONEMD RRSet contains more than one RR " "with the same scheme and hash algorithm"; + only_unsupported = 0; continue; } regional_free_all(region); if(serial != soa_serial) { *reason = "ZONEMD serial is wrong"; + only_unsupported = 0; continue; } if(auth_zone_generate_zonemd_check(z, scheme, hashalgo, hash, hashlen, region, buf, reason)) { /* success */ + if(*reason) { + if(!unsupported_reason) + unsupported_reason = *reason; + /* continue to check for valid ZONEMD */ + if(verbosity >= VERB_ALGO) { + char zstr[255+1]; + dname_str(z->name, zstr); + verbose(VERB_ALGO, "auth-zone %s ZONEMD %d %d is unsupported: %s", zstr, (int)scheme, (int)hashalgo, *reason); + } + continue; + } + only_unsupported = 0; if(verbosity >= VERB_ALGO) { char zstr[255+1]; dname_str(z->name, zstr); - verbose(VERB_ALGO, "auth-zone %s ZONEMD hash is correct", zstr); + if(!reason) + verbose(VERB_ALGO, "auth-zone %s ZONEMD hash is correct", zstr); } return 1; } /* try next one */ } + /* have we seen no failures but only unsupported algo, + * and one unsupported algorithm, or more. */ + if(only_unsupported && unsupported_reason) { + /* only unsupported algorithms, with valid serial, not + * malformed. Did not see supported algorithms, failed or + * successful ones. */ + *reason = unsupported_reason; + return 1; + } /* fail, we may have reason */ if(!*reason) *reason = "no ZONEMD records found"; @@ -7659,13 +7686,16 @@ int auth_zone_generate_zonemd_check(struct auth_zone* z, int scheme, { uint8_t gen[512]; size_t genlen = 0; + *reason = NULL; if(!zonemd_hashalgo_supported(hashalgo)) { + /* allow it */ *reason = "unsupported algorithm"; - return 0; + return 1; } if(!zonemd_scheme_supported(scheme)) { + /* allow it */ *reason = "unsupported scheme"; - return 0; + return 1; } if(hashlen < 12) { /* the ZONEMD draft requires digests to fail if too small */ @@ -8030,9 +8060,13 @@ auth_zone_verify_zonemd_with_key(struct auth_zone* z, struct module_env* env, } /* success! log the success */ - auth_zone_log(z->name, VERB_ALGO, "ZONEMD verification successful"); + if(reason) + auth_zone_log(z->name, VERB_ALGO, "ZONEMD %s", reason); + else auth_zone_log(z->name, VERB_ALGO, "ZONEMD verification successful"); if(result) { - *result = strdup("ZONEMD verification successful"); + if(reason) + *result = strdup(reason); + else *result = strdup("ZONEMD verification successful"); if(!*result) log_err("out of memory"); } } diff --git a/services/authzone.h b/services/authzone.h index d24e569d3..07614ed82 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -747,6 +747,9 @@ int zonemd_scheme_supported(int scheme); * @param region: temp region for allocs during canonicalisation. * @param buf: temp buffer during canonicalisation. * @param reason: string returned with failure reason. + * If the hash cannot be checked, but it is allowed, for unknown + * algorithms, the routine returns success, and the reason is nonNULL, + * with the allowance reason. * @return false on failure. */ int auth_zone_generate_zonemd_check(struct auth_zone* z, int scheme, diff --git a/testcode/unitzonemd.c b/testcode/unitzonemd.c index 5caa68a10..23c9f7010 100644 --- a/testcode/unitzonemd.c +++ b/testcode/unitzonemd.c @@ -221,10 +221,10 @@ static void zonemd_check_test(void) unit_assert(result && reason == NULL); result = auth_zone_generate_zonemd_check(z, 241, hashalgo, hash, hashlen, region, buf, &reason); - unit_assert(!result && strcmp(reason, "unsupported scheme")==0); + unit_assert(result && strcmp(reason, "unsupported scheme")==0); result = auth_zone_generate_zonemd_check(z, scheme, 242, hash, hashlen, region, buf, &reason); - unit_assert(!result && strcmp(reason, "unsupported algorithm")==0); + unit_assert(result && strcmp(reason, "unsupported algorithm")==0); result = auth_zone_generate_zonemd_check(z, scheme, hashalgo, hash, 2, region, buf, &reason); unit_assert(!result && strcmp(reason, "digest length too small, less than 12")==0); diff --git a/testdata/auth_zonemd_file_unknown.rpl b/testdata/auth_zonemd_file_unknown.rpl new file mode 100644 index 000000000..f5c5f276e --- /dev/null +++ b/testdata/auth_zonemd_file_unknown.rpl @@ -0,0 +1,184 @@ +; config options +server: + target-fetch-policy: "0 0 0 0 0" + +auth-zone: + name: "example.com." + ## zonefile (or none). + ## zonefile: "example.com.zone" + ## master by IP address or hostname + ## can list multiple masters, each on one line. + ## master: + ## url for http fetch + ## url: + ## queries from downstream clients get authoritative answers. + ## for-downstream: yes + for-downstream: no + ## queries are used to fetch authoritative answers from this zone, + ## instead of unbound itself sending queries there. + ## for-upstream: yes + for-upstream: yes + ## on failures with for-upstream, fallback to sending queries to + ## the authority servers + ## fallback-enabled: no + zonemd-check: yes + + ## this line generates zonefile: \n"/tmp/xxx.example.com"\n + zonefile: +TEMPFILE_NAME example.com + ## this is the inline file /tmp/xxx.example.com + ## the tempfiles are deleted when the testrun is over. +TEMPFILE_CONTENTS example.com +example.com. IN SOA ns.example.com. hostmaster.example.com. 200154054 28800 7200 604800 3600 +example.com. IN NS ns.example.com. +example.com. IN ZONEMD 200154054 1 22 EFAA5B78B38AB1C45DE57B8167BCCE906451D0E72118E1F5E80B5F0C3CF04BFFC65D53C011185528EAD439D6F3A02F511961E090E5E4E0DFA013BD276D728B22 +example.com. IN ZONEMD 200154054 21 2 EFAA5B78B38AB1C45DE57B8167BCCE906451D0E72118E1F5E80B5F0C3CF04BFFC65D53C011185528EAD439D6F3A02F511961E090E5E4E0DFA013BD276D728B22 +www.example.com. IN A 127.0.0.1 +ns.example.com. IN A 127.0.0.1 +bar.example.com. IN A 1.2.3.4 +ding.example.com. IN A 1.2.3.4 +foo.example.com. IN A 1.2.3.4 +TEMPFILE_END + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test authority zone with ZONEMD with unknown algo from zonefile + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION AUTHORITY +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END +RANGE_END + +; a.gtld-servers.net. +RANGE_BEGIN 0 100 + ADDRESS 192.5.6.30 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION ANSWER +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.44 +ENTRY_END +RANGE_END + +; ns.example.net. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.44 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.net. IN NS +SECTION ANSWER +example.net. IN NS ns.example.net. +SECTION ADDITIONAL +ns.example.net. IN A 1.2.3.44 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.example.net. IN A +SECTION ANSWER +ns.example.net. IN A 1.2.3.44 +SECTION AUTHORITY +example.net. IN NS ns.example.net. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.example.net. IN AAAA +SECTION AUTHORITY +example.net. IN NS ns.example.net. +SECTION ADDITIONAL +www.example.net. IN A 1.2.3.44 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION ANSWER +example.com. IN NS ns.example.net. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +ENTRY_END +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; recursion happens here. +STEP 20 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 127.0.0.1 +ENTRY_END + +SCENARIO_END