From be997aa7551cb89a16c45234318d25a1dbbdc682 Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Sun, 20 Oct 2024 23:06:03 +0200 Subject: [PATCH] More HMAC functionality --- wled00/crypto.cpp | 117 +++++++++++++++++++++++++++++++++-------- wled00/data/index.js | 11 +++- wled00/fcn_declare.h | 7 +-- wled00/wled.cpp | 2 +- wled00/wled_server.cpp | 61 +++++++++++---------- wled00/ws.cpp | 12 ++++- 6 files changed, 149 insertions(+), 61 deletions(-) diff --git a/wled00/crypto.cpp b/wled00/crypto.cpp index 142f1561e..94c5c069f 100644 --- a/wled00/crypto.cpp +++ b/wled00/crypto.cpp @@ -3,62 +3,133 @@ #define HMAC_KEY_SIZE 32 -void print_byte_array(const byte* arr, size_t len) { +void printByteArray(const byte* arr, size_t len) { for (size_t i = 0; i < len; i++) { Serial.print(arr[i], HEX); } Serial.println(); } -void hmac_sign(const char* message, const char* psk, byte* signature) { - SHA256HMAC hmac((const byte*)psk, strlen(psk)); - hmac.doUpdate(message, strlen(message)); +void hexStringToByteArray(const char* hexString, unsigned char* byteArray, size_t byteArraySize) { + 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 + } +} + +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 hmac_verify(const char* message, const char* psk, const byte* signature) { - byte sig_calculated[SHA256HMAC_SIZE]; - hmac_sign(message, psk, sig_calculated); - if (memcmp(sig_calculated, signature, SHA256HMAC_SIZE) != 0) { +bool hmacVerify(const byte* message, size_t msgLen, const char* pskHex, const byte* signature) { + byte sigCalculated[SHA256HMAC_SIZE]; + hmacSign(message, msgLen, pskHex, sigCalculated); + if (memcmp(sigCalculated, signature, SHA256HMAC_SIZE) != 0) { DEBUG_PRINTLN(F("HMAC verification failed!")); Serial.print(F("Expected: ")); - print_byte_array(signature, SHA256HMAC_SIZE); + printByteArray(signature, SHA256HMAC_SIZE); Serial.print(F("Calculated: ")); - print_byte_array(sig_calculated, SHA256HMAC_SIZE); + printByteArray(sigCalculated, SHA256HMAC_SIZE); return false; } Serial.println(F("HMAC verification successful!")); return true; } -bool verify_json_hmac(JsonObject root) { - JsonObject msg = root["msg"]; - if (!msg) { - Serial.println(F("No message object found in JSON.")); +#define WLED_HMAC_TEST_PW "guessihadthekeyafterall" +#define WLED_HMAC_TEST_PSK "a6f8488da62c5888d7f640276676e78da8639faf0495110b43e226b35ac37a4c" + +bool verifyHmacFromJsonStr(const char* jsonStr, uint32_t maxLen) { + // Extract the signature from the JSON string + size_t jsonLen = strlen(jsonStr); + if (jsonLen > maxLen) { // memory safety + Serial.println(F("JSON string too long!")); + Serial.print(F("Length: ")); + Serial.print(jsonLen); + Serial.print(F(", max: ")); + Serial.println(maxLen); return false; } - const char *sig = msg["sig"]; - if (sig == nullptr) { + Serial.print(F("Received JSON: ")); + Serial.println(jsonStr); + char* sigPos = strstr(jsonStr, PSTR("\"sig\":\"")); + if (sigPos == nullptr) { Serial.println(F("No signature found in JSON.")); return false; } - + StaticJsonDocument<256> doc; + DeserializationError error = deserializeJson(doc, jsonStr +7); + if (error) { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.c_str()); + return false; + } + const char* sig = doc.as(); + if (sig == nullptr) { + Serial.println(F("Failed signature JSON.")); + return false; + } + Serial.print(F("Received signature: ")); + Serial.println(sig); + + // extract the message object from the JSON string + char* msgPos = strstr(jsonStr, PSTR("\"msg\":\"")); + char* objStart = strchr(msgPos + 7, '{'); + size_t maxObjLen = jsonLen - (objStart - jsonStr); + uint32_t objDepth = 0; + char* objEnd = nullptr; + + for (size_t i = 0; i < maxObjLen; i++) { + if (objStart[i] == '{') objDepth++; + if (objStart [i] == '}') objDepth--; + if (objDepth == 0) { + objEnd = objStart + i; + break; + } + i++; + } + if (objEnd == nullptr) { + Serial.println(F("Couldn't find msg object end.")); + return false; + } + + // Convert the signature from hex string to byte array + size_t len = strlen(sig) / 2; // This will drop the last character if the string has an odd length + if (len != SHA256HMAC_SIZE) { + Serial.println(F("Received sig not expected size!")); + return false; + } + unsigned char sigByteArray[len]; + hexStringToByteArray(sig, sigByteArray, len); + + // Calculate the HMAC of the message object + return hmacVerify((const byte*)objStart, objEnd - objStart + 1, WLED_HMAC_TEST_PSK, sigByteArray); } -bool hmac_test() { +bool hmacTest() { Serial.println(F("Testing HMAC...")); unsigned long start = millis(); - char message[] = "Hello, World!"; - char psk[] = "tokyo"; + const char message[] = "Hello, World!"; + const char psk[] = "d0c0ffeedeadbeef"; byte signature[SHA256HMAC_SIZE]; - hmac_sign(message, psk, signature); + hmacSign((const byte*)message, strlen(message), psk, signature); Serial.print(F("Took ")); Serial.print(millis() - start); Serial.println(F("ms to sign message.")); Serial.print(F("Signature: ")); - print_byte_array(signature, SHA256HMAC_SIZE); + printByteArray(signature, SHA256HMAC_SIZE); start = millis(); - bool result = hmac_verify(message, psk, signature); + bool result = hmacVerify((const byte*)message, strlen(message), psk, signature); Serial.print(F("Took ")); Serial.print(millis() - start); Serial.println(F("ms to verify signature.")); diff --git a/wled00/data/index.js b/wled00/data/index.js index 35b4cd502..cc078d23b 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -326,7 +326,8 @@ function handleWindowMessageEvent(event) { sraWindow = event.source; sraOrigin = event.origin; } else if (json['wled-rc'] === 'hmac') { - console.log(`Received HMAC: ${json['hmac']}`); + console.log(`Received HMAC: ${json['sig']}`); + requestJson(json); } } @@ -1435,6 +1436,14 @@ function makeWS() { if (isInfo) populateInfo(i); } else i = lastinfo; + if (json.error) { + if (json.error == 1) { + showToast('HMAC verification failed! Please make sure you used the right password!', true); + return; + } + showToast(json.error, true); + return; + } var s = json.state ? json.state : json; displayRover(i, s); readState(s); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 78d38b85c..13a0cd3ad 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -96,9 +96,10 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb); void setRandomColor(byte* rgb); //crypto.cpp -void hmac_sign(const char* message, const char* psk, byte* signature); -bool hmac_verify(const char* message, const char* psk, const byte* signature); -bool hmac_test(); +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); +bool verifyHmacFromJsonStr(const char* jsonStr, uint32_t maxLen); +bool hmacTest(); //dmx.cpp void initDMX(); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index c2e4431f5..64562a134 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -556,7 +556,7 @@ void WLED::setup() WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); //enable brownout detector #endif - hmac_test(); + hmacTest(); } void WLED::beginStrip() diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index cace01186..407bfb9d0 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -288,6 +288,12 @@ void initServer() bool isConfig = false; Serial.println("JSON request"); + Serial.println((const char*)request->_tempObject); + if (!verifyHmacFromJsonStr((const char*)request->_tempObject, request->contentLength())) { + //releaseJSONBufferLock(); + serveJsonError(request, 401, ERR_DENIED); + return; + } if (!requestJSONBufferLock(14)) { serveJsonError(request, 503, ERR_NOBUF); @@ -296,26 +302,26 @@ void initServer() 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()); + // // 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, '{'); + // // 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(); if (error || root.isNull()) { @@ -324,17 +330,6 @@ void initServer() return; } - // if (root.containsKey("sig")) { - // const char* hmacProvided = root["sig"]; - // char hmac_calculated[SHA256HMAC_SIZE]; - // hmac_sign((const char*)request->_tempObject, settings.hmacKey, (byte*)hmac_calculated); - // if (memcmp(hmac_calculated, hmac, SHA256HMAC_SIZE) != 0) { - // releaseJSONBufferLock(); - // serveJsonError(request, 401, ERR_HMAC); - // return; - // } - // } - // old 4-digit pin logic for settings authentication (no transport encryption) if (root.containsKey("pin")) checkSettingsPIN(root["pin"].as()); @@ -348,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(); diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 45640b68c..41d0615ad 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -41,6 +41,9 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp return; } + Serial.print(F("WS message: ")); + Serial.println((const char*)data); + DeserializationError error = deserializeJson(*pDoc, data, len); JsonObject root = pDoc->as(); if (error || root.isNull()) { @@ -48,12 +51,17 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp return; } if (root["v"] && root.size() == 1) { - //if the received value is just "{"v":true}", send only to this client + // 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; } else { - verboseResponse = deserializeState(root); + if (!verifyHmacFromJsonStr((const char*)data, len)) { + releaseJSONBufferLock(); + client->text(F("{\"error\":1}")); // ERR_DENIED + return; + } + verboseResponse = deserializeState(root["msg"]); } releaseJSONBufferLock();