xref: /freebsd/usr.sbin/certctl/tests/certctl_test.sh (revision 03221b189a48a509c1bc9adb8331638ae3eac065)
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}
185rehash_body()
186{
187	certctl_setup
188	atf_check certctl rehash
189
190	# Verify non-colliding trusted certificates
191	(set1; set2) >trusted
192	while read crtname hash ; do
193		check_trusted "${crtname}"
194	done <trusted
195
196	# Verify colliding trusted certificates
197	coll >coll
198	while read crtname hash ; do
199		check_trusted "${crtname}" $(wc -l <coll)
200	done <coll
201
202	# Verify untrusted certificates
203	set3 >untrusted
204	while read crtname hash ; do
205		check_untrusted "${crtname}"
206	done <untrusted
207
208	# Verify bundle
209	for f in etc/ssl/certs/*.? ; do
210		check_in_bundle "${f}"
211	done
212	for f in etc/ssl/untrusted/*.? ; do
213		check_not_in_bundle "${f}"
214	done
215}
216
217atf_test_case list
218list_head()
219{
220	atf_set "descr" "Test the list and untrusted commands"
221}
222list_body()
223{
224	certctl_setup
225	atf_check certctl rehash
226
227	atf_check -o save:trusted.out certctl list
228	sortfile trusted.out
229	# the ordering of the colliding certificates is partly
230	# determined by fields that change every time we regenerate
231	# them, so ignore them in the diff
232	atf_check diff -u \
233	    --ignore-matching-lines $collhash \
234	    trusted.expect trusted.out
235
236	atf_check -o save:untrusted.out certctl untrusted
237	sortfile untrusted.out
238	atf_check diff -u \
239	    untrusted.expect untrusted.out
240}
241
242atf_test_case trust
243trust_head()
244{
245	atf_set "descr" "Test the trust command"
246}
247trust_body()
248{
249	certctl_setup
250	atf_check certctl rehash
251	crtname=$(set3 | (read crtname hash ; echo ${crtname}))
252	crtfile=usr/share/certs/untrusted/${crtname}.crt
253	check_untrusted ${crtname}
254	check_not_in_bundle ${crtfile}
255	atf_check -e match:"was previously untrusted" \
256	    certctl trust ${crtfile}
257	check_trusted ${crtname}
258	check_in_bundle ${crtfile}
259}
260
261atf_test_case untrust
262untrust_head()
263{
264	atf_set "descr" "Test the untrust command"
265}
266untrust_body()
267{
268	certctl_setup
269	atf_check certctl rehash
270	crtname=$(set1 | (read crtname hash ; echo ${crtname}))
271	crtfile=usr/share/certs/trusted/${crtname}.crt
272	check_trusted "${crtname}"
273	check_in_bundle ${crtfile}
274	atf_check certctl untrust "${crtfile}"
275	check_untrusted "${crtname}"
276	check_not_in_bundle ${crtfile}
277}
278
279atf_test_case metalog
280metalog_head()
281{
282	atf_set "descr" "Verify the metalog"
283}
284metalog_body()
285{
286	export DISTBASE=/base
287	certctl_setup
288
289	# certctl gets DESTDIR and DISTBASE from environment
290	rm -f metalog.orig
291	atf_check certctl -U -M metalog.orig rehash
292	sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short
293	atf_check diff -u metalog.expect metalog.short
294
295	# certctl gets DESTDIR and DISTBASE from command line
296	rm -f metalog.orig
297	atf_check env -uDESTDIR -uDISTBASE \
298	    certctl -D ${DESTDIR} -d ${DISTBASE} -U -M metalog.orig rehash
299	sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short
300	atf_check diff -u metalog.expect metalog.short
301
302	# as above, but intentionally add trailing slashes
303	rm -f metalog.orig
304	atf_check env -uDESTDIR -uDISTBASE \
305	    certctl -D ${DESTDIR}// -d ${DISTBASE}/ -U -M metalog.orig rehash
306	sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short
307	atf_check diff -u metalog.expect metalog.short
308}
309
310atf_test_case misc
311misc_head()
312{
313	atf_set "descr" "Test miscellaneous edge cases"
314}
315misc_body()
316{
317	# certctl rejects DISTBASE that does not begin with a slash
318	atf_check -s exit:1 -e match:"begin with a slash" \
319	    certctl -d base -n rehash
320	atf_check -s exit:1 -e match:"begin with a slash" \
321	    env DISTBASE=base certctl -n rehash
322}
323
324atf_init_test_cases()
325{
326	atf_add_test_case rehash
327	atf_add_test_case list
328	atf_add_test_case trust
329	atf_add_test_case untrust
330	atf_add_test_case metalog
331	atf_add_test_case misc
332}
333