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