diff --git a/CHANGES b/CHANGES index 3333080a61..4e66137b0a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +4973. [func] verifyzone() and the functions it uses were moved to + libdns and refactored to prevent exit() from being + called upon failure. A side effect of that is that + dnssec-signzone and dnssec-verify now check for memory + leaks upon shutdown. [GL #266] + 4972. [func] Declare the 'rdata' argument for dns_rdata_tostruct() to be const. [GL #341] diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c index 7259254dc4..2747c627fd 100644 --- a/bin/dnssec/dnssec-signzone.c +++ b/bin/dnssec/dnssec-signzone.c @@ -76,6 +76,7 @@ #include #include #include +#include #include @@ -96,6 +97,10 @@ typedef struct hashlist hashlist_t; static int nsec_datatype = dns_rdatatype_nsec; +#define check_dns_dbiterator_current(result) \ + check_result((result == DNS_R_NEWORIGIN) ? ISC_R_SUCCESS : result, \ + "dns_dbiterator_current()") + #define IS_NSEC3 (nsec_datatype == dns_rdatatype_nsec3) #define OPTOUT(x) (((x) & DNS_NSEC3FLAG_OPTOUT) != 0) @@ -498,11 +503,11 @@ signset(dns_diff_t *del, dns_diff_t *add, dns_dbnode_t *node, dns_name_t *name, dns_ttl_t ttl; int i; char namestr[DNS_NAME_FORMATSIZE]; - char typestr[TYPE_FORMATSIZE]; + char typestr[DNS_RDATATYPE_FORMATSIZE]; char sigstr[SIG_FORMATSIZE]; dns_name_format(name, namestr, sizeof(namestr)); - type_format(set->type, typestr, sizeof(typestr)); + dns_rdatatype_format(set->type, typestr, sizeof(typestr)); ttl = ISC_MIN(set->ttl, endtime - starttime); @@ -1042,6 +1047,47 @@ secure(dns_name_t *name, dns_dbnode_t *node) { return (ISC_TF(result == ISC_R_SUCCESS)); } +static isc_boolean_t +is_delegation(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin, + dns_name_t *name, dns_dbnode_t *node, isc_uint32_t *ttlp) +{ + dns_rdataset_t nsset; + isc_result_t result; + + if (dns_name_equal(name, origin)) + return (ISC_FALSE); + + dns_rdataset_init(&nsset); + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_ns, + 0, 0, &nsset, NULL); + if (dns_rdataset_isassociated(&nsset)) { + if (ttlp != NULL) + *ttlp = nsset.ttl; + dns_rdataset_disassociate(&nsset); + } + + return (ISC_TF(result == ISC_R_SUCCESS)); +} + +/*% + * Return ISC_TRUE if version 'ver' of database 'db' contains a DNAME RRset at + * 'node'; return ISC_FALSE otherwise. + */ +static isc_boolean_t +has_dname(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node) { + dns_rdataset_t dnameset; + isc_result_t result; + + dns_rdataset_init(&dnameset); + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dname, 0, 0, + &dnameset, NULL); + if (dns_rdataset_isassociated(&dnameset)) { + dns_rdataset_disassociate(&dnameset); + } + + return (ISC_TF(result == ISC_R_SUCCESS)); +} + /*% * Signs all records at a name. */ @@ -2090,10 +2136,10 @@ rrset_cleanup(dns_name_t *name, dns_rdataset_t *rdataset, unsigned int count1 = 0; dns_rdataset_t tmprdataset; char namestr[DNS_NAME_FORMATSIZE]; - char typestr[TYPE_FORMATSIZE]; + char typestr[DNS_RDATATYPE_FORMATSIZE]; dns_name_format(name, namestr, sizeof(namestr)); - type_format(rdataset->type, typestr, sizeof(typestr)); + dns_rdatatype_format(rdataset->type, typestr, sizeof(typestr)); dns_rdataset_init(&tmprdataset); for (result = dns_rdataset_first(rdataset); @@ -3181,7 +3227,7 @@ main(int argc, char *argv[]) { isc_time_t timer_start, timer_finish; isc_time_t sign_start, sign_finish; dns_dnsseckey_t *key; - isc_result_t result; + isc_result_t result, vresult; isc_log_t *log = NULL; #ifdef USE_PKCS11 const char *engine = PKCS11_ENGINE; @@ -3866,9 +3912,18 @@ main(int argc, char *argv[]) { postsign(); TIME_NOW(&sign_finish); - if (!disable_zone_check) - verifyzone(gdb, gversion, gorigin, mctx, - ignore_kskflag, keyset_kskonly); + if (disable_zone_check) { + vresult = ISC_R_SUCCESS; + } else { + vresult = dns_zoneverify_dnssec(NULL, gdb, gversion, gorigin, + mctx, ignore_kskflag, + keyset_kskonly); + if (vresult != ISC_R_SUCCESS) { + fprintf(output_stdout ? stderr : stdout, + "Zone verification failed (%s)\n", + isc_result_totext(vresult)); + } + } if (outputformat != dns_masterformat_text) { dns_masterrawheader_t header; @@ -3894,12 +3949,16 @@ main(int argc, char *argv[]) { check_result(result, "isc_stdio_close"); removefile = ISC_FALSE; - result = isc_file_rename(tempfile, output); - if (result != ISC_R_SUCCESS) - fatal("failed to rename temp file to %s: %s", - output, isc_result_totext(result)); - - printf("%s\n", output); + if (vresult == ISC_R_SUCCESS) { + result = isc_file_rename(tempfile, output); + if (result != ISC_R_SUCCESS) { + fatal("failed to rename temp file to %s: %s", + output, isc_result_totext(result)); + } + printf("%s\n", output); + } else { + isc_file_remove(tempfile); + } } dns_db_closeversion(gdb, &gversion, ISC_FALSE); @@ -3939,5 +3998,5 @@ main(int argc, char *argv[]) { #ifdef _WIN32 DestroySockets(); #endif - return (0); + return (vresult == ISC_R_SUCCESS ? 0 : 1); } diff --git a/bin/dnssec/dnssec-verify.c b/bin/dnssec/dnssec-verify.c index 09e8e30c2d..3fb5fa2c49 100644 --- a/bin/dnssec/dnssec-verify.c +++ b/bin/dnssec/dnssec-verify.c @@ -58,6 +58,7 @@ #include #include #include +#include #include @@ -322,8 +323,8 @@ main(int argc, char *argv[]) { result = dns_db_newversion(gdb, &gversion); check_result(result, "dns_db_newversion()"); - verifyzone(gdb, gversion, gorigin, mctx, - ignore_kskflag, keyset_kskonly); + result = dns_zoneverify_dnssec(NULL, gdb, gversion, gorigin, mctx, + ignore_kskflag, keyset_kskonly); dns_db_closeversion(gdb, &gversion, ISC_FALSE); dns_db_detach(&gdb); @@ -337,5 +338,5 @@ main(int argc, char *argv[]) { (void) isc_app_finish(); - return (0); + return (result == ISC_R_SUCCESS ? 0 : 1); } diff --git a/bin/dnssec/dnssectool.c b/bin/dnssec/dnssectool.c index 88a0c77fd0..e7ea7d522d 100644 --- a/bin/dnssec/dnssectool.c +++ b/bin/dnssec/dnssectool.c @@ -57,18 +57,6 @@ #include "dnssectool.h" -static isc_heap_t *expected_chains, *found_chains; - -struct nsec3_chain_fixed { - isc_uint8_t hash; - isc_uint8_t salt_length; - isc_uint8_t next_length; - isc_uint16_t iterations; - /* unsigned char salt[0]; */ - /* unsigned char owner[0]; */ - /* unsigned char next[0]; */ -}; - extern int verbose; extern const char *program; @@ -116,19 +104,6 @@ version(const char *name) { exit(0); } -void -type_format(const dns_rdatatype_t type, char *cp, unsigned int size) { - isc_buffer_t b; - isc_region_t r; - isc_result_t result; - - isc_buffer_init(&b, cp, size - 1); - result = dns_rdatatype_totext(type, &b); - check_result(result, "dns_rdatatype_totext()"); - isc_buffer_usedregion(&b, &r); - r.base[r.length] = 0; -} - void sig_format(dns_rdata_rrsig_t *sig, char *cp, unsigned int size) { char namestr[DNS_NAME_FORMATSIZE]; @@ -503,1334 +478,6 @@ key_collision(dst_key_t *dstkey, dns_name_t *name, const char *dir, return (conflict); } -isc_boolean_t -is_delegation(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin, - dns_name_t *name, dns_dbnode_t *node, isc_uint32_t *ttlp) -{ - dns_rdataset_t nsset; - isc_result_t result; - - if (dns_name_equal(name, origin)) - return (ISC_FALSE); - - dns_rdataset_init(&nsset); - result = dns_db_findrdataset(db, node, ver, dns_rdatatype_ns, - 0, 0, &nsset, NULL); - if (dns_rdataset_isassociated(&nsset)) { - if (ttlp != NULL) - *ttlp = nsset.ttl; - dns_rdataset_disassociate(&nsset); - } - - return (ISC_TF(result == ISC_R_SUCCESS)); -} - -isc_boolean_t -has_dname(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node) { - dns_rdataset_t dnameset; - isc_result_t result; - - dns_rdataset_init(&dnameset); - result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dname, 0, 0, - &dnameset, NULL); - if (dns_rdataset_isassociated(&dnameset)) { - dns_rdataset_disassociate(&dnameset); - } - - return (ISC_TF(result == ISC_R_SUCCESS)); -} - -static isc_boolean_t -goodsig(dns_name_t *origin, dns_rdata_t *sigrdata, dns_name_t *name, - dns_rdataset_t *keyrdataset, dns_rdataset_t *rdataset, isc_mem_t *mctx) -{ - dns_rdata_dnskey_t key; - dns_rdata_rrsig_t sig; - dst_key_t *dstkey = NULL; - isc_result_t result; - - result = dns_rdata_tostruct(sigrdata, &sig, NULL); - check_result(result, "dns_rdata_tostruct()"); - - for (result = dns_rdataset_first(keyrdataset); - result == ISC_R_SUCCESS; - result = dns_rdataset_next(keyrdataset)) { - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_current(keyrdataset, &rdata); - result = dns_rdata_tostruct(&rdata, &key, NULL); - check_result(result, "dns_rdata_tostruct()"); - result = dns_dnssec_keyfromrdata(origin, &rdata, mctx, - &dstkey); - if (result != ISC_R_SUCCESS) - return (ISC_FALSE); - if (sig.algorithm != key.algorithm || - sig.keyid != dst_key_id(dstkey) || - !dns_name_equal(&sig.signer, origin)) { - dst_key_free(&dstkey); - continue; - } - result = dns_dnssec_verify(name, rdataset, dstkey, ISC_FALSE, - 0, mctx, sigrdata, NULL); - dst_key_free(&dstkey); - if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) { - return(ISC_TRUE); - } - } - return (ISC_FALSE); -} - -static isc_result_t -verifynsec(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, - dns_dbnode_t *node, dns_name_t *nextname) -{ - 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(db, node, ver, dns_rdatatype_nsec, - 0, 0, &rdataset, NULL); - if (result != ISC_R_SUCCESS) { - dns_name_format(name, namebuf, sizeof(namebuf)); - fprintf(stderr, "Missing NSEC record for %s\n", namebuf); - goto failure; - } - - result = dns_rdataset_first(&rdataset); - check_result(result, "dns_rdataset_first()"); - - dns_rdataset_current(&rdataset, &rdata); - result = dns_rdata_tostruct(&rdata, &nsec, NULL); - check_result(result, "dns_rdata_tostruct()"); - /* Check bit 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)); - fprintf(stderr, "Bad NSEC record for %s, next name " - "mismatch (expected:%s, found:%s)\n", namebuf, - nextbuf, found); - goto failure; - } - /* Check bit map is consistent */ - result = dns_nsec_buildrdata(db, ver, node, nextname, buffer, - &tmprdata); - check_result(result, "dns_nsec_buildrdata()"); - if (dns_rdata_compare(&rdata, &tmprdata) != 0) { - dns_name_format(name, namebuf, sizeof(namebuf)); - fprintf(stderr, "Bad NSEC record for %s, bit map " - "mismatch\n", namebuf); - goto failure; - } - result = dns_rdataset_next(&rdataset); - if (result != ISC_R_NOMORE) { - dns_name_format(name, namebuf, sizeof(namebuf)); - fprintf(stderr, "Multipe NSEC records for %s\n", namebuf); - goto failure; - - } - dns_rdataset_disassociate(&rdataset); - return (ISC_R_SUCCESS); - failure: - if (dns_rdataset_isassociated(&rdataset)) - dns_rdataset_disassociate(&rdataset); - return (ISC_R_FAILURE); -} - -static void -check_no_rrsig(dns_db_t *db, dns_dbversion_t *ver, dns_rdataset_t *rdataset, - dns_name_t *name, dns_dbnode_t *node) -{ - char namebuf[DNS_NAME_FORMATSIZE]; - char typebuf[80]; - dns_rdataset_t sigrdataset; - dns_rdatasetiter_t *rdsiter = NULL; - isc_result_t result; - - dns_rdataset_init(&sigrdataset); - result = dns_db_allrdatasets(db, node, ver, 0, &rdsiter); - check_result(result, "dns_db_allrdatasets()"); - 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)); - type_format(rdataset->type, typebuf, sizeof(typebuf)); - fprintf(stderr, "Warning: Found unexpected signatures for " - "%s/%s\n", namebuf, typebuf); - } - if (dns_rdataset_isassociated(&sigrdataset)) - dns_rdataset_disassociate(&sigrdataset); - dns_rdatasetiter_destroy(&rdsiter); -} - -static isc_boolean_t -chain_compare(void *arg1, void *arg2) { - struct nsec3_chain_fixed *e1 = arg1, *e2 = arg2; - size_t len; - - /* - * Do each element in turn to get a stable sort. - */ - if (e1->hash < e2->hash) - return (ISC_TRUE); - if (e1->hash > e2->hash) - return (ISC_FALSE); - if (e1->iterations < e2->iterations) - return (ISC_TRUE); - if (e1->iterations > e2->iterations) - return (ISC_FALSE); - if (e1->salt_length < e2->salt_length) - return (ISC_TRUE); - if (e1->salt_length > e2->salt_length) - return (ISC_FALSE); - if (e1->next_length < e2->next_length) - return (ISC_TRUE); - if (e1->next_length > e2->next_length) - return (ISC_FALSE); - len = e1->salt_length + 2 * e1->next_length; - if (memcmp(e1 + 1, e2 + 1, len) < 0) - return (ISC_TRUE); - return (ISC_FALSE); -} - -static isc_boolean_t -chain_equal(struct nsec3_chain_fixed *e1, struct nsec3_chain_fixed *e2) { - size_t len; - - if (e1->hash != e2->hash) - return (ISC_FALSE); - if (e1->iterations != e2->iterations) - return (ISC_FALSE); - if (e1->salt_length != e2->salt_length) - return (ISC_FALSE); - if (e1->next_length != e2->next_length) - return (ISC_FALSE); - len = e1->salt_length + 2 * e1->next_length; - if (memcmp(e1 + 1, e2 + 1, len) != 0) - return (ISC_FALSE); - return (ISC_TRUE); -} - -static isc_result_t -record_nsec3(const unsigned char *rawhash, const dns_rdata_nsec3_t *nsec3, - isc_mem_t *mctx, isc_heap_t *chains) -{ - struct nsec3_chain_fixed *element; - size_t len; - unsigned char *cp; - isc_result_t result; - - len = sizeof(*element) + nsec3->next_length * 2 + nsec3->salt_length; - - element = isc_mem_get(mctx, len); - if (element == NULL) - return (ISC_R_NOMEMORY); - 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); - result = isc_heap_insert(chains, element); - if (result != ISC_R_SUCCESS) { - fprintf(stderr, "isc_heap_insert failed: %s\n", - isc_result_totext(result)); - isc_mem_put(mctx, element, len); - } - return (result); -} - -static isc_result_t -match_nsec3(dns_name_t *name, isc_mem_t *mctx, - dns_rdata_nsec3param_t *nsec3param, dns_rdataset_t *rdataset, - unsigned char types[8192], unsigned int maxtype, - unsigned char *rawhash, size_t rhsize) -{ - unsigned char cbm[8244]; - char namebuf[DNS_NAME_FORMATSIZE]; - dns_rdata_nsec3_t nsec3; - isc_result_t result; - unsigned int len; - - /* - * 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, NULL); - check_result(result, "dns_rdata_tostruct()"); - if (nsec3.hash == nsec3param->hash && - nsec3.next_length == rhsize && - nsec3.iterations == nsec3param->iterations && - nsec3.salt_length == nsec3param->salt_length && - memcmp(nsec3.salt, nsec3param->salt, - nsec3param->salt_length) == 0) - break; - } - if (result != ISC_R_SUCCESS) { - dns_name_format(name, namebuf, sizeof(namebuf)); - fprintf(stderr, "Missing NSEC3 record for %s\n", namebuf); - return (result); - } - - /* - * 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)); - fprintf(stderr, "Bad NSEC3 record for %s, bit map " - "mismatch\n", namebuf); - return (ISC_R_FAILURE); - } - - /* - * Record chain. - */ - result = record_nsec3(rawhash, &nsec3, mctx, expected_chains); - check_result(result, "record_nsec3()"); - - /* - * 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); - check_result(result, "dns_rdata_tostruct()"); - 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)); - fprintf(stderr, "Multiple NSEC3 records with the " - "same parameter set for %s", namebuf); - result = DNS_R_DUPLICATE; - break; - } - } - if (result != ISC_R_NOMORE) - return (result); - - result = ISC_R_SUCCESS; - return (result); -} - -static isc_boolean_t -innsec3params(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); - check_result(result, "dns_rdata_tostruct()"); - 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 (ISC_TRUE); - } - return (ISC_FALSE); -} - -static isc_result_t -record_found(dns_db_t *db, dns_dbversion_t *ver, isc_mem_t *mctx, - 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(db, node, 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) - 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); - check_result(result, "dns_rdata_tostruct()"); - 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. - */ - result = record_nsec3(owner, &nsec3, mctx, found_chains); - check_result(result, "record_nsec3()"); - } - - cleanup: - dns_rdataset_disassociate(&rdataset); - return (ISC_R_SUCCESS); -} - -static isc_boolean_t -isoptout(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin, - dns_rdata_t *nsec3rdata) -{ - dns_rdataset_t rdataset; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdata_nsec3_t nsec3; - dns_rdata_nsec3param_t nsec3param; - 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); - isc_boolean_t ret; - - result = dns_rdata_tostruct(nsec3rdata, &nsec3param, NULL); - check_result(result, "dns_rdata_tostruct()"); - - dns_fixedname_init(&fixed); - result = dns_nsec3_hashname(&fixed, rawhash, &rhsize, origin, origin, - nsec3param.hash, nsec3param.iterations, - nsec3param.salt, nsec3param.salt_length); - check_result(result, "dns_nsec3_hashname()"); - - dns_rdataset_init(&rdataset); - hashname = dns_fixedname_name(&fixed); - result = dns_db_findnsec3node(db, hashname, ISC_FALSE, &node); - if (result == ISC_R_SUCCESS) - result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3, - 0, 0, &rdataset, NULL); - if (result != ISC_R_SUCCESS) - return (ISC_FALSE); - - result = dns_rdataset_first(&rdataset); - check_result(result, "dns_rdataset_first()"); - - dns_rdataset_current(&rdataset, &rdata); - - result = dns_rdata_tostruct(&rdata, &nsec3, NULL); - if (result != ISC_R_SUCCESS) - ret = ISC_FALSE; - else - ret = ISC_TF((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0); - - if (dns_rdataset_isassociated(&rdataset)) - dns_rdataset_disassociate(&rdataset); - if (node != NULL) - dns_db_detachnode(db, &node); - - return (ret); -} - -static isc_result_t -verifynsec3(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin, - isc_mem_t *mctx, dns_name_t *name, dns_rdata_t *rdata, - isc_boolean_t delegation, isc_boolean_t empty, - unsigned char types[8192], unsigned int maxtype) -{ - 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; - dns_dbnode_t *node = NULL; - unsigned char rawhash[NSEC3_MAX_HASH_LENGTH]; - size_t rhsize = sizeof(rawhash); - isc_boolean_t optout; - - result = dns_rdata_tostruct(rdata, &nsec3param, NULL); - check_result(result, "dns_rdata_tostruct()"); - - if (nsec3param.flags != 0) - return (ISC_R_SUCCESS); - - if (!dns_nsec3_supportedhash(nsec3param.hash)) - return (ISC_R_SUCCESS); - - optout = isoptout(db, ver, origin, rdata); - - dns_fixedname_init(&fixed); - result = dns_nsec3_hashname(&fixed, rawhash, &rhsize, name, origin, - nsec3param.hash, nsec3param.iterations, - nsec3param.salt, nsec3param.salt_length); - check_result(result, "dns_nsec3_hashname()"); - - /* - * We don't use dns_db_find() here as it works with the choosen - * 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(db, hashname, ISC_FALSE, &node); - if (result == ISC_R_SUCCESS) - result = dns_db_findrdataset(db, node, 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)); - fprintf(stderr, "Missing NSEC3 record for %s (%s)\n", - namebuf, hashbuf); - } else if (result == ISC_R_NOTFOUND && - delegation && (!empty || optout)) - { - result = ISC_R_SUCCESS; - } else if (result == ISC_R_SUCCESS) { - result = match_nsec3(name, mctx, &nsec3param, &rdataset, - types, maxtype, rawhash, rhsize); - } - - if (dns_rdataset_isassociated(&rdataset)) - dns_rdataset_disassociate(&rdataset); - if (node != NULL) - dns_db_detachnode(db, &node); - - return (result); -} - -static isc_result_t -verifynsec3s(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin, - isc_mem_t *mctx, dns_name_t *name, dns_rdataset_t *nsec3paramset, - isc_boolean_t delegation, isc_boolean_t empty, - unsigned char types[8192], unsigned int maxtype) -{ - 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(db, ver, origin, mctx, name, &rdata, - delegation, empty, types, maxtype); - if (result != ISC_R_SUCCESS) - break; - } - if (result == ISC_R_NOMORE) - result = ISC_R_SUCCESS; - return (result); -} - -static void -verifyset(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin, - isc_mem_t *mctx, dns_rdataset_t *rdataset, dns_name_t *name, - dns_dbnode_t *node, dns_rdataset_t *keyrdataset, - unsigned char *act_algorithms, unsigned char *bad_algorithms) -{ - unsigned char set_algorithms[256]; - char namebuf[DNS_NAME_FORMATSIZE]; - char algbuf[80]; - char typebuf[80]; - dns_rdataset_t sigrdataset; - dns_rdatasetiter_t *rdsiter = NULL; - isc_result_t result; - int i; - - dns_rdataset_init(&sigrdataset); - result = dns_db_allrdatasets(db, node, ver, 0, &rdsiter); - check_result(result, "dns_db_allrdatasets()"); - 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)); - type_format(rdataset->type, typebuf, sizeof(typebuf)); - fprintf(stderr, "No signatures for %s/%s\n", namebuf, typebuf); - for (i = 0; i < 256; i++) - if (act_algorithms[i] != 0) - bad_algorithms[i] = 1; - dns_rdatasetiter_destroy(&rdsiter); - return; - } - - memset(set_algorithms, 0, sizeof(set_algorithms)); - 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); - check_result(result, "dns_rdata_tostruct()"); - if (rdataset->ttl != sig.originalttl) { - dns_name_format(name, namebuf, sizeof(namebuf)); - type_format(rdataset->type, typebuf, sizeof(typebuf)); - fprintf(stderr, "TTL mismatch for %s %s keytag %u\n", - namebuf, typebuf, sig.keyid); - continue; - } - if ((set_algorithms[sig.algorithm] != 0) || - (act_algorithms[sig.algorithm] == 0)) - continue; - if (goodsig(origin, &rdata, name, keyrdataset, rdataset, mctx)) - set_algorithms[sig.algorithm] = 1; - } - dns_rdatasetiter_destroy(&rdsiter); - if (memcmp(set_algorithms, act_algorithms, sizeof(set_algorithms))) { - dns_name_format(name, namebuf, sizeof(namebuf)); - type_format(rdataset->type, typebuf, sizeof(typebuf)); - for (i = 0; i < 256; i++) - if ((act_algorithms[i] != 0) && - (set_algorithms[i] == 0)) { - dns_secalg_format(i, algbuf, sizeof(algbuf)); - fprintf(stderr, "No correct %s signature for " - "%s %s\n", algbuf, namebuf, typebuf); - bad_algorithms[i] = 1; - } - } - dns_rdataset_disassociate(&sigrdataset); -} - -static isc_result_t -verifynode(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin, - isc_mem_t *mctx, dns_name_t *name, dns_dbnode_t *node, - isc_boolean_t delegation, dns_rdataset_t *keyrdataset, - unsigned char *act_algorithms, unsigned char *bad_algorithms, - dns_rdataset_t *nsecset, dns_rdataset_t *nsec3paramset, - dns_name_t *nextname) -{ - unsigned char types[8192]; - unsigned int maxtype = 0; - dns_rdataset_t rdataset; dns_rdatasetiter_t *rdsiter = NULL; - isc_result_t result, tresult; - - memset(types, 0, sizeof(types)); - result = dns_db_allrdatasets(db, node, ver, 0, &rdsiter); - check_result(result, "dns_db_allrdatasets()"); - 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 existance 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)) { - verifyset(db, ver, origin, mctx, &rdataset, - name, node, keyrdataset, - act_algorithms, bad_algorithms); - 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); - check_no_rrsig(db, ver, &rdataset, name, node); - } else - dns_nsec_setbit(types, rdataset.type, 1); - dns_rdataset_disassociate(&rdataset); - result = dns_rdatasetiter_next(rdsiter); - } - if (result != ISC_R_NOMORE) - fatal("rdataset iteration failed: %s", - isc_result_totext(result)); - dns_rdatasetiter_destroy(&rdsiter); - - result = ISC_R_SUCCESS; - - if (nsecset != NULL && dns_rdataset_isassociated(nsecset)) - result = verifynsec(db, ver, name, node, nextname); - - if (nsec3paramset != NULL && dns_rdataset_isassociated(nsec3paramset)) { - tresult = verifynsec3s(db, ver, origin, mctx, name, - nsec3paramset, delegation, ISC_FALSE, - types, maxtype); - if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS) - result = tresult; - } - return (result); -} - -static isc_boolean_t -is_empty(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node) { - dns_rdatasetiter_t *rdsiter = NULL; - isc_result_t result; - - result = dns_db_allrdatasets(db, node, ver, 0, &rdsiter); - check_result(result, "dns_db_allrdatasets()"); - result = dns_rdatasetiter_first(rdsiter); - dns_rdatasetiter_destroy(&rdsiter); - if (result == ISC_R_NOMORE) - return (ISC_TRUE); - return (ISC_FALSE); -} - -static void -check_no_nsec(dns_name_t *name, dns_dbnode_t *node, dns_db_t *db, - dns_dbversion_t *ver) -{ - dns_rdataset_t rdataset; - isc_result_t result; - - dns_rdataset_init(&rdataset); - result = dns_db_findrdataset(db, node, 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)); - fatal("unexpected NSEC RRset at %s\n", namebuf); - } - - if (dns_rdataset_isassociated(&rdataset)) - dns_rdataset_disassociate(&rdataset); -} - -static isc_boolean_t -newchain(const struct nsec3_chain_fixed *first, - const struct nsec3_chain_fixed *e) -{ - if (first->hash != e->hash || - first->iterations != e->iterations || - first->salt_length != e->salt_length || - first->next_length != e->next_length || - memcmp(first + 1, e + 1, first->salt_length) != 0) - return (ISC_TRUE); - return (ISC_FALSE); -} - -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 isc_boolean_t -checknext(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 (ISC_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); - fprintf(stderr, "Break in NSEC3 chain at: %.*s\n", - (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); - fprintf(stderr, "Expected: %.*s\n", (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); - fprintf(stderr, "Found: %.*s\n", (int) isc_buffer_usedlength(&b), buf); - - return (ISC_FALSE); -} - -#define EXPECTEDANDFOUND "Expected and found NSEC3 chains not equal\n" - -static isc_result_t -verify_nsec3_chains(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(expected_chains, 1)) != NULL) { - isc_heap_delete(expected_chains, 1); - if (f == NULL) - f = isc_heap_element(found_chains, 1); - if (f != NULL) { - isc_heap_delete(found_chains, 1); - - /* - * Check that they match. - */ - if (chain_equal(e, f)) { - free_element(mctx, f); - f = NULL; - } else { - if (result == ISC_R_SUCCESS) - fprintf(stderr, EXPECTEDANDFOUND); - result = ISC_R_FAILURE; - /* - * Attempt to resync found_chain. - */ - while (f != NULL && !chain_compare(e, f)) { - free_element(mctx, f); - f = isc_heap_element(found_chains, 1); - if (f != NULL) - isc_heap_delete(found_chains, 1); - if (f != NULL && chain_equal(e, f)) { - free_element(mctx, f); - f = NULL; - break; - } - } - } - } else if (result == ISC_R_SUCCESS) { - fprintf(stderr, EXPECTEDANDFOUND); - result = ISC_R_FAILURE; - } - if (first == NULL || newchain(first, e)) { - if (prev != NULL) { - if (!checknext(prev, first)) - result = ISC_R_FAILURE; - if (prev != first) - free_element(mctx, prev); - } - if (first != NULL) - free_element(mctx, first); - prev = first = e; - continue; - } - if (!checknext(prev, e)) - result = ISC_R_FAILURE; - if (prev != first) - free_element(mctx, prev); - prev = e; - } - if (prev != NULL) { - if (!checknext(prev, first)) - result = ISC_R_FAILURE; - if (prev != first) - free_element(mctx, prev); - } - if (first != NULL) - free_element(mctx, first); - do { - if (f != NULL) { - if (result == ISC_R_SUCCESS) { - fprintf(stderr, EXPECTEDANDFOUND); - result = ISC_R_FAILURE; - } - free_element(mctx, f); - } - f = isc_heap_element(found_chains, 1); - if (f != NULL) - isc_heap_delete(found_chains, 1); - } while (f != NULL); - - return (result); -} - -static isc_result_t -verifyemptynodes(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin, - isc_mem_t *mctx, dns_name_t *name, dns_name_t *prevname, - isc_boolean_t isdelegation, dns_rdataset_t *nsec3paramset) -{ - dns_namereln_t reln; - int order; - unsigned int labels, nlabels, i; - dns_name_t suffix; - isc_result_t result = ISC_R_SUCCESS, tresult; - - reln = dns_name_fullcompare(prevname, name, &order, &labels); - if (order >= 0) - return (result); - - 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)) { - tresult = verifynsec3s(db, ver, origin, mctx, - &suffix, nsec3paramset, - isdelegation, ISC_TRUE, - NULL, 0); - if (result == ISC_R_SUCCESS && - tresult != ISC_R_SUCCESS) - result = tresult; - } - } - } - return (result); -} - -/*% - * Verify that certain things are sane: - * - * The apex has a DNSKEY record with at least one KSK, and at least - * one ZSK if the -x flag was not used. - * - * The DNSKEY record was signed with at least one of the KSKs in this - * set. - * - * The rest of the zone was signed with at least one of the ZSKs - * present in the DNSKEY RRSET. - */ -void -verifyzone(dns_db_t *db, dns_dbversion_t *ver, - dns_name_t *origin, isc_mem_t *mctx, - isc_boolean_t ignore_kskflag, isc_boolean_t keyset_kskonly) -{ - char algbuf[80]; - dns_dbiterator_t *dbiter = NULL; - dns_dbnode_t *node = NULL, *nextnode = NULL; - dns_fixedname_t fname, fnextname, fprevname, fzonecut; - dns_name_t *name, *nextname, *prevname, *zonecut; - dns_rdata_dnskey_t dnskey; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_t keyset, soaset; - dns_rdataset_t keysigs, soasigs; - dns_rdataset_t nsecset, nsecsigs; - dns_rdataset_t nsec3paramset, nsec3paramsigs; - int i; - isc_boolean_t done = ISC_FALSE; - isc_boolean_t first = ISC_TRUE; - isc_boolean_t goodksk = ISC_FALSE; - isc_boolean_t goodzsk = ISC_FALSE; - isc_result_t result, vresult = ISC_R_UNSET; - 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]; - - result = isc_heap_create(mctx, chain_compare, NULL, 1024, - &expected_chains); - check_result(result, "isc_heap_create()"); - result = isc_heap_create(mctx, chain_compare, NULL, 1024, - &found_chains); - check_result(result, "isc_heap_create()"); - - result = dns_db_findnode(db, origin, ISC_FALSE, &node); - if (result != ISC_R_SUCCESS) - fatal("failed to find the zone's origin: %s", - isc_result_totext(result)); - - dns_rdataset_init(&keyset); - dns_rdataset_init(&keysigs); - dns_rdataset_init(&soaset); - dns_rdataset_init(&soasigs); - dns_rdataset_init(&nsecset); - dns_rdataset_init(&nsecsigs); - dns_rdataset_init(&nsec3paramset); - dns_rdataset_init(&nsec3paramsigs); - result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey, - 0, 0, &keyset, &keysigs); - if (result != ISC_R_SUCCESS) - fatal("Zone contains no DNSSEC keys\n"); - - result = dns_db_findrdataset(db, node, ver, dns_rdatatype_soa, - 0, 0, &soaset, &soasigs); - if (result != ISC_R_SUCCESS) - fatal("Zone contains no SOA record\n"); - - result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec, - 0, 0, &nsecset, &nsecsigs); - if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) - fatal("NSEC lookup failed\n"); - - result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param, - 0, 0, &nsec3paramset, &nsec3paramsigs); - if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) - fatal("NSEC3PARAM lookup failed\n"); - - if (!dns_rdataset_isassociated(&keysigs)) - fatal("DNSKEY is not signed (keys offline or inactive?)\n"); - - if (!dns_rdataset_isassociated(&soasigs)) - fatal("SOA is not signed (keys offline or inactive?)\n"); - - if (dns_rdataset_isassociated(&nsecset) && - !dns_rdataset_isassociated(&nsecsigs)) - fatal("NSEC is not signed (keys offline or inactive?)\n"); - - if (dns_rdataset_isassociated(&nsec3paramset) && - !dns_rdataset_isassociated(&nsec3paramsigs)) - fatal("NSEC3PARAM is not signed (keys offline or inactive?)\n"); - - if (!dns_rdataset_isassociated(&nsecset) && - !dns_rdataset_isassociated(&nsec3paramset)) - fatal("No valid NSEC/NSEC3 chain for testing\n"); - - dns_db_detachnode(db, &node); - - memset(revoked_ksk, 0, sizeof(revoked_ksk)); - memset(revoked_zsk, 0, sizeof(revoked_zsk)); - memset(standby_ksk, 0, sizeof(standby_ksk)); - memset(standby_zsk, 0, sizeof(standby_zsk)); - memset(ksk_algorithms, 0, sizeof(ksk_algorithms)); - memset(zsk_algorithms, 0, sizeof(zsk_algorithms)); - memset(bad_algorithms, 0, sizeof(bad_algorithms)); - memset(act_algorithms, 0, sizeof(act_algorithms)); - - /* - * 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). - */ - for (result = dns_rdataset_first(&keyset); - result == ISC_R_SUCCESS; - result = dns_rdataset_next(&keyset)) { - dns_rdataset_current(&keyset, &rdata); - result = dns_rdata_tostruct(&rdata, &dnskey, NULL); - check_result(result, "dns_rdata_tostruct"); - - if ((dnskey.flags & DNS_KEYOWNER_ZONE) == 0) - ; - else if ((dnskey.flags & DNS_KEYFLAG_REVOKE) != 0) { - if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 && - !dns_dnssec_selfsigns(&rdata, origin, &keyset, - &keysigs, ISC_FALSE, - mctx)) { - char namebuf[DNS_NAME_FORMATSIZE]; - char buffer[1024]; - isc_buffer_t buf; - - dns_name_format(origin, namebuf, - sizeof(namebuf)); - isc_buffer_init(&buf, buffer, sizeof(buffer)); - result = dns_rdata_totext(&rdata, NULL, &buf); - check_result(result, "dns_rdata_totext"); - fatal("revoked KSK is not self signed:\n" - "%s DNSKEY %.*s", namebuf, - (int)isc_buffer_usedlength(&buf), buffer); - } - if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 && - revoked_ksk[dnskey.algorithm] != 255) - revoked_ksk[dnskey.algorithm]++; - else if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0 && - revoked_zsk[dnskey.algorithm] != 255) - revoked_zsk[dnskey.algorithm]++; - } else if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0) { - if (dns_dnssec_selfsigns(&rdata, origin, &keyset, - &keysigs, ISC_FALSE, mctx)) { - if (ksk_algorithms[dnskey.algorithm] != 255) - ksk_algorithms[dnskey.algorithm]++; - goodksk = ISC_TRUE; - } else { - if (standby_ksk[dnskey.algorithm] != 255) - standby_ksk[dnskey.algorithm]++; - } - } else if (dns_dnssec_selfsigns(&rdata, origin, &keyset, - &keysigs, ISC_FALSE, mctx)) { - if (zsk_algorithms[dnskey.algorithm] != 255) - zsk_algorithms[dnskey.algorithm]++; - goodzsk = ISC_TRUE; - } else if (dns_dnssec_signs(&rdata, origin, &soaset, - &soasigs, ISC_FALSE, mctx)) { - if (zsk_algorithms[dnskey.algorithm] != 255) - zsk_algorithms[dnskey.algorithm]++; - } else { - if (standby_zsk[dnskey.algorithm] != 255) - standby_zsk[dnskey.algorithm]++; - } - dns_rdata_freestruct(&dnskey); - dns_rdata_reset(&rdata); - } - dns_rdataset_disassociate(&keysigs); - dns_rdataset_disassociate(&soaset); - dns_rdataset_disassociate(&soasigs); - if (dns_rdataset_isassociated(&nsecsigs)) - dns_rdataset_disassociate(&nsecsigs); - if (dns_rdataset_isassociated(&nsec3paramsigs)) - dns_rdataset_disassociate(&nsec3paramsigs); - - if (ignore_kskflag ) { - if (!goodksk && !goodzsk) - fatal("No self-signed DNSKEY found."); - } else if (!goodksk) - fatal("No self-signed KSK DNSKEY found. Supply an active\n" - "key with the KSK flag set, or use '-P'."); - - fprintf(stderr, "Verifying the zone using the following algorithms:"); - for (i = 0; i < 256; i++) { - if (ignore_kskflag) - act_algorithms[i] = (ksk_algorithms[i] != 0 || - zsk_algorithms[i] != 0) ? 1 : 0; - else - act_algorithms[i] = ksk_algorithms[i] != 0 ? 1 : 0; - if (act_algorithms[i] != 0) { - dns_secalg_format(i, algbuf, sizeof(algbuf)); - fprintf(stderr, " %s", algbuf); - } - } - fprintf(stderr, ".\n"); - - if (!ignore_kskflag && !keyset_kskonly) { - for (i = 0; i < 256; i++) { - /* - * The counts should both be zero or both be non-zero. - * Mark the algorithm as bad if this is not met. - */ - if ((ksk_algorithms[i] != 0) == - (zsk_algorithms[i] != 0)) - continue; - dns_secalg_format(i, algbuf, sizeof(algbuf)); - fprintf(stderr, "Missing %s for algorithm %s\n", - (ksk_algorithms[i] != 0) - ? "ZSK" - : "self-signed KSK", - algbuf); - bad_algorithms[i] = 1; - } - } - - /* - * Check that all the other records were signed by keys that are - * present in the DNSKEY RRSET. - */ - - name = dns_fixedname_initname(&fname); - nextname = dns_fixedname_initname(&fnextname); - dns_fixedname_init(&fprevname); - prevname = NULL; - dns_fixedname_init(&fzonecut); - zonecut = NULL; - - result = dns_db_createiterator(db, DNS_DB_NONSEC3, &dbiter); - check_result(result, "dns_db_createiterator()"); - - result = dns_dbiterator_first(dbiter); - check_result(result, "dns_dbiterator_first()"); - - while (!done) { - isc_boolean_t isdelegation = ISC_FALSE; - - result = dns_dbiterator_current(dbiter, &node, name); - check_dns_dbiterator_current(result); - if (!dns_name_issubdomain(name, origin)) { - check_no_nsec(name, node, db, ver); - dns_db_detachnode(db, &node); - result = dns_dbiterator_next(dbiter); - if (result == ISC_R_NOMORE) - done = ISC_TRUE; - else - check_result(result, "dns_dbiterator_next()"); - continue; - } - if (is_delegation(db, ver, origin, name, node, NULL)) { - zonecut = dns_fixedname_name(&fzonecut); - dns_name_copy(name, zonecut, NULL); - isdelegation = ISC_TRUE; - } else if (has_dname(db, ver, node)) { - zonecut = dns_fixedname_name(&fzonecut); - dns_name_copy(name, zonecut, NULL); - } - nextnode = NULL; - result = dns_dbiterator_next(dbiter); - while (result == ISC_R_SUCCESS) { - result = dns_dbiterator_current(dbiter, &nextnode, - nextname); - check_dns_dbiterator_current(result); - if (!dns_name_issubdomain(nextname, origin) || - (zonecut != NULL && - dns_name_issubdomain(nextname, zonecut))) - { - check_no_nsec(nextname, nextnode, db, ver); - dns_db_detachnode(db, &nextnode); - result = dns_dbiterator_next(dbiter); - continue; - } - if (is_empty(db, ver, nextnode)) { - dns_db_detachnode(db, &nextnode); - result = dns_dbiterator_next(dbiter); - continue; - } - dns_db_detachnode(db, &nextnode); - break; - } - if (result == ISC_R_NOMORE) { - done = ISC_TRUE; - nextname = origin; - } else if (result != ISC_R_SUCCESS) - fatal("iterating through the database failed: %s", - isc_result_totext(result)); - result = verifynode(db, ver, origin, mctx, name, node, - isdelegation, &keyset, act_algorithms, - bad_algorithms, &nsecset, &nsec3paramset, - nextname); - if (vresult == ISC_R_UNSET) - vresult = ISC_R_SUCCESS; - if (vresult == ISC_R_SUCCESS && result != ISC_R_SUCCESS) - vresult = result; - if (prevname != NULL) { - result = verifyemptynodes(db, ver, origin, mctx, name, - prevname, isdelegation, - &nsec3paramset); - } else - prevname = dns_fixedname_name(&fprevname); - dns_name_copy(name, prevname, NULL); - if (vresult == ISC_R_SUCCESS && result != ISC_R_SUCCESS) - vresult = result; - dns_db_detachnode(db, &node); - } - - dns_dbiterator_destroy(&dbiter); - - result = dns_db_createiterator(db, DNS_DB_NSEC3ONLY, &dbiter); - check_result(result, "dns_db_createiterator()"); - - for (result = dns_dbiterator_first(dbiter); - result == ISC_R_SUCCESS; - result = dns_dbiterator_next(dbiter) ) { - result = dns_dbiterator_current(dbiter, &node, name); - check_dns_dbiterator_current(result); - result = verifynode(db, ver, origin, mctx, name, node, - ISC_FALSE, &keyset, act_algorithms, - bad_algorithms, NULL, NULL, NULL); - check_result(result, "verifynode"); - record_found(db, ver, mctx, name, node, &nsec3paramset); - dns_db_detachnode(db, &node); - } - dns_dbiterator_destroy(&dbiter); - - dns_rdataset_disassociate(&keyset); - if (dns_rdataset_isassociated(&nsecset)) - dns_rdataset_disassociate(&nsecset); - if (dns_rdataset_isassociated(&nsec3paramset)) - dns_rdataset_disassociate(&nsec3paramset); - - result = verify_nsec3_chains(mctx); - if (vresult == ISC_R_UNSET) - vresult = ISC_R_SUCCESS; - if (result != ISC_R_SUCCESS && vresult == ISC_R_SUCCESS) - vresult = result; - isc_heap_destroy(&expected_chains); - isc_heap_destroy(&found_chains); - - /* - * If we made it this far, we have what we consider a properly signed - * zone. Set the good flag. - */ - for (i = 0; i < 256; i++) { - if (bad_algorithms[i] != 0) { - if (first) - fprintf(stderr, "The zone is not fully signed " - "for the following algorithms:"); - dns_secalg_format(i, algbuf, sizeof(algbuf)); - fprintf(stderr, " %s", algbuf); - first = ISC_FALSE; - } - } - if (!first) { - fprintf(stderr, ".\n"); - fatal("DNSSEC completeness test failed."); - } - - if (vresult != ISC_R_SUCCESS) - fatal("DNSSEC completeness test failed (%s).", - dns_result_totext(vresult)); - - if (goodksk || ignore_kskflag) { - /* - * Print the success summary. - */ - fprintf(stderr, "Zone fully signed:\n"); - for (i = 0; i < 256; i++) { - if ((ksk_algorithms[i] != 0) || - (standby_ksk[i] != 0) || - (revoked_ksk[i] != 0) || - (zsk_algorithms[i] != 0) || - (standby_zsk[i] != 0) || - (revoked_zsk[i] != 0)) { - dns_secalg_format(i, algbuf, sizeof(algbuf)); - fprintf(stderr, "Algorithm: %s: KSKs: " - "%u active, %u stand-by, %u revoked\n", - algbuf, ksk_algorithms[i], - standby_ksk[i], revoked_ksk[i]); - fprintf(stderr, "%*sZSKs: " - "%u active, %u %s, %u revoked\n", - (int) strlen(algbuf) + 13, "", - zsk_algorithms[i], - standby_zsk[i], - keyset_kskonly ? "present" : "stand-by", - revoked_zsk[i]); - } - } - } -} - isc_boolean_t isoptarg(const char *arg, char **argv, void(*usage)(void)) { if (!strcasecmp(isc_commandline_argument, arg)) { diff --git a/bin/dnssec/dnssectool.h b/bin/dnssec/dnssectool.h index 189292b713..0ce686c393 100644 --- a/bin/dnssec/dnssectool.h +++ b/bin/dnssec/dnssectool.h @@ -18,11 +18,6 @@ #include #include -#define check_dns_dbiterator_current(result) \ - check_result((result == DNS_R_NEWORIGIN) ? ISC_R_SUCCESS : result, \ - "dns_dbiterator_current()") - - typedef void (fatalcallback_t)(void); ISC_PLATFORM_NORETURN_PRE void @@ -41,10 +36,6 @@ vbprintf(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3); ISC_PLATFORM_NORETURN_PRE void version(const char *program) ISC_PLATFORM_NORETURN_POST; -void -type_format(const dns_rdatatype_t type, char *cp, unsigned int size); -#define TYPE_FORMATSIZE 20 - void sig_format(dns_rdata_rrsig_t *sig, char *cp, unsigned int size); #define SIG_FORMATSIZE (DNS_NAME_FORMATSIZE + DNS_SECALG_FORMATSIZE + sizeof("65535")) @@ -80,22 +71,6 @@ isc_boolean_t key_collision(dst_key_t *key, dns_name_t *name, const char *dir, isc_mem_t *mctx, isc_boolean_t *exact); -isc_boolean_t -is_delegation(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin, - dns_name_t *name, dns_dbnode_t *node, isc_uint32_t *ttlp); - -/*% - * Return ISC_TRUE if version 'ver' of database 'db' contains a DNAME RRset at - * 'node'; return ISC_FALSE otherwise. - */ -isc_boolean_t -has_dname(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node); - -void -verifyzone(dns_db_t *db, dns_dbversion_t *ver, - dns_name_t *origin, isc_mem_t *mctx, - isc_boolean_t ignore_kskflag, isc_boolean_t keyset_kskonly); - isc_boolean_t isoptarg(const char *arg, char **argv, void (*usage)(void)); diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in index 22d2a313ee..85c422d8d3 100644 --- a/lib/dns/Makefile.in +++ b/lib/dns/Makefile.in @@ -77,7 +77,8 @@ DNSOBJS = acl.@O@ adb.@O@ badcache.@O@ byaddr.@O@ \ sdlz.@O@ soa.@O@ ssu.@O@ ssu_external.@O@ \ stats.@O@ tcpmsg.@O@ time.@O@ timer.@O@ tkey.@O@ \ tsec.@O@ tsig.@O@ ttl.@O@ update.@O@ validator.@O@ \ - version.@O@ view.@O@ xfrin.@O@ zone.@O@ zonekey.@O@ zt.@O@ + version.@O@ view.@O@ xfrin.@O@ zone.@O@ zonekey.@O@ \ + zoneverify.@O@ zt.@O@ PORTDNSOBJS = client.@O@ ecdb.@O@ OBJS= @DNSTAPOBJS@ ${DNSOBJS} ${OTHEROBJS} ${DSTOBJS} \ @@ -119,7 +120,8 @@ DNSSRCS = acl.c adb.c badcache. byaddr.c \ sdb.c sdlz.c soa.c ssu.c ssu_external.c \ stats.c tcpmsg.c time.c timer.c tkey.c \ tsec.c tsig.c ttl.c update.c validator.c \ - version.c view.c xfrin.c zone.c zonekey.c zt.c ${OTHERSRCS} + version.c view.c xfrin.c zone.c zoneverify.c \ + zonekey.c zt.c ${OTHERSRCS} PORTDNSSRCS = client.c ecdb.c SRCS = ${DSTSRCS} ${DNSSRCS} ${PORTDNSSRCS} @DNSTAPSRCS@ @GEOIPLINKSRCS@ diff --git a/lib/dns/include/dns/Makefile.in b/lib/dns/include/dns/Makefile.in index ab63d83277..91ca18b5ad 100644 --- a/lib/dns/include/dns/Makefile.in +++ b/lib/dns/include/dns/Makefile.in @@ -31,7 +31,7 @@ HEADERS = acl.h adb.h badcache.h bit.h byaddr.h \ sdb.h sdlz.h secalg.h secproto.h soa.h ssu.h stats.h \ tcpmsg.h time.h timer.h tkey.h tsec.h tsig.h ttl.h types.h \ update.h validator.h version.h view.h xfrin.h \ - zone.h zonekey.h zt.h + zone.h zonekey.h zoneverify.h zt.h GENHEADERS = @DNSTAP_PB_C_H@ enumclass.h enumtype.h rdatastruct.h diff --git a/lib/dns/include/dns/zoneverify.h b/lib/dns/include/dns/zoneverify.h new file mode 100644 index 0000000000..0e491c23dd --- /dev/null +++ b/lib/dns/include/dns/zoneverify.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/zoneverify.h */ + +#include + +#include + +ISC_LANG_BEGINDECLS + +/*% + * Verify that certain things are sane: + * + * The apex has a DNSKEY record with at least one KSK, and at least + * one ZSK if the -x flag was not used. + * + * The DNSKEY record was signed with at least one of the KSKs in this + * set. + * + * The rest of the zone was signed with at least one of the ZSKs + * present in the DNSKEY RRSET. + */ +isc_result_t +dns_zoneverify_dnssec(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + dns_name_t *origin, isc_mem_t *mctx, + isc_boolean_t ignore_kskflag, + isc_boolean_t keyset_kskonly); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index ce44becd63..796dccb9e2 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -1334,6 +1334,7 @@ dns_zonemgr_shutdown dns_zonemgr_unreachable dns_zonemgr_unreachableadd dns_zonemgr_unreachabledel +dns_zoneverify_dnssec dns_zt_apply dns_zt_asyncload dns_zt_attach diff --git a/lib/dns/zoneverify.c b/lib/dns/zoneverify.c new file mode 100644 index 0000000000..52c37a72a7 --- /dev/null +++ b/lib/dns/zoneverify.c @@ -0,0 +1,1989 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://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 + +typedef struct vctx { + isc_mem_t * mctx; + dns_zone_t * zone; + dns_db_t * db; + dns_dbversion_t * ver; + dns_name_t * origin; + isc_boolean_t goodksk; + isc_boolean_t 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 { + isc_uint8_t hash; + isc_uint8_t salt_length; + isc_uint8_t next_length; + isc_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]; + */ +}; + +/*% + * 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); +} + +/*% + * If invoked from a standalone tool, print a message described by 'fmt' and + * the variable arguments following it to stderr. + */ +static void +zoneverify_print(const vctx_t *vctx, const char *fmt, ...) { + va_list ap; + + if (vctx->zone != NULL) { + return; + } + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static isc_boolean_t +is_delegation(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node, + isc_uint32_t *ttlp) +{ + dns_rdataset_t nsset; + isc_result_t result; + + if (dns_name_equal(name, vctx->origin)) { + return (ISC_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 (ISC_TF(result == ISC_R_SUCCESS)); +} + +/*% + * Return ISC_TRUE if version 'ver' of database 'db' contains a DNAME RRset at + * 'node'; return ISC_FALSE otherwise. + */ +static isc_boolean_t +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 (ISC_TF(result == ISC_R_SUCCESS)); +} + +static isc_boolean_t +goodsig(const vctx_t *vctx, dns_rdata_t *sigrdata, const dns_name_t *name, + dns_rdataset_t *keyrdataset, dns_rdataset_t *rdataset) +{ + dns_rdata_dnskey_t key; + dns_rdata_rrsig_t sig; + dst_key_t *dstkey = NULL; + isc_result_t result; + + result = dns_rdata_tostruct(sigrdata, &sig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + for (result = dns_rdataset_first(keyrdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(keyrdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(keyrdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &key, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + result = dns_dnssec_keyfromrdata(vctx->origin, &rdata, + vctx->mctx, &dstkey); + if (result != ISC_R_SUCCESS) { + return (ISC_FALSE); + } + if (sig.algorithm != key.algorithm || + sig.keyid != dst_key_id(dstkey) || + !dns_name_equal(&sig.signer, vctx->origin)) + { + dst_key_free(&dstkey); + continue; + } + result = dns_dnssec_verify(name, rdataset, dstkey, ISC_FALSE, + 0, vctx->mctx, sigrdata, NULL); + dst_key_free(&dstkey); + if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) { + return (ISC_TRUE); + } + } + return (ISC_FALSE); +} + +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 (dns_rdata_compare(&rdata, &tmprdata) != 0) { + 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, &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, + "Warning: Found unexpected signatures " + "for %s/%s", + namebuf, typebuf); + } + if (dns_rdataset_isassociated(&sigrdataset)) { + dns_rdataset_disassociate(&sigrdataset); + } + dns_rdatasetiter_destroy(&rdsiter); + + return (ISC_R_SUCCESS); +} + +static isc_boolean_t +chain_compare(void *arg1, void *arg2) { + struct nsec3_chain_fixed *e1 = arg1, *e2 = arg2; + size_t len; + + /* + * Do each element in turn to get a stable sort. + */ + if (e1->hash < e2->hash) { + return (ISC_TRUE); + } + if (e1->hash > e2->hash) { + return (ISC_FALSE); + } + if (e1->iterations < e2->iterations) { + return (ISC_TRUE); + } + if (e1->iterations > e2->iterations) { + return (ISC_FALSE); + } + if (e1->salt_length < e2->salt_length) { + return (ISC_TRUE); + } + if (e1->salt_length > e2->salt_length) { + return (ISC_FALSE); + } + if (e1->next_length < e2->next_length) { + return (ISC_TRUE); + } + if (e1->next_length > e2->next_length) { + return (ISC_FALSE); + } + len = e1->salt_length + 2 * e1->next_length; + if (memcmp(e1 + 1, e2 + 1, len) < 0) { + return (ISC_TRUE); + } + return (ISC_FALSE); +} + +static isc_boolean_t +chain_equal(const struct nsec3_chain_fixed *e1, + const struct nsec3_chain_fixed *e2) +{ + size_t len; + + if (e1->hash != e2->hash) { + return (ISC_FALSE); + } + if (e1->iterations != e2->iterations) { + return (ISC_FALSE); + } + if (e1->salt_length != e2->salt_length) { + return (ISC_FALSE); + } + if (e1->next_length != e2->next_length) { + return (ISC_FALSE); + } + len = e1->salt_length + 2 * e1->next_length; + if (memcmp(e1 + 1, e2 + 1, len) != 0) { + return (ISC_FALSE); + } + return (ISC_TRUE); +} + +static isc_result_t +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; + size_t len; + unsigned char *cp; + isc_result_t result; + + len = sizeof(*element) + nsec3->next_length * 2 + nsec3->salt_length; + + element = isc_mem_get(vctx->mctx, len); + if (element == NULL) { + return (ISC_R_NOMEMORY); + } + 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); + result = isc_heap_insert(chains, element); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "isc_heap_insert failed: %s", + isc_result_totext(result)); + isc_mem_put(vctx->mctx, element, len); + } + 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; + + /* + * 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, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (nsec3.hash == nsec3param->hash && + nsec3.next_length == rhsize && + nsec3.iterations == nsec3param->iterations && + nsec3.salt_length == nsec3param->salt_length && + memcmp(nsec3.salt, nsec3param->salt, + nsec3param->salt_length) == 0) + { + break; + } + } + 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. + */ + result = record_nsec3(vctx, rawhash, &nsec3, vctx->expected_chains); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "record_nsec3(): %s", + isc_result_totext(result)); + return (result); + } + + /* + * 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 isc_boolean_t +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 (ISC_TRUE); + } + } + return (ISC_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. + */ + result = record_nsec3(vctx, owner, &nsec3, vctx->found_chains); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "record_nsec3(): %s", + isc_result_totext(result)); + goto cleanup; + } + } + result = ISC_R_SUCCESS; + + cleanup: + dns_rdataset_disassociate(&rdataset); + return (result); +} + +static isc_result_t +isoptout(const vctx_t *vctx, const dns_rdata_t *nsec3rdata, + isc_boolean_t *optout) +{ + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec3_t nsec3; + dns_rdata_nsec3param_t nsec3param; + 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); + + result = dns_rdata_tostruct(nsec3rdata, &nsec3param, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + 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, ISC_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 = ISC_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 = ISC_TF((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, isc_boolean_t delegation, + isc_boolean_t 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; + dns_dbnode_t *node = NULL; + unsigned char rawhash[NSEC3_MAX_HASH_LENGTH]; + size_t rhsize = sizeof(rawhash); + isc_boolean_t optout = ISC_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); + } + + result = isoptout(vctx, rdata, &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 choosen + * 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, ISC_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, isc_boolean_t delegation, + isc_boolean_t 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, dns_rdataset_t *keyrdataset) +{ + unsigned char set_algorithms[256]; + 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; + int i; + + dns_rdataset_init(&sigrdataset); + result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 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 (i = 0; i < 256; i++) { + if (vctx->act_algorithms[i] != 0) { + vctx->bad_algorithms[i] = 1; + } + } + result = ISC_R_SUCCESS; + goto done; + } + + memset(set_algorithms, 0, sizeof(set_algorithms)); + 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, keyrdataset, rdataset)) { + set_algorithms[sig.algorithm] = 1; + } + } + result = ISC_R_SUCCESS; + + if (memcmp(set_algorithms, vctx->act_algorithms, + sizeof(set_algorithms))) + { + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); + for (i = 0; i < 256; 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, + isc_boolean_t delegation, dns_rdataset_t *keyrdataset, + dns_rdataset_t *nsecset, dns_rdataset_t *nsec3paramset, + const dns_name_t *nextname, isc_result_t *vresult) +{ + unsigned char types[8192]; + unsigned int maxtype = 0; + dns_rdataset_t rdataset; dns_rdatasetiter_t *rdsiter = NULL; + isc_result_t result, tvresult; + + REQUIRE(vresult != NULL || (nsecset == NULL && nsec3paramset == NULL)); + + memset(types, 0, sizeof(types)); + result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 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 existance 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, + keyrdataset); + 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, + ISC_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, isc_boolean_t *empty) { + dns_rdatasetiter_t *rdsiter = NULL; + isc_result_t result; + + result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 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 = ISC_TF(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) { + isc_boolean_t nsec_exists = ISC_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 = ISC_TRUE; + } + + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + + return (nsec_exists ? ISC_R_FAILURE : ISC_R_SUCCESS); +} + +static isc_boolean_t +newchain(const struct nsec3_chain_fixed *first, + const struct nsec3_chain_fixed *e) +{ + if (first->hash != e->hash || + first->iterations != e->iterations || + first->salt_length != e->salt_length || + first->next_length != e->next_length || + memcmp(first + 1, e + 1, first->salt_length) != 0) + { + return (ISC_TRUE); + } + return (ISC_FALSE); +} + +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 isc_boolean_t +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 (ISC_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 (ISC_FALSE); +} + +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)) { + 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)) { + 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 || newchain(first, e)) { + if (prev != NULL) { + if (!checknext(vctx, prev, first)) { + result = ISC_R_FAILURE; + } + if (prev != first) { + free_element(mctx, prev); + } + } + if (first != NULL) { + free_element(mctx, first); + } + prev = first = e; + continue; + } + if (!checknext(vctx, prev, e)) { + result = ISC_R_FAILURE; + } + if (prev != first) { + free_element(mctx, prev); + } + prev = e; + } + if (prev != NULL) { + if (!checknext(vctx, prev, first)) { + result = ISC_R_FAILURE; + } + if (prev != first) { + free_element(mctx, prev); + } + } + if (first != NULL) { + free_element(mctx, first); + } + 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, isc_boolean_t 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; + + *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, ISC_TRUE, + NULL, 0, &tvresult); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (*vresult == ISC_R_SUCCESS) { + *vresult = tvresult; + } + } + } + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +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) +{ + isc_result_t result; + + memset(vctx, 0, sizeof(*vctx)); + + vctx->mctx = mctx; + vctx->zone = zone; + vctx->db = db; + vctx->ver = ver; + vctx->origin = origin; + vctx->goodksk = ISC_FALSE; + vctx->goodzsk = ISC_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; + result = isc_heap_create(mctx, chain_compare, NULL, 1024, + &vctx->expected_chains); + if (result != ISC_R_SUCCESS) { + return (result); + } + + vctx->found_chains = NULL; + result = isc_heap_create(mctx, chain_compare, NULL, 1024, + &vctx->found_chains); + if (result != ISC_R_SUCCESS) { + isc_heap_destroy(&vctx->expected_chains); + return (result); + } + + return (result); +} + +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, ISC_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. + * + * 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 *rdata, isc_boolean_t is_ksk) +{ + unsigned char *active_keys, *standby_keys; + isc_boolean_t *goodkey; + + 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); + + if (dns_dnssec_selfsigns(rdata, vctx->origin, &vctx->keyset, + &vctx->keysigs, ISC_FALSE, vctx->mctx)) + { + if (active_keys[dnskey->algorithm] != 255) { + active_keys[dnskey->algorithm]++; + } + *goodkey = ISC_TRUE; + } else if (!is_ksk && + dns_dnssec_signs(rdata, vctx->origin, &vctx->soaset, + &vctx->soasigs, ISC_FALSE, vctx->mctx)) + { + if (active_keys[dnskey->algorithm] != 255) { + active_keys[dnskey->algorithm]++; + } + } else { + if (standby_keys[dnskey->algorithm] != 255) { + standby_keys[dnskey->algorithm]++; + } + } +} + +/*% + * 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; + isc_boolean_t 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 = ISC_TF((dnskey.flags & DNS_KEYFLAG_KSK) != 0); + + if ((dnskey.flags & DNS_KEYOWNER_ZONE) == 0) { + ; + } else if ((dnskey.flags & DNS_KEYFLAG_REVOKE) != 0) { + if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 && + !dns_dnssec_selfsigns(&rdata, vctx->origin, + &vctx->keyset, + &vctx->keysigs, ISC_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] != 255) + { + vctx->revoked_ksk[dnskey.algorithm]++; + } else if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0 && + vctx->revoked_zsk[dnskey.algorithm] != 255) + { + 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, isc_boolean_t ignore_kskflag, + isc_boolean_t keyset_kskonly) +{ + char algbuf[DNS_SECALG_FORMATSIZE]; + int i; + + zoneverify_print(vctx, + "Verifying the zone using the following algorithms:"); + + for (i = 0; i < 256; 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)); + zoneverify_print(vctx, " %s", algbuf); + } + } + zoneverify_print(vctx, ".\n"); + + if (ignore_kskflag || keyset_kskonly) { + return; + } + + for (i = 0; i < 256; 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; + isc_boolean_t done = ISC_FALSE; + isc_result_t tvresult; + 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; + + 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)); + return (result); + } + + 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) { + isc_boolean_t isdelegation = ISC_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 = ISC_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, NULL); + isdelegation = ISC_TRUE; + } else if (has_dname(vctx, node)) { + zonecut = dns_fixedname_name(&fzonecut); + dns_name_copy(name, zonecut, NULL); + } + nextnode = NULL; + result = dns_dbiterator_next(dbiter); + while (result == ISC_R_SUCCESS) { + isc_boolean_t 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 = ISC_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, + &vctx->keyset, &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, NULL); + 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, ISC_FALSE, &vctx->keyset, + 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: + dns_dbiterator_destroy(&dbiter); + + return (result); +} + +static isc_result_t +check_bad_algorithms(const vctx_t *vctx) { + char algbuf[DNS_SECALG_FORMATSIZE]; + isc_boolean_t first = ISC_TRUE; + int i; + + for (i = 0; i < 256; i++) { + if (vctx->bad_algorithms[i] == 0) { + continue; + } + if (first) { + zoneverify_print(vctx, + "The zone is not fully signed for " + "the following algorithms:"); + } + dns_secalg_format(i, algbuf, sizeof(algbuf)); + zoneverify_print(vctx, " %s", algbuf); + first = ISC_FALSE; + } + + if (!first) { + zoneverify_print(vctx, ".\n"); + } + + return (first ? ISC_R_SUCCESS : ISC_R_FAILURE); +} + +static void +print_summary(const vctx_t *vctx, isc_boolean_t keyset_kskonly) { + char algbuf[DNS_SECALG_FORMATSIZE]; + int i; + + zoneverify_print(vctx, "Zone fully signed:\n"); + for (i = 0; i < 256; 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)); + zoneverify_print(vctx, + "Algorithm: %s: KSKs: " + "%u active, %u stand-by, %u revoked\n", + algbuf, vctx->ksk_algorithms[i], + vctx->standby_ksk[i], + vctx->revoked_ksk[i]); + zoneverify_print(vctx, + "%*sZSKs: " + "%u active, %u %s, %u revoked\n", + (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, isc_mem_t *mctx, + isc_boolean_t ignore_kskflag, + isc_boolean_t keyset_kskonly) +{ + isc_result_t result, vresult = ISC_R_UNSET; + vctx_t vctx; + + result = vctx_init(&vctx, mctx, zone, db, ver, origin); + if (result != ISC_R_SUCCESS) { + return (result); + } + + 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 self-signed DNSKEY found"); + result = ISC_R_FAILURE; + goto done; + } + } else if (!vctx.goodksk) { + zoneverify_log_error(&vctx, "No self-signed KSK DNSKEY found"); + result = ISC_R_FAILURE; + goto done; + } + + determine_active_algorithms(&vctx, ignore_kskflag, keyset_kskonly); + + 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); + if (result != ISC_R_SUCCESS) { + zoneverify_print(&vctx, "DNSSEC completeness test failed.\n"); + goto done; + } + + result = vresult; + if (result != ISC_R_SUCCESS) { + zoneverify_print(&vctx, + "DNSSEC completeness test failed (%s).\n", + dns_result_totext(result)); + goto done; + } + + if (vctx.goodksk || ignore_kskflag) { + print_summary(&vctx, keyset_kskonly); + } + + done: + vctx_destroy(&vctx); + + return (result); +} diff --git a/util/copyrights b/util/copyrights index 3d2aa50290..d948050617 100644 --- a/util/copyrights +++ b/util/copyrights @@ -3081,6 +3081,7 @@ ./lib/dns/include/dns/xfrin.h C 1999,2000,2001,2003,2004,2005,2006,2007,2009,2013,2016,2018 ./lib/dns/include/dns/zone.h C 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018 ./lib/dns/include/dns/zonekey.h C 2001,2004,2005,2006,2007,2016,2018 +./lib/dns/include/dns/zoneverify.h C 2018 ./lib/dns/include/dns/zt.h C 1999,2000,2001,2002,2004,2005,2006,2007,2011,2016,2017,2018 ./lib/dns/include/dst/Makefile.in MAKE 1998,1999,2000,2001,2004,2007,2012,2015,2016,2018 ./lib/dns/include/dst/dst.h C 2000,2001,2002,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018 @@ -3421,6 +3422,7 @@ ./lib/dns/zone.c C 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018 ./lib/dns/zone_p.h C 2018 ./lib/dns/zonekey.c C 2001,2003,2004,2005,2007,2016,2018 +./lib/dns/zoneverify.c C 2018 ./lib/dns/zt.c C 1999,2000,2001,2002,2004,2005,2006,2007,2011,2012,2013,2014,2015,2016,2017,2018 ./lib/irs/Atffile X 2016,2018 ./lib/irs/Kyuafile X 2017,2018