Skip to content

Commit

Permalink
More HMAC functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Aircoookie committed Oct 20, 2024
1 parent b02bc29 commit be997aa
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 61 deletions.
117 changes: 94 additions & 23 deletions wled00/crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const char*>();
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."));
Expand Down
11 changes: 10 additions & 1 deletion wled00/data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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);
Expand Down
7 changes: 4 additions & 3 deletions wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion wled00/wled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
61 changes: 30 additions & 31 deletions wled00/wled_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<JsonObject>();
if (error || root.isNull()) {
Expand All @@ -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<const char*>());

Expand All @@ -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();
Expand Down
12 changes: 10 additions & 2 deletions wled00/ws.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,27 @@ 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<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
// 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();

Expand Down

0 comments on commit be997aa

Please sign in to comment.