Commit Graph

3072 Commits (dc03049a13738d8cf8de55d1a30e5e462e9a3166)

Author SHA1 Message Date
John Safranek f494688e3a wolfsshd: let QNX own the host key's permissions
QNX system images fix the host key's owner and modes, and the daemon cannot
change either, so the secure gate refuses to load a key the integrator has no
way to correct. Add a hand-defined WOLFSSH_NO_HOSTKEY_PERMS, further
conditional on QNX, that hands only that policy to the platform.

- Fold the macro and the QNX test into the internal
  WOLFSSHD_HOSTKEY_RELAX_PERMS in wolfsshd.c.
- Add a relaxPerms argument to wolfSSHD_OpenSecureFile() that skips the owner,
  mode and ancestor-directory checks.
- Keep the structural checks: lstat, O_NOFOLLOW, S_ISREG and the dev/ino
  recheck, so a symlink, a non-regular file or a swap during the open is still
  refused.
- Set it only on the host key load, leaving the host cert, UserCAKeysFile,
  authorized_keys and shadow gates unchanged.
- Log at startup when the guard is built in.
- Add six test_OpenSecureFile scenarios for the relaxed path, unreachable at
  runtime off QNX and so otherwise uncovered.

Issue: ZD-22308
2026-08-25 14:33:52 -06:00
John Safranek 44bd4a06f4 Compose sshd_config Match blocks per keyword
wolfSSHD_GetUserConf returned the first matching Match block whole, so a
keyword named only in a later matching block was dropped and the outcome
depended on the order the blocks were written.

- track in a new setMask which keywords a node set itself, so a value
  inherited from the globals can be told from one the block named, with a
  compile time check that no option tag shifts out of the mask
- resolve into a fresh config seeded from the globals, letting every
  matching block contribute the keywords no earlier block claimed
- the resolved config now belongs to the caller, so wolfsshd and the auth
  paths free it and the tests compare values rather than node identity
- put sshd_match_overlap_test.sh back in the suite

Issue: ZD-22324
2026-08-25 14:30:39 -06:00
John Safranek d9f596b8c5 Test overlapping sshd_config Match blocks
OpenSSH resolves sshd_config one keyword at a time, scanning every Match
block that applies. wolfSSHD_GetUserConf returns the first matching block
whole, so a setting made only in a later matching block is dropped.

- add test_GetUserConfMatchOverlapCompose, covering a user matched by both
  a Match User and a Match Group block, in either order
- add sshd_match_overlap_test.sh, the same case against a live daemon,
  where the group block's ForceCommand is the setting that goes missing
- both fail until per keyword composition lands, so the unit test runs
  last and the script stays commented out of run_all_sshd_tests.sh
2026-08-25 14:30:39 -06:00
Yosuke Shimizu ad28e21221 wolfsshd: enforce shadow password and account aging
- IsShadowExpired() in auth.c returns 1 when a shadow entry's
  sp_expire date has arrived, its sp_lstchg is 0, or the day is at or
  past sp_lstchg + sp_max. Negative fields leave the matching check
  off; a negative day count, standing for an unavailable clock,
  denies the entries that carry aging. WSSHD_SECS_PER_DAY converts
  WTIME() into the unit those fields use. The helper is compiled
  under HAVE_SHADOW and !WOLFSSH_USE_PAM, as its caller is.
- CheckPasswordUnix() runs the shadow entry it looked up through the
  helper and, after an otherwise successful hash compare, logs the
  denial and returns WSSHD_AUTH_FAILURE.
- auth.h declares IsShadowExpired() for the unit test build.
- test_configuration.c adds test_IsShadowExpired() over a table of
  aging fields and day counts, and test_CheckPasswordUnix_expired()
  for the denial of a correct password.
- The CheckPasswordUnix() tests share one driver,
  wsshd_test_CheckPasswordUnixCase(), with the crypt() setup in
  wsshd_test_LoadShadowHash() and the three fail-closed shadow
  lookups gathered into test_CheckPasswordUnix_failClosed().

Issue: F-10577
2026-08-25 10:42:15 -07:00
Yosuke Shimizu 3aefadc102 internal: gate public-key auth on every compiled signing algorithm
- wolfssh/internal.h derives WOLFSSH_NO_PUBKEY_AUTH when RSA, ECDSA,
  Ed25519, and ML-DSA are all disabled.
- DoUserAuthRequestPublicKey(), the publickey dispatch in
  DoUserAuthRequest(), the ID_USERAUTH_PUBLICKEY case in
  DoUserAuthFailure(), Prepare/BuildUserAuthRequestPublicKey(), and
  GetAllowedAuth() are all guarded by that macro.
- The DoUserAuthFailure() and GetAllowedAuth() guards previously
  omitted Ed25519 and ML-DSA; the DoUserAuthFailure() guard also
  carried a WOLFSSH_TPM term, which is dropped.

Issue: F-10542
2026-08-25 10:40:41 -07:00
Yosuke Shimizu 6c2bbccc77 wolfsftp: report a failed remote write from SFTP put
- STATE_PUT_WRITE logs, sets ret to WS_FATAL_ERROR, clears
  state->handleSz and moves to STATE_PUT_CLOSE_LOCAL when
  wolfSSH_SFTP_SendWritePacket() returns a non-positive size and
  NoticeError() is false.
- ssh->error takes that return value, or WS_FATAL_ERROR for a size of
  zero, when ssh->error is still WS_SUCCESS.
- The write loop is followed by a continue when ret is not
  WS_SUCCESS.
- tests/unit.c gains WOLFSSH_TEST_SFTP_PUT, the SftpBuildReply()
  helper, and test_SftpClientPutWriteStatusFail(), which drives
  wolfSSH_SFTP_Put() over a staged handle reply and a write answered
  by an FXP_STATUS failure.

Issue: F-10543
2026-08-25 10:35:52 -07:00
JacobBarthelmeh 9777bc5ce8 add Windows sanity close of token before acquiring a new one 2026-08-24 14:49:09 -07:00
JacobBarthelmeh eb3fd6bcf0 add clean up in failure case and use WFREE instead of XFREE 2026-08-24 14:49:09 -07:00
JacobBarthelmeh b6bd975ccf Fixes for Windows wolfSSHd, f-8853 and f-8819 2026-08-24 14:49:09 -07:00
Paul Adelsbach 581053bcf6 CI: add code coverage workflow, misc script updates 2026-08-21 11:30:40 -07:00
John Safranek 17461f2271 Derive the expected ECC rejection in the priv-only key test
wolfSSL master now rejects a private scalar outside [1, n-1] when importing
one, so the zeroed key that test_IdentifyAsn1Key_EccPrivOnlyDerFailure builds
fails in wc_EccPrivateKeyDecode instead of reaching the wc_ecc_make_pub
fallback in IdentifyAsn1Key. The identify call then reports the key as
unidentified, WS_UNIMPLEMENTED_E, rather than WS_CRYPTO_FAILED, and the test
failed against any wolfSSL built from master.

- decode the corrupted DER first and expect the rejection the linked wolfSSL
  performs, so the assertion stays strict on either library
2026-08-21 10:25:16 -06:00
John Safranek e7c8dc2c2c tests: guard stop_wolfsshd so it cannot fail its caller
stop_wolfsshd killed $PID unconditionally. With the daemon already gone the
kill failed, and under "set -e" that aborted the caller -- in
sshd_forcedcmd_test.sh before PID was cleared, so the ForceCommand-SFTP
scenario was silently skipped, the EXIT trap killed the dead pid a second
time, and the script exited 1.

- Guard on a non-empty PID, ignore a failed kill and return 0, so the
  function is safe to call from an EXIT trap.
- Clear PID after stopping, so a second call cannot kill a recycled pid.
- Remove the temp key dir even when no daemon was recorded, so a daemon that
  failed to start does not leak it.
- Collapse sshd_forcedcmd_test.sh's cleanup() wrapper to a bare
  trap stop_wolfsshd EXIT now that the function guards itself.
- Check the cd back to the test directory in sshd_x509_upn_fail.sh; the log
  it counts after the client run is the one there.
2026-08-20 10:28:20 -07:00
John Safranek 8496451357 tests: fix privdrop test's client key paths
sshd_privdrop_fail_test.sh runs from apps/wolfsshd/test and handed the
example clients relative key paths, but the clients call
ChangeToWolfSshRoot() before parsing arguments. Every client died at
"Error setting private key" and the test blamed the privilege drop.

- Anchor the key, payload and client paths at the script's own directory.
- Rename the saved directory to TESTDIR so a later cd cannot clobber it.
- Report "no fork at all" separately in the timeout diagnostic.
- Print the client's own output at every failure exit, so a client that
  never connects cannot be read as a daemon fault.
- Keep the client logs like log.txt, gitignored and removed on success.
2026-08-20 10:28:20 -07:00
John Safranek f52c3f7e22 tests: save sshd test scripts' dir in TESTDIR
Eight scripts saved their starting directory in PWD, which the shell
rewrites on every cd, so the "cd $PWD" restore landed in the repository
root. sshd_forcedcmd_test.sh's second scenario and the log count in
sshd_x509_upn_fail.sh silently never did what they cover.

- Save the starting directory in TESTDIR, as sshd_pubkey_reject_test.sh does.
- Quote "$TESTDIR" at every cd, now that the saved value is really used.
- Stop the daemon from a trap in sshd_forcedcmd_test.sh, so its now reachable
  second scenario cannot leave one on the shared port when set -e aborts.
- Take start_wolfsshd's before and after daemon PID snapshots from pgrep -x
  instead of scraping every digit run out of "ps -e", which mixed the TIME
  field's clock digits in with the PID and, with a leftover daemon running,
  stopped the wrong process.
- Let both snapshot pipelines fail, so a set -e caller survives no daemon
  being up and a daemon that dies after sudo returns is reported by the
  caller's own empty-PID check.
2026-08-20 10:28:20 -07:00
John Safranek 1d5199bb94 tests: fix StrictModes count aborting sshd suite
grep -c prints 0 and exits 1 when nothing matches, so the "|| echo 0"
fallback fired too and the count became "0\n0". The arithmetic error
unwound bash out of the test block, skipping the last eleven tests while
the summary still printed a pass and exited 0.

- Use the bare grep -c result and default only the empty case.
- Add a RUN_COMPLETE sentinel at the end of each branch that runs tests.
- Check the sentinel before the summary so an abort exits non-zero.
- Kill lingering daemons by process name, so the teardown does not kill
  the run itself before that check when invoked by a path holding
  "wolfsshd".
2026-08-20 10:28:20 -07:00
John Safranek 33bf57b3c7 Build the confinement test fixture paths from the working directory
- Chaining each path off root formats one 256 byte buffer into another,
  which GCC cannot prove fits, failing every Linux build with
  -Werror=format-truncation.
- Sizing cwd to leave room for the suffixes bounds the paths by the
  declared sizes. A cwd too deep to fit fails WGETCWD and skips as
  before.
2026-08-20 09:56:48 -06:00
John Safranek 3b43e485dd Split SFTP confinement from the start path
- Add wolfSSH_SFTP_SetConfinePath and a separate sftpConfinePath, so
  the start path only says where a session begins, and only an
  explicit confinement root rejects out-of-tree requests.
- Have GetAndCleanPath take the WOLFSSH and enforce the confinement
  root, resolving relative requests against the start path.
- Factor the shared canonicalize-and-store work out of
  wolfSSH_SFTP_SetDefaultPath into CanonicalizePath and StorePath.
- Give the echoserver -D to opt a session into confinement; without
  it the -d home directory only says where SFTP starts.
- Document both settings, and the symlink and TOCTOU caveats, once in
  wolfsftp.h, noting the confinement root itself is trusted.
- Cover the split in api.c, and in regress.c start a session in a
  subdirectory of the confinement root: a sibling of the start
  directory is reachable, anything above the root is not.

Issue: ZD-22308
2026-08-20 09:56:48 -06:00
Yosuke Shimizu 091399cc67 agent: size the signature buffer from the identity
- PostSignRequest() allocates the signature buffer from agent->heap,
  sized from the identity's modulus mpint for RSA and from
  ECDSA_ASN_SIG_SZ for ECDSA, and frees it before returning. A modulus
  longer than RSA_MAX_SIZE, or a key type that sets no size, returns
  WS_BUFFER_E.
- wolfSSH_AGENT_SignRequest() reads the agent's reply into a heap
  buffer of WOLFSSH_AGENT_MAX_RSP_SZ, a new overridable define in
  agent.c, freed after the last use of agent->msg.
- tests/api.c carries a 3072-bit RSA key and a P-521 key as hex string
  components, and build_string() and build_mpint() helpers that write
  the message fields. AgentTestCtx.response sizes from
  AGENT_TEST_BUF_SZ.
- test_wolfSSH_agent_signrequest_rsa_3072() and
  test_wolfSSH_agent_signrequest_ecc_p521() add their identity through
  the agent callbacks and sign with it, then clear the stored private
  exponent or point and sign again.
  test_wolfSSH_agent_signrequest_rsa_too_large() adds an identity whose
  modulus exceeds RSA_MAX_SIZE and expects WS_BUFFER_E.
- The comment on test_wolfSSH_agent_signrequest_oversize_rsa_key()
  describes the identity that test uses.

Issue: F-10541
2026-08-19 21:33:15 -07: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
John Safranek 8f5d31a2c6 Add wolfssh-options build option probe
Test scripts sniff the build by grepping usage text, config.log and daemon
logs. This prints the enabled build options for them to check instead.
2026-08-19 16:55:00 -07:00
Paul Adelsbach b92e57ab4c PR feedback 2026-08-19 16:53:24 -07:00
Paul Adelsbach b1cd689ecc F-8824: enable config files without trailing newline 2026-08-19 16:53:24 -07:00
Yosuke Shimizu f530d32548 Load every PEM block in a root CA buffer
- LoadRootCaPemBuffer() loads every block a PEM CA buffer holds as a
  root CA, skipping the ones that fail. It returns WS_SUCCESS when
  any loaded, WS_PARSE_E when all failed, and WS_BAD_FILE_E when the
  buffer holds no block.
- A block runs header to footer with the next header capping the
  footer search, so wc_PemToDer() gets the block rather than the rest
  of the buffer. A header that nothing closes is skipped and the walk
  resumes at its end; each form's header is re-sought only from
  behind the one just read.
- A block takes the plain or the trusted form, whichever header leads
  picking the type. FindInBuffer() searches a length-delimited
  buffer, so an embedded NUL does not end the search.
- wolfSSH_ProcessBuffer() routes a PEM BUFTYPE_CA there and, like
  DoPemCert(), gives WS_BAD_FILETYPE_E for the trusted form as a
  certificate; SniffCertForm() reads its header as X.509 PEM.
- internal.h defines WOLFSSH_HAVE_TRUSTED_CERT_PEM under
  WOLFSSH_CERTS with wolfSSL 5.8.0 or newer and declares
  IsTrustedCertPem(); ssh.h documents the cert buffer calls.
- tests/api.c adds catBuffers(), makeTrustedPem() and
  assertCaInstalled(), with tests for the bundle, trusted file and
  trusted ReadCert paths.
2026-08-19 14:54:06 -07:00
Yosuke Shimizu 5ae0cc4c93 port: resolve the full SFTP offset in the Harmony and Zephyr ports
- Assemble the split offset with wResolveOffset() in the Harmony wPread
  and wPwrite, and seek with the resolved value.
- Assemble the split offset with wResolveOffset() in the Zephyr wPread
  and wPwrite, and seek with the resolved value.
- Define WOLFSSH_MAX_FILE_OFFSET as 0x7FFFFFFF in the Harmony block, so
  the ceiling comes from SYS_FS_FileSeek's int32_t offset rather than
  from off_t.
- Add test_PreadPwriteOffsetCeiling() covering ports whose seek type
  cannot reach 4 GiB, including a read back at an in-range offset.
- Add test_ResolveOffset() covering offset assembly, both sides of the
  ceiling, and the NULL guards.

Issue: F-8823
2026-08-18 15:01:39 -07:00
Paul Adelsbach 2b2e86fb4f Move path to end of log line to avoid early truncation of the log 2026-08-18 14:28:40 -07:00
John Safranek dac55852e3 Use wolfCrypt's Base16_Decode in test.h when it is available
- test.h's static Base16_Decode collides with wolfSSL's public one
    when coding.h lands first, breaking --enable-tpm builds.
  - Include coding.h in test.h, keeping the local copy only when
    WOLFSSL_BASE16 is absent; --enable-wolfssh alone does not set it.
  - api.c includes coding.h too, dropping its hand-declared
    Base64_Encode_NoNl, which would now be a duplicate.
2026-08-16 12:30:15 -07:00
Yosuke Shimizu 5c92896d03 Run the API tests in the TPM CI job
- No job compiled a wolfSSH test binary with WOLFSSH_TPM defined, so a
  test guarded on it compiled out everywhere and could not gate a
  merge. This job enables TPM but only builds; the jobs that run make
  check do not enable it.
- Add a make check step. automake's check-am builds every check_PROGRAM
  regardless of the TESTS override, so this is the only job that
  compiles wolfSSH's tests with TPM support.
- Override TESTS to run only tests/api.test, the one suite with
  TPM-specific tests. kex.test also aborts in the example client, which
  demands -K in a TPM build.
- Restrict it to one matrix cell. The 2x2x2 matrix varies the simulator
  and the host key, neither of which these tests touch.
- Assert -DWOLFSSH_TPM in AM_CPPFLAGS first. A build without it
  compiles the guarded tests out and still exits 0, a hollow pass.
- Dump tests/api.log on failure and archive it.
2026-08-14 14:35:24 -07:00
Yosuke Shimizu 1644bb773d Accept exact-fit ScpBuffer in no-filesystem SCP send callback 2026-08-14 14:34:16 -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 2142821bc6 Stop wPread and wPwrite dropping the high offset word 2026-08-14 14:25:55 -07:00
Paul Adelsbach 326d181389 PR feedback: fix test issue with --disable-server --enable-sshclient 2026-08-13 14:36:28 -07:00
Paul Adelsbach aeb3c4e3d2 Fix build with --disable-server 2026-08-13 14:36:28 -07:00
Yosuke Shimizu e91ff50def Fix a one byte heap overflow in LoadTpmSshKey 2026-08-13 11:59:57 -07:00
David Garske 8cef200ebd Add CONTRIBUTING.md covering the contributor agreement and PR process 2026-08-13 09:53:09 -07:00
Yosuke Shimizu 255dd926d9 Adopt the certificate loading APIs in the examples and apps 2026-08-12 16:06:39 -07:00
Yosuke Shimizu ca6a036edc Replace the matching certificate slot instead of appending a duplicate 2026-08-12 15:43:01 -07:00
John Safranek 31ed01d050 Stop the client discarding the remote command
- keepOpen was hardcoded to 1, so the TERMINAL channel type was always
  set right after EXEC, freeing the command and reverting to a shell.
- Derive keepOpen from whether a command was given.
- Gate MODES_CLEAR() the same way. A remote command runs with no pty,
  so its LF terminated output needs OPOST left on.
- The SINGLE_THREADED guard is now unconditional, matching what it did
  when keepOpen was always 1.
- Remove -N. It was parsed into config.noCommand and read nowhere, and
  making it work needs a way to request no session at all.
- Add the optional command to the usage line.
- Reject a command of WOLFSSH_MAX_CHN_NAMESZ or longer. SetChannelType
  discards it and still returns WS_SUCCESS, so the client would send an
  exec request with no command string at all.
- Drop the I/O thread guard. With keepOpen derived from config.command,
  the condition was true for every input.

Issue: F-8816
2026-08-12 14:38:48 -06:00
Yosuke Shimizu 6579f59236 wolfsshd: base Match blocks on the global config and keep included ones 2026-08-11 18:13:34 -07:00
John Safranek ddd9c1a761 Guard IdentifyAsn1Key test on TEST_INTERNAL
- IdentifyAsn1Key is WOLFSSH_LOCAL, so the check and its DER blob
  only build when linking the test library.
2026-08-11 11:55:02 -05:00
Yosuke Shimizu 53538e8261 Report a failed PEM decode as WS_PARSE_E, not WS_BAD_FILE_E 2026-08-10 15:11:39 -07:00
Paul Adelsbach 0c355c31ac Enforce null termination in sshd unit tests 2026-08-10 15:07:59 -07:00
Emma Stensland 244c82c86d Add remaining ML-DSA composite signature algorithms 2026-08-10 14:51:37 -07:00
Emma Stensland 866f7392cd Add ML-DSA44-Ed25519 composite signature support 2026-08-10 14:51:37 -07:00
John Safranek b2b32cca51 tests: add test_ByteHighwater
- The byte-count branch of HighwaterCheck() had no coverage. Exercise
  the boundary at the mark, the once-per-epoch flag that keeps the
  callback from firing a second time, the receive side, and a mark of
  0 disabling the check.

Issue: F-6978
2026-08-10 13:53:08 -06:00
John Safranek 6793c3e273 internal: validate the ECC curve name in user auth
- DoUserAuthRequestEcc() skipped the curve name in the public key blob
  and imported the point with wc_ecc_import_x963(), which picks the
  curve from the point length, so the key did not have to be on the
  curve the declared algorithm names.
- Derive the curve from pk->publicKeyType with NameToId() and
  wcPrimeForId(), require the blob's curve name to equal
  PrimeNameForId() for that id, and import with
  wc_ecc_import_x963_ex() pinned to that curve.
- The import's error check sat outside the success guard, so it
  rewrote any earlier error as WS_CRYPTO_FAILED. Scope it to the
  import itself so the parse and algorithm-match errors keep their
  own codes.
- Add test_EccUserAuthCurveMismatch, which offers a blob naming one
  curve under another algorithm and expects the request to fail.

Issue: F-6979
2026-08-10 13:53:08 -06:00
John Safranek 75b3418c0a ssh.h: document the WS_CallbackPublicKeyCheck contract
- The callback is the client's only defense against a man in the
  middle, and 0 accepts the server host key, so a stub that defaults
  to "return 0" trusts whatever key is presented.
- State that the callback must match the key against a trust store,
  and point at ClientPublicKeyCheck() in the examples.
- Record that with no callback registered the host key is rejected
  with WS_PUBKEY_REJECTED_E.

Issue: F-6976
2026-08-10 13:53:08 -06:00
John Safranek 4bdc7e6c5c ssh.h: document the WS_CallbackUserAuth contract
- WOLFSSH_USERAUTH_SUCCESS is 0, the same value as WS_SUCCESS and the
  C "no error" idiom, so a bare "return 0", a forwarded WS_SUCCESS, or
  a fall-through default of 0 silently authenticates the client.
- Spell out that the callback must fail closed: return
  WOLFSSH_USERAUTH_FAILURE for any authType or code path it does not
  explicitly handle.
- Note that for WOLFSSH_USERAUTH_PUBLICKEY the library verifies the
  signature but not the key's authorization, so the callback has to
  check the offered key against the user's authorized keys.

Issue: F-6815
2026-08-10 13:53:08 -06:00
John Safranek 4f827ca4a9 Reject client conn messages before key exchange
- IsMessageAllowedClient() gated the post-userauth rejection on
  connectState >= CONNECT_KEYED, so every state below it allowed
  msgid 80 and above.
- A client in the pre-key-exchange DoReceive() loops accepted an
  unencrypted CHANNEL_OPEN, created the channel and replied with
  CHANNEL_OPEN_CONF.
- Add a state sweep over the connection-protocol range and a full
  DoReceive() of a plaintext CHANNEL_OPEN from a pre-auth state.

Issue: F-7628
2026-08-10 12:49:49 -07:00
Ruby Martin ab8058d7cd check return value of WFTELL() 2026-08-10 10:19:45 -07:00
Ruby Martin 97bbe42263 Add WFSEEK return value checks where previously discarded 2026-08-10 10:19:45 -07:00