Merge branch '763-matthijs-active-zsk-but-ksk-only-2' into 'master'

Don't sign DNSKEY RRset with ZSK if the KSK is offline and dnskey-kskonly

Closes #763

See merge request isc-projects/bind9!1747
This commit is contained in:
Matthijs Mekking 2019-04-11 09:41:30 -04:00
commit 8184e5097c
9 changed files with 339 additions and 40 deletions

View file

@ -1,3 +1,7 @@
5209. [bug] When update-check-ksk is true, add_sigs was not
considering offline keys, leaving record sets signed
with the incorrect type key. [GL #763]
5208. [test] Run valid rdata wire encodings through totext+fromtext
and tofmttext+fromtext methods to check these methods.
[GL #899]

View file

@ -993,7 +993,7 @@ $RNDCCMD 10.53.0.2 loadkeys bar. 2>&1 | sed 's/^/ns2 /' | cat_i
echo_i "waiting for changes to take effect"
sleep 5
echo_i "checking former standby key is now active ($n)"
echo_i "checking former standby key $newid is now active ($n)"
ret=0
$DIG $DIGOPTS dnskey . @10.53.0.1 > dig.out.ns1.test$n || ret=1
grep 'RRSIG.*'" $newid "'\. ' dig.out.ns1.test$n > /dev/null || ret=1

View file

@ -15,7 +15,7 @@ rm -f ./*/K* ./*/keyset-* ./*/dsset-* ./*/dlvset-* ./*/signedkey-* ./*/*.signed
rm -f ./*/example.bk
rm -f ./*/named.conf
rm -f ./*/named.memstats
rm -f ./*/named.run
rm -f ./*/named.run ./*/named.run.prev
rm -f ./*/named.secroots
rm -f ./*/tmp* ./*/*.jnl ./*/*.bk ./*/*.jbk
rm -f ./*/trusted.conf ./*/managed.conf ./*/revoked.conf
@ -49,6 +49,7 @@ rm -f ./ns2/in-addr.arpa.db
rm -f ./ns2/nsec3chain-test.db
rm -f ./ns2/private.secure.example.db
rm -f ./ns2/single-nsec3.db
rm -f ./ns2/updatecheck-kskonly.secure.*
rm -f ./ns3/secure.example.db ./ns3/*.managed.db ./ns3/*.trusted.db
rm -f ./ns3/unsupported.managed.db.tmp ./ns3/unsupported.trusted.db.tmp
rm -f ./ns3/auto-nsec.example.db ./ns3/auto-nsec3.example.db

View file

@ -26,6 +26,15 @@ options {
minimal-responses no;
};
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;
};
controls {
inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
zone "." {
type hint;
file "../../common/root.hint";
@ -167,6 +176,18 @@ zone "cdnskey-auto.secure" {
allow-update { any; };
};
zone "updatecheck-kskonly.secure" {
type master;
auto-dnssec maintain;
key-directory ".";
dnssec-dnskey-kskonly yes;
update-check-ksk yes;
sig-validity-interval 10;
dnskey-sig-validity 40;
file "updatecheck-kskonly.secure.db.signed";
allow-update { any; };
};
zone "corp" {
type master;
file "corp.db";

View file

@ -314,3 +314,20 @@ key1=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone -f KSK "$
key2=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone "$zone")
sed 's/DNSKEY/CDNSKEY/' "$key1.key" > "$key1.cds"
cat "$infile" "$key1.cds" > "$zonefile.signed"
zone=updatecheck-kskonly.secure
infile=template.secure.db.in
zonefile=${zone}.db
key1=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone -f KSK "$zone")
key2=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone "$zone")
# Save key id's for checking active key usage
echo "$key1" | sed -e 's/.*[+]//' -e 's/^0*//' > $zone.ksk.id
echo "$key2" | sed -e 's/.*[+]//' -e 's/^0*//' > $zone.zsk.id
echo "${key1}" > $zone.ksk.key
echo "${key2}" > $zone.zsk.key
# Add CDS and CDNSKEY records
sed 's/DNSKEY/CDNSKEY/' "$key1.key" > "$key1.cdnskey"
"$DSFROMKEY" -C "$key1.key" > "$key1.cds"
cat "$infile" "$key1.key" "$key2.key" "$key1.cdnskey" "$key1.cds" > "$zonefile"
# Don't sign, let auto-dnssec maintain do it.
mv $zonefile "$zonefile.signed"

View file

@ -0,0 +1,12 @@
; 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
@ SOA ns2.example. . 1 3600 1200 86400 1200
@ NS ns2.example.

View file

@ -40,6 +40,26 @@ rndccmd() {
"$RNDC" -c "$SYSTEMTESTTOP/common/rndc.conf" -p "$CONTROLPORT" -s "$@"
}
# TODO: Move wait_for_log and loadkeys_on to conf.sh.common
wait_for_log() {
msg=$1
file=$2
for i in 1 2 3 4 5 6 7 8 9 10; do
nextpart "$file" | grep "$msg" > /dev/null && return
sleep 1
done
echo_i "exceeded time limit waiting for '$msg' in $file"
ret=1
}
dnssec_loadkeys_on() {
nsidx=$1
zone=$2
nextpart ns${nsidx}/named.run > /dev/null
rndccmd 10.53.0.${nsidx} loadkeys ${zone} | sed "s/^/ns${nsidx} /" | cat_i
wait_for_log "next key event" ns${nsidx}/named.run
}
# convert private-type records to readable form
showprivate () {
echo "-- $* --"
@ -2653,7 +2673,7 @@ my_dig() {
"$DIG" +noadd +nosea +nostat +noquest +nocomm +nocmd -p "$PORT" @10.53.0.4 "$@"
}
echo_i "checking dnskey query with no data still gets put in cache ($n)"
echo_i "checking DNSKEY query with no data still gets put in cache ($n)"
ret=0
firstVal=$(my_dig insecure.example. dnskey| awk '$1 != ";;" { print $2 }')
sleep 1
@ -2709,7 +2729,7 @@ do
fi
echo_i "sleeping ...."
sleep 3
done;
done
grep "ANSWER: 3," dig.out.ns2.test$n > /dev/null || ret=1
if [ "$ret" -ne 0 ]; then echo_i "nsec3 chain generation not complete"; fi
dig_with_opts +noauth +nodnssec soa nsec3chain-test @10.53.0.2 > dig.out.ns2.test$n || ret=1
@ -3856,5 +3876,198 @@ n=$((n+1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
###
### Additional checks for when the KSK is offline.
###
# Save some useful information
zone="updatecheck-kskonly.secure"
KSK=`cat ns2/${zone}.ksk.key`
ZSK=`cat ns2/${zone}.zsk.key`
KSK_ID=`cat ns2/${zone}.ksk.id`
ZSK_ID=`cat ns2/${zone}.zsk.id`
SECTIONS="+answer +noauthority +noadditional"
echo_i "testing zone $zone KSK=$KSK_ID ZSK=$ZSK_ID"
# Basic checks to make sure everything is fine before the KSK is made offline.
for qtype in "DNSKEY" "CDNSKEY" "CDS"
do
echo_i "checking $qtype RRset is signed with KSK only (update-check-ksk, dnssec-ksk-only) ($n)"
ret=0
dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone > dig.out.test$n
lines=$(awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print}' dig.out.test$n | wc -l)
test "$lines" -eq 1 || ret=1
grep $KSK_ID dig.out.test$n > /dev/null || ret=1
grep $ZSK_ID dig.out.test$n > /dev/null && ret=1
n=$((n+1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
done
echo_i "checking SOA RRset is signed with ZSK only (update-check-ksk and dnssec-ksk-only) ($n)"
ret=0
dig_with_opts $SECTIONS @10.53.0.2 soa $zone > dig.out.test$n
lines=$(awk '$4 == "RRSIG" && $5 == "SOA" {print}' dig.out.test$n | wc -l)
grep $KSK_ID dig.out.test$n > /dev/null && ret=1
grep $ZSK_ID dig.out.test$n > /dev/null || ret=1
test "$lines" -eq 1 || ret=1
n=$((n+1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
# Roll the ZSK.
sleep 1
zsk2=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -K ns2 -n zone "$zone")
echo_i "new ZSK $zsk2 created for zone $zone"
echo "$zsk2" | sed -e 's/.*[+]//' -e 's/^0*//' > ns2/$zone.zsk.id2
ZSK_ID2=`cat ns2/$zone.zsk.id2`
dnssec_loadkeys_on 2 $zone
# Wait until new ZSK becomes active.
sleep 1
echo_i "make ZSK $ZSK inactive and make new ZSK $zsk2 active for zone $zone"
$SETTIME -I now -K ns2 $ZSK > /dev/null
$SETTIME -A now -K ns2 $zsk2 > /dev/null
dnssec_loadkeys_on 2 $zone
# Remove the KSK from disk.
sleep 1
echo_i "remove the KSK $KSK for zone $zone from disk"
mv ns2/$KSK.key ns2/$KSK.key.bak
mv ns2/$KSK.private ns2/$KSK.private.bak
# Update the zone that requires a resign of the SOA RRset.
sleep 1
echo_i "update the zone with $zone IN TXT nsupdate added me"
(
echo zone $zone
echo server 10.53.0.2 "$PORT"
echo update add $zone. 300 in txt "nsupdate added me"
echo send
) | $NSUPDATE
# Redo the tests now that the zone is updated and the KSK is offline.
for qtype in "DNSKEY" "CDNSKEY" "CDS"
do
echo_i "checking $qtype RRset is signed with KSK only, KSK offline (update-check-ksk, dnssec-ksk-only) ($n)"
ret=0
dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone > dig.out.test$n
lines=$(awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print}' dig.out.test$n | wc -l)
test "$lines" -eq 1 || ret=1
grep $KSK_ID dig.out.test$n > /dev/null || ret=1
grep $ZSK_ID dig.out.test$n > /dev/null && ret=1
grep $ZSK_ID2 dig.out.test$n > /dev/null && ret=1
n=$((n+1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
done
for qtype in "SOA" "TXT"
do
echo_i "checking $qtype RRset is signed with ZSK only, KSK offline (update-check-ksk and dnssec-ksk-only) ($n)"
ret=0
dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone > dig.out.test$n
lines=$(awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print}' dig.out.test$n | wc -l)
grep $KSK_ID dig.out.test$n > /dev/null && ret=1
grep $ZSK_ID dig.out.test$n > /dev/null && ret=1
grep $ZSK_ID2 dig.out.test$n > /dev/null || ret=1
test "$lines" -eq 1 || ret=1
n=$((n+1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
done
# Put back the KSK.
sleep 1
echo_i "put back the KSK $KSK for zone $zone from disk"
mv ns2/$KSK.key.bak ns2/$KSK.key
mv ns2/$KSK.private.bak ns2/$KSK.private
# Roll the ZSK again.
sleep 1
zsk3=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -K ns2 -n zone "$zone")
echo_i "new ZSK $zsk3 created for zone $zone"
echo "$zsk3" | sed -e 's/.*[+]//' -e 's/^0*//' > ns2/$zone.zsk.id3
ZSK_ID3=`cat ns2/$zone.zsk.id3`
dnssec_loadkeys_on 2 $zone
# Wait until new ZSK becomes active.
sleep 1
echo_i "delete old ZSK $ZSK make ZSK $ZSK2 inactive and make new ZSK $zsk3 active for zone $zone"
$SETTIME -D now -K ns2 $ZSK > /dev/null
$SETTIME -I +5 -K ns2 $zsk2 > /dev/null
$SETTIME -A +5 -K ns2 $zsk3 > /dev/null
dnssec_loadkeys_on 2 $zone
# Remove the KSK from disk.
sleep 1
echo_i "remove the KSK $KSK for zone $zone from disk"
mv ns2/$KSK.key ns2/$KSK.key.bak
mv ns2/$KSK.private ns2/$KSK.private.bak
# Update the zone that requires a resign of the SOA RRset.
sleep 1
echo_i "update the zone with $zone IN TXT nsupdate added me again"
(
echo zone $zone
echo server 10.53.0.2 "$PORT"
echo update add $zone. 300 in txt "nsupdate added me again"
echo send
) | $NSUPDATE
# Redo the tests now that the ZSK roll has deleted the old key.
for qtype in "DNSKEY" "CDNSKEY" "CDS"
do
echo_i "checking $qtype RRset is signed with KSK only, old ZSK deleted (update-check-ksk, dnssec-ksk-only) ($n)"
ret=0
dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone > dig.out.test$n
lines=$(awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print}' dig.out.test$n | wc -l)
test "$lines" -eq 1 || ret=1
grep $KSK_ID dig.out.test$n > /dev/null || ret=1
grep $ZSK_ID dig.out.test$n > /dev/null && ret=1
grep $ZSK_ID2 dig.out.test$n > /dev/null && ret=1
grep $ZSK_ID3 dig.out.test$n > /dev/null && ret=1
n=$((n+1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
done
for qtype in "SOA" "TXT"
do
echo_i "checking $qtype RRset is signed with ZSK only, old ZSK deleted (update-check-ksk and dnssec-ksk-only) ($n)"
ret=0
dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone > dig.out.test$n
lines=$(awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print}' dig.out.test$n | wc -l)
grep $KSK_ID dig.out.test$n > /dev/null && ret=1
grep $ZSK_ID dig.out.test$n > /dev/null && ret=1
grep $ZSK_ID2 dig.out.test$n > /dev/null || ret=1
grep $ZSK_ID3 dig.out.test$n > /dev/null && ret=1
test "$lines" -eq 1 || ret=1
n=$((n+1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
done
# Wait for newest ZSK to become active.
echo_i "sleep 6 to make new ZSK $zsk3 active and ZSK $zsk2 inactive"
sleep 6
# Redo the tests one more time.
for qtype in "DNSKEY" "CDNSKEY" "CDS"
do
echo_i "checking $qtype RRset is signed with KSK only, new ZSK active (update-check-ksk, dnssec-ksk-only) ($n)"
ret=0
dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone > dig.out.test$n
lines=$(awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print}' dig.out.test$n | wc -l)
test "$lines" -eq 1 || ret=1
grep $KSK_ID dig.out.test$n > /dev/null || ret=1
grep $ZSK_ID dig.out.test$n > /dev/null && ret=1
grep $ZSK_ID2 dig.out.test$n > /dev/null && ret=1
grep $ZSK_ID3 dig.out.test$n > /dev/null && ret=1
n=$((n+1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
done
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1

View file

@ -1106,10 +1106,13 @@ add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
for (i = 0; i < nkeys; i++) {
bool both = false;
if (!dst_key_isprivate(keys[i]))
/* Don't add signatures for offline or inactive keys */
if (!dst_key_isprivate(keys[i])) {
continue;
if (dst_key_inactive(keys[i])) /* Should be redundant. */
}
if (dst_key_inactive(keys[i])) {
continue;
}
if (check_ksk && !REVOKE(keys[i])) {
bool have_ksk, have_nonksk;
@ -1121,21 +1124,31 @@ add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
have_nonksk = true;
}
for (j = 0; j < nkeys; j++) {
if (j == i || ALG(keys[i]) != ALG(keys[j]))
if (j == i || ALG(keys[i]) != ALG(keys[j])) {
continue;
if (!dst_key_isprivate(keys[j]))
}
/* Don't consider inactive keys, however
* the key may be temporary offline, so do
* consider keys which private key files are
* unavailable.
*/
if (dst_key_inactive(keys[j])) {
continue;
if (dst_key_inactive(keys[j])) /* SBR */
}
if (REVOKE(keys[j])) {
continue;
if (REVOKE(keys[j]))
continue;
if (KSK(keys[j]))
}
if (KSK(keys[j])) {
have_ksk = true;
else
} else {
have_nonksk = true;
}
both = have_ksk && have_nonksk;
if (both)
if (both) {
break;
}
}
}

View file

@ -6461,10 +6461,11 @@ del_sigs(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
* If there is not a matching DNSKEY then
* delete the RRSIG.
*/
if (!found)
if (!found) {
result = update_one_rr(db, ver, zonediff->diff,
DNS_DIFFOP_DELRESIGN, name,
rdataset.ttl, &rdata);
}
if (result != ISC_R_SUCCESS)
break;
}
@ -6529,10 +6530,13 @@ add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
for (i = 0; i < nkeys; i++) {
bool both = false;
if (!dst_key_isprivate(keys[i]))
/* Don't add signatures for offline or inactive keys */
if (!dst_key_isprivate(keys[i])) {
continue;
if (dst_key_inactive(keys[i])) /* Should be redundant. */
}
if (dst_key_inactive(keys[i])) {
continue;
}
if (check_ksk && !REVOKE(keys[i])) {
bool have_ksk, have_nonksk;
@ -6543,24 +6547,36 @@ add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
have_ksk = false;
have_nonksk = true;
}
for (j = 0; j < nkeys; j++) {
if (j == i || ALG(keys[i]) != ALG(keys[j]))
if (j == i || ALG(keys[i]) != ALG(keys[j])) {
continue;
if (!dst_key_isprivate(keys[j]))
}
/* Don't consider inactive keys, however
* the key may be temporary offline, so do
* consider keys which private key files are
* unavailable.
*/
if (dst_key_inactive(keys[j])) {
continue;
if (dst_key_inactive(keys[j])) /* SBR */
}
if (REVOKE(keys[j])) {
continue;
if (REVOKE(keys[j]))
continue;
if (KSK(keys[j]))
}
if (KSK(keys[j])) {
have_ksk = true;
else
} else {
have_nonksk = true;
}
both = have_ksk && have_nonksk;
if (both)
if (both) {
break;
}
}
}
if (both) {
/*
* CDS and CDNSKEY are signed with KSK (RFC 7344, 4.1).
@ -8851,9 +8867,6 @@ zone_sign(dns_zone_t *zone) {
if (!dst_key_isprivate(zone_keys[i])) {
continue;
}
/*
* Should be redundant.
*/
if (dst_key_inactive(zone_keys[i])) {
continue;
}
@ -8897,11 +8910,10 @@ zone_sign(dns_zone_t *zone) {
{
continue;
}
if (!dst_key_isprivate(zone_keys[j])) {
continue;
}
/*
* Should be redundant.
/* Don't consider inactive keys, however
* the key may be temporary offline, so do
* consider keys which private key files are
* unavailable.
*/
if (dst_key_inactive(zone_keys[j])) {
continue;
@ -10485,14 +10497,17 @@ zone_maintenance(dns_zone_t *zone) {
if (zone->rss_event != NULL)
break;
if (!isc_time_isepoch(&zone->signingtime) &&
isc_time_compare(&now, &zone->signingtime) >= 0)
isc_time_compare(&now, &zone->signingtime) >= 0) {
zone_sign(zone);
}
else if (!isc_time_isepoch(&zone->resigntime) &&
isc_time_compare(&now, &zone->resigntime) >= 0)
isc_time_compare(&now, &zone->resigntime) >= 0) {
zone_resigninc(zone);
}
else if (!isc_time_isepoch(&zone->nsec3chaintime) &&
isc_time_compare(&now, &zone->nsec3chaintime) >= 0)
isc_time_compare(&now, &zone->nsec3chaintime) >= 0) {
zone_nsec3chain(zone);
}
/*
* Do we need to issue a key expiry warning?
*/
@ -18016,15 +18031,18 @@ add_signing_records(dns_db_t *db, dns_rdatatype_t privatetype,
for (tuple = ISC_LIST_HEAD(diff->tuples);
tuple != NULL;
tuple = ISC_LIST_NEXT(tuple, link)) {
if (tuple->rdata.type != dns_rdatatype_dnskey)
if (tuple->rdata.type != dns_rdatatype_dnskey) {
continue;
}
result = dns_rdata_tostruct(&tuple->rdata, &dnskey, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
if ((dnskey.flags &
(DNS_KEYFLAG_OWNERMASK|DNS_KEYTYPE_NOAUTH))
!= DNS_KEYOWNER_ZONE)
{
continue;
}
dns_rdata_toregion(&tuple->rdata, &r);
@ -18042,8 +18060,10 @@ add_signing_records(dns_db_t *db, dns_rdatatype_t privatetype,
if (sign_all || tuple->op == DNS_DIFFOP_DEL) {
CHECK(rr_exists(db, ver, name, &rdata, &flag));
if (flag)
if (flag) {
continue;
}
CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD,
name, 0, &rdata, &newtuple));
CHECK(do_one_tuple(&newtuple, db, ver, diff));
@ -18369,7 +18389,6 @@ zone_rekey(dns_zone_t *zone) {
goto failure;
}
/* Get the CDS rdataset */
result = dns_db_findrdataset(db, node, ver, dns_rdatatype_cds,
dns_rdatatype_none, 0, &cdsset, NULL);
@ -18395,7 +18414,6 @@ zone_rekey(dns_zone_t *zone) {
if (result == ISC_R_SUCCESS) {
bool check_ksk;
check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK);
result = dns_dnssec_updatekeys(&dnskeys, &keys, &rmkeys,
&zone->origin, ttl, &diff,
!check_ksk, mctx,