From bd71ae629d26584d96a80e32a2cfc699bd60cfa1 Mon Sep 17 00:00:00 2001 From: Simon J Stuart Date: Thu, 20 Aug 2026 20:18:41 +0200 Subject: [PATCH 1/7] fix: declare RTTI requirement for Observable-backed Security builds --- library.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library.json b/library.json index 8f65b7d..6eb1ead 100644 --- a/library.json +++ b/library.json @@ -8,7 +8,10 @@ "license": "Apache-2.0", "frameworks": "*", "platforms": "*", - "build": {"flags": ["-std=gnu++17", "-DESPRESSIO_SECURITY"]}, + "build": { + "flags": ["-std=gnu++17", "-frtti", "-DESPRESSIO_SECURITY"], + "unflags": ["-fno-rtti"] + }, "dependencies": [ { "name": "Flowduino ESPressio-Observable", From 9c109c12fa9e066ca2310080f802c776be42f0dc Mon Sep 17 00:00:00 2001 From: Simon J Stuart Date: Thu, 20 Aug 2026 20:18:50 +0200 Subject: [PATCH 2/7] fix: enable RTTI for ESP-IDF Observable dispatch --- component.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component.mk b/component.mk index b2452c9..b53eaa0 100644 --- a/component.mk +++ b/component.mk @@ -1,6 +1,6 @@ COMPONENT_ADD_INCLUDEDIRS := src COMPONENT_SRCDIRS := -CXXFLAGS += -std=gnu++17 +CXXFLAGS += -std=gnu++17 -frtti ESPRESSIO_SECURITY_VERSION_MAJOR := 0 ESPRESSIO_SECURITY_VERSION_MINOR := 2 From 344e236bfacad9d82845baf495e6c405b2fe349f Mon Sep 17 00:00:00 2001 From: Simon J Stuart Date: Thu, 20 Aug 2026 20:19:01 +0200 Subject: [PATCH 3/7] test: validate Security package-level RTTI configuration --- .github/workflows/host-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/host-tests.yml b/.github/workflows/host-tests.yml index 9004f21..fe2c364 100644 --- a/.github/workflows/host-tests.yml +++ b/.github/workflows/host-tests.yml @@ -38,7 +38,7 @@ jobs: --lib . --lib deps/ESPressio-Observable --project-option="build_flags=-std=gnu++17" - --project-option="build_unflags=-std=gnu++11 -fno-rtti" + --project-option="build_unflags=-std=gnu++11" - name: Compile MbedTLSAlgorithms run: >- pio ci examples/MbedTLSAlgorithms/MbedTLSAlgorithms.ino @@ -46,4 +46,4 @@ jobs: --lib . --lib deps/ESPressio-Observable --project-option="build_flags=-std=gnu++17" - --project-option="build_unflags=-std=gnu++11 -fno-rtti" + --project-option="build_unflags=-std=gnu++11" From b1c7ade9a93c8d29171f5409c98a0c37f16058e5 Mon Sep 17 00:00:00 2001 From: Simon J Stuart Date: Thu, 20 Aug 2026 20:20:24 +0200 Subject: [PATCH 4/7] ci: validate Security RTTI through real PlatformIO project --- .github/workflows/host-tests.yml | 39 ++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/.github/workflows/host-tests.yml b/.github/workflows/host-tests.yml index fe2c364..dff8ec0 100644 --- a/.github/workflows/host-tests.yml +++ b/.github/workflows/host-tests.yml @@ -31,19 +31,30 @@ jobs: path: deps/ESPressio-Observable - name: Install PlatformIO run: pip install platformio + - name: Create PlatformIO consumer project + shell: bash + run: | + mkdir -p ci-project/src ci-project/lib + cp -R . ci-project/lib/ESPressio-Security + cp -R deps/ESPressio-Observable ci-project/lib/ESPressio-Observable + cat > ci-project/platformio.ini <<'EOF' + [env:esp32dev] + platform = espressif32 + board = esp32dev + framework = arduino + build_unflags = -std=gnu++11 + lib_deps = + ESPressio-Security + ESPressio-Observable + EOF - name: Compile BasicSecurePayload - run: >- - pio ci examples/BasicSecurePayload/BasicSecurePayload.ino - --board esp32dev - --lib . - --lib deps/ESPressio-Observable - --project-option="build_flags=-std=gnu++17" - --project-option="build_unflags=-std=gnu++11" + shell: bash + run: | + cp examples/BasicSecurePayload/BasicSecurePayload.ino ci-project/src/main.cpp + pio run -d ci-project - name: Compile MbedTLSAlgorithms - run: >- - pio ci examples/MbedTLSAlgorithms/MbedTLSAlgorithms.ino - --board esp32dev - --lib . - --lib deps/ESPressio-Observable - --project-option="build_flags=-std=gnu++17" - --project-option="build_unflags=-std=gnu++11" + shell: bash + run: | + rm -rf ci-project/.pio + cp examples/MbedTLSAlgorithms/MbedTLSAlgorithms.ino ci-project/src/main.cpp + pio run -d ci-project From 1f7e8d8c6e29b38355c245971d9cb2aa18443cd8 Mon Sep 17 00:00:00 2001 From: Simon J Stuart Date: Thu, 20 Aug 2026 20:21:20 +0200 Subject: [PATCH 5/7] ci: build consumer project outside repository tree --- .github/workflows/host-tests.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/host-tests.yml b/.github/workflows/host-tests.yml index dff8ec0..0b13f78 100644 --- a/.github/workflows/host-tests.yml +++ b/.github/workflows/host-tests.yml @@ -34,10 +34,10 @@ jobs: - name: Create PlatformIO consumer project shell: bash run: | - mkdir -p ci-project/src ci-project/lib - cp -R . ci-project/lib/ESPressio-Security - cp -R deps/ESPressio-Observable ci-project/lib/ESPressio-Observable - cat > ci-project/platformio.ini <<'EOF' + mkdir -p "$RUNNER_TEMP/espressio-security-ci/src" "$RUNNER_TEMP/espressio-security-ci/lib" + rsync -a --exclude='.git' --exclude='deps' --exclude='build' ./ "$RUNNER_TEMP/espressio-security-ci/lib/ESPressio-Security/" + cp -R deps/ESPressio-Observable "$RUNNER_TEMP/espressio-security-ci/lib/ESPressio-Observable" + cat > "$RUNNER_TEMP/espressio-security-ci/platformio.ini" <<'EOF' [env:esp32dev] platform = espressif32 board = esp32dev @@ -50,11 +50,11 @@ jobs: - name: Compile BasicSecurePayload shell: bash run: | - cp examples/BasicSecurePayload/BasicSecurePayload.ino ci-project/src/main.cpp - pio run -d ci-project + cp examples/BasicSecurePayload/BasicSecurePayload.ino "$RUNNER_TEMP/espressio-security-ci/src/main.cpp" + pio run -d "$RUNNER_TEMP/espressio-security-ci" - name: Compile MbedTLSAlgorithms shell: bash run: | - rm -rf ci-project/.pio - cp examples/MbedTLSAlgorithms/MbedTLSAlgorithms.ino ci-project/src/main.cpp - pio run -d ci-project + rm -rf "$RUNNER_TEMP/espressio-security-ci/.pio" + cp examples/MbedTLSAlgorithms/MbedTLSAlgorithms.ino "$RUNNER_TEMP/espressio-security-ci/src/main.cpp" + pio run -d "$RUNNER_TEMP/espressio-security-ci" From 543b375156286bffe1ec5b6b8b7e8cddc303ebd5 Mon Sep 17 00:00:00 2001 From: Simon J Stuart Date: Thu, 20 Aug 2026 20:23:30 +0200 Subject: [PATCH 6/7] ci: validate documented project-level RTTI configuration --- .github/workflows/host-tests.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/host-tests.yml b/.github/workflows/host-tests.yml index 0b13f78..2c4e81d 100644 --- a/.github/workflows/host-tests.yml +++ b/.github/workflows/host-tests.yml @@ -42,7 +42,12 @@ jobs: platform = espressif32 board = esp32dev framework = arduino - build_unflags = -std=gnu++11 + build_flags = + -std=gnu++17 + -frtti + build_unflags = + -std=gnu++11 + -fno-rtti lib_deps = ESPressio-Security ESPressio-Observable From 43a5f13bbf33c4498f7a18a3f2f99959e61369a4 Mon Sep 17 00:00:00 2001 From: Simon J Stuart Date: Thu, 20 Aug 2026 20:24:07 +0200 Subject: [PATCH 7/7] docs: require RTTI for Observable-backed ESP32 builds --- README.md | 174 ++++++++++++++++++++++++++---------------------------- 1 file changed, 85 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 6ebe45a..4e126a0 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,18 @@ For 0.2.0, ESPressio Security adds a required dependency on **ESPressio Observab ESPressio Event remains **optional**. ESPressio Event 5.8.0 adds `TransportSecurityEventBridge`, which binds to a specific `TransportSecurity` instance and converts those observations into asynchronous Events without making Event a Security dependency. Key material is never exposed through the observer or Event surfaces. +On ESP32/Arduino, Observable's typed observer dispatch requires RTTI. PlatformIO projects consuming Security 0.2.0 must therefore enable RTTI at the **project level** because Arduino's default `-fno-rtti` applies to the application translation unit and cannot be reliably removed by a dependency's `library.json` alone: + +```ini +build_flags = + -std=gnu++17 + -frtti + +build_unflags = + -std=gnu++11 + -fno-rtti +``` + Development-branch PlatformIO dependencies are therefore: ```ini @@ -108,7 +120,21 @@ build_unflags = -std=gnu++11 ``` -For the 0.2.0 release generation, add ESPressio Observable 3.x as shown in the development update above. +For 0.2.0 on ESP32/Arduino, add ESPressio Observable 3.x and enable RTTI at project level: + +```ini +lib_deps = + flowduino/ESPressio-Security@^0.2.0 + flowduino/ESPressio-Observable@^3.0.1 + +build_flags = + -std=gnu++17 + -frtti + +build_unflags = + -std=gnu++11 + -fno-rtti +``` To deliberately consume the current repository instead of a release: @@ -230,138 +256,108 @@ Each authenticated envelope contains a non-zero 64-bit session ID and sequence n sender ID + key ID + session ID ``` -A sliding window permits limited legitimate reordering while rejecting duplicates and stale packets within that session. Replay state is committed **only after successful AEAD authentication and protocol validation**, preventing unauthenticated forged high sequence numbers from advancing receiver state. - -This solves the sender-reboot case cleanly: +A sliding window permits limited legitimate reordering while rejecting duplicates and stale packets within that session. Replay state is committed **only after successful AEAD authentication and protocol validation**, so forged packets cannot poison replay state. -```text -boot A: sender X / session A / sequence 1, 2, 3 ... -boot B: sender X / session B / sequence 1, 2, 3 ... -``` +This permits a sender to reboot, generate a new authenticated session ID, reset its sequence to `1`, and remain acceptable to receivers that have already seen much larger sequence numbers from its previous session. -The restarted sequence is accepted because session B is a distinct authenticated replay domain; replaying either session's already-seen packets is still rejected. +`ResetReplayProtection()` remains available for explicit administrative/session-boundary use. ## Observable Security Lifecycle (0.2.0) -`TransportSecurity` now accepts `ITransportSecurityObserver` registrations: +`TransportSecurity` can be observed directly: ```cpp +#include + class SecurityObserver final : public ESPressio::Security::ITransportSecurityObserver { public: void OnTransportSecuritySessionEstablished(uint64_t sessionID) override { - // Session lifecycle observation. + // Record or display the authenticated session identity. } void OnTransportSecurityFailure( const ESPressio::Security::SecurityResult& result ) override { - // Diagnostics / metrics / audit handling. + // Diagnostics/metrics only; ordinary return-value handling remains authoritative. } }; SecurityObserver observer; -auto observerHandle = security.RegisterObserver(&observer); +auto handle = security.RegisterObserver(&observer); ``` -The observer surface supplements rather than replaces `SecurityResult`. Observer exceptions are isolated from Security processing so a diagnostics consumer cannot interrupt a cryptographic state transition. - -When ESPressio Event 5.8.0 is selected, `ESPressio_TransportSecurityEventBridge.hpp` converts the same observations into asynchronous Event instances without changing Security's dependency direction. - -## Protecting a Payload - -```cpp -#include - -using namespace ESPressio::Security; - -AES256GCMCipher aes; -AeadCipherRegistry ciphers; -StaticKeyProvider keys; -ESP32RandomSource random; - -ciphers.Register(aes); -uint8_t key[32] = { /* securely provisioned bytes */ }; -keys.Add(1, AeadAlgorithm::AES256GCM, key, sizeof(key)); +Available observations are: -TransportSecurityConfig config; -config.Policy = TransportSecurityPolicy::Required; -config.OutboundAlgorithm = AeadAlgorithm::AES256GCM; -config.OutboundKeyID = 1; -config.SenderID = ESP.getEfuseMac(); -config.SessionID = 0; // automatically generate a fresh sender epoch +- configuration changed; +- security session reset; +- security session established; +- replay protection reset; +- security failure. -TransportSecurity security(ciphers, keys, random, config); -std::vector protectedBytes; -auto result = security.Protect(42, payload, payloadLength, protectedBytes); -``` +The observation layer is intentionally passive. It does not replace the ordinary `Protect()` / `Unprotect()` result model and it never exposes key material. -`42` is the application/transport protocol identifier cryptographically bound to the payload. +## Optional ESPressio Event Bridge (0.2.0) -## Receiving a Protected Payload +ESPressio Event **5.8.0+** provides the opt-in bridge: ```cpp -UnprotectedPayload opened; -auto result = security.Unprotect(42, receivedBytes, receivedSize, opened); - -if (!result.Success) { - // Drop it. It must not reach protocol/application processing. - return; -} +#include -// opened.Data has passed authentication, decryption, -// protocol binding and replay checks. +ESPressio::Event::TransportSecurityEventBridge bridge(security); ``` -Authenticated sender, key, session and sequence metadata is available without exposing the secret key. +This converts Security observations to asynchronous ESPressio Events while keeping the dependency direction correct: Security depends only on Observable; Event optionally adapts Security. -## Generic Secure Transport Decorator +## Generic Transport Decoration -Concrete transports can implement the intentionally small `ITransportSecurityCarrier` interface and then be decorated: +`SecureTransportDecorator` can wrap any `ITransportSecurityCarrier`: ```cpp -SecureTransportDecorator secureCarrier(carrier, security); +SecureTransportDecorator secure(carrier, security); +secure.Send(protocolID, payload); ``` -The decorator calls its application receiver only with data accepted by `TransportSecurity`. This is the intended integration point for ESPressio ESP-Now, ESPressio Sockets and future transports. - -See [TRANSPORT_SECURITY.md](TRANSPORT_SECURITY.md) for the wire format and downstream-integration details. +Inbound carrier packets are authenticated/decrypted before the registered receive callback is invoked. Authentication failure, replay rejection or protocol mismatch therefore prevents plaintext delivery. -## Examples +Concrete transport libraries may alternatively integrate `TransportSecurity` directly where their callback/threading model makes that more natural. -The repository includes: +## Concurrency Model -- `examples/BasicSecurePayload` — AES-256-GCM registration, key provisioning, automatic session generation, ESP32 nonce generation, protect/open flow. -- `examples/MbedTLSAlgorithms` — compile-time discovery and registration of available mbedTLS-backed AEAD implementations. +The initial `TransportSecurity` implementation protects mutable outbound sequence/replay state and configuration transitions with a lightweight internal mutex when `` is available. -Example keys are demonstration-only. Do not copy hard-coded example key material into production firmware. +For embedded integrations, applications should nevertheless prefer a single well-defined security ownership/execution context per security instance where practical. This keeps transport callback behavior deterministic and avoids avoidable contention. ## Testing -Host-side CMake/CTest coverage uses a deterministic **test-only** AEAD implementation contained exclusively under `tests/`. - -Coverage includes protect/open round trips, authenticated metadata, ciphertext/tag/header/session tampering, protocol binding, replay rejection, in-window reordering, sender reboot/session rollover, explicit and automatically generated sessions, key rotation, Required/Preferred/Disabled policy behavior, malformed envelopes, payload limits, generic decorator flow, and 0.2.0 observable lifecycle behavior. - -A separate production-cipher contract target instantiates all included mbedTLS-backed cipher classes against API-compatible host stubs. GitHub Actions also compile the ESP32 examples so the actual Arduino-ESP32 mbedTLS API surface is validated in addition to host abstraction tests. - -## Security Considerations - -Cryptography is only one part of a secure system. Applications remain responsible for secure key provisioning, physical security, firmware trust, secure boot/flash encryption where appropriate, key rotation policy, sender identity assignment and protection of secrets outside this library. - -Do not log, serialize or expose key material. ESPressio Security APIs intentionally expose key IDs rather than keys in envelope metadata/results. - -`Preferred` permits plaintext and must not be used where plaintext acceptance is unacceptable. Transport authentication also does not automatically authorize *what* an authenticated device may do; Command authorization/policy remains a separate application concern. - -If an application explicitly supplies session IDs rather than allowing automatic generation, it must not reuse a session ID with a restarted sequence while receivers may still retain replay state for that same sender/key/session domain. - -## Future Direction - -Potential extensions include secure ESP32 NVS key providers, key derivation/provider integrations, signed identity/provisioning workflows, group/per-peer key management helpers, explicit key-expiry/rotation policy, bounded/persistent replay-state strategies, hardware-backed keys, and downstream secure adapters for ESPressio ESP-Now and ESPressio Sockets. - -## Contributing - -Issues and contributions are welcome through the ESPressio Security GitHub repository. Security-sensitive changes should include corresponding tests and should avoid bespoke cryptographic primitives where established, reviewed platform cryptography is available. +Host tests cover: + +- envelope encode/decode; +- AES-GCM protect/unprotect where available; +- protocol binding; +- replay rejection; +- sender-session scoped replay behavior; +- automatic outbound session generation; +- tamper rejection; +- key rotation; +- policy behavior; +- transport decoration; +- Observable lifecycle notifications and observer-handle lifetime; +- compile coverage for the mbedTLS wrapper interfaces. + +ESP32 CI additionally builds the production examples with the documented project-level RTTI configuration required by Observable's typed observer dispatch. + +## Operational Notes + +- Prefer `TransportSecurityPolicy::Required` where secure transport is actually required. +- Avoid hard-coding production keys into firmware source. +- Treat authenticated sender/session metadata as identity input, not as authorization policy by itself. +- Use a secure provisioning/storage strategy appropriate to the deployment. +- Keep production mbedTLS/Arduino-ESP32/ESP-IDF versions under update and vulnerability-management processes. +- Treat `Preferred` as a migration mode, not equivalent security to `Required`. +- Keep replay windows sized for the expected amount of legitimate packet reordering. +- Ensure ESP32/Arduino PlatformIO builds enable RTTI (`-frtti` and removal of `-fno-rtti`) when using Observable-backed Security 0.2.x. ## Changelog -See [CHANGELOG.md](CHANGELOG.md) for release history and notable changes. +See [CHANGELOG.md](CHANGELOG.md).