freebsd-src/usr.sbin/certctl/certctl.sh
Mark Peek 3fed4f0db5 certctl: Revert to symlinks.
Unfortunately tar will not be able to extract base.txz to a system where
/etc and /usr are not on the same filesystem if the certificates are
hard links.

PR:		277828
Reviewed by:	mp
Differential Revision:	https://reviews.freebsd.org/D44496
2024-03-25 16:59:02 +01:00

367 lines
8.8 KiB
Bash
Executable file

#!/bin/sh
#-
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright 2018 Allan Jude <allanjude@freebsd.org>
#
# 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##*/}"
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} \
"$(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} \
"$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
;;
*)
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 <destdir>] [-d <distbase>] [-M <metalog>] rehash"
echo " Generate hash links for all certificates"
echo " $SCRIPTNAME [-nv] untrust <file>"
echo " Add <file> to the list of untrusted certificates"
echo " $SCRIPTNAME [-nv] trust <file>"
echo " Remove <file> from the list of untrusted certificates"
exit 64
}
############################################################ MAIN
while getopts D:d:M:nUv flag; do
case "$flag" in
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}"
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
################################################################################