diff --git a/bin/tests/system/dnssec/dnssec_update_test.pl b/bin/tests/system/dnssec/dnssec_update_test.pl deleted file mode 100644 index a06c563e3c..0000000000 --- a/bin/tests/system/dnssec/dnssec_update_test.pl +++ /dev/null @@ -1,99 +0,0 @@ -#!/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; diff --git a/bin/tests/system/dnssec/ns2/named.conf.j2 b/bin/tests/system/dnssec/ns2/named.conf.j2 index da6df5d5fb..093aba3120 100644 --- a/bin/tests/system/dnssec/ns2/named.conf.j2 +++ b/bin/tests/system/dnssec/ns2/named.conf.j2 @@ -185,7 +185,7 @@ zone "cdnskey-auto.secure" { zone "updatecheck-kskonly.secure" { type primary; - file "updatecheck-kskonly.secure.db.signed"; + file "updatecheck-kskonly.secure.db"; dnssec-policy kskonly; allow-update { any; }; }; diff --git a/bin/tests/system/dnssec/ns2/sign.sh b/bin/tests/system/dnssec/ns2/sign.sh index 2658fd10b3..ca824a2ad3 100644 --- a/bin/tests/system/dnssec/ns2/sign.sh +++ b/bin/tests/system/dnssec/ns2/sign.sh @@ -297,7 +297,6 @@ $SETTIME -s -g OMNIPRESENT -k OMNIPRESENT now -r OMNIPRESENT now -d RUMOURED now $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 diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index 2b4f9c864a..89fcf1c6ab 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -19,62 +19,10 @@ set -e 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' ' /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 diff --git a/bin/tests/system/dnssec/tests_sh_dnssec.py b/bin/tests/system/dnssec/tests_sh_dnssec.py index 9f9cc76bf6..8448082c8c 100644 --- a/bin/tests/system/dnssec/tests_sh_dnssec.py +++ b/bin/tests/system/dnssec/tests_sh_dnssec.py @@ -72,6 +72,7 @@ pytestmark = pytest.mark.extra_artifacts( "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", diff --git a/bin/tests/system/dnssec/tests_signing.py b/bin/tests/system/dnssec/tests_signing.py new file mode 100644 index 0000000000..a5919e78d9 --- /dev/null +++ b/bin/tests/system/dnssec/tests_signing.py @@ -0,0 +1,613 @@ +# 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) diff --git a/bin/tests/system/dnssec/tests_validation.py b/bin/tests/system/dnssec/tests_validation.py index f0d132b9dc..8c9bc78554 100644 --- a/bin/tests/system/dnssec/tests_validation.py +++ b/bin/tests/system/dnssec/tests_validation.py @@ -1084,12 +1084,6 @@ def test_validating_forwarder(servers): 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")