Skip to content

Accept a pending key only for the key-verification exchange#11107

Merged
thebentern merged 1 commit into
developfrom
fix-pending-key-scope
Jul 21, 2026
Merged

Accept a pending key only for the key-verification exchange#11107
thebentern merged 1 commit into
developfrom
fix-pending-key-scope

Conversation

@caveman99

@caveman99 caveman99 commented Jul 20, 2026

Copy link
Copy Markdown
Member

Router::perhapsDecode resolved the sender's key as NodeDB first, else the pending key held during an in-progress key-verification handshake, and then used it for any incoming PKI unicast.

That pending key is whatever 32 bytes the initiator put in hash1. KeyVerificationModule::allocReply stashes it for the claimed from whenever that node is not already in NodeDB, which is exactly the bootstrap case. It proves only that the sender holds the key, not that they are the node they claim to be, and getPendingPublicKey has no expiry of its own.

So an attacker could open a handshake claiming to be an unknown node, then send DMs on any port as that node. They decrypted, were marked pki_encrypted, and carried the attacker's key in public_key, until the session was reset.

perhapsEncode already restricts the pending key to KEY_VERIFICATION_APP when choosing a destination key. The receive path now applies the same rule: a decrypt that used the pending key is rejected unless the payload is the key-verification exchange itself. Keys resolved from NodeDB and the admin-key fallback are unchanged, and a pending key is still never persisted.

Bootstrapping is unaffected, since the follow-on handshake packet is on that port.

Adds a regression test: a text DM encrypted with a pending key no longer decodes and is not marked PKI-authenticated, while the key-verification packet still decodes. Verified the test fails without the fix.

Summary by CodeRabbit

  • Bug Fixes
    • Improved security for packets encrypted with keys still undergoing verification.
    • Prevented unverified keys from authenticating unrelated application messages.
    • Continued allowing pending keys to decrypt key-verification messages.
    • Ensured pending keys are not permanently stored until verification is complete.

perhapsDecode fell back to the not-yet-verified key held during a key
verification handshake for any incoming PKI unicast. That key is supplied
by whoever opened the handshake and proves only that they hold it, not that
they are the node they claim to be, so until the session ended they could
send DMs on any port that decrypted and were marked pki_encrypted.

perhapsEncode already restricts the pending key to KEY_VERIFICATION_APP.
The receive path now applies the same rule.
@caveman99 caveman99 added the bugfix Pull request that fixes bugs label Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e2a735c-42f1-41fa-8fb8-ce93de88773e

📥 Commits

Reviewing files that changed from the base of the PR and between 0199a1f and c2b8b59.

📒 Files selected for processing (2)
  • src/mesh/Router.cpp
  • test/test_pki_admin_fallback/test_main.cpp

📝 Walkthrough

Walkthrough

PKI decryption now distinguishes persisted keys from pending verification keys and rejects pending-key packets outside KEY_VERIFICATION_APP. A regression test covers rejection, valid key verification, and prevention of premature NodeDB persistence.

Changes

Pending PKI key security

Layer / File(s) Summary
Pending-key authorization
src/mesh/Router.cpp
PKI key resolution tracks pending-key provenance and permits decoded pending-key packets only on KEY_VERIFICATION_APP.
Pending-key regression coverage
test/test_pki_admin_fallback/test_main.cpp
Tests reject unrelated pending-key traffic, accept key verification traffic, verify no NodeDB persistence, and register the test runner invocation.

Estimated code review effort: 3 (Moderate) | ~15–30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant IncomingPacket
  participant RouterPerhapsDecode
  participant PendingVerification
  participant NodeDB
  IncomingPacket->>RouterPerhapsDecode: encrypted packet
  RouterPerhapsDecode->>PendingVerification: resolve pending public key
  RouterPerhapsDecode->>NodeDB: resolve persisted public key
  RouterPerhapsDecode->>RouterPerhapsDecode: decrypt and decode packet
  RouterPerhapsDecode->>RouterPerhapsDecode: allow pending-key result only for KEY_VERIFICATION_APP
Loading

Possibly related PRs

Suggested reviewers: jp-bennett, thebentern

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% 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 accurately summarizes the main behavior change around allowing pending keys only for key verification.
Description check ✅ Passed The description clearly explains the bug, fix, and regression test, with only the template's attestation checklist omitted.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-pending-key-scope

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@caveman99
caveman99 requested a review from jp-bennett July 20, 2026 18:21
@github-actions

github-actions Bot commented Jul 20, 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 (30)
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 Pocket V3 rak_wismesh_pocket nrf52840
RAK WisMesh Pod rak_wismesh_pod nrf52840
RAK WisMesh Repeater Mini V2 rak_wismesh_repeater_mini nrf52840
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-19. Updated for c2b8b59.

@thebentern
thebentern merged commit d9f8839 into develop Jul 21, 2026
107 checks passed
@thebentern
thebentern deleted the fix-pending-key-scope branch July 21, 2026 01:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Pull request that fixes bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants