From ad127d839d2e7aa542939a8a336691407e23397e Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Mon, 25 Jun 2012 13:57:32 +1000 Subject: [PATCH] 3341. [func] New "dnssec-verify" command checks a signed zone to ensure correctness of signatures and of NSEC/NSEC3 chains. [RT #23673] --- CHANGES | 4 + bin/dnssec/.gitignore | 1 + bin/dnssec/Makefile.in | 22 +- bin/dnssec/dnssec-signzone.c | 511 +-------- bin/dnssec/dnssec-verify.8 | 97 ++ bin/dnssec/dnssec-verify.c | 325 ++++++ bin/dnssec/dnssec-verify.docbook | 186 +++ bin/dnssec/dnssec-verify.html | 117 ++ bin/dnssec/dnssectool.c | 1258 +++++++++++++++++++++ bin/dnssec/dnssectool.h | 13 + bin/tests/system/autosign/ns3/keygen.sh | 228 ++-- bin/tests/system/conf.sh.in | 1 + bin/tests/system/verify/.gitignore | 7 + bin/tests/system/verify/clean.sh | 9 + bin/tests/system/verify/setup.sh | 23 + bin/tests/system/verify/tests.sh | 81 ++ bin/tests/system/verify/zones/genzones.sh | 175 +++ bin/tests/system/verify/zones/unsigned.db | 17 + configure.in | 2 +- doc/arm/Bv9ARM-book.xml | 1 + doc/design/verify | 25 + lib/dns/include/dns/db.h | 4 + lib/dns/include/dns/nsec.h | 22 + lib/dns/nsec.c | 93 +- lib/dns/nsec3.c | 61 +- lib/isc/include/isc/heap.h | 2 + 26 files changed, 2578 insertions(+), 707 deletions(-) create mode 100644 bin/dnssec/dnssec-verify.8 create mode 100644 bin/dnssec/dnssec-verify.c create mode 100644 bin/dnssec/dnssec-verify.docbook create mode 100644 bin/dnssec/dnssec-verify.html create mode 100644 bin/tests/system/verify/.gitignore create mode 100644 bin/tests/system/verify/clean.sh create mode 100644 bin/tests/system/verify/setup.sh create mode 100644 bin/tests/system/verify/tests.sh create mode 100644 bin/tests/system/verify/zones/genzones.sh create mode 100644 bin/tests/system/verify/zones/unsigned.db create mode 100644 doc/design/verify diff --git a/CHANGES b/CHANGES index 10fac85868..decceac508 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +3341. [func] New "dnssec-verify" command checks a signed zone + to ensure correctness of signatures and of NSEC/NSEC3 + chains. [RT #23673] + 3340. [func] Added new 'fast' zone file format, which is an image of a zone database that can be loaded directly into memory via mmap(), allowing much faster zone loading. diff --git a/bin/dnssec/.gitignore b/bin/dnssec/.gitignore index d8239a43dd..518d946497 100644 --- a/bin/dnssec/.gitignore +++ b/bin/dnssec/.gitignore @@ -6,4 +6,5 @@ dnssec-revoke dnssec-settime dnssec-signkey dnssec-signzone +dnssec-verify .libs diff --git a/bin/dnssec/Makefile.in b/bin/dnssec/Makefile.in index 6bfd162d8d..b15df0588a 100644 --- a/bin/dnssec/Makefile.in +++ b/bin/dnssec/Makefile.in @@ -13,7 +13,7 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: Makefile.in,v 1.42 2009/12/05 23:31:40 each Exp $ +# $Id: Makefile.in,v 1.42.332.1 2011/03/16 06:37:51 each Exp $ srcdir = @srcdir@ VPATH = @srcdir@ @@ -44,19 +44,23 @@ NOSYMLIBS = ${DNSLIBS} ${ISCNOSYMLIBS} @LIBS@ # Alphabetically TARGETS = dnssec-keygen@EXEEXT@ dnssec-signzone@EXEEXT@ \ dnssec-keyfromlabel@EXEEXT@ dnssec-dsfromkey@EXEEXT@ \ - dnssec-revoke@EXEEXT@ dnssec-settime@EXEEXT@ + dnssec-revoke@EXEEXT@ dnssec-settime@EXEEXT@ \ + dnssec-verify@EXEEXT@ OBJS = dnssectool.@O@ SRCS = dnssec-dsfromkey.c dnssec-keyfromlabel.c dnssec-keygen.c \ - dnssec-revoke.c dnssec-settime.c dnssec-signzone.c dnssectool.c + dnssec-revoke.c dnssec-settime.c dnssec-signzone.c \ + dnssec-verify.c dnssectool.c MANPAGES = dnssec-dsfromkey.8 dnssec-keyfromlabel.8 dnssec-keygen.8 \ - dnssec-revoke.8 dnssec-settime.8 dnssec-signzone.8 + dnssec-revoke.8 dnssec-settime.8 dnssec-signzone.8 \ + dnssec-verify.8 HTMLPAGES = dnssec-dsfromkey.html dnssec-keyfromlabel.html \ dnssec-keygen.html dnssec-revoke.html \ - dnssec-settime.html dnssec-signzone.html + dnssec-settime.html dnssec-signzone.html \ + dnssec-verify.html MANOBJS = ${MANPAGES} ${HTMLPAGES} @@ -82,6 +86,14 @@ dnssec-signzone@EXEEXT@: dnssec-signzone.@O@ ${OBJS} ${DEPLIBS} export BASEOBJS="dnssec-signzone.@O@ ${OBJS}"; \ ${FINALBUILDCMD} +dnssec-verify.@O@: dnssec-verify.c + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -DVERSION=\"${VERSION}\" \ + -c ${srcdir}/dnssec-verify.c + +dnssec-verify@EXEEXT@: dnssec-verify.@O@ ${OBJS} ${DEPLIBS} + export BASEOBJS="dnssec-verify.@O@ ${OBJS}"; \ + ${FINALBUILDCMD} + dnssec-revoke@EXEEXT@: dnssec-revoke.@O@ ${OBJS} ${DEPLIBS} ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ dnssec-revoke.@O@ ${OBJS} ${LIBS} diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c index 335e03b3a1..1038292b2f 100644 --- a/bin/dnssec/dnssec-signzone.c +++ b/bin/dnssec/dnssec-signzone.c @@ -189,10 +189,6 @@ static isc_boolean_t output_stdout = ISC_FALSE; static void sign(isc_task_t *task, isc_event_t *event); -#define check_dns_dbiterator_current(result) \ - check_result((result == DNS_R_NEWORIGIN) ? ISC_R_SUCCESS : result, \ - "dns_dbiterator_current()") - static void dumpnode(dns_name_t *name, dns_dbnode_t *node) { dns_rdataset_t rds; @@ -997,26 +993,6 @@ loadds(dns_name_t *name, isc_uint32_t ttl, dns_rdataset_t *dsset) { return (result); } -static isc_boolean_t -delegation(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, gorigin)) - return (ISC_FALSE); - - dns_rdataset_init(&nsset); - result = dns_db_findrdataset(gdb, node, gversion, 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)); -} - static isc_boolean_t secure(dns_name_t *name, dns_dbnode_t *node) { dns_rdataset_t dsset; @@ -1052,7 +1028,7 @@ signname(dns_dbnode_t *node, dns_name_t *name) { /* * Determine if this is a delegation point. */ - if (delegation(name, node, NULL)) + if (is_delegation(gdb, gversion, gorigin, name, node, NULL)) isdelegation = ISC_TRUE; /* @@ -1385,453 +1361,6 @@ postsign(void) { dns_dbiterator_destroy(&gdbiter); } -static isc_boolean_t -goodsig(dns_rdata_t *sigrdata, 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; - - dns_rdata_tostruct(sigrdata, &sig, NULL); - - 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); - dns_rdata_tostruct(&rdata, &key, NULL); - result = dns_dnssec_keyfromrdata(gorigin, &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, gorigin)) { - dst_key_free(&dstkey); - continue; - } - result = dns_dnssec_verify(name, rdataset, dstkey, ISC_FALSE, - mctx, sigrdata); - dst_key_free(&dstkey); - if (result == ISC_R_SUCCESS) - return(ISC_TRUE); - } - return (ISC_FALSE); -} - -static void -verifyset(dns_rdataset_t *rdataset, dns_name_t *name, dns_dbnode_t *node, - dns_rdataset_t *keyrdataset, unsigned char *ksk_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(gdb, node, gversion, 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 (ksk_algorithms[i] != 0) - bad_algorithms[i] = 1; - 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); - dns_rdata_tostruct(&rdata, &sig, NULL); - 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) || - (ksk_algorithms[sig.algorithm] == 0)) - continue; - if (goodsig(&rdata, name, keyrdataset, rdataset)) - set_algorithms[sig.algorithm] = 1; - } - dns_rdatasetiter_destroy(&rdsiter); - if (memcmp(set_algorithms, ksk_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 ((ksk_algorithms[i] != 0) && - (set_algorithms[i] == 0)) { - dns_secalg_format(i, algbuf, sizeof(algbuf)); - fprintf(stderr, "Missing %s signature for " - "%s %s\n", algbuf, namebuf, typebuf); - bad_algorithms[i] = 1; - } - } - dns_rdataset_disassociate(&sigrdataset); -} - -static void -verifynode(dns_name_t *name, dns_dbnode_t *node, isc_boolean_t delegation, - dns_rdataset_t *keyrdataset, unsigned char *ksk_algorithms, - unsigned char *bad_algorithms) -{ - dns_rdataset_t rdataset; - dns_rdatasetiter_t *rdsiter = NULL; - isc_result_t result; - - result = dns_db_allrdatasets(gdb, node, gversion, 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 (rdataset.type != dns_rdatatype_rrsig && - rdataset.type != dns_rdatatype_dnskey && - (!delegation || rdataset.type == dns_rdatatype_ds || - rdataset.type == dns_rdatatype_nsec)) { - verifyset(&rdataset, name, node, keyrdataset, - ksk_algorithms, bad_algorithms); - } - 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); -} - -/*% - * Verify that certain things are sane: - * - * The apex has a DNSKEY RRset 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 - * the DNSKEY RRset. - * - * The rest of the zone was signed with at least one of the ZSKs - * present in the DNSKEY RRset. - */ -static void -verifyzone(void) { - char algbuf[80]; - dns_dbiterator_t *dbiter = NULL; - dns_dbnode_t *node = NULL, *nextnode = NULL; - dns_fixedname_t fname, fnextname, fzonecut; - dns_name_t *name, *nextname, *zonecut; - dns_rdata_dnskey_t dnskey; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_t keyset, soaset; - dns_rdataset_t keysigs, soasigs; - int i; - isc_boolean_t done = ISC_FALSE; - isc_boolean_t first = ISC_TRUE; - isc_boolean_t goodksk = ISC_FALSE; - isc_result_t result; - 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]; - - if (disable_zone_check) - return; - - result = dns_db_findnode(gdb, gorigin, 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); - - result = dns_db_findrdataset(gdb, node, gversion, - dns_rdatatype_dnskey, - 0, 0, &keyset, &keysigs); - if (result != ISC_R_SUCCESS) - fatal("Zone contains no DNSSEC keys\n"); - - result = dns_db_findrdataset(gdb, node, gversion, - dns_rdatatype_soa, - 0, 0, &soaset, &soasigs); - dns_db_detachnode(gdb, &node); - if (result != ISC_R_SUCCESS) - fatal("Zone contains no SOA record\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"); - - 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)); - - /* - * 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, gorigin, &keyset, - &keysigs, ISC_FALSE, - mctx)) { - char namebuf[DNS_NAME_FORMATSIZE]; - char buffer[1024]; - isc_buffer_t buf; - - dns_name_format(gorigin, 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, gorigin, &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, gorigin, &keyset, - &keysigs, ISC_FALSE, - mctx)) { - if (zsk_algorithms[dnskey.algorithm] != 255) - zsk_algorithms[dnskey.algorithm]++; - } else if (dns_dnssec_signs(&rdata, gorigin, &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 (!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 (ksk_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 signing KSK", - algbuf); - bad_algorithms[i] = 1; - } - } - - /* - * Check that all the other records were signed by keys that are - * present in the DNSKEY RRSET. - */ - - dns_fixedname_init(&fname); - name = dns_fixedname_name(&fname); - dns_fixedname_init(&fnextname); - nextname = dns_fixedname_name(&fnextname); - dns_fixedname_init(&fzonecut); - zonecut = NULL; - - result = dns_db_createiterator(gdb, 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, gorigin)) { - dns_db_detachnode(gdb, &node); - result = dns_dbiterator_next(dbiter); - if (result == ISC_R_NOMORE) - done = ISC_TRUE; - else - check_result(result, "dns_dbiterator_next()"); - continue; - } - if (delegation(name, node, NULL)) { - zonecut = dns_fixedname_name(&fzonecut); - dns_name_copy(name, zonecut, NULL); - isdelegation = ISC_TRUE; - } - verifynode(name, node, isdelegation, &keyset, - ksk_algorithms, bad_algorithms); - result = dns_dbiterator_next(dbiter); - nextnode = NULL; - while (result == ISC_R_SUCCESS) { - result = dns_dbiterator_current(dbiter, &nextnode, - nextname); - check_dns_dbiterator_current(result); - if (!dns_name_issubdomain(nextname, gorigin) || - (zonecut != NULL && - dns_name_issubdomain(nextname, zonecut))) - { - dns_db_detachnode(gdb, &nextnode); - result = dns_dbiterator_next(dbiter); - continue; - } - dns_db_detachnode(gdb, &nextnode); - break; - } - if (result == ISC_R_NOMORE) { - done = ISC_TRUE; - } else if (result != ISC_R_SUCCESS) - fatal("iterating through the database failed: %s", - isc_result_totext(result)); - dns_db_detachnode(gdb, &node); - } - - dns_dbiterator_destroy(&dbiter); - - result = dns_db_createiterator(gdb, 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); - verifynode(name, node, ISC_FALSE, &keyset, - ksk_algorithms, bad_algorithms); - dns_db_detachnode(gdb, &node); - } - dns_dbiterator_destroy(&dbiter); - - dns_rdataset_disassociate(&keyset); - - /* - * 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 (goodksk || ignore_kskflag) { - /* - * Print the success summary. - */ - fprintf(stderr, "Zone signing complete:\n"); - for (i = 0; i < 256; i++) { - if ((ksk_algorithms[i] != 0) || - (standby_ksk[i] != 0) || - (revoked_zsk[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]); - } - } - } -} - /*% * Sign the apex of the zone. * Note the origin may not be the first node if there are out of zone @@ -1929,7 +1458,7 @@ assignwork(isc_task_t *task, isc_task_t *worker) { if (dns_name_issubdomain(name, gorigin) && (zonecut == NULL || !dns_name_issubdomain(name, zonecut))) { - if (delegation(name, node, NULL)) { + if (is_delegation(gdb, gversion, gorigin, name, node, NULL)) { dns_fixedname_init(&fzonecut); zonecut = dns_fixedname_name(&fzonecut); dns_name_copy(name, zonecut, NULL); @@ -2187,7 +1716,7 @@ nsecify(void) { if (dns_name_equal(name, gorigin)) remove_records(node, dns_rdatatype_nsec3param); - if (delegation(name, node, &nsttl)) { + if (is_delegation(gdb, gversion, gorigin, name, node, &nsttl)) { zonecut = dns_fixedname_name(&fzonecut); dns_name_copy(name, zonecut, NULL); if (generateds) @@ -2622,7 +2151,9 @@ nsec3ify(unsigned int hashalg, unsigned int iterations, result = dns_dbiterator_next(dbiter); continue; } - if (delegation(nextname, nextnode, &nsttl)) { + if (is_delegation(gdb, gversion, gorigin, + nextname, nextnode, &nsttl)) + { zonecut = dns_fixedname_name(&fzonecut); dns_name_copy(nextname, zonecut, NULL); if (generateds) @@ -2750,7 +2281,9 @@ nsec3ify(unsigned int hashalg, unsigned int iterations, result = dns_dbiterator_next(dbiter); continue; } - if (delegation(nextname, nextnode, NULL)) { + if (is_delegation(gdb, gversion, gorigin, + nextname, nextnode, NULL)) + { zonecut = dns_fixedname_name(&fzonecut); dns_name_copy(nextname, zonecut, NULL); if (OPTOUT(nsec3flags) && @@ -3469,9 +3002,10 @@ main(int argc, char *argv[]) { isc_boolean_t set_salt = ISC_FALSE; isc_boolean_t set_optout = ISC_FALSE; isc_boolean_t set_iter = ISC_FALSE; + isc_boolean_t nonsecify = ISC_FALSE; #define CMDLINE_FLAGS \ - "3:AaCc:Dd:E:e:f:FghH:i:I:j:K:k:L:l:m:n:N:o:O:PpRr:s:ST:tuUv:X:xz" + "3:AaCc:Dd:E:e:f:FghH:i:I:j:K:k:L:l:m:n:N:o:O:PpRr:s:ST:tuUv:X:xzZ:" /* * Process memory debugging argument first. @@ -3741,6 +3275,10 @@ main(int argc, char *argv[]) { fprintf(stderr, "%s: unhandled option -%c\n", program, isc_commandline_option); exit(1); + case 'Z': /* Undocumented test options */ + if (!strcmp(isc_commandline_argument, "nonsecify")) + nonsecify = ISC_TRUE; + break; } } @@ -3995,11 +3533,13 @@ main(int argc, char *argv[]) { remove_duplicates(); - if (IS_NSEC3) - nsec3ify(dns_hash_sha1, nsec3iter, salt, salt_length, - &hashlist); - else - nsecify(); + if (!nonsecify) { + if (IS_NSEC3) + nsec3ify(dns_hash_sha1, nsec3iter, salt, salt_length, + &hashlist); + else + nsecify(); + } if (!nokeys) { writeset("dsset-", dns_rdatatype_ds); @@ -4086,9 +3626,12 @@ main(int argc, char *argv[]) { isc_mem_put(mctx, tasks, ntasks * sizeof(isc_task_t *)); postsign(); TIME_NOW(&sign_finish); - verifyzone(); - if (outputformat == dns_masterformat_raw) { + if (!disable_zone_check) + verifyzone(gdb, gversion, gorigin, mctx, + ignore_kskflag, keyset_kskonly); + + if (outputformat != dns_masterformat_text) { dns_masterrawheader_t header; dns_master_initrawheader(&header); if (rawversion == 0U) diff --git a/bin/dnssec/dnssec-verify.8 b/bin/dnssec/dnssec-verify.8 new file mode 100644 index 0000000000..c68cfa3c7c --- /dev/null +++ b/bin/dnssec/dnssec-verify.8 @@ -0,0 +1,97 @@ +.\" Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") +.\" +.\" Permission to use, copy, modify, and/or distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +.\" REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +.\" AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +.\" INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +.\" LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +.\" OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +.\" PERFORMANCE OF THIS SOFTWARE. +.\" +.\" $Id$ +.\" +.hy 0 +.ad l +.\" Title: dnssec\-verify +.\" Author: +.\" Generator: DocBook XSL Stylesheets v1.71.1 +.\" Date: April 12, 2012 +.\" Manual: BIND9 +.\" Source: BIND9 +.\" +.TH "DNSSEC\-VERIFY" "8" "April 12, 2012" "BIND9" "BIND9" +.\" disable hyphenation +.nh +.\" disable justification (adjust text to left margin only) +.ad l +.SH "NAME" +dnssec\-verify \- DNSSEC zone verification tool +.SH "SYNOPSIS" +.HP 14 +\fBdnssec\-verify\fR [\fB\-c\ \fR\fB\fIclass\fR\fR] [\fB\-E\ \fR\fB\fIengine\fR\fR] [\fB\-I\ \fR\fB\fIinput\-format\fR\fR] [\fB\-o\ \fR\fB\fIorigin\fR\fR] [\fB\-v\ \fR\fB\fIlevel\fR\fR] [\fB\-x\fR] [\fB\-z\fR] {zonefile} +.SH "DESCRIPTION" +.PP +\fBdnssec\-verify\fR +verifies that a zone is fully signed for each algorithm found in the DNSKEY RRset for the zone, and that the NSEC / NSEC3 chains are complete. +.SH "OPTIONS" +.PP +\-c \fIclass\fR +.RS 4 +Specifies the DNS class of the zone. +.RE +.PP +\-I \fIinput\-format\fR +.RS 4 +The format of the input zone file. Possible formats are +\fB"text"\fR +(default) and +\fB"raw"\fR. This option is primarily intended to be used for dynamic signed zones so that the dumped zone file in a non\-text format containing updates can be verified independently. The use of this option does not make much sense for non\-dynamic zones. +.RE +.PP +\-o \fIorigin\fR +.RS 4 +The zone origin. If not specified, the name of the zone file is assumed to be the origin. +.RE +.PP +\-v \fIlevel\fR +.RS 4 +Sets the debugging level. +.RE +.PP +\-x +.RS 4 +Only verify that the DNSKEY RRset is signed with key\-signing keys. Without this flag, it is assumed that the DNSKEY RRset will be signed by all active keys. When this flag is set, it will not be an error if the DNSKEY RRset is not signed by zone\-signing keys. This corresponds to the +\fB\-x\fR +option in +\fBdnssec\-signzone\fR. +.RE +.PP +\-z +.RS 4 +Ignore the KSK flag on the keys when determining whether the zone if correctly signed. Without this flag it is assumed that there will be a non\-revoked, self\-signed DNSKEY with the KSK flag set for each algorithm and that RRsets other than DNSKEY RRset will be signed with a different DNSKEY without the KSK flag set. +.sp +With this flag set, we only require that for each algorithm, there will be at least one non\-revoked, self\-signed DNSKEY, regardless of the KSK flag state, and that other RRsets will be signed by a non\-revoked key for the same algorithm that includes the self\-signed key; the same key may be used for both purposes. This corresponds to the +\fB\-z\fR +option in +\fBdnssec\-signzone\fR. +.RE +.PP +zonefile +.RS 4 +The file containing the zone to be signed. +.RE +.SH "SEE ALSO" +.PP +\fBdnssec\-signzone\fR(8), +BIND 9 Administrator Reference Manual, +RFC 4033. +.SH "AUTHOR" +.PP +Internet Systems Consortium +.SH "COPYRIGHT" +Copyright \(co 2012 Internet Systems Consortium, Inc. ("ISC") +.br diff --git a/bin/dnssec/dnssec-verify.c b/bin/dnssec/dnssec-verify.c new file mode 100644 index 0000000000..f996370a1b --- /dev/null +++ b/bin/dnssec/dnssec-verify.c @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: dnssec-verify.c,v 1.1.2.1 2011/03/16 06:37:51 each Exp $ */ + +/*! \file */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dnssectool.h" + +const char *program = "dnssec-verify"; +int verbose; + +static isc_stdtime_t now; +static isc_mem_t *mctx = NULL; +static isc_entropy_t *ectx = NULL; +static dns_masterformat_t inputformat = dns_masterformat_text; +static dns_db_t *gdb; /* The database */ +static dns_dbversion_t *gversion; /* The database version */ +static dns_rdataclass_t gclass; /* The class */ +static dns_name_t *gorigin; /* The database origin */ +static isc_boolean_t ignore_kskflag = ISC_FALSE; +static isc_boolean_t keyset_kskonly = ISC_FALSE; + +/*% + * Load the zone file from disk + */ +static void +loadzone(char *file, char *origin, dns_rdataclass_t rdclass, dns_db_t **db) { + isc_buffer_t b; + int len; + dns_fixedname_t fname; + dns_name_t *name; + isc_result_t result; + + len = strlen(origin); + isc_buffer_init(&b, origin, len); + isc_buffer_add(&b, len); + + dns_fixedname_init(&fname); + name = dns_fixedname_name(&fname); + result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) + fatal("failed converting name '%s' to dns format: %s", + origin, isc_result_totext(result)); + + result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone, + rdclass, 0, NULL, db); + check_result(result, "dns_db_create()"); + + result = dns_db_load2(*db, file, inputformat); + if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) + fatal("failed loading zone from '%s': %s", + file, isc_result_totext(result)); +} + +ISC_PLATFORM_NORETURN_PRE static void +usage(void) ISC_PLATFORM_NORETURN_POST; + +static void +usage(void) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, "\t%s [options] zonefile [keys]\n", program); + + fprintf(stderr, "\n"); + + fprintf(stderr, "Version: %s\n", VERSION); + + fprintf(stderr, "Options: (default value in parenthesis) \n"); + fprintf(stderr, "\t-v debuglevel (0)\n"); + fprintf(stderr, "\t-o origin:\n"); + fprintf(stderr, "\t\tzone origin (name of zonefile)\n"); + fprintf(stderr, "\t-I format:\n"); + fprintf(stderr, "\t\tfile format of input zonefile (text)\n"); + fprintf(stderr, "\t-c class (IN)\n"); + fprintf(stderr, "\t-E engine:\n"); +#ifdef USE_PKCS11 + fprintf(stderr, "\t\tname of an OpenSSL engine to use " + "(default is \"pkcs11\")\n"); +#else + fprintf(stderr, "\t\tname of an OpenSSL engine to use\n"); +#endif + fprintf(stderr, "\t-x:\tDNSKEY record signed with KSKs only, " + "not ZSKs\n"); + fprintf(stderr, "\t-z:\tAll records signed with KSKs\n"); + exit(0); +} + +int +main(int argc, char *argv[]) { + char *origin = NULL, *file = NULL; + char *inputformatstr = NULL; + isc_result_t result; + isc_log_t *log = NULL; +#ifdef USE_PKCS11 + const char *engine = "pkcs11"; +#else + const char *engine = NULL; +#endif + char *classname = NULL; + dns_rdataclass_t rdclass; + char ch, *endp; + +#define CMDLINE_FLAGS \ + "m:o:I:c:E:v:xz" + + /* + * Process memory debugging argument first. + */ + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case 'm': + if (strcasecmp(isc_commandline_argument, "record") == 0) + isc_mem_debugging |= ISC_MEM_DEBUGRECORD; + if (strcasecmp(isc_commandline_argument, "trace") == 0) + isc_mem_debugging |= ISC_MEM_DEBUGTRACE; + if (strcasecmp(isc_commandline_argument, "usage") == 0) + isc_mem_debugging |= ISC_MEM_DEBUGUSAGE; + if (strcasecmp(isc_commandline_argument, "size") == 0) + isc_mem_debugging |= ISC_MEM_DEBUGSIZE; + if (strcasecmp(isc_commandline_argument, "mctx") == 0) + isc_mem_debugging |= ISC_MEM_DEBUGCTX; + break; + default: + break; + } + } + isc_commandline_reset = ISC_TRUE; + check_result(isc_app_start(), "isc_app_start"); + + result = isc_mem_create(0, 0, &mctx); + if (result != ISC_R_SUCCESS) + fatal("out of memory"); + + dns_result_register(); + + isc_commandline_errprint = ISC_FALSE; + + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case 'c': + classname = isc_commandline_argument; + break; + + case 'E': + engine = isc_commandline_argument; + break; + + case 'h': + usage(); + break; + + case 'I': + inputformatstr = isc_commandline_argument; + break; + + case 'm': + break; + + case 'o': + origin = isc_commandline_argument; + break; + + case 'v': + endp = NULL; + verbose = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') + fatal("verbose level must be numeric"); + break; + + case 'x': + keyset_kskonly = ISC_TRUE; + break; + + case 'z': + ignore_kskflag = ISC_TRUE; + break; + + case '?': + if (isc_commandline_option != '?') + fprintf(stderr, "%s: invalid argument -%c\n", + program, isc_commandline_option); + usage(); + break; + + default: + fprintf(stderr, "%s: unhandled option -%c\n", + program, isc_commandline_option); + exit(1); + } + } + + if (ectx == NULL) + setup_entropy(mctx, NULL, &ectx); + + result = isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE); + if (result != ISC_R_SUCCESS) + fatal("could not create hash context"); + + result = dst_lib_init2(mctx, ectx, engine, ISC_ENTROPY_BLOCKING); + if (result != ISC_R_SUCCESS) + fatal("could not initialize dst: %s", + isc_result_totext(result)); + + isc_stdtime_get(&now); + + rdclass = strtoclass(classname); + + setup_logging(verbose, mctx, &log); + + argc -= isc_commandline_index; + argv += isc_commandline_index; + + if (argc < 1) + usage(); + + file = argv[0]; + + argc -= 1; + argv += 1; + + if (origin == NULL) + origin = file; + + if (inputformatstr != NULL) { + if (strcasecmp(inputformatstr, "text") == 0) + inputformat = dns_masterformat_text; + else if (strcasecmp(inputformatstr, "raw") == 0) + inputformat = dns_masterformat_raw; + else + fatal("unknown file format: %s\n", inputformatstr); + } + + gdb = NULL; + fprintf(stderr, "Loading zone '%s' from file '%s'\n", origin, file); + loadzone(file, origin, rdclass, &gdb); + gorigin = dns_db_origin(gdb); + gclass = dns_db_class(gdb); + + gversion = NULL; + result = dns_db_newversion(gdb, &gversion); + check_result(result, "dns_db_newversion()"); + + verifyzone(gdb, gversion, gorigin, mctx, + ignore_kskflag, keyset_kskonly); + + dns_db_closeversion(gdb, &gversion, ISC_FALSE); + dns_db_detach(&gdb); + + cleanup_logging(&log); + dst_lib_destroy(); + isc_hash_destroy(); + cleanup_entropy(&ectx); + dns_name_destroy(); + if (verbose > 10) + isc_mem_stats(mctx, stdout); + isc_mem_destroy(&mctx); + + (void) isc_app_finish(); + + return (0); +} diff --git a/bin/dnssec/dnssec-verify.docbook b/bin/dnssec/dnssec-verify.docbook new file mode 100644 index 0000000000..771ecfe3f2 --- /dev/null +++ b/bin/dnssec/dnssec-verify.docbook @@ -0,0 +1,186 @@ +]> + + + + + + April 12, 2012 + + + + dnssec-verify + 8 + BIND9 + + + + dnssec-verify + DNSSEC zone verification tool + + + + + 2012 + Internet Systems Consortium, Inc. ("ISC") + + + + + + dnssec-verify + + + + + + + + zonefile + + + + + DESCRIPTION + dnssec-verify + verifies that a zone is fully signed for each algorithm found + in the DNSKEY RRset for the zone, and that the NSEC / NSEC3 + chains are complete. + + + + + OPTIONS + + + + -c class + + + Specifies the DNS class of the zone. + + + + + + -I input-format + + + The format of the input zone file. + Possible formats are "text" (default) + and "raw". + This option is primarily intended to be used for dynamic + signed zones so that the dumped zone file in a non-text + format containing updates can be verified independently. + The use of this option does not make much sense for + non-dynamic zones. + + + + + + -o origin + + + The zone origin. If not specified, the name of the zone file + is assumed to be the origin. + + + + + + -v level + + + Sets the debugging level. + + + + + + -x + + + Only verify that the DNSKEY RRset is signed with key-signing + keys. Without this flag, it is assumed that the DNSKEY RRset + will be signed by all active keys. When this flag is set, + it will not be an error if the DNSKEY RRset is not signed + by zone-signing keys. This corresponds to the + option in dnssec-signzone. + + + + + + -z + + + Ignore the KSK flag on the keys when determining whether + the zone if correctly signed. Without this flag it is + assumed that there will be a non-revoked, self-signed + DNSKEY with the KSK flag set for each algorithm and + that RRsets other than DNSKEY RRset will be signed with + a different DNSKEY without the KSK flag set. + + + With this flag set, we only require that for each algorithm, + there will be at least one non-revoked, self-signed DNSKEY, + regardless of the KSK flag state, and that other RRsets + will be signed by a non-revoked key for the same algorithm + that includes the self-signed key; the same key may be used + for both purposes. This corresponds to the + option in dnssec-signzone. + + + + + + zonefile + + + The file containing the zone to be signed. + + + + + + + + + SEE ALSO + + + dnssec-signzone8 + , + BIND 9 Administrator Reference Manual, + RFC 4033. + + + + + AUTHOR + Internet Systems Consortium + + + + diff --git a/bin/dnssec/dnssec-verify.html b/bin/dnssec/dnssec-verify.html new file mode 100644 index 0000000000..135556f510 --- /dev/null +++ b/bin/dnssec/dnssec-verify.html @@ -0,0 +1,117 @@ + + + + + +dnssec-verify + + +
+
+
+

Name

+

dnssec-verify — DNSSEC zone verification tool

+
+
+

Synopsis

+

dnssec-verify [-c class] [-E engine] [-I input-format] [-o origin] [-v level] [-x] [-z] {zonefile}

+
+
+

DESCRIPTION

+

dnssec-verify + verifies that a zone is fully signed for each algorithm found + in the DNSKEY RRset for the zone, and that the NSEC / NSEC3 + chains are complete. +

+
+
+

OPTIONS

+
+
-c class
+

+ Specifies the DNS class of the zone. +

+
-I input-format
+

+ The format of the input zone file. + Possible formats are "text" (default) + and "raw". + This option is primarily intended to be used for dynamic + signed zones so that the dumped zone file in a non-text + format containing updates can be verified independently. + The use of this option does not make much sense for + non-dynamic zones. +

+
-o origin
+

+ The zone origin. If not specified, the name of the zone file + is assumed to be the origin. +

+
-v level
+

+ Sets the debugging level. +

+
-x
+

+ Only verify that the DNSKEY RRset is signed with key-signing + keys. Without this flag, it is assumed that the DNSKEY RRset + will be signed by all active keys. When this flag is set, + it will not be an error if the DNSKEY RRset is not signed + by zone-signing keys. This corresponds to the -x + option in dnssec-signzone. +

+
-z
+
+

+ Ignore the KSK flag on the keys when determining whether + the zone if correctly signed. Without this flag it is + assumed that there will be a non-revoked, self-signed + DNSKEY with the KSK flag set for each algorithm and + that RRsets other than DNSKEY RRset will be signed with + a different DNSKEY without the KSK flag set. +

+

+ With this flag set, we only require that for each algorithm, + there will be at least one non-revoked, self-signed DNSKEY, + regardless of the KSK flag state, and that other RRsets + will be signed by a non-revoked key for the same algorithm + that includes the self-signed key; the same key may be used + for both purposes. This corresponds to the -z + option in dnssec-signzone. +

+
+
zonefile
+

+ The file containing the zone to be signed. +

+
+
+
+

SEE ALSO

+

+ dnssec-signzone(8), + BIND 9 Administrator Reference Manual, + RFC 4033. +

+
+
+

AUTHOR

+

Internet Systems Consortium +

+
+
+ diff --git a/bin/dnssec/dnssectool.c b/bin/dnssec/dnssectool.c index b58e070b15..9c61b0bffc 100644 --- a/bin/dnssec/dnssectool.c +++ b/bin/dnssec/dnssectool.c @@ -27,9 +27,11 @@ #include +#include #include #include #include +#include #include #include #include @@ -37,12 +39,19 @@ #include #include +#include +#include #include +#include #include #include #include +#include +#include #include #include +#include +#include #include #include #include @@ -50,6 +59,18 @@ #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; @@ -467,3 +488,1240 @@ 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)); +} + +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; + + dns_rdata_tostruct(sigrdata, &sig, NULL); + + 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); + dns_rdata_tostruct(&rdata, &key, NULL); + 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, + mctx, sigrdata); + dst_key_free(&dstkey); + if (result == ISC_R_SUCCESS) + 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 record 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 record 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); + memcpy(cp, nsec3->salt, nsec3->salt_length); + cp += nsec3->salt_length; + memcpy(cp, rawhash, nsec3->next_length); + cp += nsec3->next_length; + memcpy(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 record 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); + 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_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, 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); + + 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); + + 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 || 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_SUCCESS) { + result = match_nsec3(name, mctx, &nsec3param, &rdataset, + types, maxtype, rawhash, rhsize); + } else if (result == ISC_R_NOTFOUND && delegation) + result = ISC_R_SUCCESS; + 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, 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, 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); + dns_rdata_tostruct(&rdata, &sig, NULL); + 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, 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 *nextname, + 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(name, nextname, &order, &labels); + if (order >= 0) + return (result); + + nlabels = dns_name_countlabels(nextname); + + if (reln == dns_namereln_commonancestor || + reln == dns_namereln_contains) { + dns_name_init(&suffix, NULL); + for (i = labels + 1; i < nlabels; i++) { + dns_name_getlabelsequence(nextname, nlabels - i, i, + &suffix); + if (nsec3paramset != NULL && + dns_rdataset_isassociated(nsec3paramset)) { + tresult = verifynsec3s(db, ver, origin, mctx, + &suffix, nsec3paramset, + ISC_FALSE, 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, fzonecut; + dns_name_t *name, *nextname, *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. + */ + + dns_fixedname_init(&fname); + name = dns_fixedname_name(&fname); + dns_fixedname_init(&fnextname); + nextname = dns_fixedname_name(&fnextname); + 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; + } + 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; + result = verifyemptynodes(db, ver, origin, mctx, name, + nextname, &nsec3paramset); + 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_zsk[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]); + } + } + } +} diff --git a/bin/dnssec/dnssectool.h b/bin/dnssec/dnssectool.h index 0e34163d2f..bee8e80e9f 100644 --- a/bin/dnssec/dnssectool.h +++ b/bin/dnssec/dnssectool.h @@ -25,6 +25,11 @@ #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 @@ -81,4 +86,12 @@ 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); + +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); #endif /* DNSSEC_DNSSECTOOL_H */ diff --git a/bin/tests/system/autosign/ns3/keygen.sh b/bin/tests/system/autosign/ns3/keygen.sh index 20e19f25aa..662b2537cf 100644 --- a/bin/tests/system/autosign/ns3/keygen.sh +++ b/bin/tests/system/autosign/ns3/keygen.sh @@ -21,256 +21,228 @@ SYSTEMTESTTOP=../.. RANDFILE=../random.data -zone=secure.example -zonefile="${zone}.db" -infile="${zonefile}.in" +dumpit () { + echo "D:${debug}: dumping ${1}" + cat "${1}" | sed 's/^/D:/' +} + +setup () { + echo "I:setting up zone: $1" + debug="$1" + zone="$1" + zonefile="${zone}.db" + infile="${zonefile}.in" + n=`expr ${n:-0} + 1` +} + +setup secure.example cp $infile $zonefile -ksk=`$KEYGEN -3 -q -r $RANDFILE -fk $zone` -$KEYGEN -3 -q -r $RANDFILE $zone > /dev/null +ksk=`$KEYGEN -3 -q -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -3 -q -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. # # NSEC3/NSEC test zone # -zone=secure.nsec3.example -zonefile="${zone}.db" -infile="${zonefile}.in" +setup secure.nsec3.example cp $infile $zonefile -ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone` -$KEYGEN -q -3 -r $RANDFILE $zone > /dev/null +ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -q -3 -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. # # NSEC3/NSEC3 test zone # -zone=nsec3.nsec3.example -zonefile="${zone}.db" -infile="${zonefile}.in" +setup nsec3.nsec3.example cp $infile $zonefile -ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone` -$KEYGEN -q -3 -r $RANDFILE $zone > /dev/null +ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -q -3 -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. # # OPTOUT/NSEC3 test zone # -zone=optout.nsec3.example -zonefile="${zone}.db" -infile="${zonefile}.in" +setup optout.nsec3.example cp $infile $zonefile -ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone` -$KEYGEN -q -3 -r $RANDFILE $zone > /dev/null +ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -q -3 -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. # # A nsec3 zone (non-optout). # -zone=nsec3.example -zonefile="${zone}.db" -infile="${zonefile}.in" +setup nsec3.example cat $infile dsset-*.${zone}. > $zonefile -ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone` -$KEYGEN -q -3 -r $RANDFILE $zone > /dev/null +ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -q -3 -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. # # An NSEC3 zone, with NSEC3 parameters set prior to signing # -zone=autonsec3.example -zonefile="${zone}.db" -infile="${zonefile}.in" +setup autonsec3.example cat $infile > $zonefile -ksk=`$KEYGEN -G -q -3 -r $RANDFILE -fk $zone` +ksk=`$KEYGEN -G -q -3 -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out echo $ksk > ../autoksk.key -zsk=`$KEYGEN -G -q -3 -r $RANDFILE $zone` +zsk=`$KEYGEN -G -q -3 -r $RANDFILE $zone 2> kg.out` || dumpit kg.out echo $zsk > ../autozsk.key $DSFROMKEY $ksk.key > dsset-${zone}. # # OPTOUT/NSEC test zone # -zone=secure.optout.example -zonefile="${zone}.db" -infile="${zonefile}.in" +setup secure.optout.example cp $infile $zonefile -ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone` -$KEYGEN -q -3 -r $RANDFILE $zone > /dev/null +ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -q -3 -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. # # OPTOUT/NSEC3 test zone # -zone=nsec3.optout.example -zonefile="${zone}.db" -infile="${zonefile}.in" +setup nsec3.optout.example cp $infile $zonefile -ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone` -$KEYGEN -q -3 -r $RANDFILE $zone > /dev/null +ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -q -3 -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. # # OPTOUT/OPTOUT test zone # -zone=optout.optout.example -zonefile="${zone}.db" -infile="${zonefile}.in" +setup optout.optout.example cp $infile $zonefile -ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone` -$KEYGEN -q -3 -r $RANDFILE $zone > /dev/null +ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -q -3 -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. # # A optout nsec3 zone. # -zone=optout.example -zonefile="${zone}.db" -infile="${zonefile}.in" +setup optout.example cat $infile dsset-*.${zone}. > $zonefile -ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone` -$KEYGEN -q -3 -r $RANDFILE $zone > /dev/null +ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -q -3 -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. # # A RSASHA256 zone. # -zone=rsasha256.example -zonefile="${zone}.db" -infile="${zonefile}.in" +setup rsasha256.example cp $infile $zonefile -ksk=`$KEYGEN -q -a RSASHA256 -b 2048 -r $RANDFILE -fk $zone` -$KEYGEN -q -a RSASHA256 -b 1024 -r $RANDFILE $zone > /dev/null +ksk=`$KEYGEN -q -a RSASHA256 -b 2048 -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -q -a RSASHA256 -b 1024 -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. # # A RSASHA512 zone. # -zone=rsasha512.example -zonefile="${zone}.db" -infile="${zonefile}.in" +setup rsasha512.example cp $infile $zonefile -ksk=`$KEYGEN -q -a RSASHA512 -b 2048 -r $RANDFILE -fk $zone` -$KEYGEN -q -a RSASHA512 -b 1024 -r $RANDFILE $zone > /dev/null +ksk=`$KEYGEN -q -a RSASHA512 -b 2048 -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -q -a RSASHA512 -b 1024 -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. # # NSEC-only zone. # -zone=nsec.example -zonefile="${zone}.db" -infile="${zonefile}.in" +setup nsec.example cp $infile $zonefile -ksk=`$KEYGEN -q -r $RANDFILE -fk $zone` -$KEYGEN -q -r $RANDFILE $zone > /dev/null +ksk=`$KEYGEN -q -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -q -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. # # Signature refresh test zone. Signatures are set to expire long # in the past; they should be updated by autosign. # -zone=oldsigs.example -zonefile="${zone}.db" -infile="${zonefile}.in" +setup oldsigs.example cp $infile $zonefile -ksk=`$KEYGEN -q -r $RANDFILE -fk $zone` -$KEYGEN -q -r $RANDFILE $zone > /dev/null -$SIGNER -PS -s now-1y -e now-6mo -o $zone -f $zonefile $infile > /dev/null 2>&1 +$KEYGEN -q -r $RANDFILE -fk $zone > kg.out 2>&1 || dumpit kg.out +$KEYGEN -q -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out +$SIGNER -PS -s now-1y -e now-6mo -o $zone -f $zonefile $infile > s.out 2>&1 || dumpit s.out # # NSEC3->NSEC transition test zone. # -zone=nsec3-to-nsec.example -zonefile="${zone}.db" -infile="${zonefile}.in" -#cp $infile $zonefile -ksk=`$KEYGEN -q -a RSASHA512 -b 2048 -r $RANDFILE -fk $zone` -$KEYGEN -q -a RSASHA512 -b 1024 -r $RANDFILE $zone > /dev/null -$SIGNER -S -3 beef -A -o $zone -f $zonefile $infile > /dev/null 2>&1 +setup nsec3-to-nsec.example +$KEYGEN -q -a RSASHA512 -b 2048 -r $RANDFILE -fk $zone > kg.out 2>&1 || dumpit kg.out +$KEYGEN -q -a RSASHA512 -b 1024 -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out +$SIGNER -S -3 beef -A -o $zone -f $zonefile $infile > s.out 2>&1 || dumpit s.out # # secure-to-insecure transition test zone; used to test removal of # keys via nsupdate # -zone=secure-to-insecure.example -zonefile="${zone}.db" -infile="${zonefile}.in" -ksk=`$KEYGEN -q -r $RANDFILE -fk $zone` -$KEYGEN -q -r $RANDFILE $zone > /dev/null -$SIGNER -S -o $zone -f $zonefile $infile > /dev/null 2>&1 +setup secure-to-insecure.example +$KEYGEN -q -r $RANDFILE -fk $zone > kg.out 2>&1 || dumpit kg.out +$KEYGEN -q -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out +$SIGNER -S -o $zone -f $zonefile $infile > s.out 2>&1 || dumpit s.out # # another secure-to-insecure transition test zone; used to test # removal of keys on schedule. # -zone=secure-to-insecure2.example -zonefile="${zone}.db" -infile="${zonefile}.in" -ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone` +setup secure-to-insecure2.example +ksk=`$KEYGEN -q -3 -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out echo $ksk > ../del1.key -zsk=`$KEYGEN -q -3 -r $RANDFILE $zone` +zsk=`$KEYGEN -q -3 -r $RANDFILE $zone 2> kg.out` || dumpit kg.out echo $zsk > ../del2.key -$SIGNER -S -3 beef -o $zone -f $zonefile $infile > /dev/null 2>&1 +$SIGNER -S -3 beef -o $zone -f $zonefile $infile > s.out 2>&1 || dumpit s.out # # Introducing a pre-published key test. # -zone=prepub.example -zonefile="${zone}.db" -$KEYGEN -3 -q -r $RANDFILE -fk $zone > /dev/null -$KEYGEN -3 -q -r $RANDFILE $zone > /dev/null -$SIGNER -S -3 beef -o $zone -f $zonefile $infile > /dev/null 2>&1 +setup prepub.example +infile="secure-to-insecure2.example.db.in" +$KEYGEN -3 -q -r $RANDFILE -fk $zone > kg.out 2>&1 || dumpit kg.out +$KEYGEN -3 -q -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out +$SIGNER -S -3 beef -o $zone -f $zonefile $infile > s.out 2>&1 || dumpit s.out # # Key TTL tests. # # no default key TTL; DNSKEY should get SOA TTL -zone=ttl1.example -zonefile="${zone}.db" -infile="${zonefile}.in" -$KEYGEN -3 -q -r $RANDFILE -fk $zone > /dev/null -$KEYGEN -3 -q -r $RANDFILE $zone > /dev/null +setup ttl1.example +$KEYGEN -3 -q -r $RANDFILE -fk $zone > kg.out 2>&1 || dumpit kg.out +$KEYGEN -3 -q -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out cp $infile $zonefile # default key TTL should be used -zone=ttl2.example -zonefile="${zone}.db" -$KEYGEN -3 -q -r $RANDFILE -fk -L 60 $zone > /dev/null -$KEYGEN -3 -q -r $RANDFILE -L 60 $zone > /dev/null +setup ttl2.example +$KEYGEN -3 -q -r $RANDFILE -fk -L 60 $zone > kg.out 2>&1 || dumpit kg.out +$KEYGEN -3 -q -r $RANDFILE -L 60 $zone > kg.out 2>&1 || dumpit kg.out cp $infile $zonefile # mismatched key TTLs, should use shortest -zone=ttl3.example -zonefile="${zone}.db" -$KEYGEN -3 -q -r $RANDFILE -fk -L 30 $zone > /dev/null -$KEYGEN -3 -q -r $RANDFILE -L 60 $zone > /dev/null +setup ttl3.example +$KEYGEN -3 -q -r $RANDFILE -fk -L 30 $zone > kg.out 2>&1 || dumpit kg.out +$KEYGEN -3 -q -r $RANDFILE -L 60 $zone > kg.out 2>&1 || dumpit kg.out cp $infile $zonefile # existing DNSKEY RRset, should retain TTL -zone=ttl4.example -zonefile="${zone}.db" -$KEYGEN -3 -q -r $RANDFILE -L 30 -fk $zone > /dev/null +setup ttl4.example +$KEYGEN -3 -q -r $RANDFILE -L 30 -fk $zone > kg.out 2>&1 || dumpit kg.out cat ${infile} K${zone}.+*.key > $zonefile -$KEYGEN -3 -q -r $RANDFILE -L 180 $zone > /dev/null +$KEYGEN -3 -q -r $RANDFILE -L 180 $zone > kg.out 2>&1 || dumpit kg.out # # A zone with a DNSKEY RRset that is published before it's activated # -zone=delay.example -zonefile="${zone}.db" -ksk=`$KEYGEN -G -q -3 -r $RANDFILE -fk $zone` +setup delay.example +ksk=`$KEYGEN -G -q -3 -r $RANDFILE -fk $zone 2> kg.out` || dumpit kg.out echo $ksk > ../delayksk.key -zsk=`$KEYGEN -G -q -3 -r $RANDFILE $zone` +zsk=`$KEYGEN -G -q -3 -r $RANDFILE $zone 2> kg.out` || dumpit kg.out echo $zsk > ../delayzsk.key # # A zone with signatures that are already expired, and the private ZSK # is missing. # -zone=nozsk.example -zonefile="${zone}.db" -$KEYGEN -q -3 -r $RANDFILE -fk $zone > /dev/null +setup nozsk.example +$KEYGEN -q -3 -r $RANDFILE -fk $zone > kg.out 2>&1 || dumpit kg.out zsk=`$KEYGEN -q -3 -r $RANDFILE $zone` -$SIGNER -S -P -s now-1mo -e now-1mi -o $zone -f $zonefile ${zonefile}.in > /dev/null 2>&1 +$SIGNER -S -P -s now-1mo -e now-1mi -o $zone -f $zonefile ${zonefile}.in > s.out 2>&1 || dumpit s.out echo $zsk > ../missingzsk.key rm -f ${zsk}.private @@ -278,19 +250,17 @@ rm -f ${zsk}.private # A zone with signatures that are already expired, and the private ZSK # is inactive. # -zone=inaczsk.example -zonefile="${zone}.db" -$KEYGEN -q -3 -r $RANDFILE -fk $zone > /dev/null +setup inaczsk.example +$KEYGEN -q -3 -r $RANDFILE -fk $zone > kg.out 2>&1 || dumpit kg.out zsk=`$KEYGEN -q -3 -r $RANDFILE $zone` -$SIGNER -S -P -s now-1mo -e now-1mi -o $zone -f $zonefile ${zonefile}.in > /dev/null 2>&1 +$SIGNER -S -P -s now-1mo -e now-1mi -o $zone -f $zonefile ${zonefile}.in > s.out 2>&1 || dumpit s.out echo $zsk > ../inactivezsk.key -$SETTIME -I now $zsk > /dev/null +$SETTIME -I now $zsk > st.out 2>&1 || dumpit st.out # # A zone that is set to 'auto-dnssec maintain' during a recofnig # -zone=reconf.example -zonefile="${zone}.db" +setup reconf.example cp secure.example.db.in $zonefile -$KEYGEN -q -3 -r $RANDFILE -fk $zone > /dev/null -$KEYGEN -q -3 -r $RANDFILE $zone > /dev/null +$KEYGEN -q -3 -r $RANDFILE -fk $zone > kg.out 2>&1 || dumpit kg.out +$KEYGEN -q -3 -r $RANDFILE $zone > kg.out 2>&1 || dumpit kg.out diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in index e87de42190..50ab018fa0 100644 --- a/bin/tests/system/conf.sh.in +++ b/bin/tests/system/conf.sh.in @@ -48,6 +48,7 @@ PK11GEN="$TOP/bin/pkcs11/pkcs11-keygen -s 0 -p 1234" PK11LIST="$TOP/bin/pkcs11/pkcs11-list -s 0 -p 1234" PK11DEL="$TOP/bin/pkcs11/pkcs11-destroy -s 0 -p 1234" JOURNALPRINT=$TOP/bin/tools/named-journalprint +VERIFY=$TOP/bin/dnssec/dnssec-verify # The "stress" test is not run by default since it creates enough # load on the machine to make it unusable to other users. diff --git a/bin/tests/system/verify/.gitignore b/bin/tests/system/verify/.gitignore new file mode 100644 index 0000000000..00eed08c0e --- /dev/null +++ b/bin/tests/system/verify/.gitignore @@ -0,0 +1,7 @@ +random.data +verify.out.* +zones/*.good +zones/*.bad +zones/K* +zones/dsset-* +zones/s.out* diff --git a/bin/tests/system/verify/clean.sh b/bin/tests/system/verify/clean.sh new file mode 100644 index 0000000000..1a8976551d --- /dev/null +++ b/bin/tests/system/verify/clean.sh @@ -0,0 +1,9 @@ +rm -f zones/*.good +rm -f zones/*.good.tmp +rm -f zones/*.bad +rm -f zones/*.bad.tmp +rm -f zones/*.out* +rm -f zones/dsset-* +rm -f zones/K* +rm -f random.data +rm -f verify.out* diff --git a/bin/tests/system/verify/setup.sh b/bin/tests/system/verify/setup.sh new file mode 100644 index 0000000000..4d65286173 --- /dev/null +++ b/bin/tests/system/verify/setup.sh @@ -0,0 +1,23 @@ +#!/bin/sh -e +# +# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +# $Id: setup.sh,v 1.20 2011/02/15 22:02:36 marka Exp $ + +sh clean.sh + +../../../tools/genrandom 400 random.data + +(cd zones && sh genzones.sh) diff --git a/bin/tests/system/verify/tests.sh b/bin/tests/system/verify/tests.sh new file mode 100644 index 0000000000..e3d5a1917e --- /dev/null +++ b/bin/tests/system/verify/tests.sh @@ -0,0 +1,81 @@ + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh +failed () { + cat verify.out.$n | sed 's/^/D:/'; + echo "I:failed"; + status=1; +} + +n=0 +status=0 + +for file in zones/*.good +do + n=`expr $n + 1` + zone=`expr "$file" : 'zones/\(.*\).good'` + echo "I:checking supposedly good zone: $zone ($n)" + ret=0 + case $zone in + zsk-only.*) only=-z;; + ksk-only.*) only=-z;; + *) only=;; + esac + $VERIFY ${only} -o $zone $file > verify.out.$n 2>&1 || ret=1 + [ $ret = 0 ] || failed +done + +for file in zones/*.bad +do + n=`expr $n + 1` + zone=`expr "$file" : 'zones/\(.*\).bad'` + echo "I:checking supposedly bad zone: $zone ($n)" + ret=0 + dumpit=0 + case $zone in + zsk-only.*) only=-z;; + ksk-only.*) only=-z;; + *) only=;; + esac + expect1= expect2= + case $zone in + *.dnskeyonly) + expect1="DNSKEY is not signed" + ;; + *.expired) + expect1="signature has expired" + expect2="No self-signed .*DNSKEY found" + ;; + *.ksk-expired) + expect1="signature has expired" + expect2="No self-signed .*DNSKEY found" + ;; + *.out-of-zone-nsec|*.below-bottom-of-zone-nsec) + expect1="unexpected NSEC RRset at" + ;; + *.nsec.broken-chain) + expect1="Bad record NSEC record for.*, next name mismatch" + ;; + *.bad-bitmap) + expect1="bit map mismatch" + ;; + *.missing-empty) + expect1="Missing NSEC3 record for"; + ;; + unsigned) + expect1="Zone contains no DNSSEC keys" + ;; + *.extra-nsec3) + expect1="Expected and found NSEC3 chains not equal"; + ;; + *) + dumpit=1 + ;; + esac + $VERIFY ${only} -o $zone $file > verify.out.$n 2>&1 && ret=1 + grep "${expect1:-.}" verify.out.$n > /dev/null || ret=1 + grep "${expect2:-.}" verify.out.$n > /dev/null || ret=1 + [ $ret = 0 ] || failed + [ $dumpit = 1 ] && cat verify.out.$n +done +exit $status diff --git a/bin/tests/system/verify/zones/genzones.sh b/bin/tests/system/verify/zones/genzones.sh new file mode 100644 index 0000000000..e50b53966f --- /dev/null +++ b/bin/tests/system/verify/zones/genzones.sh @@ -0,0 +1,175 @@ +SYSTEMTESTTOP=../.. +. $SYSTEMTESTTOP/conf.sh + +RANDFILE=../random.data + +dumpit () { + echo "D:${debug}: dumping ${1}" + cat "${1}" | sed 's/^/D:/' +} +setup () { + echo "I:setting up $2 zone: $1" + debug="$1" + zone="$1" + file="$1.$2" + n=`expr ${n:-0} + 1` +} + +# A unsigned zone should fail validation. +setup unsigned bad +cp unsigned.db unsigned.bad + +# A set of nsec zones. +setup zsk-only.nsec good +$KEYGEN -r $RANDFILE ${zone}> kg.out$n 2>&1 || dumpit kg.out$n +$SIGNER -SP -o ${zone} -f ${file} unsigned.db > s.out$n 2>&1 || dumpit s.out$n + +setup ksk-only.nsec good +$KEYGEN -r $RANDFILE -fK ${zone} > kg.out$n 2>&1 || dumpit kg.out$n +$SIGNER -SPz -o ${zone} -f ${file} unsigned.db > s.out$n 2>&1 || dumpit s.out$n + +setup ksk+zsk.nsec good +$KEYGEN -r $RANDFILE ${zone} > kg1.out$n 2>&1 || dumpit kg1.out$n +$KEYGEN -r $RANDFILE -fK ${zone} > kg2.out$n 2>&1 || dumpit kg2.out$n +$SIGNER -SPx -o ${zone} -f ${file} unsigned.db > s.out$n 2>&1 || dumpit s.out$n + +# A set of nsec3 zones. +setup zsk-only.nsec3 good +$KEYGEN -3 -r $RANDFILE ${zone}> kg.out$n 2>&1 || dumpit kg.out$n +$SIGNER -3 - -SP -o ${zone} -f ${file} unsigned.db > s.out$n 2>&1 || dumpit s.out$n + +setup ksk-only.nsec3 good +$KEYGEN -3 -r $RANDFILE -fK ${zone} > kg.out$n 2>&1 || dumpit kg.out$n +$SIGNER -3 - -SPz -o ${zone} -f ${file} unsigned.db > s.out$n 2>&1 || dumpit s.out$n + +setup ksk+zsk.nsec3 good +$KEYGEN -3 -r $RANDFILE ${zone} > kg1.out$n 2>&1 || dumpit kg1.out$n +$KEYGEN -3 -r $RANDFILE -fK ${zone} > kg2.out$n 2>&1 || dumpit kg2.out$n +$SIGNER -3 - -SPx -o ${zone} -f ${file} unsigned.db > s.out$n 2>&1 || dumpit s.out$n + +setup ksk+zsk.outout good +$KEYGEN -3 -r $RANDFILE ${zone} > kg1.out$n 2>&1 || dumpit kg1.out$n +$KEYGEN -3 -r $RANDFILE -fK ${zone} > kg2.out$n 2>&1 || dumpit kg2.out$n +$SIGNER -3 - -A -SPx -o ${zone} -f ${file} unsigned.db > s.out$n 2>&1 || dumpit s.out$n + +# A set of zones with only DNSKEY records. +setup zsk-only.dnskeyonly bad +key1=`$KEYGEN -r $RANDFILE ${zone} 2>kg.out` || dumpit kg.out$n +cat unsigned.db $key1.key > ${file} + +setup ksk-only.dnskeyonly bad +key1=`$KEYGEN -r $RANDFILE -fK ${zone} 2>kg.out` || dumpit kg.out$n +cat unsigned.db $key1.key > ${file} + +setup ksk+zsk.dnskeyonly bad +key1=`$KEYGEN -r $RANDFILE ${zone} 2>kg.out` || dumpit kg.out$n +key2=`$KEYGEN -r $RANDFILE -fK ${zone} 2>kg.out` || dumpit kg.out$n +cat unsigned.db $key1.key $key2.key > ${file} + +# A set of zones with expired records +s="-s -2678400" +setup zsk-only.nsec.expired bad +$KEYGEN -r $RANDFILE ${zone}> kg.out$n 2>&1 || dumpit kg.out$n +$SIGNER -SP ${s} -o ${zone} -f ${file} unsigned.db > s.out$n 2>&1 || dumpit s.out$n + +setup ksk-only.nsec.expired bad +$KEYGEN -r $RANDFILE -fK ${zone} > kg.out$n 2>&1 || dumpit kg.out$n +$SIGNER -SPz ${s} -o ${zone} -f ${file} unsigned.db > s.out$n 2>&1 || dumpit s.out$n + +setup ksk+zsk.nsec.expired bad +$KEYGEN -r $RANDFILE ${zone} > kg1.out$n 2>&1 || dumpit kg1.out$n +$KEYGEN -r $RANDFILE -fK ${zone} > kg2.out$n 2>&1 || dumpit kg2.out$n +$SIGNER -SP ${s} -o ${zone} -f ${file} unsigned.db > s.out$n 2>&1 || dumpit s.out$n + +setup zsk-only.nsec3.expired bad +$KEYGEN -3 -r $RANDFILE ${zone}> kg.out$n 2>&1 || dumpit kg.out$n +$SIGNER -3 - ${s} -SP -o ${zone} -f ${file} unsigned.db > s.out$n 2>&1 || dumpit s.out$n + +setup ksk-only.nsec3.expired bad +$KEYGEN -3 -r $RANDFILE -fK ${zone} > kg.out$n 2>&1 || dumpit kg.out$n +$SIGNER -3 - ${s} -SPz -o ${zone} -f ${file} unsigned.db > s.out$n 2>&1 || dumpit s.out$n + +setup ksk+zsk.nsec3.expired bad +$KEYGEN -3 -r $RANDFILE ${zone} > kg1.out$n 2>&1 || dumpit kg1.out$n +$KEYGEN -3 -r $RANDFILE -fK ${zone} > kg2.out$n 2>&1 || dumpit kg2.out$n +$SIGNER -3 - ${s} -SPx -o ${zone} -f ${file} unsigned.db > s.out$n 2>&1 || dumpit s.out$n + +# ksk expired +setup ksk+zsk.nsec.ksk-expired bad +zsk=`$KEYGEN -r $RANDFILE ${zone} 2> kg1.out$n` || dumpit kg1.out$n +ksk=`$KEYGEN -r $RANDFILE -fK ${zone} 2> kg2.out$n` || dumpit kg2.out$n +cat unsigned.db $ksk.key $zsk.key > $file +$SIGNER -Px -o ${zone} -f ${file} ${file} $zsk > s.out$n 2>&1 || dumpit s.out$n +$SIGNER ${s} -P -O full -o ${zone} -f ${file} ${file} $ksk > s.out$n 2>&1 || dumpit s.out$n +now=`date -u +%Y%m%d%H%M%S` +exp=`awk '$4 == "RRSIG" && $5 == "DNSKEY" { print $9;}' ${file}` +[ "${exp:-40001231246060}" -lt ${now:-0} ] || dumpit $file + +setup ksk+zsk.nsec3.ksk-expired bad +zsk=`$KEYGEN -3 -r $RANDFILE ${zone} 2> kg1.out$n` || dumpit kg1.out$n +ksk=`$KEYGEN -3 -r $RANDFILE -fK ${zone} 2> kg2.out$n` || dumpit kg2.out$n +cat unsigned.db $ksk.key $zsk.key > $file +$SIGNER -3 - -Px -o ${zone} -f ${file} ${file} $zsk > s.out$n 2>&1 || dumpit s.out$n +$SIGNER -3 - ${s} -P -O full -o ${zone} -f ${file} ${file} $ksk > s.out$n 2>&1 || dumpit s.out$n +now=`date -u +%Y%m%d%H%M%S` +exp=`awk '$4 == "RRSIG" && $5 == "DNSKEY" { print $9;}' ${file}` +[ "${exp:-40001231246060}" -lt ${now:-0} ] || dumpit $file + +# broken nsec chain +setup ksk+zsk.nsec.broken-chain bad +zsk=`$KEYGEN -r $RANDFILE ${zone} 2> kg1.out$n` || dumpit kg1.out$n +ksk=`$KEYGEN -r $RANDFILE -fK ${zone} 2> kg2.out$n` || dumpit kg2.out$n +cat unsigned.db $ksk.key $zsk.key > $file +$SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk > s.out$n 2>&1 || dumpit s.out$n +awk '$4 == "NSEC" { $5 = "'$zone'."; print } { print }' ${file} > ${file}.tmp +$SIGNER -Px -Z nonsecify -o ${zone} -f ${file} ${file}.tmp $zsk > s.out$n 2>&1 || dumpit s.out$n + +# bad nsec bitmap +setup ksk+zsk.nsec.bad-bitmap bad +zsk=`$KEYGEN -r $RANDFILE ${zone} 2> kg1.out$n` || dumpit kg1.out$n +ksk=`$KEYGEN -r $RANDFILE -fK ${zone} 2> kg2.out$n` || dumpit kg2.out$n +cat unsigned.db $ksk.key $zsk.key > $file +$SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk > s.out$n 2>&1 || dumpit s.out$n +awk '$4 == "NSEC" && /SOA/ { $6=""; print } { print }' ${file} > ${file}.tmp +$SIGNER -Px -Z nonsecify -o ${zone} -f ${file} ${file}.tmp $zsk > s.out$n 2>&1 || dumpit s.out$n + +# extra NSEC record out side of zone +setup ksk+zsk.nsec.out-of-zone-nsec bad +zsk=`$KEYGEN -r $RANDFILE ${zone} 2> kg1.out$n` || dumpit kg1.out$n +ksk=`$KEYGEN -r $RANDFILE -fK ${zone} 2> kg2.out$n` || dumpit kg2.out$n +cat unsigned.db $ksk.key $zsk.key > $file +$SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk > s.out$n 2>&1 || dumpit s.out$n +echo "out-of-zone. 3600 IN NSEC ${zone}. A" >> ${file} +$SIGNER -Px -Z nonsecify -O full -o ${zone} -f ${file} ${file} $zsk > s.out$n 2>&1 || dumpit s.out$n + +# extra NSEC record below bottom of one +setup ksk+zsk.nsec.below-bottom-of-zone-nsec bad +zsk=`$KEYGEN -r $RANDFILE ${zone} 2> kg1.out$n` || dumpit kg1.out$n +ksk=`$KEYGEN -r $RANDFILE -fK ${zone} 2> kg2.out$n` || dumpit kg2.out$n +cat unsigned.db $ksk.key $zsk.key > $file +$SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk > s.out$n 2>&1 || dumpit s.out$n +echo "ns.sub.${zone}. 3600 IN NSEC ${zone}. A AAAA" >> ${file} +$SIGNER -Px -Z nonsecify -O full -o ${zone} -f ${file}.tmp ${file} $zsk > s.out$n 2>&1 || dumpit s.out$n +# dnssec-signzone signs any node with a NSEC record. +awk '$1 ~ /^ns.sub/ && $4 == "RRSIG" && $5 != "NSEC" { next; } { print; }' ${file}.tmp > ${file} + +# missing NSEC3 record at empty node +setup ksk+zsk.nsec3.missing-empty bad +zsk=`$KEYGEN -3 -r $RANDFILE ${zone} 2> kg1.out$n` || dumpit kg1.out$n +ksk=`$KEYGEN -3 -r $RANDFILE -fK ${zone} 2> kg2.out$n` || dumpit kg2.out$n +cat unsigned.db $ksk.key $zsk.key > $file +$SIGNER -3 - -P -O full -o ${zone} -f ${file} ${file} $ksk > s.out$n 2>&1 || dumpit s.out$n +awk '$4 == "NSEC3" && NF == 9 { next; } { print; }' ${file} > ${file}.tmp +$SIGNER -3 - -Px -Z nonsecify -O full -o ${zone} -f ${file} ${file}.tmp $zsk > s.out$n 2>&1 || dumpit s.out$n + +# extra NSEC3 record +setup ksk+zsk.nsec3.extra-nsec3 bad +zsk=`$KEYGEN -3 -r $RANDFILE ${zone} 2> kg1.out$n` || dumpit kg1.out$n +ksk=`$KEYGEN -3 -r $RANDFILE -fK ${zone} 2> kg2.out$n` || dumpit kg2.out$n +cat unsigned.db $ksk.key $zsk.key > $file +$SIGNER -3 - -P -O full -o ${zone} -f ${file} ${file} $ksk > s.out$n 2>&1 || dumpit s.out$n +awk -v ZONE=${zone}. '$4 == "NSEC3" && NF == 9 { + $1 = "H9P7U7TR2U91D0V0LJS9L1GIDNP90U3H." ZONE; + $9 = "H9P7U7TR2U91D0V0LJS9L1GIDNP90U3I"; + print; }' ${file} >> ${file} +$SIGNER -3 - -Px -Z nonsecify -O full -o ${zone} -f ${file} ${file} $zsk > s.out$n 2>&1 || dumpit s.out$n diff --git a/bin/tests/system/verify/zones/unsigned.db b/bin/tests/system/verify/zones/unsigned.db new file mode 100644 index 0000000000..64ba0de8d9 --- /dev/null +++ b/bin/tests/system/verify/zones/unsigned.db @@ -0,0 +1,17 @@ +$TTL 3600 +@ SOA . . 0 0 0 2419200 3600 ; 28 day expire +@ NS . +data A 1.2.3.4 +dname DNAME data +longttl 2419200 A 1.2.3.4 +sub.dname TXT sub.dname +sub.empty TXT sub.empty +sub NS ns.sub +ns.sub A 1.2.3.4 +ns.sub AAAA 2002::1.2.3.4 +other.sub TXT other.sub +secure NS secure +secure DS 1312 50 100 96EEB2FFD9B00CD4694E78278B5EFDAB0A80446567B69F634DA078F0 +secure A 1.2.3.4 +secure AAAA 2002::1.2.3.4 +out-of-zone. A 1.2.3.4 diff --git a/configure.in b/configure.in index 6f6529776a..3c7e8b0589 100644 --- a/configure.in +++ b/configure.in @@ -3022,7 +3022,7 @@ AC_ARG_WITH(docbook-xsl, case "$docbook_path" in auto) AC_MSG_RESULT(auto) - docbook_xsl_trees="/usr/pkg/share/xsl/docbook /usr/local/share/xsl/docbook /usr/share/xsl/docbook" + docbook_xsl_trees="/usr/pkg/share/xsl/docbook /usr/local/share/xsl/docbook /usr/share/xsl/docbook /opt/local/share/xsl/docbook-xsl/" ;; *) docbook_xsl_trees="$withval" diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index 716b6cfbd0..fdbc7010a6 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -16879,6 +16879,7 @@ zone "example.com" { + diff --git a/doc/design/verify b/doc/design/verify new file mode 100644 index 0000000000..1cf8bdc982 --- /dev/null +++ b/doc/design/verify @@ -0,0 +1,25 @@ + + dnssec-verify a tool to verify a zone is correctly signed. + +* check that every record that should be signed has a valid RRSIG set. +* check that every record that shouldn't be signed isn't. +* check that each RRSIG set has a valid RRSIG and that all DNSKEY algorithms + in use are checked. +* provide a mechanism to mark DNSKEY algorithms to be ignored to support + verification of zones that are in the processs of adding/removing + support for a algorithm. +* provide a mechanism to check the zone as of a specified date and time. +* check that RRSIG won't expire within the TTL interval. +* check that original TTL matches. + +NSEC: +* check that every node with data within the zone has a NSEC RRset. +* check that empty nodes don't have a NSEC record. +* check that nodes outside the zone do not have a NSEC record. +* check that the NSEC chain is valid. + +NSEC3: for each NSEC3 chain +* check that every node with data within the zone has a NSEC3 RRset. +* check that empty nodes within the zone have a NSEC3 record. +* check that nodes outside the zone do not have a NSEC3 record. +* check that each NSEC3 in the NSEC3PARAM record is valid. diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h index 61e3e93cd1..3ad42ca6cf 100644 --- a/lib/dns/include/dns/db.h +++ b/lib/dns/include/dns/db.h @@ -799,6 +799,10 @@ dns_db_findext(dns_db_t *db, dns_name_t *name, dns_dbversion_t *version, * that it is correct. This only affects answers returned from the * cache. * + * \li In the #DNS_DBFIND_FORCENSEC3 option is set, then we are looking + * in the NSEC3 tree and not the main tree. Without this option being + * set NSEC3 records will not be found. + * * \li To respond to a query for SIG records, the caller should create a * rdataset iterator and extract the signatures from each rdataset. * diff --git a/lib/dns/include/dns/nsec.h b/lib/dns/include/dns/nsec.h index fca843a1e2..dddc91edee 100644 --- a/lib/dns/include/dns/nsec.h +++ b/lib/dns/include/dns/nsec.h @@ -76,6 +76,28 @@ dns_nsec_nseconly(dns_db_t *db, dns_dbversion_t *version, * 'answer' to be non NULL. */ +unsigned int +dns_nsec_compressbitmap(unsigned char *map, const unsigned char *raw, + unsigned int max_type); +/*%< + * Convert a raw bitmap into a compressed windowed bit map. 'map' and 'raw' + * may overlap. + * + * Returns the length of the compressed windowed bit map. + */ + +void +dns_nsec_setbit(unsigned char *array, unsigned int type, unsigned int bit); +/*%< + * Set type bit in raw 'array' to 'bit'. + */ + +isc_boolean_t +dns_nsec_isset(const unsigned char *array, unsigned int type); +/*%< + * Test if the corresponding 'type' bit is set in 'array'. + */ + ISC_LANG_ENDDECLS #endif /* DNS_NSEC_H */ diff --git a/lib/dns/nsec.c b/lib/dns/nsec.c index 6e3a69ab26..d399f1f459 100644 --- a/lib/dns/nsec.c +++ b/lib/dns/nsec.c @@ -41,28 +41,61 @@ goto failure; \ } while (0) -static void -set_bit(unsigned char *array, unsigned int index, unsigned int bit) { +void +dns_nsec_setbit(unsigned char *array, unsigned int type, unsigned int bit) { unsigned int shift, mask; - shift = 7 - (index % 8); + shift = 7 - (type % 8); mask = 1 << shift; if (bit != 0) - array[index / 8] |= mask; + array[type / 8] |= mask; else - array[index / 8] &= (~mask & 0xFF); + array[type / 8] &= (~mask & 0xFF); } -static unsigned int -bit_isset(unsigned char *array, unsigned int index) { +isc_boolean_t +dns_nsec_isset(const unsigned char *array, unsigned int type) { unsigned int byte, shift, mask; - byte = array[index / 8]; - shift = 7 - (index % 8); + byte = array[type / 8]; + shift = 7 - (type % 8); mask = 1 << shift; - return ((byte & mask) != 0); + return (ISC_TF(byte & mask)); +} + +unsigned int +dns_nsec_compressbitmap(unsigned char *map, const unsigned char *raw, + unsigned int max_type) +{ + unsigned char *start = map; + unsigned int window; + int octet; + + if (raw == NULL) + return (0); + + for (window = 0; window < 256; window++) { + if (window * 256 > max_type) + break; + for (octet = 31; octet >= 0; octet--) + if (*(raw + octet) != 0) + break; + if (octet < 0) { + raw += 32; + continue; + } + *map++ = window; + *map++ = octet + 1; + /* + * Note: potential overlapping move. + */ + memmove(map, raw, octet + 1); + map += octet + 1; + raw += 32; + } + return (map - start); } isc_result_t @@ -73,8 +106,7 @@ dns_nsec_buildrdata(dns_db_t *db, dns_dbversion_t *version, isc_result_t result; dns_rdataset_t rdataset; isc_region_t r; - unsigned int i, window; - int octet; + unsigned int i; unsigned char *nsec_bits, *bm; unsigned int max_type; @@ -90,8 +122,8 @@ dns_nsec_buildrdata(dns_db_t *db, dns_dbversion_t *version, */ bm = r.base + r.length + 512; nsec_bits = r.base + r.length; - set_bit(bm, dns_rdatatype_rrsig, 1); - set_bit(bm, dns_rdatatype_nsec, 1); + dns_nsec_setbit(bm, dns_rdatatype_rrsig, 1); + dns_nsec_setbit(bm, dns_rdatatype_nsec, 1); max_type = dns_rdatatype_nsec; dns_rdataset_init(&rdataset); rdsiter = NULL; @@ -108,7 +140,7 @@ dns_nsec_buildrdata(dns_db_t *db, dns_dbversion_t *version, rdataset.type != dns_rdatatype_rrsig) { if (rdataset.type > max_type) max_type = rdataset.type; - set_bit(bm, rdataset.type, 1); + dns_nsec_setbit(bm, rdataset.type, 1); } dns_rdataset_disassociate(&rdataset); } @@ -116,12 +148,12 @@ dns_nsec_buildrdata(dns_db_t *db, dns_dbversion_t *version, /* * At zone cuts, deny the existence of glue in the parent zone. */ - if (bit_isset(bm, dns_rdatatype_ns) && - ! bit_isset(bm, dns_rdatatype_soa)) { + if (dns_nsec_isset(bm, dns_rdatatype_ns) && + ! dns_nsec_isset(bm, dns_rdatatype_soa)) { for (i = 0; i <= max_type; i++) { - if (bit_isset(bm, i) && + if (dns_nsec_isset(bm, i) && ! dns_rdatatype_iszonecutauth((dns_rdatatype_t)i)) - set_bit(bm, i, 0); + dns_nsec_setbit(bm, i, 0); } } @@ -129,22 +161,8 @@ dns_nsec_buildrdata(dns_db_t *db, dns_dbversion_t *version, if (result != ISC_R_NOMORE) return (result); - for (window = 0; window < 256; window++) { - if (window * 256 > max_type) - break; - for (octet = 31; octet >= 0; octet--) - if (bm[window * 32 + octet] != 0) - break; - if (octet < 0) - continue; - nsec_bits[0] = window; - nsec_bits[1] = octet + 1; - /* - * Note: potential overlapping move. - */ - memmove(&nsec_bits[2], &bm[window * 32], octet + 1); - nsec_bits += 3 + octet; - } + nsec_bits += dns_nsec_compressbitmap(nsec_bits, bm, max_type); + r.length = nsec_bits - r.base; INSIST(r.length <= DNS_NSEC_BUFFERSIZE); dns_rdata_fromregion(rdata, @@ -155,7 +173,6 @@ dns_nsec_buildrdata(dns_db_t *db, dns_dbversion_t *version, return (ISC_R_SUCCESS); } - isc_result_t dns_nsec_build(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, dns_name_t *target, dns_ttl_t ttl) @@ -216,8 +233,8 @@ dns_nsec_typepresent(dns_rdata_t *nsec, dns_rdatatype_t type) { if ((window + 1) * 256 <= type) continue; if (type < (window * 256) + len * 8) - present = ISC_TF(bit_isset(&nsecstruct.typebits[i], - type % 256)); + present = ISC_TF(dns_nsec_isset(&nsecstruct.typebits[i], + type % 256)); break; } dns_rdata_freestruct(&nsecstruct); diff --git a/lib/dns/nsec3.c b/lib/dns/nsec3.c index 44cc652791..231de132fc 100644 --- a/lib/dns/nsec3.c +++ b/lib/dns/nsec3.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -52,30 +53,6 @@ #define INITIAL(x) (((x) & DNS_NSEC3FLAG_INITIAL) != 0) #define REMOVE(x) (((x) & DNS_NSEC3FLAG_REMOVE) != 0) -static void -set_bit(unsigned char *array, unsigned int index, unsigned int bit) { - unsigned int shift, mask; - - shift = 7 - (index % 8); - mask = 1 << shift; - - if (bit != 0) - array[index / 8] |= mask; - else - array[index / 8] &= (~mask & 0xFF); -} - -static unsigned int -bit_isset(unsigned char *array, unsigned int index) { - unsigned int byte, shift, mask; - - byte = array[index / 8]; - shift = 7 - (index % 8); - mask = 1 << shift; - - return ((byte & mask) != 0); -} - isc_result_t dns_nsec3_buildrdata(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, unsigned int hashalg, @@ -87,8 +64,7 @@ dns_nsec3_buildrdata(dns_db_t *db, dns_dbversion_t *version, isc_result_t result; dns_rdataset_t rdataset; isc_region_t r; - unsigned int i, window; - int octet; + unsigned int i; isc_boolean_t found; isc_boolean_t found_ns; isc_boolean_t need_rrsig; @@ -156,7 +132,7 @@ dns_nsec3_buildrdata(dns_db_t *db, dns_dbversion_t *version, rdataset.type != dns_rdatatype_rrsig) { if (rdataset.type > max_type) max_type = rdataset.type; - set_bit(bm, rdataset.type, 1); + dns_nsec_setbit(bm, rdataset.type, 1); /* * Work out if we need to set the RRSIG bit for * this node. We set the RRSIG bit if either of @@ -179,18 +155,18 @@ dns_nsec3_buildrdata(dns_db_t *db, dns_dbversion_t *version, if ((found && !found_ns) || need_rrsig) { if (dns_rdatatype_rrsig > max_type) max_type = dns_rdatatype_rrsig; - set_bit(bm, dns_rdatatype_rrsig, 1); + dns_nsec_setbit(bm, dns_rdatatype_rrsig, 1); } /* * At zone cuts, deny the existence of glue in the parent zone. */ - if (bit_isset(bm, dns_rdatatype_ns) && - ! bit_isset(bm, dns_rdatatype_soa)) { + if (dns_nsec_isset(bm, dns_rdatatype_ns) && + ! dns_nsec_isset(bm, dns_rdatatype_soa)) { for (i = 0; i <= max_type; i++) { - if (bit_isset(bm, i) && + if (dns_nsec_isset(bm, i) && ! dns_rdatatype_iszonecutauth((dns_rdatatype_t)i)) - set_bit(bm, i, 0); + dns_nsec_setbit(bm, i, 0); } } @@ -199,22 +175,7 @@ dns_nsec3_buildrdata(dns_db_t *db, dns_dbversion_t *version, return (result); collapse_bitmap: - for (window = 0; window < 256; window++) { - if (window * 256 > max_type) - break; - for (octet = 31; octet >= 0; octet--) - if (bm[window * 32 + octet] != 0) - break; - if (octet < 0) - continue; - nsec_bits[0] = window; - nsec_bits[1] = octet + 1; - /* - * Note: potentially overlapping move. - */ - memmove(&nsec_bits[2], &bm[window * 32], octet + 1); - nsec_bits += 3 + octet; - } + nsec_bits += dns_nsec_compressbitmap(nsec_bits, bm, max_type); r.length = nsec_bits - r.base; INSIST(r.length <= DNS_NSEC3_BUFFERSIZE); dns_rdata_fromregion(rdata, dns_db_class(db), dns_rdatatype_nsec3, &r); @@ -249,8 +210,8 @@ dns_nsec3_typepresent(dns_rdata_t *rdata, dns_rdatatype_t type) { if ((window + 1) * 256 <= type) continue; if (type < (window * 256) + len * 8) - present = ISC_TF(bit_isset(&nsec3.typebits[i], - type % 256)); + present = ISC_TF(dns_nsec_isset(&nsec3.typebits[i], + type % 256)); break; } dns_rdata_freestruct(&nsec3); diff --git a/lib/isc/include/isc/heap.h b/lib/isc/include/isc/heap.h index 77bf07c344..738b402087 100644 --- a/lib/isc/include/isc/heap.h +++ b/lib/isc/include/isc/heap.h @@ -60,6 +60,8 @@ isc_heap_create(isc_mem_t *mctx, isc_heapcompare_t compare, * storage method. When the heap elements are deleted space is not freed * but will be reused when new elements are inserted. * + * Heap elements are indexed from 1. + * * Requires: *\li "mctx" is valid. *\li "compare" is a function which takes two void * arguments and