Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/mesh/CryptoEngine.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#include "CryptoEngine.h"
// #include "NodeDB.h"
#include "architecture.h"
#ifdef MESHTASTIC_CHANNEL_HMAC
#include "meshUtils.h"
#endif

#if !(MESHTASTIC_EXCLUDE_PKI)
#include "NodeDB.h"
Expand Down Expand Up @@ -261,6 +264,87 @@ void CryptoEngine::initNonce(uint32_t fromNode, uint64_t packetId, uint32_t extr
if (extraNonce)
memcpy(nonce + sizeof(uint32_t), &extraNonce, sizeof(uint32_t));
}

#ifdef MESHTASTIC_CHANNEL_HMAC
#include "meshUtils.h" // for constant_time_compare

/**
* Compute a 4-byte AES-CBC-MAC over (nonce || ciphertext) using the channel key.
* This provides integrity/authenticity for channel-encrypted packets.
*/
static void computeCBCMAC(const CryptoKey &macKey, const uint8_t *macNonce, const uint8_t *data, size_t dataLen, uint8_t *mac)
{
uint8_t tag[16], tmp[16];

// Start with nonce as IV
memcpy(tag, macNonce, 16);

// Use AES256 for MAC regardless of key length (zero-pad short keys)
AESSmall256 macCipher;
uint8_t paddedKey[32] = {0};
memcpy(paddedKey, macKey.bytes, macKey.length);
macCipher.setKey(paddedKey, 32);

// Encrypt the nonce-IV as first CBC-MAC block
macCipher.encryptBlock(tmp, tag);
memcpy(tag, tmp, 16);

// Process ciphertext in 16-byte blocks
for (size_t i = 0; i < dataLen; i += 16) {
size_t blockLen = (dataLen - i < 16) ? dataLen - i : 16;
for (size_t j = 0; j < blockLen; j++)
tag[j] ^= data[i + j];
macCipher.encryptBlock(tmp, tag);
memcpy(tag, tmp, 16);
}

memcpy(mac, tag, CHANNEL_HMAC_SIZE);
}

size_t CryptoEngine::encryptPacketWithMAC(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes)
{
if (key.length > 0 && numBytes + CHANNEL_HMAC_SIZE <= MAX_BLOCKSIZE) {
initNonce(fromNode, packetId);
encryptAESCtr(key, nonce, numBytes, bytes);

// Compute MAC over the ciphertext and append it
uint8_t mac[CHANNEL_HMAC_SIZE];
computeCBCMAC(key, nonce, bytes, numBytes, mac);
memcpy(bytes + numBytes, mac, CHANNEL_HMAC_SIZE);
return numBytes + CHANNEL_HMAC_SIZE;
}

// Fallback: packet too large for MAC, encrypt without
encryptPacket(fromNode, packetId, numBytes, bytes);
return numBytes;
}

size_t CryptoEngine::decryptWithMAC(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes)
{
if (key.length > 0 && numBytes > CHANNEL_HMAC_SIZE) {
initNonce(fromNode, packetId);

// Try verifying MAC on last CHANNEL_HMAC_SIZE bytes
size_t cipherLen = numBytes - CHANNEL_HMAC_SIZE;
uint8_t computedMAC[CHANNEL_HMAC_SIZE];
computeCBCMAC(key, nonce, bytes, cipherLen, computedMAC);

if (constant_time_compare(computedMAC, bytes + cipherLen, CHANNEL_HMAC_SIZE) == 0) {
// MAC valid: decrypt only the ciphertext portion
encryptAESCtr(key, nonce, cipherLen, bytes);
return cipherLen;
}

// MAC invalid: legacy packet without MAC, decrypt all bytes
encryptAESCtr(key, nonce, numBytes, bytes);
return numBytes;
}

decrypt(fromNode, packetId, numBytes, bytes);
return numBytes;
}
#endif

#ifndef HAS_CUSTOM_CRYPTO_ENGINE
CryptoEngine *crypto = new CryptoEngine;
#endif
24 changes: 24 additions & 0 deletions src/mesh/CryptoEngine.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
#pragma once

// Uncomment to enable authenticated channel encryption (AES-CTR + CBC-MAC).
// When enabled, a 4-byte MAC is appended to channel-encrypted packets.
// Receive path is backward-compatible: falls back to legacy decryption if no valid MAC.
// WARNING: Send path is NOT backward-compatible with firmware that lacks this feature.
// #define MESHTASTIC_CHANNEL_HMAC

#include "AES.h"
#include "CTR.h"
#include "concurrency/LockGuard.h"
Expand Down Expand Up @@ -71,6 +78,23 @@ class CryptoEngine
virtual void encryptPacket(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes);
virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes);
virtual void encryptAESCtr(CryptoKey key, uint8_t *nonce, size_t numBytes, uint8_t *bytes);

#ifdef MESHTASTIC_CHANNEL_HMAC
#define CHANNEL_HMAC_SIZE 4
/**
* Encrypt a packet and append a 4-byte AES-CBC-MAC for integrity.
* Returns the total output size (numBytes + CHANNEL_HMAC_SIZE).
*/
size_t encryptPacketWithMAC(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes);

/**
* Verify MAC and decrypt. If MAC is valid, decrypts (numBytes - HMAC_SIZE) bytes.
* If MAC is missing/invalid, falls back to legacy full-buffer decryption.
* Returns the decrypted payload size.
*/
size_t decryptWithMAC(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes);
#endif

#ifndef PIO_UNIT_TESTING
protected:
#endif
Expand Down
23 changes: 21 additions & 2 deletions src/mesh/Router.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,14 +473,20 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
// fresh copy for each decrypt attempt.
memcpy(bytes, p->encrypted.bytes, rawSize);
// Try to decrypt the packet if we can
#ifdef MESHTASTIC_CHANNEL_HMAC
// Verify MAC if present, then decrypt. Falls back to legacy if no valid MAC.
size_t decryptedSize = crypto->decryptWithMAC(p->from, p->id, rawSize, bytes);
#else
size_t decryptedSize = rawSize;
crypto->decrypt(p->from, p->id, rawSize, bytes);
#endif

// printBytes("plaintext", bytes, p->encrypted.size);

// Take those raw bytes and convert them back into a well structured protobuf we can understand
meshtastic_Data decodedtmp;
memset(&decodedtmp, 0, sizeof(decodedtmp));
if (!pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp)) {
if (!pb_decode_from_bytes(bytes, decryptedSize, &meshtastic_Data_msg, &decodedtmp)) {
LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!", p->id);
} else if (decodedtmp.portnum == meshtastic_PortNum_UNKNOWN_APP) {
LOG_ERROR("Invalid portnum (bad psk?)!");
Expand Down Expand Up @@ -597,7 +603,11 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
}
} */

if (numbytes + MESHTASTIC_HEADER_LENGTH > MAX_LORA_PAYLOAD_LEN)
size_t channelOverhead = 0;
#ifdef MESHTASTIC_CHANNEL_HMAC
channelOverhead = CHANNEL_HMAC_SIZE;
#endif
if (numbytes + MESHTASTIC_HEADER_LENGTH + channelOverhead > MAX_LORA_PAYLOAD_LEN)
return meshtastic_Routing_Error_TOO_LARGE;

// printBytes("plaintext", bytes, numbytes);
Expand Down Expand Up @@ -656,7 +666,12 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
// No suitable channel could be found for
return meshtastic_Routing_Error_NO_CHANNEL;
}

#ifdef MESHTASTIC_CHANNEL_HMAC
numbytes = crypto->encryptPacketWithMAC(getFrom(p), p->id, numbytes, bytes);
#else
crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes);
#endif
memcpy(p->encrypted.bytes, bytes, numbytes);
}
#else
Expand All @@ -672,7 +687,11 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
// No suitable channel could be found for
return meshtastic_Routing_Error_NO_CHANNEL;
}
#ifdef MESHTASTIC_CHANNEL_HMAC
numbytes = crypto->encryptPacketWithMAC(getFrom(p), p->id, numbytes, bytes);
#else
crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes);
#endif
memcpy(p->encrypted.bytes, bytes, numbytes);
#endif

Expand Down
16 changes: 16 additions & 0 deletions src/meshUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ char *strnstr(const char *s, const char *find, size_t slen)
return ((char *)s);
}

int constant_time_compare(const void *a_, const void *b_, size_t len)
{
const volatile uint8_t *volatile a = (const volatile uint8_t *volatile)a_;
const volatile uint8_t *volatile b = (const volatile uint8_t *volatile)b_;
if (len == 0)
return 0;
if (a == NULL || b == NULL)
return -1;
size_t i;
volatile uint8_t d = 0U;
for (i = 0U; i < len; i++) {
d |= (a[i] ^ b[i]);
}
return (1 & ((d - 1) >> 8)) - 1;
}

void printBytes(const char *label, const uint8_t *p, size_t numbytes)
{
int labelSize = strlen(label);
Expand Down
4 changes: 4 additions & 0 deletions src/meshUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ char *strnstr(const char *s, const char *find, size_t slen);

void printBytes(const char *label, const uint8_t *p, size_t numbytes);

/// Constant-time comparison of two byte arrays. Returns 0 if equal, -1 if different.
/// Must be used for all security-sensitive comparisons (keys, MACs, hashes, tokens).
int constant_time_compare(const void *a, const void *b, size_t len);

// is the memory region filled with a single character?
bool memfll(const uint8_t *mem, uint8_t find, size_t numbytes);

Expand Down