Skip site navigation (1)Skip section navigation (2)



index | | raw e-mail

diff --git a/usr.sbin/certctl/certctl.8 b/usr.sbin/certctl/certctl.8
index 2d38ce020c04..7df89e4d35d0 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 December 3, 2025
+.Dd April 24, 2026
 .Dt CERTCTL 8
 .Os
 .Sh NAME
@@ -113,8 +113,19 @@ In addition, a bundle containing the trusted certificates is placed in
 .Ev BUNDLE .
 .It Ic untrust
 Add the specified file to the untrusted list.
+Note that the next
+.Ic rehash
+will remove it unless a copy of it is also placed somewhere in a
+directory included in
+.Ev UNTRUSTPATH .
 .It Ic trust
-Remove the specified file from the untrusted list.
+Add the specified file to the trusted list, unless it is already
+untrusted.
+Note that the next
+.Ic rehash
+will remove it unless a copy of it is also placed somewhere in a
+directory included in
+.Ev TRUSTPATH .
 .El
 .Sh ENVIRONMENT
 .Bl -tag -width UNTRUSTDESTDIR
diff --git a/usr.sbin/certctl/certctl.c b/usr.sbin/certctl/certctl.c
index a53ed7b2b4b2..0a07703bf37b 100644
--- a/usr.sbin/certctl/certctl.c
+++ b/usr.sbin/certctl/certctl.c
@@ -636,23 +636,18 @@ write_bundle(const char *dir, const char *file, struct cert_tree *tree)
  * Returns the number of certificates loaded.
  */
 static unsigned int
-load_trusted(bool all, struct cert_tree *exclude)
+load_trusted(void)
 {
 	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);
+	for (i = n = 0; trusted_paths[i] != NULL; i++) {
+		ret = read_certs(trusted_paths[i], &trusted, &untrusted);
 		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);
 }
@@ -663,24 +658,19 @@ load_trusted(bool all, struct cert_tree *exclude)
  * Returns the number of certificates loaded.
  */
 static unsigned int
-load_untrusted(bool all)
+load_untrusted(void)
 {
 	char *path;
 	unsigned int i, n;
 	int ret;
 
 	/* load external untrusted certs */
-	for (i = n = 0; all && untrusted_paths[i] != NULL; i++) {
+	for (i = n = 0; 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);
@@ -796,8 +786,8 @@ certctl_list(int argc, char **argv __unused)
 {
 	if (argc > 1)
 		usage();
-	/* load trusted certificates */
-	load_trusted(false, NULL);
+	/* load installed trusted certificates */
+	read_certs(trusted_dest, &trusted, NULL);
 	/* list them */
 	list_certs(&trusted);
 	free_certs(&trusted);
@@ -814,8 +804,8 @@ certctl_untrusted(int argc, char **argv __unused)
 {
 	if (argc > 1)
 		usage();
-	/* load untrusted certificates */
-	load_untrusted(false);
+	/* load installed untrusted certificates */
+	read_certs(untrusted_dest, &untrusted, NULL);
 	/* list them */
 	list_certs(&untrusted);
 	free_certs(&untrusted);
@@ -842,10 +832,10 @@ certctl_rehash(int argc, char **argv __unused)
 	}
 
 	/* load untrusted certs first */
-	load_untrusted(true);
+	load_untrusted();
 
 	/* load trusted certs, excluding any that are already untrusted */
-	load_trusted(true, &untrusted);
+	load_trusted();
 
 	/* save everything */
 	ret = save_all();
@@ -859,7 +849,8 @@ certctl_rehash(int argc, char **argv __unused)
 }
 
 /*
- * Manually add one or more certificates to the list of trusted certificates.
+ * Manually add one or more certificates to the list of trusted
+ * certificates.
  *
  * Returns 0 on success and -1 on failure.
  */
@@ -875,10 +866,10 @@ certctl_trust(int argc, char **argv)
 		usage();
 
 	/* load untrusted certs first */
-	load_untrusted(true);
+	load_untrusted();
 
 	/* load trusted certs, excluding any that are already untrusted */
-	load_trusted(true, &untrusted);
+	load_trusted();
 
 	/* now load the additional trusted certificates */
 	n = 0;
@@ -891,9 +882,9 @@ certctl_trust(int argc, char **argv)
 		warnx("no new trusted certificates found");
 		free_certs(&untrusted);
 		free_certs(&trusted);
-		free_certs(&extra);
 		return (0);
 	}
+	warnx("%u new trusted certificate%s found", n, n > 1 ? "s" : "");
 
 	/*
 	 * For each new trusted cert, move it from the extra list to the
@@ -906,10 +897,16 @@ certctl_trust(int argc, char **argv)
 		RB_INSERT(cert_tree, &trusted, cert);
 		if ((other = RB_FIND(cert_tree, &untrusted, cert)) != NULL) {
 			warnx("%s was previously untrusted", cert->name);
+			warnx("source of untrust: %s", other->path);
 			RB_REMOVE(cert_tree, &untrusted, other);
 			free_cert(other);
 		}
 	}
+	warnx("This operation is not persistent.  To persistently add");
+	warnx("trusted certificates to the system store, copy them to");
+	warnx("one of these directories, then run `certctl rehash`:");
+	for (i = 0; trusted_paths[i] != NULL; i++)
+		warnx("    %s", trusted_paths[i]);
 
 	/* save everything */
 	ret = save_all();
@@ -929,6 +926,8 @@ certctl_trust(int argc, char **argv)
 static int
 certctl_untrust(int argc, char **argv)
 {
+	struct cert_tree extra = RB_INITIALIZER(&extra);
+	struct cert *cert, *other, *tmp;
 	unsigned int n;
 	int i, ret;
 
@@ -936,23 +935,47 @@ certctl_untrust(int argc, char **argv)
 		usage();
 
 	/* load untrusted certs first */
-	load_untrusted(true);
+	load_untrusted();
+
+	/* load trusted certs, excluding any that are already untrusted */
+	load_trusted();
 
 	/* now load the additional untrusted certificates */
 	n = 0;
 	for (i = 1; i < argc; i++) {
-		ret = read_cert(argv[i], &untrusted, NULL);
+		ret = read_cert(argv[i], &extra, NULL);
 		if (ret > 0)
 			n += ret;
 	}
 	if (n == 0) {
 		warnx("no new untrusted certificates found");
 		free_certs(&untrusted);
+		free_certs(&trusted);
 		return (0);
 	}
+	warnx("%u new untrusted certificate%s found", n, n > 1 ? "s" : "");
 
-	/* load trusted certs, excluding any that are already untrusted */
-	load_trusted(true, &untrusted);
+	/*
+	 * For each new untrusted cert, move it from the extra list to the
+	 * untrusted list, then check if a matching certificate exists on
+	 * the trusted list.  If that is the case, warn the user, then
+	 * remove the matching certificate from the trusted list.
+	 */
+	RB_FOREACH_SAFE(cert, cert_tree, &extra, tmp) {
+		RB_REMOVE(cert_tree, &extra, cert);
+		RB_INSERT(cert_tree, &untrusted, cert);
+		if ((other = RB_FIND(cert_tree, &trusted, cert)) != NULL) {
+			warnx("%s was previously trusted", cert->name);
+			warnx("source of trust: %s", other->path);
+			RB_REMOVE(cert_tree, &trusted, other);
+			free_cert(other);
+		}
+	}
+	warnx("This operation is not persistent.  To persistently add");
+	warnx("untrusted certificates to the system store, copy them to");
+	warnx("one of these directories, then run `certctl rehash`:");
+	for (i = 0; untrusted_paths[i] != NULL; i++)
+		warnx("    %s", untrusted_paths[i]);
 
 	/* save everything */
 	ret = save_all();
diff --git a/usr.sbin/certctl/tests/certctl_test.sh b/usr.sbin/certctl/tests/certctl_test.sh
index 74749db0b3f5..133d65854c42 100644
--- a/usr.sbin/certctl/tests/certctl_test.sh
+++ b/usr.sbin/certctl/tests/certctl_test.sh
@@ -271,7 +271,8 @@ untrust_body()
 	crtfile=usr/share/certs/trusted/${crtname}.crt
 	check_trusted "${crtname}"
 	check_in_bundle ${crtfile}
-	atf_check certctl untrust "${crtfile}"
+	atf_check -e match:"1 new untrusted" \
+	    certctl untrust "${crtfile}"
 	check_untrusted "${crtname}"
 	check_not_in_bundle ${crtfile}
 }


home | help