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