234 lines
16 KiB
Markdown
234 lines
16 KiB
Markdown
# 8D.21 — HTTPS-first security settings
|
|
|
|
## Status (2026-09-13)
|
|
|
|
**Chosen HTTPS slice implemented end-to-end, host-tested and build-verified.**
|
|
Independent review complete with no confirmed actionable findings; target validation/sign-off remains pending. Full
|
|
8D.21 remains incomplete: SSH identity work requires its separately requested
|
|
owner/scope audit and implementation. No SSH identity changes were made here.
|
|
|
|
The previous agent delivered only an audit and baseline, modifying this document,
|
|
`docs/agent/current-state.md` and `docs/phase8d_plan.md`. Those were the only initial
|
|
worktree changes in this continuation. This implementation supersedes their
|
|
NOT-implemented status, not prior user sign-offs or resource follow-ups.
|
|
|
|
Independent reviewer checked shared service/identity reservations, all canonical mutation callers, installed-IDF TLS material copies, commit/failure semantics, auth/ACK and public metadata. Reviewer reran security17, lifecycle44+2 integration groups, cookie lifecycle8+shared, UI/renderer/HTML/CSP, dispatcher/console lifecycle and diff checks PASS. Parent final `pio run` confirmation PASS6.98s,100,532 B RAM/1,825,073 B flash; diff check PASS. No target trust/reset/NVS power-loss or runtime-margin validation inferred.
|
|
|
|
## Delivered scope and user workflow
|
|
|
|
- Admin Settings → **HTTPS / Reboot** now also shows the **stored** HTTPS
|
|
certificate's SHA-256 fingerprint, identity generation and service generation.
|
|
This is public metadata behind current-admin authorization, not an unauthenticated
|
|
endpoint, certificate download, private-key export or proof of the served identity.
|
|
- **Rotate HTTPS identity…** confirms the old fingerprint and both generations,
|
|
persistence, changed browser trust and loss of all web logins/both browser terminal
|
|
routes, including clients admitted before execution. Save browser drafts first.
|
|
- Verify the NEW fingerprint using trusted UART0 `web certificate info` before
|
|
accepting changed browser trust. A browser certificate warning alone is not
|
|
trusted verification. Reload and sign in freshly; never automatically retry.
|
|
- A new identity may already be persisted even if stop/start fails. A retained
|
|
HTTPD can still serve the old certificate; compare stored and served identity
|
|
explicitly and recover using canonical `web stop` / `web start` when necessary.
|
|
- SSH identity, accounts, Wi-Fi, serial configuration and other saved settings
|
|
are unchanged. UART0 administration and native USB UART1 access are independent
|
|
of this rotation. USB is not an administrative console. The preexisting reboot
|
|
action still interrupts the entire device; rotation does not reboot it.
|
|
- No duplicate browser Reset for healthy TLS material. Existing CLI
|
|
`web reset --force` retains TLS-only replacement/recovery and starts a stopped
|
|
HTTPS service; ordinary CLI rotation leaves a stopped service stopped. Neither
|
|
touches users. Unavailable-user-database recovery stays UART0-only. No browser
|
|
unavailable-material recovery, bootstrap, retained recovery secret, new password
|
|
feature, configuration wipe, factory reset, OTA, encryption or secure boot.
|
|
|
|
## Ownership and admission contract
|
|
|
|
### Combined service/security owner
|
|
|
|
`web_server_replace_identity(service_generation, identity_generation, reset,
|
|
&committed)` is the shared off-HTTPD owner entry point. Both nonzero generations
|
|
select conditional healthy-running rotation; both zero select existing canonical
|
|
CLI semantics. Reset cannot be combined with conditional generations.
|
|
|
|
1. Conditional calls avoid the blocking legacy initializer and take the existing
|
|
server mutex with zero wait. Reject stale/saturated service generation, absent
|
|
server, transition or failed cleanup **before any identity mutation**.
|
|
2. Set `s_transitioning` under the server mutex, then release it. This reservation
|
|
excludes canonical start/stop/restart/reboot and competing CLI identity work.
|
|
3. Reserve the expected identity using `web_security_reserve_identity()`. Its
|
|
zero-wait security mutex check excludes stale/unavailable/saturated identity,
|
|
existing reservation and exhausted reservation IDs. Canonical direct
|
|
`web_security_rotate_certificate()` / `web_security_reset_all()` acquire the
|
|
same reservation, so they cannot bypass an admitted combined operation.
|
|
4. After both reservations, advance the saturated service generation. Generate a
|
|
local candidate and commit its bounded existing NVS blob without holding either
|
|
mutex or a spinlock. Publish only after successful commit, wiping the old key.
|
|
The security mutex protects short state admission/copies/publication, not crypto
|
|
or flash. Initialization/migration retains its existing startup locking.
|
|
5. Keep the identity reservation through canonical reserved stop/start. Start can
|
|
copy the committed TLS pair under the ordinary security mutex without deadlock.
|
|
Running rotation/reset restarts HTTPS; stopped rotation remains stopped and
|
|
stopped reset starts HTTPS. No unreserved stop-to-start gap is introduced.
|
|
6. Release the nonreused identity token on return. Only its reserving owner may
|
|
replace once and release it; old tokens cannot replace/release a new reservation.
|
|
|
|
Identity generation advances only on committed replacement, retaining the existing
|
|
persisted UINT32_MAX fail-closed rule. Service generation advances on admitted
|
|
replacement and on each admitted stop/start, including failed attempts, saturating
|
|
at UINT32_MAX without wrapping. Failed identity admission does not advance it;
|
|
failed crypto/storage after combined admission can advance service generation
|
|
without changing identity or stopping HTTPD. Reservation IDs never reuse; exhaustion
|
|
denies identity mutations until reboot, while existing canonical service recovery
|
|
remains available. Reboot invalidates all old browser sessions.
|
|
|
|
Public snapshot copies use separate zero-wait service/security observations, not
|
|
one cross-owner atomic view. They authorize nothing; any intervening service or
|
|
identity change is rejected by the combined execution-time reservations. Unavailable
|
|
identity metadata yields generation0/empty fingerprint/rotatable false without
|
|
removing ordinary lifecycle controls.
|
|
|
|
### Failures, revocation and console safety
|
|
|
|
- Generation, RNG, NVS open/set/commit errors before publication leave the live
|
|
identity unchanged and do not stop HTTPD or revoke its logins. Host NVS faults
|
|
also verify unchanged stored bytes; doubles do not prove power-loss behavior.
|
|
- `committed=true` is irreversible publication even when stop/start returns an
|
|
error. Stop failure skips start and retains canonical cleanup ownership. Failed
|
|
start follows the existing retained-handle cleanup rules. No rollback to the old
|
|
identity occurs. CLI reports the precommit/postcommit distinction; typed results
|
|
deliberately retain conservative failure/unknown-outcome language.
|
|
- Stop invokes canonical `web_cookie_auth_stop()` and transport detach/stopped
|
|
paths, invalidating all web sessions/tickets and closing browser routes. No
|
|
unrelated account or SSH revocation is added. Later logout/revocation is not
|
|
cancellation of admitted crypto/commit/restart.
|
|
- `web_console.c` rotate/reset wrappers and `web_admin_transport.c` deferred
|
|
browser-shell rotation call the same combined owner, not separate commit/stop/start.
|
|
Browser shell retains its existing parsed force policy and drain/dispatcher mask;
|
|
its acknowledgement is still the bounded application-drain heuristic, not HTTP ACK.
|
|
- Inspected `web_admin_transport_detach()`: it waits only for queue submitters, not
|
|
console execution. `admin_ssh_console_close()` marks an executing slot inactive
|
|
without waiting/wiping it; dispatcher return retires that slot. No self-console
|
|
cleanup wait, command-string replay, HTTPD self-stop or lock-held HTTPD wait.
|
|
|
|
## Bounded HTTP and UI contracts
|
|
|
|
Reuse the three existing optional routes and single lifecycle operation slot:
|
|
|
|
- `GET /api/settings/lifecycle`: seven fields, **320-byte response buffer**:
|
|
`generation`, `running`, `transitioning`, `controllable`, `identity_generation`,
|
|
`fingerprint` (64 lowercase hex digits, SHA-256), `rotatable`.
|
|
- `POST /api/settings/lifecycle-operation`: `stop|restart|reboot` still require
|
|
exactly action + service generation. `rotate` requires exactly
|
|
`{"action":"rotate","generation":7,"identity_generation":11}`.
|
|
No reset, extra fields, duplicate fields, escaped/coerced values, zero/saturated
|
|
generations or trailing bytes. Existing **256-byte/four-receive** bound retained.
|
|
- `GET /api/settings/lifecycle-operation`: existing **96-byte** login-isolated
|
|
`id/action/state` result. `failed` can mean rejected admission OR partial effects;
|
|
no falsely precise success, cancellation, rollback or durable-history guarantee.
|
|
|
|
Current cookie/principal/admin checks protect all routes; mutations additionally
|
|
require existing JSON/Origin/CSRF policy. The operation keeps the original login,
|
|
not merely the account. Reuse8D.20's send-return → nonreused-ID HTTPD callback →
|
|
existing dispatcher handoff: no crypto, persistence or lifecycle on HTTPD and no
|
|
request/fd/operation pointer captured. Two-second ACK and post-validation30-second
|
|
dequeue deadlines remain admission bounds, not peer receipt or execution deadlines.
|
|
One accepted-but-lost callback reservation survives until callback arrival or
|
|
successful HTTPD destruction; failed destruction cannot accumulate new callbacks.
|
|
|
|
`web_ui.c` extends the existing lifecycle view/controller, not a parallel Security
|
|
controller. It validates the complete bounded public schema, captures both versions
|
|
before confirmation/session validation, and shares single-flight/pending gates with
|
|
all lifecycle actions. Identity unavailable/exhausted disables rotation. Explicit
|
|
manual Check Result/Refresh,15-second whole-request deadline, late-result/context
|
|
fencing and original-login rules remain. Unknown/lost ACK cannot clear the duplicate
|
|
gate by adopting another result; navigation/pagehide does not replay or restore a
|
|
mutation. No generated secret, secret input or retained retry material is introduced.
|
|
No generated assets or loader/CSP changes; existing HTML/CSP tests pass.
|
|
|
|
## Validation performed
|
|
|
|
All commands below passed in this continuation; no hardware/runtime pass inferred.
|
|
|
|
- `python3 tests/web_security/run.py`: **17 production-security groups** with real
|
|
host mbedTLS, including exact NVS open/set/commit transactional failures, real
|
|
fingerprint/key changes, generation exhaustion, public-only projection, zero-wait
|
|
contention, one-use/nonreused tokens and competing canonical writers during real
|
|
crypto outside locks. Existing migration/invalid storage/recovery tests retained.
|
|
- `python3 tests/web_admin_transport/server_lifecycle.py`: **44 orchestration/ACK
|
|
groups plus2 integrated owner/security groups**. The second executable links the
|
|
extracted production server owner to the complete production security implementation
|
|
and real mbedTLS, with NVS/HTTPD/scheduler doubles. Tests real generation-time RNG
|
|
and NVS open/set/commit failures, exact unchanged identity/storage before commit,
|
|
committed identity surviving stop/start failures, stale service/identity rejection,
|
|
and canonical/direct-security exclusion during crypto AND stop/start. The44 groups
|
|
additionally cover CLI stopped rotation/reset, saturated/stale/busy/currentness
|
|
admission, every detach failure, original ACK sequencing and lifecycle recovery.
|
|
- `python3 tests/web_cookie_auth/run.py --lifecycle`: **8 extended lifecycle groups
|
|
plus shared auth tests**, actual handlers/parser/auth/session store; rotate's exact
|
|
third generation, malformed/truncated inputs, bounded fingerprint-only projection,
|
|
all four actions off-handler/off-callback, login invalidation after admission,
|
|
queue/send/lost/late callback/request storage reuse and deadline/wrap regressions.
|
|
- `python3 tests/web_ui_session/run.py`: **156 browser behavior groups plus C-renderer,
|
|
seven-view HTML and exact CSP checks**. Three new groups cover fingerprint/version
|
|
confirmation, missing/invalid generation/fingerprint/secret-field rejection,
|
|
unavailable identity, shared pending gate, partial-effect warning, role loss,
|
|
lost ACK, expired login and no replay/restore. Existing lifecycle timing and
|
|
navigation tests remain. Not real-browser geometry or TLS target validation.
|
|
- `python3 tests/admin_console_boundary/run.py` and `lifecycle.py`: PASS, including
|
|
deferred browser certificate dispatcher/executing-slot/self-detach safety and
|
|
typed dispatcher queue isolation. `python3 tests/web_admin_transport/run.py`:
|
|
**25 groups**; its owner dependency double now represents the combined API.
|
|
- `python3 tests/web_cookie_auth/run.py` with no option and each of `--settings`,
|
|
`--serial-settings`, `--accounts`, `--network`, `--display`, `--broker`, `--ssh`,
|
|
`--lifecycle`, `--admin`: PASS.
|
|
- `python3 tests/<suite>/run.py` for `admin_ssh_policy`, `web_auth_parse`,
|
|
`web_session_store`, `web_httpd_idle`, `web_serial_performance`, `web_diagnostics`,
|
|
`web_login_ui`, `ssh_management`, `web_network_settings`,
|
|
`session_broker_diagnostics`, `web_security`, `web_admin_tickets`: PASS.
|
|
`python3 tests/web_session_store/run.py --serial`: PASS.
|
|
- Final combined owner/integration suite rerun after adding generation-time RNG and
|
|
stop/start reservation interleavings: PASS. The initial integration-fixture compile
|
|
found an unused copied test counter; fixture extraction was corrected, not compiler
|
|
warnings disabled. No production test failure was hidden.
|
|
- `git --no-pager diff --check`: PASS. Final worktree inspection shows only the
|
|
intended source/tests/docs; no assets, configuration or device-operation changes.
|
|
|
|
## Build, resources and preserved work
|
|
|
|
- Prior audit baseline: `pio run` PASS7.17s, **100,508 B RAM / 1,821,505 B flash**.
|
|
It was not redundantly rerun before implementation.
|
|
- Intermediate implementation build: PASS21.92s,100,532 /1,824,889 B.
|
|
- Final production `pio run`: **PASS22.78s,100,532 B RAM /1,825,073 B flash**.
|
|
Final delta against audited baseline: **+24 B RAM /+3,568 B flash**.
|
|
- Still **39 handlers/six sockets**, same existing lifecycle slot/dispatcher item
|
|
and queue depth, no new task/timer/stack size/session/ticket/heap allocation/schema.
|
|
Lifecycle snapshot buffer grows128→320 bytes; identity-operation slot adds one
|
|
uint32 field; security adds bounded token/one-use state. The existing local TLS
|
|
candidate is retained, not another persistent private-material allocation.
|
|
- Defaults, active sdkconfig and generated sdkconfig.h independently checked:
|
|
**CPU160MHz**. No configuration changes. Combined binary WebSocket send,
|
|
signed-off throughput, serial broker isolation and native USB path unchanged.
|
|
- No assets regeneration, SDK/dependency changes, device actions, upload, erase,
|
|
commit or new branch. User/prior-agent documentation work preserved and updated.
|
|
Runtime HTTPD/dispatcher stack margins and heap minima are not inferred from
|
|
linked RAM or host sizeof; the larger snapshot handler frame needs target checks.
|
|
|
|
## Pending parent/target gates and remaining phase
|
|
|
|
- [ ] Independent parent review of combined ownership, direct canonical exclusions,
|
|
snapshots/ACK/auth/parser/UI and failure/uncertainty contracts.
|
|
- [ ] On target, verify old/new stored and served fingerprints, trusted UART0
|
|
verification, browser trust renewal, fresh login and persisted identity after
|
|
a normal reboot (no automatic reboot in this feature).
|
|
- [ ] Verify canonical CLI reset with unavailable TLS material and stopped-service
|
|
rotation/reset semantics, including low-memory/cleanup failure recovery.
|
|
- [ ] Real-client lost response, timeout, two-admin concurrency, stale confirmations,
|
|
changed role/login and cleanup behavior; no mutation replay.
|
|
- [ ] Full mix at160MHz (two web serial, browser admin, SSH serial/admin and USB),
|
|
unchanged binary transparency/writer isolation, UART0 recovery/native USB
|
|
availability through HTTPS rotation, HTTPD/dispatcher stack and heap minima.
|
|
- [ ] User target sign-off. No reserve approval, full M3 or full8D.21 claim.
|
|
|
|
**HTTPS slice has no remaining implementation item identified here.** SSH identity
|
|
metadata/rotation/reset remains outside this slice and requires a separate request;
|
|
no speculative SSH owner design or implementation is included.
|