diff --git a/docs/audits/PROTOCOL_ADAPTER_DRIFT_AUDIT.md b/docs/audits/PROTOCOL_ADAPTER_DRIFT_AUDIT.md index ad95a9a1..0acde975 100644 --- a/docs/audits/PROTOCOL_ADAPTER_DRIFT_AUDIT.md +++ b/docs/audits/PROTOCOL_ADAPTER_DRIFT_AUDIT.md @@ -304,13 +304,15 @@ Current state: - Confirmed platform gap, now documented in fine-grained capabilities. - ESP32 direct text/app-data sends now use `MeshCoreDirectRoutePolicy` for the shared decision table: missing peer pubkey -> discover/fail, selected route -> direct route path, no selected route -> flood, - and preferred-channel secret derivation may fall back to the requested channel. + and route/channel fallback remains explicit without redefining direct-secret material. - Incoming MeshCore discover request/response decisions now run through `MeshCoreRuntime`: request filter/since matching emits `SendDiscoverResponseEffect`, and responses emit `PublishNodeInfoEffect` plus `UpdatePeerRouteEffect`. - MeshCore direct shared-secret expansion and nRF peer-key derivation now use `MeshCoreDirectSecretCore`; ESP32 keeps identity storage and peer pubkey lookup in the adapter, then delegates key expansion to the shared helper. +- ESP32 no longer accepts the historical group-secret-derived direct key fallback; MeshCore direct secrets are + identity/pubkey-derived only. Residual risk: diff --git a/docs/specification/PROTOCOL_RUNTIME_DESIGN_SPEC.md b/docs/specification/PROTOCOL_RUNTIME_DESIGN_SPEC.md index 5dca7f98..d624b8b7 100644 --- a/docs/specification/PROTOCOL_RUNTIME_DESIGN_SPEC.md +++ b/docs/specification/PROTOCOL_RUNTIME_DESIGN_SPEC.md @@ -369,13 +369,15 @@ Current C++17 migration state: - Direct-route send decision for ESP32 MeshCore direct text/app-data now uses shared `MeshCoreDirectRoutePolicy`: missing peer pubkey triggers discover/failure, selected routes use direct path, - and direct secret derivation may fall back from preferred route channel to requested channel. + and route/channel fallback remains explicit without redefining direct-secret material. - Incoming MeshCore discover request/response control payloads now flow through `MeshCoreRuntime`: filter/since matching emits `SendDiscoverResponseEffect`, and discover responses emit `PublishNodeInfoEffect` plus `UpdatePeerRouteEffect`. - MeshCore identity shared-secret expansion and nRF peer-key derivation now use shared `MeshCoreDirectSecretCore`; ESP32 still owns private-key storage and route pubkey lookup before delegating key expansion to the runtime helper. +- ESP32 no longer falls back to the historical group-secret-derived direct key; MeshCore direct secrets are + identity/pubkey-derived only. - Peer route storage, pubkey persistence, private identity storage, response scheduling, and frame transmission are still platform adapter responsibilities until they can be represented as runtime state/effects. diff --git a/modules/core_chat/include/chat/runtime/meshcore_direct_route_policy.h b/modules/core_chat/include/chat/runtime/meshcore_direct_route_policy.h index 31dadc37..2a94b425 100644 --- a/modules/core_chat/include/chat/runtime/meshcore_direct_route_policy.h +++ b/modules/core_chat/include/chat/runtime/meshcore_direct_route_policy.h @@ -34,9 +34,6 @@ struct MeshCoreDirectRouteDecision MeshCoreDirectRouteStatus status = MeshCoreDirectRouteStatus::Ready; MeshCoreRouteMode route_mode = MeshCoreRouteMode::Flood; ChannelId tx_channel = ChannelId::PRIMARY; - ChannelId primary_secret_channel = ChannelId::PRIMARY; - ChannelId fallback_secret_channel = ChannelId::PRIMARY; - bool allow_secret_fallback = false; bool should_discover = false; }; @@ -45,8 +42,6 @@ inline MeshCoreDirectRouteDecision resolveMeshCoreDirectRoutePolicy( { MeshCoreDirectRouteDecision decision{}; decision.tx_channel = facts.requested_channel; - decision.primary_secret_channel = facts.requested_channel; - decision.fallback_secret_channel = facts.requested_channel; if (facts.identity_ready && (!facts.has_peer_route || !facts.peer_has_public_key)) { @@ -59,9 +54,6 @@ inline MeshCoreDirectRouteDecision resolveMeshCoreDirectRoutePolicy( { decision.route_mode = MeshCoreRouteMode::Direct; decision.tx_channel = facts.selected_route_channel; - decision.primary_secret_channel = facts.selected_route_channel; - decision.fallback_secret_channel = facts.requested_channel; - decision.allow_secret_fallback = facts.selected_route_channel != facts.requested_channel; } return decision; diff --git a/modules/core_chat/tests/test_meshcore_direct_route_policy.cpp b/modules/core_chat/tests/test_meshcore_direct_route_policy.cpp index 69907b90..1a83e685 100644 --- a/modules/core_chat/tests/test_meshcore_direct_route_policy.cpp +++ b/modules/core_chat/tests/test_meshcore_direct_route_policy.cpp @@ -26,8 +26,6 @@ int main() assert(decision.status == chat::runtime::MeshCoreDirectRouteStatus::Ready); assert(decision.route_mode == chat::runtime::MeshCoreRouteMode::Flood); assert(decision.tx_channel == chat::ChannelId::SECONDARY); - assert(decision.primary_secret_channel == chat::ChannelId::SECONDARY); - assert(!decision.allow_secret_fallback); } { @@ -43,9 +41,6 @@ int main() assert(decision.status == chat::runtime::MeshCoreDirectRouteStatus::Ready); assert(decision.route_mode == chat::runtime::MeshCoreRouteMode::Direct); assert(decision.tx_channel == chat::ChannelId::SECONDARY); - assert(decision.primary_secret_channel == chat::ChannelId::SECONDARY); - assert(decision.fallback_secret_channel == chat::ChannelId::PRIMARY); - assert(decision.allow_secret_fallback); } return 0; diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshcore/meshcore_adapter.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshcore/meshcore_adapter.h index 8bb8aa2d..6c0bf6f9 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshcore/meshcore_adapter.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshcore/meshcore_adapter.h @@ -339,8 +339,6 @@ class MeshCoreAdapter : public IMeshAdapter, public IMeshCoreBleBackend bool resolveGroupSecret(ChannelId channel, uint8_t out_key16[16], uint8_t out_key32[32], uint8_t* out_hash) const; ChannelId resolveChannelFromHash(uint8_t channel_hash, bool* out_match) const; - bool deriveLegacyDirectSecret(ChannelId channel, uint8_t peer_hash, - uint8_t out_key16[16], uint8_t out_key32[32]) const; bool deriveIdentitySecret(uint8_t peer_hash, uint8_t out_key16[16], uint8_t out_key32[32]) const; bool deriveDirectSecret(ChannelId channel, uint8_t peer_hash, diff --git a/platform/esp/arduino_common/src/chat/infra/meshcore/meshcore_adapter.cpp b/platform/esp/arduino_common/src/chat/infra/meshcore/meshcore_adapter.cpp index 817197ed..03f27f75 100644 --- a/platform/esp/arduino_common/src/chat/infra/meshcore/meshcore_adapter.cpp +++ b/platform/esp/arduino_common/src/chat/infra/meshcore/meshcore_adapter.cpp @@ -164,7 +164,6 @@ using chat::meshcore::ParsedPacket; using chat::meshcore::parsePacket; using chat::meshcore::saturatingAddU32; using chat::meshcore::scoreFromSnr; -using chat::meshcore::sha256Trunc; using chat::meshcore::toHex; using chat::meshcore::toHmacKey32; @@ -1481,39 +1480,11 @@ bool MeshCoreAdapter::deriveIdentitySecret(uint8_t peer_hash, out_key32, 32); } -bool MeshCoreAdapter::deriveLegacyDirectSecret(ChannelId channel, uint8_t peer_hash, - uint8_t out_key16[16], uint8_t out_key32[32]) const -{ - if (!out_key16 || !out_key32) - { - return false; - } - - uint8_t base_key16[16]; - uint8_t base_key32[32]; - if (!resolveGroupSecret(channel, base_key16, base_key32, nullptr)) - { - return false; - } - - uint8_t material[18]; - memcpy(material, base_key16, sizeof(base_key16)); - material[16] = (peer_hash < self_hash_) ? peer_hash : self_hash_; - material[17] = (peer_hash < self_hash_) ? self_hash_ : peer_hash; - - sha256Trunc(out_key32, 32, material, sizeof(material)); - memcpy(out_key16, out_key32, 16); - return true; -} - bool MeshCoreAdapter::deriveDirectSecret(ChannelId channel, uint8_t peer_hash, uint8_t out_key16[16], uint8_t out_key32[32]) const { - if (deriveIdentitySecret(peer_hash, out_key16, out_key32)) - { - return true; - } - return deriveLegacyDirectSecret(channel, peer_hash, out_key16, out_key32); + (void)channel; + return deriveIdentitySecret(peer_hash, out_key16, out_key32); } bool MeshCoreAdapter::tryDecryptPeerPayload(uint8_t src_hash, @@ -1582,24 +1553,6 @@ bool MeshCoreAdapter::tryDecryptPeerPayload(uint8_t src_hash, for (size_t i = 0; i < order_len; ++i) { - { - uint8_t key16[16] = {}; - uint8_t key32[32] = {}; - if (deriveIdentitySecret(src_hash, key16, key32) && - tryCandidate(order[i], key16, key32)) - { - return true; - } - } - { - uint8_t key16[16] = {}; - uint8_t key32[32] = {}; - if (deriveLegacyDirectSecret(order[i], src_hash, key16, key32) && - tryCandidate(order[i], key16, key32)) - { - return true; - } - } { uint8_t key16[16] = {}; uint8_t key32[32] = {}; @@ -3169,13 +3122,9 @@ MeshActionResult MeshCoreAdapter::sendDirectTextDetailed(ChannelId channel, cons uint8_t peer_key16[16]; uint8_t peer_key32[32]; - if (!deriveDirectSecret(route_decision.primary_secret_channel, peer_hash, peer_key16, peer_key32)) + if (!deriveDirectSecret(route_decision.tx_channel, peer_hash, peer_key16, peer_key32)) { - if (!route_decision.allow_secret_fallback || - !deriveDirectSecret(route_decision.fallback_secret_channel, peer_hash, peer_key16, peer_key32)) - { - return MeshActionResult::fail(MeshOperationFailure::PeerKeyMissing); - } + return MeshActionResult::fail(MeshOperationFailure::PeerKeyMissing); } constexpr size_t kDirectPlainPrefixSize = 5; // ts(4) + flags(1) @@ -3352,16 +3301,12 @@ bool MeshCoreAdapter::sendAppData(ChannelId channel, uint32_t portnum, uint8_t peer_key16[16]; uint8_t peer_key32[32]; - if (!deriveDirectSecret(route_decision.primary_secret_channel, peer_hash, peer_key16, peer_key32)) + if (!deriveDirectSecret(route_decision.tx_channel, peer_hash, peer_key16, peer_key32)) { - if (!route_decision.allow_secret_fallback || - !deriveDirectSecret(route_decision.fallback_secret_channel, peer_hash, peer_key16, peer_key32)) - { - MESHCORE_LOG("[MESHCORE] TX direct app-data dropped (no peer secret) peer=%08lX port=%u\n", - static_cast(dest), - static_cast(portnum)); - return false; - } + MESHCORE_LOG("[MESHCORE] TX direct app-data dropped (no peer secret) peer=%08lX port=%u\n", + static_cast(dest), + static_cast(portnum)); + return false; } (void)peer_key16;