Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/sanitizer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ jobs:
make -j$(nproc)

- name: Run tests
env:
# The runner has OpenSSH, so openssh-interop.test must run in full.
WOLFSSH_TEST_REQUIRE_OPENSSH: 1
run: timeout -k 30 300 make check

- name: Show test logs on failure
Expand Down
60 changes: 59 additions & 1 deletion .github/workflows/tpm-ssh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ jobs:
run: |
cd wolfssh
./autogen.sh
./configure --enable-tpm --enable-certs
./configure --enable-tpm --enable-certs --enable-ossh-certs
make
sudo make install
sudo ldconfig
Expand Down Expand Up @@ -228,6 +228,60 @@ jobs:
timeout 20 ./examples/client/client -i ../wolftpm/keyblob.bin \
-u hansel -K ThisIsMyKeyAuth

# The same TPM key offered as an OpenSSH user certificate. The certificate
# is a file carrying the TPM key's public half; the TPM still signs.
- name: Test TPM client OpenSSH certificate auth
if: matrix.keytype == 'rsa' && matrix.hostkey == 'raw'
run: |
cd wolfssh
ssh-keygen -q -t rsa -N '' -f ossh_ca < /dev/null
cp key.ssh tpmkey.pub
ssh-keygen -q -s ossh_ca -I tpm-cert -n hansel tpmkey.pub
timeout 30 ./examples/echoserver/echoserver -1 -p 22225 -s key.ssh \
> ossh_cert_server.txt 2>&1 &
SERVER_PID=$!
echo "Echoserver (TPM key behind an OpenSSH certificate) PID: $SERVER_PID"
sleep 2
timeout 20 ./examples/client/client -i ../wolftpm/keyblob.bin \
-u hansel -K ThisIsMyKeyAuth -j tpmkey-cert.pub -p 22225 -x \
< /dev/null > ossh_cert_client.txt 2>&1 || {
cat ossh_cert_client.txt
exit 1
}
# The one-shot server flushes its output when it exits.
wait $SERVER_PID || true
echo "----- server -----"; cat ossh_cert_server.txt
grep -q "auth = Success" ossh_cert_server.txt

# Negative test: a certificate for another key. The echoserver accepts
# that key, so only the signature check can refuse the TPM's signature.
- name: Test TPM client OpenSSH certificate for another key fails
if: matrix.keytype == 'rsa' && matrix.hostkey == 'raw'
run: |
cd wolfssh
# Same size as the TPM key, or the client fails before signing. The
# comment is cut because the echoserver appends the user name.
ssh-keygen -q -t rsa -b 2048 -N '' -f other < /dev/null
ssh-keygen -q -s ossh_ca -I other-cert -n hansel other.pub
cut -d' ' -f1,2 other.pub > other.ssh
timeout 30 ./examples/echoserver/echoserver -1 -p 22226 -s other.ssh \
> ossh_cert_server_neg.txt 2>&1 &
SERVER_PID=$!
echo "Echoserver (another key's certificate) PID: $SERVER_PID"
sleep 2
if timeout 20 ./examples/client/client -i ../wolftpm/keyblob.bin \
-u hansel -K ThisIsMyKeyAuth -j other-cert.pub -p 22226 -x \
< /dev/null > ossh_cert_client_neg.txt 2>&1; then
echo "ERROR: a certificate for another key authenticated"
cat ossh_cert_client_neg.txt
exit 1
fi
wait $SERVER_PID || true
echo "----- server -----"; cat ossh_cert_server_neg.txt
echo "----- client -----"; cat ossh_cert_client_neg.txt
# The server took the key and then refused the signature.
grep -q "auth = Failure" ossh_cert_server_neg.txt

- name: Archive test artifacts
if: always()
uses: actions/upload-artifact@v7
Expand All @@ -241,4 +295,8 @@ jobs:
wolfssh/tpmcert_client.txt
wolfssh/tpmcert_server_neg.txt
wolfssh/tpmcert_client_neg.txt
wolfssh/ossh_cert_server.txt
wolfssh/ossh_cert_client.txt
wolfssh/ossh_cert_server_neg.txt
wolfssh/ossh_cert_client_neg.txt
wolfssh/tests/api.log
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,36 @@ If you used a custom password for keygen you must specify the password you used:

$ ./examples/client/client -i ../wolfTPM/keyblob.bin -u hansel -K <custompassword>

with an OpenSSH user certificate
--------------------------------

The TPM key can also be offered as an OpenSSH user certificate
(`ssh-rsa-cert-v01@openssh.com`). The certificate is a file carrying the TPM
key's public half; the private key stays in the TPM, which makes the
signature. Build wolfSSH with OpenSSH certificate support as well:

wolfSSH
$ ./configure --enable-tpm --enable-ossh-certs

Create the key and `key.ssh` as above, then have your CA sign the public key.
`ssh-keygen` names the certificate after its input, so this writes
`tpmkey-cert.pub`:

$ cp key.ssh tpmkey.pub
$ ssh-keygen -s <CA private key> -I <key id> -n hansel tpmkey.pub

Run the echoserver with `key.ssh` as above, and pass the certificate to the
client with `-j`:

$ ./examples/client/client -i ../wolfTPM/keyblob.bin -u hansel \
-K ThisIsMyKeyAuth -j tpmkey-cert.pub

Notes:

- RSA keys only, as for the plain TPM key.
- The certificate must be issued for the key in the TPM, or the server
rejects the signature.

TPM SERVER HOST KEY (ECDSA / RSA)
=================================

Expand Down
143 changes: 122 additions & 21 deletions scripts/openssh-interop.test
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@
# peer that disagreed about the counters would fail the first packet that
# had to authenticate.
#
# Part 3 has the wolfSSH client authenticate to sshd with an RSA key held
# by OpenSSH's ssh-agent. The client has only the public half; the agent
# makes the signature.
#
# The checks read OpenSSH's own debug output, so they are worded the way
# OpenSSH words them. Strict KEX arrived in OpenSSH 9.6; anything older is
# skipped.

no_pid=-1
server_pid=$no_pid
sshd_pid=$no_pid
agent_pid=$no_pid
work_dir="`pwd`/openssh_interop_test$$"
ready_file="$work_dir/ready"
client_out="$work_dir/client.out"
Expand Down Expand Up @@ -53,6 +58,11 @@ do_cleanup() {
kill -9 $server_pid 2>/dev/null
server_pid=$no_pid
fi
if [ $agent_pid != $no_pid ]
then
kill $agent_pid 2>/dev/null
agent_pid=$no_pid
fi
rm -rf -- "$work_dir"
}

Expand Down Expand Up @@ -102,12 +112,29 @@ fail() {
exit 1
}

# Set WOLFSSH_TEST_REQUIRE_OPENSSH=1 where every part must run, as in CI;
# each skip below is then a failure.
require_openssh=${WOLFSSH_TEST_REQUIRE_OPENSSH:-0}

skip() {
if [ "$require_openssh" = "1" ]; then
fail "$1; WOLFSSH_TEST_REQUIRE_OPENSSH=1 does not allow skipping"
fi
printf '%s, skipping\n' "$1"
rm -rf -- "$work_dir"
exit 77
}

# Skip the part named by $2, keeping the parts that already passed.
skip_rest() {
if [ "$require_openssh" = "1" ]; then
fail "$1; WOLFSSH_TEST_REQUIRE_OPENSSH=1 does not allow skipping $2"
fi
echo "$1, skipping $2"
do_cleanup
exit 0
}

command -v ssh > /dev/null 2>&1 || skip "no ssh in the path"

ssh_version=`ssh -V 2>&1`
Expand Down Expand Up @@ -269,19 +296,13 @@ if [ -z "$sshd_bin" ]; then
fi

if [ -z "$sshd_bin" ] || [ ! -x "$sshd_bin" ]; then
echo "no sshd on this machine, skipping the wolfSSH client half"
do_cleanup
exit 0
skip_rest "no sshd on this machine" "the wolfSSH client half"
fi
if ! command -v ssh-keygen > /dev/null 2>&1; then
echo "no ssh-keygen, skipping the wolfSSH client half"
do_cleanup
exit 0
skip_rest "no ssh-keygen" "the wolfSSH client half"
fi
if [ ! -x ./examples/client/client ]; then
echo "the example client wasn't built, skipping the wolfSSH client half"
do_cleanup
exit 0
skip_rest "the example client wasn't built" "the wolfSSH client half"
fi

echo "Test the wolfSSH client against OpenSSH's sshd"
Expand All @@ -300,16 +321,20 @@ ssh-keygen -q -t ed25519 -N '' -f "$work_dir/hostkey-ed25519" < /dev/null \
# sshd in the foreground for one connection. ForceCommand stands in for the
# user's login shell: the client exits with the session's exit status, and
# whatever the user's dotfiles leave behind is not this test's business.
# Arguments after the port are passed to sshd.
start_sshd() {
sshd_listen_port=$1
shift
rm -f "$sshd_log"
"$sshd_bin" -ddd -f /dev/null -p "$1" -h "$work_dir/hostkey" \
"$sshd_bin" -ddd -f /dev/null -p "$sshd_listen_port" \
-h "$work_dir/hostkey" \
-h "$work_dir/hostkey-rsa" -h "$work_dir/hostkey-ed25519" \
-o "ListenAddress=127.0.0.1" \
-o "AuthorizedKeysFile=$work_dir/authorized_keys" \
-o "StrictModes=no" \
-o "PasswordAuthentication=no" \
-o "ForceCommand=true" \
-o "PidFile=none" > "$sshd_log" 2>&1 &
-o "PidFile=none" "$@" > "$sshd_log" 2>&1 &
sshd_pid=$!

# Its own counter: the loop that calls this one is counting too. A
Expand Down Expand Up @@ -344,10 +369,8 @@ while [ "$counter" -lt 5 ]; do
done

if [ "$sshd_port" -eq 0 ]; then
echo "sshd wouldn't start here, skipping the wolfSSH client half"
[ -s "$sshd_log" ] && tail -n 5 "$sshd_log"
do_cleanup
exit 0
skip_rest "sshd wouldn't start here" "the wolfSSH client half"
fi

# The version gate above covered the client. This sshd was found on its own
Expand All @@ -358,10 +381,9 @@ sshd_version=`sed -n 's/.*sshd version \(OpenSSH_[^,]*\).*/\1/p' \
"$sshd_log" | head -n 1`
has_strict_kex "$sshd_version"
if [ $? -ne 0 ]; then
echo "$sshd_bin is ${sshd_version:-an unreadable version}, no strict KEX,"
echo "skipping the wolfSSH client half"
do_cleanup
exit 0
skip_rest \
"$sshd_bin is ${sshd_version:-an unreadable version}, no strict KEX" \
"the wolfSSH client half"
fi

# sshd authenticates the user running this test, with one of the canned
Expand All @@ -386,12 +408,12 @@ run_wolfssh_client() {

# sshd handles one connection per -d run, so it needs restarting between
# attempts. Wait for the old one to go first, or the restart races it for
# the port.
# the port. Any arguments are passed to sshd.
restart_sshd() {
kill -9 $sshd_pid 2>/dev/null
wait $sshd_pid 2>/dev/null
sshd_pid=$no_pid
start_sshd $sshd_port || fail "couldn't restart sshd"
start_sshd $sshd_port "$@" || fail "couldn't restart sshd"
}

run_wolfssh_client "$keys_dir/hansel-key-ecc.der" \
Expand Down Expand Up @@ -439,6 +461,85 @@ fi

echo "the wolfSSH client negotiated strict KEX with sshd"

# Part 3 needs the client's agent support and OpenSSH's agent tools.
if ! ./examples/client/client '-?' 2>&1 | grep -q 'SSH-AGENT'; then
skip_rest "the example client has no agent support" "the agent half"
fi
if ! command -v ssh-agent > /dev/null 2>&1 ||
! command -v ssh-add > /dev/null 2>&1; then
skip_rest "no ssh-agent or ssh-add" "the agent half"
fi

# The RSA signature algorithms this client was built with; -E lists them
# without connecting.
agent_rsa_algs=
for alg in rsa-sha2-256 rsa-sha2-512; do
if ./examples/client/client -E -u x 2>&1 | grep -q ": $alg\$"; then
agent_rsa_algs="$agent_rsa_algs $alg"
fi
done
if [ -z "$agent_rsa_algs" ]; then
skip_rest "the client has no rsa-sha2 algorithms" "the agent half"
fi

echo "Test the wolfSSH client signing through ssh-agent for sshd"

# Run the client with only the public key $1, leaving the signature to the
# agent. $2 is the authorized_keys file sshd reads.
run_wolfssh_agent_client() {
cat "$2" > "$work_dir/authorized_keys" \
|| fail "couldn't write the authorized_keys file"
chmod 600 "$work_dir/authorized_keys"

rm -f "$client_out"
HOME="$work_dir" ./examples/client/client \
-h 127.0.0.1 -p $sshd_port -u "$login_user" -a -j "$1" -x \
< /dev/null > "$client_out" 2>&1 &
wait_with_limit $!
}

# Check that the client, which exited with status $1, succeeded and that
# sshd accepted the agent's signature under pkalg $2, an extended regular
# expression.
check_agent_auth() {
counter=0
while [ "$counter" -lt 50 ]; do
grep -q 'Accepted publickey' "$sshd_log" 2>/dev/null && break
sleep 0.1
counter=$((counter + 1))
done

if [ "$1" -ne 0 ] || ! grep -q 'Accepted publickey' "$sshd_log" ||
! grep -Eq "authenticated 1 pkalg ($2)( |\$)" "$sshd_log"; then
cat "$client_out"
tail -n 20 "$sshd_log"
fail "the client didn't authenticate with $2 signed by ssh-agent"
fi
}

# The agent picks its own socket path; one under work_dir can outgrow
# sun_path in a deep build directory.
SSH_AGENT_PID=
eval `ssh-agent -s` > /dev/null
[ -n "$SSH_AGENT_PID" ] || fail "couldn't start ssh-agent"
agent_pid=$SSH_AGENT_PID

# A key of ssh-keygen's default size, which the client doesn't have
# built in, so only the agent can sign for it.
ssh-keygen -q -t rsa -N '' -C wolfssh-agent -f "$work_dir/agent-rsa" \
< /dev/null || fail "couldn't make an RSA client key"
ssh-add -q "$work_dir/agent-rsa" 2>/dev/null \
|| fail "couldn't add the RSA key to ssh-agent"

for alg in $agent_rsa_algs; do
restart_sshd -o "PubkeyAcceptedAlgorithms=$alg"
run_wolfssh_agent_client "$work_dir/agent-rsa.pub" \
"$work_dir/agent-rsa.pub"
check_agent_auth $? "$alg"
done

echo "ssh-agent signed the wolfSSH client's RSA user auth"

do_cleanup
echo "OpenSSH strict KEX interop tests passed"
echo "OpenSSH interop tests passed"
exit 0
Loading
Loading