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 }