1# $OpenBSD: cert-hostkey.sh,v 1.31 2026/02/11 22:58:23 djm 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-//'` 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 multiple KRL files" "no" \ 147 -oRevokedHostKeys="/dev/null $OBJ/host_krl_plain" 148 attempt_connect "$ktype KRL w/ plain key revoked" "no" \ 149 -oRevokedHostKeys=$OBJ/host_krl_plain 150 attempt_connect "$ktype KRL w/ cert revoked" "no" \ 151 -oRevokedHostKeys=$OBJ/host_krl_cert 152 attempt_connect "$ktype KRL w/ CA revoked" "no" \ 153 -oRevokedHostKeys=$OBJ/host_krl_ca 154 attempt_connect "$ktype empty plaintext revocation" "yes" \ 155 -oRevokedHostKeys=$OBJ/host_revoked_empty 156 attempt_connect "$ktype plain key plaintext revocation" "no" \ 157 -oRevokedHostKeys=$OBJ/host_revoked_plain 158 attempt_connect "$ktype cert plaintext revocation" "no" \ 159 -oRevokedHostKeys=$OBJ/host_revoked_cert 160 attempt_connect "$ktype CA plaintext revocation" "no" \ 161 -oRevokedHostKeys=$OBJ/host_revoked_ca 162done 163 164# Revoked certificates with key present 165kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig 166for ktype in $PLAIN_TYPES ; do 167 test -f "$OBJ/cert_host_key_${ktype}.pub" || fatal "no pubkey" 168 kh_revoke cert_host_key_${ktype}.pub >> $OBJ/known_hosts-cert.orig 169done 170cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert 171for ktype in $PLAIN_TYPES ; do 172 verbose "$tid: host ${ktype} revoked cert" 173 ( 174 cat $OBJ/sshd_proxy_bak 175 echo HostKey $OBJ/cert_host_key_${ktype} 176 echo HostCertificate $OBJ/cert_host_key_${ktype}-cert.pub 177 ) > $OBJ/sshd_proxy 178 179 cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert 180 ${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \ 181 -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \ 182 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 183 if [ $? -eq 0 ]; then 184 fail "ssh cert connect succeeded unexpectedly" 185 fi 186done 187 188# Revoked CA 189kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig 190kh_revoke host_ca_key.pub host_ca_key2.pub >> $OBJ/known_hosts-cert.orig 191cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert 192for ktype in $PLAIN_TYPES ; do 193 verbose "$tid: host ${ktype} revoked cert" 194 ( 195 cat $OBJ/sshd_proxy_bak 196 echo HostKey $OBJ/cert_host_key_${ktype} 197 echo HostCertificate $OBJ/cert_host_key_${ktype}-cert.pub 198 ) > $OBJ/sshd_proxy 199 cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert 200 ${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \ 201 -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \ 202 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 203 if [ $? -eq 0 ]; then 204 fail "ssh cert connect succeeded unexpectedly" 205 fi 206done 207 208# Create a CA key and add it to known hosts 209kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig 210cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert 211 212test_one() { 213 ident="$1" 214 result="$2" 215 hosts="$3" 216 sign_opts="$4" 217 218 test -z "$hosts" || sign_opts="$sign_opts -n $hosts" 219 220 for kt in $PLAIN_TYPES; do 221 case $ktype in 222 rsa-sha2-*) tflag="-t $ktype"; ca="$OBJ/host_ca_key2" ;; 223 *) tflag=""; ca="$OBJ/host_ca_key" ;; 224 esac 225 if test -z "$hosts" ; then 226 # Empty principals section. 227 ${SSHKEYGEN} -q -s $ca $tflag $sign_opts \ 228 -I "regress host key for $USER" \ 229 $OBJ/cert_host_key_${kt} 2>/dev/null || 230 fatal "couldn't sign cert_host_key_${kt}" 231 else 232 # Be careful with quoting principals, which may contain 233 # wilcards. 234 ${SSHKEYGEN} -q -s $ca $tflag $sign_opts \ 235 -I "regress host key for $USER" -n "$hosts" \ 236 $OBJ/cert_host_key_${kt} || 237 fatal "couldn't sign cert_host_key_${kt}" 238 fi 239 ( 240 cat $OBJ/sshd_proxy_bak 241 echo HostKey $OBJ/cert_host_key_${kt} 242 echo HostCertificate $OBJ/cert_host_key_${kt}-cert.pub 243 ) > $OBJ/sshd_proxy 244 245 cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert 246 ${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \ 247 -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \ 248 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 249 rc=$? 250 if [ "x$result" = "xsuccess" ] ; then 251 if [ $rc -ne 0 ]; then 252 fail "ssh cert connect $ident failed unexpectedly" 253 fi 254 else 255 if [ $rc -eq 0 ]; then 256 fail "ssh cert connect $ident succeeded unexpectedly" 257 fi 258 fi 259 done 260} 261 262test_one "simple" success $HOSTS "-h" 263test_one "wildcard" success "loc*" "-h" 264test_one "user-certificate" failure $HOSTS 265test_one "wildcard user" failure "local*" 266test_one "empty principals" failure "" "-h" 267test_one "wrong principals" failure foo "-h" 268test_one "cert not yet valid" failure $HOSTS "-h -V20300101:20320101" 269test_one "cert expired" failure $HOSTS "-h -V19800101:19900101" 270test_one "cert valid interval" success $HOSTS "-h -V-1w:+2w" 271test_one "cert has constraints" failure $HOSTS "-h -Oforce-command=false" 272 273# Check downgrade of cert to raw key when no CA found 274for ktype in $PLAIN_TYPES ; do 275 rm -f $OBJ/known_hosts-cert $OBJ/cert_host_key* 276 verbose "$tid: host ${ktype} ${v} cert downgrade to raw key" 277 # Generate and sign a host key 278 ${SSHKEYGEN} -q -N '' -t ${ktype} -f $OBJ/cert_host_key_${ktype} || \ 279 fail "ssh-keygen of cert_host_key_${ktype} failed" 280 case $ktype in 281 rsa-sha2-*) tflag="-t $ktype"; ca="$OBJ/host_ca_key2" ;; 282 *) tflag=""; ca="$OBJ/host_ca_key" ;; 283 esac 284 ${SSHKEYGEN} -h -q $tflag -s $ca $tflag \ 285 -I "regress host key for $USER" \ 286 -n $HOSTS $OBJ/cert_host_key_${ktype} || 287 fatal "couldn't sign cert_host_key_${ktype}" 288 ( 289 printf "$HOSTS " 290 cat $OBJ/cert_host_key_${ktype}.pub 291 ) > $OBJ/known_hosts-cert 292 ( 293 cat $OBJ/sshd_proxy_bak 294 echo HostKey $OBJ/cert_host_key_${ktype} 295 echo HostCertificate $OBJ/cert_host_key_${ktype}-cert.pub 296 ) > $OBJ/sshd_proxy 297 298 ${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \ 299 -oGlobalKnownHostsFile=none -F $OBJ/ssh_proxy somehost true 300 if [ $? -ne 0 ]; then 301 fail "ssh cert connect failed" 302 fi 303 # Also check that it works when the known_hosts file is not in the 304 # first array position. 305 ${SSH} -oUserKnownHostsFile="/dev/null $OBJ/known_hosts-cert" \ 306 -oGlobalKnownHostsFile=none -F $OBJ/ssh_proxy somehost true 307 if [ $? -ne 0 ]; then 308 fail "ssh cert connect failed known_hosts 2nd" 309 fi 310done 311 312# Wrong certificate 313kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig 314cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert 315for kt in $PLAIN_TYPES ; do 316 verbose "$tid: host ${kt} connect wrong cert" 317 rm -f $OBJ/cert_host_key* 318 # Self-sign key 319 ${SSHKEYGEN} -q -N '' -t ${kt} -f $OBJ/cert_host_key_${kt} || \ 320 fail "ssh-keygen of cert_host_key_${kt} failed" 321 case $kt in 322 rsa-sha2-*) tflag="-t $kt" ;; 323 *) tflag="" ;; 324 esac 325 ${SSHKEYGEN} $tflag -h -q -s $OBJ/cert_host_key_${kt} \ 326 -I "regress host key for $USER" \ 327 -n $HOSTS $OBJ/cert_host_key_${kt} || 328 fatal "couldn't sign cert_host_key_${kt}" 329 ( 330 cat $OBJ/sshd_proxy_bak 331 echo HostKey $OBJ/cert_host_key_${kt} 332 echo HostCertificate $OBJ/cert_host_key_${kt}-cert.pub 333 ) > $OBJ/sshd_proxy 334 335 cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert 336 ${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \ 337 -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \ 338 -F $OBJ/ssh_proxy -q somehost true >/dev/null 2>&1 339 if [ $? -eq 0 ]; then 340 fail "ssh cert connect $ident succeeded unexpectedly" 341 fi 342done 343 344rm -f $OBJ/known_hosts-cert* $OBJ/host_ca_key* $OBJ/cert_host_key* 345