Files
HaloKeymind/test/test_webconfig_keys/test_webconfig_keys.cpp
T
mikecarper cbfe5f79ea Merge observer firmware branch into keymindCascade
Integrate compatible observer board, preset, WebConfig, display, CI, and policy updates. Preserve the checkout's newer MQTT binary preferences and WiFi bridge runtime where the forked implementations conflict. Keep ThinkNode observer networking WiFi-based until the Ethernet transport is wired into that runtime.
2026-09-24 01:20:56 -07:00

237 lines
9.5 KiB
C++

// Host tests for the WebConfig key allowlist / secret / slot-prefix helpers
// (src/helpers/WebConfigKeys.h). These parse attacker-supplied POST keys, so
// coverage of the length-guard and boundary cases matters for safety.
#include <gtest/gtest.h>
#include "helpers/WebConfigKeys.h"
// ---- allowlist ------------------------------------------------------------
TEST(WebConfigKeys, AllowsKnownScalarKeys) {
EXPECT_TRUE(wcIsAllowedSetKey("name"));
EXPECT_TRUE(wcIsAllowedSetKey("bluetooth.name"));
EXPECT_TRUE(wcIsAllowedSetKey("bluetooth.mac"));
EXPECT_TRUE(wcIsAllowedSetKey("bluetooth.stealth"));
EXPECT_TRUE(wcIsAllowedSetKey("radio"));
EXPECT_TRUE(wcIsAllowedSetKey("radio.rxps"));
EXPECT_TRUE(wcIsAllowedSetKey("powersaving"));
EXPECT_TRUE(wcIsAllowedSetKey("repeat"));
EXPECT_TRUE(wcIsAllowedSetKey("radio.fem.rxgain")); // Board::handleCommand(), not CommonCLI
EXPECT_TRUE(wcIsAllowedSetKey("radio.fem.txgain"));
EXPECT_TRUE(wcIsAllowedSetKey("wifi.ssid"));
EXPECT_TRUE(wcIsAllowedSetKey("espnow.channel"));
EXPECT_TRUE(wcIsAllowedSetKey("mqtt.iata"));
EXPECT_TRUE(wcIsAllowedSetKey("mqtt.neighbors"));
EXPECT_TRUE(wcIsAllowedSetKey("mqtt.neighbors.interval"));
EXPECT_TRUE(wcIsAllowedSetKey("snmp.community"));
EXPECT_TRUE(wcIsAllowedSetKey("timezone.offset"));
}
TEST(WebConfigKeys, AllowsPerSlotKeys) {
EXPECT_TRUE(wcIsAllowedSetKey("mqtt1.preset"));
EXPECT_TRUE(wcIsAllowedSetKey("mqtt1.server"));
EXPECT_TRUE(wcIsAllowedSetKey("mqtt1.token"));
EXPECT_TRUE(wcIsAllowedSetKey("mqtt2.filter"));
EXPECT_TRUE(wcIsAllowedSetKey("mqtt6.audience")); // MAX_MQTT_SLOTS == 6
}
TEST(WebConfigKeys, RejectsDangerousOrUnknownKeys) {
EXPECT_FALSE(wcIsAllowedSetKey("erase"));
EXPECT_FALSE(wcIsAllowedSetKey("password"));
EXPECT_FALSE(wcIsAllowedSetKey("reboot"));
EXPECT_FALSE(wcIsAllowedSetKey("bogus"));
EXPECT_FALSE(wcIsAllowedSetKey("bluetooth.stealth.extra"));
EXPECT_FALSE(wcIsAllowedSetKey("mqtt1.bogus")); // unknown slot field
EXPECT_FALSE(wcIsAllowedSetKey(""));
}
TEST(WebConfigKeys, AdminPasswordIsNotAnAllowlistedSetKey) {
EXPECT_TRUE(wcIsAdminPasswordKey("password"));
EXPECT_FALSE(wcIsAdminPasswordKey("admin.password"));
EXPECT_FALSE(wcIsAdminPasswordKey(""));
EXPECT_FALSE(wcIsAllowedSetKey("password")); // never reachable as `set password`
}
TEST(WebConfigKeys, AdminPasswordFitsNodePrefsAndRejectsLineBreaks) {
EXPECT_FALSE(wcIsValidAdminPassword(NULL));
EXPECT_FALSE(wcIsValidAdminPassword(""));
EXPECT_TRUE(wcIsValidAdminPassword("new-password"));
EXPECT_TRUE(wcIsValidAdminPassword("123456789012345"));
EXPECT_FALSE(wcIsValidAdminPassword("1234567890123456"));
EXPECT_FALSE(wcIsValidAdminPassword("line\nbreak"));
EXPECT_FALSE(wcIsValidAdminPassword("line\rbreak"));
}
TEST(WebConfigKeys, SlotIndexBoundsMatchMaxSlots) {
EXPECT_FALSE(wcIsAllowedSetKey("mqtt0.preset")); // slot 0 invalid
EXPECT_TRUE(wcIsAllowedSetKey("mqtt6.preset")); // last valid slot
EXPECT_FALSE(wcIsAllowedSetKey("mqtt7.preset")); // beyond MAX_MQTT_SLOTS
EXPECT_FALSE(wcIsAllowedSetKey("mqtt9.preset"));
}
TEST(WebConfigKeys, IsCaseSensitive) {
EXPECT_FALSE(wcIsAllowedSetKey("Name"));
EXPECT_FALSE(wcIsAllowedSetKey("MQTT1.preset"));
}
TEST(WebConfigKeys, AddressChangingKeysAlwaysRequireAReboot) {
EXPECT_TRUE(wcSetKeyRequiresReboot("espnow.channel"));
EXPECT_TRUE(wcSetKeyRequiresReboot("bluetooth.mac"));
EXPECT_TRUE(wcSetKeyRequiresReboot("bluetooth.stealth"));
EXPECT_FALSE(wcSetKeyRequiresReboot("bluetooth.stealth.extra"));
EXPECT_FALSE(wcSetKeyRequiresReboot(NULL));
EXPECT_FALSE(wcSetKeyRequiresReboot("espnow.channel.extra"));
EXPECT_FALSE(wcSetKeyRequiresReboot("ESPNOW.channel"));
EXPECT_FALSE(wcSetKeyRequiresReboot("wifi.ssid"));
EXPECT_FALSE(wcSetKeyRequiresReboot(""));
}
TEST(WebConfigKeys, EspNowChannelClassificationIsNarrow) {
EXPECT_TRUE(wcIsEspNowChannelKey("espnow.channel"));
EXPECT_FALSE(wcIsEspNowChannelKey("bluetooth.mac"));
EXPECT_FALSE(wcIsEspNowChannelKey("espnow.channel.extra"));
EXPECT_FALSE(wcIsEspNowChannelKey(NULL));
}
// ---- short-key OOB guard --------------------------------------------------
// The slot-prefix probe indexes key[4..6]; these short strings must be rejected
// without ever reading past the terminator.
TEST(WebConfigKeys, ShortKeysRejectedSafely) {
EXPECT_FALSE(wcIsSlotKeyPrefix(""));
EXPECT_FALSE(wcIsSlotKeyPrefix("m"));
EXPECT_FALSE(wcIsSlotKeyPrefix("mq"));
EXPECT_FALSE(wcIsSlotKeyPrefix("mqt"));
EXPECT_FALSE(wcIsSlotKeyPrefix("mqtt")); // 4 chars -- no digit/dot
EXPECT_FALSE(wcIsSlotKeyPrefix("mqtt1")); // 5 chars -- no dot
EXPECT_FALSE(wcIsSlotKeyPrefix("mqtt1.")); // 6 chars -- no field char
EXPECT_TRUE(wcIsSlotKeyPrefix("mqtt1.x")); // 7 chars -- minimum valid
// Same guard via the public allowlist/secret entry points:
EXPECT_FALSE(wcIsAllowedSetKey("mqtt"));
EXPECT_FALSE(wcIsSecretKey("m"));
EXPECT_FALSE(wcIsSecretKey("mqtt"));
}
TEST(WebConfigKeys, SlotPrefixDigitRange) {
EXPECT_FALSE(wcIsSlotKeyPrefix("mqtt0.x"));
EXPECT_TRUE(wcIsSlotKeyPrefix("mqtt6.x"));
EXPECT_FALSE(wcIsSlotKeyPrefix("mqtt7.x"));
EXPECT_FALSE(wcIsSlotKeyPrefix("mqttA.x")); // non-digit
}
// ---- secret classification ------------------------------------------------
TEST(WebConfigKeys, SecretKeysDetected) {
EXPECT_TRUE(wcIsSecretKey("wifi.pwd"));
EXPECT_TRUE(wcIsSecretKey("mqtt1.password"));
EXPECT_TRUE(wcIsSecretKey("mqtt3.token"));
EXPECT_TRUE(wcIsSecretKey("mqtt6.password"));
}
TEST(WebConfigKeys, NonSecretKeysNotFlagged) {
EXPECT_FALSE(wcIsSecretKey("wifi.ssid"));
EXPECT_FALSE(wcIsSecretKey("mqtt1.username")); // username is not masked
EXPECT_FALSE(wcIsSecretKey("mqtt1.server"));
EXPECT_FALSE(wcIsSecretKey("mqtt1.filter"));
EXPECT_FALSE(wcIsSecretKey("mqtt.origin"));
EXPECT_FALSE(wcIsSecretKey("name"));
EXPECT_FALSE(wcIsSecretKey(""));
}
TEST(WebConfigKeys, EverySecretKeyIsAlsoAllowed) {
// A secret key must be one the portal can actually set, or the masking is moot.
const char* secrets[] = {"wifi.pwd", "mqtt1.password", "mqtt1.token",
"mqtt6.password", "mqtt6.token"};
for (const char* k : secrets) {
EXPECT_TRUE(wcIsSecretKey(k)) << k;
EXPECT_TRUE(wcIsAllowedSetKey(k)) << k;
}
}
// ---- CLI secret reads ----------------------------------------------------
// The web CLI runs commands with sender_timestamp 0, which is how CommonCLI
// recognises the serial console and answers secret getters in plaintext. These
// are the reads that must be masked back down for an HTTP caller.
TEST(WebConfigKeys, ClassifiesEverySecretReadTheCliCanReach) {
const char* masked[] = {
"get password",
"get prv.key", // this node's identity -- the worst one to leak
"get wifi.pwd", // grants the operator's LAN, not just the node
"get guest.password",
"get alert.psk",
"get bridge.secret",
"get mqtt1.password", "get mqtt1.token",
"get mqtt6.password", "get mqtt6.token",
"get wifi.pwd", // extra space after the verb
};
for (const char* c : masked) EXPECT_TRUE(wcIsSecretReadCommand(c)) << c;
}
TEST(WebConfigKeys, DoesNotClassifyPublicReadsAsSecrets) {
const char* plain[] = {
"get wifi.ssid", "get mqtt1.username", "get mqtt1.server", "get tx",
"get public.key", // public half, safe to read
"get mqtt.owner", // an owner's public key, not a credential
};
for (const char* c : plain) EXPECT_FALSE(wcIsSecretReadCommand(c)) << c;
}
TEST(WebConfigKeys, OnlyMasksReads) {
// Writing a secret has always been the portal's job and reveals nothing;
// only the read is restricted. Nor may a prefix be mistaken for a `get`.
EXPECT_FALSE(wcIsSecretReadCommand("set wifi.pwd hunter2"));
EXPECT_FALSE(wcIsSecretReadCommand("set prv.key aabb"));
EXPECT_FALSE(wcIsSecretReadCommand("password hunter2"));
EXPECT_FALSE(wcIsSecretReadCommand("getwifi.pwd"));
EXPECT_FALSE(wcIsSecretReadCommand("get"));
EXPECT_FALSE(wcIsSecretReadCommand(""));
}
// ---- request correlation -------------------------------------------------
TEST(WebConfigKeys, AcceptsExactHexRequestIds) {
EXPECT_TRUE(wcIsValidReqId("0123456789abcdef"));
EXPECT_TRUE(wcIsValidReqId("ABCDEF0123456789"));
}
TEST(WebConfigKeys, RejectsMissingMalformedOrWrongLengthRequestIds) {
EXPECT_FALSE(wcIsValidReqId(NULL));
EXPECT_FALSE(wcIsValidReqId(""));
EXPECT_FALSE(wcIsValidReqId("0123456789abcde"));
EXPECT_FALSE(wcIsValidReqId("0123456789abcdef0"));
EXPECT_FALSE(wcIsValidReqId("0123456789abcdeg"));
EXPECT_FALSE(wcIsValidReqId("01234567-9abcdef"));
EXPECT_FALSE(wcIsValidReqId("01234567 9abcdef"));
}
// ---- browser terminal commands -------------------------------------------
TEST(WebConfigKeys, AcceptsSingleLineCliCommands) {
EXPECT_TRUE(wcIsValidCliCommand("get wifi.status"));
EXPECT_TRUE(wcIsValidCliCommand("set flood.max 12"));
EXPECT_TRUE(wcIsValidCliCommand(" neighbors "));
}
TEST(WebConfigKeys, RejectsBlankMultilineOrOversizedCliCommands) {
EXPECT_FALSE(wcIsValidCliCommand(NULL));
EXPECT_FALSE(wcIsValidCliCommand(""));
EXPECT_FALSE(wcIsValidCliCommand(" \t"));
EXPECT_FALSE(wcIsValidCliCommand("get role\nreboot"));
EXPECT_FALSE(wcIsValidCliCommand("get role\rreboot"));
char max_command[160];
memset(max_command, 'x', sizeof(max_command) - 1);
max_command[sizeof(max_command) - 1] = 0;
EXPECT_TRUE(wcIsValidCliCommand(max_command));
char oversized[161];
memset(oversized, 'x', sizeof(oversized) - 1);
oversized[sizeof(oversized) - 1] = 0;
EXPECT_FALSE(wcIsValidCliCommand(oversized));
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}