Add restricted wolfSSH ordering fix

Apply hash-pinned generated edits for CVE-2025-14942 while keeping
wolfSSH 1.4.20 managed sources unchanged. Add the ABI header overlay,
provenance records, and real state-machine interoperability contracts.
This commit is contained in:
2026-09-16 14:04:34 +02:00
parent 4d3bb490c9
commit bea33e1c95
28 changed files with 4653 additions and 64 deletions
+29
View File
@@ -28,6 +28,11 @@ endif()
include("${CMAKE_BINARY_DIR}/security_overrides/manifest.cmake")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
"${_sak_security_script}" "${SAK_SECURITY_VERSION_HEADER}")
if(SAK_SECURITY_HEADER_IDS)
get_filename_component(_sak_security_tools "${_sak_security_script}" DIRECTORY)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
"${_sak_security_tools}/wolfssh_order/delta.json")
endif()
# Public extension point: the Python Entry registry supplies the mapping. This
# function is backend-agnostic; a later pinned project/vendor source uses it too.
@@ -112,6 +117,30 @@ function(sak_security_replace_source component original generated nested_target)
message(STATUS "Security override: ${component}: ${original} -> ${generated}")
endfunction()
# HandshakeInfo and WOLFSSH are ABI-sensitive. Force the same generated header
# into the library AND every transitive consumer, even if a consumer adds a
# vendor include directory ahead of the overlay. The original include guard
# then prevents a second, stale definition. This also creates compiler deps.
foreach(_sak_security_id IN LISTS SAK_SECURITY_HEADER_IDS)
if(NOT _sak_security_id STREQUAL "wolfssh_internal_header" OR
NOT SAK_SECURITY_${_sak_security_id}_COMPONENT STREQUAL "wolfssl__wolfssh")
message(FATAL_ERROR "Security override: unaudited header overlay")
endif()
idf_component_get_property(_sak_security_wolfssh wolfssl__wolfssh COMPONENT_LIB)
if(NOT TARGET "${_sak_security_wolfssh}")
message(FATAL_ERROR "Security override: missing wolfSSH overlay target")
endif()
target_include_directories("${_sak_security_wolfssh}" BEFORE PUBLIC
"${SAK_SECURITY_WOLFSSH_INCLUDE}")
# PlatformIO's ESP-IDF adapter sorts app flags and deduplicates component
# flags. Keep option+operand atomic, as with wolf_crypto_policy.h.
target_compile_options("${_sak_security_wolfssh}" PUBLIC
"-include${SAK_SECURITY_${_sak_security_id}_GENERATED}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
"${SAK_SECURITY_${_sak_security_id}_ORIGINAL}"
"${SAK_SECURITY_${_sak_security_id}_GENERATED}")
endforeach()
foreach(_sak_security_id IN LISTS SAK_SECURITY_OVERRIDE_IDS)
sak_security_replace_source(
"${SAK_SECURITY_${_sak_security_id}_COMPONENT}"
+6 -5
View File
@@ -20,11 +20,12 @@ This is a semantic map, not a complete file inventory. Start here, then read the
## Source-pinned dependency corrections (Phases 9C9D)
- Files: root `CMakeLists.txt` (after `project()`), `cmake/security_overrides.cmake`, `tools/security_overrides.py`; tests: `tests/sdk_security_overrides/run.py --build-dir .pio/build/esp32-s3-devkitc-1-n16r8`.
- Build input is the exact-hash original **plus checked-in edits**, not installed source alone. Generated copies replace seven target sources without modifying SDK/managed components: HTTPS cleanup/private-key release, HTTPD scratch lifetime/null first read, ESP-TLS server-only protocol list, wolfSSH password bounds/payload wiping plus bounded IGNORE/service/string parsing, channel-window overflow rejection and ECC/Ed25519 label/exact-signature framing, DHCP option bounds (CVE-2026-45160), TLS 1.2 EMS error propagation (CVE-2026-50581), and X.509 OID allocation failure (CVE-2026-34874). The mbedTLS entries explicitly select validated nested `mbedtls`/`mbedx509` targets, not the component wrapper. Original notices and compile properties retained; all seven copies carry the 2026-09-15 modification notice; source/hash/target ambiguity fails configuration. Never hand-edit generated copies or silently repin.
- Xtensa crypto policy: root `CMakeLists.txt` sets `CURVE25519_SMALL`/`ED25519_SMALL` before component parsing; `cmake/wolf_crypto_policy.cmake` PUBLIC-propagates `cmake/wolf_crypto_policy.h` to wolfSSL consumers. PUBLIC `WOLFSSL_VALIDATE_ECC_IMPORT` and `WOLFSSL_ECDHX_SHARED_NOT_ZERO` enable existing P-256 import and X25519 all-zero-result checks. The resolved-settings guard requires these checks and both small implementations, rejects reviewed ECC validator-disabling/hardware-stub configurations, X25519 blinding and unreviewed Curve448/Ed448 enablement. Tests: `tests/wolf_crypto_policy/run.py` (strict actual production flags, real vendor crypto/ASN vectors and independently specified exact source deltas; candidate injection is not production evidence). [Key-validation review](../ssh_key_validation_review.md) records effective flags, caller/API limits and unmeasured validation cost. [Wolf review](../wolf_security_review.md) distinguishes implemented mitigations from unresolved ordering/deferred parsers; [IDF review](../idf_security_review.md) records the three backports and remaining findings.
- Parser tests: `tests/wolfssh_parser_contract/run.py`, [scope and exclusions](../../tests/wolfssh_parser_contract/README.md): 3,124 extracted-function cases per each of two stack modes, guard pages/UBSan traps and six rejected guard-removal mutations. Crypto doubles test gating, not arithmetic. PR892/881/880 subsets plus local signature framing only; no PR899 or ordering changes. Password/dispatch/deferred-source fences preserve prior contracts.
- Latest supplied parent build PASS: 94,340 B linked RAM / 1,768,949 B flash (+1,732 B versus 1,767,217 B). Strict crypto, parser, auth (135 cases), protocol policy and SDK override `--build-dir .pio/build/esp32-s3-devkitc-1-n16r8` commands PASS; independent review found no scoped blocker. Not target/runtime-reserve evidence.
- Ordering remains open: temporary PR793/819/840/855/921 attempt retained no changes; `SendNewKeys` WANT_WRITE/`SendExtInfo` continuation, `extInfoSent` rekey semantics and manual prerequisites unresolved. Registry 1.5.0/5.9.2 queries returned 404 on 2026-09-15 despite upstream tags. [Next strategy/commit pins](../wolf_security_review.md#ordering-blocker-and-actionable-next-strategy): isolated packaging/compatibility evaluation or prerequisite-audited source/header backport, with nonblocking/initial-KEX/rekey ordering tests before closure. No upgrade/device operation.
- Build input is the exact-hash original **plus checked-in edits**, not installed source alone. Generated copies replace eight target C sources plus one PUBLIC forced header without modifying SDK/managed components: HTTPS cleanup/private-key release, HTTPD scratch lifetime/null first read, ESP-TLS server-only protocol list, wolfSSH password bounds/payload wiping plus bounded IGNORE/service/string parsing, channel-window overflow rejection and ECC/Ed25519 label/exact-signature framing, DHCP option bounds (CVE-2026-45160), TLS 1.2 EMS error propagation (CVE-2026-50581), and X.509 OID allocation failure (CVE-2026-34874). The mbedTLS entries explicitly select validated nested `mbedtls`/`mbedx509` targets, not the component wrapper. Original notices and compile properties retained; all outputs carry the 2026-09-15 baseline modification notice and wolfSSH outputs add the 2026-09-16 ordering/provenance notice; source/hash/target ambiguity fails configuration. Never hand-edit generated copies or silently repin.
- Xtensa crypto policy: root `CMakeLists.txt` sets `CURVE25519_SMALL`/`ED25519_SMALL` before component parsing; `cmake/wolf_crypto_policy.cmake` PUBLIC-propagates `cmake/wolf_crypto_policy.h` to wolfSSL consumers. PUBLIC `WOLFSSL_VALIDATE_ECC_IMPORT` and `WOLFSSL_ECDHX_SHARED_NOT_ZERO` enable existing P-256 import and X25519 all-zero-result checks. The resolved-settings guard requires these checks and both small implementations, rejects reviewed ECC validator-disabling/hardware-stub configurations, X25519 blinding and unreviewed Curve448/Ed448 enablement. Tests: `tests/wolf_crypto_policy/run.py` (strict actual production flags, real vendor crypto/ASN vectors and independently specified exact source deltas; candidate injection is not production evidence). [Key-validation review](../ssh_key_validation_review.md) records effective flags, caller/API limits and unmeasured validation cost. [Wolf review](../wolf_security_review.md) distinguishes implemented restricted ordering/crypto/parser mitigations from pending target gates and deferred parsers; [IDF review](../idf_security_review.md) records the three backports and remaining findings.
- Parser tests: `tests/wolfssh_parser_contract/run.py`, [scope and exclusions](../../tests/wolfssh_parser_contract/README.md): 3,124 extracted-function cases per each of two stack modes, guard pages/UBSan traps and six rejected guard-removal mutations. Crypto doubles test gating, not arithmetic. PR892/881/880 subsets plus local signature framing only; no PR899 changes; ordering has its separate suite below. Password/dispatch/deferred-source fences preserve prior contracts.
- Latest supplied parent `pio run` PASS: **94,340 B linked RAM / 1,768,701 B flash (248 B versus 1,768,949 B)**. Final parent **all six suites PASS**: ordering `--interop` (8,028 checks / seven rejected mutations / 12 exact-256-KiB sessions, clean channel close and transport EOF), SDK overrides `--build-dir .pio/build/esp32-s3-devkitc-1-n16r8`, auth (135), protocol, strict crypto and parser (3,124 × two modes). Interop used unsandboxed approval only for local AF_UNIX sockets; no remote network/device operation. Review's misplaced EOF guard is corrected in `SendChannelEof` before lookup/serialization/state mutation; follow-up verification found no scoped blocker. Not target/runtime-reserve evidence.
- Restricted **CVE-2025-14942** ordering correction implemented: `tools/wolfssh_order/delta.json` supplies exact executable edits to `internal.c`, `ssh.c` and `internal.h`; [README](../../tools/wolfssh_order/README.md) records PR793/819/840/855/921 prerequisite disposition, and `provenance.json` pins archived patch hashes/commit IDs. Existing X25519/P-256 KEX only; independent SELF/PEER bits, expected-message/auth gates and exactly-once queued NEWKEYS under WANT_WRITE. **EXT_INFO intentionally disabled, no `server-sig-algs`; `extInfoSent` stays zero.** No full upstream backport or dependency upgrade. CMake propagates the generated ABI header BEFORE PUBLIC plus PUBLIC forced include with stale-header rejection; joined `-include/path` flags preserve ordering/crypto guards through PlatformIO sorting/deduplication.
- Ordering tests: [tests/wolfssh_order_contract](../../tests/wolfssh_order_contract/README.md), full generated C/real wolfCrypt, **8,028 checks / seven rejected mutations**; `pio_adapter.py` validates installed PlatformIO/SCons flags with a real Xtensa consumer. Initial interop failed a harness close race despite early PASS output; fixed harness awaits peer close and transport EOF and independently reaps the server via fd-passing proxy. Final agent `--interop --interop-repeat 3`: **36/36 sessions**, exact **256 KiB** each, **two or ten** key exchanges and clean client/server exits. [Remaining gates](../wolf_security_review.md#restricted-ordering-correction-and-remaining-gates): target cleanup during rekey, no-EXT_INFO client compatibility, resource/timing and whole-phase validation; no device operation or phase sign-off.
- Policy/evidence/limits: [library review](../security_library_review.md), [Phase 9C](../security_hardening.md#9c-library-cleanup-and-protocol-policy). Source-contract tests must locate and verify actual generated compilation inputs, not assume original vendor paths.
## Secure randomness
+9
View File
@@ -2,6 +2,15 @@
Working memory, not an implementation timeline. Source is authoritative; begin with [code map](code-map.md), then [architecture](architecture.md) and [decisions](design-decisions.md).
## Phase 9D continuation — restricted SSH ordering correction — 2026-09-16
- Initial Git status clean. Implemented restricted-profile CVE-2025-14942 correction without upgrading managed pins. `tools/wolfssh_order/` archives exact PR793/819/840/855/921 bytes, hashes/commits, authoritative consolidated delta and scope. Generator now replaces eight C sources plus one internal header. PUBLIC forced overlay and stale-header marker preserve ABI across all consumers; joined `-include/path` avoids PlatformIO argument sorting/dedup failure.
- Independent SELF/PEER keying bits, exact expected-message tracking, wrong-role/preauth/repeated-message rejection, nonblocking NEWKEYS continuation and EOF/window/exit send fences. Only current Curve25519/P256 KEX supported. Existing auth/password/parser/crypto fixes retained. **EXT_INFO deliberately disabled** in advertise/send/receive paths to eliminate skipped continuation: no server-sig-algs on wire. Tested Ed25519/P256/password OpenSSH combinations work; no RSA or broad-client compatibility claim. This is not wholesale upstream backport or unrestricted CVE closure.
- Review caught misplaced EOF guard (on exit-status); corrected real SendChannelEof and retained explicit CHANNEL_REQUEST exit guard. Added actual shutdown/pending-buffer/stale-WANT_WRITE tests, zero output/state mutation during keying. Independent recheck found no remaining scoped blockers. Initial build flag integration failure and obsolete constant warning fixed; no guards weakened.
- Parent `pio run` PASS **94,340 B linked RAM / 1,768,701 B flash** (248 B vs prior slice). Final parent six suites PASS: ordering `--interop` (8,028 checks, seven rejected mutations, 12 sessions each exact256KiB with completed rekey/channel close/transportEOF), SDK actual build registration, auth135, protocol, strict crypto and parser3,124x2. Agent also passed36 consecutive interop sessions. Real generated wolfSSH/wolfCrypt tested with both KEX and three rekey directions; production authentication/broker integration and target timing still separate gates.
- Initial parent interop exposed harness early exit/false-early PASS before peer channel close; corrected harness independently owns server over AF_UNIX fd-passing, waits channel closure/EOF and requires both exit0 plus exact binary data. Final parent AF_UNIX test needed explicit unsandboxed approval; no remote network or device operation. Remaining suites sandboxed.
- Updated review/hardening/roadmap/license/code map with effective restricted scope and evidence. **Next:** finish remaining advisory/parser applicability and release notice/source obligations, then whole-Phase9 target validation including cleanup during rekey, no-EXT client compatibility and validation CPU/heap/stack costs. Secure boot/encrypted NVS excluded; no device operations/assets/version upgrades/commits. Phase9 remains in progress, not production sign-off.
## Phase 9D continuation — SSH parser / key validation — 2026-09-15
- Initial Git status clean; previous 9D work already retained. Current slice keeps managed pins and seven-source override mechanism unchanged. `tools/security_overrides.py` now bounds IGNORE/service/helper parsing, rejects window-add overflow, fixes ECC/Ed25519 label predicates, and enforces ECC nested r/s plus outer signature exact consumption and Ed25519 exact signature-field consumption. Password wipe/async and state ordering unchanged.
+23 -7
View File
@@ -1,10 +1,10 @@
# Dependency license inventory — Phase 9D
## Implementation addendum — 2026-09-15
## Implementation addendum — 2026-09-16
**Missing generated modification/date notices: resolved for all seven generated sources.** `tools/security_overrides.py` prepends a prominent project modification notice dated **2026-09-15**, identifying the editable generator and warning against editing derived copies. Read-only verification matched every existing generated file to its pinned original plus checked-in edits and notice. Both new mbedTLS copies retain their original Mbed TLS Contributors copyright and **`Apache-2.0 OR GPL-2.0-or-later`** SPDX header; the Apache-2.0 selection in this inventory is unchanged. This resolves the narrow notice-generation finding, **not release packaging or license clearance**.
**Missing generated modification/date notices: resolved; current count is eight C sources plus one header.** `tools/security_overrides.py` prepends the baseline project modification notice dated **2026-09-15**, identifying the editable generator and warning against editing derived copies. The wolfSSH `internal.c`, added `ssh.c` and generated `wolfssh/internal.h` also receive a prominent **2026-09-16** ordering-profile/provenance notice. Exact original hashes and exact-once edits remain required; upstream notices are retained. The header is a PUBLIC forced build input, not a ninth C source. Both new mbedTLS copies retain their original Mbed TLS Contributors copyright and **`Apache-2.0 OR GPL-2.0-or-later`** SPDX header; the Apache-2.0 selection in this inventory is unchanged. This resolves the narrow notice-generation finding, **not release packaging or license clearance**.
Radio-blob corresponding-source/exception questions, the actual firmware/device/browser notice bundle and delivery, preferred-source packaging, wolfSSH license-package discrepancy, icon provenance/full-license work, and release-specific runtime/bootloader review remain open. Include the new `cmake/wolf_crypto_policy.{cmake,h}` policy inputs with the reproducible build/source package. No upgrades, asset regeneration or device operations were performed for this update.
Radio-blob corresponding-source/exception questions, the actual firmware/device/browser notice bundle and delivery, preferred-source packaging, wolfSSH license-package discrepancy, icon provenance/full-license work, and release-specific runtime/bootloader review remain open. Include `cmake/wolf_crypto_policy.{cmake,h}` and the ordering inputs described below with the reproducible build/source package. No upgrades, asset regeneration or device operations were performed for this update.
## Historical inventory baseline — 2026-09-15
@@ -99,21 +99,37 @@ For conveyance, review GPLv3 §§1, 46, 7 and 10 in [LICENSE](../LICENSE):
1. Supply required license/copyright/warranty notices and use a §6-compliant corresponding-source delivery method for binaries. A dependency name, registry URL, generic upstream source link, or this inventory alone is not corresponding source or a compliant written offer.
2. Preserve exact application source, lock/manifests, relevant SDK and managed source, configuration/build/link inputs, asset preferred sources and generation scripts. Include the scripts controlling compilation/installation. Generic unmodified build tools may fall outside corresponding source, but record precise versions needed for reproduction; if distributing those tools themselves, satisfy their separate licenses.
3. The Phase 9C firmware compiles **pinned originals plus project edits**. Include `tools/security_overrides.py`, `cmake/security_overrides.cmake`, root CMake integration, original hash-matching sources, and the means to reproduce the generated copies. A stock IDF/wolfSSH tarball alone omits the compiled modifications. Generated copies alone are not a substitute for the preferred editable patch/generator inputs.
4. **Resolved 2026-09-15:** all seven generated files below retain upstream notices and now carry the explicit project modification/date notice from the generator. The historical four-file audit found the notice missing; this narrow defect is repaired. GPLv3 §5(a) calls for a relevant date, and Apache-2.0 §4(b) requires prominent change notices. Continue including notices and reproducible generator inputs in distribution; do not hand-edit generated copies or vendor sources.
3. The Phase 9C9D firmware compiles **pinned originals plus project edits**. Include `tools/security_overrides.py`, `cmake/security_overrides.cmake`, root CMake integration, `cmake/wolf_crypto_policy.{cmake,h}`, the complete `tools/wolfssh_order/` inputs, original hash-matching C/header sources, and the means to reproduce all eight C copies and the PUBLIC forced header. A stock IDF/wolfSSH tarball alone omits the compiled modifications. Generated copies alone are not a substitute for the preferred editable patch/generator inputs.
4. **Resolved; updated 2026-09-16:** all eight generated C files plus one header below retain upstream notices and carry explicit project modification/date notices from the generator (baseline 2026-09-15, plus 2026-09-16 ordering notices on wolfSSH outputs). The historical four-file audit found the notice missing; this narrow defect is repaired. GPLv3 §5(a) calls for a relevant date, and Apache-2.0 §4(b) requires prominent change notices. Continue including notices and reproducible generator inputs in distribution; do not hand-edit generated copies or vendor sources.
5. If distributing a GPLv3 “User Product,” assess and provide required Installation Information under §6. Do not publish device passwords, private keys, Wi-Fi material, account verifiers, tickets, NVS images, or real credential backups as build/source artifacts. Installation compliance should use appropriate documented procedures, not routine secret disclosure. Secure boot was not enabled or altered here.
6. **Prebuilt radio libraries are the principal unresolved compatibility/source risk.** Apache-2.0 permits binary redistribution, but does not itself satisfy GPL corresponding-source obligations for a combined firmware. Establish whether required preferred source is obtainable, or whether a defensible GPL System Library/other exception applies to the actual linked artifacts. Static linkage is not automatically “mere aggregation.” Do not declare distribution cleared or definitely prohibited from this bounded audit. Escalate this before a public binary/device release.
Checked generated paths and retained licenses:
Current generated paths and retained licenses (eight C sources plus one header):
- `BUILD/security_overrides/https_server/https_server.c` — Espressif Apache-2.0.
- `BUILD/security_overrides/httpd_parse/httpd_parse.c` — Espressif Apache-2.0.
- `BUILD/security_overrides/esp_tls_mbedtls/esp_tls_mbedtls.c` — Espressif Apache-2.0.
- `BUILD/security_overrides/wolfssh_internal/internal.c` — wolfSSH GPL-3.0-or-later header.
- `BUILD/security_overrides/wolfssh_ssh/ssh.c` — wolfSSH GPL-3.0-or-later header.
- `BUILD/security_overrides/wolfssh_include/wolfssh/internal.h` — wolfSSH GPL-3.0-or-later header; ABI overlay propagated BEFORE PUBLIC and as a PUBLIC forced include, not a C compilation unit.
- `BUILD/security_overrides/dhcpserver/dhcpserver.c` — retained Espressif Apache-2.0 header.
- `BUILD/security_overrides/mbedtls_ssl_tls/ssl_tls.c` — retained Mbed TLS Contributors, `Apache-2.0 OR GPL-2.0-or-later`.
- `BUILD/security_overrides/mbedtls_x509_create/x509_create.c` — retained Mbed TLS Contributors, `Apache-2.0 OR GPL-2.0-or-later`.
### Archived ordering patches and license context
[Ordering provenance](../tools/wolfssh_order/README.md) documents the restricted CVE-2025-14942 correction, not a complete upstream backport or upgrade. [`delta.json`](../tools/wolfssh_order/delta.json) is the preferred editable, executable consolidated delta; archived upstream mail patches are provenance/prerequisite evidence, not a statement that all their hunks compile into the firmware. [`provenance.json`](../tools/wolfssh_order/provenance.json) retains the original URLs and every full embedded commit ID. Archived bytes fetched 2026-09-16 are pinned as follows; future PR responses may differ:
| Archive under `tools/wolfssh_order/` | SHA-256 |
| --- | --- |
| `793.patch` | `66aa999521800371f97b340db2bff162be4e8ffab4b4b953f9d32b8e33f56cea` |
| `819.patch` | `8e63c2b24679a7d831f7dba12e412d2f8a7fdc391fc4e8fd33f029487110219b` |
| `840.patch` | `23e25873cb4dfa36063357111019e0960dfc008a68cc29d6e2d5c37bde47ec94` |
| `855.patch` | `b94393df9528f66f6df1aba94015331fee3110c9ea2007a0eff369d8887b4b29` |
| `921.patch` | `f6c88598d7f3c94d92c31cecc4ef1e6d779b49f9eff6556ac1c57426d272d504` |
Preserve upstream mail authorship, commit metadata and applicable file notices with these archives. They contain upstream wolfSSH code/context, not newly project-authored material automatically relicensed by the generator's `GPL-3.0-only` SPDX line. The compiled wolfSSH C/header derivatives retain the upstream **GPL-3.0-or-later** grants; select GPLv3 for this integration. Patch hashes prove identity, not license permission or full archive-level clearance. Retain the upstream license texts and package discrepancy evidence, and review applicable per-file terms for any archived but unapplied example/test/workflow content when assembling the source package. No standalone blanket license grant for every patch byte is inferred from mail-patch format. Source/notice delivery and release packaging remain open.
For Apache-2.0 components, retain required attribution and any applicable upstream NOTICE contents under §4(d), **if present**; do not fabricate NOTICE text or assert every Apache package must have a NOTICE file. For MIT/BSD components, distribute the actual copyright, permission/conditions and disclaimer, not merely an SPDX name. Preserve mixed notices such as argtable3 and Newlib rather than replacing them with generic templates.
## Actionable release work (not performed)
@@ -123,7 +139,7 @@ For Apache-2.0 components, retain required attribution and any applicable upstre
| Release blocker to resolve | Document a defensible corresponding-source/exception basis for the **selected radio blobs** and other source-unavailable linked content. This is an unresolved question, not a legal verdict. |
| Before distributing | Assemble a versioned third-party notice bundle from the **actual resolved packages**: GPL texts/options, Apache text and applicable NOTICE/attribution, MIT/BSD notices, mixed libc/console texts, and GCC exception. Validate firmware/device/browser delivery, not just Git presence. Include TLSF's correct full copyright/conditions. |
| Before distributing icon derivatives | Obtain the full Apache-2.0 text and confirm exact two-icon provenance/licensing at 7.4.47; retain upstream attribution and any applicable NOTICE. Existing source comments already identify adaptations. Do not invent an absent upstream NOTICE or copy unrelated icon licenses. |
| Resolved in generator; retain in distribution | Prominent modification notices dated **2026-09-15** now appear in **all 7 generated files**, with original notices retained. Release packaging must still include them and the reproducible patch/build inputs. |
| Resolved in generator; retain in distribution | Prominent modification notices cover **eight C sources plus one header** (baseline **2026-09-15**, wolfSSH ordering **2026-09-16**), with original notices retained. Release packaging must still include them, archived patches/provenance, consolidated delta and reproducible build inputs. |
| Upstream clarification | Record/resolve wolfSSH 1.4.20 package LICENSE/README vs source-header mismatch without editing the managed package in place. GPLv3 is supported by the inspected headers and is also within the package prose's “or later” wording. |
| Release packaging verification | Archive exact corresponding source/build inputs and required asset preferred sources; validate source delivery/offer and any User Product installation information. Recheck bootloader, runtime objects and aggregation boundaries against the final release map. |
+1 -1
View File
@@ -216,7 +216,7 @@ Staged work:
1. **9A — Crash/debug build policy and operational profiles — In progress; hardware pending.** `src/security_build_policy.c` requires `CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y` and `CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT=y`; rejects core-dump enable/flash/UART, panic print/halt/GDBstub, runtime GDBstub and ESP/FreeRTOS debugger-aware options. `sdkconfig.defaults` makes the baseline explicit. Development/test/production use the same build baseline, not separate PlatformIO environments. Host matrix (`python3 tests/security_build_policy/run.py`) compiles the actual guard: 17 cases plus the generated-header check passed on 2026-09-15. `pio run` passed (94,196 B linked RAM / 1,828,565 B flash); target panic/recovery tests have not run. Production readiness remains pending.
2. **9B — SSH admission and credential handling — Implemented; combined target validation deferred.** Boot-lifetime, owner-only token buckets independently bound handshakes, password/signed-key requests and unsigned probes; reconnect/restart/counter clearing do not replenish them. Existing per-slot attempt limits/currentness remain. Explicit keyboard-interactive rejection, pending-signature result fencing, secret-free admission counters, consumed admin-buffer wipes and fail-closed hidden-prompt overflow/unsupported-byte handling are implemented. Four focused suites (including 35 pinned-vendor control-flow cases), 11 related regressions and `pio run` passed on 2026-09-15: 94,340 B linked RAM / 1,829,925 B flash. Global-budget starvation remains a documented tradeoff, not a solved availability problem.
3. **9C — Library cleanup and protocol policy — Implemented; combined target validation deferred.** Exact-hash build-tree overrides correct HTTPS cleanup/leaks, HTTPD scratch failure/wiping/first-read handling, bounded SSH password parsing/wiping and server-local TLS policy without modifying installed dependencies. Secure wolfSSL allocation hooks and explicit SSH policy fail closed; early web quota probing avoids receiving already-throttled bodies. TLS1.2 ECDHE-ECDSA AES-GCM and SSH GCM/modern-KEX allowlists intentionally exclude legacy-only clients; no identity migration. Bounded password/certificate/header/destructor review is documented, not exhaustive zeroization. Final build PASS 94,340 B linked RAM / 1,831,309 B flash; focused and related host/source-contract tests passed. [Review and maintenance contract](security_library_review.md).
4. **9D — Maintenance and lifecycle — In progress; unresolved security and distribution questions.** [Security operations](security_operations.md) documents provisioning, explicit saves, rotation, recovery, reconstruction (no implemented backup/export) and approved destructive reset/decommissioning. The [wolfSSL/wolfSSH implementation addendum](wolf_security_review.md) records the implemented Xtensa small X25519/Ed25519 mitigation (CVE-2025-12888), with consistent library/consumer flags and a resolved-settings guard; small X25519 is not combined with blinding. Bounded IGNORE/service/string parsing, channel-window overflow, ECC/Ed25519 labels and exact signature framing are now corrected in the existing generated override. [P-256 import and X25519 all-zero-result checks](ssh_key_validation_review.md) are enabled with effective PUBLIC flags confirmed; generic digest/OID API hardening and deferred parser semantics remain open. **Server-recommended ordering work (CVE-2025-14942) is not fixed:** a temporary PR793/819/840/855/921 backport attempt retained no changes because manual prerequisites, nonblocking `SendNewKeys`/`WS_WANT_WRITE``SendExtInfo` continuation and `extInfoSent` rekey semantics remain unresolved. The [IDF implementation addendum](idf_security_review.md) records pinned DHCP (CVE-2026-45160), TLS 1.2 EMS (CVE-2026-50581) and X.509 allocation-failure (CVE-2026-34874) backports; historical research remains labeled and retained. Supplied parent build PASS: **94,340 B linked RAM / 1,768,949 B flash**, unchanged RAM / **+1,732 B flash** versus the preceding 1,767,217 B build. All five parent commands passed: strict wolf-crypto-policy, wolfSSH-parser-contract (3,124 cases per each of two modes plus six rejected mutations), wolfSSH-auth-contract (135 cases), SSH-protocol-policy and SDK-override with actual build-directory registration. Independent review found no blocker in these scoped changes; see [evidence and limits](security_hardening.md#mitigation-hostbuild-evidence--2026-09-15). The [bounded license inventory](dependency_licenses.md) marks modification/date notices resolved for all seven generated files (2026-09-15; upstream licenses retained), but radio-blob corresponding-source/exception, source/notice delivery and packaging/provenance questions remain open. Official registry wolfSSH 1.5.0 / wolfSSL 5.9.2 queries returned 404 on 2026-09-15; upstream tags exist but managed compatibility is not established. The [next strategy and immutable commit references](wolf_security_review.md#ordering-blocker-and-actionable-next-strategy) call for isolated upstream packaging/compatibility evaluation or a prerequisite-audited coherent backport, with partial-send/EXT_INFO/rekey and negative ordering tests. Upgrades/backports require coherent source/header review, override rebasing and contract tests, not blind repinning. Broader dependency advisory coverage remains unfinished. No dependency upgrade was performed. **Phase 9 is not complete or production-ready; scoped mitigations do not establish full advisory closure, device validation or license/distribution clearance.** OTA signing trust remains separate Phase 10 work.
4. **9D — Maintenance and lifecycle — In progress; unresolved security and distribution questions.** [Security operations](security_operations.md) documents provisioning, explicit saves, rotation, recovery, reconstruction (no implemented backup/export) and approved destructive reset/decommissioning. The [wolfSSL/wolfSSH implementation addendum](wolf_security_review.md) records the implemented Xtensa small X25519/Ed25519 mitigation (CVE-2025-12888), with consistent library/consumer flags and a resolved-settings guard; small X25519 is not combined with blinding. Bounded IGNORE/service/string parsing, channel-window overflow, ECC/Ed25519 labels and exact signature framing are now corrected in the existing generated override. [P-256 import and X25519 all-zero-result checks](ssh_key_validation_review.md) are enabled with effective PUBLIC flags confirmed; generic digest/OID API hardening and deferred parser semantics remain open. **Restricted existing-profile ordering correction (CVE-2025-14942) is implemented**, using audited PR793/819/840/855/921 subsets plus local gates, not a full upstream backport. X25519/P-256 only, independent SELF/PEER bits, expected-message/authentication gates and queued NEWKEYS continuation handle nonblocking writes. **EXT_INFO is intentionally disabled; no `server-sig-algs` is advertised.** Eight C overrides plus one PUBLIC forced ABI header retain hash pins/notices; joined forced-header arguments fix PlatformIO sorting/deduplication. The review's misplaced EOF guard is corrected and verified with no scoped blocker. The [IDF implementation addendum](idf_security_review.md) records pinned DHCP (CVE-2026-45160), TLS 1.2 EMS (CVE-2026-50581) and X.509 allocation-failure (CVE-2026-34874) backports; historical research remains labeled and retained. Supplied parent build PASS: **94,340 B linked RAM / 1,768,701 B flash**, unchanged RAM / **248 B flash** versus 1,768,949 B. Final parent **all six suites PASS**: ordering `--interop` (8,028 checks, seven rejected mutations, 12 exact-256-KiB sessions with clean channel close/transport EOF), SDK overrides with actual `--build-dir` registration, auth (135), protocol, strict crypto and parser (3,124 × two modes). Interop required unsandboxed approval only for local AF_UNIX sockets; no remote network/device operation. Agent ordering evidence: **8,028 checks / seven rejected mutations**. Initial interop failed a harness close race despite early PASS output; the corrected harness waits for peer close/transport EOF and independently reaps the server through an fd-passing proxy. Final agent repeated OpenSSH matrix: **36/36 sessions**, each exact **256 KiB**, **two or ten** completed key exchanges and clean client/server exits; see [evidence and limits](security_hardening.md#ordering-hostbuild-evidence--2026-09-16). The [bounded license inventory](dependency_licenses.md) marks modification/date notices resolved for all eight generated C files plus one header (baseline 2026-09-15 and wolfSSH ordering 2026-09-16; upstream licenses retained), with archived patch hashes/commits and patch license context recorded, but radio-blob corresponding-source/exception, source/notice delivery and packaging/provenance questions remain open. Official registry wolfSSH 1.5.0 / wolfSSL 5.9.2 queries returned 404 on 2026-09-15; upstream tags exist but managed compatibility is not established. The [remaining ordering gates and immutable provenance](wolf_security_review.md#restricted-ordering-correction-and-remaining-gates) retain target cleanup during rekey, intended-client compatibility without EXT_INFO and whole-phase resource/timing validation. Future upstream adoption still requires isolated packaging/compatibility evaluation. Upgrades/backports require coherent source/header review, override rebasing and contract tests, not blind repinning. Broader dependency advisory coverage remains unfinished. No dependency upgrade was performed. **Phase 9 is not complete or production-ready; scoped mitigations do not establish full advisory closure, device validation or license/distribution clearance.** OTA signing trust remains separate Phase 10 work.
At the user's request, hardware validation is deferred to **Phase 9 as a whole**, not required between implementation slices. [Security hardening](security_hardening.md) collects profiles, host evidence and the combined target checklist. Silent panic reboot removes useful crash diagnostics, not ordinary reset/boot/status information or every possible log disclosure. Raw flash/RAM/dumps remain secret-bearing, not routine diagnostic exports. Existing coredump bytes are not retroactively cleared; no secure erase is claimed. Isolated synthetic-secret debug builds require explicit reviewed source-policy changes, not a provided bypass flag.
+17 -9
View File
@@ -71,7 +71,7 @@ Hidden UART0 and shared remote-console prompts now reject overflow or unsupporte
### Reproducible source corrections
`tools/security_overrides.py` verifies full original-file SHA-256 values and ESP-IDF 5.5.0, applies exact-once edits, and generates seven corrected sources under the build directory (four from 9C plus three advisory backports in 9D). `cmake/security_overrides.cmake`, included after `project()`, replaces exactly the corresponding sources in existing IDF/component targets, retaining compilation properties. Installed SDK/managed sources and their notices remain unchanged. Missing, changed or ambiguous sources fail configuration; there is no unpatched fallback. Do not edit derived files or repin a hash merely to make an upgrade build.
`tools/security_overrides.py` verifies full original-file SHA-256 values and ESP-IDF 5.5.0, applies exact-once edits, and generates eight corrected C sources plus one header under the build directory (the original four 9C sources, three IDF advisory sources, and the additional wolfSSH `ssh.c`/`internal.h` ordering inputs). `cmake/security_overrides.cmake`, included after `project()`, replaces exactly the corresponding sources in existing IDF/component targets, retaining compilation properties. Installed SDK/managed sources and their notices remain unchanged. Missing, changed or ambiguous sources fail configuration; there is no unpatched fallback. Do not edit derived files or repin a hash merely to make an upgrade build.
- **HTTPS:** delete TLS on post-handshake transport-allocation failure; fully destroy retained TLS configuration on failed HTTPD start; wipe the copied raw private key before free. Failed stop still retains live ownership.
- **HTTPD parser:** allocate/copy/wipe/free scratch on resize, preserve the old pointer on allocation failure, wipe final scratch, and handle the null initial parser pointer without undefined subtraction. Pending/unread bytes retain their existing behavior.
@@ -90,11 +90,11 @@ The new `src/ssh_memory.{c,h}` wolfSSL/wolfCrypt allocation hooks wipe the full
| SSH host key | `ecdsa-sha2-nistp256` |
| SSH ciphers, both directions | `aes128-gcm@openssh.com`, `aes256-gcm@openssh.com` |
| SSH MAC advertisement | `hmac-sha2-256` (GCM supplies packet authentication) |
| SSH user-key advertisement | `ssh-ed25519`, `ecdsa-sha2-nistp256`; database authorization remains authoritative |
| SSH user-key policy | `ssh-ed25519`, `ecdsa-sha2-nistp256`; database authorization remains authoritative; no EXT_INFO/`server-sig-algs` advertisement |
`src/ssh_protocol_policy.c` uses permanent strings and checks every setter; any failure destroys the unpublished context without fallback. Tests verify the actual compiler's available algorithms and generated KEXINIT lists, not merely setter success. The server-only TLS correction avoids breaking future outbound HTTPS clients by globally removing RSA-certificate suites.
**Compatibility:** TLS CBC/CCM/ARIA-only and SSH CBC/CTR-only clients no longer connect; removed KEX-only clients also fail. Mainstream-client interoperability and rekey remain target gates, not host-test claims. Existing TLS/SSH identity and user-key storage need no rotation or migration. Password/KDF, certificate validity/trust and browser-header policy were reviewed and retained with documented limits; no blind KDF-cost increase or HSTS policy was introduced.
**Compatibility:** TLS CBC/CCM/ARIA-only and SSH CBC/CTR-only clients no longer connect; removed KEX-only clients also fail. Bounded OpenSSH host interoperability is recorded below; intended-client compatibility without EXT_INFO and rekey/cleanup on hardware remain target gates. RSA-SHA2 discovery is not claimed. Existing TLS/SSH identity and user-key storage need no rotation or migration. Password/KDF, certificate validity/trust and browser-header policy were reviewed and retained with documented limits; no blind KDF-cost increase or HSTS policy was introduced.
### Web admission and shorter plaintext lifetimes
@@ -105,10 +105,10 @@ The existing global five-verifications/60-second fixed window is unchanged: malf
## 9D maintenance and lifecycle — in progress
- [Security operations](security_operations.md) supplies source-checked provisioning, explicit-save, account/key/identity rotation, recovery, reconstruction and approved erase/decommissioning procedures. There is no implemented configuration backup/export or private-identity restore workflow. Commands and checklists are not device execution evidence; the user will validate Phase 9 as a whole.
- [wolfSSL/wolfSSH review and implementation addendum](wolf_security_review.md): the **CVE-2025-12888** Xtensa mitigation now selects `CURVE25519_SMALL` and `ED25519_SMALL` consistently for wolfSSL and consumers. The PUBLIC forced-include resolved-settings guard rejects missing small implementations, X25519 blinding (incompatible with small in this pinned version), and unreviewed Curve448/Ed448 enablement. The existing generated wolfSSH override now bounds IGNORE/service strings, rejects zero-capacity string output and channel-window overflow, corrects ECC/Ed25519 key/signature labels, and enforces exact signature framing (ECC nested r/s bounds plus inner/outer consumption; Ed25519 outer consumption). These are PR892/881/880 subsets plus local framing corrections, not full backports; password wiping/async retention remain unchanged. PUBLIC `WOLFSSL_VALIDATE_ECC_IMPORT` and `WOLFSSL_ECDHX_SHARED_NOT_ZERO` now enable P-256 import validation and X25519 all-zero-result rejection, with effective production flags confirmed and fail-closed backend guards. See [key-validation evidence](ssh_key_validation_review.md) and [parser scope/limits](../tests/wolfssh_parser_contract/README.md). **Still open:** server-recommended message-order correction for **CVE-2025-14942**, PR899/deferred parsers, service/key-blob semantics and broader API applicability. Current raw SSH digest construction does not expose the reviewed short-digest/OID trigger; generic PR10131 API hardening is not backported. No exploit or whole-library clearance is demonstrated; added validation CPU/allocation cost and target interoperability remain unmeasured.
- [wolfSSL/wolfSSH review and implementation addendum](wolf_security_review.md): the **CVE-2025-12888** Xtensa mitigation now selects `CURVE25519_SMALL` and `ED25519_SMALL` consistently for wolfSSL and consumers. The PUBLIC forced-include resolved-settings guard rejects missing small implementations, X25519 blinding (incompatible with small in this pinned version), and unreviewed Curve448/Ed448 enablement. The existing generated wolfSSH override now bounds IGNORE/service strings, rejects zero-capacity string output and channel-window overflow, corrects ECC/Ed25519 key/signature labels, and enforces exact signature framing (ECC nested r/s bounds plus inner/outer consumption; Ed25519 outer consumption). These are PR892/881/880 subsets plus local framing corrections, not full backports; password wiping/async retention remain unchanged. PUBLIC `WOLFSSL_VALIDATE_ECC_IMPORT` and `WOLFSSL_ECDHX_SHARED_NOT_ZERO` now enable P-256 import validation and X25519 all-zero-result rejection, with effective production flags confirmed and fail-closed backend guards. See [key-validation evidence](ssh_key_validation_review.md) and [parser scope/limits](../tests/wolfssh_parser_contract/README.md). The restricted existing-profile correction for **CVE-2025-14942** is implemented as described below. **Still open:** PR899/deferred parsers, service/key-blob semantics and broader API applicability. Current raw SSH digest construction does not expose the reviewed short-digest/OID trigger; generic PR10131 API hardening is not backported. No exploit or whole-library clearance is demonstrated; added validation CPU/allocation cost and target interoperability remain unmeasured.
- [Focused IDF review and implementation addendum](idf_security_review.md): pinned backports now implement DHCP option bounds (**CVE-2026-45160**), TLS 1.2 EMS failure return (**CVE-2026-50581**) and X.509 OID allocation-failure handling (**CVE-2026-34874**). Explicit nested-target validation places the mbedTLS edits on `mbedtls`/`mbedx509`, retaining source properties and exactly-one-source checks. WS negotiation **CVE-2026-45541** and ASN.1 named-data **CVE-2025-48965** remain unpatched with the review's qualified applicability, not blanket closure.
- [Dependency license inventory](dependency_licenses.md) is a bounded engineering inventory, not legal or distribution clearance. All seven generated sources now carry prominent modification notices dated **2026-09-15**, with upstream notices retained, including both mbedTLS dual-license headers. That narrow finding is resolved. Radio-blob corresponding-source/exception questions, actual firmware/device/browser notice delivery, preferred-source packaging, wolfSSH package-license discrepancy and icon provenance/full-license work remain open. Existing repository notices alone are not a complete release bundle.
- **Ordering is not fixed.** A temporary coherent PR793/819/840/855/921 backport attempt retained no changes: manual prerequisites and nonblocking `SendNewKeys`/`WS_WANT_WRITE` skipping `SendExtInfo` continuation, plus `extInfoSent` rekey semantics, remain unresolved. Official registry 1.5.0/5.9.2 queries returned 404 on 2026-09-15 despite upstream tags existing. Follow the [actionable ordering strategy](wolf_security_review.md#ordering-blocker-and-actionable-next-strategy): evaluate immutable upstream snapshots with reviewed packaging or audit a complete prerequisite-aware backport; require partial-send/EXT_INFO/initial-KEX/rekey and negative ordering tests before closure.
- [Dependency license inventory](dependency_licenses.md) is a bounded engineering inventory, not legal or distribution clearance. All **eight generated C sources plus one header** carry prominent modification notices: the baseline **2026-09-15** notice plus **2026-09-16** ordering/provenance notices on wolfSSH outputs, with upstream notices retained, including both mbedTLS dual-license headers. That narrow finding is resolved. Radio-blob corresponding-source/exception questions, actual firmware/device/browser notice delivery, preferred-source packaging, wolfSSH package-license discrepancy and icon provenance/full-license work remain open. Existing repository notices alone are not a complete release bundle.
- **Restricted ordering correction implemented, not a full upstream backport or sign-off.** The [ordering review](wolf_security_review.md#restricted-ordering-correction-and-remaining-gates) and [provenance/prerequisite disposition](../tools/wolfssh_order/README.md) document the audited PR793/819/840/855/921 subsets plus local gates. Existing X25519/P-256 KEX only; independent SELF/PEER state, exact expected replies and authentication-phase checks cover both roles. Queued NEWKEYS survives WANT_WRITE without duplication. **EXT_INFO is deliberately disabled; no `server-sig-algs` is sent**, and `extInfoSent` stays zero. CMake applies the generated ABI header BEFORE PUBLIC and via a PUBLIC forced include; joined `-include/path` flags fix PlatformIO sorting/deduplication for ordering and crypto guards. Review's misplaced EOF guard is corrected before channel mutation; verification found no scoped blocker. Target cleanup during rekey and no-EXT_INFO client compatibility remain pending.
- No dependency versions were upgraded. Beyond the implemented backports above, proposed upgrades/backports remain candidates, not approved compatible versions. Re-audit coherent source/header changes, effective compile policy, exact-hash overrides and callback/parser contracts, then obtain host/build and whole-phase target evidence. 9A9C passes below are historical scoped evidence, not closure of these newly recorded findings.
Secure boot and encrypted NVS remain excluded. No runbook, advisory report or license inventory establishes production readiness or authorizes a destructive device operation.
@@ -129,6 +129,14 @@ Raw flash, RAM and dumps can contain Wi-Fi passwords, private keys, password ver
## Validation gates
### Ordering host/build evidence — 2026-09-16
Supplied parent `pio run` **PASS**: **94,340 B linked RAM / 1,768,701 B flash**, unchanged RAM / **248 B flash** versus 1,768,949 B. **Supplied final parent results: all six suites PASS** — ordering `--interop` (8,028 checks, seven rejected mutations, 12 sessions with exact 256 KiB echo each and clean channel close plus transport EOF), SDK overrides `--build-dir .pio/build/esp32-s3-devkitc-1-n16r8`, auth (135 cases), protocol, strict crypto, and parser (3,124 cases in each of two stack modes). Interop required unsandboxed approval solely for local AF_UNIX sockets; no remote network or device operation occurred. This documentation update did not rerun firmware or host suites.
Supplied agent ordering tests passed **8,028 checks and seven rejected mutations**, including the corrected EOF guard and real shutdown/exit-status rekey fences. The installed PlatformIO/SCons adapter regression validates joined forced-header flags with a real Xtensa consumer and rejects a split-option mutation. The [test README and code](../tests/wolfssh_order_contract/README.md) describe full generated translation units, real wolfCrypt, message-ID matrices, fragmented writes and both roles/rekey directions.
Initial host interoperability failed a harness close race: early `INTEROP PASS` preceded OpenSSH `Broken pipe` and was not a pass. The harness now waits for peer channel close and transport EOF, passes a local socket descriptor to OpenSSH, independently owns/reaps the server, and checks both process exits. Final agent `python3 tests/wolfssh_order_contract/run.py --interop --interop-repeat 3` evidence: **36/36 sessions**, each exact **256 KiB** binary echo, **ten** key exchanges in client-rekey cases or **two** in fragmented server-rekey cases, clean exits and no EXT_INFO. Coverage uses OpenSSH 10.2p1, both KEX algorithms, Ed25519/P-256/password authentication and AES128-GCM. It is not general library shutdown, arbitrary-client or target evidence. Whole-phase gates remain pending.
### Mitigation host/build evidence — 2026-09-15
Supplied parent results (not rerun for this documentation update): `pio run` **PASS**, **94,340 B linked RAM / 1,768,949 B flash**. RAM is unchanged and flash is **1,732 B larger** than the preceding 1,767,217 B mitigation build. Linked size is not runtime headroom or timing evidence.
@@ -169,7 +177,7 @@ Record the revision, compiler/build outcome and effective configuration. Confirm
### Combined Phase 9 target validation — deferred, not run
Retain these checks for the user's final whole-phase test session; do not stop implementation for separate slice sign-off. Include the [9D operational rehearsal](security_operations.md#deferred-whole-phase-9-hardware-rehearsal) and targeted message-order/parser/key-validation/interoperability checks for the eventual reviewed advisory mitigations. None is recorded as passed here.
Retain these checks for the user's final whole-phase test session; do not stop implementation for separate slice sign-off. Include the [9D operational rehearsal](security_operations.md#deferred-whole-phase-9-hardware-rehearsal) and targeted message-order/parser/key-validation/interoperability checks for the implemented restricted mitigations and any subsequent reviewed changes. None is recorded as passed here.
#### Crash and recovery
@@ -190,7 +198,7 @@ Retain these checks for the user's final whole-phase test session; do not stop i
#### Protocol compatibility and allocation-failure recovery
1. Verify both allowed TLS suites and both SSH GCM ciphers using compatible clients; force excluded CBC/CTR/other-only offers and confirm rejection. Exercise both SSH KEX choices and both user-key types, initial handshake and rekey, plus TLS renegotiation rejection. Retain UART0 access; do not rotate identities to work around an algorithm mismatch.
1. Verify both allowed TLS suites and both SSH GCM ciphers using compatible clients; force excluded CBC/CTR/other-only offers and confirm rejection. Exercise both SSH KEX choices and both user-key types, initial handshake and rekey, plus TLS renegotiation rejection. Verify intended clients work without EXT_INFO/`server-sig-algs`; do not assume RSA-SHA2 discovery. Exercise disconnect/cleanup during rekey and subsequent session recovery under load. Retain UART0 access; do not rotate identities to work around an algorithm mismatch.
2. With synthetic credentials, test truncated/oversized SSH password and change-password packets: no authentication callback for malformed fields, no crash, bounded disconnect/recovery. Include malformed IGNORE/service strings, window overflow, ECC/Ed25519 labels and nested/trailing signature bytes, invalid P-256 points and low-order X25519 inputs. Measure added import-validation latency/allocation/stack cost, host-key loading and handshake deadlines under repeated KEX/rekey and combined load. Host canary/vector assertions are not real encrypted-packet coverage.
3. Exercise HTTPS failed-start, post-handshake allocation failure, normal/failed-stop retry and split-header scratch allocation failure on a separately reviewed fault-injection image. Observe recovery/no accumulating allocation loss without exporting keys or RAM. Failed stop must not prematurely free live TLS state.
4. Repeatedly start/stop HTTPS and SSH and stress header parsing/authentication under the full transport mix. Capture internal/DMA/PSRAM free/minimum/largest-block and stack margins alongside serial/broker loss counters. Specifically measure old-plus-new allocation peaks and secure-free CPU cost; previous very low internal minima remain important.
@@ -198,7 +206,7 @@ Retain these checks for the user's final whole-phase test session; do not stop i
## Staged next work
- **Continue 9D maintenance and lifecycle.** Execute the [ordering strategy and remaining advisory work](wolf_security_review.md#ordering-blocker-and-actionable-next-strategy), finish broader dependency coverage beyond the bounded IDF/mbedTLS review and three implemented backports, and address the [release source/notice work](dependency_licenses.md#actionable-release-work-not-performed). Runbooks are documented, not rehearsed; remaining mitigations/reviews, distribution clearance and whole-phase acceptance remain outstanding.
- **Continue 9D maintenance and lifecycle.** Execute the [remaining ordering gates and advisory work](wolf_security_review.md#restricted-ordering-correction-and-remaining-gates), finish broader dependency coverage beyond the bounded IDF/mbedTLS review and three implemented backports, and address the [release source/notice work](dependency_licenses.md#actionable-release-work-not-performed). Runbooks are documented, not rehearsed; remaining mitigations/reviews, distribution clearance and whole-phase acceptance remain outstanding.
- **Retained evidence limits:** 9C completes a bounded cleanup/protocol review, not every-library-copy zeroization. Live inline residue, compaction tails, hardware/stack intermediates, global admission starvation and resource/interop measurements remain documented limitations or combined target gates. Any additional hardening must preserve owner lifetimes and bounded recovery.
- **Phase 10: OTA trust.** Define independent image-signature verification, trust-anchor provisioning, rotation/revocation, rollback/downgrade and recovery policy without secure boot. Authenticated transport alone is not image-signing policy, and OTA signature checks cannot prevent physical firmware replacement.
+16 -8
View File
@@ -1,28 +1,36 @@
# Phase 9D — wolfSSL / wolfSSH advisory review (partial)
## Implementation addendum — 2026-09-15
## Implementation addendum — 2026-09-16
**Xtensa small-math mitigation implemented; the broader review and security sign-off remain open.** Root `CMakeLists.txt` defines `CURVE25519_SMALL` and `ED25519_SMALL` before component parsing. `cmake/wolf_crypto_policy.cmake` propagates a forced-include resolved-settings guard PUBLIC from wolfSSL to consumers, including wolfSSH/application code. `cmake/wolf_crypto_policy.h` requires both enabled small implementations, rejects `WOLFSSL_CURVE25519_BLINDING`, and rejects enabling Curve448/Ed448 without review. This follows PR 9275's small-math policy for the enabled Xtensa algorithms, with consistent library/consumer production flags and ABI-sensitive layouts. **Blinding is not enabled with small X25519**: wolfSSL 5.8.2 excludes/rejects this combination; do not force it back on or present the historical blinding observation below as current policy.
**Parser corrections implemented:** `tools/security_overrides.py` extends the existing hash-pinned wolfSSH generated source (still seven overridden files overall). The PR892 subset bounds `DoIgnore`/`GetSkip` and service-string reads, accepts boundary-empty skips, rejects zero-capacity `GetString`, and preserves the old strict service-name length limit. The PR881 subset rejects channel-window addition overflow without changing the window. The PR880 subset changes both ECC/Ed25519 key/signature label comparisons to short-circuit OR. Local framing corrections bound ECC r/s reads to the declared sub-blob and require exact inner/outer consumption; Ed25519 also requires exact outer consumption. Valid framing and crypto/digest/message construction remain unchanged; formerly tolerated malformed labels/trailing bytes reject. Existing password bounds/wiping and async-pending retention remain intact. See the [parser contract and deferred scope](../tests/wolfssh_parser_contract/README.md); these are subsets, not complete PR backports. PR899 has no applied hunks; service semantics, other parsers and standalone ECC key-blob semantics remain open.
**Parser corrections implemented:** `tools/security_overrides.py` extends the existing hash-pinned wolfSSH generated source (now eight C source overrides plus one header overall). The PR892 subset bounds `DoIgnore`/`GetSkip` and service-string reads, accepts boundary-empty skips, rejects zero-capacity `GetString`, and preserves the old strict service-name length limit. The PR881 subset rejects channel-window addition overflow without changing the window. The PR880 subset changes both ECC/Ed25519 key/signature label comparisons to short-circuit OR. Local framing corrections bound ECC r/s reads to the declared sub-blob and require exact inner/outer consumption; Ed25519 also requires exact outer consumption. Valid framing and crypto/digest/message construction remain unchanged; formerly tolerated malformed labels/trailing bytes reject. Existing password bounds/wiping and async-pending retention remain intact. See the [parser contract and deferred scope](../tests/wolfssh_parser_contract/README.md); these are subsets, not complete PR backports. PR899 has no applied hunks; service semantics, other parsers and standalone ECC key-blob semantics remain open.
**Crypto validation implemented:** PUBLIC `WOLFSSL_VALIDATE_ECC_IMPORT` and `WOLFSSL_ECDHX_SHARED_NOT_ZERO` enable existing upstream P-256 import validation and X25519 all-zero-result rejection. The guard rejects missing checks and reviewed validator-disabling/hardware-stub configurations. Strict production checks confirm the effective flags without candidate injection. The [key-validation review](ssh_key_validation_review.md) traces the previously missing unauthenticated P-256 KEX point check and low-order X25519 inputs that pass the old precheck. Current raw SSH signature callers supply locally derived full digests; no current short-digest/OID-confusion trigger was found. This does not backport generic PR10131 API hardening or demonstrate an exploit. Import validation adds CPU/allocation cost requiring target measurement.
Supplied parent evidence: `pio run` **PASS**, **94,340 B linked RAM / 1,768,949 B flash**, unchanged RAM / **+1,732 B flash** versus the preceding 1,767,217 B mitigation build. All five [focused commands](security_hardening.md#mitigation-hostbuild-evidence--2026-09-15) passed: strict crypto policy, parser contract (3,124 cases in each of two stack modes plus six rejected guard-removal mutations), auth contract (135 cases), SSH protocol policy, and SDK overrides with actual build-directory registration. Independent review found no blocker in the scoped changes. Parser tests use crypto doubles; crypto tests execute real vendor arithmetic and independently check exact generated-source deltas. Neither establishes an end-to-end SSH exchange. These are supplied results, not build/test reruns by this documentation update or runtime-headroom evidence.
Supplied parent build evidence: `pio run` **PASS**, **94,340 B linked RAM / 1,768,701 B flash**, unchanged RAM / **248 B flash** versus 1,768,949 B. Final parent results: **all six suites PASS** — ordering `--interop` (8,028 checks, seven rejected mutations, 12 exact-256-KiB sessions with clean channel close/transport EOF), SDK overrides with actual `--build-dir` registration, auth (135 cases), protocol, strict crypto and parser (3,124 cases × two modes). Interop's unsandboxed approval was solely for local AF_UNIX sockets; no remote network or device operation. See [dated evidence and limits](security_hardening.md#ordering-hostbuild-evidence--2026-09-16). These are supplied results, not build/test reruns by this documentation update or runtime-headroom evidence.
**Still pending:** coherent wolfSSH ordering correction (CVE-2025-14942), deferred parser/API/advisory review, whole-phase target validation and [license/source packaging](dependency_licenses.md). No dependency upgrade or device operation was performed. Phase 9 is not complete or production-ready.
**Restricted ordering correction implemented for CVE-2025-14942; not a full upstream backport or advisory/phase sign-off.** Deferred parser/API/advisory review, whole-phase target validation and [license/source packaging](dependency_licenses.md) remain open. No dependency upgrade or device operation was performed. Phase 9 is not complete or production-ready.
### Ordering blocker and actionable next strategy
### Restricted ordering correction and remaining gates
The supplied follow-up reports an attempted coherent PR793/819/840/855/921 backport in temporary work only; **no ordering changes were retained**. Manual patch prerequisites remained unresolved, as did nonblocking `SendNewKeys` returning `WS_WANT_WRITE` and skipping the `SendExtInfo` continuation, and `extInfoSent` semantics across rekey. Parser/crypto passes do not close these state-machine issues.
The earlier temporary attempt is superseded by the [implemented restricted-profile correction and prerequisite disposition](../tools/wolfssh_order/README.md). wolfSSH remains pinned to **1.4.20** with the existing **Curve25519-SHA256 / ECDH-P256** KEX profile; DH/GEX/PQ widening is rejected. Independent SELF/PEER keying bits, exact nonzero expected-message tracking and service/authentication phase gates reject wrong-direction, premature and duplicate traffic. Receive expectations are separate from send-side rekey fences. Pre-peer-KEXINIT in-flight traffic remains legal when only SELF initiated rekey.
`SendNewKeys` queues one packet under the old sending keys and installs new sending keys; SUCCESS or WANT_WRITE clears SELF, retaining PEER and its expected NEWKEYS. Existing flush paths finish the same buffered packet without resending NEWKEYS. **EXT_INFO is intentionally disabled**, rather than adding a pending extension continuation: no `ext-info-c`, no outgoing EXT_INFO, incoming EXT_INFO rejected, `sendExtInfo`/`extInfoSent` remain zero. There is **no `server-sig-algs` advertisement** now; the KeyAccepted setter does not produce that wire extension. RSA-SHA2 discovery/RSA user-key interoperability is not claimed.
The executable consolidated edits are [delta.json](../tools/wolfssh_order/delta.json), not all hunks of PR793/819/840/855/921. [provenance.json](../tools/wolfssh_order/provenance.json) records exact archived patch byte hashes, source URLs and full embedded commit IDs; preserve those archives rather than relying on mutable PR responses. Original source/header hashes are pinned, upstream notices retained, and generated wolfSSH files carry the **2026-09-16** ordering modification/provenance notice. There are **eight C overrides plus one generated `wolfssh/internal.h`**. CMake propagates the ABI overlay BEFORE PUBLIC and as a PUBLIC forced include, with a stale-header guard and configure/compiler dependencies. Joined `-include/path` arguments preserve both ordering and crypto forced headers through PlatformIO sorting/deduplication; the adapter regression exercises a real Xtensa consumer and a failing split-option mutation.
Review found a misplaced EOF guard; it is now corrected in `SendChannelEof` before lookup/serialization/`eofTxd` mutation. The extra `SendChannelExit` guard remains intentional local hardening, not PR793's EOF hunk. Follow-up verification found no scoped blocker. Supplied ordering evidence: **8,028 checks and seven rejected mutations**, including actual EOF/shutdown/exit-status rekey fences.
The initial host interoperability run failed a close race despite printing `INTEROP PASS` early; that output was not successful completion. The corrected harness waits for peer channel close and transport EOF, uses a local fd-passing proxy, independently owns/reaps the server, and requires both process exits to be zero. Final agent evidence: **36/36 repeated OpenSSH 10.2p1 sessions**, each exact **256 KiB** binary echo, with **ten** completed exchanges for client-rekey cases or **two** for fragmented server-rekey cases, clean exits and no EXT_INFO. See the [test contract, harness diagnosis and limits](../tests/wolfssh_order_contract/README.md). This establishes bounded host interoperability, not general shutdown correctness or device behavior.
Official registry queries for wolfSSH **1.5.0** and wolfSSL **5.9.2** returned **404 on 2026-09-15**. Upstream tags exist at wolfSSH commit `8643d7be841184f766374e3b0ed68ced6391543c` and wolfSSL commit `ac01707f552c611fbd135cc723b2682b3e7f80f2`; tag existence is not managed-component availability or ESP compatibility. This is supplied query evidence, not a fresh network check by this documentation update.
Next, evaluate those immutable upstream snapshots in an isolated compatibility branch/worktree with an explicit reviewed packaging/provenance plan, rather than assume a registry version bump works. Alternatively, inventory and review every prerequisite of a coherent source/header backport before applying it. In either approach, first add state-machine regression coverage for partial sends/`WS_WANT_WRITE`, exactly-once EXT_INFO continuation, initial KEX versus rekey and `extInfoSent` lifetime, unexpected/pre-auth messages and valid client flows. Rebase source overrides and version/callback contracts explicitly; preserve password wiping and parser/crypto checks, then rerun focused suites and the firmware build. Only after review and whole-phase target interoperability/resource tests may ordering closure be claimed.
Remaining gates: target cleanup/disconnect during rekey, no-EXT_INFO compatibility with intended clients, initial KEX/rekey under load, and stack/heap/timing measurements during whole-phase validation. The broader advisory/parser review and release packaging remain open. Any future upstream upgrade still needs isolated packaging/compatibility evaluation, coherent source/header review and explicit rebasing of source overrides and version/callback contracts while preserving password/parser/crypto protections. The restricted correction is not a full wolfSSH 1.4.22/1.5.0 backport, dependency upgrade, or phase sign-off.
## Historical pre-mitigation research baseline — 2026-09-15
**The remainder retains the original pre-mitigation research evidence. “Current” macros, generated hashes, priorities and “not applied/tested” statements below describe that historical snapshot, not today's implementation. The addendum and linked key-validation review supersede the scoped small-math, parser and ECC/X25519 status claims. Ordering and explicitly deferred findings remain open.**
**The remainder retains the original pre-mitigation research evidence. “Current” macros, generated hashes, priorities and “not applied/tested” statements below describe that historical snapshot, not today's implementation. The addendum and linked key-validation review supersede the scoped small-math, parser, ECC/X25519 and ordering status claims. Restricted ordering is implemented; its target/compatibility gates and explicitly deferred findings remain open.**
Review date: **2026-09-15**. Read-only external research and local applicability inspection; stopped at the user's requested handoff. **Not a completed security review or release clearance.** Only this new report was written. No dependencies, sources, generated inputs, or parent documents were changed; no build, hardware test, network exploit, or public PoC was executed.
+54 -12
View File
@@ -27,6 +27,13 @@ sdk = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = sdk
SPEC.loader.exec_module(sdk)
TLS_ENTRY = next(e for e in sdk.ENTRIES if e.name == "esp_tls_mbedtls")
SOURCE_ENTRIES = tuple(e for e in sdk.ENTRIES if not e.header)
def generated_path(binary, entry):
base = binary / "security_overrides"
return (base / "wolfssh_include/wolfssh" / Path(entry.source).name if entry.header
else base / entry.name / Path(entry.source).name)
def source_path(entry, idf, project=ROOT):
@@ -91,17 +98,32 @@ def generator_tests(idf, work):
assert before == {p: (p.read_bytes(), p.stat().st_mtime_ns) for p in before}
for entry in sdk.ENTRIES:
original = source_path(entry, idf).read_bytes()
derived = (binary / "security_overrides" / entry.name / Path(entry.source).name).read_bytes()
derived = generated_path(binary, entry).read_bytes()
notice = sdk.MODIFICATION_NOTICE.encode()
assert derived.startswith(notice)
assert derived.count(notice) == 1
assert b"Modified by the ESP32_serial_swiss_army_knife project on 2026-09-15" in notice
assert derived[len(notice):].startswith(original[:original.index(b"*/") + 2])
assert original[:original.index(b"*/") + 2] in derived[:2500]
if entry.component == "wolfssl__wolfssh":
assert b"Ordering profile modified 2026-09-16" in derived[:1000]
assert derived != original
expect_error(lambda: sdk.render_entry(replace(sdk.ENTRIES[0], target="mbedtls"),
{"idf": idf, "project": ROOT}), "invalid nested target")
expect_error(lambda: sdk.render_entry(replace(sdk.ENTRIES[1], target="unknown"),
{"idf": idf, "project": ROOT}), "invalid nested target")
expect_error(lambda: sdk.render_entry(replace(sdk.ENTRIES[0], header=True),
{"idf": idf, "project": ROOT}), "unaudited header overlay")
project_copy = work / "header_mismatch"
for entry in sdk.ENTRIES:
if entry.root == "project":
copied = project_copy / entry.source
copied.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(ROOT / entry.source, copied)
header = next(e for e in sdk.ENTRIES if e.header)
(project_copy / header.source).write_bytes(b"changed header")
header_failed = work / "header_failed"
expect_error(lambda: sdk.generate(idf, project_copy, header_failed), "SHA256 mismatch")
assert not header_failed.exists(), "header drift must reject the entire source/ABI plan"
expect_error(lambda: sdk.apply_edits("x", (sdk.Edit("missing", "z"),)), "got 0")
expect_error(lambda: sdk.apply_edits("xx", (sdk.Edit("x", "z"),)), "got 2")
expect_error(lambda: sdk.generate(idf, ROOT, binary, ()), "absent")
@@ -136,7 +158,7 @@ def generator_tests(idf, work):
def extracted_tests(idf, binary, work):
texts = {e.name: (binary / "security_overrides" / e.name / Path(e.source).name).read_text() for e in sdk.ENTRIES}
texts = {e.name: generated_path(binary, e).read_text() for e in sdk.ENTRIES}
aux = {}
for rel, expected in AUXILIARY.items():
raw = (idf / rel).read_bytes()
@@ -224,6 +246,8 @@ def cmake_fixture_tests(idf, work):
copied = fixture / e.source
copied.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source_path(e, idf), copied)
if e.header:
continue
target = e.target or f"test_{e.component}"
owner_lines = nested_lines if e.target else lines
registered_source = Path(e.source).name if e.target else source_path(e, idf, fixture)
@@ -245,11 +269,16 @@ def cmake_fixture_tests(idf, work):
'if(TEST_AMBIGUOUS)', f'set_property(TARGET test_{sdk.ENTRIES[0].component} APPEND PROPERTY SOURCES "{source_path(sdk.ENTRIES[0], idf, fixture)}")', 'endif()',
'if(TEST_TARGET_MISSING)', 'function(idf_component_get_property out component property)',
'set(${out} nonexistent PARENT_SCOPE)', 'endfunction()', 'endif()']
for e in sdk.ENTRIES:
for e in SOURCE_ENTRIES:
directory = f' DIRECTORY "{nested_dir}"' if e.target else ''
lines += [f'set_source_files_properties("{source_path(e, idf, fixture)}"{directory} PROPERTIES COMPILE_FLAGS "-DSOURCE_FLAG" COMPILE_DEFINITIONS "SOURCE_DEFINE" COMPILE_OPTIONS "-fno-common")']
lines += [f'include("{ROOT / "cmake/security_overrides.cmake"}")']
for e in sdk.ENTRIES:
(fixture / 'consumer.c').write_text('int consumer(void) { return 0; }\n')
lines += ['add_library(direct_consumer STATIC consumer.c)',
'target_link_libraries(direct_consumer PUBLIC test_wolfssl__wolfssh)',
'add_library(transitive_consumer STATIC consumer.c)',
'target_link_libraries(transitive_consumer PRIVATE direct_consumer)',
f'include("{ROOT / "cmake/security_overrides.cmake"}")']
for e in SOURCE_ENTRIES:
target = e.target or f"test_{e.component}"
directory = f' DIRECTORY "{nested_dir}"' if e.target else ''
lines += [f'file(GENERATE OUTPUT "${{CMAKE_BINARY_DIR}}/{e.name}.sources" CONTENT "$<TARGET_PROPERTY:{target},SOURCES>")',
@@ -260,24 +289,33 @@ def cmake_fixture_tests(idf, work):
(fixture / "CMakeLists.txt").write_text("\n".join(lines) + "\n")
build = work / "cmake_good"
run(["cmake", "-G", "Ninja", "-S", fixture, "-B", build])
for e in sdk.ENTRIES:
for e in SOURCE_ENTRIES:
source = (build / (e.name + ".sources")).read_text()
assert source.split(';').count(str(build / "security_overrides" / e.name / Path(e.source).name)) == 1
assert str(source_path(e, idf, fixture)) not in source
build_registration(build, idf, fixture)
commands = json.loads((build / "compile_commands.json").read_text())
for e in sdk.ENTRIES:
generated = str(build / "security_overrides" / e.name / Path(e.source).name)
for e in SOURCE_ENTRIES:
generated = str(generated_path(build, e))
matches = [c for c in commands if c["file"] == generated]
assert len(matches) == 1, (e.name, matches)
for option in ("-DSOURCE_FLAG", "-DSOURCE_DEFINE", "-fno-common",
str(source_path(e, idf, fixture).parent)):
assert option in matches[0]["command"], (e.name, option, matches)
header = next(e for e in sdk.ENTRIES if e.header)
overlay = str(generated_path(build, header))
consumers = [c for c in commands if c['file'].endswith('/consumer.c') or
'/wolfssh_internal/' in c['file'] or '/wolfssh_ssh/' in c['file']]
assert len(consumers) == 4
for command in consumers:
assert '-include' + overlay in command['command']
assert str(build / 'security_overrides/wolfssh_include') in command['command']
print('PUBLIC forced header/overlay reaches library, direct and transitive consumers PASS')
ninja = (build / "build.ninja").read_text()
for path in [ROOT / "tools/security_overrides.py", idf / "components/esp_common/include/esp_idf_version.h"] + [source_path(e, idf, fixture) for e in sdk.ENTRIES]:
for path in [ROOT / "tools/wolfssh_order/delta.json", ROOT / "tools/security_overrides.py", idf / "components/esp_common/include/esp_idf_version.h"] + [source_path(e, idf, fixture) for e in sdk.ENTRIES]:
assert str(path) in next(line for line in ninja.splitlines() if ": RERUN_CMAKE" in line), path
for flag, phrase in (("TEST_MISSING", "found 0"), ("TEST_AMBIGUOUS", "found 2"),
("TEST_TARGET_MISSING", "missing component target"),
("TEST_TARGET_MISSING", "missing wolfSSH overlay target"),
("TEST_NESTED_MISSING", "missing nested target"),
("TEST_NESTED_OWNER", "unexpected nested target owner"),
("TEST_mbedtls_MISSING_SOURCE", "found 0"),
@@ -345,7 +383,11 @@ def build_registration(build, idf, project=ROOT):
ninja = (build / "build.ninja").read_text()
compile_lines = [line for line in ninja.splitlines() if ": C_COMPILER" in line]
for e in sdk.ENTRIES:
generated = build / "security_overrides" / e.name / Path(e.source).name
generated = generated_path(build, e)
if e.header:
assert not any(str(generated) in line for line in compile_lines)
assert generated.read_bytes() == sdk.render_entry(e, {"idf": idf, "project": project})[1]
continue
matches = [line for line in compile_lines if str(generated) in line]
assert len(matches) == 1, (e.name, matches)
assert not any(str(source_path(e, idf, project)) in line for line in compile_lines), e.name
+33 -3
View File
@@ -117,6 +117,28 @@ def enum_containing(source, token):
return matches[0] + "\n"
def reviewed_order_function(name, original):
"""Independent, exact allowlist; do not accept an arbitrary generator delta."""
if name == 'SendExtInfo':
return ('int SendExtInfo(WOLFSSH* ssh)\n{\n'
' WOLFSSH_UNUSED(ssh);\n return WS_NOT_COMPILED;\n}\n')
if name == 'SendKexInit':
edits = (
(' ssh->isKeying = 1;',
' /* Set self is keying flag since we started sending the KEX init msg */\n'
' ssh->isKeying |= WOLFSSH_SELF_IS_KEYING;'),
(' if (ssh->ctx->side == WOLFSSH_ENDPOINT_CLIENT) {\n'
' kexAlgoNamesPlus = ",ext-info-c";\n'
' kexAlgoNamesPlusSz = (word32)WSTRLEN(kexAlgoNamesPlus);\n }\n\n', ''),
(' if (ret == WS_SUCCESS)\n ret = wolfSSH_SendPacket(ssh);',
' if (ret == WS_SUCCESS) {\n ret = wolfSSH_SendPacket(ssh);\n }'),
)
for old, new in edits:
assert original.count(old) == 1, name
original = original.replace(old, new)
return original
def main():
parser = argparse.ArgumentParser(description=__doc__)
databases = sorted((ROOT / ".pio/build").glob("*/compile_commands.json"))
@@ -182,15 +204,23 @@ def main():
functions = ("NameToId", "IdToName", "AlgoListSz", "CopyNameList",
"CopyNameListPlus", "BuildNameList", "SendKexInit", "SendExtInfo")
actual = "\n".join(extract(sources["ssh.c"], "wolfSSH_CTX_SetAlgoList" + field)
for field in FIELDS)
ssh_entry = next(e for e in ENTRIES if e.name == 'wolfssh_ssh')
_, generated_ssh = render_entry(ssh_entry, {'project': ROOT})
actual = ''
for field in FIELDS:
name = 'wolfSSH_CTX_SetAlgoList' + field
body = extract(generated_ssh.decode(), name)
assert body == extract(sources['ssh.c'], name), name
actual += body
for name in functions:
if extract(internal, name) != extract(sources["internal.c"], name):
if extract(internal, name) != reviewed_order_function(name, extract(sources["internal.c"], name)):
raise RuntimeError(f"Override changed reviewed protocol function: {name}")
actual += "\n".join(extract(internal, name) for name in functions)
# Preserve actual conditional enum values and feature-filtered name table.
types = "\n".join(enum_containing(resolved, token) for token in
("ID_NONE", "TYPE_KEX", "MSGID_KEXINIT", "WOLFSSH_ENDPOINT_SERVER"))
assert macros['WOLFSSH_SELF_IS_KEYING'] == '0x02'
types += '#define WOLFSSH_SELF_IS_KEYING 0x02\n'
types += "typedef struct { byte id; byte type; const char *name; } NameIdPair;\n" + mapping
assignments = []
for field in FIELDS:
+9 -11
View File
@@ -7,6 +7,7 @@
#include "resolved.h"
#define WLOG(...) ((void)0)
#define WOLFSSH_UNUSED(x) ((void)(x))
#define INLINE inline
#define WMEMCPY memcpy
#define WSTRLEN strlen
@@ -18,9 +19,7 @@
#define LENGTH_SZ 4U
#define BOOLEAN_SZ 1U
#define COOKIE_SZ 16U
#define WS_EXTINFO_EXTENSION_COUNT 1
static const char cannedNoneNames[] = "none";
static const char serverSigAlgsName[] = "server-sig-algs";
typedef struct { byte *kexInit; word32 kexInitSz; } HandshakeInfo;
typedef struct {
@@ -206,14 +205,13 @@ int main(void)
assert(SendKexInit(ssh) == WS_SUCCESS);
check_kex(ssh);
assert(sends == 2 && allocations == 2 && frees == 1);
assert(SendExtInfo(ssh) == WS_SUCCESS);
size_t offset = 1, length = ssh->outputBuffer.length - 8U;
const byte *p = packet + 8;
assert(p[0] == MSGID_EXT_INFO);
assert(take_u32(p, length, &offset) == 1);
expect_name(p, length, &offset, "server-sig-algs");
expect_name(p, length, &offset, "ssh-ed25519,ecdsa-sha2-nistp256");
assert(offset == length);
assert(ssh->isKeying == WOLFSSH_SELF_IS_KEYING);
assert(SendExtInfo(ssh) == WS_NOT_COMPILED);
assert(sends == 2);
ctx.side = WOLFSSH_ENDPOINT_CLIENT;
assert(SendKexInit(ssh) == WS_SUCCESS);
check_kex(ssh); /* Client must not append ext-info-c either. */
ctx.side = WOLFSSH_ENDPOINT_SERVER;
/* No key and injected packet/allocation failures must not send a fallback. */
unsigned before = sends;
ctx.privateKeyCount = 0;
@@ -234,6 +232,6 @@ int main(void)
assert(purges == old_purges);
check_kex(ssh);
bounded_free(handshake.kexInit);
puts("PASS: resolved vendor name/ID/type map, actual setters, initial/rekey KEXINIT both directions, server-sig-algs, bounded failure paths");
puts("PASS: resolved vendor name/ID/type map, actual setters, initial/rekey KEXINIT both directions, no EXT_INFO negotiation, bounded failure paths");
return 0;
}
+1 -1
View File
@@ -207,7 +207,7 @@ def profiles(database, candidate):
'wolfcrypt/src/curve25519.c', 'wolfcrypt/src/ed25519.c',
'wolfcrypt/src/fe_operations.c', 'wolfcrypt/src/ge_operations.c',
'wolfcrypt/src/fe_low_mem.c', 'wolfcrypt/src/ge_low_mem.c',
'wolfssl__wolfssh/src/ssh.c',
'security_overrides/wolfssh_ssh/ssh.c',
'security_overrides/wolfssh_internal/internal.c',
'src/ssh_transport.c', 'src/ssh_security.c')
for suffix in suffixes:
+182
View File
@@ -0,0 +1,182 @@
# Generated wolfSSH ordering contract
Implementation/provenance and restricted-profile rationale:
[`tools/wolfssh_order/README.md`](../../tools/wolfssh_order/README.md).
```sh
python3 tests/wolfssh_order_contract/run.py
python3 tests/wolfssh_order_contract/run.py --interop --target-contracts
python3 tests/wolfssh_order_contract/run.py --interop --interop-repeat 3
python3 tests/wolfssh_parser_contract/run.py
python3 tests/wolfssh_auth_contract/run.py --host-only
python3 tests/sdk_security_overrides/run.py
python3 tests/wolf_crypto_policy/run.py --host-only
```
Requires the already-installed pinned managed components, a host C compiler,
and (for `--interop`) OpenSSH `ssh`/`ssh-keygen` with `ProxyUseFdpass`, OpenSSL,
and POSIX Unix sockets with `SCM_RIGHTS` descriptor passing. The
optional `--target-contracts` also requires the existing ESP-IDF compiler and
`.pio/build/esp32-s3-devkitc-1-n16r8/compile_commands.json`. It **does not run
PlatformIO**. Everything generated by this suite is in a temporary directory;
it never writes managed sources, persistent keys or the production build tree.
No downloads or IP network sockets: a short-lived ProxyCommand passes a temporary
local Unix socket to OpenSSH. Python independently owns/reaps the server, whose
stdio uses the accepted socket. User/global SSH configuration and agents are disabled.
Sandboxes that prohibit even AF_UNIX sockets require permission for `--interop`.
All keys
and the fixed test password are disposable fixtures, not production credentials.
## Follow-up fixes and validation — 2026-09-16
`python3 tests/wolfssh_order_contract/pio_adapter.py` executes the installed
PlatformIO `get_app_flags` function and SCons ParseFlags/AppendUnique on the
configured project flags. Both forced headers must remain joined `-include/path`
arguments. A real Xtensa `-c/-o` consumer compiles; a split-option mutation must
reproduce the multiple-input failure. Also available as `run.py --pio-adapter`.
Real EOF, shutdown and exit-status tests now cover SELF, PEER and SELF|PEER,
empty/pending output, stale WANT_WRITE and repeated calls: no bytes, callbacks,
sequence changes, expectation consumption, `eofTxd` or `closeTxd` mutation.
EOF succeeds exactly once after keying clears. The misplaced PR793 EOF guard is
corrected; the additional exit-status guard is intentionally retained. Removing
the EOF guard is the seventh required failing mutation. Latest run: 8,028 checks.
Authorized firmware build PASS: `pio run`, 21.31 s, **94,340 B RAM / 1,768,701 B
flash**. Strict SDK registration, auth, protocol and crypto suites PASS against
that build. This supersedes the initial no-build/stale-artifact status below;
no device validation occurred. The 12-session OpenSSH matrix was not rerun in
this narrowly scoped follow-up; its previous evidence remains historical.
## What executes
The generator verifies original source hashes and applies the real checked-in
edits. The test includes the **entire generated `internal.c`**, links generated
`ssh.c`, unmodified pinned IO/log/port sources, and real wolfCrypt code. Static
functions are directly visible to the test; they are not rewritten copies or
models. Every test translation unit uses the generated ABI header. The original
include directory is deliberately searched first to exercise the forced overlay.
An original-header-before-overlay compile must fail explicitly.
The host crypto settings enable portable small X25519/Ed25519, TFM P-256,
import/shared-secret validation and AES-GCM. They are **host settings**, not an
assertion that every ESP compile option is identical. UBSan trap instrumentation
is enabled; the host's standalone UBSan runtime is unavailable. `WOLFSSL_USE_ALIGN`
selects bytewise encoding so x86 unaligned fast-path stores do not mask protocol
tests with alignment traps. Production crypto policy is not changed.
Coverage:
- Every byte-sized message ID at initial state for both roles; every ID during
all combinations of self/peer keying; explicit service/auth phase boundaries.
- Actual `DoPacket` rejects malformed pre-auth auth/channel/extension payloads
**before dispatch**, for both roles, without consuming the input.
- Missing, wrong, duplicate and prematurely received KEX/NEWKEYS messages;
benign transport notifications do not consume an expectation.
- Wrong optimistic INIT guess preserves the real INIT expectation; duplicate
`DoKexInit` fails before parsing another exchange.
- Actual NEWKEYS framing, queued bytes, sequence counter and key installation:
zero-through-complete one-byte write quotas, repeated output flush, no duplicate
NEWKEYS, independent self/peer bits and exactly-once handshake disposal.
Fatal IO and real invalid-AES-key errors retain the required keying state.
- Generated client/server complete password-authenticated handshakes with both
KEX algorithms and AES256-GCM, followed by server-, client- and simultaneously
initiated rekeys. IO fragments to 11/13 bytes and injects WANT_WRITE regularly.
- Six deliberately bad generated-source mutations must fail: pre-auth injection,
wrong expected KEX, unnegotiated EXT_INFO, clearing both keying bits, accepting
peer NEWKEYS before local NEWKEYS, and removing PR921's server expectation.
- Archived upstream patch hashes/commit IDs, source/header pins and unchanged
logging ABI are independently checked.
## OpenSSH interoperability (`--interop`)
Twelve real sessions cover:
- X25519 and P-256 KEX, P-256 host identity and AES128-GCM.
- Ed25519 public-key, P-256 public-key and password authentication.
- Client rekey every 32 KiB, or a server-initiated rekey with 31/37-byte fragmented
IO and forced WANT_WRITE every third send callback.
- Exact 256 KiB binary echo per session, successful channel close/status,
at least two completed exchanges and **no EXT_INFO/server-sig-algs received**.
Verified with OpenSSH **10.2p1 / OpenSSL 3.5.8** on 2026-09-16. The unfragmented
client-rekey fixtures completed ten exchanges each; fragmented server-rekey
fixtures completed two and forced roughly 3,600 write stalls each. These are
host interoperability results, not device measurements or universal-client
claims. Test-only authentication authorizes a generated key blob or a synthetic
password; public-key signature verification is still performed by real wolfSSH.
### Closure-race diagnosis and regression — 2026-09-16
The original harness failure was reproduced: exact echo and `INTEROP PASS` were
followed by OpenSSH `Broken pipe` while sending channel close. The harness called
`wolfSSH_shutdown()` once, accepted WANT_READ as retryable, drained only output,
and exited without receiving the peer's close. Flushing is not shutdown completion.
A second lifecycle issue is that OpenSSH terminates its ProxyCommand on exit;
keeping the server itself as that proxy cannot reliably prove server completion.
The harness now queues shutdown once, flushes output, pumps the real worker until
`WS_CHANNEL_CLOSED` and channel removal, then keeps receiving through transport
EOF. It accepts the expected socket-close error only with an actual zero-length
transport read, the library closed flag, and no reset. Python owns the server
independently via a local fd-passing proxy; no sleep-based grace period or return
code waiver is used. The existing 100-us polling backoff is not a close deadline.
Shutdown remains unfragmented, as before; this does not expand nonblocking-shutdown
coverage beyond the separate contract tests.
Each session requires **both process exit codes zero**, byte-for-byte 262,144-byte
echo, a single complete server evidence record, at least two completed exchanges,
the expected signed/password authentication counts, stalls in fragmented cases,
peer channel-close completion and transport EOF. OpenSSH must independently report
receipt of exit-status and channel close and `Exit status 0`; EXT_INFO remains
forbidden. Early server exit, missing evidence, nonzero client/server status,
truncated/corrupted echo, and missing rekey evidence all fail.
`--interop-repeat N` repeats the full 12-session matrix (120, default 1), compiling
the contracts once and generating new disposable keys per matrix. Bounds: 10 s
proxy connection, 30 s server alarm, 45 s client communication, 5 s server reap;
failed/timed-out processes are killed and reaped. No production code changes are
needed for the reproduced harness race. Final validation on OpenSSH 10.2p1:
`python3 tests/wolfssh_order_contract/run.py --interop --interop-repeat 3` passed
**36/36 sessions**, plus 8,028 contract checks and seven rejected mutations.
Client-rekey cases completed ten exchanges and server-rekey cases two; each
session had exact echo and both clean process exits. An earlier revision of the
fix also passed 36 sessions before the final close/EOF-error assertions were added.
These results do not establish general library shutdown correctness or hardware
behavior.
## Candidate target contracts (`--target-contracts`)
Reads, but does not modify, existing compiler commands. Preserves their flags
and crypto definitions while substituting freshly generated SSH sources and the
PUBLIC-equivalent header overlay in a **temporary candidate database**. Runs:
- Xtensa syntax checks for generated `internal.c`/`ssh.c` and SSH application
consumers.
- `ssh_protocol_policy`, including independently specified exact changes to
KEXINIT and disabled EXT_INFO serialization (not an arbitrary delta allowlist).
- `wolf_crypto_policy`, including real vendor vectors, independently checked
protected crypto/parser function bodies and resolved compiler flags.
- `wolfssh_auth_contract`, including password bounds/wiping/async contracts.
Those existing suites label some checks “production compiler/profile”; in this
mode they use **production-derived flags with candidate source/header paths**.
The wrapper explicitly labels the run candidate-only. This does not establish
actual firmware build registration, linking, flashing, resource reserve or
hardware behavior. Their ordinary strict commands correctly reject the old
production generated source until a firmware reconfigure is performed.
The SDK override suite separately executes the actual CMake module against a
mock IDF target graph: source ownership/properties, header hash-drift atomic
failure, PUBLIC overlay/forced include propagation to direct and transitive
consumers, configure dependencies and rejection cases. This is real CMake wiring
evidence, not a full ESP-IDF firmware build.
## Limits
No whole-upstream-fix claim. EXT_INFO and keyboard-interactive are excluded; KEX
is intentionally limited to the project algorithms. No RSA-SHA2 discovery,
DH/GEX/PQ, arbitrary-client, hardware, timing, memory-headroom, or complete SSH
parser audit claim. Existing parser/password protections remain covered by their
own suites. Firmware reconfigure/build and whole-phase device validation remain
follow-up work, deliberately not executed in this task.
+288
View File
@@ -0,0 +1,288 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
/* The entire freshly generated translation unit, not a model of its gates. */
#include "internal.c"
static unsigned checks;
#define CHECK(x) do { ++checks; if (!(x)) { \
fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #x); abort(); } } while (0)
typedef struct {
byte bytes[32768];
word32 size;
int quota, chunk, calls, fatal;
} Sink;
static int send_test(WOLFSSH* ssh, void* buf, word32 size, void* context)
{
Sink* sink = context;
(void)ssh;
++sink->calls;
if (sink->fatal) return WS_CBIO_ERR_GENERAL;
if (sink->quota == 0) return WS_CBIO_ERR_WANT_WRITE;
if (sink->quota > 0) --sink->quota;
if (size > (word32)sink->chunk) size = sink->chunk;
CHECK(size <= sizeof(sink->bytes) - sink->size);
memcpy(sink->bytes + sink->size, buf, size);
sink->size += size;
return (int)size;
}
static void policy(WOLFSSH_CTX* ctx)
{
CHECK(wolfSSH_CTX_SetAlgoListKex(ctx, "curve25519-sha256,ecdh-sha2-nistp256") == 0);
CHECK(wolfSSH_CTX_SetAlgoListKey(ctx, "ecdsa-sha2-nistp256") == 0);
CHECK(wolfSSH_CTX_SetAlgoListCipher(ctx, "aes128-gcm@openssh.com,aes256-gcm@openssh.com") == 0);
CHECK(wolfSSH_CTX_SetAlgoListMac(ctx, "hmac-sha2-256") == 0);
CHECK(wolfSSH_CTX_SetAlgoListKeyAccepted(ctx, "ssh-ed25519,ecdsa-sha2-nistp256") == 0);
}
static void gates(WOLFSSH* ssh)
{
byte consumed;
int side, state, msg;
for (side = WOLFSSH_ENDPOINT_SERVER; side <= WOLFSSH_ENDPOINT_CLIENT; ++side) {
ssh->ctx->side = side;
ssh->acceptState = ACCEPT_BEGIN;
ssh->connectState = CONNECT_BEGIN;
for (msg = 0; msg < 256; ++msg) {
ssh->isKeying = 0;
int expect = (msg >= MSGID_DISCONNECT && msg <= MSGID_DEBUG) || msg == MSGID_KEXINIT;
CHECK(IsMessageAllowed(ssh, msg, WS_MSG_RECV) == expect);
}
for (state = 1; state <= 3; ++state) {
ssh->isKeying = state;
ssh->acceptState = ACCEPT_SERVER_USERAUTH_SENT;
ssh->connectState = CONNECT_SERVER_USERAUTH_ACCEPT_DONE;
for (msg = 0; msg < 256; ++msg) {
ssh->handshake->expectMsgId = MSGID_NEWKEYS;
int result = IsMessageAllowed(ssh, msg, WS_MSG_RECV);
if (state & WOLFSSH_PEER_IS_KEYING) {
CHECK(result == ((msg >= 1 && msg <= 4) || msg == MSGID_NEWKEYS));
CHECK(ssh->handshake->expectMsgId == (msg == MSGID_NEWKEYS ? MSGID_NONE : MSGID_NEWKEYS));
}
CHECK(!IsMessageAllowed(ssh, msg, WS_MSG_SEND));
}
}
ssh->isKeying = WOLFSSH_PEER_IS_KEYING;
ssh->handshake->expectMsgId = MSGID_NONE;
CHECK(!IsMessageAllowed(ssh, MSGID_NEWKEYS, WS_MSG_RECV));
CHECK(!IsMessageAllowed(ssh, MSGID_KEXDH_INIT, WS_MSG_RECV));
ssh->handshake->expectMsgId = MSGID_NEWKEYS;
CHECK(!IsMessageAllowed(ssh, MSGID_KEXDH_REPLY, WS_MSG_RECV));
CHECK(ssh->handshake->expectMsgId == MSGID_NEWKEYS);
CHECK(IsMessageAllowed(ssh, MSGID_IGNORE, WS_MSG_RECV));
CHECK(ssh->handshake->expectMsgId == MSGID_NEWKEYS);
}
ssh->ctx->side = WOLFSSH_ENDPOINT_SERVER;
ssh->isKeying = 0;
ssh->acceptState = ACCEPT_KEYED;
CHECK(IsMessageAllowed(ssh, MSGID_SERVICE_REQUEST, WS_MSG_RECV));
CHECK(!IsMessageAllowed(ssh, MSGID_USERAUTH_REQUEST, WS_MSG_RECV));
ssh->acceptState = ACCEPT_SERVER_USERAUTH_ACCEPT_SENT;
CHECK(IsMessageAllowed(ssh, MSGID_USERAUTH_REQUEST, WS_MSG_RECV));
CHECK(!IsMessageAllowed(ssh, MSGID_SERVICE_REQUEST, WS_MSG_RECV));
CHECK(!IsMessageAllowed(ssh, MSGID_USERAUTH_SUCCESS, WS_MSG_RECV));
CHECK(!IsMessageAllowed(ssh, MSGID_CHANNEL_OPEN, WS_MSG_RECV));
ssh->acceptState = ACCEPT_SERVER_USERAUTH_SENT;
CHECK(IsMessageAllowed(ssh, MSGID_CHANNEL_OPEN, WS_MSG_RECV));
CHECK(!IsMessageAllowed(ssh, MSGID_USERAUTH_REQUEST, WS_MSG_RECV));
CHECK(!IsMessageAllowed(ssh, MSGID_EXT_INFO, WS_MSG_RECV));
CHECK(SendExtInfo(ssh) == WS_NOT_COMPILED);
ssh->ctx->side = WOLFSSH_ENDPOINT_CLIENT;
ssh->connectState = CONNECT_KEYED;
CHECK(!IsMessageAllowed(ssh, MSGID_USERAUTH_FAILURE, WS_MSG_RECV));
CHECK(!IsMessageAllowed(ssh, MSGID_USERAUTH_SUCCESS, WS_MSG_RECV));
CHECK(!IsMessageAllowed(ssh, MSGID_CHANNEL_OPEN_CONF, WS_MSG_RECV));
ssh->connectState = CONNECT_CLIENT_USERAUTH_SENT;
CHECK(IsMessageAllowed(ssh, MSGID_USERAUTH_FAILURE, WS_MSG_RECV));
CHECK(IsMessageAllowed(ssh, MSGID_USERAUTH_SUCCESS, WS_MSG_RECV));
ssh->connectState = CONNECT_SERVER_USERAUTH_ACCEPT_DONE;
CHECK(!IsMessageAllowed(ssh, MSGID_USERAUTH_FAILURE, WS_MSG_RECV));
CHECK(IsMessageAllowed(ssh, MSGID_CHANNEL_OPEN_CONF, WS_MSG_RECV));
/* A wrong first_kex_packet_follows guess does not consume the real INIT. */
ssh->ctx->side = WOLFSSH_ENDPOINT_SERVER;
ssh->isKeying = WOLFSSH_PEER_IS_KEYING;
ssh->handshake->kexPacketFollows = 1;
ssh->handshake->kexIdGuess = ID_UNKNOWN;
ssh->handshake->kexId = ID_CURVE25519_SHA256;
ssh->handshake->expectMsgId = MSGID_KEXDH_INIT;
CHECK(IsMessageAllowed(ssh, MSGID_KEXDH_INIT, WS_MSG_RECV));
word32 idx = 0;
byte guessed[1] = {0};
CHECK(DoKexDhInit(ssh, guessed, sizeof(guessed), &idx) == WS_SUCCESS);
CHECK(idx == 1 && ssh->handshake->expectMsgId == MSGID_KEXDH_INIT);
CHECK(DoKexInit(ssh, guessed, sizeof(guessed), &idx) == WS_INVALID_STATE_E);
/* Real dispatch must fail before parsing malformed auth/channel payloads. */
for (side = WOLFSSH_ENDPOINT_SERVER; side <= WOLFSSH_ENDPOINT_CLIENT; ++side) {
ssh->ctx->side = side;
ssh->connectState = CONNECT_BEGIN;
for (msg = 0; msg < 256; ++msg) {
if (msg == MSGID_KEXINIT || (msg >= 1 && msg <= 4)) continue;
ssh->acceptState = ACCEPT_BEGIN;
ssh->isKeying = 0;
ssh->inputBuffer.idx = 0;
ssh->curSz = 8;
memset(ssh->inputBuffer.buffer, 0, 12);
ssh->inputBuffer.buffer[4] = 6;
ssh->inputBuffer.buffer[5] = msg;
CHECK(DoPacket(ssh, &consumed) == WS_MSGID_NOT_ALLOWED_E);
CHECK(consumed == 0);
}
}
ssh->ctx->side = WOLFSSH_ENDPOINT_SERVER;
}
static void newkeys(WOLFSSH_CTX* ctx)
{
int quota;
for (quota = 0; quota <= 16; ++quota) {
WOLFSSH* ssh = wolfSSH_new(ctx);
Sink sink = {.quota = quota, .chunk = 1};
CHECK(ssh != NULL);
CHECK(ssh->isKeying == 0);
wolfSSH_SetIOWriteCtx(ssh, &sink);
ssh->isKeying = WOLFSSH_SELF_IS_KEYING | WOLFSSH_PEER_IS_KEYING;
ssh->handshake->expectMsgId = MSGID_NEWKEYS;
ssh->handshake->encryptId = ID_AES128_GCM;
ssh->handshake->aeadMode = 1;
ssh->handshake->blockSz = 16;
ssh->handshake->keys.encKeySz = 16;
ssh->handshake->peerKeys.encKeySz = 16;
ssh->handshake->keys.ivSz = ssh->handshake->peerKeys.ivSz = 12;
CHECK(DoNewKeys(ssh, NULL, 0, NULL) == WS_INVALID_STATE_E);
int ret = SendNewKeys(ssh);
CHECK(ret == WS_SUCCESS || ret == WS_WANT_WRITE);
CHECK(ssh->isKeying == WOLFSSH_PEER_IS_KEYING);
CHECK(ssh->handshake->expectMsgId == MSGID_NEWKEYS);
word32 seq = ssh->seq;
sink.quota = -1;
CHECK(wolfSSH_SendPacket(ssh) == WS_SUCCESS);
CHECK(ssh->seq == seq);
CHECK(sink.bytes[5] == MSGID_NEWKEYS);
CHECK(sink.size == 16);
CHECK(ssh->outputBuffer.length == 0);
CHECK(IsMessageAllowed(ssh, MSGID_NEWKEYS, WS_MSG_RECV));
CHECK(DoNewKeys(ssh, NULL, 0, NULL) == WS_SUCCESS);
CHECK(ssh->isKeying == 0 && ssh->handshake == NULL);
CHECK(!IsMessageAllowed(ssh, MSGID_NEWKEYS, WS_MSG_RECV));
CHECK(DoNewKeys(ssh, NULL, 0, NULL) == WS_BAD_ARGUMENT);
wolfSSH_free(ssh);
}
WOLFSSH* ssh = wolfSSH_new(ctx);
Sink sink = {.fatal = 1, .chunk = 1};
wolfSSH_SetIOWriteCtx(ssh, &sink);
ssh->isKeying = WOLFSSH_SELF_IS_KEYING | WOLFSSH_PEER_IS_KEYING;
ssh->handshake->encryptId = ID_NONE;
CHECK(SendNewKeys(ssh) != WS_SUCCESS);
CHECK(ssh->isKeying & WOLFSSH_SELF_IS_KEYING);
wolfSSH_free(ssh);
ssh = wolfSSH_new(ctx);
CHECK(ssh != NULL);
wolfSSH_SetIOWriteCtx(ssh, &sink);
ssh->isKeying = WOLFSSH_SELF_IS_KEYING | WOLFSSH_PEER_IS_KEYING;
ssh->handshake->encryptId = ID_AES128_GCM;
ssh->handshake->keys.encKeySz = 15; /* invalid AES key length */
CHECK(SendNewKeys(ssh) != WS_SUCCESS);
CHECK(ssh->isKeying == (WOLFSSH_SELF_IS_KEYING | WOLFSSH_PEER_IS_KEYING));
ssh->isKeying = WOLFSSH_PEER_IS_KEYING;
ssh->handshake->peerKeys.encKeySz = 15;
CHECK(DoNewKeys(ssh, NULL, 0, NULL) == WS_CRYPTO_FAILED);
CHECK(ssh->handshake != NULL && ssh->isKeying == WOLFSSH_PEER_IS_KEYING);
wolfSSH_free(ssh);
}
static void shutdown_rekey(WOLFSSH_CTX* ctx)
{
const int errors[] = {WS_SUCCESS, WS_WANT_WRITE};
int keying, operation, pending, error;
for (keying = 1; keying <= 3; ++keying) {
for (operation = 0; operation < 3; ++operation) {
for (pending = 0; pending < 2; ++pending) {
for (error = 0; error < 2; ++error) {
WOLFSSH* ssh = wolfSSH_new(ctx);
Sink sink = {.quota = -1, .chunk = 1024};
CHECK(ssh != NULL);
wolfSSH_SetIOWriteCtx(ssh, &sink);
WOLFSSH_CHANNEL* channel = ChannelNew(ssh, ID_CHANTYPE_SESSION, 1024, 1024);
CHECK(channel != NULL);
channel->peerChannel = channel->channel;
CHECK(ChannelAppend(ssh, channel) == WS_SUCCESS);
if (pending) {
sink.quota = 0;
ssh->isKeying = WOLFSSH_SELF_IS_KEYING | WOLFSSH_PEER_IS_KEYING;
CHECK(SendNewKeys(ssh) == WS_WANT_WRITE);
sink.quota = -1;
}
ssh->isKeying = keying;
ssh->handshake->expectMsgId = MSGID_NEWKEYS;
word32 length = ssh->outputBuffer.length;
word32 index = ssh->outputBuffer.idx, seq = ssh->seq;
byte saved[64];
CHECK(length <= sizeof(saved));
memcpy(saved, ssh->outputBuffer.buffer, length);
int calls = sink.calls;
int repeat;
for (repeat = 0; repeat < 2; ++repeat) {
ssh->error = errors[error]; /* Including a stale WANT_WRITE. */
int ret = operation == 0 ? SendChannelEof(ssh, channel->peerChannel) :
operation == 1 ? wolfSSH_shutdown(ssh) :
SendChannelExit(ssh, channel->peerChannel, 0);
CHECK(ret == WS_MSGID_NOT_ALLOWED_E);
CHECK(ssh->error == WS_REKEYING);
CHECK(sink.size == 0 && sink.calls == calls);
CHECK(!channel->eofTxd && !channel->closeTxd);
CHECK(ssh->outputBuffer.length == length && ssh->outputBuffer.idx == index);
CHECK(memcmp(saved, ssh->outputBuffer.buffer, length) == 0);
CHECK(ssh->seq == seq && ssh->isKeying == keying);
CHECK(ssh->handshake->expectMsgId == MSGID_NEWKEYS);
}
/* Retry after keying: EOF is emitted once, not suppressed forever. */
ssh->isKeying = 0;
ssh->error = WS_SUCCESS;
CHECK(SendChannelEof(ssh, channel->peerChannel) == WS_SUCCESS);
CHECK(channel->eofTxd && !channel->closeTxd);
CHECK(sink.size == length + 16 && sink.bytes[length + 5] == MSGID_CHANNEL_EOF);
calls = sink.calls;
CHECK(SendChannelEof(ssh, channel->peerChannel) == WS_SUCCESS);
CHECK(sink.calls == calls);
wolfSSH_free(ssh);
}
}
}
}
puts("PASS: real EOF/shutdown/exit rekey fences, pending output/stale WANT_WRITE, zero bytes and no channel mutation");
}
#include "interop.c"
#include "paired.c"
int main(int argc, char** argv)
{
if (argc > 1) return serve(argc, argv);
CHECK(wolfSSH_Init() == WS_SUCCESS);
WOLFSSH_CTX* ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
CHECK(ctx != NULL);
policy(ctx);
wolfSSH_SetIOSend(ctx, send_test);
WOLFSSH* ssh = wolfSSH_new(ctx);
CHECK(ssh != NULL && ssh->handshake != NULL);
gates(ssh);
wolfSSH_free(ssh);
newkeys(ctx);
shutdown_rekey(ctx);
paired();
wolfSSH_CTX_free(ctx);
wolfSSH_Cleanup();
printf("PASS: %u real generated ordering/dispatch/NEWKEYS checks\n", checks);
return 0;
}
+175
View File
@@ -0,0 +1,175 @@
/* Test-only stdio server; no sockets, persistent keys, or production auth. */
#include <fcntl.h>
#include <signal.h>
#include <execinfo.h>
static void trap_trace(int signum)
{
void* trace[32];
int count = backtrace(trace, 32);
backtrace_symbols_fd(trace, count, 2);
_exit(128 + signum);
}
static byte authorized[2048];
static size_t authorizedSz;
static int keyings, signedAuth, passwordAuth, writes, stalls, fragment, transportEof;
static int auth_test(byte type, WS_UserAuthData* data, void* context)
{
(void)context;
if (type == WOLFSSH_USERAUTH_PASSWORD &&
data->sf.password.passwordSz == 19 &&
memcmp(data->sf.password.password, "order-test-password", 19) == 0) {
++passwordAuth;
return WOLFSSH_USERAUTH_SUCCESS;
}
if (type == WOLFSSH_USERAUTH_PUBLICKEY &&
data->sf.publicKey.publicKeySz == authorizedSz &&
memcmp(data->sf.publicKey.publicKey, authorized, authorizedSz) == 0) {
if (data->sf.publicKey.hasSignature) ++signedAuth;
return WOLFSSH_USERAUTH_SUCCESS;
}
return WOLFSSH_USERAUTH_FAILURE;
}
static void keyed_test(void* context) { (void)context; ++keyings; }
static int stdio_recv(WOLFSSH* ssh, void* buffer, word32 size, void* context)
{
(void)ssh; (void)context;
if (fragment && size > 31) size = 31;
int ret = (int)read(0, buffer, size);
if (ret > 0) return ret;
if (ret < 0 && (errno == EAGAIN || errno == EINTR))
return WS_CBIO_ERR_WANT_READ;
if (ret == 0) transportEof = 1;
return WS_CBIO_ERR_CONN_CLOSE;
}
static int stdio_send(WOLFSSH* ssh, void* buffer, word32 size, void* context)
{
(void)ssh; (void)context;
if (fragment && ++writes % 3 == 0) {
++stalls;
return WS_CBIO_ERR_WANT_WRITE;
}
if (fragment && size > 37) size = 37;
int ret = (int)write(1, buffer, size);
if (ret > 0) return ret;
if (ret < 0 && (errno == EAGAIN || errno == EINTR))
return WS_CBIO_ERR_WANT_WRITE;
return WS_CBIO_ERR_GENERAL;
}
static int retry(int ret, WOLFSSH* ssh)
{
int error = ret == WS_FATAL_ERROR ? wolfSSH_get_error(ssh) : ret;
if (error == WS_WANT_READ || error == WS_WANT_WRITE || error == WS_REKEYING ||
error == WS_CHAN_RXD || error == WS_SUCCESS) return 1;
fprintf(stderr, "server failure ret=%d err=%d accept=%d keying=%d expected=%d\n",
ret, error, ssh->acceptState, ssh->isKeying,
ssh->handshake ? ssh->handshake->expectMsgId : -1);
return 0;
}
static int serve(int argc, char** argv)
{
byte key[4096], buffer[4096];
size_t keySz, sent = 0, length = 0, total = 0;
int triggered = 0, channelClosed = 0;
CHECK(argc == 6);
fragment = atoi(argv[4]);
int initiate = atoi(argv[5]);
alarm(30);
signal(SIGILL, trap_trace);
signal(SIGSEGV, trap_trace);
FILE* file = fopen(argv[2], "rb"); CHECK(file != NULL);
keySz = fread(key, 1, sizeof(key), file); fclose(file);
file = fopen(argv[3], "rb"); CHECK(file != NULL);
authorizedSz = fread(authorized, 1, sizeof(authorized), file); fclose(file);
CHECK(fcntl(0, F_SETFL, O_NONBLOCK) == 0);
CHECK(fcntl(1, F_SETFL, O_NONBLOCK) == 0);
CHECK(wolfSSH_Init() == 0);
WOLFSSH_CTX* ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
CHECK(ctx != NULL);
policy(ctx);
CHECK(wolfSSH_CTX_UsePrivateKey_buffer(ctx, key, keySz, WOLFSSH_FORMAT_ASN1) == 0);
wolfSSH_SetUserAuth(ctx, auth_test);
wolfSSH_SetKeyingCompletionCb(ctx, keyed_test);
wolfSSH_SetIORecv(ctx, stdio_recv);
wolfSSH_SetIOSend(ctx, stdio_send);
WOLFSSH* ssh = wolfSSH_new(ctx); CHECK(ssh != NULL);
int ret;
while ((ret = wolfSSH_accept(ssh)) != WS_SUCCESS) {
CHECK(retry(ret, ssh));
usleep(100);
}
CHECK(keyings == 1 && (signedAuth == 1 || passwordAuth == 1));
CHECK(ssh->sendExtInfo == 0 && ssh->extInfoSent == 0);
for (;;) {
if (ssh->outputBuffer.length) {
ret = wolfSSH_SendPacket(ssh);
CHECK(retry(ret, ssh));
if (ret != WS_SUCCESS) { usleep(100); continue; }
}
if (initiate && !triggered && total >= 65536 && !ssh->isKeying) {
triggered = 1;
ret = wolfSSH_TriggerKeyExchange(ssh);
CHECK(retry(ret, ssh));
}
if (ssh->isKeying) {
ret = wolfSSH_worker(ssh, NULL);
CHECK(retry(ret, ssh));
usleep(100);
continue;
}
if (sent < length) {
ret = wolfSSH_stream_send(ssh, buffer + sent, length - sent);
if (ret > 0) { sent += ret; total += ret; }
else { CHECK(retry(ret, ssh)); usleep(100); }
continue;
}
ret = wolfSSH_stream_read(ssh, buffer, sizeof(buffer));
if (ret == WS_EOF || (ssh->channelList && ssh->channelList->eofRxd)) break;
if (ret > 0) { length = ret; sent = 0; }
else { CHECK(retry(ret, ssh)); usleep(100); }
}
CHECK(total == 262144);
CHECK(keyings >= 2);
CHECK(!fragment || stalls > 0);
CHECK(ssh->sendExtInfo == 0 && ssh->extInfoSent == 0);
/* Queue shutdown once, then finish IO without re-enqueuing exit-status.
* WANT_READ is not channel-close completion. Keep the proxy read end alive
* until OpenSSH has also finished writing its transport disconnect. */
fragment = 0;
ret = wolfSSH_shutdown(ssh);
if (ret == WS_CHANNEL_CLOSED) channelClosed = 1;
else CHECK(retry(ret, ssh));
while (ssh->channelList || ssh->outputBuffer.length) {
ret = ssh->outputBuffer.length ? wolfSSH_SendPacket(ssh) :
wolfSSH_worker(ssh, NULL);
if (ret == WS_CHANNEL_CLOSED) {
CHECK(ssh->channelList == NULL);
channelClosed = 1;
}
else CHECK(retry(ret, ssh));
usleep(100);
}
CHECK(channelClosed && !transportEof);
while (!transportEof) {
ret = wolfSSH_worker(ssh, NULL);
if (transportEof) {
int error = ret == WS_FATAL_ERROR ? wolfSSH_get_error(ssh) : ret;
CHECK(error == WS_SOCKET_ERROR_E && ssh->isClosed && !ssh->connReset);
}
else CHECK(retry(ret, ssh));
usleep(100);
}
CHECK(!ssh->isKeying && !ssh->outputBuffer.length);
fprintf(stderr, "INTEROP PASS keyings=%d signed=%d password=%d stalls=%d bytes=%zu channel_closed=1 transport_eof=1\n",
keyings, signedAuth, passwordAuth, stalls, total);
wolfSSH_free(ssh);
wolfSSH_CTX_free(ctx);
wolfSSH_Cleanup();
return 0;
}
+115
View File
@@ -0,0 +1,115 @@
/* Real generated client/server state machines over bounded nonblocking queues. */
typedef struct Pipe {
byte bytes[65536];
word32 read, length;
unsigned calls, stalls;
} Pipe;
static int pipe_send(WOLFSSH* ssh, void* data, word32 size, void* context)
{
Pipe* pipe = context;
(void)ssh;
if (++pipe->calls % 3 == 0) {
++pipe->stalls;
return WS_CBIO_ERR_WANT_WRITE;
}
if (size > 13) size = 13;
CHECK(size <= sizeof(pipe->bytes) - pipe->length);
memcpy(pipe->bytes + pipe->length, data, size);
pipe->length += size;
return size;
}
static int pipe_recv(WOLFSSH* ssh, void* data, word32 size, void* context)
{
Pipe* pipe = context;
(void)ssh;
word32 available = pipe->length - pipe->read;
if (!available) return WS_CBIO_ERR_WANT_READ;
if (size > available) size = available;
if (size > 11) size = 11;
memcpy(data, pipe->bytes + pipe->read, size);
pipe->read += size;
if (pipe->read == pipe->length) pipe->read = pipe->length = 0;
return size;
}
static int client_auth(byte type, WS_UserAuthData* data, void* context)
{
(void)context;
if (type != WOLFSSH_USERAUTH_PASSWORD) return WOLFSSH_USERAUTH_FAILURE;
data->sf.password.password = (const byte*)"order-test-password";
data->sf.password.passwordSz = 19;
return WOLFSSH_USERAUTH_SUCCESS;
}
static void paired(void)
{
const char* algorithms[] = {"curve25519-sha256", "ecdh-sha2-nistp256"};
int algorithm;
for (algorithm = 0; algorithm < 2; ++algorithm) {
WOLFSSH_CTX* serverCtx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
WOLFSSH_CTX* clientCtx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
CHECK(serverCtx && clientCtx);
policy(serverCtx); policy(clientCtx);
CHECK(wolfSSH_CTX_SetAlgoListKex(clientCtx, algorithms[algorithm]) == 0);
CHECK(wolfSSH_CTX_SetAlgoListCipher(clientCtx, "aes256-gcm@openssh.com") == 0);
WC_RNG rng;
ecc_key key;
byte der[1024];
CHECK(wc_InitRng(&rng) == 0);
CHECK(wc_ecc_init(&key) == 0);
CHECK(wc_ecc_make_key(&rng, 32, &key) == 0);
int derSz = wc_EccKeyToDer(&key, der, sizeof(der));
CHECK(derSz > 0);
CHECK(wolfSSH_CTX_UsePrivateKey_buffer(serverCtx, der, derSz, WOLFSSH_FORMAT_ASN1) == 0);
wc_ecc_free(&key); wc_FreeRng(&rng);
wolfSSH_SetUserAuth(serverCtx, auth_test);
wolfSSH_SetUserAuth(clientCtx, client_auth);
wolfSSH_SetIOSend(serverCtx, pipe_send); wolfSSH_SetIORecv(serverCtx, pipe_recv);
wolfSSH_SetIOSend(clientCtx, pipe_send); wolfSSH_SetIORecv(clientCtx, pipe_recv);
WOLFSSH* server = wolfSSH_new(serverCtx);
WOLFSSH* client = wolfSSH_new(clientCtx);
Pipe toServer = {0}, toClient = {0};
CHECK(server && client);
CHECK(wolfSSH_SetUsername(client, "order-test") == 0);
wolfSSH_SetIOWriteCtx(client, &toServer); wolfSSH_SetIOReadCtx(server, &toServer);
wolfSSH_SetIOWriteCtx(server, &toClient); wolfSSH_SetIOReadCtx(client, &toClient);
int clientDone = 0, serverDone = 0, i, ret;
for (i = 0; i < 100000 && !(clientDone && serverDone); ++i) {
if (!clientDone) {
ret = wolfSSH_connect(client);
if (ret == 0) clientDone = 1;
else CHECK(retry(ret, client));
}
if (!serverDone) {
ret = wolfSSH_accept(server);
if (ret == 0) serverDone = 1;
else CHECK(retry(ret, server));
}
}
CHECK(clientDone && serverDone);
CHECK(client->handshake == NULL && server->handshake == NULL);
CHECK(client->sendExtInfo == 0 && server->sendExtInfo == 0);
CHECK(client->peerSigId == NULL && client->peerSigIdSz == 0);
/* Both initiation directions and simultaneous initiation. */
int round;
for (round = 0; round < 3; ++round) {
if (round != 1) CHECK(retry(wolfSSH_TriggerKeyExchange(server), server));
if (round != 0) CHECK(retry(wolfSSH_TriggerKeyExchange(client), client));
for (i = 0; i < 100000; ++i) {
CHECK(retry(wolfSSH_worker(server, NULL), server));
CHECK(retry(wolfSSH_worker(client, NULL), client));
if (!server->isKeying && !client->isKeying &&
!toServer.length && !toClient.length) break;
}
CHECK(i < 100000);
CHECK(client->handshake == NULL && server->handshake == NULL);
CHECK(client->sendExtInfo == 0 && server->sendExtInfo == 0);
}
CHECK(toServer.stalls > 0 && toClient.stalls > 0);
wolfSSH_free(client); wolfSSH_free(server);
wolfSSH_CTX_free(clientCtx); wolfSSH_CTX_free(serverCtx);
}
puts("PASS: real generated client/server, both KEX, AES256, fragmented initial KEX and three rekey directions");
}
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""Exercise installed PlatformIO ESP-IDF flag sorting and SCons deduplication.
Uses the real configured CMake file API and target compiler. Never runs pio or
writes build artifacts; compiler outputs and the split-option mutation are temp.
"""
import ast
import json
import os
from pathlib import Path
import shlex
import subprocess
import sys
import tempfile
import click
ROOT = Path(__file__).resolve().parents[2]
def main():
platformio = Path.home() / '.platformio'
adapter = platformio / 'platforms/espressif32/builder/frameworks/espidf.py'
tree = ast.parse(adapter.read_text())
functions = [node for node in tree.body if isinstance(node, ast.FunctionDef)
and node.name == 'get_app_flags']
assert len(functions) == 1
scope = {'click': click}
exec(compile(ast.Module(body=functions, type_ignores=[]), str(adapter), 'exec'), scope)
scons = list((platformio / 'packages/tool-scons').glob('scons-local-*'))
assert len(scons) == 1
sys.path.insert(0, str(scons[0]))
from SCons.Script import Environment
build = ROOT / '.pio/build/esp32-s3-devkitc-1-n16r8'
replies = build / '.cmake/api/v1/reply'
targets = [json.loads(path.read_text()) for path in replies.glob('target-*.json')]
matches = [target for target in targets if target['name'] == '__idf_src']
assert len(matches) == 1, 'requires configured PlatformIO app target'
app = matches[0]
groups = [group for group in app['compileGroups'] if group['language'] == 'C']
assert len(groups) == 1
group = groups[0]
defaults = {'compileGroups': [{'language': lang, 'compileCommandFragments': []}
for lang in ('C', 'CXX', 'ASM')]}
commands = json.loads((build / 'compile_commands.json').read_text())
main_command = next(entry for entry in commands if entry['file'].endswith('/src/main.c'))
compiler = (main_command.get('arguments') or shlex.split(main_command['command']))[0]
overlay = str(build / 'security_overrides/wolfssh_include/wolfssh/internal.h')
crypto = str(ROOT / 'cmake/wolf_crypto_policy.h')
defines = ['-D' + item['define'] for item in group.get('defines', [])]
includes = ['-I' + item['path'] for item in group.get('includes', [])]
flags = scope['get_app_flags'](app, defaults)['CFLAGS']
for header in (overlay, crypto):
assert '-include' + header in flags, header
assert header not in flags, 'orphan header operand'
assert '-include' not in flags
env = Environment(tools=[])
env.AppendUnique(CFLAGS=flags)
env.AppendUnique(CFLAGS=flags)
assert list(env['CFLAGS']) == flags
# Component path uses ParseFlags/AppendUnique rather than get_app_flags.
component_env = Environment(tools=[])
for header in (crypto, overlay, crypto, overlay):
component_env.AppendUnique(**component_env.ParseFlags('-include' + header))
assert list(component_env['CCFLAGS']) == ['-include' + crypto, '-include' + overlay]
with tempfile.TemporaryDirectory(prefix='pio-forced-include-') as temp:
temp = Path(temp)
source = temp / 'consumer.c'
source.write_text('''#if SAK_WOLFSSH_ORDER_ABI != 20260916
#error missing_shared_ssh_ABI
#endif
#if !defined(WOLFSSL_VALIDATE_ECC_IMPORT) || !defined(WOLFSSL_ECDHX_SHARED_NOT_ZERO)
#error missing_crypto_guards
#endif
int consumer(void) { return 0; }
''')
command = [compiler, *env['CFLAGS'], *defines, *includes, '-c', str(source), '-o', str(temp / 'consumer.o')]
result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, timeout=30,
env={**os.environ, 'CCACHE_DISABLE': '1'})
assert result.returncode == 0, result.stderr
assert (temp / 'consumer.o').is_file()
# Prove this catches the historical split-option failure using the same
# installed adapter, not a hand-written approximation of its sorting.
bad = json.loads(json.dumps(app))
count = 0
for cg in bad['compileGroups']:
for fragment in cg['compileCommandFragments']:
text = fragment['fragment']
if '-include' + overlay in text:
fragment['fragment'] = text.replace('-include' + overlay, '-include ' + overlay)
count += 1
assert count > 0
broken_flags = scope['get_app_flags'](bad, defaults)['CFLAGS']
result = subprocess.run([compiler, *broken_flags, *defines, *includes, '-c', str(source),
'-o', str(temp / 'broken.o')], cwd=ROOT, capture_output=True,
text=True, timeout=30, env={**os.environ, 'CCACHE_DISABLE': '1'})
assert result.returncode != 0 and 'multiple files' in result.stderr, result.stderr
print('PASS: installed PlatformIO get_app_flags + SCons AppendUnique/ParseFlags; '
'real Xtensa -c/-o consumer; split-include mutation reproduces multiple-input failure')
if __name__ == '__main__':
main()
+314
View File
@@ -0,0 +1,314 @@
#!/usr/bin/env python3
"""Offline real generated wolfSSH/real wolfCrypt ordering and interoperability tests.
No PlatformIO, device, managed writes, or IP network. OpenSSH uses a local Unix socket.
"""
import argparse
import array
import socket
import hashlib
import base64
import shlex
import resource
import re
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
sys.dont_write_bytecode = True
ROOT = Path(__file__).resolve().parents[2]
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(ROOT / 'tools'))
import security_overrides as sdk
SSH = ROOT / 'managed_components/wolfssl__wolfssh'
SSL = ROOT / 'managed_components/wolfssl__wolfssl'
ENV = {**os.environ, 'CCACHE_DISABLE': '1'}
def run(args, **kw):
return subprocess.run([str(x) for x in args], check=True, timeout=120,
env=ENV, **kw)
def provenance():
directory = ROOT / 'tools/wolfssh_order'
records = json.loads((directory / 'provenance.json').read_text())
assert set(records) == {'793', '819', '840', '855', '921'}
for number, record in records.items():
raw = (directory / (number + '.patch')).read_bytes()
assert hashlib.sha256(raw).hexdigest() == record['sha256']
assert re.findall(rb'^From ([0-9a-f]{40}) Mon Sep', raw, re.M) == [
commit.encode() for commit in record['commits']]
assert set(sdk.WOLFSSH_ORDER_PLAN) == {'src/internal.c', 'src/ssh.c', 'wolfssh/internal.h'}
for path, record in sdk.WOLFSSH_ORDER_PLAN.items():
entry = next(e for e in sdk.ENTRIES if e.source == 'managed_components/wolfssl__wolfssh/' + path)
assert record['sha256'] == entry.sha256
assert hashlib.sha256((SSH / 'src/log.c').read_bytes()).hexdigest() == \
'66e5f053af05a103aca997fbcae11a52acd8f79c9c97645f0d0cd390e192135a'
assert hashlib.sha256((SSH / 'wolfssh/log.h').read_bytes()).hexdigest() == \
'4a3e71f8148b0e6ecd533b949e2f9574449561bda21bc200163a1e53bfdb0bd2'
print('PASS: archived upstream patch hashes/commits; exact source/header pins; unchanged logging ABI')
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--interop', action='store_true')
parser.add_argument('--interop-repeat', type=int, default=1)
parser.add_argument('--target-contracts', action='store_true')
parser.add_argument('--pio-adapter', action='store_true')
parser.add_argument('--proxy-socket', type=Path, help=argparse.SUPPRESS)
options = parser.parse_args()
if options.proxy_socket:
with socket.socket(socket.AF_UNIX) as connection:
connection.connect(str(options.proxy_socket))
with socket.socket(fileno=1) as output:
output.sendmsg([b'\0'], [(socket.SOL_SOCKET, socket.SCM_RIGHTS,
array.array('i', [connection.fileno()]))])
return
if not 1 <= options.interop_repeat <= 20:
parser.error('--interop-repeat must be between 1 and 20')
provenance()
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
with tempfile.TemporaryDirectory(prefix='wolfssh-order-') as tmp:
work = Path(tmp)
for entry in sdk.ENTRIES:
if entry.component != 'wolfssl__wolfssh':
continue
_, data = sdk.render_entry(entry, {'project': ROOT})
dest = (work / 'wolfssh/internal.h' if entry.header else
work / Path(entry.source).name)
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_bytes(data)
flags = ['cc', '-std=gnu11', '-O1', '-g', '-DWOLFSSL_USER_SETTINGS',
'-DHAVE_WC_ECC_SET_RNG', '-ffunction-sections', '-fdata-sections',
'-fsanitize=undefined', '-fsanitize-undefined-trap-on-error', '-no-pie',
'-I' + str(HERE), '-I' + str(SSH), '-I' + str(work), '-I' + str(SSL),
'-include', str(work / 'wolfssh/internal.h')]
stale = subprocess.run([*flags[:-2], '-include', str(SSH / 'wolfssh/internal.h'),
*flags[-2:], '-x', 'c', '-fsyntax-only', '-'],
input='', text=True, capture_output=True, timeout=30, env=ENV)
assert stale.returncode != 0 and 'stale wolfSSH internal.h' in stale.stderr
print('PASS: vendor-first forced-header ABI conflict rejected')
crypto = ('aes asn coding curve25519 ecc ed25519 fe_low_mem ge_low_mem '
'hash hmac kdf logging memory random sha256 sha512 signature '
'tfm wc_port wolfmath').split()
sources = [work / 'ssh.c', *[SSH / 'src' / (s + '.c') for s in ('io', 'log', 'port')],
*[SSL / 'wolfcrypt/src' / (s + '.c') for s in crypto]]
objects = []
for i, source in enumerate(sources):
obj = work / f'{i}.o'
run([*flags, '-c', source, '-o', obj], capture_output=True)
objects.append(obj)
binary = work / 'contract'
run([*flags, HERE / 'contract.c', *objects, '-Wl,--gc-sections', '-o', binary])
run([binary])
mutations(work, flags, objects)
if options.interop:
for iteration in range(options.interop_repeat):
print(f'INTEROP matrix {iteration + 1}/{options.interop_repeat}', flush=True)
interop(work, binary)
if options.target_contracts:
target_contracts(work)
if options.pio_adapter:
run([sys.executable, HERE / 'pio_adapter.py'])
print('PASS: generated pinned source/header ordering profile (host, not firmware)')
def target_contracts(work):
"""Explicit candidate replay, not a firmware reconfigure/build claim."""
original_db = ROOT / '.pio/build/esp32-s3-devkitc-1-n16r8/compile_commands.json'
entries = json.loads(original_db.read_text())
replacements = {}
for entry in sdk.ENTRIES:
if entry.component != 'wolfssl__wolfssh':
continue
_, data = sdk.render_entry(entry, {'project': ROOT})
dest = (work / 'security_overrides/wolfssh_include/wolfssh/internal.h' if entry.header
else work / 'security_overrides' / entry.name / Path(entry.source).name)
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_bytes(data)
if entry.header:
overlay = dest
else:
replacements[entry.name] = dest
for entry in entries:
source = Path(entry['file'])
if not source.is_absolute():
source = Path(entry['directory']) / source
args = entry.get('arguments') or shlex.split(entry['command'])
dest = None
if source.name == 'internal.c' and 'wolfssh_internal' in source.parts:
dest = replacements['wolfssh_internal']
if source.name == 'ssh.c' and ('wolfssl__wolfssh' in source.parts or 'wolfssh_ssh' in source.parts):
dest = replacements['wolfssh_ssh']
if dest:
args = [str(dest) if arg == entry['file'] or arg == str(source) else arg for arg in args]
entry['file'] = str(dest)
args = [(('-include' if arg.startswith('-include') else '') + str(overlay))
if arg.endswith('/security_overrides/wolfssh_include/wolfssh/internal.h')
else arg for arg in args]
# Same ABI overlay for all candidate consumers. Original compile flags,
# crypto policy and feature settings are preserved, not synthesized.
if any(arg.startswith('-I') and 'wolfssl__wolfssh' in arg for arg in args):
args += ['-include' + str(overlay)]
entry['arguments'] = args
entry.pop('command', None)
database = work / 'candidate-compile_commands.json'
database.write_text(json.dumps(entries))
print('CANDIDATE ONLY: replaying existing target flags with freshly generated SSH sources/header; '
'no production build-tree changes', flush=True)
syntax_sources = []
for entry in entries:
if (entry['file'] not in map(str, replacements.values()) and
not entry['file'].endswith(('/src/ssh_transport.c', '/src/ssh_security.c'))):
continue
command = []
skip = False
for arg in entry['arguments']:
if skip:
skip = False
elif arg in ('-o', '-MF', '-MT', '-MQ'):
skip = True
elif arg not in ('-c', '-MD', '-MMD', '-MP'):
command.append(arg)
run([*command, '-fsyntax-only'], cwd=entry['directory'], capture_output=True)
syntax_sources.append(entry['file'])
assert len(syntax_sources) == len(set(syntax_sources)) == 4, syntax_sources
print('PASS: candidate Xtensa syntax for generated internal.c/ssh.c and SSH application consumers')
for suite in ('ssh_protocol_policy', 'wolf_crypto_policy', 'wolfssh_auth_contract'):
run([sys.executable, ROOT / 'tests' / suite / 'run.py', '--compile-commands', database])
print('PASS: candidate target source-contract replay; NOT firmware/build registration evidence')
def mutations(work, flags, objects):
source = work / 'internal.c'
original = source.read_text()
cases = (
('missing EOF rekey guard',
' if (ret == WS_SUCCESS) {\n'
' if (!IsMessageAllowed(ssh, MSGID_CHANNEL_EOF, WS_MSG_SEND)) {\n'
' ret = WS_MSGID_NOT_ALLOWED_E;\n }\n }\n\n', ''),
('preauth injection', ' if (state != WS_MSG_RECV)\n',
' if (msg == MSGID_USERAUTH_FAILURE) return 1;\n if (state != WS_MSG_RECV)\n'),
('wrong expected KEX', 'ssh->handshake->expectMsgId == msg)',
'ssh->handshake->expectMsgId != msg)'),
('unnegotiated EXT_INFO', 'if (msg == MSGID_EXT_INFO)\n goto reject;',
'if (msg == MSGID_EXT_INFO)\n return 1;'),
('NEWKEYS clears peer too', 'ssh->isKeying &= ~WOLFSSH_SELF_IS_KEYING;',
'ssh->isKeying = 0;'),
('peer NEWKEYS before local', '(ssh->isKeying & WOLFSSH_SELF_IS_KEYING) ||\n ', ''),
('PR921 missing server expectation',
' if (ret == WS_SUCCESS) {\n ssh->handshake->expectMsgId = MSGID_NEWKEYS;\n'
' WLOG_EXPECT_MSGID(ssh->handshake->expectMsgId);\n ret = SendNewKeys(ssh);\n }\n\n'
' if (ret != WS_WANT_WRITE && ret != WS_SUCCESS)',
' if (ret == WS_SUCCESS) {\n ret = SendNewKeys(ssh);\n }\n\n'
' if (ret != WS_WANT_WRITE && ret != WS_SUCCESS)'),
)
try:
for label, old, new in cases:
assert original.count(old) == 1, label
source.write_text(original.replace(old, new))
binary = work / 'mutation'
run([*flags, HERE / 'contract.c', *objects, '-Wl,--gc-sections', '-o', binary], capture_output=True)
result = subprocess.run([str(binary)], capture_output=True, timeout=30, env=ENV)
assert result.returncode != 0, 'undetected ordering mutation: ' + label
finally:
source.write_text(original)
print(f'PASS: {len(cases)} ordering/NEWKEYS/PR921 guard-removal mutations rejected')
def interop(work, binary):
run(['openssl', 'ecparam', '-name', 'prime256v1', '-genkey', '-noout',
'-outform', 'DER', '-out', work / 'host.der'], capture_output=True)
for kind in ('ed25519', 'ecdsa'):
(work / kind).unlink(missing_ok=True)
(work / (kind + '.pub')).unlink(missing_ok=True)
run(['ssh-keygen', '-q', '-t', kind, '-N', '', '-C', 'order-test',
'-f', work / kind], capture_output=True)
askpass = work / 'askpass'
askpass.write_text('#!/bin/sh\nprintf "%s\\n" "order-test-password"\n')
askpass.chmod(0o700)
payload = bytes(range(256)) * 1024
for kex in ('curve25519-sha256', 'ecdh-sha2-nistp256'):
for auth in ('ed25519', 'ecdsa', 'password'):
key = work / ('ecdsa' if auth == 'password' else auth)
(work / 'authorized').write_bytes(base64.b64decode(
key.with_suffix('.pub').read_text().split()[1]))
for fragment, initiate in ((0, 0), (1, 1)):
endpoint = work / 'interop.sock'
proxy = ' '.join(shlex.quote(str(x)) for x in
(sys.executable, HERE / 'run.py', '--proxy-socket', endpoint))
server_args = [str(x) for x in
(binary, '--stdio', work / 'host.der', work / 'authorized', fragment, initiate)]
args = ['ssh', '-F', '/dev/null', '-T', '-vv', '-o', 'StrictHostKeyChecking=no',
'-o', 'UserKnownHostsFile=/dev/null', '-o', 'GlobalKnownHostsFile=/dev/null',
'-o', 'IdentityAgent=none', '-o', 'IdentitiesOnly=yes',
'-o', 'HostKeyAlgorithms=ecdsa-sha2-nistp256',
'-o', 'KexAlgorithms=' + kex, '-o', 'Ciphers=aes128-gcm@openssh.com',
'-o', 'RekeyLimit=' + ('1G' if initiate else '32K'),
'-o', 'ProxyCommand=' + proxy, '-o', 'ProxyUseFdpass=yes',
'-o', 'NumberOfPasswordPrompts=1',
'-o', 'PreferredAuthentications=' + ('password' if auth == 'password' else 'publickey'),
'-i', str(key), 'order-test@stdio.invalid']
env = {**ENV, 'SSH_ASKPASS': str(askpass), 'SSH_ASKPASS_REQUIRE': 'force', 'DISPLAY': ':order-test'}
# Own/reap the server independently: OpenSSH kills its proxy at
# exit. The proxy only passes a local Unix socket, not the server.
with socket.socket(socket.AF_UNIX) as listener, tempfile.TemporaryFile() as server_log:
listener.bind(str(endpoint))
listener.listen(1)
listener.settimeout(10)
client = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, env=env)
server = None
try:
connection, _ = listener.accept()
with connection:
server = subprocess.Popen(server_args, stdin=connection, stdout=connection,
stderr=server_log, env=env)
stdout, stderr = client.communicate(payload, timeout=45)
server_rc = server.wait(timeout=5)
finally:
for process in (client, server):
if process is not None and process.poll() is None:
process.kill()
process.wait(timeout=5)
endpoint.unlink(missing_ok=True)
server_log.seek(0)
server_text = server_log.read().decode(errors='replace')
result = subprocess.CompletedProcess(args, client.returncode, stdout, stderr)
client_log = stderr.decode(errors='replace')
log = client_log + '\nSERVER:\n' + server_text
evidence = re.fullmatch(
r'INTEROP PASS keyings=(\d+) signed=(\d+) password=(\d+) stalls=(\d+) '
r'bytes=262144 channel_closed=1 transport_eof=1\n', server_text)
valid = (evidence is not None and int(evidence[1]) >= 2 and
(int(evidence[2]), int(evidence[3])) ==
((0, 1) if auth == 'password' else (1, 0)) and
(not fragment or int(evidence[4]) > 0) and
'rtype exit-status reply 0' in client_log and
'channel 0: rcvd close' in client_log and
'Exit status 0' in client_log)
if result.returncode or server_rc or result.stdout != payload or not valid:
addresses = re.findall(r'\[(0x[0-9a-f]+)\]', log)
if addresses:
run(['addr2line', '-f', '-e', binary, *addresses])
raise AssertionError(f'{kex}/{auth}/{fragment}/{initiate}: rc={result.returncode} server_rc={server_rc} '
f'output={len(result.stdout)}\n{log}')
assert 'SSH2_MSG_EXT_INFO received' not in log
assert 'server-sig-algs=<' not in log
print(f'PASS: OpenSSH {kex}/{auth} fragment={fragment} server-rekey={initiate}; '
+ next(line for line in log.splitlines() if 'INTEROP PASS' in line))
if __name__ == '__main__':
try:
main()
except subprocess.CalledProcessError as error:
if error.stdout:
print(error.stdout.decode() if isinstance(error.stdout, bytes) else error.stdout)
if error.stderr:
print(error.stderr.decode() if isinstance(error.stderr, bytes) else error.stderr)
raise
@@ -0,0 +1,43 @@
/* Host test profile: real generated wolfSSH + real wolfCrypt, not ESP-IDF. */
#ifndef ORDER_TEST_SETTINGS_H
#define ORDER_TEST_SETTINGS_H
#include <strings.h>
#define WOLFCRYPT_ONLY
#define WOLFSSL_WOLFSSH
/* Use the portable bytewise encoding path under alignment sanitization. */
#define WOLFSSL_USE_ALIGN
#define SINGLE_THREADED
#define USE_FAST_MATH
#define TFM_NO_ASM
#define TFM_TIMING_RESISTANT
#define WOLFSSL_SMALL_STACK
#define NO_RSA
#define NO_DH
#define NO_DSA
#define NO_MD5
#define NO_SHA
#define NO_SHA224
#define NO_DES3
#define NO_RC4
#define NO_HC128
#define NO_RABBIT
#define NO_PSK
#define NO_PWDBASED
#define NO_PKCS12
#define NO_CERTS
#define WOLFSSL_ASN_TEMPLATE
#define HAVE_ECC
#define ECC_TIMING_RESISTANT
#define HAVE_ECC_CHECK_KEY
#define WOLFSSL_VALIDATE_ECC_IMPORT
#define HAVE_CURVE25519
#define CURVE25519_SMALL
#define WOLFSSL_ECDHX_SHARED_NOT_ZERO
#define HAVE_ED25519
#define ED25519_SMALL
#define WOLFSSL_SHA512
#define WOLFSSL_ED25519_STREAMING_VERIFY
#define HAVE_AESGCM
#define WOLFSSH_NO_AES_CBC
#define WOLFSSH_NO_AES_CTR
#endif
+4 -3
View File
@@ -20,9 +20,10 @@ original, generated = render_entry(entry, {'project': ROOT})
names = ('GetUint32', 'GetSize', 'GetString', 'GetSkip', 'GetStringRef',
'DoIgnore', 'DoServiceRequest', 'DoChannelWindowAdjust', 'DoUserAuthRequestEcc',
'DoUserAuthRequestEd25519')
# Verify this slice cannot accidentally change ordering or existing password logic.
# Parser edits must not change the independently applied ordering/password logic.
from security_overrides import apply_edits, MODIFICATION_NOTICE, WOLFSSH_PARSER_EDITS
baseline = MODIFICATION_NOTICE + apply_edits(original.read_text(), entry.edits[len(WOLFSSH_PARSER_EDITS):])
baseline = MODIFICATION_NOTICE + apply_edits(original.read_text(), tuple(
edit for edit in entry.edits if edit not in WOLFSSH_PARSER_EDITS))
for name in ('DoUserAuthRequestPassword', 'DoPacket', 'DoChannelFailure',
'ParseRSAPubKey', 'ParseECCPubKey', 'DoUserAuthRequestPublicKey'):
assert extract(generated.decode(), name) == extract(baseline, name), name
@@ -75,5 +76,5 @@ with tempfile.TemporaryDirectory(prefix='wolfssh-parser-') as directory:
result = subprocess.run([str(binary)], capture_output=True, timeout=30)
assert result.returncode != 0, f'Undetected mutation: {label}'
print(f'PASS: {len(mutations)} parser guard-removal mutations rejected')
print('PASS: exact original hash; generated parser; unchanged ordering/password/deferred functions')
print('PASS: exact original hash; generated parser; parser-isolated ordering/password/deferred functions')
print('NOTE: production build-tree registration/firmware not regenerated or validated')
+46 -4
View File
@@ -11,6 +11,7 @@ from __future__ import annotations
import argparse
from dataclasses import dataclass
import hashlib
import json
import os
from pathlib import Path
import re
@@ -37,6 +38,7 @@ class Entry:
sha256: str
edits: tuple[Edit, ...]
target: str = "" # Explicit nested library; empty means IDF COMPONENT_LIB.
header: bool = False # PUBLIC overlay, never a translation unit.
MODIFICATION_NOTICE = """/* Modified by the ESP32_serial_swiss_army_knife project on 2026-09-15.
@@ -47,6 +49,15 @@ MODIFICATION_NOTICE = """/* Modified by the ESP32_serial_swiss_army_knife projec
"""
# Exact, reviewed consolidated delta; upstream mail patches are provenance only.
# No network, patch utility, fuzz, or managed-component mutation at configure time.
WOLFSSH_ORDER_DIR = Path(__file__).resolve().parent / "wolfssh_order"
WOLFSSH_ORDER_PLAN = json.loads((WOLFSSH_ORDER_DIR / "delta.json").read_text())
WOLFSSH_ORDER_EDITS = {
path: tuple(Edit(**edit) for edit in item["edits"])
for path, item in WOLFSSH_ORDER_PLAN.items()
}
WIPE = """/* Retired secret-bearing storage must not survive allocator reuse. */
static void security_override_wipe(void *buffer, size_t length)
{
@@ -427,7 +438,7 @@ ENTRIES = (
), target="mbedx509"),
Entry("wolfssh_internal", "wolfssl__wolfssh", "project",
"managed_components/wolfssl__wolfssh/src/internal.c",
"81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9", WOLFSSH_PARSER_EDITS + (
"81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9", WOLFSSH_ORDER_EDITS["src/internal.c"] + WOLFSSH_PARSER_EDITS + (
Edit(""" WS_UserAuthData_Password* pw = NULL;
int ret = WS_SUCCESS;
""", """ WS_UserAuthData_Password* pw = NULL;
@@ -582,6 +593,18 @@ ENTRIES = (
)
ENTRIES += (
Entry("wolfssh_ssh", "wolfssl__wolfssh", "project",
"managed_components/wolfssl__wolfssh/src/ssh.c",
"a4f479ff87eea0980ec1ebdf2c7dd090da473780181b695a56799cb9611f4366",
WOLFSSH_ORDER_EDITS["src/ssh.c"]),
Entry("wolfssh_internal_header", "wolfssl__wolfssh", "project",
"managed_components/wolfssl__wolfssh/wolfssh/internal.h",
"8e417149a68f8a6c0506957adf014b3e6c1727a723536826ce5fb0c9e1f1aba3",
WOLFSSH_ORDER_EDITS["wolfssh/internal.h"], header=True),
)
def apply_edits(text: str, edits: tuple[Edit, ...]) -> str:
for index, edit in enumerate(edits, 1):
count = text.count(edit.old) if edit.old else 0
@@ -607,6 +630,11 @@ def render_entry(entry: Entry, roots: dict[str, Path]) -> tuple[Path, bytes]:
if entry.target and (entry.component != "mbedtls" or entry.target not in
{"mbedtls", "mbedx509", "mbedcrypto"}):
raise OverrideError("invalid nested target selection")
if entry.header and (entry.name != "wolfssh_internal_header" or
entry.component != "wolfssl__wolfssh" or entry.target or
entry.root != "project" or entry.source !=
"managed_components/wolfssl__wolfssh/wolfssh/internal.h"):
raise OverrideError("unaudited header overlay")
root = roots[entry.root].resolve()
source = (root / entry.source).resolve()
if not source.is_relative_to(root):
@@ -615,7 +643,18 @@ def render_entry(entry: Entry, roots: dict[str, Path]) -> tuple[Path, bytes]:
actual = hashlib.sha256(raw).hexdigest()
if actual != entry.sha256:
raise OverrideError(f"{entry.name}: SHA256 mismatch for {source}: expected {entry.sha256}, got {actual}; reaudit, do not repin blindly")
return source, (MODIFICATION_NOTICE + apply_edits(raw.decode("utf-8"), entry.edits)).encode("utf-8")
notice = MODIFICATION_NOTICE
if entry.component == "wolfssl__wolfssh":
notice += ("/* Ordering profile modified 2026-09-16: PR793/819/840/855/921\n"
" * plus project restricted no-EXT_INFO correction. Provenance and\n"
" * limitations: tools/wolfssh_order/README.md and delta.json.\n"
" */\n")
if entry.header:
notice += ("#if defined(_WOLFSSH_INTERNAL_H_) && \\\n"
" (!defined(SAK_WOLFSSH_ORDER_ABI) || SAK_WOLFSSH_ORDER_ABI != 20260916)\n"
'#error "Security override: stale wolfSSH internal.h included before overlay"\n'
"#endif\n#define SAK_WOLFSSH_ORDER_ABI 20260916\n")
return source, (notice + apply_edits(raw.decode("utf-8"), entry.edits)).encode("utf-8")
def write_if_changed(path: Path, data: bytes) -> bool:
@@ -659,10 +698,13 @@ def generate(idf: Path, project: Path, binary: Path, entries: tuple[Entry, ...]
if source in seen:
raise OverrideError(f"ambiguous duplicate source: {source}")
seen.add(source)
target = output / entry.name / source.name
target = (output / "wolfssh_include" / "wolfssh" / source.name
if entry.header else output / entry.name / source.name)
rendered.append((entry, source, target, data))
lines = ["# Generated by tools/security_overrides.py; do not edit.",
"set(SAK_SECURITY_OVERRIDE_IDS " + " ".join(names) + ")",
"set(SAK_SECURITY_OVERRIDE_IDS " + " ".join(e.name for e in entries if not e.header) + ")",
"set(SAK_SECURITY_HEADER_IDS " + " ".join(e.name for e in entries if e.header) + ")",
"set(SAK_SECURITY_WOLFSSH_INCLUDE " + cmake_quote(str(output / "wolfssh_include")) + ")",
"set(SAK_SECURITY_VERSION_HEADER " + cmake_quote(str(version)) + ")"]
for entry, source, target, _ in rendered:
for key, value in (("COMPONENT", entry.component), ("TARGET", entry.target), ("ORIGINAL", str(source)),
+882
View File
@@ -0,0 +1,882 @@
From 5fa6c0fce30a421879b355a007ef843eb48332d3 Mon Sep 17 00:00:00 2001
From: JacobBarthelmeh <jacob@wolfssl.com>
Date: Thu, 27 Mar 2025 08:54:23 -0600
Subject: [PATCH 1/8] sanity checks on message types during rekey
---
src/internal.c | 55 +++++++++++++++++++++++++++++++++++++++++++---
wolfssh/internal.h | 5 ++++-
2 files changed, 56 insertions(+), 4 deletions(-)
diff --git a/src/internal.c b/src/internal.c
index 63f0e1af7..739a9bad2 100644
--- a/src/internal.c
+++ b/src/internal.c
@@ -595,6 +595,40 @@ static void HandshakeInfoFree(HandshakeInfo* hs, void* heap)
}
+/* RFC 4253 section 7.1, Once having sent SSH_MSG_KEXINIT the only messages
+* that can be sent are 1-19 (except SSH_MSG_SERVICE_REQUEST and
+* SSH_MSG_SERVICE_ACCEPT), 20-29 (except SSH_MSG_KEXINIT again), and 30-49
+*/
+INLINE static int IsMessageAllowedKeying(WOLFSSH *ssh, byte msg)
+{
+ if (ssh->isKeying == 0) {
+ return 1;
+ }
+
+ /* case of servie request or accept in 1-19 */
+ if (msg == MSGID_SERVICE_REQUEST || msg == MSGID_SERVICE_ACCEPT) {
+ WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by during rekeying", msg);
+ ssh->error = WS_REKEYING;
+ return 0;
+ }
+
+ /* case of resending SSH_MSG_KEXINIT */
+ if (msg == MSGID_KEXINIT) {
+ WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by during rekeying", msg);
+ ssh->error = WS_REKEYING;
+ return 0;
+ }
+
+ /* case where message id greater than 49 */
+ if (msg >= MSGID_USERAUTH_REQUEST) {
+ WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by during rekeying", msg);
+ ssh->error = WS_REKEYING;
+ return 0;
+ }
+ return 1;
+}
+
+
#ifndef NO_WOLFSSH_SERVER
INLINE static int IsMessageAllowedServer(WOLFSSH *ssh, byte msg)
{
@@ -673,8 +707,12 @@ INLINE static int IsMessageAllowedClient(WOLFSSH *ssh, byte msg)
#endif /* NO_WOLFSSH_CLIENT */
-INLINE static int IsMessageAllowed(WOLFSSH *ssh, byte msg)
+INLINE static int IsMessageAllowed(WOLFSSH *ssh, byte msg, byte state)
{
+ if (state == WS_MSG_SEND && !IsMessageAllowedKeying(ssh, msg)) {
+ return 0;
+ }
+
#ifndef NO_WOLFSSH_SERVER
if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER) {
return IsMessageAllowedServer(ssh, msg);
@@ -5905,7 +5943,6 @@ static int DoNewKeys(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
HandshakeInfoFree(ssh->handshake, ssh->ctx->heap);
ssh->handshake = NULL;
WLOG(WS_LOG_DEBUG, "Keying completed");
-
if (ssh->ctx->keyingCompletionCb)
ssh->ctx->keyingCompletionCb(ssh->keyingCompletionCtx);
}
@@ -9309,7 +9346,7 @@ static int DoPacket(WOLFSSH* ssh, byte* bufferConsumed)
return WS_OVERFLOW_E;
}
- if (!IsMessageAllowed(ssh, msg)) {
+ if (!IsMessageAllowed(ssh, msg, WS_MSG_RECV)) {
return WS_MSGID_NOT_ALLOWED_E;
}
@@ -15649,6 +15686,12 @@ int SendChannelEof(WOLFSSH* ssh, word32 peerChannelId)
if (ssh == NULL)
ret = WS_BAD_ARGUMENT;
+ if (ret == WS_SUCCESS) {
+ if (!IsMessageAllowed(ssh, MSGID_CHANNEL_EOF, WS_MSG_SEND)) {
+ ret = WS_MSGID_NOT_ALLOWED_E;
+ }
+ }
+
if (ret == WS_SUCCESS) {
channel = ChannelFind(ssh, peerChannelId, WS_CHANNEL_ID_PEER);
if (channel == NULL)
@@ -16077,6 +16120,12 @@ int SendChannelWindowAdjust(WOLFSSH* ssh, word32 channelId,
if (ssh == NULL)
ret = WS_BAD_ARGUMENT;
+ if (ret == WS_SUCCESS) {
+ if (!IsMessageAllowed(ssh, MSGID_CHANNEL_WINDOW_ADJUST, WS_MSG_SEND)) {
+ ret = WS_MSGID_NOT_ALLOWED_E;
+ }
+ }
+
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
if (channel == NULL) {
WLOG(WS_LOG_DEBUG, "Invalid channel");
diff --git a/wolfssh/internal.h b/wolfssh/internal.h
index 261ae6d42..29a6f8ef8 100644
--- a/wolfssh/internal.h
+++ b/wolfssh/internal.h
@@ -1249,6 +1249,10 @@ enum WS_MessageIds {
#define CHANNEL_EXTENDED_DATA_STDERR WOLFSSH_EXT_DATA_STDERR
+/* Used when checking IsMessageAllowed() to determine if createing and sending
+ * the message or receiving the message is allowed */
+#define WS_MSG_SEND 1
+#define WS_MSG_RECV 2
/* dynamic memory types */
enum WS_DynamicTypes {
@@ -1442,4 +1446,3 @@ enum TerminalModes {
#endif
#endif /* _WOLFSSH_INTERNAL_H_ */
-
From af45bc3719ddeac112d9d70b2e6a969f1aa3f3e7 Mon Sep 17 00:00:00 2001
From: JacobBarthelmeh <jacob@wolfssl.com>
Date: Tue, 13 May 2025 16:01:01 -0600
Subject: [PATCH 2/8] update example client for rekey and sanity check on
window update after read attempt
---
examples/client/client.c | 11 +++++++++++
src/ssh.c | 12 +++++++++++-
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/examples/client/client.c b/examples/client/client.c
index e27305f72..f415bd801 100644
--- a/examples/client/client.c
+++ b/examples/client/client.c
@@ -342,6 +342,9 @@ static THREAD_RET readInput(void* in)
ret = wolfSSH_stream_send(args->ssh, buf, sz);
wc_UnLockMutex(&args->lock);
if (ret <= 0) {
+ if (ret == WS_REKEYING) {
+ continue;
+ }
fprintf(stderr, "Couldn't send data\n");
return THREAD_RET_SUCCESS;
}
@@ -472,8 +475,16 @@ static THREAD_RET readPeer(void* in)
continue;
}
#endif /* WOLFSSH_AGENT */
+ else if (ret == WS_REKEYING) {
+ wolfSSH_worker(args->ssh, NULL);
+ ret = 0;
+ }
}
else if (ret != WS_EOF) {
+ if (ret == 0) {
+ bytes = 0;
+ continue;
+ }
err_sys("Stream read failed.");
}
}
diff --git a/src/ssh.c b/src/ssh.c
index 05c1a7b3e..2ed75d1bc 100644
--- a/src/ssh.c
+++ b/src/ssh.c
@@ -1135,6 +1135,11 @@ int wolfSSH_stream_read(WOLFSSH* ssh, byte* buf, word32 bufSz)
return WS_ERROR;
}
+ if (ssh->isKeying) {
+ ssh->error = WS_REKEYING;
+ return WS_FATAL_ERROR;
+ }
+
inputBuffer = &ssh->channelList->inputBuffer;
ssh->error = WS_SUCCESS;
@@ -1164,7 +1169,7 @@ int wolfSSH_stream_read(WOLFSSH* ssh, byte* buf, word32 bufSz)
}
/* update internal input buffer based on data read */
- if (ret == WS_SUCCESS) {
+ if (ret == WS_SUCCESS && !ssh->isKeying) {
int n;
n = min(bufSz, inputBuffer->length - inputBuffer->idx);
@@ -2901,6 +2906,11 @@ int wolfSSH_ChannelRead(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)
if (channel == NULL || buf == NULL || bufSz == 0)
return WS_BAD_ARGUMENT;
+ if (channel->ssh->isKeying) {
+ channel->ssh->error = WS_REKEYING;
+ return WS_REKEYING;
+ }
+
bufSz = _ChannelRead(channel, buf, bufSz);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelRead(), bytesRxd = %d",
From d74c942c84d44fb46d3a10cb56233b704733e466 Mon Sep 17 00:00:00 2001
From: JacobBarthelmeh <jacob@wolfssl.com>
Date: Thu, 15 May 2025 11:38:45 -0600
Subject: [PATCH 3/8] refactor SFTP to use NoticeError
---
examples/sftpclient/sftpclient.c | 8 +++++++-
src/ssh.c | 2 +-
src/wolfsftp.c | 25 +++++++++++--------------
3 files changed, 19 insertions(+), 16 deletions(-)
diff --git a/examples/sftpclient/sftpclient.c b/examples/sftpclient/sftpclient.c
index 779baff1f..a990bc14e 100644
--- a/examples/sftpclient/sftpclient.c
+++ b/examples/sftpclient/sftpclient.c
@@ -1119,7 +1119,7 @@ static int doCmds(func_args* args)
/* alternate main loop for the autopilot get/receive */
static int doAutopilot(int cmd, char* local, char* remote)
{
- int err;
+ int err = 0;
int ret = WS_SUCCESS;
char fullpath[128] = ".";
WS_SFTPNAME* name = NULL;
@@ -1156,6 +1156,12 @@ static int doAutopilot(int cmd, char* local, char* remote)
}
do {
+ if (err == WS_REKEYING) { /* handle rekeying state */
+ do {
+ ret = wolfSSH_worker(ssh, NULL);
+ } while (ret == WS_REKEYING);
+ }
+
if (cmd == AUTOPILOT_PUT) {
ret = wolfSSH_SFTP_Put(ssh, local, fullpath, 0, NULL);
}
diff --git a/src/ssh.c b/src/ssh.c
index 2ed75d1bc..56c248589 100644
--- a/src/ssh.c
+++ b/src/ssh.c
@@ -1201,7 +1201,7 @@ int wolfSSH_stream_send(WOLFSSH* ssh, byte* buf, word32 bufSz)
if (ssh->isKeying) {
ssh->error = WS_REKEYING;
- return WS_REKEYING;
+ return WS_FATAL_ERROR;
}
bytesTxd = SendChannelData(ssh, ssh->channelList->channel, buf, bufSz);
diff --git a/src/wolfsftp.c b/src/wolfsftp.c
index 761830a34..a95428fd0 100644
--- a/src/wolfsftp.c
+++ b/src/wolfsftp.c
@@ -1418,7 +1418,11 @@ int wolfSSH_SFTP_read(WOLFSSH* ssh)
ret = wolfSSH_SFTP_buffer_read(ssh, &state->buffer,
state->buffer.sz);
if (ret < 0) {
- if (!NoticeError(ssh)) {
+ if (NoticeError(ssh)) {
+ /* keep state for returning to */
+ ret = WS_FATAL_ERROR;
+ }
+ else {
wolfSSH_SFTP_ClearState(ssh, STATE_ID_RECV);
}
return ret;
@@ -7452,8 +7456,7 @@ int wolfSSH_SFTP_SendWritePacket(WOLFSSH* ssh, byte* handle, word32 handleSz,
/* send header and type specific data */
ret = wolfSSH_SFTP_buffer_send(ssh, &state->buffer);
if (ret < 0) {
- if (ssh->error == WS_WANT_READ ||
- ssh->error == WS_WANT_WRITE) {
+ if (NoticeError(ssh)) {
return WS_FATAL_ERROR;
}
state->state = STATE_SEND_WRITE_CLEANUP;
@@ -7465,12 +7468,8 @@ int wolfSSH_SFTP_SendWritePacket(WOLFSSH* ssh, byte* handle, word32 handleSz,
case STATE_SEND_WRITE_SEND_BODY:
WLOG(WS_LOG_SFTP, "SFTP SEND_WRITE STATE: SEND_BODY");
state->sentSz = wolfSSH_stream_send(ssh, in, inSz);
- if (state->sentSz == WS_WINDOW_FULL ||
- state->sentSz == WS_REKEYING ||
- state->sentSz == WS_WANT_READ ||
- state->sentSz == WS_WANT_WRITE) {
- ret = wolfSSH_worker(ssh, NULL);
- continue; /* skip past rest and send more */
+ if (NoticeError(ssh)) {
+ return WS_FATAL_ERROR;
}
if (state->sentSz <= 0) {
ssh->error = state->sentSz;
@@ -7496,8 +7495,7 @@ int wolfSSH_SFTP_SendWritePacket(WOLFSSH* ssh, byte* handle, word32 handleSz,
state->maxSz = SFTP_GetHeader(ssh, &state->reqId, &type,
&state->buffer);
if (state->maxSz <= 0) {
- if (ssh->error == WS_WANT_READ ||
- ssh->error == WS_WANT_WRITE) {
+ if (NoticeError(ssh)) {
return WS_FATAL_ERROR;
}
ssh->error = WS_SFTP_BAD_HEADER;
@@ -9167,10 +9165,9 @@ int wolfSSH_SFTP_Put(WOLFSSH* ssh, char* from, char* to, byte resume,
state->handle, state->handleSz, state->pOfst,
state->r, state->rSz);
if (sz <= 0) {
- if (ssh->error == WS_WANT_READ ||
- ssh->error == WS_WANT_WRITE ||
- ssh->error == WS_WINDOW_FULL)
+ if (NoticeError(ssh)) {
return WS_FATAL_ERROR;
+ }
}
else {
AddAssign64(state->pOfst, sz);
From ff95f3c3029d766b114a91d98b013e0a1636a6c1 Mon Sep 17 00:00:00 2001
From: JacobBarthelmeh <jacob@wolfssl.com>
Date: Thu, 15 May 2025 13:32:33 -0600
Subject: [PATCH 4/8] increase timeout time on test, fix spelling, add comment
on new arg
---
.github/workflows/sshd-test.yml | 2 +-
src/internal.c | 2 ++
wolfssh/internal.h | 2 +-
3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/sshd-test.yml b/.github/workflows/sshd-test.yml
index 3fbe3daf8..eb075a6f1 100644
--- a/.github/workflows/sshd-test.yml
+++ b/.github/workflows/sshd-test.yml
@@ -66,7 +66,7 @@ jobs:
wolfssl: ${{ fromJson(needs.create_matrix.outputs['versions']) }}
name: Build and test wolfsshd
runs-on: ${{ matrix.os }}
- timeout-minutes: 10
+ timeout-minutes: 15
steps:
- name: Checking cache for wolfssl
uses: actions/cache@v4
diff --git a/src/internal.c b/src/internal.c
index 739a9bad2..ff912ef74 100644
--- a/src/internal.c
+++ b/src/internal.c
@@ -707,6 +707,8 @@ INLINE static int IsMessageAllowedClient(WOLFSSH *ssh, byte msg)
#endif /* NO_WOLFSSH_CLIENT */
+/* 'state' argument is for if trying to send a message or receive one.
+ * Returns 1 if allowed 0 if not allowed. */
INLINE static int IsMessageAllowed(WOLFSSH *ssh, byte msg, byte state)
{
if (state == WS_MSG_SEND && !IsMessageAllowedKeying(ssh, msg)) {
diff --git a/wolfssh/internal.h b/wolfssh/internal.h
index 29a6f8ef8..ad5e00b0e 100644
--- a/wolfssh/internal.h
+++ b/wolfssh/internal.h
@@ -1249,7 +1249,7 @@ enum WS_MessageIds {
#define CHANNEL_EXTENDED_DATA_STDERR WOLFSSH_EXT_DATA_STDERR
-/* Used when checking IsMessageAllowed() to determine if createing and sending
+/* Used when checking IsMessageAllowed() to determine if creating and sending
* the message or receiving the message is allowed */
#define WS_MSG_SEND 1
#define WS_MSG_RECV 2
From 2a11471bb717a2ee6f06b3e1beab8a3e2b0ef261 Mon Sep 17 00:00:00 2001
From: JacobBarthelmeh <jacob@wolfssl.com>
Date: Tue, 16 Sep 2025 13:30:25 -0600
Subject: [PATCH 5/8] refactor introducing more use of NoticeError
---
examples/echoserver/echoserver.c | 5 +-
examples/sftpclient/sftpclient.c | 81 ++++++++++++++++++++++++++++----
src/wolfsftp.c | 35 +++++++-------
tests/api.c | 5 ++
4 files changed, 100 insertions(+), 26 deletions(-)
diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c
index 1fbd58a0c..8d14a7c95 100644
--- a/examples/echoserver/echoserver.c
+++ b/examples/echoserver/echoserver.c
@@ -1416,8 +1416,11 @@ static int sftp_worker(thread_ctx_t* threadCtx)
}
else if (ret < 0) {
error = wolfSSH_get_error(ssh);
- if (error == WS_EOF)
+ if (error == WS_EOF) {
+ /* shutdown is happening, clear peek error */
+ ret = 0;
break;
+ }
}
if (ret == WS_FATAL_ERROR && error == 0) {
diff --git a/examples/sftpclient/sftpclient.c b/examples/sftpclient/sftpclient.c
index a990bc14e..b735c0d51 100644
--- a/examples/sftpclient/sftpclient.c
+++ b/examples/sftpclient/sftpclient.c
@@ -566,11 +566,8 @@ static int doCmds(func_args* args)
}
do {
- while (ret == WS_REKEYING || ssh->error == WS_REKEYING) {
+ while (wolfSSH_get_error(ssh) == WS_REKEYING) {
ret = wolfSSH_worker(ssh, NULL);
- if (ret != WS_SUCCESS && ret == WS_FATAL_ERROR) {
- ret = wolfSSH_get_error(ssh);
- }
}
ret = wolfSSH_SFTP_Get(ssh, pt, to, resume, &myStatusCb);
@@ -747,6 +744,13 @@ static int doCmds(func_args* args)
/* check directory is valid */
do {
+ while (ret == WS_REKEYING || ssh->error == WS_REKEYING) {
+ ret = wolfSSH_worker(ssh, NULL);
+ if (ret != WS_SUCCESS && ret == WS_FATAL_ERROR) {
+ ret = wolfSSH_get_error(ssh);
+ }
+ }
+
ret = wolfSSH_SFTP_STAT(ssh, pt, &atrb);
err = wolfSSH_get_error(ssh);
} while ((err == WS_WANT_READ || err == WS_WANT_WRITE)
@@ -828,6 +832,13 @@ static int doCmds(func_args* args)
/* update permissions */
do {
+ while (ret == WS_REKEYING || ssh->error == WS_REKEYING) {
+ ret = wolfSSH_worker(ssh, NULL);
+ if (ret != WS_SUCCESS && ret == WS_FATAL_ERROR) {
+ ret = wolfSSH_get_error(ssh);
+ }
+ }
+
ret = wolfSSH_SFTP_CHMOD(ssh, pt, mode);
err = wolfSSH_get_error(ssh);
} while ((err == WS_WANT_READ || err == WS_WANT_WRITE)
@@ -878,6 +889,13 @@ static int doCmds(func_args* args)
}
do {
+ while (ret == WS_REKEYING || ssh->error == WS_REKEYING) {
+ ret = wolfSSH_worker(ssh, NULL);
+ if (ret != WS_SUCCESS && ret == WS_FATAL_ERROR) {
+ ret = wolfSSH_get_error(ssh);
+ }
+ }
+
ret = wolfSSH_SFTP_RMDIR(ssh, pt);
err = wolfSSH_get_error(ssh);
} while ((err == WS_WANT_READ || err == WS_WANT_WRITE)
@@ -924,6 +942,13 @@ static int doCmds(func_args* args)
}
do {
+ while (ret == WS_REKEYING || ssh->error == WS_REKEYING) {
+ ret = wolfSSH_worker(ssh, NULL);
+ if (ret != WS_SUCCESS && ret == WS_FATAL_ERROR) {
+ ret = wolfSSH_get_error(ssh);
+ }
+ }
+
ret = wolfSSH_SFTP_Remove(ssh, pt);
err = wolfSSH_get_error(ssh);
} while ((err == WS_WANT_READ || err == WS_WANT_WRITE)
@@ -1458,14 +1483,52 @@ THREAD_RETURN WOLFSSH_THREAD sftpclient_test(void* args)
WFREE(workingDir, NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (ret == WS_SUCCESS) {
- if (wolfSSH_shutdown(ssh) != WS_SUCCESS) {
- int rc;
- rc = wolfSSH_get_error(ssh);
+ int err;
+ ret = wolfSSH_shutdown(ssh);
+
+ /* peer hung up, stop trying to shutdown */
+ if (ret == WS_SOCKET_ERROR_E) {
+ ret = 0;
+ }
+
+ err = wolfSSH_get_error(ssh);
+ if (err != WS_SOCKET_ERROR_E &&
+ (err == WS_WANT_READ || err == WS_WANT_WRITE)) {
+ int maxAttempt = 10; /* make 10 attempts max before giving up */
+ int attempt;
+
+ for (attempt = 0; attempt < maxAttempt; attempt++) {
+ ret = wolfSSH_worker(ssh, NULL);
+ err = wolfSSH_get_error(ssh);
+
+ /* peer succesfully closed down gracefully */
+ if (ret == WS_CHANNEL_CLOSED) {
+ ret = 0;
+ break;
+ }
- if (rc != WS_SOCKET_ERROR_E && rc != WS_EOF)
- printf("error with wolfSSH_shutdown()\n");
+ /* peer hung up, stop shutdown */
+ if (ret == WS_SOCKET_ERROR_E) {
+ ret = 0;
+ break;
+ }
+
+ if (err == WS_WANT_READ || err == WS_WANT_WRITE) {
+ /* Wanting read or wanting write. Clear ret. */
+ ret = 0;
+ }
+ else {
+ break;
+ }
+ }
+
+ if (attempt == maxAttempt) {
+ printf("SFTP client gave up on gracefull shutdown,"
+ "closing the socket\n");
+ }
}
}
+
WCLOSESOCKET(sockFd);
wolfSSH_free(ssh);
wolfSSH_CTX_free(ctx);
diff --git a/src/wolfsftp.c b/src/wolfsftp.c
index a95428fd0..53c5dfd1a 100644
--- a/src/wolfsftp.c
+++ b/src/wolfsftp.c
@@ -865,6 +865,7 @@ static int SFTP_GetHeader(WOLFSSH* ssh, word32* reqId, byte* type,
*/
static int SFTP_SetHeader(WOLFSSH* ssh, word32 reqId, byte type, word32 len,
byte* buf) {
+
c32toa(len + LENGTH_SZ + MSG_ID_SZ, buf);
buf[LENGTH_SZ] = type;
c32toa(reqId, buf + LENGTH_SZ + MSG_ID_SZ);
@@ -1170,8 +1171,9 @@ int wolfSSH_SFTP_accept(WOLFSSH* ssh)
case SFTP_EXT:
ret = SFTP_ServerRecvInit(ssh);
if (ret != WS_SUCCESS) {
- if (ssh->error != WS_WANT_READ && ssh->error != WS_WANT_WRITE)
+ if (!NoticeError(ssh)) {
wolfSSH_SFTP_ClearState(ssh, STATE_ID_ALL);
+ }
return ret;
}
ssh->sftpState = SFTP_RECV;
@@ -1573,8 +1575,9 @@ int wolfSSH_SFTP_read(WOLFSSH* ssh)
/* break out if encountering an error with nothing stored to send */
if (ret < 0 && !state->toSend) {
- if (ssh->error != WS_WANT_READ && ssh->error != WS_WANT_WRITE)
+ if (!NoticeError(ssh)) {
wolfSSH_SFTP_ClearState(ssh, STATE_ID_RECV);
+ }
return ret;
}
state->buffer.idx = 0;
@@ -7674,8 +7677,8 @@ int wolfSSH_SFTP_SendReadPacket(WOLFSSH* ssh, byte* handle, word32 handleSz,
/* send header and type specific data */
ret = wolfSSH_SFTP_buffer_send(ssh, &state->buffer);
if (ret < 0) {
- if (ret == WS_REKEYING) {
- return ret;
+ if (NoticeError(ssh)) {
+ return WS_FATAL_ERROR;
}
if (ssh->error != WS_WANT_READ &&
ssh->error != WS_WANT_WRITE) {
@@ -7693,14 +7696,12 @@ int wolfSSH_SFTP_SendReadPacket(WOLFSSH* ssh, byte* handle, word32 handleSz,
/* Get response */
if ((ret = SFTP_GetHeader(ssh, &state->reqId, &state->type,
&state->buffer)) <= 0) {
- if (ssh->error != WS_WANT_READ &&
- ssh->error != WS_WANT_WRITE) {
+ if (!NoticeError(ssh)) {
state->state = STATE_SEND_READ_CLEANUP;
continue;
}
return WS_FATAL_ERROR;
}
-
ret = wolfSSH_SFTP_buffer_create(ssh, &state->buffer, ret);
if (ret != WS_SUCCESS) {
state->state = STATE_SEND_READ_CLEANUP;
@@ -7718,8 +7719,9 @@ int wolfSSH_SFTP_SendReadPacket(WOLFSSH* ssh, byte* handle, word32 handleSz,
state->state = STATE_SEND_READ_CLEANUP;
continue;
}
- else
+ else {
ssh->reqId++;
+ }
if (state->type == WOLFSSH_FTP_DATA)
state->state = STATE_SEND_READ_FTP_DATA;
@@ -7737,8 +7739,7 @@ int wolfSSH_SFTP_SendReadPacket(WOLFSSH* ssh, byte* handle, word32 handleSz,
/* get size of string and place it into out buffer */
ret = wolfSSH_stream_read(ssh, szFlat, UINT32_SZ);
if (ret < 0) {
- if (ssh->error != WS_WANT_READ &&
- ssh->error != WS_WANT_WRITE) {
+ if (!NoticeError(ssh)) {
state->state = STATE_SEND_READ_CLEANUP;
continue;
}
@@ -7917,8 +7918,9 @@ int wolfSSH_SFTP_MKDIR(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr)
/* send header and type specific data */
ret = wolfSSH_SFTP_buffer_send(ssh, &state->buffer);
if (ret < 0) {
- if (ssh->error != WS_WANT_READ && ssh->error != WS_WANT_WRITE)
+ if (!NoticeError(ssh)) {
wolfSSH_SFTP_ClearState(ssh, STATE_ID_MKDIR);
+ }
return ret;
}
@@ -7931,8 +7933,9 @@ int wolfSSH_SFTP_MKDIR(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr)
/* Get response */
if ((ret = SFTP_GetHeader(ssh, &state->reqId, &type,
&state->buffer)) <= 0) {
- if (ssh->error != WS_WANT_READ && ssh->error != WS_WANT_WRITE)
+ if (!NoticeError(ssh)) {
wolfSSH_SFTP_ClearState(ssh, STATE_ID_MKDIR);
+ }
return WS_FATAL_ERROR;
}
@@ -7963,8 +7966,9 @@ int wolfSSH_SFTP_MKDIR(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr)
ret = wolfSSH_SFTP_buffer_read(ssh, &state->buffer,
wolfSSH_SFTP_buffer_size(&state->buffer));
if (ret < 0) {
- if (ssh->error != WS_WANT_READ && ssh->error != WS_WANT_WRITE)
- wolfSSH_SFTP_ClearState(ssh, STATE_ID_MKDIR);
+ if (!NoticeError(ssh)) {
+ wolfSSH_SFTP_ClearState(ssh, STATE_ID_MKDIR);
+ }
return WS_FATAL_ERROR;
}
@@ -8031,8 +8035,7 @@ WS_SFTPNAME* wolfSSH_SFTP_ReadDir(WOLFSSH* ssh, byte* handle,
case STATE_READDIR_NAME:
name = wolfSSH_SFTP_DoName(ssh);
if (name == NULL) {
- if (ssh->error != WS_WANT_READ
- && ssh->error != WS_WANT_WRITE) {
+ if (!NoticeError(ssh)) {
wolfSSH_SFTP_ClearState(ssh, STATE_ID_READDIR);
}
return NULL;
diff --git a/tests/api.c b/tests/api.c
index 2bef34998..701425d20 100644
--- a/tests/api.c
+++ b/tests/api.c
@@ -1075,6 +1075,11 @@ static void test_wolfSSH_SFTP_SendReadPacket(void)
}
}
+ /* take care of re-keying state before shutdown call */
+ while (wolfSSH_get_error(ssh) == WS_REKEYING) {
+ wolfSSH_worker(ssh, NULL);
+ }
+
argsCount = wolfSSH_shutdown(ssh);
if (argsCount == WS_SOCKET_ERROR_E) {
/* If the socket is closed on shutdown, peer is gone, this is OK. */
From 813ec263cc56e7c9093135d854b3fc887633d368 Mon Sep 17 00:00:00 2001
From: JacobBarthelmeh <jacob@wolfssl.com>
Date: Tue, 16 Sep 2025 13:52:12 -0600
Subject: [PATCH 6/8] fix for scan-build report of unused return value
---
examples/sftpclient/sftpclient.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/examples/sftpclient/sftpclient.c b/examples/sftpclient/sftpclient.c
index b735c0d51..1194b0de5 100644
--- a/examples/sftpclient/sftpclient.c
+++ b/examples/sftpclient/sftpclient.c
@@ -566,8 +566,11 @@ static int doCmds(func_args* args)
}
do {
- while (wolfSSH_get_error(ssh) == WS_REKEYING) {
+ while (ret == WS_REKEYING || ssh->error == WS_REKEYING) {
ret = wolfSSH_worker(ssh, NULL);
+ if (ret != WS_SUCCESS && ret == WS_FATAL_ERROR) {
+ ret = wolfSSH_get_error(ssh);
+ }
}
ret = wolfSSH_SFTP_Get(ssh, pt, to, resume, &myStatusCb);
From cc17941a6125daefbadb8c236fdeaeb1a21ec786 Mon Sep 17 00:00:00 2001
From: JacobBarthelmeh <jacob@wolfssl.com>
Date: Tue, 16 Sep 2025 14:16:16 -0600
Subject: [PATCH 7/8] adjust test case to account for re-keying return
---
tests/api.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/tests/api.c b/tests/api.c
index 701425d20..9da02849b 100644
--- a/tests/api.c
+++ b/tests/api.c
@@ -1057,14 +1057,16 @@ static void test_wolfSSH_SFTP_SendReadPacket(void)
outSz = WOLFSSH_MAX_SFTP_RW / 2;
rxSz = wolfSSH_SFTP_SendReadPacket(ssh, handle, handleSz,
ofst, out, outSz);
- AssertIntGT(rxSz, 0);
- AssertIntLE(rxSz, outSz);
+ if (wolfSSH_get_error(ssh) != WS_REKEYING) {
+ AssertIntGT(rxSz, 0);
+ AssertIntLE(rxSz, outSz);
+ }
/* read all */
outSz = WOLFSSH_MAX_SFTP_RW;
rxSz = wolfSSH_SFTP_SendReadPacket(ssh, handle, handleSz,
ofst, out, outSz);
- if (rxSz != WS_REKEYING) {
+ if (wolfSSH_get_error(ssh) != WS_REKEYING) {
AssertIntGT(rxSz, 0);
AssertIntLE(rxSz, outSz);
}
From 4862400a374253216e596ff5c3b018b857015cbb Mon Sep 17 00:00:00 2001
From: JacobBarthelmeh <jacob@wolfssl.com>
Date: Mon, 6 Oct 2025 00:42:05 -0600
Subject: [PATCH 8/8] fix spelling issues and SFTP send state
---
.github/workflows/sshd-test.yml | 2 +-
examples/client/client.c | 4 ++--
examples/sftpclient/sftpclient.c | 7 ++++---
src/internal.c | 2 +-
src/wolfsftp.c | 15 +++++++++------
5 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/.github/workflows/sshd-test.yml b/.github/workflows/sshd-test.yml
index eb075a6f1..3fbe3daf8 100644
--- a/.github/workflows/sshd-test.yml
+++ b/.github/workflows/sshd-test.yml
@@ -66,7 +66,7 @@ jobs:
wolfssl: ${{ fromJson(needs.create_matrix.outputs['versions']) }}
name: Build and test wolfsshd
runs-on: ${{ matrix.os }}
- timeout-minutes: 15
+ timeout-minutes: 10
steps:
- name: Checking cache for wolfssl
uses: actions/cache@v4
diff --git a/examples/client/client.c b/examples/client/client.c
index f415bd801..49d00f44f 100644
--- a/examples/client/client.c
+++ b/examples/client/client.c
@@ -481,10 +481,10 @@ static THREAD_RET readPeer(void* in)
}
}
else if (ret != WS_EOF) {
- if (ret == 0) {
+ if (ret == 0) {
bytes = 0;
continue;
- }
+ }
err_sys("Stream read failed.");
}
}
diff --git a/examples/sftpclient/sftpclient.c b/examples/sftpclient/sftpclient.c
index 1194b0de5..e074b3d13 100644
--- a/examples/sftpclient/sftpclient.c
+++ b/examples/sftpclient/sftpclient.c
@@ -1184,7 +1184,7 @@ static int doAutopilot(int cmd, char* local, char* remote)
}
do {
- if (err == WS_REKEYING) { /* handle rekeying state */
+ if (err == WS_REKEYING || err == WS_WINDOW_FULL) { /* handle rekeying state */
do {
ret = wolfSSH_worker(ssh, NULL);
} while (ret == WS_REKEYING);
@@ -1198,7 +1198,8 @@ static int doAutopilot(int cmd, char* local, char* remote)
}
err = wolfSSH_get_error(ssh);
} while ((err == WS_WANT_READ || err == WS_WANT_WRITE ||
- err == WS_CHAN_RXD || err == WS_REKEYING) &&
+ err == WS_CHAN_RXD || err == WS_REKEYING ||
+ err == WS_WINDOW_FULL) &&
ret == WS_FATAL_ERROR);
if (ret != WS_SUCCESS) {
@@ -1504,7 +1505,7 @@ THREAD_RETURN WOLFSSH_THREAD sftpclient_test(void* args)
ret = wolfSSH_worker(ssh, NULL);
err = wolfSSH_get_error(ssh);
- /* peer succesfully closed down gracefully */
+ /* peer successfully closed down gracefully */
if (ret == WS_CHANNEL_CLOSED) {
ret = 0;
break;
diff --git a/src/internal.c b/src/internal.c
index ff912ef74..b9e3a3432 100644
--- a/src/internal.c
+++ b/src/internal.c
@@ -605,7 +605,7 @@ INLINE static int IsMessageAllowedKeying(WOLFSSH *ssh, byte msg)
return 1;
}
- /* case of servie request or accept in 1-19 */
+ /* case of service request or accept in 1-19 */
if (msg == MSGID_SERVICE_REQUEST || msg == MSGID_SERVICE_ACCEPT) {
WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by during rekeying", msg);
ssh->error = WS_REKEYING;
diff --git a/src/wolfsftp.c b/src/wolfsftp.c
index 53c5dfd1a..998806aef 100644
--- a/src/wolfsftp.c
+++ b/src/wolfsftp.c
@@ -416,6 +416,7 @@ static INLINE int NoticeError(WOLFSSH* ssh)
return (ssh->error == WS_WANT_READ ||
ssh->error == WS_WANT_WRITE ||
ssh->error == WS_CHAN_RXD ||
+ ssh->error == WS_WINDOW_FULL ||
ssh->error == WS_REKEYING);
}
@@ -865,7 +866,6 @@ static int SFTP_GetHeader(WOLFSSH* ssh, word32* reqId, byte* type,
*/
static int SFTP_SetHeader(WOLFSSH* ssh, word32 reqId, byte type, word32 len,
byte* buf) {
-
c32toa(len + LENGTH_SZ + MSG_ID_SZ, buf);
buf[LENGTH_SZ] = type;
c32toa(reqId, buf + LENGTH_SZ + MSG_ID_SZ);
@@ -7471,12 +7471,15 @@ int wolfSSH_SFTP_SendWritePacket(WOLFSSH* ssh, byte* handle, word32 handleSz,
case STATE_SEND_WRITE_SEND_BODY:
WLOG(WS_LOG_SFTP, "SFTP SEND_WRITE STATE: SEND_BODY");
state->sentSz = wolfSSH_stream_send(ssh, in, inSz);
- if (NoticeError(ssh)) {
- return WS_FATAL_ERROR;
- }
if (state->sentSz <= 0) {
- ssh->error = state->sentSz;
ret = WS_FATAL_ERROR;
+ if (NoticeError(ssh)) {
+ ret = wolfSSH_worker(ssh,NULL);
+ continue;
+ }
+
+ /* if it was not a notice error then clean up the state and
+ * exit out */
state->state = STATE_SEND_WRITE_CLEANUP;
continue;
}
@@ -9170,7 +9173,7 @@ int wolfSSH_SFTP_Put(WOLFSSH* ssh, char* from, char* to, byte resume,
if (sz <= 0) {
if (NoticeError(ssh)) {
return WS_FATAL_ERROR;
- }
+ }
}
else {
AddAssign64(state->pOfst, sz);
+46
View File
@@ -0,0 +1,46 @@
From 201029797b260eee894b12d488bce6022290bf67 Mon Sep 17 00:00:00 2001
From: JacobBarthelmeh <jacob@wolfssl.com>
Date: Fri, 18 Jul 2025 15:43:26 -0600
Subject: [PATCH] only send ext info once after SSH_MSG_NEWKEYS
---
src/internal.c | 6 +++++-
wolfssh/internal.h | 1 +
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/internal.c b/src/internal.c
index 080ded6f3..912315ca2 100644
--- a/src/internal.c
+++ b/src/internal.c
@@ -4080,7 +4080,7 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
if (ret == WS_SUCCESS) {
/* Only checking for this is we are server. Our client does
* not have anything to say to a server, yet. */
- if (side == WOLFSSH_ENDPOINT_SERVER) {
+ if (side == WOLFSSH_ENDPOINT_SERVER && !ssh->extInfoSent) {
byte extInfo;
/* Match the client accepts extInfo. */
@@ -13216,6 +13216,10 @@ int SendExtInfo(WOLFSSH* ssh)
}
if (ret == WS_SUCCESS) {
+ ssh->sendExtInfo = 0;
+ ssh->extInfoSent = 1; /* RFC 8308 section 2.4 ext. info should only be
+ * sent after SSH_MSG_NEWKEYS or after
+ * SSH_MSG_USERAUTH_SUCCESS. Not on re-key */
ret = wolfSSH_SendPacket(ssh);
}
diff --git a/wolfssh/internal.h b/wolfssh/internal.h
index 1b60139a1..26c3a05b0 100644
--- a/wolfssh/internal.h
+++ b/wolfssh/internal.h
@@ -844,6 +844,7 @@ struct WOLFSSH {
byte sendTerminalRequest;
byte userAuthPkDone;
byte sendExtInfo;
+ byte extInfoSent; /* track if the ext info has already been sent */
byte* peerSigId;
word32 peerSigIdSz;
+147
View File
@@ -0,0 +1,147 @@
From 9dc1071da7e560db2ea899fa23aab885a25ea862 Mon Sep 17 00:00:00 2001
From: JacobBarthelmeh <jacob@wolfssl.com>
Date: Mon, 13 Oct 2025 09:44:29 -0600
Subject: [PATCH 1/2] improvements to keying and track side
---
src/internal.c | 35 ++++++++++++++++++++++++++++++-----
wolfssh/internal.h | 5 +++++
2 files changed, 35 insertions(+), 5 deletions(-)
diff --git a/src/internal.c b/src/internal.c
index edab14eb4..230904489 100644
--- a/src/internal.c
+++ b/src/internal.c
@@ -1096,7 +1096,7 @@ WOLFSSH* SshInit(WOLFSSH* ssh, WOLFSSH_CTX* ctx)
ssh->fs = NULL;
ssh->acceptState = ACCEPT_BEGIN;
ssh->clientState = CLIENT_BEGIN;
- ssh->isKeying = 1;
+ ssh->isKeying = 0; /* initial state of not keying yet */
ssh->authId = ID_USERAUTH_PUBLICKEY;
ssh->supportedAuth[0] = ID_USERAUTH_PUBLICKEY;
ssh->supportedAuth[1] = ID_USERAUTH_PASSWORD;
@@ -4058,6 +4058,15 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
ret = WS_BAD_ARGUMENT;
}
+ if (ret == WS_SUCCESS) {
+ /* Check if already in process of keying and error out if so. */
+ if (ssh->isKeying & WOLFSSH_PEER_IS_KEYING) {
+ WLOG(WS_LOG_ERROR,
+ "Already in keying process and got KEX init");
+ ret = WS_INVALID_STATE_E;
+ }
+ }
+
/*
* I don't need to save what the client sends here. I should decode
* each list into a local array of IDs, and pick the one the peer is
@@ -4067,6 +4076,8 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
*/
if (ret == WS_SUCCESS) {
+ /* Set peer is keying flag after receiving SSH_MSG_KEX_INIT */
+ ssh->isKeying |= WOLFSSH_PEER_IS_KEYING;
if (ssh->handshake == NULL) {
ssh->handshake = HandshakeInfoNew(ssh->ctx->heap);
if (ssh->handshake == NULL) {
@@ -5881,6 +5892,13 @@ static int DoNewKeys(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
if (ssh == NULL || ssh->handshake == NULL)
ret = WS_BAD_ARGUMENT;
+ if (ret == WS_SUCCESS) {
+ if (ssh->isKeying & WOLFSSH_SELF_IS_KEYING) {
+ WLOG(WS_LOG_ERROR, "Keying failed");
+ ret = WS_INVALID_STATE_E;
+ }
+ }
+
if (ret == WS_SUCCESS) {
ssh->peerEncryptId = ssh->handshake->encryptId;
ssh->peerMacId = ssh->handshake->macId;
@@ -5941,7 +5959,9 @@ static int DoNewKeys(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
if (ret == WS_SUCCESS) {
ssh->rxCount = 0;
ssh->highwaterFlag = 0;
- ssh->isKeying = 0;
+
+ /* Clear peer is keying flag */
+ ssh->isKeying &= ~WOLFSSH_PEER_IS_KEYING;
HandshakeInfoFree(ssh->handshake, ssh->ctx->heap);
ssh->handshake = NULL;
WLOG(WS_LOG_DEBUG, "Keying completed");
@@ -9405,7 +9425,7 @@ static int DoPacket(WOLFSSH* ssh, byte* bufferConsumed)
case MSGID_KEXINIT:
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXINIT");
ret = DoKexInit(ssh, buf + idx, payloadSz, &payloadIdx);
- if (ssh->isKeying == 1 &&
+ if (ssh->isKeying &&
ssh->connectState == CONNECT_SERVER_CHANNEL_REQUEST_DONE) {
if (ssh->handshake->kexId == ID_DH_GEX_SHA256) {
#if !defined(WOLFSSH_NO_DH) && !defined(WOLFSSH_NO_DH_GEX_SHA256)
@@ -10501,7 +10521,8 @@ int SendKexInit(WOLFSSH* ssh)
}
if (ret == WS_SUCCESS) {
- ssh->isKeying = 1;
+ /* Set self is keying flag since we started sending the KEX init msg */
+ ssh->isKeying |= WOLFSSH_SELF_IS_KEYING;
if (ssh->handshake == NULL) {
ssh->handshake = HandshakeInfoNew(ssh->ctx->heap);
if (ssh->handshake == NULL) {
@@ -12534,9 +12555,13 @@ int SendNewKeys(WOLFSSH* ssh)
ssh->txCount = 0;
}
- if (ret == WS_SUCCESS)
+ if (ret == WS_SUCCESS) {
ret = wolfSSH_SendPacket(ssh);
+ /* Clear self is keying flag */
+ ssh->isKeying &= ~WOLFSSH_SELF_IS_KEYING;
+ }
+
WLOG(WS_LOG_DEBUG, "Leaving SendNewKeys(), ret = %d", ret);
return ret;
}
diff --git a/wolfssh/internal.h b/wolfssh/internal.h
index 1b7dada16..6df5f1147 100644
--- a/wolfssh/internal.h
+++ b/wolfssh/internal.h
@@ -473,6 +473,11 @@ enum NameIdType {
#define WOLFSSH_KEY_QUANTITY_REQ 1
#endif
+/* Keep track of keying state for both sides of the connection.
+ * WOLFSSH_SELF_IS_KEYING gets set on sending KEX init and
+ * WOLFSSH_PEER_IS_KEYING gets set on receiving KEX init */
+#define WOLFSSH_PEER_IS_KEYING 0x01
+#define WOLFSSH_SELF_IS_KEYING 0x02
WOLFSSH_LOCAL byte NameToId(const char* name, word32 nameSz);
WOLFSSH_LOCAL const char* IdToName(byte id);
From 024b14124aa2434e90468408b94121c897311f37 Mon Sep 17 00:00:00 2001
From: JacobBarthelmeh <jacob@wolfssl.com>
Date: Mon, 13 Oct 2025 22:47:45 -0600
Subject: [PATCH 2/2] update Kex Init response after adding keying track sides
---
src/internal.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/internal.c b/src/internal.c
index 230904489..db70d0f6f 100644
--- a/src/internal.c
+++ b/src/internal.c
@@ -4338,7 +4338,8 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
byte scratchLen[LENGTH_SZ];
word32 strSz = 0;
- if (!ssh->isKeying) {
+ /* respond with KEX Init message if not having initiated the keying */
+ if ((ssh->isKeying & WOLFSSH_SELF_IS_KEYING) == 0) {
WLOG(WS_LOG_DEBUG, "Keying initiated");
ret = SendKexInit(ssh);
}
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
From e9d288ec603531a1d544e77fb1bbdf634cb2a57f Mon Sep 17 00:00:00 2001
From: John Safranek <john@wolfssl.com>
Date: Mon, 13 Apr 2026 15:05:48 -0700
Subject: [PATCH] Server Does Not Set expectMsgId to MSGID_NEWKEYS
In the server code, the server is not setting the expectedMsgId to
MSGID_NEWKEYS before sending its new keys message. Update DoKexDhReply()
to set expectMsgId to MSGID_NEWKEYS.
Affected function: DoKeyDhReply.
Issue: F-1275
---
src/internal.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/internal.c b/src/internal.c
index 970bece9b..511d77ef2 100644
--- a/src/internal.c
+++ b/src/internal.c
@@ -12914,8 +12914,11 @@ int SendKexDhReply(WOLFSSH* ssh)
ret = BundlePacket(ssh);
}
- if (ret == WS_SUCCESS)
+ if (ret == WS_SUCCESS) {
+ ssh->handshake->expectMsgId = MSGID_NEWKEYS;
+ WLOG_EXPECT_MSGID(ssh->handshake->expectMsgId);
ret = SendNewKeys(ssh);
+ }
if (ret == WS_SUCCESS && ssh->sendExtInfo) {
ret = SendExtInfo(ssh);
+155
View File
@@ -0,0 +1,155 @@
# wolfSSH 1.4.20 restricted ordering correction
Project modification: **2026-09-16**. This is a bounded project-profile correction
for the CVE-2025-14942 ordering defect, **not a complete wolfSSH 1.4.22/1.5.0
backport or general-purpose upstream-fix claim**. wolfSSH remains pinned to
1.4.20; wolfSSL and all existing password/parser/crypto protections remain pinned
and intact. No managed source is edited.
## Inputs and provenance
`793.patch`, `819.patch`, `840.patch`, `855.patch`, and `921.patch` are the exact
upstream mail-patch responses fetched on 2026-09-16. `provenance.json` records
URLs, byte SHA-256 hashes, and every embedded full commit ID. PR endpoints can
change: the archived bytes and commit identities, not a future PR response,
identify this review. Commit pages are recoverable as
`https://github.com/wolfSSL/wolfssh/commit/<commit>`.
`delta.json` is the **authoritative executable consolidated delta**, not a claim
that all archived hunks apply. Its exact old/new edits are applied before the
existing parser/password edits by `tools/security_overrides.py`; every anchor
must occur exactly once. It pins:
| Input | Original SHA-256 |
| --- | --- |
| `src/internal.c` | `81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9` |
| `src/ssh.c` | `a4f479ff87eea0980ec1ebdf2c7dd090da473780181b695a56799cb9611f4366` |
| `wolfssh/internal.h` | `8e417149a68f8a6c0506957adf014b3e6c1727a723536826ce5fb0c9e1f1aba3` |
Original copyright/license notices remain in each generated file, preceded by
project modification/provenance notices. Configuration needs no network,
`patch(1)`, Git, or fuzzy matching. All inputs validate before outputs are written.
### Prerequisite disposition
- **PR793:** retain stream/channel read rekey fencing, stream-send fatal/error
convention, EOF/window-adjust send checks and send/receive direction constants.
Exclude example, SFTP, workflow and upstream-test changes. The intermediate
`IsMessageAllowedKeying` implementation was removed by PR855 itself; it is not
resurrected. Local send checks are distinct from receive checks. The follow-up
corrects a misapplied EOF hunk: `SendChannelEof` now checks before lookup,
serialization or `eofTxd` mutation. The extra `SendChannelExit` guard is retained
intentionally as local hardening, using `MSGID_CHANNEL_REQUEST` (exit-status),
not falsely attributed to PR793's EOF hunk. Real shutdown/rekey tests cover both.
- **PR819:** retain `extInfoSent` ABI field, but supersede its negotiation/send
logic by disabling EXT_INFO uniformly. The field stays zero; no reset-on-rekey
or exactly-once extension continuation is needed.
- **PR840:** initialize keying to zero; maintain independent SELF/PEER bits;
reject duplicate peer KEXINIT; respond only if SELF was not already keying;
reject peer NEWKEYS while SELF still owes NEWKEYS; clear PEER and release the
handshake only after successful incoming key installation.
- **PR855:** retain `expectMsgId`, message IDs/ranges, and expected-reply writes
**before** nonblocking sends. Adapt the client `DoKexDhReply` hunk to 1.4.20's
`useEccKyber` spelling; no PQ algorithm is enabled. Replace permissive range
fallthrough with the explicit restricted gates described below. Upstream
test-only entry points/build rules are not imported. Its new log macro is
local to generated `internal.c`: the pinned `log.h` has a different WLOG
implementation, so neither `log.h` nor `log.c` needs an ABI/source change.
- **PR921:** set server `expectMsgId = MSGID_NEWKEYS` before `SendNewKeys`, including
WANT_WRITE. The former post-NEWKEYS EXT_INFO call is deliberately removed.
Local additions also set the server's expected INIT after valid KEXINIT, retain
that expectation after skipping a wrong optimistic INIT guess, reject absent
expectations instead of accepting arbitrary KEX packets, and only allow client
rekey dispatch after successful/queued KEXINIT with a live handshake. Client
rekey dispatch includes the final `CONNECT_DONE` state.
## Restricted protocol contract
- Negotiate only the existing project KEX profile: **Curve25519-SHA256 and
ECDH-P256**. A caller trying to widen the algorithm list to DH/GEX/PQ gets a
negotiation error. Those continuation paths are not represented as supported.
- Receive transport notifications 14 without consuming an expected KEX reply.
KEXINIT is legal only before the peer has begun this exchange; other KEX
messages require a live handshake and an exact nonzero expectation. Acceptance
consumes that expectation once; the handler sets the next expectation.
- Reject authentication/service/connection traffic after **peer** KEXINIT until
peer NEWKEYS. When only SELF has initiated rekey, pre-peer-KEXINIT in-flight
traffic remains legal according to the authentication phase (RFC4253).
- Server requires keyed service request, then service acceptance before userauth
requests, then completed authentication before connection messages. Reject
wrong-direction and repeated authentication/service messages. Client rejects
premature auth results/channel messages and accepts auth responses only in
its request phase. Keyboard-interactive is outside the project profile and
incoming INFO_RESPONSE is rejected before dispatch.
- Send-side EOF/window-adjust checks never mutate receive expectations. Existing
channel-data rekey fencing is preserved.
### NEWKEYS/backpressure and the EXT_INFO choice
`SendNewKeys` bundles NEWKEYS with the old sending keys, installs the new sending
keys, then calls `wolfSSH_SendPacket`. `WS_WANT_WRITE` means that **same bundled
packet** remains in the bounded output buffer. Clear SELF on SUCCESS or
WANT_WRITE, retain PEER and the expected peer NEWKEYS. The existing
accept/connect/worker flush paths send the remaining bytes; they must not call
`SendNewKeys` again. A fatal send does not clear SELF. Peer NEWKEYS installs the
receive keys and releases the handshake once; premature/duplicate NEWKEYS fails.
Previously `SendKexDhReply` called `SendExtInfo` only on SUCCESS from NEWKEYS. A
partial send skipped it with no continuation. Rather than invent another pending
send state and its handshake-lifetime rules, this profile:
1. Does not append `ext-info-c` to client KEXINIT.
2. Ignores peers' extension willingness; sets `sendExtInfo` to zero on KEXINIT.
3. Removes the post-NEWKEYS extension send and makes `SendExtInfo` return
`WS_NOT_COMPILED` even if called directly.
4. Rejects incoming EXT_INFO in every phase, since it was never negotiated.
This follows RFC8308's optional-negotiation model. **There is no
`server-sig-algs` advertisement.** The existing KeyAccepted setter remains valid
but no longer produces that extension on the wire. This matters particularly to
RSA-SHA2 discovery: RSA user keys are outside the project's enrolled/advertised
profile, and RSA interoperability is not claimed. Host tests establish that
OpenSSH 10.2p1 still authenticates using Ed25519, P-256 and passwords with both
project KEX algorithms, initial KEX and rekeys, without receiving EXT_INFO.
Other clients and hardware remain validation work.
## Header overlay and build ownership
The ABI changes require **all** consumers to use the generated header, not only
the relocated C file. `cmake/security_overrides.cmake` installs
`security_overrides/wolfssh_include/wolfssh/internal.h` with BEFORE PUBLIC include
propagation and a PUBLIC forced include. The original header's include guard
prevents a later vendor-first include path from defining a stale layout. A dated
ABI marker rejects an original header forcibly included *before* the overlay,
rather than silently skipping the corrected layout. Existing
unmodified wolfSSH files and transitive application consumers therefore use the
same layout. Source properties are retained; header originals, generated header,
and `delta.json` are configure dependencies. Compiler dependencies track the
forced header too. There are now **eight source overrides plus one header**.
Tests intentionally put the original include root before the overlay when
compiling full host translation units. The SDK CMake fixture checks PUBLIC
propagation to library, direct and transitive consumers. Forced headers use joined
`-include/path` arguments: PlatformIO sorts app flags and deduplicates component
flags, which breaks split option/operand pairs. The installed adapter/SCons test
compiles a real Xtensa consumer and reproduces the failure with a split-option
mutation. The crypto guard remains enabled with its existing joined argument.
The obsolete unused EXT_INFO name constant is removed, without relaxing warnings.
## Validation and boundaries
Run `python3 tests/wolfssh_order_contract/run.py --interop` and the related
commands listed in that test's README. Tests execute the actual generated full
`internal.c` and `ssh.c` with real wolfCrypt, bounded host IO, hostile message-ID
matrices, partial writes, both roles and all three rekey initiation directions.
Guard-removal mutations must fail. The parser/password suites separately preserve
prior protections; SDK tests exercise real override/overlay CMake wiring.
Follow-up validation on 2026-09-16: authorized `pio run` PASS in 21.31 seconds,
**94,340 B linked RAM / 1,768,701 B flash**. Strict auth, protocol, crypto and
SDK override tests against the rebuilt production artifacts PASS. Ordering tests
PASS 8,028 checks and seven rejected mutations; parser tests PASS 3,124 cases in
each stack mode. No upload, erase, dependency upgrade or device operation.
This does not establish stack/heap reserve, timing, arbitrary-algorithm,
strict-KEX-extension, full-client compatibility or phase-wide security sign-off.
+161
View File
@@ -0,0 +1,161 @@
{
"src/internal.c": {
"sha256": "81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9",
"edits": [
{
"old": " * API calls into this module to do the work of processing the connections.\n */\n\n\n#ifdef HAVE_CONFIG_H\n #include <config.h>\n#endif\n\n#include <stdio.h>\n#include <wolfssh/ssh.h>\n#include <wolfssh/internal.h>\n#include <wolfssh/log.h>\n#include <wolfssl/version.h>\n#include <wolfssl/wolfcrypt/asn.h>\n#ifndef WOLFSSH_NO_DH\n #include <wolfssl/wolfcrypt/dh.h>\n#endif\n#include <wolfssl/wolfcrypt/curve25519.h>\n#include <wolfssl/wolfcrypt/ed25519.h>\n#ifdef WOLFSSH_CERTS\n #include <wolfssl/wolfcrypt/error-crypt.h>\n#endif\n#include <wolfssl/wolfcrypt/rsa.h>\n#include <wolfssl/wolfcrypt/ecc.h>\n",
"new": " * API calls into this module to do the work of processing the connections.\n */\n\n\n#ifdef HAVE_CONFIG_H\n #include <config.h>\n#endif\n\n#include <stdio.h>\n#include <wolfssh/ssh.h>\n#include <wolfssh/internal.h>\n#include <wolfssh/log.h>\n#define WLOG_EXPECT_MSGID(x) WLOG(WS_LOG_DEBUG, \"Expecting message %d\", (x))\n#include <wolfssl/version.h>\n#include <wolfssl/wolfcrypt/asn.h>\n#ifndef WOLFSSH_NO_DH\n #include <wolfssl/wolfcrypt/dh.h>\n#endif\n#include <wolfssl/wolfcrypt/curve25519.h>\n#include <wolfssl/wolfcrypt/ed25519.h>\n#ifdef WOLFSSH_CERTS\n #include <wolfssl/wolfcrypt/error-crypt.h>\n#endif\n#include <wolfssl/wolfcrypt/rsa.h>\n#include <wolfssl/wolfcrypt/ecc.h>\n"
},
{
"old": " WFREE(hs->primeGroup, heap, DYNTYPE_MPINT);\n WFREE(hs->generator, heap, DYNTYPE_MPINT);\n#endif\n if (hs->kexHashId != WC_HASH_TYPE_NONE) {\n wc_HashFree(&hs->kexHash, (enum wc_HashType)hs->kexHashId);\n }\n ForceZero(hs, sizeof(HandshakeInfo));\n WFREE(hs, heap, DYNTYPE_HS);\n }\n}\n\n\n#ifndef NO_WOLFSSH_SERVER\nINLINE static int IsMessageAllowedServer(WOLFSSH *ssh, byte msg)\n{\n /* Has client userauth started? */\n if (ssh->acceptState < ACCEPT_KEYED) {\n if (msg > MSGID_KEXDH_LIMIT) {\n return 0;\n }\n }\n /* Is server userauth complete? */\n if (ssh->acceptState < ACCEPT_SERVER_USERAUTH_SENT) {\n /* Explicitly check for messages not allowed before user\n * authentication has comleted. */\n if (msg >= MSGID_USERAUTH_LIMIT) {\n WLOG(WS_LOG_DEBUG, \"Message ID %u not allowed by server \"\n \"before user authentication is complete\", msg);\n return 0;\n }\n /* Explicitly check for the user authentication messages that\n * only the server sends, it shouldn't receive them. */\n if ((msg > MSGID_USERAUTH_RESTRICT) &&\n (msg != MSGID_USERAUTH_INFO_RESPONSE)) {\n WLOG(WS_LOG_DEBUG, \"Message ID %u not allowed by server \"\n \"during user authentication\", msg);\n return 0;\n }\n }\n else {\n if (msg >= MSGID_USERAUTH_RESTRICT && msg < MSGID_USERAUTH_LIMIT) {\n WLOG(WS_LOG_DEBUG, \"Message ID %u not allowed by server \"\n \"after user authentication\", msg);\n return 0;\n }\n }\n\n return 1;\n}\n#endif /* NO_WOLFSSH_SERVER */\n\n\n#ifndef NO_WOLFSSH_CLIENT\nINLINE static int IsMessageAllowedClient(WOLFSSH *ssh, byte msg)\n{\n /* Has client userauth started? */\n if (ssh->connectState < CONNECT_CLIENT_KEXDH_INIT_SENT) {\n if (msg >= MSGID_KEXDH_LIMIT) {\n return 0;\n }\n }\n /* Is client userauth complete? */\n if (ssh->connectState < CONNECT_SERVER_USERAUTH_ACCEPT_DONE) {\n /* Explicitly check for messages not allowed before user\n * authentication has comleted. */\n if (msg >= MSGID_USERAUTH_LIMIT) {\n WLOG(WS_LOG_DEBUG, \"Message ID %u not allowed by client \"\n \"before user authentication is complete\", msg);\n return 0;\n }\n /* Explicitly check for the user authentication message that\n * only the client sends, it shouldn't receive it. */\n if (msg == MSGID_USERAUTH_RESTRICT) {\n WLOG(WS_LOG_DEBUG, \"Message ID %u not allowed by client \"\n \"during user authentication\", msg);\n return 0;\n }\n }\n else {\n if (msg >= MSGID_USERAUTH_RESTRICT && msg < MSGID_USERAUTH_LIMIT) {\n WLOG(WS_LOG_DEBUG, \"Message ID %u not allowed by client \"\n \"after user authentication\", msg);\n return 0;\n }\n }\n return 1;\n}\n#endif /* NO_WOLFSSH_CLIENT */\n\n\nINLINE static int IsMessageAllowed(WOLFSSH *ssh, byte msg)\n{\n#ifndef NO_WOLFSSH_SERVER\n if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER) {\n return IsMessageAllowedServer(ssh, msg);\n }\n#endif /* NO_WOLFSSH_SERVER */\n#ifndef NO_WOLFSSH_CLIENT\n if (ssh->ctx->side == WOLFSSH_ENDPOINT_CLIENT) {\n return IsMessageAllowedClient(ssh, msg);\n }\n#endif /* NO_WOLFSSH_CLIENT */\n return 0;\n}\n\n\nstatic const char cannedKexAlgoNames[] =\n#if !defined(WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256)\n \"ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org,\"\n#endif\n#ifndef WOLFSSH_NO_CURVE25519_SHA256\n \"curve25519-sha256,\"\n#endif\n#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP521)\n",
"new": " WFREE(hs->primeGroup, heap, DYNTYPE_MPINT);\n WFREE(hs->generator, heap, DYNTYPE_MPINT);\n#endif\n if (hs->kexHashId != WC_HASH_TYPE_NONE) {\n wc_HashFree(&hs->kexHash, (enum wc_HashType)hs->kexHashId);\n }\n ForceZero(hs, sizeof(HandshakeInfo));\n WFREE(hs, heap, DYNTYPE_HS);\n }\n}\n\n\n/* Project restricted ordering profile, derived from wolfSSH PR855.\n * EXT_INFO is deliberately not negotiated. See README.md for scope.\n * This fragment is installed into the pinned internal.c by the exact delta.\n */\n#ifndef NO_WOLFSSH_SERVER\nINLINE static int IsMessageAllowedServer(WOLFSSH* ssh, byte msg)\n{\n if (msg == MSGID_SERVICE_REQUEST)\n return ssh->acceptState == ACCEPT_KEYED;\n if (msg == MSGID_USERAUTH_REQUEST)\n return ssh->acceptState >= ACCEPT_SERVER_USERAUTH_ACCEPT_SENT &&\n ssh->acceptState < ACCEPT_SERVER_USERAUTH_SENT;\n /* Keyboard-interactive is not part of the project profile. */\n return MSGIDLIMIT_POST_USERAUTH(msg) &&\n ssh->acceptState >= ACCEPT_SERVER_USERAUTH_SENT;\n}\n#endif\n\n#ifndef NO_WOLFSSH_CLIENT\nINLINE static int IsMessageAllowedClient(WOLFSSH* ssh, byte msg)\n{\n if (msg == MSGID_SERVICE_ACCEPT)\n return ssh->connectState == CONNECT_CLIENT_USERAUTH_REQUEST_SENT;\n if (msg == MSGID_USERAUTH_FAILURE || msg == MSGID_USERAUTH_SUCCESS ||\n msg == MSGID_USERAUTH_BANNER || msg == MSGID_USERAUTH_PK_OK)\n return ssh->connectState >= CONNECT_CLIENT_USERAUTH_SENT &&\n ssh->connectState < CONNECT_SERVER_USERAUTH_ACCEPT_DONE;\n return MSGIDLIMIT_POST_USERAUTH(msg) &&\n ssh->connectState >= CONNECT_SERVER_USERAUTH_ACCEPT_DONE;\n}\n#endif\n\nINLINE static int IsMessageAllowed(WOLFSSH* ssh, byte msg, byte state)\n{\n int allowed = 0;\n if (state == WS_MSG_SEND) {\n /* EOF/window-adjust callers must not consume receive expectations. */\n allowed = !ssh->isKeying;\n if (!allowed)\n ssh->error = WS_REKEYING;\n return allowed;\n }\n if (state != WS_MSG_RECV)\n goto reject;\n\n /* RFC4253 transport notifications may interrupt an expected KEX message. */\n if (msg >= MSGID_DISCONNECT && msg <= MSGID_DEBUG)\n return 1;\n\n /* No RFC8308 negotiation, including during rekey or after authentication. */\n if (msg == MSGID_EXT_INFO)\n goto reject;\n\n if (MSGIDLIMIT_TRANS_ALGO(msg) || MSGIDLIMIT_TRANS_KEX(msg)) {\n if (msg == MSGID_KEXINIT) {\n allowed = !(ssh->isKeying & WOLFSSH_PEER_IS_KEYING);\n }\n else if ((ssh->isKeying & WOLFSSH_PEER_IS_KEYING) &&\n ssh->handshake != NULL &&\n ssh->handshake->expectMsgId != MSGID_NONE &&\n ssh->handshake->expectMsgId == msg) {\n ssh->handshake->expectMsgId = MSGID_NONE;\n return 1;\n }\n if (allowed)\n return 1;\n goto reject;\n }\n\n /* Locally initiated rekey can have old, in-flight peer traffic. Once the\n * peer KEXINIT arrives, only transport/KEX is legal until peer NEWKEYS. */\n if (ssh->isKeying & WOLFSSH_PEER_IS_KEYING)\n goto reject;\n#ifndef NO_WOLFSSH_SERVER\n if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER)\n allowed = IsMessageAllowedServer(ssh, msg);\n#endif\n#ifndef NO_WOLFSSH_CLIENT\n if (ssh->ctx->side == WOLFSSH_ENDPOINT_CLIENT)\n allowed = IsMessageAllowedClient(ssh, msg);\n#endif\n if (allowed)\n return 1;\nreject:\n ssh->error = WS_MSGID_NOT_ALLOWED_E;\n return 0;\n}\n\n\nstatic const char cannedKexAlgoNames[] =\n#if !defined(WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256)\n \"ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org,\"\n#endif\n#ifndef WOLFSSH_NO_CURVE25519_SHA256\n \"curve25519-sha256,\"\n#endif\n#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP521)\n"
},
{
"old": "#else\n ssh->rfd = -1; /* set to invalid */\n ssh->wfd = -1; /* set to invalid */\n#endif\n ssh->ioReadCtx = &ssh->rfd; /* prevent invalid access if not correctly */\n ssh->ioWriteCtx = &ssh->wfd; /* set */\n ssh->highwaterMark = ctx->highwaterMark;\n ssh->highwaterCtx = (void*)ssh;\n ssh->reqSuccessCtx = (void*)ssh;\n ssh->fs = NULL;\n ssh->acceptState = ACCEPT_BEGIN;\n ssh->clientState = CLIENT_BEGIN;\n ssh->isKeying = 1;\n ssh->authId = ID_USERAUTH_PUBLICKEY;\n ssh->supportedAuth[0] = ID_USERAUTH_PUBLICKEY;\n ssh->supportedAuth[1] = ID_USERAUTH_PASSWORD;\n ssh->supportedAuth[2] = ID_USERAUTH_KEYBOARD;\n ssh->supportedAuth[3] = ID_NONE; /* ID_NONE is treated as empty slot */\n ssh->nextChannel = DEFAULT_NEXT_CHANNEL;\n ssh->blockSz = MIN_BLOCK_SZ;\n ssh->encryptId = ID_NONE;\n ssh->macId = ID_NONE;\n ssh->peerBlockSz = MIN_BLOCK_SZ;\n ssh->rng = rng;\n ssh->kSz = (word32)sizeof(ssh->k);\n",
"new": "#else\n ssh->rfd = -1; /* set to invalid */\n ssh->wfd = -1; /* set to invalid */\n#endif\n ssh->ioReadCtx = &ssh->rfd; /* prevent invalid access if not correctly */\n ssh->ioWriteCtx = &ssh->wfd; /* set */\n ssh->highwaterMark = ctx->highwaterMark;\n ssh->highwaterCtx = (void*)ssh;\n ssh->reqSuccessCtx = (void*)ssh;\n ssh->fs = NULL;\n ssh->acceptState = ACCEPT_BEGIN;\n ssh->clientState = CLIENT_BEGIN;\n ssh->isKeying = 0; /* initial state of not keying yet */\n ssh->authId = ID_USERAUTH_PUBLICKEY;\n ssh->supportedAuth[0] = ID_USERAUTH_PUBLICKEY;\n ssh->supportedAuth[1] = ID_USERAUTH_PASSWORD;\n ssh->supportedAuth[2] = ID_USERAUTH_KEYBOARD;\n ssh->supportedAuth[3] = ID_NONE; /* ID_NONE is treated as empty slot */\n ssh->nextChannel = DEFAULT_NEXT_CHANNEL;\n ssh->blockSz = MIN_BLOCK_SZ;\n ssh->encryptId = ID_NONE;\n ssh->macId = ID_NONE;\n ssh->peerBlockSz = MIN_BLOCK_SZ;\n ssh->rng = rng;\n ssh->kSz = (word32)sizeof(ssh->k);\n"
},
{
"old": " word32 cannedAlgoNamesSz;\n word32 skipSz = 0;\n word32 begin;\n\n WLOG(WS_LOG_DEBUG, \"Entering DoKexInit()\");\n\n if (ssh == NULL || ssh->ctx == NULL ||\n buf == NULL || len == 0 || idx == NULL) {\n\n ret = WS_BAD_ARGUMENT;\n }\n\n /*\n * I don't need to save what the client sends here. I should decode\n * each list into a local array of IDs, and pick the one the peer is\n * using that's on my known list, or verify that the one the peer can\n * support the other direction is on my known list. All I need to do\n * is save the actual values.\n */\n\n if (ret == WS_SUCCESS) {\n if (ssh->handshake == NULL) {\n ssh->handshake = HandshakeInfoNew(ssh->ctx->heap);\n if (ssh->handshake == NULL) {\n WLOG(WS_LOG_DEBUG, \"Couldn't allocate handshake info\");\n ret = WS_MEMORY_E;\n }\n }\n }\n\n if (ret == WS_SUCCESS) {\n begin = *idx;\n side = ssh->ctx->side;\n",
"new": " word32 cannedAlgoNamesSz;\n word32 skipSz = 0;\n word32 begin;\n\n WLOG(WS_LOG_DEBUG, \"Entering DoKexInit()\");\n\n if (ssh == NULL || ssh->ctx == NULL ||\n buf == NULL || len == 0 || idx == NULL) {\n\n ret = WS_BAD_ARGUMENT;\n }\n\n if (ret == WS_SUCCESS) {\n /* Check if already in process of keying and error out if so. */\n if (ssh->isKeying & WOLFSSH_PEER_IS_KEYING) {\n WLOG(WS_LOG_ERROR,\n \"Already in keying process and got KEX init\");\n ret = WS_INVALID_STATE_E;\n }\n }\n\n /*\n * I don't need to save what the client sends here. I should decode\n * each list into a local array of IDs, and pick the one the peer is\n * using that's on my known list, or verify that the one the peer can\n * support the other direction is on my known list. All I need to do\n * is save the actual values.\n */\n\n if (ret == WS_SUCCESS) {\n /* Set peer is keying flag after receiving SSH_MSG_KEX_INIT */\n ssh->isKeying |= WOLFSSH_PEER_IS_KEYING;\n if (ssh->handshake == NULL) {\n ssh->handshake = HandshakeInfoNew(ssh->ctx->heap);\n if (ssh->handshake == NULL) {\n WLOG(WS_LOG_DEBUG, \"Couldn't allocate handshake info\");\n ret = WS_MEMORY_E;\n }\n }\n }\n\n if (ret == WS_SUCCESS) {\n begin = *idx;\n side = ssh->ctx->side;\n"
},
{
"old": " (const byte*)ssh->algoListKex, cannedAlgoNamesSz);\n }\n if (ret == WS_SUCCESS) {\n ssh->handshake->kexIdGuess = list[0];\n algoId = MatchIdLists(side, list, listSz,\n cannedList, cannedListSz);\n if (algoId == ID_UNKNOWN) {\n WLOG(WS_LOG_DEBUG, \"Unable to negotiate KEX Algo\");\n ret = WS_MATCH_KEX_ALGO_E;\n }\n }\n if (ret == WS_SUCCESS) {\n ssh->kexId = ssh->handshake->kexId = algoId;\n ssh->handshake->kexHashId = HashForId(algoId);\n }\n /* Extension Info Flag */\n if (ret == WS_SUCCESS) {\n /* Only checking for this is we are server. Our client does\n * not have anything to say to a server, yet. */\n if (side == WOLFSSH_ENDPOINT_SERVER) {\n byte extInfo;\n\n /* Match the client accepts extInfo. */\n algoId = ID_EXTINFO_C;\n extInfo = MatchIdLists(side, list, listSz, &algoId, 1);\n ssh->sendExtInfo = extInfo == algoId;\n }\n }\n\n /* Server Host Key Algorithms */\n if (ret == WS_SUCCESS) {\n WLOG(WS_LOG_DEBUG, \"DKI: Server Host Key Algorithms\");\n listSz = (word32)sizeof(list);\n ret = GetNameList(list, &listSz, buf, len, &begin);\n }\n if (ret == WS_SUCCESS) {\n if (side == WOLFSSH_ENDPOINT_SERVER && !ssh->algoListKey) {\n cannedListSz = ssh->ctx->publicKeyAlgoCount;\n WMEMCPY(cannedList, ssh->ctx->publicKeyAlgo, cannedListSz);\n }\n",
"new": " (const byte*)ssh->algoListKex, cannedAlgoNamesSz);\n }\n if (ret == WS_SUCCESS) {\n ssh->handshake->kexIdGuess = list[0];\n algoId = MatchIdLists(side, list, listSz,\n cannedList, cannedListSz);\n if (algoId == ID_UNKNOWN) {\n WLOG(WS_LOG_DEBUG, \"Unable to negotiate KEX Algo\");\n ret = WS_MATCH_KEX_ALGO_E;\n }\n }\n if (ret == WS_SUCCESS) {\n /* The reviewed project profile has only these two KEX algorithms.\n * Do not silently enable an untested GEX/PQ continuation. */\n if (algoId != ID_CURVE25519_SHA256 && algoId != ID_ECDH_SHA2_NISTP256)\n ret = WS_MATCH_KEX_ALGO_E;\n ssh->kexId = ssh->handshake->kexId = algoId;\n ssh->handshake->kexHashId = HashForId(algoId);\n }\n /* RFC8308 is optional. This profile never sends EXT_INFO, including\n * after a nonblocking NEWKEYS send or on subsequent rekeys. */\n ssh->sendExtInfo = 0;\n\n /* Server Host Key Algorithms */\n if (ret == WS_SUCCESS) {\n WLOG(WS_LOG_DEBUG, \"DKI: Server Host Key Algorithms\");\n listSz = (word32)sizeof(list);\n ret = GetNameList(list, &listSz, buf, len, &begin);\n }\n if (ret == WS_SUCCESS) {\n if (side == WOLFSSH_ENDPOINT_SERVER && !ssh->algoListKey) {\n cannedListSz = ssh->ctx->publicKeyAlgoCount;\n WMEMCPY(cannedList, ssh->ctx->publicKeyAlgo, cannedListSz);\n }\n"
},
{
"old": " WLOG(WS_LOG_DEBUG, \"DKI: For Future Use\");\n ret = GetUint32(&skipSz, buf, len, &begin);\n if (ret == WS_SUCCESS)\n begin += skipSz;\n }\n\n if (ret == WS_SUCCESS) {\n wc_HashAlg* hash = &ssh->handshake->kexHash;\n enum wc_HashType hashId = (enum wc_HashType)ssh->handshake->kexHashId;\n byte scratchLen[LENGTH_SZ];\n word32 strSz = 0;\n\n if (!ssh->isKeying) {\n WLOG(WS_LOG_DEBUG, \"Keying initiated\");\n ret = SendKexInit(ssh);\n }\n\n /* account for possible want write case from SendKexInit */\n if (ret == WS_SUCCESS || ret == WS_WANT_WRITE)\n ret = wc_HashInit(hash, hashId);\n\n if (ret == WS_SUCCESS) {\n if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER) {\n ret = HashUpdate(hash, hashId,\n ssh->peerProtoId, ssh->peerProtoIdSz);\n",
"new": " WLOG(WS_LOG_DEBUG, \"DKI: For Future Use\");\n ret = GetUint32(&skipSz, buf, len, &begin);\n if (ret == WS_SUCCESS)\n begin += skipSz;\n }\n\n if (ret == WS_SUCCESS) {\n wc_HashAlg* hash = &ssh->handshake->kexHash;\n enum wc_HashType hashId = (enum wc_HashType)ssh->handshake->kexHashId;\n byte scratchLen[LENGTH_SZ];\n word32 strSz = 0;\n\n /* respond with KEX Init message if not having initiated the keying */\n if ((ssh->isKeying & WOLFSSH_SELF_IS_KEYING) == 0) {\n WLOG(WS_LOG_DEBUG, \"Keying initiated\");\n ret = SendKexInit(ssh);\n }\n\n /* account for possible want write case from SendKexInit */\n if (ret == WS_SUCCESS || ret == WS_WANT_WRITE)\n ret = wc_HashInit(hash, hashId);\n\n if (ret == WS_SUCCESS) {\n if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER) {\n ret = HashUpdate(hash, hashId,\n ssh->peerProtoId, ssh->peerProtoIdSz);\n"
},
{
"old": "\n if (ret == WS_SUCCESS)\n ret = HashUpdate(hash, hashId, buf, len);\n\n if (ret == WS_SUCCESS) {\n if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER)\n ret = HashUpdate(hash, hashId,\n ssh->handshake->kexInit, ssh->handshake->kexInitSz);\n }\n\n if (ret == WS_SUCCESS) {\n *idx = begin;\n if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER)\n ssh->clientState = CLIENT_KEXINIT_DONE;\n else\n ssh->serverState = SERVER_KEXINIT_DONE;\n\n /* Propagate potential want write case from SendKexInit. */\n if (ssh->error != 0)\n ret = ssh->error;\n }\n }\n WLOG(WS_LOG_DEBUG, \"Leaving DoKexInit(), ret = %d\", ret);\n return ret;\n}\n\n",
"new": "\n if (ret == WS_SUCCESS)\n ret = HashUpdate(hash, hashId, buf, len);\n\n if (ret == WS_SUCCESS) {\n if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER)\n ret = HashUpdate(hash, hashId,\n ssh->handshake->kexInit, ssh->handshake->kexInitSz);\n }\n\n if (ret == WS_SUCCESS) {\n *idx = begin;\n if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER) {\n ssh->handshake->expectMsgId = MSGID_KEXDH_INIT;\n ssh->clientState = CLIENT_KEXINIT_DONE;\n }\n else\n ssh->serverState = SERVER_KEXINIT_DONE;\n\n /* Propagate potential want write case from SendKexInit. */\n if (ssh->error != 0)\n ret = ssh->error;\n }\n }\n WLOG(WS_LOG_DEBUG, \"Leaving DoKexInit(), ret = %d\", ret);\n return ret;\n}\n\n"
},
{
"old": "\n if (ssh == NULL || ssh->handshake == NULL || buf == NULL || len == 0 ||\n idx == NULL)\n ret = WS_BAD_ARGUMENT;\n\n if (ret == WS_SUCCESS) {\n if (ssh->handshake->kexPacketFollows\n && ssh->handshake->kexIdGuess != ssh->handshake->kexId) {\n\n /* skip this message. */\n WLOG(WS_LOG_DEBUG, \"Skipping the client's KEX init function.\");\n ssh->handshake->kexPacketFollows = 0;\n *idx += len;\n return WS_SUCCESS;\n }\n }\n\n if (ret == WS_SUCCESS) {\n begin = *idx;\n ret = GetUint32(&eSz, buf, len, &begin);\n }\n\n if (ret == WS_SUCCESS) {\n /* Validate eSz */\n",
"new": "\n if (ssh == NULL || ssh->handshake == NULL || buf == NULL || len == 0 ||\n idx == NULL)\n ret = WS_BAD_ARGUMENT;\n\n if (ret == WS_SUCCESS) {\n if (ssh->handshake->kexPacketFollows\n && ssh->handshake->kexIdGuess != ssh->handshake->kexId) {\n\n /* skip this message. */\n WLOG(WS_LOG_DEBUG, \"Skipping the client's KEX init function.\");\n ssh->handshake->kexPacketFollows = 0;\n ssh->handshake->expectMsgId = MSGID_KEXDH_INIT;\n *idx += len;\n return WS_SUCCESS;\n }\n }\n\n if (ret == WS_SUCCESS) {\n begin = *idx;\n ret = GetUint32(&eSz, buf, len, &begin);\n }\n\n if (ret == WS_SUCCESS) {\n /* Validate eSz */\n"
},
{
"old": " ret = WS_INVALID_ALGO_ID;\n }\n }\n }\n FreePubKey(sigKeyBlock_ptr);\n }\n\n if (ret == WS_SUCCESS) {\n /* If we aren't using EccKyber, use padding. */\n ret = GenerateKeys(ssh, hashId, !ssh->handshake->useEccKyber);\n }\n\n if (ret == WS_SUCCESS)\n ret = SendNewKeys(ssh);\n\n if (sigKeyBlock_ptr)\n WFREE(sigKeyBlock_ptr, ssh->ctx->heap, DYNTYPE_PRIVKEY);\n WLOG(WS_LOG_DEBUG, \"Leaving DoKexDhReply(), ret = %d\", ret);\n return ret;\n}\n\n\nstatic int DoNewKeys(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)\n{\n int ret = WS_SUCCESS;\n\n WOLFSSH_UNUSED(buf);\n WOLFSSH_UNUSED(len);\n WOLFSSH_UNUSED(idx);\n\n if (ssh == NULL || ssh->handshake == NULL)\n ret = WS_BAD_ARGUMENT;\n\n if (ret == WS_SUCCESS) {\n ssh->peerEncryptId = ssh->handshake->encryptId;\n ssh->peerMacId = ssh->handshake->macId;\n ssh->peerBlockSz = ssh->handshake->blockSz;\n ssh->peerMacSz = ssh->handshake->macSz;\n ssh->peerAeadMode = ssh->handshake->aeadMode;\n WMEMCPY(&ssh->peerKeys, &ssh->handshake->peerKeys, sizeof(Keys));\n\n switch (ssh->peerEncryptId) {\n case ID_NONE:\n WLOG(WS_LOG_DEBUG, \"DNK: peer using cipher none\");\n",
"new": " ret = WS_INVALID_ALGO_ID;\n }\n }\n }\n FreePubKey(sigKeyBlock_ptr);\n }\n\n if (ret == WS_SUCCESS) {\n /* If we aren't using EccKyber, use padding. */\n ret = GenerateKeys(ssh, hashId, !ssh->handshake->useEccKyber);\n }\n\n if (ret == WS_SUCCESS) {\n ssh->handshake->expectMsgId = MSGID_NEWKEYS;\n WLOG_EXPECT_MSGID(ssh->handshake->expectMsgId);\n ret = SendNewKeys(ssh);\n }\n\n if (sigKeyBlock_ptr)\n WFREE(sigKeyBlock_ptr, ssh->ctx->heap, DYNTYPE_PRIVKEY);\n WLOG(WS_LOG_DEBUG, \"Leaving DoKexDhReply(), ret = %d\", ret);\n return ret;\n}\n\n\nstatic int DoNewKeys(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)\n{\n int ret = WS_SUCCESS;\n\n WOLFSSH_UNUSED(buf);\n WOLFSSH_UNUSED(len);\n WOLFSSH_UNUSED(idx);\n\n if (ssh == NULL || ssh->handshake == NULL)\n ret = WS_BAD_ARGUMENT;\n\n if (ret == WS_SUCCESS) {\n if ((ssh->isKeying & WOLFSSH_SELF_IS_KEYING) ||\n !(ssh->isKeying & WOLFSSH_PEER_IS_KEYING)) {\n WLOG(WS_LOG_ERROR, \"Keying failed\");\n ret = WS_INVALID_STATE_E;\n }\n }\n\n if (ret == WS_SUCCESS) {\n ssh->peerEncryptId = ssh->handshake->encryptId;\n ssh->peerMacId = ssh->handshake->macId;\n ssh->peerBlockSz = ssh->handshake->blockSz;\n ssh->peerMacSz = ssh->handshake->macSz;\n ssh->peerAeadMode = ssh->handshake->aeadMode;\n WMEMCPY(&ssh->peerKeys, &ssh->handshake->peerKeys, sizeof(Keys));\n\n switch (ssh->peerEncryptId) {\n case ID_NONE:\n WLOG(WS_LOG_DEBUG, \"DNK: peer using cipher none\");\n"
},
{
"old": " break;\n }\n\n if (ret == 0)\n ret = WS_SUCCESS;\n else\n ret = WS_CRYPTO_FAILED;\n }\n\n if (ret == WS_SUCCESS) {\n ssh->rxCount = 0;\n ssh->highwaterFlag = 0;\n ssh->isKeying = 0;\n HandshakeInfoFree(ssh->handshake, ssh->ctx->heap);\n ssh->handshake = NULL;\n WLOG(WS_LOG_DEBUG, \"Keying completed\");\n\n if (ssh->ctx->keyingCompletionCb)\n ssh->ctx->keyingCompletionCb(ssh->keyingCompletionCtx);\n }\n\n return ret;\n}\n\n\n#ifndef WOLFSSH_NO_DH_GEX_SHA256\nstatic int DoKexDhGexRequest(WOLFSSH* ssh,\n byte* buf, word32 len, word32* idx)\n{\n",
"new": " break;\n }\n\n if (ret == 0)\n ret = WS_SUCCESS;\n else\n ret = WS_CRYPTO_FAILED;\n }\n\n if (ret == WS_SUCCESS) {\n ssh->rxCount = 0;\n ssh->highwaterFlag = 0;\n\n /* Clear peer is keying flag */\n ssh->isKeying &= ~WOLFSSH_PEER_IS_KEYING;\n HandshakeInfoFree(ssh->handshake, ssh->ctx->heap);\n ssh->handshake = NULL;\n WLOG(WS_LOG_DEBUG, \"Keying completed\");\n if (ssh->ctx->keyingCompletionCb)\n ssh->ctx->keyingCompletionCb(ssh->keyingCompletionCtx);\n }\n\n return ret;\n}\n\n\n#ifndef WOLFSSH_NO_DH_GEX_SHA256\nstatic int DoKexDhGexRequest(WOLFSSH* ssh,\n byte* buf, word32 len, word32* idx)\n{\n"
},
{
"old": "\n msg = buf[idx++];\n /* At this point, payload starts at \"buf + idx\". */\n\n /* sanity check on payloadSz. Uses \"or\" condition because of the case when\n * adding idx to payloadSz causes it to overflow.\n */\n if ((ssh->inputBuffer.bufferSz < payloadSz + idx) ||\n (payloadSz + idx < payloadSz)) {\n return WS_OVERFLOW_E;\n }\n\n if (!IsMessageAllowed(ssh, msg)) {\n return WS_MSGID_NOT_ALLOWED_E;\n }\n\n switch (msg) {\n\n case MSGID_DISCONNECT:\n WLOG(WS_LOG_DEBUG, \"Decoding MSGID_DISCONNECT\");\n ret = DoDisconnect(ssh, buf + idx, payloadSz, &payloadIdx);\n break;\n\n case MSGID_IGNORE:\n WLOG(WS_LOG_DEBUG, \"Decoding MSGID_IGNORE\");\n",
"new": "\n msg = buf[idx++];\n /* At this point, payload starts at \"buf + idx\". */\n\n /* sanity check on payloadSz. Uses \"or\" condition because of the case when\n * adding idx to payloadSz causes it to overflow.\n */\n if ((ssh->inputBuffer.bufferSz < payloadSz + idx) ||\n (payloadSz + idx < payloadSz)) {\n return WS_OVERFLOW_E;\n }\n\n if (!IsMessageAllowed(ssh, msg, WS_MSG_RECV)) {\n return WS_MSGID_NOT_ALLOWED_E;\n }\n\n switch (msg) {\n\n case MSGID_DISCONNECT:\n WLOG(WS_LOG_DEBUG, \"Decoding MSGID_DISCONNECT\");\n ret = DoDisconnect(ssh, buf + idx, payloadSz, &payloadIdx);\n break;\n\n case MSGID_IGNORE:\n WLOG(WS_LOG_DEBUG, \"Decoding MSGID_IGNORE\");\n"
},
{
"old": " WLOG(WS_LOG_DEBUG, \"Decoding MSGID_DEBUG\");\n ret = DoDebug(ssh, buf + idx, payloadSz, &payloadIdx);\n break;\n\n case MSGID_EXT_INFO:\n WLOG(WS_LOG_DEBUG, \"Decoding MSGID_EXT_INFO\");\n ret = DoExtInfo(ssh, buf + idx, payloadSz, &payloadIdx);\n break;\n\n case MSGID_KEXINIT:\n WLOG(WS_LOG_DEBUG, \"Decoding MSGID_KEXINIT\");\n ret = DoKexInit(ssh, buf + idx, payloadSz, &payloadIdx);\n if (ssh->isKeying == 1 &&\n ssh->connectState == CONNECT_SERVER_CHANNEL_REQUEST_DONE) {\n if (ssh->handshake->kexId == ID_DH_GEX_SHA256) {\n#if !defined(WOLFSSH_NO_DH) && !defined(WOLFSSH_NO_DH_GEX_SHA256)\n ssh->error = SendKexDhGexRequest(ssh);\n#endif\n }\n else\n ssh->error = SendKexDhInit(ssh);\n }\n break;\n\n case MSGID_NEWKEYS:\n WLOG(WS_LOG_DEBUG, \"Decoding MSGID_NEWKEYS\");\n",
"new": " WLOG(WS_LOG_DEBUG, \"Decoding MSGID_DEBUG\");\n ret = DoDebug(ssh, buf + idx, payloadSz, &payloadIdx);\n break;\n\n case MSGID_EXT_INFO:\n WLOG(WS_LOG_DEBUG, \"Decoding MSGID_EXT_INFO\");\n ret = DoExtInfo(ssh, buf + idx, payloadSz, &payloadIdx);\n break;\n\n case MSGID_KEXINIT:\n WLOG(WS_LOG_DEBUG, \"Decoding MSGID_KEXINIT\");\n ret = DoKexInit(ssh, buf + idx, payloadSz, &payloadIdx);\n if ((ret == WS_SUCCESS || ret == WS_WANT_WRITE) &&\n ssh->handshake != NULL && ssh->isKeying &&\n ssh->connectState >= CONNECT_SERVER_CHANNEL_REQUEST_DONE) {\n if (ssh->handshake->kexId == ID_DH_GEX_SHA256) {\n#if !defined(WOLFSSH_NO_DH) && !defined(WOLFSSH_NO_DH_GEX_SHA256)\n ssh->error = SendKexDhGexRequest(ssh);\n#endif\n }\n else\n ssh->error = SendKexDhInit(ssh);\n }\n break;\n\n case MSGID_NEWKEYS:\n WLOG(WS_LOG_DEBUG, \"Decoding MSGID_NEWKEYS\");\n"
},
{
"old": " WLOG(WS_LOG_DEBUG, \"Entering SendKexInit()\");\n\n if (ssh == NULL)\n ret = WS_BAD_ARGUMENT;\n\n if (ret == WS_SUCCESS && ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER &&\n ssh->ctx->privateKeyCount == 0) {\n WLOG(WS_LOG_DEBUG, \"Server needs at least one private key\");\n ret = WS_BAD_ARGUMENT;\n }\n\n if (ret == WS_SUCCESS) {\n ssh->isKeying = 1;\n if (ssh->handshake == NULL) {\n ssh->handshake = HandshakeInfoNew(ssh->ctx->heap);\n if (ssh->handshake == NULL) {\n WLOG(WS_LOG_DEBUG, \"Couldn't allocate handshake info\");\n ret = WS_MEMORY_E;\n }\n }\n }\n\n if (ret == WS_SUCCESS) {\n if (!ssh->algoListKey && ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER) {\n keyAlgoNamesSz = BuildNameList(NULL, 0,\n",
"new": " WLOG(WS_LOG_DEBUG, \"Entering SendKexInit()\");\n\n if (ssh == NULL)\n ret = WS_BAD_ARGUMENT;\n\n if (ret == WS_SUCCESS && ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER &&\n ssh->ctx->privateKeyCount == 0) {\n WLOG(WS_LOG_DEBUG, \"Server needs at least one private key\");\n ret = WS_BAD_ARGUMENT;\n }\n\n if (ret == WS_SUCCESS) {\n /* Set self is keying flag since we started sending the KEX init msg */\n ssh->isKeying |= WOLFSSH_SELF_IS_KEYING;\n if (ssh->handshake == NULL) {\n ssh->handshake = HandshakeInfoNew(ssh->ctx->heap);\n if (ssh->handshake == NULL) {\n WLOG(WS_LOG_DEBUG, \"Couldn't allocate handshake info\");\n ret = WS_MEMORY_E;\n }\n }\n }\n\n if (ret == WS_SUCCESS) {\n if (!ssh->algoListKey && ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER) {\n keyAlgoNamesSz = BuildNameList(NULL, 0,\n"
},
{
"old": " if (ret > 0) {\n keyAlgoNamesSz = (word32)ret;\n ret = WS_SUCCESS;\n }\n }\n else {\n ret = WS_MEMORY_E;\n }\n }\n }\n\n if (ret == WS_SUCCESS) {\n if (ssh->ctx->side == WOLFSSH_ENDPOINT_CLIENT) {\n kexAlgoNamesPlus = \",ext-info-c\";\n kexAlgoNamesPlusSz = (word32)WSTRLEN(kexAlgoNamesPlus);\n }\n\n kexAlgoNamesSz = AlgoListSz(ssh->algoListKex);\n encAlgoNamesSz = AlgoListSz(ssh->algoListCipher);\n if (!keyAlgoNames) {\n keyAlgoNamesSz = AlgoListSz(ssh->algoListKey);\n }\n else {\n keyAlgoNamesSz = AlgoListSz(keyAlgoNames);\n }\n macAlgoNamesSz = AlgoListSz(ssh->algoListMac);\n noneNamesSz = AlgoListSz(cannedNoneNames);\n payloadSz = MSG_ID_SZ + COOKIE_SZ + (LENGTH_SZ * 11) + BOOLEAN_SZ +\n + kexAlgoNamesSz + kexAlgoNamesPlusSz + keyAlgoNamesSz\n",
"new": " if (ret > 0) {\n keyAlgoNamesSz = (word32)ret;\n ret = WS_SUCCESS;\n }\n }\n else {\n ret = WS_MEMORY_E;\n }\n }\n }\n\n if (ret == WS_SUCCESS) {\n kexAlgoNamesSz = AlgoListSz(ssh->algoListKex);\n encAlgoNamesSz = AlgoListSz(ssh->algoListCipher);\n if (!keyAlgoNames) {\n keyAlgoNamesSz = AlgoListSz(ssh->algoListKey);\n }\n else {\n keyAlgoNamesSz = AlgoListSz(keyAlgoNames);\n }\n macAlgoNamesSz = AlgoListSz(ssh->algoListMac);\n noneNamesSz = AlgoListSz(cannedNoneNames);\n payloadSz = MSG_ID_SZ + COOKIE_SZ + (LENGTH_SZ * 11) + BOOLEAN_SZ +\n + kexAlgoNamesSz + kexAlgoNamesPlusSz + keyAlgoNamesSz\n"
},
{
"old": " }\n\n if (keyAlgoNames) {\n WFREE(keyAlgoNames, ssh->ctx->heap, DYNTYPE_STRING);\n }\n\n if (ret == WS_SUCCESS) {\n /* increase amount to be sent only if BundlePacket will be called */\n ssh->outputBuffer.length = idx;\n ret = BundlePacket(ssh);\n }\n\n if (ret == WS_SUCCESS)\n ret = wolfSSH_SendPacket(ssh);\n\n if (ret != WS_WANT_WRITE && ret != WS_SUCCESS)\n PurgePacket(ssh);\n\n WLOG(WS_LOG_DEBUG, \"Leaving SendKexInit(), ret = %d\", ret);\n return ret;\n}\n\n\nstruct wolfSSH_sigKeyBlockFull {\n byte pubKeyId; /* handshake->pubKeyId */\n byte pubKeyFmtId;\n",
"new": " }\n\n if (keyAlgoNames) {\n WFREE(keyAlgoNames, ssh->ctx->heap, DYNTYPE_STRING);\n }\n\n if (ret == WS_SUCCESS) {\n /* increase amount to be sent only if BundlePacket will be called */\n ssh->outputBuffer.length = idx;\n ret = BundlePacket(ssh);\n }\n\n if (ret == WS_SUCCESS) {\n ret = wolfSSH_SendPacket(ssh);\n }\n\n if (ret != WS_WANT_WRITE && ret != WS_SUCCESS)\n PurgePacket(ssh);\n\n WLOG(WS_LOG_DEBUG, \"Leaving SendKexInit(), ret = %d\", ret);\n return ret;\n}\n\n\nstruct wolfSSH_sigKeyBlockFull {\n byte pubKeyId; /* handshake->pubKeyId */\n byte pubKeyFmtId;\n"
},
{
"old": " sigKeyBlock_ptr->pubKeyName, sigKeyBlock_ptr->pubKeyNameSz);\n idx += sigKeyBlock_ptr->pubKeyNameSz;\n c32toa(sigSz, output + idx);\n idx += LENGTH_SZ;\n WMEMCPY(output + idx, sig_ptr, sigSz);\n idx += sigSz;\n\n ssh->outputBuffer.length = idx;\n\n ret = BundlePacket(ssh);\n }\n\n if (ret == WS_SUCCESS)\n ret = SendNewKeys(ssh);\n\n if (ret == WS_SUCCESS && ssh->sendExtInfo) {\n ret = SendExtInfo(ssh);\n }\n\n if (ret != WS_WANT_WRITE && ret != WS_SUCCESS)\n PurgePacket(ssh);\n\n WLOG(WS_LOG_DEBUG, \"Leaving SendKexDhReply(), ret = %d\", ret);\n if (sigKeyBlock_ptr)\n WFREE(sigKeyBlock_ptr, heap, DYNTYPE_PRIVKEY);\n#ifdef WOLFSSH_SMALL_STACK\n if (f_ptr)\n WFREE(f_ptr, heap, DYNTYPE_BUFFER);\n if (sig_ptr)\n",
"new": " sigKeyBlock_ptr->pubKeyName, sigKeyBlock_ptr->pubKeyNameSz);\n idx += sigKeyBlock_ptr->pubKeyNameSz;\n c32toa(sigSz, output + idx);\n idx += LENGTH_SZ;\n WMEMCPY(output + idx, sig_ptr, sigSz);\n idx += sigSz;\n\n ssh->outputBuffer.length = idx;\n\n ret = BundlePacket(ssh);\n }\n\n if (ret == WS_SUCCESS) {\n ssh->handshake->expectMsgId = MSGID_NEWKEYS;\n WLOG_EXPECT_MSGID(ssh->handshake->expectMsgId);\n ret = SendNewKeys(ssh);\n }\n\n if (ret != WS_WANT_WRITE && ret != WS_SUCCESS)\n PurgePacket(ssh);\n\n WLOG(WS_LOG_DEBUG, \"Leaving SendKexDhReply(), ret = %d\", ret);\n if (sigKeyBlock_ptr)\n WFREE(sigKeyBlock_ptr, heap, DYNTYPE_PRIVKEY);\n#ifdef WOLFSSH_SMALL_STACK\n if (f_ptr)\n WFREE(f_ptr, heap, DYNTYPE_BUFFER);\n if (sig_ptr)\n"
},
{
"old": "#endif\n\n default:\n WLOG(WS_LOG_DEBUG, \"SNK: using cipher invalid\");\n ret = WS_INVALID_ALGO_ID;\n }\n }\n\n if (ret == WS_SUCCESS) {\n ssh->txCount = 0;\n }\n\n if (ret == WS_SUCCESS)\n ret = wolfSSH_SendPacket(ssh);\n\n WLOG(WS_LOG_DEBUG, \"Leaving SendNewKeys(), ret = %d\", ret);\n return ret;\n}\n\n\n#ifndef WOLFSSH_NO_DH_GEX_SHA256\nint SendKexDhGexRequest(WOLFSSH* ssh)\n{\n byte* output;\n word32 idx = 0;\n word32 payloadSz;\n",
"new": "#endif\n\n default:\n WLOG(WS_LOG_DEBUG, \"SNK: using cipher invalid\");\n ret = WS_INVALID_ALGO_ID;\n }\n }\n\n if (ret == WS_SUCCESS) {\n ssh->txCount = 0;\n }\n\n if (ret == WS_SUCCESS) {\n ret = wolfSSH_SendPacket(ssh);\n\n /* Queued NEWKEYS is already bundled with the old keys. A partial\n * write is resumed by SendPacket, never by rebuilding NEWKEYS. */\n if (ret == WS_SUCCESS || ret == WS_WANT_WRITE)\n ssh->isKeying &= ~WOLFSSH_SELF_IS_KEYING;\n }\n\n WLOG(WS_LOG_DEBUG, \"Leaving SendNewKeys(), ret = %d\", ret);\n return ret;\n}\n\n\n#ifndef WOLFSSH_NO_DH_GEX_SHA256\nint SendKexDhGexRequest(WOLFSSH* ssh)\n{\n byte* output;\n word32 idx = 0;\n word32 payloadSz;\n"
},
{
"old": " c32toa(ssh->handshake->dhGexMinSz, output + idx);\n idx += UINT32_SZ;\n c32toa(ssh->handshake->dhGexPreferredSz, output + idx);\n idx += UINT32_SZ;\n c32toa(ssh->handshake->dhGexMaxSz, output + idx);\n idx += UINT32_SZ;\n\n ssh->outputBuffer.length = idx;\n\n ret = BundlePacket(ssh);\n }\n\n if (ret == WS_SUCCESS)\n ret = wolfSSH_SendPacket(ssh);\n\n WLOG(WS_LOG_DEBUG, \"Leaving SendKexDhGexRequest(), ret = %d\", ret);\n return ret;\n}\n\n\nint SendKexDhGexGroup(WOLFSSH* ssh)\n{\n byte* output;\n word32 idx = 0;\n word32 payloadSz;\n const byte* primeGroup = dhPrimeGroup14;\n",
"new": " c32toa(ssh->handshake->dhGexMinSz, output + idx);\n idx += UINT32_SZ;\n c32toa(ssh->handshake->dhGexPreferredSz, output + idx);\n idx += UINT32_SZ;\n c32toa(ssh->handshake->dhGexMaxSz, output + idx);\n idx += UINT32_SZ;\n\n ssh->outputBuffer.length = idx;\n\n ret = BundlePacket(ssh);\n }\n\n if (ret == WS_SUCCESS) {\n WLOG_EXPECT_MSGID(MSGID_KEXDH_GEX_GROUP);\n ssh->handshake->expectMsgId = MSGID_KEXDH_GEX_GROUP;\n ret = wolfSSH_SendPacket(ssh);\n }\n\n WLOG(WS_LOG_DEBUG, \"Leaving SendKexDhGexRequest(), ret = %d\", ret);\n return ret;\n}\n\n\nint SendKexDhGexGroup(WOLFSSH* ssh)\n{\n byte* output;\n word32 idx = 0;\n word32 payloadSz;\n const byte* primeGroup = dhPrimeGroup14;\n"
},
{
"old": "{\n byte* output;\n word32 idx = 0;\n word32 payloadSz;\n#ifndef WOLFSSH_NO_DH\n const byte* primeGroup = NULL;\n word32 primeGroupSz = 0;\n const byte* generator = NULL;\n word32 generatorSz = 0;\n#endif\n int ret = WS_SUCCESS;\n byte msgId = MSGID_KEXDH_INIT;\n byte e[MAX_KEX_KEY_SZ+1]; /* plus 1 in case of padding. */\n word32 eSz = (word32)sizeof(e);\n byte ePad = 0;\n\n WLOG(WS_LOG_DEBUG, \"Entering SendKexDhInit()\");\n\n switch (ssh->handshake->kexId) {\n#ifndef WOLFSSH_NO_DH_GROUP1_SHA1\n case ID_DH_GROUP1_SHA1:\n ssh->handshake->useDh = 1;\n primeGroup = dhPrimeGroup1;\n primeGroupSz = dhPrimeGroup1Sz;\n",
"new": "{\n byte* output;\n word32 idx = 0;\n word32 payloadSz;\n#ifndef WOLFSSH_NO_DH\n const byte* primeGroup = NULL;\n word32 primeGroupSz = 0;\n const byte* generator = NULL;\n word32 generatorSz = 0;\n#endif\n int ret = WS_SUCCESS;\n byte msgId = MSGID_KEXDH_INIT;\n byte expectMsgId = MSGID_KEXDH_REPLY;\n byte e[MAX_KEX_KEY_SZ+1]; /* plus 1 in case of padding. */\n word32 eSz = (word32)sizeof(e);\n byte ePad = 0;\n\n WLOG(WS_LOG_DEBUG, \"Entering SendKexDhInit()\");\n\n switch (ssh->handshake->kexId) {\n#ifndef WOLFSSH_NO_DH_GROUP1_SHA1\n case ID_DH_GROUP1_SHA1:\n ssh->handshake->useDh = 1;\n primeGroup = dhPrimeGroup1;\n primeGroupSz = dhPrimeGroup1Sz;\n"
},
{
"old": " generator = dhGenerator;\n generatorSz = dhGeneratorSz;\n break;\n#endif\n#ifndef WOLFSSH_NO_DH_GEX_SHA256\n case ID_DH_GEX_SHA256:\n ssh->handshake->useDh = 1;\n primeGroup = ssh->handshake->primeGroup;\n primeGroupSz = ssh->handshake->primeGroupSz;\n generator = ssh->handshake->generator;\n generatorSz = ssh->handshake->generatorSz;\n msgId = MSGID_KEXDH_GEX_INIT;\n break;\n#endif\n#ifndef WOLFSSH_NO_ECDH_SHA2_NISTP256\n case ID_ECDH_SHA2_NISTP256:\n ssh->handshake->useEcc = 1;\n msgId = MSGID_KEXECDH_INIT;\n break;\n#endif\n#ifndef WOLFSSH_NO_ECDH_SHA2_NISTP384\n case ID_ECDH_SHA2_NISTP384:\n ssh->handshake->useEcc = 1;\n msgId = MSGID_KEXECDH_INIT;\n",
"new": " generator = dhGenerator;\n generatorSz = dhGeneratorSz;\n break;\n#endif\n#ifndef WOLFSSH_NO_DH_GEX_SHA256\n case ID_DH_GEX_SHA256:\n ssh->handshake->useDh = 1;\n primeGroup = ssh->handshake->primeGroup;\n primeGroupSz = ssh->handshake->primeGroupSz;\n generator = ssh->handshake->generator;\n generatorSz = ssh->handshake->generatorSz;\n msgId = MSGID_KEXDH_GEX_INIT;\n expectMsgId = MSGID_KEXDH_GEX_REPLY;\n break;\n#endif\n#ifndef WOLFSSH_NO_ECDH_SHA2_NISTP256\n case ID_ECDH_SHA2_NISTP256:\n ssh->handshake->useEcc = 1;\n msgId = MSGID_KEXECDH_INIT;\n break;\n#endif\n#ifndef WOLFSSH_NO_ECDH_SHA2_NISTP384\n case ID_ECDH_SHA2_NISTP384:\n ssh->handshake->useEcc = 1;\n msgId = MSGID_KEXECDH_INIT;\n"
},
{
"old": " output[idx] = 0;\n idx++;\n }\n\n WMEMCPY(output + idx, e, eSz);\n idx += eSz;\n\n ssh->outputBuffer.length = idx;\n\n ret = BundlePacket(ssh);\n }\n\n if (ret == WS_SUCCESS)\n ret = wolfSSH_SendPacket(ssh);\n\n WLOG(WS_LOG_DEBUG, \"Leaving SendKexDhInit(), ret = %d\", ret);\n return ret;\n}\n\n\nint SendUnimplemented(WOLFSSH* ssh)\n{\n byte* output;\n word32 idx = 0;\n int ret = WS_SUCCESS;\n\n",
"new": " output[idx] = 0;\n idx++;\n }\n\n WMEMCPY(output + idx, e, eSz);\n idx += eSz;\n\n ssh->outputBuffer.length = idx;\n\n ret = BundlePacket(ssh);\n }\n\n if (ret == WS_SUCCESS) {\n WLOG_EXPECT_MSGID(expectMsgId);\n ssh->handshake->expectMsgId = expectMsgId;\n ret = wolfSSH_SendPacket(ssh);\n }\n\n WLOG(WS_LOG_DEBUG, \"Leaving SendKexDhInit(), ret = %d\", ret);\n return ret;\n}\n\n\nint SendUnimplemented(WOLFSSH* ssh)\n{\n byte* output;\n word32 idx = 0;\n int ret = WS_SUCCESS;\n\n"
},
{
"old": " ret = SendUserAuthBanner(ssh);\n\n return ret;\n}\n\n\n#define WS_EXTINFO_EXTENSION_COUNT 1\nstatic const char serverSigAlgsName[] = \"server-sig-algs\";\n\n\nint SendExtInfo(WOLFSSH* ssh)\n{\n byte* output;\n word32 idx;\n word32 keyAlgoNamesSz = 0;\n word32 serverSigAlgsNameSz = 0;\n int ret = WS_SUCCESS;\n\n WLOG(WS_LOG_DEBUG, \"Entering SendExtInfo()\");\n\n if (ssh == NULL) {\n ret = WS_BAD_ARGUMENT;\n }\n\n if (ret == WS_SUCCESS) {\n keyAlgoNamesSz = AlgoListSz(ssh->algoListKeyAccepted);\n serverSigAlgsNameSz = AlgoListSz(serverSigAlgsName);\n ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ + (LENGTH_SZ * 2)\n + serverSigAlgsNameSz + keyAlgoNamesSz);\n }\n\n if (ret == WS_SUCCESS) {\n output = ssh->outputBuffer.buffer;\n idx = ssh->outputBuffer.length;\n\n output[idx++] = MSGID_EXT_INFO;\n c32toa(WS_EXTINFO_EXTENSION_COUNT, output + idx);\n idx += UINT32_SZ;\n\n c32toa(serverSigAlgsNameSz, output + idx);\n idx += LENGTH_SZ;\n WMEMCPY(output + idx, serverSigAlgsName, serverSigAlgsNameSz);\n idx += serverSigAlgsNameSz;\n\n c32toa(keyAlgoNamesSz, output + idx);\n idx += LENGTH_SZ;\n WMEMCPY(output + idx, ssh->algoListKeyAccepted, keyAlgoNamesSz);\n idx += keyAlgoNamesSz;\n\n ssh->outputBuffer.length = idx;\n\n ret = BundlePacket(ssh);\n }\n\n if (ret == WS_SUCCESS) {\n ret = wolfSSH_SendPacket(ssh);\n }\n\n WLOG(WS_LOG_DEBUG, \"Leaving SendExtInfo(), ret = %d\", ret);\n return ret;\n}\n\n\n/* Updates the payload size, and maybe loads keys. */\nstatic int PrepareUserAuthRequestPassword(WOLFSSH* ssh, word32* payloadSz,\n const WS_UserAuthData* authData)\n{\n int ret = WS_SUCCESS;\n\n if (ssh == NULL || payloadSz == NULL || authData == NULL)\n ret = WS_BAD_ARGUMENT;\n\n",
"new": " ret = SendUserAuthBanner(ssh);\n\n return ret;\n}\n\n\n#define WS_EXTINFO_EXTENSION_COUNT 1\nstatic const char serverSigAlgsName[] = \"server-sig-algs\";\n\n\nint SendExtInfo(WOLFSSH* ssh)\n{\n WOLFSSH_UNUSED(ssh);\n return WS_NOT_COMPILED;\n}\n\n\n/* Updates the payload size, and maybe loads keys. */\nstatic int PrepareUserAuthRequestPassword(WOLFSSH* ssh, word32* payloadSz,\n const WS_UserAuthData* authData)\n{\n int ret = WS_SUCCESS;\n\n if (ssh == NULL || payloadSz == NULL || authData == NULL)\n ret = WS_BAD_ARGUMENT;\n\n"
},
{
"old": "int SendChannelEof(WOLFSSH* ssh, word32 peerChannelId)\n{\n byte* output;\n word32 idx;\n int ret = WS_SUCCESS;\n WOLFSSH_CHANNEL* channel = NULL;\n\n WLOG(WS_LOG_DEBUG, \"Entering SendChannelEof()\");\n\n if (ssh == NULL)\n ret = WS_BAD_ARGUMENT;",
"new": "int SendChannelEof(WOLFSSH* ssh, word32 peerChannelId)\n{\n byte* output;\n word32 idx;\n int ret = WS_SUCCESS;\n WOLFSSH_CHANNEL* channel = NULL;\n\n WLOG(WS_LOG_DEBUG, \"Entering SendChannelEof()\");\n\n if (ssh == NULL)\n ret = WS_BAD_ARGUMENT;\n\n if (ret == WS_SUCCESS) {\n if (!IsMessageAllowed(ssh, MSGID_CHANNEL_EOF, WS_MSG_SEND)) {\n ret = WS_MSGID_NOT_ALLOWED_E;\n }\n }"
},
{
"old": " const char* str = \"exit-status\";\n word32 idx;\n word32 strSz = 0;\n int ret = WS_SUCCESS;\n WOLFSSH_CHANNEL* channel = NULL;\n\n WLOG(WS_LOG_DEBUG, \"Entering SendChannelExit(), status = %d\", status);\n\n if (ssh == NULL)\n ret = WS_BAD_ARGUMENT;\n\n if (ret == WS_SUCCESS) {\n channel = ChannelFind(ssh, peerChannelId, WS_CHANNEL_ID_PEER);\n if (channel == NULL)\n ret = WS_INVALID_CHANID;\n }\n\n if (ret == WS_SUCCESS) {\n strSz = (word32)WSTRLEN(str);\n ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ + LENGTH_SZ + strSz +\n BOOLEAN_SZ + UINT32_SZ);\n }\n\n if (ret == WS_SUCCESS) {\n",
"new": " const char* str = \"exit-status\";\n word32 idx;\n word32 strSz = 0;\n int ret = WS_SUCCESS;\n WOLFSSH_CHANNEL* channel = NULL;\n\n WLOG(WS_LOG_DEBUG, \"Entering SendChannelExit(), status = %d\", status);\n\n if (ssh == NULL)\n ret = WS_BAD_ARGUMENT;\n\n if (ret == WS_SUCCESS) {\n if (!IsMessageAllowed(ssh, MSGID_CHANNEL_REQUEST, WS_MSG_SEND)) {\n ret = WS_MSGID_NOT_ALLOWED_E;\n }\n }\n\n if (ret == WS_SUCCESS) {\n channel = ChannelFind(ssh, peerChannelId, WS_CHANNEL_ID_PEER);\n if (channel == NULL)\n ret = WS_INVALID_CHANID;\n }\n\n if (ret == WS_SUCCESS) {\n strSz = (word32)WSTRLEN(str);\n ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ + LENGTH_SZ + strSz +\n BOOLEAN_SZ + UINT32_SZ);\n }\n\n if (ret == WS_SUCCESS) {\n"
},
{
"old": "int SendChannelWindowAdjust(WOLFSSH* ssh, word32 channelId,\n word32 bytesToAdd)\n{\n byte* output;\n word32 idx;\n int ret = WS_SUCCESS;\n WOLFSSH_CHANNEL* channel;\n\n WLOG(WS_LOG_DEBUG, \"Entering SendChannelWindowAdjust()\");\n\n if (ssh == NULL)\n ret = WS_BAD_ARGUMENT;\n\n channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);\n if (channel == NULL) {\n WLOG(WS_LOG_DEBUG, \"Invalid channel\");\n ret = WS_INVALID_CHANID;\n }\n if (ret == WS_SUCCESS)\n ret = PreparePacket(ssh, MSG_ID_SZ + (UINT32_SZ * 2));\n\n if (ret == WS_SUCCESS) {\n output = ssh->outputBuffer.buffer;\n idx = ssh->outputBuffer.length;\n",
"new": "int SendChannelWindowAdjust(WOLFSSH* ssh, word32 channelId,\n word32 bytesToAdd)\n{\n byte* output;\n word32 idx;\n int ret = WS_SUCCESS;\n WOLFSSH_CHANNEL* channel;\n\n WLOG(WS_LOG_DEBUG, \"Entering SendChannelWindowAdjust()\");\n\n if (ssh == NULL)\n ret = WS_BAD_ARGUMENT;\n\n if (ret == WS_SUCCESS) {\n if (!IsMessageAllowed(ssh, MSGID_CHANNEL_WINDOW_ADJUST, WS_MSG_SEND)) {\n ret = WS_MSGID_NOT_ALLOWED_E;\n }\n }\n\n channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);\n if (channel == NULL) {\n WLOG(WS_LOG_DEBUG, \"Invalid channel\");\n ret = WS_INVALID_CHANID;\n }\n if (ret == WS_SUCCESS)\n ret = PreparePacket(ssh, MSG_ID_SZ + (UINT32_SZ * 2));\n\n if (ret == WS_SUCCESS) {\n output = ssh->outputBuffer.buffer;\n idx = ssh->outputBuffer.length;\n"
},
{
"old": "static const char serverSigAlgsName[] = \"server-sig-algs\";\n",
"new": ""
}
]
},
"src/ssh.c": {
"sha256": "a4f479ff87eea0980ec1ebdf2c7dd090da473780181b695a56799cb9611f4366",
"edits": [
{
"old": " WOLFSSH_BUFFER* inputBuffer;\n\n WLOG(WS_LOG_DEBUG, \"Entering wolfSSH_stream_read()\");\n\n if (ssh == NULL || buf == NULL || bufSz == 0 || ssh->channelList == NULL)\n return WS_BAD_ARGUMENT;\n\n if (ssh->channelList->eofRxd) {\n ssh->error = WS_EOF;\n return WS_ERROR;\n }\n\n inputBuffer = &ssh->channelList->inputBuffer;\n ssh->error = WS_SUCCESS;\n\n if (ret == WS_SUCCESS) {\n WLOG(WS_LOG_DEBUG, \" Stream read index of %u\", inputBuffer->idx);\n WLOG(WS_LOG_DEBUG, \" Stream read ava data %u\", inputBuffer->length);\n while (inputBuffer->length - inputBuffer->idx == 0) {\n WLOG(WS_LOG_DEBUG,\n \"Starting to recieve data at current index of %u\",\n inputBuffer->idx);\n ret = DoReceive(ssh);\n if (ssh->channelList == NULL || ssh->channelList->eofRxd)\n",
"new": " WOLFSSH_BUFFER* inputBuffer;\n\n WLOG(WS_LOG_DEBUG, \"Entering wolfSSH_stream_read()\");\n\n if (ssh == NULL || buf == NULL || bufSz == 0 || ssh->channelList == NULL)\n return WS_BAD_ARGUMENT;\n\n if (ssh->channelList->eofRxd) {\n ssh->error = WS_EOF;\n return WS_ERROR;\n }\n\n if (ssh->isKeying) {\n ssh->error = WS_REKEYING;\n return WS_FATAL_ERROR;\n }\n\n inputBuffer = &ssh->channelList->inputBuffer;\n ssh->error = WS_SUCCESS;\n\n if (ret == WS_SUCCESS) {\n WLOG(WS_LOG_DEBUG, \" Stream read index of %u\", inputBuffer->idx);\n WLOG(WS_LOG_DEBUG, \" Stream read ava data %u\", inputBuffer->length);\n while (inputBuffer->length - inputBuffer->idx == 0) {\n WLOG(WS_LOG_DEBUG,\n \"Starting to recieve data at current index of %u\",\n inputBuffer->idx);\n ret = DoReceive(ssh);\n if (ssh->channelList == NULL || ssh->channelList->eofRxd)\n"
},
{
"old": " if (ssh->lastRxId != ssh->channelList->channel) {\n ret = WS_ERROR;\n break;\n }\n else {\n ret = WS_SUCCESS;\n }\n }\n }\n }\n\n /* update internal input buffer based on data read */\n if (ret == WS_SUCCESS) {\n int n;\n\n n = min(bufSz, inputBuffer->length - inputBuffer->idx);\n if (n <= 0)\n ret = WS_BUFFER_E;\n else {\n WMEMCPY(buf, inputBuffer->buffer + inputBuffer->idx, n);\n ret = _UpdateChannelWindow(ssh->channelList);\n if (ret == WS_SUCCESS) {\n inputBuffer->idx += n;\n ret = n;\n }\n",
"new": " if (ssh->lastRxId != ssh->channelList->channel) {\n ret = WS_ERROR;\n break;\n }\n else {\n ret = WS_SUCCESS;\n }\n }\n }\n }\n\n /* update internal input buffer based on data read */\n if (ret == WS_SUCCESS && !ssh->isKeying) {\n int n;\n\n n = min(bufSz, inputBuffer->length - inputBuffer->idx);\n if (n <= 0)\n ret = WS_BUFFER_E;\n else {\n WMEMCPY(buf, inputBuffer->buffer + inputBuffer->idx, n);\n ret = _UpdateChannelWindow(ssh->channelList);\n if (ret == WS_SUCCESS) {\n inputBuffer->idx += n;\n ret = n;\n }\n"
},
{
"old": "\nint wolfSSH_stream_send(WOLFSSH* ssh, byte* buf, word32 bufSz)\n{\n int bytesTxd = 0;\n\n WLOG(WS_LOG_DEBUG, \"Entering wolfSSH_stream_send()\");\n\n if (ssh == NULL || buf == NULL || ssh->channelList == NULL)\n return WS_BAD_ARGUMENT;\n\n if (ssh->isKeying) {\n ssh->error = WS_REKEYING;\n return WS_REKEYING;\n }\n\n bytesTxd = SendChannelData(ssh, ssh->channelList->channel, buf, bufSz);\n\n WLOG(WS_LOG_DEBUG, \"Leaving wolfSSH_stream_send(), txd = %d\", bytesTxd);\n return bytesTxd;\n}\n\n\nint wolfSSH_ChannelIdSend(WOLFSSH* ssh, word32 channelId,\n byte* buf, word32 bufSz)\n{\n",
"new": "\nint wolfSSH_stream_send(WOLFSSH* ssh, byte* buf, word32 bufSz)\n{\n int bytesTxd = 0;\n\n WLOG(WS_LOG_DEBUG, \"Entering wolfSSH_stream_send()\");\n\n if (ssh == NULL || buf == NULL || ssh->channelList == NULL)\n return WS_BAD_ARGUMENT;\n\n if (ssh->isKeying) {\n ssh->error = WS_REKEYING;\n return WS_FATAL_ERROR;\n }\n\n bytesTxd = SendChannelData(ssh, ssh->channelList->channel, buf, bufSz);\n\n WLOG(WS_LOG_DEBUG, \"Leaving wolfSSH_stream_send(), txd = %d\", bytesTxd);\n return bytesTxd;\n}\n\n\nint wolfSSH_ChannelIdSend(WOLFSSH* ssh, word32 channelId,\n byte* buf, word32 bufSz)\n{\n"
},
{
"old": " return bufSz;\n}\n\n\nint wolfSSH_ChannelRead(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)\n{\n\n WLOG(WS_LOG_DEBUG, \"Entering wolfSSH_ChannelRead()\");\n\n if (channel == NULL || buf == NULL || bufSz == 0)\n return WS_BAD_ARGUMENT;\n\n bufSz = _ChannelRead(channel, buf, bufSz);\n\n WLOG(WS_LOG_DEBUG, \"Leaving wolfSSH_ChannelRead(), bytesRxd = %d\",\n bufSz);\n return bufSz;\n}\n\n\nint wolfSSH_ChannelSend(WOLFSSH_CHANNEL* channel,\n const byte* buf, word32 bufSz)\n{\n int bytesTxd = 0;\n",
"new": " return bufSz;\n}\n\n\nint wolfSSH_ChannelRead(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)\n{\n\n WLOG(WS_LOG_DEBUG, \"Entering wolfSSH_ChannelRead()\");\n\n if (channel == NULL || buf == NULL || bufSz == 0)\n return WS_BAD_ARGUMENT;\n\n if (channel->ssh->isKeying) {\n channel->ssh->error = WS_REKEYING;\n return WS_REKEYING;\n }\n\n bufSz = _ChannelRead(channel, buf, bufSz);\n\n WLOG(WS_LOG_DEBUG, \"Leaving wolfSSH_ChannelRead(), bytesRxd = %d\",\n bufSz);\n return bufSz;\n}\n\n\nint wolfSSH_ChannelSend(WOLFSSH_CHANNEL* channel,\n const byte* buf, word32 bufSz)\n{\n int bytesTxd = 0;\n"
}
]
},
"wolfssh/internal.h": {
"sha256": "8e417149a68f8a6c0506957adf014b3e6c1727a723536826ce5fb0c9e1f1aba3",
"edits": [
{
"old": " #define WOLFSSH_MAX_FILE_SIZE (1024ul * 1024ul * 4)\n#endif\n#ifndef WOLFSSH_MAX_PVT_KEYS\n #define WOLFSSH_MAX_PVT_KEYS 8\n#endif\n#ifndef WOLFSSH_MAX_PUB_KEY_ALGO\n #define WOLFSSH_MAX_PUB_KEY_ALGO (WOLFSSH_MAX_PVT_KEYS + 2)\n#endif\n#ifndef WOLFSSH_KEY_QUANTITY_REQ\n #define WOLFSSH_KEY_QUANTITY_REQ 1\n#endif\n\n\nWOLFSSH_LOCAL byte NameToId(const char* name, word32 nameSz);\nWOLFSSH_LOCAL const char* IdToName(byte id);\nWOLFSSH_LOCAL const char* NameByIndexType(byte type, word32* index);\n\n\n/* For cases when openssl coexist is used */\n#ifdef WC_NO_COMPAT_AES_BLOCK_SIZE\n #define AES_BLOCK_SIZE WC_AES_BLOCK_SIZE\n#endif\n\n#define STATIC_BUFFER_LEN AES_BLOCK_SIZE\n",
"new": " #define WOLFSSH_MAX_FILE_SIZE (1024ul * 1024ul * 4)\n#endif\n#ifndef WOLFSSH_MAX_PVT_KEYS\n #define WOLFSSH_MAX_PVT_KEYS 8\n#endif\n#ifndef WOLFSSH_MAX_PUB_KEY_ALGO\n #define WOLFSSH_MAX_PUB_KEY_ALGO (WOLFSSH_MAX_PVT_KEYS + 2)\n#endif\n#ifndef WOLFSSH_KEY_QUANTITY_REQ\n #define WOLFSSH_KEY_QUANTITY_REQ 1\n#endif\n\n/* Keep track of keying state for both sides of the connection.\n * WOLFSSH_SELF_IS_KEYING gets set on sending KEX init and\n * WOLFSSH_PEER_IS_KEYING gets set on receiving KEX init */\n#define WOLFSSH_PEER_IS_KEYING 0x01\n#define WOLFSSH_SELF_IS_KEYING 0x02\n\nWOLFSSH_LOCAL byte NameToId(const char* name, word32 nameSz);\nWOLFSSH_LOCAL const char* IdToName(byte id);\nWOLFSSH_LOCAL const char* NameByIndexType(byte type, word32* index);\n\n\n/* For cases when openssl coexist is used */\n#ifdef WC_NO_COMPAT_AES_BLOCK_SIZE\n #define AES_BLOCK_SIZE WC_AES_BLOCK_SIZE\n#endif\n\n#define STATIC_BUFFER_LEN AES_BLOCK_SIZE\n"
},
{
"old": "\ntypedef struct Keys {\n byte iv[AES_BLOCK_SIZE];\n byte ivSz;\n byte encKey[AES_256_KEY_SIZE];\n byte encKeySz;\n byte macKey[MAX_HMAC_SZ];\n byte macKeySz;\n} Keys;\n\n\ntypedef struct HandshakeInfo {\n byte kexId;\n byte kexIdGuess;\n byte kexHashId;\n byte pubKeyId;\n byte encryptId;\n byte macId;\n byte kexPacketFollows;\n byte aeadMode;\n\n byte blockSz;\n byte macSz;\n\n",
"new": "\ntypedef struct Keys {\n byte iv[AES_BLOCK_SIZE];\n byte ivSz;\n byte encKey[AES_256_KEY_SIZE];\n byte encKeySz;\n byte macKey[MAX_HMAC_SZ];\n byte macKeySz;\n} Keys;\n\n\ntypedef struct HandshakeInfo {\n byte expectMsgId;\n byte kexId;\n byte kexIdGuess;\n byte kexHashId;\n byte pubKeyId;\n byte encryptId;\n byte macId;\n byte kexPacketFollows;\n byte aeadMode;\n\n byte blockSz;\n byte macSz;\n\n"
},
{
"old": " char* userName;\n word32 userNameSz;\n char* password;\n word32 passwordSz;\n byte* pkBlob;\n word32 pkBlobSz;\n byte* peerProtoId; /* Save for rekey */\n word32 peerProtoIdSz;\n void* publicKeyCheckCtx;\n byte sendTerminalRequest;\n byte userAuthPkDone;\n byte sendExtInfo;\n byte* peerSigId;\n word32 peerSigIdSz;\n\n#ifdef USE_WINDOWS_API\n word32 defaultAttr; /* default windows attributes */\n byte defaultAttrSet;\n byte escBuf[WOLFSSL_MAX_ESCBUF]; /* console codes are about 3 byte and\n * have max arguments of 16 */\n byte escBufSz;\n byte escState; /* current console translation state */\n#endif\n#ifdef WOLFSSH_SFTP\n",
"new": " char* userName;\n word32 userNameSz;\n char* password;\n word32 passwordSz;\n byte* pkBlob;\n word32 pkBlobSz;\n byte* peerProtoId; /* Save for rekey */\n word32 peerProtoIdSz;\n void* publicKeyCheckCtx;\n byte sendTerminalRequest;\n byte userAuthPkDone;\n byte sendExtInfo;\n byte extInfoSent; /* track if the ext info has already been sent */\n byte* peerSigId;\n word32 peerSigIdSz;\n\n#ifdef USE_WINDOWS_API\n word32 defaultAttr; /* default windows attributes */\n byte defaultAttrSet;\n byte escBuf[WOLFSSL_MAX_ESCBUF]; /* console codes are about 3 byte and\n * have max arguments of 16 */\n byte escBufSz;\n byte escState; /* current console translation state */\n#endif\n#ifdef WOLFSSH_SFTP\n"
},
{
"old": "};\n\n\nenum ProcessReplyStates {\n PROCESS_INIT,\n PROCESS_PACKET_LENGTH,\n PROCESS_PACKET_FINISH,\n PROCESS_PACKET\n};\n\n\nenum WS_MessageIds {\n MSGID_DISCONNECT = 1,\n MSGID_IGNORE = 2,\n MSGID_UNIMPLEMENTED = 3,\n MSGID_DEBUG = 4,\n MSGID_SERVICE_REQUEST = 5,\n MSGID_SERVICE_ACCEPT = 6,\n MSGID_EXT_INFO = 7,\n\n MSGID_KEXINIT = 20,\n MSGID_NEWKEYS = 21,\n\n MSGID_KEXDH_INIT = 30,\n",
"new": "};\n\n\nenum ProcessReplyStates {\n PROCESS_INIT,\n PROCESS_PACKET_LENGTH,\n PROCESS_PACKET_FINISH,\n PROCESS_PACKET\n};\n\n\nenum WS_MessageIds {\n MSGID_NONE = 0,\n\n MSGID_DISCONNECT = 1,\n MSGID_IGNORE = 2,\n MSGID_UNIMPLEMENTED = 3,\n MSGID_DEBUG = 4,\n MSGID_SERVICE_REQUEST = 5,\n MSGID_SERVICE_ACCEPT = 6,\n MSGID_EXT_INFO = 7,\n\n MSGID_KEXINIT = 20,\n MSGID_NEWKEYS = 21,\n\n MSGID_KEXDH_INIT = 30,\n"
},
{
"old": " MSGID_CHANNEL_OPEN_FAIL = 92,\n MSGID_CHANNEL_WINDOW_ADJUST = 93,\n MSGID_CHANNEL_DATA = 94,\n MSGID_CHANNEL_EXTENDED_DATA = 95,\n MSGID_CHANNEL_EOF = 96,\n MSGID_CHANNEL_CLOSE = 97,\n MSGID_CHANNEL_REQUEST = 98,\n MSGID_CHANNEL_SUCCESS = 99,\n MSGID_CHANNEL_FAILURE = 100\n};\n\n\n/* Allows the server to receive up to KEXDH GEX Request during KEX. */\n#define MSGID_KEXDH_LIMIT MSGID_KEXDH_GEX_REQUEST\n\n/* The endpoints should not allow message IDs greater than or\n * equal to msgid 80 before user authentication is complete.\n * Per RFC 4252 section 6. */\n#define MSGID_USERAUTH_LIMIT 80\n\n/* The client should only send the user auth request message\n * (50), it should not accept it. The server should only receive\n * the user auth request message, it should not accept the other\n * user auth messages, it sends them. (>50) */\n#define MSGID_USERAUTH_RESTRICT 50\n\n\n#define CHANNEL_EXTENDED_DATA_STDERR WOLFSSH_EXT_DATA_STDERR\n\n\n/* dynamic memory types */\nenum WS_DynamicTypes {\n DYNTYPE_STRING = 500,\n DYNTYPE_CTX,\n DYNTYPE_SSH,\n DYNTYPE_CHANNEL,\n DYNTYPE_BUFFER,\n DYNTYPE_ID,\n DYNTYPE_HS,\n DYNTYPE_CA,\n DYNTYPE_CERT,\n",
"new": " MSGID_CHANNEL_OPEN_FAIL = 92,\n MSGID_CHANNEL_WINDOW_ADJUST = 93,\n MSGID_CHANNEL_DATA = 94,\n MSGID_CHANNEL_EXTENDED_DATA = 95,\n MSGID_CHANNEL_EOF = 96,\n MSGID_CHANNEL_CLOSE = 97,\n MSGID_CHANNEL_REQUEST = 98,\n MSGID_CHANNEL_SUCCESS = 99,\n MSGID_CHANNEL_FAILURE = 100\n};\n\n\n/* The following message ID ranges are described in RFC 5251, section 7. */\nenum WS_MessageIdLimits {\n/* Transport Layer Protocol: */\n MSGIDLIMIT_TRANS_MIN = 1,\n MSGIDLIMIT_TRANS_GEN_MIN = 1,\n MSGIDLIMIT_TRANS_GEN_MAX = 19,\n MSGIDLIMIT_TRANS_ALGO_MIN = 20,\n MSGIDLIMIT_TRANS_ALGO_MAX = 29,\n MSGIDLIMIT_TRANS_KEX_MIN = 30,\n MSGIDLIMIT_TRANS_KEX_MAX = 49,\n MSGIDLIMIT_TRANS_MAX = 49,\n/* User Authentication Protocol: */\n MSGIDLIMIT_AUTH_MIN = 50,\n MSGIDLIMIT_AUTH_GEN_MIN = 50,\n MSGIDLIMIT_AUTH_GEN_MAX = 59,\n MSGIDLIMIT_AUTH_METH_MIN = 60,\n MSGIDLIMIT_AUTH_METH_MAX = 79,\n MSGIDLIMIT_AUTH_MAX = 79,\n/* Connection Protocol: */\n MSGIDLIMIT_CONN_MIN = 80,\n MSGIDLIMIT_CONN_GEN_MIN = 80,\n MSGIDLIMIT_CONN_GEN_MAX = 89,\n MSGIDLIMIT_CONN_CHAN_MIN = 90,\n MSGIDLIMIT_CONN_CHAN_MAX = 127,\n MSGIDLIMIT_CONN_MAX = 127,\n/* Reserved For Client Protocols: */\n MSGIDLIMIT_RESERVED_MIN = 128,\n MSGIDLIMIT_RESERVED_MAX = 191,\n/* Local Extensions: */\n MSGIDLIMIT_EXTENDED_MIN = 192,\n MSGIDLIMIT_EXTENDED_MAX = 255,\n};\n\n/* Message ID bounds checking. */\n#define MSGIDLIMIT_BOUND(x,y,z) ((x) >= (y) && (x) <= (z))\n#define MSGIDLIMIT_COMP(x,name) \\\n MSGIDLIMIT_BOUND((x),MSGIDLIMIT_##name##_MIN,MSGIDLIMIT_##name##_MAX)\n#define MSGIDLIMIT_TRANS(x) MSGIDLIMIT_COMP((x),TRANS)\n#define MSGIDLIMIT_TRANS_GEN(x) MSGIDLIMIT_COMP((x),TRANS_GEN)\n#define MSGIDLIMIT_TRANS_ALGO(x) MSGIDLIMIT_COMP((x),TRANS_ALGO)\n#define MSGIDLIMIT_TRANS_KEX(x) MSGIDLIMIT_COMP((x),TRANS_KEX)\n#define MSGIDLIMIT_AUTH(x) MSGIDLIMIT_COMP((x),AUTH)\n#define MSGIDLIMIT_AUTH_GEN(x) MSGIDLIMIT_COMP((x),AUTH_GEN)\n#define MSGIDLIMIT_AUTH_METH(x) MSGIDLIMIT_COMP((x),AUTH_METH)\n#define MSGIDLIMIT_CONN(x) MSGIDLIMIT_COMP((x),CONN)\n#define MSGIDLIMIT_CONN_GEN(x) MSGIDLIMIT_COMP((x),CONN_GEN)\n#define MSGIDLIMIT_CONN_CHAN(x) MSGIDLIMIT_COMP((x),CONN_CHAN)\n#define MSGIDLIMIT_RESERVED(x) MSGIDLIMIT_COMP((x),RESERVED)\n#define MSGIDLIMIT_EXTENDED(x) MSGIDLIMIT_COMP((x),EXTENDED)\n#define MSGIDLIMIT_POST_USERAUTH(x) ((x) >= MSGIDLIMIT_CONN_MIN)\n\n\n#define CHANNEL_EXTENDED_DATA_STDERR WOLFSSH_EXT_DATA_STDERR\n\n/* Used when checking IsMessageAllowed() to determine if creating and sending\n * the message or receiving the message is allowed */\n#define WS_MSG_SEND 1\n#define WS_MSG_RECV 2\n\n/* dynamic memory types */\nenum WS_DynamicTypes {\n DYNTYPE_STRING = 500,\n DYNTYPE_CTX,\n DYNTYPE_SSH,\n DYNTYPE_CHANNEL,\n DYNTYPE_BUFFER,\n DYNTYPE_ID,\n DYNTYPE_HS,\n DYNTYPE_CA,\n DYNTYPE_CERT,\n"
},
{
"old": "\n\n#define WOLFSSL_V5_0_0 0x05000000\n#define WOLFSSL_V5_7_0 0x05007000\n#define WOLFSSL_V5_7_2 0x05007002\n\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* _WOLFSSH_INTERNAL_H_ */\n\n",
"new": "\n\n#define WOLFSSL_V5_0_0 0x05000000\n#define WOLFSSL_V5_7_0 0x05007000\n#define WOLFSSL_V5_7_2 0x05007002\n\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* _WOLFSSH_INTERNAL_H_ */\n"
}
]
}
}
+51
View File
@@ -0,0 +1,51 @@
{
"793": {
"url": "https://patch-diff.githubusercontent.com/raw/wolfSSL/wolfssh/pull/793.patch",
"sha256": "66aa999521800371f97b340db2bff162be4e8ffab4b4b953f9d32b8e33f56cea",
"commits": [
"5fa6c0fce30a421879b355a007ef843eb48332d3",
"af45bc3719ddeac112d9d70b2e6a969f1aa3f3e7",
"d74c942c84d44fb46d3a10cb56233b704733e466",
"ff95f3c3029d766b114a91d98b013e0a1636a6c1",
"2a11471bb717a2ee6f06b3e1beab8a3e2b0ef261",
"813ec263cc56e7c9093135d854b3fc887633d368",
"cc17941a6125daefbadb8c236fdeaeb1a21ec786",
"4862400a374253216e596ff5c3b018b857015cbb"
]
},
"819": {
"url": "https://patch-diff.githubusercontent.com/raw/wolfSSL/wolfssh/pull/819.patch",
"sha256": "8e63c2b24679a7d831f7dba12e412d2f8a7fdc391fc4e8fd33f029487110219b",
"commits": [
"201029797b260eee894b12d488bce6022290bf67"
]
},
"840": {
"url": "https://patch-diff.githubusercontent.com/raw/wolfSSL/wolfssh/pull/840.patch",
"sha256": "23e25873cb4dfa36063357111019e0960dfc008a68cc29d6e2d5c37bde47ec94",
"commits": [
"9dc1071da7e560db2ea899fa23aab885a25ea862",
"024b14124aa2434e90468408b94121c897311f37"
]
},
"855": {
"url": "https://patch-diff.githubusercontent.com/raw/wolfSSL/wolfssh/pull/855.patch",
"sha256": "b94393df9528f66f6df1aba94015331fee3110c9ea2007a0eff369d8887b4b29",
"commits": [
"73b165651c80a20047d913d32b0ffac47cb99ef6",
"a87ab400b3900d1e7fdda33c898094d6e3ada21d",
"5ae5c250e2d9b3bfdaea4f9c73cdf4b9f2592daa",
"dee1c59f263220ecd05a88a72da104c22bef0598",
"2e5484f36f279484ca06e02ef34ddd420fc0cf87",
"03ca9221a36a2a7160a99aeee7bc7ea8368013b5",
"2086f34ff37911a1fa388f843ee9d336e740bf44"
]
},
"921": {
"url": "https://patch-diff.githubusercontent.com/raw/wolfSSL/wolfssh/pull/921.patch",
"sha256": "f6c88598d7f3c94d92c31cecc4ef1e6d779b49f9eff6556ac1c57426d272d504",
"commits": [
"e9d288ec603531a1d544e77fb1bbdf634cb2a57f"
]
}
}