1 /*- 2 * Copyright (c) 2023-2025 Dag-Erling Smørgrav <des@FreeBSD.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7 #include <sys/types.h> 8 #include <sys/sysctl.h> 9 #include <sys/stat.h> 10 #include <sys/tree.h> 11 12 #include <dirent.h> 13 #include <err.h> 14 #include <errno.h> 15 #include <fcntl.h> 16 #include <fts.h> 17 #include <paths.h> 18 #include <stdbool.h> 19 #include <stdio.h> 20 #include <stdlib.h> 21 #include <string.h> 22 #include <unistd.h> 23 24 #include <openssl/ssl.h> 25 26 #define info(fmt, ...) \ 27 do { \ 28 if (verbose) \ 29 fprintf(stderr, fmt "\n", ##__VA_ARGS__); \ 30 } while (0) 31 32 static char * 33 xasprintf(const char *fmt, ...) 34 { 35 va_list ap; 36 char *str; 37 int ret; 38 39 va_start(ap, fmt); 40 ret = vasprintf(&str, fmt, ap); 41 va_end(ap); 42 if (ret < 0 || str == NULL) 43 err(1, NULL); 44 return (str); 45 } 46 47 static char * 48 xstrdup(const char *str) 49 { 50 char *dup; 51 52 if ((dup = strdup(str)) == NULL) 53 err(1, NULL); 54 return (dup); 55 } 56 57 static void usage(void); 58 59 static bool dryrun; 60 static bool longnames; 61 static bool nobundle; 62 static bool unprivileged; 63 static bool verbose; 64 65 static const char *localbase; 66 static const char *destdir; 67 static const char *distbase; 68 static const char *metalog; 69 70 static const char *uname = "root"; 71 static const char *gname = "wheel"; 72 73 static const char *const default_trusted_paths[] = { 74 "/usr/share/certs/trusted", 75 "%L/share/certs/trusted", 76 "%L/share/certs", 77 NULL 78 }; 79 static char **trusted_paths; 80 81 static const char *const default_untrusted_paths[] = { 82 "/usr/share/certs/untrusted", 83 "%L/share/certs/untrusted", 84 NULL 85 }; 86 static char **untrusted_paths; 87 88 static char *trusted_dest; 89 static char *untrusted_dest; 90 static char *bundle_dest; 91 92 #define SSL_PATH "/etc/ssl" 93 #define TRUSTED_DIR "certs" 94 #define TRUSTED_PATH SSL_PATH "/" TRUSTED_DIR 95 #define UNTRUSTED_DIR "untrusted" 96 #define UNTRUSTED_PATH SSL_PATH "/" UNTRUSTED_DIR 97 #define LEGACY_DIR "blacklisted" 98 #define LEGACY_PATH SSL_PATH "/" LEGACY_DIR 99 #define BUNDLE_FILE "cert.pem" 100 #define BUNDLE_PATH SSL_PATH "/" BUNDLE_FILE 101 102 static FILE *mlf; 103 104 /* 105 * Create a directory and its parents as needed. 106 */ 107 static void 108 mkdirp(const char *dir) 109 { 110 struct stat sb; 111 const char *sep; 112 char *parent; 113 114 if (stat(dir, &sb) == 0) 115 return; 116 if ((sep = strrchr(dir, '/')) != NULL) { 117 parent = xasprintf("%.*s", (int)(sep - dir), dir); 118 mkdirp(parent); 119 free(parent); 120 } 121 info("creating %s", dir); 122 if (mkdir(dir, 0755) != 0) 123 err(1, "mkdir %s", dir); 124 } 125 126 /* 127 * Remove duplicate and trailing slashes from a path. 128 */ 129 static char * 130 normalize_path(const char *str) 131 { 132 char *buf, *dst; 133 134 if ((buf = malloc(strlen(str) + 1)) == NULL) 135 err(1, NULL); 136 for (dst = buf; *str != '\0'; dst++) { 137 if ((*dst = *str++) == '/') { 138 while (*str == '/') 139 str++; 140 if (*str == '\0') 141 break; 142 } 143 } 144 *dst = '\0'; 145 return (buf); 146 } 147 148 /* 149 * Split a colon-separated list into a NULL-terminated array. 150 */ 151 static char ** 152 split_paths(const char *str) 153 { 154 char **paths; 155 const char *p, *q; 156 unsigned int i, n; 157 158 for (p = str, n = 1; *p; p++) { 159 if (*p == ':') 160 n++; 161 } 162 if ((paths = calloc(n + 1, sizeof(*paths))) == NULL) 163 err(1, NULL); 164 for (p = q = str, i = 0; i < n; i++, p = q + 1) { 165 q = strchrnul(p, ':'); 166 if ((paths[i] = strndup(p, q - p)) == NULL) 167 err(1, NULL); 168 } 169 return (paths); 170 } 171 172 /* 173 * Expand %L into LOCALBASE and prefix DESTDIR and DISTBASE as needed. 174 */ 175 static char * 176 expand_path(const char *template) 177 { 178 if (template[0] == '%' && template[1] == 'L') 179 return (xasprintf("%s%s%s", destdir, localbase, template + 2)); 180 return (xasprintf("%s%s%s", destdir, distbase, template)); 181 } 182 183 /* 184 * Expand an array of paths. 185 */ 186 static char ** 187 expand_paths(const char *const *templates) 188 { 189 char **paths; 190 unsigned int i, n; 191 192 for (n = 0; templates[n] != NULL; n++) 193 continue; 194 if ((paths = calloc(n + 1, sizeof(*paths))) == NULL) 195 err(1, NULL); 196 for (i = 0; i < n; i++) 197 paths[i] = expand_path(templates[i]); 198 return (paths); 199 } 200 201 /* 202 * If destdir is a prefix of path, returns a pointer to the rest of path, 203 * otherwise returns path. 204 * 205 * Note that this intentionally does not strip distbase from the path! 206 * Unlike destdir, distbase is expected to be included in the metalog. 207 */ 208 static const char * 209 unexpand_path(const char *path) 210 { 211 const char *p = path; 212 const char *q = destdir; 213 214 while (*p && *p == *q) { 215 p++; 216 q++; 217 } 218 return (*q == '\0' && *p == '/' ? p : path); 219 } 220 221 /* 222 * X509 certificate in a rank-balanced tree. 223 */ 224 struct cert { 225 RB_ENTRY(cert) entry; 226 unsigned long hash; 227 char *name; 228 X509 *x509; 229 char *path; 230 }; 231 232 static void 233 free_cert(struct cert *cert) 234 { 235 free(cert->name); 236 X509_free(cert->x509); 237 free(cert->path); 238 free(cert); 239 } 240 241 static int 242 certcmp(const struct cert *a, const struct cert *b) 243 { 244 return (X509_cmp(a->x509, b->x509)); 245 } 246 247 RB_HEAD(cert_tree, cert); 248 static struct cert_tree trusted = RB_INITIALIZER(&trusted); 249 static struct cert_tree untrusted = RB_INITIALIZER(&untrusted); 250 RB_GENERATE_STATIC(cert_tree, cert, entry, certcmp); 251 252 static void 253 free_certs(struct cert_tree *tree) 254 { 255 struct cert *cert, *tmp; 256 257 RB_FOREACH_SAFE(cert, cert_tree, tree, tmp) { 258 RB_REMOVE(cert_tree, tree, cert); 259 free_cert(cert); 260 } 261 } 262 263 static struct cert * 264 find_cert(struct cert_tree *haystack, X509 *x509) 265 { 266 struct cert needle = { .x509 = x509 }; 267 268 return (RB_FIND(cert_tree, haystack, &needle)); 269 } 270 271 /* 272 * File containing a certificate in a rank-balanced tree sorted by 273 * certificate hash and disambiguating counter. This is needed because 274 * the certificate hash function is prone to collisions, necessitating a 275 * counter to distinguish certificates that hash to the same value. 276 */ 277 struct file { 278 RB_ENTRY(file) entry; 279 const struct cert *cert; 280 unsigned int c; 281 }; 282 283 static int 284 filecmp(const struct file *a, const struct file *b) 285 { 286 if (a->cert->hash > b->cert->hash) 287 return (1); 288 if (a->cert->hash < b->cert->hash) 289 return (-1); 290 return (a->c - b->c); 291 } 292 293 RB_HEAD(file_tree, file); 294 RB_GENERATE_STATIC(file_tree, file, entry, filecmp); 295 296 /* 297 * Lexicographical sort for scandir(). 298 */ 299 static int 300 lexisort(const struct dirent **d1, const struct dirent **d2) 301 { 302 return (strcmp((*d1)->d_name, (*d2)->d_name)); 303 } 304 305 /* 306 * Read certificate(s) from a single file and insert them into a tree. 307 * Ignore certificates that already exist in the tree. If exclude is not 308 * null, also ignore certificates that exist in exclude. 309 * 310 * Returns the number certificates added to the tree, or -1 on failure. 311 */ 312 static int 313 read_cert(const char *path, struct cert_tree *tree, struct cert_tree *exclude) 314 { 315 FILE *f; 316 X509 *x509; 317 X509_NAME *name; 318 struct cert *cert; 319 unsigned long hash; 320 int len, ni, no; 321 322 if ((f = fopen(path, "r")) == NULL) { 323 warn("%s", path); 324 return (-1); 325 } 326 for (ni = no = 0; 327 (x509 = PEM_read_X509(f, NULL, NULL, NULL)) != NULL; 328 ni++) { 329 hash = X509_subject_name_hash(x509); 330 if (exclude && find_cert(exclude, x509)) { 331 info("%08lx: excluded", hash); 332 X509_free(x509); 333 continue; 334 } 335 if (find_cert(tree, x509)) { 336 info("%08lx: duplicate", hash); 337 X509_free(x509); 338 continue; 339 } 340 if ((cert = calloc(1, sizeof(*cert))) == NULL) 341 err(1, NULL); 342 cert->x509 = x509; 343 name = X509_get_subject_name(x509); 344 cert->hash = X509_NAME_hash_ex(name, NULL, NULL, NULL); 345 len = X509_NAME_get_text_by_NID(name, NID_commonName, 346 NULL, 0); 347 if (len > 0) { 348 if ((cert->name = malloc(len + 1)) == NULL) 349 err(1, NULL); 350 X509_NAME_get_text_by_NID(name, NID_commonName, 351 cert->name, len + 1); 352 } else { 353 /* fallback for certificates without CN */ 354 cert->name = X509_NAME_oneline(name, NULL, 0); 355 } 356 cert->path = xstrdup(unexpand_path(path)); 357 if (RB_INSERT(cert_tree, tree, cert) != NULL) 358 errx(1, "unexpected duplicate"); 359 info("%08lx: %s", cert->hash, cert->name); 360 no++; 361 } 362 /* 363 * ni is the number of certificates we found in the file. 364 * no is the number of certificates that weren't already in our 365 * tree or on the exclusion list. 366 */ 367 if (ni == 0) 368 warnx("%s: no valid certificates found", path); 369 fclose(f); 370 return (no); 371 } 372 373 /* 374 * Load all certificates found in the specified path into a tree, 375 * optionally excluding those that already exist in a different tree. 376 * 377 * Returns the number of certificates added to the tree, or -1 on failure. 378 */ 379 static int 380 read_certs(const char *path, struct cert_tree *tree, struct cert_tree *exclude) 381 { 382 struct stat sb; 383 char *paths[] = { __DECONST(char *, path), NULL }; 384 FTS *fts; 385 FTSENT *ent; 386 int fts_options = FTS_LOGICAL | FTS_NOCHDIR; 387 int ret, total = 0; 388 389 if (stat(path, &sb) != 0) { 390 return (-1); 391 } else if (!S_ISDIR(sb.st_mode)) { 392 errno = ENOTDIR; 393 return (-1); 394 } 395 if ((fts = fts_open(paths, fts_options, NULL)) == NULL) 396 err(1, "fts_open()"); 397 while ((ent = fts_read(fts)) != NULL) { 398 if (ent->fts_info != FTS_F) { 399 if (ent->fts_info == FTS_ERR) 400 warnc(ent->fts_errno, "fts_read()"); 401 continue; 402 } 403 info("found %s", ent->fts_path); 404 ret = read_cert(ent->fts_path, tree, exclude); 405 if (ret > 0) 406 total += ret; 407 } 408 fts_close(fts); 409 return (total); 410 } 411 412 /* 413 * Save the contents of a cert tree to disk. 414 * 415 * Returns 0 on success and -1 on failure. 416 */ 417 static int 418 write_certs(const char *dir, struct cert_tree *tree) 419 { 420 struct file_tree files = RB_INITIALIZER(&files); 421 struct cert *cert; 422 struct file *file, *tmp; 423 struct dirent **dents, **ent; 424 char *path, *tmppath = NULL; 425 FILE *f; 426 mode_t mode = 0444; 427 int cmp, d, fd, ndents, ret = 0; 428 429 /* 430 * Start by generating unambiguous file names for each certificate 431 * and storing them in lexicographical order 432 */ 433 RB_FOREACH(cert, cert_tree, tree) { 434 if ((file = calloc(1, sizeof(*file))) == NULL) 435 err(1, NULL); 436 file->cert = cert; 437 for (file->c = 0; file->c < INT_MAX; file->c++) 438 if (RB_INSERT(file_tree, &files, file) == NULL) 439 break; 440 if (file->c == INT_MAX) 441 errx(1, "unable to disambiguate %08lx", cert->hash); 442 free(cert->path); 443 cert->path = xasprintf("%08lx.%d", cert->hash, file->c); 444 } 445 /* 446 * Open and scan the directory. 447 */ 448 if ((d = open(dir, O_DIRECTORY | O_RDONLY)) < 0 || 449 #ifdef BOOTSTRAPPING 450 (ndents = scandir(dir, &dents, NULL, lexisort)) 451 #else 452 (ndents = fdscandir(d, &dents, NULL, lexisort)) 453 #endif 454 < 0) 455 err(1, "%s", dir); 456 /* 457 * Iterate over the directory listing and the certificate listing 458 * in parallel. If the directory listing gets ahead of the 459 * certificate listing, we need to write the current certificate 460 * and advance the certificate listing. If the certificate 461 * listing is ahead of the directory listing, we need to delete 462 * the current file and advance the directory listing. If they 463 * are neck and neck, we have a match and could in theory compare 464 * the two, but in practice it's faster to just replace the 465 * current file with the current certificate (and advance both). 466 */ 467 ent = dents; 468 file = RB_MIN(file_tree, &files); 469 for (;;) { 470 if (ent < dents + ndents) { 471 /* skip directories */ 472 if ((*ent)->d_type == DT_DIR) { 473 free(*ent++); 474 continue; 475 } 476 if (file != NULL) { 477 /* compare current dirent to current cert */ 478 path = file->cert->path; 479 cmp = strcmp((*ent)->d_name, path); 480 } else { 481 /* trailing files in directory */ 482 path = NULL; 483 cmp = -1; 484 } 485 } else { 486 if (file != NULL) { 487 /* trailing certificates */ 488 path = file->cert->path; 489 cmp = 1; 490 } else { 491 /* end of both lists */ 492 path = NULL; 493 break; 494 } 495 } 496 if (cmp < 0) { 497 /* a file on disk with no matching certificate */ 498 info("removing %s/%s", dir, (*ent)->d_name); 499 if (!dryrun) 500 (void)unlinkat(d, (*ent)->d_name, 0); 501 free(*ent++); 502 continue; 503 } 504 if (cmp == 0) { 505 /* a file on disk with a matching certificate */ 506 info("replacing %s/%s", dir, (*ent)->d_name); 507 if (dryrun) { 508 fd = open(_PATH_DEVNULL, O_WRONLY); 509 } else { 510 tmppath = xasprintf(".%s", path); 511 fd = openat(d, tmppath, 512 O_CREAT | O_WRONLY | O_TRUNC, mode); 513 if (!unprivileged && fd >= 0) 514 (void)fchmod(fd, mode); 515 } 516 free(*ent++); 517 } else { 518 /* a certificate with no matching file */ 519 info("writing %s/%s", dir, path); 520 if (dryrun) { 521 fd = open(_PATH_DEVNULL, O_WRONLY); 522 } else { 523 tmppath = xasprintf(".%s", path); 524 fd = openat(d, tmppath, 525 O_CREAT | O_WRONLY | O_EXCL, mode); 526 } 527 } 528 /* write the certificate */ 529 if (fd < 0 || 530 (f = fdopen(fd, "w")) == NULL || 531 !PEM_write_X509(f, file->cert->x509)) { 532 if (tmppath != NULL && fd >= 0) { 533 int serrno = errno; 534 (void)unlinkat(d, tmppath, 0); 535 errno = serrno; 536 } 537 err(1, "%s/%s", dir, tmppath ? tmppath : path); 538 } 539 /* rename temp file if applicable */ 540 if (tmppath != NULL) { 541 if (ret == 0 && renameat(d, tmppath, d, path) != 0) { 542 warn("%s/%s", dir, path); 543 ret = -1; 544 } 545 if (ret != 0) 546 (void)unlinkat(d, tmppath, 0); 547 free(tmppath); 548 tmppath = NULL; 549 } 550 fflush(f); 551 /* emit metalog */ 552 if (mlf != NULL) { 553 fprintf(mlf, ".%s/%s type=file " 554 "uname=%s gname=%s mode=%#o size=%ld\n", 555 unexpand_path(dir), path, 556 uname, gname, mode, ftell(f)); 557 } 558 fclose(f); 559 /* advance certificate listing */ 560 tmp = RB_NEXT(file_tree, &files, file); 561 RB_REMOVE(file_tree, &files, file); 562 free(file); 563 file = tmp; 564 } 565 free(dents); 566 close(d); 567 return (ret); 568 } 569 570 /* 571 * Save all certs in a tree to a single file (bundle). 572 * 573 * Returns 0 on success and -1 on failure. 574 */ 575 static int 576 write_bundle(const char *dir, const char *file, struct cert_tree *tree) 577 { 578 struct cert *cert; 579 char *tmpfile = NULL; 580 FILE *f; 581 int d, fd, ret = 0; 582 mode_t mode = 0444; 583 584 if (dir != NULL) { 585 if ((d = open(dir, O_DIRECTORY | O_RDONLY)) < 0) 586 err(1, "%s", dir); 587 } else { 588 dir = "."; 589 d = AT_FDCWD; 590 } 591 info("writing %s/%s", dir, file); 592 if (dryrun) { 593 fd = open(_PATH_DEVNULL, O_WRONLY); 594 } else { 595 tmpfile = xasprintf(".%s", file); 596 fd = openat(d, tmpfile, O_WRONLY | O_CREAT | O_EXCL, mode); 597 } 598 if (fd < 0 || (f = fdopen(fd, "w")) == NULL) { 599 if (tmpfile != NULL && fd >= 0) { 600 int serrno = errno; 601 (void)unlinkat(d, tmpfile, 0); 602 errno = serrno; 603 } 604 err(1, "%s/%s", dir, tmpfile ? tmpfile : file); 605 } 606 RB_FOREACH(cert, cert_tree, tree) { 607 if (!PEM_write_X509(f, cert->x509)) { 608 warn("%s/%s", dir, tmpfile ? tmpfile : file); 609 ret = -1; 610 break; 611 } 612 } 613 if (tmpfile != NULL) { 614 if (ret == 0 && renameat(d, tmpfile, d, file) != 0) { 615 warn("%s/%s", dir, file); 616 ret = -1; 617 } 618 if (ret != 0) 619 (void)unlinkat(d, tmpfile, 0); 620 free(tmpfile); 621 } 622 if (ret == 0 && mlf != NULL) { 623 fprintf(mlf, 624 ".%s/%s type=file uname=%s gname=%s mode=%#o size=%ld\n", 625 unexpand_path(dir), file, uname, gname, mode, ftell(f)); 626 } 627 fclose(f); 628 if (d != AT_FDCWD) 629 close(d); 630 return (ret); 631 } 632 633 /* 634 * Load trusted certificates. 635 * 636 * Returns the number of certificates loaded. 637 */ 638 static unsigned int 639 load_trusted(void) 640 { 641 unsigned int i, n; 642 int ret; 643 644 /* load external trusted certs */ 645 for (i = n = 0; trusted_paths[i] != NULL; i++) { 646 ret = read_certs(trusted_paths[i], &trusted, &untrusted); 647 if (ret > 0) 648 n += ret; 649 } 650 651 info("%d trusted certificates found", n); 652 return (n); 653 } 654 655 /* 656 * Load untrusted certificates. 657 * 658 * Returns the number of certificates loaded. 659 */ 660 static unsigned int 661 load_untrusted(void) 662 { 663 char *path; 664 unsigned int i, n; 665 int ret; 666 667 /* load external untrusted certs */ 668 for (i = n = 0; untrusted_paths[i] != NULL; i++) { 669 ret = read_certs(untrusted_paths[i], &untrusted, NULL); 670 if (ret > 0) 671 n += ret; 672 } 673 674 /* load legacy untrusted certs */ 675 path = expand_path(LEGACY_PATH); 676 ret = read_certs(path, &untrusted, NULL); 677 if (ret > 0) { 678 warnx("certificates found in legacy directory %s", 679 path); 680 n += ret; 681 } else if (ret == 0) { 682 warnx("legacy directory %s can safely be deleted", 683 path); 684 } 685 free(path); 686 687 info("%d untrusted certificates found", n); 688 return (n); 689 } 690 691 /* 692 * Save trusted certificates. 693 * 694 * Returns 0 on success and -1 on failure. 695 */ 696 static int 697 save_trusted(void) 698 { 699 int ret; 700 701 mkdirp(trusted_dest); 702 ret = write_certs(trusted_dest, &trusted); 703 return (ret); 704 } 705 706 /* 707 * Save untrusted certificates. 708 * 709 * Returns 0 on success and -1 on failure. 710 */ 711 static int 712 save_untrusted(void) 713 { 714 int ret; 715 716 mkdirp(untrusted_dest); 717 ret = write_certs(untrusted_dest, &untrusted); 718 return (ret); 719 } 720 721 /* 722 * Save certificate bundle. 723 * 724 * Returns 0 on success and -1 on failure. 725 */ 726 static int 727 save_bundle(void) 728 { 729 char *dir, *file, *sep; 730 int ret; 731 732 if ((sep = strrchr(bundle_dest, '/')) == NULL) { 733 dir = NULL; 734 file = bundle_dest; 735 } else { 736 dir = xasprintf("%.*s", (int)(sep - bundle_dest), bundle_dest); 737 file = sep + 1; 738 mkdirp(dir); 739 } 740 ret = write_bundle(dir, file, &trusted); 741 free(dir); 742 return (ret); 743 } 744 745 /* 746 * Save everything. 747 * 748 * Returns 0 on success and -1 on failure. 749 */ 750 static int 751 save_all(void) 752 { 753 int ret = 0; 754 755 ret |= save_untrusted(); 756 ret |= save_trusted(); 757 if (!nobundle) 758 ret |= save_bundle(); 759 return (ret); 760 } 761 762 /* 763 * List the contents of a certificate tree. 764 */ 765 static void 766 list_certs(struct cert_tree *tree) 767 { 768 struct cert *cert; 769 char *path, *name; 770 771 RB_FOREACH(cert, cert_tree, tree) { 772 path = longnames ? NULL : strrchr(cert->path, '/'); 773 name = longnames ? NULL : strrchr(cert->name, '='); 774 printf("%s\t%s\n", path ? path + 1 : cert->path, 775 name ? name + 1 : cert->name); 776 } 777 } 778 779 /* 780 * Load installed trusted certificates, then list them. 781 * 782 * Returns 0 on success and -1 on failure. 783 */ 784 static int 785 certctl_list(int argc, char **argv __unused) 786 { 787 if (argc > 1) 788 usage(); 789 /* load installed trusted certificates */ 790 read_certs(trusted_dest, &trusted, NULL); 791 /* list them */ 792 list_certs(&trusted); 793 free_certs(&trusted); 794 return (0); 795 } 796 797 /* 798 * Load installed untrusted certificates, then list them. 799 * 800 * Returns 0 on success and -1 on failure. 801 */ 802 static int 803 certctl_untrusted(int argc, char **argv __unused) 804 { 805 if (argc > 1) 806 usage(); 807 /* load installed untrusted certificates */ 808 read_certs(untrusted_dest, &untrusted, NULL); 809 /* list them */ 810 list_certs(&untrusted); 811 free_certs(&untrusted); 812 return (0); 813 } 814 815 /* 816 * Load trusted and untrusted certificates from all sources, then 817 * regenerate both the hashed directories and the bundle. 818 * 819 * Returns 0 on success and -1 on failure. 820 */ 821 static int 822 certctl_rehash(int argc, char **argv __unused) 823 { 824 int ret; 825 826 if (argc > 1) 827 usage(); 828 829 if (unprivileged && (mlf = fopen(metalog, "a")) == NULL) { 830 warn("%s", metalog); 831 return (-1); 832 } 833 834 /* load untrusted certs first */ 835 load_untrusted(); 836 837 /* load trusted certs, excluding any that are already untrusted */ 838 load_trusted(); 839 840 /* save everything */ 841 ret = save_all(); 842 843 /* clean up */ 844 free_certs(&untrusted); 845 free_certs(&trusted); 846 if (mlf != NULL) 847 fclose(mlf); 848 return (ret); 849 } 850 851 /* 852 * Manually add one or more certificates to the list of trusted 853 * certificates. 854 * 855 * Returns 0 on success and -1 on failure. 856 */ 857 static int 858 certctl_trust(int argc, char **argv) 859 { 860 struct cert_tree extra = RB_INITIALIZER(&extra); 861 struct cert *cert, *other, *tmp; 862 unsigned int n; 863 int i, ret; 864 865 if (argc < 2) 866 usage(); 867 868 /* load untrusted certs first */ 869 load_untrusted(); 870 871 /* load trusted certs, excluding any that are already untrusted */ 872 load_trusted(); 873 874 /* now load the additional trusted certificates */ 875 n = 0; 876 for (i = 1; i < argc; i++) { 877 ret = read_cert(argv[i], &extra, &trusted); 878 if (ret > 0) 879 n += ret; 880 } 881 if (n == 0) { 882 warnx("no new trusted certificates found"); 883 free_certs(&untrusted); 884 free_certs(&trusted); 885 return (0); 886 } 887 warnx("%u new trusted certificate%s found", n, n > 1 ? "s" : ""); 888 889 /* 890 * For each new trusted cert, move it from the extra list to the 891 * trusted list, then check if a matching certificate exists on 892 * the untrusted list. If that is the case, warn the user, then 893 * remove the matching certificate from the untrusted list. 894 */ 895 RB_FOREACH_SAFE(cert, cert_tree, &extra, tmp) { 896 RB_REMOVE(cert_tree, &extra, cert); 897 RB_INSERT(cert_tree, &trusted, cert); 898 if ((other = RB_FIND(cert_tree, &untrusted, cert)) != NULL) { 899 warnx("%s was previously untrusted", cert->name); 900 warnx("source of untrust: %s", other->path); 901 RB_REMOVE(cert_tree, &untrusted, other); 902 free_cert(other); 903 } 904 } 905 warnx("This operation is not persistent. To persistently add"); 906 warnx("trusted certificates to the system store, copy them to"); 907 warnx("one of these directories, then run `certctl rehash`:"); 908 for (i = 0; trusted_paths[i] != NULL; i++) 909 warnx(" %s", trusted_paths[i]); 910 911 /* save everything */ 912 ret = save_all(); 913 914 /* clean up */ 915 free_certs(&untrusted); 916 free_certs(&trusted); 917 return (ret); 918 } 919 920 /* 921 * Manually add one or more certificates to the list of untrusted 922 * certificates. 923 * 924 * Returns 0 on success and -1 on failure. 925 */ 926 static int 927 certctl_untrust(int argc, char **argv) 928 { 929 struct cert_tree extra = RB_INITIALIZER(&extra); 930 struct cert *cert, *other, *tmp; 931 unsigned int n; 932 int i, ret; 933 934 if (argc < 2) 935 usage(); 936 937 /* load untrusted certs first */ 938 load_untrusted(); 939 940 /* load trusted certs, excluding any that are already untrusted */ 941 load_trusted(); 942 943 /* now load the additional untrusted certificates */ 944 n = 0; 945 for (i = 1; i < argc; i++) { 946 ret = read_cert(argv[i], &extra, NULL); 947 if (ret > 0) 948 n += ret; 949 } 950 if (n == 0) { 951 warnx("no new untrusted certificates found"); 952 free_certs(&untrusted); 953 free_certs(&trusted); 954 return (0); 955 } 956 warnx("%u new untrusted certificate%s found", n, n > 1 ? "s" : ""); 957 958 /* 959 * For each new untrusted cert, move it from the extra list to the 960 * untrusted list, then check if a matching certificate exists on 961 * the trusted list. If that is the case, warn the user, then 962 * remove the matching certificate from the trusted list. 963 */ 964 RB_FOREACH_SAFE(cert, cert_tree, &extra, tmp) { 965 RB_REMOVE(cert_tree, &extra, cert); 966 RB_INSERT(cert_tree, &untrusted, cert); 967 if ((other = RB_FIND(cert_tree, &trusted, cert)) != NULL) { 968 warnx("%s was previously trusted", cert->name); 969 warnx("source of trust: %s", other->path); 970 RB_REMOVE(cert_tree, &trusted, other); 971 free_cert(other); 972 } 973 } 974 warnx("This operation is not persistent. To persistently add"); 975 warnx("untrusted certificates to the system store, copy them to"); 976 warnx("one of these directories, then run `certctl rehash`:"); 977 for (i = 0; untrusted_paths[i] != NULL; i++) 978 warnx(" %s", untrusted_paths[i]); 979 980 /* save everything */ 981 ret = save_all(); 982 983 /* clean up */ 984 free_certs(&untrusted); 985 free_certs(&trusted); 986 return (ret); 987 } 988 989 static void 990 set_defaults(void) 991 { 992 const char *value; 993 char *str; 994 size_t len; 995 996 if (localbase == NULL && 997 (localbase = getenv("LOCALBASE")) == NULL) { 998 if ((str = malloc((len = PATH_MAX) + 1)) == NULL) 999 err(1, NULL); 1000 while (sysctlbyname("user.localbase", str, &len, NULL, 0) < 0) { 1001 if (errno != ENOMEM) 1002 err(1, "sysctl(user.localbase)"); 1003 if ((str = realloc(str, len + 1)) == NULL) 1004 err(1, NULL); 1005 } 1006 str[len] = '\0'; 1007 localbase = str; 1008 } 1009 1010 if (destdir == NULL && 1011 (destdir = getenv("DESTDIR")) == NULL) 1012 destdir = ""; 1013 destdir = normalize_path(destdir); 1014 1015 if (distbase == NULL && 1016 (distbase = getenv("DISTBASE")) == NULL) 1017 distbase = ""; 1018 if (*distbase != '\0' && *distbase != '/') 1019 errx(1, "DISTBASE=%s does not begin with a slash", distbase); 1020 distbase = normalize_path(distbase); 1021 1022 if (unprivileged && metalog == NULL && 1023 (metalog = getenv("METALOG")) == NULL) 1024 metalog = xasprintf("%s/METALOG", destdir); 1025 1026 if (!verbose) { 1027 if ((value = getenv("CERTCTL_VERBOSE")) != NULL) { 1028 if (value[0] != '\0') { 1029 verbose = true; 1030 } 1031 } 1032 } 1033 1034 if ((value = getenv("TRUSTPATH")) != NULL) 1035 trusted_paths = split_paths(value); 1036 else 1037 trusted_paths = expand_paths(default_trusted_paths); 1038 1039 if ((value = getenv("UNTRUSTPATH")) != NULL) 1040 untrusted_paths = split_paths(value); 1041 else 1042 untrusted_paths = expand_paths(default_untrusted_paths); 1043 1044 if ((value = getenv("TRUSTDESTDIR")) != NULL || 1045 (value = getenv("CERTDESTDIR")) != NULL) 1046 trusted_dest = normalize_path(value); 1047 else 1048 trusted_dest = expand_path(TRUSTED_PATH); 1049 1050 if ((value = getenv("UNTRUSTDESTDIR")) != NULL) 1051 untrusted_dest = normalize_path(value); 1052 else 1053 untrusted_dest = expand_path(UNTRUSTED_PATH); 1054 1055 if ((value = getenv("BUNDLE")) != NULL) 1056 bundle_dest = normalize_path(value); 1057 else 1058 bundle_dest = expand_path(BUNDLE_PATH); 1059 1060 info("localbase:\t%s", localbase); 1061 info("destdir:\t%s", destdir); 1062 info("distbase:\t%s", distbase); 1063 info("unprivileged:\t%s", unprivileged ? "true" : "false"); 1064 info("verbose:\t%s", verbose ? "true" : "false"); 1065 } 1066 1067 typedef int (*main_t)(int, char **); 1068 1069 static struct { 1070 const char *name; 1071 main_t func; 1072 } commands[] = { 1073 { "list", certctl_list }, 1074 { "untrusted", certctl_untrusted }, 1075 { "rehash", certctl_rehash }, 1076 { "untrust", certctl_untrust }, 1077 { "trust", certctl_trust }, 1078 { 0 }, 1079 }; 1080 1081 static void 1082 usage(void) 1083 { 1084 fprintf(stderr, "usage: certctl [-lv] [-D destdir] [-d distbase] list\n" 1085 " certctl [-lv] [-D destdir] [-d distbase] untrusted\n" 1086 " certctl [-BnUv] [-D destdir] [-d distbase] [-M metalog] rehash\n" 1087 " certctl [-nv] [-D destdir] [-d distbase] untrust <file>\n" 1088 " certctl [-nv] [-D destdir] [-d distbase] trust <file>\n"); 1089 exit(1); 1090 } 1091 1092 int 1093 main(int argc, char *argv[]) 1094 { 1095 const char *command; 1096 int opt; 1097 1098 while ((opt = getopt(argc, argv, "BcD:d:g:lL:M:no:Uv")) != -1) 1099 switch (opt) { 1100 case 'B': 1101 nobundle = true; 1102 break; 1103 case 'c': 1104 /* ignored for compatibility */ 1105 break; 1106 case 'D': 1107 destdir = optarg; 1108 break; 1109 case 'd': 1110 distbase = optarg; 1111 break; 1112 case 'g': 1113 gname = optarg; 1114 break; 1115 case 'l': 1116 longnames = true; 1117 break; 1118 case 'L': 1119 localbase = optarg; 1120 break; 1121 case 'M': 1122 metalog = optarg; 1123 break; 1124 case 'n': 1125 dryrun = true; 1126 break; 1127 case 'o': 1128 uname = optarg; 1129 break; 1130 case 'U': 1131 unprivileged = true; 1132 break; 1133 case 'v': 1134 verbose = true; 1135 break; 1136 default: 1137 usage(); 1138 } 1139 1140 argc -= optind; 1141 argv += optind; 1142 1143 if (argc < 1) 1144 usage(); 1145 1146 command = *argv; 1147 1148 if ((nobundle || unprivileged || metalog != NULL) && 1149 strcmp(command, "rehash") != 0) 1150 usage(); 1151 if (!unprivileged && metalog != NULL) { 1152 warnx("-M may only be used in conjunction with -U"); 1153 usage(); 1154 } 1155 1156 set_defaults(); 1157 1158 for (unsigned i = 0; commands[i].name != NULL; i++) 1159 if (strcmp(command, commands[i].name) == 0) 1160 exit(!!commands[i].func(argc, argv)); 1161 usage(); 1162 } 1163