mirror of
https://git.FreeBSD.org/src.git
synced 2024-10-19 02:29:40 +00:00
f7d16a627e
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
367 lines
8.7 KiB
Bash
Executable File
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
|
|
################################################################################
|