Skip to content

feat: Add optional Perfect Forward Secrecy for direct messages#9116

Open
krisclarkdev wants to merge 38 commits into
meshtastic:developfrom
krisclarkdev:feature/pfs-clean
Open

feat: Add optional Perfect Forward Secrecy for direct messages#9116
krisclarkdev wants to merge 38 commits into
meshtastic:developfrom
krisclarkdev:feature/pfs-clean

Conversation

@krisclarkdev

Copy link
Copy Markdown

Summary

Adds optional Perfect Forward Secrecy (PFS) for direct messages using ephemeral Curve25519 key exchange.

Fully backward compatible - nodes running this code can still communicate with stock firmware.

How It Works

Each node generates an ephemeral keypair at boot. When two nodes both support PFS, messages between them use Triple-DH key derivation:

DH1 = DH(local_identity_priv, remote_ephemeral_pub)
DH2 = DH(local_ephemeral_priv, remote_identity_pub)
DH3 = DH(local_ephemeral_priv, remote_ephemeral_pub)
session_key = SHA256(DH1 || DH2 || DH3)

This provides mutual authentication and forward secrecy.

Key Features

  • EphemeralKeyManager - Generates/rotates Curve25519 ephemeral keys
  • Triple-DH derivation - Mutual auth + forward secrecy
  • Key rotation - After 100 messages or 24 hours
  • LRU cache - 32 remote key entries
  • Persistence - Keys saved to flash with checksum validation
  • Graceful fallback - Falls back to legacy PKI if PFS unavailable

Files Changed

  • src/mesh/EphemeralKeyManager.h/.cpp - New ephemeral key manager
  • src/mesh/CryptoEngine.h/.cpp - Triple-DH and PFS encrypt/decrypt
  • src/mesh/Router.cpp - PFS integration in encode/decode paths
  • src/main.cpp - EphemeralKeyManager initialization

Build Status

Tested on tbeam (ESP32) - 95.8% flash usage. Compiles clean.

Testing Needed

I only have one device currently. Would appreciate community testing:

  1. PFS node ↔ PFS node communication
  2. PFS node ↔ stock firmware (fallback test)
  3. Key persistence across power cycles
  4. Key rotation behavior (build with -D PFS_TEST_MODE for 5-message rotation)

krisclarkdev and others added 20 commits December 23, 2025 10:13
- BinarySemaphorePosix: Replace delay() stub with pthread_cond_timedwait
- doLightSleep: Add 30 second default parameter, matching THIRTY_SECONDS_MS
- main_matrix.yml: Fetch branches before merge-base to fix shame job failure
Replace FreeRTOS-specific pdTRUE macro with literal 1 for compatibility
with native/POSIX builds that don't have FreeRTOS headers.
- Create PhoneAPIModule to handle packet delivery to phone/API clients
- Move phone delivery logic from RoutingModule to dedicated module
- Add PhoneAPIModule as friend class to MeshService for handleFromRadio access
- Update ESP-IDF deprecation comment to note existing version-conditional handling
- Register PhoneAPIModule in Modules.cpp
Reverting meshtastic#6 (PhoneAPIModule) due to concerns about behavioral changes.
Keeping meshtastic#8 (ESP-IDF deprecation comment fix) which is safe.
- Add ARCH_PORTDUINO guard to use pthread only on Linux/native
- Keep stub implementation for STM32 and other non-POSIX platforms
- Fixes CI failure on rak3172 build
Use origin/ prefix for both base and head branches in git merge-base.
The branches are fetched as remote refs, not local branches.
The gh run download command does not support --commit flag.
Changed to use gh run list --commit to find the run ID first,
then download using that run ID. Added fallback for cases where
no workflow run exists for the merge base commit.
Fixes 'Not a valid object name' error by:
1. Adding explicit git fetch of base branch before merge-base
2. Using gh run list to find valid run ID before download
3. Adding graceful fallback when no previous artifacts exist
Adds ephemeral key exchange using Triple-DH for forward secrecy when both
nodes support it. Falls back to standard PKI encryption for compatibility
with stock firmware.

Key features:
- EphemeralKeyManager: generates/rotates Curve25519 ephemeral key pairs
- Triple-DH session key derivation for mutual auth + forward secrecy
- Key rotation after 100 messages or 24 hours
- LRU cache for remote ephemeral keys (32 entries)
- Persistence to flash with checksum validation
- Graceful fallback to legacy PKI if PFS unavailable
@krisclarkdev

Copy link
Copy Markdown
Author

I have tested that my proposed changes behave as described.

I have tested that my proposed changes do not cause any obvious regressions on the following devices:

[] Heltec (Lora32) V3
[x] Heltec (Lora32) V4
[] LilyGo T-Deck
[] LilyGo T-Beam (build verified)
[] RAK WisBlock / STM32 (build verified)
[] Seeed Studio T-1000E tracker card
[x] Native simulator (all tests pass)

Jorropo added a commit to Jorropo/firmware that referenced this pull request Jan 2, 2026
The previous setup would be extremely lenient.
AFAIK would basically only enforce tabulation more or less.

Add a .clang-format file, the point is you can't accidently have whitespace changes.
See meshtastic#9116 for what sitatuation we are in before this patch.

The point is there is a single valid way to format the file,
and running trunk fmt -a will bring you to it.
@Jorropo Jorropo mentioned this pull request Jan 2, 2026

@Jorropo Jorropo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Well you managed to resolve the whitespace situation yourself.

I've submitted #9154 so robots would take care of the formatting for us.

Comment thread .github/workflows/main_matrix.yml Outdated
Jorropo added a commit to Jorropo/firmware that referenced this pull request Jan 2, 2026
The previous setup would be extremely lenient.
AFAIK would basically only enforce tabulation more or less.

Add a .clang-format file, the point is you can't accidently have whitespace changes.
See meshtastic#9116 for what sitatuation we are in before this patch.

The point is there is a single valid way to format the file,
and running trunk fmt -a will bring you to it.
Jorropo added a commit to Jorropo/firmware that referenced this pull request Jan 2, 2026
The previous setup would be extremely lenient.
AFAIK would basically only enforce tabulation more or less.

Add a .clang-format file, the point is you can't accidently have whitespace changes.
See meshtastic#9116 for what sitatuation we are in before this patch.

The point is there is a single valid way to format the file,
and running trunk fmt -a will bring you to it.
Jorropo added a commit to Jorropo/firmware that referenced this pull request Jan 3, 2026
The previous setup would be extremely lenient.
AFAIK would basically only enforce tabulation more or less.

Add a .clang-format file, the point is you can't accidently have whitespace changes.
See meshtastic#9116 for what sitatuation we are in before this patch.

The point is there is a single valid way to format the file,
and running trunk fmt -a will bring you to it.
Jorropo added a commit to Jorropo/firmware that referenced this pull request Jan 3, 2026
The previous setup would be extremely lenient.
AFAIK would basically only enforce tabulation more or less.

Add a .clang-format file, the point is you can't accidently have whitespace changes.
See meshtastic#9116 for what sitatuation we are in before this patch.

The point is there is a single valid way to format the file,
and running trunk fmt -a will bring you to it.
@github-actions github-actions Bot added the Stale Issues that will be closed if not triaged. label Feb 25, 2026
@github-actions github-actions Bot closed this Mar 5, 2026
@Jorropo Jorropo reopened this Mar 5, 2026
- Accept upstream for: .gitignore, AdminModule.cpp, sleep.cpp, protobufs
- Keep ours for: Router.cpp (PFS decrypt/encode logic), main.cpp (PFS init)
- Surgical merge for CryptoEngine.cpp: kept clearKeys(), PFS Triple-DH
  functions, and adopted upstream smart pointer (unique_ptr) refactoring
  for aesSetKey and encryptAESCtr
- Remove non-existent Led.h and RAKled.h includes
- Remove ledBlinker() and ledPeriodic (replaced by StatusLEDThread in upstream)
- Restore RadioInterface *rIf = NULL global
- Add missing upstream includes: RadioLibInterface.h, TransmitHistory.h,
  power/PowerHAL.h, MessageStore.h (HAS_SCREEN)
- Add clearKeys() declaration to CryptoEngine.h to match .cpp definition
- Remove ARCH_PORTDUINO guard from RadioLibHAL (now always global)
- Remove duplicate UserButtonThread definition (canonical in InputBroker.cpp)
- Remove duplicate radioType definition (canonical in RadioInterface.cpp)
- Fix router->addInterface() to use unique_ptr<RadioInterface> (upstream API change)
- Capture getPacketTime() before ownership transfer to avoid null dereference
- QMC6310 -> QMC6310U + QMC6310N (enum has two variants, not a bare QMC6310)
- PMSA0031 -> PMSA003I (typo: trailing digit '1' should be letter 'I')
@github-actions github-actions Bot removed the Stale Issues that will be closed if not triaged. label Mar 11, 2026
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.

3 participants