Add Phase 9C security hardening

Generate exact-hash SDK source overrides without modifying dependencies.
Harden
SSH allocation and algorithm policy, tighten web authentication cleanup,
and add
focused host contract tests and documentation.
This commit is contained in:
2026-09-15 22:12:57 +02:00
parent 751dfb9ddb
commit cdc9c7335a
41 changed files with 3597 additions and 89 deletions
+141
View File
@@ -0,0 +1,141 @@
# SSH protocol policy contracts
Run from the project root:
```sh
CCACHE_DISABLE=1 python3 tests/ssh_protocol_policy/run.py
```
The runner requires the installed, exactly pinned wolfSSH 1.4.20 source, the
production compilation database, its target compiler, Python 3, and a host C99
compiler (`CC`, default `cc`). It never downloads dependencies or invokes a
firmware build. All host headers, extracted functions, and binaries are created
in a temporary directory and removed afterward. Every compiler/preprocessor
invocation is limited to 30 seconds; each host executable to 10 seconds.
`CCACHE_DISABLE=1` is also forced for subprocesses.
If multiple build environments exist, select the intended profile explicitly:
```sh
CCACHE_DISABLE=1 python3 tests/ssh_protocol_policy/run.py \
--compile-commands .pio/build/esp32-s3-devkitc-1-n16r8/compile_commands.json
```
There is deliberately no host-only mode that could silently skip resolved
production-feature verification. The database must select exactly one generated
`security_overrides/wolfssh_internal/internal.c`, whose bytes must equal the
in-memory `tools/security_overrides.py` `render_entry()` output. Original vendor
compilation, missing/duplicate entries, and stale generated content fail closed
with a reconfiguration diagnostic. The runner does not regenerate anything.
The helper is syntax-checked against real target headers using that generated
translation unit's compile settings.
## Production contract and integration
`src/ssh_protocol_policy.{c,h}` exports:
```c
int ssh_protocol_policy_apply(WOLFSSH_CTX *context);
```
It applies permanent, borrowed strings for these five context settings:
| Setter suffix | Exact value |
|---|---|
| `Kex` | `curve25519-sha256,ecdh-sha2-nistp256` |
| `Key` | `ecdsa-sha2-nistp256` |
| `Cipher` | `aes128-gcm@openssh.com,aes256-gcm@openssh.com` |
| `Mac` | `hmac-sha2-256` |
| `KeyAccepted` | `ssh-ed25519,ecdsa-sha2-nistp256` |
The helper returns `WS_SSH_CTX_NULL_E` for NULL and otherwise returns the first
non-success setter result, without subsequent calls or fallback. It does not
allocate, free, or publish a context. A failure can leave earlier settings
applied: **the caller must discard the candidate, not use it**.
The parent integrated the helper in `create_context()` after host-key import and
full staging-buffer wipe, before callback registration and `s_context`
publication. Any policy failure frees the unpublished candidate and returns
`ESP_FAIL`. `context.c` now executes the actual extracted function with the real
policy helper to test this boundary. Service startup isolation, owner/task
lifecycle, session creation, and complete restart paths remain outside this
focused harness.
The exact-version guard rejects unreviewed wolfSSH versions. The source/config
checks below independently verify the actual feature profile; setter success
alone does not validate an algorithm list. `KeyAccepted` controls only the
`server-sig-algs` advertisement in this vendor version. User-key enrollment and
authorization remain enforced by the existing database/authentication path.
## Evidence provided
- Pins the SHA-256 of installed `src/internal.c` and `src/ssh.c`, the application
manifest's exact wolfSSH version, and the resolved compiler version macro.
Source changes require re-audit, not blind hash refresh. Independently checks
the registered override's original-source hash, renders it in memory, and
requires exact equality with the actual generated compiler input. Requires
all original algorithm tables/default strings and extracted protocol-function
bodies to remain unchanged by the override. Negative database cases reject
original/missing/duplicate entries and mismatched render output.
- Replays the actual generated vendor compile command without output/dependency-writing
flags to resolve feature macros, the name/ID/type map, and conditional enums.
All seven distinct policy algorithm names must have their expected compiled
IDs and categories. Required RNG/software-crypto and Ed25519 streaming settings
must remain present; policy-disabling macros are rejected. Negative map cases
demonstrate that missing algorithms fail the checker.
- Compiles the real helper against injected setter doubles. Tests NULL without
dispatch, all five exact lists in order, negative and positive non-success
return propagation at every step, no later calls/fallback, preservation of
unapplied fields, context sentinel survival, and retained string pointers.
These remain helper-boundary checks; the separate context harness below tests
actual caller cleanup and publication.
- Executes actual extracted `src/ssh_transport.c` `create_context()` together
with the real policy helper: 15 cases covering identity-copy failure,
context-allocation failure, both positive/negative key-import failures,
positive/negative setter failures at all five steps, and success. Checks error
mapping, no later policy calls/callbacks/publication on failure, exactly one
free for allocated failed candidates, no free on success, and publication only
after all eight callback registrations. Marks the entire synthetic identity
staging buffer, including the unused tail; asserts full-capacity wipe before
policy initialization, callbacks, and candidate destruction. Context creation
and key import necessarily precede that wipe. Buffer checks occur only while
the extracted function's stack frame is live, never after return.
- Executes the five actual vendor context setter bodies, confirming null-context
errors and their acceptance of invalid, empty, and NULL lists. The helper then
overwrites all five with the fixed policy.
- Executes actual vendor `NameToId`, `IdToName`, `AlgoListSz`, `CopyNameList`,
`CopyNameListPlus`, `BuildNameList`, `SendKexInit`, and `SendExtInfo` bodies.
The mapping and enum values come from production preprocessing. The five actual
`SshInit` list-pointer assignments are checked and reused in the reduced layout.
- Independently decodes initial and repeated/rekey KEXINIT plaintext payloads:
exact KEX/host-key lists, both cipher directions, both MAC directions,
compression/language lists, first-packet flag, reserved field, total bounds,
canaries, and saved exchange-hash input. Exact equality excludes CBC, CTR,
AES192, extra KEX/MAC entries, or an appended default fallback.
- Decodes the actual `server-sig-algs` extension with exactly Ed25519/P-256.
- Exercises missing-host-key, packet-preparation, saved-payload allocation, and
WANT_WRITE behavior with bounded doubles. Checks no send on early failures
and preservation of the exact payload on WANT_WRITE.
- Compiles negative older/newer version cases against the production guard.
## Limits and deferred validation
Host context/session layouts are reduced doubles, not vendor ABI replicas.
The context harness doubles identity copying, key import, context allocation/free,
callback registration, and the wipe primitive. It verifies production call order,
wipe extent, cleanup, and publication, not vendor destruction or secure-wipe
machine code. Synthetic identity bytes are not actual private-key material.
Handshake allocation, packet reservation/wrapping/purging, deterministic cookie
RNG, big-endian integer writing, and send are doubles; payload encoders and list
setters are extracted vendor code. The fixed packet/storage buffers are 1024
bytes. No private key, signature, KEX arithmetic, encryption, MAC, complete SSH
packet framing, peer negotiation, socket, device, or scheduling behavior is
executed. Repeated KEXINIT tests serialization on rekey, not an entire rekey
exchange. The extension test does not establish user-key enforcement.
Hardware/live-client acceptance remains deferred to combined Phase 9: both key
types, explicit rejection of excluded algorithms, real rekey, and mixed
transport responsiveness. No negotiated-handshake or target pass is implied.
This work does not change TLS policy, dependencies, vendor files, generated
assets, global crypto primitives/settings, NVS encryption, or eFuses.