Skip to content
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ wled-update.sh
/wled00/Release
/wled00/wled00.ino.cpp
/wled00/html_*.h
_codeql_detected_source_root
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,8 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL
int16_t extractModeDefaults(uint8_t mode, const char *segVar);
void checkSettingsPIN(const char *pin);
uint16_t crc16(const unsigned char* data_p, size_t length);
String computeSHA1(const String& input);
String getDeviceId();
uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0);
Expand Down
2 changes: 2 additions & 0 deletions wled00/json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,8 @@ void serializeInfo(JsonObject root)
root[F("vid")] = VERSION;
root[F("cn")] = F(WLED_CODENAME);
root[F("release")] = releaseString;
root[F("repo")] = repoString;
root[F("deviceId")] = getDeviceId();

JsonObject leds = root.createNestedObject(F("leds"));
leds[F("count")] = strip.getLengthTotal();
Expand Down
100 changes: 100 additions & 0 deletions wled00/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
#include "const.h"
#ifdef ESP8266
#include "user_interface.h" // for bootloop detection
#include <Hash.h> // for SHA1 on ESP8266
#else
#include <Update.h>
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
#include "esp32/rtc.h" // for bootloop detection
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
#include "soc/rtc.h"
#endif
#include "mbedtls/sha1.h" // for SHA1 on ESP32
#include "esp_adc_cal.h"
#endif


Expand Down Expand Up @@ -745,3 +748,100 @@ void handleBootLoop() {

ESP.restart(); // restart cleanly and don't wait for another crash
}

// Platform-agnostic SHA1 computation from String input
String computeSHA1(const String& input) {
#ifdef ESP8266
return sha1(input); // ESP8266 has built-in sha1() function
#else
// ESP32: Compute SHA1 hash using mbedtls
unsigned char shaResult[20]; // SHA1 produces 20 bytes
mbedtls_sha1_context ctx;

mbedtls_sha1_init(&ctx);
mbedtls_sha1_starts_ret(&ctx);
mbedtls_sha1_update_ret(&ctx, (const unsigned char*)input.c_str(), input.length());
mbedtls_sha1_finish_ret(&ctx, shaResult);
mbedtls_sha1_free(&ctx);

// Convert to hexadecimal string
char hexString[41];
for (int i = 0; i < 20; i++) {
sprintf(&hexString[i*2], "%02x", shaResult[i]);
}
hexString[40] = '\0';

return String(hexString);
#endif
}

#ifdef ESP32
String generateDeviceFingerprint() {
uint32_t fp[2] = {0, 0}; // create 64 bit fingerprint
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
esp_efuse_mac_get_default((uint8_t*)fp);
fp[1] ^= ESP.getFlashChipSize();
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
Comment thread
softhack007 marked this conversation as resolved.
Outdated
fp[0] ^= chip_info.full_revision | (chip_info.model << 16);
#else
fp[0] ^= chip_info.revision | (chip_info.model << 16);
#endif
// mix in ADC calibration data:
esp_adc_cal_characteristics_t ch;
#if SOC_ADC_MAX_BITWIDTH == 13 // S2 has 13 bit ADC
#define BIT_WIDTH ADC_WIDTH_BIT_13
#else
#define BIT_WIDTH ADC_WIDTH_BIT_12
#endif
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, BIT_WIDTH, 1100, &ch);
#undef BIT_WIDTH
Comment thread
softhack007 marked this conversation as resolved.
Outdated
fp[0] ^= ch.coeff_a;
fp[1] ^= ch.coeff_b;
if (ch.low_curve) {
for (int i = 0; i < 8; i++) {
fp[0] ^= ch.low_curve[i];
}
}
if (ch.high_curve) {
for (int i = 0; i < 8; i++) {
fp[1] ^= ch.high_curve[i];
}
}
char fp_string[17]; // 16 hex chars + null terminator
sprintf(fp_string, "%08X%08X", fp[1], fp[0]);
return String(fp_string);
}
#else // ESP8266
String generateDeviceFingerprint() {
uint32_t fp[2] = {0, 0}; // create 64 bit fingerprint
WiFi.macAddress((uint8_t*)&fp); // use MAC address as fingerprint base
fp[0] ^= ESP.getFlashChipId();
fp[1] ^= ESP.getFlashChipSize() | ESP.getFlashChipVendorId() << 16;
char fp_string[17]; // 16 hex chars + null terminator
sprintf(fp_string, "%08X%08X", fp[1], fp[0]);
return String(fp_string);
}
#endif

// Generate a device ID based on SHA1 hash of MAC address salted with other unique device info
// Returns: original SHA1 + last 2 chars of double-hashed SHA1 (42 chars total)
String getDeviceId() {
static String cachedDeviceId = "";
if (cachedDeviceId.length() > 0) return cachedDeviceId;
// The device string is deterministic as it needs to be consistent for the same device, even after a full flash erase
// MAC is salted with other consistent device info to avoid rainbow table attacks.
// If the MAC address is known by malicious actors, they could precompute SHA1 hashes to impersonate devices,
// but as WLED developers are just looking at statistics and not authenticating devices, this is acceptable.
// If the usage data was exfiltrated, you could not easily determine the MAC from the device ID without brute forcing SHA1

String firstHash = computeSHA1(generateDeviceFingerprint());

// Second hash: SHA1 of the first hash
String secondHash = computeSHA1(firstHash);

// Concatenate first hash + last 2 chars of second hash
cachedDeviceId = firstHash + secondHash.substring(38);

return cachedDeviceId;
}