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