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