Commit Graph

262 Commits (master)

Author SHA1 Message Date
John Safranek def95992f5 echoserver: copy only usernameSz in PwMapNew
The copy read one byte past the username; the terminator is written
explicitly after it.

Issue: F-13329
2026-11-03 16:02:53 -06:00
John Safranek bd15e4b1c5 echoserver: free the DecodedCert in FPKI userauth
wsUserAuth parses the peer certificate to print its FASC-N and UUID.
Free the DecodedCert before leaving that block, on success and on
failure alike.

Issue: F-13997
2026-11-03 16:02:53 -06:00
John Safranek 057a756f33 examples: compare auth secrets in constant time
The echoserver and tpmcertserver userauth callbacks compare the stored
digest or password with wolfSSH_ConstantCompare(). The echoserver
callback now wipes its digest buffer on every return.

Issue: F-8858, F-12630
2026-11-03 16:02:53 -06:00
John Safranek bfafd5c9ab echoserver: drop root before serving with -F
A root-run echoserver with -F now switches to an unprivileged user once
the listener is bound and the keys are loaded, before any peer is
served, so forwarded connects and binds no longer run as root. The new
-u option names the user; the default is nobody, with a warning. A
failed switch ends the server.

- Add DropPrivileges(): setgroups, setgid, setuid, then check root
  cannot be regained
- Build it only for the standalone POSIX echoserver with WOLFSSH_FWD,
  not for the tests that run echoserver_test() in-process
- Warn on stderr that -u is ignored in a build without it
- Document -u in the usage text and README.md

Issue: F-11935
2026-11-03 15:44:32 -06:00
John Safranek e7b7e8ccfe echoserver: limit direct-tcpip destinations
With -F a peer's direct-tcpip open is set up only for a loopback
destination. The new -L option replaces that default with a list of
allowed host or host:port entries. Any other open is refused, which
the library reports as administratively prohibited.

- Check the destination in the WOLFSSH_FWD_LOCAL_SETUP action
- Document -L in the usage text and README.md
- openssh-interop.test checks a non-loopback destination and one left
  out of -L are refused, and that host and host:port entries connect

Issue: F-11935
2026-11-03 15:44:32 -06:00
John Safranek ecb81b5b7f echoserver: forward ports only when given -F
The forwarding callback connects and binds with the server's own
credentials, so it is registered only with the new -F option. Without
it the library turns down every forwarding request.

- Bind a remote forward for localhost or 127.0.0.1 to the loopback
  address, not INADDR_ANY
- Refuse a remote forward to a privileged port or one over 65535
- Pass -F in fwd.test and fwd-bulk.test; document it in README.md
- openssh-interop.test checks a remote forward fails without -F, works
  with it, and is refused by the port check for port 80

Issue: F-11935
2026-11-03 15:44:32 -06:00
John Safranek ffbe58d4fe echoserver: fix agent forwarding and its cleanup
The forwarded agent socket now lives in a mkdtemp() directory, and the
session's end runs the agent cleanup, which the library never calls on
a server. The relay addresses the agent channel by its own id and
drains each agent reply in full.

- Set agentChannelId from ssh->agent once the agent channel is open
- Drop agent replies when no local agent client is connected
- Close the accepted agent client in WOLFSSH_AGENT_LOCAL_CLEANUP and
  remove the socket and its directory
- Print the socket path; openssh-interop.test forwards ssh-agent
  through the echoserver, lists a reply larger than the relay buffer,
  and checks the directory is private and removed

Issue: F-10553, F-11936, F-14396
2026-11-03 15:44:32 -06:00
John Safranek 48f36f753a Reject an out-of-range -p port
The -p option of wolfssh, client, wolfscp, wolfsftp and echoserver now
goes through a strtol check, so a value that is not wholly a number in
range exits with an error instead of wrapping into a different port.

- apps/wolfssh: ClientParsePort(), shared with ClientParseDestination(),
  which also rejects an empty "ssh://host:" port instead of defaulting
- examples: a ClientParsePort() in examples/client/common.c; port 0 is
  rejected in every build, not only with a main driver
- echoserver: accepts 0 to 65535, keeping 0 for the test harness
- add TestClientParsePort and an empty URI port case to regress.test

Issue: F-14763, F-14767, F-14768, F-12552
2026-10-28 13:10:29 -05:00
John Safranek 02f6e9c1c4 Build cleanly with a C++ compiler
C++ rejects three C idioms the tree uses: a conditional mixing an enum
constant with a byte, one mixing INADDR_ANY with a pointer, and a {0}
initializer for a struct. Cast the integer arms of the conditionals,
and clear the structs with WMEMSET instead.

- internal.c, regress.c: cast the ID_UNKNOWN arm to byte
- test.h: cast INADDR_ANY through size_t to const char*
- keygen.c, certman.c, echoserver.c, auth.c: replace {0} with WMEMSET
2026-10-22 21:30:18 -05:00
John Safranek 48bcd39c13 examples: half close and drain at shutdown
The client and the echoserver both sent their shutdown messages and then
spun wolfSSH_worker() a fixed number of times, so whether the peer's
close was seen at all depended on timing. Both now half close the
socket and read until the peer hangs up; the client sends a disconnect
first, the echoserver does not.

- test.h adds WSHUTDOWN(), a write-side shutdown() on POSIX and Windows,
  left undefined where the stack has no half close.
- test.h adds HalfCloseAndDrain(). It flushes a queued write, then reads
  the raw socket, since the library reads nothing after the session
  ends, and gives up ten seconds in or after 256 reads. MQX gets a stub.
- SendDisconnectAndDrain() sends MSG_DISCONNECT for up to ten seconds,
  then drains. It returns WS_WANT_READ for a peer that never hung up,
  WS_WANT_WRITE for a disconnect still queued, WS_SUCCESS for a reset.
- test.h adds WaitForChannelClose(), which runs the session until the
  peer's channel close, bounded at ten seconds, and returns at once on a
  session an error already ended. MQX gets one pass.
- client.c waits for the close, keeping a late exit status, and reports
  an unexpected shutdown result. It exits 0 on either give-up and fails
  on a broken wait or a disconnect it could not send.
- echoserver.c replaces its 10-attempt worker loop with the drain alone:
  the close exchange ended the session, and OpenSSH's client exits 255
  on a disconnect that lands before it is done.
2026-10-13 10:45:39 -05:00
John Safranek 8cad955043 echoserver: wait out a partial packet in SFTP
When select() reports the socket readable but only part of a packet
has arrived, wolfSSH_worker() returns WS_WANT_READ. sftp_worker() now
goes back to waiting on the socket instead of ending the session,
which cut off SFTP transfers whose packets arrived split across reads.
2026-09-30 22:49:25 -05:00
Yosuke Shimizu 6ed8e12166 examples/echoserver, scripts: hold what a send did not take
- WS_AppCtx stages each direction on its own: buffer, bufferIdx and
  bufferOff from appFd to the channel; fdBuffer, fdIdx and fdOff from
  the channel to appFd. thread_ctx_t drops channelBuffer and
  eofBuffer.
- app_drain_to_channel() advances bufferOff by what
  wolfSSH_ChannelIdSend() took, and holds the rest on WS_WANT_WRITE,
  WS_WINDOW_FULL, WS_REKEYING, WS_CHANNEL_NOT_CONF and WS_CHAN_RXD,
  putting back the ssh->error from before that send for all but
  WS_WANT_WRITE. A zero or over-reported send ends the session.
- app_pump_to_fd() reads the channel into fdBuffer and writes it to
  the pty or forward socket, both now nonblocking, keeping what would
  block.
- ssh_worker() leaves appFd out of the read set while buffer holds
  bytes, puts it in the write set while fdBuffer does, and runs both
  pumps on every pass.
- A pty read of zero ends the loop whatever errno holds.
- app_echo_pump() echoes the shell channel through shellCtx.buffer
  and then hands each new chunk to process_bytes(). It replaces the
  EOF drain along with eofOff and eofRead. A negative read ends the
  session.
- app_flush_fd() gives a connected forward up to APP_FLUSH_SECS
  without progress after the loop to take what fdBuffer still holds.
- The agent keeps blocking writes, through app_write_all(), which
  retries an interrupted send.
- A forward clears both of its buffers as it connects.
- fwd-bulk.test phase 3 logs in with a password and echoes 32 MB
  through ssh -L while the client keeps sending but stops reading for
  10 s. Phase 4 ends a forward while bytes are held for its target
  and checks the next forward's target gets only its own.

Issue: F-10544
2026-09-25 13:13:39 -07:00
John Safranek 07a16f6868 echoserver: check the id before the legacy claim
The legacy claim in ssh_worker() now takes the channel id as part of
its condition and marks the session connected only once the id is
stored, the same order wsShellStartCb() uses.

Issue: CID-656285
2026-09-23 17:12:05 -05:00
Emma Stensland 3bec025365 Fix ML-DSA composite logic from code review 2026-09-21 11:11:16 -07:00
John Safranek 7026839c11 echoserver: add Ed25519 sample public keys
The example server loads hansel's and gretel's Ed25519 public keys when
neither RSA nor ECDSA is compiled in, so public key auth works there
instead of falling back to passwords. That arm is the "#if" and the
userEcc choice the "#else", so no empty if/else is left behind.
2026-09-17 23:27:22 +02:00
John Safranek 3a3ad23de3 examples, tests: drop the WOLFSSH_NO_ECC guard
Nothing defines WOLFSSH_NO_ECC, so every "#ifndef WOLFSSH_NO_ECC" block
was always compiled and the sample ECC keys behind them reached their
"#error" with no curve available. Use WOLFSSH_NO_ECDSA, which
wolfssh/internal.h derives.

- echoserver builds load_key() and its two default host keys only when
  RSA or ECDSA is there; the Ed25519 host key still loads
- ECC_PATH moves inside that guard, its only reader
- peerEcc is now read only by load_key(), so mark it used
2026-09-17 23:27:22 +02:00
Yosuke Shimizu 1a195cb1ec examples, ide, tests: classify the worker's status at the call site
- ssh_worker() in both echoservers maps a WS_FATAL_ERROR return from
  wolfSSH_worker() to WS_WANT_READ or WS_WANT_WRITE at the call site,
  off a new local err, and keeps the result in rc.
- The terminal arm tests rc in place of re-reading wolfSSH_get_error():
  rc != WS_WANT_READ in examples/echoserver, and rc != WS_WANT_READ &&
  rc != WS_WANT_WRITE in the Espressif copy, whose empty WS_WANT_WRITE
  arm is removed.
- ssh_worker() in examples/echoserver sets wantWrite from
  wolfSSH_OutputPending() before it builds writeFds.
- The comment above rc = cnt_r in both echoservers names cnt_r and
  ssh->error as the values that are reused.
- scripts/sshclient.test and scripts/fwd-bulk.test name the client's
  missing non-blocking mode in the skip message where they echoed the
  macro name, and the comments above both skips are removed.
2026-09-17 10:26:52 -07:00
John Safranek 10d97e151f echoserver: wait to write in the SFTP loop
A send the socket refused leaves WS_WANT_WRITE, and the retry branch
continues past the only tcp_select() in the iteration, which watches
reads. Wait on write readiness there, so a peer that has stopped
reading costs a descriptor wait rather than a spin.

- a buffered send with a willing socket still goes straight around
- an error-ready or failed descriptor ends the loop
- an interrupted select() retries instead of ending the session
2026-09-16 09:55:24 -07:00
John Safranek 95369017da echoserver: retry a send the socket did not take
ssh_worker() reads a WS_WANT_WRITE from wolfSSH_worker() as a send the
socket has not taken yet: it waits for the socket to accept one and runs
the worker again, rather than leaving the loop. Application-driven mode
answers session requests here, and the peer waits on that reply, so a
blocked one ended a session the legacy accept() carried through.

- the worker runs on a writable socket as well as a readable one, or
  the owed send is never retried
- the ssh socket joins the select write set only while a want-write is
  outstanding
- a queued agent channel open joins the same wait, so the poll runs
  ahead of the write set
2026-09-15 09:31:16 -07:00
John Safranek ecb79ea4e3 echoserver: bind sftp and scp as the library does
The subsystem and exec callbacks pick out the same commands the accept
state machine does: sftp matched whole, by length and bytes, and scp on
the three-byte prefix ChannelCommandIsScp() takes.

- sftp with an embedded NUL is refused, rather than granted and then
  dropped by wolfSSH_SFTP_accept()
- a transfer is granted only on the head channel the accept APIs serve,
  so a request on a later channel is refused rather than answered
  success
2026-09-15 09:31:16 -07:00
John Safranek eef5346f7f echoserver: claim only a granted, usable session
ssh_worker() takes the session channel accept() established only when
the request was granted and there is somewhere to put the data. The
type and command stay set on a refusal, so they do not say what was
granted, and a refused request leaves nothing running.

- a shell build serves the channel through the pty, so with no shell
  started only echo mode can take it
- wsShellStartCb() closes the pty and reaps the child when the terminal
  setup fails, installs ChildSig() only once the session will run, and
  leaves ChildRunning alone when forkpty() fails
- the connection holds the shell's pid, so the worker loop's exit and
  an accept() whose reply failed end the child too
- the shell, exec and subsystem callbacks share SessionInUse(), so a
  second program start on the connection is refused
- the EOF drain answers a half-close only on a claimed session, rather
  than on whatever channel id 0 finds
2026-09-15 09:31:16 -07:00
John Safranek 971f2f01e8 echoserver: answer session requests in callbacks
With -A the echoserver drives its own channels: accept() returns at
userauth and the callbacks below start the shell, SFTP or SCP session.
Off by default. The two modes are exclusive, since the callbacks answer
the session requests the accept state machine otherwise answers itself.

- wsShellStartCb() forks the pty, so it is registered in either mode,
  and claims the channel only once there is a shell behind it; a second
  request is refused rather than forking over the running shell
- wsExecStartCb() takes an "scp " command as a transfer and any other
  command as a session, and is registered in either mode, since the
  legacy path has always started a shell for an exec request too
- wsSubsysStartCb() is registered only with -A, as accept() serves sftp
  itself, and guards a NULL command, which a truncated request leaves
  behind
- ssh_worker() drives the session through shellCtx.appFd, claims the
  channel itself when no callback did, and leaves an SFTP or SCP
  handoff through its cleanup so the pty master still closes
- open the agent channel from the select loop, since the peer's
  auth-agent-req lands after accept() has returned, and read the
  listener from the context each pass because it appears mid-loop
- resume a subsystem accept that returns a want, waiting on the socket
  between attempts rather than spinning
- close the accepted socket again, clear fwdFd on EOF or reset, and
  stay in the loop on WS_REKEYING, which the read arm already handles
- key ChildRunning's sig_atomic_t on WOLFSSH_SHELL, the only build
  with the SIGCHLD handler that writes it, so a target whose libc has
  no signal.h still compiles
- ask for echo mode in the keyboard-interactive test, which has no
  account on the host for the shell callback to fork a shell for
2026-09-15 09:31:16 -07:00
Yosuke Shimizu a14e6d0f3c ssh, apps, examples: take the worker's status from its return
- wolfSSH_OutputPending() moves from wolfssh/internal.h to
  wolfssh/ssh.h as a WOLFSSH_API taking a const WOLFSSH*, defined
  in src/ssh.c beside the other public calls.
- wolfSSH_worker() puts the send's code in the return in place of
  an event when the flush fails outright, and gates its flush on
  wolfSSH_OutputPending(). wolfssh/ssh.h states both.
- Both wolfsshd shell loops, both echoservers and
  ReceiveScpMessage() dispatch on wolfSSH_worker()'s return, and
  call wolfSSH_get_error() only to tell a transient failure from a
  terminal one. The POSIX wolfsshd loop sets wantWrite from
  wolfSSH_OutputPending().
- Five worker tests expect the send's code where they expected the
  event, and tests/testsuite.c calls wolfSSH_OutputPending().
2026-09-14 10:40:48 -07:00
Yosuke Shimizu 21deb724fe scp, test.h, apps, examples: wait to write and route on the return
- tcp_select_write() joins tcp_select(), with WS_SELECT_SEND_READY
  at the end of the enum. tcp_select() passes NULL for writefds and
  cannot wait on the write side.
- The Windows wolfsshd window-change drain and sftp_worker()'s
  handshake flush retry wait on it. The drain records a give-up in
  ret, and sftp_worker() breaks only on WS_SELECT_ERROR_READY.
- The echoserver, Espressif and both wolfsshd shell loops, and
  ReceiveScpMessage(), take the event from wolfSSH_worker()'s
  return when it carries one, in place of reading ssh->error alone.
  The two echoservers cover WS_CHAN_RXD, WS_REKEYING,
  WS_CHANNEL_CLOSED and WS_EOF; the wolfsshd loops and
  ReceiveScpMessage() cover the ones they have arms for.
- The POSIX wolfsshd loop takes an owed write into wantWrite before
  the event overwrites rc, and its WS_WANT_WRITE arm runs the
  channel drain below in place of skipping it.
- FlushQueuedSend() folds the receive's own statuses into
  WS_SUCCESS inside its loop and keeps flushing while
  wolfSSH_get_error() reports WS_WANT_WRITE within the deadline, in
  place of looping on the worker's return. It reports
  WS_WANT_WRITE when the deadline leaves the packet queued.
2026-09-14 10:40:48 -07:00
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
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 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
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 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
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
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
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 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 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
Yosuke Shimizu 5acbeaeaee Run threaded api-test SFTP/SCP tests on Windows 2026-07-22 17:29:27 -05:00