/* $NetBSD: zoneverify.c,v 1.9.2.2 2024/02/25 15:46:54 martin Exp $ */ /* * 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. */ /*! \file */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct vctx { isc_mem_t *mctx; dns_zone_t *zone; dns_db_t *db; dns_dbversion_t *ver; dns_name_t *origin; dns_keytable_t *secroots; bool goodksk; bool goodzsk; dns_rdataset_t keyset; dns_rdataset_t keysigs; dns_rdataset_t soaset; dns_rdataset_t soasigs; dns_rdataset_t nsecset; dns_rdataset_t nsecsigs; dns_rdataset_t nsec3paramset; dns_rdataset_t nsec3paramsigs; unsigned char revoked_ksk[256]; unsigned char revoked_zsk[256]; unsigned char standby_ksk[256]; unsigned char standby_zsk[256]; unsigned char ksk_algorithms[256]; unsigned char zsk_algorithms[256]; unsigned char bad_algorithms[256]; unsigned char act_algorithms[256]; isc_heap_t *expected_chains; isc_heap_t *found_chains; } vctx_t; struct nsec3_chain_fixed { uint8_t hash; uint8_t salt_length; uint8_t next_length; uint16_t iterations; /* * The following non-fixed-length data is stored in memory after the * fields declared above for each NSEC3 chain element: * * unsigned char salt[salt_length]; * unsigned char owner[next_length]; * unsigned char next[next_length]; */ }; /* * Helper function used to calculate length of variable-length * data section in object pointed to by 'chain'. */ static size_t chain_length(struct nsec3_chain_fixed *chain) { return (chain->salt_length + 2 * chain->next_length); } /*% * Log a zone verification error described by 'fmt' and the variable arguments * following it. Either use dns_zone_logv() or print to stderr, depending on * whether the function was invoked from within named or by a standalone tool, * respectively. */ static void zoneverify_log_error(const vctx_t *vctx, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (vctx->zone != NULL) { dns_zone_logv(vctx->zone, DNS_LOGCATEGORY_GENERAL, ISC_LOG_ERROR, NULL, fmt, ap); } else { vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); } va_end(ap); } static bool is_delegation(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node, uint32_t *ttlp) { dns_rdataset_t nsset; isc_result_t result; if (dns_name_equal(name, vctx->origin)) { return (false); } dns_rdataset_init(&nsset); result = dns_db_findrdataset(vctx->db, node, vctx->ver, dns_rdatatype_ns, 0, 0, &nsset, NULL); if (dns_rdataset_isassociated(&nsset)) { if (ttlp != NULL) { *ttlp = nsset.ttl; } dns_rdataset_disassociate(&nsset); } return ((result == ISC_R_SUCCESS)); } /*% * Return true if version 'ver' of database 'db' contains a DNAME RRset at * 'node'; return false otherwise. */ static bool has_dname(const vctx_t *vctx, dns_dbnode_t *node) { dns_rdataset_t dnameset; isc_result_t result; dns_rdataset_init(&dnameset); result = dns_db_findrdataset(vctx->db, node, vctx->ver, dns_rdatatype_dname, 0, 0, &dnameset, NULL); if (dns_rdataset_isassociated(&dnameset)) { dns_rdataset_disassociate(&dnameset); } return ((result == ISC_R_SUCCESS)); } static bool goodsig(const vctx_t *vctx, dns_rdata_t *sigrdata, const dns_name_t *name, dst_key_t **dstkeys, size_t nkeys, dns_rdataset_t *rdataset) { dns_rdata_rrsig_t sig; isc_result_t result; result = dns_rdata_tostruct(sigrdata, &sig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); for (size_t key = 0; key < nkeys; key++) { if (sig.algorithm != dst_key_alg(dstkeys[key]) || sig.keyid != dst_key_id(dstkeys[key]) || !dns_name_equal(&sig.signer, vctx->origin)) { continue; } result = dns_dnssec_verify(name, rdataset, dstkeys[key], false, 0, vctx->mctx, sigrdata, NULL); if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) { return (true); } } return (false); } static bool nsec_bitmap_equal(dns_rdata_nsec_t *nsec, dns_rdata_t *rdata) { isc_result_t result; dns_rdata_nsec_t tmpnsec; result = dns_rdata_tostruct(rdata, &tmpnsec, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (nsec->len != tmpnsec.len || memcmp(nsec->typebits, tmpnsec.typebits, nsec->len) != 0) { return (false); } return (true); } static isc_result_t verifynsec(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node, const dns_name_t *nextname, isc_result_t *vresult) { unsigned char buffer[DNS_NSEC_BUFFERSIZE]; char namebuf[DNS_NAME_FORMATSIZE]; char nextbuf[DNS_NAME_FORMATSIZE]; char found[DNS_NAME_FORMATSIZE]; dns_rdataset_t rdataset; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_t tmprdata = DNS_RDATA_INIT; dns_rdata_nsec_t nsec; isc_result_t result; dns_rdataset_init(&rdataset); result = dns_db_findrdataset(vctx->db, node, vctx->ver, dns_rdatatype_nsec, 0, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { dns_name_format(name, namebuf, sizeof(namebuf)); zoneverify_log_error(vctx, "Missing NSEC record for %s", namebuf); *vresult = ISC_R_FAILURE; 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, &nsec, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); /* Check next name is consistent */ if (!dns_name_equal(&nsec.next, nextname)) { dns_name_format(name, namebuf, sizeof(namebuf)); dns_name_format(nextname, nextbuf, sizeof(nextbuf)); dns_name_format(&nsec.next, found, sizeof(found)); zoneverify_log_error(vctx, "Bad NSEC record for %s, next name " "mismatch (expected:%s, found:%s)", namebuf, nextbuf, found); *vresult = ISC_R_FAILURE; goto done; } /* Check bit map is consistent */ result = dns_nsec_buildrdata(vctx->db, vctx->ver, node, nextname, buffer, &tmprdata); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "dns_nsec_buildrdata(): %s", isc_result_totext(result)); goto done; } if (!nsec_bitmap_equal(&nsec, &tmprdata)) { dns_name_format(name, namebuf, sizeof(namebuf)); zoneverify_log_error(vctx, "Bad NSEC record for %s, bit map " "mismatch", namebuf); *vresult = ISC_R_FAILURE; goto done; } result = dns_rdataset_next(&rdataset); if (result != ISC_R_NOMORE) { dns_name_format(name, namebuf, sizeof(namebuf)); zoneverify_log_error(vctx, "Multiple NSEC records for %s", namebuf); *vresult = ISC_R_FAILURE; goto done; } *vresult = ISC_R_SUCCESS; result = ISC_R_SUCCESS; done: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } return (result); } static isc_result_t check_no_rrsig(const vctx_t *vctx, const dns_rdataset_t *rdataset, const dns_name_t *name, dns_dbnode_t *node) { char namebuf[DNS_NAME_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; dns_rdataset_t sigrdataset; dns_rdatasetiter_t *rdsiter = NULL; isc_result_t result; dns_rdataset_init(&sigrdataset); result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s", isc_result_totext(result)); return (result); } for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(rdsiter)) { dns_rdatasetiter_current(rdsiter, &sigrdataset); if (sigrdataset.type == dns_rdatatype_rrsig && sigrdataset.covers == rdataset->type) { dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); zoneverify_log_error( vctx, "Warning: Found unexpected signatures " "for %s/%s", namebuf, typebuf); break; } dns_rdataset_disassociate(&sigrdataset); } if (dns_rdataset_isassociated(&sigrdataset)) { dns_rdataset_disassociate(&sigrdataset); } dns_rdatasetiter_destroy(&rdsiter); return (ISC_R_SUCCESS); } static bool chain_compare(void *arg1, void *arg2) { struct nsec3_chain_fixed *e1 = arg1, *e2 = arg2; /* * Do each element in turn to get a stable sort. */ if (e1->hash < e2->hash) { return (true); } if (e1->hash > e2->hash) { return (false); } if (e1->iterations < e2->iterations) { return (true); } if (e1->iterations > e2->iterations) { return (false); } if (e1->salt_length < e2->salt_length) { return (true); } if (e1->salt_length > e2->salt_length) { return (false); } if (e1->next_length < e2->next_length) { return (true); } if (e1->next_length > e2->next_length) { return (false); } if (memcmp(e1 + 1, e2 + 1, chain_length(e1)) < 0) { return (true); } return (false); } static bool chain_equal(const struct nsec3_chain_fixed *e1, const struct nsec3_chain_fixed *e2, size_t data_length) { if (e1->hash != e2->hash) { return (false); } if (e1->iterations != e2->iterations) { return (false); } if (e1->salt_length != e2->salt_length) { return (false); } if (e1->next_length != e2->next_length) { return (false); } return (memcmp(e1 + 1, e2 + 1, data_length) == 0); } static void record_nsec3(const vctx_t *vctx, const unsigned char *rawhash, const dns_rdata_nsec3_t *nsec3, isc_heap_t *chains) { struct nsec3_chain_fixed *element = NULL; unsigned char *cp = NULL; size_t len; len = sizeof(*element) + nsec3->next_length * 2 + nsec3->salt_length; element = isc_mem_get(vctx->mctx, len); memset(element, 0, len); element->hash = nsec3->hash; element->salt_length = nsec3->salt_length; element->next_length = nsec3->next_length; element->iterations = nsec3->iterations; cp = (unsigned char *)(element + 1); memmove(cp, nsec3->salt, nsec3->salt_length); cp += nsec3->salt_length; memmove(cp, rawhash, nsec3->next_length); cp += nsec3->next_length; memmove(cp, nsec3->next, nsec3->next_length); isc_heap_insert(chains, element); } /* * Check whether any NSEC3 within 'rdataset' matches the parameters in * 'nsec3param'. */ static isc_result_t find_nsec3_match(const dns_rdata_nsec3param_t *nsec3param, dns_rdataset_t *rdataset, size_t rhsize, dns_rdata_nsec3_t *nsec3_match) { isc_result_t result; /* * Find matching NSEC3 record. */ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, nsec3_match, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (nsec3_match->hash == nsec3param->hash && nsec3_match->next_length == rhsize && nsec3_match->iterations == nsec3param->iterations && nsec3_match->salt_length == nsec3param->salt_length && memcmp(nsec3_match->salt, nsec3param->salt, nsec3param->salt_length) == 0) { return (ISC_R_SUCCESS); } } return (result); } static isc_result_t match_nsec3(const vctx_t *vctx, const dns_name_t *name, const dns_rdata_nsec3param_t *nsec3param, dns_rdataset_t *rdataset, const unsigned char types[8192], unsigned int maxtype, const unsigned char *rawhash, size_t rhsize, isc_result_t *vresult) { unsigned char cbm[8244]; char namebuf[DNS_NAME_FORMATSIZE]; dns_rdata_nsec3_t nsec3; isc_result_t result; unsigned int len; result = find_nsec3_match(nsec3param, rdataset, rhsize, &nsec3); if (result != ISC_R_SUCCESS) { dns_name_format(name, namebuf, sizeof(namebuf)); zoneverify_log_error(vctx, "Missing NSEC3 record for %s", namebuf); *vresult = result; return (ISC_R_SUCCESS); } /* * Check the type list. */ len = dns_nsec_compressbitmap(cbm, types, maxtype); if (nsec3.len != len || memcmp(cbm, nsec3.typebits, len) != 0) { dns_name_format(name, namebuf, sizeof(namebuf)); zoneverify_log_error(vctx, "Bad NSEC3 record for %s, bit map " "mismatch", namebuf); *vresult = ISC_R_FAILURE; return (ISC_R_SUCCESS); } /* * Record chain. */ record_nsec3(vctx, rawhash, &nsec3, vctx->expected_chains); /* * Make sure there is only one NSEC3 record with this set of * parameters. */ for (result = dns_rdataset_next(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &nsec3, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (nsec3.hash == nsec3param->hash && nsec3.iterations == nsec3param->iterations && nsec3.salt_length == nsec3param->salt_length && memcmp(nsec3.salt, nsec3param->salt, nsec3.salt_length) == 0) { dns_name_format(name, namebuf, sizeof(namebuf)); zoneverify_log_error(vctx, "Multiple NSEC3 records with the " "same parameter set for %s", namebuf); *vresult = DNS_R_DUPLICATE; return (ISC_R_SUCCESS); } } if (result != ISC_R_NOMORE) { return (result); } *vresult = ISC_R_SUCCESS; return (ISC_R_SUCCESS); } static bool innsec3params(const dns_rdata_nsec3_t *nsec3, dns_rdataset_t *nsec3paramset) { dns_rdata_nsec3param_t nsec3param; isc_result_t result; for (result = dns_rdataset_first(nsec3paramset); result == ISC_R_SUCCESS; result = dns_rdataset_next(nsec3paramset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(nsec3paramset, &rdata); result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (nsec3param.flags == 0 && nsec3param.hash == nsec3->hash && nsec3param.iterations == nsec3->iterations && nsec3param.salt_length == nsec3->salt_length && memcmp(nsec3param.salt, nsec3->salt, nsec3->salt_length) == 0) { return (true); } } return (false); } static isc_result_t record_found(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node, dns_rdataset_t *nsec3paramset) { unsigned char owner[NSEC3_MAX_HASH_LENGTH]; dns_rdata_nsec3_t nsec3; dns_rdataset_t rdataset; dns_label_t hashlabel; isc_buffer_t b; isc_result_t result; if (nsec3paramset == NULL || !dns_rdataset_isassociated(nsec3paramset)) { return (ISC_R_SUCCESS); } dns_rdataset_init(&rdataset); result = dns_db_findrdataset(vctx->db, node, vctx->ver, dns_rdatatype_nsec3, 0, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { return (ISC_R_SUCCESS); } dns_name_getlabel(name, 0, &hashlabel); isc_region_consume(&hashlabel, 1); isc_buffer_init(&b, owner, sizeof(owner)); result = isc_base32hex_decoderegion(&hashlabel, &b); if (result != ISC_R_SUCCESS) { result = ISC_R_SUCCESS; goto cleanup; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &nsec3, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (nsec3.next_length != isc_buffer_usedlength(&b)) { continue; } /* * We only care about NSEC3 records that match a NSEC3PARAM * record. */ if (!innsec3params(&nsec3, nsec3paramset)) { continue; } /* * Record chain. */ record_nsec3(vctx, owner, &nsec3, vctx->found_chains); } result = ISC_R_SUCCESS; cleanup: dns_rdataset_disassociate(&rdataset); return (result); } static isc_result_t isoptout(const vctx_t *vctx, const dns_rdata_nsec3param_t *nsec3param, bool *optout) { dns_rdataset_t rdataset; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_nsec3_t nsec3; dns_fixedname_t fixed; dns_name_t *hashname; 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, nsec3param->salt_length); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "dns_nsec3_hashname(): %s", isc_result_totext(result)); 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) { *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); RUNTIME_CHECK(result == ISC_R_SUCCESS); *optout = ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0); done: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (node != NULL) { dns_db_detachnode(vctx->db, &node); } return (result); } 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, isc_result_t *vresult) { char namebuf[DNS_NAME_FORMATSIZE]; char hashbuf[DNS_NAME_FORMATSIZE]; dns_rdataset_t rdataset; dns_rdata_nsec3param_t nsec3param; dns_fixedname_t fixed; dns_name_t *hashname; isc_result_t result, tvresult = ISC_R_UNSET; 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); } if (nsec3param.iterations > DNS_NSEC3_MAXITERATIONS) { result = DNS_R_NSEC3ITERRANGE; zoneverify_log_error(vctx, "verifynsec3: %s", isc_result_totext(result)); return (result); } result = isoptout(vctx, &nsec3param, &optout); if (result != ISC_R_SUCCESS) { return (result); } dns_fixedname_init(&fixed); result = dns_nsec3_hashname( &fixed, rawhash, &rhsize, name, vctx->origin, nsec3param.hash, nsec3param.iterations, nsec3param.salt, nsec3param.salt_length); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "dns_nsec3_hashname(): %s", isc_result_totext(result)); return (result); } /* * We don't use dns_db_find() here as it works with the chosen * nsec3 chain and we may also be called with uncommitted data * 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, dns_rdatatype_nsec3, 0, 0, &rdataset, NULL); } if (result != ISC_R_SUCCESS && (!delegation || (empty && !optout) || (!empty && dns_nsec_isset(types, dns_rdatatype_ds)))) { dns_name_format(name, namebuf, sizeof(namebuf)); dns_name_format(hashname, hashbuf, sizeof(hashbuf)); zoneverify_log_error(vctx, "Missing NSEC3 record for %s (%s)", namebuf, hashbuf); } else if (result == ISC_R_NOTFOUND && delegation && (!empty || optout)) { result = ISC_R_SUCCESS; } else if (result == ISC_R_SUCCESS) { result = match_nsec3(vctx, name, &nsec3param, &rdataset, types, maxtype, rawhash, rhsize, &tvresult); if (result != ISC_R_SUCCESS) { goto done; } result = tvresult; } *vresult = result; result = ISC_R_SUCCESS; done: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (node != NULL) { dns_db_detachnode(vctx->db, &node); } return (result); } static isc_result_t verifynsec3s(const vctx_t *vctx, const dns_name_t *name, dns_rdataset_t *nsec3paramset, bool delegation, bool empty, const unsigned char types[8192], unsigned int maxtype, isc_result_t *vresult) { isc_result_t result; for (result = dns_rdataset_first(nsec3paramset); result == ISC_R_SUCCESS; result = dns_rdataset_next(nsec3paramset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(nsec3paramset, &rdata); result = verifynsec3(vctx, name, &rdata, delegation, empty, types, maxtype, vresult); if (result != ISC_R_SUCCESS) { return (result); } if (*vresult != ISC_R_SUCCESS) { break; } } if (result == ISC_R_NOMORE) { result = ISC_R_SUCCESS; } return (result); } static isc_result_t verifyset(vctx_t *vctx, dns_rdataset_t *rdataset, const dns_name_t *name, dns_dbnode_t *node, dst_key_t **dstkeys, size_t nkeys) { unsigned char set_algorithms[256] = { 0 }; char namebuf[DNS_NAME_FORMATSIZE]; char algbuf[DNS_SECALG_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; dns_rdataset_t sigrdataset; dns_rdatasetiter_t *rdsiter = NULL; isc_result_t result; dns_rdataset_init(&sigrdataset); result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s", isc_result_totext(result)); return (result); } for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(rdsiter)) { dns_rdatasetiter_current(rdsiter, &sigrdataset); if (sigrdataset.type == dns_rdatatype_rrsig && sigrdataset.covers == rdataset->type) { break; } dns_rdataset_disassociate(&sigrdataset); } if (result != ISC_R_SUCCESS) { dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); zoneverify_log_error(vctx, "No signatures for %s/%s", namebuf, typebuf); for (size_t i = 0; i < ARRAY_SIZE(set_algorithms); i++) { if (vctx->act_algorithms[i] != 0) { vctx->bad_algorithms[i] = 1; } } result = ISC_R_SUCCESS; goto done; } for (result = dns_rdataset_first(&sigrdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&sigrdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_rrsig_t sig; dns_rdataset_current(&sigrdataset, &rdata); result = dns_rdata_tostruct(&rdata, &sig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (rdataset->ttl != sig.originalttl) { dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); zoneverify_log_error(vctx, "TTL mismatch for " "%s %s keytag %u", namebuf, typebuf, sig.keyid); continue; } if ((set_algorithms[sig.algorithm] != 0) || (vctx->act_algorithms[sig.algorithm] == 0)) { continue; } if (goodsig(vctx, &rdata, name, dstkeys, nkeys, rdataset)) { dns_rdataset_settrust(rdataset, dns_trust_secure); dns_rdataset_settrust(&sigrdataset, dns_trust_secure); set_algorithms[sig.algorithm] = 1; } } result = ISC_R_SUCCESS; if (memcmp(set_algorithms, vctx->act_algorithms, sizeof(set_algorithms)) != 0) { dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); for (size_t i = 0; i < ARRAY_SIZE(set_algorithms); i++) { if ((vctx->act_algorithms[i] != 0) && (set_algorithms[i] == 0)) { dns_secalg_format(i, algbuf, sizeof(algbuf)); zoneverify_log_error(vctx, "No correct %s signature " "for %s %s", algbuf, namebuf, typebuf); vctx->bad_algorithms[i] = 1; } } } done: if (dns_rdataset_isassociated(&sigrdataset)) { dns_rdataset_disassociate(&sigrdataset); } dns_rdatasetiter_destroy(&rdsiter); return (result); } static isc_result_t verifynode(vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node, bool delegation, dst_key_t **dstkeys, size_t nkeys, dns_rdataset_t *nsecset, dns_rdataset_t *nsec3paramset, const dns_name_t *nextname, isc_result_t *vresult) { unsigned char types[8192] = { 0 }; unsigned int maxtype = 0; dns_rdataset_t rdataset; dns_rdatasetiter_t *rdsiter = NULL; isc_result_t result, tvresult = ISC_R_UNSET; REQUIRE(vresult != NULL || (nsecset == NULL && nsec3paramset == NULL)); result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s", isc_result_totext(result)); return (result); } result = dns_rdatasetiter_first(rdsiter); dns_rdataset_init(&rdataset); while (result == ISC_R_SUCCESS) { dns_rdatasetiter_current(rdsiter, &rdataset); /* * If we are not at a delegation then everything should be * signed. If we are at a delegation then only the DS set * is signed. The NS set is not signed at a delegation but * its existence is recorded in the bit map. Anything else * other than NSEC and DS is not signed at a delegation. */ if (rdataset.type != dns_rdatatype_rrsig && rdataset.type != dns_rdatatype_dnskey && (!delegation || rdataset.type == dns_rdatatype_ds || rdataset.type == dns_rdatatype_nsec)) { result = verifyset(vctx, &rdataset, name, node, dstkeys, nkeys); if (result != ISC_R_SUCCESS) { dns_rdataset_disassociate(&rdataset); dns_rdatasetiter_destroy(&rdsiter); return (result); } dns_nsec_setbit(types, rdataset.type, 1); if (rdataset.type > maxtype) { maxtype = rdataset.type; } } else if (rdataset.type != dns_rdatatype_rrsig && rdataset.type != dns_rdatatype_dnskey) { if (rdataset.type == dns_rdatatype_ns) { dns_nsec_setbit(types, rdataset.type, 1); } result = check_no_rrsig(vctx, &rdataset, name, node); if (result != ISC_R_SUCCESS) { dns_rdataset_disassociate(&rdataset); dns_rdatasetiter_destroy(&rdsiter); return (result); } } else { dns_nsec_setbit(types, rdataset.type, 1); } dns_rdataset_disassociate(&rdataset); result = dns_rdatasetiter_next(rdsiter); } dns_rdatasetiter_destroy(&rdsiter); if (result != ISC_R_NOMORE) { zoneverify_log_error(vctx, "rdataset iteration failed: %s", isc_result_totext(result)); return (result); } if (vresult == NULL) { return (ISC_R_SUCCESS); } *vresult = ISC_R_SUCCESS; if (nsecset != NULL && dns_rdataset_isassociated(nsecset)) { result = verifynsec(vctx, name, node, nextname, &tvresult); if (result != ISC_R_SUCCESS) { return (result); } *vresult = tvresult; } if (nsec3paramset != NULL && dns_rdataset_isassociated(nsec3paramset)) { result = verifynsec3s(vctx, name, nsec3paramset, delegation, false, types, maxtype, &tvresult); if (result != ISC_R_SUCCESS) { return (result); } if (*vresult == ISC_R_SUCCESS) { *vresult = tvresult; } } return (ISC_R_SUCCESS); } static isc_result_t is_empty(const vctx_t *vctx, dns_dbnode_t *node, bool *empty) { dns_rdatasetiter_t *rdsiter = NULL; isc_result_t result; result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s", isc_result_totext(result)); return (result); } result = dns_rdatasetiter_first(rdsiter); dns_rdatasetiter_destroy(&rdsiter); *empty = (result == ISC_R_NOMORE); return (ISC_R_SUCCESS); } static isc_result_t check_no_nsec(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node) { bool nsec_exists = false; dns_rdataset_t rdataset; isc_result_t result; dns_rdataset_init(&rdataset); result = dns_db_findrdataset(vctx->db, node, vctx->ver, dns_rdatatype_nsec, 0, 0, &rdataset, NULL); if (result != ISC_R_NOTFOUND) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(name, namebuf, sizeof(namebuf)); zoneverify_log_error(vctx, "unexpected NSEC RRset at %s", namebuf); nsec_exists = true; } if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } return (nsec_exists ? ISC_R_FAILURE : ISC_R_SUCCESS); } static void free_element(isc_mem_t *mctx, struct nsec3_chain_fixed *e) { size_t len; len = sizeof(*e) + e->salt_length + 2 * e->next_length; isc_mem_put(mctx, e, len); } static void free_element_heap(void *element, void *uap) { struct nsec3_chain_fixed *e = (struct nsec3_chain_fixed *)element; isc_mem_t *mctx = (isc_mem_t *)uap; free_element(mctx, e); } static bool _checknext(const vctx_t *vctx, const struct nsec3_chain_fixed *first, const struct nsec3_chain_fixed *e) { char buf[512]; const unsigned char *d1 = (const unsigned char *)(first + 1); const unsigned char *d2 = (const unsigned char *)(e + 1); isc_buffer_t b; isc_region_t sr; d1 += first->salt_length + first->next_length; d2 += e->salt_length; if (memcmp(d1, d2, first->next_length) == 0) { return (true); } DE_CONST(d1 - first->next_length, sr.base); sr.length = first->next_length; isc_buffer_init(&b, buf, sizeof(buf)); isc_base32hex_totext(&sr, 1, "", &b); zoneverify_log_error(vctx, "Break in NSEC3 chain at: %.*s", (int)isc_buffer_usedlength(&b), buf); DE_CONST(d1, sr.base); sr.length = first->next_length; isc_buffer_init(&b, buf, sizeof(buf)); isc_base32hex_totext(&sr, 1, "", &b); zoneverify_log_error(vctx, "Expected: %.*s", (int)isc_buffer_usedlength(&b), buf); DE_CONST(d2, sr.base); sr.length = first->next_length; isc_buffer_init(&b, buf, sizeof(buf)); isc_base32hex_totext(&sr, 1, "", &b); zoneverify_log_error(vctx, "Found: %.*s", (int)isc_buffer_usedlength(&b), buf); return (false); } static bool checknext(isc_mem_t *mctx, const vctx_t *vctx, const struct nsec3_chain_fixed *first, struct nsec3_chain_fixed *prev, const struct nsec3_chain_fixed *cur) { bool result = _checknext(vctx, prev, cur); if (prev != first) { free_element(mctx, prev); } return (result); } static bool checklast(isc_mem_t *mctx, const vctx_t *vctx, struct nsec3_chain_fixed *first, struct nsec3_chain_fixed *prev) { bool result = _checknext(vctx, prev, first); if (prev != first) { free_element(mctx, prev); } free_element(mctx, first); return (result); } static isc_result_t verify_nsec3_chains(const vctx_t *vctx, isc_mem_t *mctx) { isc_result_t result = ISC_R_SUCCESS; struct nsec3_chain_fixed *e, *f = NULL; struct nsec3_chain_fixed *first = NULL, *prev = NULL; while ((e = isc_heap_element(vctx->expected_chains, 1)) != NULL) { isc_heap_delete(vctx->expected_chains, 1); if (f == NULL) { f = isc_heap_element(vctx->found_chains, 1); } if (f != NULL) { isc_heap_delete(vctx->found_chains, 1); /* * Check that they match. */ if (chain_equal(e, f, chain_length(e))) { free_element(mctx, f); f = NULL; } else { if (result == ISC_R_SUCCESS) { zoneverify_log_error(vctx, "Expected " "and found " "NSEC3 " "chains not " "equal"); } result = ISC_R_FAILURE; /* * Attempt to resync found_chain. */ while (f != NULL && !chain_compare(e, f)) { free_element(mctx, f); f = isc_heap_element(vctx->found_chains, 1); if (f != NULL) { isc_heap_delete( vctx->found_chains, 1); } if (f != NULL && chain_equal(e, f, chain_length(e))) { free_element(mctx, f); f = NULL; break; } } } } else if (result == ISC_R_SUCCESS) { zoneverify_log_error(vctx, "Expected and found NSEC3 " "chains " "not equal"); result = ISC_R_FAILURE; } if (first == NULL) { prev = first = e; } else if (!chain_equal(first, e, first->salt_length)) { if (!checklast(mctx, vctx, first, prev)) { result = ISC_R_FAILURE; } prev = first = e; } else { if (!checknext(mctx, vctx, first, prev, e)) { result = ISC_R_FAILURE; } prev = e; } } if (prev != NULL) { if (!checklast(mctx, vctx, first, prev)) { result = ISC_R_FAILURE; } } do { if (f != NULL) { if (result == ISC_R_SUCCESS) { zoneverify_log_error(vctx, "Expected and found " "NSEC3 chains not " "equal"); result = ISC_R_FAILURE; } free_element(mctx, f); } f = isc_heap_element(vctx->found_chains, 1); if (f != NULL) { isc_heap_delete(vctx->found_chains, 1); } } while (f != NULL); return (result); } static isc_result_t verifyemptynodes(const vctx_t *vctx, const dns_name_t *name, const dns_name_t *prevname, bool isdelegation, dns_rdataset_t *nsec3paramset, isc_result_t *vresult) { dns_namereln_t reln; int order; unsigned int labels, nlabels, i; dns_name_t suffix; isc_result_t result, tvresult = ISC_R_UNSET; *vresult = ISC_R_SUCCESS; reln = dns_name_fullcompare(prevname, name, &order, &labels); if (order >= 0) { return (ISC_R_SUCCESS); } nlabels = dns_name_countlabels(name); if (reln == dns_namereln_commonancestor || reln == dns_namereln_contains) { dns_name_init(&suffix, NULL); for (i = labels + 1; i < nlabels; i++) { dns_name_getlabelsequence(name, nlabels - i, i, &suffix); if (nsec3paramset != NULL && dns_rdataset_isassociated(nsec3paramset)) { result = verifynsec3s( vctx, &suffix, nsec3paramset, isdelegation, true, NULL, 0, &tvresult); if (result != ISC_R_SUCCESS) { return (result); } if (*vresult == ISC_R_SUCCESS) { *vresult = tvresult; } } } } return (ISC_R_SUCCESS); } static void vctx_init(vctx_t *vctx, isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin, dns_keytable_t *secroots) { memset(vctx, 0, sizeof(*vctx)); vctx->mctx = mctx; vctx->zone = zone; vctx->db = db; vctx->ver = ver; vctx->origin = origin; vctx->secroots = secroots; vctx->goodksk = false; vctx->goodzsk = false; dns_rdataset_init(&vctx->keyset); dns_rdataset_init(&vctx->keysigs); dns_rdataset_init(&vctx->soaset); dns_rdataset_init(&vctx->soasigs); dns_rdataset_init(&vctx->nsecset); dns_rdataset_init(&vctx->nsecsigs); dns_rdataset_init(&vctx->nsec3paramset); dns_rdataset_init(&vctx->nsec3paramsigs); vctx->expected_chains = NULL; isc_heap_create(mctx, chain_compare, NULL, 1024, &vctx->expected_chains); vctx->found_chains = NULL; isc_heap_create(mctx, chain_compare, NULL, 1024, &vctx->found_chains); } static void vctx_destroy(vctx_t *vctx) { if (dns_rdataset_isassociated(&vctx->keyset)) { dns_rdataset_disassociate(&vctx->keyset); } if (dns_rdataset_isassociated(&vctx->keysigs)) { dns_rdataset_disassociate(&vctx->keysigs); } if (dns_rdataset_isassociated(&vctx->soaset)) { dns_rdataset_disassociate(&vctx->soaset); } if (dns_rdataset_isassociated(&vctx->soasigs)) { dns_rdataset_disassociate(&vctx->soasigs); } if (dns_rdataset_isassociated(&vctx->nsecset)) { dns_rdataset_disassociate(&vctx->nsecset); } if (dns_rdataset_isassociated(&vctx->nsecsigs)) { dns_rdataset_disassociate(&vctx->nsecsigs); } if (dns_rdataset_isassociated(&vctx->nsec3paramset)) { dns_rdataset_disassociate(&vctx->nsec3paramset); } if (dns_rdataset_isassociated(&vctx->nsec3paramsigs)) { dns_rdataset_disassociate(&vctx->nsec3paramsigs); } isc_heap_foreach(vctx->expected_chains, free_element_heap, vctx->mctx); isc_heap_destroy(&vctx->expected_chains); isc_heap_foreach(vctx->found_chains, free_element_heap, vctx->mctx); isc_heap_destroy(&vctx->found_chains); } static isc_result_t check_apex_rrsets(vctx_t *vctx) { dns_dbnode_t *node = NULL; isc_result_t result; 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_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; } result = dns_db_findrdataset(vctx->db, node, vctx->ver, dns_rdatatype_soa, 0, 0, &vctx->soaset, &vctx->soasigs); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "Zone contains no SOA record"); goto done; } result = dns_db_findrdataset(vctx->db, node, vctx->ver, dns_rdatatype_nsec, 0, 0, &vctx->nsecset, &vctx->nsecsigs); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { zoneverify_log_error(vctx, "NSEC lookup failed"); goto done; } result = dns_db_findrdataset( vctx->db, node, vctx->ver, dns_rdatatype_nsec3param, 0, 0, &vctx->nsec3paramset, &vctx->nsec3paramsigs); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { zoneverify_log_error(vctx, "NSEC3PARAM lookup failed"); goto done; } if (!dns_rdataset_isassociated(&vctx->keysigs)) { zoneverify_log_error(vctx, "DNSKEY is not signed " "(keys offline or inactive?)"); result = ISC_R_FAILURE; goto done; } if (!dns_rdataset_isassociated(&vctx->soasigs)) { zoneverify_log_error(vctx, "SOA is not signed " "(keys offline or inactive?)"); result = ISC_R_FAILURE; goto done; } if (dns_rdataset_isassociated(&vctx->nsecset) && !dns_rdataset_isassociated(&vctx->nsecsigs)) { zoneverify_log_error(vctx, "NSEC is not signed " "(keys offline or inactive?)"); result = ISC_R_FAILURE; goto done; } if (dns_rdataset_isassociated(&vctx->nsec3paramset) && !dns_rdataset_isassociated(&vctx->nsec3paramsigs)) { zoneverify_log_error(vctx, "NSEC3PARAM is not signed " "(keys offline or inactive?)"); result = ISC_R_FAILURE; goto done; } if (!dns_rdataset_isassociated(&vctx->nsecset) && !dns_rdataset_isassociated(&vctx->nsec3paramset)) { zoneverify_log_error(vctx, "No valid NSEC/NSEC3 chain for " "testing"); result = ISC_R_FAILURE; goto done; } result = ISC_R_SUCCESS; done: dns_db_detachnode(vctx->db, &node); return (result); } /*% * Update 'vctx' tables tracking active and standby key algorithms used in the * verified zone based on the signatures made using 'dnskey' (prepared from * 'rdata') found at zone apex. Set 'vctx->goodksk' or 'vctx->goodzsk' to true * if 'dnskey' correctly signs the DNSKEY RRset at zone apex and either * 'vctx->secroots' is NULL or 'dnskey' is present in 'vctx->secroots'. * * The variables to update are chosen based on 'is_ksk', which is true when * 'dnskey' is a KSK and false otherwise. */ static void check_dnskey_sigs(vctx_t *vctx, const dns_rdata_dnskey_t *dnskey, dns_rdata_t *keyrdata, bool is_ksk) { unsigned char *active_keys = NULL, *standby_keys = NULL; dns_keynode_t *keynode = NULL; bool *goodkey = NULL; dst_key_t *key = NULL; isc_result_t result; dns_rdataset_t dsset; active_keys = (is_ksk ? vctx->ksk_algorithms : vctx->zsk_algorithms); standby_keys = (is_ksk ? vctx->standby_ksk : vctx->standby_zsk); goodkey = (is_ksk ? &vctx->goodksk : &vctx->goodzsk); /* * First, does this key sign the DNSKEY rrset? */ if (!dns_dnssec_selfsigns(keyrdata, vctx->origin, &vctx->keyset, &vctx->keysigs, false, vctx->mctx)) { if (!is_ksk && dns_dnssec_signs(keyrdata, vctx->origin, &vctx->soaset, &vctx->soasigs, false, vctx->mctx)) { if (active_keys[dnskey->algorithm] != DNS_KEYALG_MAX) { active_keys[dnskey->algorithm]++; } } else { if (standby_keys[dnskey->algorithm] != DNS_KEYALG_MAX) { standby_keys[dnskey->algorithm]++; } } return; } if (active_keys[dnskey->algorithm] != DNS_KEYALG_MAX) { active_keys[dnskey->algorithm]++; } /* * If a trust anchor table was not supplied, a correctly self-signed * DNSKEY RRset is good enough. */ if (vctx->secroots == NULL) { *goodkey = true; return; } /* * Convert the supplied key rdata to dst_key_t. (If this * fails we can't go further.) */ result = dns_dnssec_keyfromrdata(vctx->origin, keyrdata, vctx->mctx, &key); RUNTIME_CHECK(result == ISC_R_SUCCESS); /* * Look up the supplied key in the trust anchor table. * If we don't find an exact match, or if the keynode data * is NULL, then we have neither a DNSKEY nor a DS format * trust anchor, and can give up. */ result = dns_keytable_find(vctx->secroots, vctx->origin, &keynode); if (result != ISC_R_SUCCESS) { /* No such trust anchor */ goto cleanup; } /* * If the keynode has any DS format trust anchors, that means * it doesn't have any DNSKEY ones. So, we can check for a DS * match and then stop. */ dns_rdataset_init(&dsset); if (dns_keynode_dsset(keynode, &dsset)) { for (result = dns_rdataset_first(&dsset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&dsset)) { dns_rdata_t dsrdata = DNS_RDATA_INIT; dns_rdata_t newdsrdata = DNS_RDATA_INIT; unsigned char buf[DNS_DS_BUFFERSIZE]; dns_rdata_ds_t ds; dns_rdata_reset(&dsrdata); dns_rdataset_current(&dsset, &dsrdata); result = dns_rdata_tostruct(&dsrdata, &ds, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (ds.key_tag != dst_key_id(key) || ds.algorithm != dst_key_alg(key)) { continue; } result = dns_ds_buildrdata(vctx->origin, keyrdata, ds.digest_type, buf, &newdsrdata); if (result != ISC_R_SUCCESS) { continue; } if (dns_rdata_compare(&dsrdata, &newdsrdata) == 0) { dns_rdataset_settrust(&vctx->keyset, dns_trust_secure); dns_rdataset_settrust(&vctx->keysigs, dns_trust_secure); *goodkey = true; break; } } dns_rdataset_disassociate(&dsset); goto cleanup; } cleanup: if (keynode != NULL) { dns_keytable_detachkeynode(vctx->secroots, &keynode); } if (key != NULL) { dst_key_free(&key); } } /*% * Check that the DNSKEY RR has at least one self signing KSK and one ZSK per * algorithm in it (or, if -x was used, one self-signing KSK). */ static isc_result_t check_dnskey(vctx_t *vctx) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_dnskey_t dnskey; isc_result_t result; bool is_ksk; for (result = dns_rdataset_first(&vctx->keyset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&vctx->keyset)) { dns_rdataset_current(&vctx->keyset, &rdata); result = dns_rdata_tostruct(&rdata, &dnskey, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); is_ksk = ((dnskey.flags & DNS_KEYFLAG_KSK) != 0); if ((dnskey.flags & DNS_KEYOWNER_ZONE) != 0 && (dnskey.flags & DNS_KEYFLAG_REVOKE) != 0) { if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 && !dns_dnssec_selfsigns(&rdata, vctx->origin, &vctx->keyset, &vctx->keysigs, false, vctx->mctx)) { char namebuf[DNS_NAME_FORMATSIZE]; char buffer[1024]; isc_buffer_t buf; dns_name_format(vctx->origin, namebuf, sizeof(namebuf)); isc_buffer_init(&buf, buffer, sizeof(buffer)); result = dns_rdata_totext(&rdata, NULL, &buf); if (result != ISC_R_SUCCESS) { zoneverify_log_error( vctx, "dns_rdata_totext: %s", isc_result_totext(result)); return (ISC_R_FAILURE); } zoneverify_log_error( vctx, "revoked KSK is not self signed:\n" "%s DNSKEY %.*s", namebuf, (int)isc_buffer_usedlength(&buf), buffer); return (ISC_R_FAILURE); } if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 && vctx->revoked_ksk[dnskey.algorithm] != DNS_KEYALG_MAX) { vctx->revoked_ksk[dnskey.algorithm]++; } else if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0 && vctx->revoked_zsk[dnskey.algorithm] != DNS_KEYALG_MAX) { vctx->revoked_zsk[dnskey.algorithm]++; } } else { check_dnskey_sigs(vctx, &dnskey, &rdata, is_ksk); } dns_rdata_freestruct(&dnskey); dns_rdata_reset(&rdata); } return (ISC_R_SUCCESS); } static void determine_active_algorithms(vctx_t *vctx, bool ignore_kskflag, bool keyset_kskonly, void (*report)(const char *, ...)) { char algbuf[DNS_SECALG_FORMATSIZE]; report("Verifying the zone using the following algorithms:"); for (size_t i = 0; i < ARRAY_SIZE(vctx->act_algorithms); i++) { if (ignore_kskflag) { vctx->act_algorithms[i] = (vctx->ksk_algorithms[i] != 0 || vctx->zsk_algorithms[i] != 0) ? 1 : 0; } else { vctx->act_algorithms[i] = vctx->ksk_algorithms[i] != 0 ? 1 : 0; } if (vctx->act_algorithms[i] != 0) { dns_secalg_format(i, algbuf, sizeof(algbuf)); report("- %s", algbuf); } } if (ignore_kskflag || keyset_kskonly) { return; } for (size_t i = 0; i < ARRAY_SIZE(vctx->ksk_algorithms); i++) { /* * The counts should both be zero or both be non-zero. Mark * the algorithm as bad if this is not met. */ if ((vctx->ksk_algorithms[i] != 0) == (vctx->zsk_algorithms[i] != 0)) { continue; } dns_secalg_format(i, algbuf, sizeof(algbuf)); zoneverify_log_error(vctx, "Missing %s for algorithm %s", (vctx->ksk_algorithms[i] != 0) ? "ZSK" : "self-" "signed " "KSK", algbuf); vctx->bad_algorithms[i] = 1; } } /*% * Check that all the records not yet verified were signed by keys that are * present in the DNSKEY RRset. */ static isc_result_t verify_nodes(vctx_t *vctx, isc_result_t *vresult) { dns_fixedname_t fname, fnextname, fprevname, fzonecut; dns_name_t *name, *nextname, *prevname, *zonecut; dns_dbnode_t *node = NULL, *nextnode; dns_dbiterator_t *dbiter = NULL; dst_key_t **dstkeys; size_t count, nkeys = 0; bool done = false; isc_result_t tvresult = ISC_R_UNSET; isc_result_t result; name = dns_fixedname_initname(&fname); nextname = dns_fixedname_initname(&fnextname); dns_fixedname_init(&fprevname); prevname = NULL; dns_fixedname_init(&fzonecut); zonecut = NULL; count = dns_rdataset_count(&vctx->keyset); dstkeys = isc_mem_get(vctx->mctx, sizeof(*dstkeys) * count); for (result = dns_rdataset_first(&vctx->keyset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&vctx->keyset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&vctx->keyset, &rdata); dstkeys[nkeys] = NULL; result = dns_dnssec_keyfromrdata(vctx->origin, &rdata, vctx->mctx, &dstkeys[nkeys]); if (result == ISC_R_SUCCESS) { nkeys++; } } result = dns_db_createiterator(vctx->db, DNS_DB_NONSEC3, &dbiter); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "dns_db_createiterator(): %s", isc_result_totext(result)); goto done; } result = dns_dbiterator_first(dbiter); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "dns_dbiterator_first(): %s", isc_result_totext(result)); goto done; } while (!done) { bool isdelegation = false; result = dns_dbiterator_current(dbiter, &node, name); if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { zoneverify_log_error(vctx, "dns_dbiterator_current(): %s", isc_result_totext(result)); goto done; } if (!dns_name_issubdomain(name, vctx->origin)) { result = check_no_nsec(vctx, name, node); if (result != ISC_R_SUCCESS) { dns_db_detachnode(vctx->db, &node); goto done; } dns_db_detachnode(vctx->db, &node); result = dns_dbiterator_next(dbiter); if (result == ISC_R_NOMORE) { done = true; } else if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "dns_dbiterator_next(): " "%s", isc_result_totext(result)); goto done; } continue; } if (is_delegation(vctx, name, node, NULL)) { zonecut = dns_fixedname_name(&fzonecut); dns_name_copy(name, zonecut); isdelegation = true; } else if (has_dname(vctx, node)) { zonecut = dns_fixedname_name(&fzonecut); dns_name_copy(name, zonecut); } nextnode = NULL; result = dns_dbiterator_next(dbiter); while (result == ISC_R_SUCCESS) { bool empty; result = dns_dbiterator_current(dbiter, &nextnode, nextname); if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { zoneverify_log_error(vctx, "dns_dbiterator_current():" " %s", isc_result_totext(result)); dns_db_detachnode(vctx->db, &node); goto done; } if (!dns_name_issubdomain(nextname, vctx->origin) || (zonecut != NULL && dns_name_issubdomain(nextname, zonecut))) { result = check_no_nsec(vctx, nextname, nextnode); if (result != ISC_R_SUCCESS) { dns_db_detachnode(vctx->db, &node); dns_db_detachnode(vctx->db, &nextnode); goto done; } dns_db_detachnode(vctx->db, &nextnode); result = dns_dbiterator_next(dbiter); continue; } result = is_empty(vctx, nextnode, &empty); dns_db_detachnode(vctx->db, &nextnode); if (result != ISC_R_SUCCESS) { dns_db_detachnode(vctx->db, &node); goto done; } if (empty) { result = dns_dbiterator_next(dbiter); continue; } break; } if (result == ISC_R_NOMORE) { done = true; nextname = vctx->origin; } else if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "iterating through the database " "failed: %s", isc_result_totext(result)); dns_db_detachnode(vctx->db, &node); goto done; } result = verifynode(vctx, name, node, isdelegation, dstkeys, nkeys, &vctx->nsecset, &vctx->nsec3paramset, nextname, &tvresult); if (result != ISC_R_SUCCESS) { dns_db_detachnode(vctx->db, &node); goto done; } if (*vresult == ISC_R_UNSET) { *vresult = ISC_R_SUCCESS; } if (*vresult == ISC_R_SUCCESS) { *vresult = tvresult; } if (prevname != NULL) { result = verifyemptynodes( vctx, name, prevname, isdelegation, &vctx->nsec3paramset, &tvresult); if (result != ISC_R_SUCCESS) { dns_db_detachnode(vctx->db, &node); goto done; } } else { prevname = dns_fixedname_name(&fprevname); } dns_name_copy(name, prevname); if (*vresult == ISC_R_SUCCESS) { *vresult = tvresult; } dns_db_detachnode(vctx->db, &node); } dns_dbiterator_destroy(&dbiter); result = dns_db_createiterator(vctx->db, DNS_DB_NSEC3ONLY, &dbiter); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "dns_db_createiterator(): %s", isc_result_totext(result)); return (result); } for (result = dns_dbiterator_first(dbiter); result == ISC_R_SUCCESS; result = dns_dbiterator_next(dbiter)) { result = dns_dbiterator_current(dbiter, &node, name); if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { zoneverify_log_error(vctx, "dns_dbiterator_current(): %s", isc_result_totext(result)); goto done; } result = verifynode(vctx, name, node, false, dstkeys, nkeys, NULL, NULL, NULL, NULL); if (result != ISC_R_SUCCESS) { zoneverify_log_error(vctx, "verifynode: %s", isc_result_totext(result)); dns_db_detachnode(vctx->db, &node); goto done; } result = record_found(vctx, name, node, &vctx->nsec3paramset); dns_db_detachnode(vctx->db, &node); if (result != ISC_R_SUCCESS) { goto done; } } result = ISC_R_SUCCESS; done: while (nkeys-- > 0U) { dst_key_free(&dstkeys[nkeys]); } isc_mem_put(vctx->mctx, dstkeys, sizeof(*dstkeys) * count); if (dbiter != NULL) { dns_dbiterator_destroy(&dbiter); } return (result); } static isc_result_t check_bad_algorithms(const vctx_t *vctx, void (*report)(const char *, ...)) { char algbuf[DNS_SECALG_FORMATSIZE]; bool first = true; for (size_t i = 0; i < ARRAY_SIZE(vctx->bad_algorithms); i++) { if (vctx->bad_algorithms[i] == 0) { continue; } if (first) { report("The zone is not fully signed " "for the following algorithms:"); } dns_secalg_format(i, algbuf, sizeof(algbuf)); report(" %s", algbuf); first = false; } if (!first) { report("."); } return (first ? ISC_R_SUCCESS : ISC_R_FAILURE); } static void print_summary(const vctx_t *vctx, bool keyset_kskonly, void (*report)(const char *, ...)) { char algbuf[DNS_SECALG_FORMATSIZE]; report("Zone fully signed:"); for (size_t i = 0; i < ARRAY_SIZE(vctx->ksk_algorithms); i++) { if ((vctx->ksk_algorithms[i] == 0) && (vctx->standby_ksk[i] == 0) && (vctx->revoked_ksk[i] == 0) && (vctx->zsk_algorithms[i] == 0) && (vctx->standby_zsk[i] == 0) && (vctx->revoked_zsk[i] == 0)) { continue; } dns_secalg_format(i, algbuf, sizeof(algbuf)); report("Algorithm: %s: KSKs: " "%u active, %u stand-by, %u revoked", algbuf, vctx->ksk_algorithms[i], vctx->standby_ksk[i], vctx->revoked_ksk[i]); report("%*sZSKs: " "%u active, %u %s, %u revoked", (int)strlen(algbuf) + 13, "", vctx->zsk_algorithms[i], vctx->standby_zsk[i], keyset_kskonly ? "present" : "stand-by", vctx->revoked_zsk[i]); } } isc_result_t dns_zoneverify_dnssec(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin, dns_keytable_t *secroots, isc_mem_t *mctx, bool ignore_kskflag, bool keyset_kskonly, void (*report)(const char *, ...)) { const char *keydesc = (secroots == NULL ? "self-signed" : "trusted"); isc_result_t result, vresult = ISC_R_UNSET; vctx_t vctx; vctx_init(&vctx, mctx, zone, db, ver, origin, secroots); result = check_apex_rrsets(&vctx); if (result != ISC_R_SUCCESS) { goto done; } result = check_dnskey(&vctx); if (result != ISC_R_SUCCESS) { goto done; } if (ignore_kskflag) { if (!vctx.goodksk && !vctx.goodzsk) { zoneverify_log_error(&vctx, "No %s DNSKEY found", keydesc); result = ISC_R_FAILURE; goto done; } } else if (!vctx.goodksk) { zoneverify_log_error(&vctx, "No %s KSK DNSKEY found", keydesc); result = ISC_R_FAILURE; goto done; } determine_active_algorithms(&vctx, ignore_kskflag, keyset_kskonly, report); result = verify_nodes(&vctx, &vresult); if (result != ISC_R_SUCCESS) { goto done; } result = verify_nsec3_chains(&vctx, mctx); if (vresult == ISC_R_UNSET) { vresult = ISC_R_SUCCESS; } if (result != ISC_R_SUCCESS && vresult == ISC_R_SUCCESS) { vresult = result; } result = check_bad_algorithms(&vctx, report); if (result != ISC_R_SUCCESS) { report("DNSSEC completeness test failed."); goto done; } result = vresult; if (result != ISC_R_SUCCESS) { report("DNSSEC completeness test failed (%s).", isc_result_totext(result)); goto done; } if (vctx.goodksk || ignore_kskflag) { print_summary(&vctx, keyset_kskonly, report); } done: vctx_destroy(&vctx); return (result); }