# Pinned wolfSSH authentication control-flow contract Run from the repository root: ```sh CCACHE_DISABLE=1 python3 tests/wolfssh_auth_contract/run.py ``` Requires Python 3, a host C compiler (`CC`, default `cc`), installed managed wolfSSH, and an existing firmware compilation database/toolchain. No packages are downloaded and no firmware build or device commands run. Generated C and the executable live in a temporary directory and are removed on exit. Compile, preprocess and execution subprocesses have 30/30/10-second limits. The runner prefers the sole `.pio/build/*/compile_commands.json`, otherwise the root database. Select another existing database with `--compile-commands PATH`. It requires the actual generated wolfSSH `internal.c` compilation input to equal `tools/security_overrides.py`'s rendering of the pinned original, preprocesses that compile command (`-E -dM`), and checks this reviewed profile: - `LIBWOLFSSH_VERSION_HEX == 0x01004020` (1.4.20). - RSA disabled; ECDSA and Ed25519 not disabled. - Certificates, `none` authentication and `NO_FAILURE_ON_REJECTED` not defined. For hosts without the ESP compiler/database, explicitly use `--host-only`. This prints a **SKIP** for production feature verification; it still checks the source/version/pin and executes the rendered host contract with the reviewed feature profile. A stale compilation database is not proof of the next firmware build's configuration. ## What executes `run.py` checks the exact application wolfSSH pin, installed version header and reviewed SHA-256 of `managed_components/wolfssl__wolfssh/src/internal.c`: ```text 81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9 ``` Any same-version source change fails before compilation. **Re-audit before updating this hash**; do not automatically bless a dependency update. The runner extracts actual **overridden production** function definitions by balancing braces after masking comments/string literals. Extraction does not rewrite their bodies; the separately verified build overlay does: - `GetBoolean`, `GetUint32`, `GetSize`, `GetStringRef` - `DoUserAuthRequestPassword`, `DoUserAuthRequestPublicKey`, `DoUserAuthRequest` - `SendUserAuthKeyboardRequest`, `GetAllowedAuth` - `SendChannelData` Callback data structures, auth result constants and method masks are extracted from the installed public header. `contract.c` supplies small session/context models, name/algorithm lookup, crypto and packet-output doubles. Binary request fixtures execute the extracted parsers; ordered event traces assert callback, hashing/signature and response order, rather than inspecting source substrings. The 35 baseline cases cover (with the stricter malformed-password contract): - Ed25519 and ECDSA: signed authorization rejection (`INVALID_PUBLICKEY`, `FAILURE`, `REJECTED`, `INVALID_USER`, `INVALID_AUTHTYPE`) never hashes, verifies or calls the result callback. - Both unsigned probe outcomes: no signature work/result callback; an accepted probe sends PK_OK but does not complete authentication. - Bad signatures, good signatures, success-result veto, ignored failure-result callback return, and auth `WOULD_BLOCK`. - Password success/failure, rejected password change, and rejection **before the callback** for a truncated new-password-length field. No password result callback. - Disabled `none`, unknown methods/key algorithms and truncated signed framing. - Direct keyboard-interactive dispatch invokes a **registered non-NULL rejecting prompt callback**, returns error and purges without preparing/building/sending a prompt. The actual library still writes the message-ID byte into its existing output buffer on this path; the test models that buffer and checks this detail. - The actual advertised-method builder excludes keyboard despite the registered keyboard callback, because the allowed-types callback overrides the defaults. - Actual `SendChannelData` copies the bounded consumed prefix before returning a positive count, both on send success and `WS_WANT_WRITE`. Wiping that caller prefix leaves the library copy intact. A blocked flush of earlier data returns a negative code without consuming new data. A further **100 generated-parser cases** test short/missing flags and lengths, truncated/oversized/`UINT32_MAX` password and replacement-password lengths, checked initial offsets and canaries, no callback on malformed fields, preserved username/service/method prefixes, and suffix wiping before response emission. They include success, invalid/backend/rejected outcomes, password changes, no callback, callback-modified credential pointers/lengths, and pending retry. `WS_AUTH_PENDING` deliberately preserves bytes; the project's synchronous callbacks do not use it. This is not an unconditional async secret-wipe promise. ## Limits / ownership This is a library parser/control-flow regression, **not application callback integration coverage**. Its rejecting keyboard callback models the parent's registration and return policy; it does not prove production registration, admission counters, awaiting-result state, principal promotion, or admin wiping. Those belong to the separate application unit suite. Crypto helpers are instrumented doubles; algorithm name lookup is limited to fixture names. Packet construction/network send, hashing and session internals are modeled. This does not validate cryptographic correctness, encrypted packet decoding, real sockets, asynchronous re-entry, complete malformed-input safety, allocation failure, memory erasure throughout wolfSSH, or device behavior. The send test proves only the extracted copy/consumed control flow with successful packet preparation/bundling and the specified send outcomes—not the entire admin transmit loop or TLS/SSH buffer lifecycle. All Phase9 hardware validation remains deferred to the combined phase.