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