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 *
xasprintf(const char * fmt,...)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 *
xstrdup(const char * str)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
mkdirp(const char * dir)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 *
normalize_path(const char * str)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 **
split_paths(const char * str)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 *
expand_path(const char * template)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 **
expand_paths(const char * const * templates)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 *
unexpand_path(const char * path)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
free_cert(struct cert * cert)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
certcmp(const struct cert * a,const struct cert * b)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
free_certs(struct cert_tree * tree)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 *
find_cert(struct cert_tree * haystack,X509 * x509)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
filecmp(const struct file * a,const struct file * b)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
lexisort(const struct dirent ** d1,const struct dirent ** d2)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
read_cert(const char * path,struct cert_tree * tree,struct cert_tree * exclude)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
read_certs(const char * path,struct cert_tree * tree,struct cert_tree * exclude)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
write_certs(const char * dir,struct cert_tree * tree)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
write_bundle(const char * dir,const char * file,struct cert_tree * tree)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
load_trusted(void)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
load_untrusted(void)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
save_trusted(void)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
save_untrusted(void)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
save_bundle(void)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
save_all(void)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
list_certs(struct cert_tree * tree)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
certctl_list(int argc,char ** argv __unused)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
certctl_untrusted(int argc,char ** argv __unused)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
certctl_rehash(int argc,char ** argv __unused)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
certctl_trust(int argc,char ** argv)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
certctl_untrust(int argc,char ** argv)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
set_defaults(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
usage(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
main(int argc,char * argv[])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