git: a401c8cb26b2 - main - certctl: Split certificate bundles before processing.

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Thu, 05 Oct 2023 15:11:31 UTC
The branch main has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=a401c8cb26b22688087ad7c5ee527718459df15a

commit a401c8cb26b22688087ad7c5ee527718459df15a
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2023-10-05 14:50:01 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2023-10-05 15:11:22 +0000

    certctl: Split certificate bundles before processing.
    
    This allows 'certctl rehash' to do the right thing when ca_root_nss is
    installed, instead of linking the entire bundle to the hash of the
    first certificate it contains.
    
    MFC after:      3 days
    Reviewed by:    allanjude
    Differential Revision:  https://reviews.freebsd.org/D42087
---
 usr.sbin/certctl/certctl.sh | 99 ++++++++++++++++++++++++++++-----------------
 1 file changed, 63 insertions(+), 36 deletions(-)

diff --git a/usr.sbin/certctl/certctl.sh b/usr.sbin/certctl/certctl.sh
index 02d055102c33..b7d3a95bc7d7 100755
--- a/usr.sbin/certctl/certctl.sh
+++ b/usr.sbin/certctl/certctl.sh
@@ -32,7 +32,6 @@ set -u
 
 : ${DESTDIR:=}
 : ${DISTBASE:=}
-: ${FILEPAT:="\.pem$|\.crt$|\.cer$|\.crl$"}
 
 ############################################################ GLOBALS
 
@@ -63,6 +62,16 @@ perform()
 	fi
 }
 
+cert_files_in()
+{
+	find -L "$@" -type f \( \
+	     -name '*.pem' -or \
+	     -name '*.crt' -or \
+	     -name '*.cer' -or \
+	     -name '*.crl' \
+	\) 2>/dev/null
+}
+
 do_hash()
 {
 	local hash
@@ -93,23 +102,32 @@ get_decimal()
 	return 0
 }
 
-create_trusted_link()
+create_trusted()
 {
 	local hash certhash otherfile otherhash
 	local suffix
+	local link=${2:+-lm}
 
 	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 $1 ($otherfile)"
+			info "Skipping untrusted certificate $hash ($otherfile)"
 			return 1
 		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} -lrs "$(realpath "$1")" "$CERTDESTDIR/$hash.$suffix"
+	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.
@@ -137,6 +155,7 @@ resolve_certname()
 create_untrusted()
 {
 	local srcfile filename
+	local link=${2:+-lm}
 
 	set -- $(resolve_certname "$1")
 	srcfile=$1
@@ -147,12 +166,13 @@ create_untrusted()
 	fi
 
 	verbose "Adding $filename to untrusted list"
-	perform install ${INSTALLFLAGS} -lrs "$srcfile" "$UNTRUSTDESTDIR/$filename"
+	perform install ${INSTALLFLAGS} -m 0444 ${link} \
+		"$srcfile" "$UNTRUSTDESTDIR/$filename"
 }
 
 do_scan()
 {
-	local CFUNC CSEARCH CPATH CFILE
+	local CFUNC CSEARCH CPATH CFILE CERT SPLITDIR
 	local oldIFS="$IFS"
 	CFUNC="$1"
 	CSEARCH="$2"
@@ -160,14 +180,25 @@ do_scan()
 	IFS=:
 	set -- $CSEARCH
 	IFS="$oldIFS"
-	for CPATH in "$@"; do
-		[ -d "$CPATH" ] || continue
-		info "Scanning $CPATH for certificates..."
-		for CFILE in $(ls -1 "${CPATH}" | grep -Ee "${FILEPAT}") ; do
-			[ -e "$CPATH/$CFILE" ] || continue
-			verbose "Reading $CFILE"
-			"$CFUNC" "$CPATH/$CFILE"
-		done
+	for CFILE in $(cert_files_in "$@") ; do
+		verbose "Reading $CFILE"
+		case $(grep -c '^Certificate:$' "$CFILE") in
+		0)
+			;;
+		1)
+			"$CFUNC" "$CFILE" link
+			;;
+		*)
+			verbose "Multiple certificates found, splitting..."
+			SPLITDIR=$(mktemp -d)
+			egrep '^[^#]' "$CFILE" | \
+				split -p '^Certificate:$' - "$SPLITDIR/x"
+			for CERT in $(find "$SPLITDIR" -type f) ; do
+				"$CFUNC" "$CERT"
+			done
+			rm -rf "$SPLITDIR"
+			;;
+		esac
 	done
 }
 
@@ -175,43 +206,39 @@ do_list()
 {
 	local CFILE subject
 
-	if [ -e "$1" ] ; then
-		cd "$1"
-		for CFILE in *.[0-9] ; do
-			if [ ! -s "$CFILE" ] ; then
-				info "Unable to read $CFILE"
-				ERRORS=$((ERRORS + 1))
-				continue
-			fi
-			subject=
-			if [ $VERBOSE -eq 0 ] ; then
-				subject=$(openssl x509 -noout -subject -nameopt multiline -in "$CFILE" |
-				    sed -n '/commonName/s/.*= //p')
-			fi
-			[ "$subject" ] ||
-			    subject=$(openssl x509 -noout -subject -in "$CFILE")
-			printf "%s\t%s\n" "$CFILE" "$subject"
-		done
-		cd -
-	fi
+	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 link -delete
+		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 link -delete
+		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_link "$TRUSTPATH"
+	do_scan create_trusted "$TRUSTPATH"
 }
 
 cmd_list()