diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 1fef05472..f195c41ca 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -29,6 +29,7 @@ #include "tls/utility/CryptoUtil.h" #endif +#include "utility/ota/FlashSHA256.h" #include "utility/ota/OTAStorage_SNU.h" #include "utility/ota/OTAStorage_SFU.h" #include "utility/ota/OTAStorage_SSU.h" @@ -94,6 +95,7 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() , _ota_topic_in{""} #if OTA_ENABLED , _ota_error{static_cast(OTAError::None)} +, _ota_img_sha256{"Inv."} #endif /* OTA_ENABLED */ { @@ -118,6 +120,20 @@ int ArduinoIoTCloudTCP::begin(String brokerAddress, uint16_t brokerPort) _brokerAddress = brokerAddress; _brokerPort = brokerPort; +#if OTA_ENABLED + /* Calculate the SHA256 checksum over the firmware stored in the flash of the + * MCU. Note: As we don't know the length per-se we read chunks of the flash + * until we detect one containing only 0xFF (= flash erased). This only works + * for firmware updated via OTA and second stage bootloaders (SxU family) + * because only those erase the complete flash before performing an update. + * Since the SHA256 firmware image is only required for the cloud servers to + * perform a version check after the OTA update this is a acceptable trade off. + * The bootloader is excluded from the calculation and occupies flash address + * range 0 to 0x2000, total flash size of 0x40000 bytes (256 kByte). + */ + _ota_img_sha256 = FlashSHA256::calc(0x2000, 0x40000 - 0x2000); +#endif /* OTA_ENABLED */ + #ifdef BOARD_HAS_ECCX08 if (!ECCX08.begin()) { DBG_ERROR("Cryptography processor failure. Make sure you have a compatible board."); return 0; } if (!CryptoUtil::readDeviceId(ECCX08, getDeviceId(), ECCX08Slot::DeviceId)) { DBG_ERROR("Cryptography processor read failure."); return 0; } @@ -229,6 +245,7 @@ void ArduinoIoTCloudTCP::printDebugInfo() void ArduinoIoTCloudTCP::setOTAStorage(OTAStorage & ota_storage) { addPropertyReal(_ota_error, "OTA_ERROR", Permission::Read); + addPropertyReal(_ota_img_sha256, "OTA_SHA256", Permission::Read); _ota_logic.setOTAStorage(ota_storage); } #endif /* OTA_ENABLED */ diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index 6bc6f0ca0..2006ee7de 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -127,6 +127,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass #if OTA_ENABLED OTALogic _ota_logic; int _ota_error; + String _ota_img_sha256; #endif /* OTA_ENABLED */ inline String getTopic_stdin () { return String("/a/d/" + getDeviceId() + "/s/i"); } diff --git a/src/tls/utility/SHA256.cpp b/src/tls/utility/SHA256.cpp new file mode 100644 index 000000000..ffed6e587 --- /dev/null +++ b/src/tls/utility/SHA256.cpp @@ -0,0 +1,47 @@ +/* + This file is part of ArduinoIoTCloud. + + Copyright 2020 ARDUINO SA (http://www.arduino.cc/) + + This software is released under the GNU General Public License version 3, + which covers the main part of arduino-cli. + The terms of this license can be found at: + https://www.gnu.org/licenses/gpl-3.0.en.html + + You can be released from the requirements of the above licenses by purchasing + a commercial license. Buying such a license is mandatory if you want to modify or + otherwise use the software for commercial activities involving the Arduino + software without disclosing the source code of your own applications. To purchase + a commercial license, send an email to license@arduino.cc. +*/ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include "SHA256.h" + +/****************************************************************************** + * STATIC MEMBER DECLARATION + ******************************************************************************/ + +constexpr size_t SHA256::HASH_SIZE; + +/****************************************************************************** + * PUBLIC MEMBER FUNCTIONS + ******************************************************************************/ + +void SHA256::begin() +{ + br_sha256_init(&_ctx); +} + +void SHA256::update(uint8_t const * data, size_t const len) +{ + br_sha256_update(&_ctx, data, len); +} + +void SHA256::finalize(uint8_t * hash) +{ + br_sha256_out(&_ctx, hash); +} diff --git a/src/tls/utility/SHA256.h b/src/tls/utility/SHA256.h new file mode 100644 index 000000000..ed0ce1d5a --- /dev/null +++ b/src/tls/utility/SHA256.h @@ -0,0 +1,48 @@ +/* + This file is part of ArduinoIoTCloud. + + Copyright 2020 ARDUINO SA (http://www.arduino.cc/) + + This software is released under the GNU General Public License version 3, + which covers the main part of arduino-cli. + The terms of this license can be found at: + https://www.gnu.org/licenses/gpl-3.0.en.html + + You can be released from the requirements of the above licenses by purchasing + a commercial license. Buying such a license is mandatory if you want to modify or + otherwise use the software for commercial activities involving the Arduino + software without disclosing the source code of your own applications. To purchase + a commercial license, send an email to license@arduino.cc. +*/ + +#ifndef ARDUINO_TLS_UTILITY_SHA256_H_ +#define ARDUINO_TLS_UTILITY_SHA256_H_ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include "../bearssl/bearssl_hash.h" + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class SHA256 +{ + +public: + + static constexpr size_t HASH_SIZE = 32; + + void begin (); + void update (uint8_t const * data, size_t const len); + void finalize(uint8_t * hash); + +private: + + br_sha256_context _ctx; + +}; + +#endif /* ARDUINO_TLS_UTILITY_SHA256_H_ */ diff --git a/src/utility/ota/FlashSHA256.cpp b/src/utility/ota/FlashSHA256.cpp new file mode 100644 index 000000000..6c3173774 --- /dev/null +++ b/src/utility/ota/FlashSHA256.cpp @@ -0,0 +1,109 @@ +/* + This file is part of ArduinoIoTCloud. + + Copyright 2020 ARDUINO SA (http://www.arduino.cc/) + + This software is released under the GNU General Public License version 3, + which covers the main part of arduino-cli. + The terms of this license can be found at: + https://www.gnu.org/licenses/gpl-3.0.en.html + + You can be released from the requirements of the above licenses by purchasing + a commercial license. Buying such a license is mandatory if you want to modify or + otherwise use the software for commercial activities involving the Arduino + software without disclosing the source code of your own applications. To purchase + a commercial license, send an email to license@arduino.cc. +*/ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include +#if OTA_ENABLED + +#include "FlashSHA256.h" + +#include "../../tls/utility/SHA256.h" + +#include + +#undef max +#undef min +#include + +/****************************************************************************** + * PUBLIC MEMBER FUNCTIONS + ******************************************************************************/ + +String FlashSHA256::calc(uint32_t const start_addr, uint32_t const max_flash_size) +{ + SHA256 sha256; + uint8_t chunk [FLASH_READ_CHUNK_SIZE], + next_chunk[FLASH_READ_CHUNK_SIZE]; + + sha256.begin(); + + /* Read the first two chunks of flash. */ + uint32_t flash_addr = start_addr; + memcpy(chunk, reinterpret_cast(flash_addr), FLASH_READ_CHUNK_SIZE); + flash_addr += FLASH_READ_CHUNK_SIZE; + + for(; flash_addr < max_flash_size; flash_addr += FLASH_READ_CHUNK_SIZE) + { + /* Read the next chunk of memory. */ + memcpy(next_chunk, reinterpret_cast(flash_addr), FLASH_READ_CHUNK_SIZE); + + /* Check if the next segment is erased, that is if all bytes within + * a read segment are 0xFF -> then we've reached the end of the firmware. + */ + bool const next_chunk_is_erased_flash = std::all_of(next_chunk, + next_chunk+FLASH_READ_CHUNK_SIZE, + [](uint8_t const elem) { return (elem == 0xFF); }); + /* Determine how many bytes at the end of the current chunk are + * already set to 0xFF and therefore erased/non-written flash + * memory. + */ + if (next_chunk_is_erased_flash) + { + /* Eliminate trailing 0xFF. */ + size_t valid_bytes_in_chunk = 0; + for(valid_bytes_in_chunk = FLASH_READ_CHUNK_SIZE; valid_bytes_in_chunk > 0; valid_bytes_in_chunk--) + { + if (chunk[valid_bytes_in_chunk-1] != 0xFF) + break; + } + DBG_VERBOSE("FlashSHA256::calc: end of firmware, %d valid bytes in last read chunk", valid_bytes_in_chunk); + /* Update with the remaining bytes. */ + sha256.update(chunk, valid_bytes_in_chunk); + break; + } + + /* We've read a normal segment with the next segment not containing + * any erased elements, just update the SHA256 hash calcultion. + */ + sha256.update(chunk, FLASH_READ_CHUNK_SIZE); + + /* Copy next_chunk to chunk. */ + memcpy(chunk, next_chunk, FLASH_READ_CHUNK_SIZE); + } + + /* Retrieve the final hash string. */ + uint8_t sha256_hash[SHA256::HASH_SIZE] = {0}; + sha256.finalize(sha256_hash); + String sha256_str; + std::for_each(sha256_hash, + sha256_hash + SHA256::HASH_SIZE, + [&sha256_str](uint8_t const elem) + { + char buf[4]; + snprintf(buf, 4, "%02X", elem); + sha256_str += buf; + }); + /* Do some debug printout. */ + DBG_VERBOSE("SHA256: %d bytes read", flash_addr); + DBG_VERBOSE("SHA256: HASH(%d) = %s", strlen(sha256_str.c_str()), sha256_str.c_str()); + return sha256_str; +} + +#endif /* OTA_ENABLED */ diff --git a/src/utility/ota/FlashSHA256.h b/src/utility/ota/FlashSHA256.h new file mode 100644 index 000000000..a3125f717 --- /dev/null +++ b/src/utility/ota/FlashSHA256.h @@ -0,0 +1,51 @@ +/* + This file is part of ArduinoIoTCloud. + + Copyright 2020 ARDUINO SA (http://www.arduino.cc/) + + This software is released under the GNU General Public License version 3, + which covers the main part of arduino-cli. + The terms of this license can be found at: + https://www.gnu.org/licenses/gpl-3.0.en.html + + You can be released from the requirements of the above licenses by purchasing + a commercial license. Buying such a license is mandatory if you want to modify or + otherwise use the software for commercial activities involving the Arduino + software without disclosing the source code of your own applications. To purchase + a commercial license, send an email to license@arduino.cc. +*/ + +#ifndef ARDUINO_OTA_FLASH_SHA256_H_ +#define ARDUINO_OTA_FLASH_SHA256_H_ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include +#if OTA_ENABLED + +#include + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class FlashSHA256 +{ +public: + + static String calc(uint32_t const start_addr, uint32_t const max_flash_size); + +private: + + FlashSHA256() { } + FlashSHA256(FlashSHA256 const &) { } + + static constexpr uint32_t FLASH_READ_CHUNK_SIZE = 64; + +}; + +#endif /* OTA_ENABLED */ + +#endif /* ARDUINO_OTA_FLASH_SHA256_H_ */ diff --git a/src/utility/ota/OTALogic.cpp b/src/utility/ota/OTALogic.cpp index 60c9f9a38..b04b4d9f2 100644 --- a/src/utility/ota/OTALogic.cpp +++ b/src/utility/ota/OTALogic.cpp @@ -125,7 +125,7 @@ void OTALogic::onOTADataReceived(uint8_t const * const data, size_t const length OTAState OTALogic::handle_Init() { #ifndef HOST - DBG_VERBOSE(__PRETTY_FUNCTION__); + DBG_VERBOSE("OTALogic::%s", __FUNCTION__); #endif if (_ota_storage->init()) { return OTAState::Idle; @@ -146,7 +146,7 @@ OTAState OTALogic::handle_Idle() OTAState OTALogic::handle_StartDownload() { #ifndef HOST - DBG_VERBOSE(__PRETTY_FUNCTION__); + DBG_VERBOSE("OTALogic::%s", __FUNCTION__); #endif if(_ota_storage->open()) { return OTAState::WaitForHeader; @@ -159,7 +159,7 @@ OTAState OTALogic::handle_StartDownload() OTAState OTALogic::handle_WaitForHeader() { #ifndef HOST - DBG_VERBOSE(__PRETTY_FUNCTION__); + DBG_VERBOSE("OTALogic::%s", __FUNCTION__); #endif if(_mqtt_ota_buf.num_bytes >= OTA_BINARY_HEADER_SIZE) { return OTAState::HeaderReceived; @@ -190,8 +190,8 @@ OTAState OTALogic::handle_HeaderReceived() _ota_bin_data.hdr_crc32 = ota_header.header.crc32; #ifndef HOST - DBG_VERBOSE("%s: header length = %d", __PRETTY_FUNCTION__, _ota_bin_data.hdr_len); - DBG_VERBOSE("%s: header CRC32 = %d", __PRETTY_FUNCTION__, _ota_bin_data.hdr_crc32); + DBG_VERBOSE("OTALogic::%s: header length = %d", __FUNCTION__, _ota_bin_data.hdr_len); + DBG_VERBOSE("OTALogic::%s: header crc32 = %X", __FUNCTION__, _ota_bin_data.hdr_crc32); #endif /* Reset the counter which is responsible for keeping tabs on how many bytes have been received */ @@ -234,7 +234,7 @@ OTAState OTALogic::handle_BinaryReceived() _mqtt_ota_buf.num_bytes = 0; #ifndef HOST - DBG_VERBOSE("%s: %d bytes written", __PRETTY_FUNCTION__, _ota_bin_data.bytes_received); + DBG_VERBOSE("OTALogic::%s: %d bytes written", __FUNCTION__, _ota_bin_data.bytes_received); #endif if(_ota_bin_data.bytes_received >= _ota_bin_data.hdr_len) { @@ -249,7 +249,7 @@ OTAState OTALogic::handle_BinaryReceived() OTAState OTALogic::handle_Verify() { #ifndef HOST - DBG_VERBOSE(__PRETTY_FUNCTION__); + DBG_VERBOSE("OTALogic::%s", __FUNCTION__); #endif if(_ota_bin_data.crc32 == _ota_bin_data.hdr_crc32) { return OTAState::Rename; @@ -263,7 +263,7 @@ OTAState OTALogic::handle_Verify() OTAState OTALogic::handle_Rename() { #ifndef HOST - DBG_VERBOSE(__PRETTY_FUNCTION__); + DBG_VERBOSE("OTALogic::%s", __FUNCTION__); #endif if(_ota_storage->rename()) { _ota_storage->deinit(); @@ -286,7 +286,7 @@ OTAState OTALogic::handle_Reset() * is started directly. */ #ifndef HOST - DBG_VERBOSE(__PRETTY_FUNCTION__); + DBG_VERBOSE("OTALogic::%s", __FUNCTION__); delay(250); #endif NVIC_SystemReset();