Commit Graph

3043 Commits (3516fb00a089382929fb89461535dfb511443804)

Author SHA1 Message Date
John Safranek 3516fb00a0 ssh, sftp: match the sftp subsystem name exactly
The built-in SFTP server takes a session only when the subsystem name
is sftp, matched whole. DoChannelRequest() keeps the parsed length in
channel->commandSz, so neither wolfSSH_SFTP_accept()'s grant gate nor
wolfSSH_accept()'s divert serves "sftpx" or "sftp\0evil".

- cover a granted name longer than sftp, one of its length, and one
  running past an embedded NUL
- cover the divert with those three names and a control that diverts
- exec keeps its command length too

Issue: F-11665
2026-09-10 17:14:23 -07:00
John Safranek 7adf524f6f sftp: require a granted subsystem to serve
wolfSSH_SFTP_accept() serves an application-driven session only on a
channel whose subsystem request was answered CHANNEL_SUCCESS.
DoChannelRequest() records the session type and command before it
decides, and leaves both set on a refusal, so they cannot say by
themselves whether anything was granted.

- add channel->sessionGranted, set from the answer a shell, exec or
  subsystem request gets rather than from the request arriving
- look the channel up again before recording it: a callback may close
  its own channel, and wolfSSH_ChannelFree() frees it
- log a request's strings where they are known good: once the parse
  has succeeded, and ahead of a callback that may free the channel
- gate the app-channels path on that flag alongside the session type
  and the command
- cover a refusal from both sides, no callback registered and a
  callback that rejects, and a callback that frees its channel
2026-09-10 17:14:23 -07:00
John Safranek 742f327ed4 sftp: serve app-channels only on a granted sftp
In application-driven mode wolfSSH_accept() parks at userauth, so the
sftp test its divert applies never runs. wolfSSH_SFTP_accept() applies
it itself: the session channel must be a subsystem the application's
callback granted sftp on, or the call returns WS_INVALID_STATE_E and
leaves the wire alone without recording an error.

- gate the app-channels branch on wolfSSH_GetSessionType() and
  wolfSSH_GetSessionCommand(), the same test accept() makes
- ask for that grant in every accept state: below the user-auth stop
  accept() returns with no channel open, and past the stop there is no
  accept() left that could have checked anything
- say in ssh.h that the mode serves SFTP through that grant and never
  reaches the SCP entry point
- regress.c refuses the call with no channel, ahead of accept(), on a
  granted shell and on an established one, and serves an INIT on a
  granted sftp subsystem
2026-09-10 17:14:23 -07:00
John Safranek 4ac4484afd ssh: correct what a late app-channels enable does
DoChannelRequest() reads ssh->appChannels when the request arrives, so
turning the mode on after accept() established the session still refuses
an uncallbacked shell, exec or subsystem request from then on. Only
accept()'s stopping point is pinned, by the guard around stopState.

- say the flag reaches the requests that follow, and that what it cannot
  do is move where accept() returns
- drive a shell request over the wire in both modes from the late-enable
  test, pinning the behaviour the header now describes
2026-09-10 17:14:23 -07:00
John Safranek 2c832010d2 tests: cover application-driven channels
wolfSSH_SetAppChannels() changes where wolfSSH_accept() stops and what
becomes of a session request with no callback behind it, so both modes
are exercised.

- regress.c drives a server with the pivot on, one with a shell
  callback and one without, and checks accept() stops at
  ACCEPT_SERVER_USERAUTH_SENT
- regress.c pins the context setter, the session's inheritance of it,
  and that turning it on after accept() established the session still
  returns
- regress.c re-enters a parked accept() with output still queued, which
  is the one path that flushes before reading the state, and pins that
  it leaves the state on the stop
- unit.c checks DoChannelRequest() refuses a shell, exec and subsystem
  request with no callback once the pivot is on
2026-09-10 17:14:23 -07:00
John Safranek b723519312 ssh: add opt-in application-driven channels
A server that wants to own its channels had no way to get them: accept()
ran the session state machine to the end, and a shell, exec or subsystem
request with no callback registered was granted regardless.

- add wolfSSH_CTX_SetAppChannels() and wolfSSH_SetAppChannels(), off by
  default, a byte on the context copied into the session
- on, accept() returns once the user is authenticated, and a session
  request with no callback behind it is refused: nothing is left to serve
- keep the stop state out of the pending-send advance, so a re-entry with
  queued output cannot step over where this call is meant to stop
- stop early only while the session is short of that state, so turning the
  mode on afterward cannot leave the loop hunting a state it went past
- teach wolfSSH_SFTP_accept() that the mode parks accept() short of an
  established session, so it stops redoing the handshake on every poll
2026-09-10 17:14:23 -07:00
Yosuke Shimizu c039486e52 scp: return directory entries from the Zephyr entry walk
- The WOLFSSH_ZEPHYR branch of FindNextDirEntry() loops while the
  entry name is "." or "..", matching the POSIX and Windows
  branches, in place of while (1).
- tests/api.c gains test_wolfSSH_SCP_SendRecursiveEntry(), staging a
  directory holding one file and driving three
  WOLFSSH_SCP_RECURSIVE_REQUEST calls through wsScpSendCallback(),
  checking the entry name, size, and bytes placed in buf.  dirPath
  is rooted at CONFIG_WOLFSSH_SFTP_DEFAULT_DIR under WOLFSSH_ZEPHYR
  and at "./scp_recur_entry" otherwise.
- The test is gated on WOLFSSH_SCP, with WOLFSSH_SCP_USER_CALLBACKS,
  NO_FILESYSTEM and NO_WOLFSSH_DIR unset, carries an empty stub
  otherwise, and is called from wolfSSH_ApiTest().
- scpStageRecurFile() writes that fixture file.

Issue: F-13315
2026-09-10 15:28:17 -07:00
Yosuke Shimizu d3ad9fa5ed internal: gate auth-agent channel opens on the client's request
- DoChannelOpen refuses an auth-agent open on a server endpoint, and on
  a client with the agent disabled or connectState below
  CONNECT_CLIENT_CHANNEL_AGENT_REQUEST_SENT.
- Both answer OPEN_ADMINISTRATIVELY_PROHIBITED; the ssh->agent check
  stays as the resource check behind them.
- Cover the refusals before the request, with the agent disabled, over
  an accepting channelOpenCb, and on a server, plus the accepted open
  past the request; each refusal asserts ssh->error stays clean.
- Move TestAgentChannelNullAgentSendsOpenFail to a client harness, and
  have the agent tests set the agent flag explicitly.

Issue: F-13389
2026-09-10 15:28:04 -07:00
Hideki Miyazaki e04a29784f windows-check: Cache the wolfSSL build in mingw-regress
Every run of this job rebuilt wolfSSL --enable-all from source on a
Windows runner, about five minutes, even though the ref is pinned
and the configure flags never change between runs.

Add an actions/cache step keyed on wolfssl-mingw-regress-wolfssl-
<ref>-windows-latest, matching the naming singlethread-check.yml and
x509-interop.yml use. Those two split the work into a build_wolfssl
job and a build_wolfssh job, with a lookup-only cache check in the
first and a fail-on-cache-miss restore in the second. This job has
no such split, so it uses a single plain actions/cache step instead:
it restores on a hit and saves automatically after the job on a
miss.

The MSYS2 shell's $HOME lives under setup-msys2's own temp install
directory, not a path stable across runner images, so build to
${{ github.workspace }}/wolfssl-install instead: actions/cache
resolves a relative path against GITHUB_WORKSPACE, and cygpath -u
gives the msys2 shell steps the same directory as $WOLFSSL_INSTALL.
2026-09-10 15:27:50 -07:00
Hideki Miyazaki 343582a61e tests: Key regress.c's Windows portability macros on _WIN32
TEST_NULL_DEVICE, TEST_SETENV, TEST_UNSETENV, and TEST_MKDIR were
gated on USE_WINDOWS_API, while the arpa/inet.h/direct.h include
guard a few lines above keys on _WIN32. Whether mkdir(), setenv(),
and /dev/null work as expected is a libc availability question, not
a wolfSSH API-selection one, so _WIN32 is the more direct fit and
now both guards in this file agree.

No behavior change: USE_WINDOWS_API is only ever set when _WIN32 is
already defined, so this does not change which branch any current
build takes.

Verified with the MinGW cross compiler both with WOLFSSH_SFTP defined
and without, and confirmed a plain Linux build still passes
tests/regress.test and tests/unit.test.
2026-09-10 15:27:50 -07:00
Hideki Miyazaki 0bb371a4b2 windows-check: Pin wolfssl to v5.9.1-stable in mingw-regress
The wolfssl checkout in mingw-regress had no ref, so it always built
against wolfssl/wolfssl's default branch. An upstream change there
could break this job with nothing changed on the wolfssh side to
explain it, and this job in particular turned out to be sensitive to
exact wolfSSL build details while it was being brought up.

Pin it to v5.9.1-stable, the same tag singlethread-check.yml and
x509-interop.yml already use, confirmed to include wc_mlkem.c so the
ML-KEM coverage this job exercises is still built. The build and
asan-tests jobs in this file have the same unpinned checkout but are
left alone here, since they were not touched by this change.
2026-09-10 15:27:50 -07:00
Hideki Miyazaki 82032bddfe tests: Stop TestKnownHostsLastEntry depending on WMKDIR
A local run with ./configure --enable-smallstack (no --enable-sftp)
showed WMKDIR as an implicit, undeclared function under MinGW.
wolfssh/port.h only defines WMKDIR when WOLFSSH_SFTP, WOLFSSH_SCP, or
WOLFSSH_SSHD is enabled, but TestKnownHostsLastEntry itself is gated
on WOLFSSL_BASE64_ENCODE alone, so it compiles in configurations
where none of those three are on and WMKDIR does not exist.

Add a small TEST_MKDIR macro next to the existing TEST_SETENV and
TEST_UNSETENV ones, backed directly by _mkdir() under USE_WINDOWS_API
and mkdir() otherwise, and use it in place of WMKDIR. The Windows
branch needs direct.h for _mkdir's declaration; include it next to
the existing _WIN32 guard around arpa/inet.h at the top of the file.

Verified with the MinGW cross compiler both with WOLFSSH_SFTP defined
and without, and confirmed a plain Linux build configured with
--enable-smallstack (no --enable-sftp) still passes tests/regress.test
and tests/unit.test.
2026-09-10 15:27:50 -07:00
Hideki Miyazaki 53683a758c tests: Use the Windows null device in the known_hosts regression test
The next mingw-regress failure after the previous fix was
TestKnownHostsLastEntry failing its "ready" assertion. It calls
open("/dev/null", O_RDONLY) to point stdin at EOF while it drives the
known_hosts prompt, but Windows has no /dev/null; the call simply
fails there, so open() returns -1 and every check gated on ready is
skipped.

Add a TEST_NULL_DEVICE macro that resolves to "NUL" under
USE_WINDOWS_API and "/dev/null" otherwise, and use it at both call
sites in this file: TestKnownHostsLastEntry, and the same pattern in
TestPasswordEofNoCrash a bit earlier, which happens to be masked in
CI by its own isatty() guard but would hit the same bug if ever run
against a real terminal on Windows.
2026-09-10 15:27:50 -07:00
Hideki Miyazaki 4df003061a apps/wolfssh: Write known_hosts entries in binary mode
CI's mingw-regress job showed AppendKeyToFile writing a known_hosts
entry, then TestAppendKeyToFile reading it back and finding a byte
mismatch on Windows. The file was opened with WFOPEN(..., "a"), a
text mode append. On Windows the C runtime rewrites '\n' to CRLF on
write in text mode, so the entry landed on disk with a trailing
"\r\n" instead of the "\n" the test wrote and expected back.

Open the file in binary mode instead, matching the WriteTextFile test
helper a few hundred lines above in tests/regress.c, which already
uses "wb" for the same reason. known_hosts is conventionally
LF-terminated regardless of platform, so this also matches the format
other SSH clients expect from the file, not just the test.
2026-09-10 15:27:50 -07:00
Hideki Miyazaki 6d163a87d9 Fix remaining MinGW build errors in the regress/unit test build
apps/wolfssh/common.c declared a CONSOLE_SCREEN_BUFFER_INFO local in
ClientSetEcho that nothing ever read; the Windows echo toggling never
grew the code that would have used it. Drop the unused declaration.

wolfssh/port.h defined WSTRSEP as strsep(), a BSD extension MSVCRT
and MinGW do not provide. Add a portable wstrsep() in src/port.c,
matching the wstrnstr/wstrncat/wstrdup pattern already used for other
missing string functions, and route WSTRSEP through it under
USE_WINDOWS_API.

tests/regress.c called the two argument POSIX mkdir(path, mode) and
setenv()/unsetenv() directly in TestKnownHostsLastEntry. Use the
existing WMKDIR macro for the directory creation, and add small
TEST_SETENV/TEST_UNSETENV macros backed by _putenv_s() on Windows so
the HOME juggling this test does still works there.

src/wolfsftp.c had three separate issues in code paths that had never
been compiled before this job existed. wolfSSH_SFTP_RecvOpen declared
a flagsAndAttrs DWORD that nothing read, since WS_CreateFileA is
called with a hardcoded FILE_ATTRIBUTE_NORMAL instead.
wolfSSH_SFTP_RecvOpenDir compared a signed loop counter against a
sizeof expression while building ssh->driveList, so make the counter
word32. wolfSSH_SFTP_Put passed &state->rSz, an int, to ReadFile()'s
DWORD* output parameter; read into a local DWORD and copy it into
state->rSz afterward, since that field is also assigned from
WFREAD() on non-Windows builds.

src/wolfterm.c's wolfSSH_DoOSC never used its handle parameter. Mark
it with WOLFSSH_UNUSED rather than removing it, since the parameter
matches the signature its two call sites already pass and future OSC
handling such as window titles is a natural use for it.

Verified against a real x86_64-w64-mingw32 cross compiler with a
config.h edited to match the sizes and header availability the
actual Windows CI run reported (SIZEOF_LONG 4, HAVE_SYS_IOCTL_H
undefined, and so on): every file this job compiles builds cleanly
under the same -Werror flag set. Also reconfirmed a clean, unmodified
Linux build still passes both tests/regress.test and tests/unit.test.
2026-09-10 15:27:50 -07:00
Hideki Miyazaki 83a2246471 Fix three MinGW -Werror build failures in the regress/unit test build
MinGW-w64 does not ship arpa/inet.h; guard tests/regress.c's include
the same way apps/wolfsshd/auth.c already does, since htonl/ntohl end
up declared via the winsock2.h wolfSSL's headers pull in later in the
same translation unit.

wolfssh/test.h guarded its MSVC #pragma warning(disable:4996) with
USE_WINDOWS_API alone, which is also true for MinGW's gcc; gcc treats
the unrecognized pragma as an error under -Werror. Require _MSC_VER
too, matching the existing ALIGN16 pragma guard in wolfssh/internal.h.

wolfSSH_CleanPath's Windows/Nucleus drive-letter cleanup re-declared
`i` in a nested scope, shadowing the function's own `i` used by every
other loop in the function. Hoist `j` to the function's declarations
(guarded by the same #if so non-Windows/Nucleus builds don't get an
unused-variable warning) and drop the now-unnecessary block so the
loop reuses the outer `i`.

Verified locally: autoreconf + configure + make tests/regress.test
tests/unit.test builds clean and both binaries pass on Linux.
2026-09-10 15:27:50 -07:00
Hideki Miyazaki 4105c2242c windows-check: Build the .exe-suffixed regress/unit test targets on MinGW
The new run showed the ws2_32/crypt32 link fix worked (configure
passed) but make then failed: "No rule to make target
'tests/regress.test'." Reproducing the autotools build locally
confirmed why: MinGW's EXEEXT is ".exe", so automake's check_PROGRAMS
rule names the binaries tests/regress.test.exe and tests/unit.test.exe,
not the extension-less names this job was asking make to build and run.
2026-09-10 15:27:50 -07:00
Hideki Miyazaki 90ce9fb61b windows-check: Link ws2_32 and crypt32 for the mingw-regress wolfssh build
config.log from a failing run showed the wolfCrypt_Init AC_CHECK_LIB
probe pulling in ssl.c/internal.c/wolfio.c from the static
libwolfssl.a, leaving Winsock (socket, send, recv, inet_pton, ...) and
cert store (CertOpenSystemStoreA, ...) symbols unresolved. A shared
build would defer that resolution to the DLL; the static archive here
needs ws2_32 and crypt32 passed explicitly via LIBS.
2026-09-10 15:27:50 -07:00
Hideki Miyazaki 3532cc4432 windows-check: Print config.log on mingw-regress failure
The mingw-regress job's wolfssh configure step fails with
"libwolfssl is required for wolfssh" even though libwolfssl.a is
installed at the expected path. AC_CHECK_LIB only reports pass/fail;
dump config.log on failure to see the actual link error.
2026-09-10 15:27:50 -07:00
Hideki Miyazaki 39fec50df2 wolfsshd: Cover -D option parsing on Windows in CI
The Windows StartSSHD() path rebuilds argv from GetCommandLineW(). A
regression there left -D foreground mode walking the raw wide command
line, so -f and -p were ignored and the daemon used its built-in
defaults. Nothing in CI caught that.

Add sshd_dash_d_test.ps1: it starts wolfsshd with -D and a config file
at a non-default path whose Port line differs from the -p value, then
checks the listener binds the -p port and not the config port. That
holds only when -D mode parsed both -f and -p. Run it from the Windows
build job next to the existing LoginGraceTime check.
2026-09-10 15:27:50 -07:00
Hideki Miyazaki ea30c9fd9d Run the SFTP regression tests on Windows under MSYS2
tests/regress.c holds the only coverage for the Windows SFTP open path,
including TestSftpWindowsOpenFlagMatrix, which walks the RecvOpen
CREAT/EXCL/TRUNC/APPEND matrix against the CreateFile() disposition
table. That test is guarded by USE_WINDOWS_API and ran in no CI job. The
MSVC solution in ide/winvs has no regress project, and regress.c does not
build with cl because it includes arpa/inet.h and unistd.h, so the
disposition fix it locks down could regress unnoticed.

Add an MSYS2 MinGW64 job to the Windows workflow. MinGW defines _WIN32,
so wolfssh/settings.h turns on USE_WINDOWS_API and the Windows-only
branches compile and run. The job builds wolfSSL static, configures
wolfSSH with --enable-sftp, and runs tests/regress.test and
tests/unit.test. wolfsshd is left out: its autotools path is not
MinGW-clean and the MSVC solution already covers it.
2026-09-10 15:27:50 -07:00
Aidan Keefe 4553b3c659 Skoll review and Github review
Added more test vectors as well as locking in behavior of VerifyProtoId
Added round trip test for proto id
Comment fixes
New define for proto id min size
2026-09-10 15:27:30 -07:00
Aidan Keefe 155a4e1c47 Validate a custom SSH proto ID string
wolfSSH_CTX_SetSshProtoIdStr() now rejects a string that is not
CRLF-terminated, exceeds 255 bytes counting the terminator, or
carries a CR or LF in the body. DoKexInit() subtracts the two
terminator bytes from the length when hashing it, so an
unterminated string underflowed the hash length.

Issue: F-10571

partial progress
2026-09-10 15:27:30 -07:00
Yosuke Shimizu 8d6e2b3aba wolfsshd: complete the Windows user profile fallback
- _GetHomeDirectory loads the user's profile when WOLFSSHD_AUTH's new
  profile member is NULL, setting PROFILEINFO.dwSize first and keeping
  the returned hProfile there.
- _GetProfileDirectory reads the home directory with
  GetUserProfileDirectoryW, in place of SHGetKnownFolderPath and the
  %USERPROFILE% expansion. CheckPublicKeyWIN calls it directly, so a
  caller that has not authenticated the user builds no profile.
- wolfSSHD_AuthCloseToken unloads the profile before closing the token,
  calling RegCloseKey when the unload fails.
- The Windows shell cleanup calls RevertToSelf() before closing the auth
  token rather than after.
- windows-sftp.yml gains a no_profile job that covers an exec session,
  two overlapping sessions, and SFTP for users created with net user
  alone; it skips the earlier SFTP step so its exec session connects
  first.
- Both Windows workflows log testuser on once so Windows builds a real
  profile, in place of writing the home directory and ProfileList entry
  by hand, and the recursive icacls grants on it are gone.

Issue: F-13326
2026-09-09 22:06:06 -07:00
Yosuke Shimizu dee02b2289 ci: cover recursive SCP on Windows
- windows-sftp.yml generates an ecdsa key for testuser, authorizes it,
  and builds a source tree holding two files, a nested subdirectory
  and an empty one.
- A new step pulls that tree with scp -O -r and fails unless every
  file and directory arrives. -O keeps OpenSSH on the legacy SCP
  protocol rather than SFTP.
- The header comment lists the recursive SCP scenario.
2026-09-08 15:44:54 -07:00
Yosuke Shimizu 29762a64c6 port, scp: distinguish end of directory from enumeration failure
- WS_FindNextFileA_ex() takes a lastError out-parameter and carries
  the body of WS_FindNextFileA(), which now calls it with NULL. A
  failing return reports the FindNextFileW() error, or
  ERROR_NO_UNICODE_TRANSLATION when the name would not convert.
- FindNextDirEntry() calls WS_FindNextFileA_ex() on Windows. An
  ERROR_NO_MORE_FILES result frees ctx->entry, sets it to NULL and
  returns WS_NEXT_ERROR so the caller pops the directory; every other
  error still returns WS_FATAL_ERROR.

Issue: F-13316
2026-09-08 15:44:54 -07:00
John Safranek e7a05229ad agent: commit the open on a completed flush
wolfSSH_AGENT_ChannelOpen() settles its channel and agent on whether
the open reached the peer, which SendPacketDelivered() answers from the
flush count. The highwater callback runs after the last byte is out, so
its failure arrives as this send's return, and the rollback read that
as a send that never left.

- give SendPacketDelivered() external linkage, contract in internal.h
- note in agent.h that an error raised once the open is on the wire
  leaves the channel open
- test a failing highwater callback: the channel and the agent stand,
  and the poll after it is the idempotent one
2026-09-08 11:50:40 -05:00
John Safranek 6164ec39d5 agent: refuse a channel open after a disconnect
wolfSSH_AGENT_ChannelOpen() answers a poll on a session that is over
with WS_FATAL_ERROR and WS_DISCONNECT in ssh->error, the shape every
other public sender uses: no channel opened, nothing on the wire,
RFC 4253 section 11.1. wolfSSH_accept() gates the open it drives, so
the new public entry point is the only way in.

- promote SendAfterDisconnect() to WOLFSSH_LOCAL so agent.c uses the
  same helper as every other public sender
- leave an open queued before the disconnect unflushed, the rule
  wolfSSH_shutdown() applies to all but its own disconnect
- keep WS_DISCONNECT in ssh->error at the accept() call site, which
  used to overwrite it with the status the open returns
2026-09-08 11:50:40 -05:00
John Safranek 41f171d982 agent: let the application open the agent channel
The one server-side site that opens auth-agent@openssh.com sits inside
wolfSSH_accept(), so an application driving its own channels cannot
reach it: the session records the request and no channel follows.

- add wolfSSH_AGENT_ChannelOpen(), the same open lifted out of accept(),
  which still calls it
- it reports WS_BAD_ARGUMENT until the peer asks and on a client
  session, and is idempotent after, so an application can poll it
- publish the agent on a queued open too, so a retry after WS_WANT_WRITE
  finds it rather than opening a second channel and leaking the first
- flush what is left of a queued open on the next call, rather than
  reporting a success the peer never saw
- record ssh->error from the send alone, so neither a poll ahead of the
  request nor a failed allocation stops accept() continuing
2026-09-08 11:50:40 -05:00
John Safranek 85f7978eb4 SCP: report only a read want from SCP_accept
wolfSSH_SCP_accept() reported any want held in ssh->error in place of
the DoScpRequest() result. A short send that SendChannelData() accepts
leaves WS_WANT_WRITE there with nothing clearing it on a later flush,
so a terminal result came back as retryable and the retry re-entered
the state machine, re-sending an abort confirmation or reading past a
bad message.

- Substitute the want only when the result is WS_FATAL_ERROR and the
  want is WS_WANT_READ, the one case GetInputData() hides by value.
- Leave write wants alone; the SCP state machine already returns them.
2026-09-08 10:17:42 -05:00
John Safranek 13b9e39f90 SCP: let the application start the transfer
An application that binds an "scp ..." command to a channel itself has no
way to run the transfer; wolfSSH_accept() did it through a WS_SCP_INIT
re-entry only that state machine can drive.

- add wolfSSH_SCP_accept(), a wrapper over DoScpRequest() reporting
  WS_SCP_COMPLETE for any non-negative result, as accept() does
- a receive-side want reaches the wrapper as a generic error with the
  want in ssh->error, so report the want itself and let the caller retry
- state that resume contract beside the prototype, and clear a stale want
  on entry the way the other re-entrant entry points do
2026-09-08 10:17:42 -05:00
John Safranek ba6bc667a9 portfwd: report the channel open response
wolfSSH_CTX_SetChannelOpenRespCb() had no caller anywhere in the tree, so
the confirm and fail hooks had no worked example and a forwarding client
had nothing to say which of its opens the peer refused.

- register both arms and print the channel each one names
- name our own id, not the peer's: a refused open never learns the peer's,
  and it is the id portfwd_worker() already tracks
2026-09-05 22:08:51 -05:00
JacobBarthelmeh 61fa771aa6 remove two UTF-8 characters and NuGet restore from test yml 2026-09-05 01:32:02 -07:00
JacobBarthelmeh 161c5ae24f expand CI test for non-fpki build, fix check for error state with CertEnumCertificatesInStore, add extended key usage type check if present, fix for EKU checks with X509 use and add test case 2026-09-05 01:32:02 -07:00
JacobBarthelmeh 0be0fae009 use wolfSSH_ prefix on wolfSSH specific config's HostKeyStore, HostKeyStoreSubject, HostKeyStoreFlags 2026-09-05 01:32:02 -07:00
JacobBarthelmeh ed9bf644d0 use proper free for cert store version, fix echoserver on mac os 2026-09-05 01:32:02 -07:00
JacobBarthelmeh 4baba585d5 fix for flags handling, ocsp case, macro guards, unused variable, changed default from MY to required to be set
enable SHA1 with windows cert store test case

expand test cases, adjust to authorized key file, minor dead code adjustments

add more documentation, refactor duplicate code sections, clean up test cases, more adjustments to logging spamming protections
2026-09-05 01:32:02 -07:00
JacobBarthelmeh 13c2adf34b add Windows cert store use with signing and add example arguments
add Windows cert store test case

make windows cert feature default disabled and simplify macro guard

additional unit tests, advertise x509 and pubkey, use CN to match username, build check for WOLFSSL_SYS_CA_CERTS, fix for CM ref count

additional build test, uniform enum name, fail on unkown cert store ecc curve, tie in of loading whole cert store for sys CA's
2026-09-05 01:32:02 -07:00
Kareem 2584a027b4 Prefix wolfSSH specific options with wolfSSH_. 2026-09-05 01:32:02 -07:00
Kareem dd955673a4 Add support for loading user CA certs from a configurable Windows cert store. 2026-09-05 01:32:02 -07:00
JacobBarthelmeh 440373681a add macro guard for system ca certs load 2026-09-05 01:32:02 -07:00
JacobBarthelmeh 37e75d4aa0 add TrustedSystemCAKeys sshd option for system CA load 2026-09-05 01:32:02 -07:00
John Safranek fb15a05ed0 tests: cover the forward cleanup callback
WOLFSSH_FWD_LOCAL_CLEANUP now fires, and what must not fire matters as
much as what must: the gate is the channel's own setup record, not its
type.

- a peer-opened direct-tcpip channel reports SETUP, CHANNEL_ID, then
  CLEANUP when it closes
- a forwarding channel this side opened reports no cleanup, and a
  session channel no forwarding action at all
- an open that fails after a successful setup still reports the cleanup,
  and a setup that reported failure draws none
- freeing the session, or the channel with wolfSSH_ChannelFree(),
  reports it, and the session free after a channel free does not
  report it again
2026-09-03 21:43:30 -05:00
John Safranek 6f9b021f0d portfwd: stop double-closing the forwarded socket
The LOCAL_CLEANUP handler closes the target socket, and portfwd_worker()
closed its own copy of the same descriptor again at teardown. That was
unreachable while the library never emitted the action, which is why the
report against it was set aside; it is reachable now.

- record the cleanup in the forwarding state and skip the second close
- clear the shared descriptor when the exit path is the one that closes
  it: the loop can leave with the channel still open, and freeing the
  session then runs the handler on a descriptor already closed
- reset the record when a fresh forward's socket is adopted, or one left
  by an earlier refused open skips closing a live socket at exit
- drop the handler comment saying the action is never emitted
2026-09-03 21:43:30 -05:00
John Safranek 70b22bd97d echoserver: let the cleanup callback tear down
WOLFSSH_FWD_LOCAL_CLEANUP now runs, and it runs from DoChannelClose()
ahead of the WS_CHANNEL_CLOSED the worker sees. The handler has already
closed the socket and moved the state on by then, so the recovery branch
no longer matched and left ssh_worker() holding a closed descriptor.

- guard the handler's close: the open can fail after the setup, with
  nothing yet connected
- gate the handler on the channel id the library passes in the port
  parameter. A channel can outlive its turn in the single forwarding
  slot, and a cleanup arriving after the next forward has moved in
  would close that one's live socket
- have the recovery branch clear its stale copy of the descriptor when
  the handler got there first, and still do the whole teardown for a
  locally opened forward, which draws no callback
- resolve the closed channel with wolfSSH_GetLastRxId(). wolfSSH_worker()
  names the channel only for the data and EOF statuses, so the recovery
  branch was comparing against a stale zero and ran only for a forward
  that happened to be channel 0
- clear the pending direct connect as well: it is only cleared on
  success, so a refused target left it set and the worker connected
  again with the host name the handler had just freed
2026-09-03 21:43:30 -05:00
John Safranek 6485062d3d internal: send LOCAL_CLEANUP on forward delete
WOLFSSH_FWD_LOCAL_CLEANUP is part of the public WS_FwdCbAction contract
and the library never sent it, so an application's handler never ran and
every peer-opened forward leaked what its setup callback allocated.
ChannelDelete() now sends it, so a peer close, an open refused after the
setup ran, wolfSSH_ChannelFree(), and freeing the session all report it.

- record the setup in a new fwdSetupTxd bit on WOLFSSH_CHANNEL: a
  locally opened forward gets no LOCAL_SETUP, so the channel type alone
  cannot say whether the application holds anything
- clear the bit when the cleanup goes out, so a channel reports it once
- pass the channel's id in the port parameter, the way
  WOLFSSH_FWD_CHANNEL_ID does, so an application with two forwards can
  tell which one ended
- TestDirectTcpipFwdCbRejectsChannelId now counts three callback calls
2026-09-03 21:43:30 -05:00
John Safranek f8b72ac079 tests: move bulk data through a forward
scripts/fwd.test sends eight short lines, so nothing in the suite drives
a forwarding channel past its first window, and a forward that stalled
once the window needed crediting would go unnoticed.

- push a payload several windows long through a local direct-tcpip
  forward and compare the bytes that arrive
- end a second transfer one window plus a short tail in, where the tail
  is what portfwd still holds at end-of-input, and repeat it since the
  window credit beating that tail is a race
- give the listening nc its stdin from a fifo a sleep holds open:
  reading end-of-input makes nc close the connection, which truncates
  the transfer and looks exactly like a stall
- bail out early once the byte count stops moving, so a real stall
  reports in seconds
- dump the logs on failure, check the listening nc came up, and take
  ports clear of the ones fwd.test.expect hardcodes, so a squatted or
  shared port is not reported as a stall
2026-09-03 20:40:55 -05:00
John Safranek 31f831b7ad portfwd: drain the buffer before leaving on EOF
A zero read on the local socket left the loop at once, and anything
read but not yet accepted by wolfSSH_ChannelSend() went with it. With
the peer's window full that tail is up to a buffer's worth, so a
transfer that ends while the window is being credited comes out short.

- stop polling the socket on end-of-input and keep looping until the
  buffer is empty
- leave anyway once the channel is gone, so a forward whose open failed
  cannot hold the loop on a buffer nothing can take
2026-09-03 20:40:55 -05:00
John Safranek ee7fa04bec ssh: flush any queued output at teardown
wolfSSH_shutdown() flushes whatever a short send left in the output
buffer, not just a queued disconnect. A rejected auth's
USERAUTH_FAILURE has no channel, and DoChannelClose() retires the
channel as soon as the close is bundled, so neither had anything
left to carry the retry.

- The gate still refuses a flush once the peer has disconnected,
  unless our own disconnect is the thing queued.
- A flush that finishes on a live session clears the WS_WANT_WRITE
  the short send latched, as the disconnected path already did.
- ssh.h documents the widened flush and the WS_WANT_WRITE a short
  one leaves wolfSSH_shutdown() returning.
- Tests cover the rejected auth with no channel and the close whose
  channel DoChannelClose() already retired.
- A flush that short-sends on a live channel leaves the teardown
  queued behind it. The retry adds no second EOF or close, and skips
  the close wait while its own flush is still short.
- A teardown send that drained the leftovers settles the flush; one
  that failed outranks it, since a reset leaves the buffer intact.
2026-09-03 16:12:17 -05:00
John Safranek 54610fd36e tests: cover the exec and subsystem req callbacks
Only the shell hook had coverage, so nothing held DoChannelRequest() to
handing the command and session type to the exec and subsystem callbacks
or to answering with what they return.

- assert each callback sees the session type and the command string the
  request carried, and the ctx set on the session
- assert an accepting callback draws CHANNEL_SUCCESS and a rejecting one
  CHANNEL_FAILURE, whether it rejects with a WS_ error or a bare nonzero
2026-09-03 15:31:58 -05:00