mirror of https://github.com/wolfSSL/wolfssh.git
326 lines
12 KiB
Bash
Executable File
326 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# OpenSSH certificate (user auth) test for wolfSSHd.
|
|
#
|
|
# Regenerates the OpenSSH user certificates for the login user via
|
|
# keys/renew-ossh-certs.sh (the certificate principal must match the login user,
|
|
# like the X.509 test which regenerates per-user via renewcerts.sh), starts
|
|
# wolfSSHd with TrustedUserCAKeys set to the signing CAs, and runs the same
|
|
# suite against two drivers:
|
|
# * the wolfSSH example client (self-contained, always run when built), and
|
|
# * the system OpenSSH "ssh" client (interop, run when ssh is present).
|
|
# Confirms a valid certificate is accepted and that an untrusted CA, a
|
|
# non-matching principal, an unknown critical option and a source-address
|
|
# mismatch are each rejected, and that force-command overrides the command.
|
|
#
|
|
# Requires: ssh-keygen and the wolfSSH client. Skips cleanly (77) when either is
|
|
# missing or wolfSSHd was not built with --enable-ossh-certs. The OpenSSH-client
|
|
# interop pass is skipped when "ssh" is unavailable.
|
|
#
|
|
# This is the gate for the CheckPublicKeyUnix OSSH orchestration (CA-trust ->
|
|
# principal -> validity -> source-address ordering); a deterministic unit-level
|
|
# test of that ordering is a deferred follow-up.
|
|
#
|
|
# On Windows, OpenSSH certificate auth is intentionally rejected outright
|
|
# (CheckPublicKeyWIN fails closed). That guard is compile-verified only (no
|
|
# Windows unit harness) and is a known untested edge; the cases here are Unix.
|
|
|
|
set +m # quiet job-control "Terminated" notices when stopping the daemon
|
|
|
|
PWD0=$(pwd)
|
|
. ./wolfssh_options.sh
|
|
cd ../../..
|
|
ROOT=$(pwd)
|
|
|
|
skip() { echo "$1"; cd "$PWD0"; exit 77; }
|
|
|
|
# Only meaningful when wolfSSHd was built with OpenSSH certificate support.
|
|
wolfssh_has OSSH_CERTS || \
|
|
skip "wolfSSHd not built with --enable-ossh-certs, skipping"
|
|
|
|
WOLFSSHD="$ROOT/apps/wolfsshd/wolfsshd"
|
|
CLIENT="$ROOT/examples/client/client"
|
|
PORT=${WOLFSSHD_TEST_PORT:-22226}
|
|
LOGINUSER=${SUDO_USER:-$(whoami)}
|
|
|
|
[ -x "$WOLFSSHD" ] || skip "wolfsshd not built, skipping OpenSSH cert test"
|
|
[ -x "$CLIENT" ] || skip "wolfSSH client not built, skipping OpenSSH cert test"
|
|
command -v ssh-keygen >/dev/null 2>&1 || \
|
|
skip "ssh-keygen not found, skipping OpenSSH cert test"
|
|
|
|
WORK=$(mktemp -d)
|
|
# Checked before the trap below is installed: an empty WORK would reduce its
|
|
# pattern to "wolfsshd .*", which matches every wolfsshd on the machine and is
|
|
# exactly the host-wide teardown this suite no longer does.
|
|
if [ -z "$WORK" ] || [ ! -d "$WORK" ]; then
|
|
echo "FAIL: could not create a work directory for the OpenSSH cert test"
|
|
exit 1
|
|
fi
|
|
# Matched on $WORK, this run's own mktemp dir, not on the config basename:
|
|
# "sshd_config_ossh" appears in every concurrent run's command line too,
|
|
# so the basename pattern tore down another run's daemon along with this
|
|
# one's. $WORK is expanded when the trap fires, by which point it is set.
|
|
trap 'pkill -f "wolfsshd .*$WORK" 2>/dev/null; rm -rf "$WORK"' EXIT
|
|
|
|
# Under sudo the daemon session runs as the login user: let it traverse $WORK
|
|
# and own the marker dir (not world-writable, so no other user can fake a PASS).
|
|
chmod 711 "$WORK"
|
|
MARKERDIR="$WORK/markers"
|
|
mkdir -p "$MARKERDIR"
|
|
chown "$LOGINUSER" "$MARKERDIR" 2>/dev/null
|
|
chmod 700 "$MARKERDIR"
|
|
|
|
# The host private key is a secret loaded through the secure gate, which refuses
|
|
# a group/world readable file. The committed key is 644, so use a 600 copy.
|
|
HOSTKEY="$WORK/hostkey.pem"
|
|
cp "$ROOT/keys/server-key.pem" "$HOSTKEY"
|
|
chmod 600 "$HOSTKEY"
|
|
|
|
# Issue certificates bound to the login user (and the negatives). The
|
|
# force-command marker is placed under the per-run work dir, not a fixed,
|
|
# world-readable /tmp path.
|
|
( cd "$ROOT/keys" && OSSH_FORCED_MARKER="$MARKERDIR/forced_marker" \
|
|
./renew-ossh-certs.sh "$LOGINUSER" )
|
|
|
|
# Trust all three signing CAs (Ed25519, RSA, ECDSA) but not ossh-bad-ca.
|
|
cat "$ROOT/keys/ossh-ca.pub" "$ROOT/keys/ossh-ca-rsa.pub" \
|
|
"$ROOT/keys/ossh-ca-ecdsa.pub" > "$WORK/trusted-cas.pub"
|
|
|
|
cat > "$WORK/sshd_config_ossh" <<EOF
|
|
Port $PORT
|
|
Protocol 2
|
|
UsePrivilegeSeparation no
|
|
UseDNS no
|
|
PasswordAuthentication no
|
|
HostKey $HOSTKEY
|
|
TrustedUserCAKeys $WORK/trusted-cas.pub
|
|
EOF
|
|
|
|
# Second config, adding a ForceCommand that is not "internal-sftp". Used to
|
|
# separate the config-sourced command from the certificate's.
|
|
cat > "$WORK/sshd_config_ossh_fc" <<EOF
|
|
Port $PORT
|
|
Protocol 2
|
|
UsePrivilegeSeparation no
|
|
UseDNS no
|
|
PasswordAuthentication no
|
|
HostKey $HOSTKEY
|
|
TrustedUserCAKeys $WORK/trusted-cas.pub
|
|
|
|
Match User $LOGINUSER
|
|
ForceCommand /bin/echo
|
|
EOF
|
|
|
|
CONFIG="$WORK/sshd_config_ossh"
|
|
FAIL=0
|
|
DRIVER=""
|
|
|
|
# Connect with the wolfSSH example client: -i private key, -j certificate.
|
|
connect_client() { # user-key cert remote-command
|
|
"$CLIENT" -u "$LOGINUSER" -i "$1" -j "$2" \
|
|
-h 127.0.0.1 -p $PORT -c "$3" >/dev/null 2>&1
|
|
}
|
|
|
|
# Connect with the system OpenSSH client.
|
|
connect_ssh() { # user-key cert remote-command
|
|
ssh -p $PORT -i "$1" -o CertificateFile="$2" \
|
|
-o IdentitiesOnly=yes -o StrictHostKeyChecking=no \
|
|
-o UserKnownHostsFile=/dev/null -o PreferredAuthentications=publickey \
|
|
-o BatchMode=yes -o ConnectTimeout=5 \
|
|
"$LOGINUSER@127.0.0.1" "$3" >/dev/null 2>&1
|
|
}
|
|
|
|
# (re)start the daemon, drive the selected client, return its exit code.
|
|
attempt() { # user-key cert [remote-command]
|
|
pkill -f "wolfsshd .*$WORK" 2>/dev/null
|
|
sleep 1
|
|
"$WOLFSSHD" -D -f "$CONFIG" -E "$WORK/sshd.log" &
|
|
local wp=$!
|
|
disown "$wp" 2>/dev/null
|
|
sleep 1
|
|
"connect_$DRIVER" "$1" "$2" "${3:-true}"
|
|
local rc=$?
|
|
kill $wp 2>/dev/null
|
|
return $rc
|
|
}
|
|
|
|
check() { # label user-key cert expect(0=accept,1=reject)
|
|
attempt "$2" "$3"
|
|
local rc=$?
|
|
local got=1; [ $rc -eq 0 ] && got=0
|
|
if [ $got -eq $4 ]; then
|
|
printf " %-20s %s\n" "$1" "PASS"
|
|
else
|
|
printf " %-20s %s (rc=%d)\n" "$1" "*** FAIL" "$rc"
|
|
FAIL=1
|
|
fi
|
|
}
|
|
|
|
force_command_check() { # user-key cert
|
|
local forced="$MARKERDIR/forced_marker"
|
|
local requested="$MARKERDIR/requested_marker"
|
|
rm -f "$forced" "$requested"
|
|
attempt "$1" "$2" "touch $requested"
|
|
local rc=$?
|
|
sleep 1
|
|
if [ $rc -eq 0 ] && [ -f "$forced" ] && [ ! -f "$requested" ]; then
|
|
printf " %-20s %s\n" "force-command" "PASS"
|
|
else
|
|
printf " %-20s %s (rc=%d forced=%s requested=%s)\n" "force-command" \
|
|
"*** FAIL" "$rc" \
|
|
"$([ -f "$forced" ] && echo yes || echo no)" \
|
|
"$([ -f "$requested" ] && echo yes || echo no)"
|
|
FAIL=1
|
|
fi
|
|
rm -f "$forced" "$requested"
|
|
}
|
|
|
|
ED="$ROOT/keys/ossh-user"
|
|
RSA="$ROOT/keys/ossh-user-rsa"
|
|
ECC="$ROOT/keys/ossh-user-ecdsa"
|
|
SFTP="$ROOT/examples/sftpclient/wolfsftp"
|
|
SCP="$ROOT/examples/scpclient/wolfscp"
|
|
SCPSRC="$WORK/scp_src.dat"
|
|
SCPDST="$WORK/scp_dst.dat"
|
|
echo "scp payload" > "$SCPSRC"
|
|
|
|
# (re)start the daemon, leaving its PID in DPID.
|
|
start_daemon() {
|
|
pkill -f "wolfsshd .*$WORK" 2>/dev/null
|
|
sleep 1
|
|
"$WOLFSSHD" -D -f "$CONFIG" -E "$WORK/sshd.log" &
|
|
DPID=$!
|
|
disown "$DPID" 2>/dev/null
|
|
sleep 1
|
|
}
|
|
|
|
# Drive an SFTP session with the wolfSSH and system clients (echo a quit
|
|
# command so a granted session exits cleanly with no transfer). The clients
|
|
# report a non-zero exit code when the subsystem request is denied.
|
|
sftp_client() { # user-key cert
|
|
echo "exit" | "$SFTP" -u "$LOGINUSER" -i "$1" -j "$2" \
|
|
-h 127.0.0.1 -p $PORT >/dev/null 2>&1
|
|
}
|
|
sftp_ssh() { # user-key cert
|
|
echo "bye" | sftp -P $PORT -i "$1" -o CertificateFile="$2" \
|
|
-o IdentitiesOnly=yes -o StrictHostKeyChecking=no \
|
|
-o UserKnownHostsFile=/dev/null -o BatchMode=yes \
|
|
-o PreferredAuthentications=publickey -o ConnectTimeout=5 \
|
|
"$LOGINUSER@127.0.0.1" >/dev/null 2>&1
|
|
}
|
|
|
|
sftp_available() {
|
|
if [ "$DRIVER" = client ]; then
|
|
[ -x "$SFTP" ]
|
|
else
|
|
command -v sftp >/dev/null 2>&1
|
|
fi
|
|
}
|
|
|
|
# Like check(), but drives an SFTP subsystem instead of a shell command.
|
|
sftp_check() { # label user-key cert expect(0=accept,1=reject)
|
|
start_daemon
|
|
"sftp_$DRIVER" "$2" "$3"
|
|
local rc=$?
|
|
kill $DPID 2>/dev/null
|
|
local got=1; [ $rc -eq 0 ] && got=0
|
|
if [ $got -eq $4 ]; then
|
|
printf " %-20s %s\n" "$1" "PASS"
|
|
else
|
|
printf " %-20s %s (rc=%d)\n" "$1" "*** FAIL" "$rc"
|
|
FAIL=1
|
|
fi
|
|
}
|
|
|
|
# Drive a native SCP upload with the wolfSSH client. wolfscp masks its exit
|
|
# code and the destination path resolves differently across platforms, so
|
|
# verify the server's enforcement decision from its log, not from a file.
|
|
scp_check() { # label user-key cert expect(0=allowed,1=denied)
|
|
[ -x "$SCP" ] || { echo " ($1: wolfscp unavailable, skipping)"; return; }
|
|
: > "$WORK/sshd.log"
|
|
start_daemon
|
|
"$SCP" -u "$LOGINUSER" -i "$2" -j "$3" \
|
|
-S"$SCPSRC:$SCPDST" -H 127.0.0.1 -p $PORT >/dev/null 2>&1
|
|
kill $DPID 2>/dev/null
|
|
rm -f "$SCPDST"
|
|
local got=0
|
|
grep -q "denying SCP" "$WORK/sshd.log" 2>/dev/null && got=1
|
|
if [ $got -eq $4 ]; then
|
|
printf " %-20s %s\n" "$1" "PASS"
|
|
else
|
|
printf " %-20s %s\n" "$1" "*** FAIL"
|
|
FAIL=1
|
|
fi
|
|
}
|
|
|
|
run_suite() { # driver
|
|
DRIVER=$1
|
|
echo "OpenSSH cert test via $DRIVER client (user=$LOGINUSER, port=$PORT):"
|
|
check "valid cert" "$ED" "$ROOT/keys/$LOGINUSER-ossh-cert.pub" 0
|
|
check "RSA CA" "$ED" "$ROOT/keys/$LOGINUSER-ossh-rsaca-cert.pub" 0
|
|
check "ECDSA CA" "$ED" "$ROOT/keys/$LOGINUSER-ossh-ecdsaca-cert.pub" 0
|
|
check "RSA user key" "$RSA" "$ROOT/keys/$LOGINUSER-ossh-rsauser-cert.pub" 0
|
|
check "ECDSA user key" "$ECC" "$ROOT/keys/$LOGINUSER-ossh-ecdsauser-cert.pub" 0
|
|
check "untrusted CA" "$ED" "$ROOT/keys/$LOGINUSER-ossh-badca-cert.pub" 1
|
|
check "wrong principal" "$ED" "$ROOT/keys/$LOGINUSER-ossh-wrongprincipal-cert.pub" 1
|
|
check "empty principal" "$ED" "$ROOT/keys/$LOGINUSER-ossh-noprincipal-cert.pub" 1
|
|
check "unknown crit opt" "$ED" "$ROOT/keys/$LOGINUSER-ossh-unkcrit-cert.pub" 1
|
|
check "source-addr match" "$ED" "$ROOT/keys/$LOGINUSER-ossh-srcok-cert.pub" 0
|
|
check "source-addr deny" "$ED" "$ROOT/keys/$LOGINUSER-ossh-srcbad-cert.pub" 1
|
|
check "expired cert" "$ED" "$ROOT/keys/$LOGINUSER-ossh-expired-cert.pub" 1
|
|
force_command_check "$ED" "$ROOT/keys/$LOGINUSER-ossh-forcecmd-cert.pub"
|
|
|
|
# A force-command must not be bypassed by requesting the SFTP subsystem.
|
|
# "internal-sftp" still permits SFTP; any other force-command denies it.
|
|
if sftp_available; then
|
|
sftp_check "valid cert sftp" "$ED" \
|
|
"$ROOT/keys/$LOGINUSER-ossh-cert.pub" 0
|
|
sftp_check "forcecmd sftp deny" "$ED" \
|
|
"$ROOT/keys/$LOGINUSER-ossh-forcecmd-cert.pub" 1
|
|
sftp_check "internal-sftp sftp" "$ED" \
|
|
"$ROOT/keys/$LOGINUSER-ossh-internalsftp-cert.pub" 0
|
|
|
|
# A configured ForceCommand is not a certificate force-command: on its
|
|
# own it must not deny SFTP, and it must not mask one carried by a
|
|
# certificate.
|
|
CONFIG="$WORK/sshd_config_ossh_fc"
|
|
sftp_check "config forcecmd sftp" "$ED" \
|
|
"$ROOT/keys/$LOGINUSER-ossh-cert.pub" 0
|
|
sftp_check "config+cert sftp deny" "$ED" \
|
|
"$ROOT/keys/$LOGINUSER-ossh-forcecmd-cert.pub" 1
|
|
CONFIG="$WORK/sshd_config_ossh"
|
|
else
|
|
echo " (sftp $DRIVER client unavailable, skipping sftp cases)"
|
|
fi
|
|
|
|
# Native SCP (an exec, not the SFTP subsystem) is denied under any
|
|
# force-command, including "internal-sftp". Driven by the wolfSSH client
|
|
# only; the system "scp" uses the SFTP protocol and is covered above.
|
|
if [ "$DRIVER" = client ]; then
|
|
scp_check "valid cert scp" "$ED" \
|
|
"$ROOT/keys/$LOGINUSER-ossh-cert.pub" 0
|
|
scp_check "forcecmd scp deny" "$ED" \
|
|
"$ROOT/keys/$LOGINUSER-ossh-forcecmd-cert.pub" 1
|
|
scp_check "internal-sftp scp" "$ED" \
|
|
"$ROOT/keys/$LOGINUSER-ossh-internalsftp-cert.pub" 1
|
|
fi
|
|
}
|
|
|
|
# Primary, self-contained pass with the wolfSSH client.
|
|
run_suite client
|
|
|
|
# Interop pass with the OpenSSH client, when available.
|
|
if command -v ssh >/dev/null 2>&1; then
|
|
run_suite ssh
|
|
else
|
|
echo "ssh not found, skipping OpenSSH-client interop pass"
|
|
fi
|
|
|
|
cd "$PWD0"
|
|
if [ $FAIL -ne 0 ]; then
|
|
echo "OpenSSH certificate test FAILED"
|
|
exit 1
|
|
fi
|
|
echo "OpenSSH certificate test passed"
|
|
exit 0
|