1#	$OpenBSD: cert-hostkey.sh,v 1.27 2021/09/30 05:26:26 dtucker Exp $
2#	Placed in the Public Domain.
3
4tid="certified host keys"
5
6rm -f $OBJ/known_hosts-cert* $OBJ/host_ca_key* $OBJ/host_revoked_*
7rm -f $OBJ/cert_host_key* $OBJ/host_krl_*
8
9# Allow all hostkey/pubkey types, prefer certs for the client
10rsa=0
11types=""
12for i in `$SSH -Q key | maybe_filter_sk`; do
13	if [ -z "$types" ]; then
14		types="$i"
15		continue
16	fi
17	case "$i" in
18	# Special treatment for RSA keys.
19	*rsa*cert*)
20		types="rsa-sha2-256-cert-v01@openssh.com,$i,$types"
21		types="rsa-sha2-512-cert-v01@openssh.com,$types";;
22	*rsa*)
23		rsa=1
24		types="$types,rsa-sha2-512,rsa-sha2-256,$i";;
25	# Prefer certificate to plain keys.
26	*cert*)	types="$i,$types";;
27	*)	types="$types,$i";;
28	esac
29done
30(
31	echo "HostKeyAlgorithms ${types}"
32	echo "PubkeyAcceptedAlgorithms *"
33) >> $OBJ/ssh_proxy
34cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
35(
36	echo "HostKeyAlgorithms *"
37	echo "PubkeyAcceptedAlgorithms *"
38) >> $OBJ/sshd_proxy_bak
39
40HOSTS='localhost-with-alias,127.0.0.1,::1'
41
42kh_ca() {
43	for k in "$@" ; do
44		printf "@cert-authority $HOSTS "
45		cat $OBJ/$k || fatal "couldn't cat $k"
46	done
47}
48kh_revoke() {
49	for k in "$@" ; do
50		printf "@revoked * "
51		cat $OBJ/$k || fatal "couldn't cat $k"
52	done
53}
54
55# Create a CA key and add it to known hosts. Ed25519 chosen for speed.
56# RSA for testing RSA/SHA2 signatures if supported.
57ktype2=ed25519
58[ "x$rsa" = "x1" ] && ktype2=rsa
59${SSHKEYGEN} -q -N '' -t ed25519  -f $OBJ/host_ca_key ||\
60	fail "ssh-keygen of host_ca_key failed"
61${SSHKEYGEN} -q -N '' -t $ktype2  -f $OBJ/host_ca_key2 ||\
62	fail "ssh-keygen of host_ca_key failed"
63
64kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig
65cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
66
67# Plain text revocation files
68touch $OBJ/host_revoked_empty
69touch $OBJ/host_revoked_plain
70touch $OBJ/host_revoked_cert
71cat $OBJ/host_ca_key.pub $OBJ/host_ca_key2.pub > $OBJ/host_revoked_ca
72
73PLAIN_TYPES=`echo "$SSH_KEYTYPES" | sed 's/^ssh-dss/ssh-dsa/g;s/^ssh-//'`
74
75if echo "$PLAIN_TYPES" | grep '^rsa$' >/dev/null 2>&1 ; then
76	PLAIN_TYPES="$PLAIN_TYPES rsa-sha2-256 rsa-sha2-512"
77fi
78
79# Prepare certificate, plain key and CA KRLs
80${SSHKEYGEN} -kf $OBJ/host_krl_empty || fatal "KRL init failed"
81${SSHKEYGEN} -kf $OBJ/host_krl_plain || fatal "KRL init failed"
82${SSHKEYGEN} -kf $OBJ/host_krl_cert || fatal "KRL init failed"
83${SSHKEYGEN} -kf $OBJ/host_krl_ca $OBJ/host_ca_key.pub $OBJ/host_ca_key2.pub \
84	|| fatal "KRL init failed"
85
86# Generate and sign host keys
87serial=1
88for ktype in $PLAIN_TYPES ; do
89	verbose "$tid: sign host ${ktype} cert"
90	# Generate and sign a host key
91	${SSHKEYGEN} -q -N '' -t ${ktype} \
92	    -f $OBJ/cert_host_key_${ktype} || \
93		fatal "ssh-keygen of cert_host_key_${ktype} failed"
94	${SSHKEYGEN} -ukf $OBJ/host_krl_plain \
95	    $OBJ/cert_host_key_${ktype}.pub || fatal "KRL update failed"
96	cat $OBJ/cert_host_key_${ktype}.pub >> $OBJ/host_revoked_plain
97	case $ktype in
98	rsa-sha2-*)	tflag="-t $ktype"; ca="$OBJ/host_ca_key2" ;;
99	*)		tflag=""; ca="$OBJ/host_ca_key" ;;
100	esac
101	${SSHKEYGEN} -h -q -s $ca -z $serial $tflag \
102	    -I "regress host key for $USER" \
103	    -n $HOSTS $OBJ/cert_host_key_${ktype} ||
104		fatal "couldn't sign cert_host_key_${ktype}"
105	${SSHKEYGEN} -ukf $OBJ/host_krl_cert \
106	    $OBJ/cert_host_key_${ktype}-cert.pub || \
107		fatal "KRL update failed"
108	cat $OBJ/cert_host_key_${ktype}-cert.pub >> $OBJ/host_revoked_cert
109	serial=`expr $serial + 1`
110done
111
112attempt_connect() {
113	_ident="$1"
114	_expect_success="$2"
115	shift; shift
116	verbose "$tid: $_ident expect success $_expect_success"
117	cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
118	${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \
119	    -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
120	    "$@" -F $OBJ/ssh_proxy somehost true
121	_r=$?
122	if [ "x$_expect_success" = "xyes" ] ; then
123		if [ $_r -ne 0 ]; then
124			fail "ssh cert connect $_ident failed"
125		fi
126	else
127		if [ $_r -eq 0 ]; then
128			fail "ssh cert connect $_ident succeeded unexpectedly"
129		fi
130	fi
131}
132
133# Basic connect and revocation tests.
134for ktype in $PLAIN_TYPES ; do
135	verbose "$tid: host ${ktype} cert connect"
136	(
137		cat $OBJ/sshd_proxy_bak
138		echo HostKey $OBJ/cert_host_key_${ktype}
139		echo HostCertificate $OBJ/cert_host_key_${ktype}-cert.pub
140	) > $OBJ/sshd_proxy
141
142	#               test name                         expect success
143	attempt_connect "$ktype basic connect"			"yes"
144	attempt_connect "$ktype empty KRL"			"yes" \
145	    -oRevokedHostKeys=$OBJ/host_krl_empty
146	attempt_connect "$ktype KRL w/ plain key revoked"	"no" \
147	    -oRevokedHostKeys=$OBJ/host_krl_plain
148	attempt_connect "$ktype KRL w/ cert revoked"		"no" \
149	    -oRevokedHostKeys=$OBJ/host_krl_cert
150	attempt_connect "$ktype KRL w/ CA revoked"		"no" \
151	    -oRevokedHostKeys=$OBJ/host_krl_ca
152	attempt_connect "$ktype empty plaintext revocation"	"yes" \
153	    -oRevokedHostKeys=$OBJ/host_revoked_empty
154	attempt_connect "$ktype plain key plaintext revocation"	"no" \
155	    -oRevokedHostKeys=$OBJ/host_revoked_plain
156	attempt_connect "$ktype cert plaintext revocation"	"no" \
157	    -oRevokedHostKeys=$OBJ/host_revoked_cert
158	attempt_connect "$ktype CA plaintext revocation"	"no" \
159	    -oRevokedHostKeys=$OBJ/host_revoked_ca
160done
161
162# Revoked certificates with key present
163kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig
164for ktype in $PLAIN_TYPES ; do
165	test -f "$OBJ/cert_host_key_${ktype}.pub" || fatal "no pubkey"
166	kh_revoke cert_host_key_${ktype}.pub >> $OBJ/known_hosts-cert.orig
167done
168cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
169for ktype in $PLAIN_TYPES ; do
170	verbose "$tid: host ${ktype} revoked cert"
171	(
172		cat $OBJ/sshd_proxy_bak
173		echo HostKey $OBJ/cert_host_key_${ktype}
174		echo HostCertificate $OBJ/cert_host_key_${ktype}-cert.pub
175	) > $OBJ/sshd_proxy
176
177	cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
178	${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \
179	    -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
180		-F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
181	if [ $? -eq 0 ]; then
182		fail "ssh cert connect succeeded unexpectedly"
183	fi
184done
185
186# Revoked CA
187kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig
188kh_revoke host_ca_key.pub host_ca_key2.pub >> $OBJ/known_hosts-cert.orig
189cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
190for ktype in $PLAIN_TYPES ; do
191	verbose "$tid: host ${ktype} revoked cert"
192	(
193		cat $OBJ/sshd_proxy_bak
194		echo HostKey $OBJ/cert_host_key_${ktype}
195		echo HostCertificate $OBJ/cert_host_key_${ktype}-cert.pub
196	) > $OBJ/sshd_proxy
197	cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
198	${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \
199	    -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
200		-F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
201	if [ $? -eq 0 ]; then
202		fail "ssh cert connect succeeded unexpectedly"
203	fi
204done
205
206# Create a CA key and add it to known hosts
207kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig
208cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
209
210test_one() {
211	ident=$1
212	result=$2
213	sign_opts=$3
214
215	for kt in $PLAIN_TYPES; do
216		case $ktype in
217		rsa-sha2-*)	tflag="-t $ktype"; ca="$OBJ/host_ca_key2" ;;
218		*)		tflag=""; ca="$OBJ/host_ca_key" ;;
219		esac
220		${SSHKEYGEN} -q -s $ca $tflag -I "regress host key for $USER" \
221		    $sign_opts $OBJ/cert_host_key_${kt} ||
222			fatal "couldn't sign cert_host_key_${kt}"
223		(
224			cat $OBJ/sshd_proxy_bak
225			echo HostKey $OBJ/cert_host_key_${kt}
226			echo HostCertificate $OBJ/cert_host_key_${kt}-cert.pub
227		) > $OBJ/sshd_proxy
228
229		cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
230		${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \
231		    -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
232		    -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
233		rc=$?
234		if [ "x$result" = "xsuccess" ] ; then
235			if [ $rc -ne 0 ]; then
236				fail "ssh cert connect $ident failed unexpectedly"
237			fi
238		else
239			if [ $rc -eq 0 ]; then
240				fail "ssh cert connect $ident succeeded unexpectedly"
241			fi
242		fi
243	done
244}
245
246test_one "user-certificate"	failure "-n $HOSTS"
247test_one "empty principals"	success "-h"
248test_one "wrong principals"	failure "-h -n foo"
249test_one "cert not yet valid"	failure "-h -V20300101:20320101"
250test_one "cert expired"		failure "-h -V19800101:19900101"
251test_one "cert valid interval"	success "-h -V-1w:+2w"
252test_one "cert has constraints"	failure "-h -Oforce-command=false"
253
254# Check downgrade of cert to raw key when no CA found
255for ktype in $PLAIN_TYPES ; do
256	rm -f $OBJ/known_hosts-cert $OBJ/cert_host_key*
257	verbose "$tid: host ${ktype} ${v} cert downgrade to raw key"
258	# Generate and sign a host key
259	${SSHKEYGEN} -q -N '' -t ${ktype} -f $OBJ/cert_host_key_${ktype} || \
260		fail "ssh-keygen of cert_host_key_${ktype} failed"
261	case $ktype in
262	rsa-sha2-*)	tflag="-t $ktype"; ca="$OBJ/host_ca_key2" ;;
263	*)		tflag=""; ca="$OBJ/host_ca_key" ;;
264	esac
265	${SSHKEYGEN} -h -q $tflag -s $ca $tflag \
266	    -I "regress host key for $USER" \
267	    -n $HOSTS $OBJ/cert_host_key_${ktype} ||
268		fatal "couldn't sign cert_host_key_${ktype}"
269	(
270		printf "$HOSTS "
271		cat $OBJ/cert_host_key_${ktype}.pub
272	) > $OBJ/known_hosts-cert
273	(
274		cat $OBJ/sshd_proxy_bak
275		echo HostKey $OBJ/cert_host_key_${ktype}
276		echo HostCertificate $OBJ/cert_host_key_${ktype}-cert.pub
277	) > $OBJ/sshd_proxy
278
279	${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \
280	    -oGlobalKnownHostsFile=none -F $OBJ/ssh_proxy somehost true
281	if [ $? -ne 0 ]; then
282		fail "ssh cert connect failed"
283	fi
284	# Also check that it works when the known_hosts file is not in the
285	# first array position.
286	${SSH} -oUserKnownHostsFile="/dev/null $OBJ/known_hosts-cert" \
287	    -oGlobalKnownHostsFile=none -F $OBJ/ssh_proxy somehost true
288	if [ $? -ne 0 ]; then
289		fail "ssh cert connect failed known_hosts 2nd"
290	fi
291done
292
293# Wrong certificate
294kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig
295cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
296for kt in $PLAIN_TYPES ; do
297	verbose "$tid: host ${kt} connect wrong cert"
298	rm -f $OBJ/cert_host_key*
299	# Self-sign key
300	${SSHKEYGEN} -q -N '' -t ${kt} -f $OBJ/cert_host_key_${kt} || \
301		fail "ssh-keygen of cert_host_key_${kt} failed"
302	case $kt in
303	rsa-sha2-*)	tflag="-t $kt" ;;
304	*)		tflag="" ;;
305	esac
306	${SSHKEYGEN} $tflag -h -q -s $OBJ/cert_host_key_${kt} \
307	    -I "regress host key for $USER" \
308	    -n $HOSTS $OBJ/cert_host_key_${kt} ||
309		fatal "couldn't sign cert_host_key_${kt}"
310	(
311		cat $OBJ/sshd_proxy_bak
312		echo HostKey $OBJ/cert_host_key_${kt}
313		echo HostCertificate $OBJ/cert_host_key_${kt}-cert.pub
314	) > $OBJ/sshd_proxy
315
316	cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
317	${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \
318	    -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
319		-F $OBJ/ssh_proxy -q somehost true >/dev/null 2>&1
320	if [ $? -eq 0 ]; then
321		fail "ssh cert connect $ident succeeded unexpectedly"
322	fi
323done
324
325rm -f $OBJ/known_hosts-cert* $OBJ/host_ca_key* $OBJ/cert_host_key*
326