Commit Graph

3292 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 6b990c3b12 zephyr: raise the twister timeout to 400 seconds
The v3.4.0 leg runs the KBI scenario in 168 to 188 seconds under
QEMU, and one run hit the 200 second limit just short of finishing.
Double the limit on all three scenarios to leave room as tests grow.
2026-11-03 16:48:02 -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 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 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 908ccef52a wolfsshd: use the exported constant-time compare
auth.c called the library-internal ConstantCompare(), which does not
resolve when the library is built with --disable-inline. Call
wolfSSH_ConstantCompare() instead.

Issue: F-7514
2026-11-03 16:02:53 -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 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 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 00f3e74e1c Fail the cppcheck job on a finding
The cppcheck step pipes through tee, so its exit code was lost and the
job passed with findings. Run the step under bash, which sets pipefail,
and upload the results file only when the step fails.
2026-11-01 23:51:19 -06:00
John Safranek 1c456fc843 Fix the cppcheck 2.13 findings
Restructure the two sites that left a redundant check in one build
configuration, and suppress a warning that only appears in a
configuration cppcheck invents.

- wolfsshd.c: set the -h host key file in the else of the cert store
  conflict check instead of retesting ret.
- internal.c CompositeEccSign: run wc_ecc_sig_to_rs from an else shared
  by the small-stack allocation check and the plain stack arrays.
- wolfsftp.c: suppress constStatement on WWRITE; cppcheck's -DWWRITE
  configuration turns the arguments into a comma expression.
2026-11-01 23:51:19 -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 b7d232fc57 Update Mac Runner
The singlethreaded CI test was using macos-14 for reasons, instead of
using macos-latest. The test would always fail on macos-15, which was
latest at the time. Switch the test back to macos-latest.
2026-10-30 16:09:39 -05:00
John Safranek 960e63cba0 wolfsshd: refuse a config line that does not fit
ConfigLoad() reports a line too long for its buffer and fails the load.
A cut line used to take effect with whatever fitted, since what is left
of it is read as a line of its own and a leftover of two bytes or less
is skipped as blank. The buffer is 1024 bytes now, so a long path or
forced command still loads.

- one byte is read past a full buffer, so a line that ends exactly
  there, at a newline or the end of the file, is not taken for a cut one
- the size is WOLFSSHD_MAX_LINE_SIZE in configuration.h, shared with
  the test
- test_configuration covers a long line, lines that exactly fill the
  buffer, and a line cut to leave a blank-looking remainder

Issue: ZD-22623
2026-10-30 13:51:24 -05:00
John Safranek 0f4002bc4e wolfsshd: apply ForceCommand to SFTP and SCP
A ForceCommand other than a bare "internal-sftp" now denies the SFTP
subsystem, and any ForceCommand denies SCP, as sshd_config(5) describes
it: a forced command applies to shell, command, and subsystem execution.
The session dispatch checks the configured command alongside a
certificate's, so either one on its own confines the session.

- the subsystem arm and both SCP arms consult the configured
  ForceCommand, not only a certificate's force-command
- "internal-sftp" followed by options denies SFTP, since no option is
  applied, while the shell still refuses it as "internal-sftp"
- every site testing for "internal-sftp" now shares one predicate
- root's transfer refusal no longer points at ForceCommand internal-sftp,
  which does not permit SCP
- tests require a ForceCommand to deny SFTP and SCP, read the daemon's
  decisions from its log, and pair a configured internal-sftp with a cert

Issue: ZD-22623
Issue: F-10597
2026-10-30 13:51:24 -05:00
John Safranek d04bad3a6e wolfsshd: match an unknown user by name
An unresolved user took the global methods, so a name a Match User
block covers advertised a different list depending on whether the
account existed. DefaultUserAuthTypes() now composes the blocks that
select on the name alone, with no group list, when the account does
not exist.

- a Match Group block still cannot apply to a name with no account, so
  it fails closed to the unmatched methods
- any other failure to resolve the user, or to compose its blocks,
  advertises no methods rather than the global ones, so a Match block
  that disabled a method is never skipped
- test_configuration stubs the account lookup and covers a lookup
  error, an unreadable group list and a resolved account as well as the
  Match User and Match Group blocks

Issue: ZD-22617
2026-10-30 12:11:37 -05:00
John Safranek a7df973842 wolfsshd: hide account existence in userauth
DefaultUserAuthTypes() resolved the requested user before any
authentication, so a username with no local account produced an empty
"methods that can continue" name-list. It now falls back to the global
configuration's methods when the user does not resolve, so the
USERAUTH_FAILURE reply no longer says which accounts exist.

- an unresolved user still cannot authenticate, since the same failed
  lookup rejects every method it tries
- test_configuration covers the advertisement for an unknown user,
  comparing against the global mask so it tracks the configuration

Issue: ZD-22617
2026-10-30 12:11:37 -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 ad187fb7c4 wolfsshd: reject passwords containing a NUL byte
The password backends check the password as a C string, so "\0junk"
was checked as "" while its nonzero length passed the
PermitEmptyPasswords check. RequestAuthentication now refuses any
password with a NUL before the policy check and the backends.

- test_RequestAuth_embeddedNulPasswordRejected puts the NUL first,
  last and mid-password, and checks the fake crypt still runs

Issue: F-16096
2026-10-28 13:22:48 -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 d38e40bc8b wolfsshd: exec the login shell path whole
SHELL_Subsystem copied pw_shell into an 80-byte buffer before execv(),
so a longer path ran whatever its first 79 bytes named. The child now
passes pw_shell to execv() directly, and builds the login argv[0] from
its basename so a long path no longer falls back to "-sh".

- add sshd_long_shell_test.sh, which logs in as a throwaway user whose
  shell path is longer than 80 bytes and checks the shell's $0

Issue: F-14757
2026-10-28 13:10:29 -05:00
John Safranek 9ddb698e16 wolfsshd: refuse the PID file without O_NOFOLLOW
Where open() cannot refuse a final-component symlink, no pre-check can
stop a link swapped in before the open, and the fstat gate accepts a
root-owned target. wolfSSHD_ConfigSavePID now writes no PID file there
instead of relying on a racy lstat.

- test_ConfigSavePID gets a variant for that build that expects no file

Issue: F-14764
2026-10-28 13:10:29 -05:00
John Safranek 17ad8f5693 internal: wipe the exchange hash context
DoKexDhReply() and SendKexDhReply() wipe kexHash after wc_HashFree().
The last input to the exchange hash is mpint(K). The software hashes
already clear the context, but a hardware hash port can leave that
block in the handshake until NEWKEYS.

Issue: F-16110
2026-10-27 11:13:03 -05:00
John Safranek d010c134dd internal: wipe the GenerateKey hash context
GenerateKey() wipes its wc_HashAlg after wc_HashFree(). The software
hashes already clear it, but a hardware hash port can leave the last
input block, the derived key, in the stack context.

Issue: F-15670
2026-10-27 11:13:03 -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 63fc1fb605 wolfsshd: exit non-zero when startup fails
The POSIX StartSSHD() now returns EXIT_FAILURE when ret holds an error,
and main() passes that on. A config check (-t) of a bad config, a
failed host key load, or a bad -p now exit 1 instead of 0.

- set ret when the accept loop cannot allocate a connection, so that
  stop also exits non-zero
- PendingConnection() returns negative on a select() error other than
  EINTR, and the accept loop sets ret from it instead of quit
- log "Closing down wolfSSHD" whenever the accept loop exits, not
  only on quit, so the error exits still log it
- add sshd_exit_status_test.sh, which checks -D -t exit codes for a
  valid config and three startup failures

Issue: F-16053
2026-10-26 17:21:29 -07:00
John Safranek 8c3640db44 wolfsshd: keep accepting after a failed fork
StartSSHD() no longer stores the NewConnection() result in ret, the
accept loop's condition. A failed fork() or CreateThread() now drops
that one connection, and the daemon goes on listening.

- add sshd_fork_fail_test.sh with an LD_PRELOAD shim that fails the
  first connection's fork() with EAGAIN
- run it from run_all_sshd_tests.sh on leased port +7

Issue: F-16052
2026-10-26 17:21:29 -07: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 ba0a8c280e Add a C++ build workflow
Build wolfSSH with a C++ compiler as CC and run make check, so a change
that does not compile as C++ fails CI. wolfSSL is built as C.

- g++ and clang++ on Ubuntu, Apple clang++ on macOS
- default and --enable-all configurations, all with -Werror
- clang++ also gets -Wno-deprecated for its .c-as-C++ warning
2026-10-22 21:30:18 -05:00
John Safranek cedffb8eb3 ssh: gate the accept/connect log strings on WLOG
The four format strings are used only through WLOG(), so they now sit
under the same DEBUG_WOLFSSH/WOLFSSH_SSHD condition that compiles it in.
A namespace-scope const is internal in C++, and clang++ rejected the
unused arrays with -Wunused-const-variable under -Werror.
2026-10-22 21:30:18 -05:00
John Safranek 894aed55ed configure: honor a cached wolfCrypt_Init result
Run the wolfSSL link probe under AC_CACHE_CHECK with AC_CHECK_LIB's
cache name, so a preset ac_cv_lib_wolfssl_wolfCrypt_Init=yes skips the
link test. The mingw cert-store CI job relies on this, as it has no
cross-built wolfSSL to link against.
2026-10-22 21:30:18 -05:00