dnssec-cds.c 31.8 KB
Newer Older
Evan Hunt's avatar
Evan Hunt committed
1
/*
2
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
Evan Hunt's avatar
Evan Hunt committed
3 4 5 6
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 8 9
 *
 * See the COPYRIGHT file distributed with this work for additional
 * information regarding copyright ownership.
Evan Hunt's avatar
Evan Hunt committed
10 11 12 13 14 15 16 17 18 19
 */

/*
 * Written by Tony Finch <dot@dotat.at> <fanf2@cam.ac.uk>
 * at Cambridge University Information Services
 */

/*! \file */

#include <errno.h>
20
#include <inttypes.h>
21
#include <stdbool.h>
Evan Hunt's avatar
Evan Hunt committed
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
#include <stdlib.h>

#include <isc/buffer.h>
#include <isc/commandline.h>
#include <isc/file.h>
#include <isc/hash.h>
#include <isc/mem.h>
#include <isc/print.h>
#include <isc/serial.h>
#include <isc/string.h>
#include <isc/time.h>
#include <isc/util.h>

#include <dns/callbacks.h>
#include <dns/db.h>
#include <dns/dbiterator.h>
#include <dns/dnssec.h>
#include <dns/ds.h>
#include <dns/fixedname.h>
#include <dns/keyvalues.h>
#include <dns/log.h>
#include <dns/master.h>
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdatasetiter.h>
#include <dns/rdatatype.h>
#include <dns/result.h>
#include <dns/time.h>

#include <dst/dst.h>

Ondřej Surý's avatar
Ondřej Surý committed
56
#if USE_PKCS11
Evan Hunt's avatar
Evan Hunt committed
57
#include <pk11/result.h>
58
#endif /* if USE_PKCS11 */
Evan Hunt's avatar
Evan Hunt committed
59 60 61 62 63 64 65 66 67 68 69 70 71 72

#include "dnssectool.h"

const char *program = "dnssec-cds";

/*
 * Infrastructure
 */
static isc_log_t *lctx = NULL;
static isc_mem_t *mctx = NULL;

/*
 * The domain we are working on
 */
Evan Hunt's avatar
Evan Hunt committed
73 74 75
static const char *namestr = NULL;
static dns_fixedname_t fixed;
static dns_name_t *name = NULL;
Evan Hunt's avatar
Evan Hunt committed
76 77
static dns_rdataclass_t rdclass = dns_rdataclass_in;

Evan Hunt's avatar
Evan Hunt committed
78 79 80
static const char *startstr = NULL; /* from which we derive notbefore */
static isc_stdtime_t notbefore = 0; /* restrict sig inception times */
static dns_rdata_rrsig_t oldestsig; /* for recording inception time */
Evan Hunt's avatar
Evan Hunt committed
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116

static int nkey; /* number of child zone DNSKEY records */

/*
 * The validation strategy of this program is top-down.
 *
 * We start with an implicitly trusted authoritative dsset.
 *
 * The child DNSKEY RRset is scanned to find out which keys are
 * authenticated by DS records, and the result is recorded in a key
 * table as described later in this comment.
 *
 * The key table is used up to three times to verify the signatures on
 * the child DNSKEY, CDNSKEY, and CDS RRsets. In this program, only keys
 * that have matching DS records are used for validating signatures.
 *
 * For replay attack protection, signatures are ignored if their inception
 * time is before the previously recorded inception time. We use the earliest
 * signature so that another run of dnssec-cds with the same records will
 * still accept all the signatures.
 *
 * A key table is an array of nkey keyinfo structures, like
 *
 *	keyinfo_t key_tbl[nkey];
 *
 * Each key is decoded into more useful representations, held in
 *	keyinfo->rdata
 *	keyinfo->dst
 *
 * If a key has no matching DS record then keyinfo->dst is NULL.
 *
 * The key algorithm and ID are saved in keyinfo->algo and
 * keyinfo->tag for quicky skipping DS and RRSIG records that can't
 * match.
 */
typedef struct keyinfo {
Evan Hunt's avatar
Evan Hunt committed
117 118
	dns_rdata_t rdata;
	dst_key_t *dst;
119
	dns_secalg_t algo;
Evan Hunt's avatar
Evan Hunt committed
120 121 122 123
	dns_keytag_t tag;
} keyinfo_t;

/* A replaceable function that can generate a DS RRset from some input */
Ondřej Surý's avatar
Ondřej Surý committed
124 125
typedef isc_result_t
ds_maker_func_t(dns_rdatalist_t *dslist, isc_buffer_t *buf, dns_rdata_t *rdata);
Evan Hunt's avatar
Evan Hunt committed
126 127 128 129 130 131 132 133

static dns_rdataset_t cdnskey_set, cdnskey_sig;
static dns_rdataset_t cds_set, cds_sig;
static dns_rdataset_t dnskey_set, dnskey_sig;
static dns_rdataset_t old_ds_set, new_ds_set;

static keyinfo_t *old_key_tbl, *new_key_tbl;

134
isc_buffer_t *new_ds_buf = NULL; /* backing store for new_ds_set */
Evan Hunt's avatar
Evan Hunt committed
135 136

static void
Evan Hunt's avatar
Evan Hunt committed
137
verbose_time(int level, const char *msg, isc_stdtime_t time) {
Evan Hunt's avatar
Evan Hunt committed
138 139
	isc_result_t result;
	isc_buffer_t timebuf;
Evan Hunt's avatar
Evan Hunt committed
140
	char timestr[32];
Evan Hunt's avatar
Evan Hunt committed
141 142 143 144 145 146 147 148 149 150 151 152

	if (verbose < level) {
		return;
	}

	isc_buffer_init(&timebuf, timestr, sizeof(timestr));
	result = dns_time64_totext(time, &timebuf);
	check_result(result, "dns_time64_totext()");
	isc_buffer_putuint8(&timebuf, 0);
	if (verbose < 3) {
		vbprintf(level, "%s %s\n", msg, timestr);
	} else {
153
		vbprintf(level, "%s %s (%" PRIu32 ")\n", msg, timestr, time);
Evan Hunt's avatar
Evan Hunt committed
154 155 156 157
	}
}

static void
Evan Hunt's avatar
Evan Hunt committed
158
initname(char *setname) {
Evan Hunt's avatar
Evan Hunt committed
159 160 161
	isc_result_t result;
	isc_buffer_t buf;

162
	name = dns_fixedname_initname(&fixed);
Evan Hunt's avatar
Evan Hunt committed
163 164 165 166 167 168 169 170 171 172 173 174
	namestr = setname;

	isc_buffer_init(&buf, setname, strlen(setname));
	isc_buffer_add(&buf, strlen(setname));
	result = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL);
	if (result != ISC_R_SUCCESS) {
		fatal("could not initialize name %s", setname);
	}
}

static void
findset(dns_db_t *db, dns_dbnode_t *node, dns_rdatatype_t type,
Evan Hunt's avatar
Evan Hunt committed
175
	dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
Evan Hunt's avatar
Evan Hunt committed
176 177 178 179 180 181
	isc_result_t result;

	dns_rdataset_init(rdataset);
	if (sigrdataset != NULL) {
		dns_rdataset_init(sigrdataset);
	}
182 183
	result = dns_db_findrdataset(db, node, NULL, type, 0, 0, rdataset,
				     sigrdataset);
Evan Hunt's avatar
Evan Hunt committed
184 185 186 187 188 189
	if (result != ISC_R_NOTFOUND) {
		check_result(result, "dns_db_findrdataset()");
	}
}

static void
Evan Hunt's avatar
Evan Hunt committed
190
freeset(dns_rdataset_t *rdataset) {
Evan Hunt's avatar
Evan Hunt committed
191 192 193 194 195 196
	if (dns_rdataset_isassociated(rdataset)) {
		dns_rdataset_disassociate(rdataset);
	}
}

static void
Evan Hunt's avatar
Evan Hunt committed
197
freelist(dns_rdataset_t *rdataset) {
Evan Hunt's avatar
Evan Hunt committed
198
	dns_rdatalist_t *rdlist;
Evan Hunt's avatar
Evan Hunt committed
199
	dns_rdata_t *rdata;
Evan Hunt's avatar
Evan Hunt committed
200 201 202 203 204 205 206

	if (!dns_rdataset_isassociated(rdataset)) {
		return;
	}

	dns_rdatalist_fromrdataset(rdataset, &rdlist);

207
	for (rdata = ISC_LIST_HEAD(rdlist->rdata); rdata != NULL;
Evan Hunt's avatar
Evan Hunt committed
208 209
	     rdata = ISC_LIST_HEAD(rdlist->rdata))
	{
Evan Hunt's avatar
Evan Hunt committed
210 211 212 213 214 215 216 217
		ISC_LIST_UNLINK(rdlist->rdata, rdata, link);
		isc_mem_put(mctx, rdata, sizeof(*rdata));
	}
	isc_mem_put(mctx, rdlist, sizeof(*rdlist));
	dns_rdataset_disassociate(rdataset);
}

static void
Evan Hunt's avatar
Evan Hunt committed
218
free_all_sets(void) {
Evan Hunt's avatar
Evan Hunt committed
219 220 221 222 223 224 225 226 227 228 229 230 231 232
	freeset(&cdnskey_set);
	freeset(&cdnskey_sig);
	freeset(&cds_set);
	freeset(&cds_sig);
	freeset(&dnskey_set);
	freeset(&dnskey_sig);
	freeset(&old_ds_set);
	freelist(&new_ds_set);
	if (new_ds_buf != NULL) {
		isc_buffer_free(&new_ds_buf);
	}
}

static void
Evan Hunt's avatar
Evan Hunt committed
233
load_db(const char *filename, dns_db_t **dbp, dns_dbnode_t **nodep) {
Evan Hunt's avatar
Evan Hunt committed
234 235
	isc_result_t result;

236 237
	result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone, rdclass, 0,
			       NULL, dbp);
Evan Hunt's avatar
Evan Hunt committed
238 239
	check_result(result, "dns_db_create()");

240 241
	result = dns_db_load(*dbp, filename, dns_masterformat_text,
			     DNS_MASTER_HINT);
Evan Hunt's avatar
Evan Hunt committed
242
	if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) {
243
		fatal("can't load %s: %s", filename, isc_result_totext(result));
Evan Hunt's avatar
Evan Hunt committed
244 245
	}

246
	result = dns_db_findnode(*dbp, name, false, nodep);
Evan Hunt's avatar
Evan Hunt committed
247 248 249 250 251 252
	if (result != ISC_R_SUCCESS) {
		fatal("can't find %s node in %s", namestr, filename);
	}
}

static void
Evan Hunt's avatar
Evan Hunt committed
253
free_db(dns_db_t **dbp, dns_dbnode_t **nodep) {
Evan Hunt's avatar
Evan Hunt committed
254 255 256 257 258
	dns_db_detachnode(*dbp, nodep);
	dns_db_detach(dbp);
}

static void
Evan Hunt's avatar
Evan Hunt committed
259 260
load_child_sets(const char *file) {
	dns_db_t *db = NULL;
Evan Hunt's avatar
Evan Hunt committed
261 262 263 264 265 266 267 268 269 270
	dns_dbnode_t *node = NULL;

	load_db(file, &db, &node);
	findset(db, node, dns_rdatatype_dnskey, &dnskey_set, &dnskey_sig);
	findset(db, node, dns_rdatatype_cdnskey, &cdnskey_set, &cdnskey_sig);
	findset(db, node, dns_rdatatype_cds, &cds_set, &cds_sig);
	free_db(&db, &node);
}

static void
271
get_dsset_name(char *filename, size_t size, const char *path,
Evan Hunt's avatar
Evan Hunt committed
272
	       const char *suffix) {
Evan Hunt's avatar
Evan Hunt committed
273 274
	isc_result_t result;
	isc_buffer_t buf;
Evan Hunt's avatar
Evan Hunt committed
275
	size_t len;
Evan Hunt's avatar
Evan Hunt committed
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298

	isc_buffer_init(&buf, filename, size);

	len = strlen(path);

	/* allow room for a trailing slash */
	if (isc_buffer_availablelength(&buf) <= len) {
		fatal("%s: pathname too long", path);
	}
	isc_buffer_putstr(&buf, path);

	if (isc_file_isdirectory(path) == ISC_R_SUCCESS) {
		const char *prefix = "dsset-";

		if (path[len - 1] != '/') {
			isc_buffer_putstr(&buf, "/");
		}

		if (isc_buffer_availablelength(&buf) < strlen(prefix)) {
			fatal("%s: pathname too long", path);
		}
		isc_buffer_putstr(&buf, prefix);

299
		result = dns_name_tofilenametext(name, false, &buf);
Evan Hunt's avatar
Evan Hunt committed
300 301 302 303 304 305 306 307 308 309 310 311 312 313
		check_result(result, "dns_name_tofilenametext()");
		if (isc_buffer_availablelength(&buf) == 0) {
			fatal("%s: pathname too long", path);
		}
	}
	/* allow room for a trailing nul */
	if (isc_buffer_availablelength(&buf) <= strlen(suffix)) {
		fatal("%s: pathname too long", path);
	}
	isc_buffer_putstr(&buf, suffix);
	isc_buffer_putuint8(&buf, 0);
}

static void
Evan Hunt's avatar
Evan Hunt committed
314 315 316
load_parent_set(const char *path) {
	isc_result_t result;
	dns_db_t *db = NULL;
Evan Hunt's avatar
Evan Hunt committed
317
	dns_dbnode_t *node = NULL;
Evan Hunt's avatar
Evan Hunt committed
318 319
	isc_time_t modtime;
	char filename[PATH_MAX + 1];
Evan Hunt's avatar
Evan Hunt committed
320 321 322 323 324

	get_dsset_name(filename, sizeof(filename), path, "");

	result = isc_file_getmodtime(filename, &modtime);
	if (result != ISC_R_SUCCESS) {
325 326
		fatal("could not get modification time of %s: %s", filename,
		      isc_result_totext(result));
Evan Hunt's avatar
Evan Hunt committed
327 328 329 330 331 332 333 334 335 336 337 338 339
	}
	notbefore = isc_time_seconds(&modtime);
	if (startstr != NULL) {
		isc_stdtime_t now;
		isc_stdtime_get(&now);
		notbefore = strtotime(startstr, now, notbefore, NULL);
	}
	verbose_time(1, "child records must not be signed before", notbefore);

	load_db(filename, &db, &node);
	findset(db, node, dns_rdatatype_ds, &old_ds_set, NULL);

	if (!dns_rdataset_isassociated(&old_ds_set)) {
340 341
		fatal("could not find DS records for %s in %s", namestr,
		      filename);
Evan Hunt's avatar
Evan Hunt committed
342 343 344 345 346
	}

	free_db(&db, &node);
}

347 348
#define MAX_CDS_RDATA_TEXT_SIZE DNS_RDATA_MAXLENGTH * 2

Evan Hunt's avatar
Evan Hunt committed
349
static isc_buffer_t *
Evan Hunt's avatar
Evan Hunt committed
350 351 352
formatset(dns_rdataset_t *rdataset) {
	isc_result_t result;
	isc_buffer_t *buf = NULL;
Evan Hunt's avatar
Evan Hunt committed
353
	dns_master_style_t *style = NULL;
Evan Hunt's avatar
Evan Hunt committed
354
	unsigned int styleflags;
Evan Hunt's avatar
Evan Hunt committed
355 356 357 358 359 360 361 362

	styleflags = (rdataset->ttl == 0) ? DNS_STYLEFLAG_NO_TTL : 0;

	/*
	 * This style is for consistency with the output of dnssec-dsfromkey
	 * which just separates fields with spaces. The huge tab stop width
	 * eliminates any tab characters.
	 */
363 364
	result = dns_master_stylecreate(&style, styleflags, 0, 0, 0, 0, 0,
					1000000, 0, mctx);
365
	check_result(result, "dns_master_stylecreate2 failed");
Evan Hunt's avatar
Evan Hunt committed
366

367
	isc_buffer_allocate(mctx, &buf, MAX_CDS_RDATA_TEXT_SIZE);
368
	result = dns_master_rdatasettotext(name, rdataset, style, NULL, buf);
369 370 371 372 373 374

	if ((result == ISC_R_SUCCESS) && isc_buffer_availablelength(buf) < 1) {
		result = ISC_R_NOSPACE;
	}

	check_result(result, "dns_rdataset_totext()");
Evan Hunt's avatar
Evan Hunt committed
375 376 377 378 379 380 381 382 383

	isc_buffer_putuint8(buf, 0);

	dns_master_styledestroy(&style, mctx);

	return (buf);
}

static void
384
write_parent_set(const char *path, const char *inplace, bool nsupdate,
Evan Hunt's avatar
Evan Hunt committed
385 386
		 dns_rdataset_t *rdataset) {
	isc_result_t result;
Evan Hunt's avatar
Evan Hunt committed
387
	isc_buffer_t *buf = NULL;
Evan Hunt's avatar
Evan Hunt committed
388 389 390 391 392 393
	isc_region_t r;
	isc_time_t filetime;
	char backname[PATH_MAX + 1];
	char filename[PATH_MAX + 1];
	char tmpname[PATH_MAX + 1];
	FILE *fp = NULL;
Evan Hunt's avatar
Evan Hunt committed
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426

	if (nsupdate && inplace == NULL) {
		return;
	}

	buf = formatset(rdataset);
	isc_buffer_usedregion(buf, &r);

	/*
	 * Try to ensure a write error doesn't make a zone go insecure!
	 */
	if (inplace == NULL) {
		printf("%s", (char *)r.base);
		isc_buffer_free(&buf);
		if (fflush(stdout) == EOF) {
			fatal("error writing to stdout: %s", strerror(errno));
		}
		return;
	}

	if (inplace[0] != '\0') {
		get_dsset_name(backname, sizeof(backname), path, inplace);
	}
	get_dsset_name(filename, sizeof(filename), path, "");
	get_dsset_name(tmpname, sizeof(tmpname), path, "-XXXXXXXXXX");

	result = isc_file_openunique(tmpname, &fp);
	if (result != ISC_R_SUCCESS) {
		fatal("open %s: %s", tmpname, isc_result_totext(result));
	}
	fprintf(fp, "%s", (char *)r.base);
	isc_buffer_free(&buf);
	if (fclose(fp) == EOF) {
427
		int err = errno;
Evan Hunt's avatar
Evan Hunt committed
428
		isc_file_remove(tmpname);
429
		fatal("error writing to %s: %s", tmpname, strerror(err));
Evan Hunt's avatar
Evan Hunt committed
430 431 432 433 434 435
	}

	isc_time_set(&filetime, oldestsig.timesigned, 0);
	result = isc_file_settime(tmpname, &filetime);
	if (result != ISC_R_SUCCESS) {
		isc_file_remove(tmpname);
436 437
		fatal("can't set modification time of %s: %s", tmpname,
		      isc_result_totext(result));
Evan Hunt's avatar
Evan Hunt committed
438 439 440 441 442 443 444 445 446 447 448 449 450
	}

	if (inplace[0] != '\0') {
		isc_file_rename(filename, backname);
	}
	isc_file_rename(tmpname, filename);
}

typedef enum { LOOSE, TIGHT } strictness_t;

/*
 * Find out if any (C)DS record matches a particular (C)DNSKEY.
 */
451
static bool
Evan Hunt's avatar
Evan Hunt committed
452 453
match_key_dsset(keyinfo_t *ki, dns_rdataset_t *dsset, strictness_t strictness) {
	isc_result_t result;
Evan Hunt's avatar
Evan Hunt committed
454 455
	unsigned char dsbuf[DNS_DS_BUFFERSIZE];

456
	for (result = dns_rdataset_first(dsset); result == ISC_R_SUCCESS;
Evan Hunt's avatar
Evan Hunt committed
457 458
	     result = dns_rdataset_next(dsset))
	{
Evan Hunt's avatar
Evan Hunt committed
459
		dns_rdata_ds_t ds;
Evan Hunt's avatar
Evan Hunt committed
460 461 462
		dns_rdata_t dsrdata = DNS_RDATA_INIT;
		dns_rdata_t newdsrdata = DNS_RDATA_INIT;
		bool c;
Evan Hunt's avatar
Evan Hunt committed
463 464 465 466 467 468 469 470 471 472 473 474

		dns_rdataset_current(dsset, &dsrdata);
		result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
		check_result(result, "dns_rdata_tostruct(DS)");

		if (ki->tag != ds.key_tag || ki->algo != ds.algorithm) {
			continue;
		}

		result = dns_ds_buildrdata(name, &ki->rdata, ds.digest_type,
					   dsbuf, &newdsrdata);
		if (result != ISC_R_SUCCESS) {
475 476
			vbprintf(3,
				 "dns_ds_buildrdata("
Evan Hunt's avatar
Evan Hunt committed
477 478 479 480 481 482 483 484 485 486
				 "keytag=%d, algo=%d, digest=%d): %s\n",
				 ds.key_tag, ds.algorithm, ds.digest_type,
				 dns_result_totext(result));
			continue;
		}
		/* allow for both DS and CDS */
		c = dsrdata.type != dns_rdatatype_ds;
		dsrdata.type = dns_rdatatype_ds;
		if (dns_rdata_compare(&dsrdata, &newdsrdata) == 0) {
			vbprintf(1, "found matching %s %d %d %d\n",
487 488
				 c ? "CDS" : "DS", ds.key_tag, ds.algorithm,
				 ds.digest_type);
489
			return (true);
Evan Hunt's avatar
Evan Hunt committed
490
		} else if (strictness == TIGHT) {
491 492 493 494 495
			vbprintf(0,
				 "key does not match %s %d %d %d "
				 "when it looks like it should\n",
				 c ? "CDS" : "DS", ds.key_tag, ds.algorithm,
				 ds.digest_type);
496
			return (false);
Evan Hunt's avatar
Evan Hunt committed
497 498 499
		}
	}

500
	vbprintf(1, "no matching %s for %s %d %d\n",
501 502
		 dsset->type == dns_rdatatype_cds ? "CDS" : "DS",
		 ki->rdata.type == dns_rdatatype_cdnskey ? "CDNSKEY" : "DNSKEY",
503 504
		 ki->tag, ki->algo);

505
	return (false);
Evan Hunt's avatar
Evan Hunt committed
506 507 508 509 510 511 512 513
}

/*
 * Find which (C)DNSKEY records match a (C)DS RRset.
 * This creates a keyinfo_t key_tbl[nkey] array.
 */
static keyinfo_t *
match_keyset_dsset(dns_rdataset_t *keyset, dns_rdataset_t *dsset,
Evan Hunt's avatar
Evan Hunt committed
514
		   strictness_t strictness) {
Evan Hunt's avatar
Evan Hunt committed
515
	isc_result_t result;
Evan Hunt's avatar
Evan Hunt committed
516 517
	keyinfo_t *keytable;
	int i;
Evan Hunt's avatar
Evan Hunt committed
518 519 520 521 522 523

	nkey = dns_rdataset_count(keyset);

	keytable = isc_mem_get(mctx, sizeof(keyinfo_t) * nkey);

	for (result = dns_rdataset_first(keyset), i = 0;
Evan Hunt's avatar
Evan Hunt committed
524 525 526
	     result == ISC_R_SUCCESS; result = dns_rdataset_next(keyset), i++)
	{
		keyinfo_t *ki;
Evan Hunt's avatar
Evan Hunt committed
527
		dns_rdata_dnskey_t dnskey;
Evan Hunt's avatar
Evan Hunt committed
528 529
		dns_rdata_t *keyrdata;
		isc_region_t r;
Evan Hunt's avatar
Evan Hunt committed
530 531 532 533 534 535 536 537 538 539 540 541 542

		INSIST(i < nkey);
		ki = &keytable[i];
		keyrdata = &ki->rdata;

		dns_rdata_init(keyrdata);
		dns_rdataset_current(keyset, keyrdata);

		result = dns_rdata_tostruct(keyrdata, &dnskey, NULL);
		check_result(result, "dns_rdata_tostruct(DNSKEY)");
		ki->algo = dnskey.algorithm;

		dns_rdata_toregion(keyrdata, &r);
Ondřej Surý's avatar
Ondřej Surý committed
543
		ki->tag = dst_region_computeid(&r);
Evan Hunt's avatar
Evan Hunt committed
544 545 546 547 548 549

		ki->dst = NULL;
		if (!match_key_dsset(ki, dsset, strictness)) {
			continue;
		}

Evan Hunt's avatar
Evan Hunt committed
550 551
		result = dns_dnssec_keyfromrdata(name, keyrdata, mctx,
						 &ki->dst);
Evan Hunt's avatar
Evan Hunt committed
552
		if (result != ISC_R_SUCCESS) {
553 554
			vbprintf(3,
				 "dns_dnssec_keyfromrdata("
Evan Hunt's avatar
Evan Hunt committed
555
				 "keytag=%d, algo=%d): %s\n",
556
				 ki->tag, ki->algo, dns_result_totext(result));
Evan Hunt's avatar
Evan Hunt committed
557 558 559 560 561 562 563
		}
	}

	return (keytable);
}

static void
Evan Hunt's avatar
Evan Hunt committed
564
free_keytable(keyinfo_t **keytable_p) {
Evan Hunt's avatar
Evan Hunt committed
565
	keyinfo_t *keytable = *keytable_p;
566
	*keytable_p = NULL;
Evan Hunt's avatar
Evan Hunt committed
567
	keyinfo_t *ki;
Evan Hunt's avatar
Evan Hunt committed
568
	int i;
Evan Hunt's avatar
Evan Hunt committed
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588

	for (i = 0; i < nkey; i++) {
		ki = &keytable[i];
		if (ki->dst != NULL) {
			dst_key_free(&ki->dst);
		}
	}

	isc_mem_put(mctx, keytable, sizeof(keyinfo_t) * nkey);
}

/*
 * Find out which keys have signed an RRset. Keys that do not match a
 * DS record are skipped.
 *
 * The return value is an array with nkey elements, one for each key,
 * either zero if the key was skipped or did not sign the RRset, or
 * otherwise the key algorithm. This is used by the signature coverage
 * check functions below.
 */
589
static dns_secalg_t *
Evan Hunt's avatar
Evan Hunt committed
590
matching_sigs(keyinfo_t *keytbl, dns_rdataset_t *rdataset,
Evan Hunt's avatar
Evan Hunt committed
591 592
	      dns_rdataset_t *sigset) {
	isc_result_t result;
593
	dns_secalg_t *algo;
Evan Hunt's avatar
Evan Hunt committed
594
	int i;
Evan Hunt's avatar
Evan Hunt committed
595 596 597 598

	algo = isc_mem_get(mctx, nkey);
	memset(algo, 0, nkey);

599
	for (result = dns_rdataset_first(sigset); result == ISC_R_SUCCESS;
Evan Hunt's avatar
Evan Hunt committed
600 601 602
	     result = dns_rdataset_next(sigset))
	{
		dns_rdata_t sigrdata = DNS_RDATA_INIT;
Evan Hunt's avatar
Evan Hunt committed
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
		dns_rdata_rrsig_t sig;

		dns_rdataset_current(sigset, &sigrdata);
		result = dns_rdata_tostruct(&sigrdata, &sig, NULL);
		check_result(result, "dns_rdata_tostruct(RRSIG)");

		/*
		 * Replay attack protection: check against current age limit
		 */
		if (isc_serial_lt(sig.timesigned, notbefore)) {
			vbprintf(1, "skip RRSIG by key %d: too old\n",
				 sig.keyid);
			continue;
		}

		for (i = 0; i < nkey; i++) {
			keyinfo_t *ki = &keytbl[i];
620
			if (sig.keyid != ki->tag || sig.algorithm != ki->algo ||
Evan Hunt's avatar
Evan Hunt committed
621 622
			    !dns_name_equal(&sig.signer, name))
			{
Evan Hunt's avatar
Evan Hunt committed
623 624
				continue;
			}
625
			if (ki->dst == NULL) {
626 627
				vbprintf(1,
					 "skip RRSIG by key %d:"
628 629 630 631
					 " no matching (C)DS\n",
					 sig.keyid);
				continue;
			}
Evan Hunt's avatar
Evan Hunt committed
632 633

			result = dns_dnssec_verify(name, rdataset, ki->dst,
634 635
						   false, 0, mctx, &sigrdata,
						   NULL);
636 637 638

			if (result != ISC_R_SUCCESS &&
			    result != DNS_R_FROMWILDCARD) {
639 640
				vbprintf(1,
					 "skip RRSIG by key %d:"
641 642
					 " verification failed: %s\n",
					 sig.keyid, isc_result_totext(result));
Evan Hunt's avatar
Evan Hunt committed
643 644 645 646 647 648 649 650 651 652 653
				continue;
			}

			vbprintf(1, "found RRSIG by key %d\n", ki->tag);
			algo[i] = sig.algorithm;

			/*
			 * Replay attack protection: work out next age limit,
			 * only after the signature has been verified
			 */
			if (oldestsig.timesigned == 0 ||
Evan Hunt's avatar
Evan Hunt committed
654 655
			    isc_serial_lt(sig.timesigned, oldestsig.timesigned))
			{
Evan Hunt's avatar
Evan Hunt committed
656 657 658 659 660 661 662 663 664 665 666 667 668 669
				verbose_time(2, "this is the oldest so far",
					     sig.timesigned);
				oldestsig = sig;
			}
		}
	}

	return (algo);
}

/*
 * Consume the result of matching_sigs(). When checking records
 * fetched from the child zone, any working signature is enough.
 */
670
static bool
Evan Hunt's avatar
Evan Hunt committed
671
signed_loose(dns_secalg_t *algo) {
672
	bool ok = false;
Evan Hunt's avatar
Evan Hunt committed
673
	int i;
Evan Hunt's avatar
Evan Hunt committed
674 675
	for (i = 0; i < nkey; i++) {
		if (algo[i] != 0) {
676
			ok = true;
Evan Hunt's avatar
Evan Hunt committed
677 678 679 680 681 682 683 684 685 686 687 688
		}
	}
	isc_mem_put(mctx, algo, nkey);
	return (ok);
}

/*
 * Consume the result of matching_sigs(). To ensure that the new DS
 * RRset does not break the chain of trust to the DNSKEY RRset, every
 * key algorithm in the DS RRset must have a signature in the DNSKEY
 * RRset.
 */
689
static bool
Evan Hunt's avatar
Evan Hunt committed
690
signed_strict(dns_rdataset_t *dsset, dns_secalg_t *algo) {
Evan Hunt's avatar
Evan Hunt committed
691
	isc_result_t result;
Evan Hunt's avatar
Evan Hunt committed
692
	bool all_ok = true;
Evan Hunt's avatar
Evan Hunt committed
693

694
	for (result = dns_rdataset_first(dsset); result == ISC_R_SUCCESS;
Evan Hunt's avatar
Evan Hunt committed
695 696 697
	     result = dns_rdataset_next(dsset))
	{
		dns_rdata_t dsrdata = DNS_RDATA_INIT;
Evan Hunt's avatar
Evan Hunt committed
698
		dns_rdata_ds_t ds;
Evan Hunt's avatar
Evan Hunt committed
699 700
		bool ds_ok;
		int i;
Evan Hunt's avatar
Evan Hunt committed
701 702 703 704 705

		dns_rdataset_current(dsset, &dsrdata);
		result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
		check_result(result, "dns_rdata_tostruct(DS)");

706
		ds_ok = false;
Evan Hunt's avatar
Evan Hunt committed
707 708
		for (i = 0; i < nkey; i++) {
			if (algo[i] == ds.algorithm) {
709
				ds_ok = true;
Evan Hunt's avatar
Evan Hunt committed
710 711 712
			}
		}
		if (!ds_ok) {
713 714 715 716
			vbprintf(0,
				 "missing signature for algorithm %d "
				 "(key %d)\n",
				 ds.algorithm, ds.key_tag);
717
			all_ok = false;
Evan Hunt's avatar
Evan Hunt committed
718 719 720 721 722 723 724 725
		}
	}

	isc_mem_put(mctx, algo, nkey);
	return (all_ok);
}

static dns_rdata_t *
Evan Hunt's avatar
Evan Hunt committed
726
rdata_get(void) {
Evan Hunt's avatar
Evan Hunt committed
727 728 729 730 731 732 733 734 735
	dns_rdata_t *rdata;

	rdata = isc_mem_get(mctx, sizeof(*rdata));
	dns_rdata_init(rdata);

	return (rdata);
}

static isc_result_t
Evan Hunt's avatar
Evan Hunt committed
736
rdata_put(isc_result_t result, dns_rdatalist_t *rdlist, dns_rdata_t *rdata) {
Evan Hunt's avatar
Evan Hunt committed
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751
	if (result == ISC_R_SUCCESS) {
		ISC_LIST_APPEND(rdlist->rdata, rdata, link);
	} else {
		isc_mem_put(mctx, rdata, sizeof(*rdata));
	}

	return (result);
}

/*
 * This basically copies the rdata into the buffer, but going via the
 * unpacked struct has the side-effect of changing the rdatatype. The
 * dns_rdata_cds_t and dns_rdata_ds_t types are aliases.
 */
static isc_result_t
Evan Hunt's avatar
Evan Hunt committed
752 753
ds_from_cds(dns_rdatalist_t *dslist, isc_buffer_t *buf, dns_rdata_t *cds) {
	isc_result_t result;
Evan Hunt's avatar
Evan Hunt committed
754
	dns_rdata_ds_t ds;
Evan Hunt's avatar
Evan Hunt committed
755
	dns_rdata_t *rdata;
Evan Hunt's avatar
Evan Hunt committed
756

757 758
	REQUIRE(buf != NULL);

Evan Hunt's avatar
Evan Hunt committed
759 760 761 762 763 764
	rdata = rdata_get();

	result = dns_rdata_tostruct(cds, &ds, NULL);
	check_result(result, "dns_rdata_tostruct(CDS)");
	ds.common.rdtype = dns_rdatatype_ds;

765 766
	result = dns_rdata_fromstruct(rdata, rdclass, dns_rdatatype_ds, &ds,
				      buf);
Evan Hunt's avatar
Evan Hunt committed
767 768 769 770 771 772

	return (rdata_put(result, dslist, rdata));
}

static isc_result_t
ds_from_cdnskey(dns_rdatalist_t *dslist, isc_buffer_t *buf,
Evan Hunt's avatar
Evan Hunt committed
773
		dns_rdata_t *cdnskey) {
Evan Hunt's avatar
Evan Hunt committed
774
	isc_result_t result;
Evan Hunt's avatar
Evan Hunt committed
775
	unsigned i, n;
Evan Hunt's avatar
Evan Hunt committed
776

777 778
	REQUIRE(buf != NULL);

779
	n = sizeof(dtype) / sizeof(dtype[0]);
Evan Hunt's avatar
Evan Hunt committed
780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
	for (i = 0; i < n; i++) {
		if (dtype[i] != 0) {
			dns_rdata_t *rdata;
			isc_region_t r;

			isc_buffer_availableregion(buf, &r);
			if (r.length < DNS_DS_BUFFERSIZE) {
				return (ISC_R_NOSPACE);
			}

			rdata = rdata_get();
			result = dns_ds_buildrdata(name, cdnskey, dtype[i],
						   r.base, rdata);
			if (result == ISC_R_SUCCESS) {
				isc_buffer_add(buf, DNS_DS_BUFFERSIZE);
			}

			result = rdata_put(result, dslist, rdata);
			if (result != ISC_R_SUCCESS) {
				return (result);
			}
		}
	}

	return (ISC_R_SUCCESS);
}

static void
808
make_new_ds_set(ds_maker_func_t *ds_from_rdata, uint32_t ttl,
Evan Hunt's avatar
Evan Hunt committed
809
		dns_rdataset_t *rdset) {
Evan Hunt's avatar
Evan Hunt committed
810 811
	unsigned int size = 16;
	for (;;) {
Evan Hunt's avatar
Evan Hunt committed
812
		isc_result_t result;
Evan Hunt's avatar
Evan Hunt committed
813 814 815 816 817 818 819 820 821 822 823 824 825
		dns_rdatalist_t *dslist;

		dslist = isc_mem_get(mctx, sizeof(*dslist));

		dns_rdatalist_init(dslist);
		dslist->rdclass = rdclass;
		dslist->type = dns_rdatatype_ds;
		dslist->ttl = ttl;

		dns_rdataset_init(&new_ds_set);
		result = dns_rdatalist_tordataset(dslist, &new_ds_set);
		check_result(result, "dns_rdatalist_tordataset(dslist)");

826
		isc_buffer_allocate(mctx, &new_ds_buf, size);
Evan Hunt's avatar
Evan Hunt committed
827 828

		for (result = dns_rdataset_first(rdset);
Evan Hunt's avatar
Evan Hunt committed
829 830
		     result == ISC_R_SUCCESS; result = dns_rdataset_next(rdset))
		{
Evan Hunt's avatar
Evan Hunt committed
831
			isc_result_t tresult;
Evan Hunt's avatar
Evan Hunt committed
832
			dns_rdata_t rdata = DNS_RDATA_INIT;
Evan Hunt's avatar
Evan Hunt committed
833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854

			dns_rdataset_current(rdset, &rdata);

			tresult = ds_from_rdata(dslist, new_ds_buf, &rdata);
			if (tresult == ISC_R_NOSPACE) {
				vbprintf(20, "DS list buffer size %u\n", size);
				freelist(&new_ds_set);
				isc_buffer_free(&new_ds_buf);
				size *= 2;
				break;
			}

			check_result(tresult, "ds_from_rdata()");
		}

		if (result == ISC_R_NOMORE) {
			break;
		}
	}
}

static inline int
Evan Hunt's avatar
Evan Hunt committed
855
rdata_cmp(const void *rdata1, const void *rdata2) {
Evan Hunt's avatar
Evan Hunt committed
856 857 858 859 860 861 862 863
	return (dns_rdata_compare((const dns_rdata_t *)rdata1,
				  (const dns_rdata_t *)rdata2));
}

/*
 * Ensure that every key identified by the DS RRset has the same set of
 * digest types.
 */
864
static bool
Evan Hunt's avatar
Evan Hunt committed
865 866 867
consistent_digests(dns_rdataset_t *dsset) {
	isc_result_t result;
	dns_rdata_t *arrdata;
Evan Hunt's avatar
Evan Hunt committed
868
	dns_rdata_ds_t *ds;
Evan Hunt's avatar
Evan Hunt committed
869 870 871 872
	dns_keytag_t key_tag;
	dns_secalg_t algorithm;
	bool match;
	int i, j, n, d;
Evan Hunt's avatar
Evan Hunt committed
873 874 875 876 877 878 879 880 881 882

	/*
	 * First sort the dsset. DS rdata fields are tag, algorithm, digest,
	 * so sorting them brings together all the records for each key.
	 */

	n = dns_rdataset_count(dsset);

	arrdata = isc_mem_get(mctx, n * sizeof(dns_rdata_t));

883
	for (result = dns_rdataset_first(dsset), i = 0; result == ISC_R_SUCCESS;
Evan Hunt's avatar
Evan Hunt committed
884 885
	     result = dns_rdataset_next(dsset), i++)
	{
Evan Hunt's avatar
Evan Hunt committed
886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915
		dns_rdata_init(&arrdata[i]);
		dns_rdataset_current(dsset, &arrdata[i]);
	}

	qsort(arrdata, n, sizeof(dns_rdata_t), rdata_cmp);

	/*
	 * Convert sorted arrdata to more accessible format
	 */
	ds = isc_mem_get(mctx, n * sizeof(dns_rdata_ds_t));

	for (i = 0; i < n; i++) {
		result = dns_rdata_tostruct(&arrdata[i], &ds[i], NULL);
		check_result(result, "dns_rdata_tostruct(DS)");
	}

	/*
	 * Count number of digest types (d) for first key
	 */
	key_tag = ds[0].key_tag;
	algorithm = ds[0].algorithm;
	for (d = 0, i = 0; i < n; i++, d++) {
		if (ds[i].key_tag != key_tag || ds[i].algorithm != algorithm) {
			break;
		}
	}

	/*
	 * Check subsequent keys match the first one
	 */
916
	match = true;
Evan Hunt's avatar
Evan Hunt committed
917 918 919
	while (i < n) {
		key_tag = ds[i].key_tag;
		algorithm = ds[i].algorithm;
920 921 922
		for (j = 0; j < d && i + j < n; j++) {
			if (ds[i + j].key_tag != key_tag ||
			    ds[i + j].algorithm != algorithm ||
Evan Hunt's avatar
Evan Hunt committed
923 924
			    ds[i + j].digest_type != ds[j].digest_type)
			{
925
				match = false;
Evan Hunt's avatar
Evan Hunt committed
926 927 928 929 930 931 932 933 934 935 936 937 938 939 940
			}
		}
		i += d;
	}

	/*
	 * Done!
	 */
	isc_mem_put(mctx, ds, n * sizeof(dns_rdata_ds_t));
	isc_mem_put(mctx, arrdata, n * sizeof(dns_rdata_t));

	return (match);
}

static void
Evan Hunt's avatar
Evan Hunt committed
941 942 943
print_diff(const char *cmd, dns_rdataset_t *rdataset) {
	isc_buffer_t *buf;
	isc_region_t r;
Evan Hunt's avatar
Evan Hunt committed
944
	unsigned char *nl;
Evan Hunt's avatar
Evan Hunt committed
945
	size_t len;
Evan Hunt's avatar
Evan Hunt committed
946 947 948 949 950 951 952 953 954 955 956 957 958 959

	buf = formatset(rdataset);
	isc_buffer_usedregion(buf, &r);

	while ((nl = memchr(r.base, '\n', r.length)) != NULL) {
		len = nl - r.base + 1;
		printf("update %s %.*s", cmd, (int)len, (char *)r.base);
		isc_region_consume(&r, len);
	}

	isc_buffer_free(&buf);
}

static void
960
update_diff(const char *cmd, uint32_t ttl, dns_rdataset_t *addset,
Evan Hunt's avatar
Evan Hunt committed
961 962 963 964
	    dns_rdataset_t *delset) {
	isc_result_t result;
	dns_db_t *db;
	dns_dbnode_t *node;
Evan Hunt's avatar
Evan Hunt committed
965
	dns_dbversion_t *ver;
Evan Hunt's avatar
Evan Hunt committed
966 967
	dns_rdataset_t diffset;
	uint32_t save;
Evan Hunt's avatar
Evan Hunt committed
968 969

	db = NULL;
970 971
	result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone, rdclass, 0,
			       NULL, &db);
Evan Hunt's avatar
Evan Hunt committed
972 973 974 975 976 977 978
	check_result(result, "dns_db_create()");

	ver = NULL;
	result = dns_db_newversion(db, &ver);
	check_result(result, "dns_db_newversion()");

	node = NULL;
979
	result = dns_db_findnode(db, name, true, &node);
Evan Hunt's avatar
Evan Hunt committed
980 981 982 983
	check_result(result, "dns_db_findnode()");

	dns_rdataset_init(&diffset);

984 985
	result = dns_db_addrdataset(db, node, ver, 0, addset, DNS_DBADD_MERGE,
				    NULL);
Evan Hunt's avatar
Evan Hunt committed
986 987
	check_result(result, "dns_db_addrdataset()");

988
	result = dns_db_subtractrdataset(db, node, ver, delset, 0, &diffset);
Evan Hunt's avatar
Evan Hunt committed
989 990 991 992 993 994 995 996 997 998 999 1000 1001
	if (result == DNS_R_UNCHANGED) {
		save = addset->ttl;
		addset->ttl = ttl;
		print_diff(cmd, addset);
		addset->ttl = save;
	} else if (result != DNS_R_NXRRSET) {
		check_result(result, "dns_db_subtractrdataset()");
		diffset.ttl = ttl;
		print_diff(cmd, &diffset);
		dns_rdataset_disassociate(&diffset);
	}

	dns_db_detachnode(db, &node);
1002
	dns_db_closeversion(db, &ver, false);
Evan Hunt's avatar
Evan Hunt committed
1003 1004 1005 1006
	dns_db_detach(&db);
}

static void
Evan Hunt's avatar
Evan Hunt committed
1007
nsdiff(uint32_t ttl, dns_rdataset_t *oldset, dns_rdataset_t *newset) {
Evan Hunt's avatar
Evan Hunt committed
1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022
	if (ttl == 0) {
		vbprintf(1, "warning: no TTL in nsupdate script\n");
	}
	update_diff("add", ttl, newset, oldset);
	update_diff("del", 0, oldset, newset);
	if (verbose > 0) {
		printf("show\nsend\nanswer\n");
	} else {
		printf("send\n");
	}
	if (fflush(stdout) == EOF) {
		fatal("write stdout: %s", strerror(errno));
	}
}

Ondřej Surý's avatar
Ondřej Surý committed
1023 1024
ISC_PLATFORM_NORETURN_PRE static void
usage(void) ISC_PLATFORM_NORETURN_POST;
Evan Hunt's avatar
Evan Hunt committed
1025 1026

static void
Evan Hunt's avatar
Evan Hunt committed
1027
usage(void) {
Evan Hunt's avatar
Evan Hunt committed
1028 1029 1030 1031 1032 1033
	fprintf(stderr, "Usage:\n");
	fprintf(stderr,
		"    %s options [options] -f <file> -d <path> <domain>\n",
		program);
	fprintf(stderr, "Version: %s\n", VERSION);
	fprintf(stderr, "Options:\n"
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
			"    -a <algorithm>     digest algorithm (SHA-1 / "
			"SHA-256 / SHA-384)\n"
			"    -c <class>         of domain (default IN)\n"
			"    -D                 prefer CDNSKEY records instead "
			"of CDS\n"
			"    -d <file|dir>      where to find parent dsset- "
			"file\n"
			"    -f <file>          child DNSKEY+CDNSKEY+CDS+RRSIG "
			"records\n"
			"    -i[extension]      update dsset- file in place\n"
			"    -s <start-time>    oldest permitted child "
			"signatures\n"
			"    -u                 emit nsupdate script\n"
			"    -T <ttl>           TTL of DS records\n"
			"    -V                 print version\n"
			"    -v <verbosity>\n");
Evan Hunt's avatar
Evan Hunt committed
1050 1051 1052 1053
	exit(1);
}

int
Evan Hunt's avatar
Evan Hunt committed
1054 1055 1056 1057
main(int argc, char *argv[]) {
	const char *child_path = NULL;
	const char *ds_path = NULL;
	const char *inplace = NULL;
Evan Hunt's avatar
Evan Hunt committed
1058
	isc_result_t result;
Evan Hunt's avatar
Evan Hunt committed
1059 1060 1061 1062 1063
	bool prefer_cdnskey = false;
	bool nsupdate = false;
	uint32_t ttl = 0;
	int ch;
	char *endp;
Evan Hunt's avatar
Evan Hunt committed
1064

1065
	isc_mem_create(&mctx);
Evan Hunt's avatar
Evan Hunt committed
1066

Ondřej Surý's avatar
Ondřej Surý committed
1067
#if USE_PKCS11
Evan Hunt's avatar
Evan Hunt committed
1068
	pk11_result_register();
1069
#endif /* if USE_PKCS11 */
Evan Hunt's avatar
Evan Hunt committed
1070 1071
	dns_result_register();

1072
	isc_commandline_errprint = false;
Evan Hunt's avatar
Evan Hunt committed
1073 1074 1075 1076 1077

#define OPTIONS "a:c:Dd:f:i:ms:T:uv:V"
	while ((ch = isc_commandline_parse(argc, argv, OPTIONS)) != -1) {
		switch (ch) {
		case 'a':
1078
			add_dtype(strtodsdigest(isc_commandline_argument));
Evan Hunt's avatar
Evan Hunt committed
1079 1080 1081 1082 1083
			break;
		case 'c':
			rdclass = strtoclass(isc_commandline_argument);
			break;
		case 'D':
1084
			prefer_cdnskey = true;
Evan Hunt's avatar
Evan Hunt committed
1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097
			break;
		case 'd':
			ds_path = isc_commandline_argument;
			break;
		case 'f':
			child_path = isc_commandline_argument;
			break;
		case 'i':
			/*
			 * This is a bodge to make the argument optional,
			 * so that it works just like sed(1).
			 */
			if (isc_commandline_argument ==
1098
			    argv[isc_commandline_index - 1]) {
Evan Hunt's avatar
Evan Hunt committed
1099 1100 1101 1102 1103 1104 1105 1106
				isc_commandline_index--;
				inplace = "";
			} else {
				inplace = isc_commandline_argument;
			}
			break;
		case 'm':
			isc_mem_debugging = ISC_MEM_DEBUGTRACE |
1107
					    ISC_MEM_DEBUGRECORD;
Evan Hunt's avatar
Evan Hunt committed
1108 1109 1110 1111 1112 1113 1114 1115
			break;
		case 's':
			startstr = isc_commandline_argument;
			break;
		case 'T':
			ttl = strtottl(isc_commandline_argument);
			break;
		case 'u':
1116
			nsupdate = true;
Evan Hunt's avatar
Evan Hunt committed
1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149
			break;
		case 'V':
			/* Does not return. */
			version(program);
			break;
		case 'v':
			verbose = strtoul(isc_commandline_argument, &endp, 0);
			if (*endp != '\0') {
				fatal("-v must be followed by a number");
			}
			break;
		default:
			usage();
			break;
		}
	}
	argv += isc_commandline_index;
	argc -= isc_commandline_index;

	if (argc != 1) {
		usage();
	}
	initname(argv[0]);

	/*
	 * Default digest type if none specified.
	 */
	if (dtype[0] == 0) {
		dtype[0] = DNS_DSDIGEST_SHA256;
	}

	setup_logging(mctx, &lctx);

1150
	result = dst_lib_init(mctx, NULL);
Evan Hunt's avatar
Evan Hunt committed
1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178
	if (result != ISC_R_SUCCESS) {
		fatal("could not initialize dst: %s",
		      isc_result_totext(result));
	}

	if (ds_path == NULL) {
		fatal("missing -d DS pathname");
	}
	load_parent_set(ds_path);

	/*
	 * Preserve the TTL if it wasn't overridden.
	 */
	if (ttl == 0) {
		ttl = old_ds_set.ttl;
	}

	if (child_path == NULL) {
		fatal("path to file containing child data must be specified");
	}

	load_child_sets(child_path);

	/*
	 * Check child records have accompanying RRSIGs and DNSKEYs
	 */

	if (!dns_rdataset_isassociated(&dnskey_set) ||
Evan Hunt's avatar
Evan Hunt committed
1179 1180
	    !dns_rdataset_isassociated(&dnskey_sig))
	{
Evan Hunt's avatar
Evan Hunt committed
1181 1182 1183 1184
		fatal("could not find signed DNSKEY RRset for %s", namestr);
	}

	if (dns_rdataset_isassociated(&cdnskey_set) &&
Evan Hunt's avatar
Evan Hunt committed
1185 1186
	    !dns_rdataset_isassociated(&cdnskey_sig))
	{
Evan Hunt's avatar
Evan Hunt committed
1187 1188 1189
		fatal("missing RRSIG CDNSKEY records for %s", namestr);
	}
	if (dns_rdataset_isassociated(&cds_set) &&
1190
	    !dns_rdataset_isassociated(&cds_sig)) {
Evan Hunt's avatar
Evan Hunt committed
1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203
		fatal("missing RRSIG CDS records for %s", namestr);
	}

	vbprintf(1, "which child DNSKEY records match parent DS records?\n");
	old_key_tbl = match_keyset_dsset(&dnskey_set, &old_ds_set, LOOSE);

	/*
	 * We have now identified the keys that are allowed to authenticate
	 * the DNSKEY RRset (RFC 4035 section 5.2 bullet 2), and CDNSKEY and
	 * CDS RRsets (RFC 7344 section 4.1 bullet 2).
	 */

	vbprintf(1, "verify DNSKEY signature(s)\n");
Evan Hunt's avatar
Evan Hunt committed
1204 1205
	if (!signed_loose(matching_sigs(old_key_tbl, &dnskey_set, &dnskey_sig)))
	{
Evan Hunt's avatar
Evan Hunt committed
1206 1207 1208 1209 1210
		fatal("could not validate child DNSKEY RRset for %s", namestr);
	}

	if (dns_rdataset_isassociated(&cdnskey_set)) {
		vbprintf(1, "verify CDNSKEY signature(s)\n");
1211 1212
		if (!signed_loose(matching_sigs(old_key_tbl, &cdnskey_set,
						&cdnskey_sig))) {
Evan Hunt's avatar
Evan Hunt committed
1213 1214 1215 1216 1217 1218
			fatal("could not validate child CDNSKEY RRset for %s",
			      namestr);
		}
	}
	if (dns_rdataset_isassociated(&cds_set)) {
		vbprintf(1, "verify CDS signature(s)\n");
1219 1220
		if (!signed_loose(
			    matching_sigs(old_key_tbl, &cds_set, &cds_sig))) {
Evan Hunt's avatar
Evan Hunt committed
1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236
			fatal("could not validate child CDS RRset for %s",
			      namestr);
		}
	}

	free_keytable(&old_key_tbl);

	/*
	 * Report the result of the replay attack protection checks
	 * used for the output file timestamp
	 */
	if (oldestsig.timesigned != 0 && verbose > 0) {
		char type[32];
		dns_rdatatype_format(oldestsig.covered, type, sizeof(type));
		verbose_time(1, "child signature inception time",
			     oldestsig.timesigned);
1237
		vbprintf(2, "from RRSIG %s by key %d\n", type, oldestsig.keyid);
Evan Hunt's avatar
Evan Hunt committed
1238 1239 1240
	}

	/*
1241
	 * Successfully do nothing if there's neither CDNSKEY nor CDS
Evan Hunt's avatar
Evan Hunt committed
1242 1243 1244
	 * RFC 7344 section 4.1 first paragraph
	 */
	if (!dns_rdataset_isassociated(&cdnskey_set) &&
Evan Hunt's avatar
Evan Hunt committed
1245 1246
	    !dns_rdataset_isassociated(&cds_set))
	{
Evan Hunt's avatar
Evan Hunt committed
1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273
		vbprintf(1, "%s has neither CDS nor CDNSKEY records\n",
			 namestr);
		write_parent_set(ds_path, inplace, nsupdate, &old_ds_set);
		exit(0);
	}

	/*
	 * Make DS records from the CDS or CDNSKEY records
	 * Prefer CDS if present, unless run with -D
	 */
	if (prefer_cdnskey && dns_rdataset_isassociated(&cdnskey_set)) {
		make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set);
	} else if (dns_rdataset_isassociated(&cds_set)) {
		make_new_ds_set(ds_from_cds, ttl, &cds_set);
	} else {
		make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set);
	}

	/*
	 * Now we have a candidate DS RRset, we need to check it
	 * won't break the delegation.
	 */
	vbprintf(1, "which child DNSKEY records match new DS records?\n");
	new_key_tbl = match_keyset_dsset(&dnskey_set, &new_ds_set, TIGHT);

	if (!consistent_digests(&new_ds_set)) {
		fatal("CDS records at %s do not cover each key "
1274 1275
		      "with the same set of digest types",
		      namestr);
Evan Hunt's avatar
Evan Hunt committed
1276 1277 1278
	}

	vbprintf(1, "verify DNSKEY signature(s)\n");
1279
	if (!signed_strict(&new_ds_set, matching_sigs(new_key_tbl, &dnskey_set,
Evan Hunt's avatar
Evan Hunt committed
1280 1281
						      &dnskey_sig)))
	{
Evan Hunt's avatar
Evan Hunt committed
1282
		fatal("could not validate child DNSKEY RRset "
1283 1284
		      "with new DS records for %s",
		      namestr);
Evan Hunt's avatar
Evan Hunt committed
1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307
	}

	free_keytable(&new_key_tbl);

	/*
	 * OK, it's all good!
	 */
	if (nsupdate) {
		nsdiff(ttl, &old_ds_set, &new_ds_set);
	}

	write_parent_set(ds_path, inplace, nsupdate, &new_ds_set);

	free_all_sets();
	cleanup_logging(&lctx);
	dst_lib_destroy();
	if (verbose > 10) {
		isc_mem_stats(mctx, stdout);
	}
	isc_mem_destroy(&mctx);

	exit(0);
}