diff --git a/README.md b/README.md index ffe0bc4..e45e403 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ESP32-S3 firmware for a secure, multi-transport RS-232 adapter. It operates one - ESP32-S3-WROOM-1-N16R8 module - 16 MB flash - 8 MB octal PSRAM -- [Adafruit MAX3243 full-pinout male RS-232 breakout](https://www.adafruit.com/product/6253) +- Adafruit MAX3243 full-pinout RS-232 breakout; the male connector version is preferred (see [Hardware wiring](docs/wiring.md) for supported connector variants) ## Development status @@ -29,7 +29,7 @@ The N16R8 target has 16 MiB flash and 8 MiB octal PSRAM. PlatformIO uses the cus | Partition | Offset | Size | Purpose | |---|---:|---:|---| -| `nvs` | `0x009000` | 512 KiB | Serial, Wi-Fi, role-based users, legacy network credential, HTTPS identity, and SSH host-key data | +| `nvs` | `0x009000` | 512 KiB | Serial, Wi-Fi, local-display, role-based user, legacy recovery credential, HTTPS identity, and SSH host-key data | | `otadata` | `0x089000` | 8 KiB | Active OTA-slot selection metadata | | `phy_init` | `0x08B000` | 4 KiB | Optional PHY initialization data | | `nvs_key` | `0x08C000` | 4 KiB | Reserved for future encrypted-NVS keys | @@ -73,7 +73,7 @@ The firmware provides an interactive UART0 console at `serial-tool>`. Run `help` The console supports session history, line editing, cursor movement, and hierarchical Tab completion. After an unattended boot, attach an ANSI-capable terminal and press Enter once to enable enhanced editing; this avoids blocking while no terminal is attached. -Serial configuration and Wi-Fi edits remain in RAM until explicitly saved with `serial save` or `wifi save`. Authenticated admin SSH sessions expose the shared operational administration registry, including interactive secrets, recovery-material management, network diagnostics, and deferred reboot/SSH lifecycle commands. Only initial administrator bootstrap and explicit recovery of an unavailable user database remain UART0-only. `web credentials show` exposes only the legacy migration/recovery credential, not an active Phase 8B network login. +Serial configuration and Wi-Fi edits remain in RAM until explicitly saved with `serial save` or `wifi save`. Authenticated admin SSH sessions expose the shared operational administration registry, including interactive secrets, recovery-material management, network diagnostics, and deferred reboot/SSH lifecycle commands. Initial administrator bootstrap and explicit recovery of an unavailable user database remain UART0-only. An administrator also cannot generate a replacement password for its own account over SSH, preventing the one-time value from being lost when that mutation revokes the session. `web credentials show` exposes only the legacy migration/recovery credential, not an active Phase 8B network login. ## Security notes diff --git a/docs/agent/current-state.md b/docs/agent/current-state.md index 48e20f4..9a2fd93 100644 --- a/docs/agent/current-state.md +++ b/docs/agent/current-state.md @@ -44,12 +44,6 @@ No authored `src/*.{c,h}` `TODO`, `FIXME`, `XXX`, or `HACK` markers were found d These observations should be checked when touching the relevant area; they are not automatically bugs requiring unrelated cleanup. - `src/main.c` logs a Phase 7E startup banner although the implementation/roadmap is at Phase 8C. -- `docs/command_reference.md` calls `web credentials show` physical-console-only in one row, while current source and surrounding text allow it to authenticated admin SSH. -- The same reference describes interactive `user key add ` as a physical UART0 prompt, but transport-neutral prompt code allows it over admin SSH. -- Historical Phase 7 electrical-test steps expect Select to remain read-only and describe a powered state before automatic OLED initialization; current firmware has local controls and initializes the display during boot. -- `README.md`'s NVS partition-purpose list omits the `local_ui/config` blob. -- `README.md` links Adafruit product 6253, while `src/board_pins.h` and `docs/wiring.md` identify product 5988. **Needs verification:** whether this is an intentional male/female breakout distinction. -- `docs/user_administration_tests.md` contains historical Phase 8A/8B behavior that differs from current Phase 8C admin SSH routing. Treat phase-specific sections as historical procedures. - Manual completion candidates omit implemented `wifi next-profile`. - Some source comments still call shared commands UART0-only or call the current local status/control task read-only. - `USER_DATABASE_LOAD_EMPTY` and the corresponding `main.c` log branch appear reserved or vestigial; the current missing-storage success path migrates valid legacy credentials. **Needs verification** before removing or repurposing. @@ -60,7 +54,6 @@ These observations should be checked when touching the relevant area; they are n - Complete the documented Phase 8C target-hardware validation before marking it complete. - Confirm task-local Newlib standard-stream behavior if ESP-IDF/Newlib configuration changes; admin SSH command output relies on dispatcher-task stream redirection. - Revalidate software-crypto/watchdog behavior before changing crypto acceleration or PSRAM placement. -- Verify the Adafruit breakout product identity and reconcile hardware documentation. - If HTTPD concurrency configuration changes, verify whether the boot-local Basic-authentication cache needs explicit locking. - Treat serial-service exclusivity as an architectural contract: its public read/write APIs do not themselves prove that only the broker calls them. diff --git a/docs/command_reference.md b/docs/command_reference.md index aaed367..4e4c2e6 100644 --- a/docs/command_reference.md +++ b/docs/command_reference.md @@ -1,6 +1,6 @@ # Command reference -UART0 and authenticated `admin` SSH sessions use the same registered command implementations through one serialized dispatcher. Admin SSH exposes the full operational registry, including interactive prompts, recovery-secret display, network diagnostics, reboot, and HTTPS/SSH material mutation. Only initial administrator bootstrap and explicit recovery of an unavailable user database remain physically bound to UART0. Run `help` for root commands and ` help` for a group summary. Configuration changes are RAM-only unless explicitly saved. +UART0 and authenticated `admin` SSH sessions use the same registered command implementations through one serialized dispatcher. Admin SSH exposes the full operational registry, including interactive prompts, recovery-secret display, network diagnostics, reboot, and HTTPS/SSH material mutation. Initial administrator bootstrap and explicit recovery of an unavailable user database remain physically bound to UART0. Admin SSH also rejects generating a replacement password for its own account so the one-time value cannot be lost when the session is revoked. Run `help` for root commands and ` help` for a group summary. Configuration changes are RAM-only unless explicitly saved. ## System @@ -24,7 +24,7 @@ UART0 and authenticated `admin` SSH sessions use the same registered command imp | `user role --force` | Change a role; the final administrator cannot be demoted. | | `user password ` | Set and confirm a new password without echo. | | `user password --generate` | Replace a password with a generated value displayed once. | -| `user key add ` | Prompt on physical UART0 for one bounded OpenSSH public-key line. | +| `user key add ` | Prompt on UART0 or authenticated admin SSH for one bounded OpenSSH public-key line. | | `user key add ` | Import a key non-interactively; intended for authenticated admin SSH and also accepted on UART0. | | `user key delete <0..2> --force` | Delete one key by the index shown by `user show`. | | `user key clear --force` | Delete all public keys for an account. | @@ -116,7 +116,7 @@ Opening `/dev/ttyACM*` with DTR asserted creates the `usb-cdc` broker client, st | `web status` | Show HTTPS and WebSocket state. | | `web start` / `web stop` | Start or stop HTTPS service. | | `web counters` / `web clear-counters` | Show or clear web counters. | -| `web credentials show` | Display the physical-console-only legacy migration/recovery credential; it is not a Phase 8B network login. | +| `web credentials show` | Display the legacy migration/recovery credential on UART0 or authenticated admin SSH; it is not a role-based network login. | | `web credentials rotate --force` | Replace the legacy recovery credential and synchronize the migrated pre-bootstrap account only. | | `web certificate info` | Display certificate identity and fingerprint. | | `web certificate rotate --force` | Replace the HTTPS certificate and private key. | @@ -144,7 +144,7 @@ UART0 and admin SSH submit to one bounded queue, and one dispatcher task is the Admin SSH supports four-entry per-session command history with Up/Down, bounded whole-line Tab completion, Backspace/Ctrl-C, and visible or no-echo interactive prompts. History is RAM-only, private to the session, and wiped on disconnect. Ping callbacks enqueue bounded typed results so all formatting remains on the dispatcher task. -`reboot`, `ssh stop`, session disconnect, and SSH host-key reset/rotation are deferred until the command acknowledgement has left both the administration output ring and transport TX buffer. The shell stops accepting another command while such an action is pending. SSH host-key replacement or service stop closes all SSH sessions; reconnect and verify the new fingerprint where applicable. Web recovery credentials/certificates, Wi-Fi secrets, and interactive user passwords/keys are available to authenticated administrators and must therefore be treated as remotely accessible administrative material. Only `user bootstrap` and `user recover --force` remain UART0-only. A connected administrator still cannot generate its own replacement password remotely, preventing the one-time password from being lost during self-revocation. SSH does not provide `exec`, SFTP, SCP, forwarding, or subsystems. +`reboot`, `ssh stop`, session disconnect, and SSH host-key reset/rotation are deferred until the command acknowledgement has left both the administration output ring and transport TX buffer. The shell stops accepting another command while such an action is pending. SSH host-key replacement or service stop closes all SSH sessions; reconnect and verify the new fingerprint where applicable. Web recovery credentials/certificates, Wi-Fi secrets, and interactive user passwords/keys are available to authenticated administrators and must therefore be treated as remotely accessible administrative material. `user bootstrap` and `user recover --force` remain UART0-only. A connected administrator also cannot generate its own replacement password remotely, preventing the one-time password from being lost during self-revocation. SSH does not provide `exec`, SFTP, SCP, forwarding, or subsystems. ## Hardware diagnostics diff --git a/docs/electrical_tests.md b/docs/electrical_tests.md index 16df947..0307636 100644 --- a/docs/electrical_tests.md +++ b/docs/electrical_tests.md @@ -1,6 +1,6 @@ # Electrical tests -These procedures verify the Phase 7A OLED and buttons, MAX3243 breakout, UART1 data path, hardware flow control, and session broker. They are manual tests: the firmware never starts one automatically. +These manual procedures verify the OLED and buttons, MAX3243 breakout, UART1 data path, hardware flow control, and session broker. The firmware does not start diagnostic commands automatically, but normal boot does initialize the OLED, play the bounded identity animation, and start the local status/control UI when the hardware responds. > **Safety:** With power removed, install only the wiring required by the selected test. DE-9 pins 3 (`TX`), 4 (`DTR`), and 7 (`RTS`) are driven outputs. Never connect one of these outputs to another driven output. Keep temporary Dupont wiring short and secure. Power the OLED only from 3.3 V because module-mounted I²C pull-ups may connect SDA and SCL to the OLED `VCC` rail. @@ -20,11 +20,11 @@ Disconnect both USB connectors and every other power source before checking or c ### 2. Powered idle checks and address probe -Apply power, but do not initialize the OLED yet. +Apply power and allow normal boot to complete. A connected, responsive OLED is normally initialized automatically and may already show the status UI. 1. Measure OLED `VCC` relative to `GND`; it should be approximately 3.3 V. 2. Measure idle SDA on GPIO11 and idle SCL on GPIO12. Both should be near 3.3 V. Power down immediately if either bus line rises toward 5 V; correct the OLED supply or pull-up wiring before continuing. -3. Run `debug display status` and record the diagnostic state. +3. Run `debug display status` and record whether normal boot initialized the display. 4. Run `debug display probe`. Confirm that it tests only 7-bit `0x3c` and `0x3d` at the initial 100 kHz bus rate. The tested module should acknowledge at `0x3c` (8-bit `0x78` write / `0x79` read). If the expected address does not respond, treat the result as a nonfatal hardware finding. On this dedicated local-UI bus, `debug display scan --force` may identify an unexpected address before further investigation. Otherwise leave the serial core running, power down, and recheck 3.3 V power, common ground, SDA/SCL order, solder joints, and pull-ups. Do not scan a bus shared with unrelated I²C devices. @@ -103,6 +103,8 @@ With all buttons released, run `debug buttons status`. Confirm previous/back GPI Run `debug buttons test` for the default 10-second interval. During the test, press and release each button separately with a deliberate short press, then repeat with a sustained long press. Confirm that the correct button and short/long classification are reported exactly once per intended action. +> **Live-UI caution:** The normal local status/control task continues to process button input during this diagnostic. Begin on a status page, watch the OLED, and cancel any Controls confirmation that opens. Do not leave a disruptive action selected while testing long holds. + Repeat with an explicit duration, for example: ```text @@ -118,9 +120,9 @@ Use the longer run to check: `debug buttons test [seconds]` accepts 1 through 30 seconds and defaults to 10 seconds when omitted. Record unexpected event duplication, missed transitions, incorrect GPIO mapping, false long presses, or a test that fails to terminate. -### 8. Read-only status UI (Phase 7C) +### 8. Status pages -After boot, the OLED starts on the **OVERVIEW** page. A short previous/back press on GPIO10 and a short next press on GPIO14 must wrap through these read-only pages: +After boot, the OLED starts on the **OVERVIEW** page. A short previous/back press on GPIO10 and a short next press on GPIO14 must wrap through these status pages: 1. **OVERVIEW** — serial, broker, USB, HTTPS/WebSocket, SSH, and Wi-Fi summary. 2. **RS232 MODEM** — framing, modem inputs, byte counters, queue depth, drops, and faults. @@ -129,7 +131,7 @@ After boot, the OLED starts on the **OVERVIEW** page. A short previous/back pres Confirm that each page remains entirely within the blue content panel. The yellow panel must retain the same fixed icon positions while pages change: serial, segmented Wi-Fi strength, USB, WebSocket, SSH, broker clients, and alert. Active icons are solid with a value below; inactive icon slots remain completely blank and omit their value. Confirm that page text uses readable upper/lowercase, separates items and values with colons, and displays the active station SSID or AP SSID with its channel on the Overview and Network pages. -The UI refreshes at 4 Hz maximum and is read-only: select (GPIO13) must not change a service, acquire/release a writer, inject serial data, or expose passwords, credentials, tickets, or key material. With UART0, USB CDC, WebSocket, and SSH active, navigate pages and confirm that the display continues to update without disrupting serial traffic or UART0 recovery. +The UI refreshes at 4 Hz maximum. Status pages are observational and must not expose passwords, credentials, tickets, or key material. A short Select press opens the separate Controls menu described below; merely navigating status pages must not change a service, alter writer ownership, or inject serial data. With UART0, USB CDC, WebSocket, and SSH active, navigate pages and confirm that the display continues to update without disrupting serial traffic or UART0 recovery. For the OLED-aging policy, leave all three buttons untouched and verify: @@ -138,7 +140,7 @@ For the OLED-aging policy, leave all three buttons untouched and verify: 3. The first debounced press of any button wakes the OLED at contrast `127` without changing the current page. 4. A subsequent previous/next press navigates normally and restarts the inactivity timers. -`debug display status` may verify an individual transition in a separate timing run. Every display diagnostic counts as activity and holds normal UI rendering for 30 seconds, so do not invoke it between the five- and ten-minute observations of one continuous run. Record a missing, stale, clipped, or implausible status value or incorrect dim/off/wake transition before Phase 7C is marked complete. +`debug display status` may verify an individual transition in a separate timing run. Every display diagnostic counts as activity and holds normal UI rendering for 30 seconds, so do not invoke it between the five- and ten-minute observations of one continuous run. Record any missing, stale, clipped, or implausible status value or incorrect dim/off/wake transition. ### 9. Local controls (Phase 7D) diff --git a/docs/roadmap.md b/docs/roadmap.md index 75d2a73..d282f55 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -45,7 +45,7 @@ These constraints apply across all phases: | 12 | Advanced network integration | **Under evaluation** | | 13 | Optional filesystem-backed features | **Under evaluation** | -## Completed foundation +## Completed phases ### Phase 0 — RS-232 hardware characterization @@ -116,7 +116,7 @@ A WPA3-required profile correctly rejects an AP that does not advertise the requ Implemented and hardware-validated: - HTTPS-only service on TCP port 443 using ESP-IDF mbedTLS. -- Persistent random shared `admin` credential. +- Persistent random shared `admin` credential, later retained only as migration/recovery material by Phase 8. - Persistent device-specific ECDSA P-256 certificate and private key. - Physical-console credential retrieval, rotation, certificate inspection, rotation, and recovery. - Explicit failure behavior that preserves UART0, USB, serial, and Wi-Fi recovery paths. @@ -136,139 +136,58 @@ Implemented and hardware-validated: - Viewport-constrained terminal fitting without recursive page growth. - Validation with multiple clients, ANSI colors, advanced escape sequences, and full-screen terminal software. -## Latest completed phase - ### Phase 6 — Authenticated SSH serial transport -The SSH implementation is complete and hardware-validated. It provides: +Implemented and target-hardware validated: -- wolfSSH on TCP port 22 while HTTPS remains on mbedTLS. -- Password authentication using the shared `admin` credential. -- A separate persistent ECDSA P-256 SSH host key and OpenSSH-compatible fingerprint. -- Interactive shell/PTY channels only. -- No `exec`, subsystem, SFTP, SCP, agent forwarding, or TCP forwarding. -- Two fixed session/handshake slots. -- Three password attempts and a 15-second handshake deadline. -- No broker client before successful authentication and shell negotiation. -- Binary-transparent serial forwarding with no in-band writer-control protocol. -- Opportunistic writer acquisition and observer behavior through the common broker. -- Credential-rotation revocation and UART0 session/host-key administration. -- A single bounded wolfSSH owner task pinned to CPU 1. -- wolfSSL/wolfSSH allocations that prefer PSRAM with internal-memory fallback. -- A per-call wolfSSH receive budget to prevent pipelined input from monopolizing the owner task. -- Bounded broker and WebSocket work bursts that guarantee scheduler/idle progress during sustained serial traffic. -- Software AES for PSRAM-backed mbedTLS records plus software wolfCrypt AES/SHA, preventing mbedTLS and wolfSSL from independently reconfiguring shared ESP32 crypto hardware. -- Internal task stacks retained for safety during flash/NVS cache-disable windows. -- Heap and SSH stack telemetry through `memory` and `ssh status`. - -SSH login, host-key verification, serial traffic, Web Serial coexistence, and USB CDC coexistence have been validated. During concurrency testing, the previously failing three-client arrangement used USB CDC and SSH as writer in turn while the remaining transports observed. - -Early runs showed healthy heap values but CPU 0 idle-task watchdog timeouts, disproving the initial memory-pressure hypothesis. One trace exposed an unbounded broker scheduling path. A later, decisive trace repeatedly stopped inside mbedTLS's external-RAM hardware-AES DMA wait while HTTPD encrypted a WebSocket record. The broker and WebSocket producer now use bounded active bursts, mbedTLS software AES avoids the hanging PSRAM DMA path, and wolfSSL AES/SHA hardware acceleration is disabled because its private locks do not coordinate with ESP-IDF's crypto lock. - -#### Phase 6 hardware validation - -The final target-hardware retest covered: - -1. USB CDC, one WebSocket terminal, and one SSH terminal connected concurrently. -2. SSH as writer with USB CDC and WebSocket observing. -3. USB CDC as writer with SSH and WebSocket observing. -4. Serial traffic with active readers and writer-token release/reassignment. -5. Continued HTTPS/WebSocket, SSH, USB CDC, serial-service, and UART0 responsiveness. - -The software-crypto build no longer reproduces the HTTPD watchdog stall. This validates that the failure was a shared hardware-crypto/PSRAM DMA problem rather than heap exhaustion. Phase 6 is complete; these concurrent arrangements remain regression tests for future transport, TLS, memory-placement, and ESP-IDF changes. - -## Current and planned phases - -The order below is the current plan. Phase 7, Phase 8A, and Phase 8B are complete; Phase 8C is implemented and awaiting target-hardware validation; later work remains planned or under evaluation. Detailed requirements should be finalized at the start of each phase, and optional features must not weaken the completed serial and recovery paths. +- wolfSSH on TCP port 22 with a separate persistent ECDSA P-256 host key. +- Two bounded interactive shell/PTY sessions; no `exec`, file transfer, forwarding, or subsystems. +- Authenticated, binary-transparent broker forwarding with opportunistic writer acquisition and observer fallback. +- Bounded handshake attempts/deadlines, receive work, buffers, and a single wolfSSH owner task pinned to CPU 1. +- UART0 lifecycle, session, counter, and host-key administration. +- PSRAM-preferred allocations with internal fallback and internal task stacks. +- Bounded broker/WebSocket work, software mbedTLS AES, and software wolfCrypt AES/SHA to avoid the validated shared-hardware-crypto/PSRAM watchdog failure while retaining ESP-IDF SHA/MPI acceleration. +- Concurrent USB, WebSocket, and SSH operation validated with USB and SSH alternately holding the writer lease. ### Phase 7 — Local display and buttons -Add a standalone local status/control interface without making it a dependency of the serial core. The planning baseline uses a 128×64 dual-color monochrome I²C OLED sold with an SSD1315 controller. Phase 7A confirmed SSD1306-compatible operation, 7-bit I²C address `0x3c`, orientation, column mapping, contrast/inversion behavior, button inputs, and the physical color geometry on the selected hardware. +Implemented and target-hardware validated: -Phase 7A diagnostics and target-hardware electrical validation are complete. Phases 7B through 7E are complete and target-hardware validated. +- Optional 128×64 SSD1315-compatible I²C OLED on GPIO11/12 and active-low buttons on GPIO10/13/14; assignments remain centralized in `board_pins.h`. +- Display service with a static framebuffer, bounded dirty-page commits, and separate yellow status rows 0–15 plus blue content rows 16–63. +- Low-priority status/control task built from copied public snapshots; no service lock is held across I²C and the UI never becomes a broker client. +- Overview, RS-232, broker, and network/service pages with no credential or key material. +- Confirmation-protected local controls for service lifecycle, Wi-Fi reconnect/profile rotation, writer release, display off, and reboot; the UI cannot assign a writer or edit secrets/configuration. +- Persistent dim/off settings, bounded boot animation, consumed wake presses, stuck-button quarantine, and one-shot confirmations. +- Missing, disconnected, or unresponsive display hardware remains nonfatal and can recover through one bounded reprobe. +- Concurrent serial, UART0, USB, WebSocket, SSH, and injected display/button fault behavior validated. See [Electrical tests](electrical_tests.md) and [Hardware wiring](wiring.md). -#### Hardware baseline +## Current and planned phases -- Wire OLED `VCC` to `3V3` and OLED `GND` to `GND`. The OLED must use 3.3 V because module-mounted I²C pull-ups may connect SDA and SCL to `VCC`. -- Wire OLED `SDA` to GPIO11 and OLED `SCL` to GPIO12. These pins are currently unused and sit in the available GPIO10–14 block on the DevKit header. -- Wire three active-low buttons between their GPIO and `GND`, using the ESP32 internal pull-ups: GPIO10 for previous/back, GPIO13 for select/confirm, and GPIO14 for next. -- Use short left/right presses for page or item navigation, short select for entry, a long left press for back/home, and an explicit select hold for disruptive confirmation. -- Verify whether the module provides suitable SDA/SCL pull-ups and that every external pull-up is tied to 3.3 V, not 5 V; add external pull-ups to 3.3 V if the module does not provide them. -- Hardware verification established yellow rows 0–15 and blue rows 16–63. A narrow physical black divider separates the two regions even though row 15 is the final yellow addressable row and row 16 the first blue addressable row. -- Keep assignments centralized in the board profile rather than scattering display or button GPIO assumptions through UI code. - -![Phase 7C icon status-page mockup](phase7c_icon_mockup.svg) - -The persistent yellow strip uses fixed-position serial, Wi-Fi-strength, USB, WebSocket, SSH, broker-client, and alert icons. Active icons are solid with a compact value below; inactive icon slots remain blank and show no value. The blue 128×48 content panel begins at row 16 and rotates through overview, RS-232, broker-client, and network/service pages; the physical black divider remains visually clear. No password, Wi-Fi secret, private-key material, or routine credential data may appear on the display. - -#### Implementation sequence - -1. **Phase 7A — Electrical bring-up and diagnostics — Complete** - - Bounded low-level display and button diagnostics are available under the existing `debug` submenu. - - The selected module acknowledged at 7-bit `0x3c` (8-bit `0x78` write / `0x79` read). A guarded full scan is retained for the dedicated local-UI bus; an absent display remains nonfatal and does not make the serial core dependent on the OLED. - - Hardware validation passed for geometry, orientation, row/column addressing, contrast, inversion, button pull-ups/debounce/short-press/long-press/stuck behavior, and the color geometry: yellow rows 0–15, blue rows 16–63, with a physical black separator between the regions. -2. **Phase 7B — Display driver — Complete** - - The `local_display` service owns the local I²C bus, SSD1315-compatible panel, and framebuffer. It uses a static 1 KiB 128×64 framebuffer, a compact 5×7 upper/lowercase, digit, and punctuation renderer, and an internal mutex; do not add LVGL for this fixed monochrome UI. - - Rendering treats the yellow 128×16 status panel (rows 0–15) and blue 128×48 content panel (rows 16–63) as separate panels so the physical divider remains clear. - - Frame commits refresh only dirty 8-pixel pages. I²C runs at 100 kHz with bounded transactions. - - The display bus initializes at boot; a known display is automatically initialized and cleared. A missing or unresponsive display is nonfatal and does not make the serial core dependent on it. - - Target-hardware validation passed for initialization, all display diagnostic patterns, the rendered panel layout and physical divider, row 15/16 boundary, contrast, inversion, display-off/reinitialization, and button diagnostics. Observed contrast control has a useful brightness range of 1–255. -3. **Phase 7C — Read-only status UI — Complete** - - The low-priority `local_status_ui` owner task builds display state from copied public serial, Wi-Fi, broker, USB, WebSocket, HTTPS, and SSH snapshots. It does not parse CLI output, reach into transport internals, or become a broker client. - - Previous/back and next short presses navigate overview, RS-232/modem, broker-client/writer, and network/service pages. Select has no state-changing action in this read-only phase. - - The task polls and debounces the existing active-low button inputs and refreshes at 4 Hz maximum. It collects service snapshots before beginning a display frame, so no service or broker lock is held across an I²C transaction. - - The yellow panel is page-independent and uses fixed icon slots. Wi-Fi strength is shown by segmented Wi-Fi bars; the USB glyph is derived from Pictogrammers Material Design Icons. Page text uses lowercase where appropriate, separates labels and values with colons, and shows the active station or AP SSID plus channel. - - To reduce OLED aging, inactivity dims contrast to `1` after five minutes and switches the panel off after ten minutes. The first debounced button press restores contrast `127` and wakes without navigating. - - A missing/unresponsive display is nonfatal; the task remains read-only and never acquires serial writer ownership. - - Target-hardware validation passed for the four pages, fixed active-only status icons, lowercase layout, diagnostics hold, dim/off/wake behavior, all available concurrent client interfaces, UART0 recovery, and serial traffic from 9600 through 230400 baud. -4. **Phase 7D — Local controls — Complete** - - A shallow local `Controls` menu directly invokes public serial-service, Wi-Fi, HTTPS, SSH, broker, display, and restart APIs; it never parses or injects CLI text. - - The menu provides serial start/stop, Wi-Fi start/stop/reconnect/next-profile, HTTPS start/stop, SSH start/stop, emergency current-writer revocation, display off, and reboot. `WiFi:next profile` queues a rotation to the enabled profile after the currently active one in priority order, wrapping safely; profile configuration and credentials remain unavailable to the UI. The menu deliberately excludes serial configuration editing, Wi-Fi profile/AP changes, host-key/certificate changes, I²C scans, and assignment of the writer to another client. - - A visible confirmation screen and one continuous two-second Select hold are required for service stops, Wi-Fi reconnect, Wi-Fi next-profile, writer revocation, and reboot. The confirmation times out after 30 seconds; previous/back cancels. A wake press is consumed and cannot navigate or confirm an action. - - Wi-Fi lifecycle results report a queued request rather than falsely claiming an asynchronous transition has completed. HTTPS/SSH start requests require a connected station or active AP. The current writer is only ever force-released to no writer; the UI is not a broker client and cannot acquire or assign a writer lease. - - Target-hardware validation passed for menu navigation and timeout behavior, controls and asynchronous results, two-second confirmations and cancellation, Wi-Fi next-profile progression/wrap, chord rejection, wake-press suppression, and preservation of UART0 plus transport recovery. -5. **Phase 7E — Reliability, persistence, and documentation — Complete** - - At startup, a bounded five-second OLED-only identity animation renders a compact monochrome, right-oriented derivative of the project logo in the blue panel: its upright `>_` terminal remains readable, while both USB and serial cable ends enter from the left. The full device name scrolls through the yellow panel. A missing or failing OLED skips the animation without delaying recovery services further. - - A versioned `local_ui` NVS configuration makes the dim and display-off inactivity delays independently configurable from 0 through 86400 seconds. Zero disables a transition; defaults remain 300/600 seconds. UART0 `display` commands provide status, RAM edits, save/load, defaults, and atomic reset behavior. - - A button held for ten seconds is quarantined until its debounced release, after which it rearms. Quarantined inputs no longer keep a chord latched or block the other controls. A confirmation hold can execute at most once. - - Dim/off wake and missing-display recovery now occur only on a new debounced press edge. A held or stuck input cannot continuously probe an absent OLED, flood logs, or indefinitely refresh the inactivity timer; the wake gesture remains consumed. - - Dirty framebuffer commits have a 500 ms scheduling budget in addition to each bounded 250 ms I²C transaction. A failed frame marks the panel unavailable, while serial, broker, network, USB, and UART0 services remain independent. Safely reconnecting the OLED and pressing a button requests one bounded reprobe. - - Wi-Fi manager queue drops now activate the persistent yellow alert slot and remain observable through Wi-Fi counters. Local action errors continue to be reported directly without blocking queue insertion. - - Target-hardware validation passed for configuration persistence and disable semantics, absent and reattached display behavior, I²C NACK/timeout handling, each stuck-button and rearm path, repeated actions/queue-error indication, and concurrent USB CDC, WebSocket, SSH, serial, and UART0 operation during faults. - -Phase 7 completion required electrical validation on the selected module, correct operation with all current transports, bounded display/button failure behavior, safe action confirmation, no unexplained serial or broker loss, and documented UART0 recovery; these criteria are met. +Phase 8A and 8B are complete; Phase 8C is implemented and awaiting target-hardware validation. Later work remains planned or under evaluation. Optional features must not weaken the completed serial and recovery paths. ### Phase 8 — Role-based users and administrative access -Replace the single shared network credential with a bounded, persistent user system. Accounts have one of two authorization roles: `user` receives the currently implemented authenticated serial/status experience, while `admin` additionally receives administrative command and user-management capabilities. UART0 remains the physical recovery and bootstrap authority. +Replace the single shared network credential with a bounded, persistent user system. Both roles can use the authenticated web serial/status interface. Over SSH, `user` routes to the broker-backed serial stream while `admin` routes exclusively to the administration shell and receives no broker client or writer lease. UART0 remains the physical recovery and bootstrap authority. Implementation sequence: 1. **Phase 8A — User database and UART0 administration — Complete** - - A versioned `user_db` NVS blob holds at most eight accounts with unique lowercase usernames, `user`/`admin` roles, random account IDs, per-account authentication generations, and fixed storage bounds. The final administrator cannot be deleted or demoted. - - Passwords use random 16-byte salts and PBKDF2-HMAC-SHA256 verifiers rather than recoverable plaintext. UART0 provides bounded no-echo entry, confirmation, and one-time 24-character generated passwords for bootstrap, create, and password-change operations. Valid authentication attempts perform one real or dummy PBKDF2 outside the database mutex and revalidate the account generation before returning a principal. - - Each account accepts up to three bounded OpenSSH public keys. Initial import supports Ed25519 and ECDSA P-256, validates key structure and P-256 points, rejects duplicate keys on the same account while allowing a key to be assigned to multiple accounts, and exposes only key type plus SHA-256 fingerprint in snapshots and status output. - - On first boot after upgrade, the existing shared `admin` network credential is transactionally imported as a role-`user` account; it is never silently elevated. `user bootstrap` sets a new password and promotes that account to `admin`. Until bootstrap, legacy web credential rotation/reset synchronizes the imported verifier and boot reconciliation repairs an interrupted second NVS commit. After bootstrap, Phase 8A deliberately leaves the legacy HTTPS/SSH credential separate until Phase 8B switches network authentication to the common database. - - `user recover --force` remains registered even when a malformed or incompatible user blob prevents normal initialization. From physical UART0 it explicitly replaces only the user-database blob with a role-`user` account derived from the current legacy network credential; it never automatically erases shared NVS. - - NVS remains unencrypted. Verifiers improve password-at-rest handling, but offline guessing remains possible and append-oriented flash may retain historical plaintext legacy credentials until the partition or flash is physically erased. - - Target-hardware basics for migration, bootstrap, CRUD, persistence, and command behavior were validated before beginning Phase 8B. The full fault-injection matrix remains a regression suite for later hardening. + - Versioned NVS database for up to eight `user`/`admin` accounts, random account IDs and authentication generations, final-admin protection, and three Ed25519/P-256 keys per account. + - Salted PBKDF2-HMAC-SHA256 password verifiers, bounded no-echo entry, one-time generated passwords, and secret-free account/key status. + - Transactional migration of the legacy credential to an unprivileged account, explicit UART0 bootstrap, and UART0-only recovery that replaces only the unavailable user blob. + - Migration, bootstrap, CRUD, persistence, and command basics target-hardware validated; the full fault-injection matrix remains regression coverage. NVS remains unencrypted and offline guessing remains possible. 2. **Phase 8B — Role-aware HTTPS and SSH authentication — Complete** - - HTTPS Basic and SSH password authentication now use the common user database and retain a secret-free principal containing account ID, username, role, authentication method, and credential generation. Authentication fails closed when the database is unavailable; both `user` and `admin` roles retain the existing serial/status experience. - - SSH advertises password and public-key authentication only. The application authorizes stored Ed25519 or ECDSA P-256 username/key pairs, while wolfSSH verifies possession. Authorized unsigned probes produce only the protocol `PK_OK` response; a principal becomes authenticated only from wolfSSH's post-signature result callback and is checked again before broker admission. - - WebSocket tickets are one-time, principal-bound bearer values. Tickets are consumed before currentness validation, and active WebSocket/SSH sessions retain copied principals. Currentness is checked around broker admission, before WebSocket input, and periodically for idle/output sessions. - - Successful password, role, key, bootstrap, delete, and legacy pre-bootstrap synchronization mutations synchronously invalidate only the affected username's tickets and sessions. Per-account generations plus ongoing currentness checks provide fail-safe revocation for changed/deleted accounts while unrelated users remain connected. - - Secret-free UART0 session snapshots show account, role, and authentication method. Transport slot generations remain separate from account authentication generations to preserve ABA-safe task/work ownership. - - The legacy `web_sec` credential no longer authenticates HTTPS or SSH. It remains only for first migration and explicit physical recovery; rotating it after bootstrap does not disconnect or alter role-based users. TLS certificate/private-key handling remains in `web_security`. - - **Completed target-hardware validation:** HTTPS Basic authentication, `/api/status`, and the WebSocket terminal work for both `user` and `admin` accounts with role-based passwords. The legacy recovery credential no longer authenticates network services after bootstrap. Normal SSH password login for both roles reaches the existing broker-backed serial stream; `admin` deliberately receives that same stream rather than UART0 administration until Phase 8C. - - **Completed SSH-key validation:** Ed25519 and ECDSA P-256 public-key login work for both roles, including normal unsigned probe followed by signed proof-of-possession. A public key may be assigned to multiple accounts but cannot be duplicated within one account; the SSH username selects the account principal. Unsupported or incorrect credentials remain rejected without granting a broker client. - - **Completed ticket and revocation validation:** WebSocket tickets are account-bound, one-time, and expire as intended. Password, role, key, delete, and username-recreation mutations promptly revoke only the affected user's tickets and active network sessions, release any affected writer lease, and leave unrelated users connected. - - **Completed recovery and concurrency validation:** The database-unavailable path fails closed and retains UART0 recovery. Concurrent USB CDC, WebSocket, SSH, UART1, and UART0 operation preserves normal serial writer/observer behavior. `web credentials rotate --force` remains recovery-only after bootstrap. `web reset --force` restarts HTTPS with a replacement certificate without revoking unrelated SSH sessions; browsers correctly require a fresh TLS certificate acceptance before reconnecting. + - HTTPS Basic plus SSH password/public-key authentication use the common database and copied secret-free principals; unavailable user storage fails closed. + - One-time principal-bound WebSocket tickets and ongoing principal-currentness checks prevent stale admission or input. + - Account mutations request targeted WebSocket/SSH revocation; authentication generations provide fail-safe invalidation while unrelated accounts remain connected. + - Legacy credentials remain migration/recovery-only after bootstrap. Password/key login, ticket behavior, targeted revocation, recovery, and concurrent transport operation are target-hardware validated. 3. **Phase 8C — SSH administrative shell — Implemented; validation pending** - Authenticated `admin` SSH shell sessions route to a bounded administration worker and never create a broker client or acquire a serial writer lease. Normal `user` sessions retain the existing broker-backed serial stream. - UART0 and admin SSH now submit complete lines to one fixed-length request queue. A single dispatcher task is the sole caller of ESP-IDF's non-reentrant `esp_console_run()` and therefore executes the same registered command handlers for both entry routes. The former separately implemented reduced SSH command dispatcher has been removed. - The worker uses fixed per-session command/input and output buffers. Queue records contain copied secret-free principals and generation-tagged session tokens; late work is discarded after disconnect, slot reuse, role change, password/key mutation, or deletion. Task-local standard streams route canonical handler output into the applicable bounded SSH ring, and only the SSH owner task calls wolfSSH APIs. - Transport-neutral bounded prompts now support interactive user passwords/keys and Wi-Fi secrets over admin SSH without exposing hidden input or allowing another command while a prompt is active. Ping callbacks enqueue typed bounded events and the dispatcher alone formats their output. Four-entry per-session history and whole-line Tab completion are RAM-only and wiped on disconnect. - - Authenticated administrators receive the full operational registry, including recovery-secret display, HTTPS material rotation/reset, reboot, ping, and SSH lifecycle/session/host-key mutation. Self-terminating reboot and SSH actions are deferred until acknowledgement output drains, block further shell input, and are executed through the existing synchronous owner APIs from a separate bounded control task. Only initial `user bootstrap` and explicit `user recover --force` remain physical-UART0 operations. + - Authenticated administrators receive the operational registry, including recovery-secret display, HTTPS material rotation/reset, reboot, ping, and SSH lifecycle/session/host-key mutation. Self-terminating reboot and SSH actions are deferred until acknowledgement output drains, block further shell input, and execute through existing synchronous owner APIs from a separate bounded control task. Initial `user bootstrap` and explicit `user recover --force` remain physical-UART0 operations; admin SSH also rejects generating a replacement password for its own account. - `ssh sessions` and `ssh counters` identify broker versus admin-console routes, worker command state, queued admin output, admission failures, and input backpressure. Admin sessions are checked for a current `admin` principal before command execution and during the active-session reconciliation. - Keep SFTP, SCP, `exec`, forwarding, subsystems, and unauthenticated shells disabled. - Pending target-hardware validation: route separation from the broker, history/Tab editing, interactive visible/hidden prompts, output/backpressure, generated and entered user/password/key management including the longest ECDSA P-256 import, ping event routing, deferred reboot/SSH lifecycle drain behavior, bootstrap/recovery rejection, targeted self/other-user revocation during queued work, UART0/SSH administration serialization, and concurrent USB/WebSocket/user-SSH/admin-SSH operation. diff --git a/docs/user_administration_tests.md b/docs/user_administration_tests.md index 95658c8..7f28503 100644 --- a/docs/user_administration_tests.md +++ b/docs/user_administration_tests.md @@ -1,8 +1,10 @@ -# Role-based user database and UART0 administration +# User administration and authentication tests -## Phase 8A role-based user database and UART0 administration +This document retains phase-specific regression procedures. The Phase 8A and 8B sections describe the behavior of those historical implementation baselines; they are not the current end-to-end acceptance behavior. In current Phase 8C firmware, HTTPS and SSH authenticate through the user database, role-`user` SSH sessions receive the broker-backed serial stream, and role-`admin` SSH sessions receive the administration shell. Use the Phase 8C section for current routing and shared-console validation. Never include generated or entered passwords in test logs. -Keep UART0 physically attached throughout these tests. Phase 8A deliberately does not change HTTPS or SSH authentication: existing network sessions still use the legacy shared credential, while the new database and public keys are prepared for the Phase 8B cutover. Do not include generated or entered passwords in test logs. +## Historical Phase 8A baseline — role-based database and UART0 administration + +Keep UART0 physically attached throughout these tests. At the Phase 8A baseline, HTTPS and SSH still used the legacy shared credential while the new database and public keys were prepared for the Phase 8B cutover. ### 1. First-boot migration and persistence @@ -85,9 +87,9 @@ Because NVS is not yet encrypted, this test validates logical verifier storage r With the maximum supported mix of USB CDC, WebSocket, and SSH serial clients active, repeat user list/show and several UART0 mutations. Confirm password prompts remain physical-UART-only, no user operation acquires a broker client or writer lease, existing Phase 8A network authentication and revocation behavior remains functional, and serial/broker counters show no unexplained loss. Record `memory`, `web counters`, and `ssh counters` before and after the run. -## Phase 8B role-aware HTTPS and SSH authentication +## Historical Phase 8B baseline — role-aware HTTPS and SSH authentication -Complete the Phase 8A bootstrap first and retain two disposable accounts, one `user` and one `admin`. Phase 8B gives both roles the existing serial/status interface; the administrative SSH shell is not expected until Phase 8C. +These procedures preserve the Phase 8B regression baseline. Complete the Phase 8A bootstrap first and retain two disposable accounts, one `user` and one `admin`. At that baseline both roles received the serial/status interface; current Phase 8C firmware instead routes SSH administrators to the administration shell. ### 1. HTTPS passwords and fail-closed behavior @@ -154,7 +156,7 @@ Confirm the full ECDSA P-256 command is accepted, fingerprints appear in `user s ### 4. Interactive administration, lifecycle actions, and revocation -Confirm only `user bootstrap` and `user recover --force` remain unavailable from SSH and continue to work through physical UART0. From admin SSH, exercise manually entered user passwords and public keys, Wi-Fi station/AP secret entry, AP secret display, legacy web recovery credential display/rotation, HTTPS certificate rotation/reset, and both `ping` and `wifi ping`. Hidden characters must not echo or enter command history; visible key input must support Backspace and Ctrl-C; ping lines must remain ordered and correctly attributed to the invoking SSH session. +Confirm `user bootstrap` and `user recover --force` remain unavailable from SSH and continue to work through physical UART0. Also confirm that an administrator cannot run `user password --generate` over its own SSH session; entered replacement passwords and generated passwords for other accounts remain available. From admin SSH, exercise manually entered user passwords and public keys, Wi-Fi station/AP secret entry, AP secret display, legacy web recovery credential display/rotation, HTTPS certificate rotation/reset, and both `ping` and `wifi ping`. Hidden characters must not echo or enter command history; visible key input must support Backspace and Ctrl-C; ping lines must remain ordered and correctly attributed to the invoking SSH session. Exercise `ssh disconnect` for another session and the current session. For the other-session case, confirm the acknowledgement drains, the target closes, and the source shell returns. Separately test `reboot`, `ssh stop`, `ssh host-key rotate --force`, and `ssh reset --force`: each must acknowledge scheduling, stop accepting another command, drain output, and then close/reboot as appropriate. Reconnect after key replacement and verify the new fingerprint. Simulate an unread SSH output window and confirm the destructive action cancels after its bounded drain timeout rather than remaining pending forever.