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:
@@ -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.
|
||||
Reference in New Issue
Block a user