Compare commits

...

6 Commits

Author SHA1 Message Date
Christian Schwinne
82073e6bc2 Functional nonce implemetation 2024-11-17 01:09:36 +01:00
Christian Schwinne
fd624aa94b HMAC fixes 2024-11-16 23:41:31 +01:00
Christian Schwinne
f3429a6c93 First successful HMAC verification 2024-10-21 00:06:01 +02:00
Christian Schwinne
be997aa755 More HMAC functionality 2024-10-20 23:06:03 +02:00
Christian Schwinne
b02bc29d29 UI communication progress 2024-10-13 22:48:20 +02:00
Christian Schwinne
af1ee61ba7 Initial HMAC calculation 2024-10-12 13:36:38 +02:00
11 changed files with 518 additions and 25 deletions

View File

@@ -141,6 +141,7 @@ lib_deps =
makuna/NeoPixelBus @ 2.8.0
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1
https://github.com/Aircoookie/arduino-crypto.git
# for I2C interface
;Wire
# ESP-NOW library

View File

@@ -10,6 +10,9 @@
</p>
> [!CAUTION]
> This branch is actively used for research purposes. **Please do not push** to it.
# Welcome to my project WLED! ✨
A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102!

View File

@@ -421,6 +421,8 @@
#define SEG_CAPABILITY_W 0x02
#define SEG_CAPABILITY_CCT 0x04
#define SESSION_ID_SIZE 16
// WLED Error modes
#define ERR_NONE 0 // All good :)
#define ERR_DENIED 1 // Permission denied
@@ -438,6 +440,11 @@
#define ERR_OVERTEMP 30 // An attached temperature sensor has measured above threshold temperature (not implemented)
#define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented)
#define ERR_UNDERVOLT 32 // An attached voltmeter has measured a voltage below the threshold (not implemented)
#define ERR_NONCE 40 // Invalid nonce
#define ERR_REPLAY 41 // Replay attack detected
#define ERR_HMAC 42 // HMAC verification failed
#define ERR_HMAC_MISS 43 // HMAC missing
#define ERR_HMAC_GEN 44 // HMAC handling error
// Timer mode types
#define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness

320
wled00/crypto.cpp Normal file
View File

@@ -0,0 +1,320 @@
#include <Crypto.h>
#include "wled.h"
#define HMAC_KEY_SIZE 32
#define MAX_SESSION_IDS 8
void printByteArray(const byte* arr, size_t len) {
for (size_t i = 0; i < len; i++) {
Serial.print(arr[i], HEX);
}
Serial.println();
}
struct Nonce {
byte sessionId[SESSION_ID_SIZE];
uint32_t counter;
};
Nonce knownSessions[MAX_SESSION_IDS] = {};
void moveToFirst(uint32_t i) {
if (i >= MAX_SESSION_IDS) return;
Nonce tmp = knownSessions[i];
for (int j = i; j > 0; j--) {
knownSessions[j] = knownSessions[j - 1];
}
knownSessions[0] = tmp;
}
uint8_t verifyNonce(const byte* sid, uint32_t counter) {
Serial.println(F("check sid"));
printByteArray(sid, SESSION_ID_SIZE);
uint32_t sum = 0;
for (size_t i = 0; i < SESSION_ID_SIZE; i++) {
sum += sid[i];
}
if (sum == 0) { // all-zero session ID is invalid as it is used for uninitialized entries
return ERR_NONCE;
}
for (int i = 0; i < MAX_SESSION_IDS; i++) {
if (memcmp(knownSessions[i].sessionId, sid, SESSION_ID_SIZE) == 0) {
Serial.print(F("Session ID matches e"));
Serial.println(i);
if (counter <= knownSessions[i].counter) {
Serial.println(F("Retransmission detected!"));
return ERR_REPLAY;
}
knownSessions[i].counter = counter;
// nonce good, move this entry to the first position of knownSessions
moveToFirst(i);
return ERR_NONE;
}
}
Serial.println(F("Unknown session ID!"));
return ERR_NONCE;
}
void addSessionId(byte* sid) {
RNG::fill(sid, SESSION_ID_SIZE);
// first, try to find a completely unused slot
for (int i = 0; i < MAX_SESSION_IDS; i++) {
// this is not perfect, but it is extremely unlikely that the first 32 bit of a random session ID are all zeroes
if ((uint32_t)(knownSessions[i].sessionId) == 0 && knownSessions[i].counter == 0) {
memcpy(knownSessions[i].sessionId, sid, SESSION_ID_SIZE);
moveToFirst(i);
return;
}
}
// next, find oldest slot that has counter 0 (not used before)
// but leave the two most recent slots alone
for (int i = MAX_SESSION_IDS - 1; i > 1; i--) {
if (knownSessions[i].counter == 0) {
memcpy(knownSessions[i].sessionId, sid, SESSION_ID_SIZE);
moveToFirst(i);
return;
}
}
// if all else fails, overwrite the oldest slot
memcpy(knownSessions[MAX_SESSION_IDS - 1].sessionId, sid, SESSION_ID_SIZE);
moveToFirst(MAX_SESSION_IDS - 1);
}
void hexStringToByteArray(const char* hexString, unsigned char* byteArray, size_t byteArraySize) {
size_t lenStr = strlen(hexString);
if (lenStr < 2 * byteArraySize) byteArraySize = lenStr / 2;
for (size_t i = 0; i < byteArraySize; i++) {
char c[3] = {hexString[2 * i], hexString[2 * i + 1], '\0'}; // Get two characters
byteArray[i] = (unsigned char)strtoul(c, NULL, 16); // Convert to byte
}
}
// requires hexString to be at least 2 * byteLen + 1 characters long
char* byteArrayToHexString(char* hexString, const byte* byteArray, size_t byteLen) {
for (size_t i = 0; i < byteLen; ++i) {
// Convert each byte to a two-character hex string
sprintf(&hexString[i * 2], "%02x", byteArray[i]);
}
// Null-terminate the string
hexString[byteLen * 2] = '\0';
return hexString;
}
void hmacSign(const byte* message, size_t msgLen, const char* pskHex, byte* signature) {
size_t len = strlen(pskHex) / 2; // This will drop the last character if the string has an odd length
if (len > HMAC_KEY_SIZE) {
Serial.println(F("PSK too long!"));
return;
}
unsigned char pskByteArray[len];
hexStringToByteArray(pskHex, pskByteArray, len);
SHA256HMAC hmac(pskByteArray, len);
hmac.doUpdate(message, msgLen);
hmac.doFinal(signature);
}
bool hmacVerify(const byte* message, size_t msgLen, const char* pskHex, const byte* signature) {
byte sigCalculated[SHA256HMAC_SIZE];
hmacSign(message, msgLen, pskHex, sigCalculated);
//Serial.print(F("Calculated: "));
//printByteArray(sigCalculated, SHA256HMAC_SIZE);
if (memcmp(sigCalculated, signature, SHA256HMAC_SIZE) != 0) {
Serial.println(F("HMAC verification failed!"));
Serial.print(F("Expected: "));
printByteArray(signature, SHA256HMAC_SIZE);
return false;
}
Serial.println(F("HMAC verification successful!"));
return true;
}
#define WLED_HMAC_TEST_PW "guessihadthekeyafterall"
#define WLED_HMAC_TEST_PSK "a6f8488da62c5888d7f640276676e78da8639faf0495110b43e226b35ac37a4c"
uint8_t verifyHmacFromJsonString0Term(byte* jsonStr, size_t len) {
// Zero-terminate the JSON string (replace the last character, usually '}', with a null terminator temporarily)
byte lastChar = jsonStr[len-1];
jsonStr[len-1] = '\0';
uint8_t result = verifyHmacFromJsonStr((const char*)jsonStr, len);
jsonStr[len-1] = lastChar;
return result;
}
uint8_t verifyHmacFromJsonStr(const char* jsonStr, uint32_t maxLen) {
// Extract the signature from the JSON string
size_t jsonLen = strlen(jsonStr);
Serial.print(F("Length: "));
Serial.println(jsonLen);
if (jsonLen > maxLen) { // memory safety
Serial.print(F("JSON string too long!"));
Serial.print(F(", max: "));
Serial.println(maxLen);
return ERR_HMAC_GEN;
}
Serial.print(F("Received JSON: "));
Serial.println(jsonStr);
const char* macPos = strstr(jsonStr, "\"mac\":\"");
if (macPos == nullptr) {
Serial.println(F("No MAC found in JSON."));
return ERR_HMAC_MISS;
}
StaticJsonDocument<128> macDoc;
DeserializationError error = deserializeJson(macDoc, macPos +6);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.c_str());
return ERR_HMAC_GEN;
}
const char* mac = macDoc.as<const char*>();
if (mac == nullptr) {
Serial.println(F("Failed MAC JSON."));
return ERR_HMAC_GEN;
}
Serial.print(F("Received MAC: "));
Serial.println(mac);
// extract the message object from the JSON string
char* msgPos = strstr(jsonStr, "\"msg\":");
char* objStart = strchr(msgPos + 6, '{');
if (objStart == nullptr) {
Serial.println(F("Couldn't find msg object start."));
return ERR_HMAC_GEN;
}
size_t maxObjLen = jsonLen - (objStart - jsonStr);
Serial.print(F("Max object length: ")); Serial.println(maxObjLen);
int32_t objDepth = 0;
char* objEnd = nullptr;
for (size_t i = 0; i < maxObjLen; i++) {
Serial.write(objStart[i]);
if (objStart[i] == '{') objDepth++;
if (objStart[i] == '}') objDepth--;
if (objDepth == 0) {
Serial.print(F("Found msg object end: "));
Serial.println(i);
objEnd = objStart + i;
break;
}
}
if (objEnd == nullptr) {
Serial.println(F("Couldn't find msg object end."));
return ERR_HMAC_GEN;
}
// get nonce (note: the nonce implementation uses "nc" for the key instead of "n" to avoid conflicts with segment names)
const char* noncePos = strstr(objStart, "\"nc\":");
if (noncePos == nullptr || noncePos > objEnd) {
// note that it is critical to check that the nonce is within the "msg" object and thus authenticated
Serial.println(F("No nonce found in msg."));
return ERR_HMAC_GEN;
}
// Convert the MAC from hex string to byte array
size_t len = strlen(mac) / 2; // This will drop the last character if the string has an odd length
if (len != SHA256HMAC_SIZE) {
Serial.println(F("Received MAC not expected size!"));
return ERR_HMAC_GEN;
}
unsigned char macByteArray[len];
hexStringToByteArray(mac, macByteArray, len);
// Calculate the HMAC of the message object
if (!hmacVerify((const byte*)objStart, objEnd - objStart + 1, WLED_HMAC_TEST_PSK, macByteArray)) {
return ERR_HMAC;
}
// Nonce verification (Replay attack prevention)
{
StaticJsonDocument<128> nonceDoc;
DeserializationError error = deserializeJson(nonceDoc, noncePos +5);
if (error) {
Serial.print(F("deser nc failed: "));
Serial.println(error.c_str());
return ERR_HMAC_GEN;
}
JsonObject nonceObj = nonceDoc.as<JsonObject>();
if (nonceObj.isNull()) {
Serial.println(F("Failed nonce JSON."));
return ERR_HMAC_GEN;
}
const char* sessionId = nonceObj["sid"];
if (sessionId == nullptr) {
Serial.println(F("No session ID found in nonce."));
return ERR_HMAC_GEN;
}
uint32_t counter = nonceObj["c"] | 0;
if (counter == 0) {
Serial.println(F("No counter found in nonce."));
return ERR_HMAC_GEN;
}
if (counter > UINT32_MAX - 100) {
Serial.println(F("Counter too large."));
return ERR_NONCE;
}
byte sidBytes[SESSION_ID_SIZE] = {};
hexStringToByteArray(sessionId, sidBytes, SESSION_ID_SIZE);
uint8_t nonceResult = verifyNonce(sidBytes, counter);
return nonceResult ? nonceResult : ERR_NONE;
}
}
bool hmacTest() {
Serial.println(F("Testing HMAC..."));
unsigned long start = millis();
const char message[] = "Hello, World!";
const char psk[] = "d0c0ffeedeadbeef";
byte mac[SHA256HMAC_SIZE];
hmacSign((const byte*)message, strlen(message), psk, mac);
Serial.print(F("Took "));
Serial.print(millis() - start);
Serial.println(F("ms to sign message."));
Serial.print(F("MAC: "));
printByteArray(mac, SHA256HMAC_SIZE);
start = millis();
bool result = hmacVerify((const byte*)message, strlen(message), psk, mac);
Serial.print(F("Took "));
Serial.print(millis() - start);
Serial.println(F("ms to verify MAC."));
return result;
}
void printDuration(unsigned long start) {
unsigned long end = millis();
Serial.print(F("Took "));
Serial.print(end - start);
Serial.println(F(" ms."));
yield();
}
#define HMAC_BENCH_ITERATIONS 100
void hmacBenchmark(const char* message) {
Serial.print(F("Starting HMAC benchmark with message length:"));
Serial.println(strlen(message));
Serial.println(F("100 iterations signing message."));
unsigned long start = millis();
byte mac[SHA256HMAC_SIZE];
for (int i = 0; i < HMAC_BENCH_ITERATIONS; i++) {
hmacSign((const byte*)message, strlen(message), WLED_HMAC_TEST_PSK, mac);
}
printDuration(start);
Serial.println(F("100 iterations verifying message."));
start = millis();
for (int i = 0; i < HMAC_BENCH_ITERATIONS; i++) {
hmacVerify((const byte*)message, strlen(message), WLED_HMAC_TEST_PSK, mac);
}
printDuration(start);
}

View File

@@ -214,6 +214,10 @@ function loadSkinCSS(cId)
}
}
var useSRA = false;
var sraWindow = null;
var sraOrigin = '';
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
}
@@ -243,6 +247,13 @@ function onLoad()
var sett = localStorage.getItem('wledUiCfg');
if (sett) cfg = mergeDeep(cfg, JSON.parse(sett));
if (window.opener) {
// can't get opener origin due to cross-origin browser policy
//var openerOrigin = window.opener.location.origin;
//console.log("WLED-UI opener origin: " + openerOrigin);
window.opener.postMessage('{"wled-ui":"onload"}', '*'); //openerOrigin);
}
tooltip();
resetPUtil();
initFilters();
@@ -301,6 +312,28 @@ function onLoad()
});
}
function handleWindowMessageEvent(event) {
console.log(`Received message: ${event.data}`);
console.log(`origin: ${event.origin}`);
try {
var json = JSON.parse(event.data)
} catch (e) {
console.log(`Error parsing JSON: ${e}`);
return;
}
if (json['wled-rc'] === 'ready') {
useSRA = true;
sraWindow = event.source;
sraOrigin = event.origin;
} else if (json['wled-rc'] === 'hmac') {
console.log(`Received HMAC: ${json['mac']}`);
// Pass the message containing the HMAC to the ESP
requestJson(json);
}
}
onmessage = (event) => { handleWindowMessageEvent(event) };
function updateTablinks(tabI)
{
var tablinks = gEBCN("tablinks");
@@ -680,6 +713,12 @@ function parseInfo(i) {
} else {
gId("filter2D").classList.remove('hide');
}
if (useSRA && i.sid) {
if (sraWindow) {
sraWindow.postMessage(JSON.stringify({"wled-ui":"sid","sid":i.sid}), sraOrigin);
}
}
// if (i.noaudio) {
// gId("filterVol").classList.add("hide");
// gId("filterFreq").classList.add("hide");
@@ -1404,6 +1443,23 @@ function makeWS() {
if (isInfo) populateInfo(i);
} else
i = lastinfo;
if (json.error) {
if (json.error == 42) {
showToast('HMAC verification failed! Please make sure you used the right password!', true);
return;
} else if (json.error == 43) {
showToast("This light's control is password protected. Please access it through rc.wled.me", true);
return;
} else if (json.error == 41) {
showToast('Replayed message detected!', true);
return;
} else if (json.error == 40) {
showToast('Invalid nonce', true);
return;
}
showToast(json.error, true);
return;
}
var s = json.state ? json.state : json;
displayRover(i, s);
readState(s);
@@ -1703,6 +1759,12 @@ function requestJson(command=null)
if (req.length > 500 && lastinfo && lastinfo.arch == "esp8266") useWs = false; // esp8266 can only handle 500 bytes
};
if (command && useSRA && !command['mac']) { // secure remote access integration, need to get HMAC from rc.wled.me
// if we already have a command including a MAC, we are good to go
sraWindow.postMessage(JSON.stringify({"wled-ui":"hmac-req", "msg":command}), sraOrigin);
return; // TODO need a sort of pending indicator
}
if (useWs) {
ws.send(req?req:'{"v":true}');
return;

View File

@@ -67,7 +67,7 @@
} catch (e) {}
if (ws && ws.readyState === WebSocket.OPEN) {
//console.info("Peek uses top WS");
ws.send("{'lv':true}");
ws.send('{"lv":true}');
} else {
//console.info("Peek WS opening");
let l = window.location;
@@ -80,7 +80,7 @@
ws = new WebSocket(url+"/ws");
ws.onopen = function () {
//console.info("Peek WS open");
ws.send("{'lv':true}");
ws.send('{"lv":true}');
}
}
ws.binaryType = "arraybuffer";

View File

@@ -31,7 +31,7 @@
ws = top.window.ws;
} catch (e) {}
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send("{'lv':true}");
ws.send('{"lv":true}');
} else {
let l = window.location;
let pathn = l.pathname;
@@ -42,7 +42,7 @@
}
ws = new WebSocket(url+"/ws");
ws.onopen = ()=>{
ws.send("{'lv':true}");
ws.send('{"lv":true}');
}
}
ws.binaryType = "arraybuffer";

View File

@@ -95,6 +95,16 @@ uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint16_t approximateKelvinFromRGB(uint32_t rgb);
void setRandomColor(byte* rgb);
//crypto.cpp
void addSessionId(byte* sid);
char* byteArrayToHexString(char* hexString, const byte* byteArray, size_t byteLen);
void hmacSign(const byte* message, size_t msgLen, const char* pskHex, byte* signature);
bool hmacVerify(const byte* message, size_t msgLen, const char* pskHex, const byte* signature);
uint8_t verifyHmacFromJsonStr(const char* jsonStr, uint32_t maxLen);
uint8_t verifyHmacFromJsonString0Term(byte* jsonStr, size_t len);
bool hmacTest();
void hmacBenchmark(const char* message);
//dmx.cpp
void initDMX();
void handleDMX();
@@ -452,7 +462,7 @@ void serveSettingsJS(AsyncWebServerRequest* request);
//ws.cpp
void handleWs();
void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len);
void sendDataWs(AsyncWebSocketClient * client = nullptr);
void sendDataWs(AsyncWebSocketClient * client = nullptr, bool initialConnection = false);
//xml.cpp
void XML_response(Print& dest);

View File

@@ -359,6 +359,13 @@ void WLED::setup()
#if !defined(WLED_DEBUG) && defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DEBUG_HOST) && ARDUINO_USB_CDC_ON_BOOT
Serial.setDebugOutput(false); // switch off kernel messages when using USBCDC
#endif
{
//hmacTest();
//const char testMsg[] = "WLED HMAC test!!";
//hmacBenchmark(testMsg);
//const char longMsg[] = "LoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIps";
//hmacBenchmark(longMsg);
}
DEBUG_PRINTLN();
DEBUG_PRINTF_P(PSTR("---WLED %s %u INIT---\n"), versionString, VERSION);
DEBUG_PRINTLN();

View File

@@ -287,18 +287,50 @@ void initServer()
bool verboseResponse = false;
bool isConfig = false;
Serial.println("JSON request");
Serial.println((const char*)request->_tempObject);
if (!verifyHmacFromJsonString0Term((byte*)request->_tempObject, request->contentLength())) {
//releaseJSONBufferLock();
serveJsonError(request, 401, ERR_DENIED);
return;
}
if (!requestJSONBufferLock(14)) {
serveJsonError(request, 503, ERR_NOBUF);
return;
}
DeserializationError error = deserializeJson(*pDoc, (uint8_t*)(request->_tempObject));
// // if enabled, calculate HMAC and verify it
// Serial.println(F("HMAC verification"));
// Serial.write((const char*)request->_tempObject, request->contentLength());
// // actually we need to verify the HMAC of the nested "msg" object
// if (strlen((const char*)request->_tempObject) > request->contentLength()) {
// Serial.println(F("HMAC verification failed: content is not null-terminated"));
// releaseJSONBufferLock();
// serveJsonError(request, 400, ERR_JSON);
// return;
// }
// // find the "msg" object in JSON
// char * msgPtr = strstr((const char*)request->_tempObject, "\"msg\":");
// if (msgPtr == NULL) {
// Serial.println(F("HMAC verification failed: no \"msg\" object found"));
// releaseJSONBufferLock();
// serveJsonError(request, 400, ERR_JSON);
// return;
// }
// char * objStart = strchr(msgPtr, '{');
JsonObject root = pDoc->as<JsonObject>();
if (error || root.isNull()) {
releaseJSONBufferLock();
serveJsonError(request, 400, ERR_JSON);
return;
}
// old 4-digit pin logic for settings authentication (no transport encryption)
if (root.containsKey("pin")) checkSettingsPIN(root["pin"].as<const char*>());
const String& url = request->url();
@@ -311,7 +343,11 @@ void initServer()
DEBUG_PRINTLN();
#endif
*/
verboseResponse = deserializeState(root);
if (root.containsKey("msg")) {
verboseResponse = deserializeState(root["msg"]);
} else {
verboseResponse = deserializeState(root);
}
} else {
if (!correctPIN && strlen(settingsPIN)>0) {
releaseJSONBufferLock();

View File

@@ -11,12 +11,28 @@ unsigned long wsLastLiveTime = 0;
#define WS_LIVE_INTERVAL 40
void sendWsError(AsyncWebSocketClient * client, uint8_t error)
{
if (!ws.count()) return;
char errorStr[16];
strcpy_P(errorStr, PSTR("{\"error\":"));
strcpy(errorStr + 9, itoa(error, errorStr + 9, 10));
strcat(errorStr + 10, "}");
if (client) {
client->text(errorStr); // ERR_NOBUF
} else {
ws.textAll(errorStr); // ERR_NOBUF
}
}
void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)
{
if(type == WS_EVT_CONNECT){
//client connected
DEBUG_PRINTLN(F("WS client connected."));
sendDataWs(client);
sendDataWs(client, true);
} else if(type == WS_EVT_DISCONNECT){
//client disconnected
if (client->id() == wsLiveClientId) wsLiveClientId = 0;
@@ -36,26 +52,47 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
}
bool verboseResponse = false;
if (!requestJSONBufferLock(11)) {
client->text(F("{\"error\":3}")); // ERR_NOBUF
return;
}
DeserializationError error = deserializeJson(*pDoc, data, len);
JsonObject root = pDoc->as<JsonObject>();
if (error || root.isNull()) {
releaseJSONBufferLock();
return;
}
if (root["v"] && root.size() == 1) {
//if the received value is just "{"v":true}", send only to this client
Serial.print(F("WS message: "));
Serial.write(data, len);
Serial.println();
if (len < 11 && memcmp(data, "{\"v\":true}", 10) == 0) {
// if the received value is just "{"v":true}", send only to this client
verboseResponse = true;
} else if (root.containsKey("lv")) {
wsLiveClientId = root["lv"] ? client->id() : 0;
Serial.println(F("Simple state query."));
} else if (len < 13 && memcmp(data, "{\"lv\":", 6) == 0) {
wsLiveClientId = data[6] == 't' ? client->id() : 0;
} else {
verboseResponse = deserializeState(root);
// check HMAC, must do before parsing JSON as that modifies "data" to store strings
uint8_t hmacVerificationResult = verifyHmacFromJsonString0Term(data, len);
if (hmacVerificationResult != ERR_NONE) {
sendWsError(client, hmacVerificationResult);
return;
}
if (!requestJSONBufferLock(11)) {
sendWsError(client, 3); // ERR_NOBUF
return;
}
Serial.print(F("deser input: "));
Serial.write(data, len);
Serial.println();
DeserializationError error = deserializeJson(*pDoc, data, len);
JsonObject root = pDoc->as<JsonObject>();
if (error || root.isNull()) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.c_str());
//Serial.println(F("WS JSON parse F!"));
sendWsError(client, 9); // ERR_JSON
releaseJSONBufferLock();
return;
}
verboseResponse = deserializeState(root["msg"]);
releaseJSONBufferLock();
}
releaseJSONBufferLock();
if (!interfaceUpdateCallMode) { // individual client response only needed if no WS broadcast soon
if (verboseResponse) {
@@ -82,7 +119,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
if((info->index + len) == info->len){
if(info->final){
if(info->message_opcode == WS_TEXT) {
client->text(F("{\"error\":9}")); // ERR_JSON we do not handle split packets right now
sendWsError(client, 9); // ERR_JSON we do not handle split packets right now
}
}
}
@@ -101,7 +138,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
}
}
void sendDataWs(AsyncWebSocketClient * client)
void sendDataWs(AsyncWebSocketClient * client, bool initialConnection)
{
if (!ws.count()) return;
@@ -120,6 +157,16 @@ void sendDataWs(AsyncWebSocketClient * client)
JsonObject info = pDoc->createNestedObject("info");
serializeInfo(info);
if (initialConnection) {
char sid[SESSION_ID_SIZE*2+1] = {};
byte sidBytes[SESSION_ID_SIZE] = {};
addSessionId(sidBytes);
byteArrayToHexString(sid, sidBytes, SESSION_ID_SIZE);
Serial.print(F("New session ID: "));
Serial.println(sid);
info["sid"] = sid;
}
size_t len = measureJson(*pDoc);
DEBUG_PRINTF_P(PSTR("JSON buffer size: %u for WS request (%u).\n"), pDoc->memoryUsage(), len);