|
3 | 3 | #include "const.h" |
4 | 4 | #ifdef ESP8266 |
5 | 5 | #include "user_interface.h" // for bootloop detection |
| 6 | +#include <Hash.h> // for SHA1 on ESP8266 |
6 | 7 | #else |
7 | 8 | #include <Update.h> |
8 | 9 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) |
9 | 10 | #include "esp32/rtc.h" // for bootloop detection |
10 | 11 | #elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0) |
11 | 12 | #include "soc/rtc.h" |
12 | 13 | #endif |
| 14 | +#include "mbedtls/sha1.h" // for SHA1 on ESP32 |
| 15 | +#include "esp_adc_cal.h" |
13 | 16 | #endif |
14 | 17 |
|
15 | 18 |
|
@@ -745,3 +748,99 @@ void handleBootLoop() { |
745 | 748 |
|
746 | 749 | ESP.restart(); // restart cleanly and don't wait for another crash |
747 | 750 | } |
| 751 | + |
| 752 | +// Platform-agnostic SHA1 computation from String input |
| 753 | +String computeSHA1(const String& input) { |
| 754 | + #ifdef ESP8266 |
| 755 | + return sha1(input); // ESP8266 has built-in sha1() function |
| 756 | + #else |
| 757 | + // ESP32: Compute SHA1 hash using mbedtls |
| 758 | + unsigned char shaResult[20]; // SHA1 produces 20 bytes |
| 759 | + mbedtls_sha1_context ctx; |
| 760 | + |
| 761 | + mbedtls_sha1_init(&ctx); |
| 762 | + mbedtls_sha1_starts_ret(&ctx); |
| 763 | + mbedtls_sha1_update_ret(&ctx, (const unsigned char*)input.c_str(), input.length()); |
| 764 | + mbedtls_sha1_finish_ret(&ctx, shaResult); |
| 765 | + mbedtls_sha1_free(&ctx); |
| 766 | + |
| 767 | + // Convert to hexadecimal string |
| 768 | + char hexString[41]; |
| 769 | + for (int i = 0; i < 20; i++) { |
| 770 | + sprintf(&hexString[i*2], "%02x", shaResult[i]); |
| 771 | + } |
| 772 | + hexString[40] = '\0'; |
| 773 | + |
| 774 | + return String(hexString); |
| 775 | + #endif |
| 776 | +} |
| 777 | + |
| 778 | +#ifdef ESP32 |
| 779 | +String generateDeviceFingerprint() { |
| 780 | + uint32_t fp[2] = {0, 0}; // create 64 bit fingerprint |
| 781 | + esp_chip_info_t chip_info; |
| 782 | + esp_chip_info(&chip_info); |
| 783 | + esp_efuse_mac_get_default((uint8_t*)fp); |
| 784 | + fp[1] ^= ESP.getFlashChipSize(); |
| 785 | + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 4) |
| 786 | + fp[0] ^= chip_info.full_revision | (chip_info.model << 16); |
| 787 | + #else |
| 788 | + fp[0] ^= chip_info.revision | (chip_info.model << 16); |
| 789 | + #endif |
| 790 | + // mix in ADC calibration data: |
| 791 | + esp_adc_cal_characteristics_t ch; |
| 792 | + #if SOC_ADC_MAX_BITWIDTH == 13 // S2 has 13 bit ADC |
| 793 | + constexpr auto myBIT_WIDTH = ADC_WIDTH_BIT_13; |
| 794 | + #else |
| 795 | + constexpr auto myBIT_WIDTH = ADC_WIDTH_BIT_12; |
| 796 | + #endif |
| 797 | + esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, myBIT_WIDTH, 1100, &ch); |
| 798 | + fp[0] ^= ch.coeff_a; |
| 799 | + fp[1] ^= ch.coeff_b; |
| 800 | + if (ch.low_curve) { |
| 801 | + for (int i = 0; i < 8; i++) { |
| 802 | + fp[0] ^= ch.low_curve[i]; |
| 803 | + } |
| 804 | + } |
| 805 | + if (ch.high_curve) { |
| 806 | + for (int i = 0; i < 8; i++) { |
| 807 | + fp[1] ^= ch.high_curve[i]; |
| 808 | + } |
| 809 | + } |
| 810 | + char fp_string[17]; // 16 hex chars + null terminator |
| 811 | + sprintf(fp_string, "%08X%08X", fp[1], fp[0]); |
| 812 | + return String(fp_string); |
| 813 | +} |
| 814 | +#else // ESP8266 |
| 815 | +String generateDeviceFingerprint() { |
| 816 | + uint32_t fp[2] = {0, 0}; // create 64 bit fingerprint |
| 817 | + WiFi.macAddress((uint8_t*)&fp); // use MAC address as fingerprint base |
| 818 | + fp[0] ^= ESP.getFlashChipId(); |
| 819 | + fp[1] ^= ESP.getFlashChipSize() | ESP.getFlashChipVendorId() << 16; |
| 820 | + char fp_string[17]; // 16 hex chars + null terminator |
| 821 | + sprintf(fp_string, "%08X%08X", fp[1], fp[0]); |
| 822 | + return String(fp_string); |
| 823 | +} |
| 824 | +#endif |
| 825 | + |
| 826 | +// Generate a device ID based on SHA1 hash of MAC address salted with other unique device info |
| 827 | +// Returns: original SHA1 + last 2 chars of double-hashed SHA1 (42 chars total) |
| 828 | +String getDeviceId() { |
| 829 | + static String cachedDeviceId = ""; |
| 830 | + if (cachedDeviceId.length() > 0) return cachedDeviceId; |
| 831 | + // The device string is deterministic as it needs to be consistent for the same device, even after a full flash erase |
| 832 | + // MAC is salted with other consistent device info to avoid rainbow table attacks. |
| 833 | + // If the MAC address is known by malicious actors, they could precompute SHA1 hashes to impersonate devices, |
| 834 | + // but as WLED developers are just looking at statistics and not authenticating devices, this is acceptable. |
| 835 | + // If the usage data was exfiltrated, you could not easily determine the MAC from the device ID without brute forcing SHA1 |
| 836 | + |
| 837 | + String firstHash = computeSHA1(generateDeviceFingerprint()); |
| 838 | + |
| 839 | + // Second hash: SHA1 of the first hash |
| 840 | + String secondHash = computeSHA1(firstHash); |
| 841 | + |
| 842 | + // Concatenate first hash + last 2 chars of second hash |
| 843 | + cachedDeviceId = firstHash + secondHash.substring(38); |
| 844 | + |
| 845 | + return cachedDeviceId; |
| 846 | +} |
0 commit comments