Merge pull request #5128 from willmmiles/cleanup-bootloader-sha

Cleanup bootloader SHA256 calculation from #4984
This commit is contained in:
Will Tatam
2026-02-26 08:54:16 +00:00
committed by GitHub
3 changed files with 227 additions and 222 deletions

View File

@@ -29,9 +29,9 @@
if (data.arch == "esp8266") {
toggle('rev');
}
const isESP32 = data.arch && (data.arch.toLowerCase() === 'esp32' || data.arch.toLowerCase() === 'esp32-s2');
if (isESP32) {
gId('bootloader-section').style.display = 'block';
const allowBl = data.arch && (data.arch.toLowerCase() === 'esp32' || data.arch.toLowerCase() === 'esp32-s2');
if (allowBl) {
toggle('bootupd')
if (data.bootloaderSHA256) {
gId('bootloader-hash').innerText = 'Current bootloader SHA256: ' + data.bootloaderSHA256;
}
@@ -44,14 +44,18 @@
document.querySelector('.release-name').textContent = 'Unknown';
});
}
function hideforms() {
gId('bootupd').classList.add("hide");
toggle('upd');
}
</script>
<style>
@import url("style.css");
</style>
</head>
<body onload="GetV();">
<h2>WLED Software Update</h2>
<form method='POST' action='./update' id='upd' enctype='multipart/form-data' onsubmit="toggle('upd')">
<h2>WLED Software Update</h2>
<form method='POST' action='./update' id='upd' enctype='multipart/form-data' onsubmit="hideforms()">
Installed version: <span class="sip installed-version">Loading...</span><br>
Release: <span class="sip release-name">Loading...</span><br>
Download the latest binary: <a href="https://github.com/wled-dev/WLED/releases" target="_blank"
@@ -66,11 +70,11 @@
<button id="rev" type="button" onclick="cR()">Revert update</button><br>
<button type="button" onclick="B()">Back</button>
</form>
<div id="bootloader-section" style="display:none;">
<div id="bootupd" class="hide">
<hr class="sml">
<h2>ESP32 Bootloader Update</h2>
<h2>Bootloader Update</h2>
<div id="bootloader-hash" class="sip" style="margin-bottom:8px;"></div>
<form method='POST' action='./updatebootloader' id='bootupd' enctype='multipart/form-data' onsubmit="toggle('bootupd')">
<form method='POST' action='./updatebootloader' enctype='multipart/form-data' onsubmit="hideforms()">
<b>Warning:</b> Only upload verified ESP32 bootloader files!<br>
<input type='file' name='update' required><br>
<button type="submit">Update Bootloader</button>

View File

@@ -18,9 +18,11 @@ constexpr size_t METADATA_OFFSET = 256; // ESP32: metadata appears afte
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6)
constexpr size_t BOOTLOADER_OFFSET = 0x0000; // esp32-S3, esp32-C3 and (future support) esp32-c6
constexpr size_t BOOTLOADER_SIZE = 0x8000; // 32KB, typical bootloader size
#define BOOTLOADER_OTA_UNSUPPORTED // still needs validation on these platforms.
#elif defined(CONFIG_IDF_TARGET_ESP32P4) || defined(CONFIG_IDF_TARGET_ESP32C5)
constexpr size_t BOOTLOADER_OFFSET = 0x2000; // (future support) esp32-P4 and esp32-C5
constexpr size_t BOOTLOADER_SIZE = 0x8000; // 32KB, typical bootloader size
#define BOOTLOADER_OTA_UNSUPPORTED // still needs testing on these platforms
#else
constexpr size_t BOOTLOADER_OFFSET = 0x1000; // esp32 and esp32-s2
constexpr size_t BOOTLOADER_SIZE = 0x8000; // 32KB, typical bootloader size
@@ -30,6 +32,7 @@ constexpr size_t BOOTLOADER_SIZE = 0x8000; // 32KB, typical bootloader size
constexpr size_t METADATA_OFFSET = 0x1000; // ESP8266: metadata appears at 4KB offset
#define UPDATE_ERROR getErrorString
#endif
constexpr size_t METADATA_SEARCH_RANGE = 512; // bytes
@@ -286,247 +289,263 @@ void markOTAvalid() {
}
#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
// Cache for bootloader SHA256 digest as hex string
static String bootloaderSHA256HexCache = "";
// Calculate and cache the bootloader SHA256 digest as hex string
void calculateBootloaderSHA256() {
if (!bootloaderSHA256HexCache.isEmpty()) return;
// Class for computing the expected bootloader data size given a stream of the data.
// If the image includes an SHA256 appended after the data stream, we do not consider it here.
class BootloaderImageSizer {
public:
bool feed(const uint8_t* data, size_t len) {
if (error) return false;
//DEBUG_PRINTF("Feed %d\n", len);
if (imageSize == 0) {
// Parse header first
if (len < sizeof(esp_image_header_t)) {
error = true;
return false;
}
esp_image_header_t header;
memcpy(&header, data, sizeof(esp_image_header_t));
if (header.segment_count == 0) {
error = true;
return false;
}
imageSize = sizeof(esp_image_header_t);
segmentsLeft = header.segment_count;
data += sizeof(esp_image_header_t);
len -= sizeof(esp_image_header_t);
//DEBUG_PRINTF("BLS parsed image header, segment count %d, is %d\n", segmentsLeft, imageSize);
}
while (len && segmentsLeft) {
if (segmentHeaderBytes < sizeof(esp_image_segment_header_t)) {
size_t headerBytes = std::min(len, sizeof(esp_image_segment_header_t) - segmentHeaderBytes);
memcpy(reinterpret_cast<uint8_t*>(&segmentHeader) + segmentHeaderBytes, data, headerBytes);
segmentHeaderBytes += headerBytes;
if (segmentHeaderBytes < sizeof(esp_image_segment_header_t)) {
return true; // needs more bytes for the header
}
//DEBUG_PRINTF("BLS parsed segment [%08X %08X=%d], segment count %d, is %d\n", segmentHeader.load_addr, segmentHeader.data_len, segmentHeader.data_len, segmentsLeft, imageSize);
// Validate segment size
if (segmentHeader.data_len > BOOTLOADER_SIZE) {
error = true;
return false;
}
data += headerBytes;
len -= headerBytes;
imageSize += sizeof(esp_image_segment_header_t) + segmentHeader.data_len;
--segmentsLeft;
if (segmentsLeft == 0) {
// all done, actually; we don't need to read any more
// Round up to nearest 16 bytes.
// Always add 1 to account for the checksum byte.
imageSize = ((imageSize/ 16) + 1) * 16;
//DEBUG_PRINTF("BLS complete, is %d\n", imageSize);
return false;
}
}
// If we don't have enough bytes ...
if (len < segmentHeader.data_len) {
//DEBUG_PRINTF("Needs more bytes\n");
segmentHeader.data_len -= len;
return true; // still in this segment
}
// Segment complete
len -= segmentHeader.data_len;
data += segmentHeader.data_len;
segmentHeaderBytes = 0;
//DEBUG_PRINTF("Segment complete: len %d\n", len);
}
return !error;
}
bool hasError() const { return error; }
bool isSizeKnown() const { return !error && imageSize != 0 && segmentsLeft == 0; }
size_t totalSize() const {
if (!isSizeKnown()) return 0;
return imageSize;
}
private:
size_t imageSize = 0;
size_t segmentsLeft = 0;
esp_image_segment_header_t segmentHeader;
size_t segmentHeaderBytes = 0;
bool error = false;
};
static bool bootloaderSHA256CacheValid = false;
static uint8_t bootloaderSHA256Cache[32];
/**
* Calculate and cache the bootloader SHA256 digest
* Reads the bootloader from flash and computes SHA256 hash
*
* Strictly speaking, most bootloader images already contain a hash at the end of the image;
* we could in theory just read it. The trouble is that we have to parse the structure anyways
* to find the actual endpoint, so we might as well always calculate it ourselves rather than
* handle a special case if the hash isn't stored.
*
*/
static void calculateBootloaderSHA256() {
// Calculate SHA256
uint8_t sha256[32];
mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts(&ctx, 0); // 0 = SHA256 (not SHA224)
const size_t chunkSize = 256;
uint8_t buffer[chunkSize];
alignas(esp_image_header_t) uint8_t buffer[chunkSize];
size_t bootloaderSize = BOOTLOADER_SIZE;
BootloaderImageSizer sizer;
size_t totalHashLen = 0;
for (uint32_t offset = 0; offset < BOOTLOADER_SIZE; offset += chunkSize) {
size_t readSize = min((size_t)(BOOTLOADER_SIZE - offset), chunkSize);
for (uint32_t offset = 0; offset < bootloaderSize; offset += chunkSize) {
size_t readSize = min((size_t)(bootloaderSize - offset), chunkSize);
if (esp_flash_read(NULL, buffer, BOOTLOADER_OFFSET + offset, readSize) == ESP_OK) {
mbedtls_sha256_update(&ctx, buffer, readSize);
sizer.feed(buffer, readSize);
size_t hashLen = readSize;
if (sizer.isSizeKnown()) {
size_t totalSize = sizer.totalSize();
if (totalSize > 0 && totalSize <= BOOTLOADER_SIZE) {
bootloaderSize = totalSize;
if (offset + readSize > totalSize) {
hashLen = (totalSize > offset) ? (totalSize - offset) : 0;
}
}
}
if (hashLen > 0) {
totalHashLen += hashLen;
mbedtls_sha256_update(&ctx, buffer, hashLen);
}
}
}
mbedtls_sha256_finish(&ctx, sha256);
mbedtls_sha256_finish(&ctx, bootloaderSHA256Cache);
mbedtls_sha256_free(&ctx);
// Convert to hex string and cache it
char hex[65];
for (int i = 0; i < 32; i++) {
sprintf(hex + (i * 2), "%02x", sha256[i]);
}
hex[64] = '\0';
bootloaderSHA256HexCache = String(hex);
bootloaderSHA256CacheValid = true;
}
// Get bootloader SHA256 as hex string
String getBootloaderSHA256Hex() {
calculateBootloaderSHA256();
return bootloaderSHA256HexCache;
}
// Invalidate cached bootloader SHA256 (call after bootloader update)
void invalidateBootloaderSHA256Cache() {
bootloaderSHA256HexCache = "";
}
// Verify complete buffered bootloader using ESP-IDF validation approach
// This matches the key validation steps from esp_image_verify() in ESP-IDF
// Returns the actual bootloader data pointer and length via the buffer and len parameters
bool verifyBootloaderImage(const uint8_t* &buffer, size_t &len, String* bootloaderErrorMsg) {
size_t availableLen = len;
if (!bootloaderErrorMsg) {
DEBUG_PRINTLN(F("bootloaderErrorMsg is null"));
return false;
if (!bootloaderSHA256CacheValid) {
calculateBootloaderSHA256();
}
// ESP32 image header structure (based on esp_image_format.h)
// Offset 0: magic (0xE9)
// Offset 1: segment_count
// Offset 2: spi_mode
// Offset 3: spi_speed (4 bits) + spi_size (4 bits)
// Offset 4-7: entry_addr (uint32_t)
// Offset 8: wp_pin
// Offset 9-11: spi_pin_drv[3]
// Offset 12-13: chip_id (uint16_t, little-endian)
// Offset 14: min_chip_rev
// Offset 15-22: reserved[8]
// Offset 23: hash_appended
const size_t MIN_IMAGE_HEADER_SIZE = 24;
// Convert to hex string
String result;
result.reserve(65);
for (int i = 0; i < 32; i++) {
char b1 = bootloaderSHA256Cache[i];
char b2 = b1 >> 4;
b1 &= 0x0F;
b1 += '0'; b2 += '0';
if (b1 > '9') b1 += 39;
if (b2 > '9') b2 += 39;
result.concat(b2);
result.concat(b1);
}
return result;
}
/**
* Invalidate cached bootloader SHA256 (call after bootloader update)
* Forces recalculation on next call to calculateBootloaderSHA256 or getBootloaderSHA256Hex
*/
static void invalidateBootloaderSHA256Cache() {
bootloaderSHA256CacheValid = false;
}
/**
* Verify complete buffered bootloader using ESP-IDF validation approach
* This matches the key validation steps from esp_image_verify() in ESP-IDF
* @param buffer Reference to pointer to bootloader binary data (will be adjusted if offset detected)
* @param len Reference to length of bootloader data (will be adjusted to actual size)
* @param bootloaderErrorMsg Pointer to String to store error message (must not be null)
* @return true if validation passed, false otherwise
*/
static bool verifyBootloaderImage(const uint8_t* &buffer, size_t &len, String& bootloaderErrorMsg) {
const size_t MIN_IMAGE_HEADER_SIZE = sizeof(esp_image_header_t);
// 1. Validate minimum size for header
if (len < MIN_IMAGE_HEADER_SIZE) {
*bootloaderErrorMsg = "Bootloader too small - invalid header";
bootloaderErrorMsg = "Too small";
return false;
}
// Check if the bootloader starts at offset 0x1000 (common in partition table dumps)
// This happens when someone uploads a complete flash dump instead of just the bootloader
if (len > BOOTLOADER_OFFSET + MIN_IMAGE_HEADER_SIZE &&
buffer[BOOTLOADER_OFFSET] == 0xE9 &&
buffer[0] != 0xE9) {
DEBUG_PRINTF_P(PSTR("Bootloader magic byte detected at offset 0x%04X - adjusting buffer\n"), BOOTLOADER_OFFSET);
buffer[BOOTLOADER_OFFSET] == ESP_IMAGE_HEADER_MAGIC &&
buffer[0] != ESP_IMAGE_HEADER_MAGIC) {
DEBUG_PRINTF_P(PSTR("Bootloader detected at offset\n"));
// Adjust buffer pointer to start at the actual bootloader
buffer = buffer + BOOTLOADER_OFFSET;
len = len - BOOTLOADER_OFFSET;
// Re-validate size after adjustment
if (len < MIN_IMAGE_HEADER_SIZE) {
*bootloaderErrorMsg = "Bootloader at offset 0x1000 too small - invalid header";
bootloaderErrorMsg = "Too small";
return false;
}
}
// 2. Magic byte check (matches esp_image_verify step 1)
if (buffer[0] != 0xE9) {
*bootloaderErrorMsg = "Invalid bootloader magic byte (expected 0xE9, got 0x" + String(buffer[0], HEX) + ")";
size_t availableLen = len;
esp_image_header_t imageHeader{};
memcpy(&imageHeader, buffer, sizeof(imageHeader));
// 2. Basic header sanity checks (matches early esp_image_verify checks)
if (imageHeader.magic != ESP_IMAGE_HEADER_MAGIC ||
imageHeader.segment_count == 0 || imageHeader.segment_count > 16 ||
imageHeader.spi_mode > 3 ||
imageHeader.entry_addr < 0x40000000 || imageHeader.entry_addr > 0x50000000) {
bootloaderErrorMsg = "Invalid header";
return false;
}
// 3. Segment count validation (matches esp_image_verify step 2)
uint8_t segmentCount = buffer[1];
if (segmentCount == 0 || segmentCount > 16) {
*bootloaderErrorMsg = "Invalid segment count: " + String(segmentCount);
// 3. Chip ID validation (matches esp_image_verify step 3)
if (imageHeader.chip_id != CONFIG_IDF_FIRMWARE_CHIP_ID) {
bootloaderErrorMsg = "Chip ID mismatch";
return false;
}
// 4. SPI mode validation (basic sanity check)
uint8_t spiMode = buffer[2];
if (spiMode > 3) { // Valid modes are 0-3 (QIO, QOUT, DIO, DOUT)
*bootloaderErrorMsg = "Invalid SPI mode: " + String(spiMode);
// 4. Validate image size
BootloaderImageSizer sizer;
sizer.feed(buffer, availableLen);
if (!sizer.isSizeKnown()) {
bootloaderErrorMsg = "Invalid image";
return false;
}
size_t actualBootloaderSize = sizer.totalSize();
// 5. Chip ID validation (matches esp_image_verify step 3)
uint16_t chipId = buffer[12] | (buffer[13] << 8); // Little-endian
// Known ESP32 chip IDs from ESP-IDF:
// 0x0000 = ESP32
// 0x0002 = ESP32-S2
// 0x0005 = ESP32-C3
// 0x0009 = ESP32-S3
// 0x000C = ESP32-C2
// 0x000D = ESP32-C6
// 0x0010 = ESP32-H2
#if defined(CONFIG_IDF_TARGET_ESP32)
if (chipId != 0x0000) {
*bootloaderErrorMsg = "Chip ID mismatch - expected ESP32 (0x0000), got 0x" + String(chipId, HEX);
return false;
}
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
if (chipId != 0x0002) {
*bootloaderErrorMsg = "Chip ID mismatch - expected ESP32-S2 (0x0002), got 0x" + String(chipId, HEX);
return false;
}
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
if (chipId != 0x0005) {
*bootloaderErrorMsg = "Chip ID mismatch - expected ESP32-C3 (0x0005), got 0x" + String(chipId, HEX);
return false;
}
*bootloaderErrorMsg = "ESP32-C3 update not supported yet";
return false;
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
if (chipId != 0x0009) {
*bootloaderErrorMsg = "Chip ID mismatch - expected ESP32-S3 (0x0009), got 0x" + String(chipId, HEX);
return false;
}
*bootloaderErrorMsg = "ESP32-S3 update not supported yet";
return false;
#elif defined(CONFIG_IDF_TARGET_ESP32C6)
if (chipId != 0x000D) {
*bootloaderErrorMsg = "Chip ID mismatch - expected ESP32-C6 (0x000D), got 0x" + String(chipId, HEX);
return false;
}
*bootloaderErrorMsg = "ESP32-C6 update not supported yet";
return false;
#else
// Generic validation - chip ID should be valid
if (chipId > 0x00FF) {
*bootloaderErrorMsg = "Invalid chip ID: 0x" + String(chipId, HEX);
return false;
}
*bootloaderErrorMsg = "Unknown ESP32 target - bootloader update not supported";
return false;
#endif
// 6. Entry point validation (should be in valid memory range)
uint32_t entryAddr = buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24);
// ESP32 bootloader entry points are typically in IRAM range (0x40000000 - 0x40400000)
// or ROM range (0x40000000 and above)
if (entryAddr < 0x40000000 || entryAddr > 0x50000000) {
*bootloaderErrorMsg = "Invalid entry address: 0x" + String(entryAddr, HEX);
return false;
}
// 7. Basic segment structure validation
// Each segment has a header: load_addr (4 bytes) + data_len (4 bytes)
size_t offset = MIN_IMAGE_HEADER_SIZE;
size_t actualBootloaderSize = MIN_IMAGE_HEADER_SIZE;
for (uint8_t i = 0; i < segmentCount && offset + 8 <= len; i++) {
uint32_t segmentSize = buffer[offset + 4] | (buffer[offset + 5] << 8) |
(buffer[offset + 6] << 16) | (buffer[offset + 7] << 24);
// Segment size sanity check
// ESP32 classic bootloader segments can be larger, C3 are smaller
if (segmentSize > 0x20000) { // 128KB max per segment (very generous)
*bootloaderErrorMsg = "Segment " + String(i) + " too large: " + String(segmentSize) + " bytes";
return false;
}
offset += 8 + segmentSize; // Skip segment header and data
}
actualBootloaderSize = offset;
// 8. Check for appended SHA256 hash (byte 23 in header)
// If hash_appended != 0, there's a 32-byte SHA256 hash after the segments
uint8_t hashAppended = buffer[23];
if (hashAppended != 0) {
// 5. SHA256 checksum (optional)
if (imageHeader.hash_appended == 1) {
actualBootloaderSize += 32;
if (actualBootloaderSize > availableLen) {
*bootloaderErrorMsg = "Bootloader missing SHA256 trailer";
return false;
}
DEBUG_PRINTF_P(PSTR("Bootloader has appended SHA256 hash\n"));
}
// 9. The image may also have a 1-byte checksum after segments/hash
// Check if there's at least one more byte available
if (actualBootloaderSize + 1 <= availableLen) {
// There's likely a checksum byte
actualBootloaderSize += 1;
} else if (actualBootloaderSize > availableLen) {
*bootloaderErrorMsg = "Bootloader truncated before checksum";
if (actualBootloaderSize > len) {
// Same as above
bootloaderErrorMsg = "Too small";
return false;
}
// 10. Align to 16 bytes (ESP32 requirement for flash writes)
// The bootloader image must be 16-byte aligned
if (actualBootloaderSize % 16 != 0) {
size_t alignedSize = ((actualBootloaderSize + 15) / 16) * 16;
// Make sure we don't exceed available data
if (alignedSize <= len) {
actualBootloaderSize = alignedSize;
}
}
DEBUG_PRINTF_P(PSTR("Bootloader validation: %d segments, actual size %d bytes (buffer size %d bytes, hash_appended=%d)\n"),
segmentCount, actualBootloaderSize, len, hashAppended);
// 11. Verify we have enough data for all segments + hash + checksum
if (actualBootloaderSize > availableLen) {
*bootloaderErrorMsg = "Bootloader truncated - expected at least " + String(actualBootloaderSize) + " bytes, have " + String(availableLen) + " bytes";
return false;
}
if (offset > availableLen) {
*bootloaderErrorMsg = "Bootloader truncated - expected at least " + String(offset) + " bytes, have " + String(len) + " bytes";
return false;
}
imageHeader.segment_count, actualBootloaderSize, len, imageHeader.hash_appended);
// Update len to reflect actual bootloader size (including hash and checksum, with alignment)
// This is critical - we must write the complete image including checksums
@@ -584,10 +603,13 @@ bool initBootloaderOTA(AsyncWebServerRequest *request) {
DEBUG_PRINTLN(F("Failed to allocate bootloader OTA context"));
return false;
}
request->_tempObject = context;
request->onDisconnect([=]() { endBootloaderOTA(request); }); // ensures cleanup on disconnect
#ifdef BOOTLOADER_OTA_UNSUPPORTED
context->errorMessage = F("Bootloader update not supported on this chip");
return false;
#else
DEBUG_PRINTLN(F("Bootloader Update Start - initializing buffer"));
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().disableWatchdog();
@@ -597,14 +619,13 @@ bool initBootloaderOTA(AsyncWebServerRequest *request) {
strip.resetSegments();
// Check available heap before attempting allocation
size_t freeHeap = getFreeHeapSize();
DEBUG_PRINTF_P(PSTR("Free heap before bootloader buffer allocation: %d bytes (need %d bytes)\n"), freeHeap, context->maxBootloaderSize);
DEBUG_PRINTF_P(PSTR("Free heap before bootloader buffer allocation: %d bytes (need %d bytes)\n"), getContiguousFreeHeap(), context->maxBootloaderSize);
context->buffer = (uint8_t*)malloc(context->maxBootloaderSize);
if (!context->buffer) {
size_t freeHeapNow = getFreeHeapSize();
DEBUG_PRINTF_P(PSTR("Failed to allocate %d byte bootloader buffer! Free heap: %d bytes\n"), context->maxBootloaderSize, freeHeapNow);
context->errorMessage = "Out of memory! Free heap: " + String(freeHeapNow) + " bytes, need: " + String(context->maxBootloaderSize) + " bytes";
size_t freeHeapNow = getContiguousFreeHeap();
DEBUG_PRINTF_P(PSTR("Failed to allocate %d byte bootloader buffer! Contiguous heap: %d bytes\n"), context->maxBootloaderSize, freeHeapNow);
context->errorMessage = "Out of memory! Contiguous heap: " + String(freeHeapNow) + " bytes, need: " + String(context->maxBootloaderSize) + " bytes";
strip.resume();
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().enableWatchdog();
@@ -614,6 +635,7 @@ bool initBootloaderOTA(AsyncWebServerRequest *request) {
context->bytesBuffered = 0;
return true;
#endif
}
// Set bootloader OTA replied flag
@@ -693,7 +715,7 @@ void handleBootloaderOTAData(AsyncWebServerRequest *request, size_t index, uint8
// Verify the complete bootloader image before flashing
// Note: verifyBootloaderImage may adjust bootloaderData pointer and bootloaderSize
// for validation purposes only
if (!verifyBootloaderImage(bootloaderData, bootloaderSize, &context->errorMessage)) {
if (!verifyBootloaderImage(bootloaderData, bootloaderSize, context->errorMessage)) {
DEBUG_PRINTLN(F("Bootloader validation failed!"));
// Error message already set by verifyBootloaderImage
} else {

View File

@@ -58,11 +58,6 @@ void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data,
void markOTAvalid();
#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
/**
* Calculate and cache the bootloader SHA256 digest
* Reads the bootloader from flash at offset 0x1000 and computes SHA256 hash
*/
void calculateBootloaderSHA256();
/**
* Get bootloader SHA256 as hex string
@@ -70,22 +65,6 @@ void calculateBootloaderSHA256();
*/
String getBootloaderSHA256Hex();
/**
* Invalidate cached bootloader SHA256 (call after bootloader update)
* Forces recalculation on next call to calculateBootloaderSHA256 or getBootloaderSHA256Hex
*/
void invalidateBootloaderSHA256Cache();
/**
* Verify complete buffered bootloader using ESP-IDF validation approach
* This matches the key validation steps from esp_image_verify() in ESP-IDF
* @param buffer Reference to pointer to bootloader binary data (will be adjusted if offset detected)
* @param len Reference to length of bootloader data (will be adjusted to actual size)
* @param bootloaderErrorMsg Pointer to String to store error message (must not be null)
* @return true if validation passed, false otherwise
*/
bool verifyBootloaderImage(const uint8_t* &buffer, size_t &len, String* bootloaderErrorMsg);
/**
* Create a bootloader OTA context object on an AsyncWebServerRequest
* @param request Pointer to web request object