Skip to content

security: use constant-time comparison for key/hash/token checks#9600

Open
weebl2000 wants to merge 1 commit into
meshtastic:developfrom
weebl2000:fix/constant-time-comparison
Open

security: use constant-time comparison for key/hash/token checks#9600
weebl2000 wants to merge 1 commit into
meshtastic:developfrom
weebl2000:fix/constant-time-comparison

Conversation

@weebl2000

@weebl2000 weebl2000 commented Feb 11, 2026

Copy link
Copy Markdown
Contributor

I don't think it's easily exploited in the field, but hey why not harden it? :)

Summary

  • Extracts constant_time_compare() from aes-ccm.cpp (where it was static) into shared meshUtils.h/.cpp
  • Replaces memcmp with constant_time_compare in all security-sensitive comparison paths
  • Prevents timing side-channel attacks that could leak key material byte-by-byte

Affected paths (10 security-sensitive memcmp calls replaced):

Location What's compared Risk
AdminModule.cpp:98-102 PKI admin key authorization (3 keys) Critical
AdminModule.cpp:1441 Session passkey (8 bytes) High
KeyVerificationModule.cpp:84 hash1 verification High
KeyVerificationModule.cpp:243 hash2 verification High
Router.cpp:637 PKI message public key Medium
MQTT.cpp:759-760 Channel PSK vs defaults Medium
NodeDB.cpp:1760 Contact public key update Medium
NodeDB.cpp:1823 Duplicate public key detection Medium
NodeDB.cpp:1841 Public key consistency check Medium

Background

MeshCore's security audit (PR #1656) found their HMAC verification used memcmp(), enabling timing attacks. The same pattern exists more broadly in Meshtastic — the constant_time_compare function was already correctly used in aes-ccm.cpp for AES-CCM tag verification, but nowhere else.

The admin key comparison is the most critical: an attacker on BLE/WiFi/serial could time 32-byte public key comparisons to determine admin keys byte-by-byte.

Test plan

  • Verify tbeam build succeeds
  • Verify admin key authentication still works
  • Verify key verification flow still works
  • Verify PKI encryption/decryption still works
  • Verify MQTT uplink filtering still works

@github-actions github-actions Bot added needs-review Needs human review enhancement New feature or request labels Feb 11, 2026
@thebentern thebentern requested review from GUVWAF, Copilot and jp-bennett and removed request for jp-bennett February 11, 2026 12:26
@weebl2000 weebl2000 force-pushed the fix/constant-time-comparison branch from 623acbc to 12131b0 Compare February 11, 2026 12:49
@weebl2000 weebl2000 force-pushed the fix/constant-time-comparison branch from 12131b0 to 62a1cd0 Compare February 11, 2026 12:56

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comment thread src/mqtt/MQTT.cpp Outdated
Comment on lines +761 to +762
(ch.settings.psk.size == 16 && constant_time_compare(ch.settings.psk.bytes, defaultpsk, 16)) ||
(ch.settings.psk.size == 32 && constant_time_compare(ch.settings.psk.bytes, eventpsk, 32)))) {

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

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

The constant_time_compare function returns 0 when arrays are equal and -1 when different. Using the return value directly in a boolean context (without comparing to 0) will evaluate to TRUE when arrays are DIFFERENT, not when they're equal. This appears to be a pre-existing logic bug from the original memcmp code.

The intent of this code is to block MQTT forwarding when the channel is using one of the default/event PSKs. To fix this, the comparisons should be:

  • constant_time_compare(ch.settings.psk.bytes, defaultpsk, 16) == 0
  • constant_time_compare(ch.settings.psk.bytes, eventpsk, 32) == 0

This ensures the condition is TRUE when the PSK matches the default (i.e., when forwarding should be blocked), not when it's different.

Suggested change
(ch.settings.psk.size == 16 && constant_time_compare(ch.settings.psk.bytes, defaultpsk, 16)) ||
(ch.settings.psk.size == 32 && constant_time_compare(ch.settings.psk.bytes, eventpsk, 32)))) {
(ch.settings.psk.size == 16 && constant_time_compare(ch.settings.psk.bytes, defaultpsk, 16) == 0) ||
(ch.settings.psk.size == 32 && constant_time_compare(ch.settings.psk.bytes, eventpsk, 32) == 0))) {

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Interesting.

@CLAassistant

CLAassistant commented Mar 8, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Replace memcmp with constant_time_compare in security-sensitive paths
to prevent timing side-channel attacks. The function already existed in
aes-ccm.cpp for MAC verification but was static and unused elsewhere.

Moved to meshUtils so it can be shared across:
- AdminModule: PKI admin key verification and session passkey check
- KeyVerificationModule: hash1/hash2 verification
- Router: PKI public key verification
- MQTT: channel PSK comparison
- NodeDB: public key identity checks
@weebl2000 weebl2000 force-pushed the fix/constant-time-comparison branch from 5462a12 to bfed0b6 Compare March 9, 2026 09:17
@github-actions github-actions Bot added the Stale Issues that will be closed if not triaged. label May 15, 2026
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 Stale Issues that will be closed if not triaged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants