Remove Legacy Credential Bootstrap Paths
Decouple user provisioning from HTTPS identity storage while retaining compatible v1 user records and migrating TLS material to the credential-free v2 format. Add focused security regression coverage and update operator documentation.
This commit is contained in:
@@ -32,7 +32,7 @@ SSH role=admin ------> shared administration dispatcher <------ UART0
|
||||
3. Attempt optional OLED initialization and a bounded boot animation. Display failure is nonfatal; a working display can delay later recovery services by about five seconds.
|
||||
4. Initialize button diagnostics and load local-UI and serial configurations, falling back to RAM defaults on load failure.
|
||||
5. Initialize the serial service, session broker, and permanent USB transport task. UART1 is not started automatically here.
|
||||
6. Load/generate HTTPS material, then initialize the user database using the legacy web credential for first migration when available. User-database failure makes network authentication fail closed.
|
||||
6. Load/generate HTTPS material, then initialize the independent user database, committing an empty database when storage is missing. User-database failure makes network authentication fail closed.
|
||||
7. Initialize the HTTPS runtime, SSH host-key material, and permanent SSH owner task.
|
||||
8. Load Wi-Fi configuration and the independent mDNS hostname configuration, persist generated first-boot Wi-Fi defaults when appropriate, initialize the nonfatal mDNS configuration service and Wi-Fi manager, and start Wi-Fi when configured for boot. The Wi-Fi manager owns subsequent mDNS announcement transitions.
|
||||
9. Start HTTPS and SSH only when their startup gates pass. The Wi-Fi portion requires valid configuration and successful manager initialization and, when enabled at boot, successful submission of its asynchronous start request; it does not require association, an IP address, or reachability. Both gates also require HTTPS security readiness, and SSH additionally requires its own security/runtime readiness. The HTTPS-security gate on SSH is an implemented dependency even though SSH has a separate host key.
|
||||
@@ -98,7 +98,7 @@ TinyUSB callbacks enqueue/copy data and state; the transport task owns broker li
|
||||
|
||||
`web_server` runs HTTPS only on port 443 using the device-specific self-signed P-256 certificate from `web_security`. Current routes provide the UI, static assets, status, ticket issuance, and serial WebSocket upgrade.
|
||||
|
||||
HTTPS login uses `user_database` and opaque server-side cookie sessions; Basic authentication and its cache are removed in 8D.3. Before administrator bootstrap, the migrated role-`user` account is synchronized from the legacy credential, so that username/password can authenticate through the database; after bootstrap, the legacy blob is independent recovery material and is no longer consulted for authentication or synchronized into role-based accounts. Both roles retain the same shipped web status/serial UI. 8D.5 adds an admin-only backend without a normal UI entry.
|
||||
HTTPS login uses `user_database` and opaque server-side cookie sessions; Basic authentication and its cache are removed in 8D.3. No legacy credential is imported, synchronized or consulted for authentication. Both roles retain the same shipped web status/serial UI. 8D.5 adds an admin-only backend without a normal UI entry.
|
||||
|
||||
`web_cookie_auth` owns login/session/logout policy: four 120-second digest-only pre-login challenges, explicit same-origin bootstrap, five credential verifications per 60-second global window, and no live-record eviction. Host-only `__Host-` Secure/HttpOnly/SameSite=Strict cookies have absolute lifetimes. Login consumes a challenge, validates bounded JSON and issues a fresh session; logout invalidates only its originating session. Mutations require CSRF and strict canonical HTTPS Origin; serial/admin upgrades require matching cookie/Origin/ticket, with admin role additionally required by the admin endpoints.
|
||||
|
||||
@@ -152,19 +152,19 @@ Revocation has two layers:
|
||||
1. after a database mutation commits, the command layer makes best-effort targeted WebSocket/SSH revocation calls; notification failure does not roll back the mutation;
|
||||
2. transports periodically and at sensitive boundaries recheck principal currentness, providing authoritative fail-safe closure if notification fails.
|
||||
|
||||
The final administrator cannot be deleted or demoted. UART0 is trusted for initial administrator bootstrap and explicit unavailable-database recovery. Authenticated admin SSH can run the operational registry but is denied those two recovery operations; other secret-bearing commands are remotely available unless their handlers deny them.
|
||||
The final administrator cannot be deleted or demoted. UART0 establishes the first administrator through normal `user add <username> admin` and owns explicit unavailable-database recovery to empty. Recovery refuses a healthy database. No bootstrap API or command remains. Authenticated admin SSH can run the operational registry but is denied recovery; other secret-bearing commands are remotely available unless their handlers deny them.
|
||||
|
||||
NVS is not encrypted. Password verifiers improve password storage, but Wi-Fi credentials, legacy recovery credentials, and TLS/SSH private keys remain recoverable under physical flash extraction.
|
||||
NVS is not encrypted. Password verifiers improve password storage, but Wi-Fi credentials and TLS/SSH private keys remain recoverable under physical flash extraction.
|
||||
|
||||
## Typed Accounts settings (8D.10)
|
||||
|
||||
**8D.11 extension:** Authorized-key operations share the Accounts slot/dispatcher and canonical database key transactions with mutation-lock account ID/generation checks. A zero-wait per-account projection returns only key slot/type/SHA256 fingerprint. Independently optional admin/Origin/CSRF JSON POST `/api/settings/accounts/keys` reads this projection; existing operation POST admits bounded public-key text or key-delete/key-clear. Three stable slots may be sparse. Successful mutations target-revoke, including self; uncertain acknowledgement never triggers automatic replay. 24 handlers, unchanged socket/task/stack-size/queue budgets. Runtime stack margins remain pending. See `docs/phase8d11_implementation.md`; older no-8D.11 statements below are historical.
|
||||
|
||||
Current slice 2 extends the same dispatcher slot to create/password and self role/delete/password. Mutation bodies are 768 bytes/four receives; results stay secret-free, 96 bytes, replaceable and session-bound. Conditional password mutation checks identity under the canonical database mutex. A one-second firmware-lifetime ESP timer cancels/wipes non-executing queued credentials at their 30-second deadline plus timer/scheduling latency; dequeue wipes shared inputs after copying, and dispatcher-local credentials persist until admitted work returns. This is not a hard execution/erasure deadline. Separate bodyless admin/Origin/CSRF POST `/api/settings/accounts/generate-password` returns a 24-character value before any commit, without retained retrieval. UI generation has a 60-second best-effort lifetime and context-bound saved acknowledgement before separate submission; JavaScript cannot securely wipe strings. Self revocation may prevent final response/result access; disconnect/401 proves neither success nor cancellation. Browser-shell restrictions and UART0-only bootstrap/recovery remain unchanged. The generated endpoint is independently optionally registered, with failure isolation/restart coverage and 23 handlers. Implementation is complete, host-tested/build-verified; target validation/signoff remains pending. Parent build: 25.61 s, 95,908 B RAM / 1,694,237 B flash; timer runtime costs and stack/heap margins remain unmeasured. No task/stack/queue depth/socket expansion or 8D.11 work. Current contracts and attributed host evidence: `docs/phase8d10_implementation.md`.
|
||||
Current slice 2 extends the same dispatcher slot to create/password and self role/delete/password. Mutation bodies are 768 bytes/four receives; results stay secret-free, 96 bytes, replaceable and session-bound. Conditional password mutation checks identity under the canonical database mutex. A one-second firmware-lifetime ESP timer cancels/wipes non-executing queued credentials at their 30-second deadline plus timer/scheduling latency; dequeue wipes shared inputs after copying, and dispatcher-local credentials persist until admitted work returns. This is not a hard execution/erasure deadline. Separate bodyless admin/Origin/CSRF POST `/api/settings/accounts/generate-password` returns a 24-character value before any commit, without retained retrieval. UI generation has a 60-second best-effort lifetime and context-bound saved acknowledgement before separate submission; JavaScript cannot securely wipe strings. Self revocation may prevent final response/result access; disconnect/401 proves neither success nor cancellation. Browser-shell restrictions and UART0 first-admin provisioning/recovery remain unchanged. The generated endpoint is independently optionally registered, with failure isolation/restart coverage and 23 handlers. Implementation is complete, host-tested/build-verified; target validation/signoff remains pending. Parent build: 25.61 s, 95,908 B RAM / 1,694,237 B flash; timer runtime costs and stack/heap margins remain unmeasured. No task/stack/queue depth/socket expansion or 8D.11 work. Current contracts and attributed host evidence: `docs/phase8d10_implementation.md`.
|
||||
|
||||
**Historical slice 1 architecture (superseded scope/counts, retained evidence):**
|
||||
|
||||
`web_account_settings` supplies an optional admin-only compact account list and one session-bound role/delete operation/result slot, separate from Serial's slot but executed on the same dispatcher queue. HTTPD authorizes/parses/queues; the dispatcher revalidates the initiating login/admin and 30-second dequeue deadline, then calls conditional database mutations and best-effort target web/SSH revocation after success. `user_database_get_accounts()` copies at most eight username/role/ID/auth-generation records under the existing mutex with zero wait and no key/password fields. `user_database_delete_current()` and `user_database_set_role_current()` compare target identity under the mutation lock and share canonical CLI commit/invariant logic; stale selection never intentionally mutates a replacement account. Results are replaceable, not durable/idempotent, and already-admitted work can complete after logout. Self-target, create/password/generated-secret workflows remain unavailable in this slice; bootstrap/recovery remain UART0-only. Accounts UI confirms mutations, retains visible stale lists/outcomes during bounded auto-check/refresh and fences navigation/session changes without changing broker ownership. Three optional routes bring HTTPD handlers to 22; six sockets/no LRU and existing tasks/stacks/queue depth remain. See `docs/phase8d10_implementation.md` for limits and pending target checks.
|
||||
`web_account_settings` supplies an optional admin-only compact account list and one session-bound role/delete operation/result slot, separate from Serial's slot but executed on the same dispatcher queue. HTTPD authorizes/parses/queues; the dispatcher revalidates the initiating login/admin and 30-second dequeue deadline, then calls conditional database mutations and best-effort target web/SSH revocation after success. `user_database_get_accounts()` copies at most eight username/role/ID/auth-generation records under the existing mutex with zero wait and no key/password fields. `user_database_delete_current()` and `user_database_set_role_current()` compare target identity under the mutation lock and share canonical CLI commit/invariant logic; stale selection never intentionally mutates a replacement account. Results are replaceable, not durable/idempotent, and already-admitted work can complete after logout. Self-target, create/password/generated-secret workflows remain unavailable in this slice; first-admin provisioning/recovery remain UART0-only. Accounts UI confirms mutations, retains visible stale lists/outcomes during bounded auto-check/refresh and fences navigation/session changes without changing broker ownership. Three optional routes bring HTTPD handlers to 22; six sockets/no LRU and existing tasks/stacks/queue depth remain. See `docs/phase8d10_implementation.md` for limits and pending target checks.
|
||||
|
||||
## Typed Serial settings (8D.9)
|
||||
|
||||
@@ -235,3 +235,9 @@ Hardware diagnostics are synchronous console commands. RS-232 tests own the phys
|
||||
- Avoid holding service/database/broker locks across I2C, network sends, or other potentially long operations unless the existing contract explicitly requires it. Preserve the existing broker-before-serial lock order.
|
||||
- Serial RX/TX stream payloads, broker per-client payloads, the transactional user-database candidate, and selected cryptographic allocations prefer PSRAM with internal fallback. The live user database, FreeRTOS control structures, UART driver buffers, and task stacks remain internal where deterministic/cache-disable access matters.
|
||||
- The build disables wolfSSL ESP32 AES/SHA acceleration, and the HTTPS path uses software AES for PSRAM-backed records. This preserves the validated workaround for uncoordinated mbedTLS/wolfSSL hardware-crypto locks and a prior mbedTLS external-RAM DMA watchdog stall.
|
||||
|
||||
## Legacy credential removal storage boundary
|
||||
|
||||
`user_database_init(load_result)` has no credential input. Missing storage is persisted empty; `user_database_recover_empty()` is the unavailable-only destructive recovery API. Valid v1 user bytes load without rewriting or account changes. The private `v1_admin_marker` retains its byte position and is derived from administrator count during mutations; it is not a public bootstrap state, new role or schema change. No user migration/bootstrap/synchronization API remains.
|
||||
|
||||
`web_security` owns TLS only. A private reader validates 1,392-byte v1 `web_sec/material`, copies exact key/certificate DER, fingerprint and generation into 1,340-byte v2, commits, then publishes. Temporary v1 credential-bearing input is wiped; no public legacy credential type/getter/rotation remains. Malformed/unknown records and read/validation/commit failures fail closed, with no fallback regeneration or overwrite of rejected records. Missing material may be generated; explicit reset replaces TLS only. Downgrade to v1-only firmware is incompatible. Logical NVS replacement is not secure flash erasure. Contracts/evidence: `docs/legacy_credential_removal.md`.
|
||||
|
||||
@@ -65,6 +65,7 @@ This is a semantic map, not a complete file inventory. Start here, then read the
|
||||
- Files: `src/web_server.{h,c}`, `src/web_serial_transport.{h,c}`, `src/web_ui.{h,c}`, `src/web_console.{h,c}`
|
||||
- Ordinary HTTPS idle cleanup: `src/web_httpd_idle.{c,h}`, owner sweep in `web_httpd_adapter.{c,h}`, lifecycle/TLS composition in `web_server.c`; `tests/web_httpd_idle/run.py`. Independent of diagnostics/optional transports: 15-second observed idle, one-second timer/one queued probe, six rows, actual WS/async/pending exemptions, safe current-owner shutdown and stop/restart fencing. No LRU/socket/timeout/stack increase. SDK queue/owner-delay limits and target checklist: `docs/https_idle_cleanup.md`.
|
||||
- Opt-in admission diagnostics: `src/web_diagnostics.{c,h}`, `tests/web_diagnostics/run.py`. Public synchronous HTTPS create/close callbacks publish six post-TLS connection records; four ticket/upgrade wrappers feed a 32-entry numeric ring. UART0/admin SSH `web diagnostics enable|disable|show|clear`; no queue/task/cleanup override or capacity change. Full bounds, SDK semantics and preaccept/TLS blind spots: `docs/phase8d11_implementation.md`.
|
||||
- Legacy removal: `user_database` persists missing storage empty and preserves valid v1 user bytes; private derived `v1_admin_marker`, no public bootstrap/migration/sync APIs. `web_security` privately migrates v1 1392-byte material to TLS-only v2 1340-byte material, exact identity/generation retained, commit before publish, fail closed without fallback overwrite. Credential commands removed; user generated passwords and TLS rotation remain. Contracts, downgrade and evidence limits: `docs/legacy_credential_removal.md`.
|
||||
- Security files: `src/web_security.{h,c}`, `src/web_cookie_auth.{h,c}`, `src/web_session_store.{h,c}`, `src/web_auth_parse.{h,c}`. Private IDF boundary: `src/web_httpd_adapter.{h,c}`.
|
||||
- Asset files: authored/generated boundary in `src/web_assets_data.{h,c}`, `web_assets/SOURCES.md`, `web_assets/generate_embedded_assets.py`
|
||||
- Interfaces: web init/start/stop/snapshots; HTTP handlers; ticket mint/consume; attach/detach; targeted session revocation
|
||||
@@ -118,11 +119,11 @@ This is a semantic map, not a complete file inventory. Start here, then read the
|
||||
**Responsibility:** persist bounded accounts, verify passwords/SSH keys, issue secret-free principals, and enforce account invariants.
|
||||
|
||||
- Files: `src/user_database.{h,c}`, `src/user_console.{h,c}`; `src/admin_command_gate.{h,c}` is currently a narrow recursive wrapper used only by the `user` command handler, not the global command serializer
|
||||
- Interfaces: init/migration/recovery, authenticate, principal-currentness, account/password/role/key mutations, snapshots
|
||||
- Interfaces: credential-independent init/empty recovery, authenticate, principal-currentness, account/password/role/key mutations, snapshots
|
||||
- Called by: web and SSH authentication/currentness checks and console administration
|
||||
- Dependencies: NVS, secure random, mbedTLS cryptography; after a committed command-layer mutation, best-effort web/SSH revocation calls supplement authoritative transport currentness checks
|
||||
- Ownership: database mutex protects the internal live record and PSRAM-preferred transactional candidate; password authentication runs PBKDF2 outside the mutex and revalidates afterward, while mutation locking must be checked per operation.
|
||||
- Authorization: UART0 exclusively owns initial administrator bootstrap and unavailable-database recovery; current admins may use admin SSH for other commands unless handler policy denies them. HTTPS currently treats both roles alike.
|
||||
- Authorization: UART0 establishes the first administrator through normal `user add` and exclusively owns unavailable-database recovery to empty (healthy database refused); current admins may use admin SSH for other commands unless handler policy denies them. HTTPS serial/status permits both roles; administration requires `admin`.
|
||||
- Constraint: final administrator cannot be deleted or demoted; transport principals must be rechecked after mutations.
|
||||
|
||||
## Administration console infrastructure
|
||||
|
||||
@@ -4,6 +4,10 @@ This file is working memory. Update it during active work and before handoff; do
|
||||
|
||||
## Development state
|
||||
|
||||
- **Legacy credential removal (2026-09-08), implemented/host-tested/build-verified; target pending:** New database and TLS module contracts inspected. Missing user storage commits empty; normal UART0 `user add <username> admin [--generate]` provisions the first administrator. Unavailable-only UART0 recovery rebuilds empty and refuses healthy storage. Existing valid user v1 bytes stay unchanged; private derived `v1_admin_marker`, no public bootstrap/migration/sync API. HTTPS private v1 reader migrates `web_sec/material` 1392 → TLS-only v2 1340 bytes, retaining exact DER/fingerprint/generation, committing before publication; failures fail closed without fallback replacement. Legacy credential commands removed; generated user passwords and TLS rotation retained; reset is TLS-only. Older v1-only firmware cannot read v2; logical NVS replacement is not secure flash wiping, and no factory erase is required. Startup integration complete; SSH gate independent of HTTPS identity, obsolete completion/policy entries removed. Parent security15, canonical accounts, policy/startup64-gate combinations, console lifecycle, cookie accounts, idle18+guards and build/diff checks PASS. Integration agent reports broad auth/transport/lifecycle/UI regression PASS. Review no actionable findings; final agent-added sparse populated v1 reload/authorization regression PASS, test-only extension. Parent final production build 3.93 s, 99,260 B RAM / 1,705,781 B flash (−56/−3,200 vs idle-cleanup baseline). Host NVS doubles do not prove flash/power-loss behavior; no independently captured old-device TLS fixture or hardware validation. Record: `docs/legacy_credential_removal.md`.
|
||||
|
||||
- **Scoped user report — HTTPS idle cleanup worked (2026-09-08):** User accepted that the cleanup worked. This supersedes the no-target-acceptance wording for that specific behavior below, not the historical measurements. No additional soak duration, detailed checklist passes, resource reserve approval, broader 8D.11/M3 sign-off or legacy-removal target validation was supplied.
|
||||
|
||||
- **User-authorized bounded ordinary HTTPS idle cleanup complete (2026-09-08), host-tested/build-verified; target pending:** Preserved existing 8D.11 key/diagnostic implementation (initial Git status was clean). User capture: post-TLS occupancy 6/6, ordinary4/serial2/admin0, ordinary connection ages 50–74 s; admin ticket returned in 14 ms, no TLS/upgrade observed and ticket unconsumed. Installed IDF stops selecting listener at capacity with LRU disabled. `web_httpd_idle.{c,h}` plus sole-private-boundary sweep in `web_httpd_adapter`: six owner-only rows, 15-second observed-idle window (three five-second status polls), one-second ESP timer/at most one queued owner probe, current fd shutdown (no reusable-pointer queued close), all-route SDK request-plus-purge completion marker and input readiness/WS/async checks, TLS-create fd reset, submit fence/nonwrapping restart generations. Diagnostics does not gate policy; socket/timeout/LRU/task/stack/asset settings unchanged. Implementation `pio run` PASS 57.55 s, final confirmation PASS 3.13 s; **99,316 B RAM / 1,708,981 B flash (+160/+1,384 versus diagnostic baseline)**. Rows144 B/module static167 B before placement; timer32 B internal heap before overhead; sweep local frame80 B, runtime margins unmeasured. Final idle **18 + SDK guards**, server **18**, diagnostics **12+1** PASS; cookie accounts/admin/settings/serial-settings, admin transport25/tickets12, store/serial, UI68+CSP and canonical console/account/key/lifecycle/policy suites PASS; diff check PASS. Cookie test-double missing `<stdint.h>` fixed and all variants rerun. Exact contracts/audit/tests/resources/target checklist: `docs/https_idle_cleanup.md`. Ordinary ages are connection ages, not proved idle duration. No hard wall-clock/admission guarantee: owner-blocking handshake/parser/send delays cleanup; continuously active ordinary sockets are not evicted. Reported queue errors retry, but accepted-and-lost nonblocking UDP work stays one reserved probe until successful stop/restart (tested), rather than accumulating unsafe delayed work. Failed stop remains detached/owned until retry. No hardware/upload/erase/commit, target acceptance or reserve approval; next is the documented target reproduction/soak, not capacity changes.
|
||||
|
||||
- **Authorized 8D.11 admission diagnostic slice complete (2026-09-08), host-tested/build-verified; target pending:** `web_diagnostics.{c,h}` uses public synchronous HTTPS create/close callbacks and four ticket/upgrade wrappers, six fixed metadata slots and a 32-record opt-in numeric ring. Console `web diagnostics enable|disable|show|clear` via UART0/admin SSH; browser policy unchanged. No close/open override, queue probe/task, capacity/timeout/log-level/assets/SDK change. Post-TLS owner-published occupancy only; TLS failures/in-progress handshakes and preaccept backlog remain invisible. Final `pio run` PASS **10.30 s, 99,156 B RAM / 1,707,597 B flash**, **+3,080/+3,912 B** versus recorded key-slice baseline. Ring/table target symbols 2,816/192 B; diagnostic handler/record/TLS callback/show local frames 144/144/160/528 B excluding callees, runtime margins unmeasured. PASS diagnostic **12+1**, server lifecycle **16**, canonical console lifecycle/policy/boundary, canonical account/key transactions, cookie/admin and cookie/accounts, admin transport **25**/tickets **12**, browser **68 plus renderer/CSP**, store/serial and diff check. Exact files/commands/contracts and reproduction checklist in `docs/phase8d11_implementation.md`. Existing key work preserved; no device/commit/upload/erase, timeout diagnosis, target acceptance or reserve approval claimed. Next is user target capture, not capacity changes or 8D.12.
|
||||
@@ -127,7 +131,7 @@ Based on checked-in source plus `README.md` and `docs/roadmap.md`:
|
||||
These observations should be checked when touching the relevant area; they are not automatically bugs requiring unrelated cleanup.
|
||||
|
||||
- Some source comments still call shared commands UART0-only or call the current local status/control task read-only.
|
||||
- `USER_DATABASE_LOAD_EMPTY` is only an initialization/failure sentinel at the checked-in revision: every successful `user_database_init()` path returns `STORED` or `MIGRATED_LEGACY`, so `main.c`'s successful "new empty" log branch is unreachable.
|
||||
- Legacy-removal module contracts now make `USER_DATABASE_LOAD_EMPTY` a successful persisted-empty initialization result; older migration/sentinel observations are superseded. Final startup integration/build evidence is pending.
|
||||
- SSH startup is currently gated on successful `web_security` initialization even though SSH uses separate host-key material. **Needs verification:** whether this coupling is intentional recovery policy or an accidental startup dependency.
|
||||
|
||||
## Items to verify in future work
|
||||
|
||||
@@ -46,11 +46,11 @@ Phase 8D.2 adds a third identity: non-reused 64-bit originating web-session IDs
|
||||
|
||||
## UART0 is the physical recovery authority
|
||||
|
||||
**Decision:** UART0 remains independent of UART1 and networking. Initial administrator bootstrap and explicit unavailable-user-database recovery are restricted to UART0.
|
||||
**Decision:** UART0 remains independent of UART1 and networking. The first administrator is created with normal `user add` on UART0; explicit unavailable-user-database recovery to empty is UART0-only and refuses healthy storage. No bootstrap command/API remains.
|
||||
|
||||
**Rationale/evidence:** `main.c` configures UART0 separately; command policy and user handlers deny these operations remotely. README/roadmap identify UART0 as the trusted recovery console.
|
||||
|
||||
**Consequence for future changes:** Network failures or credential corruption must not remove UART0 recovery. Do not expose bootstrap/recovery through web or admin SSH without an explicit security redesign.
|
||||
**Consequence for future changes:** Network failures or credential corruption must not remove UART0 recovery. Do not expose unauthenticated first-admin provisioning or recovery through web or admin SSH without an explicit security redesign.
|
||||
|
||||
**Relevant files:** `src/main.c`, `src/admin_ssh_console.c`, `src/user_console.c`, `docs/roadmap.md`
|
||||
|
||||
@@ -140,17 +140,17 @@ The following first-slice exclusions are historical and superseded by slice 2:
|
||||
|
||||
**Rationale/evidence:** Serial, Wi-Fi, local UI, web security, users, and SSH security each validate schema/size and own their namespace. User/security mutations build and validate candidate state before committing it; security modules avoid silently replacing an established identity. The live user database remains internal while its 5,360-byte candidate is a persistent PSRAM-preferred allocation with internal fallback and is wiped after every transaction.
|
||||
|
||||
**Consequence for future changes:** Add schema versions and transactional candidate validation. Do not overwrite unknown records automatically; provide explicit migration/reset behavior. Preserve the distinct persistence contracts: explicit save/load/default/reset for working configuration and per-blob commit-before-live-install for user and identity mutation. Keep candidate ownership mutex-local and wipe/free it on initialization or recovery failure. Recheck external-buffer staging in the flash/NVS implementation when upgrading from the pinned ESP-IDF 5.5 baseline. Pre-bootstrap legacy credential rotation spans `web_sec/material` and `user_db/database`, is not cross-namespace atomic, and relies on boot reconciliation after interruption.
|
||||
**Consequence for future changes:** Add schema versions and transactional candidate validation. Do not overwrite unknown records automatically; provide explicit migration/reset behavior. Preserve the distinct persistence contracts: explicit save/load/default/reset for working configuration and per-blob commit-before-live-install for user and identity mutation. Keep candidate ownership mutex-local and wipe/free it on initialization or recovery failure. Recheck external-buffer staging in the flash/NVS implementation when upgrading from the pinned ESP-IDF 5.5 baseline. Legacy credential synchronization and reconciliation are removed. Missing user storage commits empty; valid user v1 bytes remain compatible, with private `v1_admin_marker` derived from admin count, not a public bootstrap contract. HTTPS v1 (1,392 bytes) migrates through a private validated reader to TLS-only v2 (1,340 bytes), preserving exact DER/fingerprint/generation and committing before publication. Failures fail closed without fallback regeneration or overwriting rejected records. See `docs/legacy_credential_removal.md`.
|
||||
|
||||
**Relevant files:** `src/serial_config.c`, `src/wifi_config.c`, `src/mdns_config.c`, `src/mdns_service.c`, `src/local_ui_config.c`, `src/web_security.c`, `src/user_database.c`, `src/ssh_security.c`
|
||||
|
||||
## NVS is persistence, not a physical security boundary
|
||||
|
||||
**Decision:** The current firmware stores Wi-Fi credentials, recovery credentials, and TLS/SSH private keys in unencrypted application NVS. The reserved NVS-key partition does not enable encryption.
|
||||
**Decision:** The current firmware stores Wi-Fi credentials and TLS/SSH private keys in unencrypted application NVS. The reserved NVS-key partition does not enable encryption.
|
||||
|
||||
**Rationale/evidence:** `partitions.csv`, README security notes, and current code show no NVS-encryption setup. Original rationale for deferring encryption is outside the implementation; the observable limitation is explicit.
|
||||
|
||||
**Consequence for future changes:** Do not claim resistance to flash extraction. Avoid increasing stored secret exposure. Enabling encryption requires migration/recovery planning, not just changing the partition table.
|
||||
**Consequence for future changes:** Do not claim resistance to flash extraction. Logical NVS replacement can leave old plaintext credentials in flash and is not secure erasure; no factory erase is required by this cleanup. Older v1-only firmware cannot read v2 HTTPS material. Avoid increasing stored secret exposure. Enabling encryption requires migration/recovery planning, not just changing the partition table.
|
||||
|
||||
**Relevant files:** `partitions.csv`, `README.md`, `src/web_security.c`, `src/ssh_security.c`, `src/wifi_config.c`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user