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