]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Convert ksr system test to pytest
authorMatthijs Mekking <matthijs@isc.org>
Mon, 2 Sep 2024 15:36:47 +0000 (17:36 +0200)
committerMatthijs Mekking <matthijs@isc.org>
Wed, 16 Oct 2024 15:16:02 +0000 (17:16 +0200)
Move all test cases from tests.sh to tests_ksr.py. The only test that
is not moved is the check that key id's match expected keys. The
shell-based system test checks two earlier set environment variables
against each other that has become redundant in the pytest variant,
because we now check the signed key response against a list of keys
and for each key we take into account the timing metadata. So we
already ensure that each published key is in the correct key bundle.

bin/tests/system/ksr/clean.sh [deleted file]
bin/tests/system/ksr/ns1/setup.sh
bin/tests/system/ksr/setup.sh
bin/tests/system/ksr/tests.sh [deleted file]
bin/tests/system/ksr/tests_ksr.py [new file with mode: 0644]
bin/tests/system/ksr/tests_sh_ksr.py [deleted file]

diff --git a/bin/tests/system/ksr/clean.sh b/bin/tests/system/ksr/clean.sh
deleted file mode 100644 (file)
index 4149685..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/sh
-
-# 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.
-
-set -e
-
-rm -f ./*.ksk*
-rm -f ./*.zsk*
-rm -f ./created.out
-rm -f ./footer.*
-rm -f ./now.out
-rm -f ./ns1/*.db
-rm -f ./ns1/*.db.jbk
-rm -f ./ns1/*.db.signed
-rm -f ./ns1/*.db.signed.jnl
-rm -f ./ns1/K*
-rm -f ./ns1/keygen.out.*
-rm -f ./ns1/named.conf
-rm -f ./ns1/named.memstats
-rm -f ./ns1/named.run
-rm -f ./python.out
-rm -f ./settime.out.*
-rm -f ./ksr.*.err.*
-rm -f ./ksr.*.expect
-rm -f ./ksr.*.expect.*
-rm -f ./ksr.*.out.*
-
-rm -rf ./ns1/keydir
-rm -rf ./ns1/offline
index c17c43de2e5e36cbc5ebb4aed5fdf9366a45d8d8..a04b01a23e8e53a2451cef9d09127fb0f0f4bc3d 100644 (file)
@@ -24,24 +24,5 @@ cp template.db.in past.test.db
 cp template.db.in future.test.db
 cp template.db.in last-bundle.test.db
 cp template.db.in in-the-middle.test.db
-
-# Create KSK for the various policies.
-create_ksk() {
-  KSK=$($KEYGEN -l named.conf -fK -k $2 $1 2>keygen.out.$1)
-  num=0
-  for ksk in $KSK; do
-    num=$(($num + 1))
-    echo $ksk >"../${1}.ksk${num}.id"
-    cat "${ksk}.key" | grep -v ";.*" >"../$1.ksk$num"
-    mv "${ksk}.key" offline/
-    mv "${ksk}.private" offline/
-    mv "${ksk}.state" offline/
-  done
-}
-create_ksk common.test common
-create_ksk past.test common
-create_ksk future.test common
-create_ksk last-bundle.test common
-create_ksk in-the-middle.test common
-create_ksk unlimited.test unlimited
-create_ksk two-tone.test two-tone
+cp template.db.in unlimited.test.db
+cp template.db.in two-tone.test.db
index 2cfad07fb17b52494f8175e1eeee40f10ab4fcb0..e200a4d434fd7e452b82f4ed22a3ef755364c4a9 100644 (file)
@@ -16,8 +16,6 @@
 
 set -e
 
-$SHELL clean.sh
-
 copy_setports ns1/named.conf.in ns1/named.conf
 
 (
diff --git a/bin/tests/system/ksr/tests.sh b/bin/tests/system/ksr/tests.sh
deleted file mode 100644 (file)
index 4fb9f9f..0000000
+++ /dev/null
@@ -1,1207 +0,0 @@
-#!/bin/sh
-
-# 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.
-
-# shellcheck source=conf.sh
-. ../conf.sh
-# shellcheck source=kasp.sh
-. ../kasp.sh
-
-set -e
-
-RNDCCMD="$RNDC -c ../_common/rndc.conf -p ${CONTROLPORT} -s"
-
-CDS_SHA1="no"
-CDS_SHA256="yes"
-CDS_SHA384="no"
-CDNSKEY="yes"
-
-status=0
-n=0
-
-# Get timing metadata from a value plus additional time.
-# $1: Value
-# $2: Additional time
-addtime() {
-  if [ -x "$PYTHON" ]; then
-    # Convert "%Y%m%d%H%M%S" format to epoch seconds.
-    # Then, add the additional time (can be negative).
-    _value=$1
-    _plus=$2
-    $PYTHON >python.out <<EOF
-from datetime import datetime
-from datetime import timedelta
-_now = datetime.strptime("$_value", "%Y%m%d%H%M%S")
-_delta = timedelta(seconds=$_plus)
-_then = _now + _delta
-print(_then.strftime("%Y%m%d%H%M%S"));
-EOF
-    cat python.out
-  fi
-}
-
-# Check keys that were created. The keys created are listed in the latest ksr
-# output file, ksr.keygen.out.$n.
-# $1: zone name
-# $2: key directory
-# $3: offset
-ksr_check_keys() (
-  zone=$1
-  dir=$2
-  offset=$3
-  lifetime=$LIFETIME
-  alg=$ALG
-  size=$SIZE
-  inception=0
-  pad=$(printf "%03d" "$alg")
-
-  num=0
-  for key in $(grep "K${zone}.+$pad+" ksr.keygen.out.$n); do
-    grep "; Created:" "${dir}/${key}.key" >created.out || return 1
-    created=$(awk '{print $3}' <created.out)
-    test "$num" -eq 0 && retired=$(addtime $created $offset)
-    # active: retired previous key
-    active=$retired
-    # published: 2h5m (dnskey-ttl + publish-safety + propagation)
-    published=$(addtime $active -7500)
-    # retired: zsk-lifetime
-    retired=$(addtime $active $lifetime)
-    # removed: 10d1h5m (ttlsig + retire-safety + sign-delay + propagation)
-    removed=$(addtime $retired 867900)
-
-    echo_i "check metadata on $key: $alg $size $lifetime $published $active $retired $removed"
-    statefile="${dir}/${key}.state"
-    grep "Algorithm: $alg" $statefile >/dev/null || return 1
-    grep "Length: $size" $statefile >/dev/null || return 1
-    grep "Lifetime: $lifetime" $statefile >/dev/null || return 1
-    grep "KSK: no" $statefile >/dev/null || return 1
-    grep "ZSK: yes" $statefile >/dev/null || return 1
-    grep "Published: $published" $statefile >/dev/null || return 1
-    grep "Active: $active" $statefile >/dev/null || return 1
-    grep "Retired: $retired" $statefile >/dev/null || return 1
-    grep "Removed: $removed" $statefile >/dev/null || return 1
-
-    inception=$((inception + lifetime))
-    num=$((num + 1))
-
-    # Save some information for testing
-    cp ${dir}/${key}.key ${key}.key.expect
-    cp ${dir}/${key}.private ${key}.private.expect
-    cp ${dir}/${key}.state ${key}.state.expect
-    cat ${dir}/${key}.key | grep -v ";.*" >"${zone}.${alg}.zsk${num}"
-    echo $key >"${zone}.${alg}.zsk${num}.id"
-  done
-
-  return 0
-)
-
-# Print the DNSKEY records for zone $1, which have keys listed in file $5
-# that match the keys with numbers $2 and $3, and match algorithm number $4,
-# sorted by keytag.
-print_dnskeys() {
-  for key in $(cat $5 | sort); do
-    for num in $2 $3; do
-      zsk=$(cat $1.$4.zsk$num.id)
-      if [ "$key" = "$zsk" ]; then
-        cat $1.$4.zsk$num >>ksr.request.expect.$n
-      fi
-    done
-  done
-}
-# Call the dnssec-ksr command:
-# ksr <policy> [options] <command> <zone>
-ksr() {
-  $KSR -l ns1/named.conf -k "$@"
-}
-
-# Unknown action.
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr' errors on unknown action ($n)"
-ret=0
-ksr common foobar common.test >ksr.foobar.out.$n 2>&1 && ret=1
-grep "dnssec-ksr: fatal: unknown command 'foobar'" ksr.foobar.out.$n >/dev/null || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Key generation: common
-set_zsk() {
-  ALG=$1
-  SIZE=$2
-  LIFETIME=$3
-}
-
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr keygen' errors on missing end date ($n)"
-ret=0
-ksr common keygen common.test >ksr.keygen.out.$n 2>&1 && ret=1
-grep "dnssec-ksr: fatal: keygen requires an end date" ksr.keygen.out.$n >/dev/null || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr keygen' pregenerates right amount of keys in the common case ($n)"
-ret=0
-ksr common -K ns1 -i now -e +1y keygen common.test >ksr.keygen.out.$n 2>&1 || ret=1
-num=$(cat ksr.keygen.out.$n | wc -l)
-[ $num -eq 2 ] || ret=1
-set_zsk $DEFAULT_ALGORITHM_NUMBER $DEFAULT_BITS 16070400
-ksr_check_keys common.test ns1 0 || ret=1
-cp ksr.keygen.out.$n ksr.keygen.out.expect
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-# save now time
-key=$(cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk1.id)
-grep "; Created:" "ns1/${key}.key" >now.out || ret=1
-now=$(awk '{print $3}' <now.out)
-
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr keygen' selects pregenerated keys for the same time bundle ($n)"
-ret=0
-ksr common -K ns1 -e +1y keygen common.test >ksr.keygen.out.$n 2>&1 || ret=1
-diff -w ksr.keygen.out.expect ksr.keygen.out.$n >/dev/null || ret=1
-for key in $(cat ksr.keygen.out.$n); do
-  # Ensure the files are not modified.
-  diff ns1/${key}.key ${key}.key.expect >/dev/null || ret=1
-  diff ns1/${key}.private ${key}.private.expect >/dev/null || ret=1
-  diff ns1/${key}.state ${key}.state.expect >/dev/null || ret=1
-done
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Create request: common
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr request' errors on missing end date ($n)"
-ret=0
-ksr common -K ns1 request common.test >ksr.request.out.$n 2>&1 && ret=1
-grep "dnssec-ksr: fatal: request requires an end date" ksr.request.out.$n >/dev/null || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr request' creates correct KSR in the common case ($n)"
-ret=0
-ksr common -K ns1 -i $now -e +1y request common.test >ksr.request.out.$n 2>&1 || ret=1
-# Bundle 1: KSK + ZSK1
-key=$(cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk1.id)
-inception=$(cat ns1/$key.state | grep "Generated" | cut -d' ' -f 2-)
-echo ";; KeySigningRequest 1.0 $inception" >ksr.request.expect.$n
-cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk1 >>ksr.request.expect.$n
-# Bundle 2: KSK + ZSK1 + ZSK2
-key=$(cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk2.id)
-inception=$(cat ns1/$key.state | grep "Published" | cut -d' ' -f 2-)
-echo ";; KeySigningRequest 1.0 $inception" >>ksr.request.expect.$n
-print_dnskeys common.test 1 2 $DEFAULT_ALGORITHM_NUMBER ksr.keygen.out.expect
-# Bundle 3: KSK + ZSK2
-key=$(cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk1.id)
-inception=$(cat ns1/$key.state | grep "Removed" | cut -d' ' -f 2-)
-echo ";; KeySigningRequest 1.0 $inception" >>ksr.request.expect.$n
-cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk2 >>ksr.request.expect.$n
-# Footer
-cp ksr.request.expect.$n ksr.request.expect.base
-grep ";; KeySigningRequest 1.0 generated at" ksr.request.out.$n >footer.$n || ret=1
-cat footer.$n >>ksr.request.expect.$n
-# Check if request output is the same as expected.
-diff -w ksr.request.out.$n ksr.request.expect.$n >/dev/null || ret=1
-# Save request for ksr sign operation.
-cp ksr.request.expect.$n ksr.request.expect
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Sign request: common
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr sign' errors on missing KSR file ($n)"
-ret=0
-ksr common -K ns1 -i $now -e +1y sign common.test >ksr.sign.out.$n 2>&1 && ret=1
-grep "dnssec-ksr: fatal: 'sign' requires a KSR file" ksr.sign.out.$n >/dev/null || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr sign' creates correct SKR in the common case ($n)"
-ret=0
-ksr common -K ns1 -i $now -e +1y -K ns1/offline -f ksr.request.expect sign common.test >ksr.sign.out.$n 2>&1 || ret=1
-
-_update_expected_zsks() {
-  zsk=$((zsk + 1))
-  next=$((next + 1))
-  inception=$rollover_done
-  if [ "$next" -le "$numzsks" ]; then
-    key1="${zone}.${DEFAULT_ALGORITHM_NUMBER}.zsk${zsk}"
-    key2="${zone}.${DEFAULT_ALGORITHM_NUMBER}.zsk${next}"
-    zsk1=$(cat $key1.id)
-    zsk2=$(cat $key2.id)
-    rollover_start=$(cat ns1/$zsk2.state | grep "Published" | awk '{print $2}')
-    rollover_done=$(cat ns1/$zsk1.state | grep "Removed" | awk '{print $2}')
-  else
-    # No more expected rollovers.
-    key1="${zone}.${DEFAULT_ALGORITHM_NUMBER}.zsk${zsk}"
-    zsk1=$(cat $key1.id)
-    rollover_start=$((end + 1))
-    rollover_done=$((end + 1))
-  fi
-}
-
-check_skr() {
-  _ret=0
-  zone=$1
-  dir=$2
-  file=$3
-  start=$4
-  end=$5
-  numzsks=$6
-  cds1=$($DSFROMKEY -T 3600 -a SHA-1 -C -w $dir/$(cat "${zone}.ksk1.id"))
-  cds2=$($DSFROMKEY -T 3600 -a SHA-256 -C -w $dir/$(cat "${zone}.ksk1.id"))
-  cds4=$($DSFROMKEY -T 3600 -a SHA-384 -C -w $dir/$(cat "${zone}.ksk1.id"))
-  cdnskey=$(awk '{sub(/DNSKEY/,"CDNSKEY")}1' <${zone}.ksk1)
-
-  echo_i "check skr: zone $1 file $2 from $3 to $4 num-zsk $5"
-
-  # Initial state: not in a rollover, expect a SignedKeyResponse header
-  # on the first line, start with the first ZSK (set zsk=0 so when we
-  # call _update_expected_zsks, zsk is set to 1.
-  rollover=0
-  expect="header"
-  zsk=0
-  next=1
-  rollover_done=$start
-  _update_expected_zsks
-
-  echo_i "check skr: inception $inception rollover-start $rollover_start rollover-done $rollover_done"
-
-  lineno=0
-  complete=0
-  while IFS= read -r line; do
-    # A single signed key response may consist of:
-    # ;; SignedKeyResponse (header)
-    # ;; DNSKEY 257 (ksk)
-    # ;; one or two (during rollover) DNSKEY 256 (zsk1, zsk2)
-    # ;; RRSIG(DNSKEY) (rrsig-dnskey)
-    # ;; CDNSKEY (cdnskey)
-    # ;; RRSIG(CDNSKEY) (rrsig-cdnskey)
-    # ;; CDS (cds)
-    # ;; RRSIG(CDS) (rrsig-cds)
-    err=0
-    lineno=$((lineno + 1))
-
-    # skip empty lines
-    if [ -z "$line" ]; then
-      continue
-    fi
-
-    if [ "$expect" = "header" ]; then
-      expected=";; SignedKeyResponse 1.0 $inception"
-      echo $line | grep "$expected" >/dev/null || err=1
-      next_inception=$(addtime $inception 777600)
-      expect="ksk"
-    elif [ "$expect" = "ksk" ]; then
-      expected="$(cat ${zone}.ksk1)"
-      echo $line | grep "$expected" >/dev/null || err=1
-      expect="zsk1"
-    elif [ "$expect" = "cdnskey" ]; then
-      expected="$cdnskey"
-      echo $line | grep "$expected" >/dev/null || err=1
-      expect="rrsig-cdnskey"
-    elif [ "$expect" = "cds1" ]; then
-      expected="$cds1"
-      echo $line | grep "$expected" >/dev/null || err=1
-      if [ "$CDS_SHA256" = "yes" ]; then
-        expect="cds2"
-      elif [ "$CDS_SHA384" = "yes" ]; then
-        expect="cds4"
-      else
-        expect="rrsig-cds"
-      fi
-    elif [ "$expect" = "cds2" ]; then
-      expected="$cds2"
-      echo $line | grep "$expected" >/dev/null || err=1
-      if [ "$CDS_SHA384" = "yes" ]; then
-        expect="cds4"
-      else
-        expect="rrsig-cds"
-      fi
-    elif [ "$expect" = "cds4" ]; then
-      expected="$cds4"
-      echo $line | grep "$expected" >/dev/null || err=1
-      expect="rrsig-cds"
-    elif [ "$expect" = "zsk1" ]; then
-      expected="$(cat $key1)"
-      echo $line | grep "$expected" >/dev/null || err=1
-      expect="rrsig-dnskey"
-      [ "$rollover" -eq 1 ] && expect="zsk2"
-    elif [ "$expect" = "zsk2" ]; then
-      expected="$(cat $key2)"
-      echo $line | grep "$expected" >/dev/null || err=1
-      expect="rrsig-dnskey"
-    elif [ "$expect" = "rrsig-dnskey" ]; then
-      exp=$(addtime $inception 1209600) # signature-validity 14 days
-      inc=$(addtime $inception -3600)   # adjust for one hour clock skew
-      expected="${zone}. 3600 IN RRSIG DNSKEY 13 2 3600 $exp $inc"
-      echo $line | grep "$expected" >/dev/null || err=1
-      if [ "$CDNSKEY" = "yes" ]; then
-        expect="cdnskey"
-      elif [ "$CDS_SHA1" = "yes" ]; then
-        expect="cds1"
-      elif [ "$CDS_SHA256" = "yes" ]; then
-        expect="cds2"
-      elif [ "$CDS_SHA384" = "yes" ]; then
-        expect="cds4"
-      else
-        complete=1
-      fi
-    elif [ "$expect" = "rrsig-cdnskey" ]; then
-      exp=$(addtime $inception 1209600) # signature-validity 14 days
-      inc=$(addtime $inception -3600)   # adjust for one hour clock skew
-      expected="${zone}. 3600 IN RRSIG CDNSKEY 13 2 3600 $exp $inc"
-      echo $line | grep "$expected" >/dev/null || err=1
-      if [ "$CDS_SHA1" = "yes" ]; then
-        expect="cds1"
-      elif [ "$CDS_SHA256" = "yes" ]; then
-        expect="cds2"
-      elif [ "$CDS_SHA384" = "yes" ]; then
-        expect="cds4"
-      else
-        complete=1
-      fi
-    elif [ "$expect" = "rrsig-cds" ]; then
-      exp=$(addtime $inception 1209600) # signature-validity 14 days
-      inc=$(addtime $inception -3600)   # adjust for one hour clock skew
-      expected="${zone}. 3600 IN RRSIG CDS 13 2 3600 $exp $inc"
-      echo $line | grep "$expected" >/dev/null || err=1
-      complete=1
-    elif [ "$expect" = "footer" ]; then
-      expected=";; SignedKeyResponse 1.0 generated at"
-      echo "$(echo $line | tr -s ' ')" | grep "$expected" >/dev/null || err=1
-
-      expect="eof"
-    elif [ "$expect" = "eof" ]; then
-      expected="EOF"
-      echo_i "failed: expected EOF"
-      err=1
-    else
-      echo_i "failed: bad expect value $expect"
-      err=1
-    fi
-
-    echo "$(echo $line | tr -s ' ')" | grep "$expected" >/dev/null || err=1
-    if [ "$err" -ne 0 ]; then
-      echo_i "unexpected data on line $lineno:"
-      echo_i "line:     $(echo $line | tr -s ' ')"
-      echo_i "expected: $expected"
-    fi
-
-    if [ "$complete" -eq 1 ]; then
-      inception=$next_inception
-      expect="header"
-
-      # Update rollover status if required.
-      if [ "$inception" -ge "$end" ]; then
-        expect="footer"
-      elif [ "$inception" -ge "$rollover_done" ]; then
-        [ "$rollover" -eq 1 ] && inception=$rollover_done
-        rollover=0
-        _update_expected_zsks
-      elif [ "$inception" -ge "$rollover_start" ]; then
-        [ "$rollover" -eq 0 ] && inception=$rollover_start
-        rollover=1
-        # Keys will be sorted, so during a rollover a key with a
-        # lower keytag will be printed first. Update key1/key2 and
-        # zsk1/zsk2 accordingly.
-        id1=$(keyfile_to_key_id "$zsk1")
-        id2=$(keyfile_to_key_id "$zsk2")
-        if [ $id1 -gt $id2 ]; then
-          key1="${zone}.${DEFAULT_ALGORITHM_NUMBER}.zsk${next}"
-          key2="${zone}.${DEFAULT_ALGORITHM_NUMBER}.zsk${zsk}"
-          zsk1=$(cat $key1.id)
-          zsk2=$(cat $key2.id)
-        fi
-      fi
-      complete=0
-    fi
-
-    _ret=$((_ret + err))
-    test "$_ret" -eq 0 || exit $_ret
-  done <$file
-
-  return $_ret
-}
-
-zsk1=$(cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk1.id)
-start=$(cat ns1/$zsk1.state | grep "Generated" | awk '{print $2}')
-end=$(addtime $start 31536000) # one year
-check_skr "common.test" "ns1/offline" "ksr.sign.out.$n" $start $end 2 || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Key generation: common (2)
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr keygen' pregenerates keys in the given key-directory ($n)"
-ret=0
-ksr common -K ns1/keydir -e +1y keygen common.test >ksr.keygen.out.$n 2>&1 || ret=1
-num=$(cat ksr.keygen.out.$n | wc -l)
-[ $num -eq 2 ] || ret=1
-set_zsk $DEFAULT_ALGORITHM_NUMBER $DEFAULT_BITS 16070400
-ksr_check_keys common.test ns1/keydir 0 || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr keygen' selects generates only necessary keys for overlapping time bundle ($n)"
-ret=0
-ksr common -K ns1 -e +2y -v 1 keygen common.test >ksr.keygen.out.$n 2>&1 || ret=1
-num=$(cat ksr.keygen.out.$n | wc -l)
-[ $num -eq 4 ] || ret=1
-# 2 selected, 2 generated
-num=$(grep "Selecting" ksr.keygen.out.$n | wc -l)
-[ $num -eq 2 ] || ret=1
-num=$(grep "Generating" ksr.keygen.out.$n | wc -l)
-[ $num -eq 2 ] || ret=1
-cp ksr.keygen.out.$n ksr.keygen.out.expect
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "run 'dnssec-ksr keygen' again with verbosity 0 ($n)"
-ret=0
-ksr common -K ns1 -i $now -e +2y keygen common.test >ksr.keygen.out.$n 2>&1 || ret=1
-num=$(cat ksr.keygen.out.$n | wc -l)
-[ $num -eq 4 ] || ret=1
-set_zsk $DEFAULT_ALGORITHM_NUMBER $DEFAULT_BITS 16070400
-ksr_check_keys common.test ns1 0 || ret=1
-cp ksr.keygen.out.$n ksr.keygen.out.expect
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Create request: common (2)
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr request' creates correct KSR if the interval is shorter ($n)"
-ret=0
-ksr common -K ns1 -i $now -e +1y request common.test >ksr.request.out.$n 2>&1 || ret=1
-# Same as earlier.
-cp ksr.request.expect.base ksr.request.expect.$n
-grep ";; KeySigningRequest 1.0 generated at" ksr.request.out.$n >footer.$n || ret=1
-cat footer.$n >>ksr.request.expect.$n
-diff -w ksr.request.out.$n ksr.request.expect.$n >/dev/null || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr request' creates correct KSR with new interval ($n)"
-ret=0
-ksr common -K ns1 -i $now -e +2y request common.test >ksr.request.out.$n 2>&1 || ret=1
-cp ksr.request.expect.base ksr.request.expect.$n
-# Bundle 4: KSK + ZSK2 + ZSK3
-key=$(cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk3.id)
-inception=$(cat ns1/$key.state | grep "Published" | cut -d' ' -f 2-)
-echo ";; KeySigningRequest 1.0 $inception" >>ksr.request.expect.$n
-print_dnskeys common.test 2 3 $DEFAULT_ALGORITHM_NUMBER ksr.keygen.out.expect
-# Bundle 5: KSK + ZSK3
-key=$(cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk2.id)
-inception=$(cat ns1/$key.state | grep "Removed" | cut -d' ' -f 2-)
-echo ";; KeySigningRequest 1.0 $inception" >>ksr.request.expect.$n
-cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk3 >>ksr.request.expect.$n
-# Bundle 6: KSK + ZSK3 + ZSK4
-key=$(cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk4.id)
-inception=$(cat ns1/$key.state | grep "Published" | cut -d' ' -f 2-)
-echo ";; KeySigningRequest 1.0 $inception" >>ksr.request.expect.$n
-print_dnskeys common.test 3 4 $DEFAULT_ALGORITHM_NUMBER ksr.keygen.out.expect
-# Bundle 7: KSK + ZSK4
-key=$(cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk3.id)
-inception=$(cat ns1/$key.state | grep "Removed" | cut -d' ' -f 2-)
-echo ";; KeySigningRequest 1.0 $inception" >>ksr.request.expect.$n
-cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk4 >>ksr.request.expect.$n
-# Footer
-cp ksr.request.expect.$n ksr.request.expect.base
-grep ";; KeySigningRequest 1.0 generated at" ksr.request.out.$n >footer.$n || ret=1
-cat footer.$n >>ksr.request.expect.$n
-diff -w ksr.request.out.$n ksr.request.expect.$n >/dev/null || ret=1
-# Save request for ksr sign operation.
-cp ksr.request.expect.$n ksr.request.expect
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr request' errors if there are not enough keys ($n)"
-ret=0
-ksr common -K ns1 -i $now -e +3y request common.test >ksr.request.out.$n 2>ksr.request.err.$n && ret=1
-grep "dnssec-ksr: fatal: no common.test/ECDSAP256SHA256 zsk key pair found for bundle" ksr.request.err.$n >/dev/null || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Sign request: common (2)
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr sign' creates correct SKR with the new interval ($n)"
-ret=0
-ksr common -K ns1 -i $now -e +2y -K ns1/offline -f ksr.request.expect sign common.test >ksr.sign.out.$n 2>&1 || ret=1
-start=$(cat ns1/$zsk1.state | grep "Generated" | awk '{print $2}')
-end=$(addtime $start 63072000) # two years
-check_skr "common.test" "ns1/offline" "ksr.sign.out.$n" $start $end 4 || ret=1
-# Save response for skr import operation.
-cp ksr.sign.out.$n ns1/common.test.skr
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Add zone: common
-n=$((n + 1))
-echo_i "add zone 'common.test' ($n)"
-ret=0
-$RNDCCMD 10.53.0.1 addzone 'common.test { type primary; file "common.test.db"; dnssec-policy common; };' 2>&1 | sed 's/^/I:ns1 /' || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Import skr: common
-n=$((n + 1))
-echo_i "import ksr to zone 'common.test' ($n)"
-ret=0
-sleep 2
-$RNDCCMD 10.53.0.1 skr -import common.test.skr common.test 2>&1 | sed 's/^/I:ns1 /' || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Test that common.test is signed and uses the right DNSKEY and RRSIG records.
-n=$((n + 1))
-echo_i "test zone 'common.test' is correctly signed ($n)"
-ret=0
-
-set_zone "common.test"
-set_policy "common" "4" "3600"
-set_server "ns1" "10.53.0.1"
-# Only ZSKs
-set_keyrole "KEY1" "zsk"
-set_keylifetime "KEY1" "16070400"
-set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY1" "no"
-set_zonesigning "KEY1" "yes"
-set_keystate "KEY1" "GOAL" "omnipresent"
-set_keystate "KEY1" "STATE_DNSKEY" "omnipresent"
-set_keystate "KEY1" "STATE_ZRRSIG" "rumoured"
-
-set_keyrole "KEY2" "zsk"
-set_keylifetime "KEY2" "16070400"
-set_keyalgorithm "KEY2" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY2" "no"
-set_zonesigning "KEY2" "no"
-set_keystate "KEY2" "GOAL" "hidden"
-set_keystate "KEY2" "STATE_DNSKEY" "hidden"
-set_keystate "KEY2" "STATE_ZRRSIG" "hidden"
-
-set_keyrole "KEY3" "zsk"
-set_keylifetime "KEY3" "16070400"
-set_keyalgorithm "KEY3" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY3" "no"
-set_zonesigning "KEY3" "no"
-set_keystate "KEY3" "GOAL" "hidden"
-set_keystate "KEY3" "STATE_DNSKEY" "hidden"
-set_keystate "KEY3" "STATE_ZRRSIG" "hidden"
-
-set_keyrole "KEY4" "zsk"
-set_keylifetime "KEY4" "16070400"
-set_keyalgorithm "KEY4" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY4" "no"
-set_zonesigning "KEY4" "no"
-set_keystate "KEY4" "GOAL" "hidden"
-set_keystate "KEY4" "STATE_DNSKEY" "hidden"
-set_keystate "KEY4" "STATE_ZRRSIG" "hidden"
-
-MAXDEPTH=1
-check_keys
-check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
-check_subdomain
-dnssec_verify
-
-# For checking the apex, we need to store the expected KSK metadata.
-key_clear "KEY2"
-key_clear "KEY3"
-key_clear "KEY4"
-
-set_policy "common" "1" "3600"
-set_server "ns1/offline" "10.53.0.1"
-set_keyrole "KEY2" "ksk"
-set_keylifetime "KEY2" "0"
-set_keyalgorithm "KEY2" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY2" "yes"
-set_zonesigning "KEY2" "no"
-check_keys "keep"
-
-DIR="ns1"
-set_keystate "KEY2" "STATE_DNSKEY" "omnipresent"
-set_keystate "KEY2" "STATE_KRRSIG" "omnipresent"
-set_keystate "KEY2" "STATE_DS" "omnipresent"
-check_apex
-
-# Check that key id's match expected keys
-n=$((n + 1))
-zsk1=$(cat common.test.$DEFAULT_ALGORITHM_NUMBER.zsk1.id)
-key1=$(key_get "KEY1" BASEFILE)
-echo_i "check that published zsk $zsk1 matches first key $key1 in bundle ($n)"
-ret=0
-[ "ns1/$zsk1" = "$key1" ] || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-ksk=$(cat common.test.ksk1.id)
-key2=$(key_get "KEY2" BASEFILE)
-echo_i "check that published ksk $ksk matches ksk $key2 ($n)"
-ret=0
-[ "ns1/offline/$ksk" = "$key2" ] || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Key generation: last-bundle
-n=$((n + 1))
-echo_i "generate keys for testing an SKR that is in the last bundle ($n)"
-ret=0
-ksr common -K ns1 -i -1y -e +1d keygen last-bundle.test >ksr.keygen.out.$n 2>&1 || ret=1
-num=$(cat ksr.keygen.out.$n | wc -l)
-[ $num -eq 2 ] || ret=1
-set_zsk $DEFAULT_ALGORITHM_NUMBER $DEFAULT_BITS 16070400
-ksr_check_keys last-bundle.test ns1 -31536000 || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-# Create request: last-bundle
-n=$((n + 1))
-echo_i "create ksr for last bundle test ($n)"
-ret=0
-ksr common -K ns1 -i -1y -e +1d request last-bundle.test >ksr.request.out.$n 2>&1 || ret=1
-cp ksr.request.out.$n last-bundle.test.ksr
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-# Sign request: last-bundle
-n=$((n + 1))
-echo_i "create skr for last bundle test ($n)"
-ret=0
-ksr common -i -1y -e +1d -K ns1/offline -f last-bundle.test.ksr sign last-bundle.test >ksr.sign.out.$n 2>&1 || ret=1
-cp ksr.sign.out.$n ns1/last-bundle.test.skr
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-# Add zone: last-bundle
-n=$((n + 1))
-echo_i "add zone 'last-bundle.test' ($n)"
-ret=0
-$RNDCCMD 10.53.0.1 addzone 'last-bundle.test { type primary; file "last-bundle.test.db"; dnssec-policy common; };' 2>&1 | sed 's/^/I:ns1 /' || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-# Import skr: last-bundle
-n=$((n + 1))
-echo_i "import ksr to zone 'last-bundle.test' ($n)"
-ret=0
-sleep 2
-$RNDCCMD 10.53.0.1 skr -import last-bundle.test.skr last-bundle.test 2>&1 | sed 's/^/I:ns1 /' || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Test that last-bundle.test is signed and uses the right DNSKEY and RRSIG records.
-n=$((n + 1))
-echo_i "test zone 'last-bundle.test' is correctly signed ($n)"
-ret=0
-
-set_zone "last-bundle.test"
-set_policy "common" "2" "3600"
-set_server "ns1" "10.53.0.1"
-# Only ZSKs
-key_clear "KEY1"
-set_keyrole "KEY1" "zsk"
-set_keylifetime "KEY1" "16070400"
-set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY1" "no"
-set_zonesigning "KEY1" "yes"
-set_keystate "KEY1" "GOAL" "omnipresent"
-set_keystate "KEY1" "STATE_DNSKEY" "omnipresent"
-set_keystate "KEY1" "STATE_ZRRSIG" "omnipresent"
-
-key_clear "KEY2"
-set_keyrole "KEY2" "zsk"
-set_keylifetime "KEY2" "16070400"
-set_keyalgorithm "KEY2" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY2" "no"
-set_zonesigning "KEY2" "no"
-set_keystate "KEY2" "GOAL" "hidden"
-set_keystate "KEY2" "STATE_DNSKEY" "hidden"
-set_keystate "KEY2" "STATE_ZRRSIG" "hidden"
-
-MAXDEPTH=1
-check_keys
-check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
-check_subdomain
-dnssec_verify
-
-# For checking the apex, we need to store the expected KSK metadata.
-key_clear "KEY2"
-key_clear "KEY3"
-key_clear "KEY4"
-
-set_policy "common" "1" "3600"
-set_server "ns1/offline" "10.53.0.1"
-set_keyrole "KEY2" "ksk"
-set_keylifetime "KEY2" "0"
-set_keyalgorithm "KEY2" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY2" "yes"
-set_zonesigning "KEY2" "no"
-check_keys "keep"
-
-DIR="ns1"
-set_keystate "KEY2" "STATE_DNSKEY" "omnipresent"
-set_keystate "KEY2" "STATE_KRRSIG" "omnipresent"
-set_keystate "KEY2" "STATE_DS" "omnipresent"
-check_apex
-
-# Check that key id's match expected keys
-n=$((n + 1))
-zsk2=$(cat last-bundle.test.$DEFAULT_ALGORITHM_NUMBER.zsk2.id)
-key1=$(key_get "KEY1" BASEFILE)
-echo_i "check that published zsk $zsk2 matches first key $key1 in bundle ($n)"
-ret=0
-[ "ns1/$zsk2" = "$key1" ] || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-ksk=$(cat last-bundle.test.ksk1.id)
-key2=$(key_get "KEY2" BASEFILE)
-echo_i "check that published ksk $ksk matches ksk $key2 ($n)"
-ret=0
-[ "ns1/offline/$ksk" = "$key2" ] || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "check that last bundle warning is logged ($n)"
-wait_for_log 3 "zone last-bundle.test/IN (signed): zone_rekey: last bundle in skr, please import new skr file" ns1/named.run || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Key generation: in-the-middle
-n=$((n + 1))
-echo_i "generate keys for testing an SKR that is in the middle ($n)"
-ret=0
-ksr common -K ns1 -i -1y -e +1y keygen in-the-middle.test >ksr.keygen.out.$n 2>&1 || ret=1
-num=$(cat ksr.keygen.out.$n | wc -l)
-[ $num -eq 4 ] || ret=1
-set_zsk $DEFAULT_ALGORITHM_NUMBER $DEFAULT_BITS 16070400
-ksr_check_keys in-the-middle.test ns1 -31536000 || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-# Create request: in-the-middle
-n=$((n + 1))
-echo_i "create ksr for in the middle test ($n)"
-ret=0
-ksr common -K ns1 -i -1y -e +1y request in-the-middle.test >ksr.request.out.$n 2>&1 || ret=1
-cp ksr.request.out.$n in-the-middle.test.ksr
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-# Sign request: in-the-middle
-n=$((n + 1))
-echo_i "create skr for in the middle test ($n)"
-ret=0
-ksr common -i -1y -e +1y -K ns1/offline -f in-the-middle.test.ksr sign in-the-middle.test >ksr.sign.out.$n 2>&1 || ret=1
-cp ksr.sign.out.$n ns1/in-the-middle.test.skr
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-# Add zone: in-the-middle
-n=$((n + 1))
-echo_i "add zone 'in-the-middle.test' ($n)"
-ret=0
-$RNDCCMD 10.53.0.1 addzone 'in-the-middle.test { type primary; file "in-the-middle.test.db"; dnssec-policy common; };' 2>&1 | sed 's/^/I:ns1 /' || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-# Import skr: in-the-middle
-n=$((n + 1))
-echo_i "import ksr to zone 'in-the-middle.test' ($n)"
-ret=0
-sleep 2
-$RNDCCMD 10.53.0.1 skr -import in-the-middle.test.skr in-the-middle.test 2>&1 | sed 's/^/I:ns1 /' || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Test that in-the-middle.test is signed and uses the right DNSKEY and RRSIG records.
-n=$((n + 1))
-echo_i "test zone 'in-the-middle.test' is correctly signed ($n)"
-ret=0
-
-set_zone "in-the-middle.test"
-set_policy "common" "4" "3600"
-set_server "ns1" "10.53.0.1"
-# Only ZSKs
-key_clear "KEY1"
-set_keyrole "KEY1" "zsk"
-set_keylifetime "KEY1" "16070400"
-set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY1" "no"
-set_zonesigning "KEY1" "yes"
-set_keystate "KEY1" "GOAL" "omnipresent"
-set_keystate "KEY1" "STATE_DNSKEY" "omnipresent"
-set_keystate "KEY1" "STATE_ZRRSIG" "omnipresent"
-
-key_clear "KEY2"
-set_keyrole "KEY2" "zsk"
-set_keylifetime "KEY2" "16070400"
-set_keyalgorithm "KEY2" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY2" "no"
-set_zonesigning "KEY2" "no"
-set_keystate "KEY2" "GOAL" "hidden"
-set_keystate "KEY2" "STATE_DNSKEY" "hidden"
-set_keystate "KEY2" "STATE_ZRRSIG" "hidden"
-
-key_clear "KEY3"
-set_keyrole "KEY3" "zsk"
-set_keylifetime "KEY3" "16070400"
-set_keyalgorithm "KEY3" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY3" "no"
-set_zonesigning "KEY3" "no"
-set_keystate "KEY3" "GOAL" "hidden"
-set_keystate "KEY3" "STATE_DNSKEY" "hidden"
-set_keystate "KEY3" "STATE_ZRRSIG" "hidden"
-
-key_clear "KEY4"
-set_keyrole "KEY4" "zsk"
-set_keylifetime "KEY4" "16070400"
-set_keyalgorithm "KEY4" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY4" "no"
-set_zonesigning "KEY4" "no"
-set_keystate "KEY4" "GOAL" "hidden"
-set_keystate "KEY4" "STATE_DNSKEY" "hidden"
-set_keystate "KEY4" "STATE_ZRRSIG" "hidden"
-
-MAXDEPTH=1
-check_keys
-check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
-check_subdomain
-dnssec_verify
-
-# For checking the apex, we need to store the expected KSK metadata.
-key_clear "KEY2"
-key_clear "KEY3"
-key_clear "KEY4"
-
-set_policy "common" "1" "3600"
-set_server "ns1/offline" "10.53.0.1"
-set_keyrole "KEY2" "ksk"
-set_keylifetime "KEY2" "0"
-set_keyalgorithm "KEY2" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY2" "yes"
-set_zonesigning "KEY2" "no"
-check_keys "keep"
-
-DIR="ns1"
-set_keystate "KEY2" "STATE_DNSKEY" "omnipresent"
-set_keystate "KEY2" "STATE_KRRSIG" "omnipresent"
-set_keystate "KEY2" "STATE_DS" "omnipresent"
-check_apex
-
-# Check that key id's match expected keys
-n=$((n + 1))
-zsk2=$(cat in-the-middle.test.$DEFAULT_ALGORITHM_NUMBER.zsk2.id)
-key1=$(key_get "KEY1" BASEFILE)
-echo_i "check that published zsk $zsk2 matches first key $key1 in bundle ($n)"
-ret=0
-[ "ns1/$zsk2" = "$key1" ] || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-ksk=$(cat in-the-middle.test.ksk1.id)
-key2=$(key_get "KEY2" BASEFILE)
-echo_i "check that published ksk $ksk matches ksk $key2 ($n)"
-ret=0
-[ "ns1/offline/$ksk" = "$key2" ] || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "check that no last bundle warning is logged ($n)"
-grep "zone $zone/IN (signed): zone_rekey failure: no available SKR bundle" ns1/named.run && ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Test error conditions
-check_rekey_logs_error() {
-  zone=$1
-  inc=$2
-  exp=$3
-  offset=$4
-
-  # Key generation
-  ksr common -K ns1 -i $inc -e $exp keygen $zone >ksr.keygen.out.$n 2>&1 || return 1
-  num=$(cat ksr.keygen.out.$n | wc -l)
-  [ $num -eq 2 ] || return 1
-  set_zsk $DEFAULT_ALGORITHM_NUMBER $DEFAULT_BITS 16070400
-  ksr_check_keys $zone ns1 $offset || return 1
-  # Create request
-  ksr common -K ns1 -i $inc -e $exp request $zone >ksr.request.out.$n 2>&1 || return 1
-  cp ksr.request.out.$n $zone.ksr
-  # Sign request
-  ksr common -K ns1/offline -i $inc -e $exp -f $zone.ksr sign $zone >ksr.sign.out.$n 2>&1 || return 1
-  cp ksr.sign.out.$n ns1/$zone.skr
-  # Import skr
-  $RNDCCMD 10.53.0.1 skr -import $zone.skr $zone 2>&1 | sed 's/^/I:ns1 /' || return 1
-  # Test that rekey logs error
-  wait_for_log 3 "zone $zone/IN (signed): zone_rekey failure: no available SKR bundle" ns1/named.run || return 1
-}
-
-n=$((n + 1))
-echo_i "check that an SKR that is too old logs error ($n)"
-$RNDCCMD 10.53.0.1 addzone 'past.test { type primary; file "past.test.db"; dnssec-policy common; };' 2>&1 | sed 's/^/I:ns1 /' || ret=1
-check_rekey_logs_error "past.test" -2y -1y -63072000 || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "check that an SKR that is too new logs error ($n)"
-$RNDCCMD 10.53.0.1 addzone 'future.test { type primary; file "future.test.db"; dnssec-policy common; };' 2>&1 | sed 's/^/I:ns1 /' || ret=1
-check_rekey_logs_error "future.test" +1mo +1y 2592000 || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Key generation: csk
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr keygen' creates no keys for policy with csk ($n)"
-ret=0
-ksr csk -e +2y keygen csk.test >ksr.keygen.out.$n 2>&1 && ret=1
-grep "dnssec-ksr: fatal: policy 'csk' has no zsks" ksr.keygen.out.$n >/dev/null || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Key generation: unlimited
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr keygen' creates only one key for zsk with unlimited lifetime ($n)"
-ret=0
-ksr unlimited -K ns1 -e +2y keygen unlimited.test >ksr.keygen.out.$n 2>&1 || ret=1
-num=$(cat ksr.keygen.out.$n | wc -l)
-[ $num -eq 1 ] || ret=1
-key=$(cat ksr.keygen.out.$n)
-grep "; Created:" "ns1/${key}.key" >created.out || ret=1
-created=$(awk '{print $3}' <created.out)
-active=$created
-published=$(addtime $active -7500)
-echo_i "check metadata on $key"
-grep "Algorithm: $DEFAULT_ALGORITHM_NUMBER" ns1/${key}.state >/dev/null || ret=1
-grep "Length: $DEFAULT_BITS" ns1/${key}.state >/dev/null || ret=1
-grep "Lifetime: 0" ns1/${key}.state >/dev/null || ret=1
-grep "KSK: no" ns1/${key}.state >/dev/null || ret=1
-grep "ZSK: yes" ns1/${key}.state >/dev/null || ret=1
-grep "Published: $published" ns1/${key}.state >/dev/null || ret=1
-grep "Active: $active" ns1/${key}.state >/dev/null || ret=1
-grep "Retired:" ns1/${key}.state >/dev/null && ret=1
-grep "Removed:" ns1/${key}.state >/dev/null && ret=1
-cat ns1/${key}.key | grep -v ";.*" >unlimited.test.$DEFAULT_ALGORITHM_NUMBER.zsk1
-echo $key >"unlimited.test.${DEFAULT_ALGORITHM_NUMBER}.zsk1.id"
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Create request: unlimited
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr request' creates correct KSR with unlimited zsk ($n)"
-ret=0
-ksr unlimited -K ns1 -i $created -e +4y request unlimited.test >ksr.request.out.$n 2>&1 || ret=1
-# Only one bundle: KSK + ZSK
-inception=$(cat ns1/$key.state | grep "Generated" | cut -d' ' -f 2-)
-echo ";; KeySigningRequest 1.0 $inception" >ksr.request.expect.$n
-cat unlimited.test.$DEFAULT_ALGORITHM_NUMBER.zsk1 >>ksr.request.expect.$n
-# Footer
-grep ";; KeySigningRequest 1.0 generated at" ksr.request.out.$n >footer.$n || ret=1
-cat footer.$n >>ksr.request.expect.$n
-diff -w ksr.request.out.$n ksr.request.expect.$n >/dev/null || ret=1
-# Save request for ksr sign operation.
-cp ksr.request.expect.$n ksr.request.expect
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Sign request: unlimited
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr sign' creates correct SKR with unlimited zsk ($n)"
-ret=0
-ksr unlimited -K ns1 -i $created -e +4y -K ns1/offline -f ksr.request.expect sign unlimited.test >ksr.sign.out.$n 2>&1 || ret=1
-start=$(cat ns1/$key.state | grep "Generated" | awk '{print $2}')
-end=$(addtime $start 126144000) # four years
-check_skr "unlimited.test" "ns1/offline" "ksr.sign.out.$n" $start $end 1 || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Sign request: unlimited (no-cdnskey)
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr sign' creates correct SKR with unlimited zsk, no cdnskey ($n)"
-ret=0
-ksr no-cdnskey -K ns1 -i $created -e +4y -K ns1/offline -f ksr.request.expect sign unlimited.test >ksr.sign.out.$n 2>&1 || ret=1
-start=$(cat ns1/$key.state | grep "Generated" | awk '{print $2}')
-end=$(addtime $start 126144000) # four years
-CDNSKEY="no"
-CDS_SHA1="yes"
-CDS_SHA256="yes"
-CDS_SHA384="yes"
-check_skr "unlimited.test" "ns1/offline" "ksr.sign.out.$n" $start $end 1 || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Sign request: unlimited (no-cds)
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr sign' creates correct SKR with unlimited zsk, no cds ($n)"
-ret=0
-ksr no-cds -K ns1 -i $created -e +4y -K ns1/offline -f ksr.request.expect sign unlimited.test >ksr.sign.out.$n 2>&1 || ret=1
-start=$(cat ns1/$key.state | grep "Generated" | awk '{print $2}')
-end=$(addtime $start 126144000) # four years
-CDNSKEY="yes"
-CDS_SHA1="no"
-CDS_SHA256="no"
-CDS_SHA384="no"
-check_skr "unlimited.test" "ns1/offline" "ksr.sign.out.$n" $start $end 1 || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Reset CDS and CDNSKEY to default values
-CDNSKEY="yes"
-CDS_SHA1="no"
-CDS_SHA256="yes"
-CDS_SHA384="no"
-
-# Key generation: two-tone
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr keygen' creates keys for different algorithms ($n)"
-ret=0
-ksr two-tone -K ns1 -e +1y keygen two-tone.test >ksr.keygen.out.$n 2>&1 || ret=1
-# First algorithm keys have a lifetime of 3 months, so there should be 4 created keys.
-alg=$(printf "%03d" "$DEFAULT_ALGORITHM_NUMBER")
-num=$(grep "Ktwo-tone.test.+$alg+" ksr.keygen.out.$n | wc -l)
-[ $num -eq 4 ] || ret=1
-set_zsk $DEFAULT_ALGORITHM_NUMBER $DEFAULT_BITS 8035200
-ksr_check_keys two-tone.test ns1 0 || ret=1
-cp ksr.keygen.out.$n ksr.keygen.out.expect.$DEFAULT_ALGORITHM_NUMBER
-# Second algorithm keys have a lifetime of 5 months, so there should be 3 created keys.
-# While only two time bundles of 5 months fit into one year, we need to create an
-# extra key for the remainder of the bundle.
-alg=$(printf "%03d" "$ALTERNATIVE_ALGORITHM_NUMBER")
-num=$(grep "Ktwo-tone.test.+$alg+" ksr.keygen.out.$n | wc -l)
-[ $num -eq 3 ] || ret=1
-set_zsk $ALTERNATIVE_ALGORITHM_NUMBER $ALTERNATIVE_BITS 13392000
-ksr_check_keys two-tone.test ns1 0 || ret=1
-cp ksr.keygen.out.$n ksr.keygen.out.expect.$ALTERNATIVE_ALGORITHM_NUMBER
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Create request: two-tone
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr request' creates correct KSR with multiple algorithms ($n)"
-ret=0
-key=$(cat two-tone.test.$DEFAULT_ALGORITHM_NUMBER.zsk1.id)
-grep "; Created:" "ns1/${key}.key" >created.out || ret=1
-created=$(awk '{print $3}' <created.out)
-ksr two-tone -K ns1 -i $created -e +6mo request two-tone.test >ksr.request.out.$n 2>&1 || ret=1
-# The two-tone policy uses two sets of KSK/ZSK with different algorithms. One
-# set uses the default algorithm (denoted as A below), the other is using the
-# alternative algorithm (denoted as B). The A-ZSKs roll every three months,
-# so in the second bundle there should be a new DNSKEY prepublished, and the
-# predecessor is removed in the third bundle. Then, after five months the
-# ZSK for the B set is rolled, adding the successor in bundle 4 and removing
-# its predecessor in bundle 5.
-#
-# Bundle 1: KSK-A1, KSK-B1, ZSK-A1, ZSK-B1
-key=$(cat two-tone.test.$DEFAULT_ALGORITHM_NUMBER.zsk1.id)
-inception=$(cat ns1/$key.state | grep "Generated" | cut -d' ' -f 2-)
-echo ";; KeySigningRequest 1.0 $inception" >ksr.request.expect.$n
-cat two-tone.test.$DEFAULT_ALGORITHM_NUMBER.zsk1 >>ksr.request.expect.$n
-cat two-tone.test.$ALTERNATIVE_ALGORITHM_NUMBER.zsk1 >>ksr.request.expect.$n
-# Bundle 2: KSK-A1, KSK-B1, ZSK-A1 + ZSK-A2, ZSK-B1
-key=$(cat two-tone.test.$DEFAULT_ALGORITHM_NUMBER.zsk2.id)
-inception=$(cat ns1/$key.state | grep "Published" | cut -d' ' -f 2-)
-echo ";; KeySigningRequest 1.0 $inception" >>ksr.request.expect.$n
-print_dnskeys two-tone.test 1 2 $DEFAULT_ALGORITHM_NUMBER ksr.keygen.out.expect.$DEFAULT_ALGORITHM_NUMBER >>ksr.request.expect.$n
-cat two-tone.test.$ALTERNATIVE_ALGORITHM_NUMBER.zsk1 >>ksr.request.expect.$n
-# Bundle 3: KSK-A1, KSK-B1, ZSK-A2, ZSK-B1
-key=$(cat two-tone.test.$DEFAULT_ALGORITHM_NUMBER.zsk1.id)
-inception=$(cat ns1/$key.state | grep "Removed" | cut -d' ' -f 2-)
-echo ";; KeySigningRequest 1.0 $inception" >>ksr.request.expect.$n
-cat two-tone.test.$DEFAULT_ALGORITHM_NUMBER.zsk2 >>ksr.request.expect.$n
-cat two-tone.test.$ALTERNATIVE_ALGORITHM_NUMBER.zsk1 >>ksr.request.expect.$n
-# Bundle 4: KSK-A1, KSK-B1, ZSK-A2, ZSK-B1 + ZSK-B2
-key=$(cat two-tone.test.$ALTERNATIVE_ALGORITHM_NUMBER.zsk2.id)
-inception=$(cat ns1/$key.state | grep "Published" | cut -d' ' -f 2-)
-echo ";; KeySigningRequest 1.0 $inception" >>ksr.request.expect.$n
-cat two-tone.test.$DEFAULT_ALGORITHM_NUMBER.zsk2 >>ksr.request.expect.$n
-print_dnskeys two-tone.test 1 2 $ALTERNATIVE_ALGORITHM_NUMBER ksr.keygen.out.expect.$ALTERNATIVE_ALGORITHM_NUMBER >>ksr.request.expect.$n
-# Bundle 5: KSK-A1, KSK-B1, ZSK-A2, ZSK-B2
-key=$(cat two-tone.test.$ALTERNATIVE_ALGORITHM_NUMBER.zsk1.id)
-inception=$(cat ns1/$key.state | grep "Removed" | cut -d' ' -f 2-)
-echo ";; KeySigningRequest 1.0 $inception" >>ksr.request.expect.$n
-cat two-tone.test.$DEFAULT_ALGORITHM_NUMBER.zsk2 >>ksr.request.expect.$n
-cat two-tone.test.$ALTERNATIVE_ALGORITHM_NUMBER.zsk2 >>ksr.request.expect.$n
-# Footer
-grep ";; KeySigningRequest 1.0 generated at" ksr.request.out.$n >footer.$n || ret=1
-cat footer.$n >>ksr.request.expect.$n
-# Check the KSR request against the expected request.
-diff -w ksr.request.out.$n ksr.request.expect.$n >/dev/null || ret=1
-# Save request for ksr sign operation.
-cp ksr.request.expect.$n ksr.request.expect
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-num_occurrences() {
-  count="$1"
-  file="$2"
-  line="$3"
-  exclude="$4"
-
-  if [ -z "$exclude" ]; then
-    lines=$(cat "$file" | while read line; do echo $line; done | grep "$line" | wc -l)
-    echo_i "$lines occurrences: $1 $2 $3"
-  else
-    lines=$(cat "$file" | while read line; do echo $line; done | grep -v "$exclude" | grep "$line" | wc -l)
-    echo_i "$lines occurrences: $1 $2 $3 (exclude $4)"
-  fi
-
-  test "$lines" -eq "$count" || return 1
-}
-
-# Sign request: two-tone
-n=$((n + 1))
-echo_i "check that 'dnssec-ksr sign' creates correct SKR with multiple algorithms ($n)"
-ret=0
-ksr two-tone -i $created -e +6mo -K ns1/offline -f ksr.request.expect sign two-tone.test >ksr.sign.out.$n 2>&1 || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-# Weak testing:
-zone="two-tone.test"
-# expect 24 headers (including the footer)
-num_occurrences 24 ksr.sign.out.$n ";; SignedKeyResponse 1.0" || ret=1
-# expect 23 KSKs and its signatures (for each header one)
-num_occurrences 23 ksr.sign.out.$n "DNSKEY 257 3 8" "CDNSKEY" || ret=1 # exclude CDNSKEY lines
-test "$ret" -eq 0 || echo_i "2 failed"
-num_occurrences 23 ksr.sign.out.$n "DNSKEY 257 3 13" "CDNSKEY" || ret=1 # exclude CDNSKEY lines
-test "$ret" -eq 0 || echo_i "3 failed"
-num_occurrences 23 ksr.sign.out.$n "RRSIG DNSKEY 8" "CDNSKEY" || ret=1 # exclude CDNSKEY lines
-test "$ret" -eq 0 || echo_i "4 failed"
-num_occurrences 23 ksr.sign.out.$n "RRSIG DNSKEY 13" "CDNSKEY" || ret=1 # exclude CDNSKEY lines
-test "$ret" -eq 0 || echo_i "5 failed"
-# ... 23 CDNSKEY records and its signatures
-num_occurrences 23 ksr.sign.out.$n "CDNSKEY 257 3 8" || ret=1
-test "$ret" -eq 0 || echo_i "6 failed"
-num_occurrences 23 ksr.sign.out.$n "CDNSKEY 257 3 13" || ret=1
-test "$ret" -eq 0 || echo_i "7 failed"
-num_occurrences 23 ksr.sign.out.$n "RRSIG CDNSKEY 8" || ret=1
-test "$ret" -eq 0 || echo_i "8 failed"
-num_occurrences 23 ksr.sign.out.$n "RRSIG CDNSKEY 13" || ret=1
-test "$ret" -eq 0 || echo_i "9 failed"
-# ... 23 CDS records and its signatures
-num_occurrences 23 ksr.sign.out.$n "CDS 8 2" || ret=1
-test "$ret" -eq 0 || echo_i "10 failed"
-num_occurrences 23 ksr.sign.out.$n "CDS 13 2" || ret=1
-test "$ret" -eq 0 || echo_i "11 failed"
-num_occurrences 23 ksr.sign.out.$n "RRSIG CDS 8" || ret=1
-test "$ret" -eq 0 || echo_i "12 failed"
-num_occurrences 23 ksr.sign.out.$n "RRSIG CDS 13" || ret=1
-test "$ret" -eq 0 || echo_i "13 failed"
-# expect 25 ZSK (two more for double keys during the rollover)
-num_occurrences 25 ksr.sign.out.$n "DNSKEY 256 3 8" || ret=1
-test "$ret" -eq 0 || echo_i "14 failed"
-num_occurrences 25 ksr.sign.out.$n "DNSKEY 256 3 13" || ret=1
-test "$ret" -eq 0 || echo_i "15 failed"
-status=$((status + ret))
-
-echo_i "exit status: $status"
-[ $status -eq 0 ] || exit 1
diff --git a/bin/tests/system/ksr/tests_ksr.py b/bin/tests/system/ksr/tests_ksr.py
new file mode 100644 (file)
index 0000000..efe688d
--- /dev/null
@@ -0,0 +1,1100 @@
+# 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.
+
+# pylint: disable=too-many-lines
+
+import os
+import shutil
+import time
+
+from datetime import datetime
+
+import isctest
+
+from isctest.kasp import (
+    addtime,
+    cds_equals,
+    dnskey_equals,
+    get_keytag,
+    get_metadata,
+    get_timing_metadata,
+)
+
+
+def between(value, start, end):
+    if int(value) == 0:
+        return False
+
+    return int(value) > int(start) and int(value) < int(end)
+
+
+def file_contents_equal(file1, file2):
+    diff_command = [
+        "diff",
+        "-w",
+        file1,
+        file2,
+    ]
+    isctest.run.cmd(diff_command)
+
+
+def keygen(zone, policy, keydir, when="now"):
+    keygen_command = [
+        *os.environ.get("KEYGEN").split(),
+        "-l",
+        "ns1/named.conf",
+        "-fK",
+        "-K",
+        keydir,
+        "-k",
+        policy,
+        "-P",
+        when,
+        "-A",
+        when,
+        "-P",
+        "sync",
+        when,
+        zone,
+    ]
+    output = isctest.run.cmd(keygen_command, log_stdout=True).stdout.decode("utf-8")
+    keys = output.split()
+    return keys
+
+
+def ksr(zone, policy, action, options="", raise_on_exception=True):
+    ksr_command = [
+        *os.environ.get("KSR").split(),
+        "-l",
+        "ns1/named.conf",
+        "-k",
+        policy,
+        *options.split(),
+        action,
+        zone,
+    ]
+
+    out = isctest.run.cmd(
+        ksr_command, log_stdout=True, raise_on_exception=raise_on_exception
+    )
+    return out.stdout.decode("utf-8"), out.stderr.decode("utf-8")
+
+
+# pylint: disable=too-many-arguments,too-many-branches,too-many-locals,too-many-statements
+def check_keys(keys, lifetime, alg, size, keydir=None, offset=0, with_state=False):
+    # Check keys that were created.
+    inception = 0
+    num = 0
+
+    now = datetime.now().strftime("%Y%m%d%H%M%S")
+
+    for key in keys:
+        if keydir is not None:
+            statefile = f"{keydir}/{key}.state"
+        else:
+            statefile = f"{key}.state"
+
+        # created: from keyfile plus offset
+        created = get_timing_metadata(key, "Created", keydir=keydir, offset=offset)
+
+        # active: retired previous key
+        if num == 0:
+            active = created
+        else:
+            active = retired
+
+        # published: 2h5m (dnskey-ttl + publish-safety + propagation)
+        published = addtime(active, -7500)
+
+        # retired: zsk-lifetime
+        if lifetime > 0:
+            retired = addtime(active, lifetime)
+            # removed: 10d1h5m
+            # (ttlsig + retire-safety + sign-delay + propagation)
+            removed = addtime(retired, 867900)
+        else:
+            retired = 0
+            removed = 0
+
+        if between(now, published, retired) or int(retired) == 0:
+            goal = "omnipresent"
+            pubdelay = addtime(published, 7500)
+            signdelay = addtime(active, 867900)
+
+            if between(now, published, pubdelay):
+                state_dnskey = "rumoured"
+            else:
+                state_dnskey = "omnipresent"
+
+            if between(now, active, signdelay):
+                state_zrrsig = "rumoured"
+            else:
+                state_zrrsig = "omnipresent"
+        else:
+            goal = "hidden"
+            state_dnskey = "hidden"
+            state_zrrsig = "hidden"
+
+        with open(statefile, "r", encoding="utf-8") as file:
+            metadata = file.read()
+            assert f"Algorithm: {alg}" in metadata
+            assert f"Length: {size}" in metadata
+            assert f"Lifetime: {lifetime}" in metadata
+            assert "KSK: no" in metadata
+            assert "ZSK: yes" in metadata
+            assert f"Published: {published}" in metadata
+            assert f"Active: {active}" in metadata
+
+            if lifetime > 0:
+                assert f"Retired: {retired}" in metadata
+                assert f"Removed: {removed}" in metadata
+            else:
+                assert "Retired:" not in metadata
+                assert "Removed:" not in metadata
+
+            if with_state:
+                assert f"GoalState: {goal}" in metadata
+                assert f"DNSKEYState: {state_dnskey}" in metadata
+                assert f"ZRRSIGState: {state_zrrsig}" in metadata
+                assert "KRRSIGState:" not in metadata
+                assert "DSState:" not in metadata
+
+        inception += lifetime
+        num += 1
+
+
+def check_keysigningrequest(out, zsks, start, end, keydir=None):
+    lines = out.split("\n")
+    line_no = 0
+
+    inception = start
+    while int(inception) < int(end):
+        next_bundle = addtime(end, 1)
+        # expect bundle header
+        assert f";; KeySigningRequest 1.0 {inception}" in lines[line_no]
+        line_no += 1
+        # expect zsks
+        for key in sorted(zsks):
+            published = get_timing_metadata(key, "Publish", keydir=keydir)
+            if between(published, inception, next_bundle):
+                next_bundle = published
+
+            removed = get_timing_metadata(
+                key, "Delete", keydir=keydir, must_exist=False
+            )
+            if between(removed, inception, next_bundle):
+                next_bundle = removed
+
+            if int(published) > int(inception):
+                continue
+            if int(removed) != 0 and int(inception) >= int(removed):
+                continue
+
+            # this zsk must be in the ksr
+            assert dnskey_equals(key, lines[line_no], keydir=keydir)
+            line_no += 1
+
+        inception = next_bundle
+
+    # ksr footer
+    assert ";; KeySigningRequest 1.0 generated at" in lines[line_no]
+    line_no += 1
+
+    # trailing empty lines
+    while line_no < len(lines):
+        assert lines[line_no] == ""
+        line_no += 1
+
+    assert line_no == len(lines)
+
+
+# pylint: disable=too-many-arguments,too-many-branches,too-many-locals,too-many-statements
+def check_signedkeyresponse(
+    out,
+    zone,
+    ksks,
+    zsks,
+    start,
+    end,
+    refresh,
+    kskdir=None,
+    zskdir=None,
+    cdnskey=True,
+    cds="SHA-256",
+):
+    lines = out.split("\n")
+    line_no = 0
+    next_bundle = addtime(end, 1)
+
+    inception = start
+    while int(inception) < int(end):
+        # A single signed key response may consist of:
+        # ;; SignedKeyResponse (header)
+        # ;; DNSKEY 257 (one per published key in ksks)
+        # ;; DNSKEY 256 (one per published key in zsks)
+        # ;; RRSIG(DNSKEY) (one per active key in ksks)
+        # ;; CDNSKEY (one per published key in ksks)
+        # ;; RRSIG(CDNSKEY) (one per active key in ksks)
+        # ;; CDS (one per published key in ksks)
+        # ;; RRSIG(CDS) (one per active key in ksks)
+
+        sigstart = addtime(inception, -3600)  # clockskew: 1 hour
+        sigend = addtime(inception, 1209600)  # sig-validity: 14 days
+        next_bundle = addtime(sigend, refresh)
+
+        # ignore empty lines
+        while line_no < len(lines):
+            if lines[line_no] == "":
+                line_no += 1
+            else:
+                break
+
+        # expect bundle header
+        assert f";; SignedKeyResponse 1.0 {inception}" in lines[line_no]
+        line_no += 1
+
+        # expect ksks
+        for key in sorted(ksks):
+            published = get_timing_metadata(key, "Publish", keydir=kskdir)
+            removed = get_timing_metadata(
+                key, "Delete", keydir=kskdir, must_exist=False
+            )
+
+            if int(published) > int(inception):
+                continue
+            if int(removed) != 0 and int(inception) >= int(removed):
+                continue
+
+            # this ksk must be in the ksr
+            assert dnskey_equals(key, lines[line_no], keydir=kskdir)
+            line_no += 1
+
+        # expect zsks
+        for key in sorted(zsks):
+            published = get_timing_metadata(key, "Publish", keydir=zskdir)
+            if between(published, inception, next_bundle):
+                next_bundle = published
+
+            removed = get_timing_metadata(
+                key, "Delete", keydir=zskdir, must_exist=False
+            )
+            if between(removed, inception, next_bundle):
+                next_bundle = removed
+
+            if int(published) > int(inception):
+                continue
+            if int(removed) != 0 and int(inception) >= int(removed):
+                continue
+
+            # this zsk must be in the ksr
+            assert dnskey_equals(key, lines[line_no], keydir=zskdir)
+            line_no += 1
+
+        # expect rrsig(dnskey)
+        for key in sorted(ksks):
+            active = get_timing_metadata(key, "Activate", keydir=kskdir)
+            inactive = get_timing_metadata(
+                key, "Inactive", keydir=kskdir, must_exist=False
+            )
+            if int(active) > int(inception):
+                continue
+            if int(inactive) != 0 and int(inception) >= int(inactive):
+                continue
+
+            # there must be a signature of this ksk
+            keytag = get_keytag(key)
+            alg = get_metadata(key, "Algorithm", keydir=kskdir)
+            expect = f"{zone}. 3600 IN RRSIG DNSKEY {alg} 2 3600 {sigend} {sigstart} {keytag} {zone}."
+            rrsig = " ".join(lines[line_no].split())
+            assert expect in rrsig
+            line_no += 1
+
+        # expect cdnskey
+        if cdnskey:
+            for key in sorted(ksks):
+                published = get_timing_metadata(key, "Publish", keydir=kskdir)
+                removed = get_timing_metadata(
+                    key, "Delete", keydir=kskdir, must_exist=False
+                )
+                if int(published) > int(inception):
+                    continue
+                if int(removed) != 0 and int(inception) >= int(removed):
+                    continue
+
+                # the cdnskey of this ksk must be in the ksr
+                assert dnskey_equals(key, lines[line_no], keydir=kskdir, cdnskey=True)
+                line_no += 1
+
+            # expect rrsig(cdnskey)
+            for key in sorted(ksks):
+                active = get_timing_metadata(key, "Activate", keydir=kskdir)
+                inactive = get_timing_metadata(
+                    key, "Inactive", keydir=kskdir, must_exist=False
+                )
+                if int(active) > int(inception):
+                    continue
+                if int(inactive) != 0 and int(inception) >= int(inactive):
+                    continue
+
+                # there must be a signature of this ksk
+                keytag = get_keytag(key)
+                alg = get_metadata(key, "Algorithm", keydir=kskdir)
+                expect = f"{zone}. 3600 IN RRSIG CDNSKEY {alg} 2 3600 {sigend} {sigstart} {keytag} {zone}."
+                rrsig = " ".join(lines[line_no].split())
+                assert expect in rrsig
+                line_no += 1
+
+        # expect cds
+        if cds != "":
+            for key in sorted(ksks):
+                published = get_timing_metadata(key, "Publish", keydir=kskdir)
+                removed = get_timing_metadata(
+                    key, "Delete", keydir=kskdir, must_exist=False
+                )
+                if int(published) > int(inception):
+                    continue
+                if int(removed) != 0 and int(inception) >= int(removed):
+                    continue
+
+                # the cds of this ksk must be in the ksr
+                expected_cds = cds.split(",")
+                for alg in expected_cds:
+                    assert cds_equals(key, lines[line_no], alg.strip(), keydir=kskdir)
+                    line_no += 1
+
+            # expect rrsig(cds)
+            for key in sorted(ksks):
+                active = get_timing_metadata(key, "Activate", keydir=kskdir)
+                inactive = get_timing_metadata(
+                    key, "Inactive", keydir=kskdir, must_exist=False
+                )
+                if int(active) > int(inception):
+                    continue
+                if int(inactive) != 0 and int(inception) >= int(inactive):
+                    continue
+
+                # there must be a signature of this ksk
+                keytag = get_keytag(key)
+                alg = get_metadata(key, "Algorithm", keydir=kskdir)
+                expect = f"{zone}. 3600 IN RRSIG CDS {alg} 2 3600 {sigend} {sigstart} {keytag} {zone}."
+                rrsig = " ".join(lines[line_no].split())
+                assert expect in rrsig
+                line_no += 1
+
+        inception = next_bundle
+
+    # skr footer
+    assert ";; SignedKeyResponse 1.0 generated at" in lines[line_no]
+    line_no += 1
+
+    # trailing empty lines
+    while line_no < len(lines):
+        assert lines[line_no] == ""
+        line_no += 1
+
+    assert line_no == len(lines)
+
+
+def test_ksr_errors():
+    # check that 'dnssec-ksr' errors on unknown action
+    _, err = ksr("common.test", "common", "foobar", raise_on_exception=False)
+    assert "dnssec-ksr: fatal: unknown command 'foobar'" in err
+
+    # check that 'dnssec-ksr keygen' errors on missing end date
+    _, err = ksr("common.test", "common", "keygen", raise_on_exception=False)
+    assert "dnssec-ksr: fatal: keygen requires an end date" in err
+
+    # check that 'dnssec-ksr keygen' errors on zone with csk
+    _, err = ksr(
+        "csk.test", "csk", "keygen", options="-K ns1 -e +2y", raise_on_exception=False
+    )
+    assert "dnssec-ksr: fatal: policy 'csk' has no zsks" in err
+
+    # check that 'dnssec-ksr request' errors on missing end date
+    _, err = ksr("common.test", "common", "request", raise_on_exception=False)
+    assert "dnssec-ksr: fatal: request requires an end date" in err
+
+    # check that 'dnssec-ksr sign' errors on missing ksr file
+    _, err = ksr(
+        "common.test",
+        "common",
+        "sign",
+        options="-K ns1/offline -i now -e +1y",
+        raise_on_exception=False,
+    )
+    assert "dnssec-ksr: fatal: 'sign' requires a KSR file" in err
+
+
+# pylint: disable=too-many-locals,too-many-statements
+def test_ksr_common(servers):
+    # common test cases (1)
+    zone = "common.test"
+    policy = "common"
+    n = 1
+
+    # create ksk
+    ksks = keygen(zone, policy, "ns1/offline")
+    assert len(ksks) == 1
+
+    # check that 'dnssec-ksr keygen' pregenerates right amount of keys
+    out, _ = ksr(zone, policy, "keygen", options="-i now -e +1y")
+    zsks = out.split()
+    assert len(zsks) == 2
+
+    alg = os.environ.get("DEFAULT_ALGORITHM_NUMBER")
+    size = os.environ.get("DEFAULT_BITS")
+    lifetime = 16070400
+    check_keys(zsks, lifetime, alg, size)
+
+    # check that 'dnssec-ksr keygen' pregenerates right amount of keys
+    # in the given key directory
+    out, _ = ksr(zone, policy, "keygen", options="-K ns1 -i now -e +1y")
+    zsks = out.split()
+    assert len(zsks) == 2
+
+    alg = os.environ.get("DEFAULT_ALGORITHM_NUMBER")
+    size = os.environ.get("DEFAULT_BITS")
+    lifetime = 16070400
+    check_keys(zsks, lifetime, alg, size, keydir="ns1")
+
+    for key in zsks:
+        privatefile = f"ns1/{key}.private"
+        keyfile = f"ns1/{key}.key"
+        statefile = f"ns1/{key}.state"
+        shutil.copyfile(privatefile, f"{privatefile}.backup")
+        shutil.copyfile(keyfile, f"{keyfile}.backup")
+        shutil.copyfile(statefile, f"{statefile}.backup")
+
+    # check that 'dnssec-ksr request' creates correct ksr
+    now = get_timing_metadata(zsks[0], "Created", keydir="ns1")
+    until = addtime(now, 31536000)  # 1 year
+    out, _ = ksr(zone, policy, "request", options=f"-K ns1 -i {now} -e +1y")
+
+    fname = f"{zone}.ksr.{n}"
+    with open(fname, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    check_keysigningrequest(out, zsks, now, until, keydir="ns1")
+
+    # check that 'dnssec-ksr sign' creates correct skr
+    out, _ = ksr(
+        zone, policy, "sign", options=f"-K ns1/offline -f {fname} -i {now} -e +1y"
+    )
+
+    fname = f"{zone}.skr.{n}"
+    with open(fname, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    refresh = -432000  # 5 days
+    check_signedkeyresponse(
+        out, zone, ksks, zsks, now, until, refresh, kskdir="ns1/offline", zskdir="ns1"
+    )
+
+    # common test cases (2)
+    n = 2
+
+    # check that 'dnssec-ksr keygen' selects pregenerated keys for
+    # the same time bundle
+    out, _ = ksr(zone, policy, "keygen", options=f"-K ns1 -i {now} -e +1y")
+    selected_zsks = out.split()
+    assert len(selected_zsks) == 2
+    for index, key in enumerate(selected_zsks):
+        assert zsks[index] == key
+        file_contents_equal(f"ns1/{key}.private", f"ns1/{key}.private.backup")
+        file_contents_equal(f"ns1/{key}.key", f"ns1/{key}.key.backup")
+        file_contents_equal(f"ns1/{key}.state", f"ns1/{key}.state.backup")
+
+    # check that 'dnssec-ksr keygen' generates only necessary keys for
+    # overlapping time bundle
+    out, err = ksr(zone, policy, "keygen", options=f"-K ns1 -i {now} -e +2y -v 1")
+    overlapping_zsks = out.split()
+    assert len(overlapping_zsks) == 4
+
+    verbose = err.split()
+    selected = 0
+    generated = 0
+    for output in verbose:
+        if "Selecting" in output:
+            selected += 1
+        if "Generating" in output:
+            generated += 1
+    assert selected == 2
+    assert generated == 2
+    for index, key in enumerate(overlapping_zsks):
+        if index < 2:
+            assert zsks[index] == key
+            file_contents_equal(f"ns1/{key}.private", f"ns1/{key}.private.backup")
+            file_contents_equal(f"ns1/{key}.key", f"ns1/{key}.key.backup")
+            file_contents_equal(f"ns1/{key}.state", f"ns1/{key}.state.backup")
+
+    # run 'dnssec-ksr keygen' again with verbosity 0
+    out, _ = ksr(zone, policy, "keygen", options=f"-K ns1 -i {now} -e +2y")
+    overlapping_zsks2 = out.split()
+    assert len(overlapping_zsks2) == 4
+    check_keys(overlapping_zsks2, lifetime, alg, size, keydir="ns1")
+    for index, key in enumerate(overlapping_zsks2):
+        assert overlapping_zsks[index] == key
+
+    # check that 'dnssec-ksr request' creates correct ksr if the
+    # interval is shorter
+    out, _ = ksr(zone, policy, "request", options=f"-K ns1 -i {now} -e +1y")
+
+    fname = f"{zone}.ksr.{n}.shorter"
+    with open(fname, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    check_keysigningrequest(out, zsks, now, until, keydir="ns1")
+
+    # check that 'dnssec-ksr request' creates correct ksr with new interval
+    out, _ = ksr(zone, policy, "request", options=f"-K ns1 -i {now} -e +2y")
+
+    fname = f"{zone}.ksr.{n}"
+    with open(fname, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    until = addtime(now, 63072000)  # 2 years
+    check_keysigningrequest(out, overlapping_zsks, now, until, keydir="ns1")
+
+    # check that 'dnssec-ksr request' errors if there are not enough keys
+    _, err = ksr(
+        zone,
+        policy,
+        "request",
+        options=f"-K ns1 -i {now} -e +3y",
+        raise_on_exception=False,
+    )
+    error = f"no {zone}/ECDSAP256SHA256 zsk key pair found for bundle"
+    assert f"dnssec-ksr: fatal: {error}" in err
+
+    # check that 'dnssec-ksr sign' creates correct skr
+    out, _ = ksr(
+        zone, policy, "sign", options=f"-K ns1/offline -f {fname} -i {now} -e +2y"
+    )
+
+    fname = f"{zone}.skr.{n}"
+    with open(fname, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    refresh = -432000  # 5 days
+    check_signedkeyresponse(
+        out,
+        zone,
+        ksks,
+        overlapping_zsks,
+        now,
+        until,
+        refresh,
+        kskdir="ns1/offline",
+        zskdir="ns1",
+    )
+
+    # add zone
+    ns1 = servers["ns1"]
+    ns1.rndc(
+        f"addzone {zone} "
+        + "{ type primary; file "
+        + f'"{zone}.db"; dnssec-policy {policy}; '
+        + "};",
+        log=False,
+    )
+
+    # import skr
+    shutil.copyfile(fname, f"ns1/{fname}")
+    ns1.rndc(f"skr -import {fname} {zone}", log=False)
+
+    # test zone is correctly signed
+    # - check rndc dnssec -status output
+    isctest.kasp.check_dnssecstatus(ns1, zone, overlapping_zsks, policy=policy)
+    # - zone is signed
+    isctest.kasp.zone_is_signed(ns1, zone)
+    # - dnssec_verify
+    isctest.kasp.dnssec_verify(ns1, zone)
+    # - check keys
+    check_keys(overlapping_zsks, lifetime, alg, size, keydir="ns1", with_state=True)
+    # - check apex
+    isctest.kasp.check_apex(
+        ns1, zone, ksks, overlapping_zsks, kskdir="ns1/offline", zskdir="ns1"
+    )
+    # - check subdomain
+    isctest.kasp.check_subdomain(
+        ns1, zone, ksks, overlapping_zsks, kskdir="ns1/offline", zskdir="ns1"
+    )
+
+
+# pylint: disable=too-many-locals
+def test_ksr_lastbundle(servers):
+    zone = "last-bundle.test"
+    policy = "common"
+    n = 1
+
+    # create ksk
+    now = datetime.now().strftime("%Y%m%d%H%M%S")
+    offset = -31536000
+    when = addtime(now, offset)
+    when = addtime(when, -86400)
+    ksks = keygen(zone, policy, "ns1/offline", when=when)
+    assert len(ksks) == 1
+
+    # check that 'dnssec-ksr keygen' pregenerates right amount of keys
+    out, _ = ksr(zone, policy, "keygen", options="-K ns1 -i -1y -e +1d")
+    zsks = out.split()
+    assert len(zsks) == 2
+
+    alg = os.environ.get("DEFAULT_ALGORITHM_NUMBER")
+    size = os.environ.get("DEFAULT_BITS")
+    lifetime = 16070400
+    check_keys(zsks, lifetime, alg, size, keydir="ns1", offset=offset)
+
+    # check that 'dnssec-ksr request' creates correct ksr
+    then = get_timing_metadata(zsks[0], "Created", keydir="ns1")
+    then = addtime(then, offset)
+    until = addtime(then, 31622400)  # 1 year, 1 day
+    out, _ = ksr(zone, policy, "request", options=f"-K ns1 -i {then} -e +1d")
+
+    fname = f"{zone}.ksr.{n}"
+    with open(fname, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    check_keysigningrequest(out, zsks, then, until, keydir="ns1")
+
+    # check that 'dnssec-ksr sign' creates correct skr
+    out, _ = ksr(
+        zone, policy, "sign", options=f"-K ns1/offline -f {fname} -i {then} -e +1d"
+    )
+
+    fname = f"{zone}.skr.{n}"
+    with open(fname, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    refresh = -432000  # 5 days
+    check_signedkeyresponse(
+        out, zone, ksks, zsks, then, until, refresh, kskdir="ns1/offline", zskdir="ns1"
+    )
+
+    # add zone
+    ns1 = servers["ns1"]
+    ns1.rndc(
+        f"addzone {zone} "
+        + "{ type primary; file "
+        + f'"{zone}.db"; dnssec-policy {policy}; '
+        + "};",
+        log=False,
+    )
+
+    # import skr
+    shutil.copyfile(fname, f"ns1/{fname}")
+    ns1.rndc(f"skr -import {fname} {zone}", log=False)
+
+    # test zone is correctly signed
+    # - check rndc dnssec -status output
+    isctest.kasp.check_dnssecstatus(ns1, zone, zsks, policy=policy)
+    # - zone is signed
+    isctest.kasp.zone_is_signed(ns1, zone)
+    # - dnssec_verify
+    isctest.kasp.dnssec_verify(ns1, zone)
+    # - check keys
+    check_keys(zsks, lifetime, alg, size, keydir="ns1", offset=offset, with_state=True)
+    # - check apex
+    isctest.kasp.check_apex(ns1, zone, ksks, zsks, kskdir="ns1/offline", zskdir="ns1")
+    # - check subdomain
+    isctest.kasp.check_subdomain(
+        ns1, zone, ksks, zsks, kskdir="ns1/offline", zskdir="ns1"
+    )
+
+    # check that last bundle warning is logged
+    warning = "last bundle in skr, please import new skr file"
+    assert f"zone {zone}/IN (signed): zone_rekey: {warning}" in ns1.log
+
+
+# pylint: disable=too-many-locals
+def test_ksr_inthemiddle(servers):
+    zone = "in-the-middle.test"
+    policy = "common"
+    n = 1
+
+    # create ksk
+    now = datetime.now().strftime("%Y%m%d%H%M%S")
+    offset = -31536000
+    when = addtime(now, offset)
+    when = addtime(when, -86400)
+    ksks = keygen(zone, policy, "ns1/offline", when=when)
+    assert len(ksks) == 1
+
+    # check that 'dnssec-ksr keygen' pregenerates right amount of keys
+    out, _ = ksr(zone, policy, "keygen", options="-K ns1 -i -1y -e +1y")
+    zsks = out.split()
+    assert len(zsks) == 4
+
+    alg = os.environ.get("DEFAULT_ALGORITHM_NUMBER")
+    size = os.environ.get("DEFAULT_BITS")
+    lifetime = 16070400
+    check_keys(zsks, lifetime, alg, size, keydir="ns1", offset=offset)
+
+    # check that 'dnssec-ksr request' creates correct ksr
+    then = get_timing_metadata(zsks[0], "Created", keydir="ns1")
+    then = addtime(then, offset)
+    until = addtime(then, 63072000)  # 2 years
+    out, _ = ksr(zone, policy, "request", options=f"-K ns1 -i {then} -e +1y")
+
+    fname = f"{zone}.ksr.{n}"
+    with open(fname, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    check_keysigningrequest(out, zsks, then, until, keydir="ns1")
+
+    # check that 'dnssec-ksr sign' creates correct skr
+    out, _ = ksr(
+        zone, policy, "sign", options=f"-K ns1/offline -f {fname} -i {then} -e +1y"
+    )
+
+    fname = f"{zone}.skr.{n}"
+    with open(fname, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    refresh = -432000  # 5 days
+    check_signedkeyresponse(
+        out, zone, ksks, zsks, then, until, refresh, kskdir="ns1/offline", zskdir="ns1"
+    )
+
+    # add zone
+    ns1 = servers["ns1"]
+    ns1.rndc(
+        f"addzone {zone} "
+        + "{ type primary; file "
+        + f'"{zone}.db"; dnssec-policy {policy}; '
+        + "};",
+        log=False,
+    )
+
+    # import skr
+    shutil.copyfile(fname, f"ns1/{fname}")
+    ns1.rndc(f"skr -import {fname} {zone}", log=False)
+
+    # test zone is correctly signed
+    # - check rndc dnssec -status output
+    isctest.kasp.check_dnssecstatus(ns1, zone, zsks, policy=policy)
+    # - zone is signed
+    isctest.kasp.zone_is_signed(ns1, zone)
+    # - dnssec_verify
+    isctest.kasp.dnssec_verify(ns1, zone)
+    # - check keys
+    check_keys(zsks, lifetime, alg, size, keydir="ns1", offset=offset, with_state=True)
+    # - check apex
+    isctest.kasp.check_apex(ns1, zone, ksks, zsks, kskdir="ns1/offline", zskdir="ns1")
+    # - check subdomain
+    isctest.kasp.check_subdomain(
+        ns1, zone, ksks, zsks, kskdir="ns1/offline", zskdir="ns1"
+    )
+
+    # check that no last bundle warning is logged
+    warning = "last bundle in skr, please import new skr file"
+    assert f"zone {zone}/IN (signed): zone_rekey: {warning}" not in ns1.log
+
+
+# pylint: disable=too-many-locals
+def check_ksr_rekey_logs_error(server, zone, policy, offset, end):
+    n = 1
+
+    # create ksk
+    now = datetime.now().strftime("%Y%m%d%H%M%S")
+    then = addtime(now, offset)
+    until = addtime(now, end)
+    ksks = keygen(zone, policy, "ns1/offline", when=then)
+    assert len(ksks) == 1
+
+    # key generation
+    out, _ = ksr(zone, policy, "keygen", options=f"-K ns1 -i {then} -e {until}")
+    zsks = out.split()
+    assert len(zsks) == 2
+
+    # create request
+    now = get_timing_metadata(zsks[0], "Created", keydir="ns1")
+    then = addtime(now, offset)
+    until = addtime(now, end)
+    out, _ = ksr(zone, policy, "request", options=f"-K ns1 -i {then} -e {until}")
+
+    fname = f"{zone}.ksr.{n}"
+    with open(fname, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    # sign request
+    out, _ = ksr(
+        zone, policy, "sign", options=f"-K ns1/offline -f {fname} -i {then} -e {until}"
+    )
+
+    fname = f"{zone}.skr.{n}"
+    with open(fname, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    # add zone
+    server.rndc(
+        f"addzone {zone} "
+        + "{ type primary; file "
+        + f'"{zone}.db"; dnssec-policy {policy}; '
+        + "};",
+        log=False,
+    )
+
+    # import skr
+    shutil.copyfile(fname, f"ns1/{fname}")
+    server.rndc(f"skr -import {fname} {zone}", log=False)
+
+    # test that rekey logs error
+    time_remaining = 10
+    warning = "no available SKR bundle"
+    line = f"zone {zone}/IN (signed): zone_rekey failure: {warning}"
+    while time_remaining > 0:
+        if line not in server.log:
+            time_remaining -= 1
+            time.sleep(1)
+        else:
+            break
+    assert line in server.log
+
+
+def test_ksr_rekey_logs_error(servers):
+    # check that an SKR that is too old logs error
+    check_ksr_rekey_logs_error(
+        servers["ns1"], "past.test", "common", -63072000, -31536000
+    )
+    # check that an SKR that is too new logs error
+    check_ksr_rekey_logs_error(
+        servers["ns1"], "future.test", "common", 2592000, 31536000
+    )
+
+
+# pylint: disable=too-many-locals
+def test_ksr_unlimited(servers):
+    zone = "unlimited.test"
+    policy = "unlimited"
+    n = 1
+
+    # create ksk
+    ksks = keygen(zone, policy, "ns1/offline")
+    assert len(ksks) == 1
+
+    # check that 'dnssec-ksr keygen' pregenerates right amount of keys
+    out, _ = ksr(zone, policy, "keygen", options="-K ns1 -i now -e +2y")
+    zsks = out.split()
+    assert len(zsks) == 1
+
+    alg = os.environ.get("DEFAULT_ALGORITHM_NUMBER")
+    size = os.environ.get("DEFAULT_BITS")
+    lifetime = 0
+    check_keys(zsks, lifetime, alg, size, keydir="ns1")
+
+    # check that 'dnssec-ksr request' creates correct ksr
+    now = get_timing_metadata(zsks[0], "Created", keydir="ns1")
+    until = addtime(now, 4 * 31536000)  # 4 years
+    out, _ = ksr(zone, policy, "request", options=f"-K ns1 -i {now} -e +4y")
+
+    fname = f"{zone}.ksr.{n}"
+    with open(fname, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    check_keysigningrequest(out, zsks, now, until, keydir="ns1")
+
+    # check that 'dnssec-ksr sign' creates correct skr without cdnskey
+    out, _ = ksr(
+        zone, "no-cdnskey", "sign", options=f"-K ns1/offline -f {fname} -i {now} -e +4y"
+    )
+
+    skrfile = f"{zone}.no-cdnskey.skr.{n}"
+    with open(skrfile, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    refresh = -432000  # 5 days
+    check_signedkeyresponse(
+        out,
+        zone,
+        ksks,
+        zsks,
+        now,
+        until,
+        refresh,
+        kskdir="ns1/offline",
+        zskdir="ns1",
+        cdnskey=False,
+        cds="SHA-1, SHA-256, SHA-384",
+    )
+
+    # check that 'dnssec-ksr sign' creates correct skr without cds
+    out, _ = ksr(
+        zone, "no-cds", "sign", options=f"-K ns1/offline -f {fname} -i {now} -e +4y"
+    )
+
+    skrfile = f"{zone}.no-cds.skr.{n}"
+    with open(skrfile, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    refresh = -432000  # 5 days
+    check_signedkeyresponse(
+        out,
+        zone,
+        ksks,
+        zsks,
+        now,
+        until,
+        refresh,
+        kskdir="ns1/offline",
+        zskdir="ns1",
+        cds="",
+    )
+
+    # check that 'dnssec-ksr sign' creates correct skr
+    out, _ = ksr(
+        zone, policy, "sign", options=f"-K ns1/offline -f {fname} -i {now} -e +4y"
+    )
+
+    skrfile = f"{zone}.{policy}.skr.{n}"
+    with open(skrfile, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    refresh = -432000  # 5 days
+    check_signedkeyresponse(
+        out, zone, ksks, zsks, now, until, refresh, kskdir="ns1/offline", zskdir="ns1"
+    )
+
+    # add zone
+    ns1 = servers["ns1"]
+    ns1.rndc(
+        f"addzone {zone} "
+        + "{ type primary; file "
+        + f'"{zone}.db"; dnssec-policy {policy}; '
+        + "};",
+        log=False,
+    )
+
+    # import skr
+    shutil.copyfile(skrfile, f"ns1/{skrfile}")
+    ns1.rndc(f"skr -import {skrfile} {zone}", log=False)
+
+    # test zone is correctly signed
+    # - check rndc dnssec -status output
+    isctest.kasp.check_dnssecstatus(ns1, zone, zsks, policy=policy)
+    # - zone is signed
+    isctest.kasp.zone_is_signed(ns1, zone)
+    # - dnssec_verify
+    isctest.kasp.dnssec_verify(ns1, zone)
+    # - check keys
+    check_keys(zsks, lifetime, alg, size, keydir="ns1", with_state=True)
+    # - check apex
+    isctest.kasp.check_apex(ns1, zone, ksks, zsks, kskdir="ns1/offline", zskdir="ns1")
+    # - check subdomain
+    isctest.kasp.check_subdomain(
+        ns1, zone, ksks, zsks, kskdir="ns1/offline", zskdir="ns1"
+    )
+
+
+# pylint: disable=too-many-locals
+def test_ksr_twotone(servers):
+    zone = "two-tone.test"
+    policy = "two-tone"
+    n = 1
+
+    # create ksk
+    ksks = keygen(zone, policy, "ns1/offline")
+    assert len(ksks) == 2
+
+    # check that 'dnssec-ksr keygen' pregenerates right amount of keys
+    out, _ = ksr(zone, policy, "keygen", options="-K ns1 -i now -e +1y")
+    zsks = out.split()
+    # First algorithm keys have a lifetime of 3 months, so there should
+    # be 4 created keys. Second algorithm keys have a lifetime of 5
+    # months, so there should be 3 created keys.  While only two time
+    # bundles of 5 months fit into one year, we need to create an extra
+    # key for the remainder of the bundle. So 7 in total.
+    assert len(zsks) == 7
+
+    zsks_defalg = []
+    zsks_altalg = []
+    for zsk in zsks:
+        alg = get_metadata(zsk, "Algorithm", keydir="ns1")
+        if alg == os.environ.get("DEFAULT_ALGORITHM_NUMBER"):
+            zsks_defalg.append(zsk)
+        elif alg == os.environ.get("ALTERNATIVE_ALGORITHM_NUMBER"):
+            zsks_altalg.append(zsk)
+
+    assert len(zsks_defalg) == 4
+    assert len(zsks_altalg) == 3
+
+    alg = os.environ.get("DEFAULT_ALGORITHM_NUMBER")
+    size = os.environ.get("DEFAULT_BITS")
+    lifetime = 8035200  # 3 months
+    check_keys(zsks_defalg, lifetime, alg, size, keydir="ns1")
+
+    alg = os.environ.get("ALTERNATIVE_ALGORITHM_NUMBER")
+    size = os.environ.get("ALTERNATIVE_BITS")
+    lifetime = 13392000  # 5 months
+    check_keys(zsks_altalg, lifetime, alg, size, keydir="ns1")
+
+    # check that 'dnssec-ksr request' creates correct ksr
+    now = get_timing_metadata(zsks[0], "Created", keydir="ns1")
+    until = addtime(now, 31536000)  # 1 year
+    out, _ = ksr(zone, policy, "request", options=f"-K ns1 -i {now} -e +1y")
+
+    fname = f"{zone}.ksr.{n}"
+    with open(fname, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    check_keysigningrequest(out, zsks, now, until, keydir="ns1")
+
+    # check that 'dnssec-ksr sign' creates correct skr
+    out, _ = ksr(
+        zone, policy, "sign", options=f"-K ns1/offline -f {fname} -i {now} -e +1y"
+    )
+
+    skrfile = f"{zone}.skr.{n}"
+    with open(skrfile, "w", encoding="utf-8") as file:
+        file.write(out)
+
+    refresh = -432000  # 5 days
+    check_signedkeyresponse(
+        out, zone, ksks, zsks, now, until, refresh, kskdir="ns1/offline", zskdir="ns1"
+    )
+
+    # add zone
+    ns1 = servers["ns1"]
+    ns1.rndc(
+        f"addzone {zone} "
+        + "{ type primary; file "
+        + f'"{zone}.db"; dnssec-policy {policy}; '
+        + "};",
+        log=False,
+    )
+
+    # import skr
+    shutil.copyfile(skrfile, f"ns1/{skrfile}")
+    ns1.rndc(f"skr -import {skrfile} {zone}", log=False)
+
+    # test zone is correctly signed
+    # - check rndc dnssec -status output
+    isctest.kasp.check_dnssecstatus(ns1, zone, zsks, policy=policy)
+    # - zone is signed
+    isctest.kasp.zone_is_signed(ns1, zone)
+    # - dnssec_verify
+    isctest.kasp.dnssec_verify(ns1, zone)
+    # - check keys
+    alg = os.environ.get("DEFAULT_ALGORITHM_NUMBER")
+    size = os.environ.get("DEFAULT_BITS")
+    lifetime = 8035200  # 3 months
+    check_keys(zsks_defalg, lifetime, alg, size, keydir="ns1", with_state=True)
+
+    alg = os.environ.get("ALTERNATIVE_ALGORITHM_NUMBER")
+    size = os.environ.get("ALTERNATIVE_BITS")
+    lifetime = 13392000  # 5 months
+    check_keys(zsks_altalg, lifetime, alg, size, keydir="ns1", with_state=True)
+    # - check apex
+    isctest.kasp.check_apex(ns1, zone, ksks, zsks, kskdir="ns1/offline", zskdir="ns1")
+    # - check subdomain
+    isctest.kasp.check_subdomain(
+        ns1, zone, ksks, zsks, kskdir="ns1/offline", zskdir="ns1"
+    )
diff --git a/bin/tests/system/ksr/tests_sh_ksr.py b/bin/tests/system/ksr/tests_sh_ksr.py
deleted file mode 100644 (file)
index 56f07ce..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-# 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.
-
-
-def test_ksr(run_tests_sh):
-    run_tests_sh()