1 /* $NetBSD: dnssec-importkey.c,v 1.10 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 <stdbool.h> 19 #include <stdlib.h> 20 21 #include <isc/attributes.h> 22 #include <isc/buffer.h> 23 #include <isc/commandline.h> 24 #include <isc/hash.h> 25 #include <isc/mem.h> 26 #include <isc/result.h> 27 #include <isc/string.h> 28 #include <isc/util.h> 29 30 #include <dns/callbacks.h> 31 #include <dns/db.h> 32 #include <dns/dbiterator.h> 33 #include <dns/ds.h> 34 #include <dns/fixedname.h> 35 #include <dns/keyvalues.h> 36 #include <dns/log.h> 37 #include <dns/master.h> 38 #include <dns/name.h> 39 #include <dns/rdata.h> 40 #include <dns/rdataclass.h> 41 #include <dns/rdataset.h> 42 #include <dns/rdatasetiter.h> 43 #include <dns/rdatatype.h> 44 45 #include <dst/dst.h> 46 47 #include "dnssectool.h" 48 49 const char *program = "dnssec-importkey"; 50 51 static dns_rdataclass_t rdclass; 52 static dns_fixedname_t fixed; 53 static dns_name_t *name = NULL; 54 static isc_mem_t *mctx = NULL; 55 static bool setpub = false, setdel = false; 56 static bool setttl = false; 57 static isc_stdtime_t pub = 0, del = 0; 58 static dns_ttl_t ttl = 0; 59 static isc_stdtime_t syncadd = 0, syncdel = 0; 60 static bool setsyncadd = false; 61 static bool setsyncdel = false; 62 63 static isc_result_t 64 initname(char *setname) { 65 isc_result_t result; 66 isc_buffer_t buf; 67 68 name = dns_fixedname_initname(&fixed); 69 70 isc_buffer_init(&buf, setname, strlen(setname)); 71 isc_buffer_add(&buf, strlen(setname)); 72 result = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL); 73 return result; 74 } 75 76 static void 77 db_load_from_stream(dns_db_t *db, FILE *fp) { 78 isc_result_t result; 79 dns_rdatacallbacks_t callbacks; 80 81 dns_rdatacallbacks_init(&callbacks); 82 result = dns_db_beginload(db, &callbacks); 83 if (result != ISC_R_SUCCESS) { 84 fatal("dns_db_beginload failed: %s", isc_result_totext(result)); 85 } 86 87 result = dns_master_loadstream(fp, name, name, rdclass, 0, &callbacks, 88 mctx); 89 if (result != ISC_R_SUCCESS) { 90 fatal("can't load from input: %s", isc_result_totext(result)); 91 } 92 93 result = dns_db_endload(db, &callbacks); 94 if (result != ISC_R_SUCCESS) { 95 fatal("dns_db_endload failed: %s", isc_result_totext(result)); 96 } 97 } 98 99 static isc_result_t 100 loadset(const char *filename, dns_rdataset_t *rdataset) { 101 isc_result_t result; 102 dns_db_t *db = NULL; 103 dns_dbnode_t *node = NULL; 104 char setname[DNS_NAME_FORMATSIZE]; 105 106 dns_name_format(name, setname, sizeof(setname)); 107 108 result = dns_db_create(mctx, ZONEDB_DEFAULT, name, dns_dbtype_zone, 109 rdclass, 0, NULL, &db); 110 if (result != ISC_R_SUCCESS) { 111 fatal("can't create database"); 112 } 113 114 if (strcmp(filename, "-") == 0) { 115 db_load_from_stream(db, stdin); 116 filename = "input"; 117 } else { 118 result = dns_db_load(db, filename, dns_masterformat_text, 119 DNS_MASTER_NOTTL); 120 if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) { 121 fatal("can't load %s: %s", filename, 122 isc_result_totext(result)); 123 } 124 } 125 126 result = dns_db_findnode(db, name, false, &node); 127 if (result != ISC_R_SUCCESS) { 128 fatal("can't find %s node in %s", setname, filename); 129 } 130 131 result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_dnskey, 0, 0, 132 rdataset, NULL); 133 134 if (result == ISC_R_NOTFOUND) { 135 fatal("no DNSKEY RR for %s in %s", setname, filename); 136 } else if (result != ISC_R_SUCCESS) { 137 fatal("dns_db_findrdataset"); 138 } 139 140 if (node != NULL) { 141 dns_db_detachnode(db, &node); 142 } 143 if (db != NULL) { 144 dns_db_detach(&db); 145 } 146 return result; 147 } 148 149 static void 150 loadkey(char *filename, unsigned char *key_buf, unsigned int key_buf_size, 151 dns_rdata_t *rdata) { 152 isc_result_t result; 153 dst_key_t *key = NULL; 154 isc_buffer_t keyb; 155 isc_region_t r; 156 157 dns_rdata_init(rdata); 158 159 isc_buffer_init(&keyb, key_buf, key_buf_size); 160 161 result = dst_key_fromnamedfile(filename, NULL, DST_TYPE_PUBLIC, mctx, 162 &key); 163 if (result != ISC_R_SUCCESS) { 164 fatal("invalid keyfile name %s: %s", filename, 165 isc_result_totext(result)); 166 } 167 168 if (verbose > 2) { 169 char keystr[DST_KEY_FORMATSIZE]; 170 171 dst_key_format(key, keystr, sizeof(keystr)); 172 fprintf(stderr, "%s: %s\n", program, keystr); 173 } 174 175 result = dst_key_todns(key, &keyb); 176 if (result != ISC_R_SUCCESS) { 177 fatal("can't decode key"); 178 } 179 180 isc_buffer_usedregion(&keyb, &r); 181 dns_rdata_fromregion(rdata, dst_key_class(key), dns_rdatatype_dnskey, 182 &r); 183 184 rdclass = dst_key_class(key); 185 186 name = dns_fixedname_initname(&fixed); 187 dns_name_copy(dst_key_name(key), name); 188 189 dst_key_free(&key); 190 } 191 192 static void 193 emit(const char *dir, dns_rdata_t *rdata) { 194 isc_result_t result; 195 char keystr[DST_KEY_FORMATSIZE]; 196 char pubname[1024]; 197 char priname[1024]; 198 isc_buffer_t buf; 199 dst_key_t *key = NULL, *tmp = NULL; 200 201 isc_buffer_init(&buf, rdata->data, rdata->length); 202 isc_buffer_add(&buf, rdata->length); 203 result = dst_key_fromdns(name, rdclass, &buf, mctx, &key); 204 if (result != ISC_R_SUCCESS) { 205 fatal("dst_key_fromdns: %s", isc_result_totext(result)); 206 } 207 208 isc_buffer_init(&buf, pubname, sizeof(pubname)); 209 result = dst_key_buildfilename(key, DST_TYPE_PUBLIC, dir, &buf); 210 if (result != ISC_R_SUCCESS) { 211 fatal("Failed to build public key filename: %s", 212 isc_result_totext(result)); 213 } 214 isc_buffer_init(&buf, priname, sizeof(priname)); 215 result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, dir, &buf); 216 if (result != ISC_R_SUCCESS) { 217 fatal("Failed to build private key filename: %s", 218 isc_result_totext(result)); 219 } 220 221 result = dst_key_fromfile( 222 dst_key_name(key), dst_key_id(key), dst_key_alg(key), 223 DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, dir, mctx, &tmp); 224 if (result == ISC_R_SUCCESS) { 225 if (dst_key_isprivate(tmp) && !dst_key_isexternal(tmp)) { 226 fatal("Private key already exists in %s", priname); 227 } 228 dst_key_free(&tmp); 229 } 230 231 dst_key_setexternal(key, true); 232 if (setpub) { 233 dst_key_settime(key, DST_TIME_PUBLISH, pub); 234 } 235 if (setdel) { 236 dst_key_settime(key, DST_TIME_DELETE, del); 237 } 238 if (setsyncadd) { 239 dst_key_settime(key, DST_TIME_SYNCPUBLISH, syncadd); 240 } 241 if (setsyncdel) { 242 dst_key_settime(key, DST_TIME_SYNCDELETE, syncdel); 243 } 244 245 if (setttl) { 246 dst_key_setttl(key, ttl); 247 } 248 249 result = dst_key_tofile(key, DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, dir); 250 if (result != ISC_R_SUCCESS) { 251 dst_key_format(key, keystr, sizeof(keystr)); 252 fatal("Failed to write key %s: %s", keystr, 253 isc_result_totext(result)); 254 } 255 printf("%s\n", pubname); 256 257 isc_buffer_clear(&buf); 258 result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, dir, &buf); 259 if (result != ISC_R_SUCCESS) { 260 fatal("Failed to build private key filename: %s", 261 isc_result_totext(result)); 262 } 263 printf("%s\n", priname); 264 dst_key_free(&key); 265 } 266 267 noreturn static void 268 usage(void); 269 270 static void 271 usage(void) { 272 fprintf(stderr, "Usage:\n"); 273 fprintf(stderr, " %s options [-K dir] keyfile\n\n", program); 274 fprintf(stderr, " %s options -f file [keyname]\n\n", program); 275 fprintf(stderr, "Version: %s\n", PACKAGE_VERSION); 276 fprintf(stderr, "Options:\n"); 277 fprintf(stderr, " -f file: read key from zone file\n"); 278 fprintf(stderr, " -K <directory>: directory in which to store " 279 "the key files\n"); 280 fprintf(stderr, " -L ttl: set default key TTL\n"); 281 fprintf(stderr, " -v <verbose level>\n"); 282 fprintf(stderr, " -V: print version information\n"); 283 fprintf(stderr, " -h: print usage and exit\n"); 284 fprintf(stderr, "Timing options:\n"); 285 fprintf(stderr, " -P date/[+-]offset/none: set/unset key " 286 "publication date\n"); 287 fprintf(stderr, " -P sync date/[+-]offset/none: set/unset " 288 "CDS and CDNSKEY publication date\n"); 289 fprintf(stderr, " -D date/[+-]offset/none: set/unset key " 290 "deletion date\n"); 291 fprintf(stderr, " -D sync date/[+-]offset/none: set/unset " 292 "CDS and CDNSKEY deletion date\n"); 293 294 exit(EXIT_FAILURE); 295 } 296 297 int 298 main(int argc, char **argv) { 299 char *classname = NULL; 300 char *filename = NULL, *dir = NULL, *namestr; 301 char *endp; 302 int ch; 303 isc_result_t result; 304 isc_log_t *log = NULL; 305 dns_rdataset_t rdataset; 306 dns_rdata_t rdata; 307 isc_stdtime_t now = isc_stdtime_now(); 308 309 dns_rdata_init(&rdata); 310 311 if (argc == 1) { 312 usage(); 313 } 314 315 isc_mem_create(&mctx); 316 317 isc_commandline_errprint = false; 318 319 #define CMDLINE_FLAGS "D:f:hK:L:P:v:V" 320 while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { 321 switch (ch) { 322 case 'D': 323 /* -Dsync ? */ 324 if (isoptarg("sync", argv, usage)) { 325 if (setsyncdel) { 326 fatal("-D sync specified more than " 327 "once"); 328 } 329 330 syncdel = strtotime(isc_commandline_argument, 331 now, now, &setsyncdel); 332 break; 333 } 334 /* -Ddnskey ? */ 335 (void)isoptarg("dnskey", argv, usage); 336 if (setdel) { 337 fatal("-D specified more than once"); 338 } 339 340 del = strtotime(isc_commandline_argument, now, now, 341 &setdel); 342 break; 343 case 'K': 344 dir = isc_commandline_argument; 345 if (strlen(dir) == 0U) { 346 fatal("directory must be non-empty string"); 347 } 348 break; 349 case 'L': 350 ttl = strtottl(isc_commandline_argument); 351 setttl = true; 352 break; 353 case 'P': 354 /* -Psync ? */ 355 if (isoptarg("sync", argv, usage)) { 356 if (setsyncadd) { 357 fatal("-P sync specified more than " 358 "once"); 359 } 360 361 syncadd = strtotime(isc_commandline_argument, 362 now, now, &setsyncadd); 363 break; 364 } 365 /* -Pdnskey ? */ 366 (void)isoptarg("dnskey", argv, usage); 367 if (setpub) { 368 fatal("-P specified more than once"); 369 } 370 371 pub = strtotime(isc_commandline_argument, now, now, 372 &setpub); 373 break; 374 case 'f': 375 filename = isc_commandline_argument; 376 break; 377 case 'v': 378 verbose = strtol(isc_commandline_argument, &endp, 0); 379 if (*endp != '\0') { 380 fatal("-v must be followed by a number"); 381 } 382 break; 383 case '?': 384 if (isc_commandline_option != '?') { 385 fprintf(stderr, "%s: invalid argument -%c\n", 386 program, isc_commandline_option); 387 } 388 FALLTHROUGH; 389 case 'h': 390 /* Does not return. */ 391 usage(); 392 393 case 'V': 394 /* Does not return. */ 395 version(program); 396 397 default: 398 fprintf(stderr, "%s: unhandled option -%c\n", program, 399 isc_commandline_option); 400 exit(EXIT_FAILURE); 401 } 402 } 403 404 rdclass = strtoclass(classname); 405 406 if (argc < isc_commandline_index + 1 && filename == NULL) { 407 fatal("the key file name was not specified"); 408 } 409 if (argc > isc_commandline_index + 1) { 410 fatal("extraneous arguments"); 411 } 412 413 result = dst_lib_init(mctx, NULL); 414 if (result != ISC_R_SUCCESS) { 415 fatal("could not initialize dst: %s", 416 isc_result_totext(result)); 417 } 418 419 setup_logging(mctx, &log); 420 421 dns_rdataset_init(&rdataset); 422 423 if (filename != NULL) { 424 if (argc < isc_commandline_index + 1) { 425 /* using filename as zone name */ 426 namestr = filename; 427 } else { 428 namestr = argv[isc_commandline_index]; 429 } 430 431 result = initname(namestr); 432 if (result != ISC_R_SUCCESS) { 433 fatal("could not initialize name %s", namestr); 434 } 435 436 result = loadset(filename, &rdataset); 437 438 if (result != ISC_R_SUCCESS) { 439 fatal("could not load DNSKEY set: %s\n", 440 isc_result_totext(result)); 441 } 442 443 for (result = dns_rdataset_first(&rdataset); 444 result == ISC_R_SUCCESS; 445 result = dns_rdataset_next(&rdataset)) 446 { 447 dns_rdata_init(&rdata); 448 dns_rdataset_current(&rdataset, &rdata); 449 emit(dir, &rdata); 450 } 451 } else { 452 unsigned char key_buf[DST_KEY_MAXSIZE]; 453 454 loadkey(argv[isc_commandline_index], key_buf, DST_KEY_MAXSIZE, 455 &rdata); 456 457 emit(dir, &rdata); 458 } 459 460 if (dns_rdataset_isassociated(&rdataset)) { 461 dns_rdataset_disassociate(&rdataset); 462 } 463 cleanup_logging(&log); 464 dst_lib_destroy(); 465 if (verbose > 10) { 466 isc_mem_stats(mctx, stdout); 467 } 468 isc_mem_destroy(&mctx); 469 470 fflush(stdout); 471 if (ferror(stdout)) { 472 fprintf(stderr, "write error\n"); 473 return 1; 474 } else { 475 return 0; 476 } 477 } 478