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