#ifdef ESP_PLATFORM #include #endif #if defined(CONFIG_SOC_WIFI_SUPPORTED) && !defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED) #include #include #include #include #include #include #include #include #include #include #include #include namespace tt::app::chat { static const auto LOGGER = Logger("ChatSettings"); constexpr auto* KEY_SENDER_ID = "senderId"; constexpr auto* KEY_NICKNAME = "nickname"; constexpr auto* KEY_ENCRYPTION_KEY = "encryptionKey"; constexpr auto* KEY_CHAT_CHANNEL = "chatChannel"; // IV_SEED provides basic obfuscation for stored encryption keys, not strong encryption. // The device master key (from crypt::getIv) provides the actual security. static constexpr auto* IV_SEED = "chat_key"; static std::string toHexString(const uint8_t* data, size_t length) { std::stringstream stream; stream << std::hex; for (size_t i = 0; i < length; ++i) { stream << std::setw(2) << std::setfill('0') << static_cast(data[i]); } return stream.str(); } static bool readHex(const std::string& input, uint8_t* buffer, size_t length) { if (input.size() != length * 2) { LOGGER.error("readHex() length mismatch"); return false; } char hex[3] = { 0 }; for (size_t i = 0; i < length; i++) { hex[0] = input[i * 2]; hex[1] = input[i * 2 + 1]; char* endptr; unsigned long val = strtoul(hex, &endptr, 16); if (endptr != hex + 2) { LOGGER.error("readHex() invalid hex character"); return false; } buffer[i] = static_cast(val); } return true; } static bool encryptKey(const uint8_t key[ESP_NOW_KEY_LEN], std::string& hexOutput) { uint8_t iv[16]; crypt::getIv(IV_SEED, std::strlen(IV_SEED), iv); uint8_t encrypted[ESP_NOW_KEY_LEN]; if (crypt::encrypt(iv, key, encrypted, ESP_NOW_KEY_LEN) != 0) { LOGGER.error("Failed to encrypt key"); return false; } hexOutput = toHexString(encrypted, ESP_NOW_KEY_LEN); return true; } static bool decryptKey(const std::string& hexInput, uint8_t key[ESP_NOW_KEY_LEN]) { if (hexInput.size() != ESP_NOW_KEY_LEN * 2) { return false; } uint8_t encrypted[ESP_NOW_KEY_LEN]; if (!readHex(hexInput, encrypted, ESP_NOW_KEY_LEN)) { return false; } uint8_t iv[16]; crypt::getIv(IV_SEED, std::strlen(IV_SEED), iv); if (crypt::decrypt(iv, encrypted, key, ESP_NOW_KEY_LEN) != 0) { LOGGER.error("Failed to decrypt key"); return false; } return true; } /** Generate a non-zero random sender ID using hardware RNG. */ static uint32_t generateSenderId() { uint32_t id; do { id = esp_random(); } while (id == 0); return id; } ChatSettingsData getDefaultSettings() { return ChatSettingsData{ .senderId = 0, .nickname = "Device", .encryptionKey = {}, .hasEncryptionKey = false, .chatChannel = "#general" }; } ChatSettingsData loadSettings() { ChatSettingsData settings = getDefaultSettings(); std::map map; if (!file::loadPropertiesFile(CHAT_SETTINGS_FILE, map)) { settings.senderId = generateSenderId(); return settings; } auto it = map.find(KEY_SENDER_ID); if (it != map.end() && !it->second.empty()) { settings.senderId = static_cast(strtoul(it->second.c_str(), nullptr, 10)); } // Generate sender ID if missing or zero if (settings.senderId == 0) { settings.senderId = generateSenderId(); } it = map.find(KEY_NICKNAME); if (it != map.end() && !it->second.empty()) { settings.nickname = it->second.substr(0, MAX_NICKNAME_LEN); } it = map.find(KEY_ENCRYPTION_KEY); if (it != map.end() && !it->second.empty()) { if (decryptKey(it->second, settings.encryptionKey.data())) { settings.hasEncryptionKey = true; } } it = map.find(KEY_CHAT_CHANNEL); if (it != map.end() && !it->second.empty()) { settings.chatChannel = it->second.substr(0, MAX_TARGET_LEN); } return settings; } bool saveSettings(const ChatSettingsData& settings) { std::map map; map[KEY_SENDER_ID] = std::to_string(settings.senderId); map[KEY_NICKNAME] = settings.nickname; map[KEY_CHAT_CHANNEL] = settings.chatChannel; if (settings.hasEncryptionKey) { std::string encryptedHex; if (!encryptKey(settings.encryptionKey.data(), encryptedHex)) { return false; } map[KEY_ENCRYPTION_KEY] = encryptedHex; } else { map[KEY_ENCRYPTION_KEY] = ""; } return file::savePropertiesFile(CHAT_SETTINGS_FILE, map); } bool settingsFileExists() { return access(CHAT_SETTINGS_FILE, F_OK) == 0; } } // namespace tt::app::chat #endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED