1 /* $NetBSD: dnssec-dsfromkey.c,v 1.14 2026/01/29 18:36:26 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 /*! \file */ 17 18 #include <inttypes.h> 19 #include <stdbool.h> 20 #include <stdlib.h> 21 22 #include <isc/attributes.h> 23 #include <isc/buffer.h> 24 #include <isc/commandline.h> 25 #include <isc/dir.h> 26 #include <isc/hash.h> 27 #include <isc/mem.h> 28 #include <isc/result.h> 29 #include <isc/string.h> 30 #include <isc/urcu.h> 31 #include <isc/util.h> 32 33 #include <dns/callbacks.h> 34 #include <dns/db.h> 35 #include <dns/dbiterator.h> 36 #include <dns/ds.h> 37 #include <dns/fixedname.h> 38 #include <dns/keyvalues.h> 39 #include <dns/log.h> 40 #include <dns/master.h> 41 #include <dns/name.h> 42 #include <dns/rdata.h> 43 #include <dns/rdataclass.h> 44 #include <dns/rdataset.h> 45 #include <dns/rdatasetiter.h> 46 #include <dns/rdatatype.h> 47 48 #include <dst/dst.h> 49 50 #include "dnssectool.h" 51 52 const char *program = "dnssec-dsfromkey"; 53 54 static dns_rdataclass_t rdclass; 55 static dns_fixedname_t fixed; 56 static dns_name_t *name = NULL; 57 static isc_mem_t *mctx = NULL; 58 static uint32_t ttl; 59 static bool emitttl = false; 60 static unsigned int split_width = 0; 61 62 static isc_result_t 63 initname(char *setname) { 64 isc_result_t result; 65 isc_buffer_t buf; 66 67 name = dns_fixedname_initname(&fixed); 68 69 isc_buffer_init(&buf, setname, strlen(setname)); 70 isc_buffer_add(&buf, strlen(setname)); 71 result = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL); 72 return result; 73 } 74 75 static void 76 db_load_from_stream(dns_db_t *db, FILE *fp) { 77 isc_result_t result; 78 dns_rdatacallbacks_t callbacks; 79 80 dns_rdatacallbacks_init(&callbacks); 81 result = dns_db_beginload(db, &callbacks); 82 if (result != ISC_R_SUCCESS) { 83 fatal("dns_db_beginload failed: %s", isc_result_totext(result)); 84 } 85 86 result = dns_master_loadstream(fp, name, name, rdclass, 0, &callbacks, 87 mctx); 88 if (result != ISC_R_SUCCESS) { 89 fatal("can't load from input: %s", isc_result_totext(result)); 90 } 91 92 result = dns_db_endload(db, &callbacks); 93 if (result != ISC_R_SUCCESS) { 94 fatal("dns_db_endload failed: %s", isc_result_totext(result)); 95 } 96 } 97 98 static isc_result_t 99 loadset(const char *filename, dns_rdataset_t *rdataset) { 100 isc_result_t result; 101 dns_db_t *db = NULL; 102 dns_dbnode_t *node = NULL; 103 char setname[DNS_NAME_FORMATSIZE]; 104 105 dns_name_format(name, setname, sizeof(setname)); 106 107 result = dns_db_create(mctx, ZONEDB_DEFAULT, name, dns_dbtype_zone, 108 rdclass, 0, NULL, &db); 109 if (result != ISC_R_SUCCESS) { 110 fatal("can't create database"); 111 } 112 113 if (strcmp(filename, "-") == 0) { 114 db_load_from_stream(db, stdin); 115 filename = "input"; 116 } else { 117 result = dns_db_load(db, filename, dns_masterformat_text, 0); 118 if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) { 119 fatal("can't load %s: %s", filename, 120 isc_result_totext(result)); 121 } 122 } 123 124 result = dns_db_findnode(db, name, false, &node); 125 if (result != ISC_R_SUCCESS) { 126 fatal("can't find %s node in %s", setname, filename); 127 } 128 129 result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_dnskey, 0, 0, 130 rdataset, NULL); 131 132 if (result == ISC_R_NOTFOUND) { 133 fatal("no DNSKEY RR for %s in %s", setname, filename); 134 } else if (result != ISC_R_SUCCESS) { 135 fatal("dns_db_findrdataset"); 136 } 137 138 if (node != NULL) { 139 dns_db_detachnode(db, &node); 140 } 141 if (db != NULL) { 142 dns_db_detach(&db); 143 } 144 return result; 145 } 146 147 static isc_result_t 148 loadkeyset(char *dirname, dns_rdataset_t *rdataset) { 149 isc_result_t result; 150 char filename[PATH_MAX + 1]; 151 isc_buffer_t buf; 152 153 dns_rdataset_init(rdataset); 154 155 isc_buffer_init(&buf, filename, sizeof(filename)); 156 if (dirname != NULL) { 157 /* allow room for a trailing slash */ 158 if (strlen(dirname) >= isc_buffer_availablelength(&buf)) { 159 return ISC_R_NOSPACE; 160 } 161 isc_buffer_putstr(&buf, dirname); 162 if (dirname[strlen(dirname) - 1] != '/') { 163 isc_buffer_putstr(&buf, "/"); 164 } 165 } 166 167 if (isc_buffer_availablelength(&buf) < 7) { 168 return ISC_R_NOSPACE; 169 } 170 isc_buffer_putstr(&buf, "keyset-"); 171 172 result = dns_name_tofilenametext(name, false, &buf); 173 check_result(result, "dns_name_tofilenametext()"); 174 if (isc_buffer_availablelength(&buf) == 0) { 175 return ISC_R_NOSPACE; 176 } 177 isc_buffer_putuint8(&buf, 0); 178 179 return loadset(filename, rdataset); 180 } 181 182 static void 183 loadkey(char *filename, unsigned char *key_buf, unsigned int key_buf_size, 184 dns_rdata_t *rdata) { 185 isc_result_t result; 186 dst_key_t *key = NULL; 187 isc_buffer_t keyb; 188 isc_region_t r; 189 190 dns_rdata_init(rdata); 191 192 isc_buffer_init(&keyb, key_buf, key_buf_size); 193 194 result = dst_key_fromnamedfile(filename, NULL, DST_TYPE_PUBLIC, mctx, 195 &key); 196 if (result != ISC_R_SUCCESS) { 197 fatal("can't load %s.key: %s", filename, 198 isc_result_totext(result)); 199 } 200 201 if (verbose > 2) { 202 char keystr[DST_KEY_FORMATSIZE]; 203 204 dst_key_format(key, keystr, sizeof(keystr)); 205 fprintf(stderr, "%s: %s\n", program, keystr); 206 } 207 208 result = dst_key_todns(key, &keyb); 209 if (result != ISC_R_SUCCESS) { 210 fatal("can't decode key"); 211 } 212 213 isc_buffer_usedregion(&keyb, &r); 214 dns_rdata_fromregion(rdata, dst_key_class(key), dns_rdatatype_dnskey, 215 &r); 216 217 rdclass = dst_key_class(key); 218 219 name = dns_fixedname_initname(&fixed); 220 dns_name_copy(dst_key_name(key), name); 221 222 dst_key_free(&key); 223 } 224 225 static void 226 logkey(dns_rdata_t *rdata) { 227 isc_result_t result; 228 dst_key_t *key = NULL; 229 isc_buffer_t buf; 230 char keystr[DST_KEY_FORMATSIZE]; 231 232 isc_buffer_init(&buf, rdata->data, rdata->length); 233 isc_buffer_add(&buf, rdata->length); 234 result = dst_key_fromdns(name, rdclass, &buf, mctx, &key); 235 if (result != ISC_R_SUCCESS) { 236 return; 237 } 238 239 dst_key_format(key, keystr, sizeof(keystr)); 240 fprintf(stderr, "%s: %s\n", program, keystr); 241 242 dst_key_free(&key); 243 } 244 245 static void 246 emit(dns_dsdigest_t dt, bool showall, bool cds, dns_rdata_t *rdata) { 247 isc_result_t result; 248 unsigned char buf[DNS_DS_BUFFERSIZE]; 249 char text_buf[DST_KEY_MAXTEXTSIZE]; 250 char name_buf[DNS_NAME_MAXWIRE]; 251 char class_buf[10]; 252 isc_buffer_t textb, nameb, classb; 253 isc_region_t r; 254 dns_rdata_t ds; 255 dns_rdata_dnskey_t dnskey; 256 257 isc_buffer_init(&textb, text_buf, sizeof(text_buf)); 258 isc_buffer_init(&nameb, name_buf, sizeof(name_buf)); 259 isc_buffer_init(&classb, class_buf, sizeof(class_buf)); 260 261 dns_rdata_init(&ds); 262 263 result = dns_rdata_tostruct(rdata, &dnskey, NULL); 264 if (result != ISC_R_SUCCESS) { 265 fatal("can't convert DNSKEY"); 266 } 267 268 if ((dnskey.flags & DNS_KEYFLAG_REVOKE) != 0) { 269 return; 270 } 271 272 if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0 && !showall) { 273 return; 274 } 275 276 result = dns_ds_buildrdata(name, rdata, dt, buf, &ds); 277 if (result != ISC_R_SUCCESS) { 278 fatal("can't build record"); 279 } 280 281 result = dns_name_totext(name, 0, &nameb); 282 if (result != ISC_R_SUCCESS) { 283 fatal("can't print name"); 284 } 285 286 result = dns_rdata_tofmttext(&ds, (dns_name_t *)NULL, 0, 0, split_width, 287 "", &textb); 288 289 if (result != ISC_R_SUCCESS) { 290 fatal("can't print rdata"); 291 } 292 293 result = dns_rdataclass_totext(rdclass, &classb); 294 if (result != ISC_R_SUCCESS) { 295 fatal("can't print class"); 296 } 297 298 isc_buffer_usedregion(&nameb, &r); 299 printf("%.*s ", (int)r.length, r.base); 300 301 if (emitttl) { 302 printf("%u ", ttl); 303 } 304 305 isc_buffer_usedregion(&classb, &r); 306 printf("%.*s", (int)r.length, r.base); 307 308 if (cds) { 309 printf(" CDS "); 310 } else { 311 printf(" DS "); 312 } 313 314 isc_buffer_usedregion(&textb, &r); 315 printf("%.*s\n", (int)r.length, r.base); 316 } 317 318 static void 319 emits(bool showall, bool cds, dns_rdata_t *rdata) { 320 unsigned int i, n; 321 322 n = sizeof(dtype) / sizeof(dtype[0]); 323 for (i = 0; i < n; i++) { 324 if (dtype[i] == DNS_DSDIGEST_SHA1) { 325 fprintf(stderr, 326 "WARNING: DS digest type %u is deprecated\n", 327 i); 328 } 329 if (dtype[i] != 0) { 330 emit(dtype[i], showall, cds, rdata); 331 } 332 } 333 } 334 335 noreturn static void 336 usage(int ret); 337 338 static void 339 usage(int ret) { 340 fprintf(stderr, "Usage:\n"); 341 fprintf(stderr, " %s [options] keyfile\n\n", program); 342 fprintf(stderr, " %s [options] -f zonefile [zonename]\n\n", program); 343 fprintf(stderr, " %s [options] -s dnsname\n\n", program); 344 fprintf(stderr, " %s [-h|-V]\n\n", program); 345 fprintf(stderr, "Version: %s\n", PACKAGE_VERSION); 346 fprintf(stderr, "Options:\n" 347 " -1: digest algorithm SHA-1 (deprecated)\n" 348 " -2: digest algorithm SHA-256\n" 349 " -a algorithm: digest algorithm (SHA-1 " 350 "(deprecated), SHA-256 or SHA-384)\n" 351 " -A: include all keys in DS set, not just KSKs (-f " 352 "only)\n" 353 " -c class: rdata class for DS set (default IN) (-f " 354 "or -s only)\n" 355 " -C: print CDS records\n" 356 " -f zonefile: read keys from a zone file\n" 357 " -h: print help information\n" 358 " -K directory: where to find key or keyset files\n" 359 " -w split base64 rdata text into chunks\n" 360 " -s: read keys from keyset-<dnsname> file\n" 361 " -T: TTL of output records (omitted by default)\n" 362 " -v level: verbosity\n" 363 " -V: print version information\n"); 364 fprintf(stderr, "Output: DS or CDS RRs\n"); 365 366 exit(ret); 367 } 368 369 int 370 main(int argc, char **argv) { 371 char *classname = NULL; 372 char *filename = NULL, *dir = NULL, *namestr; 373 char *endp, *arg1; 374 int ch; 375 bool cds = false; 376 bool usekeyset = false; 377 bool showall = false; 378 isc_result_t result; 379 isc_log_t *log = NULL; 380 dns_rdataset_t rdataset; 381 dns_rdata_t rdata; 382 383 dns_rdata_init(&rdata); 384 385 if (argc == 1) { 386 usage(EXIT_FAILURE); 387 } 388 389 isc_mem_create(&mctx); 390 391 isc_commandline_errprint = false; 392 393 #define OPTIONS "12Aa:Cc:d:Ff:K:l:sT:v:whV" 394 while ((ch = isc_commandline_parse(argc, argv, OPTIONS)) != -1) { 395 switch (ch) { 396 case '1': 397 add_dtype(DNS_DSDIGEST_SHA1); 398 break; 399 case '2': 400 add_dtype(DNS_DSDIGEST_SHA256); 401 break; 402 case 'A': 403 showall = true; 404 break; 405 case 'a': 406 add_dtype(strtodsdigest(isc_commandline_argument)); 407 break; 408 case 'C': 409 cds = true; 410 break; 411 case 'c': 412 classname = isc_commandline_argument; 413 break; 414 case 'd': 415 fprintf(stderr, 416 "%s: the -d option is deprecated; " 417 "use -K\n", 418 program); 419 /* fall through */ 420 case 'K': 421 dir = isc_commandline_argument; 422 if (strlen(dir) == 0U) { 423 fatal("directory must be non-empty string"); 424 } 425 break; 426 case 'f': 427 filename = isc_commandline_argument; 428 break; 429 case 'l': 430 fatal("-l option (DLV lookaside) is obsolete"); 431 break; 432 case 's': 433 usekeyset = true; 434 break; 435 case 'T': 436 emitttl = true; 437 ttl = strtottl(isc_commandline_argument); 438 break; 439 case 'v': 440 verbose = strtol(isc_commandline_argument, &endp, 0); 441 if (*endp != '\0') { 442 fatal("-v must be followed by a number"); 443 } 444 break; 445 case 'w': 446 split_width = UINT_MAX; 447 break; 448 case 'F': 449 /* Reserved for FIPS mode */ 450 FALLTHROUGH; 451 case '?': 452 if (isc_commandline_option != '?') { 453 fprintf(stderr, "%s: invalid argument -%c\n", 454 program, isc_commandline_option); 455 } 456 /* Does not return. */ 457 usage(EXIT_FAILURE); 458 459 case 'h': 460 /* Does not return. */ 461 usage(EXIT_SUCCESS); 462 463 case 'V': 464 /* Does not return. */ 465 version(program); 466 467 default: 468 fprintf(stderr, "%s: unhandled option -%c\n", program, 469 isc_commandline_option); 470 exit(EXIT_FAILURE); 471 } 472 } 473 474 rdclass = strtoclass(classname); 475 476 if (usekeyset && filename != NULL) { 477 fatal("cannot use both -s and -f"); 478 } 479 480 /* When not using -f, -A is implicit */ 481 if (filename == NULL) { 482 showall = true; 483 } 484 485 /* Default digest type if none specified. */ 486 if (dtype[0] == 0) { 487 dtype[0] = DNS_DSDIGEST_SHA256; 488 } 489 490 /* 491 * Use local variable arg1 so that clang can correctly analyse 492 * reachable paths rather than 'argc < isc_commandline_index + 1'. 493 */ 494 arg1 = argv[isc_commandline_index]; 495 if (arg1 == NULL && filename == NULL) { 496 fatal("the key file name was not specified"); 497 } 498 if (arg1 != NULL && argv[isc_commandline_index + 1] != NULL) { 499 fatal("extraneous arguments"); 500 } 501 502 result = dst_lib_init(mctx, NULL); 503 if (result != ISC_R_SUCCESS) { 504 fatal("could not initialize dst: %s", 505 isc_result_totext(result)); 506 } 507 508 setup_logging(mctx, &log); 509 510 dns_rdataset_init(&rdataset); 511 512 if (usekeyset || filename != NULL) { 513 if (arg1 == NULL) { 514 /* using file name as the zone name */ 515 namestr = filename; 516 } else { 517 namestr = arg1; 518 } 519 520 result = initname(namestr); 521 if (result != ISC_R_SUCCESS) { 522 fatal("could not initialize name %s", namestr); 523 } 524 525 if (usekeyset) { 526 result = loadkeyset(dir, &rdataset); 527 } else { 528 INSIST(filename != NULL); 529 result = loadset(filename, &rdataset); 530 } 531 532 if (result != ISC_R_SUCCESS) { 533 fatal("could not load DNSKEY set: %s\n", 534 isc_result_totext(result)); 535 } 536 537 for (result = dns_rdataset_first(&rdataset); 538 result == ISC_R_SUCCESS; 539 result = dns_rdataset_next(&rdataset)) 540 { 541 dns_rdata_init(&rdata); 542 dns_rdataset_current(&rdataset, &rdata); 543 544 if (verbose > 2) { 545 logkey(&rdata); 546 } 547 548 emits(showall, cds, &rdata); 549 } 550 } else { 551 unsigned char key_buf[DST_KEY_MAXSIZE]; 552 553 loadkey(arg1, key_buf, DST_KEY_MAXSIZE, &rdata); 554 555 emits(showall, cds, &rdata); 556 } 557 558 if (dns_rdataset_isassociated(&rdataset)) { 559 dns_rdataset_disassociate(&rdataset); 560 } 561 cleanup_logging(&log); 562 dst_lib_destroy(); 563 if (verbose > 10) { 564 isc_mem_stats(mctx, stdout); 565 } 566 isc_mem_destroy(&mctx); 567 568 rcu_barrier(); 569 570 fflush(stdout); 571 if (ferror(stdout)) { 572 fprintf(stderr, "write error\n"); 573 return 1; 574 } else { 575 return 0; 576 } 577 } 578