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