Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
1 change: 1 addition & 0 deletions wled00/json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,7 @@ void serializeInfo(JsonObject root)
root[F("vid")] = VERSION;
root[F("cn")] = F(WLED_CODENAME);
root[F("release")] = releaseString;
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" // for ADC calibration data on ESP32
#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)
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);
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;
}