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:
@@ -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}"
|
||||
|
||||
@@ -20,11 +20,12 @@ This is a semantic map, not a complete file inventory. Start here, then read the
|
||||
## Source-pinned dependency corrections (Phases 9C–9D)
|
||||
|
||||
- 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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, 4–6, 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 9C–9D 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
@@ -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.
|
||||
|
||||
|
||||
@@ -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. 9A–9C 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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 (1–20, 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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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')
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
@@ -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 1–4 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.
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user