certctl.sh revision 1.7 1 1.1 riastrad #!/bin/sh
2 1.1 riastrad
3 1.7 riastrad # $NetBSD: certctl.sh,v 1.7 2024/03/04 20:37:31 riastradh Exp $
4 1.1 riastrad #
5 1.1 riastrad # Copyright (c) 2023 The NetBSD Foundation, Inc.
6 1.1 riastrad # All rights reserved.
7 1.1 riastrad #
8 1.1 riastrad # Redistribution and use in source and binary forms, with or without
9 1.1 riastrad # modification, are permitted provided that the following conditions
10 1.1 riastrad # are met:
11 1.1 riastrad # 1. Redistributions of source code must retain the above copyright
12 1.1 riastrad # notice, this list of conditions and the following disclaimer.
13 1.1 riastrad # 2. Redistributions in binary form must reproduce the above copyright
14 1.1 riastrad # notice, this list of conditions and the following disclaimer in the
15 1.1 riastrad # documentation and/or other materials provided with the distribution.
16 1.1 riastrad #
17 1.1 riastrad # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18 1.1 riastrad # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 1.1 riastrad # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 1.1 riastrad # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21 1.1 riastrad # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 1.1 riastrad # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 1.1 riastrad # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 1.1 riastrad # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 1.1 riastrad # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 1.1 riastrad # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 1.1 riastrad # POSSIBILITY OF SUCH DAMAGE.
28 1.1 riastrad #
29 1.1 riastrad
30 1.1 riastrad set -o pipefail
31 1.1 riastrad set -Ceu
32 1.1 riastrad
33 1.6 riastrad progname=${0##*/}
34 1.1 riastrad
35 1.1 riastrad ### Options and arguments
36 1.1 riastrad
37 1.1 riastrad usage()
38 1.1 riastrad {
39 1.1 riastrad exec >&2
40 1.1 riastrad printf 'Usage: %s %s\n' \
41 1.1 riastrad "$progname" \
42 1.1 riastrad "[-nv] [-C <config>] [-c <certsdir>] [-u <untrusted>]"
43 1.1 riastrad printf ' <cmd> <args>...\n'
44 1.1 riastrad printf ' %s list\n' "$progname"
45 1.1 riastrad printf ' %s rehash\n' "$progname"
46 1.1 riastrad printf ' %s trust <cert>\n' "$progname"
47 1.1 riastrad printf ' %s untrust <cert>\n' "$progname"
48 1.1 riastrad printf ' %s untrusted\n' "$progname"
49 1.1 riastrad exit 1
50 1.1 riastrad }
51 1.1 riastrad
52 1.1 riastrad certsdir=/etc/openssl/certs
53 1.1 riastrad config=/etc/openssl/certs.conf
54 1.1 riastrad distrustdir=/etc/openssl/untrusted
55 1.1 riastrad nflag=false # dry run
56 1.1 riastrad vflag=false # verbose
57 1.1 riastrad
58 1.1 riastrad # Options used by FreeBSD:
59 1.1 riastrad #
60 1.1 riastrad # -D destdir
61 1.1 riastrad # -M metalog
62 1.1 riastrad # -U (unprivileged)
63 1.1 riastrad # -d distbase
64 1.1 riastrad #
65 1.1 riastrad while getopts C:c:nu:v f; do
66 1.1 riastrad case $f in
67 1.1 riastrad C) config=$OPTARG;;
68 1.1 riastrad c) certsdir=$OPTARG;;
69 1.1 riastrad n) nflag=true;;
70 1.1 riastrad u) distrustdir=$OPTARG;;
71 1.1 riastrad v) vflag=true;;
72 1.1 riastrad \?) usage;;
73 1.1 riastrad esac
74 1.1 riastrad done
75 1.1 riastrad shift $((OPTIND - 1))
76 1.1 riastrad
77 1.1 riastrad if [ $# -lt 1 ]; then
78 1.1 riastrad usage
79 1.1 riastrad fi
80 1.1 riastrad cmd=$1
81 1.1 riastrad
82 1.1 riastrad ### Global state
83 1.1 riastrad
84 1.1 riastrad config_paths=
85 1.1 riastrad config_manual=false
86 1.1 riastrad tmpfile=
87 1.1 riastrad
88 1.1 riastrad # If tmpfile is set to nonempty, clean it up on exit.
89 1.1 riastrad
90 1.1 riastrad trap 'test -n "$tmpfile" && rm -f "$tmpfile"' EXIT HUP INT TERM
91 1.1 riastrad
92 1.1 riastrad ### Subroutines
93 1.1 riastrad
94 1.1 riastrad # error <msg> ...
95 1.1 riastrad #
96 1.1 riastrad # Print an error message to stderr.
97 1.1 riastrad #
98 1.1 riastrad # Does not exit the process.
99 1.1 riastrad #
100 1.1 riastrad error()
101 1.1 riastrad {
102 1.1 riastrad echo "$progname:" "$@" >&2
103 1.1 riastrad }
104 1.1 riastrad
105 1.1 riastrad # run <cmd> <args>...
106 1.1 riastrad #
107 1.1 riastrad # Print a command if verbose, and run it unless it's a dry run.
108 1.1 riastrad #
109 1.1 riastrad run()
110 1.1 riastrad {
111 1.1 riastrad local t q cmdline
112 1.1 riastrad
113 1.1 riastrad if $vflag; then # print command if verbose
114 1.1 riastrad for t; do
115 1.1 riastrad case $t in
116 1.1 riastrad ''|*[^[:alnum:]+,-./:=_@]*)
117 1.1 riastrad # empty or unsafe -- quotify
118 1.1 riastrad ;;
119 1.1 riastrad *)
120 1.1 riastrad # nonempty and safe-only -- no quotify
121 1.1 riastrad cmdline="${cmdline:+$cmdline }$t"
122 1.1 riastrad continue
123 1.1 riastrad ;;
124 1.1 riastrad esac
125 1.1 riastrad q=$(printf '%s' "$t" | sed -e "s/'/'\\\''/g'")
126 1.1 riastrad cmdline="${cmdline:+$cmdline }'$q'"
127 1.1 riastrad done
128 1.1 riastrad printf '%s\n' "$cmdline"
129 1.1 riastrad fi
130 1.1 riastrad if ! $nflag; then # skip command if dry run
131 1.1 riastrad "$@"
132 1.1 riastrad fi
133 1.1 riastrad }
134 1.1 riastrad
135 1.1 riastrad # configure
136 1.1 riastrad #
137 1.1 riastrad # Parse the configuration file, initializing config_*.
138 1.1 riastrad #
139 1.1 riastrad configure()
140 1.1 riastrad {
141 1.1 riastrad local lineno status formatok vconfig line contline op path vpath vop
142 1.1 riastrad
143 1.1 riastrad # Count line numbers, record a persistent error status to
144 1.1 riastrad # return at the end, and record whether we got a format line.
145 1.1 riastrad lineno=0
146 1.1 riastrad status=0
147 1.1 riastrad formatok=false
148 1.1 riastrad
149 1.1 riastrad # vis the config name for terminal-safe error messages.
150 1.1 riastrad vconfig=$(printf '%s' "$config" | vis -M)
151 1.1 riastrad
152 1.1 riastrad # Read and process each line of the config file.
153 1.1 riastrad while read -r line; do
154 1.1 riastrad lineno=$((lineno + 1))
155 1.1 riastrad
156 1.1 riastrad # If the line ends in an odd number of backslashes, it
157 1.1 riastrad # has a continuation line, so read on.
158 1.1 riastrad while expr "$line" : '^\(\\\\\)*\\' >/dev/null ||
159 1.1 riastrad expr "$line" : '^.*[^\\]\(\\\\\)*\\$' >/dev/null; do
160 1.1 riastrad if ! read -r contline; then
161 1.1 riastrad error "$vconfig:$lineno: premature end of file"
162 1.1 riastrad return 1
163 1.1 riastrad fi
164 1.1 riastrad line="$line$contline"
165 1.1 riastrad done
166 1.1 riastrad
167 1.1 riastrad # Skip blank lines and comments.
168 1.1 riastrad case $line in
169 1.1 riastrad ''|'#'*)
170 1.1 riastrad continue
171 1.1 riastrad ;;
172 1.1 riastrad esac
173 1.1 riastrad
174 1.1 riastrad # Require the first non-blank/comment line to identify
175 1.1 riastrad # the config file format.
176 1.1 riastrad if ! $formatok; then
177 1.1 riastrad if [ "$line" = "netbsd-certctl 20230816" ]; then
178 1.1 riastrad formatok=true
179 1.1 riastrad continue
180 1.1 riastrad else
181 1.1 riastrad error "$vconfig:$lineno: missing format line"
182 1.1 riastrad status=1
183 1.1 riastrad break
184 1.1 riastrad fi
185 1.1 riastrad fi
186 1.1 riastrad
187 1.1 riastrad # Split the line into words and dispatch on the first.
188 1.1 riastrad set -- $line
189 1.1 riastrad op=$1
190 1.1 riastrad case $op in
191 1.1 riastrad manual)
192 1.1 riastrad config_manual=true
193 1.1 riastrad ;;
194 1.1 riastrad path)
195 1.1 riastrad if [ $# -lt 2 ]; then
196 1.1 riastrad error "$vconfig:$lineno: missing path"
197 1.1 riastrad status=1
198 1.1 riastrad continue
199 1.1 riastrad fi
200 1.1 riastrad if [ $# -gt 3 ]; then
201 1.1 riastrad error "$vconfig:$lineno: excess args"
202 1.1 riastrad status=1
203 1.1 riastrad continue
204 1.1 riastrad fi
205 1.1 riastrad
206 1.1 riastrad # Unvis the path. Hack: if the user has had
207 1.1 riastrad # the audacity to choose a path ending in
208 1.1 riastrad # newlines, prevent the shell from consuming
209 1.1 riastrad # them so we don't choke on their subterfuge.
210 1.1 riastrad path=$(printf '%s.' "$2" | unvis)
211 1.1 riastrad path=${path%.}
212 1.1 riastrad
213 1.1 riastrad # Ensure the path is absolute. It is unclear
214 1.1 riastrad # what directory it should be relative to if
215 1.1 riastrad # not.
216 1.1 riastrad case $path in
217 1.1 riastrad /*)
218 1.1 riastrad ;;
219 1.1 riastrad *)
220 1.1 riastrad error "$vconfig:$lineno:" \
221 1.1 riastrad "relative path forbidden"
222 1.1 riastrad status=1
223 1.1 riastrad continue
224 1.1 riastrad ;;
225 1.1 riastrad esac
226 1.1 riastrad
227 1.1 riastrad # Record the vis-encoded path in a
228 1.1 riastrad # space-separated list.
229 1.1 riastrad vpath=$(printf '%s' "$path" | vis -M)
230 1.1 riastrad config_paths="$config_paths $vpath"
231 1.1 riastrad ;;
232 1.1 riastrad *)
233 1.1 riastrad vop=$(printf '%s' "$op" | vis -M)
234 1.1 riastrad error "$vconfig:$lineno: unknown command: $vop"
235 1.1 riastrad ;;
236 1.1 riastrad esac
237 1.2 riastrad done <$config || status=$?
238 1.1 riastrad
239 1.1 riastrad return $status
240 1.1 riastrad }
241 1.1 riastrad
242 1.1 riastrad # list_default_trusted
243 1.1 riastrad #
244 1.1 riastrad # List the vis-encoded certificate paths and their base names,
245 1.1 riastrad # separated by a space, for the certificates that are trusted by
246 1.1 riastrad # default according to the configuration.
247 1.1 riastrad #
248 1.1 riastrad # No order guaranteed; caller must sort.
249 1.1 riastrad #
250 1.1 riastrad list_default_trusted()
251 1.1 riastrad {
252 1.1 riastrad local vpath path cert base vcert vbase
253 1.1 riastrad
254 1.1 riastrad for vpath in $config_paths; do
255 1.1 riastrad path=$(printf '%s.' "$vpath" | unvis)
256 1.1 riastrad path=${path%.}
257 1.1 riastrad
258 1.1 riastrad # Enumerate the .pem, .cer, and .crt files.
259 1.1 riastrad for cert in "$path"/*.pem "$path"/*.cer "$path"/*.crt; do
260 1.1 riastrad # vis the certificate path.
261 1.1 riastrad vcert=$(printf '%s' "$cert" | vis -M)
262 1.1 riastrad
263 1.1 riastrad # If the file doesn't exist, then either:
264 1.1 riastrad #
265 1.1 riastrad # (a) it's a broken symlink, so fail;
266 1.1 riastrad # or
267 1.1 riastrad # (b) the shell glob failed to match,
268 1.1 riastrad # so ignore it and move on.
269 1.1 riastrad if [ ! -e "$cert" ]; then
270 1.1 riastrad if [ -h "$cert" ]; then
271 1.1 riastrad error "broken symlink: $vcert"
272 1.1 riastrad status=1
273 1.1 riastrad fi
274 1.1 riastrad continue
275 1.1 riastrad fi
276 1.1 riastrad
277 1.1 riastrad # Print the vis-encoded absolute path to the
278 1.1 riastrad # certificate and base name on a single line.
279 1.6 riastrad vbase=${vcert##*/}
280 1.1 riastrad printf '%s %s\n' "$vcert" "$vbase"
281 1.1 riastrad done
282 1.1 riastrad done
283 1.1 riastrad }
284 1.1 riastrad
285 1.1 riastrad # list_distrusted
286 1.1 riastrad #
287 1.1 riastrad # List the vis-encoded certificate paths and their base names,
288 1.1 riastrad # separated by a space, for the certificates that have been
289 1.1 riastrad # distrusted by the user.
290 1.1 riastrad #
291 1.1 riastrad # No order guaranteed; caller must sort.
292 1.1 riastrad #
293 1.1 riastrad list_distrusted()
294 1.1 riastrad {
295 1.1 riastrad local status link vlink cert vcert
296 1.1 riastrad
297 1.1 riastrad status=0
298 1.1 riastrad
299 1.1 riastrad for link in "$distrustdir"/*; do
300 1.1 riastrad # vis the link for terminal-safe error messages.
301 1.1 riastrad vlink=$(printf '%s' "$link" | vis -M)
302 1.1 riastrad
303 1.1 riastrad # The distrust directory must only have symlinks to
304 1.1 riastrad # certificates. If we find a non-symlink, print a
305 1.1 riastrad # warning and arrange to fail.
306 1.1 riastrad if [ ! -h "$link" ]; then
307 1.1 riastrad if [ ! -e "$link" ] && \
308 1.1 riastrad [ "$link" = "$distrustdir/*" ]; then
309 1.1 riastrad # Shell glob matched nothing -- just
310 1.1 riastrad # ignore it.
311 1.1 riastrad break
312 1.1 riastrad fi
313 1.1 riastrad error "distrusted non-symlink: $vlink"
314 1.1 riastrad status=1
315 1.1 riastrad continue
316 1.1 riastrad fi
317 1.1 riastrad
318 1.1 riastrad # Read the target of the symlink, nonrecursively. If
319 1.1 riastrad # the user has had the audacity to make a symlink whose
320 1.1 riastrad # target ends in newline, prevent the shell from
321 1.1 riastrad # consuming them so we don't choke on their subterfuge.
322 1.1 riastrad cert=$(readlink -n -- "$link" && printf .)
323 1.1 riastrad cert=${cert%.}
324 1.1 riastrad
325 1.1 riastrad # Warn if the target is relative. Although it is clear
326 1.1 riastrad # what directory it would be relative to, there might
327 1.1 riastrad # be issues with canonicalization.
328 1.1 riastrad case $cert in
329 1.1 riastrad /*)
330 1.1 riastrad ;;
331 1.1 riastrad *)
332 1.1 riastrad vlink=$(printf '%s' "$link" | vis -M)
333 1.1 riastrad vcert=$(printf '%s' "$cert" | vis -M)
334 1.1 riastrad error "distrusted relative symlink: $vlink -> $vcert"
335 1.1 riastrad ;;
336 1.1 riastrad esac
337 1.1 riastrad
338 1.1 riastrad # Print the vis-encoded absolute path to the
339 1.1 riastrad # certificate and base name on a single line.
340 1.1 riastrad vcert=$(printf '%s' "$cert" | vis -M)
341 1.6 riastrad vbase=${vcert##*/}
342 1.1 riastrad printf '%s %s\n' "$vcert" "$vbase"
343 1.1 riastrad done
344 1.1 riastrad
345 1.1 riastrad return $status
346 1.1 riastrad }
347 1.1 riastrad
348 1.1 riastrad # list_trusted
349 1.1 riastrad #
350 1.1 riastrad # List the trusted certificates, excluding the distrusted one, as
351 1.1 riastrad # one vis(3) line per certificate. Reject duplicate base names,
352 1.1 riastrad # since we will be creating symlinks to the same base names in
353 1.1 riastrad # the certsdir. Sorted lexicographically by vis-encoding.
354 1.1 riastrad #
355 1.1 riastrad list_trusted()
356 1.1 riastrad {
357 1.1 riastrad
358 1.1 riastrad # XXX Use dev/ino to match files instead of symlink targets?
359 1.1 riastrad
360 1.1 riastrad {
361 1.1 riastrad list_default_trusted \
362 1.1 riastrad | while read -r vcert vbase; do
363 1.1 riastrad printf 'trust %s %s\n' "$vcert" "$vbase"
364 1.1 riastrad done
365 1.1 riastrad
366 1.1 riastrad # XXX Find a good way to list the default-untrusted
367 1.1 riastrad # certificates, so if you have already distrusted one
368 1.1 riastrad # and it is removed from default-trust on update,
369 1.1 riastrad # nothing warns about this.
370 1.1 riastrad
371 1.1 riastrad # list_default_untrusted \
372 1.1 riastrad # | while read -r vcert vbase; do
373 1.1 riastrad # printf 'distrust %s %s\n' "$vcert" "$vbase"
374 1.1 riastrad # done
375 1.1 riastrad
376 1.1 riastrad list_distrusted \
377 1.1 riastrad | while read -r vcert vbase; do
378 1.1 riastrad printf 'distrust %s %s\n' "$vcert" "$vbase"
379 1.1 riastrad done
380 1.1 riastrad } | awk -v progname="$progname" '
381 1.1 riastrad BEGIN { status = 0 }
382 1.1 riastrad $1 == "trust" && $3 in trust && $2 != trust[$3] {
383 1.1 riastrad printf "%s: duplicate base name %s\n %s\n %s\n", \
384 1.1 riastrad progname, $3, trust[$3], $2 >"/dev/stderr"
385 1.1 riastrad status = 1
386 1.1 riastrad next
387 1.1 riastrad }
388 1.1 riastrad $1 == "trust" { trust[$3] = $2 }
389 1.1 riastrad $1 == "distrust" && !trust[$3] && !distrust[$3] {
390 1.1 riastrad printf "%s: distrusted certificate not found: %s\n", \
391 1.1 riastrad progname, $3 >"/dev/stderr"
392 1.1 riastrad status = 1
393 1.1 riastrad }
394 1.1 riastrad $1 == "distrust" && $2 in trust && $2 != trust[$3] {
395 1.1 riastrad printf "%s: distrusted certificate %s" \
396 1.1 riastrad " has multiple paths\n" \
397 1.1 riastrad " %s\n %s\n",
398 1.1 riastrad progname, $3, trust[$3], $2 >"/dev/stderr"
399 1.1 riastrad status = 1
400 1.1 riastrad }
401 1.1 riastrad $1 == "distrust" { distrust[$3] = 1 }
402 1.1 riastrad END {
403 1.1 riastrad for (vbase in trust) {
404 1.1 riastrad if (!distrust[vbase])
405 1.1 riastrad print trust[vbase]
406 1.1 riastrad }
407 1.1 riastrad exit status
408 1.1 riastrad }
409 1.1 riastrad ' | sort -u
410 1.1 riastrad }
411 1.1 riastrad
412 1.1 riastrad # rehash
413 1.1 riastrad #
414 1.1 riastrad # Delete and rebuild certsdir.
415 1.1 riastrad #
416 1.1 riastrad rehash()
417 1.1 riastrad {
418 1.1 riastrad local vcert cert certbase hash counter bundle vbundle
419 1.1 riastrad
420 1.1 riastrad # If manual operation is enabled, refuse to rehash the
421 1.1 riastrad # certsdir, but succeed anyway so this can safely be used in
422 1.1 riastrad # automated scripts.
423 1.1 riastrad if $config_manual; then
424 1.1 riastrad error "manual certificates enabled, not rehashing"
425 1.1 riastrad return
426 1.1 riastrad fi
427 1.1 riastrad
428 1.3 riastrad # Delete the active certificates symlink cache, if either it is
429 1.3 riastrad # empty or nonexistent, or it is tagged for use by certctl.
430 1.3 riastrad if [ -f "$certsdir/.certctl" ]; then
431 1.3 riastrad # Directory exists and is managed by certctl(8).
432 1.3 riastrad # Safe to delete it and everything in it.
433 1.4 riastrad run rm -rf -- "$certsdir"
434 1.3 riastrad elif [ -h "$certsdir" ]; then
435 1.3 riastrad # Paranoia: refuse to chase a symlink. (Caveat: this
436 1.3 riastrad # is not secure against an adversary who can recreate
437 1.3 riastrad # the symlink at any time. Just a helpful check for
438 1.3 riastrad # mistakes.)
439 1.3 riastrad error "certificates directory is a symlink"
440 1.3 riastrad return 1
441 1.3 riastrad elif [ ! -e "$certsdir" ]; then
442 1.3 riastrad # Directory doesn't exist at all. Nothing to do!
443 1.7 riastrad :
444 1.3 riastrad elif [ ! -d "$certsdir" ]; then
445 1.3 riastrad error "certificates directory is not a directory"
446 1.3 riastrad return 1
447 1.4 riastrad elif ! find -f "$certsdir" -- -maxdepth 0 -type d -empty -exit 1; then
448 1.3 riastrad # certsdir exists, is a directory, and is empty. Safe
449 1.3 riastrad # to delete it with rmdir and take it over.
450 1.4 riastrad run rmdir -- "$certsdir"
451 1.3 riastrad else
452 1.3 riastrad error "existing certificates; set manual or move them"
453 1.3 riastrad return 1
454 1.3 riastrad fi
455 1.4 riastrad run mkdir -- "$certsdir"
456 1.3 riastrad if $vflag; then
457 1.3 riastrad printf '# initialize %s\n' "$certsdir"
458 1.3 riastrad fi
459 1.3 riastrad if ! $nflag; then
460 1.3 riastrad printf 'This directory is managed by certctl(8).\n' \
461 1.3 riastrad >$certsdir/.certctl
462 1.3 riastrad fi
463 1.1 riastrad
464 1.1 riastrad # Create a temporary file for the single-file bundle. This
465 1.1 riastrad # will be automatically deleted on normal exit or
466 1.1 riastrad # SIGHUP/SIGINT/SIGTERM.
467 1.1 riastrad if ! $nflag; then
468 1.1 riastrad tmpfile=$(mktemp -t "$progname.XXXXXX")
469 1.1 riastrad fi
470 1.1 riastrad
471 1.1 riastrad # Recreate symlinks for all of the trusted certificates.
472 1.1 riastrad list_trusted \
473 1.1 riastrad | while read -r vcert; do
474 1.1 riastrad cert=$(printf '%s.' "$vcert" | unvis)
475 1.1 riastrad cert=${cert%.}
476 1.1 riastrad run ln -s -- "$cert" "$certsdir"
477 1.1 riastrad
478 1.1 riastrad # Add the certificate to the single-file bundle.
479 1.1 riastrad if ! $nflag; then
480 1.1 riastrad cat -- "$cert" >>$tmpfile
481 1.1 riastrad fi
482 1.1 riastrad done
483 1.1 riastrad
484 1.1 riastrad # Hash the directory with openssl.
485 1.1 riastrad #
486 1.1 riastrad # XXX Pass `-v' to openssl in a way that doesn't mix with our
487 1.1 riastrad # shell-safe verbose commands? (Need to handle `-n' too.)
488 1.1 riastrad run openssl rehash -- "$certsdir"
489 1.1 riastrad
490 1.1 riastrad # Install the single-file bundle.
491 1.1 riastrad bundle=$certsdir/ca-certificates.crt
492 1.1 riastrad vbundle=$(printf '%s' "$bundle" | vis -M)
493 1.1 riastrad $vflag && printf '# create %s\n' "$vbundle"
494 1.1 riastrad if ! $nflag; then
495 1.5 riastrad (umask 0022; cat <$tmpfile >${bundle}.tmp)
496 1.5 riastrad mv -f -- "${bundle}.tmp" "$bundle"
497 1.1 riastrad rm -f -- "$tmpfile"
498 1.1 riastrad tmpfile=
499 1.1 riastrad fi
500 1.1 riastrad }
501 1.1 riastrad
502 1.1 riastrad ### Commands
503 1.1 riastrad
504 1.1 riastrad usage_list()
505 1.1 riastrad {
506 1.1 riastrad exec >&2
507 1.1 riastrad printf 'Usage: %s list\n' "$progname"
508 1.1 riastrad exit 1
509 1.1 riastrad }
510 1.1 riastrad cmd_list()
511 1.1 riastrad {
512 1.1 riastrad test $# -eq 1 || usage_list
513 1.1 riastrad
514 1.1 riastrad configure
515 1.1 riastrad
516 1.1 riastrad list_trusted \
517 1.1 riastrad | while read -r vcert vbase; do
518 1.1 riastrad printf '%s\n' "$vcert"
519 1.1 riastrad done
520 1.1 riastrad }
521 1.1 riastrad
522 1.1 riastrad usage_rehash()
523 1.1 riastrad {
524 1.1 riastrad exec >&2
525 1.1 riastrad printf 'Usage: %s rehash\n' "$progname"
526 1.1 riastrad exit 1
527 1.1 riastrad }
528 1.1 riastrad cmd_rehash()
529 1.1 riastrad {
530 1.1 riastrad test $# -eq 1 || usage_rehash
531 1.1 riastrad
532 1.1 riastrad configure
533 1.1 riastrad
534 1.1 riastrad rehash
535 1.1 riastrad }
536 1.1 riastrad
537 1.1 riastrad usage_trust()
538 1.1 riastrad {
539 1.1 riastrad exec >&2
540 1.1 riastrad printf 'Usage: %s trust <cert>\n' "$progname"
541 1.1 riastrad exit 1
542 1.1 riastrad }
543 1.1 riastrad cmd_trust()
544 1.1 riastrad {
545 1.1 riastrad local cert vcert certbase vcertbase
546 1.1 riastrad
547 1.1 riastrad test $# -eq 2 || usage_trust
548 1.1 riastrad cert=$2
549 1.1 riastrad
550 1.1 riastrad configure
551 1.1 riastrad
552 1.1 riastrad # XXX Accept base name.
553 1.1 riastrad
554 1.1 riastrad # vis the certificate path for terminal-safe error messages.
555 1.1 riastrad vcert=$(printf '%s' "$cert" | vis -M)
556 1.1 riastrad
557 1.1 riastrad # Verify the certificate actually exists.
558 1.1 riastrad if [ ! -f "$cert" ]; then
559 1.1 riastrad error "no such certificate: $vcert"
560 1.1 riastrad return 1
561 1.1 riastrad fi
562 1.1 riastrad
563 1.1 riastrad # Verify we currently distrust a certificate by this base name.
564 1.6 riastrad certbase=${cert##*/}
565 1.1 riastrad if [ ! -h "$distrustdir/$certbase" ]; then
566 1.1 riastrad error "not currently distrusted: $vcert"
567 1.1 riastrad return 1
568 1.1 riastrad fi
569 1.1 riastrad
570 1.1 riastrad # Verify the certificate we distrust by this base name is the
571 1.1 riastrad # same one.
572 1.1 riastrad target=$(readlink -n -- "$distrustdir/$certbase" && printf .)
573 1.1 riastrad target=${target%.}
574 1.1 riastrad if [ "$cert" != "$target" ]; then
575 1.6 riastrad vcertbase=${vcert##*/}
576 1.1 riastrad error "distrusted $vcertbase does not point to $vcert"
577 1.1 riastrad return 1
578 1.1 riastrad fi
579 1.1 riastrad
580 1.1 riastrad # Remove the link from the distrusted directory, and rehash --
581 1.1 riastrad # quietly, so verbose output emphasizes the distrust part and
582 1.1 riastrad # not the whole certificate set.
583 1.1 riastrad run rm -- "$distrustdir/$certbase"
584 1.1 riastrad $vflag && echo '# rehash'
585 1.1 riastrad vflag=false
586 1.1 riastrad rehash
587 1.1 riastrad }
588 1.1 riastrad
589 1.1 riastrad usage_untrust()
590 1.1 riastrad {
591 1.1 riastrad exec >&2
592 1.1 riastrad printf 'Usage: %s untrust <cert>\n' "$progname"
593 1.1 riastrad exit 1
594 1.1 riastrad }
595 1.1 riastrad cmd_untrust()
596 1.1 riastrad {
597 1.1 riastrad local cert vcert certbase vcertbase target vtarget
598 1.1 riastrad
599 1.1 riastrad test $# -eq 2 || usage_untrust
600 1.1 riastrad cert=$2
601 1.1 riastrad
602 1.1 riastrad configure
603 1.1 riastrad
604 1.1 riastrad # vis the certificate path for terminal-safe error messages.
605 1.1 riastrad vcert=$(printf '%s' "$cert" | vis -M)
606 1.1 riastrad
607 1.1 riastrad # Verify the certificate actually exists. Otherwise, you might
608 1.1 riastrad # fail to distrust a certificate you intended to distrust,
609 1.1 riastrad # e.g. if you made a typo in its path.
610 1.1 riastrad if [ ! -f "$cert" ]; then
611 1.1 riastrad error "no such certificate: $vcert"
612 1.1 riastrad return 1
613 1.1 riastrad fi
614 1.1 riastrad
615 1.1 riastrad # Check whether this certificate is already distrusted.
616 1.1 riastrad # - If the same base name points to the same path, stop here.
617 1.1 riastrad # - Otherwise, fail noisily.
618 1.6 riastrad certbase=${cert##*/}
619 1.1 riastrad if [ -h "$distrustdir/$certbase" ]; then
620 1.1 riastrad target=$(readlink -n -- "$distrustdir/$certbase" && printf .)
621 1.1 riastrad target=${target%.}
622 1.1 riastrad if [ "$target" = "$cert" ]; then
623 1.1 riastrad $vflag && echo '# already distrusted'
624 1.1 riastrad return
625 1.1 riastrad fi
626 1.1 riastrad vcertbase=$(printf '%s' "$certbase" | vis -M)
627 1.1 riastrad vtarget=$(printf '%s' "$target" | vis -M)
628 1.1 riastrad error "distrusted $vcertbase at different path $vtarget"
629 1.1 riastrad return 1
630 1.1 riastrad fi
631 1.1 riastrad
632 1.1 riastrad # Create the distrustdir if needed, create a symlink in it, and
633 1.1 riastrad # rehash -- quietly, so verbose output emphasizes the distrust
634 1.1 riastrad # part and not the whole certificate set.
635 1.1 riastrad test -d "$distrustdir" || run mkdir -- "$distrustdir"
636 1.1 riastrad run ln -s -- "$cert" "$distrustdir"
637 1.1 riastrad $vflag && echo '# rehash'
638 1.1 riastrad vflag=false
639 1.1 riastrad rehash
640 1.1 riastrad }
641 1.1 riastrad
642 1.1 riastrad usage_untrusted()
643 1.1 riastrad {
644 1.1 riastrad exec >&2
645 1.1 riastrad printf 'Usage: %s untrusted\n' "$progname"
646 1.1 riastrad exit 1
647 1.1 riastrad }
648 1.1 riastrad cmd_untrusted()
649 1.1 riastrad {
650 1.1 riastrad test $# -eq 1 || usage_untrusted
651 1.1 riastrad
652 1.1 riastrad configure
653 1.1 riastrad
654 1.1 riastrad list_distrusted \
655 1.1 riastrad | while read -r vcert vbase; do
656 1.1 riastrad printf '%s\n' "$vcert"
657 1.1 riastrad done
658 1.1 riastrad }
659 1.1 riastrad
660 1.1 riastrad ### Main
661 1.1 riastrad
662 1.1 riastrad # We accept the following aliases for user interface compatibility with
663 1.1 riastrad # FreeBSD:
664 1.1 riastrad #
665 1.1 riastrad # blacklist = untrust
666 1.1 riastrad # blacklisted = untrusted
667 1.1 riastrad # unblacklist = trust
668 1.1 riastrad
669 1.1 riastrad case $cmd in
670 1.1 riastrad list) cmd_list "$@"
671 1.1 riastrad ;;
672 1.1 riastrad rehash) cmd_rehash "$@"
673 1.1 riastrad ;;
674 1.1 riastrad trust|unblacklist)
675 1.1 riastrad cmd_trust "$@"
676 1.1 riastrad ;;
677 1.1 riastrad untrust|blacklist)
678 1.1 riastrad cmd_untrust "$@"
679 1.1 riastrad ;;
680 1.1 riastrad untrusted|blacklisted)
681 1.1 riastrad cmd_untrusted "$@"
682 1.1 riastrad ;;
683 1.1 riastrad *) vcmd=$(printf '%s' "$cmd" | vis -M)
684 1.1 riastrad printf '%s: unknown command: %s\n' "$progname" "$vcmd" >&2
685 1.1 riastrad usage
686 1.1 riastrad ;;
687 1.1 riastrad esac
688