1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-10-19 02:29:40 +00:00
freebsd/usr.sbin/certctl/certctl.sh
Dag-Erling Smørgrav f7d16a627e certctl: Convert line endings before inspecting files.
This ensures that certificate files or bundles with DOS or Mac line
endings are recognized as such and handled identically to those with
Unix line endings.

PR:		274952
Reviewed by:	allanjude
Differential Revision:	https://reviews.freebsd.org/D42490
2023-11-07 20:53:09 +01:00

367 lines
8.7 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:+-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 $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:+-lm}
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
################################################################################