xref: /netbsd-src/external/mpl/bind/dist/bin/tests/system/kasp.sh (revision a04395531661c5e8d314125d5ae77d4cbedd5d73)
1#!/bin/sh
2#
3# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
4#
5# This Source Code Form is subject to the terms of the Mozilla Public
6# License, v. 2.0. If a copy of the MPL was not distributed with this
7# file, you can obtain one at https://mozilla.org/MPL/2.0/.
8#
9# See the COPYRIGHT file distributed with this work for additional
10# information regarding copyright ownership.
11
12#
13# Common configuration data for kasp system tests, to be sourced into
14# other shell scripts.
15#
16
17# shellcheck source=conf.sh
18. ../conf.sh
19
20###############################################################################
21# Constants                                                                   #
22###############################################################################
23DEFAULT_TTL=300
24
25###############################################################################
26# Query properties                                                            #
27###############################################################################
28TSIG=""
29SHA1="FrSt77yPTFx6hTs4i2tKLB9LmE0="
30SHA224="hXfwwwiag2QGqblopofai9NuW28q/1rH4CaTnA=="
31SHA256="R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY="
32VIEW1="YPfMoAk6h+3iN8MDRQC004iSNHY="
33VIEW2="4xILSZQnuO1UKubXHkYUsvBRPu8="
34VIEW3="C1Azf+gGPMmxrUg/WQINP6eV9Y0="
35
36###############################################################################
37# Key properties                                                              #
38###############################################################################
39# ID
40# BASEFILE
41# EXPECT
42# ROLE
43# KSK
44# ZSK
45# LIFETIME
46# ALG_NUM
47# ALG_STR
48# ALG_LEN
49# CREATED
50# PUBLISHED
51# ACTIVE
52# RETIRED
53# REVOKED
54# REMOVED
55# GOAL
56# STATE_DNSKEY
57# STATE_ZRRSIG
58# STATE_KRRSIG
59# STATE_DS
60# EXPECT_ZRRSIG
61# EXPECT_KRRSIG
62# LEGACY
63# PRIVATE
64
65key_key() {
66	echo "${1}__${2}"
67}
68
69key_get() {
70	eval "echo \${$(key_key "$1" "$2")}"
71}
72
73key_set() {
74	eval "$(key_key "$1" "$2")='$3'"
75}
76
77# Save certain values in the KEY array.
78key_save()
79{
80	# Save key id.
81	key_set "$1" ID "$KEY_ID"
82	# Save base filename.
83	key_set "$1" BASEFILE "$BASE_FILE"
84	# Save creation date.
85	key_set "$1" CREATED "${KEY_CREATED}"
86}
87
88# Clear key state.
89#
90# This will update either the KEY1, KEY2, or KEY3 array.
91key_clear() {
92	key_set "$1" "ID" 'no'
93	key_set "$1" "IDPAD" 'no'
94	key_set "$1" "EXPECT" 'no'
95	key_set "$1" "ROLE" 'none'
96	key_set "$1" "KSK" 'no'
97	key_set "$1" "ZSK" 'no'
98	key_set "$1" "LIFETIME" 'none'
99	key_set "$1" "ALG_NUM" '0'
100	key_set "$1" "ALG_STR" 'none'
101	key_set "$1" "ALG_LEN" '0'
102	key_set "$1" "CREATED" '0'
103	key_set "$1" "PUBLISHED" 'none'
104	key_set "$1" "SYNCPUBLISH" 'none'
105	key_set "$1" "ACTIVE" 'none'
106	key_set "$1" "RETIRED" 'none'
107	key_set "$1" "REVOKED" 'none'
108	key_set "$1" "REMOVED" 'none'
109	key_set "$1" "GOAL" 'none'
110	key_set "$1" "STATE_DNSKEY" 'none'
111	key_set "$1" "STATE_KRRSIG" 'none'
112	key_set "$1" "STATE_ZRRSIG" 'none'
113	key_set "$1" "STATE_DS" 'none'
114	key_set "$1" "EXPECT_ZRRSIG" 'no'
115	key_set "$1" "EXPECT_KRRSIG" 'no'
116	key_set "$1" "LEGACY" 'no'
117	key_set "$1" "PRIVATE" 'yes'
118}
119
120# Start clear.
121# There can be at most 4 keys at the same time during a rollover:
122# 2x KSK, 2x ZSK
123key_clear "KEY1"
124key_clear "KEY2"
125key_clear "KEY3"
126key_clear "KEY4"
127
128###############################################################################
129# Utilities                                                                   #
130###############################################################################
131
132# Call dig with default options.
133_dig_with_opts() {
134
135	if [ -n "$TSIG" ]; then
136		"$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p "$PORT" -y "$TSIG" "$@"
137	else
138		"$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p "$PORT" "$@"
139	fi
140}
141
142# RNDC.
143_rndccmd() {
144	"$RNDC" -c ../common/rndc.conf -p "$CONTROLPORT" -s "$@"
145}
146
147# Print IDs of keys used for generating RRSIG records for RRsets of type $1
148# found in dig output file $2.
149get_keys_which_signed() {
150	_qtype=$1
151	_output=$2
152	# The key ID is the 11th column of the RRSIG record line.
153	awk -v qt="$_qtype" '$4 == "RRSIG" && $5 == qt {print $11}' < "$_output"
154}
155
156# Get the key ids from key files for zone $2 in directory $1.
157get_keyids() {
158	_dir=$1
159	_zone=$2
160	_regex="K${_zone}.+*+*.key"
161
162	find "${_dir}" -mindepth 1 -maxdepth 1 -name "${_regex}" | sed "s,$_dir/K${_zone}.+\([0-9]\{3\}\)+\([0-9]\{5\}\).key,\2,"
163}
164
165# By default log errors and don't quit immediately.
166_log=1
167_log_error() {
168	test $_log -eq 1 && echo_i "error: $1"
169	ret=$((ret+1))
170}
171disable_logerror() {
172	_log=0
173}
174enable_logerror() {
175	_log=1
176}
177
178# Set server key-directory ($1) and address ($2) for testing keys.
179set_server() {
180	DIR=$1
181	SERVER=$2
182}
183# Set zone name for testing keys.
184set_zone() {
185	ZONE=$1
186	DYNAMIC="no"
187}
188# By default zones are considered static.
189# When testing dynamic zones, call 'set_dynamic' after 'set_zone'.
190set_dynamic() {
191	DYNAMIC="yes"
192}
193
194# Set policy settings (name $1, number of keys $2, dnskey ttl $3) for testing keys.
195set_policy() {
196	POLICY=$1
197	NUM_KEYS=$2
198	DNSKEY_TTL=$3
199	CDS_DELETE="no"
200}
201# By default policies are considered to be secure.
202# If a zone sets its policy to "insecure", call 'set_cdsdelete' to tell the
203# system test to expect a CDS and CDNSKEY Delete record.
204set_cdsdelete() {
205	CDS_DELETE="yes"
206}
207
208# Set key properties for testing keys.
209# $1: Key to update (KEY1, KEY2, ...)
210# $2: Value
211set_keyrole() {
212	key_set "$1" "EXPECT" "yes"
213	key_set "$1" "ROLE" "$2"
214	key_set "$1" "KSK" "no"
215	key_set "$1" "ZSK" "no"
216	test "$2" = "ksk" && key_set "$1" "KSK" "yes"
217	test "$2" = "zsk" && key_set "$1" "ZSK" "yes"
218	test "$2" = "csk" && key_set "$1" "KSK" "yes"
219	test "$2" = "csk" && key_set "$1" "ZSK" "yes"
220}
221set_keylifetime() {
222	key_set "$1" "EXPECT" "yes"
223	key_set "$1" "LIFETIME" "$2"
224}
225# The algorithm value consists of three parts:
226# $2: Algorithm (number)
227# $3: Algorithm (string-format)
228# $4: Algorithm length
229set_keyalgorithm() {
230	key_set "$1" "EXPECT" "yes"
231	key_set "$1" "ALG_NUM" "$2"
232	key_set "$1" "ALG_STR" "$3"
233	key_set "$1" "ALG_LEN" "$4"
234}
235set_keysigning() {
236	key_set "$1" "EXPECT" "yes"
237	key_set "$1" "EXPECT_KRRSIG" "$2"
238}
239set_zonesigning() {
240	key_set "$1" "EXPECT" "yes"
241	key_set "$1" "EXPECT_ZRRSIG" "$2"
242}
243
244# Set key timing metadata. Set to "none" to unset.
245# $1: Key to update (KEY1, KEY2, ...)
246# $2: Time to update (PUBLISHED, SYNCPUBLISH, ACTIVE, RETIRED, REVOKED, or REMOVED).
247# $3: Value
248set_keytime() {
249	key_set "$1" "EXPECT" "yes"
250	key_set "$1" "$2" "$3"
251}
252
253# Set key timing metadata to a value plus additional time.
254# $1: Key to update (KEY1, KEY2, ...)
255# $2: Time to update (PUBLISHED, SYNCPUBLISH, ACTIVE, RETIRED, REVOKED, or REMOVED).
256# $3: Value
257# $4: Additional time.
258set_addkeytime() {
259	if [ -x "$PYTHON" ]; then
260		# Convert "%Y%m%d%H%M%S" format to epoch seconds.
261		# Then, add the additional time (can be negative).
262		_value=$3
263		_plus=$4
264		$PYTHON > python.out.$ZONE.$1.$2 <<EOF
265from datetime import datetime
266from datetime import timedelta
267_now = datetime.strptime("$_value", "%Y%m%d%H%M%S")
268_delta = timedelta(seconds=$_plus)
269_then = _now + _delta
270print(_then.strftime("%Y%m%d%H%M%S"));
271EOF
272		# Set the expected timing metadata.
273		key_set "$1" "$2" $(cat python.out.$ZONE.$1.$2)
274	fi
275}
276
277# Set key state metadata. Set to "none" to unset.
278# $1: Key to update (KEY1, KEY2, ...)
279# $2: Key state to update (GOAL, STATE_DNSKEY, STATE_ZRRSIG, STATE_KRRSIG, or STATE_DS)
280# $3: Value
281set_keystate() {
282	key_set "$1" "EXPECT" "yes"
283	key_set "$1" "$2" "$3"
284}
285
286# Check the key $1 with id $2.
287# This requires environment variables to be set.
288#
289# This will set the following environment variables for testing:
290# BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}"
291# KEY_FILE="${BASE_FILE}.key"
292# PRIVATE_FILE="${BASE_FILE}.private"
293# STATE_FILE="${BASE_FILE}.state"
294# KEY_ID=$(echo $1 | sed 's/^0\{0,4\}//')
295# KEY_CREATED (from the KEY_FILE)
296check_key() {
297	_dir="$DIR"
298	_zone="$ZONE"
299	_role=$(key_get "$1" ROLE)
300	_key_idpad="$2"
301	_key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//')
302	_alg_num=$(key_get "$1" ALG_NUM)
303	_alg_numpad=$(printf "%03d" "$_alg_num")
304	_alg_string=$(key_get "$1" ALG_STR)
305	_length=$(key_get "$1" "ALG_LEN")
306	_dnskey_ttl="$DNSKEY_TTL"
307	_lifetime=$(key_get "$1" LIFETIME)
308	_legacy=$(key_get "$1" LEGACY)
309	_private=$(key_get "$1" PRIVATE)
310
311	_published=$(key_get "$1" PUBLISHED)
312	_active=$(key_get "$1" ACTIVE)
313	_retired=$(key_get "$1" RETIRED)
314	_revoked=$(key_get "$1" REVOKED)
315	_removed=$(key_get "$1" REMOVED)
316
317	_goal=$(key_get "$1" GOAL)
318	_state_dnskey=$(key_get "$1" STATE_DNSKEY)
319	_state_zrrsig=$(key_get "$1" STATE_ZRRSIG)
320	_state_krrsig=$(key_get "$1" STATE_KRRSIG)
321	_state_ds=$(key_get "$1" STATE_DS)
322
323	_ksk="no"
324	_zsk="no"
325	if [ "$_role" = "ksk" ]; then
326		_role2="key-signing"
327		_ksk="yes"
328		_flags="257"
329	elif [ "$_role" = "zsk" ]; then
330		_role2="zone-signing"
331		_zsk="yes"
332		_flags="256"
333	elif [ "$_role" = "csk" ]; then
334		_role2="key-signing"
335		_zsk="yes"
336		_ksk="yes"
337		_flags="257"
338	fi
339
340	BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}"
341	KEY_FILE="${BASE_FILE}.key"
342	PRIVATE_FILE="${BASE_FILE}.private"
343	STATE_FILE="${BASE_FILE}.state"
344	KEY_ID="${_key_id}"
345
346	# Check file existence.
347	[ -s "$KEY_FILE" ] || ret=1
348	if [ "$_private" = "yes" ]; then
349		[ -s "$PRIVATE_FILE" ] || ret=1
350	fi
351	if [ "$_legacy" = "no" ]; then
352		[ -s "$STATE_FILE" ] || ret=1
353	fi
354	[ "$ret" -eq 0 ] || _log_error "${BASE_FILE} files missing"
355	[ "$ret" -eq 0 ] || return
356
357	# Retrieve creation date.
358	grep "; Created:" "$KEY_FILE" > "${ZONE}.${KEY_ID}.${_alg_num}.created" || _log_error "mismatch created comment in $KEY_FILE"
359	KEY_CREATED=$(awk '{print $3}' < "${ZONE}.${KEY_ID}.${_alg_num}.created")
360
361	if [ "$_private" = "yes" ]; then
362		grep "Created: ${KEY_CREATED}" "$PRIVATE_FILE" > /dev/null || _log_error "mismatch created in $PRIVATE_FILE"
363	fi
364	if [ "$_legacy" = "no" ]; then
365		grep "Generated: ${KEY_CREATED}" "$STATE_FILE" > /dev/null || _log_error "mismatch generated in $STATE_FILE"
366	fi
367
368	test $_log -eq 1 && echo_i "check key file $BASE_FILE"
369
370	# Check the public key file.
371	grep "This is a ${_role2} key, keyid ${_key_id}, for ${_zone}." "$KEY_FILE" > /dev/null || _log_error "mismatch top comment in $KEY_FILE"
372	grep "${_zone}\. ${_dnskey_ttl} IN DNSKEY ${_flags} 3 ${_alg_num}" "$KEY_FILE" > /dev/null || _log_error "mismatch DNSKEY record in $KEY_FILE"
373	# Now check the private key file.
374	if [ "$_private" = "yes" ]; then
375		grep "Private-key-format: v1.3" "$PRIVATE_FILE" > /dev/null || _log_error "mismatch private key format in $PRIVATE_FILE"
376		grep "Algorithm: ${_alg_num} (${_alg_string})" "$PRIVATE_FILE" > /dev/null || _log_error "mismatch algorithm in $PRIVATE_FILE"
377	fi
378	# Now check the key state file.
379	if [ "$_legacy" = "no" ]; then
380		grep "This is the state of key ${_key_id}, for ${_zone}." "$STATE_FILE" > /dev/null || _log_error "mismatch top comment in $STATE_FILE"
381		if [ "$_lifetime" = "none" ]; then
382			grep "Lifetime: " "$STATE_FILE" > /dev/null && _log_error "unexpected lifetime in $STATE_FILE"
383		else
384			grep "Lifetime: ${_lifetime}" "$STATE_FILE" > /dev/null || _log_error "mismatch lifetime in $STATE_FILE"
385		fi
386		grep "Algorithm: ${_alg_num}" "$STATE_FILE" > /dev/null || _log_error "mismatch algorithm in $STATE_FILE"
387		grep "Length: ${_length}" "$STATE_FILE" > /dev/null || _log_error "mismatch length in $STATE_FILE"
388		grep "KSK: ${_ksk}" "$STATE_FILE" > /dev/null || _log_error "mismatch ksk in $STATE_FILE"
389		grep "ZSK: ${_zsk}" "$STATE_FILE" > /dev/null || _log_error "mismatch zsk in $STATE_FILE"
390
391		# Check key states.
392		if [ "$_goal" = "none" ]; then
393			grep "GoalState: " "$STATE_FILE" > /dev/null && _log_error "unexpected goal state in $STATE_FILE"
394		else
395			grep "GoalState: ${_goal}" "$STATE_FILE" > /dev/null || _log_error "mismatch goal state in $STATE_FILE"
396		fi
397
398		if [ "$_state_dnskey" = "none" ]; then
399			grep "DNSKEYState: " "$STATE_FILE" > /dev/null && _log_error "unexpected dnskey state in $STATE_FILE"
400			grep "DNSKEYChange: " "$STATE_FILE" > /dev/null && _log_error "unexpected dnskey change in $STATE_FILE"
401		else
402			grep "DNSKEYState: ${_state_dnskey}" "$STATE_FILE" > /dev/null || _log_error "mismatch dnskey state in $STATE_FILE"
403			grep "DNSKEYChange: " "$STATE_FILE" > /dev/null || _log_error "mismatch dnskey change in $STATE_FILE"
404		fi
405
406		if [ "$_state_zrrsig" = "none" ]; then
407			grep "ZRRSIGState: " "$STATE_FILE" > /dev/null && _log_error "unexpected zrrsig state in $STATE_FILE"
408			grep "ZRRSIGChange: " "$STATE_FILE" > /dev/null && _log_error "unexpected zrrsig change in $STATE_FILE"
409		else
410			grep "ZRRSIGState: ${_state_zrrsig}" "$STATE_FILE" > /dev/null || _log_error "mismatch zrrsig state in $STATE_FILE"
411			grep "ZRRSIGChange: " "$STATE_FILE" > /dev/null || _log_error "mismatch zrrsig change in $STATE_FILE"
412		fi
413
414		if [ "$_state_krrsig" = "none" ]; then
415			grep "KRRSIGState: " "$STATE_FILE" > /dev/null && _log_error "unexpected krrsig state in $STATE_FILE"
416			grep "KRRSIGChange: " "$STATE_FILE" > /dev/null && _log_error "unexpected krrsig change in $STATE_FILE"
417		else
418			grep "KRRSIGState: ${_state_krrsig}" "$STATE_FILE" > /dev/null || _log_error "mismatch krrsig state in $STATE_FILE"
419			grep "KRRSIGChange: " "$STATE_FILE" > /dev/null || _log_error "mismatch krrsig change in $STATE_FILE"
420		fi
421
422		if [ "$_state_ds" = "none" ]; then
423			grep "DSState: " "$STATE_FILE" > /dev/null && _log_error "unexpected ds state in $STATE_FILE"
424			grep "DSChange: " "$STATE_FILE" > /dev/null && _log_error "unexpected ds change in $STATE_FILE"
425		else
426			grep "DSState: ${_state_ds}" "$STATE_FILE" > /dev/null || _log_error "mismatch ds state in $STATE_FILE"
427			grep "DSChange: " "$STATE_FILE" > /dev/null || _log_error "mismatch ds change in $STATE_FILE"
428		fi
429	fi
430}
431
432# Check the key timing metadata for key $1.
433check_timingmetadata() {
434	_dir="$DIR"
435	_zone="$ZONE"
436	_key_idpad=$(key_get "$1" ID)
437	_key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//')
438	_alg_num=$(key_get "$1" ALG_NUM)
439	_alg_numpad=$(printf "%03d" "$_alg_num")
440
441	_published=$(key_get "$1" PUBLISHED)
442	_active=$(key_get "$1" ACTIVE)
443	_retired=$(key_get "$1" RETIRED)
444	_revoked=$(key_get "$1" REVOKED)
445	_removed=$(key_get "$1" REMOVED)
446
447	_goal=$(key_get "$1" GOAL)
448	_state_dnskey=$(key_get "$1" STATE_DNSKEY)
449	_state_zrrsig=$(key_get "$1" STATE_ZRRSIG)
450	_state_krrsig=$(key_get "$1" STATE_KRRSIG)
451	_state_ds=$(key_get "$1" STATE_DS)
452
453	_base_file=$(key_get "$1" BASEFILE)
454	_key_file="${_base_file}.key"
455	_private_file="${_base_file}.private"
456	_state_file="${_base_file}.state"
457	_legacy=$(key_get "$1" LEGACY)
458	_private=$(key_get "$1" PRIVATE)
459
460	_published=$(key_get "$1" PUBLISHED)
461	_syncpublish=$(key_get "$1" SYNCPUBLISH)
462	_active=$(key_get "$1" ACTIVE)
463	_retired=$(key_get "$1" RETIRED)
464	_revoked=$(key_get "$1" REVOKED)
465	_removed=$(key_get "$1" REMOVED)
466
467	# Check timing metadata.
468	n=$((n+1))
469	echo_i "check key timing metadata for key $1 id ${_key_id} zone ${ZONE} ($n)"
470	ret=0
471
472	if [ "$_published" = "none" ]; then
473		grep "; Publish:" "${_key_file}" > /dev/null && _log_error "unexpected publish comment in ${_key_file}"
474		if [ "$_private" = "yes" ]; then
475			grep "Publish:" "${_private_file}" > /dev/null && _log_error "unexpected publish in ${_private_file}"
476		fi
477		if [ "$_legacy" = "no" ]; then
478			grep "Published: " "${_state_file}" > /dev/null && _log_error "unexpected publish in ${_state_file}"
479		fi
480	else
481		grep "; Publish: $_published" "${_key_file}" > /dev/null || _log_error "mismatch publish comment in ${_key_file} (expected ${_published})"
482		if [ "$_private" = "yes" ]; then
483			grep "Publish: $_published" "${_private_file}" > /dev/null || _log_error "mismatch publish in ${_private_file} (expected ${_published})"
484		fi
485		if [ "$_legacy" = "no" ]; then
486			grep "Published: $_published" "${_state_file}" > /dev/null || _log_error "mismatch publish in ${_state_file} (expected ${_published})"
487		fi
488	fi
489
490	if [ "$_syncpublish" = "none" ]; then
491		grep "; SyncPublish:" "${_key_file}" > /dev/null && _log_error "unexpected syncpublish comment in ${_key_file}"
492		if [ "$_private" = "yes" ]; then
493			grep "SyncPublish:" "${_private_file}" > /dev/null && _log_error "unexpected syncpublish in ${_private_file}"
494		fi
495		if [ "$_legacy" = "no" ]; then
496			grep "PublishCDS: " "${_state_file}" > /dev/null && _log_error "unexpected syncpublish in ${_state_file}"
497		fi
498	else
499		grep "; SyncPublish: $_syncpublish" "${_key_file}" > /dev/null || _log_error "mismatch syncpublish comment in ${_key_file} (expected ${_syncpublish})"
500		if [ "$_private" = "yes" ]; then
501			grep "SyncPublish: $_syncpublish" "${_private_file}" > /dev/null || _log_error "mismatch syncpublish in ${_private_file} (expected ${_syncpublish})"
502		fi
503		if [ "$_legacy" = "no" ]; then
504			grep "PublishCDS: $_syncpublish" "${_state_file}" > /dev/null || _log_error "mismatch syncpublish in ${_state_file} (expected ${_syncpublish})"
505		fi
506	fi
507
508	if [ "$_active" = "none" ]; then
509		grep "; Activate:" "${_key_file}" > /dev/null && _log_error "unexpected active comment in ${_key_file}"
510		if [ "$_private" = "yes" ]; then
511			grep "Activate:" "${_private_file}" > /dev/null && _log_error "unexpected active in ${_private_file}"
512		fi
513		if [ "$_legacy" = "no" ]; then
514			grep "Active: " "${_state_file}" > /dev/null && _log_error "unexpected active in ${_state_file}"
515		fi
516	else
517		grep "; Activate: $_active" "${_key_file}" > /dev/null || _log_error "mismatch active comment in ${_key_file} (expected ${_active})"
518		if [ "$_private" = "yes" ]; then
519			grep "Activate: $_active" "${_private_file}" > /dev/null || _log_error "mismatch active in ${_private_file} (expected ${_active})"
520		fi
521		if [ "$_legacy" = "no" ]; then
522			grep "Active: $_active" "${_state_file}" > /dev/null || _log_error "mismatch active in ${_state_file} (expected ${_active})"
523		fi
524	fi
525
526	if [ "$_retired" = "none" ]; then
527		grep "; Inactive:" "${_key_file}" > /dev/null && _log_error "unexpected retired comment in ${_key_file}"
528		if [ "$_private" = "yes" ]; then
529			grep "Inactive:" "${_private_file}" > /dev/null && _log_error "unexpected retired in ${_private_file}"
530		fi
531		if [ "$_legacy" = "no" ]; then
532			grep "Retired: " "${_state_file}" > /dev/null && _log_error "unexpected retired in ${_state_file}"
533		fi
534	else
535		grep "; Inactive: $_retired" "${_key_file}" > /dev/null || _log_error "mismatch retired comment in ${_key_file} (expected ${_retired})"
536		if [ "$_private" = "yes" ]; then
537			grep "Inactive: $_retired" "${_private_file}" > /dev/null || _log_error "mismatch retired in ${_private_file} (expected ${_retired})"
538		fi
539		if [ "$_legacy" = "no" ]; then
540			grep "Retired: $_retired" "${_state_file}" > /dev/null || _log_error "mismatch retired in ${_state_file} (expected ${_retired})"
541		fi
542	fi
543
544	if [ "$_revoked" = "none" ]; then
545		grep "; Revoke:" "${_key_file}" > /dev/null && _log_error "unexpected revoked comment in ${_key_file}"
546		if [ "$_private" = "yes" ]; then
547			grep "Revoke:" "${_private_file}" > /dev/null && _log_error "unexpected revoked in ${_private_file}"
548		fi
549		if [ "$_legacy" = "no" ]; then
550			grep "Revoked: " "${_state_file}" > /dev/null && _log_error "unexpected revoked in ${_state_file}"
551		fi
552	else
553		grep "; Revoke: $_revoked" "${_key_file}" > /dev/null || _log_error "mismatch revoked comment in ${_key_file} (expected ${_revoked})"
554		if [ "$_private" = "yes" ]; then
555			grep "Revoke: $_revoked" "${_private_file}" > /dev/null || _log_error "mismatch revoked in ${_private_file} (expected ${_revoked})"
556		fi
557		if [ "$_legacy" = "no" ]; then
558			grep "Revoked: $_revoked" "${_state_file}" > /dev/null || _log_error "mismatch revoked in ${_state_file} (expected ${_revoked})"
559		fi
560	fi
561
562	if [ "$_removed" = "none" ]; then
563		grep "; Delete:" "${_key_file}" > /dev/null && _log_error "unexpected removed comment in ${_key_file}"
564		if [ "$_private" = "yes" ]; then
565			grep "Delete:" "${_private_file}" > /dev/null && _log_error "unexpected removed in ${_private_file}"
566		fi
567		if [ "$_legacy" = "no" ]; then
568			grep "Removed: " "${_state_file}" > /dev/null && _log_error "unexpected removed in ${_state_file}"
569		fi
570	else
571		grep "; Delete: $_removed" "${_key_file}" > /dev/null || _log_error "mismatch removed comment in ${_key_file} (expected ${_removed})"
572		if [ "$_private" = "yes" ]; then
573			grep "Delete: $_removed" "${_private_file}" > /dev/null || _log_error "mismatch removed in ${_private_file} (expected ${_removed})"
574		fi
575		if [ "$_legacy" = "no" ]; then
576			grep "Removed: $_removed" "${_state_file}" > /dev/null || _log_error "mismatch removed in ${_state_file} (expected ${_removed})"
577		fi
578	fi
579
580	test "$ret" -eq 0 || echo_i "failed"
581	status=$((status+ret))
582}
583
584check_keytimes() {
585	# The script relies on Python to set keytimes.
586	if [ -x "$PYTHON" ]; then
587
588		if [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
589			check_timingmetadata "KEY1"
590		fi
591		if [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
592			check_timingmetadata "KEY2"
593		fi
594		if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
595			check_timingmetadata "KEY3"
596		fi
597		if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
598			check_timingmetadata "KEY4"
599		fi
600	fi
601}
602
603# Check the key with key id $1 and see if it is unused.
604# This requires environment variables to be set.
605#
606# This will set the following environment variables for testing:
607# BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}"
608# KEY_FILE="${BASE_FILE}.key"
609# PRIVATE_FILE="${BASE_FILE}.private"
610# STATE_FILE="${BASE_FILE}.state"
611# KEY_ID=$(echo $1 | sed 's/^0\{0,4\}//')
612key_unused() {
613	_dir=$DIR
614	_zone=$ZONE
615	_key_idpad=$1
616	_key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//')
617	_alg_num=$2
618        _alg_numpad=$(printf "%03d" "$_alg_num")
619
620	BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}"
621	KEY_FILE="${BASE_FILE}.key"
622	PRIVATE_FILE="${BASE_FILE}.private"
623	STATE_FILE="${BASE_FILE}.state"
624	KEY_ID="${_key_id}"
625
626	test $_log -eq 1 && echo_i "key unused $KEY_ID?"
627
628	# Check file existence.
629	[ -s "$KEY_FILE" ] || ret=1
630	[ -s "$PRIVATE_FILE" ] || ret=1
631	[ -s "$STATE_FILE" ] || ret=1
632	[ "$ret" -eq 0 ] || return
633
634	# Treat keys that have been removed from the zone as unused.
635	_check_removed=1
636	grep "; Created:" "$KEY_FILE" > created.key-${KEY_ID}.test${n} || _check_removed=0
637	grep "; Delete:" "$KEY_FILE" > unused.key-${KEY_ID}.test${n} || _check_removed=0
638	if [ "$_check_removed" -eq 1 ]; then
639		_created=$(awk '{print $3}' < created.key-${KEY_ID}.test${n})
640		_removed=$(awk '{print $3}' < unused.key-${KEY_ID}.test${n})
641		[ "$_removed" -le "$_created" ] && return
642	fi
643
644	# If no timing metadata is set, this key is unused.
645	grep "; Publish:" "$KEY_FILE" > /dev/null && _log_error "unexpected publish comment in $KEY_FILE"
646	grep "; Activate:" "$KEY_FILE" > /dev/null && _log_error "unexpected active comment in $KEY_FILE"
647	grep "; Inactive:" "$KEY_FILE" > /dev/null && _log_error "unexpected retired comment in $KEY_FILE"
648	grep "; Revoke:" "$KEY_FILE" > /dev/null && _log_error "unexpected revoked comment in $KEY_FILE"
649	grep "; Delete:" "$KEY_FILE" > /dev/null && _log_error "unexpected removed comment in $KEY_FILE"
650
651	grep "Publish:" "$PRIVATE_FILE" > /dev/null && _log_error "unexpected publish in $PRIVATE_FILE"
652	grep "Activate:" "$PRIVATE_FILE" > /dev/null && _log_error "unexpected active in $PRIVATE_FILE"
653	grep "Inactive:" "$PRIVATE_FILE" > /dev/null && _log_error "unexpected retired in $PRIVATE_FILE"
654	grep "Revoke:" "$PRIVATE_FILE" > /dev/null && _log_error "unexpected revoked in $PRIVATE_FILE"
655	grep "Delete:" "$PRIVATE_FILE" > /dev/null && _log_error "unexpected removed in $PRIVATE_FILE"
656
657	grep "Published: " "$STATE_FILE" > /dev/null && _log_error "unexpected publish in $STATE_FILE"
658	grep "Active: " "$STATE_FILE" > /dev/null && _log_error "unexpected active in $STATE_FILE"
659	grep "Retired: " "$STATE_FILE" > /dev/null && _log_error "unexpected retired in $STATE_FILE"
660	grep "Revoked: " "$STATE_FILE" > /dev/null && _log_error "unexpected revoked in $STATE_FILE"
661	grep "Removed: " "$STATE_FILE" > /dev/null && _log_error "unexpected removed in $STATE_FILE"
662}
663
664# Test: dnssec-verify zone $1.
665dnssec_verify()
666{
667	n=$((n+1))
668	echo_i "dnssec-verify zone ${ZONE} ($n)"
669	ret=0
670	_dig_with_opts "$ZONE" "@${SERVER}" AXFR > dig.out.axfr.test$n || _log_error "dig ${ZONE} AXFR failed"
671	$VERIFY -z -o "$ZONE" dig.out.axfr.test$n > /dev/null || _log_error "dnssec verify zone $ZONE failed"
672	test "$ret" -eq 0 || echo_i "failed"
673	status=$((status+ret))
674}
675
676# Wait for the zone to be signed.
677# The apex NSEC record indicates that it is signed.
678_wait_for_nsec() {
679	_dig_with_opts "@${SERVER}" "$ZONE" NSEC > "dig.out.nsec.test$n" || return 1
680	grep "NS SOA" "dig.out.nsec.test$n" > /dev/null || return 1
681	grep "${ZONE}\..*IN.*RRSIG" "dig.out.nsec.test$n" > /dev/null || return 1
682	return 0
683}
684wait_for_nsec() {
685	n=$((n+1))
686	ret=0
687	echo_i "wait for ${ZONE} to be signed ($n)"
688	retry_quiet 10 _wait_for_nsec  || _log_error "wait for ${ZONE} to be signed failed"
689	test "$ret" -eq 0 || echo_i "failed"
690	status=$((status+ret))
691}
692
693check_numkeys() {
694	_numkeys=$(get_keyids "$DIR" "$ZONE" | wc -l)
695	test "$_numkeys" -eq "$NUM_KEYS" || return 1
696	return 0
697}
698
699_check_keys() {
700	ret=0
701
702	# Clear key ids.
703	key_set KEY1 ID "no"
704	key_set KEY2 ID "no"
705	key_set KEY3 ID "no"
706	key_set KEY4 ID "no"
707
708	# Check key files.
709	_ids=$(get_keyids "$DIR" "$ZONE")
710	for _id in $_ids; do
711		# There are multiple key files with the same algorithm.
712		# Check them until a match is found.
713		ret=0
714		echo_i "check key id $_id"
715
716		if [ "no" = "$(key_get KEY1 ID)" ] && [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
717			ret=0
718			check_key "KEY1" "$_id"
719			test "$ret" -eq 0 && key_save KEY1 && continue
720		fi
721		if [ "no" = "$(key_get KEY2 ID)" ] && [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
722			ret=0
723			check_key "KEY2" "$_id"
724			test "$ret" -eq 0 && key_save KEY2 && continue
725		fi
726		if [ "no" = "$(key_get KEY3 ID)" ] && [ "$(key_get KEY3 EXPECT)" = "yes"  ]; then
727			ret=0
728			check_key "KEY3" "$_id"
729			test "$ret" -eq 0 && key_save KEY3 && continue
730		fi
731		if [ "no" = "$(key_get KEY4 ID)" ] && [ "$(key_get KEY4 EXPECT)" = "yes"  ]; then
732			ret=0
733			check_key "KEY4" "$_id"
734			test "$ret" -eq 0 && key_save KEY4 && continue
735		fi
736
737		# This may be an unused key. Assume algorithm of KEY1.
738		ret=0 && key_unused "$_id" "$(key_get KEY1 ALG_NUM)"
739		test "$ret" -eq 0 && continue
740
741		# If ret is still non-zero, none of the files matched.
742		echo_i "failed"
743		return 1
744	done
745
746	return 0
747}
748
749# Check keys for a configured zone. This verifies:
750# 1. The right number of keys exist in the key pool ($1).
751# 2. The right number of keys is active. Checks KEY1, KEY2, KEY3, and KEY4.
752#
753# It is expected that KEY1, KEY2, KEY3, and KEY4 arrays are set correctly.
754# Found key identifiers are stored in the right key array.
755check_keys() {
756	n=$((n+1))
757	echo_i "check keys are created for zone ${ZONE} ($n)"
758	ret=0
759
760	echo_i "check number of keys for zone ${ZONE} in dir ${DIR} ($n)"
761	retry_quiet 10 check_numkeys || ret=1
762	if [ $ret -ne 0 ]; then
763		_numkeys=$(get_keyids "$DIR" "$ZONE" | wc -l)
764		_log_error "bad number of key files ($_numkeys) for zone $ZONE (expected $NUM_KEYS)"
765		status=$((status+ret))
766	fi
767
768	# Temporarily don't log errors because we are searching multiple files.
769	disable_logerror
770
771	retry_quiet 3 _check_keys || ret=1
772	test "$ret" -eq 0 || echo_i "failed"
773	status=$((status+ret))
774
775	# Turn error logs on again.
776	enable_logerror
777
778	ret=0
779	if [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
780		echo_i "KEY1 ID $(key_get KEY1 ID)"
781		test "no" = "$(key_get KEY1 ID)" && _log_error "No KEY1 found for zone ${ZONE}"
782	fi
783	if [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
784		echo_i "KEY2 ID $(key_get KEY2 ID)"
785		test "no" = "$(key_get KEY2 ID)" && _log_error "No KEY2 found for zone ${ZONE}"
786	fi
787	if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
788		echo_i "KEY3 ID $(key_get KEY3 ID)"
789		test "no" = "$(key_get KEY3 ID)" && _log_error "No KEY3 found for zone ${ZONE}"
790	fi
791	if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
792		echo_i "KEY4 ID $(key_get KEY4 ID)"
793		test "no" = "$(key_get KEY4 ID)" && _log_error "No KEY4 found for zone ${ZONE}"
794	fi
795	test "$ret" -eq 0 || echo_i "failed"
796	status=$((status+ret))
797}
798
799# Call rndc dnssec -status on server $1 for zone $2 and check output.
800# This is a loose verification, it just tests if the right policy
801# name is returned, and if all expected keys are listed.  The rndc
802# dnssec -status output also lists whether a key is published,
803# used for signing, is retired, or is removed, and if not when
804# it is scheduled to do so, and it shows the states for the various
805# DNSSEC records.
806check_dnssecstatus() {
807	_server=$1
808	_policy=$2
809	_zone=$3
810	_view=$4
811
812	n=$((n+1))
813	echo_i "check rndc dnssec -status output for ${_zone} (policy: $_policy) ($n)"
814	ret=0
815
816	_rndccmd $_server dnssec -status $_zone in $_view > rndc.dnssec.status.out.$_zone.$n || _log_error "rndc dnssec -status zone ${_zone} failed"
817
818	if [ "$_policy" = "none" ]; then
819		grep "Zone does not have dnssec-policy" rndc.dnssec.status.out.$_zone.$n > /dev/null || log_error "bad dnssec status for unsigned zone ${_zone}"
820	else
821		grep "dnssec-policy: ${_policy}" rndc.dnssec.status.out.$_zone.$n > /dev/null || _log_error "bad dnssec status for signed zone ${_zone}"
822		if [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
823			grep "key: $(key_get KEY1 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || _log_error "missing key $(key_get KEY1 ID) from dnssec status"
824		fi
825		if [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
826			grep "key: $(key_get KEY2 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || _log_error "missing key $(key_get KEY2 ID) from dnssec status"
827		fi
828		if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
829			grep "key: $(key_get KEY3 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || _log_error "missing key $(key_get KEY3 ID) from dnssec status"
830		fi
831		if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
832			grep "key: $(key_get KEY4 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || _log_error "missing key $(key_get KEY4 ID) from dnssec status"
833		fi
834	fi
835
836	test "$ret" -eq 0 || echo_i "failed"
837	status=$((status+ret))
838}
839
840# Check if RRset of type $1 in file $2 is signed with the right keys.
841# The right keys are the ones that expect a signature and matches the role $3.
842_check_signatures() {
843	_qtype=$1
844	_file=$2
845	_role=$3
846
847	numsigs=0
848
849	if [ "$_role" = "KSK" ]; then
850		_expect_type=EXPECT_KRRSIG
851	elif [ "$_role" = "ZSK" ]; then
852		_expect_type=EXPECT_ZRRSIG
853	fi
854
855	if [ "$(key_get KEY1 "$_expect_type")" = "yes" ] && [ "$(key_get KEY1 "$_role")" = "yes" ]; then
856		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY1 ID)$" > /dev/null || return 1
857		numsigs=$((numsigs+1))
858	elif [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
859		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY1 ID)$" > /dev/null && return 1
860	fi
861
862	if [ "$(key_get KEY2 "$_expect_type")" = "yes" ] && [ "$(key_get KEY2 "$_role")" = "yes" ]; then
863		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY2 ID)$" > /dev/null || return 1
864		numsigs=$((numsigs+1))
865	elif [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
866		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY2 ID)$" > /dev/null && return 1
867	fi
868
869	if [ "$(key_get KEY3 "$_expect_type")" = "yes" ] && [ "$(key_get KEY3 "$_role")" = "yes" ]; then
870		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY3 ID)$" > /dev/null || return 1
871		numsigs=$((numsigs+1))
872	elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
873		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY3 ID)$" > /dev/null && return 1
874	fi
875
876	if [ "$(key_get KEY4 "$_expect_type")" = "yes" ] && [ "$(key_get KEY4 "$_role")" = "yes" ]; then
877		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY4 ID)$" > /dev/null || return 1
878		numsigs=$((numsigs+1))
879	elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
880		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY4 ID)$" > /dev/null && return 1
881	fi
882
883	lines=$(get_keys_which_signed "${_qtype}" "${_file}" | wc -l)
884	test "$lines" -eq "$numsigs" || echo_i "bad number of signatures for $_qtype (got $lines, expected $numsigs)"
885	test "$lines" -eq "$numsigs" || return 1
886
887	return 0
888}
889check_signatures() {
890	retry_quiet 3 _check_signatures $1 $2 $3 || _log_error "RRset $1 in zone $ZONE incorrectly signed"
891}
892
893response_has_cds_for_key() (
894	awk -v zone="${ZONE%%.}." \
895	    -v ttl="${DNSKEY_TTL}" \
896	    -v qtype="CDS" \
897	    -v keyid="$(key_get "${1}" ID)" \
898	    -v keyalg="$(key_get "${1}" ALG_NUM)" \
899	    -v hashalg="2" \
900	    'BEGIN { ret=1; }
901	     $1 == zone && $2 == ttl && $4 == qtype && $5 == keyid && $6 == keyalg && $7 == hashalg { ret=0; exit; }
902	     END { exit ret; }' \
903	    "$2"
904)
905
906response_has_cdnskey_for_key() (
907	awk -v zone="${ZONE%%.}." \
908	    -v ttl="${DNSKEY_TTL}" \
909	    -v qtype="CDNSKEY" \
910	    -v flags="257" \
911	    -v keyalg="$(key_get "${1}" ALG_NUM)" \
912	    'BEGIN { ret=1; }
913	     $1 == zone && $2 == ttl && $4 == qtype && $5 == flags && $7 == keyalg { ret=0; exit; }
914	     END { exit ret; }' \
915	    "$2"
916)
917
918# Test CDS and CDNSKEY publication.
919check_cds() {
920
921	n=$((n+1))
922	echo_i "check CDS and CDNSKEY rrset are signed correctly for zone ${ZONE} ($n)"
923	ret=0
924
925	_checksig=0
926
927	_dig_with_opts "$ZONE" "@${SERVER}" "CDS" > "dig.out.$DIR.test$n.cds" || _log_error "dig ${ZONE} CDS failed"
928	grep "status: NOERROR" "dig.out.$DIR.test$n.cds" > /dev/null || _log_error "mismatch status in DNS response"
929
930	_dig_with_opts "$ZONE" "@${SERVER}" "CDNSKEY" > "dig.out.$DIR.test$n.cdnskey" || _log_error "dig ${ZONE} CDNSKEY failed"
931	grep "status: NOERROR" "dig.out.$DIR.test$n.cdnskey" > /dev/null || _log_error "mismatch status in DNS response"
932
933	if [ "$CDS_DELETE" = "no" ]; then
934		grep "CDS.*0 0 0 00" "dig.out.$DIR.test$n.cds" > /dev/null && _log_error "unexpected CDS DELETE record in DNS response"
935		grep "CDNSKEY.*0 3 0 AA==" "dig.out.$DIR.test$n.cdnskey" > /dev/null && _log_error "unexpected CDNSKEY DELETE record in DNS response"
936	else
937		grep "CDS.*0 0 0 00" "dig.out.$DIR.test$n.cds" > /dev/null || _log_error "missing CDS DELETE record in DNS response"
938		grep "CDNSKEY.*0 3 0 AA==" "dig.out.$DIR.test$n.cdnskey" > /dev/null || _log_error "missing CDNSKEY DELETE record in DNS response"
939		_checksig=1
940	fi
941
942	if [ "$(key_get KEY1 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY1 STATE_DS)" = "omnipresent" ]; then
943		response_has_cds_for_key KEY1 "dig.out.$DIR.test$n.cds" || _log_error "missing CDS record in response for key $(key_get KEY1 ID)"
944		response_has_cdnskey_for_key KEY1 "dig.out.$DIR.test$n.cdnskey" || _log_error "missing CDNSKEY record in response for key $(key_get KEY1 ID)"
945		_checksig=1
946	elif [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
947		response_has_cds_for_key KEY1 "dig.out.$DIR.test$n.cds" && _log_error "unexpected CDS record in response for key $(key_get KEY1 ID)"
948		# KEY1 should not have an associated CDNSKEY, but there may be
949		# one for another key.  Since the CDNSKEY has no field for key
950		# id, it is hard to check what key the CDNSKEY may belong to
951		# so let's skip this check for now.
952	fi
953
954	if [ "$(key_get KEY2 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY2 STATE_DS)" = "omnipresent" ]; then
955		response_has_cds_for_key KEY2 "dig.out.$DIR.test$n.cds" || _log_error "missing CDS record in response for key $(key_get KEY2 ID)"
956		response_has_cdnskey_for_key KEY2 "dig.out.$DIR.test$n.cdnskey" || _log_error "missing CDNSKEY record in response for key $(key_get KEY2 ID)"
957		_checksig=1
958	elif [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
959		response_has_cds_for_key KEY2 "dig.out.$DIR.test$n.cds" && _log_error "unexpected CDS record in response for key $(key_get KEY2 ID)"
960		# KEY2 should not have an associated CDNSKEY, but there may be
961		# one for another key.  Since the CDNSKEY has no field for key
962		# id, it is hard to check what key the CDNSKEY may belong to
963		# so let's skip this check for now.
964	fi
965
966	if [ "$(key_get KEY3 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY3 STATE_DS)" = "omnipresent" ]; then
967		response_has_cds_for_key KEY3 "dig.out.$DIR.test$n.cds" || _log_error "missing CDS record in response for key $(key_get KEY3 ID)"
968		response_has_cdnskey_for_key KEY3 "dig.out.$DIR.test$n.cdnskey" || _log_error "missing CDNSKEY record in response for key $(key_get KEY3 ID)"
969		_checksig=1
970	elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
971		response_has_cds_for_key KEY3 "dig.out.$DIR.test$n.cds" && _log_error "unexpected CDS record in response for key $(key_get KEY3 ID)"
972		# KEY3 should not have an associated CDNSKEY, but there may be
973		# one for another key.  Since the CDNSKEY has no field for key
974		# id, it is hard to check what key the CDNSKEY may belong to
975		# so let's skip this check for now.
976	fi
977
978	if [ "$(key_get KEY4 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY4 STATE_DS)" = "omnipresent" ]; then
979		response_has_cds_for_key KEY4 "dig.out.$DIR.test$n.cds" || _log_error "missing CDS record in response for key $(key_get KEY4 ID)"
980		response_has_cdnskey_for_key KEY4 "dig.out.$DIR.test$n.cdnskey" || _log_error "missing CDNSKEY record in response for key $(key_get KEY4 ID)"
981		_checksig=1
982	elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
983		response_has_cds_for_key KEY4 "dig.out.$DIR.test$n.cds" && _log_error "unexpected CDS record in response for key $(key_get KEY4 ID)"
984		# KEY4 should not have an associated CDNSKEY, but there may be
985		# one for another key.  Since the CDNSKEY has no field for key
986		# id, it is hard to check what key the CDNSKEY may belong to
987		# so let's skip this check for now.
988	fi
989
990	test "$_checksig" -eq 0 || check_signatures "CDS" "dig.out.$DIR.test$n.cds" "KSK"
991	test "$_checksig" -eq 0 || check_signatures "CDNSKEY" "dig.out.$DIR.test$n.cdnskey" "KSK"
992
993	test "$ret" -eq 0 || echo_i "failed"
994	status=$((status+ret))
995}
996
997
998# Test DNSKEY query.
999_check_apex_dnskey() {
1000	_dig_with_opts "$ZONE" "@${SERVER}" "DNSKEY" > "dig.out.$DIR.test$n" || return 1
1001	grep "status: NOERROR" "dig.out.$DIR.test$n" > /dev/null || return 1
1002
1003	_checksig=0
1004
1005	if [ "$(key_get KEY1 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY1 STATE_DNSKEY)" = "omnipresent" ]; then
1006		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*DNSKEY.*257.*.3.*$(key_get KEY1 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || return 1
1007		_checksig=1
1008	elif [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
1009		grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*DNSKEY.*257.*.3.*$(key_get KEY1 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && return 1
1010	fi
1011
1012	if [ "$(key_get KEY2 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY2 STATE_DNSKEY)" = "omnipresent" ]; then
1013		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*DNSKEY.*257.*.3.*$(key_get KEY2 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || return 1
1014		_checksig=1
1015	elif [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
1016		grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*DNSKEY.*257.*.3.*$(key_get KEY2 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && return 1
1017	fi
1018
1019	if [ "$(key_get KEY3 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY3 STATE_DNSKEY)" = "omnipresent" ]; then
1020		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*DNSKEY.*257.*.3.*$(key_get KEY3 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || return 1
1021		_checksig=1
1022	elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
1023		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*DNSKEY.*257.*.3.*$(key_get KEY3 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && return 1
1024	fi
1025
1026	if [ "$(key_get KEY4 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY4 STATE_DNSKEY)" = "omnipresent" ]; then
1027		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*DNSKEY.*257.*.3.*$(key_get KEY4 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || return 1
1028		_checksig=1
1029	elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
1030		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*DNSKEY.*257.*.3.*$(key_get KEY4 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && return 1
1031	fi
1032
1033	test "$_checksig" -eq 0 && return 0
1034
1035	retry_quiet 3 _check_signatures "DNSKEY" "dig.out.$DIR.test$n" "KSK" || return 1
1036
1037	return 0
1038}
1039
1040# Test the apex of a configured zone. This checks that the SOA and DNSKEY
1041# RRsets are signed correctly and with the appropriate keys.
1042check_apex() {
1043
1044	# Test DNSKEY query.
1045	n=$((n+1))
1046	echo_i "check DNSKEY rrset is signed correctly for zone ${ZONE} ($n)"
1047	ret=0
1048	retry_quiet 3 _check_apex_dnskey || ret=1
1049	test "$ret" -eq 0 || echo_i "failed"
1050	status=$((status+ret))
1051
1052	# We retry the DNSKEY query for at most three seconds to avoid test
1053	# failures due to timing issues. If the DNSKEY query check passes this
1054	# means the zone is resigned and further apex checks (SOA, CDS, CDNSKEY)
1055	# don't need to be retried quietly.
1056
1057	# Test SOA query.
1058	n=$((n+1))
1059	echo_i "check SOA rrset is signed correctly for zone ${ZONE} ($n)"
1060	ret=0
1061	_dig_with_opts "$ZONE" "@${SERVER}" "SOA" > "dig.out.$DIR.test$n" || _log_error "dig ${ZONE} SOA failed"
1062	grep "status: NOERROR" "dig.out.$DIR.test$n" > /dev/null || _log_error "mismatch status in DNS response"
1063	grep "${ZONE}\..*${DEFAULT_TTL}.*IN.*SOA.*" "dig.out.$DIR.test$n" > /dev/null || _log_error "missing SOA record in response"
1064	check_signatures "SOA" "dig.out.$DIR.test$n" "ZSK"
1065	test "$ret" -eq 0 || echo_i "failed"
1066	status=$((status+ret))
1067
1068	# Test CDS and CDNSKEY publication.
1069	check_cds
1070}
1071
1072# Test an RRset below the apex and verify it is signed correctly.
1073check_subdomain() {
1074	_qtype="A"
1075	n=$((n+1))
1076	echo_i "check ${_qtype} a.${ZONE} rrset is signed correctly for zone ${ZONE} ($n)"
1077	ret=0
1078	_dig_with_opts "a.$ZONE" "@${SERVER}" $_qtype > "dig.out.$DIR.test$n" || _log_error "dig a.${ZONE} ${_qtype} failed"
1079	grep "status: NOERROR" "dig.out.$DIR.test$n" > /dev/null || _log_error "mismatch status in DNS response"
1080	grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*${_qtype}.*10\.0\.0\.1" "dig.out.$DIR.test$n" > /dev/null || _log_error "missing a.${ZONE} ${_qtype} record in response"
1081	lines=$(get_keys_which_signed $_qtype "dig.out.$DIR.test$n" | wc -l)
1082	check_signatures $_qtype "dig.out.$DIR.test$n" "ZSK"
1083	test "$ret" -eq 0 || echo_i "failed"
1084	status=$((status+ret))
1085}
1086
1087# Check if "CDS/CDNSKEY Published" is logged.
1088check_cdslog() {
1089	_dir=$1
1090	_zone=$2
1091	_key=$3
1092
1093	_alg=$(key_get $_key ALG_STR)
1094	_id=$(key_get $_key ID)
1095
1096	n=$((n+1))
1097	echo_i "check CDS/CDNSKEY publication is logged in ${_dir}/named.run for key ${_zone}/${_alg}/${_id} ($n)"
1098	ret=0
1099
1100	grep "CDS for key ${_zone}/${_alg}/${_id} is now published" "${_dir}/named.run" > /dev/null || ret=1
1101	grep "CDNSKEY for key ${_zone}/${_alg}/${_id} is now published" "${_dir}/named.run" > /dev/null || ret=1
1102
1103	test "$ret" -eq 0 || echo_i "failed"
1104	status=$((status+ret))
1105}
1106
1107# Tell named that the DS for the key in given zone has been seen in the
1108# parent (this does not actually has to be true, we just issue the command
1109# to make named believe it can continue with the rollover).
1110rndc_checkds() {
1111	_server=$1
1112	_dir=$2
1113	_key=$3
1114	_when=$4
1115	_what=$5
1116	_zone=$6
1117	_view=$7
1118
1119	_keycmd=""
1120	if [ "${_key}" != "-" ]; then
1121		_keyid=$(key_get $_key ID)
1122		_keycmd=" -key ${_keyid}"
1123	fi
1124
1125	_whencmd=""
1126	if [ "${_when}" != "now" ]; then
1127		_whencmd=" -when ${_when}"
1128	fi
1129
1130	n=$((n+1))
1131	echo_i "calling rndc dnssec -checkds${_keycmd}${_whencmd} ${_what} zone ${_zone} in ${_view} ($n)"
1132	ret=0
1133
1134	_rndccmd $_server dnssec -checkds $_keycmd $_whencmd $_what $_zone in $_view > rndc.dnssec.checkds.out.$_zone.$n || _log_error "rndc dnssec -checkds${_keycmd}${_whencmd} ${_what} zone ${_zone} failed"
1135
1136	test "$ret" -eq 0 || echo_i "failed"
1137	status=$((status+ret))
1138}
1139
1140# Tell named to schedule a key rollover.
1141rndc_rollover() {
1142	_server=$1
1143	_dir=$2
1144	_keyid=$3
1145	_when=$4
1146	_zone=$5
1147	_view=$6
1148
1149	_whencmd=""
1150	if [ "${_when}" != "now" ]; then
1151		_whencmd="-when ${_when}"
1152	fi
1153
1154	n=$((n+1))
1155	echo_i "calling rndc dnssec -rollover key ${_keyid} ${_whencmd} zone ${_zone} ($n)"
1156	ret=0
1157
1158	_rndccmd $_server dnssec -rollover -key $_keyid $_whencmd $_zone in $_view > rndc.dnssec.rollover.out.$_zone.$n || _log_error "rndc dnssec -rollover (key ${_keyid} when ${_when}) zone ${_zone} failed"
1159
1160	test "$ret" -eq 0 || echo_i "failed"
1161	status=$((status+ret))
1162}
1163