From 6cbbe1500a8cb0bdc062a3fd4ffcef830373c5ef Mon Sep 17 00:00:00 2001 From: Kemal Hadimli Date: Thu, 11 Jun 2026 22:21:54 +0100 Subject: [PATCH] filter channel: accept a raw hex key in addition to base64 A 16/32-byte key is 32/64 hex chars while base64 PSKs are 24/44 chars, so all-hex-and-32/64-long disambiguates cleanly. Widen the stored-PSK buffer to fit a 64-char hex key. Co-Authored-By: Claude Opus 4.8 --- docs/cli_commands.md | 2 +- examples/simple_repeater/MyMesh.cpp | 26 ++++++++++++++++++++------ examples/simple_repeater/MyMesh.h | 6 +++--- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 374899e2c..8f616aab9 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -1144,7 +1144,7 @@ Repeater only. Lets a repeater decrypt channels it holds the key for, inspect th - `filter channel clear` **Parameters:** -- `psk`: Channel pre-shared key in Base64 (16 or 32 bytes when decoded), e.g. the value shared by a MeshCore client. The literal `public` is a shortcut for the well-known public channel key. +- `psk`: Channel pre-shared key, as either Base64 (the form MeshCore clients share, e.g. `izOH6cXN6mrJ5e26oRXNcg==`) or raw hex (`62be0e1267c401381f4ea6f44217d7a9`). Either way it must decode to 16 or 32 bytes. The literal `public` is a shortcut for the well-known public channel key. **Note:** Adding a channel only enables decryption/inspection of that channel; messages still forward normally unless they match a blocked keyword or sender. `filter channel clear` removes all channel keys (the repeater stops decrypting and forwards everything blind again). diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 9e2fb6d89..33cb1b43e 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -36,6 +36,17 @@ static int decodeBase64(const char* in, uint8_t* out, int max_out) { } return n; } + +// A 16/32-byte key in hex is 32/64 chars; base64 PSKs are 24/44 chars, so the +// length is unambiguous as long as every char is a hex digit. +static bool isHexKey(const char* s) { + int n = 0; + for (const char* p = s; *p; p++, n++) { + char c = *p; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) return false; + } + return n == 32 || n == 64; +} #endif // WITH_CHANNEL_FILTER @@ -666,23 +677,26 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { } #ifdef WITH_CHANNEL_FILTER -bool MyMesh::addFilterChannel(const char *psk_b64) { +bool MyMesh::addFilterChannel(const char *psk) { if (num_filter_channels >= MAX_FILTER_CHANNELS) return false; auto ch = &filter_channels[num_filter_channels]; memset(ch->secret, 0, sizeof(ch->secret)); int len; - if (strcmp(psk_b64, "public") == 0) { + if (strcmp(psk, "public") == 0) { memcpy(ch->secret, PUBLIC_CHANNEL_SECRET, sizeof(PUBLIC_CHANNEL_SECRET)); len = sizeof(PUBLIC_CHANNEL_SECRET); + } else if (isHexKey(psk)) { + len = strlen(psk) / 2; + if (!mesh::Utils::fromHex(ch->secret, len, psk)) return false; } else { - len = decodeBase64(psk_b64, ch->secret, sizeof(ch->secret)); + len = decodeBase64(psk, ch->secret, sizeof(ch->secret)); } if (len != 16 && len != 32) return false; mesh::Utils::sha256(ch->hash, sizeof(ch->hash), ch->secret, len); - StrHelper::strncpy(filter_channel_psk[num_filter_channels], psk_b64, FILTER_PSK_B64_LEN); + StrHelper::strncpy(filter_channel_psk[num_filter_channels], psk, FILTER_PSK_LEN); num_filter_channels++; return true; } @@ -767,7 +781,7 @@ void MyMesh::loadChannelFilter() { #endif if (!f) return; - char line[FILTER_PSK_B64_LEN + 8]; + char line[FILTER_PSK_LEN + 8]; while (f.available()) { int n = f.readBytesUntil('\n', (uint8_t *)line, sizeof(line) - 1); line[n] = 0; @@ -868,7 +882,7 @@ void MyMesh::handleFilterCommand(char *command, char *reply) { strcpy(reply, "OK - filter reset"); return; } - strcpy(reply, "Err - usage: filter [list|stats [reset]|channel |block |sender |clear|reset]"); + strcpy(reply, "Err - usage: filter [list|stats [reset]|channel |block |sender |clear|reset]"); } #endif // WITH_CHANNEL_FILTER diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 12d3357d8..97c8cefff 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -90,7 +90,7 @@ struct NeighbourInfo { #define MAX_FILTER_TERMS 8 #endif #define FILTER_TERM_LEN 24 -#define FILTER_PSK_B64_LEN 48 +#define FILTER_PSK_LEN 68 // fits a 64-char hex key, a 44-char base64 PSK, or "public" #endif class MyMesh : public mesh::Mesh, public CommonCLICallbacks { @@ -129,7 +129,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #ifdef WITH_CHANNEL_FILTER mesh::GroupChannel filter_channels[MAX_FILTER_CHANNELS]; - char filter_channel_psk[MAX_FILTER_CHANNELS][FILTER_PSK_B64_LEN]; + char filter_channel_psk[MAX_FILTER_CHANNELS][FILTER_PSK_LEN]; uint8_t num_filter_channels; char block_keywords[MAX_FILTER_TERMS][FILTER_TERM_LEN]; uint8_t num_block_keywords; @@ -155,7 +155,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { bool isLooped(const mesh::Packet* packet, const uint8_t max_counters[]); #ifdef WITH_CHANNEL_FILTER - bool addFilterChannel(const char* psk_b64); + bool addFilterChannel(const char* psk); void loadChannelFilter(); void saveChannelFilter(); void handleFilterCommand(char* command, char* reply);