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