Skip to content

Allow key verification to work for unknown nodes. - #10669

Merged
thebentern merged 13 commits into
developfrom
keyVerificationEnhancement
Jul 15, 2026
Merged

Allow key verification to work for unknown nodes.#10669
thebentern merged 13 commits into
developfrom
keyVerificationEnhancement

Conversation

@jp-bennett

@jp-bennett jp-bennett commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Still slightly WIP, but tested working.

Summary by CodeRabbit

  • New Features
    • Number picker now supports hexadecimal entry mode.
    • Key verification can proceed during bootstrapping using pending public-key data when a peer key isn’t stored yet.
  • Bug Fixes
    • Verified/accepted key confirmations now commit consistently, including when a pending key is involved.
    • PKI encrypted message handling is more resilient: it can fall back to admin keys and learn/persist sender keys for future decrypts.
  • Tests
    • Added coverage for PKI admin-key fallback decryption for previously unknown senders.

@github-actions github-actions Bot added needs-review Needs human review enhancement New feature or request labels Jun 10, 2026
@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Firmware Size Report

42 targets | vs develop: 21 increased, 1 decreased, net +460,504 (+449.7 KB)

Target Flash vs develop RAM RAM vs develop
picow 1,275,436 📈 +30,556 (+29.8 KB) 152,508 n/a
pico2w 1,249,920 📈 +29,404 (+28.7 KB) 158,988 n/a
rak11310 834,480 📈 +29,056 (+28.4 KB) 86,320 n/a
pico 811,728 📈 +29,040 (+28.4 KB) 85,876 n/a
seeed_xiao_rp2040 809,944 📈 +29,040 (+28.4 KB) 85,868 n/a
Show 37 more target(s)
Target Flash vs develop RAM RAM vs develop
seeed_xiao_rp2350 796,048 📈 +28,112 (+27.5 KB) 92,368 n/a
pico2 797,880 📈 +28,104 (+27.4 KB) 92,376 n/a
elecrow-adv-35-tft 3,440,496 📈 +23,872 (+23.3 KB) 94,624 n/a
heltec-vision-master-e213-inkhud 2,250,944 📈 +23,584 (+23.0 KB) 92,400 n/a
t-deck-tft 3,834,160 📈 +23,488 (+22.9 KB) 108,008 n/a
rak3312 2,291,984 📈 +19,744 (+19.3 KB) 102,040 n/a
heltec-ht62-esp32c3-sx1262 2,154,240 📈 +19,248 (+18.8 KB) 112,004 n/a
seeed-xiao-s3 2,295,232 📈 +19,088 (+18.6 KB) 102,064 n/a
rak11200 1,879,520 📈 +18,912 (+18.5 KB) 87,052 n/a
station-g2 2,285,200 📈 +18,848 (+18.4 KB) 101,848 n/a
station-g3 2,285,200 📈 +18,848 (+18.4 KB) 101,848 n/a
heltec-v3 2,282,864 📈 +18,784 (+18.3 KB) 130,232 n/a
t-eth-elite 2,509,328 📈 +17,840 (+17.4 KB) 103,076 n/a
heltec-v4 2,295,056 📈 +17,760 (+17.3 KB) 101,112 n/a
tlora-c6 2,385,984 📈 +17,680 (+17.3 KB) 102,764 n/a
rak3172 183,656 📉 -2,640 (-2.6 KB) 25,008 n/a
wio-e5 240,764 📈 +2,136 (+2.1 KB) 26,640 n/a
heltec-mesh-node-t096 751,416 110,492 n/a
heltec-mesh-node-t1 749,296 102,236 n/a
heltec-mesh-node-t114 749,168 110,428 n/a
heltec-mesh-solar 746,280 110,596 n/a
heltec-mesh-solar-eink 738,152 109,916 n/a
heltec-mesh-solar-inkhud 718,744 101,372 n/a
heltec-mesh-solar-oled 746,384 110,596 n/a
heltec-mesh-solar-tft 752,640 110,692 n/a
heltec-mesh-tower-v2 737,352 101,452 n/a
meshnology_w10 2,465,632 136,116 n/a
rak4631 767,272 104,500 n/a
rak_wismeshtag 601,112 92,604 n/a
seeed_mesh_tracker_X1 508,160 87,316 n/a
seeed_wio_tracker_L1 740,832 110,340 n/a
seeed_xiao_nrf52840_kit 735,560 102,060 n/a
t-echo 754,368 93,236 n/a
t-echo-inkhud 714,184 83,956 n/a
t-echo-plus 752,072 93,236 n/a
t-impulse-plus 727,856 110,324 n/a
tracker-t1000-e 507,240 87,316 n/a

Size budgets

Env Metric Measured Budget Used
rak4631 RAM (.data+.bss) 104,500 113,000 92.5%
rak4631 flash 767,272 786,000 97.6%

Budgets live in bin/ram_budgets.json and are raised deliberately in the PR that needs the headroom - never automatically.


Updated for b2a9d77

jp-bennett and others added 2 commits July 8, 2026 21:14
Resolved conflicts in the crypto/PKI path where both branches changed
public-key resolution:

- CryptoEngine.h: kept both branches' new protected members
  (key-verification pending-key fields + develop's XEDDSA fields).
- Router.cpp perhapsDecode(): use develop's nodeDB->copyPublicKey()
  (hot-store/warm-tier lookup) as the primary sender-key source, then
  fall back to crypto->getPendingPublicKey() for an in-progress
  key-verification handshake.
- Router.cpp send path: same combination for the destination key —
  copyPublicKey() primary, pending key fallback for KEY_VERIFICATION_APP
  follow-on packets.

protobufs submodule advanced to develop's pointer (1ae3be3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Adds pending public-key storage, updates Router PKI key resolution, reworks the key-verification handshake bootstrap and commit flow, wires the UI into verification, and adds admin-fallback PKI tests.

Changes

Key verification handshake feature

Layer / File(s) Summary
CryptoEngine pending public key storage
src/mesh/CryptoEngine.h, src/mesh/CryptoEngine.cpp
Adds pending public key setters, getters, clearers, and backing state to CryptoEngine.
Router PKI key resolution fallback
src/mesh/Router.cpp
perhapsDecode and perhapsEncode resolve sender and destination public keys from NodeDB or the pending key store, and adjust PKI eligibility for key verification packets.
KeyVerificationModule bootstrap handshake
src/modules/KeyVerificationModule.h, src/modules/KeyVerificationModule.cpp
Reworks the key-verification handshake to bootstrap through hash1 and hash2, store missing keys as pending, and update request and response generation.
KeyVerificationModule commit and state handling
src/modules/KeyVerificationModule.h, src/modules/KeyVerificationModule.cpp
Adds commitVerifiedRemoteNode, changes updateState timing control, and clears pending keys during reset.
Screen and MenuHandler wiring
src/graphics/Screen.h, src/graphics/Screen.cpp, src/graphics/draw/MenuHandler.cpp
showNumberPicker adds hex picker selection, numberTest starts verification, and the accept prompt commits the verified remote node.
PKI admin fallback tests
test/native-suite-count, test/test_pki_admin_fallback/*
Adds a native test suite for admin-key decryption fallback, node persistence, and failure cases, plus the updated suite count.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is too vague and omits the required template sections, test attestations, device regressions, and any issue reference. Replace it with the full PR template, including the attestations checklist, testing details, any regressions/device notes, and a fixes #... reference if applicable.
Docstring Coverage ⚠️ Warning Docstring coverage is 21.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: key verification now works with unknown nodes.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch keyVerificationEnhancement

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/modules/KeyVerificationModule.cpp (2)

363-372: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Committed key/verification flag is not persisted.

commitVerifiedRemoteNode() writes the peer's public key and the manual-verification bitfield into the in-memory NodeInfoLite, but the todo: initiate save means these are lost on reboot until some other path happens to flush NodeDB. Since manual verification is meant to be durable, this should trigger a NodeDB save.

Want me to open an issue or wire up the appropriate NodeDB save call here?

🤖 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/modules/KeyVerificationModule.cpp` around lines 363 - 372, The committed
peer key and manual-verification flag in commitVerifiedRemoteNode() are only
updated in memory, so make this change durable by triggering a NodeDB save after
updating node->public_key and node->bitfield. Use the existing NodeDB
persistence path available to KeyVerificationModule (and any nearby
NodeInfoModule/NodeDB helper methods) so the verified state survives reboot
instead of relying on a later flush.

195-195: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Security number should come from a CSPRNG.

The security number is the entropy that gives this handshake its MitM resistance. random() is not cryptographically secure and may be poorly seeded on embedded targets, making the number predictable to an on-path attacker. The existing // fixme, use better random acknowledges this; consider sourcing it from the same secure RNG used for key material.

Want me to open an issue to track switching to a CSPRNG here?

🤖 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/modules/KeyVerificationModule.cpp` at line 195, The security number
generation in KeyVerificationModule::currentSecurityNumber uses random(), which
is not cryptographically secure and can be predictable on embedded targets.
Replace it with a CSPRNG-backed source, ideally the same secure RNG used for key
material in this module, and keep the value in the same 1..999999 range if
needed. Update the security-number assignment in KeyVerificationModule so the
handshake entropy comes from a secure source instead of the current random()
call.
🤖 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/graphics/draw/MenuHandler.cpp`:
- Line 2513: The accepted verification state is only changed in memory in
KeyVerificationModule::commitVerifiedRemoteNode(), so the manual-verification
flag and related pending key data are lost after restart. Update
commitVerifiedRemoteNode() to persist the verified state to storage after
setting the bitfield and clearing the pending public key, using the existing
save/persist path in MenuHandler so the change survives reboot.

In `@src/modules/KeyVerificationModule.cpp`:
- Line 369: The warning in KeyVerificationModule::manual verification logging
uses decimal output for currentRemoteNode, but 32-bit node IDs must be logged
consistently as zero-padded hex. Update the LOG_WARN call in
KeyVerificationModule.cpp to format currentRemoteNode as 0x%08x while keeping
currentSecurityNumber unchanged, matching the established node/packet ID logging
convention.

---

Nitpick comments:
In `@src/modules/KeyVerificationModule.cpp`:
- Around line 363-372: The committed peer key and manual-verification flag in
commitVerifiedRemoteNode() are only updated in memory, so make this change
durable by triggering a NodeDB save after updating node->public_key and
node->bitfield. Use the existing NodeDB persistence path available to
KeyVerificationModule (and any nearby NodeInfoModule/NodeDB helper methods) so
the verified state survives reboot instead of relying on a later flush.
- Line 195: The security number generation in
KeyVerificationModule::currentSecurityNumber uses random(), which is not
cryptographically secure and can be predictable on embedded targets. Replace it
with a CSPRNG-backed source, ideally the same secure RNG used for key material
in this module, and keep the value in the same 1..999999 range if needed. Update
the security-number assignment in KeyVerificationModule so the handshake entropy
comes from a secure source instead of the current random() call.
🪄 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: 2869bd9c-21ec-463b-8dbd-c341be88e5d8

📥 Commits

Reviewing files that changed from the base of the PR and between b4b1ece and 95139c1.

📒 Files selected for processing (8)
  • src/graphics/Screen.cpp
  • src/graphics/Screen.h
  • src/graphics/draw/MenuHandler.cpp
  • src/mesh/CryptoEngine.cpp
  • src/mesh/CryptoEngine.h
  • src/mesh/Router.cpp
  • src/modules/KeyVerificationModule.cpp
  • src/modules/KeyVerificationModule.h

Comment thread src/graphics/draw/MenuHandler.cpp
Comment thread src/modules/KeyVerificationModule.cpp Outdated
@github-actions

github-actions Bot commented Jul 9, 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-08. Updated for 7c9a7a0.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

532-534: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Shorten the expanded PKI comments.

These comments exceed the project’s one-to-two-line limit; the key source and admin persistence rationale can be kept concise.

Proposed cleanup
-        // If we know the sender's key, make a single attempt with it. Otherwise fall back to trying each
-        // configured admin key, so an authorized admin can reach a node that has not yet learned their
-        // key. decryptCurve25519 is AES-CCM AEAD, so a wrong key fails authentication and we try the next.
+        // Try the sender key first, then configured admin keys.
+        // Wrong Curve25519 candidates fail AEAD authentication.
@@
-                    // We decrypted this using a configured admin key, so we had no pubkey from the sender.
-                    // Persist the key so future packets take the fast path and we can PKI-reply. The from
-                    // nodenum is bound into the AEAD nonce, so this NodeNum->key binding is authenticated
-                    // by the (trusted) admin that produced the packet.
+                    // Persist the admin-authenticated NodeNum->key binding for future PKI replies.

As per coding guidelines, “Keep code comments minimal: use at most one or two lines, only when the reason is not obvious.”

Also applies to: 567-570

🤖 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/Router.cpp` around lines 532 - 534, Shorten the expanded PKI
comments in Router::decryptCurve25519-related logic so they fit the
one-to-two-line comment guideline. Keep only the essential point: prefer the
sender’s known key first, then fall back to configured admin keys so authorized
admins can still reach nodes that haven’t learned their key yet; update both
comment blocks referenced in Router.cpp to be concise and remove extra
explanation.

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 527-564: The PKI decrypt path in Router::processPacket is using an
unguarded remotePublic and also treats pending keys as sufficient for marking
the packet PKI-encrypted. Update the decrypt branch to only enter Curve25519
work when haveRemoteKey is true and the packet matches the channel 0 /
addressed-to-us / non-broadcast / size requirements, and keep
getPendingPublicKey results scoped to verification-only until the key is
actually committed. Make the acceptance of decrypted data depend on a verified
public key source before setting p->pki_encrypted, copying p->public_key, or
using the decoded payload.

---

Nitpick comments:
In `@src/mesh/Router.cpp`:
- Around line 532-534: Shorten the expanded PKI comments in
Router::decryptCurve25519-related logic so they fit the one-to-two-line comment
guideline. Keep only the essential point: prefer the sender’s known key first,
then fall back to configured admin keys so authorized admins can still reach
nodes that haven’t learned their key yet; update both comment blocks referenced
in Router.cpp to be concise and remove extra explanation.
🪄 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: d85a0d9e-3c1b-49fc-b7da-c28d92aedf06

📥 Commits

Reviewing files that changed from the base of the PR and between 95139c1 and 5ea0b3a.

📒 Files selected for processing (1)
  • src/mesh/Router.cpp

Comment thread src/mesh/Router.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/test_pki_admin_fallback/test_main.cpp (1)

1-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Trim multi-line comment blocks per coding guidelines.

Several comment blocks in this file span 3+ lines (e.g. the file header at Lines 1-11, and section headers at Lines 32-35, 72-74, 85-87, 137-139, 173-175, 211-212). The repo guideline restricts comments to 1-2 lines unless the reason is non-obvious.

As per coding guidelines, "Keep code comments minimal: one or two lines maximum, only when the reason is not obvious, and do not restate the next line."

Also applies to: 32-35, 72-74, 85-87, 137-139, 173-175, 211-212

🤖 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 `@test/test_pki_admin_fallback/test_main.cpp` around lines 1 - 11, Several
comment blocks in this test file are too verbose and exceed the 1–2 line
guideline. Trim the file header and the section headers around the test cases so
each comment is brief, keeps only the non-obvious rationale, and does not
restate what the following code already shows. Update the multi-line comments
near the admin-key fallback tests in test_main.cpp, especially the header and
the repeated section markers, to shorter single- or two-line notes.

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.

Nitpick comments:
In `@test/test_pki_admin_fallback/test_main.cpp`:
- Around line 1-11: Several comment blocks in this test file are too verbose and
exceed the 1–2 line guideline. Trim the file header and the section headers
around the test cases so each comment is brief, keeps only the non-obvious
rationale, and does not restate what the following code already shows. Update
the multi-line comments near the admin-key fallback tests in test_main.cpp,
especially the header and the repeated section markers, to shorter single- or
two-line notes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b6c3a08-07bf-4b3b-a9ef-6a698c400713

📥 Commits

Reviewing files that changed from the base of the PR and between 5ea0b3a and 4771d39.

📒 Files selected for processing (2)
  • test/native-suite-count
  • test/test_pki_admin_fallback/test_main.cpp

@jp-bennett
jp-bennett requested a review from Copilot July 9, 2026 18:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Enables key verification (and PKI encode/decode) to work when a peer’s public key is not yet in NodeDB by bootstrapping keys via the key-verification handshake and by adding admin-key fallback on PKI decryption.

Changes:

  • Key-verification handshake now supports “unknown node” bootstrapping via public-key carriage + pending-key handling.
  • Router PKI decode/encode now consults a pending key and adds admin-key fallback decryption for unknown senders.
  • Adds a native test suite covering admin-key fallback behavior, and extends number picker to support hex mode.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
test/test_pki_admin_fallback/test_main.cpp New crypto-backed tests for admin-key fallback PKI decryption + persistence behavior.
test/native-suite-count Increments native test suite count for newly added suite.
src/modules/KeyVerificationModule.h Documents handshake + introduces commitVerifiedRemoteNode() and updateState(resetTimer).
src/modules/KeyVerificationModule.cpp Implements bootstrap key exchange using pending keys; commits pending key on user acceptance.
src/mesh/Router.cpp Uses pending keys for PKI encode/decode and attempts admin-key fallback decryption when sender key unknown.
src/mesh/CryptoEngine.h Adds pending public key API/state for in-progress key verification.
src/mesh/CryptoEngine.cpp Implements pending public key storage/retrieval.
src/graphics/draw/MenuHandler.cpp Updates number test to use hex nodenum entry and trigger key verification.
src/graphics/Screen.h Extends showNumberPicker signature to accept base-16 mode.
src/graphics/Screen.cpp Implements base-16 mode selection (hex picker vs decimal picker).

Comment thread src/mesh/Router.cpp
Comment thread src/modules/KeyVerificationModule.cpp Outdated
Comment thread src/graphics/draw/MenuHandler.cpp Outdated
Comment thread src/mesh/CryptoEngine.cpp
Comment thread src/modules/KeyVerificationModule.cpp
Comment thread test/test_pki_admin_fallback/test_main.cpp
- Persist the committed key + manually-verified flag in
  commitVerifiedRemoteNode via saveToDisk(SEGMENT_NODEDATABASE),
  replacing the "todo: initiate save"
- Guard the CryptoEngine pending-key slot with a dedicated internal
  lock; the Router reads it while already holding the non-recursive
  cryptLock, so the accessors cannot reuse that lock
- Draw the security number from the hardware RNG (CryptRNG fallback)
  under cryptLock instead of random(); on nRF52 the entropy fill
  toggles the same CC310 the BLE task's packet crypto uses
- Return true after fully handling the hash2 response (restores
  develop behavior; consistent with the hash1 branch)
- Take uint32_t in the number-picker callbacks so 8-digit hex
  nodenums can't truncate through int
- Trim over-long comments flagged by review
@ndoo

ndoo commented Jul 13, 2026

Copy link
Copy Markdown
Contributor
INFO  | 17:20:25 52 [Router] Received Admin from=0x2deb7bcc, id=0x2a43c732, portnum=6, payloadlen=40
INFO  | 17:20:25 52 [Router] PKC admin payload with authorized sender key
INFO  | 17:20:25 52 [Router] Handle admin payload 32
DEBUG | 17:20:25 52 [Router] Incoming session key:  29 04 b4 78 d8 68 a7 ff
DEBUG | 17:20:25 52 [Router] Expected session key:  00 00 00 00 00 00 00 00
WARN  | 17:20:25 52 [Router] Admin message without session_key!

^ On the receiving end.

Looks like it gets a bit further with this PR than without, but losing the node in nodedb causes the session to be lost too?

@thebentern

Copy link
Copy Markdown
Contributor
INFO  | 17:20:25 52 [Router] Received Admin from=0x2deb7bcc, id=0x2a43c732, portnum=6, payloadlen=40
INFO  | 17:20:25 52 [Router] PKC admin payload with authorized sender key
INFO  | 17:20:25 52 [Router] Handle admin payload 32
DEBUG | 17:20:25 52 [Router] Incoming session key:  29 04 b4 78 d8 68 a7 ff
DEBUG | 17:20:25 52 [Router] Expected session key:  00 00 00 00 00 00 00 00
WARN  | 17:20:25 52 [Router] Admin message without session_key!

^ On the receiving end.

Looks like it gets a bit further with this PR than without, but losing the node in nodedb causes the session to be lost too?

Did this happen right after a reboot or a nodedb reset where the session key would be gone perhaps?

@ndoo

ndoo commented Jul 14, 2026

Copy link
Copy Markdown
Contributor
INFO  | 17:20:25 52 [Router] Received Admin from=0x2deb7bcc, id=0x2a43c732, portnum=6, payloadlen=40
INFO  | 17:20:25 52 [Router] PKC admin payload with authorized sender key
INFO  | 17:20:25 52 [Router] Handle admin payload 32
DEBUG | 17:20:25 52 [Router] Incoming session key:  29 04 b4 78 d8 68 a7 ff
DEBUG | 17:20:25 52 [Router] Expected session key:  00 00 00 00 00 00 00 00
WARN  | 17:20:25 52 [Router] Admin message without session_key!

^ On the receiving end.
Looks like it gets a bit further with this PR than without, but losing the node in nodedb causes the session to be lost too?

Did this happen right after a reboot or a nodedb reset where the session key would be gone perhaps?

On STM32WL, where it did work in the same session (no reboot) then after some time stopped working (or rather works intermittently) because nodes keep getting popped out of NodeDB (due to the 10-node limit)

@thebentern

Copy link
Copy Markdown
Contributor

Dug into this — the Expected session key: 00…00 isn't the node losing it out of the nodedb. The session passkey is a single global anti-replay nonce in AdminModule (session_passkey), not stored per-node and not in the nodedb at all, so eviction can't touch it. It's minted only when the node answers an admin GET (setPassKey runs on the get_config/get_owner/etc. responses). A setter like set_owner (variant 32, the message in your log) never mints one, and nothing resets it at runtime — it's zeroed only at construction.

So Expected: 0 means one specific thing: this node has answered zero admin GETs since it last started. The client sent set_owner using a session key it had cached from an earlier session, without doing a fresh GET to establish a session with the freshly-started node. The 52s uptime lines up with that — the node just came up (flash/power-cycle), not necessarily a crash.

Worth stressing the PR's part worked here: the packet decrypted via the authorized admin key and got all the way to the session check, which is impossible without a successful decrypt. The session gate is a separate, pre-existing mechanism the PR doesn't touch.

The reason it doesn't self-heal is that the BAD_SESSION_KEY NAK is a bare routing ack/nak with no session key in it, so the client can't learn the fresh session from the rejection — it has to proactively re-GET. If we want admin-to-a-restarted-node to just work, the clean fix is to have that NAK carry a fresh session_passkey so the client can retry in a single round-trip (proper challenge/response).

@ndoo — in the receiving node's log from boot, do you see any get_* handled / a Set admin key to … line before the failing set_owner? If not, the client is skipping session establishment on the fresh node and reusing a stale key. If you do see one, then a passkey was minted this boot and it'd be a different bug — would want that full log.

@ndoo

ndoo commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@ndoo — in the receiving node's log from boot, do you see any get_* handled / a Set admin key to … line before the failing set_owner? If not, the client is skipping session establishment on the fresh node and reusing a stale key. If you do see one, then a passkey was minted this boot and it'd be a different bug — would want that full log.

It'll be a while before I can test this, unfortunately. But forcing the 10-node NodeDB limit might help you to reproduce it quickly.

@thebentern

Copy link
Copy Markdown
Contributor

Based on what I'm finding locally this may be something specific to STM32 because I can't repro on NRF

@ndoo

ndoo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Based on what I'm finding locally this may be something specific to STM32 because I can't repro on NRF

I’ll only be able to try this further tomorrow night, but don't let STM32 hold back this PR since it’s not a stable platform.

@ndoo

ndoo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Based on what I'm finding locally this may be something specific to STM32 because I can't repro on NRF

here is what my phantom brain thinks:
https://gist.github.com/ndoo/ad73e260dc2d7a03488aef05d8115217

@jp-bennett

Copy link
Copy Markdown
Collaborator Author
INFO  | 17:20:25 52 [Router] Received Admin from=0x2deb7bcc, id=0x2a43c732, portnum=6, payloadlen=40
INFO  | 17:20:25 52 [Router] PKC admin payload with authorized sender key
INFO  | 17:20:25 52 [Router] Handle admin payload 32
DEBUG | 17:20:25 52 [Router] Incoming session key:  29 04 b4 78 d8 68 a7 ff
DEBUG | 17:20:25 52 [Router] Expected session key:  00 00 00 00 00 00 00 00
WARN  | 17:20:25 52 [Router] Admin message without session_key!

^ On the receiving end.

Looks like it gets a bit further with this PR than without, but losing the node in nodedb causes the session to be lost too?

Would love to see the full log with this error.

@thebentern
thebentern merged commit 952c825 into develop Jul 15, 2026
82 checks passed
@ndoo

ndoo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

INFO | 17:20:25 52 [Router] Received Admin from=0x2deb7bcc, id=0x2a43c732, portnum=6, payloadlen=40

INFO | 17:20:25 52 [Router] PKC admin payload with authorized sender key

INFO | 17:20:25 52 [Router] Handle admin payload 32

DEBUG | 17:20:25 52 [Router] Incoming session key: 29 04 b4 78 d8 68 a7 ff

DEBUG | 17:20:25 52 [Router] Expected session key: 00 00 00 00 00 00 00 00

WARN | 17:20:25 52 [Router] Admin message without session_key!

^ On the receiving end.

Looks like it gets a bit further with this PR than without, but losing the node in nodedb causes the session to be lost too?

Would love to see the full log with this error.

I'll raise an issue when I manage to capture it tomorrow. Seems related to STM32WL not having the warm store (see the CC analysis I linked)

@thebentern
thebentern deleted the keyVerificationEnhancement branch July 15, 2026 11:19
NomDeTom added a commit to NomDeTom/MeshtasticFirmware that referenced this pull request Jul 18, 2026
The canonical suite count drifted from the actual test/test_*/ directory count
upstream on develop: meshtastic#10669 added two suites but bumped the count by one, and
meshtastic#11037 added test_xmodem without bumping it at all - leaving native-suite-count
at 35 against 37 real suites, which run-tests.sh reports as AMBER. Correct it to 37.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NomDeTom added a commit to NomDeTom/MeshtasticFirmware that referenced this pull request Jul 18, 2026
The canonical suite count drifted from the actual test/test_*/ directory count
upstream on develop: meshtastic#10669 added two suites but bumped the count by one, and
meshtastic#11037 added test_xmodem without bumping it at all - leaving native-suite-count
at 35 against 37 real suites, which run-tests.sh reports as AMBER. Correct it to 37.
NomDeTom added a commit to NomDeTom/MeshtasticFirmware that referenced this pull request Jul 18, 2026
native-suite-count drifted from the actual test/test_*/ directory count: meshtastic#10669
added two suites (test_admin_session_repro, test_pki_admin_fallback) but bumped
the count by only one, and meshtastic#11037 added test_xmodem without bumping it at all.
The file reads 35 against 37 real suites, which bin/run-tests.sh reports as AMBER
on every full run. Correct it to 37.
thebentern pushed a commit that referenced this pull request Jul 18, 2026
native-suite-count drifted from the actual test/test_*/ directory count: #10669
added two suites (test_admin_session_repro, test_pki_admin_fallback) but bumped
the count by only one, and #11037 added test_xmodem without bumping it at all.
The file reads 35 against 37 real suites, which bin/run-tests.sh reports as AMBER
on every full run. Correct it to 37.
Itzdavid01 pushed a commit to Itzdavid01/firmware that referenced this pull request Sep 5, 2026
* Allow key verification to work for unknown nodes.

* trunk

* More reliable admin key decryption

* Add admin key fallback tests

* Actually check haveRemoteKey

* Logging cleanup

* Address review feedback

- Persist the committed key + manually-verified flag in
  commitVerifiedRemoteNode via saveToDisk(SEGMENT_NODEDATABASE),
  replacing the "todo: initiate save"
- Guard the CryptoEngine pending-key slot with a dedicated internal
  lock; the Router reads it while already holding the non-recursive
  cryptLock, so the accessors cannot reuse that lock
- Draw the security number from the hardware RNG (CryptRNG fallback)
  under cryptLock instead of random(); on nRF52 the entropy fill
  toggles the same CC310 the BLE task's packet crypto uses
- Return true after fully handling the hash2 response (restores
  develop behavior; consistent with the hash1 branch)
- Take uint32_t in the number-picker callbacks so 8-digit hex
  nodenums can't truncate through int
- Trim over-long comments flagged by review

* feat(tests): add deterministic tests for admin session-key behavior

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request needs-review Needs human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants