From 31ac42b486ebb5609c94cfd8a9bec86e457f9b53 Mon Sep 17 00:00:00 2001 From: Colin Percival Date: Sat, 16 Aug 2025 17:17:03 -0700 Subject: [PATCH] Revert certctl reimplementation and follow-ups The reimplementation of certctl, while much needed, broke the release build and 72 hours later corrections are still under review (D51896). This revert should be reverted once that is ready to land; I just need this out of the tree temporarily because breakage is interfering with release engineering for the upcoming 15.0-RELEASE. Unsquashed reversions: Revert "etc: add missing mtree entry for certctl tests" This reverts commit f751757259158a8d3b81d4fb7576b3ebe226dece. Revert "certctl: Fix bootstrap build" This reverts commit c989e3cc3da1bfd8ac3ec5a05d1e86ab8ff719f7. Revert "certctl: Reimplement in C" This reverts commit 81d8827ad8752e35411204541f1f09df1481e417. With hat: re@ --- Makefile.inc1 | 21 +- etc/mtree/BSD.tests.dist | 2 - usr.sbin/certctl/Makefile | 11 +- usr.sbin/certctl/certctl.8 | 94 +-- usr.sbin/certctl/certctl.c | 1065 ------------------------ usr.sbin/certctl/certctl.sh | 366 ++++++++ usr.sbin/certctl/tests/Makefile | 5 - usr.sbin/certctl/tests/certctl.subr | 44 - usr.sbin/certctl/tests/certctl_test.sh | 221 ----- 9 files changed, 414 insertions(+), 1415 deletions(-) delete mode 100644 usr.sbin/certctl/certctl.c create mode 100755 usr.sbin/certctl/certctl.sh delete mode 100644 usr.sbin/certctl/tests/Makefile delete mode 100644 usr.sbin/certctl/tests/certctl.subr delete mode 100644 usr.sbin/certctl/tests/certctl_test.sh diff --git a/Makefile.inc1 b/Makefile.inc1 index cf32248b6b9..d8853fef321 100644 --- a/Makefile.inc1 +++ b/Makefile.inc1 @@ -1021,7 +1021,8 @@ IMAKE_MTREE= MTREE_CMD="${MTREE_CMD} ${MTREEFLAGS}" .endif .if make(distributeworld) -CERTCTLDESTDIR= ${DESTDIR}/${DISTDIR}/base +CERTCTLDESTDIR= ${DESTDIR}/${DISTDIR} +CERTCTLFLAGS+= -d /base .else CERTCTLDESTDIR= ${DESTDIR} .endif @@ -1541,10 +1542,14 @@ distributeworld installworld stageworld: _installcheck_world .PHONY .endif # make(distributeworld) ${_+_}cd ${.CURDIR}; ${IMAKE} re${.TARGET:S/world$//}; \ ${IMAKEENV} rm -rf ${INSTALLTMP} -.if !make(packageworld) && ${MK_CAROOT} != "no" && ${MK_OPENSSL} != "no" - PATH=${TMPPATH:Q}:${PATH:Q} \ - LOCALBASE=${LOCALBASE:Q} \ - certctl ${CERTCTLFLAGS} rehash +.if !make(packageworld) && ${MK_CAROOT} != "no" + @if which openssl>/dev/null; then \ + PATH=${TMPPATH:Q}:${PATH:Q} \ + LOCALBASE=${LOCALBASE:Q} \ + sh ${SRCTOP}/usr.sbin/certctl/certctl.sh ${CERTCTLFLAGS} rehash; \ + else \ + echo "No openssl on the host, not rehashing certificates target -- /etc/ssl may not be populated."; \ + fi .endif .if make(distributeworld) .for dist in ${EXTRA_DISTRIBUTIONS} @@ -2708,11 +2713,6 @@ _basic_bootstrap_tools+=sbin/md5 _basic_bootstrap_tools+=usr.sbin/tzsetup .endif -# certctl is needed as an install tool -.if ${MK_CAROOT} != "no" && ${MK_OPENSSL} != "no" -_certctl=usr.sbin/certctl -.endif - .if defined(BOOTSTRAP_ALL_TOOLS) _other_bootstrap_tools+=${_basic_bootstrap_tools} .for _subdir _links in ${_basic_bootstrap_tools_multilink} @@ -2776,7 +2776,6 @@ bootstrap-tools: ${_bt}-links .PHONY ${_strfile} \ usr.bin/dtc \ ${_cat} \ - ${_certctl} \ ${_kbdcontrol} \ ${_elftoolchain_libs} \ ${_libkldelf} \ diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist index e6a013f010d..2c25d938603 100644 --- a/etc/mtree/BSD.tests.dist +++ b/etc/mtree/BSD.tests.dist @@ -1255,8 +1255,6 @@ .. .. usr.sbin - certctl - .. chown .. ctladm diff --git a/usr.sbin/certctl/Makefile b/usr.sbin/certctl/Makefile index 6900f0ce3b6..88c024daf7e 100644 --- a/usr.sbin/certctl/Makefile +++ b/usr.sbin/certctl/Makefile @@ -1,14 +1,5 @@ -.include - PACKAGE= certctl -PROG= certctl +SCRIPTS=certctl.sh MAN= certctl.8 -LIBADD= crypto -HAS_TESTS= -SUBDIR.${MK_TESTS}= tests - -.ifdef BOOTSTRAPPING -CFLAGS+=-DBOOTSTRAPPING -.endif .include diff --git a/usr.sbin/certctl/certctl.8 b/usr.sbin/certctl/certctl.8 index 97bdc840c35..7e49bb89e2a 100644 --- a/usr.sbin/certctl/certctl.8 +++ b/usr.sbin/certctl/certctl.8 @@ -24,7 +24,7 @@ .\" IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd August 11, 2025 +.Dd July 17, 2025 .Dt CERTCTL 8 .Os .Sh NAME @@ -32,83 +32,63 @@ .Nd "tool for managing trusted and untrusted TLS certificates" .Sh SYNOPSIS .Nm -.Op Fl lv +.Op Fl v .Ic list .Nm -.Op Fl lv +.Op Fl v .Ic untrusted .Nm -.Op Fl BnUv +.Op Fl cnUv .Op Fl D Ar destdir .Op Fl M Ar metalog .Ic rehash .Nm -.Op Fl nv -.Ic untrust Ar +.Op Fl cnv +.Ic untrust Ar file .Nm -.Op Fl nv -.Ic trust Ar +.Op Fl cnv +.Ic trust Ar file .Sh DESCRIPTION The .Nm utility manages the list of TLS Certificate Authorities that are trusted by applications that use OpenSSL. .Pp -The following options are available: +Flags: .Bl -tag -width 4n -.It Fl B -Do not generate a bundle. -This option is only valid in conjunction with the -.Ic rehash -command. +.It Fl c +Copy certificates instead of linking to them. .It Fl D Ar destdir Specify the DESTDIR (overriding values from the environment). -.It Fl l -When listing installed (trusted or untrusted) certificates, show the -full path and distinguished name for each certificate. +.It Fl d Ar distbase +Specify the DISTBASE (overriding values from the environment). .It Fl M Ar metalog -Specify the path of the METALOG file -.Po -default: -.Pa ${DESTDIR}/METALOG -.Pc . -This option is only valid in conjunction with the -.Ic rehash -command. +Specify the path of the METALOG file (default: $DESTDIR/METALOG). .It Fl n -Dry-run mode. -Do not actually perform any actions except write the metalog. +No-Op mode, do not actually perform any actions. .It Fl v -Verbose mode. -Print detailed information about each action taken. +Be verbose, print details about actions before performing them. .It Fl U -Unprivileged mode. -Do not attempt to set the ownership of created files. -This option is only valid in conjunction with the -.Fl M -option and the -.Ic rehash -command. +Unprivileged mode, do not change the ownership of created links. +Do record the ownership in the METALOG file. .El .Pp Primary command functions: .Bl -tag -width untrusted .It Ic list -List all currently trusted certificates. +List all currently trusted certificate authorities. .It Ic untrusted List all currently untrusted certificates. .It Ic rehash -Rebuild the list of trusted certificates by scanning all directories +Rebuild the list of trusted certificate authorities by scanning all directories in .Ev TRUSTPATH and all untrusted certificates in .Ev UNTRUSTPATH . -A copy of each trusted certificate is placed in +A symbolic link to each trusted certificate is placed in .Ev CERTDESTDIR and each untrusted certificate in .Ev UNTRUSTDESTDIR . -In addition, a bundle containing the trusted certificates is placed in -.Ev BUNDLEFILE . .It Ic untrust Add the specified file to the untrusted list. .It Ic trust @@ -118,6 +98,8 @@ Remove the specified file from the untrusted list. .Bl -tag -width UNTRUSTDESTDIR .It Ev DESTDIR Alternate destination directory to operate on. +.It Ev DISTBASE +Additional path component to include when operating on certificate directories. .It Ev LOCALBASE Location for local programs. Defaults to the value of the user.localbase sysctl which is usually @@ -125,34 +107,32 @@ Defaults to the value of the user.localbase sysctl which is usually .It Ev TRUSTPATH List of paths to search for trusted certificates. Default: -.Pa ${DESTDIR}/usr/share/certs/trusted -.Pa ${DESTDIR}${LOCALBASE}/share/certs/trusted -.Pa ${DESTDIR}${LOCALBASE}/share/certs +.Pa /usr/share/certs/trusted +.Pa /usr/local/share/certs +.Pa /etc/ssl/certs .It Ev UNTRUSTPATH List of paths to search for untrusted certificates. Default: -.Pa ${DESTDIR}/usr/share/certs/untrusted -.Pa ${DESTDIR}${LOCALBASE}/share/certs/untrusted -.It Ev TRUSTDESTDIR +.Pa /usr/share/certs/untrusted +.Pa /etc/ssl/untrusted +.Pa /etc/ssl/blacklisted +.It Ev CERTDESTDIR Destination directory for symbolic links to trusted certificates. Default: -.Pa ${DESTDIR}/etc/ssl/certs +.Pa /etc/ssl/certs .It Ev UNTRUSTDESTDIR Destination directory for symbolic links to untrusted certificates. Default: -.Pa ${DESTDIR}/etc/ssl/untrusted -.It Ev BUNDLE -File name of bundle to produce. +.Pa /etc/ssl/untrusted +.It Ev EXTENSIONS +List of file extensions to read as certificate files. +Default: *.pem *.crt *.cer *.crl *.0 .El .Sh SEE ALSO .Xr openssl 1 .Sh HISTORY .Nm first appeared in -.Fx 12.2 . +.Fx 12.2 .Sh AUTHORS -.An -nosplit -The original shell implementation was written by -.An Allan Jude Aq Mt allanjude@FreeBSD.org . -The current C implementation was written by -.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org . +.An Allan Jude Aq Mt allanjude@freebsd.org diff --git a/usr.sbin/certctl/certctl.c b/usr.sbin/certctl/certctl.c deleted file mode 100644 index 365870167ae..00000000000 --- a/usr.sbin/certctl/certctl.c +++ /dev/null @@ -1,1065 +0,0 @@ -/*- - * Copyright (c) 2023-2025 Dag-Erling Smørgrav - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define info(fmt, ...) \ - do { \ - if (verbose) \ - fprintf(stderr, fmt "\n", ##__VA_ARGS__); \ - } while (0) - -static char * -xasprintf(const char *fmt, ...) -{ - va_list ap; - char *str; - int ret; - - va_start(ap, fmt); - ret = vasprintf(&str, fmt, ap); - va_end(ap); - if (ret < 0 || str == NULL) - err(1, NULL); - return (str); -} - -static char * -xstrdup(const char *str) -{ - char *dup; - - if ((dup = strdup(str)) == NULL) - err(1, NULL); - return (dup); -} - -static void usage(void); - -static bool dryrun; -static bool longnames; -static bool nobundle; -static bool unprivileged; -static bool verbose; - -static const char *localbase; -static const char *destdir; -static const char *metalog; - -static const char *uname = "root"; -static const char *gname = "wheel"; - -static const char *const default_trusted_paths[] = { - "/usr/share/certs/trusted", - "%L/share/certs/trusted", - "%L/share/certs", - NULL -}; -static char **trusted_paths; - -static const char *const default_untrusted_paths[] = { - "/usr/share/certs/untrusted", - "%L/share/certs/untrusted", - NULL -}; -static char **untrusted_paths; - -static char *trusted_dest; -static char *untrusted_dest; -static char *bundle_dest; - -#define SSL_PATH "/etc/ssl" -#define TRUSTED_DIR "certs" -#define TRUSTED_PATH SSL_PATH "/" TRUSTED_DIR -#define UNTRUSTED_DIR "untrusted" -#define UNTRUSTED_PATH SSL_PATH "/" UNTRUSTED_DIR -#define LEGACY_DIR "blacklisted" -#define LEGACY_PATH SSL_PATH "/" LEGACY_DIR -#define BUNDLE_FILE "cert.pem" -#define BUNDLE_PATH SSL_PATH "/" BUNDLE_FILE - -static FILE *mlf; - -/* - * Split a colon-separated list into a NULL-terminated array. - */ -static char ** -split_paths(const char *str) -{ - char **paths; - const char *p, *q; - unsigned int i, n; - - for (p = str, n = 1; *p; p++) { - if (*p == ':') - n++; - } - if ((paths = calloc(n + 1, sizeof(*paths))) == NULL) - err(1, NULL); - for (p = q = str, i = 0; i < n; i++, p = q + 1) { - q = strchrnul(p, ':'); - if ((paths[i] = strndup(p, q - p)) == NULL) - err(1, NULL); - } - return (paths); -} - -/* - * Expand %L into LOCALBASE and prefix DESTDIR. - */ -static char * -expand_path(const char *template) -{ - if (template[0] == '%' && template[1] == 'L') - return (xasprintf("%s%s%s", destdir, localbase, template + 2)); - return (xasprintf("%s%s", destdir, template)); -} - -/* - * Expand an array of paths. - */ -static char ** -expand_paths(const char *const *templates) -{ - char **paths; - unsigned int i, n; - - for (n = 0; templates[n] != NULL; n++) - continue; - if ((paths = calloc(n + 1, sizeof(*paths))) == NULL) - err(1, NULL); - for (i = 0; i < n; i++) - paths[i] = expand_path(templates[i]); - return (paths); -} - -/* - * If destdir is a prefix of path, returns a pointer to the rest of path, - * otherwise returns path. - */ -static const char * -unexpand_path(const char *path) -{ - const char *p = path; - const char *q = destdir; - - while (*p && *p == *q) { - p++; - q++; - } - return (*q == '\0' && *p == '/' ? p : path); -} - -/* - * X509 certificate in a rank-balanced tree. - */ -struct cert { - RB_ENTRY(cert) entry; - unsigned long hash; - char *name; - X509 *x509; - char *path; -}; - -static void -free_cert(struct cert *cert) -{ - free(cert->name); - X509_free(cert->x509); - free(cert->path); - free(cert); -} - -static int -certcmp(const struct cert *a, const struct cert *b) -{ - return (X509_cmp(a->x509, b->x509)); -} - -RB_HEAD(cert_tree, cert); -static struct cert_tree trusted = RB_INITIALIZER(&trusted); -static struct cert_tree untrusted = RB_INITIALIZER(&untrusted); -RB_GENERATE_STATIC(cert_tree, cert, entry, certcmp); - -static void -free_certs(struct cert_tree *tree) -{ - struct cert *cert, *tmp; - - RB_FOREACH_SAFE(cert, cert_tree, tree, tmp) { - RB_REMOVE(cert_tree, tree, cert); - free_cert(cert); - } -} - -static struct cert * -find_cert(struct cert_tree *haystack, X509 *x509) -{ - struct cert needle = { .x509 = x509 }; - - return (RB_FIND(cert_tree, haystack, &needle)); -} - -/* - * File containing a certificate in a rank-balanced tree sorted by - * certificate hash and disambiguating counter. This is needed because - * the certificate hash function is prone to collisions, necessitating a - * counter to distinguish certificates that hash to the same value. - */ -struct file { - RB_ENTRY(file) entry; - const struct cert *cert; - unsigned int c; -}; - -static int -filecmp(const struct file *a, const struct file *b) -{ - if (a->cert->hash > b->cert->hash) - return (1); - if (a->cert->hash < b->cert->hash) - return (-1); - return (a->c - b->c); -} - -RB_HEAD(file_tree, file); -RB_GENERATE_STATIC(file_tree, file, entry, filecmp); - -/* - * Lexicographical sort for scandir(). - */ -static int -lexisort(const struct dirent **d1, const struct dirent **d2) -{ - return (strcmp((*d1)->d_name, (*d2)->d_name)); -} - -/* - * Read certificate(s) from a single file and insert them into a tree. - * Ignore certificates that already exist in the tree. If exclude is not - * null, also ignore certificates that exist in exclude. - * - * Returns the number certificates added to the tree, or -1 on failure. - */ -static int -read_cert(const char *path, struct cert_tree *tree, struct cert_tree *exclude) -{ - FILE *f; - X509 *x509; - X509_NAME *name; - struct cert *cert; - unsigned long hash; - int ni, no; - - if ((f = fopen(path, "r")) == NULL) { - warn("%s", path); - return (-1); - } - for (ni = no = 0; - (x509 = PEM_read_X509(f, NULL, NULL, NULL)) != NULL; - ni++) { - hash = X509_subject_name_hash(x509); - if (exclude && find_cert(exclude, x509)) { - info("%08lx: excluded", hash); - X509_free(x509); - continue; - } - if (find_cert(tree, x509)) { - info("%08lx: duplicate", hash); - X509_free(x509); - continue; - } - if ((cert = calloc(1, sizeof(*cert))) == NULL) - err(1, NULL); - cert->x509 = x509; - name = X509_get_subject_name(x509); - cert->hash = X509_NAME_hash_ex(name, NULL, NULL, NULL); - cert->name = X509_NAME_oneline(name, NULL, 0); - cert->path = xstrdup(unexpand_path(path)); - if (RB_INSERT(cert_tree, tree, cert) != NULL) - errx(1, "unexpected duplicate"); - info("%08lx: %s", cert->hash, strrchr(cert->name, '=') + 1); - no++; - } - /* - * ni is the number of certificates we found in the file. - * no is the number of certificates that weren't already in our - * tree or on the exclusion list. - */ - if (ni == 0) - warnx("%s: no valid certificates found", path); - fclose(f); - return (no); -} - -/* - * Load all certificates found in the specified path into a tree, - * optionally excluding those that already exist in a different tree. - * - * Returns the number of certificates added to the tree, or -1 on failure. - */ -static int -read_certs(const char *path, struct cert_tree *tree, struct cert_tree *exclude) -{ - struct stat sb; - char *paths[] = { (char *)(uintptr_t)path, NULL }; - FTS *fts; - FTSENT *ent; - int fts_options = FTS_LOGICAL | FTS_NOCHDIR; - int ret, total = 0; - - if (stat(path, &sb) != 0) { - return (-1); - } else if (!S_ISDIR(sb.st_mode)) { - errno = ENOTDIR; - return (-1); - } - if ((fts = fts_open(paths, fts_options, NULL)) == NULL) - err(1, "fts_open()"); - while ((ent = fts_read(fts)) != NULL) { - if (ent->fts_info != FTS_F) { - if (ent->fts_info == FTS_ERR) - warnc(ent->fts_errno, "fts_read()"); - continue; - } - info("found %s", ent->fts_path); - ret = read_cert(ent->fts_path, tree, exclude); - if (ret > 0) - total += ret; - } - fts_close(fts); - return (total); -} - -/* - * Save the contents of a cert tree to disk. - * - * Returns 0 on success and -1 on failure. - */ -static int -write_certs(const char *dir, struct cert_tree *tree) -{ - struct file_tree files = RB_INITIALIZER(&files); - struct cert *cert; - struct file *file, *tmp; - struct dirent **dents, **ent; - char *path, *tmppath = NULL; - FILE *f; - mode_t mode = 0444; - int cmp, d, fd, ndents, ret = 0; - - /* - * Start by generating unambiguous file names for each certificate - * and storing them in lexicographical order - */ - RB_FOREACH(cert, cert_tree, tree) { - if ((file = calloc(1, sizeof(*file))) == NULL) - err(1, NULL); - file->cert = cert; - for (file->c = 0; file->c < INT_MAX; file->c++) - if (RB_INSERT(file_tree, &files, file) == NULL) - break; - if (file->c == INT_MAX) - errx(1, "unable to disambiguate %08lx", cert->hash); - free(cert->path); - cert->path = xasprintf("%08lx.%d", cert->hash, file->c); - } - /* - * Open and scan the directory. - */ - if ((d = open(dir, O_DIRECTORY | O_RDONLY)) < 0 || -#ifdef BOOTSTRAPPING - (ndents = scandir(dir, &dents, NULL, lexisort)) -#else - (ndents = fdscandir(d, &dents, NULL, lexisort)) -#endif - < 0) - err(1, "%s", dir); - /* - * Iterate over the directory listing and the certificate listing - * in parallel. If the directory listing gets ahead of the - * certificate listing, we need to write the current certificate - * and advance the certificate listing. If the certificate - * listing is ahead of the directory listing, we need to delete - * the current file and advance the directory listing. If they - * are neck and neck, we have a match and could in theory compare - * the two, but in practice it's faster to just replace the - * current file with the current certificate (and advance both). - */ - ent = dents; - file = RB_MIN(file_tree, &files); - for (;;) { - if (ent < dents + ndents) { - /* skip directories */ - if ((*ent)->d_type == DT_DIR) { - free(*ent++); - continue; - } - if (file != NULL) { - /* compare current dirent to current cert */ - path = file->cert->path; - cmp = strcmp((*ent)->d_name, path); - } else { - /* trailing files in directory */ - path = NULL; - cmp = -1; - } - } else { - if (file != NULL) { - /* trailing certificates */ - path = file->cert->path; - cmp = 1; - } else { - /* end of both lists */ - path = NULL; - break; - } - } - if (cmp < 0) { - /* a file on disk with no matching certificate */ - info("removing %s/%s", dir, (*ent)->d_name); - if (!dryrun) - (void)unlinkat(d, (*ent)->d_name, 0); - free(*ent++); - continue; - } - if (cmp == 0) { - /* a file on disk with a matching certificate */ - info("replacing %s/%s", dir, (*ent)->d_name); - if (dryrun) { - fd = open(_PATH_DEVNULL, O_WRONLY); - } else { - tmppath = xasprintf(".%s", path); - fd = openat(d, tmppath, - O_CREAT | O_WRONLY | O_TRUNC, mode); - if (!unprivileged && fd >= 0) - (void)fchmod(fd, mode); - } - free(*ent++); - } else { - /* a certificate with no matching file */ - info("writing %s/%s", dir, path); - if (dryrun) { - fd = open(_PATH_DEVNULL, O_WRONLY); - } else { - tmppath = xasprintf(".%s", path); - fd = openat(d, tmppath, - O_CREAT | O_WRONLY | O_EXCL, mode); - } - } - /* write the certificate */ - if (fd < 0 || - (f = fdopen(fd, "w")) == NULL || - !PEM_write_X509(f, file->cert->x509)) { - if (tmppath != NULL && fd >= 0) { - int serrno = errno; - (void)unlinkat(d, tmppath, 0); - errno = serrno; - } - err(1, "%s/%s", dir, tmppath ? tmppath : path); - } - /* rename temp file if applicable */ - if (tmppath != NULL) { - if (ret == 0 && renameat(d, tmppath, d, path) != 0) { - warn("%s/%s", dir, path); - ret = -1; - } - if (ret != 0) - (void)unlinkat(d, tmppath, 0); - free(tmppath); - tmppath = NULL; - } - /* emit metalog */ - if (mlf != NULL) { - fprintf(mlf, "%s/%s type=file " - "uname=%s gname=%s mode=%#o size=%ld\n", - unexpand_path(dir), path, - uname, gname, mode, ftell(f)); - } - fclose(f); - /* advance certificate listing */ - tmp = RB_NEXT(file_tree, &files, file); - RB_REMOVE(file_tree, &files, file); - free(file); - file = tmp; - } - free(dents); - close(d); - return (ret); -} - -/* - * Save all certs in a tree to a single file (bundle). - * - * Returns 0 on success and -1 on failure. - */ -static int -write_bundle(const char *dir, const char *file, struct cert_tree *tree) -{ - struct cert *cert; - char *tmpfile = NULL; - FILE *f; - int d, fd, ret = 0; - mode_t mode = 0444; - - if (dir != NULL) { - if ((d = open(dir, O_DIRECTORY | O_RDONLY)) < 0) - err(1, "%s", dir); - } else { - dir = "."; - d = AT_FDCWD; - } - info("writing %s/%s", dir, file); - if (dryrun) { - fd = open(_PATH_DEVNULL, O_WRONLY); - } else { - tmpfile = xasprintf(".%s", file); - fd = openat(d, tmpfile, O_WRONLY | O_CREAT | O_EXCL, mode); - } - if (fd < 0 || (f = fdopen(fd, "w")) == NULL) { - if (tmpfile != NULL && fd >= 0) { - int serrno = errno; - (void)unlinkat(d, tmpfile, 0); - errno = serrno; - } - err(1, "%s/%s", dir, tmpfile ? tmpfile : file); - } - RB_FOREACH(cert, cert_tree, tree) { - if (!PEM_write_X509(f, cert->x509)) { - warn("%s/%s", dir, tmpfile ? tmpfile : file); - ret = -1; - break; - } - } - if (tmpfile != NULL) { - if (ret == 0 && renameat(d, tmpfile, d, file) != 0) { - warn("%s/%s", dir, file); - ret = -1; - } - if (ret != 0) - (void)unlinkat(d, tmpfile, 0); - free(tmpfile); - } - if (ret == 0 && mlf != NULL) { - fprintf(mlf, - "%s/%s type=file uname=%s gname=%s mode=%#o size=%ld\n", - unexpand_path(dir), file, uname, gname, mode, ftell(f)); - } - fclose(f); - if (d != AT_FDCWD) - close(d); - return (ret); -} - -/* - * Load trusted certificates. - * - * Returns the number of certificates loaded. - */ -static unsigned int -load_trusted(bool all, struct cert_tree *exclude) -{ - unsigned int i, n; - int ret; - - /* load external trusted certs */ - for (i = n = 0; all && trusted_paths[i] != NULL; i++) { - ret = read_certs(trusted_paths[i], &trusted, exclude); - if (ret > 0) - n += ret; - } - - /* load installed trusted certs */ - ret = read_certs(trusted_dest, &trusted, exclude); - if (ret > 0) - n += ret; - - info("%d trusted certificates found", n); - return (n); -} - -/* - * Load untrusted certificates. - * - * Returns the number of certificates loaded. - */ -static unsigned int -load_untrusted(bool all) -{ - char *path; - unsigned int i, n; - int ret; - - /* load external untrusted certs */ - for (i = n = 0; all && untrusted_paths[i] != NULL; i++) { - ret = read_certs(untrusted_paths[i], &untrusted, NULL); - if (ret > 0) - n += ret; - } - - /* load installed untrusted certs */ - ret = read_certs(untrusted_dest, &untrusted, NULL); - if (ret > 0) - n += ret; - - /* load legacy untrusted certs */ - path = expand_path(LEGACY_PATH); - ret = read_certs(path, &untrusted, NULL); - if (ret > 0) { - warnx("certificates found in legacy directory %s", - path); - n += ret; - } else if (ret == 0) { - warnx("legacy directory %s can safely be deleted", - path); - } - free(path); - - info("%d untrusted certificates found", n); - return (n); -} - -/* - * Save trusted certificates. - * - * Returns 0 on success and -1 on failure. - */ -static int -save_trusted(void) -{ - int ret; - - /* save untrusted certs */ - ret = write_certs(trusted_dest, &trusted); - return (ret); -} - -/* - * Save untrusted certificates. - * - * Returns 0 on success and -1 on failure. - */ -static int -save_untrusted(void) -{ - int ret; - - ret = write_certs(untrusted_dest, &untrusted); - return (ret); -} - -/* - * Save certificate bundle. - * - * Returns 0 on success and -1 on failure. - */ -static int -save_bundle(void) -{ - char *dir, *file, *sep; - int ret; - - if ((sep = strrchr(bundle_dest, '/')) == NULL) { - dir = NULL; - file = bundle_dest; - } else { - dir = xasprintf("%.*s", (int)(sep - bundle_dest), bundle_dest); - file = sep + 1; - } - ret = write_bundle(dir, file, &trusted); - free(dir); - return (ret); -} - -/* - * Save everything. - * - * Returns 0 on success and -1 on failure. - */ -static int -save_all(void) -{ - int ret = 0; - - ret |= save_untrusted(); - ret |= save_trusted(); - if (!nobundle) - ret |= save_bundle(); - return (ret); -} - -/* - * List the contents of a certificate tree. - */ -static void -list_certs(struct cert_tree *tree) -{ - struct cert *cert; - char *path, *name; - - RB_FOREACH(cert, cert_tree, tree) { - path = longnames ? NULL : strrchr(cert->path, '/'); - name = longnames ? NULL : strrchr(cert->name, '='); - printf("%s\t%s\n", path ? path + 1 : cert->path, - name ? name + 1 : cert->name); - } -} - -/* - * Load installed trusted certificates, then list them. - * - * Returns 0 on success and -1 on failure. - */ -static int -certctl_list(int argc, char **argv __unused) -{ - if (argc > 1) - usage(); - /* load trusted certificates */ - load_trusted(false, NULL); - /* list them */ - list_certs(&trusted); - free_certs(&trusted); - return (0); -} - -/* - * Load installed untrusted certificates, then list them. - * - * Returns 0 on success and -1 on failure. - */ -static int -certctl_untrusted(int argc, char **argv __unused) -{ - if (argc > 1) - usage(); - /* load untrusted certificates */ - load_untrusted(false); - /* list them */ - list_certs(&untrusted); - free_certs(&untrusted); - return (0); -} - -/* - * Load trusted and untrusted certificates from all sources, then - * regenerate both the hashed directories and the bundle. - * - * Returns 0 on success and -1 on failure. - */ -static int -certctl_rehash(int argc, char **argv __unused) -{ - int ret; - - if (argc > 1) - usage(); - - if (unprivileged && (mlf = fopen(metalog, "a")) == NULL) { - warn("%s", metalog); - return (-1); - } - - /* load untrusted certs first */ - load_untrusted(true); - - /* load trusted certs, excluding any that are already untrusted */ - load_trusted(true, &untrusted); - - /* save everything */ - ret = save_all(); - - /* clean up */ - free_certs(&untrusted); - free_certs(&trusted); - if (mlf != NULL) - fclose(mlf); - return (ret); -} - -/* - * Manually add one or more certificates to the list of trusted certificates. - * - * Returns 0 on success and -1 on failure. - */ -static int -certctl_trust(int argc, char **argv) -{ - struct cert_tree extra = RB_INITIALIZER(&extra); - struct cert *cert, *other, *tmp; - unsigned int n; - int i, ret; - - if (argc < 2) - usage(); - - /* load untrusted certs first */ - load_untrusted(true); - - /* load trusted certs, excluding any that are already untrusted */ - load_trusted(true, &untrusted); - - /* now load the additional trusted certificates */ - n = 0; - for (i = 1; i < argc; i++) { - ret = read_cert(argv[i], &extra, &trusted); - if (ret > 0) - n += ret; - } - if (n == 0) { - warnx("no new trusted certificates found"); - free_certs(&untrusted); - free_certs(&trusted); - free_certs(&extra); - return (0); - } - - /* - * For each new trusted cert, move it from the extra list to the - * trusted list, then check if a matching certificate exists on - * the untrusted list. If that is the case, warn the user, then - * remove the matching certificate from the untrusted list. - */ - RB_FOREACH_SAFE(cert, cert_tree, &extra, tmp) { - RB_REMOVE(cert_tree, &extra, cert); - RB_INSERT(cert_tree, &trusted, cert); - if ((other = RB_FIND(cert_tree, &untrusted, cert)) != NULL) { - warnx("%s was previously untrusted", cert->name); - RB_REMOVE(cert_tree, &untrusted, other); - free_cert(other); - } - } - - /* save everything */ - ret = save_all(); - - /* clean up */ - free_certs(&untrusted); - free_certs(&trusted); - return (ret); -} - -/* - * Manually add one or more certificates to the list of untrusted - * certificates. - * - * Returns 0 on success and -1 on failure. - */ -static int -certctl_untrust(int argc, char **argv) -{ - unsigned int n; - int i, ret; - - if (argc < 2) - usage(); - - /* load untrusted certs first */ - load_untrusted(true); - - /* now load the additional untrusted certificates */ - n = 0; - for (i = 1; i < argc; i++) { - ret = read_cert(argv[i], &untrusted, NULL); - if (ret > 0) - n += ret; - } - if (n == 0) { - warnx("no new untrusted certificates found"); - free_certs(&untrusted); - return (0); - } - - /* load trusted certs, excluding any that are already untrusted */ - load_trusted(true, &untrusted); - - /* save everything */ - ret = save_all(); - - /* clean up */ - free_certs(&untrusted); - free_certs(&trusted); - return (ret); -} - -static void -set_defaults(void) -{ - const char *value; - char *str; - size_t len; - - if (localbase == NULL && - (localbase = getenv("LOCALBASE")) == NULL) { - if ((str = malloc((len = PATH_MAX) + 1)) == NULL) - err(1, NULL); - while (sysctlbyname("user.localbase", str, &len, NULL, 0) < 0) { - if (errno != ENOMEM) - err(1, "sysctl(user.localbase)"); - if ((str = realloc(str, len + 1)) == NULL) - err(1, NULL); - } - str[len] = '\0'; - localbase = str; - } - - if (destdir == NULL && - (destdir = getenv("DESTDIR")) == NULL) - destdir = ""; - - if (unprivileged && metalog == NULL && - (metalog = getenv("METALOG")) == NULL) - metalog = xasprintf("%s/METALOG", destdir); - - if (!verbose) { - if ((value = getenv("CERTCTL_VERBOSE")) != NULL) { - if (value[0] != '\0') { - verbose = true; - } - } - } - - if ((value = getenv("TRUSTPATH")) != NULL) - trusted_paths = split_paths(value); - else - trusted_paths = expand_paths(default_trusted_paths); - - if ((value = getenv("UNTRUSTPATH")) != NULL) - untrusted_paths = split_paths(value); - else - untrusted_paths = expand_paths(default_untrusted_paths); - - if ((value = getenv("TRUSTDESTDIR")) != NULL || - (value = getenv("CERTDESTDIR")) != NULL) - trusted_dest = xstrdup(value); - else - trusted_dest = expand_path(TRUSTED_PATH); - - if ((value = getenv("UNTRUSTDESTDIR")) != NULL) - untrusted_dest = xstrdup(value); - else - untrusted_dest = expand_path(UNTRUSTED_PATH); - - if ((value = getenv("BUNDLE")) != NULL) - bundle_dest = xstrdup(value); - else - bundle_dest = expand_path(BUNDLE_PATH); - - info("localbase:\t%s", localbase); - info("destdir:\t%s", destdir); - info("unprivileged:\t%s", unprivileged ? "true" : "false"); - info("verbose:\t%s", verbose ? "true" : "false"); -} - -typedef int (*main_t)(int, char **); - -static struct { - const char *name; - main_t func; -} commands[] = { - { "list", certctl_list }, - { "untrusted", certctl_untrusted }, - { "rehash", certctl_rehash }, - { "untrust", certctl_untrust }, - { "trust", certctl_trust }, - { 0 }, -}; - -static void -usage(void) -{ - fprintf(stderr, "usage: certctl [-lv] [-D destdir] list\n" - " certctl [-lv] [-D destdir] untrusted\n" - " certctl [-BnUv] [-D destdir] [-M metalog] rehash\n" - " certctl [-nv] [-D destdir] untrust \n" - " certctl [-nv] [-D destdir] trust \n"); - exit(1); -} - -int -main(int argc, char *argv[]) -{ - const char *command; - int opt; - - while ((opt = getopt(argc, argv, "BcD:g:lL:M:no:Uv")) != -1) - switch (opt) { - case 'B': - nobundle = true; - break; - case 'c': - /* ignored for compatibility */ - break; - case 'D': - destdir = optarg; - break; - case 'g': - gname = optarg; - break; - case 'l': - longnames = true; - break; - case 'L': - localbase = optarg; - break; - case 'M': - metalog = optarg; - break; - case 'n': - dryrun = true; - break; - case 'o': - uname = optarg; - break; - case 'U': - unprivileged = true; - break; - case 'v': - verbose = true; - break; - default: - usage(); - } - - argc -= optind; - argv += optind; - - if (argc < 1) - usage(); - - command = *argv; - - if ((nobundle || unprivileged || metalog != NULL) && - strcmp(command, "rehash") != 0) - usage(); - if (!unprivileged && metalog != NULL) { - warnx("-M may only be used in conjunction with -U"); - usage(); - } - - set_defaults(); - - for (unsigned i = 0; commands[i].name != NULL; i++) - if (strcmp(command, commands[i].name) == 0) - exit(!!commands[i].func(argc, argv)); - usage(); -} diff --git a/usr.sbin/certctl/certctl.sh b/usr.sbin/certctl/certctl.sh new file mode 100755 index 00000000000..2bde651de12 --- /dev/null +++ b/usr.sbin/certctl/certctl.sh @@ -0,0 +1,366 @@ +#!/bin/sh +#- +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright 2018 Allan Jude +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted providing that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +set -u + +############################################################ CONFIGURATION + +: ${DESTDIR:=} +: ${DISTBASE:=} + +############################################################ GLOBALS + +SCRIPTNAME="${0##*/}" +LINK=-lrs +ERRORS=0 +NOOP=false +UNPRIV=false +VERBOSE=false + +############################################################ FUNCTIONS + +info() +{ + echo "${0##*/}: $@" >&2 +} + +verbose() +{ + if "${VERBOSE}" ; then + info "$@" + fi +} + +perform() +{ + if ! "${NOOP}" ; then + "$@" + fi +} + +cert_files_in() +{ + find -L "$@" -type f \( \ + -name '*.pem' -or \ + -name '*.crt' -or \ + -name '*.cer' \ + \) 2>/dev/null +} + +eolcvt() +{ + cat "$@" | tr -s '\r' '\n' +} + +do_hash() +{ + local hash + + if hash=$(openssl x509 -noout -subject_hash -in "$1") ; then + echo "$hash" + return 0 + else + info "Error: $1" + ERRORS=$((ERRORS + 1)) + return 1 + fi +} + +get_decimal() +{ + local checkdir hash decimal + + checkdir=$1 + hash=$2 + decimal=0 + + while [ -e "$checkdir/$hash.$decimal" ] ; do + decimal=$((decimal + 1)) + done + + echo ${decimal} + return 0 +} + +create_trusted() +{ + local hash certhash otherfile otherhash + local suffix + + hash=$(do_hash "$1") || return + certhash=$(openssl x509 -sha1 -in "$1" -noout -fingerprint) + for otherfile in $(find $UNTRUSTDESTDIR -name "$hash.*") ; do + otherhash=$(openssl x509 -sha1 -in "$otherfile" -noout -fingerprint) + if [ "$certhash" = "$otherhash" ] ; then + info "Skipping untrusted certificate $hash ($otherfile)" + return 0 + fi + done + for otherfile in $(find $CERTDESTDIR -name "$hash.*") ; do + otherhash=$(openssl x509 -sha1 -in "$otherfile" -noout -fingerprint) + if [ "$certhash" = "$otherhash" ] ; then + verbose "Skipping duplicate entry for certificate $hash" + return 0 + fi + done + suffix=$(get_decimal "$CERTDESTDIR" "$hash") + verbose "Adding $hash.$suffix to trust store" + perform install ${INSTALLFLAGS} -m 0444 ${LINK} \ + "$(realpath "$1")" "$CERTDESTDIR/$hash.$suffix" +} + +# Accepts either dot-hash form from `certctl list` or a path to a valid cert. +resolve_certname() +{ + local hash srcfile filename + local suffix + + # If it exists as a file, we'll try that; otherwise, we'll scan + if [ -e "$1" ] ; then + hash=$(do_hash "$1") || return + srcfile=$(realpath "$1") + suffix=$(get_decimal "$UNTRUSTDESTDIR" "$hash") + filename="$hash.$suffix" + echo "$srcfile" "$hash.$suffix" + elif [ -e "${CERTDESTDIR}/$1" ] ; then + srcfile=$(realpath "${CERTDESTDIR}/$1") + hash=$(echo "$1" | sed -Ee 's/\.([0-9])+$//') + suffix=$(get_decimal "$UNTRUSTDESTDIR" "$hash") + filename="$hash.$suffix" + echo "$srcfile" "$hash.$suffix" + fi +} + +create_untrusted() +{ + local srcfile filename + + set -- $(resolve_certname "$1") + srcfile=$1 + filename=$2 + + if [ -z "$srcfile" -o -z "$filename" ] ; then + return + fi + + verbose "Adding $filename to untrusted list" + perform install ${INSTALLFLAGS} -m 0444 ${LINK} \ + "$srcfile" "$UNTRUSTDESTDIR/$filename" +} + +do_scan() +{ + local CFUNC CSEARCH CPATH CFILE CERT SPLITDIR + local oldIFS="$IFS" + CFUNC="$1" + CSEARCH="$2" + + IFS=: + set -- $CSEARCH + IFS="$oldIFS" + for CFILE in $(cert_files_in "$@") ; do + verbose "Reading $CFILE" + case $(eolcvt "$CFILE" | egrep -c '^-+BEGIN CERTIFICATE-+$') in + 0) + ;; + 1) + "$CFUNC" "$CFILE" + ;; + *) + verbose "Multiple certificates found, splitting..." + SPLITDIR=$(mktemp -d) + eolcvt "$CFILE" | egrep '^(---|[0-9A-Za-z/+=]+$)' | \ + split -p '^-+BEGIN CERTIFICATE-+$' - "$SPLITDIR/x" + for CERT in $(find "$SPLITDIR" -type f) ; do + "$CFUNC" "$CERT" + done + rm -rf "$SPLITDIR" + ;; + esac + done +} + +do_list() +{ + local CFILE subject + + for CFILE in $(find "$@" \( -type f -or -type l \) -name '*.[0-9]') ; do + if [ ! -s "$CFILE" ] ; then + info "Unable to read $CFILE" + ERRORS=$((ERRORS + 1)) + continue + fi + subject= + if ! "$VERBOSE" ; then + subject=$(openssl x509 -noout -subject -nameopt multiline -in "$CFILE" | sed -n '/commonName/s/.*= //p') + fi + if [ -z "$subject" ] ; then + subject=$(openssl x509 -noout -subject -in "$CFILE") + fi + printf "%s\t%s\n" "${CFILE##*/}" "$subject" + done +} + +cmd_rehash() +{ + + if [ -e "$CERTDESTDIR" ] ; then + perform find "$CERTDESTDIR" \( -type f -or -type l \) -delete + else + perform install -d -m 0755 "$CERTDESTDIR" + fi + if [ -e "$UNTRUSTDESTDIR" ] ; then + perform find "$UNTRUSTDESTDIR" \( -type f -or -type l \) -delete + else + perform install -d -m 0755 "$UNTRUSTDESTDIR" + fi + + do_scan create_untrusted "$UNTRUSTPATH" + do_scan create_trusted "$TRUSTPATH" +} + +cmd_list() +{ + info "Listing Trusted Certificates:" + do_list "$CERTDESTDIR" +} + +cmd_untrust() +{ + local UTFILE + + shift # verb + perform install -d -m 0755 "$UNTRUSTDESTDIR" + for UTFILE in "$@"; do + info "Adding $UTFILE to untrusted list" + create_untrusted "$UTFILE" + done +} + +cmd_trust() +{ + local UTFILE untrustedhash certhash hash + + shift # verb + for UTFILE in "$@"; do + if [ -s "$UTFILE" ] ; then + hash=$(do_hash "$UTFILE") + certhash=$(openssl x509 -sha1 -in "$UTFILE" -noout -fingerprint) + for UNTRUSTEDFILE in $(find $UNTRUSTDESTDIR -name "$hash.*") ; do + untrustedhash=$(openssl x509 -sha1 -in "$UNTRUSTEDFILE" -noout -fingerprint) + if [ "$certhash" = "$untrustedhash" ] ; then + info "Removing $(basename "$UNTRUSTEDFILE") from untrusted list" + perform rm -f $UNTRUSTEDFILE + fi + done + elif [ -e "$UNTRUSTDESTDIR/$UTFILE" ] ; then + info "Removing $UTFILE from untrusted list" + perform rm -f "$UNTRUSTDESTDIR/$UTFILE" + else + info "Cannot find $UTFILE" + ERRORS=$((ERRORS + 1)) + fi + done +} + +cmd_untrusted() +{ + info "Listing Untrusted Certificates:" + do_list "$UNTRUSTDESTDIR" +} + +usage() +{ + exec >&2 + echo "Manage the TLS trusted certificates on the system" + echo " $SCRIPTNAME [-v] list" + echo " List trusted certificates" + echo " $SCRIPTNAME [-v] untrusted" + echo " List untrusted certificates" + echo " $SCRIPTNAME [-cnUv] [-D ] [-d ] [-M ] rehash" + echo " Rehash all trusted and untrusted certificates" + echo " $SCRIPTNAME [-cnv] untrust " + echo " Add to the list of untrusted certificates" + echo " $SCRIPTNAME [-cnv] trust " + echo " Remove from the list of untrusted certificates" + exit 64 +} + +############################################################ MAIN + +while getopts cD:d:M:nUv flag; do + case "$flag" in + c) LINK=-c ;; + D) DESTDIR=${OPTARG} ;; + d) DISTBASE=${OPTARG} ;; + M) METALOG=${OPTARG} ;; + n) NOOP=true ;; + U) UNPRIV=true ;; + v) VERBOSE=true ;; + esac +done +shift $((OPTIND - 1)) + +DESTDIR=${DESTDIR%/} + +if ! [ -z "${CERTCTL_VERBOSE:-}" ] ; then + VERBOSE=true +fi +: ${METALOG:=${DESTDIR}/METALOG} +INSTALLFLAGS= +if "$UNPRIV" ; then + INSTALLFLAGS="-U -M ${METALOG} -D ${DESTDIR:-/} -o root -g wheel" +fi +: ${LOCALBASE:=$(sysctl -n user.localbase)} +: ${TRUSTPATH:=${DESTDIR}${DISTBASE}/usr/share/certs/trusted:${DESTDIR}${LOCALBASE}/share/certs:${DESTDIR}${LOCALBASE}/etc/ssl/certs} +: ${UNTRUSTPATH:=${DESTDIR}${DISTBASE}/usr/share/certs/untrusted:${DESTDIR}${LOCALBASE}/etc/ssl/untrusted:${DESTDIR}${LOCALBASE}/etc/ssl/blacklisted} +: ${CERTDESTDIR:=${DESTDIR}${DISTBASE}/etc/ssl/certs} +: ${UNTRUSTDESTDIR:=${DESTDIR}${DISTBASE}/etc/ssl/untrusted} + +[ $# -gt 0 ] || usage +case "$1" in +list) cmd_list ;; +rehash) cmd_rehash ;; +blacklist) cmd_untrust "$@" ;; +untrust) cmd_untrust "$@" ;; +trust) cmd_trust "$@" ;; +unblacklist) cmd_trust "$@" ;; +untrusted) cmd_untrusted ;; +blacklisted) cmd_untrusted ;; +*) usage # NOTREACHED +esac + +retval=$? +if [ $ERRORS -gt 0 ] ; then + info "Encountered $ERRORS errors" +fi +exit $retval + +################################################################################ +# END +################################################################################ diff --git a/usr.sbin/certctl/tests/Makefile b/usr.sbin/certctl/tests/Makefile deleted file mode 100644 index da301c3ded0..00000000000 --- a/usr.sbin/certctl/tests/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -PACKAGE= tests -ATF_TESTS_SH= certctl_test -${PACKAGE}FILES+= certctl.subr - -.include diff --git a/usr.sbin/certctl/tests/certctl.subr b/usr.sbin/certctl/tests/certctl.subr deleted file mode 100644 index 841cc1781e6..00000000000 --- a/usr.sbin/certctl/tests/certctl.subr +++ /dev/null @@ -1,44 +0,0 @@ -# -# Copyright (c) 2025 Dag-Erling Smørgrav -# -# SPDX-License-Identifier: BSD-2-Clause -# - -# Generate a random name -rand_name() { - local length=${1:-32} - - jot -r -c -s '' ${length} A Z -} - -# Generate a subject for a given name -subject() { - local crtname=$1 - - echo "/CN=${crtname}/O=FreeBSD/OU=Test/" -} - -# Generate a key -gen_key() { - local keyname=$1 - - env -i PATH="${PATH}" OPENSSL_CONF=/dev/null \ - openssl genrsa -out ${keyname}.key -} - -# Generate a certificate for a given name, key, and serial number -gen_crt() { - local crtname=$1 - local keyname=${2:-${crtname}} - local serial=${3:-1} - - if ! [ -f "${keyname}".key ]; then - gen_key "${keyname}" - fi - env -i PATH="${PATH}" OPENSSL_CONF=/dev/null \ - openssl req -x509 -new \ - -subj="$(subject ${crtname})" \ - -set_serial ${serial} \ - -key ${keyname}.key \ - -out ${crtname}.crt -} diff --git a/usr.sbin/certctl/tests/certctl_test.sh b/usr.sbin/certctl/tests/certctl_test.sh deleted file mode 100644 index 4e236d5bfae..00000000000 --- a/usr.sbin/certctl/tests/certctl_test.sh +++ /dev/null @@ -1,221 +0,0 @@ -# -# Copyright (c) 2025 Dag-Erling Smørgrav -# -# SPDX-License-Identifier: BSD-2-Clause -# - -. $(atf_get_srcdir)/certctl.subr - -# Random sets of eight non-colliding names -set1() -{ - cat <usr/local/share/certs/bundle.crt - set3 | while read crtname hash ; do - gen_crt ${crtname} ${keyname} - mv ${crtname}.crt usr/share/certs/untrusted - done -} - -check_trusted() { - local crtname=$1 - local subject="$(subject ${crtname})" - local c=${2:-1} - - atf_check -o match:"found: ${c}\$" \ - openssl storeutl -noout -subject "${subject}" \ - etc/ssl/certs - atf_check -o match:"found: 0\$" \ - openssl storeutl -noout -subject "${subject}" \ - etc/ssl/untrusted -} - -check_untrusted() { - local crtname=$1 - local subject="$(subject ${crtname})" - local c=${2:-1} - - atf_check -o match:"found: 0\$" \ - openssl storeutl -noout -subject "${subject}" \ - etc/ssl/certs - atf_check -o match:"found: ${c}\$" \ - openssl storeutl -noout -subject "${subject}" \ - etc/ssl/untrusted -} - -check_in_bundle() { - local crtfile=$1 - local line - - line=$(tail +5 "${crtfile}" | head -1) - atf_check grep -q "${line}" etc/ssl/cert.pem -} - -check_not_in_bundle() { - local crtfile=$1 - local line - - line=$(tail +5 "${crtfile}" | head -1) - atf_check -s exit:1 grep -q "${line}" etc/ssl/cert.pem -} - -atf_test_case rehash -rehash_head() -{ - atf_set "descr" "Test the rehash command" -} -rehash_body() -{ - certctl_setup - atf_check certctl rehash - - # Verify non-colliding trusted certificates - (set1 ; set2) > trusted - while read crtname hash ; do - check_trusted "${crtname}" - done coll - while read crtname hash ; do - check_trusted "${crtname}" $(wc -l untrusted - while read crtname hash ; do - check_untrusted "${crtname}" - done