From: Jelte Jansen Date: Mon, 21 Apr 2008 09:58:50 +0000 (+0000) Subject: new tool to verify a DNSSEC signed zone (no context, only internals X-Git-Tag: release-1.3.0~66 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=738c51b3ceac0287c30e33bbf4ae55d7c525df2a;p=thirdparty%2Fldns.git new tool to verify a DNSSEC signed zone (no context, only internals are checked: is every RRSET signed, do the signatures actually verify with any of the keys at the apex, etc). Not every possible problem will be detected, but it's a start --- diff --git a/examples/Makefile.in b/examples/Makefile.in index 8b2c7fdb..37de52c5 100644 --- a/examples/Makefile.in +++ b/examples/Makefile.in @@ -46,7 +46,8 @@ MAIN_SOURCES = ldns-read-zone.c \ ldns-keyfetcher.c \ ldns-notify.c \ ldns-testns.c \ - ldns-compare-zones.c + ldns-compare-zones.c \ + ldns-verifyzone.c OTHER_SOURCES = ldns-testpkts.c PROGRAMS=$(MAIN_SOURCES:.c=) diff --git a/examples/ldns-verifyzone.c b/examples/ldns-verifyzone.c new file mode 100644 index 00000000..bae1f030 --- /dev/null +++ b/examples/ldns-verifyzone.c @@ -0,0 +1,419 @@ +/* + * read a zone file from disk and prints it, one RR per line + * + * (c) NLnetLabs 2005-2008 + * + * See the file LICENSE for the license + */ + +#include "config.h" +#include +#include + +#include + +#include + +#ifdef HAVE_SSL +#include +#endif + +int verbosity = 3; + +bool +ldns_rr_list_contains_name(const ldns_rr_list *rr_list, + const ldns_rdf *name) +{ + size_t i; + for (i = 0; i < ldns_rr_list_rr_count(rr_list); i++) { + if (ldns_dname_compare(name, + ldns_rr_owner(ldns_rr_list_rr(rr_list, + i)) + ) + ) { + return true; + } + } + return false; +} + +void +print_type(ldns_rr_type type) +{ + const ldns_rr_descriptor *descriptor; + + descriptor = ldns_rr_descript(type); + if (descriptor && descriptor->_name) { + fprintf(stdout, "%s", descriptor->_name); + } else { + fprintf(stdout, "TYPE%u", + type); + } + +} + +ldns_dnssec_zone * +create_dnssec_zone(ldns_zone *orig_zone) +{ + size_t i; + ldns_dnssec_zone *dnssec_zone; + + dnssec_zone = ldns_dnssec_zone_new(); + if (ldns_dnssec_zone_add_rr(dnssec_zone, ldns_zone_soa(orig_zone)) != + LDNS_STATUS_OK) { + fprintf(stderr, "Error adding SOA to dnssec zone, skipping record\n"); + } + + for (i = 0; i < ldns_rr_list_rr_count(ldns_zone_rrs(orig_zone)); i++) { + if (ldns_dnssec_zone_add_rr(dnssec_zone, + ldns_rr_list_rr(ldns_zone_rrs(orig_zone), + i)) != + LDNS_STATUS_OK) { + fprintf(stderr, "Error adding RR to dnssec zone"); + fprintf(stderr, ", skipping record:\n"); + ldns_rr_print(stderr, + ldns_rr_list_rr(ldns_zone_rrs(orig_zone), i)); + } + } + + return dnssec_zone; +} + +ldns_status +verify_dnssec_rrset(ldns_dnssec_rrsets *rrset, ldns_rr_list *keys) +{ + ldns_rr_list *rrset_rrs; + ldns_dnssec_rrs *cur_rr, *cur_sig; + ldns_status status; + ldns_rr_list *good_keys; + ldns_status result = LDNS_STATUS_OK; + + rrset_rrs = ldns_rr_list_new(); + cur_rr = rrset->rrs; + while(cur_rr) { + ldns_rr_list_push_rr(rrset_rrs, cur_rr->rr); + cur_rr = cur_rr->next; + } + cur_sig = rrset->signatures; + if (cur_sig) { + while (cur_sig) { + good_keys = ldns_rr_list_new(); + status = ldns_verify_rrsig_keylist(rrset_rrs, + cur_sig->rr, + keys, + good_keys); + if (status != LDNS_STATUS_OK) { + printf("Error: %s", ldns_get_errorstr_by_id(status)); + printf(" for "); + ldns_rdf_print(stdout, ldns_rr_owner(rrset->rrs->rr)); + printf("\t"); + print_type(rrset->type); + printf("\n"); + if (result == LDNS_STATUS_OK) { + result = status; + } +#ifdef HAVE_SSL + if (status == LDNS_STATUS_SSL_ERR) { + ERR_load_crypto_strings(); + ERR_print_errors_fp(stdout); + } +#endif + } + ldns_rr_list_free(good_keys); + cur_sig = cur_sig->next; + } + } else { + printf("Error: no signatures for "); + ldns_rdf_print(stdout, ldns_rr_owner(rrset->rrs->rr)); + printf("\t"); + print_type(rrset->type); + printf("\n"); + } + return result; +} + +ldns_status +verify_single_rr(ldns_rr *rr, + ldns_dnssec_rrs *signature_rrs, + ldns_rr_list *keys) +{ + ldns_rr_list *rrset_rrs; + ldns_rr_list *good_keys; + ldns_dnssec_rrs *cur_sig; + ldns_status status; + ldns_status result = LDNS_STATUS_OK; + + rrset_rrs = ldns_rr_list_new(); + ldns_rr_list_push_rr(rrset_rrs, rr); + + cur_sig = signature_rrs; + while (cur_sig) { + good_keys = ldns_rr_list_new(); + status = ldns_verify_rrsig_keylist(rrset_rrs, + cur_sig->rr, + keys, + good_keys); + if (status != LDNS_STATUS_OK) { + printf("Error: %s ", ldns_get_errorstr_by_id(status)); + if (result == LDNS_STATUS_OK) { + result = status; + } + printf("for "); + ldns_rdf_print(stdout, ldns_rr_owner(rr)); + printf("\t"); + print_type(ldns_rr_get_type(rr)); + printf("\n"); +#ifdef HAVE_SSL + if (status == LDNS_STATUS_SSL_ERR) { + ERR_load_crypto_strings(); + ERR_print_errors_fp(stdout); + } +#endif + } + ldns_rr_list_free(good_keys); + cur_sig = cur_sig->next; + } + + return result; + +} + +ldns_status +verify_dnssec_name(ldns_dnssec_name *name, + ldns_rr_list *keys, + ldns_rr_list *glue_rrs) +{ + ldns_status result = LDNS_STATUS_OK; + ldns_status status; + ldns_dnssec_rrsets *cur_rrset; + + printf("Checking "); + ldns_rdf_print(stdout, name->name); + printf("\n"); + + if (ldns_rr_list_contains_name(glue_rrs, name->name)) { + /* glue */ + cur_rrset = name->rrsets; + while (cur_rrset) { + if (cur_rrset->signatures) { + printf("Error: "); + ldns_rdf_print(stdout, name->name); + printf("\t"); + print_type(cur_rrset->type); + printf(" has signature(s), but is glue\n"); + result = LDNS_STATUS_ERR; + } + cur_rrset = cur_rrset->next; + } + if (name->nsec) { + printf("Error: "); + ldns_rdf_print(stdout, name->name); + printf("\t"); + print_type(cur_rrset->type); + printf(" has an NSEC(3), but is glue\n"); + result = LDNS_STATUS_ERR; + } + } else { + /* not glue, do real verify */ + cur_rrset = name->rrsets; + while(cur_rrset) { + status = verify_dnssec_rrset(cur_rrset, keys); + if (status != LDNS_STATUS_OK && result == LDNS_STATUS_OK) { + result = status; + } + cur_rrset = cur_rrset->next; + } + + if (name->nsec) { + if (name->nsec_signatures) { + status = verify_single_rr(name->nsec, + name->nsec_signatures, + keys); + } else { + printf("Error: the NSEC(3) record of "); + ldns_rdf_print(stdout, name->name); + printf(" has no signatures\n"); + if (result == LDNS_STATUS_OK) { + result = LDNS_STATUS_ERR; + } + } + } else { + printf("Error: there is no NSEC(3) for "); + ldns_rdf_print(stdout, name->name); + printf("\n"); + if (result == LDNS_STATUS_OK) { + result = LDNS_STATUS_ERR; + } + } + } + return result; +} + +ldns_status +verify_dnssec_zone(ldns_dnssec_zone *dnssec_zone, + ldns_rdf *zone_name, + ldns_rr_list *glue_rrs) +{ + ldns_rr_list *keys; + ldns_rbnode_t *cur_node; + ldns_dnssec_rrsets *cur_key_rrset; + ldns_dnssec_rrs *cur_key; + ldns_dnssec_name *cur_name; + ldns_status status; + ldns_status result = LDNS_STATUS_OK; + + keys = ldns_rr_list_new(); + cur_key_rrset = ldns_dnssec_zone_find_rrset(dnssec_zone, + zone_name, + LDNS_RR_TYPE_DNSKEY); + if (!cur_key_rrset || !cur_key_rrset->rrs) { + printf("No DNSKEY records at zone apex\n"); + result = LDNS_STATUS_ERR; + } else { + cur_key = cur_key_rrset->rrs; + while (cur_key) { + ldns_rr_list_push_rr(keys, cur_key->rr); + cur_key = cur_key->next; + } + + cur_node = ldns_rbtree_first(dnssec_zone->names); + if (cur_node == LDNS_RBTREE_NULL) { + printf("Empty zone?\n"); + } + while (cur_node != LDNS_RBTREE_NULL) { + cur_name = (ldns_dnssec_name *) cur_node->data; + status = verify_dnssec_name(cur_name, keys, glue_rrs); + if (status != LDNS_STATUS_OK && result == LDNS_STATUS_OK) { + result = status; + } + cur_node = ldns_rbtree_next(cur_node); + } + } + + ldns_rr_list_free(keys); + return result; +} + +int +main(int argc, char **argv) +{ + char *filename; + FILE *fp; + ldns_zone *z; + int line_nr = 0; + int c; + bool canonicalize = false; + bool sort = false; + bool strip = false; + ldns_status s; + size_t i; + ldns_rr_list *stripped_list; + ldns_rr *cur_rr; + ldns_dnssec_zone *dnssec_zone; + ldns_status result = LDNS_STATUS_ERR; + ldns_rr_list *glue_rrs; + + while ((c = getopt(argc, argv, "chsvz")) != -1) { + switch(c) { + case 'c': + canonicalize = true; + break; + case 'h': + printf("Usage: %s [OPTIONS] \n", argv[0]); + printf("\tReads the zonefile and checks for DNSSEC errors.\n"); + printf("\nIt checks whether NSEC(3)s are present,"); + printf(" and verifies all signatures\n"); + printf("It does NOT check the NSEC(3) chain itself\n"); + printf("\nOPTIONS:\n"); + printf("\t-h show this text\n"); + printf("\t-v shows the version and exits\n"); + printf("\nif no file is given standard input is read\n"); + exit(EXIT_SUCCESS); + break; + case 's': + strip = true; + break; + case 'v': + printf("read zone version %s (ldns version %s)\n", + LDNS_VERSION, ldns_version()); + exit(EXIT_SUCCESS); + break; + case 'z': + canonicalize = true; + sort = true; + break; + } + } + + argc -= optind; + argv += optind; + + if (argc == 0) { + fp = stdin; + } else { + filename = argv[0]; + + fp = fopen(filename, "r"); + if (!fp) { + fprintf(stderr, "Unable to open %s: %s\n", filename, strerror(errno)); + exit(EXIT_FAILURE); + } + } + + s = ldns_zone_new_frm_fp_l(&z, fp, NULL, 0, LDNS_RR_CLASS_IN, &line_nr); + + if (strip) { + stripped_list = ldns_rr_list_new(); + while ((cur_rr = ldns_rr_list_pop_rr(ldns_zone_rrs(z)))) { + if (ldns_rr_get_type(cur_rr) == LDNS_RR_TYPE_RRSIG || + ldns_rr_get_type(cur_rr) == LDNS_RR_TYPE_NSEC + ) { + + ldns_rr_free(cur_rr); + } else { + ldns_rr_list_push_rr(stripped_list, cur_rr); + } + } + ldns_rr_list_free(ldns_zone_rrs(z)); + ldns_zone_set_rrs(z, stripped_list); + } + + glue_rrs = ldns_zone_glue_rr_list(z); + printf("GLUE RRS:\n"); + ldns_rr_list_print(stdout, glue_rrs); + + if (s == LDNS_STATUS_OK) { + if (canonicalize) { + ldns_rr2canonical(ldns_zone_soa(z)); + for (i = 0; i < ldns_rr_list_rr_count(ldns_zone_rrs(z)); i++) { + ldns_rr2canonical(ldns_rr_list_rr(ldns_zone_rrs(z), i)); + } + } + if (sort) { + ldns_zone_sort(z); + } + + /*ldns_zone_print(stdout, z);*/ + dnssec_zone = create_dnssec_zone(z); + + result = verify_dnssec_zone(dnssec_zone, + ldns_rr_owner(ldns_zone_soa(z)), + glue_rrs); + + if (result == LDNS_STATUS_OK) { + printf("Zone is verified and complete\n"); + } else { + printf("There were errors in the zone\n"); + } + + ldns_zone_deep_free(z); + } else { + fprintf(stderr, "%s at %d\n", + ldns_get_errorstr_by_id(s), + line_nr); + exit(EXIT_FAILURE); + } + fclose(fp); + + exit(result); +}