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