xref: /netbsd-src/external/mpl/bind/dist/bin/dnssec/dnssec-cds.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: dnssec-cds.c,v 1.12 2025/01/26 16:24:32 christos Exp $	*/
2 
3 /*
4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5  *
6  * SPDX-License-Identifier: MPL-2.0
7  *
8  * This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11  *
12  * See the COPYRIGHT file distributed with this work for additional
13  * information regarding copyright ownership.
14  */
15 
16 /*
17  * Written by Tony Finch <dot@dotat.at> <fanf2@cam.ac.uk>
18  * at Cambridge University Information Services
19  */
20 
21 /*! \file */
22 
23 #include <errno.h>
24 #include <inttypes.h>
25 #include <stdbool.h>
26 #include <stdlib.h>
27 
28 #include <isc/attributes.h>
29 #include <isc/buffer.h>
30 #include <isc/commandline.h>
31 #include <isc/dir.h>
32 #include <isc/file.h>
33 #include <isc/hash.h>
34 #include <isc/mem.h>
35 #include <isc/result.h>
36 #include <isc/serial.h>
37 #include <isc/string.h>
38 #include <isc/time.h>
39 #include <isc/util.h>
40 
41 #include <dns/callbacks.h>
42 #include <dns/db.h>
43 #include <dns/dbiterator.h>
44 #include <dns/dnssec.h>
45 #include <dns/ds.h>
46 #include <dns/fixedname.h>
47 #include <dns/keyvalues.h>
48 #include <dns/log.h>
49 #include <dns/master.h>
50 #include <dns/name.h>
51 #include <dns/rdata.h>
52 #include <dns/rdataclass.h>
53 #include <dns/rdatalist.h>
54 #include <dns/rdataset.h>
55 #include <dns/rdatasetiter.h>
56 #include <dns/rdatatype.h>
57 #include <dns/time.h>
58 
59 #include <dst/dst.h>
60 
61 #include "dnssectool.h"
62 
63 const char *program = "dnssec-cds";
64 
65 /*
66  * Infrastructure
67  */
68 static isc_log_t *lctx = NULL;
69 static isc_mem_t *mctx = NULL;
70 
71 /*
72  * The domain we are working on
73  */
74 static const char *namestr = NULL;
75 static dns_fixedname_t fixed;
76 static dns_name_t *name = NULL;
77 static dns_rdataclass_t rdclass = dns_rdataclass_in;
78 
79 static const char *startstr = NULL; /* from which we derive notbefore */
80 static isc_stdtime_t notbefore = 0; /* restrict sig inception times */
81 static dns_rdata_rrsig_t oldestsig; /* for recording inception time */
82 
83 static int nkey; /* number of child zone DNSKEY records */
84 
85 /*
86  * The validation strategy of this program is top-down.
87  *
88  * We start with an implicitly trusted authoritative dsset.
89  *
90  * The child DNSKEY RRset is scanned to find out which keys are
91  * authenticated by DS records, and the result is recorded in a key
92  * table as described later in this comment.
93  *
94  * The key table is used up to three times to verify the signatures on
95  * the child DNSKEY, CDNSKEY, and CDS RRsets. In this program, only keys
96  * that have matching DS records are used for validating signatures.
97  *
98  * For replay attack protection, signatures are ignored if their inception
99  * time is before the previously recorded inception time. We use the earliest
100  * signature so that another run of dnssec-cds with the same records will
101  * still accept all the signatures.
102  *
103  * A key table is an array of nkey keyinfo structures, like
104  *
105  *	keyinfo_t key_tbl[nkey];
106  *
107  * Each key is decoded into more useful representations, held in
108  *	keyinfo->rdata
109  *	keyinfo->dst
110  *
111  * If a key has no matching DS record then keyinfo->dst is NULL.
112  *
113  * The key algorithm and ID are saved in keyinfo->algo and
114  * keyinfo->tag for quicky skipping DS and RRSIG records that can't
115  * match.
116  */
117 typedef struct keyinfo {
118 	dns_rdata_t rdata;
119 	dst_key_t *dst;
120 	dns_secalg_t algo;
121 	dns_keytag_t tag;
122 } keyinfo_t;
123 
124 /* A replaceable function that can generate a DS RRset from some input */
125 typedef isc_result_t
126 ds_maker_func_t(isc_buffer_t *buf, dns_rdata_t *ds, dns_dsdigest_t dt,
127 		dns_rdata_t *crdata);
128 
129 static dns_rdataset_t cdnskey_set = DNS_RDATASET_INIT;
130 static dns_rdataset_t cdnskey_sig = DNS_RDATASET_INIT;
131 static dns_rdataset_t cds_set = DNS_RDATASET_INIT;
132 static dns_rdataset_t cds_sig = DNS_RDATASET_INIT;
133 static dns_rdataset_t dnskey_set = DNS_RDATASET_INIT;
134 static dns_rdataset_t dnskey_sig = DNS_RDATASET_INIT;
135 static dns_rdataset_t old_ds_set = DNS_RDATASET_INIT;
136 static dns_rdataset_t new_ds_set = DNS_RDATASET_INIT;
137 
138 static keyinfo_t *old_key_tbl = NULL, *new_key_tbl = NULL;
139 
140 isc_buffer_t *new_ds_buf = NULL; /* backing store for new_ds_set */
141 
142 static dns_db_t *child_db = NULL;
143 static dns_dbnode_t *child_node = NULL;
144 static dns_db_t *parent_db = NULL;
145 static dns_dbnode_t *parent_node = NULL;
146 static dns_db_t *update_db = NULL;
147 static dns_dbnode_t *update_node = NULL;
148 static dns_dbversion_t *update_version = NULL;
149 static bool cleanup_dst = false;
150 static bool print_mem_stats = false;
151 
152 static void
153 verbose_time(int level, const char *msg, isc_stdtime_t time) {
154 	isc_result_t result;
155 	isc_buffer_t timebuf;
156 	char timestr[32];
157 
158 	if (verbose < level) {
159 		return;
160 	}
161 
162 	isc_buffer_init(&timebuf, timestr, sizeof(timestr));
163 	result = dns_time64_totext(time, &timebuf);
164 	check_result(result, "dns_time64_totext()");
165 	isc_buffer_putuint8(&timebuf, 0);
166 	if (verbose < 3) {
167 		vbprintf(level, "%s %s\n", msg, timestr);
168 	} else {
169 		vbprintf(level, "%s %s (%" PRIu32 ")\n", msg, timestr, time);
170 	}
171 }
172 
173 static void
174 initname(char *setname) {
175 	isc_result_t result;
176 	isc_buffer_t buf;
177 
178 	name = dns_fixedname_initname(&fixed);
179 	namestr = setname;
180 
181 	isc_buffer_init(&buf, setname, strlen(setname));
182 	isc_buffer_add(&buf, strlen(setname));
183 	result = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL);
184 	if (result != ISC_R_SUCCESS) {
185 		fatal("could not initialize name %s", setname);
186 	}
187 }
188 
189 static void
190 findset(dns_db_t *db, dns_dbnode_t *node, dns_rdatatype_t type,
191 	dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
192 	isc_result_t result;
193 
194 	dns_rdataset_init(rdataset);
195 	if (sigrdataset != NULL) {
196 		dns_rdataset_init(sigrdataset);
197 	}
198 	result = dns_db_findrdataset(db, node, NULL, type, 0, 0, rdataset,
199 				     sigrdataset);
200 	if (result != ISC_R_NOTFOUND) {
201 		check_result(result, "dns_db_findrdataset()");
202 	}
203 }
204 
205 static void
206 freeset(dns_rdataset_t *rdataset) {
207 	if (dns_rdataset_isassociated(rdataset)) {
208 		dns_rdataset_disassociate(rdataset);
209 	}
210 }
211 
212 static void
213 freelist(dns_rdataset_t *rdataset) {
214 	dns_rdatalist_t *rdlist;
215 	dns_rdata_t *rdata;
216 
217 	if (!dns_rdataset_isassociated(rdataset)) {
218 		return;
219 	}
220 
221 	dns_rdatalist_fromrdataset(rdataset, &rdlist);
222 
223 	for (rdata = ISC_LIST_HEAD(rdlist->rdata); rdata != NULL;
224 	     rdata = ISC_LIST_HEAD(rdlist->rdata))
225 	{
226 		ISC_LIST_UNLINK(rdlist->rdata, rdata, link);
227 		isc_mem_put(mctx, rdata, sizeof(*rdata));
228 	}
229 	isc_mem_put(mctx, rdlist, sizeof(*rdlist));
230 	dns_rdataset_disassociate(rdataset);
231 }
232 
233 static void
234 free_all_sets(void) {
235 	freeset(&cdnskey_set);
236 	freeset(&cdnskey_sig);
237 	freeset(&cds_set);
238 	freeset(&cds_sig);
239 	freeset(&dnskey_set);
240 	freeset(&dnskey_sig);
241 	freeset(&old_ds_set);
242 	freelist(&new_ds_set);
243 	if (new_ds_buf != NULL) {
244 		isc_buffer_free(&new_ds_buf);
245 	}
246 }
247 
248 static void
249 load_db(const char *filename, dns_db_t **dbp, dns_dbnode_t **nodep) {
250 	isc_result_t result;
251 
252 	result = dns_db_create(mctx, ZONEDB_DEFAULT, name, dns_dbtype_zone,
253 			       rdclass, 0, NULL, dbp);
254 	check_result(result, "dns_db_create()");
255 
256 	result = dns_db_load(*dbp, filename, dns_masterformat_text,
257 			     DNS_MASTER_HINT);
258 	if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) {
259 		fatal("can't load %s: %s", filename, isc_result_totext(result));
260 	}
261 
262 	result = dns_db_findnode(*dbp, name, false, nodep);
263 	if (result != ISC_R_SUCCESS) {
264 		fatal("can't find %s node in %s", namestr, filename);
265 	}
266 }
267 
268 static void
269 free_db(dns_db_t **dbp, dns_dbnode_t **nodep, dns_dbversion_t **versionp) {
270 	if (*dbp != NULL) {
271 		if (*nodep != NULL) {
272 			dns_db_detachnode(*dbp, nodep);
273 		}
274 		if (versionp != NULL && *versionp != NULL) {
275 			dns_db_closeversion(*dbp, versionp, false);
276 		}
277 		dns_db_detach(dbp);
278 	}
279 }
280 
281 static void
282 load_child_sets(const char *file) {
283 	load_db(file, &child_db, &child_node);
284 	findset(child_db, child_node, dns_rdatatype_dnskey, &dnskey_set,
285 		&dnskey_sig);
286 	findset(child_db, child_node, dns_rdatatype_cdnskey, &cdnskey_set,
287 		&cdnskey_sig);
288 	findset(child_db, child_node, dns_rdatatype_cds, &cds_set, &cds_sig);
289 	free_db(&child_db, &child_node, NULL);
290 }
291 
292 static void
293 get_dsset_name(char *filename, size_t size, const char *path,
294 	       const char *suffix) {
295 	isc_result_t result;
296 	isc_buffer_t buf;
297 	size_t len;
298 
299 	isc_buffer_init(&buf, filename, size);
300 
301 	len = strlen(path);
302 
303 	/* allow room for a trailing slash */
304 	if (isc_buffer_availablelength(&buf) <= len) {
305 		fatal("%s: pathname too long", path);
306 	}
307 	isc_buffer_putstr(&buf, path);
308 
309 	if (isc_file_isdirectory(path) == ISC_R_SUCCESS) {
310 		const char *prefix = "dsset-";
311 
312 		if (path[len - 1] != '/') {
313 			isc_buffer_putstr(&buf, "/");
314 		}
315 
316 		if (isc_buffer_availablelength(&buf) < strlen(prefix)) {
317 			fatal("%s: pathname too long", path);
318 		}
319 		isc_buffer_putstr(&buf, prefix);
320 
321 		result = dns_name_tofilenametext(name, false, &buf);
322 		check_result(result, "dns_name_tofilenametext()");
323 		if (isc_buffer_availablelength(&buf) == 0) {
324 			fatal("%s: pathname too long", path);
325 		}
326 	}
327 	/* allow room for a trailing nul */
328 	if (isc_buffer_availablelength(&buf) <= strlen(suffix)) {
329 		fatal("%s: pathname too long", path);
330 	}
331 	isc_buffer_putstr(&buf, suffix);
332 	isc_buffer_putuint8(&buf, 0);
333 }
334 
335 static void
336 load_parent_set(const char *path) {
337 	isc_result_t result;
338 	isc_time_t modtime;
339 	char filename[PATH_MAX + 1];
340 
341 	get_dsset_name(filename, sizeof(filename), path, "");
342 
343 	result = isc_file_getmodtime(filename, &modtime);
344 	if (result != ISC_R_SUCCESS) {
345 		fatal("could not get modification time of %s: %s", filename,
346 		      isc_result_totext(result));
347 	}
348 	notbefore = isc_time_seconds(&modtime);
349 	if (startstr != NULL) {
350 		isc_stdtime_t now = isc_stdtime_now();
351 		notbefore = strtotime(startstr, now, notbefore, NULL);
352 	}
353 	verbose_time(1, "child records must not be signed before", notbefore);
354 
355 	load_db(filename, &parent_db, &parent_node);
356 	findset(parent_db, parent_node, dns_rdatatype_ds, &old_ds_set, NULL);
357 
358 	if (!dns_rdataset_isassociated(&old_ds_set)) {
359 		fatal("could not find DS records for %s in %s", namestr,
360 		      filename);
361 	}
362 
363 	free_db(&parent_db, &parent_node, NULL);
364 }
365 
366 #define MAX_CDS_RDATA_TEXT_SIZE DNS_RDATA_MAXLENGTH * 2
367 
368 static isc_buffer_t *
369 formatset(dns_rdataset_t *rdataset) {
370 	isc_result_t result;
371 	isc_buffer_t *buf = NULL;
372 	dns_master_style_t *style = NULL;
373 	unsigned int styleflags;
374 
375 	styleflags = (rdataset->ttl == 0) ? DNS_STYLEFLAG_NO_TTL : 0;
376 
377 	/*
378 	 * This style is for consistency with the output of dnssec-dsfromkey
379 	 * which just separates fields with spaces. The huge tab stop width
380 	 * eliminates any tab characters.
381 	 */
382 	result = dns_master_stylecreate(&style, styleflags, 0, 0, 0, 0, 0,
383 					1000000, 0, mctx);
384 	check_result(result, "dns_master_stylecreate2 failed");
385 
386 	isc_buffer_allocate(mctx, &buf, MAX_CDS_RDATA_TEXT_SIZE);
387 	result = dns_master_rdatasettotext(name, rdataset, style, NULL, buf);
388 	dns_master_styledestroy(&style, mctx);
389 
390 	if ((result == ISC_R_SUCCESS) && isc_buffer_availablelength(buf) < 1) {
391 		result = ISC_R_NOSPACE;
392 	}
393 
394 	if (result != ISC_R_SUCCESS) {
395 		isc_buffer_free(&buf);
396 		check_result(result, "dns_rdataset_totext()");
397 	}
398 
399 	isc_buffer_putuint8(buf, 0);
400 	return buf;
401 }
402 
403 static void
404 write_parent_set(const char *path, const char *inplace, bool nsupdate,
405 		 dns_rdataset_t *rdataset) {
406 	isc_result_t result;
407 	isc_buffer_t *buf = NULL;
408 	isc_region_t r;
409 	isc_time_t filetime;
410 	char backname[PATH_MAX + 1];
411 	char filename[PATH_MAX + 1];
412 	char tmpname[PATH_MAX + 1];
413 	FILE *fp = NULL;
414 
415 	if (nsupdate && inplace == NULL) {
416 		return;
417 	}
418 
419 	buf = formatset(rdataset);
420 	isc_buffer_usedregion(buf, &r);
421 
422 	/*
423 	 * Try to ensure a write error doesn't make a zone go insecure!
424 	 */
425 	if (inplace == NULL) {
426 		printf("%s", (char *)r.base);
427 		isc_buffer_free(&buf);
428 		if (fflush(stdout) == EOF) {
429 			fatal("error writing to stdout: %s", strerror(errno));
430 		}
431 		return;
432 	}
433 
434 	if (inplace[0] != '\0') {
435 		get_dsset_name(backname, sizeof(backname), path, inplace);
436 	}
437 	get_dsset_name(filename, sizeof(filename), path, "");
438 	get_dsset_name(tmpname, sizeof(tmpname), path, "-XXXXXXXXXX");
439 
440 	result = isc_file_openunique(tmpname, &fp);
441 	if (result != ISC_R_SUCCESS) {
442 		isc_buffer_free(&buf);
443 		fatal("open %s: %s", tmpname, isc_result_totext(result));
444 	}
445 	fprintf(fp, "%s", (char *)r.base);
446 	isc_buffer_free(&buf);
447 	if (fclose(fp) == EOF) {
448 		int err = errno;
449 		isc_file_remove(tmpname);
450 		fatal("error writing to %s: %s", tmpname, strerror(err));
451 	}
452 
453 	isc_time_set(&filetime, oldestsig.timesigned, 0);
454 	result = isc_file_settime(tmpname, &filetime);
455 	if (result != ISC_R_SUCCESS) {
456 		isc_file_remove(tmpname);
457 		fatal("can't set modification time of %s: %s", tmpname,
458 		      isc_result_totext(result));
459 	}
460 
461 	if (inplace[0] != '\0') {
462 		isc_file_rename(filename, backname);
463 	}
464 	isc_file_rename(tmpname, filename);
465 }
466 
467 typedef enum { LOOSE, TIGHT } strictness_t;
468 
469 /*
470  * Find out if any (C)DS record matches a particular (C)DNSKEY.
471  */
472 static bool
473 match_key_dsset(keyinfo_t *ki, dns_rdataset_t *dsset, strictness_t strictness) {
474 	isc_result_t result;
475 	unsigned char dsbuf[DNS_DS_BUFFERSIZE];
476 
477 	for (result = dns_rdataset_first(dsset); result == ISC_R_SUCCESS;
478 	     result = dns_rdataset_next(dsset))
479 	{
480 		dns_rdata_ds_t ds;
481 		dns_rdata_t dsrdata = DNS_RDATA_INIT;
482 		dns_rdata_t newdsrdata = DNS_RDATA_INIT;
483 		bool c;
484 
485 		dns_rdataset_current(dsset, &dsrdata);
486 		result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
487 		check_result(result, "dns_rdata_tostruct(DS)");
488 
489 		if (ki->tag != ds.key_tag || ki->algo != ds.algorithm) {
490 			continue;
491 		}
492 
493 		result = dns_ds_buildrdata(name, &ki->rdata, ds.digest_type,
494 					   dsbuf, &newdsrdata);
495 		if (result != ISC_R_SUCCESS) {
496 			vbprintf(3,
497 				 "dns_ds_buildrdata("
498 				 "keytag=%d, algo=%d, digest=%d): %s\n",
499 				 ds.key_tag, ds.algorithm, ds.digest_type,
500 				 isc_result_totext(result));
501 			continue;
502 		}
503 		/* allow for both DS and CDS */
504 		c = dsrdata.type != dns_rdatatype_ds;
505 		dsrdata.type = dns_rdatatype_ds;
506 		if (dns_rdata_compare(&dsrdata, &newdsrdata) == 0) {
507 			vbprintf(1, "found matching %s %d %d %d\n",
508 				 c ? "CDS" : "DS", ds.key_tag, ds.algorithm,
509 				 ds.digest_type);
510 			return true;
511 		} else if (strictness == TIGHT) {
512 			vbprintf(0,
513 				 "key does not match %s %d %d %d "
514 				 "when it looks like it should\n",
515 				 c ? "CDS" : "DS", ds.key_tag, ds.algorithm,
516 				 ds.digest_type);
517 			return false;
518 		}
519 	}
520 
521 	vbprintf(1, "no matching %s for %s %d %d\n",
522 		 dsset->type == dns_rdatatype_cds ? "CDS" : "DS",
523 		 ki->rdata.type == dns_rdatatype_cdnskey ? "CDNSKEY" : "DNSKEY",
524 		 ki->tag, ki->algo);
525 
526 	return false;
527 }
528 
529 /*
530  * Find which (C)DNSKEY records match a (C)DS RRset.
531  * This creates a keyinfo_t key_tbl[nkey] array.
532  */
533 static keyinfo_t *
534 match_keyset_dsset(dns_rdataset_t *keyset, dns_rdataset_t *dsset,
535 		   strictness_t strictness) {
536 	isc_result_t result;
537 	keyinfo_t *keytable, *ki;
538 	int i;
539 
540 	nkey = dns_rdataset_count(keyset);
541 
542 	keytable = isc_mem_cget(mctx, nkey, sizeof(keytable[0]));
543 
544 	for (result = dns_rdataset_first(keyset), i = 0, ki = keytable;
545 	     result == ISC_R_SUCCESS;
546 	     result = dns_rdataset_next(keyset), i++, ki++)
547 	{
548 		dns_rdata_dnskey_t dnskey;
549 		dns_rdata_t *keyrdata;
550 		isc_region_t r;
551 
552 		INSIST(i < nkey);
553 		keyrdata = &ki->rdata;
554 
555 		dns_rdata_init(keyrdata);
556 		dns_rdataset_current(keyset, keyrdata);
557 
558 		result = dns_rdata_tostruct(keyrdata, &dnskey, NULL);
559 		check_result(result, "dns_rdata_tostruct(DNSKEY)");
560 		ki->algo = dnskey.algorithm;
561 
562 		dns_rdata_toregion(keyrdata, &r);
563 		ki->tag = dst_region_computeid(&r);
564 
565 		ki->dst = NULL;
566 		if (!match_key_dsset(ki, dsset, strictness)) {
567 			continue;
568 		}
569 
570 		result = dns_dnssec_keyfromrdata(name, keyrdata, mctx,
571 						 &ki->dst);
572 		if (result != ISC_R_SUCCESS) {
573 			vbprintf(3,
574 				 "dns_dnssec_keyfromrdata("
575 				 "keytag=%d, algo=%d): %s\n",
576 				 ki->tag, ki->algo, isc_result_totext(result));
577 		}
578 	}
579 
580 	return keytable;
581 }
582 
583 static void
584 free_keytable(keyinfo_t **keytable_p) {
585 	keyinfo_t *keytable = *keytable_p;
586 	*keytable_p = NULL;
587 	keyinfo_t *ki;
588 	int i;
589 
590 	REQUIRE(keytable != NULL);
591 
592 	for (i = 0, ki = keytable; i < nkey; i++, ki++) {
593 		if (ki->dst != NULL) {
594 			dst_key_free(&ki->dst);
595 		}
596 	}
597 
598 	isc_mem_cput(mctx, keytable, nkey, sizeof(keytable[0]));
599 }
600 
601 /*
602  * Find out which keys have signed an RRset. Keys that do not match a
603  * DS record are skipped.
604  *
605  * The return value is an array with nkey elements, one for each key,
606  * either zero if the key was skipped or did not sign the RRset, or
607  * otherwise the key algorithm. This is used by the signature coverage
608  * check functions below.
609  */
610 static dns_secalg_t *
611 matching_sigs(keyinfo_t *keytbl, dns_rdataset_t *rdataset,
612 	      dns_rdataset_t *sigset) {
613 	isc_result_t result;
614 	dns_secalg_t *algo;
615 	int i;
616 
617 	REQUIRE(keytbl != NULL);
618 
619 	algo = isc_mem_cget(mctx, nkey, sizeof(algo[0]));
620 
621 	for (result = dns_rdataset_first(sigset); result == ISC_R_SUCCESS;
622 	     result = dns_rdataset_next(sigset))
623 	{
624 		dns_rdata_t sigrdata = DNS_RDATA_INIT;
625 		dns_rdata_rrsig_t sig;
626 
627 		dns_rdataset_current(sigset, &sigrdata);
628 		result = dns_rdata_tostruct(&sigrdata, &sig, NULL);
629 		check_result(result, "dns_rdata_tostruct(RRSIG)");
630 
631 		/*
632 		 * Replay attack protection: check against current age limit
633 		 */
634 		if (isc_serial_lt(sig.timesigned, notbefore)) {
635 			vbprintf(1, "skip RRSIG by key %d: too old\n",
636 				 sig.keyid);
637 			continue;
638 		}
639 
640 		for (i = 0; i < nkey; i++) {
641 			keyinfo_t *ki = &keytbl[i];
642 			if (sig.keyid != ki->tag || sig.algorithm != ki->algo ||
643 			    !dns_name_equal(&sig.signer, name))
644 			{
645 				continue;
646 			}
647 			if (ki->dst == NULL) {
648 				vbprintf(1,
649 					 "skip RRSIG by key %d:"
650 					 " no matching (C)DS\n",
651 					 sig.keyid);
652 				continue;
653 			}
654 
655 			result = dns_dnssec_verify(name, rdataset, ki->dst,
656 						   false, 0, mctx, &sigrdata,
657 						   NULL);
658 
659 			if (result != ISC_R_SUCCESS &&
660 			    result != DNS_R_FROMWILDCARD)
661 			{
662 				vbprintf(1,
663 					 "skip RRSIG by key %d:"
664 					 " verification failed: %s\n",
665 					 sig.keyid, isc_result_totext(result));
666 				continue;
667 			}
668 
669 			vbprintf(1, "found RRSIG by key %d\n", ki->tag);
670 			algo[i] = sig.algorithm;
671 
672 			/*
673 			 * Replay attack protection: work out next age limit,
674 			 * only after the signature has been verified
675 			 */
676 			if (oldestsig.timesigned == 0 ||
677 			    isc_serial_lt(sig.timesigned, oldestsig.timesigned))
678 			{
679 				verbose_time(2, "this is the oldest so far",
680 					     sig.timesigned);
681 				oldestsig = sig;
682 			}
683 		}
684 	}
685 
686 	return algo;
687 }
688 
689 /*
690  * Consume the result of matching_sigs(). When checking records
691  * fetched from the child zone, any working signature is enough.
692  */
693 static bool
694 signed_loose(dns_secalg_t *algo) {
695 	bool ok = false;
696 	int i;
697 	for (i = 0; i < nkey; i++) {
698 		if (algo[i] != 0) {
699 			ok = true;
700 		}
701 	}
702 	isc_mem_cput(mctx, algo, nkey, sizeof(algo[0]));
703 	return ok;
704 }
705 
706 /*
707  * Consume the result of matching_sigs(). To ensure that the new DS
708  * RRset does not break the chain of trust to the DNSKEY RRset, every
709  * key algorithm in the DS RRset must have a signature in the DNSKEY
710  * RRset.
711  */
712 static bool
713 signed_strict(dns_rdataset_t *dsset, dns_secalg_t *algo) {
714 	isc_result_t result;
715 	bool all_ok = true;
716 
717 	for (result = dns_rdataset_first(dsset); result == ISC_R_SUCCESS;
718 	     result = dns_rdataset_next(dsset))
719 	{
720 		dns_rdata_t dsrdata = DNS_RDATA_INIT;
721 		dns_rdata_ds_t ds;
722 		bool ds_ok;
723 		int i;
724 
725 		dns_rdataset_current(dsset, &dsrdata);
726 		result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
727 		check_result(result, "dns_rdata_tostruct(DS)");
728 
729 		ds_ok = false;
730 		for (i = 0; i < nkey; i++) {
731 			if (algo[i] == ds.algorithm) {
732 				ds_ok = true;
733 			}
734 		}
735 		if (!ds_ok) {
736 			vbprintf(0,
737 				 "missing signature for algorithm %d "
738 				 "(key %d)\n",
739 				 ds.algorithm, ds.key_tag);
740 			all_ok = false;
741 		}
742 	}
743 
744 	isc_mem_cput(mctx, algo, nkey, sizeof(algo[0]));
745 	return all_ok;
746 }
747 
748 /*
749  * This basically copies the rdata into the buffer, but going via the
750  * unpacked struct lets us change the rdatatype. (The dns_rdata_cds_t
751  * and dns_rdata_ds_t types are aliases.)
752  */
753 static isc_result_t
754 ds_from_cds(isc_buffer_t *buf, dns_rdata_t *rds, dns_dsdigest_t dt,
755 	    dns_rdata_t *cds) {
756 	isc_result_t result;
757 	dns_rdata_ds_t ds;
758 
759 	REQUIRE(buf != NULL);
760 
761 	result = dns_rdata_tostruct(cds, &ds, NULL);
762 	check_result(result, "dns_rdata_tostruct(CDS)");
763 	ds.common.rdtype = dns_rdatatype_ds;
764 
765 	if (ds.digest_type != dt) {
766 		return ISC_R_IGNORE;
767 	}
768 
769 	return dns_rdata_fromstruct(rds, rdclass, dns_rdatatype_ds, &ds, buf);
770 }
771 
772 static isc_result_t
773 ds_from_cdnskey(isc_buffer_t *buf, dns_rdata_t *ds, dns_dsdigest_t dt,
774 		dns_rdata_t *cdnskey) {
775 	isc_result_t result;
776 	isc_region_t r;
777 
778 	REQUIRE(buf != NULL);
779 
780 	isc_buffer_availableregion(buf, &r);
781 	if (r.length < DNS_DS_BUFFERSIZE) {
782 		return ISC_R_NOSPACE;
783 	}
784 
785 	result = dns_ds_buildrdata(name, cdnskey, dt, r.base, ds);
786 	if (result == ISC_R_SUCCESS) {
787 		isc_buffer_add(buf, DNS_DS_BUFFERSIZE);
788 	}
789 
790 	return result;
791 }
792 
793 static isc_result_t
794 append_new_ds_set(ds_maker_func_t *ds_from_rdata, isc_buffer_t *buf,
795 		  dns_rdatalist_t *dslist, dns_dsdigest_t dt,
796 		  dns_rdataset_t *crdset) {
797 	isc_result_t result;
798 
799 	for (result = dns_rdataset_first(crdset); result == ISC_R_SUCCESS;
800 	     result = dns_rdataset_next(crdset))
801 	{
802 		dns_rdata_t crdata = DNS_RDATA_INIT;
803 		dns_rdata_t *ds = NULL;
804 
805 		dns_rdataset_current(crdset, &crdata);
806 
807 		ds = isc_mem_get(mctx, sizeof(*ds));
808 		dns_rdata_init(ds);
809 
810 		result = ds_from_rdata(buf, ds, dt, &crdata);
811 
812 		switch (result) {
813 		case ISC_R_SUCCESS:
814 			ISC_LIST_APPEND(dslist->rdata, ds, link);
815 			break;
816 		case ISC_R_IGNORE:
817 			isc_mem_put(mctx, ds, sizeof(*ds));
818 			continue;
819 		case ISC_R_NOSPACE:
820 			isc_mem_put(mctx, ds, sizeof(*ds));
821 			return result;
822 		default:
823 			isc_mem_put(mctx, ds, sizeof(*ds));
824 			check_result(result, "ds_from_rdata()");
825 		}
826 	}
827 
828 	return ISC_R_SUCCESS;
829 }
830 
831 static void
832 make_new_ds_set(ds_maker_func_t *ds_from_rdata, uint32_t ttl,
833 		dns_rdataset_t *crdset) {
834 	unsigned int size = 16;
835 
836 	for (;;) {
837 		isc_result_t result = ISC_R_SUCCESS;
838 		dns_rdatalist_t *dslist = NULL;
839 		size_t n;
840 
841 		dslist = isc_mem_get(mctx, sizeof(*dslist));
842 		dns_rdatalist_init(dslist);
843 		dslist->rdclass = rdclass;
844 		dslist->type = dns_rdatatype_ds;
845 		dslist->ttl = ttl;
846 
847 		dns_rdataset_init(&new_ds_set);
848 		dns_rdatalist_tordataset(dslist, &new_ds_set);
849 
850 		isc_buffer_allocate(mctx, &new_ds_buf, size);
851 
852 		n = sizeof(dtype) / sizeof(dtype[0]);
853 		for (size_t i = 0; i < n && dtype[i] != 0; i++) {
854 			result = append_new_ds_set(ds_from_rdata, new_ds_buf,
855 						   dslist, dtype[i], crdset);
856 			if (result != ISC_R_SUCCESS) {
857 				break;
858 			}
859 		}
860 		if (result == ISC_R_SUCCESS) {
861 			return;
862 		}
863 
864 		vbprintf(2, "doubling DS list buffer size from %u\n", size);
865 		freelist(&new_ds_set);
866 		isc_buffer_free(&new_ds_buf);
867 		size *= 2;
868 	}
869 }
870 
871 static int
872 rdata_cmp(const void *rdata1, const void *rdata2) {
873 	return dns_rdata_compare((const dns_rdata_t *)rdata1,
874 				 (const dns_rdata_t *)rdata2);
875 }
876 
877 /*
878  * Ensure that every key identified by the DS RRset has the same set of
879  * digest types.
880  */
881 static bool
882 consistent_digests(dns_rdataset_t *dsset) {
883 	isc_result_t result;
884 	dns_rdata_t *arrdata;
885 	dns_rdata_ds_t *ds;
886 	dns_keytag_t key_tag;
887 	dns_secalg_t algorithm;
888 	bool match;
889 	int i, j, n, d;
890 
891 	/*
892 	 * First sort the dsset. DS rdata fields are tag, algorithm,
893 	 * digest, so sorting them brings together all the records for
894 	 * each key.
895 	 */
896 
897 	n = dns_rdataset_count(dsset);
898 
899 	arrdata = isc_mem_cget(mctx, n, sizeof(dns_rdata_t));
900 
901 	for (result = dns_rdataset_first(dsset), i = 0; result == ISC_R_SUCCESS;
902 	     result = dns_rdataset_next(dsset), i++)
903 	{
904 		dns_rdata_init(&arrdata[i]);
905 		dns_rdataset_current(dsset, &arrdata[i]);
906 	}
907 
908 	qsort(arrdata, n, sizeof(dns_rdata_t), rdata_cmp);
909 
910 	/*
911 	 * Convert sorted arrdata to more accessible format
912 	 */
913 	ds = isc_mem_cget(mctx, n, sizeof(dns_rdata_ds_t));
914 
915 	for (i = 0; i < n; i++) {
916 		result = dns_rdata_tostruct(&arrdata[i], &ds[i], NULL);
917 		check_result(result, "dns_rdata_tostruct(DS)");
918 	}
919 
920 	/*
921 	 * Count number of digest types (d) for first key
922 	 */
923 	key_tag = ds[0].key_tag;
924 	algorithm = ds[0].algorithm;
925 	for (d = 0, i = 0; i < n; i++, d++) {
926 		if (ds[i].key_tag != key_tag || ds[i].algorithm != algorithm) {
927 			break;
928 		}
929 	}
930 
931 	/*
932 	 * Check subsequent keys match the first one
933 	 */
934 	match = true;
935 	while (i < n) {
936 		key_tag = ds[i].key_tag;
937 		algorithm = ds[i].algorithm;
938 		for (j = 0; j < d && i + j < n; j++) {
939 			if (ds[i + j].key_tag != key_tag ||
940 			    ds[i + j].algorithm != algorithm ||
941 			    ds[i + j].digest_type != ds[j].digest_type)
942 			{
943 				match = false;
944 			}
945 		}
946 		i += d;
947 	}
948 
949 	/*
950 	 * Done!
951 	 */
952 	isc_mem_cput(mctx, ds, n, sizeof(dns_rdata_ds_t));
953 	isc_mem_cput(mctx, arrdata, n, sizeof(dns_rdata_t));
954 
955 	return match;
956 }
957 
958 static void
959 print_diff(const char *cmd, dns_rdataset_t *rdataset) {
960 	isc_buffer_t *buf;
961 	isc_region_t r;
962 	unsigned char *nl;
963 	size_t len;
964 
965 	buf = formatset(rdataset);
966 	isc_buffer_usedregion(buf, &r);
967 
968 	while ((nl = memchr(r.base, '\n', r.length)) != NULL) {
969 		len = nl - r.base + 1;
970 		printf("update %s %.*s", cmd, (int)len, (char *)r.base);
971 		isc_region_consume(&r, len);
972 	}
973 
974 	isc_buffer_free(&buf);
975 }
976 
977 static void
978 update_diff(const char *cmd, uint32_t ttl, dns_rdataset_t *addset,
979 	    dns_rdataset_t *delset) {
980 	isc_result_t result;
981 	dns_rdataset_t diffset;
982 	uint32_t save;
983 
984 	result = dns_db_create(mctx, ZONEDB_DEFAULT, name, dns_dbtype_zone,
985 			       rdclass, 0, NULL, &update_db);
986 	check_result(result, "dns_db_create()");
987 
988 	result = dns_db_newversion(update_db, &update_version);
989 	check_result(result, "dns_db_newversion()");
990 
991 	result = dns_db_findnode(update_db, name, true, &update_node);
992 	check_result(result, "dns_db_findnode()");
993 
994 	dns_rdataset_init(&diffset);
995 
996 	result = dns_db_addrdataset(update_db, update_node, update_version, 0,
997 				    addset, DNS_DBADD_MERGE, NULL);
998 	check_result(result, "dns_db_addrdataset()");
999 
1000 	result = dns_db_subtractrdataset(update_db, update_node, update_version,
1001 					 delset, 0, &diffset);
1002 	if (result == DNS_R_UNCHANGED) {
1003 		save = addset->ttl;
1004 		addset->ttl = ttl;
1005 		print_diff(cmd, addset);
1006 		addset->ttl = save;
1007 	} else if (result != DNS_R_NXRRSET) {
1008 		check_result(result, "dns_db_subtractrdataset()");
1009 		diffset.ttl = ttl;
1010 		print_diff(cmd, &diffset);
1011 		dns_rdataset_disassociate(&diffset);
1012 	}
1013 
1014 	free_db(&update_db, &update_node, &update_version);
1015 }
1016 
1017 static void
1018 nsdiff(uint32_t ttl, dns_rdataset_t *oldset, dns_rdataset_t *newset) {
1019 	if (ttl == 0) {
1020 		vbprintf(1, "warning: no TTL in nsupdate script\n");
1021 	}
1022 	update_diff("add", ttl, newset, oldset);
1023 	update_diff("del", 0, oldset, newset);
1024 	if (verbose > 0) {
1025 		printf("show\nsend\nanswer\n");
1026 	} else {
1027 		printf("send\n");
1028 	}
1029 	if (fflush(stdout) == EOF) {
1030 		fatal("write stdout: %s", strerror(errno));
1031 	}
1032 }
1033 
1034 noreturn static void
1035 usage(void);
1036 
1037 static void
1038 usage(void) {
1039 	fprintf(stderr, "Usage:\n");
1040 	fprintf(stderr,
1041 		"    %s options [options] -f <file> -d <path> <domain>\n",
1042 		program);
1043 	fprintf(stderr, "Version: %s\n", PACKAGE_VERSION);
1044 	fprintf(stderr, "Options:\n"
1045 			"    -a <algorithm>     digest algorithm (SHA-1 / "
1046 			"SHA-256 / SHA-384)\n"
1047 			"    -c <class>         of domain (default IN)\n"
1048 			"    -D                 prefer CDNSKEY records instead "
1049 			"of CDS\n"
1050 			"    -d <file|dir>      where to find parent dsset- "
1051 			"file\n"
1052 			"    -f <file>          child DNSKEY+CDNSKEY+CDS+RRSIG "
1053 			"records\n"
1054 			"    -i[extension]      update dsset- file in place\n"
1055 			"    -s <start-time>    oldest permitted child "
1056 			"signatures\n"
1057 			"    -u                 emit nsupdate script\n"
1058 			"    -T <ttl>           TTL of DS records\n"
1059 			"    -V                 print version\n"
1060 			"    -v <verbosity>\n");
1061 	exit(EXIT_FAILURE);
1062 }
1063 
1064 static void
1065 cleanup(void) {
1066 	free_db(&child_db, &child_node, NULL);
1067 	free_db(&parent_db, &parent_node, NULL);
1068 	free_db(&update_db, &update_node, &update_version);
1069 	if (old_key_tbl != NULL) {
1070 		free_keytable(&old_key_tbl);
1071 	}
1072 	if (new_key_tbl != NULL) {
1073 		free_keytable(&new_key_tbl);
1074 	}
1075 	free_all_sets();
1076 	if (lctx != NULL) {
1077 		cleanup_logging(&lctx);
1078 	}
1079 	if (cleanup_dst) {
1080 		dst_lib_destroy();
1081 	}
1082 	if (mctx != NULL) {
1083 		if (print_mem_stats && verbose > 10) {
1084 			isc_mem_stats(mctx, stdout);
1085 		}
1086 		isc_mem_destroy(&mctx);
1087 	}
1088 }
1089 
1090 int
1091 main(int argc, char *argv[]) {
1092 	const char *child_path = NULL;
1093 	const char *ds_path = NULL;
1094 	const char *inplace = NULL;
1095 	isc_result_t result;
1096 	bool prefer_cdnskey = false;
1097 	bool nsupdate = false;
1098 	uint32_t ttl = 0;
1099 	int ch;
1100 	char *endp;
1101 
1102 	setfatalcallback(cleanup);
1103 
1104 	isc_mem_create(&mctx);
1105 
1106 	isc_commandline_errprint = false;
1107 
1108 #define OPTIONS "a:c:Dd:f:i:ms:T:uv:V"
1109 	while ((ch = isc_commandline_parse(argc, argv, OPTIONS)) != -1) {
1110 		switch (ch) {
1111 		case 'a':
1112 			add_dtype(strtodsdigest(isc_commandline_argument));
1113 			break;
1114 		case 'c':
1115 			rdclass = strtoclass(isc_commandline_argument);
1116 			break;
1117 		case 'D':
1118 			prefer_cdnskey = true;
1119 			break;
1120 		case 'd':
1121 			ds_path = isc_commandline_argument;
1122 			break;
1123 		case 'f':
1124 			child_path = isc_commandline_argument;
1125 			break;
1126 		case 'i':
1127 			/*
1128 			 * This is a bodge to make the argument
1129 			 * optional, so that it works just like sed(1).
1130 			 */
1131 			if (isc_commandline_argument ==
1132 			    argv[isc_commandline_index - 1])
1133 			{
1134 				isc_commandline_index--;
1135 				inplace = "";
1136 			} else {
1137 				inplace = isc_commandline_argument;
1138 			}
1139 			break;
1140 		case 'm':
1141 			isc_mem_debugging = ISC_MEM_DEBUGTRACE |
1142 					    ISC_MEM_DEBUGRECORD;
1143 			break;
1144 		case 's':
1145 			startstr = isc_commandline_argument;
1146 			break;
1147 		case 'T':
1148 			ttl = strtottl(isc_commandline_argument);
1149 			break;
1150 		case 'u':
1151 			nsupdate = true;
1152 			break;
1153 		case 'V':
1154 			/* Does not return. */
1155 			version(program);
1156 			break;
1157 		case 'v':
1158 			verbose = strtoul(isc_commandline_argument, &endp, 0);
1159 			if (*endp != '\0') {
1160 				fatal("-v must be followed by a number");
1161 			}
1162 			break;
1163 		default:
1164 			usage();
1165 			break;
1166 		}
1167 	}
1168 	argv += isc_commandline_index;
1169 	argc -= isc_commandline_index;
1170 
1171 	if (argc != 1) {
1172 		usage();
1173 	}
1174 	initname(argv[0]);
1175 
1176 	/*
1177 	 * Default digest type if none specified.
1178 	 */
1179 	if (dtype[0] == 0) {
1180 		dtype[0] = DNS_DSDIGEST_SHA256;
1181 	}
1182 
1183 	setup_logging(mctx, &lctx);
1184 
1185 	result = dst_lib_init(mctx, NULL);
1186 	if (result != ISC_R_SUCCESS) {
1187 		fatal("could not initialize dst: %s",
1188 		      isc_result_totext(result));
1189 	}
1190 	cleanup_dst = true;
1191 
1192 	if (ds_path == NULL) {
1193 		fatal("missing -d DS pathname");
1194 	}
1195 	load_parent_set(ds_path);
1196 
1197 	/*
1198 	 * Preserve the TTL if it wasn't overridden.
1199 	 */
1200 	if (ttl == 0) {
1201 		ttl = old_ds_set.ttl;
1202 	}
1203 
1204 	if (child_path == NULL) {
1205 		fatal("path to file containing child data must be specified");
1206 	}
1207 
1208 	load_child_sets(child_path);
1209 
1210 	/*
1211 	 * Check child records have accompanying RRSIGs and DNSKEYs
1212 	 */
1213 
1214 	if (!dns_rdataset_isassociated(&dnskey_set) ||
1215 	    !dns_rdataset_isassociated(&dnskey_sig))
1216 	{
1217 		fatal("could not find signed DNSKEY RRset for %s", namestr);
1218 	}
1219 
1220 	if (dns_rdataset_isassociated(&cdnskey_set) &&
1221 	    !dns_rdataset_isassociated(&cdnskey_sig))
1222 	{
1223 		fatal("missing RRSIG CDNSKEY records for %s", namestr);
1224 	}
1225 	if (dns_rdataset_isassociated(&cds_set) &&
1226 	    !dns_rdataset_isassociated(&cds_sig))
1227 	{
1228 		fatal("missing RRSIG CDS records for %s", namestr);
1229 	}
1230 
1231 	vbprintf(1, "which child DNSKEY records match parent DS records?\n");
1232 	old_key_tbl = match_keyset_dsset(&dnskey_set, &old_ds_set, LOOSE);
1233 
1234 	/*
1235 	 * We have now identified the keys that are allowed to
1236 	 * authenticate the DNSKEY RRset (RFC 4035 section 5.2 bullet
1237 	 * 2), and CDNSKEY and CDS RRsets (RFC 7344 section 4.1 bullet
1238 	 * 2).
1239 	 */
1240 
1241 	vbprintf(1, "verify DNSKEY signature(s)\n");
1242 	if (!signed_loose(matching_sigs(old_key_tbl, &dnskey_set, &dnskey_sig)))
1243 	{
1244 		fatal("could not validate child DNSKEY RRset for %s", namestr);
1245 	}
1246 
1247 	if (dns_rdataset_isassociated(&cdnskey_set)) {
1248 		vbprintf(1, "verify CDNSKEY signature(s)\n");
1249 		if (!signed_loose(matching_sigs(old_key_tbl, &cdnskey_set,
1250 						&cdnskey_sig)))
1251 		{
1252 			fatal("could not validate child CDNSKEY RRset for %s",
1253 			      namestr);
1254 		}
1255 	}
1256 	if (dns_rdataset_isassociated(&cds_set)) {
1257 		vbprintf(1, "verify CDS signature(s)\n");
1258 		if (!signed_loose(
1259 			    matching_sigs(old_key_tbl, &cds_set, &cds_sig)))
1260 		{
1261 			fatal("could not validate child CDS RRset for %s",
1262 			      namestr);
1263 		}
1264 	}
1265 
1266 	free_keytable(&old_key_tbl);
1267 
1268 	/*
1269 	 * Report the result of the replay attack protection checks
1270 	 * used for the output file timestamp
1271 	 */
1272 	if (oldestsig.timesigned != 0 && verbose > 0) {
1273 		char type[32];
1274 		dns_rdatatype_format(oldestsig.covered, type, sizeof(type));
1275 		verbose_time(1, "child signature inception time",
1276 			     oldestsig.timesigned);
1277 		vbprintf(2, "from RRSIG %s by key %d\n", type, oldestsig.keyid);
1278 	}
1279 
1280 	/*
1281 	 * Successfully do nothing if there's neither CDNSKEY nor CDS
1282 	 * RFC 7344 section 4.1 first paragraph
1283 	 */
1284 	if (!dns_rdataset_isassociated(&cdnskey_set) &&
1285 	    !dns_rdataset_isassociated(&cds_set))
1286 	{
1287 		vbprintf(1, "%s has neither CDS nor CDNSKEY records\n",
1288 			 namestr);
1289 		write_parent_set(ds_path, inplace, nsupdate, &old_ds_set);
1290 		goto cleanup;
1291 	}
1292 
1293 	/*
1294 	 * Make DS records from the CDS or CDNSKEY records
1295 	 * Prefer CDS if present, unless run with -D
1296 	 */
1297 	if (prefer_cdnskey && dns_rdataset_isassociated(&cdnskey_set)) {
1298 		make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set);
1299 	} else if (dns_rdataset_isassociated(&cds_set)) {
1300 		make_new_ds_set(ds_from_cds, ttl, &cds_set);
1301 	} else {
1302 		make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set);
1303 	}
1304 
1305 	/*
1306 	 * Try to use CDNSKEY records if the CDS records are missing
1307 	 * or did not match.
1308 	 */
1309 	if (dns_rdataset_count(&new_ds_set) == 0 &&
1310 	    dns_rdataset_isassociated(&cdnskey_set))
1311 	{
1312 		vbprintf(1, "CDS records have no allowed digest types; "
1313 			    "using CDNSKEY instead\n");
1314 		freelist(&new_ds_set);
1315 		isc_buffer_free(&new_ds_buf);
1316 		make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set);
1317 	}
1318 	if (dns_rdataset_count(&new_ds_set) == 0) {
1319 		fatal("CDS records at %s do not match any -a digest types",
1320 		      namestr);
1321 	}
1322 
1323 	/*
1324 	 * Now we have a candidate DS RRset, we need to check it
1325 	 * won't break the delegation.
1326 	 */
1327 	vbprintf(1, "which child DNSKEY records match new DS records?\n");
1328 	new_key_tbl = match_keyset_dsset(&dnskey_set, &new_ds_set, TIGHT);
1329 
1330 	if (!consistent_digests(&new_ds_set)) {
1331 		fatal("CDS records at %s do not cover each key "
1332 		      "with the same set of digest types",
1333 		      namestr);
1334 	}
1335 
1336 	vbprintf(1, "verify DNSKEY signature(s)\n");
1337 	if (!signed_strict(&new_ds_set, matching_sigs(new_key_tbl, &dnskey_set,
1338 						      &dnskey_sig)))
1339 	{
1340 		fatal("could not validate child DNSKEY RRset "
1341 		      "with new DS records for %s",
1342 		      namestr);
1343 	}
1344 
1345 	free_keytable(&new_key_tbl);
1346 
1347 	/*
1348 	 * OK, it's all good!
1349 	 */
1350 	if (nsupdate) {
1351 		nsdiff(ttl, &old_ds_set, &new_ds_set);
1352 	}
1353 
1354 	write_parent_set(ds_path, inplace, nsupdate, &new_ds_set);
1355 
1356 cleanup:
1357 	print_mem_stats = true;
1358 	cleanup();
1359 
1360 	return 0;
1361 }
1362