Skip to content

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

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

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

Conversation

@weebl2000
Copy link
Contributor

@weebl2000 weebl2000 commented Feb 11, 2026

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
Copy link
Contributor

Copilot AI left a comment

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.

Copy link
Contributor

Copilot AI left a comment

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 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)))) {
Copy link

Copilot AI Feb 12, 2026

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
Contributor Author

Choose a reason for hiding this comment

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

Interesting.

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 adb7dad to 788689c Compare February 13, 2026 10:24
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.

1 participant