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 if (!unprivileged && fd >= 0) 527 (void)fchmod(fd, mode); 528 } 529 } 530 /* write the certificate */ 531 if (fd < 0 || 532 (f = fdopen(fd, "w")) == NULL || 533 !PEM_write_X509(f, file->cert->x509)) { 534 if (tmppath != NULL && fd >= 0) { 535 int serrno = errno; 536 (void)unlinkat(d, tmppath, 0); 537 errno = serrno; 538 } 539 err(1, "%s/%s", dir, tmppath ? tmppath : path); 540 } 541 /* rename temp file if applicable */ 542 if (tmppath != NULL) { 543 if (ret == 0 && renameat(d, tmppath, d, path) != 0) { 544 warn("%s/%s", dir, path); 545 ret = -1; 546 } 547 if (ret != 0) 548 (void)unlinkat(d, tmppath, 0); 549 free(tmppath); 550 tmppath = NULL; 551 } 552 fflush(f); 553 /* emit metalog */ 554 if (mlf != NULL) { 555 fprintf(mlf, ".%s/%s type=file " 556 "uname=%s gname=%s mode=%#o size=%ld\n", 557 unexpand_path(dir), path, 558 uname, gname, mode, ftell(f)); 559 } 560 fclose(f); 561 /* advance certificate listing */ 562 tmp = RB_NEXT(file_tree, &files, file); 563 RB_REMOVE(file_tree, &files, file); 564 free(file); 565 file = tmp; 566 } 567 free(dents); 568 close(d); 569 return (ret); 570 } 571 572 /* 573 * Save all certs in a tree to a single file (bundle). 574 * 575 * Returns 0 on success and -1 on failure. 576 */ 577 static int 578 write_bundle(const char *dir, const char *file, struct cert_tree *tree) 579 { 580 struct cert *cert; 581 char *tmpfile = NULL; 582 FILE *f; 583 int d, fd, ret = 0; 584 mode_t mode = 0444; 585 586 if (dir != NULL) { 587 if ((d = open(dir, O_DIRECTORY | O_RDONLY)) < 0) 588 err(1, "%s", dir); 589 } else { 590 dir = "."; 591 d = AT_FDCWD; 592 } 593 info("writing %s/%s", dir, file); 594 if (dryrun) { 595 fd = open(_PATH_DEVNULL, O_WRONLY); 596 } else { 597 tmpfile = xasprintf(".%s", file); 598 fd = openat(d, tmpfile, O_WRONLY | O_CREAT | O_EXCL, mode); 599 if (!unprivileged && fd >= 0) 600 (void)fchmod(fd, mode); 601 } 602 if (fd < 0 || (f = fdopen(fd, "w")) == NULL) { 603 if (tmpfile != NULL && fd >= 0) { 604 int serrno = errno; 605 (void)unlinkat(d, tmpfile, 0); 606 errno = serrno; 607 } 608 err(1, "%s/%s", dir, tmpfile ? tmpfile : file); 609 } 610 RB_FOREACH(cert, cert_tree, tree) { 611 if (!PEM_write_X509(f, cert->x509)) { 612 warn("%s/%s", dir, tmpfile ? tmpfile : file); 613 ret = -1; 614 break; 615 } 616 } 617 if (tmpfile != NULL) { 618 if (ret == 0 && renameat(d, tmpfile, d, file) != 0) { 619 warn("%s/%s", dir, file); 620 ret = -1; 621 } 622 if (ret != 0) 623 (void)unlinkat(d, tmpfile, 0); 624 free(tmpfile); 625 } 626 if (ret == 0 && mlf != NULL) { 627 fprintf(mlf, 628 ".%s/%s type=file uname=%s gname=%s mode=%#o size=%ld\n", 629 unexpand_path(dir), file, uname, gname, mode, ftell(f)); 630 } 631 fclose(f); 632 if (d != AT_FDCWD) 633 close(d); 634 return (ret); 635 } 636 637 /* 638 * Load trusted certificates. 639 * 640 * Returns the number of certificates loaded. 641 */ 642 static unsigned int 643 load_trusted(void) 644 { 645 unsigned int i, n; 646 int ret; 647 648 /* load external trusted certs */ 649 for (i = n = 0; trusted_paths[i] != NULL; i++) { 650 ret = read_certs(trusted_paths[i], &trusted, &untrusted); 651 if (ret > 0) 652 n += ret; 653 } 654 655 info("%d trusted certificates found", n); 656 return (n); 657 } 658 659 /* 660 * Load untrusted certificates. 661 * 662 * Returns the number of certificates loaded. 663 */ 664 static unsigned int 665 load_untrusted(void) 666 { 667 char *path; 668 unsigned int i, n; 669 int ret; 670 671 /* load external untrusted certs */ 672 for (i = n = 0; untrusted_paths[i] != NULL; i++) { 673 ret = read_certs(untrusted_paths[i], &untrusted, NULL); 674 if (ret > 0) 675 n += ret; 676 } 677 678 /* load legacy untrusted certs */ 679 path = expand_path(LEGACY_PATH); 680 ret = read_certs(path, &untrusted, NULL); 681 if (ret > 0) { 682 warnx("certificates found in legacy directory %s", 683 path); 684 n += ret; 685 } else if (ret == 0) { 686 warnx("legacy directory %s can safely be deleted", 687 path); 688 } 689 free(path); 690 691 info("%d untrusted certificates found", n); 692 return (n); 693 } 694 695 /* 696 * Save trusted certificates. 697 * 698 * Returns 0 on success and -1 on failure. 699 */ 700 static int 701 save_trusted(void) 702 { 703 int ret; 704 705 mkdirp(trusted_dest); 706 ret = write_certs(trusted_dest, &trusted); 707 return (ret); 708 } 709 710 /* 711 * Save untrusted certificates. 712 * 713 * Returns 0 on success and -1 on failure. 714 */ 715 static int 716 save_untrusted(void) 717 { 718 int ret; 719 720 mkdirp(untrusted_dest); 721 ret = write_certs(untrusted_dest, &untrusted); 722 return (ret); 723 } 724 725 /* 726 * Save certificate bundle. 727 * 728 * Returns 0 on success and -1 on failure. 729 */ 730 static int 731 save_bundle(void) 732 { 733 char *dir, *file, *sep; 734 int ret; 735 736 if ((sep = strrchr(bundle_dest, '/')) == NULL) { 737 dir = NULL; 738 file = bundle_dest; 739 } else { 740 dir = xasprintf("%.*s", (int)(sep - bundle_dest), bundle_dest); 741 file = sep + 1; 742 mkdirp(dir); 743 } 744 ret = write_bundle(dir, file, &trusted); 745 free(dir); 746 return (ret); 747 } 748 749 /* 750 * Save everything. 751 * 752 * Returns 0 on success and -1 on failure. 753 */ 754 static int 755 save_all(void) 756 { 757 int ret = 0; 758 759 ret |= save_untrusted(); 760 ret |= save_trusted(); 761 if (!nobundle) 762 ret |= save_bundle(); 763 return (ret); 764 } 765 766 /* 767 * List the contents of a certificate tree. 768 */ 769 static void 770 list_certs(struct cert_tree *tree) 771 { 772 struct cert *cert; 773 char *path, *name; 774 775 RB_FOREACH(cert, cert_tree, tree) { 776 path = longnames ? NULL : strrchr(cert->path, '/'); 777 name = longnames ? NULL : strrchr(cert->name, '='); 778 printf("%s\t%s\n", path ? path + 1 : cert->path, 779 name ? name + 1 : cert->name); 780 } 781 } 782 783 /* 784 * Load installed trusted certificates, then list them. 785 * 786 * Returns 0 on success and -1 on failure. 787 */ 788 static int 789 certctl_list(int argc, char **argv __unused) 790 { 791 if (argc > 1) 792 usage(); 793 /* load installed trusted certificates */ 794 read_certs(trusted_dest, &trusted, NULL); 795 /* list them */ 796 list_certs(&trusted); 797 free_certs(&trusted); 798 return (0); 799 } 800 801 /* 802 * Load installed untrusted certificates, then list them. 803 * 804 * Returns 0 on success and -1 on failure. 805 */ 806 static int 807 certctl_untrusted(int argc, char **argv __unused) 808 { 809 if (argc > 1) 810 usage(); 811 /* load installed untrusted certificates */ 812 read_certs(untrusted_dest, &untrusted, NULL); 813 /* list them */ 814 list_certs(&untrusted); 815 free_certs(&untrusted); 816 return (0); 817 } 818 819 /* 820 * Load trusted and untrusted certificates from all sources, then 821 * regenerate both the hashed directories and the bundle. 822 * 823 * Returns 0 on success and -1 on failure. 824 */ 825 static int 826 certctl_rehash(int argc, char **argv __unused) 827 { 828 int ret; 829 830 if (argc > 1) 831 usage(); 832 833 if (unprivileged && (mlf = fopen(metalog, "a")) == NULL) { 834 warn("%s", metalog); 835 return (-1); 836 } 837 838 /* load untrusted certs first */ 839 load_untrusted(); 840 841 /* load trusted certs, excluding any that are already untrusted */ 842 load_trusted(); 843 844 /* save everything */ 845 ret = save_all(); 846 847 /* clean up */ 848 free_certs(&untrusted); 849 free_certs(&trusted); 850 if (mlf != NULL) 851 fclose(mlf); 852 return (ret); 853 } 854 855 /* 856 * Manually add one or more certificates to the list of trusted 857 * certificates. 858 * 859 * Returns 0 on success and -1 on failure. 860 */ 861 static int 862 certctl_trust(int argc, char **argv) 863 { 864 struct cert_tree extra = RB_INITIALIZER(&extra); 865 struct cert *cert, *other, *tmp; 866 unsigned int n; 867 int i, ret; 868 869 if (argc < 2) 870 usage(); 871 872 /* load untrusted certs first */ 873 load_untrusted(); 874 875 /* load trusted certs, excluding any that are already untrusted */ 876 load_trusted(); 877 878 /* now load the additional trusted certificates */ 879 n = 0; 880 for (i = 1; i < argc; i++) { 881 ret = read_cert(argv[i], &extra, &trusted); 882 if (ret > 0) 883 n += ret; 884 } 885 if (n == 0) { 886 warnx("no new trusted certificates found"); 887 free_certs(&untrusted); 888 free_certs(&trusted); 889 return (0); 890 } 891 warnx("%u new trusted certificate%s found", n, n > 1 ? "s" : ""); 892 893 /* 894 * For each new trusted cert, move it from the extra list to the 895 * trusted list, then check if a matching certificate exists on 896 * the untrusted list. If that is the case, warn the user, then 897 * remove the matching certificate from the untrusted list. 898 */ 899 RB_FOREACH_SAFE(cert, cert_tree, &extra, tmp) { 900 RB_REMOVE(cert_tree, &extra, cert); 901 RB_INSERT(cert_tree, &trusted, cert); 902 if ((other = RB_FIND(cert_tree, &untrusted, cert)) != NULL) { 903 warnx("%s was previously untrusted", cert->name); 904 warnx("source of untrust: %s", other->path); 905 RB_REMOVE(cert_tree, &untrusted, other); 906 free_cert(other); 907 } 908 } 909 warnx("This operation is not persistent. To persistently add"); 910 warnx("trusted certificates to the system store, copy them to"); 911 warnx("one of these directories, then run `certctl rehash`:"); 912 for (i = 0; trusted_paths[i] != NULL; i++) 913 warnx(" %s", trusted_paths[i]); 914 915 /* save everything */ 916 ret = save_all(); 917 918 /* clean up */ 919 free_certs(&untrusted); 920 free_certs(&trusted); 921 return (ret); 922 } 923 924 /* 925 * Manually add one or more certificates to the list of untrusted 926 * certificates. 927 * 928 * Returns 0 on success and -1 on failure. 929 */ 930 static int 931 certctl_untrust(int argc, char **argv) 932 { 933 struct cert_tree extra = RB_INITIALIZER(&extra); 934 struct cert *cert, *other, *tmp; 935 unsigned int n; 936 int i, ret; 937 938 if (argc < 2) 939 usage(); 940 941 /* load untrusted certs first */ 942 load_untrusted(); 943 944 /* load trusted certs, excluding any that are already untrusted */ 945 load_trusted(); 946 947 /* now load the additional untrusted certificates */ 948 n = 0; 949 for (i = 1; i < argc; i++) { 950 ret = read_cert(argv[i], &extra, NULL); 951 if (ret > 0) 952 n += ret; 953 } 954 if (n == 0) { 955 warnx("no new untrusted certificates found"); 956 free_certs(&untrusted); 957 free_certs(&trusted); 958 return (0); 959 } 960 warnx("%u new untrusted certificate%s found", n, n > 1 ? "s" : ""); 961 962 /* 963 * For each new untrusted cert, move it from the extra list to the 964 * untrusted list, then check if a matching certificate exists on 965 * the trusted list. If that is the case, warn the user, then 966 * remove the matching certificate from the trusted list. 967 */ 968 RB_FOREACH_SAFE(cert, cert_tree, &extra, tmp) { 969 RB_REMOVE(cert_tree, &extra, cert); 970 RB_INSERT(cert_tree, &untrusted, cert); 971 if ((other = RB_FIND(cert_tree, &trusted, cert)) != NULL) { 972 warnx("%s was previously trusted", cert->name); 973 warnx("source of trust: %s", other->path); 974 RB_REMOVE(cert_tree, &trusted, other); 975 free_cert(other); 976 } 977 } 978 warnx("This operation is not persistent. To persistently add"); 979 warnx("untrusted certificates to the system store, copy them to"); 980 warnx("one of these directories, then run `certctl rehash`:"); 981 for (i = 0; untrusted_paths[i] != NULL; i++) 982 warnx(" %s", untrusted_paths[i]); 983 984 /* save everything */ 985 ret = save_all(); 986 987 /* clean up */ 988 free_certs(&untrusted); 989 free_certs(&trusted); 990 return (ret); 991 } 992 993 static void 994 set_defaults(void) 995 { 996 const char *value; 997 char *str; 998 size_t len; 999 1000 if (localbase == NULL && 1001 (localbase = getenv("LOCALBASE")) == NULL) { 1002 if ((str = malloc((len = PATH_MAX) + 1)) == NULL) 1003 err(1, NULL); 1004 while (sysctlbyname("user.localbase", str, &len, NULL, 0) < 0) { 1005 if (errno != ENOMEM) 1006 err(1, "sysctl(user.localbase)"); 1007 if ((str = realloc(str, len + 1)) == NULL) 1008 err(1, NULL); 1009 } 1010 str[len] = '\0'; 1011 localbase = str; 1012 } 1013 1014 if (destdir == NULL && 1015 (destdir = getenv("DESTDIR")) == NULL) 1016 destdir = ""; 1017 destdir = normalize_path(destdir); 1018 1019 if (distbase == NULL && 1020 (distbase = getenv("DISTBASE")) == NULL) 1021 distbase = ""; 1022 if (*distbase != '\0' && *distbase != '/') 1023 errx(1, "DISTBASE=%s does not begin with a slash", distbase); 1024 distbase = normalize_path(distbase); 1025 1026 if (unprivileged && metalog == NULL && 1027 (metalog = getenv("METALOG")) == NULL) 1028 metalog = xasprintf("%s/METALOG", destdir); 1029 1030 if (!verbose) { 1031 if ((value = getenv("CERTCTL_VERBOSE")) != NULL) { 1032 if (value[0] != '\0') { 1033 verbose = true; 1034 } 1035 } 1036 } 1037 1038 if ((value = getenv("TRUSTPATH")) != NULL) 1039 trusted_paths = split_paths(value); 1040 else 1041 trusted_paths = expand_paths(default_trusted_paths); 1042 1043 if ((value = getenv("UNTRUSTPATH")) != NULL) 1044 untrusted_paths = split_paths(value); 1045 else 1046 untrusted_paths = expand_paths(default_untrusted_paths); 1047 1048 if ((value = getenv("TRUSTDESTDIR")) != NULL || 1049 (value = getenv("CERTDESTDIR")) != NULL) 1050 trusted_dest = normalize_path(value); 1051 else 1052 trusted_dest = expand_path(TRUSTED_PATH); 1053 1054 if ((value = getenv("UNTRUSTDESTDIR")) != NULL) 1055 untrusted_dest = normalize_path(value); 1056 else 1057 untrusted_dest = expand_path(UNTRUSTED_PATH); 1058 1059 if ((value = getenv("BUNDLE")) != NULL) 1060 bundle_dest = normalize_path(value); 1061 else 1062 bundle_dest = expand_path(BUNDLE_PATH); 1063 1064 info("localbase:\t%s", localbase); 1065 info("destdir:\t%s", destdir); 1066 info("distbase:\t%s", distbase); 1067 info("unprivileged:\t%s", unprivileged ? "true" : "false"); 1068 info("verbose:\t%s", verbose ? "true" : "false"); 1069 } 1070 1071 typedef int (*main_t)(int, char **); 1072 1073 static struct { 1074 const char *name; 1075 main_t func; 1076 } commands[] = { 1077 { "list", certctl_list }, 1078 { "untrusted", certctl_untrusted }, 1079 { "rehash", certctl_rehash }, 1080 { "untrust", certctl_untrust }, 1081 { "trust", certctl_trust }, 1082 { 0 }, 1083 }; 1084 1085 static void 1086 usage(void) 1087 { 1088 fprintf(stderr, "usage: certctl [-lv] [-D destdir] [-d distbase] list\n" 1089 " certctl [-lv] [-D destdir] [-d distbase] untrusted\n" 1090 " certctl [-BnUv] [-D destdir] [-d distbase] [-M metalog] rehash\n" 1091 " certctl [-nv] [-D destdir] [-d distbase] untrust <file>\n" 1092 " certctl [-nv] [-D destdir] [-d distbase] trust <file>\n"); 1093 exit(1); 1094 } 1095 1096 int 1097 main(int argc, char *argv[]) 1098 { 1099 const char *command; 1100 unsigned int i; 1101 int opt; 1102 1103 while ((opt = getopt(argc, argv, "BcD:d:g:lL:M:no:Uv")) != -1) 1104 switch (opt) { 1105 case 'B': 1106 nobundle = true; 1107 break; 1108 case 'c': 1109 /* ignored for compatibility */ 1110 break; 1111 case 'D': 1112 destdir = optarg; 1113 break; 1114 case 'd': 1115 distbase = optarg; 1116 break; 1117 case 'g': 1118 gname = optarg; 1119 break; 1120 case 'l': 1121 longnames = true; 1122 break; 1123 case 'L': 1124 localbase = optarg; 1125 break; 1126 case 'M': 1127 metalog = optarg; 1128 break; 1129 case 'n': 1130 dryrun = true; 1131 break; 1132 case 'o': 1133 uname = optarg; 1134 break; 1135 case 'U': 1136 unprivileged = true; 1137 break; 1138 case 'v': 1139 verbose = true; 1140 break; 1141 default: 1142 usage(); 1143 } 1144 1145 argc -= optind; 1146 argv += optind; 1147 1148 if (argc < 1) 1149 usage(); 1150 1151 command = *argv; 1152 1153 if ((nobundle || unprivileged || metalog != NULL) && 1154 strcmp(command, "rehash") != 0) 1155 usage(); 1156 if (!unprivileged && metalog != NULL) { 1157 warnx("-M may only be used in conjunction with -U"); 1158 usage(); 1159 } 1160 1161 set_defaults(); 1162 1163 for (i = 0; commands[i].name != NULL; i++) 1164 if (strcmp(command, commands[i].name) == 0) 1165 exit(commands[i].func(argc, argv) == 0 ? 0 : 1); 1166 usage(); 1167 } 1168