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$"} 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 59get_decimal() 60{ 61 local checkdir hash decimal 62 63 checkdir=$1 64 hash=$2 65 decimal=0 66 67 while [ -e "$checkdir/$hash.$decimal" ]; do 68 decimal=$((decimal + 1)) 69 done 70 71 echo ${decimal} 72 return 0 73} 74 75create_trusted_link() 76{ 77 local blisthash certhash hash 78 local suffix 79 80 hash=$( do_hash "$1" ) || return 81 certhash=$( openssl x509 -sha1 -in "$1" -noout -fingerprint ) 82 for blistfile in $(find $BLACKLISTDESTDIR -name "$hash.*"); do 83 blisthash=$( openssl x509 -sha1 -in "$blistfile" -noout -fingerprint ) 84 if [ "$certhash" = "$blisthash" ]; then 85 echo "Skipping blacklisted certificate $1 ($blistfile)" 86 return 1 87 fi 88 done 89 suffix=$(get_decimal "$CERTDESTDIR" "$hash") 90 [ $VERBOSE -gt 0 ] && echo "Adding $hash.$suffix to trust store" 91 [ $NOOP -eq 0 ] && \ 92 install ${INSTALLFLAGS} -lrs $(realpath "$1") "$CERTDESTDIR/$hash.$suffix" 93} 94 95create_blacklisted() 96{ 97 local hash srcfile filename 98 local suffix 99 100 # If it exists as a file, we'll try that; otherwise, we'll scan 101 if [ -e "$1" ]; then 102 hash=$( do_hash "$1" ) || return 103 srcfile=$(realpath "$1") 104 suffix=$(get_decimal "$BLACKLISTDESTDIR" "$hash") 105 filename="$hash.$suffix" 106 elif [ -e "${CERTDESTDIR}/$1" ]; then 107 srcfile=$(realpath "${CERTDESTDIR}/$1") 108 hash=$(echo "$1" | sed -Ee 's/\.([0-9])+$//') 109 suffix=$(get_decimal "$BLACKLISTDESTDIR" "$hash") 110 filename="$hash.$suffix" 111 else 112 return 113 fi 114 [ $VERBOSE -gt 0 ] && echo "Adding $filename to blacklist" 115 [ $NOOP -eq 0 ] && install ${INSTALLFLAGS} -lrs "$srcfile" "$BLACKLISTDESTDIR/$filename" 116} 117 118do_scan() 119{ 120 local CFUNC CSEARCH CPATH CFILE 121 local oldIFS="$IFS" 122 CFUNC="$1" 123 CSEARCH="$2" 124 125 IFS=: 126 set -- $CSEARCH 127 IFS="$oldIFS" 128 for CPATH in "$@"; do 129 [ -d "$CPATH" ] || continue 130 echo "Scanning $CPATH for certificates..." 131 for CFILE in $(ls -1 "${CPATH}" | grep -Ee "${FILEPAT}"); do 132 [ -e "$CPATH/$CFILE" ] || continue 133 [ $VERBOSE -gt 0 ] && echo "Reading $CFILE" 134 "$CFUNC" "$CPATH/$CFILE" 135 done 136 done 137} 138 139do_list() 140{ 141 local CFILE subject 142 143 if [ -e "$1" ]; then 144 cd "$1" 145 for CFILE in *.[0-9]; do 146 if [ ! -s "$CFILE" ]; then 147 echo "Unable to read $CFILE" >&2 148 ERRORS=$(( $ERRORS + 1 )) 149 continue 150 fi 151 subject= 152 if [ $VERBOSE -eq 0 ]; then 153 subject=$( openssl x509 -noout -subject -nameopt multiline -in "$CFILE" | 154 sed -n '/commonName/s/.*= //p' ) 155 fi 156 [ "$subject" ] || 157 subject=$( openssl x509 -noout -subject -in "$CFILE" ) 158 printf "%s\t%s\n" "$CFILE" "$subject" 159 done 160 cd - 161 fi 162} 163 164cmd_rehash() 165{ 166 167 if [ $NOOP -eq 0 ]; then 168 if [ -e "$CERTDESTDIR" ]; then 169 find "$CERTDESTDIR" -type link -delete 170 else 171 mkdir -p "$CERTDESTDIR" 172 fi 173 if [ -e "$BLACKLISTDESTDIR" ]; then 174 find "$BLACKLISTDESTDIR" -type link -delete 175 else 176 mkdir -p "$BLACKLISTDESTDIR" 177 fi 178 fi 179 180 do_scan create_blacklisted "$BLACKLISTPATH" 181 do_scan create_trusted_link "$TRUSTPATH" 182} 183 184cmd_list() 185{ 186 echo "Listing Trusted Certificates:" 187 do_list "$CERTDESTDIR" 188} 189 190cmd_blacklist() 191{ 192 local BPATH 193 194 shift # verb 195 [ $NOOP -eq 0 ] && mkdir -p "$BLACKLISTDESTDIR" 196 for BFILE in "$@"; do 197 echo "Adding $BFILE to blacklist" 198 create_blacklisted "$BFILE" 199 done 200} 201 202cmd_unblacklist() 203{ 204 local BFILE blisthash certhash hash 205 206 shift # verb 207 for BFILE in "$@"; do 208 if [ -s "$BFILE" ]; then 209 hash=$( do_hash "$BFILE" ) 210 certhash=$( openssl x509 -sha1 -in "$BFILE" -noout -fingerprint ) 211 for BLISTEDFILE in $(find $BLACKLISTDESTDIR -name "$hash.*"); do 212 blisthash=$( openssl x509 -sha1 -in "$BLISTEDFILE" -noout -fingerprint ) 213 if [ "$certhash" = "$blisthash" ]; then 214 echo "Removing $(basename "$BLISTEDFILE") from blacklist" 215 [ $NOOP -eq 0 ] && rm -f $BLISTEDFILE 216 fi 217 done 218 elif [ -e "$BLACKLISTDESTDIR/$BFILE" ]; then 219 echo "Removing $BFILE from blacklist" 220 [ $NOOP -eq 0 ] && rm -f "$BLACKLISTDESTDIR/$BFILE" 221 else 222 echo "Cannot find $BFILE" >&2 223 ERRORS=$(( $ERRORS + 1 )) 224 fi 225 done 226} 227 228cmd_blacklisted() 229{ 230 echo "Listing Blacklisted Certificates:" 231 do_list "$BLACKLISTDESTDIR" 232} 233 234usage() 235{ 236 exec >&2 237 echo "Manage the TLS trusted certificates on the system" 238 echo " $SCRIPTNAME [-v] list" 239 echo " List trusted certificates" 240 echo " $SCRIPTNAME [-v] blacklisted" 241 echo " List blacklisted certificates" 242 echo " $SCRIPTNAME [-nUv] [-D <destdir>] [-M <metalog>] rehash" 243 echo " Generate hash links for all certificates" 244 echo " $SCRIPTNAME [-nv] blacklist <file>" 245 echo " Add <file> to the list of blacklisted certificates" 246 echo " $SCRIPTNAME [-nv] unblacklist <file>" 247 echo " Remove <file> from the list of blacklisted certificates" 248 exit 64 249} 250 251############################################################ MAIN 252 253while getopts D:M:nUv flag; do 254 case "$flag" in 255 D) DESTDIR=${OPTARG} ;; 256 M) METALOG=${OPTARG} ;; 257 n) NOOP=1 ;; 258 U) UNPRIV=1 ;; 259 v) VERBOSE=$(( $VERBOSE + 1 )) ;; 260 esac 261done 262shift $(( $OPTIND - 1 )) 263 264: ${METALOG:=${DESTDIR}/METALOG} 265INSTALLFLAGS= 266[ $UNPRIV -eq 1 ] && INSTALLFLAGS="-U -M ${METALOG} -D ${DESTDIR}" 267: ${TRUSTPATH:=${DESTDIR}/usr/share/certs/trusted:${DESTDIR}/usr/local/share/certs:${DESTDIR}/usr/local/etc/ssl/certs} 268: ${BLACKLISTPATH:=${DESTDIR}/usr/share/certs/blacklisted:${DESTDIR}/usr/local/etc/ssl/blacklisted} 269: ${CERTDESTDIR:=${DESTDIR}/etc/ssl/certs} 270: ${BLACKLISTDESTDIR:=${DESTDIR}/etc/ssl/blacklisted} 271 272[ $# -gt 0 ] || usage 273case "$1" in 274list) cmd_list ;; 275rehash) cmd_rehash ;; 276blacklist) cmd_blacklist "$@" ;; 277unblacklist) cmd_unblacklist "$@" ;; 278blacklisted) cmd_blacklisted ;; 279*) usage # NOTREACHED 280esac 281 282retval=$? 283[ $ERRORS -gt 0 ] && echo "Encountered $ERRORS errors" >&2 284exit $retval 285 286################################################################################ 287# END 288################################################################################ 289