Add kasp tests for offline keys

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.
This commit is contained in:
Matthijs Mekking
2021-04-12 15:26:34 +02:00
parent 6a60bf637d
commit 4a8ad0a77f
4 changed files with 143 additions and 20 deletions

View 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"

View 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.
*/

View 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.

View File

@@ -325,6 +325,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
#
@@ -1339,6 +1360,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.
#
@@ -1346,7 +1386,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.