1#!/bin/sh 2#- 3# SPDX-License-Identifier: BSD-2-Clause-FreeBSD 4# 5# Copyright 2018 Allan Jude <allanjude@freebsd.org> 6# 7# Redistribution and use in source and binary forms, with or without 8# modification, are permitted providing that the following conditions 9# are met: 10# 1. Redistributions of source code must retain the above copyright 11# notice, this list of conditions and the following disclaimer. 12# 2. Redistributions in binary form must reproduce the above copyright 13# notice, this list of conditions and the following disclaimer in the 14# documentation and/or other materials provided with the distribution. 15# 16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 20# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 24# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 25# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26# POSSIBILITY OF SUCH DAMAGE. 27# 28# $FreeBSD$ 29 30############################################################ CONFIGURATION 31 32: ${DESTDIR:=} 33: ${FILEPAT:="\.pem$|\.crt$|\.cer$|\.crl$|\.0$"} 34: ${VERBOSE:=0} 35 36############################################################ GLOBALS 37 38SCRIPTNAME="${0##*/}" 39ERRORS=0 40NOOP=0 41UNPRIV=0 42 43############################################################ FUNCTIONS 44 45do_hash() 46{ 47 local hash 48 49 if hash=$( openssl x509 -noout -subject_hash -in "$1" ); then 50 echo "$hash" 51 return 0 52 else 53 echo "Error: $1" >&2 54 ERRORS=$(( $ERRORS + 1 )) 55 return 1 56 fi 57} 58 59create_trusted_link() 60{ 61 local hash 62 63 hash=$( do_hash "$1" ) || return 64 if [ -e "$BLACKLISTDESTDIR/$hash.0" ]; then 65 echo "Skipping blacklisted certificate $1 ($BLACKLISTDESTDIR/$hash.0)" 66 return 1 67 fi 68 [ $VERBOSE -gt 0 ] && echo "Adding $hash.0 to trust store" 69 [ $NOOP -eq 0 ] && install ${INSTALLFLAGS} -lrs $(realpath "$1") "$CERTDESTDIR/$hash.0" 70} 71 72create_blacklisted() 73{ 74 local hash srcfile filename 75 76 # If it exists as a file, we'll try that; otherwise, we'll scan 77 if [ -e "$1" ]; then 78 hash=$( do_hash "$1" ) || return 79 srcfile=$(realpath "$1") 80 filename="$hash.0" 81 elif [ -e "${CERTDESTDIR}/$1" ]; then 82 srcfile=$(realpath "${CERTDESTDIR}/$1") 83 filename="$1" 84 else 85 return 86 fi 87 [ $VERBOSE -gt 0 ] && echo "Adding $filename to blacklist" 88 [ $NOOP -eq 0 ] && install ${INSTALLFLAGS} -lrs "$srcfile" "$BLACKLISTDESTDIR/$filename" 89} 90 91do_scan() 92{ 93 local CFUNC CSEARCH CPATH CFILE 94 local oldIFS="$IFS" 95 CFUNC="$1" 96 CSEARCH="$2" 97 98 IFS=: 99 set -- $CSEARCH 100 IFS="$oldIFS" 101 for CPATH in "$@"; do 102 [ -d "$CPATH" ] || continue 103 echo "Scanning $CPATH for certificates..." 104 for CFILE in $(ls -1 "${CPATH}" | grep -Ee "${FILEPAT}"); do 105 [ -e "$CPATH/$CFILE" -a $UNPRIV -eq 0 ] || continue 106 [ $VERBOSE -gt 0 ] && echo "Reading $CFILE" 107 "$CFUNC" "$CPATH/$CFILE" 108 done 109 done 110} 111 112do_list() 113{ 114 local CFILE subject 115 116 if [ -e "$1" ]; then 117 cd "$1" 118 for CFILE in *.0; do 119 if [ ! -s "$CFILE" ]; then 120 echo "Unable to read $CFILE" >&2 121 ERRORS=$(( $ERRORS + 1 )) 122 continue 123 fi 124 subject= 125 if [ $VERBOSE -eq 0 ]; then 126 subject=$( openssl x509 -noout -subject -nameopt multiline -in "$CFILE" | 127 sed -n '/commonName/s/.*= //p' ) 128 fi 129 [ "$subject" ] || 130 subject=$( openssl x509 -noout -subject -in "$CFILE" ) 131 printf "%s\t%s\n" "$CFILE" "$subject" 132 done 133 cd - 134 fi 135} 136 137cmd_rehash() 138{ 139 140 if [ $NOOP -eq 0 ]; then 141 if [ -e "$CERTDESTDIR" ]; then 142 find "$CERTDESTDIR" -type link -delete 143 else 144 mkdir -p "$CERTDESTDIR" 145 fi 146 if [ -e "$BLACKLISTDESTDIR" ]; then 147 find "$BLACKLISTDESTDIR" -type link -delete 148 else 149 mkdir -p "$BLACKLISTDESTDIR" 150 fi 151 fi 152 153 do_scan create_blacklisted "$BLACKLISTPATH" 154 do_scan create_trusted_link "$TRUSTPATH" 155} 156 157cmd_list() 158{ 159 echo "Listing Trusted Certificates:" 160 do_list "$CERTDESTDIR" 161} 162 163cmd_blacklist() 164{ 165 local BPATH 166 167 shift # verb 168 [ $NOOP -eq 0 ] && mkdir -p "$BLACKLISTDESTDIR" 169 for BFILE in "$@"; do 170 echo "Adding $BFILE to blacklist" 171 create_blacklisted "$BFILE" 172 done 173} 174 175cmd_unblacklist() 176{ 177 local BFILE hash 178 179 shift # verb 180 for BFILE in "$@"; do 181 if [ -s "$BFILE" ]; then 182 hash=$( do_hash "$BFILE" ) 183 echo "Removing $hash.0 from blacklist" 184 [ $NOOP -eq 0 ] && rm -f "$BLACKLISTDESTDIR/$hash.0" 185 elif [ -e "$BLACKLISTDESTDIR/$BFILE" ]; then 186 echo "Removing $BFILE from blacklist" 187 [ $NOOP -eq 0 ] && rm -f "$BLACKLISTDESTDIR/$BFILE" 188 else 189 echo "Cannot find $BFILE" >&2 190 ERRORS=$(( $ERRORS + 1 )) 191 fi 192 done 193} 194 195cmd_blacklisted() 196{ 197 echo "Listing Blacklisted Certificates:" 198 do_list "$BLACKLISTDESTDIR" 199} 200 201usage() 202{ 203 exec >&2 204 echo "Manage the TLS trusted certificates on the system" 205 echo " $SCRIPTNAME [-v] list" 206 echo " List trusted certificates" 207 echo " $SCRIPTNAME [-v] blacklisted" 208 echo " List blacklisted certificates" 209 echo " $SCRIPTNAME [-nUv] [-D <destdir>] [-M <metalog>] rehash" 210 echo " Generate hash links for all certificates" 211 echo " $SCRIPTNAME [-nv] blacklist <file>" 212 echo " Add <file> to the list of blacklisted certificates" 213 echo " $SCRIPTNAME [-nv] unblacklist <file>" 214 echo " Remove <file> from the list of blacklisted certificates" 215 exit 64 216} 217 218############################################################ MAIN 219 220while getopts D:M:nUv flag; do 221 case "$flag" in 222 D) DESTDIR=${OPTARG} ;; 223 M) METALOG=${OPTARG} ;; 224 n) NOOP=1 ;; 225 U) UNPRIV=1 ;; 226 v) VERBOSE=$(( $VERBOSE + 1 )) ;; 227 esac 228done 229shift $(( $OPTIND - 1 )) 230 231: ${METALOG:=${DESTDIR}/METALOG} 232INSTALLFLAGS= 233[ $UNPRIV -eq 1 ] && INSTALLFLAGS=-U -M ${METALOG} -D ${DESTDIR} 234: ${TRUSTPATH:=${DESTDIR}/usr/share/certs/trusted:${DESTDIR}/usr/local/share/certs:${DESTDIR}/usr/local/etc/ssl/certs} 235: ${BLACKLISTPATH:=${DESTDIR}/usr/share/certs/blacklisted:${DESTDIR}/usr/local/etc/ssl/blacklisted} 236: ${CERTDESTDIR:=${DESTDIR}/etc/ssl/certs} 237: ${BLACKLISTDESTDIR:=${DESTDIR}/etc/ssl/blacklisted} 238 239[ $# -gt 0 ] || usage 240case "$1" in 241list) cmd_list ;; 242rehash) cmd_rehash ;; 243blacklist) cmd_blacklist "$@" ;; 244unblacklist) cmd_unblacklist "$@" ;; 245blacklisted) cmd_blacklisted ;; 246*) usage # NOTREACHED 247esac 248 249retval=$? 250[ $ERRORS -gt 0 ] && echo "Encountered $ERRORS errors" >&2 251exit $retval 252 253################################################################################ 254# END 255################################################################################ 256