diff --git a/usr.sbin/certctl/certctl.8 b/usr.sbin/certctl/certctl.8 index 286072c1b4d6..7e49bb89e2ac 100644 --- a/usr.sbin/certctl/certctl.8 +++ b/usr.sbin/certctl/certctl.8 @@ -1,136 +1,138 @@ .\" .\" SPDX-License-Identifier: BSD-2-Clause .\" .\" Copyright 2018 Allan Jude .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted providing that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED .\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY .\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, .\" STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING .\" IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd October 10, 2023 +.Dd July 17, 2025 .Dt CERTCTL 8 .Os .Sh NAME .Nm certctl .Nd "tool for managing trusted and untrusted TLS certificates" .Sh SYNOPSIS .Nm .Op Fl v .Ic list .Nm .Op Fl v .Ic untrusted .Nm -.Op Fl nUv +.Op Fl cnUv .Op Fl D Ar destdir .Op Fl M Ar metalog .Ic rehash .Nm -.Op Fl nv +.Op Fl cnv .Ic untrust Ar file .Nm -.Op Fl nv +.Op Fl cnv .Ic trust Ar file .Sh DESCRIPTION The .Nm utility manages the list of TLS Certificate Authorities that are trusted by applications that use OpenSSL. .Pp Flags: .Bl -tag -width 4n +.It Fl c +Copy certificates instead of linking to them. .It Fl D Ar destdir Specify the DESTDIR (overriding values from the environment). .It Fl d Ar distbase Specify the DISTBASE (overriding values from the environment). .It Fl M Ar metalog Specify the path of the METALOG file (default: $DESTDIR/METALOG). .It Fl n No-Op mode, do not actually perform any actions. .It Fl v Be verbose, print details about actions before performing them. .It Fl U Unprivileged mode, do not change the ownership of created links. Do record the ownership in the METALOG file. .El .Pp Primary command functions: .Bl -tag -width untrusted .It Ic list List all currently trusted certificate authorities. .It Ic untrusted List all currently untrusted certificates. .It Ic rehash Rebuild the list of trusted certificate authorities by scanning all directories in .Ev TRUSTPATH and all untrusted certificates in .Ev UNTRUSTPATH . A symbolic link to each trusted certificate is placed in .Ev CERTDESTDIR and each untrusted certificate in .Ev UNTRUSTDESTDIR . .It Ic untrust Add the specified file to the untrusted list. .It Ic trust Remove the specified file from the untrusted list. .El .Sh ENVIRONMENT .Bl -tag -width UNTRUSTDESTDIR .It Ev DESTDIR Alternate destination directory to operate on. .It Ev DISTBASE Additional path component to include when operating on certificate directories. .It Ev LOCALBASE Location for local programs. Defaults to the value of the user.localbase sysctl which is usually .Pa /usr/local . .It Ev TRUSTPATH List of paths to search for trusted certificates. Default: .Pa /usr/share/certs/trusted .Pa /usr/local/share/certs .Pa /etc/ssl/certs .It Ev UNTRUSTPATH List of paths to search for untrusted certificates. Default: .Pa /usr/share/certs/untrusted .Pa /etc/ssl/untrusted .Pa /etc/ssl/blacklisted .It Ev CERTDESTDIR Destination directory for symbolic links to trusted certificates. Default: .Pa /etc/ssl/certs .It Ev UNTRUSTDESTDIR Destination directory for symbolic links to untrusted certificates. Default: .Pa /etc/ssl/untrusted .It Ev EXTENSIONS List of file extensions to read as certificate files. Default: *.pem *.crt *.cer *.crl *.0 .El .Sh SEE ALSO .Xr openssl 1 .Sh HISTORY .Nm first appeared in .Fx 12.2 .Sh AUTHORS .An Allan Jude Aq Mt allanjude@freebsd.org diff --git a/usr.sbin/certctl/certctl.sh b/usr.sbin/certctl/certctl.sh index 458f5c53682f..2bde651de126 100755 --- a/usr.sbin/certctl/certctl.sh +++ b/usr.sbin/certctl/certctl.sh @@ -1,366 +1,366 @@ #!/bin/sh #- # SPDX-License-Identifier: BSD-2-Clause # # Copyright 2018 Allan Jude # # Redistribution and use in source and binary forms, with or without # modification, are permitted providing that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # set -u ############################################################ CONFIGURATION : ${DESTDIR:=} : ${DISTBASE:=} ############################################################ GLOBALS SCRIPTNAME="${0##*/}" +LINK=-lrs ERRORS=0 NOOP=false UNPRIV=false VERBOSE=false ############################################################ FUNCTIONS info() { echo "${0##*/}: $@" >&2 } verbose() { if "${VERBOSE}" ; then info "$@" fi } perform() { if ! "${NOOP}" ; then "$@" fi } cert_files_in() { find -L "$@" -type f \( \ -name '*.pem' -or \ -name '*.crt' -or \ -name '*.cer' \ \) 2>/dev/null } eolcvt() { cat "$@" | tr -s '\r' '\n' } do_hash() { local hash if hash=$(openssl x509 -noout -subject_hash -in "$1") ; then echo "$hash" return 0 else info "Error: $1" ERRORS=$((ERRORS + 1)) return 1 fi } get_decimal() { local checkdir hash decimal checkdir=$1 hash=$2 decimal=0 while [ -e "$checkdir/$hash.$decimal" ] ; do decimal=$((decimal + 1)) done echo ${decimal} return 0 } create_trusted() { local hash certhash otherfile otherhash local suffix - local link=${2:+-lrs} hash=$(do_hash "$1") || return certhash=$(openssl x509 -sha1 -in "$1" -noout -fingerprint) for otherfile in $(find $UNTRUSTDESTDIR -name "$hash.*") ; do otherhash=$(openssl x509 -sha1 -in "$otherfile" -noout -fingerprint) if [ "$certhash" = "$otherhash" ] ; then info "Skipping untrusted certificate $hash ($otherfile)" return 0 fi done for otherfile in $(find $CERTDESTDIR -name "$hash.*") ; do otherhash=$(openssl x509 -sha1 -in "$otherfile" -noout -fingerprint) if [ "$certhash" = "$otherhash" ] ; then verbose "Skipping duplicate entry for certificate $hash" return 0 fi done suffix=$(get_decimal "$CERTDESTDIR" "$hash") verbose "Adding $hash.$suffix to trust store" - perform install ${INSTALLFLAGS} -m 0444 ${link} \ + perform install ${INSTALLFLAGS} -m 0444 ${LINK} \ "$(realpath "$1")" "$CERTDESTDIR/$hash.$suffix" } # Accepts either dot-hash form from `certctl list` or a path to a valid cert. resolve_certname() { local hash srcfile filename local suffix # If it exists as a file, we'll try that; otherwise, we'll scan if [ -e "$1" ] ; then hash=$(do_hash "$1") || return srcfile=$(realpath "$1") suffix=$(get_decimal "$UNTRUSTDESTDIR" "$hash") filename="$hash.$suffix" echo "$srcfile" "$hash.$suffix" elif [ -e "${CERTDESTDIR}/$1" ] ; then srcfile=$(realpath "${CERTDESTDIR}/$1") hash=$(echo "$1" | sed -Ee 's/\.([0-9])+$//') suffix=$(get_decimal "$UNTRUSTDESTDIR" "$hash") filename="$hash.$suffix" echo "$srcfile" "$hash.$suffix" fi } create_untrusted() { local srcfile filename - local link=${2:+-lrs} set -- $(resolve_certname "$1") srcfile=$1 filename=$2 if [ -z "$srcfile" -o -z "$filename" ] ; then return fi verbose "Adding $filename to untrusted list" - perform install ${INSTALLFLAGS} -m 0444 ${link} \ + perform install ${INSTALLFLAGS} -m 0444 ${LINK} \ "$srcfile" "$UNTRUSTDESTDIR/$filename" } do_scan() { local CFUNC CSEARCH CPATH CFILE CERT SPLITDIR local oldIFS="$IFS" CFUNC="$1" CSEARCH="$2" IFS=: set -- $CSEARCH IFS="$oldIFS" for CFILE in $(cert_files_in "$@") ; do verbose "Reading $CFILE" case $(eolcvt "$CFILE" | egrep -c '^-+BEGIN CERTIFICATE-+$') in 0) ;; 1) - "$CFUNC" "$CFILE" link + "$CFUNC" "$CFILE" ;; *) verbose "Multiple certificates found, splitting..." SPLITDIR=$(mktemp -d) eolcvt "$CFILE" | egrep '^(---|[0-9A-Za-z/+=]+$)' | \ split -p '^-+BEGIN CERTIFICATE-+$' - "$SPLITDIR/x" for CERT in $(find "$SPLITDIR" -type f) ; do "$CFUNC" "$CERT" done rm -rf "$SPLITDIR" ;; esac done } do_list() { local CFILE subject for CFILE in $(find "$@" \( -type f -or -type l \) -name '*.[0-9]') ; do if [ ! -s "$CFILE" ] ; then info "Unable to read $CFILE" ERRORS=$((ERRORS + 1)) continue fi subject= if ! "$VERBOSE" ; then subject=$(openssl x509 -noout -subject -nameopt multiline -in "$CFILE" | sed -n '/commonName/s/.*= //p') fi if [ -z "$subject" ] ; then subject=$(openssl x509 -noout -subject -in "$CFILE") fi printf "%s\t%s\n" "${CFILE##*/}" "$subject" done } cmd_rehash() { if [ -e "$CERTDESTDIR" ] ; then perform find "$CERTDESTDIR" \( -type f -or -type l \) -delete else perform install -d -m 0755 "$CERTDESTDIR" fi if [ -e "$UNTRUSTDESTDIR" ] ; then perform find "$UNTRUSTDESTDIR" \( -type f -or -type l \) -delete else perform install -d -m 0755 "$UNTRUSTDESTDIR" fi do_scan create_untrusted "$UNTRUSTPATH" do_scan create_trusted "$TRUSTPATH" } cmd_list() { info "Listing Trusted Certificates:" do_list "$CERTDESTDIR" } cmd_untrust() { local UTFILE shift # verb perform install -d -m 0755 "$UNTRUSTDESTDIR" for UTFILE in "$@"; do info "Adding $UTFILE to untrusted list" create_untrusted "$UTFILE" done } cmd_trust() { local UTFILE untrustedhash certhash hash shift # verb for UTFILE in "$@"; do if [ -s "$UTFILE" ] ; then hash=$(do_hash "$UTFILE") certhash=$(openssl x509 -sha1 -in "$UTFILE" -noout -fingerprint) for UNTRUSTEDFILE in $(find $UNTRUSTDESTDIR -name "$hash.*") ; do untrustedhash=$(openssl x509 -sha1 -in "$UNTRUSTEDFILE" -noout -fingerprint) if [ "$certhash" = "$untrustedhash" ] ; then info "Removing $(basename "$UNTRUSTEDFILE") from untrusted list" perform rm -f $UNTRUSTEDFILE fi done elif [ -e "$UNTRUSTDESTDIR/$UTFILE" ] ; then info "Removing $UTFILE from untrusted list" perform rm -f "$UNTRUSTDESTDIR/$UTFILE" else info "Cannot find $UTFILE" ERRORS=$((ERRORS + 1)) fi done } cmd_untrusted() { info "Listing Untrusted Certificates:" do_list "$UNTRUSTDESTDIR" } usage() { exec >&2 echo "Manage the TLS trusted certificates on the system" echo " $SCRIPTNAME [-v] list" echo " List trusted certificates" echo " $SCRIPTNAME [-v] untrusted" echo " List untrusted certificates" - echo " $SCRIPTNAME [-nUv] [-D ] [-d ] [-M ] rehash" - echo " Generate hash links for all certificates" - echo " $SCRIPTNAME [-nv] untrust " + echo " $SCRIPTNAME [-cnUv] [-D ] [-d ] [-M ] rehash" + echo " Rehash all trusted and untrusted certificates" + echo " $SCRIPTNAME [-cnv] untrust " echo " Add to the list of untrusted certificates" - echo " $SCRIPTNAME [-nv] trust " + echo " $SCRIPTNAME [-cnv] trust " echo " Remove from the list of untrusted certificates" exit 64 } ############################################################ MAIN -while getopts D:d:M:nUv flag; do +while getopts cD:d:M:nUv flag; do case "$flag" in + c) LINK=-c ;; D) DESTDIR=${OPTARG} ;; d) DISTBASE=${OPTARG} ;; M) METALOG=${OPTARG} ;; n) NOOP=true ;; U) UNPRIV=true ;; v) VERBOSE=true ;; esac done shift $((OPTIND - 1)) DESTDIR=${DESTDIR%/} if ! [ -z "${CERTCTL_VERBOSE:-}" ] ; then VERBOSE=true fi : ${METALOG:=${DESTDIR}/METALOG} INSTALLFLAGS= if "$UNPRIV" ; then - INSTALLFLAGS="-U -M ${METALOG} -D ${DESTDIR} -o root -g wheel" + INSTALLFLAGS="-U -M ${METALOG} -D ${DESTDIR:-/} -o root -g wheel" fi : ${LOCALBASE:=$(sysctl -n user.localbase)} : ${TRUSTPATH:=${DESTDIR}${DISTBASE}/usr/share/certs/trusted:${DESTDIR}${LOCALBASE}/share/certs:${DESTDIR}${LOCALBASE}/etc/ssl/certs} : ${UNTRUSTPATH:=${DESTDIR}${DISTBASE}/usr/share/certs/untrusted:${DESTDIR}${LOCALBASE}/etc/ssl/untrusted:${DESTDIR}${LOCALBASE}/etc/ssl/blacklisted} : ${CERTDESTDIR:=${DESTDIR}${DISTBASE}/etc/ssl/certs} : ${UNTRUSTDESTDIR:=${DESTDIR}${DISTBASE}/etc/ssl/untrusted} [ $# -gt 0 ] || usage case "$1" in list) cmd_list ;; rehash) cmd_rehash ;; blacklist) cmd_untrust "$@" ;; untrust) cmd_untrust "$@" ;; trust) cmd_trust "$@" ;; unblacklist) cmd_trust "$@" ;; untrusted) cmd_untrusted ;; blacklisted) cmd_untrusted ;; *) usage # NOTREACHED esac retval=$? if [ $ERRORS -gt 0 ] ; then info "Encountered $ERRORS errors" fi exit $retval ################################################################################ # END ################################################################################