+++ /dev/null
-#!/usr/bin/perl
-
-# 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 Dynamic update test suite.
-#
-# Usage:
-#
-# perl update_test.pl [-s server] [-p port] zone
-#
-# The server defaults to 127.0.0.1.
-# The port defaults to 53.
-#
-# Installation notes:
-#
-# This program uses the Net::DNS::Resolver module.
-# You can install it by saying
-#
-# perl -MCPAN -e "install Net::DNS"
-#
-
-use Getopt::Std;
-use Net::DNS;
-use Net::DNS::Update;
-use Net::DNS::Resolver;
-
-$opt_s = "127.0.0.1";
-$opt_p = 53;
-
-getopt('s:p:');
-
-$res = new Net::DNS::Resolver;
-$res->nameservers($opt_s);
-$res->port($opt_p);
-$res->defnames(0); # Do not append default domain.
-
-@ARGV == 1 or die
- "usage: perl update_test.pl [-s server] [-p port] zone\n";
-
-$zone = shift @ARGV;
-
-my $failures = 0;
-
-sub assert {
- my ($cond, $explanation) = @_;
- if (!$cond) {
- print "Test Failed: $explanation ***\n";
- $failures++
- }
-}
-
-sub test {
- my ($expected, @records) = @_;
-
- my $update = new Net::DNS::Update("$zone");
-
- foreach $rec (@records) {
- $update->push(@$rec);
- }
-
- $reply = $res->send($update);
-
- # Did it work?
- if (defined $reply) {
- my $rcode = $reply->header->rcode;
- assert($rcode eq $expected, "expected $expected, got $rcode");
- } else {
- print "Update failed: ", $res->errorstring, "\n";
- }
-}
-
-sub section {
- my ($msg) = @_;
- print "$msg\n";
-}
-
-section("Add a name");
-test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.49")]);
-
-section("Delete the name");
-test("NOERROR", ["update", rr_del("a.$zone")]);
-
-if ($failures) {
- print "$failures update tests failed.\n";
-} else {
- print "All update tests successful.\n";
-}
-
-exit $failures;
zone "updatecheck-kskonly.secure" {
type primary;
- file "updatecheck-kskonly.secure.db.signed";
+ file "updatecheck-kskonly.secure.db";
dnssec-policy kskonly;
allow-update { any; };
};
$SETTIME -s -g OMNIPRESENT -k OMNIPRESENT now -z OMNIPRESENT now $key2 >settime.out.$zone.zsk 2>&1
# Don't sign, let dnssec-policy maintain do it.
cat "$infile" "$key1.key" "$key2.key" >"$zonefile"
-mv $zonefile "$zonefile.signed"
zone=hours-vs-days
infile=hours-vs-days.db.in
status=0
n=1
-rm -f dig.out.*
-
-dig_with_opts() {
- "$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p "$PORT" "$@"
-}
-
-dig_with_answeropts() {
- "$DIG" +noall +answer +dnssec -p "$PORT" "$@"
-}
-
delv_with_opts() {
"$DELV" -a ns1/trusted.conf -p "$PORT" "$@"
}
-rndccmd() {
- "$RNDC" -c ../_common/rndc.conf -p "$CONTROLPORT" -s "$@"
-}
-
-# TODO: Move loadkeys_on to conf.sh.common
-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 20 "next key event" ns${nsidx}/named.run || return 1
-}
-
-# convert private-type records to readable form
-showprivate() {
- echo "-- $* --"
- dig_with_opts +nodnssec +short "@$2" -t type65534 "$1" >dig.out.$1.test$n
- cut -f3 -d' ' <dig.out.$1.$n | while read -r record; do
- # shellcheck disable=SC2016
- $PERL -e 'my $rdata = pack("H*", @ARGV[0]);
- die "invalid record" unless length($rdata) == 5 || length($rdata) == 7;
- my ($dns, $key, $remove, $complete, $alg) = unpack("CnCCn", $rdata);
- die "invalid record" unless $dns != 0;
- my $action = "signing";
- $action = "removing" if $remove;
- my $state = " (incomplete)";
- $state = " (complete)" if $complete;
- $alg = $dns if ! defined($alg);
- print ("$action: alg: $alg, key: $key$state\n");' "$record"
- done
-}
-
-# check that signing records are marked as complete
-checkprivate() {
- for i in 1 2 3 4 5 6 7 8 9 10; do
- showprivate "$@" | grep -q incomplete || return 0
- sleep 1
- done
- echo_d "$1 signing incomplete"
- return 1
-}
-
if [ -x "${DELV}" ]; then
ret=0
echo_i "checking positive validation NSEC using dns_client ($n)"
status=$((status + ret))
fi
-# Run a minimal update test if possible. This is really just
-# a regression test for RT #2399; more tests should be added.
-
-if $PERL -e 'use Net::DNS;' 2>/dev/null; then
- echo_i "running DNSSEC update test"
- ret=0
- {
- output=$($PERL dnssec_update_test.pl -s 10.53.0.3 -p "$PORT" dynamic.example.)
- rc=$?
- } || true
- test "$rc" -eq 0 || ret=1
- echo "$output" | cat_i
- [ $ret -eq 1 ] && status=1
-else
- echo_i "The DNSSEC update test requires the Net::DNS library." >&2
-fi
-
-echo_i "checking that the NSEC3 record for the apex is properly signed when a DNSKEY is added via UPDATE ($n)"
-ret=0
-(
- kskname=$($KEYGEN -q -3 -a $DEFAULT_ALGORITHM -fk update-nsec3.example)
- (
- echo zone update-nsec3.example
- echo server 10.53.0.3 "$PORT"
- grep DNSKEY "${kskname}.key" | sed -e 's/^/update add /' -e 's/IN/300 IN/'
- echo send
- ) | $NSUPDATE
-)
-dig_with_opts +dnssec a update-nsec3.example. @10.53.0.4 >dig.out.ns4.test$n || ret=1
-grep "NOERROR" dig.out.ns4.test$n >/dev/null || ret=1
-grep "flags:.* ad[ ;]" dig.out.ns4.test$n >/dev/null || ret=1
-grep "NSEC3 1 0 0 - .*" dig.out.ns4.test$n >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "checking that signing records have been marked as complete ($n)"
-ret=0
-checkprivate dynamic.example 10.53.0.3 || ret=1
-checkprivate auto-nsec3.example 10.53.0.3 || ret=1
-checkprivate expiring.example 10.53.0.3 || ret=1
-checkprivate auto-nsec.example 10.53.0.3 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that 'rndc signing' without arguments is handled ($n)"
-ret=0
-rndccmd 10.53.0.3 signing >/dev/null 2>&1 && ret=1
-rndccmd 10.53.0.3 status >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that 'rndc signing -list' without zone is handled ($n)"
-ret=0
-rndccmd 10.53.0.3 signing -list >/dev/null 2>&1 && ret=1
-rndccmd 10.53.0.3 status >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that 'rndc signing -clear' without additional arguments is handled ($n)"
-ret=0
-rndccmd 10.53.0.3 signing -clear >/dev/null 2>&1 && ret=1
-rndccmd 10.53.0.3 status >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that 'rndc signing -clear all' without zone is handled ($n)"
-ret=0
-rndccmd 10.53.0.3 signing -clear all >/dev/null 2>&1 && ret=1
-rndccmd 10.53.0.3 status >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check rndc signing -list output ($n)"
-ret=0
-{ rndccmd 10.53.0.3 signing -list dynamic.example >signing.out.dynamic.example; } 2>&1
-grep -q "No signing records found" signing.out.dynamic.example || {
- ret=1
- sed 's/^/ns3 /' signing.out.dynamic.example | cat_i
-}
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that a split dnssec dnssec-signzone work ($n)"
-ret=0
-dig_with_opts soa split-dnssec.example. @10.53.0.4 >dig.out.ns4.test$n || ret=1
-grep "NOERROR" dig.out.ns4.test$n >/dev/null || ret=1
-grep "ANSWER: 2," dig.out.ns4.test$n >/dev/null || ret=1
-grep "flags:.* ad[ ;]" dig.out.ns4.test$n >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that a smart split dnssec dnssec-signzone work ($n)"
-ret=0
-dig_with_opts soa split-smart.example. @10.53.0.4 >dig.out.ns4.test$n || ret=1
-grep "NOERROR" dig.out.ns4.test$n >/dev/null || ret=1
-grep "ANSWER: 2," dig.out.ns4.test$n >/dev/null || ret=1
-grep "flags:.* ad[ ;]" dig.out.ns4.test$n >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "testing soon-to-expire RRSIGs without a replacement private key ($n)"
-ret=0
-dig_with_answeropts +nottlid expiring.example ns @10.53.0.3 | grep RRSIG >dig.out.ns3.test$n 2>&1
-# there must be a signature here
-[ -s dig.out.ns3.test$n ] || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that named doesn't loop when all private keys are not available ($n)"
-ret=0
-lines=$(grep -c "reading private key file expiring.example" ns3/named.run || true)
-test "${lines:-1000}" -lt 15 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check the correct resigning time is reported in zonestatus ($n)"
-ret=0
-rndccmd 10.53.0.3 \
- zonestatus secure.example >rndc.out.ns3.test$n
-# next resign node: secure.example/DNSKEY
-qname=$(awk '/next resign node:/ { print $4 }' rndc.out.ns3.test$n | sed 's,/.*,,')
-qtype=$(awk '/next resign node:/ { print $4 }' rndc.out.ns3.test$n | sed 's,.*/,,')
-# next resign time: Thu, 24 Apr 2014 10:38:16 GMT
-time=$(awk 'BEGIN { m["Jan"] = "01"; m["Feb"] = "02"; m["Mar"] = "03";
- m["Apr"] = "04"; m["May"] = "05"; m["Jun"] = "06";
- m["Jul"] = "07"; m["Aug"] = "08"; m["Sep"] = "09";
- m["Oct"] = "10"; m["Nov"] = "11"; m["Dec"] = "12";}
- /next resign time:/ { printf "%d%s%02d%s\n", $7, m[$6], $5, $8 }' rndc.out.ns3.test$n | sed 's/://g')
-dig_with_opts +noall +answer "$qname" "$qtype" @10.53.0.3 >dig.out.test$n
-expire=$(awk '$4 == "RRSIG" { print $9 }' dig.out.test$n)
-inception=$(awk '$4 == "RRSIG" { print $10 }' dig.out.test$n)
-$PERL -e 'exit(0) if ("'"$time"'" lt "'"$expire"'" && "'"$time"'" gt "'"$inception"'"); exit(1);' || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDS records are signed using KSK by dnssec-signzone ($n)"
-ret=0
-dig_with_opts +noall +answer @10.53.0.2 cds cds.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDS records are not signed using ZSK by dnssec-signzone -x ($n)"
-ret=0
-dig_with_opts +noall +answer @10.53.0.2 cds cds-x.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDS records are signed using KSK by with dnssec-policy ($n)"
-ret=0
-dig_with_opts +noall +answer @10.53.0.2 cds cds-auto.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that a CDS deletion record is accepted ($n)"
-ret=0
-(
- echo zone cds-update.secure
- echo server 10.53.0.2 "$PORT"
- echo update delete cds-update.secure CDS
- echo update add cds-update.secure 0 CDS 0 0 0 00
- echo send
-) | $NSUPDATE >nsupdate.out.test$n 2>&1
-dig_with_opts +noall +answer @10.53.0.2 cds cds-update.secure >dig.out.test$n
-lines=$(awk '$4 == "CDS" {print}' dig.out.test$n | wc -l)
-test "${lines:-10}" -eq 1 || ret=1
-lines=$(awk '$4 == "CDS" && $5 == "0" && $6 == "0" && $7 == "0" && $8 == "00" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDS records are signed only using KSK when added by nsupdate ($n)"
-ret=0
-keyid=$(cat ns2/cds-update.secure.id)
-(
- echo zone cds-update.secure
- echo server 10.53.0.2 "$PORT"
- echo update delete cds-update.secure CDS
- echo send
- dig_with_opts +noall +answer @10.53.0.2 dnskey cds-update.secure \
- | grep "DNSKEY.257" \
- | $DSFROMKEY -12 -C -f - -T 1 cds-update.secure \
- | sed "s/^/update add /"
- echo send
-) | $NSUPDATE
-dig_with_opts +noall +answer @10.53.0.2 cds cds-update.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk -v id="${keyid}" '$4 == "RRSIG" && $5 == "CDS" && $11 == id {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk '$4 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDS deletion records are signed only using KSK when added by nsupdate ($n)"
-ret=0
-keyid=$(cat ns2/cds-update.secure.id)
-(
- echo zone cds-update.secure
- echo server 10.53.0.2 "$PORT"
- echo update delete cds-update.secure CDS
- echo update add cds-update.secure 0 CDS 0 0 0 00
- echo send
-) | $NSUPDATE
-dig_with_opts +noall +answer @10.53.0.2 cds cds-update.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk -v id="${keyid}" '$4 == "RRSIG" && $5 == "CDS" && $11 == id {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk '$4 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk '$4 == "CDS" && $5 == "0" && $6 == "0" && $7 == "0" && $8 == "00" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that a non matching CDS record is accepted with a matching CDS record ($n)"
-ret=0
-(
- echo zone cds-update.secure
- echo server 10.53.0.2 "$PORT"
- echo update delete cds-update.secure CDS
- echo send
- dig_with_opts +noall +answer @10.53.0.2 dnskey cds-update.secure \
- | grep "DNSKEY.257" \
- | $DSFROMKEY -12 -C -f - -T 1 cds-update.secure \
- | sed "s/^/update add /"
- dig_with_opts +noall +answer @10.53.0.2 dnskey cds-update.secure \
- | grep "DNSKEY.257" | sed 's/DNSKEY.257/DNSKEY 258/' \
- | $DSFROMKEY -12 -C -A -f - -T 1 cds-update.secure \
- | sed "s/^/update add /"
- echo send
-) | $NSUPDATE
-dig_with_opts +noall +answer @10.53.0.2 cds cds-update.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk '$4 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 4 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDNSKEY records are signed using KSK by dnssec-signzone ($n)"
-ret=0
-dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDNSKEY records are not signed using ZSK by dnssec-signzone -x ($n)"
-ret=0
-dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey-x.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDNSKEY records are signed using KSK by with dnssec-auto ($n)"
-ret=0
-dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey-auto.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# TODO: test case for GL #1689.
-# If we allow the dnssec tools to use deprecated algorithms (such as RSAMD5)
-# we could write a test that signs a zone with supported and unsupported
-# algorithm, apply a fixed rrset order such that the unsupported algorithm
-# precedes the supported one in the DNSKEY RRset, and verify the result still
-# validates succesfully.
-
-echo_i "check that a CDNSKEY deletion record is accepted ($n)"
-ret=0
-(
- echo zone cdnskey-update.secure
- echo server 10.53.0.2 "$PORT"
- echo update delete cdnskey-update.secure CDNSKEY
- echo update add cdnskey-update.secure 0 CDNSKEY 0 3 0 AA==
- echo send
-) | $NSUPDATE >nsupdate.out.test$n 2>&1
-dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey-update.secure >dig.out.test$n
-lines=$(awk '$4 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "${lines:-10}" -eq 1 || ret=1
-lines=$(awk '$4 == "CDNSKEY" && $5 == "0" && $6 == "3" && $7 == "0" && $8 == "AA==" {print}' dig.out.test$n | wc -l)
-test "${lines:-10}" -eq 1 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDNSKEY records are signed using KSK only when added by nsupdate ($n)"
-ret=0
-keyid=$(cat ns2/cdnskey-update.secure.id)
-(
- echo zone cdnskey-update.secure
- echo server 10.53.0.2 "$PORT"
- echo update delete cdnskey-update.secure CDNSKEY
- dig_with_opts +noall +answer @10.53.0.2 dnskey cdnskey-update.secure \
- | sed -n -e "s/^/update add /" -e 's/DNSKEY.257/CDNSKEY 257/p'
- echo send
-) | $NSUPDATE
-dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey-update.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk -v id="${keyid}" '$4 == "RRSIG" && $5 == "CDNSKEY" && $11 == id {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk '$4 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that a non matching CDNSKEY record is accepted with a matching CDNSKEY record ($n)"
-ret=0
-(
- echo zone cdnskey-update.secure
- echo server 10.53.0.2 "$PORT"
- echo update delete cdnskey-update.secure CDNSKEY
- dig_with_opts +noall +answer @10.53.0.2 dnskey cdnskey-update.secure \
- | sed -n -e "s/^/update add /" -e 's/DNSKEY.257/CDNSKEY 257/p'
- dig_with_opts +noall +answer @10.53.0.2 dnskey cdnskey-update.secure \
- | sed -n -e "s/^/update add /" -e 's/DNSKEY.257/CDNSKEY 258/p'
- echo send
-) | $NSUPDATE
-dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey-update.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk '$4 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that DNAME at apex with NSEC3 is correctly signed (dnssec-signzone) ($n)"
-ret=0
-dig_with_opts txt dname-at-apex-nsec3.example @10.53.0.3 >dig.out.ns3.test$n || ret=1
-grep "RRSIG.NSEC3 $DEFAULT_ALGORITHM_NUMBER 3 600" dig.out.ns3.test$n >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that DNSKEY and other occluded data are excluded from the delegating bitmap ($n)"
-ret=0
-dig_with_opts axfr occluded.example @10.53.0.3 >dig.out.ns3.test$n || ret=1
-grep "^delegation.occluded.example..*NSEC.*NS KEY DS RRSIG NSEC$" dig.out.ns3.test$n >/dev/null || ret=1
-grep "^delegation.occluded.example..*DNSKEY.*" dig.out.ns3.test$n >/dev/null || ret=1
-grep "^delegation.occluded.example..*AAAA.*" dig.out.ns3.test$n >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "checking DNSSEC records are occluded from ANY in an insecure zone ($n)"
-ret=0
-dig_with_opts any x.insecure.example. @10.53.0.3 >dig.out.ns3.1.test$n || ret=1
-grep "status: NOERROR" dig.out.ns3.1.test$n >/dev/null || ret=1
-grep "ANSWER: 0," dig.out.ns3.1.test$n >/dev/null || ret=1
-dig_with_opts any z.secure.example. @10.53.0.3 >dig.out.ns3.2.test$n || ret=1
-grep "status: NOERROR" dig.out.ns3.2.test$n >/dev/null || ret=1
-# A+RRSIG, NSEC+RRSIG
-grep "ANSWER: 4," dig.out.ns3.2.test$n >/dev/null || ret=1
-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"
-
-# Set key state for KSK. The ZSK rollovers below assume that there is a chain
-# of trust established, so we tell named that the DS is in omnipresent state.
-$SETTIME -s -d OMNIPRESENT now -K ns2 $KSK >/dev/null
-
-# Print IDs of keys used for generating RRSIG records for RRsets of type $1
-# found in dig output file $2.
-get_keys_which_signed() {
- qtype=$1
- output=$2
- # The key ID is the 11th column of the RRSIG record line.
- awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print $11}' <"$output"
-}
-
-# 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 ($n)"
- ret=0
- dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
- lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
- test "$lines" -eq 1 || ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null || ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/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 ($n)"
-ret=0
-dig_with_opts $SECTIONS @10.53.0.2 soa $zone >dig.out.test$n
-lines=$(get_keys_which_signed "SOA" dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-get_keys_which_signed "SOA" dig.out.test$n | grep "^$KSK_ID$" >/dev/null && ret=1
-get_keys_which_signed "SOA" dig.out.test$n | grep "^$ZSK_ID$" >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Roll the ZSK.
-zsk2=$("$KEYGEN" -q -P none -A none -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -K ns2 "$zone")
-keyfile_to_key_id "$zsk2" >ns2/$zone.zsk.id2
-ZSK_ID2=$(cat ns2/$zone.zsk.id2)
-ret=0
-echo_i "prepublish new ZSK $ZSK_ID2 for $zone ($n)"
-rndccmd 10.53.0.2 dnssec -rollover -key $ZSK_ID $zone 2>&1 | sed 's/^/ns2 /' | cat_i
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-zsk_count_equals() {
- expectedzsks=$1
- dig_with_opts @10.53.0.2 DNSKEY $zone >dig.out.test$n
- lines=$(cat dig.out.test$n | grep "DNSKEY.*256 3 13" | wc -l)
- test "$lines" -eq $expectedzsks || return 1
-}
-echo_i "check DNSKEY RRset has successor ZSK $ZSK_ID2 ($n)"
-ret=0
-# The expected number of ZSKs is 2.
-retry_quiet 5 zsk_count_equals 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Make new ZSK active.
-echo_i "make ZSK $ZSK_ID inactive and make new ZSK $ZSK_ID2 active for zone $zone ($n)"
-ret=0
-$SETTIME -s -I now -K ns2 $ZSK >/dev/null
-$SETTIME -s -k OMNIPRESENT now -A now -K ns2 $zsk2 >/dev/null
-dnssec_loadkeys_on 2 $zone || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Wait for newest ZSK to become active.
-echo_i "wait until new ZSK $ZSK_ID2 active and ZSK $ZSK_ID inactive"
-for i in 1 2 3 4 5 6 7 8 9 10; do
- ret=0
- grep "DNSKEY $zone/$DEFAULT_ALGORITHM/$ZSK_ID2 (ZSK) is now active" ns2/named.run >/dev/null || ret=1
- grep "DNSKEY $zone/$DEFAULT_ALGORITHM/$ZSK_ID (ZSK) is now inactive" ns2/named.run >/dev/null || ret=1
- [ "$ret" -eq 0 ] && break
- sleep 1
-done
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Remove the KSK from disk.
-echo_i "remove the KSK $KSK_ID 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.
-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 ($n)"
- ret=0
- dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
- lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
- test "$lines" -eq 1 || ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null || ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/dev/null && ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID2$" >/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 new ZSK $ZSK_ID2 only, KSK offline ($n)"
- ret=0
- dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
- lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
- test "$lines" -eq 1 || ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null && ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/dev/null && ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID2$" >/dev/null || ret=1
- n=$((n + 1))
- test "$ret" -eq 0 || echo_i "failed"
- status=$((status + ret))
-done
-
-# Put back the KSK.
-echo_i "put back the KSK $KSK_ID 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.
-zsk3=$("$KEYGEN" -q -P none -A none -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -K ns2 "$zone")
-ret=0
-keyfile_to_key_id "$zsk3" >ns2/$zone.zsk.id3
-ZSK_ID3=$(cat ns2/$zone.zsk.id3)
-echo_i "delete old ZSK $ZSK_ID, schedule ZSK $ZSK_ID2 inactive, and pre-publish ZSK $ZSK_ID3 for zone $zone ($n)"
-$SETTIME -s -k HIDDEN now -z HIDDEN now -D now -K ns2 $ZSK >/dev/null
-$SETTIME -s -k OMNIPRESENT now -z OMNIPRESENT now -K ns2 $zsk2 >/dev/null
-dnssec_loadkeys_on 2 $zone || ret=1
-rndccmd 10.53.0.2 dnssec -rollover -key $ZSK_ID2 $zone 2>&1 | sed 's/^/ns2 /' | cat_i
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Wait for newest ZSK to become published.
-echo_i "wait until new ZSK $ZSK_ID3 published"
-for i in 1 2 3 4 5 6 7 8 9 10; do
- ret=0
- grep "DNSKEY $zone/$DEFAULT_ALGORITHM/$ZSK_ID3 (ZSK) is now published" ns2/named.run >/dev/null || ret=1
- [ "$ret" -eq 0 ] && break
- sleep 1
-done
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Remove the KSK from disk.
-echo_i "remove the KSK $KSK_ID 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.
-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 ($n)"
- ret=0
- dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
- lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
- test "$lines" -eq 1 || ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null || ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/dev/null && ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID2$" >/dev/null && ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID3$" >/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 $ZSK_ID2 only, old ZSK deleted ($n)"
- ret=0
- dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
- lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
- test "$lines" -eq 1 || ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null && ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/dev/null && ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID2$" >/dev/null || ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID3$" >/dev/null && ret=1
- n=$((n + 1))
- test "$ret" -eq 0 || echo_i "failed"
- status=$((status + ret))
-done
-
-# Put back the KSK.
-echo_i "put back the KSK $KSK_ID for zone $zone from disk"
-mv ns2/$KSK.key.bak ns2/$KSK.key
-mv ns2/$KSK.private.bak ns2/$KSK.private
-
-# Make the new ZSK (ZSK3) active.
-echo_i "make new ZSK $ZSK_ID3 active for zone $zone ($n)"
-ret=0
-$SETTIME -s -I now -K ns2 $zsk2 >/dev/null
-$SETTIME -s -k OMNIPRESENT now -A now -K ns2 $zsk3 >/dev/null
-dnssec_loadkeys_on 2 $zone || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Wait for newest ZSK to become active.
-echo_i "wait until new ZSK $ZSK_ID3 active and ZSK $ZSK_ID2 inactive"
-for i in 1 2 3 4 5 6 7 8 9 10; do
- ret=0
- grep "DNSKEY $zone/$DEFAULT_ALGORITHM/$ZSK_ID3 (ZSK) is now active" ns2/named.run >/dev/null || ret=1
- grep "DNSKEY $zone/$DEFAULT_ALGORITHM/$ZSK_ID2 (ZSK) is now inactive" ns2/named.run >/dev/null || ret=1
- [ "$ret" -eq 0 ] && break
- sleep 1
-done
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Remove the KSK from disk.
-echo_i "remove the KSK $KSK_ID 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.
-echo_i "update the zone with $zone IN TXT nsupdate added me one more time"
-(
- echo zone $zone
- echo server 10.53.0.2 "$PORT"
- echo update add $zone. 300 in txt "nsupdate added me one more time"
- echo send
-) | $NSUPDATE
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# 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 ($n)"
- ret=0
- dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
- lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
- test "$lines" -eq 1 || ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null || ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/dev/null && ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID2$" >/dev/null && ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID3$" >/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 new ZSK $ZSK_ID3 only, new ZSK active ($n)"
- ret=0
- dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
- lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
- test "$lines" -eq 1 || ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null && ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/dev/null && ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID2$" >/dev/null && ret=1
- get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID3$" >/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
"ns2/too-many-iterations.db",
"ns2/inconsistent.db",
"ns2/trusted.db",
+ "ns2/updatecheck-kskonly.secure.db",
"ns2/updatecheck-kskonly.secure.ksk.id",
"ns2/updatecheck-kskonly.secure.ksk.key",
"ns2/updatecheck-kskonly.secure.zsk.id",
--- /dev/null
+# 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.
+
+from collections import namedtuple
+import os
+import re
+import struct
+import time
+
+from dns import dnssec, name, rdataclass, rdatatype, update
+
+import pytest
+
+pytest.importorskip("dns", minversion="2.0.0")
+import isctest
+
+
+# helper functions
+def grep_c(regex, filename):
+ with open(filename, "r", encoding="utf-8") as f:
+ blob = f.read().splitlines()
+ results = [x for x in blob if re.search(regex, x)]
+ return len(results)
+
+
+# run dnssec-keygen
+def keygen(*args):
+ keygen_cmd = [os.environ.get("KEYGEN")]
+ keygen_cmd.extend(args)
+ return isctest.run.cmd(keygen_cmd, log_stdout=True).stdout.decode("utf-8").strip()
+
+
+# run dnssec-settime
+def settime(*args):
+ settime_cmd = [os.environ.get("SETTIME")]
+ settime_cmd.extend(args)
+ return isctest.run.cmd(settime_cmd, log_stdout=True).stdout.decode("utf-8").strip()
+
+
+@pytest.mark.parametrize(
+ "domain",
+ [
+ "auto-nsec.example",
+ "auto-nsec3.example",
+ ],
+)
+def test_signing_complete(domain):
+ PrivateType = namedtuple("PrivateType", ["alg", "key", "rem", "complete"])
+
+ def convert_private(rdata) -> PrivateType:
+ length = len(rdata.to_wire())
+ assert length in (5, 7)
+ if length == 7:
+ _, key, rem, complete, alg = struct.unpack(">BHBBH", rdata.to_wire())
+ else:
+ alg, key, rem, complete = struct.unpack(">BHBB", rdata.to_wire())
+ return PrivateType(alg, key, rem, complete)
+
+ # query for a private type record, make sure it shows "complete"
+ def check_complete():
+ msg = isctest.query.create(domain, 65534)
+ res = isctest.query.tcp(msg, "10.53.0.3")
+ assert res.answer
+ for rdata in res.answer[0]:
+ record = convert_private(rdata)
+ assert record.complete
+ return True
+
+ isctest.run.retry_with_timeout(check_complete, 10)
+
+
+def test_split_dnssec():
+ # check that split-dnssec signing worked (dnssec-signzone -D)
+ msg = isctest.query.create("split-dnssec.example.", "SOA")
+ res1 = isctest.query.tcp(msg, "10.53.0.3")
+ res2 = isctest.query.tcp(msg, "10.53.0.4")
+ isctest.check.same_answer(res1, res2)
+ isctest.check.noerror(res2)
+ isctest.check.rr_count_eq(res2.answer, 2)
+ isctest.check.adflag(res2)
+
+ # check that smart split-dnssec signing worked (dnssec-signzone -DS)
+ msg = isctest.query.create("split-smart.example.", "SOA")
+ res1 = isctest.query.tcp(msg, "10.53.0.3")
+ res2 = isctest.query.tcp(msg, "10.53.0.4")
+ isctest.check.same_answer(res1, res2)
+ isctest.check.noerror(res2)
+ isctest.check.rr_count_eq(res2.answer, 2)
+ isctest.check.adflag(res2)
+
+
+def test_expiring_rrsig():
+ # check soon-to-expire RRSIGs without a replacement private
+ # key aren't deleted. this response has to have an RRSIG:
+ msg = isctest.query.create("expiring.example.", "NS")
+ res = isctest.query.tcp(msg, "10.53.0.3")
+ _, sigs = res.answer
+ assert sigs
+
+ # check that named doesn't loop when private keys are not available
+ n = grep_c("reading private key file expiring.example", "ns3/named.run")
+ assert n < 15
+
+ # check expired signatures stay place when updates are disabled
+ msg = isctest.query.create("expired.example", "SOA")
+ res = isctest.query.tcp(msg, "10.53.0.3")
+ _, sigs = res.answer
+ assert sigs
+
+
+def test_apex_signing():
+ # check that DNAME at apex with NSEC3 is correctly signed
+ msg = isctest.query.create("dname-at-apex-nsec3.example.", "TXT")
+ res = isctest.query.tcp(msg, "10.53.0.3")
+ sigs = [str(a) for a in res.authority if a.rdtype == rdatatype.RRSIG]
+ alg = os.environ.get("DEFAULT_ALGORITHM_NUMBER")
+ assert any(f"NSEC3 {alg} 3 600" in a for a in sigs)
+
+
+def test_occluded_data():
+ # check that DNSKEY and other occluded data are excluded from
+ # a delegating bitmap
+ msg = isctest.query.create("occluded.example.", "AXFR")
+ res = isctest.query.tcp(msg, "10.53.0.3")
+
+ n = "delegation.occluded.example."
+ delegation = [r for r in res.answer if str(r.name) == n]
+ assert [r for r in delegation if r.rdtype == rdatatype.DNSKEY], str(delegation)
+ assert [r for r in delegation if r.rdtype == rdatatype.AAAA], str(delegation)
+ nsec = [r for r in delegation if r.rdtype == rdatatype.NSEC]
+ assert nsec, str(delegation)
+ assert "DNSKEY" not in str(nsec[0]), str(res)
+ assert "AAAA" not in str(nsec[0]), str(res)
+
+ # check that DNSSEC records are occluded from ANY in an insecure zone
+ msg = isctest.query.create("x.extrakey.example.", "ANY")
+ res = isctest.query.tcp(msg, "10.53.0.3")
+ isctest.check.noerror(res)
+ isctest.check.empty_answer(res)
+ msg = isctest.query.create("z.secure.example.", "ANY")
+ res = isctest.query.tcp(msg, "10.53.0.3")
+ isctest.check.noerror(res)
+ isctest.check.rr_count_eq(res.answer, 4) # A+RRSIG, NSEC+RRSIG
+
+
+def test_update_signing():
+ # minimal update test: add and delete a single record
+ up = update.UpdateMessage("dynamic.example.")
+ up.add("a.dynamic.example.", 300, "A", "73.80.65.49")
+ res = isctest.query.tcp(up, "10.53.0.3")
+ isctest.check.noerror(res)
+
+ up = update.UpdateMessage("dynamic.example.")
+ up.delete("a.dynamic.example.")
+ res = isctest.query.tcp(up, "10.53.0.3")
+ isctest.check.noerror(res)
+
+ msg = isctest.query.create("a.dynamic.example", "A")
+ res = isctest.query.tcp(msg, "10.53.0.4")
+ isctest.check.nxdomain(res)
+ isctest.check.adflag(res)
+
+ # check that the NSEC3 record for the apex is properly signed
+ # when a DNSKEY is added via UPDATE
+ key = keygen(
+ "-q3fk", "-a", os.environ["DEFAULT_ALGORITHM"], "update-nsec3.example."
+ )
+
+ with open(f"{key}.key", "r", encoding="utf-8") as f:
+ dnskey = f.read().splitlines()[-1]
+ dnskey = " ".join(dnskey.split()[3:])
+
+ up = update.UpdateMessage("update-nsec3.example.")
+ up.add("update-nsec3.example.", 300, "DNSKEY", dnskey)
+ res = isctest.query.tcp(up, "10.53.0.3")
+ isctest.check.noerror(res)
+
+ msg = isctest.query.create("update-nsec3.example", "A")
+ res = isctest.query.tcp(msg, "10.53.0.4")
+ isctest.check.noerror(res)
+ isctest.check.adflag(res)
+ nsec3 = [str(a) for a in res.authority if a.rdtype == rdatatype.NSEC3]
+ assert any("1 0 0 -" in a for a in nsec3)
+
+
+def test_cds_signing():
+ # check that CDS records are signed using KSK+ZSK by dnssec-signzone
+ msg = isctest.query.create("cds.secure.", "CDS")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ cds, sigs = res.answer
+ assert len(sigs) == 2
+
+ # check that CDS records are not signed using ZSK by dnssec-signzone -x
+ msg = isctest.query.create("cds-x.secure.", "CDS")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ cds, sigs = res.answer
+ assert len(sigs) == 2 # there are two KSKs here
+
+ # check that CDS records are signed using KSK by dnssec-policy
+ msg = isctest.query.create("cds-auto.secure.", "CDS")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ cds, sigs = res.answer
+ assert len(sigs) == 1
+
+ # check that CDS records are signed only using KSK when added by nsupdate
+ with open("ns2/cds-update.secure.id", encoding="utf-8") as f:
+ keyid = int(f.read().splitlines()[0])
+ up = update.UpdateMessage("cds-update.secure.")
+ up.delete("cds-update.secure.", "CDS")
+ res = isctest.query.tcp(up, "10.53.0.2")
+ isctest.check.noerror(res)
+
+ msg = isctest.query.create("cds-update.secure.", "DNSKEY")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ isctest.check.noerror(res)
+ dnskeys, sigs = res.answer
+ ksk = [a for a in dnskeys if a.flags == 257][0]
+ ds = dnssec.make_ds("cds-update.secure.", ksk, 2)
+ up.add("cds-update.secure.", 1, "CDS", str(ds))
+ res = isctest.query.tcp(up, "10.53.0.2")
+ isctest.check.noerror(res)
+
+ msg = isctest.query.create("cds-update.secure.", "CDS")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ cds, sig = res.answer
+ assert len(cds) == 1
+ assert len(sig) == 1
+ assert sig[0].key_tag == keyid
+
+ # check that CDS deletion records are signed only using KSK when
+ # added by nsupdate
+ up = update.UpdateMessage("cds-update.secure.")
+ up.delete("cds-update.secure.", "CDS")
+ up.add("cds-update.secure.", 0, "CDS", "0 0 0 00")
+ res = isctest.query.tcp(up, "10.53.0.2")
+ isctest.check.noerror(res)
+
+ msg = isctest.query.create("cds-update.secure.", "CDS")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ cds, sig = res.answer
+ assert len(cds) == 1
+ assert "0 0 0 00" in str(cds[0])
+ assert len(sig) == 1
+ assert sig[0].key_tag == keyid
+
+ # check that a non-matching CDS record is accepted with a
+ # matching CDS record. first, generate a DNSKEY with different flags:
+ badksk = type(ksk)(
+ ksk.rdclass, ksk.rdtype, ksk.flags + 1, ksk.protocol, ksk.algorithm, ksk.key
+ )
+ up = update.UpdateMessage("cds-update.secure.")
+ badds = dnssec.make_ds("cds-update.secure.", badksk, 2)
+ up.delete("cds-update.secure.", "CDS")
+ up.add("cds-update.secure.", 1, "CDS", str(ds))
+ up.add("cds-update.secure.", 1, "CDS", str(badds))
+ res = isctest.query.tcp(up, "10.53.0.2")
+ isctest.check.noerror(res)
+
+ msg = isctest.query.create("cds-update.secure.", "CDS")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ cds, sig = res.answer
+ assert len(cds) == 2
+ assert len(sig) == 1
+
+
+def test_cdnskey_signing():
+ # check that CDNSKEY records are signed using KSK+ZSK by dnssec-signzone
+ msg = isctest.query.create("cdnskey.secure.", "CDNSKEY")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ cdnskey, sigs = res.answer
+ assert len(sigs) == 2
+
+ # check that CDNSKEY records are not signed using ZSK by dnssec-signzone -x
+ msg = isctest.query.create("cdnskey-x.secure.", "CDNSKEY")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ cdnskey, sigs = res.answer
+ assert len(sigs) == 2 # two KSKs here
+
+ # check that CDNSKEY records are signed using KSK by dnssec-policy
+ msg = isctest.query.create("cdnskey-auto.secure.", "CDNSKEY")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ cdnskey, sigs = res.answer
+ assert len(sigs) == 1
+
+ # check that CDNSKEY records are signed only using KSK
+ # when added by nsupdate
+ with open("ns2/cdnskey-update.secure.id", encoding="utf-8") as f:
+ keyid = int(f.read().splitlines()[0])
+ up = update.UpdateMessage("cdnskey-update.secure.")
+ up.delete("cdnskey-update.secure.", "CDNSKEY")
+ res = isctest.query.tcp(up, "10.53.0.2")
+ isctest.check.noerror(res)
+
+ msg = isctest.query.create("cdnskey-update.secure.", "DNSKEY")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ isctest.check.noerror(res)
+ dnskeys, sigs = res.answer
+ ksk = [a for a in dnskeys if a.flags == 257][0]
+ up.add("cdnskey-update.secure.", 1, "CDNSKEY", str(ksk))
+ res = isctest.query.tcp(up, "10.53.0.2")
+ isctest.check.noerror(res)
+
+ msg = isctest.query.create("cdnskey-update.secure.", "CDNSKEY")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ cdnskey, sig = res.answer
+ assert len(cdnskey) == 1
+ assert len(sig) == 1
+ assert sig[0].key_tag == keyid
+
+ # check that CDNSKEY deletion records are signed only using KSK when
+ # added by nsupdate
+ up = update.UpdateMessage("cdnskey-update.secure.")
+ up.delete("cdnskey-update.secure.", "CDNSKEY")
+ up.add("cdnskey-update.secure.", 0, "CDNSKEY", "0 3 0 AA==")
+ res = isctest.query.tcp(up, "10.53.0.2")
+ isctest.check.noerror(res)
+
+ msg = isctest.query.create("cdnskey-update.secure.", "CDNSKEY")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ cdnskey, sig = res.answer
+ assert len(cdnskey) == 1
+ assert "0 3 0 AA==" in str(cdnskey[0])
+ assert len(sig) == 1
+ assert sig[0].key_tag == keyid
+
+ # check that a non-matching CDNSKEY record is accepted with a
+ # matching CDNSKEY record. first, generate a DNSKEY with different flags:
+ badksk = type(ksk)(
+ ksk.rdclass, ksk.rdtype, ksk.flags + 1, ksk.protocol, ksk.algorithm, ksk.key
+ )
+ up = update.UpdateMessage("cdnskey-update.secure.")
+ up.delete("cdnskey-update.secure.", "CDNSKEY")
+ up.add("cdnskey-update.secure.", 1, "CDNSKEY", str(ksk))
+ up.add("cdnskey-update.secure.", 1, "CDNSKEY", str(badksk))
+ res = isctest.query.tcp(up, "10.53.0.2")
+ isctest.check.noerror(res)
+
+ msg = isctest.query.create("cdnskey-update.secure.", "CDNSKEY")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ cdnskey, sig = res.answer
+ assert len(cdnskey) == 2
+ assert len(sig) == 1
+
+
+@pytest.mark.parametrize(
+ "cmd",
+ [
+ "signing", # without arguments
+ "signing -list", # without zone
+ "signing -clear", # without zone
+ "signing -clear all", # without zone
+ ],
+)
+def test_rndc_signing_except(cmd, servers):
+ ns3 = servers["ns3"]
+
+ # check that 'rndc signing' errors are handled
+ with pytest.raises(isctest.rndc.RNDCException):
+ ns3.rndc(cmd, log=False)
+ ns3.rndc("status", log=False)
+
+
+def test_rndc_signing_output(servers):
+ ns3 = servers["ns3"]
+
+ response = ns3.rndc("signing -list dynamic.example", log=False)
+ assert "No signing records found" in response
+
+
+def test_zonestatus_signing(servers):
+ ns3 = servers["ns3"]
+ # check that the correct resigning time is reported in zonestatus.
+ # zonestatus reports a name/type and expecting resigning time;
+ # we convert the time to seconds since epoch, look up the RRSIG
+ # for the name and type, and check that the resigning time is
+ # after the inception and before the expiration.
+
+ response = ns3.rndc("zonestatus secure.example", log=False)
+
+ # next resign node: secure.example/DNSKEY
+ nrn = [r for r in response.splitlines() if "next resign node" in r][0]
+ rdname, rdtype = nrn.split()[3].split("/")
+
+ # next resign time: Thu, 24 Apr 2014 10:38:16 GMT
+ nrt = [r for r in response.splitlines() if "next resign time" in r][0]
+ rtime = " ".join(nrt.split()[3:])
+ rt = time.strptime(rtime, "%a, %d %b %Y %H:%M:%S %Z")
+ when = int(time.strftime("%s", rt))
+
+ msg = isctest.query.create(rdname, rdtype)
+ res = isctest.query.tcp(msg, "10.53.0.3")
+ _, sigs = res.answer
+ assert sigs[0].inception < when
+ assert when < sigs[0].expiration
+
+
+def test_offline_ksk_signing(servers):
+ def getfrom(file):
+ with open(file, encoding="utf-8") as f:
+ return f.read().strip()
+
+ def getkeyid(key: str):
+ m = re.match(r"K.*\+\d*\+(\d*)", key)
+ return int(m.group(1))
+
+ def check_signing_keys(types: list[str], expect: list[str], prohibit: list[str]):
+ for qtype in types:
+ isctest.log.debug(f"checking signing keys for {qtype}")
+ msg = isctest.query.create(zone, qtype)
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ assert res.answer, str(res)
+ rrset = res.get_rrset(
+ res.answer,
+ name.from_text(f"{zone}."),
+ rdataclass.IN,
+ rdatatype.RRSIG,
+ rdatatype.RdataType.make(qtype),
+ )
+ assert rrset, f"expected RRSIG({qtype}) missing from ANSWER" + str(res)
+ keys = {rr.key_tag for rr in rrset}
+ assert len(keys) == 1, str(res)
+ for exp in expect:
+ assert exp in keys
+ for proh in prohibit:
+ assert proh not in keys
+ return True
+
+ def check_zskcount():
+ msg = isctest.query.create(zone, "DNSKEY")
+ res = isctest.query.tcp(msg, "10.53.0.2")
+ dnskeys, _ = res.answer
+ zskcount = len([rr for rr in dnskeys if rr.flags == 256])
+ assert zskcount == 2, str(res)
+ return True
+
+ def ksk_remove():
+ isctest.log.info("remove the KSK from disk")
+ os.rename(f"ns2/{KSK}.key", f"ns2/{KSK}.key.bak")
+ os.rename(f"ns2/{KSK}.private", f"ns2/{KSK}.private.bak")
+
+ def ksk_recover():
+ isctest.log.info("put back the KSK")
+ os.rename(f"ns2/{KSK}.key.bak", f"ns2/{KSK}.key")
+ os.rename(f"ns2/{KSK}.private.bak", f"ns2/{KSK}.private")
+
+ def loadkeys():
+ pattern = re.compile(f"{zone}/IN.*next key event")
+ with ns2.watch_log_from_here() as watcher:
+ ns2.rndc(f"loadkeys {zone}", log=False)
+ watcher.wait_for_line(pattern)
+
+ ksk_only_types = ["DNSKEY", "CDNSKEY", "CDS"]
+
+ ns2 = servers["ns2"]
+ zone = "updatecheck-kskonly.secure"
+ KSK = getfrom(f"ns2/{zone}.ksk.key")
+ ZSK = getfrom(f"ns2/{zone}.zsk.key")
+ KSKID = int(getfrom(f"ns2/{zone}.ksk.id"))
+ ZSKID = int(getfrom(f"ns2/{zone}.zsk.id"))
+
+ # set key state for KSK. the ZSK rollovers below assume that there is a
+ # chain of trust established, so we tell named that the DS is in
+ # omnipresent state.
+ settime("-s", "-d", "OMNIPRESENT", "now", "-Kns2", KSK)
+
+ isctest.log.info("check state before KSK is made offline")
+ isctest.log.info("make sure certain types are signed with KSK only")
+ check_signing_keys(ksk_only_types, expect=[KSKID], prohibit=[ZSKID])
+
+ isctest.log.info("check SOA is signed with ZSK only")
+ check_signing_keys(["SOA"], expect=[ZSKID], prohibit=[KSKID])
+
+ isctest.log.info("roll the ZSK")
+ ZSK2 = keygen(
+ "-qKns2",
+ "-Pnone",
+ "-Anone",
+ "-a",
+ os.environ["DEFAULT_ALGORITHM"],
+ "-b",
+ os.environ["DEFAULT_BITS"],
+ zone,
+ )
+ ZSKID2 = getkeyid(ZSK2)
+
+ isctest.log.info("prepublish new ZSK")
+ ns2.rndc(f"dnssec -rollover -key {ZSKID} {zone}", log=False)
+ isctest.run.retry_with_timeout(check_zskcount, 5)
+
+ isctest.log.info("make the new ZSK active")
+ settime("-sKns2", "-Inow", ZSK)
+ settime("-sKns2", "-Anow", "-k", "OMNIPRESENT", "now", ZSK2)
+ loadkeys()
+
+ with ns2.watch_log_from_start() as watcher:
+ watcher.wait_for_line(
+ [f"{ZSKID2} (ZSK) is now active", f"{ZSKID} (ZSK) is now inactive"]
+ )
+
+ ksk_remove()
+
+ isctest.log.info("update the zone, requiring a resign of the SOA RRset")
+ up = update.UpdateMessage(f"{zone}.")
+ up.add(f"{zone}.", 300, "TXT", "added by UPDATE")
+ res = isctest.query.tcp(up, "10.53.0.2")
+ isctest.check.noerror(res)
+
+ isctest.log.info(
+ "redo the tests now that the zone is updated and the KSK is offline"
+ )
+ isctest.log.info("make sure certain types are signed with KSK only")
+ check_signing_keys(ksk_only_types, expect=[KSKID], prohibit=[ZSKID, ZSKID2])
+
+ isctest.log.info("check TXT, SOA are signed with ZSK2 only")
+
+ def check_txt_soa_zsk2():
+ return check_signing_keys(
+ ["TXT", "SOA"], expect=[ZSKID2], prohibit=[KSKID, ZSKID]
+ )
+
+ isctest.run.retry_with_timeout(check_txt_soa_zsk2, 5)
+
+ ksk_recover()
+
+ isctest.log.info("roll the ZSK again")
+ ZSK3 = keygen(
+ "-qKns2",
+ "-Pnone",
+ "-Anone",
+ "-a",
+ os.environ["DEFAULT_ALGORITHM"],
+ "-b",
+ os.environ["DEFAULT_BITS"],
+ zone,
+ )
+ ZSKID3 = getkeyid(ZSK3)
+
+ isctest.log.info("delete old ZSK, schedule ZSK2 inactive, pre-publish ZSK3")
+ settime("-sKns2", "-k", "HIDDEN", "now", "-z", "HIDDEN", "now", "-Dnow", ZSK)
+ settime("-sKns2", "-k", "OMNIPRESENT", "now", "-z", "OMNIPRESENT", "now", ZSK2)
+ loadkeys()
+ ns2.rndc(f"dnssec -rollover -key {ZSKID2} {zone}", log=False)
+
+ with ns2.watch_log_from_start() as watcher:
+ watcher.wait_for_line(f"{ZSKID3} (ZSK) is now published")
+
+ ksk_remove()
+
+ isctest.log.info("update the zone again, requiring a resign of the SOA RRset")
+ up = update.UpdateMessage(f"{zone}.")
+ up.add(f"{zone}.", 300, "TXT", "added by UPDATE again")
+ up.add(f"{zone}.", 300, "A", "1.2.3.4")
+ res = isctest.query.tcp(up, "10.53.0.2")
+ isctest.check.noerror(res)
+
+ isctest.log.info("redo the tests now that the ZSK roll has deleted the old key")
+
+ isctest.log.info("make sure certain types are signed with KSK only")
+ check_signing_keys(ksk_only_types, expect=[KSKID], prohibit=[ZSKID, ZSKID2, ZSKID3])
+
+ isctest.log.info("check A, TXT, SOA are signed with ZSK2 only")
+
+ def check_a_txt_soa_zsk2():
+ return check_signing_keys(
+ ["A", "TXT", "SOA"], expect=[ZSKID2], prohibit=[KSKID, ZSKID, ZSKID3]
+ )
+
+ isctest.run.retry_with_timeout(check_a_txt_soa_zsk2, 5)
+
+ ksk_recover()
+
+ isctest.log.info("make ZSK3 active")
+ settime("-sKns2", "-Inow", ZSK2)
+ settime("-sKns2", "-k", "OMNIPRESENT", "now", "-Anow", ZSK3)
+ loadkeys()
+
+ with ns2.watch_log_from_start() as watcher:
+ watcher.wait_for_line(
+ [f"{ZSKID3} (ZSK) is now active", f"{ZSKID2} (ZSK) is now inactive"]
+ )
+
+ ksk_remove()
+
+ isctest.log.info("update the zone again, requiring a resign of the SOA RRset")
+ up = update.UpdateMessage(f"{zone}.")
+ up.add(f"{zone}.", 300, "TXT", "added by UPDATE one more time")
+ up.add(f"{zone}.", 300, "A", "4.3.2.1")
+ up.add(f"{zone}.", 300, "AAAA", "dead::beef")
+ res = isctest.query.tcp(up, "10.53.0.2")
+ isctest.check.noerror(res)
+
+ isctest.log.info("redo the tests one last time")
+ isctest.log.info("make sure certain types are signed with KSK only")
+ check_signing_keys(ksk_only_types, expect=[KSKID], prohibit=[ZSKID, ZSKID2, ZSKID3])
+
+ isctest.log.info("check A, TXT, SOA are signed with ZSK2 only")
+
+ def check_aaaa_a_txt_soa_zsk3():
+ return check_signing_keys(
+ ["AAAA", "A", "TXT", "SOA"],
+ expect=[ZSKID3],
+ prohibit=[KSKID, ZSKID, ZSKID2],
+ )
+
+ isctest.run.retry_with_timeout(check_aaaa_a_txt_soa_zsk3, 5)
def test_expired_signatures(servers):
- # check expired signatures are still in place when updates are disabled
- msg = isctest.query.create("expired.example", "SOA")
- res = isctest.query.tcp(msg, "10.53.0.3")
- soa, sigs = res.answer
- assert sigs
-
# check expired signatures do not validate
msg = isctest.query.create("expired.example", "SOA")
res = isctest.query.tcp(msg, "10.53.0.3")