1/* $NetBSD: dnssec-dsfromkey.c,v 1.11 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/*! \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/print.h> 29#include <isc/result.h> 30#include <isc/string.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 52const char *program = "dnssec-dsfromkey"; 53 54static dns_rdataclass_t rdclass; 55static dns_fixedname_t fixed; 56static dns_name_t *name = NULL; 57static isc_mem_t *mctx = NULL; 58static uint32_t ttl; 59static bool emitttl = false; 60 61static isc_result_t 62initname(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 74static void 75db_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 97static isc_result_t 98loadset(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, "rbt", name, dns_dbtype_zone, rdclass, 0, 107 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 146static isc_result_t 147loadkeyset(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 181static void 182loadkey(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 224static void 225logkey(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 244static void 245emit(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, false, &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, 0, "", 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 317static void 318emits(bool showall, bool cds, dns_rdata_t *rdata) { 319 unsigned 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 329noreturn static void 330usage(void); 331 332static void 333usage(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 " -s: read keys from keyset-<dnsname> file\n" 354 " -T: TTL of output records (omitted by default)\n" 355 " -v level: verbosity\n" 356 " -V: print version information\n"); 357 fprintf(stderr, "Output: DS or CDS RRs\n"); 358 359 exit(-1); 360} 361 362int 363main(int argc, char **argv) { 364 char *classname = NULL; 365 char *filename = NULL, *dir = NULL, *namestr; 366 char *endp, *arg1; 367 int ch; 368 bool cds = false; 369 bool usekeyset = false; 370 bool showall = false; 371 isc_result_t result; 372 isc_log_t *log = NULL; 373 dns_rdataset_t rdataset; 374 dns_rdata_t rdata; 375 376 dns_rdata_init(&rdata); 377 378 if (argc == 1) { 379 usage(); 380 } 381 382 isc_mem_create(&mctx); 383 384 isc_commandline_errprint = false; 385 386#define OPTIONS "12Aa:Cc:d:Ff:K:l:sT:v:hV" 387 while ((ch = isc_commandline_parse(argc, argv, OPTIONS)) != -1) { 388 switch (ch) { 389 case '1': 390 add_dtype(DNS_DSDIGEST_SHA1); 391 break; 392 case '2': 393 add_dtype(DNS_DSDIGEST_SHA256); 394 break; 395 case 'A': 396 showall = true; 397 break; 398 case 'a': 399 add_dtype(strtodsdigest(isc_commandline_argument)); 400 break; 401 case 'C': 402 cds = true; 403 break; 404 case 'c': 405 classname = isc_commandline_argument; 406 break; 407 case 'd': 408 fprintf(stderr, 409 "%s: the -d option is deprecated; " 410 "use -K\n", 411 program); 412 /* fall through */ 413 case 'K': 414 dir = isc_commandline_argument; 415 if (strlen(dir) == 0U) { 416 fatal("directory must be non-empty string"); 417 } 418 break; 419 case 'f': 420 filename = isc_commandline_argument; 421 break; 422 case 'l': 423 fatal("-l option (DLV lookaside) is obsolete"); 424 break; 425 case 's': 426 usekeyset = true; 427 break; 428 case 'T': 429 emitttl = true; 430 ttl = strtottl(isc_commandline_argument); 431 break; 432 case 'v': 433 verbose = strtol(isc_commandline_argument, &endp, 0); 434 if (*endp != '\0') { 435 fatal("-v must be followed by a number"); 436 } 437 break; 438 case 'F': 439 /* Reserved for FIPS mode */ 440 FALLTHROUGH; 441 case '?': 442 if (isc_commandline_option != '?') { 443 fprintf(stderr, "%s: invalid argument -%c\n", 444 program, isc_commandline_option); 445 } 446 FALLTHROUGH; 447 case 'h': 448 /* Does not return. */ 449 usage(); 450 451 case 'V': 452 /* Does not return. */ 453 version(program); 454 455 default: 456 fprintf(stderr, "%s: unhandled option -%c\n", program, 457 isc_commandline_option); 458 exit(1); 459 } 460 } 461 462 rdclass = strtoclass(classname); 463 464 if (usekeyset && filename != NULL) { 465 fatal("cannot use both -s and -f"); 466 } 467 468 /* When not using -f, -A is implicit */ 469 if (filename == NULL) { 470 showall = true; 471 } 472 473 /* Default digest type if none specified. */ 474 if (dtype[0] == 0) { 475 dtype[0] = DNS_DSDIGEST_SHA256; 476 } 477 478 /* 479 * Use local variable arg1 so that clang can correctly analyse 480 * reachable paths rather than 'argc < isc_commandline_index + 1'. 481 */ 482 arg1 = argv[isc_commandline_index]; 483 if (arg1 == NULL && filename == NULL) { 484 fatal("the key file name was not specified"); 485 } 486 if (arg1 != NULL && argv[isc_commandline_index + 1] != NULL) { 487 fatal("extraneous arguments"); 488 } 489 490 result = dst_lib_init(mctx, NULL); 491 if (result != ISC_R_SUCCESS) { 492 fatal("could not initialize dst: %s", 493 isc_result_totext(result)); 494 } 495 496 setup_logging(mctx, &log); 497 498 dns_rdataset_init(&rdataset); 499 500 if (usekeyset || filename != NULL) { 501 if (arg1 == NULL) { 502 /* using file name as the zone name */ 503 namestr = filename; 504 } else { 505 namestr = arg1; 506 } 507 508 result = initname(namestr); 509 if (result != ISC_R_SUCCESS) { 510 fatal("could not initialize name %s", namestr); 511 } 512 513 if (usekeyset) { 514 result = loadkeyset(dir, &rdataset); 515 } else { 516 INSIST(filename != NULL); 517 result = loadset(filename, &rdataset); 518 } 519 520 if (result != ISC_R_SUCCESS) { 521 fatal("could not load DNSKEY set: %s\n", 522 isc_result_totext(result)); 523 } 524 525 for (result = dns_rdataset_first(&rdataset); 526 result == ISC_R_SUCCESS; 527 result = dns_rdataset_next(&rdataset)) 528 { 529 dns_rdata_init(&rdata); 530 dns_rdataset_current(&rdataset, &rdata); 531 532 if (verbose > 2) { 533 logkey(&rdata); 534 } 535 536 emits(showall, cds, &rdata); 537 } 538 } else { 539 unsigned char key_buf[DST_KEY_MAXSIZE]; 540 541 loadkey(arg1, key_buf, DST_KEY_MAXSIZE, &rdata); 542 543 emits(showall, cds, &rdata); 544 } 545 546 if (dns_rdataset_isassociated(&rdataset)) { 547 dns_rdataset_disassociate(&rdataset); 548 } 549 cleanup_logging(&log); 550 dst_lib_destroy(); 551 if (verbose > 10) { 552 isc_mem_stats(mctx, stdout); 553 } 554 isc_mem_destroy(&mctx); 555 556 fflush(stdout); 557 if (ferror(stdout)) { 558 fprintf(stderr, "write error\n"); 559 return (1); 560 } else { 561 return (0); 562 } 563} 564