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