Annotation of src/usr.bin/openssl/certhash.c, Revision 1.1
1.1 ! jsing 1: /*
! 2: * Copyright (c) 2014, 2015 Joel Sing <jsing@openbsd.org>
! 3: *
! 4: * Permission to use, copy, modify, and distribute this software for any
! 5: * purpose with or without fee is hereby granted, provided that the above
! 6: * copyright notice and this permission notice appear in all copies.
! 7: *
! 8: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
! 9: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
! 10: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
! 11: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
! 12: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
! 13: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
! 14: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
! 15: */
! 16:
! 17: #include <sys/param.h>
! 18: #include <sys/types.h>
! 19: #include <sys/limits.h>
! 20: #include <sys/stat.h>
! 21:
! 22: #include <errno.h>
! 23: #include <dirent.h>
! 24: #include <fcntl.h>
! 25: #include <stdio.h>
! 26: #include <string.h>
! 27: #include <unistd.h>
! 28:
! 29: #include <openssl/bio.h>
! 30: #include <openssl/evp.h>
! 31: #include <openssl/pem.h>
! 32: #include <openssl/x509.h>
! 33:
! 34: #include "apps.h"
! 35:
! 36: static struct {
! 37: int dryrun;
! 38: int verbose;
! 39: } certhash_config;
! 40:
! 41: struct option certhash_options[] = {
! 42: {
! 43: .name = "n",
! 44: .desc = "Perform a dry-run - do not make any changes",
! 45: .type = OPTION_FLAG,
! 46: .opt.flag = &certhash_config.dryrun,
! 47: },
! 48: {
! 49: .name = "v",
! 50: .desc = "Verbose",
! 51: .type = OPTION_FLAG,
! 52: .opt.flag = &certhash_config.verbose,
! 53: },
! 54: { NULL },
! 55: };
! 56:
! 57: struct hashinfo {
! 58: char *filename;
! 59: char *target;
! 60: unsigned long hash;
! 61: unsigned int index;
! 62: unsigned char fingerprint[EVP_MAX_MD_SIZE];
! 63: int is_crl;
! 64: int is_dup;
! 65: int exists;
! 66: int changed;
! 67: struct hashinfo *reference;
! 68: struct hashinfo *next;
! 69: };
! 70:
! 71: static struct hashinfo *
! 72: hashinfo(const char *filename, unsigned long hash, unsigned char *fingerprint)
! 73: {
! 74: struct hashinfo *hi;
! 75:
! 76: if ((hi = calloc(1, sizeof(*hi))) == NULL)
! 77: return (NULL);
! 78: if (filename != NULL) {
! 79: if ((hi->filename = strdup(filename)) == NULL) {
! 80: free(hi);
! 81: return (NULL);
! 82: }
! 83: }
! 84: hi->hash = hash;
! 85: if (fingerprint != NULL)
! 86: memcpy(hi->fingerprint, fingerprint, sizeof(hi->fingerprint));
! 87:
! 88: return (hi);
! 89: }
! 90:
! 91: static void
! 92: hashinfo_free(struct hashinfo *hi)
! 93: {
! 94: free(hi->filename);
! 95: free(hi->target);
! 96: free(hi);
! 97: }
! 98:
! 99: #ifdef DEBUG
! 100: static void
! 101: hashinfo_print(struct hashinfo *hi)
! 102: {
! 103: int i;
! 104:
! 105: printf("hashinfo %s %08lx %u %i\n", hi->filename, hi->hash,
! 106: hi->index, hi->is_crl);
! 107: for (i = 0; i < (int)EVP_MAX_MD_SIZE; i++) {
! 108: printf("%02X%c", hi->fingerprint[i],
! 109: (i + 1 == (int)EVP_MAX_MD_SIZE) ? '\n' : ':');
! 110: }
! 111: }
! 112: #endif
! 113:
! 114: static int
! 115: hashinfo_compare(const void *a, const void *b)
! 116: {
! 117: struct hashinfo *hia = *(struct hashinfo **)a;
! 118: struct hashinfo *hib = *(struct hashinfo **)b;
! 119: int rv;
! 120:
! 121: rv = hia->hash - hib->hash;
! 122: if (rv != 0)
! 123: return (rv);
! 124: rv = bcmp(hia->fingerprint, hib->fingerprint, sizeof(hia->fingerprint));
! 125: if (rv != 0)
! 126: return (rv);
! 127: return strcmp(hia->filename, hib->filename);
! 128: }
! 129:
! 130: static struct hashinfo *
! 131: hashinfo_chain(struct hashinfo *head, struct hashinfo *entry)
! 132: {
! 133: struct hashinfo *hi = head;
! 134:
! 135: if (hi == NULL)
! 136: return (entry);
! 137: while (hi->next != NULL)
! 138: hi = hi->next;
! 139: hi->next = entry;
! 140:
! 141: return (head);
! 142: }
! 143:
! 144: static void
! 145: hashinfo_chain_free(struct hashinfo *hi)
! 146: {
! 147: struct hashinfo *next;
! 148:
! 149: while (hi != NULL) {
! 150: next = hi->next;
! 151: hashinfo_free(hi);
! 152: hi = next;
! 153: }
! 154: }
! 155:
! 156: static size_t
! 157: hashinfo_chain_length(struct hashinfo *hi)
! 158: {
! 159: int len = 0;
! 160:
! 161: while (hi != NULL) {
! 162: len++;
! 163: hi = hi->next;
! 164: }
! 165: return (len);
! 166: }
! 167:
! 168: static int
! 169: hashinfo_chain_sort(struct hashinfo **head)
! 170: {
! 171: struct hashinfo **list, *entry;
! 172: size_t len;
! 173: int i;
! 174:
! 175: if (*head == NULL)
! 176: return (0);
! 177:
! 178: len = hashinfo_chain_length(*head);
! 179: if ((list = reallocarray(NULL, len, sizeof(struct hashinfo *))) == NULL)
! 180: return (-1);
! 181:
! 182: for (entry = *head, i = 0; entry != NULL; entry = entry->next, i++)
! 183: list[i] = entry;
! 184: qsort(list, len, sizeof(struct hashinfo *), hashinfo_compare);
! 185:
! 186: *head = entry = list[0];
! 187: for (i = 1; i < len; i++) {
! 188: entry->next = list[i];
! 189: entry = list[i];
! 190: }
! 191: entry->next = NULL;
! 192:
! 193: return (0);
! 194: }
! 195:
! 196: static char *
! 197: hashinfo_linkname(struct hashinfo *hi)
! 198: {
! 199: char *filename;
! 200:
! 201: if (asprintf(&filename, "%08lx.%s%u", hi->hash,
! 202: (hi->is_crl ? "r" : ""), hi->index) == -1)
! 203: return (NULL);
! 204:
! 205: return (filename);
! 206: }
! 207:
! 208: static int
! 209: filename_is_hash(const char *filename)
! 210: {
! 211: const char *p = filename;
! 212:
! 213: while ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f'))
! 214: p++;
! 215: if (*p++ != '.')
! 216: return (0);
! 217: if (*p == 'r') /* CRL format. */
! 218: p++;
! 219: while (*p >= '0' && *p <= '9')
! 220: p++;
! 221: if (*p != '\0')
! 222: return (0);
! 223:
! 224: return (1);
! 225: }
! 226:
! 227: static int
! 228: filename_is_pem(const char *filename)
! 229: {
! 230: const char *q, *p = filename;
! 231:
! 232: if ((q = strchr(p, '\0')) == NULL)
! 233: return (0);
! 234: if ((q - p) < 4)
! 235: return (0);
! 236: if (strncmp((q - 4), ".pem", 4) != 0)
! 237: return (0);
! 238:
! 239: return (1);
! 240: }
! 241:
! 242: static struct hashinfo *
! 243: hashinfo_from_linkname(const char *linkname, const char *target)
! 244: {
! 245: struct hashinfo *hi = NULL;
! 246: const char *errstr;
! 247: char *l, *p, *ep;
! 248: long long val;
! 249:
! 250: if ((l = strdup(linkname)) == NULL)
! 251: goto err;
! 252: if ((p = strchr(l, '.')) == NULL)
! 253: goto err;
! 254: *p++ = '\0';
! 255:
! 256: if ((hi = hashinfo(linkname, 0, NULL)) == NULL)
! 257: goto err;
! 258: if ((hi->target = strdup(target)) == NULL)
! 259: goto err;
! 260:
! 261: errno = 0;
! 262: val = strtoll(l, &ep, 16);
! 263: if (l[0] == '\0' || *ep != '\0')
! 264: goto err;
! 265: if (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
! 266: goto err;
! 267: if (val < 0 || val > ULONG_MAX)
! 268: goto err;
! 269: hi->hash = (unsigned long)val;
! 270:
! 271: if (*p == 'r') {
! 272: hi->is_crl = 1;
! 273: p++;
! 274: }
! 275:
! 276: val = strtonum(p, 0, 0xffffffff, &errstr);
! 277: if (errstr != NULL)
! 278: goto err;
! 279:
! 280: hi->index = (unsigned int)val;
! 281:
! 282: goto done;
! 283:
! 284: err:
! 285: hashinfo_free(hi);
! 286: hi = NULL;
! 287:
! 288: done:
! 289: free(l);
! 290:
! 291: return (hi);
! 292: }
! 293:
! 294: static struct hashinfo *
! 295: certhash_cert(BIO *bio, const char *filename)
! 296: {
! 297: unsigned char fingerprint[EVP_MAX_MD_SIZE];
! 298: struct hashinfo *hi = NULL;
! 299: const EVP_MD *digest;
! 300: X509 *cert = NULL;
! 301: unsigned long hash;
! 302: unsigned int len;
! 303:
! 304: if ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) == NULL)
! 305: goto err;
! 306:
! 307: hash = X509_subject_name_hash(cert);
! 308:
! 309: digest = EVP_sha256();
! 310: if (X509_digest(cert, digest, fingerprint, &len) != 1) {
! 311: fprintf(stderr, "out of memory\n");
! 312: goto err;
! 313: }
! 314:
! 315: hi = hashinfo(filename, hash, fingerprint);
! 316:
! 317: err:
! 318: X509_free(cert);
! 319:
! 320: return (hi);
! 321: }
! 322:
! 323: static struct hashinfo *
! 324: certhash_crl(BIO *bio, const char *filename)
! 325: {
! 326: unsigned char fingerprint[EVP_MAX_MD_SIZE];
! 327: struct hashinfo *hi = NULL;
! 328: const EVP_MD *digest;
! 329: X509_CRL *crl = NULL;
! 330: unsigned long hash;
! 331: unsigned int len;
! 332:
! 333: if ((crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL)) == NULL)
! 334: return (NULL);
! 335:
! 336: hash = X509_NAME_hash(X509_CRL_get_issuer(crl));
! 337:
! 338: digest = EVP_sha256();
! 339: if (X509_CRL_digest(crl, digest, fingerprint, &len) != 1) {
! 340: fprintf(stderr, "out of memory\n");
! 341: goto err;
! 342: }
! 343:
! 344: hi = hashinfo(filename, hash, fingerprint);
! 345:
! 346: err:
! 347: X509_CRL_free(crl);
! 348:
! 349: return (hi);
! 350: }
! 351:
! 352: static int
! 353: certhash_addlink(struct hashinfo **links, struct hashinfo *hi)
! 354: {
! 355: struct hashinfo *link = NULL;
! 356:
! 357: if ((link = hashinfo(NULL, hi->hash, hi->fingerprint)) == NULL)
! 358: goto err;
! 359:
! 360: printf("hi->is_crl = %i\n", hi->is_crl);
! 361: if ((link->filename = hashinfo_linkname(hi)) == NULL)
! 362: goto err;
! 363: printf("filename = %s\n", link->filename);
! 364:
! 365: link->reference = hi;
! 366: link->changed = 1;
! 367: *links = hashinfo_chain(*links, link);
! 368: hi->reference = link;
! 369:
! 370: return (0);
! 371:
! 372: err:
! 373: hashinfo_free(link);
! 374: return (-1);
! 375: }
! 376:
! 377: static void
! 378: certhash_findlink(struct hashinfo *links, struct hashinfo *hi)
! 379: {
! 380: struct hashinfo *link;
! 381:
! 382: for (link = links; link != NULL; link = link->next) {
! 383: if (link->is_crl == hi->is_crl &&
! 384: link->hash == hi->hash &&
! 385: link->index == hi->index &&
! 386: link->reference == NULL) {
! 387: link->reference = hi;
! 388: if (link->target == NULL ||
! 389: strcmp(link->target, hi->filename) != 0)
! 390: link->changed = 1;
! 391: hi->reference = link;
! 392: break;
! 393: }
! 394: }
! 395: }
! 396:
! 397: static void
! 398: certhash_index(struct hashinfo *head, const char *name)
! 399: {
! 400: struct hashinfo *last, *entry;
! 401: int index = 0;
! 402:
! 403: last = NULL;
! 404: for (entry = head; entry != NULL; entry = entry->next) {
! 405: if (last != NULL) {
! 406: if (entry->hash == last->hash) {
! 407: if (bcmp(entry->fingerprint, last->fingerprint,
! 408: sizeof(entry->fingerprint)) == 0) {
! 409: fprintf(stderr, "WARNING: duplicate %s "
! 410: "in %s (using %s), ignoring...\n",
! 411: name, entry->filename,
! 412: last->filename);
! 413: entry->is_dup = 1;
! 414: continue;
! 415: }
! 416: index++;
! 417: } else {
! 418: index = 0;
! 419: }
! 420: }
! 421: entry->index = index;
! 422: last = entry;
! 423: }
! 424: }
! 425:
! 426: static int
! 427: certhash_merge(struct hashinfo **links, struct hashinfo **certs,
! 428: struct hashinfo **crls)
! 429: {
! 430: struct hashinfo *cert, *crl;
! 431:
! 432: /* Pass 1 - sort and index entries. */
! 433: if (hashinfo_chain_sort(certs) == -1)
! 434: return (-1);
! 435: if (hashinfo_chain_sort(crls) == -1)
! 436: return (-1);
! 437: certhash_index(*certs, "certificate");
! 438: certhash_index(*crls, "CRL");
! 439:
! 440: /* Pass 2 - map to existing links. */
! 441: for (cert = *certs; cert != NULL; cert = cert->next) {
! 442: if (cert->is_dup == 1)
! 443: continue;
! 444: certhash_findlink(*links, cert);
! 445: }
! 446: for (crl = *crls; crl != NULL; crl = crl->next) {
! 447: if (crl->is_dup == 1)
! 448: continue;
! 449: certhash_findlink(*links, crl);
! 450: }
! 451:
! 452: /* Pass 3 - determine missing links. */
! 453: for (cert = *certs; cert != NULL; cert = cert->next) {
! 454: if (cert->is_dup == 1 || cert->reference != NULL)
! 455: continue;
! 456: if (certhash_addlink(links, cert) == -1)
! 457: return (-1);
! 458: }
! 459: for (crl = *crls; crl != NULL; crl = crl->next) {
! 460: if (crl->is_dup == 1 || crl->reference != NULL)
! 461: continue;
! 462: if (certhash_addlink(links, crl) == -1)
! 463: return (-1);
! 464: }
! 465:
! 466: return (0);
! 467: }
! 468:
! 469: static int
! 470: certhash_link(int dfd, struct dirent *dep, struct hashinfo **links)
! 471: {
! 472: struct hashinfo *hi = NULL;
! 473: char target[MAXPATHLEN];
! 474: struct stat sb;
! 475: int n;
! 476:
! 477: if (fstatat(dfd, dep->d_name, &sb, AT_SYMLINK_NOFOLLOW) == -1) {
! 478: fprintf(stderr, "failed to stat %s\n", dep->d_name);
! 479: return (-1);
! 480: }
! 481: if (!S_ISLNK(sb.st_mode))
! 482: return (0);
! 483:
! 484: n = readlinkat(dfd, dep->d_name, target, sizeof(target));
! 485: if (n == -1) {
! 486: fprintf(stderr, "failed to readlink %s\n", dep->d_name);
! 487: return (-1);
! 488: }
! 489: target[n] = '\0';
! 490:
! 491: hi = hashinfo_from_linkname(dep->d_name, target);
! 492: if (hi == NULL) {
! 493: fprintf(stderr, "failed to get hash info %s\n", dep->d_name);
! 494: return (-1);
! 495: }
! 496: hi->exists = 1;
! 497: *links = hashinfo_chain(*links, hi);
! 498:
! 499: return (0);
! 500: }
! 501:
! 502: static int
! 503: certhash_file(int dfd, struct dirent *dep, struct hashinfo **certs,
! 504: struct hashinfo **crls)
! 505: {
! 506: struct hashinfo *hi = NULL;
! 507: int has_cert, has_crl;
! 508: int ffd, ret = -1;
! 509: BIO *bio = NULL;
! 510: FILE *f;
! 511:
! 512: has_cert = has_crl = 0;
! 513:
! 514: if ((ffd = openat(dfd, dep->d_name, O_RDONLY)) == -1) {
! 515: fprintf(stderr, "failed to open %s\n", dep->d_name);
! 516: goto err;
! 517: }
! 518: if ((f = fdopen(ffd, "r")) == NULL) {
! 519: fprintf(stderr, "failed to fdopen %s\n", dep->d_name);
! 520: goto err;
! 521: }
! 522: if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) {
! 523: fprintf(stderr, "failed to create bio\n");
! 524: goto err;
! 525: }
! 526:
! 527: if ((hi = certhash_cert(bio, dep->d_name)) != NULL) {
! 528: has_cert = 1;
! 529: *certs = hashinfo_chain(*certs, hi);
! 530: }
! 531:
! 532: if (BIO_reset(bio) != 0) {
! 533: fprintf(stderr, "BIO_reset failed\n");
! 534: goto err;
! 535: }
! 536:
! 537: if ((hi = certhash_crl(bio, dep->d_name)) != NULL) {
! 538: has_crl = hi->is_crl = 1;
! 539: *crls = hashinfo_chain(*crls, hi);
! 540: }
! 541:
! 542: if (!has_cert && !has_crl)
! 543: fprintf(stderr, "PEM file %s does not contain a certificate "
! 544: "or CRL, ignoring...\n", dep->d_name);
! 545:
! 546: ret = 0;
! 547:
! 548: err:
! 549: BIO_free(bio);
! 550: if (ffd != -1)
! 551: close(ffd);
! 552:
! 553: return (ret);
! 554: }
! 555:
! 556: static int
! 557: certhash_directory(const char *path)
! 558: {
! 559: struct hashinfo *links = NULL, *certs = NULL, *crls = NULL, *link;
! 560: int dfd = -1, ret = 0;
! 561: struct dirent *dep;
! 562: DIR *dip = NULL;
! 563:
! 564: if ((dfd = open(path, O_DIRECTORY)) == -1) {
! 565: fprintf(stderr, "failed to open directory %s\n", path);
! 566: goto err;
! 567: }
! 568: if ((dip = fdopendir(dfd)) == NULL) {
! 569: fprintf(stderr, "failed to open directory %s\n", path);
! 570: goto err;
! 571: }
! 572:
! 573: if (certhash_config.verbose)
! 574: fprintf(stdout, "scanning directory %s\n", path);
! 575:
! 576: /* Create lists of existing hash links, certs and CRLs. */
! 577: while ((dep = readdir(dip)) != NULL) {
! 578: if (filename_is_hash(dep->d_name)) {
! 579: if (certhash_link(dfd, dep, &links) == -1)
! 580: goto err;
! 581: }
! 582: if (filename_is_pem(dep->d_name)) {
! 583: if (certhash_file(dfd, dep, &certs, &crls) == -1)
! 584: goto err;
! 585: }
! 586: }
! 587:
! 588: if (certhash_merge(&links, &certs, &crls) == -1) {
! 589: fprintf(stderr, "certhash merge failed\n");
! 590: goto err;
! 591: }
! 592:
! 593: /* Remove spurious links. */
! 594: for (link = links; link != NULL; link = link->next) {
! 595: if (link->exists == 0 ||
! 596: (link->reference != NULL && link->changed == 0))
! 597: continue;
! 598: if (certhash_config.verbose)
! 599: fprintf(stdout, "%s link %s -> %s\n",
! 600: (certhash_config.dryrun ? "would remove" :
! 601: "removing"), link->filename, link->target);
! 602: if (certhash_config.dryrun)
! 603: continue;
! 604: if (unlinkat(dfd, link->filename, 0) == -1) {
! 605: fprintf(stderr, "failed to remove link %s\n",
! 606: link->filename);
! 607: goto err;
! 608: }
! 609: }
! 610:
! 611: /* Create missing links. */
! 612: for (link = links; link != NULL; link = link->next) {
! 613: if (link->exists == 1 && link->changed == 0)
! 614: continue;
! 615: if (certhash_config.verbose)
! 616: fprintf(stdout, "%s link %s -> %s\n",
! 617: (certhash_config.dryrun ? "would create" :
! 618: "creating"), link->filename,
! 619: link->reference->filename);
! 620: if (certhash_config.dryrun)
! 621: continue;
! 622: if (symlinkat(link->reference->filename, dfd,
! 623: link->filename) == -1) {
! 624: fprintf(stderr, "failed to create link %s -> %s\n",
! 625: link->filename, link->reference->filename);
! 626: goto err;
! 627: }
! 628: }
! 629:
! 630: goto done;
! 631:
! 632: err:
! 633: ret = 1;
! 634:
! 635: done:
! 636: hashinfo_chain_free(certs);
! 637: hashinfo_chain_free(crls);
! 638: hashinfo_chain_free(links);
! 639:
! 640: if (dip != NULL)
! 641: closedir(dip);
! 642: else if (dfd != -1)
! 643: close(dfd);
! 644:
! 645: return (ret);
! 646: }
! 647:
! 648: static void
! 649: certhash_usage(void)
! 650: {
! 651: fprintf(stderr, "usage: certhash [-nv] dir ...\n");
! 652: options_usage(certhash_options);
! 653: }
! 654:
! 655: int certhash_main(int argc, char **argv);
! 656:
! 657: int
! 658: certhash_main(int argc, char **argv)
! 659: {
! 660: int argsused;
! 661: int i, ret = 0;
! 662:
! 663: memset(&certhash_config, 0, sizeof(certhash_config));
! 664:
! 665: if (options_parse(argc, argv, certhash_options, NULL, &argsused) != 0) {
! 666: certhash_usage();
! 667: return (1);
! 668: }
! 669:
! 670: for (i = argsused; i < argc; i++)
! 671: ret |= certhash_directory(argv[i]);
! 672:
! 673: return (ret);
! 674: }