Skip to content

Calculate SHA256 over firmware image uploaded via OTA #174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 20, 2020
Merged
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
17 changes: 17 additions & 0 deletions src/ArduinoIoTCloudTCP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -94,6 +95,7 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP()
, _ota_topic_in{""}
#if OTA_ENABLED
, _ota_error{static_cast<int>(OTAError::None)}
, _ota_img_sha256{"Inv."}
#endif /* OTA_ENABLED */
{

Expand All @@ -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; }
Expand Down Expand Up @@ -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 */
Expand Down
1 change: 1 addition & 0 deletions src/ArduinoIoTCloudTCP.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"); }
Expand Down
47 changes: 47 additions & 0 deletions src/tls/utility/SHA256.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
48 changes: 48 additions & 0 deletions src/tls/utility/SHA256.h
Original file line number Diff line number Diff line change
@@ -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_ */
109 changes: 109 additions & 0 deletions src/utility/ota/FlashSHA256.cpp
Original file line number Diff line number Diff line change
@@ -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 <AIoTC_Config.h>
#if OTA_ENABLED

#include "FlashSHA256.h"

#include "../../tls/utility/SHA256.h"

#include <Arduino_DebugUtils.h>

#undef max
#undef min
#include <algorithm>

/******************************************************************************
* 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<const void *>(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<const void *>(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 */
51 changes: 51 additions & 0 deletions src/utility/ota/FlashSHA256.h
Original file line number Diff line number Diff line change
@@ -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 <AIoTC_Config.h>
#if OTA_ENABLED

#include <Arduino.h>

/******************************************************************************
* 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_ */
18 changes: 9 additions & 9 deletions src/utility/ota/OTALogic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand All @@ -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();
Expand All @@ -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();
Expand Down