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