1 /*
2  * Copyright (C) 2009-2015  Internet Systems Consortium, Inc. ("ISC")
3  *
4  * Permission to use, copy, modify, and/or distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
9  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
11  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
13  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14  * PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 /*! \file */
18 
19 #include <config.h>
20 
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <errno.h>
24 #include <time.h>
25 
26 #include <isc/buffer.h>
27 #include <isc/commandline.h>
28 #include <isc/entropy.h>
29 #include <isc/file.h>
30 #include <isc/hash.h>
31 #include <isc/mem.h>
32 #include <isc/print.h>
33 #include <isc/string.h>
34 #include <isc/util.h>
35 
36 #include <dns/keyvalues.h>
37 #include <dns/result.h>
38 #include <dns/log.h>
39 
40 #include <dst/dst.h>
41 
42 #include "dnssectool.h"
43 
44 const char *program = "dnssec-settime";
45 int verbose;
46 
47 static isc_mem_t	*mctx = NULL;
48 
49 ISC_PLATFORM_NORETURN_PRE static void
50 usage(void) ISC_PLATFORM_NORETURN_POST;
51 
52 static void
usage(void)53 usage(void) {
54 	fprintf(stderr, "Usage:\n");
55 	fprintf(stderr,	"    %s [options] keyfile\n\n", program);
56 	fprintf(stderr, "Version: %s\n", VERSION);
57 	fprintf(stderr, "General options:\n");
58 #ifdef USE_PKCS11
59 	fprintf(stderr, "    -E engine:          specify OpenSSL engine "
60 						 "(default \"pkcs11\")\n");
61 #else
62 	fprintf(stderr, "    -E engine:          specify OpenSSL engine\n");
63 #endif
64 	fprintf(stderr, "    -f:                 force update of old-style "
65 						 "keys\n");
66 	fprintf(stderr, "    -K directory:       set key file location\n");
67 	fprintf(stderr, "    -L ttl:             set default key TTL\n");
68 	fprintf(stderr, "    -v level:           set level of verbosity\n");
69 	fprintf(stderr, "    -V:                 print version information\n");
70 	fprintf(stderr, "    -h:                 help\n");
71 	fprintf(stderr, "Timing options:\n");
72 	fprintf(stderr, "    -P date/[+-]offset/none: set/unset key "
73 						     "publication date\n");
74 	fprintf(stderr, "    -A date/[+-]offset/none: set/unset key "
75 						     "activation date\n");
76 	fprintf(stderr, "    -R date/[+-]offset/none: set/unset key "
77 						     "revocation date\n");
78 	fprintf(stderr, "    -I date/[+-]offset/none: set/unset key "
79 						     "inactivation date\n");
80 	fprintf(stderr, "    -D date/[+-]offset/none: set/unset key "
81 						     "deletion date\n");
82 	fprintf(stderr, "Printing options:\n");
83 	fprintf(stderr, "    -p C/P/A/R/I/D/all: print a particular time "
84 						"value or values\n");
85 	fprintf(stderr, "    -u:                 print times in unix epoch "
86 						"format\n");
87 	fprintf(stderr, "Output:\n");
88 	fprintf(stderr, "     K<name>+<alg>+<new id>.key, "
89 			     "K<name>+<alg>+<new id>.private\n");
90 
91 	exit (-1);
92 }
93 
94 static void
printtime(dst_key_t * key,int type,const char * tag,isc_boolean_t epoch,FILE * stream)95 printtime(dst_key_t *key, int type, const char *tag, isc_boolean_t epoch,
96 	  FILE *stream)
97 {
98 	isc_result_t result;
99 	const char *output = NULL;
100 	isc_stdtime_t when;
101 
102 	if (tag != NULL)
103 		fprintf(stream, "%s: ", tag);
104 
105 	result = dst_key_gettime(key, type, &when);
106 	if (result == ISC_R_NOTFOUND) {
107 		fprintf(stream, "UNSET\n");
108 	} else if (epoch) {
109 		fprintf(stream, "%d\n", (int) when);
110 	} else {
111 		time_t timet = when;
112 		output = ctime(&timet);
113 		fprintf(stream, "%s", output);
114 	}
115 }
116 
117 int
main(int argc,char ** argv)118 main(int argc, char **argv) {
119 	isc_result_t	result;
120 #ifdef USE_PKCS11
121 	const char	*engine = "pkcs11";
122 #else
123 	const char	*engine = NULL;
124 #endif
125 	const char 	*filename = NULL;
126 	char		*directory = NULL;
127 	char		newname[1024];
128 	char		keystr[DST_KEY_FORMATSIZE];
129 	char		*endp, *p;
130 	int		ch;
131 	isc_entropy_t	*ectx = NULL;
132 	const char	*predecessor = NULL;
133 	dst_key_t	*prevkey = NULL;
134 	dst_key_t	*key = NULL;
135 	isc_buffer_t	buf;
136 	dns_name_t	*name = NULL;
137 	dns_secalg_t 	alg = 0;
138 	unsigned int 	size = 0;
139 	isc_uint16_t	flags = 0;
140 	int		prepub = -1;
141 	dns_ttl_t	ttl = 0;
142 	isc_stdtime_t	now;
143 	isc_stdtime_t	pub = 0, act = 0, rev = 0, inact = 0, del = 0;
144 	isc_stdtime_t	prevact = 0, previnact = 0, prevdel = 0;
145 	isc_boolean_t	setpub = ISC_FALSE, setact = ISC_FALSE;
146 	isc_boolean_t	setrev = ISC_FALSE, setinact = ISC_FALSE;
147 	isc_boolean_t	setdel = ISC_FALSE, setttl = ISC_FALSE;
148 	isc_boolean_t	unsetpub = ISC_FALSE, unsetact = ISC_FALSE;
149 	isc_boolean_t	unsetrev = ISC_FALSE, unsetinact = ISC_FALSE;
150 	isc_boolean_t	unsetdel = ISC_FALSE;
151 	isc_boolean_t	printcreate = ISC_FALSE, printpub = ISC_FALSE;
152 	isc_boolean_t	printact = ISC_FALSE,  printrev = ISC_FALSE;
153 	isc_boolean_t	printinact = ISC_FALSE, printdel = ISC_FALSE;
154 	isc_boolean_t	force = ISC_FALSE;
155 	isc_boolean_t   epoch = ISC_FALSE;
156 	isc_boolean_t   changed = ISC_FALSE;
157 	isc_log_t       *log = NULL;
158 
159 	if (argc == 1)
160 		usage();
161 
162 	result = isc_mem_create(0, 0, &mctx);
163 	if (result != ISC_R_SUCCESS)
164 		fatal("Out of memory");
165 
166 	setup_logging(mctx, &log);
167 
168 	dns_result_register();
169 
170 	isc_commandline_errprint = ISC_FALSE;
171 
172 	isc_stdtime_get(&now);
173 
174 #define CMDLINE_FLAGS "A:D:E:fhI:i:K:L:P:p:R:S:uv:V"
175 	while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) {
176 		switch (ch) {
177 		case 'E':
178 			engine = isc_commandline_argument;
179 			break;
180 		case 'f':
181 			force = ISC_TRUE;
182 			break;
183 		case 'p':
184 			p = isc_commandline_argument;
185 			if (!strcasecmp(p, "all")) {
186 				printcreate = ISC_TRUE;
187 				printpub = ISC_TRUE;
188 				printact = ISC_TRUE;
189 				printrev = ISC_TRUE;
190 				printinact = ISC_TRUE;
191 				printdel = ISC_TRUE;
192 				break;
193 			}
194 
195 			do {
196 				switch (*p++) {
197 				case 'C':
198 					printcreate = ISC_TRUE;
199 					break;
200 				case 'P':
201 					printpub = ISC_TRUE;
202 					break;
203 				case 'A':
204 					printact = ISC_TRUE;
205 					break;
206 				case 'R':
207 					printrev = ISC_TRUE;
208 					break;
209 				case 'I':
210 					printinact = ISC_TRUE;
211 					break;
212 				case 'D':
213 					printdel = ISC_TRUE;
214 					break;
215 				case ' ':
216 					break;
217 				default:
218 					usage();
219 					break;
220 				}
221 			} while (*p != '\0');
222 			break;
223 		case 'u':
224 			epoch = ISC_TRUE;
225 			break;
226 		case 'K':
227 			/*
228 			 * We don't have to copy it here, but do it to
229 			 * simplify cleanup later
230 			 */
231 			directory = isc_mem_strdup(mctx,
232 						   isc_commandline_argument);
233 			if (directory == NULL) {
234 				fatal("Failed to allocate memory for "
235 				      "directory");
236 			}
237 			break;
238 		case 'L':
239 			ttl = strtottl(isc_commandline_argument);
240 			setttl = ISC_TRUE;
241 			break;
242 		case 'v':
243 			verbose = strtol(isc_commandline_argument, &endp, 0);
244 			if (*endp != '\0')
245 				fatal("-v must be followed by a number");
246 			break;
247 		case 'P':
248 			if (setpub || unsetpub)
249 				fatal("-P specified more than once");
250 
251 			changed = ISC_TRUE;
252 			pub = strtotime(isc_commandline_argument,
253 					now, now, &setpub);
254 			unsetpub = !setpub;
255 			break;
256 		case 'A':
257 			if (setact || unsetact)
258 				fatal("-A specified more than once");
259 
260 			changed = ISC_TRUE;
261 			act = strtotime(isc_commandline_argument,
262 					now, now, &setact);
263 			unsetact = !setact;
264 			break;
265 		case 'R':
266 			if (setrev || unsetrev)
267 				fatal("-R specified more than once");
268 
269 			changed = ISC_TRUE;
270 			rev = strtotime(isc_commandline_argument,
271 					now, now, &setrev);
272 			unsetrev = !setrev;
273 			break;
274 		case 'I':
275 			if (setinact || unsetinact)
276 				fatal("-I specified more than once");
277 
278 			changed = ISC_TRUE;
279 			inact = strtotime(isc_commandline_argument,
280 					now, now, &setinact);
281 			unsetinact = !setinact;
282 			break;
283 		case 'D':
284 			if (setdel || unsetdel)
285 				fatal("-D specified more than once");
286 
287 			changed = ISC_TRUE;
288 			del = strtotime(isc_commandline_argument,
289 					now, now, &setdel);
290 			unsetdel = !setdel;
291 			break;
292 		case 'S':
293 			predecessor = isc_commandline_argument;
294 			break;
295 		case 'i':
296 			prepub = strtottl(isc_commandline_argument);
297 			break;
298 		case '?':
299 			if (isc_commandline_option != '?')
300 				fprintf(stderr, "%s: invalid argument -%c\n",
301 					program, isc_commandline_option);
302 			/* Falls into */
303 		case 'h':
304 			/* Does not return. */
305 			usage();
306 
307 		case 'V':
308 			/* Does not return. */
309 			version(program);
310 
311 		default:
312 			fprintf(stderr, "%s: unhandled option -%c\n",
313 				program, isc_commandline_option);
314 			exit(1);
315 		}
316 	}
317 
318 	if (argc < isc_commandline_index + 1 ||
319 	    argv[isc_commandline_index] == NULL)
320 		fatal("The key file name was not specified");
321 	if (argc > isc_commandline_index + 1)
322 		fatal("Extraneous arguments");
323 
324 	if (ectx == NULL)
325 		setup_entropy(mctx, NULL, &ectx);
326 	result = isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE);
327 	if (result != ISC_R_SUCCESS)
328 		fatal("Could not initialize hash");
329 	result = dst_lib_init2(mctx, ectx, engine,
330 			       ISC_ENTROPY_BLOCKING | ISC_ENTROPY_GOODONLY);
331 	if (result != ISC_R_SUCCESS)
332 		fatal("Could not initialize dst: %s",
333 		      isc_result_totext(result));
334 	isc_entropy_stopcallbacksources(ectx);
335 
336 	if (predecessor != NULL) {
337 		int major, minor;
338 
339 		if (prepub == -1)
340 			prepub = (30 * 86400);
341 
342 		if (setpub || unsetpub)
343 			fatal("-S and -P cannot be used together");
344 		if (setact || unsetact)
345 			fatal("-S and -A cannot be used together");
346 
347 		result = dst_key_fromnamedfile(predecessor, directory,
348 					       DST_TYPE_PUBLIC |
349 					       DST_TYPE_PRIVATE,
350 					       mctx, &prevkey);
351 		if (result != ISC_R_SUCCESS)
352 			fatal("Invalid keyfile %s: %s",
353 			      filename, isc_result_totext(result));
354 		if (!dst_key_isprivate(prevkey) && !dst_key_isexternal(prevkey))
355 			fatal("%s is not a private key", filename);
356 
357 		name = dst_key_name(prevkey);
358 		alg = dst_key_alg(prevkey);
359 		size = dst_key_size(prevkey);
360 		flags = dst_key_flags(prevkey);
361 
362 		dst_key_format(prevkey, keystr, sizeof(keystr));
363 		dst_key_getprivateformat(prevkey, &major, &minor);
364 		if (major != DST_MAJOR_VERSION || minor < DST_MINOR_VERSION)
365 			fatal("Predecessor has incompatible format "
366 			      "version %d.%d\n\t", major, minor);
367 
368 		result = dst_key_gettime(prevkey, DST_TIME_ACTIVATE, &prevact);
369 		if (result != ISC_R_SUCCESS)
370 			fatal("Predecessor has no activation date. "
371 			      "You must set one before\n\t"
372 			      "generating a successor.");
373 
374 		result = dst_key_gettime(prevkey, DST_TIME_INACTIVE,
375 					 &previnact);
376 		if (result != ISC_R_SUCCESS)
377 			fatal("Predecessor has no inactivation date. "
378 			      "You must set one before\n\t"
379 			      "generating a successor.");
380 
381 		pub = prevact - prepub;
382 		if (pub < now && prepub != 0)
383 			fatal("Predecessor will become inactive before the\n\t"
384 			      "prepublication period ends.  Either change "
385 			      "its inactivation date,\n\t"
386 			      "or use the -i option to set a shorter "
387 			      "prepublication interval.");
388 
389 		result = dst_key_gettime(prevkey, DST_TIME_DELETE, &prevdel);
390 		if (result != ISC_R_SUCCESS)
391 			fprintf(stderr, "%s: warning: Predecessor has no "
392 					"removal date;\n\t"
393 					"it will remain in the zone "
394 					"indefinitely after rollover.\n",
395 					program);
396 		else if (prevdel < previnact)
397 			fprintf(stderr, "%s: warning: Predecessor is "
398 					"scheduled to be deleted\n\t"
399 					"before it is scheduled to be "
400 					"inactive.\n", program);
401 
402 		changed = setpub = setact = ISC_TRUE;
403 	} else {
404 		if (prepub < 0)
405 			prepub = 0;
406 
407 		if (prepub > 0) {
408 			if (setpub && setact && (act - prepub) < pub)
409 				fatal("Activation and publication dates "
410 				      "are closer together than the\n\t"
411 				      "prepublication interval.");
412 
413 			if (setpub && !setact) {
414 				setact = ISC_TRUE;
415 				act = pub + prepub;
416 			} else if (setact && !setpub) {
417 				setpub = ISC_TRUE;
418 				pub = act - prepub;
419 			}
420 
421 			if ((act - prepub) < now)
422 				fatal("Time until activation is shorter "
423 				      "than the\n\tprepublication interval.");
424 		}
425 	}
426 
427 	if (directory != NULL) {
428 		filename = argv[isc_commandline_index];
429 	} else {
430 		result = isc_file_splitpath(mctx, argv[isc_commandline_index],
431 					    &directory, &filename);
432 		if (result != ISC_R_SUCCESS)
433 			fatal("cannot process filename %s: %s",
434 			      argv[isc_commandline_index],
435 			      isc_result_totext(result));
436 	}
437 
438 	result = dst_key_fromnamedfile(filename, directory,
439 				       DST_TYPE_PUBLIC | DST_TYPE_PRIVATE,
440 				       mctx, &key);
441 	if (result != ISC_R_SUCCESS)
442 		fatal("Invalid keyfile %s: %s",
443 		      filename, isc_result_totext(result));
444 
445 	if (!dst_key_isprivate(key) && !dst_key_isexternal(key))
446 		fatal("%s is not a private key", filename);
447 
448 	dst_key_format(key, keystr, sizeof(keystr));
449 
450 	if (predecessor != NULL) {
451 		if (!dns_name_equal(name, dst_key_name(key)))
452 			fatal("Key name mismatch");
453 		if (alg != dst_key_alg(key))
454 			fatal("Key algorithm mismatch");
455 		if (size != dst_key_size(key))
456 			fatal("Key size mismatch");
457 		if (flags != dst_key_flags(key))
458 			fatal("Key flags mismatch");
459 	}
460 
461 	prevdel = previnact = 0;
462 	if ((setdel && setinact && del < inact) ||
463 	    (dst_key_gettime(key, DST_TIME_INACTIVE,
464 			     &previnact) == ISC_R_SUCCESS &&
465 	     setdel && !setinact && del < previnact) ||
466 	    (dst_key_gettime(key, DST_TIME_DELETE,
467 			     &prevdel) == ISC_R_SUCCESS &&
468 	     setinact && !setdel && prevdel < inact) ||
469 	    (!setdel && !setinact && prevdel < previnact))
470 		fprintf(stderr, "%s: warning: Key is scheduled to "
471 				"be deleted before it is\n\t"
472 				"scheduled to be inactive.\n",
473 			program);
474 
475 	if (force)
476 		set_keyversion(key);
477 	else
478 		check_keyversion(key, keystr);
479 
480 	if (verbose > 2)
481 		fprintf(stderr, "%s: %s\n", program, keystr);
482 
483 	/*
484 	 * Set time values.
485 	 */
486 	if (setpub)
487 		dst_key_settime(key, DST_TIME_PUBLISH, pub);
488 	else if (unsetpub)
489 		dst_key_unsettime(key, DST_TIME_PUBLISH);
490 
491 	if (setact)
492 		dst_key_settime(key, DST_TIME_ACTIVATE, act);
493 	else if (unsetact)
494 		dst_key_unsettime(key, DST_TIME_ACTIVATE);
495 
496 	if (setrev) {
497 		if ((dst_key_flags(key) & DNS_KEYFLAG_REVOKE) != 0)
498 			fprintf(stderr, "%s: warning: Key %s is already "
499 					"revoked; changing the revocation date "
500 					"will not affect this.\n",
501 					program, keystr);
502 		if ((dst_key_flags(key) & DNS_KEYFLAG_KSK) == 0)
503 			fprintf(stderr, "%s: warning: Key %s is not flagged as "
504 					"a KSK, but -R was used.  Revoking a "
505 					"ZSK is legal, but undefined.\n",
506 					program, keystr);
507 		dst_key_settime(key, DST_TIME_REVOKE, rev);
508 	} else if (unsetrev) {
509 		if ((dst_key_flags(key) & DNS_KEYFLAG_REVOKE) != 0)
510 			fprintf(stderr, "%s: warning: Key %s is already "
511 					"revoked; removing the revocation date "
512 					"will not affect this.\n",
513 					program, keystr);
514 		dst_key_unsettime(key, DST_TIME_REVOKE);
515 	}
516 
517 	if (setinact)
518 		dst_key_settime(key, DST_TIME_INACTIVE, inact);
519 	else if (unsetinact)
520 		dst_key_unsettime(key, DST_TIME_INACTIVE);
521 
522 	if (setdel)
523 		dst_key_settime(key, DST_TIME_DELETE, del);
524 	else if (unsetdel)
525 		dst_key_unsettime(key, DST_TIME_DELETE);
526 
527 	if (setttl)
528 		dst_key_setttl(key, ttl);
529 
530 	/*
531 	 * No metadata changes were made but we're forcing an upgrade
532 	 * to the new format anyway: use "-P now -A now" as the default
533 	 */
534 	if (force && !changed) {
535 		dst_key_settime(key, DST_TIME_PUBLISH, now);
536 		dst_key_settime(key, DST_TIME_ACTIVATE, now);
537 		changed = ISC_TRUE;
538 	}
539 
540 	if (!changed && setttl)
541 		changed = ISC_TRUE;
542 
543 	/*
544 	 * Print out time values, if -p was used.
545 	 */
546 	if (printcreate)
547 		printtime(key, DST_TIME_CREATED, "Created", epoch, stdout);
548 
549 	if (printpub)
550 		printtime(key, DST_TIME_PUBLISH, "Publish", epoch, stdout);
551 
552 	if (printact)
553 		printtime(key, DST_TIME_ACTIVATE, "Activate", epoch, stdout);
554 
555 	if (printrev)
556 		printtime(key, DST_TIME_REVOKE, "Revoke", epoch, stdout);
557 
558 	if (printinact)
559 		printtime(key, DST_TIME_INACTIVE, "Inactive", epoch, stdout);
560 
561 	if (printdel)
562 		printtime(key, DST_TIME_DELETE, "Delete", epoch, stdout);
563 
564 	if (changed) {
565 		isc_buffer_init(&buf, newname, sizeof(newname));
566 		result = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory,
567 					       &buf);
568 		if (result != ISC_R_SUCCESS) {
569 			fatal("Failed to build public key filename: %s",
570 			      isc_result_totext(result));
571 		}
572 
573 		result = dst_key_tofile(key, DST_TYPE_PUBLIC|DST_TYPE_PRIVATE,
574 					directory);
575 		if (result != ISC_R_SUCCESS) {
576 			dst_key_format(key, keystr, sizeof(keystr));
577 			fatal("Failed to write key %s: %s", keystr,
578 			      isc_result_totext(result));
579 		}
580 
581 		printf("%s\n", newname);
582 
583 		isc_buffer_clear(&buf);
584 		result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory,
585 					       &buf);
586 		if (result != ISC_R_SUCCESS) {
587 			fatal("Failed to build private key filename: %s",
588 			      isc_result_totext(result));
589 		}
590 		printf("%s\n", newname);
591 	}
592 
593 	if (prevkey != NULL)
594 		dst_key_free(&prevkey);
595 	dst_key_free(&key);
596 	dst_lib_destroy();
597 	isc_hash_destroy();
598 	cleanup_entropy(&ectx);
599 	if (verbose > 10)
600 		isc_mem_stats(mctx, stdout);
601 	cleanup_logging(&log);
602 	isc_mem_free(mctx, directory);
603 	isc_mem_destroy(&mctx);
604 
605 	return (0);
606 }
607