Enable NVS and flash encryption at rest (fw 0.8.3).
Add initEncryptedStorage, development-mode Kconfig defaults, production overlay, and security HIL docs; wire NetBootstrap through encrypted NVS bring-up. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
**Open-source digital radio — Si4684 tuner · ADAU1701 SigmaDSP · Bluetooth aptX Adaptive · ESP32-S3**
|
**Open-source digital radio — Si4684 tuner · ADAU1701 SigmaDSP · Bluetooth aptX Adaptive · ESP32-S3**
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
@@ -57,7 +57,7 @@ no analogue conversions are introduced between the tuner and the wireless link.
|
|||||||
- **6-layer impedance-controlled PCB**, 50 × 90 mm, two ground planes
|
- **6-layer impedance-controlled PCB**, 50 × 90 mm, two ground planes
|
||||||
- Three antennas (ESP32 2.4 GHz, BT1035 2.4 GHz, FM/DAB SMA) with proper keep-outs
|
- Three antennas (ESP32 2.4 GHz, BT1035 2.4 GHz, FM/DAB SMA) with proper keep-outs
|
||||||
|
|
||||||
### Firmware (ESP-IDF, C++23 — fw **0.8.2**)
|
### Firmware (ESP-IDF, C++23 — fw **0.8.3**)
|
||||||
|
|
||||||
| Area | Capability |
|
| Area | Capability |
|
||||||
|------|------------|
|
|------|------------|
|
||||||
@@ -68,7 +68,7 @@ no analogue conversions are introduced between the tuner and the wireless link.
|
|||||||
| **Presets** | Station list CRUD, reorder, recall with audio profile re-apply, last-preset restore at boot |
|
| **Presets** | Station list CRUD, reorder, recall with audio profile re-apply, last-preset restore at boot |
|
||||||
| **Bluetooth** | Discoverable pairing, A2DP status, disconnect |
|
| **Bluetooth** | Discoverable pairing, A2DP status, disconnect |
|
||||||
| **Network** | SoftAP setup mode, STA provisioning, tabbed gzipped SPA (`/`), typed JSON REST API |
|
| **Network** | SoftAP setup mode, STA provisioning, tabbed gzipped SPA (`/`), typed JSON REST API |
|
||||||
| **Security** | NVS-backed credential and preset storage (flash encryption planned — see roadmap) |
|
| **Security** | NVS + flash encryption at rest (dev mode); keys never in repo |
|
||||||
| **Quality** | **13** host unit tests, Doxygen gate, LaTeX manual sync check, GitHub Actions CI on `main` |
|
| **Quality** | **13** host unit tests, Doxygen gate, LaTeX manual sync check, GitHub Actions CI on `main` |
|
||||||
|
|
||||||
Architecture follows a **functional core + imperative shell**: pure domain logic
|
Architecture follows a **functional core + imperative shell**: pure domain logic
|
||||||
@@ -83,6 +83,7 @@ Vertical slices landed on `main` (newest first):
|
|||||||
|
|
||||||
| Version | Highlights |
|
| Version | Highlights |
|
||||||
|---------|------------|
|
|---------|------------|
|
||||||
|
| **0.8.3** | NVS + flash encryption (development mode), `initEncryptedStorage`, security docs |
|
||||||
| **0.8.2** | Tabbed configuration Web UI — now-playing, 6-band EQ, full API coverage |
|
| **0.8.2** | Tabbed configuration Web UI — now-playing, 6-band EQ, full API coverage |
|
||||||
| **0.8.1** | `IntegrationService` — boot preset recall, tune orchestration (tuner + audio + NVS `last_preset`); services stub removed |
|
| **0.8.1** | `IntegrationService` — boot preset recall, tune orchestration (tuner + audio + NVS `last_preset`); services stub removed |
|
||||||
| **0.8.0** | Preset reorder API/UI; broadcast metadata (RDS + DAB DLS); `readDabServiceData` driver path |
|
| **0.8.0** | Preset reorder API/UI; broadcast metadata (RDS + DAB DLS); `readDabServiceData` driver path |
|
||||||
@@ -91,7 +92,8 @@ Vertical slices landed on `main` (newest first):
|
|||||||
| **0.5–0.6** | ADAU1701 runtime EQ/mixer, Si4684 tuning & DAB service list, BT1035 driver |
|
| **0.5–0.6** | ADAU1701 runtime EQ/mixer, Si4684 tuning & DAB service list, BT1035 driver |
|
||||||
| **0.3–0.4** | Secure store, Wi-Fi provisioning, companion-chip boot, walking skeleton |
|
| **0.3–0.4** | Secure store, Wi-Fi provisioning, companion-chip boot, walking skeleton |
|
||||||
|
|
||||||
**Next up** ([`Software/docs/TODO.md`](Software/docs/TODO.md)): NVS/flash encryption (T8).
|
**Next up:** device HIL for encrypted NVS (when PCB arrives). Agent backlog:
|
||||||
|
[`Software/docs/TODO.md`](Software/docs/TODO.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -221,8 +223,9 @@ Generated C++ API reference: run `doxygen Doxyfile` → `Software/docs/api/html/
|
|||||||
| **PCB layout** | 6-layer, DRC clean, plane continuity verified |
|
| **PCB layout** | 6-layer, DRC clean, plane continuity verified |
|
||||||
| **BOM** | Finalised (manufacturable / sourced) |
|
| **BOM** | Finalised (manufacturable / sourced) |
|
||||||
| **Prototype** | In fabrication (PCBWay) |
|
| **Prototype** | In fabrication (PCBWay) |
|
||||||
| **Firmware** | **0.8.2** on `main` — CI green; full Web UI, integration, RDS/DLS |
|
| **Firmware** | **0.8.3** on `main` — encrypted NVS, full Web UI, integration, RDS/DLS |
|
||||||
| **Web UI** | Tabbed SPA covering every REST endpoint |
|
| **Web UI** | Tabbed SPA covering every REST endpoint |
|
||||||
|
| **HIL** | Security checklist pending PCB (`Software/docs/security-flash-nvs.md`) |
|
||||||
| **Production hardening** | Si4684 blob policy (T7), NVS encryption (T8) — open |
|
| **Production hardening** | Si4684 blob policy (T7), NVS encryption (T8) — open |
|
||||||
|
|
||||||
Agent task list: [`Software/docs/TODO.md`](Software/docs/TODO.md).
|
Agent task list: [`Software/docs/TODO.md`](Software/docs/TODO.md).
|
||||||
|
|||||||
+4
-2
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Open-source Hi-Fi DAB+/FM receiver firmware for the ESP32-S3.
|
Open-source Hi-Fi DAB+/FM receiver firmware for the ESP32-S3.
|
||||||
|
|
||||||
**Status:** fw **0.8.2** — tabbed Web UI (now-playing, 6-band EQ, all APIs);
|
**Status:** fw **0.8.3** — NVS + flash encryption (dev mode); tabbed Web UI;
|
||||||
`IntegrationService`; **13** host tests; CI on `main`.
|
`IntegrationService`; **13** host tests; CI on `main`.
|
||||||
See [`docs/TODO.md`](docs/TODO.md) for the agent task list.
|
See [`docs/TODO.md`](docs/TODO.md) for the agent task list.
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ Open this directory (`Software/`) as the Cursor project so `AGENTS.md` and
|
|||||||
```bash
|
```bash
|
||||||
idf.py set-target esp32s3
|
idf.py set-target esp32s3
|
||||||
idf.py build
|
idf.py build
|
||||||
|
idf.py erase-flash flash # once when upgrading to encrypted NVS (0.8.3+)
|
||||||
idf.py -p <port> flash monitor
|
idf.py -p <port> flash monitor
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ Manual PDF (design + HTTP API + class reference):
|
|||||||
cd docs/manual && latexmk -lualatex manual.tex
|
cd docs/manual && latexmk -lualatex manual.tex
|
||||||
```
|
```
|
||||||
|
|
||||||
## HTTP API (fw 0.8.2)
|
## HTTP API (fw 0.8.3)
|
||||||
|
|
||||||
| Method | Path | Purpose |
|
| Method | Path | Purpose |
|
||||||
|--------|------|---------|
|
|--------|------|---------|
|
||||||
@@ -79,6 +80,7 @@ C++ signatures: `doxygen Doxyfile` → `docs/api/html/index.html`.
|
|||||||
| `components/services/` | Tuner, audio, Bluetooth, station, integration services |
|
| `components/services/` | Tuner, audio, Bluetooth, station, integration services |
|
||||||
| `components/net/` | Wi-Fi, HTTP server, gzipped web UI |
|
| `components/net/` | Wi-Fi, HTTP server, gzipped web UI |
|
||||||
| `docs/manual/` | LaTeX technical manual (canonical) |
|
| `docs/manual/` | LaTeX technical manual (canonical) |
|
||||||
|
| `docs/security-flash-nvs.md` | NVS + flash encryption and HIL checklist |
|
||||||
| `docs/TODO.md` | Agent task list (prioritised backlog) |
|
| `docs/TODO.md` | Agent task list (prioritised backlog) |
|
||||||
|
|
||||||
See [`AGENTS.md`](AGENTS.md) §12 and [`instructions.md`](instructions.md) for
|
See [`AGENTS.md`](AGENTS.md) §12 and [`instructions.md`](instructions.md) for
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ idf_component_register(
|
|||||||
"src/NetBootstrap.cpp"
|
"src/NetBootstrap.cpp"
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
EMBED_FILES "www/index.html.gz"
|
EMBED_FILES "www/index.html.gz"
|
||||||
REQUIRES core esp_wifi esp_netif esp_event nvs_flash esp_http_server tuner audio bluetooth station integration bt1035
|
REQUIRES core esp_wifi esp_netif esp_event nvs_flash esp_http_server secure_store tuner audio bluetooth station integration bt1035
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
|
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
|
||||||
|
|||||||
@@ -22,9 +22,9 @@
|
|||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
#include "esp_wifi.h"
|
#include "esp_wifi.h"
|
||||||
#include "nvs_flash.h"
|
|
||||||
#include "audio/AudioService.hpp"
|
#include "audio/AudioService.hpp"
|
||||||
#include "bluetooth/BluetoothService.hpp"
|
#include "bluetooth/BluetoothService.hpp"
|
||||||
|
#include "secure_store/NvsPlatformInit.hpp"
|
||||||
#include "station/StationService.hpp"
|
#include "station/StationService.hpp"
|
||||||
#include "tuner/TunerService.hpp"
|
#include "tuner/TunerService.hpp"
|
||||||
|
|
||||||
@@ -38,21 +38,16 @@ constexpr char kTag[] = "NetBootstrap";
|
|||||||
*
|
*
|
||||||
* @dname initPlatform
|
* @dname initPlatform
|
||||||
* @return Ok on success, or a NetError describing the failure.
|
* @return Ok on success, or a NetError describing the failure.
|
||||||
* @pubstate initialises NVS, esp_netif, and the default event loop.
|
* @pubstate initialises encrypted NVS, esp_netif, and the default event loop.
|
||||||
*
|
*
|
||||||
* @author Michele Bigi
|
* @author Michele Bigi
|
||||||
* @date 2026-07-06
|
* @date 2026-07-06
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] std::expected<void, NetError> initPlatform()
|
[[nodiscard]] std::expected<void, NetError> initPlatform()
|
||||||
{
|
{
|
||||||
esp_err_t nvsErr = nvs_flash_init();
|
const auto nvsResult = secure_store::initEncryptedStorage();
|
||||||
if (nvsErr == ESP_ERR_NVS_NO_FREE_PAGES
|
if (!nvsResult) {
|
||||||
|| nvsErr == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
ESP_LOGE(kTag, "encrypted NVS init failed");
|
||||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
|
||||||
nvsErr = nvs_flash_init();
|
|
||||||
}
|
|
||||||
if (nvsErr != ESP_OK) {
|
|
||||||
ESP_LOGE(kTag, "nvs_flash_init failed");
|
|
||||||
return std::unexpected(NetError::NvsInitFailed);
|
return std::unexpected(NetError::NvsInitFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ namespace net {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr char kTag[] = "SetupWebServer";
|
constexpr char kTag[] = "SetupWebServer";
|
||||||
constexpr char kFirmwareVersion[] = "0.8.2";
|
constexpr char kFirmwareVersion[] = "0.8.3";
|
||||||
constexpr unsigned kRebootDelaySec = 3;
|
constexpr unsigned kRebootDelaySec = 3;
|
||||||
|
|
||||||
extern const uint8_t www_index_html_gz_start[] asm(
|
extern const uint8_t www_index_html_gz_start[] asm(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ idf_component_register(
|
|||||||
SRCS
|
SRCS
|
||||||
"src/NvsSecureStore.cpp"
|
"src/NvsSecureStore.cpp"
|
||||||
"src/NvsAudioProfileStore.cpp"
|
"src/NvsAudioProfileStore.cpp"
|
||||||
|
"src/NvsPlatformInit.cpp"
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
REQUIRES core nvs_flash
|
REQUIRES core nvs_flash
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* @file NvsPlatformInit.hpp
|
||||||
|
* @brief Encrypted NVS partition bring-up for ISecureStore backends.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-07
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <expected>
|
||||||
|
|
||||||
|
namespace secure_store {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief NvsInitError — failure mode for encrypted NVS initialisation.
|
||||||
|
*
|
||||||
|
* @dname NvsInitError
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate none
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-07
|
||||||
|
*/
|
||||||
|
enum class NvsInitError {
|
||||||
|
EraseFailed, ///< nvs_flash_erase failed during recovery.
|
||||||
|
InitFailed, ///< nvs_flash_init failed after recovery attempt.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief initEncryptedStorage — initialise default NVS (+ nvs_keys when enabled).
|
||||||
|
*
|
||||||
|
* @dname initEncryptedStorage
|
||||||
|
* @return Ok on success, or NvsInitError describing the failure.
|
||||||
|
* @pubstate When CONFIG_NVS_ENCRYPTION is set, nvs_flash_init() uses the
|
||||||
|
* nvs_keys partition and flash-encryption key protection per
|
||||||
|
* ESP-IDF v5.5 security docs. Erases and retries on layout mismatch.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-07
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, NvsInitError> initEncryptedStorage() noexcept;
|
||||||
|
|
||||||
|
} // namespace secure_store
|
||||||
@@ -12,9 +12,9 @@
|
|||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Production builds should enable NVS encryption (nvs_keys partition in
|
* Production: NVS encryption + flash encryption enabled in sdkconfig.defaults
|
||||||
* partitions.csv) per ESP-IDF security docs; this slice uses plain NVS
|
* (fw 0.8.3+). Call secure_store::initEncryptedStorage() before first use;
|
||||||
* for development bring-up.
|
* see docs/security-flash-nvs.md.
|
||||||
*
|
*
|
||||||
* @author Michele Bigi
|
* @author Michele Bigi
|
||||||
* @date 2026-07-06
|
* @date 2026-07-06
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* @file NvsPlatformInit.cpp
|
||||||
|
* @brief Encrypted NVS partition bring-up implementation.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-07
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "secure_store/NvsPlatformInit.hpp"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
namespace secure_store {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr char kTag[] = "NvsPlatformInit";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief logEncryptionMode — log active NVS security Kconfig (no secrets).
|
||||||
|
*
|
||||||
|
* @dname logEncryptionMode
|
||||||
|
* @pubstate none; INFO log only.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-07
|
||||||
|
*/
|
||||||
|
void logEncryptionMode() noexcept
|
||||||
|
{
|
||||||
|
#if CONFIG_NVS_ENCRYPTION
|
||||||
|
ESP_LOGI(kTag, "NVS encryption enabled");
|
||||||
|
#if CONFIG_SECURE_FLASH_ENC_ENABLED
|
||||||
|
ESP_LOGI(kTag, "Flash encryption enabled (development=%d)",
|
||||||
|
static_cast<int>(CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT));
|
||||||
|
#else
|
||||||
|
ESP_LOGW(kTag, "NVS encryption without flash encryption — check Kconfig");
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
ESP_LOGW(kTag, "NVS encryption disabled — not for production");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::expected<void, NvsInitError> initEncryptedStorage() noexcept
|
||||||
|
{
|
||||||
|
logEncryptionMode();
|
||||||
|
|
||||||
|
esp_err_t err = nvs_flash_init();
|
||||||
|
if (err == ESP_ERR_NVS_NO_FREE_PAGES
|
||||||
|
|| err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||||
|
ESP_LOGW(kTag, "NVS partition needs erase (err=0x%x)", static_cast<unsigned>(err));
|
||||||
|
err = nvs_flash_erase();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(kTag, "nvs_flash_erase failed (0x%x)", static_cast<unsigned>(err));
|
||||||
|
return std::unexpected(NvsInitError::EraseFailed);
|
||||||
|
}
|
||||||
|
err = nvs_flash_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(kTag, "nvs_flash_init failed (0x%x)", static_cast<unsigned>(err));
|
||||||
|
return std::unexpected(NvsInitError::InitFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace secure_store
|
||||||
+9
-11
@@ -12,12 +12,12 @@ errors, no plaintext secrets.
|
|||||||
|
|
||||||
Working directory for all commands is `Software/`.
|
Working directory for all commands is `Software/`.
|
||||||
|
|
||||||
**Current firmware:** `0.8.2` — tabbed configuration Web UI (full API
|
**Current firmware:** `0.8.3` — NVS + flash encryption (dev mode), tabbed Web
|
||||||
coverage), integration service, RDS/DLS metadata, CI gate.
|
UI, integration service, CI gate.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Completed (fw 0.7.0–0.7.2)
|
## Completed (fw 0.7.0–0.8.3)
|
||||||
|
|
||||||
- **Integration service (T5)** — preset recall with audio profile re-apply,
|
- **Integration service (T5)** — preset recall with audio profile re-apply,
|
||||||
last-preset NVS, \texttt{app\_main} orchestration.
|
last-preset NVS, \texttt{app\_main} orchestration.
|
||||||
@@ -74,14 +74,12 @@ embedded gzip blob. No debug routes in `SetupWebServer`.
|
|||||||
Procurement documented in `Si4684-Firmware/README.md`; CI job
|
Procurement documented in `Si4684-Firmware/README.md`; CI job
|
||||||
`si4684-blobs` runs `tools/check_si4684_blobs.py`.
|
`si4684-blobs` runs `tools/check_si4684_blobs.py`.
|
||||||
|
|
||||||
### T8. Flash/NVS encryption enablement
|
### T8. Flash/NVS encryption enablement — **DONE (fw 0.8.3)**
|
||||||
**Why:** secure storage holds Wi-Fi and user credentials; encryption at
|
`CONFIG_NVS_ENCRYPTION` + flash encryption (development mode) in
|
||||||
rest was deferred.
|
`sdkconfig.defaults`; `secure_store::initEncryptedStorage()`; production
|
||||||
**What:** enable NVS encryption on an encrypted partition (with flash
|
overlay `sdkconfig.defaults.production`; HIL checklist in
|
||||||
encryption), per current ESP-IDF security docs. Verify the mechanism
|
`docs/security-flash-nvs.md`. **Pending:** device verification when PCB
|
||||||
before enabling; keep keys out of the repo.
|
arrives.
|
||||||
**Done when:** credentials are encrypted at rest and the boot path still
|
|
||||||
loads them.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Returns a health-check DTO serialised by
|
|||||||
|
|
||||||
\begin{drnote}[Response schema]
|
\begin{drnote}[Response schema]
|
||||||
\begin{drcode}[JSON]
|
\begin{drcode}[JSON]
|
||||||
{"status":"ok","fw":"0.8.2",
|
{"status":"ok","fw":"0.8.3",
|
||||||
"chips":{"si4684":true,"adau1701":true,"bt1035":true}}
|
"chips":{"si4684":true,"adau1701":true,"bt1035":true}}
|
||||||
\end{drcode}
|
\end{drcode}
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
@@ -354,8 +354,10 @@ as \texttt{last\_preset} (u8). Passwords are wrapped in
|
|||||||
\texttt{core::Secret} in RAM and are never logged or returned by the API.
|
\texttt{core::Secret} in RAM and are never logged or returned by the API.
|
||||||
|
|
||||||
\begin{drcaution}[Encryption at rest]
|
\begin{drcaution}[Encryption at rest]
|
||||||
Development builds use plain NVS. Production should enable NVS encryption
|
Firmware~0.8.3+ enables NVS encryption (\texttt{CONFIG\_NVS\_ENCRYPTION}) and
|
||||||
using the reserved \texttt{nvs\_keys} partition (see
|
flash encryption in development mode (\texttt{sdkconfig.defaults}). Keys live in
|
||||||
\texttt{partitions.csv} and \texttt{sdkconfig.defaults} comments) per
|
the \texttt{nvs\_keys} partition; Wi-Fi passwords remain wrapped in
|
||||||
current ESP-IDF security guidance.
|
\texttt{core::Secret} in RAM and are never logged. First upgrade from plain NVS
|
||||||
|
requires \texttt{idf.py erase-flash}. HIL checklist:
|
||||||
|
\texttt{Software/docs/security-flash-nvs.md}.
|
||||||
\end{drcaution}
|
\end{drcaution}
|
||||||
|
|||||||
@@ -99,8 +99,9 @@ RAII STA join helper with an explicit connect timeout. Assumes
|
|||||||
|
|
||||||
\section{NvsSecureStore}\label{cls:NvsSecureStore}
|
\section{NvsSecureStore}\label{cls:NvsSecureStore}
|
||||||
\texttt{ISecureStore} implementation backed by an NVS namespace. Passwords
|
\texttt{ISecureStore} implementation backed by an NVS namespace. Passwords
|
||||||
are stored as NVS strings and never logged. Production should enable NVS
|
are stored as NVS strings and never logged. NVS encryption and flash
|
||||||
encryption using the reserved \texttt{nvs\_keys} partition.
|
encryption are enabled in \texttt{sdkconfig.defaults}; initialisation runs in
|
||||||
|
\texttt{secure\_store::initEncryptedStorage()} before network bring-up.
|
||||||
|
|
||||||
% ------------------------------------------------------------------
|
% ------------------------------------------------------------------
|
||||||
% Hardware drivers (Slice 3)
|
% Hardware drivers (Slice 3)
|
||||||
|
|||||||
@@ -180,8 +180,9 @@ Chapter~\ref{ch:api}.
|
|||||||
Sensitive data --- Wi-Fi credentials today; user credentials and the
|
Sensitive data --- Wi-Fi credentials today; user credentials and the
|
||||||
station/frequency list in later slices --- uses \texttt{core::Secret} so
|
station/frequency list in later slices --- uses \texttt{core::Secret} so
|
||||||
values cannot be logged or implicitly converted to a string; buffers are
|
values cannot be logged or implicitly converted to a string; buffers are
|
||||||
cleared on destruction. Production builds should enable NVS encryption
|
cleared on destruction. Firmware~0.8.3+ enables NVS and flash encryption at
|
||||||
at rest (see Chapter~\ref{sec:api-storage}).
|
rest via \texttt{secure\_store::initEncryptedStorage()} (see
|
||||||
|
\texttt{docs/security-flash-nvs.md} and Chapter~\ref{sec:api-storage}).
|
||||||
|
|
||||||
\section{Companion-chip boot at power-up}
|
\section{Companion-chip boot at power-up}
|
||||||
\label{sec:fw-chip-boot}
|
\label{sec:fw-chip-boot}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# Flash and NVS encryption (T8)
|
||||||
|
|
||||||
|
DigiRadio stores Wi-Fi credentials, preset lists, audio profiles, and the last
|
||||||
|
preset index in the `digiradio` NVS namespace. Firmware **0.8.3+** enables:
|
||||||
|
|
||||||
|
| Layer | Kconfig | Effect |
|
||||||
|
|-------|---------|--------|
|
||||||
|
| Flash encryption | `CONFIG_SECURE_FLASH_ENC_ENABLED` | On-chip transparent flash ciphertext |
|
||||||
|
| Mode (default) | `CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT` | Plaintext download still allowed for bring-up |
|
||||||
|
| NVS encryption | `CONFIG_NVS_ENCRYPTION` | XTS-AES over NVS entries; keys in `nvs_keys` partition |
|
||||||
|
| Partition table | `partitions.csv` | `nvs` @ 0x9000, `nvs_keys` @ 0xf000 |
|
||||||
|
|
||||||
|
Implementation: `secure_store::initEncryptedStorage()` (called from
|
||||||
|
`NetBootstrap` before any `NvsSecureStore` access). With `CONFIG_NVS_ENCRYPTION`,
|
||||||
|
ESP-IDF `nvs_flash_init()` loads or generates keys in the first `nvs_keys`
|
||||||
|
partition automatically (ESP-IDF v5.5 NVS Encryption guide).
|
||||||
|
|
||||||
|
**No encryption keys are stored in this repository.**
|
||||||
|
|
||||||
|
## First flash (virgin ESP32-S3)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd Software
|
||||||
|
idf.py set-target esp32s3
|
||||||
|
idf.py erase-flash flash monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
Provision Wi-Fi via SoftAP UI, save a preset, reboot — credentials should
|
||||||
|
survive `STA` reconnect.
|
||||||
|
|
||||||
|
## Migrating from plain NVS (pre-0.8.3 dev boards)
|
||||||
|
|
||||||
|
Encryption changes the on-flash layout. **Erase once** before using encrypted
|
||||||
|
firmware:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
idf.py erase-flash flash
|
||||||
|
```
|
||||||
|
|
||||||
|
Users must re-provision Wi-Fi and presets after erase.
|
||||||
|
|
||||||
|
## Production release mode
|
||||||
|
|
||||||
|
After HIL sign-off, build with the production overlay
|
||||||
|
(`sdkconfig.defaults.production`) so flash encryption uses **RELEASE** mode.
|
||||||
|
This limits future plaintext downloads — follow Espressif’s flash encryption
|
||||||
|
checklist for ESP32-S3 before shipping units.
|
||||||
|
|
||||||
|
## HIL checklist (requires hardware — pending)
|
||||||
|
|
||||||
|
- [ ] Boot log shows `NvsPlatformInit: NVS encryption enabled` and flash encryption enabled
|
||||||
|
- [ ] `POST /api/wifi` → reboot → STA connects without re-provisioning
|
||||||
|
- [ ] Preset save/recall and `last_preset` survive power cycle
|
||||||
|
- [ ] Audio profile round-trip after reboot
|
||||||
|
- [ ] `espefuse.py summary` shows expected `SPI_BOOT_CRYPT_CNT` after first encrypted boot
|
||||||
|
- [ ] Optional: UART hex dump of NVS region shows non-plaintext SSID (do not log secrets in CI)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- ESP-IDF v5.5 — [NVS Encryption (ESP32-S3)](https://docs.espressif.com/projects/esp-idf/en/v5.5.3/esp32s3/api-reference/storage/nvs_encryption.html)
|
||||||
|
- ESP-IDF v5.5 — [Flash Encryption (ESP32-S3)](https://docs.espressif.com/projects/esp-idf/en/v5.5.3/esp32s3/security/flash-encryption.html)
|
||||||
|
- `Software/partitions.csv`, `Software/sdkconfig.defaults`
|
||||||
@@ -62,8 +62,8 @@ so every later slice drops into a working frame.
|
|||||||
|
|
||||||
Build:
|
Build:
|
||||||
- Top-level ESP-IDF project targeting `esp32s3`.
|
- Top-level ESP-IDF project targeting `esp32s3`.
|
||||||
- `sdkconfig.defaults` sets C++23, exceptions off, and documents flash/NVS
|
- `sdkconfig.defaults` sets C++23, exceptions off, NVS + flash encryption
|
||||||
encryption options (not hard-enabled until production).
|
(development mode). Production overlay: `sdkconfig.defaults.production`.
|
||||||
- The `components/core` component compiles both under ESP-IDF and
|
- The `components/core` component compiles both under ESP-IDF and
|
||||||
standalone on the host.
|
standalone on the host.
|
||||||
|
|
||||||
@@ -97,7 +97,8 @@ Acceptance criteria:
|
|||||||
- [x] Doxygen green; manual sync green; `ch-api.tex` documents endpoints.
|
- [x] Doxygen green; manual sync green; `ch-api.tex` documents endpoints.
|
||||||
- [x] No ESP-IDF headers in `components/core`.
|
- [x] No ESP-IDF headers in `components/core`.
|
||||||
|
|
||||||
Out of scope: station list, user credentials, NVS encryption enablement
|
Out of scope for Slice 2: station list (later slices). NVS encryption landed
|
||||||
|
in fw 0.8.3 — see `docs/security-flash-nvs.md`.
|
||||||
(production), chip drivers.
|
(production), chip drivers.
|
||||||
|
|
||||||
## Slice 3 — Companion-chip boot (complete)
|
## Slice 3 — Companion-chip boot (complete)
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
# DigiRadio — default Kconfig
|
# DigiRadio — default Kconfig (development / first-board bring-up)
|
||||||
# NVS encryption: enable CONFIG_NVS_ENCRYPTION and flash encryption for
|
# Security: NVS + flash encryption — see docs/security-flash-nvs.md
|
||||||
# production; nvs_keys partition is reserved in partitions.csv.
|
# Production release mode: sdkconfig.defaults.production (overlay at build time)
|
||||||
|
|
||||||
CONFIG_IDF_TARGET="esp32s3"
|
CONFIG_IDF_TARGET="esp32s3"
|
||||||
|
|
||||||
|
# Custom partition table (nvs + nvs_keys for encrypted storage)
|
||||||
|
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||||
|
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||||
|
|
||||||
|
# Flash encryption — DEVELOPMENT mode (re-flash plaintext until eFuse policy set)
|
||||||
|
CONFIG_SECURE_FLASH_ENC_ENABLED=y
|
||||||
|
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=y
|
||||||
|
|
||||||
|
# NVS encryption at rest (XTS-AES; keys in nvs_keys, protected by flash encryption)
|
||||||
|
CONFIG_NVS_ENCRYPTION=y
|
||||||
|
|
||||||
# C++23, exceptions and RTTI off (AGENTS.md §2)
|
# C++23, exceptions and RTTI off (AGENTS.md §2)
|
||||||
CONFIG_COMPILER_CXX_EXCEPTIONS=n
|
CONFIG_COMPILER_CXX_EXCEPTIONS=n
|
||||||
CONFIG_COMPILER_CXX_RTTI=n
|
CONFIG_COMPILER_CXX_RTTI=n
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# DigiRadio — production security overlay (apply on top of sdkconfig.defaults)
|
||||||
|
#
|
||||||
|
# Build example:
|
||||||
|
# cp sdkconfig.defaults.production sdkconfig.defaults.release
|
||||||
|
# idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.release" build
|
||||||
|
#
|
||||||
|
# WARNING: RELEASE flash encryption is irreversible on the chip. Test on a
|
||||||
|
# dedicated unit after HIL validation in development mode (T8 checklist).
|
||||||
|
|
||||||
|
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_RELEASE=y
|
||||||
|
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=n
|
||||||
Reference in New Issue
Block a user