1# $OpenBSD: cert-userkey.sh,v 1.32 2026/02/11 22:58:23 djm Exp $ 2# Placed in the Public Domain. 3 4tid="certified user keys" 5 6rm -f $OBJ/authorized_keys_${USER}* $OBJ/user_ca_key* $OBJ/cert_user_key* 7rm -f $OBJ/authorized_principals* 8cp $OBJ/ssh_proxy $OBJ/ssh_proxy_bak 9 10grep -v AuthorizedKeysFile $OBJ/sshd_proxy > $OBJ/sshd_proxy_bak 11echo "AuthorizedKeysFile $OBJ/authorized_keys_%u_*" >> $OBJ/sshd_proxy_bak 12 13PLAIN_TYPES=`$SSH -Q key-plain | maybe_filter_sk | sed 's/^ssh-//'` 14EXTRA_TYPES="" 15rsa="" 16 17if echo "$PLAIN_TYPES" | grep '^rsa$' >/dev/null 2>&1 ; then 18 rsa=rsa 19 PLAIN_TYPES="$PLAIN_TYPES rsa-sha2-256 rsa-sha2-512" 20fi 21 22kname() { 23 case $1 in 24 rsa-sha2-*) n="$1" ;; 25 sk-ecdsa-*) n="sk-ecdsa" ;; 26 sk-ssh-ed25519*) n="sk-ssh-ed25519" ;; 27 # subshell because some seds will add a newline 28 *) n=$(echo $1 | sed 's/^rsa/ssh-rsa/;s/^ed/ssh-ed/') ;; 29 esac 30 if [ -z "$rsa" ]; then 31 echo "$n*,ssh-ed25519*" 32 else 33 echo "$n*,ssh-rsa*,ssh-ed25519*" 34 fi 35} 36 37# Create a CA key 38if [ ! -z "$rsa" ]; then 39 catype=rsa 40else 41 catype=ed25519 42fi 43${SSHKEYGEN} -q -N '' -t $catype -f $OBJ/user_ca_key ||\ 44 fail "ssh-keygen of user_ca_key failed" 45 46# Generate and sign user keys 47for ktype in $PLAIN_TYPES $EXTRA_TYPES ; do 48 verbose "$tid: sign user ${ktype} cert" 49 ${SSHKEYGEN} -q -N '' -t ${ktype} \ 50 -f $OBJ/cert_user_key_${ktype} || \ 51 fatal "ssh-keygen of cert_user_key_${ktype} failed" 52 # Generate RSA/SHA2 certs for rsa-sha2* keys. 53 case $ktype in 54 rsa-sha2-*) tflag="-t $ktype" ;; 55 *) tflag="" ;; 56 esac 57 ${SSHKEYGEN} -q -s $OBJ/user_ca_key -z $$ \ 58 -I "regress user key for $USER" \ 59 -n ${USER},mekmitasdigoat $tflag $OBJ/cert_user_key_${ktype} || \ 60 fatal "couldn't sign cert_user_key_${ktype}" 61done 62 63# Test explicitly-specified principals 64for ktype in $EXTRA_TYPES $PLAIN_TYPES ; do 65 t=$(kname $ktype) 66 _prefix="${ktype}" 67 68 # Setup for AuthorizedPrincipalsFile 69 rm -f $OBJ/authorized_keys_${USER}* $OBJ/authorized_principals_${USER}* 70 touch $OBJ/authorized_keys_${USER}_A 71 touch $OBJ/authorized_keys_${USER}_Z 72 touch $OBJ/authorized_principals_${USER}_A 73 touch $OBJ/authorized_principals_${USER}_Z 74 ( 75 cat $OBJ/sshd_proxy_bak 76 echo "AuthorizedPrincipalsFile " \ 77 "$OBJ/authorized_principals_%u_*" 78 echo "TrustedUserCAKeys $OBJ/user_ca_key.pub" 79 echo "PubkeyAcceptedAlgorithms ${t}" 80 ) > $OBJ/sshd_proxy 81 ( 82 cat $OBJ/ssh_proxy_bak 83 echo "PubkeyAcceptedAlgorithms ${t}" 84 ) > $OBJ/ssh_proxy 85 86 # Missing authorized_principals 87 verbose "$tid: ${_prefix} missing authorized_principals" 88 rm -f $OBJ/authorized_principals_${USER}_X 89 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 90 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 91 if [ $? -eq 0 ]; then 92 fail "ssh cert connect succeeded unexpectedly" 93 fi 94 95 # Empty authorized_principals 96 verbose "$tid: ${_prefix} empty authorized_principals" 97 echo > $OBJ/authorized_principals_${USER}_X 98 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 99 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 100 if [ $? -eq 0 ]; then 101 fail "ssh cert connect succeeded unexpectedly" 102 fi 103 104 # Wrong authorized_principals 105 verbose "$tid: ${_prefix} wrong authorized_principals" 106 echo gregorsamsa > $OBJ/authorized_principals_${USER}_X 107 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 108 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 109 if [ $? -eq 0 ]; then 110 fail "ssh cert connect succeeded unexpectedly" 111 fi 112 113 # Correct authorized_principals 114 verbose "$tid: ${_prefix} correct authorized_principals" 115 echo mekmitasdigoat > $OBJ/authorized_principals_${USER}_X 116 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 117 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 118 if [ $? -ne 0 ]; then 119 fail "ssh cert connect failed" 120 fi 121 122 # authorized_principals with bad key option 123 verbose "$tid: ${_prefix} authorized_principals bad key opt" 124 echo 'blah mekmitasdigoat' > $OBJ/authorized_principals_${USER}_X 125 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 126 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 127 if [ $? -eq 0 ]; then 128 fail "ssh cert connect succeeded unexpectedly" 129 fi 130 131 # authorized_principals with command=false 132 verbose "$tid: ${_prefix} authorized_principals command=false" 133 echo 'command="false" mekmitasdigoat' > \ 134 $OBJ/authorized_principals_${USER}_X 135 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 136 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 137 if [ $? -eq 0 ]; then 138 fail "ssh cert connect succeeded unexpectedly" 139 fi 140 141 142 # authorized_principals with command=true 143 verbose "$tid: ${_prefix} authorized_principals command=true" 144 echo 'command="true" mekmitasdigoat' > \ 145 $OBJ/authorized_principals_${USER}_X 146 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 147 -F $OBJ/ssh_proxy somehost false >/dev/null 2>&1 148 if [ $? -ne 0 ]; then 149 fail "ssh cert connect failed" 150 fi 151 152 # Setup for principals= key option 153 rm -f $OBJ/authorized_principals_${USER}_X 154 ( 155 cat $OBJ/sshd_proxy_bak 156 echo "PubkeyAcceptedAlgorithms ${t}" 157 ) > $OBJ/sshd_proxy 158 ( 159 cat $OBJ/ssh_proxy_bak 160 echo "PubkeyAcceptedAlgorithms ${t}" 161 ) > $OBJ/ssh_proxy 162 163 # Wrong principals list 164 verbose "$tid: ${_prefix} wrong principals key option" 165 ( 166 printf 'cert-authority,principals="gregorsamsa" ' 167 cat $OBJ/user_ca_key.pub 168 ) > $OBJ/authorized_keys_${USER}_X 169 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 170 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 171 if [ $? -eq 0 ]; then 172 fail "ssh cert connect succeeded unexpectedly" 173 fi 174 175 # Correct principals list 176 verbose "$tid: ${_prefix} correct principals key option" 177 ( 178 printf 'cert-authority,principals="mekmitasdigoat" ' 179 cat $OBJ/user_ca_key.pub 180 ) > $OBJ/authorized_keys_${USER}_X 181 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 182 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 183 if [ $? -ne 0 ]; then 184 fail "ssh cert connect failed" 185 fi 186done 187 188basic_tests() { 189 auth=$1 190 rm -f $OBJ/authorized_keys_${USER}* 191 touch $OBJ/authorized_keys_${USER}_A 192 touch $OBJ/authorized_keys_${USER}_Z 193 if test "x$auth" = "xauthorized_keys" ; then 194 # Add CA to authorized_keys 195 ( 196 printf 'cert-authority ' 197 cat $OBJ/user_ca_key.pub 198 ) > $OBJ/authorized_keys_${USER}_X 199 else 200 echo > $OBJ/authorized_keys_${USER}_X 201 extra_sshd="TrustedUserCAKeys $OBJ/user_ca_key.pub" 202 fi 203 204 for ktype in $PLAIN_TYPES ; do 205 t=$(kname $ktype) 206 _prefix="${ktype} $auth" 207 # Simple connect 208 verbose "$tid: ${_prefix} connect" 209 ( 210 cat $OBJ/sshd_proxy_bak 211 echo "PubkeyAcceptedAlgorithms ${t}" 212 echo "$extra_sshd" 213 ) > $OBJ/sshd_proxy 214 ( 215 cat $OBJ/ssh_proxy_bak 216 echo "PubkeyAcceptedAlgorithms ${t}" 217 ) > $OBJ/ssh_proxy 218 219 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 220 -F $OBJ/ssh_proxy somehost true 221 if [ $? -ne 0 ]; then 222 fail "ssh cert connect failed" 223 fi 224 225 # Revoked keys 226 verbose "$tid: ${_prefix} revoked key" 227 ( 228 cat $OBJ/sshd_proxy_bak 229 # Also test multiple RevokedKeys files. 230 echo "RevokedKeys /dev/null $OBJ/cert_user_key_revoked" 231 echo "PubkeyAcceptedAlgorithms ${t}" 232 echo "$extra_sshd" 233 ) > $OBJ/sshd_proxy 234 cp $OBJ/cert_user_key_${ktype}.pub \ 235 $OBJ/cert_user_key_revoked 236 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 237 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 238 if [ $? -eq 0 ]; then 239 fail "ssh cert connect succeeded unexpecedly" 240 fi 241 verbose "$tid: ${_prefix} revoked via KRL" 242 rm $OBJ/cert_user_key_revoked 243 ${SSHKEYGEN} -kqf $OBJ/cert_user_key_revoked \ 244 $OBJ/cert_user_key_${ktype}.pub 245 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 246 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 247 if [ $? -eq 0 ]; then 248 fail "ssh cert connect succeeded unexpecedly" 249 fi 250 verbose "$tid: ${_prefix} empty KRL" 251 ${SSHKEYGEN} -kqf $OBJ/cert_user_key_revoked 252 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 253 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 254 if [ $? -ne 0 ]; then 255 fail "ssh cert connect failed" 256 fi 257 done 258 259 # Revoked CA 260 verbose "$tid: ${ktype} $auth revoked CA key" 261 ( 262 cat $OBJ/sshd_proxy_bak 263 echo "RevokedKeys $OBJ/user_ca_key.pub" 264 echo "PubkeyAcceptedAlgorithms ${t}" 265 echo "$extra_sshd" 266 ) > $OBJ/sshd_proxy 267 ${SSH} -i $OBJ/cert_user_key_${ktype} -F $OBJ/ssh_proxy \ 268 somehost true >/dev/null 2>&1 269 if [ $? -eq 0 ]; then 270 fail "ssh cert connect succeeded unexpecedly" 271 fi 272 273 verbose "$tid: $auth CA does not authenticate" 274 ( 275 cat $OBJ/sshd_proxy_bak 276 echo "PubkeyAcceptedAlgorithms ${t}" 277 echo "$extra_sshd" 278 ) > $OBJ/sshd_proxy 279 verbose "$tid: ensure CA key does not authenticate user" 280 ${SSH} -i $OBJ/user_ca_key \ 281 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 282 if [ $? -eq 0 ]; then 283 fail "ssh cert connect with CA key succeeded unexpectedly" 284 fi 285} 286 287basic_tests authorized_keys 288basic_tests TrustedUserCAKeys 289 290test_one() { 291 ident=$1 292 result=$2 293 sign_opts=$3 294 auth_choice=$4 295 auth_opt=$5 296 297 if test "x$auth_choice" = "x" ; then 298 auth_choice="authorized_keys TrustedUserCAKeys" 299 fi 300 301 for auth in $auth_choice ; do 302 for ktype in $rsa ed25519 ; do 303 cat $OBJ/sshd_proxy_bak > $OBJ/sshd_proxy 304 if test "x$auth" = "xauthorized_keys" ; then 305 # Add CA to authorized_keys 306 ( 307 printf "cert-authority${auth_opt} " 308 cat $OBJ/user_ca_key.pub 309 ) > $OBJ/authorized_keys_${USER}_X 310 else 311 echo > $OBJ/authorized_keys_${USER}_X 312 echo "TrustedUserCAKeys $OBJ/user_ca_key.pub" \ 313 >> $OBJ/sshd_proxy 314 echo "PubkeyAcceptedAlgorithms ${t}*" \ 315 >> $OBJ/sshd_proxy 316 if test "x$auth_opt" != "x" ; then 317 echo $auth_opt >> $OBJ/sshd_proxy 318 fi 319 fi 320 321 verbose "$tid: $ident auth $auth expect $result $ktype" 322 ${SSHKEYGEN} -q -s $OBJ/user_ca_key \ 323 -I "regress user key for $USER" \ 324 $sign_opts $OBJ/cert_user_key_${ktype} || 325 fail "couldn't sign cert_user_key_${ktype}" 326 327 ${SSH} -i $OBJ/cert_user_key_${ktype} \ 328 -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1 329 rc=$? 330 if [ "x$result" = "xsuccess" ] ; then 331 if [ $rc -ne 0 ]; then 332 fail "$ident failed unexpectedly" 333 fi 334 else 335 if [ $rc -eq 0 ]; then 336 fail "$ident succeeded unexpectedly" 337 fi 338 fi 339 done 340 done 341} 342 343test_one "correct principal" success "-n ${USER}" 344test_one "correct principal" success "-n ${USER},*" 345test_one "host-certificate" failure "-n ${USER} -h" 346test_one "wrong principals" failure "-n foo,*" 347test_one "cert not yet valid" failure "-n ${USER} -V20300101:20320101" 348test_one "cert expired" failure "-n ${USER} -V19800101:19900101" 349test_one "cert valid interval" success "-n ${USER} -V-1w:+2w" 350test_one "wrong source-address" failure "-n ${USER} -Osource-address=10.0.0.0/8" 351test_one "force-command" failure "-n ${USER} -Oforce-command=false" 352test_one "empty principals" failure "" authorized_keys 353test_one "empty principals" failure "" TrustedUserCAKeys 354 355# Check explicitly-specified principals: an empty principals list in the cert 356# should always be refused. 357 358# AuthorizedPrincipalsFile 359rm -f $OBJ/authorized_keys_${USER}_X 360echo mekmitasdigoat > $OBJ/authorized_principals_${USER}_X 361test_one "AuthorizedPrincipalsFile principals" success "-n mekmitasdigoat" \ 362 TrustedUserCAKeys "AuthorizedPrincipalsFile $OBJ/authorized_principals_%u_*" 363test_one "AuthorizedPrincipalsFile no principals" failure "" \ 364 TrustedUserCAKeys "AuthorizedPrincipalsFile $OBJ/authorized_principals_%u_*" 365 366# principals= key option 367rm -f $OBJ/authorized_principals_${USER}_X 368test_one "principals key option principals" success "-n mekmitasdigoat" \ 369 authorized_keys ',principals="mekmitasdigoat"' 370test_one "principals key option no principals" failure "" \ 371 authorized_keys ',principals="mekmitasdigoat"' 372 373# command= options vs. force-command in key 374test_one "force-command match true" success \ 375 "-n ${USER} -Oforce-command=true" \ 376 authorized_keys ',command="true"' 377test_one "force-command match true" failure \ 378 "-n ${USER} -Oforce-command=false" \ 379 authorized_keys ',command="false"' 380test_one "force-command mismatch 1" failure \ 381 "-n ${USER} -Oforce-command=false" \ 382 authorized_keys ',command="true"' 383test_one "force-command mismatch 2" failure \ 384 "-n ${USER} -Oforce-command=true" \ 385 authorized_keys ',command="false"' 386 387# Wrong certificate 388cat $OBJ/sshd_proxy_bak > $OBJ/sshd_proxy 389for ktype in $PLAIN_TYPES ; do 390 t=$(kname $ktype) 391 # Self-sign 392 ${SSHKEYGEN} -q -s $OBJ/cert_user_key_${ktype} -I \ 393 "regress user key for $USER" \ 394 -n $USER $OBJ/cert_user_key_${ktype} || 395 fatal "couldn't sign cert_user_key_${ktype}" 396 verbose "$tid: user ${ktype} connect wrong cert" 397 ${SSH} -i $OBJ/cert_user_key_${ktype} -F $OBJ/ssh_proxy \ 398 somehost true >/dev/null 2>&1 399 if [ $? -eq 0 ]; then 400 fail "ssh cert connect $ident succeeded unexpectedly" 401 fi 402done 403 404rm -f $OBJ/authorized_keys_${USER}* $OBJ/user_ca_key* $OBJ/cert_user_key* 405rm -f $OBJ/authorized_principals* 406 407