Home | History | Annotate | Line # | Download | only in certctl
t_certctl.sh revision 1.5
      1 #!/bin/sh
      2 
      3 #	$NetBSD: t_certctl.sh,v 1.5 2023/08/28 22:25:49 riastradh Exp $
      4 #
      5 # Copyright (c) 2023 The NetBSD Foundation, Inc.
      6 # All rights reserved.
      7 #
      8 # Redistribution and use in source and binary forms, with or without
      9 # modification, are permitted provided that the following conditions
     10 # are met:
     11 # 1. Redistributions of source code must retain the above copyright
     12 #    notice, this list of conditions and the following disclaimer.
     13 # 2. Redistributions in binary form must reproduce the above copyright
     14 #    notice, this list of conditions and the following disclaimer in the
     15 #    documentation and/or other materials provided with the distribution.
     16 #
     17 # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     18 # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     19 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     20 # PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     21 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     22 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     23 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     24 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     25 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     26 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     27 # POSSIBILITY OF SUCH DAMAGE.
     28 #
     29 
     30 CERTCTL="certctl -C certs.conf -c certs -u untrusted"
     31 
     32 # setupconf <subdir>...
     33 #
     34 #	Create certs/ and set up certs.conf to search the specified
     35 #	subdirectories of the source directory.
     36 #
     37 setupconf()
     38 {
     39 	local sep subdir dir
     40 
     41 	mkdir certs
     42 	cat <<EOF >certs.conf
     43 netbsd-certctl 20230816
     44 
     45 # comment at line start
     46 	# comment not at line start, plus some intentional whitespace
     47    
     48 # THE WHITESPACE ABOVE IS INTENTIONAL, DO NOT DELETE
     49 EOF
     50 	# Start with a continuation line separator; then switch to
     51 	# non-continuation lines.
     52 	sep=$(printf ' \\\n\t')
     53 	for subdir; do
     54 		dir=$(atf_get_srcdir)/$subdir
     55 		cat <<EOF >>certs.conf
     56 path$sep$(printf '%s' "$dir" | vis -M)
     57 EOF
     58 		sep=' '
     59 	done
     60 }
     61 
     62 # check_empty
     63 #
     64 #	Verify the certs directory is empty after dry runs or after
     65 #	clearing the directory.
     66 #
     67 check_empty()
     68 {
     69 	local why
     70 
     71 	why=${1:-dry run}
     72 	for x in certs/*; do
     73 		if [ -e "$x" -o -h "$x" ]; then
     74 			atf_fail "certs/ should be empty after $why"
     75 		fi
     76 	done
     77 }
     78 
     79 # check_nonempty
     80 #
     81 #	Verify the certs directory is nonempty.
     82 #
     83 check_nonempty()
     84 {
     85 	for x in certs/*.0; do
     86 		test -e "$x" && test -h "$x" && return
     87 	done
     88 	atf_fail "certs/ should be nonempty"
     89 }
     90 
     91 # checks <certsN>...
     92 #
     93 #	Run various checks with certctl.
     94 #
     95 checks()
     96 {
     97 	local certs1 diginotar_base diginotar diginotar_hash subdir srcdir
     98 
     99 	certs1=$(atf_get_srcdir)/certs1
    100 	diginotar_base=Explicitly_Distrust_DigiNotar_Root_CA.pem
    101 	diginotar=$certs1/$diginotar_base
    102 	diginotar_hash=$(openssl x509 -hash -noout <$diginotar)
    103 
    104 	# Do a dry run of rehash and make sure the directory is still
    105 	# empty.
    106 	atf_check -s exit:0 $CERTCTL -n rehash
    107 	check_empty
    108 
    109 	# Distrust and trust one CA, as a dry run.  The trust should
    110 	# fail because it's not currently distrusted.
    111 	atf_check -s exit:0 $CERTCTL -n untrust "$diginotar"
    112 	check_empty
    113 	atf_check -s not-exit:0 -e match:currently \
    114 	    $CERTCTL -n trust "$diginotar"
    115 	check_empty
    116 
    117 	# Do a real rehash, not a dry run.
    118 	atf_check -s exit:0 $CERTCTL rehash
    119 
    120 	# Make sure all the certificates are trusted.
    121 	for subdir; do
    122 		case $subdir in
    123 		/*)	srcdir=$subdir;;
    124 		*)	srcdir=$(atf_get_srcdir)/$subdir;;
    125 		esac
    126 		for cert in "$srcdir"/*.pem; do
    127 			# Verify the certificate is linked by its base name.
    128 			certbase=$(basename "$cert")
    129 			atf_check -s exit:0 -o inline:"$cert" \
    130 			    readlink -n "certs/$certbase"
    131 
    132 			# Verify the certificate is linked by a hash.
    133 			hash=$(openssl x509 -hash -noout <$cert)
    134 			counter=0
    135 			found=false
    136 			while [ $counter -lt 10 ]; do
    137 				if cmp -s "certs/$hash.$counter" "$cert"; then
    138 					found=true
    139 					break
    140 				fi
    141 				counter=$((counter + 1))
    142 			done
    143 			if ! $found; then
    144 				atf_fail "missing $cert"
    145 			fi
    146 
    147 			# Delete both links.
    148 			rm "certs/$certbase"
    149 			rm "certs/$hash.$counter"
    150 		done
    151 	done
    152 
    153 	# Verify the certificate bundle is there and delete it.
    154 	#
    155 	# XXX Verify its content.
    156 	atf_check -s exit:0 test -f certs/ca-certificates.crt
    157 	atf_check -s exit:0 test ! -h certs/ca-certificates.crt
    158 	rm certs/ca-certificates.crt
    159 
    160 	# Make sure after deleting everything there's nothing left.
    161 	check_empty "removing all expected certificates"
    162 
    163 	# Distrust, trust, and re-distrust one CA, and verify that it
    164 	# ceases to appear, reappears, and again ceases to appear.
    165 	# (This one has no subject hash collisions to worry about, so
    166 	# we hard-code the `.0' suffix.)
    167 	atf_check -s exit:0 $CERTCTL untrust "$diginotar"
    168 	atf_check -s exit:0 test -e "untrusted/$diginotar_base"
    169 	atf_check -s exit:0 test -h "untrusted/$diginotar_base"
    170 	atf_check -s exit:0 test ! -e "certs/$diginotar_base"
    171 	atf_check -s exit:0 test ! -h "certs/$diginotar_base"
    172 	atf_check -s exit:0 test ! -e "certs/$diginotar_hash.0"
    173 	atf_check -s exit:0 test ! -h "certs/$diginotar_hash.0"
    174 	check_nonempty
    175 
    176 	atf_check -s exit:0 $CERTCTL trust "$diginotar"
    177 	atf_check -s exit:0 test ! -e "untrusted/$diginotar_base"
    178 	atf_check -s exit:0 test ! -h "untrusted/$diginotar_base"
    179 	atf_check -s exit:0 test -e "certs/$diginotar_base"
    180 	atf_check -s exit:0 test -h "certs/$diginotar_base"
    181 	atf_check -s exit:0 test -e "certs/$diginotar_hash.0"
    182 	atf_check -s exit:0 test -h "certs/$diginotar_hash.0"
    183 	rm "certs/$diginotar_base"
    184 	rm "certs/$diginotar_hash.0"
    185 	check_nonempty
    186 
    187 	atf_check -s exit:0 $CERTCTL untrust "$diginotar"
    188 	atf_check -s exit:0 test -e "untrusted/$diginotar_base"
    189 	atf_check -s exit:0 test -h "untrusted/$diginotar_base"
    190 	atf_check -s exit:0 test ! -e "certs/$diginotar_base"
    191 	atf_check -s exit:0 test ! -h "certs/$diginotar_base"
    192 	atf_check -s exit:0 test ! -e "certs/$diginotar_hash.0"
    193 	atf_check -s exit:0 test ! -h "certs/$diginotar_hash.0"
    194 	check_nonempty
    195 }
    196 
    197 atf_test_case empty
    198 empty_head()
    199 {
    200 	atf_set "descr" "Test empty certificates store"
    201 }
    202 empty_body()
    203 {
    204 	setupconf		# no directories
    205 	check_empty "empty cert path"
    206 	atf_check -s exit:0 $CERTCTL -n rehash
    207 	check_empty
    208 	atf_check -s exit:0 $CERTCTL rehash
    209 	atf_check -s exit:0 test -f certs/ca-certificates.crt
    210 	atf_check -s exit:0 test \! -h certs/ca-certificates.crt
    211 	atf_check -s exit:0 test \! -s certs/ca-certificates.crt
    212 	atf_check -s exit:0 rm certs/ca-certificates.crt
    213 	check_empty "empty cert path"
    214 }
    215 
    216 atf_test_case onedir
    217 onedir_head()
    218 {
    219 	atf_set "descr" "Test one certificates directory"
    220 }
    221 onedir_body()
    222 {
    223 	setupconf certs1
    224 	checks certs1
    225 }
    226 
    227 atf_test_case twodir
    228 twodir_head()
    229 {
    230 	atf_set "descr" "Test two certificates directories"
    231 }
    232 twodir_body()
    233 {
    234 	setupconf certs1 certs2
    235 	checks certs1 certs2
    236 }
    237 
    238 atf_test_case collidehash
    239 collidehash_head()
    240 {
    241 	atf_set "descr" "Test colliding hashes"
    242 }
    243 collidehash_body()
    244 {
    245 	# certs3 has two certificates with the same subject hash
    246 	setupconf certs1 certs3
    247 	checks certs1 certs3
    248 }
    249 
    250 atf_test_case collidebase
    251 collidebase_head()
    252 {
    253 	atf_set "descr" "Test colliding base names"
    254 }
    255 collidebase_body()
    256 {
    257 	# certs1 and certs4 both have DigiCert_Global_Root_CA.pem,
    258 	# which should cause list and rehash to fail and mention
    259 	# duplicates.
    260 	setupconf certs1 certs4
    261 	atf_check -s not-exit:0 -o ignore -e match:duplicate $CERTCTL list
    262 	atf_check -s not-exit:0 -o ignore -e match:duplicate $CERTCTL rehash
    263 }
    264 
    265 atf_test_case manual
    266 manual_head()
    267 {
    268 	atf_set "descr" "Test manual operation"
    269 }
    270 manual_body()
    271 {
    272 	local certs1 diginotar_base diginotar diginotar_hash
    273 
    274 	certs1=$(atf_get_srcdir)/certs1
    275 	diginotar_base=Explicitly_Distrust_DigiNotar_Root_CA.pem
    276 	diginotar=$certs1/$diginotar_base
    277 	diginotar_hash=$(openssl x509 -hash -noout <$diginotar)
    278 
    279 	setupconf certs1 certs2
    280 	cat <<EOF >>certs.conf
    281 manual
    282 EOF
    283 	touch certs/bogus.pem
    284 	ln -s bogus.pem certs/0123abcd.0
    285 
    286 	# Listing shouldn't mention anything in the certs/ cache.
    287 	atf_check -s exit:0 -o not-match:bogus $CERTCTL list
    288 	atf_check -s exit:0 -o not-match:bogus $CERTCTL untrusted
    289 
    290 	# Rehashing and changing the configuration should succeed, but
    291 	# mention `manual' in a warning message and should not touch
    292 	# the cache.
    293 	atf_check -s exit:0 -e match:manual $CERTCTL rehash
    294 	atf_check -s exit:0 -e match:manual $CERTCTL untrust "$diginotar"
    295 	atf_check -s exit:0 -e match:manual $CERTCTL trust "$diginotar"
    296 
    297 	# The files we created should still be there.
    298 	atf_check -s exit:0 test -f certs/bogus.pem
    299 	atf_check -s exit:0 test -h certs/0123abcd.0
    300 }
    301 
    302 atf_test_case evilpath
    303 evilpath_head()
    304 {
    305 	atf_set "descr" "Test certificate paths with evil characters"
    306 }
    307 evilpath_body()
    308 {
    309 	local evildir
    310 
    311 	evildir="$(printf 'evil\n.')"
    312         evildir=${evildir%.}
    313         mkdir "$evildir"
    314 
    315         cp -p "$(atf_get_srcdir)/certs2"/*.pem "$evildir"/
    316 
    317         setupconf certs1
    318         cat <<EOF >>certs.conf
    319 path $(printf '%s' "$(pwd)/$evildir" | vis -M)
    320 EOF
    321         checks certs1 "$(pwd)/$evildir"
    322 }
    323 
    324 atf_test_case missingconf
    325 missingconf_head()
    326 {
    327 	atf_set "descr" "Test certctl with missing certs.conf"
    328 }
    329 missingconf_body()
    330 {
    331 	mkdir certs
    332 	atf_check -s exit:0 test ! -e certs.conf
    333 	atf_check -s not-exit:0 -e match:'certs\.conf' \
    334 	    $CERTCTL rehash
    335 }
    336 
    337 atf_test_case nonexistentcertsdir
    338 nonexistentcertsdir_head()
    339 {
    340 	atf_set "descr" "Test certctl succeeds when certsdir is nonexistent"
    341 }
    342 nonexistentcertsdir_body()
    343 {
    344 	setupconf certs1
    345 	rmdir certs
    346 	checks certs1
    347 }
    348 
    349 atf_test_case symlinkcertsdir
    350 symlinkcertsdir_head()
    351 {
    352 	atf_set "descr" "Test certctl fails when certsdir is a symlink"
    353 }
    354 symlinkcertsdir_body()
    355 {
    356 	setupconf certs1
    357 	rmdir certs
    358 	mkdir empty
    359 	ln -sfn empty certs
    360 
    361 	atf_check -s not-exit:0 -e match:symlink $CERTCTL -n rehash
    362 	atf_check -s not-exit:0 -e match:symlink $CERTCTL rehash
    363 	atf_check -s exit:0 rmdir empty
    364 }
    365 
    366 atf_test_case regularfilecertsdir
    367 regularfilecertsdir_head()
    368 {
    369 	atf_set "descr" "Test certctl fails when certsdir is a regular file"
    370 }
    371 regularfilecertsdir_body()
    372 {
    373 	setupconf certs1
    374 	rmdir certs
    375 	echo 'hello world' >certs
    376 
    377 	atf_check -s not-exit:0 -e match:directory $CERTCTL -n rehash
    378 	atf_check -s not-exit:0 -e match:directory $CERTCTL rehash
    379 	atf_check -s exit:0 rm certs
    380 }
    381 
    382 atf_test_case prepopulatedcerts
    383 prepopulatedcerts_head()
    384 {
    385 	atf_set "descr" "Test certctl fails when directory is prepopulated"
    386 }
    387 prepopulatedcerts_body()
    388 {
    389 	local cert certbase target
    390 
    391 	setupconf certs1
    392 	ln -sfn "$(atf_get_srcdir)/certs2"/*.pem certs/
    393 
    394 	atf_check -s not-exit:0 -e match:manual $CERTCTL -n rehash
    395 	atf_check -s not-exit:0 -e match:manual $CERTCTL rehash
    396 	for cert in "$(atf_get_srcdir)/certs2"/*.pem; do
    397 		certbase=$(basename "$cert")
    398 		atf_check -s exit:0 -o inline:"$cert" \
    399 		    readlink -n "certs/$certbase"
    400 		rm "certs/$certbase"
    401 	done
    402 	check_empty
    403 }
    404 
    405 atf_init_test_cases()
    406 {
    407 	atf_add_test_case collidebase
    408 	atf_add_test_case collidehash
    409 	atf_add_test_case empty
    410 	atf_add_test_case evilpath
    411 	atf_add_test_case manual
    412 	atf_add_test_case missingconf
    413 	atf_add_test_case nonexistentcertsdir
    414 	atf_add_test_case onedir
    415 	atf_add_test_case prepopulatedcerts
    416 	atf_add_test_case regularfilecertsdir
    417 	atf_add_test_case symlinkcertsdir
    418 	atf_add_test_case twodir
    419 }
    420