Commit Graph

467 Commits (586b697b18b25a4e3e90acc76225c6aa8874cd78)

Author SHA1 Message Date
Yosuke Shimizu 586b697b18 ssh, internal: always flush the worker's output
- wolfSSH_worker() calls wolfSSH_SendPacket() whenever
  ssh->outputBuffer holds bytes and the session is not
  disconnected. ssh->error keeps the receive's code when the
  receive failed, and the close's when a WS_CHANNEL_CLOSED pass
  hard-failed its flush; WS_REKEYING is withheld on a failed
  flush. Drops the second DoReceive(), the WOLFSSH_TEST_BLOCK
  fork and the separate WS_CHANNEL_CLOSED flush.
- BundlePacket() resets ssh->outputBuffer.length to
  ssh->packetStartIdx when the framing fails. wolfSSH_shutdown()
  reports WS_WANT_WRITE when its close-read leaves output queued,
  and the send's own error in place of it when that send failed.
  SendPacketFlush() records its code in ssh->error on every
  transport failure path, and wolfSSH_TriggerKeyExchange() writes
  it only when SendKexInit() fails.
- portfwd, client and scpclient accept WS_WANT_WRITE from
  wolfSSH_shutdown(); in scpclient the close-message drain runs
  on it.
- wolfssh/ssh.h drops WS_WINDOW_FULL from wolfSSH_worker() and says
  to read the return and wolfSSH_get_error() as independent channels
  on every pass.
- Twenty unit tests and the extended TestWorkerReportsDisconnect
  cover what ret and ssh->error hold after a receive, send, buffer,
  callback or framing failure.
2026-09-14 10:40:48 -07:00
Yosuke Shimizu ab979865aa apps, examples: tolerate a worker want-write
- The echoserver and Espressif shell loops and the Windows
  wolfsshd shell loop treat a WS_WANT_WRITE from wolfSSH_worker()
  as non-fatal.
2026-09-14 10:40:48 -07: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 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 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
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 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
Emma Stensland 6a71292401 internal: Refactor and gate ML-DSA composite key handling
Gates ML-DSA composites behind WOLFSSH_NO_MLDSA_COMPOSITES.
Deduplicates key handling and uses heap allocation for
composite buffers when compiling for small stacks.
2026-09-02 15:35:09 -07:00
John Safranek 7325678553 echoserver: keep the EOF reply owed across a rekey
wolfSSH_ChannelIdRead() has no rekey guard, so a drained channel still
reports zero mid-rekey and the drain loop calls the reply in. That send
returns WS_REKEYING before it prepares a packet, so nothing is queued.

- take the send's status instead of discarding it
- latch eofAnswered and ChildRunning on every status but WS_REKEYING, so
  the reply is retried on a later pass; the KEX traffic wakes it
- a short send is left latching: it bundled the EOF and set eofTxd, so a
  retry queues nothing and the loop would stall in an untimed select
  waiting on a peer that has already half-closed
- same change in the Espressif copy
2026-08-31 11:53:29 -05:00
John Safranek fc3b0a86bd Take the worker's status before the EOF drain
The drain runs between wolfSSH_worker() and the get_error() that classifies
its result, and its reads and sends latch their own status: WS_WINDOW_FULL
and WS_WANT_WRITE from a send, WS_REKEYING from a read. The ladder then read
the drain's status as the worker's, matched no arm, and ended the session
with the backlog unsent and no EOF.

- Read the error once, right after the worker returns
2026-08-31 11:53:29 -05:00
John Safranek 033646ce0a Re-resolve the forwarded channel before the half-close check
A refused channel open frees the channel and surfaces as a fatal error
rather than a close, so the guard that clears fwdChannel never runs and
the half-close check read freed memory on every refused -L forward.

- Stash the channel id wherever the channel is created or adopted
- Look the channel up by id each pass and stop once it is gone
2026-08-31 11:53:29 -05:00
John Safranek ad08a10ba6 Hold the EOF drain's unsent tail across passes
A chunk read out of the channel is gone from it, so breaking the drain on
a non-positive send dropped whatever the send had not taken, and the echo
back to a half-closing peer came up short.

- Keep the chunk and its offset across worker passes, and read the next
  chunk only once the last one is out
- Give the drain its own buffer; the read path below it reuses
  channelBuffer in the same pass
- Answer the EOF off a drained flag, since a held tail means a zero read
  count no longer marks an emptied channel
2026-08-31 11:53:29 -05:00
John Safranek ff59c723ec Handle the EOF status in apps and examples
Every in-tree caller of wolfSSH_worker() now recognises a peer half-close.
wolfsshd's shell loop and both echoservers need it: all three ladders end in
"else if (rc != WS_WANT_READ) break", and wolfsshd's reaches
kill(childPid, SIGKILL), so without it a client half-close kills the command
it just finished feeding.

- wolfsshd closes the child's stdin off the channel's own EOF state instead of
  off a worker return of zero, which no longer happens on a half-close.
- The echoservers answer the half-close off wolfSSH_ChannelGetEof() rather
  than the WS_EOF status: the flush inside wolfSSH_worker() can supersede that
  status, and it is raised once. They hand back the backlog first, finish a
  short send, and only send the EOF once the channel is empty. Answering is
  not conditional on the shell build, where an echo session is the default.
- The SFTP loops peek before leaving, so a half-close with requests still
  buffered is served rather than dropped, and they report an ordinary session
  end as success.
- The clients -- examples/client, scpclient, sftpclient, apps/wolfssh -- treat
  it as the graceful case instead of an error. apps/wolfssh counts it as a
  finished flush as well, since one worker pass can drain the queue and
  consume the peer's EOF together.
- portfwd relays it to the local socket with shutdown(SHUT_WR) so a local
  reader waiting on end-of-input returns, once the backlog has genuinely been
  handed over: a read cut short by a rekey leaves the half-close for a later
  pass.
- The Windows half of wolfsshd does not answer with an EOF of its own. That
  latches eofTxd and the child's remaining output would be refused, which is
  the defect this series removes from the library.
- The mplabx port drains before tearing down, the way its SFTP read path
  already did; its worker arm was unreachable for a half-close until now.
2026-08-31 11:53:29 -05:00
John Safranek ad059d7d0e echoserver: only continue the rekey drain on an empty read
wolfSSH_ChannelIdRead() returns a negative value for a real error, and
the rekey arm treated that the same as a zero read. Restrict the
continue to cnt_r == 0 so an error still ends the loop.
2026-08-28 18:26:25 -05:00
John Safranek 59a1ef8eec echoserver: close the agent socket on reset and handle a rekey
The worker drops back to APP_STATE_LISTEN when an agent connection ends,
but never closes the socket. The next accept() overwrites agentFd, so
every agent connection after the first leaks the previous descriptor. The
forward path has the same gap on its connection-reset arm, where the
socket is closed but fwdFd keeps the closed number.

A rekey was treated as a read failure and ended the session. It cannot
just be skipped either: wolfSSH_worker() reports WS_REKEYING in place of
WS_CHAN_RXD while keying, and nothing raises the data report again, so
ignoring it strands whatever arrived in that call and the peer waits on
an answer that never comes. This is the hazard the library already calls
out for WS_EXTDATA, which is exempted from the same override.

- close agentFd and clear it on both the read-zero and the
  ECONNRESET/ECONNABORTED arms
- clear fwdFd on the forward reset arm, matching the read-zero arm
- clear agentCtx.appFd and fwdCtx.appFd wherever the worker closes the
  socket, so the stored copy cannot outlive the descriptor
- drain the channel on WS_REKEYING as well as WS_CHAN_RXD, and take an
  empty read as "nothing buffered" rather than a failure on that path.
  wolfSSH_ChannelIdRead() has no isKeying gate and the window credit it
  owes is parked until the rekey completes
2026-08-28 18:26:25 -05:00
John Safranek 26ccac3cdd examples, tests: terminate the ES_ERROR messages with a newline
ES_ERROR() passes its arguments straight to fprintf(stderr, ...), so a
message with no trailing newline runs into whatever the shell or the
test harness prints next.

- 7 sites in the echoserver, 6 in tests/auth.c, 8 in the Espressif copy
  of the echoserver, which carries the same macro and takes this kind of
  cross-cutting fix (e91ff50d, ab8058d7)
- the two wrapped format strings already ended with a newline
2026-08-28 18:22:34 -05:00
Yosuke Shimizu 8e8b62d358 examples/portfwd: drop the password from the options banner
- The startup banner's password line and its argument are dropped
  from the printf; ssh host, username and the two forward endpoints
  remain.
- userPassword has internal linkage, and portfwd_worker() zeroes it
  with wc_ForceZero() as soon as wolfSSH_connect() returns, on both
  the success and the failure path.
- portfwd.c includes wolfssl/wolfcrypt/memory.h.

Issue: F-11673
2026-08-26 09:12:27 -07:00
John Safranek 3b43e485dd Split SFTP confinement from the start path
- Add wolfSSH_SFTP_SetConfinePath and a separate sftpConfinePath, so
  the start path only says where a session begins, and only an
  explicit confinement root rejects out-of-tree requests.
- Have GetAndCleanPath take the WOLFSSH and enforce the confinement
  root, resolving relative requests against the start path.
- Factor the shared canonicalize-and-store work out of
  wolfSSH_SFTP_SetDefaultPath into CanonicalizePath and StorePath.
- Give the echoserver -D to opt a session into confinement; without
  it the -d home directory only says where SFTP starts.
- Document both settings, and the symlink and TOCTOU caveats, once in
  wolfsftp.h, noting the confinement root itself is trusted.
- Cover the split in api.c, and in regress.c start a session in a
  subdirectory of the confinement root: a sibling of the start
  directory is reachable, anything above the root is not.

Issue: ZD-22308
2026-08-20 09:56:48 -06:00
John Safranek 754317b6bf Use wolfssh-options in the test scripts
The scripts now read the build options from the probe instead of grepping
usage text, config.log and daemon logs. Drops the usage lines only tests read.
2026-08-19 16:55:00 -07:00
Yosuke Shimizu 497da8a130 examples: use the selected algorithm for the default public key
- sftpclient passes userEcc to ClientUsePubKey(), and scpclient to
  both ClientSetPrivateKey() and ClientUsePubKey(), in place of a
  hardcoded 0.
- scpclient gains a userEcc; it and client default it to 1 under
  WOLFSSH_NO_RSA, as sftpclient already did.
- ClientSetPrivateKey() and ClientUsePubKey() name the missing
  algorithm on stderr and return WS_NOT_COMPILED when the built-in
  key they select is compiled out.
- Both skip the built-in load entirely when neither RSA nor ECC is
  compiled in, clearing the key size and type and returning success
  so password-only authentication still runs. ClientUsePubKey()'s
  buffer pointer moves inside the guard so it is not left unused.

Issue: F-8829
2026-08-14 14:32:15 -07:00
Yosuke Shimizu e91ff50def Fix a one byte heap overflow in LoadTpmSshKey 2026-08-13 11:59:57 -07:00
Yosuke Shimizu 255dd926d9 Adopt the certificate loading APIs in the examples and apps 2026-08-12 16:06:39 -07:00
Emma Stensland 244c82c86d Add remaining ML-DSA composite signature algorithms 2026-08-10 14:51:37 -07:00
Emma Stensland 866f7392cd Add ML-DSA44-Ed25519 composite signature support 2026-08-10 14:51:37 -07:00
Ruby Martin ab8058d7cd check return value of WFTELL() 2026-08-10 10:19:45 -07:00
Ruby Martin 97bbe42263 Add WFSEEK return value checks where previously discarded 2026-08-10 10:19:45 -07:00
Ruby Martin 2a30f48772 Update WFSEEK call sites that may misinterpret return value in Harmony/Nucleus builds 2026-08-10 10:19:45 -07:00
John Safranek a62534936f client: handle select() errors in readPeer
- store select()'s result in an int so an error is not read
  as a large positive count
- retry on EINTR, bail out on any other error
- re-arm the descriptor sets on every pass

Issue: F-7508
2026-08-10 10:31:07 -06:00
John Safranek c61c7a04dd client: fix keyboard-interactive responses
- zero the response arrays and stop filling after the first
  failed read, tracking only the slots populated
- release the previous round's responses on each INFO_REQUEST
- add auth.test coverage for EOF stdin and a second round

Issue: F-7515, F-7516
2026-08-10 10:31:07 -06:00
John Safranek 0ac4a3d771 examples/client: drop dead select_ret tests in NonBlockSSH_connect
The loop condition already guarantees a want-read or want-write error,
so the select_ret arms of the retry test and the else chain could never
run. Retry unconditionally; tcp_select still throttles the loop. Same
change applied to the copy in apps/wolfssh/wolfssh.c.

Issue: CID-572884
2026-08-05 13:57:57 -05:00
John Safranek afb4d70c5f apps/wolfssh, examples/client: retry readInput sends over a rekey
Both loop exits in readInput() returned early, leaving the per-thread
ECC cache cleanup after the loop unreachable. Break out of the loop
instead.

wolfSSH_stream_send() reports a rekey in progress by returning
WS_FATAL_ERROR with the ssh error set to WS_REKEYING, so testing the
return value against WS_REKEYING never matches. Read the code with
wolfSSH_get_error() and resend the same buffer once the rekey
finishes, in both the app and the example client. Looping back to
read() instead would drop the input already taken from stdin.

Issue: CID-572833
2026-08-05 13:57:57 -05:00
John Safranek c2da5989ec scp: fix duplicate file header on send
- Gate header on new scpFileHeaderSent flag, not scpFileOffset==0
- A send callback returning 0 bytes first no longer re-sends header
- Skip zero-length SCP_SEND_FILE send to avoid empty CHANNEL_DATA
- Abort on a second consecutive 0-byte send callback return with file
  data outstanding; skipping the send would otherwise spin
  SCP_SEND_FILE -> SCP_TRANSFER with no socket I/O
- Reset offset/bufferedSz/flags in ScpSourceInit for connection reuse
- Document the WS_CallbackScpSend contract, including when a 0 return
  is valid, next to the typedef
- Add test_wolfSSH_SCP_SendZeroFirst regression (func_args scp_send hook)
- Zero func_args in kex.c/testsuite.c so the new scp_send field is not read uninitialized

Issue: ZD-22176
2026-08-04 12:20:49 -04:00
John Safranek 90e76fde5d examples/portfwd: always retry the buffered send
The send at the end of the loop only ran when select() reported a
readable descriptor, but appBuffer can hold data across iterations.
Mask appFd out of the read set while the buffer is full, and fall
through the select() timeout to the send.

Issue: F-7207
2026-08-02 22:10:24 -05:00
John Safranek 085ad6816c examples/client: fix public key buffer leak
- Track userPublicKey allocations with a userPublicKeyAlloc flag
- Free on that flag, not pubKeyName; -J with no -j leaked the cert
- Restore userPublicKeyBuf when a key load fails, not a stale pointer
- Tag load_der_file() allocations DYNTYPE_PRIVKEY to match the frees
- Free the CA cert in ClientLoadCA() with the heap it came from
- Pass the caller's heap into wolfSSH_TPM_InitKey() so the TPM public
  key is allocated from the pool ClientFreeBuffers() frees it with

Issue: F-7210, F-7211
2026-08-02 22:10:24 -05:00
John Safranek 0883b9b868 examples/echoserver: check key load errors
- Test wolfSSH_ReadKey_buffer() and wc_CertPemToDer() results and skip
  the entry; a malformed file gave PwMapNew() a NULL buf and an
  indeterminate length.
- NULL-check both WMALLOC() results, and guard the load_file() size so
  a missing file no longer reaches WMALLOC(0).
- Fall through to the loop's existing WFREE()/advance so both buffers
  are freed on every path.

Issue: F-7204
2026-08-02 22:10:24 -05:00
John Safranek 4be56bdcf7 examples/portfwd: fix partial-send handling in portfwd_worker
- wolfSSH_ChannelSend() can return fewer bytes than requested, but
  appBufferUsed was decremented without moving the unsent tail to the
  front of appBuffer, corrupting subsequent sends. Shift the remaining
  data down with WMEMMOVE() when the send is short.
- Skip the recv() when appBuffer is full. The length argument would be
  0, and a zero length recv() returns 0, which the loop read as the
  peer closing; the buffered data was dropped and the forward torn
  down.

Issue: F-7207
2026-08-02 22:10:24 -05:00
Emma Stensland 024a9a21a0 Fix memory-safety and error-handling edge cases 2026-07-29 14:35:14 -07:00
Mark Atwood 419d8f94b8 fix: check WMALLOC result in sftp rm/rename handlers 2026-07-28 13:29:08 -07:00
John Safranek 4caf4558f4 Fix echoserver remote-forward listener accept
- ssh_worker() cached fwdCtx.listenFd at entry, while still -1
- The listener is only made later, from WOLFSSH_FWD_REMOTE_SETUP
- FD_SET() and accept() then got a bogus fd, failing with EBADF,
  which broke the worker loop and dropped the session
- Read listenFd from the context at each use, guarded on validity
- agentListenFd has the same shape but is set up before ssh_worker()

Issue: ZD-21867
2026-07-23 15:54:30 -06:00
John Safranek 9382811225 Add client-side remote port forwarding
- Add wolfSSH_FwdRemoteSetup/Cancel to request tcpip-forward
- Add SendGlobalRequestFwd to frame the request per RFC 4254
- Add portfwd -r reverse mode, driven by the fwd and req-success cbs
- Wait for the peer's reply before -R reports ready, and take the
  bound port from it; with -f 0 that reply is the only source
- Accept -f 0 in reverse mode, where it asks the peer to pick the port
- Cancel names the port the peer bound, not the one requested, so a
  peer keyed on its listener's port can still find it
- Add a req-failure cb so a refused forward is reported, not waited on

Issue: ZD-21867
2026-07-23 15:54:30 -06:00
Yosuke Shimizu 5acbeaeaee Run threaded api-test SFTP/SCP tests on Windows 2026-07-22 17:29:27 -05:00
aidan garske f9fc298637 F-4105 - Write only bytes read to Windows stdout handle 2026-07-20 17:24:02 -05:00
aidan garske ca85658950 F-3446 - Bound default password copy to userPassword buffer size 2026-07-20 17:24:02 -05:00
aidan garske d18e282457 F-4583 - Remove vestigial zero-length read in echoserver global_req 2026-07-20 17:24:02 -05:00
Emma Stensland 1f674b0ec7 SSHD/Echoserver: Fix memory leaks and public-key lookup 2026-07-17 20:01:44 -05:00
John Safranek ac762368ac Fix keyboard-interactive without TERM
Keyboard-interactive was unusable in builds with
WOLFSSH_KEYBOARD_INTERACTIVE but no WOLFSSH_TERM, in two ways.

- ClientFreeBuffers declared 'entry' under TERM && KBI but used it
  under KBI alone, so KBI-without-TERM failed to compile; widen the
  declaration guard to match the use
- ClientUserAuth's keyboard-interactive branch is guarded on
  TERM && KBI, so without TERM a keyboard-interactive request fell
  past every branch and returned the initial WOLFSSH_USERAUTH_SUCCESS
  with no responses populated. There is no terminal to prompt on, so
  answer WOLFSSH_USERAUTH_FAILURE instead of claiming success.
2026-07-14 13:58:43 -07:00
Yosuke Shimizu 331b2c4c97 examples/client: add cert-name length bounds check in ParseRFC6187 2026-07-08 23:36:25 -07:00