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