Commit Graph

164 Commits (master)

Author SHA1 Message Date
John Safranek 30b1b6e9c1 client: mask control bytes in keyboard prompts
The example client prints the keyboard-interactive name, instruction
and prompts through ClientPrintSafe(), which prints a '.' in place of
each control character other than tab and newline, DEL, and each byte
0x80 and up, per RFC 4256 section 3.3 and RFC 4251 section 9.2.

- tests: auth case capturing the printed strings

Issue: F-15534
2026-11-03 16:24:06 -06:00
John Safranek b05fe9f4c9 Unbuffer stdin before a client password prompt
The password prompt reads stdin with fgets(), and a buffered stdin
keeps its own copy of the line in libc for the life of the process.
The clients make stdin unbuffered before any stdin I/O, so the typed
password lands only in the buffer ClientUserAuth() wipes.

- the wolfssh app, and the example client, sftp client and scp client
  whether or not -P was given, as keyboard-interactive reads stdin too
- ClientUnbufferStdin() acts once per process and reports a failure
- scripts/sshclient.test sends the session data right after the
  password, with no sleep

Issue: F-15669
2026-11-03 16:24:06 -06:00
John Safranek 9e206dd2f1 Wipe the client password once connect is done
ClientUserAuth() hands the library its static password buffer, and the
library sends from it after the callback returns. The clients now wipe
it, and the example's keyboard-interactive responses, as soon as
connect returns instead of holding them for the whole session.

- ClientClearPassword() in both common.c copies, called after connect
  in the wolfssh app, the example client and the sftp client
- the password arm wipes before each prompt and on failure, so a retry
  keeps no tail of a longer password
- the keyboard arm wipes its scratch copy after each response
- tests: regress and auth cases for the retry tail, the wipe and a
  failed read

Issue: F-8846
2026-11-03 16:24:06 -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 7c6f6fcb5e internal: gate channel requests and opens by role
A client refuses shell, exec, subsystem and pty-req channel requests
and direct-tcpip opens before any callback runs (RFC 4254 6.2, 6.5,
7.2). CHANNEL_OPEN_CONFIRMATION and CHANNEL_OPEN_FAILURE are a
protocol error unless the channel's own open is still outstanding.

- SshInit() installs the default WS_TermResize for servers only.
- Drop the Windows clients' raw-HANDLE resize context; nothing uses it.
- Move the channel request builders out of the server-only test block.

Issue: F-10582, F-13923, F-15533, F-15572, F-14434
2026-10-27 10:49:29 -05:00
John Safranek dbcaa75f49 apps, examples: join readInput() on Windows
The Windows path of apps/wolfssh and examples/client sets the volatile
quit with InterlockedOr8() once readPeer() returns, repeats
CancelSynchronousIo() on the readInput() thread until it has exited,
and only then closes the handles and frees the session. readInput()
checks quit around its console read and inside its send retries, and
returns quietly when it is set.

Issue: F-11662
2026-10-16 11:54:31 -05:00
John Safranek 00b79a4d12 apps, examples: wipe the client thread buffers
readInput() and readPeer() in apps/wolfssh and examples/client clear
their stack buffer with wc_ForceZero() before returning, so neither
what was typed into the session nor the last of its output is left on
a finished thread's stack. On POSIX the main thread cancels
readInput() inside read(), so a pthread cleanup handler does the wipe
for it.

- readInput() holds only the buffer and the cleanup handler; the loop
  is readInputLoop(), since glibc's pthread_cleanup_push() is a setjmp
  and gcc -Wclobbered flags the locals of the function holding it.

Issue: F-10594, F-12647
2026-10-16 11:54:31 -05:00
John Safranek cf5df4a187 apps, examples: finish short sends and writes
readInput() in apps/wolfssh and examples/client sends again from
where a short wolfSSH_stream_send() stopped until the whole chunk is
taken, and waits out WS_WINDOW_FULL the way it waits out a rekey.
readPeer() in both writes stdout and stderr through a WriteAll() that
loops over short writes.

- A send clamped to the peer's window or packet size no longer drops
  the rest of the chunk, and the WS_WINDOW_FULL that follows the clamp
  no longer ends the stdin thread.
- The Windows stderr path writes the bytes read instead of a "%s".

Issue: F-8830, F-10548, F-10555
2026-10-16 11:54:31 -05:00
John Safranek fac8b97ef5 agent: exchange whole messages when signing
wolfSSH_AGENT_SignRequest() writes the sign request and reads the reply
through the whole-message helpers the relay uses, so a short write is
followed up and a reply is read to its declared length before it is
parsed. The setup callback's WS_AgentCbError result becomes
WS_AGENT_CXN_FAIL, and an ECDSA key's reply room fits the SSH blob.

- A short write, a hang-up, or a failed setup is WS_AGENT_CXN_FAIL;
  cleanup runs only after a setup that succeeded, and the client
  callbacks close their socket when connect() fails.
- PrepareUserAuthRequestEcc() reserves r and s as mpints a pad byte over
  the curve size, and BuildUserAuthRequestEcc() hands the agent that
  room, checks the reply's algorithm name, and bounds it by outputSz.
- A NULL sig, or a NULL digest or key blob with a size, is
  WS_BAD_ARGUMENT; agent.h documents the contract and return codes.
- api: the mock agent's short write is followed up, a reply read a byte
  at a time is reassembled, and a failed setup and NULL buffers are
  refused.
- regress: the mock agents answer as a stream; ECDSA user auth for each
  curve at, under, and over the largest blob, and a wrong curve name
  refused; a failed setup refused with nothing sent.

Issue: F-13325, F-14629, F-14631
2026-10-16 11:15:32 -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
Yosuke Shimizu d05cbd691d apps/wolfssh, examples/client: resend stdin a send did not take
- readInput() in apps/wolfssh runs FlushQueuedSend() when
  wolfSSH_stream_send() returns WS_WANT_WRITE without taking the
  buffer, then sends the same buffer again; a failed flush still
  ends in "Couldn't send data".
- readInput() in examples/client retries WS_WANT_WRITE as it
  retries WS_REKEYING, for up to SEND_RETRY_TIMEOUT (10) seconds
  per buffer.
- The comment on the queued check in apps/wolfssh describes the
  positive-return case.
2026-10-08 11:28:07 -07:00
John Safranek 96fdbe5238 apps, examples: keep readPeer up on a held reply
A held status from wolfSSH_AGENT_RelayChannel() is negative, and
leaving it in ret ended the outer while (ret >= 0) loop before the owed
reply was retried. Reset ret to WS_SUCCESS when the reply is held so
readPeer() returns to select() and retries it.

Issue: F-10545
2026-09-24 17:24:14 -05:00
Yosuke Shimizu a384999f61 agent, apps, examples: relay whole agent messages
- src/agent.c gains AgentWriteAll(), AgentReadFull(),
  AgentReadMessage() and AgentWriteMessage(), which loop the
  agentIoCb, bound a declared length by the new
  WOLFSSH_AGENT_MAX_MSG_SZ, and reconnect once when a write finds
  the socket dead. wolfSSH_AGENT_Relay() is rebuilt on them.
- New WOLFSSH_API wolfSSH_AGENT_RelayChannel() frames the channel
  side, holding a partial request and an unfinished reply on
  WOLFSSH_AGENT_CTX in two WOLFSSH_BUFFERs whose whole allocation
  is zeroed as each message is consumed. It answers WS_WANT_WRITE,
  WS_WINDOW_FULL or WS_REKEYING while a reply is owed, flushes
  what is left queued below under those last two, and answers
  WS_WANT_WRITE while any of it is still queued.
- readPeer() in apps/wolfssh/wolfssh.c and examples/client/client.c
  calls it, drops its local ato32() and 512-byte buffer, and arms a
  select() write set for the transport's own want-write. While a
  reply is owed it ends the read loop so the retry above select()
  runs, and examples/client/client.c times its select() out at one
  second.
- tests/api.c adds nine wolfSSH_AGENT_Relay() cases and
  tests/regress.c twenty-two for wolfSSH_AGENT_RelayChannel().

Issue: F-10545
2026-09-24 17:24:14 -05:00
Emma Stensland 3bec025365 Fix ML-DSA composite logic from code review 2026-09-21 11:11:16 -07:00
John Safranek dd78bb6146 examples, keys: add an Ed25519 user credential
The sample clients load hansel's Ed25519 key when neither RSA nor ECDSA
is compiled in, so public key auth reaches the echoserver's matching
sample keys instead of sending a request carrying no key.

- commit keys/{hansel,gretel}-key-ed25519.{der,pem,pub}, the pair to the
  echoserver's sample authorized keys
- ClientUserAuth() fails a publickey request with no key loaded, so the
  library can offer another method
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 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
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 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 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 255dd926d9 Adopt the certificate loading APIs in the examples and apps 2026-08-12 16:06:39 -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 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
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
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
Yosuke Shimizu c8a347de85 Reject symlinks in default SCP send callback 2026-06-23 14:37:57 -07:00
Yosuke Shimizu 12d39dd970 examples/client: guard rxBuf indexing against negative stream_read error 2026-06-09 09:34:14 -07:00
John Safranek 1aac6d2f5a ClientFreeBuffers: zero secrets before free
- Zero private key and password buffers in both apps/wolfssh
  and examples client.
- Also zero keyboard-interactive response buffers in the
  example client.

Issue: F-249
2026-05-21 11:42:43 -07:00
John Safranek 2f1231d185 macOS Semaphore Cleanup
macOS uses GCD for threading and semaphores, but they aren't quite like
POSIX semaphores. macOS allows the use of named POSIX semaphores.

1. Convert the semaphores to named POSIX semaphores.
2. Simplify all calls for semaphores into single function calls of the
   wrapper API.
3. Update both examples/client/client.c and apps/wolfssh/wolfssh.c.
4. Update both to deregister the WINCH signal.
2026-03-17 19:26:32 -07:00
John Safranek f85ec920e3 Client rsa-sha2-512
1. Separate the key type and signature type when handling user
   authentication. ssh-rsa is a key type used with rsa-sha2-256 and
   rsa-sha2-512 signatures, along with ssh-rsa signatures.
2. Allow rsa-sha2-512 to be used.
3. Fix bug where setting the key algorithm on the client command line
   was setting the list to NULL.
4. Fix bug where adding rsa-sha2-512 to the list of canned algorithms
   was terminating the list, rather that adding it.

Issue: ZD-21247
2026-03-09 10:56:37 -07:00
aidan garske 09144d0c7a Add more wolfSSH CI workflows 2026-03-03 21:22:39 -08:00
John Safranek 8951bd9209 Release v1.4.22
1. Update copyright date to 2026.
2025-12-29 16:53:33 -08:00
JacobBarthelmeh 4862400a37 fix spelling issues and SFTP send state 2025-10-06 00:42:05 -06:00
JacobBarthelmeh af45bc3719 update example client for rekey and sanity check on window update after read attempt 2025-08-26 10:50:14 -06:00
John Safranek 903bbc7fb2 Coverity: Argument cannot be negative
1. Due to not checking the result of fseek(), it is possible to try to
   malloc() -1 bytes of storage. Checking the return from fseek() and
   erring if negative.
2. Changing the check between the result of fseek() and fread() to match
   signedness. Adding some casting, as at that point the fseek() result
   is always positive.

Fixes CIDs:
  573009 572928 572868
2025-07-21 09:36:35 -07:00
John Safranek 0f650789de Soft Disable AES-CBC
1. By default, soft disable AES-CBC. It isn't offered as a default
   encrypt algorithm, but may be set at runtime.
2. Add guard where AES-CBC can be added back as a default.
3. Add option to example client to run it with a custom encrypt
   algorithm list.
4. In the client, add macro to add items to the arg lists while checking
   the number of items in the list.
2025-05-16 11:41:52 -05:00
Andrew Hutchings c368efb8b2 Make Keyboard Interactive a compile time option
Now an off-by-default compile-time option. This saves resources and
confusion if you are not expecting it.

ZD #19704
2025-04-22 13:12:42 +01:00
aidan garske a52c3b120e Used W port.h file operations for keyblob and added username argument so can append any username needed. 2025-04-02 10:33:27 -07:00
aidan garske 77c8db6cc2 Rebase for tpm public key authentication with wolfssh 2025-04-01 16:44:55 -07:00
Andrew Hutchings a4733ecd95 RFC-4256 Keyboard-Interactive authentication
This implements Keyboard-Interactive authentication.

Adds an additional callback set by `wolfSSH_KeyboarAuthPrompts()` which
will set a callback in the server to ask the application to provide the
prompt details for the client.
2025-02-11 14:19:52 +00:00