2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-09-02 23:55:27 +00:00

dnssec-signzone now updates ZONEMD records

If the zone already contains ZONEMD records, they will be updated
when signing the zone. If not, ZONEMD records can be added by using
the "-Z" option: for instance, "dnssec-signzone -Z - -S example.com".

Also, dnssec-verify now verifies the ZONEMD records, if any are
present. If run with the "-Z" flag, ZONEMD is mandatory, and the
zone will be rejected if it is not present.

Some zones in the mirror system test are now signed with ZONEMD.
This commit is contained in:
Mark Andrews
2025-06-10 16:17:35 +10:00
committed by Evan Hunt
parent 4b3f3bc38a
commit 22c0fcdc9f
13 changed files with 421 additions and 33 deletions

View File

@@ -173,6 +173,7 @@ static bool set_maxttl = false;
static dns_ttl_t maxttl = 0; static dns_ttl_t maxttl = 0;
static bool no_max_check = false; static bool no_max_check = false;
static const char *sync_records = "cdnskey,cds:sha-256"; static const char *sync_records = "cdnskey,cds:sha-256";
static bool add_zonemd = false;
#define INCSTAT(counter) \ #define INCSTAT(counter) \
if (printstats) { \ if (printstats) { \
@@ -1215,6 +1216,8 @@ signname(dns_dbnode_t *node, bool apex, dns_name_t *name) {
char namebuf[DNS_NAME_FORMATSIZE]; char namebuf[DNS_NAME_FORMATSIZE];
dns_name_format(name, namebuf, sizeof(namebuf)); dns_name_format(name, namebuf, sizeof(namebuf));
fatal("'%s': Non-apex DNSKEY RRset\n", namebuf); fatal("'%s': Non-apex DNSKEY RRset\n", namebuf);
} else if (rdataset.type == dns_rdatatype_zonemd && apex) {
goto skip;
} }
signset(&del, &add, node, name, &rdataset); signset(&del, &add, node, name, &rdataset);
@@ -1472,10 +1475,42 @@ presign(void) {
/*% /*%
* Clean up the iterator and global state after the tasks complete. * Clean up the iterator and global state after the tasks complete.
*
* Update ZONEMD if present.
*/ */
static void static void
postsign(void) { postsign(void) {
isc_result_t result;
dns_diff_t diff;
dns_dbnode_t *node = NULL;
dns_rdataset_t rdataset = DNS_RDATASET_INIT;
dns_dbiterator_destroy(&gdbiter); dns_dbiterator_destroy(&gdbiter);
dns_diff_init(isc_g_mctx, &diff);
result = dns_update_zonemd(gdb, gversion, &diff);
check_result(result, "dns_update_zonemd");
dns_diff_clear(&diff);
result = dns_db_getoriginnode(gdb, &node);
check_result(result, "dns_db_findnode");
result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_zonemd,
0, (isc_stdtime_t)0, &rdataset, NULL);
if (result == ISC_R_SUCCESS) {
signset(&diff, &diff, node, gorigin, &rdataset);
}
result = dns_diff_applysilently(&diff, gdb, gversion);
check_result(result, "dns_diff_applysilently");
if (dns_rdataset_isassociated(&rdataset)) {
dns_rdataset_disassociate(&rdataset);
}
dns_db_detachnode(&node);
dns_diff_clear(&diff);
} }
/*% /*%
@@ -2100,6 +2135,88 @@ nsec3clean(dns_name_t *name, dns_dbnode_t *node, unsigned int hashalg,
} }
} }
static void
addzonemd(uint8_t scheme, uint8_t digest_type) {
dns_dbnode_t *node = NULL;
dns_rdata_zonemd_t zonemd;
dns_rdatalist_t zmlist;
dns_rdataset_t zmset = DNS_RDATASET_INIT;
dns_rdata_t rdata = DNS_RDATA_INIT;
unsigned char digest[64] = { 0 };
unsigned char zmbuf[6 + sizeof(digest)];
isc_buffer_t b;
isc_result_t result;
dns_ttl_t ttl = 0;
/* Is there already a ZONEMD? */
result = dns_db_getoriginnode(gdb, &node);
check_result(result, "dns_db_getoriginnode()");
result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_zonemd,
0, 0, &zmset, NULL);
if (result == ISC_R_SUCCESS) {
DNS_RDATASET_FOREACH(&zmset) {
dns_rdataset_current(&zmset, &rdata);
(void)dns_rdata_tostruct(&rdata, &zonemd, NULL);
/*
* Does the scheme/digest_type pair already exist?
*/
if (zonemd.scheme == scheme &&
zonemd.digest_type == digest_type)
{
dns_rdataset_disassociate(&zmset);
dns_db_detachnode(&node);
return;
}
dns_rdata_init(&rdata);
}
/*
* Remember the rdataset's ttl.
*/
ttl = zmset.ttl;
dns_rdataset_disassociate(&zmset);
}
/* No, so add a dummy */
zonemd.common.rdclass = gclass;
zonemd.common.rdtype = dns_rdatatype_zonemd;
zonemd.mctx = NULL;
zonemd.serial = 0;
zonemd.scheme = scheme;
zonemd.digest_type = digest_type;
zonemd.digest = digest;
switch (digest_type) {
case DNS_ZONEMD_DIGEST_SHA384:
zonemd.length = ISC_SHA384_DIGESTLENGTH;
break;
case DNS_ZONEMD_DIGEST_SHA512:
zonemd.length = ISC_SHA512_DIGESTLENGTH;
break;
default:
UNREACHABLE();
}
isc_buffer_init(&b, zmbuf, sizeof(zmbuf));
result = dns_rdata_fromstruct(&rdata, gclass, dns_rdatatype_zonemd,
&zonemd, &b);
check_result(result, "dns_rdata_fromstruct()");
dns_rdatalist_init(&zmlist);
zmlist.rdclass = rdata.rdclass;
zmlist.type = rdata.type;
zmlist.ttl = ttl;
ISC_LIST_APPEND(zmlist.rdata, &rdata, link);
dns_rdatalist_tordataset(&zmlist, &zmset);
result = dns_db_addrdataset(gdb, node, gversion, 0, &zmset,
DNS_DBADD_MERGE, NULL);
if (result == DNS_R_UNCHANGED) {
result = ISC_R_SUCCESS;
}
check_result(result, "addnsec3param: dns_db_addrdataset()");
dns_db_detachnode(&node);
}
static void static void
rrset_cleanup(dns_name_t *name, dns_rdataset_t *rdataset, dns_diff_t *add, rrset_cleanup(dns_name_t *name, dns_rdataset_t *rdataset, dns_diff_t *add,
dns_diff_t *del) { dns_diff_t *del) {
@@ -3272,6 +3389,7 @@ main(int argc, char *argv[]) {
bool set_optout = false; bool set_optout = false;
bool set_iter = false; bool set_iter = false;
bool nonsecify = false; bool nonsecify = false;
uint8_t zonemd_scheme[2] = { 0 }, zonemd_digest[2] = { 0 };
atomic_init(&shuttingdown, false); atomic_init(&shuttingdown, false);
atomic_init(&finished, false); atomic_init(&finished, false);
@@ -3283,7 +3401,7 @@ main(int argc, char *argv[]) {
* l was previously used for DLV lookaside. * l was previously used for DLV lookaside.
*/ */
#define CMDLINE_FLAGS \ #define CMDLINE_FLAGS \
"3:AaCc:Dd:E:e:f:FgG:hH:i:I:j:J:K:k:L:m:M:n:N:o:O:PpQqRr:s:ST:tuUv:" \ "3:AaCc:Dd:E:e:f:FgG:hH:i:I:j:J:K:k:L:m:M:n:N:o:O:PpQqRr:s:ST:tuU:v:" \
"VX:xzZ:" "VX:xzZ:"
/* /*
@@ -3529,8 +3647,13 @@ main(int argc, char *argv[]) {
printstats = true; printstats = true;
break; break;
case 'U': /* Undocumented for testing only. */ case 'U': /* Undocumented test options */
if (!strcmp(isc_commandline_argument, "nonsecify")) {
nonsecify = true;
}
if (!strcmp(isc_commandline_argument, "unknownalg")) {
unknownalg = true; unknownalg = true;
}
break; break;
case 'u': case 'u':
@@ -3561,6 +3684,57 @@ main(int argc, char *argv[]) {
ignore_kskflag = true; ignore_kskflag = true;
break; break;
case 'Z':
add_zonemd = true;
if (strcasecmp(isc_commandline_argument,
"simple-sha384") == 0 ||
strcasecmp(isc_commandline_argument, "sha384") ==
0 ||
strcmp(isc_commandline_argument, "-") == 0)
{
for (size_t i = 0;
i < ARRAY_SIZE(zonemd_scheme); i++)
{
if (zonemd_scheme[i] ==
DNS_ZONEMD_SCHEME_SIMPLE &&
zonemd_digest[i] ==
DNS_ZONEMD_DIGEST_SHA384)
{
break;
} else if (zonemd_scheme[i] == 0) {
zonemd_scheme[i] =
DNS_ZONEMD_SCHEME_SIMPLE;
zonemd_digest[i] =
DNS_ZONEMD_DIGEST_SHA384;
}
}
} else if (strcasecmp(isc_commandline_argument,
"simple-sha512") == 0 ||
strcasecmp(isc_commandline_argument,
"sha512") == 0)
{
for (size_t i = 0;
i < ARRAY_SIZE(zonemd_scheme); i++)
{
if (zonemd_scheme[i] ==
DNS_ZONEMD_SCHEME_SIMPLE &&
zonemd_digest[i] ==
DNS_ZONEMD_DIGEST_SHA512)
{
break;
} else if (zonemd_scheme[i] == 0) {
zonemd_scheme[i] =
DNS_ZONEMD_SCHEME_SIMPLE;
zonemd_digest[i] =
DNS_ZONEMD_DIGEST_SHA512;
}
}
} else {
fatal("unknown ZONEMD scheme/digest");
}
break;
case 'F': case 'F':
if (isc_crypto_fips_enable() != ISC_R_SUCCESS) { if (isc_crypto_fips_enable() != ISC_R_SUCCESS) {
fatal("setting FIPS mode failed"); fatal("setting FIPS mode failed");
@@ -3582,12 +3756,6 @@ main(int argc, char *argv[]) {
/* Does not return. */ /* Does not return. */
version(isc_commandline_progname); version(isc_commandline_progname);
case 'Z': /* Undocumented test options */
if (!strcmp(isc_commandline_argument, "nonsecify")) {
nonsecify = true;
}
break;
default: default:
fprintf(stderr, "%s: unhandled option -%c\n", fprintf(stderr, "%s: unhandled option -%c\n",
isc_commandline_progname, isc_commandline_progname,
@@ -3875,6 +4043,14 @@ main(int argc, char *argv[]) {
/* Remove duplicates and cap TTLs at maxttl */ /* Remove duplicates and cap TTLs at maxttl */
cleanup_zone(); cleanup_zone();
if (add_zonemd) {
for (size_t i = 0; i < ARRAY_SIZE(zonemd_scheme); i++) {
if (zonemd_scheme[i] != 0) {
addzonemd(zonemd_scheme[i], zonemd_digest[i]);
}
}
}
if (!nonsecify) { if (!nonsecify) {
if (IS_NSEC3) { if (IS_NSEC3) {
nsec3ify(dns_hash_sha1, nsec3iter, gsalt, salt_length, nsec3ify(dns_hash_sha1, nsec3iter, gsalt, salt_length,

View File

@@ -21,7 +21,7 @@ dnssec-signzone - DNSSEC zone signing tool
Synopsis Synopsis
~~~~~~~~ ~~~~~~~~
:program:`dnssec-signzone` [**-a**] [**-c** class] [**-d** directory] [**-D**] [**-e** end-time] [**-f** output-file] [**-F**] [**-g**] [**-G sync-records**] [**-h**] [**-i** interval] [**-I** input-format] [**-j** jitter] [**-J** filename] [**-K** directory] [**-k** key] [**-L** serial] [**-M** maxttl] [**-N** soa-serial-format] [**-o** origin] [**-O** output-format] [**-P**] [**-Q**] [**-q**] [**-R**] [**-S**] [**-s** start-time] [**-T** ttl] [**-t**] [**-u**] [**-v** level] [**-V**] [**-X** extended end-time] [**-x**] [**-z**] [**-3** salt] [**-H** iterations] [**-A**] {zonefile} [key...] :program:`dnssec-signzone` [**-a**] [**-c** class] [**-d** directory] [**-D**] [**-e** end-time] [**-f** output-file] [**-F**] [**-g**] [**-G sync-records**] [**-h**] [**-i** interval] [**-I** input-format] [**-j** jitter] [**-J** filename] [**-K** directory] [**-k** key] [**-L** serial] [**-M** maxttl] [**-N** soa-serial-format] [**-o** origin] [**-O** output-format] [**-P**] [**-Q**] [**-q**] [**-R**] [**-S**] [**-s** start-time] [**-T** ttl] [**-t**] [**-u**] [**-v** level] [**-V**] [**-X** extended end-time] [**-x**] [**-z**] [**-Z**] [**-3** salt] [**-H** iterations] [**-A**] {zonefile} [key...]
Description Description
~~~~~~~~~~~ ~~~~~~~~~~~
@@ -401,6 +401,16 @@ Options
all records. This is useful when using the :option:`-u` option to modify an all records. This is useful when using the :option:`-u` option to modify an
NSEC3 chain which previously had OPTOUT set. NSEC3 chain which previously had OPTOUT set.
.. option:: -Z method
This option causes a ZONEMD record to be added to the signed zone,
if there wasn't a ZONEMD already present. The ``method`` parameter
indicates the scheme and digest type to use: valid options are
``simple-sha384`` (or ``sha384``, or ``-``) for scheme SIMPLE
and digest type SHA384, and ``simple-sha512`` (or ``sha512``)
for scheme SIMPLE and digest type SHA512. Multiple ``-Z`` options
can be used simultaneously to add multiple ZONEMD records.
.. option:: zonefile .. option:: zonefile
This option sets the file containing the zone to be signed. This option sets the file containing the zone to be signed.

View File

@@ -57,6 +57,7 @@
#include <dns/rdatatype.h> #include <dns/rdatatype.h>
#include <dns/soa.h> #include <dns/soa.h>
#include <dns/time.h> #include <dns/time.h>
#include <dns/zone.h>
#include <dns/zoneverify.h> #include <dns/zoneverify.h>
#include <dst/dst.h> #include <dst/dst.h>
@@ -134,6 +135,37 @@ loadzone(char *file, char *origin, dns_rdataclass_t rdclass, dns_db_t **db) {
} }
} }
static isc_result_t
check_zonemd(const char *file, const char *jfile, const dns_name_t *origin,
dns_rdataclass_t rdclass, bool required) {
dns_zone_t *zone = NULL;
dns_zoneopt_t options = DNS_ZONEOPT_ZONEMD_CHECK |
DNS_ZONEOPT_ZONEMD_DNSSEC;
isc_result_t result;
if (required) {
options |= DNS_ZONEOPT_ZONEMD_REQUIRED;
}
dns_zone_create(&zone, isc_g_mctx, 0);
dns_zone_settype(zone, dns_zone_primary);
dns_zone_setoption(zone, options, true);
dns_zone_setorigin(zone, origin);
dns_zone_setclass(zone, rdclass);
const char *dbtype[] = { ZONEDB_DEFAULT };
dns_zone_setdbtype(zone, 1, (const char *const *)dbtype);
dns_zone_setfile(zone, file, NULL, inputformat,
&dns_master_style_default);
dns_zone_setjournal(zone, jfile);
result = dns_zone_load(zone, false);
dns_zone_detach(&zone);
return result;
}
ISC_NORETURN static void ISC_NORETURN static void
usage(void); usage(void);
@@ -167,14 +199,15 @@ main(int argc, char *argv[]) {
char *origin = NULL, *file = NULL; char *origin = NULL, *file = NULL;
char *inputformatstr = NULL; char *inputformatstr = NULL;
isc_result_t result; isc_result_t result;
char *classname = NULL; dns_rdataclass_t rdclass = dns_rdataclass_in;
dns_rdataclass_t rdclass; bool zonemd = false, require_zonemd = false;
char *endp; dns_dbnode_t *node = NULL;
char *endp = NULL;
int ch; int ch;
isc_commandline_init(argc, argv); isc_commandline_init(argc, argv);
#define CMDLINE_FLAGS "c:E:hJ:m:o:I:qv:Vxz" #define CMDLINE_FLAGS "c:E:hJ:m:o:I:qv:VxzZ"
/* /*
* Process memory debugging argument first. * Process memory debugging argument first.
@@ -206,7 +239,7 @@ main(int argc, char *argv[]) {
while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) {
switch (ch) { switch (ch) {
case 'c': case 'c':
classname = isc_commandline_argument; rdclass = strtoclass(isc_commandline_argument);
break; break;
case 'E': case 'E':
@@ -248,6 +281,10 @@ main(int argc, char *argv[]) {
ignore_kskflag = true; ignore_kskflag = true;
break; break;
case 'Z':
require_zonemd = true;
break;
case '?': case '?':
if (isc_commandline_option != '?') { if (isc_commandline_option != '?') {
fprintf(stderr, "%s: invalid argument -%c\n", fprintf(stderr, "%s: invalid argument -%c\n",
@@ -274,8 +311,6 @@ main(int argc, char *argv[]) {
now = isc_stdtime_now(); now = isc_stdtime_now();
rdclass = strtoclass(classname);
setup_logging(); setup_logging();
argc -= isc_commandline_index; argc -= isc_commandline_index;
@@ -320,6 +355,17 @@ main(int argc, char *argv[]) {
result = dns_db_newversion(gdb, &gversion); result = dns_db_newversion(gdb, &gversion);
check_result(result, "dns_db_newversion()"); check_result(result, "dns_db_newversion()");
dns_rdataset_t zmset = DNS_RDATASET_INIT;
result = dns_db_getoriginnode(gdb, &node);
check_result(result, "dns_db_getoriginnode()");
result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_zonemd,
0, 0, &zmset, NULL);
if (result == ISC_R_SUCCESS) {
zonemd = true;
dns_rdataset_disassociate(&zmset);
}
dns_db_detachnode(&node);
result = dns_zoneverify_dnssec(NULL, gdb, gversion, gorigin, NULL, result = dns_zoneverify_dnssec(NULL, gdb, gversion, gorigin, NULL,
isc_g_mctx, ignore_kskflag, isc_g_mctx, ignore_kskflag,
keyset_kskonly, report); keyset_kskonly, report);
@@ -327,6 +373,14 @@ main(int argc, char *argv[]) {
dns_db_closeversion(gdb, &gversion, false); dns_db_closeversion(gdb, &gversion, false);
dns_db_detach(&gdb); dns_db_detach(&gdb);
if (zonemd && result == ISC_R_SUCCESS) {
result = check_zonemd(file, journal, gorigin, gclass,
require_zonemd);
report("ZONEMD verification: %s", isc_result_totext(result));
} else if (!zonemd && require_zonemd) {
fatal("Valid ZONEMD required but not present");
}
if (verbose > 10) { if (verbose > 10) {
isc_mem_stats(isc_g_mctx, stdout); isc_mem_stats(isc_g_mctx, stdout);
} }

View File

@@ -27,8 +27,9 @@ Description
~~~~~~~~~~~ ~~~~~~~~~~~
:program:`dnssec-verify` verifies that a zone is fully signed for each :program:`dnssec-verify` verifies that a zone is fully signed for each
algorithm found in the DNSKEY RRset for the zone, and that the algorithm found in the DNSKEY RRset for the zone, that the
NSEC/NSEC3 chains are complete. NSEC/NSEC3 chains are complete, and, if a ZONEMD record is present,
that it is valid.
Options Options
~~~~~~~ ~~~~~~~
@@ -94,6 +95,11 @@ Options
key; the same key may be used for both purposes. This corresponds to key; the same key may be used for both purposes. This corresponds to
the :option:`-z option in dnssec-signzone <dnssec-signzone -z>`. the :option:`-z option in dnssec-signzone <dnssec-signzone -z>`.
.. option:: -Z
This option indicates that a valid ZONEMD record must be present
in the zone.
.. option:: zonefile .. option:: zonefile
This option indicates the file containing the zone to be signed. This option indicates the file containing the zone to be signed.

View File

@@ -0,0 +1,16 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
dnssec-policy "wrong" {
zonemd sha384;
};

View File

@@ -253,7 +253,7 @@ keyname=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone")
cat "$infile" "$keyname.key" >"$zonefile" cat "$infile" "$keyname.key" >"$zonefile"
"$SIGNER" -z -3 - -PU -o "$zone" "$zonefile" >/dev/null "$SIGNER" -z -3 - -P -U unknownalg -o "$zone" "$zonefile" >/dev/null
# #
# A optout nsec3 zone with a unknown nsec3 hash algorithm (-U). # A optout nsec3 zone with a unknown nsec3 hash algorithm (-U).
@@ -266,7 +266,7 @@ keyname=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone")
cat "$infile" "$keyname.key" >"$zonefile" cat "$infile" "$keyname.key" >"$zonefile"
"$SIGNER" -z -3 - -PU -A -o "$zone" "$zonefile" >/dev/null "$SIGNER" -z -3 - -P -U unknownalg -A -o "$zone" "$zonefile" >/dev/null
# #
# A zone that is signed with an unknown DNSKEY algorithm. # A zone that is signed with an unknown DNSKEY algorithm.
@@ -375,7 +375,7 @@ keyname=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone")
cat "$infile" "$keyname.key" >"$zonefile" cat "$infile" "$keyname.key" >"$zonefile"
"$SIGNER" -z -3 - -o "$zone" -PU -O full -f ${zonefile}.tmp "$zonefile" >/dev/null "$SIGNER" -z -3 - -o "$zone" -P -U unknownalg -O full -f ${zonefile}.tmp "$zonefile" >/dev/null
awk '$4 == "DNSKEY" { $7 = 100; print } $4 == "RRSIG" { $6 = 100; print } { print }' ${zonefile}.tmp >${zonefile}.signed awk '$4 == "DNSKEY" { $7 = 100; print } $4 == "RRSIG" { $6 = 100; print } { print }' ${zonefile}.tmp >${zonefile}.signed

View File

@@ -0,0 +1,20 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
; This is a zone which has two DNSKEY records, both of which have
; existing private key files available. They should be loaded automatically
; and the zone correctly signed.
;
$TTL 3600
example.com. IN SOA ns hostmaster 00090000 1200 3600 604800 300
example.com. IN ZONEMD 0 1 1 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
$include Kexample.com.+010+18240.key
$include Kexample.com.+010+28633.key

View File

@@ -0,0 +1,21 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
; This is a zone which has two DNSKEY records, both of which have
; existing private key files available. They should be loaded automatically
; and the zone correctly signed.
;
$TTL 3600
example.com. IN SOA ns hostmaster 00090000 1200 3600 604800 300
example.com. IN ZONEMD 0 1 1 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
example.com. IN ZONEMD 0 1 2 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
$include Kexample.com.+010+18240.key
$include Kexample.com.+010+28633.key

View File

@@ -245,6 +245,40 @@ n=$((n + 1))
test "$ret" -eq 0 || echo_i "failed" test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret)) status=$((status + ret))
echo_ic "check that dnssec-signzone updates a ZONEMD record ($n)"
ret=0
(
cd signer/general || exit 1
rm -f signed.zone
$SIGNER -f signed.zone -o example.com. test13.zone >signer.out.$n
count=$(grep -c "ZONEMD.90000 1 1 " signed.zone)
test $count -eq 1 || exit 1
count=$(grep -c "RRSIG.ZONEMD" signed.zone)
test $count -eq 1 || exit 1
test -f signed.zone
) || ret=1
n=$((n + 1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
echo_ic "check that dnssec-signzone updates both ZONEMD records ($n)"
ret=0
(
cd signer/general || exit 1
rm -f signed.zone
$SIGNER -f signed.zone -o example.com. test14.zone >signer.out.$n
count=$(grep -c "ZONEMD.90000 1 1 " signed.zone)
test $count -eq 1 || exit 1
count=$(grep -c "ZONEMD.90000 1 2 " signed.zone)
test $count -eq 1 || exit 1
count=$(grep -c "RRSIG.ZONEMD" signed.zone)
test $count -eq 1 || exit 1
test -f signed.zone
) || ret=1
n=$((n + 1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
get_default_algorithm_key_ids_from_sigs() { get_default_algorithm_key_ids_from_sigs() {
zone=$1 zone=$1

View File

@@ -59,8 +59,13 @@ for variant in addzone axfr ixfr load reconfig untrusted; do
cat $infile $keyname1.key $keyname2.key >$zonefile cat $infile $keyname1.key $keyname2.key >$zonefile
# for axfr and ixfr variants, add a ZONEMD record
if [ "$variant" = "axfr" -o "$variant" = "ixfr" ]; then
zopt="-Z-"
fi
# Prepare a properly signed version of the zone ("*.original.signed"). # Prepare a properly signed version of the zone ("*.original.signed").
$SIGNER -P -o $zone $zonefile >/dev/null $SIGNER $zopt -P -o $zone $zonefile >/dev/null
cp $zonefile.signed $zonefile.original.signed cp $zonefile.signed $zonefile.original.signed
# Prepare a version of the zone with a bogus SOA RRSIG ("*.bad.signed"). # Prepare a version of the zone with a bogus SOA RRSIG ("*.bad.signed").
sed "s/${ORIGINAL_SERIAL}/${UPDATED_SERIAL_BAD}/;" $zonefile.signed >$zonefile.bad.signed sed "s/${ORIGINAL_SERIAL}/${UPDATED_SERIAL_BAD}/;" $zonefile.signed >$zonefile.bad.signed

View File

@@ -62,7 +62,7 @@ keyname=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} $zone)
cat "$infile" "$keyname.key" >"$zonefile" cat "$infile" "$keyname.key" >"$zonefile"
# do not regenerate NSEC chain as there in a minimal NSEC record present # do not regenerate NSEC chain as there in a minimal NSEC record present
$SIGNER -P -Z nonsecify -o $zone $zonefile >/dev/null $SIGNER -P -U nonsecify -o $zone $zonefile >/dev/null
zone=soa-without-dnskey zone=soa-without-dnskey
infile=soa-without-dnskey.db.in infile=soa-without-dnskey.db.in
@@ -72,7 +72,7 @@ keyname=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} $zone)
cat "$infile" "$keyname.key" >"$zonefile" cat "$infile" "$keyname.key" >"$zonefile"
# do not regenerate NSEC chain as there in a minimal NSEC record present # do not regenerate NSEC chain as there in a minimal NSEC record present
$SIGNER -P -Z nonsecify -o $zone $zonefile >/dev/null $SIGNER -P -U nonsecify -o $zone $zonefile >/dev/null
zone=. zone=.
infile=root.db.in infile=root.db.in

View File

@@ -184,3 +184,37 @@ def test_verify_j_reads_journal_file():
] ]
).stdout.decode("utf-8") ).stdout.decode("utf-8")
assert "Loading zone 'updated' from file 'zones/updated.other'" in output assert "Loading zone 'updated' from file 'zones/updated.other'" in output
@pytest.mark.parametrize(
"zone",
[
"ksk+zsk.zonemd.nsec",
"ksk+zsk.zonemd.nsec3",
],
)
def test_verify_zonemd(zone):
output = isctest.run.cmd(
[VERIFY, "-o", zone, f"zones/{zone}.good"],
raise_on_exception=False,
log_stdout=True,
)
stream = (output.stdout + output.stderr).decode("utf-8").replace("\n", "")
assert "ZONEMD verification: success" in stream
@pytest.mark.parametrize(
"zone",
[
"ksk+zsk.nsec",
"ksk+zsk.nsec3",
],
)
def test_verify_no_zonemd(zone):
output = isctest.run.cmd(
[VERIFY, "-Zo", zone, f"zones/{zone}.good"],
raise_on_exception=False,
log_stdout=True,
)
stream = (output.stdout + output.stderr).decode("utf-8").replace("\n", "")
assert "Valid ZONEMD required but not present" in stream

View File

@@ -169,7 +169,7 @@ ksk=$($KEYGEN -a ${DEFAULT_ALGORITHM} -fK ${zone} 2>kg2.out$n) || dumpit kg2.out
cat unsigned.db $ksk.key $zsk.key >$file cat unsigned.db $ksk.key $zsk.key >$file
$SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk >s.out$n || dumpit s.out$n $SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk >s.out$n || dumpit s.out$n
awk '$4 == "NSEC" { $5 = "'$zone'."; print } { print }' ${file} >${file}.tmp awk '$4 == "NSEC" { $5 = "'$zone'."; print } { print }' ${file} >${file}.tmp
$SIGNER -Px -Z nonsecify -o ${zone} -f ${file} ${file}.tmp $zsk >s.out$n || dumpit s.out$n $SIGNER -Px -U nonsecify -o ${zone} -f ${file} ${file}.tmp $zsk >s.out$n || dumpit s.out$n
# bad nsec bitmap # bad nsec bitmap
setup ksk+zsk.nsec.bad-bitmap bad setup ksk+zsk.nsec.bad-bitmap bad
@@ -178,7 +178,7 @@ ksk=$($KEYGEN -a ${DEFAULT_ALGORITHM} -fK ${zone} 2>kg2.out$n) || dumpit kg2.out
cat unsigned.db $ksk.key $zsk.key >$file cat unsigned.db $ksk.key $zsk.key >$file
$SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk >s.out$n || dumpit s.out$n $SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk >s.out$n || dumpit s.out$n
awk '$4 == "NSEC" && /SOA/ { $6=""; print } { print }' ${file} >${file}.tmp awk '$4 == "NSEC" && /SOA/ { $6=""; print } { print }' ${file} >${file}.tmp
$SIGNER -Px -Z nonsecify -o ${zone} -f ${file} ${file}.tmp $zsk >s.out$n || dumpit s.out$n $SIGNER -Px -U nonsecify -o ${zone} -f ${file} ${file}.tmp $zsk >s.out$n || dumpit s.out$n
# extra NSEC record out side of zone # extra NSEC record out side of zone
setup ksk+zsk.nsec.out-of-zone-nsec bad setup ksk+zsk.nsec.out-of-zone-nsec bad
@@ -187,7 +187,7 @@ ksk=$($KEYGEN -a ${DEFAULT_ALGORITHM} -fK ${zone} 2>kg2.out$n) || dumpit kg2.out
cat unsigned.db $ksk.key $zsk.key >$file cat unsigned.db $ksk.key $zsk.key >$file
$SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk >s.out$n || dumpit s.out$n $SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk >s.out$n || dumpit s.out$n
echo "out-of-zone. 3600 IN NSEC ${zone}. A" >>${file} 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 || dumpit s.out$n $SIGNER -Px -U nonsecify -O full -o ${zone} -f ${file} ${file} $zsk >s.out$n || dumpit s.out$n
# extra NSEC record below bottom of zone # extra NSEC record below bottom of zone
setup ksk+zsk.nsec.below-bottom-of-zone-nsec bad setup ksk+zsk.nsec.below-bottom-of-zone-nsec bad
@@ -196,7 +196,7 @@ ksk=$($KEYGEN -a ${DEFAULT_ALGORITHM} -fK ${zone} 2>kg2.out$n) || dumpit kg2.out
cat unsigned.db $ksk.key $zsk.key >$file cat unsigned.db $ksk.key $zsk.key >$file
$SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk >s.out$n || dumpit s.out$n $SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk >s.out$n || dumpit s.out$n
echo "ns.sub.${zone}. 3600 IN NSEC ${zone}. A AAAA" >>${file} 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 || dumpit s.out$n $SIGNER -Px -U nonsecify -O full -o ${zone} -f ${file}.tmp ${file} $zsk >s.out$n || dumpit s.out$n
# dnssec-signzone signs any node with a NSEC record. # dnssec-signzone signs any node with a NSEC record.
awk '$1 ~ /^ns.sub/ && $4 == "RRSIG" && $5 != "NSEC" { next; } { print; }' ${file}.tmp >${file} awk '$1 ~ /^ns.sub/ && $4 == "RRSIG" && $5 != "NSEC" { next; } { print; }' ${file}.tmp >${file}
@@ -207,7 +207,7 @@ ksk=$($KEYGEN -a ${DEFAULT_ALGORITHM} -fK ${zone} 2>kg2.out$n) || dumpit kg2.out
cat unsigned.db $ksk.key $zsk.key >$file cat unsigned.db $ksk.key $zsk.key >$file
$SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk >s.out$n || dumpit s.out$n $SIGNER -P -O full -o ${zone} -f ${file} ${file} $ksk >s.out$n || dumpit s.out$n
echo "sub.dname.${zone}. 3600 IN NSEC ${zone}. TXT" >>${file} echo "sub.dname.${zone}. 3600 IN NSEC ${zone}. TXT" >>${file}
$SIGNER -Px -Z nonsecify -O full -o ${zone} -f ${file} ${file} $zsk >s.out$n || dumpit s.out$n $SIGNER -Px -U nonsecify -O full -o ${zone} -f ${file} ${file} $zsk >s.out$n || dumpit s.out$n
# missing NSEC3 record at empty node # missing NSEC3 record at empty node
# extract the hash fields from the empty node's NSEC 3 record then fix up # extract the hash fields from the empty node's NSEC 3 record then fix up
@@ -223,7 +223,7 @@ awk '
$4 == "NSEC3" && $9 == "'$a'" { $9 = "'$b'"; print; next; } $4 == "NSEC3" && $9 == "'$a'" { $9 = "'$b'"; print; next; }
$4 == "NSEC3" && NF == 9 { next; } $4 == "NSEC3" && NF == 9 { next; }
{ print; }' ${file} >${file}.tmp { print; }' ${file} >${file}.tmp
$SIGNER -3 - -Px -Z nonsecify -O full -o ${zone} -f ${file} ${file}.tmp $zsk >s.out$n || dumpit s.out$n $SIGNER -3 - -Px -U nonsecify -O full -o ${zone} -f ${file} ${file}.tmp $zsk >s.out$n || dumpit s.out$n
# extra NSEC3 record # extra NSEC3 record
setup ksk+zsk.nsec3.extra-nsec3 bad setup ksk+zsk.nsec3.extra-nsec3 bad
@@ -242,7 +242,7 @@ $4 == "NSEC3" && NF == 9 {
}' ${file} >${file}.tmp }' ${file} >${file}.tmp
cat ${file}.tmp >>${file} cat ${file}.tmp >>${file}
rm -f ${file}.tmp rm -f ${file}.tmp
$SIGNER -3 - -Px -Z nonsecify -O full -o ${zone} -f ${file} ${file} $zsk >s.out$n || dumpit s.out$n $SIGNER -3 - -Px -U nonsecify -O full -o ${zone} -f ${file} ${file} $zsk >s.out$n || dumpit s.out$n
# sign and verify with journal file # sign and verify with journal file
setup updated other setup updated other
@@ -254,3 +254,15 @@ sed -e '/serial/s/0/1/' $file >${file}.update
echo "extra 3600 IN A 4.3.2.1" >>${file}.update echo "extra 3600 IN A 4.3.2.1" >>${file}.update
$SIGNER -SPx -o ${zone} -f ${file}.update ${file}.update >s.out$n || dumpit s.out$n $SIGNER -SPx -o ${zone} -f ${file}.update ${file}.update >s.out$n || dumpit s.out$n
$MAKEJOURNAL updated ${file} ${file}.update ${file}.jnl >mj.out$n 2>&1 || dumpit mj.out$n $MAKEJOURNAL updated ${file} ${file}.update ${file}.jnl >mj.out$n 2>&1 || dumpit mj.out$n
# NSEC zone with ZONEMD.
setup ksk+zsk.zonemd.nsec good
$KEYGEN -a ${DEFAULT_ALGORITHM} ${zone} >kg1.out$n 2>&1 || dumpit kg1.out$n
$KEYGEN -a ${DEFAULT_ALGORITHM} -fK ${zone} >kg2.out$n 2>&1 || dumpit kg2.out$n
$SIGNER -Z simple-sha384 -SPx -o ${zone} -f ${file} unsigned.db >s.out$n || dumpit s.out$n
# NSEC3 zone with ZONEMD.
setup ksk+zsk.zonemd.nsec3 good
$KEYGEN -a ${DEFAULT_ALGORITHM} ${zone} >kg1.out$n 2>&1 || dumpit kg1.out$n
$KEYGEN -a ${DEFAULT_ALGORITHM} -fK ${zone} >kg2.out$n 2>&1 || dumpit kg2.out$n
$SIGNER -3 - -Z simple-sha512 -SPx -o ${zone} -f ${file} unsigned.db >s.out$n || dumpit s.out$n