Files
ESP32_Serial_Swiss_Army_Knife/tests/web_security/README.md
T
Commander1024 91267b371e Consolidate Phase 8 documentation
Mark web administration complete, centralize current contracts and
acceptance evidence, and remove superseded slice records. Update
roadmap,
architecture notes, and test references without changing firmware
sources.
2026-09-13 22:27:10 +02:00

7.0 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 HTTPS identity contracts and acceptance evidence and 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.