Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F163715726
D51896.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
14 KB
Referenced Files
None
Subscribers
None
D51896.diff
View Options
diff --git a/Makefile.inc1 b/Makefile.inc1
--- a/Makefile.inc1
+++ b/Makefile.inc1
@@ -1021,7 +1021,8 @@
.endif
.if make(distributeworld)
-CERTCTLDESTDIR= ${DESTDIR}/${DISTDIR}/base
+CERTCTLDESTDIR= ${DESTDIR}/${DISTDIR}
+CERTCTLFLAGS+= -d /base
.else
CERTCTLDESTDIR= ${DESTDIR}
.endif
diff --git a/usr.sbin/certctl/certctl.8 b/usr.sbin/certctl/certctl.8
--- 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 August 15, 2025
.Dt CERTCTL 8
.Os
.Sh NAME
@@ -63,6 +63,8 @@
command.
.It Fl D Ar destdir
Specify the DESTDIR (overriding values from the environment).
+.It Fl d Ar distbase
+Specify the DISTBASE (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.
@@ -117,7 +119,13 @@
.Sh ENVIRONMENT
.Bl -tag -width UNTRUSTDESTDIR
.It Ev DESTDIR
-Alternate destination directory to operate on.
+Absolute path to an alternate destination directory to operate on
+instead of the file system root, e.g.
+.Dq Li /tmp/install .
+.It Ev DISTBASE
+Additional path component to include when operating on certificate directories.
+This must start with a slash, e.g.
+.Dq Li /base .
.It Ev LOCALBASE
Location for local programs.
Defaults to the value of the user.localbase sysctl which is usually
@@ -125,22 +133,22 @@
.It Ev TRUSTPATH
List of paths to search for trusted certificates.
Default:
-.Pa ${DESTDIR}/usr/share/certs/trusted
+.Pa ${DESTDIR}${DISTBASE}/usr/share/certs/trusted
.Pa ${DESTDIR}${LOCALBASE}/share/certs/trusted
.Pa ${DESTDIR}${LOCALBASE}/share/certs
.It Ev UNTRUSTPATH
List of paths to search for untrusted certificates.
Default:
-.Pa ${DESTDIR}/usr/share/certs/untrusted
+.Pa ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted
.Pa ${DESTDIR}${LOCALBASE}/share/certs/untrusted
.It Ev TRUSTDESTDIR
Destination directory for symbolic links to trusted certificates.
Default:
-.Pa ${DESTDIR}/etc/ssl/certs
+.Pa ${DESTDIR}${DISTBASE}/etc/ssl/certs
.It Ev UNTRUSTDESTDIR
Destination directory for symbolic links to untrusted certificates.
Default:
-.Pa ${DESTDIR}/etc/ssl/untrusted
+.Pa ${DESTDIR}${DISTBASE}/etc/ssl/untrusted
.It Ev BUNDLE
File name of bundle to produce.
.El
diff --git a/usr.sbin/certctl/certctl.c b/usr.sbin/certctl/certctl.c
--- a/usr.sbin/certctl/certctl.c
+++ b/usr.sbin/certctl/certctl.c
@@ -63,6 +63,7 @@
static const char *localbase;
static const char *destdir;
+static const char *distbase;
static const char *metalog;
static const char *uname = "root";
@@ -99,6 +100,28 @@
static FILE *mlf;
+/*
+ * Remove duplicate and trailing slashes from a path.
+ */
+static char *
+normalize_path(const char *str)
+{
+ char *buf, *dst;
+
+ if ((buf = malloc(strlen(str) + 1)) == NULL)
+ err(1, NULL);
+ for (dst = buf; *str != '\0'; dst++) {
+ if ((*dst = *str++) == '/') {
+ while (*str == '/')
+ str++;
+ if (*str == '\0')
+ break;
+ }
+ }
+ *dst = '\0';
+ return (buf);
+}
+
/*
* Split a colon-separated list into a NULL-terminated array.
*/
@@ -124,14 +147,14 @@
}
/*
- * Expand %L into LOCALBASE and prefix DESTDIR.
+ * Expand %L into LOCALBASE and prefix DESTDIR and DISTBASE as needed.
*/
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));
+ return (xasprintf("%s%s%s", destdir, distbase, template));
}
/*
@@ -155,6 +178,9 @@
/*
* If destdir is a prefix of path, returns a pointer to the rest of path,
* otherwise returns path.
+ *
+ * Note that this intentionally does not strip distbase from the path!
+ * Unlike destdir, distbase is expected to be included in the metalog.
*/
static const char *
unexpand_path(const char *path)
@@ -268,7 +294,7 @@
X509_NAME *name;
struct cert *cert;
unsigned long hash;
- int ni, no;
+ int len, ni, no;
if ((f = fopen(path, "r")) == NULL) {
warn("%s", path);
@@ -293,7 +319,17 @@
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);
+ len = X509_NAME_get_text_by_NID(name, NID_commonName,
+ NULL, 0);
+ if (len > 0) {
+ if ((cert->name = malloc(len + 1)) == NULL)
+ err(1, NULL);
+ X509_NAME_get_text_by_NID(name, NID_commonName,
+ cert->name, len + 1);
+ } else {
+ /* fallback for certificates without CN */
+ 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");
@@ -488,9 +524,10 @@
free(tmppath);
tmppath = NULL;
}
+ fflush(f);
/* emit metalog */
if (mlf != NULL) {
- fprintf(mlf, "%s/%s type=file "
+ fprintf(mlf, ".%s/%s type=file "
"uname=%s gname=%s mode=%#o size=%ld\n",
unexpand_path(dir), path,
uname, gname, mode, ftell(f));
@@ -561,7 +598,7 @@
}
if (ret == 0 && mlf != NULL) {
fprintf(mlf,
- "%s/%s type=file uname=%s gname=%s mode=%#o size=%ld\n",
+ ".%s/%s type=file uname=%s gname=%s mode=%#o size=%ld\n",
unexpand_path(dir), file, uname, gname, mode, ftell(f));
}
fclose(f);
@@ -925,6 +962,14 @@
if (destdir == NULL &&
(destdir = getenv("DESTDIR")) == NULL)
destdir = "";
+ destdir = normalize_path(destdir);
+
+ if (distbase == NULL &&
+ (distbase = getenv("DISTBASE")) == NULL)
+ distbase = "";
+ if (*distbase != '\0' && *distbase != '/')
+ errx(1, "DISTBASE=%s does not begin with a slash", distbase);
+ distbase = normalize_path(distbase);
if (unprivileged && metalog == NULL &&
(metalog = getenv("METALOG")) == NULL)
@@ -966,6 +1011,7 @@
info("localbase:\t%s", localbase);
info("destdir:\t%s", destdir);
+ info("distbase:\t%s", distbase);
info("unprivileged:\t%s", unprivileged ? "true" : "false");
info("verbose:\t%s", verbose ? "true" : "false");
}
@@ -987,11 +1033,11 @@
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 <file>\n"
- " certctl [-nv] [-D destdir] trust <file>\n");
+ fprintf(stderr, "usage: certctl [-lv] [-D destdir] [-d distbase] list\n"
+ " certctl [-lv] [-D destdir] [-d distbase] untrusted\n"
+ " certctl [-BnUv] [-D destdir] [-d distbase] [-M metalog] rehash\n"
+ " certctl [-nv] [-D destdir] [-d distbase] untrust <file>\n"
+ " certctl [-nv] [-D destdir] [-d distbase] trust <file>\n");
exit(1);
}
@@ -1001,7 +1047,7 @@
const char *command;
int opt;
- while ((opt = getopt(argc, argv, "BcD:g:lL:M:no:Uv")) != -1)
+ while ((opt = getopt(argc, argv, "BcD:d:g:lL:M:no:Uv")) != -1)
switch (opt) {
case 'B':
nobundle = true;
@@ -1012,6 +1058,9 @@
case 'D':
destdir = optarg;
break;
+ case 'd':
+ distbase = optarg;
+ break;
case 'g':
gname = optarg;
break;
diff --git a/usr.sbin/certctl/tests/certctl_test.sh b/usr.sbin/certctl/tests/certctl_test.sh
--- a/usr.sbin/certctl/tests/certctl_test.sh
+++ b/usr.sbin/certctl/tests/certctl_test.sh
@@ -60,41 +60,77 @@
EOF
}
+sortfile() {
+ for filename; do
+ sort "${filename}" >"${filename}"-
+ mv "${filename}"- "${filename}"
+ done
+}
+
certctl_setup()
{
export DESTDIR="$PWD"
# Create input directories
- mkdir -p usr/share/certs/trusted
- mkdir -p usr/share/certs/untrusted
- mkdir -p usr/local/share/certs
+ mkdir -p ${DESTDIR}${DISTBASE}/usr/share/certs/trusted
+ mkdir -p ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted
+ mkdir -p ${DESTDIR}/usr/local/share/certs
# Create output directories
- mkdir -p etc/ssl/certs
- mkdir -p etc/ssl/untrusted
+ mkdir -p ${DESTDIR}${DISTBASE}/etc/ssl/certs
+ mkdir -p ${DESTDIR}${DISTBASE}/etc/ssl/untrusted
# Generate a random key
keyname="testkey"
gen_key ${keyname}
# Generate certificates
+ :>metalog.expect
+ :>trusted.expect
+ :>untrusted.expect
+ metalog() {
+ echo ".${DISTBASE}$@ type=file" >>metalog.expect
+ }
+ trusted() {
+ local crtname=$1
+ local filename=$2
+ printf "%s\t%s\n" "${filename}" "${crtname}" >>trusted.expect
+ metalog "/etc/ssl/certs/${filename}"
+ }
+ untrusted() {
+ local crtname=$1
+ local filename=$2
+ printf "%s\t%s\n" "${filename}" "${crtname}" >>untrusted.expect
+ metalog "/etc/ssl/untrusted/${filename}"
+ }
set1 | while read crtname hash ; do
gen_crt ${crtname} ${keyname}
- mv ${crtname}.crt usr/share/certs/trusted
+ mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/trusted
+ trusted "${crtname}" "${hash}.0"
done
+ local c=0
coll | while read crtname hash ; do
gen_crt ${crtname} ${keyname}
- mv ${crtname}.crt usr/share/certs/trusted
+ mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/trusted
+ trusted "${crtname}" "${hash}.${c}"
+ c=$((c+1))
done
set2 | while read crtname hash ; do
gen_crt ${crtname} ${keyname}
openssl x509 -in ${crtname}.crt
rm ${crtname}.crt
+ trusted "${crtname}" "${hash}.0"
done >usr/local/share/certs/bundle.crt
set3 | while read crtname hash ; do
gen_crt ${crtname} ${keyname}
- mv ${crtname}.crt usr/share/certs/untrusted
+ mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted
+ untrusted "${crtname}" "${hash}.0"
done
+ metalog "/etc/ssl/cert.pem"
+ unset -f untrusted
+ unset -f trusted
+ unset -f metalog
+ sortfile *.expect
}
check_trusted() {
@@ -102,12 +138,12 @@
local subject="$(subject ${crtname})"
local c=${2:-1}
- atf_check -o match:"found: ${c}\$" \
+ atf_check -e ignore -o match:"found: ${c}\$" \
openssl storeutl -noout -subject "${subject}" \
- etc/ssl/certs
- atf_check -o match:"found: 0\$" \
+ ${DESTDIR}${DISTBASE}/etc/ssl/certs
+ atf_check -e ignore -o not-match:"found: [1-9]" \
openssl storeutl -noout -subject "${subject}" \
- etc/ssl/untrusted
+ ${DESTDIR}${DISTBASE}/etc/ssl/untrusted
}
check_untrusted() {
@@ -115,23 +151,25 @@
local subject="$(subject ${crtname})"
local c=${2:-1}
- atf_check -o match:"found: 0\$" \
+ atf_check -e ignore -o not-match:"found: [1-9]" \
openssl storeutl -noout -subject "${subject}" \
- etc/ssl/certs
- atf_check -o match:"found: ${c}\$" \
+ ${DESTDIR}/${DISTBASE}/etc/ssl/certs
+ atf_check -e ignore -o match:"found: ${c}\$" \
openssl storeutl -noout -subject "${subject}" \
- etc/ssl/untrusted
+ ${DESTDIR}/${DISTBASE}/etc/ssl/untrusted
}
check_in_bundle() {
+ local b=${DISTBASE}${DISTBASE+/}
local crtfile=$1
local line
line=$(tail +5 "${crtfile}" | head -1)
- atf_check grep -q "${line}" etc/ssl/cert.pem
+ atf_check grep -q "${line}" ${DESTDIR}${DISTBASE}/etc/ssl/cert.pem
}
check_not_in_bundle() {
+ local b=${DISTBASE}${DISTBASE+/}
local crtfile=$1
local line
@@ -150,7 +188,7 @@
atf_check certctl rehash
# Verify non-colliding trusted certificates
- (set1 ; set2) > trusted
+ (set1; set2) >trusted
while read crtname hash ; do
check_trusted "${crtname}"
done <trusted
@@ -167,7 +205,7 @@
check_untrusted "${crtname}"
done <untrusted
- # Verify bundle; storeutl is no help here
+ # Verify bundle
for f in etc/ssl/certs/*.? ; do
check_in_bundle "${f}"
done
@@ -176,6 +214,31 @@
done
}
+atf_test_case list
+list_head()
+{
+ atf_set "descr" "Test the list and untrusted commands"
+}
+list_body()
+{
+ certctl_setup
+ atf_check certctl rehash
+
+ atf_check -o save:trusted.out certctl list
+ sortfile trusted.out
+ # the ordering of the colliding certificates is partly
+ # determined by fields that change every time we regenerate
+ # them, so ignore them in the diff
+ atf_check diff -u \
+ --ignore-matching-lines $collhash \
+ trusted.expect trusted.out
+
+ atf_check -o save:untrusted.out certctl untrusted
+ sortfile untrusted.out
+ atf_check diff -u \
+ untrusted.expect untrusted.out
+}
+
atf_test_case trust
trust_head()
{
@@ -185,7 +248,7 @@
{
certctl_setup
atf_check certctl rehash
- crtname=NJWIRLPWAIICVJBKXXHFHLCPAERZATRL
+ crtname=$(set3 | (read crtname hash ; echo ${crtname}))
crtfile=usr/share/certs/untrusted/${crtname}.crt
check_untrusted ${crtname}
check_not_in_bundle ${crtfile}
@@ -204,7 +267,7 @@
{
certctl_setup
atf_check certctl rehash
- crtname=AVOYKJHSLFHWPVQMKBHENUAHJTEGMCCB
+ crtname=$(set1 | (read crtname hash ; echo ${crtname}))
crtfile=usr/share/certs/trusted/${crtname}.crt
check_trusted "${crtname}"
check_in_bundle ${crtfile}
@@ -213,9 +276,57 @@
check_not_in_bundle ${crtfile}
}
+atf_test_case metalog
+metalog_head()
+{
+ atf_set "descr" "Verify the metalog"
+}
+metalog_body()
+{
+ export DISTBASE=/base
+ certctl_setup
+
+ # certctl gets DESTDIR and DISTBASE from environment
+ rm -f metalog.orig
+ atf_check certctl -U -M metalog.orig rehash
+ sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short
+ atf_check diff -u metalog.expect metalog.short
+
+ # certctl gets DESTDIR and DISTBASE from command line
+ rm -f metalog.orig
+ atf_check env -uDESTDIR -uDISTBASE \
+ certctl -D ${DESTDIR} -d ${DISTBASE} -U -M metalog.orig rehash
+ sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short
+ atf_check diff -u metalog.expect metalog.short
+
+ # as above, but intentionally add trailing slashes
+ rm -f metalog.orig
+ atf_check env -uDESTDIR -uDISTBASE \
+ certctl -D ${DESTDIR}// -d ${DISTBASE}/ -U -M metalog.orig rehash
+ sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short
+ atf_check diff -u metalog.expect metalog.short
+}
+
+atf_test_case misc
+misc_head()
+{
+ atf_set "descr" "Test miscellaneous edge cases"
+}
+misc_body()
+{
+ # certctl rejects DISTBASE that does not begin with a slash
+ atf_check -s exit:1 -e match:"begin with a slash" \
+ certctl -d base -n rehash
+ atf_check -s exit:1 -e match:"begin with a slash" \
+ env DISTBASE=base certctl -n rehash
+}
+
atf_init_test_cases()
{
atf_add_test_case rehash
+ atf_add_test_case list
atf_add_test_case trust
atf_add_test_case untrust
+ atf_add_test_case metalog
+ atf_add_test_case misc
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Jul 26, 4:00 PM (4 h, 20 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
35484130
Default Alt Text
D51896.diff (14 KB)
Attached To
Mode
D51896: certctl: Restore distbase
Attached
Detach File
Event Timeline
Log In to Comment