Commit 8ca60f70 authored by Mark Andrews's avatar Mark Andrews

Merge branch...

Merge branch '240-multiple-rrsigs-on-some-records-in-signed-zone-even-though-only-one-key-is-ever-active-at-a-time-v9_12-v9_11' into 'v9_11'

Resolve "Multiple RRSIGs on some records in signed zone even though only one key is ever active at a time"

Closes #240

See merge request !231
parents 5ab633d5 1c8aa284
Pipeline #2186 passed with stages
in 5 minutes and 59 seconds
4964. [bug] Reduce the probabilty of double signature when deleting
a DNSKEY by checking if the node is otherwise signed
by the algorithm of the key to be deleted. [GL #240]
4963. [test] ifconfig.sh now uses "ip" instead of "ifconfig",
if available, to configure the test interfaces on
linux. [GL #302]
......
......@@ -182,6 +182,7 @@ EXTERN isc_boolean_t ns_g_disable6 INIT(ISC_FALSE);
EXTERN isc_boolean_t ns_g_disable4 INIT(ISC_FALSE);
EXTERN unsigned int ns_g_tat_interval INIT(24*3600);
EXTERN isc_boolean_t ns_g_fixedlocal INIT(ISC_FALSE);
EXTERN isc_boolean_t ns_g_sigvalinsecs INIT(ISC_FALSE);
#ifdef HAVE_GEOIP
EXTERN dns_geoip_databases_t *ns_g_geoip INIT(NULL);
......
......@@ -532,6 +532,8 @@ parse_T_opt(char *option) {
if (dns_zone_mkey_month < dns_zone_mkey_day) {
ns_main_earlyfatal("bad mkeytimer");
}
} else if (!strcmp(option, "sigvalinsecs")) {
ns_g_sigvalinsecs = ISC_TRUE;
} else if (!strncmp(option, "tat=", 4)) {
ns_g_tat_interval = atoi(option + 4);
} else {
......
......@@ -1349,31 +1349,33 @@ ns_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
}
if (ztype == dns_zone_master || raw != NULL) {
const cfg_obj_t *validity, *resign;
isc_boolean_t allow = ISC_FALSE, maint = ISC_FALSE;
obj = NULL;
result = ns_config_get(maps, "sig-validity-interval", &obj);
INSIST(result == ISC_R_SUCCESS && obj != NULL);
{
const cfg_obj_t *validity, *resign;
validity = cfg_tuple_get(obj, "validity");
seconds = cfg_obj_asuint32(validity) * 86400;
dns_zone_setsigvalidityinterval(zone, seconds);
resign = cfg_tuple_get(obj, "re-sign");
if (cfg_obj_isvoid(resign)) {
seconds /= 4;
validity = cfg_tuple_get(obj, "validity");
seconds = cfg_obj_asuint32(validity);
if (!ns_g_sigvalinsecs) {
seconds *= 86400;
}
dns_zone_setsigvalidityinterval(zone, seconds);
resign = cfg_tuple_get(obj, "re-sign");
if (cfg_obj_isvoid(resign)) {
seconds /= 4;
} else if (!ns_g_sigvalinsecs) {
if (seconds > 7 * 86400) {
seconds = cfg_obj_asuint32(resign) * 86400;
} else {
if (seconds > 7 * 86400)
seconds = cfg_obj_asuint32(resign) *
86400;
else
seconds = cfg_obj_asuint32(resign) *
3600;
seconds = cfg_obj_asuint32(resign) * 3600;
}
dns_zone_setsigresigninginterval(zone, seconds);
} else {
seconds = cfg_obj_asuint32(resign);
}
dns_zone_setsigresigninginterval(zone, seconds);
obj = NULL;
result = ns_config_get(maps, "key-directory", &obj);
......
......@@ -75,8 +75,8 @@ KRB5_CONFIG=/dev/null
# The "stress" test is not run by default since it creates enough
# load on the machine to make it unusable to other users.
# The "dialup" and "delzone" tests are also not run by default because
# they take a very long time to complete.
# The "dialup", "delzone", and "dupsigs" tests are also not run by
# default because they take a very long time to complete.
#
# List of tests hard-coded to use ports 5300 and 9953. For this
# reason, these must be run sequentially.
......
......@@ -85,7 +85,8 @@ KRB5_CONFIG=NUL
# The "stress" test is not run by default since it creates enough
# load on the machine to make it unusable to other users.
# v6synth
# The "dialup", "delzone", and "dupsigs" tests are also not run by
# default because they take a very long time to complete.
#
# List of tests that use ports 5300 and 9953. For this reason, these must
# be run sequentially.
......
#!/usr/bin/env perl
#
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
use strict;
use warnings;
sub process_changeset;
my @changeset;
while( my $line = <> ) {
chomp $line;
if( $line =~ /^(?<op>add|del) (?<label>\S+)\s+(?<ttl>\d+)\s+IN\s+(?<rrtype>\S+)\s+(?<rdata>.*)/ ) {
my $change = {
op => $+{op},
label => $+{label},
ttl => $+{ttl},
rrtype => $+{rrtype},
rdata => $+{rdata},
};
if( $change->{op} eq 'del' and $change->{rrtype} eq 'SOA' ) {
if( @changeset ) {
process_changeset( @changeset );
@changeset = ();
}
}
push @changeset, $change;
}
else {
die "error parsing journal data";
}
}
if( @changeset ) {
process_changeset( @changeset );
}
{
my %rrsig_db;
my %keys;
my $apex;
sub process_changeset {
my @changeset = @_;
if( not $apex ) {
# the first record of the first changeset is guaranteed to be the apex
$apex = $changeset[0]{label};
}
my $newserial;
my %touched_rrsigs;
my %touched_keys;
foreach my $change( @changeset ) {
if( $change->{rrtype} eq 'SOA' ) {
if( $change->{op} eq 'add' ) {
if( $change->{rdata} !~ /^\S+ \S+ (?<serial>\d+)/ ) {
die "unable to parse SOA";
}
$newserial = $+{serial};
}
}
elsif( $change->{rrtype} eq 'NSEC' ) {
; # do nothing
}
elsif( $change->{rrtype} eq 'DNSKEY' ) {
; # ignore for now
}
elsif( $change->{rrtype} eq 'TYPE65534' and $change->{label} eq $apex ) {
# key status
if( $change->{rdata} !~ /^\\# (?<datasize>\d+) (?<data>[0-9A-F]+)$/ ) {
die "unable to parse key status record";
}
my $datasize = $+{datasize};
my $data = $+{data};
if( $datasize == 5 ) {
my( $alg, $id, $flag_del, $flag_done ) = unpack 'CnCC', pack( 'H10', $data );
if( $change->{op} eq 'add' ) {
if( not exists $keys{$id} ) {
$touched_keys{$id} //= 1;
$keys{$id} = {
$data => 1,
rrs => 1,
done_signing => $flag_done,
deleting => $flag_del,
};
}
else {
if( not exists $keys{$id}{$data} ) {
my $keydata = $keys{$id};
$touched_keys{$id} = { %$keydata };
$keydata->{rrs}++;
$keydata->{$data} = 1;
$keydata->{done_signing} += $flag_done;
$keydata->{deleting} += $flag_del;
}
}
}
else {
# this logic relies upon the convention that there won't
# ever be multiple records with the same flag set
if( exists $keys{$id} ) {
my $keydata = $keys{$id};
if( exists $keydata->{$data} ) {
$touched_keys{$id} = { %$keydata };
$keydata->{rrs}--;
delete $keydata->{$data};
$keydata->{done_signing} -= $flag_done;
$keydata->{deleting} -= $flag_del;
if( $keydata->{rrs} == 0 ) {
delete $keys{$id};
}
}
}
}
}
else {
die "unexpected key status record content";
}
}
elsif( $change->{rrtype} eq 'RRSIG' ) {
if( $change->{rdata} !~ /^(?<covers>\S+) \d+ \d+ \d+ (?<validity_end>\d+) (?<validity_start>\d+) (?<signing_key>\d+)/ ) {
die "unable to parse RRSIG rdata";
}
$change->{covers} = $+{covers};
$change->{validity_end} = $+{validity_end};
$change->{validity_start} = $+{validity_start};
$change->{signing_key} = $+{signing_key};
my $db_key = $change->{label} . ':' . $change->{covers};
$rrsig_db{$db_key} //= {};
$touched_rrsigs{$db_key} = 1;
if( $change->{op} eq 'add' ) {
$rrsig_db{$db_key}{ $change->{signing_key} } = 1;
}
else {
# del
delete $rrsig_db{$db_key}{ $change->{signing_key} };
}
}
}
foreach my $key_id( sort keys %touched_keys ) {
my $old_data;
my $new_data;
if( ref $touched_keys{$key_id} ) {
$old_data = $touched_keys{$key_id};
}
if( exists $keys{$key_id} ) {
$new_data = $keys{$key_id};
}
if( $old_data ) {
if( $new_data ) {
print "at serial $newserial key $key_id status changed from ($old_data->{deleting},$old_data->{done_signing}) to ($new_data->{deleting},$new_data->{done_signing})\n";
}
else {
print "at serial $newserial key $key_id status removed from zone\n";
}
}
else {
print "at serial $newserial key $key_id status added with flags ($new_data->{deleting},$new_data->{done_signing})\n";
}
}
foreach my $rrsig_id( sort keys %touched_rrsigs ) {
my $n_signing_keys = keys %{ $rrsig_db{$rrsig_id} };
if( $n_signing_keys == 0 ) {
print "at serial $newserial $rrsig_id went unsigned\n";
}
elsif( $rrsig_id =~ /:DNSKEY$/ ) {
if( $n_signing_keys != 2 ) {
print "at serial $newserial $rrsig_id was signed $n_signing_keys time(s) when it should have been signed twice\n";
}
}
elsif( $n_signing_keys > 1 ) {
my @signing_keys = sort { $a <=> $b } keys %{ $rrsig_db{$rrsig_id} };
print "at serial $newserial $rrsig_id was signed too many times, keys (@signing_keys)\n";
}
}
}
}
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
rm -f ns1/named.conf
rm -f ns1/named.lock
rm -f ns1/named.memstats
rm -f ns1/named.run
rm -f ns1/signing.test.db
rm -f ns1/signing.test.db.jbk
rm -f ns1/signing.test.db.signed
rm -f ns1/signing.test.db.signed.jnl
rm -f ns1/keys/signing.test/K*
rm -f ns1/managed-keys.bind*
-D dupsigs-ns1 -X named.lock -m record,size,mctx -T clienttest -c named.conf -d 99 -g -U 4 -T sigvalinsecs
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
options {
recursion no;
max-journal-size unlimited;
port @PORT@;
listen-on { 10.53.0.1; };
listen-on-v6 { none; };
pid-file "named.pid";
};
zone "signing.test" {
type master;
masterfile-format text;
allow-update { any; };
file "signing.test.db";
update-check-ksk yes;
key-directory "keys/signing.test";
inline-signing yes;
auto-dnssec maintain;
sig-validity-interval 120 30;
};
#!/bin/sh
#
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
SYSTEMTESTTOP=../..
. $SYSTEMTESTTOP/conf.sh
zone=signing.test
rm -rf keys/signing.test
mkdir -p keys/signing.test
timetodnssec() {
$PERL -e 'my ($S,$M,$H,$d,$m,$y,$x) = gmtime(@ARGV[0]);
printf("%04u%02u%02u%02u%02u%02u\n", $y+1900,$m+1,$d,$H,$M,$S);' ${1}
}
KEYDIR=keys/signing.test
KSK=`$KEYGEN -a RSASHA256 -b 1024 -K $KEYDIR -q -f KSK $zone`
ZSK0=`$KEYGEN -a RSASHA256 -b 1024 -K $KEYDIR -q $zone`
ZSK1=`$KEYGEN -a RSASHA256 -b 1024 -K $KEYDIR -q $zone`
ZSK2=`$KEYGEN -a RSASHA256 -b 1024 -K $KEYDIR -q $zone`
ZSK3=`$KEYGEN -a RSASHA256 -b 1024 -K $KEYDIR -q $zone`
ZSK4=`$KEYGEN -a RSASHA256 -b 1024 -K $KEYDIR -q $zone`
ZSK5=`$KEYGEN -a RSASHA256 -b 1024 -K $KEYDIR -q $zone`
ZSK6=`$KEYGEN -a RSASHA256 -b 1024 -K $KEYDIR -q $zone`
ZSK7=`$KEYGEN -a RSASHA256 -b 1024 -K $KEYDIR -q $zone`
ZSK8=`$KEYGEN -a RSASHA256 -b 1024 -K $KEYDIR -q $zone`
ZSK9=`$KEYGEN -a RSASHA256 -b 1024 -K $KEYDIR -q $zone`
# clear all times on all keys
for FILEN in keys/signing.test/*.key
do
$SETTIME -P none -A none -R none -I none -D none $FILEN
done
BASE=`date +%s`
BASET=`timetodnssec $BASE`
# reset the publish and activation time on the KSK
$SETTIME -P $BASET -A $BASET $KEYDIR/$KSK
# reset the publish and activation time on the first ZSK
$SETTIME -P $BASET -A $BASET $KEYDIR/$ZSK0
# schedule the first roll
R1=`expr $BASE + 300`
R1T=`timetodnssec $R1`
$SETTIME -I $R1T $KEYDIR/$ZSK0
$SETTIME -P $BASET -A $R1T $KEYDIR/$ZSK1
# schedule the second roll (which includes the delete of the first key)
R2=`expr $R1 + 300`
R2T=`timetodnssec $R2`
DT=$R2
DTT=`timetodnssec $DT`
$SETTIME -D $DTT $KEYDIR/$ZSK0
$SETTIME -I $R2T $KEYDIR/$ZSK1
$SETTIME -P $R1T -A $R2T $KEYDIR/$ZSK2
# schedule the third roll
# this isn't long enough for the signing to complete
R3=`expr $R2 + 60`
R3T=`timetodnssec $R3`
$SETTIME -D $R3T $KEYDIR/$ZSK1
$SETTIME -I $R3T $KEYDIR/$ZSK2
$SETTIME -P $R2T -A $R3T $KEYDIR/$ZSK3
$SETTIME -P $R3T $KEYDIR/$ZSK4
echo KSK=$KSK
echo ZSK0=$ZSK0
echo ZSK1=$ZSK1
echo ZSK2=$ZSK2
echo ZSK3=$ZSK3
echo ZSK4=$ZSK4
exit
# schedule the fourth roll
# this isn't long enough for the signing to complete
R4=`expr $R3 + 30`
R4T=`timetodnssec $R4`
$SETTIME -D $R4T $KEYDIR/$ZSK2
$SETTIME -I $R4T $KEYDIR/$ZSK3
$SETTIME -P $R3T -A $R4T $KEYDIR/$ZSK4
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
$TTL 3600
@ IN SOA ns root.ns 1996072700 3600 1800 86400 60
@ NS ns
ns A 127.0.0.1
ns AAAA ::1
$GENERATE 0-1999 a${0,4,d} AAAA ::$
#!/bin/sh
#
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
SYSTEMTESTTOP=..
. $SYSTEMTESTTOP/conf.sh
exec $SHELL ../testcrypto.sh
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
SYSTEMTESTTOP=..
. $SYSTEMTESTTOP/conf.sh
test -f clean.sh && $SHELL clean.sh
test -r $RANDFILE || $GENRANDOM 800 $RANDFILE
copy_setports ns1/named.conf.in ns1/named.conf
cp -f ns1/signing.test.db.in ns1/signing.test.db
(cd ns1; $SHELL ./reset_keys.sh)
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
SYSTEMTESTTOP=..
. $SYSTEMTESTTOP/conf.sh
status=0
start=`date +%s`
end=`expr $start + 1200`
now=$start
while test $now -lt $end
do
et=`expr $now - $start`
echo "=============== $et ============"
$JOURNALPRINT ns1/signing.test.db.signed.jnl | $PERL check_journal.pl
$DIG axfr signing.test -p 5300 @10.53.0.1 > dig.out.at$et
awk '$4 == "RRSIG" { print $11 }' dig.out.at$et | sort | uniq -c
lines=`awk '$4 == "RRSIG" { print}' dig.out.at$et | wc -l`
if [ ${et} -ne 0 -a ${lines} -ne 4009 ]
then
echo_i "failed"
status=`expr $status + 1`
fi
sleep 20
now=`date +%s`
done
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1
......@@ -812,7 +812,8 @@ static void zone_maintenance(dns_zone_t *zone);
static void zone_notify(dns_zone_t *zone, isc_time_t *now);
static void dump_done(void *arg, isc_result_t result);
static isc_result_t zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm,
isc_uint16_t keyid, isc_boolean_t deleteit);
isc_uint16_t keyid,
isc_boolean_t deleteit);
static isc_result_t delete_nsec(dns_db_t *db, dns_dbversion_t *ver,
dns_dbnode_t *node, dns_name_t *name,
dns_diff_t *diff);
......@@ -6434,7 +6435,7 @@ zone_resigninc(dns_zone_t *zone) {
isc_boolean_t check_ksk, keyset_kskonly = ISC_FALSE;
isc_result_t result;
isc_stdtime_t now, inception, soaexpire, expire, stop;
isc_uint32_t jitter;
isc_uint32_t jitter, sigvalidityinterval;
unsigned int i;
unsigned int nkeys = 0;
unsigned int resign;
......@@ -6479,15 +6480,25 @@ zone_resigninc(dns_zone_t *zone) {
goto failure;
}
sigvalidityinterval = zone->sigvalidityinterval;
inception = now - 3600; /* Allow for clock skew. */
soaexpire = now + dns_zone_getsigvalidityinterval(zone);
soaexpire = now + sigvalidityinterval;
/*
* Spread out signatures over time if they happen to be
* clumped. We don't do this for each add_sigs() call as
* we still want some clustering to occur.
*/
isc_random_get(&jitter);
expire = soaexpire - jitter % 3600 - 1;
if (sigvalidityinterval >= 3600U) {
isc_random_get(&jitter);
if (sigvalidityinterval > 7200U) {
jitter %= 3600;
} else {
jitter %= 1200;
}
expire = soaexpire - jitter - 1;
} else {
expire = soaexpire - 1;
}
stop = now + 5;
check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK);
......@@ -7381,7 +7392,7 @@ zone_nsec3chain(dns_zone_t *zone) {
isc_boolean_t first;
isc_result_t result;
isc_stdtime_t now, inception, soaexpire, expire;
isc_uint32_t jitter;
isc_uint32_t jitter, sigvalidityinterval;
unsigned int i;
unsigned int nkeys = 0;
isc_uint32_t nodes;
......@@ -7450,16 +7461,26 @@ zone_nsec3chain(dns_zone_t *zone) {
goto failure;
}
sigvalidityinterval = dns_zone_getsigvalidityinterval(zone);
inception = now - 3600; /* Allow for clock skew. */
soaexpire = now + dns_zone_getsigvalidityinterval(zone);
soaexpire = now + sigvalidityinterval;
/*
* Spread out signatures over time if they happen to be
* clumped. We don't do this for each add_sigs() call as
* we still want some clustering to occur.
*/
isc_random_get(&jitter);
expire = soaexpire - jitter % 3600;
if (sigvalidityinterval >= 3600U) {
isc_random_get(&jitter);
if (sigvalidityinterval > 7200U) {
jitter %= 3600;
} else {
jitter %= 1200;
}
expire = soaexpire - jitter - 1;
} else {
expire = soaexpire - 1;
}
check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK);
keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY);
......@@ -8187,15 +8208,26 @@ zone_nsec3chain(dns_zone_t *zone) {
INSIST(version == NULL);
}
/*%
* Delete all RRSIG records with the given algorithm and keyid.
* Remove the NSEC record and RRSIGs if nkeys is zero.
* If all remaining RRsets are signed with the given algorithm
* set *has_algp to ISC_TRUE.
*/
static isc_result_t
del_sig(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name,
dns_dbnode_t *node, unsigned int nkeys, dns_secalg_t algorithm,
isc_uint16_t keyid, dns_diff_t *diff)
isc_uint16_t keyid, isc_boolean_t *has_algp, dns_diff_t *diff)
{
dns_rdata_rrsig_t rrsig;
dns_rdataset_t rdataset;
dns_rdatasetiter_t *iterator = NULL;
isc_result_t result;
isc_boolean_t alg_missed = ISC_FALSE;
isc_boolean_t alg_found = ISC_FALSE;
char namebuf[DNS_NAME_FORMATSIZE];
dns_name_format(name, namebuf, sizeof(namebuf));
result = dns_db_allrdatasets(db, node, version, 0, &iterator);
if (result != ISC_R_SUCCESS) {
......@@ -8208,6 +8240,7 @@ del_sig(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name,
for (result = dns_rdatasetiter_first(iterator);
result == ISC_R_SUCCESS;
result = dns_rdatasetiter_next(iterator)) {
isc_boolean_t has_alg = ISC_FALSE;
dns_rdatasetiter_current(iterator, &rdataset);
if (nkeys == 0 && rdataset.type == dns_rdatatype_nsec) {
for (result = dns_rdataset_first(&rdataset);
......@@ -8230,13 +8263,20 @@ del_sig(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name,
}