From a865d0a3030957d54a304f8292ee475c72962f91 Mon Sep 17 00:00:00 2001 From: agessaman Date: Tue, 12 May 2026 21:05:56 -0700 Subject: [PATCH] Update alert PSK handling to accept both base64 and hex formats Enhanced the CLI command for setting the alert PSK to support both base64 (24/44 chars) and hex (32/64 chars) formats. This change allows for greater flexibility in key input, accommodating the mobile app's "share" output. Updated error messages for clarity and ensured that previously derived hashtags are cleared when a new PSK is set. Relevant adjustments made in CommonCLI and MQTT implementation documentation. --- MQTT_IMPLEMENTATION.md | 6 ++-- src/helpers/CommonCLI.cpp | 63 +++++++++++++++++++++++++++++---------- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/MQTT_IMPLEMENTATION.md b/MQTT_IMPLEMENTATION.md index 159d6fab..7c1d7880 100644 --- a/MQTT_IMPLEMENTATION.md +++ b/MQTT_IMPLEMENTATION.md @@ -714,7 +714,7 @@ A "recovered" message is sent once when the underlying connection comes back. Af | Setting | Default | Notes | |---------|---------|-------| | `alert` | `off` | Master enable for automatic fault alerts | -| `alert.psk` | *(unset)* | Private base64 PSK (24 or 44 chars). The active channel key. | +| `alert.psk` | *(unset)* | Private channel secret. Accepts either base64 (24 or 44 chars, like `BaseChatMesh::addChannel`) or hex (32 or 64 chars, like the mobile app's "Share Channel" output). Stored internally as base64. | | `alert.hashtag` | *(unset)* | Informational only; set via `set alert.hashtag` to pre-derive `alert.psk` from `sha256("#name")[0..15]`. Cleared when `alert.psk` is set directly. | | `alert.region` | *(unset)* | Optional region name; overrides the repeater's `default_scope` for alert sends only. Empty = use `default_scope`. Looked up lazily via `RegionMap`; unknown names silently fall back to `default_scope`. | | `alert.wifi` | `30` (min) | 0 disables WiFi alerts | @@ -734,7 +734,7 @@ Get: Set: - `set alert on` / `set alert off` -- `set alert.psk ` — 24-char (16-byte) or 44-char (32-byte) base64; rejects banned channels (Public, `#test`, `#bot`). Clears `alert.hashtag` since the new key is operator-supplied. +- `set alert.psk ` — 24-/44-char base64 **or** 32-/64-char hex (16- or 32-byte secret); rejects banned channels (Public, `#test`, `#bot`). Clears `alert.hashtag` since the new key is operator-supplied. The mobile app's "share" output is hex; either format is accepted. - `set alert.psk` (no argument) — clears both `alert.psk` and `alert.hashtag` - `set alert.hashtag ` — derives the 16-byte key from `sha256("#name")` *once*, stores it as `alert.psk`, and remembers the hashtag for `get alert.hashtag`. `#` prefix is added if omitted (so `alerts` and `#alerts` are equivalent). Refuses banned hashtag names. - `set alert.hashtag` (no argument) — clears both `alert.psk` and `alert.hashtag` @@ -765,7 +765,7 @@ Anyone running a companion app and subscribed to the `#ops-alerts` hashtag chann Generate a 16-byte random PSK and base64-encode it (24 chars), or use the companion app's "Add channel" feature to create one and copy the secret. Then: ```bash -set alert.psk +set alert.psk # base64 (24/44 chars) or hex (32/64 chars) — mobile "share" works as-is set alert.wifi 10 set alert.mqtt 60 set alert on diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 32417a6f..b327a3bc 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -1631,23 +1631,54 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep strcpy(reply, "Error: PSK too long (max 47 chars)"); } else if (val[0] == '#') { strcpy(reply, "Error: use 'set alert.hashtag' for hashtag channels"); - } else if (len != 24 && len != 44) { - // Quick character-count check before storing; AlertReporter will redo the - // full base64 decode and reject anything that doesn't yield 16 or 32 bytes. - strcpy(reply, "Error: PSK must be 24 chars (16-byte) or 44 chars (32-byte) base64"); - } else if (const char* banned = alertReporterBannedChannelMatchB64(val)) { - // Refuse any key on the banned channel list (Public PSK, well-known - // auto-responder hashtags like #test/#bot, etc.). Fault alerts on those - // channels would spam every node in the area. - sprintf(reply, "Error: refusing banned channel '%s'; pick a private key or hashtag", banned); } else { - StrHelper::strncpy(_prefs->alert_psk_b64, val, sizeof(_prefs->alert_psk_b64)); - // The new PSK is operator-supplied, so any previously-derived hashtag - // name is no longer accurate provenance — drop it. - _prefs->alert_hashtag[0] = '\0'; - savePrefs(); - _callbacks->onAlertConfigChanged(); - strcpy(reply, "OK - alert.psk updated"); + // Accept either: + // - base64: 24 chars (16-byte secret) or 44 chars (32-byte secret) — + // the format `BaseChatMesh::addChannel` already expects. + // - hex: 32 chars (16-byte secret) or 64 chars (32-byte secret) — + // what the MeshCore mobile app's "share" button emits for both + // private channels and hashtag channels. + // Hex input is converted to base64 once at CLI time so the on-disk + // representation (and AlertReporter's decode path) stays unchanged. + char canonical_b64[48]; + const char* store = nullptr; + + if (len == 32 || len == 64) { + bool all_hex = true; + for (size_t i = 0; i < len; i++) { + if (!mesh::Utils::isHexChar(val[i])) { all_hex = false; break; } + } + if (all_hex) { + uint8_t raw[32]; + int raw_len = (int)(len / 2); + if (mesh::Utils::fromHex(raw, raw_len, val)) { + size_t b64_len = alert_encode_base64(raw, (size_t)raw_len, canonical_b64, sizeof(canonical_b64)); + if (b64_len > 0 && b64_len < sizeof(_prefs->alert_psk_b64)) { + store = canonical_b64; + } + } + } + } + if (!store && (len == 24 || len == 44)) { + store = val; + } + + if (!store) { + strcpy(reply, "Error: PSK must be 32/64 hex chars or 24/44 chars base64 (16- or 32-byte secret)"); + } else if (const char* banned = alertReporterBannedChannelMatchB64(store)) { + // Refuse any key on the banned channel list (Public PSK, well-known + // auto-responder hashtags like #test/#bot, etc.). Fault alerts on + // those channels would spam every node in the area. + sprintf(reply, "Error: refusing banned channel '%s'; pick a private key or hashtag", banned); + } else { + StrHelper::strncpy(_prefs->alert_psk_b64, store, sizeof(_prefs->alert_psk_b64)); + // The new PSK is operator-supplied, so any previously-derived hashtag + // name is no longer accurate provenance — drop it. + _prefs->alert_hashtag[0] = '\0'; + savePrefs(); + _callbacks->onAlertConfigChanged(); + strcpy(reply, "OK - alert.psk updated"); + } } } else if (memcmp(config, "alert.hashtag", 13) == 0 && (config[13] == 0 || config[13] == ' ')) { const char* val = (config[13] == ' ') ? &config[14] : "";