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