diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 7fd16ce3d0..405633b990 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -268,14 +268,14 @@ void ESPHomeOTAComponent::handle_data_() { // TODO: Remove this entire ifdef block in 2026.1.0 if (client_supports_sha256) { sha256::SHA256 sha_hasher; - auth_success = this->perform_hash_auth_(&sha_hasher, this->password_, 16, ota::OTA_RESPONSE_REQUEST_SHA256_AUTH, + auth_success = this->perform_hash_auth_(&sha_hasher, this->password_, ota::OTA_RESPONSE_REQUEST_SHA256_AUTH, LOG_STR("SHA256"), sbuf); } else { #ifdef USE_OTA_MD5 ESP_LOGW(TAG, "Using MD5 auth for compatibility (deprecated)"); md5::MD5Digest md5_hasher; - auth_success = this->perform_hash_auth_(&md5_hasher, this->password_, 8, ota::OTA_RESPONSE_REQUEST_AUTH, - LOG_STR("MD5"), sbuf); + auth_success = + this->perform_hash_auth_(&md5_hasher, this->password_, ota::OTA_RESPONSE_REQUEST_AUTH, LOG_STR("MD5"), sbuf); #endif // USE_OTA_MD5 } #else @@ -286,7 +286,7 @@ void ESPHomeOTAComponent::handle_data_() { goto error; // NOLINT(cppcoreguidelines-avoid-goto) } sha256::SHA256 sha_hasher; - auth_success = this->perform_hash_auth_(&sha_hasher, this->password_, 16, ota::OTA_RESPONSE_REQUEST_SHA256_AUTH, + auth_success = this->perform_hash_auth_(&sha_hasher, this->password_, ota::OTA_RESPONSE_REQUEST_SHA256_AUTH, LOG_STR("SHA256"), sbuf); #endif // ALLOW_OTA_DOWNGRADE_MD5 #else @@ -295,7 +295,7 @@ void ESPHomeOTAComponent::handle_data_() { #ifdef USE_OTA_MD5 md5::MD5Digest md5_hasher; auth_success = - this->perform_hash_auth_(&md5_hasher, this->password_, 8, ota::OTA_RESPONSE_REQUEST_AUTH, LOG_STR("MD5"), sbuf); + this->perform_hash_auth_(&md5_hasher, this->password_, ota::OTA_RESPONSE_REQUEST_AUTH, LOG_STR("MD5"), sbuf); #endif // USE_OTA_MD5 #endif // USE_OTA_SHA256 @@ -528,11 +528,20 @@ void ESPHomeOTAComponent::log_auth_warning_(const LogString *action, const LogSt ESP_LOGW(TAG, "Auth: %s %s failed", LOG_STR_ARG(action), LOG_STR_ARG(hash_name)); } +// Helper to convert uint32 to big-endian bytes +static inline void uint32_to_bytes(uint32_t value, uint8_t *bytes) { + bytes[0] = (value >> 24) & 0xFF; + bytes[1] = (value >> 16) & 0xFF; + bytes[2] = (value >> 8) & 0xFF; + bytes[3] = value & 0xFF; +} + // Non-template function definition to reduce binary size -bool ESPHomeOTAComponent::perform_hash_auth_(HashBase *hasher, const std::string &password, size_t nonce_size, - uint8_t auth_request, const LogString *name, char *buf) { +bool ESPHomeOTAComponent::perform_hash_auth_(HashBase *hasher, const std::string &password, uint8_t auth_request, + const LogString *name, char *buf) { // Get sizes from the hasher - const size_t hex_size = hasher->get_size() * 2; // Hex is twice the byte size + const size_t hex_size = hasher->get_size() * 2; // Hex is twice the byte size + const size_t nonce_len = hasher->get_size() / 4; // Nonce is 1/4 of hash size in bytes // Use the provided buffer for all hex operations @@ -545,25 +554,11 @@ bool ESPHomeOTAComponent::perform_hash_auth_(HashBase *hasher, const std::string hasher->init(); // Generate nonce seed bytes - uint32_t r1 = random_uint32(); - // Convert first uint32 to bytes (always needed for MD5) - nonce_bytes[0] = (r1 >> 24) & 0xFF; - nonce_bytes[1] = (r1 >> 16) & 0xFF; - nonce_bytes[2] = (r1 >> 8) & 0xFF; - nonce_bytes[3] = r1 & 0xFF; - - if (nonce_size == 8) { - // MD5: 8 chars = "%08x" format = 4 bytes from one random uint32 - hasher->add(nonce_bytes, 4); - } else { - // SHA256: 16 chars = "%08x%08x" format = 8 bytes from two random uint32s - uint32_t r2 = random_uint32(); - nonce_bytes[4] = (r2 >> 24) & 0xFF; - nonce_bytes[5] = (r2 >> 16) & 0xFF; - nonce_bytes[6] = (r2 >> 8) & 0xFF; - nonce_bytes[7] = r2 & 0xFF; - hasher->add(nonce_bytes, 8); + uint32_to_bytes(random_uint32(), nonce_bytes); + if (nonce_len > 4) { + uint32_to_bytes(random_uint32(), nonce_bytes + 4); } + hasher->add(nonce_bytes, nonce_len); hasher->calculate(); // Generate and send nonce diff --git a/esphome/components/esphome/ota/ota_esphome.h b/esphome/components/esphome/ota/ota_esphome.h index 39f2f878de..5bacb60706 100644 --- a/esphome/components/esphome/ota/ota_esphome.h +++ b/esphome/components/esphome/ota/ota_esphome.h @@ -32,8 +32,8 @@ class ESPHomeOTAComponent : public ota::OTAComponent { void handle_handshake_(); void handle_data_(); #ifdef USE_OTA_PASSWORD - bool perform_hash_auth_(HashBase *hasher, const std::string &password, size_t nonce_size, uint8_t auth_request, - const LogString *name, char *buf); + bool perform_hash_auth_(HashBase *hasher, const std::string &password, uint8_t auth_request, const LogString *name, + char *buf); void log_auth_warning_(const LogString *action, const LogString *hash_name); #endif // USE_OTA_PASSWORD bool readall_(uint8_t *buf, size_t len); diff --git a/esphome/espota2.py b/esphome/espota2.py index 2a4d21dc3e..2712d00127 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -166,7 +166,7 @@ def check_error(data: list[int] | bytes, expect: int | list[int] | None) -> None raise OTAError("Error: Authentication invalid. Is the password correct?") if dat == RESPONSE_ERROR_WRITING_FLASH: raise OTAError( - "Error: Wring OTA data to flash memory failed. See USB logs for more " + "Error: Writing OTA data to flash memory failed. See USB logs for more " "information." ) if dat == RESPONSE_ERROR_UPDATE_END: diff --git a/tests/unit_tests/test_espota2.py b/tests/unit_tests/test_espota2.py index c036a5de8e..bd1a6bde81 100644 --- a/tests/unit_tests/test_espota2.py +++ b/tests/unit_tests/test_espota2.py @@ -149,7 +149,7 @@ def test_receive_exactly_socket_error(mock_socket: Mock) -> None: (espota2.RESPONSE_ERROR_AUTH_INVALID, "Error: Authentication invalid"), ( espota2.RESPONSE_ERROR_WRITING_FLASH, - "Error: Wring OTA data to flash memory failed", + "Error: Writing OTA data to flash memory failed", ), (espota2.RESPONSE_ERROR_UPDATE_END, "Error: Finishing update failed"), (