Module Name:    src
Committed By:   riastradh
Date:           Sat Aug 26 05:27:15 UTC 2023

Modified Files:
        src/distrib/sets/lists/base: mi
        src/distrib/sets/lists/man: mi
        src/distrib/sets/lists/tests: mi
        src/etc: Makefile
        src/etc/mtree: NetBSD.dist.tests
        src/tests/usr.sbin: Makefile
        src/usr.sbin: Makefile
Added Files:
        src/tests/usr.sbin/certctl: Makefile Makefile.inc t_certctl.sh
        src/tests/usr.sbin/certctl/certs1: DigiCert_Global_Root_CA.pem
            Explicitly_Distrust_DigiNotar_Root_CA.pem Makefile
        src/tests/usr.sbin/certctl/certs2: GTS_Root_R1.pem
            GlobalSign_Root_CA_-_R3.pem Makefile
        src/tests/usr.sbin/certctl/certs3:
            Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.1.pem
            Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.pem
            Makefile
        src/tests/usr.sbin/certctl/certs4: AC_RAIZ_FNMT-RCM.pem
            DigiCert_Global_Root_CA.pem Makefile
        src/usr.sbin/certctl: Makefile certctl.8 certctl.sh certs.conf

Log Message:
certctl(8): New tool for managing OpenSSL CA certificates.

Same command-line syntax as FreeBSD, clearer semantics about which
parts are config and which parts are cache.


To generate a diff of this commit:
cvs rdiff -u -r1.1325 -r1.1326 src/distrib/sets/lists/base/mi
cvs rdiff -u -r1.1763 -r1.1764 src/distrib/sets/lists/man/mi
cvs rdiff -u -r1.1291 -r1.1292 src/distrib/sets/lists/tests/mi
cvs rdiff -u -r1.466 -r1.467 src/etc/Makefile
cvs rdiff -u -r1.199 -r1.200 src/etc/mtree/NetBSD.dist.tests
cvs rdiff -u -r1.7 -r1.8 src/tests/usr.sbin/Makefile
cvs rdiff -u -r0 -r1.1 src/tests/usr.sbin/certctl/Makefile \
    src/tests/usr.sbin/certctl/Makefile.inc \
    src/tests/usr.sbin/certctl/t_certctl.sh
cvs rdiff -u -r0 -r1.1 \
    src/tests/usr.sbin/certctl/certs1/DigiCert_Global_Root_CA.pem \
    src/tests/usr.sbin/certctl/certs1/Explicitly_Distrust_DigiNotar_Root_CA.pem 
\
    src/tests/usr.sbin/certctl/certs1/Makefile
cvs rdiff -u -r0 -r1.1 src/tests/usr.sbin/certctl/certs2/GTS_Root_R1.pem \
    src/tests/usr.sbin/certctl/certs2/GlobalSign_Root_CA_-_R3.pem \
    src/tests/usr.sbin/certctl/certs2/Makefile
cvs rdiff -u -r0 -r1.1 \
    
src/tests/usr.sbin/certctl/certs3/Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.1.pem
 \
    
src/tests/usr.sbin/certctl/certs3/Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.pem
 \
    src/tests/usr.sbin/certctl/certs3/Makefile
cvs rdiff -u -r0 -r1.1 src/tests/usr.sbin/certctl/certs4/AC_RAIZ_FNMT-RCM.pem \
    src/tests/usr.sbin/certctl/certs4/DigiCert_Global_Root_CA.pem \
    src/tests/usr.sbin/certctl/certs4/Makefile
cvs rdiff -u -r1.291 -r1.292 src/usr.sbin/Makefile
cvs rdiff -u -r0 -r1.1 src/usr.sbin/certctl/Makefile \
    src/usr.sbin/certctl/certctl.8 src/usr.sbin/certctl/certctl.sh \
    src/usr.sbin/certctl/certs.conf

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/distrib/sets/lists/base/mi
diff -u src/distrib/sets/lists/base/mi:1.1325 src/distrib/sets/lists/base/mi:1.1326
--- src/distrib/sets/lists/base/mi:1.1325	Tue Aug  1 06:35:55 2023
+++ src/distrib/sets/lists/base/mi	Sat Aug 26 05:27:13 2023
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.1325 2023/08/01 06:35:55 mrg Exp $
+# $NetBSD: mi,v 1.1326 2023/08/26 05:27:13 riastradh Exp $
 #
 # Note:	Don't delete entries from here - mark them as "obsolete" instead,
 #	unless otherwise stated below.
@@ -114,6 +114,7 @@
 ./etc/openldap					base-ldap-root
 ./etc/openssl					base-crypto-root
 ./etc/openssl/certs				base-crypto-root
+./etc/openssl/certs.conf			base-sysutil-bin
 ./etc/openssl/misc				base-crypto-root
 ./etc/openssl/private				base-crypto-root
 ./etc/pam.d					base-sys-root
@@ -1457,6 +1458,7 @@
 ./usr/sbin/btpand				base-sysutil-bin
 ./usr/sbin/btuartd				base-obsolete		obsolete
 ./usr/sbin/catman				base-man-bin
+./usr/sbin/certctl				base-sysutil-bin
 ./usr/sbin/chat					base-ppp-bin
 ./usr/sbin/chown				base-sysutil-bin
 ./usr/sbin/chroot				base-sysutil-bin

Index: src/distrib/sets/lists/man/mi
diff -u src/distrib/sets/lists/man/mi:1.1763 src/distrib/sets/lists/man/mi:1.1764
--- src/distrib/sets/lists/man/mi:1.1763	Fri Aug 11 23:04:29 2023
+++ src/distrib/sets/lists/man/mi	Sat Aug 26 05:27:13 2023
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.1763 2023/08/11 23:04:29 christos Exp $
+# $NetBSD: mi,v 1.1764 2023/08/26 05:27:13 riastradh Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 #
@@ -2648,6 +2648,7 @@
 ./usr/share/man/cat8/ccdconfig.0		man-sysutil-catman	.cat
 ./usr/share/man/cat8/cesfic/MAKEDEV.0		man-obsolete		obsolete
 ./usr/share/man/cat8/cesfic/makedev.0		man-obsolete		obsolete
+./usr/share/man/cat8/certctl.0			man-sysutil-catman	.cat
 ./usr/share/man/cat8/cgdconfig.0		man-sysutil-catman	.cat
 ./usr/share/man/cat8/chat.0			man-ppp-catman		.cat
 ./usr/share/man/cat8/chown.0			man-sysutil-catman	.cat
@@ -5902,6 +5903,7 @@
 ./usr/share/man/html8/canconfig.html		man-netutil-htmlman	html
 ./usr/share/man/html8/catman.html		man-man-htmlman		html
 ./usr/share/man/html8/ccdconfig.html		man-sysutil-htmlman	html
+./usr/share/man/html8/certctl.html		man-sysutil-htmlman	html
 ./usr/share/man/html8/cgdconfig.html		man-sysutil-htmlman	html
 ./usr/share/man/html8/chat.html			man-ppp-htmlman		html
 ./usr/share/man/html8/chown.html		man-sysutil-htmlman	html
@@ -9203,6 +9205,7 @@
 ./usr/share/man/man8/ccdconfig.8		man-sysutil-man		.man
 ./usr/share/man/man8/cesfic/MAKEDEV.8		man-obsolete		obsolete
 ./usr/share/man/man8/cesfic/makedev.8		man-obsolete		obsolete
+./usr/share/man/man8/certctl.8			man-sysutil-man		.man
 ./usr/share/man/man8/cgdconfig.8		man-sysutil-man		.man
 ./usr/share/man/man8/chat.8			man-ppp-man		.man
 ./usr/share/man/man8/chown.8			man-sysutil-man		.man

Index: src/distrib/sets/lists/tests/mi
diff -u src/distrib/sets/lists/tests/mi:1.1291 src/distrib/sets/lists/tests/mi:1.1292
--- src/distrib/sets/lists/tests/mi:1.1291	Sun Aug 20 19:36:56 2023
+++ src/distrib/sets/lists/tests/mi	Sat Aug 26 05:27:14 2023
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.1291 2023/08/20 19:36:56 riastradh Exp $
+# $NetBSD: mi,v 1.1292 2023/08/26 05:27:14 riastradh Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 #
@@ -7564,6 +7564,22 @@
 ./usr/tests/usr.sbin					tests-usr.sbin-tests	compattestfile,atf
 ./usr/tests/usr.sbin/Atffile				tests-usr.sbin-tests	compattestfile,atf
 ./usr/tests/usr.sbin/Kyuafile				tests-usr.sbin-tests	compattestfile,atf,kyua
+./usr/tests/usr.sbin/certctl				tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/Atffile			tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/Kyuafile			tests-usr.sbin-tests	compattestfile,atf,kyua
+./usr/tests/usr.sbin/certctl/certs1			tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/certs1/DigiCert_Global_Root_CA.pem			tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/certs1/Explicitly_Distrust_DigiNotar_Root_CA.pem	tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/certs2			tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/certs2/GTS_Root_R1.pem		tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/certs2/GlobalSign_Root_CA_-_R3.pem	tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/certs3			tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/certs3/Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.1.pem	tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/certs3/Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.pem	tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/certs4			tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/certs4/AC_RAIZ_FNMT-RCM.pem	tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/certs4/DigiCert_Global_Root_CA.pem	tests-usr.sbin-tests	compattestfile,atf
+./usr/tests/usr.sbin/certctl/t_certctl			tests-usr.sbin-tests	compattestfile,atf
 ./usr/tests/usr.sbin/cpuctl				tests-usr.sbin-tests	compattestfile,atf
 ./usr/tests/usr.sbin/cpuctl/Atffile			tests-usr.sbin-tests	compattestfile,atf
 ./usr/tests/usr.sbin/cpuctl/Kyuafile			tests-usr.sbin-tests	compattestfile,atf,kyua

Index: src/etc/Makefile
diff -u src/etc/Makefile:1.466 src/etc/Makefile:1.467
--- src/etc/Makefile:1.466	Sun Aug 21 07:10:03 2022
+++ src/etc/Makefile	Sat Aug 26 05:27:14 2023
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.466 2022/08/21 07:10:03 lukem Exp $
+#	$NetBSD: Makefile,v 1.467 2023/08/26 05:27:14 riastradh Exp $
 #	from: @(#)Makefile	8.7 (Berkeley) 5/25/95
 
 # Environment variables without default values:
@@ -348,6 +348,7 @@ install-etc-files: .PHONY .MAKE check_DE
 .endfor
 	${MAKEDIRTARGET} ${NETBSDSRCDIR}/external/bsd/dhcpcd/sbin/dhcpcd configinstall
 	${MAKEDIRTARGET} ${NETBSDSRCDIR}/usr.bin/mail configinstall
+	${MAKEDIRTARGET} ${NETBSDSRCDIR}/usr.sbin/certctl configinstall
 .if (${MKPF} != "no")
 	${MAKEDIRTARGET} ${NETBSDSRCDIR}/usr.sbin/pf configinstall
 .endif

Index: src/etc/mtree/NetBSD.dist.tests
diff -u src/etc/mtree/NetBSD.dist.tests:1.199 src/etc/mtree/NetBSD.dist.tests:1.200
--- src/etc/mtree/NetBSD.dist.tests:1.199	Sun Aug 20 10:27:11 2023
+++ src/etc/mtree/NetBSD.dist.tests	Sat Aug 26 05:27:14 2023
@@ -1,4 +1,4 @@
-#	$NetBSD: NetBSD.dist.tests,v 1.199 2023/08/20 10:27:11 riastradh Exp $
+#	$NetBSD: NetBSD.dist.tests,v 1.200 2023/08/26 05:27:14 riastradh Exp $
 
 ./usr/libdata/debug/usr/tests
 ./usr/libdata/debug/usr/tests/atf
@@ -486,6 +486,11 @@
 ./usr/tests/usr.bin/xlint/xlint
 ./usr/tests/usr.bin/ztest
 ./usr/tests/usr.sbin
+./usr/tests/usr.sbin/certctl
+./usr/tests/usr.sbin/certctl/certs1
+./usr/tests/usr.sbin/certctl/certs2
+./usr/tests/usr.sbin/certctl/certs3
+./usr/tests/usr.sbin/certctl/certs4
 ./usr/tests/usr.sbin/cpuctl
 ./usr/tests/usr.sbin/execsnoop
 ./usr/tests/usr.sbin/inetd

Index: src/tests/usr.sbin/Makefile
diff -u src/tests/usr.sbin/Makefile:1.7 src/tests/usr.sbin/Makefile:1.8
--- src/tests/usr.sbin/Makefile:1.7	Sun Aug 29 09:54:18 2021
+++ src/tests/usr.sbin/Makefile	Sat Aug 26 05:27:14 2023
@@ -1,8 +1,9 @@
-# $NetBSD: Makefile,v 1.7 2021/08/29 09:54:18 christos Exp $
+# $NetBSD: Makefile,v 1.8 2023/08/26 05:27:14 riastradh Exp $
 .include <bsd.own.mk>
 
 TESTSDIR=       ${TESTSBASE}/usr.sbin
 
+TESTS_SUBDIRS+= certctl
 TESTS_SUBDIRS+= cpuctl
 TESTS_SUBDIRS+= execsnoop
 TESTS_SUBDIRS+= inetd

Index: src/usr.sbin/Makefile
diff -u src/usr.sbin/Makefile:1.291 src/usr.sbin/Makefile:1.292
--- src/usr.sbin/Makefile:1.291	Mon Jun  6 10:56:29 2022
+++ src/usr.sbin/Makefile	Sat Aug 26 05:27:15 2023
@@ -1,11 +1,11 @@
-#	$NetBSD: Makefile,v 1.291 2022/06/06 10:56:29 nia Exp $
+#	$NetBSD: Makefile,v 1.292 2023/08/26 05:27:15 riastradh Exp $
 #	from: @(#)Makefile	5.20 (Berkeley) 6/12/93
 
 .include <bsd.own.mk>
 
 SUBDIR=	ac accton acpitools altq apm apmd arp autofs \
 	bad144 bootp bta2dpd btattach btconfig btdevctl bthcid btpand catman \
-	chroot cnwctl cpuctl crash \
+	certctl chroot cnwctl cpuctl crash \
 	dev_mkdb diskpart dumpfs dumplfs \
 	edquota eeprom envstat etcupdate extattrctl \
 	flashctl fssconfig fstyp fusermount fwctl \

Added files:

Index: src/tests/usr.sbin/certctl/Makefile
diff -u /dev/null src/tests/usr.sbin/certctl/Makefile:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/Makefile	Sat Aug 26 05:27:14 2023
@@ -0,0 +1,10 @@
+#	$NetBSD: Makefile,v 1.1 2023/08/26 05:27:14 riastradh Exp $
+#
+
+.include "Makefile.inc"         # TESTSDIR
+
+SUBDIR=		certs1 certs2 certs3 certs4
+
+TESTS_SH=	t_certctl
+
+.include <bsd.test.mk>
Index: src/tests/usr.sbin/certctl/Makefile.inc
diff -u /dev/null src/tests/usr.sbin/certctl/Makefile.inc:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/Makefile.inc	Sat Aug 26 05:27:14 2023
@@ -0,0 +1,6 @@
+#	$NetBSD: Makefile.inc,v 1.1 2023/08/26 05:27:14 riastradh Exp $
+#
+
+TESTSDIR=	${TESTSBASE}/usr.sbin/certctl
+
+.include <bsd.own.mk>
Index: src/tests/usr.sbin/certctl/t_certctl.sh
diff -u /dev/null src/tests/usr.sbin/certctl/t_certctl.sh:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/t_certctl.sh	Sat Aug 26 05:27:14 2023
@@ -0,0 +1,333 @@
+#!/bin/sh
+
+#	$NetBSD: t_certctl.sh,v 1.1 2023/08/26 05:27:14 riastradh Exp $
+#
+# Copyright (c) 2023 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``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 FOUNDATION OR CONTRIBUTORS
+# 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.
+#
+
+CERTCTL="certctl -C certs.conf -c certs -u untrusted"
+
+# setupconf <subdir>...
+#
+#	Create certs/ and set up certs.conf to search the specified
+#	subdirectories of the source directory.
+#
+setupconf()
+{
+	local sep subdir dir
+
+	mkdir certs
+	cat <<EOF >certs.conf
+netbsd-certctl 20230816
+
+# comment at line start
+	# comment not at line start, plus some intentional whitespace
+   
+# THE WHITESPACE ABOVE IS INTENTIONAL, DO NOT DELETE
+EOF
+	# Start with a continuation line separator; then switch to
+	# non-continuation lines.
+	sep=$(printf ' \\\n\t')
+	for subdir; do
+		dir=$(atf_get_srcdir)/$subdir
+		cat <<EOF >>certs.conf
+path$sep$(printf '%s' "$dir" | vis -M)
+EOF
+		sep=' '
+	done
+}
+
+# check_empty
+#
+#	Verify the certs directory is empty after dry runs or after
+#	clearing the directory.
+#
+check_empty()
+{
+	local why
+
+	why=${1:-dry run}
+	for x in certs/*; do
+		if [ -e "$x" -o -h "$x" ]; then
+			atf_fail "certs/ should be empty after $why"
+		fi
+	done
+}
+
+# check_nonempty
+#
+#	Verify the certs directory is nonempty.
+#
+check_nonempty()
+{
+	for x in certs/*.0; do
+		test -e "$x" && test -h "$x" && return
+	done
+	atf_fail "certs/ should be nonempty"
+}
+
+# checks <certsN>...
+#
+#	Run various checks with certctl.
+#
+checks()
+{
+	local certs1 diginotar_base diginotar diginotar_hash subdir srcdir
+
+	certs1=$(atf_get_srcdir)/certs1
+	diginotar_base=Explicitly_Distrust_DigiNotar_Root_CA.pem
+	diginotar=$certs1/$diginotar_base
+	diginotar_hash=$(openssl x509 -hash -noout <$diginotar)
+
+	# Do a dry run of rehash and make sure the directory is still
+	# empty.
+	atf_check -s exit:0 $CERTCTL -n rehash
+	check_empty
+
+	# Distrust and trust one CA, as a dry run.  The trust should
+	# fail because it's not currently distrusted.
+	atf_check -s exit:0 $CERTCTL -n untrust "$diginotar"
+	check_empty
+	atf_check -s not-exit:0 -e match:currently \
+	    $CERTCTL -n trust "$diginotar"
+	check_empty
+
+	# Do a real rehash, not a dry run.
+	atf_check -s exit:0 $CERTCTL rehash
+
+	# Make sure all the certificates are trusted.
+	for subdir; do
+		case $subdir in
+		/*)	srcdir=$subdir;;
+		*)	srcdir=$(atf_get_srcdir)/$subdir;;
+		esac
+		for cert in "$srcdir"/*.pem; do
+			# Verify the certificate is linked by its base name.
+			certbase=$(basename "$cert")
+			atf_check -s exit:0 -o inline:"$cert" \
+			    readlink -n "certs/$certbase"
+
+			# Verify the certificate is linked by a hash.
+			hash=$(openssl x509 -hash -noout <$cert)
+			counter=0
+			found=false
+			while [ $counter -lt 10 ]; do
+				if cmp -s "certs/$hash.$counter" "$cert"; then
+					found=true
+					break
+				fi
+				counter=$((counter + 1))
+			done
+			if ! $found; then
+				atf_fail "missing $cert"
+			fi
+
+			# Delete both links.
+			rm "certs/$certbase"
+			rm "certs/$hash.$counter"
+		done
+	done
+
+	# Verify the certificate bundle is there and delete it.
+	#
+	# XXX Verify its content.
+	atf_check -s exit:0 test -f certs/ca-certificates.crt
+	atf_check -s exit:0 test ! -h certs/ca-certificates.crt
+	rm certs/ca-certificates.crt
+
+	# Make sure after deleting everything there's nothing left.
+	check_empty "removing all expected certificates"
+
+	# Distrust, trust, and re-distrust one CA, and verify that it
+	# ceases to appear, reappears, and again ceases to appear.
+	# (This one has no subject hash collisions to worry about, so
+	# we hard-code the `.0' suffix.)
+	atf_check -s exit:0 $CERTCTL untrust "$diginotar"
+	atf_check -s exit:0 test -e "untrusted/$diginotar_base"
+	atf_check -s exit:0 test -h "untrusted/$diginotar_base"
+	atf_check -s exit:0 test ! -e "certs/$diginotar_base"
+	atf_check -s exit:0 test ! -h "certs/$diginotar_base"
+	atf_check -s exit:0 test ! -e "certs/$diginotar_hash.0"
+	atf_check -s exit:0 test ! -h "certs/$diginotar_hash.0"
+	check_nonempty
+
+	atf_check -s exit:0 $CERTCTL trust "$diginotar"
+	atf_check -s exit:0 test ! -e "untrusted/$diginotar_base"
+	atf_check -s exit:0 test ! -h "untrusted/$diginotar_base"
+	atf_check -s exit:0 test -e "certs/$diginotar_base"
+	atf_check -s exit:0 test -h "certs/$diginotar_base"
+	atf_check -s exit:0 test -e "certs/$diginotar_hash.0"
+	atf_check -s exit:0 test -h "certs/$diginotar_hash.0"
+	rm "certs/$diginotar_base"
+	rm "certs/$diginotar_hash.0"
+	check_nonempty
+
+	atf_check -s exit:0 $CERTCTL untrust "$diginotar"
+	atf_check -s exit:0 test -e "untrusted/$diginotar_base"
+	atf_check -s exit:0 test -h "untrusted/$diginotar_base"
+	atf_check -s exit:0 test ! -e "certs/$diginotar_base"
+	atf_check -s exit:0 test ! -h "certs/$diginotar_base"
+	atf_check -s exit:0 test ! -e "certs/$diginotar_hash.0"
+	atf_check -s exit:0 test ! -h "certs/$diginotar_hash.0"
+	check_nonempty
+}
+
+atf_test_case empty
+empty_head()
+{
+	atf_set "descr" "Test empty certificates store"
+}
+empty_body()
+{
+	setupconf		# no directories
+	check_empty "empty cert path"
+	atf_check -s exit:0 $CERTCTL -n rehash
+	check_empty
+	atf_check -s exit:0 $CERTCTL rehash
+	atf_check -s exit:0 test -f certs/ca-certificates.crt
+	atf_check -s exit:0 test \! -h certs/ca-certificates.crt
+	atf_check -s exit:0 test \! -s certs/ca-certificates.crt
+	atf_check -s exit:0 rm certs/ca-certificates.crt
+	check_empty "empty cert path"
+}
+
+atf_test_case onedir
+onedir_head()
+{
+	atf_set "descr" "Test one certificates directory"
+}
+onedir_body()
+{
+	setupconf certs1
+	checks certs1
+}
+
+atf_test_case twodir
+twodir_head()
+{
+	atf_set "descr" "Test two certificates directories"
+}
+twodir_body()
+{
+	setupconf certs1 certs2
+	checks certs1 certs2
+}
+
+atf_test_case collidehash
+collidehash_head()
+{
+	atf_set "descr" "Test colliding hashes"
+}
+collidehash_body()
+{
+	# certs3 has two certificates with the same subject hash
+	setupconf certs1 certs3
+	checks certs1 certs3
+}
+
+atf_test_case collidebase
+collidebase_head()
+{
+	atf_set "descr" "Test colliding base names"
+}
+collidebase_body()
+{
+	# certs1 and certs4 both have DigiCert_Global_Root_CA.pem,
+	# which should cause list and rehash to fail and mention
+	# duplicates.
+	setupconf certs1 certs4
+	atf_check -s not-exit:0 -o ignore -e match:duplicate $CERTCTL list
+	atf_check -s not-exit:0 -o ignore -e match:duplicate $CERTCTL rehash
+}
+
+atf_test_case manual
+manual_head()
+{
+	atf_set "descr" "Test manual operation"
+}
+manual_body()
+{
+	local certs1 diginotar_base diginotar diginotar_hash
+
+	certs1=$(atf_get_srcdir)/certs1
+	diginotar_base=Explicitly_Distrust_DigiNotar_Root_CA.pem
+	diginotar=$certs1/$diginotar_base
+	diginotar_hash=$(openssl x509 -hash -noout <$diginotar)
+
+	setupconf certs1 certs2
+	cat <<EOF >>certs.conf
+manual
+EOF
+	touch certs/bogus.pem
+	ln -s bogus.pem certs/0123abcd.0
+
+	# Listing shouldn't mention anything in the certs/ cache.
+	atf_check -s exit:0 -o not-match:bogus $CERTCTL list
+	atf_check -s exit:0 -o not-match:bogus $CERTCTL untrusted
+
+	# Rehashing and changing the configuration should succeed, but
+	# mention `manual' in a warning message and should not touch
+	# the cache.
+	atf_check -s exit:0 -e match:manual $CERTCTL rehash
+	atf_check -s exit:0 -e match:manual $CERTCTL untrust "$diginotar"
+	atf_check -s exit:0 -e match:manual $CERTCTL trust "$diginotar"
+
+	# The files we created should still be there.
+	atf_check -s exit:0 test -f certs/bogus.pem
+	atf_check -s exit:0 test -h certs/0123abcd.0
+}
+
+atf_test_case evilpath
+evilpath_head()
+{
+	atf_set "descr" "Test certificate paths with evil characters"
+}
+evilpath_body()
+{
+	local evildir
+
+	evildir="$(printf 'evil\n.')"
+        evildir=${evildir%.}
+        mkdir "$evildir"
+
+        cp -p "$(atf_get_srcdir)/certs2"/*.pem "$evildir"/
+
+        setupconf certs1
+        cat <<EOF >>certs.conf
+path $(printf '%s' "$(pwd)/$evildir" | vis -M)
+EOF
+        checks certs1 "$(pwd)/$evildir"
+}
+
+atf_init_test_cases()
+{
+	atf_add_test_case collidebase
+	atf_add_test_case collidehash
+	atf_add_test_case empty
+	atf_add_test_case evilpath
+	atf_add_test_case manual
+	atf_add_test_case onedir
+	atf_add_test_case twodir
+}

Index: src/tests/usr.sbin/certctl/certs1/DigiCert_Global_Root_CA.pem
diff -u /dev/null src/tests/usr.sbin/certctl/certs1/DigiCert_Global_Root_CA.pem:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/certs1/DigiCert_Global_Root_CA.pem	Sat Aug 26 05:27:14 2023
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
+CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
+nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
+43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
+T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
+gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
+BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
+TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
+DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
+hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
+06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
+PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
+YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----
Index: src/tests/usr.sbin/certctl/certs1/Explicitly_Distrust_DigiNotar_Root_CA.pem
diff -u /dev/null src/tests/usr.sbin/certctl/certs1/Explicitly_Distrust_DigiNotar_Root_CA.pem:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/certs1/Explicitly_Distrust_DigiNotar_Root_CA.pem	Sat Aug 26 05:27:14 2023
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFijCCA3KgAwIBAgIQD////////////////////zANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJOTDESMBAGA1UEChMJRGlnaU5vdGFyMRowGAYDVQQDExFEaWdp
+Tm90YXIgUm9vdCBDQTEgMB4GCSqGSIb3DQEJARYRaW5mb0BkaWdpbm90YXIubmww
+HhcNMDcwNzI3MTcxOTM3WhcNMjUwMzMxMTgxOTIyWjBfMQswCQYDVQQGEwJOTDES
+MBAGA1UEChMJRGlnaU5vdGFyMRowGAYDVQQDExFEaWdpTm90YXIgUm9vdCBDQTEg
+MB4GCSqGSIb3DQEJARYRaW5mb0BkaWdpbm90YXIubmwwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCssFjBAL3YIQgLK5r+blYwBZ8bd5AQQVzDDYcRd46B
+8cp86Yxq7Th0Nbva3/m7wAk3tJZzgX0zGpg595NvlX89ubF1h7pRSOiLcD6VBMXY
+tsMW2YiwsYcdcNqGtA8Ui3rPENF0NqISe3eGSnnme98CEWilToauNFibJBN4ViIl
+HgGLS1Fx+4LMWZZpiFpoU8W5DQI3y0u8ZkqQfioLBQftFl9VkHXYRskbg+IIvvEj
+zJkd1ioPgyAVWCeCLvriIsJJsbkBgWqdbZ1Ad2h2TiEqbYRAhU52mXyC8/O3AlnU
+JgEbjt+tUwbRrhjd4rI6y9eIOI6sWym5GdOY+RgDz0iChmYLG2kPyes4iHomGgVM
+ktck1JbyrFIto0fVUvY//s6EBnCmqj6i8rZWNBhXouSBbefK8GrTx5FrAoNBfBXv
+a5pkXuPQPOWx63tdhvvL5ndJzaNl3Pe5nLjkC1+Tz8wwGjIczhxjlaX56uF0i57p
+K6kwe6AYHw4YC+VbqdPRbB4HZ4+RS6mKvNJmqpMBiLKR+jFc1abBUggJzQpjotMi
+puih2TkGl/VujQKQjBR7P4DNG5y6xFhyI6+2Vp/GekIzKQc/gsnmHwUNzUwoNovT
+yD4cxojvXu6JZOkd69qJfjKmadHdzIif0dDJZiHcBmfFlHqabWJMfczgZICynkeO
+owIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV
+HQ4EFgQUiGi/4I41xDs4a2L3KDuEgcgM100wDQYJKoZIhvcNAQEFBQADggIBADsC
+jcs8MOhuoK3yc7NfniUTBAXT9uOLuwt5zlPe5JbF0a9zvNXD0EBVfEB/zRtfCdXy
+fJ9oHbtdzno5wozWmHvFg1Wo1X1AyuAe94leY12hE8JdiraKfADzI8PthV9xdvBo
+Y6pFITlIYXg23PFDk9Qlx/KAZeFTAnVR/Ho67zerhChXDNjU1JlWbOOi/lmEtDHo
+M/hklJRRl6s5xUvt2t2AC298KQ3EjopyDedTFLJgQT2EkTFoPSdE2+Xe9PpjRchM
+Ppj1P0G6Tss3DbpmmPHdy59c91Q2gmssvBNhl0L4eLvMyKKfyvBovWsdst+Nbwed
+2o5nx0ceyrm/KkKRt2NTZvFCo+H0Wk1Ya7XkpDOtXHAd3ODy63MUkZoDweoAZbwH
+/M8SESIsrqC9OuCiKthZ6SnTGDWkrBFfGbW1G/8iSlzGeuQX7yCpp/Q/rYqnmgQl
+nQ7KN+ZQ/YxCKQSa7LnPS3K94gg2ryMvYuXKAdNw23yCIywWMQzGNgeQerEfZ1jE
+O1hZibCMjFCz2IbLaKPECudpSyDOwR5WS5WpI2jYMNjD67BVUc3l/Su49bsRn1NU
+9jQZjHkJNsphFyUXC4KYcwx3dMPVDceoEkzHp1RxRy4sGn3J4ys7SN4nhKdjNrN9
+j6BkOSQNPXuHr2ZcdBtLc7LljPCGmbjlxd+Ewbfr
+-----END CERTIFICATE-----
Index: src/tests/usr.sbin/certctl/certs1/Makefile
diff -u /dev/null src/tests/usr.sbin/certctl/certs1/Makefile:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/certs1/Makefile	Sat Aug 26 05:27:14 2023
@@ -0,0 +1,10 @@
+#	$NetBSD: Makefile,v 1.1 2023/08/26 05:27:14 riastradh Exp $
+#
+
+FILESDIR=	${TESTSDIR}/certs1
+
+FILES+=		DigiCert_Global_Root_CA.pem
+FILES+=		Explicitly_Distrust_DigiNotar_Root_CA.pem
+
+.include <bsd.files.mk>
+.include <bsd.inc.mk>

Index: src/tests/usr.sbin/certctl/certs2/GTS_Root_R1.pem
diff -u /dev/null src/tests/usr.sbin/certctl/certs2/GTS_Root_R1.pem:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/certs2/GTS_Root_R1.pem	Sat Aug 26 05:27:14 2023
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
+MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo
+27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w
+Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw
+TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl
+qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH
+szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8
+Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk
+MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
+wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p
+aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN
+VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID
+AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb
+C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
+QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy
+h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4
+7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J
+ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef
+MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/
+Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT
+6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ
+0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm
+2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb
+bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
+-----END CERTIFICATE-----
Index: src/tests/usr.sbin/certctl/certs2/GlobalSign_Root_CA_-_R3.pem
diff -u /dev/null src/tests/usr.sbin/certctl/certs2/GlobalSign_Root_CA_-_R3.pem:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/certs2/GlobalSign_Root_CA_-_R3.pem	Sat Aug 26 05:27:14 2023
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
+MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
+A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
+RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
+gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
+KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
+QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
+XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
+DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
+LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
+RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
+jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
+6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
+mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
+Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
+WD9f
+-----END CERTIFICATE-----
Index: src/tests/usr.sbin/certctl/certs2/Makefile
diff -u /dev/null src/tests/usr.sbin/certctl/certs2/Makefile:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/certs2/Makefile	Sat Aug 26 05:27:14 2023
@@ -0,0 +1,10 @@
+#	$NetBSD: Makefile,v 1.1 2023/08/26 05:27:14 riastradh Exp $
+#
+
+FILESDIR=	${TESTSDIR}/certs2
+
+FILES+=		GTS_Root_R1.pem
+FILES+=		GlobalSign_Root_CA_-_R3.pem
+
+.include <bsd.files.mk>
+.include <bsd.inc.mk>

Index: src/tests/usr.sbin/certctl/certs3/Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.1.pem
diff -u /dev/null src/tests/usr.sbin/certctl/certs3/Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.1.pem:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/certs3/Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.1.pem	Sat Aug 26 05:27:14 2023
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE
+BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
+cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1
+MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
+Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
+thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
+cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
+L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
+NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
+X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
+m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
+Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
+EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
+KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
+6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
+OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc
+tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd
+IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j
+b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC
+AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw
+ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m
+iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF
+Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ
+hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P
+Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE
+EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV
+1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t
+CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR
+5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw
+f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9
+ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK
+GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV
+-----END CERTIFICATE-----
Index: src/tests/usr.sbin/certctl/certs3/Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.pem
diff -u /dev/null src/tests/usr.sbin/certctl/certs3/Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.pem:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/certs3/Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.pem	Sat Aug 26 05:27:14 2023
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE
+BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
+cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy
+MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
+Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
+thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
+cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
+L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
+NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
+X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
+m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
+Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
+EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
+KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
+6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
+OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD
+VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD
+VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
+cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv
+ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl
+AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF
+661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9
+am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1
+ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481
+PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS
+3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k
+SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF
+3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM
+ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g
+StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz
+Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB
+jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
+-----END CERTIFICATE-----
Index: src/tests/usr.sbin/certctl/certs3/Makefile
diff -u /dev/null src/tests/usr.sbin/certctl/certs3/Makefile:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/certs3/Makefile	Sat Aug 26 05:27:14 2023
@@ -0,0 +1,10 @@
+#	$NetBSD: Makefile,v 1.1 2023/08/26 05:27:14 riastradh Exp $
+#
+
+FILESDIR=	${TESTSDIR}/certs3
+
+FILES+=		Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.1.pem
+FILES+=		Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.pem
+
+.include <bsd.files.mk>
+.include <bsd.inc.mk>

Index: src/tests/usr.sbin/certctl/certs4/AC_RAIZ_FNMT-RCM.pem
diff -u /dev/null src/tests/usr.sbin/certctl/certs4/AC_RAIZ_FNMT-RCM.pem:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/certs4/AC_RAIZ_FNMT-RCM.pem	Sat Aug 26 05:27:14 2023
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx
+CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ
+WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ
+BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG
+Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/
+yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf
+BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz
+WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF
+tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z
+374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC
+IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL
+mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7
+wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS
+MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2
+ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet
+UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H
+YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3
+LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD
+nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1
+RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM
+LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf
+77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N
+JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm
+fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp
+6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp
+1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B
+9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok
+RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv
+uu8wd+RU4riEmViAqhOLUTpPSPaLtrM=
+-----END CERTIFICATE-----
Index: src/tests/usr.sbin/certctl/certs4/DigiCert_Global_Root_CA.pem
diff -u /dev/null src/tests/usr.sbin/certctl/certs4/DigiCert_Global_Root_CA.pem:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/certs4/DigiCert_Global_Root_CA.pem	Sat Aug 26 05:27:15 2023
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
+CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
+nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
+43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
+T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
+gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
+BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
+TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
+DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
+hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
+06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
+PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
+YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----
Index: src/tests/usr.sbin/certctl/certs4/Makefile
diff -u /dev/null src/tests/usr.sbin/certctl/certs4/Makefile:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/tests/usr.sbin/certctl/certs4/Makefile	Sat Aug 26 05:27:15 2023
@@ -0,0 +1,10 @@
+#	$NetBSD: Makefile,v 1.1 2023/08/26 05:27:15 riastradh Exp $
+#
+
+FILESDIR=	${TESTSDIR}/certs4
+
+FILES+=		AC_RAIZ_FNMT-RCM.pem
+FILES+=		DigiCert_Global_Root_CA.pem
+
+.include <bsd.files.mk>
+.include <bsd.inc.mk>

Index: src/usr.sbin/certctl/Makefile
diff -u /dev/null src/usr.sbin/certctl/Makefile:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/usr.sbin/certctl/Makefile	Sat Aug 26 05:27:15 2023
@@ -0,0 +1,10 @@
+#	$NetBSD: Makefile,v 1.1 2023/08/26 05:27:15 riastradh Exp $
+#
+
+MAN=		certctl.8
+SCRIPTS=	certctl.sh
+
+FILESDIR=	/etc/openssl
+CONFIGFILES=	certs.conf
+
+.include <bsd.prog.mk>
Index: src/usr.sbin/certctl/certctl.8
diff -u /dev/null src/usr.sbin/certctl/certctl.8:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/usr.sbin/certctl/certctl.8	Sat Aug 26 05:27:15 2023
@@ -0,0 +1,337 @@
+.\"	$NetBSD: certctl.8,v 1.1 2023/08/26 05:27:15 riastradh Exp $
+.\"
+.\" Copyright (c) 2023 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``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 FOUNDATION OR CONTRIBUTORS
+.\" 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.
+.\"
+.Dd August 16, 2023
+.Dt CERTCTL 8
+.Os
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.Sh NAME
+.Nm certctl
+.Nd configure OpenSSL certificate trust anchors
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.Sh SYNOPSIS
+.Nm
+.Op Fl nv
+.Op Fl C Ar config
+.Op Fl c Ar certsdir
+.Op Fl u Ar distrustdir
+.Ar cmd
+.Op Ar args...
+.\""""""""""""""""""
+.Nm
+.Oo Ar options Oc Cm list
+.Nm
+.Oo Ar options Oc Cm rehash
+.Nm
+.Oo Ar options Oc Cm trust Ar cert
+.Nm
+.Oo Ar options Oc Cm untrust Ar cert
+.Nm
+.Oo Ar options Oc Cm untrusted
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.Sh DESCRIPTION
+The
+.Nm
+utility manages certificates used by OpenSSL-based applications as
+trust anchors for certificate validation in HTTPS or other purposes.
+.Nm
+allows configuring the set of certificates and persistently excluding
+individual certificates.
+.Pp
+For HTTPS certificate validation, OpenSSL applications typically
+use either a directory at
+.Pa /etc/openssl/certs
+of hashed certificates in PEM format, with names like
+.Pa "3513523f.0"
+used for lookup
+.Pq see Xr openssl_rehash 1 ,
+or a single-file bundle at
+.Pa /etc/openssl/certs/ca-certificates.crt
+concatenating all the certificates in PEM format.
+.Pp
+.Nm
+scans all directories in the certificate search path specified by the
+configuration file
+.Ar config
+.Pq default: Pa /etc/openssl/certs.conf
+for files called
+.Pa *.cer ,
+.Pa *.crt ,
+or
+.Pa *.pem
+in PEM format, except for those that have been excluded by
+.Nm Cm untrust ,
+and keeps
+.Ar certsdir
+.Pq default: Pa /etc/openssl/certs
+populated with symlinks to them.
+.Pp
+.Nm
+treats
+.Ar config
+and
+.Ar distrustdir
+as configuration, and
+.Ar certsdir
+strictly as a cache that can be safely deleted and rebuilt with
+.Nm Cm rehash .
+.Nm
+can also be instructed not to touch
+.Ar certsdir
+at all by putting
+.Cm manual
+in
+.Ar config .
+.
+.\""""""""""""""""""""""""""""""""""""""
+.Ss Commands
+.Bl -tag -width Cm
+.\""""""""""""""""""
+.It Cm list
+List absolute paths to trusted certificates, one per line, in
+.Xr vis 1
+format to encode any shell metacharacters, that
+.Nm Cm rehash
+would use to populate the
+.Ar certsdir
+cache.
+.\""""""""""""""""""
+.It Cm rehash
+Populate
+.Ar certsdir
+with all trusted certificates, excluding any from
+.Nm Cm untrust .
+.\""""""""""""""""""
+.It Cm trust Ar cert
+Allow
+.Ar cert
+to be included in the certificate cache if it is in the certificate
+search path, and rehash the certificate cache.
+In other words, reverse the persistent effect of
+.Nm Cm untrust Ar cert .
+.Pp
+.Ar cert
+must be the full absolute path to a certificate that has been excluded
+by
+.Nm Cm untrust Ar cert .
+.Pp
+This does not add a new certificate which is not in the search path.
+To do that, you can create a directory to hold it and put that
+directory in the search path.
+.\""""""""""""""""""
+.It Cm untrust Ar cert
+Persistently prevent
+.Ar
+from being included in the certificate cache, and rehash the
+certificate cache.
+.Pp
+.Ar cert
+must be the full absolute path to a certificate that is in the
+certificate search path.
+.\""""""""""""""""""
+.It Cm untrusted
+List absolute paths to untrusted certificates, one per line, in
+.Xr vis 1
+format to encode any shell metacharacters, that have been excluded by
+.Nm Cm untrust
+so that
+.Nm Cm rehash
+will not put them in
+.Ar certsdir .
+.\""""""""""""""""""
+.El
+.\""""""""""""""""""""""""""""""""""""""
+.Ss Configuration file
+The configuration file is a plain text file of lines separated by
+.Tn US-ASCII
+line feeds.
+.Pp
+.Pp
+The first line must be:
+.Dl netbsd-certctl 20230816
+.Pp
+Lines with only whitespace, or whitespace followed by the comment
+character
+.Ql #
+are ignored.
+Each line has a directive and arguments separated by whitespace, and
+may be extended by
+.Ql \e
+to continuation lines.
+.Bl -tag -width Cm
+.\""""""""""""""""""
+.It Cm path Ar dir
+Add
+.Ar dir
+to the certificate search path.
+.Ar dir
+must be an absolute pathname,
+.Xr vis 3 Ns -encoded .
+.Pp
+All certificates must have unique base names across all directories
+in the certificate search path.
+.\""""""""""""""""""
+.It Cm manual
+Manual override.
+If specified,
+.Nm
+will
+.Em not
+modify
+.Ar certsdir ,
+but may still check consistency of the configuration when run and
+update
+.Ar distrustdir .
+.\""""""""""""""""""
+.El
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.Sh FILES
+.Bl -tag -width Pa
+.It Pa /etc/openssl/certs
+Default directory of hashed HTTPS CA certificates.
+.It Pa /etc/openssl/certs/ca-certificates.crt
+Default single-file HTTPS CA certificate bundle.
+.It Pa /etc/openssl/certs.conf
+Default configuration file for HTTPS CA certificates.
+.It Pa /etc/openssl/untrusted
+Default
+.Ar untrusted
+directory of excluded HTTPS CA certificates.
+.It Pa /usr/share/certs/mozilla/all
+All root CA certificates published by Mozilla, including untrustworthy
+certificates.
+.It Pa /usr/share/certs/mozilla/code
+All root CA certificates published by Mozilla for use in code-signing.
+.It Pa /usr/share/certs/mozilla/email
+All root CA certificates published by Mozilla for use in email
+authentication.
+.It Pa /usr/share/certs/mozilla/server
+All root CA certificates published by Mozilla for use in HTTPS server
+authentication.
+.El
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.Sh EXAMPLES
+Example configuration file
+.Pq Pa /etc/openssl/certs.conf :
+.Bd -literal -offset indent
+netbsd-certctl 20230816
+
+# Blank lines and comments are ignored.
+# Comments begin with a `#' sign.
+
+# Gather certificates from files called *.cer, *.crt, and *.pem
+# under these directories.
+path /usr/share/certs/mozilla/server
+path /usr/pkg/share/chromium-cacerts
+
+# If the next line is uncommented, certctl(8) will decline to
+# touch /etc/openssl/certs.
+#manual
+.Ed
+.Pp
+Exclude a certificate:
+.Bd -literal -offset indent
+$ certctl untrust /usr/share/certs/mozilla/server/GTS_Root_R1.pem
+.Ed
+.Pp
+There is no need to run
+.Nm Cm rehash
+explicitly after
+.Nm Cm untrust ,
+but if you do, the setting will persist.
+.Pp
+Rebuild the hashed certificate cache at
+.Pa /etc/myapplication/certs
+from
+.Pa /etc/myapplication/certs.conf
+and
+.Pa /etc/myapplication/untrusted :
+.Bd -literal -offset indent
+$ certctl -c /etc/myapplication/certs \e
+        -C /etc/myapplication/certs.conf \e
+        -u /etc/myapplication/untrusted
+.Ed
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.Sh DIAGNOSTICS
+.Ex -std
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.Sh COMPATIBILITY
+The
+.Nm
+utility is mostly compatible with a utility of the same name in
+.Fx .
+Differences:
+.Bl -enum
+.\""""""""""""""""""
+.It
+.Fx Nm
+supports destdir/metalog handling;
+.Nx Nm
+does not.
+.\""""""""""""""""""
+.It
+.Fx Nm
+treats
+.Pa /etc/ssl/certs
+and
+.Pa /etc/ssl/untrusted
+both as configuration
+.Em and
+as caches;
+.Nx Nm
+treats
+.Pa /etc/openssl/certs.conf
+and
+.Pa /etc/openssl/untrusted
+as configuration, and treats
+.Pa /etc/openssl/certs
+strictly as a cache.
+.Fx Nm
+will forget any
+.Nm Cm untrust
+settings on
+.Nm Cm rehash ,
+but
+.Nx Nm
+will remember them.
+.\""""""""""""""""""
+.It
+.Fx Nm
+takes configuration through environment variables;
+.Nx Nm
+takes configuration through a file and command-line arguments.
+.El
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.Sh SEE ALSO
+.Xr openssl 1 ,
+.Xr openssl_rehash 1
+.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.Sh HISTORY
+.Nm
+first appeared in
+.Nx 10.0 .
+A utility of the same name previously appeared in
+.Fx 12.2 .
Index: src/usr.sbin/certctl/certctl.sh
diff -u /dev/null src/usr.sbin/certctl/certctl.sh:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/usr.sbin/certctl/certctl.sh	Sat Aug 26 05:27:15 2023
@@ -0,0 +1,658 @@
+#!/bin/sh
+
+#	$NetBSD: certctl.sh,v 1.1 2023/08/26 05:27:15 riastradh Exp $
+#
+# Copyright (c) 2023 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``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 FOUNDATION OR CONTRIBUTORS
+# 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 -o pipefail
+set -Ceu
+
+progname=$(basename -- "$0")
+
+### Options and arguments
+
+usage()
+{
+	exec >&2
+	printf 'Usage: %s %s\n' \
+	    "$progname" \
+	    "[-nv] [-C <config>] [-c <certsdir>] [-u <untrusted>]"
+	printf '               <cmd> <args>...\n'
+	printf '       %s list\n' "$progname"
+	printf '       %s rehash\n' "$progname"
+	printf '       %s trust <cert>\n' "$progname"
+	printf '       %s untrust <cert>\n' "$progname"
+	printf '       %s untrusted\n' "$progname"
+	exit 1
+}
+
+certsdir=/etc/openssl/certs
+config=/etc/openssl/certs.conf
+distrustdir=/etc/openssl/untrusted
+nflag=false			# dry run
+vflag=false			# verbose
+
+# Options used by FreeBSD:
+#
+#	-D destdir
+#	-M metalog
+#	-U		(unprivileged)
+#	-d distbase
+#
+while getopts C:c:nu:v f; do
+	case $f in
+	C)	config=$OPTARG;;
+	c)	certsdir=$OPTARG;;
+	n)	nflag=true;;
+	u)	distrustdir=$OPTARG;;
+	v)	vflag=true;;
+	\?)	usage;;
+	esac
+done
+shift $((OPTIND - 1))
+
+if [ $# -lt 1 ]; then
+	usage
+fi
+cmd=$1
+
+### Global state
+
+config_paths=
+config_manual=false
+tmpfile=
+
+# If tmpfile is set to nonempty, clean it up on exit.
+
+trap 'test -n "$tmpfile" && rm -f "$tmpfile"' EXIT HUP INT TERM
+
+### Subroutines
+
+# error <msg> ...
+#
+#	Print an error message to stderr.
+#
+#	Does not exit the process.
+#
+error()
+{
+	echo "$progname:" "$@" >&2
+}
+
+# run <cmd> <args>...
+#
+#	Print a command if verbose, and run it unless it's a dry run.
+#
+run()
+{
+	local t q cmdline
+
+	if $vflag; then	       # print command if verbose
+		for t; do
+			case $t in
+			''|*[^[:alnum:]+,-./:=_@]*)
+				# empty or unsafe -- quotify
+				;;
+			*)
+				# nonempty and safe-only -- no quotify
+				cmdline="${cmdline:+$cmdline }$t"
+				continue
+				;;
+			esac
+			q=$(printf '%s' "$t" | sed -e "s/'/'\\\''/g'")
+			cmdline="${cmdline:+$cmdline }'$q'"
+		done
+		printf '%s\n' "$cmdline"
+	fi
+	if ! $nflag; then	# skip command if dry run
+		"$@"
+	fi
+}
+
+# configure
+#
+#	Parse the configuration file, initializing config_*.
+#
+configure()
+{
+	local lineno status formatok vconfig line contline op path vpath vop
+
+	# Count line numbers, record a persistent error status to
+	# return at the end, and record whether we got a format line.
+	lineno=0
+	status=0
+	formatok=false
+
+	# vis the config name for terminal-safe error messages.
+	vconfig=$(printf '%s' "$config" | vis -M)
+
+	# Read and process each line of the config file.
+	while read -r line; do
+		lineno=$((lineno + 1))
+
+		# If the line ends in an odd number of backslashes, it
+		# has a continuation line, so read on.
+		while expr "$line" : '^\(\\\\\)*\\' >/dev/null ||
+		    expr "$line" : '^.*[^\\]\(\\\\\)*\\$' >/dev/null; do
+			if ! read -r contline; then
+				error "$vconfig:$lineno: premature end of file"
+				return 1
+			fi
+			line="$line$contline"
+		done
+
+		# Skip blank lines and comments.
+		case $line in
+		''|'#'*)
+			continue
+			;;
+		esac
+
+		# Require the first non-blank/comment line to identify
+		# the config file format.
+		if ! $formatok; then
+			if [ "$line" = "netbsd-certctl 20230816" ]; then
+				formatok=true
+				continue
+			else
+				error "$vconfig:$lineno: missing format line"
+				status=1
+				break
+			fi
+		fi
+
+		# Split the line into words and dispatch on the first.
+		set -- $line
+		op=$1
+		case $op in
+		manual)
+			config_manual=true
+			;;
+		path)
+			if [ $# -lt 2 ]; then
+				error "$vconfig:$lineno: missing path"
+				status=1
+				continue
+			fi
+			if [ $# -gt 3 ]; then
+				error "$vconfig:$lineno: excess args"
+				status=1
+				continue
+			fi
+
+			# Unvis the path.  Hack: if the user has had
+			# the audacity to choose a path ending in
+			# newlines, prevent the shell from consuming
+			# them so we don't choke on their subterfuge.
+			path=$(printf '%s.' "$2" | unvis)
+			path=${path%.}
+
+			# Ensure the path is absolute.  It is unclear
+			# what directory it should be relative to if
+			# not.
+			case $path in
+			/*)
+				;;
+			*)
+				error "$vconfig:$lineno:" \
+				    "relative path forbidden"
+				status=1
+				continue
+				;;
+			esac
+
+			# Record the vis-encoded path in a
+			# space-separated list.
+			vpath=$(printf '%s' "$path" | vis -M)
+			config_paths="$config_paths $vpath"
+			;;
+		*)
+			vop=$(printf '%s' "$op" | vis -M)
+			error "$vconfig:$lineno: unknown command: $vop"
+			;;
+		esac
+	done <$config
+
+	return $status
+}
+
+# list_default_trusted
+#
+#	List the vis-encoded certificate paths and their base names,
+#	separated by a space, for the certificates that are trusted by
+#	default according to the configuration.
+#
+#	No order guaranteed; caller must sort.
+#
+list_default_trusted()
+{
+	local vpath path cert base vcert vbase
+
+	for vpath in $config_paths; do
+		path=$(printf '%s.' "$vpath" | unvis)
+		path=${path%.}
+
+		# Enumerate the .pem, .cer, and .crt files.
+		for cert in "$path"/*.pem "$path"/*.cer "$path"/*.crt; do
+			# vis the certificate path.
+			vcert=$(printf '%s' "$cert" | vis -M)
+
+			# If the file doesn't exist, then either:
+			#
+			# (a) it's a broken symlink, so fail;
+			# or
+			# (b) the shell glob failed to match,
+			#     so ignore it and move on.
+			if [ ! -e "$cert" ]; then
+				if [ -h "$cert" ]; then
+					error "broken symlink: $vcert"
+					status=1
+				fi
+				continue
+			fi
+
+			# Print the vis-encoded absolute path to the
+			# certificate and base name on a single line.
+			vbase=$(basename -- "$vcert.")
+			vbase=${vbase%.}
+			printf '%s %s\n' "$vcert" "$vbase"
+		done
+	done
+}
+
+# list_distrusted
+#
+#	List the vis-encoded certificate paths and their base names,
+#	separated by a space, for the certificates that have been
+#	distrusted by the user.
+#
+#	No order guaranteed; caller must sort.
+#
+list_distrusted()
+{
+	local status link vlink cert vcert
+
+	status=0
+
+	for link in "$distrustdir"/*; do
+		# vis the link for terminal-safe error messages.
+		vlink=$(printf '%s' "$link" | vis -M)
+
+		# The distrust directory must only have symlinks to
+		# certificates.  If we find a non-symlink, print a
+		# warning and arrange to fail.
+		if [ ! -h "$link" ]; then
+			if [ ! -e "$link" ] && \
+			    [ "$link" = "$distrustdir/*" ]; then
+				# Shell glob matched nothing -- just
+				# ignore it.
+				break
+			fi
+			error "distrusted non-symlink: $vlink"
+			status=1
+			continue
+		fi
+
+		# Read the target of the symlink, nonrecursively.  If
+		# the user has had the audacity to make a symlink whose
+		# target ends in newline, prevent the shell from
+		# consuming them so we don't choke on their subterfuge.
+		cert=$(readlink -n -- "$link" && printf .)
+		cert=${cert%.}
+
+		# Warn if the target is relative.  Although it is clear
+		# what directory it would be relative to, there might
+		# be issues with canonicalization.
+		case $cert in
+		/*)
+			;;
+		*)
+			vlink=$(printf '%s' "$link" | vis -M)
+			vcert=$(printf '%s' "$cert" | vis -M)
+			error "distrusted relative symlink: $vlink -> $vcert"
+			;;
+		esac
+
+		# Print the vis-encoded absolute path to the
+		# certificate and base name on a single line.
+		vcert=$(printf '%s' "$cert" | vis -M)
+		vbase=$(basename -- "$vcert.")
+		vbase=${vbase%.}
+		printf '%s %s\n' "$vcert" "$vbase"
+	done
+
+	return $status
+}
+
+# list_trusted
+#
+#	List the trusted certificates, excluding the distrusted one, as
+#	one vis(3) line per certificate.  Reject duplicate base names,
+#	since we will be creating symlinks to the same base names in
+#	the certsdir.  Sorted lexicographically by vis-encoding.
+#
+list_trusted()
+{
+
+	# XXX Use dev/ino to match files instead of symlink targets?
+
+	{
+		list_default_trusted \
+		| while read -r vcert vbase; do
+			printf 'trust %s %s\n' "$vcert" "$vbase"
+		done
+
+		# XXX Find a good way to list the default-untrusted
+		# certificates, so if you have already distrusted one
+		# and it is removed from default-trust on update,
+		# nothing warns about this.
+
+		# list_default_untrusted \
+		# | while read -r vcert vbase; do
+		# 	printf 'distrust %s %s\n' "$vcert" "$vbase"
+		# done
+
+		list_distrusted \
+		| while read -r vcert vbase; do
+			printf 'distrust %s %s\n' "$vcert" "$vbase"
+		done
+	} | awk -v progname="$progname" '
+		BEGIN			{ status = 0 }
+		$1 == "trust" && $3 in trust && $2 != trust[$3] {
+			printf "%s: duplicate base name %s\n  %s\n  %s\n", \
+			    progname, $3, trust[$3], $2 >"/dev/stderr"
+			status = 1
+			next
+		}
+		$1 == "trust"		{ trust[$3] = $2 }
+		$1 == "distrust" && !trust[$3] && !distrust[$3] {
+			printf "%s: distrusted certificate not found: %s\n", \
+			    progname, $3 >"/dev/stderr"
+			status = 1
+		}
+		$1 == "distrust" && $2 in trust && $2 != trust[$3] {
+			printf "%s: distrusted certificate %s" \
+			    " has multiple paths\n" \
+			    "  %s\n  %s\n",
+			    progname, $3, trust[$3], $2 >"/dev/stderr"
+			status = 1
+		}
+		$1 == "distrust"	{ distrust[$3] = 1 }
+		END			{
+			for (vbase in trust) {
+				if (!distrust[vbase])
+					print trust[vbase]
+			}
+			exit status
+		}
+	' | sort -u
+}
+
+# rehash
+#
+#	Delete and rebuild certsdir.
+#
+rehash()
+{
+	local vcert cert certbase hash counter bundle vbundle
+
+	# If manual operation is enabled, refuse to rehash the
+	# certsdir, but succeed anyway so this can safely be used in
+	# automated scripts.
+	if $config_manual; then
+		error "manual certificates enabled, not rehashing"
+		return
+	fi
+
+	# Delete the active certificates symlink cache.
+	run rm -rf "$certsdir"
+	run mkdir "$certsdir"
+
+	# Create a temporary file for the single-file bundle.  This
+	# will be automatically deleted on normal exit or
+	# SIGHUP/SIGINT/SIGTERM.
+	if ! $nflag; then
+		tmpfile=$(mktemp -t "$progname.XXXXXX")
+	fi
+
+	# Recreate symlinks for all of the trusted certificates.
+	list_trusted \
+	| while read -r vcert; do
+		cert=$(printf '%s.' "$vcert" | unvis)
+		cert=${cert%.}
+		run ln -s -- "$cert" "$certsdir"
+
+		# Add the certificate to the single-file bundle.
+		if ! $nflag; then
+			cat -- "$cert" >>$tmpfile
+		fi
+	done
+
+	# Hash the directory with openssl.
+	#
+	# XXX Pass `-v' to openssl in a way that doesn't mix with our
+	# shell-safe verbose commands?  (Need to handle `-n' too.)
+	run openssl rehash -- "$certsdir"
+
+	# Install the single-file bundle.
+	bundle=$certsdir/ca-certificates.crt
+	vbundle=$(printf '%s' "$bundle" | vis -M)
+	$vflag && printf '# create %s\n' "$vbundle"
+	if ! $nflag; then
+		cp -- "$tmpfile" "$bundle"
+		rm -f -- "$tmpfile"
+		tmpfile=
+	fi
+}
+
+### Commands
+
+usage_list()
+{
+	exec >&2
+	printf 'Usage: %s list\n' "$progname"
+	exit 1
+}
+cmd_list()
+{
+	test $# -eq 1 || usage_list
+
+	configure
+
+	list_trusted \
+	| while read -r vcert vbase; do
+		printf '%s\n' "$vcert"
+	done
+}
+
+usage_rehash()
+{
+	exec >&2
+	printf 'Usage: %s rehash\n' "$progname"
+	exit 1
+}
+cmd_rehash()
+{
+	test $# -eq 1 || usage_rehash
+
+	configure
+
+	rehash
+}
+
+usage_trust()
+{
+	exec >&2
+	printf 'Usage: %s trust <cert>\n' "$progname"
+	exit 1
+}
+cmd_trust()
+{
+	local cert vcert certbase vcertbase
+
+	test $# -eq 2 || usage_trust
+	cert=$2
+
+	configure
+
+	# XXX Accept base name.
+
+	# vis the certificate path for terminal-safe error messages.
+	vcert=$(printf '%s' "$cert" | vis -M)
+
+	# Verify the certificate actually exists.
+	if [ ! -f "$cert" ]; then
+		error "no such certificate: $vcert"
+		return 1
+	fi
+
+	# Verify we currently distrust a certificate by this base name.
+	certbase=$(basename -- "$cert.")
+	certbase=${certbase%.}
+	if [ ! -h "$distrustdir/$certbase" ]; then
+		error "not currently distrusted: $vcert"
+		return 1
+	fi
+
+	# Verify the certificate we distrust by this base name is the
+	# same one.
+	target=$(readlink -n -- "$distrustdir/$certbase" && printf .)
+	target=${target%.}
+	if [ "$cert" != "$target" ]; then
+		vcertbase=$(basename -- "$vcert")
+		error "distrusted $vcertbase does not point to $vcert"
+		return 1
+	fi
+
+	# Remove the link from the distrusted directory, and rehash --
+	# quietly, so verbose output emphasizes the distrust part and
+	# not the whole certificate set.
+	run rm -- "$distrustdir/$certbase"
+	$vflag && echo '# rehash'
+	vflag=false
+	rehash
+}
+
+usage_untrust()
+{
+	exec >&2
+	printf 'Usage: %s untrust <cert>\n' "$progname"
+	exit 1
+}
+cmd_untrust()
+{
+	local cert vcert certbase vcertbase target vtarget
+
+	test $# -eq 2 || usage_untrust
+	cert=$2
+
+	configure
+
+	# vis the certificate path for terminal-safe error messages.
+	vcert=$(printf '%s' "$cert" | vis -M)
+
+	# Verify the certificate actually exists.  Otherwise, you might
+	# fail to distrust a certificate you intended to distrust,
+	# e.g. if you made a typo in its path.
+	if [ ! -f "$cert" ]; then
+		error "no such certificate: $vcert"
+		return 1
+	fi
+
+	# Check whether this certificate is already distrusted.
+	# - If the same base name points to the same path, stop here.
+	# - Otherwise, fail noisily.
+	certbase=$(basename "$cert.")
+	certbase=${certbase%.}
+	if [ -h "$distrustdir/$certbase" ]; then
+		target=$(readlink -n -- "$distrustdir/$certbase" && printf .)
+		target=${target%.}
+		if [ "$target" = "$cert" ]; then
+			$vflag && echo '# already distrusted'
+			return
+		fi
+		vcertbase=$(printf '%s' "$certbase" | vis -M)
+		vtarget=$(printf '%s' "$target" | vis -M)
+		error "distrusted $vcertbase at different path $vtarget"
+		return 1
+	fi
+
+	# Create the distrustdir if needed, create a symlink in it, and
+	# rehash -- quietly, so verbose output emphasizes the distrust
+	# part and not the whole certificate set.
+	test -d "$distrustdir" || run mkdir -- "$distrustdir"
+	run ln -s -- "$cert" "$distrustdir"
+	$vflag && echo '# rehash'
+	vflag=false
+	rehash
+}
+
+usage_untrusted()
+{
+	exec >&2
+	printf 'Usage: %s untrusted\n' "$progname"
+	exit 1
+}
+cmd_untrusted()
+{
+	test $# -eq 1 || usage_untrusted
+
+	configure
+
+	list_distrusted \
+	| while read -r vcert vbase; do
+		printf '%s\n' "$vcert"
+	done
+}
+
+### Main
+
+# We accept the following aliases for user interface compatibility with
+# FreeBSD:
+#
+#	blacklist = untrust
+#	blacklisted = untrusted
+#	unblacklist = trust
+
+case $cmd in
+list)	cmd_list "$@"
+	;;
+rehash)	cmd_rehash "$@"
+	;;
+trust|unblacklist)
+	cmd_trust "$@"
+	;;
+untrust|blacklist)
+	cmd_untrust "$@"
+	;;
+untrusted|blacklisted)
+	cmd_untrusted "$@"
+	;;
+*)	vcmd=$(printf '%s' "$cmd" | vis -M)
+	printf '%s: unknown command: %s\n' "$progname" "$vcmd" >&2
+	usage
+	;;
+esac
Index: src/usr.sbin/certctl/certs.conf
diff -u /dev/null src/usr.sbin/certctl/certs.conf:1.1
--- /dev/null	Sat Aug 26 05:27:15 2023
+++ src/usr.sbin/certctl/certs.conf	Sat Aug 26 05:27:15 2023
@@ -0,0 +1,13 @@
+netbsd-certctl 20230816
+
+#	$NetBSD: certs.conf,v 1.1 2023/08/26 05:27:15 riastradh Exp $
+#
+# Configuration file for certctl(8) to manage HTTPS root CA
+# certificates in /etc/openssl/certs.
+#
+
+path	/usr/share/certs/mozilla/server
+
+# For manual control over /etc/openssl/certs, e.g. if you want to
+# install a separate CA bundle from pkgsrc, uncomment the next line:
+#manual

Reply via email to