]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add kasp tests for offline keys
authorMatthijs Mekking <matthijs@isc.org>
Mon, 12 Apr 2021 13:26:34 +0000 (15:26 +0200)
committerMatthijs Mekking <matthijs@isc.org>
Wed, 5 May 2021 10:50:07 +0000 (12:50 +0200)
Add a test for default.kasp that if we remove the private key file,
no successor key is created for it. We need to update the kasp script
to deal with a missing private key. If this is the case, skip checks
for private key files.

Add a test with a zone for which the private key of the ZSK is missing.

Add a test with a zone for which the private key of the KSK is missing.

(cherry picked from commit 4a8ad0a77f7f8baa0fda4d30760b6fbd2e617845)

bin/tests/system/kasp.sh
bin/tests/system/kasp/ns3/named.conf.in
bin/tests/system/kasp/ns3/setup.sh
bin/tests/system/kasp/tests.sh

index 94e395fb4d904932e1b0ef997dbc745c87108a07..d21839a08bda57c70adbaf95621d76ec22fa68be 100644 (file)
@@ -59,6 +59,7 @@ VIEW2="4xILSZQnuO1UKubXHkYUsvBRPu8="
 # EXPECT_ZRRSIG
 # EXPECT_KRRSIG
 # LEGACY
+# PRIVATE
 
 key_key() {
        echo "${1}__${2}"
@@ -112,6 +113,7 @@ key_clear() {
        key_set "$1" "EXPECT_ZRRSIG" 'no'
        key_set "$1" "EXPECT_KRRSIG" 'no'
        key_set "$1" "LEGACY" 'no'
+       key_set "$1" "PRIVATE" 'yes'
 }
 
 # Start clear.
@@ -303,6 +305,7 @@ check_key() {
        _dnskey_ttl="$DNSKEY_TTL"
        _lifetime=$(key_get "$1" LIFETIME)
        _legacy=$(key_get "$1" LEGACY)
+       _private=$(key_get "$1" PRIVATE)
 
        _published=$(key_get "$1" PUBLISHED)
        _active=$(key_get "$1" ACTIVE)
@@ -341,7 +344,9 @@ check_key() {
 
        # Check file existence.
        [ -s "$KEY_FILE" ] || ret=1
-       [ -s "$PRIVATE_FILE" ] || ret=1
+       if [ "$_private" = "yes" ]; then
+               [ -s "$PRIVATE_FILE" ] || ret=1
+       fi
        if [ "$_legacy" = "no" ]; then
                [ -s "$STATE_FILE" ] || ret=1
        fi
@@ -352,7 +357,9 @@ check_key() {
        grep "; Created:" "$KEY_FILE" > "${ZONE}.${KEY_ID}.${_alg_num}.created" || _log_error "mismatch created comment in $KEY_FILE"
        KEY_CREATED=$(awk '{print $3}' < "${ZONE}.${KEY_ID}.${_alg_num}.created")
 
-       grep "Created: ${KEY_CREATED}" "$PRIVATE_FILE" > /dev/null || _log_error "mismatch created in $PRIVATE_FILE"
+       if [ "$_private" = "yes" ]; then
+               grep "Created: ${KEY_CREATED}" "$PRIVATE_FILE" > /dev/null || _log_error "mismatch created in $PRIVATE_FILE"
+       fi
        if [ "$_legacy" = "no" ]; then
                grep "Generated: ${KEY_CREATED}" "$STATE_FILE" > /dev/null || _log_error "mismatch generated in $STATE_FILE"
        fi
@@ -363,8 +370,10 @@ check_key() {
        grep "This is a ${_role2} key, keyid ${_key_id}, for ${_zone}." "$KEY_FILE" > /dev/null || _log_error "mismatch top comment in $KEY_FILE"
        grep "${_zone}\. ${_dnskey_ttl} IN DNSKEY ${_flags} 3 ${_alg_num}" "$KEY_FILE" > /dev/null || _log_error "mismatch DNSKEY record in $KEY_FILE"
        # Now check the private key file.
-       grep "Private-key-format: v1.3" "$PRIVATE_FILE" > /dev/null || _log_error "mismatch private key format in $PRIVATE_FILE"
-       grep "Algorithm: ${_alg_num} (${_alg_string})" "$PRIVATE_FILE" > /dev/null || _log_error "mismatch algorithm in $PRIVATE_FILE"
+       if [ "$_private" = "yes" ]; then
+               grep "Private-key-format: v1.3" "$PRIVATE_FILE" > /dev/null || _log_error "mismatch private key format in $PRIVATE_FILE"
+               grep "Algorithm: ${_alg_num} (${_alg_string})" "$PRIVATE_FILE" > /dev/null || _log_error "mismatch algorithm in $PRIVATE_FILE"
+       fi
        # Now check the key state file.
        if [ "$_legacy" = "no" ]; then
                grep "This is the state of key ${_key_id}, for ${_zone}." "$STATE_FILE" > /dev/null || _log_error "mismatch top comment in $STATE_FILE"
@@ -444,6 +453,8 @@ check_timingmetadata() {
        _key_file="${_base_file}.key"
        _private_file="${_base_file}.private"
        _state_file="${_base_file}.state"
+       _legacy=$(key_get "$1" LEGACY)
+       _private=$(key_get "$1" PRIVATE)
 
        _published=$(key_get "$1" PUBLISHED)
        _syncpublish=$(key_get "$1" SYNCPUBLISH)
@@ -459,13 +470,17 @@ check_timingmetadata() {
 
        if [ "$_published" = "none" ]; then
                grep "; Publish:" "${_key_file}" > /dev/null && _log_error "unexpected publish comment in ${_key_file}"
-               grep "Publish:" "${_private_file}" > /dev/null && _log_error "unexpected publish in ${_private_file}"
+               if [ "$_private" = "yes" ]; then
+                       grep "Publish:" "${_private_file}" > /dev/null && _log_error "unexpected publish in ${_private_file}"
+               fi
                if [ "$_legacy" = "no" ]; then
                        grep "Published: " "${_state_file}" > /dev/null && _log_error "unexpected publish in ${_state_file}"
                fi
        else
                grep "; Publish: $_published" "${_key_file}" > /dev/null || _log_error "mismatch publish comment in ${_key_file} (expected ${_published})"
-               grep "Publish: $_published" "${_private_file}" > /dev/null || _log_error "mismatch publish in ${_private_file} (expected ${_published})"
+               if [ "$_private" = "yes" ]; then
+                       grep "Publish: $_published" "${_private_file}" > /dev/null || _log_error "mismatch publish in ${_private_file} (expected ${_published})"
+               fi
                if [ "$_legacy" = "no" ]; then
                        grep "Published: $_published" "${_state_file}" > /dev/null || _log_error "mismatch publish in ${_state_file} (expected ${_published})"
                fi
@@ -473,13 +488,17 @@ check_timingmetadata() {
 
        if [ "$_syncpublish" = "none" ]; then
                grep "; SyncPublish:" "${_key_file}" > /dev/null && _log_error "unexpected syncpublish comment in ${_key_file}"
-               grep "SyncPublish:" "${_private_file}" > /dev/null && _log_error "unexpected syncpublish in ${_private_file}"
+               if [ "$_private" = "yes" ]; then
+                       grep "SyncPublish:" "${_private_file}" > /dev/null && _log_error "unexpected syncpublish in ${_private_file}"
+               fi
                if [ "$_legacy" = "no" ]; then
                        grep "PublishCDS: " "${_state_file}" > /dev/null && _log_error "unexpected syncpublish in ${_state_file}"
                fi
        else
                grep "; SyncPublish: $_syncpublish" "${_key_file}" > /dev/null || _log_error "mismatch syncpublish comment in ${_key_file} (expected ${_syncpublish})"
-               grep "SyncPublish: $_syncpublish" "${_private_file}" > /dev/null || _log_error "mismatch syncpublish in ${_private_file} (expected ${_syncpublish})"
+               if [ "$_private" = "yes" ]; then
+                       grep "SyncPublish: $_syncpublish" "${_private_file}" > /dev/null || _log_error "mismatch syncpublish in ${_private_file} (expected ${_syncpublish})"
+               fi
                if [ "$_legacy" = "no" ]; then
                        grep "PublishCDS: $_syncpublish" "${_state_file}" > /dev/null || _log_error "mismatch syncpublish in ${_state_file} (expected ${_syncpublish})"
                fi
@@ -487,13 +506,17 @@ check_timingmetadata() {
 
        if [ "$_active" = "none" ]; then
                grep "; Activate:" "${_key_file}" > /dev/null && _log_error "unexpected active comment in ${_key_file}"
-               grep "Activate:" "${_private_file}" > /dev/null && _log_error "unexpected active in ${_private_file}"
+               if [ "$_private" = "yes" ]; then
+                       grep "Activate:" "${_private_file}" > /dev/null && _log_error "unexpected active in ${_private_file}"
+               fi
                if [ "$_legacy" = "no" ]; then
                        grep "Active: " "${_state_file}" > /dev/null && _log_error "unexpected active in ${_state_file}"
                fi
        else
                grep "; Activate: $_active" "${_key_file}" > /dev/null || _log_error "mismatch active comment in ${_key_file} (expected ${_active})"
-               grep "Activate: $_active" "${_private_file}" > /dev/null || _log_error "mismatch active in ${_private_file} (expected ${_active})"
+               if [ "$_private" = "yes" ]; then
+                       grep "Activate: $_active" "${_private_file}" > /dev/null || _log_error "mismatch active in ${_private_file} (expected ${_active})"
+               fi
                if [ "$_legacy" = "no" ]; then
                        grep "Active: $_active" "${_state_file}" > /dev/null || _log_error "mismatch active in ${_state_file} (expected ${_active})"
                fi
@@ -501,13 +524,17 @@ check_timingmetadata() {
 
        if [ "$_retired" = "none" ]; then
                grep "; Inactive:" "${_key_file}" > /dev/null && _log_error "unexpected retired comment in ${_key_file}"
-               grep "Inactive:" "${_private_file}" > /dev/null && _log_error "unexpected retired in ${_private_file}"
+               if [ "$_private" = "yes" ]; then
+                       grep "Inactive:" "${_private_file}" > /dev/null && _log_error "unexpected retired in ${_private_file}"
+               fi
                if [ "$_legacy" = "no" ]; then
                        grep "Retired: " "${_state_file}" > /dev/null && _log_error "unexpected retired in ${_state_file}"
                fi
        else
                grep "; Inactive: $_retired" "${_key_file}" > /dev/null || _log_error "mismatch retired comment in ${_key_file} (expected ${_retired})"
-               grep "Inactive: $_retired" "${_private_file}" > /dev/null || _log_error "mismatch retired in ${_private_file} (expected ${_retired})"
+               if [ "$_private" = "yes" ]; then
+                       grep "Inactive: $_retired" "${_private_file}" > /dev/null || _log_error "mismatch retired in ${_private_file} (expected ${_retired})"
+               fi
                if [ "$_legacy" = "no" ]; then
                        grep "Retired: $_retired" "${_state_file}" > /dev/null || _log_error "mismatch retired in ${_state_file} (expected ${_retired})"
                fi
@@ -515,13 +542,17 @@ check_timingmetadata() {
 
        if [ "$_revoked" = "none" ]; then
                grep "; Revoke:" "${_key_file}" > /dev/null && _log_error "unexpected revoked comment in ${_key_file}"
-               grep "Revoke:" "${_private_file}" > /dev/null && _log_error "unexpected revoked in ${_private_file}"
+               if [ "$_private" = "yes" ]; then
+                       grep "Revoke:" "${_private_file}" > /dev/null && _log_error "unexpected revoked in ${_private_file}"
+               fi
                if [ "$_legacy" = "no" ]; then
                        grep "Revoked: " "${_state_file}" > /dev/null && _log_error "unexpected revoked in ${_state_file}"
                fi
        else
                grep "; Revoke: $_revoked" "${_key_file}" > /dev/null || _log_error "mismatch revoked comment in ${_key_file} (expected ${_revoked})"
-               grep "Revoke: $_revoked" "${_private_file}" > /dev/null || _log_error "mismatch revoked in ${_private_file} (expected ${_revoked})"
+               if [ "$_private" = "yes" ]; then
+                       grep "Revoke: $_revoked" "${_private_file}" > /dev/null || _log_error "mismatch revoked in ${_private_file} (expected ${_revoked})"
+               fi
                if [ "$_legacy" = "no" ]; then
                        grep "Revoked: $_revoked" "${_state_file}" > /dev/null || _log_error "mismatch revoked in ${_state_file} (expected ${_revoked})"
                fi
@@ -529,13 +560,17 @@ check_timingmetadata() {
 
        if [ "$_removed" = "none" ]; then
                grep "; Delete:" "${_key_file}" > /dev/null && _log_error "unexpected removed comment in ${_key_file}"
-               grep "Delete:" "${_private_file}" > /dev/null && _log_error "unexpected removed in ${_private_file}"
+               if [ "$_private" = "yes" ]; then
+                       grep "Delete:" "${_private_file}" > /dev/null && _log_error "unexpected removed in ${_private_file}"
+               fi
                if [ "$_legacy" = "no" ]; then
                        grep "Removed: " "${_state_file}" > /dev/null && _log_error "unexpected removed in ${_state_file}"
                fi
        else
                grep "; Delete: $_removed" "${_key_file}" > /dev/null || _log_error "mismatch removed comment in ${_key_file} (expected ${_removed})"
-               grep "Delete: $_removed" "${_private_file}" > /dev/null || _log_error "mismatch removed in ${_private_file} (expected ${_removed})"
+               if [ "$_private" = "yes" ]; then
+                       grep "Delete: $_removed" "${_private_file}" > /dev/null || _log_error "mismatch removed in ${_private_file} (expected ${_removed})"
+               fi
                if [ "$_legacy" = "no" ]; then
                        grep "Removed: $_removed" "${_state_file}" > /dev/null || _log_error "mismatch removed in ${_state_file} (expected ${_removed})"
                fi
@@ -672,7 +707,7 @@ _check_keys() {
        # Check key files.
        _ids=$(get_keyids "$DIR" "$ZONE")
        for _id in $_ids; do
-               # There are three key files with the same algorithm.
+               # There are multiple key files with the same algorithm.
                # Check them until a match is found.
                ret=0
                echo_i "check key id $_id"
index 6e6f7bfa06e6de0d8619b005b67cfc5878c79764..a2731099e9c0f3c5cebaadda7b77b3f81442a33b 100644 (file)
@@ -252,6 +252,15 @@ zone "unfresh-sigs.autosign" {
        dnssec-policy "autosign";
 };
 
+/*
+ * Zone that has missing private KSK.
+ */
+zone "ksk-missing.autosign" {
+       type primary;
+       file "ksk-missing.autosign.db";
+       dnssec-policy "autosign";
+};
+
 /*
  * Zone that has missing private ZSK.
  */
index c6666758f9ce84f767ce5241225da499907267e4..fd5adc4bd2df43674b825b63937c8835c999b10e 100644 (file)
@@ -194,7 +194,25 @@ private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >> "$infile"
 private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >> "$infile"
 $SIGNER -S -x -s now-1w -e now+1w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1
 
-# These signatures are already expired, and the private ZSK is missing.
+# These signatures are still good, but the private KSK is missing.
+setup ksk-missing.autosign
+T="now-6mo"
+ksktimes="-P $T -A $T -P sync $T"
+zsktimes="-P $T -A $T"
+KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2> keygen.out.$zone.1)
+ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300        $zsktimes $zone 2> keygen.out.$zone.2)
+$SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" > settime.out.$zone.1 2>&1
+$SETTIME -s -g $O -k $O $T -z $O $T          "$ZSK" > settime.out.$zone.2 2>&1
+cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile"
+private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >> "$infile"
+private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >> "$infile"
+$SIGNER -S -x -s now-1w -e now+1w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1
+echo "KSK: yes" >> "${KSK}".state
+echo "ZSK: no" >> "${KSK}".state
+echo "Lifetime: 63072000" >> "${KSK}".state # PT2Y
+rm -f "${KSK}".private
+
+# These signatures are still good, but the private ZSK is missing.
 setup zsk-missing.autosign
 T="now-6mo"
 ksktimes="-P $T -A $T -P sync $T"
@@ -206,7 +224,10 @@ $SETTIME -s -g $O -k $O $T -z $O $T          "$ZSK" > settime.out.$zone.2 2>&1
 cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile"
 private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >> "$infile"
 private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >> "$infile"
-$SIGNER -PS -x -s now-2w -e now-1mi -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1
+$SIGNER -S -x -s now-1w -e now+1w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1
+echo "KSK: no" >> "${ZSK}".state
+echo "ZSK: yes" >> "${ZSK}".state
+echo "Lifetime: 31536000" >> "${ZSK}".state # PT1Y
 rm -f "${ZSK}".private
 
 # These signatures are already expired, and the private ZSK is retired.
index a69896efb869212839c5fbffadf03c5935ccc364..d4361ec0814dec15a91a017dc3594f722a0de953 100644 (file)
@@ -326,6 +326,27 @@ retry_quiet 10 update_is_signed "10.0.0.11" "10.0.0.44" || ret=1
 test "$ret" -eq 0 || echo_i "failed"
 status=$((status+ret))
 
+# Move the private key file, a rekey event should not introduce replacement
+# keys.
+ret=0
+echo_i "test that if private key files are inaccessible this doesn't trigger a rollover ($n)"
+basefile=$(key_get KEY1 BASEFILE)
+mv "${basefile}.private" "${basefile}.offline"
+rndccmd 10.53.0.3 loadkeys "$ZONE" > /dev/null || log_error "rndc loadkeys zone ${ZONE} failed"
+wait_for_log 3 "offline, policy default" $DIR/named.run || ret=1
+mv "${basefile}.offline" "${basefile}.private"
+test "$ret" -eq 0 || echo_i "failed"
+status=$((status+ret))
+
+# Nothing has changed.
+check_keys
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
+set_keytimes_csk_policy
+check_keytimes
+check_apex
+check_subdomain
+dnssec_verify
+
 #
 # Zone: dynamic.kasp
 #
@@ -1340,6 +1361,25 @@ check_subdomain
 dnssec_verify
 check_rrsig_refresh
 
+#
+# Zone: ksk-missing.autosign.
+#
+set_zone "ksk-missing.autosign"
+set_policy "autosign" "2" "300"
+set_server "ns3" "10.53.0.3"
+# Key properties, timings and states same as above.
+# Skip checking the private file, because it is missing.
+key_set "KEY1" "PRIVATE" "no"
+
+check_keys
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
+check_apex
+check_subdomain
+dnssec_verify
+
+# Restore the PRIVATE variable.
+key_set "KEY1" "PRIVATE" "yes"
+
 #
 # Zone: zsk-missing.autosign.
 #
@@ -1347,7 +1387,25 @@ set_zone "zsk-missing.autosign"
 set_policy "autosign" "2" "300"
 set_server "ns3" "10.53.0.3"
 # Key properties, timings and states same as above.
-# TODO.
+# Skip checking the private file, because it is missing.
+key_set "KEY2" "PRIVATE" "no"
+
+check_keys
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
+# For the apex, we expect the SOA to be signed with the KSK because the ZSK is
+# offline. Temporary treat KEY1 as a zone signing key too.
+set_keyrole "KEY1" "csk"
+set_zonesigning "KEY1" "yes"
+set_zonesigning "KEY2" "no"
+check_apex
+set_keyrole "KEY1" "ksk"
+set_zonesigning "KEY1" "no"
+set_zonesigning "KEY2" "yes"
+check_subdomain
+dnssec_verify
+
+# Restore the PRIVATE variable.
+key_set "KEY2" "PRIVATE" "yes"
 
 #
 # Zone: zsk-retired.autosign.