6.9 KiB
Production web-security host regression
Run from the repository root:
python3 tests/web_security/run.py
Requires a C11 compiler, nm, Python 3, and installed mbedTLS 3.6 headers plus
libmbedx509 / libmbedcrypto. Verified with host mbedTLS 3.6.7. No downloads,
firmware build, device access, asset generation or persistent build outputs.
The runner creates adapters and binaries in a temporary directory.
security.c includes the entire unchanged production src/web_security.c.
No crypto function, generator, validator, decoder or transaction is extracted or
replaced. Actual mbedTLS generates P-256 keys and signed certificates and parses,
hashes, checks the key pair and verifies the self-signature. The legacy fixture
is independently assembled at fixed little-endian byte offsets from a freshly
generated real identity, not a production legacy encoder. No real device secret
or fixed private key is checked in.
Host adapters provide a fixed device MAC, Linux getrandom, a lock-ownership
assertion, and fault-injectable NVS with staged writes/commit. At every write and
commit the adapter checks that production live state is still its predecessor.
Legacy wipe calls are counted and checked. Production restart is simulated by
clearing only module RAM while retaining the adapter's stored record. These
adapters do not prove ESP entropy initialization, allocation failure inside
mbedTLS, FreeRTOS concurrency, ESP NVS flash/power-loss semantics, TLS handshakes,
HTTPD restarts, target stack margins, or secure physical flash erasure. In
particular, modeled failed commits retain predecessor storage; real flash fault
and power-loss behavior needs target validation. No claim that NVS logical
replacement securely erases historical flash pages.
The suite reports 17 production security groups plus one API/console static absence check. Coverage includes fresh and stored-v2 paths, exact v1 migration, metadata/pair-copy bounds, NVS failures and retries, 21 legacy corruptions, 14 v2 corruptions, unknown sizes, real bad signatures with recomputed hashes, mismatched private keys, wrong-device certificates, RNG/MAC/mutex failures, rotation/reset commit-before-publication, unavailable explicit recovery, generation exhaustion and invalid arguments. Console checks establish removal of legacy command/secret/synchronization references, not runtime console lifecycle execution. Read-only database status and login-failure counters are intentionally retained.
Integration/API contract
The five existing public functions remain:
web_security_init(web_security_load_result_t *)web_security_copy_tls_material(...)(unchanged pair-copy API)web_security_get_certificate_metadata(...)(unchanged metadata)web_security_rotate_certificate(void)web_security_reset_all(void)(TLS only, changed signature)
8D.21 adds web_security_get_identity_snapshot() (zero-wait public fingerprint/
generation only) and the owner-only reservation contract
web_security_reserve_identity() / web_security_replace_reserved() /
web_security_release_identity(). The API-symbol check includes all nine functions.
New tests cover zero-wait contention, stale generations, one-use/nonreused tokens,
reservation exhaustion and competing canonical rotate/reset/init during real crypto.
Crypto/NVS runs outside the normal mutex during replacement; the identity reservation
survives until its owner releases it after service stop/start.
python3 tests/web_admin_transport/server_lifecycle.py additionally links real
production security and mbedTLS to the production HTTPS owner, with NVS/HTTPD doubles,
for unchanged identity/storage before commit and no rollback after stop/start failure.
See 8D.21 contracts and evidence limits.
Removed: two credential functions (show_credentials, rotate_credentials),
one credential struct type, three username/password capacity/length constants,
and two console operations (web credentials show, web credentials rotate).
There is no credential generation/display/synchronization path. Authentication
continues to belong to the user database; read-only status does not mutate it.
Legacy startup callers and console policy/completion integrations were removed in
the accepted legacy-credential cleanup; this test does not reintroduce them.
Load results retain STORED=0, GENERATED_MISSING=1, and add MIGRATED_V1=2.
Repeated successful init returns the remembered result without reloading; an active
identity reservation rejects init until its owner finishes.
Migration must validate and commit before publication; no fallback generation
or overwrite follows migration failure. Reset explicitly overwrites missing,
valid, or incompatible material, increments a live generation or uses one when
no live identity exists, and fails on live generation exhaustion. Rotation
requires live material and also fails at UINT32_MAX.
CLI and browser-shell identity mutations now share web_server_replace_identity()
service/security reservation composition. web reset --force retains the old
lifecycle: commit first; when running,
stop then start, with no start after failed stop; otherwise attempt start.
Lifecycle failure does not roll back committed identity. Database accounts are
never synchronized, reset or otherwise mutated by these operations.
Storage contract
The namespace/key remain web_sec/material, one blob, no new NVS keys.
V1 is exactly 1392 bytes, read only through a private fixed-offset decoder.
Its credential layout must still match the shipped admin/24-character URL-safe
format, zero padding/reserved fields, and strict TLS validation.
V2 is exactly 1340 bytes (52 bytes smaller), native little-endian ESP32 layout with compile-time offset/size assertions:
| Offset | Size | Field |
|---|---|---|
| 0 | 4 | schema version = 2 |
| 4 | 2 | blob size = 1340 |
| 6 | 2 | reserved, zero |
| 8 | 4 | nonzero generation |
| 12 | 2 | private key DER length |
| 14 | 2 | certificate DER length |
| 16 | 256 | private key DER, unused bytes zero |
| 272 | 1024 | certificate DER, unused bytes zero |
| 1296 | 32 | certificate SHA-256 fingerprint |
| 1328 | 12 | reserved, zero |
Migration preserves exact DER bytes, fingerprint and generation, including
UINT32_MAX; it never regenerates TLS identity. TLS validation includes exact
outer DER lengths, P-256 pair consistency, self-signature, fingerprint, device
CN/SAN, validity and existing certificate extension policy. Unknown sizes or
schema versions fail with ESP_ERR_INVALID_VERSION; malformed known records
fail with ESP_ERR_INVALID_RESPONSE (underlying operational failures propagate).
Every legacy input buffer is wiped on all post-read exits. The live blob has no
credential fields. No new task, queue, mutex, heap allocation or storage key is
introduced; the existing mutex remains. Migration adds a bounded 1392-byte
transient decoder buffer alongside the 1340-byte candidate; target call-stack
high-water usage is unmeasured.