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