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