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                                 /* LINTED */
 264                                 x = sk_X509_value(ta_in, i);
 265                                 (void) printf(
 266                                     gettext("\nTrust Anchor cert %d:\n"), i);
 267 
 268                                 /*
 269                                  * sunw_subject_attrs() returns a pointer to
 270                                  * memory allocated on our behalf. We get the
 271                                  * same behavior from sunw_issuer_attrs().
 272                                  */
 273                                 bufp = sunw_subject_attrs(x, NULL, 0);
 274                                 if (bufp != NULL) {
 275                                         (void) printf(
 276                                             gettext("  Subject: %s\n"), bufp);
 277                                         OPENSSL_free(bufp);
 278                                 }
 279 
 280                                 bufp = sunw_issuer_attrs(x, NULL, 0);
 281                                 if (bufp != NULL) {
 282                                         (void) printf(
 283                                             gettext("  Issuer: %s\n"), bufp);
 284                                         OPENSSL_free(bufp);
 285                                 }
 286 
 287                                 (void) sunw_print_times(stdout, PRNT_BOTH,
 288                                     NULL, x);
 289                         }
 290                 }
 291         }
 292 
 293         check_certs(ta_in, &xcert_in);
 294         if (xcert_in != NULL && pkey_in != NULL) {
 295                 if (sunw_check_keys(xcert_in, pkey_in) == 0) {
 296                         wbku_printerr("warning: key and certificate do "
 297                             "not match\n");
 298                 }
 299         }
 300 
 301         return (write_files(ta_in, xcert_in, pkey_in));
 302 }
 303 
 304 static int
 305 read_files(STACK_OF(X509) **t_in, X509 **c_in, EVP_PKEY **k_in)
 306 {
 307         char *i_pass;
 308 
 309         i_pass = getpassphrase(gettext("Enter key password: "));
 310 
 311         if (get_ifile(input, i_pass, k_in, c_in, t_in) < 0)
 312                 return (-1);
 313 
 314         /*
 315          * If we are only interested in getting a trust anchor, and if there
 316          * is no trust anchor but is a regular cert, use it instead.  Do this
 317          * to handle the insanity with openssl, which requires a matching cert
 318          * and key in order to write a PKCS12 file.
 319          */
 320         if (outfiles == IO_TRUSTFILE) {
 321                 if (c_in != NULL && *c_in != NULL && t_in != NULL) {
 322                         if (*t_in == NULL) {
 323                                 if ((*t_in = sk_X509_new_null()) == NULL) {
 324                                         wbku_printerr("out of memory\n");
 325                                         return (-1);
 326                                 }
 327                         }
 328 
 329                         if (sk_X509_num(*t_in) == 0) {
 330                                 if (sk_X509_push(*t_in, *c_in) == 0) {
 331                                         wbku_printerr("out of memory\n");
 332                                         return (-1);
 333                                 }
 334                                 *c_in = NULL;
 335                         }
 336                 }
 337         }
 338 
 339         if ((outfiles & IO_KEYFILE) && *k_in == NULL) {
 340                 wbku_printerr("no matching key found\n");
 341                 return (-1);
 342         }
 343         if ((outfiles & IO_CERTFILE) && *c_in == NULL) {
 344                 wbku_printerr("no matching certificate found\n");
 345                 return (-1);
 346         }
 347         if ((outfiles & IO_TRUSTFILE) && *t_in == NULL) {
 348                 wbku_printerr("no matching trust anchor found\n");
 349                 return (-1);
 350         }
 351 
 352         return (0);
 353 }
 354 
 355 static void
 356 check_certs(STACK_OF(X509) *ta_in, X509 **c_in)
 357 {
 358         X509 *curr;
 359         time_errs_t ret;
 360         int i;
 361         int del_expired = (outfiles != 0);
 362 
 363         if (c_in != NULL && *c_in != NULL) {
 364                 ret = time_check_print(*c_in);
 365                 if ((ret != CHK_TIME_OK && ret != CHK_TIME_IS_BEFORE) &&
 366                     del_expired) {
 367                         (void) fprintf(stderr, gettext("  Removing cert\n"));
 368                         X509_free(*c_in);
 369                         *c_in = NULL;
 370                 }
 371         }
 372 
 373         if (ta_in == NULL)
 374                 return;
 375 
 376         for (i = 0; i < sk_X509_num(ta_in); ) {
 377                 /* LINTED */
 378                 curr = sk_X509_value(ta_in, i);
 379                 ret = time_check_print(curr);
 380                 if ((ret != CHK_TIME_OK && ret != CHK_TIME_IS_BEFORE) &&
 381                     del_expired) {
 382                         (void) fprintf(stderr, gettext("  Removing cert\n"));
 383                         /* LINTED */
 384                         curr = sk_X509_delete(ta_in, i);
 385                         X509_free(curr);
 386                         continue;
 387                 }
 388                 i++;
 389         }
 390 }
 391 
 392 static time_errs_t
 393 time_check_print(X509 *cert)
 394 {
 395         char buf[256];
 396         int ret;
 397 
 398         ret = time_check(cert);
 399         if (ret == CHK_TIME_OK)
 400                 return (CHK_TIME_OK);
 401 
 402         (void) fprintf(stderr, gettext("  Subject: %s"),
 403             sunw_subject_attrs(cert, buf, sizeof (buf)));
 404         (void) fprintf(stderr, gettext("  Issuer:  %s"),
 405             sunw_issuer_attrs(cert, buf, sizeof (buf)));
 406 
 407         switch (ret) {
 408         case CHK_TIME_BEFORE_BAD:
 409                 (void) fprintf(stderr,
 410                     gettext("\n  Invalid cert 'not before' field\n"));
 411                 break;
 412 
 413         case CHK_TIME_AFTER_BAD:
 414                 (void) fprintf(stderr,
 415                     gettext("\n  Invalid cert 'not after' field\n"));
 416                 break;
 417 
 418         case CHK_TIME_HAS_EXPIRED:
 419                 (void) sunw_print_times(stderr, PRNT_NOT_AFTER,
 420                     gettext("\n  Cert has expired\n"), cert);
 421                 break;
 422 
 423         case CHK_TIME_IS_BEFORE:
 424                 (void) sunw_print_times(stderr, PRNT_NOT_BEFORE,
 425                     gettext("\n  Warning: cert not yet valid\n"), cert);
 426                 break;
 427 
 428         default:
 429                 break;
 430         }
 431 
 432         return (ret);
 433 }
 434 
 435 static time_errs_t
 436 time_check(X509 *cert)
 437 {
 438         int i;
 439 
 440         i = X509_cmp_time(X509_get_notBefore(cert), NULL);
 441         if (i == 0)
 442                 return (CHK_TIME_BEFORE_BAD);
 443         if (i > 0)
 444                 return (CHK_TIME_IS_BEFORE);
 445         /* After 'not before' time */
 446 
 447         i = X509_cmp_time(X509_get_notAfter(cert), NULL);
 448         if (i == 0)
 449                 return (CHK_TIME_AFTER_BAD);
 450         if (i < 0)
 451                 return (CHK_TIME_HAS_EXPIRED);
 452         return (CHK_TIME_OK);
 453 }
 454 
 455 static int
 456 write_files(STACK_OF(X509) *t_out, X509 *c_out, EVP_PKEY *k_out)
 457 {
 458         if (key_out != NULL) {
 459                 if (verbose)
 460                         (void) printf(gettext("%s: writing key\n"), progname);
 461                 if (do_ofile(key_out, k_out, NULL, NULL) < 0)
 462                         return (-1);
 463         }
 464 
 465         if (cert_out != NULL) {
 466                 if (verbose)
 467                         (void) printf(gettext("%s: writing cert\n"), progname);
 468                 if (do_ofile(cert_out, NULL, c_out, NULL) < 0)
 469                         return (-1);
 470         }
 471 
 472         if (trust_out != NULL) {
 473                 if (verbose)
 474                         (void) printf(gettext("%s: writing trust\n"),
 475                             progname);
 476                 if (do_ofile(trust_out, NULL, NULL, t_out) < 0)
 477                         return (-1);
 478         }
 479 
 480         return (0);
 481 }
 482 
 483 static int
 484 get_ifile(char *name, char *pass, EVP_PKEY **tmp_k, X509 **tmp_c,
 485     STACK_OF(X509) **tmp_t)
 486 {
 487         PKCS12          *p12;
 488         FILE            *fp;
 489         int             ret;
 490         struct stat     sbuf;
 491 
 492         if (stat(name, &sbuf) == 0 && !S_ISREG(sbuf.st_mode)) {
 493                 wbku_printerr("%s is not a regular file\n", name);
 494                 return (-1);
 495         }
 496 
 497         if ((fp = fopen(name, "r")) == NULL) {
 498                 wbku_printerr("cannot open input file %s", name);
 499                 return (-1);
 500         }
 501 
 502         p12 = d2i_PKCS12_fp(fp, NULL);
 503         if (p12 == NULL) {
 504                 wbku_printerr("cannot read file %s: %s\n", name, cryptoerr());
 505                 (void) fclose(fp);
 506                 return (-1);
 507         }
 508         (void) fclose(fp);
 509 
 510         ret = sunw_PKCS12_parse(p12, pass, matchty, k_matchval, k_len,
 511             NULL, tmp_k, tmp_c, tmp_t);
 512         if (ret <= 0) {
 513                 if (ret == 0)
 514                         wbku_printerr("cannot find matching cert and key\n");
 515                 else
 516                         wbku_printerr("cannot parse %s: %s\n", name,
 517                             cryptoerr());
 518                 PKCS12_free(p12);
 519                 return (-1);
 520         }
 521         return (0);
 522 }
 523 
 524 static int
 525 do_ofile(char *name, EVP_PKEY *pkey, X509 *cert, STACK_OF(X509) *ta)
 526 {
 527         STACK_OF(EVP_PKEY) *klist = NULL;
 528         STACK_OF(X509)  *clist = NULL;
 529         PKCS12          *p12 = NULL;
 530         int             ret = 0;
 531         FILE            *fp;
 532         struct stat     sbuf;
 533 
 534         if (stat(name, &sbuf) == 0 && !S_ISREG(sbuf.st_mode)) {
 535                 wbku_printerr("%s is not a regular file\n", name);
 536                 return (-1);
 537         }
 538 
 539         if ((fp = fopen(name, "w")) == NULL) {
 540                 wbku_printerr("cannot open output file %s", name);
 541                 return (-1);
 542         }
 543 
 544         if ((clist = sk_X509_new_null()) == NULL ||
 545             (klist = sk_EVP_PKEY_new_null()) == NULL) {
 546                 wbku_printerr("out of memory\n");
 547                 ret = -1;
 548                 goto cleanup;
 549         }
 550 
 551         if (cert != NULL && sk_X509_push(clist, cert) == 0) {
 552                 wbku_printerr("out of memory\n");
 553                 ret = -1;
 554                 goto cleanup;
 555         }
 556 
 557         if (pkey != NULL && sk_EVP_PKEY_push(klist, pkey) == 0) {
 558                 wbku_printerr("out of memory\n");
 559                 ret = -1;
 560                 goto cleanup;
 561         }
 562 
 563         p12 = sunw_PKCS12_create(WANBOOT_PASSPHRASE, klist, clist, ta);
 564         if (p12 == NULL) {
 565                 wbku_printerr("cannot create %s: %s\n", name, cryptoerr());
 566                 ret = -1;
 567                 goto cleanup;
 568         }
 569 
 570         if (i2d_PKCS12_fp(fp, p12) == 0) {
 571                 wbku_printerr("cannot write %s: %s\n", name, cryptoerr());
 572                 ret = -1;
 573                 goto cleanup;
 574         }
 575 
 576 cleanup:
 577         (void) fclose(fp);
 578         if (p12 != NULL)
 579                 PKCS12_free(p12);
 580         /*
 581          * Put the cert and pkey off of the stack so that they won't
 582          * be freed two times.  (If they get left in the stack then
 583          * they will be freed with the stack.)
 584          */
 585         if (clist != NULL) {
 586                 if (cert != NULL && sk_X509_num(clist) == 1) {
 587                         /* LINTED */
 588                         (void) sk_X509_delete(clist, 0);
 589                 }
 590                 sk_X509_pop_free(clist, X509_free);
 591         }
 592         if (klist != NULL) {
 593                 if (pkey != NULL && sk_EVP_PKEY_num(klist) == 1) {
 594                         /* LINTED */
 595                         (void) sk_EVP_PKEY_delete(klist, 0);
 596                 }
 597                 sk_EVP_PKEY_pop_free(klist, sunw_evp_pkey_free);
 598         }
 599 
 600         return (ret);
 601 }
 602 
 603 static void
 604 usage(void)
 605 {
 606         (void) fprintf(stderr,
 607             gettext("usage:\n"
 608             "     %s -i <file> -c <file> -k <file> -t <file> [-l <keyid> -v]\n"
 609             "\n"),
 610             progname);
 611         (void) fprintf(stderr,
 612             gettext(" where:\n"
 613             "  -i - input file to be split into component parts and put in\n"
 614             "       files given by -c, -k and -t\n"
 615             "  -c - output file for the client certificate\n"
 616             "  -k - output file for the client private key\n"
 617             "  -t - output file for the remaining certificates (assumed\n"
 618             "       to be trust anchors)\n"
 619             "\n Files are assumed to be pkcs12-format files.\n\n"
 620             "  -v - verbose\n"
 621             "  -l - value of 'localkeyid' attribute in client cert and\n"
 622             "       private key to be selected from the input file.\n\n"));
 623         exit(EXIT_FAILURE);
 624 }
 625 
 626 /*
 627  * Return a pointer to a static buffer that contains a listing of crypto
 628  * errors.  We presume that the user doesn't want more than 8KB of error
 629  * messages :-)
 630  */
 631 static const char *
 632 cryptoerr(void)
 633 {
 634         static char     errbuf[8192];
 635         ulong_t         err;
 636         const char      *pfile;
 637         int             line;
 638         unsigned int    nerr = 0;
 639 
 640         errbuf[0] = '\0';
 641         while ((err = ERR_get_error_line(&pfile, &line)) != 0) {
 642                 if (++nerr > 1)
 643                         (void) strlcat(errbuf, "\n\t", sizeof (errbuf));
 644 
 645                 if (err == (ulong_t)-1) {
 646                         (void) strlcat(errbuf, strerror(errno),
 647                             sizeof (errbuf));
 648                         break;
 649                 }
 650                 (void) strlcat(errbuf, ERR_reason_error_string(err),
 651                     sizeof (errbuf));
 652         }
 653 
 654         return (errbuf);
 655 }