Skip to content

Commit b6a1257

Browse files
authored
Merge pull request #174 from arduino-libraries/sha256-firmware-image
Calculate SHA256 over firmware image uploaded via OTA
2 parents 923931a + 2130a9a commit b6a1257

File tree

7 files changed

+282
-9
lines changed

7 files changed

+282
-9
lines changed

Diff for: src/ArduinoIoTCloudTCP.cpp

+17
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "tls/utility/CryptoUtil.h"
3030
#endif
3131

32+
#include "utility/ota/FlashSHA256.h"
3233
#include "utility/ota/OTAStorage_SNU.h"
3334
#include "utility/ota/OTAStorage_SFU.h"
3435
#include "utility/ota/OTAStorage_SSU.h"
@@ -94,6 +95,7 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP()
9495
, _ota_topic_in{""}
9596
#if OTA_ENABLED
9697
, _ota_error{static_cast<int>(OTAError::None)}
98+
, _ota_img_sha256{"Inv."}
9799
#endif /* OTA_ENABLED */
98100
{
99101

@@ -118,6 +120,20 @@ int ArduinoIoTCloudTCP::begin(String brokerAddress, uint16_t brokerPort)
118120
_brokerAddress = brokerAddress;
119121
_brokerPort = brokerPort;
120122

123+
#if OTA_ENABLED
124+
/* Calculate the SHA256 checksum over the firmware stored in the flash of the
125+
* MCU. Note: As we don't know the length per-se we read chunks of the flash
126+
* until we detect one containing only 0xFF (= flash erased). This only works
127+
* for firmware updated via OTA and second stage bootloaders (SxU family)
128+
* because only those erase the complete flash before performing an update.
129+
* Since the SHA256 firmware image is only required for the cloud servers to
130+
* perform a version check after the OTA update this is a acceptable trade off.
131+
* The bootloader is excluded from the calculation and occupies flash address
132+
* range 0 to 0x2000, total flash size of 0x40000 bytes (256 kByte).
133+
*/
134+
_ota_img_sha256 = FlashSHA256::calc(0x2000, 0x40000 - 0x2000);
135+
#endif /* OTA_ENABLED */
136+
121137
#ifdef BOARD_HAS_ECCX08
122138
if (!ECCX08.begin()) { DBG_ERROR("Cryptography processor failure. Make sure you have a compatible board."); return 0; }
123139
if (!CryptoUtil::readDeviceId(ECCX08, getDeviceId(), ECCX08Slot::DeviceId)) { DBG_ERROR("Cryptography processor read failure."); return 0; }
@@ -229,6 +245,7 @@ void ArduinoIoTCloudTCP::printDebugInfo()
229245
void ArduinoIoTCloudTCP::setOTAStorage(OTAStorage & ota_storage)
230246
{
231247
addPropertyReal(_ota_error, "OTA_ERROR", Permission::Read);
248+
addPropertyReal(_ota_img_sha256, "OTA_SHA256", Permission::Read);
232249
_ota_logic.setOTAStorage(ota_storage);
233250
}
234251
#endif /* OTA_ENABLED */

Diff for: src/ArduinoIoTCloudTCP.h

+1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass
127127
#if OTA_ENABLED
128128
OTALogic _ota_logic;
129129
int _ota_error;
130+
String _ota_img_sha256;
130131
#endif /* OTA_ENABLED */
131132

132133
inline String getTopic_stdin () { return String("/a/d/" + getDeviceId() + "/s/i"); }

Diff for: src/tls/utility/SHA256.cpp

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
This file is part of ArduinoIoTCloud.
3+
4+
Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
5+
6+
This software is released under the GNU General Public License version 3,
7+
which covers the main part of arduino-cli.
8+
The terms of this license can be found at:
9+
https://www.gnu.org/licenses/gpl-3.0.en.html
10+
11+
You can be released from the requirements of the above licenses by purchasing
12+
a commercial license. Buying such a license is mandatory if you want to modify or
13+
otherwise use the software for commercial activities involving the Arduino
14+
software without disclosing the source code of your own applications. To purchase
15+
a commercial license, send an email to [email protected].
16+
*/
17+
18+
/******************************************************************************
19+
* INCLUDE
20+
******************************************************************************/
21+
22+
#include "SHA256.h"
23+
24+
/******************************************************************************
25+
* STATIC MEMBER DECLARATION
26+
******************************************************************************/
27+
28+
constexpr size_t SHA256::HASH_SIZE;
29+
30+
/******************************************************************************
31+
* PUBLIC MEMBER FUNCTIONS
32+
******************************************************************************/
33+
34+
void SHA256::begin()
35+
{
36+
br_sha256_init(&_ctx);
37+
}
38+
39+
void SHA256::update(uint8_t const * data, size_t const len)
40+
{
41+
br_sha256_update(&_ctx, data, len);
42+
}
43+
44+
void SHA256::finalize(uint8_t * hash)
45+
{
46+
br_sha256_out(&_ctx, hash);
47+
}

Diff for: src/tls/utility/SHA256.h

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
This file is part of ArduinoIoTCloud.
3+
4+
Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
5+
6+
This software is released under the GNU General Public License version 3,
7+
which covers the main part of arduino-cli.
8+
The terms of this license can be found at:
9+
https://www.gnu.org/licenses/gpl-3.0.en.html
10+
11+
You can be released from the requirements of the above licenses by purchasing
12+
a commercial license. Buying such a license is mandatory if you want to modify or
13+
otherwise use the software for commercial activities involving the Arduino
14+
software without disclosing the source code of your own applications. To purchase
15+
a commercial license, send an email to [email protected].
16+
*/
17+
18+
#ifndef ARDUINO_TLS_UTILITY_SHA256_H_
19+
#define ARDUINO_TLS_UTILITY_SHA256_H_
20+
21+
/******************************************************************************
22+
* INCLUDE
23+
******************************************************************************/
24+
25+
#include "../bearssl/bearssl_hash.h"
26+
27+
/******************************************************************************
28+
* CLASS DECLARATION
29+
******************************************************************************/
30+
31+
class SHA256
32+
{
33+
34+
public:
35+
36+
static constexpr size_t HASH_SIZE = 32;
37+
38+
void begin ();
39+
void update (uint8_t const * data, size_t const len);
40+
void finalize(uint8_t * hash);
41+
42+
private:
43+
44+
br_sha256_context _ctx;
45+
46+
};
47+
48+
#endif /* ARDUINO_TLS_UTILITY_SHA256_H_ */

Diff for: src/utility/ota/FlashSHA256.cpp

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
This file is part of ArduinoIoTCloud.
3+
4+
Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
5+
6+
This software is released under the GNU General Public License version 3,
7+
which covers the main part of arduino-cli.
8+
The terms of this license can be found at:
9+
https://www.gnu.org/licenses/gpl-3.0.en.html
10+
11+
You can be released from the requirements of the above licenses by purchasing
12+
a commercial license. Buying such a license is mandatory if you want to modify or
13+
otherwise use the software for commercial activities involving the Arduino
14+
software without disclosing the source code of your own applications. To purchase
15+
a commercial license, send an email to [email protected].
16+
*/
17+
18+
/******************************************************************************
19+
* INCLUDE
20+
******************************************************************************/
21+
22+
#include <AIoTC_Config.h>
23+
#if OTA_ENABLED
24+
25+
#include "FlashSHA256.h"
26+
27+
#include "../../tls/utility/SHA256.h"
28+
29+
#include <Arduino_DebugUtils.h>
30+
31+
#undef max
32+
#undef min
33+
#include <algorithm>
34+
35+
/******************************************************************************
36+
* PUBLIC MEMBER FUNCTIONS
37+
******************************************************************************/
38+
39+
String FlashSHA256::calc(uint32_t const start_addr, uint32_t const max_flash_size)
40+
{
41+
SHA256 sha256;
42+
uint8_t chunk [FLASH_READ_CHUNK_SIZE],
43+
next_chunk[FLASH_READ_CHUNK_SIZE];
44+
45+
sha256.begin();
46+
47+
/* Read the first two chunks of flash. */
48+
uint32_t flash_addr = start_addr;
49+
memcpy(chunk, reinterpret_cast<const void *>(flash_addr), FLASH_READ_CHUNK_SIZE);
50+
flash_addr += FLASH_READ_CHUNK_SIZE;
51+
52+
for(; flash_addr < max_flash_size; flash_addr += FLASH_READ_CHUNK_SIZE)
53+
{
54+
/* Read the next chunk of memory. */
55+
memcpy(next_chunk, reinterpret_cast<const void *>(flash_addr), FLASH_READ_CHUNK_SIZE);
56+
57+
/* Check if the next segment is erased, that is if all bytes within
58+
* a read segment are 0xFF -> then we've reached the end of the firmware.
59+
*/
60+
bool const next_chunk_is_erased_flash = std::all_of(next_chunk,
61+
next_chunk+FLASH_READ_CHUNK_SIZE,
62+
[](uint8_t const elem) { return (elem == 0xFF); });
63+
/* Determine how many bytes at the end of the current chunk are
64+
* already set to 0xFF and therefore erased/non-written flash
65+
* memory.
66+
*/
67+
if (next_chunk_is_erased_flash)
68+
{
69+
/* Eliminate trailing 0xFF. */
70+
size_t valid_bytes_in_chunk = 0;
71+
for(valid_bytes_in_chunk = FLASH_READ_CHUNK_SIZE; valid_bytes_in_chunk > 0; valid_bytes_in_chunk--)
72+
{
73+
if (chunk[valid_bytes_in_chunk-1] != 0xFF)
74+
break;
75+
}
76+
DBG_VERBOSE("FlashSHA256::calc: end of firmware, %d valid bytes in last read chunk", valid_bytes_in_chunk);
77+
/* Update with the remaining bytes. */
78+
sha256.update(chunk, valid_bytes_in_chunk);
79+
break;
80+
}
81+
82+
/* We've read a normal segment with the next segment not containing
83+
* any erased elements, just update the SHA256 hash calcultion.
84+
*/
85+
sha256.update(chunk, FLASH_READ_CHUNK_SIZE);
86+
87+
/* Copy next_chunk to chunk. */
88+
memcpy(chunk, next_chunk, FLASH_READ_CHUNK_SIZE);
89+
}
90+
91+
/* Retrieve the final hash string. */
92+
uint8_t sha256_hash[SHA256::HASH_SIZE] = {0};
93+
sha256.finalize(sha256_hash);
94+
String sha256_str;
95+
std::for_each(sha256_hash,
96+
sha256_hash + SHA256::HASH_SIZE,
97+
[&sha256_str](uint8_t const elem)
98+
{
99+
char buf[4];
100+
snprintf(buf, 4, "%02X", elem);
101+
sha256_str += buf;
102+
});
103+
/* Do some debug printout. */
104+
DBG_VERBOSE("SHA256: %d bytes read", flash_addr);
105+
DBG_VERBOSE("SHA256: HASH(%d) = %s", strlen(sha256_str.c_str()), sha256_str.c_str());
106+
return sha256_str;
107+
}
108+
109+
#endif /* OTA_ENABLED */

Diff for: src/utility/ota/FlashSHA256.h

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
This file is part of ArduinoIoTCloud.
3+
4+
Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
5+
6+
This software is released under the GNU General Public License version 3,
7+
which covers the main part of arduino-cli.
8+
The terms of this license can be found at:
9+
https://www.gnu.org/licenses/gpl-3.0.en.html
10+
11+
You can be released from the requirements of the above licenses by purchasing
12+
a commercial license. Buying such a license is mandatory if you want to modify or
13+
otherwise use the software for commercial activities involving the Arduino
14+
software without disclosing the source code of your own applications. To purchase
15+
a commercial license, send an email to [email protected].
16+
*/
17+
18+
#ifndef ARDUINO_OTA_FLASH_SHA256_H_
19+
#define ARDUINO_OTA_FLASH_SHA256_H_
20+
21+
/******************************************************************************
22+
* INCLUDE
23+
******************************************************************************/
24+
25+
#include <AIoTC_Config.h>
26+
#if OTA_ENABLED
27+
28+
#include <Arduino.h>
29+
30+
/******************************************************************************
31+
* CLASS DECLARATION
32+
******************************************************************************/
33+
34+
class FlashSHA256
35+
{
36+
public:
37+
38+
static String calc(uint32_t const start_addr, uint32_t const max_flash_size);
39+
40+
private:
41+
42+
FlashSHA256() { }
43+
FlashSHA256(FlashSHA256 const &) { }
44+
45+
static constexpr uint32_t FLASH_READ_CHUNK_SIZE = 64;
46+
47+
};
48+
49+
#endif /* OTA_ENABLED */
50+
51+
#endif /* ARDUINO_OTA_FLASH_SHA256_H_ */

Diff for: src/utility/ota/OTALogic.cpp

+9-9
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ void OTALogic::onOTADataReceived(uint8_t const * const data, size_t const length
125125
OTAState OTALogic::handle_Init()
126126
{
127127
#ifndef HOST
128-
DBG_VERBOSE(__PRETTY_FUNCTION__);
128+
DBG_VERBOSE("OTALogic::%s", __FUNCTION__);
129129
#endif
130130
if (_ota_storage->init()) {
131131
return OTAState::Idle;
@@ -146,7 +146,7 @@ OTAState OTALogic::handle_Idle()
146146
OTAState OTALogic::handle_StartDownload()
147147
{
148148
#ifndef HOST
149-
DBG_VERBOSE(__PRETTY_FUNCTION__);
149+
DBG_VERBOSE("OTALogic::%s", __FUNCTION__);
150150
#endif
151151
if(_ota_storage->open()) {
152152
return OTAState::WaitForHeader;
@@ -159,7 +159,7 @@ OTAState OTALogic::handle_StartDownload()
159159
OTAState OTALogic::handle_WaitForHeader()
160160
{
161161
#ifndef HOST
162-
DBG_VERBOSE(__PRETTY_FUNCTION__);
162+
DBG_VERBOSE("OTALogic::%s", __FUNCTION__);
163163
#endif
164164
if(_mqtt_ota_buf.num_bytes >= OTA_BINARY_HEADER_SIZE) {
165165
return OTAState::HeaderReceived;
@@ -190,8 +190,8 @@ OTAState OTALogic::handle_HeaderReceived()
190190
_ota_bin_data.hdr_crc32 = ota_header.header.crc32;
191191

192192
#ifndef HOST
193-
DBG_VERBOSE("%s: header length = %d", __PRETTY_FUNCTION__, _ota_bin_data.hdr_len);
194-
DBG_VERBOSE("%s: header CRC32 = %d", __PRETTY_FUNCTION__, _ota_bin_data.hdr_crc32);
193+
DBG_VERBOSE("OTALogic::%s: header length = %d", __FUNCTION__, _ota_bin_data.hdr_len);
194+
DBG_VERBOSE("OTALogic::%s: header crc32 = %X", __FUNCTION__, _ota_bin_data.hdr_crc32);
195195
#endif
196196

197197
/* Reset the counter which is responsible for keeping tabs on how many bytes have been received */
@@ -234,7 +234,7 @@ OTAState OTALogic::handle_BinaryReceived()
234234
_mqtt_ota_buf.num_bytes = 0;
235235

236236
#ifndef HOST
237-
DBG_VERBOSE("%s: %d bytes written", __PRETTY_FUNCTION__, _ota_bin_data.bytes_received);
237+
DBG_VERBOSE("OTALogic::%s: %d bytes written", __FUNCTION__, _ota_bin_data.bytes_received);
238238
#endif
239239

240240
if(_ota_bin_data.bytes_received >= _ota_bin_data.hdr_len) {
@@ -249,7 +249,7 @@ OTAState OTALogic::handle_BinaryReceived()
249249
OTAState OTALogic::handle_Verify()
250250
{
251251
#ifndef HOST
252-
DBG_VERBOSE(__PRETTY_FUNCTION__);
252+
DBG_VERBOSE("OTALogic::%s", __FUNCTION__);
253253
#endif
254254
if(_ota_bin_data.crc32 == _ota_bin_data.hdr_crc32) {
255255
return OTAState::Rename;
@@ -263,7 +263,7 @@ OTAState OTALogic::handle_Verify()
263263
OTAState OTALogic::handle_Rename()
264264
{
265265
#ifndef HOST
266-
DBG_VERBOSE(__PRETTY_FUNCTION__);
266+
DBG_VERBOSE("OTALogic::%s", __FUNCTION__);
267267
#endif
268268
if(_ota_storage->rename()) {
269269
_ota_storage->deinit();
@@ -286,7 +286,7 @@ OTAState OTALogic::handle_Reset()
286286
* is started directly.
287287
*/
288288
#ifndef HOST
289-
DBG_VERBOSE(__PRETTY_FUNCTION__);
289+
DBG_VERBOSE("OTALogic::%s", __FUNCTION__);
290290
delay(250);
291291
#endif
292292
NVIC_SystemReset();

0 commit comments

Comments
 (0)