Index: usr.sbin/Makefile =================================================================== --- usr.sbin/Makefile +++ usr.sbin/Makefile @@ -8,6 +8,7 @@ binmiscctl \ camdd \ cdcontrol \ + certctl \ chkgrp \ chown \ chroot \ Index: usr.sbin/certctl/Makefile =================================================================== --- /dev/null +++ usr.sbin/certctl/Makefile @@ -0,0 +1,6 @@ +# $FreeBSD$ + +SCRIPTS=trustctl.sh +#MAN= trustctl.8 + +.include Index: usr.sbin/certctl/trustctl.sh =================================================================== --- /dev/null +++ usr.sbin/certctl/trustctl.sh @@ -0,0 +1,225 @@ +#!/bin/sh +#- +# SPDX-License-Identifier: BSD-2-Clause-FreeBSD +# +# 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. +# +# $FreeBSD$ + +############################################################ CONFIGURATION + +: ${TRUSTPATH:=/usr/share/certs/trusted:/usr/local/share/certs:/usr/local/etc/ssl/certs} +: ${BLACKLISTPATH:=/usr/share/certs/blacklisted:/usr/local/etc/ssl/blacklisted} +: ${CERTDESTDIR:=/etc/ssl/certs} +: ${BLACKLISTDESTDIR:=/etc/ssl/blacklisted} +: ${EXTENSIONS:="*.pem *.crt *.cer *.crl *.0"} + +############################################################ GLOBALS + +SCRIPTNAME="${0##*/}" +ERRORS=0 +VERBOSE=0 + +############################################################ FUNCTIONS + +do_hash() +{ + local hash + + if hash=$( openssl x509 -noout -subject_hash -in "$1" ); then + echo "$hash" + return 0 + else + echo "Error: $1" >&2 + ERRORS=$(( $ERRORS + 1 )) + return 1 + fi +} + +create_trusted_link() +{ + local hash + + hash=$( do_hash "$1" ) || return + if [ -e "$BLACKLISTDESTDIR/$hash.0" ]; then + echo "Skipping blacklisted certificate $1 ($BLACKLISTDESTDIR/$hash.0)" + return 1 + fi + [ $VERBOSE -gt 0 ] && echo "Adding $hash.0 to trust store" + ln -fs "$1" "$CERTDESTDIR/$hash.0" +} + +create_blacklisted() +{ + local hash + + hash=$( do_hash "$1" ) || return + [ $VERBOSE -gt 0 ] && echo "Adding $hash.0 to blacklist" + cp "$1" "$BLACKLISTDESTDIR/$hash.0" +} + +do_scan() +{ + local CFILE + + cd "$2" + for CFILE in $EXTENSIONS; do + [ -e "$CFILE" ] || continue + [ $VERBOSE -gt 0 ] && echo "Reading $CFILE" + "$1" "$2/$CFILE" + done + cd - +} + +do_list() +{ + local CFILE subject + + cd "$1" + for CFILE in *.0; do + if [ ! -s "$CFILE" ]; then + echo "Unable to read $CFILE" >&2 + 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 - +} + +cmd_rehash() +{ + local BPATH CPATH + local oldIFS="$IFS" + + rm -rf "$CERTDESTDIR" + mkdir -p "$CERTDESTDIR" + mkdir -p "$BLACKLISTDESTDIR" + + IFS=: + set -- $BLACKLISTPATH + IFS="$oldIFS" + for BPATH in "$@"; do + [ -d "$BPATH" ] || continue + echo "Scanning $BPATH for blacklisted certificates..." + do_scan create_blacklisted "$BPATH" + done + + IFS=: + set -- $TRUSTPATH + IFS="$oldIFS" + for CPATH in "$@"; do + [ -d "$CPATH" ] || continue + echo "Scanning $CPATH for trusted certificates..." + do_scan create_trusted_link "$CPATH" + done +} + +cmd_list() +{ + echo "Listing Trusted Certificates:" + do_list "$CERTDESTDIR" +} + +cmd_blacklist() +{ + local BPATH + + shift # verb + mkdir -p "$BLACKLISTDESTDIR" + for BFILE in "$@"; do + echo "Adding $BFILE to blacklist" + create_blacklisted "$BFILE" + done +} + +cmd_unblacklist() +{ + local BFILE hash + + shift # verb + for BFILE in "$@"; do + if [ -s "$BFILE" ]; then + hash=$( do_hash "$BFILE" ) + echo "Removing $hash.0 from blacklist" + rm -f "$BLACKLISTDESTDIR/$hash.0" + elif [ -e "$BLACKLISTDESTDIR/$BFILE" ]; then + echo "Removing $BFILE from blacklist" + rm -f "$BLACKLISTDESTDIR/$BFILE" + else + echo "Cannot find $BFILE" >&2 + ERRORS=$(( $ERRORS + 1 )) + fi + done +} + +cmd_blacklisted() +{ + echo "Listing Blacklisted Certificates:" + do_list "$BLACKLISTDESTDIR" +} + +usage() +{ + exec >&2 + echo "Manage the TLS trusted certificates on the system" + echo " $SCRIPTNAME list" + echo " List trusted certificates" + echo " $SCRIPTNAME blacklisted" + echo " List blacklisted certificates" + echo " $SCRIPTNAME rehash" + echo " Rehash the list of trusted certificates" + echo " $SCRIPTNAME blacklist " + echo " Add to the list of blacklisted certificates" + echo " $SCRIPTNAME unblacklist " + echo " Remove from the list of blacklisted certificates" + exit 64 +} + +############################################################ MAIN + +[ $# -gt 0 ] || usage +case "$1" in +list) cmd_list ;; +rehash) cmd_rehash ;; +blacklist) cmd_blacklist "$@" ;; +unblacklist) cmd_unblacklist "$@" ;; +blacklisted) cmd_blacklisted ;; +*) usage # NOTREACHED +esac + +retval=$? +[ $ERRORS -gt 0 ] && echo "Encountered $ERRORS errors" >&2 +exit $retval + +################################################################################ +# END +################################################################################