1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28#include <stdio.h>
29#include <ctype.h>
30#include <unistd.h>
31#include <strings.h>
32#include <libintl.h>
33#include <locale.h>
34#include <limits.h>
35#include <libgen.h>
36#include <errno.h>
37#include <assert.h>
38#include <wanbootutil.h>
39#include <sys/sysmacros.h>
40#include <sys/socket.h>
41#include <sys/types.h>
42#include <sys/stat.h>
43#include <sys/wanboot_impl.h>
44#include <netinet/in.h>
45#include <arpa/inet.h>
46
47/* Return codes */
48#define	KEYGEN_SUCCESS	0
49#define	KEYGEN_ERROR	1
50
51/* Defaults */
52static char default_net[] = "0.0.0.0";
53static char default_cid[] = "00000000000000";
54
55/* Suboption. */
56#define	NET	0
57#define	CID	1
58#define	TYPE	2
59
60static char *opts[] = { "net", "cid", "type", NULL };
61
62/*
63 * This routine is used to parse the suboptions of '-o' option.
64 *
65 * The option should be of the form:
66 *              net=<addr>,cid=<cid>,type=<3des|aes|sha1|rsa>
67 *
68 * This routine will pass the values of each of the suboptions back in the
69 * supplied arguments, 'net', 'cid' and 'ka'.
70 *
71 * Returns:
72 *	KEYGEN_SUCCESS or KEYGEN_ERROR.
73 */
74static int
75process_option(char *arg, char **net, char **cid, wbku_key_attr_t *ka)
76{
77	char *value;
78	wbku_retcode_t ret;
79
80	while (*arg != '\0') {
81		switch (getsubopt(&arg, opts, &value)) {
82		case NET:
83			/*
84			 * Network number.
85			 */
86			*net = value;
87			break;
88		case CID:
89			/*
90			 * Client ID.
91			 */
92			*cid = value;
93			break;
94		case TYPE:
95			/*
96			 * Key type.
97			 */
98			ret = wbku_str_to_keyattr(value, ka, WBKU_ANY_KEY);
99			if (ret != WBKU_SUCCESS) {
100				wbku_printerr("%s\n", wbku_retmsg(ret));
101				return (KEYGEN_ERROR);
102			}
103			break;
104		default:
105			wbku_printerr("%s is not a valid option\n", value);
106			return (KEYGEN_ERROR);
107		}
108	}
109
110	/*
111	 * Sanity checks
112	 */
113	if (*net != NULL && **net == '\0') {
114		wbku_printerr("Missing net option value\n");
115		return (KEYGEN_ERROR);
116	}
117	if (*cid != NULL && **cid == '\0') {
118		wbku_printerr("Missing cid option value\n");
119		return (KEYGEN_ERROR);
120	}
121	if (*cid != NULL && *net == NULL) {
122		wbku_printerr(
123		    "The cid option requires net option specification\n");
124		return (KEYGEN_ERROR);
125	}
126	if (ka->ka_type == WBKU_KEY_UNKNOWN) {
127		wbku_printerr("Missing key type option value\n");
128		return (KEYGEN_ERROR);
129	}
130
131	return (KEYGEN_SUCCESS);
132}
133
134/*
135 * This routine parses a buffer to determine whether or not it
136 * contains a hexascii string. If the buffer contains any characters
137 * that are not hexascii, then it is not a hexascii string. Since
138 * this function is used to validate a CID value (which is then used
139 * to identify a directory in the filesystem), no evaluation of the
140 * string is performed. That is, hex strings are not padded (e.g. "A"
141 * is not padded to "0A").
142 *
143 * Returns:
144 *	B_TRUE or B_FALSE
145 */
146static boolean_t
147isxstring(const char *buf)
148{
149	if ((strlen(buf) % 2) != 0) {
150		return (B_FALSE);
151	}
152
153	for (; *buf != '\0'; ++buf) {
154		if (!isxdigit(*buf)) {
155			return (B_FALSE);
156		}
157	}
158	return (B_TRUE);
159}
160
161/*
162 * This routine uses the 'net' and the 'cid' to generate the client's
163 * keystore filename and, if requested, creates the directory path to
164 * the file if any of the directories do not exist. If directory path
165 * creation is not requested and any of the directories do not exist,
166 * then an error is returned.
167 *
168 * Returns:
169 *	KEYGEN_SUCCESS or KEYGEN_ERROR.
170 */
171static int
172create_client_filename(char *filename, size_t len, const char *net,
173    const char *cid, boolean_t create)
174{
175	struct in_addr addr;
176	size_t size;
177
178	if (net == NULL) {
179		size = snprintf(filename, len, "%s", CLIENT_KEY_DIR);
180	} else if (inet_pton(AF_INET, net, &addr) != 1) {
181		wbku_printerr("%s is not a valid network address\n", net);
182		return (KEYGEN_ERROR);
183	} else if (cid == NULL) {
184		size = snprintf(filename, len, "%s/%s", CLIENT_KEY_DIR, net);
185	} else if (!isxstring(cid)) {
186		wbku_printerr(
187		    "%s must be an even number of hexadecimal characters\n",
188		    cid);
189		return (KEYGEN_ERROR);
190	} else {
191		size = snprintf(filename, len, "%s/%s/%s", CLIENT_KEY_DIR,
192		    net, cid);
193	}
194
195	/*
196	 * Shouldn't be a problem, but make sure buffer was big enough.
197	 */
198	if (size >= len) {
199		wbku_printerr("Keystore path too long\n");
200		return (KEYGEN_ERROR);
201	}
202
203	/*
204	 * If directory creation is allowed, then try to create it.
205	 * If the directory already exists, then march on.
206	 */
207	if (create) {
208		if (mkdirp(filename, S_IRWXU) == -1 && errno != EEXIST) {
209			wbku_printerr("Cannot create client keystore");
210			return (KEYGEN_ERROR);
211		}
212	}
213
214	/*
215	 * Append the filename.
216	 */
217	if (strlcat(filename, "/keystore", len) >= len) {
218		wbku_printerr("Keystore path too long\n");
219		return (KEYGEN_ERROR);
220	}
221
222	return (KEYGEN_SUCCESS);
223}
224
225/*
226 * This routine generates a random key of the type defined by 'ka'.
227 * The key value is returned in 'rand_key' and the buffer pointed to
228 * by 'rand_key' is assumed to be of the correct size.
229 *
230 * Note:
231 *	If 'ka' has a non-NULL keycheck value, then the routine will
232 *	generate randon keys until a non-weak key is generated.
233 *
234 * Returns:
235 *	KEYGEN_SUCCESS or KEYGEN_ERROR.
236 */
237static int
238gen_key(const wbku_key_attr_t *ka, uint8_t *rand_key)
239{
240	/*
241	 * Generate key, until non-weak key generated.
242	 */
243	for (;;) {
244		if (wbio_nread_rand(rand_key, ka->ka_len) != 0) {
245			wbku_printerr("Cannot generate random number");
246			return (KEYGEN_ERROR);
247		}
248
249		if (ka->ka_keycheck == NULL || ka->ka_keycheck(rand_key)) {
250			return (KEYGEN_SUCCESS);
251		}
252	}
253}
254
255/*
256 * This routine generates a random master key of the type (currently only
257 * HMAC SHA1 supported) defined by 'ka' and stores it in the master key
258 * file.
259 *
260 * Returns:
261 *	KEYGEN_SUCCESS or KEYGEN_ERROR.
262 */
263static int
264master_gen_key(wbku_key_attr_t *ka)
265{
266	uint8_t mas_key[WANBOOT_HMAC_KEY_SIZE];
267	int fd;
268	FILE *fp = NULL;
269	fpos_t pos;
270	wbku_retcode_t ret;
271	boolean_t exists = B_FALSE;
272
273	/*
274	 * If the file already exists (possibly via keymgmt), then open
275	 * the file for update. Otherwise create it and open it for
276	 * for writing.
277	 */
278	fd = open(MASTER_KEY_FILE, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR);
279	if (fd < 0) {
280		if (errno == EEXIST) {
281			fp = fopen(MASTER_KEY_FILE, "r+");
282			exists = B_TRUE;
283		}
284	} else {
285		if ((fp = fdopen(fd, "w")) == NULL) {
286			(void) close(fd);
287		}
288	}
289
290	if (fp == NULL) {
291		wbku_printerr("Cannot open master keystore", MASTER_KEY_FILE);
292		return (KEYGEN_ERROR);
293	}
294
295	/*
296	 * If the file already exists, then see if a master key already
297	 * exists. We will not overwrite it if it does.
298	 */
299	ret = WBKU_NOKEY;
300	if (exists) {
301		ret = wbku_find_key(fp, NULL, ka, NULL, B_TRUE);
302		if (ret != WBKU_NOKEY) {
303			if (ret == WBKU_SUCCESS) {
304				wbku_printerr("The master %s key already "
305				    "exists and will not be overwritten\n",
306				    ka->ka_str);
307			} else {
308				wbku_printerr("%s\n", wbku_retmsg(ret));
309			}
310			(void) fclose(fp);
311			return (KEYGEN_ERROR);
312		}
313	}
314
315	/*
316	 * If wbku_find_key() did not find the key position for us
317	 * (expected behavior), then we should set position to
318	 * the end of the file.
319	 */
320	if (ret == WBKU_NOKEY &&
321	    (fseek(fp, 0, SEEK_END) != 0 || fgetpos(fp, &pos) != 0)) {
322		wbku_printerr("Internal error");
323		(void) fclose(fp);
324		return (KEYGEN_ERROR);
325	}
326
327	/*
328	 * Generate a key and write it.
329	 */
330	if (gen_key(ka, mas_key) != KEYGEN_SUCCESS) {
331		(void) fclose(fp);
332		return (KEYGEN_ERROR);
333	}
334
335	ret = wbku_write_key(fp, &pos, ka, mas_key, B_TRUE);
336	(void) fclose(fp);
337	if (ret != WBKU_SUCCESS) {
338		wbku_printerr("%s\n", wbku_retmsg(ret));
339		return (KEYGEN_ERROR);
340	}
341
342	(void) printf(gettext("The master %s key has been generated\n"),
343	    ka->ka_str);
344	return (KEYGEN_SUCCESS);
345}
346
347/*
348 * This routine generates a random client key of the type
349 * defined by 'ka' and stores it in the client keystore.
350 * file.
351 *
352 * Returns:
353 *	KEYGEN_SUCCESS or KEYGEN_ERROR.
354 */
355static int
356client_gen_key(const char *filename, wbku_key_attr_t *ka, const char *net,
357    const char *cid)
358{
359	int fd;
360	FILE *cli_fp = NULL;
361	FILE *mas_fp;
362	fpos_t pos;
363	uint8_t cli_key[WANBOOT_MAXKEYLEN];
364	uint8_t mas_key[WANBOOT_HMAC_KEY_SIZE];
365	SHA1_CTX ctx;
366	char cid_buf[PATH_MAX];
367	boolean_t exists = B_FALSE;
368	wbku_retcode_t ret;
369
370	/*
371	 * If the file already exists (possibly via keymgmt), then open
372	 * the file for update. Otherwise create it and open it for
373	 * for writing.
374	 */
375	fd = open(filename, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR);
376	if (fd < 0) {
377		if (errno == EEXIST) {
378			cli_fp = fopen(filename, "r+");
379			exists = B_TRUE;
380		}
381	} else {
382		if ((cli_fp = fdopen(fd, "w")) == NULL) {
383			(void) close(fd);
384		}
385	}
386
387	if (cli_fp == NULL) {
388		wbku_printerr("Cannot open client keystore");
389		return (KEYGEN_ERROR);
390	}
391
392	/*
393	 * Generate the key. Encryption keys can be generated by simply
394	 * calling gen_key(). An HMAC SHA1 key will be generated by
395	 * hashing the master key.
396	 */
397	switch (ka->ka_type) {
398	case WBKU_KEY_3DES:
399	case WBKU_KEY_AES_128:
400		if (gen_key(ka, cli_key) != KEYGEN_SUCCESS) {
401			(void) fclose(cli_fp);
402			return (KEYGEN_ERROR);
403		}
404		break;
405	case WBKU_KEY_HMAC_SHA1:
406		/*
407		 * Follow RFC 3118 Appendix A's algorithm to generate
408		 * the HMAC/SHA1 client key.
409		 */
410
411		/*
412		 * Open the master keystore for reading only.
413		 */
414		if ((mas_fp = fopen(MASTER_KEY_FILE, "r")) == NULL) {
415			wbku_printerr("Cannot open master keystore");
416			(void) fclose(cli_fp);
417			return (KEYGEN_ERROR);
418		}
419
420		/*
421		 * Find the master key.
422		 */
423		ret = wbku_find_key(mas_fp, NULL, ka, mas_key, B_TRUE);
424		if (ret != WBKU_SUCCESS) {
425			if (ret == WBKU_NOKEY) {
426				wbku_printerr("Cannot create a client key "
427				    "without first creating a master key\n");
428			} else {
429				wbku_printerr("%s\n", wbku_retmsg(ret));
430			}
431			(void) fclose(mas_fp);
432			(void) fclose(cli_fp);
433			return (KEYGEN_ERROR);
434		}
435		(void) fclose(mas_fp);
436
437		/*
438		 * Now generate the client's unique ID buffer.
439		 */
440		if (strlcpy(cid_buf, net, PATH_MAX) >= PATH_MAX ||
441		    strlcat(cid_buf, cid, PATH_MAX) >= PATH_MAX) {
442			wbku_printerr("Unique id for client is too big\n");
443			(void) fclose(cli_fp);
444			return (KEYGEN_ERROR);
445		}
446
447		/*
448		 * Hash the buffer to create the client key.
449		 */
450		HMACInit(&ctx, mas_key, WANBOOT_HMAC_KEY_SIZE);
451		HMACUpdate(&ctx, (uint8_t *)cid_buf, strlen(cid_buf));
452		HMACFinal(&ctx, mas_key, WANBOOT_HMAC_KEY_SIZE, cli_key);
453
454		break;
455	case WBKU_KEY_RSA:
456		wbku_printerr("Cannot generate RSA key using keygen\n");
457		(void) fclose(cli_fp);
458		return (KEYGEN_ERROR);
459	default:
460		wbku_printerr("Internal error\n");
461		(void) fclose(cli_fp);
462		return (KEYGEN_ERROR);
463	}
464
465	/*
466	 * Look to see if a client key of this type exists and if
467	 * it does note its position in the file.
468	 */
469	ret = WBKU_NOKEY;
470	if (exists) {
471		ret = wbku_find_key(cli_fp, &pos, ka, NULL, B_FALSE);
472		if (ret != WBKU_SUCCESS && ret != WBKU_NOKEY) {
473			wbku_printerr("%s\n", wbku_retmsg(ret));
474			(void) fclose(cli_fp);
475			return (KEYGEN_ERROR);
476		}
477	}
478
479	/*
480	 * If wbku_find_key() did not find the key position for us,
481	 * then we should set position to the end of the file.
482	 */
483	if (ret == WBKU_NOKEY &&
484	    (fseek(cli_fp, 0, SEEK_END) != 0 || fgetpos(cli_fp, &pos) != 0)) {
485		wbku_printerr("Internal error");
486		(void) fclose(cli_fp);
487		return (KEYGEN_ERROR);
488	}
489
490	/*
491	 * Write the key.
492	 */
493	ret = wbku_write_key(cli_fp, &pos, ka, cli_key, B_FALSE);
494	if (ret != WBKU_SUCCESS) {
495		wbku_printerr("%s\n", wbku_retmsg(ret));
496		(void) fclose(cli_fp);
497		return (KEYGEN_ERROR);
498	}
499	(void) fclose(cli_fp);
500
501	(void) printf(gettext("A new client %s key has been generated\n"),
502	    ka->ka_str);
503
504	return (KEYGEN_SUCCESS);
505}
506
507/*
508 * This routine is used to print a hexascii version of a key.
509 * The hexascii version of the key will be twice the length
510 * of 'datalen'.
511 */
512static void
513keydump(const char *key, int keylen)
514{
515	uint16_t *p16;
516
517	assert(IS_P2ALIGNED(key, sizeof (uint16_t)));
518/*LINTED aligned*/
519	for (p16 = (uint16_t *)key; keylen > 0; keylen -= 2) {
520		(void) printf("%04x", htons(*p16++));
521	}
522	(void) printf("\n");
523}
524
525/*
526 * This routine is used to print a key of the type
527 * described by 'ka'. If 'master' is true, then the
528 * key to display is the master key. Otherwise, it's a
529 * client key.
530 *
531 * Returns:
532 *	KEYGEN_SUCCESS or KEYGEN_ERROR.
533 */
534static int
535display_key(const char *filename, wbku_key_attr_t *ka, boolean_t master)
536{
537	uint8_t key[WANBOOT_MAXKEYLEN];
538	FILE *fp;
539	wbku_retcode_t ret;
540
541	/*
542	 * Open the keystore for reading only.
543	 */
544	if ((fp = fopen(filename, "r")) == NULL) {
545		wbku_printerr("Cannot open keystore");
546		return (KEYGEN_ERROR);
547	}
548
549	/*
550	 * Find the key.
551	 */
552	ret = wbku_find_key(fp, NULL, ka, key, master);
553	if (ret != WBKU_SUCCESS) {
554		if (ret == WBKU_NOKEY) {
555			wbku_printerr("The %s %s key does not exist\n",
556			    (master ? "master" : "client"), ka->ka_str);
557		} else {
558			wbku_printerr("%s\n", wbku_retmsg(ret));
559		}
560		(void) fclose(fp);
561		return (KEYGEN_ERROR);
562	}
563	(void) fclose(fp);
564
565	/*
566	 * Dump the key in hex.
567	 */
568	keydump((char *)key, ka->ka_len);
569
570	return (KEYGEN_SUCCESS);
571}
572
573/*
574 * Prints usage().
575 */
576static void
577usage(const char *cmd)
578{
579	(void) fprintf(stderr, gettext("Usage: %s [-m | -c "
580	    "-o net=<addr>,cid=<cid>,type=<%s|%s|%s>]\n"
581	    "       %s -d [-m | -c -o net=<addr>,cid=<cid>,"
582	    "type=<%s|%s|%s|%s>]\n"),
583	    cmd, WBKU_KW_3DES, WBKU_KW_AES_128, WBKU_KW_HMAC_SHA1,
584	    cmd, WBKU_KW_3DES, WBKU_KW_AES_128, WBKU_KW_HMAC_SHA1, WBKU_KW_RSA);
585}
586
587/*
588 * This program is used to generate and display WAN boot encryption and
589 * hash keys. The paths to the keystores are predetermined. That is, the
590 * master keystore (used to store a master HMAC SHA1 key) will always
591 * reside in the default location, MASTER_KEY_FILE. The client keystores
592 * will always reside in default locations that are computed using their
593 * network number and cid values.
594 *
595 * Note:
596 * 	The master keystore can store client keys too. This program
597 *	cannot be used to insert client keys into the master keystore.
598 *	However, it must not corrupt any client keystore inserted into
599 *	the file by other means (keymgmt).
600 *
601 *	We do not do any file locking scheme.  This means that if two
602 *	keygen commands are run concurrently, results can be disastrous.
603 *
604 * Returns:
605 *	KEYGEN_SUCCESS or KEYGEN_ERROR.
606 */
607int
608main(int argc, char **argv)
609{
610	char filename[PATH_MAX];
611	char *filenamep;
612	int c;
613	boolean_t is_client = B_FALSE;
614	boolean_t is_master = B_FALSE;
615	boolean_t display = B_FALSE;
616	char *net = NULL;
617	char *cid = NULL;
618	wbku_key_attr_t ka;
619	wbku_retcode_t ret;
620
621	/*
622	 * Do the necessary magic for localization support.
623	 */
624	(void) setlocale(LC_ALL, "");
625#if !defined(TEXT_DOMAIN)
626#define	TEXT_DOMAIN "SYS_TEST"
627#endif
628	(void) textdomain(TEXT_DOMAIN);
629
630	/*
631	 * Initialize program name for use by wbku_printerr().
632	 */
633	wbku_errinit(argv[0]);
634
635	/*
636	 * At the very least, we'll need one arg.
637	 */
638	if (argc < 2) {
639		usage(argv[0]);
640		return (KEYGEN_ERROR);
641	}
642
643	/*
644	 * Parse the options.
645	 */
646	ka.ka_type = WBKU_KEY_UNKNOWN;
647	while ((c = getopt(argc, argv, "dcmo:")) != EOF) {
648		switch (c) {
649		case 'd':
650			/*
651			 * Display a key.
652			 */
653			display = B_TRUE;
654			break;
655		case 'o':
656			/*
657			 * Suboptions.
658			 */
659			if (process_option(optarg, &net, &cid, &ka) != 0) {
660				usage(argv[0]);
661				return (KEYGEN_ERROR);
662			}
663			break;
664		case 'c':
665			is_client = B_TRUE;
666			break;
667		case 'm':
668			is_master = B_TRUE;
669			break;
670		default:
671			usage(argv[0]);
672			return (KEYGEN_ERROR);
673		}
674	}
675
676	/*
677	 * Must be operating on a master or client key and if
678	 * it's a client key, then type must have been given.
679	 */
680	if ((is_client == is_master) ||
681	    (is_client && ka.ka_type == WBKU_KEY_UNKNOWN)) {
682		usage(argv[0]);
683		return (KEYGEN_ERROR);
684	}
685
686	/*
687	 * If operating on the master key, then it is an HMAC SHA1
688	 * key. Build the correct 'ka'. If we're working on a client
689	 * key, the 'ka' was already built as part of option parsing.
690	 */
691	if (is_master) {
692		ret = wbku_str_to_keyattr(WBKU_KW_HMAC_SHA1, &ka,
693		    WBKU_HASH_KEY);
694		if (ret != WBKU_SUCCESS) {
695			wbku_printerr("Internal error\n");
696			return (KEYGEN_ERROR);
697		}
698		filenamep = MASTER_KEY_FILE;
699	} else {
700		/*
701		 * Build the path to the client keystore.
702		 */
703		if (create_client_filename(filename, sizeof (filename), net,
704		    cid, !display) != KEYGEN_SUCCESS) {
705			return (KEYGEN_ERROR);
706		}
707		filenamep = filename;
708	}
709
710	/*
711	 * If display chosen, go do it.
712	 */
713	if (display) {
714		return (display_key(filenamep, &ka, is_master));
715	}
716
717	/*
718	 * Can't generate RSA key here.
719	 */
720	if (ka.ka_type == WBKU_KEY_RSA) {
721		wbku_printerr("keygen cannot create RSA key\n");
722		return (KEYGEN_ERROR);
723	}
724
725	/*
726	 * If generating a master key, go do it.
727	 */
728	if (is_master) {
729		return (master_gen_key(&ka));
730	}
731
732	/*
733	 * Must be generating a client key, go do it.
734	 */
735	if (net == NULL) {
736		net = default_net;
737	}
738	if (cid == NULL) {
739		cid = default_cid;
740	}
741	if (client_gen_key(filename, &ka, net, cid) != KEYGEN_SUCCESS) {
742		return (KEYGEN_ERROR);
743	}
744
745	return (KEYGEN_SUCCESS);
746}
747