xref: /netbsd-src/external/mpl/bind/dist/bin/dnssec/dnssec-importkey.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
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