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
35 changes: 29 additions & 6 deletions .github/workflows/windows-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,12 @@ jobs:
working-directory: ${{env.GITHUB_WORKSPACE}}wolfssl
run: nuget restore ${{env.WOLFSSL_SOLUTION_FILE_PATH}}

- name: updated user_settings.h for sshd and x509
- name: Enable wolfSSH options (sshd, sftp, x509) in user_settings.h
working-directory: ${{env.GITHUB_WORKSPACE}}
run: cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}}

- name: replace wolfSSL user_settings.h with wolfSSH user_settings.h
working-directory: ${{env.GITHUB_WORKSPACE}}
run: get-content ${{env.USER_SETTINGS_H_NEW}} | %{$_ -replace "if 0","if 1"}
shell: bash
run: |
sed -i 's/#if 0/#if 1/g' ${{env.USER_SETTINGS_H_NEW}}
cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}}

- name: Build wolfssl library
working-directory: ${{env.GITHUB_WORKSPACE}}wolfssl
Expand All @@ -65,3 +64,27 @@ jobs:
# See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference
run: msbuild /m /p:PlatformToolset=v142 /p:Platform=${{env.BUILD_PLATFORM}} /p:WindowsTargetPlatformVersion=${{env.TARGET_PLATFORM}} /p:Configuration=${{env.WOLFSSH_BUILD_CONFIGURATION}} ${{env.SOLUTION_FILE_PATH}}

- name: Locate wolfsshd.exe and stage wolfssl.dll
working-directory: ${{ github.workspace }}\wolfssh
shell: pwsh
run: |
$sshdExe = Get-ChildItem -Path "${{ github.workspace }}\wolfssh" -Recurse -Filter "wolfsshd.exe" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -like "*Release*" } | Select-Object -First 1
if (-not $sshdExe) {
Write-Host "ERROR: wolfsshd.exe not found"
exit 1
}
Add-Content -Path $env:GITHUB_ENV -Value "SSHD_PATH=$($sshdExe.FullName)"

$sshdDir = Split-Path -Parent $sshdExe.FullName
$wolfsslDll = Get-ChildItem -Path "${{ github.workspace }}\wolfssl" -Recurse -Filter "wolfssl.dll" -ErrorAction SilentlyContinue | Select-Object -First 1
if ($wolfsslDll -and -not (Test-Path (Join-Path $sshdDir "wolfssl.dll"))) {
Copy-Item -Path $wolfsslDll.FullName -Destination (Join-Path $sshdDir "wolfssl.dll") -Force
}

- name: Test LoginGraceTime enforcement on Windows
working-directory: ${{ github.workspace }}\wolfssh\apps\wolfsshd\test
shell: pwsh
timeout-minutes: 2
run: pwsh -File .\sshd_login_grace_test.ps1 -SshdExe "$env:SSHD_PATH"

4 changes: 2 additions & 2 deletions apps/wolfsshd/test/run_all_sshd_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ else

#Github actions needs resolved for these test cases
#run_test "error_return.sh"
#run_test "sshd_login_grace_test.sh"

# add additional tests here, check on var USING_LOCAL_HOST if can make sshd
# server start/restart with changes
Expand All @@ -147,9 +146,10 @@ else
run_test "sshd_forcedcmd_test.sh"
run_test "sshd_window_full_test.sh"
run_test "sshd_empty_password_test.sh"
run_test "sshd_login_grace_test.sh"
else
printf "Skipping tests that need to setup local SSHD\n"
SKIPPED=$((SKIPPED+3))
SKIPPED=$((SKIPPED+4))
fi

# these tests run with X509 sshd-config loaded
Expand Down
129 changes: 129 additions & 0 deletions apps/wolfsshd/test/sshd_login_grace_test.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/env pwsh
#
# Windows regression test for wolfsshd LoginGraceTime enforcement.
#
# Opens a raw TCP connection that never authenticates and verifies that
# wolfsshd drops it at the login grace deadline. No Windows user account or
# authorized key is required, because the connection is closed before
# authentication ever completes - this exercises the pre-auth grace timer only.
#
# Enforcement is checked behaviorally (the server closes the connection at the
# grace deadline), not by reading the daemon log, so the test does not depend on
# debug logging being compiled into the wolfSSH Windows build.
#
# Usage:
# pwsh sshd_login_grace_test.ps1 -SshdExe <path-to-wolfsshd.exe> [-Port N] [-Grace N]
# (SshdExe also accepts the SSHD_PATH environment variable.)

param(
[string]$SshdExe = $env:SSHD_PATH,
[int]$Port = 22224,
[int]$Grace = 5
)

$ErrorActionPreference = "Stop"
$exitCode = 1

$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$repoRoot = (Resolve-Path (Join-Path $scriptDir "..\..\..")).Path
$keyPath = (Resolve-Path (Join-Path $repoRoot "keys\server-key.pem")).Path
$confFile = Join-Path $scriptDir "sshd_config_test_login_grace"
$authFile = Join-Path $scriptDir "authorized_keys_test"

if (-not $SshdExe -or -not (Test-Path $SshdExe)) {
Write-Host "ERROR: wolfsshd.exe not found (pass -SshdExe or set SSHD_PATH)"
exit 1
}

@"
Port $Port
Protocol 2
LoginGraceTime $Grace
PermitRootLogin yes
PasswordAuthentication yes
PermitEmptyPasswords no
UseDNS no
HostKey $keyPath
AuthorizedKeysFile $authFile
"@ | Out-File -FilePath $confFile -Encoding ASCII

"" | Out-File -FilePath $authFile -Encoding ASCII

# Run wolfsshd in the foreground (-D selects the non-service path on Windows).
$sshd = Start-Process -FilePath $SshdExe `
-ArgumentList "-D", "-f", "`"$confFile`"", "-p", "$Port" `
-NoNewWindow -PassThru

try {
# Wait for the listener to accept connections.
$up = $false
for ($i = 0; $i -lt 20; $i++) {
try {
$probe = New-Object System.Net.Sockets.TcpClient
$probe.Connect("127.0.0.1", $Port)
$probe.Close()
$up = $true
break
}
catch {
Start-Sleep -Milliseconds 500
}
}
if (-not $up) {
# throw rather than exit so the finally block still stops the daemon
throw "wolfsshd did not start listening on port $Port"
}

# Open a raw TCP connection and never authenticate. The server sends its
# banner, waits for ours, and must close the connection once the login grace
# time expires. Block on Read (with a timeout well past the grace time) and
# measure when the server closes the connection.
$stall = New-Object System.Net.Sockets.TcpClient
$stall.Connect("127.0.0.1", $Port)
$stream = $stall.GetStream()
$stream.ReadTimeout = ($Grace + 5) * 1000

$buf = New-Object byte[] 4096
$dropped = $false
$sw = [System.Diagnostics.Stopwatch]::StartNew()
try {
while ($true) {
$n = $stream.Read($buf, 0, $buf.Length)
if ($n -le 0) {
$dropped = $true # server closed the connection
break
}
# otherwise the server sent its banner; keep waiting for the drop
}
}
catch [System.IO.IOException] {
# Read timed out: the connection was still open past the grace time.
$dropped = $false
}
$elapsed = [math]::Round($sw.Elapsed.TotalSeconds, 1)
$stall.Close()

Write-Host "connection closed=$dropped after ${elapsed}s (grace=$Grace)"

if ($dropped -and ($elapsed -ge ($Grace - 1)) -and ($elapsed -le ($Grace + 4))) {
Write-Host "PASS: unauthenticated connection dropped at login grace deadline"
$exitCode = 0
}
elseif ($dropped) {
Write-Host "FAIL: connection closed at ${elapsed}s, not near the grace deadline ($Grace s)"
}
else {
Write-Host "FAIL: connection still open past the grace time (not enforced)"
}
}
catch {
Write-Host "ERROR: $_"
$exitCode = 1
}
finally {
if ($sshd -and -not $sshd.HasExited) {
Stop-Process -Id $sshd.Id -Force -ErrorAction SilentlyContinue
}
}

exit $exitCode
55 changes: 45 additions & 10 deletions apps/wolfsshd/test/sshd_login_grace_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,53 @@ if [ "$RESULT" != 0 ]; then
exit 1
fi

# attempt clearing out stdin from previous echo/grep
read -t 1 -n 1000 discard
popd

# test grace login timeout by stalling on password prompt
timeout --foreground 7 "$TEST_CLIENT" -u "$USER" -h "$TEST_HOST" -p "$TEST_PORT" -t
# Test the grace-time timeout behaviorally: open a raw TCP connection, never
# authenticate, and confirm the server closes it at the grace deadline. This
# asserts the actual behavior rather than scraping the log, matching the Windows
# PowerShell test (and not relying on the daemon log being readable).
GRACE=5
exec 3<>"/dev/tcp/$TEST_HOST/$TEST_PORT"
if [ "$?" != 0 ]; then
echo "FAIL: could not connect to $TEST_HOST:$TEST_PORT"
exit 1
fi

popd
cat ./log.txt | grep "Failed login within grace period"
RESULT=$?
if [ "$RESULT" != 0 ]; then
echo "FAIL: Grace period not hit"
cat ./log.txt
# The server sends its banner, waits for ours (which never comes), then closes
# the connection once the grace time expires. Read until the server closes the
# connection (EOF) or the per-read timeout elapses, and measure how long it
# took. Use a large read timeout (GRACE + 8) and decide by elapsed time rather
# than read's exit status, which differs across bash versions (timeout returns
# >128 on modern bash but 1 on the macOS bash 3.2).
START=$SECONDS
while true; do
if IFS= read -r -t $((GRACE + 8)) -n 1024 _ <&3; then
: # received banner/data, keep waiting for the server to close
else
break # server closed (EOF) or the read timeout elapsed
fi
done
ELAPSED=$((SECONDS - START))
exec 3>&-

# An exit well before the read timeout means the server closed the connection;
# an exit near GRACE + 8 means it stayed open (not enforced).
if [ "$ELAPSED" -le $((GRACE + 4)) ]; then
DROPPED=1
else
DROPPED=0
fi

echo "connection closed=$DROPPED after ${ELAPSED}s (grace=$GRACE)"

if [ "$DROPPED" = 1 ] && [ "$ELAPSED" -ge $((GRACE - 1)) ]; then
echo "PASS: unauthenticated connection dropped at login grace deadline"
elif [ "$DROPPED" = 1 ]; then
echo "FAIL: connection closed at ${ELAPSED}s, before the grace deadline ($GRACE s)"
exit 1
else
echo "FAIL: connection still open past the grace time (not enforced)"
exit 1
fi

Expand Down
Loading
Loading