xref: /titanic_52/usr/src/cmd/cmd-inet/usr.lib/wanboot/p12split/p12split.c (revision 826ac02a0def83e0a41b29321470d299c7389aab)
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 2002-2003 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <stdio.h>
28 #include <libintl.h>
29 #include <locale.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/wanboot_impl.h>
33 #include <unistd.h>
34 #include <string.h>
35 #include <libinetutil.h>
36 #include <wanbootutil.h>
37 
38 #include <openssl/crypto.h>
39 #include <openssl/buffer.h>
40 #include <openssl/bio.h>
41 #include <openssl/err.h>
42 #include <openssl/x509.h>
43 #include <openssl/x509v3.h>
44 #include <openssl/pkcs12.h>
45 #include <openssl/evp.h>
46 #include <p12aux.h>
47 
48 static boolean_t verbose = B_FALSE;	/* When nonzero, do in verbose mode */
49 
50 /* The following match/cert values require PKCS12 */
51 static int  matchty;		/* Type of matching do to on input */
52 static char *k_matchval;	/* localkeyid value to match */
53 static uint_t k_len;		/* length of k_matchval */
54 
55 #define	IO_KEYFILE	1	/* Have a separate key file or data */
56 #define	IO_CERTFILE	2	/* Have a separate cert file or data */
57 #define	IO_TRUSTFILE	4	/* Have a separate trustanchor file */
58 
59 static char *input = NULL;	/* Consolidated input file */
60 static char *key_out = NULL;	/* Key file to be output */
61 static char *cert_out = NULL;	/* Cert file to be output */
62 static char *trust_out = NULL;	/* Trust anchor file to be output */
63 static uint_t outfiles;		/* What files are there for output */
64 static char *progname;
65 
66 /* Returns from time_check */
67 typedef enum {
68 	CHK_TIME_OK = 0,		/* Cert in effect and not expired */
69 	CHK_TIME_BEFORE_BAD,		/* not_before field is invalid */
70 	CHK_TIME_AFTER_BAD,		/* not_after field is invalid */
71 	CHK_TIME_IS_BEFORE,		/* Cert not yet in force */
72 	CHK_TIME_HAS_EXPIRED		/* Cert has expired */
73 } time_errs_t;
74 
75 static int parse_keyid(const char *);
76 static int do_certs(void);
77 static int read_files(STACK_OF(X509) **, X509 **, EVP_PKEY **);
78 static void check_certs(STACK_OF(X509) *, X509 **);
79 static time_errs_t time_check_print(X509 *);
80 static time_errs_t time_check(X509 *);
81 static int write_files(STACK_OF(X509) *, X509 *, EVP_PKEY *);
82 static int get_ifile(char *, char *, EVP_PKEY **, X509 **, STACK_OF(X509) **);
83 static int do_ofile(char *, EVP_PKEY *, X509 *, STACK_OF(X509) *);
84 static void usage(void);
85 static const char *cryptoerr(void);
86 
87 int
88 main(int argc, char **argv)
89 {
90 	int	i;
91 
92 	/*
93 	 * Do the necessary magic for localization support.
94 	 */
95 	(void) setlocale(LC_ALL, "");
96 #if !defined(TEXT_DOMAIN)
97 #define	TEXT_DOMAIN "SYS_TEST"
98 #endif
99 	(void) textdomain(TEXT_DOMAIN);
100 
101 	progname = strrchr(argv[0], '/');
102 	if (progname != NULL)
103 		progname++;
104 	else
105 		progname = argv[0];
106 
107 	wbku_errinit(progname);
108 
109 	matchty = DO_FIRST_PAIR;
110 	while ((i = getopt(argc, argv, "vc:i:k:l:t:")) != -1) {
111 		switch (i) {
112 		case 'v':
113 			verbose = B_TRUE;
114 			break;
115 
116 		case 'l':
117 			if (parse_keyid(optarg) < 0)
118 				return (EXIT_FAILURE);
119 			matchty = DO_FIND_KEYID;
120 			break;
121 
122 		case 'c':
123 			cert_out = optarg;
124 			outfiles |= IO_CERTFILE;
125 			break;
126 
127 		case 'k':
128 			key_out = optarg;
129 			outfiles |= IO_KEYFILE;
130 			break;
131 
132 		case 't':
133 			trust_out = optarg;
134 			outfiles |= IO_TRUSTFILE;
135 			break;
136 
137 		case 'i':
138 			input = optarg;
139 			break;
140 
141 		default:
142 			usage();
143 		}
144 	}
145 
146 	if (input == NULL) {
147 		wbku_printerr("no input file specified\n");
148 		usage();
149 	}
150 
151 	/*
152 	 * Need output files.
153 	 */
154 	if (outfiles == 0) {
155 		wbku_printerr("at least one output file must be specified\n");
156 		usage();
157 	}
158 
159 	if (do_certs() < 0)
160 		return (EXIT_FAILURE);
161 
162 	return (EXIT_SUCCESS);
163 }
164 
165 static int
166 parse_keyid(const char *keystr)
167 {
168 	const char 	*rp;
169 	char		*wp;
170 	char		*nkeystr;
171 	uint_t 		nkeystrlen;
172 
173 	/*
174 	 * In the worst case, we'll need one additional character in our
175 	 * output string -- e.g. "A\0" -> "0A\0"
176 	 */
177 	nkeystrlen = strlen(keystr) + 2;
178 	k_len = (nkeystrlen + 1) / 2;
179 	nkeystr = malloc(nkeystrlen);
180 	k_matchval = malloc(k_len);
181 	if (nkeystr == NULL || k_matchval == NULL) {
182 		free(nkeystr);
183 		free(k_matchval);
184 		wbku_printerr("cannot allocate keyid");
185 		return (-1);
186 	}
187 
188 	/*
189 	 * For convenience, we allow the user to put spaces between each digit
190 	 * when entering it on the command line.  As a result, we need to
191 	 * process it into a format that hexascii_to_octet() can handle.  Note
192 	 * that we're careful to map strings like "AA B CC D" to "AA0BCC0D".
193 	 */
194 	for (rp = keystr, wp = nkeystr; *rp != '\0'; rp++) {
195 		if (*rp == ' ')
196 			continue;
197 
198 		if (rp[1] == ' ' || rp[1] == '\0') {
199 			*wp++ = '0';	/* one character sequence; prepend 0 */
200 			*wp++ = *rp;
201 		} else {
202 			*wp++ = *rp++;
203 			*wp++ = *rp;
204 		}
205 	}
206 	*wp = '\0';
207 
208 	if (hexascii_to_octet(nkeystr, wp - nkeystr, k_matchval, &k_len) != 0) {
209 		free(nkeystr);
210 		free(k_matchval);
211 		wbku_printerr("invalid keyid `%s'\n", keystr);
212 		return (-1);
213 	}
214 
215 	free(nkeystr);
216 	return (0);
217 }
218 
219 static int
220 do_certs(void)
221 {
222 	char *bufp;
223 	STACK_OF(X509) *ta_in = NULL;
224 	EVP_PKEY *pkey_in = NULL;
225 	X509 *xcert_in = NULL;
226 
227 	sunw_crypto_init();
228 
229 	if (read_files(&ta_in, &xcert_in, &pkey_in) < 0)
230 		return (-1);
231 
232 	if (verbose) {
233 		if (xcert_in != NULL) {
234 			(void) printf(gettext("\nMain cert:\n"));
235 
236 			/*
237 			 * sunw_subject_attrs() returns a pointer to
238 			 * memory allocated on our behalf. The same
239 			 * behavior is exhibited by sunw_issuer_attrs().
240 			 */
241 			bufp = sunw_subject_attrs(xcert_in, NULL, 0);
242 			if (bufp != NULL) {
243 				(void) printf(gettext("  Subject: %s\n"),
244 				    bufp);
245 				OPENSSL_free(bufp);
246 			}
247 
248 			bufp = sunw_issuer_attrs(xcert_in, NULL, 0);
249 			if (bufp != NULL) {
250 				(void) printf(gettext("  Issuer: %s\n"), bufp);
251 				OPENSSL_free(bufp);
252 			}
253 
254 			(void) sunw_print_times(stdout, PRNT_BOTH, NULL,
255 			    xcert_in);
256 		}
257 
258 		if (ta_in != NULL) {
259 			X509 *x;
260 			int i;
261 
262 			for (i = 0; i < sk_X509_num(ta_in); i++) {
263 				x = sk_X509_value(ta_in, i);
264 				(void) printf(
265 				    gettext("\nTrust Anchor cert %d:\n"), i);
266 
267 				/*
268 				 * sunw_subject_attrs() returns a pointer to
269 				 * memory allocated on our behalf. We get the
270 				 * same behavior from sunw_issuer_attrs().
271 				 */
272 				bufp = sunw_subject_attrs(x, NULL, 0);
273 				if (bufp != NULL) {
274 					(void) printf(
275 					    gettext("  Subject: %s\n"), bufp);
276 					OPENSSL_free(bufp);
277 				}
278 
279 				bufp = sunw_issuer_attrs(x, NULL, 0);
280 				if (bufp != NULL) {
281 					(void) printf(
282 					    gettext("  Issuer: %s\n"), bufp);
283 					OPENSSL_free(bufp);
284 				}
285 
286 				(void) sunw_print_times(stdout, PRNT_BOTH,
287 				    NULL, x);
288 			}
289 		}
290 	}
291 
292 	check_certs(ta_in, &xcert_in);
293 	if (xcert_in != NULL && pkey_in != NULL) {
294 		if (sunw_check_keys(xcert_in, pkey_in) == 0) {
295 			wbku_printerr("warning: key and certificate do "
296 			    "not match\n");
297 		}
298 	}
299 
300 	return (write_files(ta_in, xcert_in, pkey_in));
301 }
302 
303 static int
304 read_files(STACK_OF(X509) **t_in, X509 **c_in, EVP_PKEY **k_in)
305 {
306 	char *i_pass;
307 
308 	i_pass = getpassphrase(gettext("Enter key password: "));
309 
310 	if (get_ifile(input, i_pass, k_in, c_in, t_in) < 0)
311 		return (-1);
312 
313 	/*
314 	 * If we are only interested in getting a trust anchor, and if there
315 	 * is no trust anchor but is a regular cert, use it instead.  Do this
316 	 * to handle the insanity with openssl, which requires a matching cert
317 	 * and key in order to write a PKCS12 file.
318 	 */
319 	if (outfiles == IO_TRUSTFILE) {
320 		if (c_in != NULL && *c_in != NULL && t_in != NULL) {
321 			if (*t_in == NULL) {
322 				if ((*t_in = sk_X509_new_null()) == NULL) {
323 					wbku_printerr("out of memory\n");
324 					return (-1);
325 				}
326 			}
327 
328 			if (sk_X509_num(*t_in) == 0) {
329 				if (sk_X509_push(*t_in, *c_in) == 0) {
330 					wbku_printerr("out of memory\n");
331 					return (-1);
332 				}
333 				*c_in = NULL;
334 			}
335 		}
336 	}
337 
338 	if ((outfiles & IO_KEYFILE) && *k_in == NULL) {
339 		wbku_printerr("no matching key found\n");
340 		return (-1);
341 	}
342 	if ((outfiles & IO_CERTFILE) && *c_in == NULL) {
343 		wbku_printerr("no matching certificate found\n");
344 		return (-1);
345 	}
346 	if ((outfiles & IO_TRUSTFILE) && *t_in == NULL) {
347 		wbku_printerr("no matching trust anchor found\n");
348 		return (-1);
349 	}
350 
351 	return (0);
352 }
353 
354 static void
355 check_certs(STACK_OF(X509) *ta_in, X509 **c_in)
356 {
357 	X509 *curr;
358 	time_errs_t ret;
359 	int i;
360 	int del_expired = (outfiles != 0);
361 
362 	if (c_in != NULL && *c_in != NULL) {
363 		ret = time_check_print(*c_in);
364 		if ((ret != CHK_TIME_OK && ret != CHK_TIME_IS_BEFORE) &&
365 		    del_expired) {
366 			(void) fprintf(stderr, gettext("  Removing cert\n"));
367 			X509_free(*c_in);
368 			*c_in = NULL;
369 		}
370 	}
371 
372 	if (ta_in == NULL)
373 		return;
374 
375 	for (i = 0; i < sk_X509_num(ta_in); ) {
376 		curr = sk_X509_value(ta_in, i);
377 		ret = time_check_print(curr);
378 		if ((ret != CHK_TIME_OK && ret != CHK_TIME_IS_BEFORE) &&
379 		    del_expired) {
380 			(void) fprintf(stderr, gettext("  Removing cert\n"));
381 			curr = sk_X509_delete(ta_in, i);
382 			X509_free(curr);
383 			continue;
384 		}
385 		i++;
386 	}
387 }
388 
389 static time_errs_t
390 time_check_print(X509 *cert)
391 {
392 	char buf[256];
393 	int ret;
394 
395 	ret = time_check(cert);
396 	if (ret == CHK_TIME_OK)
397 		return (CHK_TIME_OK);
398 
399 	(void) fprintf(stderr, gettext("  Subject: %s"),
400 	    sunw_subject_attrs(cert, buf, sizeof (buf)));
401 	(void) fprintf(stderr, gettext("  Issuer:  %s"),
402 	    sunw_issuer_attrs(cert, buf, sizeof (buf)));
403 
404 	switch (ret) {
405 	case CHK_TIME_BEFORE_BAD:
406 		(void) fprintf(stderr,
407 		    gettext("\n  Invalid cert 'not before' field\n"));
408 		break;
409 
410 	case CHK_TIME_AFTER_BAD:
411 		(void) fprintf(stderr,
412 		    gettext("\n  Invalid cert 'not after' field\n"));
413 		break;
414 
415 	case CHK_TIME_HAS_EXPIRED:
416 		(void) sunw_print_times(stderr, PRNT_NOT_AFTER,
417 		    gettext("\n  Cert has expired\n"), cert);
418 		break;
419 
420 	case CHK_TIME_IS_BEFORE:
421 		(void) sunw_print_times(stderr, PRNT_NOT_BEFORE,
422 		    gettext("\n  Warning: cert not yet valid\n"), cert);
423 		break;
424 
425 	default:
426 		break;
427 	}
428 
429 	return (ret);
430 }
431 
432 static time_errs_t
433 time_check(X509 *cert)
434 {
435 	int i;
436 
437 	i = X509_cmp_time(X509_get_notBefore(cert), NULL);
438 	if (i == 0)
439 		return (CHK_TIME_BEFORE_BAD);
440 	if (i > 0)
441 		return (CHK_TIME_IS_BEFORE);
442 	/* After 'not before' time */
443 
444 	i = X509_cmp_time(X509_get_notAfter(cert), NULL);
445 	if (i == 0)
446 		return (CHK_TIME_AFTER_BAD);
447 	if (i < 0)
448 		return (CHK_TIME_HAS_EXPIRED);
449 	return (CHK_TIME_OK);
450 }
451 
452 static int
453 write_files(STACK_OF(X509) *t_out, X509 *c_out, EVP_PKEY *k_out)
454 {
455 	if (key_out != NULL) {
456 		if (verbose)
457 			(void) printf(gettext("%s: writing key\n"), progname);
458 		if (do_ofile(key_out, k_out, NULL, NULL) < 0)
459 			return (-1);
460 	}
461 
462 	if (cert_out != NULL) {
463 		if (verbose)
464 			(void) printf(gettext("%s: writing cert\n"), progname);
465 		if (do_ofile(cert_out, NULL, c_out, NULL) < 0)
466 			return (-1);
467 	}
468 
469 	if (trust_out != NULL) {
470 		if (verbose)
471 			(void) printf(gettext("%s: writing trust\n"),
472 			    progname);
473 		if (do_ofile(trust_out, NULL, NULL, t_out) < 0)
474 			return (-1);
475 	}
476 
477 	return (0);
478 }
479 
480 static int
481 get_ifile(char *name, char *pass, EVP_PKEY **tmp_k, X509 **tmp_c,
482     STACK_OF(X509) **tmp_t)
483 {
484 	PKCS12		*p12;
485 	FILE		*fp;
486 	int		ret;
487 	struct stat	sbuf;
488 
489 	if (stat(name, &sbuf) == 0 && !S_ISREG(sbuf.st_mode)) {
490 		wbku_printerr("%s is not a regular file\n", name);
491 		return (-1);
492 	}
493 
494 	if ((fp = fopen(name, "r")) == NULL) {
495 		wbku_printerr("cannot open input file %s", name);
496 		return (-1);
497 	}
498 
499 	p12 = d2i_PKCS12_fp(fp, NULL);
500 	if (p12 == NULL) {
501 		wbku_printerr("cannot read file %s: %s\n", name, cryptoerr());
502 		(void) fclose(fp);
503 		return (-1);
504 	}
505 	(void) fclose(fp);
506 
507 	ret = sunw_PKCS12_parse(p12, pass, matchty, k_matchval, k_len,
508 	    NULL, tmp_k, tmp_c, tmp_t);
509 	if (ret <= 0) {
510 		if (ret == 0)
511 			wbku_printerr("cannot find matching cert and key\n");
512 		else
513 			wbku_printerr("cannot parse %s: %s\n", name,
514 			    cryptoerr());
515 		PKCS12_free(p12);
516 		return (-1);
517 	}
518 	return (0);
519 }
520 
521 static int
522 do_ofile(char *name, EVP_PKEY *pkey, X509 *cert, STACK_OF(X509) *ta)
523 {
524 	STACK_OF(EVP_PKEY) *klist = NULL;
525 	STACK_OF(X509)	*clist = NULL;
526 	PKCS12		*p12 = NULL;
527 	int		ret = 0;
528 	FILE		*fp;
529 	struct stat	sbuf;
530 
531 	if (stat(name, &sbuf) == 0 && !S_ISREG(sbuf.st_mode)) {
532 		wbku_printerr("%s is not a regular file\n", name);
533 		return (-1);
534 	}
535 
536 	if ((fp = fopen(name, "w")) == NULL) {
537 		wbku_printerr("cannot open output file %s", name);
538 		return (-1);
539 	}
540 
541 	if ((clist = sk_X509_new_null()) == NULL ||
542 	    (klist = sk_EVP_PKEY_new_null()) == NULL) {
543 		wbku_printerr("out of memory\n");
544 		ret = -1;
545 		goto cleanup;
546 	}
547 
548 	if (cert != NULL && sk_X509_push(clist, cert) == 0) {
549 		wbku_printerr("out of memory\n");
550 		ret = -1;
551 		goto cleanup;
552 	}
553 
554 	if (pkey != NULL && sk_EVP_PKEY_push(klist, pkey) == 0) {
555 		wbku_printerr("out of memory\n");
556 		ret = -1;
557 		goto cleanup;
558 	}
559 
560 	p12 = sunw_PKCS12_create(WANBOOT_PASSPHRASE, klist, clist, ta);
561 	if (p12 == NULL) {
562 		wbku_printerr("cannot create %s: %s\n", name, cryptoerr());
563 		ret = -1;
564 		goto cleanup;
565 	}
566 
567 	if (i2d_PKCS12_fp(fp, p12) == 0) {
568 		wbku_printerr("cannot write %s: %s\n", name, cryptoerr());
569 		ret = -1;
570 		goto cleanup;
571 	}
572 
573 cleanup:
574 	(void) fclose(fp);
575 	if (p12 != NULL)
576 		PKCS12_free(p12);
577 	/*
578 	 * Put the cert and pkey off of the stack so that they won't
579 	 * be freed two times.  (If they get left in the stack then
580 	 * they will be freed with the stack.)
581 	 */
582 	if (clist != NULL) {
583 		if (cert != NULL && sk_X509_num(clist) == 1) {
584 			(void) sk_X509_delete(clist, 0);
585 		}
586 		sk_X509_pop_free(clist, X509_free);
587 	}
588 	if (klist != NULL) {
589 		if (pkey != NULL && sk_EVP_PKEY_num(klist) == 1) {
590 			(void) sk_EVP_PKEY_delete(klist, 0);
591 		}
592 		sk_EVP_PKEY_pop_free(klist, sunw_evp_pkey_free);
593 	}
594 
595 	return (ret);
596 }
597 
598 static void
599 usage(void)
600 {
601 	(void) fprintf(stderr,
602 	    gettext("usage:\n"
603 	    "     %s -i <file> -c <file> -k <file> -t <file> [-l <keyid> -v]\n"
604 	    "\n"),
605 	    progname);
606 	(void) fprintf(stderr,
607 	    gettext(" where:\n"
608 	    "  -i - input file to be split into component parts and put in\n"
609 	    "       files given by -c, -k and -t\n"
610 	    "  -c - output file for the client certificate\n"
611 	    "  -k - output file for the client private key\n"
612 	    "  -t - output file for the remaining certificates (assumed\n"
613 	    "       to be trust anchors)\n"
614 	    "\n Files are assumed to be pkcs12-format files.\n\n"
615 	    "  -v - verbose\n"
616 	    "  -l - value of 'localkeyid' attribute in client cert and\n"
617 	    "       private key to be selected from the input file.\n\n"));
618 	exit(EXIT_FAILURE);
619 }
620 
621 /*
622  * Return a pointer to a static buffer that contains a listing of crypto
623  * errors.  We presume that the user doesn't want more than 8KB of error
624  * messages :-)
625  */
626 static const char *
627 cryptoerr(void)
628 {
629 	static char	errbuf[8192];
630 	ulong_t		err;
631 	const char	*pfile;
632 	int		line;
633 	unsigned int	nerr = 0;
634 
635 	errbuf[0] = '\0';
636 	while ((err = ERR_get_error_line(&pfile, &line)) != 0) {
637 		if (++nerr > 1)
638 			(void) strlcat(errbuf, "\n\t", sizeof (errbuf));
639 
640 		if (err == (ulong_t)-1) {
641 			(void) strlcat(errbuf, strerror(errno),
642 			    sizeof (errbuf));
643 			break;
644 		}
645 		(void) strlcat(errbuf, ERR_reason_error_string(err),
646 		    sizeof (errbuf));
647 	}
648 
649 	return (errbuf);
650 }
651