xref: /freebsd/usr.sbin/certctl/tests/certctl_test.sh (revision a259b98fa211ed87bfee58c575de4e2de94ee0fa)
1#
2# Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org>
3#
4# SPDX-License-Identifier: BSD-2-Clause
5#
6
7. $(atf_get_srcdir)/certctl.subr
8
9# Random sets of eight non-colliding names
10set1()
11{
12	cat <<EOF
13AVOYKJHSLFHWPVQMKBHENUAHJTEGMCCB 0ca83bbe
14UYSYXKDNNJTYOQPBGIKQDHRJYZHTDPKK 0d9a6512
15LODHGFXMZYKGOKAYGWTMMYQJYHDATDDM 4e6219f5
16NBBTQHJLHKBFFFWJTHHSNKOQYMGLHLPW 5dd76abc
17BJFAQZXZHYQLIDDPCAQFPDMNXICUXBXW ad68573d
18IOKNTHVEVVIJMNMYAVILMEMQQWLVRESN b577803d
19BHGMAJJGNJPIVMHMFCUTJLGFROJICEKN c98a6338
20HCRFQMGDQJALMLUQNXMPGLXFLLJRODJW f50c6379
21EOF
22}
23
24set2()
25{
26	cat <<EOF
27GOHKZTSKIPDSYNLMGYXGLROPTATELXIU 30789c88
28YOOTYHEGHZIYFXOBLNKENPSJUDGOPJJU 7fadbc13
29ETRINNYBGKIENAVGOKVJYFSSHFZIJZRH 8ed664af
30DBFGMFFMRNLPQLQPOLXOEUVLCRXLRSWT 8f34355e
31WFOPBQPLQFHDHZOUQFEIDGSYDUOTSNDQ ac0471df
32HMNETZMGNIWRGXQCVZXVZGWSGFBRRDQC b32f1472
33SHFYBXDVAUACBFPPAIGDAQIAGYOYGMQE baca75fa
34PCBGDNVPYCDGNRQSGRSLXFHYKXLAVLHW ddeeae01
35EOF
36}
37
38set3()
39{
40	cat <<EOF
41NJWIRLPWAIICVJBKXXHFHLCPAERZATRL 000aa2e5
42RJAENDPOCZQEVCPFUWOWDXPCSMYJPVYC 021b95a3
43PQUQDSWHBNVLBTNBGONYRLGZZVEFXVLO 071e8c50
44VZEXRKJUPZSFBDWBOLUZXOGLNTEAPCZM 3af7bb9b
45ZXOWOXQTXNZMAMZIWVFDZDJEWOOAGAOH 48d5c7cc
46KQSFQYVJMFTMADIHJIWGSQISWKSHRYQO 509f5ba1
47AIECYSLWZOIEPJWWUTWSQXCNCIHHZHYI 8cb0c503
48RFHWDJZEPOFLMPGXAHVEJFHCDODAPVEV 9ae4e049
49EOF
50}
51
52# Random set of three colliding names
53collhash=f2888ce3
54coll()
55{
56	cat <<EOF
57EJFTZEOANQLOYPEHWWXBWEWEFVKHMSNA $collhash
58LEMRWZAZLKZLPPSFLNLQZVGKKBEOFYWG $collhash
59ZWUPHYWKKTVEFBJOLLPDAIKGRDFVXZID $collhash
60EOF
61}
62
63sortfile() {
64	for filename; do
65		sort "${filename}" >"${filename}"-
66		mv "${filename}"- "${filename}"
67	done
68}
69
70certctl_setup()
71{
72	export DESTDIR="$PWD"
73
74	# Create input directories
75	mkdir -p ${DESTDIR}${DISTBASE}/usr/share/certs/trusted
76	mkdir -p ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted
77	mkdir -p ${DESTDIR}/usr/local/share/certs
78
79	# Do not create output directories; certctl will take care of it
80	#mkdir -p ${DESTDIR}${DISTBASE}/etc/ssl/certs
81	#mkdir -p ${DESTDIR}${DISTBASE}/etc/ssl/untrusted
82
83	# Generate a random key
84	keyname="testkey"
85	gen_key ${keyname}
86
87	# Generate certificates
88	:>metalog.expect
89	:>trusted.expect
90	:>untrusted.expect
91	metalog() {
92		echo ".${DISTBASE}$@ type=file" >>metalog.expect
93	}
94	trusted() {
95		local crtname=$1
96		local filename=$2
97		printf "%s\t%s\n" "${filename}" "${crtname}" >>trusted.expect
98		metalog "/etc/ssl/certs/${filename}"
99	}
100	untrusted() {
101		local crtname=$1
102		local filename=$2
103		printf "%s\t%s\n" "${filename}" "${crtname}" >>untrusted.expect
104		metalog "/etc/ssl/untrusted/${filename}"
105	}
106	set1 | while read crtname hash ; do
107		gen_crt ${crtname} ${keyname}
108		mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/trusted
109		trusted "${crtname}" "${hash}.0"
110	done
111	local c=0
112	coll | while read crtname hash ; do
113		gen_crt ${crtname} ${keyname}
114		mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/trusted
115		trusted "${crtname}" "${hash}.${c}"
116		c=$((c+1))
117	done
118	set2 | while read crtname hash ; do
119		gen_crt ${crtname} ${keyname}
120		openssl x509 -in ${crtname}.crt
121		rm ${crtname}.crt
122		trusted "${crtname}" "${hash}.0"
123	done >usr/local/share/certs/bundle.crt
124	set3 | while read crtname hash ; do
125		gen_crt ${crtname} ${keyname}
126		mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted
127		untrusted "${crtname}" "${hash}.0"
128	done
129	metalog "/etc/ssl/cert.pem"
130	unset -f untrusted
131	unset -f trusted
132	unset -f metalog
133	sortfile *.expect
134}
135
136check_trusted() {
137	local crtname=$1
138	local subject="$(subject ${crtname})"
139	local c=${2:-1}
140
141	atf_check -e ignore -o match:"found: ${c}\$" \
142	    openssl storeutl -noout -subject "${subject}" \
143	    ${DESTDIR}${DISTBASE}/etc/ssl/certs
144	atf_check -e ignore -o not-match:"found: [1-9]"  \
145	    openssl storeutl -noout -subject "${subject}" \
146	    ${DESTDIR}${DISTBASE}/etc/ssl/untrusted
147}
148
149check_untrusted() {
150	local crtname=$1
151	local subject="$(subject ${crtname})"
152	local c=${2:-1}
153
154	atf_check -e ignore -o not-match:"found: [1-9]" \
155	    openssl storeutl -noout -subject "${subject}" \
156	    ${DESTDIR}/${DISTBASE}/etc/ssl/certs
157	atf_check -e ignore -o match:"found: ${c}\$" \
158	    openssl storeutl -noout -subject "${subject}" \
159	    ${DESTDIR}/${DISTBASE}/etc/ssl/untrusted
160}
161
162check_in_bundle() {
163	local b=${DISTBASE}${DISTBASE+/}
164	local crtfile=$1
165	local line
166
167	line=$(tail +5 "${crtfile}" | head -1)
168	atf_check grep -q "${line}" ${DESTDIR}${DISTBASE}/etc/ssl/cert.pem
169}
170
171check_not_in_bundle() {
172	local b=${DISTBASE}${DISTBASE+/}
173	local crtfile=$1
174	local line
175
176	line=$(tail +5 "${crtfile}" | head -1)
177	atf_check -s exit:1 grep -q "${line}" etc/ssl/cert.pem
178}
179
180atf_test_case rehash
181rehash_head()
182{
183	atf_set "descr" "Test the rehash command"
184	atf_set "require.progs" certctl
185}
186rehash_body()
187{
188	certctl_setup
189	atf_check certctl rehash
190
191	# Verify non-colliding trusted certificates
192	(set1; set2) >trusted
193	while read crtname hash ; do
194		check_trusted "${crtname}"
195	done <trusted
196
197	# Verify colliding trusted certificates
198	coll >coll
199	while read crtname hash ; do
200		check_trusted "${crtname}" $(wc -l <coll)
201	done <coll
202
203	# Verify untrusted certificates
204	set3 >untrusted
205	while read crtname hash ; do
206		check_untrusted "${crtname}"
207	done <untrusted
208
209	# Verify bundle
210	for f in etc/ssl/certs/*.? ; do
211		check_in_bundle "${f}"
212	done
213	for f in etc/ssl/untrusted/*.? ; do
214		check_not_in_bundle "${f}"
215	done
216}
217
218atf_test_case list
219list_head()
220{
221	atf_set "descr" "Test the list and untrusted commands"
222	atf_set "require.progs" certctl
223}
224list_body()
225{
226	certctl_setup
227	atf_check certctl rehash
228
229	atf_check -o save:trusted.out certctl list
230	sortfile trusted.out
231	# the ordering of the colliding certificates is partly
232	# determined by fields that change every time we regenerate
233	# them, so ignore them in the diff
234	atf_check diff -u \
235	    --ignore-matching-lines $collhash \
236	    trusted.expect trusted.out
237
238	atf_check -o save:untrusted.out certctl untrusted
239	sortfile untrusted.out
240	atf_check diff -u \
241	    untrusted.expect untrusted.out
242}
243
244atf_test_case trust
245trust_head()
246{
247	atf_set "descr" "Test the trust command"
248	atf_set "require.progs" certctl
249}
250trust_body()
251{
252	certctl_setup
253	atf_check certctl rehash
254	crtname=$(set3 | (read crtname hash ; echo ${crtname}))
255	crtfile=usr/share/certs/untrusted/${crtname}.crt
256	check_untrusted ${crtname}
257	check_not_in_bundle ${crtfile}
258	atf_check -e match:"was previously untrusted" \
259	    certctl trust ${crtfile}
260	check_trusted ${crtname}
261	check_in_bundle ${crtfile}
262}
263
264atf_test_case untrust
265untrust_head()
266{
267	atf_set "descr" "Test the untrust command"
268	atf_set "require.progs" certctl
269}
270untrust_body()
271{
272	certctl_setup
273	atf_check certctl rehash
274	crtname=$(set1 | (read crtname hash ; echo ${crtname}))
275	crtfile=usr/share/certs/trusted/${crtname}.crt
276	check_trusted "${crtname}"
277	check_in_bundle ${crtfile}
278	atf_check -e match:"1 new untrusted" \
279	    certctl untrust "${crtfile}"
280	check_untrusted "${crtname}"
281	check_not_in_bundle ${crtfile}
282}
283
284atf_test_case metalog
285metalog_head()
286{
287	atf_set "descr" "Verify the metalog"
288	atf_set "require.progs" certctl
289}
290metalog_body()
291{
292	export DISTBASE=/base
293	certctl_setup
294
295	# certctl gets DESTDIR and DISTBASE from environment
296	rm -f metalog.orig
297	atf_check certctl -U -M metalog.orig rehash
298	sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short
299	atf_check diff -u metalog.expect metalog.short
300
301	# certctl gets DESTDIR and DISTBASE from command line
302	rm -f metalog.orig
303	atf_check env -uDESTDIR -uDISTBASE \
304	    certctl -D ${DESTDIR} -d ${DISTBASE} -U -M metalog.orig rehash
305	sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short
306	atf_check diff -u metalog.expect metalog.short
307
308	# as above, but intentionally add trailing slashes
309	rm -f metalog.orig
310	atf_check env -uDESTDIR -uDISTBASE \
311	    certctl -D ${DESTDIR}// -d ${DISTBASE}/ -U -M metalog.orig rehash
312	sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short
313	atf_check diff -u metalog.expect metalog.short
314}
315
316atf_test_case misc
317misc_head()
318{
319	atf_set "descr" "Test miscellaneous edge cases"
320	atf_set "require.progs" certctl
321}
322misc_body()
323{
324	# certctl rejects DISTBASE that does not begin with a slash
325	atf_check -s exit:1 -e match:"begin with a slash" \
326	    certctl -d base -n rehash
327	atf_check -s exit:1 -e match:"begin with a slash" \
328	    env DISTBASE=base certctl -n rehash
329}
330
331atf_init_test_cases()
332{
333	atf_add_test_case rehash
334	atf_add_test_case list
335	atf_add_test_case trust
336	atf_add_test_case untrust
337	atf_add_test_case metalog
338	atf_add_test_case misc
339}
340