summaryrefslogtreecommitdiffstats
path: root/regress
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:40:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:40:04 +0000
commit25505898530a333011f4fd5cbc841ad6b26c089c (patch)
tree333a33fdd60930bcccc3f177ed9467d535e9bac6 /regress
parentInitial commit. (diff)
downloadopenssh-upstream.tar.xz
openssh-upstream.zip
Adding upstream version 1:9.2p1.upstream/1%9.2p1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--regress/Makefile278
-rw-r--r--regress/README.regress161
-rw-r--r--regress/addrmatch.sh68
-rw-r--r--regress/agent-getpeereid.sh59
-rw-r--r--regress/agent-pkcs11.sh124
-rw-r--r--regress/agent-ptrace.sh67
-rw-r--r--regress/agent-restrict.sh495
-rw-r--r--regress/agent-subprocess.sh22
-rw-r--r--regress/agent-timeout.sh38
-rw-r--r--regress/agent.sh227
-rw-r--r--regress/allow-deny-users.sh43
-rw-r--r--regress/authinfo.sh17
-rw-r--r--regress/banner.sh46
-rw-r--r--regress/broken-pipe.sh12
-rw-r--r--regress/brokenkeys.sh23
-rw-r--r--regress/cert-file.sh166
-rw-r--r--regress/cert-hostkey.sh325
-rw-r--r--regress/cert-userkey.sh396
-rw-r--r--regress/cfginclude.sh293
-rw-r--r--regress/cfgmatch.sh158
-rw-r--r--regress/cfgmatchlisten.sh202
-rw-r--r--regress/cfgparse.sh75
-rw-r--r--regress/channel-timeout.sh91
-rw-r--r--regress/check-perm.c205
-rw-r--r--regress/cipher-speed.sh42
-rw-r--r--regress/conch-ciphers.sh28
-rw-r--r--regress/connect-privsep.sh34
-rw-r--r--regress/connect-uri.sh29
-rw-r--r--regress/connect.sh18
-rw-r--r--regress/connection-timeout.sh87
-rw-r--r--regress/dhgex.sh61
-rw-r--r--regress/dsa_ssh2.prv14
-rw-r--r--regress/dsa_ssh2.pub13
-rw-r--r--regress/dynamic-forward.sh110
-rw-r--r--regress/ed25519_openssh.prv7
-rw-r--r--regress/ed25519_openssh.pub1
-rw-r--r--regress/envpass.sh125
-rw-r--r--regress/exit-status-signal.sh24
-rw-r--r--regress/exit-status.sh22
-rw-r--r--regress/forcecommand.sh35
-rw-r--r--regress/forward-control.sh228
-rw-r--r--regress/forwarding.sh136
-rw-r--r--regress/host-expand.sh16
-rw-r--r--regress/hostbased.sh66
-rw-r--r--regress/hostkey-agent.sh87
-rw-r--r--regress/hostkey-rotate.sh152
-rw-r--r--regress/integrity.sh76
-rw-r--r--regress/kextype.sh25
-rw-r--r--regress/key-options.sh124
-rw-r--r--regress/keygen-change.sh22
-rw-r--r--regress/keygen-comment.sh52
-rw-r--r--regress/keygen-convert.sh55
-rw-r--r--regress/keygen-knownhosts.sh220
-rw-r--r--regress/keygen-moduli.sh27
-rw-r--r--regress/keygen-sshfp.sh29
-rw-r--r--regress/keys-command.sh79
-rw-r--r--regress/keyscan.sh25
-rw-r--r--regress/keytype.sh83
-rw-r--r--regress/knownhosts-command.sh55
-rw-r--r--regress/knownhosts.sh17
-rw-r--r--regress/krl.sh217
-rw-r--r--regress/limit-keytype.sh133
-rw-r--r--regress/localcommand.sh13
-rw-r--r--regress/login-timeout.sh18
-rw-r--r--regress/misc/Makefile3
-rw-r--r--regress/misc/fuzz-harness/Makefile55
-rw-r--r--regress/misc/fuzz-harness/README1
-rw-r--r--regress/misc/fuzz-harness/agent_fuzz.cc15
-rw-r--r--regress/misc/fuzz-harness/agent_fuzz_helper.c177
-rw-r--r--regress/misc/fuzz-harness/authkeys_fuzz.cc81
-rw-r--r--regress/misc/fuzz-harness/authopt_fuzz.cc33
-rw-r--r--regress/misc/fuzz-harness/fixed-keys.h119
-rw-r--r--regress/misc/fuzz-harness/kex_fuzz.cc460
-rw-r--r--regress/misc/fuzz-harness/privkey_fuzz.cc21
-rw-r--r--regress/misc/fuzz-harness/pubkey_fuzz.cc18
-rw-r--r--regress/misc/fuzz-harness/sig_fuzz.cc62
-rw-r--r--regress/misc/fuzz-harness/ssh-sk-null.cc52
-rw-r--r--regress/misc/fuzz-harness/sshsig_fuzz.cc37
-rw-r--r--regress/misc/fuzz-harness/sshsigopt_fuzz.cc29
-rw-r--r--regress/misc/fuzz-harness/testdata/README4
-rwxr-xr-xregress/misc/fuzz-harness/testdata/create-agent-corpus.sh44
-rw-r--r--regress/misc/fuzz-harness/testdata/id_dsa21
-rw-r--r--regress/misc/fuzz-harness/testdata/id_dsa-cert.pub1
-rw-r--r--regress/misc/fuzz-harness/testdata/id_dsa.pub1
-rw-r--r--regress/misc/fuzz-harness/testdata/id_ecdsa8
-rw-r--r--regress/misc/fuzz-harness/testdata/id_ecdsa-cert.pub1
-rw-r--r--regress/misc/fuzz-harness/testdata/id_ecdsa.pub1
-rw-r--r--regress/misc/fuzz-harness/testdata/id_ecdsa_sk14
-rw-r--r--regress/misc/fuzz-harness/testdata/id_ecdsa_sk-cert.pub1
-rw-r--r--regress/misc/fuzz-harness/testdata/id_ecdsa_sk.pub1
-rw-r--r--regress/misc/fuzz-harness/testdata/id_ed255197
-rw-r--r--regress/misc/fuzz-harness/testdata/id_ed25519-cert.pub1
-rw-r--r--regress/misc/fuzz-harness/testdata/id_ed25519.pub2
-rw-r--r--regress/misc/fuzz-harness/testdata/id_ed25519_sk8
-rw-r--r--regress/misc/fuzz-harness/testdata/id_ed25519_sk-cert.pub1
-rw-r--r--regress/misc/fuzz-harness/testdata/id_ed25519_sk.pub1
-rw-r--r--regress/misc/fuzz-harness/testdata/id_rsa27
-rw-r--r--regress/misc/fuzz-harness/testdata/id_rsa-cert.pub1
-rw-r--r--regress/misc/fuzz-harness/testdata/id_rsa.pub1
-rw-r--r--regress/misc/sk-dummy/Makefile66
-rw-r--r--regress/misc/sk-dummy/fatal.c27
-rw-r--r--regress/misc/sk-dummy/sk-dummy.c552
-rw-r--r--regress/mkdtemp.c61
-rw-r--r--regress/modpipe.c150
-rw-r--r--regress/moduli.in3
-rw-r--r--regress/multiplex.sh210
-rw-r--r--regress/multipubkey.sh75
-rw-r--r--regress/netcat.c1686
-rw-r--r--regress/percent.sh128
-rw-r--r--regress/portnum.sh34
-rw-r--r--regress/principals-command.sh168
-rw-r--r--regress/proto-mismatch.sh17
-rw-r--r--regress/proto-version.sh30
-rw-r--r--regress/proxy-connect.sh27
-rw-r--r--regress/putty-ciphers.sh32
-rw-r--r--regress/putty-kex.sh28
-rw-r--r--regress/putty-transfer.sh50
-rw-r--r--regress/reconfigure.sh65
-rw-r--r--regress/reexec.sh57
-rw-r--r--regress/rekey.sh172
-rw-r--r--regress/rsa_openssh.prv15
-rw-r--r--regress/rsa_openssh.pub1
-rw-r--r--regress/rsa_ssh2.prv16
-rw-r--r--regress/scp-ssh-wrapper.sh71
-rw-r--r--regress/scp-uri.sh79
-rw-r--r--regress/scp.sh195
-rw-r--r--regress/scp3.sh62
-rw-r--r--regress/servcfginclude.sh188
-rw-r--r--regress/setuid-allowed.c58
-rw-r--r--regress/sftp-badcmds.sh65
-rw-r--r--regress/sftp-batch.sh55
-rw-r--r--regress/sftp-chroot.sh28
-rw-r--r--regress/sftp-cmds.sh233
-rw-r--r--regress/sftp-glob.sh75
-rw-r--r--regress/sftp-perm.sh271
-rw-r--r--regress/sftp-uri.sh63
-rw-r--r--regress/sftp.sh32
-rw-r--r--regress/ssh-com-client.sh130
-rw-r--r--regress/ssh-com-keygen.sh74
-rw-r--r--regress/ssh-com-sftp.sh65
-rw-r--r--regress/ssh-com.sh119
-rwxr-xr-xregress/ssh2putty.sh36
-rw-r--r--regress/sshcfgparse.sh119
-rw-r--r--regress/sshd-log-wrapper.sh12
-rw-r--r--regress/sshfp-connect.sh66
-rw-r--r--regress/sshsig.sh476
-rw-r--r--regress/stderr-after-eof.sh24
-rw-r--r--regress/stderr-data.sh27
-rw-r--r--regress/t11.ok1
-rw-r--r--regress/t4.ok1
-rw-r--r--regress/t5.ok1
-rw-r--r--regress/test-exec.sh816
-rw-r--r--regress/transfer.sh23
-rw-r--r--regress/try-ciphers.sh28
-rw-r--r--regress/unittests/Makefile7
-rw-r--r--regress/unittests/Makefile.inc89
-rw-r--r--regress/unittests/authopt/Makefile27
-rw-r--r--regress/unittests/authopt/testdata/all_permit.cert1
-rw-r--r--regress/unittests/authopt/testdata/bad_sourceaddr.cert1
-rw-r--r--regress/unittests/authopt/testdata/force_command.cert1
-rw-r--r--regress/unittests/authopt/testdata/host.cert1
-rw-r--r--regress/unittests/authopt/testdata/mktestdata.sh48
-rw-r--r--regress/unittests/authopt/testdata/no_agentfwd.cert1
-rw-r--r--regress/unittests/authopt/testdata/no_permit.cert1
-rw-r--r--regress/unittests/authopt/testdata/no_portfwd.cert1
-rw-r--r--regress/unittests/authopt/testdata/no_pty.cert1
-rw-r--r--regress/unittests/authopt/testdata/no_user_rc.cert1
-rw-r--r--regress/unittests/authopt/testdata/no_x11fwd.cert1
-rw-r--r--regress/unittests/authopt/testdata/only_agentfwd.cert1
-rw-r--r--regress/unittests/authopt/testdata/only_portfwd.cert1
-rw-r--r--regress/unittests/authopt/testdata/only_pty.cert1
-rw-r--r--regress/unittests/authopt/testdata/only_user_rc.cert1
-rw-r--r--regress/unittests/authopt/testdata/only_x11fwd.cert1
-rw-r--r--regress/unittests/authopt/testdata/sourceaddr.cert1
-rw-r--r--regress/unittests/authopt/testdata/unknown_critical.cert1
-rw-r--r--regress/unittests/authopt/tests.c578
-rw-r--r--regress/unittests/bitmap/Makefile14
-rw-r--r--regress/unittests/bitmap/tests.c138
-rw-r--r--regress/unittests/conversion/Makefile16
-rw-r--r--regress/unittests/conversion/tests.c52
-rw-r--r--regress/unittests/hostkeys/Makefile25
-rw-r--r--regress/unittests/hostkeys/mktestdata.sh86
-rw-r--r--regress/unittests/hostkeys/test_iterate.c1117
-rw-r--r--regress/unittests/hostkeys/testdata/dsa_1.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/dsa_2.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/dsa_3.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/dsa_4.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/dsa_5.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/dsa_6.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/ecdsa_1.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/ecdsa_2.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/ecdsa_3.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/ecdsa_4.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/ecdsa_5.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/ecdsa_6.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/ed25519_1.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/ed25519_2.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/ed25519_3.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/ed25519_4.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/ed25519_5.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/ed25519_6.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/known_hosts50
-rw-r--r--regress/unittests/hostkeys/testdata/rsa1_1.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/rsa1_2.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/rsa1_3.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/rsa1_4.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/rsa1_5.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/rsa1_6.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/rsa_1.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/rsa_2.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/rsa_3.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/rsa_4.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/rsa_5.pub1
-rw-r--r--regress/unittests/hostkeys/testdata/rsa_6.pub1
-rw-r--r--regress/unittests/hostkeys/tests.c16
-rw-r--r--regress/unittests/kex/Makefile40
-rw-r--r--regress/unittests/kex/test_kex.c208
-rw-r--r--regress/unittests/kex/test_proposal.c83
-rw-r--r--regress/unittests/kex/tests.c16
-rw-r--r--regress/unittests/match/Makefile16
-rw-r--r--regress/unittests/match/tests.c131
-rw-r--r--regress/unittests/misc/Makefile33
-rw-r--r--regress/unittests/misc/test_argv.c186
-rw-r--r--regress/unittests/misc/test_convtime.c121
-rw-r--r--regress/unittests/misc/test_expand.c89
-rw-r--r--regress/unittests/misc/test_hpdelim.c82
-rw-r--r--regress/unittests/misc/test_parse.c85
-rw-r--r--regress/unittests/misc/test_ptimeout.c85
-rw-r--r--regress/unittests/misc/test_strdelim.c201
-rw-r--r--regress/unittests/misc/tests.c41
-rw-r--r--regress/unittests/sshbuf/Makefile22
-rw-r--r--regress/unittests/sshbuf/test_sshbuf.c243
-rw-r--r--regress/unittests/sshbuf/test_sshbuf_fixed.c125
-rw-r--r--regress/unittests/sshbuf/test_sshbuf_fuzz.c131
-rw-r--r--regress/unittests/sshbuf/test_sshbuf_getput_basic.c712
-rw-r--r--regress/unittests/sshbuf/test_sshbuf_getput_crypto.c280
-rw-r--r--regress/unittests/sshbuf/test_sshbuf_getput_fuzz.c132
-rw-r--r--regress/unittests/sshbuf/test_sshbuf_misc.c217
-rw-r--r--regress/unittests/sshbuf/tests.c30
-rw-r--r--regress/unittests/sshkey/Makefile26
-rw-r--r--regress/unittests/sshkey/common.c163
-rw-r--r--regress/unittests/sshkey/common.h25
-rwxr-xr-xregress/unittests/sshkey/mktestdata.sh222
-rw-r--r--regress/unittests/sshkey/test_file.c559
-rw-r--r--regress/unittests/sshkey/test_fuzz.c391
-rw-r--r--regress/unittests/sshkey/test_sshkey.c528
-rw-r--r--regress/unittests/sshkey/testdata/dsa_112
-rw-r--r--regress/unittests/sshkey/testdata/dsa_1-cert.fp1
-rw-r--r--regress/unittests/sshkey/testdata/dsa_1-cert.pub1
-rw-r--r--regress/unittests/sshkey/testdata/dsa_1.fp1
-rw-r--r--regress/unittests/sshkey/testdata/dsa_1.fp.bb1
-rw-r--r--regress/unittests/sshkey/testdata/dsa_1.param.g1
-rw-r--r--regress/unittests/sshkey/testdata/dsa_1.param.priv1
-rw-r--r--regress/unittests/sshkey/testdata/dsa_1.param.pub1
-rw-r--r--regress/unittests/sshkey/testdata/dsa_1.pub1
-rw-r--r--regress/unittests/sshkey/testdata/dsa_1_pw15
-rw-r--r--regress/unittests/sshkey/testdata/dsa_212
-rw-r--r--regress/unittests/sshkey/testdata/dsa_2.fp1
-rw-r--r--regress/unittests/sshkey/testdata/dsa_2.fp.bb1
-rw-r--r--regress/unittests/sshkey/testdata/dsa_2.pub1
-rw-r--r--regress/unittests/sshkey/testdata/dsa_n21
-rw-r--r--regress/unittests/sshkey/testdata/dsa_n_pw21
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_15
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_1-cert.fp1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_1-cert.pub1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_1.fp1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_1.fp.bb1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_1.param.curve1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_1.param.priv1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_1.param.pub1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_1.pub1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_1_pw8
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_27
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_2.fp1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_2.fp.bb1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_2.param.curve1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_2.param.priv1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_2.param.pub1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_2.pub1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_n8
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_n_pw9
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_sk113
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_sk1-cert.fp1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_sk1-cert.pub1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_sk1.fp1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_sk1.fp.bb1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_sk1.pub1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_sk1_pw14
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_sk213
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_sk2.fp1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_sk2.fp.bb1
-rw-r--r--regress/unittests/sshkey/testdata/ecdsa_sk2.pub1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_17
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_1-cert.fp1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_1-cert.pub1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_1.fp1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_1.fp.bb1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_1.pub1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_1_pw8
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_27
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_2.fp1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_2.fp.bb1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_2.pub1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_sk18
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_sk1-cert.fp1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_sk1-cert.pub1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_sk1.fp1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_sk1.fp.bb1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_sk1.pub1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_sk1_pw9
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_sk28
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_sk2.fp1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_sk2.fp.bb1
-rw-r--r--regress/unittests/sshkey/testdata/ed25519_sk2.pub1
-rw-r--r--regress/unittests/sshkey/testdata/pw1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_115
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1-cert.fp1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1-cert.pub1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1.fp1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1.fp.bb1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1.param.n1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1.param.p1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1.param.q1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1.pub1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1_pw18
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1_sha115
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1_sha1-cert.pub1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1_sha1.pub1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1_sha51215
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1_sha512-cert.pub1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_1_sha512.pub1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_227
-rw-r--r--regress/unittests/sshkey/testdata/rsa_2.fp1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_2.fp.bb1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_2.param.n1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_2.param.p1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_2.param.q1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_2.pub1
-rw-r--r--regress/unittests/sshkey/testdata/rsa_n16
-rw-r--r--regress/unittests/sshkey/testdata/rsa_n_pw17
-rw-r--r--regress/unittests/sshkey/tests.c22
-rw-r--r--regress/unittests/sshsig/Makefile25
-rwxr-xr-xregress/unittests/sshsig/mktestdata.sh42
-rw-r--r--regress/unittests/sshsig/testdata/dsa12
-rw-r--r--regress/unittests/sshsig/testdata/dsa.pub1
-rw-r--r--regress/unittests/sshsig/testdata/dsa.sig13
-rw-r--r--regress/unittests/sshsig/testdata/ecdsa5
-rw-r--r--regress/unittests/sshsig/testdata/ecdsa.pub1
-rw-r--r--regress/unittests/sshsig/testdata/ecdsa.sig7
-rw-r--r--regress/unittests/sshsig/testdata/ecdsa_sk13
-rw-r--r--regress/unittests/sshsig/testdata/ecdsa_sk.pub1
-rw-r--r--regress/unittests/sshsig/testdata/ecdsa_sk.sig8
-rw-r--r--regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.pub1
-rw-r--r--regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.sig13
-rw-r--r--regress/unittests/sshsig/testdata/ed255197
-rw-r--r--regress/unittests/sshsig/testdata/ed25519.pub1
-rw-r--r--regress/unittests/sshsig/testdata/ed25519.sig6
-rw-r--r--regress/unittests/sshsig/testdata/ed25519_sk8
-rw-r--r--regress/unittests/sshsig/testdata/ed25519_sk.pub1
-rw-r--r--regress/unittests/sshsig/testdata/ed25519_sk.sig7
-rw-r--r--regress/unittests/sshsig/testdata/namespace1
-rw-r--r--regress/unittests/sshsig/testdata/rsa39
-rw-r--r--regress/unittests/sshsig/testdata/rsa.pub1
-rw-r--r--regress/unittests/sshsig/testdata/rsa.sig19
-rw-r--r--regress/unittests/sshsig/testdata/signed-data1
-rw-r--r--regress/unittests/sshsig/tests.c142
-rw-r--r--regress/unittests/sshsig/webauthn.html766
-rw-r--r--regress/unittests/test_helper/Makefile15
-rw-r--r--regress/unittests/test_helper/fuzz.c438
-rw-r--r--regress/unittests/test_helper/test_helper.c595
-rw-r--r--regress/unittests/test_helper/test_helper.h326
-rw-r--r--regress/unittests/utf8/Makefile14
-rw-r--r--regress/unittests/utf8/tests.c104
-rwxr-xr-xregress/valgrind-unit.sh24
-rw-r--r--regress/yes-head.sh13
375 files changed, 25289 insertions, 0 deletions
diff --git a/regress/Makefile b/regress/Makefile
new file mode 100644
index 0000000..bf1d057
--- /dev/null
+++ b/regress/Makefile
@@ -0,0 +1,278 @@
+# $OpenBSD: Makefile,v 1.122 2023/01/06 08:07:39 djm Exp $
+
+tests: prep file-tests t-exec unit
+
+REGRESS_TARGETS= t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12
+
+# File based tests
+file-tests: $(REGRESS_TARGETS)
+
+# Interop tests are not run by default
+interop interop-tests: t-exec-interop
+
+prep:
+ test "x${USE_VALGRIND}" = "x" || mkdir -p $(OBJ)/valgrind-out
+
+clean:
+ for F in $(CLEANFILES); do rm -f $(OBJ)$$F; done
+ rm -rf $(OBJ).putty
+
+distclean: clean
+
+LTESTS= connect \
+ proxy-connect \
+ sshfp-connect \
+ connect-privsep \
+ connect-uri \
+ proto-version \
+ proto-mismatch \
+ exit-status \
+ exit-status-signal \
+ envpass \
+ transfer \
+ banner \
+ rekey \
+ dhgex \
+ stderr-data \
+ stderr-after-eof \
+ broken-pipe \
+ try-ciphers \
+ yes-head \
+ login-timeout \
+ agent \
+ agent-getpeereid \
+ agent-timeout \
+ agent-ptrace \
+ agent-subprocess \
+ keyscan \
+ keygen-change \
+ keygen-comment \
+ keygen-convert \
+ keygen-knownhosts \
+ keygen-moduli \
+ keygen-sshfp \
+ key-options \
+ scp \
+ scp3 \
+ scp-uri \
+ sftp \
+ sftp-chroot \
+ sftp-cmds \
+ sftp-badcmds \
+ sftp-batch \
+ sftp-glob \
+ sftp-perm \
+ sftp-uri \
+ reconfigure \
+ dynamic-forward \
+ forwarding \
+ multiplex \
+ reexec \
+ brokenkeys \
+ sshcfgparse \
+ cfgparse \
+ cfgmatch \
+ cfgmatchlisten \
+ percent \
+ addrmatch \
+ localcommand \
+ forcecommand \
+ portnum \
+ keytype \
+ kextype \
+ cert-hostkey \
+ cert-userkey \
+ host-expand \
+ keys-command \
+ forward-control \
+ integrity \
+ krl \
+ multipubkey \
+ limit-keytype \
+ hostkey-agent \
+ hostkey-rotate \
+ principals-command \
+ cert-file \
+ cfginclude \
+ servcfginclude \
+ allow-deny-users \
+ authinfo \
+ sshsig \
+ knownhosts \
+ knownhosts-command \
+ agent-restrict \
+ hostbased \
+ channel-timeout \
+ connection-timeout
+
+INTEROP_TESTS= putty-transfer putty-ciphers putty-kex conch-ciphers
+#INTEROP_TESTS+=ssh-com ssh-com-client ssh-com-keygen ssh-com-sftp
+
+EXTRA_TESTS= agent-pkcs11
+#EXTRA_TESTS+= cipher-speed
+
+USERNAME= ${LOGNAME}
+CLEANFILES= *.core actual agent-key.* authorized_keys_${USERNAME} \
+ authorized_keys_${USERNAME}.* \
+ authorized_principals_${USERNAME} \
+ banner.in banner.out cert_host_key* cert_user_key* \
+ copy.1 copy.2 data ed25519-agent ed25519-agent* \
+ ed25519-agent.pub ed25519 ed25519.pub empty.in \
+ expect failed-regress.log failed-ssh.log failed-sshd.log \
+ hkr.* host.ecdsa-sha2-nistp256 host.ecdsa-sha2-nistp384 \
+ host.ecdsa-sha2-nistp521 host.ssh-dss host.ssh-ed25519 \
+ host.ssh-rsa host_ca_key* host_krl_* host_revoked_* key.* \
+ key.dsa-* key.ecdsa-* key.ed25519-512 \
+ key.ed25519-512.pub key.rsa-* keys-command-args kh.* askpass \
+ known_hosts known_hosts-cert known_hosts.* krl-* ls.copy \
+ modpipe netcat no_identity_config \
+ pidfile putty.rsa2 ready regress.log remote_pid \
+ revoked-* rsa rsa-agent rsa-agent.pub rsa.pub rsa_ssh2_cr.prv \
+ rsa_ssh2_crnl.prv scp-ssh-wrapper.exe \
+ scp-ssh-wrapper.scp setuid-allowed sftp-server.log \
+ sftp-server.sh sftp.log ssh-log-wrapper.sh ssh.log \
+ ssh-agent.log ssh-add.log slow-sftp-server.sh \
+ ssh-rsa_oldfmt knownhosts_command \
+ ssh_config ssh_config.* ssh_proxy ssh_proxy_bak \
+ ssh_proxy_* sshd.log sshd_config sshd_config.* \
+ sshd_config.* sshd_proxy sshd_proxy.* sshd_proxy_bak \
+ sshd_proxy_orig t10.out t10.out.pub t12.out t12.out.pub \
+ t2.out t3.out t6.out1 t6.out2 t7.out t7.out.pub \
+ t8.out t8.out.pub t9.out t9.out.pub testdata \
+ user_*key* user_ca* user_key*
+
+# Enable all malloc(3) randomisations and checks
+TEST_ENV= "MALLOC_OPTIONS=CFGJRSUX"
+
+TEST_SSH_SSHKEYGEN?=ssh-keygen
+
+CPPFLAGS=-I..
+
+t1:
+ ${TEST_SSH_SSHKEYGEN} -if ${.CURDIR}/rsa_ssh2.prv | diff - ${.CURDIR}/rsa_openssh.prv
+ tr '\n' '\r' <${.CURDIR}/rsa_ssh2.prv > ${.OBJDIR}/rsa_ssh2_cr.prv
+ ${TEST_SSH_SSHKEYGEN} -if ${.OBJDIR}/rsa_ssh2_cr.prv | diff - ${.CURDIR}/rsa_openssh.prv
+ awk '{print $$0 "\r"}' ${.CURDIR}/rsa_ssh2.prv > ${.OBJDIR}/rsa_ssh2_crnl.prv
+ ${TEST_SSH_SSHKEYGEN} -if ${.OBJDIR}/rsa_ssh2_crnl.prv | diff - ${.CURDIR}/rsa_openssh.prv
+
+t2:
+ cat ${.CURDIR}/rsa_openssh.prv > $(OBJ)/t2.out
+ chmod 600 $(OBJ)/t2.out
+ ${TEST_SSH_SSHKEYGEN} -yf $(OBJ)/t2.out | diff - ${.CURDIR}/rsa_openssh.pub
+
+t3:
+ ${TEST_SSH_SSHKEYGEN} -ef ${.CURDIR}/rsa_openssh.pub >$(OBJ)/t3.out
+ ${TEST_SSH_SSHKEYGEN} -if $(OBJ)/t3.out | diff - ${.CURDIR}/rsa_openssh.pub
+
+t4:
+ ${TEST_SSH_SSHKEYGEN} -E md5 -lf ${.CURDIR}/rsa_openssh.pub |\
+ awk '{print $$2}' | diff - ${.CURDIR}/t4.ok
+
+t5:
+ ${TEST_SSH_SSHKEYGEN} -Bf ${.CURDIR}/rsa_openssh.pub |\
+ awk '{print $$2}' | diff - ${.CURDIR}/t5.ok
+
+t6:
+ ${TEST_SSH_SSHKEYGEN} -if ${.CURDIR}/dsa_ssh2.prv > $(OBJ)/t6.out1
+ ${TEST_SSH_SSHKEYGEN} -if ${.CURDIR}/dsa_ssh2.pub > $(OBJ)/t6.out2
+ chmod 600 $(OBJ)/t6.out1
+ ${TEST_SSH_SSHKEYGEN} -yf $(OBJ)/t6.out1 | diff - $(OBJ)/t6.out2
+
+$(OBJ)/t7.out:
+ ${TEST_SSH_SSHKEYGEN} -q -t rsa -N '' -f $@
+
+t7: $(OBJ)/t7.out
+ ${TEST_SSH_SSHKEYGEN} -lf $(OBJ)/t7.out > /dev/null
+ ${TEST_SSH_SSHKEYGEN} -Bf $(OBJ)/t7.out > /dev/null
+
+$(OBJ)/t8.out:
+ ${TEST_SSH_SSHKEYGEN} -q -t dsa -N '' -f $@
+
+t8: $(OBJ)/t8.out
+ ${TEST_SSH_SSHKEYGEN} -lf $(OBJ)/t8.out > /dev/null
+ ${TEST_SSH_SSHKEYGEN} -Bf $(OBJ)/t8.out > /dev/null
+
+$(OBJ)/t9.out:
+ ! ${TEST_SSH_SSH} -Q key-plain | grep ecdsa >/dev/null || \
+ ${TEST_SSH_SSHKEYGEN} -q -t ecdsa -N '' -f $@
+
+t9: $(OBJ)/t9.out
+ ! ${TEST_SSH_SSH} -Q key-plain | grep ecdsa >/dev/null || \
+ ${TEST_SSH_SSHKEYGEN} -lf $(OBJ)/t9.out > /dev/null
+ ! ${TEST_SSH_SSH} -Q key-plain | grep ecdsa >/dev/null || \
+ ${TEST_SSH_SSHKEYGEN} -Bf $(OBJ)/t9.out > /dev/null
+
+
+$(OBJ)/t10.out:
+ ${TEST_SSH_SSHKEYGEN} -q -t ed25519 -N '' -f $@
+
+t10: $(OBJ)/t10.out
+ ${TEST_SSH_SSHKEYGEN} -lf $(OBJ)/t10.out > /dev/null
+ ${TEST_SSH_SSHKEYGEN} -Bf $(OBJ)/t10.out > /dev/null
+
+t11:
+ ${TEST_SSH_SSHKEYGEN} -E sha256 -lf ${.CURDIR}/rsa_openssh.pub |\
+ awk '{print $$2}' | diff - ${.CURDIR}/t11.ok
+
+$(OBJ)/t12.out:
+ ${TEST_SSH_SSHKEYGEN} -q -t ed25519 -N '' -C 'test-comment-1234' -f $@
+
+t12: $(OBJ)/t12.out
+ ${TEST_SSH_SSHKEYGEN} -lf $(OBJ)/t12.out.pub | grep test-comment-1234 >/dev/null
+
+t-exec: ${LTESTS:=.sh}
+ @if [ "x$?" = "x" ]; then exit 0; fi; \
+ for TEST in ""$?; do \
+ skip=no; \
+ for t in ""$${SKIP_LTESTS}; do \
+ if [ "x$${t}.sh" = "x$${TEST}" ]; then skip=yes; fi; \
+ done; \
+ if [ "x$${skip}" = "xno" ]; then \
+ echo "run test $${TEST}" ... 1>&2; \
+ (env SUDO="${SUDO}" TEST_ENV=${TEST_ENV} ${TEST_SHELL} ${.CURDIR}/test-exec.sh ${.OBJDIR} ${.CURDIR}/$${TEST}) || exit $$?; \
+ else \
+ echo skip test $${TEST} 1>&2; \
+ fi; \
+ done
+
+t-exec-interop: ${INTEROP_TESTS:=.sh}
+ @if [ "x$?" = "x" ]; then exit 0; fi; \
+ for TEST in ""$?; do \
+ echo "run test $${TEST}" ... 1>&2; \
+ (env SUDO="${SUDO}" TEST_ENV=${TEST_ENV} ${TEST_SHELL} ${.CURDIR}/test-exec.sh ${.OBJDIR} ${.CURDIR}/$${TEST}) || exit $$?; \
+ done
+
+t-extra: ${EXTRA_TESTS:=.sh}
+ @if [ "x$?" = "x" ]; then exit 0; fi; \
+ for TEST in ""$?; do \
+ echo "run test $${TEST}" ... 1>&2; \
+ (env SUDO="${SUDO}" TEST_ENV=${TEST_ENV} ${TEST_SHELL} ${.CURDIR}/test-exec.sh ${.OBJDIR} ${.CURDIR}/$${TEST}) || exit $$?; \
+ done
+
+# Not run by default
+interop: ${INTEROP_TARGETS}
+
+# Unit tests, built by top-level Makefile
+unit:
+ set -e ; if test -z "${SKIP_UNIT}" ; then \
+ V="" ; \
+ test "x${USE_VALGRIND}" = "x" || \
+ V=${.CURDIR}/valgrind-unit.sh ; \
+ $$V ${.OBJDIR}/unittests/sshbuf/test_sshbuf ; \
+ $$V ${.OBJDIR}/unittests/sshkey/test_sshkey \
+ -d ${.CURDIR}/unittests/sshkey/testdata ; \
+ $$V ${.OBJDIR}/unittests/sshsig/test_sshsig \
+ -d ${.CURDIR}/unittests/sshsig/testdata ; \
+ $$V ${.OBJDIR}/unittests/authopt/test_authopt \
+ -d ${.CURDIR}/unittests/authopt/testdata ; \
+ $$V ${.OBJDIR}/unittests/bitmap/test_bitmap ; \
+ $$V ${.OBJDIR}/unittests/conversion/test_conversion ; \
+ $$V ${.OBJDIR}/unittests/kex/test_kex ; \
+ $$V ${.OBJDIR}/unittests/hostkeys/test_hostkeys \
+ -d ${.CURDIR}/unittests/hostkeys/testdata ; \
+ $$V ${.OBJDIR}/unittests/match/test_match ; \
+ $$V ${.OBJDIR}/unittests/misc/test_misc ; \
+ if test "x${TEST_SSH_UTF8}" = "xyes" ; then \
+ $$V ${.OBJDIR}/unittests/utf8/test_utf8 ; \
+ fi \
+ fi
diff --git a/regress/README.regress b/regress/README.regress
new file mode 100644
index 0000000..ac2e848
--- /dev/null
+++ b/regress/README.regress
@@ -0,0 +1,161 @@
+Overview.
+
+$ ./configure && make tests
+
+You'll see some progress info. A failure will cause either the make to
+abort or the driver script to report a "FATAL" failure.
+
+The test consists of 2 parts. The first is the file-based tests which is
+driven by the Makefile, and the second is a set of network or proxycommand
+based tests, which are driven by a driver script (test-exec.sh) which is
+called multiple times by the Makefile.
+
+Failures in the first part will cause the Makefile to return an error.
+Failures in the second part will print a "FATAL" message for the failed
+test and continue.
+
+OpenBSD has a system-wide regression test suite. OpenSSH Portable's test
+suite is based on OpenBSD's with modifications.
+
+
+Environment variables.
+
+SKIP_UNIT: Skip unit tests.
+SUDO: path to sudo/doas command, if desired. Note that some systems
+ (notably systems using PAM) require sudo to execute some tests.
+LTESTS: Whitespace separated list of tests (filenames without the .sh
+ extension) to run.
+SKIP_LTESTS: Whitespace separated list of tests to skip.
+OBJ: used by test scripts to access build dir.
+TEST_SHELL: shell used for running the test scripts.
+TEST_SSH_FAIL_FATAL: set to "yes" to make any failure abort the test
+ currently in progress.
+TEST_SSH_PORT: TCP port to be used for the listening tests.
+TEST_SSH_QUIET: set to "yes" to suppress non-fatal output.
+TEST_SSH_SSHD_CONFOPTS: Configuration directives to be added to sshd_config
+ before running each test.
+TEST_SSH_SSH_CONFOPTS: Configuration directives to be added to
+ ssh_config before running each test.
+TEST_SSH_TRACE: set to "yes" for verbose output from tests
+TEST_SSH_x: path to "ssh" command under test, where x is one of
+ SSH, SSHD, SSHAGENT, SSHADD, SSHKEYGEN, SSHKEYSCAN, SFTP or
+ SFTPSERVER
+USE_VALGRIND: Run the tests under valgrind memory checker.
+
+
+Individual tests.
+
+You can run an individual test from the top-level Makefile, eg:
+$ make tests LTESTS=agent-timeout
+
+If you need to manipulate the environment more you can invoke test-exec.sh
+directly if you set up the path to find the binaries under test and the
+test scripts themselves, for example:
+
+$ cd regress
+$ PATH=`pwd`/..:$PATH:. TEST_SHELL=/bin/sh sh test-exec.sh `pwd` \
+ agent-timeout.sh
+ok agent timeout test
+
+
+Files.
+
+test-exec.sh: the main test driver. Sets environment, creates config files
+and keys and runs the specified test.
+
+At the time of writing, the individual tests are:
+connect.sh: simple connect
+proxy-connect.sh: proxy connect
+connect-privsep.sh: proxy connect with privsep
+connect-uri.sh: uri connect
+proto-version.sh: sshd version with different protocol combinations
+proto-mismatch.sh: protocol version mismatch
+exit-status.sh: remote exit status
+envpass.sh: environment passing
+transfer.sh: transfer data
+banner.sh: banner
+rekey.sh: rekey
+stderr-data.sh: stderr data transfer
+stderr-after-eof.sh: stderr data after eof
+broken-pipe.sh: broken pipe test
+try-ciphers.sh: try ciphers
+yes-head.sh: yes pipe head
+login-timeout.sh: connect after login grace timeout
+agent.sh: simple connect via agent
+agent-getpeereid.sh: disallow agent attach from other uid
+agent-timeout.sh: agent timeout test
+agent-ptrace.sh: disallow agent ptrace attach
+keyscan.sh: keyscan
+keygen-change.sh: change passphrase for key
+keygen-convert.sh: convert keys
+keygen-moduli.sh: keygen moduli
+key-options.sh: key options
+scp.sh: scp
+scp-uri.sh: scp-uri
+sftp.sh: basic sftp put/get
+sftp-chroot.sh: sftp in chroot
+sftp-cmds.sh: sftp command
+sftp-badcmds.sh: sftp invalid commands
+sftp-batch.sh: sftp batchfile
+sftp-glob.sh: sftp glob
+sftp-perm.sh: sftp permissions
+sftp-uri.sh: sftp-uri
+ssh-com-client.sh: connect with ssh.com client
+ssh-com-keygen.sh: ssh.com key import
+ssh-com-sftp.sh: basic sftp put/get with ssh.com server
+ssh-com.sh: connect to ssh.com server
+reconfigure.sh: simple connect after reconfigure
+dynamic-forward.sh: dynamic forwarding
+forwarding.sh: local and remote forwarding
+multiplex.sh: connection multiplexing
+reexec.sh: reexec tests
+brokenkeys.sh: broken keys
+sshcfgparse.sh: ssh config parse
+cfgparse.sh: sshd config parse
+cfgmatch.sh: sshd_config match
+cfgmatchlisten.sh: sshd_config matchlisten
+addrmatch.sh: address match
+localcommand.sh: localcommand
+forcecommand.sh: forced command
+portnum.sh: port number parsing
+keytype.sh: login with different key types
+kextype.sh: login with different key exchange algorithms
+cert-hostkey.sh certified host keys
+cert-userkey.sh: certified user keys
+host-expand.sh: expand %h and %n
+keys-command.sh: authorized keys from command
+forward-control.sh: sshd control of local and remote forwarding
+integrity.sh: integrity
+krl.sh: key revocation lists
+multipubkey.sh: multiple pubkey
+limit-keytype.sh: restrict pubkey type
+hostkey-agent.sh: hostkey agent
+keygen-knownhosts.sh: ssh-keygen known_hosts
+hostkey-rotate.sh: hostkey rotate
+principals-command.sh: authorized principals command
+cert-file.sh: ssh with certificates
+cfginclude.sh: config include
+allow-deny-users.sh: AllowUsers/DenyUsers
+authinfo.sh: authinfo
+
+
+Problems?
+
+Run the failing test with shell tracing (-x) turned on:
+$ PATH=`pwd`/..:$PATH:. sh -x test-exec.sh `pwd` agent-timeout.sh
+
+Failed tests can be difficult to diagnose. Suggestions:
+- run the individual test via ./test-exec.sh `pwd` [testname]
+- set LogLevel to VERBOSE in test-exec.sh and enable syslogging of
+ auth.debug (eg to /var/log/authlog).
+
+
+Known Issues.
+
+- Similarly, if you do not have "scp" in your system's $PATH then the
+ multiplex scp tests will fail (since the system's shell startup scripts
+ will determine where the shell started by sshd will look for scp).
+
+- Recent GNU coreutils deprecate "head -[n]": this will cause the yes-head
+ test to fail. The old behaviour can be restored by setting (and
+ exporting) _POSIX2_VERSION=199209 before running the tests.
diff --git a/regress/addrmatch.sh b/regress/addrmatch.sh
new file mode 100644
index 0000000..26e0c99
--- /dev/null
+++ b/regress/addrmatch.sh
@@ -0,0 +1,68 @@
+# $OpenBSD: addrmatch.sh,v 1.6 2020/08/28 03:17:13 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="address match"
+
+mv $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+
+run_trial()
+{
+ user="$1"; addr="$2"; host="$3"; laddr="$4"; lport="$5"
+ expected="$6"; descr="$7"
+
+ verbose "test $descr for $user $addr $host"
+ result=`${SSHD} -f $OBJ/sshd_proxy -T \
+ -C user=${user},addr=${addr},host=${host},laddr=${laddr},lport=${lport} | \
+ awk '/^forcecommand/ {print $2}'`
+ if [ "$result" != "$expected" ]; then
+ fail "failed '$descr' expected $expected got $result"
+ fi
+}
+
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+cat >>$OBJ/sshd_proxy <<EOD
+ForceCommand nomatch
+Match Address 192.168.0.0/16,!192.168.30.0/24,10.0.0.0/8,host.example.com
+ ForceCommand match1
+Match Address 1.1.1.1,::1,!::3,2000::/16
+ ForceCommand match2
+Match LocalAddress 127.0.0.1,::1
+ ForceCommand match3
+Match LocalPort 5678
+ ForceCommand match4
+EOD
+
+run_trial user 192.168.0.1 somehost 1.2.3.4 1234 match1 "first entry"
+run_trial user 192.168.30.1 somehost 1.2.3.4 1234 nomatch "negative match"
+run_trial user 19.0.0.1 somehost 1.2.3.4 1234 nomatch "no match"
+run_trial user 10.255.255.254 somehost 1.2.3.4 1234 match1 "list middle"
+run_trial user 192.168.30.1 192.168.0.1 1.2.3.4 1234 nomatch "faked IP in hostname"
+run_trial user 1.1.1.1 somehost.example.com 1.2.3.4 1234 match2 "bare IP4 address"
+run_trial user 19.0.0.1 somehost 127.0.0.1 1234 match3 "localaddress"
+run_trial user 19.0.0.1 somehost 1.2.3.4 5678 match4 "localport"
+
+if test "$TEST_SSH_IPV6" != "no"; then
+run_trial user ::1 somehost.example.com ::2 1234 match2 "bare IP6 address"
+run_trial user ::2 somehost.example.com ::2 1234 nomatch "deny IPv6"
+run_trial user ::3 somehost ::2 1234 nomatch "IP6 negated"
+run_trial user ::4 somehost ::2 1234 nomatch "IP6 no match"
+run_trial user 2000::1 somehost ::2 1234 match2 "IP6 network"
+run_trial user 2001::1 somehost ::2 1234 nomatch "IP6 network"
+run_trial user ::5 somehost ::1 1234 match3 "IP6 localaddress"
+run_trial user ::5 somehost ::2 5678 match4 "IP6 localport"
+fi
+
+#
+# Check that we catch invalid address/mask in Match Address/Localaddress
+#
+for i in 10.0.1.0/8 10.0.0.1/24 2000:aa:bb:01::/56; do
+ for a in address localaddress; do
+ verbose "test invalid Match $a $i"
+ echo "Match $a $i" > $OBJ/sshd_proxy
+ ${SUDO} ${SSHD} -f $OBJ/sshd_proxy -t >/dev/null 2>&1 && \
+ fail "accepted invalid match $a $i"
+ done
+done
+
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+rm $OBJ/sshd_proxy_bak
diff --git a/regress/agent-getpeereid.sh b/regress/agent-getpeereid.sh
new file mode 100644
index 0000000..79e9c7d
--- /dev/null
+++ b/regress/agent-getpeereid.sh
@@ -0,0 +1,59 @@
+# $OpenBSD: agent-getpeereid.sh,v 1.13 2021/09/01 00:50:27 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="disallow agent attach from other uid"
+
+UNPRIV=nobody
+ASOCK=${OBJ}/agent
+SSH_AUTH_SOCK=/nonexistent
+>$OBJ/ssh-agent.log
+>$OBJ/ssh-add.log
+
+if config_defined HAVE_GETPEEREID HAVE_GETPEERUCRED HAVE_SO_PEERCRED ; then
+ :
+else
+ skip "skipped (not supported on this platform)"
+fi
+if test "x$USER" = "xroot"; then
+ skip "skipped (running as root)"
+fi
+case "x$SUDO" in
+ xsudo) sudo=1;;
+ xdoas|xdoas\ *) ;;
+ x)
+ skip "need SUDO to switch to uid $UNPRIV" ;;
+ *)
+ skip "unsupported $SUDO - "doas" and "sudo" are allowed" ;;
+esac
+
+trace "start agent"
+eval `${SSHAGENT} ${EXTRA_AGENT_ARGS} -s -a ${ASOCK}` >$OBJ/ssh-agent.log 2>&1
+r=$?
+if [ $r -ne 0 ]; then
+ fail "could not start ssh-agent: exit code $r"
+else
+ chmod 644 ${SSH_AUTH_SOCK}
+
+ ${SSHADD} -vvv -l >>$OBJ/ssh-add.log 2>&1
+ r=$?
+ if [ $r -ne 1 ]; then
+ fail "ssh-add failed with $r != 1"
+ fi
+ if test -z "$sudo" ; then
+ # doas
+ ${SUDO} -n -u ${UNPRIV} ${SSHADD} -l 2>/dev/null
+ else
+ # sudo
+ < /dev/null ${SUDO} -S -u ${UNPRIV} ${SSHADD} -vvv -l >>$OBJ/ssh-add.log 2>&1
+ fi
+ r=$?
+ if [ $r -lt 2 ]; then
+ fail "ssh-add did not fail for ${UNPRIV}: $r < 2"
+ cat $OBJ/ssh-add.log
+ fi
+
+ trace "kill agent"
+ ${SSHAGENT} -vvv -k >>$OBJ/ssh-agent.log 2>&1
+fi
+
+rm -f ${OBJ}/agent
diff --git a/regress/agent-pkcs11.sh b/regress/agent-pkcs11.sh
new file mode 100644
index 0000000..268a70d
--- /dev/null
+++ b/regress/agent-pkcs11.sh
@@ -0,0 +1,124 @@
+# $OpenBSD: agent-pkcs11.sh,v 1.9 2021/07/25 12:13:03 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="pkcs11 agent test"
+
+try_token_libs() {
+ for _lib in "$@" ; do
+ if test -f "$_lib" ; then
+ verbose "Using token library $_lib"
+ TEST_SSH_PKCS11="$_lib"
+ return
+ fi
+ done
+ echo "skipped: Unable to find PKCS#11 token library"
+ exit 0
+}
+
+try_token_libs \
+ /usr/local/lib/softhsm/libsofthsm2.so \
+ /usr/lib64/pkcs11/libsofthsm2.so \
+ /usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so
+
+TEST_SSH_PIN=1234
+TEST_SSH_SOPIN=12345678
+if [ "x$TEST_SSH_SSHPKCS11HELPER" != "x" ]; then
+ SSH_PKCS11_HELPER="${TEST_SSH_SSHPKCS11HELPER}"
+ export SSH_PKCS11_HELPER
+fi
+
+test -f "$TEST_SSH_PKCS11" || fatal "$TEST_SSH_PKCS11 does not exist"
+
+# setup environment for softhsm2 token
+DIR=$OBJ/SOFTHSM
+rm -rf $DIR
+TOKEN=$DIR/tokendir
+mkdir -p $TOKEN
+SOFTHSM2_CONF=$DIR/softhsm2.conf
+export SOFTHSM2_CONF
+cat > $SOFTHSM2_CONF << EOF
+# SoftHSM v2 configuration file
+directories.tokendir = ${TOKEN}
+objectstore.backend = file
+# ERROR, WARNING, INFO, DEBUG
+log.level = DEBUG
+# If CKF_REMOVABLE_DEVICE flag should be set
+slots.removable = false
+EOF
+out=$(softhsm2-util --init-token --free --label token-slot-0 --pin "$TEST_SSH_PIN" --so-pin "$TEST_SSH_SOPIN")
+slot=$(echo -- $out | sed 's/.* //')
+
+# prevent ssh-agent from calling ssh-askpass
+SSH_ASKPASS=/usr/bin/true
+export SSH_ASKPASS
+unset DISPLAY
+
+# start command w/o tty, so ssh-add accepts pin from stdin
+notty() {
+ perl -e 'use POSIX; POSIX::setsid();
+ if (fork) { wait; exit($? >> 8); } else { exec(@ARGV) }' "$@"
+}
+
+trace "generating keys"
+RSA=${DIR}/RSA
+EC=${DIR}/EC
+$OPENSSL_BIN genpkey -algorithm rsa > $RSA
+$OPENSSL_BIN pkcs8 -nocrypt -in $RSA |\
+ softhsm2-util --slot "$slot" --label 01 --id 01 --pin "$TEST_SSH_PIN" --import /dev/stdin
+$OPENSSL_BIN genpkey \
+ -genparam \
+ -algorithm ec \
+ -pkeyopt ec_paramgen_curve:prime256v1 |\
+ $OPENSSL_BIN genpkey \
+ -paramfile /dev/stdin > $EC
+$OPENSSL_BIN pkcs8 -nocrypt -in $EC |\
+ softhsm2-util --slot "$slot" --label 02 --id 02 --pin "$TEST_SSH_PIN" --import /dev/stdin
+
+trace "start agent"
+eval `${SSHAGENT} ${EXTRA_AGENT_ARGS} -s` > /dev/null
+r=$?
+if [ $r -ne 0 ]; then
+ fail "could not start ssh-agent: exit code $r"
+else
+ trace "add pkcs11 key to agent"
+ echo ${TEST_SSH_PIN} | notty ${SSHADD} -s ${TEST_SSH_PKCS11} > /dev/null 2>&1
+ r=$?
+ if [ $r -ne 0 ]; then
+ fail "ssh-add -s failed: exit code $r"
+ fi
+
+ trace "pkcs11 list via agent"
+ ${SSHADD} -l > /dev/null 2>&1
+ r=$?
+ if [ $r -ne 0 ]; then
+ fail "ssh-add -l failed: exit code $r"
+ fi
+
+ for k in $RSA $EC; do
+ trace "testing $k"
+ chmod 600 $k
+ ssh-keygen -y -f $k > $k.pub
+ pub=$(cat $k.pub)
+ ${SSHADD} -L | grep -q "$pub" || fail "key $k missing in ssh-add -L"
+ ${SSHADD} -T $k.pub || fail "ssh-add -T with $k failed"
+
+ # add to authorized keys
+ cat $k.pub > $OBJ/authorized_keys_$USER
+ trace "pkcs11 connect via agent ($k)"
+ ${SSH} -F $OBJ/ssh_proxy somehost exit 5
+ r=$?
+ if [ $r -ne 5 ]; then
+ fail "ssh connect failed (exit code $r)"
+ fi
+ done
+
+ trace "remove pkcs11 keys"
+ echo ${TEST_SSH_PIN} | notty ${SSHADD} -e ${TEST_SSH_PKCS11} > /dev/null 2>&1
+ r=$?
+ if [ $r -ne 0 ]; then
+ fail "ssh-add -e failed: exit code $r"
+ fi
+
+ trace "kill agent"
+ ${SSHAGENT} -k > /dev/null
+fi
diff --git a/regress/agent-ptrace.sh b/regress/agent-ptrace.sh
new file mode 100644
index 0000000..df55b34
--- /dev/null
+++ b/regress/agent-ptrace.sh
@@ -0,0 +1,67 @@
+# $OpenBSD: agent-ptrace.sh,v 1.5 2022/04/22 05:08:43 anton Exp $
+# Placed in the Public Domain.
+
+tid="disallow agent ptrace attach"
+
+if have_prog uname ; then
+ case `uname` in
+ AIX|CYGWIN*|OSF1)
+ echo "skipped (not supported on this platform)"
+ exit 0
+ ;;
+ esac
+fi
+
+if [ "x$USER" = "xroot" ]; then
+ echo "Skipped: running as root"
+ exit 0
+fi
+
+if have_prog gdb ; then
+ : ok
+else
+ echo "skipped (gdb not found)"
+ exit 0
+fi
+
+if $OBJ/setuid-allowed ${SSHAGENT} ; then
+ : ok
+else
+ echo "skipped (${SSHAGENT} is mounted on a no-setuid filesystem)"
+ exit 0
+fi
+
+if test -z "$SUDO" ; then
+ echo "skipped (SUDO not set)"
+ exit 0
+else
+ $SUDO chown 0 ${SSHAGENT}
+ $SUDO chgrp 0 ${SSHAGENT}
+ $SUDO chmod 2755 ${SSHAGENT}
+ trap "$SUDO chown ${USER} ${SSHAGENT}; $SUDO chmod 755 ${SSHAGENT}" 0
+fi
+
+trace "start agent"
+eval `${SSHAGENT} ${EXTRA_AGENT_ARGS} -s` > /dev/null
+r=$?
+if [ $r -ne 0 ]; then
+ fail "could not start ssh-agent: exit code $r"
+else
+ # ls -l ${SSH_AUTH_SOCK}
+ gdb ${SSHAGENT} ${SSH_AGENT_PID} > ${OBJ}/gdb.out 2>&1 << EOF
+ quit
+EOF
+ r=$?
+ if [ $r -ne 0 ]; then
+ fail "gdb failed: exit code $r"
+ fi
+ egrep 'ptrace: Operation not permitted.|procfs:.*Permission denied.|ttrace.*Permission denied.|procfs:.*: Invalid argument.|Unable to access task ' >/dev/null ${OBJ}/gdb.out
+ r=$?
+ rm -f ${OBJ}/gdb.out
+ if [ $r -ne 0 ]; then
+ fail "ptrace succeeded?: exit code $r"
+ fi
+
+ trace "kill agent"
+ ${SSHAGENT} -k > /dev/null
+fi
diff --git a/regress/agent-restrict.sh b/regress/agent-restrict.sh
new file mode 100644
index 0000000..a30aed7
--- /dev/null
+++ b/regress/agent-restrict.sh
@@ -0,0 +1,495 @@
+# $OpenBSD: agent-restrict.sh,v 1.5 2022/01/13 04:53:16 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="agent restrictions"
+
+SSH_AUTH_SOCK="$OBJ/agent.sock"
+export SSH_AUTH_SOCK
+rm -f $SSH_AUTH_SOCK $OBJ/agent.log $OBJ/host_[abcdex]* $OBJ/user_[abcdex]*
+rm -f $OBJ/sshd_proxy_host* $OBJ/ssh_output* $OBJ/expect_*
+rm -f $OBJ/ssh_proxy[._]* $OBJ/command
+
+verbose "generate keys"
+for h in a b c d e x ca ; do
+ $SSHKEYGEN -q -t ed25519 -C host_$h -N '' -f $OBJ/host_$h || \
+ fatal "ssh-keygen hostkey failed"
+ $SSHKEYGEN -q -t ed25519 -C user_$h -N '' -f $OBJ/user_$h || \
+ fatal "ssh-keygen userkey failed"
+done
+
+# Make some hostcerts
+for h in d e ; do
+ id="host_$h"
+ $SSHKEYGEN -q -s $OBJ/host_ca -I $id -n $id -h $OBJ/host_${h}.pub || \
+ fatal "ssh-keygen certify failed"
+done
+
+verbose "prepare client config"
+egrep -vi '(identityfile|hostname|hostkeyalias|proxycommand)' \
+ $OBJ/ssh_proxy > $OBJ/ssh_proxy.bak
+cat << _EOF > $OBJ/ssh_proxy
+IdentitiesOnly yes
+ForwardAgent yes
+ExitOnForwardFailure yes
+_EOF
+cp $OBJ/ssh_proxy $OBJ/ssh_proxy_noid
+for h in a b c d e ; do
+ cat << _EOF >> $OBJ/ssh_proxy
+Host host_$h
+ Hostname host_$h
+ HostkeyAlias host_$h
+ IdentityFile $OBJ/user_$h
+ ProxyCommand ${SUDO} env SSH_SK_HELPER=\"$SSH_SK_HELPER\" sh ${SRC}/sshd-log-wrapper.sh ${TEST_SSHD_LOGFILE} ${SSHD} -i -f $OBJ/sshd_proxy_host_$h
+_EOF
+ # Variant with no specified keys.
+ cat << _EOF >> $OBJ/ssh_proxy_noid
+Host host_$h
+ Hostname host_$h
+ HostkeyAlias host_$h
+ ProxyCommand ${SUDO} env SSH_SK_HELPER=\"$SSH_SK_HELPER\" sh ${SRC}/sshd-log-wrapper.sh ${TEST_SSHD_LOGFILE} ${SSHD} -i -f $OBJ/sshd_proxy_host_$h
+_EOF
+done
+cat $OBJ/ssh_proxy.bak >> $OBJ/ssh_proxy
+cat $OBJ/ssh_proxy.bak >> $OBJ/ssh_proxy_noid
+
+LC_ALL=C
+export LC_ALL
+echo "SetEnv LC_ALL=${LC_ALL}" >> sshd_proxy
+
+verbose "prepare known_hosts"
+rm -f $OBJ/known_hosts
+for h in a b c x ; do
+ (printf "host_$h " ; cat $OBJ/host_${h}.pub) >> $OBJ/known_hosts
+done
+(printf "@cert-authority host_* " ; cat $OBJ/host_ca.pub) >> $OBJ/known_hosts
+
+verbose "prepare server configs"
+egrep -vi '(hostkey|pidfile)' $OBJ/sshd_proxy \
+ > $OBJ/sshd_proxy.bak
+for h in a b c d e; do
+ cp $OBJ/sshd_proxy.bak $OBJ/sshd_proxy_host_$h
+ cat << _EOF >> $OBJ/sshd_proxy_host_$h
+ExposeAuthInfo yes
+PidFile none
+Hostkey $OBJ/host_$h
+_EOF
+done
+for h in d e ; do
+ echo "HostCertificate $OBJ/host_${h}-cert.pub" \
+ >> $OBJ/sshd_proxy_host_$h
+done
+# Create authorized_keys with canned command.
+reset_keys() {
+ _whichcmd="$1"
+ _command=""
+ case "$_whichcmd" in
+ authinfo) _command="cat \$SSH_USER_AUTH" ;;
+ keylist) _command="$SSHADD -L | cut -d' ' -f-2 | sort" ;;
+ *) fatal "unsupported command $_whichcmd" ;;
+ esac
+ trace "reset keys"
+ >$OBJ/authorized_keys_$USER
+ for h in e d c b a; do
+ (printf "%s" "restrict,agent-forwarding,command=\"$_command\" ";
+ cat $OBJ/user_$h.pub) >> $OBJ/authorized_keys_$USER
+ done
+}
+# Prepare a key for comparison with ExposeAuthInfo/$SSH_USER_AUTH.
+expect_key() {
+ _key="$OBJ/${1}.pub"
+ _file="$OBJ/$2"
+ (printf "publickey " ; cut -d' ' -f-2 $_key) > $_file
+}
+# Prepare expect_* files to compare against authinfo forced command to ensure
+# keys used for authentication match.
+reset_expect_keys() {
+ for u in a b c d e; do
+ expect_key user_$u expect_$u
+ done
+}
+# ssh to host, expecting success and that output matched expectation for
+# that host (expect_$h file).
+expect_succeed() {
+ _id="$1"
+ _case="$2"
+ shift; shift; _extra="$@"
+ _host="host_$_id"
+ trace "connect $_host expect success"
+ rm -f $OBJ/ssh_output
+ ${SSH} $_extra -F $OBJ/ssh_proxy $_host true > $OBJ/ssh_output
+ _s=$?
+ test $_s -eq 0 || fail "host $_host $_case fail, exit status $_s"
+ diff $OBJ/ssh_output $OBJ/expect_${_id} ||
+ fail "unexpected ssh output"
+}
+# ssh to host using explicit key, expecting success and that the key was
+# actually used for authentication.
+expect_succeed_key() {
+ _id="$1"
+ _key="$2"
+ _case="$3"
+ shift; shift; shift; _extra="$@"
+ _host="host_$_id"
+ trace "connect $_host expect success, with key $_key"
+ _keyfile="$OBJ/$_key"
+ rm -f $OBJ/ssh_output
+ ${SSH} $_extra -F $OBJ/ssh_proxy_noid \
+ -oIdentityFile=$_keyfile $_host true > $OBJ/ssh_output
+ _s=$?
+ test $_s -eq 0 || fail "host $_host $_key $_case fail, exit status $_s"
+ expect_key $_key expect_key
+ diff $OBJ/ssh_output $OBJ/expect_key ||
+ fail "incorrect key used for authentication"
+}
+# ssh to a host, expecting it to fail.
+expect_fail() {
+ _host="$1"
+ _case="$2"
+ shift; shift; _extra="$@"
+ trace "connect $_host expect failure"
+ ${SSH} $_extra -F $OBJ/ssh_proxy $_host true >/dev/null && \
+ fail "host $_host $_case succeeded unexpectedly"
+}
+# ssh to a host using an explicit key, expecting it to fail.
+expect_fail_key() {
+ _id="$1"
+ _key="$2"
+ _case="$3"
+ shift; shift; shift; _extra="$@"
+ _host="host_$_id"
+ trace "connect $_host expect failure, with key $_key"
+ _keyfile="$OBJ/$_key"
+ ${SSH} $_extra -F $OBJ/ssh_proxy_noid -oIdentityFile=$_keyfile \
+ $_host true > $OBJ/ssh_output && \
+ fail "host $_host $_key $_case succeeded unexpectedly"
+}
+# Move the private key files out of the way to force use of agent-hosted keys.
+hide_privatekeys() {
+ trace "hide private keys"
+ for u in a b c d e x; do
+ mv $OBJ/user_$u $OBJ/user_x$u || fatal "hide privkey $u"
+ done
+}
+# Put the private key files back.
+restore_privatekeys() {
+ trace "restore private keys"
+ for u in a b c d e x; do
+ mv $OBJ/user_x$u $OBJ/user_$u || fatal "restore privkey $u"
+ done
+}
+clear_agent() {
+ ${SSHADD} -D > /dev/null 2>&1 || fatal "clear agent failed"
+}
+
+reset_keys authinfo
+reset_expect_keys
+
+verbose "authentication w/o agent"
+for h in a b c d e ; do
+ expect_succeed $h "w/o agent"
+ wrongkey=user_e
+ test "$h" = "e" && wrongkey=user_a
+ expect_succeed_key $h $wrongkey "\"wrong\" key w/o agent"
+done
+hide_privatekeys
+for h in a b c d e ; do
+ expect_fail $h "w/o agent"
+done
+restore_privatekeys
+
+verbose "start agent"
+${SSHAGENT} ${EXTRA_AGENT_ARGS} -d -a $SSH_AUTH_SOCK > $OBJ/agent.log 2>&1 &
+AGENT_PID=$!
+trap "kill $AGENT_PID" EXIT
+sleep 4 # Give it a chance to start
+# Check that it's running.
+${SSHADD} -l > /dev/null 2>&1
+if [ $? -ne 1 ]; then
+ fail "ssh-add -l did not fail with exit code 1"
+fi
+
+verbose "authentication with agent (no restrict)"
+for u in a b c d e x; do
+ $SSHADD -q $OBJ/user_$u || fatal "add key $u unrestricted"
+done
+hide_privatekeys
+for h in a b c d e ; do
+ expect_succeed $h "with agent"
+ wrongkey=user_e
+ test "$h" = "e" && wrongkey=user_a
+ expect_succeed_key $h $wrongkey "\"wrong\" key with agent"
+done
+
+verbose "unrestricted keylist"
+reset_keys keylist
+rm -f $OBJ/expect_list.pre
+# List of keys from agent should contain everything.
+for u in a b c d e x; do
+ cut -d " " -f-2 $OBJ/user_${u}.pub >> $OBJ/expect_list.pre
+done
+sort $OBJ/expect_list.pre > $OBJ/expect_list
+for h in a b c d e; do
+ cp $OBJ/expect_list $OBJ/expect_$h
+ expect_succeed $h "unrestricted keylist"
+done
+restore_privatekeys
+
+verbose "authentication with agent (basic restrict)"
+reset_keys authinfo
+reset_expect_keys
+for h in a b c d e; do
+ $SSHADD -h host_$h -H $OBJ/known_hosts -q $OBJ/user_$h \
+ || fatal "add key $u basic restrict"
+done
+# One more, unrestricted
+$SSHADD -q $OBJ/user_x || fatal "add unrestricted key"
+hide_privatekeys
+# Authentication to host with expected key should work.
+for h in a b c d e ; do
+ expect_succeed $h "with agent"
+done
+# Authentication to host with incorrect key should fail.
+verbose "authentication with agent incorrect key (basic restrict)"
+for h in a b c d e ; do
+ wrongkey=user_e
+ test "$h" = "e" && wrongkey=user_a
+ expect_fail_key $h $wrongkey "wrong key with agent (basic restrict)"
+done
+
+verbose "keylist (basic restrict)"
+reset_keys keylist
+# List from forwarded agent should contain only user_x - the unrestricted key.
+cut -d " " -f-2 $OBJ/user_x.pub > $OBJ/expect_list
+for h in a b c d e; do
+ cp $OBJ/expect_list $OBJ/expect_$h
+ expect_succeed $h "keylist (basic restrict)"
+done
+restore_privatekeys
+
+verbose "username"
+reset_keys authinfo
+reset_expect_keys
+for h in a b c d e; do
+ $SSHADD -h "${USER}@host_$h" -H $OBJ/known_hosts -q $OBJ/user_$h \
+ || fatal "add key $u basic restrict"
+done
+hide_privatekeys
+for h in a b c d e ; do
+ expect_succeed $h "wildcard user"
+done
+restore_privatekeys
+
+verbose "username wildcard"
+reset_keys authinfo
+reset_expect_keys
+for h in a b c d e; do
+ $SSHADD -h "*@host_$h" -H $OBJ/known_hosts -q $OBJ/user_$h \
+ || fatal "add key $u basic restrict"
+done
+hide_privatekeys
+for h in a b c d e ; do
+ expect_succeed $h "wildcard user"
+done
+restore_privatekeys
+
+verbose "username incorrect"
+reset_keys authinfo
+reset_expect_keys
+for h in a b c d e; do
+ $SSHADD -h "--BADUSER@host_$h" -H $OBJ/known_hosts -q $OBJ/user_$h \
+ || fatal "add key $u basic restrict"
+done
+hide_privatekeys
+for h in a b c d e ; do
+ expect_fail $h "incorrect user"
+done
+restore_privatekeys
+
+
+verbose "agent restriction honours certificate principal"
+reset_keys authinfo
+reset_expect_keys
+clear_agent
+$SSHADD -h host_e -H $OBJ/known_hosts -q $OBJ/user_d || fatal "add key"
+hide_privatekeys
+expect_fail d "restricted agent w/ incorrect cert principal"
+restore_privatekeys
+
+# Prepares the script used to drive chained ssh connections for the
+# multihop tests. Believe me, this is easier than getting the escaping
+# right for 5 hops on the command-line...
+prepare_multihop_script() {
+ MULTIHOP_RUN=$OBJ/command
+ cat << _EOF > $MULTIHOP_RUN
+#!/bin/sh
+#set -x
+me="\$1" ; shift
+next="\$1"
+if test ! -z "\$me" ; then
+ rm -f $OBJ/done
+ echo "HOSTNAME host_\$me"
+ echo "AUTHINFO"
+ cat \$SSH_USER_AUTH
+fi
+echo AGENT
+$SSHADD -L | egrep "^ssh" | cut -d" " -f-2 | sort
+if test -z "\$next" ; then
+ touch $OBJ/done
+ echo "FINISH"
+ e=0
+else
+ echo NEXT
+ ${SSH} -F $OBJ/ssh_proxy_noid -oIdentityFile=$OBJ/user_a \
+ host_\$next $MULTIHOP_RUN "\$@"
+ e=\$?
+fi
+echo "COMPLETE \"\$me\""
+if test ! -z "\$me" ; then
+ if test ! -f $OBJ/done ; then
+ echo "DONE MARKER MISSING"
+ test \$e -eq 0 && e=63
+ fi
+fi
+exit \$e
+_EOF
+ chmod u+x $MULTIHOP_RUN
+}
+
+# Prepare expected output for multihop tests at expect_a
+prepare_multihop_expected() {
+ _keys="$1"
+ _hops="a b c d e"
+ test -z "$2" || _hops="$2"
+ _revhops=$(echo "$_hops" | rev)
+ _lasthop=$(echo "$_hops" | sed 's/.* //')
+
+ rm -f $OBJ/expect_keys
+ for h in a b c d e; do
+ cut -d" " -f-2 $OBJ/user_${h}.pub >> $OBJ/expect_keys
+ done
+ rm -f $OBJ/expect_a
+ echo "AGENT" >> $OBJ/expect_a
+ test "x$_keys" = "xnone" || sort $OBJ/expect_keys >> $OBJ/expect_a
+ echo "NEXT" >> $OBJ/expect_a
+ for h in $_hops ; do
+ echo "HOSTNAME host_$h" >> $OBJ/expect_a
+ echo "AUTHINFO" >> $OBJ/expect_a
+ (printf "publickey " ; cut -d" " -f-2 $OBJ/user_a.pub) >> $OBJ/expect_a
+ echo "AGENT" >> $OBJ/expect_a
+ if test "x$_keys" = "xall" ; then
+ sort $OBJ/expect_keys >> $OBJ/expect_a
+ fi
+ if test "x$h" != "x$_lasthop" ; then
+ if test "x$_keys" = "xfiltered" ; then
+ cut -d" " -f-2 $OBJ/user_a.pub >> $OBJ/expect_a
+ fi
+ echo "NEXT" >> $OBJ/expect_a
+ fi
+ done
+ echo "FINISH" >> $OBJ/expect_a
+ for h in $_revhops "" ; do
+ echo "COMPLETE \"$h\"" >> $OBJ/expect_a
+ done
+}
+
+prepare_multihop_script
+cp $OBJ/user_a.pub $OBJ/authorized_keys_$USER # only one key used.
+
+verbose "multihop without agent"
+clear_agent
+prepare_multihop_expected none
+$MULTIHOP_RUN "" a b c d e > $OBJ/ssh_output || fail "multihop no agent ssh failed"
+diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
+
+verbose "multihop agent unrestricted"
+clear_agent
+$SSHADD -q $OBJ/user_[abcde]
+prepare_multihop_expected all
+$MULTIHOP_RUN "" a b c d e > $OBJ/ssh_output || fail "multihop no agent ssh failed"
+diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
+
+verbose "multihop restricted"
+clear_agent
+prepare_multihop_expected filtered
+# Add user_a, with permission to connect through the whole chain.
+$SSHADD -h host_a -h "host_a>host_b" -h "host_b>host_c" \
+ -h "host_c>host_d" -h "host_d>host_e" \
+ -H $OBJ/known_hosts -q $OBJ/user_a \
+ || fatal "add key user_a multihop"
+# Add the other keys, bound to a unused host.
+$SSHADD -q -h host_x -H $OBJ/known_hosts $OBJ/user_[bcde] || fail "add keys"
+hide_privatekeys
+$MULTIHOP_RUN "" a b c d e > $OBJ/ssh_output || fail "multihop ssh failed"
+diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
+restore_privatekeys
+
+verbose "multihop username"
+$SSHADD -h host_a -h "host_a>${USER}@host_b" -h "host_b>${USER}@host_c" \
+ -h "host_c>${USER}@host_d" -h "host_d>${USER}@host_e" \
+ -H $OBJ/known_hosts -q $OBJ/user_a || fatal "add key user_a multihop"
+hide_privatekeys
+$MULTIHOP_RUN "" a b c d e > $OBJ/ssh_output || fail "multihop w/ user ssh failed"
+diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
+restore_privatekeys
+
+verbose "multihop wildcard username"
+$SSHADD -h host_a -h "host_a>*@host_b" -h "host_b>*@host_c" \
+ -h "host_c>*@host_d" -h "host_d>*@host_e" \
+ -H $OBJ/known_hosts -q $OBJ/user_a || fatal "add key user_a multihop"
+hide_privatekeys
+$MULTIHOP_RUN "" a b c d e > $OBJ/ssh_output || fail "multihop w/ user ssh failed"
+diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
+restore_privatekeys
+
+verbose "multihop wrong username"
+$SSHADD -h host_a -h "host_a>*@host_b" -h "host_b>*@host_c" \
+ -h "host_c>--BADUSER@host_d" -h "host_d>*@host_e" \
+ -H $OBJ/known_hosts -q $OBJ/user_a || fatal "add key user_a multihop"
+hide_privatekeys
+$MULTIHOP_RUN "" a b c d e > $OBJ/ssh_output && \
+ fail "multihop with wrong user succeeded unexpectedly"
+restore_privatekeys
+
+verbose "multihop cycle no agent"
+clear_agent
+prepare_multihop_expected none "a b a a c d e"
+$MULTIHOP_RUN "" a b a a c d e > $OBJ/ssh_output || \
+ fail "multihop cycle no-agent fail"
+diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
+
+verbose "multihop cycle agent unrestricted"
+clear_agent
+$SSHADD -q $OBJ/user_[abcde] || fail "add keys"
+prepare_multihop_expected all "a b a a c d e"
+$MULTIHOP_RUN "" a b a a c d e > $OBJ/ssh_output || \
+ fail "multihop cycle agent ssh failed"
+diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
+
+verbose "multihop cycle restricted deny"
+clear_agent
+$SSHADD -q -h host_x -H $OBJ/known_hosts $OBJ/user_[bcde] || fail "add keys"
+$SSHADD -h host_a -h "host_a>host_b" -h "host_b>host_c" \
+ -h "host_c>host_d" -h "host_d>host_e" \
+ -H $OBJ/known_hosts -q $OBJ/user_a \
+ || fatal "add key user_a multihop"
+prepare_multihop_expected filtered "a b a a c d e"
+hide_privatekeys
+$MULTIHOP_RUN "" a b a a c d e > $OBJ/ssh_output && \
+ fail "multihop cycle restricted deny succeded unexpectedly"
+restore_privatekeys
+
+verbose "multihop cycle restricted allow"
+clear_agent
+$SSHADD -q -h host_x -H $OBJ/known_hosts $OBJ/user_[bcde] || fail "add keys"
+$SSHADD -h host_a -h "host_a>host_b" -h "host_b>host_c" \
+ -h "host_c>host_d" -h "host_d>host_e" \
+ -h "host_b>host_a" -h "host_a>host_a" -h "host_a>host_c" \
+ -H $OBJ/known_hosts -q $OBJ/user_a \
+ || fatal "add key user_a multihop"
+prepare_multihop_expected filtered "a b a a c d e"
+hide_privatekeys
+$MULTIHOP_RUN "" a b a a c d e > $OBJ/ssh_output || \
+ fail "multihop cycle restricted allow failed"
+diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
+restore_privatekeys
+
diff --git a/regress/agent-subprocess.sh b/regress/agent-subprocess.sh
new file mode 100644
index 0000000..2f36d70
--- /dev/null
+++ b/regress/agent-subprocess.sh
@@ -0,0 +1,22 @@
+# $OpenBSD: agent-subprocess.sh,v 1.1 2020/06/19 05:07:09 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="agent subprocess"
+
+trace "ensure agent exits when run as subprocess"
+${SSHAGENT} sh -c "echo \$SSH_AGENT_PID >$OBJ/pidfile; sleep 1"
+
+pid=`cat $OBJ/pidfile`
+
+# Currently ssh-agent polls every 10s so we need to wait at least that long.
+n=12
+while kill -0 $pid >/dev/null 2>&1 && test "$n" -gt "0"; do
+ n=$(($n - 1))
+ sleep 1
+done
+
+if test "$n" -eq "0"; then
+ fail "agent still running"
+fi
+
+rm -f $OBJ/pidfile
diff --git a/regress/agent-timeout.sh b/regress/agent-timeout.sh
new file mode 100644
index 0000000..6dec092
--- /dev/null
+++ b/regress/agent-timeout.sh
@@ -0,0 +1,38 @@
+# $OpenBSD: agent-timeout.sh,v 1.6 2019/11/26 23:43:10 djm Exp $
+# Placed in the Public Domain.
+
+tid="agent timeout test"
+
+SSHAGENT_TIMEOUT=10
+
+trace "start agent"
+eval `${SSHAGENT} -s ${EXTRA_AGENT_ARGS}` > /dev/null
+r=$?
+if [ $r -ne 0 ]; then
+ fail "could not start ssh-agent: exit code $r"
+else
+ trace "add keys with timeout"
+ keys=0
+ for t in ${SSH_KEYTYPES}; do
+ ${SSHADD} -kt ${SSHAGENT_TIMEOUT} $OBJ/$t > /dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ fail "ssh-add did succeed exit code 0"
+ fi
+ keys=$((${keys} + 1))
+ done
+ n=`${SSHADD} -l 2> /dev/null | wc -l`
+ trace "agent has $n keys"
+ if [ $n -ne $keys ]; then
+ fail "ssh-add -l did not return $keys keys: $n"
+ fi
+ trace "sleeping 2*${SSHAGENT_TIMEOUT} seconds"
+ sleep ${SSHAGENT_TIMEOUT}
+ sleep ${SSHAGENT_TIMEOUT}
+ ${SSHADD} -l 2> /dev/null | grep 'The agent has no identities.' >/dev/null
+ if [ $? -ne 0 ]; then
+ fail "ssh-add -l still returns keys after timeout"
+ fi
+
+ trace "kill agent"
+ ${SSHAGENT} -k > /dev/null
+fi
diff --git a/regress/agent.sh b/regress/agent.sh
new file mode 100644
index 0000000..f187b67
--- /dev/null
+++ b/regress/agent.sh
@@ -0,0 +1,227 @@
+# $OpenBSD: agent.sh,v 1.20 2021/02/25 03:27:34 djm Exp $
+# Placed in the Public Domain.
+
+tid="simple agent test"
+
+SSH_AUTH_SOCK=/nonexistent ${SSHADD} -l > /dev/null 2>&1
+if [ $? -ne 2 ]; then
+ fail "ssh-add -l did not fail with exit code 2"
+fi
+
+trace "start agent, args ${EXTRA_AGENT_ARGS} -s"
+eval `${SSHAGENT} ${EXTRA_AGENT_ARGS} -s` > /dev/null
+r=$?
+if [ $r -ne 0 ]; then
+ fatal "could not start ssh-agent: exit code $r"
+fi
+
+eval `${SSHAGENT} ${EXTRA_AGENT_ARGS} -s | sed 's/SSH_/FW_SSH_/g'` > /dev/null
+r=$?
+if [ $r -ne 0 ]; then
+ fatal "could not start second ssh-agent: exit code $r"
+fi
+
+${SSHADD} -l > /dev/null 2>&1
+if [ $? -ne 1 ]; then
+ fail "ssh-add -l did not fail with exit code 1"
+fi
+
+rm -f $OBJ/user_ca_key $OBJ/user_ca_key.pub
+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_ca_key \
+ || fatal "ssh-keygen failed"
+
+trace "overwrite authorized keys"
+printf '' > $OBJ/authorized_keys_$USER
+
+for t in ${SSH_KEYTYPES}; do
+ # generate user key for agent
+ rm -f $OBJ/$t-agent $OBJ/$t-agent.pub*
+ ${SSHKEYGEN} -q -N '' -t $t -f $OBJ/$t-agent ||\
+ fatal "ssh-keygen for $t-agent failed"
+ # Make a certificate for each too.
+ ${SSHKEYGEN} -qs $OBJ/user_ca_key -I "$t cert" \
+ -n estragon $OBJ/$t-agent.pub || fatal "ca sign failed"
+
+ # add to authorized keys
+ cat $OBJ/$t-agent.pub >> $OBJ/authorized_keys_$USER
+ # add private key to agent
+ ${SSHADD} $OBJ/$t-agent > /dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ fail "ssh-add failed exit code $?"
+ fi
+ # add private key to second agent
+ SSH_AUTH_SOCK=$FW_SSH_AUTH_SOCK ${SSHADD} $OBJ/$t-agent > /dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ fail "ssh-add failed exit code $?"
+ fi
+ # Move private key to ensure that we aren't accidentally using it.
+ # Keep the corresponding public keys/certs around for later use.
+ mv -f $OBJ/$t-agent $OBJ/$t-agent-private
+ cp -f $OBJ/$t-agent.pub $OBJ/$t-agent-private.pub
+ cp -f $OBJ/$t-agent-cert.pub $OBJ/$t-agent-private-cert.pub
+done
+
+# Remove explicit identity directives from ssh_proxy
+mv $OBJ/ssh_proxy $OBJ/ssh_proxy_bak
+grep -vi identityfile $OBJ/ssh_proxy_bak > $OBJ/ssh_proxy
+
+${SSHADD} -l > /dev/null 2>&1
+r=$?
+if [ $r -ne 0 ]; then
+ fail "ssh-add -l failed: exit code $r"
+fi
+# the same for full pubkey output
+${SSHADD} -L > /dev/null 2>&1
+r=$?
+if [ $r -ne 0 ]; then
+ fail "ssh-add -L failed: exit code $r"
+fi
+
+trace "simple connect via agent"
+${SSH} -F $OBJ/ssh_proxy somehost exit 52
+r=$?
+if [ $r -ne 52 ]; then
+ fail "ssh connect with failed (exit code $r)"
+fi
+
+for t in ${SSH_KEYTYPES}; do
+ trace "connect via agent using $t key"
+ if [ "$t" = "ssh-dss" ]; then
+ echo "PubkeyAcceptedAlgorithms +ssh-dss" >> $OBJ/ssh_proxy
+ echo "PubkeyAcceptedAlgorithms +ssh-dss" >> $OBJ/sshd_proxy
+ fi
+ ${SSH} -F $OBJ/ssh_proxy -i $OBJ/$t-agent.pub -oIdentitiesOnly=yes \
+ somehost exit 52
+ r=$?
+ if [ $r -ne 52 ]; then
+ fail "ssh connect with failed (exit code $r)"
+ fi
+done
+
+trace "agent forwarding"
+${SSH} -A -F $OBJ/ssh_proxy somehost ${SSHADD} -l > /dev/null 2>&1
+r=$?
+if [ $r -ne 0 ]; then
+ fail "ssh-add -l via agent fwd failed (exit code $r)"
+fi
+${SSH} "-oForwardAgent=$SSH_AUTH_SOCK" -F $OBJ/ssh_proxy somehost ${SSHADD} -l > /dev/null 2>&1
+r=$?
+if [ $r -ne 0 ]; then
+ fail "ssh-add -l via agent path fwd failed (exit code $r)"
+fi
+${SSH} -A -F $OBJ/ssh_proxy somehost \
+ "${SSH} -F $OBJ/ssh_proxy somehost exit 52"
+r=$?
+if [ $r -ne 52 ]; then
+ fail "agent fwd failed (exit code $r)"
+fi
+
+trace "agent forwarding different agent"
+${SSH} "-oForwardAgent=$FW_SSH_AUTH_SOCK" -F $OBJ/ssh_proxy somehost ${SSHADD} -l > /dev/null 2>&1
+r=$?
+if [ $r -ne 0 ]; then
+ fail "ssh-add -l via agent path fwd of different agent failed (exit code $r)"
+fi
+${SSH} '-oForwardAgent=$FW_SSH_AUTH_SOCK' -F $OBJ/ssh_proxy somehost ${SSHADD} -l > /dev/null 2>&1
+r=$?
+if [ $r -ne 0 ]; then
+ fail "ssh-add -l via agent path env fwd of different agent failed (exit code $r)"
+fi
+
+# Remove keys from forwarded agent, ssh-add on remote machine should now fail.
+SSH_AUTH_SOCK=$FW_SSH_AUTH_SOCK ${SSHADD} -D > /dev/null 2>&1
+r=$?
+if [ $r -ne 0 ]; then
+ fail "ssh-add -D failed: exit code $r"
+fi
+${SSH} '-oForwardAgent=$FW_SSH_AUTH_SOCK' -F $OBJ/ssh_proxy somehost ${SSHADD} -l > /dev/null 2>&1
+r=$?
+if [ $r -ne 1 ]; then
+ fail "ssh-add -l with different agent did not fail with exit code 1 (exit code $r)"
+fi
+
+(printf 'cert-authority,principals="estragon" '; cat $OBJ/user_ca_key.pub) \
+ > $OBJ/authorized_keys_$USER
+for t in ${SSH_KEYTYPES}; do
+ if [ "$t" != "ssh-dss" ]; then
+ trace "connect via agent using $t key"
+ ${SSH} -F $OBJ/ssh_proxy -i $OBJ/$t-agent.pub \
+ -oCertificateFile=$OBJ/$t-agent-cert.pub \
+ -oIdentitiesOnly=yes somehost exit 52
+ r=$?
+ if [ $r -ne 52 ]; then
+ fail "ssh connect with failed (exit code $r)"
+ fi
+ fi
+done
+
+## Deletion tests.
+
+trace "delete all agent keys"
+${SSHADD} -D > /dev/null 2>&1
+r=$?
+if [ $r -ne 0 ]; then
+ fail "ssh-add -D failed: exit code $r"
+fi
+# make sure they're gone
+${SSHADD} -l > /dev/null 2>&1
+r=$?
+if [ $r -ne 1 ]; then
+ fail "ssh-add -l returned unexpected exit code: $r"
+fi
+trace "readd keys"
+# re-add keys/certs to agent
+for t in ${SSH_KEYTYPES}; do
+ ${SSHADD} $OBJ/$t-agent-private >/dev/null 2>&1 || \
+ fail "ssh-add failed exit code $?"
+done
+# make sure they are there
+${SSHADD} -l > /dev/null 2>&1
+r=$?
+if [ $r -ne 0 ]; then
+ fail "ssh-add -l failed: exit code $r"
+fi
+
+check_key_absent() {
+ ${SSHADD} -L | grep "^$1 " >/dev/null
+ if [ $? -eq 0 ]; then
+ fail "$1 key unexpectedly present"
+ fi
+}
+check_key_present() {
+ ${SSHADD} -L | grep "^$1 " >/dev/null
+ if [ $? -ne 0 ]; then
+ fail "$1 key missing from agent"
+ fi
+}
+
+# delete the ed25519 key
+trace "delete single key by file"
+${SSHADD} -qdk $OBJ/ssh-ed25519-agent || fail "ssh-add -d ed25519 failed"
+check_key_absent ssh-ed25519
+check_key_present ssh-ed25519-cert-v01@openssh.com
+# Put key/cert back.
+${SSHADD} $OBJ/ssh-ed25519-agent-private >/dev/null 2>&1 || \
+ fail "ssh-add failed exit code $?"
+check_key_present ssh-ed25519
+# Delete both key and certificate.
+trace "delete key/cert by file"
+${SSHADD} -qd $OBJ/ssh-ed25519-agent || fail "ssh-add -d ed25519 failed"
+check_key_absent ssh-ed25519
+check_key_absent ssh-ed25519-cert-v01@openssh.com
+# Put key/cert back.
+${SSHADD} $OBJ/ssh-ed25519-agent-private >/dev/null 2>&1 || \
+ fail "ssh-add failed exit code $?"
+check_key_present ssh-ed25519
+# Delete certificate via stdin
+${SSHADD} -qd - < $OBJ/ssh-ed25519-agent-cert.pub || fail "ssh-add -d - failed"
+check_key_present ssh-ed25519
+check_key_absent ssh-ed25519-cert-v01@openssh.com
+# Delete key via stdin
+${SSHADD} -qd - < $OBJ/ssh-ed25519-agent.pub || fail "ssh-add -d - failed"
+check_key_absent ssh-ed25519
+check_key_absent ssh-ed25519-cert-v01@openssh.com
+
+trace "kill agent"
+${SSHAGENT} -k > /dev/null
+SSH_AGENT_PID=$FW_SSH_AGENT_PID ${SSHAGENT} -k > /dev/null
diff --git a/regress/allow-deny-users.sh b/regress/allow-deny-users.sh
new file mode 100644
index 0000000..6c053ee
--- /dev/null
+++ b/regress/allow-deny-users.sh
@@ -0,0 +1,43 @@
+# Public Domain
+# Zev Weiss, 2016
+# $OpenBSD: allow-deny-users.sh,v 1.6 2021/06/07 00:00:50 djm Exp $
+
+tid="AllowUsers/DenyUsers"
+
+me="$LOGNAME"
+if [ "x$me" = "x" ]; then
+ me=`whoami`
+fi
+other="nobody"
+
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy.orig
+
+test_auth()
+{
+ deny="$1"
+ allow="$2"
+ should_succeed="$3"
+ failmsg="$4"
+
+ cp $OBJ/sshd_proxy.orig $OBJ/sshd_proxy
+ test -z "$deny" || echo DenyUsers="$deny" >> $OBJ/sshd_proxy
+ test -z "$allow" || echo AllowUsers="$allow" >> $OBJ/sshd_proxy
+
+ ${SSH} -F $OBJ/ssh_proxy "$me@somehost" true
+ status=$?
+
+ if (test $status -eq 0 && ! $should_succeed) \
+ || (test $status -ne 0 && $should_succeed); then
+ fail "$failmsg"
+ fi
+}
+
+# DenyUsers AllowUsers should_succeed failure_message
+test_auth "" "" true "user in neither DenyUsers nor AllowUsers denied"
+test_auth "$other $me" "" false "user in DenyUsers allowed"
+test_auth "$me $other" "" false "user in DenyUsers allowed"
+test_auth "" "$other" false "user not in AllowUsers allowed"
+test_auth "" "$other $me" true "user in AllowUsers denied"
+test_auth "" "$me $other" true "user in AllowUsers denied"
+test_auth "$me $other" "$me $other" false "user in both DenyUsers and AllowUsers allowed"
+test_auth "$other $me" "$other $me" false "user in both DenyUsers and AllowUsers allowed"
diff --git a/regress/authinfo.sh b/regress/authinfo.sh
new file mode 100644
index 0000000..693424a
--- /dev/null
+++ b/regress/authinfo.sh
@@ -0,0 +1,17 @@
+# $OpenBSD: authinfo.sh,v 1.3 2018/04/10 00:13:27 djm Exp $
+# Placed in the Public Domain.
+
+tid="authinfo"
+
+# Ensure the environment variable doesn't leak when ExposeAuthInfo=no.
+verbose "ExposeAuthInfo=no"
+env SSH_USER_AUTH=blah ${SSH} -F $OBJ/ssh_proxy x \
+ 'env | grep SSH_USER_AUTH >/dev/null' && fail "SSH_USER_AUTH present"
+
+verbose "ExposeAuthInfo=yes"
+echo ExposeAuthInfo=yes >> $OBJ/sshd_proxy
+${SSH} -F $OBJ/ssh_proxy x \
+ 'grep ^publickey "$SSH_USER_AUTH" /dev/null >/dev/null' ||
+ fail "ssh with ExposeAuthInfo failed"
+
+# XXX test multiple auth and key contents
diff --git a/regress/banner.sh b/regress/banner.sh
new file mode 100644
index 0000000..a84feb5
--- /dev/null
+++ b/regress/banner.sh
@@ -0,0 +1,46 @@
+# $OpenBSD: banner.sh,v 1.4 2021/08/08 06:38:33 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="banner"
+echo "Banner $OBJ/banner.in" >> $OBJ/sshd_proxy
+
+rm -f $OBJ/banner.out $OBJ/banner.in $OBJ/empty.in
+touch $OBJ/empty.in
+
+trace "test missing banner file"
+verbose "test $tid: missing banner file"
+( ${SSH} -F $OBJ/ssh_proxy otherhost true 2>$OBJ/banner.out && \
+ cmp $OBJ/empty.in $OBJ/banner.out ) || \
+ fail "missing banner file"
+
+for s in 0 10 100 1000 10000 100000 ; do
+ if [ "$s" = "0" ]; then
+ # create empty banner
+ touch $OBJ/banner.in
+ elif [ "$s" = "10" ]; then
+ # create 10-byte banner file
+ echo "abcdefghi" >$OBJ/banner.in
+ else
+ # increase size 10x
+ cp $OBJ/banner.in $OBJ/banner.out
+ for i in 0 1 2 3 4 5 6 7 8 ; do
+ cat $OBJ/banner.out >> $OBJ/banner.in
+ done
+ fi
+
+ trace "test banner size $s"
+ verbose "test $tid: size $s"
+ ( ${SSH} -F $OBJ/ssh_proxy otherhost true 2>$OBJ/banner.out && \
+ cmp $OBJ/banner.in $OBJ/banner.out ) || \
+ fail "banner size $s mismatch"
+done
+
+trace "test suppress banner (-q)"
+verbose "test $tid: suppress banner (-q)"
+# ssh-log-wrapper drops "-q" to preserve debug output so use ssh directly
+# for just this test.
+( ${REAL_SSH} -q -F $OBJ/ssh_proxy otherhost true 2>$OBJ/banner.out && \
+ cmp $OBJ/empty.in $OBJ/banner.out ) || \
+ fail "suppress banner (-q)"
+
+rm -f $OBJ/banner.out $OBJ/banner.in $OBJ/empty.in
diff --git a/regress/broken-pipe.sh b/regress/broken-pipe.sh
new file mode 100644
index 0000000..c69276e
--- /dev/null
+++ b/regress/broken-pipe.sh
@@ -0,0 +1,12 @@
+# $OpenBSD: broken-pipe.sh,v 1.6 2017/04/30 23:34:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="broken pipe test"
+
+for i in 1 2 3 4; do
+ ${SSH} -F $OBJ/ssh_config_config nexthost echo $i 2> /dev/null | true
+ r=$?
+ if [ $r -ne 0 ]; then
+ fail "broken pipe returns $r"
+ fi
+done
diff --git a/regress/brokenkeys.sh b/regress/brokenkeys.sh
new file mode 100644
index 0000000..9d5a54f
--- /dev/null
+++ b/regress/brokenkeys.sh
@@ -0,0 +1,23 @@
+# $OpenBSD: brokenkeys.sh,v 1.2 2017/04/30 23:34:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="broken keys"
+
+KEYS="$OBJ/authorized_keys_${USER}"
+
+start_sshd
+
+mv ${KEYS} ${KEYS}.bak
+
+# Truncated key
+echo "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEABTM= bad key" > $KEYS
+cat ${KEYS}.bak >> ${KEYS}
+cat $OBJ/$t.pub >> $OBJ/authorized_keys_$USER
+
+${SSH} -F $OBJ/ssh_config somehost true
+if [ $? -ne 0 ]; then
+ fail "ssh connect with failed"
+fi
+
+mv ${KEYS}.bak ${KEYS}
+
diff --git a/regress/cert-file.sh b/regress/cert-file.sh
new file mode 100644
index 0000000..94e672a
--- /dev/null
+++ b/regress/cert-file.sh
@@ -0,0 +1,166 @@
+# $OpenBSD: cert-file.sh,v 1.8 2019/11/26 23:43:10 djm Exp $
+# Placed in the Public Domain.
+
+tid="ssh with certificates"
+
+rm -f $OBJ/user_ca_key* $OBJ/user_key*
+rm -f $OBJ/cert_user_key*
+
+# Create a CA key
+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_ca_key1 ||\
+ fatal "ssh-keygen failed"
+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_ca_key2 ||\
+ fatal "ssh-keygen failed"
+
+# Make some keys and certificates.
+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_key1 || \
+ fatal "ssh-keygen failed"
+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_key2 || \
+ fatal "ssh-keygen failed"
+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_key3 || \
+ fatal "ssh-keygen failed"
+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_key4 || \
+ fatal "ssh-keygen failed"
+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_key5 || \
+ fatal "ssh-keygen failed"
+
+# Move the certificate to a different address to better control
+# when it is offered.
+${SSHKEYGEN} -q -s $OBJ/user_ca_key1 -I "regress user key for $USER" \
+ -z $$ -n ${USER} $OBJ/user_key1 ||
+ fatal "couldn't sign user_key1 with user_ca_key1"
+mv $OBJ/user_key1-cert.pub $OBJ/cert_user_key1_1.pub
+${SSHKEYGEN} -q -s $OBJ/user_ca_key2 -I "regress user key for $USER" \
+ -z $$ -n ${USER} $OBJ/user_key1 ||
+ fatal "couldn't sign user_key1 with user_ca_key2"
+mv $OBJ/user_key1-cert.pub $OBJ/cert_user_key1_2.pub
+${SSHKEYGEN} -q -s $OBJ/user_ca_key1 -I "regress user key for $USER" \
+ -z $$ -n ${USER} $OBJ/user_key3 ||
+ fatal "couldn't sign user_key3 with user_ca_key1"
+rm $OBJ/user_key3.pub # to test use of private key w/o public half.
+${SSHKEYGEN} -q -s $OBJ/user_ca_key1 -I "regress user key for $USER" \
+ -z $$ -n ${USER} $OBJ/user_key4 ||
+ fatal "couldn't sign user_key4 with user_ca_key1"
+rm $OBJ/user_key4 $OBJ/user_key4.pub # to test no matching pub/private key case.
+
+trace 'try with identity files'
+opts="-F $OBJ/ssh_proxy -oIdentitiesOnly=yes"
+opts2="$opts -i $OBJ/user_key1 -i $OBJ/user_key2"
+echo "cert-authority $(cat $OBJ/user_ca_key1.pub)" > $OBJ/authorized_keys_$USER
+
+# Make a clean config that doesn't have any pre-added identities.
+cat $OBJ/ssh_proxy | grep -v IdentityFile > $OBJ/no_identity_config
+
+# XXX: verify that certificate used was what we expect. Needs exposure of
+# keys via environment variable or similar.
+
+ # Key with no .pub should work - finding the equivalent *-cert.pub.
+verbose "identity cert with no plain public file"
+${SSH} -F $OBJ/no_identity_config -oIdentitiesOnly=yes \
+ -i $OBJ/user_key3 somehost exit 52
+[ $? -ne 52 ] && fail "ssh failed"
+
+# CertificateFile matching private key with no .pub file should work.
+verbose "CertificateFile with no plain public file"
+${SSH} -F $OBJ/no_identity_config -oIdentitiesOnly=yes \
+ -oCertificateFile=$OBJ/user_key3-cert.pub \
+ -i $OBJ/user_key3 somehost exit 52
+[ $? -ne 52 ] && fail "ssh failed"
+
+# Just keys should fail
+verbose "plain keys"
+${SSH} $opts2 somehost exit 52
+r=$?
+if [ $r -eq 52 ]; then
+ fail "ssh succeeded with no certs"
+fi
+
+# Keys with untrusted cert should fail.
+verbose "untrusted cert"
+opts3="$opts2 -oCertificateFile=$OBJ/cert_user_key1_2.pub"
+${SSH} $opts3 somehost exit 52
+r=$?
+if [ $r -eq 52 ]; then
+ fail "ssh succeeded with bad cert"
+fi
+
+# Good cert with bad key should fail.
+verbose "good cert, bad key"
+opts3="$opts -i $OBJ/user_key2"
+opts3="$opts3 -oCertificateFile=$OBJ/cert_user_key1_1.pub"
+${SSH} $opts3 somehost exit 52
+r=$?
+if [ $r -eq 52 ]; then
+ fail "ssh succeeded with no matching key"
+fi
+
+# Keys with one trusted cert, should succeed.
+verbose "single trusted"
+opts3="$opts2 -oCertificateFile=$OBJ/cert_user_key1_1.pub"
+${SSH} $opts3 somehost exit 52
+r=$?
+if [ $r -ne 52 ]; then
+ fail "ssh failed with trusted cert and key"
+fi
+
+# Multiple certs and keys, with one trusted cert, should succeed.
+verbose "multiple trusted"
+opts3="$opts2 -oCertificateFile=$OBJ/cert_user_key1_2.pub"
+opts3="$opts3 -oCertificateFile=$OBJ/cert_user_key1_1.pub"
+${SSH} $opts3 somehost exit 52
+r=$?
+if [ $r -ne 52 ]; then
+ fail "ssh failed with multiple certs"
+fi
+
+#next, using an agent in combination with the keys
+SSH_AUTH_SOCK=/nonexistent ${SSHADD} -l > /dev/null 2>&1
+if [ $? -ne 2 ]; then
+ fatal "ssh-add -l did not fail with exit code 2"
+fi
+
+trace "start agent"
+eval `${SSHAGENT} ${EXTRA_AGENT_ARGS} -s` > /dev/null
+r=$?
+if [ $r -ne 0 ]; then
+ fatal "could not start ssh-agent: exit code $r"
+fi
+
+# add private keys to agent
+${SSHADD} -k $OBJ/user_key2 > /dev/null 2>&1
+if [ $? -ne 0 ]; then
+ fatal "ssh-add did not succeed with exit code 0"
+fi
+${SSHADD} -k $OBJ/user_key1 > /dev/null 2>&1
+if [ $? -ne 0 ]; then
+ fatal "ssh-add did not succeed with exit code 0"
+fi
+
+# try ssh with the agent and certificates
+opts="-F $OBJ/ssh_proxy"
+# with no certificates, should fail
+${SSH} $opts somehost exit 52
+if [ $? -eq 52 ]; then
+ fail "ssh connect with agent in succeeded with no cert"
+fi
+
+#with an untrusted certificate, should fail
+opts="$opts -oCertificateFile=$OBJ/cert_user_key1_2.pub"
+${SSH} $opts somehost exit 52
+if [ $? -eq 52 ]; then
+ fail "ssh connect with agent in succeeded with bad cert"
+fi
+
+#with an additional trusted certificate, should succeed
+opts="$opts -oCertificateFile=$OBJ/cert_user_key1_1.pub"
+${SSH} $opts somehost exit 52
+if [ $? -ne 52 ]; then
+ fail "ssh connect with agent in failed with good cert"
+fi
+
+trace "kill agent"
+${SSHAGENT} -k > /dev/null
+
+#cleanup
+rm -f $OBJ/user_ca_key* $OBJ/user_key*
+rm -f $OBJ/cert_user_key*
diff --git a/regress/cert-hostkey.sh b/regress/cert-hostkey.sh
new file mode 100644
index 0000000..a3414e1
--- /dev/null
+++ b/regress/cert-hostkey.sh
@@ -0,0 +1,325 @@
+# $OpenBSD: cert-hostkey.sh,v 1.27 2021/09/30 05:26:26 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="certified host keys"
+
+rm -f $OBJ/known_hosts-cert* $OBJ/host_ca_key* $OBJ/host_revoked_*
+rm -f $OBJ/cert_host_key* $OBJ/host_krl_*
+
+# Allow all hostkey/pubkey types, prefer certs for the client
+rsa=0
+types=""
+for i in `$SSH -Q key | maybe_filter_sk`; do
+ if [ -z "$types" ]; then
+ types="$i"
+ continue
+ fi
+ case "$i" in
+ # Special treatment for RSA keys.
+ *rsa*cert*)
+ types="rsa-sha2-256-cert-v01@openssh.com,$i,$types"
+ types="rsa-sha2-512-cert-v01@openssh.com,$types";;
+ *rsa*)
+ rsa=1
+ types="$types,rsa-sha2-512,rsa-sha2-256,$i";;
+ # Prefer certificate to plain keys.
+ *cert*) types="$i,$types";;
+ *) types="$types,$i";;
+ esac
+done
+(
+ echo "HostKeyAlgorithms ${types}"
+ echo "PubkeyAcceptedAlgorithms *"
+) >> $OBJ/ssh_proxy
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+(
+ echo "HostKeyAlgorithms *"
+ echo "PubkeyAcceptedAlgorithms *"
+) >> $OBJ/sshd_proxy_bak
+
+HOSTS='localhost-with-alias,127.0.0.1,::1'
+
+kh_ca() {
+ for k in "$@" ; do
+ printf "@cert-authority $HOSTS "
+ cat $OBJ/$k || fatal "couldn't cat $k"
+ done
+}
+kh_revoke() {
+ for k in "$@" ; do
+ printf "@revoked * "
+ cat $OBJ/$k || fatal "couldn't cat $k"
+ done
+}
+
+# Create a CA key and add it to known hosts. Ed25519 chosen for speed.
+# RSA for testing RSA/SHA2 signatures if supported.
+ktype2=ed25519
+[ "x$rsa" = "x1" ] && ktype2=rsa
+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/host_ca_key ||\
+ fail "ssh-keygen of host_ca_key failed"
+${SSHKEYGEN} -q -N '' -t $ktype2 -f $OBJ/host_ca_key2 ||\
+ fail "ssh-keygen of host_ca_key failed"
+
+kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig
+cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
+
+# Plain text revocation files
+touch $OBJ/host_revoked_empty
+touch $OBJ/host_revoked_plain
+touch $OBJ/host_revoked_cert
+cat $OBJ/host_ca_key.pub $OBJ/host_ca_key2.pub > $OBJ/host_revoked_ca
+
+PLAIN_TYPES=`echo "$SSH_KEYTYPES" | sed 's/^ssh-dss/ssh-dsa/g;s/^ssh-//'`
+
+if echo "$PLAIN_TYPES" | grep '^rsa$' >/dev/null 2>&1 ; then
+ PLAIN_TYPES="$PLAIN_TYPES rsa-sha2-256 rsa-sha2-512"
+fi
+
+# Prepare certificate, plain key and CA KRLs
+${SSHKEYGEN} -kf $OBJ/host_krl_empty || fatal "KRL init failed"
+${SSHKEYGEN} -kf $OBJ/host_krl_plain || fatal "KRL init failed"
+${SSHKEYGEN} -kf $OBJ/host_krl_cert || fatal "KRL init failed"
+${SSHKEYGEN} -kf $OBJ/host_krl_ca $OBJ/host_ca_key.pub $OBJ/host_ca_key2.pub \
+ || fatal "KRL init failed"
+
+# Generate and sign host keys
+serial=1
+for ktype in $PLAIN_TYPES ; do
+ verbose "$tid: sign host ${ktype} cert"
+ # Generate and sign a host key
+ ${SSHKEYGEN} -q -N '' -t ${ktype} \
+ -f $OBJ/cert_host_key_${ktype} || \
+ fatal "ssh-keygen of cert_host_key_${ktype} failed"
+ ${SSHKEYGEN} -ukf $OBJ/host_krl_plain \
+ $OBJ/cert_host_key_${ktype}.pub || fatal "KRL update failed"
+ cat $OBJ/cert_host_key_${ktype}.pub >> $OBJ/host_revoked_plain
+ case $ktype in
+ rsa-sha2-*) tflag="-t $ktype"; ca="$OBJ/host_ca_key2" ;;
+ *) tflag=""; ca="$OBJ/host_ca_key" ;;
+ esac
+ ${SSHKEYGEN} -h -q -s $ca -z $serial $tflag \
+ -I "regress host key for $USER" \
+ -n $HOSTS $OBJ/cert_host_key_${ktype} ||
+ fatal "couldn't sign cert_host_key_${ktype}"
+ ${SSHKEYGEN} -ukf $OBJ/host_krl_cert \
+ $OBJ/cert_host_key_${ktype}-cert.pub || \
+ fatal "KRL update failed"
+ cat $OBJ/cert_host_key_${ktype}-cert.pub >> $OBJ/host_revoked_cert
+ serial=`expr $serial + 1`
+done
+
+attempt_connect() {
+ _ident="$1"
+ _expect_success="$2"
+ shift; shift
+ verbose "$tid: $_ident expect success $_expect_success"
+ cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
+ ${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \
+ -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
+ "$@" -F $OBJ/ssh_proxy somehost true
+ _r=$?
+ if [ "x$_expect_success" = "xyes" ] ; then
+ if [ $_r -ne 0 ]; then
+ fail "ssh cert connect $_ident failed"
+ fi
+ else
+ if [ $_r -eq 0 ]; then
+ fail "ssh cert connect $_ident succeeded unexpectedly"
+ fi
+ fi
+}
+
+# Basic connect and revocation tests.
+for ktype in $PLAIN_TYPES ; do
+ verbose "$tid: host ${ktype} cert connect"
+ (
+ cat $OBJ/sshd_proxy_bak
+ echo HostKey $OBJ/cert_host_key_${ktype}
+ echo HostCertificate $OBJ/cert_host_key_${ktype}-cert.pub
+ ) > $OBJ/sshd_proxy
+
+ # test name expect success
+ attempt_connect "$ktype basic connect" "yes"
+ attempt_connect "$ktype empty KRL" "yes" \
+ -oRevokedHostKeys=$OBJ/host_krl_empty
+ attempt_connect "$ktype KRL w/ plain key revoked" "no" \
+ -oRevokedHostKeys=$OBJ/host_krl_plain
+ attempt_connect "$ktype KRL w/ cert revoked" "no" \
+ -oRevokedHostKeys=$OBJ/host_krl_cert
+ attempt_connect "$ktype KRL w/ CA revoked" "no" \
+ -oRevokedHostKeys=$OBJ/host_krl_ca
+ attempt_connect "$ktype empty plaintext revocation" "yes" \
+ -oRevokedHostKeys=$OBJ/host_revoked_empty
+ attempt_connect "$ktype plain key plaintext revocation" "no" \
+ -oRevokedHostKeys=$OBJ/host_revoked_plain
+ attempt_connect "$ktype cert plaintext revocation" "no" \
+ -oRevokedHostKeys=$OBJ/host_revoked_cert
+ attempt_connect "$ktype CA plaintext revocation" "no" \
+ -oRevokedHostKeys=$OBJ/host_revoked_ca
+done
+
+# Revoked certificates with key present
+kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig
+for ktype in $PLAIN_TYPES ; do
+ test -f "$OBJ/cert_host_key_${ktype}.pub" || fatal "no pubkey"
+ kh_revoke cert_host_key_${ktype}.pub >> $OBJ/known_hosts-cert.orig
+done
+cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
+for ktype in $PLAIN_TYPES ; do
+ verbose "$tid: host ${ktype} revoked cert"
+ (
+ cat $OBJ/sshd_proxy_bak
+ echo HostKey $OBJ/cert_host_key_${ktype}
+ echo HostCertificate $OBJ/cert_host_key_${ktype}-cert.pub
+ ) > $OBJ/sshd_proxy
+
+ cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
+ ${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \
+ -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpectedly"
+ fi
+done
+
+# Revoked CA
+kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig
+kh_revoke host_ca_key.pub host_ca_key2.pub >> $OBJ/known_hosts-cert.orig
+cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
+for ktype in $PLAIN_TYPES ; do
+ verbose "$tid: host ${ktype} revoked cert"
+ (
+ cat $OBJ/sshd_proxy_bak
+ echo HostKey $OBJ/cert_host_key_${ktype}
+ echo HostCertificate $OBJ/cert_host_key_${ktype}-cert.pub
+ ) > $OBJ/sshd_proxy
+ cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
+ ${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \
+ -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpectedly"
+ fi
+done
+
+# Create a CA key and add it to known hosts
+kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig
+cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
+
+test_one() {
+ ident=$1
+ result=$2
+ sign_opts=$3
+
+ for kt in $PLAIN_TYPES; do
+ case $ktype in
+ rsa-sha2-*) tflag="-t $ktype"; ca="$OBJ/host_ca_key2" ;;
+ *) tflag=""; ca="$OBJ/host_ca_key" ;;
+ esac
+ ${SSHKEYGEN} -q -s $ca $tflag -I "regress host key for $USER" \
+ $sign_opts $OBJ/cert_host_key_${kt} ||
+ fatal "couldn't sign cert_host_key_${kt}"
+ (
+ cat $OBJ/sshd_proxy_bak
+ echo HostKey $OBJ/cert_host_key_${kt}
+ echo HostCertificate $OBJ/cert_host_key_${kt}-cert.pub
+ ) > $OBJ/sshd_proxy
+
+ cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
+ ${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \
+ -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ rc=$?
+ if [ "x$result" = "xsuccess" ] ; then
+ if [ $rc -ne 0 ]; then
+ fail "ssh cert connect $ident failed unexpectedly"
+ fi
+ else
+ if [ $rc -eq 0 ]; then
+ fail "ssh cert connect $ident succeeded unexpectedly"
+ fi
+ fi
+ done
+}
+
+test_one "user-certificate" failure "-n $HOSTS"
+test_one "empty principals" success "-h"
+test_one "wrong principals" failure "-h -n foo"
+test_one "cert not yet valid" failure "-h -V20300101:20320101"
+test_one "cert expired" failure "-h -V19800101:19900101"
+test_one "cert valid interval" success "-h -V-1w:+2w"
+test_one "cert has constraints" failure "-h -Oforce-command=false"
+
+# Check downgrade of cert to raw key when no CA found
+for ktype in $PLAIN_TYPES ; do
+ rm -f $OBJ/known_hosts-cert $OBJ/cert_host_key*
+ verbose "$tid: host ${ktype} ${v} cert downgrade to raw key"
+ # Generate and sign a host key
+ ${SSHKEYGEN} -q -N '' -t ${ktype} -f $OBJ/cert_host_key_${ktype} || \
+ fail "ssh-keygen of cert_host_key_${ktype} failed"
+ case $ktype in
+ rsa-sha2-*) tflag="-t $ktype"; ca="$OBJ/host_ca_key2" ;;
+ *) tflag=""; ca="$OBJ/host_ca_key" ;;
+ esac
+ ${SSHKEYGEN} -h -q $tflag -s $ca $tflag \
+ -I "regress host key for $USER" \
+ -n $HOSTS $OBJ/cert_host_key_${ktype} ||
+ fatal "couldn't sign cert_host_key_${ktype}"
+ (
+ printf "$HOSTS "
+ cat $OBJ/cert_host_key_${ktype}.pub
+ ) > $OBJ/known_hosts-cert
+ (
+ cat $OBJ/sshd_proxy_bak
+ echo HostKey $OBJ/cert_host_key_${ktype}
+ echo HostCertificate $OBJ/cert_host_key_${ktype}-cert.pub
+ ) > $OBJ/sshd_proxy
+
+ ${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \
+ -oGlobalKnownHostsFile=none -F $OBJ/ssh_proxy somehost true
+ if [ $? -ne 0 ]; then
+ fail "ssh cert connect failed"
+ fi
+ # Also check that it works when the known_hosts file is not in the
+ # first array position.
+ ${SSH} -oUserKnownHostsFile="/dev/null $OBJ/known_hosts-cert" \
+ -oGlobalKnownHostsFile=none -F $OBJ/ssh_proxy somehost true
+ if [ $? -ne 0 ]; then
+ fail "ssh cert connect failed known_hosts 2nd"
+ fi
+done
+
+# Wrong certificate
+kh_ca host_ca_key.pub host_ca_key2.pub > $OBJ/known_hosts-cert.orig
+cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
+for kt in $PLAIN_TYPES ; do
+ verbose "$tid: host ${kt} connect wrong cert"
+ rm -f $OBJ/cert_host_key*
+ # Self-sign key
+ ${SSHKEYGEN} -q -N '' -t ${kt} -f $OBJ/cert_host_key_${kt} || \
+ fail "ssh-keygen of cert_host_key_${kt} failed"
+ case $kt in
+ rsa-sha2-*) tflag="-t $kt" ;;
+ *) tflag="" ;;
+ esac
+ ${SSHKEYGEN} $tflag -h -q -s $OBJ/cert_host_key_${kt} \
+ -I "regress host key for $USER" \
+ -n $HOSTS $OBJ/cert_host_key_${kt} ||
+ fatal "couldn't sign cert_host_key_${kt}"
+ (
+ cat $OBJ/sshd_proxy_bak
+ echo HostKey $OBJ/cert_host_key_${kt}
+ echo HostCertificate $OBJ/cert_host_key_${kt}-cert.pub
+ ) > $OBJ/sshd_proxy
+
+ cp $OBJ/known_hosts-cert.orig $OBJ/known_hosts-cert
+ ${SSH} -oUserKnownHostsFile=$OBJ/known_hosts-cert \
+ -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
+ -F $OBJ/ssh_proxy -q somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect $ident succeeded unexpectedly"
+ fi
+done
+
+rm -f $OBJ/known_hosts-cert* $OBJ/host_ca_key* $OBJ/cert_host_key*
diff --git a/regress/cert-userkey.sh b/regress/cert-userkey.sh
new file mode 100644
index 0000000..4ea29b7
--- /dev/null
+++ b/regress/cert-userkey.sh
@@ -0,0 +1,396 @@
+# $OpenBSD: cert-userkey.sh,v 1.28 2021/09/30 05:26:26 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="certified user keys"
+
+rm -f $OBJ/authorized_keys_$USER $OBJ/user_ca_key* $OBJ/cert_user_key*
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+cp $OBJ/ssh_proxy $OBJ/ssh_proxy_bak
+
+PLAIN_TYPES=`$SSH -Q key-plain | maybe_filter_sk | sed 's/^ssh-dss/ssh-dsa/;s/^ssh-//'`
+EXTRA_TYPES=""
+rsa=""
+
+if echo "$PLAIN_TYPES" | grep '^rsa$' >/dev/null 2>&1 ; then
+ rsa=rsa
+ PLAIN_TYPES="$PLAIN_TYPES rsa-sha2-256 rsa-sha2-512"
+fi
+
+kname() {
+ case $1 in
+ rsa-sha2-*) n="$1" ;;
+ sk-ecdsa-*) n="sk-ecdsa" ;;
+ sk-ssh-ed25519*) n="sk-ssh-ed25519" ;;
+ # subshell because some seds will add a newline
+ *) n=$(echo $1 | sed 's/^dsa/ssh-dss/;s/^rsa/ssh-rsa/;s/^ed/ssh-ed/') ;;
+ esac
+ if [ -z "$rsa" ]; then
+ echo "$n*,ssh-ed25519*"
+ else
+ echo "$n*,ssh-rsa*,ssh-ed25519*"
+ fi
+}
+
+# Create a CA key
+if [ ! -z "$rsa" ]; then
+ catype=rsa
+else
+ catype=ed25519
+fi
+${SSHKEYGEN} -q -N '' -t $catype -f $OBJ/user_ca_key ||\
+ fail "ssh-keygen of user_ca_key failed"
+
+# Generate and sign user keys
+for ktype in $PLAIN_TYPES $EXTRA_TYPES ; do
+ verbose "$tid: sign user ${ktype} cert"
+ ${SSHKEYGEN} -q -N '' -t ${ktype} \
+ -f $OBJ/cert_user_key_${ktype} || \
+ fatal "ssh-keygen of cert_user_key_${ktype} failed"
+ # Generate RSA/SHA2 certs for rsa-sha2* keys.
+ case $ktype in
+ rsa-sha2-*) tflag="-t $ktype" ;;
+ *) tflag="" ;;
+ esac
+ ${SSHKEYGEN} -q -s $OBJ/user_ca_key -z $$ \
+ -I "regress user key for $USER" \
+ -n ${USER},mekmitasdigoat $tflag $OBJ/cert_user_key_${ktype} || \
+ fatal "couldn't sign cert_user_key_${ktype}"
+done
+
+# Test explicitly-specified principals
+for ktype in $EXTRA_TYPES $PLAIN_TYPES ; do
+ t=$(kname $ktype)
+ _prefix="${ktype}"
+
+ # Setup for AuthorizedPrincipalsFile
+ rm -f $OBJ/authorized_keys_$USER
+ (
+ cat $OBJ/sshd_proxy_bak
+ echo "AuthorizedPrincipalsFile " \
+ "$OBJ/authorized_principals_%u"
+ echo "TrustedUserCAKeys $OBJ/user_ca_key.pub"
+ echo "PubkeyAcceptedAlgorithms ${t}"
+ ) > $OBJ/sshd_proxy
+ (
+ cat $OBJ/ssh_proxy_bak
+ echo "PubkeyAcceptedAlgorithms ${t}"
+ ) > $OBJ/ssh_proxy
+
+ # Missing authorized_principals
+ verbose "$tid: ${_prefix} missing authorized_principals"
+ rm -f $OBJ/authorized_principals_$USER
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpectedly"
+ fi
+
+ # Empty authorized_principals
+ verbose "$tid: ${_prefix} empty authorized_principals"
+ echo > $OBJ/authorized_principals_$USER
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpectedly"
+ fi
+
+ # Wrong authorized_principals
+ verbose "$tid: ${_prefix} wrong authorized_principals"
+ echo gregorsamsa > $OBJ/authorized_principals_$USER
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpectedly"
+ fi
+
+ # Correct authorized_principals
+ verbose "$tid: ${_prefix} correct authorized_principals"
+ echo mekmitasdigoat > $OBJ/authorized_principals_$USER
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ fail "ssh cert connect failed"
+ fi
+
+ # authorized_principals with bad key option
+ verbose "$tid: ${_prefix} authorized_principals bad key opt"
+ echo 'blah mekmitasdigoat' > $OBJ/authorized_principals_$USER
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpectedly"
+ fi
+
+ # authorized_principals with command=false
+ verbose "$tid: ${_prefix} authorized_principals command=false"
+ echo 'command="false" mekmitasdigoat' > \
+ $OBJ/authorized_principals_$USER
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpectedly"
+ fi
+
+
+ # authorized_principals with command=true
+ verbose "$tid: ${_prefix} authorized_principals command=true"
+ echo 'command="true" mekmitasdigoat' > \
+ $OBJ/authorized_principals_$USER
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost false >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ fail "ssh cert connect failed"
+ fi
+
+ # Setup for principals= key option
+ rm -f $OBJ/authorized_principals_$USER
+ (
+ cat $OBJ/sshd_proxy_bak
+ echo "PubkeyAcceptedAlgorithms ${t}"
+ ) > $OBJ/sshd_proxy
+ (
+ cat $OBJ/ssh_proxy_bak
+ echo "PubkeyAcceptedAlgorithms ${t}"
+ ) > $OBJ/ssh_proxy
+
+ # Wrong principals list
+ verbose "$tid: ${_prefix} wrong principals key option"
+ (
+ printf 'cert-authority,principals="gregorsamsa" '
+ cat $OBJ/user_ca_key.pub
+ ) > $OBJ/authorized_keys_$USER
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpectedly"
+ fi
+
+ # Correct principals list
+ verbose "$tid: ${_prefix} correct principals key option"
+ (
+ printf 'cert-authority,principals="mekmitasdigoat" '
+ cat $OBJ/user_ca_key.pub
+ ) > $OBJ/authorized_keys_$USER
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ fail "ssh cert connect failed"
+ fi
+done
+
+basic_tests() {
+ auth=$1
+ if test "x$auth" = "xauthorized_keys" ; then
+ # Add CA to authorized_keys
+ (
+ printf 'cert-authority '
+ cat $OBJ/user_ca_key.pub
+ ) > $OBJ/authorized_keys_$USER
+ else
+ echo > $OBJ/authorized_keys_$USER
+ extra_sshd="TrustedUserCAKeys $OBJ/user_ca_key.pub"
+ fi
+
+ for ktype in $PLAIN_TYPES ; do
+ t=$(kname $ktype)
+ _prefix="${ktype} $auth"
+ # Simple connect
+ verbose "$tid: ${_prefix} connect"
+ (
+ cat $OBJ/sshd_proxy_bak
+ echo "PubkeyAcceptedAlgorithms ${t}"
+ echo "$extra_sshd"
+ ) > $OBJ/sshd_proxy
+ (
+ cat $OBJ/ssh_proxy_bak
+ echo "PubkeyAcceptedAlgorithms ${t}"
+ ) > $OBJ/ssh_proxy
+
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost true
+ if [ $? -ne 0 ]; then
+ fail "ssh cert connect failed"
+ fi
+
+ # Revoked keys
+ verbose "$tid: ${_prefix} revoked key"
+ (
+ cat $OBJ/sshd_proxy_bak
+ echo "RevokedKeys $OBJ/cert_user_key_revoked"
+ echo "PubkeyAcceptedAlgorithms ${t}"
+ echo "$extra_sshd"
+ ) > $OBJ/sshd_proxy
+ cp $OBJ/cert_user_key_${ktype}.pub \
+ $OBJ/cert_user_key_revoked
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpecedly"
+ fi
+ verbose "$tid: ${_prefix} revoked via KRL"
+ rm $OBJ/cert_user_key_revoked
+ ${SSHKEYGEN} -kqf $OBJ/cert_user_key_revoked \
+ $OBJ/cert_user_key_${ktype}.pub
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpecedly"
+ fi
+ verbose "$tid: ${_prefix} empty KRL"
+ ${SSHKEYGEN} -kqf $OBJ/cert_user_key_revoked
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ fail "ssh cert connect failed"
+ fi
+ done
+
+ # Revoked CA
+ verbose "$tid: ${ktype} $auth revoked CA key"
+ (
+ cat $OBJ/sshd_proxy_bak
+ echo "RevokedKeys $OBJ/user_ca_key.pub"
+ echo "PubkeyAcceptedAlgorithms ${t}"
+ echo "$extra_sshd"
+ ) > $OBJ/sshd_proxy
+ ${SSH} -i $OBJ/cert_user_key_${ktype} -F $OBJ/ssh_proxy \
+ somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpecedly"
+ fi
+
+ verbose "$tid: $auth CA does not authenticate"
+ (
+ cat $OBJ/sshd_proxy_bak
+ echo "PubkeyAcceptedAlgorithms ${t}"
+ echo "$extra_sshd"
+ ) > $OBJ/sshd_proxy
+ verbose "$tid: ensure CA key does not authenticate user"
+ ${SSH} -i $OBJ/user_ca_key \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect with CA key succeeded unexpectedly"
+ fi
+}
+
+basic_tests authorized_keys
+basic_tests TrustedUserCAKeys
+
+test_one() {
+ ident=$1
+ result=$2
+ sign_opts=$3
+ auth_choice=$4
+ auth_opt=$5
+
+ if test "x$auth_choice" = "x" ; then
+ auth_choice="authorized_keys TrustedUserCAKeys"
+ fi
+
+ for auth in $auth_choice ; do
+ for ktype in $rsa ed25519 ; do
+ cat $OBJ/sshd_proxy_bak > $OBJ/sshd_proxy
+ if test "x$auth" = "xauthorized_keys" ; then
+ # Add CA to authorized_keys
+ (
+ printf "cert-authority${auth_opt} "
+ cat $OBJ/user_ca_key.pub
+ ) > $OBJ/authorized_keys_$USER
+ else
+ echo > $OBJ/authorized_keys_$USER
+ echo "TrustedUserCAKeys $OBJ/user_ca_key.pub" \
+ >> $OBJ/sshd_proxy
+ echo "PubkeyAcceptedAlgorithms ${t}*" \
+ >> $OBJ/sshd_proxy
+ if test "x$auth_opt" != "x" ; then
+ echo $auth_opt >> $OBJ/sshd_proxy
+ fi
+ fi
+
+ verbose "$tid: $ident auth $auth expect $result $ktype"
+ ${SSHKEYGEN} -q -s $OBJ/user_ca_key \
+ -I "regress user key for $USER" \
+ $sign_opts $OBJ/cert_user_key_${ktype} ||
+ fail "couldn't sign cert_user_key_${ktype}"
+
+ ${SSH} -i $OBJ/cert_user_key_${ktype} \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ rc=$?
+ if [ "x$result" = "xsuccess" ] ; then
+ if [ $rc -ne 0 ]; then
+ fail "$ident failed unexpectedly"
+ fi
+ else
+ if [ $rc -eq 0 ]; then
+ fail "$ident succeeded unexpectedly"
+ fi
+ fi
+ done
+ done
+}
+
+test_one "correct principal" success "-n ${USER}"
+test_one "host-certificate" failure "-n ${USER} -h"
+test_one "wrong principals" failure "-n foo"
+test_one "cert not yet valid" failure "-n ${USER} -V20300101:20320101"
+test_one "cert expired" failure "-n ${USER} -V19800101:19900101"
+test_one "cert valid interval" success "-n ${USER} -V-1w:+2w"
+test_one "wrong source-address" failure "-n ${USER} -Osource-address=10.0.0.0/8"
+test_one "force-command" failure "-n ${USER} -Oforce-command=false"
+
+# Behaviour is different here: TrustedUserCAKeys doesn't allow empty principals
+test_one "empty principals" success "" authorized_keys
+test_one "empty principals" failure "" TrustedUserCAKeys
+
+# Check explicitly-specified principals: an empty principals list in the cert
+# should always be refused.
+
+# AuthorizedPrincipalsFile
+rm -f $OBJ/authorized_keys_$USER
+echo mekmitasdigoat > $OBJ/authorized_principals_$USER
+test_one "AuthorizedPrincipalsFile principals" success "-n mekmitasdigoat" \
+ TrustedUserCAKeys "AuthorizedPrincipalsFile $OBJ/authorized_principals_%u"
+test_one "AuthorizedPrincipalsFile no principals" failure "" \
+ TrustedUserCAKeys "AuthorizedPrincipalsFile $OBJ/authorized_principals_%u"
+
+# principals= key option
+rm -f $OBJ/authorized_principals_$USER
+test_one "principals key option principals" success "-n mekmitasdigoat" \
+ authorized_keys ',principals="mekmitasdigoat"'
+test_one "principals key option no principals" failure "" \
+ authorized_keys ',principals="mekmitasdigoat"'
+
+# command= options vs. force-command in key
+test_one "force-command match true" success \
+ "-n ${USER} -Oforce-command=true" \
+ authorized_keys ',command="true"'
+test_one "force-command match true" failure \
+ "-n ${USER} -Oforce-command=false" \
+ authorized_keys ',command="false"'
+test_one "force-command mismatch 1" failure \
+ "-n ${USER} -Oforce-command=false" \
+ authorized_keys ',command="true"'
+test_one "force-command mismatch 2" failure \
+ "-n ${USER} -Oforce-command=true" \
+ authorized_keys ',command="false"'
+
+# Wrong certificate
+cat $OBJ/sshd_proxy_bak > $OBJ/sshd_proxy
+for ktype in $PLAIN_TYPES ; do
+ t=$(kname $ktype)
+ # Self-sign
+ ${SSHKEYGEN} -q -s $OBJ/cert_user_key_${ktype} -I \
+ "regress user key for $USER" \
+ -n $USER $OBJ/cert_user_key_${ktype} ||
+ fatal "couldn't sign cert_user_key_${ktype}"
+ verbose "$tid: user ${ktype} connect wrong cert"
+ ${SSH} -i $OBJ/cert_user_key_${ktype} -F $OBJ/ssh_proxy \
+ somehost true >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ fail "ssh cert connect $ident succeeded unexpectedly"
+ fi
+done
+
+rm -f $OBJ/authorized_keys_$USER $OBJ/user_ca_key* $OBJ/cert_user_key*
+rm -f $OBJ/authorized_principals_$USER
+
diff --git a/regress/cfginclude.sh b/regress/cfginclude.sh
new file mode 100644
index 0000000..f5b492f
--- /dev/null
+++ b/regress/cfginclude.sh
@@ -0,0 +1,293 @@
+# $OpenBSD: cfginclude.sh,v 1.3 2021/06/08 06:52:43 djm Exp $
+# Placed in the Public Domain.
+
+tid="config include"
+
+# to appease StrictModes
+umask 022
+
+cat > $OBJ/ssh_config.i << _EOF
+Match host a
+ Hostname aa
+
+Match host b # comment
+ Hostname bb
+ Include $OBJ/ssh_config.i.*
+
+Match host c
+ Include $OBJ/ssh_config.i.*
+ Hostname cc
+
+Match host m
+ Include $OBJ/ssh_config.i.* # comment
+
+Host d
+ Hostname dd # comment
+
+Host e
+ Hostname ee
+ Include $OBJ/ssh_config.i.*
+
+Host f
+ Include $OBJ/ssh_config.i.*
+ Hostname ff
+
+Host n
+ Include $OBJ/ssh_config.i.*
+_EOF
+
+cat > $OBJ/ssh_config.i.0 << _EOF
+Match host xxxxxx
+_EOF
+
+cat > $OBJ/ssh_config.i.1 << _EOF
+Match host a
+ Hostname aaa
+
+Match host b
+ Hostname bbb
+
+Match host c # comment
+ Hostname ccc
+
+Host d # comment
+ Hostname ddd
+
+Host e
+ Hostname eee
+
+Host f
+ Hostname fff # comment
+_EOF
+
+cat > $OBJ/ssh_config.i.2 << _EOF
+Match host a
+ Hostname aaaa
+
+Match host b
+ Hostname bbbb
+
+Match host c
+ Hostname cccc
+
+Host d
+ Hostname dddd
+
+Host e
+ Hostname eeee
+
+Host f
+ Hostname ffff
+
+Match all
+ Hostname xxxx
+_EOF
+
+trial() {
+ _host="$1"
+ _exp="$2"
+ ${REAL_SSH} -F $OBJ/ssh_config.i -G "$_host" > $OBJ/ssh_config.out ||
+ fatal "ssh config parse failed"
+ _got=`grep -i '^hostname ' $OBJ/ssh_config.out | awk '{print $2}'`
+ if test "x$_exp" != "x$_got" ; then
+ fail "host $_host include fail: expected $_exp got $_got"
+ fi
+}
+
+trial a aa
+trial b bb
+trial c ccc
+trial d dd
+trial e ee
+trial f fff
+trial m xxxx
+trial n xxxx
+trial x x
+
+# Prepare an included config with an error.
+
+cat > $OBJ/ssh_config.i.3 << _EOF
+Hostname xxxx
+ Junk
+_EOF
+
+${REAL_SSH} -F $OBJ/ssh_config.i -G a 2>/dev/null && \
+ fail "ssh include allowed invalid config"
+
+${REAL_SSH} -F $OBJ/ssh_config.i -G x 2>/dev/null && \
+ fail "ssh include allowed invalid config"
+
+rm -f $OBJ/ssh_config.i.*
+
+# Ensure that a missing include is not fatal.
+cat > $OBJ/ssh_config.i << _EOF
+Include $OBJ/ssh_config.i.*
+Hostname aa
+_EOF
+
+trial a aa
+
+# Ensure that Match/Host in an included config does not affect parent.
+cat > $OBJ/ssh_config.i.x << _EOF
+Match host x
+_EOF
+
+trial a aa
+
+cat > $OBJ/ssh_config.i.x << _EOF
+Host x
+_EOF
+
+trial a aa
+
+# cleanup
+rm -f $OBJ/ssh_config.i $OBJ/ssh_config.i.* $OBJ/ssh_config.out
+# $OpenBSD: cfginclude.sh,v 1.3 2021/06/08 06:52:43 djm Exp $
+# Placed in the Public Domain.
+
+tid="config include"
+
+cat > $OBJ/ssh_config.i << _EOF
+Match host a
+ Hostname aa
+
+Match host b
+ Hostname bb
+ Include $OBJ/ssh_config.i.*
+
+Match host c
+ Include $OBJ/ssh_config.i.*
+ Hostname cc
+
+Match host m
+ Include $OBJ/ssh_config.i.*
+
+Host d
+ Hostname dd
+
+Host e
+ Hostname ee
+ Include $OBJ/ssh_config.i.*
+
+Host f
+ Include $OBJ/ssh_config.i.*
+ Hostname ff
+
+Host n
+ Include $OBJ/ssh_config.i.*
+_EOF
+
+cat > $OBJ/ssh_config.i.0 << _EOF
+Match host xxxxxx
+_EOF
+
+cat > $OBJ/ssh_config.i.1 << _EOF
+Match host a
+ Hostname aaa
+
+Match host b # comment
+ Hostname bbb
+
+Match host c
+ Hostname ccc # comment
+
+Host d
+ Hostname ddd
+
+Host e
+ Hostname eee
+
+Host f
+ Hostname fff
+_EOF
+
+cat > $OBJ/ssh_config.i.2 << _EOF
+Match host a
+ Hostname aaaa
+
+Match host b
+ Hostname bbbb
+
+Match host c
+ Hostname cccc
+
+Host d
+ Hostname dddd
+
+Host e
+ Hostname eeee
+
+Host f
+ Hostname ffff
+
+Match all # comment
+ Hostname xxxx # comment
+_EOF
+
+trial() {
+ _host="$1"
+ _exp="$2"
+ ${REAL_SSH} -F $OBJ/ssh_config.i -G "$_host" > $OBJ/ssh_config.out ||
+ fatal "ssh config parse failed"
+ _got=`grep -i '^hostname ' $OBJ/ssh_config.out | awk '{print $2}'`
+ if test "x$_exp" != "x$_got" ; then
+ fail "host $_host include fail: expected $_exp got $_got"
+ fi
+}
+
+trial a aa
+trial b bb
+trial c ccc
+trial d dd
+trial e ee
+trial f fff
+trial m xxxx
+trial n xxxx
+trial x x
+
+# Prepare an included config with an error.
+
+cat > $OBJ/ssh_config.i.3 << _EOF
+Hostname xxxx
+ Junk
+_EOF
+
+${REAL_SSH} -F $OBJ/ssh_config.i -G a 2>/dev/null && \
+ fail "ssh include allowed invalid config"
+
+${REAL_SSH} -F $OBJ/ssh_config.i -G x 2>/dev/null && \
+ fail "ssh include allowed invalid config"
+
+rm -f $OBJ/ssh_config.i.*
+
+# Ensure that a missing include is not fatal.
+cat > $OBJ/ssh_config.i << _EOF
+Include $OBJ/ssh_config.i.*
+Hostname aa
+_EOF
+
+trial a aa
+
+# Ensure that Match/Host in an included config does not affect parent.
+cat > $OBJ/ssh_config.i.x << _EOF
+Match host x
+_EOF
+
+trial a aa
+
+cat > $OBJ/ssh_config.i.x << _EOF
+Host x
+_EOF
+
+trial a aa
+
+# Ensure that recursive includes are bounded.
+cat > $OBJ/ssh_config.i << _EOF
+Include $OBJ/ssh_config.i
+_EOF
+
+${REAL_SSH} -F $OBJ/ssh_config.i -G a 2>/dev/null && \
+ fail "ssh include allowed infinite recursion?" # or hang...
+
+# cleanup
+rm -f $OBJ/ssh_config.i $OBJ/ssh_config.i.* $OBJ/ssh_config.out
diff --git a/regress/cfgmatch.sh b/regress/cfgmatch.sh
new file mode 100644
index 0000000..05a6668
--- /dev/null
+++ b/regress/cfgmatch.sh
@@ -0,0 +1,158 @@
+# $OpenBSD: cfgmatch.sh,v 1.13 2021/06/08 06:52:43 djm Exp $
+# Placed in the Public Domain.
+
+tid="sshd_config match"
+
+pidfile=$OBJ/remote_pid
+fwdport=3301
+fwd="-L $fwdport:127.0.0.1:$PORT"
+
+echo "ExitOnForwardFailure=yes" >> $OBJ/ssh_config
+echo "ExitOnForwardFailure=yes" >> $OBJ/ssh_proxy
+
+start_client()
+{
+ rm -f $pidfile
+ ${SSH} -q $fwd "$@" somehost \
+ exec sh -c \'"echo \$\$ > $pidfile; exec sleep 100"\' \
+ >>$TEST_REGRESS_LOGFILE 2>&1 &
+ client_pid=$!
+ # Wait for remote end
+ n=0
+ while test ! -f $pidfile ; do
+ sleep 1
+ n=`expr $n + 1`
+ if test $n -gt 60; then
+ kill $client_pid
+ fatal "timeout waiting for background ssh"
+ fi
+ done
+}
+
+stop_client()
+{
+ pid=`cat $pidfile`
+ if [ ! -z "$pid" ]; then
+ kill $pid
+ fi
+ wait
+}
+
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+echo "PermitOpen 127.0.0.1:1 # comment" >>$OBJ/sshd_config
+echo "Match Address 127.0.0.1" >>$OBJ/sshd_config
+echo "PermitOpen 127.0.0.1:2 127.0.0.1:3 127.0.0.1:$PORT" >>$OBJ/sshd_config
+
+grep -v AuthorizedKeysFile $OBJ/sshd_proxy_bak > $OBJ/sshd_proxy
+echo "AuthorizedKeysFile /dev/null # comment" >>$OBJ/sshd_proxy
+echo "PermitOpen 127.0.0.1:1" >>$OBJ/sshd_proxy
+echo "Match user $USER" >>$OBJ/sshd_proxy
+echo "AuthorizedKeysFile /dev/null $OBJ/authorized_keys_%u" >>$OBJ/sshd_proxy
+echo "Match Address 127.0.0.1 # comment" >>$OBJ/sshd_proxy
+echo "PermitOpen 127.0.0.1:2 127.0.0.1:3 127.0.0.1:$PORT" >>$OBJ/sshd_proxy
+
+${SUDO} ${SSHD} -f $OBJ/sshd_config -T >/dev/null || \
+ fail "config w/match fails config test"
+
+start_sshd
+
+# Test Match + PermitOpen in sshd_config. This should be permitted
+trace "match permitopen localhost"
+start_client -F $OBJ/ssh_config
+${SSH} -q -p $fwdport -F $OBJ/ssh_config somehost true || \
+ fail "match permitopen permit"
+stop_client
+
+# Same but from different source. This should not be permitted
+trace "match permitopen proxy"
+start_client -F $OBJ/ssh_proxy
+${SSH} -q -p $fwdport -F $OBJ/ssh_config somehost true && \
+ fail "match permitopen deny"
+stop_client
+
+# Retry previous with key option, should also be denied.
+cp /dev/null $OBJ/authorized_keys_$USER
+for t in ${SSH_KEYTYPES}; do
+ printf 'permitopen="127.0.0.1:'$PORT'" ' >> $OBJ/authorized_keys_$USER
+ cat $OBJ/$t.pub >> $OBJ/authorized_keys_$USER
+done
+trace "match permitopen proxy w/key opts"
+start_client -F $OBJ/ssh_proxy
+${SSH} -q -p $fwdport -F $OBJ/ssh_config somehost true && \
+ fail "match permitopen deny w/key opt"
+stop_client
+
+# Test both sshd_config and key options permitting the same dst/port pair.
+# Should be permitted.
+trace "match permitopen localhost"
+start_client -F $OBJ/ssh_config
+${SSH} -q -p $fwdport -F $OBJ/ssh_config somehost true || \
+ fail "match permitopen permit"
+stop_client
+
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+echo "PermitOpen 127.0.0.1:1 127.0.0.1:$PORT 127.0.0.2:2" >>$OBJ/sshd_proxy
+echo "Match User $USER" >>$OBJ/sshd_proxy
+echo "PermitOpen 127.0.0.1:1 127.0.0.1:2" >>$OBJ/sshd_proxy
+
+# Test that a Match overrides a PermitOpen in the global section
+trace "match permitopen proxy w/key opts"
+start_client -F $OBJ/ssh_proxy
+${SSH} -q -p $fwdport -F $OBJ/ssh_config somehost true && \
+ fail "match override permitopen"
+stop_client
+
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+echo "PermitOpen 127.0.0.1:1 127.0.0.1:$PORT 127.0.0.2:2" >>$OBJ/sshd_proxy
+echo "Match User NoSuchUser" >>$OBJ/sshd_proxy
+echo "PermitOpen 127.0.0.1:1 127.0.0.1:2" >>$OBJ/sshd_proxy
+
+# Test that a rule that doesn't match doesn't override, plus test a
+# PermitOpen entry that's not at the start of the list
+trace "nomatch permitopen proxy w/key opts"
+start_client -F $OBJ/ssh_proxy
+${SSH} -q -p $fwdport -F $OBJ/ssh_config somehost true || \
+ fail "nomatch override permitopen"
+stop_client
+
+# Test parsing of available Match criteria (with the exception of Group which
+# requires knowledge of actual group memberships user running the test).
+params="user:user:u1 host:host:h1 address:addr:1.2.3.4 \
+ localaddress:laddr:5.6.7.8 rdomain:rdomain:rdom1"
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_config
+echo 'Banner /nomatch' >>$OBJ/sshd_config
+for i in $params; do
+ config=`echo $i | cut -f1 -d:`
+ criteria=`echo $i | cut -f2 -d:`
+ value=`echo $i | cut -f3 -d:`
+ cat >>$OBJ/sshd_config <<EOD
+ Match $config $value
+ Banner /$value
+EOD
+done
+
+${SUDO} ${SSHD} -f $OBJ/sshd_config -T >/dev/null || \
+ fail "validate config for w/out spec"
+
+# Test matching each criteria.
+for i in $params; do
+ testcriteria=`echo $i | cut -f2 -d:`
+ expected=/`echo $i | cut -f3 -d:`
+ spec=""
+ for j in $params; do
+ config=`echo $j | cut -f1 -d:`
+ criteria=`echo $j | cut -f2 -d:`
+ value=`echo $j | cut -f3 -d:`
+ if [ "$criteria" = "$testcriteria" ]; then
+ spec="$criteria=$value,$spec"
+ else
+ spec="$criteria=1$value,$spec"
+ fi
+ done
+ trace "test spec $spec"
+ result=`${SUDO} ${SSHD} -f $OBJ/sshd_config -T -C "$spec" | \
+ awk '$1=="banner"{print $2}'`
+ if [ "$result" != "$expected" ]; then
+ fail "match $config expected $expected got $result"
+ fi
+done
diff --git a/regress/cfgmatchlisten.sh b/regress/cfgmatchlisten.sh
new file mode 100644
index 0000000..a4fd66b
--- /dev/null
+++ b/regress/cfgmatchlisten.sh
@@ -0,0 +1,202 @@
+# $OpenBSD: cfgmatchlisten.sh,v 1.3 2018/07/02 14:13:30 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="sshd_config matchlisten"
+
+pidfile=$OBJ/remote_pid
+fwdport=3301
+fwdspec="localhost:${fwdport}"
+fwd="-R $fwdport:127.0.0.1:$PORT"
+
+echo "ExitOnForwardFailure=yes" >> $OBJ/ssh_config
+echo "ExitOnForwardFailure=yes" >> $OBJ/ssh_proxy
+
+start_client()
+{
+ rm -f $pidfile
+ ${SSH} -vvv $fwd "$@" somehost true >>$TEST_REGRESS_LOGFILE 2>&1
+ r=$?
+ if [ $r -ne 0 ]; then
+ return $r
+ fi
+ ${SSH} -vvv $fwd "$@" somehost \
+ exec sh -c \'"echo \$\$ > $pidfile; exec sleep 100"\' \
+ >>$TEST_REGRESS_LOGFILE 2>&1 &
+ client_pid=$!
+ # Wait for remote end
+ n=0
+ while test ! -f $pidfile ; do
+ sleep 1
+ n=`expr $n + 1`
+ if test $n -gt 60; then
+ kill $client_pid
+ fatal "timeout waiting for background ssh"
+ fi
+ done
+ return $r
+}
+
+expect_client_ok()
+{
+ start_client "$@" ||
+ fail "client did not start"
+}
+
+expect_client_fail()
+{
+ local failmsg="$1"
+ shift
+ start_client "$@" &&
+ fail $failmsg
+}
+
+stop_client()
+{
+ pid=`cat $pidfile`
+ if [ ! -z "$pid" ]; then
+ kill $pid
+ fi
+ wait
+}
+
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+echo "PermitListen 127.0.0.1:1" >>$OBJ/sshd_config
+echo "Match Address 127.0.0.1" >>$OBJ/sshd_config
+echo "PermitListen 127.0.0.1:2 127.0.0.1:3 $fwdspec" >>$OBJ/sshd_config
+
+grep -v AuthorizedKeysFile $OBJ/sshd_proxy_bak > $OBJ/sshd_proxy
+echo "AuthorizedKeysFile /dev/null" >>$OBJ/sshd_proxy
+echo "PermitListen 127.0.0.1:1" >>$OBJ/sshd_proxy
+echo "Match user $USER" >>$OBJ/sshd_proxy
+echo "AuthorizedKeysFile /dev/null $OBJ/authorized_keys_%u" >>$OBJ/sshd_proxy
+echo "Match Address 127.0.0.1" >>$OBJ/sshd_proxy
+echo "PermitListen 127.0.0.1:2 127.0.0.1:3 $fwdspec" >>$OBJ/sshd_proxy
+
+start_sshd
+
+#set -x
+
+# Test Match + PermitListen in sshd_config. This should be permitted
+trace "match permitlisten localhost"
+expect_client_ok -F $OBJ/ssh_config
+${SSH} -q -p $fwdport -F $OBJ/ssh_config somehost true || \
+ fail "match permitlisten permit"
+stop_client
+
+# Same but from different source. This should not be permitted
+trace "match permitlisten proxy"
+expect_client_fail "match permitlisten deny" \
+ -F $OBJ/ssh_proxy
+
+# Retry previous with key option, should also be denied.
+cp /dev/null $OBJ/authorized_keys_$USER
+for t in ${SSH_KEYTYPES}; do
+ printf 'permitlisten="'$fwdspec'" ' >> $OBJ/authorized_keys_$USER
+ cat $OBJ/$t.pub >> $OBJ/authorized_keys_$USER
+done
+trace "match permitlisten proxy w/key opts"
+expect_client_fail "match permitlisten deny w/key opt"\
+ -F $OBJ/ssh_proxy
+
+# Test both sshd_config and key options permitting the same dst/port pair.
+# Should be permitted.
+trace "match permitlisten localhost"
+expect_client_ok -F $OBJ/ssh_config
+${SSH} -q -p $fwdport -F $OBJ/ssh_config somehost true || \
+ fail "match permitlisten permit"
+stop_client
+
+# Test that a bare port number is accepted in PermitListen
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+echo "PermitListen 127.0.0.1:1 $fwdport 127.0.0.2:2" >>$OBJ/sshd_proxy
+trace "match permitlisten bare"
+expect_client_ok -F $OBJ/ssh_config
+${SSH} -q -p $fwdport -F $OBJ/ssh_config somehost true || \
+ fail "match permitlisten bare"
+stop_client
+
+# Test that an incorrect bare port number is denied as expected
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+echo "PermitListen 1 2 99" >>$OBJ/sshd_proxy
+trace "match permitlisten bare"
+expect_client_fail -F $OBJ/ssh_config
+
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+echo "PermitListen 127.0.0.1:1 $fwdspec 127.0.0.2:2" >>$OBJ/sshd_proxy
+echo "Match User $USER" >>$OBJ/sshd_proxy
+echo "PermitListen 127.0.0.1:1 127.0.0.1:2" >>$OBJ/sshd_proxy
+
+# Test that a Match overrides a PermitListen in the global section
+trace "match permitlisten proxy w/key opts"
+expect_client_fail "match override permitlisten" \
+ -F $OBJ/ssh_proxy
+
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+echo "PermitListen 127.0.0.1:1 $fwdspec 127.0.0.2:2" >>$OBJ/sshd_proxy
+echo "Match User NoSuchUser" >>$OBJ/sshd_proxy
+echo "PermitListen 127.0.0.1:1 127.0.0.1:2" >>$OBJ/sshd_proxy
+
+# Test that a rule that doesn't match doesn't override, plus test a
+# PermitListen entry that's not at the start of the list
+trace "nomatch permitlisten proxy w/key opts"
+expect_client_ok -F $OBJ/ssh_proxy
+${SSH} -q -p $fwdport -F $OBJ/ssh_config somehost true || \
+ fail "nomatch override permitlisten"
+stop_client
+
+# bind to 127.0.0.1 instead of default localhost
+fwdspec2="127.0.0.1:${fwdport}"
+fwd="-R ${fwdspec2}:127.0.0.1:$PORT"
+
+# first try w/ old fwdspec both in server config and key opts
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+echo "PermitListen 127.0.0.1:1 $fwdspec 127.0.0.2:2" >>$OBJ/sshd_proxy
+cp /dev/null $OBJ/authorized_keys_$USER
+for t in ${SSH_KEYTYPES}; do
+ printf 'permitlisten="'$fwdspec'" ' >> $OBJ/authorized_keys_$USER
+ cat $OBJ/$t.pub >> $OBJ/authorized_keys_$USER
+done
+trace "nomatch permitlisten 127.0.0.1 server config and userkey"
+expect_client_fail "nomatch 127.0.0.1 server config and userkey" \
+ -F $OBJ/ssh_config
+
+# correct server config, denied by key opts
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+echo "PermitListen 127.0.0.1:1 ${fwdspec2} 127.0.0.2:2" >>$OBJ/sshd_proxy
+trace "nomatch permitlisten 127.0.0.1 w/key opts"
+expect_client_fail "nomatch 127.0.0.1 w/key opts" \
+ -F $OBJ/ssh_config
+
+# fix key opts
+cp /dev/null $OBJ/authorized_keys_$USER
+for t in ${SSH_KEYTYPES}; do
+ printf 'permitlisten="'$fwdspec2'" ' >> $OBJ/authorized_keys_$USER
+ cat $OBJ/$t.pub >> $OBJ/authorized_keys_$USER
+done
+trace "match permitlisten 127.0.0.1 server config w/key opts"
+expect_client_ok -F $OBJ/ssh_proxy
+${SSH} -q -p $fwdport -F $OBJ/ssh_config somehost true || \
+ fail "match 127.0.0.1 server config w/key opts"
+stop_client
+
+# key opts with bare port number
+cp /dev/null $OBJ/authorized_keys_$USER
+for t in ${SSH_KEYTYPES}; do
+ printf 'permitlisten="'$fwdport'" ' >> $OBJ/authorized_keys_$USER
+ cat $OBJ/$t.pub >> $OBJ/authorized_keys_$USER
+done
+trace "match permitlisten 127.0.0.1 server config w/key opts (bare)"
+expect_client_ok -F $OBJ/ssh_proxy
+${SSH} -q -p $fwdport -F $OBJ/ssh_config somehost true || \
+ fail "match 127.0.0.1 server config w/key opts (bare)"
+stop_client
+
+# key opts with incorrect bare port number
+cp /dev/null $OBJ/authorized_keys_$USER
+for t in ${SSH_KEYTYPES}; do
+ printf 'permitlisten="99" ' >> $OBJ/authorized_keys_$USER
+ cat $OBJ/$t.pub >> $OBJ/authorized_keys_$USER
+done
+trace "match permitlisten 127.0.0.1 server config w/key opts (wrong bare)"
+expect_client_fail "nomatch 127.0.0.1 w/key opts (wrong bare)" \
+ -F $OBJ/ssh_config
diff --git a/regress/cfgparse.sh b/regress/cfgparse.sh
new file mode 100644
index 0000000..a9e5c6b
--- /dev/null
+++ b/regress/cfgparse.sh
@@ -0,0 +1,75 @@
+# $OpenBSD: cfgparse.sh,v 1.7 2018/05/11 03:51:06 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="sshd config parse"
+
+# This is a reasonable proxy for IPv6 support.
+if ! config_defined HAVE_STRUCT_IN6_ADDR ; then
+ SKIP_IPV6=yes
+fi
+
+# We need to use the keys generated for the regression test because sshd -T
+# will fail if we're not running with SUDO (no permissions for real keys) or
+# if we are running tests on a system that has never had sshd installed
+# because the keys won't exist.
+
+grep "HostKey " $OBJ/sshd_config > $OBJ/sshd_config_minimal
+SSHD_KEYS="`cat $OBJ/sshd_config_minimal`"
+
+verbose "reparse minimal config"
+($SUDO ${SSHD} -T -f $OBJ/sshd_config_minimal >$OBJ/sshd_config.1 &&
+ $SUDO ${SSHD} -T -f $OBJ/sshd_config.1 >$OBJ/sshd_config.2 &&
+ diff $OBJ/sshd_config.1 $OBJ/sshd_config.2) || fail "reparse minimal config"
+
+verbose "reparse regress config"
+($SUDO ${SSHD} -T -f $OBJ/sshd_config >$OBJ/sshd_config.1 &&
+ $SUDO ${SSHD} -T -f $OBJ/sshd_config.1 >$OBJ/sshd_config.2 &&
+ diff $OBJ/sshd_config.1 $OBJ/sshd_config.2) || fail "reparse regress config"
+
+verbose "listenaddress order"
+# expected output
+cat > $OBJ/sshd_config.0 <<EOD
+listenaddress 1.2.3.4:1234
+listenaddress 1.2.3.4:5678
+EOD
+[ X${SKIP_IPV6} = Xyes ] || cat >> $OBJ/sshd_config.0 <<EOD
+listenaddress [::1]:1234
+listenaddress [::1]:5678
+EOD
+
+# test input sets. should all result in the output above.
+# test 1: addressfamily and port first
+cat > $OBJ/sshd_config.1 <<EOD
+${SSHD_KEYS}
+addressfamily any
+port 1234
+port 5678
+listenaddress 1.2.3.4
+EOD
+[ X${SKIP_IPV6} = Xyes ] || cat >> $OBJ/sshd_config.1 <<EOD
+listenaddress ::1
+EOD
+
+($SUDO ${SSHD} -T -f $OBJ/sshd_config.1 | \
+ grep 'listenaddress ' >$OBJ/sshd_config.2 &&
+ diff $OBJ/sshd_config.0 $OBJ/sshd_config.2) || \
+ fail "listenaddress order 1"
+# test 2: listenaddress first
+cat > $OBJ/sshd_config.1 <<EOD
+${SSHD_KEYS}
+listenaddress 1.2.3.4
+port 1234
+port 5678
+addressfamily any
+EOD
+[ X${SKIP_IPV6} = Xyes ] || cat >> $OBJ/sshd_config.1 <<EOD
+listenaddress ::1
+EOD
+
+($SUDO ${SSHD} -T -f $OBJ/sshd_config.1 | \
+ grep 'listenaddress ' >$OBJ/sshd_config.2 &&
+ diff $OBJ/sshd_config.0 $OBJ/sshd_config.2) || \
+ fail "listenaddress order 2"
+
+# cleanup
+rm -f $OBJ/sshd_config.[012]
diff --git a/regress/channel-timeout.sh b/regress/channel-timeout.sh
new file mode 100644
index 0000000..1c42e83
--- /dev/null
+++ b/regress/channel-timeout.sh
@@ -0,0 +1,91 @@
+# $OpenBSD: channel-timeout.sh,v 1.1 2023/01/06 08:07:39 djm Exp $
+# Placed in the Public Domain.
+
+tid="channel timeout"
+
+# XXX not comprehensive. Still need -R -L agent X11 forwarding + interactive
+
+rm -f $OBJ/sshd_proxy.orig
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy.orig
+
+verbose "no timeout"
+${SSH} -F $OBJ/ssh_proxy somehost "sleep 5 ; exit 23"
+r=$?
+if [ $r -ne 23 ]; then
+ fail "ssh failed"
+fi
+
+verbose "command timeout"
+(cat $OBJ/sshd_proxy.orig ; echo "ChannelTimeout session:command=1") \
+ > $OBJ/sshd_proxy
+${SSH} -F $OBJ/ssh_proxy somehost "sleep 5 ; exit 23"
+r=$?
+if [ $r -ne 255 ]; then
+ fail "ssh returned unexpected error code $r"
+fi
+
+verbose "command wildcard timeout"
+(cat $OBJ/sshd_proxy.orig ; echo "ChannelTimeout session:*=1") \
+ > $OBJ/sshd_proxy
+${SSH} -F $OBJ/ssh_proxy somehost "sleep 5 ; exit 23"
+r=$?
+if [ $r -ne 255 ]; then
+ fail "ssh returned unexpected error code $r"
+fi
+
+verbose "command irrelevant timeout"
+(cat $OBJ/sshd_proxy.orig ; echo "ChannelTimeout session:shell=1") \
+ > $OBJ/sshd_proxy
+${SSH} -F $OBJ/ssh_proxy somehost "sleep 5 ; exit 23"
+r=$?
+if [ $r -ne 23 ]; then
+ fail "ssh failed"
+fi
+
+# Set up a "slow sftp server" that sleeps before executing the real one.
+cat > $OBJ/slow-sftp-server.sh << _EOF
+#!/bin/sh
+
+sleep 5
+$SFTPSERVER
+_EOF
+chmod a+x $OBJ/slow-sftp-server.sh
+
+verbose "sftp no timeout"
+(grep -vi subsystem.*sftp $OBJ/sshd_proxy.orig;
+ echo "Subsystem sftp $OBJ/slow-sftp-server.sh" ) > $OBJ/sshd_proxy
+
+rm -f ${COPY}
+$SFTP -qS $SSH -F $OBJ/ssh_proxy somehost:$DATA $COPY
+r=$?
+if [ $r -ne 0 ]; then
+ fail "sftp failed"
+fi
+cmp $DATA $COPY || fail "corrupted copy"
+
+verbose "sftp timeout"
+(grep -vi subsystem.*sftp $OBJ/sshd_proxy.orig;
+ echo "ChannelTimeout session:subsystem:sftp=1" ;
+ echo "Subsystem sftp $OBJ/slow-sftp-server.sh" ) > $OBJ/sshd_proxy
+
+rm -f ${COPY}
+$SFTP -qS $SSH -F $OBJ/ssh_proxy somehost:$DATA $COPY
+r=$?
+if [ $r -eq 0 ]; then
+ fail "sftp succeeded unexpectedly"
+fi
+test -f $COPY && cmp $DATA $COPY && fail "intact copy"
+
+verbose "sftp irrelevant timeout"
+(grep -vi subsystem.*sftp $OBJ/sshd_proxy.orig;
+ echo "ChannelTimeout session:subsystem:command=1" ;
+ echo "Subsystem sftp $OBJ/slow-sftp-server.sh" ) > $OBJ/sshd_proxy
+
+rm -f ${COPY}
+$SFTP -qS $SSH -F $OBJ/ssh_proxy somehost:$DATA $COPY
+r=$?
+if [ $r -ne 0 ]; then
+ fail "sftp failed"
+fi
+cmp $DATA $COPY || fail "corrupted copy"
+
diff --git a/regress/check-perm.c b/regress/check-perm.c
new file mode 100644
index 0000000..dac307d
--- /dev/null
+++ b/regress/check-perm.c
@@ -0,0 +1,205 @@
+/*
+ * Placed in the public domain
+ */
+
+/* $OpenBSD: modpipe.c,v 1.6 2013/11/21 03:16:47 djm Exp $ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <pwd.h>
+#ifdef HAVE_LIBGEN_H
+#include <libgen.h>
+#endif
+
+static void
+fatal(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ fputc('\n', stderr);
+ va_end(args);
+ exit(1);
+}
+/* Based on session.c. NB. keep tests in sync */
+static void
+safely_chroot(const char *path, uid_t uid)
+{
+ const char *cp;
+ char component[PATH_MAX];
+ struct stat st;
+
+ if (*path != '/')
+ fatal("chroot path does not begin at root");
+ if (strlen(path) >= sizeof(component))
+ fatal("chroot path too long");
+
+ /*
+ * Descend the path, checking that each component is a
+ * root-owned directory with strict permissions.
+ */
+ for (cp = path; cp != NULL;) {
+ if ((cp = strchr(cp, '/')) == NULL)
+ strlcpy(component, path, sizeof(component));
+ else {
+ cp++;
+ memcpy(component, path, cp - path);
+ component[cp - path] = '\0';
+ }
+
+ /* debug3("%s: checking '%s'", __func__, component); */
+
+ if (stat(component, &st) != 0)
+ fatal("%s: stat(\"%s\"): %s", __func__,
+ component, strerror(errno));
+ if (st.st_uid != 0 || (st.st_mode & 022) != 0)
+ fatal("bad ownership or modes for chroot "
+ "directory %s\"%s\"",
+ cp == NULL ? "" : "component ", component);
+ if (!S_ISDIR(st.st_mode))
+ fatal("chroot path %s\"%s\" is not a directory",
+ cp == NULL ? "" : "component ", component);
+
+ }
+
+ if (chdir(path) == -1)
+ fatal("Unable to chdir to chroot path \"%s\": "
+ "%s", path, strerror(errno));
+}
+
+/* from platform.c */
+int
+platform_sys_dir_uid(uid_t uid)
+{
+ if (uid == 0)
+ return 1;
+#ifdef PLATFORM_SYS_DIR_UID
+ if (uid == PLATFORM_SYS_DIR_UID)
+ return 1;
+#endif
+ return 0;
+}
+
+/* from auth.c */
+int
+auth_secure_path(const char *name, struct stat *stp, const char *pw_dir,
+ uid_t uid, char *err, size_t errlen)
+{
+ char buf[PATH_MAX], homedir[PATH_MAX];
+ char *cp;
+ int comparehome = 0;
+ struct stat st;
+
+ if (realpath(name, buf) == NULL) {
+ snprintf(err, errlen, "realpath %s failed: %s", name,
+ strerror(errno));
+ return -1;
+ }
+ if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL)
+ comparehome = 1;
+
+ if (!S_ISREG(stp->st_mode)) {
+ snprintf(err, errlen, "%s is not a regular file", buf);
+ return -1;
+ }
+ if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) ||
+ (stp->st_mode & 022) != 0) {
+ snprintf(err, errlen, "bad ownership or modes for file %s",
+ buf);
+ return -1;
+ }
+
+ /* for each component of the canonical path, walking upwards */
+ for (;;) {
+ if ((cp = dirname(buf)) == NULL) {
+ snprintf(err, errlen, "dirname() failed");
+ return -1;
+ }
+ strlcpy(buf, cp, sizeof(buf));
+
+ if (stat(buf, &st) < 0 ||
+ (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) ||
+ (st.st_mode & 022) != 0) {
+ snprintf(err, errlen,
+ "bad ownership or modes for directory %s", buf);
+ return -1;
+ }
+
+ /* If are past the homedir then we can stop */
+ if (comparehome && strcmp(homedir, buf) == 0)
+ break;
+
+ /*
+ * dirname should always complete with a "/" path,
+ * but we can be paranoid and check for "." too
+ */
+ if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
+ break;
+ }
+ return 0;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "check-perm -m [chroot | keys-command] [path]\n");
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *path = ".";
+ char errmsg[256];
+ int ch, mode = -1;
+ extern char *optarg;
+ extern int optind;
+ struct stat st;
+
+ while ((ch = getopt(argc, argv, "hm:")) != -1) {
+ switch (ch) {
+ case 'm':
+ if (strcasecmp(optarg, "chroot") == 0)
+ mode = 1;
+ else if (strcasecmp(optarg, "keys-command") == 0)
+ mode = 2;
+ else {
+ fprintf(stderr, "Invalid -m option\n"),
+ usage();
+ }
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 1)
+ usage();
+ else if (argc == 1)
+ path = argv[0];
+
+ if (mode == 1)
+ safely_chroot(path, getuid());
+ else if (mode == 2) {
+ if (stat(path, &st) < 0)
+ fatal("Could not stat %s: %s", path, strerror(errno));
+ if (auth_secure_path(path, &st, NULL, 0,
+ errmsg, sizeof(errmsg)) != 0)
+ fatal("Unsafe %s: %s", path, errmsg);
+ } else {
+ fprintf(stderr, "Invalid mode\n");
+ usage();
+ }
+ return 0;
+}
diff --git a/regress/cipher-speed.sh b/regress/cipher-speed.sh
new file mode 100644
index 0000000..1340bd1
--- /dev/null
+++ b/regress/cipher-speed.sh
@@ -0,0 +1,42 @@
+# $OpenBSD: cipher-speed.sh,v 1.14 2017/04/30 23:34:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="cipher speed"
+
+# Enable all supported ciphers and macs.
+ciphers=`${SSH} -Q Ciphers | tr '\n' , | sed 's/,$//'`
+macs=`${SSH} -Q MACs | tr '\n' , | sed 's/,$//'`
+cat >>$OBJ/sshd_proxy <<EOD
+Ciphers $ciphers
+MACs $macs
+EOD
+
+increase_datafile_size 10000 # 10MB
+
+getbytes ()
+{
+ sed -n -e '/transferred/s/.*secs (\(.* bytes.sec\).*/\1/p' \
+ -e '/copied/s/.*s, \(.* MB.s\).*/\1/p'
+}
+
+tries="1 2"
+
+for c in `${SSH} -Q cipher`; do n=0; for m in `${SSH} -Q mac`; do
+ trace "cipher $c mac $m"
+ for x in $tries; do
+ printf "%-60s" "$c/$m:"
+ ( ${SSH} -o 'compression no' \
+ -F $OBJ/ssh_proxy -m $m -c $c somehost \
+ exec sh -c \'"dd of=/dev/null obs=32k"\' \
+ < ${DATA} ) 2>&1 | getbytes
+
+ if [ $? -ne 0 ]; then
+ fail "ssh failed with mac $m cipher $c"
+ fi
+ done
+ # No point trying all MACs for AEAD ciphers since they are ignored.
+ if ${SSH} -Q cipher-auth | grep "^${c}\$" >/dev/null 2>&1 ; then
+ break
+ fi
+ n=`expr $n + 1`
+done; done
diff --git a/regress/conch-ciphers.sh b/regress/conch-ciphers.sh
new file mode 100644
index 0000000..6678813
--- /dev/null
+++ b/regress/conch-ciphers.sh
@@ -0,0 +1,28 @@
+# $OpenBSD: conch-ciphers.sh,v 1.4 2019/07/05 04:12:46 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="conch ciphers"
+
+if test "x$REGRESS_INTEROP_CONCH" != "xyes" ; then
+ echo "conch interop tests not enabled"
+ exit 0
+fi
+
+start_sshd
+
+for c in aes256-ctr aes256-cbc aes192-ctr aes192-cbc aes128-ctr aes128-cbc \
+ cast128-cbc blowfish 3des-cbc ; do
+ verbose "$tid: cipher $c"
+ rm -f ${COPY}
+ # XXX the 2nd "cat" seems to be needed because of buggy FD handling
+ # in conch
+ ${CONCH} --identity $OBJ/ssh-rsa --port $PORT --user $USER -e none \
+ --known-hosts $OBJ/known_hosts --notty --noagent --nox11 -n \
+ 127.0.0.1 "cat ${DATA}" 2>/dev/null | cat > ${COPY}
+ if [ $? -ne 0 ]; then
+ fail "ssh cat $DATA failed"
+ fi
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+done
+rm -f ${COPY}
+
diff --git a/regress/connect-privsep.sh b/regress/connect-privsep.sh
new file mode 100644
index 0000000..8970340
--- /dev/null
+++ b/regress/connect-privsep.sh
@@ -0,0 +1,34 @@
+# $OpenBSD: connect-privsep.sh,v 1.9 2017/04/30 23:34:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="proxy connect with privsep"
+
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy.orig
+echo 'UsePrivilegeSeparation yes' >> $OBJ/sshd_proxy
+
+${SSH} -F $OBJ/ssh_proxy 999.999.999.999 true
+if [ $? -ne 0 ]; then
+ fail "ssh privsep+proxyconnect failed"
+fi
+
+cp $OBJ/sshd_proxy.orig $OBJ/sshd_proxy
+echo 'UsePrivilegeSeparation sandbox' >> $OBJ/sshd_proxy
+
+${SSH} -F $OBJ/ssh_proxy 999.999.999.999 true
+if [ $? -ne 0 ]; then
+ fail "ssh privsep/sandbox+proxyconnect failed"
+fi
+
+# Because sandbox is sensitive to changes in libc, especially malloc, retest
+# with every malloc.conf option (and none).
+if [ -z "$TEST_MALLOC_OPTIONS" ]; then
+ mopts="C F G J R S U X < >"
+else
+ mopts=`echo $TEST_MALLOC_OPTIONS | sed 's/./& /g'`
+fi
+for m in '' $mopts ; do
+ env MALLOC_OPTIONS="$m" ${SSH} -F $OBJ/ssh_proxy 999.999.999.999 true
+ if [ $? -ne 0 ]; then
+ fail "ssh privsep/sandbox+proxyconnect mopt '$m' failed"
+ fi
+done
diff --git a/regress/connect-uri.sh b/regress/connect-uri.sh
new file mode 100644
index 0000000..f13f15e
--- /dev/null
+++ b/regress/connect-uri.sh
@@ -0,0 +1,29 @@
+# $OpenBSD: connect-uri.sh,v 1.1 2017/10/24 19:33:32 millert Exp $
+# Placed in the Public Domain.
+
+tid="uri connect"
+
+# Remove Port and User from ssh_config, we want to rely on the URI
+cp $OBJ/ssh_config $OBJ/ssh_config.orig
+egrep -v '^ +(Port|User) +.*$' $OBJ/ssh_config.orig > $OBJ/ssh_config
+
+start_sshd
+
+verbose "$tid: no trailing slash"
+${SSH} -F $OBJ/ssh_config "ssh://${USER}@somehost:${PORT}" true
+if [ $? -ne 0 ]; then
+ fail "ssh connection failed"
+fi
+
+verbose "$tid: trailing slash"
+${SSH} -F $OBJ/ssh_config "ssh://${USER}@somehost:${PORT}/" true
+if [ $? -ne 0 ]; then
+ fail "ssh connection failed"
+fi
+
+verbose "$tid: with path name"
+${SSH} -F $OBJ/ssh_config "ssh://${USER}@somehost:${PORT}/${DATA}" true \
+ > /dev/null 2>&1
+if [ $? -eq 0 ]; then
+ fail "ssh connection succeeded, expected failure"
+fi
diff --git a/regress/connect.sh b/regress/connect.sh
new file mode 100644
index 0000000..46f12b7
--- /dev/null
+++ b/regress/connect.sh
@@ -0,0 +1,18 @@
+# $OpenBSD: connect.sh,v 1.8 2020/01/25 02:57:53 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="simple connect"
+
+start_sshd
+
+trace "direct connect"
+${SSH} -F $OBJ/ssh_config somehost true
+if [ $? -ne 0 ]; then
+ fail "ssh direct connect failed"
+fi
+
+trace "proxy connect"
+${SSH} -F $OBJ/ssh_config -o "proxycommand $NC %h %p" somehost true
+if [ $? -ne 0 ]; then
+ fail "ssh proxycommand connect failed"
+fi
diff --git a/regress/connection-timeout.sh b/regress/connection-timeout.sh
new file mode 100644
index 0000000..c77abb3
--- /dev/null
+++ b/regress/connection-timeout.sh
@@ -0,0 +1,87 @@
+# $OpenBSD: connection-timeout.sh,v 1.2 2023/01/17 10:15:10 djm Exp $
+# Placed in the Public Domain.
+
+tid="unused connection timeout"
+if config_defined DISABLE_FD_PASSING ; then
+ skip "not supported on this platform"
+fi
+
+CTL=$OBJ/ctl-sock
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy.orig
+
+check_ssh() {
+ test -S $CTL || return 1
+ if ! ${REAL_SSH} -qF$OBJ/ssh_proxy -O check \
+ -oControlPath=$CTL somehost >/dev/null 2>&1 ; then
+ return 1
+ fi
+ return 0
+}
+
+start_ssh() {
+ trace "start ssh"
+ ${SSH} -nNfF $OBJ/ssh_proxy "$@" -oExitOnForwardFailure=yes \
+ -oControlMaster=yes -oControlPath=$CTL somehost
+ r=$?
+ test $r -eq 0 || fatal "failed to start ssh $r"
+ check_ssh || fatal "ssh process unresponsive"
+}
+
+stop_ssh() {
+ test -S $CTL || return
+ check_ssh || fatal "ssh process is unresponsive: cannot close"
+ if ! ${REAL_SSH} -qF$OBJ/ssh_proxy -O exit \
+ -oControlPath=$CTL >/dev/null somehost >/dev/null ; then
+ fatal "ssh process did not respond to close"
+ fi
+ n=0
+ while [ "$n" -lt 20 ] ; do
+ test -S $CTL || break
+ sleep 1
+ n=`expr $n + 1`
+ done
+ if test -S $CTL ; then
+ fatal "ssh process did not exit"
+ fi
+}
+
+trap "stop_ssh" EXIT
+
+verbose "no timeout"
+start_ssh
+sleep 5
+check_ssh || fatal "ssh unexpectedly missing"
+stop_ssh
+
+(cat $OBJ/sshd_proxy.orig ; echo "UnusedConnectionTimeout 2") > $OBJ/sshd_proxy
+
+verbose "timeout"
+start_ssh
+sleep 8
+check_ssh && fail "ssh unexpectedly present"
+stop_ssh
+
+verbose "session inhibits timeout"
+rm -f $OBJ/copy.1
+start_ssh
+${REAL_SSH} -qoControlPath=$CTL -oControlMaster=no -Fnone somehost \
+ "sleep 8; touch $OBJ/copy.1" &
+check_ssh || fail "ssh unexpectedly missing"
+wait
+test -f $OBJ/copy.1 || fail "missing result file"
+
+verbose "timeout after session"
+# Session should still be running from previous
+sleep 8
+check_ssh && fail "ssh unexpectedly present"
+stop_ssh
+
+LPORT=`expr $PORT + 1`
+RPORT=`expr $LPORT + 1`
+DPORT=`expr $RPORT + 1`
+RDPORT=`expr $DPORT + 1`
+verbose "timeout with listeners"
+start_ssh -L$LPORT:127.0.0.1:$PORT -R$RPORT:127.0.0.1:$PORT -D$DPORT -R$RDPORT
+sleep 8
+check_ssh && fail "ssh unexpectedly present"
+stop_ssh
diff --git a/regress/dhgex.sh b/regress/dhgex.sh
new file mode 100644
index 0000000..6dd4cfe
--- /dev/null
+++ b/regress/dhgex.sh
@@ -0,0 +1,61 @@
+# $OpenBSD: dhgex.sh,v 1.7 2020/12/21 22:48:41 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="dhgex"
+
+LOG=${TEST_SSH_LOGFILE}
+rm -f ${LOG}
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+
+kexs=`${SSH} -Q kex | grep diffie-hellman-group-exchange`
+
+ssh_test_dhgex()
+{
+ bits="$1"; shift
+ cipher="$1"; shift
+ kex="$1"; shift
+
+ cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+ echo "KexAlgorithms=$kex" >> $OBJ/sshd_proxy
+ echo "Ciphers=$cipher" >> $OBJ/sshd_proxy
+ rm -f ${LOG}
+ opts="-oKexAlgorithms=$kex -oCiphers=$cipher"
+ min=2048
+ max=8192
+ groupsz="$min<$bits<$max"
+ verbose "$tid bits $bits $kex $cipher"
+ ${SSH} ${opts} $@ -vvv -F ${OBJ}/ssh_proxy somehost true
+ if [ $? -ne 0 ]; then
+ fail "ssh failed ($@)"
+ fi
+ # check what we request
+ grep "SSH2_MSG_KEX_DH_GEX_REQUEST($groupsz) sent" ${LOG} >/dev/null
+ if [ $? != 0 ]; then
+ got=`egrep "SSH2_MSG_KEX_DH_GEX_REQUEST(.*) sent" ${LOG}`
+ fail "$tid unexpected GEX sizes, expected $groupsz, got $got"
+ fi
+ # check what we got.
+ gotbits="`awk 'BEGIN{FS="/"}/bits set:/{print $2}' ${LOG} |
+ head -1 | tr -d '\r\n'`"
+ trace "expected '$bits' got '$gotbits'"
+ if [ -z "$gotbits" ] || [ "$gotbits" -lt "$bits" ]; then
+ fatal "$tid expected $bits bit group, got $gotbits"
+ fi
+}
+
+check()
+{
+ bits="$1"; shift
+
+ for c in $@; do
+ for k in $kexs; do
+ ssh_test_dhgex $bits $c $k
+ done
+ done
+}
+
+check 3072 3des-cbc # 112 bits.
+check 3072 `${SSH} -Q cipher | grep 128`
+check 7680 `${SSH} -Q cipher | grep 192`
+check 8192 `${SSH} -Q cipher | grep 256`
+check 8192 chacha20-poly1305@openssh.com
diff --git a/regress/dsa_ssh2.prv b/regress/dsa_ssh2.prv
new file mode 100644
index 0000000..c93b403
--- /dev/null
+++ b/regress/dsa_ssh2.prv
@@ -0,0 +1,14 @@
+---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----
+Subject: ssh-keygen test
+Comment: "1024-bit dsa, Tue Jan 08 2002 22:00:23 +0100"
+P2/56wAAAgIAAAAmZGwtbW9kcHtzaWdue2RzYS1uaXN0LXNoYTF9LGRoe3BsYWlufX0AAA
+AEbm9uZQAAAcQAAAHAAAAAAAAABACwUfm3AxZTut3icBmwCcD48nY64HzuELlQ+vEqjIcR
+Lo49es/DQTeLNQ+kdKRCfouosGNv0WqxRtF0tUsWdXxS37oHGa4QPugBdHRd7YlZGZv8kg
+x7FsoepY7v7E683/97dv2zxL3AGagTEzWr7fl0yPexAaZoDvtQrrjX44BLmwAABACWQkvv
+MxnD8eFkS1konFfMJ1CkuRfTN34CBZ6dY7VTSGemy4QwtFdMKmoufD0eKgy3p5WOeWCYKt
+F4FhjHKZk/aaxFjjIbtkrnlvXg64QI11dSZyBN6/ViQkHPSkUDF+A6AAEhrNbQbAFSvao1
+kTvNtPCtL0AkUIduEMzGQfLCTAAAAKDeC043YVo9Zo0zAEeIA4uZh4LBCQAAA/9aj7Y5ik
+ehygJ4qTDSlVypsPuV+n59tMS0e2pfrSG87yf5r94AKBmJeho5OO6wYaXCxsVB7AFbSUD6
+75AK8mHF4v1/+7SWKk5f8xlMCMSPZ9K0+j/W1d/q2qkhnnDZolOHDomLA+U00i5ya/jnTV
+zyDPWLFpWK8u3xGBPAYX324gAAAKDHFvooRnaXdZbeWGTTqmgHB1GU9A==
+---- END SSH2 ENCRYPTED PRIVATE KEY ----
diff --git a/regress/dsa_ssh2.pub b/regress/dsa_ssh2.pub
new file mode 100644
index 0000000..215d73b
--- /dev/null
+++ b/regress/dsa_ssh2.pub
@@ -0,0 +1,13 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Subject: ssh-keygen test
+Comment: "1024-bit dsa, Tue Jan 08 2002 22:00:23 +0100"
+AAAAB3NzaC1kc3MAAACBALBR+bcDFlO63eJwGbAJwPjydjrgfO4QuVD68SqMhxEujj16z8
+NBN4s1D6R0pEJ+i6iwY2/RarFG0XS1SxZ1fFLfugcZrhA+6AF0dF3tiVkZm/ySDHsWyh6l
+ju/sTrzf/3t2/bPEvcAZqBMTNavt+XTI97EBpmgO+1CuuNfjgEubAAAAFQDeC043YVo9Zo
+0zAEeIA4uZh4LBCQAAAIEAlkJL7zMZw/HhZEtZKJxXzCdQpLkX0zd+AgWenWO1U0hnpsuE
+MLRXTCpqLnw9HioMt6eVjnlgmCrReBYYxymZP2msRY4yG7ZK55b14OuECNdXUmcgTev1Yk
+JBz0pFAxfgOgABIazW0GwBUr2qNZE7zbTwrS9AJFCHbhDMxkHywkwAAACAWo+2OYpHocoC
+eKkw0pVcqbD7lfp+fbTEtHtqX60hvO8n+a/eACgZiXoaOTjusGGlwsbFQewBW0lA+u+QCv
+JhxeL9f/u0lipOX/MZTAjEj2fStPo/1tXf6tqpIZ5w2aJThw6JiwPlNNIucmv4501c8gz1
+ixaVivLt8RgTwGF99uI=
+---- END SSH2 PUBLIC KEY ----
diff --git a/regress/dynamic-forward.sh b/regress/dynamic-forward.sh
new file mode 100644
index 0000000..5a4aa6d
--- /dev/null
+++ b/regress/dynamic-forward.sh
@@ -0,0 +1,110 @@
+# $OpenBSD: dynamic-forward.sh,v 1.15 2023/01/06 08:50:33 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="dynamic forwarding"
+
+# This is a reasonable proxy for IPv6 support.
+if ! config_defined HAVE_STRUCT_IN6_ADDR ; then
+ SKIP_IPV6=yes
+fi
+
+FWDPORT=`expr $PORT + 1`
+make_tmpdir
+CTL=${SSH_REGRESS_TMP}/ctl-sock
+cp $OBJ/ssh_config $OBJ/ssh_config.orig
+proxycmd="$OBJ/netcat -x 127.0.0.1:$FWDPORT -X"
+trace "will use ProxyCommand $proxycmd"
+
+start_ssh() {
+ direction="$1"
+ arg="$2"
+ n=0
+ error="1"
+ trace "start dynamic -$direction forwarding, fork to background"
+ (cat $OBJ/ssh_config.orig ; echo "$arg") > $OBJ/ssh_config
+ ${REAL_SSH} -vvvnNfF $OBJ/ssh_config -E$TEST_SSH_LOGFILE \
+ -$direction $FWDPORT -oExitOnForwardFailure=yes \
+ -oControlMaster=yes -oControlPath=$CTL somehost
+ r=$?
+ test $r -eq 0 || fatal "failed to start dynamic forwarding $r"
+ if ! ${REAL_SSH} -qF$OBJ/ssh_config -O check \
+ -oControlPath=$CTL somehost >/dev/null 2>&1 ; then
+ fatal "forwarding ssh process unresponsive"
+ fi
+}
+
+stop_ssh() {
+ test -S $CTL || return
+ if ! ${REAL_SSH} -qF$OBJ/ssh_config -O exit \
+ -oControlPath=$CTL >/dev/null somehost >/dev/null ; then
+ fatal "forwarding ssh process did not respond to close"
+ fi
+ n=0
+ while [ "$n" -lt 20 ] ; do
+ test -S $CTL || break
+ sleep 1
+ n=`expr $n + 1`
+ done
+ if test -S $CTL ; then
+ fatal "forwarding ssh process did not exit"
+ fi
+}
+
+check_socks() {
+ direction=$1
+ expect_success=$2
+ for s in 4 5; do
+ for h in 127.0.0.1 localhost; do
+ trace "testing ssh socks version $s host $h (-$direction)"
+ ${REAL_SSH} -q -F $OBJ/ssh_config \
+ -o "ProxyCommand ${proxycmd}${s} $h $PORT 2>/dev/null" \
+ somehost cat ${DATA} > ${COPY}
+ r=$?
+ if [ "x$expect_success" = "xY" ] ; then
+ if [ $r -ne 0 ] ; then
+ fail "ssh failed with exit status $r"
+ fi
+ test -f ${COPY} || fail "failed copy ${DATA}"
+ cmp ${DATA} ${COPY} || fail "corrupted copy of ${DATA}"
+ elif [ $r -eq 0 ] ; then
+ fail "ssh unexpectedly succeeded"
+ fi
+ done
+ done
+}
+
+start_sshd
+trap "stop_ssh" EXIT
+
+for d in D R; do
+ verbose "test -$d forwarding"
+ start_ssh $d
+ check_socks $d Y
+ stop_ssh
+ test "x$d" = "xR" || continue
+
+ # Test PermitRemoteOpen
+ verbose "PermitRemoteOpen=any"
+ start_ssh $d PermitRemoteOpen=any
+ check_socks $d Y
+ stop_ssh
+
+ verbose "PermitRemoteOpen=none"
+ start_ssh $d PermitRemoteOpen=none
+ check_socks $d N
+ stop_ssh
+
+ verbose "PermitRemoteOpen=explicit"
+ permit="127.0.0.1:$PORT [::1]:$PORT localhost:$PORT"
+ test -z "$SKIP_IPV6" || permit="127.0.0.1:$PORT localhost:$PORT"
+ start_ssh $d PermitRemoteOpen="$permit"
+ check_socks $d Y
+ stop_ssh
+
+ verbose "PermitRemoteOpen=disallowed"
+ permit="127.0.0.1:1 [::1]:1 localhost:1"
+ test -z "$SKIP_IPV6" || permit="127.0.0.1:1 localhost:1"
+ start_ssh $d PermitRemoteOpen="$permit"
+ check_socks $d N
+ stop_ssh
+done
diff --git a/regress/ed25519_openssh.prv b/regress/ed25519_openssh.prv
new file mode 100644
index 0000000..9f191b7
--- /dev/null
+++ b/regress/ed25519_openssh.prv
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDE8/0FM7Yw6xc53QpiZUQAh/LK2mEAwNDNYdSR6GIGIwAAAKC+Cfdzvgn3
+cwAAAAtzc2gtZWQyNTUxOQAAACDE8/0FM7Yw6xc53QpiZUQAh/LK2mEAwNDNYdSR6GIGIw
+AAAEBm+60DgH0WMW7Z5oyvu1dxo7MaXe5RRMWTMJCfLkHexMTz/QUztjDrFzndCmJlRACH
+8sraYQDA0M1h1JHoYgYjAAAAGWR0dWNrZXJAcXVvbGwuZHR1Y2tlci5uZXQBAgME
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/ed25519_openssh.pub b/regress/ed25519_openssh.pub
new file mode 100644
index 0000000..9103631
--- /dev/null
+++ b/regress/ed25519_openssh.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMTz/QUztjDrFzndCmJlRACH8sraYQDA0M1h1JHoYgYj
diff --git a/regress/envpass.sh b/regress/envpass.sh
new file mode 100644
index 0000000..cb10468
--- /dev/null
+++ b/regress/envpass.sh
@@ -0,0 +1,125 @@
+# $OpenBSD: envpass.sh,v 1.5 2022/06/03 04:31:54 djm Exp $
+# Placed in the Public Domain.
+
+tid="environment passing"
+
+# NB accepted env vars are in test-exec.sh (_XXX_TEST_* and _XXX_TEST)
+
+# Prepare a custom config to test for a configuration parsing bug fixed in 4.0
+cat << EOF > $OBJ/ssh_proxy_envpass
+Host test-sendenv-confparse-bug
+ SendEnv *
+EOF
+cat $OBJ/ssh_proxy >> $OBJ/ssh_proxy_envpass
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+
+trace "pass env, don't accept"
+verbose "test $tid: pass env, don't accept"
+_TEST_ENV=blah ${SSH} -oSendEnv="*" -F $OBJ/ssh_proxy_envpass otherhost \
+ sh << 'EOF'
+ test -z "$_TEST_ENV"
+EOF
+r=$?
+if [ $r -ne 0 ]; then
+ fail "environment found"
+fi
+
+trace "setenv, don't accept"
+verbose "test $tid: setenv, don't accept"
+${SSH} -oSendEnv="*" -F $OBJ/ssh_proxy_envpass -oSetEnv="_TEST_ENV=blah" \
+ otherhost \
+ sh << 'EOF'
+ test -z "$_TEST_ENV"
+EOF
+r=$?
+if [ $r -ne 0 ]; then
+ fail "environment found"
+fi
+
+trace "don't pass env, accept"
+verbose "test $tid: don't pass env, accept"
+_XXX_TEST_A=1 _XXX_TEST_B=2 ${SSH} -F $OBJ/ssh_proxy_envpass otherhost \
+ sh << 'EOF'
+ test -z "$_XXX_TEST_A" && test -z "$_XXX_TEST_B"
+EOF
+r=$?
+if [ $r -ne 0 ]; then
+ fail "environment found"
+fi
+
+trace "pass single env, accept single env"
+verbose "test $tid: pass single env, accept single env"
+_XXX_TEST=blah ${SSH} -oSendEnv="_XXX_TEST" -F $OBJ/ssh_proxy_envpass \
+ otherhost sh << 'EOF'
+ test X"$_XXX_TEST" = X"blah"
+EOF
+r=$?
+if [ $r -ne 0 ]; then
+ fail "environment not found"
+fi
+
+trace "pass multiple env, accept multiple env"
+verbose "test $tid: pass multiple env, accept multiple env"
+_XXX_TEST_A=1 _XXX_TEST_B=2 ${SSH} -oSendEnv="_XXX_TEST_*" \
+ -F $OBJ/ssh_proxy_envpass otherhost \
+ sh << 'EOF'
+ test X"$_XXX_TEST_A" = X"1" -a X"$_XXX_TEST_B" = X"2"
+EOF
+r=$?
+if [ $r -ne 0 ]; then
+ fail "environment not found"
+fi
+
+trace "setenv, accept"
+verbose "test $tid: setenv, accept"
+${SSH} -F $OBJ/ssh_proxy_envpass \
+ -oSetEnv="_XXX_TEST_A=1 _XXX_TEST_B=2" otherhost \
+ sh << 'EOF'
+ test X"$_XXX_TEST_A" = X"1" -a X"$_XXX_TEST_B" = X"2"
+EOF
+r=$?
+if [ $r -ne 0 ]; then
+ fail "environment not found"
+fi
+trace "setenv, first match wins"
+verbose "test $tid: setenv, first match wins"
+${SSH} -F $OBJ/ssh_proxy_envpass \
+ -oSetEnv="_XXX_TEST_A=1 _XXX_TEST_A=11 _XXX_TEST_B=2" otherhost \
+ sh << 'EOF'
+ test X"$_XXX_TEST_A" = X"1" -a X"$_XXX_TEST_B" = X"2"
+EOF
+r=$?
+if [ $r -ne 0 ]; then
+ fail "environment not found"
+fi
+
+trace "server setenv wins"
+verbose "test $tid: server setenv wins"
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+echo "SetEnv _XXX_TEST_A=23" >> $OBJ/sshd_proxy
+${SSH} -F $OBJ/ssh_proxy_envpass \
+ -oSetEnv="_XXX_TEST_A=1 _XXX_TEST_B=2" otherhost \
+ sh << 'EOF'
+ test X"$_XXX_TEST_A" = X"23" -a X"$_XXX_TEST_B" = X"2"
+EOF
+r=$?
+if [ $r -ne 0 ]; then
+ fail "environment not found"
+fi
+
+trace "server setenv first match wins"
+verbose "test $tid: server setenv wins"
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+echo "SetEnv _XXX_TEST_A=23 _XXX_TEST_A=42" >> $OBJ/sshd_proxy
+${SSH} -F $OBJ/ssh_proxy_envpass \
+ -oSetEnv="_XXX_TEST_A=1 _XXX_TEST_B=2" otherhost \
+ sh << 'EOF'
+ test X"$_XXX_TEST_A" = X"23" -a X"$_XXX_TEST_B" = X"2"
+EOF
+r=$?
+if [ $r -ne 0 ]; then
+ fail "environment not found"
+fi
+
+
+rm -f $OBJ/ssh_proxy_envpass
diff --git a/regress/exit-status-signal.sh b/regress/exit-status-signal.sh
new file mode 100644
index 0000000..1b3af0d
--- /dev/null
+++ b/regress/exit-status-signal.sh
@@ -0,0 +1,24 @@
+# This test performs validation that ssh client is not successive on being terminated
+
+tid="exit status on signal"
+
+# spawn client in background
+rm -f $OBJ/remote_pid
+${SSH} -F $OBJ/ssh_proxy somehost 'echo $$ >'$OBJ'/remote_pid; sleep 444' &
+ssh_pid=$!
+
+# wait for it to start
+n=20
+while [ ! -f $OBJ/remote_pid ] && [ $n -gt 0 ]; do
+ n=$(($n - 1))
+ sleep 1
+done
+
+kill $ssh_pid
+wait $ssh_pid
+exit_code=$?
+
+if [ $exit_code -eq 0 ]; then
+ fail "ssh client should fail on signal"
+fi
+
diff --git a/regress/exit-status.sh b/regress/exit-status.sh
new file mode 100644
index 0000000..aadf99f
--- /dev/null
+++ b/regress/exit-status.sh
@@ -0,0 +1,22 @@
+# $OpenBSD: exit-status.sh,v 1.8 2017/04/30 23:34:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="remote exit status"
+
+for s in 0 1 4 5 44; do
+ trace "status $s"
+ verbose "test $tid: status $s"
+ ${SSH} -F $OBJ/ssh_proxy otherhost exit $s
+ r=$?
+ if [ $r -ne $s ]; then
+ fail "exit code mismatch for: $r != $s"
+ fi
+
+ # same with early close of stdout/err
+ ${SSH} -F $OBJ/ssh_proxy -n otherhost exec \
+ sh -c \'"sleep 2; exec > /dev/null 2>&1; sleep 3; exit $s"\'
+ r=$?
+ if [ $r -ne $s ]; then
+ fail "exit code (with sleep) mismatch for: $r != $s"
+ fi
+done
diff --git a/regress/forcecommand.sh b/regress/forcecommand.sh
new file mode 100644
index 0000000..e059f1f
--- /dev/null
+++ b/regress/forcecommand.sh
@@ -0,0 +1,35 @@
+# $OpenBSD: forcecommand.sh,v 1.4 2017/04/30 23:34:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="forced command"
+
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+
+cp /dev/null $OBJ/authorized_keys_$USER
+for t in ${SSH_KEYTYPES}; do
+ printf 'command="true" ' >>$OBJ/authorized_keys_$USER
+ cat $OBJ/$t.pub >> $OBJ/authorized_keys_$USER
+done
+
+trace "forced command in key option"
+${SSH} -F $OBJ/ssh_proxy somehost false || fail "forced command in key"
+
+cp /dev/null $OBJ/authorized_keys_$USER
+for t in ${SSH_KEYTYPES}; do
+ printf 'command="false" ' >> $OBJ/authorized_keys_$USER
+ cat $OBJ/$t.pub >> $OBJ/authorized_keys_$USER
+done
+
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+echo "ForceCommand true" >> $OBJ/sshd_proxy
+
+trace "forced command in sshd_config overrides key option"
+${SSH} -F $OBJ/ssh_proxy somehost false || fail "forced command in key"
+
+cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+echo "ForceCommand false" >> $OBJ/sshd_proxy
+echo "Match User $USER" >> $OBJ/sshd_proxy
+echo " ForceCommand true" >> $OBJ/sshd_proxy
+
+trace "forced command with match"
+${SSH} -F $OBJ/ssh_proxy somehost false || fail "forced command in key"
diff --git a/regress/forward-control.sh b/regress/forward-control.sh
new file mode 100644
index 0000000..63bbdeb
--- /dev/null
+++ b/regress/forward-control.sh
@@ -0,0 +1,228 @@
+# $OpenBSD: forward-control.sh,v 1.11 2022/04/21 01:36:46 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="sshd control of local and remote forwarding"
+
+LFWD_PORT=3320
+RFWD_PORT=3321
+CTL=$OBJ/ctl-sock
+WAIT_SECONDS=20
+
+wait_for_process_to_exit() {
+ _pid=$1
+ _n=0
+ while kill -0 $_pid 2>/dev/null ; do
+ test $_n -eq 1 && trace "waiting for $_pid to exit"
+ _n=`expr $_n + 1`
+ test $_n -ge $WAIT_SECONDS && return 1
+ sleep 1
+ done
+ return 0
+}
+
+mux_cmd() {
+ ${SSH} -F $OBJ/ssh_proxy -S $CTL -O $1 host 2>&1
+}
+
+controlmaster_pid() {
+ mux_cmd check | cut -f2 -d= | cut -f1 -d')'
+}
+
+# usage: check_lfwd Y|N message
+check_lfwd() {
+ _expected=$1
+ _message=$2
+ ${SSH} -F $OBJ/ssh_proxy \
+ -L$LFWD_PORT:127.0.0.1:$PORT \
+ -o ExitOnForwardFailure=yes \
+ -MS $CTL -o ControlPersist=yes \
+ -f host true
+ mux_cmd check >/dev/null || fatal "check_lfwd ssh fail: $_message"
+ ${SSH} -F $OBJ/ssh_config -p $LFWD_PORT \
+ -oConnectionAttempts=10 host true >/dev/null 2>&1
+ _result=$?
+ _sshpid=`controlmaster_pid`
+ mux_cmd exit >/dev/null
+ wait_for_process_to_exit $_sshpid
+ if test "x$_expected" = "xY" -a $_result -ne 0 ; then
+ fail "check_lfwd failed (expecting success): $_message"
+ elif test "x$_expected" = "xN" -a $_result -eq 0 ; then
+ fail "check_lfwd succeeded (expecting failure): $_message"
+ elif test "x$_expected" != "xY" -a "x$_expected" != "xN" ; then
+ fatal "check_lfwd invalid argument \"$_expected\""
+ else
+ verbose "check_lfwd done (expecting $_expected): $_message"
+ fi
+}
+
+# usage: check_rfwd Y|N message
+check_rfwd() {
+ _expected=$1
+ _message=$2
+ ${SSH} -F $OBJ/ssh_proxy \
+ -R127.0.0.1:$RFWD_PORT:127.0.0.1:$PORT \
+ -o ExitOnForwardFailure=yes \
+ -MS $CTL -o ControlPersist=yes \
+ -f host true
+ mux_cmd check >/dev/null
+ _result=$?
+ _sshpid=`controlmaster_pid`
+ if test $_result -eq 0; then
+ ${SSH} -F $OBJ/ssh_config -p $RFWD_PORT \
+ -oConnectionAttempts=10 host true >/dev/null 2>&1
+ _result=$?
+ mux_cmd exit >/dev/null
+ wait_for_process_to_exit $_sshpid
+ fi
+ if test "x$_expected" = "xY" -a $_result -ne 0 ; then
+ fail "check_rfwd failed (expecting success): $_message"
+ elif test "x$_expected" = "xN" -a $_result -eq 0 ; then
+ fail "check_rfwd succeeded (expecting failure): $_message"
+ elif test "x$_expected" != "xY" -a "x$_expected" != "xN" ; then
+ fatal "check_rfwd invalid argument \"$_expected\""
+ else
+ verbose "check_rfwd done (expecting $_expected): $_message"
+ fi
+}
+
+start_sshd
+cp ${OBJ}/sshd_proxy ${OBJ}/sshd_proxy.bak
+cp ${OBJ}/authorized_keys_${USER} ${OBJ}/authorized_keys_${USER}.bak
+
+# Sanity check: ensure the default config allows forwarding
+check_lfwd Y "default configuration"
+check_rfwd Y "default configuration"
+
+# Usage: lperm_tests yes|local|remote|no Y|N Y|N Y|N Y|N Y|N Y|N
+lperm_tests() {
+ _tcpfwd=$1
+ _plain_lfwd=$2
+ _plain_rfwd=$3
+ _nopermit_lfwd=$4
+ _nopermit_rfwd=$5
+ _permit_lfwd=$6
+ _permit_rfwd=$7
+ _badfwd1=127.0.0.1:22
+ _badfwd2=127.0.0.2:22
+ _goodfwd=127.0.0.1:${PORT}
+ cp ${OBJ}/authorized_keys_${USER}.bak ${OBJ}/authorized_keys_${USER}
+ _prefix="AllowTcpForwarding=$_tcpfwd"
+
+ # No PermitOpen
+ ( cat ${OBJ}/sshd_proxy.bak ;
+ echo "AllowTcpForwarding $_tcpfwd" ) \
+ > ${OBJ}/sshd_proxy
+ check_lfwd $_plain_lfwd "$_prefix"
+ check_rfwd $_plain_rfwd "$_prefix"
+
+ # PermitOpen via sshd_config that doesn't match
+ ( cat ${OBJ}/sshd_proxy.bak ;
+ echo "AllowTcpForwarding $_tcpfwd" ;
+ echo "PermitOpen $_badfwd1 $_badfwd2" ) \
+ > ${OBJ}/sshd_proxy
+ check_lfwd $_nopermit_lfwd "$_prefix, !PermitOpen"
+ check_rfwd $_nopermit_rfwd "$_prefix, !PermitOpen"
+ # PermitOpen via sshd_config that does match
+ ( cat ${OBJ}/sshd_proxy.bak ;
+ echo "AllowTcpForwarding $_tcpfwd" ;
+ echo "PermitOpen $_badfwd1 $_goodfwd $_badfwd2" ) \
+ > ${OBJ}/sshd_proxy
+ check_lfwd $_plain_lfwd "$_prefix, PermitOpen"
+ check_rfwd $_plain_rfwd "$_prefix, PermitOpen"
+
+ # permitopen keys option.
+ # NB. permitopen via authorized_keys should have same
+ # success/fail as via sshd_config
+ # permitopen via authorized_keys that doesn't match
+ sed "s/^/permitopen=\"$_badfwd1\",permitopen=\"$_badfwd2\" /" \
+ < ${OBJ}/authorized_keys_${USER}.bak \
+ > ${OBJ}/authorized_keys_${USER} || fatal "sed 1 fail"
+ ( cat ${OBJ}/sshd_proxy.bak ;
+ echo "AllowTcpForwarding $_tcpfwd" ) \
+ > ${OBJ}/sshd_proxy
+ check_lfwd $_nopermit_lfwd "$_prefix, !permitopen"
+ check_rfwd $_nopermit_rfwd "$_prefix, !permitopen"
+ # permitopen via authorized_keys that does match
+ sed "s/^/permitopen=\"$_badfwd1\",permitopen=\"$_goodfwd\" /" \
+ < ${OBJ}/authorized_keys_${USER}.bak \
+ > ${OBJ}/authorized_keys_${USER} || fatal "sed 2 fail"
+ ( cat ${OBJ}/sshd_proxy.bak ;
+ echo "AllowTcpForwarding $_tcpfwd" ) \
+ > ${OBJ}/sshd_proxy
+ check_lfwd $_permit_lfwd "$_prefix, permitopen"
+ check_rfwd $_permit_rfwd "$_prefix, permitopen"
+
+ # Check port-forwarding flags in authorized_keys.
+ # These two should refuse all.
+ sed "s/^/no-port-forwarding /" \
+ < ${OBJ}/authorized_keys_${USER}.bak \
+ > ${OBJ}/authorized_keys_${USER} || fatal "sed 3 fail"
+ ( cat ${OBJ}/sshd_proxy.bak ;
+ echo "AllowTcpForwarding $_tcpfwd" ) \
+ > ${OBJ}/sshd_proxy
+ check_lfwd N "$_prefix, no-port-forwarding"
+ check_rfwd N "$_prefix, no-port-forwarding"
+ sed "s/^/restrict /" \
+ < ${OBJ}/authorized_keys_${USER}.bak \
+ > ${OBJ}/authorized_keys_${USER} || fatal "sed 4 fail"
+ ( cat ${OBJ}/sshd_proxy.bak ;
+ echo "AllowTcpForwarding $_tcpfwd" ) \
+ > ${OBJ}/sshd_proxy
+ check_lfwd N "$_prefix, restrict"
+ check_rfwd N "$_prefix, restrict"
+ # This should pass the same cases as _nopermit*
+ sed "s/^/restrict,port-forwarding /" \
+ < ${OBJ}/authorized_keys_${USER}.bak \
+ > ${OBJ}/authorized_keys_${USER} || fatal "sed 5 fail"
+ ( cat ${OBJ}/sshd_proxy.bak ;
+ echo "AllowTcpForwarding $_tcpfwd" ) \
+ > ${OBJ}/sshd_proxy
+ check_lfwd $_plain_lfwd "$_prefix, restrict,port-forwarding"
+ check_rfwd $_plain_rfwd "$_prefix, restrict,port-forwarding"
+}
+
+# permit-open none mismatch match
+# AllowTcpForwarding local remote local remote local remote
+lperm_tests yes Y Y N Y Y Y
+lperm_tests local Y N N N Y N
+lperm_tests remote N Y N Y N Y
+lperm_tests no N N N N N N
+
+# Usage: rperm_tests yes|local|remote|no Y|N Y|N Y|N Y|N Y|N Y|N
+rperm_tests() {
+ _tcpfwd=$1
+ _plain_lfwd=$2
+ _plain_rfwd=$3
+ _nopermit_lfwd=$4
+ _nopermit_rfwd=$5
+ _permit_lfwd=$6
+ _permit_rfwd=$7
+ _badfwd1=127.0.0.1:22
+ _badfwd2=127.0.0.2:${RFWD_PORT}
+ _goodfwd=127.0.0.1:${RFWD_PORT}
+ cp ${OBJ}/authorized_keys_${USER}.bak ${OBJ}/authorized_keys_${USER}
+ _prefix="AllowTcpForwarding=$_tcpfwd"
+
+ # PermitListen via sshd_config that doesn't match
+ ( cat ${OBJ}/sshd_proxy.bak ;
+ echo "AllowTcpForwarding $_tcpfwd" ;
+ echo "PermitListen $_badfwd1 $_badfwd2" ) \
+ > ${OBJ}/sshd_proxy
+ check_lfwd $_nopermit_lfwd "$_prefix, !PermitListen"
+ check_rfwd $_nopermit_rfwd "$_prefix, !PermitListen"
+ # PermitListen via sshd_config that does match
+ ( cat ${OBJ}/sshd_proxy.bak ;
+ echo "AllowTcpForwarding $_tcpfwd" ;
+ echo "PermitListen $_badfwd1 $_goodfwd $_badfwd2" ) \
+ > ${OBJ}/sshd_proxy
+ check_lfwd $_plain_lfwd "$_prefix, PermitListen"
+ check_rfwd $_plain_rfwd "$_prefix, PermitListen"
+}
+
+# permit-remote-open none mismatch match
+# AllowTcpForwarding local remote local remote local remote
+rperm_tests yes Y Y Y N Y Y
+rperm_tests local Y N Y N Y N
+rperm_tests remote N Y N N N Y
+rperm_tests no N N N N N N
+
diff --git a/regress/forwarding.sh b/regress/forwarding.sh
new file mode 100644
index 0000000..a72bd3a
--- /dev/null
+++ b/regress/forwarding.sh
@@ -0,0 +1,136 @@
+# $OpenBSD: forwarding.sh,v 1.24 2021/05/07 09:23:40 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="local and remote forwarding"
+
+DATA=/bin/ls${EXEEXT}
+
+start_sshd
+
+base=33
+last=$PORT
+fwd=""
+make_tmpdir
+CTL=${SSH_REGRESS_TMP}/ctl-sock
+
+for j in 0 1 2; do
+ for i in 0 1 2; do
+ a=$base$j$i
+ b=`expr $a + 50`
+ c=$last
+ # fwd chain: $a -> $b -> $c
+ fwd="$fwd -L$a:127.0.0.1:$b -R$b:127.0.0.1:$c"
+ last=$a
+ done
+done
+
+trace "start forwarding, fork to background"
+rm -f $CTL
+${SSH} -S $CTL -N -M -F $OBJ/ssh_config -f $fwd somehost
+
+trace "transfer over forwarded channels and check result"
+${SSH} -F $OBJ/ssh_config -p$last -o 'ConnectionAttempts=10' \
+ somehost cat ${DATA} > ${COPY}
+test -s ${COPY} || fail "failed copy of ${DATA}"
+cmp ${DATA} ${COPY} || fail "corrupted copy of ${DATA}"
+
+${SSH} -F $OBJ/ssh_config -S $CTL -O exit somehost 2>/dev/null
+
+for d in L R; do
+ trace "exit on -$d forward failure"
+
+ # this one should succeed
+ ${SSH} -F $OBJ/ssh_config \
+ -$d ${base}01:127.0.0.1:$PORT \
+ -$d ${base}02:127.0.0.1:$PORT \
+ -$d ${base}03:127.0.0.1:$PORT \
+ -$d ${base}04:127.0.0.1:$PORT \
+ -oExitOnForwardFailure=yes somehost true
+ if [ $? != 0 ]; then
+ fatal "connection failed, should not"
+ else
+ # this one should fail
+ ${SSH} -q -F $OBJ/ssh_config \
+ -$d ${base}01:127.0.0.1:$PORT \
+ -$d ${base}02:127.0.0.1:$PORT \
+ -$d ${base}03:127.0.0.1:$PORT \
+ -$d ${base}01:localhost:$PORT \
+ -$d ${base}04:127.0.0.1:$PORT \
+ -oExitOnForwardFailure=yes somehost true
+ r=$?
+ if [ $r != 255 ]; then
+ fail "connection not termintated, but should ($r)"
+ fi
+ fi
+done
+
+trace "simple clear forwarding"
+${SSH} -F $OBJ/ssh_config -oClearAllForwardings=yes somehost true
+
+trace "clear local forward"
+rm -f $CTL
+${SSH} -S $CTL -N -M -f -F $OBJ/ssh_config -L ${base}01:127.0.0.1:$PORT \
+ -oClearAllForwardings=yes somehost
+if [ $? != 0 ]; then
+ fail "connection failed with cleared local forwarding"
+else
+ # this one should fail
+ ${SSH} -F $OBJ/ssh_config -p ${base}01 somehost true \
+ >>$TEST_REGRESS_LOGFILE 2>&1 && \
+ fail "local forwarding not cleared"
+fi
+${SSH} -F $OBJ/ssh_config -S $CTL -O exit somehost 2>/dev/null
+
+trace "clear remote forward"
+rm -f $CTL
+${SSH} -S $CTL -N -M -f -F $OBJ/ssh_config -R ${base}01:127.0.0.1:$PORT \
+ -oClearAllForwardings=yes somehost
+if [ $? != 0 ]; then
+ fail "connection failed with cleared remote forwarding"
+else
+ # this one should fail
+ ${SSH} -F $OBJ/ssh_config -p ${base}01 somehost true \
+ >>$TEST_REGRESS_LOGFILE 2>&1 && \
+ fail "remote forwarding not cleared"
+fi
+${SSH} -F $OBJ/ssh_config -S $CTL -O exit somehost 2>/dev/null
+
+trace "stdio forwarding"
+cmd="${SSH} -F $OBJ/ssh_config"
+$cmd -o "ProxyCommand $cmd -q -W localhost:$PORT somehost" somehost true
+if [ $? != 0 ]; then
+ fail "stdio forwarding"
+fi
+
+echo "LocalForward ${base}01 127.0.0.1:$PORT" >> $OBJ/ssh_config
+echo "RemoteForward ${base}02 127.0.0.1:${base}01" >> $OBJ/ssh_config
+
+trace "config file: start forwarding, fork to background"
+rm -f $CTL
+${SSH} -S $CTL -N -M -F $OBJ/ssh_config -f somehost
+
+trace "config file: transfer over forwarded channels and check result"
+${SSH} -F $OBJ/ssh_config -p${base}02 -o 'ConnectionAttempts=10' \
+ somehost cat ${DATA} > ${COPY}
+test -s ${COPY} || fail "failed copy of ${DATA}"
+cmp ${DATA} ${COPY} || fail "corrupted copy of ${DATA}"
+
+${SSH} -F $OBJ/ssh_config -S $CTL -O exit somehost 2>/dev/null
+
+trace "transfer over chained unix domain socket forwards and check result"
+rm -f $OBJ/unix-[123].fwd
+rm -f $CTL $CTL.[123]
+${SSH} -S $CTL -N -M -f -F $OBJ/ssh_config -R${base}01:[$OBJ/unix-1.fwd] somehost
+${SSH} -S $CTL.1 -N -M -f -F $OBJ/ssh_config -L[$OBJ/unix-1.fwd]:[$OBJ/unix-2.fwd] somehost
+${SSH} -S $CTL.2 -N -M -f -F $OBJ/ssh_config -R[$OBJ/unix-2.fwd]:[$OBJ/unix-3.fwd] somehost
+${SSH} -S $CTL.3 -N -M -f -F $OBJ/ssh_config -L[$OBJ/unix-3.fwd]:127.0.0.1:$PORT somehost
+${SSH} -F $OBJ/ssh_config -p${base}01 -o 'ConnectionAttempts=10' \
+ somehost cat ${DATA} > ${COPY}
+test -s ${COPY} || fail "failed copy ${DATA}"
+cmp ${DATA} ${COPY} || fail "corrupted copy of ${DATA}"
+
+${SSH} -F $OBJ/ssh_config -S $CTL -O exit somehost 2>/dev/null
+${SSH} -F $OBJ/ssh_config -S $CTL.1 -O exit somehost 2>/dev/null
+${SSH} -F $OBJ/ssh_config -S $CTL.2 -O exit somehost 2>/dev/null
+${SSH} -F $OBJ/ssh_config -S $CTL.3 -O exit somehost 2>/dev/null
+
diff --git a/regress/host-expand.sh b/regress/host-expand.sh
new file mode 100644
index 0000000..9444f7f
--- /dev/null
+++ b/regress/host-expand.sh
@@ -0,0 +1,16 @@
+# $OpenBSD: host-expand.sh,v 1.5 2017/04/30 23:34:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="expand %h and %n"
+
+echo 'PermitLocalCommand yes' >> $OBJ/ssh_proxy
+printf 'LocalCommand printf "%%%%s\\n" "%%n" "%%h"\n' >> $OBJ/ssh_proxy
+
+cat >$OBJ/expect <<EOE
+somehost
+127.0.0.1
+EOE
+
+${SSH} -F $OBJ/ssh_proxy somehost true >$OBJ/actual
+diff $OBJ/expect $OBJ/actual || fail "$tid"
+
diff --git a/regress/hostbased.sh b/regress/hostbased.sh
new file mode 100644
index 0000000..eb9cf27
--- /dev/null
+++ b/regress/hostbased.sh
@@ -0,0 +1,66 @@
+# $OpenBSD: hostbased.sh,v 1.4 2022/12/07 11:45:43 dtucker Exp $
+# Placed in the Public Domain.
+
+# This test requires external setup and thus is skipped unless
+# TEST_SSH_HOSTBASED_AUTH and SUDO are set to "yes".
+# Since ssh-keysign has key paths hard coded, unlike the other tests it
+# needs to use the real host keys. It requires:
+# - ssh-keysign must be installed and setuid.
+# - "EnableSSHKeysign yes" must be in the system ssh_config.
+# - the system's own real FQDN the system-wide shosts.equiv.
+# - the system's real public key fingerprints must be in global ssh_known_hosts.
+#
+tid="hostbased"
+
+if [ -z "${TEST_SSH_HOSTBASED_AUTH}" ]; then
+ skip "TEST_SSH_HOSTBASED_AUTH not set."
+elif [ -z "${SUDO}" ]; then
+ skip "SUDO not set"
+fi
+
+# Enable all supported hostkey algos (but no others)
+hostkeyalgos=`${SSH} -Q HostKeyAlgorithms | tr '\n' , | sed 's/,$//'`
+
+cat >>$OBJ/sshd_proxy <<EOD
+HostbasedAuthentication yes
+HostbasedAcceptedAlgorithms $hostkeyalgos
+HostbasedUsesNameFromPacketOnly yes
+HostKeyAlgorithms $hostkeyalgos
+EOD
+
+cat >>$OBJ/ssh_proxy <<EOD
+HostbasedAuthentication yes
+HostKeyAlgorithms $hostkeyalgos
+HostbasedAcceptedAlgorithms $hostkeyalgos
+PreferredAuthentications hostbased
+EOD
+
+algos=""
+for key in `${SUDO} ${SSHD} -T | awk '$1=="hostkey"{print $2}'`; do
+ case "`$SSHKEYGEN -l -f ${key}.pub`" in
+ 256*ECDSA*) algos="$algos ecdsa-sha2-nistp256" ;;
+ 384*ECDSA*) algos="$algos ecdsa-sha2-nistp384" ;;
+ 521*ECDSA*) algos="$algos ecdsa-sha2-nistp521" ;;
+ *RSA*) algos="$algos ssh-rsa rsa-sha2-256 rsa-sha2-512" ;;
+ *ED25519*) algos="$algos ssh-ed25519" ;;
+ *DSA*) algos="$algos ssh-dss" ;;
+ *) verbose "unknown host key type $key" ;;
+ esac
+done
+
+for algo in $algos; do
+ trace "hostbased algo $algo"
+ opts="-F $OBJ/ssh_proxy"
+ if [ "x$algo" != "xdefault" ]; then
+ opts="$opts -oHostbasedAcceptedAlgorithms=$algo"
+ fi
+ SSH_CONNECTION=`${SSH} $opts localhost 'echo $SSH_CONNECTION'`
+ if [ $? -ne 0 ]; then
+ fail "connect failed, hostbased algo $algo"
+ elif [ "$SSH_CONNECTION" != "UNKNOWN 65535 UNKNOWN 65535" ]; then
+ fail "hostbased algo $algo bad SSH_CONNECTION" \
+ "$SSH_CONNECTION"
+ else
+ verbose "ok hostbased algo $algo"
+ fi
+done
diff --git a/regress/hostkey-agent.sh b/regress/hostkey-agent.sh
new file mode 100644
index 0000000..222d424
--- /dev/null
+++ b/regress/hostkey-agent.sh
@@ -0,0 +1,87 @@
+# $OpenBSD: hostkey-agent.sh,v 1.13 2021/09/30 05:20:08 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="hostkey agent"
+
+rm -f $OBJ/agent-key.* $OBJ/ssh_proxy.orig $OBJ/known_hosts.orig $OBJ/agent-ca*
+
+trace "start agent"
+eval `${SSHAGENT} ${EXTRA_AGENT_ARGS} -s` > /dev/null
+r=$?
+[ $r -ne 0 ] && fatal "could not start ssh-agent: exit code $r"
+
+grep -vi 'hostkey' $OBJ/sshd_proxy > $OBJ/sshd_proxy.orig
+echo "HostKeyAgent $SSH_AUTH_SOCK" >> $OBJ/sshd_proxy.orig
+
+trace "make CA key"
+
+${SSHKEYGEN} -qt ed25519 -f $OBJ/agent-ca -N '' || fatal "ssh-keygen CA"
+
+trace "load hostkeys"
+for k in $SSH_KEYTYPES ; do
+ ${SSHKEYGEN} -qt $k -f $OBJ/agent-key.$k -N '' || fatal "ssh-keygen $k"
+ ${SSHKEYGEN} -s $OBJ/agent-ca -qh -n localhost-with-alias \
+ -I localhost-with-alias $OBJ/agent-key.$k.pub || \
+ fatal "sign $k"
+ ${SSHADD} -k $OBJ/agent-key.$k >/dev/null 2>&1 || \
+ fatal "couldn't load key $OBJ/agent-key.$k"
+ # Remove private key so the server can't use it.
+ rm $OBJ/agent-key.$k || fatal "couldn't rm $OBJ/agent-key.$k"
+done
+rm $OBJ/agent-ca # Don't need CA private any more either
+
+unset SSH_AUTH_SOCK
+
+for k in $SSH_KEYTYPES ; do
+ verbose "key type $k"
+ cp $OBJ/sshd_proxy.orig $OBJ/sshd_proxy
+ echo "HostKeyAlgorithms $k" >> $OBJ/sshd_proxy
+ echo "Hostkey $OBJ/agent-key.${k}" >> $OBJ/sshd_proxy
+ opts="-oHostKeyAlgorithms=$k -F $OBJ/ssh_proxy"
+ ( printf 'localhost-with-alias,127.0.0.1,::1 ' ;
+ cat $OBJ/agent-key.$k.pub) > $OBJ/known_hosts
+ SSH_CONNECTION=`${SSH} $opts host 'echo $SSH_CONNECTION'`
+ if [ $? -ne 0 ]; then
+ fail "keytype $k failed"
+ fi
+ if [ "$SSH_CONNECTION" != "UNKNOWN 65535 UNKNOWN 65535" ]; then
+ fail "bad SSH_CONNECTION key type $k"
+ fi
+done
+
+SSH_CERTTYPES=`ssh -Q key-sig | grep 'cert-v01@openssh.com'`
+
+# Prepare sshd_proxy for certificates.
+cp $OBJ/sshd_proxy.orig $OBJ/sshd_proxy
+HOSTKEYALGS=""
+for k in $SSH_CERTTYPES ; do
+ test -z "$HOSTKEYALGS" || HOSTKEYALGS="${HOSTKEYALGS},"
+ HOSTKEYALGS="${HOSTKEYALGS}${k}"
+done
+for k in $SSH_KEYTYPES ; do
+ echo "Hostkey $OBJ/agent-key.${k}.pub" >> $OBJ/sshd_proxy
+ echo "HostCertificate $OBJ/agent-key.${k}-cert.pub" >> $OBJ/sshd_proxy
+ test -f $OBJ/agent-key.${k}.pub || fatal "no $k key"
+ test -f $OBJ/agent-key.${k}-cert.pub || fatal "no $k cert"
+done
+echo "HostKeyAlgorithms $HOSTKEYALGS" >> $OBJ/sshd_proxy
+
+# Add only CA trust anchor to known_hosts.
+( printf '@cert-authority localhost-with-alias ' ;
+ cat $OBJ/agent-ca.pub) > $OBJ/known_hosts
+
+for k in $SSH_CERTTYPES ; do
+ verbose "cert type $k"
+ opts="-oHostKeyAlgorithms=$k -F $OBJ/ssh_proxy"
+ SSH_CONNECTION=`${SSH} $opts host 'echo $SSH_CONNECTION'`
+ if [ $? -ne 0 ]; then
+ fail "cert type $k failed"
+ fi
+ if [ "$SSH_CONNECTION" != "UNKNOWN 65535 UNKNOWN 65535" ]; then
+ fail "bad SSH_CONNECTION key type $k"
+ fi
+done
+
+trace "kill agent"
+${SSHAGENT} -k > /dev/null
+
diff --git a/regress/hostkey-rotate.sh b/regress/hostkey-rotate.sh
new file mode 100644
index 0000000..5898cbd
--- /dev/null
+++ b/regress/hostkey-rotate.sh
@@ -0,0 +1,152 @@
+# $OpenBSD: hostkey-rotate.sh,v 1.10 2022/01/05 08:25:05 djm Exp $
+# Placed in the Public Domain.
+
+tid="hostkey rotate"
+
+#
+# GNU (f)grep <=2.18, as shipped by FreeBSD<=12 and NetBSD<=9 will occasionally
+# fail to find ssh host keys in the hostkey-rotate test. If we have those
+# versions, use awk instead.
+# See # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=258616
+#
+case `grep --version 2>&1 | awk '/GNU grep/{print $4}'` in
+2.19) fgrep=good ;;
+1.*|2.?|2.?.?|2.1?) fgrep=bad ;; # stock GNU grep
+2.5.1*) fgrep=bad ;; # FreeBSD and NetBSD
+*) fgrep=good ;;
+esac
+if test "x$fgrep" = "xbad"; then
+ fgrep()
+{
+ awk 'BEGIN{e=1} {if (index($0,"'$1'")>0){e=0;print}} END{exit e}' $2
+}
+fi
+
+rm -f $OBJ/hkr.* $OBJ/ssh_proxy.orig $OBJ/ssh_proxy.orig
+
+grep -vi 'hostkey' $OBJ/sshd_proxy > $OBJ/sshd_proxy.orig
+mv $OBJ/ssh_proxy $OBJ/ssh_proxy.orig
+grep -vi 'globalknownhostsfile' $OBJ/ssh_proxy.orig > $OBJ/ssh_proxy
+echo "UpdateHostkeys=yes" >> $OBJ/ssh_proxy
+echo "GlobalKnownHostsFile=none" >> $OBJ/ssh_proxy
+rm $OBJ/known_hosts
+
+# The "primary" key type is ed25519 since it's supported even when built
+# without OpenSSL. The secondary is RSA if it's supported.
+primary="ssh-ed25519"
+secondary="$primary"
+
+trace "prepare hostkeys"
+nkeys=0
+all_algs=""
+for k in $SSH_HOSTKEY_TYPES; do
+ ${SSHKEYGEN} -qt $k -f $OBJ/hkr.$k -N '' || fatal "ssh-keygen $k"
+ echo "Hostkey $OBJ/hkr.${k}" >> $OBJ/sshd_proxy.orig
+ nkeys=`expr $nkeys + 1`
+ test "x$all_algs" = "x" || all_algs="${all_algs},"
+ case "$k" in
+ ssh-rsa)
+ secondary="ssh-rsa"
+ all_algs="${all_algs}rsa-sha2-256,rsa-sha2-512,$k"
+ ;;
+ *)
+ all_algs="${all_algs}$k"
+ ;;
+ esac
+done
+
+dossh() {
+ # All ssh should succeed in this test
+ ${SSH} -F $OBJ/ssh_proxy "$@" x true || fail "ssh $@ failed"
+}
+
+expect_nkeys() {
+ _expected=$1
+ _message=$2
+ _n=`wc -l $OBJ/known_hosts | awk '{ print $1 }'` || fatal "wc failed"
+ [ "x$_n" = "x$_expected" ] || fail "$_message (got $_n wanted $_expected)"
+}
+
+check_key_present() {
+ _type=$1
+ _kfile=$2
+ test "x$_kfile" = "x" && _kfile="$OBJ/hkr.${_type}.pub"
+ _kpub=`awk "/$_type /"' { print $2 }' < $_kfile` || \
+ fatal "awk failed"
+ fgrep "$_kpub" $OBJ/known_hosts > /dev/null
+}
+
+cp $OBJ/sshd_proxy.orig $OBJ/sshd_proxy
+
+# Connect to sshd with StrictHostkeyChecking=no
+verbose "learn hostkey with StrictHostKeyChecking=no"
+>$OBJ/known_hosts
+dossh -oHostKeyAlgorithms=$primary -oStrictHostKeyChecking=no
+# Verify no additional keys learned
+expect_nkeys 1 "unstrict connect keys"
+check_key_present $primary || fail "unstrict didn't learn key"
+
+# Connect to sshd as usual
+verbose "learn additional hostkeys"
+dossh -oStrictHostKeyChecking=yes -oHostKeyAlgorithms=$all_algs
+# Check that other keys learned
+expect_nkeys $nkeys "learn hostkeys"
+for k in $SSH_HOSTKEY_TYPES; do
+ check_key_present $k || fail "didn't learn keytype $k"
+done
+
+# Check each key type
+for k in $SSH_HOSTKEY_TYPES; do
+ case "$k" in
+ ssh-rsa) alg="rsa-sha2-256,rsa-sha2-512,ssh-rsa" ;;
+ *) alg="$k" ;;
+ esac
+ verbose "learn additional hostkeys, type=$k"
+ dossh -oStrictHostKeyChecking=yes -oHostKeyAlgorithms=$alg,$all_algs
+ expect_nkeys $nkeys "learn hostkeys $k"
+ check_key_present $k || fail "didn't learn $k correctly"
+done
+
+# Change one hostkey (non primary) and relearn
+if [ "$primary" != "$secondary" ]; then
+ verbose "learn changed non-primary hostkey type=${secondary}"
+ mv $OBJ/hkr.${secondary}.pub $OBJ/hkr.${secondary}.pub.old
+ rm -f $OBJ/hkr.${secondary}
+ ${SSHKEYGEN} -qt ${secondary} -f $OBJ/hkr.${secondary} -N '' || \
+ fatal "ssh-keygen $secondary"
+ dossh -oStrictHostKeyChecking=yes -oHostKeyAlgorithms=$all_algs
+ # Check that the key was replaced
+ expect_nkeys $nkeys "learn hostkeys"
+ check_key_present ${secondary} $OBJ/hkr.${secondary}.pub.old && \
+ fail "old key present"
+ check_key_present ${secondary} || fail "didn't learn changed key"
+fi
+
+# Add new hostkey (primary type) to sshd and connect
+verbose "learn new primary hostkey"
+${SSHKEYGEN} -qt ${primary} -f $OBJ/hkr.${primary}-new -N '' || fatal "ssh-keygen ed25519"
+( cat $OBJ/sshd_proxy.orig ; echo HostKey $OBJ/hkr.${primary}-new ) \
+ > $OBJ/sshd_proxy
+# Check new hostkey added
+dossh -oStrictHostKeyChecking=yes -oHostKeyAlgorithms=${primary},$all_algs
+expect_nkeys `expr $nkeys + 1` "learn hostkeys"
+check_key_present ${primary} || fail "current key missing"
+check_key_present ${primary} $OBJ/hkr.${primary}-new.pub || fail "new key missing"
+
+# Remove old hostkey (primary type) from sshd
+verbose "rotate primary hostkey"
+cp $OBJ/sshd_proxy.orig $OBJ/sshd_proxy
+mv $OBJ/hkr.${primary}.pub $OBJ/hkr.${primary}.pub.old
+mv $OBJ/hkr.${primary}-new.pub $OBJ/hkr.${primary}.pub
+mv $OBJ/hkr.${primary}-new $OBJ/hkr.${primary}
+# Check old hostkey removed
+dossh -oStrictHostKeyChecking=yes -oHostKeyAlgorithms=${primary},$all_algs
+expect_nkeys $nkeys "learn hostkeys"
+check_key_present ${primary} $OBJ/hkr.${primary}.pub.old && fail "old key present"
+check_key_present ${primary} || fail "didn't learn changed key"
+
+# Connect again, forcing rotated key
+verbose "check rotate primary hostkey"
+dossh -oStrictHostKeyChecking=yes -oHostKeyAlgorithms=${primary}
+expect_nkeys 1 "learn hostkeys"
+check_key_present ${primary} || fail "didn't learn changed key"
diff --git a/regress/integrity.sh b/regress/integrity.sh
new file mode 100644
index 0000000..bc030cb
--- /dev/null
+++ b/regress/integrity.sh
@@ -0,0 +1,76 @@
+# $OpenBSD: integrity.sh,v 1.24 2020/01/21 08:06:27 djm Exp $
+# Placed in the Public Domain.
+
+tid="integrity"
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+
+# start at byte 2900 (i.e. after kex) and corrupt at different offsets
+tries=10
+startoffset=2900
+macs=`${SSH} -Q mac`
+# The following are not MACs, but ciphers with integrated integrity. They are
+# handled specially below.
+macs="$macs `${SSH} -Q cipher-auth`"
+
+# avoid DH group exchange as the extra traffic makes it harder to get the
+# offset into the stream right.
+#echo "KexAlgorithms -diffie-hellman-group*" \
+# >> $OBJ/ssh_proxy
+
+# sshd-command for proxy (see test-exec.sh)
+cmd="$SUDO env SSH_SK_HELPER="$SSH_SK_HELPER" sh ${SRC}/sshd-log-wrapper.sh ${TEST_SSHD_LOGFILE} ${SSHD} -i -f $OBJ/sshd_proxy"
+
+for m in $macs; do
+ trace "test $tid: mac $m"
+ elen=0
+ epad=0
+ emac=0
+ etmo=0
+ ecnt=0
+ skip=0
+ for off in `jot $tries $startoffset`; do
+ skip=`expr $skip - 1`
+ if [ $skip -gt 0 ]; then
+ # avoid modifying the high bytes of the length
+ continue
+ fi
+ cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+ # modify output from sshd at offset $off
+ pxy="proxycommand=$cmd | $OBJ/modpipe -wm xor:$off:1"
+ if ${SSH} -Q cipher-auth | grep "^${m}\$" >/dev/null 2>&1 ; then
+ echo "Ciphers=$m" >> $OBJ/sshd_proxy
+ macopt="-c $m"
+ else
+ echo "Ciphers=aes128-ctr" >> $OBJ/sshd_proxy
+ echo "MACs=$m" >> $OBJ/sshd_proxy
+ macopt="-m $m -c aes128-ctr"
+ fi
+ verbose "test $tid: $m @$off"
+ ${SSH} $macopt -F $OBJ/ssh_proxy -o "$pxy" \
+ -oServerAliveInterval=1 -oServerAliveCountMax=30 \
+ 999.999.999.999 'printf "%4096s" " "' >/dev/null
+ if [ $? -eq 0 ]; then
+ fail "ssh -m $m succeeds with bit-flip at $off"
+ fi
+ ecnt=`expr $ecnt + 1`
+ out=$(egrep -v "^debug" $TEST_SSH_LOGFILE | tail -2 | \
+ tr -s '\r\n' '.')
+ case "$out" in
+ Bad?packet*) elen=`expr $elen + 1`; skip=3;;
+ Corrupted?MAC* | *message?authentication?code?incorrect*)
+ emac=`expr $emac + 1`; skip=0;;
+ padding*) epad=`expr $epad + 1`; skip=0;;
+ *Timeout,?server*)
+ etmo=`expr $etmo + 1`; skip=0;;
+ *) fail "unexpected error mac $m at $off: $out";;
+ esac
+ done
+ verbose "test $tid: $ecnt errors: mac $emac padding $epad length $elen timeout $etmo"
+ if [ $emac -eq 0 ]; then
+ fail "$m: no mac errors"
+ fi
+ expect=`expr $ecnt - $epad - $elen - $etmo`
+ if [ $emac -ne $expect ]; then
+ fail "$m: expected $expect mac errors, got $emac"
+ fi
+done
diff --git a/regress/kextype.sh b/regress/kextype.sh
new file mode 100644
index 0000000..e271899
--- /dev/null
+++ b/regress/kextype.sh
@@ -0,0 +1,25 @@
+# $OpenBSD: kextype.sh,v 1.6 2015/03/24 20:19:15 markus Exp $
+# Placed in the Public Domain.
+
+tid="login with different key exchange algorithms"
+
+TIME=/usr/bin/time
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+cp $OBJ/ssh_proxy $OBJ/ssh_proxy_bak
+
+# Make server accept all key exchanges.
+ALLKEX=`${SSH} -Q kex`
+KEXOPT=`echo $ALLKEX | tr ' ' ,`
+echo "KexAlgorithms=$KEXOPT" >> $OBJ/sshd_proxy
+
+tries="1 2 3 4"
+for k in `${SSH} -Q kex`; do
+ verbose "kex $k"
+ for i in $tries; do
+ ${SSH} -F $OBJ/ssh_proxy -o KexAlgorithms=$k x true
+ if [ $? -ne 0 ]; then
+ fail "ssh kex $k"
+ fi
+ done
+done
+
diff --git a/regress/key-options.sh b/regress/key-options.sh
new file mode 100644
index 0000000..2f3d66e
--- /dev/null
+++ b/regress/key-options.sh
@@ -0,0 +1,124 @@
+# $OpenBSD: key-options.sh,v 1.9 2018/07/03 13:53:26 djm Exp $
+# Placed in the Public Domain.
+
+tid="key options"
+
+origkeys="$OBJ/authkeys_orig"
+authkeys="$OBJ/authorized_keys_${USER}"
+cp $authkeys $origkeys
+
+# Allocating ptys can require privileges on some platforms.
+skip_pty=""
+if ! config_defined HAVE_OPENPTY && [ "x$SUDO" = "x" ]; then
+ skip_pty="no openpty(3) and SUDO not set"
+fi
+
+# Test command= forced command
+for c in 'command="echo bar"' 'no-pty,command="echo bar"'; do
+ sed "s/.*/$c &/" $origkeys >$authkeys
+ verbose "key option $c"
+ r=`${SSH} -q -F $OBJ/ssh_proxy somehost echo foo`
+ if [ "$r" = "foo" ]; then
+ fail "key option forced command not restricted"
+ fi
+ if [ "$r" != "bar" ]; then
+ fail "key option forced command not executed"
+ fi
+done
+
+# Test no-pty
+expect_pty_succeed() {
+ which=$1
+ opts=$2
+ rm -f $OBJ/data
+ sed "s/.*/$opts &/" $origkeys >$authkeys
+ verbose "key option pty $which"
+ [ "x$skip_pty" != "x" ] && verbose "skipped because $skip_pty" && return
+ ${SSH} -ttq -F $OBJ/ssh_proxy somehost "tty > $OBJ/data; exit 0"
+ if [ $? -ne 0 ] ; then
+ fail "key option failed $which"
+ else
+ r=`cat $OBJ/data`
+ case "$r" in
+ /dev/*) ;;
+ *) fail "key option failed $which (pty $r)" ;;
+ esac
+ fi
+}
+expect_pty_fail() {
+ which=$1
+ opts=$2
+ rm -f $OBJ/data
+ sed "s/.*/$opts &/" $origkeys >$authkeys
+ verbose "key option pty $which"
+ [ "x$skip_pty" != "x" ] && verbose "skipped because $skip_pty" && return
+ ${SSH} -ttq -F $OBJ/ssh_proxy somehost "tty > $OBJ/data; exit 0"
+ if [ $? -eq 0 ]; then
+ r=`cat $OBJ/data`
+ if [ -e "$r" ]; then
+ fail "key option failed $which (pty $r)"
+ fi
+ case "$r" in
+ /dev/*) fail "key option failed $which (pty $r)" ;;
+ *) ;;
+ esac
+ fi
+}
+# First ensure that we can allocate a pty by default.
+expect_pty_succeed "default" ""
+expect_pty_fail "no-pty" "no-pty"
+expect_pty_fail "restrict" "restrict"
+expect_pty_succeed "restrict,pty" "restrict,pty"
+
+# Test environment=
+# XXX this can fail if ~/.ssh/environment exists for the user running the test
+echo 'PermitUserEnvironment yes' >> $OBJ/sshd_proxy
+sed 's/.*/environment="FOO=bar" &/' $origkeys >$authkeys
+verbose "key option environment"
+r=`${SSH} -q -F $OBJ/ssh_proxy somehost 'echo $FOO'`
+if [ "$r" != "bar" ]; then
+ fail "key option environment not set"
+fi
+
+# Test from= restriction
+start_sshd
+for f in 127.0.0.1 '127.0.0.0\/8'; do
+ cat $origkeys >$authkeys
+ ${SSH} -q -F $OBJ/ssh_proxy somehost true
+ if [ $? -ne 0 ]; then
+ fail "key option failed without restriction"
+ fi
+
+ sed 's/.*/from="'"$f"'" &/' $origkeys >$authkeys
+ from=`head -1 $authkeys | cut -f1 -d ' '`
+ verbose "key option $from"
+ r=`${SSH} -q -F $OBJ/ssh_proxy somehost 'echo true'`
+ if [ "$r" = "true" ]; then
+ fail "key option $from not restricted"
+ fi
+
+ r=`${SSH} -q -F $OBJ/ssh_config somehost 'echo true'`
+ if [ "$r" != "true" ]; then
+ fail "key option $from not allowed but should be"
+ fi
+done
+
+check_valid_before() {
+ which=$1
+ opts=$2
+ expect=$3
+ sed "s/.*/$opts &/" $origkeys >$authkeys
+ verbose "key option expiry-time $which"
+ ${SSH} -q -F $OBJ/ssh_proxy somehost true
+ r=$?
+ case "$expect" in
+ fail) test $r -eq 0 && fail "key option succeeded $which" ;;
+ pass) test $r -ne 0 && fail "key option failed $which" ;;
+ *) fatal "unknown expectation $expect" ;;
+ esac
+}
+check_valid_before "default" "" "pass"
+check_valid_before "invalid" 'expiry-time="INVALID"' "fail"
+check_valid_before "expired" 'expiry-time="19990101"' "fail"
+check_valid_before "valid" 'expiry-time="20380101"' "pass"
+
diff --git a/regress/keygen-change.sh b/regress/keygen-change.sh
new file mode 100644
index 0000000..3863e33
--- /dev/null
+++ b/regress/keygen-change.sh
@@ -0,0 +1,22 @@
+# $OpenBSD: keygen-change.sh,v 1.9 2019/12/16 02:39:05 djm Exp $
+# Placed in the Public Domain.
+
+tid="change passphrase for key"
+
+S1="secret1"
+S2="2secret"
+
+for t in $SSH_KEYTYPES; do
+ trace "generating $t key"
+ rm -f $OBJ/$t-key
+ ${SSHKEYGEN} -q -N ${S1} -t $t -f $OBJ/$t-key
+ if [ $? -eq 0 ]; then
+ ${SSHKEYGEN} -p -P ${S1} -N ${S2} -f $OBJ/$t-key > /dev/null
+ if [ $? -ne 0 ]; then
+ fail "ssh-keygen -p failed for $t-key"
+ fi
+ else
+ fail "ssh-keygen for $t-key failed"
+ fi
+ rm -f $OBJ/$t-key $OBJ/$t-key.pub
+done
diff --git a/regress/keygen-comment.sh b/regress/keygen-comment.sh
new file mode 100644
index 0000000..af571d3
--- /dev/null
+++ b/regress/keygen-comment.sh
@@ -0,0 +1,52 @@
+#    Placed in the Public Domain.
+
+tid="Comment extraction from private key"
+
+S1="secret1"
+
+check_fingerprint () {
+ file="$1"
+ comment="$2"
+ trace "fingerprinting $file"
+ if ! ${SSHKEYGEN} -l -E sha256 -f $file > $OBJ/$t-fgp ; then
+ fail "ssh-keygen -l failed for $t-key"
+ fi
+ if ! egrep "^([0-9]+) SHA256:(.){43} ${comment} \(.*\)\$" \
+ $OBJ/$t-fgp >/dev/null 2>&1 ; then
+ fail "comment is not correctly recovered for $t-key"
+ fi
+ rm -f $OBJ/$t-fgp
+}
+
+for fmt in '' RFC4716 PKCS8 PEM; do
+ for t in $SSH_KEYTYPES; do
+ trace "generating $t key in '$fmt' format"
+ rm -f $OBJ/$t-key*
+ oldfmt=""
+ case "$fmt" in
+ PKCS8|PEM) oldfmt=1 ;;
+ esac
+ # Some key types like ssh-ed25519 and *@openssh.com are never
+ # stored in old formats.
+ case "$t" in
+ ssh-ed25519|*openssh.com) test -z "$oldfmt" || continue ;;
+ esac
+ comment="foo bar"
+ fmtarg=""
+ test -z "$fmt" || fmtarg="-m $fmt"
+ ${SSHKEYGEN} $fmtarg -N '' -C "${comment}" \
+ -t $t -f $OBJ/$t-key >/dev/null 2>&1 || \
+ fatal "keygen of $t in format $fmt failed"
+ check_fingerprint $OBJ/$t-key "${comment}"
+ check_fingerprint $OBJ/$t-key.pub "${comment}"
+ # Output fingerprint using only private file
+ trace "fingerprinting $t key using private key file"
+ rm -f $OBJ/$t-key.pub
+ if [ ! -z "$oldfmt" ] ; then
+ # Comment cannot be recovered from old format keys.
+ comment="no comment"
+ fi
+ check_fingerprint $OBJ/$t-key "${comment}"
+ rm -f $OBJ/$t-key*
+ done
+done
diff --git a/regress/keygen-convert.sh b/regress/keygen-convert.sh
new file mode 100644
index 0000000..9565658
--- /dev/null
+++ b/regress/keygen-convert.sh
@@ -0,0 +1,55 @@
+# $OpenBSD: keygen-convert.sh,v 1.6 2021/07/24 02:57:28 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="convert keys"
+
+cat > $OBJ/askpass <<EOD
+#!/bin/sh
+echo hunter2
+EOD
+chmod u+x $OBJ/askpass
+
+if ${SSHKEYGEN} -? 2>&1 | grep "ssh-keygen -e" >/dev/null; then
+ test_import_export=1
+fi
+
+for t in ${SSH_KEYTYPES}; do
+ # generate user key for agent
+ trace "generating $t key"
+ rm -f $OBJ/$t-key
+ ${SSHKEYGEN} -q -N "" -t $t -f $OBJ/$t-key
+
+ if test "x$test_import_export" = "x1"; then
+ trace "export $t private to rfc4716 public"
+ ${SSHKEYGEN} -q -e -f $OBJ/$t-key >$OBJ/$t-key-rfc || \
+ fail "export $t private to rfc4716 public"
+
+ trace "export $t public to rfc4716 public"
+ ${SSHKEYGEN} -q -e -f $OBJ/$t-key.pub >$OBJ/$t-key-rfc.pub || \
+ fail "$t public to rfc4716 public"
+
+ cmp $OBJ/$t-key-rfc $OBJ/$t-key-rfc.pub || \
+ fail "$t rfc4716 exports differ between public and private"
+
+ trace "import $t rfc4716 public"
+ ${SSHKEYGEN} -q -i -f $OBJ/$t-key-rfc >$OBJ/$t-rfc-imported || \
+ fail "$t import rfc4716 public"
+
+ cut -f1,2 -d " " $OBJ/$t-key.pub >$OBJ/$t-key-nocomment.pub
+ cmp $OBJ/$t-key-nocomment.pub $OBJ/$t-rfc-imported || \
+ fail "$t imported differs from original"
+ fi
+
+ trace "set passphrase $t"
+ ${SSHKEYGEN} -q -p -P '' -N 'hunter2' -f $OBJ/$t-key >/dev/null || \
+ fail "$t set passphrase failed"
+
+ trace "export $t to public with passphrase"
+ SSH_ASKPASS=$OBJ/askpass SSH_ASKPASS_REQUIRE=force \
+ ${SSHKEYGEN} -y -f $OBJ/$t-key >$OBJ/$t-key-nocomment.pub
+ cmp $OBJ/$t-key.pub $OBJ/$t-key-nocomment.pub || \
+ fail "$t exported pubkey differs from generated"
+
+ rm -f $OBJ/$t-key $OBJ/$t-key.pub $OBJ/$t-key-rfc $OBJ/$t-key-rfc.pub \
+ $OBJ/$t-rfc-imported $OBJ/$t-key-nocomment.pub
+done
diff --git a/regress/keygen-knownhosts.sh b/regress/keygen-knownhosts.sh
new file mode 100644
index 0000000..37af347
--- /dev/null
+++ b/regress/keygen-knownhosts.sh
@@ -0,0 +1,220 @@
+# $OpenBSD: keygen-knownhosts.sh,v 1.4 2018/06/01 03:52:37 djm Exp $
+# Placed in the Public Domain.
+
+tid="ssh-keygen known_hosts"
+
+rm -f $OBJ/kh.*
+
+# Generate some keys for testing (just ed25519 for speed) and make a hosts file.
+for x in host-a host-b host-c host-d host-e host-f host-a2 host-b2; do
+ ${SSHKEYGEN} -qt ed25519 -f $OBJ/kh.$x -C "$x" -N "" || \
+ fatal "ssh-keygen failed"
+ # Add a comment that we expect should be preserved.
+ echo "# $x" >> $OBJ/kh.hosts
+ (
+ case "$x" in
+ host-a|host-b) printf "$x " ;;
+ host-c) printf "@cert-authority $x " ;;
+ host-d) printf "@revoked $x " ;;
+ host-e) printf "host-e* " ;;
+ host-f) printf "host-f,host-g,host-h " ;;
+ host-a2) printf "host-a " ;;
+ host-b2) printf "host-b " ;;
+ esac
+ cat $OBJ/kh.${x}.pub
+ # Blank line should be preserved.
+ echo "" >> $OBJ/kh.hosts
+ ) >> $OBJ/kh.hosts
+done
+
+# Generate a variant with an invalid line. We'll use this for most tests,
+# because keygen should be able to cope and it should be preserved in any
+# output file.
+cat $OBJ/kh.hosts >> $OBJ/kh.invalid
+echo "host-i " >> $OBJ/kh.invalid
+
+cp $OBJ/kh.invalid $OBJ/kh.invalid.orig
+cp $OBJ/kh.hosts $OBJ/kh.hosts.orig
+
+expect_key() {
+ _host=$1
+ _hosts=$2
+ _key=$3
+ _line=$4
+ _mark=$5
+ _marker=""
+ test "x$_mark" = "xCA" && _marker="@cert-authority "
+ test "x$_mark" = "xREVOKED" && _marker="@revoked "
+ test "x$_line" != "x" &&
+ echo "# Host $_host found: line $_line $_mark" >> $OBJ/kh.expect
+ printf "${_marker}$_hosts " >> $OBJ/kh.expect
+ cat $OBJ/kh.${_key}.pub >> $OBJ/kh.expect ||
+ fatal "${_key}.pub missing"
+}
+
+check_find() {
+ _host=$1
+ _name=$2
+ shift; shift
+ ${SSHKEYGEN} "$@" -f $OBJ/kh.invalid -F $_host > $OBJ/kh.result
+ if ! diff -w $OBJ/kh.expect $OBJ/kh.result ; then
+ fail "didn't find $_name"
+ fi
+}
+
+check_find_exit_code() {
+ _host=$1
+ _name=$2
+ _keygenopt=$3
+ _exp_exit_code=$4
+ ${SSHKEYGEN} $_keygenopt -f $OBJ/kh.invalid -F $_host > /dev/null
+ if [ "$?" != "$_exp_exit_code" ] ; then
+ fail "Unexpected exit code $_name"
+ fi
+}
+
+# Find key
+rm -f $OBJ/kh.expect
+expect_key host-a host-a host-a 2
+expect_key host-a host-a host-a2 20
+check_find host-a "simple find"
+
+# find CA key
+rm -f $OBJ/kh.expect
+expect_key host-c host-c host-c 8 CA
+check_find host-c "find CA key"
+
+# find revoked key
+rm -f $OBJ/kh.expect
+expect_key host-d host-d host-d 11 REVOKED
+check_find host-d "find revoked key"
+
+# find key with wildcard
+rm -f $OBJ/kh.expect
+expect_key host-e.somedomain "host-e*" host-e 14
+check_find host-e.somedomain "find wildcard key"
+
+# find key among multiple hosts
+rm -f $OBJ/kh.expect
+expect_key host-h "host-f,host-g,host-h " host-f 17
+check_find host-h "find multiple hosts"
+
+# Check exit code, known host
+check_find_exit_code host-a "known host" "-q" "0"
+
+# Check exit code, unknown host
+check_find_exit_code host-aa "unknown host" "-q" "1"
+
+# Check exit code, the hash mode, known host
+check_find_exit_code host-a "known host" "-q -H" "0"
+
+# Check exit code, the hash mode, unknown host
+check_find_exit_code host-aa "unknown host" "-q -H" "1"
+
+check_hashed_find() {
+ _host=$1
+ _name=$2
+ _file=$3
+ test "x$_file" = "x" && _file=$OBJ/kh.invalid
+ ${SSHKEYGEN} -f $_file -HF $_host | grep '|1|' | \
+ sed "s/^[^ ]*/$_host/" > $OBJ/kh.result
+ if ! diff -w $OBJ/kh.expect $OBJ/kh.result ; then
+ fail "didn't find $_name"
+ fi
+}
+
+# Find key and hash
+rm -f $OBJ/kh.expect
+expect_key host-a host-a host-a
+expect_key host-a host-a host-a2
+check_hashed_find host-a "find simple and hash"
+
+# Find CA key and hash
+rm -f $OBJ/kh.expect
+expect_key host-c host-c host-c "" CA
+# CA key output is not hashed.
+check_find host-c "find simple and hash" -Hq
+
+# Find revoked key and hash
+rm -f $OBJ/kh.expect
+expect_key host-d host-d host-d "" REVOKED
+# Revoked key output is not hashed.
+check_find host-d "find simple and hash" -Hq
+
+# find key with wildcard and hash
+rm -f $OBJ/kh.expect
+expect_key host-e "host-e*" host-e ""
+# Key with wildcard hostname should not be hashed.
+check_find host-e "find wildcard key" -Hq
+
+# find key among multiple hosts
+rm -f $OBJ/kh.expect
+# Comma-separated hostnames should be expanded and hashed.
+expect_key host-f "host-h " host-f
+expect_key host-g "host-h " host-f
+expect_key host-h "host-h " host-f
+check_hashed_find host-h "find multiple hosts"
+
+# Attempt remove key on invalid file.
+cp $OBJ/kh.invalid.orig $OBJ/kh.invalid
+${SSHKEYGEN} -qf $OBJ/kh.invalid -R host-a 2>/dev/null
+diff $OBJ/kh.invalid $OBJ/kh.invalid.orig || fail "remove on invalid succeeded"
+
+# Remove key
+cp $OBJ/kh.hosts.orig $OBJ/kh.hosts
+${SSHKEYGEN} -qf $OBJ/kh.hosts -R host-a 2>/dev/null
+grep -v "^host-a " $OBJ/kh.hosts.orig > $OBJ/kh.expect
+diff $OBJ/kh.hosts $OBJ/kh.expect || fail "remove simple"
+
+# Remove CA key
+cp $OBJ/kh.hosts.orig $OBJ/kh.hosts
+${SSHKEYGEN} -qf $OBJ/kh.hosts -R host-c 2>/dev/null
+# CA key should not be removed.
+diff $OBJ/kh.hosts $OBJ/kh.hosts.orig || fail "remove CA"
+
+# Remove revoked key
+cp $OBJ/kh.hosts.orig $OBJ/kh.hosts
+${SSHKEYGEN} -qf $OBJ/kh.hosts -R host-d 2>/dev/null
+# revoked key should not be removed.
+diff $OBJ/kh.hosts $OBJ/kh.hosts.orig || fail "remove revoked"
+
+# Remove wildcard
+cp $OBJ/kh.hosts.orig $OBJ/kh.hosts
+${SSHKEYGEN} -qf $OBJ/kh.hosts -R host-e.blahblah 2>/dev/null
+grep -v "^host-e[*] " $OBJ/kh.hosts.orig > $OBJ/kh.expect
+diff $OBJ/kh.hosts $OBJ/kh.expect || fail "remove wildcard"
+
+# Remove multiple
+cp $OBJ/kh.hosts.orig $OBJ/kh.hosts
+${SSHKEYGEN} -qf $OBJ/kh.hosts -R host-h 2>/dev/null
+grep -v "^host-f," $OBJ/kh.hosts.orig > $OBJ/kh.expect
+diff $OBJ/kh.hosts $OBJ/kh.expect || fail "remove wildcard"
+
+# Attempt hash on invalid file
+cp $OBJ/kh.invalid.orig $OBJ/kh.invalid
+${SSHKEYGEN} -qf $OBJ/kh.invalid -H 2>/dev/null && fail "hash invalid succeeded"
+diff $OBJ/kh.invalid $OBJ/kh.invalid.orig || fail "invalid file modified"
+
+# Hash valid file
+cp $OBJ/kh.hosts.orig $OBJ/kh.hosts
+${SSHKEYGEN} -qf $OBJ/kh.hosts -H 2>/dev/null || fail "hash failed"
+diff $OBJ/kh.hosts.old $OBJ/kh.hosts.orig || fail "backup differs"
+grep "^host-[abfgh]" $OBJ/kh.hosts && fail "original hostnames persist"
+
+cp $OBJ/kh.hosts $OBJ/kh.hashed.orig
+
+# Test lookup
+rm -f $OBJ/kh.expect
+expect_key host-a host-a host-a
+expect_key host-a host-a host-a2
+check_hashed_find host-a "find simple in hashed" $OBJ/kh.hosts
+
+# Test multiple expanded
+rm -f $OBJ/kh.expect
+expect_key host-h host-h host-f
+check_hashed_find host-h "find simple in hashed" $OBJ/kh.hosts
+
+# Test remove
+cp $OBJ/kh.hashed.orig $OBJ/kh.hashed
+${SSHKEYGEN} -qf $OBJ/kh.hashed -R host-a 2>/dev/null
+${SSHKEYGEN} -qf $OBJ/kh.hashed -F host-a && fail "found key after hashed remove"
diff --git a/regress/keygen-moduli.sh b/regress/keygen-moduli.sh
new file mode 100644
index 0000000..8be53f9
--- /dev/null
+++ b/regress/keygen-moduli.sh
@@ -0,0 +1,27 @@
+# $OpenBSD: keygen-moduli.sh,v 1.4 2020/01/02 13:25:38 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="keygen moduli"
+
+dhgex=0
+for kex in `${SSH} -Q kex`; do
+ case $kex in
+ diffie-hellman-group*) dhgex=1 ;;
+ esac
+done
+
+# Try "start at the beginning and stop after 1", "skip 1 then stop after 1"
+# and "skip 2 and run to the end with checkpointing". Since our test data
+# file has 3 lines, these should always result in 1 line of output.
+if [ "x$dhgex" = "x1" ]; then
+ for i in "-O lines=1" "-O start-line=1 -O lines=1" "-O start-line=2 -O checkpoint=$OBJ/moduli.ckpt"; do
+ trace "keygen $i"
+ rm -f $OBJ/moduli.out $OBJ/moduli.ckpt
+ ${SSHKEYGEN} -M screen -f ${SRC}/moduli.in $i $OBJ/moduli.out 2>/dev/null || \
+ fail "keygen screen failed $i"
+ lines=`wc -l <$OBJ/moduli.out`
+ test "$lines" -eq "1" || fail "expected 1 line, got $lines"
+ done
+fi
+
+rm -f $OBJ/moduli.out $OBJ/moduli.ckpt
diff --git a/regress/keygen-sshfp.sh b/regress/keygen-sshfp.sh
new file mode 100644
index 0000000..2abf9ad
--- /dev/null
+++ b/regress/keygen-sshfp.sh
@@ -0,0 +1,29 @@
+# $OpenBSD: keygen-sshfp.sh,v 1.2 2021/07/19 02:29:28 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="keygen-sshfp"
+
+trace "keygen fingerprints"
+fp=`${SSHKEYGEN} -r test -f ${SRC}/ed25519_openssh.pub | \
+ awk '$5=="1"{print $6}'`
+if [ "$fp" != "8a8647a7567e202ce317e62606c799c53d4c121f" ]; then
+ fail "keygen fingerprint sha1"
+fi
+fp=`${SSHKEYGEN} -r test -f ${SRC}/ed25519_openssh.pub | \
+ awk '$5=="2"{print $6}'`
+if [ "$fp" != \
+ "54a506fb849aafb9f229cf78a94436c281efcb4ae67c8a430e8c06afcb5ee18f" ]; then
+ fail "keygen fingerprint sha256"
+fi
+
+if ${SSH} -Q key-plain | grep ssh-rsa >/dev/null; then
+ fp=`${SSHKEYGEN} -r test -f ${SRC}/rsa_openssh.pub | awk '$5=="1"{print $6}'`
+ if [ "$fp" != "99c79cc09f5f81069cc017cdf9552cfc94b3b929" ]; then
+ fail "keygen fingerprint sha1"
+ fi
+ fp=`${SSHKEYGEN} -r test -f ${SRC}/rsa_openssh.pub | awk '$5=="2"{print $6}'`
+ if [ "$fp" != \
+ "e30d6b9eb7a4de495324e4d5870b8220577993ea6af417e8e4a4f1c5bf01a9b6" ]; then
+ fail "keygen fingerprint sha256"
+ fi
+fi
diff --git a/regress/keys-command.sh b/regress/keys-command.sh
new file mode 100644
index 0000000..5feec17
--- /dev/null
+++ b/regress/keys-command.sh
@@ -0,0 +1,79 @@
+# $OpenBSD: keys-command.sh,v 1.8 2021/09/30 04:22:50 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="authorized keys from command"
+
+if [ -z "$SUDO" -a ! -w /var/run ]; then
+ skip "need SUDO to create file in /var/run, test won't work without"
+fi
+
+rm -f $OBJ/keys-command-args
+
+touch $OBJ/keys-command-args
+chmod a+rw $OBJ/keys-command-args
+
+expected_key_text=`awk '{ print $2 }' < $OBJ/ssh-ed25519.pub`
+expected_key_fp=`$SSHKEYGEN -lf $OBJ/ssh-ed25519.pub | awk '{ print $2 }'`
+
+# Establish a AuthorizedKeysCommand in /var/run where it will have
+# acceptable directory permissions.
+KEY_COMMAND="/var/run/keycommand_${LOGNAME}.$$"
+trap "${SUDO} rm -f ${KEY_COMMAND}" 0
+cat << _EOF | $SUDO sh -c "rm -f '$KEY_COMMAND' ; cat > '$KEY_COMMAND'"
+#!/bin/sh
+echo args: "\$@" >> $OBJ/keys-command-args
+echo "$PATH" | grep -q mekmitasdigoat && exit 7
+test "x\$1" != "x${LOGNAME}" && exit 1
+if test $# -eq 6 ; then
+ test "x\$2" != "xblah" && exit 2
+ test "x\$3" != "x${expected_key_text}" && exit 3
+ test "x\$4" != "xssh-rsa" && exit 4
+ test "x\$5" != "x${expected_key_fp}" && exit 5
+ test "x\$6" != "xblah" && exit 6
+fi
+exec cat "$OBJ/authorized_keys_${LOGNAME}"
+_EOF
+$SUDO chmod 0755 "$KEY_COMMAND"
+
+if ! $OBJ/check-perm -m keys-command $KEY_COMMAND ; then
+ echo "skipping: $KEY_COMMAND is unsuitable as AuthorizedKeysCommand"
+ $SUDO rm -f $KEY_COMMAND
+ exit 0
+fi
+
+if [ -x $KEY_COMMAND ]; then
+ cp $OBJ/sshd_proxy $OBJ/sshd_proxy.bak
+
+ verbose "AuthorizedKeysCommand with arguments"
+ (
+ grep -vi AuthorizedKeysFile $OBJ/sshd_proxy.bak
+ echo AuthorizedKeysFile none
+ echo AuthorizedKeysCommand $KEY_COMMAND %u blah %k %t %f blah
+ echo AuthorizedKeysCommandUser ${LOGNAME}
+ ) > $OBJ/sshd_proxy
+
+ # Ensure that $PATH is sanitised in sshd
+ env PATH=$PATH:/sbin/mekmitasdigoat \
+ ${SSH} -F $OBJ/ssh_proxy somehost true
+ if [ $? -ne 0 ]; then
+ fail "connect failed"
+ fi
+
+ verbose "AuthorizedKeysCommand without arguments"
+ # Check legacy behavior of no-args resulting in username being passed.
+ (
+ grep -vi AuthorizedKeysFile $OBJ/sshd_proxy.bak
+ echo AuthorizedKeysFile none
+ echo AuthorizedKeysCommand $KEY_COMMAND
+ echo AuthorizedKeysCommandUser ${LOGNAME}
+ ) > $OBJ/sshd_proxy
+
+ # Ensure that $PATH is sanitised in sshd
+ env PATH=$PATH:/sbin/mekmitasdigoat \
+ ${SSH} -F $OBJ/ssh_proxy somehost true
+ if [ $? -ne 0 ]; then
+ fail "connect failed"
+ fi
+else
+ skip "$KEY_COMMAND not executable (/var/run mounted noexec?)"
+fi
diff --git a/regress/keyscan.sh b/regress/keyscan.sh
new file mode 100644
index 0000000..75a14ee
--- /dev/null
+++ b/regress/keyscan.sh
@@ -0,0 +1,25 @@
+# $OpenBSD: keyscan.sh,v 1.13 2020/01/22 07:31:27 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="keyscan"
+
+for i in $SSH_KEYTYPES; do
+ if [ -z "$algs" ]; then
+ algs="$i"
+ else
+ algs="$algs,$i"
+ fi
+done
+echo "HostKeyAlgorithms $algs" >> $OBJ/sshd_config
+
+start_sshd
+
+for t in $SSH_KEYTYPES; do
+ trace "keyscan type $t"
+ ${SSHKEYSCAN} -t $t -T 15 -p $PORT 127.0.0.1 127.0.0.1 127.0.0.1 \
+ > /dev/null 2>&1
+ r=$?
+ if [ $r -ne 0 ]; then
+ fail "ssh-keyscan -t $t failed with: $r"
+ fi
+done
diff --git a/regress/keytype.sh b/regress/keytype.sh
new file mode 100644
index 0000000..f1c0451
--- /dev/null
+++ b/regress/keytype.sh
@@ -0,0 +1,83 @@
+# $OpenBSD: keytype.sh,v 1.11 2021/02/25 03:27:34 djm Exp $
+# Placed in the Public Domain.
+
+tid="login with different key types"
+
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+cp $OBJ/ssh_proxy $OBJ/ssh_proxy_bak
+
+# Construct list of key types based on what the built binaries support.
+ktypes=""
+for i in ${SSH_KEYTYPES}; do
+ case "$i" in
+ ssh-dss) ktypes="$ktypes dsa-1024" ;;
+ ssh-rsa) ktypes="$ktypes rsa-2048 rsa-3072" ;;
+ ssh-ed25519) ktypes="$ktypes ed25519-512" ;;
+ ecdsa-sha2-nistp256) ktypes="$ktypes ecdsa-256" ;;
+ ecdsa-sha2-nistp384) ktypes="$ktypes ecdsa-384" ;;
+ ecdsa-sha2-nistp521) ktypes="$ktypes ecdsa-521" ;;
+ sk-ssh-ed25519*) ktypes="$ktypes ed25519-sk" ;;
+ sk-ecdsa-sha2-nistp256*) ktypes="$ktypes ecdsa-sk" ;;
+ esac
+done
+
+for kt in $ktypes; do
+ rm -f $OBJ/key.$kt
+ xbits=`echo ${kt} | awk -F- '{print $2}'`
+ xtype=`echo ${kt} | awk -F- '{print $1}'`
+ case "$kt" in
+ *sk) type="$kt"; bits="n/a"; bits_arg="";;
+ *) type=$xtype; bits=$xbits; bits_arg="-b $bits";;
+ esac
+ verbose "keygen $type, $bits bits"
+ ${SSHKEYGEN} $bits_arg -q -N '' -t $type -f $OBJ/key.$kt || \
+ fail "ssh-keygen for type $type, $bits bits failed"
+done
+
+kname_to_ktype() {
+ case $1 in
+ dsa-1024) echo ssh-dss;;
+ ecdsa-256) echo ecdsa-sha2-nistp256;;
+ ecdsa-384) echo ecdsa-sha2-nistp384;;
+ ecdsa-521) echo ecdsa-sha2-nistp521;;
+ ed25519-512) echo ssh-ed25519;;
+ rsa-*) echo rsa-sha2-512,rsa-sha2-256,ssh-rsa;;
+ ed25519-sk) echo sk-ssh-ed25519@openssh.com;;
+ ecdsa-sk) echo sk-ecdsa-sha2-nistp256@openssh.com;;
+ esac
+}
+
+tries="1 2 3"
+for ut in $ktypes; do
+ user_type=`kname_to_ktype "$ut"`
+ htypes="$ut"
+ #htypes=$ktypes
+ for ht in $htypes; do
+ host_type=`kname_to_ktype "$ht"`
+ trace "ssh connect, userkey $ut, hostkey $ht"
+ (
+ grep -v HostKey $OBJ/sshd_proxy_bak
+ echo HostKey $OBJ/key.$ht
+ echo PubkeyAcceptedAlgorithms $user_type
+ echo HostKeyAlgorithms $host_type
+ ) > $OBJ/sshd_proxy
+ (
+ grep -v IdentityFile $OBJ/ssh_proxy_bak
+ echo IdentityFile $OBJ/key.$ut
+ echo PubkeyAcceptedAlgorithms $user_type
+ echo HostKeyAlgorithms $host_type
+ ) > $OBJ/ssh_proxy
+ (
+ printf 'localhost-with-alias,127.0.0.1,::1 '
+ cat $OBJ/key.$ht.pub
+ ) > $OBJ/known_hosts
+ cat $OBJ/key.$ut.pub > $OBJ/authorized_keys_$USER
+ for i in $tries; do
+ verbose "userkey $ut, hostkey ${ht}"
+ ${SSH} -F $OBJ/ssh_proxy 999.999.999.999 true
+ if [ $? -ne 0 ]; then
+ fail "ssh userkey $ut, hostkey $ht failed"
+ fi
+ done
+ done
+done
diff --git a/regress/knownhosts-command.sh b/regress/knownhosts-command.sh
new file mode 100644
index 0000000..8472ec8
--- /dev/null
+++ b/regress/knownhosts-command.sh
@@ -0,0 +1,55 @@
+# $OpenBSD: knownhosts-command.sh,v 1.3 2021/08/30 01:15:45 djm Exp $
+# Placed in the Public Domain.
+
+tid="known hosts command "
+
+rm -f $OBJ/knownhosts_command $OBJ/ssh_proxy_khc
+cp $OBJ/ssh_proxy $OBJ/ssh_proxy_orig
+
+( grep -vi GlobalKnownHostsFile $OBJ/ssh_proxy_orig | \
+ grep -vi UserKnownHostsFile;
+ echo "GlobalKnownHostsFile none" ;
+ echo "UserKnownHostsFile none" ;
+ echo "KnownHostsCommand $OBJ/knownhosts_command '%t' '%K' '%u'" ;
+) > $OBJ/ssh_proxy
+
+verbose "simple connection"
+cat > $OBJ/knownhosts_command << _EOF
+#!/bin/sh
+cat $OBJ/known_hosts
+_EOF
+chmod a+x $OBJ/knownhosts_command
+${SSH} -F $OBJ/ssh_proxy x true || fail "ssh connect failed"
+
+verbose "no keys"
+cat > $OBJ/knownhosts_command << _EOF
+#!/bin/sh
+exit 0
+_EOF
+chmod a+x $OBJ/knownhosts_command
+${SSH} -F $OBJ/ssh_proxy x true && fail "ssh connect succeeded with no keys"
+
+verbose "bad exit status"
+cat > $OBJ/knownhosts_command << _EOF
+#!/bin/sh
+cat $OBJ/known_hosts
+exit 1
+_EOF
+chmod a+x $OBJ/knownhosts_command
+${SSH} -F $OBJ/ssh_proxy x true && fail "ssh connect succeeded with bad exit"
+
+for keytype in ${SSH_HOSTKEY_TYPES} ; do
+ algs=$keytype
+ test "x$keytype" = "xssh-dss" && continue
+ test "x$keytype" = "xssh-rsa" && algs=ssh-rsa,rsa-sha2-256,rsa-sha2-512
+ verbose "keytype $keytype"
+ cat > $OBJ/knownhosts_command << _EOF
+#!/bin/sh
+die() { echo "\$@" 1>&2 ; exit 1; }
+test "x\$1" = "x$keytype" || die "wrong keytype \$1 (expected $keytype)"
+test "x\$3" = "x$LOGNAME" || die "wrong username \$3 (expected $LOGNAME)"
+grep -- "\$1.*\$2" $OBJ/known_hosts
+_EOF
+ ${SSH} -F $OBJ/ssh_proxy -oHostKeyAlgorithms=$algs x true ||
+ fail "ssh connect failed for keytype $x"
+done
diff --git a/regress/knownhosts.sh b/regress/knownhosts.sh
new file mode 100644
index 0000000..dfc768a
--- /dev/null
+++ b/regress/knownhosts.sh
@@ -0,0 +1,17 @@
+# $OpenBSD: knownhosts.sh,v 1.1 2021/10/01 05:20:20 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="known hosts"
+
+opts="-F $OBJ/ssh_proxy"
+
+trace "test initial connection"
+${SSH} $opts somehost true || fail "initial connection"
+
+trace "learn hashed known host"
+>$OBJ/known_hosts
+${SSH} -ohashknownhosts=yes -o stricthostkeychecking=no $opts somehost true \
+ || fail "learn hashed known_hosts"
+
+trace "test hashed known hosts"
+${SSH} $opts somehost true || fail "reconnect with hashed known hosts"
diff --git a/regress/krl.sh b/regress/krl.sh
new file mode 100644
index 0000000..d560d61
--- /dev/null
+++ b/regress/krl.sh
@@ -0,0 +1,217 @@
+# $OpenBSD: krl.sh,v 1.12 2023/01/16 04:11:29 djm Exp $
+# Placed in the Public Domain.
+
+tid="key revocation lists"
+
+# Use ed25519 by default since it's fast and it's supported when building
+# w/out OpenSSL. Populate ktype[2-4] with the other types if supported.
+ktype1=ed25519; ktype2=ed25519; ktype3=ed25519;
+ktype4=ed25519; ktype5=ed25519; ktype6=ed25519;
+for t in $SSH_KEYTYPES; do
+ case "$t" in
+ ecdsa*) ktype2=ecdsa ;;
+ ssh-rsa) ktype3=rsa ;;
+ ssh-dss) ktype4=dsa ;;
+ sk-ssh-ed25519@openssh.com) ktype5=ed25519-sk ;;
+ sk-ecdsa-sha2-nistp256@openssh.com) ktype6=ecdsa-sk ;;
+ esac
+done
+
+# Do most testing with ssh-keygen; it uses the same verification code as sshd.
+
+# Old keys will interfere with ssh-keygen.
+rm -f $OBJ/revoked-* $OBJ/krl-*
+
+# Generate a CA key
+$SSHKEYGEN -t $ktype1 -f $OBJ/revoked-ca -C "" -N "" > /dev/null ||
+ fatal "$SSHKEYGEN CA failed"
+$SSHKEYGEN -t $ktype2 -f $OBJ/revoked-ca2 -C "" -N "" > /dev/null ||
+ fatal "$SSHKEYGEN CA2 failed"
+
+# A specification that revokes some certificates by serial numbers
+# The serial pattern is chosen to ensure the KRL includes list, range and
+# bitmap sections.
+cat << EOF >> $OBJ/revoked-serials
+serial: 1-4
+serial: 10
+serial: 15
+serial: 30
+serial: 50
+serial: 90
+serial: 999
+# The following sum to 500-799
+serial: 500
+serial: 501
+serial: 502
+serial: 503-600
+serial: 700-797
+serial: 798
+serial: 799
+serial: 599-701
+# Some multiple consecutive serial number ranges
+serial: 10000-20000
+serial: 30000-40000
+EOF
+
+# A specification that revokes some certificated by key ID.
+touch $OBJ/revoked-keyid
+for n in 1 2 3 4 10 15 30 50 90 `jot 500 300` 999 1000 1001 1002; do
+ test "x$n" = "x499" && continue
+ # Fill in by-ID revocation spec.
+ echo "id: revoked $n" >> $OBJ/revoked-keyid
+done
+
+keygen() {
+ N=$1
+ f=$OBJ/revoked-`printf "%04d" $N`
+ # Vary the keytype. We use mostly ed25519 since this is fast and well
+ # supported.
+ keytype=$ktype1
+ case $N in
+ 2 | 10 | 510 | 1001) keytype=$ktype2 ;;
+ 4 | 30 | 520 | 1002) keytype=$ktype3 ;;
+ 8 | 50 | 530 | 1003) keytype=$ktype4 ;;
+ 16 | 70 | 540 | 1004) keytype=$ktype5 ;;
+ 32 | 90 | 550 | 1005) keytype=$ktype6 ;;
+ esac
+ $SSHKEYGEN -t $keytype -f $f -C "" -N "" > /dev/null \
+ || fatal "$SSHKEYGEN failed"
+ # Sign cert
+ $SSHKEYGEN -s $OBJ/revoked-ca -z $n -I "revoked $N" $f >/dev/null 2>&1 \
+ || fatal "$SSHKEYGEN sign failed"
+ echo $f
+}
+
+# Generate some keys.
+verbose "$tid: generating test keys"
+REVOKED_SERIALS="1 4 10 50 90 500 510 520 550 799 999"
+for n in $REVOKED_SERIALS ; do
+ f=`keygen $n`
+ RKEYS="$RKEYS ${f}.pub"
+ RCERTS="$RCERTS ${f}-cert.pub"
+done
+UNREVOKED_SERIALS="5 9 14 16 29 49 51 499 800 1010 1011"
+UNREVOKED=""
+for n in $UNREVOKED_SERIALS ; do
+ f=`keygen $n`
+ UKEYS="$UKEYS ${f}.pub"
+ UCERTS="$UCERTS ${f}-cert.pub"
+done
+
+# Specifications that revoke keys by hash.
+touch $OBJ/revoked-sha1 $OBJ/revoked-sha256 $OBJ/revoked-hash
+for rkey in $RKEYS; do
+ (printf "sha1: "; cat $rkey) >> $OBJ/revoked-sha1
+ (printf "sha256: "; cat $rkey) >> $OBJ/revoked-sha256
+ (printf "hash: "; $SSHKEYGEN -lf $rkey | \
+ awk '{ print $2 }') >> $OBJ/revoked-hash
+done
+
+genkrls() {
+ OPTS=$1
+$SSHKEYGEN $OPTS -kf $OBJ/krl-empty - </dev/null \
+ >/dev/null || fatal "$SSHKEYGEN KRL failed"
+$SSHKEYGEN $OPTS -kf $OBJ/krl-keys $RKEYS \
+ >/dev/null || fatal "$SSHKEYGEN KRL failed"
+$SSHKEYGEN $OPTS -kf $OBJ/krl-cert $RCERTS \
+ >/dev/null || fatal "$SSHKEYGEN KRL failed"
+$SSHKEYGEN $OPTS -kf $OBJ/krl-all $RKEYS $RCERTS \
+ >/dev/null || fatal "$SSHKEYGEN KRL failed"
+$SSHKEYGEN $OPTS -kf $OBJ/krl-ca $OBJ/revoked-ca.pub \
+ >/dev/null || fatal "$SSHKEYGEN KRL failed"
+$SSHKEYGEN $OPTS -kf $OBJ/krl-sha1 $OBJ/revoked-sha1 \
+ >/dev/null 2>&1 || fatal "$SSHKEYGEN KRL failed"
+$SSHKEYGEN $OPTS -kf $OBJ/krl-sha256 $OBJ/revoked-sha256 \
+ >/dev/null 2>&1 || fatal "$SSHKEYGEN KRL failed"
+$SSHKEYGEN $OPTS -kf $OBJ/krl-hash $OBJ/revoked-hash \
+ >/dev/null 2>&1 || fatal "$SSHKEYGEN KRL failed"
+# This should fail as KRLs from serial/key-id spec need the CA specified.
+$SSHKEYGEN $OPTS -kf $OBJ/krl-serial $OBJ/revoked-serials \
+ >/dev/null 2>&1 && fatal "$SSHKEYGEN KRL succeeded unexpectedly"
+$SSHKEYGEN $OPTS -kf $OBJ/krl-keyid $OBJ/revoked-keyid \
+ >/dev/null 2>&1 && fatal "$SSHKEYGEN KRL succeeded unexpectedly"
+# These should succeed; they specify an explicit CA key.
+$SSHKEYGEN $OPTS -kf $OBJ/krl-serial -s $OBJ/revoked-ca \
+ $OBJ/revoked-serials >/dev/null || fatal "$SSHKEYGEN KRL failed"
+$SSHKEYGEN $OPTS -kf $OBJ/krl-keyid -s $OBJ/revoked-ca.pub \
+ $OBJ/revoked-keyid >/dev/null || fatal "$SSHKEYGEN KRL failed"
+# These should succeed; they specify an wildcard CA key.
+$SSHKEYGEN $OPTS -kf $OBJ/krl-serial-wild -s NONE $OBJ/revoked-serials \
+ >/dev/null || fatal "$SSHKEYGEN KRL failed"
+$SSHKEYGEN $OPTS -kf $OBJ/krl-keyid-wild -s NONE $OBJ/revoked-keyid \
+ >/dev/null || fatal "$SSHKEYGEN KRL failed"
+# Revoke the same serials with the second CA key to ensure a multi-CA
+# KRL is generated.
+$SSHKEYGEN $OPTS -kf $OBJ/krl-serial -u -s $OBJ/revoked-ca2 \
+ $OBJ/revoked-serials >/dev/null || fatal "$SSHKEYGEN KRL failed"
+}
+
+## XXX dump with trace and grep for set cert serials
+## XXX test ranges near (u64)-1, etc.
+
+verbose "$tid: generating KRLs"
+genkrls
+
+check_krl() {
+ KEY=$1
+ KRL=$2
+ EXPECT_REVOKED=$3
+ TAG=$4
+ $SSHKEYGEN -Qf $KRL $KEY >/dev/null
+ result=$?
+ if test "x$EXPECT_REVOKED" = "xy" -a $result -eq 0 ; then
+ fatal "key $KEY not revoked by KRL $KRL: $TAG"
+ elif test "x$EXPECT_REVOKED" = "xn" -a $result -ne 0 ; then
+ fatal "key $KEY unexpectedly revoked by KRL $KRL: $TAG"
+ fi
+}
+test_rev() {
+ FILES=$1
+ TAG=$2
+ KEYS_RESULT=$3
+ ALL_RESULT=$4
+ HASH_RESULT=$5
+ SERIAL_RESULT=$6
+ KEYID_RESULT=$7
+ CERTS_RESULT=$8
+ CA_RESULT=$9
+ SERIAL_WRESULT=${10}
+ KEYID_WRESULT=${11}
+ verbose "$tid: checking revocations for $TAG"
+ for f in $FILES ; do
+ check_krl $f $OBJ/krl-empty no "$TAG"
+ check_krl $f $OBJ/krl-keys $KEYS_RESULT "$TAG"
+ check_krl $f $OBJ/krl-all $ALL_RESULT "$TAG"
+ check_krl $f $OBJ/krl-sha1 $HASH_RESULT "$TAG"
+ check_krl $f $OBJ/krl-sha256 $HASH_RESULT "$TAG"
+ check_krl $f $OBJ/krl-hash $HASH_RESULT "$TAG"
+ check_krl $f $OBJ/krl-serial $SERIAL_RESULT "$TAG"
+ check_krl $f $OBJ/krl-keyid $KEYID_RESULT "$TAG"
+ check_krl $f $OBJ/krl-cert $CERTS_RESULT "$TAG"
+ check_krl $f $OBJ/krl-ca $CA_RESULT "$TAG"
+ check_krl $f $OBJ/krl-serial-wild $SERIAL_WRESULT "$TAG"
+ check_krl $f $OBJ/krl-keyid-wild $KEYID_WRESULT "$TAG"
+ done
+}
+
+test_all() {
+ # wildcard
+ # keys all hash sr# ID cert CA srl ID
+ test_rev "$RKEYS" "revoked keys" y y y n n n n n n
+ test_rev "$UKEYS" "unrevoked keys" n n n n n n n n n
+ test_rev "$RCERTS" "revoked certs" y y y y y y y y y
+ test_rev "$UCERTS" "unrevoked certs" n n n n n n y n n
+}
+
+test_all
+
+# Check update. Results should be identical.
+verbose "$tid: testing KRL update"
+for f in $OBJ/krl-keys $OBJ/krl-cert $OBJ/krl-all \
+ $OBJ/krl-ca $OBJ/krl-serial $OBJ/krl-keyid \
+ $OBJ/krl-serial-wild $OBJ/krl-keyid-wild; do
+ cp -f $OBJ/krl-empty $f
+ genkrls -u
+done
+
+test_all
diff --git a/regress/limit-keytype.sh b/regress/limit-keytype.sh
new file mode 100644
index 0000000..7127de0
--- /dev/null
+++ b/regress/limit-keytype.sh
@@ -0,0 +1,133 @@
+# $OpenBSD: limit-keytype.sh,v 1.10 2021/02/25 03:27:34 djm Exp $
+# Placed in the Public Domain.
+
+tid="restrict pubkey type"
+
+# XXX sk-* keys aren't actually tested ATM.
+
+rm -f $OBJ/authorized_keys_$USER $OBJ/user_ca_key* $OBJ/user_key*
+rm -f $OBJ/authorized_principals_$USER $OBJ/cert_user_key*
+
+mv $OBJ/sshd_proxy $OBJ/sshd_proxy.orig
+mv $OBJ/ssh_proxy $OBJ/ssh_proxy.orig
+
+ktype1=ed25519; ktype2=ed25519; ktype3=ed25519;
+ktype4=ed25519; ktype5=ed25519; ktype6=ed25519;
+for t in $SSH_KEYTYPES ; do
+ case "$t" in
+ ssh-rsa) ktype2=rsa ;;
+ ecdsa*) ktype3=ecdsa ;; # unused
+ ssh-dss) ktype4=dsa ;;
+ sk-ssh-ed25519@openssh.com) ktype5=ed25519-sk ;;
+ sk-ecdsa-sha2-nistp256@openssh.com) ktype6=ecdsa-sk ;;
+ esac
+done
+
+# Create a CA key
+${SSHKEYGEN} -q -N '' -t $ktype1 -f $OBJ/user_ca_key ||\
+ fatal "ssh-keygen failed"
+
+# Make some keys and a certificate.
+${SSHKEYGEN} -q -N '' -t $ktype1 -f $OBJ/user_key1 || \
+ fatal "ssh-keygen failed"
+${SSHKEYGEN} -q -N '' -t $ktype2 -f $OBJ/user_key2 || \
+ fatal "ssh-keygen failed"
+${SSHKEYGEN} -q -N '' -t $ktype2 -f $OBJ/user_key3 || \
+ fatal "ssh-keygen failed"
+${SSHKEYGEN} -q -N '' -t $ktype4 -f $OBJ/user_key4 || \
+ fatal "ssh-keygen failed"
+${SSHKEYGEN} -q -N '' -t $ktype5 -f $OBJ/user_key5 || \
+ fatal "ssh-keygen failed"
+${SSHKEYGEN} -q -N '' -t $ktype6 -f $OBJ/user_key6 || \
+ fatal "ssh-keygen failed"
+${SSHKEYGEN} -q -s $OBJ/user_ca_key -I "regress user key for $USER" \
+ -z $$ -n ${USER},mekmitasdigoat $OBJ/user_key3 ||
+ fatal "couldn't sign user_key1"
+# Copy the private key alongside the cert to allow better control of when
+# it is offered.
+mv $OBJ/user_key3-cert.pub $OBJ/cert_user_key3.pub
+
+grep -v IdentityFile $OBJ/ssh_proxy.orig > $OBJ/ssh_proxy
+
+opts="-oProtocol=2 -F $OBJ/ssh_proxy -oIdentitiesOnly=yes"
+certopts="$opts -i $OBJ/user_key3 -oCertificateFile=$OBJ/cert_user_key3.pub"
+
+echo mekmitasdigoat > $OBJ/authorized_principals_$USER
+cat $OBJ/user_key1.pub > $OBJ/authorized_keys_$USER
+cat $OBJ/user_key2.pub >> $OBJ/authorized_keys_$USER
+
+prepare_config() {
+ (
+ grep -v "Protocol" $OBJ/sshd_proxy.orig
+ echo "Protocol 2"
+ echo "AuthenticationMethods publickey"
+ echo "TrustedUserCAKeys $OBJ/user_ca_key.pub"
+ echo "AuthorizedPrincipalsFile $OBJ/authorized_principals_%u"
+ for x in "$@" ; do
+ echo "$x"
+ done
+ ) > $OBJ/sshd_proxy
+}
+
+# Return the required parameter for PubkeyAcceptedAlgorithms corresponding to
+# the supplied key type.
+keytype() {
+ case "$1" in
+ ecdsa) printf "ecdsa-sha2-*" ;;
+ ed25519) printf "ssh-ed25519" ;;
+ dsa) printf "ssh-dss" ;;
+ rsa) printf "rsa-sha2-256,rsa-sha2-512,ssh-rsa" ;;
+ sk-ecdsa) printf "sk-ecdsa-*" ;;
+ sk-ssh-ed25519) printf "sk-ssh-ed25519-*" ;;
+ esac
+}
+
+prepare_config
+
+# Check we can log in with all key types.
+${SSH} $certopts proxy true || fatal "cert failed"
+${SSH} $opts -i $OBJ/user_key1 proxy true || fatal "key1 failed"
+${SSH} $opts -i $OBJ/user_key2 proxy true || fatal "key2 failed"
+
+# Allow plain Ed25519 and RSA. The certificate should fail.
+verbose "allow $ktype2,$ktype1"
+prepare_config \
+ "PubkeyAcceptedAlgorithms `keytype $ktype2`,`keytype $ktype1`"
+${SSH} $certopts proxy true && fatal "cert succeeded"
+${SSH} $opts -i $OBJ/user_key1 proxy true || fatal "key1 failed"
+${SSH} $opts -i $OBJ/user_key2 proxy true || fatal "key2 failed"
+
+# Allow Ed25519 only.
+verbose "allow $ktype1"
+prepare_config "PubkeyAcceptedAlgorithms `keytype $ktype1`"
+${SSH} $certopts proxy true && fatal "cert succeeded"
+${SSH} $opts -i $OBJ/user_key1 proxy true || fatal "key1 failed"
+if [ "$ktype1" != "$ktype2" ]; then
+ ${SSH} $opts -i $OBJ/user_key2 proxy true && fatal "key2 succeeded"
+fi
+
+# Allow all certs. Plain keys should fail.
+verbose "allow cert only"
+prepare_config "PubkeyAcceptedAlgorithms *-cert-v01@openssh.com"
+${SSH} $certopts proxy true || fatal "cert failed"
+${SSH} $opts -i $OBJ/user_key1 proxy true && fatal "key1 succeeded"
+${SSH} $opts -i $OBJ/user_key2 proxy true && fatal "key2 succeeded"
+
+# Allow RSA in main config, Ed25519 for non-existent user.
+verbose "match w/ no match"
+prepare_config "PubkeyAcceptedAlgorithms `keytype $ktype2`" \
+ "Match user x$USER" "PubkeyAcceptedAlgorithms +`keytype $ktype1`"
+${SSH} $certopts proxy true && fatal "cert succeeded"
+if [ "$ktype1" != "$ktype2" ]; then
+ ${SSH} $opts -i $OBJ/user_key1 proxy true && fatal "key1 succeeded"
+fi
+${SSH} $opts -i $OBJ/user_key2 proxy true || fatal "key2 failed"
+
+# Allow only DSA in main config, Ed25519 for user.
+verbose "match w/ matching"
+prepare_config "PubkeyAcceptedAlgorithms `keytype $ktype4`" \
+ "Match user $USER" "PubkeyAcceptedAlgorithms +`keytype $ktype1`"
+${SSH} $certopts proxy true || fatal "cert failed"
+${SSH} $opts -i $OBJ/user_key1 proxy true || fatal "key1 failed"
+${SSH} $opts -i $OBJ/user_key4 proxy true && fatal "key4 succeeded"
+
diff --git a/regress/localcommand.sh b/regress/localcommand.sh
new file mode 100644
index 0000000..5224a16
--- /dev/null
+++ b/regress/localcommand.sh
@@ -0,0 +1,13 @@
+# $OpenBSD: localcommand.sh,v 1.4 2017/04/30 23:34:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="localcommand"
+
+echo 'PermitLocalCommand yes' >> $OBJ/ssh_proxy
+echo 'LocalCommand echo foo' >> $OBJ/ssh_proxy
+
+verbose "test $tid: proto $p localcommand"
+a=`${SSH} -F $OBJ/ssh_proxy somehost true`
+if [ "$a" != "foo" ] ; then
+ fail "$tid proto $p"
+fi
diff --git a/regress/login-timeout.sh b/regress/login-timeout.sh
new file mode 100644
index 0000000..1577da1
--- /dev/null
+++ b/regress/login-timeout.sh
@@ -0,0 +1,18 @@
+# $OpenBSD: login-timeout.sh,v 1.10 2021/09/30 05:20:08 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="connect after login grace timeout"
+
+trace "test login grace time"
+cp $OBJ/sshd_config $OBJ/sshd_config.orig
+grep -vi LoginGraceTime $OBJ/sshd_config.orig > $OBJ/sshd_config
+echo "LoginGraceTime 10s" >> $OBJ/sshd_config
+echo "MaxStartups 1" >> $OBJ/sshd_config
+start_sshd
+
+(echo SSH-2.0-fake; sleep 60) | telnet 127.0.0.1 ${PORT} >/dev/null 2>&1 &
+sleep 15
+${SSH} -F $OBJ/ssh_config somehost true
+if [ $? -ne 0 ]; then
+ fail "ssh connect after login grace timeout failed"
+fi
diff --git a/regress/misc/Makefile b/regress/misc/Makefile
new file mode 100644
index 0000000..b9149f2
--- /dev/null
+++ b/regress/misc/Makefile
@@ -0,0 +1,3 @@
+SUBDIR= sk-dummy
+
+.include <bsd.subdir.mk>
diff --git a/regress/misc/fuzz-harness/Makefile b/regress/misc/fuzz-harness/Makefile
new file mode 100644
index 0000000..0b4238f
--- /dev/null
+++ b/regress/misc/fuzz-harness/Makefile
@@ -0,0 +1,55 @@
+# NB. libssh and libopenbsd-compat should be built with the same sanitizer opts.
+CC=clang-11
+CXX=clang++-11
+FUZZ_FLAGS=-fsanitize=address,fuzzer -fno-omit-frame-pointer
+FUZZ_LIBS=-lFuzzer
+
+CXXFLAGS=-O2 -g -Wall -Wextra -Wno-unused-parameter -I ../../.. $(FUZZ_FLAGS)
+CFLAGS=$(CXXFLAGS)
+LDFLAGS=-L ../../.. -L ../../../openbsd-compat -g $(FUZZ_FLAGS)
+LIBS=-lssh -lopenbsd-compat -lmd -lcrypto -lfido2 -lcbor $(FUZZ_LIBS)
+SK_NULL_OBJS=ssh-sk-null.o
+COMMON_DEPS=../../../libssh.a
+
+TARGETS=pubkey_fuzz sig_fuzz authopt_fuzz authkeys_fuzz sshsig_fuzz \
+ sshsigopt_fuzz privkey_fuzz kex_fuzz agent_fuzz
+
+all: $(TARGETS)
+
+.cc.o:
+ $(CXX) $(CXXFLAGS) -c $< -o $@
+
+pubkey_fuzz: pubkey_fuzz.o $(SK_NULL_OBJS) $(COMMON_DEPS)
+ $(CXX) -o $@ pubkey_fuzz.o $(SK_NULL_OBJS) $(LDFLAGS) $(LIBS)
+
+sig_fuzz: sig_fuzz.o $(SK_NULL_OBJS) $(COMMON_DEPS)
+ $(CXX) -o $@ sig_fuzz.o $(SK_NULL_OBJS) $(LDFLAGS) $(LIBS)
+
+authopt_fuzz: authopt_fuzz.o $(SK_NULL_OBJS) $(COMMON_DEPS)
+ $(CXX) -o $@ authopt_fuzz.o $(SK_NULL_OBJS) ../../../auth-options.o $(LDFLAGS) $(LIBS)
+
+authkeys_fuzz: authkeys_fuzz.o $(SK_NULL_OBJS) $(COMMON_DEPS)
+ $(CXX) -o $@ authkeys_fuzz.o $(SK_NULL_OBJS) ../../../auth-options.o ../../../auth2-pubkeyfile.o $(LDFLAGS) $(LIBS)
+
+sshsig_fuzz: sshsig_fuzz.o $(SK_NULL_OBJS) $(COMMON_DEPS)
+ $(CXX) -o $@ sshsig_fuzz.o $(SK_NULL_OBJS) ../../../sshsig.o $(LDFLAGS) $(LIBS)
+
+sshsigopt_fuzz: sshsigopt_fuzz.o $(SK_NULL_OBJS) $(COMMON_DEPS)
+ $(CXX) -o $@ sshsigopt_fuzz.o $(SK_NULL_OBJS) ../../../sshsig.o $(LDFLAGS) $(LIBS)
+
+privkey_fuzz: privkey_fuzz.o $(SK_NULL_OBJS) $(COMMON_DEPS)
+ $(CXX) -o $@ privkey_fuzz.o $(SK_NULL_OBJS) $(LDFLAGS) $(LIBS)
+
+kex_fuzz: kex_fuzz.o $(SK_NULL_OBJS) $(COMMON_DEPS)
+ $(CXX) -o $@ kex_fuzz.o $(SK_NULL_OBJS) $(LDFLAGS) $(LIBS) -lz
+
+agent_fuzz: agent_fuzz.o agent_fuzz_helper.o sk-dummy.o ../../../ssh-sk.o $(COMMON_DEPS)
+ $(CXX) -o $@ agent_fuzz.o agent_fuzz_helper.o sk-dummy.o ../../../ssh-sk.o $(LDFLAGS) $(LIBS) -lz
+
+agent_fuzz_helper.o: agent_fuzz_helper.c ../../../ssh-agent.c
+
+sk-dummy.o: ../sk-dummy/sk-dummy.c
+ $(CC) $(CFLAGS) -c -o $@ ../sk-dummy/sk-dummy.c -DSK_DUMMY_INTEGRATE=1 $(LDFLAGS)
+
+clean:
+ -rm -f *.o $(TARGETS)
diff --git a/regress/misc/fuzz-harness/README b/regress/misc/fuzz-harness/README
new file mode 100644
index 0000000..ae6fbe7
--- /dev/null
+++ b/regress/misc/fuzz-harness/README
@@ -0,0 +1 @@
+This directory contains fuzzing harnesses for use with clang's libfuzzer.
diff --git a/regress/misc/fuzz-harness/agent_fuzz.cc b/regress/misc/fuzz-harness/agent_fuzz.cc
new file mode 100644
index 0000000..ad85b2f
--- /dev/null
+++ b/regress/misc/fuzz-harness/agent_fuzz.cc
@@ -0,0 +1,15 @@
+// cc_fuzz_target test for ssh-agent.
+extern "C" {
+
+#include <stdint.h>
+#include <sys/types.h>
+
+extern void test_one(const uint8_t* s, size_t slen);
+
+int LLVMFuzzerTestOneInput(const uint8_t* s, size_t slen)
+{
+ test_one(s, slen);
+ return 0;
+}
+
+} // extern
diff --git a/regress/misc/fuzz-harness/agent_fuzz_helper.c b/regress/misc/fuzz-harness/agent_fuzz_helper.c
new file mode 100644
index 0000000..1d41982
--- /dev/null
+++ b/regress/misc/fuzz-harness/agent_fuzz_helper.c
@@ -0,0 +1,177 @@
+#include "fixed-keys.h"
+#include <assert.h>
+
+#define main(ac, av) xxxmain(ac, av)
+#include "../../../ssh-agent.c"
+
+void test_one(const uint8_t* s, size_t slen);
+
+static int
+devnull_or_die(void)
+{
+ int fd;
+
+ if ((fd = open("/dev/null", O_RDWR)) == -1) {
+ error_f("open /dev/null: %s", strerror(errno));
+ abort();
+ }
+ return fd;
+}
+
+static struct sshkey *
+pubkey_or_die(const char *s)
+{
+ char *tmp, *cp;
+ struct sshkey *pubkey;
+ int r;
+
+ tmp = cp = xstrdup(s);
+ if ((pubkey = sshkey_new(KEY_UNSPEC)) == NULL)
+ abort();
+ if ((r = sshkey_read(pubkey, &cp)) != 0) {
+ error_fr(r, "parse");
+ abort();
+ }
+ free(tmp);
+ return pubkey;
+}
+
+static struct sshkey *
+privkey_or_die(const char *s)
+{
+ int r;
+ struct sshbuf *b;
+ struct sshkey *privkey;
+
+ if ((b = sshbuf_from(s, strlen(s))) == NULL) {
+ error_f("sshbuf_from failed");
+ abort();
+ }
+ if ((r = sshkey_parse_private_fileblob(b, "", &privkey, NULL)) != 0) {
+ error_fr(r, "parse");
+ abort();
+ }
+ sshbuf_free(b);
+ return privkey;
+}
+
+static void
+add_key(const char *privkey, const char *certpath)
+{
+ Identity *id;
+ int r;
+ struct sshkey *cert;
+
+ id = xcalloc(1, sizeof(Identity));
+ TAILQ_INSERT_TAIL(&idtab->idlist, id, next);
+ idtab->nentries++;
+ id->key = privkey_or_die(privkey);
+ id->comment = xstrdup("rhododaktulos Eos");
+ if (sshkey_is_sk(id->key))
+ id->sk_provider = xstrdup("internal");
+
+ /* Now the cert too */
+ id = xcalloc(1, sizeof(Identity));
+ TAILQ_INSERT_TAIL(&idtab->idlist, id, next);
+ idtab->nentries++;
+ id->key = privkey_or_die(privkey);
+ cert = pubkey_or_die(certpath);
+ if ((r = sshkey_to_certified(id->key)) != 0) {
+ error_fr(r, "sshkey_to_certified");
+ abort();
+ }
+ if ((r = sshkey_cert_copy(cert, id->key)) != 0) {
+ error_fr(r, "sshkey_cert_copy");
+ abort();
+ }
+ sshkey_free(cert);
+ id->comment = xstrdup("outis");
+ if (sshkey_is_sk(id->key))
+ id->sk_provider = xstrdup("internal");
+}
+
+static void
+cleanup_idtab(void)
+{
+ Identity *id;
+
+ if (idtab == NULL) return;
+ for (id = TAILQ_FIRST(&idtab->idlist); id;
+ id = TAILQ_FIRST(&idtab->idlist)) {
+ TAILQ_REMOVE(&idtab->idlist, id, next);
+ free_identity(id);
+ }
+ free(idtab);
+ idtab = NULL;
+}
+
+static void
+reset_idtab(void)
+{
+ cleanup_idtab();
+ idtab_init();
+ // Load keys.
+ add_key(PRIV_RSA, CERT_RSA);
+ add_key(PRIV_DSA, CERT_DSA);
+ add_key(PRIV_ECDSA, CERT_ECDSA);
+ add_key(PRIV_ED25519, CERT_ED25519);
+ add_key(PRIV_ECDSA_SK, CERT_ECDSA_SK);
+ add_key(PRIV_ED25519_SK, CERT_ED25519_SK);
+}
+
+static void
+cleanup_sockettab(void)
+{
+ u_int i;
+ for (i = 0; i < sockets_alloc; i++) {
+ if (sockets[i].type != AUTH_UNUSED)
+ close_socket(sockets + i);
+ }
+ free(sockets);
+ sockets = NULL;
+ sockets_alloc = 0;
+}
+
+static void
+reset_sockettab(int devnull)
+{
+ int fd;
+
+ cleanup_sockettab();
+ if ((fd = dup(devnull)) == -1) {
+ error_f("dup: %s", strerror(errno));
+ abort();
+ }
+ new_socket(AUTH_CONNECTION, fd);
+ assert(sockets[0].type == AUTH_CONNECTION);
+ assert(sockets[0].fd == fd);
+}
+
+#define MAX_MESSAGES 256
+void
+test_one(const uint8_t* s, size_t slen)
+{
+ static int devnull = -1;
+ size_t i, olen, nlen;
+
+ if (devnull == -1) {
+ log_init(__progname, SYSLOG_LEVEL_DEBUG3,
+ SYSLOG_FACILITY_AUTH, 1);
+ devnull = devnull_or_die();
+ allowed_providers = xstrdup("");
+ setenv("DISPLAY", "", 1); /* ban askpass */
+ }
+
+ reset_idtab();
+ reset_sockettab(devnull);
+ (void)sshbuf_put(sockets[0].input, s, slen);
+ for (i = 0; i < MAX_MESSAGES; i++) {
+ olen = sshbuf_len(sockets[0].input);
+ process_message(0);
+ nlen = sshbuf_len(sockets[0].input);
+ if (nlen == 0 || nlen == olen)
+ break;
+ }
+ cleanup_idtab();
+ cleanup_sockettab();
+}
diff --git a/regress/misc/fuzz-harness/authkeys_fuzz.cc b/regress/misc/fuzz-harness/authkeys_fuzz.cc
new file mode 100644
index 0000000..8b3e54e
--- /dev/null
+++ b/regress/misc/fuzz-harness/authkeys_fuzz.cc
@@ -0,0 +1,81 @@
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <unistd.h>
+
+extern "C" {
+
+#include "hostfile.h"
+#include "auth.h"
+#include "auth-options.h"
+#include "sshkey.h"
+
+// testdata/id_ed25519.pub and testdata/id_ed25519-cert.pub
+const char *pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDPQXmEVMVLmeFRyafKMVWgPDkv8/uRBTwmcEDatZzMD";
+const char *certtext = "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIMDQjYH6XRzH3j3MW1DdjCoAfvrHfgjnVGF+sLK0pBfqAAAAIDPQXmEVMVLmeFRyafKMVWgPDkv8/uRBTwmcEDatZzMDAAAAAAAAA+sAAAABAAAAB3VseXNzZXMAAAAXAAAAB3VseXNzZXMAAAAIb2R5c3NldXMAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgM9BeYRUxUuZ4VHJp8oxVaA8OS/z+5EFPCZwQNq1nMwMAAABTAAAAC3NzaC1lZDI1NTE5AAAAQBj0og+s09/HpwdHZbzN0twooKPDWWrxGfnP1Joy6cDnY2BCSQ7zg9vbq11kLF8H/sKOTZWAQrUZ7LlChOu9Ogw= id_ed25519.pub";
+
+// stubs
+void auth_debug_add(const char *fmt,...)
+{
+}
+
+void
+auth_log_authopts(const char *loc, const struct sshauthopt *opts, int do_remote)
+{
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ char *tmp, *o, *cp = (char *)malloc(size + 1 + strlen(pubkey) + 1);
+ struct sshauthopt *opts = NULL;
+ struct passwd *pw = getpwuid(getuid());
+ static struct sshkey *key, *cert;
+
+ if (key == NULL) {
+ if ((key = sshkey_new(KEY_UNSPEC)) == NULL ||
+ (cert = sshkey_new(KEY_UNSPEC)) == NULL)
+ abort();
+ if ((o = tmp = strdup(pubkey)) == NULL ||
+ sshkey_read(key, &tmp) != 0)
+ abort();
+ free(o);
+ if ((o = tmp = strdup(certtext)) == NULL ||
+ sshkey_read(cert, &tmp) != 0)
+ abort();
+ free(o);
+ }
+ if (cp == NULL || pw == NULL || key == NULL || cert == NULL)
+ abort();
+
+ // Cleanup whitespace at input EOL.
+ for (; size > 0 && strchr(" \t\r\n", data[size - 1]) != NULL; size--) ;
+
+ // Append a pubkey that will match.
+ memcpy(cp, data, size);
+ cp[size] = ' ';
+ memcpy(cp + size + 1, pubkey, strlen(pubkey) + 1);
+
+ // Try key.
+ if ((tmp = strdup(cp)) == NULL)
+ abort();
+ (void) auth_check_authkey_line(pw, key, tmp, "127.0.0.1", "localhost",
+ "fuzz", &opts);
+ free(tmp);
+ sshauthopt_free(opts);
+
+ // Try cert.
+ if ((tmp = strdup(cp)) == NULL)
+ abort();
+ (void) auth_check_authkey_line(pw, cert, tmp, "127.0.0.1", "localhost",
+ "fuzz", &opts);
+ free(tmp);
+ sshauthopt_free(opts);
+
+ free(cp);
+ return 0;
+}
+
+} // extern "C"
diff --git a/regress/misc/fuzz-harness/authopt_fuzz.cc b/regress/misc/fuzz-harness/authopt_fuzz.cc
new file mode 100644
index 0000000..a76d5a3
--- /dev/null
+++ b/regress/misc/fuzz-harness/authopt_fuzz.cc
@@ -0,0 +1,33 @@
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+extern "C" {
+
+#include "auth-options.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ char *cp = (char *)malloc(size + 1);
+ struct sshauthopt *opts = NULL, *merge = NULL, *add = sshauthopt_new();
+
+ if (cp == NULL || add == NULL)
+ goto out;
+ memcpy(cp, data, size);
+ cp[size] = '\0';
+ if ((opts = sshauthopt_parse(cp, NULL)) == NULL)
+ goto out;
+ if ((merge = sshauthopt_merge(opts, add, NULL)) == NULL)
+ goto out;
+
+ out:
+ free(cp);
+ sshauthopt_free(add);
+ sshauthopt_free(opts);
+ sshauthopt_free(merge);
+ return 0;
+}
+
+} // extern "C"
diff --git a/regress/misc/fuzz-harness/fixed-keys.h b/regress/misc/fuzz-harness/fixed-keys.h
new file mode 100644
index 0000000..c6e7c6c
--- /dev/null
+++ b/regress/misc/fuzz-harness/fixed-keys.h
@@ -0,0 +1,119 @@
+/*
+ * Some keys used by fuzzers
+ */
+
+#define PRIV_RSA \
+"-----BEGIN OPENSSH PRIVATE KEY-----\n"\
+"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn\n"\
+"NhAAAAAwEAAQAAAQEA3+epf+VGKoGPaAZXrf6S0cyumQnddkGBnVFX0A5eh37RtLug0qY5\n"\
+"thxsBUbGGVr9mTd2QXwLujBwYg5l1MP/Fmg+5312Zgx9pHmS+qKULbar0hlNgptNEb+aNU\n"\
+"d3o9qg3aXqXm7+ZnjAV05ef/mxNRN2ZvuEkw7cRppTJcbBI+vF3lXuCXnX2klDI95Gl2AW\n"\
+"3WHRtanqLHZXuBkjjRBDKc7MUq/GP1hmLiAd95dvU7fZjRlIEsP84zGEI1Fb0L/kmPHcOt\n"\
+"iVfHft8CtmC9v6+94JrOiPBBNScV+dyrgAGPsdKdr/1vIpQmCNiI8s3PCiD8J7ZiBaYm0I\n"\
+"8fq5G/qnUwAAA7ggw2dXIMNnVwAAAAdzc2gtcnNhAAABAQDf56l/5UYqgY9oBlet/pLRzK\n"\
+"6ZCd12QYGdUVfQDl6HftG0u6DSpjm2HGwFRsYZWv2ZN3ZBfAu6MHBiDmXUw/8WaD7nfXZm\n"\
+"DH2keZL6opQttqvSGU2Cm00Rv5o1R3ej2qDdpepebv5meMBXTl5/+bE1E3Zm+4STDtxGml\n"\
+"MlxsEj68XeVe4JedfaSUMj3kaXYBbdYdG1qeosdle4GSONEEMpzsxSr8Y/WGYuIB33l29T\n"\
+"t9mNGUgSw/zjMYQjUVvQv+SY8dw62JV8d+3wK2YL2/r73gms6I8EE1JxX53KuAAY+x0p2v\n"\
+"/W8ilCYI2Ijyzc8KIPwntmIFpibQjx+rkb+qdTAAAAAwEAAQAAAQEArWm5B4tFasppjUHM\n"\
+"SsAuajtCxtizI1Hc10EW59cZM4vvUzE2f6+qZvdgWj3UU/L7Et23w0QVuSCnCerox379ZB\n"\
+"ddEOFFAAiQjwBx65hbd4RRUymxtIQfjq18++LcMJW1nbVQ7c69ThQbtALIggmbS+ZE/8Gx\n"\
+"jkwmIrCH0Ww8TlpsPe+mNHuyNk7UEZoXLm22lNLqq5qkIL5JgT6M2iNJpMOJy9/CKi6kO4\n"\
+"JPuVwjdG4C5pBPaMN3KJ1IvAlSlLGNaXnfXcn85gWfsCjsZmH3liey2NJamqp/w83BrKUg\n"\
+"YZvMR2qeWZaKkFTahpzN5KRK1BFeB37O0P84Dzh1biDX8QAAAIEAiWXW8ePYFwLpa2mFIh\n"\
+"VvRTdcrN70rVK5eWVaL3pyS4vGA56Jixq86dHveOnbSY+iNb1jQidtXc8SWUt2wtHqZ32h\n"\
+"Lji9/hMSKqe9SEP3xvDRDmUJqsVw0ySyrFrzm4160QY6RKU3CIQCVFslMZ9fxmrfZ/hxoU\n"\
+"0X3FVsxmC4+kwAAACBAPOc1YERpV6PjANBrGR+1o1RCdACbm5myc42QzSNIaOZmgrYs+Gt\n"\
+"7+EcoqSdbJzHJNCNQfF+A+vjbIkFiuZqq/5wwr59qXx5OAlijLB/ywwKmTWq6lp//Zxny+\n"\
+"ka3sIGNO14eQvmxNDnlLL+RIZleCTEKBXSW6CZhr+uHMZFKKMtAAAAgQDrSkm+LbILB7H9\n"\
+"jxEBZLhv53aAn4u81kFKQOJ7PzzpBGSoD12i7oIJu5siSD5EKDNVEr+SvCf0ISU3BuMpzl\n"\
+"t3YrPrHRheOFhn5e3j0e//zB8rBC0DGB4CtTDdeh7rOXUL4K0pz+8wEpNkV62SWxhC6NRW\n"\
+"I79JhtGkh+GtcnkEfwAAAAAB\n"\
+"-----END OPENSSH PRIVATE KEY-----\n"
+#define PUB_RSA \
+"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDf56l/5UYqgY9oBlet/pLRzK6ZCd12QYGdUVfQDl6HftG0u6DSpjm2HGwFRsYZWv2ZN3ZBfAu6MHBiDmXUw/8WaD7nfXZmDH2keZL6opQttqvSGU2Cm00Rv5o1R3ej2qDdpepebv5meMBXTl5/+bE1E3Zm+4STDtxGmlMlxsEj68XeVe4JedfaSUMj3kaXYBbdYdG1qeosdle4GSONEEMpzsxSr8Y/WGYuIB33l29Tt9mNGUgSw/zjMYQjUVvQv+SY8dw62JV8d+3wK2YL2/r73gms6I8EE1JxX53KuAAY+x0p2v/W8ilCYI2Ijyzc8KIPwntmIFpibQjx+rkb+qdT"
+#define CERT_RSA \
+"ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg89JX6OBMYDSxER8fnU5y8xxeMCHR/hI0uVqdEhNyCpcAAAADAQABAAABAQDf56l/5UYqgY9oBlet/pLRzK6ZCd12QYGdUVfQDl6HftG0u6DSpjm2HGwFRsYZWv2ZN3ZBfAu6MHBiDmXUw/8WaD7nfXZmDH2keZL6opQttqvSGU2Cm00Rv5o1R3ej2qDdpepebv5meMBXTl5/+bE1E3Zm+4STDtxGmlMlxsEj68XeVe4JedfaSUMj3kaXYBbdYdG1qeosdle4GSONEEMpzsxSr8Y/WGYuIB33l29Tt9mNGUgSw/zjMYQjUVvQv+SY8dw62JV8d+3wK2YL2/r73gms6I8EE1JxX53KuAAY+x0p2v/W8ilCYI2Ijyzc8KIPwntmIFpibQjx+rkb+qdTAAAAAAAAA+0AAAABAAAAB3VseXNzZXMAAAAXAAAAB3VseXNzZXMAAAAIb2R5c3NldXMAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgM9BeYRUxUuZ4VHJp8oxVaA8OS/z+5EFPCZwQNq1nMwMAAABTAAAAC3NzaC1lZDI1NTE5AAAAQGCDA6PWw4x9bHQl0w7NqifHepumqD3dmyMx+hZGuPRon+TsyCjfytu7hWmV7l9XUF0fPQNFQ7FGat5e+7YUNgE= id_rsa.pub"
+#define PRIV_DSA \
+"-----BEGIN OPENSSH PRIVATE KEY-----\n"\
+"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsgAAAAdzc2gtZH\n"\
+"NzAAAAgQCsGTfjpQ465EOkfQXJM9BOvfRQE0fqlykAls+ncz+T7hrbeScRu8xpwzsznJNm\n"\
+"xlW8o6cUDiHmBJ5OHgamUC9N7YJeU/6fnOAZifgN8mqK6k8pKHuje8ANOiYgHLl0yiASQA\n"\
+"3//qMyzZ+W/hemoLSmLAbEqlfWVeyYx+wta1Vm+QAAABUAvWyehvUvdHvQxavYgS5p0t5Q\n"\
+"d7UAAACBAIRA9Yy+f4Kzqpv/qICPO3zk42UuP7WAhSW2nCbQdLlCiSTxcjKgcvXNRckwJP\n"\
+"44JjSHOtJy/AMtJrPIbLYG6KuWTdBlEHFiG6DafvLG+qPMSL2bPjXTOhuOMbCHIZ+5WBkW\n"\
+"THeG/Nv11iI01Of9V6tXkig23K370flkRkXFi9MdAAAAgCt6YUcQkNwG7B/e5M1FZsLP9O\n"\
+"kVB3BwLAOjmWdHpyhu3HpwSJa3XLEvhXN0i6IVI2KgPo/2GtYA6rHt14L+6u1pmhh8sAvQ\n"\
+"ksp3qZB+xh/NP+hBqf0sbHX0yYbzKOvI5SCc/kKK6yagcBZOsubM/KC8TxyVgmD5c6WzYs\n"\
+"h5TEpvAAAB2PHjRbbx40W2AAAAB3NzaC1kc3MAAACBAKwZN+OlDjrkQ6R9Bckz0E699FAT\n"\
+"R+qXKQCWz6dzP5PuGtt5JxG7zGnDOzOck2bGVbyjpxQOIeYEnk4eBqZQL03tgl5T/p+c4B\n"\
+"mJ+A3yaorqTykoe6N7wA06JiAcuXTKIBJADf/+ozLNn5b+F6agtKYsBsSqV9ZV7JjH7C1r\n"\
+"VWb5AAAAFQC9bJ6G9S90e9DFq9iBLmnS3lB3tQAAAIEAhED1jL5/grOqm/+ogI87fOTjZS\n"\
+"4/tYCFJbacJtB0uUKJJPFyMqBy9c1FyTAk/jgmNIc60nL8Ay0ms8hstgboq5ZN0GUQcWIb\n"\
+"oNp+8sb6o8xIvZs+NdM6G44xsIchn7lYGRZMd4b82/XWIjTU5/1Xq1eSKDbcrfvR+WRGRc\n"\
+"WL0x0AAACAK3phRxCQ3AbsH97kzUVmws/06RUHcHAsA6OZZ0enKG7cenBIlrdcsS+Fc3SL\n"\
+"ohUjYqA+j/Ya1gDqse3Xgv7q7WmaGHywC9CSynepkH7GH80/6EGp/SxsdfTJhvMo68jlIJ\n"\
+"z+QorrJqBwFk6y5sz8oLxPHJWCYPlzpbNiyHlMSm8AAAAUUA+OGldMi76ClO/sstpdbBUE\n"\
+"lq8AAAAAAQI=\n"\
+"-----END OPENSSH PRIVATE KEY-----\n"
+#define PUB_DSA \
+"ssh-dss AAAAB3NzaC1kc3MAAACBAKwZN+OlDjrkQ6R9Bckz0E699FATR+qXKQCWz6dzP5PuGtt5JxG7zGnDOzOck2bGVbyjpxQOIeYEnk4eBqZQL03tgl5T/p+c4BmJ+A3yaorqTykoe6N7wA06JiAcuXTKIBJADf/+ozLNn5b+F6agtKYsBsSqV9ZV7JjH7C1rVWb5AAAAFQC9bJ6G9S90e9DFq9iBLmnS3lB3tQAAAIEAhED1jL5/grOqm/+ogI87fOTjZS4/tYCFJbacJtB0uUKJJPFyMqBy9c1FyTAk/jgmNIc60nL8Ay0ms8hstgboq5ZN0GUQcWIboNp+8sb6o8xIvZs+NdM6G44xsIchn7lYGRZMd4b82/XWIjTU5/1Xq1eSKDbcrfvR+WRGRcWL0x0AAACAK3phRxCQ3AbsH97kzUVmws/06RUHcHAsA6OZZ0enKG7cenBIlrdcsS+Fc3SLohUjYqA+j/Ya1gDqse3Xgv7q7WmaGHywC9CSynepkH7GH80/6EGp/SxsdfTJhvMo68jlIJz+QorrJqBwFk6y5sz8oLxPHJWCYPlzpbNiyHlMSm8="
+#define CERT_DSA \
+"ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAguF716Yub+vVKNlONKLsfxGYWkRe/PyjfYdGRTsFaDvAAAACBAKwZN+OlDjrkQ6R9Bckz0E699FATR+qXKQCWz6dzP5PuGtt5JxG7zGnDOzOck2bGVbyjpxQOIeYEnk4eBqZQL03tgl5T/p+c4BmJ+A3yaorqTykoe6N7wA06JiAcuXTKIBJADf/+ozLNn5b+F6agtKYsBsSqV9ZV7JjH7C1rVWb5AAAAFQC9bJ6G9S90e9DFq9iBLmnS3lB3tQAAAIEAhED1jL5/grOqm/+ogI87fOTjZS4/tYCFJbacJtB0uUKJJPFyMqBy9c1FyTAk/jgmNIc60nL8Ay0ms8hstgboq5ZN0GUQcWIboNp+8sb6o8xIvZs+NdM6G44xsIchn7lYGRZMd4b82/XWIjTU5/1Xq1eSKDbcrfvR+WRGRcWL0x0AAACAK3phRxCQ3AbsH97kzUVmws/06RUHcHAsA6OZZ0enKG7cenBIlrdcsS+Fc3SLohUjYqA+j/Ya1gDqse3Xgv7q7WmaGHywC9CSynepkH7GH80/6EGp/SxsdfTJhvMo68jlIJz+QorrJqBwFk6y5sz8oLxPHJWCYPlzpbNiyHlMSm8AAAAAAAAD6AAAAAEAAAAHdWx5c3NlcwAAABcAAAAHdWx5c3NlcwAAAAhvZHlzc2V1cwAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAz0F5hFTFS5nhUcmnyjFVoDw5L/P7kQU8JnBA2rWczAwAAAFMAAAALc3NoLWVkMjU1MTkAAABAjMQEZcbdUYJBjIC4GxByFDOb8tv71vDZdx7irHwaqIjx5rzpJUuOV1r8ZO4kY+Yaiun1yrWj2QYkfJrHBvD1DA== id_dsa.pub"
+#define PRIV_ECDSA \
+"-----BEGIN OPENSSH PRIVATE KEY-----\n"\
+"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n"\
+"1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTDJ0VlMv+0rguNzaJ1DF2KueHaxRSQ\n"\
+"6LpIxGbulrg1a8RPbnMXwag5GcDiDllD2lDUJUuBEWyjXA0rZoZX35ELAAAAoE/Bbr5PwW\n"\
+"6+AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMMnRWUy/7SuC43N\n"\
+"onUMXYq54drFFJDoukjEZu6WuDVrxE9ucxfBqDkZwOIOWUPaUNQlS4ERbKNcDStmhlffkQ\n"\
+"sAAAAhAIhE6hCID5oOm1TDktc++KFKyScjLifcZ6Cgv5xSSyLOAAAAAAECAwQFBgc=\n"\
+"-----END OPENSSH PRIVATE KEY-----\n"
+#define PUB_ECDSA \
+"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMMnRWUy/7SuC43NonUMXYq54drFFJDoukjEZu6WuDVrxE9ucxfBqDkZwOIOWUPaUNQlS4ERbKNcDStmhlffkQs="
+#define CERT_ECDSA \
+"ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgVJZuM/1AOe6n++qRWMyUuAThYqLvvQxj5CGflLODp60AAAAIbmlzdHAyNTYAAABBBMMnRWUy/7SuC43NonUMXYq54drFFJDoukjEZu6WuDVrxE9ucxfBqDkZwOIOWUPaUNQlS4ERbKNcDStmhlffkQsAAAAAAAAD6QAAAAEAAAAHdWx5c3NlcwAAABcAAAAHdWx5c3NlcwAAAAhvZHlzc2V1cwAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAz0F5hFTFS5nhUcmnyjFVoDw5L/P7kQU8JnBA2rWczAwAAAFMAAAALc3NoLWVkMjU1MTkAAABAtdJpcF6ZmQL+ueices4QZeL7AK8Xuo08jyLgiolhjKy2jj4LSUki4aX/ZeZeJuby1ovGrfaeFAgx3itPLR7IAQ== id_ecdsa.pub"
+#define PRIV_ED25519 \
+"-----BEGIN OPENSSH PRIVATE KEY-----\n"\
+"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n"\
+"QyNTUxOQAAACAz0F5hFTFS5nhUcmnyjFVoDw5L/P7kQU8JnBA2rWczAwAAAIhWlP99VpT/\n"\
+"fQAAAAtzc2gtZWQyNTUxOQAAACAz0F5hFTFS5nhUcmnyjFVoDw5L/P7kQU8JnBA2rWczAw\n"\
+"AAAEDE1rlcMC0s0X3TKVZAOVavZOywwkXw8tO5dLObxaCMEDPQXmEVMVLmeFRyafKMVWgP\n"\
+"Dkv8/uRBTwmcEDatZzMDAAAAAAECAwQF\n"\
+"-----END OPENSSH PRIVATE KEY-----\n"
+#define PUB_ED25519 \
+"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDPQXmEVMVLmeFRyafKMVWgPDkv8/uRBTwmcEDatZzMD"
+#define CERT_ED25519 \
+"ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIMDQjYH6XRzH3j3MW1DdjCoAfvrHfgjnVGF+sLK0pBfqAAAAIDPQXmEVMVLmeFRyafKMVWgPDkv8/uRBTwmcEDatZzMDAAAAAAAAA+sAAAABAAAAB3VseXNzZXMAAAAXAAAAB3VseXNzZXMAAAAIb2R5c3NldXMAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgM9BeYRUxUuZ4VHJp8oxVaA8OS/z+5EFPCZwQNq1nMwMAAABTAAAAC3NzaC1lZDI1NTE5AAAAQBj0og+s09/HpwdHZbzN0twooKPDWWrxGfnP1Joy6cDnY2BCSQ7zg9vbq11kLF8H/sKOTZWAQrUZ7LlChOu9Ogw= id_ed25519.pub"
+#define PRIV_ECDSA_SK \
+"-----BEGIN OPENSSH PRIVATE KEY-----\n"\
+"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAfwAAACJzay1lY2\n"\
+"RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQTYyU76zop1\n"\
+"VOb4DfKWYnR5b0TOC3zw8DzObAfHWB5o6xls+tOYiEleXvIEi00Da2iCK47habZTOhLyeB\n"\
+"X2Avu5AAAABHNzaDoAAAGYqUAQSKlAEEgAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBv\n"\
+"cGVuc3NoLmNvbQAAAAhuaXN0cDI1NgAAAEEE2MlO+s6KdVTm+A3ylmJ0eW9Ezgt88PA8zm\n"\
+"wHx1geaOsZbPrTmIhJXl7yBItNA2togiuO4Wm2UzoS8ngV9gL7uQAAAARzc2g6AQAAAOMt\n"\
+"LS0tLUJFR0lOIEVDIFBSSVZBVEUgS0VZLS0tLS0KTUhjQ0FRRUVJSHFsZjNsWTkxZFhwUn\n"\
+"dYZDBrS0lYWmNpeDRRcDBNSU15Ny9JMUxXSTFuWG9Bb0dDQ3FHU000OQpBd0VIb1VRRFFn\n"\
+"QUUyTWxPK3M2S2RWVG0rQTN5bG1KMGVXOUV6Z3Q4OFBBOHptd0h4MWdlYU9zWmJQclRtSW\n"\
+"hKClhsN3lCSXROQTJ0b2dpdU80V20yVXpvUzhuZ1Y5Z0w3dVE9PQotLS0tLUVORCBFQyBQ\n"\
+"UklWQVRFIEtFWS0tLS0tCgAAAAAAAAAbZGptQGRqbS5zeWQuY29ycC5nb29nbGUuY29tAQ\n"\
+"IDBAUG\n"\
+"-----END OPENSSH PRIVATE KEY-----\n"
+#define PUB_ECDSA_SK \
+"sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBNjJTvrOinVU5vgN8pZidHlvRM4LfPDwPM5sB8dYHmjrGWz605iISV5e8gSLTQNraIIrjuFptlM6EvJ4FfYC+7kAAAAEc3NoOg=="
+#define CERT_ECDSA_SK \
+"sk-ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAK3NrLWVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgKLHtIca++5VoDrUAXU/KqGJZ7jZEnuJSTvt7VrYY9foAAAAIbmlzdHAyNTYAAABBBNjJTvrOinVU5vgN8pZidHlvRM4LfPDwPM5sB8dYHmjrGWz605iISV5e8gSLTQNraIIrjuFptlM6EvJ4FfYC+7kAAAAEc3NoOgAAAAAAAAPqAAAAAQAAAAd1bHlzc2VzAAAAFwAAAAd1bHlzc2VzAAAACG9keXNzZXVzAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIDPQXmEVMVLmeFRyafKMVWgPDkv8/uRBTwmcEDatZzMDAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEB1naZOQDLaDr+fwn6E9x8/8HeiaUubDzPexfNQMz+m/7RD0gd5uJhHYUfDb5+/sIx1I7bUEeRIDkBbmZ2foo0E"
+#define PRIV_ED25519_SK \
+"-----BEGIN OPENSSH PRIVATE KEY-----\n"\
+"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAABpzay1zc2\n"\
+"gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACCTJtH10vWhIDxd62edvMLg9u2cwYKyqa7332je\n"\
+"RArHjAAAAARzc2g6AAAAwN7vvE3e77xNAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY2\n"\
+"9tAAAAIJMm0fXS9aEgPF3rZ528wuD27ZzBgrKprvffaN5ECseMAAAABHNzaDoBAAAAQEsS\n"\
+"xLFiVzfpH2mt9xh8i/zmHV646Hud4QruNBAGNl8gkybR9dL1oSA8XetnnbzC4PbtnMGCsq\n"\
+"mu999o3kQKx4wAAAAAAAAAG2RqbUBkam0uc3lkLmNvcnAuZ29vZ2xlLmNvbQECAwQFBg==\n"\
+"-----END OPENSSH PRIVATE KEY-----\n"
+#define PUB_ED25519_SK \
+"sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIJMm0fXS9aEgPF3rZ528wuD27ZzBgrKprvffaN5ECseMAAAABHNzaDo="
+#define CERT_ED25519_SK \
+"sk-ssh-ed25519-cert-v01@openssh.com AAAAI3NrLXNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIJiT+C/VLMWholFZ4xhOyJr0nSLZSFRIM3I07wUNTRPaAAAAIJMm0fXS9aEgPF3rZ528wuD27ZzBgrKprvffaN5ECseMAAAABHNzaDoAAAAAAAAD7AAAAAEAAAAHdWx5c3NlcwAAABcAAAAHdWx5c3NlcwAAAAhvZHlzc2V1cwAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAz0F5hFTFS5nhUcmnyjFVoDw5L/P7kQU8JnBA2rWczAwAAAFMAAAALc3NoLWVkMjU1MTkAAABAX0Pu13B94pVR3qq8MJQGkOS1Cd7AAM1k6O2VSwyDPM/LfsWIQ4ywgxDmk3hjXWOY7BqljuMxo5VO4JymEIhQBA=="
diff --git a/regress/misc/fuzz-harness/kex_fuzz.cc b/regress/misc/fuzz-harness/kex_fuzz.cc
new file mode 100644
index 0000000..d38ca85
--- /dev/null
+++ b/regress/misc/fuzz-harness/kex_fuzz.cc
@@ -0,0 +1,460 @@
+// libfuzzer driver for key exchange fuzzing.
+
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+extern "C" {
+
+#include "includes.h"
+#include "ssherr.h"
+#include "ssh_api.h"
+#include "sshbuf.h"
+#include "packet.h"
+#include "myproposal.h"
+#include "xmalloc.h"
+#include "authfile.h"
+#include "log.h"
+
+#include "fixed-keys.h"
+
+// Define if you want to generate traces.
+/* #define STANDALONE 1 */
+
+static int prepare_key(struct shared_state *st, int keytype, int bits);
+
+struct shared_state {
+ size_t nkeys;
+ struct sshkey **privkeys, **pubkeys;
+};
+
+struct test_state {
+ struct sshbuf *smsgs, *cmsgs; /* output, for standalone mode */
+ struct sshbuf *sin, *cin; /* input; setup per-test in do_kex_with_key */
+ struct sshbuf *s_template, *c_template; /* main copy of input */
+};
+
+static int
+do_send_and_receive(struct ssh *from, struct ssh *to,
+ struct sshbuf *store, int clobber, size_t *n)
+{
+ u_char type;
+ size_t len;
+ const u_char *buf;
+ int r;
+
+ for (*n = 0;; (*n)++) {
+ if ((r = ssh_packet_next(from, &type)) != 0) {
+ debug_fr(r, "ssh_packet_next");
+ return r;
+ }
+ if (type != 0)
+ return 0;
+ buf = ssh_output_ptr(from, &len);
+ debug_f("%zu%s", len, clobber ? " ignore" : "");
+ if (len == 0)
+ return 0;
+ if ((r = ssh_output_consume(from, len)) != 0) {
+ debug_fr(r, "ssh_output_consume");
+ return r;
+ }
+ if (store != NULL && (r = sshbuf_put(store, buf, len)) != 0) {
+ debug_fr(r, "sshbuf_put");
+ return r;
+ }
+ if (!clobber && (r = ssh_input_append(to, buf, len)) != 0) {
+ debug_fr(r, "ssh_input_append");
+ return r;
+ }
+ }
+}
+
+static int
+run_kex(struct test_state *ts, struct ssh *client, struct ssh *server)
+{
+ int r = 0;
+ size_t cn, sn;
+
+ /* If fuzzing, replace server/client input */
+ if (ts->sin != NULL) {
+ if ((r = ssh_input_append(server, sshbuf_ptr(ts->sin),
+ sshbuf_len(ts->sin))) != 0) {
+ error_fr(r, "ssh_input_append");
+ return r;
+ }
+ sshbuf_reset(ts->sin);
+ }
+ if (ts->cin != NULL) {
+ if ((r = ssh_input_append(client, sshbuf_ptr(ts->cin),
+ sshbuf_len(ts->cin))) != 0) {
+ error_fr(r, "ssh_input_append");
+ return r;
+ }
+ sshbuf_reset(ts->cin);
+ }
+ while (!server->kex->done || !client->kex->done) {
+ cn = sn = 0;
+ debug_f("S:");
+ if ((r = do_send_and_receive(server, client,
+ ts->smsgs, ts->cin != NULL, &sn)) != 0) {
+ debug_fr(r, "S->C");
+ break;
+ }
+ debug_f("C:");
+ if ((r = do_send_and_receive(client, server,
+ ts->cmsgs, ts->sin != NULL, &cn)) != 0) {
+ debug_fr(r, "C->S");
+ break;
+ }
+ if (cn == 0 && sn == 0) {
+ debug_f("kex stalled");
+ r = SSH_ERR_PROTOCOL_ERROR;
+ break;
+ }
+ }
+ debug_fr(r, "done");
+ return r;
+}
+
+static void
+store_key(struct shared_state *st, struct sshkey *pubkey,
+ struct sshkey *privkey)
+{
+ if (st == NULL || pubkey->type < 0 || pubkey->type > INT_MAX ||
+ privkey->type != pubkey->type ||
+ ((size_t)pubkey->type < st->nkeys &&
+ st->pubkeys[pubkey->type] != NULL))
+ abort();
+ if ((size_t)pubkey->type >= st->nkeys) {
+ st->pubkeys = (struct sshkey **)xrecallocarray(st->pubkeys,
+ st->nkeys, pubkey->type + 1, sizeof(*st->pubkeys));
+ st->privkeys = (struct sshkey **)xrecallocarray(st->privkeys,
+ st->nkeys, privkey->type + 1, sizeof(*st->privkeys));
+ st->nkeys = privkey->type + 1;
+ }
+ debug_f("store %s at %d", sshkey_ssh_name(pubkey), pubkey->type);
+ st->pubkeys[pubkey->type] = pubkey;
+ st->privkeys[privkey->type] = privkey;
+}
+
+static int
+prepare_keys(struct shared_state *st)
+{
+ if (prepare_key(st, KEY_RSA, 2048) != 0 ||
+ prepare_key(st, KEY_DSA, 1024) != 0 ||
+ prepare_key(st, KEY_ECDSA, 256) != 0 ||
+ prepare_key(st, KEY_ED25519, 256) != 0) {
+ error_f("key prepare failed");
+ return -1;
+ }
+ return 0;
+}
+
+static struct sshkey *
+get_pubkey(struct shared_state *st, int keytype)
+{
+ if (st == NULL || keytype < 0 || (size_t)keytype >= st->nkeys ||
+ st->pubkeys == NULL || st->pubkeys[keytype] == NULL)
+ abort();
+ return st->pubkeys[keytype];
+}
+
+static struct sshkey *
+get_privkey(struct shared_state *st, int keytype)
+{
+ if (st == NULL || keytype < 0 || (size_t)keytype >= st->nkeys ||
+ st->privkeys == NULL || st->privkeys[keytype] == NULL)
+ abort();
+ return st->privkeys[keytype];
+}
+
+static int
+do_kex_with_key(struct shared_state *st, struct test_state *ts,
+ const char *kex, int keytype)
+{
+ struct ssh *client = NULL, *server = NULL;
+ struct sshkey *privkey = NULL, *pubkey = NULL;
+ struct sshbuf *state = NULL;
+ struct kex_params kex_params;
+ const char *ccp, *proposal[PROPOSAL_MAX] = { KEX_CLIENT };
+ char *myproposal[PROPOSAL_MAX] = {0}, *keyname = NULL;
+ int i, r;
+
+ ts->cin = ts->sin = NULL;
+ if (ts->c_template != NULL &&
+ (ts->cin = sshbuf_fromb(ts->c_template)) == NULL)
+ abort();
+ if (ts->s_template != NULL &&
+ (ts->sin = sshbuf_fromb(ts->s_template)) == NULL)
+ abort();
+
+ pubkey = get_pubkey(st, keytype);
+ privkey = get_privkey(st, keytype);
+ keyname = xstrdup(sshkey_ssh_name(privkey));
+ if (ts->cin != NULL) {
+ debug_f("%s %s clobber client %zu", kex, keyname,
+ sshbuf_len(ts->cin));
+ } else if (ts->sin != NULL) {
+ debug_f("%s %s clobber server %zu", kex, keyname,
+ sshbuf_len(ts->sin));
+ } else
+ debug_f("%s %s noclobber", kex, keyname);
+
+ for (i = 0; i < PROPOSAL_MAX; i++) {
+ ccp = proposal[i];
+#ifdef CIPHER_NONE_AVAIL
+ if (i == PROPOSAL_ENC_ALGS_CTOS || i == PROPOSAL_ENC_ALGS_STOC)
+ ccp = "none";
+#endif
+ if (i == PROPOSAL_SERVER_HOST_KEY_ALGS)
+ ccp = keyname;
+ else if (i == PROPOSAL_KEX_ALGS && kex != NULL)
+ ccp = kex;
+ if ((myproposal[i] = strdup(ccp)) == NULL) {
+ error_f("strdup prop %d", i);
+ goto fail;
+ }
+ }
+ memcpy(kex_params.proposal, myproposal, sizeof(myproposal));
+ if ((r = ssh_init(&client, 0, &kex_params)) != 0) {
+ error_fr(r, "init client");
+ goto fail;
+ }
+ if ((r = ssh_init(&server, 1, &kex_params)) != 0) {
+ error_fr(r, "init server");
+ goto fail;
+ }
+ if ((r = ssh_add_hostkey(server, privkey)) != 0 ||
+ (r = ssh_add_hostkey(client, pubkey)) != 0) {
+ error_fr(r, "add hostkeys");
+ goto fail;
+ }
+ if ((r = run_kex(ts, client, server)) != 0) {
+ error_fr(r, "kex");
+ goto fail;
+ }
+ /* XXX rekex, set_state, etc */
+ fail:
+ for (i = 0; i < PROPOSAL_MAX; i++)
+ free(myproposal[i]);
+ sshbuf_free(ts->sin);
+ sshbuf_free(ts->cin);
+ sshbuf_free(state);
+ ssh_free(client);
+ ssh_free(server);
+ free(keyname);
+ return r;
+}
+
+static int
+prepare_key(struct shared_state *st, int kt, int bits)
+{
+ const char *pubstr = NULL;
+ const char *privstr = NULL;
+ char *tmp, *cp;
+ struct sshkey *privkey = NULL, *pubkey = NULL;
+ struct sshbuf *b = NULL;
+ int r;
+
+ switch (kt) {
+ case KEY_RSA:
+ pubstr = PUB_RSA;
+ privstr = PRIV_RSA;
+ break;
+ case KEY_DSA:
+ pubstr = PUB_DSA;
+ privstr = PRIV_DSA;
+ break;
+ case KEY_ECDSA:
+ pubstr = PUB_ECDSA;
+ privstr = PRIV_ECDSA;
+ break;
+ case KEY_ED25519:
+ pubstr = PUB_ED25519;
+ privstr = PRIV_ED25519;
+ break;
+ default:
+ abort();
+ }
+ if ((b = sshbuf_from(privstr, strlen(privstr))) == NULL)
+ abort();
+ if ((r = sshkey_parse_private_fileblob(b, "", &privkey, NULL)) != 0) {
+ error_fr(r, "priv %d", kt);
+ abort();
+ }
+ sshbuf_free(b);
+ tmp = cp = xstrdup(pubstr);
+ if ((pubkey = sshkey_new(KEY_UNSPEC)) == NULL)
+ abort();
+ if ((r = sshkey_read(pubkey, &cp)) != 0) {
+ error_fr(r, "pub %d", kt);
+ abort();
+ }
+ free(tmp);
+
+ store_key(st, pubkey, privkey);
+ return 0;
+}
+
+#if defined(STANDALONE)
+
+#if 0 /* use this if generating new keys to embed above */
+static int
+prepare_key(struct shared_state *st, int keytype, int bits)
+{
+ struct sshkey *privkey = NULL, *pubkey = NULL;
+ int r;
+
+ if ((r = sshkey_generate(keytype, bits, &privkey)) != 0) {
+ error_fr(r, "generate");
+ abort();
+ }
+ if ((r = sshkey_from_private(privkey, &pubkey)) != 0) {
+ error_fr(r, "make pubkey");
+ abort();
+ }
+ store_key(st, pubkey, privkey);
+ return 0;
+}
+#endif
+
+int main(void)
+{
+ static struct shared_state *st;
+ struct test_state *ts;
+ const int keytypes[] = { KEY_RSA, KEY_DSA, KEY_ECDSA, KEY_ED25519, -1 };
+ static const char * const kextypes[] = {
+ "sntrup761x25519-sha512@openssh.com",
+ "curve25519-sha256@libssh.org",
+ "ecdh-sha2-nistp256",
+ "diffie-hellman-group1-sha1",
+ "diffie-hellman-group-exchange-sha1",
+ NULL,
+ };
+ int i, j;
+ char *path;
+ FILE *f;
+
+ log_init("kex_fuzz", SYSLOG_LEVEL_DEBUG3, SYSLOG_FACILITY_AUTH, 1);
+
+ if (st == NULL) {
+ st = (struct shared_state *)xcalloc(1, sizeof(*st));
+ prepare_keys(st);
+ }
+ /* Run each kex method for each key and save client/server packets */
+ for (i = 0; keytypes[i] != -1; i++) {
+ for (j = 0; kextypes[j] != NULL; j++) {
+ ts = (struct test_state *)xcalloc(1, sizeof(*ts));
+ ts->smsgs = sshbuf_new();
+ ts->cmsgs = sshbuf_new();
+ do_kex_with_key(st, ts, kextypes[j], keytypes[i]);
+ xasprintf(&path, "S2C-%s-%s",
+ kextypes[j], sshkey_type(st->pubkeys[keytypes[i]]));
+ debug_f("%s", path);
+ if ((f = fopen(path, "wb+")) == NULL)
+ abort();
+ if (fwrite(sshbuf_ptr(ts->smsgs), 1,
+ sshbuf_len(ts->smsgs), f) != sshbuf_len(ts->smsgs))
+ abort();
+ fclose(f);
+ free(path);
+ //sshbuf_dump(ts->smsgs, stderr);
+ xasprintf(&path, "C2S-%s-%s",
+ kextypes[j], sshkey_type(st->pubkeys[keytypes[i]]));
+ debug_f("%s", path);
+ if ((f = fopen(path, "wb+")) == NULL)
+ abort();
+ if (fwrite(sshbuf_ptr(ts->cmsgs), 1,
+ sshbuf_len(ts->cmsgs), f) != sshbuf_len(ts->cmsgs))
+ abort();
+ fclose(f);
+ free(path);
+ //sshbuf_dump(ts->cmsgs, stderr);
+ sshbuf_free(ts->smsgs);
+ sshbuf_free(ts->cmsgs);
+ free(ts);
+ }
+ }
+ for (i = 0; keytypes[i] != -1; i++) {
+ xasprintf(&path, "%s.priv",
+ sshkey_type(st->privkeys[keytypes[i]]));
+ debug_f("%s", path);
+ if (sshkey_save_private(st->privkeys[keytypes[i]], path,
+ "", "", SSHKEY_PRIVATE_OPENSSH, NULL, 0) != 0)
+ abort();
+ free(path);
+ xasprintf(&path, "%s.pub",
+ sshkey_type(st->pubkeys[keytypes[i]]));
+ debug_f("%s", path);
+ if (sshkey_save_public(st->pubkeys[keytypes[i]], path, "") != 0)
+ abort();
+ free(path);
+ }
+}
+#else /* !STANDALONE */
+static void
+do_kex(struct shared_state *st, struct test_state *ts, const char *kex)
+{
+ do_kex_with_key(st, ts, kex, KEY_RSA);
+ do_kex_with_key(st, ts, kex, KEY_DSA);
+ do_kex_with_key(st, ts, kex, KEY_ECDSA);
+ do_kex_with_key(st, ts, kex, KEY_ED25519);
+}
+
+static void
+kex_tests(struct shared_state *st, struct test_state *ts)
+{
+ do_kex(st, ts, "sntrup761x25519-sha512@openssh.com");
+ do_kex(st, ts, "curve25519-sha256@libssh.org");
+ do_kex(st, ts, "ecdh-sha2-nistp256");
+ do_kex(st, ts, "diffie-hellman-group1-sha1");
+ do_kex(st, ts, "diffie-hellman-group-exchange-sha1");
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ static struct shared_state *st;
+ struct test_state *ts;
+ u_char crbuf[SSH_MAX_PRE_BANNER_LINES * 4];
+ u_char zbuf[4096] = {0};
+ static LogLevel loglevel = SYSLOG_LEVEL_INFO;
+
+ if (st == NULL) {
+ if (getenv("DEBUG") != NULL || getenv("KEX_FUZZ_DEBUG") != NULL)
+ loglevel = SYSLOG_LEVEL_DEBUG3;
+ log_init("kex_fuzz",
+ loglevel, SYSLOG_FACILITY_AUTH, 1);
+ st = (struct shared_state *)xcalloc(1, sizeof(*st));
+ prepare_keys(st);
+ }
+
+ /* Ensure that we can complete (fail) banner exchange at least */
+ memset(crbuf, '\n', sizeof(crbuf));
+
+ ts = (struct test_state *)xcalloc(1, sizeof(*ts));
+ if ((ts->s_template = sshbuf_new()) == NULL ||
+ sshbuf_put(ts->s_template, data, size) != 0 ||
+ sshbuf_put(ts->s_template, crbuf, sizeof(crbuf)) != 0 ||
+ sshbuf_put(ts->s_template, zbuf, sizeof(zbuf)) != 0)
+ abort();
+ kex_tests(st, ts);
+ sshbuf_free(ts->s_template);
+ free(ts);
+
+ ts = (struct test_state *)xcalloc(1, sizeof(*ts));
+ if ((ts->c_template = sshbuf_new()) == NULL ||
+ sshbuf_put(ts->c_template, data, size) != 0 ||
+ sshbuf_put(ts->c_template, crbuf, sizeof(crbuf)) != 0 ||
+ sshbuf_put(ts->c_template, zbuf, sizeof(zbuf)) != 0)
+ abort();
+ kex_tests(st, ts);
+ sshbuf_free(ts->c_template);
+ free(ts);
+
+ return 0;
+}
+#endif /* STANDALONE */
+} /* extern "C" */
diff --git a/regress/misc/fuzz-harness/privkey_fuzz.cc b/regress/misc/fuzz-harness/privkey_fuzz.cc
new file mode 100644
index 0000000..ff0b0f7
--- /dev/null
+++ b/regress/misc/fuzz-harness/privkey_fuzz.cc
@@ -0,0 +1,21 @@
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+
+extern "C" {
+
+#include "sshkey.h"
+#include "sshbuf.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ struct sshkey *k = NULL;
+ struct sshbuf *b = sshbuf_from(data, size);
+ int r = sshkey_private_deserialize(b, &k);
+ if (r == 0) sshkey_free(k);
+ sshbuf_free(b);
+ return 0;
+}
+
+} // extern
+
diff --git a/regress/misc/fuzz-harness/pubkey_fuzz.cc b/regress/misc/fuzz-harness/pubkey_fuzz.cc
new file mode 100644
index 0000000..8bbc110
--- /dev/null
+++ b/regress/misc/fuzz-harness/pubkey_fuzz.cc
@@ -0,0 +1,18 @@
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+
+extern "C" {
+
+#include "sshkey.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ struct sshkey *k = NULL;
+ int r = sshkey_from_blob(data, size, &k);
+ if (r == 0) sshkey_free(k);
+ return 0;
+}
+
+} // extern
+
diff --git a/regress/misc/fuzz-harness/sig_fuzz.cc b/regress/misc/fuzz-harness/sig_fuzz.cc
new file mode 100644
index 0000000..b32502b
--- /dev/null
+++ b/regress/misc/fuzz-harness/sig_fuzz.cc
@@ -0,0 +1,62 @@
+// cc_fuzz_target test for public key parsing.
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+extern "C" {
+
+#include "includes.h"
+#include "sshkey.h"
+#include "ssherr.h"
+
+static struct sshkey *generate_or_die(int type, unsigned bits) {
+ int r;
+ struct sshkey *ret;
+ if ((r = sshkey_generate(type, bits, &ret)) != 0) {
+ fprintf(stderr, "generate(%d, %u): %s", type, bits, ssh_err(r));
+ abort();
+ }
+ return ret;
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t* sig, size_t slen)
+{
+#ifdef WITH_OPENSSL
+ static struct sshkey *rsa = generate_or_die(KEY_RSA, 2048);
+ static struct sshkey *dsa = generate_or_die(KEY_DSA, 1024);
+ static struct sshkey *ecdsa256 = generate_or_die(KEY_ECDSA, 256);
+ static struct sshkey *ecdsa384 = generate_or_die(KEY_ECDSA, 384);
+ static struct sshkey *ecdsa521 = generate_or_die(KEY_ECDSA, 521);
+#endif
+ struct sshkey_sig_details *details = NULL;
+ static struct sshkey *ed25519 = generate_or_die(KEY_ED25519, 0);
+ static const char *data = "If everyone started announcing his nose had "
+ "run away, I don’t know how it would all end";
+ static const size_t dlen = strlen(data);
+
+#ifdef WITH_OPENSSL
+ sshkey_verify(rsa, sig, slen, (const u_char *)data, dlen, NULL, 0, &details);
+ sshkey_sig_details_free(details);
+ details = NULL;
+ sshkey_verify(dsa, sig, slen, (const u_char *)data, dlen, NULL, 0, &details);
+ sshkey_sig_details_free(details);
+ details = NULL;
+ sshkey_verify(ecdsa256, sig, slen, (const u_char *)data, dlen, NULL, 0, &details);
+ sshkey_sig_details_free(details);
+ details = NULL;
+ sshkey_verify(ecdsa384, sig, slen, (const u_char *)data, dlen, NULL, 0, &details);
+ sshkey_sig_details_free(details);
+ details = NULL;
+ sshkey_verify(ecdsa521, sig, slen, (const u_char *)data, dlen, NULL, 0, &details);
+ sshkey_sig_details_free(details);
+ details = NULL;
+#endif
+ sshkey_verify(ed25519, sig, slen, (const u_char *)data, dlen, NULL, 0, &details);
+ sshkey_sig_details_free(details);
+ return 0;
+}
+
+} // extern
diff --git a/regress/misc/fuzz-harness/ssh-sk-null.cc b/regress/misc/fuzz-harness/ssh-sk-null.cc
new file mode 100644
index 0000000..948c3d9
--- /dev/null
+++ b/regress/misc/fuzz-harness/ssh-sk-null.cc
@@ -0,0 +1,52 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2019 Google LLC
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+extern "C" {
+
+#include "includes.h"
+
+#include <sys/types.h>
+
+#include "ssherr.h"
+#include "ssh-sk.h"
+
+int
+sshsk_enroll(int type, const char *provider_path, const char *device,
+ const char *application, const char *userid, uint8_t flags,
+ const char *pin, struct sshbuf *challenge_buf,
+ struct sshkey **keyp, struct sshbuf *attest)
+{
+ return SSH_ERR_FEATURE_UNSUPPORTED;
+}
+
+int
+sshsk_sign(const char *provider_path, struct sshkey *key,
+ u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
+ u_int compat, const char *pin)
+{
+ return SSH_ERR_FEATURE_UNSUPPORTED;
+}
+
+int
+sshsk_load_resident(const char *provider_path, const char *device,
+ const char *pin, u_int flags, struct sshsk_resident_key ***srksp,
+ size_t *nsrksp)
+{
+ return SSH_ERR_FEATURE_UNSUPPORTED;
+}
+
+};
diff --git a/regress/misc/fuzz-harness/sshsig_fuzz.cc b/regress/misc/fuzz-harness/sshsig_fuzz.cc
new file mode 100644
index 0000000..02211a0
--- /dev/null
+++ b/regress/misc/fuzz-harness/sshsig_fuzz.cc
@@ -0,0 +1,37 @@
+// cc_fuzz_target test for sshsig verification.
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+extern "C" {
+
+#include "includes.h"
+#include "sshkey.h"
+#include "ssherr.h"
+#include "sshbuf.h"
+#include "sshsig.h"
+#include "log.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t* sig, size_t slen)
+{
+ static const char *data = "If everyone started announcing his nose had "
+ "run away, I don’t know how it would all end";
+ struct sshbuf *signature = sshbuf_from(sig, slen);
+ struct sshbuf *message = sshbuf_from(data, strlen(data));
+ struct sshkey *k = NULL;
+ struct sshkey_sig_details *details = NULL;
+ extern char *__progname;
+
+ log_init(__progname, SYSLOG_LEVEL_QUIET, SYSLOG_FACILITY_USER, 1);
+ sshsig_verifyb(signature, message, "castle", &k, &details);
+ sshkey_sig_details_free(details);
+ sshkey_free(k);
+ sshbuf_free(signature);
+ sshbuf_free(message);
+ return 0;
+}
+
+} // extern
diff --git a/regress/misc/fuzz-harness/sshsigopt_fuzz.cc b/regress/misc/fuzz-harness/sshsigopt_fuzz.cc
new file mode 100644
index 0000000..7424fcb
--- /dev/null
+++ b/regress/misc/fuzz-harness/sshsigopt_fuzz.cc
@@ -0,0 +1,29 @@
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+extern "C" {
+
+#include "sshsig.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ char *cp = (char *)malloc(size + 1);
+ struct sshsigopt *opts = NULL;
+
+ if (cp == NULL)
+ goto out;
+ memcpy(cp, data, size);
+ cp[size] = '\0';
+ if ((opts = sshsigopt_parse(cp, "libfuzzer", 0, NULL)) == NULL)
+ goto out;
+
+ out:
+ free(cp);
+ sshsigopt_free(opts);
+ return 0;
+}
+
+} // extern "C"
diff --git a/regress/misc/fuzz-harness/testdata/README b/regress/misc/fuzz-harness/testdata/README
new file mode 100644
index 0000000..7520530
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/README
@@ -0,0 +1,4 @@
+This is preparatory data for fuzzing testing including scripts and test keys,
+corresponding to ../fixed-keys that are used in the fuzz tests and consequent
+fuzzing seed corpora. They should not be changed unless the affected seed
+corpora are also regenerated.
diff --git a/regress/misc/fuzz-harness/testdata/create-agent-corpus.sh b/regress/misc/fuzz-harness/testdata/create-agent-corpus.sh
new file mode 100755
index 0000000..1043b9f
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/create-agent-corpus.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+# Exercise ssh-agent to generate fuzzing corpus
+
+# XXX assumes agent hacked up with sk-dummy.o and ssh-sk.o linked directly
+# and dumping of e->request for each message.
+
+set -xe
+SSH_AUTH_SOCK=$PWD/sock
+rm -f agent-[0-9]* $SSH_AUTH_SOCK
+export SSH_AUTH_SOCK
+../../../../ssh-agent -D -a $SSH_AUTH_SOCK &
+sleep 1
+AGENT_PID=$!
+trap "kill $AGENT_PID" EXIT
+
+PRIV="id_dsa id_ecdsa id_ecdsa_sk id_ed25519 id_ed25519_sk id_rsa"
+
+# add keys
+ssh-add $PRIV
+
+# sign
+ssh-add -T *.pub
+
+# list
+ssh-add -l
+
+# remove individually
+ssh-add -d $PRIV
+
+# re-add with constraints
+ssh-add -c -t 3h $PRIV
+
+# delete all
+ssh-add -D
+
+# attempt to add a PKCS#11 token
+ssh-add -s /fake || :
+
+# attempt to delete PKCS#11
+ssh-add -e /fake || :
+
+ssh-add -L
+
diff --git a/regress/misc/fuzz-harness/testdata/id_dsa b/regress/misc/fuzz-harness/testdata/id_dsa
new file mode 100644
index 0000000..88bf556
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_dsa
@@ -0,0 +1,21 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsgAAAAdzc2gtZH
+NzAAAAgQCsGTfjpQ465EOkfQXJM9BOvfRQE0fqlykAls+ncz+T7hrbeScRu8xpwzsznJNm
+xlW8o6cUDiHmBJ5OHgamUC9N7YJeU/6fnOAZifgN8mqK6k8pKHuje8ANOiYgHLl0yiASQA
+3//qMyzZ+W/hemoLSmLAbEqlfWVeyYx+wta1Vm+QAAABUAvWyehvUvdHvQxavYgS5p0t5Q
+d7UAAACBAIRA9Yy+f4Kzqpv/qICPO3zk42UuP7WAhSW2nCbQdLlCiSTxcjKgcvXNRckwJP
+44JjSHOtJy/AMtJrPIbLYG6KuWTdBlEHFiG6DafvLG+qPMSL2bPjXTOhuOMbCHIZ+5WBkW
+THeG/Nv11iI01Of9V6tXkig23K370flkRkXFi9MdAAAAgCt6YUcQkNwG7B/e5M1FZsLP9O
+kVB3BwLAOjmWdHpyhu3HpwSJa3XLEvhXN0i6IVI2KgPo/2GtYA6rHt14L+6u1pmhh8sAvQ
+ksp3qZB+xh/NP+hBqf0sbHX0yYbzKOvI5SCc/kKK6yagcBZOsubM/KC8TxyVgmD5c6WzYs
+h5TEpvAAAB2PHjRbbx40W2AAAAB3NzaC1kc3MAAACBAKwZN+OlDjrkQ6R9Bckz0E699FAT
+R+qXKQCWz6dzP5PuGtt5JxG7zGnDOzOck2bGVbyjpxQOIeYEnk4eBqZQL03tgl5T/p+c4B
+mJ+A3yaorqTykoe6N7wA06JiAcuXTKIBJADf/+ozLNn5b+F6agtKYsBsSqV9ZV7JjH7C1r
+VWb5AAAAFQC9bJ6G9S90e9DFq9iBLmnS3lB3tQAAAIEAhED1jL5/grOqm/+ogI87fOTjZS
+4/tYCFJbacJtB0uUKJJPFyMqBy9c1FyTAk/jgmNIc60nL8Ay0ms8hstgboq5ZN0GUQcWIb
+oNp+8sb6o8xIvZs+NdM6G44xsIchn7lYGRZMd4b82/XWIjTU5/1Xq1eSKDbcrfvR+WRGRc
+WL0x0AAACAK3phRxCQ3AbsH97kzUVmws/06RUHcHAsA6OZZ0enKG7cenBIlrdcsS+Fc3SL
+ohUjYqA+j/Ya1gDqse3Xgv7q7WmaGHywC9CSynepkH7GH80/6EGp/SxsdfTJhvMo68jlIJ
+z+QorrJqBwFk6y5sz8oLxPHJWCYPlzpbNiyHlMSm8AAAAUUA+OGldMi76ClO/sstpdbBUE
+lq8AAAAAAQI=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/misc/fuzz-harness/testdata/id_dsa-cert.pub b/regress/misc/fuzz-harness/testdata/id_dsa-cert.pub
new file mode 100644
index 0000000..3afb87f
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_dsa-cert.pub
@@ -0,0 +1 @@
+ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAguF716Yub+vVKNlONKLsfxGYWkRe/PyjfYdGRTsFaDvAAAACBAKwZN+OlDjrkQ6R9Bckz0E699FATR+qXKQCWz6dzP5PuGtt5JxG7zGnDOzOck2bGVbyjpxQOIeYEnk4eBqZQL03tgl5T/p+c4BmJ+A3yaorqTykoe6N7wA06JiAcuXTKIBJADf/+ozLNn5b+F6agtKYsBsSqV9ZV7JjH7C1rVWb5AAAAFQC9bJ6G9S90e9DFq9iBLmnS3lB3tQAAAIEAhED1jL5/grOqm/+ogI87fOTjZS4/tYCFJbacJtB0uUKJJPFyMqBy9c1FyTAk/jgmNIc60nL8Ay0ms8hstgboq5ZN0GUQcWIboNp+8sb6o8xIvZs+NdM6G44xsIchn7lYGRZMd4b82/XWIjTU5/1Xq1eSKDbcrfvR+WRGRcWL0x0AAACAK3phRxCQ3AbsH97kzUVmws/06RUHcHAsA6OZZ0enKG7cenBIlrdcsS+Fc3SLohUjYqA+j/Ya1gDqse3Xgv7q7WmaGHywC9CSynepkH7GH80/6EGp/SxsdfTJhvMo68jlIJz+QorrJqBwFk6y5sz8oLxPHJWCYPlzpbNiyHlMSm8AAAAAAAAD6AAAAAEAAAAHdWx5c3NlcwAAABcAAAAHdWx5c3NlcwAAAAhvZHlzc2V1cwAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAz0F5hFTFS5nhUcmnyjFVoDw5L/P7kQU8JnBA2rWczAwAAAFMAAAALc3NoLWVkMjU1MTkAAABAjMQEZcbdUYJBjIC4GxByFDOb8tv71vDZdx7irHwaqIjx5rzpJUuOV1r8ZO4kY+Yaiun1yrWj2QYkfJrHBvD1DA== id_dsa.pub
diff --git a/regress/misc/fuzz-harness/testdata/id_dsa.pub b/regress/misc/fuzz-harness/testdata/id_dsa.pub
new file mode 100644
index 0000000..6f91c4e
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAKwZN+OlDjrkQ6R9Bckz0E699FATR+qXKQCWz6dzP5PuGtt5JxG7zGnDOzOck2bGVbyjpxQOIeYEnk4eBqZQL03tgl5T/p+c4BmJ+A3yaorqTykoe6N7wA06JiAcuXTKIBJADf/+ozLNn5b+F6agtKYsBsSqV9ZV7JjH7C1rVWb5AAAAFQC9bJ6G9S90e9DFq9iBLmnS3lB3tQAAAIEAhED1jL5/grOqm/+ogI87fOTjZS4/tYCFJbacJtB0uUKJJPFyMqBy9c1FyTAk/jgmNIc60nL8Ay0ms8hstgboq5ZN0GUQcWIboNp+8sb6o8xIvZs+NdM6G44xsIchn7lYGRZMd4b82/XWIjTU5/1Xq1eSKDbcrfvR+WRGRcWL0x0AAACAK3phRxCQ3AbsH97kzUVmws/06RUHcHAsA6OZZ0enKG7cenBIlrdcsS+Fc3SLohUjYqA+j/Ya1gDqse3Xgv7q7WmaGHywC9CSynepkH7GH80/6EGp/SxsdfTJhvMo68jlIJz+QorrJqBwFk6y5sz8oLxPHJWCYPlzpbNiyHlMSm8=
diff --git a/regress/misc/fuzz-harness/testdata/id_ecdsa b/regress/misc/fuzz-harness/testdata/id_ecdsa
new file mode 100644
index 0000000..c1a96c6
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_ecdsa
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
+1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTDJ0VlMv+0rguNzaJ1DF2KueHaxRSQ
+6LpIxGbulrg1a8RPbnMXwag5GcDiDllD2lDUJUuBEWyjXA0rZoZX35ELAAAAoE/Bbr5PwW
+6+AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMMnRWUy/7SuC43N
+onUMXYq54drFFJDoukjEZu6WuDVrxE9ucxfBqDkZwOIOWUPaUNQlS4ERbKNcDStmhlffkQ
+sAAAAhAIhE6hCID5oOm1TDktc++KFKyScjLifcZ6Cgv5xSSyLOAAAAAAECAwQFBgc=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/misc/fuzz-harness/testdata/id_ecdsa-cert.pub b/regress/misc/fuzz-harness/testdata/id_ecdsa-cert.pub
new file mode 100644
index 0000000..9de5999
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_ecdsa-cert.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgVJZuM/1AOe6n++qRWMyUuAThYqLvvQxj5CGflLODp60AAAAIbmlzdHAyNTYAAABBBMMnRWUy/7SuC43NonUMXYq54drFFJDoukjEZu6WuDVrxE9ucxfBqDkZwOIOWUPaUNQlS4ERbKNcDStmhlffkQsAAAAAAAAD6QAAAAEAAAAHdWx5c3NlcwAAABcAAAAHdWx5c3NlcwAAAAhvZHlzc2V1cwAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAz0F5hFTFS5nhUcmnyjFVoDw5L/P7kQU8JnBA2rWczAwAAAFMAAAALc3NoLWVkMjU1MTkAAABAtdJpcF6ZmQL+ueices4QZeL7AK8Xuo08jyLgiolhjKy2jj4LSUki4aX/ZeZeJuby1ovGrfaeFAgx3itPLR7IAQ== id_ecdsa.pub
diff --git a/regress/misc/fuzz-harness/testdata/id_ecdsa.pub b/regress/misc/fuzz-harness/testdata/id_ecdsa.pub
new file mode 100644
index 0000000..30a7cc2
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_ecdsa.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMMnRWUy/7SuC43NonUMXYq54drFFJDoukjEZu6WuDVrxE9ucxfBqDkZwOIOWUPaUNQlS4ERbKNcDStmhlffkQs=
diff --git a/regress/misc/fuzz-harness/testdata/id_ecdsa_sk b/regress/misc/fuzz-harness/testdata/id_ecdsa_sk
new file mode 100644
index 0000000..5a364ed
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_ecdsa_sk
@@ -0,0 +1,14 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAfwAAACJzay1lY2
+RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQTYyU76zop1
+VOb4DfKWYnR5b0TOC3zw8DzObAfHWB5o6xls+tOYiEleXvIEi00Da2iCK47habZTOhLyeB
+X2Avu5AAAABHNzaDoAAAGYqUAQSKlAEEgAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBv
+cGVuc3NoLmNvbQAAAAhuaXN0cDI1NgAAAEEE2MlO+s6KdVTm+A3ylmJ0eW9Ezgt88PA8zm
+wHx1geaOsZbPrTmIhJXl7yBItNA2togiuO4Wm2UzoS8ngV9gL7uQAAAARzc2g6AQAAAOMt
+LS0tLUJFR0lOIEVDIFBSSVZBVEUgS0VZLS0tLS0KTUhjQ0FRRUVJSHFsZjNsWTkxZFhwUn
+dYZDBrS0lYWmNpeDRRcDBNSU15Ny9JMUxXSTFuWG9Bb0dDQ3FHU000OQpBd0VIb1VRRFFn
+QUUyTWxPK3M2S2RWVG0rQTN5bG1KMGVXOUV6Z3Q4OFBBOHptd0h4MWdlYU9zWmJQclRtSW
+hKClhsN3lCSXROQTJ0b2dpdU80V20yVXpvUzhuZ1Y5Z0w3dVE9PQotLS0tLUVORCBFQyBQ
+UklWQVRFIEtFWS0tLS0tCgAAAAAAAAAbZGptQGRqbS5zeWQuY29ycC5nb29nbGUuY29tAQ
+IDBAUG
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/misc/fuzz-harness/testdata/id_ecdsa_sk-cert.pub b/regress/misc/fuzz-harness/testdata/id_ecdsa_sk-cert.pub
new file mode 100644
index 0000000..14040fa
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_ecdsa_sk-cert.pub
@@ -0,0 +1 @@
+sk-ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAK3NrLWVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgKLHtIca++5VoDrUAXU/KqGJZ7jZEnuJSTvt7VrYY9foAAAAIbmlzdHAyNTYAAABBBNjJTvrOinVU5vgN8pZidHlvRM4LfPDwPM5sB8dYHmjrGWz605iISV5e8gSLTQNraIIrjuFptlM6EvJ4FfYC+7kAAAAEc3NoOgAAAAAAAAPqAAAAAQAAAAd1bHlzc2VzAAAAFwAAAAd1bHlzc2VzAAAACG9keXNzZXVzAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIDPQXmEVMVLmeFRyafKMVWgPDkv8/uRBTwmcEDatZzMDAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEB1naZOQDLaDr+fwn6E9x8/8HeiaUubDzPexfNQMz+m/7RD0gd5uJhHYUfDb5+/sIx1I7bUEeRIDkBbmZ2foo0E djm@djm.syd.corp.google.com
diff --git a/regress/misc/fuzz-harness/testdata/id_ecdsa_sk.pub b/regress/misc/fuzz-harness/testdata/id_ecdsa_sk.pub
new file mode 100644
index 0000000..1b5e829
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_ecdsa_sk.pub
@@ -0,0 +1 @@
+sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBNjJTvrOinVU5vgN8pZidHlvRM4LfPDwPM5sB8dYHmjrGWz605iISV5e8gSLTQNraIIrjuFptlM6EvJ4FfYC+7kAAAAEc3NoOg== djm@djm.syd.corp.google.com
diff --git a/regress/misc/fuzz-harness/testdata/id_ed25519 b/regress/misc/fuzz-harness/testdata/id_ed25519
new file mode 100644
index 0000000..6a7fbac
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAz0F5hFTFS5nhUcmnyjFVoDw5L/P7kQU8JnBA2rWczAwAAAIhWlP99VpT/
+fQAAAAtzc2gtZWQyNTUxOQAAACAz0F5hFTFS5nhUcmnyjFVoDw5L/P7kQU8JnBA2rWczAw
+AAAEDE1rlcMC0s0X3TKVZAOVavZOywwkXw8tO5dLObxaCMEDPQXmEVMVLmeFRyafKMVWgP
+Dkv8/uRBTwmcEDatZzMDAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/misc/fuzz-harness/testdata/id_ed25519-cert.pub b/regress/misc/fuzz-harness/testdata/id_ed25519-cert.pub
new file mode 100644
index 0000000..6a95fed
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_ed25519-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIMDQjYH6XRzH3j3MW1DdjCoAfvrHfgjnVGF+sLK0pBfqAAAAIDPQXmEVMVLmeFRyafKMVWgPDkv8/uRBTwmcEDatZzMDAAAAAAAAA+sAAAABAAAAB3VseXNzZXMAAAAXAAAAB3VseXNzZXMAAAAIb2R5c3NldXMAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgM9BeYRUxUuZ4VHJp8oxVaA8OS/z+5EFPCZwQNq1nMwMAAABTAAAAC3NzaC1lZDI1NTE5AAAAQBj0og+s09/HpwdHZbzN0twooKPDWWrxGfnP1Joy6cDnY2BCSQ7zg9vbq11kLF8H/sKOTZWAQrUZ7LlChOu9Ogw= id_ed25519.pub
diff --git a/regress/misc/fuzz-harness/testdata/id_ed25519.pub b/regress/misc/fuzz-harness/testdata/id_ed25519.pub
new file mode 100644
index 0000000..87b6174
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_ed25519.pub
@@ -0,0 +1,2 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDPQXmEVMVLmeFRyafKMVWgPDkv8/uRBTwmcEDatZzMD
+
diff --git a/regress/misc/fuzz-harness/testdata/id_ed25519_sk b/regress/misc/fuzz-harness/testdata/id_ed25519_sk
new file mode 100644
index 0000000..9dcda6c
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_ed25519_sk
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAABpzay1zc2
+gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACCTJtH10vWhIDxd62edvMLg9u2cwYKyqa7332je
+RArHjAAAAARzc2g6AAAAwN7vvE3e77xNAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY2
+9tAAAAIJMm0fXS9aEgPF3rZ528wuD27ZzBgrKprvffaN5ECseMAAAABHNzaDoBAAAAQEsS
+xLFiVzfpH2mt9xh8i/zmHV646Hud4QruNBAGNl8gkybR9dL1oSA8XetnnbzC4PbtnMGCsq
+mu999o3kQKx4wAAAAAAAAAG2RqbUBkam0uc3lkLmNvcnAuZ29vZ2xlLmNvbQECAwQFBg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/misc/fuzz-harness/testdata/id_ed25519_sk-cert.pub b/regress/misc/fuzz-harness/testdata/id_ed25519_sk-cert.pub
new file mode 100644
index 0000000..9e41eec
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_ed25519_sk-cert.pub
@@ -0,0 +1 @@
+sk-ssh-ed25519-cert-v01@openssh.com AAAAI3NrLXNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIJiT+C/VLMWholFZ4xhOyJr0nSLZSFRIM3I07wUNTRPaAAAAIJMm0fXS9aEgPF3rZ528wuD27ZzBgrKprvffaN5ECseMAAAABHNzaDoAAAAAAAAD7AAAAAEAAAAHdWx5c3NlcwAAABcAAAAHdWx5c3NlcwAAAAhvZHlzc2V1cwAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAz0F5hFTFS5nhUcmnyjFVoDw5L/P7kQU8JnBA2rWczAwAAAFMAAAALc3NoLWVkMjU1MTkAAABAX0Pu13B94pVR3qq8MJQGkOS1Cd7AAM1k6O2VSwyDPM/LfsWIQ4ywgxDmk3hjXWOY7BqljuMxo5VO4JymEIhQBA== djm@djm.syd.corp.google.com
diff --git a/regress/misc/fuzz-harness/testdata/id_ed25519_sk.pub b/regress/misc/fuzz-harness/testdata/id_ed25519_sk.pub
new file mode 100644
index 0000000..38d1984
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_ed25519_sk.pub
@@ -0,0 +1 @@
+sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIJMm0fXS9aEgPF3rZ528wuD27ZzBgrKprvffaN5ECseMAAAABHNzaDo= djm@djm.syd.corp.google.com
diff --git a/regress/misc/fuzz-harness/testdata/id_rsa b/regress/misc/fuzz-harness/testdata/id_rsa
new file mode 100644
index 0000000..574fecf
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_rsa
@@ -0,0 +1,27 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAQEA3+epf+VGKoGPaAZXrf6S0cyumQnddkGBnVFX0A5eh37RtLug0qY5
+thxsBUbGGVr9mTd2QXwLujBwYg5l1MP/Fmg+5312Zgx9pHmS+qKULbar0hlNgptNEb+aNU
+d3o9qg3aXqXm7+ZnjAV05ef/mxNRN2ZvuEkw7cRppTJcbBI+vF3lXuCXnX2klDI95Gl2AW
+3WHRtanqLHZXuBkjjRBDKc7MUq/GP1hmLiAd95dvU7fZjRlIEsP84zGEI1Fb0L/kmPHcOt
+iVfHft8CtmC9v6+94JrOiPBBNScV+dyrgAGPsdKdr/1vIpQmCNiI8s3PCiD8J7ZiBaYm0I
+8fq5G/qnUwAAA7ggw2dXIMNnVwAAAAdzc2gtcnNhAAABAQDf56l/5UYqgY9oBlet/pLRzK
+6ZCd12QYGdUVfQDl6HftG0u6DSpjm2HGwFRsYZWv2ZN3ZBfAu6MHBiDmXUw/8WaD7nfXZm
+DH2keZL6opQttqvSGU2Cm00Rv5o1R3ej2qDdpepebv5meMBXTl5/+bE1E3Zm+4STDtxGml
+MlxsEj68XeVe4JedfaSUMj3kaXYBbdYdG1qeosdle4GSONEEMpzsxSr8Y/WGYuIB33l29T
+t9mNGUgSw/zjMYQjUVvQv+SY8dw62JV8d+3wK2YL2/r73gms6I8EE1JxX53KuAAY+x0p2v
+/W8ilCYI2Ijyzc8KIPwntmIFpibQjx+rkb+qdTAAAAAwEAAQAAAQEArWm5B4tFasppjUHM
+SsAuajtCxtizI1Hc10EW59cZM4vvUzE2f6+qZvdgWj3UU/L7Et23w0QVuSCnCerox379ZB
+ddEOFFAAiQjwBx65hbd4RRUymxtIQfjq18++LcMJW1nbVQ7c69ThQbtALIggmbS+ZE/8Gx
+jkwmIrCH0Ww8TlpsPe+mNHuyNk7UEZoXLm22lNLqq5qkIL5JgT6M2iNJpMOJy9/CKi6kO4
+JPuVwjdG4C5pBPaMN3KJ1IvAlSlLGNaXnfXcn85gWfsCjsZmH3liey2NJamqp/w83BrKUg
+YZvMR2qeWZaKkFTahpzN5KRK1BFeB37O0P84Dzh1biDX8QAAAIEAiWXW8ePYFwLpa2mFIh
+VvRTdcrN70rVK5eWVaL3pyS4vGA56Jixq86dHveOnbSY+iNb1jQidtXc8SWUt2wtHqZ32h
+Lji9/hMSKqe9SEP3xvDRDmUJqsVw0ySyrFrzm4160QY6RKU3CIQCVFslMZ9fxmrfZ/hxoU
+0X3FVsxmC4+kwAAACBAPOc1YERpV6PjANBrGR+1o1RCdACbm5myc42QzSNIaOZmgrYs+Gt
+7+EcoqSdbJzHJNCNQfF+A+vjbIkFiuZqq/5wwr59qXx5OAlijLB/ywwKmTWq6lp//Zxny+
+ka3sIGNO14eQvmxNDnlLL+RIZleCTEKBXSW6CZhr+uHMZFKKMtAAAAgQDrSkm+LbILB7H9
+jxEBZLhv53aAn4u81kFKQOJ7PzzpBGSoD12i7oIJu5siSD5EKDNVEr+SvCf0ISU3BuMpzl
+t3YrPrHRheOFhn5e3j0e//zB8rBC0DGB4CtTDdeh7rOXUL4K0pz+8wEpNkV62SWxhC6NRW
+I79JhtGkh+GtcnkEfwAAAAAB
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/misc/fuzz-harness/testdata/id_rsa-cert.pub b/regress/misc/fuzz-harness/testdata/id_rsa-cert.pub
new file mode 100644
index 0000000..01761a3
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_rsa-cert.pub
@@ -0,0 +1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg89JX6OBMYDSxER8fnU5y8xxeMCHR/hI0uVqdEhNyCpcAAAADAQABAAABAQDf56l/5UYqgY9oBlet/pLRzK6ZCd12QYGdUVfQDl6HftG0u6DSpjm2HGwFRsYZWv2ZN3ZBfAu6MHBiDmXUw/8WaD7nfXZmDH2keZL6opQttqvSGU2Cm00Rv5o1R3ej2qDdpepebv5meMBXTl5/+bE1E3Zm+4STDtxGmlMlxsEj68XeVe4JedfaSUMj3kaXYBbdYdG1qeosdle4GSONEEMpzsxSr8Y/WGYuIB33l29Tt9mNGUgSw/zjMYQjUVvQv+SY8dw62JV8d+3wK2YL2/r73gms6I8EE1JxX53KuAAY+x0p2v/W8ilCYI2Ijyzc8KIPwntmIFpibQjx+rkb+qdTAAAAAAAAA+0AAAABAAAAB3VseXNzZXMAAAAXAAAAB3VseXNzZXMAAAAIb2R5c3NldXMAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgM9BeYRUxUuZ4VHJp8oxVaA8OS/z+5EFPCZwQNq1nMwMAAABTAAAAC3NzaC1lZDI1NTE5AAAAQGCDA6PWw4x9bHQl0w7NqifHepumqD3dmyMx+hZGuPRon+TsyCjfytu7hWmV7l9XUF0fPQNFQ7FGat5e+7YUNgE= id_rsa.pub
diff --git a/regress/misc/fuzz-harness/testdata/id_rsa.pub b/regress/misc/fuzz-harness/testdata/id_rsa.pub
new file mode 100644
index 0000000..05015e1
--- /dev/null
+++ b/regress/misc/fuzz-harness/testdata/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDf56l/5UYqgY9oBlet/pLRzK6ZCd12QYGdUVfQDl6HftG0u6DSpjm2HGwFRsYZWv2ZN3ZBfAu6MHBiDmXUw/8WaD7nfXZmDH2keZL6opQttqvSGU2Cm00Rv5o1R3ej2qDdpepebv5meMBXTl5/+bE1E3Zm+4STDtxGmlMlxsEj68XeVe4JedfaSUMj3kaXYBbdYdG1qeosdle4GSONEEMpzsxSr8Y/WGYuIB33l29Tt9mNGUgSw/zjMYQjUVvQv+SY8dw62JV8d+3wK2YL2/r73gms6I8EE1JxX53KuAAY+x0p2v/W8ilCYI2Ijyzc8KIPwntmIFpibQjx+rkb+qdT
diff --git a/regress/misc/sk-dummy/Makefile b/regress/misc/sk-dummy/Makefile
new file mode 100644
index 0000000..18b0a24
--- /dev/null
+++ b/regress/misc/sk-dummy/Makefile
@@ -0,0 +1,66 @@
+# $OpenBSD: Makefile,v 1.3 2023/01/15 23:35:10 djm Exp $
+
+.include <bsd.own.mk>
+.include <bsd.obj.mk>
+
+PROG= sk-dummy.so
+NOMAN=
+
+SSHREL=../../../../../usr.bin/ssh
+.PATH: ${.CURDIR}/${SSHREL}
+
+SRCS=sk-dummy.c
+# From usr.bin/ssh
+SRCS+=ed25519.c hash.c
+OPENSSL?= yes
+
+CFLAGS+= -fPIC
+
+.if (${OPENSSL:L} == "yes")
+CFLAGS+= -DWITH_OPENSSL
+.endif
+
+# enable warnings
+WARNINGS=Yes
+
+DEBUG=-g
+CFLAGS+= -fstack-protector-all
+CDIAGFLAGS= -Wall
+CDIAGFLAGS+= -Wextra
+CDIAGFLAGS+= -Werror
+CDIAGFLAGS+= -Wchar-subscripts
+CDIAGFLAGS+= -Wcomment
+CDIAGFLAGS+= -Wformat
+CDIAGFLAGS+= -Wformat-security
+CDIAGFLAGS+= -Wimplicit
+CDIAGFLAGS+= -Winline
+CDIAGFLAGS+= -Wmissing-declarations
+CDIAGFLAGS+= -Wmissing-prototypes
+CDIAGFLAGS+= -Wparentheses
+CDIAGFLAGS+= -Wpointer-arith
+CDIAGFLAGS+= -Wreturn-type
+CDIAGFLAGS+= -Wshadow
+CDIAGFLAGS+= -Wsign-compare
+CDIAGFLAGS+= -Wstrict-aliasing
+CDIAGFLAGS+= -Wstrict-prototypes
+CDIAGFLAGS+= -Wswitch
+CDIAGFLAGS+= -Wtrigraphs
+CDIAGFLAGS+= -Wuninitialized
+CDIAGFLAGS+= -Wunused
+CDIAGFLAGS+= -Wno-unused-parameter
+.if ${COMPILER_VERSION:L} != "gcc3"
+CDIAGFLAGS+= -Wold-style-definition
+.endif
+
+CFLAGS+=-I${.CURDIR}/${SSHREL}
+
+.if (${OPENSSL:L} == "yes")
+LDADD+= -lcrypto
+DPADD+= ${LIBCRYPTO}
+.endif
+
+$(PROG): $(OBJS)
+ $(CC) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDADD)
+
+.include <bsd.prog.mk>
+
diff --git a/regress/misc/sk-dummy/fatal.c b/regress/misc/sk-dummy/fatal.c
new file mode 100644
index 0000000..c6e4b5d
--- /dev/null
+++ b/regress/misc/sk-dummy/fatal.c
@@ -0,0 +1,27 @@
+/* public domain */
+
+#include "includes.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+
+#include "log.h"
+
+void
+sshfatal(const char *file, const char *func, int line, int showfunc,
+ LogLevel level, const char *suffix, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (showfunc)
+ fprintf(stderr, "%s: ", func);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ if (suffix != NULL)
+ fprintf(stderr, ": %s", suffix);
+ fputc('\n', stderr);
+ _exit(1);
+}
diff --git a/regress/misc/sk-dummy/sk-dummy.c b/regress/misc/sk-dummy/sk-dummy.c
new file mode 100644
index 0000000..ad5e474
--- /dev/null
+++ b/regress/misc/sk-dummy/sk-dummy.c
@@ -0,0 +1,552 @@
+/*
+ * Copyright (c) 2019 Markus Friedl
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdarg.h>
+#ifdef HAVE_SHA2_H
+#include <sha2.h>
+#endif
+
+#include "crypto_api.h"
+#include "sk-api.h"
+
+#if defined(WITH_OPENSSL) && !defined(OPENSSL_HAS_ECC)
+# undef WITH_OPENSSL
+#endif
+
+#ifdef WITH_OPENSSL
+/* We don't use sha2 from OpenSSL and they can conflict with system sha2.h */
+#define OPENSSL_NO_SHA
+#define USE_LIBC_SHA2 /* NetBSD 9 */
+#include <openssl/opensslv.h>
+#include <openssl/crypto.h>
+#include <openssl/evp.h>
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/ecdsa.h>
+#include <openssl/pem.h>
+
+/* Compatibility with OpenSSH 1.0.x */
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
+#define ECDSA_SIG_get0(sig, pr, ps) \
+ do { \
+ (*pr) = sig->r; \
+ (*ps) = sig->s; \
+ } while (0)
+#endif
+#endif /* WITH_OPENSSL */
+
+/* #define SK_DEBUG 1 */
+
+#if SSH_SK_VERSION_MAJOR != 0x000a0000
+# error SK API has changed, sk-dummy.c needs an update
+#endif
+
+#ifdef SK_DUMMY_INTEGRATE
+# define sk_api_version ssh_sk_api_version
+# define sk_enroll ssh_sk_enroll
+# define sk_sign ssh_sk_sign
+# define sk_load_resident_keys ssh_sk_load_resident_keys
+#endif /* !SK_STANDALONE */
+
+static void skdebug(const char *func, const char *fmt, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+
+static void
+skdebug(const char *func, const char *fmt, ...)
+{
+#if defined(SK_DEBUG)
+ va_list ap;
+
+ va_start(ap, fmt);
+ fprintf(stderr, "sk-dummy %s: ", func);
+ vfprintf(stderr, fmt, ap);
+ fputc('\n', stderr);
+ va_end(ap);
+#else
+ (void)func; /* XXX */
+ (void)fmt; /* XXX */
+#endif
+}
+
+uint32_t
+sk_api_version(void)
+{
+ return SSH_SK_VERSION_MAJOR;
+}
+
+static int
+pack_key_ecdsa(struct sk_enroll_response *response)
+{
+#ifdef OPENSSL_HAS_ECC
+ EC_KEY *key = NULL;
+ const EC_GROUP *g;
+ const EC_POINT *q;
+ int ret = -1;
+ long privlen;
+ BIO *bio = NULL;
+ char *privptr;
+
+ response->public_key = NULL;
+ response->public_key_len = 0;
+ response->key_handle = NULL;
+ response->key_handle_len = 0;
+
+ if ((key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)) == NULL) {
+ skdebug(__func__, "EC_KEY_new_by_curve_name");
+ goto out;
+ }
+ if (EC_KEY_generate_key(key) != 1) {
+ skdebug(__func__, "EC_KEY_generate_key");
+ goto out;
+ }
+ EC_KEY_set_asn1_flag(key, OPENSSL_EC_NAMED_CURVE);
+ if ((bio = BIO_new(BIO_s_mem())) == NULL ||
+ (g = EC_KEY_get0_group(key)) == NULL ||
+ (q = EC_KEY_get0_public_key(key)) == NULL) {
+ skdebug(__func__, "couldn't get key parameters");
+ goto out;
+ }
+ response->public_key_len = EC_POINT_point2oct(g, q,
+ POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL);
+ if (response->public_key_len == 0 || response->public_key_len > 2048) {
+ skdebug(__func__, "bad pubkey length %zu",
+ response->public_key_len);
+ goto out;
+ }
+ if ((response->public_key = malloc(response->public_key_len)) == NULL) {
+ skdebug(__func__, "malloc pubkey failed");
+ goto out;
+ }
+ if (EC_POINT_point2oct(g, q, POINT_CONVERSION_UNCOMPRESSED,
+ response->public_key, response->public_key_len, NULL) == 0) {
+ skdebug(__func__, "EC_POINT_point2oct failed");
+ goto out;
+ }
+ /* Key handle contains PEM encoded private key */
+ if (!PEM_write_bio_ECPrivateKey(bio, key, NULL, NULL, 0, NULL, NULL)) {
+ skdebug(__func__, "PEM_write_bio_ECPrivateKey failed");
+ goto out;
+ }
+ if ((privlen = BIO_get_mem_data(bio, &privptr)) <= 0) {
+ skdebug(__func__, "BIO_get_mem_data failed");
+ goto out;
+ }
+ if ((response->key_handle = malloc(privlen)) == NULL) {
+ skdebug(__func__, "malloc key_handle failed");
+ goto out;
+ }
+ response->key_handle_len = (size_t)privlen;
+ memcpy(response->key_handle, privptr, response->key_handle_len);
+ /* success */
+ ret = 0;
+ out:
+ if (ret != 0) {
+ if (response->public_key != NULL) {
+ memset(response->public_key, 0,
+ response->public_key_len);
+ free(response->public_key);
+ response->public_key = NULL;
+ }
+ if (response->key_handle != NULL) {
+ memset(response->key_handle, 0,
+ response->key_handle_len);
+ free(response->key_handle);
+ response->key_handle = NULL;
+ }
+ }
+ BIO_free(bio);
+ EC_KEY_free(key);
+ return ret;
+#else
+ return -1;
+#endif
+}
+
+static int
+pack_key_ed25519(struct sk_enroll_response *response)
+{
+ int ret = -1;
+ u_char pk[crypto_sign_ed25519_PUBLICKEYBYTES];
+ u_char sk[crypto_sign_ed25519_SECRETKEYBYTES];
+
+ response->public_key = NULL;
+ response->public_key_len = 0;
+ response->key_handle = NULL;
+ response->key_handle_len = 0;
+
+ memset(pk, 0, sizeof(pk));
+ memset(sk, 0, sizeof(sk));
+ crypto_sign_ed25519_keypair(pk, sk);
+
+ response->public_key_len = sizeof(pk);
+ if ((response->public_key = malloc(response->public_key_len)) == NULL) {
+ skdebug(__func__, "malloc pubkey failed");
+ goto out;
+ }
+ memcpy(response->public_key, pk, sizeof(pk));
+ /* Key handle contains sk */
+ response->key_handle_len = sizeof(sk);
+ if ((response->key_handle = malloc(response->key_handle_len)) == NULL) {
+ skdebug(__func__, "malloc key_handle failed");
+ goto out;
+ }
+ memcpy(response->key_handle, sk, sizeof(sk));
+ /* success */
+ ret = 0;
+ out:
+ if (ret != 0)
+ free(response->public_key);
+ return ret;
+}
+
+static int
+check_options(struct sk_option **options)
+{
+ size_t i;
+
+ if (options == NULL)
+ return 0;
+ for (i = 0; options[i] != NULL; i++) {
+ skdebug(__func__, "requested unsupported option %s",
+ options[i]->name);
+ if (options[i]->required) {
+ skdebug(__func__, "unknown required option");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int
+sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
+ const char *application, uint8_t flags, const char *pin,
+ struct sk_option **options, struct sk_enroll_response **enroll_response)
+{
+ struct sk_enroll_response *response = NULL;
+ int ret = SSH_SK_ERR_GENERAL;
+
+ (void)flags; /* XXX; unused */
+
+ if (enroll_response == NULL) {
+ skdebug(__func__, "enroll_response == NULL");
+ goto out;
+ }
+ *enroll_response = NULL;
+ if (check_options(options) != 0)
+ goto out; /* error already logged */
+ if ((response = calloc(1, sizeof(*response))) == NULL) {
+ skdebug(__func__, "calloc response failed");
+ goto out;
+ }
+ response->flags = flags;
+ switch(alg) {
+ case SSH_SK_ECDSA:
+ if (pack_key_ecdsa(response) != 0)
+ goto out;
+ break;
+ case SSH_SK_ED25519:
+ if (pack_key_ed25519(response) != 0)
+ goto out;
+ break;
+ default:
+ skdebug(__func__, "unsupported key type %d", alg);
+ return -1;
+ }
+ /* Have to return something here */
+ if ((response->signature = calloc(1, 1)) == NULL) {
+ skdebug(__func__, "calloc signature failed");
+ goto out;
+ }
+ response->signature_len = 0;
+
+ *enroll_response = response;
+ response = NULL;
+ ret = 0;
+ out:
+ if (response != NULL) {
+ free(response->public_key);
+ free(response->key_handle);
+ free(response->signature);
+ free(response->attestation_cert);
+ free(response);
+ }
+ return ret;
+}
+
+static void
+dump(const char *preamble, const void *sv, size_t l)
+{
+#ifdef SK_DEBUG
+ const u_char *s = (const u_char *)sv;
+ size_t i;
+
+ fprintf(stderr, "%s (len %zu):\n", preamble, l);
+ for (i = 0; i < l; i++) {
+ if (i % 16 == 0)
+ fprintf(stderr, "%04zu: ", i);
+ fprintf(stderr, "%02x", s[i]);
+ if (i % 16 == 15 || i == l - 1)
+ fprintf(stderr, "\n");
+ }
+#endif
+}
+
+static int
+sig_ecdsa(const uint8_t *message, size_t message_len,
+ const char *application, uint32_t counter, uint8_t flags,
+ const uint8_t *key_handle, size_t key_handle_len,
+ struct sk_sign_response *response)
+{
+#ifdef OPENSSL_HAS_ECC
+ ECDSA_SIG *sig = NULL;
+ const BIGNUM *sig_r, *sig_s;
+ int ret = -1;
+ BIO *bio = NULL;
+ EVP_PKEY *pk = NULL;
+ EC_KEY *ec = NULL;
+ SHA2_CTX ctx;
+ uint8_t apphash[SHA256_DIGEST_LENGTH];
+ uint8_t sighash[SHA256_DIGEST_LENGTH];
+ uint8_t countbuf[4];
+
+ /* Decode EC_KEY from key handle */
+ if ((bio = BIO_new(BIO_s_mem())) == NULL ||
+ BIO_write(bio, key_handle, key_handle_len) != (int)key_handle_len) {
+ skdebug(__func__, "BIO setup failed");
+ goto out;
+ }
+ if ((pk = PEM_read_bio_PrivateKey(bio, NULL, NULL, "")) == NULL) {
+ skdebug(__func__, "PEM_read_bio_PrivateKey failed");
+ goto out;
+ }
+ if (EVP_PKEY_base_id(pk) != EVP_PKEY_EC) {
+ skdebug(__func__, "Not an EC key: %d", EVP_PKEY_base_id(pk));
+ goto out;
+ }
+ if ((ec = EVP_PKEY_get1_EC_KEY(pk)) == NULL) {
+ skdebug(__func__, "EVP_PKEY_get1_EC_KEY failed");
+ goto out;
+ }
+ /* Expect message to be pre-hashed */
+ if (message_len != SHA256_DIGEST_LENGTH) {
+ skdebug(__func__, "bad message len %zu", message_len);
+ goto out;
+ }
+ /* Prepare data to be signed */
+ dump("message", message, message_len);
+ SHA256Init(&ctx);
+ SHA256Update(&ctx, (const u_char *)application, strlen(application));
+ SHA256Final(apphash, &ctx);
+ dump("apphash", apphash, sizeof(apphash));
+ countbuf[0] = (counter >> 24) & 0xff;
+ countbuf[1] = (counter >> 16) & 0xff;
+ countbuf[2] = (counter >> 8) & 0xff;
+ countbuf[3] = counter & 0xff;
+ dump("countbuf", countbuf, sizeof(countbuf));
+ dump("flags", &flags, sizeof(flags));
+ SHA256Init(&ctx);
+ SHA256Update(&ctx, apphash, sizeof(apphash));
+ SHA256Update(&ctx, &flags, sizeof(flags));
+ SHA256Update(&ctx, countbuf, sizeof(countbuf));
+ SHA256Update(&ctx, message, message_len);
+ SHA256Final(sighash, &ctx);
+ dump("sighash", sighash, sizeof(sighash));
+ /* create and encode signature */
+ if ((sig = ECDSA_do_sign(sighash, sizeof(sighash), ec)) == NULL) {
+ skdebug(__func__, "ECDSA_do_sign failed");
+ goto out;
+ }
+ ECDSA_SIG_get0(sig, &sig_r, &sig_s);
+ response->sig_r_len = BN_num_bytes(sig_r);
+ response->sig_s_len = BN_num_bytes(sig_s);
+ if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL ||
+ (response->sig_s = calloc(1, response->sig_s_len)) == NULL) {
+ skdebug(__func__, "calloc signature failed");
+ goto out;
+ }
+ BN_bn2bin(sig_r, response->sig_r);
+ BN_bn2bin(sig_s, response->sig_s);
+ ret = 0;
+ out:
+ explicit_bzero(&ctx, sizeof(ctx));
+ explicit_bzero(&apphash, sizeof(apphash));
+ explicit_bzero(&sighash, sizeof(sighash));
+ ECDSA_SIG_free(sig);
+ if (ret != 0) {
+ free(response->sig_r);
+ free(response->sig_s);
+ response->sig_r = NULL;
+ response->sig_s = NULL;
+ }
+ BIO_free(bio);
+ EC_KEY_free(ec);
+ EVP_PKEY_free(pk);
+ return ret;
+#else
+ return -1;
+#endif
+}
+
+static int
+sig_ed25519(const uint8_t *message, size_t message_len,
+ const char *application, uint32_t counter, uint8_t flags,
+ const uint8_t *key_handle, size_t key_handle_len,
+ struct sk_sign_response *response)
+{
+ size_t o;
+ int ret = -1;
+ SHA2_CTX ctx;
+ uint8_t apphash[SHA256_DIGEST_LENGTH];
+ uint8_t signbuf[sizeof(apphash) + sizeof(flags) +
+ sizeof(counter) + SHA256_DIGEST_LENGTH];
+ uint8_t sig[crypto_sign_ed25519_BYTES + sizeof(signbuf)];
+ unsigned long long smlen;
+
+ if (key_handle_len != crypto_sign_ed25519_SECRETKEYBYTES) {
+ skdebug(__func__, "bad key handle length %zu", key_handle_len);
+ goto out;
+ }
+ /* Expect message to be pre-hashed */
+ if (message_len != SHA256_DIGEST_LENGTH) {
+ skdebug(__func__, "bad message len %zu", message_len);
+ goto out;
+ }
+ /* Prepare data to be signed */
+ dump("message", message, message_len);
+ SHA256Init(&ctx);
+ SHA256Update(&ctx, (const u_char *)application, strlen(application));
+ SHA256Final(apphash, &ctx);
+ dump("apphash", apphash, sizeof(apphash));
+
+ memcpy(signbuf, apphash, sizeof(apphash));
+ o = sizeof(apphash);
+ signbuf[o++] = flags;
+ signbuf[o++] = (counter >> 24) & 0xff;
+ signbuf[o++] = (counter >> 16) & 0xff;
+ signbuf[o++] = (counter >> 8) & 0xff;
+ signbuf[o++] = counter & 0xff;
+ memcpy(signbuf + o, message, message_len);
+ o += message_len;
+ if (o != sizeof(signbuf)) {
+ skdebug(__func__, "bad sign buf len %zu, expected %zu",
+ o, sizeof(signbuf));
+ goto out;
+ }
+ dump("signbuf", signbuf, sizeof(signbuf));
+ /* create and encode signature */
+ smlen = sizeof(signbuf);
+ if (crypto_sign_ed25519(sig, &smlen, signbuf, sizeof(signbuf),
+ key_handle) != 0) {
+ skdebug(__func__, "crypto_sign_ed25519 failed");
+ goto out;
+ }
+ if (smlen <= sizeof(signbuf)) {
+ skdebug(__func__, "bad sign smlen %llu, expected min %zu",
+ smlen, sizeof(signbuf) + 1);
+ goto out;
+ }
+ response->sig_r_len = (size_t)(smlen - sizeof(signbuf));
+ if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL) {
+ skdebug(__func__, "calloc signature failed");
+ goto out;
+ }
+ memcpy(response->sig_r, sig, response->sig_r_len);
+ dump("sig_r", response->sig_r, response->sig_r_len);
+ ret = 0;
+ out:
+ explicit_bzero(&ctx, sizeof(ctx));
+ explicit_bzero(&apphash, sizeof(apphash));
+ explicit_bzero(&signbuf, sizeof(signbuf));
+ explicit_bzero(&sig, sizeof(sig));
+ if (ret != 0) {
+ free(response->sig_r);
+ response->sig_r = NULL;
+ }
+ return ret;
+}
+
+int
+sk_sign(uint32_t alg, const uint8_t *data, size_t datalen,
+ const char *application, const uint8_t *key_handle, size_t key_handle_len,
+ uint8_t flags, const char *pin, struct sk_option **options,
+ struct sk_sign_response **sign_response)
+{
+ struct sk_sign_response *response = NULL;
+ int ret = SSH_SK_ERR_GENERAL;
+ SHA2_CTX ctx;
+ uint8_t message[32];
+
+ if (sign_response == NULL) {
+ skdebug(__func__, "sign_response == NULL");
+ goto out;
+ }
+ *sign_response = NULL;
+ if (check_options(options) != 0)
+ goto out; /* error already logged */
+ if ((response = calloc(1, sizeof(*response))) == NULL) {
+ skdebug(__func__, "calloc response failed");
+ goto out;
+ }
+ SHA256Init(&ctx);
+ SHA256Update(&ctx, data, datalen);
+ SHA256Final(message, &ctx);
+ response->flags = flags;
+ response->counter = 0x12345678;
+ switch(alg) {
+ case SSH_SK_ECDSA:
+ if (sig_ecdsa(message, sizeof(message), application,
+ response->counter, flags, key_handle, key_handle_len,
+ response) != 0)
+ goto out;
+ break;
+ case SSH_SK_ED25519:
+ if (sig_ed25519(message, sizeof(message), application,
+ response->counter, flags, key_handle, key_handle_len,
+ response) != 0)
+ goto out;
+ break;
+ default:
+ skdebug(__func__, "unsupported key type %d", alg);
+ return -1;
+ }
+ *sign_response = response;
+ response = NULL;
+ ret = 0;
+ out:
+ explicit_bzero(message, sizeof(message));
+ if (response != NULL) {
+ free(response->sig_r);
+ free(response->sig_s);
+ free(response);
+ }
+ return ret;
+}
+
+int
+sk_load_resident_keys(const char *pin, struct sk_option **options,
+ struct sk_resident_key ***rks, size_t *nrks)
+{
+ return SSH_SK_ERR_UNSUPPORTED;
+}
diff --git a/regress/mkdtemp.c b/regress/mkdtemp.c
new file mode 100644
index 0000000..a7be1bd
--- /dev/null
+++ b/regress/mkdtemp.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017 Colin Watson <cjwatson@debian.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* Roughly equivalent to "mktemp -d -t TEMPLATE", but portable. */
+
+#include "includes.h"
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "log.h"
+
+static void
+usage(void)
+{
+ fprintf(stderr, "mkdtemp template\n");
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *base;
+ const char *tmpdir;
+ char template[PATH_MAX];
+ int r;
+ char *dir;
+
+ if (argc != 2)
+ usage();
+ base = argv[1];
+
+ if ((tmpdir = getenv("TMPDIR")) == NULL)
+ tmpdir = "/tmp";
+ r = snprintf(template, sizeof(template), "%s/%s", tmpdir, base);
+ if (r < 0 || (size_t)r >= sizeof(template))
+ fatal("template string too long");
+ dir = mkdtemp(template);
+ if (dir == NULL) {
+ perror("mkdtemp");
+ exit(1);
+ }
+ puts(dir);
+ return 0;
+}
diff --git a/regress/modpipe.c b/regress/modpipe.c
new file mode 100644
index 0000000..5f4824b
--- /dev/null
+++ b/regress/modpipe.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2012 Damien Miller <djm@mindrot.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* $OpenBSD: modpipe.c,v 1.6 2013/11/21 03:16:47 djm Exp $ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <errno.h>
+#ifdef HAVE_ERR_H
+# include <err.h>
+#endif
+#include "openbsd-compat/getopt_long.c"
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: modpipe -w [-m modspec ...] < in > out\n");
+ fprintf(stderr, "modspec is one of:\n");
+ fprintf(stderr, " xor:offset:value - XOR \"value\" at \"offset\"\n");
+ fprintf(stderr, " andor:offset:val1:val2 - AND \"val1\" then OR \"val2\" at \"offset\"\n");
+ exit(1);
+}
+
+#define MAX_MODIFICATIONS 256
+struct modification {
+ enum { MOD_XOR, MOD_AND_OR } what;
+ unsigned long long offset;
+ u_int8_t m1, m2;
+};
+
+static void
+parse_modification(const char *s, struct modification *m)
+{
+ char what[16+1];
+ int n, m1, m2;
+
+ bzero(m, sizeof(*m));
+ if ((n = sscanf(s, "%16[^:]%*[:]%llu%*[:]%i%*[:]%i",
+ what, &m->offset, &m1, &m2)) < 3)
+ errx(1, "Invalid modification spec \"%s\"", s);
+ if (strcasecmp(what, "xor") == 0) {
+ if (n > 3)
+ errx(1, "Invalid modification spec \"%s\"", s);
+ if (m1 < 0 || m1 > 0xff)
+ errx(1, "Invalid XOR modification value");
+ m->what = MOD_XOR;
+ m->m1 = m1;
+ } else if (strcasecmp(what, "andor") == 0) {
+ if (n != 4)
+ errx(1, "Invalid modification spec \"%s\"", s);
+ if (m1 < 0 || m1 > 0xff)
+ errx(1, "Invalid AND modification value");
+ if (m2 < 0 || m2 > 0xff)
+ errx(1, "Invalid OR modification value");
+ m->what = MOD_AND_OR;
+ m->m1 = m1;
+ m->m2 = m2;
+ } else
+ errx(1, "Invalid modification type \"%s\"", what);
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch;
+ u_char buf[8192];
+ size_t total;
+ ssize_t r, s, o;
+ struct modification mods[MAX_MODIFICATIONS];
+ u_int i, wflag = 0, num_mods = 0;
+
+ while ((ch = getopt(argc, argv, "wm:")) != -1) {
+ switch (ch) {
+ case 'm':
+ if (num_mods >= MAX_MODIFICATIONS)
+ errx(1, "Too many modifications");
+ parse_modification(optarg, &(mods[num_mods++]));
+ break;
+ case 'w':
+ wflag = 1;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ for (total = 0;;) {
+ r = s = read(STDIN_FILENO, buf, sizeof(buf));
+ if (r == 0)
+ break;
+ if (r < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ err(1, "read");
+ }
+ for (i = 0; i < num_mods; i++) {
+ if (mods[i].offset < total ||
+ mods[i].offset >= total + s)
+ continue;
+ switch (mods[i].what) {
+ case MOD_XOR:
+ buf[mods[i].offset - total] ^= mods[i].m1;
+ break;
+ case MOD_AND_OR:
+ buf[mods[i].offset - total] &= mods[i].m1;
+ buf[mods[i].offset - total] |= mods[i].m2;
+ break;
+ }
+ }
+ for (o = 0; o < s; o += r) {
+ r = write(STDOUT_FILENO, buf, s - o);
+ if (r == 0)
+ break;
+ if (r < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ err(1, "write");
+ }
+ }
+ total += s;
+ }
+ /* Warn if modifications not reached in input stream */
+ r = 0;
+ for (i = 0; wflag && i < num_mods; i++) {
+ if (mods[i].offset < total)
+ continue;
+ r = 1;
+ fprintf(stderr, "modpipe: warning - mod %u not reached\n", i);
+ }
+ return r;
+}
diff --git a/regress/moduli.in b/regress/moduli.in
new file mode 100644
index 0000000..e69c902
--- /dev/null
+++ b/regress/moduli.in
@@ -0,0 +1,3 @@
+20160301052556 2 6 100 2047 5 DA57B18976E9C55CEAC3BFFF70419A1550258EA7359400BD4FAC8F4203B73E0BC54D62C0A2D9AA9B543FACA0290514EA426DE6FEF897CB858243511DCE5170420C799D888DCFDC4502FF49B66F34E75C00E98A55408A791FF5CFEA7C288F8E6664226A6A90BE237D2E40C207B5AD0CAEDFDA4946E63AEA351A09EF462515FED4098694241CD07E2CB7727B39B8B1B9467D72DFB908D8169F5DB3CD5A6BEBE1344C585A882508B760402E86EB9B5548A7B98635ECFCDC02FF62B29C53847142FC598ADC66F622F6E9F73BDF02B3D795C0DF23D00E5A3A7748F3E1D5B06F46D4568CE3F4CC57E67D4C36DF5C12800620698C727CC5F5BCACF3B7E17E37D19F4647
+20160301052601 2 6 100 2047 2 DA57B18976E9C55CEAC3BFFF70419A1550258EA7359400BD4FAC8F4203B73E0BC54D62C0A2D9AA9B543FACA0290514EA426DE6FEF897CB858243511DCE5170420C799D888DCFDC4502FF49B66F34E75C00E98A55408A791FF5CFEA7C288F8E6664226A6A90BE237D2E40C207B5AD0CAEDFDA4946E63AEA351A09EF462515FED4098694241CD07E2CB7727B39B8B1B9467D72DFB908D8169F5DB3CD5A6BEBE1344C585A882508B760402E86EB9B5548A7B98635ECFCDC02FF62B29C53847142FC598ADC66F622F6E9F73BDF02B3D795C0DF23D00E5A3A7748F3E1D5B06F46D4568CE3F4CC57E67D4C36DF5C12800620698C727CC5F5BCACF3B7E17E37D1A5C13B
+20160301052612 2 6 100 2047 5 DA57B18976E9C55CEAC3BFFF70419A1550258EA7359400BD4FAC8F4203B73E0BC54D62C0A2D9AA9B543FACA0290514EA426DE6FEF897CB858243511DCE5170420C799D888DCFDC4502FF49B66F34E75C00E98A55408A791FF5CFEA7C288F8E6664226A6A90BE237D2E40C207B5AD0CAEDFDA4946E63AEA351A09EF462515FED4098694241CD07E2CB7727B39B8B1B9467D72DFB908D8169F5DB3CD5A6BEBE1344C585A882508B760402E86EB9B5548A7B98635ECFCDC02FF62B29C53847142FC598ADC66F622F6E9F73BDF02B3D795C0DF23D00E5A3A7748F3E1D5B06F46D4568CE3F4CC57E67D4C36DF5C12800620698C727CC5F5BCACF3B7E17E37D1B7A3EF
diff --git a/regress/multiplex.sh b/regress/multiplex.sh
new file mode 100644
index 0000000..f9c8fc1
--- /dev/null
+++ b/regress/multiplex.sh
@@ -0,0 +1,210 @@
+# $OpenBSD: multiplex.sh,v 1.35 2023/01/13 04:47:34 dtucker Exp $
+# Placed in the Public Domain.
+
+make_tmpdir
+CTL=${SSH_REGRESS_TMP}/ctl-sock
+
+tid="connection multiplexing"
+
+trace "will use ProxyCommand $proxycmd"
+if config_defined DISABLE_FD_PASSING ; then
+ echo "skipped (not supported on this platform)"
+ exit 0
+fi
+
+P=3301 # test port
+
+wait_for_mux_master_ready()
+{
+ for i in 1 2 3 4 5 6 7 8 9; do
+ ${SSH} -F $OBJ/ssh_config -S $CTL -Ocheck otherhost \
+ >/dev/null 2>&1 && return 0
+ sleep $i
+ done
+ fatal "mux master never becomes ready"
+}
+
+maybe_add_scp_path_to_sshd
+start_sshd
+
+start_mux_master()
+{
+ trace "start master, fork to background"
+ ${SSH} -Nn2 -MS$CTL -F $OBJ/ssh_config -oSendEnv="_XXX_TEST" somehost \
+ -E $TEST_REGRESS_LOGFILE 2>&1 &
+ # NB. $SSH_PID will be killed by test-exec.sh:cleanup on fatal errors.
+ SSH_PID=$!
+ wait_for_mux_master_ready
+}
+
+start_mux_master
+
+verbose "test $tid: setenv"
+trace "setenv over multiplexed connection"
+_XXX_TEST=blah ${SSH} -F $OBJ/ssh_config -oSendEnv="_XXX_TEST" -S$CTL otherhost sh << 'EOF'
+ test X"$_XXX_TEST" = X"blah"
+EOF
+if [ $? -ne 0 ]; then
+ fail "environment not found"
+fi
+
+verbose "test $tid: envpass"
+trace "env passing over multiplexed connection"
+${SSH} -F $OBJ/ssh_config -oSetEnv="_XXX_TEST=foo" -S$CTL otherhost sh << 'EOF'
+ test X"$_XXX_TEST" = X"foo"
+EOF
+if [ $? -ne 0 ]; then
+ fail "environment not found"
+fi
+
+
+verbose "test $tid: transfer"
+rm -f ${COPY}
+trace "ssh transfer over multiplexed connection and check result"
+${SSH} -F $OBJ/ssh_config -S$CTL otherhost cat ${DATA} > ${COPY}
+test -f ${COPY} || fail "ssh -Sctl: failed copy ${DATA}"
+cmp ${DATA} ${COPY} || fail "ssh -Sctl: corrupted copy of ${DATA}"
+
+rm -f ${COPY}
+trace "ssh transfer over multiplexed connection and check result"
+${SSH} -F $OBJ/ssh_config -S $CTL otherhost cat ${DATA} > ${COPY}
+test -f ${COPY} || fail "ssh -S ctl: failed copy ${DATA}"
+cmp ${DATA} ${COPY} || fail "ssh -S ctl: corrupted copy of ${DATA}"
+
+rm -f ${COPY}
+trace "sftp transfer over multiplexed connection and check result"
+echo "get ${DATA} ${COPY}" | \
+ ${SFTP} -S ${SSH} -F $OBJ/ssh_config -oControlPath=$CTL otherhost >>$TEST_REGRESS_LOGFILE 2>&1
+test -f ${COPY} || fail "sftp: failed copy ${DATA}"
+cmp ${DATA} ${COPY} || fail "sftp: corrupted copy of ${DATA}"
+
+rm -f ${COPY}
+trace "scp transfer over multiplexed connection and check result"
+${SCP} -S ${SSH} -F $OBJ/ssh_config -oControlPath=$CTL otherhost:${DATA} ${COPY} >>$TEST_REGRESS_LOGFILE 2>&1
+test -f ${COPY} || fail "scp: failed copy ${DATA}"
+cmp ${DATA} ${COPY} || fail "scp: corrupted copy of ${DATA}"
+
+rm -f ${COPY}
+verbose "test $tid: forward"
+trace "forward over TCP/IP and check result"
+$NC -N -l 127.0.0.1 $((${PORT} + 1)) < ${DATA} > /dev/null &
+netcat_pid=$!
+${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -L127.0.0.1:$((${PORT} + 2)):127.0.0.1:$((${PORT} + 1)) otherhost >>$TEST_SSH_LOGFILE 2>&1
+sleep 1 # XXX remove once race fixed
+$NC 127.0.0.1 $((${PORT} + 2)) < /dev/null > ${COPY}
+cmp ${DATA} ${COPY} || fail "ssh: corrupted copy of ${DATA}"
+kill $netcat_pid 2>/dev/null
+rm -f ${COPY} $OBJ/unix-[123].fwd
+
+trace "forward over UNIX and check result"
+$NC -N -Ul $OBJ/unix-1.fwd < ${DATA} > /dev/null &
+netcat_pid=$!
+${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -L$OBJ/unix-2.fwd:$OBJ/unix-1.fwd otherhost >>$TEST_SSH_LOGFILE 2>&1
+${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -R$OBJ/unix-3.fwd:$OBJ/unix-2.fwd otherhost >>$TEST_SSH_LOGFILE 2>&1
+sleep 1 # XXX remove once race fixed
+$NC -U $OBJ/unix-3.fwd < /dev/null > ${COPY}
+cmp ${DATA} ${COPY} || fail "ssh: corrupted copy of ${DATA}"
+kill $netcat_pid 2>/dev/null
+rm -f ${COPY} $OBJ/unix-[123].fwd
+
+for s in 0 1 4 5 44; do
+ for mode in "" "-Oproxy"; do
+ trace "exit status $s over multiplexed connection ($mode)"
+ verbose "test $tid: status $s ($mode)"
+ ${SSH} -F $OBJ/ssh_config -S $CTL $mode otherhost exit $s
+ r=$?
+ if [ $r -ne $s ]; then
+ fail "exit code mismatch: $r != $s"
+ fi
+
+ # same with early close of stdout/err
+ trace "exit status $s with early close over multiplexed connection ($mode)"
+ ${SSH} -F $OBJ/ssh_config -S $CTL -n $mode otherhost \
+ exec sh -c \'"sleep 2; exec > /dev/null 2>&1; sleep 3; exit $s"\'
+ r=$?
+ if [ $r -ne $s ]; then
+ fail "exit code (with sleep) mismatch: $r != $s"
+ fi
+ done
+done
+
+verbose "test $tid: cmd check"
+${SSH} -F $OBJ/ssh_config -S $CTL -Ocheck otherhost >>$TEST_REGRESS_LOGFILE 2>&1 \
+ || fail "check command failed"
+
+verbose "test $tid: cmd forward local (TCP)"
+${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -L $P:localhost:$PORT otherhost \
+ || fail "request local forward failed"
+sleep 1 # XXX remove once race fixed
+${SSH} -F $OBJ/ssh_config -p$P otherhost true \
+ || fail "connect to local forward port failed"
+${SSH} -F $OBJ/ssh_config -S $CTL -Ocancel -L $P:localhost:$PORT otherhost \
+ || fail "cancel local forward failed"
+${SSH} -F $OBJ/ssh_config -p$P otherhost true \
+ && fail "local forward port still listening"
+
+verbose "test $tid: cmd forward remote (TCP)"
+${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -R $P:localhost:$PORT otherhost \
+ || fail "request remote forward failed"
+sleep 1 # XXX remove once race fixed
+${SSH} -F $OBJ/ssh_config -p$P otherhost true \
+ || fail "connect to remote forwarded port failed"
+${SSH} -F $OBJ/ssh_config -S $CTL -Ocancel -R $P:localhost:$PORT otherhost \
+ || fail "cancel remote forward failed"
+${SSH} -F $OBJ/ssh_config -p$P otherhost true \
+ && fail "remote forward port still listening"
+
+verbose "test $tid: cmd forward local (UNIX)"
+${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -L $OBJ/unix-1.fwd:localhost:$PORT otherhost \
+ || fail "request local forward failed"
+sleep 1 # XXX remove once race fixed
+echo "" | $NC -U $OBJ/unix-1.fwd | \
+ grep "Invalid SSH identification string" >/dev/null 2>&1 \
+ || fail "connect to local forward path failed"
+${SSH} -F $OBJ/ssh_config -S $CTL -Ocancel -L $OBJ/unix-1.fwd:localhost:$PORT otherhost \
+ || fail "cancel local forward failed"
+N=$(echo "xyzzy" | $NC -U $OBJ/unix-1.fwd 2>&1 | grep "xyzzy" | wc -l)
+test ${N} -eq 0 || fail "local forward path still listening"
+rm -f $OBJ/unix-1.fwd
+
+verbose "test $tid: cmd forward remote (UNIX)"
+${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -R $OBJ/unix-1.fwd:localhost:$PORT otherhost \
+ || fail "request remote forward failed"
+sleep 1 # XXX remove once race fixed
+echo "" | $NC -U $OBJ/unix-1.fwd | \
+ grep "Invalid SSH identification string" >/dev/null 2>&1 \
+ || fail "connect to remote forwarded path failed"
+${SSH} -F $OBJ/ssh_config -S $CTL -Ocancel -R $OBJ/unix-1.fwd:localhost:$PORT otherhost \
+ || fail "cancel remote forward failed"
+N=$(echo "xyzzy" | $NC -U $OBJ/unix-1.fwd 2>&1 | grep "xyzzy" | wc -l)
+test ${N} -eq 0 || fail "remote forward path still listening"
+rm -f $OBJ/unix-1.fwd
+
+verbose "test $tid: cmd exit"
+${SSH} -F $OBJ/ssh_config -S $CTL -Oexit otherhost >>$TEST_REGRESS_LOGFILE 2>&1 \
+ || fail "send exit command failed"
+
+# Wait for master to exit
+wait $SSH_PID
+kill -0 $SSH_PID >/dev/null 2>&1 && fail "exit command failed"
+
+# Restart master and test -O stop command with master using -N
+verbose "test $tid: cmd stop"
+trace "restart master, fork to background"
+start_mux_master
+
+# start a long-running command then immediately request a stop
+${SSH} -F $OBJ/ssh_config -S $CTL otherhost "sleep 10; exit 0" \
+ >>$TEST_REGRESS_LOGFILE 2>&1 &
+SLEEP_PID=$!
+${SSH} -F $OBJ/ssh_config -S $CTL -Ostop otherhost >>$TEST_REGRESS_LOGFILE 2>&1 \
+ || fail "send stop command failed"
+
+# wait until both long-running command and master have exited.
+wait $SLEEP_PID
+[ $! != 0 ] || fail "waiting for concurrent command"
+wait $SSH_PID
+[ $! != 0 ] || fail "waiting for master stop"
+kill -0 $SSH_PID >/dev/null 2>&1 && fatal "stop command failed"
+SSH_PID="" # Already gone, so don't kill in cleanup
+
diff --git a/regress/multipubkey.sh b/regress/multipubkey.sh
new file mode 100644
index 0000000..8cdda1a
--- /dev/null
+++ b/regress/multipubkey.sh
@@ -0,0 +1,75 @@
+# $OpenBSD: multipubkey.sh,v 1.4 2021/06/07 01:16:34 djm Exp $
+# Placed in the Public Domain.
+
+tid="multiple pubkey"
+
+rm -f $OBJ/authorized_keys_$USER $OBJ/user_ca_key* $OBJ/user_key*
+rm -f $OBJ/authorized_principals_$USER $OBJ/cert_user_key*
+
+mv $OBJ/sshd_proxy $OBJ/sshd_proxy.orig
+mv $OBJ/ssh_proxy $OBJ/ssh_proxy.orig
+
+# Create a CA key
+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_ca_key ||\
+ fatal "ssh-keygen failed"
+
+# Make some keys and a certificate.
+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_key1 || \
+ fatal "ssh-keygen failed"
+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_key2 || \
+ fatal "ssh-keygen failed"
+${SSHKEYGEN} -q -s $OBJ/user_ca_key -I "regress user key for $USER" \
+ -z $$ -n ${USER},mekmitasdigoat $OBJ/user_key1 ||
+ fail "couldn't sign user_key1"
+# Copy the private key alongside the cert to allow better control of when
+# it is offered.
+mv $OBJ/user_key1-cert.pub $OBJ/cert_user_key1.pub
+cp -p $OBJ/user_key1 $OBJ/cert_user_key1
+
+grep -v IdentityFile $OBJ/ssh_proxy.orig > $OBJ/ssh_proxy
+
+opts="-oProtocol=2 -F $OBJ/ssh_proxy -oIdentitiesOnly=yes"
+opts="$opts -i $OBJ/cert_user_key1 -i $OBJ/user_key1 -i $OBJ/user_key2"
+
+for match in no yes ; do
+ (
+ cat $OBJ/sshd_proxy.orig
+ echo "Protocol 2"
+ echo "TrustedUserCAKeys $OBJ/user_ca_key.pub"
+ echo "AuthorizedPrincipalsFile $OBJ/authorized_principals_%u"
+ ) > $OBJ/sshd_proxy
+ if test "$match" = "yes" ; then
+ echo "AuthenticationMethods none" >> $OBJ/sshd_proxy
+ echo "PubkeyAuthentication no" >> $OBJ/sshd_proxy
+ echo "Match all" >> $OBJ/sshd_proxy
+ echo "PubkeyAuthentication yes" >> $OBJ/sshd_proxy
+ fi
+ echo "AuthenticationMethods publickey,publickey" >> $OBJ/sshd_proxy
+
+ # Single key should fail.
+ trace "match $match single key"
+ rm -f $OBJ/authorized_principals_$USER
+ cat $OBJ/user_key1.pub > $OBJ/authorized_keys_$USER
+ ${SSH} $opts proxy true && fail "ssh succeeded with key"
+
+ # Single key with same-public cert should fail.
+ trace "match $match pubkey + identical cert"
+ echo mekmitasdigoat > $OBJ/authorized_principals_$USER
+ cat $OBJ/user_key1.pub > $OBJ/authorized_keys_$USER
+ ${SSH} $opts proxy true && fail "ssh succeeded with key+cert"
+
+ # Multiple plain keys should succeed.
+ trace "match $match multiple public"
+ rm -f $OBJ/authorized_principals_$USER
+ cat $OBJ/user_key1.pub $OBJ/user_key2.pub > \
+ $OBJ/authorized_keys_$USER
+ ${SSH} $opts proxy true || fail "ssh failed with multiple keys"
+ # Cert and different key should succeed
+
+ # Key and different-public cert should succeed.
+ trace "match $match pubkey + different cert"
+ echo mekmitasdigoat > $OBJ/authorized_principals_$USER
+ cat $OBJ/user_key2.pub > $OBJ/authorized_keys_$USER
+ ${SSH} $opts proxy true || fail "ssh failed with key/cert"
+done
+
diff --git a/regress/netcat.c b/regress/netcat.c
new file mode 100644
index 0000000..20ec3f5
--- /dev/null
+++ b/regress/netcat.c
@@ -0,0 +1,1686 @@
+/* $OpenBSD: netcat.c,v 1.131 2015/09/03 23:06:28 sobrado Exp $ */
+/*
+ * Copyright (c) 2001 Eric Jackson <ericj@monkey.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Re-written nc(1) for OpenBSD. Original implementation by
+ * *Hobbit* <hobbit@avian.org>.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netinet/ip.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "atomicio.h"
+
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#else
+# ifdef HAVE_SYS_POLL_H
+# include <sys/poll.h>
+# endif
+#endif
+#ifdef HAVE_ERR_H
+# include <err.h>
+#endif
+#ifdef HAVE_SYS_BYTEORDER_H
+# include <sys/byteorder.h>
+#endif
+
+/* rename to avoid collision in libssh */
+#define timeout_connect netcat_timeout_connect
+
+/* Telnet options from arpa/telnet.h */
+#define IAC 255
+#define DONT 254
+#define DO 253
+#define WONT 252
+#define WILL 251
+
+#ifndef SUN_LEN
+#define SUN_LEN(su) \
+ (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
+#endif
+
+#define PORT_MAX 65535
+#define PORT_MAX_LEN 6
+#define UNIX_DG_TMP_SOCKET_SIZE 19
+
+#define POLL_STDIN 0
+#define POLL_NETOUT 1
+#define POLL_NETIN 2
+#define POLL_STDOUT 3
+#define BUFSIZE 16384
+
+/* Command Line Options */
+int dflag; /* detached, no stdin */
+int Fflag; /* fdpass sock to stdout */
+unsigned int iflag; /* Interval Flag */
+int kflag; /* More than one connect */
+int lflag; /* Bind to local port */
+int Nflag; /* shutdown() network socket */
+int nflag; /* Don't do name look up */
+char *Pflag; /* Proxy username */
+char *pflag; /* Localport flag */
+int rflag; /* Random ports flag */
+char *sflag; /* Source Address */
+int tflag; /* Telnet Emulation */
+int uflag; /* UDP - Default to TCP */
+int vflag; /* Verbosity */
+int xflag; /* Socks proxy */
+int zflag; /* Port Scan Flag */
+int Dflag; /* sodebug */
+int Iflag; /* TCP receive buffer size */
+int Oflag; /* TCP send buffer size */
+int Sflag; /* TCP MD5 signature option */
+int Tflag = -1; /* IP Type of Service */
+int rtableid = -1;
+
+int timeout = -1;
+int family = AF_UNSPEC;
+char *portlist[PORT_MAX+1];
+char *unix_dg_tmp_socket;
+
+void atelnet(int, unsigned char *, unsigned int);
+void build_ports(char *);
+void help(void);
+int local_listen(char *, char *, struct addrinfo);
+void readwrite(int);
+void fdpass(int nfd) __attribute__((noreturn));
+int remote_connect(const char *, const char *, struct addrinfo);
+int timeout_connect(int, const struct sockaddr *, socklen_t);
+int socks_connect(const char *, const char *, struct addrinfo,
+ const char *, const char *, struct addrinfo, int, const char *);
+int udptest(int);
+int unix_bind(char *);
+int unix_connect(char *);
+int unix_listen(char *);
+void set_common_sockopts(int, int);
+int map_tos(char *, int *);
+void report_connect(const struct sockaddr *, socklen_t);
+void usage(int);
+ssize_t drainbuf(int, unsigned char *, size_t *);
+ssize_t fillbuf(int, unsigned char *, size_t *);
+
+
+int
+main(int argc, char *argv[])
+{
+ int ch, s, ret, socksv;
+ char *host, *uport;
+ struct addrinfo hints;
+ struct servent *sv;
+ socklen_t len;
+ struct sockaddr_storage cliaddr;
+ char *proxy = NULL;
+ const char *errstr, *proxyhost = "", *proxyport = NULL;
+ struct addrinfo proxyhints;
+ char unix_dg_tmp_socket_buf[UNIX_DG_TMP_SOCKET_SIZE];
+
+ ret = 1;
+ s = 0;
+ socksv = 5;
+ host = NULL;
+ uport = NULL;
+ sv = NULL;
+
+ signal(SIGPIPE, SIG_IGN);
+
+ while ((ch = getopt(argc, argv,
+ "46DdFhI:i:klNnO:P:p:rSs:tT:UuV:vw:X:x:z")) != -1) {
+ switch (ch) {
+ case '4':
+ family = AF_INET;
+ break;
+ case '6':
+ family = AF_INET6;
+ break;
+ case 'U':
+ family = AF_UNIX;
+ break;
+ case 'X':
+ if (strcasecmp(optarg, "connect") == 0)
+ socksv = -1; /* HTTP proxy CONNECT */
+ else if (strcmp(optarg, "4") == 0)
+ socksv = 4; /* SOCKS v.4 */
+ else if (strcmp(optarg, "5") == 0)
+ socksv = 5; /* SOCKS v.5 */
+ else
+ errx(1, "unsupported proxy protocol");
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'F':
+ Fflag = 1;
+ break;
+ case 'h':
+ help();
+ break;
+ case 'i':
+ iflag = strtonum(optarg, 0, UINT_MAX, &errstr);
+ if (errstr)
+ errx(1, "interval %s: %s", errstr, optarg);
+ break;
+ case 'k':
+ kflag = 1;
+ break;
+ case 'l':
+ lflag = 1;
+ break;
+ case 'N':
+ Nflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'P':
+ Pflag = optarg;
+ break;
+ case 'p':
+ pflag = optarg;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 's':
+ sflag = optarg;
+ break;
+ case 't':
+ tflag = 1;
+ break;
+ case 'u':
+ uflag = 1;
+ break;
+#ifdef SO_RTABLE
+ case 'V':
+ rtableid = (int)strtonum(optarg, 0,
+ RT_TABLEID_MAX, &errstr);
+ if (errstr)
+ errx(1, "rtable %s: %s", errstr, optarg);
+ break;
+#endif
+ case 'v':
+ vflag = 1;
+ break;
+ case 'w':
+ timeout = strtonum(optarg, 0, INT_MAX / 1000, &errstr);
+ if (errstr)
+ errx(1, "timeout %s: %s", errstr, optarg);
+ timeout *= 1000;
+ break;
+ case 'x':
+ xflag = 1;
+ if ((proxy = strdup(optarg)) == NULL)
+ errx(1, "strdup");
+ break;
+ case 'z':
+ zflag = 1;
+ break;
+ case 'D':
+ Dflag = 1;
+ break;
+ case 'I':
+ Iflag = strtonum(optarg, 1, 65536 << 14, &errstr);
+ if (errstr != NULL)
+ errx(1, "TCP receive window %s: %s",
+ errstr, optarg);
+ break;
+ case 'O':
+ Oflag = strtonum(optarg, 1, 65536 << 14, &errstr);
+ if (errstr != NULL)
+ errx(1, "TCP send window %s: %s",
+ errstr, optarg);
+ break;
+ case 'S':
+ Sflag = 1;
+ break;
+ case 'T':
+ errstr = NULL;
+ errno = 0;
+ if (map_tos(optarg, &Tflag))
+ break;
+ if (strlen(optarg) > 1 && optarg[0] == '0' &&
+ optarg[1] == 'x')
+ Tflag = (int)strtol(optarg, NULL, 16);
+ else
+ Tflag = (int)strtonum(optarg, 0, 255,
+ &errstr);
+ if (Tflag < 0 || Tflag > 255 || errstr || errno)
+ errx(1, "illegal tos value %s", optarg);
+ break;
+ default:
+ usage(1);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Cruft to make sure options are clean, and used properly. */
+ if (argv[0] && !argv[1] && family == AF_UNIX) {
+ host = argv[0];
+ uport = NULL;
+ } else if (argv[0] && !argv[1]) {
+ if (!lflag)
+ usage(1);
+ uport = argv[0];
+ host = NULL;
+ } else if (argv[0] && argv[1]) {
+ host = argv[0];
+ uport = argv[1];
+ } else
+ usage(1);
+
+ if (lflag && sflag)
+ errx(1, "cannot use -s and -l");
+ if (lflag && pflag)
+ errx(1, "cannot use -p and -l");
+ if (lflag && zflag)
+ errx(1, "cannot use -z and -l");
+ if (!lflag && kflag)
+ errx(1, "must use -l with -k");
+
+ /* Get name of temporary socket for unix datagram client */
+ if ((family == AF_UNIX) && uflag && !lflag) {
+ if (sflag) {
+ unix_dg_tmp_socket = sflag;
+ } else {
+ strlcpy(unix_dg_tmp_socket_buf, "/tmp/nc.XXXXXXXXXX",
+ UNIX_DG_TMP_SOCKET_SIZE);
+ if (mktemp(unix_dg_tmp_socket_buf) == NULL)
+ err(1, "mktemp");
+ unix_dg_tmp_socket = unix_dg_tmp_socket_buf;
+ }
+ }
+
+ /* Initialize addrinfo structure. */
+ if (family != AF_UNIX) {
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = family;
+ hints.ai_socktype = uflag ? SOCK_DGRAM : SOCK_STREAM;
+ hints.ai_protocol = uflag ? IPPROTO_UDP : IPPROTO_TCP;
+ if (nflag)
+ hints.ai_flags |= AI_NUMERICHOST;
+ }
+
+ if (xflag) {
+ if (uflag)
+ errx(1, "no proxy support for UDP mode");
+
+ if (lflag)
+ errx(1, "no proxy support for listen");
+
+ if (family == AF_UNIX)
+ errx(1, "no proxy support for unix sockets");
+
+ /* XXX IPv6 transport to proxy would probably work */
+ if (family == AF_INET6)
+ errx(1, "no proxy support for IPv6");
+
+ if (sflag)
+ errx(1, "no proxy support for local source address");
+
+ proxyhost = strsep(&proxy, ":");
+ proxyport = proxy;
+
+ memset(&proxyhints, 0, sizeof(struct addrinfo));
+ proxyhints.ai_family = family;
+ proxyhints.ai_socktype = SOCK_STREAM;
+ proxyhints.ai_protocol = IPPROTO_TCP;
+ if (nflag)
+ proxyhints.ai_flags |= AI_NUMERICHOST;
+ }
+
+ if (lflag) {
+ int connfd;
+ ret = 0;
+
+ if (family == AF_UNIX) {
+ if (uflag)
+ s = unix_bind(host);
+ else
+ s = unix_listen(host);
+ }
+
+ /* Allow only one connection at a time, but stay alive. */
+ for (;;) {
+ if (family != AF_UNIX)
+ s = local_listen(host, uport, hints);
+ if (s < 0)
+ err(1, "local_listen");
+ /*
+ * For UDP and -k, don't connect the socket, let it
+ * receive datagrams from multiple socket pairs.
+ */
+ if (uflag && kflag)
+ readwrite(s);
+ /*
+ * For UDP and not -k, we will use recvfrom() initially
+ * to wait for a caller, then use the regular functions
+ * to talk to the caller.
+ */
+ else if (uflag && !kflag) {
+ int rv, plen;
+ char buf[16384];
+ struct sockaddr_storage z;
+
+ len = sizeof(z);
+ plen = 2048;
+ rv = recvfrom(s, buf, plen, MSG_PEEK,
+ (struct sockaddr *)&z, &len);
+ if (rv < 0)
+ err(1, "recvfrom");
+
+ rv = connect(s, (struct sockaddr *)&z, len);
+ if (rv < 0)
+ err(1, "connect");
+
+ if (vflag)
+ report_connect((struct sockaddr *)&z, len);
+
+ readwrite(s);
+ } else {
+ len = sizeof(cliaddr);
+ connfd = accept(s, (struct sockaddr *)&cliaddr,
+ &len);
+ if (connfd == -1) {
+ /* For now, all errnos are fatal */
+ err(1, "accept");
+ }
+ if (vflag)
+ report_connect((struct sockaddr *)&cliaddr, len);
+
+ readwrite(connfd);
+ close(connfd);
+ }
+
+ if (family != AF_UNIX)
+ close(s);
+ else if (uflag) {
+ if (connect(s, NULL, 0) < 0)
+ err(1, "connect");
+ }
+
+ if (!kflag)
+ break;
+ }
+ } else if (family == AF_UNIX) {
+ ret = 0;
+
+ if ((s = unix_connect(host)) > 0 && !zflag) {
+ readwrite(s);
+ close(s);
+ } else
+ ret = 1;
+
+ if (uflag)
+ unlink(unix_dg_tmp_socket);
+ exit(ret);
+
+ } else {
+ int i = 0;
+
+ /* Construct the portlist[] array. */
+ build_ports(uport);
+
+ /* Cycle through portlist, connecting to each port. */
+ for (i = 0; portlist[i] != NULL; i++) {
+ if (s)
+ close(s);
+
+ if (xflag)
+ s = socks_connect(host, portlist[i], hints,
+ proxyhost, proxyport, proxyhints, socksv,
+ Pflag);
+ else
+ s = remote_connect(host, portlist[i], hints);
+
+ if (s < 0)
+ continue;
+
+ ret = 0;
+ if (vflag || zflag) {
+ /* For UDP, make sure we are connected. */
+ if (uflag) {
+ if (udptest(s) == -1) {
+ ret = 1;
+ continue;
+ }
+ }
+
+ /* Don't look up port if -n. */
+ if (nflag)
+ sv = NULL;
+ else {
+ sv = getservbyport(
+ ntohs(atoi(portlist[i])),
+ uflag ? "udp" : "tcp");
+ }
+
+ fprintf(stderr,
+ "Connection to %s %s port [%s/%s] "
+ "succeeded!\n", host, portlist[i],
+ uflag ? "udp" : "tcp",
+ sv ? sv->s_name : "*");
+ }
+ if (Fflag)
+ fdpass(s);
+ else if (!zflag)
+ readwrite(s);
+ }
+ }
+
+ if (s)
+ close(s);
+
+ exit(ret);
+}
+
+/*
+ * unix_bind()
+ * Returns a unix socket bound to the given path
+ */
+int
+unix_bind(char *path)
+{
+ struct sockaddr_un sun_sa;
+ int s;
+
+ /* Create unix domain socket. */
+ if ((s = socket(AF_UNIX, uflag ? SOCK_DGRAM : SOCK_STREAM,
+ 0)) < 0)
+ return (-1);
+
+ memset(&sun_sa, 0, sizeof(struct sockaddr_un));
+ sun_sa.sun_family = AF_UNIX;
+
+ if (strlcpy(sun_sa.sun_path, path, sizeof(sun_sa.sun_path)) >=
+ sizeof(sun_sa.sun_path)) {
+ close(s);
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+
+ if (bind(s, (struct sockaddr *)&sun_sa, SUN_LEN(&sun_sa)) < 0) {
+ close(s);
+ return (-1);
+ }
+ return (s);
+}
+
+/*
+ * unix_connect()
+ * Returns a socket connected to a local unix socket. Returns -1 on failure.
+ */
+int
+unix_connect(char *path)
+{
+ struct sockaddr_un sun_sa;
+ int s;
+
+ if (uflag) {
+ if ((s = unix_bind(unix_dg_tmp_socket)) < 0)
+ return (-1);
+ } else {
+ if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ return (-1);
+ }
+ (void)fcntl(s, F_SETFD, FD_CLOEXEC);
+
+ memset(&sun_sa, 0, sizeof(struct sockaddr_un));
+ sun_sa.sun_family = AF_UNIX;
+
+ if (strlcpy(sun_sa.sun_path, path, sizeof(sun_sa.sun_path)) >=
+ sizeof(sun_sa.sun_path)) {
+ close(s);
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+ if (connect(s, (struct sockaddr *)&sun_sa, SUN_LEN(&sun_sa)) < 0) {
+ close(s);
+ return (-1);
+ }
+ return (s);
+
+}
+
+/*
+ * unix_listen()
+ * Create a unix domain socket, and listen on it.
+ */
+int
+unix_listen(char *path)
+{
+ int s;
+ if ((s = unix_bind(path)) < 0)
+ return (-1);
+
+ if (listen(s, 5) < 0) {
+ close(s);
+ return (-1);
+ }
+ return (s);
+}
+
+/*
+ * remote_connect()
+ * Returns a socket connected to a remote host. Properly binds to a local
+ * port or source address if needed. Returns -1 on failure.
+ */
+int
+remote_connect(const char *host, const char *port, struct addrinfo hints)
+{
+ struct addrinfo *res, *res0;
+ int s, error;
+#if defined(SO_RTABLE) || defined(SO_BINDANY)
+ int on = 1;
+#endif
+
+ if ((error = getaddrinfo(host, port, &hints, &res)))
+ errx(1, "getaddrinfo: %s", gai_strerror(error));
+
+ res0 = res;
+ do {
+ if ((s = socket(res0->ai_family, res0->ai_socktype,
+ res0->ai_protocol)) < 0)
+ continue;
+
+#ifdef SO_RTABLE
+ if (rtableid >= 0 && (setsockopt(s, SOL_SOCKET, SO_RTABLE,
+ &rtableid, sizeof(rtableid)) == -1))
+ err(1, "setsockopt SO_RTABLE");
+#endif
+ /* Bind to a local port or source address if specified. */
+ if (sflag || pflag) {
+ struct addrinfo ahints, *ares;
+
+#ifdef SO_BINDANY
+ /* try SO_BINDANY, but don't insist */
+ setsockopt(s, SOL_SOCKET, SO_BINDANY, &on, sizeof(on));
+#endif
+ memset(&ahints, 0, sizeof(struct addrinfo));
+ ahints.ai_family = res0->ai_family;
+ ahints.ai_socktype = uflag ? SOCK_DGRAM : SOCK_STREAM;
+ ahints.ai_protocol = uflag ? IPPROTO_UDP : IPPROTO_TCP;
+ ahints.ai_flags = AI_PASSIVE;
+ if ((error = getaddrinfo(sflag, pflag, &ahints, &ares)))
+ errx(1, "getaddrinfo: %s", gai_strerror(error));
+
+ if (bind(s, (struct sockaddr *)ares->ai_addr,
+ ares->ai_addrlen) < 0)
+ err(1, "bind failed");
+ freeaddrinfo(ares);
+ }
+
+ set_common_sockopts(s, res0->ai_family);
+
+ if (timeout_connect(s, res0->ai_addr, res0->ai_addrlen) == 0)
+ break;
+ else if (vflag)
+ warn("connect to %s port %s (%s) failed", host, port,
+ uflag ? "udp" : "tcp");
+
+ close(s);
+ s = -1;
+ } while ((res0 = res0->ai_next) != NULL);
+
+ freeaddrinfo(res);
+
+ return (s);
+}
+
+int
+timeout_connect(int s, const struct sockaddr *name, socklen_t namelen)
+{
+ struct pollfd pfd;
+ socklen_t optlen;
+ int flags = 0, optval;
+ int ret;
+
+ if (timeout != -1) {
+ flags = fcntl(s, F_GETFL, 0);
+ if (fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1)
+ err(1, "set non-blocking mode");
+ }
+
+ if ((ret = connect(s, name, namelen)) != 0 && errno == EINPROGRESS) {
+ pfd.fd = s;
+ pfd.events = POLLOUT;
+ if ((ret = poll(&pfd, 1, timeout)) == 1) {
+ optlen = sizeof(optval);
+ if ((ret = getsockopt(s, SOL_SOCKET, SO_ERROR,
+ &optval, &optlen)) == 0) {
+ errno = optval;
+ ret = optval == 0 ? 0 : -1;
+ }
+ } else if (ret == 0) {
+ errno = ETIMEDOUT;
+ ret = -1;
+ } else
+ err(1, "poll failed");
+ }
+
+ if (timeout != -1 && fcntl(s, F_SETFL, flags) == -1)
+ err(1, "restoring flags");
+
+ return (ret);
+}
+
+/*
+ * local_listen()
+ * Returns a socket listening on a local port, binds to specified source
+ * address. Returns -1 on failure.
+ */
+int
+local_listen(char *host, char *port, struct addrinfo hints)
+{
+ struct addrinfo *res, *res0;
+ int s, ret, x = 1;
+ int error;
+
+ /* Allow nodename to be null. */
+ hints.ai_flags |= AI_PASSIVE;
+
+ /*
+ * In the case of binding to a wildcard address
+ * default to binding to an ipv4 address.
+ */
+ if (host == NULL && hints.ai_family == AF_UNSPEC)
+ hints.ai_family = AF_INET;
+
+ if ((error = getaddrinfo(host, port, &hints, &res)))
+ errx(1, "getaddrinfo: %s", gai_strerror(error));
+
+ res0 = res;
+ do {
+ if ((s = socket(res0->ai_family, res0->ai_socktype,
+ res0->ai_protocol)) < 0)
+ continue;
+
+#ifdef SO_RTABLE
+ if (rtableid >= 0 && (setsockopt(s, SOL_SOCKET, SO_RTABLE,
+ &rtableid, sizeof(rtableid)) == -1))
+ err(1, "setsockopt SO_RTABLE");
+#endif
+#ifdef SO_REUSEPORT
+ ret = setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &x, sizeof(x));
+ if (ret == -1)
+ err(1, "setsockopt SO_REUSEPORT");
+#endif
+#ifdef SO_REUSEADDR
+ ret = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
+ if (ret == -1)
+ err(1, "setsockopt SO_REUSEADDR");
+#endif
+ set_common_sockopts(s, res0->ai_family);
+
+ if (bind(s, (struct sockaddr *)res0->ai_addr,
+ res0->ai_addrlen) == 0)
+ break;
+
+ close(s);
+ s = -1;
+ } while ((res0 = res0->ai_next) != NULL);
+
+ if (!uflag && s != -1) {
+ if (listen(s, 1) < 0)
+ err(1, "listen");
+ }
+
+ freeaddrinfo(res);
+
+ return (s);
+}
+
+/*
+ * readwrite()
+ * Loop that polls on the network file descriptor and stdin.
+ */
+void
+readwrite(int net_fd)
+{
+ struct pollfd pfd[4];
+ int stdin_fd = STDIN_FILENO;
+ int stdout_fd = STDOUT_FILENO;
+ unsigned char netinbuf[BUFSIZE];
+ size_t netinbufpos = 0;
+ unsigned char stdinbuf[BUFSIZE];
+ size_t stdinbufpos = 0;
+ int n, num_fds;
+ ssize_t ret;
+
+ /* don't read from stdin if requested */
+ if (dflag)
+ stdin_fd = -1;
+
+ /* stdin */
+ pfd[POLL_STDIN].fd = stdin_fd;
+ pfd[POLL_STDIN].events = POLLIN;
+
+ /* network out */
+ pfd[POLL_NETOUT].fd = net_fd;
+ pfd[POLL_NETOUT].events = 0;
+
+ /* network in */
+ pfd[POLL_NETIN].fd = net_fd;
+ pfd[POLL_NETIN].events = POLLIN;
+
+ /* stdout */
+ pfd[POLL_STDOUT].fd = stdout_fd;
+ pfd[POLL_STDOUT].events = 0;
+
+ while (1) {
+ /* both inputs are gone, buffers are empty, we are done */
+ if (pfd[POLL_STDIN].fd == -1 && pfd[POLL_NETIN].fd == -1
+ && stdinbufpos == 0 && netinbufpos == 0) {
+ close(net_fd);
+ return;
+ }
+ /* both outputs are gone, we can't continue */
+ if (pfd[POLL_NETOUT].fd == -1 && pfd[POLL_STDOUT].fd == -1) {
+ close(net_fd);
+ return;
+ }
+ /* listen and net in gone, queues empty, done */
+ if (lflag && pfd[POLL_NETIN].fd == -1
+ && stdinbufpos == 0 && netinbufpos == 0) {
+ close(net_fd);
+ return;
+ }
+
+ /* help says -i is for "wait between lines sent". We read and
+ * write arbitrary amounts of data, and we don't want to start
+ * scanning for newlines, so this is as good as it gets */
+ if (iflag)
+ sleep(iflag);
+
+ /* poll */
+ num_fds = poll(pfd, 4, timeout);
+
+ /* treat poll errors */
+ if (num_fds == -1) {
+ close(net_fd);
+ err(1, "polling error");
+ }
+
+ /* timeout happened */
+ if (num_fds == 0)
+ return;
+
+ /* treat socket error conditions */
+ for (n = 0; n < 4; n++) {
+ if (pfd[n].revents & (POLLERR|POLLNVAL)) {
+ pfd[n].fd = -1;
+ }
+ }
+ /* reading is possible after HUP */
+ if (pfd[POLL_STDIN].events & POLLIN &&
+ pfd[POLL_STDIN].revents & POLLHUP &&
+ ! (pfd[POLL_STDIN].revents & POLLIN))
+ pfd[POLL_STDIN].fd = -1;
+
+ if (pfd[POLL_NETIN].events & POLLIN &&
+ pfd[POLL_NETIN].revents & POLLHUP &&
+ ! (pfd[POLL_NETIN].revents & POLLIN))
+ pfd[POLL_NETIN].fd = -1;
+
+ if (pfd[POLL_NETOUT].revents & POLLHUP) {
+ if (Nflag)
+ shutdown(pfd[POLL_NETOUT].fd, SHUT_WR);
+ pfd[POLL_NETOUT].fd = -1;
+ }
+ /* if HUP, stop watching stdout */
+ if (pfd[POLL_STDOUT].revents & POLLHUP)
+ pfd[POLL_STDOUT].fd = -1;
+ /* if no net out, stop watching stdin */
+ if (pfd[POLL_NETOUT].fd == -1)
+ pfd[POLL_STDIN].fd = -1;
+ /* if no stdout, stop watching net in */
+ if (pfd[POLL_STDOUT].fd == -1) {
+ if (pfd[POLL_NETIN].fd != -1)
+ shutdown(pfd[POLL_NETIN].fd, SHUT_RD);
+ pfd[POLL_NETIN].fd = -1;
+ }
+
+ /* try to read from stdin */
+ if (pfd[POLL_STDIN].revents & POLLIN && stdinbufpos < BUFSIZE) {
+ ret = fillbuf(pfd[POLL_STDIN].fd, stdinbuf,
+ &stdinbufpos);
+ /* error or eof on stdin - remove from pfd */
+ if (ret == 0 || ret == -1)
+ pfd[POLL_STDIN].fd = -1;
+ /* read something - poll net out */
+ if (stdinbufpos > 0)
+ pfd[POLL_NETOUT].events = POLLOUT;
+ /* filled buffer - remove self from polling */
+ if (stdinbufpos == BUFSIZE)
+ pfd[POLL_STDIN].events = 0;
+ }
+ /* try to write to network */
+ if (pfd[POLL_NETOUT].revents & POLLOUT && stdinbufpos > 0) {
+ ret = drainbuf(pfd[POLL_NETOUT].fd, stdinbuf,
+ &stdinbufpos);
+ if (ret == -1)
+ pfd[POLL_NETOUT].fd = -1;
+ /* buffer empty - remove self from polling */
+ if (stdinbufpos == 0)
+ pfd[POLL_NETOUT].events = 0;
+ /* buffer no longer full - poll stdin again */
+ if (stdinbufpos < BUFSIZE)
+ pfd[POLL_STDIN].events = POLLIN;
+ }
+ /* try to read from network */
+ if (pfd[POLL_NETIN].revents & POLLIN && netinbufpos < BUFSIZE) {
+ ret = fillbuf(pfd[POLL_NETIN].fd, netinbuf,
+ &netinbufpos);
+ if (ret == -1)
+ pfd[POLL_NETIN].fd = -1;
+ /* eof on net in - remove from pfd */
+ if (ret == 0) {
+ shutdown(pfd[POLL_NETIN].fd, SHUT_RD);
+ pfd[POLL_NETIN].fd = -1;
+ }
+ /* read something - poll stdout */
+ if (netinbufpos > 0)
+ pfd[POLL_STDOUT].events = POLLOUT;
+ /* filled buffer - remove self from polling */
+ if (netinbufpos == BUFSIZE)
+ pfd[POLL_NETIN].events = 0;
+ /* handle telnet */
+ if (tflag)
+ atelnet(pfd[POLL_NETIN].fd, netinbuf,
+ netinbufpos);
+ }
+ /* try to write to stdout */
+ if (pfd[POLL_STDOUT].revents & POLLOUT && netinbufpos > 0) {
+ ret = drainbuf(pfd[POLL_STDOUT].fd, netinbuf,
+ &netinbufpos);
+ if (ret == -1)
+ pfd[POLL_STDOUT].fd = -1;
+ /* buffer empty - remove self from polling */
+ if (netinbufpos == 0)
+ pfd[POLL_STDOUT].events = 0;
+ /* buffer no longer full - poll net in again */
+ if (netinbufpos < BUFSIZE)
+ pfd[POLL_NETIN].events = POLLIN;
+ }
+
+ /* stdin gone and queue empty? */
+ if (pfd[POLL_STDIN].fd == -1 && stdinbufpos == 0) {
+ if (pfd[POLL_NETOUT].fd != -1 && Nflag)
+ shutdown(pfd[POLL_NETOUT].fd, SHUT_WR);
+ pfd[POLL_NETOUT].fd = -1;
+ }
+ /* net in gone and queue empty? */
+ if (pfd[POLL_NETIN].fd == -1 && netinbufpos == 0) {
+ pfd[POLL_STDOUT].fd = -1;
+ }
+ }
+}
+
+ssize_t
+drainbuf(int fd, unsigned char *buf, size_t *bufpos)
+{
+ ssize_t n;
+ ssize_t adjust;
+
+ n = write(fd, buf, *bufpos);
+ /* don't treat EAGAIN, EINTR as error */
+ if (n == -1 && (errno == EAGAIN || errno == EINTR))
+ n = -2;
+ if (n <= 0)
+ return n;
+ /* adjust buffer */
+ adjust = *bufpos - n;
+ if (adjust > 0)
+ memmove(buf, buf + n, adjust);
+ *bufpos -= n;
+ return n;
+}
+
+
+ssize_t
+fillbuf(int fd, unsigned char *buf, size_t *bufpos)
+{
+ size_t num = BUFSIZE - *bufpos;
+ ssize_t n;
+
+ n = read(fd, buf + *bufpos, num);
+ /* don't treat EAGAIN, EINTR as error */
+ if (n == -1 && (errno == EAGAIN || errno == EINTR))
+ n = -2;
+ if (n <= 0)
+ return n;
+ *bufpos += n;
+ return n;
+}
+
+/*
+ * fdpass()
+ * Pass the connected file descriptor to stdout and exit.
+ */
+void
+fdpass(int nfd)
+{
+#if defined(HAVE_SENDMSG) && (defined(HAVE_ACCRIGHTS_IN_MSGHDR) || defined(HAVE_CONTROL_IN_MSGHDR))
+ struct msghdr msg;
+#ifndef HAVE_ACCRIGHTS_IN_MSGHDR
+ union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(int))];
+ } cmsgbuf;
+ struct cmsghdr *cmsg;
+#endif
+ struct iovec vec;
+ char ch = '\0';
+ struct pollfd pfd;
+ ssize_t r;
+
+ memset(&msg, 0, sizeof(msg));
+#ifdef HAVE_ACCRIGHTS_IN_MSGHDR
+ msg.msg_accrights = (caddr_t)&nfd;
+ msg.msg_accrightslen = sizeof(nfd);
+#else
+ memset(&cmsgbuf, 0, sizeof(cmsgbuf));
+ msg.msg_control = (caddr_t)&cmsgbuf.buf;
+ msg.msg_controllen = sizeof(cmsgbuf.buf);
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ *(int *)CMSG_DATA(cmsg) = nfd;
+#endif
+
+ vec.iov_base = &ch;
+ vec.iov_len = 1;
+ msg.msg_iov = &vec;
+ msg.msg_iovlen = 1;
+
+ bzero(&pfd, sizeof(pfd));
+ pfd.fd = STDOUT_FILENO;
+ pfd.events = POLLOUT;
+ for (;;) {
+ r = sendmsg(STDOUT_FILENO, &msg, 0);
+ if (r == -1) {
+ if (errno == EAGAIN || errno == EINTR) {
+ if (poll(&pfd, 1, -1) == -1)
+ err(1, "poll");
+ continue;
+ }
+ err(1, "sendmsg");
+ } else if (r != 1)
+ errx(1, "sendmsg: unexpected return value %zd", r);
+ else
+ break;
+ }
+ exit(0);
+#else
+ errx(1, "%s: file descriptor passing not supported", __func__);
+#endif
+}
+
+/* Deal with RFC 854 WILL/WONT DO/DONT negotiation. */
+void
+atelnet(int nfd, unsigned char *buf, unsigned int size)
+{
+ unsigned char *p, *end;
+ unsigned char obuf[4];
+
+ if (size < 3)
+ return;
+ end = buf + size - 2;
+
+ for (p = buf; p < end; p++) {
+ if (*p != IAC)
+ continue;
+
+ obuf[0] = IAC;
+ p++;
+ if ((*p == WILL) || (*p == WONT))
+ obuf[1] = DONT;
+ else if ((*p == DO) || (*p == DONT))
+ obuf[1] = WONT;
+ else
+ continue;
+
+ p++;
+ obuf[2] = *p;
+ if (atomicio(vwrite, nfd, obuf, 3) != 3)
+ warn("Write Error!");
+ }
+}
+
+/*
+ * build_ports()
+ * Build an array of ports in portlist[], listing each port
+ * that we should try to connect to.
+ */
+void
+build_ports(char *p)
+{
+ const char *errstr;
+ char *n;
+ int hi, lo, cp;
+ int x = 0;
+
+ if ((n = strchr(p, '-')) != NULL) {
+ *n = '\0';
+ n++;
+
+ /* Make sure the ports are in order: lowest->highest. */
+ hi = strtonum(n, 1, PORT_MAX, &errstr);
+ if (errstr)
+ errx(1, "port number %s: %s", errstr, n);
+ lo = strtonum(p, 1, PORT_MAX, &errstr);
+ if (errstr)
+ errx(1, "port number %s: %s", errstr, p);
+
+ if (lo > hi) {
+ cp = hi;
+ hi = lo;
+ lo = cp;
+ }
+
+ /* Load ports sequentially. */
+ for (cp = lo; cp <= hi; cp++) {
+ portlist[x] = calloc(1, PORT_MAX_LEN);
+ if (portlist[x] == NULL)
+ errx(1, "calloc");
+ snprintf(portlist[x], PORT_MAX_LEN, "%d", cp);
+ x++;
+ }
+
+ /* Randomly swap ports. */
+ if (rflag) {
+ int y;
+ char *c;
+
+ for (x = 0; x <= (hi - lo); x++) {
+ y = (arc4random() & 0xFFFF) % (hi - lo);
+ c = portlist[x];
+ portlist[x] = portlist[y];
+ portlist[y] = c;
+ }
+ }
+ } else {
+ hi = strtonum(p, 1, PORT_MAX, &errstr);
+ if (errstr)
+ errx(1, "port number %s: %s", errstr, p);
+ portlist[0] = strdup(p);
+ if (portlist[0] == NULL)
+ errx(1, "strdup");
+ }
+}
+
+/*
+ * udptest()
+ * Do a few writes to see if the UDP port is there.
+ * Fails once PF state table is full.
+ */
+int
+udptest(int s)
+{
+ int i, ret;
+
+ for (i = 0; i <= 3; i++) {
+ if (write(s, "X", 1) == 1)
+ ret = 1;
+ else
+ ret = -1;
+ }
+ return (ret);
+}
+
+void
+set_common_sockopts(int s, int af)
+{
+ int x = 1;
+
+#ifdef TCP_MD5SIG
+ if (Sflag) {
+ if (setsockopt(s, IPPROTO_TCP, TCP_MD5SIG,
+ &x, sizeof(x)) == -1)
+ err(1, "setsockopt");
+ }
+#endif
+ if (Dflag) {
+ if (setsockopt(s, SOL_SOCKET, SO_DEBUG,
+ &x, sizeof(x)) == -1)
+ err(1, "setsockopt");
+ }
+#if defined(IP_TOS) && defined(IPV6_TCLASS)
+ if (Tflag != -1) {
+ int proto, option;
+
+ if (af == AF_INET6) {
+ proto = IPPROTO_IPV6;
+ option = IPV6_TCLASS;
+ } else {
+ proto = IPPROTO_IP;
+ option = IP_TOS;
+ }
+
+ if (setsockopt(s, proto, option, &Tflag, sizeof(Tflag)) == -1)
+ err(1, "set IP ToS");
+ }
+#endif
+ if (Iflag) {
+ if (setsockopt(s, SOL_SOCKET, SO_RCVBUF,
+ &Iflag, sizeof(Iflag)) == -1)
+ err(1, "set TCP receive buffer size");
+ }
+ if (Oflag) {
+ if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
+ &Oflag, sizeof(Oflag)) == -1)
+ err(1, "set TCP send buffer size");
+ }
+}
+
+int
+map_tos(char *s, int *val)
+{
+#ifdef IP_TOS
+ /* DiffServ Codepoints and other TOS mappings */
+ const struct toskeywords {
+ const char *keyword;
+ int val;
+ } *t, toskeywords[] = {
+ { "af11", IPTOS_DSCP_AF11 },
+ { "af12", IPTOS_DSCP_AF12 },
+ { "af13", IPTOS_DSCP_AF13 },
+ { "af21", IPTOS_DSCP_AF21 },
+ { "af22", IPTOS_DSCP_AF22 },
+ { "af23", IPTOS_DSCP_AF23 },
+ { "af31", IPTOS_DSCP_AF31 },
+ { "af32", IPTOS_DSCP_AF32 },
+ { "af33", IPTOS_DSCP_AF33 },
+ { "af41", IPTOS_DSCP_AF41 },
+ { "af42", IPTOS_DSCP_AF42 },
+ { "af43", IPTOS_DSCP_AF43 },
+ { "critical", IPTOS_PREC_CRITIC_ECP },
+ { "cs0", IPTOS_DSCP_CS0 },
+ { "cs1", IPTOS_DSCP_CS1 },
+ { "cs2", IPTOS_DSCP_CS2 },
+ { "cs3", IPTOS_DSCP_CS3 },
+ { "cs4", IPTOS_DSCP_CS4 },
+ { "cs5", IPTOS_DSCP_CS5 },
+ { "cs6", IPTOS_DSCP_CS6 },
+ { "cs7", IPTOS_DSCP_CS7 },
+ { "ef", IPTOS_DSCP_EF },
+ { "inetcontrol", IPTOS_PREC_INTERNETCONTROL },
+ { "lowdelay", IPTOS_LOWDELAY },
+ { "netcontrol", IPTOS_PREC_NETCONTROL },
+ { "reliability", IPTOS_RELIABILITY },
+ { "throughput", IPTOS_THROUGHPUT },
+ { NULL, -1 },
+ };
+
+ for (t = toskeywords; t->keyword != NULL; t++) {
+ if (strcmp(s, t->keyword) == 0) {
+ *val = t->val;
+ return (1);
+ }
+ }
+#endif
+
+ return (0);
+}
+
+void
+report_connect(const struct sockaddr *sa, socklen_t salen)
+{
+ char remote_host[NI_MAXHOST];
+ char remote_port[NI_MAXSERV];
+ int herr;
+ int flags = NI_NUMERICSERV;
+
+ if (nflag)
+ flags |= NI_NUMERICHOST;
+
+ if ((herr = getnameinfo(sa, salen,
+ remote_host, sizeof(remote_host),
+ remote_port, sizeof(remote_port),
+ flags)) != 0) {
+ if (herr == EAI_SYSTEM)
+ err(1, "getnameinfo");
+ else
+ errx(1, "getnameinfo: %s", gai_strerror(herr));
+ }
+
+ fprintf(stderr,
+ "Connection from %s %s "
+ "received!\n", remote_host, remote_port);
+}
+
+void
+help(void)
+{
+ usage(0);
+ fprintf(stderr, "\tCommand Summary:\n\
+ \t-4 Use IPv4\n\
+ \t-6 Use IPv6\n\
+ \t-D Enable the debug socket option\n\
+ \t-d Detach from stdin\n\
+ \t-F Pass socket fd\n\
+ \t-h This help text\n\
+ \t-I length TCP receive buffer length\n\
+ \t-i secs\t Delay interval for lines sent, ports scanned\n\
+ \t-k Keep inbound sockets open for multiple connects\n\
+ \t-l Listen mode, for inbound connects\n\
+ \t-N Shutdown the network socket after EOF on stdin\n\
+ \t-n Suppress name/port resolutions\n\
+ \t-O length TCP send buffer length\n\
+ \t-P proxyuser\tUsername for proxy authentication\n\
+ \t-p port\t Specify local port for remote connects\n\
+ \t-r Randomize remote ports\n\
+ \t-S Enable the TCP MD5 signature option\n\
+ \t-s addr\t Local source address\n\
+ \t-T toskeyword\tSet IP Type of Service\n\
+ \t-t Answer TELNET negotiation\n\
+ \t-U Use UNIX domain socket\n\
+ \t-u UDP mode\n\
+ \t-V rtable Specify alternate routing table\n\
+ \t-v Verbose\n\
+ \t-w secs\t Timeout for connects and final net reads\n\
+ \t-X proto Proxy protocol: \"4\", \"5\" (SOCKS) or \"connect\"\n\
+ \t-x addr[:port]\tSpecify proxy address and port\n\
+ \t-z Zero-I/O mode [used for scanning]\n\
+ Port numbers can be individual or ranges: lo-hi [inclusive]\n");
+ exit(1);
+}
+
+void
+usage(int ret)
+{
+ fprintf(stderr,
+ "usage: nc [-46DdFhklNnrStUuvz] [-I length] [-i interval] [-O length]\n"
+ "\t [-P proxy_username] [-p source_port] [-s source] [-T toskeyword]\n"
+ "\t [-V rtable] [-w timeout] [-X proxy_protocol]\n"
+ "\t [-x proxy_address[:port]] [destination] [port]\n");
+ if (ret)
+ exit(1);
+}
+
+/* *** src/usr.bin/nc/socks.c *** */
+
+
+/* $OpenBSD: socks.c,v 1.20 2012/03/08 09:56:28 espie Exp $ */
+
+/*
+ * Copyright (c) 1999 Niklas Hallqvist. All rights reserved.
+ * Copyright (c) 2004, 2005 Damien Miller. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <resolv.h>
+
+#define SOCKS_PORT "1080"
+#define HTTP_PROXY_PORT "3128"
+#define HTTP_MAXHDRS 64
+#define SOCKS_V5 5
+#define SOCKS_V4 4
+#define SOCKS_NOAUTH 0
+#define SOCKS_NOMETHOD 0xff
+#define SOCKS_CONNECT 1
+#define SOCKS_IPV4 1
+#define SOCKS_DOMAIN 3
+#define SOCKS_IPV6 4
+
+int remote_connect(const char *, const char *, struct addrinfo);
+int socks_connect(const char *, const char *, struct addrinfo,
+ const char *, const char *, struct addrinfo, int,
+ const char *);
+
+static int
+decode_addrport(const char *h, const char *p, struct sockaddr *addr,
+ socklen_t addrlen, int v4only, int numeric)
+{
+ int r;
+ struct addrinfo hints, *res;
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = v4only ? PF_INET : PF_UNSPEC;
+ hints.ai_flags = numeric ? AI_NUMERICHOST : 0;
+ hints.ai_socktype = SOCK_STREAM;
+ r = getaddrinfo(h, p, &hints, &res);
+ /* Don't fatal when attempting to convert a numeric address */
+ if (r != 0) {
+ if (!numeric) {
+ errx(1, "getaddrinfo(\"%.64s\", \"%.64s\"): %s", h, p,
+ gai_strerror(r));
+ }
+ return (-1);
+ }
+ if (addrlen < res->ai_addrlen) {
+ freeaddrinfo(res);
+ errx(1, "internal error: addrlen < res->ai_addrlen");
+ }
+ memcpy(addr, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ return (0);
+}
+
+static int
+proxy_read_line(int fd, char *buf, size_t bufsz)
+{
+ size_t off;
+
+ for(off = 0;;) {
+ if (off >= bufsz)
+ errx(1, "proxy read too long");
+ if (atomicio(read, fd, buf + off, 1) != 1)
+ err(1, "proxy read");
+ /* Skip CR */
+ if (buf[off] == '\r')
+ continue;
+ if (buf[off] == '\n') {
+ buf[off] = '\0';
+ break;
+ }
+ off++;
+ }
+ return (off);
+}
+
+static const char *
+getproxypass(const char *proxyuser, const char *proxyhost)
+{
+ char prompt[512];
+ static char pw[256];
+
+ snprintf(prompt, sizeof(prompt), "Proxy password for %s@%s: ",
+ proxyuser, proxyhost);
+ if (readpassphrase(prompt, pw, sizeof(pw), RPP_REQUIRE_TTY) == NULL)
+ errx(1, "Unable to read proxy passphrase");
+ return (pw);
+}
+
+int
+socks_connect(const char *host, const char *port,
+ struct addrinfo hints __attribute__ ((__unused__)),
+ const char *proxyhost, const char *proxyport, struct addrinfo proxyhints,
+ int socksv, const char *proxyuser)
+{
+ int proxyfd, r, authretry = 0;
+ size_t hlen, wlen = 0;
+ unsigned char buf[1024];
+ size_t cnt;
+ struct sockaddr_storage addr;
+ struct sockaddr_in *in4 = (struct sockaddr_in *)&addr;
+ struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&addr;
+ in_port_t serverport;
+ const char *proxypass = NULL;
+
+ if (proxyport == NULL)
+ proxyport = (socksv == -1) ? HTTP_PROXY_PORT : SOCKS_PORT;
+
+ /* Abuse API to lookup port */
+ if (decode_addrport("0.0.0.0", port, (struct sockaddr *)&addr,
+ sizeof(addr), 1, 1) == -1)
+ errx(1, "unknown port \"%.64s\"", port);
+ serverport = in4->sin_port;
+
+ again:
+ if (authretry++ > 3)
+ errx(1, "Too many authentication failures");
+
+ proxyfd = remote_connect(proxyhost, proxyport, proxyhints);
+
+ if (proxyfd < 0)
+ return (-1);
+
+ if (socksv == 5) {
+ if (decode_addrport(host, port, (struct sockaddr *)&addr,
+ sizeof(addr), 0, 1) == -1)
+ addr.ss_family = 0; /* used in switch below */
+
+ /* Version 5, one method: no authentication */
+ buf[0] = SOCKS_V5;
+ buf[1] = 1;
+ buf[2] = SOCKS_NOAUTH;
+ cnt = atomicio(vwrite, proxyfd, buf, 3);
+ if (cnt != 3)
+ err(1, "write failed (%zu/3)", cnt);
+
+ cnt = atomicio(read, proxyfd, buf, 2);
+ if (cnt != 2)
+ err(1, "read failed (%zu/3)", cnt);
+
+ if (buf[1] == SOCKS_NOMETHOD)
+ errx(1, "authentication method negotiation failed");
+
+ switch (addr.ss_family) {
+ case 0:
+ /* Version 5, connect: domain name */
+
+ /* Max domain name length is 255 bytes */
+ hlen = strlen(host);
+ if (hlen > 255)
+ errx(1, "host name too long for SOCKS5");
+ buf[0] = SOCKS_V5;
+ buf[1] = SOCKS_CONNECT;
+ buf[2] = 0;
+ buf[3] = SOCKS_DOMAIN;
+ buf[4] = hlen;
+ memcpy(buf + 5, host, hlen);
+ memcpy(buf + 5 + hlen, &serverport, sizeof serverport);
+ wlen = 7 + hlen;
+ break;
+ case AF_INET:
+ /* Version 5, connect: IPv4 address */
+ buf[0] = SOCKS_V5;
+ buf[1] = SOCKS_CONNECT;
+ buf[2] = 0;
+ buf[3] = SOCKS_IPV4;
+ memcpy(buf + 4, &in4->sin_addr, sizeof in4->sin_addr);
+ memcpy(buf + 8, &in4->sin_port, sizeof in4->sin_port);
+ wlen = 10;
+ break;
+ case AF_INET6:
+ /* Version 5, connect: IPv6 address */
+ buf[0] = SOCKS_V5;
+ buf[1] = SOCKS_CONNECT;
+ buf[2] = 0;
+ buf[3] = SOCKS_IPV6;
+ memcpy(buf + 4, &in6->sin6_addr, sizeof in6->sin6_addr);
+ memcpy(buf + 20, &in6->sin6_port,
+ sizeof in6->sin6_port);
+ wlen = 22;
+ break;
+ default:
+ errx(1, "internal error: silly AF");
+ }
+
+ cnt = atomicio(vwrite, proxyfd, buf, wlen);
+ if (cnt != wlen)
+ err(1, "write failed (%zu/%zu)", cnt, wlen);
+
+ cnt = atomicio(read, proxyfd, buf, 4);
+ if (cnt != 4)
+ err(1, "read failed (%zu/4)", cnt);
+ if (buf[1] != 0)
+ errx(1, "connection failed, SOCKS error %d", buf[1]);
+ switch (buf[3]) {
+ case SOCKS_IPV4:
+ cnt = atomicio(read, proxyfd, buf + 4, 6);
+ if (cnt != 6)
+ err(1, "read failed (%zu/6)", cnt);
+ break;
+ case SOCKS_IPV6:
+ cnt = atomicio(read, proxyfd, buf + 4, 18);
+ if (cnt != 18)
+ err(1, "read failed (%zu/18)", cnt);
+ break;
+ default:
+ errx(1, "connection failed, unsupported address type");
+ }
+ } else if (socksv == 4) {
+ /* This will exit on lookup failure */
+ decode_addrport(host, port, (struct sockaddr *)&addr,
+ sizeof(addr), 1, 0);
+
+ /* Version 4 */
+ buf[0] = SOCKS_V4;
+ buf[1] = SOCKS_CONNECT; /* connect */
+ memcpy(buf + 2, &in4->sin_port, sizeof in4->sin_port);
+ memcpy(buf + 4, &in4->sin_addr, sizeof in4->sin_addr);
+ buf[8] = 0; /* empty username */
+ wlen = 9;
+
+ cnt = atomicio(vwrite, proxyfd, buf, wlen);
+ if (cnt != wlen)
+ err(1, "write failed (%zu/%zu)", cnt, wlen);
+
+ cnt = atomicio(read, proxyfd, buf, 8);
+ if (cnt != 8)
+ err(1, "read failed (%zu/8)", cnt);
+ if (buf[1] != 90)
+ errx(1, "connection failed, SOCKS error %d", buf[1]);
+ } else if (socksv == -1) {
+ /* HTTP proxy CONNECT */
+
+ /* Disallow bad chars in hostname */
+ if (strcspn(host, "\r\n\t []:") != strlen(host))
+ errx(1, "Invalid hostname");
+
+ /* Try to be sane about numeric IPv6 addresses */
+ if (strchr(host, ':') != NULL) {
+ r = snprintf(buf, sizeof(buf),
+ "CONNECT [%s]:%d HTTP/1.0\r\n",
+ host, ntohs(serverport));
+ } else {
+ r = snprintf(buf, sizeof(buf),
+ "CONNECT %s:%d HTTP/1.0\r\n",
+ host, ntohs(serverport));
+ }
+ if (r == -1 || (size_t)r >= sizeof(buf))
+ errx(1, "hostname too long");
+ r = strlen(buf);
+
+ cnt = atomicio(vwrite, proxyfd, buf, r);
+ if (cnt != (size_t)r)
+ err(1, "write failed (%zu/%d)", cnt, r);
+
+ if (authretry > 1) {
+ char resp[1024];
+
+ proxypass = getproxypass(proxyuser, proxyhost);
+ r = snprintf(buf, sizeof(buf), "%s:%s",
+ proxyuser, proxypass);
+ if (r == -1 || (size_t)r >= sizeof(buf) ||
+ b64_ntop(buf, strlen(buf), resp,
+ sizeof(resp)) == -1)
+ errx(1, "Proxy username/password too long");
+ r = snprintf(buf, sizeof(buf), "Proxy-Authorization: "
+ "Basic %s\r\n", resp);
+ if (r == -1 || (size_t)r >= sizeof(buf))
+ errx(1, "Proxy auth response too long");
+ r = strlen(buf);
+ if ((cnt = atomicio(vwrite, proxyfd, buf, r)) != (size_t)r)
+ err(1, "write failed (%zu/%d)", cnt, r);
+ }
+
+ /* Terminate headers */
+ if ((r = atomicio(vwrite, proxyfd, "\r\n", 2)) != 2)
+ err(1, "write failed (2/%d)", r);
+
+ /* Read status reply */
+ proxy_read_line(proxyfd, buf, sizeof(buf));
+ if (proxyuser != NULL &&
+ strncmp(buf, "HTTP/1.0 407 ", 12) == 0) {
+ if (authretry > 1) {
+ fprintf(stderr, "Proxy authentication "
+ "failed\n");
+ }
+ close(proxyfd);
+ goto again;
+ } else if (strncmp(buf, "HTTP/1.0 200 ", 12) != 0 &&
+ strncmp(buf, "HTTP/1.1 200 ", 12) != 0)
+ errx(1, "Proxy error: \"%s\"", buf);
+
+ /* Headers continue until we hit an empty line */
+ for (r = 0; r < HTTP_MAXHDRS; r++) {
+ proxy_read_line(proxyfd, buf, sizeof(buf));
+ if (*buf == '\0')
+ break;
+ }
+ if (*buf != '\0')
+ errx(1, "Too many proxy headers received");
+ } else
+ errx(1, "Unknown proxy protocol %d", socksv);
+
+ return (proxyfd);
+}
+
diff --git a/regress/percent.sh b/regress/percent.sh
new file mode 100644
index 0000000..3dfa8d2
--- /dev/null
+++ b/regress/percent.sh
@@ -0,0 +1,128 @@
+# $OpenBSD: percent.sh,v 1.16 2023/01/14 09:57:08 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="percent expansions"
+
+if [ -x "/usr/xpg4/bin/id" ]; then
+ PATH=/usr/xpg4/bin:$PATH
+ export PATH
+fi
+
+USER=`id -u -n`
+USERID=`id -u`
+HOST=`hostname | cut -f1 -d.`
+HOSTNAME=`hostname`
+HASH=""
+
+# Localcommand is evaluated after connection because %T is not available
+# until then. Because of this we use a different method of exercising it,
+# and we can't override the remote user otherwise authentication will fail.
+# We also have to explicitly enable it.
+echo "permitlocalcommand yes" >> $OBJ/ssh_proxy
+
+trial()
+{
+ opt="$1"; arg="$2"
+ expect=`echo "$3" | sed 's|^//|/|'` # approximate realpath
+
+ trace "test $opt=$arg $expect"
+ rm -f $OBJ/actual
+ got=""
+ case "$opt" in
+ localcommand)
+ ${SSH} -F $OBJ/ssh_proxy -o $opt="echo '$arg' >$OBJ/actual" \
+ somehost true
+ got=`cat $OBJ/actual`
+ ;;
+ userknownhostsfile)
+ # Move the userknownhosts file to what the expansion says,
+ # make sure ssh works then put it back.
+ mv "$OBJ/known_hosts" "$OBJ/$expect"
+ ${SSH} -F $OBJ/ssh_proxy -o $opt="$OBJ/$arg" somehost true && \
+ got="$expect"
+ mv "$OBJ/$expect" "$OBJ/known_hosts"
+ ;;
+ matchexec)
+ (cat $OBJ/ssh_proxy && \
+ echo "Match Exec \"echo '$arg' >$OBJ/actual\"") \
+ >$OBJ/ssh_proxy_match
+ ${SSH} -F $OBJ/ssh_proxy_match remuser@somehost true || true
+ got=`cat $OBJ/actual`
+ ;;
+ *forward)
+ # LocalForward and RemoteForward take two args and only
+ # operate on Unix domain socket paths
+ got=`${SSH} -F $OBJ/ssh_proxy -o $opt="/$arg /$arg" -G \
+ remuser@somehost | awk '$1=="'$opt'"{print $2" "$3}'`
+ expect="/$expect /$expect"
+ ;;
+ *)
+ got=`${SSH} -F $OBJ/ssh_proxy -o $opt="$arg" -G \
+ remuser@somehost | awk '$1=="'$opt'"{print $2}'`
+ esac
+ if [ "$got" != "$expect" ]; then
+ fail "$opt=$arg expect $expect got $got"
+ fi
+}
+
+for i in matchexec localcommand remotecommand controlpath identityagent \
+ forwardagent localforward remoteforward userknownhostsfile; do
+ verbose $tid $i percent
+ case "$i" in
+ localcommand|userknownhostsfile)
+ # Any test that's going to actually make a connection needs
+ # to use the real username.
+ REMUSER=$USER ;;
+ *)
+ REMUSER=remuser ;;
+ esac
+ if [ "$i" = "$localcommand" ]; then
+ trial $i '%T' NONE
+ fi
+ # Matches implementation in readconf.c:ssh_connection_hash()
+ if [ ! -z "${OPENSSL_BIN}" ]; then
+ HASH=`printf "${HOSTNAME}127.0.0.1${PORT}$REMUSER" |
+ $OPENSSL_BIN sha1 | cut -f2 -d' '`
+ trial $i '%C' $HASH
+ fi
+ trial $i '%%' '%'
+ trial $i '%i' $USERID
+ trial $i '%h' 127.0.0.1
+ trial $i '%L' $HOST
+ trial $i '%l' $HOSTNAME
+ trial $i '%n' somehost
+ trial $i '%k' localhost-with-alias
+ trial $i '%p' $PORT
+ trial $i '%r' $REMUSER
+ trial $i '%u' $USER
+ # We can't specify a full path outside the regress dir, so skip tests
+ # containing %d for UserKnownHostsFile
+ if [ "$i" != "userknownhostsfile" ]; then
+ trial $i '%d' $HOME
+ in='%%/%i/%h/%d/%L/%l/%n/%p/%r/%u'
+ out="%/$USERID/127.0.0.1/$HOME/$HOST/$HOSTNAME/somehost/$PORT/$REMUSER/$USER"
+ if [ ! -z "${HASH}" ]; then
+ in="$in/%C"
+ out="$out/$HASH"
+ fi
+ trial $i "$in" "$out"
+ fi
+done
+
+# Subset of above since we don't expand shell-style variables on anything that
+# runs a command because the shell will expand those.
+for i in controlpath identityagent forwardagent localforward remoteforward \
+ userknownhostsfile; do
+ verbose $tid $i dollar
+ FOO=bar
+ export FOO
+ trial $i '${FOO}' $FOO
+done
+
+
+# A subset of options support tilde expansion
+for i in controlpath identityagent forwardagent; do
+ verbose $tid $i tilde
+ trial $i '~' $HOME/
+ trial $i '~/.ssh' $HOME/.ssh
+done
diff --git a/regress/portnum.sh b/regress/portnum.sh
new file mode 100644
index 0000000..c56b869
--- /dev/null
+++ b/regress/portnum.sh
@@ -0,0 +1,34 @@
+# $OpenBSD: portnum.sh,v 1.2 2013/05/17 10:34:30 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="port number parsing"
+
+badport() {
+ port=$1
+ verbose "$tid: invalid port $port"
+ if ${SSH} -F $OBJ/ssh_proxy -p $port somehost true 2>/dev/null ; then
+ fail "$tid accepted invalid port $port"
+ fi
+}
+goodport() {
+ port=$1
+ verbose "$tid: valid port $port"
+ if ${SSH} -F $OBJ/ssh_proxy -p $port somehost true 2>/dev/null ; then
+ :
+ else
+ fail "$tid rejected valid port $port"
+ fi
+}
+
+badport 0
+badport 65536
+badport 131073
+badport 2000blah
+badport blah2000
+
+goodport 1
+goodport 22
+goodport 2222
+goodport 22222
+goodport 65535
+
diff --git a/regress/principals-command.sh b/regress/principals-command.sh
new file mode 100644
index 0000000..8278711
--- /dev/null
+++ b/regress/principals-command.sh
@@ -0,0 +1,168 @@
+# $OpenBSD: principals-command.sh,v 1.14 2021/09/30 05:26:26 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="authorized principals command"
+
+rm -f $OBJ/user_ca_key* $OBJ/cert_user_key*
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+
+if [ -z "$SUDO" -a ! -w /var/run ]; then
+ skip "need SUDO to create file in /var/run, test won't work without"
+fi
+
+case "$SSH_KEYTYPES" in
+ *ssh-rsa*) userkeytype=rsa ;;
+ *) userkeytype=ed25519 ;;
+esac
+
+SERIAL=$$
+
+# Create a CA key and a user certificate.
+${SSHKEYGEN} -q -N '' -t ed25519 -f $OBJ/user_ca_key || \
+ fatal "ssh-keygen of user_ca_key failed"
+${SSHKEYGEN} -q -N '' -t ${userkeytype} -f $OBJ/cert_user_key || \
+ fatal "ssh-keygen of cert_user_key failed"
+${SSHKEYGEN} -q -s $OBJ/user_ca_key -I "Joanne User" \
+ -z $$ -n ${USER},mekmitasdigoat $OBJ/cert_user_key || \
+ fatal "couldn't sign cert_user_key"
+
+CERT_BODY=`cat $OBJ/cert_user_key-cert.pub | awk '{ print $2 }'`
+CA_BODY=`cat $OBJ/user_ca_key.pub | awk '{ print $2 }'`
+CERT_FP=`${SSHKEYGEN} -lf $OBJ/cert_user_key-cert.pub | awk '{ print $2 }'`
+CA_FP=`${SSHKEYGEN} -lf $OBJ/user_ca_key.pub | awk '{ print $2 }'`
+
+# Establish a AuthorizedPrincipalsCommand in /var/run where it will have
+# acceptable directory permissions.
+PRINCIPALS_COMMAND="/var/run/principals_command_${LOGNAME}.$$"
+trap "$SUDO rm -f ${PRINCIPALS_COMMAND}" 0
+cat << _EOF | $SUDO sh -c "cat > '$PRINCIPALS_COMMAND'"
+#!/bin/sh
+test "x\$1" != "x${LOGNAME}" && exit 1
+test "x\$2" != "xssh-${userkeytype}-cert-v01@openssh.com" && exit 1
+test "x\$3" != "xssh-ed25519" && exit 1
+test "x\$4" != "xJoanne User" && exit 1
+test "x\$5" != "x${SERIAL}" && exit 1
+test "x\$6" != "x${CA_FP}" && exit 1
+test "x\$7" != "x${CERT_FP}" && exit 1
+test "x\$8" != "x${CERT_BODY}" && exit 1
+test "x\$9" != "x${CA_BODY}" && exit 1
+test -f "$OBJ/authorized_principals_${LOGNAME}" &&
+ exec cat "$OBJ/authorized_principals_${LOGNAME}"
+_EOF
+test $? -eq 0 || fatal "couldn't prepare principals command"
+$SUDO chmod 0755 "$PRINCIPALS_COMMAND"
+
+if ! $OBJ/check-perm -m keys-command $PRINCIPALS_COMMAND ; then
+ echo "skipping: $PRINCIPALS_COMMAND is unsuitable as " \
+ "AuthorizedPrincipalsCommand"
+ $SUDO rm -f $PRINCIPALS_COMMAND
+ exit 0
+fi
+
+if [ ! -x $PRINCIPALS_COMMAND ]; then
+ skip "$PRINCIPALS_COMMAND not executable " \
+ "(/var/run mounted noexec?)"
+fi
+
+# Test explicitly-specified principals
+# Setup for AuthorizedPrincipalsCommand
+rm -f $OBJ/authorized_keys_$USER
+(
+ cat $OBJ/sshd_proxy_bak
+ echo "AuthorizedKeysFile none"
+ echo "AuthorizedPrincipalsCommand $PRINCIPALS_COMMAND" \
+ "%u %t %T %i %s %F %f %k %K"
+ echo "AuthorizedPrincipalsCommandUser ${LOGNAME}"
+ echo "TrustedUserCAKeys $OBJ/user_ca_key.pub"
+) > $OBJ/sshd_proxy
+
+# XXX test missing command
+# XXX test failing command
+
+# Empty authorized_principals
+verbose "$tid: empty authorized_principals"
+echo > $OBJ/authorized_principals_$USER
+${SSH} -i $OBJ/cert_user_key \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpectedly"
+fi
+
+# Wrong authorized_principals
+verbose "$tid: wrong authorized_principals"
+echo gregorsamsa > $OBJ/authorized_principals_$USER
+${SSH} -i $OBJ/cert_user_key \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpectedly"
+fi
+
+# Correct authorized_principals
+verbose "$tid: correct authorized_principals"
+echo mekmitasdigoat > $OBJ/authorized_principals_$USER
+${SSH} -i $OBJ/cert_user_key \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+if [ $? -ne 0 ]; then
+ fail "ssh cert connect failed"
+fi
+
+# authorized_principals with bad key option
+verbose "$tid: authorized_principals bad key opt"
+echo 'blah mekmitasdigoat' > $OBJ/authorized_principals_$USER
+${SSH} -i $OBJ/cert_user_key \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpectedly"
+fi
+
+# authorized_principals with command=false
+verbose "$tid: authorized_principals command=false"
+echo 'command="false" mekmitasdigoat' > \
+ $OBJ/authorized_principals_$USER
+${SSH} -i $OBJ/cert_user_key \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpectedly"
+fi
+
+
+# authorized_principals with command=true
+verbose "$tid: authorized_principals command=true"
+echo 'command="true" mekmitasdigoat' > \
+ $OBJ/authorized_principals_$USER
+${SSH} -i $OBJ/cert_user_key \
+ -F $OBJ/ssh_proxy somehost false >/dev/null 2>&1
+if [ $? -ne 0 ]; then
+ fail "ssh cert connect failed"
+fi
+
+# Setup for principals= key option
+# TODO: remove?
+rm -f $OBJ/authorized_principals_$USER
+(
+ cat $OBJ/sshd_proxy_bak
+) > $OBJ/sshd_proxy
+
+# Wrong principals list
+verbose "$tid: wrong principals key option"
+(
+ printf 'cert-authority,principals="gregorsamsa" '
+ cat $OBJ/user_ca_key.pub
+) > $OBJ/authorized_keys_$USER
+${SSH} -i $OBJ/cert_user_key \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+if [ $? -eq 0 ]; then
+ fail "ssh cert connect succeeded unexpectedly"
+fi
+
+# Correct principals list
+verbose "$tid: correct principals key option"
+(
+ printf 'cert-authority,principals="mekmitasdigoat" '
+ cat $OBJ/user_ca_key.pub
+) > $OBJ/authorized_keys_$USER
+${SSH} -i $OBJ/cert_user_key \
+ -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+if [ $? -ne 0 ]; then
+ fail "ssh cert connect failed"
+fi
diff --git a/regress/proto-mismatch.sh b/regress/proto-mismatch.sh
new file mode 100644
index 0000000..6ab28c9
--- /dev/null
+++ b/regress/proto-mismatch.sh
@@ -0,0 +1,17 @@
+# $OpenBSD: proto-mismatch.sh,v 1.5 2017/04/30 23:34:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="protocol version mismatch"
+
+mismatch ()
+{
+ client=$2
+ banner=`echo ${client} | ${SSHD} -i -f ${OBJ}/sshd_proxy`
+ r=$?
+ trace "sshd prints ${banner}"
+ if [ $r -ne 255 ]; then
+ fail "sshd prints ${banner} but accepts version ${client}"
+ fi
+}
+
+mismatch SSH-1.5-HALLO
diff --git a/regress/proto-version.sh b/regress/proto-version.sh
new file mode 100644
index 0000000..1f33b1f
--- /dev/null
+++ b/regress/proto-version.sh
@@ -0,0 +1,30 @@
+# $OpenBSD: proto-version.sh,v 1.7 2017/06/07 01:48:15 djm Exp $
+# Placed in the Public Domain.
+
+tid="sshd version with different protocol combinations"
+
+# we just start sshd in inetd mode and check the banner
+check_version ()
+{
+ expect=$1
+ banner=`printf '' | ${SSHD} -i -f ${OBJ}/sshd_proxy`
+ case ${banner} in
+ SSH-1.99-*)
+ proto=199
+ ;;
+ SSH-2.0-*)
+ proto=20
+ ;;
+ SSH-1.5-*)
+ proto=15
+ ;;
+ *)
+ proto=0
+ ;;
+ esac
+ if [ ${expect} -ne ${proto} ]; then
+ fail "wrong protocol version ${banner}"
+ fi
+}
+
+check_version 20
diff --git a/regress/proxy-connect.sh b/regress/proxy-connect.sh
new file mode 100644
index 0000000..8847fe0
--- /dev/null
+++ b/regress/proxy-connect.sh
@@ -0,0 +1,27 @@
+# $OpenBSD: proxy-connect.sh,v 1.12 2020/01/23 11:19:12 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="proxy connect"
+
+if [ "`${SSH} -Q compression`" = "none" ]; then
+ comp="no"
+else
+ comp="no yes"
+fi
+
+for c in $comp; do
+ verbose "plain username comp=$c"
+ opts="-oCompression=$c -F $OBJ/ssh_proxy"
+ SSH_CONNECTION=`${SSH} $opts 999.999.999.999 'echo $SSH_CONNECTION'`
+ if [ $? -ne 0 ]; then
+ fail "ssh proxyconnect comp=$c failed"
+ fi
+ if [ "$SSH_CONNECTION" != "UNKNOWN 65535 UNKNOWN 65535" ]; then
+ fail "bad SSH_CONNECTION comp=$c: " \
+ "$SSH_CONNECTION"
+ fi
+done
+
+verbose "username with style"
+${SSH} -F $OBJ/ssh_proxy ${USER}:style@999.999.999.999 true || \
+ fail "ssh proxyconnect failed"
diff --git a/regress/putty-ciphers.sh b/regress/putty-ciphers.sh
new file mode 100644
index 0000000..5b8e25a
--- /dev/null
+++ b/regress/putty-ciphers.sh
@@ -0,0 +1,32 @@
+# $OpenBSD: putty-ciphers.sh,v 1.11 2021/09/01 03:16:06 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="putty ciphers"
+
+if test "x$REGRESS_INTEROP_PUTTY" != "xyes" ; then
+ skip "putty interop tests not enabled"
+fi
+
+# Re-enable ssh-rsa on older PuTTY versions.
+oldver="`${PLINK} --version | awk '/plink: Release/{if ($3<0.76)print "yes"}'`"
+if [ "x$oldver" = "xyes" ]; then
+ echo "HostKeyAlgorithms +ssh-rsa" >> ${OBJ}/sshd_proxy
+ echo "PubkeyAcceptedKeyTypes +ssh-rsa" >> ${OBJ}/sshd_proxy
+fi
+
+for c in aes 3des aes128-ctr aes192-ctr aes256-ctr chacha20 ; do
+ verbose "$tid: cipher $c"
+ cp ${OBJ}/.putty/sessions/localhost_proxy \
+ ${OBJ}/.putty/sessions/cipher_$c
+ echo "Cipher=$c" >> ${OBJ}/.putty/sessions/cipher_$c
+
+ rm -f ${COPY}
+ env HOME=$PWD ${PLINK} -load cipher_$c -batch -i ${OBJ}/putty.rsa2 \
+ cat ${DATA} > ${COPY}
+ if [ $? -ne 0 ]; then
+ fail "ssh cat $DATA failed"
+ fi
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+done
+rm -f ${COPY}
+
diff --git a/regress/putty-kex.sh b/regress/putty-kex.sh
new file mode 100644
index 0000000..c75802a
--- /dev/null
+++ b/regress/putty-kex.sh
@@ -0,0 +1,28 @@
+# $OpenBSD: putty-kex.sh,v 1.9 2021/09/01 03:16:06 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="putty KEX"
+
+if test "x$REGRESS_INTEROP_PUTTY" != "xyes" ; then
+ skip "putty interop tests not enabled"
+fi
+
+# Re-enable ssh-rsa on older PuTTY versions.
+oldver="`${PLINK} --version | awk '/plink: Release/{if ($3<0.76)print "yes"}'`"
+if [ "x$oldver" = "xyes" ]; then
+ echo "HostKeyAlgorithms +ssh-rsa" >> ${OBJ}/sshd_proxy
+ echo "PubkeyAcceptedKeyTypes +ssh-rsa" >> ${OBJ}/sshd_proxy
+fi
+
+for k in dh-gex-sha1 dh-group1-sha1 dh-group14-sha1 ecdh ; do
+ verbose "$tid: kex $k"
+ cp ${OBJ}/.putty/sessions/localhost_proxy \
+ ${OBJ}/.putty/sessions/kex_$k
+ echo "KEX=$k" >> ${OBJ}/.putty/sessions/kex_$k
+
+ env HOME=$PWD ${PLINK} -load kex_$k -batch -i ${OBJ}/putty.rsa2 true
+ if [ $? -ne 0 ]; then
+ fail "KEX $k failed"
+ fi
+done
+
diff --git a/regress/putty-transfer.sh b/regress/putty-transfer.sh
new file mode 100644
index 0000000..a6864f9
--- /dev/null
+++ b/regress/putty-transfer.sh
@@ -0,0 +1,50 @@
+# $OpenBSD: putty-transfer.sh,v 1.11 2021/09/01 03:16:06 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="putty transfer data"
+
+if test "x$REGRESS_INTEROP_PUTTY" != "xyes" ; then
+ skip "putty interop tests not enabled"
+fi
+
+# Re-enable ssh-rsa on older PuTTY versions.
+oldver="`${PLINK} --version | awk '/plink: Release/{if ($3<0.76)print "yes"}'`"
+if [ "x$oldver" = "xyes" ]; then
+ echo "HostKeyAlgorithms +ssh-rsa" >> ${OBJ}/sshd_proxy
+ echo "PubkeyAcceptedKeyTypes +ssh-rsa" >> ${OBJ}/sshd_proxy
+fi
+
+if [ "`${SSH} -Q compression`" = "none" ]; then
+ comp="0"
+else
+ comp="0 1"
+fi
+
+for c in $comp; do
+ verbose "$tid: compression $c"
+ rm -f ${COPY}
+ cp ${OBJ}/.putty/sessions/localhost_proxy \
+ ${OBJ}/.putty/sessions/compression_$c
+ echo "Compression=$c" >> ${OBJ}/.putty/sessions/kex_$k
+ env HOME=$PWD ${PLINK} -load compression_$c -batch \
+ -i ${OBJ}/putty.rsa2 cat ${DATA} > ${COPY}
+ if [ $? -ne 0 ]; then
+ fail "ssh cat $DATA failed"
+ fi
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+
+ for s in 10 100 1k 32k 64k 128k 256k; do
+ trace "compression $c dd-size ${s}"
+ rm -f ${COPY}
+ dd if=$DATA obs=${s} 2> /dev/null | \
+ env HOME=$PWD ${PLINK} -load compression_$c \
+ -batch -i ${OBJ}/putty.rsa2 \
+ "cat > ${COPY}"
+ if [ $? -ne 0 ]; then
+ fail "ssh cat $DATA failed"
+ fi
+ cmp $DATA ${COPY} || fail "corrupted copy"
+ done
+done
+rm -f ${COPY}
+
diff --git a/regress/reconfigure.sh b/regress/reconfigure.sh
new file mode 100644
index 0000000..d5b4e98
--- /dev/null
+++ b/regress/reconfigure.sh
@@ -0,0 +1,65 @@
+# $OpenBSD: reconfigure.sh,v 1.9 2021/06/10 09:46:28 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="simple connect after reconfigure"
+
+# we need the full path to sshd for -HUP
+if test "x$USE_VALGRIND" = "x" ; then
+ case $SSHD in
+ /*)
+ # full path is OK
+ ;;
+ *)
+ # otherwise make fully qualified
+ SSHD=$OBJ/$SSHD
+ esac
+fi
+
+start_sshd
+
+trace "connect before restart"
+${SSH} -F $OBJ/ssh_config somehost true
+if [ $? -ne 0 ]; then
+ fail "ssh connect with failed before reconfigure"
+fi
+
+PID=`$SUDO cat $PIDFILE`
+rm -f $PIDFILE
+$SUDO kill -HUP $PID
+
+trace "wait for sshd to restart"
+i=0;
+while [ ! -f $PIDFILE -a $i -lt 10 ]; do
+ i=`expr $i + 1`
+ sleep $i
+done
+
+test -f $PIDFILE || fatal "sshd did not restart"
+
+trace "connect after restart"
+${SSH} -F $OBJ/ssh_config somehost true
+if [ $? -ne 0 ]; then
+ fail "ssh connect with failed after reconfigure"
+fi
+
+trace "reconfigure with active clients"
+${SSH} -F $OBJ/ssh_config somehost sleep 10 # authenticated client
+${NC} -d 127.0.0.1 $PORT >/dev/null & # unauthenticated client
+PID=`$SUDO cat $PIDFILE`
+rm -f $PIDFILE
+$SUDO kill -HUP $PID
+
+trace "wait for sshd to restart"
+i=0;
+while [ ! -f $PIDFILE -a $i -lt 10 ]; do
+ i=`expr $i + 1`
+ sleep $i
+done
+
+test -f $PIDFILE || fatal "sshd did not restart"
+
+trace "connect after restart with active clients"
+${SSH} -F $OBJ/ssh_config somehost true
+if [ $? -ne 0 ]; then
+ fail "ssh connect with failed after reconfigure"
+fi
diff --git a/regress/reexec.sh b/regress/reexec.sh
new file mode 100644
index 0000000..3f88d41
--- /dev/null
+++ b/regress/reexec.sh
@@ -0,0 +1,57 @@
+# $OpenBSD: reexec.sh,v 1.13 2023/01/19 07:53:45 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="reexec tests"
+
+SSHD_ORIG=$SSHD
+SSHD_COPY=$OBJ/sshd
+
+# Start a sshd and then delete it
+start_sshd_copy ()
+{
+ # NB. prefer ln to cp here. On some OSX 19.4 configurations,
+ # djm has seen failure after fork() when the executable image
+ # has been removed from the filesystem.
+ ln $SSHD_ORIG $SSHD_COPY || cp $SSHD_ORIG $SSHD_COPY
+ SSHD=$SSHD_COPY
+ start_sshd
+ SSHD=$SSHD_ORIG
+}
+
+# Do basic copy tests
+copy_tests ()
+{
+ rm -f ${COPY}
+ ${SSH} -nq -F $OBJ/ssh_config somehost \
+ cat ${DATA} > ${COPY}
+ if [ $? -ne 0 ]; then
+ fail "ssh cat $DATA failed"
+ fi
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+ rm -f ${COPY}
+}
+
+verbose "test config passing"
+
+cp $OBJ/sshd_config $OBJ/sshd_config.orig
+start_sshd
+echo "InvalidXXX=no" >> $OBJ/sshd_config
+
+copy_tests
+
+stop_sshd
+
+cp $OBJ/sshd_config.orig $OBJ/sshd_config
+
+# cygwin can't fork a deleted binary
+if [ "$os" != "cygwin" ]; then
+
+verbose "test reexec fallback"
+
+start_sshd_copy
+$SUDO rm -f $SSHD_COPY
+
+copy_tests
+
+stop_sshd
+fi
diff --git a/regress/rekey.sh b/regress/rekey.sh
new file mode 100644
index 0000000..61723cd
--- /dev/null
+++ b/regress/rekey.sh
@@ -0,0 +1,172 @@
+# $OpenBSD: rekey.sh,v 1.19 2021/07/19 05:08:54 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="rekey"
+
+LOG=${TEST_SSH_LOGFILE}
+
+rm -f ${LOG}
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+
+# Test rekeying based on data volume only.
+# Arguments will be passed to ssh.
+ssh_data_rekeying()
+{
+ _kexopt=$1 ; shift
+ _opts="$@"
+ if ! test -z "$_kexopts" ; then
+ cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+ echo "$_kexopt" >> $OBJ/sshd_proxy
+ _opts="$_opts -o$_kexopt"
+ fi
+ rm -f ${COPY} ${LOG}
+ _opts="$_opts -oCompression=no"
+ ${SSH} <${DATA} $_opts -v -F $OBJ/ssh_proxy somehost "cat > ${COPY}"
+ if [ $? -ne 0 ]; then
+ fail "ssh failed ($@)"
+ fi
+ cmp ${DATA} ${COPY} || fail "corrupted copy ($@)"
+ n=`grep 'NEWKEYS sent' ${LOG} | wc -l`
+ n=`expr $n - 1`
+ trace "$n rekeying(s)"
+ if [ $n -lt 1 ]; then
+ fail "no rekeying occurred ($@)"
+ fi
+}
+
+increase_datafile_size 300
+
+opts=""
+for i in `${SSH} -Q kex`; do
+ opts="$opts KexAlgorithms=$i"
+done
+for i in `${SSH} -Q cipher`; do
+ opts="$opts Ciphers=$i"
+done
+for i in `${SSH} -Q mac`; do
+ opts="$opts MACs=$i"
+done
+
+for opt in $opts; do
+ verbose "client rekey $opt"
+ ssh_data_rekeying "$opt" -oRekeyLimit=256k
+done
+
+# AEAD ciphers are magical so test with all KexAlgorithms
+if ${SSH} -Q cipher-auth | grep '^.*$' >/dev/null 2>&1 ; then
+ for c in `${SSH} -Q cipher-auth`; do
+ for kex in `${SSH} -Q kex`; do
+ verbose "client rekey $c $kex"
+ ssh_data_rekeying "KexAlgorithms=$kex" -oRekeyLimit=256k -oCiphers=$c
+ done
+ done
+fi
+
+for s in 16 1k 128k 256k; do
+ verbose "client rekeylimit ${s}"
+ ssh_data_rekeying "" -oCompression=no -oRekeyLimit=$s
+done
+
+for s in 5 10; do
+ verbose "client rekeylimit default ${s}"
+ rm -f ${COPY} ${LOG}
+ ${SSH} < ${DATA} -oCompression=no -oRekeyLimit="default $s" -F \
+ $OBJ/ssh_proxy somehost "cat >${COPY};sleep $s;sleep 10"
+ if [ $? -ne 0 ]; then
+ fail "ssh failed"
+ fi
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+ n=`grep 'NEWKEYS sent' ${LOG} | wc -l`
+ n=`expr $n - 1`
+ trace "$n rekeying(s)"
+ if [ $n -lt 1 ]; then
+ fail "no rekeying occurred"
+ fi
+done
+
+for s in 5 10; do
+ verbose "client rekeylimit default ${s} no data"
+ rm -f ${COPY} ${LOG}
+ ${SSH} -oCompression=no -oRekeyLimit="default $s" -F \
+ $OBJ/ssh_proxy somehost "sleep $s;sleep 10"
+ if [ $? -ne 0 ]; then
+ fail "ssh failed"
+ fi
+ n=`grep 'NEWKEYS sent' ${LOG} | wc -l`
+ n=`expr $n - 1`
+ trace "$n rekeying(s)"
+ if [ $n -lt 1 ]; then
+ fail "no rekeying occurred"
+ fi
+done
+
+for s in 16 1k 128k 256k; do
+ verbose "server rekeylimit ${s}"
+ cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+ echo "rekeylimit ${s}" >>$OBJ/sshd_proxy
+ rm -f ${COPY} ${LOG}
+ ${SSH} -oCompression=no -F $OBJ/ssh_proxy somehost "cat ${DATA}" \
+ > ${COPY}
+ if [ $? -ne 0 ]; then
+ fail "ssh failed"
+ fi
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+ n=`grep 'NEWKEYS sent' ${LOG} | wc -l`
+ n=`expr $n - 1`
+ trace "$n rekeying(s)"
+ if [ $n -lt 1 ]; then
+ fail "no rekeying occurred"
+ fi
+done
+
+for s in 5 10; do
+ verbose "server rekeylimit default ${s} no data"
+ cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+ echo "rekeylimit default ${s}" >>$OBJ/sshd_proxy
+ rm -f ${COPY} ${LOG}
+ ${SSH} -oCompression=no -F $OBJ/ssh_proxy somehost "sleep $s;sleep 10"
+ if [ $? -ne 0 ]; then
+ fail "ssh failed"
+ fi
+ n=`grep 'NEWKEYS sent' ${LOG} | wc -l`
+ n=`expr $n - 1`
+ trace "$n rekeying(s)"
+ if [ $n -lt 1 ]; then
+ fail "no rekeying occurred"
+ fi
+done
+
+verbose "rekeylimit parsing"
+for size in 16 1k 1K 1m 1M 1g 1G 4G 8G; do
+ for time in 1 1m 1M 1h 1H 1d 1D 1w 1W; do
+ case $size in
+ 16) bytes=16 ;;
+ 1k|1K) bytes=1024 ;;
+ 1m|1M) bytes=1048576 ;;
+ 1g|1G) bytes=1073741824 ;;
+ 4g|4G) bytes=4294967296 ;;
+ 8g|8G) bytes=8589934592 ;;
+ esac
+ case $time in
+ 1) seconds=1 ;;
+ 1m|1M) seconds=60 ;;
+ 1h|1H) seconds=3600 ;;
+ 1d|1D) seconds=86400 ;;
+ 1w|1W) seconds=604800 ;;
+ esac
+
+ b=`$SUDO ${SSHD} -T -o "rekeylimit $size $time" -f $OBJ/sshd_proxy | \
+ awk '/rekeylimit/{print $2}'`
+ s=`$SUDO ${SSHD} -T -o "rekeylimit $size $time" -f $OBJ/sshd_proxy | \
+ awk '/rekeylimit/{print $3}'`
+
+ if [ "$bytes" != "$b" ]; then
+ fatal "rekeylimit size: expected $bytes bytes got $b"
+ fi
+ if [ "$seconds" != "$s" ]; then
+ fatal "rekeylimit time: expected $time seconds got $s"
+ fi
+ done
+done
+
+rm -f ${COPY} ${DATA}
diff --git a/regress/rsa_openssh.prv b/regress/rsa_openssh.prv
new file mode 100644
index 0000000..2675555
--- /dev/null
+++ b/regress/rsa_openssh.prv
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICWgIBAAKBgQDsilwKcaKN6wSMNd1WgQ9+HRqQEkD0kCTVttrazGu0OhBU3Uko
++dFD1Ip0CxdXmN25JQWxOYF7h/Ocu8P3jzv3RTX87xKR0YzlXTLX+SLtF/ySebS3
+xWPrlfRUDhh03hR5V+8xxvvy9widPYKw/oItwGSueOsEq1LTczCDv2dAjQIDAQAB
+An8nH5VzvHkMbSqJ6eOYDsVwomRvYbH5IEaYl1x6VATITNvAu9kUdQ4NsSpuMc+7
+Jj9gKZvmO1y2YCKc0P/iO+i/eV0L+yQh1Rw18jQZll+12T+LZrKRav03YNvMx0gN
+wqWY48Kt6hv2/N/ebQzKRe79+D0t2cTh92hT7xENFLIBAkEBGnoGKFjAUkJCwO1V
+mzpUqMHpRZVOrqP9hUmPjzNJ5oBPFGe4+h1hoSRFOAzaNuZt8ssbqaLCkzB8bfzj
+qhZqAQJBANZekuUpp8iBLeLSagw5FkcPwPzq6zfExbhvsZXb8Bo/4SflNs4JHXwI
+7SD9Z8aJLvM4uQ/5M70lblDMQ40i3o0CQQDIJvBYBFL5tlOgakq/O7yi+wt0L5BZ
+9H79w5rCSAA0IHRoK/qI1urHiHC3f3vbbLk5UStfrqEaND/mm0shyNIBAkBLsYdC
+/ctt5Bc0wUGK4Vl5bBmj9LtrrMJ4FpBpLwj/69BwCuKoK9XKZ0h73p6XHveCEGRg
+PIlFX4MtaoLrwgU9AkBV2k4dgIws+X8YX65EsyyFjnlDqX4x0nSOjQB1msIKfHBr
+dh5XLDBTTCxnKhMJ0Yx/opgOvf09XHBFwaQntR5i
+-----END RSA PRIVATE KEY-----
diff --git a/regress/rsa_openssh.pub b/regress/rsa_openssh.pub
new file mode 100644
index 0000000..b504730
--- /dev/null
+++ b/regress/rsa_openssh.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDsilwKcaKN6wSMNd1WgQ9+HRqQEkD0kCTVttrazGu0OhBU3Uko+dFD1Ip0CxdXmN25JQWxOYF7h/Ocu8P3jzv3RTX87xKR0YzlXTLX+SLtF/ySebS3xWPrlfRUDhh03hR5V+8xxvvy9widPYKw/oItwGSueOsEq1LTczCDv2dAjQ==
diff --git a/regress/rsa_ssh2.prv b/regress/rsa_ssh2.prv
new file mode 100644
index 0000000..1ece3d7
--- /dev/null
+++ b/regress/rsa_ssh2.prv
@@ -0,0 +1,16 @@
+---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----
+Subject: ssh-keygen test
+Comment: "1024-bit rsa, Sat Jun 23 2001 12:21:26 -0400"
+P2/56wAAAi4AAAA3aWYtbW9kbntzaWdue3JzYS1wa2NzMS1zaGExfSxlbmNyeXB0e3JzYS
+1wa2NzMXYyLW9hZXB9fQAAAARub25lAAAB3wAAAdsAAAARAQABAAAD9icflXO8eQxtKonp
+45gOxXCiZG9hsfkgRpiXXHpUBMhM28C72RR1Dg2xKm4xz7smP2Apm+Y7XLZgIpzQ/+I76L
+95XQv7JCHVHDXyNBmWX7XZP4tmspFq/Tdg28zHSA3CpZjjwq3qG/b8395tDMpF7v34PS3Z
+xOH3aFPvEQ0UsgEAAAQA7IpcCnGijesEjDXdVoEPfh0akBJA9JAk1bba2sxrtDoQVN1JKP
+nRQ9SKdAsXV5jduSUFsTmBe4fznLvD948790U1/O8SkdGM5V0y1/ki7Rf8knm0t8Vj65X0
+VA4YdN4UeVfvMcb78vcInT2CsP6CLcBkrnjrBKtS03Mwg79nQI0AAAH/VdpOHYCMLPl/GF
++uRLMshY55Q6l+MdJ0jo0AdZrCCnxwa3YeVywwU0wsZyoTCdGMf6KYDr39PVxwRcGkJ7Ue
+YgAAAgDWXpLlKafIgS3i0moMORZHD8D86us3xMW4b7GV2/AaP+En5TbOCR18CO0g/WfGiS
+7zOLkP+TO9JW5QzEONIt6NAAACAQEaegYoWMBSQkLA7VWbOlSowelFlU6uo/2FSY+PM0nm
+gE8UZ7j6HWGhJEU4DNo25m3yyxuposKTMHxt/OOqFmoB
+---- END SSH2 ENCRYPTED PRIVATE KEY ----
+---
diff --git a/regress/scp-ssh-wrapper.sh b/regress/scp-ssh-wrapper.sh
new file mode 100644
index 0000000..7fb21f4
--- /dev/null
+++ b/regress/scp-ssh-wrapper.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+# $OpenBSD: scp-ssh-wrapper.sh,v 1.4 2019/07/19 03:45:44 djm Exp $
+# Placed in the Public Domain.
+
+printname () {
+ NAME=$1
+ save_IFS=$IFS
+ IFS=/
+ set -- `echo "$NAME"`
+ IFS="$save_IFS"
+ while [ $# -ge 1 ] ; do
+ if [ "x$1" != "x" ]; then
+ echo "D0755 0 $1"
+ fi
+ shift;
+ done
+}
+
+# Discard all but last argument. We use arg later.
+while test "x$1" != "x"; do
+ arg="$1"
+ shift
+done
+
+BAD="../../../../../../../../../../../../../${DIR}/dotpathdir"
+
+case "$SCPTESTMODE" in
+badserver_0)
+ echo "D0755 0 /${DIR}/rootpathdir"
+ echo "C755 2 rootpathfile"
+ echo "X"
+ ;;
+badserver_1)
+ echo "D0755 0 $BAD"
+ echo "C755 2 file"
+ echo "X"
+ ;;
+badserver_2)
+ echo "D0755 0 $BAD"
+ echo "C755 2 file"
+ echo "X"
+ ;;
+badserver_3)
+ printname $BAD
+ echo "C755 2 file"
+ echo "X"
+ ;;
+badserver_4)
+ printname $BAD
+ echo "D0755 0 .."
+ echo "C755 2 file"
+ echo "X"
+ ;;
+badserver_5)
+ echo "D0555 0 "
+ echo "X"
+ ;;
+badserver_6)
+ echo "D0555 0 ."
+ echo "X"
+ ;;
+badserver_7)
+ echo "C0755 2 extrafile"
+ echo "X"
+ ;;
+*)
+ set -- $arg
+ shift
+ exec $SCP "$@"
+ ;;
+esac
diff --git a/regress/scp-uri.sh b/regress/scp-uri.sh
new file mode 100644
index 0000000..eacbd45
--- /dev/null
+++ b/regress/scp-uri.sh
@@ -0,0 +1,79 @@
+# $OpenBSD: scp-uri.sh,v 1.5 2023/01/13 04:47:34 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="scp-uri"
+
+#set -x
+
+COPY2=${OBJ}/copy2
+DIR=${COPY}.dd
+DIR2=${COPY}.dd2
+
+maybe_add_scp_path_to_sshd
+
+SRC=`dirname ${SCRIPT}`
+cp ${SRC}/scp-ssh-wrapper.sh ${OBJ}/scp-ssh-wrapper.scp
+chmod 755 ${OBJ}/scp-ssh-wrapper.scp
+export SCP # used in scp-ssh-wrapper.scp
+
+scpclean() {
+ rm -rf ${COPY} ${COPY2} ${DIR} ${DIR2}
+ mkdir ${DIR} ${DIR2}
+}
+
+# Remove Port and User from ssh_config, we want to rely on the URI
+cp $OBJ/ssh_config $OBJ/ssh_config.orig
+egrep -v '^ +(Port|User) +.*$' $OBJ/ssh_config.orig > $OBJ/ssh_config
+
+for mode in scp sftp ; do
+ tag="$tid: $mode mode"
+ if test $mode = scp ; then
+ scpopts="-O -q -S ${OBJ}/scp-ssh-wrapper.scp"
+ else
+ scpopts="-s -D ${SFTPSERVER}"
+ fi
+ verbose "$tag: simple copy local file to remote file"
+ scpclean
+ $SCP $scpopts ${DATA} "scp://${USER}@somehost:${PORT}/${COPY}" || fail "copy failed"
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+
+ verbose "$tag: simple copy remote file to local file"
+ scpclean
+ $SCP $scpopts "scp://${USER}@somehost:${PORT}/${DATA}" ${COPY} || fail "copy failed"
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+
+ verbose "$tag: simple copy local file to remote dir"
+ scpclean
+ cp ${DATA} ${COPY}
+ $SCP $scpopts ${COPY} "scp://${USER}@somehost:${PORT}/${DIR}" || fail "copy failed"
+ cmp ${COPY} ${DIR}/copy || fail "corrupted copy"
+
+ verbose "$tag: simple copy remote file to local dir"
+ scpclean
+ cp ${DATA} ${COPY}
+ $SCP $scpopts "scp://${USER}@somehost:${PORT}/${COPY}" ${DIR} || fail "copy failed"
+ cmp ${COPY} ${DIR}/copy || fail "corrupted copy"
+
+ verbose "$tag: recursive local dir to remote dir"
+ scpclean
+ rm -rf ${DIR2}
+ cp ${DATA} ${DIR}/copy
+ $SCP $scpopts -r ${DIR} "scp://${USER}@somehost:${PORT}/${DIR2}" || fail "copy failed"
+ for i in $(cd ${DIR} && echo *); do
+ cmp ${DIR}/$i ${DIR2}/$i || fail "corrupted copy"
+ done
+
+ verbose "$tag: recursive remote dir to local dir"
+ scpclean
+ rm -rf ${DIR2}
+ cp ${DATA} ${DIR}/copy
+ $SCP $scpopts -r "scp://${USER}@somehost:${PORT}/${DIR}" ${DIR2} || fail "copy failed"
+ for i in $(cd ${DIR} && echo *); do
+ cmp ${DIR}/$i ${DIR2}/$i || fail "corrupted copy"
+ done
+
+ # TODO: scp -3
+done
+
+scpclean
+rm -f ${OBJ}/scp-ssh-wrapper.exe
diff --git a/regress/scp.sh b/regress/scp.sh
new file mode 100644
index 0000000..76c2b2a
--- /dev/null
+++ b/regress/scp.sh
@@ -0,0 +1,195 @@
+# $OpenBSD: scp.sh,v 1.18 2023/01/13 04:47:34 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="scp"
+
+#set -x
+
+COPY2=${OBJ}/copy2
+DIR=${COPY}.dd
+DIR2=${COPY}.dd2
+COPY3=${OBJ}/copy.glob[123]
+DIR3=${COPY}.dd.glob[456]
+DIFFOPT="-rN"
+
+# Figure out if diff does not understand "-N"
+if ! diff -N ${SRC}/scp.sh ${SRC}/scp.sh 2>/dev/null; then
+ DIFFOPT="-r"
+fi
+
+maybe_add_scp_path_to_sshd
+
+SRC=`dirname ${SCRIPT}`
+cp ${SRC}/scp-ssh-wrapper.sh ${OBJ}/scp-ssh-wrapper.scp
+chmod 755 ${OBJ}/scp-ssh-wrapper.scp
+export SCP # used in scp-ssh-wrapper.scp
+
+scpclean() {
+ rm -rf ${COPY} ${COPY2} ${DIR} ${DIR2} ${COPY3} ${DIR3}
+ mkdir ${DIR} ${DIR2} ${DIR3}
+ chmod 755 ${DIR} ${DIR2} ${DIR3}
+}
+
+for mode in scp sftp ; do
+ tag="$tid: $mode mode"
+ if test $mode = scp ; then
+ scpopts="-O -q -S ${OBJ}/scp-ssh-wrapper.scp"
+ else
+ scpopts="-s -D ${SFTPSERVER}"
+ fi
+ verbose "$tag: simple copy local file to local file"
+ scpclean
+ $SCP $scpopts ${DATA} ${COPY} || fail "copy failed"
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+
+ verbose "$tag: simple copy local file to remote file"
+ scpclean
+ $SCP $scpopts ${DATA} somehost:${COPY} || fail "copy failed"
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+
+ verbose "$tag: simple copy remote file to local file"
+ scpclean
+ $SCP $scpopts somehost:${DATA} ${COPY} || fail "copy failed"
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+
+ verbose "$tag: copy local file to remote file in place"
+ scpclean
+ cp ${DATA} ${COPY}
+ $SCP $scpopts ${COPY} somehost:${COPY} || fail "copy failed"
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+
+ verbose "$tag: copy remote file to local file in place"
+ scpclean
+ cp ${DATA} ${COPY}
+ $SCP $scpopts somehost:${COPY} ${COPY} || fail "copy failed"
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+
+ verbose "$tag: copy local file to remote file clobber"
+ scpclean
+ cat ${DATA} ${DATA} > ${COPY}
+ $SCP $scpopts ${DATA} somehost:${COPY} || fail "copy failed"
+ ls -l $DATA $COPY
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+
+ verbose "$tag: copy remote file to local file clobber"
+ scpclean
+ cat ${DATA} ${DATA} > ${COPY}
+ $SCP $scpopts somehost:${DATA} ${COPY} || fail "copy failed"
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+
+ verbose "$tag: simple copy local file to remote dir"
+ scpclean
+ cp ${DATA} ${COPY}
+ $SCP $scpopts ${COPY} somehost:${DIR} || fail "copy failed"
+ cmp ${COPY} ${DIR}/copy || fail "corrupted copy"
+
+ verbose "$tag: simple copy local file to local dir"
+ scpclean
+ cp ${DATA} ${COPY}
+ $SCP $scpopts ${COPY} ${DIR} || fail "copy failed"
+ cmp ${COPY} ${DIR}/copy || fail "corrupted copy"
+
+ verbose "$tag: simple copy remote file to local dir"
+ scpclean
+ cp ${DATA} ${COPY}
+ $SCP $scpopts somehost:${COPY} ${DIR} || fail "copy failed"
+ cmp ${COPY} ${DIR}/copy || fail "corrupted copy"
+
+ verbose "$tag: recursive local dir to remote dir"
+ scpclean
+ rm -rf ${DIR2}
+ cp ${DATA} ${DIR}/copy
+ $SCP $scpopts -r ${DIR} somehost:${DIR2} || fail "copy failed"
+ diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy"
+
+ verbose "$tag: recursive local dir to local dir"
+ scpclean
+ rm -rf ${DIR2}
+ cp ${DATA} ${DIR}/copy
+ $SCP $scpopts -r ${DIR} ${DIR2} || fail "copy failed"
+ diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy"
+
+ verbose "$tag: recursive remote dir to local dir"
+ scpclean
+ rm -rf ${DIR2}
+ cp ${DATA} ${DIR}/copy
+ $SCP $scpopts -r somehost:${DIR} ${DIR2} || fail "copy failed"
+ diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy"
+
+ verbose "$tag: unmatched glob file local->remote"
+ scpclean
+ $SCP $scpopts ${DATA} somehost:${COPY3} || fail "copy failed"
+ cmp ${DATA} ${COPY3} || fail "corrupted copy"
+
+ verbose "$tag: unmatched glob file remote->local"
+ # NB. no clean
+ $SCP $scpopts somehost:${COPY3} ${COPY2} || fail "copy failed"
+ cmp ${DATA} ${COPY2} || fail "corrupted copy"
+
+ verbose "$tag: unmatched glob dir recursive local->remote"
+ scpclean
+ rm -rf ${DIR3}
+ cp ${DATA} ${DIR}/copy
+ cp ${DATA} ${DIR}/copy.glob[1234]
+ $SCP $scpopts -r ${DIR} somehost:${DIR3} || fail "copy failed"
+ diff ${DIFFOPT} ${DIR} ${DIR3} || fail "corrupted copy"
+
+ verbose "$tag: unmatched glob dir recursive remote->local"
+ # NB. no clean
+ rm -rf ${DIR2}
+ $SCP $scpopts -r somehost:${DIR3} ${DIR2} || fail "copy failed"
+ diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy"
+
+ verbose "$tag: shell metacharacters"
+ scpclean
+ (cd ${DIR} && \
+ touch '`touch metachartest`' && \
+ $SCP $scpopts *metachar* ${DIR2} 2>/dev/null; \
+ [ ! -f metachartest ] ) || fail "shell metacharacters"
+
+ if [ ! -z "$SUDO" ]; then
+ verbose "$tag: skipped file after scp -p with failed chown+utimes"
+ scpclean
+ cp -p ${DATA} ${DIR}/copy
+ cp -p ${DATA} ${DIR}/copy2
+ cp ${DATA} ${DIR2}/copy
+ chmod 660 ${DIR2}/copy
+ $SUDO chown root ${DIR2}/copy
+ $SCP -p $scpopts somehost:${DIR}/\* ${DIR2} >/dev/null 2>&1
+ $SUDO diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy"
+ $SUDO rm ${DIR2}/copy
+ fi
+
+ for i in 0 1 2 3 4 5 6 7; do
+ verbose "$tag: disallow bad server #$i"
+ SCPTESTMODE=badserver_$i
+ export DIR SCPTESTMODE
+ scpclean
+ $SCP $scpopts somehost:${DATA} ${DIR} >/dev/null 2>/dev/null
+ [ -d {$DIR}/rootpathdir ] && fail "allows dir relative to root dir"
+ [ -d ${DIR}/dotpathdir ] && fail "allows dir creation in non-recursive mode"
+
+ scpclean
+ $SCP -r $scpopts somehost:${DATA} ${DIR2} >/dev/null 2>/dev/null
+ [ -d ${DIR}/dotpathdir ] && fail "allows dir creation outside of subdir"
+
+ scpclean
+ $SCP -pr $scpopts somehost:${DATA} ${DIR2} >/dev/null 2>/dev/null
+ [ ! -w ${DIR2} ] && fail "allows target root attribute change"
+
+ scpclean
+ $SCP $scpopts somehost:${DATA} ${DIR2} >/dev/null 2>/dev/null
+ [ -e ${DIR2}/extrafile ] && fail "allows unauth object creation"
+ rm -f ${DIR2}/extrafile
+ done
+
+ verbose "$tag: detect non-directory target"
+ scpclean
+ echo a > ${COPY}
+ echo b > ${COPY2}
+ $SCP $scpopts ${DATA} ${COPY} ${COPY2}
+ cmp ${COPY} ${COPY2} >/dev/null && fail "corrupt target"
+done
+
+scpclean
+rm -f ${OBJ}/scp-ssh-wrapper.scp
diff --git a/regress/scp3.sh b/regress/scp3.sh
new file mode 100644
index 0000000..383121f
--- /dev/null
+++ b/regress/scp3.sh
@@ -0,0 +1,62 @@
+# $OpenBSD: scp3.sh,v 1.4 2023/01/13 04:47:34 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="scp3"
+
+set -x
+
+COPY2=${OBJ}/copy2
+DIR=${COPY}.dd
+DIR2=${COPY}.dd2
+
+maybe_add_scp_path_to_sshd
+
+SRC=`dirname ${SCRIPT}`
+cp ${SRC}/scp-ssh-wrapper.sh ${OBJ}/scp-ssh-wrapper.scp
+chmod 755 ${OBJ}/scp-ssh-wrapper.scp
+export SCP # used in scp-ssh-wrapper.scp
+
+scpclean() {
+ rm -rf ${COPY} ${COPY2} ${DIR} ${DIR2}
+ mkdir ${DIR} ${DIR2}
+ chmod 755 ${DIR} ${DIR2}
+}
+
+for mode in scp sftp ; do
+ scpopts="-F${OBJ}/ssh_proxy -S ${SSH} -q"
+ tag="$tid: $mode mode"
+ if test $mode = scp ; then
+ scpopts="$scpopts -O"
+ else
+ scpopts="-s -D ${SFTPSERVER}"
+ fi
+
+ verbose "$tag: simple copy remote file to remote file"
+ scpclean
+ $SCP $scpopts -3 hostA:${DATA} hostB:${COPY} || fail "copy failed"
+ cmp ${DATA} ${COPY} || fail "corrupted copy"
+
+ verbose "$tag: simple copy remote file to remote dir"
+ scpclean
+ cp ${DATA} ${COPY}
+ $SCP $scpopts -3 hostA:${COPY} hostB:${DIR} || fail "copy failed"
+ cmp ${COPY} ${DIR}/copy || fail "corrupted copy"
+
+ verbose "$tag: recursive remote dir to remote dir"
+ scpclean
+ rm -rf ${DIR2}
+ cp ${DATA} ${DIR}/copy
+ $SCP $scpopts -3r hostA:${DIR} hostB:${DIR2} || fail "copy failed"
+ diff -r ${DIR} ${DIR2} || fail "corrupted copy"
+ diff -r ${DIR2} ${DIR} || fail "corrupted copy"
+
+ verbose "$tag: detect non-directory target"
+ scpclean
+ echo a > ${COPY}
+ echo b > ${COPY2}
+ $SCP $scpopts -3 hostA:${DATA} hostA:${COPY} hostB:${COPY2}
+ cmp ${COPY} ${COPY2} >/dev/null && fail "corrupt target"
+done
+
+scpclean
+rm -f ${OBJ}/scp-ssh-wrapper.exe
diff --git a/regress/servcfginclude.sh b/regress/servcfginclude.sh
new file mode 100644
index 0000000..518a703
--- /dev/null
+++ b/regress/servcfginclude.sh
@@ -0,0 +1,188 @@
+# Placed in the Public Domain.
+
+tid="server config include"
+
+cat > $OBJ/sshd_config.i << _EOF
+HostKey $OBJ/host.ssh-ed25519
+Match host a
+ Banner /aa
+
+Match host b
+ Banner /bb
+ Include $OBJ/sshd_config.i.* # comment
+
+Match host c
+ Include $OBJ/sshd_config.i.* # comment
+ Banner /cc
+
+Match host m
+ Include $OBJ/sshd_config.i.*
+
+Match Host d
+ Banner /dd # comment
+
+Match Host e
+ Banner /ee
+ Include $OBJ/sshd_config.i.*
+
+Match Host f
+ Include $OBJ/sshd_config.i.*
+ Banner /ff
+
+Match Host n
+ Include $OBJ/sshd_config.i.*
+_EOF
+
+cat > $OBJ/sshd_config.i.0 << _EOF
+Match host xxxxxx
+_EOF
+
+cat > $OBJ/sshd_config.i.1 << _EOF
+Match host a
+ Banner /aaa
+
+Match host b
+ Banner /bbb
+
+Match host c
+ Banner /ccc
+
+Match Host d
+ Banner /ddd
+
+Match Host e
+ Banner /eee
+
+Match Host f
+ Banner /fff
+_EOF
+
+cat > $OBJ/sshd_config.i.2 << _EOF
+Match host a
+ Banner /aaaa
+
+Match host b
+ Banner /bbbb
+
+Match host c # comment
+ Banner /cccc
+
+Match Host d
+ Banner /dddd
+
+Match Host e
+ Banner /eeee
+
+Match Host f
+ Banner /ffff
+
+Match all
+ Banner /xxxx
+_EOF
+
+trial() {
+ _host="$1"
+ _exp="$2"
+ _desc="$3"
+ test -z "$_desc" && _desc="test match"
+ trace "$_desc host=$_host expect=$_exp"
+ ${SUDO} ${REAL_SSHD} -f $OBJ/sshd_config.i -T \
+ -C "host=$_host,user=test,addr=127.0.0.1" > $OBJ/sshd_config.out ||
+ fatal "ssh config parse failed: $_desc host=$_host expect=$_exp"
+ _got=`grep -i '^banner ' $OBJ/sshd_config.out | awk '{print $2}'`
+ if test "x$_exp" != "x$_got" ; then
+ fail "$desc_ host $_host include fail: expected $_exp got $_got"
+ fi
+}
+
+trial a /aa
+trial b /bb
+trial c /ccc
+trial d /dd
+trial e /ee
+trial f /fff
+trial m /xxxx
+trial n /xxxx
+trial x none
+
+# Prepare an included config with an error.
+
+cat > $OBJ/sshd_config.i.3 << _EOF
+Banner xxxx
+ Junk
+_EOF
+
+trace "disallow invalid config host=a"
+${SUDO} ${REAL_SSHD} -f $OBJ/sshd_config.i \
+ -C "host=a,user=test,addr=127.0.0.1" 2>/dev/null && \
+ fail "sshd include allowed invalid config"
+
+trace "disallow invalid config host=x"
+${SUDO} ${REAL_SSHD} -f $OBJ/sshd_config.i \
+ -C "host=x,user=test,addr=127.0.0.1" 2>/dev/null && \
+ fail "sshd include allowed invalid config"
+
+rm -f $OBJ/sshd_config.i.*
+
+# Ensure that a missing include is not fatal.
+cat > $OBJ/sshd_config.i << _EOF
+HostKey $OBJ/host.ssh-ed25519
+Include $OBJ/sshd_config.i.*
+Banner /aa
+_EOF
+
+trial a /aa "missing include non-fatal"
+
+# Ensure that Match/Host in an included config does not affect parent.
+cat > $OBJ/sshd_config.i.x << _EOF
+Match host x
+_EOF
+
+trial a /aa "included file does not affect match state"
+
+# Ensure the empty include directive is not accepted
+cat > $OBJ/sshd_config.i.x << _EOF
+Include
+_EOF
+
+trace "disallow invalid with no argument"
+${SUDO} ${REAL_SSHD} -f $OBJ/sshd_config.i.x -T \
+ -C "host=x,user=test,addr=127.0.0.1" 2>/dev/null && \
+ fail "sshd allowed Include with no argument"
+
+# Ensure the Include before any Match block works as expected (bug #3122)
+cat > $OBJ/sshd_config.i << _EOF
+Banner /xx
+HostKey $OBJ/host.ssh-ed25519
+Include $OBJ/sshd_config.i.2
+Match host a
+ Banner /aaaa
+_EOF
+cat > $OBJ/sshd_config.i.2 << _EOF
+Match host a
+ Banner /aa
+_EOF
+
+trace "Include before match blocks"
+trial a /aa "included file before match blocks is properly evaluated"
+
+# Port in included file is correctly interpretted (bug #3169)
+cat > $OBJ/sshd_config.i << _EOF
+Include $OBJ/sshd_config.i.2
+Port 7722
+_EOF
+cat > $OBJ/sshd_config.i.2 << _EOF
+HostKey $OBJ/host.ssh-ed25519
+_EOF
+
+trace "Port after included files"
+${SUDO} ${REAL_SSHD} -f $OBJ/sshd_config.i -T \
+ -C "host=x,user=test,addr=127.0.0.1" > $OBJ/sshd_config.out || \
+ fail "failed to parse Port after included files"
+_port=`grep -i '^port ' $OBJ/sshd_config.out | awk '{print $2}'`
+if test "x7722" != "x$_port" ; then
+ fail "The Port in included file was intertepretted wrongly. Expected 7722, got $_port"
+fi
+
+# cleanup
+rm -f $OBJ/sshd_config.i $OBJ/sshd_config.i.* $OBJ/sshd_config.out
diff --git a/regress/setuid-allowed.c b/regress/setuid-allowed.c
new file mode 100644
index 0000000..d91d9f1
--- /dev/null
+++ b/regress/setuid-allowed.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013 Damien Miller <djm@mindrot.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* $OpenBSD$ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_STATVFS_H
+# include <sys/statvfs.h>
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+static void
+usage(void)
+{
+ fprintf(stderr, "check-setuid [path]\n");
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *path = ".";
+ struct statvfs sb;
+
+ if (argc > 2)
+ usage();
+ else if (argc == 2)
+ path = argv[1];
+
+ if (statvfs(path, &sb) != 0) {
+ /* Don't return an error if the host doesn't support statvfs */
+ if (errno == ENOSYS)
+ return 0;
+ fprintf(stderr, "statvfs for \"%s\" failed: %s\n",
+ path, strerror(errno));
+ }
+ return (sb.f_flag & ST_NOSUID) ? 1 : 0;
+}
+
+
diff --git a/regress/sftp-badcmds.sh b/regress/sftp-badcmds.sh
new file mode 100644
index 0000000..5b016d5
--- /dev/null
+++ b/regress/sftp-badcmds.sh
@@ -0,0 +1,65 @@
+# $OpenBSD: sftp-badcmds.sh,v 1.7 2020/03/13 03:18:45 djm Exp $
+# Placed in the Public Domain.
+
+tid="sftp invalid commands"
+
+DATA2=/bin/sh${EXEEXT}
+NONEXIST=/NONEXIST.$$
+GLOBFILES=`(cd /bin;echo l*)`
+
+rm -rf ${COPY} ${COPY}.1 ${COPY}.2 ${COPY}.dd
+
+rm -f ${COPY}
+verbose "$tid: get nonexistent"
+echo "get $NONEXIST $COPY" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "get nonexistent failed"
+test -f ${COPY} && fail "existing copy after get nonexistent"
+
+rm -f ${COPY}.dd/*
+verbose "$tid: glob get to nonexistent directory"
+echo "get /bin/l* $NONEXIST" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "get nonexistent failed"
+for x in $GLOBFILES; do
+ test -f ${COPY}.dd/$x && fail "existing copy after get nonexistent"
+done
+
+rm -f ${COPY}
+verbose "$tid: put nonexistent"
+echo "put $NONEXIST $COPY" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "put nonexistent failed"
+test -f ${COPY} && fail "existing copy after put nonexistent"
+
+rm -f ${COPY}.dd/*
+verbose "$tid: glob put to nonexistent directory"
+echo "put /bin/l* ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "put nonexistent failed"
+for x in $GLOBFILES; do
+ test -f ${COPY}.dd/$x && fail "existing copy after nonexistent"
+done
+
+rm -f ${COPY}
+verbose "$tid: rename nonexistent"
+echo "rename $NONEXIST ${COPY}.1" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "rename nonexist failed"
+test -f ${COPY}.1 && fail "file exists after rename nonexistent"
+
+rm -rf ${COPY} ${COPY}.dd
+cp $DATA $COPY
+mkdir ${COPY}.dd
+verbose "$tid: rename target exists (directory)"
+echo "rename $COPY ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "rename target exists (directory) failed"
+test -f ${COPY} || fail "oldname missing after rename target exists (directory)"
+test -d ${COPY}.dd || fail "newname missing after rename target exists (directory)"
+cmp $DATA ${COPY} >/dev/null 2>&1 || fail "corrupted oldname after rename target exists (directory)"
+
+rm -f ${COPY}.dd/*
+rm -rf ${COPY}
+cp ${DATA2} ${COPY}
+verbose "$tid: glob put files to local file"
+echo "put /bin/l* $COPY" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1
+cmp ${DATA2} ${COPY} || fail "put succeeded when it should have failed"
+
+rm -rf ${COPY} ${COPY}.1 ${COPY}.2 ${COPY}.dd
+
+
diff --git a/regress/sftp-batch.sh b/regress/sftp-batch.sh
new file mode 100644
index 0000000..4101154
--- /dev/null
+++ b/regress/sftp-batch.sh
@@ -0,0 +1,55 @@
+# $OpenBSD: sftp-batch.sh,v 1.5 2013/05/17 04:29:14 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="sftp batchfile"
+
+BATCH=${OBJ}/sftp.bb
+
+rm -rf ${COPY} ${COPY}.1 ${COPY}.2 ${COPY}.dd ${BATCH}.*
+
+cat << EOF > ${BATCH}.pass.1
+ get $DATA $COPY
+ put ${COPY} ${COPY}.1
+ rm ${COPY}
+ -put ${COPY} ${COPY}.2
+EOF
+
+cat << EOF > ${BATCH}.pass.2
+ # This is a comment
+
+ # That was a blank line
+ ls
+EOF
+
+cat << EOF > ${BATCH}.fail.1
+ get $DATA $COPY
+ put ${COPY} ${COPY}.3
+ rm ${COPY}.*
+ # The next command should fail
+ put ${COPY}.3 ${COPY}.4
+EOF
+
+cat << EOF > ${BATCH}.fail.2
+ # The next command should fail
+ jajajajaja
+EOF
+
+verbose "$tid: good commands"
+${SFTP} -b ${BATCH}.pass.1 -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "good commands failed"
+
+verbose "$tid: bad commands"
+${SFTP} -b ${BATCH}.fail.1 -D ${SFTPSERVER} >/dev/null 2>&1 \
+ && fail "bad commands succeeded"
+
+verbose "$tid: comments and blanks"
+${SFTP} -b ${BATCH}.pass.2 -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "comments & blanks failed"
+
+verbose "$tid: junk command"
+${SFTP} -b ${BATCH}.fail.2 -D ${SFTPSERVER} >/dev/null 2>&1 \
+ && fail "junk command succeeded"
+
+rm -rf ${COPY} ${COPY}.1 ${COPY}.2 ${COPY}.dd ${BATCH}.*
+
+
diff --git a/regress/sftp-chroot.sh b/regress/sftp-chroot.sh
new file mode 100644
index 0000000..a7766fe
--- /dev/null
+++ b/regress/sftp-chroot.sh
@@ -0,0 +1,28 @@
+# $OpenBSD: sftp-chroot.sh,v 1.8 2021/09/01 00:50:27 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="sftp in chroot"
+
+CHROOT=/var/run
+FILENAME=testdata_${USER}.$$
+PRIVDATA=${CHROOT}/${FILENAME}
+trap "${SUDO} rm -f ${PRIVDATA}" 0
+
+if [ -z "$SUDO" -a ! -w /var/run ]; then
+ skip "need SUDO to create file in /var/run, test won't work without"
+fi
+
+if ! $OBJ/check-perm -m chroot "$CHROOT" ; then
+ skip "$CHROOT is unsuitable as ChrootDirectory"
+fi
+
+$SUDO sh -c "echo mekmitastdigoat > $PRIVDATA" || \
+ fatal "create $PRIVDATA failed"
+
+start_sshd -oChrootDirectory=$CHROOT -oForceCommand="internal-sftp -d /"
+
+verbose "test $tid: get"
+${SFTP} -S "$SSH" -F $OBJ/ssh_config host:/${FILENAME} $COPY \
+ >>$TEST_REGRESS_LOGFILE 2>&1 || \
+ fatal "Fetch ${FILENAME} failed"
+cmp $PRIVDATA $COPY || fail "$PRIVDATA $COPY differ"
diff --git a/regress/sftp-cmds.sh b/regress/sftp-cmds.sh
new file mode 100644
index 0000000..85f0e97
--- /dev/null
+++ b/regress/sftp-cmds.sh
@@ -0,0 +1,233 @@
+# $OpenBSD: sftp-cmds.sh,v 1.15 2022/03/31 03:07:33 djm Exp $
+# Placed in the Public Domain.
+
+# XXX - TODO:
+# - chmod / chown / chgrp
+# - -p flag for get & put
+
+tid="sftp commands"
+
+# test that these files are readable!
+for i in `(cd /bin;echo l*)`
+do
+ if [ -r $i ]; then
+ GLOBFILES="$GLOBFILES $i"
+ fi
+done
+
+# Path with embedded quote
+QUOTECOPY=${COPY}".\"blah\""
+QUOTECOPY_ARG=${COPY}'.\"blah\"'
+# File with spaces
+SPACECOPY="${COPY} this has spaces.txt"
+SPACECOPY_ARG="${COPY}\ this\ has\ spaces.txt"
+# File with glob metacharacters
+GLOBMETACOPY="${COPY} [metachar].txt"
+
+rm -rf ${COPY} ${COPY}.1 ${COPY}.2 ${COPY}.dd ${COPY}.dd2
+mkdir ${COPY}.dd
+
+verbose "$tid: lls"
+(echo "lcd ${OBJ}" ; echo "lls") | ${SFTP} -D ${SFTPSERVER} 2>&1 | \
+ grep copy.dd >/dev/null 2>&1 || fail "lls failed"
+
+verbose "$tid: lls w/path"
+echo "lls ${OBJ}" | ${SFTP} -D ${SFTPSERVER} 2>&1 | \
+ grep copy.dd >/dev/null 2>&1 || fail "lls w/path failed"
+
+verbose "$tid: ls"
+echo "ls ${OBJ}" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "ls failed"
+# XXX always successful
+
+verbose "$tid: shell"
+echo "!echo hi there" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "shell failed"
+# XXX always successful
+
+verbose "$tid: pwd"
+echo "pwd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "pwd failed"
+# XXX always successful
+
+verbose "$tid: lpwd"
+echo "lpwd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "lpwd failed"
+# XXX always successful
+
+verbose "$tid: quit"
+echo "quit" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "quit failed"
+# XXX always successful
+
+verbose "$tid: help"
+echo "help" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "help failed"
+# XXX always successful
+
+rm -f ${COPY}
+verbose "$tid: get"
+echo "get $DATA $COPY" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "get failed"
+cmp $DATA ${COPY} || fail "corrupted copy after get"
+
+rm -f ${COPY}
+verbose "$tid: get quoted"
+echo "get \"$DATA\" $COPY" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "get failed"
+cmp $DATA ${COPY} || fail "corrupted copy after get"
+
+rm -f ${QUOTECOPY}
+cp $DATA ${QUOTECOPY}
+verbose "$tid: get filename with quotes"
+echo "get \"$QUOTECOPY_ARG\" ${COPY}" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "get failed"
+cmp ${COPY} ${QUOTECOPY} || fail "corrupted copy after get with quotes"
+rm -f ${QUOTECOPY} ${COPY}
+
+rm -f "$SPACECOPY" ${COPY}
+cp $DATA "$SPACECOPY"
+verbose "$tid: get filename with spaces"
+echo "get ${SPACECOPY_ARG} ${COPY}" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "get failed"
+cmp ${COPY} "$SPACECOPY" || fail "corrupted copy after get with spaces"
+
+rm -f "$GLOBMETACOPY" ${COPY}
+cp $DATA "$GLOBMETACOPY"
+verbose "$tid: get filename with glob metacharacters"
+echo "get \"${GLOBMETACOPY}\" ${COPY}" | \
+ ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "get failed"
+cmp ${COPY} "$GLOBMETACOPY" || \
+ fail "corrupted copy after get with glob metacharacters"
+
+rm -f ${COPY}.dd/*
+verbose "$tid: get to directory"
+echo "get $DATA ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "get failed"
+cmp $DATA ${COPY}.dd/$DATANAME || fail "corrupted copy after get"
+
+rm -f ${COPY}.dd/*
+verbose "$tid: glob get to directory"
+echo "get /bin/l* ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "get failed"
+for x in $GLOBFILES; do
+ cmp /bin/$x ${COPY}.dd/$x || fail "corrupted copy after get"
+done
+
+rm -f ${COPY}.dd/*
+verbose "$tid: get to local dir"
+(echo "lcd ${COPY}.dd"; echo "get $DATA" ) | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "get failed"
+cmp $DATA ${COPY}.dd/$DATANAME || fail "corrupted copy after get"
+
+rm -f ${COPY}.dd/*
+verbose "$tid: glob get to local dir"
+(echo "lcd ${COPY}.dd"; echo "get /bin/l*") | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "get failed"
+for x in $GLOBFILES; do
+ cmp /bin/$x ${COPY}.dd/$x || fail "corrupted copy after get"
+done
+
+rm -f ${COPY}
+verbose "$tid: put"
+echo "put $DATA $COPY" | \
+ ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "put failed"
+cmp $DATA ${COPY} || fail "corrupted copy after put"
+
+rm -f ${QUOTECOPY}
+verbose "$tid: put filename with quotes"
+echo "put $DATA \"$QUOTECOPY_ARG\"" | \
+ ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "put failed"
+cmp $DATA ${QUOTECOPY} || fail "corrupted copy after put with quotes"
+
+rm -f "$SPACECOPY"
+verbose "$tid: put filename with spaces"
+echo "put $DATA ${SPACECOPY_ARG}" | \
+ ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "put failed"
+cmp $DATA "$SPACECOPY" || fail "corrupted copy after put with spaces"
+
+rm -f ${COPY}.dd/*
+verbose "$tid: put to directory"
+echo "put $DATA ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "put failed"
+cmp $DATA ${COPY}.dd/$DATANAME || fail "corrupted copy after put"
+
+rm -f ${COPY}.dd/*
+verbose "$tid: glob put to directory"
+echo "put /bin/l? ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "put failed"
+for x in $GLOBFILES; do
+ cmp /bin/$x ${COPY}.dd/$x || fail "corrupted copy after put"
+done
+
+rm -f ${COPY}.dd/*
+verbose "$tid: put to local dir"
+(echo "cd ${COPY}.dd"; echo "put $DATA") | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "put failed"
+cmp $DATA ${COPY}.dd/$DATANAME || fail "corrupted copy after put"
+
+rm -f ${COPY}.dd/*
+verbose "$tid: glob put to local dir"
+(echo "cd ${COPY}.dd"; echo "put /bin/l?") | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "put failed"
+for x in $GLOBFILES; do
+ cmp /bin/$x ${COPY}.dd/$x || fail "corrupted copy after put"
+done
+
+verbose "$tid: rename"
+echo "rename $COPY ${COPY}.1" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "rename failed"
+test -f ${COPY}.1 || fail "missing file after rename"
+cmp $DATA ${COPY}.1 >/dev/null 2>&1 || fail "corrupted copy after rename"
+
+verbose "$tid: rename directory"
+echo "rename ${COPY}.dd ${COPY}.dd2" | \
+ ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || \
+ fail "rename directory failed"
+test -d ${COPY}.dd && fail "oldname exists after rename directory"
+test -d ${COPY}.dd2 || fail "missing newname after rename directory"
+
+verbose "$tid: ln"
+echo "ln ${COPY}.1 ${COPY}.2" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "ln failed"
+test -f ${COPY}.2 || fail "missing file after ln"
+cmp ${COPY}.1 ${COPY}.2 || fail "created file is not equal after ln"
+
+verbose "$tid: ln -s"
+rm -f ${COPY}.2
+echo "ln -s ${COPY}.1 ${COPY}.2" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "ln -s failed"
+test -h ${COPY}.2 || fail "missing file after ln -s"
+
+verbose "$tid: cp"
+rm -f ${COPY}.2
+echo "cp ${COPY}.1 ${COPY}.2" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "cp failed"
+cmp ${COPY}.1 ${COPY}.2 || fail "created file is not equal after cp"
+
+verbose "$tid: mkdir"
+echo "mkdir ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "mkdir failed"
+test -d ${COPY}.dd || fail "missing directory after mkdir"
+
+# XXX do more here
+verbose "$tid: chdir"
+echo "chdir ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "chdir failed"
+
+verbose "$tid: rmdir"
+echo "rmdir ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "rmdir failed"
+test -d ${COPY}.1 && fail "present directory after rmdir"
+
+verbose "$tid: lmkdir"
+echo "lmkdir ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "lmkdir failed"
+test -d ${COPY}.dd || fail "missing directory after lmkdir"
+
+# XXX do more here
+verbose "$tid: lchdir"
+echo "lchdir ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \
+ || fail "lchdir failed"
+
+rm -rf ${COPY} ${COPY}.1 ${COPY}.2 ${COPY}.dd ${COPY}.dd2
+rm -rf ${QUOTECOPY} "$SPACECOPY" "$GLOBMETACOPY"
+
+
diff --git a/regress/sftp-glob.sh b/regress/sftp-glob.sh
new file mode 100644
index 0000000..8d4df2c
--- /dev/null
+++ b/regress/sftp-glob.sh
@@ -0,0 +1,75 @@
+# $OpenBSD: sftp-glob.sh,v 1.4 2009/08/13 01:11:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="sftp glob"
+
+config_defined FILESYSTEM_NO_BACKSLASH && nobs="not supported on this platform"
+
+sftp_ls() {
+ target=$1
+ errtag=$2
+ expected=$3
+ unexpected=$4
+ skip=$5
+ if test "x$skip" != "x" ; then
+ verbose "$tid: $errtag (skipped: $skip)"
+ return
+ fi
+ verbose "$tid: $errtag"
+ printf "ls -l %s" "${target}" | \
+ ${SFTP} -b - -D ${SFTPSERVER} 2>/dev/null | \
+ grep -v "^sftp>" > ${RESULTS}
+ if [ $? -ne 0 ]; then
+ fail "$errtag failed"
+ fi
+ if test "x$expected" != "x" ; then
+ if fgrep "$expected" ${RESULTS} >/dev/null 2>&1 ; then
+ :
+ else
+ fail "$expected missing from $errtag results"
+ fi
+ fi
+ if test "x$unexpected" != "x" && \
+ fgrep "$unexpected" ${RESULTS} >/dev/null 2>&1 ; then
+ fail "$unexpected present in $errtag results"
+ fi
+ rm -f ${RESULTS}
+}
+
+BASE=${OBJ}/glob
+RESULTS=${OBJ}/results
+DIR=${BASE}/dir
+DATA=${DIR}/file
+
+GLOB1="${DIR}/g-wild*"
+GLOB2="${DIR}/g-wildx"
+QUOTE="${DIR}/g-quote\""
+SLASH="${DIR}/g-sl\\ash"
+ESLASH="${DIR}/g-slash\\"
+QSLASH="${DIR}/g-qs\\\""
+SPACE="${DIR}/g-q space"
+
+rm -rf ${BASE}
+mkdir -p ${DIR}
+touch "${DATA}" "${GLOB1}" "${GLOB2}" "${QUOTE}" "${SPACE}"
+test "x$nobs" = "x" && touch "${QSLASH}" "${ESLASH}" "${SLASH}"
+
+# target message expected unexpected
+sftp_ls "${DIR}/fil*" "file glob" "${DATA}" ""
+sftp_ls "${BASE}/d*" "dir glob" "`basename ${DATA}`" ""
+sftp_ls "${DIR}/g-wild\"*\"" "quoted glob" "g-wild*" "g-wildx"
+sftp_ls "${DIR}/g-wild\*" "escaped glob" "g-wild*" "g-wildx"
+sftp_ls "${DIR}/g-quote\\\"" "escaped quote" "g-quote\"" ""
+sftp_ls "\"${DIR}/g-quote\\\"\"" "quoted quote" "g-quote\"" ""
+sftp_ls "'${DIR}/g-quote\"'" "single-quoted quote" "g-quote\"" ""
+sftp_ls "${DIR}/g-q\\ space" "escaped space" "g-q space" ""
+sftp_ls "'${DIR}/g-q space'" "quoted space" "g-q space" ""
+sftp_ls "${DIR}/g-sl\\\\ash" "escaped slash" "g-sl\\ash" "" "$nobs"
+sftp_ls "'${DIR}/g-sl\\\\ash'" "quoted slash" "g-sl\\ash" "" "$nobs"
+sftp_ls "${DIR}/g-slash\\\\" "escaped slash at EOL" "g-slash\\" "" "$nobs"
+sftp_ls "'${DIR}/g-slash\\\\'" "quoted slash at EOL" "g-slash\\" "" "$nobs"
+sftp_ls "${DIR}/g-qs\\\\\\\"" "escaped slash+quote" "g-qs\\\"" "" "$nobs"
+sftp_ls "'${DIR}/g-qs\\\\\"'" "quoted slash+quote" "g-qs\\\"" "" "$nobs"
+
+rm -rf ${BASE}
+
diff --git a/regress/sftp-perm.sh b/regress/sftp-perm.sh
new file mode 100644
index 0000000..de96a14
--- /dev/null
+++ b/regress/sftp-perm.sh
@@ -0,0 +1,271 @@
+# $OpenBSD: sftp-perm.sh,v 1.3 2021/03/31 21:59:26 djm Exp $
+# Placed in the Public Domain.
+
+tid="sftp permissions"
+
+SERVER_LOG=${OBJ}/sftp-server.log
+CLIENT_LOG=${OBJ}/sftp.log
+TEST_SFTP_SERVER=${OBJ}/sftp-server.sh
+
+prepare_server() {
+ printf "#!/bin/sh\nexec $SFTPSERVER -el debug3 $* 2>$SERVER_LOG\n" \
+ > $TEST_SFTP_SERVER
+ chmod a+x $TEST_SFTP_SERVER
+}
+
+run_client() {
+ echo "$@" | ${SFTP} -D ${TEST_SFTP_SERVER} -vvvb - >$CLIENT_LOG 2>&1
+}
+
+prepare_files() {
+ _prep="$1"
+ rm -f ${COPY} ${COPY}.1
+ test -d ${COPY}.dd && { rmdir ${COPY}.dd || fatal "rmdir ${COPY}.dd"; }
+ test -z "$_prep" && return
+ sh -c "$_prep" || fail "preparation failed: \"$_prep\""
+}
+
+postcondition() {
+ _title="$1"
+ _check="$2"
+ test -z "$_check" && return
+ ${TEST_SHELL} -c "$_check" || fail "postcondition check failed: $_title"
+}
+
+ro_test() {
+ _desc=$1
+ _cmd="$2"
+ _prep="$3"
+ _expect_success_post="$4"
+ _expect_fail_post="$5"
+ verbose "$tid: read-only $_desc"
+ # Plain (no options, mostly to test that _cmd is good)
+ prepare_files "$_prep"
+ prepare_server
+ run_client "$_cmd" || fail "plain $_desc failed"
+ postcondition "$_desc no-readonly" "$_expect_success_post"
+ # Read-only enabled
+ prepare_files "$_prep"
+ prepare_server -R
+ run_client "$_cmd" && fail "read-only $_desc succeeded"
+ postcondition "$_desc readonly" "$_expect_fail_post"
+}
+
+perm_test() {
+ _op=$1
+ _whitelist_ops=$2
+ _cmd="$3"
+ _prep="$4"
+ _expect_success_post="$5"
+ _expect_fail_post="$6"
+ verbose "$tid: explicit $_op"
+ # Plain (no options, mostly to test that _cmd is good)
+ prepare_files "$_prep"
+ prepare_server
+ run_client "$_cmd" || fail "plain $_op failed"
+ postcondition "$_op no white/blacklists" "$_expect_success_post"
+ # Whitelist
+ prepare_files "$_prep"
+ prepare_server -p $_op,$_whitelist_ops
+ run_client "$_cmd" || fail "whitelisted $_op failed"
+ postcondition "$_op whitelisted" "$_expect_success_post"
+ # Blacklist
+ prepare_files "$_prep"
+ prepare_server -P $_op
+ run_client "$_cmd" && fail "blacklisted $_op succeeded"
+ postcondition "$_op blacklisted" "$_expect_fail_post"
+ # Whitelist with op missing.
+ prepare_files "$_prep"
+ prepare_server -p $_whitelist_ops
+ run_client "$_cmd" && fail "no whitelist $_op succeeded"
+ postcondition "$_op not in whitelist" "$_expect_fail_post"
+}
+
+ro_test \
+ "upload" \
+ "put $DATA $COPY" \
+ "" \
+ "cmp $DATA $COPY" \
+ "test ! -f $COPY"
+
+ro_test \
+ "setstat" \
+ "chmod 0700 $COPY" \
+ "touch $COPY; chmod 0400 $COPY" \
+ "test -x $COPY" \
+ "test ! -x $COPY"
+
+ro_test \
+ "rm" \
+ "rm $COPY" \
+ "touch $COPY" \
+ "test ! -f $COPY" \
+ "test -f $COPY"
+
+ro_test \
+ "mkdir" \
+ "mkdir ${COPY}.dd" \
+ "" \
+ "test -d ${COPY}.dd" \
+ "test ! -d ${COPY}.dd"
+
+ro_test \
+ "rmdir" \
+ "rmdir ${COPY}.dd" \
+ "mkdir ${COPY}.dd" \
+ "test ! -d ${COPY}.dd" \
+ "test -d ${COPY}.dd"
+
+ro_test \
+ "posix-rename" \
+ "rename $COPY ${COPY}.1" \
+ "touch $COPY" \
+ "test -f ${COPY}.1 -a ! -f $COPY" \
+ "test -f $COPY -a ! -f ${COPY}.1"
+
+ro_test \
+ "oldrename" \
+ "rename -l $COPY ${COPY}.1" \
+ "touch $COPY" \
+ "test -f ${COPY}.1 -a ! -f $COPY" \
+ "test -f $COPY -a ! -f ${COPY}.1"
+
+ro_test \
+ "symlink" \
+ "ln -s $COPY ${COPY}.1" \
+ "touch $COPY" \
+ "test -h ${COPY}.1" \
+ "test ! -h ${COPY}.1"
+
+ro_test \
+ "hardlink" \
+ "ln $COPY ${COPY}.1" \
+ "touch $COPY" \
+ "test -f ${COPY}.1" \
+ "test ! -f ${COPY}.1"
+
+# Test explicit permissions
+
+perm_test \
+ "open" \
+ "realpath,stat,lstat,read,close" \
+ "get $DATA $COPY" \
+ "" \
+ "cmp $DATA $COPY" \
+ "! cmp $DATA $COPY 2>/dev/null"
+
+perm_test \
+ "read" \
+ "realpath,stat,lstat,open,close" \
+ "get $DATA $COPY" \
+ "" \
+ "cmp $DATA $COPY" \
+ "! cmp $DATA $COPY 2>/dev/null"
+
+perm_test \
+ "write" \
+ "realpath,stat,lstat,open,close" \
+ "put $DATA $COPY" \
+ "" \
+ "cmp $DATA $COPY" \
+ "! cmp $DATA $COPY 2>/dev/null"
+
+perm_test \
+ "lstat" \
+ "realpath,stat,open,read,close" \
+ "get $DATA $COPY" \
+ "" \
+ "cmp $DATA $COPY" \
+ "! cmp $DATA $COPY 2>/dev/null"
+
+perm_test \
+ "opendir" \
+ "realpath,readdir,stat,lstat" \
+ "ls -ln $OBJ"
+
+perm_test \
+ "readdir" \
+ "realpath,opendir,stat,lstat" \
+ "ls -ln $OBJ"
+
+perm_test \
+ "setstat" \
+ "realpath,stat,lstat" \
+ "chmod 0700 $COPY" \
+ "touch $COPY; chmod 0400 $COPY" \
+ "test -x $COPY" \
+ "test ! -x $COPY"
+
+perm_test \
+ "remove" \
+ "realpath,stat,lstat" \
+ "rm $COPY" \
+ "touch $COPY" \
+ "test ! -f $COPY" \
+ "test -f $COPY"
+
+perm_test \
+ "mkdir" \
+ "realpath,stat,lstat" \
+ "mkdir ${COPY}.dd" \
+ "" \
+ "test -d ${COPY}.dd" \
+ "test ! -d ${COPY}.dd"
+
+perm_test \
+ "rmdir" \
+ "realpath,stat,lstat" \
+ "rmdir ${COPY}.dd" \
+ "mkdir ${COPY}.dd" \
+ "test ! -d ${COPY}.dd" \
+ "test -d ${COPY}.dd"
+
+# Can't readily test this because the client falls back to traditional rename.
+# XXX maybe there is a behaviorial difference we can test for?
+#perm_test \
+# "posix-rename" \
+# "realpath,stat,lstat" \
+# "rename $COPY ${COPY}.1" \
+# "touch $COPY" \
+# "test -f ${COPY}.1 -a ! -f $COPY" \
+# "test -f $COPY -a ! -f ${COPY}.1"
+
+perm_test \
+ "rename" \
+ "realpath,stat,lstat" \
+ "rename -l $COPY ${COPY}.1" \
+ "touch $COPY" \
+ "test -f ${COPY}.1 -a ! -f $COPY" \
+ "test -f $COPY -a ! -f ${COPY}.1"
+
+perm_test \
+ "symlink" \
+ "realpath,stat,lstat" \
+ "ln -s $COPY ${COPY}.1" \
+ "touch $COPY" \
+ "test -h ${COPY}.1" \
+ "test ! -h ${COPY}.1"
+
+perm_test \
+ "hardlink" \
+ "realpath,stat,lstat" \
+ "ln $COPY ${COPY}.1" \
+ "touch $COPY" \
+ "test -f ${COPY}.1" \
+ "test ! -f ${COPY}.1"
+
+perm_test \
+ "statvfs" \
+ "realpath,stat,lstat" \
+ "df /"
+
+# XXX need good tests for:
+# fstat
+# fsetstat
+# realpath
+# stat
+# readlink
+# fstatvfs
+
+rm -rf ${COPY} ${COPY}.1 ${COPY}.dd
+
diff --git a/regress/sftp-uri.sh b/regress/sftp-uri.sh
new file mode 100644
index 0000000..7be104d
--- /dev/null
+++ b/regress/sftp-uri.sh
@@ -0,0 +1,63 @@
+# $OpenBSD: sftp-uri.sh,v 1.1 2017/10/24 19:33:32 millert Exp $
+# Placed in the Public Domain.
+
+tid="sftp-uri"
+
+#set -x
+
+COPY2=${OBJ}/copy2
+DIR=${COPY}.dd
+DIR2=${COPY}.dd2
+SRC=`dirname ${SCRIPT}`
+
+sftpclean() {
+ rm -rf ${COPY} ${COPY2} ${DIR} ${DIR2}
+ mkdir ${DIR} ${DIR2}
+}
+
+start_sshd -oForceCommand="internal-sftp -d /"
+
+# Remove Port and User from ssh_config, we want to rely on the URI
+cp $OBJ/ssh_config $OBJ/ssh_config.orig
+egrep -v '^ +(Port|User) +.*$' $OBJ/ssh_config.orig > $OBJ/ssh_config
+
+verbose "$tid: non-interactive fetch to local file"
+sftpclean
+${SFTP} -q -S "$SSH" -F $OBJ/ssh_config "sftp://${USER}@somehost:${PORT}/${DATA}" ${COPY} || fail "copy failed"
+cmp ${DATA} ${COPY} || fail "corrupted copy"
+
+verbose "$tid: non-interactive fetch to local dir"
+sftpclean
+cp ${DATA} ${COPY}
+${SFTP} -q -S "$SSH" -F $OBJ/ssh_config "sftp://${USER}@somehost:${PORT}/${COPY}" ${DIR} || fail "copy failed"
+cmp ${COPY} ${DIR}/copy || fail "corrupted copy"
+
+verbose "$tid: put to remote directory (trailing slash)"
+sftpclean
+${SFTP} -q -S "$SSH" -F $OBJ/ssh_config -b - \
+ "sftp://${USER}@somehost:${PORT}/${DIR}/" > /dev/null 2>&1 << EOF
+ version
+ put ${DATA} copy
+EOF
+r=$?
+if [ $r -ne 0 ]; then
+ fail "sftp failed with $r"
+else
+ cmp ${DATA} ${DIR}/copy || fail "corrupted copy"
+fi
+
+verbose "$tid: put to remote directory (no slash)"
+sftpclean
+${SFTP} -q -S "$SSH" -F $OBJ/ssh_config -b - \
+ "sftp://${USER}@somehost:${PORT}/${DIR}" > /dev/null 2>&1 << EOF
+ version
+ put ${DATA} copy
+EOF
+r=$?
+if [ $r -ne 0 ]; then
+ fail "sftp failed with $r"
+else
+ cmp ${DATA} ${DIR}/copy || fail "corrupted copy"
+fi
+
+sftpclean
diff --git a/regress/sftp.sh b/regress/sftp.sh
new file mode 100644
index 0000000..a5c88f5
--- /dev/null
+++ b/regress/sftp.sh
@@ -0,0 +1,32 @@
+# $OpenBSD: sftp.sh,v 1.6 2017/10/30 21:59:43 djm Exp $
+# Placed in the Public Domain.
+
+tid="basic sftp put/get"
+
+SFTPCMDFILE=${OBJ}/batch
+cat >$SFTPCMDFILE <<EOF
+version
+get $DATA ${COPY}.1
+put $DATA ${COPY}.2
+EOF
+
+BUFFERSIZE="5 1000 32000 64000"
+REQUESTS="1 2 10"
+
+for B in ${BUFFERSIZE}; do
+ for R in ${REQUESTS}; do
+ verbose "test $tid: buffer_size $B num_requests $R"
+ rm -f ${COPY}.1 ${COPY}.2
+ ${SFTP} -D ${SFTPSERVER} -B $B -R $R -b $SFTPCMDFILE \
+ > /dev/null 2>&1
+ r=$?
+ if [ $r -ne 0 ]; then
+ fail "sftp failed with $r"
+ else
+ cmp $DATA ${COPY}.1 || fail "corrupted copy after get"
+ cmp $DATA ${COPY}.2 || fail "corrupted copy after put"
+ fi
+ done
+done
+rm -f ${COPY}.1 ${COPY}.2
+rm -f $SFTPCMDFILE
diff --git a/regress/ssh-com-client.sh b/regress/ssh-com-client.sh
new file mode 100644
index 0000000..e4f80cf
--- /dev/null
+++ b/regress/ssh-com-client.sh
@@ -0,0 +1,130 @@
+# $OpenBSD: ssh-com-client.sh,v 1.7 2013/05/17 04:29:14 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="connect with ssh.com client"
+
+#TEST_COMBASE=/path/to/ssh/com/binaries
+if [ "X${TEST_COMBASE}" = "X" ]; then
+ fatal '$TEST_COMBASE is not set'
+fi
+
+VERSIONS="
+ 2.1.0
+ 2.2.0
+ 2.3.0
+ 2.3.1
+ 2.4.0
+ 3.0.0
+ 3.1.0
+ 3.2.0
+ 3.2.2
+ 3.2.3
+ 3.2.5
+ 3.2.9
+ 3.2.9.1
+ 3.3.0"
+
+# 2.0.10 2.0.12 2.0.13 don't like the test setup
+
+# setup authorized keys
+SRC=`dirname ${SCRIPT}`
+cp ${SRC}/dsa_ssh2.prv ${OBJ}/id.com
+chmod 600 ${OBJ}/id.com
+${SSHKEYGEN} -i -f ${OBJ}/id.com > $OBJ/id.openssh
+chmod 600 ${OBJ}/id.openssh
+${SSHKEYGEN} -y -f ${OBJ}/id.openssh > $OBJ/authorized_keys_$USER
+${SSHKEYGEN} -e -f ${OBJ}/id.openssh > $OBJ/id.com.pub
+echo IdKey ${OBJ}/id.com > ${OBJ}/id.list
+
+# we need a DSA host key
+t=dsa
+rm -f ${OBJ}/$t ${OBJ}/$t.pub
+${SSHKEYGEN} -q -N '' -t $t -f ${OBJ}/$t
+$SUDO cp $OBJ/$t $OBJ/host.$t
+echo HostKey $OBJ/host.$t >> $OBJ/sshd_config
+
+# add hostkeys to known hosts
+mkdir -p ${OBJ}/${USER}/hostkeys
+HK=${OBJ}/${USER}/hostkeys/key_${PORT}_127.0.0.1
+${SSHKEYGEN} -e -f ${OBJ}/rsa.pub > ${HK}.ssh-rsa.pub
+${SSHKEYGEN} -e -f ${OBJ}/dsa.pub > ${HK}.ssh-dss.pub
+
+cat > ${OBJ}/ssh2_config << EOF
+*:
+ QuietMode yes
+ StrictHostKeyChecking yes
+ Port ${PORT}
+ User ${USER}
+ Host 127.0.0.1
+ IdentityFile ${OBJ}/id.list
+ RandomSeedFile ${OBJ}/random_seed
+ UserConfigDirectory ${OBJ}/%U
+ AuthenticationSuccessMsg no
+ BatchMode yes
+ ForwardX11 no
+EOF
+
+# we need a real server (no ProxyConnect option)
+start_sshd
+
+# go for it
+for v in ${VERSIONS}; do
+ ssh2=${TEST_COMBASE}/${v}/ssh2
+ if [ ! -x ${ssh2} ]; then
+ continue
+ fi
+ verbose "ssh2 ${v}"
+ key=ssh-dss
+ skipcat=0
+ case $v in
+ 2.1.*|2.3.0)
+ skipcat=1
+ ;;
+ 3.0.*)
+ key=ssh-rsa
+ ;;
+ esac
+ cp ${HK}.$key.pub ${HK}.pub
+
+ # check exit status
+ ${ssh2} -q -F ${OBJ}/ssh2_config somehost exit 42
+ r=$?
+ if [ $r -ne 42 ]; then
+ fail "ssh2 ${v} exit code test failed (got $r, expected 42)"
+ fi
+
+ # data transfer
+ rm -f ${COPY}
+ ${ssh2} -F ${OBJ}/ssh2_config somehost cat ${DATA} > ${COPY}
+ if [ $? -ne 0 ]; then
+ fail "ssh2 ${v} cat test (receive) failed"
+ fi
+ cmp ${DATA} ${COPY} || fail "ssh2 ${v} cat test (receive) data mismatch"
+
+ # data transfer, again
+ if [ $skipcat -eq 0 ]; then
+ rm -f ${COPY}
+ cat ${DATA} | \
+ ${ssh2} -F ${OBJ}/ssh2_config host "cat > ${COPY}"
+ if [ $? -ne 0 ]; then
+ fail "ssh2 ${v} cat test (send) failed"
+ fi
+ cmp ${DATA} ${COPY} || \
+ fail "ssh2 ${v} cat test (send) data mismatch"
+ fi
+
+ # no stderr after eof
+ rm -f ${COPY}
+ ${ssh2} -F ${OBJ}/ssh2_config somehost \
+ exec sh -c \'"exec > /dev/null; sleep 1; echo bla 1>&2; exit 0"\' \
+ 2> /dev/null
+ if [ $? -ne 0 ]; then
+ fail "ssh2 ${v} stderr test failed"
+ fi
+done
+
+rm -rf ${OBJ}/${USER}
+for i in ssh2_config random_seed dsa.pub dsa host.dsa \
+ id.list id.com id.com.pub id.openssh; do
+ rm -f ${OBJ}/$i
+done
diff --git a/regress/ssh-com-keygen.sh b/regress/ssh-com-keygen.sh
new file mode 100644
index 0000000..29b02d9
--- /dev/null
+++ b/regress/ssh-com-keygen.sh
@@ -0,0 +1,74 @@
+# $OpenBSD: ssh-com-keygen.sh,v 1.4 2004/02/24 17:06:52 markus Exp $
+# Placed in the Public Domain.
+
+tid="ssh.com key import"
+
+#TEST_COMBASE=/path/to/ssh/com/binaries
+if [ "X${TEST_COMBASE}" = "X" ]; then
+ fatal '$TEST_COMBASE is not set'
+fi
+
+VERSIONS="
+ 2.0.10
+ 2.0.12
+ 2.0.13
+ 2.1.0
+ 2.2.0
+ 2.3.0
+ 2.3.1
+ 2.4.0
+ 3.0.0
+ 3.1.0
+ 3.2.0
+ 3.2.2
+ 3.2.3
+ 3.2.5
+ 3.2.9
+ 3.2.9.1
+ 3.3.0"
+
+COMPRV=${OBJ}/comkey
+COMPUB=${COMPRV}.pub
+OPENSSHPRV=${OBJ}/opensshkey
+OPENSSHPUB=${OPENSSHPRV}.pub
+
+# go for it
+for v in ${VERSIONS}; do
+ keygen=${TEST_COMBASE}/${v}/ssh-keygen2
+ if [ ! -x ${keygen} ]; then
+ continue
+ fi
+ types="dss"
+ case $v in
+ 2.3.1|3.*)
+ types="$types rsa"
+ ;;
+ esac
+ for t in $types; do
+ verbose "ssh-keygen $v/$t"
+ rm -f $COMPRV $COMPUB $OPENSSHPRV $OPENSSHPUB
+ ${keygen} -q -P -t $t ${COMPRV} > /dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ fail "${keygen} -t $t failed"
+ continue
+ fi
+ ${SSHKEYGEN} -if ${COMPUB} > ${OPENSSHPUB}
+ if [ $? -ne 0 ]; then
+ fail "import public key ($v/$t) failed"
+ continue
+ fi
+ ${SSHKEYGEN} -if ${COMPRV} > ${OPENSSHPRV}
+ if [ $? -ne 0 ]; then
+ fail "import private key ($v/$t) failed"
+ continue
+ fi
+ chmod 600 ${OPENSSHPRV}
+ ${SSHKEYGEN} -yf ${OPENSSHPRV} |\
+ diff - ${OPENSSHPUB}
+ if [ $? -ne 0 ]; then
+ fail "public keys ($v/$t) differ"
+ fi
+ done
+done
+
+rm -f $COMPRV $COMPUB $OPENSSHPRV $OPENSSHPUB
diff --git a/regress/ssh-com-sftp.sh b/regress/ssh-com-sftp.sh
new file mode 100644
index 0000000..fabfa49
--- /dev/null
+++ b/regress/ssh-com-sftp.sh
@@ -0,0 +1,65 @@
+# $OpenBSD: ssh-com-sftp.sh,v 1.7 2013/05/17 04:29:14 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="basic sftp put/get with ssh.com server"
+
+SFTPCMDFILE=${OBJ}/batch
+
+cat >$SFTPCMDFILE <<EOF
+version
+get $DATA ${COPY}.1
+put $DATA ${COPY}.2
+EOF
+
+BUFFERSIZE="5 1000 32000 64000"
+REQUESTS="1 2 10"
+
+#TEST_COMBASE=/path/to/ssh/com/binaries
+if [ "X${TEST_COMBASE}" = "X" ]; then
+ fatal '$TEST_COMBASE is not set'
+fi
+
+VERSIONS="
+ 2.0.10
+ 2.0.12
+ 2.0.13
+ 2.1.0
+ 2.2.0
+ 2.3.0
+ 2.3.1
+ 2.4.0
+ 3.0.0
+ 3.1.0
+ 3.2.0
+ 3.2.2
+ 3.2.3
+ 3.2.5
+ 3.2.9
+ 3.2.9.1
+ 3.3.0"
+
+# go for it
+for v in ${VERSIONS}; do
+ server=${TEST_COMBASE}/${v}/sftp-server2
+ if [ ! -x ${server} ]; then
+ continue
+ fi
+ verbose "sftp-server $v"
+ for B in ${BUFFERSIZE}; do
+ for R in ${REQUESTS}; do
+ verbose "test $tid: buffer_size $B num_requests $R"
+ rm -f ${COPY}.1 ${COPY}.2
+ ${SFTP} -D ${server} -B $B -R $R -b $SFTPCMDFILE \
+ > /dev/null 2>&1
+ r=$?
+ if [ $r -ne 0 ]; then
+ fail "sftp failed with $r"
+ else
+ cmp $DATA ${COPY}.1 || fail "corrupted copy after get"
+ cmp $DATA ${COPY}.2 || fail "corrupted copy after put"
+ fi
+ done
+ done
+done
+rm -f ${COPY}.1 ${COPY}.2
+rm -f $SFTPCMDFILE
diff --git a/regress/ssh-com.sh b/regress/ssh-com.sh
new file mode 100644
index 0000000..b1a2505
--- /dev/null
+++ b/regress/ssh-com.sh
@@ -0,0 +1,119 @@
+# $OpenBSD: ssh-com.sh,v 1.10 2017/05/08 01:52:49 djm Exp $
+# Placed in the Public Domain.
+
+tid="connect to ssh.com server"
+
+#TEST_COMBASE=/path/to/ssh/com/binaries
+if [ "X${TEST_COMBASE}" = "X" ]; then
+ fatal '$TEST_COMBASE is not set'
+fi
+
+VERSIONS="
+ 2.0.12
+ 2.0.13
+ 2.1.0
+ 2.2.0
+ 2.3.0
+ 2.4.0
+ 3.0.0
+ 3.1.0
+ 3.2.0
+ 3.2.2
+ 3.2.3
+ 3.2.5
+ 3.2.9
+ 3.2.9.1
+ 3.3.0"
+# 2.0.10 does not support UserConfigDirectory
+# 2.3.1 requires a config in $HOME/.ssh2
+
+SRC=`dirname ${SCRIPT}`
+
+# ssh.com
+cat << EOF > $OBJ/sshd2_config
+#*:
+ # Port and ListenAddress are not used.
+ QuietMode yes
+ Port 4343
+ ListenAddress 127.0.0.1
+ UserConfigDirectory ${OBJ}/%U
+ Ciphers AnyCipher
+ PubKeyAuthentication yes
+ #AllowedAuthentications publickey
+ AuthorizationFile authorization
+ HostKeyFile ${SRC}/dsa_ssh2.prv
+ PublicHostKeyFile ${SRC}/dsa_ssh2.pub
+ RandomSeedFile ${OBJ}/random_seed
+ MaxConnections 0
+ PermitRootLogin yes
+ VerboseMode no
+ CheckMail no
+ Ssh1Compatibility no
+EOF
+
+# create client config
+sed "s/HostKeyAlias.*/HostKeyAlias ssh2-localhost-with-alias/" \
+ < $OBJ/ssh_config > $OBJ/ssh_config_com
+
+# we need a DSA key for
+rm -f ${OBJ}/dsa ${OBJ}/dsa.pub
+${SSHKEYGEN} -q -N '' -t dsa -f ${OBJ}/dsa
+
+# setup userdir, try rsa first
+mkdir -p ${OBJ}/${USER}
+cp /dev/null ${OBJ}/${USER}/authorization
+for t in rsa dsa; do
+ ${SSHKEYGEN} -e -f ${OBJ}/$t.pub > ${OBJ}/${USER}/$t.com
+ echo Key $t.com >> ${OBJ}/${USER}/authorization
+ echo IdentityFile ${OBJ}/$t >> ${OBJ}/ssh_config_com
+done
+
+# convert and append DSA hostkey
+(
+ printf 'ssh2-localhost-with-alias,127.0.0.1,::1 '
+ ${SSHKEYGEN} -if ${SRC}/dsa_ssh2.pub
+) >> $OBJ/known_hosts
+
+# go for it
+for v in ${VERSIONS}; do
+ sshd2=${TEST_COMBASE}/${v}/sshd2
+ if [ ! -x ${sshd2} ]; then
+ continue
+ fi
+ trace "sshd2 ${v}"
+ PROXY="proxycommand ${sshd2} -qif ${OBJ}/sshd2_config 2> /dev/null"
+ ${SSH} -qF ${OBJ}/ssh_config_com -o "${PROXY}" dummy exit 0
+ if [ $? -ne 0 ]; then
+ fail "ssh connect to sshd2 ${v} failed"
+ fi
+
+ ciphers="3des-cbc"
+ macs="hmac-md5"
+ case $v in
+ 2.4.*)
+ ciphers="$ciphers cast128-cbc"
+ macs="$macs hmac-sha1 hmac-sha1-96 hmac-md5-96"
+ ;;
+ 3.*)
+ ciphers="$ciphers aes128-cbc cast128-cbc"
+ macs="$macs hmac-sha1 hmac-sha1-96 hmac-md5-96"
+ ;;
+ esac
+ #ciphers="3des-cbc"
+ for m in $macs; do
+ for c in $ciphers; do
+ trace "sshd2 ${v} cipher $c mac $m"
+ verbose "test ${tid}: sshd2 ${v} cipher $c mac $m"
+ ${SSH} -c $c -m $m -qF ${OBJ}/ssh_config_com -o "${PROXY}" dummy exit 0
+ if [ $? -ne 0 ]; then
+ fail "ssh connect to sshd2 ${v} with $c/$m failed"
+ fi
+ done
+ done
+done
+
+rm -rf ${OBJ}/${USER}
+for i in sshd_config_proxy ssh_config_proxy random_seed \
+ sshd2_config dsa.pub dsa ssh_config_com; do
+ rm -f ${OBJ}/$i
+done
diff --git a/regress/ssh2putty.sh b/regress/ssh2putty.sh
new file mode 100755
index 0000000..9b08310
--- /dev/null
+++ b/regress/ssh2putty.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# $OpenBSD: ssh2putty.sh,v 1.9 2021/07/25 12:13:03 dtucker Exp $
+
+if test "x$1" = "x" -o "x$2" = "x" -o "x$3" = "x" ; then
+ echo "Usage: ssh2putty hostname port ssh-private-key"
+ exit 1
+fi
+
+HOST=$1
+PORT=$2
+KEYFILE=$3
+
+OPENSSL_BIN="${OPENSSL_BIN:-openssl}"
+
+# XXX - support DSA keys too
+if grep "BEGIN RSA PRIVATE KEY" $KEYFILE >/dev/null 2>&1 ; then
+ :
+else
+ echo "Unsupported private key format"
+ exit 1
+fi
+
+public_exponent=`
+ $OPENSSL_BIN rsa -noout -text -in $KEYFILE | grep ^publicExponent |
+ sed 's/.*(//;s/).*//'
+`
+test $? -ne 0 && exit 1
+
+modulus=`
+ $OPENSSL_BIN rsa -noout -modulus -in $KEYFILE | grep ^Modulus= |
+ sed 's/^Modulus=/0x/' | tr A-Z a-z
+`
+test $? -ne 0 && exit 1
+
+echo "rsa2@$PORT:$HOST $public_exponent,$modulus"
+
diff --git a/regress/sshcfgparse.sh b/regress/sshcfgparse.sh
new file mode 100644
index 0000000..504853d
--- /dev/null
+++ b/regress/sshcfgparse.sh
@@ -0,0 +1,119 @@
+# $OpenBSD: sshcfgparse.sh,v 1.9 2021/06/08 07:05:27 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="ssh config parse"
+
+dsa=0
+for t in $SSH_KEYTYPES; do
+ case "$t" in
+ ssh-dss) dsa=1 ;;
+ esac
+done
+
+expect_result_present() {
+ _str="$1" ; shift
+ for _expect in "$@" ; do
+ echo "$f" | tr ',' '\n' | grep "^$_expect\$" >/dev/null
+ if test $? -ne 0 ; then
+ fail "missing expected \"$_expect\" from \"$_str\""
+ fi
+ done
+}
+expect_result_absent() {
+ _str="$1" ; shift
+ for _expect in "$@" ; do
+ echo "$f" | tr ',' '\n' | grep "^$_expect\$" >/dev/null
+ if test $? -eq 0 ; then
+ fail "unexpected \"$_expect\" present in \"$_str\""
+ fi
+ done
+}
+
+verbose "reparse minimal config"
+(${SSH} -G -F $OBJ/ssh_config somehost >$OBJ/ssh_config.1 &&
+ ${SSH} -G -F $OBJ/ssh_config.1 somehost >$OBJ/ssh_config.2 &&
+ diff $OBJ/ssh_config.1 $OBJ/ssh_config.2) || fail "failed to reparse minimal"
+
+verbose "ssh -W opts"
+f=`${SSH} -GF $OBJ/ssh_config host | awk '/exitonforwardfailure/{print $2}'`
+test "$f" = "no" || fail "exitonforwardfailure default"
+f=`${SSH} -GF $OBJ/ssh_config -W a:1 h | awk '/exitonforwardfailure/{print $2}'`
+test "$f" = "yes" || fail "exitonforwardfailure enable"
+f=`${SSH} -GF $OBJ/ssh_config -W a:1 -o exitonforwardfailure=no h | \
+ awk '/exitonforwardfailure/{print $2}'`
+test "$f" = "no" || fail "exitonforwardfailure override"
+
+f=`${SSH} -GF $OBJ/ssh_config host | awk '/clearallforwardings/{print $2}'`
+test "$f" = "no" || fail "clearallforwardings default"
+f=`${SSH} -GF $OBJ/ssh_config -W a:1 h | awk '/clearallforwardings/{print $2}'`
+test "$f" = "yes" || fail "clearallforwardings enable"
+f=`${SSH} -GF $OBJ/ssh_config -W a:1 -o clearallforwardings=no h | \
+ awk '/clearallforwardings/{print $2}'`
+test "$f" = "no" || fail "clearallforwardings override"
+
+verbose "user first match"
+user=`awk '$1=="User" {print $2}' $OBJ/ssh_config`
+f=`${SSH} -GF $OBJ/ssh_config host | awk '/^user /{print $2}'`
+test "$f" = "$user" || fail "user from config, expected '$user' got '$f'"
+f=`${SSH} -GF $OBJ/ssh_config -o user=foo -l bar baz@host | awk '/^user /{print $2}'`
+test "$f" = "foo" || fail "user first match -oUser, expected 'foo' got '$f' "
+f=`${SSH} -GF $OBJ/ssh_config -lbar baz@host user=foo baz@host | awk '/^user /{print $2}'`
+test "$f" = "bar" || fail "user first match -l, expected 'bar' got '$f'"
+f=`${SSH} -GF $OBJ/ssh_config baz@host -o user=foo -l bar baz@host | awk '/^user /{print $2}'`
+test "$f" = "baz" || fail "user first match user@host, expected 'baz' got '$f'"
+
+verbose "pubkeyacceptedalgorithms"
+# Default set
+f=`${SSH} -GF none host | awk '/^pubkeyacceptedalgorithms /{print $2}'`
+expect_result_present "$f" "ssh-ed25519" "ssh-ed25519-cert-v01.*"
+expect_result_absent "$f" "ssh-dss"
+# Explicit override
+f=`${SSH} -GF none -opubkeyacceptedalgorithms=ssh-ed25519 host | \
+ awk '/^pubkeyacceptedalgorithms /{print $2}'`
+expect_result_present "$f" "ssh-ed25519"
+expect_result_absent "$f" "ssh-ed25519-cert-v01.*" "ssh-dss"
+# Removal from default set
+f=`${SSH} -GF none -opubkeyacceptedalgorithms=-ssh-ed25519-cert* host | \
+ awk '/^pubkeyacceptedalgorithms /{print $2}'`
+expect_result_present "$f" "ssh-ed25519"
+expect_result_absent "$f" "ssh-ed25519-cert-v01.*" "ssh-dss"
+f=`${SSH} -GF none -opubkeyacceptedalgorithms=-ssh-ed25519 host | \
+ awk '/^pubkeyacceptedalgorithms /{print $2}'`
+expect_result_present "$f" "ssh-ed25519-cert-v01.*"
+expect_result_absent "$f" "ssh-ed25519" "ssh-dss"
+# Append to default set.
+# This is not tested when built !WITH_OPENSSL
+if [ "$dsa" = "1" ]; then
+ f=`${SSH} -GF none -opubkeyacceptedalgorithms=+ssh-dss-cert* host | \
+ awk '/^pubkeyacceptedalgorithms /{print $2}'`
+ expect_result_present "$f" "ssh-ed25519" "ssh-dss-cert-v01.*"
+ expect_result_absent "$f" "ssh-dss"
+ f=`${SSH} -GF none -opubkeyacceptedalgorithms=+ssh-dss host | \
+ awk '/^pubkeyacceptedalgorithms /{print $2}'`
+ expect_result_present "$f" "ssh-ed25519" "ssh-ed25519-cert-v01.*" "ssh-dss"
+ expect_result_absent "$f" "ssh-dss-cert-v01.*"
+fi
+
+verbose "agentforwarding"
+f=`${SSH} -GF none host | awk '/^forwardagent /{print$2}'`
+expect_result_present "$f" "no"
+f=`${SSH} -GF none -oforwardagent=no host | awk '/^forwardagent /{print$2}'`
+expect_result_present "$f" "no"
+f=`${SSH} -GF none -oforwardagent=yes host | awk '/^forwardagent /{print$2}'`
+expect_result_present "$f" "yes"
+f=`${SSH} -GF none '-oforwardagent=SSH_AUTH_SOCK.forward' host | awk '/^forwardagent /{print$2}'`
+expect_result_present "$f" "SSH_AUTH_SOCK.forward"
+
+verbose "command line override"
+cat >$OBJ/ssh_config.0 <<EOD
+Host *
+ IPQoS af21 cs1
+ TunnelDevice 1:2
+EOD
+f=`${SSH} -GF $OBJ/ssh_config.0 -oipqos=cs1 host | awk '/^ipqos /{print$2}'`
+expect_result_present "$f" "cs1"
+f=`${SSH} -GF $OBJ/ssh_config.0 -otunneldevice=3:4 host | awk '/^tunneldevice /{print$2}'`
+expect_result_present "$f" "3:4"
+
+# cleanup
+rm -f $OBJ/ssh_config.[012]
diff --git a/regress/sshd-log-wrapper.sh b/regress/sshd-log-wrapper.sh
new file mode 100644
index 0000000..4b3c911
--- /dev/null
+++ b/regress/sshd-log-wrapper.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+# $OpenBSD: sshd-log-wrapper.sh,v 1.5 2022/01/04 08:38:53 dtucker Exp $
+# Placed in the Public Domain.
+#
+# simple wrapper for sshd proxy mode to catch stderr output
+# sh sshd-log-wrapper.sh /path/to/logfile /path/to/sshd [args...]
+
+log=$1
+shift
+
+echo "Executing: $@" >>$log
+exec "$@" -E$log
diff --git a/regress/sshfp-connect.sh b/regress/sshfp-connect.sh
new file mode 100644
index 0000000..f786469
--- /dev/null
+++ b/regress/sshfp-connect.sh
@@ -0,0 +1,66 @@
+# $OpenBSD: sshfp-connect.sh,v 1.4 2021/09/01 00:50:27 dtucker Exp $
+# Placed in the Public Domain.
+
+# This test requires external setup and thus is skipped unless
+# TEST_SSH_SSHFP_DOMAIN is set. It requires:
+# 1) A DNSSEC-enabled domain, which TEST_SSH_SSHFP_DOMAIN points to.
+# 2) A DNSSEC-validating resolver such as unwind(8).
+# 3) The following SSHFP records with fingerprints from rsa_openssh.pub
+# in that domain that are expected to succeed:
+# sshtest: valid sha1 and sha256 fingerprints.
+# sshtest-sha{1,256}, : valid fingerprints for that type only.
+# and the following records that are expected to fail:
+# sshtest-bad: invalid sha1 fingerprint and good sha256 fingerprint
+# sshtest-sha{1,256}-bad: invalid fingerprints for that type only.
+#
+# sshtest IN SSHFP 1 1 99C79CC09F5F81069CC017CDF9552CFC94B3B929
+# sshtest IN SSHFP 1 2 E30D6B9EB7A4DE495324E4D5870B8220577993EA6AF417E8E4A4F1C5 BF01A9B6
+# sshtest-sha1 IN SSHFP 1 1 99C79CC09F5F81069CC017CDF9552CFC94B3B929
+# sshtest-sha256 IN SSHFP 1 2 E30D6B9EB7A4DE495324E4D5870B8220577993EA6AF417E8E4A4F1C5 BF01A9B6
+# sshtest-bad IN SSHFP 1 2 E30D6B9EB7A4DE495324E4D5870B8220577993EA6AF417E8E4A4F1C5 BF01A9B6
+# sshtest-bad IN SSHFP 1 1 99C79CC09F5F81069CC017CDF9552CFC94B3B928
+# sshtest-sha1-bad IN SSHFP 1 1 99D79CC09F5F81069CC017CDF9552CFC94B3B929
+# sshtest-sha256-bad IN SSHFP 1 2 E30D6B9EB7A4DE495324E4D5870B8220577993EA6AF417E8E4A4F1C5 BF01A9B5
+
+tid="sshfp connect"
+
+if ! $SSH -Q key-plain | grep ssh-rsa >/dev/null; then
+ skip "RSA keys not supported."
+elif [ -z "${TEST_SSH_SSHFP_DOMAIN}" ]; then
+ skip "TEST_SSH_SSHFP_DOMAIN not set."
+else
+ # Set RSA host key to match fingerprints above.
+ mv $OBJ/sshd_proxy $OBJ/sshd_proxy.orig
+ $SUDO cp $SRC/rsa_openssh.prv $OBJ/host.ssh-rsa
+ $SUDO chmod 600 $OBJ/host.ssh-rsa
+ sed -e "s|$OBJ/ssh-rsa|$OBJ/host.ssh-rsa|" \
+ $OBJ/sshd_proxy.orig > $OBJ/sshd_proxy
+
+ # Zero out known hosts and key aliases to force use of SSHFP records.
+ > $OBJ/known_hosts
+ mv $OBJ/ssh_proxy $OBJ/ssh_proxy.orig
+ sed -e "/HostKeyAlias.*localhost-with-alias/d" \
+ -e "/Hostname.*127.0.0.1/d" \
+ $OBJ/ssh_proxy.orig > $OBJ/ssh_proxy
+
+ for n in sshtest sshtest-sha1 sshtest-sha256; do
+ trace "sshfp connect $n good fingerprint"
+ host="${n}.dtucker.net"
+ opts="-F $OBJ/ssh_proxy -o VerifyHostKeyDNS=yes "
+ opts="$opts -o HostKeyAlgorithms=rsa-sha2-512,rsa-sha2-256"
+ host="${n}.${TEST_SSH_SSHFP_DOMAIN}"
+ SSH_CONNECTION=`${SSH} $opts $host 'echo $SSH_CONNECTION'`
+ if [ $? -ne 0 ]; then
+ fail "ssh sshfp connect failed"
+ fi
+ if [ "$SSH_CONNECTION" != "UNKNOWN 65535 UNKNOWN 65535" ]; then
+ fail "bad SSH_CONNECTION: $SSH_CONNECTION"
+ fi
+
+ trace "sshfp connect $n bad fingerprint"
+ host="${n}-bad.${TEST_SSH_SSHFP_DOMAIN}"
+ if ${SSH} $opts ${host} true; then
+ fail "sshfp-connect succeeded with bad SSHFP record"
+ fi
+ done
+fi
diff --git a/regress/sshsig.sh b/regress/sshsig.sh
new file mode 100644
index 0000000..d4daa5c
--- /dev/null
+++ b/regress/sshsig.sh
@@ -0,0 +1,476 @@
+# $OpenBSD: sshsig.sh,v 1.14 2022/02/01 23:37:15 djm Exp $
+# Placed in the Public Domain.
+
+tid="sshsig"
+
+DATA2=$OBJ/${DATANAME}.2
+cat ${DATA} ${DATA} > ${DATA2}
+
+rm -f $OBJ/sshsig-*.sig $OBJ/wrong-key* $OBJ/sigca-key*
+
+sig_namespace="test-$$"
+sig_principal="user-$$@example.com"
+
+# Make a "wrong key"
+${SSHKEYGEN} -q -t ed25519 -f $OBJ/wrong-key \
+ -C "wrong trousers, Grommit" -N '' \
+ || fatal "couldn't generate key"
+WRONG=$OBJ/wrong-key.pub
+
+# Make a CA key.
+${SSHKEYGEN} -q -t ed25519 -f $OBJ/sigca-key -C "CA" -N '' \
+ || fatal "couldn't generate key"
+CA_PRIV=$OBJ/sigca-key
+CA_PUB=$OBJ/sigca-key.pub
+
+trace "start agent"
+eval `${SSHAGENT} ${EXTRA_AGENT_ARGS} -s` > /dev/null
+r=$?
+if [ $r -ne 0 ]; then
+ fatal "could not start ssh-agent: exit code $r"
+fi
+
+SIGNKEYS="$SSH_KEYTYPES"
+verbose "$tid: make certificates"
+for t in $SSH_KEYTYPES ; do
+ ${SSHKEYGEN} -q -s $CA_PRIV -z $$ \
+ -I "regress signature key for $USER" \
+ -V "19840101:19860101" \
+ -n $sig_principal $OBJ/${t} || \
+ fatal "couldn't sign ${t}"
+ SIGNKEYS="$SIGNKEYS ${t}-cert.pub"
+done
+
+for t in $SIGNKEYS; do
+ verbose "$tid: check signature for $t"
+ keybase=`basename $t .pub`
+ privkey=${OBJ}/`basename $t -cert.pub`
+ sigfile=${OBJ}/sshsig-${keybase}.sig
+ sigfile_agent=${OBJ}/sshsig-agent-${keybase}.sig
+ pubkey=${OBJ}/${keybase}.pub
+ cert=${OBJ}/${keybase}-cert.pub
+ sigfile_cert=${OBJ}/sshsig-${keybase}-cert.sig
+
+ ${SSHKEYGEN} -vvv -Y sign -f ${OBJ}/$t -n $sig_namespace \
+ -Ohashalg=sha1 < $DATA > $sigfile 2>/dev/null && \
+ fail "sign using $t with bad hash algorithm succeeded"
+
+ for h in default sha256 sha512 ; do
+ case "$h" in
+ default) hashalg_arg="" ;;
+ *) hashalg_arg="-Ohashalg=$h" ;;
+ esac
+ ${SSHKEYGEN} -vvv -Y sign -f ${OBJ}/$t -n $sig_namespace \
+ $hashalg_arg < $DATA > $sigfile 2>/dev/null || \
+ fail "sign using $t / $h failed"
+ (printf "$sig_principal " ; cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 || \
+ fail "failed signature for $t / $h key"
+ done
+
+ (printf "$sig_principal namespaces=\"$sig_namespace,whatever\" ";
+ cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 || \
+ fail "failed signature for $t key w/ limited namespace"
+
+ (printf "$sig_principal namespaces=\"$sig_namespace,whatever\" ";
+ cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -q -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -O print-pubkey \
+ < $DATA | cut -d' ' -f1-2 > ${OBJ}/${keybase}-fromsig.pub || \
+ fail "failed signature for $t key w/ print-pubkey"
+ cut -d' ' -f1-2 ${OBJ}/${keybase}.pub > ${OBJ}/${keybase}-strip.pub
+ diff -r ${OBJ}/${keybase}-strip.pub ${OBJ}/${keybase}-fromsig.pub || \
+ fail "print-pubkey differs from signature key"
+
+ # Invalid option
+ (printf "$sig_principal octopus " ; cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t key with bad signers option"
+
+ # Wrong key trusted.
+ (printf "$sig_principal " ; cat $WRONG) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t key with wrong key trusted"
+
+ # incorrect data
+ (printf "$sig_principal " ; cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ < $DATA2 >/dev/null 2>&1 && \
+ fail "passed signature for wrong data with $t key"
+
+ # wrong principal in signers
+ (printf "josef.k@example.com " ; cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t key with wrong principal"
+
+ # wrong namespace
+ (printf "$sig_principal " ; cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n COWS_COWS_COWS \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t key with wrong namespace"
+
+ # namespace excluded by option
+ (printf "$sig_principal namespaces=\"whatever\" " ;
+ cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t key with excluded namespace"
+
+ ( printf "$sig_principal " ;
+ printf "valid-after=\"19800101\",valid-before=\"19900101\" " ;
+ cat $pubkey) > $OBJ/allowed_signers
+
+ # key lifespan valid
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -Overify-time=19850101 \
+ < $DATA >/dev/null 2>&1 || \
+ fail "failed signature for $t key with valid expiry interval"
+ # key not yet valid
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -Overify-time=19790101 \
+ < $DATA >/dev/null 2>&1 && \
+ fail "failed signature for $t not-yet-valid key"
+ # key expired
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -Overify-time=19910101 \
+ < $DATA >/dev/null 2>&1 && \
+ fail "failed signature for $t with expired key"
+ # NB. assumes we're not running this test in the 1980s
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 && \
+ fail "failed signature for $t with expired key"
+
+ # key lifespan valid
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -Overify-time="19850101" \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 || \
+ fail "failed find-principals for $t key with valid expiry interval"
+ # key not yet valid
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -Overify-time="19790101" \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 && \
+ fail "failed find-principals for $t not-yet-valid key"
+ # key expired
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -Overify-time="19990101" \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 && \
+ fail "failed find-principals for $t with expired key"
+ # NB. assumes we're not running this test in the 1980s
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 && \
+ fail "failed find-principals for $t with expired key"
+
+ # public key in revoked keys file
+ cat $pubkey > $OBJ/revoked_keys
+ (printf "$sig_principal namespaces=\"whatever\" " ;
+ cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -r $OBJ/revoked_keys \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t key, but key is in revoked_keys"
+
+ # public key not revoked, but others are present in revoked_keysfile
+ cat $WRONG > $OBJ/revoked_keys
+ (printf "$sig_principal " ; cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -r $OBJ/revoked_keys \
+ < $DATA >/dev/null 2>&1 || \
+ fail "couldn't verify signature for $t key, but key not in revoked_keys"
+
+ # check-novalidate with valid data
+ ${SSHKEYGEN} -vvv -Y check-novalidate -s $sigfile -n $sig_namespace \
+ < $DATA >/dev/null 2>&1 || \
+ fail "failed to check valid signature for $t key"
+
+ # check-novalidate with invalid data
+ ${SSHKEYGEN} -vvv -Y check-novalidate -s $sigfile -n $sig_namespace \
+ < $DATA2 >/dev/null 2>&1 && \
+ fail "succeeded checking signature for $t key with invalid data"
+
+ # find-principals with valid public key
+ (printf "$sig_principal " ; cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile -f $OBJ/allowed_signers >/dev/null 2>&1 || \
+ fail "failed to find valid principals in allowed_signers"
+
+ # find-principals with wrong key not in allowed_signers
+ (printf "$sig_principal " ; cat $WRONG) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile -f $OBJ/allowed_signers >/dev/null 2>&1 && \
+ fail "succeeded finding principal with invalid signers file"
+
+ # find-principals with a configured namespace but none on command-line
+ (printf "$sig_principal " ;
+ printf "namespaces=\"test1,test2\" ";
+ cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 || \
+ fail "failed finding principal when namespaces are configured"
+
+ # Check signing keys using ssh-agent.
+ ${SSHADD} -D >/dev/null 2>&1 # Remove all previously-loaded keys.
+ ${SSHADD} ${privkey} > /dev/null 2>&1 || fail "ssh-add failed"
+
+ # Move private key to ensure agent key is used
+ mv ${privkey} ${privkey}.tmp
+
+ ${SSHKEYGEN} -vvv -Y sign -f $pubkey -n $sig_namespace \
+ < $DATA > $sigfile_agent 2>/dev/null || \
+ fail "ssh-agent based sign using $pubkey failed"
+ ${SSHKEYGEN} -vvv -Y check-novalidate -s $sigfile_agent \
+ -n $sig_namespace < $DATA >/dev/null 2>&1 || \
+ fail "failed to check valid signature for $t key"
+
+ # Move private key back
+ mv ${privkey}.tmp ${privkey}
+
+ # Duplicate principals & keys in allowed_signers but with different validities
+ ( printf "$sig_principal " ;
+ printf "valid-after=\"19800101\",valid-before=\"19900101\" " ;
+ cat $pubkey;
+ printf "${sig_principal} " ;
+ printf "valid-after=\"19850101\",valid-before=\"20000101\" " ;
+ cat $pubkey) > $OBJ/allowed_signers
+
+ # find-principals outside of any validity lifespan
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -Overify-time="20100101" \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 && \
+ fail "succeeded find-principals for $t verify-time outside of validity"
+ # find-principals matching only the first lifespan
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -Overify-time="19830101" \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 || \
+ fail "failed find-principals for $t verify-time within first span"
+ # find-principals matching both lifespans
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -Overify-time="19880101" \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 || \
+ fail "failed find-principals for $t verify-time within both spans"
+ # find-principals matching only the second lifespan
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -Overify-time="19950101" \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 || \
+ fail "failed find-principals for $t verify-time within second span"
+
+ # verify outside of any validity lifespan
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -Overify-time="20100101" -I $sig_principal \
+ -r $OBJ/revoked_keys -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 && \
+ fail "succeeded verify for $t verify-time outside of validity"
+ # verify matching only the first lifespan
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -Overify-time="19830101" -I $sig_principal \
+ -r $OBJ/revoked_keys -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 || \
+ fail "failed verify for $t verify-time within first span"
+ # verify matching both lifespans
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -Overify-time="19880101" -I $sig_principal \
+ -r $OBJ/revoked_keys -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 || \
+ fail "failed verify for $t verify-time within both spans"
+ # verify matching only the second lifespan
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -Overify-time="19950101" -I $sig_principal \
+ -r $OBJ/revoked_keys -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 || \
+ fail "failed verify for $t verify-time within second span"
+
+ # Remaining tests are for certificates only.
+ case "$keybase" in
+ *-cert) ;;
+ *) continue ;;
+ esac
+
+ # Check key lifespan on find-principals when using the CA
+ ( printf "$sig_principal " ;
+ printf "cert-authority,valid-after=\"19800101\",valid-before=\"19900101\" ";
+ cat $CA_PUB) > $OBJ/allowed_signers
+ # key lifespan valid
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -Overify-time="19850101" \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 || \
+ fail "failed find-principals for $t key with valid expiry interval"
+ # key not yet valid
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -Overify-time="19790101" \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 && \
+ fail "failed find-principals for $t not-yet-valid key"
+ # key expired
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -Overify-time="19990101" \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 && \
+ fail "failed find-principals for $t with expired key"
+ # NB. assumes we're not running this test in the 1980s
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 && \
+ fail "failed find-principals for $t with expired key"
+
+ # correct CA key
+ (printf "$sig_principal cert-authority " ;
+ cat $CA_PUB) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -Overify-time=19850101 \
+ < $DATA >/dev/null 2>&1 || \
+ fail "failed signature for $t cert"
+
+ # find-principals
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -Overify-time=19850101 \
+ -f $OBJ/allowed_signers >/dev/null 2>&1 || \
+ fail "failed find-principals for $t with ca key"
+
+ # CA with wildcard principal
+ (printf "*@example.com cert-authority " ;
+ cat $CA_PUB) > $OBJ/allowed_signers
+ # find-principals CA with wildcard principal
+ ${SSHKEYGEN} -vvv -Y find-principals -s $sigfile \
+ -Overify-time=19850101 \
+ -f $OBJ/allowed_signers 2>/dev/null | \
+ fgrep "$sig_principal" >/dev/null || \
+ fail "failed find-principals for $t with ca key using wildcard principal"
+
+ # verify CA with wildcard principal
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -Overify-time=19850101 \
+ < $DATA >/dev/null 2>&1 || \
+ fail "failed signature for $t cert using wildcard principal"
+
+ # signing key listed as cert-authority
+ (printf "$sig_principal cert-authority " ;
+ cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature with $t key listed as CA"
+
+ # CA key not flagged cert-authority
+ (printf "$sig_principal " ; cat $CA_PUB) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t cert with CA not marked"
+
+ # mismatch between cert principal and file
+ (printf "josef.k@example.com cert-authority " ;
+ cat $CA_PUB) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t cert with wrong principal"
+
+ # Cert valid but CA revoked
+ cat $CA_PUB > $OBJ/revoked_keys
+ (printf "$sig_principal " ; cat $pubkey) > $OBJ/allowed_signers
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -r $OBJ/revoked_keys \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t key, but CA key in revoked_keys"
+
+ # Set lifespan of CA key and verify signed user certs behave accordingly
+ ( printf "$sig_principal " ;
+ printf "cert-authority,valid-after=\"19800101\",valid-before=\"19900101\" " ;
+ cat $CA_PUB) > $OBJ/allowed_signers
+
+ # CA key lifespan valid
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -Overify-time=19850101 \
+ < $DATA >/dev/null 2>&1 >/dev/null 2>&1 || \
+ fail "failed signature for $t key with valid CA expiry interval"
+ # CA lifespan is valid but user key not yet valid
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -Overify-time=19810101 \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t key with valid CA expiry interval but not yet valid cert"
+ # CA lifespan is valid but user key expired
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -Overify-time=19890101 \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t key with valid CA expiry interval but expired cert"
+ # CA key not yet valid
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -Overify-time=19790101 \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t not-yet-valid CA key"
+ # CA key expired
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -Overify-time=19910101 \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t with expired CA key"
+ # NB. assumes we're not running this test in the 1980s
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t with expired CA key"
+
+ # Set lifespan of CA outside of the cert validity
+ ( printf "$sig_principal " ;
+ printf "cert-authority,valid-after=\"19800101\",valid-before=\"19820101\" " ;
+ cat $CA_PUB) > $OBJ/allowed_signers
+ # valid cert validity but expired CA
+ ${SSHKEYGEN} -vvv -Y verify -s $sigfile -n $sig_namespace \
+ -I $sig_principal -f $OBJ/allowed_signers \
+ -Overify-time=19840101 \
+ < $DATA >/dev/null 2>&1 && \
+ fail "accepted signature for $t key with expired CA but valid cert"
+
+done
+
+# Test key independant match-principals
+(
+ printf "principal1 " ; cat $pubkey;
+ printf "princi* " ; cat $pubkey;
+ printf "unique " ; cat $pubkey;
+) > $OBJ/allowed_signers
+
+verbose "$tid: match principals"
+${SSHKEYGEN} -Y match-principals -f $OBJ/allowed_signers -I "unique" | \
+ fgrep "unique" >/dev/null || \
+ fail "faild to match static principal"
+
+${SSHKEYGEN} -Y match-principals -f $OBJ/allowed_signers -I "princip" | \
+ fgrep "princi*" >/dev/null || \
+ fail "faild to match wildcard principal"
+
+${SSHKEYGEN} -Y match-principals -f $OBJ/allowed_signers -I "principal1" | \
+ fgrep -e "principal1" -e "princi*" >/dev/null || \
+ fail "faild to match static and wildcard principal"
+verbose "$tid: nomatch principals"
+for x in princ prince unknown ; do
+ ${SSHKEYGEN} -Y match-principals -f $OBJ/allowed_signers \
+ -I $x >/dev/null 2>&1 && \
+ fail "succeeded to match unknown principal \"$x\""
+done
+
+trace "kill agent"
+${SSHAGENT} -k > /dev/null
+
diff --git a/regress/stderr-after-eof.sh b/regress/stderr-after-eof.sh
new file mode 100644
index 0000000..9065245
--- /dev/null
+++ b/regress/stderr-after-eof.sh
@@ -0,0 +1,24 @@
+# $OpenBSD: stderr-after-eof.sh,v 1.3 2017/04/30 23:34:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="stderr data after eof"
+
+# setup data
+rm -f ${DATA} ${COPY}
+cp /dev/null ${DATA}
+for i in 1 2 3 4 5 6; do
+ (date;echo $i) | md5 >> ${DATA}
+done
+
+${SSH} -F $OBJ/ssh_proxy otherhost \
+ exec sh -c \'"exec > /dev/null; sleep 2; cat ${DATA} 1>&2 $s"\' \
+ 2> ${COPY}
+r=$?
+if [ $r -ne 0 ]; then
+ fail "ssh failed with exit code $r"
+fi
+egrep 'Disconnecting: Received extended_data after EOF' ${COPY} &&
+ fail "ext data received after eof"
+cmp ${DATA} ${COPY} || fail "stderr corrupt"
+
+rm -f ${DATA} ${COPY}
diff --git a/regress/stderr-data.sh b/regress/stderr-data.sh
new file mode 100644
index 0000000..0ceb72b
--- /dev/null
+++ b/regress/stderr-data.sh
@@ -0,0 +1,27 @@
+# $OpenBSD: stderr-data.sh,v 1.5 2017/04/30 23:34:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="stderr data transfer"
+
+for n in '' -n; do
+ verbose "test $tid: ($n)"
+ ${SSH} $n -F $OBJ/ssh_proxy otherhost exec \
+ sh -c \'"exec > /dev/null; sleep 3; cat ${DATA} 1>&2 $s"\' \
+ 2> ${COPY}
+ r=$?
+ if [ $r -ne 0 ]; then
+ fail "ssh failed with exit code $r"
+ fi
+ cmp ${DATA} ${COPY} || fail "stderr corrupt"
+ rm -f ${COPY}
+
+ ${SSH} $n -F $OBJ/ssh_proxy otherhost exec \
+ sh -c \'"echo a; exec > /dev/null; sleep 3; cat ${DATA} 1>&2 $s"\' \
+ > /dev/null 2> ${COPY}
+ r=$?
+ if [ $r -ne 0 ]; then
+ fail "ssh failed with exit code $r"
+ fi
+ cmp ${DATA} ${COPY} || fail "stderr corrupt"
+ rm -f ${COPY}
+done
diff --git a/regress/t11.ok b/regress/t11.ok
new file mode 100644
index 0000000..1925bb4
--- /dev/null
+++ b/regress/t11.ok
@@ -0,0 +1 @@
+SHA256:4w1rnrek3klTJOTVhwuCIFd5k+pq9Bfo5KTxxb8BqbY
diff --git a/regress/t4.ok b/regress/t4.ok
new file mode 100644
index 0000000..4631ea8
--- /dev/null
+++ b/regress/t4.ok
@@ -0,0 +1 @@
+MD5:3b:dd:44:e9:49:18:84:95:f1:e7:33:6b:9d:93:b1:36
diff --git a/regress/t5.ok b/regress/t5.ok
new file mode 100644
index 0000000..bd622f3
--- /dev/null
+++ b/regress/t5.ok
@@ -0,0 +1 @@
+xokes-lylis-byleh-zebib-kalus-bihas-tevah-haroz-suhar-foved-noxex
diff --git a/regress/test-exec.sh b/regress/test-exec.sh
new file mode 100644
index 0000000..df43f02
--- /dev/null
+++ b/regress/test-exec.sh
@@ -0,0 +1,816 @@
+# $OpenBSD: test-exec.sh,v 1.94 2023/01/13 04:47:34 dtucker Exp $
+# Placed in the Public Domain.
+
+#SUDO=sudo
+
+if [ ! -z "$TEST_SSH_ELAPSED_TIMES" ]; then
+ STARTTIME=`date '+%s'`
+fi
+
+if [ ! -z "$TEST_SSH_PORT" ]; then
+ PORT="$TEST_SSH_PORT"
+else
+ PORT=4242
+fi
+
+OBJ=$1
+if [ "x$OBJ" = "x" ]; then
+ echo '$OBJ not defined'
+ exit 2
+fi
+if [ ! -d $OBJ ]; then
+ echo "not a directory: $OBJ"
+ exit 2
+fi
+SCRIPT=$2
+if [ "x$SCRIPT" = "x" ]; then
+ echo '$SCRIPT not defined'
+ exit 2
+fi
+if [ ! -f $SCRIPT ]; then
+ echo "not a file: $SCRIPT"
+ exit 2
+fi
+if $TEST_SHELL -n $SCRIPT; then
+ true
+else
+ echo "syntax error in $SCRIPT"
+ exit 2
+fi
+unset SSH_AUTH_SOCK
+
+# Portable-specific settings.
+
+if [ -x /usr/ucb/whoami ]; then
+ USER=`/usr/ucb/whoami`
+elif whoami >/dev/null 2>&1; then
+ USER=`whoami`
+elif logname >/dev/null 2>&1; then
+ USER=`logname`
+else
+ USER=`id -un`
+fi
+if test -z "$LOGNAME"; then
+ LOGNAME="${USER}"
+ export LOGNAME
+fi
+
+# Unbreak GNU head(1)
+_POSIX2_VERSION=199209
+export _POSIX2_VERSION
+
+case `uname -s 2>/dev/null` in
+OSF1*)
+ BIN_SH=xpg4
+ export BIN_SH
+ ;;
+CYGWIN*)
+ os=cygwin
+ ;;
+esac
+
+# If configure tells us to use a different egrep, create a wrapper function
+# to call it. This means we don't need to change all the tests that depend
+# on a good implementation.
+if test "x${EGREP}" != "x"; then
+ egrep ()
+{
+ ${EGREP} "$@"
+}
+fi
+
+SRC=`dirname ${SCRIPT}`
+
+# defaults
+SSH=ssh
+SSHD=sshd
+SSHAGENT=ssh-agent
+SSHADD=ssh-add
+SSHKEYGEN=ssh-keygen
+SSHKEYSCAN=ssh-keyscan
+SFTP=sftp
+SFTPSERVER=/usr/libexec/openssh/sftp-server
+SCP=scp
+
+# Set by make_tmpdir() on demand (below).
+SSH_REGRESS_TMP=
+
+# Interop testing
+PLINK=plink
+PUTTYGEN=puttygen
+CONCH=conch
+
+# Tools used by multiple tests
+NC=$OBJ/netcat
+OPENSSL_BIN="${OPENSSL_BIN:-openssl}"
+
+if [ "x$TEST_SSH_SSH" != "x" ]; then
+ SSH="${TEST_SSH_SSH}"
+fi
+if [ "x$TEST_SSH_SSHD" != "x" ]; then
+ SSHD="${TEST_SSH_SSHD}"
+fi
+if [ "x$TEST_SSH_SSHAGENT" != "x" ]; then
+ SSHAGENT="${TEST_SSH_SSHAGENT}"
+fi
+if [ "x$TEST_SSH_SSHADD" != "x" ]; then
+ SSHADD="${TEST_SSH_SSHADD}"
+fi
+if [ "x$TEST_SSH_SSHKEYGEN" != "x" ]; then
+ SSHKEYGEN="${TEST_SSH_SSHKEYGEN}"
+fi
+if [ "x$TEST_SSH_SSHKEYSCAN" != "x" ]; then
+ SSHKEYSCAN="${TEST_SSH_SSHKEYSCAN}"
+fi
+if [ "x$TEST_SSH_SFTP" != "x" ]; then
+ SFTP="${TEST_SSH_SFTP}"
+fi
+if [ "x$TEST_SSH_SFTPSERVER" != "x" ]; then
+ SFTPSERVER="${TEST_SSH_SFTPSERVER}"
+fi
+if [ "x$TEST_SSH_SCP" != "x" ]; then
+ SCP="${TEST_SSH_SCP}"
+fi
+if [ "x$TEST_SSH_PLINK" != "x" ]; then
+ # Find real binary, if it exists
+ case "${TEST_SSH_PLINK}" in
+ /*) PLINK="${TEST_SSH_PLINK}" ;;
+ *) PLINK=`which ${TEST_SSH_PLINK} 2>/dev/null` ;;
+ esac
+fi
+if [ "x$TEST_SSH_PUTTYGEN" != "x" ]; then
+ # Find real binary, if it exists
+ case "${TEST_SSH_PUTTYGEN}" in
+ /*) PUTTYGEN="${TEST_SSH_PUTTYGEN}" ;;
+ *) PUTTYGEN=`which ${TEST_SSH_PUTTYGEN} 2>/dev/null` ;;
+ esac
+fi
+if [ "x$TEST_SSH_CONCH" != "x" ]; then
+ # Find real binary, if it exists
+ case "${TEST_SSH_CONCH}" in
+ /*) CONCH="${TEST_SSH_CONCH}" ;;
+ *) CONCH=`which ${TEST_SSH_CONCH} 2>/dev/null` ;;
+ esac
+fi
+if [ "x$TEST_SSH_PKCS11_HELPER" != "x" ]; then
+ SSH_PKCS11_HELPER="${TEST_SSH_PKCS11_HELPER}"
+fi
+if [ "x$TEST_SSH_SK_HELPER" != "x" ]; then
+ SSH_SK_HELPER="${TEST_SSH_SK_HELPER}"
+fi
+if [ "x$TEST_SSH_OPENSSL" != "x" ]; then
+ OPENSSL_BIN="${TEST_SSH_OPENSSL}"
+fi
+
+# Path to sshd must be absolute for rexec
+case "$SSHD" in
+/*) ;;
+*) SSHD=`which $SSHD` ;;
+esac
+
+case "$SSHAGENT" in
+/*) ;;
+*) SSHAGENT=`which $SSHAGENT` ;;
+esac
+
+# Record the actual binaries used.
+SSH_BIN=${SSH}
+SSHD_BIN=${SSHD}
+SSHAGENT_BIN=${SSHAGENT}
+SSHADD_BIN=${SSHADD}
+SSHKEYGEN_BIN=${SSHKEYGEN}
+SSHKEYSCAN_BIN=${SSHKEYSCAN}
+SFTP_BIN=${SFTP}
+SFTPSERVER_BIN=${SFTPSERVER}
+SCP_BIN=${SCP}
+
+if [ "x$USE_VALGRIND" != "x" ]; then
+ rm -rf $OBJ/valgrind-out $OBJ/valgrind-vgdb
+ mkdir -p $OBJ/valgrind-out $OBJ/valgrind-vgdb
+ # When using sudo ensure low-priv tests can write pipes and logs.
+ if [ "x$SUDO" != "x" ]; then
+ chmod 777 $OBJ/valgrind-out $OBJ/valgrind-vgdb
+ fi
+ VG_TEST=`basename $SCRIPT .sh`
+
+ # Some tests are difficult to fix.
+ case "$VG_TEST" in
+ reexec)
+ VG_SKIP=1 ;;
+ sftp-chroot)
+ if [ "x${SUDO}" != "x" ]; then
+ VG_SKIP=1
+ fi ;;
+ esac
+
+ if [ x"$VG_SKIP" = "x" ]; then
+ VG_LEAK="--leak-check=no"
+ if [ x"$VALGRIND_CHECK_LEAKS" != "x" ]; then
+ VG_LEAK="--leak-check=full"
+ fi
+ VG_IGNORE="/bin/*,/sbin/*,/usr/*,/var/*"
+ VG_LOG="$OBJ/valgrind-out/${VG_TEST}."
+ VG_OPTS="--track-origins=yes $VG_LEAK"
+ VG_OPTS="$VG_OPTS --trace-children=yes"
+ VG_OPTS="$VG_OPTS --trace-children-skip=${VG_IGNORE}"
+ VG_OPTS="$VG_OPTS --vgdb-prefix=$OBJ/valgrind-vgdb/"
+ VG_PATH="valgrind"
+ if [ "x$VALGRIND_PATH" != "x" ]; then
+ VG_PATH="$VALGRIND_PATH"
+ fi
+ VG="$VG_PATH $VG_OPTS"
+ SSH="$VG --log-file=${VG_LOG}ssh.%p $SSH"
+ SSHD="$VG --log-file=${VG_LOG}sshd.%p $SSHD"
+ SSHAGENT="$VG --log-file=${VG_LOG}ssh-agent.%p $SSHAGENT"
+ SSHADD="$VG --log-file=${VG_LOG}ssh-add.%p $SSHADD"
+ SSHKEYGEN="$VG --log-file=${VG_LOG}ssh-keygen.%p $SSHKEYGEN"
+ SSHKEYSCAN="$VG --log-file=${VG_LOG}ssh-keyscan.%p $SSHKEYSCAN"
+ SFTP="$VG --log-file=${VG_LOG}sftp.%p ${SFTP}"
+ SCP="$VG --log-file=${VG_LOG}scp.%p $SCP"
+ cat > $OBJ/valgrind-sftp-server.sh << EOF
+#!/bin/sh
+exec $VG --log-file=${VG_LOG}sftp-server.%p $SFTPSERVER "\$@"
+EOF
+ chmod a+rx $OBJ/valgrind-sftp-server.sh
+ SFTPSERVER="$OBJ/valgrind-sftp-server.sh"
+ fi
+fi
+
+# Logfiles.
+# SSH_LOGFILE should be the debug output of ssh(1) only
+# SSHD_LOGFILE should be the debug output of sshd(8) only
+# REGRESS_LOGFILE is the output of the test itself stdout and stderr
+if [ "x$TEST_SSH_LOGFILE" = "x" ]; then
+ TEST_SSH_LOGFILE=$OBJ/ssh.log
+fi
+if [ "x$TEST_SSHD_LOGFILE" = "x" ]; then
+ TEST_SSHD_LOGFILE=$OBJ/sshd.log
+fi
+if [ "x$TEST_REGRESS_LOGFILE" = "x" ]; then
+ TEST_REGRESS_LOGFILE=$OBJ/regress.log
+fi
+
+# If set, keep track of successful tests and skip them them if we've
+# previously completed that test.
+if [ "x$TEST_REGRESS_CACHE_DIR" != "x" ]; then
+ if [ ! -d "$TEST_REGRESS_CACHE_DIR" ]; then
+ mkdir -p "$TEST_REGRESS_CACHE_DIR"
+ fi
+ TEST="`basename $SCRIPT .sh`"
+ CACHE="${TEST_REGRESS_CACHE_DIR}/${TEST}.cache"
+ for i in ${SSH} ${SSHD} ${SSHAGENT} ${SSHADD} ${SSHKEYGEN} ${SCP} \
+ ${SFTP} ${SFTPSERVER} ${SSHKEYSCAN}; do
+ case $i in
+ /*) bin="$i" ;;
+ *) bin="`which $i`" ;;
+ esac
+ if [ "$bin" -nt "$CACHE" ]; then
+ rm -f "$CACHE"
+ fi
+ done
+ if [ -f "$CACHE" ]; then
+ echo ok cached $CACHE
+ exit 0
+ fi
+fi
+
+# truncate logfiles
+>$TEST_SSH_LOGFILE
+>$TEST_SSHD_LOGFILE
+>$TEST_REGRESS_LOGFILE
+
+# Create wrapper ssh with logging. We can't just specify "SSH=ssh -E..."
+# because sftp and scp don't handle spaces in arguments. scp and sftp like
+# to use -q so we remove those to preserve our debug logging. In the rare
+# instance where -q is desirable -qq is equivalent and is not removed.
+SSHLOGWRAP=$OBJ/ssh-log-wrapper.sh
+cat >$SSHLOGWRAP <<EOD
+#!/bin/sh
+echo "Executing: ${SSH} \$@" >>${TEST_SSH_LOGFILE}
+for i in "\$@";do shift;case "\$i" in -q):;; *) set -- "\$@" "\$i";;esac;done
+exec ${SSH} -E${TEST_SSH_LOGFILE} "\$@"
+EOD
+
+chmod a+rx $OBJ/ssh-log-wrapper.sh
+REAL_SSH="$SSH"
+REAL_SSHD="$SSHD"
+SSH="$SSHLOGWRAP"
+
+# Some test data. We make a copy because some tests will overwrite it.
+# The tests may assume that $DATA exists and is writable and $COPY does
+# not exist. Tests requiring larger data files can call increase_datafile_size
+# [kbytes] to ensure the file is at least that large.
+DATANAME=data
+DATA=$OBJ/${DATANAME}
+cat ${SSHAGENT_BIN} >${DATA}
+chmod u+w ${DATA}
+COPY=$OBJ/copy
+rm -f ${COPY}
+
+increase_datafile_size()
+{
+ while [ `du -k ${DATA} | cut -f1` -lt $1 ]; do
+ cat ${SSHAGENT_BIN} >>${DATA}
+ done
+}
+
+# these should be used in tests
+export SSH SSHD SSHAGENT SSHADD SSHKEYGEN SSHKEYSCAN SFTP SFTPSERVER SCP
+export SSH_PKCS11_HELPER SSH_SK_HELPER
+#echo $SSH $SSHD $SSHAGENT $SSHADD $SSHKEYGEN $SSHKEYSCAN $SFTP $SFTPSERVER $SCP
+
+# Portable specific functions
+which()
+{
+ saved_IFS="$IFS"
+ IFS=":"
+ for i in $PATH
+ do
+ if [ -x $i/$1 ]; then
+ IFS="$saved_IFS"
+ echo "$i/$1"
+ return 0
+ fi
+ done
+ IFS="$saved_IFS"
+ echo "$i/$1"
+ return 1
+}
+
+have_prog()
+{
+ which "$1" >/dev/null 2>&1
+ return $?
+}
+
+jot() {
+ awk "BEGIN { for (i = $2; i < $2 + $1; i++) { printf \"%d\n\", i } exit }"
+}
+if [ ! -x "`which rev`" ]; then
+rev()
+{
+ awk '{for (i=length; i>0; i--) printf "%s", substr($0, i, 1); print ""}'
+}
+fi
+
+# Check whether preprocessor symbols are defined in config.h.
+config_defined ()
+{
+ str=$1
+ while test "x$2" != "x" ; do
+ str="$str|$2"
+ shift
+ done
+ egrep "^#define.*($str)" ${BUILDDIR}/config.h >/dev/null 2>&1
+}
+
+md5 () {
+ if have_prog md5sum; then
+ md5sum
+ elif have_prog openssl; then
+ openssl md5
+ elif have_prog cksum; then
+ cksum
+ elif have_prog sum; then
+ sum
+ elif [ -x ${OPENSSL_BIN} ]; then
+ ${OPENSSL_BIN} md5
+ else
+ wc -c
+ fi
+}
+
+# Some platforms don't have hostname at all, but on others uname -n doesn't
+# provide the fully qualified name we need, so in the former case we create
+# our own hostname function.
+if ! have_prog hostname; then
+ hostname() {
+ uname -n
+ }
+fi
+
+make_tmpdir ()
+{
+ SSH_REGRESS_TMP="$($OBJ/mkdtemp openssh-XXXXXXXX)" || \
+ fatal "failed to create temporary directory"
+}
+# End of portable specific functions
+
+stop_sshd ()
+{
+ if [ -f $PIDFILE ]; then
+ pid=`$SUDO cat $PIDFILE`
+ if [ "X$pid" = "X" ]; then
+ echo no sshd running
+ else
+ if [ $pid -lt 2 ]; then
+ echo bad pid for sshd: $pid
+ else
+ $SUDO kill $pid
+ trace "wait for sshd to exit"
+ i=0;
+ while [ -f $PIDFILE -a $i -lt 5 ]; do
+ i=`expr $i + 1`
+ sleep $i
+ done
+ if test -f $PIDFILE; then
+ if $SUDO kill -0 $pid; then
+ echo "sshd didn't exit " \
+ "port $PORT pid $pid"
+ else
+ echo "sshd died without cleanup"
+ fi
+ exit 1
+ fi
+ fi
+ fi
+ fi
+}
+
+# helper
+cleanup ()
+{
+ if [ "x$SSH_PID" != "x" ]; then
+ if [ $SSH_PID -lt 2 ]; then
+ echo bad pid for ssh: $SSH_PID
+ else
+ kill $SSH_PID
+ fi
+ fi
+ if [ "x$SSH_REGRESS_TMP" != "x" ]; then
+ rm -rf "$SSH_REGRESS_TMP"
+ fi
+ stop_sshd
+ if [ ! -z "$TEST_SSH_ELAPSED_TIMES" ]; then
+ now=`date '+%s'`
+ elapsed=$(($now - $STARTTIME))
+ echo elapsed $elapsed `basename $SCRIPT .sh`
+ fi
+}
+
+start_debug_log ()
+{
+ echo "trace: $@" >$TEST_REGRESS_LOGFILE
+ echo "trace: $@" >$TEST_SSH_LOGFILE
+ echo "trace: $@" >$TEST_SSHD_LOGFILE
+}
+
+save_debug_log ()
+{
+ echo $@ >>$TEST_REGRESS_LOGFILE
+ echo $@ >>$TEST_SSH_LOGFILE
+ echo $@ >>$TEST_SSHD_LOGFILE
+ (cat $TEST_REGRESS_LOGFILE; echo) >>$OBJ/failed-regress.log
+ (cat $TEST_SSH_LOGFILE; echo) >>$OBJ/failed-ssh.log
+ (cat $TEST_SSHD_LOGFILE; echo) >>$OBJ/failed-sshd.log
+}
+
+trace ()
+{
+ start_debug_log $@
+ if [ "X$TEST_SSH_TRACE" = "Xyes" ]; then
+ echo "$@"
+ fi
+}
+
+verbose ()
+{
+ start_debug_log $@
+ if [ "X$TEST_SSH_QUIET" != "Xyes" ]; then
+ echo "$@"
+ fi
+}
+
+fail ()
+{
+ save_debug_log "FAIL: $@"
+ RESULT=1
+ echo "$@"
+ if test "x$TEST_SSH_FAIL_FATAL" != "x" ; then
+ cleanup
+ exit $RESULT
+ fi
+}
+
+fatal ()
+{
+ save_debug_log "FATAL: $@"
+ printf "FATAL: "
+ fail "$@"
+ cleanup
+ exit $RESULT
+}
+
+# Skip remaining tests in script.
+skip ()
+{
+ echo "SKIPPED: $@"
+ cleanup
+ exit $RESULT
+}
+
+maybe_add_scp_path_to_sshd ()
+{
+ # If we're testing a non-installed scp, add its directory to sshd's
+ # PATH so we can test it. We don't do this for all tests as it
+ # breaks the SetEnv tests.
+ case "$SCP" in
+ /*) PATH_WITH_SCP="`dirname $SCP`:$PATH"
+ echo " SetEnv PATH='$PATH_WITH_SCP'" >>$OBJ/sshd_config
+ echo " SetEnv PATH='$PATH_WITH_SCP'" >>$OBJ/sshd_proxy ;;
+ esac
+}
+
+RESULT=0
+PIDFILE=$OBJ/pidfile
+
+trap fatal 3 2
+
+# create server config
+cat << EOF > $OBJ/sshd_config
+ StrictModes no
+ Port $PORT
+ AddressFamily inet
+ ListenAddress 127.0.0.1
+ #ListenAddress ::1
+ PidFile $PIDFILE
+ AuthorizedKeysFile $OBJ/authorized_keys_%u
+ LogLevel DEBUG3
+ AcceptEnv _XXX_TEST_*
+ AcceptEnv _XXX_TEST
+ Subsystem sftp $SFTPSERVER
+EOF
+
+# This may be necessary if /usr/src and/or /usr/obj are group-writable,
+# but if you aren't careful with permissions then the unit tests could
+# be abused to locally escalate privileges.
+if [ ! -z "$TEST_SSH_UNSAFE_PERMISSIONS" ]; then
+ echo " StrictModes no" >> $OBJ/sshd_config
+else
+ # check and warn if excessive permissions are likely to cause failures.
+ unsafe=""
+ dir="${OBJ}"
+ while test ${dir} != "/"; do
+ if test -d "${dir}" && ! test -h "${dir}"; then
+ perms=`ls -ld ${dir}`
+ case "${perms}" in
+ ?????w????*|????????w?*) unsafe="${unsafe} ${dir}" ;;
+ esac
+ fi
+ dir=`dirname ${dir}`
+ done
+ if ! test -z "${unsafe}"; then
+ cat <<EOD
+
+WARNING: Unsafe (group or world writable) directory permissions found:
+${unsafe}
+
+These could be abused to locally escalate privileges. If you are
+sure that this is not a risk (eg there are no other users), you can
+bypass this check by setting TEST_SSH_UNSAFE_PERMISSIONS=1
+
+EOD
+ fi
+fi
+
+if [ ! -z "$TEST_SSH_MODULI_FILE" ]; then
+ trace "adding modulifile='$TEST_SSH_MODULI_FILE' to sshd_config"
+ echo " ModuliFile '$TEST_SSH_MODULI_FILE'" >> $OBJ/sshd_config
+fi
+
+if [ ! -z "$TEST_SSH_SSHD_CONFOPTS" ]; then
+ trace "adding sshd_config option $TEST_SSH_SSHD_CONFOPTS"
+ echo "$TEST_SSH_SSHD_CONFOPTS" >> $OBJ/sshd_config
+fi
+
+# server config for proxy connects
+cp $OBJ/sshd_config $OBJ/sshd_proxy
+
+# allow group-writable directories in proxy-mode
+echo 'StrictModes no' >> $OBJ/sshd_proxy
+
+# create client config
+cat << EOF > $OBJ/ssh_config
+Host *
+ Hostname 127.0.0.1
+ HostKeyAlias localhost-with-alias
+ Port $PORT
+ User $USER
+ GlobalKnownHostsFile $OBJ/known_hosts
+ UserKnownHostsFile $OBJ/known_hosts
+ PubkeyAuthentication yes
+ ChallengeResponseAuthentication no
+ PasswordAuthentication no
+ BatchMode yes
+ StrictHostKeyChecking yes
+ LogLevel DEBUG3
+EOF
+
+if [ ! -z "$TEST_SSH_SSH_CONFOPTS" ]; then
+ trace "adding ssh_config option $TEST_SSH_SSH_CONFOPTS"
+ echo "$TEST_SSH_SSH_CONFOPTS" >> $OBJ/ssh_config
+fi
+
+rm -f $OBJ/known_hosts $OBJ/authorized_keys_$USER
+
+SSH_SK_PROVIDER=
+if ! config_defined ENABLE_SK; then
+ trace skipping sk-dummy
+elif [ -f "${SRC}/misc/sk-dummy/obj/sk-dummy.so" ] ; then
+ SSH_SK_PROVIDER="${SRC}/misc/sk-dummy/obj/sk-dummy.so"
+elif [ -f "${OBJ}/misc/sk-dummy/sk-dummy.so" ] ; then
+ SSH_SK_PROVIDER="${OBJ}/misc/sk-dummy/sk-dummy.so"
+elif [ -f "${SRC}/misc/sk-dummy/sk-dummy.so" ] ; then
+ SSH_SK_PROVIDER="${SRC}/misc/sk-dummy/sk-dummy.so"
+fi
+export SSH_SK_PROVIDER
+
+if ! test -z "$SSH_SK_PROVIDER"; then
+ EXTRA_AGENT_ARGS='-P/*' # XXX want realpath(1)...
+ echo "SecurityKeyProvider $SSH_SK_PROVIDER" >> $OBJ/ssh_config
+ echo "SecurityKeyProvider $SSH_SK_PROVIDER" >> $OBJ/sshd_config
+ echo "SecurityKeyProvider $SSH_SK_PROVIDER" >> $OBJ/sshd_proxy
+fi
+export EXTRA_AGENT_ARGS
+
+maybe_filter_sk() {
+ if test -z "$SSH_SK_PROVIDER" ; then
+ grep -v ^sk
+ else
+ cat
+ fi
+}
+
+SSH_KEYTYPES=`$SSH -Q key-plain | maybe_filter_sk`
+SSH_HOSTKEY_TYPES=`$SSH -Q key-plain | maybe_filter_sk`
+
+for t in ${SSH_KEYTYPES}; do
+ # generate user key
+ if [ ! -f $OBJ/$t ] || [ ${SSHKEYGEN_BIN} -nt $OBJ/$t ]; then
+ trace "generating key type $t"
+ rm -f $OBJ/$t
+ ${SSHKEYGEN} -q -N '' -t $t -f $OBJ/$t ||\
+ fail "ssh-keygen for $t failed"
+ else
+ trace "using cached key type $t"
+ fi
+
+ # setup authorized keys
+ cat $OBJ/$t.pub >> $OBJ/authorized_keys_$USER
+ echo IdentityFile $OBJ/$t >> $OBJ/ssh_config
+done
+
+for t in ${SSH_HOSTKEY_TYPES}; do
+ # known hosts file for client
+ (
+ printf 'localhost-with-alias,127.0.0.1,::1 '
+ cat $OBJ/$t.pub
+ ) >> $OBJ/known_hosts
+
+ # use key as host key, too
+ (umask 077; $SUDO cp $OBJ/$t $OBJ/host.$t)
+ echo HostKey $OBJ/host.$t >> $OBJ/sshd_config
+
+ # don't use SUDO for proxy connect
+ echo HostKey $OBJ/$t >> $OBJ/sshd_proxy
+done
+chmod 644 $OBJ/authorized_keys_$USER
+
+# Activate Twisted Conch tests if the binary is present
+REGRESS_INTEROP_CONCH=no
+if test -x "$CONCH" ; then
+ REGRESS_INTEROP_CONCH=yes
+fi
+
+# If PuTTY is present, new enough and we are running a PuTTY test, prepare
+# keys and configuration.
+REGRESS_INTEROP_PUTTY=no
+if test -x "$PUTTYGEN" -a -x "$PLINK" &&
+ "$PUTTYGEN" --help 2>&1 | grep -- --new-passphrase >/dev/null; then
+ REGRESS_INTEROP_PUTTY=yes
+fi
+case "$SCRIPT" in
+*putty*) ;;
+*) REGRESS_INTEROP_PUTTY=no ;;
+esac
+
+if test "$REGRESS_INTEROP_PUTTY" = "yes" ; then
+ mkdir -p ${OBJ}/.putty
+
+ # Add a PuTTY key to authorized_keys
+ rm -f ${OBJ}/putty.rsa2
+ if ! "$PUTTYGEN" -t rsa -o ${OBJ}/putty.rsa2 \
+ --random-device=/dev/urandom \
+ --new-passphrase /dev/null < /dev/null > /dev/null; then
+ echo "Your installed version of PuTTY is too old to support --new-passphrase, skipping test" >&2
+ exit 1
+ fi
+ "$PUTTYGEN" -O public-openssh ${OBJ}/putty.rsa2 \
+ >> $OBJ/authorized_keys_$USER
+
+ # Convert rsa2 host key to PuTTY format
+ cp $OBJ/ssh-rsa $OBJ/ssh-rsa_oldfmt
+ ${SSHKEYGEN} -p -N '' -m PEM -f $OBJ/ssh-rsa_oldfmt >/dev/null
+ ${SRC}/ssh2putty.sh 127.0.0.1 $PORT $OBJ/ssh-rsa_oldfmt > \
+ ${OBJ}/.putty/sshhostkeys
+ ${SRC}/ssh2putty.sh 127.0.0.1 22 $OBJ/ssh-rsa_oldfmt >> \
+ ${OBJ}/.putty/sshhostkeys
+ rm -f $OBJ/ssh-rsa_oldfmt
+
+ # Setup proxied session
+ mkdir -p ${OBJ}/.putty/sessions
+ rm -f ${OBJ}/.putty/sessions/localhost_proxy
+ echo "Protocol=ssh" >> ${OBJ}/.putty/sessions/localhost_proxy
+ echo "HostName=127.0.0.1" >> ${OBJ}/.putty/sessions/localhost_proxy
+ echo "PortNumber=$PORT" >> ${OBJ}/.putty/sessions/localhost_proxy
+ echo "ProxyMethod=5" >> ${OBJ}/.putty/sessions/localhost_proxy
+ echo "ProxyTelnetCommand=sh ${SRC}/sshd-log-wrapper.sh ${TEST_SSHD_LOGFILE} ${SSHD} -i -f $OBJ/sshd_proxy" >> ${OBJ}/.putty/sessions/localhost_proxy
+ echo "ProxyLocalhost=1" >> ${OBJ}/.putty/sessions/localhost_proxy
+
+ PUTTYDIR=${OBJ}/.putty
+ export PUTTYDIR
+fi
+
+# create a proxy version of the client config
+(
+ cat $OBJ/ssh_config
+ echo proxycommand ${SUDO} env SSH_SK_HELPER=\"$SSH_SK_HELPER\" sh ${SRC}/sshd-log-wrapper.sh ${TEST_SSHD_LOGFILE} ${SSHD} -i -f $OBJ/sshd_proxy
+) > $OBJ/ssh_proxy
+
+# check proxy config
+${SSHD} -t -f $OBJ/sshd_proxy || fatal "sshd_proxy broken"
+
+start_sshd ()
+{
+ # start sshd
+ $SUDO ${SSHD} -f $OBJ/sshd_config "$@" -t || fatal "sshd_config broken"
+ $SUDO env SSH_SK_HELPER="$SSH_SK_HELPER" \
+ ${SSHD} -f $OBJ/sshd_config "$@" -E$TEST_SSHD_LOGFILE
+
+ trace "wait for sshd"
+ i=0;
+ while [ ! -f $PIDFILE -a $i -lt 10 ]; do
+ i=`expr $i + 1`
+ sleep $i
+ done
+
+ test -f $PIDFILE || fatal "no sshd running on port $PORT"
+}
+
+# source test body
+. $SCRIPT
+
+# kill sshd
+cleanup
+
+if [ "x$USE_VALGRIND" != "x" ]; then
+ # If there is an EXIT trap handler, invoke it now.
+ # Some tests set these to clean up processes such as ssh-agent. We
+ # need to wait for all valgrind processes to complete so we can check
+ # their logs, but since the EXIT traps are not invoked until
+ # test-exec.sh exits, waiting here will deadlock.
+ # This is not very portable but then neither is valgrind itself.
+ # As a bonus, dash (as used on the runners) has a "trap" that doesn't
+ # work in a pipeline (hence the temp file) or a subshell.
+ exithandler=""
+ trap >/tmp/trap.$$ && exithandler=$(cat /tmp/trap.$$ | \
+ awk -F "'" '/EXIT$/{print $2}')
+ rm -f /tmp/trap.$$
+ if [ "x${exithandler}" != "x" ]; then
+ verbose invoking EXIT trap handler early: ${exithandler}
+ eval "${exithandler}"
+ trap '' EXIT
+ fi
+
+ # wait for any running process to complete
+ wait; sleep 1
+ VG_RESULTS=$(find $OBJ/valgrind-out -type f -print)
+ VG_RESULT_COUNT=0
+ VG_FAIL_COUNT=0
+ for i in $VG_RESULTS; do
+ if grep "ERROR SUMMARY" $i >/dev/null; then
+ VG_RESULT_COUNT=$(($VG_RESULT_COUNT + 1))
+ if ! grep "ERROR SUMMARY: 0 errors" $i >/dev/null; then
+ VG_FAIL_COUNT=$(($VG_FAIL_COUNT + 1))
+ RESULT=1
+ verbose valgrind failure $i
+ cat $i
+ fi
+ fi
+ done
+ if [ x"$VG_SKIP" != "x" ]; then
+ verbose valgrind skipped
+ else
+ verbose valgrind results $VG_RESULT_COUNT failures $VG_FAIL_COUNT
+ fi
+fi
+
+if [ $RESULT -eq 0 ]; then
+ verbose ok $tid
+ if [ "x$CACHE" != "x" ]; then
+ touch "$CACHE"
+ fi
+else
+ echo failed $tid
+fi
+exit $RESULT
diff --git a/regress/transfer.sh b/regress/transfer.sh
new file mode 100644
index 0000000..cf174a0
--- /dev/null
+++ b/regress/transfer.sh
@@ -0,0 +1,23 @@
+# $OpenBSD: transfer.sh,v 1.4 2017/04/30 23:34:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="transfer data"
+
+rm -f ${COPY}
+${SSH} -n -q -F $OBJ/ssh_proxy somehost cat ${DATA} > ${COPY}
+if [ $? -ne 0 ]; then
+ fail "ssh cat $DATA failed"
+fi
+cmp ${DATA} ${COPY} || fail "corrupted copy"
+
+for s in 10 100 1k 32k 64k 128k 256k; do
+ trace "dd-size ${s}"
+ rm -f ${COPY}
+ dd if=$DATA obs=${s} 2> /dev/null | \
+ ${SSH} -q -F $OBJ/ssh_proxy somehost "cat > ${COPY}"
+ if [ $? -ne 0 ]; then
+ fail "ssh cat $DATA failed"
+ fi
+ cmp $DATA ${COPY} || fail "corrupted copy"
+done
+rm -f ${COPY}
diff --git a/regress/try-ciphers.sh b/regress/try-ciphers.sh
new file mode 100644
index 0000000..e04268b
--- /dev/null
+++ b/regress/try-ciphers.sh
@@ -0,0 +1,28 @@
+# $OpenBSD: try-ciphers.sh,v 1.26 2017/04/30 23:34:55 djm Exp $
+# Placed in the Public Domain.
+
+tid="try ciphers"
+
+cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
+
+for c in `${SSH} -Q cipher`; do
+ n=0
+ for m in `${SSH} -Q mac`; do
+ trace "cipher $c mac $m"
+ verbose "test $tid: cipher $c mac $m"
+ cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
+ echo "Ciphers=$c" >> $OBJ/sshd_proxy
+ echo "MACs=$m" >> $OBJ/sshd_proxy
+ ${SSH} -F $OBJ/ssh_proxy -m $m -c $c somehost true
+ if [ $? -ne 0 ]; then
+ fail "ssh failed with mac $m cipher $c"
+ fi
+ # No point trying all MACs for AEAD ciphers since they
+ # are ignored.
+ if ${SSH} -Q cipher-auth | grep "^${c}\$" >/dev/null 2>&1 ; then
+ break
+ fi
+ n=`expr $n + 1`
+ done
+done
+
diff --git a/regress/unittests/Makefile b/regress/unittests/Makefile
new file mode 100644
index 0000000..4d26b74
--- /dev/null
+++ b/regress/unittests/Makefile
@@ -0,0 +1,7 @@
+# $OpenBSD: Makefile,v 1.12 2020/06/19 04:34:21 djm Exp $
+
+REGRESS_FAIL_EARLY?= yes
+SUBDIR= test_helper sshbuf sshkey bitmap kex hostkeys utf8 match conversion
+SUBDIR+=authopt misc sshsig
+
+.include <bsd.subdir.mk>
diff --git a/regress/unittests/Makefile.inc b/regress/unittests/Makefile.inc
new file mode 100644
index 0000000..370224a
--- /dev/null
+++ b/regress/unittests/Makefile.inc
@@ -0,0 +1,89 @@
+# $OpenBSD: Makefile.inc,v 1.14 2019/11/25 10:32:35 djm Exp $
+
+REGRESS_FAIL_EARLY?= yes
+
+.include <bsd.own.mk>
+.include <bsd.obj.mk>
+
+# User-settable options
+UNITTEST_FAST?= no # Skip slow tests (e.g. less intensive fuzzing).
+UNITTEST_SLOW?= no # Include slower tests (e.g. more intensive fuzzing).
+UNITTEST_VERBOSE?= no # Verbose test output (inc. per-test names).
+
+MALLOC_OPTIONS?= CFGJRSUX
+TEST_ENV?= MALLOC_OPTIONS=${MALLOC_OPTIONS}
+
+# XXX detect from ssh binary?
+OPENSSL?= yes
+
+.if (${OPENSSL:L} == "yes")
+CFLAGS+= -DWITH_OPENSSL
+.endif
+
+# enable warnings
+WARNINGS=Yes
+
+DEBUG=-g
+CFLAGS+= -fstack-protector-all
+CDIAGFLAGS= -Wall
+CDIAGFLAGS+= -Wextra
+CDIAGFLAGS+= -Werror
+CDIAGFLAGS+= -Wchar-subscripts
+CDIAGFLAGS+= -Wcomment
+CDIAGFLAGS+= -Wformat
+CDIAGFLAGS+= -Wformat-security
+CDIAGFLAGS+= -Wimplicit
+CDIAGFLAGS+= -Winline
+CDIAGFLAGS+= -Wmissing-declarations
+CDIAGFLAGS+= -Wmissing-prototypes
+CDIAGFLAGS+= -Wparentheses
+CDIAGFLAGS+= -Wpointer-arith
+CDIAGFLAGS+= -Wreturn-type
+CDIAGFLAGS+= -Wshadow
+CDIAGFLAGS+= -Wsign-compare
+CDIAGFLAGS+= -Wstrict-aliasing
+CDIAGFLAGS+= -Wstrict-prototypes
+CDIAGFLAGS+= -Wswitch
+CDIAGFLAGS+= -Wtrigraphs
+CDIAGFLAGS+= -Wuninitialized
+CDIAGFLAGS+= -Wunused
+CDIAGFLAGS+= -Wno-unused-parameter
+.if ${COMPILER_VERSION:L} != "gcc3"
+CDIAGFLAGS+= -Wold-style-definition
+.endif
+
+SSHREL=../../../../../usr.bin/ssh
+
+CFLAGS+=-I${.CURDIR}/../test_helper -I${.CURDIR}/${SSHREL}
+
+.if exists(${.CURDIR}/../test_helper/${__objdir})
+LDADD+=-L${.CURDIR}/../test_helper/${__objdir} -ltest_helper
+DPADD+=${.CURDIR}/../test_helper/${__objdir}/libtest_helper.a
+.else
+LDADD+=-L${.CURDIR}/../test_helper -ltest_helper
+DPADD+=${.CURDIR}/../test_helper/libtest_helper.a
+.endif
+
+.PATH: ${.CURDIR}/${SSHREL}
+
+LDADD+= -lutil
+DPADD+= ${LIBUTIL}
+
+.if (${OPENSSL:L} == "yes")
+LDADD+= -lcrypto
+DPADD+= ${LIBCRYPTO}
+.endif
+
+LDADD+= -lfido2 -lcbor -lusbhid
+DPADD+= ${LIBFIDO2} ${LIBCBOR} ${LIBUSBHID}
+
+UNITTEST_ARGS?=
+
+.if (${UNITTEST_VERBOSE:L} != "no")
+UNITTEST_ARGS+= -v
+.endif
+.if (${UNITTEST_FAST:L} != "no")
+UNITTEST_ARGS+= -f
+.elif (${UNITTEST_SLOW:L} != "no")
+UNITTEST_ARGS+= -F
+.endif
diff --git a/regress/unittests/authopt/Makefile b/regress/unittests/authopt/Makefile
new file mode 100644
index 0000000..3045ec7
--- /dev/null
+++ b/regress/unittests/authopt/Makefile
@@ -0,0 +1,27 @@
+# $OpenBSD: Makefile,v 1.7 2023/01/15 23:35:10 djm Exp $
+
+PROG=test_authopt
+SRCS=tests.c
+
+SRCS+=auth-options.c
+
+# From usr.bin/ssh
+SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c
+SRCS+=sshbuf-io.c atomicio.c sshkey.c authfile.c cipher.c log.c ssh-rsa.c
+SRCS+=ssh-dss.c ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c
+SRCS+=ssherr.c uidswap.c cleanup.c xmalloc.c match.c krl.c fatal.c
+SRCS+=addr.c addrmatch.c bitmap.c
+SRCS+=ed25519.c hash.c
+SRCS+=cipher-chachapoly.c chacha.c poly1305.c ssh-ecdsa-sk.c ssh-sk.c
+SRCS+=ssh-ed25519-sk.c sk-usbhid.c
+
+SRCS+=digest-openssl.c
+#SRCS+=digest-libc.c
+SRCS+=utf8.c
+
+REGRESS_TARGETS=run-regress-${PROG}
+
+run-regress-${PROG}: ${PROG}
+ env ${TEST_ENV} ./${PROG} -d ${.CURDIR}/testdata
+
+.include <bsd.regress.mk>
diff --git a/regress/unittests/authopt/testdata/all_permit.cert b/regress/unittests/authopt/testdata/all_permit.cert
new file mode 100644
index 0000000..38ac573
--- /dev/null
+++ b/regress/unittests/authopt/testdata/all_permit.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIOv/h7mJS1WkRHukSvqPwKDiNVrcib/VqBLpbHW6xjWCAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgKFWCzCzQTh9UkoHphbgwaa86Q16Kern0UjqOr7Q+Jk8AAABTAAAAC3NzaC1lZDI1NTE5AAAAQNe1XDN+J4Eb82TH5J5sYypcabocufjTFRfpU57K+csRP41Yo1FCSEWx95ilUuNvK9Iv3yFDOeVPzdqRqzWoHwE= user key
diff --git a/regress/unittests/authopt/testdata/bad_sourceaddr.cert b/regress/unittests/authopt/testdata/bad_sourceaddr.cert
new file mode 100644
index 0000000..9732745
--- /dev/null
+++ b/regress/unittests/authopt/testdata/bad_sourceaddr.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAILFEJyunlz9scYU3mwbOEJoSSkeO1z20uNBw13tEn+lJAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAHwAAAA5zb3VyY2UtYWRkcmVzcwAAAAkAAAAFeHh4eHgAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIChVgsws0E4fVJKB6YW4MGmvOkNeinq59FI6jq+0PiZPAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEA5xY/OEAJ3tgg8/KJqaBR5KMdYYRDiMJ6u4VKS9lQOV1HJQvDDvjj3F5k53BIqTJRVQx242YWs+B3C4db/uLgB user key
diff --git a/regress/unittests/authopt/testdata/force_command.cert b/regress/unittests/authopt/testdata/force_command.cert
new file mode 100644
index 0000000..f7af27e
--- /dev/null
+++ b/regress/unittests/authopt/testdata/force_command.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIJkpCeqaVl6qnp7qa90KehAmHFecx3HW8HZQ22KEqeKBAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAHAAAAA1mb3JjZS1jb21tYW5kAAAABwAAAANmb28AAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIChVgsws0E4fVJKB6YW4MGmvOkNeinq59FI6jq+0PiZPAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEAxbhjgbXvfEumRP1E7VH8nUfuJyVlDChhCxiPg9Nvb9PFK8cHdDUEybDCzKCsIDieRc3mtLTyEu7Kb52va/B4C user key
diff --git a/regress/unittests/authopt/testdata/host.cert b/regress/unittests/authopt/testdata/host.cert
new file mode 100644
index 0000000..6326d04
--- /dev/null
+++ b/regress/unittests/authopt/testdata/host.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIFWMw3ftP29RSefnxQwdvK1KiE2G9Y7rPRrJ7ZsrDiOeAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAACAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAAAAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAoVYLMLNBOH1SSgemFuDBprzpDXop6ufRSOo6vtD4mTwAAAFMAAAALc3NoLWVkMjU1MTkAAABAKTMqwPkaBg23RS7/aj347dc2kY4bWt/sHwzREYSrKRqZ5RNBnSvZOQ8m5euMCEuf92bZ8VJEdF653jRiW6VoBA== user key
diff --git a/regress/unittests/authopt/testdata/mktestdata.sh b/regress/unittests/authopt/testdata/mktestdata.sh
new file mode 100644
index 0000000..06a24e3
--- /dev/null
+++ b/regress/unittests/authopt/testdata/mktestdata.sh
@@ -0,0 +1,48 @@
+#/bin/sh
+
+set -xe
+
+rm -f ca_key ca_key.pub
+rm -f user_key user_key.pub
+rm -f *.cert
+
+ssh-keygen -q -f ca_key -t ed25519 -C CA -N ''
+ssh-keygen -q -f user_key -t ed25519 -C "user key" -N ''
+
+sign() {
+ output=$1
+ shift
+ set -xe
+ ssh-keygen -q -s ca_key -I user -n user \
+ -V 19990101:19991231 -z 1 "$@" user_key.pub
+ mv user_key-cert.pub "$output"
+}
+
+sign all_permit.cert -Opermit-agent-forwarding -Opermit-port-forwarding \
+ -Opermit-pty -Opermit-user-rc -Opermit-X11-forwarding
+sign no_permit.cert -Oclear
+
+sign no_agentfwd.cert -Ono-agent-forwarding
+sign no_portfwd.cert -Ono-port-forwarding
+sign no_pty.cert -Ono-pty
+sign no_user_rc.cert -Ono-user-rc
+sign no_x11fwd.cert -Ono-X11-forwarding
+
+sign only_agentfwd.cert -Oclear -Opermit-agent-forwarding
+sign only_portfwd.cert -Oclear -Opermit-port-forwarding
+sign only_pty.cert -Oclear -Opermit-pty
+sign only_user_rc.cert -Oclear -Opermit-user-rc
+sign only_x11fwd.cert -Oclear -Opermit-X11-forwarding
+
+sign force_command.cert -Oforce-command="foo"
+sign sourceaddr.cert -Osource-address="127.0.0.1/32,::1/128"
+
+# ssh-keygen won't permit generation of certs with invalid source-address
+# values, so we do it as a custom extension.
+sign bad_sourceaddr.cert -Ocritical:source-address=xxxxx
+
+sign unknown_critical.cert -Ocritical:blah=foo
+
+sign host.cert -h
+
+rm -f user_key ca_key user_key.pub ca_key.pub
diff --git a/regress/unittests/authopt/testdata/no_agentfwd.cert b/regress/unittests/authopt/testdata/no_agentfwd.cert
new file mode 100644
index 0000000..bfa5c2e
--- /dev/null
+++ b/regress/unittests/authopt/testdata/no_agentfwd.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIL2qEsLCVtKaBkbCrZicxbPUorcHHrQ8yw5h/26krTOlAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAAAAAAGMAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAoVYLMLNBOH1SSgemFuDBprzpDXop6ufRSOo6vtD4mTwAAAFMAAAALc3NoLWVkMjU1MTkAAABAdRhISpol01OwV30g39PM/JD1t35muskX4lyCcGpFQ08GQtBuHE/hABOp6apbGBJIC7CZYYF+uHkD7PfGU3NPAQ== user key
diff --git a/regress/unittests/authopt/testdata/no_permit.cert b/regress/unittests/authopt/testdata/no_permit.cert
new file mode 100644
index 0000000..351e138
--- /dev/null
+++ b/regress/unittests/authopt/testdata/no_permit.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIGVQtVgp9sD4sc8esIhVWbZaM8d0NxpX3UbEVzTHm9feAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAAAAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAoVYLMLNBOH1SSgemFuDBprzpDXop6ufRSOo6vtD4mTwAAAFMAAAALc3NoLWVkMjU1MTkAAABAIKlI0TqqraKjYTjIuKhwoxAV/XnzWRJHq8lNs4aj5yDb84un2xXDF/0vXoLjPgVcLgEbksBKKn0i4whp+xn9Ag== user key
diff --git a/regress/unittests/authopt/testdata/no_portfwd.cert b/regress/unittests/authopt/testdata/no_portfwd.cert
new file mode 100644
index 0000000..9457dc3
--- /dev/null
+++ b/regress/unittests/authopt/testdata/no_portfwd.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIE6gC/QjjuzGWVDkr8ZyaHhja80V+lKLC/MvmEFa+CEBAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAAAAAAGQAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgKFWCzCzQTh9UkoHphbgwaa86Q16Kern0UjqOr7Q+Jk8AAABTAAAAC3NzaC1lZDI1NTE5AAAAQEzpgckYlfc1BK1ir0reDSXo9OIDx4UoDMrNXrFO6I44NXoJJ4TlUUJH07WcKp/Xp5ESCdyVZtqwgHQxZr0+PwI= user key
diff --git a/regress/unittests/authopt/testdata/no_pty.cert b/regress/unittests/authopt/testdata/no_pty.cert
new file mode 100644
index 0000000..e8154ec
--- /dev/null
+++ b/regress/unittests/authopt/testdata/no_pty.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIFFjhISpSDR3blDejuCf2T9Fe4aHW53jG7KOH2PV/E7jAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAAAAAAHAAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgKFWCzCzQTh9UkoHphbgwaa86Q16Kern0UjqOr7Q+Jk8AAABTAAAAC3NzaC1lZDI1NTE5AAAAQF5c4BdxVYgqbMGAep414IGFK4deCFBCeNUTOLpKodrfb1M0gS4d2qoeMxZvMv5yMf/viKl/gallHzEmcrEcIQY= user key
diff --git a/regress/unittests/authopt/testdata/no_user_rc.cert b/regress/unittests/authopt/testdata/no_user_rc.cert
new file mode 100644
index 0000000..6676a0c
--- /dev/null
+++ b/regress/unittests/authopt/testdata/no_user_rc.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIFUM0VLATkYh05QeS5uuhB1X50NMom3jTWeQUmrPQ1FwAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAAAAAAGwAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAoVYLMLNBOH1SSgemFuDBprzpDXop6ufRSOo6vtD4mTwAAAFMAAAALc3NoLWVkMjU1MTkAAABAcmJ3c2FCKJL9BCLv1Ij+uN1N+NWZmMXYionsSkv42Go4pMZiH3g8UfTd+OKq9Q7GAcCzGXa///6Dr/wqFssoDA== user key
diff --git a/regress/unittests/authopt/testdata/no_x11fwd.cert b/regress/unittests/authopt/testdata/no_x11fwd.cert
new file mode 100644
index 0000000..0aff9e6
--- /dev/null
+++ b/regress/unittests/authopt/testdata/no_x11fwd.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIPRKPAP+b5S+4zihdgoJrYNcMovFBgKZaJupIhN1kUvkAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAAAAAAGUAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIChVgsws0E4fVJKB6YW4MGmvOkNeinq59FI6jq+0PiZPAAAAUwAAAAtzc2gtZWQyNTUxOQAAAECMzj6VDfT+BJmIEo1qUKdr8VDLExF92K7KkbNxTH77n7uip7TL24HDfXjYBCvqxSSn9KAGBhnWsIC/GPx6A+cP user key
diff --git a/regress/unittests/authopt/testdata/only_agentfwd.cert b/regress/unittests/authopt/testdata/only_agentfwd.cert
new file mode 100644
index 0000000..3cf64b0
--- /dev/null
+++ b/regress/unittests/authopt/testdata/only_agentfwd.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIOvJ28yW5uvA7yxE3ySuyFvPjcRYKAr03CYr4okGTNIFAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAAAAAAB8AAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgKFWCzCzQTh9UkoHphbgwaa86Q16Kern0UjqOr7Q+Jk8AAABTAAAAC3NzaC1lZDI1NTE5AAAAQEG2uTgmOSk9dJ0s/Ol1EIERXFP9PF6AauF9t5jBMSthNyvSANSrC/1EIaf4TV5kMYfhZxJXoS0XHQjGndcq2AE= user key
diff --git a/regress/unittests/authopt/testdata/only_portfwd.cert b/regress/unittests/authopt/testdata/only_portfwd.cert
new file mode 100644
index 0000000..bb09c3a
--- /dev/null
+++ b/regress/unittests/authopt/testdata/only_portfwd.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIGPoYoExiSyHMyDEvOFgoNZXk5z91u7xq/7357X23TotAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAAAAAAB4AAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAoVYLMLNBOH1SSgemFuDBprzpDXop6ufRSOo6vtD4mTwAAAFMAAAALc3NoLWVkMjU1MTkAAABAHN3YnwipcbDKVn+PObGSoaT9rwlau+yrPYZ50oetvCKng3RMjGaV+roqlv0vjjLcxE9J4Y0ti+9MXtQ0D7beBA== user key
diff --git a/regress/unittests/authopt/testdata/only_pty.cert b/regress/unittests/authopt/testdata/only_pty.cert
new file mode 100644
index 0000000..520c89f
--- /dev/null
+++ b/regress/unittests/authopt/testdata/only_pty.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAILvocWYto5Lg7P46YLbe7U4/b2h9Lr5rWqMZ4Cj4ra7RAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAAAAAABIAAAAKcGVybWl0LXB0eQAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAoVYLMLNBOH1SSgemFuDBprzpDXop6ufRSOo6vtD4mTwAAAFMAAAALc3NoLWVkMjU1MTkAAABASv2xQvp+Y6E8dCf5pzg3MZaan5bl1ToYXNcmQ3ysGrk9Djkcu8m3TytDpF471KmUejxy/iF4xjs9CDpk7h+SBQ== user key
diff --git a/regress/unittests/authopt/testdata/only_user_rc.cert b/regress/unittests/authopt/testdata/only_user_rc.cert
new file mode 100644
index 0000000..fb49c35
--- /dev/null
+++ b/regress/unittests/authopt/testdata/only_user_rc.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIJwsRZQ7kx4A8AQ0q/G/3i6sHM48kr4TxJtTcyy3lZAPAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAAAAAABYAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgKFWCzCzQTh9UkoHphbgwaa86Q16Kern0UjqOr7Q+Jk8AAABTAAAAC3NzaC1lZDI1NTE5AAAAQDhgEXsvoHr21XrxmiZq/sIjWeYapp11XvEVkkTBPVhBnPwtrrUeJbPmGs3gmJkQdv8BYajYpT7TXEX8GvEeLwU= user key
diff --git a/regress/unittests/authopt/testdata/only_x11fwd.cert b/regress/unittests/authopt/testdata/only_x11fwd.cert
new file mode 100644
index 0000000..6715585
--- /dev/null
+++ b/regress/unittests/authopt/testdata/only_x11fwd.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIDAhZFZBl3eu8Qa8I5BaHCz/mpH8xCjaPusBwo1eJ9OGAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAAAAAAB0AAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIChVgsws0E4fVJKB6YW4MGmvOkNeinq59FI6jq+0PiZPAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEDysfgbhniX/zdA8576rrDJpaO2D7QtQse2KWIM9XmREPkLKeP6FKiXKKFcPQiMyV28rptfvK8bBXAiOvITSUgL user key
diff --git a/regress/unittests/authopt/testdata/sourceaddr.cert b/regress/unittests/authopt/testdata/sourceaddr.cert
new file mode 100644
index 0000000..0fcf7b1
--- /dev/null
+++ b/regress/unittests/authopt/testdata/sourceaddr.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIJ54qqoPs87gtjN1aJoLUn7ZTYUtcaGxkzLyJvRkYG7nAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAALgAAAA5zb3VyY2UtYWRkcmVzcwAAABgAAAAUMTI3LjAuMC4xLzMyLDo6MS8xMjgAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIChVgsws0E4fVJKB6YW4MGmvOkNeinq59FI6jq+0PiZPAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEAppSUKQ/a9tw/HgIazWceCO3d48GU7mkV4iQMpWWs2nB1dFryY1GDtZrBggAjMviwmBXyM3jIk5vxJDINZXGQJ user key
diff --git a/regress/unittests/authopt/testdata/unknown_critical.cert b/regress/unittests/authopt/testdata/unknown_critical.cert
new file mode 100644
index 0000000..216960a
--- /dev/null
+++ b/regress/unittests/authopt/testdata/unknown_critical.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIIjs/wRAB/p5QShSfqoU9cWnCLT3lSveUirk61A27KxVAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAEwAAAARibGFoAAAABwAAAANmb28AAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIChVgsws0E4fVJKB6YW4MGmvOkNeinq59FI6jq+0PiZPAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEDix3FV7JIBuHNAwtZOVIqGBq8lqhnEwP51DqPA43qt+Tzynm56EWxuFzgGehBPF3L8gl+fVqxIJmiQ9iHB0LUD user key
diff --git a/regress/unittests/authopt/tests.c b/regress/unittests/authopt/tests.c
new file mode 100644
index 0000000..d9e1903
--- /dev/null
+++ b/regress/unittests/authopt/tests.c
@@ -0,0 +1,578 @@
+/* $OpenBSD: tests.c,v 1.3 2021/12/14 21:25:27 deraadt Exp $ */
+
+/*
+ * Regress test for keys options functions.
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "sshkey.h"
+#include "authfile.h"
+#include "auth-options.h"
+#include "misc.h"
+#include "log.h"
+
+static struct sshkey *
+load_key(const char *name)
+{
+ struct sshkey *ret;
+ int r;
+
+ r = sshkey_load_public(test_data_file(name), &ret, NULL);
+ ASSERT_INT_EQ(r, 0);
+ ASSERT_PTR_NE(ret, NULL);
+ return ret;
+}
+
+static struct sshauthopt *
+default_authkey_opts(void)
+{
+ struct sshauthopt *ret = sshauthopt_new();
+
+ ASSERT_PTR_NE(ret, NULL);
+ ret->permit_port_forwarding_flag = 1;
+ ret->permit_agent_forwarding_flag = 1;
+ ret->permit_x11_forwarding_flag = 1;
+ ret->permit_pty_flag = 1;
+ ret->permit_user_rc = 1;
+ return ret;
+}
+
+static struct sshauthopt *
+default_authkey_restrict_opts(void)
+{
+ struct sshauthopt *ret = sshauthopt_new();
+
+ ASSERT_PTR_NE(ret, NULL);
+ ret->permit_port_forwarding_flag = 0;
+ ret->permit_agent_forwarding_flag = 0;
+ ret->permit_x11_forwarding_flag = 0;
+ ret->permit_pty_flag = 0;
+ ret->permit_user_rc = 0;
+ ret->restricted = 1;
+ return ret;
+}
+
+static char **
+commasplit(const char *s, size_t *np)
+{
+ char *ocp, *cp, *cp2, **ret = NULL;
+ size_t n;
+
+ ocp = cp = strdup(s);
+ ASSERT_PTR_NE(cp, NULL);
+ for (n = 0; (cp2 = strsep(&cp, ",")) != NULL;) {
+ ret = recallocarray(ret, n, n + 1, sizeof(*ret));
+ ASSERT_PTR_NE(ret, NULL);
+ cp2 = strdup(cp2);
+ ASSERT_PTR_NE(cp2, NULL);
+ ret[n++] = cp2;
+ }
+ free(ocp);
+ *np = n;
+ return ret;
+}
+
+static void
+compare_opts(const struct sshauthopt *opts,
+ const struct sshauthopt *expected)
+{
+ size_t i;
+
+ ASSERT_PTR_NE(opts, NULL);
+ ASSERT_PTR_NE(expected, NULL);
+ ASSERT_PTR_NE(expected, opts); /* bozo :) */
+
+#define FLAG_EQ(x) ASSERT_INT_EQ(opts->x, expected->x)
+ FLAG_EQ(permit_port_forwarding_flag);
+ FLAG_EQ(permit_agent_forwarding_flag);
+ FLAG_EQ(permit_x11_forwarding_flag);
+ FLAG_EQ(permit_pty_flag);
+ FLAG_EQ(permit_user_rc);
+ FLAG_EQ(restricted);
+ FLAG_EQ(cert_authority);
+#undef FLAG_EQ
+
+#define STR_EQ(x) \
+ do { \
+ if (expected->x == NULL) \
+ ASSERT_PTR_EQ(opts->x, expected->x); \
+ else \
+ ASSERT_STRING_EQ(opts->x, expected->x); \
+ } while (0)
+ STR_EQ(cert_principals);
+ STR_EQ(force_command);
+ STR_EQ(required_from_host_cert);
+ STR_EQ(required_from_host_keys);
+#undef STR_EQ
+
+#define ARRAY_EQ(nx, x) \
+ do { \
+ ASSERT_SIZE_T_EQ(opts->nx, expected->nx); \
+ if (expected->nx == 0) \
+ break; \
+ for (i = 0; i < expected->nx; i++) \
+ ASSERT_STRING_EQ(opts->x[i], expected->x[i]); \
+ } while (0)
+ ARRAY_EQ(nenv, env);
+ ARRAY_EQ(npermitopen, permitopen);
+#undef ARRAY_EQ
+}
+
+static void
+test_authkeys_parse(void)
+{
+ struct sshauthopt *opts, *expected;
+ const char *errstr;
+
+#define FAIL_TEST(label, keywords) \
+ do { \
+ TEST_START("sshauthopt_parse invalid " label); \
+ opts = sshauthopt_parse(keywords, &errstr); \
+ ASSERT_PTR_EQ(opts, NULL); \
+ ASSERT_PTR_NE(errstr, NULL); \
+ TEST_DONE(); \
+ } while (0)
+#define CHECK_SUCCESS_AND_CLEANUP() \
+ do { \
+ if (errstr != NULL) \
+ ASSERT_STRING_EQ(errstr, ""); \
+ compare_opts(opts, expected); \
+ sshauthopt_free(expected); \
+ sshauthopt_free(opts); \
+ } while (0)
+
+ /* Basic tests */
+ TEST_START("sshauthopt_parse empty");
+ expected = default_authkey_opts();
+ opts = sshauthopt_parse("", &errstr);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ TEST_START("sshauthopt_parse trailing whitespace");
+ expected = default_authkey_opts();
+ opts = sshauthopt_parse(" ", &errstr);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ TEST_START("sshauthopt_parse restrict");
+ expected = default_authkey_restrict_opts();
+ opts = sshauthopt_parse("restrict", &errstr);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ /* Invalid syntax */
+ FAIL_TEST("trailing comma", "restrict,");
+ FAIL_TEST("bare comma", ",");
+ FAIL_TEST("unknown option", "BLAH");
+ FAIL_TEST("unknown option with trailing comma", "BLAH,");
+ FAIL_TEST("unknown option with trailing whitespace", "BLAH ");
+
+ /* force_tun_device */
+ TEST_START("sshauthopt_parse tunnel explicit");
+ expected = default_authkey_opts();
+ expected->force_tun_device = 1;
+ opts = sshauthopt_parse("tunnel=\"1\"", &errstr);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ TEST_START("sshauthopt_parse tunnel any");
+ expected = default_authkey_opts();
+ expected->force_tun_device = SSH_TUNID_ANY;
+ opts = sshauthopt_parse("tunnel=\"any\"", &errstr);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ FAIL_TEST("tunnel", "tunnel=\"blah\"");
+
+ /* Flag options */
+#define FLAG_TEST(keyword, var, val) \
+ do { \
+ TEST_START("sshauthopt_parse " keyword); \
+ expected = default_authkey_opts(); \
+ expected->var = val; \
+ opts = sshauthopt_parse(keyword, &errstr); \
+ CHECK_SUCCESS_AND_CLEANUP(); \
+ expected = default_authkey_restrict_opts(); \
+ expected->var = val; \
+ opts = sshauthopt_parse("restrict,"keyword, &errstr); \
+ CHECK_SUCCESS_AND_CLEANUP(); \
+ TEST_DONE(); \
+ } while (0)
+ /* Positive flags */
+ FLAG_TEST("cert-authority", cert_authority, 1);
+ FLAG_TEST("port-forwarding", permit_port_forwarding_flag, 1);
+ FLAG_TEST("agent-forwarding", permit_agent_forwarding_flag, 1);
+ FLAG_TEST("x11-forwarding", permit_x11_forwarding_flag, 1);
+ FLAG_TEST("pty", permit_pty_flag, 1);
+ FLAG_TEST("user-rc", permit_user_rc, 1);
+ /* Negative flags */
+ FLAG_TEST("no-port-forwarding", permit_port_forwarding_flag, 0);
+ FLAG_TEST("no-agent-forwarding", permit_agent_forwarding_flag, 0);
+ FLAG_TEST("no-x11-forwarding", permit_x11_forwarding_flag, 0);
+ FLAG_TEST("no-pty", permit_pty_flag, 0);
+ FLAG_TEST("no-user-rc", permit_user_rc, 0);
+#undef FLAG_TEST
+ FAIL_TEST("no-cert-authority", "no-cert-authority");
+
+ /* String options */
+#define STRING_TEST(keyword, var, val) \
+ do { \
+ TEST_START("sshauthopt_parse " keyword); \
+ expected = default_authkey_opts(); \
+ expected->var = strdup(val); \
+ ASSERT_PTR_NE(expected->var, NULL); \
+ opts = sshauthopt_parse(keyword "=" #val, &errstr); \
+ CHECK_SUCCESS_AND_CLEANUP(); \
+ expected = default_authkey_restrict_opts(); \
+ expected->var = strdup(val); \
+ ASSERT_PTR_NE(expected->var, NULL); \
+ opts = sshauthopt_parse( \
+ "restrict," keyword "=" #val ",restrict", &errstr); \
+ CHECK_SUCCESS_AND_CLEANUP(); \
+ TEST_DONE(); \
+ } while (0)
+ STRING_TEST("command", force_command, "/bin/true");
+ STRING_TEST("principals", cert_principals, "gregor,josef,K");
+ STRING_TEST("from", required_from_host_keys, "127.0.0.0/8");
+#undef STRING_TEST
+ FAIL_TEST("unquoted command", "command=oops");
+ FAIL_TEST("unquoted principals", "principals=estragon");
+ FAIL_TEST("unquoted from", "from=127.0.0.1");
+
+ /* String array option tests */
+#define ARRAY_TEST(label, keywords, var, nvar, val) \
+ do { \
+ TEST_START("sshauthopt_parse " label); \
+ expected = default_authkey_opts(); \
+ expected->var = commasplit(val, &expected->nvar); \
+ ASSERT_PTR_NE(expected->var, NULL); \
+ opts = sshauthopt_parse(keywords, &errstr); \
+ CHECK_SUCCESS_AND_CLEANUP(); \
+ expected = default_authkey_restrict_opts(); \
+ expected->var = commasplit(val, &expected->nvar); \
+ ASSERT_PTR_NE(expected->var, NULL); \
+ opts = sshauthopt_parse( \
+ "restrict," keywords ",restrict", &errstr); \
+ CHECK_SUCCESS_AND_CLEANUP(); \
+ TEST_DONE(); \
+ } while (0)
+ ARRAY_TEST("environment", "environment=\"foo=1\",environment=\"bar=2\"",
+ env, nenv, "foo=1,bar=2");
+ ARRAY_TEST("environment", "environment=\"foo=1\",environment=\"foo=2\"",
+ env, nenv, "foo=1");
+ ARRAY_TEST("permitopen", "permitopen=\"foo:123\",permitopen=\"bar:*\"",
+ permitopen, npermitopen, "foo:123,bar:*");
+#undef ARRAY_TEST
+ FAIL_TEST("environment", "environment=\",=bah\"");
+ FAIL_TEST("permitopen port", "foo:bar");
+ FAIL_TEST("permitopen missing port", "foo:");
+ FAIL_TEST("permitopen missing port specification", "foo");
+ FAIL_TEST("permitopen invalid host", "[:");
+
+#undef CHECK_SUCCESS_AND_CLEANUP
+#undef FAIL_TEST
+}
+
+static void
+test_cert_parse(void)
+{
+ struct sshkey *cert;
+ struct sshauthopt *opts, *expected;
+
+#define CHECK_SUCCESS_AND_CLEANUP() \
+ do { \
+ compare_opts(opts, expected); \
+ sshauthopt_free(expected); \
+ sshauthopt_free(opts); \
+ sshkey_free(cert); \
+ } while (0)
+#define FLAG_TEST(keybase, var) \
+ do { \
+ TEST_START("sshauthopt_from_cert no_" keybase); \
+ cert = load_key("no_" keybase ".cert"); \
+ expected = default_authkey_opts(); \
+ expected->var = 0; \
+ opts = sshauthopt_from_cert(cert); \
+ CHECK_SUCCESS_AND_CLEANUP(); \
+ TEST_DONE(); \
+ TEST_START("sshauthopt_from_cert only_" keybase); \
+ cert = load_key("only_" keybase ".cert"); \
+ expected = sshauthopt_new(); \
+ ASSERT_PTR_NE(expected, NULL); \
+ expected->var = 1; \
+ opts = sshauthopt_from_cert(cert); \
+ CHECK_SUCCESS_AND_CLEANUP(); \
+ TEST_DONE(); \
+ } while (0)
+ FLAG_TEST("agentfwd", permit_agent_forwarding_flag);
+ FLAG_TEST("portfwd", permit_port_forwarding_flag);
+ FLAG_TEST("pty", permit_pty_flag);
+ FLAG_TEST("user_rc", permit_user_rc);
+ FLAG_TEST("x11fwd", permit_x11_forwarding_flag);
+#undef FLAG_TEST
+
+ TEST_START("sshauthopt_from_cert all permitted");
+ cert = load_key("all_permit.cert");
+ expected = default_authkey_opts();
+ opts = sshauthopt_from_cert(cert);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ TEST_START("sshauthopt_from_cert nothing permitted");
+ cert = load_key("no_permit.cert");
+ expected = sshauthopt_new();
+ ASSERT_PTR_NE(expected, NULL);
+ opts = sshauthopt_from_cert(cert);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ TEST_START("sshauthopt_from_cert force-command");
+ cert = load_key("force_command.cert");
+ expected = default_authkey_opts();
+ expected->force_command = strdup("foo");
+ ASSERT_PTR_NE(expected->force_command, NULL);
+ opts = sshauthopt_from_cert(cert);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ TEST_START("sshauthopt_from_cert source-address");
+ cert = load_key("sourceaddr.cert");
+ expected = default_authkey_opts();
+ expected->required_from_host_cert = strdup("127.0.0.1/32,::1/128");
+ ASSERT_PTR_NE(expected->required_from_host_cert, NULL);
+ opts = sshauthopt_from_cert(cert);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+#undef CHECK_SUCCESS_AND_CLEANUP
+
+#define FAIL_TEST(keybase) \
+ do { \
+ TEST_START("sshauthopt_from_cert " keybase); \
+ cert = load_key(keybase ".cert"); \
+ opts = sshauthopt_from_cert(cert); \
+ ASSERT_PTR_EQ(opts, NULL); \
+ sshkey_free(cert); \
+ TEST_DONE(); \
+ } while (0)
+ FAIL_TEST("host");
+ FAIL_TEST("bad_sourceaddr");
+ FAIL_TEST("unknown_critical");
+#undef FAIL_TEST
+}
+
+static void
+test_merge(void)
+{
+ struct sshkey *cert;
+ struct sshauthopt *key_opts, *cert_opts, *merge_opts, *expected;
+ const char *errstr;
+
+ /*
+ * Prepare for a test by making some key and cert options and
+ * attempting to merge them.
+ */
+#define PREPARE(label, keyname, keywords) \
+ do { \
+ expected = NULL; \
+ TEST_START("sshauthopt_merge " label); \
+ cert = load_key(keyname ".cert"); \
+ cert_opts = sshauthopt_from_cert(cert); \
+ ASSERT_PTR_NE(cert_opts, NULL); \
+ key_opts = sshauthopt_parse(keywords, &errstr); \
+ if (errstr != NULL) \
+ ASSERT_STRING_EQ(errstr, ""); \
+ ASSERT_PTR_NE(key_opts, NULL); \
+ merge_opts = sshauthopt_merge(key_opts, \
+ cert_opts, &errstr); \
+ } while (0)
+
+ /* Cleanup stuff allocated by PREPARE() */
+#define CLEANUP() \
+ do { \
+ sshauthopt_free(expected); \
+ sshauthopt_free(merge_opts); \
+ sshauthopt_free(key_opts); \
+ sshauthopt_free(cert_opts); \
+ sshkey_free(cert); \
+ } while (0)
+
+ /* Check the results of PREPARE() against expectation; calls CLEANUP */
+#define CHECK_SUCCESS_AND_CLEANUP() \
+ do { \
+ if (errstr != NULL) \
+ ASSERT_STRING_EQ(errstr, ""); \
+ compare_opts(merge_opts, expected); \
+ CLEANUP(); \
+ } while (0)
+
+ /* Check a single case of merging of flag options */
+#define FLAG_CASE(keybase, label, keyname, keywords, mostly_off, var, val) \
+ do { \
+ PREPARE(keybase " " label, keyname, keywords); \
+ expected = mostly_off ? \
+ sshauthopt_new() : default_authkey_opts(); \
+ expected->var = val; \
+ ASSERT_PTR_NE(expected, NULL); \
+ CHECK_SUCCESS_AND_CLEANUP(); \
+ TEST_DONE(); \
+ } while (0)
+
+ /*
+ * Fairly exhaustive exercise of a flag option. Tests
+ * option both set and clear in certificate, set and clear in
+ * authorized_keys and set and cleared via restrict keyword.
+ */
+#define FLAG_TEST(keybase, keyword, var) \
+ do { \
+ FLAG_CASE(keybase, "keys:default,yes cert:default,no", \
+ "no_" keybase, keyword, 0, var, 0); \
+ FLAG_CASE(keybase,"keys:-*,yes cert:default,no", \
+ "no_" keybase, "restrict," keyword, 1, var, 0); \
+ FLAG_CASE(keybase, "keys:default,no cert:default,no", \
+ "no_" keybase, "no-" keyword, 0, var, 0); \
+ FLAG_CASE(keybase, "keys:-*,no cert:default,no", \
+ "no_" keybase, "restrict,no-" keyword, 1, var, 0); \
+ \
+ FLAG_CASE(keybase, "keys:default,yes cert:-*,yes", \
+ "only_" keybase, keyword, 1, var, 1); \
+ FLAG_CASE(keybase,"keys:-*,yes cert:-*,yes", \
+ "only_" keybase, "restrict," keyword, 1, var, 1); \
+ FLAG_CASE(keybase, "keys:default,no cert:-*,yes", \
+ "only_" keybase, "no-" keyword, 1, var, 0); \
+ FLAG_CASE(keybase, "keys:-*,no cert:-*,yes", \
+ "only_" keybase, "restrict,no-" keyword, 1, var, 0); \
+ \
+ FLAG_CASE(keybase, "keys:default,yes cert:-*", \
+ "no_permit", keyword, 1, var, 0); \
+ FLAG_CASE(keybase,"keys:-*,yes cert:-*", \
+ "no_permit", "restrict," keyword, 1, var, 0); \
+ FLAG_CASE(keybase, "keys:default,no cert:-*", \
+ "no_permit", "no-" keyword, 1, var, 0); \
+ FLAG_CASE(keybase, "keys:-*,no cert:-*", \
+ "no_permit", "restrict,no-" keyword, 1, var, 0); \
+ \
+ FLAG_CASE(keybase, "keys:default,yes cert:*", \
+ "all_permit", keyword, 0, var, 1); \
+ FLAG_CASE(keybase,"keys:-*,yes cert:*", \
+ "all_permit", "restrict," keyword, 1, var, 1); \
+ FLAG_CASE(keybase, "keys:default,no cert:*", \
+ "all_permit", "no-" keyword, 0, var, 0); \
+ FLAG_CASE(keybase, "keys:-*,no cert:*", \
+ "all_permit", "restrict,no-" keyword, 1, var, 0); \
+ \
+ } while (0)
+ FLAG_TEST("portfwd", "port-forwarding", permit_port_forwarding_flag);
+ FLAG_TEST("agentfwd", "agent-forwarding", permit_agent_forwarding_flag);
+ FLAG_TEST("pty", "pty", permit_pty_flag);
+ FLAG_TEST("user_rc", "user-rc", permit_user_rc);
+ FLAG_TEST("x11fwd", "x11-forwarding", permit_x11_forwarding_flag);
+#undef FLAG_TEST
+
+ PREPARE("source-address both", "sourceaddr", "from=\"127.0.0.1\"");
+ expected = default_authkey_opts();
+ expected->required_from_host_cert = strdup("127.0.0.1/32,::1/128");
+ ASSERT_PTR_NE(expected->required_from_host_cert, NULL);
+ expected->required_from_host_keys = strdup("127.0.0.1");
+ ASSERT_PTR_NE(expected->required_from_host_keys, NULL);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ PREPARE("source-address none", "all_permit", "");
+ expected = default_authkey_opts();
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ PREPARE("source-address keys", "all_permit", "from=\"127.0.0.1\"");
+ expected = default_authkey_opts();
+ expected->required_from_host_keys = strdup("127.0.0.1");
+ ASSERT_PTR_NE(expected->required_from_host_keys, NULL);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ PREPARE("source-address cert", "sourceaddr", "");
+ expected = default_authkey_opts();
+ expected->required_from_host_cert = strdup("127.0.0.1/32,::1/128");
+ ASSERT_PTR_NE(expected->required_from_host_cert, NULL);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ PREPARE("force-command both", "force_command", "command=\"foo\"");
+ expected = default_authkey_opts();
+ expected->force_command = strdup("foo");
+ ASSERT_PTR_NE(expected->force_command, NULL);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ PREPARE("force-command none", "all_permit", "");
+ expected = default_authkey_opts();
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ PREPARE("force-command keys", "all_permit", "command=\"bar\"");
+ expected = default_authkey_opts();
+ expected->force_command = strdup("bar");
+ ASSERT_PTR_NE(expected->force_command, NULL);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ PREPARE("force-command cert", "force_command", "");
+ expected = default_authkey_opts();
+ expected->force_command = strdup("foo");
+ ASSERT_PTR_NE(expected->force_command, NULL);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ PREPARE("force-command mismatch", "force_command", "command=\"bar\"");
+ ASSERT_PTR_EQ(merge_opts, NULL);
+ CLEANUP();
+ TEST_DONE();
+
+ PREPARE("tunnel", "all_permit", "tunnel=\"6\"");
+ expected = default_authkey_opts();
+ expected->force_tun_device = 6;
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ PREPARE("permitopen", "all_permit",
+ "permitopen=\"127.0.0.1:*\",permitopen=\"127.0.0.1:123\"");
+ expected = default_authkey_opts();
+ expected->permitopen = commasplit("127.0.0.1:*,127.0.0.1:123",
+ &expected->npermitopen);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+
+ PREPARE("environment", "all_permit",
+ "environment=\"foo=a\",environment=\"bar=b\"");
+ expected = default_authkey_opts();
+ expected->env = commasplit("foo=a,bar=b", &expected->nenv);
+ CHECK_SUCCESS_AND_CLEANUP();
+ TEST_DONE();
+}
+
+void
+tests(void)
+{
+ extern char *__progname;
+ LogLevel ll = test_is_verbose() ?
+ SYSLOG_LEVEL_DEBUG3 : SYSLOG_LEVEL_QUIET;
+
+ /* test_cert_parse() are a bit spammy to error() by default... */
+ log_init(__progname, ll, SYSLOG_FACILITY_USER, 1);
+
+ test_authkeys_parse();
+ test_cert_parse();
+ test_merge();
+}
diff --git a/regress/unittests/bitmap/Makefile b/regress/unittests/bitmap/Makefile
new file mode 100644
index 0000000..fe30acc
--- /dev/null
+++ b/regress/unittests/bitmap/Makefile
@@ -0,0 +1,14 @@
+# $OpenBSD: Makefile,v 1.4 2017/12/21 00:41:22 djm Exp $
+
+PROG=test_bitmap
+SRCS=tests.c
+
+# From usr.sbin/ssh
+SRCS+=bitmap.c atomicio.c
+
+REGRESS_TARGETS=run-regress-${PROG}
+
+run-regress-${PROG}: ${PROG}
+ env ${TEST_ENV} ./${PROG}
+
+.include <bsd.regress.mk>
diff --git a/regress/unittests/bitmap/tests.c b/regress/unittests/bitmap/tests.c
new file mode 100644
index 0000000..576b863
--- /dev/null
+++ b/regress/unittests/bitmap/tests.c
@@ -0,0 +1,138 @@
+/* $OpenBSD: tests.c,v 1.2 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for bitmap.h bitmap API
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef WITH_OPENSSL
+#include <openssl/bn.h>
+#endif
+
+#include "../test_helper/test_helper.h"
+
+#include "bitmap.h"
+
+#define NTESTS 131
+
+void
+tests(void)
+{
+#ifdef WITH_OPENSSL
+ struct bitmap *b;
+ BIGNUM *bn;
+ size_t len;
+ int i, j, k, n;
+ u_char bbuf[1024], bnbuf[1024];
+ int r;
+
+ TEST_START("bitmap_new");
+ b = bitmap_new();
+ ASSERT_PTR_NE(b, NULL);
+ bn = BN_new();
+ ASSERT_PTR_NE(bn, NULL);
+ TEST_DONE();
+
+ TEST_START("bitmap_set_bit / bitmap_test_bit");
+ for (i = -1; i < NTESTS; i++) {
+ for (j = -1; j < NTESTS; j++) {
+ for (k = -1; k < NTESTS; k++) {
+ bitmap_zero(b);
+ BN_clear(bn);
+
+ test_subtest_info("set %d/%d/%d", i, j, k);
+ /* Set bits */
+ if (i >= 0) {
+ ASSERT_INT_EQ(bitmap_set_bit(b, i), 0);
+ ASSERT_INT_EQ(BN_set_bit(bn, i), 1);
+ }
+ if (j >= 0) {
+ ASSERT_INT_EQ(bitmap_set_bit(b, j), 0);
+ ASSERT_INT_EQ(BN_set_bit(bn, j), 1);
+ }
+ if (k >= 0) {
+ ASSERT_INT_EQ(bitmap_set_bit(b, k), 0);
+ ASSERT_INT_EQ(BN_set_bit(bn, k), 1);
+ }
+
+ /* Check perfect match between bitmap and bn */
+ test_subtest_info("match %d/%d/%d", i, j, k);
+ for (n = 0; n < NTESTS; n++) {
+ ASSERT_INT_EQ(BN_is_bit_set(bn, n),
+ bitmap_test_bit(b, n));
+ }
+
+ /* Test length calculations */
+ test_subtest_info("length %d/%d/%d", i, j, k);
+ ASSERT_INT_EQ(BN_num_bits(bn),
+ (int)bitmap_nbits(b));
+ ASSERT_INT_EQ(BN_num_bytes(bn),
+ (int)bitmap_nbytes(b));
+
+ /* Test serialisation */
+ test_subtest_info("serialise %d/%d/%d",
+ i, j, k);
+ len = bitmap_nbytes(b);
+ memset(bbuf, 0xfc, sizeof(bbuf));
+ ASSERT_INT_EQ(bitmap_to_string(b, bbuf,
+ sizeof(bbuf)), 0);
+ for (n = len; n < (int)sizeof(bbuf); n++)
+ ASSERT_U8_EQ(bbuf[n], 0xfc);
+ r = BN_bn2bin(bn, bnbuf);
+ ASSERT_INT_GE(r, 0);
+ ASSERT_INT_EQ(r, (int)len);
+ ASSERT_MEM_EQ(bbuf, bnbuf, len);
+
+ /* Test deserialisation */
+ test_subtest_info("deserialise %d/%d/%d",
+ i, j, k);
+ bitmap_zero(b);
+ ASSERT_INT_EQ(bitmap_from_string(b, bnbuf,
+ len), 0);
+ for (n = 0; n < NTESTS; n++) {
+ ASSERT_INT_EQ(BN_is_bit_set(bn, n),
+ bitmap_test_bit(b, n));
+ }
+
+ /* Test clearing bits */
+ test_subtest_info("clear %d/%d/%d",
+ i, j, k);
+ for (n = 0; n < NTESTS; n++) {
+ ASSERT_INT_EQ(bitmap_set_bit(b, n), 0);
+ ASSERT_INT_EQ(BN_set_bit(bn, n), 1);
+ }
+ if (i >= 0) {
+ bitmap_clear_bit(b, i);
+ BN_clear_bit(bn, i);
+ }
+ if (j >= 0) {
+ bitmap_clear_bit(b, j);
+ BN_clear_bit(bn, j);
+ }
+ if (k >= 0) {
+ bitmap_clear_bit(b, k);
+ BN_clear_bit(bn, k);
+ }
+ for (n = 0; n < NTESTS; n++) {
+ ASSERT_INT_EQ(BN_is_bit_set(bn, n),
+ bitmap_test_bit(b, n));
+ }
+ }
+ }
+ }
+ bitmap_free(b);
+ BN_free(bn);
+ TEST_DONE();
+#endif
+}
+
diff --git a/regress/unittests/conversion/Makefile b/regress/unittests/conversion/Makefile
new file mode 100644
index 0000000..5793c49
--- /dev/null
+++ b/regress/unittests/conversion/Makefile
@@ -0,0 +1,16 @@
+# $OpenBSD: Makefile,v 1.4 2021/01/09 12:24:30 dtucker Exp $
+
+PROG=test_conversion
+SRCS=tests.c
+
+# From usr.bin/ssh
+SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c
+SRCS+=atomicio.c misc.c xmalloc.c log.c uidswap.c cleanup.c fatal.c ssherr.c
+SRCS+=match.c addr.c addrmatch.c
+
+REGRESS_TARGETS=run-regress-${PROG}
+
+run-regress-${PROG}: ${PROG}
+ env ${TEST_ENV} ./${PROG}
+
+.include <bsd.regress.mk>
diff --git a/regress/unittests/conversion/tests.c b/regress/unittests/conversion/tests.c
new file mode 100644
index 0000000..5b526f7
--- /dev/null
+++ b/regress/unittests/conversion/tests.c
@@ -0,0 +1,52 @@
+/* $OpenBSD: tests.c,v 1.4 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for conversions
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "misc.h"
+
+void
+tests(void)
+{
+ char buf[1024];
+
+ TEST_START("conversion_convtime");
+ ASSERT_INT_EQ(convtime("0"), 0);
+ ASSERT_INT_EQ(convtime("1"), 1);
+ ASSERT_INT_EQ(convtime("1S"), 1);
+ /* from the examples in the comment above the function */
+ ASSERT_INT_EQ(convtime("90m"), 5400);
+ ASSERT_INT_EQ(convtime("1h30m"), 5400);
+ ASSERT_INT_EQ(convtime("2d"), 172800);
+ ASSERT_INT_EQ(convtime("1w"), 604800);
+
+ /* negative time is not allowed */
+ ASSERT_INT_EQ(convtime("-7"), -1);
+ ASSERT_INT_EQ(convtime("-9d"), -1);
+
+ /* overflow */
+ snprintf(buf, sizeof buf, "%llu", (unsigned long long)INT_MAX);
+ ASSERT_INT_EQ(convtime(buf), INT_MAX);
+ snprintf(buf, sizeof buf, "%llu", (unsigned long long)INT_MAX + 1);
+ ASSERT_INT_EQ(convtime(buf), -1);
+
+ /* overflow with multiplier */
+ snprintf(buf, sizeof buf, "%lluM", (unsigned long long)INT_MAX/60 + 1);
+ ASSERT_INT_EQ(convtime(buf), -1);
+ ASSERT_INT_EQ(convtime("1000000000000000000000w"), -1);
+ TEST_DONE();
+}
diff --git a/regress/unittests/hostkeys/Makefile b/regress/unittests/hostkeys/Makefile
new file mode 100644
index 0000000..04d9335
--- /dev/null
+++ b/regress/unittests/hostkeys/Makefile
@@ -0,0 +1,25 @@
+# $OpenBSD: Makefile,v 1.10 2023/01/15 23:35:10 djm Exp $
+
+PROG=test_hostkeys
+SRCS=tests.c test_iterate.c
+
+# From usr.bin/ssh
+SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c
+SRCS+=sshbuf-io.c atomicio.c sshkey.c authfile.c cipher.c log.c ssh-rsa.c
+SRCS+=ssh-dss.c ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c
+SRCS+=ssherr.c uidswap.c cleanup.c xmalloc.c match.c krl.c fatal.c
+SRCS+=addr.c addrmatch.c bitmap.c hostfile.c
+SRCS+=ed25519.c hash.c
+SRCS+=cipher-chachapoly.c chacha.c poly1305.c ssh-ecdsa-sk.c ssh-sk.c
+SRCS+=ssh-ed25519-sk.c sk-usbhid.c
+
+SRCS+=digest-openssl.c
+#SRCS+=digest-libc.c
+SRCS+=utf8.c
+
+REGRESS_TARGETS=run-regress-${PROG}
+
+run-regress-${PROG}: ${PROG}
+ env ${TEST_ENV} ./${PROG} -d ${.CURDIR}/testdata
+
+.include <bsd.regress.mk>
diff --git a/regress/unittests/hostkeys/mktestdata.sh b/regress/unittests/hostkeys/mktestdata.sh
new file mode 100644
index 0000000..5a46de9
--- /dev/null
+++ b/regress/unittests/hostkeys/mktestdata.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+# $OpenBSD: mktestdata.sh,v 1.2 2017/04/30 23:33:48 djm Exp $
+
+set -ex
+
+cd testdata
+
+rm -f rsa* dsa* ecdsa* ed25519*
+rm -f known_hosts*
+
+gen_all() {
+ _n=$1
+ _ecdsa_bits=256
+ test "x$_n" = "x1" && _ecdsa_bits=384
+ test "x$_n" = "x2" && _ecdsa_bits=521
+ ssh-keygen -qt rsa -b 1024 -C "RSA #$_n" -N "" -f rsa_$_n
+ ssh-keygen -qt dsa -b 1024 -C "DSA #$_n" -N "" -f dsa_$_n
+ ssh-keygen -qt ecdsa -b $_ecdsa_bits -C "ECDSA #$_n" -N "" -f ecdsa_$_n
+ ssh-keygen -qt ed25519 -C "ED25519 #$_n" -N "" -f ed25519_$_n
+ # Don't need private keys
+ rm -f rsa_$_n dsa_$_n ecdsa_$_n ed25519_$_n
+}
+
+hentries() {
+ _preamble=$1
+ _kspec=$2
+ for k in `ls -1 $_kspec | sort` ; do
+ printf "$_preamble "
+ cat $k
+ done
+ echo
+}
+
+gen_all 1
+gen_all 2
+gen_all 3
+gen_all 4
+gen_all 5
+gen_all 6
+
+# A section of known_hosts with hashed hostnames.
+(
+ hentries "sisyphus.example.com" "*_5.pub"
+ hentries "prometheus.example.com,192.0.2.1,2001:db8::1" "*_6.pub"
+) > known_hosts_hash_frag
+ssh-keygen -Hf known_hosts_hash_frag
+rm -f known_hosts_hash_frag.old
+
+# Populated known_hosts, including comments, hashed names and invalid lines
+(
+ echo "# Plain host keys, plain host names"
+ hentries "sisyphus.example.com" "*_1.pub"
+
+ echo "# Plain host keys, hostnames + addresses"
+ hentries "prometheus.example.com,192.0.2.1,2001:db8::1" "*_2.pub"
+
+ echo "# Some hosts with wildcard names / IPs"
+ hentries "*.example.com,192.0.2.*,2001:*" "*_3.pub"
+
+ echo "# Hashed hostname and address entries"
+ cat known_hosts_hash_frag
+ rm -f known_hosts_hash_frag
+ echo
+
+ echo "# Revoked and CA keys"
+ printf "@revoked sisyphus.example.com " ; cat ed25519_4.pub
+ printf "@cert-authority prometheus.example.com " ; cat ecdsa_4.pub
+ printf "@cert-authority *.example.com " ; cat dsa_4.pub
+
+ printf "\n"
+ echo "# Some invalid lines"
+ # Invalid marker
+ printf "@what sisyphus.example.com " ; cat dsa_1.pub
+ # Key missing
+ echo "sisyphus.example.com "
+ # Key blob missing
+ echo "prometheus.example.com ssh-ed25519 "
+ # Key blob truncated
+ echo "sisyphus.example.com ssh-dsa AAAATgAAAAdz"
+ # Invalid type
+ echo "sisyphus.example.com ssh-XXX AAAATgAAAAdzc2gtWFhYAAAAP0ZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRg=="
+ # Type mismatch with blob
+ echo "prometheus.example.com ssh-rsa AAAATgAAAAdzc2gtWFhYAAAAP0ZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRg=="
+) > known_hosts
+
+echo OK
diff --git a/regress/unittests/hostkeys/test_iterate.c b/regress/unittests/hostkeys/test_iterate.c
new file mode 100644
index 0000000..84f26b5
--- /dev/null
+++ b/regress/unittests/hostkeys/test_iterate.c
@@ -0,0 +1,1117 @@
+/* $OpenBSD: test_iterate.c,v 1.8 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for hostfile.h hostkeys_foreach()
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "sshkey.h"
+#include "authfile.h"
+#include "hostfile.h"
+
+struct expected {
+ const char *key_file; /* Path for key, NULL for none */
+ int no_parse_status; /* Expected status w/o key parsing */
+ int no_parse_keytype; /* Expected keytype w/o key parsing */
+ int match_host_p; /* Match 'prometheus.example.com' */
+ int match_host_s; /* Match 'sisyphus.example.com' */
+ int match_ipv4; /* Match '192.0.2.1' */
+ int match_ipv6; /* Match '2001:db8::1' */
+ int match_flags; /* Expected flags from match */
+ struct hostkey_foreach_line l; /* Expected line contents */
+};
+
+struct cbctx {
+ const struct expected *expected;
+ size_t nexpected;
+ size_t i;
+ int flags;
+ int match_host_p;
+ int match_host_s;
+ int match_ipv4;
+ int match_ipv6;
+};
+
+/*
+ * hostkeys_foreach() iterator callback that verifies the line passed
+ * against an array of expected entries.
+ */
+static int
+check(struct hostkey_foreach_line *l, void *_ctx)
+{
+ struct cbctx *ctx = (struct cbctx *)_ctx;
+ const struct expected *expected;
+ int parse_key = (ctx->flags & HKF_WANT_PARSE_KEY) != 0;
+ const int matching = (ctx->flags & HKF_WANT_MATCH) != 0;
+ u_int expected_status, expected_match;
+ int expected_keytype, skip = 0;
+
+ test_subtest_info("entry %zu/%zu, file line %ld",
+ ctx->i + 1, ctx->nexpected, l->linenum);
+
+ for (;;) {
+ ASSERT_SIZE_T_LT(ctx->i, ctx->nexpected);
+ expected = ctx->expected + ctx->i++;
+ /* If we are matching host/IP then skip entries that don't */
+ if (!matching)
+ break;
+ if (ctx->match_host_p && expected->match_host_p)
+ break;
+ if (ctx->match_host_s && expected->match_host_s)
+ break;
+ if (ctx->match_ipv4 && expected->match_ipv4)
+ break;
+ if (ctx->match_ipv6 && expected->match_ipv6)
+ break;
+ }
+ expected_status = (parse_key || expected->no_parse_status < 0) ?
+ expected->l.status : (u_int)expected->no_parse_status;
+ expected_match = expected->l.match;
+#define UPDATE_MATCH_STATUS(x) do { \
+ if (ctx->x && expected->x) { \
+ expected_match |= expected->x; \
+ if (expected_status == HKF_STATUS_OK) \
+ expected_status = HKF_STATUS_MATCHED; \
+ } \
+ } while (0)
+ expected_keytype = (parse_key || expected->no_parse_keytype < 0) ?
+ expected->l.keytype : expected->no_parse_keytype;
+
+#ifndef OPENSSL_HAS_ECC
+ if (expected->l.keytype == KEY_ECDSA ||
+ expected->no_parse_keytype == KEY_ECDSA)
+ skip = 1;
+#endif /* OPENSSL_HAS_ECC */
+#ifndef WITH_OPENSSL
+ if (expected->l.keytype == KEY_DSA ||
+ expected->no_parse_keytype == KEY_DSA ||
+ expected->l.keytype == KEY_RSA ||
+ expected->no_parse_keytype == KEY_RSA ||
+ expected->l.keytype == KEY_ECDSA ||
+ expected->no_parse_keytype == KEY_ECDSA)
+ skip = 1;
+#endif /* WITH_OPENSSL */
+ if (skip) {
+ expected_status = HKF_STATUS_INVALID;
+ expected_keytype = KEY_UNSPEC;
+ parse_key = 0;
+ }
+ UPDATE_MATCH_STATUS(match_host_p);
+ UPDATE_MATCH_STATUS(match_host_s);
+ UPDATE_MATCH_STATUS(match_ipv4);
+ UPDATE_MATCH_STATUS(match_ipv6);
+
+ ASSERT_PTR_NE(l->path, NULL); /* Don't care about path */
+ ASSERT_LONG_LONG_EQ(l->linenum, expected->l.linenum);
+ ASSERT_U_INT_EQ(l->status, expected_status);
+ ASSERT_U_INT_EQ(l->match, expected_match);
+ /* Not all test entries contain fulltext */
+ if (expected->l.line != NULL)
+ ASSERT_STRING_EQ(l->line, expected->l.line);
+ ASSERT_INT_EQ(l->marker, expected->l.marker);
+ /* XXX we skip hashed hostnames for now; implement checking */
+ if (expected->l.hosts != NULL)
+ ASSERT_STRING_EQ(l->hosts, expected->l.hosts);
+ /* Not all test entries contain raw keys */
+ if (expected->l.rawkey != NULL)
+ ASSERT_STRING_EQ(l->rawkey, expected->l.rawkey);
+ /* XXX synthesise raw key for cases lacking and compare */
+ ASSERT_INT_EQ(l->keytype, expected_keytype);
+ if (parse_key) {
+ if (expected->l.key == NULL)
+ ASSERT_PTR_EQ(l->key, NULL);
+ if (expected->l.key != NULL) {
+ ASSERT_PTR_NE(l->key, NULL);
+ ASSERT_INT_EQ(sshkey_equal(l->key, expected->l.key), 1);
+ }
+ }
+ if (parse_key && !(l->comment == NULL && expected->l.comment == NULL))
+ ASSERT_STRING_EQ(l->comment, expected->l.comment);
+ return 0;
+}
+
+/* Loads public keys for a set of expected results */
+static void
+prepare_expected(struct expected *expected, size_t n)
+{
+ size_t i;
+
+ for (i = 0; i < n; i++) {
+ if (expected[i].key_file == NULL)
+ continue;
+#ifndef OPENSSL_HAS_ECC
+ if (expected[i].l.keytype == KEY_ECDSA)
+ continue;
+#endif /* OPENSSL_HAS_ECC */
+#ifndef WITH_OPENSSL
+ switch (expected[i].l.keytype) {
+ case KEY_RSA:
+ case KEY_DSA:
+ case KEY_ECDSA:
+ continue;
+ }
+#endif /* WITH_OPENSSL */
+ ASSERT_INT_EQ(sshkey_load_public(
+ test_data_file(expected[i].key_file), &expected[i].l.key,
+ NULL), 0);
+ }
+}
+
+static void
+cleanup_expected(struct expected *expected, size_t n)
+{
+ size_t i;
+
+ for (i = 0; i < n; i++) {
+ sshkey_free(expected[i].l.key);
+ expected[i].l.key = NULL;
+ }
+}
+
+struct expected expected_full[] = {
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL, /* path, don't care */
+ 1, /* line number */
+ HKF_STATUS_COMMENT, /* status */
+ 0, /* match flags */
+ "# Plain host keys, plain host names", /* full line, optional */
+ MRK_NONE, /* marker (CA / revoked) */
+ NULL, /* hosts text */
+ NULL, /* raw key, optional */
+ KEY_UNSPEC, /* key type */
+ NULL, /* deserialised key */
+ NULL, /* comment */
+ 0, /* note */
+ } },
+ { "dsa_1.pub" , -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, {
+ NULL,
+ 2,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ "sisyphus.example.com",
+ NULL,
+ KEY_DSA,
+ NULL, /* filled at runtime */
+ "DSA #1",
+ 0,
+ } },
+ { "ecdsa_1.pub" , -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, {
+ NULL,
+ 3,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ "sisyphus.example.com",
+ NULL,
+ KEY_ECDSA,
+ NULL, /* filled at runtime */
+ "ECDSA #1",
+ 0,
+ } },
+ { "ed25519_1.pub" , -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, {
+ NULL,
+ 4,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ "sisyphus.example.com",
+ NULL,
+ KEY_ED25519,
+ NULL, /* filled at runtime */
+ "ED25519 #1",
+ 0,
+ } },
+ { "rsa_1.pub" , -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, {
+ NULL,
+ 5,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ "sisyphus.example.com",
+ NULL,
+ KEY_RSA,
+ NULL, /* filled at runtime */
+ "RSA #1",
+ 0,
+ } },
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL,
+ 6,
+ HKF_STATUS_COMMENT,
+ 0,
+ "",
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL,
+ 7,
+ HKF_STATUS_COMMENT,
+ 0,
+ "# Plain host keys, hostnames + addresses",
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { "dsa_2.pub" , -1, -1, HKF_MATCH_HOST, 0, HKF_MATCH_IP, HKF_MATCH_IP, -1, {
+ NULL,
+ 8,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ "prometheus.example.com,192.0.2.1,2001:db8::1",
+ NULL,
+ KEY_DSA,
+ NULL, /* filled at runtime */
+ "DSA #2",
+ 0,
+ } },
+ { "ecdsa_2.pub" , -1, -1, HKF_MATCH_HOST, 0, HKF_MATCH_IP, HKF_MATCH_IP, -1, {
+ NULL,
+ 9,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ "prometheus.example.com,192.0.2.1,2001:db8::1",
+ NULL,
+ KEY_ECDSA,
+ NULL, /* filled at runtime */
+ "ECDSA #2",
+ 0,
+ } },
+ { "ed25519_2.pub" , -1, -1, HKF_MATCH_HOST, 0, HKF_MATCH_IP, HKF_MATCH_IP, -1, {
+ NULL,
+ 10,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ "prometheus.example.com,192.0.2.1,2001:db8::1",
+ NULL,
+ KEY_ED25519,
+ NULL, /* filled at runtime */
+ "ED25519 #2",
+ 0,
+ } },
+ { "rsa_2.pub" , -1, -1, HKF_MATCH_HOST, 0, HKF_MATCH_IP, HKF_MATCH_IP, -1, {
+ NULL,
+ 11,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ "prometheus.example.com,192.0.2.1,2001:db8::1",
+ NULL,
+ KEY_RSA,
+ NULL, /* filled at runtime */
+ "RSA #2",
+ 0,
+ } },
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL,
+ 12,
+ HKF_STATUS_COMMENT,
+ 0,
+ "",
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL,
+ 13,
+ HKF_STATUS_COMMENT,
+ 0,
+ "# Some hosts with wildcard names / IPs",
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { "dsa_3.pub" , -1, -1, HKF_MATCH_HOST, HKF_MATCH_HOST, HKF_MATCH_IP, HKF_MATCH_IP, -1, {
+ NULL,
+ 14,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ "*.example.com,192.0.2.*,2001:*",
+ NULL,
+ KEY_DSA,
+ NULL, /* filled at runtime */
+ "DSA #3",
+ 0,
+ } },
+ { "ecdsa_3.pub" , -1, -1, HKF_MATCH_HOST, HKF_MATCH_HOST, HKF_MATCH_IP, HKF_MATCH_IP, -1, {
+ NULL,
+ 15,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ "*.example.com,192.0.2.*,2001:*",
+ NULL,
+ KEY_ECDSA,
+ NULL, /* filled at runtime */
+ "ECDSA #3",
+ 0,
+ } },
+ { "ed25519_3.pub" , -1, -1, HKF_MATCH_HOST, HKF_MATCH_HOST, HKF_MATCH_IP, HKF_MATCH_IP, -1, {
+ NULL,
+ 16,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ "*.example.com,192.0.2.*,2001:*",
+ NULL,
+ KEY_ED25519,
+ NULL, /* filled at runtime */
+ "ED25519 #3",
+ 0,
+ } },
+ { "rsa_3.pub" , -1, -1, HKF_MATCH_HOST, HKF_MATCH_HOST, HKF_MATCH_IP, HKF_MATCH_IP, -1, {
+ NULL,
+ 17,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ "*.example.com,192.0.2.*,2001:*",
+ NULL,
+ KEY_RSA,
+ NULL, /* filled at runtime */
+ "RSA #3",
+ 0,
+ } },
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL,
+ 18,
+ HKF_STATUS_COMMENT,
+ 0,
+ "",
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL,
+ 19,
+ HKF_STATUS_COMMENT,
+ 0,
+ "# Hashed hostname and address entries",
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { "dsa_5.pub" , -1, -1, 0, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, -1, {
+ NULL,
+ 20,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_DSA,
+ NULL, /* filled at runtime */
+ "DSA #5",
+ 0,
+ } },
+ { "ecdsa_5.pub" , -1, -1, 0, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, -1, {
+ NULL,
+ 21,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_ECDSA,
+ NULL, /* filled at runtime */
+ "ECDSA #5",
+ 0,
+ } },
+ { "ed25519_5.pub" , -1, -1, 0, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, -1, {
+ NULL,
+ 22,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_ED25519,
+ NULL, /* filled at runtime */
+ "ED25519 #5",
+ 0,
+ } },
+ { "rsa_5.pub" , -1, -1, 0, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, -1, {
+ NULL,
+ 23,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_RSA,
+ NULL, /* filled at runtime */
+ "RSA #5",
+ 0,
+ } },
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL,
+ 24,
+ HKF_STATUS_COMMENT,
+ 0,
+ "",
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ /*
+ * The next series have each key listed multiple times, as the
+ * hostname and addresses in the pre-hashed known_hosts are split
+ * to separate lines.
+ */
+ { "dsa_6.pub" , -1, -1, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, 0, -1, {
+ NULL,
+ 25,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_DSA,
+ NULL, /* filled at runtime */
+ "DSA #6",
+ 0,
+ } },
+ { "dsa_6.pub" , -1, -1, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, 0, -1, {
+ NULL,
+ 26,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_DSA,
+ NULL, /* filled at runtime */
+ "DSA #6",
+ 0,
+ } },
+ { "dsa_6.pub" , -1, -1, 0, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, -1, {
+ NULL,
+ 27,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_DSA,
+ NULL, /* filled at runtime */
+ "DSA #6",
+ 0,
+ } },
+ { "ecdsa_6.pub" , -1, -1, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, 0, -1, {
+ NULL,
+ 28,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_ECDSA,
+ NULL, /* filled at runtime */
+ "ECDSA #6",
+ 0,
+ } },
+ { "ecdsa_6.pub" , -1, -1, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, 0, -1, {
+ NULL,
+ 29,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_ECDSA,
+ NULL, /* filled at runtime */
+ "ECDSA #6",
+ 0,
+ } },
+ { "ecdsa_6.pub" , -1, -1, 0, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, -1, {
+ NULL,
+ 30,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_ECDSA,
+ NULL, /* filled at runtime */
+ "ECDSA #6",
+ 0,
+ } },
+ { "ed25519_6.pub" , -1, -1, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, 0, -1, {
+ NULL,
+ 31,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_ED25519,
+ NULL, /* filled at runtime */
+ "ED25519 #6",
+ 0,
+ } },
+ { "ed25519_6.pub" , -1, -1, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, 0, -1, {
+ NULL,
+ 32,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_ED25519,
+ NULL, /* filled at runtime */
+ "ED25519 #6",
+ 0,
+ } },
+ { "ed25519_6.pub" , -1, -1, 0, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, -1, {
+ NULL,
+ 33,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_ED25519,
+ NULL, /* filled at runtime */
+ "ED25519 #6",
+ 0,
+ } },
+ { "rsa_6.pub" , -1, -1, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, 0, -1, {
+ NULL,
+ 34,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_RSA,
+ NULL, /* filled at runtime */
+ "RSA #6",
+ 0,
+ } },
+ { "rsa_6.pub" , -1, -1, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, 0, -1, {
+ NULL,
+ 35,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_RSA,
+ NULL, /* filled at runtime */
+ "RSA #6",
+ 0,
+ } },
+ { "rsa_6.pub" , -1, -1, 0, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, -1, {
+ NULL,
+ 36,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_RSA,
+ NULL, /* filled at runtime */
+ "RSA #6",
+ 0,
+ } },
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL,
+ 37,
+ HKF_STATUS_COMMENT,
+ 0,
+ "",
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL,
+ 38,
+ HKF_STATUS_COMMENT,
+ 0,
+ "",
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL,
+ 39,
+ HKF_STATUS_COMMENT,
+ 0,
+ "# Revoked and CA keys",
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { "ed25519_4.pub" , -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, {
+ NULL,
+ 40,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_REVOKE,
+ "sisyphus.example.com",
+ NULL,
+ KEY_ED25519,
+ NULL, /* filled at runtime */
+ "ED25519 #4",
+ 0,
+ } },
+ { "ecdsa_4.pub" , -1, -1, HKF_MATCH_HOST, 0, 0, 0, -1, {
+ NULL,
+ 41,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_CA,
+ "prometheus.example.com",
+ NULL,
+ KEY_ECDSA,
+ NULL, /* filled at runtime */
+ "ECDSA #4",
+ 0,
+ } },
+ { "dsa_4.pub" , -1, -1, HKF_MATCH_HOST, HKF_MATCH_HOST, 0, 0, -1, {
+ NULL,
+ 42,
+ HKF_STATUS_OK,
+ 0,
+ NULL,
+ MRK_CA,
+ "*.example.com",
+ NULL,
+ KEY_DSA,
+ NULL, /* filled at runtime */
+ "DSA #4",
+ 0,
+ } },
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL,
+ 43,
+ HKF_STATUS_COMMENT,
+ 0,
+ "",
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL,
+ 44,
+ HKF_STATUS_COMMENT,
+ 0,
+ "# Some invalid lines",
+ MRK_NONE,
+ NULL,
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { NULL, -1, -1, 0, 0, 0, 0, -1, {
+ NULL,
+ 45,
+ HKF_STATUS_INVALID,
+ 0,
+ NULL,
+ MRK_ERROR,
+ NULL,
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { NULL, -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, {
+ NULL,
+ 46,
+ HKF_STATUS_INVALID,
+ 0,
+ NULL,
+ MRK_NONE,
+ "sisyphus.example.com",
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { NULL, -1, -1, HKF_MATCH_HOST, 0, 0, 0, -1, {
+ NULL,
+ 47,
+ HKF_STATUS_INVALID,
+ 0,
+ NULL,
+ MRK_NONE,
+ "prometheus.example.com",
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { NULL, -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, {
+ NULL,
+ 48,
+ HKF_STATUS_INVALID, /* Would be ok if key not parsed */
+ 0,
+ NULL,
+ MRK_NONE,
+ "sisyphus.example.com",
+ NULL,
+ KEY_UNSPEC,
+ NULL,
+ NULL,
+ 0,
+ } },
+ { NULL, -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, {
+ NULL,
+ 49,
+ HKF_STATUS_INVALID,
+ 0,
+ NULL,
+ MRK_NONE,
+ "sisyphus.example.com",
+ NULL,
+ KEY_UNSPEC,
+ NULL, /* filled at runtime */
+ NULL,
+ 0,
+ } },
+ { NULL, HKF_STATUS_OK, KEY_RSA, HKF_MATCH_HOST, 0, 0, 0, -1, {
+ NULL,
+ 50,
+ HKF_STATUS_INVALID, /* Would be ok if key not parsed */
+ 0,
+ NULL,
+ MRK_NONE,
+ "prometheus.example.com",
+ NULL,
+ KEY_UNSPEC,
+ NULL, /* filled at runtime */
+ NULL,
+ 0,
+ } },
+};
+
+void test_iterate(void);
+
+void
+test_iterate(void)
+{
+ struct cbctx ctx;
+
+ TEST_START("hostkeys_iterate all with key parse");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = HKF_WANT_PARSE_KEY;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, NULL, NULL, ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate all without key parse");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = 0;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, NULL, NULL, ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate specify host 1");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = 0;
+ ctx.match_host_p = 1;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "prometheus.example.com", NULL, ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate specify host 2");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = 0;
+ ctx.match_host_s = 1;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "sisyphus.example.com", NULL, ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate match host 1");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = HKF_WANT_MATCH;
+ ctx.match_host_p = 1;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "prometheus.example.com", NULL, ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate match host 2");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = HKF_WANT_MATCH;
+ ctx.match_host_s = 1;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "sisyphus.example.com", NULL, ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate specify host missing");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = 0;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "actaeon.example.org", NULL, ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate match host missing");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = HKF_WANT_MATCH;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "actaeon.example.org", NULL, ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate specify IPv4");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = 0;
+ ctx.match_ipv4 = 1;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "tiresias.example.org", "192.0.2.1", ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate specify IPv6");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = 0;
+ ctx.match_ipv6 = 1;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "tiresias.example.org", "2001:db8::1",
+ ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate match IPv4");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = HKF_WANT_MATCH;
+ ctx.match_ipv4 = 1;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "tiresias.example.org", "192.0.2.1", ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate match IPv6");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = HKF_WANT_MATCH;
+ ctx.match_ipv6 = 1;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "tiresias.example.org", "2001:db8::1",
+ ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate specify addr missing");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = 0;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "tiresias.example.org", "192.168.0.1",
+ ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate match addr missing");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = HKF_WANT_MATCH;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "tiresias.example.org", "::1", ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate specify host 2 and IPv4");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = 0;
+ ctx.match_host_s = 1;
+ ctx.match_ipv4 = 1;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "sisyphus.example.com", "192.0.2.1", ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate match host 1 and IPv6");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = HKF_WANT_MATCH;
+ ctx.match_host_p = 1;
+ ctx.match_ipv6 = 1;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "prometheus.example.com",
+ "2001:db8::1", ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate specify host 2 and IPv4 w/ key parse");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = HKF_WANT_PARSE_KEY;
+ ctx.match_host_s = 1;
+ ctx.match_ipv4 = 1;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "sisyphus.example.com", "192.0.2.1", ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+
+ TEST_START("hostkeys_iterate match host 1 and IPv6 w/ key parse");
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.expected = expected_full;
+ ctx.nexpected = sizeof(expected_full)/sizeof(*expected_full);
+ ctx.flags = HKF_WANT_MATCH|HKF_WANT_PARSE_KEY;
+ ctx.match_host_p = 1;
+ ctx.match_ipv6 = 1;
+ prepare_expected(expected_full, ctx.nexpected);
+ ASSERT_INT_EQ(hostkeys_foreach(test_data_file("known_hosts"),
+ check, &ctx, "prometheus.example.com",
+ "2001:db8::1", ctx.flags, 0), 0);
+ cleanup_expected(expected_full, ctx.nexpected);
+ TEST_DONE();
+}
+
diff --git a/regress/unittests/hostkeys/testdata/dsa_1.pub b/regress/unittests/hostkeys/testdata/dsa_1.pub
new file mode 100644
index 0000000..56e1e37
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/dsa_1.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAOqffHxEW4c+Z9q/r3l4sYK8F7qrBsU8XF9upGsW62T9InROFFq9IO0x3pQ6mDA0Wtw0sqcDmkPCHPyP4Ok/fU3/drLaZusHoVYu8pBBrWsIDrKgkeX9TEodBsSrYdl4Sqtqq9EZv9+DttV6LStZrgYyUTOKwOF95wGantpLynX5AAAAFQDdt+zjRNlETDsgmxcSYFgREirJrQAAAIBQlrPaiPhR24FhnMLcHH4016vL7AqDDID6Qw7PhbXGa4/XlxWMIigjBKrIPKvnZ6p712LSnCKtcbfdx0MtmJlNa01CYqPaRhgRaf+uGdvTkTUcdaq8R5lLJL+JMNwUhcC8ijm3NqEjXjffuebGe1EzIeiITbA7Nndcd+GytwRDegAAAIEAkRYPjSVcUxfUHhHdpP6V8CuY1+CYSs9EPJ7iiWTDuXWVIBTU32oJLAnrmAcOwtIzEfPvm+rff5FI/Yhon2pB3VTXhPPEBjYzE5qANanAT4e6tzAVc5f3DUhHaDknwRYfDz86GFvuLtDjeE/UZ9t6OofYoEsCBpYozLAprBvNIQY= DSA #1
diff --git a/regress/unittests/hostkeys/testdata/dsa_2.pub b/regress/unittests/hostkeys/testdata/dsa_2.pub
new file mode 100644
index 0000000..394e0bf
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/dsa_2.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAI38Hy/61/O5Bp6yUG8J5XQCeNjRS0xvjlCdzKLyXCueMa+L+X2L/u9PWUsy5SVbTjGgpB8sF6UkCNsV+va7S8zCCHas2MZ7GPlxP6GZBkRPTIFR0N/Pu7wfBzDQz0t0iL4VmxBfTBQv/SxkGWZg+yHihIQP9fwdSAwD/7aVh6ItAAAAFQDSyihIUlINlswM0PJ8wXSti3yIMwAAAIB+oqzaB6ozqs8YxpN5oQOBa/9HEBQEsp8RSIlQmVubXRNgktp42n+Ii1waU9UUk8DX5ahhIeR6B7ojWkqmDAji4SKpoHf4kmr6HvYo85ZSTSx0W4YK/gJHSpDJwhlT52tAfb1JCbWSObjl09B4STv7KedCHcR5oXQvvrV+XoKOSAAAAIAue/EXrs2INw1RfaKNHC0oqOMxmRitv0BFMuNVPo1VDj39CE5kA7AHjwvS1TNeaHtK5Hhgeb6vsmLmNPTOc8xCob0ilyQbt9O0GbONeF2Ge7D2UJyULA/hxql+tCYFIC6yUrmo35fF9XiNisXLoaflk9fjp7ROWWVwnki/jstaQw== DSA #2
diff --git a/regress/unittests/hostkeys/testdata/dsa_3.pub b/regress/unittests/hostkeys/testdata/dsa_3.pub
new file mode 100644
index 0000000..e506ea4
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/dsa_3.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAI6lz2Ip9bzE7TGuDD4SjO9S4Ac90gq0h6ai1O06eI8t/Ot2uJ5Jk2QyVr2jvIZHDl/5bwBx7+5oyjlwRoUrAPPD814wf5tU2tSnmdu1Wbf0cBswif5q0r4tevzmopp/AtgH11QHo3u0/pfyJd10qBDLV2FaYSKMmZvyPfZJ0s9pAAAAFQD5Eqjl6Rx2qVePodD9OwAPT0bU6wAAAIAfnDm6csZF0sFaJR3NIJvaYgSGr8s7cqlsk2gLltB/1wOOO2yX+NeEC+B0H93hlMfaUsPa08bwgmYxnavSMqEBpmtPceefJiEd68zwYqXd38f88wyWZ9Z5iwaI/6OVZPHzCbDxOa4ewVTevRNYUKP1xUTZNT8/gSMfZLYPk4T2AQAAAIAUKroozRMyV+3V/rxt0gFnNxRXBKk+9cl3vgsQ7ktkI9cYg7V1T2K0XF21AVMK9gODszy6PBJjV6ruXBV6TRiqIbQauivp3bHHKYsG6wiJNqwdbVwIjfvv8nn1qFoZQLXG3sdONr9NwN8KzrX89OV0BlR2dVM5qqp+YxOXymP9yg== DSA #3
diff --git a/regress/unittests/hostkeys/testdata/dsa_4.pub b/regress/unittests/hostkeys/testdata/dsa_4.pub
new file mode 100644
index 0000000..8552c38
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/dsa_4.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAKvjnFHm0VvMr5h2Zu3nURsxQKGoxm+DCzYDxRYcilK07Cm5c4XTrFbA2X86+9sGs++W7QRMcTJUYIg0a+UtIMtAjwORd6ZPXM2K5dBW+gh1oHyvKi767tWX7I2c+1ZPJDY95mUUfZQUEfdy9eGDSBmw/pSsveQ1ur6XNUh/MtP/AAAAFQDHnXk/9jBJAdce1pHtLWnbdPSGdQAAAIEAm2OLy8tZBfiEO3c3X1yyB/GTcDwrQCqRMDkhnsmrliec3dWkOfNTzu+MrdvF8ymTWLEqPpbMheYtvNyZ3TF0HO5W7aVBpdGZbOdOAIfB+6skqGbI8A5Up1d7dak/bSsqL2r5NjwbDOdq+1hBzzvbl/qjh+sQarV2zHrpKoQaV28AAACANtkBVedBbqIAdphCrN/LbUi9WlyuF9UZz+tlpVLYrj8GJVwnplV2tvOmUw6yP5/pzCimTsao8dpL5PWxm7fKxLWVxA+lEsA4WeC885CiZn8xhdaJOCN+NyJ2bqkz+4VPI7oDGBm0aFwUqJn+M1PiSgvI50XdF2dBsFRTRNY0wzA= DSA #4
diff --git a/regress/unittests/hostkeys/testdata/dsa_5.pub b/regress/unittests/hostkeys/testdata/dsa_5.pub
new file mode 100644
index 0000000..149e1ef
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/dsa_5.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBALrFy7w5ihlaOG+qR+6fj+vm5EQaO3qwxgACLcgH+VfShuOG4mkx8qFJmf+OZ3fh5iKngjNZfKtfcqI7zHWdk6378TQfQC52/kbZukjNXOLCpyNkogahcjA00onIoTK1RUDuMW28edAHwPFbpttXDTaqis+8JPMY8hZwsZGENCzTAAAAFQD6+It5vozwGgaN9ROYPMlByhi6jwAAAIBz2mcAC694vNzz9b6614gkX9d9E99PzJYfU1MPkXDziKg7MrjBw7Opd5y1jL09S3iL6lSTlHkKwVKvQ3pOwWRwXXRrKVus4I0STveoApm526jmp6mY0YEtqR98vMJ0v97h1ydt8FikKlihefCsnXVicb8887PXs2Y8C6GuFT3tfQAAAIBbmHtV5tPcrMRDkULhaQ/Whap2VKvT2DUhIHA7lx6oy/KpkltOpxDZOIGUHKqffGbiR7Jh01/y090AY5L2eCf0S2Ytx93+eADwVVpJbFJo6zSwfeey2Gm6L2oA+rCz9zTdmtZoekpD3/RAOQjnJIAPwbs7mXwabZTw4xRtiYIRrw== DSA #5
diff --git a/regress/unittests/hostkeys/testdata/dsa_6.pub b/regress/unittests/hostkeys/testdata/dsa_6.pub
new file mode 100644
index 0000000..edbb976
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/dsa_6.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAIutigAse65TCW6hHDOEGXenE9L4L0talHbs65hj3UUNtWflKdQeXLofqXgW8AwaDKmnuRPrxRoxVNXj84n45wtBEdt4ztmdAZteAbXSnHqpcxME3jDxh3EtxzGPXLs+RUmKPVguraSgo7W2oN7KFx6VM+AcAtxANSTlvDid3s47AAAAFQCd9Q3kkHSLWe77sW0eRaayI45ovwAAAIAw6srGF6xvFasI44Y3r9JJ2K+3ezozl3ldL3p2+p2HG3iWafC4SdV8pB6ZIxKlYAywiiFb3LzH/JweGFq1jtoFDRM3MlYORBevydU4zPz7b5QLDVB0sY4evYtWmg2BFJvoWRfhLnlZVW7h5N8v4fNIwdVmVsw4Ljes7iF2HRGhHgAAAIBDFT3fww2Oby1xUA6G9pDAcVikrQFqp1sJRylNTUyeyQ37SNAGzYxwHJFgQr8gZLdRQ1UW+idYpqVbVNcYFMOiw/zSqK2OfVwPZ9U+TTKdc992ChSup6vJEKM/ZVIyDWDbJr7igQ4ahy7jo9mFvm8ljN926EnspQzCvs0Dxk6tHA== DSA #6
diff --git a/regress/unittests/hostkeys/testdata/ecdsa_1.pub b/regress/unittests/hostkeys/testdata/ecdsa_1.pub
new file mode 100644
index 0000000..16a535b
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/ecdsa_1.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBF6yQEtD9yBw9gmDRf477WBBzvWhAa0ioBI3nbA4emKykj0RbuQd5C4XdQAEOZGzE7v//FcCjwB2wi+JH5eKkxCtN6CjohDASZ1huoIV2UVyYIicZJEEOg1IWjjphvaxtw== ECDSA #1
diff --git a/regress/unittests/hostkeys/testdata/ecdsa_2.pub b/regress/unittests/hostkeys/testdata/ecdsa_2.pub
new file mode 100644
index 0000000..d2bad11
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/ecdsa_2.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAB8qVcXwgBM92NCmReQlPrZAoui4Bz/mW0VUBFOpHXXW1n+15b/Y7Pc6UBd/ITTZmaBciXY+PWaSBGdwc5GdqGdLgFyJ/QAGrFMPNpVutm/82gNQzlxpNwjbMcKyiZEXzSgnjS6DzMQ0WuSMdzIBXq8OW/Kafxg4ZkU6YqALUXxlQMZuQ== ECDSA #2
diff --git a/regress/unittests/hostkeys/testdata/ecdsa_3.pub b/regress/unittests/hostkeys/testdata/ecdsa_3.pub
new file mode 100644
index 0000000..e3ea925
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/ecdsa_3.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIb3BhJZk+vUQPg5TQc1koIzuGqloCq7wjr9LjlhG24IBeiFHLsdWw74HDlH4DrOmlxToVYk2lTdnjARleRByjk= ECDSA #3
diff --git a/regress/unittests/hostkeys/testdata/ecdsa_4.pub b/regress/unittests/hostkeys/testdata/ecdsa_4.pub
new file mode 100644
index 0000000..2d616f5
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/ecdsa_4.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHZd0OXHIWwK3xnjAdMZ1tojxWycdu38pORO/UX5cqsKMgGCKQVBWWO3TFk1ePkGIE9VMWT1hCGqWRRwYlH+dSE= ECDSA #4
diff --git a/regress/unittests/hostkeys/testdata/ecdsa_5.pub b/regress/unittests/hostkeys/testdata/ecdsa_5.pub
new file mode 100644
index 0000000..a3df9b3
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/ecdsa_5.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIudcagzq4QPtP1jkpje34+0POLB0jwT64hqrbCqhTH2T800KDZ0h2vwlJYa3OP3Oqru9AB5pnuHsKw7mAhUGY= ECDSA #5
diff --git a/regress/unittests/hostkeys/testdata/ecdsa_6.pub b/regress/unittests/hostkeys/testdata/ecdsa_6.pub
new file mode 100644
index 0000000..139f5a7
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/ecdsa_6.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK1wRLyKtvK3Mmhd0XPkKwW4ev1KBVf8J4aG8lESq1TsaqqfOXYGyxMq5pN8fCGiD5UPOqyTYz/ZNzClRhJRHao= ECDSA #6
diff --git a/regress/unittests/hostkeys/testdata/ed25519_1.pub b/regress/unittests/hostkeys/testdata/ed25519_1.pub
new file mode 100644
index 0000000..0b12efe
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/ed25519_1.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK9ks7jkua5YWIwByRnnnc6UPJQWI75O0e/UJdPYU1JI ED25519 #1
diff --git a/regress/unittests/hostkeys/testdata/ed25519_2.pub b/regress/unittests/hostkeys/testdata/ed25519_2.pub
new file mode 100644
index 0000000..78e262b
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/ed25519_2.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIBp6PVW0z2o9C4Ukv/JOgmK7QMFe1pD1s3ADFF7IQob ED25519 #2
diff --git a/regress/unittests/hostkeys/testdata/ed25519_3.pub b/regress/unittests/hostkeys/testdata/ed25519_3.pub
new file mode 100644
index 0000000..64e5f12
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/ed25519_3.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBlYfExtYZAPqYvYdrlpGlSWhh/XNHcH3v3c2JzsVNbB ED25519 #3
diff --git a/regress/unittests/hostkeys/testdata/ed25519_4.pub b/regress/unittests/hostkeys/testdata/ed25519_4.pub
new file mode 100644
index 0000000..47b6724
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/ed25519_4.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFP8L9REfN/iYy1KIRtFqSCn3V2+vOCpoZYENFGLdOF ED25519 #4
diff --git a/regress/unittests/hostkeys/testdata/ed25519_5.pub b/regress/unittests/hostkeys/testdata/ed25519_5.pub
new file mode 100644
index 0000000..72ccae6
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/ed25519_5.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINf63qSV8rD57N+digID8t28WVhd3Yf2K2UhaoG8TsWQ ED25519 #5
diff --git a/regress/unittests/hostkeys/testdata/ed25519_6.pub b/regress/unittests/hostkeys/testdata/ed25519_6.pub
new file mode 100644
index 0000000..0f71973
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/ed25519_6.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPLW0ZwCkRQldpLa4I5BpwGa/om+WE6OgC8jdVqakt0Z ED25519 #6
diff --git a/regress/unittests/hostkeys/testdata/known_hosts b/regress/unittests/hostkeys/testdata/known_hosts
new file mode 100644
index 0000000..4446f45
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/known_hosts
@@ -0,0 +1,50 @@
+# Plain host keys, plain host names
+sisyphus.example.com ssh-dss AAAAB3NzaC1kc3MAAACBAOqffHxEW4c+Z9q/r3l4sYK8F7qrBsU8XF9upGsW62T9InROFFq9IO0x3pQ6mDA0Wtw0sqcDmkPCHPyP4Ok/fU3/drLaZusHoVYu8pBBrWsIDrKgkeX9TEodBsSrYdl4Sqtqq9EZv9+DttV6LStZrgYyUTOKwOF95wGantpLynX5AAAAFQDdt+zjRNlETDsgmxcSYFgREirJrQAAAIBQlrPaiPhR24FhnMLcHH4016vL7AqDDID6Qw7PhbXGa4/XlxWMIigjBKrIPKvnZ6p712LSnCKtcbfdx0MtmJlNa01CYqPaRhgRaf+uGdvTkTUcdaq8R5lLJL+JMNwUhcC8ijm3NqEjXjffuebGe1EzIeiITbA7Nndcd+GytwRDegAAAIEAkRYPjSVcUxfUHhHdpP6V8CuY1+CYSs9EPJ7iiWTDuXWVIBTU32oJLAnrmAcOwtIzEfPvm+rff5FI/Yhon2pB3VTXhPPEBjYzE5qANanAT4e6tzAVc5f3DUhHaDknwRYfDz86GFvuLtDjeE/UZ9t6OofYoEsCBpYozLAprBvNIQY= DSA #1
+sisyphus.example.com ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBF6yQEtD9yBw9gmDRf477WBBzvWhAa0ioBI3nbA4emKykj0RbuQd5C4XdQAEOZGzE7v//FcCjwB2wi+JH5eKkxCtN6CjohDASZ1huoIV2UVyYIicZJEEOg1IWjjphvaxtw== ECDSA #1
+sisyphus.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK9ks7jkua5YWIwByRnnnc6UPJQWI75O0e/UJdPYU1JI ED25519 #1
+sisyphus.example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDg4hB4vAZHJ0PVRiJajOv/GlytFWNpv5/9xgB9+5BIbvp8LOrFZ5D9K0Gsmwpd4G4rfaAz8j896DhMArg0vtkilIPPGt/6VzWMERgvaIQPJ/IE99X3+fjcAG56oAWwy29JX10lQMzBPU6XJIaN/zqpkb6qUBiAHBdLpxrFBBU0/w== RSA #1
+
+# Plain host keys, hostnames + addresses
+prometheus.example.com,192.0.2.1,2001:db8::1 ssh-dss AAAAB3NzaC1kc3MAAACBAI38Hy/61/O5Bp6yUG8J5XQCeNjRS0xvjlCdzKLyXCueMa+L+X2L/u9PWUsy5SVbTjGgpB8sF6UkCNsV+va7S8zCCHas2MZ7GPlxP6GZBkRPTIFR0N/Pu7wfBzDQz0t0iL4VmxBfTBQv/SxkGWZg+yHihIQP9fwdSAwD/7aVh6ItAAAAFQDSyihIUlINlswM0PJ8wXSti3yIMwAAAIB+oqzaB6ozqs8YxpN5oQOBa/9HEBQEsp8RSIlQmVubXRNgktp42n+Ii1waU9UUk8DX5ahhIeR6B7ojWkqmDAji4SKpoHf4kmr6HvYo85ZSTSx0W4YK/gJHSpDJwhlT52tAfb1JCbWSObjl09B4STv7KedCHcR5oXQvvrV+XoKOSAAAAIAue/EXrs2INw1RfaKNHC0oqOMxmRitv0BFMuNVPo1VDj39CE5kA7AHjwvS1TNeaHtK5Hhgeb6vsmLmNPTOc8xCob0ilyQbt9O0GbONeF2Ge7D2UJyULA/hxql+tCYFIC6yUrmo35fF9XiNisXLoaflk9fjp7ROWWVwnki/jstaQw== DSA #2
+prometheus.example.com,192.0.2.1,2001:db8::1 ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAB8qVcXwgBM92NCmReQlPrZAoui4Bz/mW0VUBFOpHXXW1n+15b/Y7Pc6UBd/ITTZmaBciXY+PWaSBGdwc5GdqGdLgFyJ/QAGrFMPNpVutm/82gNQzlxpNwjbMcKyiZEXzSgnjS6DzMQ0WuSMdzIBXq8OW/Kafxg4ZkU6YqALUXxlQMZuQ== ECDSA #2
+prometheus.example.com,192.0.2.1,2001:db8::1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIBp6PVW0z2o9C4Ukv/JOgmK7QMFe1pD1s3ADFF7IQob ED25519 #2
+prometheus.example.com,192.0.2.1,2001:db8::1 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDmbUhNabB5AmBDX6GNHZ3lbn7pRxqfpW+f53QqNGlK0sLV+0gkMIrOfUp1kdE2ZLE6tfzdicatj/RlH6/wuo4yyYb+Pyx3G0vxdmAIiA4aANq38XweDucBC0TZkRWVHK+Gs5V/uV0z7N0axJvkkJujMLvST3CRiiWwlficBc6yVQ== RSA #2
+
+# Some hosts with wildcard names / IPs
+*.example.com,192.0.2.*,2001:* ssh-dss AAAAB3NzaC1kc3MAAACBAI6lz2Ip9bzE7TGuDD4SjO9S4Ac90gq0h6ai1O06eI8t/Ot2uJ5Jk2QyVr2jvIZHDl/5bwBx7+5oyjlwRoUrAPPD814wf5tU2tSnmdu1Wbf0cBswif5q0r4tevzmopp/AtgH11QHo3u0/pfyJd10qBDLV2FaYSKMmZvyPfZJ0s9pAAAAFQD5Eqjl6Rx2qVePodD9OwAPT0bU6wAAAIAfnDm6csZF0sFaJR3NIJvaYgSGr8s7cqlsk2gLltB/1wOOO2yX+NeEC+B0H93hlMfaUsPa08bwgmYxnavSMqEBpmtPceefJiEd68zwYqXd38f88wyWZ9Z5iwaI/6OVZPHzCbDxOa4ewVTevRNYUKP1xUTZNT8/gSMfZLYPk4T2AQAAAIAUKroozRMyV+3V/rxt0gFnNxRXBKk+9cl3vgsQ7ktkI9cYg7V1T2K0XF21AVMK9gODszy6PBJjV6ruXBV6TRiqIbQauivp3bHHKYsG6wiJNqwdbVwIjfvv8nn1qFoZQLXG3sdONr9NwN8KzrX89OV0BlR2dVM5qqp+YxOXymP9yg== DSA #3
+*.example.com,192.0.2.*,2001:* ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIb3BhJZk+vUQPg5TQc1koIzuGqloCq7wjr9LjlhG24IBeiFHLsdWw74HDlH4DrOmlxToVYk2lTdnjARleRByjk= ECDSA #3
+*.example.com,192.0.2.*,2001:* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBlYfExtYZAPqYvYdrlpGlSWhh/XNHcH3v3c2JzsVNbB ED25519 #3
+*.example.com,192.0.2.*,2001:* ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDX8F93W3SH4ZSus4XUQ2cw9dqcuyUETTlKEeGv3zlknV3YCoe2Mp04naDhiuwj8sOsytrZSESzLY1ZEyzrjxE6ZFVv8NKgck/AbRjcwlRFOcx9oKUxOrXRa0IoXlTq0kyjKCJfaHBKnGitZThknCPTbVmpATkm5xx6J0WEDozfoQ== RSA #3
+
+# Hashed hostname and address entries
+|1|z3xOIdT5ue3Vuf3MzT67kaioqjw=|GZhhe5uwDOBQrC9N4cCjpbLpSn4= ssh-dss AAAAB3NzaC1kc3MAAACBALrFy7w5ihlaOG+qR+6fj+vm5EQaO3qwxgACLcgH+VfShuOG4mkx8qFJmf+OZ3fh5iKngjNZfKtfcqI7zHWdk6378TQfQC52/kbZukjNXOLCpyNkogahcjA00onIoTK1RUDuMW28edAHwPFbpttXDTaqis+8JPMY8hZwsZGENCzTAAAAFQD6+It5vozwGgaN9ROYPMlByhi6jwAAAIBz2mcAC694vNzz9b6614gkX9d9E99PzJYfU1MPkXDziKg7MrjBw7Opd5y1jL09S3iL6lSTlHkKwVKvQ3pOwWRwXXRrKVus4I0STveoApm526jmp6mY0YEtqR98vMJ0v97h1ydt8FikKlihefCsnXVicb8887PXs2Y8C6GuFT3tfQAAAIBbmHtV5tPcrMRDkULhaQ/Whap2VKvT2DUhIHA7lx6oy/KpkltOpxDZOIGUHKqffGbiR7Jh01/y090AY5L2eCf0S2Ytx93+eADwVVpJbFJo6zSwfeey2Gm6L2oA+rCz9zTdmtZoekpD3/RAOQjnJIAPwbs7mXwabZTw4xRtiYIRrw== DSA #5
+|1|B7t/AYabn8zgwU47Cb4A/Nqt3eI=|arQPZyRphkzisr7w6wwikvhaOyE= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIudcagzq4QPtP1jkpje34+0POLB0jwT64hqrbCqhTH2T800KDZ0h2vwlJYa3OP3Oqru9AB5pnuHsKw7mAhUGY= ECDSA #5
+|1|JR81WxEocTP5d7goIRkl8fHBbno=|l6sj6FOsoXxgEZMzn/BnOfPKN68= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINf63qSV8rD57N+digID8t28WVhd3Yf2K2UhaoG8TsWQ ED25519 #5
+|1|W7x4zY6KtTZJgsopyOusJqvVPag=|QauLt7hKezBZFZi2i4Xopho7Nsk= ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC/C15Q4sfnk7BZff1er8bscay+5s51oD4eWArlHWMK/ZfYeeTAccTy+7B7Jv+MS4nKCpflrvJI2RQz4kS8vF0ATdBbi4jeWefStlHNg0HLhnCY7NAfDIlRdaN9lm3Pqm2vmr+CkqwcJaSpycDg8nPN9yNAuD6pv7NDuUnECezojQ== RSA #5
+
+|1|mxnU8luzqWLvfVi5qBm5xVIyCRM=|9Epopft7LBd80Bf6RmWPIpwa8yU= ssh-dss AAAAB3NzaC1kc3MAAACBAIutigAse65TCW6hHDOEGXenE9L4L0talHbs65hj3UUNtWflKdQeXLofqXgW8AwaDKmnuRPrxRoxVNXj84n45wtBEdt4ztmdAZteAbXSnHqpcxME3jDxh3EtxzGPXLs+RUmKPVguraSgo7W2oN7KFx6VM+AcAtxANSTlvDid3s47AAAAFQCd9Q3kkHSLWe77sW0eRaayI45ovwAAAIAw6srGF6xvFasI44Y3r9JJ2K+3ezozl3ldL3p2+p2HG3iWafC4SdV8pB6ZIxKlYAywiiFb3LzH/JweGFq1jtoFDRM3MlYORBevydU4zPz7b5QLDVB0sY4evYtWmg2BFJvoWRfhLnlZVW7h5N8v4fNIwdVmVsw4Ljes7iF2HRGhHgAAAIBDFT3fww2Oby1xUA6G9pDAcVikrQFqp1sJRylNTUyeyQ37SNAGzYxwHJFgQr8gZLdRQ1UW+idYpqVbVNcYFMOiw/zSqK2OfVwPZ9U+TTKdc992ChSup6vJEKM/ZVIyDWDbJr7igQ4ahy7jo9mFvm8ljN926EnspQzCvs0Dxk6tHA== DSA #6
+|1|klvLmvh2vCpkNMDEjVvrE8SJWTg=|e/dqEEBLnbgqmwEesl4cDRu/7TM= ssh-dss AAAAB3NzaC1kc3MAAACBAIutigAse65TCW6hHDOEGXenE9L4L0talHbs65hj3UUNtWflKdQeXLofqXgW8AwaDKmnuRPrxRoxVNXj84n45wtBEdt4ztmdAZteAbXSnHqpcxME3jDxh3EtxzGPXLs+RUmKPVguraSgo7W2oN7KFx6VM+AcAtxANSTlvDid3s47AAAAFQCd9Q3kkHSLWe77sW0eRaayI45ovwAAAIAw6srGF6xvFasI44Y3r9JJ2K+3ezozl3ldL3p2+p2HG3iWafC4SdV8pB6ZIxKlYAywiiFb3LzH/JweGFq1jtoFDRM3MlYORBevydU4zPz7b5QLDVB0sY4evYtWmg2BFJvoWRfhLnlZVW7h5N8v4fNIwdVmVsw4Ljes7iF2HRGhHgAAAIBDFT3fww2Oby1xUA6G9pDAcVikrQFqp1sJRylNTUyeyQ37SNAGzYxwHJFgQr8gZLdRQ1UW+idYpqVbVNcYFMOiw/zSqK2OfVwPZ9U+TTKdc992ChSup6vJEKM/ZVIyDWDbJr7igQ4ahy7jo9mFvm8ljN926EnspQzCvs0Dxk6tHA== DSA #6
+|1|wsk3ddB3UjuxEsoeNCeZjZ6NvZs=|O3O/q2Z/u7DrxoTiIq6kzCevQT0= ssh-dss AAAAB3NzaC1kc3MAAACBAIutigAse65TCW6hHDOEGXenE9L4L0talHbs65hj3UUNtWflKdQeXLofqXgW8AwaDKmnuRPrxRoxVNXj84n45wtBEdt4ztmdAZteAbXSnHqpcxME3jDxh3EtxzGPXLs+RUmKPVguraSgo7W2oN7KFx6VM+AcAtxANSTlvDid3s47AAAAFQCd9Q3kkHSLWe77sW0eRaayI45ovwAAAIAw6srGF6xvFasI44Y3r9JJ2K+3ezozl3ldL3p2+p2HG3iWafC4SdV8pB6ZIxKlYAywiiFb3LzH/JweGFq1jtoFDRM3MlYORBevydU4zPz7b5QLDVB0sY4evYtWmg2BFJvoWRfhLnlZVW7h5N8v4fNIwdVmVsw4Ljes7iF2HRGhHgAAAIBDFT3fww2Oby1xUA6G9pDAcVikrQFqp1sJRylNTUyeyQ37SNAGzYxwHJFgQr8gZLdRQ1UW+idYpqVbVNcYFMOiw/zSqK2OfVwPZ9U+TTKdc992ChSup6vJEKM/ZVIyDWDbJr7igQ4ahy7jo9mFvm8ljN926EnspQzCvs0Dxk6tHA== DSA #6
+|1|B8epmkLSni+vGZDijr/EwxeR2k4=|7ct8yzNOVJhKm3ZD2w0XIT7df8E= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK1wRLyKtvK3Mmhd0XPkKwW4ev1KBVf8J4aG8lESq1TsaqqfOXYGyxMq5pN8fCGiD5UPOqyTYz/ZNzClRhJRHao= ECDSA #6
+|1|JojD885UhYhbCu571rgyM/5PpYU=|BJaU2aE1FebQZy3B5tzTDRWFRG0= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK1wRLyKtvK3Mmhd0XPkKwW4ev1KBVf8J4aG8lESq1TsaqqfOXYGyxMq5pN8fCGiD5UPOqyTYz/ZNzClRhJRHao= ECDSA #6
+|1|5t7UDHDybVrDZVQPCpwdnr6nk4k=|EqJ73W/veIL3H2x+YWHcJxI5ETA= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK1wRLyKtvK3Mmhd0XPkKwW4ev1KBVf8J4aG8lESq1TsaqqfOXYGyxMq5pN8fCGiD5UPOqyTYz/ZNzClRhJRHao= ECDSA #6
+|1|OCcBfGc/b9+ip+W6Gp+3ftdluO4=|VbrKUdzOOtIBOOmEE+jlK4SD3Xc= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPLW0ZwCkRQldpLa4I5BpwGa/om+WE6OgC8jdVqakt0Z ED25519 #6
+|1|9fLN0YdP+BJ25lKuKvYuOdUo93w=|vZyr0rOiX01hv5XbghhHMW+Zb3U= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPLW0ZwCkRQldpLa4I5BpwGa/om+WE6OgC8jdVqakt0Z ED25519 #6
+|1|nc9RoaaQ0s5jdPxwlUmluGHU3uk=|un6OsJajokKQ3MgyS9mfDNeyP6U= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPLW0ZwCkRQldpLa4I5BpwGa/om+WE6OgC8jdVqakt0Z ED25519 #6
+|1|rsHB6juT9q6GOY91qOeOwL6TSJE=|ps/vXF9Izuues5PbOn887Gw/2Dg= ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQClu/3I6GG1Ai89Imnw0vXmWJ2OW0ftQwRrsbIAD0qzLFYpkJ76QWnzpCehvK9u0L5hcw7z2Y6mRLcSBsqONc+HVU73Qi7M4zHRvtjprPs3SOyLpf0J9sL1WiHBDwg2P0miHMCdqHDd5nVXkJB2d4eeecmgezGLa29NOHZjbza5yw== RSA #6
+|1|BsckdLH2aRyWQooRmv+Yo3t4dKg=|Lf3tJc5Iyx0KxNwAG89FsImsfEE= ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQClu/3I6GG1Ai89Imnw0vXmWJ2OW0ftQwRrsbIAD0qzLFYpkJ76QWnzpCehvK9u0L5hcw7z2Y6mRLcSBsqONc+HVU73Qi7M4zHRvtjprPs3SOyLpf0J9sL1WiHBDwg2P0miHMCdqHDd5nVXkJB2d4eeecmgezGLa29NOHZjbza5yw== RSA #6
+|1|plqkBA4hq7UATyd5+/Xl+zL7ghw=|stacofaUed46666mfqxp9gJFjt4= ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQClu/3I6GG1Ai89Imnw0vXmWJ2OW0ftQwRrsbIAD0qzLFYpkJ76QWnzpCehvK9u0L5hcw7z2Y6mRLcSBsqONc+HVU73Qi7M4zHRvtjprPs3SOyLpf0J9sL1WiHBDwg2P0miHMCdqHDd5nVXkJB2d4eeecmgezGLa29NOHZjbza5yw== RSA #6
+
+
+# Revoked and CA keys
+@revoked sisyphus.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFP8L9REfN/iYy1KIRtFqSCn3V2+vOCpoZYENFGLdOF ED25519 #4
+@cert-authority prometheus.example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHZd0OXHIWwK3xnjAdMZ1tojxWycdu38pORO/UX5cqsKMgGCKQVBWWO3TFk1ePkGIE9VMWT1hCGqWRRwYlH+dSE= ECDSA #4
+@cert-authority *.example.com ssh-dss AAAAB3NzaC1kc3MAAACBAKvjnFHm0VvMr5h2Zu3nURsxQKGoxm+DCzYDxRYcilK07Cm5c4XTrFbA2X86+9sGs++W7QRMcTJUYIg0a+UtIMtAjwORd6ZPXM2K5dBW+gh1oHyvKi767tWX7I2c+1ZPJDY95mUUfZQUEfdy9eGDSBmw/pSsveQ1ur6XNUh/MtP/AAAAFQDHnXk/9jBJAdce1pHtLWnbdPSGdQAAAIEAm2OLy8tZBfiEO3c3X1yyB/GTcDwrQCqRMDkhnsmrliec3dWkOfNTzu+MrdvF8ymTWLEqPpbMheYtvNyZ3TF0HO5W7aVBpdGZbOdOAIfB+6skqGbI8A5Up1d7dak/bSsqL2r5NjwbDOdq+1hBzzvbl/qjh+sQarV2zHrpKoQaV28AAACANtkBVedBbqIAdphCrN/LbUi9WlyuF9UZz+tlpVLYrj8GJVwnplV2tvOmUw6yP5/pzCimTsao8dpL5PWxm7fKxLWVxA+lEsA4WeC885CiZn8xhdaJOCN+NyJ2bqkz+4VPI7oDGBm0aFwUqJn+M1PiSgvI50XdF2dBsFRTRNY0wzA= DSA #4
+
+# Some invalid lines
+@what sisyphus.example.com ssh-dss AAAAB3NzaC1kc3MAAACBAOqffHxEW4c+Z9q/r3l4sYK8F7qrBsU8XF9upGsW62T9InROFFq9IO0x3pQ6mDA0Wtw0sqcDmkPCHPyP4Ok/fU3/drLaZusHoVYu8pBBrWsIDrKgkeX9TEodBsSrYdl4Sqtqq9EZv9+DttV6LStZrgYyUTOKwOF95wGantpLynX5AAAAFQDdt+zjRNlETDsgmxcSYFgREirJrQAAAIBQlrPaiPhR24FhnMLcHH4016vL7AqDDID6Qw7PhbXGa4/XlxWMIigjBKrIPKvnZ6p712LSnCKtcbfdx0MtmJlNa01CYqPaRhgRaf+uGdvTkTUcdaq8R5lLJL+JMNwUhcC8ijm3NqEjXjffuebGe1EzIeiITbA7Nndcd+GytwRDegAAAIEAkRYPjSVcUxfUHhHdpP6V8CuY1+CYSs9EPJ7iiWTDuXWVIBTU32oJLAnrmAcOwtIzEfPvm+rff5FI/Yhon2pB3VTXhPPEBjYzE5qANanAT4e6tzAVc5f3DUhHaDknwRYfDz86GFvuLtDjeE/UZ9t6OofYoEsCBpYozLAprBvNIQY= DSA #1
+sisyphus.example.com
+prometheus.example.com ssh-ed25519
+sisyphus.example.com ssh-dsa AAAATgAAAAdz
+sisyphus.example.com ssh-XXX AAAATgAAAAdzc2gtWFhYAAAAP0ZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRg==
+prometheus.example.com ssh-rsa AAAATgAAAAdzc2gtWFhYAAAAP0ZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRg==
diff --git a/regress/unittests/hostkeys/testdata/rsa1_1.pub b/regress/unittests/hostkeys/testdata/rsa1_1.pub
new file mode 100644
index 0000000..772ce9c
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/rsa1_1.pub
@@ -0,0 +1 @@
+1024 65537 153895431603677073925890314548566704948446776958334195280085080329934839226701954473292358821568047724356487621573742372399387931887004184139835510820577359977148363519970774657801798872789118894962853659233045778161859413980935372685480527355016624825696983269800574755126132814333241868538220824608980319407 RSA1 #1
diff --git a/regress/unittests/hostkeys/testdata/rsa1_2.pub b/regress/unittests/hostkeys/testdata/rsa1_2.pub
new file mode 100644
index 0000000..78794b9
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/rsa1_2.pub
@@ -0,0 +1 @@
+1024 65537 135970715082947442639683969597180728933388298633245835186618852623800675939308729462220235058285909679252157995530180587329132927339620517781785310829060832352381015614725360278571924286986474946772141568893116432268565829418506866604294073334978275702221949783314402806080929601995102334442541344606109853641 RSA1 #2
diff --git a/regress/unittests/hostkeys/testdata/rsa1_3.pub b/regress/unittests/hostkeys/testdata/rsa1_3.pub
new file mode 100644
index 0000000..0c035fe
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/rsa1_3.pub
@@ -0,0 +1 @@
+1024 65537 125895605498029643697051635076028105429632810811904702876152645261610759866299221305725069141163240694267669117205342283569102183636228981857946763978553664895308762890072813014496700601576921921752482059207749978374872713540759920335553799711267170948655579130584031555334229966603000896364091459595522912269 RSA1 #3
diff --git a/regress/unittests/hostkeys/testdata/rsa1_4.pub b/regress/unittests/hostkeys/testdata/rsa1_4.pub
new file mode 100644
index 0000000..0006442
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/rsa1_4.pub
@@ -0,0 +1 @@
+1024 65537 174143366122697048196335388217056770310345753698079464367148030836533360510864881734142526411160017107552815906024399248049666856133771656680462456979369587903909343046704480897527203474513676654933090991684252819423129896444427656841613263783484827101210734799449281639493127615902427443211183258155381810593 RSA1 #4
diff --git a/regress/unittests/hostkeys/testdata/rsa1_5.pub b/regress/unittests/hostkeys/testdata/rsa1_5.pub
new file mode 100644
index 0000000..bb53c26
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/rsa1_5.pub
@@ -0,0 +1 @@
+1024 65537 127931411493401587586867047972295564331543694182352197506125410692673654572057908999642645524647232712160516076508316152810117209181150078352725299319149726341058893406440426414316276977768958023952319602422835879783057966985348561111880658922724668687074412548487722084792283453716871417610020757212399252171 RSA1 #5
diff --git a/regress/unittests/hostkeys/testdata/rsa1_6.pub b/regress/unittests/hostkeys/testdata/rsa1_6.pub
new file mode 100644
index 0000000..85d6576
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/rsa1_6.pub
@@ -0,0 +1 @@
+1024 65537 140883028436203600354693376066567741282115117509696517282419557936340193768851493584179972504103033755515036493433917203732876685813283050574208967197963391667532902202382549275760997891673884333346000558018002659506756213191532156293935482587878596032743105911487673274674568768638010598205190227631909167257 RSA1 #6
diff --git a/regress/unittests/hostkeys/testdata/rsa_1.pub b/regress/unittests/hostkeys/testdata/rsa_1.pub
new file mode 100644
index 0000000..2b87885
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/rsa_1.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDg4hB4vAZHJ0PVRiJajOv/GlytFWNpv5/9xgB9+5BIbvp8LOrFZ5D9K0Gsmwpd4G4rfaAz8j896DhMArg0vtkilIPPGt/6VzWMERgvaIQPJ/IE99X3+fjcAG56oAWwy29JX10lQMzBPU6XJIaN/zqpkb6qUBiAHBdLpxrFBBU0/w== RSA #1
diff --git a/regress/unittests/hostkeys/testdata/rsa_2.pub b/regress/unittests/hostkeys/testdata/rsa_2.pub
new file mode 100644
index 0000000..33f1fd9
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/rsa_2.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDmbUhNabB5AmBDX6GNHZ3lbn7pRxqfpW+f53QqNGlK0sLV+0gkMIrOfUp1kdE2ZLE6tfzdicatj/RlH6/wuo4yyYb+Pyx3G0vxdmAIiA4aANq38XweDucBC0TZkRWVHK+Gs5V/uV0z7N0axJvkkJujMLvST3CRiiWwlficBc6yVQ== RSA #2
diff --git a/regress/unittests/hostkeys/testdata/rsa_3.pub b/regress/unittests/hostkeys/testdata/rsa_3.pub
new file mode 100644
index 0000000..c2f6b20
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/rsa_3.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDX8F93W3SH4ZSus4XUQ2cw9dqcuyUETTlKEeGv3zlknV3YCoe2Mp04naDhiuwj8sOsytrZSESzLY1ZEyzrjxE6ZFVv8NKgck/AbRjcwlRFOcx9oKUxOrXRa0IoXlTq0kyjKCJfaHBKnGitZThknCPTbVmpATkm5xx6J0WEDozfoQ== RSA #3
diff --git a/regress/unittests/hostkeys/testdata/rsa_4.pub b/regress/unittests/hostkeys/testdata/rsa_4.pub
new file mode 100644
index 0000000..35545a7
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/rsa_4.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDI8AdjBAozcdRnIikVlt69iyDHKyrtxmpdkbRy9bWaL86OH+PTmLUk5e+T/ufiakpeE2pm0hkE3e4Sh/FsY+rsQdRoraWVNFfchcMeVlKvuy5RZN0ElvmaQebOJUeNeBn2LLw8aL8bJ4CP/bQRKrmrSSqjz3+4H9YNVyyk1OGBPQ== RSA #4
diff --git a/regress/unittests/hostkeys/testdata/rsa_5.pub b/regress/unittests/hostkeys/testdata/rsa_5.pub
new file mode 100644
index 0000000..befbaa7
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/rsa_5.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC/C15Q4sfnk7BZff1er8bscay+5s51oD4eWArlHWMK/ZfYeeTAccTy+7B7Jv+MS4nKCpflrvJI2RQz4kS8vF0ATdBbi4jeWefStlHNg0HLhnCY7NAfDIlRdaN9lm3Pqm2vmr+CkqwcJaSpycDg8nPN9yNAuD6pv7NDuUnECezojQ== RSA #5
diff --git a/regress/unittests/hostkeys/testdata/rsa_6.pub b/regress/unittests/hostkeys/testdata/rsa_6.pub
new file mode 100644
index 0000000..393e116
--- /dev/null
+++ b/regress/unittests/hostkeys/testdata/rsa_6.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQClu/3I6GG1Ai89Imnw0vXmWJ2OW0ftQwRrsbIAD0qzLFYpkJ76QWnzpCehvK9u0L5hcw7z2Y6mRLcSBsqONc+HVU73Qi7M4zHRvtjprPs3SOyLpf0J9sL1WiHBDwg2P0miHMCdqHDd5nVXkJB2d4eeecmgezGLa29NOHZjbza5yw== RSA #6
diff --git a/regress/unittests/hostkeys/tests.c b/regress/unittests/hostkeys/tests.c
new file mode 100644
index 0000000..92c7646
--- /dev/null
+++ b/regress/unittests/hostkeys/tests.c
@@ -0,0 +1,16 @@
+/* $OpenBSD: tests.c,v 1.1 2015/02/16 22:18:34 djm Exp $ */
+/*
+ * Regress test for known_hosts-related API.
+ *
+ * Placed in the public domain
+ */
+
+void tests(void);
+void test_iterate(void); /* test_iterate.c */
+
+void
+tests(void)
+{
+ test_iterate();
+}
+
diff --git a/regress/unittests/kex/Makefile b/regress/unittests/kex/Makefile
new file mode 100644
index 0000000..981affe
--- /dev/null
+++ b/regress/unittests/kex/Makefile
@@ -0,0 +1,40 @@
+# $OpenBSD: Makefile,v 1.14 2023/02/02 12:12:52 djm Exp $
+
+PROG=test_kex
+SRCS=tests.c test_kex.c test_proposal.c
+
+# From usr.bin/ssh
+SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c
+SRCS+=sshbuf-io.c atomicio.c sshkey.c authfile.c cipher.c log.c ssh-rsa.c
+SRCS+=ssh-dss.c ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c
+SRCS+=ssherr.c uidswap.c cleanup.c xmalloc.c match.c krl.c fatal.c
+SRCS+=addr.c addrmatch.c bitmap.c packet.c dispatch.c canohost.c ssh_api.c
+SRCS+=compat.c ed25519.c hash.c
+SRCS+=cipher-chachapoly.c chacha.c poly1305.c ssh-ecdsa-sk.c ssh-sk.c
+SRCS+=ssh-ed25519-sk.c sk-usbhid.c
+
+SRCS+= kex.c
+SRCS+= dh.c
+SRCS+= kexdh.c
+SRCS+= kexecdh.c
+SRCS+= kexgex.c
+SRCS+= kexgexc.c
+SRCS+= kexgexs.c
+SRCS+= kexc25519.c
+SRCS+= smult_curve25519_ref.c
+SRCS+= kexgen.c
+SRCS+= kexsntrup761x25519.c
+SRCS+= sntrup761.c
+SRCS+= utf8.c
+
+SRCS+=digest-openssl.c
+#SRCS+=digest-libc.c
+
+REGRESS_TARGETS=run-regress-${PROG}
+
+run-regress-${PROG}: ${PROG}
+ env ${TEST_ENV} ./${PROG}
+
+.include <bsd.regress.mk>
+
+LDADD+=-lz
diff --git a/regress/unittests/kex/test_kex.c b/regress/unittests/kex/test_kex.c
new file mode 100644
index 0000000..c26761e
--- /dev/null
+++ b/regress/unittests/kex/test_kex.c
@@ -0,0 +1,208 @@
+/* $OpenBSD: test_kex.c,v 1.6 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test KEX
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "ssherr.h"
+#include "ssh_api.h"
+#include "sshbuf.h"
+#include "packet.h"
+#include "myproposal.h"
+
+void kex_tests(void);
+static int do_debug = 0;
+
+static int
+do_send_and_receive(struct ssh *from, struct ssh *to)
+{
+ u_char type;
+ size_t len;
+ const u_char *buf;
+ int r;
+
+ for (;;) {
+ if ((r = ssh_packet_next(from, &type)) != 0) {
+ fprintf(stderr, "ssh_packet_next: %s\n", ssh_err(r));
+ return r;
+ }
+ if (type != 0)
+ return 0;
+ buf = ssh_output_ptr(from, &len);
+ if (do_debug)
+ printf("%zu", len);
+ if (len == 0)
+ return 0;
+ if ((r = ssh_output_consume(from, len)) != 0 ||
+ (r = ssh_input_append(to, buf, len)) != 0)
+ return r;
+ }
+}
+
+static void
+run_kex(struct ssh *client, struct ssh *server)
+{
+ int r = 0;
+
+ while (!server->kex->done || !client->kex->done) {
+ if (do_debug)
+ printf(" S:");
+ if ((r = do_send_and_receive(server, client)))
+ break;
+ if (do_debug)
+ printf(" C:");
+ if ((r = do_send_and_receive(client, server)))
+ break;
+ }
+ if (do_debug)
+ printf("done: %s\n", ssh_err(r));
+ ASSERT_INT_EQ(r, 0);
+ ASSERT_INT_EQ(server->kex->done, 1);
+ ASSERT_INT_EQ(client->kex->done, 1);
+}
+
+static void
+do_kex_with_key(char *kex, int keytype, int bits)
+{
+ struct ssh *client = NULL, *server = NULL, *server2 = NULL;
+ struct sshkey *private, *public;
+ struct sshbuf *state;
+ struct kex_params kex_params;
+ char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT };
+ char *keyname = NULL;
+
+ TEST_START("sshkey_generate");
+ ASSERT_INT_EQ(sshkey_generate(keytype, bits, &private), 0);
+ TEST_DONE();
+
+ TEST_START("sshkey_from_private");
+ ASSERT_INT_EQ(sshkey_from_private(private, &public), 0);
+ TEST_DONE();
+
+ TEST_START("ssh_init");
+ memcpy(kex_params.proposal, myproposal, sizeof(myproposal));
+ if (kex != NULL)
+ kex_params.proposal[PROPOSAL_KEX_ALGS] = kex;
+ keyname = strdup(sshkey_ssh_name(private));
+ ASSERT_PTR_NE(keyname, NULL);
+ kex_params.proposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = keyname;
+ ASSERT_INT_EQ(ssh_init(&client, 0, &kex_params), 0);
+ ASSERT_INT_EQ(ssh_init(&server, 1, &kex_params), 0);
+ ASSERT_PTR_NE(client, NULL);
+ ASSERT_PTR_NE(server, NULL);
+ TEST_DONE();
+
+ TEST_START("ssh_add_hostkey");
+ ASSERT_INT_EQ(ssh_add_hostkey(server, private), 0);
+ ASSERT_INT_EQ(ssh_add_hostkey(client, public), 0);
+ TEST_DONE();
+
+ TEST_START("kex");
+ run_kex(client, server);
+ TEST_DONE();
+
+ TEST_START("rekeying client");
+ ASSERT_INT_EQ(kex_send_kexinit(client), 0);
+ run_kex(client, server);
+ TEST_DONE();
+
+ TEST_START("rekeying server");
+ ASSERT_INT_EQ(kex_send_kexinit(server), 0);
+ run_kex(client, server);
+ TEST_DONE();
+
+ TEST_START("ssh_packet_get_state");
+ state = sshbuf_new();
+ ASSERT_PTR_NE(state, NULL);
+ ASSERT_INT_EQ(ssh_packet_get_state(server, state), 0);
+ ASSERT_INT_GE(sshbuf_len(state), 1);
+ TEST_DONE();
+
+ TEST_START("ssh_packet_set_state");
+ server2 = NULL;
+ ASSERT_INT_EQ(ssh_init(&server2, 1, NULL), 0);
+ ASSERT_PTR_NE(server2, NULL);
+ ASSERT_INT_EQ(ssh_add_hostkey(server2, private), 0);
+ ASSERT_INT_EQ(ssh_packet_set_state(server2, state), 0);
+ ASSERT_INT_EQ(sshbuf_len(state), 0);
+ sshbuf_free(state);
+ ASSERT_PTR_NE(server2->kex, NULL);
+ /* XXX we need to set the callbacks */
+#ifdef WITH_OPENSSL
+ server2->kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_server;
+ server2->kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_server;
+ server2->kex->kex[KEX_DH_GEX_SHA1] = kexgex_server;
+ server2->kex->kex[KEX_DH_GEX_SHA256] = kexgex_server;
+#ifdef OPENSSL_HAS_ECC
+ server2->kex->kex[KEX_ECDH_SHA2] = kex_gen_server;
+#endif /* OPENSSL_HAS_ECC */
+#endif /* WITH_OPENSSL */
+ server2->kex->kex[KEX_C25519_SHA256] = kex_gen_server;
+ server2->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
+ server2->kex->load_host_public_key = server->kex->load_host_public_key;
+ server2->kex->load_host_private_key = server->kex->load_host_private_key;
+ server2->kex->sign = server->kex->sign;
+ TEST_DONE();
+
+ TEST_START("rekeying server2");
+ ASSERT_INT_EQ(kex_send_kexinit(server2), 0);
+ run_kex(client, server2);
+ ASSERT_INT_EQ(kex_send_kexinit(client), 0);
+ run_kex(client, server2);
+ TEST_DONE();
+
+ TEST_START("cleanup");
+ sshkey_free(private);
+ sshkey_free(public);
+ ssh_free(client);
+ ssh_free(server);
+ ssh_free(server2);
+ free(keyname);
+ TEST_DONE();
+}
+
+static void
+do_kex(char *kex)
+{
+#ifdef WITH_OPENSSL
+ do_kex_with_key(kex, KEY_RSA, 2048);
+ do_kex_with_key(kex, KEY_DSA, 1024);
+#ifdef OPENSSL_HAS_ECC
+ do_kex_with_key(kex, KEY_ECDSA, 256);
+#endif /* OPENSSL_HAS_ECC */
+#endif /* WITH_OPENSSL */
+ do_kex_with_key(kex, KEY_ED25519, 256);
+}
+
+void
+kex_tests(void)
+{
+ do_kex("curve25519-sha256@libssh.org");
+#ifdef WITH_OPENSSL
+#ifdef OPENSSL_HAS_ECC
+ do_kex("ecdh-sha2-nistp256");
+ do_kex("ecdh-sha2-nistp384");
+ do_kex("ecdh-sha2-nistp521");
+#endif /* OPENSSL_HAS_ECC */
+ do_kex("diffie-hellman-group-exchange-sha256");
+ do_kex("diffie-hellman-group-exchange-sha1");
+ do_kex("diffie-hellman-group14-sha1");
+ do_kex("diffie-hellman-group1-sha1");
+# ifdef USE_SNTRUP761X25519
+ do_kex("sntrup761x25519-sha512@openssh.com");
+# endif /* USE_SNTRUP761X25519 */
+#endif /* WITH_OPENSSL */
+}
diff --git a/regress/unittests/kex/test_proposal.c b/regress/unittests/kex/test_proposal.c
new file mode 100644
index 0000000..d6cf0f5
--- /dev/null
+++ b/regress/unittests/kex/test_proposal.c
@@ -0,0 +1,83 @@
+/* $OpenBSD: test_proposal.c,v 1.1 2023/02/02 12:12:52 djm Exp $ */
+/*
+ * Regress test KEX
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <signal.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "compat.h"
+#include "ssherr.h"
+#include "sshbuf.h"
+#include "kex.h"
+#include "packet.h"
+#include "xmalloc.h"
+
+void kex_proposal(void);
+
+#define CURVE25519 "curve25519-sha256@libssh.org"
+#define DHGEX1 "diffie-hellman-group-exchange-sha1"
+#define DHGEX256 "diffie-hellman-group-exchange-sha256"
+#define KEXALGOS CURVE25519","DHGEX256","DHGEX1
+void
+kex_proposal(void)
+{
+ size_t i;
+ struct ssh ssh;
+ char *result, *out, *in;
+ struct {
+ char *in; /* TODO: make this const */
+ char *out;
+ int compat;
+ } tests[] = {
+ { KEXALGOS, KEXALGOS, 0},
+ { KEXALGOS, DHGEX256","DHGEX1, SSH_BUG_CURVE25519PAD },
+ { KEXALGOS, CURVE25519, SSH_OLD_DHGEX },
+ { "a,"KEXALGOS, "a", SSH_BUG_CURVE25519PAD|SSH_OLD_DHGEX },
+ /* TODO: enable once compat_kex_proposal doesn't fatal() */
+ /* { KEXALGOS, "", SSH_BUG_CURVE25519PAD|SSH_OLD_DHGEX }, */
+ };
+
+ TEST_START("compat_kex_proposal");
+ for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) {
+ ssh.compat = tests[i].compat;
+ /* match entire string */
+ result = compat_kex_proposal(&ssh, tests[i].in);
+ ASSERT_STRING_EQ(result, tests[i].out);
+ free(result);
+ /* match at end */
+ in = kex_names_cat("a", tests[i].in);
+ out = kex_names_cat("a", tests[i].out);
+ result = compat_kex_proposal(&ssh, in);
+ ASSERT_STRING_EQ(result, out);
+ free(result); free(in); free(out);
+ /* match at start */
+ in = kex_names_cat(tests[i].in, "a");
+ out = kex_names_cat(tests[i].out, "a");
+ result = compat_kex_proposal(&ssh, in);
+ ASSERT_STRING_EQ(result, out);
+ free(result); free(in); free(out);
+ /* match in middle */
+ xasprintf(&in, "a,%s,b", tests[i].in);
+ if (*(tests[i].out) == '\0')
+ out = xstrdup("a,b");
+ else
+ xasprintf(&out, "a,%s,b", tests[i].out);
+ result = compat_kex_proposal(&ssh, in);
+ ASSERT_STRING_EQ(result, out);
+ free(result); free(in); free(out);
+ }
+ TEST_DONE();
+}
diff --git a/regress/unittests/kex/tests.c b/regress/unittests/kex/tests.c
new file mode 100644
index 0000000..2a83daf
--- /dev/null
+++ b/regress/unittests/kex/tests.c
@@ -0,0 +1,16 @@
+/* $OpenBSD: tests.c,v 1.2 2023/02/02 12:12:52 djm Exp $ */
+/*
+ * Placed in the public domain
+ */
+
+#include "../test_helper/test_helper.h"
+
+void kex_tests(void);
+void kex_proposal(void);
+
+void
+tests(void)
+{
+ kex_tests();
+ kex_proposal();
+}
diff --git a/regress/unittests/match/Makefile b/regress/unittests/match/Makefile
new file mode 100644
index 0000000..939163d
--- /dev/null
+++ b/regress/unittests/match/Makefile
@@ -0,0 +1,16 @@
+# $OpenBSD: Makefile,v 1.5 2021/01/09 12:24:31 dtucker Exp $
+
+PROG=test_match
+SRCS=tests.c
+
+# From usr.bin/ssh
+SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c
+SRCS+=match.c misc.c log.c uidswap.c fatal.c ssherr.c addrmatch.c xmalloc.c
+SRCS+=cleanup.c atomicio.c addr.c
+
+REGRESS_TARGETS=run-regress-${PROG}
+
+run-regress-${PROG}: ${PROG}
+ env ${TEST_ENV} ./${PROG}
+
+.include <bsd.regress.mk>
diff --git a/regress/unittests/match/tests.c b/regress/unittests/match/tests.c
new file mode 100644
index 0000000..f00d1f9
--- /dev/null
+++ b/regress/unittests/match/tests.c
@@ -0,0 +1,131 @@
+/* $OpenBSD: tests.c,v 1.8 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for matching functions
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "match.h"
+
+void
+tests(void)
+{
+ TEST_START("match_pattern");
+ ASSERT_INT_EQ(match_pattern("", ""), 1);
+ ASSERT_INT_EQ(match_pattern("", "aaa"), 0);
+ ASSERT_INT_EQ(match_pattern("aaa", ""), 0);
+ ASSERT_INT_EQ(match_pattern("aaa", "aaaa"), 0);
+ ASSERT_INT_EQ(match_pattern("aaaa", "aaa"), 0);
+ TEST_DONE();
+
+ TEST_START("match_pattern wildcard");
+ ASSERT_INT_EQ(match_pattern("", "*"), 1);
+ ASSERT_INT_EQ(match_pattern("a", "?"), 1);
+ ASSERT_INT_EQ(match_pattern("aa", "a?"), 1);
+ ASSERT_INT_EQ(match_pattern("a", "*"), 1);
+ ASSERT_INT_EQ(match_pattern("aa", "a*"), 1);
+ ASSERT_INT_EQ(match_pattern("aa", "?*"), 1);
+ ASSERT_INT_EQ(match_pattern("aa", "**"), 1);
+ ASSERT_INT_EQ(match_pattern("aa", "?a"), 1);
+ ASSERT_INT_EQ(match_pattern("aa", "*a"), 1);
+ ASSERT_INT_EQ(match_pattern("ba", "a?"), 0);
+ ASSERT_INT_EQ(match_pattern("ba", "a*"), 0);
+ ASSERT_INT_EQ(match_pattern("ab", "?a"), 0);
+ ASSERT_INT_EQ(match_pattern("ab", "*a"), 0);
+ TEST_DONE();
+
+ TEST_START("match_pattern_list");
+ ASSERT_INT_EQ(match_pattern_list("", "", 0), 0); /* no patterns */
+ ASSERT_INT_EQ(match_pattern_list("", "*", 0), 1);
+ ASSERT_INT_EQ(match_pattern_list("", "!*", 0), -1);
+ ASSERT_INT_EQ(match_pattern_list("", "!a,*", 0), 1);
+ ASSERT_INT_EQ(match_pattern_list("", "*,!a", 0), 1);
+ ASSERT_INT_EQ(match_pattern_list("", "a,!*", 0), -1);
+ ASSERT_INT_EQ(match_pattern_list("", "!*,a", 0), -1);
+ ASSERT_INT_EQ(match_pattern_list("a", "", 0), 0);
+ ASSERT_INT_EQ(match_pattern_list("a", "*", 0), 1);
+ ASSERT_INT_EQ(match_pattern_list("a", "!*", 0), -1);
+ ASSERT_INT_EQ(match_pattern_list("a", "!a", 0), -1);
+ /* XXX negated ASSERT_INT_EQ(match_pattern_list("a", "!b", 0), 1); */
+ ASSERT_INT_EQ(match_pattern_list("a", "!a,*", 0), -1);
+ ASSERT_INT_EQ(match_pattern_list("b", "!a,*", 0), 1);
+ ASSERT_INT_EQ(match_pattern_list("a", "*,!a", 0), -1);
+ ASSERT_INT_EQ(match_pattern_list("b", "*,!a", 0), 1);
+ ASSERT_INT_EQ(match_pattern_list("a", "a,!*", 0), -1);
+ ASSERT_INT_EQ(match_pattern_list("b", "a,!*", 0), -1);
+ ASSERT_INT_EQ(match_pattern_list("a", "a,!a", 0), -1);
+ /* XXX negated ASSERT_INT_EQ(match_pattern_list("b", "a,!a", 0), 1); */
+ ASSERT_INT_EQ(match_pattern_list("a", "!*,a", 0), -1);
+ ASSERT_INT_EQ(match_pattern_list("b", "!*,a", 0), -1);
+ TEST_DONE();
+
+ TEST_START("match_pattern_list lowercase");
+ ASSERT_INT_EQ(match_pattern_list("abc", "ABC", 0), 0);
+ ASSERT_INT_EQ(match_pattern_list("ABC", "abc", 0), 0);
+ ASSERT_INT_EQ(match_pattern_list("abc", "ABC", 1), 1);
+ ASSERT_INT_EQ(match_pattern_list("ABC", "abc", 1), 0);
+ TEST_DONE();
+
+ TEST_START("addr_match_list");
+ ASSERT_INT_EQ(addr_match_list("127.0.0.1", "127.0.0.1/44"), -2);
+ ASSERT_INT_EQ(addr_match_list(NULL, "127.0.0.1/44"), -2);
+ ASSERT_INT_EQ(addr_match_list("a", "*"), 0);
+ ASSERT_INT_EQ(addr_match_list("127.0.0.1", "*"), 1);
+ ASSERT_INT_EQ(addr_match_list(NULL, "*"), 0);
+ ASSERT_INT_EQ(addr_match_list("127.0.0.1", "127.0.0.1"), 1);
+ ASSERT_INT_EQ(addr_match_list("127.0.0.1", "127.0.0.2"), 0);
+ ASSERT_INT_EQ(addr_match_list("127.0.0.1", "!127.0.0.1"), -1);
+ /* XXX negated ASSERT_INT_EQ(addr_match_list("127.0.0.1", "!127.0.0.2"), 1); */
+ ASSERT_INT_EQ(addr_match_list("127.0.0.255", "127.0.0.0/24"), 1);
+ ASSERT_INT_EQ(addr_match_list("127.0.1.1", "127.0.0.0/24"), 0);
+ ASSERT_INT_EQ(addr_match_list("127.0.0.1", "127.0.0.0/24"), 1);
+ ASSERT_INT_EQ(addr_match_list("127.0.0.1", "127.0.1.0/24"), 0);
+ ASSERT_INT_EQ(addr_match_list("127.0.0.1", "!127.0.0.0/24"), -1);
+ /* XXX negated ASSERT_INT_EQ(addr_match_list("127.0.0.1", "!127.0.1.0/24"), 1); */
+ ASSERT_INT_EQ(addr_match_list("127.0.0.1", "10.0.0.1,!127.0.0.1"), -1);
+ ASSERT_INT_EQ(addr_match_list("127.0.0.1", "!127.0.0.1,10.0.0.1"), -1);
+ ASSERT_INT_EQ(addr_match_list("127.0.0.1", "10.0.0.1,127.0.0.2"), 0);
+ ASSERT_INT_EQ(addr_match_list("127.0.0.1", "127.0.0.2,10.0.0.1"), 0);
+ /* XXX negated ASSERT_INT_EQ(addr_match_list("127.0.0.1", "10.0.0.1,!127.0.0.2"), 1); */
+ /* XXX negated ASSERT_INT_EQ(addr_match_list("127.0.0.1", "!127.0.0.2,10.0.0.1"), 1); */
+ TEST_DONE();
+
+#define CHECK_FILTER(string,filter,expected) \
+ do { \
+ char *result = match_filter_denylist((string), (filter)); \
+ ASSERT_STRING_EQ(result, expected); \
+ free(result); \
+ } while (0)
+
+ TEST_START("match_filter_list");
+ CHECK_FILTER("a,b,c", "", "a,b,c");
+ CHECK_FILTER("a,b,c", "a", "b,c");
+ CHECK_FILTER("a,b,c", "b", "a,c");
+ CHECK_FILTER("a,b,c", "c", "a,b");
+ CHECK_FILTER("a,b,c", "a,b", "c");
+ CHECK_FILTER("a,b,c", "a,c", "b");
+ CHECK_FILTER("a,b,c", "b,c", "a");
+ CHECK_FILTER("a,b,c", "a,b,c", "");
+ CHECK_FILTER("a,b,c", "b,c", "a");
+ CHECK_FILTER("", "a,b,c", "");
+ TEST_DONE();
+/*
+ * XXX TODO
+ * int match_host_and_ip(const char *, const char *, const char *);
+ * int match_user(const char *, const char *, const char *, const char *);
+ * char *match_list(const char *, const char *, u_int *);
+ * int addr_match_cidr_list(const char *, const char *);
+ */
+}
diff --git a/regress/unittests/misc/Makefile b/regress/unittests/misc/Makefile
new file mode 100644
index 0000000..d2be393
--- /dev/null
+++ b/regress/unittests/misc/Makefile
@@ -0,0 +1,33 @@
+# $OpenBSD: Makefile,v 1.9 2023/01/06 02:59:50 djm Exp $
+
+PROG=test_misc
+SRCS=tests.c
+SRCS+= test_convtime.c
+SRCS+= test_expand.c
+SRCS+= test_parse.c
+SRCS+= test_argv.c
+SRCS+= test_strdelim.c
+SRCS+= test_hpdelim.c
+SRCS+= test_ptimeout.c
+
+# From usr.bin/ssh/Makefile.inc
+SRCS+= sshbuf.c
+SRCS+= sshbuf-getput-basic.c
+SRCS+= sshbuf-misc.c
+SRCS+= ssherr.c
+SRCS+= log.c
+SRCS+= xmalloc.c
+SRCS+= misc.c
+SRCS+= match.c
+SRCS+= addr.c
+SRCS+= addrmatch.c
+
+# From usr.bin/ssh/sshd/Makefile
+SRCS+= atomicio.c cleanup.c fatal.c
+
+REGRESS_TARGETS=run-regress-${PROG}
+
+run-regress-${PROG}: ${PROG}
+ env ${TEST_ENV} ./${PROG}
+
+.include <bsd.regress.mk>
diff --git a/regress/unittests/misc/test_argv.c b/regress/unittests/misc/test_argv.c
new file mode 100644
index 0000000..682863e
--- /dev/null
+++ b/regress/unittests/misc/test_argv.c
@@ -0,0 +1,186 @@
+/* $OpenBSD: test_argv.c,v 1.4 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for misc argv handling functions.
+ *
+ * Placed in the public domain.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "log.h"
+#include "misc.h"
+
+void test_argv(void);
+
+void
+test_argv(void)
+{
+ char **av = NULL;
+ int ac = 0;
+
+#define RESET_ARGV() \
+ do { \
+ argv_free(av, ac); \
+ av = NULL; \
+ ac = -1; \
+ } while (0)
+
+ TEST_START("empty args");
+ ASSERT_INT_EQ(argv_split("", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 0);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_PTR_EQ(av[0], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split(" ", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 0);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_PTR_EQ(av[0], NULL);
+ RESET_ARGV();
+ TEST_DONE();
+
+ TEST_START("trivial args");
+ ASSERT_INT_EQ(argv_split("leamas", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 1);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "leamas");
+ ASSERT_PTR_EQ(av[1], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("smiley leamas", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 2);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "smiley");
+ ASSERT_STRING_EQ(av[1], "leamas");
+ ASSERT_PTR_EQ(av[2], NULL);
+ RESET_ARGV();
+ TEST_DONE();
+
+ TEST_START("quoted");
+ ASSERT_INT_EQ(argv_split("\"smiley\"", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 1);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "smiley");
+ ASSERT_PTR_EQ(av[1], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("leamas \" smiley \"", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 2);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "leamas");
+ ASSERT_STRING_EQ(av[1], " smiley ");
+ ASSERT_PTR_EQ(av[2], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("\"smiley leamas\"", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 1);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "smiley leamas");
+ ASSERT_PTR_EQ(av[1], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("smiley\" leamas\" liz", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 2);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "smiley leamas");
+ ASSERT_STRING_EQ(av[1], "liz");
+ ASSERT_PTR_EQ(av[2], NULL);
+ RESET_ARGV();
+ TEST_DONE();
+
+ TEST_START("escaped");
+ ASSERT_INT_EQ(argv_split("\\\"smiley\\'", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 1);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "\"smiley'");
+ ASSERT_PTR_EQ(av[1], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("'\\'smiley\\\"'", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 1);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "'smiley\"");
+ ASSERT_PTR_EQ(av[1], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("smiley\\'s leamas\\'", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 2);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "smiley's");
+ ASSERT_STRING_EQ(av[1], "leamas'");
+ ASSERT_PTR_EQ(av[2], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("leamas\\\\smiley", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 1);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "leamas\\smiley");
+ ASSERT_PTR_EQ(av[1], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("leamas\\\\ \\\\smiley", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 2);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "leamas\\");
+ ASSERT_STRING_EQ(av[1], "\\smiley");
+ ASSERT_PTR_EQ(av[2], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("smiley\\ leamas", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 1);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "smiley leamas");
+ ASSERT_PTR_EQ(av[1], NULL);
+ RESET_ARGV();
+ TEST_DONE();
+
+ TEST_START("quoted escaped");
+ ASSERT_INT_EQ(argv_split("'smiley\\ leamas'", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 1);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "smiley\\ leamas");
+ ASSERT_PTR_EQ(av[1], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("\"smiley\\ leamas\"", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 1);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "smiley\\ leamas");
+ ASSERT_PTR_EQ(av[1], NULL);
+ RESET_ARGV();
+ TEST_DONE();
+
+ TEST_START("comments");
+ ASSERT_INT_EQ(argv_split("# gold", &ac, &av, 0), 0);
+ ASSERT_INT_EQ(ac, 2);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "#");
+ ASSERT_STRING_EQ(av[1], "gold");
+ ASSERT_PTR_EQ(av[2], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("# gold", &ac, &av, 1), 0);
+ ASSERT_INT_EQ(ac, 0);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_PTR_EQ(av[0], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("leamas#gold", &ac, &av, 1), 0);
+ ASSERT_INT_EQ(ac, 1);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "leamas#gold");
+ ASSERT_PTR_EQ(av[1], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("\"leamas # gold\"", &ac, &av, 1), 0);
+ ASSERT_INT_EQ(ac, 1);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "leamas # gold");
+ ASSERT_PTR_EQ(av[1], NULL);
+ RESET_ARGV();
+ ASSERT_INT_EQ(argv_split("\"leamas\"#gold", &ac, &av, 1), 0);
+ ASSERT_INT_EQ(ac, 1);
+ ASSERT_PTR_NE(av, NULL);
+ ASSERT_STRING_EQ(av[0], "leamas#gold");
+ ASSERT_PTR_EQ(av[1], NULL);
+ RESET_ARGV();
+ TEST_DONE();
+
+ /* XXX test char *argv_assemble(int argc, char **argv) */
+}
diff --git a/regress/unittests/misc/test_convtime.c b/regress/unittests/misc/test_convtime.c
new file mode 100644
index 0000000..4794dbd
--- /dev/null
+++ b/regress/unittests/misc/test_convtime.c
@@ -0,0 +1,121 @@
+/* $OpenBSD: test_convtime.c,v 1.3 2022/08/11 01:57:50 djm Exp $ */
+/*
+ * Regress test for misc time conversion functions.
+ *
+ * Placed in the public domain.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <limits.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "log.h"
+#include "misc.h"
+#include "ssherr.h"
+
+void test_convtime(void);
+
+void
+test_convtime(void)
+{
+ char buf[1024];
+ uint64_t t;
+
+ TEST_START("misc_convtime");
+ ASSERT_INT_EQ(convtime("0"), 0);
+ ASSERT_INT_EQ(convtime("1"), 1);
+ ASSERT_INT_EQ(convtime("2s"), 2);
+ ASSERT_INT_EQ(convtime("3m"), 180);
+ ASSERT_INT_EQ(convtime("1m30"), 90);
+ ASSERT_INT_EQ(convtime("1m30s"), 90);
+ ASSERT_INT_EQ(convtime("1h1s"), 3601);
+ ASSERT_INT_EQ(convtime("1h30m"), 90 * 60);
+ ASSERT_INT_EQ(convtime("1d"), 24 * 60 * 60);
+ ASSERT_INT_EQ(convtime("1w"), 7 * 24 * 60 * 60);
+ ASSERT_INT_EQ(convtime("1w2d3h4m5"), 788645);
+ ASSERT_INT_EQ(convtime("1w2d3h4m5s"), 788645);
+ /* any negative number or error returns -1 */
+ ASSERT_INT_EQ(convtime("-1"), -1);
+ ASSERT_INT_EQ(convtime(""), -1);
+ ASSERT_INT_EQ(convtime("trout"), -1);
+ ASSERT_INT_EQ(convtime("-77"), -1);
+ /* boundary conditions */
+ snprintf(buf, sizeof buf, "%llu", (long long unsigned)INT_MAX);
+ ASSERT_INT_EQ(convtime(buf), INT_MAX);
+ snprintf(buf, sizeof buf, "%llu", (long long unsigned)INT_MAX + 1);
+ ASSERT_INT_EQ(convtime(buf), -1);
+ ASSERT_INT_EQ(convtime("3550w5d3h14m7s"), 2147483647);
+#if INT_MAX == 2147483647
+ ASSERT_INT_EQ(convtime("3550w5d3h14m8s"), -1);
+#endif
+ TEST_DONE();
+
+ /* XXX timezones/DST make verification of this tricky */
+ /* XXX maybe setenv TZ and tzset() to make it unambiguous? */
+ TEST_START("misc_parse_absolute_time");
+ ASSERT_INT_EQ(parse_absolute_time("20000101", &t), 0);
+ ASSERT_INT_EQ(parse_absolute_time("200001011223", &t), 0);
+ ASSERT_INT_EQ(parse_absolute_time("20000101122345", &t), 0);
+
+ /* forced UTC TZ */
+ ASSERT_INT_EQ(parse_absolute_time("20000101Z", &t), 0);
+ ASSERT_U64_EQ(t, 946684800);
+ ASSERT_INT_EQ(parse_absolute_time("200001011223Z", &t), 0);
+ ASSERT_U64_EQ(t, 946729380);
+ ASSERT_INT_EQ(parse_absolute_time("20000101122345Z", &t), 0);
+ ASSERT_U64_EQ(t, 946729425);
+ ASSERT_INT_EQ(parse_absolute_time("20000101UTC", &t), 0);
+ ASSERT_U64_EQ(t, 946684800);
+ ASSERT_INT_EQ(parse_absolute_time("200001011223UTC", &t), 0);
+ ASSERT_U64_EQ(t, 946729380);
+ ASSERT_INT_EQ(parse_absolute_time("20000101122345UTC", &t), 0);
+ ASSERT_U64_EQ(t, 946729425);
+
+ /* Bad month */
+ ASSERT_INT_EQ(parse_absolute_time("20001301", &t),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(parse_absolute_time("20000001", &t),
+ SSH_ERR_INVALID_FORMAT);
+ /* Incomplete */
+ ASSERT_INT_EQ(parse_absolute_time("2", &t),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(parse_absolute_time("2000", &t),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(parse_absolute_time("20000", &t),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(parse_absolute_time("200001", &t),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(parse_absolute_time("2000010", &t),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(parse_absolute_time("200001010", &t),
+ SSH_ERR_INVALID_FORMAT);
+ /* Bad day, hour, minute, second */
+ ASSERT_INT_EQ(parse_absolute_time("20000199", &t),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(parse_absolute_time("200001019900", &t),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(parse_absolute_time("200001010099", &t),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(parse_absolute_time("20000101000099", &t),
+ SSH_ERR_INVALID_FORMAT);
+ /* Invalid TZ specifier */
+ ASSERT_INT_EQ(parse_absolute_time("20000101ZZ", &t),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(parse_absolute_time("20000101PDT", &t),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(parse_absolute_time("20000101U", &t),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(parse_absolute_time("20000101UTCUTC", &t),
+ SSH_ERR_INVALID_FORMAT);
+
+ TEST_DONE();
+}
diff --git a/regress/unittests/misc/test_expand.c b/regress/unittests/misc/test_expand.c
new file mode 100644
index 0000000..6f2cd8a
--- /dev/null
+++ b/regress/unittests/misc/test_expand.c
@@ -0,0 +1,89 @@
+/* $OpenBSD: test_expand.c,v 1.3 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for misc string expansion functions.
+ *
+ * Placed in the public domain.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "log.h"
+#include "misc.h"
+
+void test_expand(void);
+
+void
+test_expand(void)
+{
+ int parseerr;
+ char *ret;
+
+ TEST_START("dollar_expand");
+ ASSERT_INT_EQ(setenv("FOO", "bar", 1), 0);
+ ASSERT_INT_EQ(setenv("BAR", "baz", 1), 0);
+ (void)unsetenv("BAZ");
+#define ASSERT_DOLLAR_EQ(x, y) do { \
+ char *str = dollar_expand(NULL, (x)); \
+ ASSERT_STRING_EQ(str, (y)); \
+ free(str); \
+} while(0)
+ ASSERT_DOLLAR_EQ("${FOO}", "bar");
+ ASSERT_DOLLAR_EQ(" ${FOO}", " bar");
+ ASSERT_DOLLAR_EQ("${FOO} ", "bar ");
+ ASSERT_DOLLAR_EQ(" ${FOO} ", " bar ");
+ ASSERT_DOLLAR_EQ("${FOO}${BAR}", "barbaz");
+ ASSERT_DOLLAR_EQ(" ${FOO} ${BAR}", " bar baz");
+ ASSERT_DOLLAR_EQ("${FOO}${BAR} ", "barbaz ");
+ ASSERT_DOLLAR_EQ(" ${FOO} ${BAR} ", " bar baz ");
+ ASSERT_DOLLAR_EQ("$", "$");
+ ASSERT_DOLLAR_EQ(" $", " $");
+ ASSERT_DOLLAR_EQ("$ ", "$ ");
+
+ /* suppress error messages for error handing tests */
+ log_init("test_misc", SYSLOG_LEVEL_QUIET, SYSLOG_FACILITY_AUTH, 1);
+ /* error checking, non existent variable */
+ ret = dollar_expand(&parseerr, "a${BAZ}");
+ ASSERT_PTR_EQ(ret, NULL); ASSERT_INT_EQ(parseerr, 0);
+ ret = dollar_expand(&parseerr, "${BAZ}b");
+ ASSERT_PTR_EQ(ret, NULL); ASSERT_INT_EQ(parseerr, 0);
+ ret = dollar_expand(&parseerr, "a${BAZ}b");
+ ASSERT_PTR_EQ(ret, NULL); ASSERT_INT_EQ(parseerr, 0);
+ /* invalid format */
+ ret = dollar_expand(&parseerr, "${");
+ ASSERT_PTR_EQ(ret, NULL); ASSERT_INT_EQ(parseerr, 1);
+ ret = dollar_expand(&parseerr, "${F");
+ ASSERT_PTR_EQ(ret, NULL); ASSERT_INT_EQ(parseerr, 1);
+ ret = dollar_expand(&parseerr, "${FO");
+ ASSERT_PTR_EQ(ret, NULL); ASSERT_INT_EQ(parseerr, 1);
+ /* empty variable name */
+ ret = dollar_expand(&parseerr, "${}");
+ ASSERT_PTR_EQ(ret, NULL); ASSERT_INT_EQ(parseerr, 1);
+ /* restore loglevel to default */
+ log_init("test_misc", SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_AUTH, 1);
+ TEST_DONE();
+
+ TEST_START("percent_expand");
+ ASSERT_STRING_EQ(percent_expand("%%", "%h", "foo", NULL), "%");
+ ASSERT_STRING_EQ(percent_expand("%h", "h", "foo", NULL), "foo");
+ ASSERT_STRING_EQ(percent_expand("%h ", "h", "foo", NULL), "foo ");
+ ASSERT_STRING_EQ(percent_expand(" %h", "h", "foo", NULL), " foo");
+ ASSERT_STRING_EQ(percent_expand(" %h ", "h", "foo", NULL), " foo ");
+ ASSERT_STRING_EQ(percent_expand(" %a%b ", "a", "foo", "b", "bar", NULL),
+ " foobar ");
+ TEST_DONE();
+
+ TEST_START("percent_dollar_expand");
+ ASSERT_STRING_EQ(percent_dollar_expand("%h${FOO}", "h", "foo", NULL),
+ "foobar");
+ TEST_DONE();
+}
diff --git a/regress/unittests/misc/test_hpdelim.c b/regress/unittests/misc/test_hpdelim.c
new file mode 100644
index 0000000..d423023
--- /dev/null
+++ b/regress/unittests/misc/test_hpdelim.c
@@ -0,0 +1,82 @@
+/* $OpenBSD: test_hpdelim.c,v 1.2 2022/02/06 22:58:33 dtucker Exp $ */
+/*
+ * Regress test for misc hpdelim() and co
+ *
+ * Placed in the public domain.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "log.h"
+#include "misc.h"
+#include "xmalloc.h"
+
+void test_hpdelim(void);
+
+void
+test_hpdelim(void)
+{
+ char *orig, *str, *cp, *port;
+
+#define START_STRING(x) orig = str = xstrdup(x)
+#define DONE_STRING() free(orig)
+
+ TEST_START("hpdelim host only");
+ START_STRING("host");
+ cp = hpdelim(&str);
+ ASSERT_STRING_EQ(cp, "host");
+ ASSERT_PTR_EQ(str, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("hpdelim :port");
+ START_STRING(":1234");
+ cp = hpdelim(&str);
+ ASSERT_STRING_EQ(cp, "");
+ ASSERT_PTR_NE(str, NULL);
+ port = hpdelim(&str);
+ ASSERT_STRING_EQ(port, "1234");
+ ASSERT_PTR_EQ(str, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("hpdelim host:port");
+ START_STRING("host:1234");
+ cp = hpdelim(&str);
+ ASSERT_STRING_EQ(cp, "host");
+ ASSERT_PTR_NE(str, NULL);
+ port = hpdelim(&str);
+ ASSERT_STRING_EQ(port, "1234");
+ ASSERT_PTR_EQ(str, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("hpdelim [host]:port");
+ START_STRING("[::1]:1234");
+ cp = hpdelim(&str);
+ ASSERT_STRING_EQ(cp, "[::1]");
+ ASSERT_PTR_NE(str, NULL);
+ port = hpdelim(&str);
+ ASSERT_STRING_EQ(port, "1234");
+ ASSERT_PTR_EQ(str, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("hpdelim missing ] error");
+ START_STRING("[::1:1234");
+ cp = hpdelim(&str);
+ ASSERT_PTR_EQ(cp, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+}
diff --git a/regress/unittests/misc/test_parse.c b/regress/unittests/misc/test_parse.c
new file mode 100644
index 0000000..1f1ea31
--- /dev/null
+++ b/regress/unittests/misc/test_parse.c
@@ -0,0 +1,85 @@
+/* $OpenBSD: test_parse.c,v 1.2 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for misc user/host/URI parsing functions.
+ *
+ * Placed in the public domain.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "log.h"
+#include "misc.h"
+
+void test_parse(void);
+
+void
+test_parse(void)
+{
+ int port;
+ char *user, *host, *path;
+
+ TEST_START("misc_parse_user_host_path");
+ ASSERT_INT_EQ(parse_user_host_path("someuser@some.host:some/path",
+ &user, &host, &path), 0);
+ ASSERT_STRING_EQ(user, "someuser");
+ ASSERT_STRING_EQ(host, "some.host");
+ ASSERT_STRING_EQ(path, "some/path");
+ free(user); free(host); free(path);
+ TEST_DONE();
+
+ TEST_START("misc_parse_user_ipv4_path");
+ ASSERT_INT_EQ(parse_user_host_path("someuser@1.22.33.144:some/path",
+ &user, &host, &path), 0);
+ ASSERT_STRING_EQ(user, "someuser");
+ ASSERT_STRING_EQ(host, "1.22.33.144");
+ ASSERT_STRING_EQ(path, "some/path");
+ free(user); free(host); free(path);
+ TEST_DONE();
+
+ TEST_START("misc_parse_user_[ipv4]_path");
+ ASSERT_INT_EQ(parse_user_host_path("someuser@[1.22.33.144]:some/path",
+ &user, &host, &path), 0);
+ ASSERT_STRING_EQ(user, "someuser");
+ ASSERT_STRING_EQ(host, "1.22.33.144");
+ ASSERT_STRING_EQ(path, "some/path");
+ free(user); free(host); free(path);
+ TEST_DONE();
+
+ TEST_START("misc_parse_user_[ipv4]_nopath");
+ ASSERT_INT_EQ(parse_user_host_path("someuser@[1.22.33.144]:",
+ &user, &host, &path), 0);
+ ASSERT_STRING_EQ(user, "someuser");
+ ASSERT_STRING_EQ(host, "1.22.33.144");
+ ASSERT_STRING_EQ(path, ".");
+ free(user); free(host); free(path);
+ TEST_DONE();
+
+ TEST_START("misc_parse_user_ipv6_path");
+ ASSERT_INT_EQ(parse_user_host_path("someuser@[::1]:some/path",
+ &user, &host, &path), 0);
+ ASSERT_STRING_EQ(user, "someuser");
+ ASSERT_STRING_EQ(host, "::1");
+ ASSERT_STRING_EQ(path, "some/path");
+ free(user); free(host); free(path);
+ TEST_DONE();
+
+ TEST_START("misc_parse_uri");
+ ASSERT_INT_EQ(parse_uri("ssh", "ssh://someuser@some.host:22/some/path",
+ &user, &host, &port, &path), 0);
+ ASSERT_STRING_EQ(user, "someuser");
+ ASSERT_STRING_EQ(host, "some.host");
+ ASSERT_INT_EQ(port, 22);
+ ASSERT_STRING_EQ(path, "some/path");
+ free(user); free(host); free(path);
+ TEST_DONE();
+}
diff --git a/regress/unittests/misc/test_ptimeout.c b/regress/unittests/misc/test_ptimeout.c
new file mode 100644
index 0000000..7adc590
--- /dev/null
+++ b/regress/unittests/misc/test_ptimeout.c
@@ -0,0 +1,85 @@
+/* $OpenBSD: test_ptimeout.c,v 1.1 2023/01/06 02:59:50 djm Exp $ */
+/*
+ * Regress test for misc poll/ppoll timeout helpers.
+ *
+ * Placed in the public domain.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <poll.h>
+#include <time.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "log.h"
+#include "misc.h"
+
+void test_ptimeout(void);
+
+void
+test_ptimeout(void)
+{
+ struct timespec pt, *ts;
+
+ TEST_START("ptimeout_init");
+ ptimeout_init(&pt);
+ ASSERT_PTR_EQ(ptimeout_get_tsp(&pt), NULL);
+ ASSERT_INT_EQ(ptimeout_get_ms(&pt), -1);
+ TEST_DONE();
+
+ TEST_START("ptimeout_deadline_sec");
+ ptimeout_deadline_sec(&pt, 100);
+ ptimeout_deadline_sec(&pt, 200);
+ ASSERT_INT_EQ(ptimeout_get_ms(&pt), 100 * 1000);
+ ts = ptimeout_get_tsp(&pt);
+ ASSERT_PTR_NE(ts, NULL);
+ ASSERT_LONG_EQ(ts->tv_nsec, 0);
+ ASSERT_LONG_EQ(ts->tv_sec, 100);
+ TEST_DONE();
+
+ TEST_START("ptimeout_deadline_ms");
+ ptimeout_deadline_ms(&pt, 50123);
+ ptimeout_deadline_ms(&pt, 50500);
+ ASSERT_INT_EQ(ptimeout_get_ms(&pt), 50123);
+ ts = ptimeout_get_tsp(&pt);
+ ASSERT_PTR_NE(ts, NULL);
+ ASSERT_LONG_EQ(ts->tv_nsec, 123 * 1000000);
+ ASSERT_LONG_EQ(ts->tv_sec, 50);
+ TEST_DONE();
+
+ TEST_START("ptimeout zero");
+ ptimeout_init(&pt);
+ ptimeout_deadline_ms(&pt, 0);
+ ASSERT_INT_EQ(ptimeout_get_ms(&pt), 0);
+ ts = ptimeout_get_tsp(&pt);
+ ASSERT_PTR_NE(ts, NULL);
+ ASSERT_LONG_EQ(ts->tv_nsec, 0);
+ ASSERT_LONG_EQ(ts->tv_sec, 0);
+ TEST_DONE();
+
+ TEST_START("ptimeout_deadline_monotime");
+ ptimeout_init(&pt);
+ ptimeout_deadline_monotime(&pt, monotime() + 100);
+ ASSERT_INT_GT(ptimeout_get_ms(&pt), 50000);
+ ASSERT_INT_LT(ptimeout_get_ms(&pt), 200000);
+ ts = ptimeout_get_tsp(&pt);
+ ASSERT_PTR_NE(ts, NULL);
+ ASSERT_LONG_GT(ts->tv_sec, 50);
+ ASSERT_LONG_LT(ts->tv_sec, 200);
+ TEST_DONE();
+
+ TEST_START("ptimeout_deadline_monotime past");
+ ptimeout_init(&pt);
+ ptimeout_deadline_monotime(&pt, monotime() + 100);
+ ptimeout_deadline_monotime(&pt, monotime() - 100);
+ ASSERT_INT_EQ(ptimeout_get_ms(&pt), 0);
+ ts = ptimeout_get_tsp(&pt);
+ ASSERT_PTR_NE(ts, NULL);
+ ASSERT_LONG_EQ(ts->tv_nsec, 0);
+ ASSERT_LONG_EQ(ts->tv_sec, 0);
+ TEST_DONE();
+}
diff --git a/regress/unittests/misc/test_strdelim.c b/regress/unittests/misc/test_strdelim.c
new file mode 100644
index 0000000..f7bea4b
--- /dev/null
+++ b/regress/unittests/misc/test_strdelim.c
@@ -0,0 +1,201 @@
+/* $OpenBSD: test_strdelim.c,v 1.3 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for misc strdelim() and co
+ *
+ * Placed in the public domain.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "log.h"
+#include "misc.h"
+#include "xmalloc.h"
+
+void test_strdelim(void);
+
+void
+test_strdelim(void)
+{
+ char *orig, *str, *cp;
+
+#define START_STRING(x) orig = str = xstrdup(x)
+#define DONE_STRING() free(orig)
+
+ TEST_START("empty");
+ START_STRING("");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, ""); /* XXX arguable */
+ cp = strdelim(&str);
+ ASSERT_PTR_EQ(cp, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("whitespace");
+ START_STRING(" ");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, ""); /* XXX better as NULL */
+ ASSERT_STRING_EQ(str, "");
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("trivial");
+ START_STRING("blob");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob");
+ cp = strdelim(&str);
+ ASSERT_PTR_EQ(cp, NULL);
+ ASSERT_PTR_EQ(str, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("trivial whitespace");
+ START_STRING("blob ");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob");
+ ASSERT_STRING_EQ(str, "");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, ""); /* XXX better as NULL */
+ ASSERT_PTR_EQ(str, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("multi");
+ START_STRING("blob1 blob2");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob1");
+ ASSERT_STRING_EQ(str, "blob2");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob2");
+ ASSERT_PTR_EQ(str, NULL);
+ cp = strdelim(&str);
+ ASSERT_PTR_EQ(cp, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("multi whitespace");
+ START_STRING("blob1 blob2 ");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob1");
+ ASSERT_STRING_EQ(str, "blob2 ");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob2");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, ""); /* XXX better as NULL */
+ ASSERT_PTR_EQ(str, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("multi equals");
+ START_STRING("blob1=blob2");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob1");
+ ASSERT_STRING_EQ(str, "blob2");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob2");
+ ASSERT_PTR_EQ(str, NULL);
+ cp = strdelim(&str);
+ ASSERT_PTR_EQ(cp, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("multi too many equals");
+ START_STRING("blob1==blob2");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob1"); /* XXX better returning NULL early */
+ ASSERT_STRING_EQ(str, "=blob2");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "");
+ ASSERT_STRING_EQ(str, "blob2");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob2"); /* XXX should (but can't) reject */
+ ASSERT_PTR_EQ(str, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("multi equals strdelimw");
+ START_STRING("blob1=blob2");
+ cp = strdelimw(&str);
+ ASSERT_STRING_EQ(cp, "blob1=blob2");
+ ASSERT_PTR_EQ(str, NULL);
+ cp = strdelimw(&str);
+ ASSERT_PTR_EQ(cp, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("quoted");
+ START_STRING("\"blob\"");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, ""); /* XXX better as NULL */
+ ASSERT_PTR_EQ(str, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("quoted multi");
+ START_STRING("\"blob1\" blob2");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob1");
+ ASSERT_STRING_EQ(str, "blob2");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob2");
+ ASSERT_PTR_EQ(str, NULL);
+ cp = strdelim(&str);
+ ASSERT_PTR_EQ(cp, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("quoted multi reverse");
+ START_STRING("blob1 \"blob2\"");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob1");
+ ASSERT_STRING_EQ(str, "\"blob2\"");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob2");
+ ASSERT_STRING_EQ(str, "");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, ""); /* XXX better as NULL */
+ ASSERT_PTR_EQ(str, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("quoted multi middle");
+ START_STRING("blob1 \"blob2\" blob3");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob1");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob2");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob3");
+ cp = strdelim(&str);
+ ASSERT_PTR_EQ(cp, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("badquote");
+ START_STRING("\"blob");
+ cp = strdelim(&str);
+ ASSERT_PTR_EQ(cp, NULL);
+ DONE_STRING();
+ TEST_DONE();
+
+ TEST_START("oops quote");
+ START_STRING("\"blob\\\"");
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "blob\\"); /* XXX wrong */
+ cp = strdelim(&str);
+ ASSERT_STRING_EQ(cp, "");
+ DONE_STRING();
+ TEST_DONE();
+
+}
diff --git a/regress/unittests/misc/tests.c b/regress/unittests/misc/tests.c
new file mode 100644
index 0000000..3269954
--- /dev/null
+++ b/regress/unittests/misc/tests.c
@@ -0,0 +1,41 @@
+/* $OpenBSD: tests.c,v 1.10 2023/01/06 02:59:50 djm Exp $ */
+/*
+ * Regress test for misc helper functions.
+ *
+ * Placed in the public domain.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "log.h"
+#include "misc.h"
+
+void test_parse(void);
+void test_convtime(void);
+void test_expand(void);
+void test_argv(void);
+void test_strdelim(void);
+void test_hpdelim(void);
+void test_ptimeout(void);
+
+void
+tests(void)
+{
+ test_parse();
+ test_convtime();
+ test_expand();
+ test_argv();
+ test_strdelim();
+ test_hpdelim();
+ test_ptimeout();
+}
diff --git a/regress/unittests/sshbuf/Makefile b/regress/unittests/sshbuf/Makefile
new file mode 100644
index 0000000..a8ddfaf
--- /dev/null
+++ b/regress/unittests/sshbuf/Makefile
@@ -0,0 +1,22 @@
+# $OpenBSD: Makefile,v 1.10 2021/01/09 12:24:31 dtucker Exp $
+
+# $OpenBSD: Makefile,v 1.8 2020/01/26 00:09:50 djm Exp $
+
+PROG=test_sshbuf
+SRCS=tests.c
+SRCS+=test_sshbuf.c
+SRCS+=test_sshbuf_getput_basic.c
+SRCS+=test_sshbuf_getput_crypto.c
+SRCS+=test_sshbuf_misc.c
+SRCS+=test_sshbuf_fuzz.c
+SRCS+=test_sshbuf_getput_fuzz.c
+SRCS+=test_sshbuf_fixed.c
+
+# From usr.bin/ssh
+SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c
+SRCS+=sshbuf-io.c atomicio.c misc.c xmalloc.c log.c fatal.c ssherr.c cleanup.c
+SRCS+=match.c addr.c addrmatch.c
+
+run-regress-${PROG}: ${PROG}
+ env ${TEST_ENV} ./${PROG} ${UNITTEST_ARGS}
+
diff --git a/regress/unittests/sshbuf/test_sshbuf.c b/regress/unittests/sshbuf/test_sshbuf.c
new file mode 100644
index 0000000..e22b390
--- /dev/null
+++ b/regress/unittests/sshbuf/test_sshbuf.c
@@ -0,0 +1,243 @@
+/* $OpenBSD: test_sshbuf.c,v 1.2 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for sshbuf.h buffer API
+ *
+ * Placed in the public domain
+ */
+
+#define SSHBUF_INTERNAL 1 /* access internals for testing */
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "ssherr.h"
+#include "sshbuf.h"
+
+void sshbuf_tests(void);
+
+#ifndef roundup
+#define roundup(x, y) ((((x)+((y)-1))/(y))*(y))
+#endif
+
+void
+sshbuf_tests(void)
+{
+ struct sshbuf *p1;
+ const u_char *cdp;
+ u_char *dp;
+ size_t sz;
+ int r;
+
+ TEST_START("allocate sshbuf");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ TEST_DONE();
+
+ TEST_START("max size on fresh buffer");
+ ASSERT_SIZE_T_GT(sshbuf_max_size(p1), 0);
+ TEST_DONE();
+
+ TEST_START("available on fresh buffer");
+ ASSERT_SIZE_T_GT(sshbuf_avail(p1), 0);
+ TEST_DONE();
+
+ TEST_START("len = 0 on empty buffer");
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ TEST_DONE();
+
+ TEST_START("set valid max size");
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, 65536), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_max_size(p1), 65536);
+ TEST_DONE();
+
+ TEST_START("available on limited buffer");
+ ASSERT_SIZE_T_EQ(sshbuf_avail(p1), 65536);
+ TEST_DONE();
+
+ TEST_START("free");
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("consume on empty buffer");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_consume(p1, 0), 0);
+ ASSERT_INT_EQ(sshbuf_consume(p1, 1), SSH_ERR_MESSAGE_INCOMPLETE);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("consume_end on empty buffer");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_consume_end(p1, 0), 0);
+ ASSERT_INT_EQ(sshbuf_consume_end(p1, 1), SSH_ERR_MESSAGE_INCOMPLETE);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("reserve space");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ r = sshbuf_reserve(p1, 1, &dp);
+ ASSERT_INT_EQ(r, 0);
+ ASSERT_PTR_NE(dp, NULL);
+ *dp = 0x11;
+ r = sshbuf_reserve(p1, 3, &dp);
+ ASSERT_INT_EQ(r, 0);
+ ASSERT_PTR_NE(dp, NULL);
+ *dp++ = 0x22;
+ *dp++ = 0x33;
+ *dp++ = 0x44;
+ TEST_DONE();
+
+ TEST_START("sshbuf_len on filled buffer");
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 4);
+ TEST_DONE();
+
+ TEST_START("sshbuf_ptr on filled buffer");
+ cdp = sshbuf_ptr(p1);
+ ASSERT_PTR_NE(cdp, NULL);
+ ASSERT_U8_EQ(cdp[0], 0x11);
+ ASSERT_U8_EQ(cdp[1], 0x22);
+ ASSERT_U8_EQ(cdp[2], 0x33);
+ ASSERT_U8_EQ(cdp[3], 0x44);
+ TEST_DONE();
+
+ TEST_START("consume on filled buffer");
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 4);
+ ASSERT_INT_EQ(sshbuf_consume(p1, 0), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 4);
+ r = sshbuf_consume(p1, 64);
+ ASSERT_INT_EQ(r, SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 4);
+ ASSERT_INT_EQ(sshbuf_consume(p1, 1), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 3);
+ cdp = sshbuf_ptr(p1);
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_U8_EQ(cdp[0], 0x22);
+ ASSERT_INT_EQ(sshbuf_consume(p1, 2), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1);
+ cdp = sshbuf_ptr(p1);
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_U8_EQ(cdp[0], 0x44);
+ r = sshbuf_consume(p1, 2);
+ ASSERT_INT_EQ(r, SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1);
+ ASSERT_INT_EQ(sshbuf_consume(p1, 1), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ r = sshbuf_consume(p1, 1);
+ ASSERT_INT_EQ(r, SSH_ERR_MESSAGE_INCOMPLETE);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("consume_end on filled buffer");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ r = sshbuf_reserve(p1, 4, &dp);
+ ASSERT_INT_EQ(r, 0);
+ ASSERT_PTR_NE(dp, NULL);
+ *dp++ = 0x11;
+ *dp++ = 0x22;
+ *dp++ = 0x33;
+ *dp++ = 0x44;
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 4);
+ r = sshbuf_consume_end(p1, 5);
+ ASSERT_INT_EQ(r, SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 4);
+ ASSERT_INT_EQ(sshbuf_consume_end(p1, 3), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1);
+ cdp = sshbuf_ptr(p1);
+ ASSERT_PTR_NE(cdp, NULL);
+ ASSERT_U8_EQ(*cdp, 0x11);
+ r = sshbuf_consume_end(p1, 2);
+ ASSERT_INT_EQ(r, SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_consume_end(p1, 1), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("fill limited buffer");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, 1223), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_max_size(p1), 1223);
+ ASSERT_SIZE_T_EQ(sshbuf_avail(p1), 1223);
+ r = sshbuf_reserve(p1, 1223, &dp);
+ ASSERT_INT_EQ(r, 0);
+ ASSERT_PTR_NE(dp, NULL);
+ memset(dp, 0xd7, 1223);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1223);
+ ASSERT_SIZE_T_EQ(sshbuf_avail(p1), 0);
+ r = sshbuf_reserve(p1, 1, &dp);
+ ASSERT_INT_EQ(r, SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_PTR_EQ(dp, NULL);
+ TEST_DONE();
+
+ TEST_START("consume and force compaction");
+ ASSERT_INT_EQ(sshbuf_consume(p1, 223), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1000);
+ ASSERT_SIZE_T_EQ(sshbuf_avail(p1), 223);
+ r = sshbuf_reserve(p1, 224, &dp);
+ ASSERT_INT_EQ(r, SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_PTR_EQ(dp, NULL);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1000);
+ ASSERT_SIZE_T_EQ(sshbuf_avail(p1), 223);
+ r = sshbuf_reserve(p1, 223, &dp);
+ ASSERT_INT_EQ(r, 0);
+ ASSERT_PTR_NE(dp, NULL);
+ memset(dp, 0x7d, 223);
+ cdp = sshbuf_ptr(p1);
+ ASSERT_PTR_NE(cdp, NULL);
+ ASSERT_MEM_FILLED_EQ(cdp, 0xd7, 1000);
+ ASSERT_MEM_FILLED_EQ(cdp + 1000, 0x7d, 223);
+ TEST_DONE();
+
+ TEST_START("resize full buffer");
+ r = sshbuf_set_max_size(p1, 1000);
+ ASSERT_INT_EQ(r, SSH_ERR_NO_BUFFER_SPACE);
+ sz = roundup(1223 + SSHBUF_SIZE_INC * 3, SSHBUF_SIZE_INC);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, sz), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_max_size(p1), sz);
+ ASSERT_SIZE_T_EQ(sshbuf_avail(p1), sz - 1223);
+ ASSERT_INT_EQ(sshbuf_len(p1), 1223);
+ TEST_DONE();
+
+ /* NB. uses sshbuf internals */
+ TEST_START("alloc chunking");
+ r = sshbuf_reserve(p1, 1, &dp);
+ ASSERT_INT_EQ(r, 0);
+ ASSERT_PTR_NE(dp, NULL);
+ *dp = 0xff;
+ cdp = sshbuf_ptr(p1);
+ ASSERT_PTR_NE(cdp, NULL);
+ ASSERT_MEM_FILLED_EQ(cdp, 0xd7, 1000);
+ ASSERT_MEM_FILLED_EQ(cdp + 1000, 0x7d, 223);
+ ASSERT_MEM_FILLED_EQ(cdp + 1223, 0xff, 1);
+ ASSERT_SIZE_T_EQ(sshbuf_alloc(p1) % SSHBUF_SIZE_INC, 0);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("reset buffer");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, 1223), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_max_size(p1), 1223);
+ r = sshbuf_reserve(p1, 1223, &dp);
+ ASSERT_INT_EQ(r, 0);
+ ASSERT_PTR_NE(dp, NULL);
+ memset(dp, 0xd7, 1223);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1223);
+ sshbuf_reset(p1);
+ ASSERT_SIZE_T_EQ(sshbuf_max_size(p1), 1223);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_avail(p1), 1223);
+ sshbuf_free(p1);
+ TEST_DONE();
+}
diff --git a/regress/unittests/sshbuf/test_sshbuf_fixed.c b/regress/unittests/sshbuf/test_sshbuf_fixed.c
new file mode 100644
index 0000000..dff77f0
--- /dev/null
+++ b/regress/unittests/sshbuf/test_sshbuf_fixed.c
@@ -0,0 +1,125 @@
+/* $OpenBSD: test_sshbuf_fixed.c,v 1.2 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for sshbuf.h buffer API
+ *
+ * Placed in the public domain
+ */
+
+#define SSHBUF_INTERNAL 1 /* access internals for testing */
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "sshbuf.h"
+#include "ssherr.h"
+
+void sshbuf_fixed(void);
+
+const u_char test_buf[] = "\x01\x12\x34\x56\x78\x00\x00\x00\x05hello";
+
+void
+sshbuf_fixed(void)
+{
+ struct sshbuf *p1, *p2, *p3;
+ u_char c;
+ char *s;
+ u_int i;
+ size_t l;
+
+ TEST_START("sshbuf_from");
+ p1 = sshbuf_from(test_buf, sizeof(test_buf));
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_PTR_EQ(sshbuf_mutable_ptr(p1), NULL);
+ ASSERT_INT_EQ(sshbuf_check_reserve(p1, 1), SSH_ERR_BUFFER_READ_ONLY);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 1, NULL), SSH_ERR_BUFFER_READ_ONLY);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, 200), SSH_ERR_BUFFER_READ_ONLY);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, 0x12345678), SSH_ERR_BUFFER_READ_ONLY);
+ ASSERT_SIZE_T_EQ(sshbuf_avail(p1), 0);
+ ASSERT_PTR_EQ(sshbuf_ptr(p1), test_buf);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_from data");
+ p1 = sshbuf_from(test_buf, sizeof(test_buf) - 1);
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_PTR_EQ(sshbuf_ptr(p1), test_buf);
+ ASSERT_INT_EQ(sshbuf_get_u8(p1, &c), 0);
+ ASSERT_PTR_EQ(sshbuf_ptr(p1), test_buf + 1);
+ ASSERT_U8_EQ(c, 1);
+ ASSERT_INT_EQ(sshbuf_get_u32(p1, &i), 0);
+ ASSERT_PTR_EQ(sshbuf_ptr(p1), test_buf + 5);
+ ASSERT_U32_EQ(i, 0x12345678);
+ ASSERT_INT_EQ(sshbuf_get_cstring(p1, &s, &l), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ ASSERT_STRING_EQ(s, "hello");
+ ASSERT_SIZE_T_EQ(l, 5);
+ sshbuf_free(p1);
+ free(s);
+ TEST_DONE();
+
+ TEST_START("sshbuf_fromb ");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_U_INT_EQ(sshbuf_refcount(p1), 1);
+ ASSERT_PTR_EQ(sshbuf_parent(p1), NULL);
+ ASSERT_INT_EQ(sshbuf_put(p1, test_buf, sizeof(test_buf) - 1), 0);
+ p2 = sshbuf_fromb(p1);
+ ASSERT_PTR_NE(p2, NULL);
+ ASSERT_U_INT_EQ(sshbuf_refcount(p1), 2);
+ ASSERT_PTR_EQ(sshbuf_parent(p1), NULL);
+ ASSERT_PTR_EQ(sshbuf_parent(p2), p1);
+ ASSERT_PTR_EQ(sshbuf_ptr(p2), sshbuf_ptr(p1));
+ ASSERT_PTR_NE(sshbuf_ptr(p1), NULL);
+ ASSERT_PTR_NE(sshbuf_ptr(p2), NULL);
+ ASSERT_PTR_EQ(sshbuf_mutable_ptr(p1), NULL);
+ ASSERT_PTR_EQ(sshbuf_mutable_ptr(p2), NULL);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sshbuf_len(p2));
+ ASSERT_INT_EQ(sshbuf_get_u8(p2, &c), 0);
+ ASSERT_PTR_EQ(sshbuf_ptr(p2), sshbuf_ptr(p1) + 1);
+ ASSERT_U8_EQ(c, 1);
+ ASSERT_INT_EQ(sshbuf_get_u32(p2, &i), 0);
+ ASSERT_PTR_EQ(sshbuf_ptr(p2), sshbuf_ptr(p1) + 5);
+ ASSERT_U32_EQ(i, 0x12345678);
+ ASSERT_INT_EQ(sshbuf_get_cstring(p2, &s, &l), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p2), 0);
+ ASSERT_STRING_EQ(s, "hello");
+ ASSERT_SIZE_T_EQ(l, 5);
+ sshbuf_free(p1);
+ ASSERT_U_INT_EQ(sshbuf_refcount(p1), 1);
+ sshbuf_free(p2);
+ free(s);
+ TEST_DONE();
+
+ TEST_START("sshbuf_froms");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u8(p1, 0x01), 0);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, 0x12345678), 0);
+ ASSERT_INT_EQ(sshbuf_put_cstring(p1, "hello"), 0);
+ p2 = sshbuf_new();
+ ASSERT_PTR_NE(p2, NULL);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(test_buf) - 1);
+ ASSERT_INT_EQ(sshbuf_put_stringb(p2, p1), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p2), sizeof(test_buf) + 4 - 1);
+ ASSERT_INT_EQ(sshbuf_froms(p2, &p3), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p2), 0);
+ ASSERT_PTR_NE(p3, NULL);
+ ASSERT_PTR_NE(sshbuf_ptr(p3), NULL);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p3), sizeof(test_buf) - 1);
+ ASSERT_MEM_EQ(sshbuf_ptr(p3), test_buf, sizeof(test_buf) - 1);
+ sshbuf_free(p3);
+ ASSERT_INT_EQ(sshbuf_put_stringb(p2, p1), 0);
+ ASSERT_INT_EQ(sshbuf_consume_end(p2, 1), 0);
+ ASSERT_INT_EQ(sshbuf_froms(p2, &p3), SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_PTR_EQ(p3, NULL);
+ sshbuf_free(p2);
+ sshbuf_free(p1);
+}
diff --git a/regress/unittests/sshbuf/test_sshbuf_fuzz.c b/regress/unittests/sshbuf/test_sshbuf_fuzz.c
new file mode 100644
index 0000000..c0b809d
--- /dev/null
+++ b/regress/unittests/sshbuf/test_sshbuf_fuzz.c
@@ -0,0 +1,131 @@
+/* $OpenBSD: test_sshbuf_fuzz.c,v 1.4 2021/12/18 06:53:59 anton Exp $ */
+/*
+ * Regress test for sshbuf.h buffer API
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "ssherr.h"
+#include "sshbuf.h"
+
+#define NUM_FUZZ_TESTS (1 << 18)
+
+void sshbuf_fuzz_tests(void);
+
+void
+sshbuf_fuzz_tests(void)
+{
+ struct sshbuf *p1;
+ u_char *dp;
+ size_t sz, sz2, i, ntests = NUM_FUZZ_TESTS;
+ u_int32_t r;
+ int ret;
+
+ if (test_is_fast())
+ ntests >>= 2;
+ if (test_is_slow())
+ ntests <<= 2;
+
+ /* NB. uses sshbuf internals */
+ TEST_START("fuzz alloc/dealloc");
+ p1 = sshbuf_new();
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, 16 * 1024), 0);
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_PTR_NE(sshbuf_ptr(p1), NULL);
+ ASSERT_MEM_ZERO_NE(sshbuf_ptr(p1), sshbuf_len(p1));
+ for (i = 0; i < ntests; i++) {
+ r = arc4random_uniform(10);
+ if (r == 0) {
+ /* 10% chance: small reserve */
+ r = arc4random_uniform(10);
+ fuzz_reserve:
+ sz = sshbuf_avail(p1);
+ sz2 = sshbuf_len(p1);
+ ret = sshbuf_reserve(p1, r, &dp);
+ if (ret < 0) {
+ ASSERT_PTR_EQ(dp, NULL);
+ ASSERT_SIZE_T_LT(sz, r);
+ ASSERT_SIZE_T_EQ(sshbuf_avail(p1), sz);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sz2);
+ } else {
+ ASSERT_PTR_NE(dp, NULL);
+ ASSERT_SIZE_T_GE(sz, r);
+ ASSERT_SIZE_T_EQ(sshbuf_avail(p1), sz - r);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sz2 + r);
+ memset(dp, arc4random_uniform(255) + 1, r);
+ }
+ } else if (r < 3) {
+ /* 20% chance: big reserve */
+ r = arc4random_uniform(8 * 1024);
+ goto fuzz_reserve;
+ } else if (r == 3) {
+ /* 10% chance: small consume */
+ r = arc4random_uniform(10);
+ fuzz_consume:
+ sz = sshbuf_avail(p1);
+ sz2 = sshbuf_len(p1);
+ /* 50% change consume from end, otherwise start */
+ ret = ((arc4random() & 1) ?
+ sshbuf_consume : sshbuf_consume_end)(p1, r);
+ if (ret < 0) {
+ ASSERT_SIZE_T_LT(sz2, r);
+ ASSERT_SIZE_T_EQ(sshbuf_avail(p1), sz);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sz2);
+ } else {
+ ASSERT_SIZE_T_GE(sz2, r);
+ ASSERT_SIZE_T_EQ(sshbuf_avail(p1), sz + r);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sz2 - r);
+ }
+ } else if (r < 8) {
+ /* 40% chance: big consume */
+ r = arc4random_uniform(2 * 1024);
+ goto fuzz_consume;
+ } else if (r == 8) {
+ /* 10% chance: reset max size */
+ r = arc4random_uniform(16 * 1024);
+ sz = sshbuf_max_size(p1);
+ if (sshbuf_set_max_size(p1, r) < 0)
+ ASSERT_SIZE_T_EQ(sshbuf_max_size(p1), sz);
+ else
+ ASSERT_SIZE_T_EQ(sshbuf_max_size(p1), r);
+ } else {
+ if (arc4random_uniform(8192) == 0) {
+ /* tiny chance: new buffer */
+ ASSERT_PTR_NE(sshbuf_ptr(p1), NULL);
+ ASSERT_MEM_ZERO_NE(sshbuf_ptr(p1), sshbuf_len(p1));
+ sshbuf_free(p1);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1,
+ 16 * 1024), 0);
+ } else {
+ /* Almost 10%: giant reserve */
+ /* use arc4random_buf for r > 2^32 on 64 bit */
+ arc4random_buf(&r, sizeof(r));
+ while (r < SSHBUF_SIZE_MAX / 2) {
+ r <<= 1;
+ r |= arc4random() & 1;
+ }
+ goto fuzz_reserve;
+ }
+ }
+ ASSERT_PTR_NE(sshbuf_ptr(p1), NULL);
+ ASSERT_SIZE_T_LE(sshbuf_max_size(p1), 16 * 1024);
+ }
+ ASSERT_PTR_NE(sshbuf_ptr(p1), NULL);
+ ASSERT_MEM_ZERO_NE(sshbuf_ptr(p1), sshbuf_len(p1));
+ sshbuf_free(p1);
+ TEST_DONE();
+}
diff --git a/regress/unittests/sshbuf/test_sshbuf_getput_basic.c b/regress/unittests/sshbuf/test_sshbuf_getput_basic.c
new file mode 100644
index 0000000..3da413e
--- /dev/null
+++ b/regress/unittests/sshbuf/test_sshbuf_getput_basic.c
@@ -0,0 +1,712 @@
+/* $OpenBSD: test_sshbuf_getput_basic.c,v 1.3 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for sshbuf.h buffer API
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+#include "ssherr.h"
+#include "sshbuf.h"
+
+void sshbuf_getput_basic_tests(void);
+
+void
+sshbuf_getput_basic_tests(void)
+{
+ struct sshbuf *p1, *p2;
+ const u_char *cd;
+ u_char *d, d2[32], x[] = {
+ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x00, 0x99
+ };
+ u_int64_t v64;
+ u_int32_t v32;
+ u_int16_t v16;
+ u_char v8;
+ size_t s;
+ char *s2;
+ int r;
+ u_char bn1[] = { 0x00, 0x00, 0x00 };
+ u_char bn2[] = { 0x00, 0x00, 0x01, 0x02 };
+ u_char bn3[] = { 0x00, 0x80, 0x09 };
+ u_char bn_exp1[] = { 0x00, 0x00, 0x00, 0x00 };
+ u_char bn_exp2[] = { 0x00, 0x00, 0x00, 0x02, 0x01, 0x02 };
+ u_char bn_exp3[] = { 0x00, 0x00, 0x00, 0x03, 0x00, 0x80, 0x09 };
+
+ TEST_START("PEEK_U64");
+ ASSERT_U64_EQ(PEEK_U64(x), 0x1122334455667788ULL);
+ TEST_DONE();
+
+ TEST_START("PEEK_U32");
+ ASSERT_U32_EQ(PEEK_U32(x), 0x11223344);
+ TEST_DONE();
+
+ TEST_START("PEEK_U16");
+ ASSERT_U16_EQ(PEEK_U16(x), 0x1122);
+ TEST_DONE();
+
+ TEST_START("POKE_U64");
+ bzero(d2, sizeof(d2));
+ POKE_U64(d2, 0x1122334455667788ULL);
+ ASSERT_MEM_EQ(d2, x, 8);
+ TEST_DONE();
+
+ TEST_START("POKE_U32");
+ bzero(d2, sizeof(d2));
+ POKE_U32(d2, 0x11223344);
+ ASSERT_MEM_EQ(d2, x, 4);
+ TEST_DONE();
+
+ TEST_START("POKE_U16");
+ bzero(d2, sizeof(d2));
+ POKE_U16(d2, 0x1122);
+ ASSERT_MEM_EQ(d2, x, 2);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, 5), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 5);
+ cd = sshbuf_ptr(p1);
+ ASSERT_PTR_NE(cd, NULL);
+ ASSERT_U8_EQ(cd[0], 0x11);
+ ASSERT_U8_EQ(cd[1], 0x22);
+ ASSERT_U8_EQ(cd[2], 0x33);
+ ASSERT_U8_EQ(cd[3], 0x44);
+ ASSERT_U8_EQ(cd[4], 0x55);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get");
+ ASSERT_INT_EQ(sshbuf_get(p1, d2, 4), 0);
+ ASSERT_MEM_EQ(d2, x, 4);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1);
+ ASSERT_U8_EQ(*(sshbuf_ptr(p1)), 0x55);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get truncated");
+ r = sshbuf_get(p1, d2, 4);
+ ASSERT_INT_EQ(r, SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1);
+ ASSERT_U8_EQ(*(sshbuf_ptr(p1)), 0x55);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put truncated");
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, 4), 0);
+ r = sshbuf_put(p1, x, 5);
+ ASSERT_INT_EQ(r, SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_u64");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, 10), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 10);
+ ASSERT_INT_EQ(sshbuf_get_u64(p1, &v64), 0);
+ ASSERT_U64_EQ(v64, 0x1122334455667788ULL);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 2);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_u64 truncated");
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 2);
+ r = sshbuf_get_u64(p1, &v64);
+ ASSERT_INT_EQ(r, SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 2);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_u32");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, 10), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 10);
+ ASSERT_INT_EQ(sshbuf_get_u32(p1, &v32), 0);
+ ASSERT_U32_EQ(v32, 0x11223344);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 6);
+ ASSERT_INT_EQ(sshbuf_get_u32(p1, &v32), 0);
+ ASSERT_U32_EQ(v32, 0x55667788);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 2);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_u32 truncated");
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 2);
+ r = sshbuf_get_u32(p1, &v32);
+ ASSERT_INT_EQ(r, SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 2);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_u16");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, 9), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 9);
+ ASSERT_INT_EQ(sshbuf_get_u16(p1, &v16), 0);
+ ASSERT_U16_EQ(v16, 0x1122);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 7);
+ ASSERT_INT_EQ(sshbuf_get_u16(p1, &v16), 0);
+ ASSERT_U16_EQ(v16, 0x3344);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 5);
+ ASSERT_INT_EQ(sshbuf_get_u16(p1, &v16), 0);
+ ASSERT_U16_EQ(v16, 0x5566);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 3);
+ ASSERT_INT_EQ(sshbuf_get_u16(p1, &v16), 0);
+ ASSERT_U16_EQ(v16, 0x7788);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_u16 truncated");
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1);
+ r = sshbuf_get_u16(p1, &v16);
+ ASSERT_INT_EQ(r, SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_u8");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, 2), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 2);
+ ASSERT_INT_EQ(sshbuf_get_u8(p1, &v8), 0);
+ ASSERT_U8_EQ(v8, 0x11);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1);
+ ASSERT_INT_EQ(sshbuf_get_u8(p1, &v8), 0);
+ ASSERT_U8_EQ(v8, 0x22);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_u8 truncated");
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ r = sshbuf_get_u8(p1, &v8);
+ ASSERT_INT_EQ(r, SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_u64");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u64(p1, 0x1122334455667788ULL), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 8);
+ ASSERT_MEM_EQ(sshbuf_ptr(p1), x, 8);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_u64 exact");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, 8), 0);
+ ASSERT_INT_EQ(sshbuf_put_u64(p1, 0x1122334455667788ULL), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 8);
+ ASSERT_MEM_EQ(sshbuf_ptr(p1), x, 8);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_u64 limited");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, 7), 0);
+ r = sshbuf_put_u64(p1, 0x1122334455667788ULL);
+ ASSERT_INT_EQ(r, SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_u32");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, 0x11223344), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 4);
+ ASSERT_MEM_EQ(sshbuf_ptr(p1), x, 4);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_u32 exact");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, 4), 0);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, 0x11223344), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 4);
+ ASSERT_MEM_EQ(sshbuf_ptr(p1), x, 4);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_u32 limited");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, 3), 0);
+ r = sshbuf_put_u32(p1, 0x11223344);
+ ASSERT_INT_EQ(r, SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_u16");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u16(p1, 0x1122), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 2);
+ ASSERT_MEM_EQ(sshbuf_ptr(p1), x, 2);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_u16");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, 2), 0);
+ ASSERT_INT_EQ(sshbuf_put_u16(p1, 0x1122), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 2);
+ ASSERT_MEM_EQ(sshbuf_ptr(p1), x, 2);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_u16 limited");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, 1), 0);
+ r = sshbuf_put_u16(p1, 0x1122);
+ ASSERT_INT_EQ(r, SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_string");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, sizeof(x)), 0);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, sizeof(x)), 0);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, sizeof(x)), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(x) + 4 + 4);
+ ASSERT_INT_EQ(sshbuf_get_string(p1, &d, &s), 0);
+ ASSERT_SIZE_T_EQ(s, sizeof(x));
+ ASSERT_MEM_EQ(d, x, sizeof(x));
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 4);
+ free(d);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_string exact");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, sizeof(x) + 4), 0);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, sizeof(x)), 0);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, sizeof(x)), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(x) + 4);
+ ASSERT_INT_EQ(sshbuf_get_string(p1, &d, &s), 0);
+ ASSERT_SIZE_T_EQ(s, sizeof(x));
+ ASSERT_MEM_EQ(d, x, sizeof(x));
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ free(d);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_string truncated");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, sizeof(x)), 0);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, sizeof(x)), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(x) + 4);
+ ASSERT_INT_EQ(sshbuf_consume_end(p1, 1), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(x) + 3);
+ r = sshbuf_get_string(p1, &d, &s);
+ ASSERT_INT_EQ(r, SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(x) + 3);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_string giant");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, 0xffffffff), 0);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, sizeof(x)), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(x) + 4);
+ r = sshbuf_get_string(p1, &d, &s);
+ ASSERT_INT_EQ(r, SSH_ERR_STRING_TOO_LARGE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(x) + 4);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_cstring giant");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, 0xffffffff), 0);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, sizeof(x)), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(x) + 4);
+ r = sshbuf_get_cstring(p1, &s2, &s);
+ ASSERT_INT_EQ(r, SSH_ERR_STRING_TOO_LARGE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(x) + 4);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_cstring embedded \\0");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, sizeof(x)), 0);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, sizeof(x)), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(x) + 4);
+ r = sshbuf_get_cstring(p1, &s2, NULL);
+ ASSERT_INT_EQ(r, SSH_ERR_INVALID_FORMAT);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_cstring trailing \\0");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, sizeof(x) - 1), 0);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, sizeof(x) - 1), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(x) + 4 - 1);
+ ASSERT_INT_EQ(sshbuf_get_cstring(p1, &s2, &s), 0);
+ ASSERT_SIZE_T_EQ(s, sizeof(x) - 1);
+ ASSERT_MEM_EQ(s2, x, s);
+ free(s2);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_string");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_string(p1, x, sizeof(x)), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(x) + 4);
+ ASSERT_U32_EQ(PEEK_U32(sshbuf_ptr(p1)), sizeof(x));
+ ASSERT_MEM_EQ(sshbuf_ptr(p1) + 4, x, sizeof(x));
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_string limited");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, sizeof(x) + 4 - 1), 0);
+ r = sshbuf_put_string(p1, x, sizeof(x));
+ ASSERT_INT_EQ(r, SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_string giant");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ r = sshbuf_put_string(p1, (void *)0x01, 0xfffffffc);
+ ASSERT_INT_EQ(r, SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_putf");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ r = sshbuf_putf(p1, "%s %d %x", "hello", 23, 0x5f);
+ ASSERT_INT_EQ(r, 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 11);
+ ASSERT_MEM_EQ(sshbuf_ptr(p1), "hello 23 5f", 11);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_putb");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ p2 = sshbuf_new();
+ ASSERT_PTR_NE(p2, NULL);
+ ASSERT_INT_EQ(sshbuf_put(p1, "blahblahblah", 12), 0);
+ ASSERT_INT_EQ(sshbuf_putb(p2, p1), 0);
+ sshbuf_free(p1);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p2), 12);
+ ASSERT_MEM_EQ(sshbuf_ptr(p2), "blahblahblah", 12);
+ sshbuf_free(p2);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_bignum2_bytes empty buf");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_bignum2_bytes(p1, NULL, 0), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(bn_exp1));
+ ASSERT_MEM_EQ(sshbuf_ptr(p1), bn_exp1, sizeof(bn_exp1));
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_bignum2_bytes all zeroes");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_bignum2_bytes(p1, bn1, sizeof(bn1)), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(bn_exp1));
+ ASSERT_MEM_EQ(sshbuf_ptr(p1), bn_exp1, sizeof(bn_exp1));
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_bignum2_bytes simple");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_bignum2_bytes(p1, bn2+2, sizeof(bn2)-2), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(bn_exp2));
+ ASSERT_MEM_EQ(sshbuf_ptr(p1), bn_exp2, sizeof(bn_exp2));
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_bignum2_bytes leading zero");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_bignum2_bytes(p1, bn2, sizeof(bn2)), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(bn_exp2));
+ ASSERT_MEM_EQ(sshbuf_ptr(p1), bn_exp2, sizeof(bn_exp2));
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_bignum2_bytes neg");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_bignum2_bytes(p1, bn3+1, sizeof(bn3)-1), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(bn_exp3));
+ ASSERT_MEM_EQ(sshbuf_ptr(p1), bn_exp3, sizeof(bn_exp3));
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_bignum2_bytes neg and leading zero");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_bignum2_bytes(p1, bn3, sizeof(bn3)), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(bn_exp3));
+ ASSERT_MEM_EQ(sshbuf_ptr(p1), bn_exp3, sizeof(bn_exp3));
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_peek_u64");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, sizeof(x)), 0);
+ ASSERT_INT_EQ(sshbuf_peek_u64(p1, 0, &v64), 0);
+ ASSERT_U64_EQ(v64, 0x1122334455667788ULL);
+ ASSERT_INT_EQ(sshbuf_peek_u64(p1, 2, &v64), 0);
+ ASSERT_U64_EQ(v64, 0x3344556677880099ULL);
+ ASSERT_INT_EQ(sshbuf_peek_u64(p1, 3, &v64), SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_peek_u64(p1, sizeof(x), &v64),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_peek_u64(p1, 1000, &v64),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_peek_u32");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, sizeof(x)), 0);
+ ASSERT_INT_EQ(sshbuf_peek_u32(p1, 0, &v32), 0);
+ ASSERT_U32_EQ(v32, 0x11223344);
+ ASSERT_INT_EQ(sshbuf_peek_u32(p1, 6, &v32), 0);
+ ASSERT_U32_EQ(v32, 0x77880099);
+ ASSERT_INT_EQ(sshbuf_peek_u32(p1, 7, &v32), SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_peek_u32(p1, sizeof(x), &v32),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_peek_u32(p1, 1000, &v32),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_peek_u16");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, sizeof(x)), 0);
+ ASSERT_INT_EQ(sshbuf_peek_u16(p1, 0, &v16), 0);
+ ASSERT_U16_EQ(v16, 0x1122);
+ ASSERT_INT_EQ(sshbuf_peek_u16(p1, 8, &v16), 0);
+ ASSERT_U16_EQ(v16, 0x99);
+ ASSERT_INT_EQ(sshbuf_peek_u16(p1, 9, &v16), SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_peek_u16(p1, sizeof(x), &v16),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_peek_u16(p1, 1000, &v16),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_peek_u8");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put(p1, x, sizeof(x)), 0);
+ ASSERT_INT_EQ(sshbuf_peek_u8(p1, 0, &v8), 0);
+ ASSERT_U8_EQ(v8, 0x11);
+ ASSERT_INT_EQ(sshbuf_peek_u8(p1, 9, &v8), 0);
+ ASSERT_U8_EQ(v8, 0x99);
+ ASSERT_INT_EQ(sshbuf_peek_u8(p1, sizeof(x), &v8),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_peek_u8(p1, 1000, &v8),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_poke_u64");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke at start of buffer */
+ ASSERT_INT_EQ(sshbuf_poke_u64(p1, 0, 0xa1b2c3d4e5f60718ULL), 0);
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "a1b2c3d4e5f607180000");
+ free(s2);
+ sshbuf_reset(p1);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke aligned with end of buffer */
+ ASSERT_INT_EQ(sshbuf_poke_u64(p1, 2, 0xa1b2c3d4e5f60718ULL), 0);
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "0000a1b2c3d4e5f60718");
+ free(s2);
+ sshbuf_reset(p1);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke past end of buffer */
+ ASSERT_INT_EQ(sshbuf_poke_u64(p1, 3, 0xa1b2c3d4e5f60718ULL),
+ SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_INT_EQ(sshbuf_poke_u64(p1, 10, 0xa1b2c3d4e5f60718ULL),
+ SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_INT_EQ(sshbuf_poke_u64(p1, 1000, 0xa1b2c3d4e5f60718ULL),
+ SSH_ERR_NO_BUFFER_SPACE);
+ /* ensure failed pokes do not modify buffer */
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "00000000000000000000");
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_poke_u32");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke at start of buffer */
+ ASSERT_INT_EQ(sshbuf_poke_u32(p1, 0, 0xa1b2c3d4), 0);
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "a1b2c3d4000000000000");
+ free(s2);
+ sshbuf_reset(p1);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke aligned with end of buffer */
+ ASSERT_INT_EQ(sshbuf_poke_u32(p1, 6, 0xa1b2c3d4), 0);
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "000000000000a1b2c3d4");
+ free(s2);
+ sshbuf_reset(p1);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke past end of buffer */
+ ASSERT_INT_EQ(sshbuf_poke_u32(p1, 7, 0xa1b2c3d4),
+ SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_INT_EQ(sshbuf_poke_u32(p1, 10, 0xa1b2c3d4),
+ SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_INT_EQ(sshbuf_poke_u32(p1, 1000, 0xa1b2c3d4),
+ SSH_ERR_NO_BUFFER_SPACE);
+ /* ensure failed pokes do not modify buffer */
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "00000000000000000000");
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_poke_u16");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke at start of buffer */
+ ASSERT_INT_EQ(sshbuf_poke_u16(p1, 0, 0xa1b2), 0);
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "a1b20000000000000000");
+ free(s2);
+ sshbuf_reset(p1);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke aligned with end of buffer */
+ ASSERT_INT_EQ(sshbuf_poke_u16(p1, 8, 0xa1b2), 0);
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "0000000000000000a1b2");
+ free(s2);
+ sshbuf_reset(p1);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke past end of buffer */
+ ASSERT_INT_EQ(sshbuf_poke_u16(p1, 9, 0xa1b2),
+ SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_INT_EQ(sshbuf_poke_u16(p1, 10, 0xa1b2),
+ SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_INT_EQ(sshbuf_poke_u16(p1, 1000, 0xa1b2),
+ SSH_ERR_NO_BUFFER_SPACE);
+ /* ensure failed pokes do not modify buffer */
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "00000000000000000000");
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_poke_u8");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke at start of buffer */
+ ASSERT_INT_EQ(sshbuf_poke_u8(p1, 0, 0xa1), 0);
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "a1000000000000000000");
+ free(s2);
+ sshbuf_reset(p1);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke aligned with end of buffer */
+ ASSERT_INT_EQ(sshbuf_poke_u8(p1, 9, 0xa1), 0);
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "000000000000000000a1");
+ free(s2);
+ sshbuf_reset(p1);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke past end of buffer */
+ ASSERT_INT_EQ(sshbuf_poke_u8(p1, 10, 0xa1), SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_INT_EQ(sshbuf_poke_u8(p1, 1000, 0xa1), SSH_ERR_NO_BUFFER_SPACE);
+ /* ensure failed pokes do not modify buffer */
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "00000000000000000000");
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_poke");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke at start of buffer */
+ ASSERT_INT_EQ(sshbuf_poke(p1, 0, "hello!", 6), 0);
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "68656c6c6f2100000000");
+ free(s2);
+ sshbuf_reset(p1);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke aligned with end of buffer */
+ ASSERT_INT_EQ(sshbuf_poke(p1, 4, "hello!", 6), 0);
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "0000000068656c6c6f21");
+ free(s2);
+ sshbuf_reset(p1);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 10, NULL), 0);
+ /* poke past end of buffer */
+ ASSERT_INT_EQ(sshbuf_poke(p1, 7, "hello!", 6),
+ SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_INT_EQ(sshbuf_poke(p1, 10, "hello!", 6),
+ SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_INT_EQ(sshbuf_poke(p1, 1000, "hello!", 6),
+ SSH_ERR_NO_BUFFER_SPACE);
+ /* ensure failed pokes do not modify buffer */
+ s2 = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(s2, NULL);
+ ASSERT_STRING_EQ(s2, "00000000000000000000");
+ sshbuf_free(p1);
+ TEST_DONE();
+}
diff --git a/regress/unittests/sshbuf/test_sshbuf_getput_crypto.c b/regress/unittests/sshbuf/test_sshbuf_getput_crypto.c
new file mode 100644
index 0000000..e3620e9
--- /dev/null
+++ b/regress/unittests/sshbuf/test_sshbuf_getput_crypto.c
@@ -0,0 +1,280 @@
+/* $OpenBSD: test_sshbuf_getput_crypto.c,v 1.3 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for sshbuf.h buffer API
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#ifdef WITH_OPENSSL
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/bn.h>
+#include <openssl/objects.h>
+#ifdef OPENSSL_HAS_NISTP256
+# include <openssl/ec.h>
+#endif
+
+#include "../test_helper/test_helper.h"
+#include "ssherr.h"
+#include "sshbuf.h"
+
+void sshbuf_getput_crypto_tests(void);
+
+void
+sshbuf_getput_crypto_tests(void)
+{
+ struct sshbuf *p1;
+ BIGNUM *bn, *bn2;
+ const char *hexbn1 = "0102030405060708090a0b0c0d0e0f10";
+ /* This one has MSB set to test bignum2 encoding negative-avoidance */
+ const char *hexbn2 = "f0e0d0c0b0a0908070605040302010007fff11";
+ u_char expbn1[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+ };
+ u_char expbn2[] = {
+ 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80,
+ 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00,
+ 0x7f, 0xff, 0x11
+ };
+#if defined(OPENSSL_HAS_ECC) && defined(OPENSSL_HAS_NISTP256)
+ const u_char *d;
+ size_t s;
+ BIGNUM *bn_x, *bn_y;
+ int ec256_nid = NID_X9_62_prime256v1;
+ char *ec256_x = "0C828004839D0106AA59575216191357"
+ "34B451459DADB586677EF9DF55784999";
+ char *ec256_y = "4D196B50F0B4E94B3C73E3A9D4CD9DF2"
+ "C8F9A35E42BDD047550F69D80EC23CD4";
+ u_char expec256[] = {
+ 0x04,
+ 0x0c, 0x82, 0x80, 0x04, 0x83, 0x9d, 0x01, 0x06,
+ 0xaa, 0x59, 0x57, 0x52, 0x16, 0x19, 0x13, 0x57,
+ 0x34, 0xb4, 0x51, 0x45, 0x9d, 0xad, 0xb5, 0x86,
+ 0x67, 0x7e, 0xf9, 0xdf, 0x55, 0x78, 0x49, 0x99,
+ 0x4d, 0x19, 0x6b, 0x50, 0xf0, 0xb4, 0xe9, 0x4b,
+ 0x3c, 0x73, 0xe3, 0xa9, 0xd4, 0xcd, 0x9d, 0xf2,
+ 0xc8, 0xf9, 0xa3, 0x5e, 0x42, 0xbd, 0xd0, 0x47,
+ 0x55, 0x0f, 0x69, 0xd8, 0x0e, 0xc2, 0x3c, 0xd4
+ };
+ EC_KEY *eck;
+ EC_POINT *ecp;
+#endif
+ int r;
+
+#define MKBN(b, bnn) \
+ do { \
+ bnn = NULL; \
+ ASSERT_INT_GT(BN_hex2bn(&bnn, b), 0); \
+ } while (0)
+
+ TEST_START("sshbuf_put_bignum2");
+ MKBN(hexbn1, bn);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_bignum2(p1, bn), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(expbn1) + 4);
+ ASSERT_U32_EQ(PEEK_U32(sshbuf_ptr(p1)), (u_int32_t)BN_num_bytes(bn));
+ ASSERT_MEM_EQ(sshbuf_ptr(p1) + 4, expbn1, sizeof(expbn1));
+ BN_free(bn);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_bignum2 limited");
+ MKBN(hexbn1, bn);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, sizeof(expbn1) + 3), 0);
+ r = sshbuf_put_bignum2(p1, bn);
+ ASSERT_INT_EQ(r, SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ BN_free(bn);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_bignum2 bn2");
+ MKBN(hexbn2, bn);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_bignum2(p1, bn), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(expbn2) + 4 + 1); /* MSB */
+ ASSERT_U32_EQ(PEEK_U32(sshbuf_ptr(p1)), (u_int32_t)BN_num_bytes(bn) + 1);
+ ASSERT_U8_EQ(*(sshbuf_ptr(p1) + 4), 0x00);
+ ASSERT_MEM_EQ(sshbuf_ptr(p1) + 5, expbn2, sizeof(expbn2));
+ BN_free(bn);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_put_bignum2 bn2 limited");
+ MKBN(hexbn2, bn);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_set_max_size(p1, sizeof(expbn2) + 3), 0);
+ r = sshbuf_put_bignum2(p1, bn);
+ ASSERT_INT_EQ(r, SSH_ERR_NO_BUFFER_SPACE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 0);
+ BN_free(bn);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_bignum2");
+ MKBN(hexbn1, bn);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, BN_num_bytes(bn)), 0);
+ ASSERT_INT_EQ(sshbuf_put(p1, expbn1, sizeof(expbn1)), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 4 + sizeof(expbn1));
+ ASSERT_INT_EQ(sshbuf_put_u16(p1, 0xd00f), 0);
+ bn2 = NULL;
+ ASSERT_INT_EQ(sshbuf_get_bignum2(p1, &bn2), 0);
+ ASSERT_BIGNUM_EQ(bn, bn2);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 2);
+ BN_free(bn);
+ BN_free(bn2);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_bignum2 truncated");
+ MKBN(hexbn1, bn);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, BN_num_bytes(bn)), 0);
+ ASSERT_INT_EQ(sshbuf_put(p1, expbn1, sizeof(expbn1) - 1), 0);
+ bn2 = NULL;
+ r = sshbuf_get_bignum2(p1, &bn2);
+ ASSERT_INT_EQ(r, SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(expbn1) + 3);
+ BN_free(bn);
+ BN_free(bn2);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_bignum2 giant");
+ MKBN(hexbn1, bn);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, 65536), 0);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 65536, NULL), 0);
+ bn2 = NULL;
+ r = sshbuf_get_bignum2(p1, &bn2);
+ ASSERT_INT_EQ(r, SSH_ERR_BIGNUM_TOO_LARGE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 65536 + 4);
+ BN_free(bn);
+ BN_free(bn2);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_bignum2 bn2");
+ MKBN(hexbn2, bn);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, BN_num_bytes(bn) + 1), 0); /* MSB */
+ ASSERT_INT_EQ(sshbuf_put_u8(p1, 0x00), 0);
+ ASSERT_INT_EQ(sshbuf_put(p1, expbn2, sizeof(expbn2)), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 4 + 1 + sizeof(expbn2));
+ ASSERT_INT_EQ(sshbuf_put_u16(p1, 0xd00f), 0);
+ bn2 = NULL;
+ ASSERT_INT_EQ(sshbuf_get_bignum2(p1, &bn2), 0);
+ ASSERT_BIGNUM_EQ(bn, bn2);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 2);
+ BN_free(bn);
+ BN_free(bn2);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_bignum2 bn2 truncated");
+ MKBN(hexbn2, bn);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, BN_num_bytes(bn) + 1), 0);
+ ASSERT_INT_EQ(sshbuf_put_u8(p1, 0x00), 0);
+ ASSERT_INT_EQ(sshbuf_put(p1, expbn2, sizeof(expbn2) - 1), 0);
+ bn2 = NULL;
+ r = sshbuf_get_bignum2(p1, &bn2);
+ ASSERT_INT_EQ(r, SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(expbn2) + 1 + 4 - 1);
+ BN_free(bn);
+ BN_free(bn2);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_bignum2 bn2 negative");
+ MKBN(hexbn2, bn);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, BN_num_bytes(bn)), 0);
+ ASSERT_INT_EQ(sshbuf_put(p1, expbn2, sizeof(expbn2)), 0);
+ bn2 = NULL;
+ r = sshbuf_get_bignum2(p1, &bn2);
+ ASSERT_INT_EQ(r, SSH_ERR_BIGNUM_IS_NEGATIVE);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(expbn2) + 4);
+ BN_free(bn);
+ BN_free(bn2);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+#if defined(OPENSSL_HAS_ECC) && defined(OPENSSL_HAS_NISTP256)
+ TEST_START("sshbuf_put_ec");
+ eck = EC_KEY_new_by_curve_name(ec256_nid);
+ ASSERT_PTR_NE(eck, NULL);
+ ecp = EC_POINT_new(EC_KEY_get0_group(eck));
+ ASSERT_PTR_NE(ecp, NULL);
+ MKBN(ec256_x, bn_x);
+ MKBN(ec256_y, bn_y);
+ ASSERT_INT_EQ(EC_POINT_set_affine_coordinates_GFp(
+ EC_KEY_get0_group(eck), ecp, bn_x, bn_y, NULL), 1);
+ ASSERT_INT_EQ(EC_KEY_set_public_key(eck, ecp), 1);
+ BN_free(bn_x);
+ BN_free(bn_y);
+ EC_POINT_free(ecp);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_eckey(p1, eck), 0);
+ ASSERT_INT_EQ(sshbuf_get_string_direct(p1, &d, &s), 0);
+ ASSERT_SIZE_T_EQ(s, sizeof(expec256));
+ ASSERT_MEM_EQ(d, expec256, sizeof(expec256));
+ sshbuf_free(p1);
+ EC_KEY_free(eck);
+ TEST_DONE();
+
+ TEST_START("sshbuf_get_ec");
+ eck = EC_KEY_new_by_curve_name(ec256_nid);
+ ASSERT_PTR_NE(eck, NULL);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_string(p1, expec256, sizeof(expec256)), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), sizeof(expec256) + 4);
+ ASSERT_INT_EQ(sshbuf_put_u8(p1, 0x00), 0);
+ ASSERT_INT_EQ(sshbuf_get_eckey(p1, eck), 0);
+ bn_x = BN_new();
+ bn_y = BN_new();
+ ASSERT_PTR_NE(bn_x, NULL);
+ ASSERT_PTR_NE(bn_y, NULL);
+ ASSERT_INT_EQ(EC_POINT_get_affine_coordinates_GFp(
+ EC_KEY_get0_group(eck), EC_KEY_get0_public_key(eck),
+ bn_x, bn_y, NULL), 1);
+ MKBN(ec256_x, bn);
+ MKBN(ec256_y, bn2);
+ ASSERT_INT_EQ(BN_cmp(bn_x, bn), 0);
+ ASSERT_INT_EQ(BN_cmp(bn_y, bn2), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1);
+ sshbuf_free(p1);
+ EC_KEY_free(eck);
+ BN_free(bn_x);
+ BN_free(bn_y);
+ BN_free(bn);
+ BN_free(bn2);
+ TEST_DONE();
+#endif
+}
+
+#endif /* WITH_OPENSSL */
diff --git a/regress/unittests/sshbuf/test_sshbuf_getput_fuzz.c b/regress/unittests/sshbuf/test_sshbuf_getput_fuzz.c
new file mode 100644
index 0000000..3b48958
--- /dev/null
+++ b/regress/unittests/sshbuf/test_sshbuf_getput_fuzz.c
@@ -0,0 +1,132 @@
+/* $OpenBSD: test_sshbuf_getput_fuzz.c,v 1.5 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for sshbuf.h buffer API
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef WITH_OPENSSL
+#include <openssl/bn.h>
+#include <openssl/objects.h>
+#ifdef OPENSSL_HAS_NISTP256
+# include <openssl/ec.h>
+#endif
+#endif
+
+#include "../test_helper/test_helper.h"
+#include "ssherr.h"
+#include "sshbuf.h"
+
+void sshbuf_getput_fuzz_tests(void);
+
+static void
+attempt_parse_blob(u_char *blob, size_t len)
+{
+ struct sshbuf *p1;
+#ifdef WITH_OPENSSL
+ BIGNUM *bn;
+#if defined(OPENSSL_HAS_ECC) && defined(OPENSSL_HAS_NISTP256)
+ EC_KEY *eck;
+#endif /* defined(OPENSSL_HAS_ECC) && defined(OPENSSL_HAS_NISTP256) */
+#endif /* WITH_OPENSSL */
+ u_char *s;
+ size_t l;
+ u_int8_t u8;
+ u_int16_t u16;
+ u_int32_t u32;
+ u_int64_t u64;
+
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put(p1, blob, len), 0);
+ sshbuf_get_u8(p1, &u8);
+ sshbuf_get_u16(p1, &u16);
+ sshbuf_get_u32(p1, &u32);
+ sshbuf_get_u64(p1, &u64);
+ if (sshbuf_get_string(p1, &s, &l) == 0) {
+ bzero(s, l);
+ free(s);
+ }
+#ifdef WITH_OPENSSL
+ bn = NULL;
+ sshbuf_get_bignum2(p1, &bn);
+ BN_clear_free(bn);
+#if defined(OPENSSL_HAS_ECC) && defined(OPENSSL_HAS_NISTP256)
+ eck = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+ ASSERT_PTR_NE(eck, NULL);
+ sshbuf_get_eckey(p1, eck);
+ EC_KEY_free(eck);
+#endif /* defined(OPENSSL_HAS_ECC) && defined(OPENSSL_HAS_NISTP256) */
+#endif /* WITH_OPENSSL */
+ sshbuf_free(p1);
+}
+
+
+static void
+onerror(void *fuzz)
+{
+ fprintf(stderr, "Failed during fuzz:\n");
+ fuzz_dump((struct fuzz *)fuzz);
+}
+
+void
+sshbuf_getput_fuzz_tests(void)
+{
+ u_char blob[] = {
+ /* u8 */
+ 0xd0,
+ /* u16 */
+ 0xc0, 0xde,
+ /* u32 */
+ 0xfa, 0xce, 0xde, 0xad,
+ /* u64 */
+ 0xfe, 0xed, 0xac, 0x1d, 0x1f, 0x1c, 0xbe, 0xef,
+ /* string */
+ 0x00, 0x00, 0x00, 0x09,
+ 'O', ' ', 'G', 'o', 'r', 'g', 'o', 'n', '!',
+ /* bignum2 */
+ 0x00, 0x00, 0x00, 0x14,
+ 0x00,
+ 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80,
+ 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00,
+ 0x7f, 0xff, 0x11,
+ /* EC point (NIST-256 curve) */
+ 0x00, 0x00, 0x00, 0x41,
+ 0x04,
+ 0x0c, 0x82, 0x80, 0x04, 0x83, 0x9d, 0x01, 0x06,
+ 0xaa, 0x59, 0x57, 0x52, 0x16, 0x19, 0x13, 0x57,
+ 0x34, 0xb4, 0x51, 0x45, 0x9d, 0xad, 0xb5, 0x86,
+ 0x67, 0x7e, 0xf9, 0xdf, 0x55, 0x78, 0x49, 0x99,
+ 0x4d, 0x19, 0x6b, 0x50, 0xf0, 0xb4, 0xe9, 0x4b,
+ 0x3c, 0x73, 0xe3, 0xa9, 0xd4, 0xcd, 0x9d, 0xf2,
+ 0xc8, 0xf9, 0xa3, 0x5e, 0x42, 0xbd, 0xd0, 0x47,
+ 0x55, 0x0f, 0x69, 0xd8, 0x0e, 0xc2, 0x3c, 0xd4,
+ };
+ struct fuzz *fuzz;
+ u_int fuzzers = FUZZ_1_BIT_FLIP | FUZZ_2_BIT_FLIP |
+ FUZZ_1_BYTE_FLIP | FUZZ_2_BYTE_FLIP |
+ FUZZ_TRUNCATE_START | FUZZ_TRUNCATE_END;
+
+ if (test_is_fast())
+ fuzzers &= ~(FUZZ_2_BYTE_FLIP|FUZZ_2_BIT_FLIP);
+
+ TEST_START("fuzz blob parsing");
+ fuzz = fuzz_begin(fuzzers, blob, sizeof(blob));
+ TEST_ONERROR(onerror, fuzz);
+ for(; !fuzz_done(fuzz); fuzz_next(fuzz))
+ attempt_parse_blob(blob, sizeof(blob));
+ fuzz_cleanup(fuzz);
+ TEST_DONE();
+ TEST_ONERROR(NULL, NULL);
+}
+
diff --git a/regress/unittests/sshbuf/test_sshbuf_misc.c b/regress/unittests/sshbuf/test_sshbuf_misc.c
new file mode 100644
index 0000000..249ecf2
--- /dev/null
+++ b/regress/unittests/sshbuf/test_sshbuf_misc.c
@@ -0,0 +1,217 @@
+/* $OpenBSD: test_sshbuf_misc.c,v 1.5 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for sshbuf.h buffer API
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "sshbuf.h"
+#include "ssherr.h"
+
+void sshbuf_misc_tests(void);
+
+void
+sshbuf_misc_tests(void)
+{
+ struct sshbuf *p1;
+ char tmp[512], msg[] = "imploring ping silence ping over", *p;
+ FILE *out;
+ size_t sz;
+
+ TEST_START("sshbuf_dump");
+ out = tmpfile();
+ ASSERT_PTR_NE(out, NULL);
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, 0x12345678), 0);
+ sshbuf_dump(p1, out);
+ fflush(out);
+ rewind(out);
+ sz = fread(tmp, 1, sizeof(tmp), out);
+ ASSERT_INT_EQ(ferror(out), 0);
+ ASSERT_INT_NE(feof(out), 0);
+ ASSERT_SIZE_T_GT(sz, 0);
+ tmp[sz] = '\0';
+ ASSERT_PTR_NE(strstr(tmp, "12 34 56 78"), NULL);
+ fclose(out);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_dtob16");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u32(p1, 0x12345678), 0);
+ p = sshbuf_dtob16(p1);
+ ASSERT_PTR_NE(p, NULL);
+ ASSERT_STRING_EQ(p, "12345678");
+ free(p);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_dtob64_string len 1");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u8(p1, 0x11), 0);
+ p = sshbuf_dtob64_string(p1, 0);
+ ASSERT_PTR_NE(p, NULL);
+ ASSERT_STRING_EQ(p, "EQ==");
+ free(p);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_dtob64_string len 2");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u8(p1, 0x11), 0);
+ ASSERT_INT_EQ(sshbuf_put_u8(p1, 0x22), 0);
+ p = sshbuf_dtob64_string(p1, 0);
+ ASSERT_PTR_NE(p, NULL);
+ ASSERT_STRING_EQ(p, "ESI=");
+ free(p);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_dtob64_string len 3");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_put_u8(p1, 0x11), 0);
+ ASSERT_INT_EQ(sshbuf_put_u8(p1, 0x22), 0);
+ ASSERT_INT_EQ(sshbuf_put_u8(p1, 0x33), 0);
+ p = sshbuf_dtob64_string(p1, 0);
+ ASSERT_PTR_NE(p, NULL);
+ ASSERT_STRING_EQ(p, "ESIz");
+ free(p);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_dtob64_string len 8191");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_reserve(p1, 8192, NULL), 0);
+ bzero(sshbuf_mutable_ptr(p1), 8192);
+ p = sshbuf_dtob64_string(p1, 0);
+ ASSERT_PTR_NE(p, NULL);
+ ASSERT_SIZE_T_EQ(strlen(p), ((8191 + 2) / 3) * 4);
+ free(p);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_b64tod len 1");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_b64tod(p1, "0A=="), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 1);
+ ASSERT_U8_EQ(*sshbuf_ptr(p1), 0xd0);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_b64tod len 2");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_b64tod(p1, "0A8="), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 2);
+ ASSERT_U16_EQ(PEEK_U16(sshbuf_ptr(p1)), 0xd00f);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_b64tod len 4");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_b64tod(p1, "0A/QDw=="), 0);
+ ASSERT_SIZE_T_EQ(sshbuf_len(p1), 4);
+ ASSERT_U32_EQ(PEEK_U32(sshbuf_ptr(p1)), 0xd00fd00f);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_dup_string");
+ p1 = sshbuf_new();
+ ASSERT_PTR_NE(p1, NULL);
+ /* Check empty buffer */
+ p = sshbuf_dup_string(p1);
+ ASSERT_PTR_NE(p, NULL);
+ ASSERT_SIZE_T_EQ(strlen(p), 0);
+ free(p);
+ /* Check buffer with string */
+ ASSERT_INT_EQ(sshbuf_put(p1, "quad1", strlen("quad1")), 0);
+ p = sshbuf_dup_string(p1);
+ ASSERT_PTR_NE(p, NULL);
+ ASSERT_SIZE_T_EQ(strlen(p), strlen("quad1"));
+ ASSERT_STRING_EQ(p, "quad1");
+ free(p);
+ /* Check buffer with terminating nul */
+ ASSERT_INT_EQ(sshbuf_put(p1, "\0", 1), 0);
+ p = sshbuf_dup_string(p1);
+ ASSERT_PTR_NE(p, NULL);
+ ASSERT_SIZE_T_EQ(strlen(p), strlen("quad1"));
+ ASSERT_STRING_EQ(p, "quad1");
+ free(p);
+ /* Check buffer with data after nul (expect failure) */
+ ASSERT_INT_EQ(sshbuf_put(p1, "quad2", strlen("quad2")), 0);
+ p = sshbuf_dup_string(p1);
+ ASSERT_PTR_EQ(p, NULL);
+ sshbuf_free(p1);
+ TEST_DONE();
+
+ TEST_START("sshbuf_cmp");
+ p1 = sshbuf_from(msg, sizeof(msg) - 1);
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_cmp(p1, 0, "i", 1), 0);
+ ASSERT_INT_EQ(sshbuf_cmp(p1, 0, "j", 1), SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(sshbuf_cmp(p1, 0, "imploring", 9), 0);
+ ASSERT_INT_EQ(sshbuf_cmp(p1, 0, "implored", 9), SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(sshbuf_cmp(p1, 10, "ping", 4), 0);
+ ASSERT_INT_EQ(sshbuf_cmp(p1, 10, "ring", 4), SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(sshbuf_cmp(p1, 28, "over", 4), 0);
+ ASSERT_INT_EQ(sshbuf_cmp(p1, 28, "rove", 4), SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(sshbuf_cmp(p1, 28, "overt", 5),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_cmp(p1, 32, "ping", 4),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_cmp(p1, 1000, "silence", 7),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_cmp(p1, 0, msg, sizeof(msg) - 1), 0);
+ TEST_DONE();
+
+ TEST_START("sshbuf_find");
+ p1 = sshbuf_from(msg, sizeof(msg) - 1);
+ ASSERT_PTR_NE(p1, NULL);
+ ASSERT_INT_EQ(sshbuf_find(p1, 0, "i", 1, &sz), 0);
+ ASSERT_SIZE_T_EQ(sz, 0);
+ ASSERT_INT_EQ(sshbuf_find(p1, 0, "j", 1, &sz), SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(sshbuf_find(p1, 0, "imploring", 9, &sz), 0);
+ ASSERT_SIZE_T_EQ(sz, 0);
+ ASSERT_INT_EQ(sshbuf_find(p1, 0, "implored", 9, &sz),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(sshbuf_find(p1, 3, "ping", 4, &sz), 0);
+ ASSERT_SIZE_T_EQ(sz, 10);
+ ASSERT_INT_EQ(sshbuf_find(p1, 11, "ping", 4, &sz), 0);
+ ASSERT_SIZE_T_EQ(sz, 23);
+ ASSERT_INT_EQ(sshbuf_find(p1, 20, "over", 4, &sz), 0);
+ ASSERT_SIZE_T_EQ(sz, 28);
+ ASSERT_INT_EQ(sshbuf_find(p1, 28, "over", 4, &sz), 0);
+ ASSERT_SIZE_T_EQ(sz, 28);
+ ASSERT_INT_EQ(sshbuf_find(p1, 28, "rove", 4, &sz),
+ SSH_ERR_INVALID_FORMAT);
+ ASSERT_INT_EQ(sshbuf_find(p1, 28, "overt", 5, &sz),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_find(p1, 32, "ping", 4, &sz),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_find(p1, 1000, "silence", 7, &sz),
+ SSH_ERR_MESSAGE_INCOMPLETE);
+ ASSERT_INT_EQ(sshbuf_find(p1, 0, msg + 1, sizeof(msg) - 2, &sz), 0);
+ ASSERT_SIZE_T_EQ(sz, 1);
+ TEST_DONE();
+}
+
diff --git a/regress/unittests/sshbuf/tests.c b/regress/unittests/sshbuf/tests.c
new file mode 100644
index 0000000..29916a1
--- /dev/null
+++ b/regress/unittests/sshbuf/tests.c
@@ -0,0 +1,30 @@
+/* $OpenBSD: tests.c,v 1.1 2014/04/30 05:32:00 djm Exp $ */
+/*
+ * Regress test for sshbuf.h buffer API
+ *
+ * Placed in the public domain
+ */
+
+#include "../test_helper/test_helper.h"
+
+void sshbuf_tests(void);
+void sshbuf_getput_basic_tests(void);
+void sshbuf_getput_crypto_tests(void);
+void sshbuf_misc_tests(void);
+void sshbuf_fuzz_tests(void);
+void sshbuf_getput_fuzz_tests(void);
+void sshbuf_fixed(void);
+
+void
+tests(void)
+{
+ sshbuf_tests();
+ sshbuf_getput_basic_tests();
+#ifdef WITH_OPENSSL
+ sshbuf_getput_crypto_tests();
+#endif
+ sshbuf_misc_tests();
+ sshbuf_fuzz_tests();
+ sshbuf_getput_fuzz_tests();
+ sshbuf_fixed();
+}
diff --git a/regress/unittests/sshkey/Makefile b/regress/unittests/sshkey/Makefile
new file mode 100644
index 0000000..cd0f44d
--- /dev/null
+++ b/regress/unittests/sshkey/Makefile
@@ -0,0 +1,26 @@
+# $OpenBSD: Makefile,v 1.12 2023/01/15 23:35:10 djm Exp $
+
+PROG=test_sshkey
+SRCS=tests.c test_sshkey.c test_file.c test_fuzz.c common.c
+
+# From usr.bin/ssh
+SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c
+SRCS+=sshbuf-io.c atomicio.c sshkey.c authfile.c cipher.c log.c ssh-rsa.c
+SRCS+=ssh-dss.c ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c
+SRCS+=ssherr.c uidswap.c cleanup.c xmalloc.c match.c krl.c fatal.c
+SRCS+=addr.c addrmatch.c bitmap.c
+SRCS+=ed25519.c hash.c
+SRCS+=cipher-chachapoly.c chacha.c poly1305.c ssh-ecdsa-sk.c ssh-sk.c
+SRCS+=ssh-ed25519-sk.c sk-usbhid.c
+
+SRCS+=digest-openssl.c
+#SRCS+=digest-libc.c
+SRCS+=utf8.c
+
+REGRESS_TARGETS=run-regress-${PROG}
+
+run-regress-${PROG}: ${PROG}
+ env ${TEST_ENV} ./${PROG} ${UNITTEST_ARGS} -d ${.CURDIR}/testdata
+
+.include <bsd.regress.mk>
+
diff --git a/regress/unittests/sshkey/common.c b/regress/unittests/sshkey/common.c
new file mode 100644
index 0000000..51b0d92
--- /dev/null
+++ b/regress/unittests/sshkey/common.c
@@ -0,0 +1,163 @@
+/* $OpenBSD: common.c,v 1.5 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Helpers for key API tests
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef WITH_OPENSSL
+#include <openssl/bn.h>
+#include <openssl/rsa.h>
+#include <openssl/dsa.h>
+#include <openssl/objects.h>
+#ifdef OPENSSL_HAS_NISTP256
+# include <openssl/ec.h>
+#endif /* OPENSSL_HAS_NISTP256 */
+#endif /* WITH_OPENSSL */
+
+#include "openbsd-compat/openssl-compat.h"
+
+#include "../test_helper/test_helper.h"
+
+#include "ssherr.h"
+#include "authfile.h"
+#include "sshkey.h"
+#include "sshbuf.h"
+
+#include "common.h"
+
+struct sshbuf *
+load_file(const char *name)
+{
+ struct sshbuf *ret = NULL;
+
+ ASSERT_INT_EQ(sshbuf_load_file(test_data_file(name), &ret), 0);
+ ASSERT_PTR_NE(ret, NULL);
+ return ret;
+}
+
+struct sshbuf *
+load_text_file(const char *name)
+{
+ struct sshbuf *ret = load_file(name);
+ const u_char *p;
+
+ /* Trim whitespace at EOL */
+ for (p = sshbuf_ptr(ret); sshbuf_len(ret) > 0;) {
+ if (p[sshbuf_len(ret) - 1] == '\r' ||
+ p[sshbuf_len(ret) - 1] == '\t' ||
+ p[sshbuf_len(ret) - 1] == ' ' ||
+ p[sshbuf_len(ret) - 1] == '\n')
+ ASSERT_INT_EQ(sshbuf_consume_end(ret, 1), 0);
+ else
+ break;
+ }
+ /* \0 terminate */
+ ASSERT_INT_EQ(sshbuf_put_u8(ret, 0), 0);
+ return ret;
+}
+
+#ifdef WITH_OPENSSL
+BIGNUM *
+load_bignum(const char *name)
+{
+ BIGNUM *ret = NULL;
+ struct sshbuf *buf;
+
+ buf = load_text_file(name);
+ ASSERT_INT_NE(BN_hex2bn(&ret, (const char *)sshbuf_ptr(buf)), 0);
+ sshbuf_free(buf);
+ return ret;
+}
+
+const BIGNUM *
+rsa_n(struct sshkey *k)
+{
+ const BIGNUM *n = NULL;
+
+ ASSERT_PTR_NE(k, NULL);
+ ASSERT_PTR_NE(k->rsa, NULL);
+ RSA_get0_key(k->rsa, &n, NULL, NULL);
+ return n;
+}
+
+const BIGNUM *
+rsa_e(struct sshkey *k)
+{
+ const BIGNUM *e = NULL;
+
+ ASSERT_PTR_NE(k, NULL);
+ ASSERT_PTR_NE(k->rsa, NULL);
+ RSA_get0_key(k->rsa, NULL, &e, NULL);
+ return e;
+}
+
+const BIGNUM *
+rsa_p(struct sshkey *k)
+{
+ const BIGNUM *p = NULL;
+
+ ASSERT_PTR_NE(k, NULL);
+ ASSERT_PTR_NE(k->rsa, NULL);
+ RSA_get0_factors(k->rsa, &p, NULL);
+ return p;
+}
+
+const BIGNUM *
+rsa_q(struct sshkey *k)
+{
+ const BIGNUM *q = NULL;
+
+ ASSERT_PTR_NE(k, NULL);
+ ASSERT_PTR_NE(k->rsa, NULL);
+ RSA_get0_factors(k->rsa, NULL, &q);
+ return q;
+}
+
+const BIGNUM *
+dsa_g(struct sshkey *k)
+{
+ const BIGNUM *g = NULL;
+
+ ASSERT_PTR_NE(k, NULL);
+ ASSERT_PTR_NE(k->dsa, NULL);
+ DSA_get0_pqg(k->dsa, NULL, NULL, &g);
+ return g;
+}
+
+const BIGNUM *
+dsa_pub_key(struct sshkey *k)
+{
+ const BIGNUM *pub_key = NULL;
+
+ ASSERT_PTR_NE(k, NULL);
+ ASSERT_PTR_NE(k->dsa, NULL);
+ DSA_get0_key(k->dsa, &pub_key, NULL);
+ return pub_key;
+}
+
+const BIGNUM *
+dsa_priv_key(struct sshkey *k)
+{
+ const BIGNUM *priv_key = NULL;
+
+ ASSERT_PTR_NE(k, NULL);
+ ASSERT_PTR_NE(k->dsa, NULL);
+ DSA_get0_key(k->dsa, NULL, &priv_key);
+ return priv_key;
+}
+#endif /* WITH_OPENSSL */
+
diff --git a/regress/unittests/sshkey/common.h b/regress/unittests/sshkey/common.h
new file mode 100644
index 0000000..7a514fd
--- /dev/null
+++ b/regress/unittests/sshkey/common.h
@@ -0,0 +1,25 @@
+/* $OpenBSD: common.h,v 1.2 2018/09/13 09:03:20 djm Exp $ */
+/*
+ * Helpers for key API tests
+ *
+ * Placed in the public domain
+ */
+
+/* Load a binary file into a buffer */
+struct sshbuf *load_file(const char *name);
+
+/* Load a text file into a buffer */
+struct sshbuf *load_text_file(const char *name);
+
+/* Load a bignum from a file */
+BIGNUM *load_bignum(const char *name);
+
+/* Accessors for key components */
+const BIGNUM *rsa_n(struct sshkey *k);
+const BIGNUM *rsa_e(struct sshkey *k);
+const BIGNUM *rsa_p(struct sshkey *k);
+const BIGNUM *rsa_q(struct sshkey *k);
+const BIGNUM *dsa_g(struct sshkey *k);
+const BIGNUM *dsa_pub_key(struct sshkey *k);
+const BIGNUM *dsa_priv_key(struct sshkey *k);
+
diff --git a/regress/unittests/sshkey/mktestdata.sh b/regress/unittests/sshkey/mktestdata.sh
new file mode 100755
index 0000000..fcd78e9
--- /dev/null
+++ b/regress/unittests/sshkey/mktestdata.sh
@@ -0,0 +1,222 @@
+#!/bin/sh
+# $OpenBSD: mktestdata.sh,v 1.11 2020/06/19 03:48:49 djm Exp $
+
+PW=mekmitasdigoat
+
+rsa_params() {
+ _in="$1"
+ _outbase="$2"
+ set -e
+ openssl rsa -noout -text -in $_in | \
+ awk '/^modulus:$/,/^publicExponent:/' | \
+ grep -v '^[a-zA-Z]' | tr -d ' \n:' > ${_outbase}.n
+ openssl rsa -noout -text -in $_in | \
+ awk '/^prime1:$/,/^prime2:/' | \
+ grep -v '^[a-zA-Z]' | tr -d ' \n:' > ${_outbase}.p
+ openssl rsa -noout -text -in $_in | \
+ awk '/^prime2:$/,/^exponent1:/' | \
+ grep -v '^[a-zA-Z]' | tr -d ' \n:' > ${_outbase}.q
+ for x in n p q ; do
+ echo "" >> ${_outbase}.$x
+ echo ============ ${_outbase}.$x
+ cat ${_outbase}.$x
+ echo ============
+ done
+}
+
+dsa_params() {
+ _in="$1"
+ _outbase="$2"
+ set -e
+ openssl dsa -noout -text -in $_in | \
+ awk '/^priv:$/,/^pub:/' | \
+ grep -v '^[a-zA-Z]' | tr -d ' \n:' > ${_outbase}.priv
+ openssl dsa -noout -text -in $_in | \
+ awk '/^pub:/,/^P:/' | #\
+ grep -v '^[a-zA-Z]' | tr -d ' \n:' > ${_outbase}.pub
+ openssl dsa -noout -text -in $_in | \
+ awk '/^G:/,0' | \
+ grep -v '^[a-zA-Z]' | tr -d ' \n:' > ${_outbase}.g
+ for x in priv pub g ; do
+ echo "" >> ${_outbase}.$x
+ echo ============ ${_outbase}.$x
+ cat ${_outbase}.$x
+ echo ============
+ done
+}
+
+ecdsa_params() {
+ _in="$1"
+ _outbase="$2"
+ set -e
+ openssl ec -noout -text -in $_in | \
+ awk '/^priv:$/,/^pub:/' | \
+ grep -v '^[a-zA-Z]' | tr -d ' \n:' > ${_outbase}.priv
+ openssl ec -noout -text -in $_in | \
+ awk '/^pub:/,/^ASN1 OID:/' | #\
+ grep -v '^[a-zA-Z]' | tr -d ' \n:' > ${_outbase}.pub
+ openssl ec -noout -text -in $_in | \
+ grep "ASN1 OID:" | \
+ sed 's/.*: //;s/ *$//' | tr -d '\n' > ${_outbase}.curve
+ for x in priv pub curve ; do
+ echo "" >> ${_outbase}.$x
+ echo ============ ${_outbase}.$x
+ cat ${_outbase}.$x
+ echo ============
+ done
+}
+
+set -ex
+
+cd testdata
+
+if [ -f ../../../misc/sk-dummy/sk-dummy.so ] ; then
+ SK_DUMMY=../../../misc/sk-dummy/sk-dummy.so
+elif [ -f ../../../misc/sk-dummy/obj/sk-dummy.so ] ; then
+ SK_DUMMY=../../../misc/sk-dummy/obj/sk-dummy.so
+else
+ echo "Can't find sk-dummy.so" 1>&2
+ exit 1
+fi
+
+rm -f rsa_1 dsa_1 ecdsa_1 ed25519_1
+rm -f rsa_2 dsa_2 ecdsa_2 ed25519_2
+rm -f rsa_n dsa_n ecdsa_n # new-format keys
+rm -f rsa_1_pw dsa_1_pw ecdsa_1_pw ed25519_1_pw
+rm -f rsa_n_pw dsa_n_pw ecdsa_n_pw
+rm -f pw *.pub *.bn.* *.param.* *.fp *.fp.bb
+
+ssh-keygen -t rsa -b 1024 -C "RSA test key #1" -N "" -f rsa_1 -m PEM
+ssh-keygen -t dsa -b 1024 -C "DSA test key #1" -N "" -f dsa_1 -m PEM
+ssh-keygen -t ecdsa -b 256 -C "ECDSA test key #1" -N "" -f ecdsa_1 -m PEM
+ssh-keygen -t ed25519 -C "ED25519 test key #1" -N "" -f ed25519_1
+ssh-keygen -w "$SK_DUMMY" -t ecdsa-sk -C "ECDSA-SK test key #1" \
+ -N "" -f ecdsa_sk1
+ssh-keygen -w "$SK_DUMMY" -t ed25519-sk -C "ED25519-SK test key #1" \
+ -N "" -f ed25519_sk1
+
+
+ssh-keygen -t rsa -b 2048 -C "RSA test key #2" -N "" -f rsa_2 -m PEM
+ssh-keygen -t dsa -b 1024 -C "DSA test key #2" -N "" -f dsa_2 -m PEM
+ssh-keygen -t ecdsa -b 521 -C "ECDSA test key #2" -N "" -f ecdsa_2 -m PEM
+ssh-keygen -t ed25519 -C "ED25519 test key #2" -N "" -f ed25519_2
+ssh-keygen -w "$SK_DUMMY" -t ecdsa-sk -C "ECDSA-SK test key #2" \
+ -N "" -f ecdsa_sk2
+ssh-keygen -w "$SK_DUMMY" -t ed25519-sk -C "ED25519-SK test key #2" \
+ -N "" -f ed25519_sk2
+
+cp rsa_1 rsa_n
+cp dsa_1 dsa_n
+cp ecdsa_1 ecdsa_n
+
+ssh-keygen -pf rsa_n -N ""
+ssh-keygen -pf dsa_n -N ""
+ssh-keygen -pf ecdsa_n -N ""
+
+cp rsa_1 rsa_1_pw
+cp dsa_1 dsa_1_pw
+cp ecdsa_1 ecdsa_1_pw
+cp ed25519_1 ed25519_1_pw
+cp ecdsa_sk1 ecdsa_sk1_pw
+cp ed25519_sk1 ed25519_sk1_pw
+cp rsa_1 rsa_n_pw
+cp dsa_1 dsa_n_pw
+cp ecdsa_1 ecdsa_n_pw
+
+ssh-keygen -pf rsa_1_pw -m PEM -N "$PW"
+ssh-keygen -pf dsa_1_pw -m PEM -N "$PW"
+ssh-keygen -pf ecdsa_1_pw -m PEM -N "$PW"
+ssh-keygen -pf ed25519_1_pw -N "$PW"
+ssh-keygen -pf ecdsa_sk1_pw -m PEM -N "$PW"
+ssh-keygen -pf ed25519_sk1_pw -N "$PW"
+ssh-keygen -pf rsa_n_pw -N "$PW"
+ssh-keygen -pf dsa_n_pw -N "$PW"
+ssh-keygen -pf ecdsa_n_pw -N "$PW"
+
+rsa_params rsa_1 rsa_1.param
+rsa_params rsa_2 rsa_2.param
+dsa_params dsa_1 dsa_1.param
+dsa_params dsa_1 dsa_1.param
+ecdsa_params ecdsa_1 ecdsa_1.param
+ecdsa_params ecdsa_2 ecdsa_2.param
+# XXX ed25519, *sk params
+
+ssh-keygen -s rsa_2 -I hugo -n user1,user2 \
+ -Oforce-command=/bin/ls -Ono-port-forwarding -Osource-address=10.0.0.0/8 \
+ -V 19990101:20110101 -z 1 rsa_1.pub
+ssh-keygen -s rsa_2 -I hugo -n user1,user2 \
+ -Oforce-command=/bin/ls -Ono-port-forwarding -Osource-address=10.0.0.0/8 \
+ -V 19990101:20110101 -z 2 dsa_1.pub
+ssh-keygen -s rsa_2 -I hugo -n user1,user2 \
+ -Oforce-command=/bin/ls -Ono-port-forwarding -Osource-address=10.0.0.0/8 \
+ -V 19990101:20110101 -z 3 ecdsa_1.pub
+ssh-keygen -s rsa_2 -I hugo -n user1,user2 \
+ -Oforce-command=/bin/ls -Ono-port-forwarding -Osource-address=10.0.0.0/8 \
+ -V 19990101:20110101 -z 4 ed25519_1.pub
+ssh-keygen -s rsa_2 -I hugo -n user1,user2 \
+ -Oforce-command=/bin/ls -Ono-port-forwarding -Osource-address=10.0.0.0/8 \
+ -V 19990101:20110101 -z 4 ecdsa_sk1.pub
+ssh-keygen -s rsa_2 -I hugo -n user1,user2 \
+ -Oforce-command=/bin/ls -Ono-port-forwarding -Osource-address=10.0.0.0/8 \
+ -V 19990101:20110101 -z 4 ed25519_sk1.pub
+
+
+# Make a few RSA variant signature too.
+cp rsa_1 rsa_1_sha1
+cp rsa_1 rsa_1_sha512
+cp rsa_1.pub rsa_1_sha1.pub
+cp rsa_1.pub rsa_1_sha512.pub
+ssh-keygen -s rsa_2 -I hugo -n user1,user2 -t ssh-rsa \
+ -Oforce-command=/bin/ls -Ono-port-forwarding -Osource-address=10.0.0.0/8 \
+ -V 19990101:20110101 -z 1 rsa_1_sha1.pub
+ssh-keygen -s rsa_2 -I hugo -n user1,user2 -t rsa-sha2-512 \
+ -Oforce-command=/bin/ls -Ono-port-forwarding -Osource-address=10.0.0.0/8 \
+ -V 19990101:20110101 -z 1 rsa_1_sha512.pub
+
+ssh-keygen -s ed25519_1 -I julius -n host1,host2 -h \
+ -V 19990101:20110101 -z 5 rsa_1.pub
+ssh-keygen -s ed25519_1 -I julius -n host1,host2 -h \
+ -V 19990101:20110101 -z 6 dsa_1.pub
+ssh-keygen -s ecdsa_1 -I julius -n host1,host2 -h \
+ -V 19990101:20110101 -z 7 ecdsa_1.pub
+ssh-keygen -s ed25519_1 -I julius -n host1,host2 -h \
+ -V 19990101:20110101 -z 8 ed25519_1.pub
+ssh-keygen -s ecdsa_1 -I julius -n host1,host2 -h \
+ -V 19990101:20110101 -z 7 ecdsa_sk1.pub
+ssh-keygen -s ed25519_1 -I julius -n host1,host2 -h \
+ -V 19990101:20110101 -z 8 ed25519_sk1.pub
+
+ssh-keygen -lf rsa_1 | awk '{print $2}' > rsa_1.fp
+ssh-keygen -lf dsa_1 | awk '{print $2}' > dsa_1.fp
+ssh-keygen -lf ecdsa_1 | awk '{print $2}' > ecdsa_1.fp
+ssh-keygen -lf ed25519_1 | awk '{print $2}' > ed25519_1.fp
+ssh-keygen -lf ecdsa_sk1 | awk '{print $2}' > ecdsa_sk1.fp
+ssh-keygen -lf ed25519_sk1 | awk '{print $2}' > ed25519_sk1.fp
+ssh-keygen -lf rsa_2 | awk '{print $2}' > rsa_2.fp
+ssh-keygen -lf dsa_2 | awk '{print $2}' > dsa_2.fp
+ssh-keygen -lf ecdsa_2 | awk '{print $2}' > ecdsa_2.fp
+ssh-keygen -lf ed25519_2 | awk '{print $2}' > ed25519_2.fp
+ssh-keygen -lf ecdsa_sk2 | awk '{print $2}' > ecdsa_sk2.fp
+ssh-keygen -lf ed25519_sk2 | awk '{print $2}' > ed25519_sk2.fp
+
+ssh-keygen -lf rsa_1-cert.pub | awk '{print $2}' > rsa_1-cert.fp
+ssh-keygen -lf dsa_1-cert.pub | awk '{print $2}' > dsa_1-cert.fp
+ssh-keygen -lf ecdsa_1-cert.pub | awk '{print $2}' > ecdsa_1-cert.fp
+ssh-keygen -lf ed25519_1-cert.pub | awk '{print $2}' > ed25519_1-cert.fp
+ssh-keygen -lf ecdsa_sk1-cert.pub | awk '{print $2}' > ecdsa_sk1-cert.fp
+ssh-keygen -lf ed25519_sk1-cert.pub | awk '{print $2}' > ed25519_sk1-cert.fp
+
+ssh-keygen -Bf rsa_1 | awk '{print $2}' > rsa_1.fp.bb
+ssh-keygen -Bf dsa_1 | awk '{print $2}' > dsa_1.fp.bb
+ssh-keygen -Bf ecdsa_1 | awk '{print $2}' > ecdsa_1.fp.bb
+ssh-keygen -Bf ed25519_1 | awk '{print $2}' > ed25519_1.fp.bb
+ssh-keygen -Bf ecdsa_sk1 | awk '{print $2}' > ecdsa_sk1.fp.bb
+ssh-keygen -Bf ed25519_sk1 | awk '{print $2}' > ed25519_sk1.fp.bb
+ssh-keygen -Bf rsa_2 | awk '{print $2}' > rsa_2.fp.bb
+ssh-keygen -Bf dsa_2 | awk '{print $2}' > dsa_2.fp.bb
+ssh-keygen -Bf ecdsa_2 | awk '{print $2}' > ecdsa_2.fp.bb
+ssh-keygen -Bf ed25519_2 | awk '{print $2}' > ed25519_2.fp.bb
+ssh-keygen -Bf ecdsa_sk2 | awk '{print $2}' > ecdsa_sk2.fp.bb
+ssh-keygen -Bf ed25519_sk2 | awk '{print $2}' > ed25519_sk2.fp.bb
+
+echo "$PW" > pw
diff --git a/regress/unittests/sshkey/test_file.c b/regress/unittests/sshkey/test_file.c
new file mode 100644
index 0000000..497ab6d
--- /dev/null
+++ b/regress/unittests/sshkey/test_file.c
@@ -0,0 +1,559 @@
+/* $OpenBSD: test_file.c,v 1.10 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for sshkey.h key management API
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef WITH_OPENSSL
+#include <openssl/bn.h>
+#include <openssl/rsa.h>
+#include <openssl/dsa.h>
+#include <openssl/objects.h>
+#ifdef OPENSSL_HAS_NISTP256
+# include <openssl/ec.h>
+#endif /* OPENSSL_HAS_NISTP256 */
+#endif /* WITH_OPENSSL */
+
+#include "../test_helper/test_helper.h"
+
+#include "ssherr.h"
+#include "authfile.h"
+#include "sshkey.h"
+#include "sshbuf.h"
+#include "digest.h"
+
+#include "common.h"
+
+void sshkey_file_tests(void);
+
+void
+sshkey_file_tests(void)
+{
+ struct sshkey *k1, *k2;
+ struct sshbuf *buf, *pw;
+#ifdef WITH_OPENSSL
+ BIGNUM *a, *b, *c;
+#endif
+ char *cp;
+
+ TEST_START("load passphrase");
+ pw = load_text_file("pw");
+ TEST_DONE();
+
+
+#ifdef WITH_OPENSSL
+ TEST_START("parse RSA from private");
+ buf = load_file("rsa_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k1, NULL);
+ a = load_bignum("rsa_1.param.n");
+ b = load_bignum("rsa_1.param.p");
+ c = load_bignum("rsa_1.param.q");
+ ASSERT_BIGNUM_EQ(rsa_n(k1), a);
+ ASSERT_BIGNUM_EQ(rsa_p(k1), b);
+ ASSERT_BIGNUM_EQ(rsa_q(k1), c);
+ BN_free(a);
+ BN_free(b);
+ BN_free(c);
+ TEST_DONE();
+
+ TEST_START("parse RSA from private w/ passphrase");
+ buf = load_file("rsa_1_pw");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf,
+ (const char *)sshbuf_ptr(pw), &k2, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("parse RSA from new-format");
+ buf = load_file("rsa_n");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k2, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("parse RSA from new-format w/ passphrase");
+ buf = load_file("rsa_n_pw");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf,
+ (const char *)sshbuf_ptr(pw), &k2, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load RSA from public");
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("rsa_1.pub"), &k2,
+ NULL), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load RSA cert with SHA1 signature");
+ ASSERT_INT_EQ(sshkey_load_cert(test_data_file("rsa_1_sha1"), &k2), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(k2->type, KEY_RSA_CERT);
+ ASSERT_INT_EQ(sshkey_equal_public(k1, k2), 1);
+ ASSERT_STRING_EQ(k2->cert->signature_type, "ssh-rsa");
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load RSA cert with SHA512 signature");
+ ASSERT_INT_EQ(sshkey_load_cert(test_data_file("rsa_1_sha512"), &k2), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(k2->type, KEY_RSA_CERT);
+ ASSERT_INT_EQ(sshkey_equal_public(k1, k2), 1);
+ ASSERT_STRING_EQ(k2->cert->signature_type, "rsa-sha2-512");
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load RSA cert");
+ ASSERT_INT_EQ(sshkey_load_cert(test_data_file("rsa_1"), &k2), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(k2->type, KEY_RSA_CERT);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 0);
+ ASSERT_INT_EQ(sshkey_equal_public(k1, k2), 1);
+ TEST_DONE();
+
+ TEST_START("RSA key hex fingerprint");
+ buf = load_text_file("rsa_1.fp");
+ cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA256, SSH_FP_BASE64);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ TEST_DONE();
+
+ TEST_START("RSA cert hex fingerprint");
+ buf = load_text_file("rsa_1-cert.fp");
+ cp = sshkey_fingerprint(k2, SSH_DIGEST_SHA256, SSH_FP_BASE64);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("RSA key bubblebabble fingerprint");
+ buf = load_text_file("rsa_1.fp.bb");
+ cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA1, SSH_FP_BUBBLEBABBLE);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ TEST_DONE();
+
+ sshkey_free(k1);
+
+ TEST_START("parse DSA from private");
+ buf = load_file("dsa_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k1, NULL);
+ a = load_bignum("dsa_1.param.g");
+ b = load_bignum("dsa_1.param.priv");
+ c = load_bignum("dsa_1.param.pub");
+ ASSERT_BIGNUM_EQ(dsa_g(k1), a);
+ ASSERT_BIGNUM_EQ(dsa_priv_key(k1), b);
+ ASSERT_BIGNUM_EQ(dsa_pub_key(k1), c);
+ BN_free(a);
+ BN_free(b);
+ BN_free(c);
+ TEST_DONE();
+
+ TEST_START("parse DSA from private w/ passphrase");
+ buf = load_file("dsa_1_pw");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf,
+ (const char *)sshbuf_ptr(pw), &k2, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("parse DSA from new-format");
+ buf = load_file("dsa_n");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k2, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("parse DSA from new-format w/ passphrase");
+ buf = load_file("dsa_n_pw");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf,
+ (const char *)sshbuf_ptr(pw), &k2, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load DSA from public");
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("dsa_1.pub"), &k2,
+ NULL), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load DSA cert");
+ ASSERT_INT_EQ(sshkey_load_cert(test_data_file("dsa_1"), &k2), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(k2->type, KEY_DSA_CERT);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 0);
+ ASSERT_INT_EQ(sshkey_equal_public(k1, k2), 1);
+ TEST_DONE();
+
+ TEST_START("DSA key hex fingerprint");
+ buf = load_text_file("dsa_1.fp");
+ cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA256, SSH_FP_BASE64);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ TEST_DONE();
+
+ TEST_START("DSA cert hex fingerprint");
+ buf = load_text_file("dsa_1-cert.fp");
+ cp = sshkey_fingerprint(k2, SSH_DIGEST_SHA256, SSH_FP_BASE64);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("DSA key bubblebabble fingerprint");
+ buf = load_text_file("dsa_1.fp.bb");
+ cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA1, SSH_FP_BUBBLEBABBLE);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ TEST_DONE();
+
+ sshkey_free(k1);
+
+#ifdef OPENSSL_HAS_ECC
+ TEST_START("parse ECDSA from private");
+ buf = load_file("ecdsa_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k1, NULL);
+ buf = load_text_file("ecdsa_1.param.curve");
+ ASSERT_STRING_EQ((const char *)sshbuf_ptr(buf),
+ OBJ_nid2sn(k1->ecdsa_nid));
+ sshbuf_free(buf);
+ a = load_bignum("ecdsa_1.param.priv");
+ b = load_bignum("ecdsa_1.param.pub");
+ c = EC_POINT_point2bn(EC_KEY_get0_group(k1->ecdsa),
+ EC_KEY_get0_public_key(k1->ecdsa), POINT_CONVERSION_UNCOMPRESSED,
+ NULL, NULL);
+ ASSERT_PTR_NE(c, NULL);
+ ASSERT_BIGNUM_EQ(EC_KEY_get0_private_key(k1->ecdsa), a);
+ ASSERT_BIGNUM_EQ(b, c);
+ BN_free(a);
+ BN_free(b);
+ BN_free(c);
+ TEST_DONE();
+
+ TEST_START("parse ECDSA from private w/ passphrase");
+ buf = load_file("ecdsa_1_pw");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf,
+ (const char *)sshbuf_ptr(pw), &k2, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("parse ECDSA from new-format");
+ buf = load_file("ecdsa_n");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k2, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("parse ECDSA from new-format w/ passphrase");
+ buf = load_file("ecdsa_n_pw");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf,
+ (const char *)sshbuf_ptr(pw), &k2, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load ECDSA from public");
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("ecdsa_1.pub"), &k2,
+ NULL), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load ECDSA cert");
+ ASSERT_INT_EQ(sshkey_load_cert(test_data_file("ecdsa_1"), &k2), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(k2->type, KEY_ECDSA_CERT);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 0);
+ ASSERT_INT_EQ(sshkey_equal_public(k1, k2), 1);
+ TEST_DONE();
+
+ TEST_START("ECDSA key hex fingerprint");
+ buf = load_text_file("ecdsa_1.fp");
+ cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA256, SSH_FP_BASE64);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ TEST_DONE();
+
+ TEST_START("ECDSA cert hex fingerprint");
+ buf = load_text_file("ecdsa_1-cert.fp");
+ cp = sshkey_fingerprint(k2, SSH_DIGEST_SHA256, SSH_FP_BASE64);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("ECDSA key bubblebabble fingerprint");
+ buf = load_text_file("ecdsa_1.fp.bb");
+ cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA1, SSH_FP_BUBBLEBABBLE);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ TEST_DONE();
+
+ sshkey_free(k1);
+#endif /* OPENSSL_HAS_ECC */
+#endif /* WITH_OPENSSL */
+
+ TEST_START("parse Ed25519 from private");
+ buf = load_file("ed25519_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k1, NULL);
+ ASSERT_INT_EQ(k1->type, KEY_ED25519);
+ /* XXX check key contents */
+ TEST_DONE();
+
+ TEST_START("parse Ed25519 from private w/ passphrase");
+ buf = load_file("ed25519_1_pw");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf,
+ (const char *)sshbuf_ptr(pw), &k2, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load Ed25519 from public");
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("ed25519_1.pub"), &k2,
+ NULL), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load Ed25519 cert");
+ ASSERT_INT_EQ(sshkey_load_cert(test_data_file("ed25519_1"), &k2), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(k2->type, KEY_ED25519_CERT);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 0);
+ ASSERT_INT_EQ(sshkey_equal_public(k1, k2), 1);
+ TEST_DONE();
+
+ TEST_START("Ed25519 key hex fingerprint");
+ buf = load_text_file("ed25519_1.fp");
+ cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA256, SSH_FP_BASE64);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ TEST_DONE();
+
+ TEST_START("Ed25519 cert hex fingerprint");
+ buf = load_text_file("ed25519_1-cert.fp");
+ cp = sshkey_fingerprint(k2, SSH_DIGEST_SHA256, SSH_FP_BASE64);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("Ed25519 key bubblebabble fingerprint");
+ buf = load_text_file("ed25519_1.fp.bb");
+ cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA1, SSH_FP_BUBBLEBABBLE);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ TEST_DONE();
+
+ sshkey_free(k1);
+
+#ifdef ENABLE_SK
+#if defined(WITH_OPENSSL) && defined(OPENSSL_HAS_ECC)
+ TEST_START("parse ECDSA-SK from private");
+ buf = load_file("ecdsa_sk1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k1, NULL);
+ ASSERT_INT_EQ(k1->type, KEY_ECDSA_SK);
+ TEST_DONE();
+
+ TEST_START("parse ECDSA-SK from private w/ passphrase");
+ buf = load_file("ecdsa_sk1_pw");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf,
+ (const char *)sshbuf_ptr(pw), &k2, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load ECDSA-SK from public");
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("ecdsa_sk1.pub"), &k2,
+ NULL), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load ECDSA-SK cert");
+ ASSERT_INT_EQ(sshkey_load_cert(test_data_file("ecdsa_sk1"), &k2), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(k2->type, KEY_ECDSA_SK_CERT);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 0);
+ ASSERT_INT_EQ(sshkey_equal_public(k1, k2), 1);
+ TEST_DONE();
+
+ TEST_START("ECDSA-SK key hex fingerprint");
+ buf = load_text_file("ecdsa_sk1.fp");
+ cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA256, SSH_FP_BASE64);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ TEST_DONE();
+
+ TEST_START("ECDSA-SK cert hex fingerprint");
+ buf = load_text_file("ecdsa_sk1-cert.fp");
+ cp = sshkey_fingerprint(k2, SSH_DIGEST_SHA256, SSH_FP_BASE64);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("ECDSA-SK key bubblebabble fingerprint");
+ buf = load_text_file("ecdsa_sk1.fp.bb");
+ cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA1, SSH_FP_BUBBLEBABBLE);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ TEST_DONE();
+
+ sshkey_free(k1);
+#endif
+
+ TEST_START("parse Ed25519-SK from private");
+ buf = load_file("ed25519_sk1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k1, NULL);
+ ASSERT_INT_EQ(k1->type, KEY_ED25519_SK);
+ /* XXX check key contents */
+ TEST_DONE();
+
+ TEST_START("parse Ed25519-SK from private w/ passphrase");
+ buf = load_file("ed25519_sk1_pw");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf,
+ (const char *)sshbuf_ptr(pw), &k2, NULL), 0);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load Ed25519-SK from public");
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("ed25519_sk1.pub"),
+ &k2, NULL), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("load Ed25519-SK cert");
+ ASSERT_INT_EQ(sshkey_load_cert(test_data_file("ed25519_sk1"), &k2), 0);
+ ASSERT_PTR_NE(k2, NULL);
+ ASSERT_INT_EQ(k2->type, KEY_ED25519_SK_CERT);
+ ASSERT_INT_EQ(sshkey_equal(k1, k2), 0);
+ ASSERT_INT_EQ(sshkey_equal_public(k1, k2), 1);
+ TEST_DONE();
+
+ TEST_START("Ed25519-SK key hex fingerprint");
+ buf = load_text_file("ed25519_sk1.fp");
+ cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA256, SSH_FP_BASE64);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ TEST_DONE();
+
+ TEST_START("Ed25519-SK cert hex fingerprint");
+ buf = load_text_file("ed25519_sk1-cert.fp");
+ cp = sshkey_fingerprint(k2, SSH_DIGEST_SHA256, SSH_FP_BASE64);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("Ed25519-SK key bubblebabble fingerprint");
+ buf = load_text_file("ed25519_sk1.fp.bb");
+ cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA1, SSH_FP_BUBBLEBABBLE);
+ ASSERT_PTR_NE(cp, NULL);
+ ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf));
+ sshbuf_free(buf);
+ free(cp);
+ TEST_DONE();
+
+ sshkey_free(k1);
+#endif /* ENABLE_SK */
+
+ sshbuf_free(pw);
+
+}
diff --git a/regress/unittests/sshkey/test_fuzz.c b/regress/unittests/sshkey/test_fuzz.c
new file mode 100644
index 0000000..2fae19d
--- /dev/null
+++ b/regress/unittests/sshkey/test_fuzz.c
@@ -0,0 +1,391 @@
+/* $OpenBSD: test_fuzz.c,v 1.13 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Fuzz tests for key parsing
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef WITH_OPENSSL
+#include <openssl/bn.h>
+#include <openssl/rsa.h>
+#include <openssl/dsa.h>
+#include <openssl/objects.h>
+#ifdef OPENSSL_HAS_NISTP256
+# include <openssl/ec.h>
+#endif
+#endif
+
+#include "../test_helper/test_helper.h"
+
+#include "ssherr.h"
+#include "authfile.h"
+#include "sshkey.h"
+#include "sshbuf.h"
+
+#include "common.h"
+
+void sshkey_fuzz_tests(void);
+
+static void
+onerror(void *fuzz)
+{
+ fprintf(stderr, "Failed during fuzz:\n");
+ fuzz_dump((struct fuzz *)fuzz);
+}
+
+static void
+public_fuzz(struct sshkey *k)
+{
+ struct sshkey *k1;
+ struct sshbuf *buf;
+ struct fuzz *fuzz;
+ u_int fuzzers = FUZZ_1_BIT_FLIP | FUZZ_1_BYTE_FLIP |
+ FUZZ_TRUNCATE_START | FUZZ_TRUNCATE_END;
+
+ if (test_is_fast())
+ fuzzers &= ~FUZZ_1_BIT_FLIP;
+ if (test_is_slow())
+ fuzzers |= FUZZ_2_BIT_FLIP | FUZZ_2_BYTE_FLIP;
+ ASSERT_PTR_NE(buf = sshbuf_new(), NULL);
+ ASSERT_INT_EQ(sshkey_putb(k, buf), 0);
+ fuzz = fuzz_begin(fuzzers, sshbuf_mutable_ptr(buf), sshbuf_len(buf));
+ ASSERT_INT_EQ(sshkey_from_blob(sshbuf_ptr(buf), sshbuf_len(buf),
+ &k1), 0);
+ sshkey_free(k1);
+ sshbuf_free(buf);
+ TEST_ONERROR(onerror, fuzz);
+ for(; !fuzz_done(fuzz); fuzz_next(fuzz)) {
+ if (sshkey_from_blob(fuzz_ptr(fuzz), fuzz_len(fuzz), &k1) == 0)
+ sshkey_free(k1);
+ }
+ fuzz_cleanup(fuzz);
+}
+
+static void
+sig_fuzz(struct sshkey *k, const char *sig_alg)
+{
+ struct fuzz *fuzz;
+ u_char *sig, c[] = "some junk to be signed";
+ size_t l;
+ u_int fuzzers = FUZZ_1_BIT_FLIP | FUZZ_1_BYTE_FLIP | FUZZ_2_BYTE_FLIP |
+ FUZZ_TRUNCATE_START | FUZZ_TRUNCATE_END;
+
+ if (test_is_fast())
+ fuzzers &= ~FUZZ_2_BYTE_FLIP;
+ if (test_is_slow())
+ fuzzers |= FUZZ_2_BIT_FLIP;
+
+ ASSERT_INT_EQ(sshkey_sign(k, &sig, &l, c, sizeof(c),
+ sig_alg, NULL, NULL, 0), 0);
+ ASSERT_SIZE_T_GT(l, 0);
+ fuzz = fuzz_begin(fuzzers, sig, l);
+ ASSERT_INT_EQ(sshkey_verify(k, sig, l, c, sizeof(c), NULL, 0, NULL), 0);
+ free(sig);
+ TEST_ONERROR(onerror, fuzz);
+ for(; !fuzz_done(fuzz); fuzz_next(fuzz)) {
+ /* Ensure 1-bit difference at least */
+ if (fuzz_matches_original(fuzz))
+ continue;
+ ASSERT_INT_NE(sshkey_verify(k, fuzz_ptr(fuzz), fuzz_len(fuzz),
+ c, sizeof(c), NULL, 0, NULL), 0);
+ }
+ fuzz_cleanup(fuzz);
+}
+
+#define NUM_FAST_BASE64_TESTS 1024
+
+void
+sshkey_fuzz_tests(void)
+{
+ struct sshkey *k1;
+ struct sshbuf *buf, *fuzzed;
+ struct fuzz *fuzz;
+ int r, i;
+
+#ifdef WITH_OPENSSL
+ TEST_START("fuzz RSA private");
+ buf = load_file("rsa_1");
+ fuzz = fuzz_begin(FUZZ_BASE64, sshbuf_mutable_ptr(buf),
+ sshbuf_len(buf));
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshkey_free(k1);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(fuzzed = sshbuf_new(), NULL);
+ TEST_ONERROR(onerror, fuzz);
+ for(i = 0; !fuzz_done(fuzz); i++, fuzz_next(fuzz)) {
+ r = sshbuf_put(fuzzed, fuzz_ptr(fuzz), fuzz_len(fuzz));
+ ASSERT_INT_EQ(r, 0);
+ if (sshkey_parse_private_fileblob(fuzzed, "", &k1, NULL) == 0)
+ sshkey_free(k1);
+ sshbuf_reset(fuzzed);
+ if (test_is_fast() && i >= NUM_FAST_BASE64_TESTS)
+ break;
+ }
+ sshbuf_free(fuzzed);
+ fuzz_cleanup(fuzz);
+ TEST_DONE();
+
+ TEST_START("fuzz RSA new-format private");
+ buf = load_file("rsa_n");
+ fuzz = fuzz_begin(FUZZ_BASE64, sshbuf_mutable_ptr(buf),
+ sshbuf_len(buf));
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshkey_free(k1);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(fuzzed = sshbuf_new(), NULL);
+ TEST_ONERROR(onerror, fuzz);
+ for(i = 0; !fuzz_done(fuzz); i++, fuzz_next(fuzz)) {
+ r = sshbuf_put(fuzzed, fuzz_ptr(fuzz), fuzz_len(fuzz));
+ ASSERT_INT_EQ(r, 0);
+ if (sshkey_parse_private_fileblob(fuzzed, "", &k1, NULL) == 0)
+ sshkey_free(k1);
+ sshbuf_reset(fuzzed);
+ if (test_is_fast() && i >= NUM_FAST_BASE64_TESTS)
+ break;
+ }
+ sshbuf_free(fuzzed);
+ fuzz_cleanup(fuzz);
+ TEST_DONE();
+
+ TEST_START("fuzz DSA private");
+ buf = load_file("dsa_1");
+ fuzz = fuzz_begin(FUZZ_BASE64, sshbuf_mutable_ptr(buf),
+ sshbuf_len(buf));
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshkey_free(k1);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(fuzzed = sshbuf_new(), NULL);
+ TEST_ONERROR(onerror, fuzz);
+ for(i = 0; !fuzz_done(fuzz); i++, fuzz_next(fuzz)) {
+ r = sshbuf_put(fuzzed, fuzz_ptr(fuzz), fuzz_len(fuzz));
+ ASSERT_INT_EQ(r, 0);
+ if (sshkey_parse_private_fileblob(fuzzed, "", &k1, NULL) == 0)
+ sshkey_free(k1);
+ sshbuf_reset(fuzzed);
+ if (test_is_fast() && i >= NUM_FAST_BASE64_TESTS)
+ break;
+ }
+ sshbuf_free(fuzzed);
+ fuzz_cleanup(fuzz);
+ TEST_DONE();
+
+ TEST_START("fuzz DSA new-format private");
+ buf = load_file("dsa_n");
+ fuzz = fuzz_begin(FUZZ_BASE64, sshbuf_mutable_ptr(buf),
+ sshbuf_len(buf));
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshkey_free(k1);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(fuzzed = sshbuf_new(), NULL);
+ TEST_ONERROR(onerror, fuzz);
+ for(i = 0; !fuzz_done(fuzz); i++, fuzz_next(fuzz)) {
+ r = sshbuf_put(fuzzed, fuzz_ptr(fuzz), fuzz_len(fuzz));
+ ASSERT_INT_EQ(r, 0);
+ if (sshkey_parse_private_fileblob(fuzzed, "", &k1, NULL) == 0)
+ sshkey_free(k1);
+ sshbuf_reset(fuzzed);
+ if (test_is_fast() && i >= NUM_FAST_BASE64_TESTS)
+ break;
+ }
+ sshbuf_free(fuzzed);
+ fuzz_cleanup(fuzz);
+ TEST_DONE();
+
+#ifdef OPENSSL_HAS_ECC
+ TEST_START("fuzz ECDSA private");
+ buf = load_file("ecdsa_1");
+ fuzz = fuzz_begin(FUZZ_BASE64, sshbuf_mutable_ptr(buf),
+ sshbuf_len(buf));
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshkey_free(k1);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(fuzzed = sshbuf_new(), NULL);
+ TEST_ONERROR(onerror, fuzz);
+ for(i = 0; !fuzz_done(fuzz); i++, fuzz_next(fuzz)) {
+ r = sshbuf_put(fuzzed, fuzz_ptr(fuzz), fuzz_len(fuzz));
+ ASSERT_INT_EQ(r, 0);
+ if (sshkey_parse_private_fileblob(fuzzed, "", &k1, NULL) == 0)
+ sshkey_free(k1);
+ sshbuf_reset(fuzzed);
+ if (test_is_fast() && i >= NUM_FAST_BASE64_TESTS)
+ break;
+ }
+ sshbuf_free(fuzzed);
+ fuzz_cleanup(fuzz);
+ TEST_DONE();
+
+ TEST_START("fuzz ECDSA new-format private");
+ buf = load_file("ecdsa_n");
+ fuzz = fuzz_begin(FUZZ_BASE64, sshbuf_mutable_ptr(buf),
+ sshbuf_len(buf));
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshkey_free(k1);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(fuzzed = sshbuf_new(), NULL);
+ TEST_ONERROR(onerror, fuzz);
+ for(i = 0; !fuzz_done(fuzz); i++, fuzz_next(fuzz)) {
+ r = sshbuf_put(fuzzed, fuzz_ptr(fuzz), fuzz_len(fuzz));
+ ASSERT_INT_EQ(r, 0);
+ if (sshkey_parse_private_fileblob(fuzzed, "", &k1, NULL) == 0)
+ sshkey_free(k1);
+ sshbuf_reset(fuzzed);
+ if (test_is_fast() && i >= NUM_FAST_BASE64_TESTS)
+ break;
+ }
+ sshbuf_free(fuzzed);
+ fuzz_cleanup(fuzz);
+ TEST_DONE();
+#endif /* OPENSSL_HAS_ECC */
+#endif /* WITH_OPENSSL */
+
+ TEST_START("fuzz Ed25519 private");
+ buf = load_file("ed25519_1");
+ fuzz = fuzz_begin(FUZZ_BASE64, sshbuf_mutable_ptr(buf),
+ sshbuf_len(buf));
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshkey_free(k1);
+ sshbuf_free(buf);
+ ASSERT_PTR_NE(fuzzed = sshbuf_new(), NULL);
+ TEST_ONERROR(onerror, fuzz);
+ for(i = 0; !fuzz_done(fuzz); i++, fuzz_next(fuzz)) {
+ r = sshbuf_put(fuzzed, fuzz_ptr(fuzz), fuzz_len(fuzz));
+ ASSERT_INT_EQ(r, 0);
+ if (sshkey_parse_private_fileblob(fuzzed, "", &k1, NULL) == 0)
+ sshkey_free(k1);
+ sshbuf_reset(fuzzed);
+ if (test_is_fast() && i >= NUM_FAST_BASE64_TESTS)
+ break;
+ }
+ sshbuf_free(fuzzed);
+ fuzz_cleanup(fuzz);
+ TEST_DONE();
+
+#ifdef WITH_OPENSSL
+ TEST_START("fuzz RSA public");
+ buf = load_file("rsa_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ public_fuzz(k1);
+ sshkey_free(k1);
+ TEST_DONE();
+
+ TEST_START("fuzz RSA cert");
+ ASSERT_INT_EQ(sshkey_load_cert(test_data_file("rsa_1"), &k1), 0);
+ public_fuzz(k1);
+ sshkey_free(k1);
+ TEST_DONE();
+
+ TEST_START("fuzz DSA public");
+ buf = load_file("dsa_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ public_fuzz(k1);
+ sshkey_free(k1);
+ TEST_DONE();
+
+ TEST_START("fuzz DSA cert");
+ ASSERT_INT_EQ(sshkey_load_cert(test_data_file("dsa_1"), &k1), 0);
+ public_fuzz(k1);
+ sshkey_free(k1);
+ TEST_DONE();
+
+#ifdef OPENSSL_HAS_ECC
+ TEST_START("fuzz ECDSA public");
+ buf = load_file("ecdsa_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ public_fuzz(k1);
+ sshkey_free(k1);
+ TEST_DONE();
+
+ TEST_START("fuzz ECDSA cert");
+ ASSERT_INT_EQ(sshkey_load_cert(test_data_file("ecdsa_1"), &k1), 0);
+ public_fuzz(k1);
+ sshkey_free(k1);
+ TEST_DONE();
+#endif /* OPENSSL_HAS_ECC */
+#endif /* WITH_OPENSSL */
+
+ TEST_START("fuzz Ed25519 public");
+ buf = load_file("ed25519_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ public_fuzz(k1);
+ sshkey_free(k1);
+ TEST_DONE();
+
+ TEST_START("fuzz Ed25519 cert");
+ ASSERT_INT_EQ(sshkey_load_cert(test_data_file("ed25519_1"), &k1), 0);
+ public_fuzz(k1);
+ sshkey_free(k1);
+ TEST_DONE();
+
+#ifdef WITH_OPENSSL
+ TEST_START("fuzz RSA sig");
+ buf = load_file("rsa_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ sig_fuzz(k1, "ssh-rsa");
+ sshkey_free(k1);
+ TEST_DONE();
+
+ TEST_START("fuzz RSA SHA256 sig");
+ buf = load_file("rsa_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ sig_fuzz(k1, "rsa-sha2-256");
+ sshkey_free(k1);
+ TEST_DONE();
+
+ TEST_START("fuzz RSA SHA512 sig");
+ buf = load_file("rsa_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ sig_fuzz(k1, "rsa-sha2-512");
+ sshkey_free(k1);
+ TEST_DONE();
+
+ TEST_START("fuzz DSA sig");
+ buf = load_file("dsa_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ sig_fuzz(k1, NULL);
+ sshkey_free(k1);
+ TEST_DONE();
+
+#ifdef OPENSSL_HAS_ECC
+ TEST_START("fuzz ECDSA sig");
+ buf = load_file("ecdsa_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ sig_fuzz(k1, NULL);
+ sshkey_free(k1);
+ TEST_DONE();
+#endif /* OPENSSL_HAS_ECC */
+#endif /* WITH_OPENSSL */
+
+ TEST_START("fuzz Ed25519 sig");
+ buf = load_file("ed25519_1");
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0);
+ sshbuf_free(buf);
+ sig_fuzz(k1, NULL);
+ sshkey_free(k1);
+ TEST_DONE();
+
+/* XXX fuzz decoded new-format blobs too */
+/* XXX fuzz XMSS too */
+
+}
diff --git a/regress/unittests/sshkey/test_sshkey.c b/regress/unittests/sshkey/test_sshkey.c
new file mode 100644
index 0000000..cc359ae
--- /dev/null
+++ b/regress/unittests/sshkey/test_sshkey.c
@@ -0,0 +1,528 @@
+/* $OpenBSD: test_sshkey.c,v 1.23 2023/01/04 22:48:57 tb Exp $ */
+/*
+ * Regress test for sshkey.h key management API
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef WITH_OPENSSL
+#include <openssl/bn.h>
+#include <openssl/rsa.h>
+#include <openssl/dsa.h>
+#if defined(OPENSSL_HAS_ECC) && defined(OPENSSL_HAS_NISTP256)
+# include <openssl/ec.h>
+#endif
+#endif
+
+#include "../test_helper/test_helper.h"
+
+#include "ssherr.h"
+#include "sshbuf.h"
+#define SSHBUF_INTERNAL 1 /* access internals for testing */
+#include "sshkey.h"
+
+#include "authfile.h"
+#include "common.h"
+#include "ssh2.h"
+
+void sshkey_tests(void);
+
+static void
+put_opt(struct sshbuf *b, const char *name, const char *value)
+{
+ struct sshbuf *sect;
+
+ sect = sshbuf_new();
+ ASSERT_PTR_NE(sect, NULL);
+ ASSERT_INT_EQ(sshbuf_put_cstring(b, name), 0);
+ if (value != NULL)
+ ASSERT_INT_EQ(sshbuf_put_cstring(sect, value), 0);
+ ASSERT_INT_EQ(sshbuf_put_stringb(b, sect), 0);
+ sshbuf_free(sect);
+}
+
+#ifdef WITH_OPENSSL
+static void
+build_cert(struct sshbuf *b, struct sshkey *k, const char *type,
+ struct sshkey *sign_key, struct sshkey *ca_key,
+ const char *sig_alg)
+{
+ struct sshbuf *ca_buf, *pk, *principals, *critopts, *exts;
+ u_char *sigblob;
+ size_t siglen;
+
+ ca_buf = sshbuf_new();
+ ASSERT_PTR_NE(ca_buf, NULL);
+ ASSERT_INT_EQ(sshkey_putb(ca_key, ca_buf), 0);
+
+ /*
+ * Get the public key serialisation by rendering the key and skipping
+ * the type string. This is a bit of a hack :/
+ */
+ pk = sshbuf_new();
+ ASSERT_PTR_NE(pk, NULL);
+ ASSERT_INT_EQ(sshkey_putb_plain(k, pk), 0);
+ ASSERT_INT_EQ(sshbuf_skip_string(pk), 0);
+
+ principals = sshbuf_new();
+ ASSERT_PTR_NE(principals, NULL);
+ ASSERT_INT_EQ(sshbuf_put_cstring(principals, "gsamsa"), 0);
+ ASSERT_INT_EQ(sshbuf_put_cstring(principals, "gregor"), 0);
+
+ critopts = sshbuf_new();
+ ASSERT_PTR_NE(critopts, NULL);
+ put_opt(critopts, "force-command", "/usr/local/bin/nethack");
+ put_opt(critopts, "source-address", "192.168.0.0/24,127.0.0.1,::1");
+
+ exts = sshbuf_new();
+ ASSERT_PTR_NE(exts, NULL);
+ put_opt(critopts, "permit-X11-forwarding", NULL);
+
+ ASSERT_INT_EQ(sshbuf_put_cstring(b, type), 0);
+ ASSERT_INT_EQ(sshbuf_put_cstring(b, "noncenoncenonce!"), 0); /* nonce */
+ ASSERT_INT_EQ(sshbuf_putb(b, pk), 0); /* public key serialisation */
+ ASSERT_INT_EQ(sshbuf_put_u64(b, 1234), 0); /* serial */
+ ASSERT_INT_EQ(sshbuf_put_u32(b, SSH2_CERT_TYPE_USER), 0); /* type */
+ ASSERT_INT_EQ(sshbuf_put_cstring(b, "gregor"), 0); /* key ID */
+ ASSERT_INT_EQ(sshbuf_put_stringb(b, principals), 0); /* principals */
+ ASSERT_INT_EQ(sshbuf_put_u64(b, 0), 0); /* start */
+ ASSERT_INT_EQ(sshbuf_put_u64(b, 0xffffffffffffffffULL), 0); /* end */
+ ASSERT_INT_EQ(sshbuf_put_stringb(b, critopts), 0); /* options */
+ ASSERT_INT_EQ(sshbuf_put_stringb(b, exts), 0); /* extensions */
+ ASSERT_INT_EQ(sshbuf_put_string(b, NULL, 0), 0); /* reserved */
+ ASSERT_INT_EQ(sshbuf_put_stringb(b, ca_buf), 0); /* signature key */
+ ASSERT_INT_EQ(sshkey_sign(sign_key, &sigblob, &siglen,
+ sshbuf_ptr(b), sshbuf_len(b), sig_alg, NULL, NULL, 0), 0);
+ ASSERT_INT_EQ(sshbuf_put_string(b, sigblob, siglen), 0); /* signature */
+
+ free(sigblob);
+ sshbuf_free(ca_buf);
+ sshbuf_free(exts);
+ sshbuf_free(critopts);
+ sshbuf_free(principals);
+ sshbuf_free(pk);
+}
+#endif /* WITH_OPENSSL */
+
+static void
+signature_test(struct sshkey *k, struct sshkey *bad, const char *sig_alg,
+ const u_char *d, size_t l)
+{
+ size_t len;
+ u_char *sig;
+
+ ASSERT_INT_EQ(sshkey_sign(k, &sig, &len, d, l, sig_alg,
+ NULL, NULL, 0), 0);
+ ASSERT_SIZE_T_GT(len, 8);
+ ASSERT_PTR_NE(sig, NULL);
+ ASSERT_INT_EQ(sshkey_verify(k, sig, len, d, l, NULL, 0, NULL), 0);
+ ASSERT_INT_NE(sshkey_verify(bad, sig, len, d, l, NULL, 0, NULL), 0);
+ /* Fuzz test is more comprehensive, this is just a smoke test */
+ sig[len - 5] ^= 0x10;
+ ASSERT_INT_NE(sshkey_verify(k, sig, len, d, l, NULL, 0, NULL), 0);
+ free(sig);
+}
+
+static void
+banana(u_char *s, size_t l)
+{
+ size_t o;
+ const u_char the_banana[] = { 'b', 'a', 'n', 'a', 'n', 'a' };
+
+ for (o = 0; o < l; o += sizeof(the_banana)) {
+ if (l - o < sizeof(the_banana)) {
+ memcpy(s + o, "nanananana", l - o);
+ break;
+ }
+ memcpy(s + o, the_banana, sizeof(the_banana));
+ }
+}
+
+static void
+signature_tests(struct sshkey *k, struct sshkey *bad, const char *sig_alg)
+{
+ u_char i, buf[2049];
+ size_t lens[] = {
+ 1, 2, 7, 8, 9, 15, 16, 17, 31, 32, 33, 127, 128, 129,
+ 255, 256, 257, 1023, 1024, 1025, 2047, 2048, 2049
+ };
+
+ for (i = 0; i < (sizeof(lens)/sizeof(lens[0])); i++) {
+ test_subtest_info("%s key, banana length %zu",
+ sshkey_type(k), lens[i]);
+ banana(buf, lens[i]);
+ signature_test(k, bad, sig_alg, buf, lens[i]);
+ }
+}
+
+static struct sshkey *
+get_private(const char *n)
+{
+ struct sshbuf *b;
+ struct sshkey *ret;
+
+ b = load_file(n);
+ ASSERT_INT_EQ(sshkey_parse_private_fileblob(b, "", &ret, NULL), 0);
+ sshbuf_free(b);
+ return ret;
+}
+
+void
+sshkey_tests(void)
+{
+ struct sshkey *k1, *k2, *k3, *kf;
+#ifdef WITH_OPENSSL
+ struct sshkey *k4, *kr, *kd;
+#ifdef OPENSSL_HAS_ECC
+ struct sshkey *ke;
+#endif /* OPENSSL_HAS_ECC */
+#endif /* WITH_OPENSSL */
+ struct sshbuf *b;
+
+ TEST_START("new invalid");
+ k1 = sshkey_new(-42);
+ ASSERT_PTR_EQ(k1, NULL);
+ TEST_DONE();
+
+ TEST_START("new/free KEY_UNSPEC");
+ k1 = sshkey_new(KEY_UNSPEC);
+ ASSERT_PTR_NE(k1, NULL);
+ sshkey_free(k1);
+ TEST_DONE();
+
+#ifdef WITH_OPENSSL
+ TEST_START("new/free KEY_RSA");
+ k1 = sshkey_new(KEY_RSA);
+ ASSERT_PTR_NE(k1, NULL);
+ ASSERT_PTR_NE(k1->rsa, NULL);
+ sshkey_free(k1);
+ TEST_DONE();
+
+ TEST_START("new/free KEY_DSA");
+ k1 = sshkey_new(KEY_DSA);
+ ASSERT_PTR_NE(k1, NULL);
+ ASSERT_PTR_NE(k1->dsa, NULL);
+ sshkey_free(k1);
+ TEST_DONE();
+
+#ifdef OPENSSL_HAS_ECC
+ TEST_START("new/free KEY_ECDSA");
+ k1 = sshkey_new(KEY_ECDSA);
+ ASSERT_PTR_NE(k1, NULL);
+ ASSERT_PTR_EQ(k1->ecdsa, NULL); /* Can't allocate without NID */
+ sshkey_free(k1);
+ TEST_DONE();
+#endif
+
+ TEST_START("new/free KEY_ED25519");
+ k1 = sshkey_new(KEY_ED25519);
+ ASSERT_PTR_NE(k1, NULL);
+ /* These should be blank until key loaded or generated */
+ ASSERT_PTR_EQ(k1->ed25519_sk, NULL);
+ ASSERT_PTR_EQ(k1->ed25519_pk, NULL);
+ sshkey_free(k1);
+ TEST_DONE();
+
+ TEST_START("generate KEY_RSA too small modulus");
+ ASSERT_INT_EQ(sshkey_generate(KEY_RSA, 128, &k1),
+ SSH_ERR_KEY_LENGTH);
+ ASSERT_PTR_EQ(k1, NULL);
+ TEST_DONE();
+
+ TEST_START("generate KEY_RSA too large modulus");
+ ASSERT_INT_EQ(sshkey_generate(KEY_RSA, 1 << 20, &k1),
+ SSH_ERR_KEY_LENGTH);
+ ASSERT_PTR_EQ(k1, NULL);
+ TEST_DONE();
+
+ TEST_START("generate KEY_DSA wrong bits");
+ ASSERT_INT_EQ(sshkey_generate(KEY_DSA, 2048, &k1),
+ SSH_ERR_KEY_LENGTH);
+ ASSERT_PTR_EQ(k1, NULL);
+ sshkey_free(k1);
+ TEST_DONE();
+
+#ifdef OPENSSL_HAS_ECC
+ TEST_START("generate KEY_ECDSA wrong bits");
+ ASSERT_INT_EQ(sshkey_generate(KEY_ECDSA, 42, &k1),
+ SSH_ERR_KEY_LENGTH);
+ ASSERT_PTR_EQ(k1, NULL);
+ sshkey_free(k1);
+ TEST_DONE();
+#endif
+
+ TEST_START("generate KEY_RSA");
+ ASSERT_INT_EQ(sshkey_generate(KEY_RSA, 767, &kr),
+ SSH_ERR_KEY_LENGTH);
+ ASSERT_INT_EQ(sshkey_generate(KEY_RSA, 1024, &kr), 0);
+ ASSERT_PTR_NE(kr, NULL);
+ ASSERT_PTR_NE(kr->rsa, NULL);
+ ASSERT_PTR_NE(rsa_n(kr), NULL);
+ ASSERT_PTR_NE(rsa_e(kr), NULL);
+ ASSERT_PTR_NE(rsa_p(kr), NULL);
+ ASSERT_INT_EQ(BN_num_bits(rsa_n(kr)), 1024);
+ TEST_DONE();
+
+ TEST_START("generate KEY_DSA");
+ ASSERT_INT_EQ(sshkey_generate(KEY_DSA, 1024, &kd), 0);
+ ASSERT_PTR_NE(kd, NULL);
+ ASSERT_PTR_NE(kd->dsa, NULL);
+ ASSERT_PTR_NE(dsa_g(kd), NULL);
+ ASSERT_PTR_NE(dsa_priv_key(kd), NULL);
+ TEST_DONE();
+
+#ifdef OPENSSL_HAS_ECC
+ TEST_START("generate KEY_ECDSA");
+ ASSERT_INT_EQ(sshkey_generate(KEY_ECDSA, 256, &ke), 0);
+ ASSERT_PTR_NE(ke, NULL);
+ ASSERT_PTR_NE(ke->ecdsa, NULL);
+ ASSERT_PTR_NE(EC_KEY_get0_public_key(ke->ecdsa), NULL);
+ ASSERT_PTR_NE(EC_KEY_get0_private_key(ke->ecdsa), NULL);
+ TEST_DONE();
+#endif /* OPENSSL_HAS_ECC */
+#endif /* WITH_OPENSSL */
+
+ TEST_START("generate KEY_ED25519");
+ ASSERT_INT_EQ(sshkey_generate(KEY_ED25519, 256, &kf), 0);
+ ASSERT_PTR_NE(kf, NULL);
+ ASSERT_INT_EQ(kf->type, KEY_ED25519);
+ ASSERT_PTR_NE(kf->ed25519_pk, NULL);
+ ASSERT_PTR_NE(kf->ed25519_sk, NULL);
+ TEST_DONE();
+
+#ifdef WITH_OPENSSL
+ TEST_START("demote KEY_RSA");
+ ASSERT_INT_EQ(sshkey_from_private(kr, &k1), 0);
+ ASSERT_PTR_NE(k1, NULL);
+ ASSERT_PTR_NE(kr, k1);
+ ASSERT_INT_EQ(k1->type, KEY_RSA);
+ ASSERT_PTR_NE(k1->rsa, NULL);
+ ASSERT_PTR_NE(rsa_n(k1), NULL);
+ ASSERT_PTR_NE(rsa_e(k1), NULL);
+ ASSERT_PTR_EQ(rsa_p(k1), NULL);
+ TEST_DONE();
+
+ TEST_START("equal KEY_RSA/demoted KEY_RSA");
+ ASSERT_INT_EQ(sshkey_equal(kr, k1), 1);
+ sshkey_free(k1);
+ TEST_DONE();
+
+ TEST_START("demote KEY_DSA");
+ ASSERT_INT_EQ(sshkey_from_private(kd, &k1), 0);
+ ASSERT_PTR_NE(k1, NULL);
+ ASSERT_PTR_NE(kd, k1);
+ ASSERT_INT_EQ(k1->type, KEY_DSA);
+ ASSERT_PTR_NE(k1->dsa, NULL);
+ ASSERT_PTR_NE(dsa_g(k1), NULL);
+ ASSERT_PTR_EQ(dsa_priv_key(k1), NULL);
+ TEST_DONE();
+
+ TEST_START("equal KEY_DSA/demoted KEY_DSA");
+ ASSERT_INT_EQ(sshkey_equal(kd, k1), 1);
+ sshkey_free(k1);
+ TEST_DONE();
+
+#ifdef OPENSSL_HAS_ECC
+ TEST_START("demote KEY_ECDSA");
+ ASSERT_INT_EQ(sshkey_from_private(ke, &k1), 0);
+ ASSERT_PTR_NE(k1, NULL);
+ ASSERT_PTR_NE(ke, k1);
+ ASSERT_INT_EQ(k1->type, KEY_ECDSA);
+ ASSERT_PTR_NE(k1->ecdsa, NULL);
+ ASSERT_INT_EQ(k1->ecdsa_nid, ke->ecdsa_nid);
+ ASSERT_PTR_NE(EC_KEY_get0_public_key(ke->ecdsa), NULL);
+ ASSERT_PTR_EQ(EC_KEY_get0_private_key(k1->ecdsa), NULL);
+ TEST_DONE();
+
+ TEST_START("equal KEY_ECDSA/demoted KEY_ECDSA");
+ ASSERT_INT_EQ(sshkey_equal(ke, k1), 1);
+ sshkey_free(k1);
+ TEST_DONE();
+#endif /* OPENSSL_HAS_ECC */
+#endif /* WITH_OPENSSL */
+
+ TEST_START("demote KEY_ED25519");
+ ASSERT_INT_EQ(sshkey_from_private(kf, &k1), 0);
+ ASSERT_PTR_NE(k1, NULL);
+ ASSERT_PTR_NE(kf, k1);
+ ASSERT_INT_EQ(k1->type, KEY_ED25519);
+ ASSERT_PTR_NE(k1->ed25519_pk, NULL);
+ ASSERT_PTR_EQ(k1->ed25519_sk, NULL);
+ TEST_DONE();
+
+ TEST_START("equal KEY_ED25519/demoted KEY_ED25519");
+ ASSERT_INT_EQ(sshkey_equal(kf, k1), 1);
+ sshkey_free(k1);
+ TEST_DONE();
+
+#ifdef WITH_OPENSSL
+ TEST_START("equal mismatched key types");
+ ASSERT_INT_EQ(sshkey_equal(kd, kr), 0);
+#ifdef OPENSSL_HAS_ECC
+ ASSERT_INT_EQ(sshkey_equal(kd, ke), 0);
+ ASSERT_INT_EQ(sshkey_equal(kr, ke), 0);
+ ASSERT_INT_EQ(sshkey_equal(ke, kf), 0);
+#endif /* OPENSSL_HAS_ECC */
+ ASSERT_INT_EQ(sshkey_equal(kd, kf), 0);
+ TEST_DONE();
+#endif /* WITH_OPENSSL */
+
+ TEST_START("equal different keys");
+#ifdef WITH_OPENSSL
+ ASSERT_INT_EQ(sshkey_generate(KEY_RSA, 1024, &k1), 0);
+ ASSERT_INT_EQ(sshkey_equal(kr, k1), 0);
+ sshkey_free(k1);
+ ASSERT_INT_EQ(sshkey_generate(KEY_DSA, 1024, &k1), 0);
+ ASSERT_INT_EQ(sshkey_equal(kd, k1), 0);
+ sshkey_free(k1);
+#ifdef OPENSSL_HAS_ECC
+ ASSERT_INT_EQ(sshkey_generate(KEY_ECDSA, 256, &k1), 0);
+ ASSERT_INT_EQ(sshkey_equal(ke, k1), 0);
+ sshkey_free(k1);
+#endif /* OPENSSL_HAS_ECC */
+#endif /* WITH_OPENSSL */
+ ASSERT_INT_EQ(sshkey_generate(KEY_ED25519, 256, &k1), 0);
+ ASSERT_INT_EQ(sshkey_equal(kf, k1), 0);
+ sshkey_free(k1);
+ TEST_DONE();
+
+#ifdef WITH_OPENSSL
+ sshkey_free(kr);
+ sshkey_free(kd);
+#ifdef OPENSSL_HAS_ECC
+ sshkey_free(ke);
+#endif /* OPENSSL_HAS_ECC */
+#endif /* WITH_OPENSSL */
+ sshkey_free(kf);
+
+ TEST_START("certify key");
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("ed25519_1.pub"),
+ &k1, NULL), 0);
+ k2 = get_private("ed25519_2");
+ ASSERT_INT_EQ(sshkey_to_certified(k1), 0);
+ ASSERT_PTR_NE(k1->cert, NULL);
+ k1->cert->type = SSH2_CERT_TYPE_USER;
+ k1->cert->serial = 1234;
+ k1->cert->key_id = strdup("estragon");
+ ASSERT_PTR_NE(k1->cert->key_id, NULL);
+ k1->cert->principals = calloc(4, sizeof(*k1->cert->principals));
+ ASSERT_PTR_NE(k1->cert->principals, NULL);
+ k1->cert->principals[0] = strdup("estragon");
+ k1->cert->principals[1] = strdup("vladimir");
+ k1->cert->principals[2] = strdup("pozzo");
+ k1->cert->principals[3] = strdup("lucky");
+ ASSERT_PTR_NE(k1->cert->principals[0], NULL);
+ ASSERT_PTR_NE(k1->cert->principals[1], NULL);
+ ASSERT_PTR_NE(k1->cert->principals[2], NULL);
+ ASSERT_PTR_NE(k1->cert->principals[3], NULL);
+ k1->cert->nprincipals = 4;
+ k1->cert->valid_after = 0;
+ k1->cert->valid_before = (u_int64_t)-1;
+ sshbuf_free(k1->cert->critical);
+ k1->cert->critical = sshbuf_new();
+ ASSERT_PTR_NE(k1->cert->critical, NULL);
+ sshbuf_free(k1->cert->extensions);
+ k1->cert->extensions = sshbuf_new();
+ ASSERT_PTR_NE(k1->cert->extensions, NULL);
+ put_opt(k1->cert->critical, "force-command", "/usr/bin/true");
+ put_opt(k1->cert->critical, "source-address", "127.0.0.1");
+ put_opt(k1->cert->extensions, "permit-X11-forwarding", NULL);
+ put_opt(k1->cert->extensions, "permit-agent-forwarding", NULL);
+ ASSERT_INT_EQ(sshkey_from_private(k2, &k1->cert->signature_key), 0);
+ ASSERT_INT_EQ(sshkey_certify(k1, k2, NULL, NULL, NULL), 0);
+ b = sshbuf_new();
+ ASSERT_PTR_NE(b, NULL);
+ ASSERT_INT_EQ(sshkey_putb(k1, b), 0);
+ ASSERT_INT_EQ(sshkey_from_blob(sshbuf_ptr(b), sshbuf_len(b), &k3), 0);
+
+ sshkey_free(k1);
+ sshkey_free(k2);
+ sshkey_free(k3);
+ sshbuf_reset(b);
+ TEST_DONE();
+
+#ifdef WITH_OPENSSL
+ TEST_START("sign and verify RSA");
+ k1 = get_private("rsa_1");
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("rsa_2.pub"), &k2,
+ NULL), 0);
+ signature_tests(k1, k2, "ssh-rsa");
+ sshkey_free(k1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("sign and verify RSA-SHA256");
+ k1 = get_private("rsa_1");
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("rsa_2.pub"), &k2,
+ NULL), 0);
+ signature_tests(k1, k2, "rsa-sha2-256");
+ sshkey_free(k1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("sign and verify RSA-SHA512");
+ k1 = get_private("rsa_1");
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("rsa_2.pub"), &k2,
+ NULL), 0);
+ signature_tests(k1, k2, "rsa-sha2-512");
+ sshkey_free(k1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+ TEST_START("sign and verify DSA");
+ k1 = get_private("dsa_1");
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("dsa_2.pub"), &k2,
+ NULL), 0);
+ signature_tests(k1, k2, NULL);
+ sshkey_free(k1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+#ifdef OPENSSL_HAS_ECC
+ TEST_START("sign and verify ECDSA");
+ k1 = get_private("ecdsa_1");
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("ecdsa_2.pub"), &k2,
+ NULL), 0);
+ signature_tests(k1, k2, NULL);
+ sshkey_free(k1);
+ sshkey_free(k2);
+ TEST_DONE();
+#endif /* OPENSSL_HAS_ECC */
+#endif /* WITH_OPENSSL */
+
+ TEST_START("sign and verify ED25519");
+ k1 = get_private("ed25519_1");
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("ed25519_2.pub"), &k2,
+ NULL), 0);
+ signature_tests(k1, k2, NULL);
+ sshkey_free(k1);
+ sshkey_free(k2);
+ TEST_DONE();
+
+#ifdef WITH_OPENSSL
+ TEST_START("nested certificate");
+ ASSERT_INT_EQ(sshkey_load_cert(test_data_file("rsa_1"), &k1), 0);
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file("rsa_1.pub"), &k2,
+ NULL), 0);
+ k3 = get_private("rsa_1");
+ build_cert(b, k2, "ssh-rsa-cert-v01@openssh.com", k3, k1, NULL);
+ ASSERT_INT_EQ(sshkey_from_blob(sshbuf_ptr(b), sshbuf_len(b), &k4),
+ SSH_ERR_KEY_CERT_INVALID_SIGN_KEY);
+ ASSERT_PTR_EQ(k4, NULL);
+ sshkey_free(k1);
+ sshkey_free(k2);
+ sshkey_free(k3);
+ sshbuf_free(b);
+ TEST_DONE();
+#endif /* WITH_OPENSSL */
+}
diff --git a/regress/unittests/sshkey/testdata/dsa_1 b/regress/unittests/sshkey/testdata/dsa_1
new file mode 100644
index 0000000..d3f2482
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_1
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvAIBAAKBgQD6kutNFRsHTwEAv6d39Lhsqy1apdHBZ9c2HfyRr7WmypyGIy2m
+Ka43vzXI8CNwmRSYs+A6d0vJC7Pl+f9QzJ/04NWOA+MiwfurwrR3CRe61QRYb8Py
+mcHOxueHs95IcjrbIPNn86cjnPP5qvv/guUzCjuww4zBdJOXpligrGt2XwIVAKMD
+/50qQy7j8JaMk+1+Xtg1pK01AoGBAO7l9QVVbSSoy5lq6cOtvpf8UlwOa6+zBwbl
+o4gmFd1RwX1yWkA8kQ7RrhCSg8Hc6mIGnKRgKRli/3LgbSfZ0obFJehkRtEWtN4P
+h8fVUeS74iQbIwFQeKlYHIlNTRoGtAbdi3nHdV+BBkEQc1V3rjqYqhjOoz/yNsgz
+LND26HrdAoGBAOdXpyfmobEBaOqZAuvgj1P0uhjG2P31Ufurv22FWPBU3A9qrkxb
+OXwE0LwvjCvrsQV/lrYhJz/tiys40VeahulWZE5SAHMXGIf95LiLSgaXMjko7joo
+t+LK84ltLymwZ4QMnYjnZSSclf1UuyQMcUtb34+I0u9Ycnyhp2mSFsQtAhRYIbQ5
+KfXsZuBPuWe5FJz3ldaEgw==
+-----END DSA PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/dsa_1-cert.fp b/regress/unittests/sshkey/testdata/dsa_1-cert.fp
new file mode 100644
index 0000000..75ff0e9
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_1-cert.fp
@@ -0,0 +1 @@
+SHA256:kOLgXSoAT8O5T6r36n5NJUYigbux1d7gdH/rmWiJm6s
diff --git a/regress/unittests/sshkey/testdata/dsa_1-cert.pub b/regress/unittests/sshkey/testdata/dsa_1-cert.pub
new file mode 100644
index 0000000..e768db1
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_1-cert.pub
@@ -0,0 +1 @@
+ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgdTlbNU9Hn9Qng3FHxwH971bxCIoq1ern/QWFFDWXgmYAAACBAPqS600VGwdPAQC/p3f0uGyrLVql0cFn1zYd/JGvtabKnIYjLaYprje/NcjwI3CZFJiz4Dp3S8kLs+X5/1DMn/Tg1Y4D4yLB+6vCtHcJF7rVBFhvw/KZwc7G54ez3khyOtsg82fzpyOc8/mq+/+C5TMKO7DDjMF0k5emWKCsa3ZfAAAAFQCjA/+dKkMu4/CWjJPtfl7YNaStNQAAAIEA7uX1BVVtJKjLmWrpw62+l/xSXA5rr7MHBuWjiCYV3VHBfXJaQDyRDtGuEJKDwdzqYgacpGApGWL/cuBtJ9nShsUl6GRG0Ra03g+Hx9VR5LviJBsjAVB4qVgciU1NGga0Bt2Lecd1X4EGQRBzVXeuOpiqGM6jP/I2yDMs0Pboet0AAACBAOdXpyfmobEBaOqZAuvgj1P0uhjG2P31Ufurv22FWPBU3A9qrkxbOXwE0LwvjCvrsQV/lrYhJz/tiys40VeahulWZE5SAHMXGIf95LiLSgaXMjko7joot+LK84ltLymwZ4QMnYjnZSSclf1UuyQMcUtb34+I0u9Ycnyhp2mSFsQtAAAAAAAAAAYAAAACAAAABmp1bGl1cwAAABIAAAAFaG9zdDEAAAAFaG9zdDIAAAAANowB8AAAAABNHmBwAAAAAAAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACBThupGO0X+FLQhbz8CoKPwc7V3JNsQuGtlsgN+F7SMGQAAAFMAAAALc3NoLWVkMjU1MTkAAABAh/z1LIdNL1b66tQ8t9DY9BTB3BQKpTKmc7ezyFKLwl96yaIniZwD9Ticdbe/8i/Li3uCFE3EAt8NAIv9zff8Bg== DSA test key #1
diff --git a/regress/unittests/sshkey/testdata/dsa_1.fp b/regress/unittests/sshkey/testdata/dsa_1.fp
new file mode 100644
index 0000000..75ff0e9
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_1.fp
@@ -0,0 +1 @@
+SHA256:kOLgXSoAT8O5T6r36n5NJUYigbux1d7gdH/rmWiJm6s
diff --git a/regress/unittests/sshkey/testdata/dsa_1.fp.bb b/regress/unittests/sshkey/testdata/dsa_1.fp.bb
new file mode 100644
index 0000000..ba37776
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_1.fp.bb
@@ -0,0 +1 @@
+xetag-todiz-mifah-torec-mynyv-cyvit-gopon-pygag-rupic-cenav-bexax
diff --git a/regress/unittests/sshkey/testdata/dsa_1.param.g b/regress/unittests/sshkey/testdata/dsa_1.param.g
new file mode 100644
index 0000000..e51c3f9
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_1.param.g
@@ -0,0 +1 @@
+00eee5f505556d24a8cb996ae9c3adbe97fc525c0e6bafb30706e5a3882615dd51c17d725a403c910ed1ae109283c1dcea62069ca460291962ff72e06d27d9d286c525e86446d116b4de0f87c7d551e4bbe2241b23015078a9581c894d4d1a06b406dd8b79c7755f81064110735577ae3a98aa18cea33ff236c8332cd0f6e87add
diff --git a/regress/unittests/sshkey/testdata/dsa_1.param.priv b/regress/unittests/sshkey/testdata/dsa_1.param.priv
new file mode 100644
index 0000000..4f74331
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_1.param.priv
@@ -0,0 +1 @@
+5821b43929f5ec66e04fb967b9149cf795d68483
diff --git a/regress/unittests/sshkey/testdata/dsa_1.param.pub b/regress/unittests/sshkey/testdata/dsa_1.param.pub
new file mode 100644
index 0000000..ba0313b
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_1.param.pub
@@ -0,0 +1 @@
+00e757a727e6a1b10168ea9902ebe08f53f4ba18c6d8fdf551fbabbf6d8558f054dc0f6aae4c5b397c04d0bc2f8c2bebb1057f96b621273fed8b2b38d1579a86e956644e520073171887fde4b88b4a0697323928ee3a28b7e2caf3896d2f29b067840c9d88e765249c95fd54bb240c714b5bdf8f88d2ef58727ca1a7699216c42d
diff --git a/regress/unittests/sshkey/testdata/dsa_1.pub b/regress/unittests/sshkey/testdata/dsa_1.pub
new file mode 100644
index 0000000..41cae2f
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_1.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAPqS600VGwdPAQC/p3f0uGyrLVql0cFn1zYd/JGvtabKnIYjLaYprje/NcjwI3CZFJiz4Dp3S8kLs+X5/1DMn/Tg1Y4D4yLB+6vCtHcJF7rVBFhvw/KZwc7G54ez3khyOtsg82fzpyOc8/mq+/+C5TMKO7DDjMF0k5emWKCsa3ZfAAAAFQCjA/+dKkMu4/CWjJPtfl7YNaStNQAAAIEA7uX1BVVtJKjLmWrpw62+l/xSXA5rr7MHBuWjiCYV3VHBfXJaQDyRDtGuEJKDwdzqYgacpGApGWL/cuBtJ9nShsUl6GRG0Ra03g+Hx9VR5LviJBsjAVB4qVgciU1NGga0Bt2Lecd1X4EGQRBzVXeuOpiqGM6jP/I2yDMs0Pboet0AAACBAOdXpyfmobEBaOqZAuvgj1P0uhjG2P31Ufurv22FWPBU3A9qrkxbOXwE0LwvjCvrsQV/lrYhJz/tiys40VeahulWZE5SAHMXGIf95LiLSgaXMjko7joot+LK84ltLymwZ4QMnYjnZSSclf1UuyQMcUtb34+I0u9Ycnyhp2mSFsQt DSA test key #1
diff --git a/regress/unittests/sshkey/testdata/dsa_1_pw b/regress/unittests/sshkey/testdata/dsa_1_pw
new file mode 100644
index 0000000..24c7303
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_1_pw
@@ -0,0 +1,15 @@
+-----BEGIN DSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,BC8386C373B22EB7F00ADC821D5D8BE9
+
++HDV2DQ09sxrIAeXTz9r3YFuPRa2hk1+NGcr3ETkXbC6KiZ14wpTnGTloKwaQjIW
+eXTa9mpCOWAoohgvsVb+hOuOlP7AfeHu1IXV4EAS+GDpkiV5UxlCXXwqlD75Buu4
+wwDd/p4SWzILH3WGjDk5JIXoxWNY13LHwC7Q6gtGJx4AicUG7YBRTXMIBDa/Kh77
+6o2rFETKmp4VHBvHbakmiETfptdM8bbWxKWeY2vakThyESgeofsLoTOQCIwlEfJC
+s2D/KYL65C8VbHYgIoSLTQnooO45DDyxIuhCqP+H23mhv9vB1Od3nc2atgHj/XFs
+dcOPFkF/msDRYqxY3V0AS6+jpKwFodZ7g/hyGcyPxOkzlJVuKoKuH6P5PyQ69Gx0
+iqri0xEPyABr7kGlXNrjjctojX+B4WwSnjg/2euXXWFXCRalIdA7ErATTiQbGOx7
+Vd6Gn8PZbSy1MkqEDrZRip0pfAFJYI/8GXPC75BpnRsrVlfhtrngbW+kBP35LzaN
+l2K+RQ3gSB3iFoqNb1Kuu6T5MZlyVl5H2dVlJSeb1euQ2OycXdDoFTyJ4AiyWS7w
+Vlh8zeJnso5QRDjMwx99pZilbbuFGSLsahiGEveFc6o=
+-----END DSA PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/dsa_2 b/regress/unittests/sshkey/testdata/dsa_2
new file mode 100644
index 0000000..3cc9631
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_2
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvQIBAAKBgQCbyPXNdHeLsjpobPVCMkfagBkt15Zsltqf/PGNP1y1cuz7rsTX
+ZekQwUkSTNm5coqXe+ZOw2O4tjobJDd60I1/VPgaB0NYlQR9Hn87M284WD4f6VY+
+aunHmP134a8ybG5G4NqVNF3ihvxAR2pVITqb7kE46r2uYZNcNlHI8voRCwIVAMcP
+bwqFNsQbH5pJyZW30wj4KVZ3AoGBAIK98BVeKQVf8qDFqx9ovMuNgVSxpd+N0Yta
+5ZEy1OI2ziu5RhjueIM2K7Gq2Mnp38ob1AM53BUxqlcBJaHEDa6rj6yvuMgW9oCJ
+dImBM8sIFxfBbXNbpJiMaDwa6WyT84OkpDE6uuAepTMnWOUWkUVkAiyokHDUGXkG
+GyoQblbXAoGBAIsf7TaZ804sUWwRV0wI8DYx+hxD5QdrfYPYMtL2fHn3lICimGt0
+FTtUZ25jKg0E0DMBPdET6ZEHB3ZZkR8hFoUzZhdnyJMu3UjVtgaV88Ue3PrXxchk
+0W2jHPaAgQU3JIWzo8HFIFqvC/HEL+EyW3rBTY2uXM3XGI+YcWSA4ZrZAhUAsY2f
+bDFNzgZ4DaZ9wLRzTgOswPU=
+-----END DSA PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/dsa_2.fp b/regress/unittests/sshkey/testdata/dsa_2.fp
new file mode 100644
index 0000000..51fbeb4
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_2.fp
@@ -0,0 +1 @@
+SHA256:ecwhWcXgpdBxZ2e+OjpRRY7dqXHHCD62BGtoVQQBwCk
diff --git a/regress/unittests/sshkey/testdata/dsa_2.fp.bb b/regress/unittests/sshkey/testdata/dsa_2.fp.bb
new file mode 100644
index 0000000..4d908ee
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_2.fp.bb
@@ -0,0 +1 @@
+xeser-megad-pocan-rozit-belup-tapoh-fapif-kyvit-vonav-cehab-naxax
diff --git a/regress/unittests/sshkey/testdata/dsa_2.pub b/regress/unittests/sshkey/testdata/dsa_2.pub
new file mode 100644
index 0000000..77bb555
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_2.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAJvI9c10d4uyOmhs9UIyR9qAGS3XlmyW2p/88Y0/XLVy7PuuxNdl6RDBSRJM2blyipd75k7DY7i2OhskN3rQjX9U+BoHQ1iVBH0efzszbzhYPh/pVj5q6ceY/XfhrzJsbkbg2pU0XeKG/EBHalUhOpvuQTjqva5hk1w2Ucjy+hELAAAAFQDHD28KhTbEGx+aScmVt9MI+ClWdwAAAIEAgr3wFV4pBV/yoMWrH2i8y42BVLGl343Ri1rlkTLU4jbOK7lGGO54gzYrsarYyenfyhvUAzncFTGqVwElocQNrquPrK+4yBb2gIl0iYEzywgXF8Ftc1ukmIxoPBrpbJPzg6SkMTq64B6lMydY5RaRRWQCLKiQcNQZeQYbKhBuVtcAAACBAIsf7TaZ804sUWwRV0wI8DYx+hxD5QdrfYPYMtL2fHn3lICimGt0FTtUZ25jKg0E0DMBPdET6ZEHB3ZZkR8hFoUzZhdnyJMu3UjVtgaV88Ue3PrXxchk0W2jHPaAgQU3JIWzo8HFIFqvC/HEL+EyW3rBTY2uXM3XGI+YcWSA4ZrZ DSA test key #2
diff --git a/regress/unittests/sshkey/testdata/dsa_n b/regress/unittests/sshkey/testdata/dsa_n
new file mode 100644
index 0000000..657624e
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_n
@@ -0,0 +1,21 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABswAAAAdzc2gtZH
+NzAAAAgQD6kutNFRsHTwEAv6d39Lhsqy1apdHBZ9c2HfyRr7WmypyGIy2mKa43vzXI8CNw
+mRSYs+A6d0vJC7Pl+f9QzJ/04NWOA+MiwfurwrR3CRe61QRYb8PymcHOxueHs95IcjrbIP
+Nn86cjnPP5qvv/guUzCjuww4zBdJOXpligrGt2XwAAABUAowP/nSpDLuPwloyT7X5e2DWk
+rTUAAACBAO7l9QVVbSSoy5lq6cOtvpf8UlwOa6+zBwblo4gmFd1RwX1yWkA8kQ7RrhCSg8
+Hc6mIGnKRgKRli/3LgbSfZ0obFJehkRtEWtN4Ph8fVUeS74iQbIwFQeKlYHIlNTRoGtAbd
+i3nHdV+BBkEQc1V3rjqYqhjOoz/yNsgzLND26HrdAAAAgQDnV6cn5qGxAWjqmQLr4I9T9L
+oYxtj99VH7q79thVjwVNwPaq5MWzl8BNC8L4wr67EFf5a2ISc/7YsrONFXmobpVmROUgBz
+FxiH/eS4i0oGlzI5KO46KLfiyvOJbS8psGeEDJ2I52UknJX9VLskDHFLW9+PiNLvWHJ8oa
+dpkhbELQAAAdhWTOFbVkzhWwAAAAdzc2gtZHNzAAAAgQD6kutNFRsHTwEAv6d39Lhsqy1a
+pdHBZ9c2HfyRr7WmypyGIy2mKa43vzXI8CNwmRSYs+A6d0vJC7Pl+f9QzJ/04NWOA+Miwf
+urwrR3CRe61QRYb8PymcHOxueHs95IcjrbIPNn86cjnPP5qvv/guUzCjuww4zBdJOXplig
+rGt2XwAAABUAowP/nSpDLuPwloyT7X5e2DWkrTUAAACBAO7l9QVVbSSoy5lq6cOtvpf8Ul
+wOa6+zBwblo4gmFd1RwX1yWkA8kQ7RrhCSg8Hc6mIGnKRgKRli/3LgbSfZ0obFJehkRtEW
+tN4Ph8fVUeS74iQbIwFQeKlYHIlNTRoGtAbdi3nHdV+BBkEQc1V3rjqYqhjOoz/yNsgzLN
+D26HrdAAAAgQDnV6cn5qGxAWjqmQLr4I9T9LoYxtj99VH7q79thVjwVNwPaq5MWzl8BNC8
+L4wr67EFf5a2ISc/7YsrONFXmobpVmROUgBzFxiH/eS4i0oGlzI5KO46KLfiyvOJbS8psG
+eEDJ2I52UknJX9VLskDHFLW9+PiNLvWHJ8oadpkhbELQAAABRYIbQ5KfXsZuBPuWe5FJz3
+ldaEgwAAAAAB
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/dsa_n_pw b/regress/unittests/sshkey/testdata/dsa_n_pw
new file mode 100644
index 0000000..24ac299
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/dsa_n_pw
@@ -0,0 +1,21 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABCVs+LsMJ
+wnB5zM9U9pTXrGAAAAEAAAAAEAAAGzAAAAB3NzaC1kc3MAAACBAPqS600VGwdPAQC/p3f0
+uGyrLVql0cFn1zYd/JGvtabKnIYjLaYprje/NcjwI3CZFJiz4Dp3S8kLs+X5/1DMn/Tg1Y
+4D4yLB+6vCtHcJF7rVBFhvw/KZwc7G54ez3khyOtsg82fzpyOc8/mq+/+C5TMKO7DDjMF0
+k5emWKCsa3ZfAAAAFQCjA/+dKkMu4/CWjJPtfl7YNaStNQAAAIEA7uX1BVVtJKjLmWrpw6
+2+l/xSXA5rr7MHBuWjiCYV3VHBfXJaQDyRDtGuEJKDwdzqYgacpGApGWL/cuBtJ9nShsUl
+6GRG0Ra03g+Hx9VR5LviJBsjAVB4qVgciU1NGga0Bt2Lecd1X4EGQRBzVXeuOpiqGM6jP/
+I2yDMs0Pboet0AAACBAOdXpyfmobEBaOqZAuvgj1P0uhjG2P31Ufurv22FWPBU3A9qrkxb
+OXwE0LwvjCvrsQV/lrYhJz/tiys40VeahulWZE5SAHMXGIf95LiLSgaXMjko7joot+LK84
+ltLymwZ4QMnYjnZSSclf1UuyQMcUtb34+I0u9Ycnyhp2mSFsQtAAAB4HiOcRW4w+sIqBL0
+TPVbf0glN1hUi0rcE63Pqxmvxb8LkldC4IxAUagPrjhNAEW2AY42+CvPrtGB1z7gDADAIW
+xZX6wKwIcXP0Qh+xHE12F4u6mwfasssnAp4t1Ki8uCjMjnimgb3KdWpp0kiUV0oR062TXV
+PAdfrWjaq4fw0KOqbHIAG/v36AqzuqjSTfDbqvLZM3y0gp2Q1RxaQVJA5ZIKKyqRyFX7sr
+BaEIyCgeE3hM0EB7BycY1oIcS/eNxrACBWVJCENl5N7LtEYXNX7TANFniztfXzwaqGTT6A
+fCfbW4gz1UKldLUBzbIrPwMWlirAstbHvOf/2Iay2pNAs/SHhI0aF2jsGfvv5/D6N+r9dG
+B2SgDKBg7pywMH1DTvg6YT3P4GjCx0GUHqRCFLvD1rDdk4KSjvaRMpVq1PJ0/Wv6UGtsMS
+TR0PaEHDRNZqAX4YxqujnWrGKuRJhuz0eUvp7fZvbWHtiAMKV7368kkeUmkOHanb+TS+zs
+KINX8ev8zJZ6WVr8Vl+IQavpv0i2bXwS6QqbEuifpv/+uBb7pqRiU4u8en0eMdX1bZoTPM
+R6xHCnGD/Jpb3zS91Ya57T6CiXZ12KCaL6nWGnCkZVpzkfJ2HjFklWSWBQ6uyaosDQ==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ecdsa_1 b/regress/unittests/sshkey/testdata/ecdsa_1
new file mode 100644
index 0000000..80382b6
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_1
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIPPNyUAnjvFr+eT/7t/IyjuQQd/aLFiTY92LB9gIjyrMoAoGCCqGSM49
+AwEHoUQDQgAEDFlblkOrW9ydKVhtM+9AY3c9saBE7SG3lFx38nBavkADDaI9jh3/
+kvG/Jt9vpm22qwoklTCGDfzCkXkIKaWlBw==
+-----END EC PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ecdsa_1-cert.fp b/regress/unittests/sshkey/testdata/ecdsa_1-cert.fp
new file mode 100644
index 0000000..e48304f
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_1-cert.fp
@@ -0,0 +1 @@
+SHA256:8ty77fOpABat1y88aNdclQTfU+lVvWe7jYZGw8VYtfg
diff --git a/regress/unittests/sshkey/testdata/ecdsa_1-cert.pub b/regress/unittests/sshkey/testdata/ecdsa_1-cert.pub
new file mode 100644
index 0000000..55e2a25
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_1-cert.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgOtFRnMigkGliaYfPmX5IidVWfV3tRH6lqRXv0l8bvKoAAAAIbmlzdHAyNTYAAABBBAxZW5ZDq1vcnSlYbTPvQGN3PbGgRO0ht5Rcd/JwWr5AAw2iPY4d/5Lxvybfb6ZttqsKJJUwhg38wpF5CCmlpQcAAAAAAAAABwAAAAIAAAAGanVsaXVzAAAAEgAAAAVob3N0MQAAAAVob3N0MgAAAAA2jAHwAAAAAE0eYHAAAAAAAAAAAAAAAAAAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAxZW5ZDq1vcnSlYbTPvQGN3PbGgRO0ht5Rcd/JwWr5AAw2iPY4d/5Lxvybfb6ZttqsKJJUwhg38wpF5CCmlpQcAAABkAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAABJAAAAIHbxGwTnue7KxhHXGFvRcxBnekhQ3Qx84vV/Vs4oVCrpAAAAIQC7vk2+d14aS7td7kVXLQn392oALjEBzMZoDvT1vT/zOA== ECDSA test key #1
diff --git a/regress/unittests/sshkey/testdata/ecdsa_1.fp b/regress/unittests/sshkey/testdata/ecdsa_1.fp
new file mode 100644
index 0000000..e48304f
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_1.fp
@@ -0,0 +1 @@
+SHA256:8ty77fOpABat1y88aNdclQTfU+lVvWe7jYZGw8VYtfg
diff --git a/regress/unittests/sshkey/testdata/ecdsa_1.fp.bb b/regress/unittests/sshkey/testdata/ecdsa_1.fp.bb
new file mode 100644
index 0000000..fa23c33
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_1.fp.bb
@@ -0,0 +1 @@
+xibah-vocun-sogyn-byhen-rivem-hegyh-luneh-dozyr-vatyf-dufid-myxyx
diff --git a/regress/unittests/sshkey/testdata/ecdsa_1.param.curve b/regress/unittests/sshkey/testdata/ecdsa_1.param.curve
new file mode 100644
index 0000000..fa04004
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_1.param.curve
@@ -0,0 +1 @@
+prime256v1
diff --git a/regress/unittests/sshkey/testdata/ecdsa_1.param.priv b/regress/unittests/sshkey/testdata/ecdsa_1.param.priv
new file mode 100644
index 0000000..dc908ad
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_1.param.priv
@@ -0,0 +1 @@
+00f3cdc940278ef16bf9e4ffeedfc8ca3b9041dfda2c589363dd8b07d8088f2acc
diff --git a/regress/unittests/sshkey/testdata/ecdsa_1.param.pub b/regress/unittests/sshkey/testdata/ecdsa_1.param.pub
new file mode 100644
index 0000000..71c9584
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_1.param.pub
@@ -0,0 +1 @@
+040c595b9643ab5bdc9d29586d33ef4063773db1a044ed21b7945c77f2705abe40030da23d8e1dff92f1bf26df6fa66db6ab0a249530860dfcc291790829a5a507
diff --git a/regress/unittests/sshkey/testdata/ecdsa_1.pub b/regress/unittests/sshkey/testdata/ecdsa_1.pub
new file mode 100644
index 0000000..84a71f9
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_1.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAxZW5ZDq1vcnSlYbTPvQGN3PbGgRO0ht5Rcd/JwWr5AAw2iPY4d/5Lxvybfb6ZttqsKJJUwhg38wpF5CCmlpQc= ECDSA test key #1
diff --git a/regress/unittests/sshkey/testdata/ecdsa_1_pw b/regress/unittests/sshkey/testdata/ecdsa_1_pw
new file mode 100644
index 0000000..5c83a65
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_1_pw
@@ -0,0 +1,8 @@
+-----BEGIN EC PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,7BA38DE00F67851E4207216809C3BB15
+
+8QkFoZHQkj9a2mt032sp+WKaJ1fwteqWDd4RpAW9OzDgqzMx1QO43qJgBDTfhzjt
+M2Q8YfiGjfBEYpg4kCbacfcV68DEV4z6Ll7rIzzzO7OfWUNL++brD64vKx4z6f46
++sn4nbZTXilpkzi/nmPDVzrNmTSywA8T7Yf0QcBUxks=
+-----END EC PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ecdsa_2 b/regress/unittests/sshkey/testdata/ecdsa_2
new file mode 100644
index 0000000..0f4e844
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_2
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIBqBtN7e6Essd3dlsgISViPCXXC0atlNkGtoMgSQdBTKVUfeJOi4lc
+RZaXJdXnqWUqI/KEsH8h8QN4YcB8ugmAcc+gBwYFK4EEACOhgYkDgYYABAHZ2VNy
+oDedBwqsdzY+kkNptc9DrtRCVmO6cULLj+691MhItqVqTMJbTFlI4MnAg9PoGTF/
+0KmLJfy8vSffXGKqqwGKcFNtd1XCo+7Qu9tXbxron9g6Dmu7y8jaLkixcwZwnwLs
+6GmA9qZGuiAfOGV0Gf9/u98sr+vikOa4Ow5JFDTw5g==
+-----END EC PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ecdsa_2.fp b/regress/unittests/sshkey/testdata/ecdsa_2.fp
new file mode 100644
index 0000000..581e48a
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_2.fp
@@ -0,0 +1 @@
+SHA256:ed8YniRHA6qCrErCRnzrWxPHxYuA62a+CAFYUVxJgaI
diff --git a/regress/unittests/sshkey/testdata/ecdsa_2.fp.bb b/regress/unittests/sshkey/testdata/ecdsa_2.fp.bb
new file mode 100644
index 0000000..e1cc664
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_2.fp.bb
@@ -0,0 +1 @@
+xufag-danul-putub-mokin-pugaz-covid-dofag-nihuz-sysab-genar-zaxyx
diff --git a/regress/unittests/sshkey/testdata/ecdsa_2.param.curve b/regress/unittests/sshkey/testdata/ecdsa_2.param.curve
new file mode 100644
index 0000000..617ea2f
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_2.param.curve
@@ -0,0 +1 @@
+secp521r1
diff --git a/regress/unittests/sshkey/testdata/ecdsa_2.param.priv b/regress/unittests/sshkey/testdata/ecdsa_2.param.priv
new file mode 100644
index 0000000..dd898d9
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_2.param.priv
@@ -0,0 +1 @@
+01a81b4dedee84b2c777765b202125623c25d70b46ad94d906b683204907414ca5547de24e8b895c45969725d5e7a9652a23f284b07f21f1037861c07cba098071cf
diff --git a/regress/unittests/sshkey/testdata/ecdsa_2.param.pub b/regress/unittests/sshkey/testdata/ecdsa_2.param.pub
new file mode 100644
index 0000000..94301c9
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_2.param.pub
@@ -0,0 +1 @@
+0401d9d95372a0379d070aac77363e924369b5cf43aed4425663ba7142cb8feebdd4c848b6a56a4cc25b4c5948e0c9c083d3e819317fd0a98b25fcbcbd27df5c62aaab018a70536d7755c2a3eed0bbdb576f1ae89fd83a0e6bbbcbc8da2e48b17306709f02ece86980f6a646ba201f38657419ff7fbbdf2cafebe290e6b83b0e491434f0e6
diff --git a/regress/unittests/sshkey/testdata/ecdsa_2.pub b/regress/unittests/sshkey/testdata/ecdsa_2.pub
new file mode 100644
index 0000000..be9d84b
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_2.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHZ2VNyoDedBwqsdzY+kkNptc9DrtRCVmO6cULLj+691MhItqVqTMJbTFlI4MnAg9PoGTF/0KmLJfy8vSffXGKqqwGKcFNtd1XCo+7Qu9tXbxron9g6Dmu7y8jaLkixcwZwnwLs6GmA9qZGuiAfOGV0Gf9/u98sr+vikOa4Ow5JFDTw5g== ECDSA test key #2
diff --git a/regress/unittests/sshkey/testdata/ecdsa_n b/regress/unittests/sshkey/testdata/ecdsa_n
new file mode 100644
index 0000000..9694f32
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_n
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
+1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQMWVuWQ6tb3J0pWG0z70Bjdz2xoETt
+IbeUXHfycFq+QAMNoj2OHf+S8b8m32+mbbarCiSVMIYN/MKReQgppaUHAAAAoFrmmZBa5p
+mQAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAxZW5ZDq1vcnSlY
+bTPvQGN3PbGgRO0ht5Rcd/JwWr5AAw2iPY4d/5Lxvybfb6ZttqsKJJUwhg38wpF5CCmlpQ
+cAAAAhAPPNyUAnjvFr+eT/7t/IyjuQQd/aLFiTY92LB9gIjyrMAAAAAAECAwQFBgc=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ecdsa_n_pw b/regress/unittests/sshkey/testdata/ecdsa_n_pw
new file mode 100644
index 0000000..36b7fa7
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_n_pw
@@ -0,0 +1,9 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABC4UwEov5
+z0RrCm7AMCxbuiAAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlz
+dHAyNTYAAABBBAxZW5ZDq1vcnSlYbTPvQGN3PbGgRO0ht5Rcd/JwWr5AAw2iPY4d/5Lxvy
+bfb6ZttqsKJJUwhg38wpF5CCmlpQcAAACgbCnAklQTHrf5qiHiMxKYwQJ7k/X9mp4fXD4v
+xUbgNZiXSxN26mn8mC2rH+WA6Lk3CexR/hrtLI2ndpBsYu1h6HhVkOwwm3Kd/PMKArCupW
+l6sYEabrT0EghXR/3aDEZvj79hgKSdu3RpayLvMdbCR8k1cg0/mDmR9hicWfeJ61n/IH05
+tUR268+0BVRW9kDhh/cuv8tVY4L09jCCQ6CpsA==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ecdsa_sk1 b/regress/unittests/sshkey/testdata/ecdsa_sk1
new file mode 100644
index 0000000..b51fb73
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_sk1
@@ -0,0 +1,13 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAfwAAACJzay1lY2
+RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQRnVT5Cji1D
+Ge2+q2X0vATh6LYnODV+DJrshJorr5GnipW29RfuaDXs0WB6XBej9dOLazVRDjQrtV19Qg
+O6cfkFAAAABHNzaDoAAAGQuPdnP7j3Zz8AAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBv
+cGVuc3NoLmNvbQAAAAhuaXN0cDI1NgAAAEEEZ1U+Qo4tQxntvqtl9LwE4ei2Jzg1fgya7I
+SaK6+Rp4qVtvUX7mg17NFgelwXo/XTi2s1UQ40K7VdfUIDunH5BQAAAARzc2g6AQAAAOMt
+LS0tLUJFR0lOIEVDIFBSSVZBVEUgS0VZLS0tLS0KTUhjQ0FRRUVJRURmVFB4YzA0alN5Zk
+Z5NlhoV1pTVlpzcnU5ZFlaSVpTOWhjeVFhcDlVT29Bb0dDQ3FHU000OQpBd0VIb1VRRFFn
+QUVaMVUrUW80dFF4bnR2cXRsOUx3RTRlaTJKemcxZmd5YTdJU2FLNitScDRxVnR2VVg3bW
+cxCjdORmdlbHdYby9YVGkyczFVUTQwSzdWZGZVSUR1bkg1QlE9PQotLS0tLUVORCBFQyBQ
+UklWQVRFIEtFWS0tLS0tCgAAAAAAAAAURUNEU0EtU0sgdGVzdCBrZXkgIzEBAgMEBQ==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ecdsa_sk1-cert.fp b/regress/unittests/sshkey/testdata/ecdsa_sk1-cert.fp
new file mode 100644
index 0000000..d192145
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_sk1-cert.fp
@@ -0,0 +1 @@
+SHA256:Go7HO0CVPYG+BSDSk9ZUJBKGSrtBExp6obTa9iqzIUo
diff --git a/regress/unittests/sshkey/testdata/ecdsa_sk1-cert.pub b/regress/unittests/sshkey/testdata/ecdsa_sk1-cert.pub
new file mode 100644
index 0000000..9586c61
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_sk1-cert.pub
@@ -0,0 +1 @@
+sk-ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAK3NrLWVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgE012YoSBE9hEC2FRzblcSx784JNo2A4g611A7I75YMMAAAAIbmlzdHAyNTYAAABBBGdVPkKOLUMZ7b6rZfS8BOHotic4NX4MmuyEmiuvkaeKlbb1F+5oNezRYHpcF6P104trNVEONCu1XX1CA7px+QUAAAAEc3NoOgAAAAAAAAAHAAAAAgAAAAZqdWxpdXMAAAASAAAABWhvc3QxAAAABWhvc3QyAAAAADaLg2AAAAAATR3h4AAAAAAAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEEAlTtPiWUHubBeCys4Xp0QF91dYARpkyqtCnzg10HRS+ZDgkMrSUvPPG+Ge8iqtnB951MBxDq9FqDFIkhQBYXDAAAAGQAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEkAAAAhALY+eXRJjVGnMk38Sm5S+H5CloNq757ypsoxt+WYoadtAAAAIA42/mAhUfLij1GY7wl+OFrI+icB/t4tGiEUZmhx6Foo ECDSA-SK test key #1
diff --git a/regress/unittests/sshkey/testdata/ecdsa_sk1.fp b/regress/unittests/sshkey/testdata/ecdsa_sk1.fp
new file mode 100644
index 0000000..d192145
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_sk1.fp
@@ -0,0 +1 @@
+SHA256:Go7HO0CVPYG+BSDSk9ZUJBKGSrtBExp6obTa9iqzIUo
diff --git a/regress/unittests/sshkey/testdata/ecdsa_sk1.fp.bb b/regress/unittests/sshkey/testdata/ecdsa_sk1.fp.bb
new file mode 100644
index 0000000..cb9f4dd
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_sk1.fp.bb
@@ -0,0 +1 @@
+xovem-sacac-dageg-vovoc-symyz-bozal-cibiv-cyvat-vylyn-romib-hoxax
diff --git a/regress/unittests/sshkey/testdata/ecdsa_sk1.pub b/regress/unittests/sshkey/testdata/ecdsa_sk1.pub
new file mode 100644
index 0000000..c3b21e0
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_sk1.pub
@@ -0,0 +1 @@
+sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGdVPkKOLUMZ7b6rZfS8BOHotic4NX4MmuyEmiuvkaeKlbb1F+5oNezRYHpcF6P104trNVEONCu1XX1CA7px+QUAAAAEc3NoOg== ECDSA-SK test key #1
diff --git a/regress/unittests/sshkey/testdata/ecdsa_sk1_pw b/regress/unittests/sshkey/testdata/ecdsa_sk1_pw
new file mode 100644
index 0000000..4fa23a7
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_sk1_pw
@@ -0,0 +1,14 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABB6vcJVx2
+cPc7yYRROup8VnAAAAEAAAAAEAAAB/AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3Bl
+bnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGdVPkKOLUMZ7b6rZfS8BOHotic4NX4MmuyEmi
+uvkaeKlbb1F+5oNezRYHpcF6P104trNVEONCu1XX1CA7px+QUAAAAEc3NoOgAAAZBrvCxe
+xFz0bvzXwaPhrUHBeNCoZy/wNKDx0kxlxUPuA+lgOvy5l3lT3yxxd0qj5PQB+NTcuz8AAE
+1f7aSWQNZSifox3COsBGoHV9C8i+glcxiBKheAZD+EBnRGjG8kbcaLhuYDW/I39qNe8lHW
+YSDjmvsT55Hy0IAtVRAXizDoXKNdFPTZisC67WyOSJ3ED7Fy4bfT4ApbvhoFTwjikZBEhy
+LOad1sbJa4eT19TsskYfQdnJf8sjAmCMOZY4ZV0FiNW5XZOp8nIal1oyULPfzTAm6oaeFN
+0ImCSU3U8h4wUQ8q/3XvBWtTKycZaoou0AwPoP0QN95Ywte7FHezNPb/n8KD7k0S6h9XAX
+UcBeCe5NHyov/0ZzA2p737hzm3w+MXGOboTQMu8WFXeGh4m7QH2o8ZJdgBhM5JF17uii+Q
+ppGoPWHf33MXwB3wxWmKZ0ua0f9AVLkQ2DfFszUoBJE/kcHRd4kj4Q4FWXeMBN0GoH8gdE
+gRWIlxn2/FAOce/BFPzzdP87H0jwz7SdcuVO1L
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ecdsa_sk2 b/regress/unittests/sshkey/testdata/ecdsa_sk2
new file mode 100644
index 0000000..19db5a3
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_sk2
@@ -0,0 +1,13 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAfwAAACJzay1lY2
+RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQSTl+SR6rTg
+lOZmcQkCtJ3Pd+lWinezo/gHk4oZdZcTQsmEYs766BlWGuB2Bz3qQRLa6cXsP+4K9kAjAJ
+7zdoFUAAAABHNzaDoAAAGQ1qllJtapZSYAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBv
+cGVuc3NoLmNvbQAAAAhuaXN0cDI1NgAAAEEEk5fkkeq04JTmZnEJArSdz3fpVop3s6P4B5
+OKGXWXE0LJhGLO+ugZVhrgdgc96kES2unF7D/uCvZAIwCe83aBVAAAAARzc2g6AQAAAOMt
+LS0tLUJFR0lOIEVDIFBSSVZBVEUgS0VZLS0tLS0KTUhjQ0FRRUVJSkxwVkxnSTVvdkRlOW
+VMWmZodCs5WWlMaitnam0rTXhHTXg5NndiRWw0Wm9Bb0dDQ3FHU000OQpBd0VIb1VRRFFn
+QUVrNWZra2VxMDRKVG1abkVKQXJTZHozZnBWb3AzczZQNEI1T0tHWFdYRTBMSmhHTE8rdW
+daClZocmdkZ2M5NmtFUzJ1bkY3RC91Q3ZaQUl3Q2U4M2FCVkE9PQotLS0tLUVORCBFQyBQ
+UklWQVRFIEtFWS0tLS0tCgAAAAAAAAAURUNEU0EtU0sgdGVzdCBrZXkgIzIBAgMEBQ==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ecdsa_sk2.fp b/regress/unittests/sshkey/testdata/ecdsa_sk2.fp
new file mode 100644
index 0000000..1bc99ea
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_sk2.fp
@@ -0,0 +1 @@
+SHA256:pz8VkgtRY3r50F4zSuzRlmq9c6vPTpJXLKKOgkyUcKE
diff --git a/regress/unittests/sshkey/testdata/ecdsa_sk2.fp.bb b/regress/unittests/sshkey/testdata/ecdsa_sk2.fp.bb
new file mode 100644
index 0000000..bfee765
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_sk2.fp.bb
@@ -0,0 +1 @@
+xobel-gavur-gorym-pedop-rarob-bunek-gucer-lofeg-syhaf-fylur-zoxix
diff --git a/regress/unittests/sshkey/testdata/ecdsa_sk2.pub b/regress/unittests/sshkey/testdata/ecdsa_sk2.pub
new file mode 100644
index 0000000..2629d95
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ecdsa_sk2.pub
@@ -0,0 +1 @@
+sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBJOX5JHqtOCU5mZxCQK0nc936VaKd7Oj+AeTihl1lxNCyYRizvroGVYa4HYHPepBEtrpxew/7gr2QCMAnvN2gVQAAAAEc3NoOg== ECDSA-SK test key #2
diff --git a/regress/unittests/sshkey/testdata/ed25519_1 b/regress/unittests/sshkey/testdata/ed25519_1
new file mode 100644
index 0000000..6b0ae01
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_1
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBThupGO0X+FLQhbz8CoKPwc7V3JNsQuGtlsgN+F7SMGQAAAJjnj4Ao54+A
+KAAAAAtzc2gtZWQyNTUxOQAAACBThupGO0X+FLQhbz8CoKPwc7V3JNsQuGtlsgN+F7SMGQ
+AAAED3KgoDbjR54V7bdNpfKlQY5m20UK1QaHytkCR+6rZEDFOG6kY7Rf4UtCFvPwKgo/Bz
+tXck2xC4a2WyA34XtIwZAAAAE0VEMjU1MTkgdGVzdCBrZXkgIzEBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ed25519_1-cert.fp b/regress/unittests/sshkey/testdata/ed25519_1-cert.fp
new file mode 100644
index 0000000..a9674e2
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_1-cert.fp
@@ -0,0 +1 @@
+SHA256:L3k/oJubblSY0lB9Ulsl7emDMnRPKm/8udf2ccwk560
diff --git a/regress/unittests/sshkey/testdata/ed25519_1-cert.pub b/regress/unittests/sshkey/testdata/ed25519_1-cert.pub
new file mode 100644
index 0000000..649b4e8
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_1-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIIxzuxl4z3uwAIslne8Huft+1n1IhHAlNbWZkQyyECCGAAAAIFOG6kY7Rf4UtCFvPwKgo/BztXck2xC4a2WyA34XtIwZAAAAAAAAAAgAAAACAAAABmp1bGl1cwAAABIAAAAFaG9zdDEAAAAFaG9zdDIAAAAANowB8AAAAABNHmBwAAAAAAAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACBThupGO0X+FLQhbz8CoKPwc7V3JNsQuGtlsgN+F7SMGQAAAFMAAAALc3NoLWVkMjU1MTkAAABABGTn+Bmz86Ajk+iqKCSdP5NClsYzn4alJd0V5bizhP0Kumc/HbqQfSt684J1WdSzih+EjvnTgBhK9jTBKb90AQ== ED25519 test key #1
diff --git a/regress/unittests/sshkey/testdata/ed25519_1.fp b/regress/unittests/sshkey/testdata/ed25519_1.fp
new file mode 100644
index 0000000..a9674e2
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_1.fp
@@ -0,0 +1 @@
+SHA256:L3k/oJubblSY0lB9Ulsl7emDMnRPKm/8udf2ccwk560
diff --git a/regress/unittests/sshkey/testdata/ed25519_1.fp.bb b/regress/unittests/sshkey/testdata/ed25519_1.fp.bb
new file mode 100644
index 0000000..309f2da
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_1.fp.bb
@@ -0,0 +1 @@
+xubop-rekyd-bakal-nubuf-pahaf-gicuh-logeb-gocif-petod-galip-fuxux
diff --git a/regress/unittests/sshkey/testdata/ed25519_1.pub b/regress/unittests/sshkey/testdata/ed25519_1.pub
new file mode 100644
index 0000000..e533059
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_1.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFOG6kY7Rf4UtCFvPwKgo/BztXck2xC4a2WyA34XtIwZ ED25519 test key #1
diff --git a/regress/unittests/sshkey/testdata/ed25519_1_pw b/regress/unittests/sshkey/testdata/ed25519_1_pw
new file mode 100644
index 0000000..da94d2b
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_1_pw
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDKT56mBA
+tXIMsWqmuuA2gdAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIFOG6kY7Rf4UtCFv
+PwKgo/BztXck2xC4a2WyA34XtIwZAAAAoC13U47yfUOSZJePNUAwWXuFOk3aOKwPM5PMvK
+0zwRnMZZjgn+tsMAYPwhsT3Mx3h5QzvVGFyFEqsiK7j4vAotD+LVQeBN5TwWbUBx4lnoGs
+3iAfYVDakO/gNvVBDDGOqv5kdCc4cgn5HacjHQLKOAx6KzHe7JFn7uCywMdVVQjlpI6LHb
+mHkaKiVX/C2oiRnsoe17HZ8Fxyt3vd1qNM8BE=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ed25519_2 b/regress/unittests/sshkey/testdata/ed25519_2
new file mode 100644
index 0000000..e4aed63
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_2
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDPVKyLnm3eZE0lm0IfM3Uy9AsdGSBtozcoCt21blYBCwAAAJix1mBGsdZg
+RgAAAAtzc2gtZWQyNTUxOQAAACDPVKyLnm3eZE0lm0IfM3Uy9AsdGSBtozcoCt21blYBCw
+AAAECZEQHXs18o3DKjhUYaTyt+bUbhqfMeqmsKjYyFvzGVgs9UrIuebd5kTSWbQh8zdTL0
+Cx0ZIG2jNygK3bVuVgELAAAAE0VEMjU1MTkgdGVzdCBrZXkgIzEBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ed25519_2.fp b/regress/unittests/sshkey/testdata/ed25519_2.fp
new file mode 100644
index 0000000..0496626
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_2.fp
@@ -0,0 +1 @@
+SHA256:vMbaARqVciRgXyZPNHDo+P5p5WK5yWG1Oo6VC35Bomw
diff --git a/regress/unittests/sshkey/testdata/ed25519_2.fp.bb b/regress/unittests/sshkey/testdata/ed25519_2.fp.bb
new file mode 100644
index 0000000..abba789
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_2.fp.bb
@@ -0,0 +1 @@
+xuces-bapyb-vikob-zesyv-budod-nupip-kebon-tacyc-fofed-lezic-soxax
diff --git a/regress/unittests/sshkey/testdata/ed25519_2.pub b/regress/unittests/sshkey/testdata/ed25519_2.pub
new file mode 100644
index 0000000..af34236
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_2.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM9UrIuebd5kTSWbQh8zdTL0Cx0ZIG2jNygK3bVuVgEL ED25519 test key #1
diff --git a/regress/unittests/sshkey/testdata/ed25519_sk1 b/regress/unittests/sshkey/testdata/ed25519_sk1
new file mode 100644
index 0000000..4196d9c
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_sk1
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAABpzay1zc2
+gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACAhaP5OS1PPOt7uumAvXlDtte9EHbqIT1EZEJ2y
+2v3XMwAAAARzc2g6AAAAuBocY6UaHGOlAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY2
+9tAAAAICFo/k5LU8863u66YC9eUO2170QduohPURkQnbLa/dczAAAABHNzaDoBAAAAQJYq
+lGHhFoA25/q8X/rdTqDAb7dhqs4ehhd/w8x99CwiIWj+TktTzzre7rpgL15Q7bXvRB26iE
+9RGRCdstr91zMAAAAAAAAAFkVEMjU1MTktU0sgdGVzdCBrZXkgIzEBAgM=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ed25519_sk1-cert.fp b/regress/unittests/sshkey/testdata/ed25519_sk1-cert.fp
new file mode 100644
index 0000000..a6bb1a9
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_sk1-cert.fp
@@ -0,0 +1 @@
+SHA256:6WZVJ44bqhAWLVP4Ns0TDkoSQSsZo/h2K+mEvOaNFbw
diff --git a/regress/unittests/sshkey/testdata/ed25519_sk1-cert.pub b/regress/unittests/sshkey/testdata/ed25519_sk1-cert.pub
new file mode 100644
index 0000000..3c72c26
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_sk1-cert.pub
@@ -0,0 +1 @@
+sk-ssh-ed25519-cert-v01@openssh.com AAAAI3NrLXNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIJr7CuMntQKvHoUshx374fJLFEkyxKsEOBA1H6hk5scoAAAAICFo/k5LU8863u66YC9eUO2170QduohPURkQnbLa/dczAAAABHNzaDoAAAAAAAAACAAAAAIAAAAGanVsaXVzAAAAEgAAAAVob3N0MQAAAAVob3N0MgAAAAA2i4NgAAAAAE0d4eAAAAAAAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIOo/0xneV3iM2qWEo5RUwvUYa2bjff292T5vvuXRomGQAAAAUwAAAAtzc2gtZWQyNTUxOQAAAECgsRGLDh1SI3m66MRp9D2iLP4wabQ0OrDgGidk7LsVn2XZHV5jBZN1RtNfe6PBMeVzfRtGUzOg18sO7H7uU+EC ED25519-SK test key #1
diff --git a/regress/unittests/sshkey/testdata/ed25519_sk1.fp b/regress/unittests/sshkey/testdata/ed25519_sk1.fp
new file mode 100644
index 0000000..a6bb1a9
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_sk1.fp
@@ -0,0 +1 @@
+SHA256:6WZVJ44bqhAWLVP4Ns0TDkoSQSsZo/h2K+mEvOaNFbw
diff --git a/regress/unittests/sshkey/testdata/ed25519_sk1.fp.bb b/regress/unittests/sshkey/testdata/ed25519_sk1.fp.bb
new file mode 100644
index 0000000..1bfe20a
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_sk1.fp.bb
@@ -0,0 +1 @@
+xucac-vusip-tydoz-dudad-nerif-raran-tezun-cogyd-pamoh-bahef-ruxix
diff --git a/regress/unittests/sshkey/testdata/ed25519_sk1.pub b/regress/unittests/sshkey/testdata/ed25519_sk1.pub
new file mode 100644
index 0000000..60fe00c
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_sk1.pub
@@ -0,0 +1 @@
+sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAICFo/k5LU8863u66YC9eUO2170QduohPURkQnbLa/dczAAAABHNzaDo= ED25519-SK test key #1
diff --git a/regress/unittests/sshkey/testdata/ed25519_sk1_pw b/regress/unittests/sshkey/testdata/ed25519_sk1_pw
new file mode 100644
index 0000000..1c29ff0
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_sk1_pw
@@ -0,0 +1,9 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDr5R9Yf/
+ucEh0Ns6c34tcIAAAAEAAAAAEAAABKAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t
+AAAAICFo/k5LU8863u66YC9eUO2170QduohPURkQnbLa/dczAAAABHNzaDoAAADA2T6owx
+OSgKz4DvLnS3UJ/renbuew5mbkIWB1/y8xd3y5Usm08iUCAlKxep9dVRQvmyoTrc/7rHOM
+DkokNw+WgKambnlYT/9QfqViZ9iCBtbdmhLM6ksUCgQefvquRyXoJxlWstjXUll6Ru+ZbT
+H//Ss8C1bYtAiXR68OQ+rhDrvQxA9P8J1sGIlkuV3h8YXddSpyBW2Sn0LTHHBXYZo86cXZ
+G4Lnc8aGYm65eqdHgkfRmht3eS8DTdzEBfBNH5Ml
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ed25519_sk2 b/regress/unittests/sshkey/testdata/ed25519_sk2
new file mode 100644
index 0000000..b9b7489
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_sk2
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAABpzay1zc2
+gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACAV8fu1Sc31QLK2R/zGPdN3ve5xuFvDc7mEAWxb
+aI+YcwAAAARzc2g6AAAAuJCMX5uQjF+bAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY2
+9tAAAAIBXx+7VJzfVAsrZH/MY903e97nG4W8NzuYQBbFtoj5hzAAAABHNzaDoBAAAAQObE
+PajcKI1W30EKOhBb6u+Fgx464kf7EjnqDSg4l7gAFfH7tUnN9UCytkf8xj3Td73ucbhbw3
+O5hAFsW2iPmHMAAAAAAAAAFkVEMjU1MTktU0sgdGVzdCBrZXkgIzIBAgM=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/ed25519_sk2.fp b/regress/unittests/sshkey/testdata/ed25519_sk2.fp
new file mode 100644
index 0000000..1c4369a
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_sk2.fp
@@ -0,0 +1 @@
+SHA256:b9BVPS5vuU4yu/FgweojLLg6zbfmBBoWLUgibdxxsoo
diff --git a/regress/unittests/sshkey/testdata/ed25519_sk2.fp.bb b/regress/unittests/sshkey/testdata/ed25519_sk2.fp.bb
new file mode 100644
index 0000000..f5fd9ef
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_sk2.fp.bb
@@ -0,0 +1 @@
+xemac-tizim-dihep-supar-zupib-cukak-pasis-febeg-dyguv-hutec-dyxox
diff --git a/regress/unittests/sshkey/testdata/ed25519_sk2.pub b/regress/unittests/sshkey/testdata/ed25519_sk2.pub
new file mode 100644
index 0000000..c7ed9f5
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/ed25519_sk2.pub
@@ -0,0 +1 @@
+sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIBXx+7VJzfVAsrZH/MY903e97nG4W8NzuYQBbFtoj5hzAAAABHNzaDo= ED25519-SK test key #2
diff --git a/regress/unittests/sshkey/testdata/pw b/regress/unittests/sshkey/testdata/pw
new file mode 100644
index 0000000..8a1dff9
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/pw
@@ -0,0 +1 @@
+mekmitasdigoat
diff --git a/regress/unittests/sshkey/testdata/rsa_1 b/regress/unittests/sshkey/testdata/rsa_1
new file mode 100644
index 0000000..5de3f84
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDLV5lUTt7FrADseB/CGhEZzpoojjEW5y8+ePvLppmK3MmMI18u
+d6vxzpK3bwZLYkVSyfJYI0HmIuGhdu7yMrW6wb84gbq8C31Xoe9EORcIUuGSvDKd
+NSM1SjlhDquRblDFB8kToqXyx1lqrXecXylxIUOL0jE+u0rU1967pDJx+wIDAQAB
+AoGAXyj5mpjmbD+YlxGIWz/zrM4hGsWgd4VteKEJxT6MMI4uzCRpkMd0ck8oHiwZ
+GAI/SwUzIsgtONQuH3AXVsUgghW4Ynn+8ksEv0IZ918WDMDwqvqkyrVzsOsZzqYj
+Pf8DUDKCpwFjnlknJ04yvWBZvVhWtY4OiZ8GV0Ttsu3k+GECQQD1YHfvBb5FdJBv
+Uhde2Il+jaFia8mwVVNNaiD2ECxXx6CzGz54ZLEB9NPVfDUZK8lJ4UJDqelWNh3i
+PF3RefWDAkEA1CVBzAFL4mNwpleVPzrfy69xP3gWOa26MxM/GE6zx9jC7HgQ3KPa
+WKdG/FuHs085aTRDaDLmGcZ8IvMuu7NgKQJAcIOKmxR0Gd8IN7NZugjqixggb0Pj
+mLKXXwESGiJyYtHL0zTj4Uqyi6Ya2GJ66o7UXscmnmYz828fJtTtZBdbRwJBALfi
+C2QvA32Zv/0PEXibKXy996WSC4G3ShwXZKtHHKHvCxY5BDSbehk59VesZrVPyG2e
+NYdOBxD0cIlCzJE56/ECQAndVkxvO8hwyEFGGwF3faHIAe/OxVb+MjaU25//Pe1/
+h/e6tlCk4w9CODpyV685gV394eYwMcGDcIkipTNUDZs=
+-----END RSA PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/rsa_1-cert.fp b/regress/unittests/sshkey/testdata/rsa_1-cert.fp
new file mode 100644
index 0000000..79f380a
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1-cert.fp
@@ -0,0 +1 @@
+SHA256:l6itGumSMcRBBAFteCgmjQBIXqLK/jFGUH3viHX1RmE
diff --git a/regress/unittests/sshkey/testdata/rsa_1-cert.pub b/regress/unittests/sshkey/testdata/rsa_1-cert.pub
new file mode 100644
index 0000000..3bacf3c
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1-cert.pub
@@ -0,0 +1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg98LhS2EHxLOWCLopZPwHdg/RJXusnkOqQXSc9R7aITkAAAADAQABAAAAgQDLV5lUTt7FrADseB/CGhEZzpoojjEW5y8+ePvLppmK3MmMI18ud6vxzpK3bwZLYkVSyfJYI0HmIuGhdu7yMrW6wb84gbq8C31Xoe9EORcIUuGSvDKdNSM1SjlhDquRblDFB8kToqXyx1lqrXecXylxIUOL0jE+u0rU1967pDJx+wAAAAAAAAAFAAAAAgAAAAZqdWxpdXMAAAASAAAABWhvc3QxAAAABWhvc3QyAAAAADaMAfAAAAAATR5gcAAAAAAAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgU4bqRjtF/hS0IW8/AqCj8HO1dyTbELhrZbIDfhe0jBkAAABTAAAAC3NzaC1lZDI1NTE5AAAAQI3QGlUCzC07KorupxpDkkGy6tniaZ8EvBflzvv+itXWNchGvfUeHmVT6aX0sRqehdz/lR+GmXRoZBhofwh0qAM= RSA test key #1
diff --git a/regress/unittests/sshkey/testdata/rsa_1.fp b/regress/unittests/sshkey/testdata/rsa_1.fp
new file mode 100644
index 0000000..79f380a
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1.fp
@@ -0,0 +1 @@
+SHA256:l6itGumSMcRBBAFteCgmjQBIXqLK/jFGUH3viHX1RmE
diff --git a/regress/unittests/sshkey/testdata/rsa_1.fp.bb b/regress/unittests/sshkey/testdata/rsa_1.fp.bb
new file mode 100644
index 0000000..45bacd5
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1.fp.bb
@@ -0,0 +1 @@
+xosis-fodod-votot-dibum-ryvac-rediz-naruf-votun-kevis-halis-gexux
diff --git a/regress/unittests/sshkey/testdata/rsa_1.param.n b/regress/unittests/sshkey/testdata/rsa_1.param.n
new file mode 100644
index 0000000..4933712
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1.param.n
@@ -0,0 +1 @@
+00cb5799544edec5ac00ec781fc21a1119ce9a288e3116e72f3e78fbcba6998adcc98c235f2e77abf1ce92b76f064b624552c9f2582341e622e1a176eef232b5bac1bf3881babc0b7d57a1ef4439170852e192bc329d3523354a39610eab916e50c507c913a2a5f2c7596aad779c5f297121438bd2313ebb4ad4d7debba43271fb
diff --git a/regress/unittests/sshkey/testdata/rsa_1.param.p b/regress/unittests/sshkey/testdata/rsa_1.param.p
new file mode 100644
index 0000000..4783d21
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1.param.p
@@ -0,0 +1 @@
+00f56077ef05be4574906f52175ed8897e8da1626bc9b055534d6a20f6102c57c7a0b31b3e7864b101f4d3d57c35192bc949e14243a9e956361de23c5dd179f583
diff --git a/regress/unittests/sshkey/testdata/rsa_1.param.q b/regress/unittests/sshkey/testdata/rsa_1.param.q
new file mode 100644
index 0000000..00fc8a2
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1.param.q
@@ -0,0 +1 @@
+00d42541cc014be26370a657953f3adfcbaf713f781639adba33133f184eb3c7d8c2ec7810dca3da58a746fc5b87b34f396934436832e619c67c22f32ebbb36029
diff --git a/regress/unittests/sshkey/testdata/rsa_1.pub b/regress/unittests/sshkey/testdata/rsa_1.pub
new file mode 100644
index 0000000..23ef872
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDLV5lUTt7FrADseB/CGhEZzpoojjEW5y8+ePvLppmK3MmMI18ud6vxzpK3bwZLYkVSyfJYI0HmIuGhdu7yMrW6wb84gbq8C31Xoe9EORcIUuGSvDKdNSM1SjlhDquRblDFB8kToqXyx1lqrXecXylxIUOL0jE+u0rU1967pDJx+w== RSA test key #1
diff --git a/regress/unittests/sshkey/testdata/rsa_1_pw b/regress/unittests/sshkey/testdata/rsa_1_pw
new file mode 100644
index 0000000..b4c0674
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1_pw
@@ -0,0 +1,18 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,0C3F819F6EEA66A471BAEEDDA8171606
+
+AhQNxgw7Z2un3dpm6KPHF1u5qVvOczm0yiTyPK4U11B3TTRhXOHdzPLAcKMX71Xq
+fmLm2/JIZATUbLTaysLKIQlmAgtpmXoKLv9b90R3AXLophgToZzOLpvlQTCt+y9G
+0E3QQZG/LFy9BLNyw6uD5cy0RHT3FQb5VQDwfBvR/I+K3qWBFLlb7Rw9bCujYczu
+D3bimcDj/k6YkrWVsEa81Ch5RF2RClOYufti6bsvc4xIsB0Kd++vokER+kXFuQqf
+Tl0Jz+SG0kr9QtjVvkhBtSxzJ6/olAosoUySQ5hqsB8iECufBgp1KelXqsHFJQXy
+gCvVmGiivFUinX0rKOuWCHTplsSKQ9BnPSwDAAs8A7ZLcTXcLs/hMQ5r6fmOYfNN
+YthhjZyE2ciJO0lydGJUJMb5aJUak0rl+uINRlYCHTRLVwmCOmpfqz9SfcJb1ieU
+4Us8NR+pXJar4U0+C2wVlNJkAdpL6GvYxN6vp7vLa+BiFwIZOQozswacIZk/ScXm
+QL9rmWug51RCmDeenX46WTEZeB0o0+xi60sDEDhhe4+iNYcJu5L0BJ5lqRFe3I5n
+HRRv1mBEjbF2fDcg/ChYfOXsc4gDivH2nObabeASuMFZyadmXfA8tnXRZf+7Wuy/
+LZGYbM2xLeEyV3ss16WBHuIqexDt04OEZvs0jN90zj6Yv7qKCB975bdOcuKkN2Nn
+n9lA11R2pgsCs6COp9rYiWXkXZeDf3sW6kdcEV+/SzkVsv4JlHcsIzgk4WGVF/E/
+ZkU4J9AvSdJPzEQDM+yszp0eeUow4+SAgpuNTqZiUO/2UUVbsr3qvlYMoCixhFAN
+-----END RSA PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/rsa_1_sha1 b/regress/unittests/sshkey/testdata/rsa_1_sha1
new file mode 100644
index 0000000..5de3f84
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1_sha1
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDLV5lUTt7FrADseB/CGhEZzpoojjEW5y8+ePvLppmK3MmMI18u
+d6vxzpK3bwZLYkVSyfJYI0HmIuGhdu7yMrW6wb84gbq8C31Xoe9EORcIUuGSvDKd
+NSM1SjlhDquRblDFB8kToqXyx1lqrXecXylxIUOL0jE+u0rU1967pDJx+wIDAQAB
+AoGAXyj5mpjmbD+YlxGIWz/zrM4hGsWgd4VteKEJxT6MMI4uzCRpkMd0ck8oHiwZ
+GAI/SwUzIsgtONQuH3AXVsUgghW4Ynn+8ksEv0IZ918WDMDwqvqkyrVzsOsZzqYj
+Pf8DUDKCpwFjnlknJ04yvWBZvVhWtY4OiZ8GV0Ttsu3k+GECQQD1YHfvBb5FdJBv
+Uhde2Il+jaFia8mwVVNNaiD2ECxXx6CzGz54ZLEB9NPVfDUZK8lJ4UJDqelWNh3i
+PF3RefWDAkEA1CVBzAFL4mNwpleVPzrfy69xP3gWOa26MxM/GE6zx9jC7HgQ3KPa
+WKdG/FuHs085aTRDaDLmGcZ8IvMuu7NgKQJAcIOKmxR0Gd8IN7NZugjqixggb0Pj
+mLKXXwESGiJyYtHL0zTj4Uqyi6Ya2GJ66o7UXscmnmYz828fJtTtZBdbRwJBALfi
+C2QvA32Zv/0PEXibKXy996WSC4G3ShwXZKtHHKHvCxY5BDSbehk59VesZrVPyG2e
+NYdOBxD0cIlCzJE56/ECQAndVkxvO8hwyEFGGwF3faHIAe/OxVb+MjaU25//Pe1/
+h/e6tlCk4w9CODpyV685gV394eYwMcGDcIkipTNUDZs=
+-----END RSA PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/rsa_1_sha1-cert.pub b/regress/unittests/sshkey/testdata/rsa_1_sha1-cert.pub
new file mode 100644
index 0000000..ff49d75
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1_sha1-cert.pub
@@ -0,0 +1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgy5PGFfSaEuSuXsjvKlMZGXYD0xlnqdZftuW9tMkUYz4AAAADAQABAAAAgQDLV5lUTt7FrADseB/CGhEZzpoojjEW5y8+ePvLppmK3MmMI18ud6vxzpK3bwZLYkVSyfJYI0HmIuGhdu7yMrW6wb84gbq8C31Xoe9EORcIUuGSvDKdNSM1SjlhDquRblDFB8kToqXyx1lqrXecXylxIUOL0jE+u0rU1967pDJx+wAAAAAAAAABAAAAAQAAAARodWdvAAAAEgAAAAV1c2VyMQAAAAV1c2VyMgAAAAA2i4NgAAAAAE0d4eAAAABEAAAADWZvcmNlLWNvbW1hbmQAAAALAAAABy9iaW4vbHMAAAAOc291cmNlLWFkZHJlc3MAAAAOAAAACjEwLjAuMC4wLzgAAABkAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQD00RRenvxICSYvj54CPiYHM86OT5xwI9XORNH6Zkl3JPCQkAEdQ3hyfhraROaHsSv43wJcKyKrEg5XUZ8fZ/BoKIGU4Rd5AmL9wyPGv2RVY7gWELqXVSpu89R2tQJRmMVMD38CH0wqCTuoZirlKMTen6yfgYuFEpuqar0uOIeAyaQG6/9rVKWK36tcfM7YXx8fmGSN4eK/JhWDDjlo28YJ7ZFF9umh5baZG2Ai/vL3BJ7C3pqaEQNdKj8XqaSoDvFWKfOujk1620Rcuj3W0D0dvp/rH8xz8YkM1dMqGlYIZ4nrF5acB58Nk5FYBjtj1hu4DGEQlWL1Avk1agU4DQLrAAABDwAAAAdzc2gtcnNhAAABAF5BtPY8FbmIekK/zNq6/Lp5agKT5zEVxqAyZKhp75bLRP+kOMZBVB9ZWrekZk6IAVAOCZGQzTsD4mxIQsxBLl8k5hvEWb90/+w9/BzW9ScOGQe+y0COa0QWWR7L3k1S8WX2oAGvtDWOj7Md85nij4ZSU9/QQQFVDF8VilWPSMxUf/3I1fqyDq7AWcZkGk/bFUN6K6RsCSxIPlGmKt0IauyvSMI2IT0XeRT242RngeeUW8vFrn9TXy9YxJRW+cSeLKCuu8agBYyQMXWQ+q39eZZqVYSoo7nFEEhtaLs8d6jzgGkcE9wGJ9KLgfY/mG2vX3gI4IzncKkVJRoeiDzXFIk= RSA test key #1
diff --git a/regress/unittests/sshkey/testdata/rsa_1_sha1.pub b/regress/unittests/sshkey/testdata/rsa_1_sha1.pub
new file mode 100644
index 0000000..23ef872
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1_sha1.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDLV5lUTt7FrADseB/CGhEZzpoojjEW5y8+ePvLppmK3MmMI18ud6vxzpK3bwZLYkVSyfJYI0HmIuGhdu7yMrW6wb84gbq8C31Xoe9EORcIUuGSvDKdNSM1SjlhDquRblDFB8kToqXyx1lqrXecXylxIUOL0jE+u0rU1967pDJx+w== RSA test key #1
diff --git a/regress/unittests/sshkey/testdata/rsa_1_sha512 b/regress/unittests/sshkey/testdata/rsa_1_sha512
new file mode 100644
index 0000000..5de3f84
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1_sha512
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDLV5lUTt7FrADseB/CGhEZzpoojjEW5y8+ePvLppmK3MmMI18u
+d6vxzpK3bwZLYkVSyfJYI0HmIuGhdu7yMrW6wb84gbq8C31Xoe9EORcIUuGSvDKd
+NSM1SjlhDquRblDFB8kToqXyx1lqrXecXylxIUOL0jE+u0rU1967pDJx+wIDAQAB
+AoGAXyj5mpjmbD+YlxGIWz/zrM4hGsWgd4VteKEJxT6MMI4uzCRpkMd0ck8oHiwZ
+GAI/SwUzIsgtONQuH3AXVsUgghW4Ynn+8ksEv0IZ918WDMDwqvqkyrVzsOsZzqYj
+Pf8DUDKCpwFjnlknJ04yvWBZvVhWtY4OiZ8GV0Ttsu3k+GECQQD1YHfvBb5FdJBv
+Uhde2Il+jaFia8mwVVNNaiD2ECxXx6CzGz54ZLEB9NPVfDUZK8lJ4UJDqelWNh3i
+PF3RefWDAkEA1CVBzAFL4mNwpleVPzrfy69xP3gWOa26MxM/GE6zx9jC7HgQ3KPa
+WKdG/FuHs085aTRDaDLmGcZ8IvMuu7NgKQJAcIOKmxR0Gd8IN7NZugjqixggb0Pj
+mLKXXwESGiJyYtHL0zTj4Uqyi6Ya2GJ66o7UXscmnmYz828fJtTtZBdbRwJBALfi
+C2QvA32Zv/0PEXibKXy996WSC4G3ShwXZKtHHKHvCxY5BDSbehk59VesZrVPyG2e
+NYdOBxD0cIlCzJE56/ECQAndVkxvO8hwyEFGGwF3faHIAe/OxVb+MjaU25//Pe1/
+h/e6tlCk4w9CODpyV685gV394eYwMcGDcIkipTNUDZs=
+-----END RSA PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/rsa_1_sha512-cert.pub b/regress/unittests/sshkey/testdata/rsa_1_sha512-cert.pub
new file mode 100644
index 0000000..4745196
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1_sha512-cert.pub
@@ -0,0 +1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg/bUEmnMYHxlv1N7iXvnYPYdzDjlTRKoaIGEPkaQQQDwAAAADAQABAAAAgQDLV5lUTt7FrADseB/CGhEZzpoojjEW5y8+ePvLppmK3MmMI18ud6vxzpK3bwZLYkVSyfJYI0HmIuGhdu7yMrW6wb84gbq8C31Xoe9EORcIUuGSvDKdNSM1SjlhDquRblDFB8kToqXyx1lqrXecXylxIUOL0jE+u0rU1967pDJx+wAAAAAAAAABAAAAAQAAAARodWdvAAAAEgAAAAV1c2VyMQAAAAV1c2VyMgAAAAA2i4NgAAAAAE0d4eAAAABEAAAADWZvcmNlLWNvbW1hbmQAAAALAAAABy9iaW4vbHMAAAAOc291cmNlLWFkZHJlc3MAAAAOAAAACjEwLjAuMC4wLzgAAABkAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQD00RRenvxICSYvj54CPiYHM86OT5xwI9XORNH6Zkl3JPCQkAEdQ3hyfhraROaHsSv43wJcKyKrEg5XUZ8fZ/BoKIGU4Rd5AmL9wyPGv2RVY7gWELqXVSpu89R2tQJRmMVMD38CH0wqCTuoZirlKMTen6yfgYuFEpuqar0uOIeAyaQG6/9rVKWK36tcfM7YXx8fmGSN4eK/JhWDDjlo28YJ7ZFF9umh5baZG2Ai/vL3BJ7C3pqaEQNdKj8XqaSoDvFWKfOujk1620Rcuj3W0D0dvp/rH8xz8YkM1dMqGlYIZ4nrF5acB58Nk5FYBjtj1hu4DGEQlWL1Avk1agU4DQLrAAABFAAAAAxyc2Etc2hhMi01MTIAAAEA7/GoZsJqrq4xYotsRbpM8arZDjCzT6kohXeD/GVy26s5E/YWXRYCrOMIzSZxjuN5rAaNRW8ffxq14JyI94566Kg2OeoxQ6rK/dTqkk7I1RyypSXunT3I4++RPs1Q+hu9eS/WBzur0/D3dMejhuc3IBg6iB0481I4pGBGcD8/KjQFfhlCuGVXwB1ALk2zfXFT1HYYrs6bYZuQqjgvArnjYJ0do3fTSDC20/ydV4BHnI3fVAY2THVjX45V2ppPadl/rpczaJqW1ZtpnpJkV8Un316stQSD0xLHUDjp89O6d9Yq5S0kDdfwTRJIPm9f2cGNakJwN5qzmmmdDroRKODYcg== RSA test key #1
diff --git a/regress/unittests/sshkey/testdata/rsa_1_sha512.pub b/regress/unittests/sshkey/testdata/rsa_1_sha512.pub
new file mode 100644
index 0000000..23ef872
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_1_sha512.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDLV5lUTt7FrADseB/CGhEZzpoojjEW5y8+ePvLppmK3MmMI18ud6vxzpK3bwZLYkVSyfJYI0HmIuGhdu7yMrW6wb84gbq8C31Xoe9EORcIUuGSvDKdNSM1SjlhDquRblDFB8kToqXyx1lqrXecXylxIUOL0jE+u0rU1967pDJx+w== RSA test key #1
diff --git a/regress/unittests/sshkey/testdata/rsa_2 b/regress/unittests/sshkey/testdata/rsa_2
new file mode 100644
index 0000000..2441d52
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_2
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA9NEUXp78SAkmL4+eAj4mBzPOjk+ccCPVzkTR+mZJdyTwkJAB
+HUN4cn4a2kTmh7Er+N8CXCsiqxIOV1GfH2fwaCiBlOEXeQJi/cMjxr9kVWO4FhC6
+l1UqbvPUdrUCUZjFTA9/Ah9MKgk7qGYq5SjE3p+sn4GLhRKbqmq9LjiHgMmkBuv/
+a1Slit+rXHzO2F8fH5hkjeHivyYVgw45aNvGCe2RRfbpoeW2mRtgIv7y9wSewt6a
+mhEDXSo/F6mkqA7xVinzro5NettEXLo91tA9Hb6f6x/Mc/GJDNXTKhpWCGeJ6xeW
+nAefDZORWAY7Y9YbuAxhEJVi9QL5NWoFOA0C6wIDAQABAoIBAQDtRGVVfwhKWHOl
+zK76xXjdqhwaWJXpKRHiI1jOMawpyKdNtAMgdW+apxUnTXePMurG/HuxEC09VvaH
+MhfhvD6G9BsCS1UQdnuyLRnTWVLIXyjeWcA9QtEpTy8vDSb+Je2xVaNmTybl5qTn
+BH22Mtj6Wg5XWJn7kplDhMdssGTDLsSCMw/rcxe9iT2qOKyltQal23RHzR7SijGp
+QTtBp2SDGhvMZcyGuyMqJ084W8sdJpbyVzdDim2iaZdHlk7uvW2n0HcJ56I6yhIq
+2U8wfgEEwydGVGHgmQNJ/n+SiT/hv6g5ebhDS46X9F9m5CHDwhdr0DrhPBVSsdhl
+1HeJ0+FhAoGBAPuC3uNHToiJis688juKlwc3SQ6ger5ffAg3yaNhEcpHkvOtdZlF
+/CfX94xazMov/YqFwkvpSSdKsX+PeXuaqnb1hPKNYX5t45U9RjB/ox7BIQj/2rPx
+Bfs99UFW9HKP4HsVmLu1xeJg1Pc9iylTK/xrnwfYiZ+H7IGVccizjnqHAoGBAPkv
+n1flAdxBzJH/O0rXoig2EtZsDRMPY51MGDdqVOW14ZOfTVlmu0OSnkSKQm2twfro
+TPDVb2TY3wTRutz8H9yOFW1c1Nz4YOyTb8FmJhE2FWAQ9t8QpwUlhn15if72dS/Y
+22+vP+AYu7wfqGL7QVVEXho5hGjXi053iEvfXBl9AoGAeZISpo1LGphRLgkKlVky
+E1zXxWgwrGB/FYHRx1UeQkZCc+K+Wy4G6kNr9r3VC04TIafx+Lt0jrd+AIibUfG6
+v/GBJ7TLEU+QmAycJskrUaxMiYsSbbPtDjoumDytv8pn2VbhEqqUUg44IqHu6DS5
+qDNlFWfHbgNHgIN6EmcoUXUCgYEAi2G57X4pRjx/4wIy9jAbggaNDuctgQXQoIGZ
+4hVWG49a+CnZKDKweKGgaZI0igjxQhmCQAwC3RP520Y9EbLtV38aOSv93QQJowrt
+Le6nSGVKG4whqrAz3EsbKUA8kiLldbgFNjl+ryjmidnjZEpKRxmQ0XZuu/4k6+Us
+ldQAPjkCgYBwjSm5eDUtK2eEPaBtbJykV05CTv5rn6CKC9L7ZBTkCcdU1hxeqe99
+wb22decnNawGRP1a5cGwqKJPOfkgybJVkdr6aqQW8ClzdFSaenjzs+nVW+T9JTXf
+9lFpIZg5kN/geld3B9B4C99riTM0jg9hbe2RQvpLRTrZbnWMA1XoRw==
+-----END RSA PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/rsa_2.fp b/regress/unittests/sshkey/testdata/rsa_2.fp
new file mode 100644
index 0000000..4659639
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_2.fp
@@ -0,0 +1 @@
+SHA256:NoQh0XBUuYUSWqnzOzOBnfpgJTRWLMj7BlWAb8IbjeE
diff --git a/regress/unittests/sshkey/testdata/rsa_2.fp.bb b/regress/unittests/sshkey/testdata/rsa_2.fp.bb
new file mode 100644
index 0000000..e9d1e4a
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_2.fp.bb
@@ -0,0 +1 @@
+xogit-gupof-mydon-hocep-zuval-feson-rarif-cefar-tobar-ryvap-kuxex
diff --git a/regress/unittests/sshkey/testdata/rsa_2.param.n b/regress/unittests/sshkey/testdata/rsa_2.param.n
new file mode 100644
index 0000000..a669dbf
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_2.param.n
@@ -0,0 +1 @@
+00f4d1145e9efc4809262f8f9e023e260733ce8e4f9c7023d5ce44d1fa66497724f09090011d4378727e1ada44e687b12bf8df025c2b22ab120e57519f1f67f068288194e117790262fdc323c6bf645563b81610ba97552a6ef3d476b5025198c54c0f7f021f4c2a093ba8662ae528c4de9fac9f818b85129baa6abd2e388780c9a406ebff6b54a58adfab5c7cced85f1f1f98648de1e2bf2615830e3968dbc609ed9145f6e9a1e5b6991b6022fef2f7049ec2de9a9a11035d2a3f17a9a4a80ef15629f3ae8e4d7adb445cba3dd6d03d1dbe9feb1fcc73f1890cd5d32a1a56086789eb17969c079f0d939158063b63d61bb80c61109562f502f9356a05380d02eb
diff --git a/regress/unittests/sshkey/testdata/rsa_2.param.p b/regress/unittests/sshkey/testdata/rsa_2.param.p
new file mode 100644
index 0000000..be7c1c3
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_2.param.p
@@ -0,0 +1 @@
+00fb82dee3474e88898acebcf23b8a970737490ea07abe5f7c0837c9a36111ca4792f3ad759945fc27d7f78c5accca2ffd8a85c24be949274ab17f8f797b9aaa76f584f28d617e6de3953d46307fa31ec12108ffdab3f105fb3df54156f4728fe07b1598bbb5c5e260d4f73d8b29532bfc6b9f07d8899f87ec819571c8b38e7a87
diff --git a/regress/unittests/sshkey/testdata/rsa_2.param.q b/regress/unittests/sshkey/testdata/rsa_2.param.q
new file mode 100644
index 0000000..6f2c542
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_2.param.q
@@ -0,0 +1 @@
+00f92f9f57e501dc41cc91ff3b4ad7a2283612d66c0d130f639d4c18376a54e5b5e1939f4d5966bb43929e448a426dadc1fae84cf0d56f64d8df04d1badcfc1fdc8e156d5cd4dcf860ec936fc166261136156010f6df10a70525867d7989fef6752fd8db6faf3fe018bbbc1fa862fb4155445e1a398468d78b4e77884bdf5c197d
diff --git a/regress/unittests/sshkey/testdata/rsa_2.pub b/regress/unittests/sshkey/testdata/rsa_2.pub
new file mode 100644
index 0000000..3322fbc
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_2.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD00RRenvxICSYvj54CPiYHM86OT5xwI9XORNH6Zkl3JPCQkAEdQ3hyfhraROaHsSv43wJcKyKrEg5XUZ8fZ/BoKIGU4Rd5AmL9wyPGv2RVY7gWELqXVSpu89R2tQJRmMVMD38CH0wqCTuoZirlKMTen6yfgYuFEpuqar0uOIeAyaQG6/9rVKWK36tcfM7YXx8fmGSN4eK/JhWDDjlo28YJ7ZFF9umh5baZG2Ai/vL3BJ7C3pqaEQNdKj8XqaSoDvFWKfOujk1620Rcuj3W0D0dvp/rH8xz8YkM1dMqGlYIZ4nrF5acB58Nk5FYBjtj1hu4DGEQlWL1Avk1agU4DQLr RSA test key #2
diff --git a/regress/unittests/sshkey/testdata/rsa_n b/regress/unittests/sshkey/testdata/rsa_n
new file mode 100644
index 0000000..b8e585e
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_n
@@ -0,0 +1,16 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAIEAy1eZVE7exawA7HgfwhoRGc6aKI4xFucvPnj7y6aZitzJjCNfLner
+8c6St28GS2JFUsnyWCNB5iLhoXbu8jK1usG/OIG6vAt9V6HvRDkXCFLhkrwynTUjNUo5YQ
+6rkW5QxQfJE6Kl8sdZaq13nF8pcSFDi9IxPrtK1Nfeu6QycfsAAAH4to4I7raOCO4AAAAH
+c3NoLXJzYQAAAIEAy1eZVE7exawA7HgfwhoRGc6aKI4xFucvPnj7y6aZitzJjCNfLner8c
+6St28GS2JFUsnyWCNB5iLhoXbu8jK1usG/OIG6vAt9V6HvRDkXCFLhkrwynTUjNUo5YQ6r
+kW5QxQfJE6Kl8sdZaq13nF8pcSFDi9IxPrtK1Nfeu6QycfsAAAADAQABAAAAgF8o+ZqY5m
+w/mJcRiFs/86zOIRrFoHeFbXihCcU+jDCOLswkaZDHdHJPKB4sGRgCP0sFMyLILTjULh9w
+F1bFIIIVuGJ5/vJLBL9CGfdfFgzA8Kr6pMq1c7DrGc6mIz3/A1AygqcBY55ZJydOMr1gWb
+1YVrWODomfBldE7bLt5PhhAAAAQAndVkxvO8hwyEFGGwF3faHIAe/OxVb+MjaU25//Pe1/
+h/e6tlCk4w9CODpyV685gV394eYwMcGDcIkipTNUDZsAAABBAPVgd+8FvkV0kG9SF17YiX
+6NoWJrybBVU01qIPYQLFfHoLMbPnhksQH009V8NRkryUnhQkOp6VY2HeI8XdF59YMAAABB
+ANQlQcwBS+JjcKZXlT8638uvcT94FjmtujMTPxhOs8fYwux4ENyj2linRvxbh7NPOWk0Q2
+gy5hnGfCLzLruzYCkAAAAAAQID
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/testdata/rsa_n_pw b/regress/unittests/sshkey/testdata/rsa_n_pw
new file mode 100644
index 0000000..dc18373
--- /dev/null
+++ b/regress/unittests/sshkey/testdata/rsa_n_pw
@@ -0,0 +1,17 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABAFw/Wg/V
+I5SAXWj/HJr9qeAAAAEAAAAAEAAACXAAAAB3NzaC1yc2EAAAADAQABAAAAgQDLV5lUTt7F
+rADseB/CGhEZzpoojjEW5y8+ePvLppmK3MmMI18ud6vxzpK3bwZLYkVSyfJYI0HmIuGhdu
+7yMrW6wb84gbq8C31Xoe9EORcIUuGSvDKdNSM1SjlhDquRblDFB8kToqXyx1lqrXecXylx
+IUOL0jE+u0rU1967pDJx+wAAAgD1iSGiMlMJt2VH4kx5yr0wCJS+4UOmX0bxKO7UH5Jcul
+K5eaSe5ZoKE7hTYBaz0K5dRF/0fqLsvVZlE4quDjFLN6Hyavgn2W/QM7SUqBHgRMal9pgH
+LnxX6mFNWJ+4yb7f3bcbVIdgmMm3sT9Xjwaf5xgzNlR2mkUWtFwjyQh6FxUo5apNzqNBwO
+l2Q4xfmyZTp1s++pStQ/su6obXpxnE2Nx5G/D84ZL5iWl+njUy/MvJTazHRbiTSyihU+UA
+mUr5ZNuP3WUYY+h3KVlHpYHJYB7l3AMTKuPMFLhY9V7BJ+DuKPaqBgX4hvRzY0eVQiFr61
+ovjWjvfu1ulx550JqdYCgH2PpP0E89OQne35Cxs9QPThfe8DKojC9YquYh9zmVTvr7kNiE
+Soluk/7oKpQIDaC+/SRk7AJ2e3Cbt1lXyGNn37PuqaaC/apaF/DOD6Yig9aClS7jOUrT96
+56trFAYfHEIKbRCUSMCiM1+x6HOLYf5ROrGE9KxT3kUD9XMsMpTva+cPpHUpbGpXcYE10N
+MyYDz+V5M2/ZoIdEhscJNQ3UnhaZpeEaqcOyNyo90n3Dnaw/WpMDD/kNMGfm8daTaYInnQ
+QnwA2gwlYfpTAqxE71oXgOuGmtA0yqJB4778Xq26Pb+B7/mZZZe6n0FVmiNC+ZG37ZGOw/
+iGL9e2Sxzw==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshkey/tests.c b/regress/unittests/sshkey/tests.c
new file mode 100644
index 0000000..78aa922
--- /dev/null
+++ b/regress/unittests/sshkey/tests.c
@@ -0,0 +1,22 @@
+/* $OpenBSD: tests.c,v 1.1 2014/06/24 01:14:18 djm Exp $ */
+/*
+ * Regress test for sshbuf.h buffer API
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include "../test_helper/test_helper.h"
+
+void sshkey_tests(void);
+void sshkey_file_tests(void);
+void sshkey_fuzz_tests(void);
+
+void
+tests(void)
+{
+ sshkey_tests();
+ sshkey_file_tests();
+ sshkey_fuzz_tests();
+}
diff --git a/regress/unittests/sshsig/Makefile b/regress/unittests/sshsig/Makefile
new file mode 100644
index 0000000..bc3c6c7
--- /dev/null
+++ b/regress/unittests/sshsig/Makefile
@@ -0,0 +1,25 @@
+# $OpenBSD: Makefile,v 1.3 2023/01/15 23:35:10 djm Exp $
+
+PROG=test_sshsig
+SRCS=tests.c
+
+# From usr.bin/ssh
+SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c
+SRCS+=sshbuf-io.c atomicio.c sshkey.c authfile.c cipher.c log.c ssh-rsa.c
+SRCS+=ssh-dss.c ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c
+SRCS+=ssherr.c uidswap.c cleanup.c xmalloc.c match.c krl.c fatal.c
+SRCS+=addr.c addrmatch.c bitmap.c sshsig.c
+SRCS+=ed25519.c hash.c
+SRCS+=cipher-chachapoly.c chacha.c poly1305.c ssh-ecdsa-sk.c ssh-sk.c
+SRCS+=ssh-ed25519-sk.c sk-usbhid.c
+
+SRCS+=digest-openssl.c
+#SRCS+=digest-libc.c
+SRCS+=utf8.c
+
+REGRESS_TARGETS=run-regress-${PROG}
+
+run-regress-${PROG}: ${PROG}
+ env ${TEST_ENV} ./${PROG} ${UNITTEST_ARGS} -d ${.CURDIR}/testdata
+
+.include <bsd.regress.mk>
diff --git a/regress/unittests/sshsig/mktestdata.sh b/regress/unittests/sshsig/mktestdata.sh
new file mode 100755
index 0000000..d2300f9
--- /dev/null
+++ b/regress/unittests/sshsig/mktestdata.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+# $OpenBSD: mktestdata.sh,v 1.1 2020/06/19 04:32:09 djm Exp $
+
+NAMESPACE=unittest
+
+set -ex
+
+cd testdata
+
+if [ -f ../../../misc/sk-dummy/sk-dummy.so ] ; then
+ SK_DUMMY=../../../misc/sk-dummy/sk-dummy.so
+elif [ -f ../../../misc/sk-dummy/obj/sk-dummy.so ] ; then
+ SK_DUMMY=../../../misc/sk-dummy/obj/sk-dummy.so
+else
+ echo "Can't find sk-dummy.so" 1>&2
+ exit 1
+fi
+
+rm -f signed-data namespace
+rm -f rsa dsa ecdsa ed25519 ecdsa_sk ed25519_sk
+rm -f rsa.sig dsa.sig ecdsa.sig ed25519.sig ecdsa_sk.sig ed25519_sk.sig
+
+printf "This is a test, this is only a test" > signed-data
+printf "$NAMESPACE" > namespace
+
+ssh-keygen -t rsa -C "RSA test" -N "" -f rsa -m PEM
+ssh-keygen -t dsa -C "DSA test" -N "" -f dsa -m PEM
+ssh-keygen -t ecdsa -C "ECDSA test" -N "" -f ecdsa -m PEM
+ssh-keygen -t ed25519 -C "ED25519 test key" -N "" -f ed25519
+ssh-keygen -w "$SK_DUMMY" -t ecdsa-sk -C "ECDSA-SK test key" \
+ -N "" -f ecdsa_sk
+ssh-keygen -w "$SK_DUMMY" -t ed25519-sk -C "ED25519-SK test key" \
+ -N "" -f ed25519_sk
+
+ssh-keygen -Y sign -f rsa -n $NAMESPACE - < signed-data > rsa.sig
+ssh-keygen -Y sign -f dsa -n $NAMESPACE - < signed-data > dsa.sig
+ssh-keygen -Y sign -f ecdsa -n $NAMESPACE - < signed-data > ecdsa.sig
+ssh-keygen -Y sign -f ed25519 -n $NAMESPACE - < signed-data > ed25519.sig
+ssh-keygen -w "$SK_DUMMY" \
+ -Y sign -f ecdsa_sk -n $NAMESPACE - < signed-data > ecdsa_sk.sig
+ssh-keygen -w "$SK_DUMMY" \
+ -Y sign -f ed25519_sk -n $NAMESPACE - < signed-data > ed25519_sk.sig
diff --git a/regress/unittests/sshsig/testdata/dsa b/regress/unittests/sshsig/testdata/dsa
new file mode 100644
index 0000000..7c0063e
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/dsa
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQCXpndQdz2mQVnk+lYOF3nxDT+h6SiJmUvBFhnFWBv8tG4pTOkb
+EwGufLEzGpzjTj+3bjVau7LFt37AFrqs4Num272BWNsYNIjOlGPgq7Xjv32FN00x
+JYh1DoRs1cGGnvohlsWEamGGhTHD1a9ipctPEBV+NrxtZMrl+pO/ZZg8vQIVAKJB
+P3iNYSpSuW74+q4WxLCuK8O3AoGAQldE+BIuxlvoG1IFiWesx0CU+H2KO0SEZc9A
+SX/qjOabh0Fb78ofTlEf9gWHFfat8SvSJQIOPMVlb76Lio8AAMT8Eaa/qQKKYmQL
+dNq4MLhhjxx5KLGt6J2JyFPExCv+qnHYHD59ngtLwKyqGjpSC8LPLktdXn8W/Aad
+Ly1K7+MCgYBsMHBczhSeUh8w7i20CVg4OlNTmfJRVU2tO6OpMxZ/quitRm3hLKSN
+u4xRkvHJwi4LhQtv1SXvLI5gs5P3gCG8tsIAiyCqLinHha63iBdJpqhnV/x/j7dB
+yJr3xJbnmLdWLkkCtNk1Ir1/CuEz+ufAyLGdKWksEAu1UUlb501BkwIVAILIa3Rg
+0h7J9lQpHJphvF3K0M1T
+-----END DSA PRIVATE KEY-----
diff --git a/regress/unittests/sshsig/testdata/dsa.pub b/regress/unittests/sshsig/testdata/dsa.pub
new file mode 100644
index 0000000..e77aa7e
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAJemd1B3PaZBWeT6Vg4XefENP6HpKImZS8EWGcVYG/y0bilM6RsTAa58sTManONOP7duNVq7ssW3fsAWuqzg26bbvYFY2xg0iM6UY+CrteO/fYU3TTEliHUOhGzVwYae+iGWxYRqYYaFMcPVr2Kly08QFX42vG1kyuX6k79lmDy9AAAAFQCiQT94jWEqUrlu+PquFsSwrivDtwAAAIBCV0T4Ei7GW+gbUgWJZ6zHQJT4fYo7RIRlz0BJf+qM5puHQVvvyh9OUR/2BYcV9q3xK9IlAg48xWVvvouKjwAAxPwRpr+pAopiZAt02rgwuGGPHHkosa3onYnIU8TEK/6qcdgcPn2eC0vArKoaOlILws8uS11efxb8Bp0vLUrv4wAAAIBsMHBczhSeUh8w7i20CVg4OlNTmfJRVU2tO6OpMxZ/quitRm3hLKSNu4xRkvHJwi4LhQtv1SXvLI5gs5P3gCG8tsIAiyCqLinHha63iBdJpqhnV/x/j7dByJr3xJbnmLdWLkkCtNk1Ir1/CuEz+ufAyLGdKWksEAu1UUlb501Bkw== DSA test
diff --git a/regress/unittests/sshsig/testdata/dsa.sig b/regress/unittests/sshsig/testdata/dsa.sig
new file mode 100644
index 0000000..0b14ad6
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/dsa.sig
@@ -0,0 +1,13 @@
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAAbEAAAAHc3NoLWRzcwAAAIEAl6Z3UHc9pkFZ5PpWDhd58Q0/oekoiZ
+lLwRYZxVgb/LRuKUzpGxMBrnyxMxqc404/t241Wruyxbd+wBa6rODbptu9gVjbGDSIzpRj
+4Ku14799hTdNMSWIdQ6EbNXBhp76IZbFhGphhoUxw9WvYqXLTxAVfja8bWTK5fqTv2WYPL
+0AAAAVAKJBP3iNYSpSuW74+q4WxLCuK8O3AAAAgEJXRPgSLsZb6BtSBYlnrMdAlPh9ijtE
+hGXPQEl/6ozmm4dBW+/KH05RH/YFhxX2rfEr0iUCDjzFZW++i4qPAADE/BGmv6kCimJkC3
+TauDC4YY8ceSixreidichTxMQr/qpx2Bw+fZ4LS8Csqho6UgvCzy5LXV5/FvwGnS8tSu/j
+AAAAgGwwcFzOFJ5SHzDuLbQJWDg6U1OZ8lFVTa07o6kzFn+q6K1GbeEspI27jFGS8cnCLg
+uFC2/VJe8sjmCzk/eAIby2wgCLIKouKceFrreIF0mmqGdX/H+Pt0HImvfElueYt1YuSQK0
+2TUivX8K4TP658DIsZ0paSwQC7VRSVvnTUGTAAAACHVuaXR0ZXN0AAAAAAAAAAZzaGE1MT
+IAAAA3AAAAB3NzaC1kc3MAAAAodi5lr0pqBpO76OY4N1CtfR85BCgZ95qfVjP/e9lToj0q
+lwjSJJXUjw==
+-----END SSH SIGNATURE-----
diff --git a/regress/unittests/sshsig/testdata/ecdsa b/regress/unittests/sshsig/testdata/ecdsa
new file mode 100644
index 0000000..55fb440
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ecdsa
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIFg0ZCSEB5LNeLsXYL25g3kqEWsqh52DR+yNOjyQJqyZoAoGCCqGSM49
+AwEHoUQDQgAE3sud88FV0N8FPspZSV7LWqj6uPPLRZiSsenNuEYAteWPyDgrZsWb
+LzXBuUJucepaCNuW/QWgHBRbrjWj3ERm3A==
+-----END EC PRIVATE KEY-----
diff --git a/regress/unittests/sshsig/testdata/ecdsa.pub b/regress/unittests/sshsig/testdata/ecdsa.pub
new file mode 100644
index 0000000..14ec6cf
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ecdsa.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBN7LnfPBVdDfBT7KWUley1qo+rjzy0WYkrHpzbhGALXlj8g4K2bFmy81wblCbnHqWgjblv0FoBwUW641o9xEZtw= ECDSA test
diff --git a/regress/unittests/sshsig/testdata/ecdsa.sig b/regress/unittests/sshsig/testdata/ecdsa.sig
new file mode 100644
index 0000000..7978157
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ecdsa.sig
@@ -0,0 +1,7 @@
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAE
+EE3sud88FV0N8FPspZSV7LWqj6uPPLRZiSsenNuEYAteWPyDgrZsWbLzXBuUJucepaCNuW
+/QWgHBRbrjWj3ERm3AAAAAh1bml0dGVzdAAAAAAAAAAGc2hhNTEyAAAAZQAAABNlY2RzYS
+1zaGEyLW5pc3RwMjU2AAAASgAAACEAycVNsTlE+XEZYyYiDxWZlliruf/pPMhEEMR/XLdQ
+a4MAAAAhALQt+5gES7L3uKGptHB6UZQMuZ2WyI0C6FJs4v6AtMIU
+-----END SSH SIGNATURE-----
diff --git a/regress/unittests/sshsig/testdata/ecdsa_sk b/regress/unittests/sshsig/testdata/ecdsa_sk
new file mode 100644
index 0000000..62ae44c
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ecdsa_sk
@@ -0,0 +1,13 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAfwAAACJzay1lY2
+RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQSg1WuY0XE+
+VexOsrJsFYuxyVoe6eQ/oXmyz2pEHKZw9moyWehv+Fs7oZWFp3JVmOtybKQ6dvfUZYauQE
+/Ov4PAAAAABHNzaDoAAAGI6iV41+oleNcAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBv
+cGVuc3NoLmNvbQAAAAhuaXN0cDI1NgAAAEEEoNVrmNFxPlXsTrKybBWLsclaHunkP6F5ss
+9qRBymcPZqMlnob/hbO6GVhadyVZjrcmykOnb31GWGrkBPzr+DwAAAAARzc2g6AQAAAOMt
+LS0tLUJFR0lOIEVDIFBSSVZBVEUgS0VZLS0tLS0KTUhjQ0FRRUVJQm9oeW54M2tpTFVEeS
+t5UjU3WXBXSU5KektnU1p6WnV2VTljYXFla3JGcW9Bb0dDQ3FHU000OQpBd0VIb1VRRFFn
+QUVvTlZybU5GeFBsWHNUckt5YkJXTHNjbGFIdW5rUDZGNXNzOXFSQnltY1BacU1sbm9iL2
+hiCk82R1ZoYWR5VlpqcmNteWtPbmIzMUdXR3JrQlB6citEd0E9PQotLS0tLUVORCBFQyBQ
+UklWQVRFIEtFWS0tLS0tCgAAAAAAAAARRUNEU0EtU0sgdGVzdCBrZXk=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshsig/testdata/ecdsa_sk.pub b/regress/unittests/sshsig/testdata/ecdsa_sk.pub
new file mode 100644
index 0000000..385ebf1
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ecdsa_sk.pub
@@ -0,0 +1 @@
+sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBKDVa5jRcT5V7E6ysmwVi7HJWh7p5D+hebLPakQcpnD2ajJZ6G/4WzuhlYWnclWY63JspDp299Rlhq5AT86/g8AAAAAEc3NoOg== ECDSA-SK test key
diff --git a/regress/unittests/sshsig/testdata/ecdsa_sk.sig b/regress/unittests/sshsig/testdata/ecdsa_sk.sig
new file mode 100644
index 0000000..86de360
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ecdsa_sk.sig
@@ -0,0 +1,8 @@
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAAH8AAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBvcGVuc3NoLmNvbQ
+AAAAhuaXN0cDI1NgAAAEEEoNVrmNFxPlXsTrKybBWLsclaHunkP6F5ss9qRBymcPZqMlno
+b/hbO6GVhadyVZjrcmykOnb31GWGrkBPzr+DwAAAAARzc2g6AAAACHVuaXR0ZXN0AAAAAA
+AAAAZzaGE1MTIAAAB3AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20A
+AABIAAAAIHohGwyy8iKT3zwd1TYA9V/Ioo7h/3zCJUtyq/Qigt/HAAAAIGzidTwq7D/kFa
+7Xjcp/KkdbIs4MfQpfAW/0OciajlpzARI0Vng=
+-----END SSH SIGNATURE-----
diff --git a/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.pub b/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.pub
new file mode 100644
index 0000000..1597302
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.pub
@@ -0,0 +1 @@
+sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBBRGwDjs4HhJFcn4tJ5Gr72KcmRmCS1OirETxaXvnsNApgoOLF1a/7rxldfSMHm73eT1nhHe97W8qicPPEAKDJQAAAALbWluZHJvdC5vcmc=
diff --git a/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.sig b/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.sig
new file mode 100644
index 0000000..4bdd8ed
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.sig
@@ -0,0 +1,13 @@
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAAIYAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBvcGVuc3NoLmNvbQ
+AAAAhuaXN0cDI1NgAAAEEEFEbAOOzgeEkVyfi0nkavvYpyZGYJLU6KsRPFpe+ew0CmCg4s
+XVr/uvGV19Iwebvd5PWeEd73tbyqJw88QAoMlAAAAAttaW5kcm90Lm9yZwAAAAh1bml0dG
+VzdAAAAAAAAAAGc2hhNTEyAAABhwAAACt3ZWJhdXRobi1zay1lY2RzYS1zaGEyLW5pc3Rw
+MjU2QG9wZW5zc2guY29tAAAASQAAACBj2oMT9tb5wRXe6mdmf4/lgAO8wrgr95ouozwNg4
+itnQAAACEAtU9g5wz3HchUiLfLD6plr9T4TiJ32lVCrATSjpiy0SMBAAADHwAAABdodHRw
+czovL3d3dy5taW5kcm90Lm9yZwAAAON7InR5cGUiOiJ3ZWJhdXRobi5nZXQiLCJjaGFsbG
+VuZ2UiOiJVMU5JVTBsSEFBQUFDSFZ1YVhSMFpYTjBBQUFBQUFBQUFBWnphR0UxTVRJQUFB
+QkFMTHU4WmdjU3h0Nk1zRlV6dWlaZ0c2R3dNZEo5ZDd4ZUU3WW9SSXcwZzlpSEpfd3NGRD
+cxbzRXbHllenZGV0VqYnFRMHFDN0Z3R3Bqa2pVUVAtTmQ2dyIsIm9yaWdpbiI6Imh0dHBz
+Oi8vd3d3Lm1pbmRyb3Qub3JnIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQAAAAA=
+-----END SSH SIGNATURE-----
diff --git a/regress/unittests/sshsig/testdata/ed25519 b/regress/unittests/sshsig/testdata/ed25519
new file mode 100644
index 0000000..b44a63d
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACCJYs0iDdw0Fe/FTzY1b78I4H/j+R6mz2AmLtwTjHYwBAAAAJjpGas/6Rmr
+PwAAAAtzc2gtZWQyNTUxOQAAACCJYs0iDdw0Fe/FTzY1b78I4H/j+R6mz2AmLtwTjHYwBA
+AAAEDpSKRA1QKW6kYiQftGRWh+H0fNekzYLG6c3bzseoCpEolizSIN3DQV78VPNjVvvwjg
+f+P5HqbPYCYu3BOMdjAEAAAAEEVEMjU1MTkgdGVzdCBrZXkBAgMEBQ==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshsig/testdata/ed25519.pub b/regress/unittests/sshsig/testdata/ed25519.pub
new file mode 100644
index 0000000..b078e45
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIlizSIN3DQV78VPNjVvvwjgf+P5HqbPYCYu3BOMdjAE ED25519 test key
diff --git a/regress/unittests/sshsig/testdata/ed25519.sig b/regress/unittests/sshsig/testdata/ed25519.sig
new file mode 100644
index 0000000..8e8ff2a
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ed25519.sig
@@ -0,0 +1,6 @@
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgiWLNIg3cNBXvxU82NW+/COB/4/
+keps9gJi7cE4x2MAQAAAAIdW5pdHRlc3QAAAAAAAAABnNoYTUxMgAAAFMAAAALc3NoLWVk
+MjU1MTkAAABAihQsbUzuNEFflk5Tw1+H9aLS7tZQk0RG8KW1DtOmDYYnWe3D3UKiG3fcJa
+DNg4vBWp1j1gLRiBMOF+gwYNegDg==
+-----END SSH SIGNATURE-----
diff --git a/regress/unittests/sshsig/testdata/ed25519_sk b/regress/unittests/sshsig/testdata/ed25519_sk
new file mode 100644
index 0000000..3a434ec
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ed25519_sk
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAABpzay1zc2
+gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACCbGg2F0GK7nOm4pQmAyCuGEjnhvs5q0TtjPbdN
+//+yxwAAAARzc2g6AAAAuBw56jAcOeowAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY2
+9tAAAAIJsaDYXQYruc6bilCYDIK4YSOeG+zmrRO2M9t03//7LHAAAABHNzaDoBAAAAQFXc
+6dCwWewIk1EBofAouGZApW8+s0XekXenxtb78+x0mxoNhdBiu5zpuKUJgMgrhhI54b7Oat
+E7Yz23Tf//sscAAAAAAAAAE0VEMjU1MTktU0sgdGVzdCBrZXkBAgMEBQY=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/regress/unittests/sshsig/testdata/ed25519_sk.pub b/regress/unittests/sshsig/testdata/ed25519_sk.pub
new file mode 100644
index 0000000..71051ec
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ed25519_sk.pub
@@ -0,0 +1 @@
+sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIJsaDYXQYruc6bilCYDIK4YSOeG+zmrRO2M9t03//7LHAAAABHNzaDo= ED25519-SK test key
diff --git a/regress/unittests/sshsig/testdata/ed25519_sk.sig b/regress/unittests/sshsig/testdata/ed25519_sk.sig
new file mode 100644
index 0000000..49b6818
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ed25519_sk.sig
@@ -0,0 +1,7 @@
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAAEoAAAAac2stc3NoLWVkMjU1MTlAb3BlbnNzaC5jb20AAAAgmxoNhd
+Biu5zpuKUJgMgrhhI54b7OatE7Yz23Tf//sscAAAAEc3NoOgAAAAh1bml0dGVzdAAAAAAA
+AAAGc2hhNTEyAAAAZwAAABpzay1zc2gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAAEAi+7eTjW
+/+LQ2M+sCD+KFtH1n7VFFJon/SZFsxODyV8cWTlFKj617Ys1Ur5TV6uaEXQhck8rBA2oQI
+HTPANLIPARI0Vng=
+-----END SSH SIGNATURE-----
diff --git a/regress/unittests/sshsig/testdata/namespace b/regress/unittests/sshsig/testdata/namespace
new file mode 100644
index 0000000..1570cd5
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/namespace
@@ -0,0 +1 @@
+unittest \ No newline at end of file
diff --git a/regress/unittests/sshsig/testdata/rsa b/regress/unittests/sshsig/testdata/rsa
new file mode 100644
index 0000000..228fad7
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/rsa
@@ -0,0 +1,39 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIG4wIBAAKCAYEA386lmjRHtJpyj87BrS+ssMmtvc/1SPN0gXTPs9jZ1hYAq98P
+ca3/RYVM4HaSu6COztQJ2ZnZD3Te/XeBnIU2mfuvQEl+DiwisGeNglVyRCi7787f
+PFFfcxzZfDa7EB2qY8S3oaSGZK8QqzuGwmGAImjlQXz6J+HCd/eD/58GoCSSirIE
+CFWCAt+uNrOC/EmgAzsbfcfaIbbVzA40tlgU3hO2J42kddz8CisDTtDKQABFcOaQ
+ZycSfn7HDP+WgXLXXBUI9wVM1Tif1f+9MX08xIsvCvGzo7yLgbbTFLSGr5SkA+tO
+rYuoA7V8fge0id/3pnVtG1Ui3I7vejeAwf0HZqtFeBEnOwkIJFmZeMtFeOVf+4ki
+4h1rDqAvSscNvMtLp6OXpbAATATAuEWEkIQBl1rngnEe0iC9iU9itKMW6qJ4FtIb
+4ACH1EoU1x8vqrFecg2hvqfk5CZBJIbV28JFuGjac3OxBZ17Fqb8ljomUir1GrET
+2z66NMgb5TjDD7BVAgMBAAECggGACfjDGCPMLhfTkuS7bGP7ZcUWoKZrX1y5jCsQ
+NgsraYaBcSb3ITGHdimCS139G68DreN0rOVV7oJejRyOAdNNo367SDn+C9ObmBCF
+FZGJDdBiz0SAXceiYRaf+hDWNNmdheR16hXShxnlvDtivbZqZx4VWN2gp7Y/W+kD
+UJhdSzVV8igMVfK5YDdnI7jL1UHSh1JS3z/QUEA9NmJLpvQ1uc9XBlwhP78g27Me
+6pwS5tccQPOE65OqF0i+xa19nzbmnC940Y34yZeI/UE+PYaO2+asapvOfu/sboBH
+Yb5BuWXVEkSeRWI23SpuZbmfNTtVgiRoRqOvqM4G88LkhYjZ6xpDggxQwJiShiiD
+oWCucs0v3pX8H8/LbGs8l50SGI5nzUqAdZ7/QQucU/GuDiQtampntkLEDgf9KIw/
+SDrtCw1E9fnCWj4Z71IYfepY9bVY6QUEcfTdnDcYSY1Z5tVpzeMHVLeo0lbNVZv9
+2qmPnjjP/IvWbjjwu/PHpUWkUs0BAoHBAPx4YwPXWYgWnesMKXkjAHyO5KA4EyBr
++rcEmOZkZDibC8PKYzIK2ztptuthahVovW20R/QJhJkO5teGZMeGPFq+floCeC5P
+la9CEYGYcTrzgSe1QM9IGMr1vGI1KIWck7VkJ0bkKoY40uIJSVZxnyG9pEpcwYSp
+tnOqA/f5YZUFctWvXUz46OfiLKstXLrcrGIU7YRmLv2rW9twnpJYTzE98g3KpVJ2
+TI1pyvrDTdGeAQUTGCAjpviY6XR5d020vQKBwQDi76wsGLQ3XLI+OAE95Ljo0Mcl
++KdJPVVQPq/VcjKgZQndFloflMRrmgNHme9gmsHOrf8DLZvEDbtT+gbmWslMFZQ9
+om1kR404gfuGmfIYdBdOwWjuBLsZs3pfqDB4Xa3NkxljwOMYTp035n0r2UMFaSy3
+gvpW7fsdPOGAJsqNhSw/JNHcokHeBm7VbV0aD7tSyIghmARb5c98fmrSPbiEo8mP
+ITIZlgbfZCq2KuXY4q16R3QvlpuSwitVobLR/3kCgcEAueH5JM7dQHFGe9RMhL/c
+j9i1Q7GFg4183lsoKBkqIPMmylSsjB+qIihHYS4r6O9g6PCfOXH4iqiKFY0BjlWr
+AjTW2naO/aniz1KZiQ0v8PNv2Eh/Gx4+AtDCjpwM5bLOnfLLaEp9dK1JttqXgGnP
+fAwgdg+s+3votWgr29tkmU+VqPagfxeUg4Xm1XFkoL/wu5Yk+iIx3trXms1kMuOK
+CvtMyBK3fetTmZqWs+Iv3XGz1oSkcqVNPiN3XyY/TJsRAoG/Q17jvjOXTNg4EkCO
+HdHJE1Tnyl4HS7bpnOj/Sl6cqQFV7Ey2dKm1pjwSvS714bgP0UvWaRshIxLwif2w
+DrLlD7FYUPPnhd24Dw6HnW4WcSwFv1uryv2cjgS6T6ueuB0Xe/AvmW2p/Y1ZHz9N
+6baWLwUKQXCg4S3FXui0CVd6yoi+mgBUTSveYguG29WbziDde7YMs+xtXtravhrJ
+m6C3Jql5LQSt2uqvH6KdC3ewxLKGzcZot7f+d5MtSj6216ECgcEA9PGmWeUkhVuW
+Xz2c9iBeHwCtmDso7gVwxNnHqdqirB4f1nDCGbrJS7hz5Ss7/wfzekP2W5if2P6U
+JPUdfykAQgALNn1twAtj1a+UAp31ZWu8JK/Qzt4hLJPBxzMo7MenJq189JmYmDnm
+6D5d9vDLCW15gCZua89GZa8K8V50lYyeHBOHAyzNTfNlnMBkHyP645+nqpuEWzIT
+3mCe2OAbl60o8VvvVUlAQyQ/ObLq37HHEoDu0U/YAnP157cxpa84
+-----END RSA PRIVATE KEY-----
diff --git a/regress/unittests/sshsig/testdata/rsa.pub b/regress/unittests/sshsig/testdata/rsa.pub
new file mode 100644
index 0000000..30142ac
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDfzqWaNEe0mnKPzsGtL6ywya29z/VI83SBdM+z2NnWFgCr3w9xrf9FhUzgdpK7oI7O1AnZmdkPdN79d4GchTaZ+69ASX4OLCKwZ42CVXJEKLvvzt88UV9zHNl8NrsQHapjxLehpIZkrxCrO4bCYYAiaOVBfPon4cJ394P/nwagJJKKsgQIVYIC3642s4L8SaADOxt9x9ohttXMDjS2WBTeE7YnjaR13PwKKwNO0MpAAEVw5pBnJxJ+fscM/5aBctdcFQj3BUzVOJ/V/70xfTzEiy8K8bOjvIuBttMUtIavlKQD606ti6gDtXx+B7SJ3/emdW0bVSLcju96N4DB/Qdmq0V4ESc7CQgkWZl4y0V45V/7iSLiHWsOoC9Kxw28y0uno5elsABMBMC4RYSQhAGXWueCcR7SIL2JT2K0oxbqongW0hvgAIfUShTXHy+qsV5yDaG+p+TkJkEkhtXbwkW4aNpzc7EFnXsWpvyWOiZSKvUasRPbPro0yBvlOMMPsFU= RSA test
diff --git a/regress/unittests/sshsig/testdata/rsa.sig b/regress/unittests/sshsig/testdata/rsa.sig
new file mode 100644
index 0000000..15a032e
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/rsa.sig
@@ -0,0 +1,19 @@
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAN/OpZo0R7Saco/Owa0vrL
+DJrb3P9UjzdIF0z7PY2dYWAKvfD3Gt/0WFTOB2krugjs7UCdmZ2Q903v13gZyFNpn7r0BJ
+fg4sIrBnjYJVckQou+/O3zxRX3Mc2Xw2uxAdqmPEt6GkhmSvEKs7hsJhgCJo5UF8+ifhwn
+f3g/+fBqAkkoqyBAhVggLfrjazgvxJoAM7G33H2iG21cwONLZYFN4TtieNpHXc/AorA07Q
+ykAARXDmkGcnEn5+xwz/loFy11wVCPcFTNU4n9X/vTF9PMSLLwrxs6O8i4G20xS0hq+UpA
+PrTq2LqAO1fH4HtInf96Z1bRtVItyO73o3gMH9B2arRXgRJzsJCCRZmXjLRXjlX/uJIuId
+aw6gL0rHDbzLS6ejl6WwAEwEwLhFhJCEAZda54JxHtIgvYlPYrSjFuqieBbSG+AAh9RKFN
+cfL6qxXnINob6n5OQmQSSG1dvCRbho2nNzsQWdexam/JY6JlIq9RqxE9s+ujTIG+U4ww+w
+VQAAAAh1bml0dGVzdAAAAAAAAAAGc2hhNTEyAAABlAAAAAxyc2Etc2hhMi01MTIAAAGACi
+nEpBrQxZi0yOrrT6h98JFfZh0XXioih4fzmvtoV0yOReWClS+otGgXoJyZHcbaKNOjDwSM
+rIkUoX6OUJmtHYP0HRELnKw35m33LdBPXpFGS4tRS7NeSpvc04KtjT6jYXY9FjWy5hcn17
+Sxc/3DnJqLgJBur8acY7FeIzpWmKixPd/dGkEjdWoD9gO6szLczGuQgrOdYmSRL4yKadTJ
+lVjz5OSeKSYYGQy33US2XQassRRNYf4e9byTA3DKvHa/OcTt7lFerea0kZdDpAboqffz7T
+Yaw/hFskAYLIEdTW3aoXBGHSOvu8AkDOtb7qwuxGSQ27pjkDLDNsp1ceCFaCaQ6X83RZuK
+ACv9JUBI5KaSf81e0bs0KezJKkhB9czeZ6dk96qISbgayEBnvhYgXvUDKtHn7HzNlCJKfK
+5ABhNxfGG2CD+NKqcrndwFgS1sQO3hbA84zPQb26ShBovT8ytHBmW1F8ZK4O9Bz61Q6EZK
+vs/u6xP6LUean/so5daa
+-----END SSH SIGNATURE-----
diff --git a/regress/unittests/sshsig/testdata/signed-data b/regress/unittests/sshsig/testdata/signed-data
new file mode 100644
index 0000000..7df4bed
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/signed-data
@@ -0,0 +1 @@
+This is a test, this is only a test \ No newline at end of file
diff --git a/regress/unittests/sshsig/tests.c b/regress/unittests/sshsig/tests.c
new file mode 100644
index 0000000..fdc3bae
--- /dev/null
+++ b/regress/unittests/sshsig/tests.c
@@ -0,0 +1,142 @@
+/* $OpenBSD: tests.c,v 1.3 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Regress test for sshbuf.h buffer API
+ *
+ * Placed in the public domain
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef WITH_OPENSSL
+#include <openssl/evp.h>
+#include <openssl/crypto.h>
+#endif
+
+#include "ssherr.h"
+#include "authfile.h"
+#include "sshkey.h"
+#include "sshbuf.h"
+#include "sshsig.h"
+#include "log.h"
+
+#include "../test_helper/test_helper.h"
+
+static struct sshbuf *
+load_file(const char *name)
+{
+ struct sshbuf *ret = NULL;
+
+ ASSERT_INT_EQ(sshbuf_load_file(test_data_file(name), &ret), 0);
+ ASSERT_PTR_NE(ret, NULL);
+ return ret;
+}
+
+static struct sshkey *
+load_key(const char *name)
+{
+ struct sshkey *ret = NULL;
+ ASSERT_INT_EQ(sshkey_load_public(test_data_file(name), &ret, NULL), 0);
+ ASSERT_PTR_NE(ret, NULL);
+ return ret;
+}
+
+static void
+check_sig(const char *keyname, const char *signame, const struct sshbuf *msg,
+ const char *namespace)
+{
+ struct sshkey *k, *sign_key;
+ struct sshbuf *sig, *rawsig;
+ struct sshkey_sig_details *sig_details;
+
+ k = load_key(keyname);
+ sig = load_file(signame);
+ sign_key = NULL;
+ sig_details = NULL;
+ rawsig = NULL;
+ ASSERT_INT_EQ(sshsig_dearmor(sig, &rawsig), 0);
+ ASSERT_INT_EQ(sshsig_verifyb(rawsig, msg, namespace,
+ &sign_key, &sig_details), 0);
+ ASSERT_INT_EQ(sshkey_equal(k, sign_key), 1);
+ sshkey_free(k);
+ sshkey_free(sign_key);
+ sshkey_sig_details_free(sig_details);
+ sshbuf_free(sig);
+ sshbuf_free(rawsig);
+}
+
+void
+tests(void)
+{
+ struct sshbuf *msg;
+ char *namespace;
+
+#if 0
+ log_init("test_sshsig", SYSLOG_LEVEL_DEBUG3, SYSLOG_FACILITY_AUTH, 1);
+#endif
+
+#ifdef WITH_OPENSSL
+ OpenSSL_add_all_algorithms();
+ ERR_load_CRYPTO_strings();
+#endif
+
+ TEST_START("load data");
+ msg = load_file("namespace");
+ namespace = sshbuf_dup_string(msg);
+ ASSERT_PTR_NE(namespace, NULL);
+ sshbuf_free(msg);
+ msg = load_file("signed-data");
+ TEST_DONE();
+
+#ifdef WITH_OPENSSL
+ TEST_START("check RSA signature");
+ check_sig("rsa.pub", "rsa.sig", msg, namespace);
+ TEST_DONE();
+
+ TEST_START("check DSA signature");
+ check_sig("dsa.pub", "dsa.sig", msg, namespace);
+ TEST_DONE();
+
+#ifdef OPENSSL_HAS_ECC
+ TEST_START("check ECDSA signature");
+ check_sig("ecdsa.pub", "ecdsa.sig", msg, namespace);
+ TEST_DONE();
+#endif
+#endif
+
+ TEST_START("check ED25519 signature");
+ check_sig("ed25519.pub", "ed25519.sig", msg, namespace);
+ TEST_DONE();
+
+#ifdef ENABLE_SK
+#if defined(WITH_OPENSSL) && defined(OPENSSL_HAS_ECC)
+ TEST_START("check ECDSA-SK signature");
+ check_sig("ecdsa_sk.pub", "ecdsa_sk.sig", msg, namespace);
+ TEST_DONE();
+#endif
+
+ TEST_START("check ED25519-SK signature");
+ check_sig("ed25519_sk.pub", "ed25519_sk.sig", msg, namespace);
+ TEST_DONE();
+
+#if defined(WITH_OPENSSL) && defined(OPENSSL_HAS_ECC)
+ TEST_START("check ECDSA-SK webauthn signature");
+ check_sig("ecdsa_sk_webauthn.pub", "ecdsa_sk_webauthn.sig",
+ msg, namespace);
+ TEST_DONE();
+#endif
+#endif /* ENABLE_SK */
+
+ sshbuf_free(msg);
+ free(namespace);
+}
diff --git a/regress/unittests/sshsig/webauthn.html b/regress/unittests/sshsig/webauthn.html
new file mode 100644
index 0000000..5c9a32e
--- /dev/null
+++ b/regress/unittests/sshsig/webauthn.html
@@ -0,0 +1,766 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>webauthn test</title>
+</head>
+<body onload="init()">
+<h1>webauthn test</h1>
+<p>
+This is a demo/test page for generating FIDO keys and signatures in SSH
+formats. The page initially displays a form to generate a FIDO key and
+convert it to a SSH public key.
+</p>
+<p>
+Once a key has been generated, an additional form will be displayed to
+allow signing of data using the just-generated key. The data may be signed
+as either a raw SSH signature or wrapped in a sshsig message (the latter is
+easier to test using command-line tools.
+</p>
+<p>
+Lots of debugging is printed along the way.
+</p>
+<h2>Enroll</h2>
+<span id="error" style="color: #800; font-weight: bold; font-size: 150%;"></span>
+<form id="enrollform">
+<table>
+<tr>
+<td><b>Username:</b></td>
+<td><input id="username" type="text" size="20" name="user" value="test" /></td>
+</tr>
+<tr><td></td><td><input id="assertsubmit" type="submit" value="submit" /></td></tr>
+</table>
+</form>
+<span id="enrollresult" style="visibility: hidden;">
+<h2>clientData</h2>
+<pre id="enrollresultjson" style="color: #008; font-family: monospace;"></pre>
+<h2>attestationObject raw</h2>
+<pre id="enrollresultraw" style="color: #008; font-family: monospace;"></pre>
+<h2>attestationObject</h2>
+<pre id="enrollresultattestobj" style="color: #008; font-family: monospace;"></pre>
+<h2>key handle</h2>
+<pre id="keyhandle" style="color: #008; font-family: monospace;"></pre>
+<h2>authData raw</h2>
+<pre id="enrollresultauthdataraw" style="color: #008; font-family: monospace;"></pre>
+<h2>authData</h2>
+<pre id="enrollresultauthdata" style="color: #008; font-family: monospace;"></pre>
+<h2>SSH pubkey blob</h2>
+<pre id="enrollresultpkblob" style="color: #008; font-family: monospace;"></pre>
+<h2>SSH pubkey string</h2>
+<pre id="enrollresultpk" style="color: #008; font-family: monospace;"></pre>
+<h2>SSH private key string</h2>
+<pre id="enrollresultprivkey" style="color: #008; font-family: monospace;"></pre>
+</span>
+<span id="assertsection" style="visibility: hidden;">
+<h2>Assert</h2>
+<form id="assertform">
+<span id="asserterror" style="color: #800; font-weight: bold;"></span>
+<table>
+<tr>
+<td><b>Data to sign:</b></td>
+<td><input id="message" type="text" size="20" name="message" value="test" /></td>
+</tr>
+<tr>
+<td><input id="message_sshsig" type="checkbox" checked /> use sshsig format</td>
+</tr>
+<tr>
+<td><b>Signature namespace:</b></td>
+<td><input id="message_namespace" type="text" size="20" name="namespace" value="test" /></td>
+</tr>
+<tr><td></td><td><input type="submit" value="submit" /></td></tr>
+</table>
+</form>
+</span>
+<span id="assertresult" style="visibility: hidden;">
+<h2>clientData</h2>
+<pre id="assertresultjson" style="color: #008; font-family: monospace;"></pre>
+<h2>signature raw</h2>
+<pre id="assertresultsigraw" style="color: #008; font-family: monospace;"></pre>
+<h2>authenticatorData raw</h2>
+<pre id="assertresultauthdataraw" style="color: #008; font-family: monospace;"></pre>
+<h2>authenticatorData</h2>
+<pre id="assertresultauthdata" style="color: #008; font-family: monospace;"></pre>
+<h2>signature in SSH format</h2>
+<pre id="assertresultsshsigraw" style="color: #008; font-family: monospace;"></pre>
+<h2>signature in SSH format (base64 encoded)</h2>
+<pre id="assertresultsshsigb64" style="color: #008; font-family: monospace;"></pre>
+</span>
+</body>
+<script>
+// ------------------------------------------------------------------
+// a crappy CBOR decoder - 20200401 djm@openbsd.org
+
+var CBORDecode = function(buffer) {
+ this.buf = buffer
+ this.v = new DataView(buffer)
+ this.offset = 0
+}
+
+CBORDecode.prototype.empty = function() {
+ return this.offset >= this.buf.byteLength
+}
+
+CBORDecode.prototype.getU8 = function() {
+ let r = this.v.getUint8(this.offset)
+ this.offset += 1
+ return r
+}
+
+CBORDecode.prototype.getU16 = function() {
+ let r = this.v.getUint16(this.offset)
+ this.offset += 2
+ return r
+}
+
+CBORDecode.prototype.getU32 = function() {
+ let r = this.v.getUint32(this.offset)
+ this.offset += 4
+ return r
+}
+
+CBORDecode.prototype.getU64 = function() {
+ let r = this.v.getUint64(this.offset)
+ this.offset += 8
+ return r
+}
+
+CBORDecode.prototype.getCBORTypeLen = function() {
+ let tl, t, l
+ tl = this.getU8()
+ t = (tl & 0xe0) >> 5
+ l = tl & 0x1f
+ return [t, this.decodeInteger(l)]
+}
+
+CBORDecode.prototype.decodeInteger = function(len) {
+ switch (len) {
+ case 0x18: return this.getU8()
+ case 0x19: return this.getU16()
+ case 0x20: return this.getU32()
+ case 0x21: return this.getU64()
+ default:
+ if (len <= 23) {
+ return len
+ }
+ throw new Error("Unsupported int type 0x" + len.toString(16))
+ }
+}
+
+CBORDecode.prototype.decodeNegint = function(len) {
+ let r = -(this.decodeInteger(len) + 1)
+ return r
+}
+
+CBORDecode.prototype.decodeByteString = function(len) {
+ let r = this.buf.slice(this.offset, this.offset + len)
+ this.offset += len
+ return r
+}
+
+CBORDecode.prototype.decodeTextString = function(len) {
+ let u8dec = new TextDecoder('utf-8')
+ r = u8dec.decode(this.decodeByteString(len))
+ return r
+}
+
+CBORDecode.prototype.decodeArray = function(len, level) {
+ let r = []
+ for (let i = 0; i < len; i++) {
+ let v = this.decodeInternal(level)
+ r.push(v)
+ // console.log("decodeArray level " + level.toString() + " index " + i.toString() + " value " + JSON.stringify(v))
+ }
+ return r
+}
+
+CBORDecode.prototype.decodeMap = function(len, level) {
+ let r = {}
+ for (let i = 0; i < len; i++) {
+ let k = this.decodeInternal(level)
+ let v = this.decodeInternal(level)
+ r[k] = v
+ // console.log("decodeMap level " + level.toString() + " key " + k.toString() + " value " + JSON.stringify(v))
+ // XXX check string keys, duplicates
+ }
+ return r
+}
+
+CBORDecode.prototype.decodePrimitive = function(t) {
+ switch (t) {
+ case 20: return false
+ case 21: return true
+ case 22: return null
+ case 23: return undefined
+ default:
+ throw new Error("Unsupported primitive 0x" + t.toString(2))
+ }
+}
+
+CBORDecode.prototype.decodeInternal = function(level) {
+ if (level > 256) {
+ throw new Error("CBOR nesting too deep")
+ }
+ let t, l, r
+ [t, l] = this.getCBORTypeLen()
+ // console.log("decode level " + level.toString() + " type " + t.toString() + " len " + l.toString())
+ switch (t) {
+ case 0:
+ r = this.decodeInteger(l)
+ break
+ case 1:
+ r = this.decodeNegint(l)
+ break
+ case 2:
+ r = this.decodeByteString(l)
+ break
+ case 3:
+ r = this.decodeTextString(l)
+ break
+ case 4:
+ r = this.decodeArray(l, level + 1)
+ break
+ case 5:
+ r = this.decodeMap(l, level + 1)
+ break
+ case 6:
+ console.log("XXX ignored semantic tag " + this.decodeInteger(l).toString())
+ break;
+ case 7:
+ r = this.decodePrimitive(l)
+ break
+ default:
+ throw new Error("Unsupported type 0x" + t.toString(2) + " len " + l.toString())
+ }
+ // console.log("decode level " + level.toString() + " value " + JSON.stringify(r))
+ return r
+}
+
+CBORDecode.prototype.decode = function() {
+ return this.decodeInternal(0)
+}
+
+// ------------------------------------------------------------------
+// a crappy SSH message packer - 20200401 djm@openbsd.org
+
+var SSHMSG = function() {
+ this.r = []
+}
+
+SSHMSG.prototype.length = function() {
+ let len = 0
+ for (buf of this.r) {
+ len += buf.length
+ }
+ return len
+}
+
+SSHMSG.prototype.serialise = function() {
+ let r = new ArrayBuffer(this.length())
+ let v = new Uint8Array(r)
+ let offset = 0
+ for (buf of this.r) {
+ v.set(buf, offset)
+ offset += buf.length
+ }
+ if (offset != r.byteLength) {
+ throw new Error("djm can't count")
+ }
+ return r
+}
+
+SSHMSG.prototype.serialiseBase64 = function(v) {
+ let b = this.serialise()
+ return btoa(String.fromCharCode(...new Uint8Array(b)));
+}
+
+SSHMSG.prototype.putU8 = function(v) {
+ this.r.push(new Uint8Array([v]))
+}
+
+SSHMSG.prototype.putU32 = function(v) {
+ this.r.push(new Uint8Array([
+ (v >> 24) & 0xff,
+ (v >> 16) & 0xff,
+ (v >> 8) & 0xff,
+ (v & 0xff)
+ ]))
+}
+
+SSHMSG.prototype.put = function(v) {
+ this.r.push(new Uint8Array(v))
+}
+
+SSHMSG.prototype.putStringRaw = function(v) {
+ let enc = new TextEncoder();
+ let venc = enc.encode(v)
+ this.put(venc)
+}
+
+SSHMSG.prototype.putString = function(v) {
+ let enc = new TextEncoder();
+ let venc = enc.encode(v)
+ this.putU32(venc.length)
+ this.put(venc)
+}
+
+SSHMSG.prototype.putSSHMSG = function(v) {
+ let msg = v.serialise()
+ this.putU32(msg.byteLength)
+ this.put(msg)
+}
+
+SSHMSG.prototype.putBytes = function(v) {
+ this.putU32(v.byteLength)
+ this.put(v)
+}
+
+SSHMSG.prototype.putECPoint = function(x, y) {
+ let x8 = new Uint8Array(x)
+ let y8 = new Uint8Array(y)
+ this.putU32(1 + x8.length + y8.length)
+ this.putU8(0x04) // Uncompressed point format.
+ this.put(x8)
+ this.put(y8)
+}
+
+// ------------------------------------------------------------------
+// webauthn to SSH glue - djm@openbsd.org 20200408
+
+function error(msg, ...args) {
+ document.getElementById("error").innerText = msg
+ console.log(msg)
+ for (const arg of args) {
+ console.dir(arg)
+ }
+}
+function hexdump(buf) {
+ const hex = Array.from(new Uint8Array(buf)).map(
+ b => b.toString(16).padStart(2, "0"))
+ const fmt = new Array()
+ for (let i = 0; i < hex.length; i++) {
+ if ((i % 16) == 0) {
+ // Prepend length every 16 bytes.
+ fmt.push(i.toString(16).padStart(4, "0"))
+ fmt.push(" ")
+ }
+ fmt.push(hex[i])
+ fmt.push(" ")
+ if ((i % 16) == 15) {
+ fmt.push("\n")
+ }
+ }
+ return fmt.join("")
+}
+function enrollform_submit(event) {
+ event.preventDefault();
+ console.log("submitted")
+ username = event.target.elements.username.value
+ if (username === "") {
+ error("no username specified")
+ return false
+ }
+ enrollStart(username)
+}
+function enrollStart(username) {
+ let challenge = new Uint8Array(32)
+ window.crypto.getRandomValues(challenge)
+ let userid = new Uint8Array(8)
+ window.crypto.getRandomValues(userid)
+
+ console.log("challenge:" + btoa(challenge))
+ console.log("userid:" + btoa(userid))
+
+ let pkopts = {
+ challenge: challenge,
+ rp: {
+ name: window.location.host,
+ id: window.location.host,
+ },
+ user: {
+ id: userid,
+ name: username,
+ displayName: username,
+ },
+ authenticatorSelection: {
+ authenticatorAttachment: "cross-platform",
+ userVerification: "discouraged",
+ },
+ pubKeyCredParams: [{alg: -7, type: "public-key"}], // ES256
+ timeout: 30 * 1000,
+ };
+ console.dir(pkopts)
+ window.enrollOpts = pkopts
+ let credpromise = navigator.credentials.create({ publicKey: pkopts });
+ credpromise.then(enrollSuccess, enrollFailure)
+}
+function enrollFailure(result) {
+ error("Enroll failed", result)
+}
+function enrollSuccess(result) {
+ console.log("Enroll succeeded")
+ console.dir(result)
+ window.enrollResult = result
+ document.getElementById("enrollresult").style.visibility = "visible"
+
+ // Show the clientData
+ let u8dec = new TextDecoder('utf-8')
+ clientData = u8dec.decode(result.response.clientDataJSON)
+ document.getElementById("enrollresultjson").innerText = clientData
+
+ // Show the raw key handle.
+ document.getElementById("keyhandle").innerText = hexdump(result.rawId)
+
+ // Decode and show the attestationObject
+ document.getElementById("enrollresultraw").innerText = hexdump(result.response.attestationObject)
+ let aod = new CBORDecode(result.response.attestationObject)
+ let attestationObject = aod.decode()
+ console.log("attestationObject")
+ console.dir(attestationObject)
+ document.getElementById("enrollresultattestobj").innerText = JSON.stringify(attestationObject)
+
+ // Decode and show the authData
+ document.getElementById("enrollresultauthdataraw").innerText = hexdump(attestationObject.authData)
+ let authData = decodeAuthenticatorData(attestationObject.authData, true)
+ console.log("authData")
+ console.dir(authData)
+ window.enrollAuthData = authData
+ document.getElementById("enrollresultauthdata").innerText = JSON.stringify(authData)
+
+ // Reformat the pubkey as a SSH key for easy verification
+ window.rawKey = reformatPubkey(authData.attestedCredentialData.credentialPublicKey, window.enrollOpts.rp.id)
+ console.log("SSH pubkey blob")
+ console.dir(window.rawKey)
+ document.getElementById("enrollresultpkblob").innerText = hexdump(window.rawKey)
+ let pk64 = btoa(String.fromCharCode(...new Uint8Array(window.rawKey)));
+ let pk = "sk-ecdsa-sha2-nistp256@openssh.com " + pk64
+ document.getElementById("enrollresultpk").innerText = pk
+
+ // Format a private key too.
+ flags = 0x01 // SSH_SK_USER_PRESENCE_REQD
+ window.rawPrivkey = reformatPrivkey(authData.attestedCredentialData.credentialPublicKey, window.enrollOpts.rp.id, result.rawId, flags)
+ let privkeyFileBlob = privkeyFile(window.rawKey, window.rawPrivkey, window.enrollOpts.user.name, window.enrollOpts.rp.id)
+ let privk64 = btoa(String.fromCharCode(...new Uint8Array(privkeyFileBlob)));
+ let privkey = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + wrapString(privk64, 70) + "-----END OPENSSH PRIVATE KEY-----\n"
+ document.getElementById("enrollresultprivkey").innerText = privkey
+
+ // Success: show the assertion form.
+ document.getElementById("assertsection").style.visibility = "visible"
+}
+
+function decodeAuthenticatorData(authData, expectCred) {
+ let r = new Object()
+ let v = new DataView(authData)
+
+ r.rpIdHash = authData.slice(0, 32)
+ r.flags = v.getUint8(32)
+ r.signCount = v.getUint32(33)
+
+ // Decode attestedCredentialData if present.
+ let offset = 37
+ let acd = new Object()
+ if (expectCred) {
+ acd.aaguid = authData.slice(offset, offset+16)
+ offset += 16
+ let credentialIdLength = v.getUint16(offset)
+ offset += 2
+ acd.credentialIdLength = credentialIdLength
+ acd.credentialId = authData.slice(offset, offset+credentialIdLength)
+ offset += credentialIdLength
+ r.attestedCredentialData = acd
+ }
+ console.log("XXXXX " + offset.toString())
+ let pubkeyrest = authData.slice(offset, authData.byteLength)
+ let pkdecode = new CBORDecode(pubkeyrest)
+ if (expectCred) {
+ // XXX unsafe: doesn't mandate COSE canonical format.
+ acd.credentialPublicKey = pkdecode.decode()
+ }
+ if (!pkdecode.empty()) {
+ // Decode extensions if present.
+ r.extensions = pkdecode.decode()
+ }
+ return r
+}
+
+function wrapString(s, l) {
+ ret = ""
+ for (i = 0; i < s.length; i += l) {
+ ret += s.slice(i, i + l) + "\n"
+ }
+ return ret
+}
+
+function checkPubkey(pk) {
+ // pk is in COSE format. We only care about a tiny subset.
+ if (pk[1] != 2) {
+ console.dir(pk)
+ throw new Error("pubkey is not EC")
+ }
+ if (pk[-1] != 1) {
+ throw new Error("pubkey is not in P256")
+ }
+ if (pk[3] != -7) {
+ throw new Error("pubkey is not ES256")
+ }
+ if (pk[-2].byteLength != 32 || pk[-3].byteLength != 32) {
+ throw new Error("pubkey EC coords have bad length")
+ }
+}
+
+function reformatPubkey(pk, rpid) {
+ checkPubkey(pk)
+ let msg = new SSHMSG()
+ msg.putString("sk-ecdsa-sha2-nistp256@openssh.com") // Key type
+ msg.putString("nistp256") // Key curve
+ msg.putECPoint(pk[-2], pk[-3]) // EC key
+ msg.putString(rpid) // RP ID
+ return msg.serialise()
+}
+
+function reformatPrivkey(pk, rpid, kh, flags) {
+ checkPubkey(pk)
+ let msg = new SSHMSG()
+ msg.putString("sk-ecdsa-sha2-nistp256@openssh.com") // Key type
+ msg.putString("nistp256") // Key curve
+ msg.putECPoint(pk[-2], pk[-3]) // EC key
+ msg.putString(rpid) // RP ID
+ msg.putU8(flags) // flags
+ msg.putBytes(kh) // handle
+ msg.putString("") // reserved
+ return msg.serialise()
+}
+
+function privkeyFile(pub, priv, user, rp) {
+ let innerMsg = new SSHMSG()
+ innerMsg.putU32(0xdeadbeef) // check byte
+ innerMsg.putU32(0xdeadbeef) // check byte
+ innerMsg.put(priv) // privkey
+ innerMsg.putString("webauthn.html " + user + "@" + rp) // comment
+ // Pad to cipher blocksize (8).
+ p = 1
+ while (innerMsg.length() % 8 != 0) {
+ innerMsg.putU8(p++)
+ }
+ let msg = new SSHMSG()
+ msg.putStringRaw("openssh-key-v1") // Magic
+ msg.putU8(0) // \0 terminate
+ msg.putString("none") // cipher
+ msg.putString("none") // KDF
+ msg.putString("") // KDF options
+ msg.putU32(1) // nkeys
+ msg.putBytes(pub) // pubkey
+ msg.putSSHMSG(innerMsg) // inner
+ //msg.put(innerMsg.serialise()) // inner
+ return msg.serialise()
+}
+
+async function assertform_submit(event) {
+ event.preventDefault();
+ console.log("submitted")
+ message = event.target.elements.message.value
+ if (message === "") {
+ error("no message specified")
+ return false
+ }
+ let enc = new TextEncoder()
+ let encmsg = enc.encode(message)
+ window.assertSignRaw = !event.target.elements.message_sshsig.checked
+ console.log("using sshsig ", !window.assertSignRaw)
+ if (window.assertSignRaw) {
+ assertStart(encmsg)
+ return
+ }
+ // Format a sshsig-style message.
+ window.sigHashAlg = "sha512"
+ let msghash = await crypto.subtle.digest("SHA-512", encmsg);
+ console.log("raw message hash")
+ console.dir(msghash)
+ window.sigNamespace = event.target.elements.message_namespace.value
+ let sigbuf = new SSHMSG()
+ sigbuf.put(enc.encode("SSHSIG"))
+ sigbuf.putString(window.sigNamespace)
+ sigbuf.putU32(0) // Reserved string
+ sigbuf.putString(window.sigHashAlg)
+ sigbuf.putBytes(msghash)
+ let msg = sigbuf.serialise()
+ console.log("sigbuf")
+ console.dir(msg)
+ assertStart(msg)
+}
+
+function assertStart(message) {
+ let assertReqOpts = {
+ challenge: message,
+ rpId: window.location.host,
+ allowCredentials: [{
+ type: 'public-key',
+ id: window.enrollResult.rawId,
+ }],
+ userVerification: "discouraged",
+ timeout: (30 * 1000),
+ }
+ console.log("assertReqOpts")
+ console.dir(assertReqOpts)
+ window.assertReqOpts = assertReqOpts
+ let assertpromise = navigator.credentials.get({
+ publicKey: assertReqOpts
+ });
+ assertpromise.then(assertSuccess, assertFailure)
+}
+function assertFailure(result) {
+ error("Assertion failed", result)
+}
+function linewrap(s) {
+ const linelen = 70
+ let ret = ""
+ for (let i = 0; i < s.length; i += linelen) {
+ end = i + linelen
+ if (end > s.length) {
+ end = s.length
+ }
+ if (i > 0) {
+ ret += "\n"
+ }
+ ret += s.slice(i, end)
+ }
+ return ret + "\n"
+}
+function assertSuccess(result) {
+ console.log("Assertion succeeded")
+ console.dir(result)
+ window.assertResult = result
+ document.getElementById("assertresult").style.visibility = "visible"
+
+ // show the clientData.
+ let u8dec = new TextDecoder('utf-8')
+ clientData = u8dec.decode(result.response.clientDataJSON)
+ document.getElementById("assertresultjson").innerText = clientData
+
+ // show the signature.
+ document.getElementById("assertresultsigraw").innerText = hexdump(result.response.signature)
+
+ // decode and show the authData.
+ document.getElementById("assertresultauthdataraw").innerText = hexdump(result.response.authenticatorData)
+ authData = decodeAuthenticatorData(result.response.authenticatorData, false)
+ document.getElementById("assertresultauthdata").innerText = JSON.stringify(authData)
+
+ // Parse and reformat the signature to an SSH style signature.
+ let sshsig = reformatSignature(result.response.signature, clientData, authData)
+ document.getElementById("assertresultsshsigraw").innerText = hexdump(sshsig)
+ let sig64 = btoa(String.fromCharCode(...new Uint8Array(sshsig)));
+ if (window.assertSignRaw) {
+ document.getElementById("assertresultsshsigb64").innerText = sig64
+ } else {
+ document.getElementById("assertresultsshsigb64").innerText =
+ "-----BEGIN SSH SIGNATURE-----\n" + linewrap(sig64) +
+ "-----END SSH SIGNATURE-----\n";
+ }
+}
+
+function reformatSignature(sig, clientData, authData) {
+ if (sig.byteLength < 2) {
+ throw new Error("signature is too short")
+ }
+ let offset = 0
+ let v = new DataView(sig)
+ // Expect an ASN.1 SEQUENCE that exactly spans the signature.
+ if (v.getUint8(offset) != 0x30) {
+ throw new Error("signature not an ASN.1 sequence")
+ }
+ offset++
+ let seqlen = v.getUint8(offset)
+ offset++
+ if ((seqlen & 0x80) != 0 || seqlen != sig.byteLength - offset) {
+ throw new Error("signature has unexpected length " + seqlen.toString() + " vs expected " + (sig.byteLength - offset).toString())
+ }
+
+ // Parse 'r' INTEGER value.
+ if (v.getUint8(offset) != 0x02) {
+ throw new Error("signature r not an ASN.1 integer")
+ }
+ offset++
+ let rlen = v.getUint8(offset)
+ offset++
+ if ((rlen & 0x80) != 0 || rlen > sig.byteLength - offset) {
+ throw new Error("signature r has unexpected length " + rlen.toString() + " vs buffer " + (sig.byteLength - offset).toString())
+ }
+ let r = sig.slice(offset, offset + rlen)
+ offset += rlen
+ console.log("sig_r")
+ console.dir(r)
+
+ // Parse 's' INTEGER value.
+ if (v.getUint8(offset) != 0x02) {
+ throw new Error("signature r not an ASN.1 integer")
+ }
+ offset++
+ let slen = v.getUint8(offset)
+ offset++
+ if ((slen & 0x80) != 0 || slen > sig.byteLength - offset) {
+ throw new Error("signature s has unexpected length " + slen.toString() + " vs buffer " + (sig.byteLength - offset).toString())
+ }
+ let s = sig.slice(offset, offset + slen)
+ console.log("sig_s")
+ console.dir(s)
+ offset += slen
+
+ if (offset != sig.byteLength) {
+ throw new Error("unexpected final offset during signature parsing " + offset.toString() + " expected " + sig.byteLength.toString())
+ }
+
+ // Reformat as an SSH signature.
+ let clientDataParsed = JSON.parse(clientData)
+ let innersig = new SSHMSG()
+ innersig.putBytes(r)
+ innersig.putBytes(s)
+
+ let rawsshsig = new SSHMSG()
+ rawsshsig.putString("webauthn-sk-ecdsa-sha2-nistp256@openssh.com")
+ rawsshsig.putSSHMSG(innersig)
+ rawsshsig.putU8(authData.flags)
+ rawsshsig.putU32(authData.signCount)
+ rawsshsig.putString(clientDataParsed.origin)
+ rawsshsig.putString(clientData)
+ if (authData.extensions == undefined) {
+ rawsshsig.putU32(0)
+ } else {
+ rawsshsig.putBytes(authData.extensions)
+ }
+
+ if (window.assertSignRaw) {
+ return rawsshsig.serialise()
+ }
+ // Format as SSHSIG.
+ let enc = new TextEncoder()
+ let sshsig = new SSHMSG()
+ sshsig.put(enc.encode("SSHSIG"))
+ sshsig.putU32(0x01) // Signature version.
+ sshsig.putBytes(window.rawKey)
+ sshsig.putString(window.sigNamespace)
+ sshsig.putU32(0) // Reserved string
+ sshsig.putString(window.sigHashAlg)
+ sshsig.putBytes(rawsshsig.serialise())
+ return sshsig.serialise()
+}
+
+function toggleNamespaceVisibility() {
+ const assertsigtype = document.getElementById('message_sshsig');
+ const assertsignamespace = document.getElementById('message_namespace');
+ assertsignamespace.disabled = !assertsigtype.checked;
+}
+
+function init() {
+ if (document.location.protocol != "https:") {
+ error("This page must be loaded via https")
+ const assertsubmit = document.getElementById('assertsubmit')
+ assertsubmit.disabled = true
+ }
+ const enrollform = document.getElementById('enrollform');
+ enrollform.addEventListener('submit', enrollform_submit);
+ const assertform = document.getElementById('assertform');
+ assertform.addEventListener('submit', assertform_submit);
+ const assertsigtype = document.getElementById('message_sshsig');
+ assertsigtype.onclick = toggleNamespaceVisibility;
+}
+</script>
+
+</html>
diff --git a/regress/unittests/test_helper/Makefile b/regress/unittests/test_helper/Makefile
new file mode 100644
index 0000000..78026e6
--- /dev/null
+++ b/regress/unittests/test_helper/Makefile
@@ -0,0 +1,15 @@
+# $OpenBSD: Makefile,v 1.3 2016/07/04 18:01:44 guenther Exp $
+
+LIB= test_helper
+SRCS= test_helper.c fuzz.c
+
+NOPROFILE= yes
+NOPIC= yes
+
+# Hack to allow building with SUBDIR in ../../Makefile
+regress: all
+
+install:
+ @echo -n
+
+.include <bsd.lib.mk>
diff --git a/regress/unittests/test_helper/fuzz.c b/regress/unittests/test_helper/fuzz.c
new file mode 100644
index 0000000..78b3665
--- /dev/null
+++ b/regress/unittests/test_helper/fuzz.c
@@ -0,0 +1,438 @@
+/* $OpenBSD: fuzz.c,v 1.8 2015/03/03 20:42:49 djm Exp $ */
+/*
+ * Copyright (c) 2011 Damien Miller <djm@mindrot.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* Utility functions/framework for fuzz tests */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "test_helper.h"
+#include "atomicio.h"
+
+/* #define FUZZ_DEBUG */
+
+#ifdef FUZZ_DEBUG
+# define FUZZ_DBG(x) do { \
+ printf("%s:%d %s: ", __FILE__, __LINE__, __func__); \
+ printf x; \
+ printf("\n"); \
+ fflush(stdout); \
+ } while (0)
+#else
+# define FUZZ_DBG(x)
+#endif
+
+/* For brevity later */
+typedef unsigned long long fuzz_ullong;
+
+/* For base-64 fuzzing */
+static const char fuzz_b64chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+struct fuzz {
+ /* Fuzz method currently in use */
+ int strategy;
+
+ /* Fuzz methods remaining */
+ int strategies;
+
+ /* Original seed data blob */
+ void *seed;
+ size_t slen;
+
+ /* Current working copy of seed with fuzz mutations applied */
+ u_char *fuzzed;
+
+ /* Used by fuzz methods */
+ size_t o1, o2;
+};
+
+static const char *
+fuzz_ntop(u_int n)
+{
+ switch (n) {
+ case 0:
+ return "NONE";
+ case FUZZ_1_BIT_FLIP:
+ return "FUZZ_1_BIT_FLIP";
+ case FUZZ_2_BIT_FLIP:
+ return "FUZZ_2_BIT_FLIP";
+ case FUZZ_1_BYTE_FLIP:
+ return "FUZZ_1_BYTE_FLIP";
+ case FUZZ_2_BYTE_FLIP:
+ return "FUZZ_2_BYTE_FLIP";
+ case FUZZ_TRUNCATE_START:
+ return "FUZZ_TRUNCATE_START";
+ case FUZZ_TRUNCATE_END:
+ return "FUZZ_TRUNCATE_END";
+ case FUZZ_BASE64:
+ return "FUZZ_BASE64";
+ default:
+ abort();
+ }
+}
+
+static int
+fuzz_fmt(struct fuzz *fuzz, char *s, size_t n)
+{
+ if (fuzz == NULL)
+ return -1;
+
+ switch (fuzz->strategy) {
+ case FUZZ_1_BIT_FLIP:
+ snprintf(s, n, "%s case %zu of %zu (bit: %zu)\n",
+ fuzz_ntop(fuzz->strategy),
+ fuzz->o1, fuzz->slen * 8, fuzz->o1);
+ return 0;
+ case FUZZ_2_BIT_FLIP:
+ snprintf(s, n, "%s case %llu of %llu (bits: %zu, %zu)\n",
+ fuzz_ntop(fuzz->strategy),
+ (((fuzz_ullong)fuzz->o2) * fuzz->slen * 8) + fuzz->o1,
+ ((fuzz_ullong)fuzz->slen * 8) * fuzz->slen * 8,
+ fuzz->o1, fuzz->o2);
+ return 0;
+ case FUZZ_1_BYTE_FLIP:
+ snprintf(s, n, "%s case %zu of %zu (byte: %zu)\n",
+ fuzz_ntop(fuzz->strategy),
+ fuzz->o1, fuzz->slen, fuzz->o1);
+ return 0;
+ case FUZZ_2_BYTE_FLIP:
+ snprintf(s, n, "%s case %llu of %llu (bytes: %zu, %zu)\n",
+ fuzz_ntop(fuzz->strategy),
+ (((fuzz_ullong)fuzz->o2) * fuzz->slen) + fuzz->o1,
+ ((fuzz_ullong)fuzz->slen) * fuzz->slen,
+ fuzz->o1, fuzz->o2);
+ return 0;
+ case FUZZ_TRUNCATE_START:
+ snprintf(s, n, "%s case %zu of %zu (offset: %zu)\n",
+ fuzz_ntop(fuzz->strategy),
+ fuzz->o1, fuzz->slen, fuzz->o1);
+ return 0;
+ case FUZZ_TRUNCATE_END:
+ snprintf(s, n, "%s case %zu of %zu (offset: %zu)\n",
+ fuzz_ntop(fuzz->strategy),
+ fuzz->o1, fuzz->slen, fuzz->o1);
+ return 0;
+ case FUZZ_BASE64:
+ assert(fuzz->o2 < sizeof(fuzz_b64chars) - 1);
+ snprintf(s, n, "%s case %llu of %llu (offset: %zu char: %c)\n",
+ fuzz_ntop(fuzz->strategy),
+ (fuzz->o1 * (fuzz_ullong)64) + fuzz->o2,
+ fuzz->slen * (fuzz_ullong)64, fuzz->o1,
+ fuzz_b64chars[fuzz->o2]);
+ return 0;
+ default:
+ return -1;
+ abort();
+ }
+}
+
+static void
+dump(u_char *p, size_t len)
+{
+ size_t i, j;
+
+ for (i = 0; i < len; i += 16) {
+ fprintf(stderr, "%.4zd: ", i);
+ for (j = i; j < i + 16; j++) {
+ if (j < len)
+ fprintf(stderr, "%02x ", p[j]);
+ else
+ fprintf(stderr, " ");
+ }
+ fprintf(stderr, " ");
+ for (j = i; j < i + 16; j++) {
+ if (j < len) {
+ if (isascii(p[j]) && isprint(p[j]))
+ fprintf(stderr, "%c", p[j]);
+ else
+ fprintf(stderr, ".");
+ }
+ }
+ fprintf(stderr, "\n");
+ }
+}
+
+void
+fuzz_dump(struct fuzz *fuzz)
+{
+ char buf[256];
+
+ if (fuzz_fmt(fuzz, buf, sizeof(buf)) != 0) {
+ fprintf(stderr, "%s: fuzz invalid\n", __func__);
+ abort();
+ }
+ fputs(buf, stderr);
+ fprintf(stderr, "fuzz original %p len = %zu\n", fuzz->seed, fuzz->slen);
+ dump(fuzz->seed, fuzz->slen);
+ fprintf(stderr, "fuzz context %p len = %zu\n", fuzz, fuzz_len(fuzz));
+ dump(fuzz_ptr(fuzz), fuzz_len(fuzz));
+}
+
+static struct fuzz *last_fuzz;
+
+static void
+siginfo(int unused __attribute__((__unused__)))
+{
+ char buf[256];
+
+ test_info(buf, sizeof(buf));
+ atomicio(vwrite, STDERR_FILENO, buf, strlen(buf));
+ if (last_fuzz != NULL) {
+ fuzz_fmt(last_fuzz, buf, sizeof(buf));
+ atomicio(vwrite, STDERR_FILENO, buf, strlen(buf));
+ }
+}
+
+struct fuzz *
+fuzz_begin(u_int strategies, const void *p, size_t l)
+{
+ struct fuzz *ret = calloc(sizeof(*ret), 1);
+
+ assert(p != NULL);
+ assert(ret != NULL);
+ ret->seed = malloc(l);
+ assert(ret->seed != NULL);
+ memcpy(ret->seed, p, l);
+ ret->slen = l;
+ ret->strategies = strategies;
+
+ assert(ret->slen < SIZE_MAX / 8);
+ assert(ret->strategies <= (FUZZ_MAX|(FUZZ_MAX-1)));
+
+ FUZZ_DBG(("begin, ret = %p", ret));
+
+ fuzz_next(ret);
+
+ last_fuzz = ret;
+#ifdef SIGINFO
+ signal(SIGINFO, siginfo);
+#endif
+ signal(SIGUSR1, siginfo);
+
+ return ret;
+}
+
+void
+fuzz_cleanup(struct fuzz *fuzz)
+{
+ FUZZ_DBG(("cleanup, fuzz = %p", fuzz));
+ last_fuzz = NULL;
+#ifdef SIGINFO
+ signal(SIGINFO, SIG_DFL);
+#endif
+ signal(SIGUSR1, SIG_DFL);
+ assert(fuzz != NULL);
+ assert(fuzz->seed != NULL);
+ assert(fuzz->fuzzed != NULL);
+ free(fuzz->seed);
+ free(fuzz->fuzzed);
+ free(fuzz);
+}
+
+static int
+fuzz_strategy_done(struct fuzz *fuzz)
+{
+ FUZZ_DBG(("fuzz = %p, strategy = %s, o1 = %zu, o2 = %zu, slen = %zu",
+ fuzz, fuzz_ntop(fuzz->strategy), fuzz->o1, fuzz->o2, fuzz->slen));
+
+ switch (fuzz->strategy) {
+ case FUZZ_1_BIT_FLIP:
+ return fuzz->o1 >= fuzz->slen * 8;
+ case FUZZ_2_BIT_FLIP:
+ return fuzz->o2 >= fuzz->slen * 8;
+ case FUZZ_2_BYTE_FLIP:
+ return fuzz->o2 >= fuzz->slen;
+ case FUZZ_1_BYTE_FLIP:
+ case FUZZ_TRUNCATE_START:
+ case FUZZ_TRUNCATE_END:
+ case FUZZ_BASE64:
+ return fuzz->o1 >= fuzz->slen;
+ default:
+ abort();
+ }
+}
+
+void
+fuzz_next(struct fuzz *fuzz)
+{
+ u_int i;
+
+ FUZZ_DBG(("start, fuzz = %p, strategy = %s, strategies = 0x%lx, "
+ "o1 = %zu, o2 = %zu, slen = %zu", fuzz, fuzz_ntop(fuzz->strategy),
+ (u_long)fuzz->strategies, fuzz->o1, fuzz->o2, fuzz->slen));
+
+ if (fuzz->strategy == 0 || fuzz_strategy_done(fuzz)) {
+ /* If we are just starting out, we need to allocate too */
+ if (fuzz->fuzzed == NULL) {
+ FUZZ_DBG(("alloc"));
+ fuzz->fuzzed = calloc(fuzz->slen, 1);
+ }
+ /* Pick next strategy */
+ FUZZ_DBG(("advance"));
+ for (i = 1; i <= FUZZ_MAX; i <<= 1) {
+ if ((fuzz->strategies & i) != 0) {
+ fuzz->strategy = i;
+ break;
+ }
+ }
+ FUZZ_DBG(("selected = %u", fuzz->strategy));
+ if (fuzz->strategy == 0) {
+ FUZZ_DBG(("done, no more strategies"));
+ return;
+ }
+ fuzz->strategies &= ~(fuzz->strategy);
+ fuzz->o1 = fuzz->o2 = 0;
+ }
+
+ assert(fuzz->fuzzed != NULL);
+
+ switch (fuzz->strategy) {
+ case FUZZ_1_BIT_FLIP:
+ assert(fuzz->o1 / 8 < fuzz->slen);
+ memcpy(fuzz->fuzzed, fuzz->seed, fuzz->slen);
+ fuzz->fuzzed[fuzz->o1 / 8] ^= 1 << (fuzz->o1 % 8);
+ fuzz->o1++;
+ break;
+ case FUZZ_2_BIT_FLIP:
+ assert(fuzz->o1 / 8 < fuzz->slen);
+ assert(fuzz->o2 / 8 < fuzz->slen);
+ memcpy(fuzz->fuzzed, fuzz->seed, fuzz->slen);
+ fuzz->fuzzed[fuzz->o1 / 8] ^= 1 << (fuzz->o1 % 8);
+ fuzz->fuzzed[fuzz->o2 / 8] ^= 1 << (fuzz->o2 % 8);
+ fuzz->o1++;
+ if (fuzz->o1 >= fuzz->slen * 8) {
+ fuzz->o1 = 0;
+ fuzz->o2++;
+ }
+ break;
+ case FUZZ_1_BYTE_FLIP:
+ assert(fuzz->o1 < fuzz->slen);
+ memcpy(fuzz->fuzzed, fuzz->seed, fuzz->slen);
+ fuzz->fuzzed[fuzz->o1] ^= 0xff;
+ fuzz->o1++;
+ break;
+ case FUZZ_2_BYTE_FLIP:
+ assert(fuzz->o1 < fuzz->slen);
+ assert(fuzz->o2 < fuzz->slen);
+ memcpy(fuzz->fuzzed, fuzz->seed, fuzz->slen);
+ fuzz->fuzzed[fuzz->o1] ^= 0xff;
+ fuzz->fuzzed[fuzz->o2] ^= 0xff;
+ fuzz->o1++;
+ if (fuzz->o1 >= fuzz->slen) {
+ fuzz->o1 = 0;
+ fuzz->o2++;
+ }
+ break;
+ case FUZZ_TRUNCATE_START:
+ case FUZZ_TRUNCATE_END:
+ assert(fuzz->o1 < fuzz->slen);
+ memcpy(fuzz->fuzzed, fuzz->seed, fuzz->slen);
+ fuzz->o1++;
+ break;
+ case FUZZ_BASE64:
+ assert(fuzz->o1 < fuzz->slen);
+ assert(fuzz->o2 < sizeof(fuzz_b64chars) - 1);
+ memcpy(fuzz->fuzzed, fuzz->seed, fuzz->slen);
+ fuzz->fuzzed[fuzz->o1] = fuzz_b64chars[fuzz->o2];
+ fuzz->o2++;
+ if (fuzz->o2 >= sizeof(fuzz_b64chars) - 1) {
+ fuzz->o2 = 0;
+ fuzz->o1++;
+ }
+ break;
+ default:
+ abort();
+ }
+
+ FUZZ_DBG(("done, fuzz = %p, strategy = %s, strategies = 0x%lx, "
+ "o1 = %zu, o2 = %zu, slen = %zu", fuzz, fuzz_ntop(fuzz->strategy),
+ (u_long)fuzz->strategies, fuzz->o1, fuzz->o2, fuzz->slen));
+}
+
+int
+fuzz_matches_original(struct fuzz *fuzz)
+{
+ if (fuzz_len(fuzz) != fuzz->slen)
+ return 0;
+ return memcmp(fuzz_ptr(fuzz), fuzz->seed, fuzz->slen) == 0;
+}
+
+int
+fuzz_done(struct fuzz *fuzz)
+{
+ FUZZ_DBG(("fuzz = %p, strategies = 0x%lx", fuzz,
+ (u_long)fuzz->strategies));
+
+ return fuzz_strategy_done(fuzz) && fuzz->strategies == 0;
+}
+
+size_t
+fuzz_len(struct fuzz *fuzz)
+{
+ assert(fuzz->fuzzed != NULL);
+ switch (fuzz->strategy) {
+ case FUZZ_1_BIT_FLIP:
+ case FUZZ_2_BIT_FLIP:
+ case FUZZ_1_BYTE_FLIP:
+ case FUZZ_2_BYTE_FLIP:
+ case FUZZ_BASE64:
+ return fuzz->slen;
+ case FUZZ_TRUNCATE_START:
+ case FUZZ_TRUNCATE_END:
+ assert(fuzz->o1 <= fuzz->slen);
+ return fuzz->slen - fuzz->o1;
+ default:
+ abort();
+ }
+}
+
+u_char *
+fuzz_ptr(struct fuzz *fuzz)
+{
+ assert(fuzz->fuzzed != NULL);
+ switch (fuzz->strategy) {
+ case FUZZ_1_BIT_FLIP:
+ case FUZZ_2_BIT_FLIP:
+ case FUZZ_1_BYTE_FLIP:
+ case FUZZ_2_BYTE_FLIP:
+ case FUZZ_BASE64:
+ return fuzz->fuzzed;
+ case FUZZ_TRUNCATE_START:
+ assert(fuzz->o1 <= fuzz->slen);
+ return fuzz->fuzzed + fuzz->o1;
+ case FUZZ_TRUNCATE_END:
+ assert(fuzz->o1 <= fuzz->slen);
+ return fuzz->fuzzed;
+ default:
+ abort();
+ }
+}
+
diff --git a/regress/unittests/test_helper/test_helper.c b/regress/unittests/test_helper/test_helper.c
new file mode 100644
index 0000000..6461d7f
--- /dev/null
+++ b/regress/unittests/test_helper/test_helper.c
@@ -0,0 +1,595 @@
+/* $OpenBSD: test_helper.c,v 1.13 2021/12/14 21:25:27 deraadt Exp $ */
+/*
+ * Copyright (c) 2011 Damien Miller <djm@mindrot.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* Utility functions/framework for regress tests */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include <stdarg.h>
+#include <fcntl.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#include <signal.h>
+
+#ifdef WITH_OPENSSL
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#endif
+
+#if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS)
+# include <vis.h>
+#endif
+
+#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
+
+#include "entropy.h"
+#include "test_helper.h"
+#include "atomicio.h"
+
+#define TEST_CHECK_INT(r, pred) do { \
+ switch (pred) { \
+ case TEST_EQ: \
+ if (r == 0) \
+ return; \
+ break; \
+ case TEST_NE: \
+ if (r != 0) \
+ return; \
+ break; \
+ case TEST_LT: \
+ if (r < 0) \
+ return; \
+ break; \
+ case TEST_LE: \
+ if (r <= 0) \
+ return; \
+ break; \
+ case TEST_GT: \
+ if (r > 0) \
+ return; \
+ break; \
+ case TEST_GE: \
+ if (r >= 0) \
+ return; \
+ break; \
+ default: \
+ abort(); \
+ } \
+ } while (0)
+
+#define TEST_CHECK(x1, x2, pred) do { \
+ switch (pred) { \
+ case TEST_EQ: \
+ if (x1 == x2) \
+ return; \
+ break; \
+ case TEST_NE: \
+ if (x1 != x2) \
+ return; \
+ break; \
+ case TEST_LT: \
+ if (x1 < x2) \
+ return; \
+ break; \
+ case TEST_LE: \
+ if (x1 <= x2) \
+ return; \
+ break; \
+ case TEST_GT: \
+ if (x1 > x2) \
+ return; \
+ break; \
+ case TEST_GE: \
+ if (x1 >= x2) \
+ return; \
+ break; \
+ default: \
+ abort(); \
+ } \
+ } while (0)
+
+extern char *__progname;
+
+static int verbose_mode = 0;
+static int quiet_mode = 0;
+static char *active_test_name = NULL;
+static u_int test_number = 0;
+static test_onerror_func_t *test_onerror = NULL;
+static void *onerror_ctx = NULL;
+static const char *data_dir = NULL;
+static char subtest_info[512];
+static int fast = 0;
+static int slow = 0;
+
+int
+main(int argc, char **argv)
+{
+ int ch;
+
+ seed_rng();
+#ifdef WITH_OPENSSL
+ ERR_load_CRYPTO_strings();
+#endif
+
+ /* Handle systems without __progname */
+ if (__progname == NULL) {
+ __progname = strrchr(argv[0], '/');
+ if (__progname == NULL || __progname[1] == '\0')
+ __progname = argv[0];
+ else
+ __progname++;
+ if ((__progname = strdup(__progname)) == NULL) {
+ fprintf(stderr, "strdup failed\n");
+ exit(1);
+ }
+ }
+
+ while ((ch = getopt(argc, argv, "Ffvqd:")) != -1) {
+ switch (ch) {
+ case 'F':
+ slow = 1;
+ break;
+ case 'f':
+ fast = 1;
+ break;
+ case 'd':
+ data_dir = optarg;
+ break;
+ case 'q':
+ verbose_mode = 0;
+ quiet_mode = 1;
+ break;
+ case 'v':
+ verbose_mode = 1;
+ quiet_mode = 0;
+ break;
+ default:
+ fprintf(stderr, "Unrecognised command line option\n");
+ fprintf(stderr, "Usage: %s [-v]\n", __progname);
+ exit(1);
+ }
+ }
+ setvbuf(stdout, NULL, _IONBF, 0);
+ if (!quiet_mode)
+ printf("%s: ", __progname);
+ if (verbose_mode)
+ printf("\n");
+
+ tests();
+
+ if (!quiet_mode)
+ printf(" %u tests ok\n", test_number);
+ return 0;
+}
+
+int
+test_is_verbose(void)
+{
+ return verbose_mode;
+}
+
+int
+test_is_quiet(void)
+{
+ return quiet_mode;
+}
+
+int
+test_is_fast(void)
+{
+ return fast;
+}
+
+int
+test_is_slow(void)
+{
+ return slow;
+}
+
+const char *
+test_data_file(const char *name)
+{
+ static char ret[PATH_MAX];
+
+ if (data_dir != NULL)
+ snprintf(ret, sizeof(ret), "%s/%s", data_dir, name);
+ else
+ strlcpy(ret, name, sizeof(ret));
+ if (access(ret, F_OK) != 0) {
+ fprintf(stderr, "Cannot access data file %s: %s\n",
+ ret, strerror(errno));
+ exit(1);
+ }
+ return ret;
+}
+
+void
+test_info(char *s, size_t len)
+{
+ snprintf(s, len, "In test %u: \"%s\"%s%s\n", test_number,
+ active_test_name == NULL ? "<none>" : active_test_name,
+ *subtest_info != '\0' ? " - " : "", subtest_info);
+}
+
+static void
+siginfo(int unused __attribute__((__unused__)))
+{
+ char buf[256];
+
+ test_info(buf, sizeof(buf));
+ atomicio(vwrite, STDERR_FILENO, buf, strlen(buf));
+}
+
+void
+test_start(const char *n)
+{
+ assert(active_test_name == NULL);
+ assert((active_test_name = strdup(n)) != NULL);
+ *subtest_info = '\0';
+ if (verbose_mode)
+ printf("test %u - \"%s\": ", test_number, active_test_name);
+ test_number++;
+#ifdef SIGINFO
+ signal(SIGINFO, siginfo);
+#endif
+ signal(SIGUSR1, siginfo);
+}
+
+void
+set_onerror_func(test_onerror_func_t *f, void *ctx)
+{
+ test_onerror = f;
+ onerror_ctx = ctx;
+}
+
+void
+test_done(void)
+{
+ *subtest_info = '\0';
+ assert(active_test_name != NULL);
+ free(active_test_name);
+ active_test_name = NULL;
+ if (verbose_mode)
+ printf("OK\n");
+ else if (!quiet_mode) {
+ printf(".");
+ fflush(stdout);
+ }
+}
+
+void
+test_subtest_info(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(subtest_info, sizeof(subtest_info), fmt, ap);
+ va_end(ap);
+}
+
+void
+ssl_err_check(const char *file, int line)
+{
+#ifdef WITH_OPENSSL
+ long openssl_error = ERR_get_error();
+
+ if (openssl_error == 0)
+ return;
+
+ fprintf(stderr, "\n%s:%d: uncaught OpenSSL error: %s",
+ file, line, ERR_error_string(openssl_error, NULL));
+#else /* WITH_OPENSSL */
+ fprintf(stderr, "\n%s:%d: uncaught OpenSSL error ",
+ file, line);
+#endif /* WITH_OPENSSL */
+ abort();
+}
+
+static const char *
+pred_name(enum test_predicate p)
+{
+ switch (p) {
+ case TEST_EQ:
+ return "EQ";
+ case TEST_NE:
+ return "NE";
+ case TEST_LT:
+ return "LT";
+ case TEST_LE:
+ return "LE";
+ case TEST_GT:
+ return "GT";
+ case TEST_GE:
+ return "GE";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static void
+test_die(void)
+{
+ if (test_onerror != NULL)
+ test_onerror(onerror_ctx);
+ abort();
+}
+
+static void
+test_header(const char *file, int line, const char *a1, const char *a2,
+ const char *name, enum test_predicate pred)
+{
+ fprintf(stderr, "\n%s:%d test #%u \"%s\"%s%s\n",
+ file, line, test_number, active_test_name,
+ *subtest_info != '\0' ? " - " : "", subtest_info);
+ fprintf(stderr, "ASSERT_%s_%s(%s%s%s) failed:\n",
+ name, pred_name(pred), a1,
+ a2 != NULL ? ", " : "", a2 != NULL ? a2 : "");
+}
+
+#ifdef WITH_OPENSSL
+void
+assert_bignum(const char *file, int line, const char *a1, const char *a2,
+ const BIGNUM *aa1, const BIGNUM *aa2, enum test_predicate pred)
+{
+ int r = BN_cmp(aa1, aa2);
+
+ TEST_CHECK_INT(r, pred);
+ test_header(file, line, a1, a2, "BIGNUM", pred);
+ fprintf(stderr, "%12s = 0x%s\n", a1, BN_bn2hex(aa1));
+ fprintf(stderr, "%12s = 0x%s\n", a2, BN_bn2hex(aa2));
+ test_die();
+}
+#endif
+
+void
+assert_string(const char *file, int line, const char *a1, const char *a2,
+ const char *aa1, const char *aa2, enum test_predicate pred)
+{
+ int r;
+
+ /* Verify pointers are not NULL */
+ assert_ptr(file, line, a1, "NULL", aa1, NULL, TEST_NE);
+ assert_ptr(file, line, a2, "NULL", aa2, NULL, TEST_NE);
+
+ r = strcmp(aa1, aa2);
+ TEST_CHECK_INT(r, pred);
+ test_header(file, line, a1, a2, "STRING", pred);
+ fprintf(stderr, "%12s = %s (len %zu)\n", a1, aa1, strlen(aa1));
+ fprintf(stderr, "%12s = %s (len %zu)\n", a2, aa2, strlen(aa2));
+ test_die();
+}
+
+static char *
+tohex(const void *_s, size_t l)
+{
+ u_int8_t *s = (u_int8_t *)_s;
+ size_t i, j;
+ const char *hex = "0123456789abcdef";
+ char *r = malloc((l * 2) + 1);
+
+ assert(r != NULL);
+ for (i = j = 0; i < l; i++) {
+ r[j++] = hex[(s[i] >> 4) & 0xf];
+ r[j++] = hex[s[i] & 0xf];
+ }
+ r[j] = '\0';
+ return r;
+}
+
+void
+assert_mem(const char *file, int line, const char *a1, const char *a2,
+ const void *aa1, const void *aa2, size_t l, enum test_predicate pred)
+{
+ int r;
+ char *aa1_tohex = NULL;
+ char *aa2_tohex = NULL;
+
+ if (l == 0)
+ return;
+ /* If length is >0, then verify pointers are not NULL */
+ assert_ptr(file, line, a1, "NULL", aa1, NULL, TEST_NE);
+ assert_ptr(file, line, a2, "NULL", aa2, NULL, TEST_NE);
+
+ r = memcmp(aa1, aa2, l);
+ TEST_CHECK_INT(r, pred);
+ test_header(file, line, a1, a2, "STRING", pred);
+ aa1_tohex = tohex(aa1, MINIMUM(l, 256));
+ aa2_tohex = tohex(aa2, MINIMUM(l, 256));
+ fprintf(stderr, "%12s = %s (len %zu)\n", a1, aa1_tohex, l);
+ fprintf(stderr, "%12s = %s (len %zu)\n", a2, aa2_tohex, l);
+ free(aa1_tohex);
+ free(aa2_tohex);
+ test_die();
+}
+
+static int
+memvalcmp(const u_int8_t *s, u_char v, size_t l, size_t *where)
+{
+ size_t i;
+
+ for (i = 0; i < l; i++) {
+ if (s[i] != v) {
+ *where = i;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void
+assert_mem_filled(const char *file, int line, const char *a1,
+ const void *aa1, u_char v, size_t l, enum test_predicate pred)
+{
+ size_t where = -1;
+ int r;
+ char tmp[64];
+ char *aa1_tohex = NULL;
+
+ if (l == 0)
+ return;
+ /* If length is >0, then verify the pointer is not NULL */
+ assert_ptr(file, line, a1, "NULL", aa1, NULL, TEST_NE);
+
+ r = memvalcmp(aa1, v, l, &where);
+ TEST_CHECK_INT(r, pred);
+ test_header(file, line, a1, NULL, "MEM_ZERO", pred);
+ aa1_tohex = tohex(aa1, MINIMUM(l, 20));
+ fprintf(stderr, "%20s = %s%s (len %zu)\n", a1,
+ aa1_tohex, l > 20 ? "..." : "", l);
+ free(aa1_tohex);
+ snprintf(tmp, sizeof(tmp), "(%s)[%zu]", a1, where);
+ fprintf(stderr, "%20s = 0x%02x (expected 0x%02x)\n", tmp,
+ ((u_char *)aa1)[where], v);
+ test_die();
+}
+
+void
+assert_int(const char *file, int line, const char *a1, const char *a2,
+ int aa1, int aa2, enum test_predicate pred)
+{
+ TEST_CHECK(aa1, aa2, pred);
+ test_header(file, line, a1, a2, "INT", pred);
+ fprintf(stderr, "%12s = %d\n", a1, aa1);
+ fprintf(stderr, "%12s = %d\n", a2, aa2);
+ test_die();
+}
+
+void
+assert_size_t(const char *file, int line, const char *a1, const char *a2,
+ size_t aa1, size_t aa2, enum test_predicate pred)
+{
+ TEST_CHECK(aa1, aa2, pred);
+ test_header(file, line, a1, a2, "SIZE_T", pred);
+ fprintf(stderr, "%12s = %zu\n", a1, aa1);
+ fprintf(stderr, "%12s = %zu\n", a2, aa2);
+ test_die();
+}
+
+void
+assert_u_int(const char *file, int line, const char *a1, const char *a2,
+ u_int aa1, u_int aa2, enum test_predicate pred)
+{
+ TEST_CHECK(aa1, aa2, pred);
+ test_header(file, line, a1, a2, "U_INT", pred);
+ fprintf(stderr, "%12s = %u / 0x%x\n", a1, aa1, aa1);
+ fprintf(stderr, "%12s = %u / 0x%x\n", a2, aa2, aa2);
+ test_die();
+}
+
+void
+assert_long(const char *file, int line, const char *a1, const char *a2,
+ long aa1, long aa2, enum test_predicate pred)
+{
+ TEST_CHECK(aa1, aa2, pred);
+ test_header(file, line, a1, a2, "LONG", pred);
+ fprintf(stderr, "%12s = %ld / 0x%lx\n", a1, aa1, aa1);
+ fprintf(stderr, "%12s = %ld / 0x%lx\n", a2, aa2, aa2);
+ test_die();
+}
+
+void
+assert_long_long(const char *file, int line, const char *a1, const char *a2,
+ long long aa1, long long aa2, enum test_predicate pred)
+{
+ TEST_CHECK(aa1, aa2, pred);
+ test_header(file, line, a1, a2, "LONG LONG", pred);
+ fprintf(stderr, "%12s = %lld / 0x%llx\n", a1, aa1, aa1);
+ fprintf(stderr, "%12s = %lld / 0x%llx\n", a2, aa2, aa2);
+ test_die();
+}
+
+void
+assert_char(const char *file, int line, const char *a1, const char *a2,
+ char aa1, char aa2, enum test_predicate pred)
+{
+ char buf[8];
+
+ TEST_CHECK(aa1, aa2, pred);
+ test_header(file, line, a1, a2, "CHAR", pred);
+ fprintf(stderr, "%12s = '%s' / 0x02%x\n", a1,
+ vis(buf, aa1, VIS_SAFE|VIS_NL|VIS_TAB|VIS_OCTAL, 0), aa1);
+ fprintf(stderr, "%12s = '%s' / 0x02%x\n", a1,
+ vis(buf, aa2, VIS_SAFE|VIS_NL|VIS_TAB|VIS_OCTAL, 0), aa2);
+ test_die();
+}
+
+void
+assert_u8(const char *file, int line, const char *a1, const char *a2,
+ u_int8_t aa1, u_int8_t aa2, enum test_predicate pred)
+{
+ TEST_CHECK(aa1, aa2, pred);
+ test_header(file, line, a1, a2, "U8", pred);
+ fprintf(stderr, "%12s = 0x%02x %u\n", a1, aa1, aa1);
+ fprintf(stderr, "%12s = 0x%02x %u\n", a2, aa2, aa2);
+ test_die();
+}
+
+void
+assert_u16(const char *file, int line, const char *a1, const char *a2,
+ u_int16_t aa1, u_int16_t aa2, enum test_predicate pred)
+{
+ TEST_CHECK(aa1, aa2, pred);
+ test_header(file, line, a1, a2, "U16", pred);
+ fprintf(stderr, "%12s = 0x%04x %u\n", a1, aa1, aa1);
+ fprintf(stderr, "%12s = 0x%04x %u\n", a2, aa2, aa2);
+ test_die();
+}
+
+void
+assert_u32(const char *file, int line, const char *a1, const char *a2,
+ u_int32_t aa1, u_int32_t aa2, enum test_predicate pred)
+{
+ TEST_CHECK(aa1, aa2, pred);
+ test_header(file, line, a1, a2, "U32", pred);
+ fprintf(stderr, "%12s = 0x%08x %u\n", a1, aa1, aa1);
+ fprintf(stderr, "%12s = 0x%08x %u\n", a2, aa2, aa2);
+ test_die();
+}
+
+void
+assert_u64(const char *file, int line, const char *a1, const char *a2,
+ u_int64_t aa1, u_int64_t aa2, enum test_predicate pred)
+{
+ TEST_CHECK(aa1, aa2, pred);
+ test_header(file, line, a1, a2, "U64", pred);
+ fprintf(stderr, "%12s = 0x%016llx %llu\n", a1,
+ (unsigned long long)aa1, (unsigned long long)aa1);
+ fprintf(stderr, "%12s = 0x%016llx %llu\n", a2,
+ (unsigned long long)aa2, (unsigned long long)aa2);
+ test_die();
+}
+
+void
+assert_ptr(const char *file, int line, const char *a1, const char *a2,
+ const void *aa1, const void *aa2, enum test_predicate pred)
+{
+ TEST_CHECK(aa1, aa2, pred);
+ test_header(file, line, a1, a2, "PTR", pred);
+ fprintf(stderr, "%12s = %p\n", a1, aa1);
+ fprintf(stderr, "%12s = %p\n", a2, aa2);
+ test_die();
+}
+
diff --git a/regress/unittests/test_helper/test_helper.h b/regress/unittests/test_helper/test_helper.h
new file mode 100644
index 0000000..6630220
--- /dev/null
+++ b/regress/unittests/test_helper/test_helper.h
@@ -0,0 +1,326 @@
+/* $OpenBSD: test_helper.h,v 1.9 2018/10/17 23:28:05 djm Exp $ */
+/*
+ * Copyright (c) 2011 Damien Miller <djm@mindrot.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* Utility functions/framework for regress tests */
+
+#ifndef _TEST_HELPER_H
+#define _TEST_HELPER_H
+
+#include "includes.h"
+
+#include <sys/types.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+
+#ifdef WITH_OPENSSL
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#endif
+
+enum test_predicate {
+ TEST_EQ, TEST_NE, TEST_LT, TEST_LE, TEST_GT, TEST_GE
+};
+typedef void (test_onerror_func_t)(void *);
+
+/* Supplied by test suite */
+void tests(void);
+
+const char *test_data_file(const char *name);
+void test_start(const char *n);
+void test_info(char *s, size_t len);
+void set_onerror_func(test_onerror_func_t *f, void *ctx);
+void test_done(void);
+int test_is_verbose(void);
+int test_is_quiet(void);
+int test_is_fast(void);
+int test_is_slow(void);
+void test_subtest_info(const char *fmt, ...)
+ __attribute__((format(printf, 1, 2)));
+void ssl_err_check(const char *file, int line);
+#ifdef WITH_OPENSSL
+void assert_bignum(const char *file, int line,
+ const char *a1, const char *a2,
+ const BIGNUM *aa1, const BIGNUM *aa2, enum test_predicate pred);
+#endif
+void assert_string(const char *file, int line,
+ const char *a1, const char *a2,
+ const char *aa1, const char *aa2, enum test_predicate pred);
+void assert_mem(const char *file, int line,
+ const char *a1, const char *a2,
+ const void *aa1, const void *aa2, size_t l, enum test_predicate pred);
+void assert_mem_filled(const char *file, int line,
+ const char *a1,
+ const void *aa1, u_char v, size_t l, enum test_predicate pred);
+void assert_int(const char *file, int line,
+ const char *a1, const char *a2,
+ int aa1, int aa2, enum test_predicate pred);
+void assert_size_t(const char *file, int line,
+ const char *a1, const char *a2,
+ size_t aa1, size_t aa2, enum test_predicate pred);
+void assert_u_int(const char *file, int line,
+ const char *a1, const char *a2,
+ u_int aa1, u_int aa2, enum test_predicate pred);
+void assert_long(const char *file, int line,
+ const char *a1, const char *a2,
+ long aa1, long aa2, enum test_predicate pred);
+void assert_long_long(const char *file, int line,
+ const char *a1, const char *a2,
+ long long aa1, long long aa2, enum test_predicate pred);
+void assert_char(const char *file, int line,
+ const char *a1, const char *a2,
+ char aa1, char aa2, enum test_predicate pred);
+void assert_ptr(const char *file, int line,
+ const char *a1, const char *a2,
+ const void *aa1, const void *aa2, enum test_predicate pred);
+void assert_u8(const char *file, int line,
+ const char *a1, const char *a2,
+ u_int8_t aa1, u_int8_t aa2, enum test_predicate pred);
+void assert_u16(const char *file, int line,
+ const char *a1, const char *a2,
+ u_int16_t aa1, u_int16_t aa2, enum test_predicate pred);
+void assert_u32(const char *file, int line,
+ const char *a1, const char *a2,
+ u_int32_t aa1, u_int32_t aa2, enum test_predicate pred);
+void assert_u64(const char *file, int line,
+ const char *a1, const char *a2,
+ u_int64_t aa1, u_int64_t aa2, enum test_predicate pred);
+
+#define TEST_START(n) test_start(n)
+#define TEST_DONE() test_done()
+#define TEST_ONERROR(f, c) set_onerror_func(f, c)
+#define SSL_ERR_CHECK() ssl_err_check(__FILE__, __LINE__)
+
+#define ASSERT_BIGNUM_EQ(a1, a2) \
+ assert_bignum(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_EQ)
+#define ASSERT_STRING_EQ(a1, a2) \
+ assert_string(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_EQ)
+#define ASSERT_MEM_EQ(a1, a2, l) \
+ assert_mem(__FILE__, __LINE__, #a1, #a2, a1, a2, l, TEST_EQ)
+#define ASSERT_MEM_FILLED_EQ(a1, c, l) \
+ assert_mem_filled(__FILE__, __LINE__, #a1, a1, c, l, TEST_EQ)
+#define ASSERT_MEM_ZERO_EQ(a1, l) \
+ assert_mem_filled(__FILE__, __LINE__, #a1, a1, '\0', l, TEST_EQ)
+#define ASSERT_INT_EQ(a1, a2) \
+ assert_int(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_EQ)
+#define ASSERT_SIZE_T_EQ(a1, a2) \
+ assert_size_t(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_EQ)
+#define ASSERT_U_INT_EQ(a1, a2) \
+ assert_u_int(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_EQ)
+#define ASSERT_LONG_EQ(a1, a2) \
+ assert_long(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_EQ)
+#define ASSERT_LONG_LONG_EQ(a1, a2) \
+ assert_long_long(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_EQ)
+#define ASSERT_CHAR_EQ(a1, a2) \
+ assert_char(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_EQ)
+#define ASSERT_PTR_EQ(a1, a2) \
+ assert_ptr(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_EQ)
+#define ASSERT_U8_EQ(a1, a2) \
+ assert_u8(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_EQ)
+#define ASSERT_U16_EQ(a1, a2) \
+ assert_u16(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_EQ)
+#define ASSERT_U32_EQ(a1, a2) \
+ assert_u32(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_EQ)
+#define ASSERT_U64_EQ(a1, a2) \
+ assert_u64(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_EQ)
+
+#define ASSERT_BIGNUM_NE(a1, a2) \
+ assert_bignum(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_NE)
+#define ASSERT_STRING_NE(a1, a2) \
+ assert_string(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_NE)
+#define ASSERT_MEM_NE(a1, a2, l) \
+ assert_mem(__FILE__, __LINE__, #a1, #a2, a1, a2, l, TEST_NE)
+#define ASSERT_MEM_ZERO_NE(a1, l) \
+ assert_mem_filled(__FILE__, __LINE__, #a1, a1, '\0', l, TEST_NE)
+#define ASSERT_INT_NE(a1, a2) \
+ assert_int(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_NE)
+#define ASSERT_SIZE_T_NE(a1, a2) \
+ assert_size_t(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_NE)
+#define ASSERT_U_INT_NE(a1, a2) \
+ assert_u_int(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_NE)
+#define ASSERT_LONG_NE(a1, a2) \
+ assert_long(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_NE)
+#define ASSERT_LONG_LONG_NE(a1, a2) \
+ assert_long_long(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_NE)
+#define ASSERT_CHAR_NE(a1, a2) \
+ assert_char(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_NE)
+#define ASSERT_PTR_NE(a1, a2) \
+ assert_ptr(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_NE)
+#define ASSERT_U8_NE(a1, a2) \
+ assert_u8(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_NE)
+#define ASSERT_U16_NE(a1, a2) \
+ assert_u16(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_NE)
+#define ASSERT_U32_NE(a1, a2) \
+ assert_u32(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_NE)
+#define ASSERT_U64_NE(a1, a2) \
+ assert_u64(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_NE)
+
+#define ASSERT_BIGNUM_LT(a1, a2) \
+ assert_bignum(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LT)
+#define ASSERT_STRING_LT(a1, a2) \
+ assert_string(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LT)
+#define ASSERT_MEM_LT(a1, a2, l) \
+ assert_mem(__FILE__, __LINE__, #a1, #a2, a1, a2, l, TEST_LT)
+#define ASSERT_INT_LT(a1, a2) \
+ assert_int(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LT)
+#define ASSERT_SIZE_T_LT(a1, a2) \
+ assert_size_t(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LT)
+#define ASSERT_U_INT_LT(a1, a2) \
+ assert_u_int(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LT)
+#define ASSERT_LONG_LT(a1, a2) \
+ assert_long(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LT)
+#define ASSERT_LONG_LONG_LT(a1, a2) \
+ assert_long_long(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LT)
+#define ASSERT_CHAR_LT(a1, a2) \
+ assert_char(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LT)
+#define ASSERT_PTR_LT(a1, a2) \
+ assert_ptr(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LT)
+#define ASSERT_U8_LT(a1, a2) \
+ assert_u8(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LT)
+#define ASSERT_U16_LT(a1, a2) \
+ assert_u16(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LT)
+#define ASSERT_U32_LT(a1, a2) \
+ assert_u32(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LT)
+#define ASSERT_U64_LT(a1, a2) \
+ assert_u64(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LT)
+
+#define ASSERT_BIGNUM_LE(a1, a2) \
+ assert_bignum(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LE)
+#define ASSERT_STRING_LE(a1, a2) \
+ assert_string(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LE)
+#define ASSERT_MEM_LE(a1, a2, l) \
+ assert_mem(__FILE__, __LINE__, #a1, #a2, a1, a2, l, TEST_LE)
+#define ASSERT_INT_LE(a1, a2) \
+ assert_int(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LE)
+#define ASSERT_SIZE_T_LE(a1, a2) \
+ assert_size_t(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LE)
+#define ASSERT_U_INT_LE(a1, a2) \
+ assert_u_int(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LE)
+#define ASSERT_LONG_LE(a1, a2) \
+ assert_long(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LE)
+#define ASSERT_LONG_LONG_LE(a1, a2) \
+ assert_long_long(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LE)
+#define ASSERT_CHAR_LE(a1, a2) \
+ assert_char(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LE)
+#define ASSERT_PTR_LE(a1, a2) \
+ assert_ptr(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LE)
+#define ASSERT_U8_LE(a1, a2) \
+ assert_u8(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LE)
+#define ASSERT_U16_LE(a1, a2) \
+ assert_u16(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LE)
+#define ASSERT_U32_LE(a1, a2) \
+ assert_u32(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LE)
+#define ASSERT_U64_LE(a1, a2) \
+ assert_u64(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_LE)
+
+#define ASSERT_BIGNUM_GT(a1, a2) \
+ assert_bignum(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GT)
+#define ASSERT_STRING_GT(a1, a2) \
+ assert_string(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GT)
+#define ASSERT_MEM_GT(a1, a2, l) \
+ assert_mem(__FILE__, __LINE__, #a1, #a2, a1, a2, l, TEST_GT)
+#define ASSERT_INT_GT(a1, a2) \
+ assert_int(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GT)
+#define ASSERT_SIZE_T_GT(a1, a2) \
+ assert_size_t(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GT)
+#define ASSERT_U_INT_GT(a1, a2) \
+ assert_u_int(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GT)
+#define ASSERT_LONG_GT(a1, a2) \
+ assert_long(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GT)
+#define ASSERT_LONG_LONG_GT(a1, a2) \
+ assert_long_long(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GT)
+#define ASSERT_CHAR_GT(a1, a2) \
+ assert_char(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GT)
+#define ASSERT_PTR_GT(a1, a2) \
+ assert_ptr(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GT)
+#define ASSERT_U8_GT(a1, a2) \
+ assert_u8(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GT)
+#define ASSERT_U16_GT(a1, a2) \
+ assert_u16(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GT)
+#define ASSERT_U32_GT(a1, a2) \
+ assert_u32(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GT)
+#define ASSERT_U64_GT(a1, a2) \
+ assert_u64(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GT)
+
+#define ASSERT_BIGNUM_GE(a1, a2) \
+ assert_bignum(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE)
+#define ASSERT_STRING_GE(a1, a2) \
+ assert_string(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE)
+#define ASSERT_MEM_GE(a1, a2, l) \
+ assert_mem(__FILE__, __LINE__, #a1, #a2, a1, a2, l, TEST_GE)
+#define ASSERT_INT_GE(a1, a2) \
+ assert_int(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE)
+#define ASSERT_SIZE_T_GE(a1, a2) \
+ assert_size_t(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE)
+#define ASSERT_U_INT_GE(a1, a2) \
+ assert_u_int(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE)
+#define ASSERT_LONG_GE(a1, a2) \
+ assert_long(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE)
+#define ASSERT_LONG_LONG_GE(a1, a2) \
+ assert_long_long(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE)
+#define ASSERT_CHAR_GE(a1, a2) \
+ assert_char(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE)
+#define ASSERT_PTR_GE(a1, a2) \
+ assert_ptr(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE)
+#define ASSERT_U8_GE(a1, a2) \
+ assert_u8(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE)
+#define ASSERT_U16_GE(a1, a2) \
+ assert_u16(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE)
+#define ASSERT_U32_GE(a1, a2) \
+ assert_u32(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE)
+#define ASSERT_U64_GE(a1, a2) \
+ assert_u64(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE)
+
+/* Fuzzing support */
+
+struct fuzz;
+#define FUZZ_1_BIT_FLIP 0x00000001 /* Flip one bit at a time */
+#define FUZZ_2_BIT_FLIP 0x00000002 /* Flip two bits at a time */
+#define FUZZ_1_BYTE_FLIP 0x00000004 /* Flip one byte at a time */
+#define FUZZ_2_BYTE_FLIP 0x00000008 /* Flip two bytes at a time */
+#define FUZZ_TRUNCATE_START 0x00000010 /* Truncate from beginning */
+#define FUZZ_TRUNCATE_END 0x00000020 /* Truncate from end */
+#define FUZZ_BASE64 0x00000040 /* Try all base64 chars */
+#define FUZZ_MAX FUZZ_BASE64
+
+/* Start fuzzing a blob of data with selected strategies (bitmask) */
+struct fuzz *fuzz_begin(u_int strategies, const void *p, size_t l);
+
+/* Free a fuzz context */
+void fuzz_cleanup(struct fuzz *fuzz);
+
+/* Prepare the next fuzz case in the series */
+void fuzz_next(struct fuzz *fuzz);
+
+/*
+ * Check whether this fuzz case is identical to the original
+ * This is slow, but useful if the caller needs to ensure that all tests
+ * generated change the input (e.g. when fuzzing signatures).
+ */
+int fuzz_matches_original(struct fuzz *fuzz);
+
+/* Determine whether the current fuzz sequence is exhausted (nonzero = yes) */
+int fuzz_done(struct fuzz *fuzz);
+
+/* Return the length and a pointer to the current fuzzed case */
+size_t fuzz_len(struct fuzz *fuzz);
+u_char *fuzz_ptr(struct fuzz *fuzz);
+
+/* Dump the current fuzz case to stderr */
+void fuzz_dump(struct fuzz *fuzz);
+
+#endif /* _TEST_HELPER_H */
diff --git a/regress/unittests/utf8/Makefile b/regress/unittests/utf8/Makefile
new file mode 100644
index 0000000..f8eec04
--- /dev/null
+++ b/regress/unittests/utf8/Makefile
@@ -0,0 +1,14 @@
+# $OpenBSD: Makefile,v 1.5 2017/12/21 00:41:22 djm Exp $
+
+PROG=test_utf8
+SRCS=tests.c
+
+# From usr.bin/ssh
+SRCS+=utf8.c atomicio.c
+
+REGRESS_TARGETS=run-regress-${PROG}
+
+run-regress-${PROG}: ${PROG}
+ env ${TEST_ENV} ./${PROG}
+
+.include <bsd.regress.mk>
diff --git a/regress/unittests/utf8/tests.c b/regress/unittests/utf8/tests.c
new file mode 100644
index 0000000..8cf524d
--- /dev/null
+++ b/regress/unittests/utf8/tests.c
@@ -0,0 +1,104 @@
+/* $OpenBSD: tests.c,v 1.4 2017/02/19 00:11:29 djm Exp $ */
+/*
+ * Regress test for the utf8.h *mprintf() API
+ *
+ * Written by Ingo Schwarze <schwarze@openbsd.org> in 2016
+ * and placed in the public domain.
+ */
+
+#include "includes.h"
+
+#include <locale.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../test_helper/test_helper.h"
+
+#include "utf8.h"
+
+static void
+badarg(void)
+{
+ char buf[16];
+ int len, width;
+
+ width = 1;
+ TEST_START("utf8_badarg");
+ len = snmprintf(buf, sizeof(buf), &width, "\377");
+ ASSERT_INT_EQ(len, -1);
+ ASSERT_STRING_EQ(buf, "");
+ ASSERT_INT_EQ(width, 0);
+ TEST_DONE();
+}
+
+static void
+one(int utf8, const char *name, const char *mbs, int width,
+ int wantwidth, int wantlen, const char *wants)
+{
+ char buf[16];
+ int *wp;
+ int len;
+
+ if (wantlen == -2)
+ wantlen = strlen(wants);
+ (void)strlcpy(buf, utf8 ? "utf8_" : "c_", sizeof(buf));
+ (void)strlcat(buf, name, sizeof(buf));
+ TEST_START(buf);
+ wp = wantwidth == -2 ? NULL : &width;
+ len = snmprintf(buf, sizeof(buf), wp, "%s", mbs);
+ ASSERT_INT_EQ(len, wantlen);
+ ASSERT_STRING_EQ(buf, wants);
+ ASSERT_INT_EQ(width, wantwidth);
+ TEST_DONE();
+}
+
+void
+tests(void)
+{
+ char *loc;
+
+ TEST_START("utf8_setlocale");
+ loc = setlocale(LC_CTYPE, "en_US.UTF-8");
+ ASSERT_PTR_NE(loc, NULL);
+ TEST_DONE();
+
+ badarg();
+ one(1, "empty", "", 2, 0, 0, "");
+ one(1, "ascii", "x", -2, -2, -2, "x");
+ one(1, "newline", "a\nb", -2, -2, -2, "a\nb");
+ one(1, "cr", "a\rb", -2, -2, -2, "a\rb");
+ one(1, "tab", "a\tb", -2, -2, -2, "a\tb");
+ one(1, "esc", "\033x", -2, -2, -2, "\\033x");
+ one(1, "inv_badbyte", "\377x", -2, -2, -2, "\\377x");
+ one(1, "inv_nocont", "\341x", -2, -2, -2, "\\341x");
+ one(1, "inv_nolead", "a\200b", -2, -2, -2, "a\\200b");
+ one(1, "sz_ascii", "1234567890123456", -2, -2, 16, "123456789012345");
+ one(1, "sz_esc", "123456789012\033", -2, -2, 16, "123456789012");
+ one(1, "width_ascii", "123", 2, 2, -1, "12");
+ one(1, "width_double", "a\343\201\201", 2, 1, -1, "a");
+ one(1, "double_fit", "a\343\201\201", 3, 3, 4, "a\343\201\201");
+ one(1, "double_spc", "a\343\201\201", 4, 3, 4, "a\343\201\201");
+
+ TEST_START("C_setlocale");
+ loc = setlocale(LC_CTYPE, "C");
+ ASSERT_PTR_NE(loc, NULL);
+ TEST_DONE();
+
+ badarg();
+ one(0, "empty", "", 2, 0, 0, "");
+ one(0, "ascii", "x", -2, -2, -2, "x");
+ one(0, "newline", "a\nb", -2, -2, -2, "a\nb");
+ one(0, "cr", "a\rb", -2, -2, -2, "a\rb");
+ one(0, "tab", "a\tb", -2, -2, -2, "a\tb");
+ one(0, "esc", "\033x", -2, -2, -2, "\\033x");
+ one(0, "inv_badbyte", "\377x", -2, -2, -2, "\\377x");
+ one(0, "inv_nocont", "\341x", -2, -2, -2, "\\341x");
+ one(0, "inv_nolead", "a\200b", -2, -2, -2, "a\\200b");
+ one(0, "sz_ascii", "1234567890123456", -2, -2, 16, "123456789012345");
+ one(0, "sz_esc", "123456789012\033", -2, -2, 16, "123456789012");
+ one(0, "width_ascii", "123", 2, 2, -1, "12");
+ one(0, "width_double", "a\343\201\201", 2, 1, -1, "a");
+ one(0, "double_fit", "a\343\201\201", 7, 5, -1, "a\\343");
+ one(0, "double_spc", "a\343\201\201", 13, 13, 13, "a\\343\\201\\201");
+}
diff --git a/regress/valgrind-unit.sh b/regress/valgrind-unit.sh
new file mode 100755
index 0000000..193289e
--- /dev/null
+++ b/regress/valgrind-unit.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+UNIT_BINARY="$1"
+shift
+UNIT_ARGS="$@"
+
+test "x$OBJ" = "x" && OBJ=$PWD
+
+# This mostly replicates the logic in test-exec.sh for running the
+# regress tests under valgrind, except that we unconditionally enable
+# leak checking because the unit tests should be clean.
+VG_LEAK="--leak-check=full"
+VG_TEST=`basename $UNIT_BINARY`
+VG_LOG="$OBJ/valgrind-out/${VG_TEST}.%p"
+VG_OPTS="--track-origins=yes $VG_LEAK --log-file=${VG_LOG}"
+VG_OPTS="$VG_OPTS --trace-children=yes"
+VG_PATH="valgrind"
+if [ "x$VALGRIND_PATH" != "x" ]; then
+ VG_PATH="$VALGRIND_PATH"
+fi
+
+mkdir -p "$OBJ/valgrind-out"
+
+exec $VG_PATH $VG_OPTS $UNIT_BINARY $UNIT_ARGS
diff --git a/regress/yes-head.sh b/regress/yes-head.sh
new file mode 100644
index 0000000..1bde504
--- /dev/null
+++ b/regress/yes-head.sh
@@ -0,0 +1,13 @@
+# $OpenBSD: yes-head.sh,v 1.7 2023/01/14 10:05:54 dtucker Exp $
+# Placed in the Public Domain.
+
+tid="yes pipe head"
+
+lines=`${SSH} -F $OBJ/ssh_proxy thishost 'sh -c "while true;do echo yes;done | _POSIX2_VERSION=199209 head -2000"' | (sleep 3 ; wc -l)`
+if [ $? -ne 0 ]; then
+ fail "yes|head test failed"
++ lines=0
+fi
+if [ $lines -ne 2000 ]; then
+ fail "yes|head returns $lines lines instead of 2000"
+fi