Skip to content

Commit 14e248f

Browse files
Enforce LoginGraceTime in wolfsshd on Windows and make the grace flag per-connection
1 parent e2bfa19 commit 14e248f

5 files changed

Lines changed: 303 additions & 42 deletions

File tree

.github/workflows/windows-check.yml

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,12 @@ jobs:
4343
working-directory: ${{env.GITHUB_WORKSPACE}}wolfssl
4444
run: nuget restore ${{env.WOLFSSL_SOLUTION_FILE_PATH}}
4545

46-
- name: updated user_settings.h for sshd and x509
46+
- name: Enable wolfSSH options (sshd, sftp, x509) in user_settings.h
4747
working-directory: ${{env.GITHUB_WORKSPACE}}
48-
run: cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}}
49-
50-
- name: replace wolfSSL user_settings.h with wolfSSH user_settings.h
51-
working-directory: ${{env.GITHUB_WORKSPACE}}
52-
run: get-content ${{env.USER_SETTINGS_H_NEW}} | %{$_ -replace "if 0","if 1"}
48+
shell: bash
49+
run: |
50+
sed -i 's/#if 0/#if 1/g' ${{env.USER_SETTINGS_H_NEW}}
51+
cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}}
5352
5453
- name: Build wolfssl library
5554
working-directory: ${{env.GITHUB_WORKSPACE}}wolfssl
@@ -65,3 +64,27 @@ jobs:
6564
# See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference
6665
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}}
6766

67+
- name: Locate wolfsshd.exe and stage wolfssl.dll
68+
working-directory: ${{ github.workspace }}\wolfssh
69+
shell: pwsh
70+
run: |
71+
$sshdExe = Get-ChildItem -Path "${{ github.workspace }}\wolfssh" -Recurse -Filter "wolfsshd.exe" -ErrorAction SilentlyContinue |
72+
Where-Object { $_.FullName -like "*Release*" } | Select-Object -First 1
73+
if (-not $sshdExe) {
74+
Write-Host "ERROR: wolfsshd.exe not found"
75+
exit 1
76+
}
77+
Add-Content -Path $env:GITHUB_ENV -Value "SSHD_PATH=$($sshdExe.FullName)"
78+
79+
$sshdDir = Split-Path -Parent $sshdExe.FullName
80+
$wolfsslDll = Get-ChildItem -Path "${{ github.workspace }}\wolfssl" -Recurse -Filter "wolfssl.dll" -ErrorAction SilentlyContinue | Select-Object -First 1
81+
if ($wolfsslDll -and -not (Test-Path (Join-Path $sshdDir "wolfssl.dll"))) {
82+
Copy-Item -Path $wolfsslDll.FullName -Destination (Join-Path $sshdDir "wolfssl.dll") -Force
83+
}
84+
85+
- name: Test LoginGraceTime enforcement on Windows
86+
working-directory: ${{ github.workspace }}\wolfssh\apps\wolfsshd\test
87+
shell: pwsh
88+
timeout-minutes: 2
89+
run: pwsh -File .\sshd_login_grace_test.ps1 -SshdExe "$env:SSHD_PATH"
90+

apps/wolfsshd/test/run_all_sshd_tests.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ else
132132

133133
#Github actions needs resolved for these test cases
134134
#run_test "error_return.sh"
135-
#run_test "sshd_login_grace_test.sh"
136135

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

155155
# these tests run with X509 sshd-config loaded
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#!/usr/bin/env pwsh
2+
#
3+
# Windows regression test for wolfsshd LoginGraceTime enforcement.
4+
#
5+
# Opens a raw TCP connection that never authenticates and verifies that
6+
# wolfsshd drops it at the login grace deadline. No Windows user account or
7+
# authorized key is required, because the connection is closed before
8+
# authentication ever completes - this exercises the pre-auth grace timer only.
9+
#
10+
# Enforcement is checked behaviorally (the server closes the connection at the
11+
# grace deadline), not by reading the daemon log, so the test does not depend on
12+
# debug logging being compiled into the wolfSSH Windows build.
13+
#
14+
# Usage:
15+
# pwsh sshd_login_grace_test.ps1 -SshdExe <path-to-wolfsshd.exe> [-Port N] [-Grace N]
16+
# (SshdExe also accepts the SSHD_PATH environment variable.)
17+
18+
param(
19+
[string]$SshdExe = $env:SSHD_PATH,
20+
[int]$Port = 22224,
21+
[int]$Grace = 5
22+
)
23+
24+
$ErrorActionPreference = "Stop"
25+
$exitCode = 1
26+
27+
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
28+
$repoRoot = (Resolve-Path (Join-Path $scriptDir "..\..\..")).Path
29+
$keyPath = (Resolve-Path (Join-Path $repoRoot "keys\server-key.pem")).Path
30+
$confFile = Join-Path $scriptDir "sshd_config_test_login_grace"
31+
$authFile = Join-Path $scriptDir "authorized_keys_test"
32+
33+
if (-not $SshdExe -or -not (Test-Path $SshdExe)) {
34+
Write-Host "ERROR: wolfsshd.exe not found (pass -SshdExe or set SSHD_PATH)"
35+
exit 1
36+
}
37+
38+
@"
39+
Port $Port
40+
Protocol 2
41+
LoginGraceTime $Grace
42+
PermitRootLogin yes
43+
PasswordAuthentication yes
44+
PermitEmptyPasswords no
45+
UseDNS no
46+
HostKey $keyPath
47+
AuthorizedKeysFile $authFile
48+
"@ | Out-File -FilePath $confFile -Encoding ASCII
49+
50+
"" | Out-File -FilePath $authFile -Encoding ASCII
51+
52+
# Run wolfsshd in the foreground (-D selects the non-service path on Windows).
53+
$sshd = Start-Process -FilePath $SshdExe `
54+
-ArgumentList "-D", "-f", "`"$confFile`"", "-p", "$Port" `
55+
-NoNewWindow -PassThru
56+
57+
try {
58+
# Wait for the listener to accept connections.
59+
$up = $false
60+
for ($i = 0; $i -lt 20; $i++) {
61+
try {
62+
$probe = New-Object System.Net.Sockets.TcpClient
63+
$probe.Connect("127.0.0.1", $Port)
64+
$probe.Close()
65+
$up = $true
66+
break
67+
}
68+
catch {
69+
Start-Sleep -Milliseconds 500
70+
}
71+
}
72+
if (-not $up) {
73+
# throw rather than exit so the finally block still stops the daemon
74+
throw "wolfsshd did not start listening on port $Port"
75+
}
76+
77+
# Open a raw TCP connection and never authenticate. The server sends its
78+
# banner, waits for ours, and must close the connection once the login grace
79+
# time expires. Block on Read (with a timeout well past the grace time) and
80+
# measure when the server closes the connection.
81+
$stall = New-Object System.Net.Sockets.TcpClient
82+
$stall.Connect("127.0.0.1", $Port)
83+
$stream = $stall.GetStream()
84+
$stream.ReadTimeout = ($Grace + 5) * 1000
85+
86+
$buf = New-Object byte[] 4096
87+
$dropped = $false
88+
$sw = [System.Diagnostics.Stopwatch]::StartNew()
89+
try {
90+
while ($true) {
91+
$n = $stream.Read($buf, 0, $buf.Length)
92+
if ($n -le 0) {
93+
$dropped = $true # server closed the connection
94+
break
95+
}
96+
# otherwise the server sent its banner; keep waiting for the drop
97+
}
98+
}
99+
catch [System.IO.IOException] {
100+
# Read timed out: the connection was still open past the grace time.
101+
$dropped = $false
102+
}
103+
$elapsed = [math]::Round($sw.Elapsed.TotalSeconds, 1)
104+
$stall.Close()
105+
106+
Write-Host "connection closed=$dropped after ${elapsed}s (grace=$Grace)"
107+
108+
if ($dropped -and ($elapsed -ge ($Grace - 1)) -and ($elapsed -le ($Grace + 4))) {
109+
Write-Host "PASS: unauthenticated connection dropped at login grace deadline"
110+
$exitCode = 0
111+
}
112+
elseif ($dropped) {
113+
Write-Host "FAIL: connection closed at ${elapsed}s, not near the grace deadline ($Grace s)"
114+
}
115+
else {
116+
Write-Host "FAIL: connection still open past the grace time (not enforced)"
117+
}
118+
}
119+
catch {
120+
Write-Host "ERROR: $_"
121+
$exitCode = 1
122+
}
123+
finally {
124+
if ($sshd -and -not $sshd.HasExited) {
125+
Stop-Process -Id $sshd.Id -Force -ErrorAction SilentlyContinue
126+
}
127+
}
128+
129+
exit $exitCode

apps/wolfsshd/test/sshd_login_grace_test.sh

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,53 @@ if [ "$RESULT" != 0 ]; then
4747
exit 1
4848
fi
4949

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

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

56-
popd
57-
cat ./log.txt | grep "Failed login within grace period"
58-
RESULT=$?
59-
if [ "$RESULT" != 0 ]; then
60-
echo "FAIL: Grace period not hit"
61-
cat ./log.txt
63+
# The server sends its banner, waits for ours (which never comes), then closes
64+
# the connection once the grace time expires. Read until the server closes the
65+
# connection (EOF) or the per-read timeout elapses, and measure how long it
66+
# took. Use a large read timeout (GRACE + 8) and decide by elapsed time rather
67+
# than read's exit status, which differs across bash versions (timeout returns
68+
# >128 on modern bash but 1 on the macOS bash 3.2).
69+
START=$SECONDS
70+
while true; do
71+
if IFS= read -r -t $((GRACE + 8)) -n 1024 _ <&3; then
72+
: # received banner/data, keep waiting for the server to close
73+
else
74+
break # server closed (EOF) or the read timeout elapsed
75+
fi
76+
done
77+
ELAPSED=$((SECONDS - START))
78+
exec 3>&-
79+
80+
# An exit well before the read timeout means the server closed the connection;
81+
# an exit near GRACE + 8 means it stayed open (not enforced).
82+
if [ "$ELAPSED" -le $((GRACE + 4)) ]; then
83+
DROPPED=1
84+
else
85+
DROPPED=0
86+
fi
87+
88+
echo "connection closed=$DROPPED after ${ELAPSED}s (grace=$GRACE)"
89+
90+
if [ "$DROPPED" = 1 ] && [ "$ELAPSED" -ge $((GRACE - 1)) ]; then
91+
echo "PASS: unauthenticated connection dropped at login grace deadline"
92+
elif [ "$DROPPED" = 1 ]; then
93+
echo "FAIL: connection closed at ${ELAPSED}s, before the grace deadline ($GRACE s)"
94+
exit 1
95+
else
96+
echo "FAIL: connection still open past the grace time (not enforced)"
6297
exit 1
6398
fi
6499

0 commit comments

Comments
 (0)