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
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
WOLFSSH_FWD_LOCAL_CLEANUP now fires, and what must not fire matters as
much as what must: the gate is the channel's own setup record, not its
type.
- a peer-opened direct-tcpip channel reports SETUP, CHANNEL_ID, then
CLEANUP when it closes
- a forwarding channel this side opened reports no cleanup, and a
session channel no forwarding action at all
- an open that fails after a successful setup still reports the cleanup,
and a setup that reported failure draws none
- freeing the session, or the channel with wolfSSH_ChannelFree(),
reports it, and the session free after a channel free does not
report it again
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
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
WOLFSSH_FWD_LOCAL_CLEANUP is part of the public WS_FwdCbAction contract
and the library never sent it, so an application's handler never ran and
every peer-opened forward leaked what its setup callback allocated.
ChannelDelete() now sends it, so a peer close, an open refused after the
setup ran, wolfSSH_ChannelFree(), and freeing the session all report it.
- record the setup in a new fwdSetupTxd bit on WOLFSSH_CHANNEL: a
locally opened forward gets no LOCAL_SETUP, so the channel type alone
cannot say whether the application holds anything
- clear the bit when the cleanup goes out, so a channel reports it once
- pass the channel's id in the port parameter, the way
WOLFSSH_FWD_CHANNEL_ID does, so an application with two forwards can
tell which one ended
- TestDirectTcpipFwdCbRejectsChannelId now counts three callback calls
scripts/fwd.test sends eight short lines, so nothing in the suite drives
a forwarding channel past its first window, and a forward that stalled
once the window needed crediting would go unnoticed.
- push a payload several windows long through a local direct-tcpip
forward and compare the bytes that arrive
- end a second transfer one window plus a short tail in, where the tail
is what portfwd still holds at end-of-input, and repeat it since the
window credit beating that tail is a race
- give the listening nc its stdin from a fifo a sleep holds open:
reading end-of-input makes nc close the connection, which truncates
the transfer and looks exactly like a stall
- bail out early once the byte count stops moving, so a real stall
reports in seconds
- dump the logs on failure, check the listening nc came up, and take
ports clear of the ones fwd.test.expect hardcodes, so a squatted or
shared port is not reported as a stall
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
wolfSSH_shutdown() flushes whatever a short send left in the output
buffer, not just a queued disconnect. A rejected auth's
USERAUTH_FAILURE has no channel, and DoChannelClose() retires the
channel as soon as the close is bundled, so neither had anything
left to carry the retry.
- The gate still refuses a flush once the peer has disconnected,
unless our own disconnect is the thing queued.
- A flush that finishes on a live session clears the WS_WANT_WRITE
the short send latched, as the disconnected path already did.
- ssh.h documents the widened flush and the WS_WANT_WRITE a short
one leaves wolfSSH_shutdown() returning.
- Tests cover the rejected auth with no channel and the close whose
channel DoChannelClose() already retired.
- A flush that short-sends on a live channel leaves the teardown
queued behind it. The retry adds no second EOF or close, and skips
the close wait while its own flush is still short.
- A teardown send that drained the leftovers settles the flush; one
that failed outranks it, since a reset leaves the buffer intact.
Only the shell hook had coverage, so nothing held DoChannelRequest() to
handing the command and session type to the exec and subsystem callbacks
or to answering with what they return.
- assert each callback sees the session type and the command string the
request carried, and the ctx set on the session
- assert an accepting callback draws CHANNEL_SUCCESS and a rejecting one
CHANNEL_FAILURE, whether it rejects with a WS_ error or a bare nonzero
wolfSSH_CTX_SetChannelCloseCb() had no caller in tests/, examples/ or
apps/, so nothing held DoChannelClose() to running the hook before it
retires the channel.
- assert the callback runs with the closing channel's id and the ctx
set on the session
- assert the channel is still on the list inside the callback and gone
by the time the caller is told
- a rejecting callback changes nothing: the return is discarded and the
close completes
The confirmation and failure hooks reach an application only through
DoChannelOpenConf() and DoChannelOpenFail(), and nothing in tests/ or
apps/ registered either, so both arms shipped unexercised.
- assert the confirm callback runs with the peer's channel id, window
and packet size already recorded
- assert the failure callback runs while the channel is still findable,
before DoChannelOpenFail() removes it
- count each hook separately, so a test can tell which one ran
- a rejecting confirm hook fails the receive and leaves the open
unfinished; a rejecting failure hook fails it and leaves the channel
on the list
- seed the unconfirmed channel through ChannelNew() and ChannelAppend(),
the state an outstanding open leaves behind
When a test is configured out, its entry function compiles down to
"return 77", the automake skip code, and the main() that forwards to it
inlines to the same two instructions. Apple's ld folds the two identical
bodies together and then writes LC_MAIN entryoff 0, so the binary starts
executing at the Mach-O header and dies with SIGILL instead of skipping.
tests/kex.test, tests/api.test and tests/auth.test all hit this under
--enable-all CPPFLAGS=-DWOLFSSH_TEST_BLOCK, and kex.test hits it again
under --disable-server.
Probe for -Wl,-no_deduplicate and add it to AM_LDFLAGS when the linker
takes it. GNU ld rejects the flag, so the check leaves Linux alone.
Making the skipped body differ does not help: anything added to the entry
function inlines into main() and the two fold again.
sshd_sftp_idle_cpu_test.sh measures the connection process it forked, so
it takes the wolfsshd present after the connection and not before. The
old symmetric difference offered a pid that left during the window just
as readily, and the smallest one wins, so an earlier test's departing
child was measured through a /proc entry that no longer existed.
- compare the pid sets one way, and poll for the fork rather than
sampling a fixed five seconds in
- let the handshake and SFTP setup finish before the baseline, so their
ticks land outside the measurement rather than inside it
- print both pid sets when no child is found, since the failure says
nothing about which pids were considered
RFC 4254 6.1 has a session open travelling client-to-server, so
DoChannelOpen refuses one on a client endpoint in the type switch,
ahead of ChannelNew and any channelOpenCb. OpenSSH and Dropbear
refuse it outright too, with no application opt-in.
- Answer OPEN_ADMINISTRATIVELY_PROHIBITED, as the forwarded-tcpip
direction gate below it does.
- Widen the client harness guard in regress.c so it builds without
WOLFSSH_FWD, and hoist AcceptChannelOpenCb out of the forwarding
block.
- Cover the refusal, and the gate outranking an accepting
channelOpenCb.
Issue: F-8836
TestClientOnlyKexMsgsBlocked now asserts that 31 and 33, the ids a
client does receive, stay allowed where the handshake expects them, so
widening the role check into a 30-34 range fails the suite. The
IsMessageAllowed() comment records the receive-only policy and the
per-KEX-method id namespace.
- assert id 33 is allowed once expectMsgId is MSGID_KEXDH_GEX_REPLY
- assert expectMsgId starts at MSGID_NONE, so the role check is what
rejects the blocked ids
- recheck all three blocked ids during a rekey on an established session
wolfSSH_CTX_SetSshProtoIdStr() now rejects a string that is not
CRLF-terminated, exceeds 255 bytes counting the terminator, or
carries a CR or LF in the body. DoKexInit() subtracts the two
terminator bytes from the length when hashing it, so an
unterminated string underflowed the hash length.
Issue: F-10571
Gates ML-DSA composites behind WOLFSSH_NO_MLDSA_COMPOSITES.
Deduplicates key handling and uses heap allocation for
composite buffers when compiling for small stacks.
FwdRemoteSettle() folds a port-0 forward the peer bound onto a
registration already standing at that port. A request still in its
send window resolved its forward before the send, so it names the
survivor from here on, the same as the queued replies do.
- Add FwdPendingRebind() and call it ahead of the unlink, so a cancel
whose send pumped in the answer commits naming a forward rather
than nothing.
- Regression test drives the port-0 answer in from the cancel's send
callback.
FwdPendingCommit() looks for a registration of the bind its entry names
before linking it. An answer pumped in from the send can settle a
port-pending forward onto that port, and the fold in FwdRemoteSettle()
runs before the committing entry joins the list.
- replies queued against the folded entry move to the one that stands
at the bind
- test drives that answer from the IO send callback; the bind takes two
registrations without the fold, and one cancel leaves one matching
A request looked its registration up again at commit, after the send had
run the application's send and highwater callbacks. A callback that
reenters the library sends behind the request that ran it, so the lookup
found what the callback did rather than what the request asked for: a
cancel from a first setup's send found nothing to take, and that setup's
commit then registered the forward the peer had just been told to drop.
- Requests in flight hang off ssh->fwdPendingHead for the length of
their send, so a reentrant request resolves to the forward the request
it interrupted is registering, not to nothing.
- FwdRemotePrepare() settles what the request names and holds it on the
pending; FwdPendingCommit() uses that instead of a fresh lookup, which
also drops the dedupe branch the double lookup needed.
- FwdRemoteUnlink() voids the pending pointers along with the queued
slots, so freeing a forward mid-send leaves no commit naming it, and
FwdPendingDiscard() goes through it rather than freeing raw.
- FwdRemoteMatch() reads the pending list under the rules the scan over
registered forwards uses: a setup mid-send is on the wire and can feed
channels; a cancel behind it or a refusal on its slot stops matching.
- Drop pend->bindAddr and pend->bindPort, which nothing reads now.
- Tests drive a cancel, a setup, an inbound forwarded-tcpip open and a
mid-send REQUEST_FAILURE from the IO send callback, the window the
highwater tests no longer reach.
Issue: ZD-22195
The match only ran once wolfSSH_FwdRemoteSetup() had registered
something, so a client that never called it had every forwarded-tcpip
open go unchecked -- the case RFC 4254 7.2 is clearest about, since a
session that asked for no forward has nothing an open could answer for.
- Refuse the open whenever it names no registration, empty list
included. wolfSSH_SetFwdRemoteMatch(WOLFSSH_FWD_MATCH_OFF) is the
opt-out for an application that keeps its own bind list.
- Drop fwdRemoteTracked, which nothing reads now.
- wolfSSH_global_request() cannot carry the bind address and port RFC
4254 7.1 puts after the want-reply boolean, so no request that
registers a forward can be framed outside wolfSSH_FwdRemoteSetup().
Issue: ZD-22195
- SFTP_SetFileAttributes() and SFTP_SetFileAttributesHandle() carry
out the size, ownership, permission and timestamp requests while ret
is WS_SUCCESS, bound the size with wResolveOffset() against
WOLFSSH_MAX_FILE_OFFSET, and set WS_UNIMPLEMENTED_E where the port
defines no wrapper.
- wolfSSH_SFTP_RecvSetSTAT() and wolfSSH_SFTP_RecvFSetSTAT() answer
WOLFSSH_FTP_UNSUPPORTED for WS_UNIMPLEMENTED_E; wolfSSH_SFTP_CHMOD()
sets the attribute flags to WOLFSSH_FILEATRB_PERM before sending.
- port.h adds WTRUNCATE, WFTRUNCATE, WCHOWN and WFCHOWN for the POSIX
port, and defines WSETTIME and WFSETTIME over the existing WUTIMES
and WFUTIMES helpers in place of their (0) definitions.
- SFTP_SetMode() guards on _WIN32_WCE in place of USE_WINDOWS_API, and
port.c adds WS_ChmodA(), which trims the SFTP leading root and calls
_wchmod(); it and the WCHMOD mapping to it are left out under
_WIN32_WCE, which keeps the _chmod mapping.
- tests/regress.c adds TestSftpSetStatAttributes(); tests/sftp.c adds
a chmod of a directory and installs SFTP_TEST_UMASK in place of the
sftpTestUmask static; the STATE_SET_ATR_SEND case in tests/api.c
sets atr.flags to WOLFSSH_FILEATRB_PERM.
Issue: F-11658
- The Windows local open in wolfSSH_SFTP_Get() passes OPEN_EXISTING
when the write offset is nonzero and CREATE_ALWAYS otherwise, and
drops FILE_APPEND_DATA from the desired access. A new
DWORD creationDisp replaces the block-scoped desiredAccess.
- That open reports INVALID_HANDLE_VALUE as WS_BAD_FILE_E and moves to
STATE_GET_CLEANUP; the OVERLAPPED offset is set from gOfst on every
open rather than only when resuming.
- STATE_GET_LOOKUP_OFFSET clears a saved offset when the remote size
STATE_GET_LSTAT stored in state->attrib is no larger than it, and
again when the local destination does not hold exactly that many
bytes. The destination stat overwrites state->attrib.
- tests/api.c adds test_wolfSSH_SFTP_GetResume() and its
sftpGetToCompletion() helper, six cases over the resume paths, built
where the hosted file wrappers are available.
- test_wolfSSH_SFTP_PutResume() and test_wolfSSH_SFTP_GetResume() drop
the WOLFSSH_ZEPHYR k_sleep() block their bodies exclude.
- .gitignore covers every wolfssh_*.tmp the api tests leave behind on
an aborted run, replacing the known_hosts-only entry.
Issue: F-12547
A client answers tcpip-forward and cancel-tcpip-forward with a failure,
RFC 4254 section 7.1, before the request body is parsed. DoPacket()
disconnects on a refused id this build implements and on any refused id
of 80 or higher, the range RFC 4252 section 6 names; a refused id below
80 is answered UNIMPLEMENTED, RFC 4253 section 11.4.
- MsgIdKnown() carries DoPacket()'s dispatch ids, build guards included
- answer a refused id off the dispatch, so one cannot reach a handler
if MsgIdKnown() falls behind
- skip the disconnect once the session is over, RFC 4253 section 11.1
forbids sending after one
- cover ids 53, 79, 200 and a channel open before user auth on one
keyed-server helper
- cover a client refusing both request names, with a reply asked for and
without, and a server still succeeding
Issue: #1047 (3), #1047 (7), F-10576, F-10581, F-12574
A port-0 reply naming a port another registration stands for merges the
two, since one bind gets one registration. The requests still queued
against the entry that goes named that same bind, so FwdReplyRebind()
hands them to the survivor. Left naming nothing, a cancel among them
settles no forward and the merged one keeps matching opens.
- FwdReplyRebind() repoints queued slots before the stale entry is
unlinked and FwdReplyVoid() clears them
- Test folds a port-0 reply onto a forward with a cancel already
queued, and checks the cancel takes the survivor down
A cancel refusal leaves the forward standing, since the peer keeps a
listener it would not drop. There is no listener to keep when the setup
was refused too, so FwdRemoteSettle() unwinds a registration nothing
establishes and nothing is still owed an answer on, rather than leaving
it unconfirmed and unmatchable until the session ends.
- A cancel refusal unlinks an unconfirmed forward with no setup queued
- Test refuses a want-reply setup and the cancel behind it, and checks
the registration is gone
The mid-send window closed to the highwater callback when the commit
moved ahead of it, and the tests written for that window went with it:
they now answer a settled slot and reach the ordinary path. The IO send
callback is where a peer's answer can still land while the sender owns
its slot, so drive the reply from there.
- FwdReplyFromSendCb() answers the request from inside the flush, with
a short-write and a fail-next mode for a send that loses the rest of
the request after the answer is in.
- Tests cover a parked success, a parked refusal, a parked port-0 reply,
a failed send giving the answered slot back, and an answer settling an
earlier request while a cancel naming the same bind is mid-send.
- The highwater tests stay, for the committed path they now cover; their
comments said they held an uncommitted slot.
The forward a "tcpip-forward" or "cancel-tcpip-forward" establishes was
committed after SendGlobalRequestFwd() returned, which is after the
post-send highwater callback had run. A request that callback sends goes
out behind this one but committed ahead of it, so the earlier request
had the last word and the client ended up on the opposite side of the
forward from the peer. A first setup whose callback cancels it left the
forward registered with no listener on the peer, and a cancel whose
callback re-establishes the forward unlinked it, refusing every open for
a listener the peer holds.
- Split the post-send highwater check off wolfSSH_SendPacket() as
SendPacketFlush(), for a sender with state to commit first.
- SendGlobalRequestFwd() takes the pending forward and settles it inside
the send window, then runs the check.
- Commit order is send order now, so the last request sent governs,
whichever call made it.
- FwdPendingCommit() still re-resolves the entry: the IO send callback
can reenter mid-flush, which no ordering fixes.
- Tests cover a reentrant cancel of a first setup, a reentrant setup
during a cancel, and an inbound forwarded-tcpip open pumped from the
callback.
Filling the reply queue sends enough to cross a lowered highwater mark.
The rekey that fires sends a KEXINIT the 256-byte mem buffer cannot hold.
- Disable the highwater; a bigger buffer only moves the cliff.
- Both harnesses, since the buffer is the same size on each.
The forwarded-tcpip check is a behaviour change on a shipped API, so an
application that trips over it needs a way out that isn't abandoning
wolfSSH_FwdRemoteSetup(). wolfSSH_SetFwdRemoteMatch() relaxes the check
for the session.
- STRICT is the default and keeps the bind-plus-port rule.
- PORT compares the port alone, for a peer that rewrites the bind
address it echoes back, which STRICT refuses every open from.
- OFF accepts any open, as wolfSSH did before the check existed.
- Tests cover each setting, and a refused one leaving the default in
place.
Only the peer gives a slot back, by answering, so a peer that never
answers a want-reply global request lets the queue grow for the life of
the session, and the queue scans run once per forward on every inbound
forwarded-tcpip open.
- WOLFSSH_MAX_FWD_REPLIES bounds the queue.
- Refuse before the request is framed: one whose slot was never queued
would mispair every later reply.
- Return WS_RESOURCE_E, not the WS_MEMORY_E that nothing failing to
allocate would have yielded.
- Test fills the queue, then covers the refusal, that a refused setup
registers nothing, and a slot coming back on an answer.
A slot named its forward only once its request committed, so the scans
could not see a request mid-send. A cancel the peer confirms while a
fresh setup for the same bind is still going out then found nothing
standing for the forward and unlinked it, leaving the peer with a
listener the client refuses every open for.
- Name a slot by its bind until it commits.
- FwdReplyNames() answers for both FwdReplyHasSetup() and
FwdReplyNewest().
- The bind is borrowed from the caller and dropped at commit, the
lifetime WOLFSSH_FWD_PENDING already assumes for it.
- A pointer to the entry would not do: the send runs callbacks that can
free and remake it, which is why the entry is re-resolved at commit.
- Test drives a confirmed cancel against a re-setup still in its send
window.
A signal was turned into a fatal error, and a refused send left its
packet counted in the output buffer. Both fixes are in
wolfSSH_SendPacket(), so they cover every sender.
- WS_CBIO_ERR_ISR fell through to WS_SOCKET_ERROR_E. Nothing went out
and the session is unharmed, so retry, as ReceiveData() already does.
- Callers that discard a packet on error, like the KEX and userauth
sends, were throwing away framed output the peer never refused.
- On WS_CBIO_ERR_GENERAL the buffer was shrunk with the packet still
counted in plainSz, so SendChannelData() flushed nothing and called it
a success. Clear it with the packet it described.
- Tests pin the retry from a forwarding sender and a plain global
request, and drive a channel send through a would-block and a refused
flush.