Commit Graph

528 Commits (master)

Author SHA1 Message Date
John Safranek b23fbe5970 ossh: drop the dead ID_UNKNOWN branch
GetOpenSshKey() leaves no keyId for a key-type name it does not know
and returns WS_UNIMPLEMENTED_E itself, so IdentifyOpenSshKey() never
sees ID_UNKNOWN after a failure and passes that result straight on.

- tests/unit.c adds test_IdentifyOpenSshKeyUnknownName() for the
  WS_UNIMPLEMENTED_E result on an unknown key-type name.

Issue: F-13330
2026-11-04 11:56:15 -06:00
John Safranek 4615513419 sftp: clear the LSTAT state on a buffer failure
wolfSSH_SFTP_LSTAT() moves on to the request ID check only once the
reply buffer is allocated, and clears its state when the allocation
fails, so the next STAT or LSTAT starts a new request.

- test LSTAT against a matched, misnumbered and unallocatable reply

Issue: F-17047
2026-11-03 22:28:30 -06:00
John Safranek a879502632 sftp: check the request ID on every client reply
SFTP_CheckReqId() matches each reply header against the outstanding
request ID, advances the ID once on a match, and sets
WS_SFTP_BAD_REQ_ID on a mismatch. Every client response handler calls
it before acting on the reply, including the STATUS-only ones.

- DoName and GetHandle set WS_SFTP_BAD_REQ_TYPE on an unexpected
  reply type and free their state
- SetSTAT, Close, Remove and RMDIR now check the reply ID
- correct the WS_SFTP_BAD_REQ_TYPE comment in error.h
- test each request against a matched, misnumbered and mistyped reply

Issue: F-10547, F-13909, F-10566
2026-11-03 22:28:30 -06:00
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 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 6ff3664602 Export a constant-time compare to applications
Userauth callbacks compare passwords and digests outside the library,
where the internal ConstantCompare() is hidden. Export a wrapper, and
add WS_CONSTANTCOMPARE(), which, like WS_FORCEZERO(), maps to
wolfCrypt's wc_ConstantCompare() when the wolfSSL build provides it
(v5.9.4 and later) and to the local loop otherwise.

- misc.c: build the local loop only as the fallback, with a volatile
  accumulator
- internal.c: compare MACs and the CBC check block with the macro
- ssh.h: point the userauth callback documentation at the export
- api.c: test match, mismatch, length and argument handling

Issue: F-7513, F-7514
2026-11-03 16:02:53 -06:00
John Safranek d43eb01a66 sftp: refuse dir REMOVE and replacing RENAME
SSH_FXP_REMOVE does not take directories and SSH_FXP_RENAME fails on an
existing newpath (draft-ietf-secsh-filexfer-02 6.5). The non-Windows
handlers check with an lstat first, oldpath before newpath, and REMOVE
unlinks. A regular file renames by linkat() and unlink(), which refuses
an existing name atomically; anything else checks then calls rename().

- configure: probe for linkat()
- port.h: WUNLINK on POSIX, WLINK as linkat() when configure finds it
- FatFs, Nucleus: only "/" and "/." stand in for the root
- regress: test both against files, directories, symlinks and a
  read-only directory

Issue: F-15535, F-15536
2026-11-02 13:40:06 -06:00
John Safranek eb3d3cedb8 sftp: bound reply lengths before client allocs
SFTP_GetHeader now takes a limit from its caller and refuses a declared
body over it, or one too short to hold the header, with WS_BUFFER_E.
Every client reply handler allocated a buffer of the peer-declared size
first, so one 9-byte header could make the client allocate 2 GiB.

- STATUS, ATTRS and HANDLE replies are held to WOLFSSH_MAX_SFTP_PACKET
- NAME keeps WOLFSSH_MAX_SFTP_NAME; DoName's own check moves into
  the call
- a READ reply may also be as large as the caller's buffer plus its
  length prefix
- SendWritePacket keeps an error already set instead of
  WS_SFTP_BAD_HEADER
- regress test drives all eleven client calls at and past the bound

Issue: F-10598, F-13430, F-14433
2026-11-01 23:29:33 -06:00
John Safranek dce29c4369 wolfssh: warn when a known host adds a key type
The known_hosts search skips an entry whose host matches but whose key
type differs, so a host pinned with one type that presents another was
reported as an unknown host. The check now remembers that the host is
known with another type and says so before offering to add the new one,
as OpenSSH does.

- note the first other-type entry's key type and line in the loop
- print a warning with that entry, the host and the new fingerprint
  before the add prompt; a later same-type entry still matches or
  rejects as before
- reject a server key type that is not printable ASCII before it is
  printed or stored
- regress.c: add TestKnownHostsOtherType

Issue: ZD-22634
2026-10-30 18:00:28 -05:00
John Safranek 4b05c4ee8e sftp: report missing and refused paths by errno
STAT, LSTAT, OPEN, REMOVE, RENAME and OPENDIR take the status for a
failed call from errno, as OpenSSH's sftp-server does: ENOENT and
ENOTDIR answer SSH_FX_NO_SUCH_FILE, EACCES and EPERM
SSH_FX_PERMISSION_DENIED, anything else SSH_FX_FAILURE. Only the
default POSIX file system sets errno; other ports keep their old status.

- OPENDIR on POSIX no longer reports every failure as NO_SUCH_FILE

Issue: F-16081
2026-10-29 14:42:52 -05:00
John Safranek 8cdfc3e82d sftp: publish an OPENDIR handle with its reply
RecvOpenDir() allocates the directory node, its name and the HANDLE
reply before linking the node into dirList, so an allocation failure
leaves no handle the peer was never told about. Both the POSIX and the
Windows branch.

Issue: F-10551
2026-10-29 14:42:52 -05:00
John Safranek eba60204c0 sftp: bound READDIR replies and report errors
RecvReadDir() puts at most WOLFSSH_MAX_SFTP_READDIR entries (default
100) in one NAME reply and keeps the handle's place, so a large
directory takes several requests. The readdir helpers return WS_EOF at
the end of the directory; any other failure, an allocation included,
answers SSH_FX_FAILURE instead of EOF or a short listing.

- free the entry list on every path
- the POSIX helper tells a readdir() error from the end by errno
- an entry whose full path does not fit is listed without attributes,
  and on Windows one whose name does not convert is skipped
- a SFTP_Name_readdir hook failure other than WS_MEMORY_E ends the
  listing; WS_NEXT_ERROR still marks the last entry
- WOLFSSH_MAX_SFTP_READDIR below 1 is a build error

Issue: F-10552, F-13429
2026-10-29 14:42:52 -05:00
John Safranek a602fc41b7 sftp: reply OP_UNSUPPORTED to unknown requests
wolfSSH_SFTP_read() answers a request type it has no handler for,
SSH_FXP_EXTENDED among them, with SSH_FX_OP_UNSUPPORTED rather than
the generic SSH_FX_FAILURE (draft-ietf-secsh-filexfer-02 Section 7).

Issue: F-8840
2026-10-29 14:42:52 -05:00
John Safranek d2530b33aa internal: reject '-' in proto ID softwareversion
RFC 4253 Section 4.2 bars the minus sign from softwareversion.
ValidateProtoId now scans the body up to the first space and rejects a
'-' there; the comments after the space may still carry one.

- document the rule on wolfSSH_CTX_SetSshProtoIdStr() in ssh.h
- unit vectors for a dash in softwareversion and in the comments

Issue: F-16082
2026-10-28 16:05:17 -05: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 fb019d5c9d internal: check ssh in SendChannelTerminalResize
It was the one channel sender that read ssh->defaultPeerChannelId
without a NULL check. It now returns WS_BAD_ARGUMENT for a NULL ssh and
logs entry and exit like its siblings.

- add TestTerminalResizeNullSsh to regress.test

Issue: F-14769
2026-10-28 13:10:29 -05:00
John Safranek 2147f888c9 internal: wipe packets dropped on error exits
DoReceive() wipes a packet that fails to decrypt, authenticate or pass
the length checks before it returns. Under CBC the wipe waits until
DiscardPacket() has run its MAC, then covers a fixed MAX_PACKET_SZ, so
it adds nothing that depends on the decrypted length.

- BundlePacket() wipes the aborted packet it rewinds over, for every
  sender
- tests: reject and discard paths check the packet is gone; the
  BundlePacket() failure test checks the output buffer tail

Issue: F-14437, F-14438
2026-10-27 11:13:03 -05:00
John Safranek 6a478ce312 Wipe consumed and compacted buffer plaintext
The channel read paths wipe the bytes they hand to the application, and
GrowBuffer() wipes the stale copy its in-place compaction leaves above
the new length. _UpdateChannelWindow() compacts through GrowBuffer()
instead of its own WMEMMOVE, so it gets the same wipe.

- _BufferTake() serves wolfSSH_stream_read(), _ChannelRead() and
  _ChannelReadExt()
- tests: short reads, a compacting read, a drained stderr chunk in the
  inline buffer, and GrowBuffer() compaction

Issue: F-11677, F-8848, F-13438, F-10593, F-16109
2026-10-27 11:13:03 -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 9281b03898 Hold algorithm-list names to RFC 4251 format
CheckAlgoList() skips unknown names, but the setters keep the caller's
string and SendKexInit() sends it verbatim. Every name in the list now
has to be at most 64 bytes of printable US-ASCII with no space and at
most one '@', not first or last, per RFC 4251 Section 6 and RFC 4250
Section 4.6, or the setter returns WS_INVALID_ALGO_ID.

- internal.h: add WOLFSSH_MAX_ALGO_NAME_SZ for the 64-byte cap
- api.c: cover the 64/65-byte bound, space, tab, DEL, high bytes, the
  at-sign count and an at-sign at either end
- ssh.h: state the name format in the setter contract

Issue: F-15529
2026-10-25 16:17:26 -05:00
John Safranek de0c7070f5 Reject a negative DH e or f
Classic DH e and f are mpints (RFC 4251 section 5), so a value with
the top bit set and no leading zero is negative. KeyAgreeDh_client()
and KeyAgreeDh_server() reject one before the range check, and the key
exchange ends with the usual KEY_EXCHANGE_FAILED disconnect.

- add test_KeyAgreeDh_rejectsNegativePeer() for both sides
- pad e in test_DhGexGroup16KeyAgree() the way SendKexDhInit() does

Issue: F-15532
2026-10-24 22:49:51 -05:00
John Safranek c9c4c1f2d2 Check the format name in host key blobs
The host key parsers compare the format identifier at the head of the
key blob (RFC 4253 section 6.6) against the expected name and return
WS_INVALID_ALGO_ID on a mismatch, as ParseECCPubKey() already does.
RSA blobs must say "ssh-rsa", Ed25519 "ssh-ed25519", and ML-DSA the
negotiated algorithm name.

- add GetPubKeyFormat() and use it in place of GetSkip()
- add the wolfSSH_TestParseMlDsaPubKey() hook
- test a mismatched name for each key type

Issue: F-15666
2026-10-24 22:49:51 -05:00
John Safranek ff0b5e2147 Enforce the RSA key size floor on host keys
ParseRSAPubKey() and ParseRSAPubKeyCert() run the server's host key
through CheckRsaKeyBits(), so a client rejects an RSA host key under
WOLFSSH_RSA_MIN_KEY_BITS the same way a server rejects a short user
auth key. The failure ends the key exchange in DoKexDhReply().

- move CheckRsaKeyBits() above the host key parsers
- ParseRSAPubKey() maps only the decode failure to WS_RSA_E, so a
  parse error or a short key keeps its own code
- test 1024 and 2047 bit host keys in test_ParseRSAPubKey()

Issue: F-14687
2026-10-24 22:49:51 -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 dff92f957e internal: skip guessed GEX requests without GEX
With WOLFSSH_NO_DH_GEX_SHA256 a guessed KEXDH_GEX_REQUEST was answered
with UNIMPLEMENTED and left the skip armed, so the server then dropped
the real KEXDH_INIT and stalled. DoPacket() now dispatches the message
in every build, and a stub DoKexDhGexRequest() skips only a guess.

- tests: a guessed GEX request through DoReceive is skipped silently
- tests: the GEX request skip case runs without GEX support
2026-10-22 16:48:50 -05:00
John Safranek ed2b8a6122 internal: compare KEX guesses to our first choice
RFC 4253 section 7 counts a first_kex_packet_follows guess as right
only when the client's and server's first KEX and host key algorithms
are the same. DoKexInit() now compares the client's guesses with the
server's own first choices, not only with the negotiated ids.

- tests: first choices differ but the guess is negotiated: skip
- tests: same first choices in longer lists: do not skip

Issue: F-15530
2026-10-22 16:48:50 -05:00
John Safranek d3fbc8676e internal: refuse a second session request
RFC 4254 sec 6.5 lets only one shell, exec or subsystem request succeed
per channel. DoChannelRequestSession() now refuses any request after
the first grant without asking a callback, so the granted session and
its command stand.

- a refusal before any grant leaves the channel with no command
- ssh.h documents the refusal; wolfsshd's own check is defence in depth
- regress.c sends a second exec, shell and subsystem after a granted
  exec, and a second exec over a generic-callback grant
- unit.c expects a shell after an exec to be refused and checks that a
  refused exec's command is scrubbed
2026-10-22 15:29:16 -05:00
John Safranek 8455528dba tests: allow a RealPath that never retries
On a loaded host the server's replies can already be waiting when the
non-blocking client first calls RealPath, so it finishes without a
WS_WANT_READ. The negotiation test now reports that run instead of
failing it, and still requires the name within the retry bound.
2026-10-20 10:38:39 -07:00
kaage edc91880b2 tests: disable automatic rekey in SFTP read error fixture 2026-10-20 09:18:06 -07:00
kaage dee608695d sftp: report local read failures during uploads 2026-10-20 09:18:06 -07:00
kaage 4190f9ea4f sftp: close remote upload handle after write failure 2026-10-20 09:18:06 -07:00
kaage b2a951b2fc sftp: close download handles on transfer failures 2026-10-20 09:18:06 -07:00
John Safranek 03a42ae011 tests: gate SCP symlink-reject on TEST_INTERNAL
test_wolfSSH_SCP_SendSymlinkReject() calls wsScpSendCallback(),
wIsSymlink(), and other WOLFSSH_LOCAL helpers. A shared build hides
those symbols, so linking api.c against libwolfssh without
WOLFSSH_TEST_INTERNAL fails. Gate the test like the recursive-entry
test; the autotools api.test still runs it.
2026-10-19 10:44:07 -07:00
John Safranek ad9fa515b3 tests: gate OsshCert, KI tests on TEST_INTERNAL
The OsshCert_* tests and test_wolfSSH_KeyboardInteractive_emptyPrompt()
call WOLFSSH_LOCAL functions, which the winvs DLL import library does
not export. Gate them on WOLFSSH_TEST_INTERNAL, like the SCP
recursive-entry test, so a DLL build with either feature still links.

- stub the OsshCert_* tests when WOLFSSH_TEST_INTERNAL is off
- gate the RSA and ECDSA certificate vectors, which only those tests use
- stub test_wolfSSH_KeyboardInteractive_emptyPrompt() the same way
2026-10-19 10:44:07 -07:00
John Safranek 1c61301873 tests: gate SCP recursive-entry on TEST_INTERNAL
test_wolfSSH_SCP_SendRecursiveEntry() drives wsScpSendCallback(), a
WOLFSSH_LOCAL symbol. The autotools api.test defines
WOLFSSH_TEST_INTERNAL and links the test library, so it still runs
there. The MSVC api-test project links the DLL import library, where
the symbol is not exported, so the test now compiles to its stub.

- fixes LNK2001 on wsScpSendCallback in the DLL Release build
2026-10-19 10:44:07 -07:00
Emma Stensland dc7789d12b tests: cover the ML-DSA host public key cache 2026-10-19 09:07:39 -07:00
Emma Stensland 3eca021b20 tests: cover the derived ML-DSA public key 2026-10-19 09:07:39 -07:00
Emma Stensland d9667164d4 mldsa: derive the public key for a private-only host key 2026-10-19 09:07:39 -07:00
John Safranek bfb94f6862 tests: move AppendBlob() to the shared helpers
The agent relay tests call AppendBlob() from the client-only block, but
it was defined inside the block that needs both client and server, so
--disable-server with the agent enabled failed to compile regress.c. It
now sits with the other shared Append helpers.
2026-10-16 13:37:01 -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 07432dd28a internal: gate keyboard-interactive by role
The client refuses the server-only INFO_RESPONSE at the filter and
answers an INFO_REQUEST only while a keyboard-interactive request of
its own is outstanding, from the request going out until the server's
SUCCESS or FAILURE. The server's second-round setup callback gets a
cleared keyboard struct rather than the freed response arrays.

- IsMessageAllowedClient() refuses MSGID_USERAUTH_INFO_RESPONSE in
  every state; DoUserAuthInfoResponse() refuses a client session too
- kbRequestSent: set by SendUserAuthRequest() once the request is
  delivered, cleared by the server's FAILURE or SUCCESS;
  DoUserAuthInfoRequest() disconnects without it or on a server
- DoUserAuthInfoResponse() zeroes the keyboard struct after freeing the
  response arrays; ssh.h says so for the setup callback
- regress: INFO_RESPONSE refused by filter and handler, an unsolicited
  INFO_REQUEST, exchanges closed by FAILURE and by SUCCESS, a blocked or
  highwater-failed send, and the server's second-round setup

Issue: F-7619, F-7629, F-13399
2026-10-16 10:51:28 -05:00
John Safranek 41fb47b205 scp: receive through a per-session dirfd
The default SCP receive callback keeps its destination per session, the
base path plus each directory entered, in place of the process working
directory that every session of a threaded server shares. With openat()
it holds that directory open, reaches it and everything created below
it with O_NOFOLLOW, and confirms a reopened parent is the one entered.

- port.h gains WOPENAT, WMKDIRAT, WOLFSSH_O_SEARCH with a search check
  as chdir() made, and WOLFSSH_HAVE_DIRFD, which sizes WOLFSSH so it
  keys only on configure's openat() probe; WOLFSSH_NO_DIRFD opts out
- ports without them open by the full path after a wIsDirNoFollow()
  screen of the base and of each directory entered; a refused base
  frees the receive path
- the receive path is capped at DEFAULT_SCP_FILE_NAME_SZ like the
  send side's; futimes() stands in for futimens() on hosts with
  openat() but not futimens(), such as macOS before 10.13
- unit: ScpRecvCallback_SessionPath walks down and back with the
  working directory untouched, then has the tree moved out from under
  it; a linked base and a file-named directory are refused
- scp.test: a planted symlink at the destination name keeps its target;
  scp-test.yml also builds and tests the fallback

Issue: F-14758, F-13998
2026-10-15 17:14:34 -05:00
John Safranek 8a0c7a91a5 tests: free the AES context on a GCM key failure
test_DoReceive_AeadTagFailure() marks decryptCipher initialized and
sets its cipher type as soon as wc_AesInit() succeeds, so a later
wc_AesGcmSetKey() failure still leaves wolfSSH_free() a context that
CipherClear() will free. Matches what CipherSetKey() does in the
library.

- give the key-set failure its own result code, so the split reports
  which of the two calls failed
2026-10-14 15:52:44 -05:00
John Safranek a77a2b2360 tests: cover PEM private keys without certs
test_wolfSSH_CTX_UsePrivateKey_buffer_pkcs8_pem() loads PKCS#8 PEM host
keys for ECDSA P-256, RSA and ML-DSA-65, plus an undecodable body that
must fail in the PEM decoder rather than be refused outright. The keys
are embedded, so the assertions do not depend on load_file(), the
working directory, or a filesystem.

- widen both PEM guards, gated on WOLFSSH_CERTS only because load_file()
  is, so neither ran in the build 722678df fixed
- spell the client-only macro NO_WOLFSSH_SERVER, not WOLFSSH_NO_SERVER,
  which is defined nowhere
- guard serverKeyEccCurveId, which the now-live guard leaves unused in a
  client-only build
2026-10-14 15:52:44 -05:00
Yosuke Shimizu 9f74313b7b wolfsftp: finish a running key exchange before the SFTP INIT
- wolfSSH_SFTP_connect() drives wolfSSH_worker() while isKeying at
  SFTP_BEGIN, returning any status other than WS_REKEYING,
  WS_CHAN_RXD, WS_EXTDATA or WS_EOF.
- TestSftpConnectPreFlushStartsRekey() retries with
  wolfSSH_SFTP_connect() alone on the client side, sends stderr from
  the server mid-exchange, and checks the server reads the INIT once.
2026-10-14 10:49:40 -07:00
Yosuke Shimizu e5987f5c34 internal: take channel data only after framing it
- SendChannelData() and SendChannelExtendedData() flush earlier
  output whenever wolfSSH_OutputPending(), return WS_REKEYING when
  that flush starts a key exchange, and map WS_WANT_WRITE to dataSz
  only for their own framed packet.
- WOLFSSH_BUFFER loses plainSz and its clears.
- ssh.h puts wolfSSH_extended_data_send() beside
  wolfSSH_stream_send() under one note on their return, and the
  wolfSSH_stream_send_eof() note names no other call.
- regress adds three pre-flush tests on the keyed duplex pair, with
  DuplexSendThenBlock(), renames TestFailedSendClearsPendingPlaintext()
  to TestFailedSendDiscardsQueuedPacket(), and lets
  IsPumpRetryable() accept WS_EXTDATA.
2026-10-14 10:49:40 -07:00
John Safranek 034964e052 internal: document the name-list truncation rule
GetNameListRaw() stops at the first empty element and drops the rest, as
OpenSSH's match_list() does. Record that at the function, along with the
two things that keep it safe for the library's own lists: the canned
defaults carry only the trailing comma AlgoListSz() strips, and a
caller's list is screened by CheckAlgoList() in the ssh.c setters.

- leave a pointer in regress.c rather than a second copy of the contract
2026-10-13 17:18:10 -05:00
John Safranek e91a63b4f4 internal: drop dead bounds check in DoChannelData
GetSize() fails with WS_BUFFER_E unless the size it reads fits in the
remaining buffer, so begin never exceeds len on its success path. Drop
the follow-up length test, note that at the call site, and record the
postcondition at GetSize() so the bound is documented where it is
enforced, not only where it is relied on.

- cover the invariant the note now carries: a dataSz that runs past the
  buffer, and one that wraps begin + dataSz back inside it, must both
  come back WS_BUFFER_E with the parse index left where it started
- drop GetSkip()'s matching recheck of the same bound
- note at GetUint32() the two properties the GetSize() bound rests on

Issue: F-247
2026-10-13 17:18:10 -05:00
John Safranek 0eb579f5d0 internal: skip guessed KEX packets on server only
Every supported KEX method opens with a client message, so only the
client can send a guessed packet (RFC 4253 section 7.1). DoKexInit()
sets ignoreNextKexMsg only on the server and logs a server's flag on
the client; DoKexDhReply() and DoKexDhGexGroup() no longer skip.

- tests: client KEXINIT with a wrong-guess follows flag does not skip
- tests: client KEXDH_REPLY and GEX_GROUP parse even with the flag set
- tests: the skip case helper drives only the server

Issue: F-10585
2026-10-13 12:31:30 -07:00
John Safranek 41c13f6d99 internal: store a decoded boolean as 0 or 1
GetBoolean() stores any nonzero wire byte as 1, per RFC 4251 section 5,
so callers and callbacks such as hasSignature see only 0 or 1.

- tests: add test_GetBoolean covering 0, 1 and other nonzero bytes

Issue: F-12573
2026-10-13 12:31:30 -07:00