Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/usr.sbin/certctl/certctl.c
101816 views
1
/*-
2
* Copyright (c) 2023-2025 Dag-Erling Smørgrav <[email protected]>
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(bool all, struct cert_tree *exclude)
640
{
641
unsigned int i, n;
642
int ret;
643
644
/* load external trusted certs */
645
for (i = n = 0; all && trusted_paths[i] != NULL; i++) {
646
ret = read_certs(trusted_paths[i], &trusted, exclude);
647
if (ret > 0)
648
n += ret;
649
}
650
651
/* load installed trusted certs */
652
ret = read_certs(trusted_dest, &trusted, exclude);
653
if (ret > 0)
654
n += ret;
655
656
info("%d trusted certificates found", n);
657
return (n);
658
}
659
660
/*
661
* Load untrusted certificates.
662
*
663
* Returns the number of certificates loaded.
664
*/
665
static unsigned int
666
load_untrusted(bool all)
667
{
668
char *path;
669
unsigned int i, n;
670
int ret;
671
672
/* load external untrusted certs */
673
for (i = n = 0; all && untrusted_paths[i] != NULL; i++) {
674
ret = read_certs(untrusted_paths[i], &untrusted, NULL);
675
if (ret > 0)
676
n += ret;
677
}
678
679
/* load installed untrusted certs */
680
ret = read_certs(untrusted_dest, &untrusted, NULL);
681
if (ret > 0)
682
n += ret;
683
684
/* load legacy untrusted certs */
685
path = expand_path(LEGACY_PATH);
686
ret = read_certs(path, &untrusted, NULL);
687
if (ret > 0) {
688
warnx("certificates found in legacy directory %s",
689
path);
690
n += ret;
691
} else if (ret == 0) {
692
warnx("legacy directory %s can safely be deleted",
693
path);
694
}
695
free(path);
696
697
info("%d untrusted certificates found", n);
698
return (n);
699
}
700
701
/*
702
* Save trusted certificates.
703
*
704
* Returns 0 on success and -1 on failure.
705
*/
706
static int
707
save_trusted(void)
708
{
709
int ret;
710
711
mkdirp(trusted_dest);
712
ret = write_certs(trusted_dest, &trusted);
713
return (ret);
714
}
715
716
/*
717
* Save untrusted certificates.
718
*
719
* Returns 0 on success and -1 on failure.
720
*/
721
static int
722
save_untrusted(void)
723
{
724
int ret;
725
726
mkdirp(untrusted_dest);
727
ret = write_certs(untrusted_dest, &untrusted);
728
return (ret);
729
}
730
731
/*
732
* Save certificate bundle.
733
*
734
* Returns 0 on success and -1 on failure.
735
*/
736
static int
737
save_bundle(void)
738
{
739
char *dir, *file, *sep;
740
int ret;
741
742
if ((sep = strrchr(bundle_dest, '/')) == NULL) {
743
dir = NULL;
744
file = bundle_dest;
745
} else {
746
dir = xasprintf("%.*s", (int)(sep - bundle_dest), bundle_dest);
747
file = sep + 1;
748
mkdirp(dir);
749
}
750
ret = write_bundle(dir, file, &trusted);
751
free(dir);
752
return (ret);
753
}
754
755
/*
756
* Save everything.
757
*
758
* Returns 0 on success and -1 on failure.
759
*/
760
static int
761
save_all(void)
762
{
763
int ret = 0;
764
765
ret |= save_untrusted();
766
ret |= save_trusted();
767
if (!nobundle)
768
ret |= save_bundle();
769
return (ret);
770
}
771
772
/*
773
* List the contents of a certificate tree.
774
*/
775
static void
776
list_certs(struct cert_tree *tree)
777
{
778
struct cert *cert;
779
char *path, *name;
780
781
RB_FOREACH(cert, cert_tree, tree) {
782
path = longnames ? NULL : strrchr(cert->path, '/');
783
name = longnames ? NULL : strrchr(cert->name, '=');
784
printf("%s\t%s\n", path ? path + 1 : cert->path,
785
name ? name + 1 : cert->name);
786
}
787
}
788
789
/*
790
* Load installed trusted certificates, then list them.
791
*
792
* Returns 0 on success and -1 on failure.
793
*/
794
static int
795
certctl_list(int argc, char **argv __unused)
796
{
797
if (argc > 1)
798
usage();
799
/* load trusted certificates */
800
load_trusted(false, NULL);
801
/* list them */
802
list_certs(&trusted);
803
free_certs(&trusted);
804
return (0);
805
}
806
807
/*
808
* Load installed untrusted certificates, then list them.
809
*
810
* Returns 0 on success and -1 on failure.
811
*/
812
static int
813
certctl_untrusted(int argc, char **argv __unused)
814
{
815
if (argc > 1)
816
usage();
817
/* load untrusted certificates */
818
load_untrusted(false);
819
/* list them */
820
list_certs(&untrusted);
821
free_certs(&untrusted);
822
return (0);
823
}
824
825
/*
826
* Load trusted and untrusted certificates from all sources, then
827
* regenerate both the hashed directories and the bundle.
828
*
829
* Returns 0 on success and -1 on failure.
830
*/
831
static int
832
certctl_rehash(int argc, char **argv __unused)
833
{
834
int ret;
835
836
if (argc > 1)
837
usage();
838
839
if (unprivileged && (mlf = fopen(metalog, "a")) == NULL) {
840
warn("%s", metalog);
841
return (-1);
842
}
843
844
/* load untrusted certs first */
845
load_untrusted(true);
846
847
/* load trusted certs, excluding any that are already untrusted */
848
load_trusted(true, &untrusted);
849
850
/* save everything */
851
ret = save_all();
852
853
/* clean up */
854
free_certs(&untrusted);
855
free_certs(&trusted);
856
if (mlf != NULL)
857
fclose(mlf);
858
return (ret);
859
}
860
861
/*
862
* Manually add one or more certificates to the list of trusted certificates.
863
*
864
* Returns 0 on success and -1 on failure.
865
*/
866
static int
867
certctl_trust(int argc, char **argv)
868
{
869
struct cert_tree extra = RB_INITIALIZER(&extra);
870
struct cert *cert, *other, *tmp;
871
unsigned int n;
872
int i, ret;
873
874
if (argc < 2)
875
usage();
876
877
/* load untrusted certs first */
878
load_untrusted(true);
879
880
/* load trusted certs, excluding any that are already untrusted */
881
load_trusted(true, &untrusted);
882
883
/* now load the additional trusted certificates */
884
n = 0;
885
for (i = 1; i < argc; i++) {
886
ret = read_cert(argv[i], &extra, &trusted);
887
if (ret > 0)
888
n += ret;
889
}
890
if (n == 0) {
891
warnx("no new trusted certificates found");
892
free_certs(&untrusted);
893
free_certs(&trusted);
894
free_certs(&extra);
895
return (0);
896
}
897
898
/*
899
* For each new trusted cert, move it from the extra list to the
900
* trusted list, then check if a matching certificate exists on
901
* the untrusted list. If that is the case, warn the user, then
902
* remove the matching certificate from the untrusted list.
903
*/
904
RB_FOREACH_SAFE(cert, cert_tree, &extra, tmp) {
905
RB_REMOVE(cert_tree, &extra, cert);
906
RB_INSERT(cert_tree, &trusted, cert);
907
if ((other = RB_FIND(cert_tree, &untrusted, cert)) != NULL) {
908
warnx("%s was previously untrusted", cert->name);
909
RB_REMOVE(cert_tree, &untrusted, other);
910
free_cert(other);
911
}
912
}
913
914
/* save everything */
915
ret = save_all();
916
917
/* clean up */
918
free_certs(&untrusted);
919
free_certs(&trusted);
920
return (ret);
921
}
922
923
/*
924
* Manually add one or more certificates to the list of untrusted
925
* certificates.
926
*
927
* Returns 0 on success and -1 on failure.
928
*/
929
static int
930
certctl_untrust(int argc, char **argv)
931
{
932
unsigned int n;
933
int i, ret;
934
935
if (argc < 2)
936
usage();
937
938
/* load untrusted certs first */
939
load_untrusted(true);
940
941
/* now load the additional untrusted certificates */
942
n = 0;
943
for (i = 1; i < argc; i++) {
944
ret = read_cert(argv[i], &untrusted, NULL);
945
if (ret > 0)
946
n += ret;
947
}
948
if (n == 0) {
949
warnx("no new untrusted certificates found");
950
free_certs(&untrusted);
951
return (0);
952
}
953
954
/* load trusted certs, excluding any that are already untrusted */
955
load_trusted(true, &untrusted);
956
957
/* save everything */
958
ret = save_all();
959
960
/* clean up */
961
free_certs(&untrusted);
962
free_certs(&trusted);
963
return (ret);
964
}
965
966
static void
967
set_defaults(void)
968
{
969
const char *value;
970
char *str;
971
size_t len;
972
973
if (localbase == NULL &&
974
(localbase = getenv("LOCALBASE")) == NULL) {
975
if ((str = malloc((len = PATH_MAX) + 1)) == NULL)
976
err(1, NULL);
977
while (sysctlbyname("user.localbase", str, &len, NULL, 0) < 0) {
978
if (errno != ENOMEM)
979
err(1, "sysctl(user.localbase)");
980
if ((str = realloc(str, len + 1)) == NULL)
981
err(1, NULL);
982
}
983
str[len] = '\0';
984
localbase = str;
985
}
986
987
if (destdir == NULL &&
988
(destdir = getenv("DESTDIR")) == NULL)
989
destdir = "";
990
destdir = normalize_path(destdir);
991
992
if (distbase == NULL &&
993
(distbase = getenv("DISTBASE")) == NULL)
994
distbase = "";
995
if (*distbase != '\0' && *distbase != '/')
996
errx(1, "DISTBASE=%s does not begin with a slash", distbase);
997
distbase = normalize_path(distbase);
998
999
if (unprivileged && metalog == NULL &&
1000
(metalog = getenv("METALOG")) == NULL)
1001
metalog = xasprintf("%s/METALOG", destdir);
1002
1003
if (!verbose) {
1004
if ((value = getenv("CERTCTL_VERBOSE")) != NULL) {
1005
if (value[0] != '\0') {
1006
verbose = true;
1007
}
1008
}
1009
}
1010
1011
if ((value = getenv("TRUSTPATH")) != NULL)
1012
trusted_paths = split_paths(value);
1013
else
1014
trusted_paths = expand_paths(default_trusted_paths);
1015
1016
if ((value = getenv("UNTRUSTPATH")) != NULL)
1017
untrusted_paths = split_paths(value);
1018
else
1019
untrusted_paths = expand_paths(default_untrusted_paths);
1020
1021
if ((value = getenv("TRUSTDESTDIR")) != NULL ||
1022
(value = getenv("CERTDESTDIR")) != NULL)
1023
trusted_dest = normalize_path(value);
1024
else
1025
trusted_dest = expand_path(TRUSTED_PATH);
1026
1027
if ((value = getenv("UNTRUSTDESTDIR")) != NULL)
1028
untrusted_dest = normalize_path(value);
1029
else
1030
untrusted_dest = expand_path(UNTRUSTED_PATH);
1031
1032
if ((value = getenv("BUNDLE")) != NULL)
1033
bundle_dest = normalize_path(value);
1034
else
1035
bundle_dest = expand_path(BUNDLE_PATH);
1036
1037
info("localbase:\t%s", localbase);
1038
info("destdir:\t%s", destdir);
1039
info("distbase:\t%s", distbase);
1040
info("unprivileged:\t%s", unprivileged ? "true" : "false");
1041
info("verbose:\t%s", verbose ? "true" : "false");
1042
}
1043
1044
typedef int (*main_t)(int, char **);
1045
1046
static struct {
1047
const char *name;
1048
main_t func;
1049
} commands[] = {
1050
{ "list", certctl_list },
1051
{ "untrusted", certctl_untrusted },
1052
{ "rehash", certctl_rehash },
1053
{ "untrust", certctl_untrust },
1054
{ "trust", certctl_trust },
1055
{ 0 },
1056
};
1057
1058
static void
1059
usage(void)
1060
{
1061
fprintf(stderr, "usage: certctl [-lv] [-D destdir] [-d distbase] list\n"
1062
" certctl [-lv] [-D destdir] [-d distbase] untrusted\n"
1063
" certctl [-BnUv] [-D destdir] [-d distbase] [-M metalog] rehash\n"
1064
" certctl [-nv] [-D destdir] [-d distbase] untrust <file>\n"
1065
" certctl [-nv] [-D destdir] [-d distbase] trust <file>\n");
1066
exit(1);
1067
}
1068
1069
int
1070
main(int argc, char *argv[])
1071
{
1072
const char *command;
1073
int opt;
1074
1075
while ((opt = getopt(argc, argv, "BcD:d:g:lL:M:no:Uv")) != -1)
1076
switch (opt) {
1077
case 'B':
1078
nobundle = true;
1079
break;
1080
case 'c':
1081
/* ignored for compatibility */
1082
break;
1083
case 'D':
1084
destdir = optarg;
1085
break;
1086
case 'd':
1087
distbase = optarg;
1088
break;
1089
case 'g':
1090
gname = optarg;
1091
break;
1092
case 'l':
1093
longnames = true;
1094
break;
1095
case 'L':
1096
localbase = optarg;
1097
break;
1098
case 'M':
1099
metalog = optarg;
1100
break;
1101
case 'n':
1102
dryrun = true;
1103
break;
1104
case 'o':
1105
uname = optarg;
1106
break;
1107
case 'U':
1108
unprivileged = true;
1109
break;
1110
case 'v':
1111
verbose = true;
1112
break;
1113
default:
1114
usage();
1115
}
1116
1117
argc -= optind;
1118
argv += optind;
1119
1120
if (argc < 1)
1121
usage();
1122
1123
command = *argv;
1124
1125
if ((nobundle || unprivileged || metalog != NULL) &&
1126
strcmp(command, "rehash") != 0)
1127
usage();
1128
if (!unprivileged && metalog != NULL) {
1129
warnx("-M may only be used in conjunction with -U");
1130
usage();
1131
}
1132
1133
set_defaults();
1134
1135
for (unsigned i = 0; commands[i].name != NULL; i++)
1136
if (strcmp(command, commands[i].name) == 0)
1137
exit(!!commands[i].func(argc, argv));
1138
usage();
1139
}
1140
1141