Skip to content

Authorize signed plaintext remote admin in licensed mode#10972

Draft
RCGV1 wants to merge 14 commits into
meshtastic:developfrom
RCGV1:codex/ham-signed-admin
Draft

Authorize signed plaintext remote admin in licensed mode#10972
RCGV1 wants to merge 14 commits into
meshtastic:developfrom
RCGV1:codex/ham-signed-admin

Conversation

@RCGV1

@RCGV1 RCGV1 commented Jul 10, 2026

Copy link
Copy Markdown
Member

Closes #10971

Design: meshtastic/design#123

Stack dependencies

This is a stacked draft that combines and depends on:

Review and merge the prerequisite drafts first. This PR intentionally targets develop because both prerequisite heads are contributor branches, so its current diff contains their changes as merge parents.

Summary

  • authorize licensed-mode remote Admin requests and mutations only when the packet is directed to this node, plaintext, Router-verified XEdDSA, and the exact verified signer key matches a populated security.admin_key[] entry;
  • carry the exact verified signer key as packet-local authenticated state, after clearing serialized authentication metadata at every untrusted ingress;
  • require signed directed licensed responses without applying the administrator allowlist in the reverse direction;
  • retain the existing Admin session challenge for mutations and add explicit initialized state so an all-zero passkey cannot authorize shortly after boot;
  • keep decoded MQTT Admin injection blocked while allowing binary ingress only through the ordinary Router verification gate;
  • reject licensed Admin origination with TOO_LARGE when the packet cannot carry its required signature;
  • preserve normal-mode PKI/admin-channel authorization and the existing XEdDSA from | id | portnum | payload tuple.

This does not introduce an authentication version, new signature format, certificate system, new protobuf field, or encryption in licensed mode.

Security properties

  • MeshPacket.public_key and xeddsa_signed are cleared at untrusted ingress and become authoritative only after successful Router verification for that packet.
  • AdminModule does not re-read NodeDB identity after verification and does not trust serialized key/signature flags.
  • Unknown, non-allowlisted, unsigned, malformed, invalid, oversized-unsigned, broadcast, wrong-destination, decoded-MQTT, and stale-session Admin traffic fails closed.
  • Reboot/new AdminModule construction invalidates the session challenge.
  • Signatures authenticate the key that emitted the payload; they do not validate a callsign/licence, provide confidentiality, or permanently prevent replay.

Validation

  • RAK3172 production build: success
    • RAM: 25,852 / 65,536 (39.4%)
    • flash: 202,360 / 247,808 (81.7%)
  • focused packet-signing, Admin, and MQTT test translation units compile with the exact protobufs#983 draft bindings;n- regression coverage re-verifies the exact signer after a simulated routing-auth cache handoff loss and rejects forged serialized signer metadata;
  • Trunk formatting/checks: clean;
  • git diff --check: clean;
  • independent dedicated security review approved exact commit 4cd335d03fd9498cc3c8fa12cfc51b5834d65e5a.

Local full native execution is host-blocked by existing macOS/Linux build incompatibilities: NotificationRenderer initialized VLAs, Linux input headers, and the coverage environment's GCC-only -fprofile-abs-path under Apple Clang. GitHub Linux CI is the executable validation source for this stacked draft.

Draft / hardware status

No RF hardware validation has been performed. Before this leaves draft:

  • verify two licensed nodes can exchange readable plaintext Admin getter, session, mutation, and reply packets over RF;
  • confirm signatures interoperate across representative devices and no PKI/PSK encryption is used;
  • verify reboot invalidates the Admin session and unauthorized/replayed mutations fail;
  • verify first-contact signed NodeInfo followed by Admin authorization and warm-key retention;
  • verify binary MQTT ingress traverses Router verification and decoded MQTT Admin remains blocked;
  • coordinate client changes so licensed remote Admin sends with pki_encrypted=false and uses verified-signature shield semantics rather than an encryption lock.

Operators remain responsible for their licence conditions, jurisdiction, permitted modes, and station-identification requirements.

Summary by CodeRabbit

  • New Features
    • Added routing-auth gating with an “opaque relay only” path and improved receive handling before routing state updates.
    • Introduced licensed-identity migration detection with client notifications, plus tighter licensed admin authorization and session management.
  • Bug Fixes
    • Prevented unsafe rebroadcast/ack behavior when packets can’t be safely reprocessed or are unauthorized.
    • Improved startup/restore licensed-channel sanitization and zero-initialized device metadata.
  • Tests
    • Expanded end-to-end coverage for routing, MQTT, signing policies, licensed admin, and identity migration.

🤝 Attestations

  • I have tested that my proposed changes behave as described within the local validation limits documented above.
  • I have tested that my proposed changes do not cause any obvious regressions on the following devices:
    • Heltec (Lora32) V3
    • LilyGo T-Deck
    • LilyGo T-Beam
    • RAK WisBlock 4631
    • Seeed Studio T-1000E tracker card
    • Other (RAK3172 build-only validation; RF hardware testing remains outstanding)

RCGV1 added 9 commits July 9, 2026 17:48
Preserve and publish identity keys in licensed mode so existing XEdDSA signatures can authenticate plaintext traffic. Sign licensed broadcasts and direct messages when they fit, while keeping PKI encryption disabled and normal routing behavior unchanged.
Stack the packet authenticity routing gate with licensed plaintext signing, then expose the exact verified signer key only during authenticated module dispatch. Licensed remote Admin requests now require directed signed plaintext and an allowlisted admin key, while responses remain signed plaintext and normal PKI administration is unchanged. Close the uninitialized zero-session window and add focused authorization/session regression coverage.
Remove the process-global signer context and carry only the exact Router-verified key on the authenticated packet. Clear serialized authentication metadata before every untrusted decode, require both the verified-signature marker and packet-local key in licensed Admin authorization, and expand authorization/session/isolation coverage while cleaning routing test ownership.
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b690a62-a2a9-4957-ba03-e027976eb748

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change centralizes XEdDSA authentication before routing state updates, supports opaque packet relay, adds licensed identity migration and channel sanitization handling, authorizes signed plaintext remote administration, and expands policy, persistence, and pipeline test coverage.

Changes

Licensed XEdDSA flow

Layer / File(s) Summary
Routing authentication and opaque relay
src/mesh/Router.*, src/mesh/*Router.*, src/mqtt/MQTT.cpp, src/mesh/udp/UdpMulticastHandler.h
Routing verdicts, decode states, authentication caching, XEdDSA policy enforcement, opaque relay handling, and ingress metadata normalization are updated across radio and MQTT paths.
Licensed identity and channel persistence
src/mesh/NodeDB.*, src/mesh/Channels.cpp, src/modules/AdminModule.*, src/modules/NodeInfoModule.cpp, src/main.cpp
Licensed identity regeneration and notifications, signer retention, channel sanitization, public-key publication, metadata initialization, and persistence flags are updated.
Admin and ingress authorization validation
src/modules/AdminModule.*, test/test_admin_radio/test_main.cpp, test/test_mqtt/MQTT.cpp, test/support/*, variants/stm32/*
Signed licensed-admin authorization, session validity, channel persistence, MQTT policy behavior, build flags, and notification tracking receive expanded coverage.
Packet policy and pipeline regression coverage
test/test_packet_signing/test_main.cpp
Deterministic signed packets, policy matrices, licensed signing and migration cases, opaque handling, and authentication pipeline ordering are tested.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Radio
  participant Router
  participant AdminModule
  participant NodeDB
  Radio->>Router: receive signed plaintext admin packet
  Router->>Router: verify XEdDSA and apply routing-auth gate
  Router->>AdminModule: deliver accepted request
  AdminModule->>NodeDB: validate licensed identity and session state
  NodeDB-->>AdminModule: return authorization and migration state
  AdminModule-->>Router: send directed plaintext reply
Loading

Possibly related issues

  • meshtastic/design#123 — Describes the signed plaintext licensed remote-administration flow implemented by the Router and AdminModule changes.
  • meshtastic/Meshtastic-Android#6183 — Covers corresponding Android licensed remote-admin plaintext transport behavior.
  • meshtastic/Meshtastic-Apple#2067 — Covers corresponding Apple licensed remote-admin plaintext transport behavior.
  • meshtastic/firmware#10963 — Covers configurable XEdDSA packet-authenticity policy across routing, decoding, MQTT/RF ingress, opaque relay, and tests.
  • meshtastic/design#122 — Covers licensed-mode XEdDSA identity migration, signing, and public-key behavior.
  • meshtastic/firmware#10966 — Covers licensed plaintext signing, identity migration, PKI exclusion, and related policy tests.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: enabling signed plaintext remote admin in licensed mode.
Description check ✅ Passed The PR description covers summary, dependencies, validation, draft status, and attestations, matching the template’s required content.
Linked Issues check ✅ Passed The changes implement licensed plaintext remote admin with verified signer-key auth, session gating, MQTT blocking, and signed replies as required by #10971.
Out of Scope Changes check ✅ Passed The diff is focused on auth/admin/router/MQTT/test support and related build flags; no clearly unrelated feature work stands out.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

⚡ Try this PR in the Web Flasher

Flash this PR in the Web Flasher

firmware commit boards expires

Warning

This is an automated, unreviewed CI test build. Back up your device configuration
before flashing, and only flash devices you are able to recover.

Supported boards built by this PR (27)
Device Board Platform
Crowpanel Adv 3.5 TFT elecrow-adv-35-tft esp32-s3
Heltec HT62 heltec-ht62-esp32c3-sx1262 esp32-c3
Heltec Mesh Node 096 heltec-mesh-node-t096 nrf52840
Heltec Mesh Node T1 heltec-mesh-node-t1 nrf52840
Heltec Mesh Node T114 heltec-mesh-node-t114 nrf52840
Heltec V3 heltec-v3 esp32-s3
Heltec V4 heltec-v4 esp32-s3
Meshnology W10 meshnology_w10 esp32-s3
Raspberry Pi Pico pico rp2040
Raspberry Pi Pico W picow rp2040
RAK WisMesh Tag rak_wismeshtag nrf52840
RAK WisBlock 11200 rak11200 esp32
RAK WisBlock 11310 rak11310 rp2040
RAK3312 rak3312 esp32-s3
RAK WisBlock 4631 rak4631 nrf52840
Seeed SenseCAP Mesh-Tracker-X1 seeed_mesh_tracker_X1 nrf52840
Seeed Wio Tracker L1 seeed_wio_tracker_L1 nrf52840
Seeed Xiao NRF52840 Kit seeed_xiao_nrf52840_kit nrf52840
Seeed Xiao ESP32-S3 seeed-xiao-s3 esp32-s3
Station G2 station-g2 esp32-s3
Station G3 station-g3 esp32-s3
LILYGO T-Deck t-deck-tft esp32-s3
LILYGO T-Echo t-echo nrf52840
LILYGO T-Echo Plus t-echo-plus nrf52840
LILYGO T-Impulse Plus t-impulse-plus nrf52840
LilyGo T3-C6 tlora-c6 esp32-c6
Seeed SenseCAP T1000-E tracker-t1000-e nrf52840

Build artifacts expire on 2026-08-10. Updated for dcf6e25.

@RCGV1

RCGV1 commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@RCGV1

RCGV1 commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

CI label check requires an enhancement (or equivalent allowed) label. This contributor account cannot add repository labels; a maintainer will need to apply it before the check can pass.

Make the routing authentication gate input const and collapse licensed Admin authorization into a single branch so legacy response, local, channel, and PKI checks run only for non-licensed-remote traffic.
@RCGV1

RCGV1 commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@RCGV1

RCGV1 commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

CI dependency status: the current Native Simulator, WASM, and Docker failures stop at DeviceMetadata.has_xeddsa being absent from the upstream protobuf submodule. That field is supplied by the linked draft dependency meshtastic/protobufs#983; the later lcov: command not found line is cascading cleanup. The independent cppcheck findings were addressed in 84689b0. No signed-Admin compile failure appears before the missing schema field.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/mesh/FloodingRouter.cpp (1)

71-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep this rationale to two lines.

This three-line comment exceeds the repository’s comment limit.

Proposed cleanup
-        // Verify the replacement before deleting the valid lower-hop copy waiting in the TX queue.
-        // This is intentionally redundant with ReliableRouter's ingress gate: it keeps this helper
-        // safe if another caller is introduced later.
+        // Re-authenticate before replacing the queued lower-hop copy so future callers remain safe.

As per coding guidelines, “Keep code comments minimal: one or two lines maximum.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mesh/FloodingRouter.cpp` around lines 71 - 73, Shorten the explanatory
comment above the replacement verification in FloodingRouter to no more than two
lines while preserving its key rationale: verify the replacement before deleting
the valid lower-hop TX-queued copy, even though ReliableRouter already enforces
this at ingress.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/mesh/Router.cpp`:
- Around line 98-105: Guard storeRoutingAuthCache with an early return when
routingAuthCacheLock is null, matching routingAuthCacheMatches,
applyRoutingAuthCache, and clearRoutingAuthCache, before constructing the
LockGuard.
- Around line 655-671: Sanitize the already-decoded fallback packet before
returning from perhapsDecode(), because the routing-auth cache may be clobbered
between passesRoutingAuthGate() and handleReceived(). In the
authCandidate.which_payload_variant == meshtastic_MeshPacket_decoded_tag path,
clear serialized authentication fields directly on *p (including xeddsa_signed,
public_key, and pki_encrypted) before copying or caching the packet, or
otherwise make the gate-to-handle handoff atomic.

In `@test/test_admin_radio/test_main.cpp`:
- Around line 1029-1066: Restore all modified global test state before each test
returns. In test_bootDefense_sanitizesStaleLicensedChannelsOnce and
test_restorePreferences_sanitizesLicensedBackupBeforeReturn, save the original
owner and relevant channel state at the start, then restore them on every exit
path; ensure the restore test also cleans up its temporary NodeDB and backup
resources. Use the existing test setup/cleanup patterns so later tests do not
inherit licensed or sanitized state.

---

Nitpick comments:
In `@src/mesh/FloodingRouter.cpp`:
- Around line 71-73: Shorten the explanatory comment above the replacement
verification in FloodingRouter to no more than two lines while preserving its
key rationale: verify the replacement before deleting the valid lower-hop
TX-queued copy, even though ReliableRouter already enforces this at ingress.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ec360569-c027-4563-9843-1875992d71d6

📥 Commits

Reviewing files that changed from the base of the PR and between 91043fa and 6c63898.

📒 Files selected for processing (24)
  • src/main.cpp
  • src/mesh/Channels.cpp
  • src/mesh/FloodingRouter.cpp
  • src/mesh/FloodingRouter.h
  • src/mesh/NextHopRouter.cpp
  • src/mesh/NextHopRouter.h
  • src/mesh/NodeDB.cpp
  • src/mesh/NodeDB.h
  • src/mesh/Router.cpp
  • src/mesh/Router.h
  • src/mesh/WarmNodeStore.h
  • src/mesh/udp/UdpMulticastHandler.h
  • src/modules/AdminModule.cpp
  • src/modules/AdminModule.h
  • src/modules/NodeInfoModule.cpp
  • src/mqtt/MQTT.cpp
  • test/support/AdminModuleTestShim.h
  • test/support/MockMeshService.h
  • test/test_admin_radio/test_main.cpp
  • test/test_mqtt/MQTT.cpp
  • test/test_packet_signing/test_main.cpp
  • variants/stm32/CDEBYTE_E77-MBL/platformio.ini
  • variants/stm32/rak3172/platformio.ini
  • variants/stm32/stm32.ini

Comment thread src/mesh/Router.cpp
Comment thread src/mesh/Router.cpp Outdated
Comment thread test/test_admin_radio/test_main.cpp
@RCGV1

RCGV1 commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[2.8.0] Authorize signed plaintext remote admin in licensed mode

1 participant