mirror of
https://github.com/esp8266/Arduino.git
synced 2025-06-16 11:21:18 +03:00
Add cryptographically signed update support (#5213)
Using a pluggable architecture, allow updates delivered via the Update class to be verified as signed by a certificate. By using plugins, avoid pulling either axTLS or BearSSL into normal builds. A signature is appended to a binary image, followed by the size of the signature as a 32-bit int. The updater takes a verification function and checks this signature using whatever method it chooses, and if it fails the update is not applied. A SHA256 hash class is presently implemented for the signing hash (since MD5 is a busted algorithm). A BearSSLPublicKey based verifier is implemented for RSA keys. The application only needs the Public Key, while to sign you can use OpenSSL and your private key (which should never leave your control or be deployed on any endpoints). An example using automatic signing is included. Update the docs to show the signing steps and how to use it in the automatic and manual modes. Also remove one debugging line from the signing tool. Saves ~600 bytes when in debug mode by moving strings to PMEM Windows can't run the signing script, nor does it normally have OpenSSL installed. When trying to build an automatically signed binary, warn and don't run the python.
This commit is contained in:
committed by
GitHub
parent
e5d50a6e4b
commit
d8acfffdb0
@ -6,6 +6,18 @@
|
||||
|
||||
//#define DEBUG_UPDATER Serial
|
||||
|
||||
#include <Updater_Signing.h>
|
||||
#ifndef ARDUINO_SIGNING
|
||||
#define ARDUINO_SIGNING 0
|
||||
#endif
|
||||
|
||||
#if ARDUINO_SIGNING
|
||||
#include "../../libraries/ESP8266WiFi/src/BearSSLHelpers.h"
|
||||
static BearSSL::PublicKey signPubKey(signing_pubkey);
|
||||
static BearSSL::HashSHA256 hash;
|
||||
static BearSSL::SigningVerifier sign(&signPubKey);
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#include "c_types.h"
|
||||
#include "spi_flash.h"
|
||||
@ -23,7 +35,12 @@ UpdaterClass::UpdaterClass()
|
||||
, _startAddress(0)
|
||||
, _currentAddress(0)
|
||||
, _command(U_FLASH)
|
||||
, _hash(nullptr)
|
||||
, _verify(nullptr)
|
||||
{
|
||||
#if ARDUINO_SIGNING
|
||||
installSignature(&hash, &sign);
|
||||
#endif
|
||||
}
|
||||
|
||||
void UpdaterClass::_reset() {
|
||||
@ -96,9 +113,9 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
|
||||
updateStartAddress = (updateEndAddress > roundedSize)? (updateEndAddress - roundedSize) : 0;
|
||||
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf("[begin] roundedSize: 0x%08zX (%zd)\n", roundedSize, roundedSize);
|
||||
DEBUG_UPDATER.printf("[begin] updateEndAddress: 0x%08zX (%zd)\n", updateEndAddress, updateEndAddress);
|
||||
DEBUG_UPDATER.printf("[begin] currentSketchSize: 0x%08zX (%zd)\n", currentSketchSize, currentSketchSize);
|
||||
DEBUG_UPDATER.printf_P(PSTR("[begin] roundedSize: 0x%08zX (%zd)\n"), roundedSize, roundedSize);
|
||||
DEBUG_UPDATER.printf_P(PSTR("[begin] updateEndAddress: 0x%08zX (%zd)\n"), updateEndAddress, updateEndAddress);
|
||||
DEBUG_UPDATER.printf_P(PSTR("[begin] currentSketchSize: 0x%08zX (%zd)\n"), currentSketchSize, currentSketchSize);
|
||||
#endif
|
||||
|
||||
//make sure that the size of both sketches is less than the total space (updateEndAddress)
|
||||
@ -131,12 +148,14 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
|
||||
_command = command;
|
||||
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf("[begin] _startAddress: 0x%08X (%d)\n", _startAddress, _startAddress);
|
||||
DEBUG_UPDATER.printf("[begin] _currentAddress: 0x%08X (%d)\n", _currentAddress, _currentAddress);
|
||||
DEBUG_UPDATER.printf("[begin] _size: 0x%08zX (%zd)\n", _size, _size);
|
||||
DEBUG_UPDATER.printf_P(PSTR("[begin] _startAddress: 0x%08X (%d)\n"), _startAddress, _startAddress);
|
||||
DEBUG_UPDATER.printf_P(PSTR("[begin] _currentAddress: 0x%08X (%d)\n"), _currentAddress, _currentAddress);
|
||||
DEBUG_UPDATER.printf_P(PSTR("[begin] _size: 0x%08zX (%zd)\n"), _size, _size);
|
||||
#endif
|
||||
|
||||
_md5.begin();
|
||||
if (!_verify) {
|
||||
_md5.begin();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -159,7 +178,7 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
||||
|
||||
if(hasError() || (!isFinished() && !evenIfRemaining)){
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf("premature end: res:%u, pos:%zu/%zu\n", getError(), progress(), _size);
|
||||
DEBUG_UPDATER.printf_P(PSTR("premature end: res:%u, pos:%zu/%zu\n"), getError(), progress(), _size);
|
||||
#endif
|
||||
|
||||
_reset();
|
||||
@ -173,15 +192,68 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
||||
_size = progress();
|
||||
}
|
||||
|
||||
_md5.calculate();
|
||||
if(_target_md5.length()) {
|
||||
if(strcasecmp(_target_md5.c_str(), _md5.toString().c_str()) != 0){
|
||||
uint32_t sigLen = 0;
|
||||
if (_verify) {
|
||||
ESP.flashRead(_startAddress + _size - sizeof(uint32_t), &sigLen, sizeof(uint32_t));
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] sigLen: %d\n"), sigLen);
|
||||
#endif
|
||||
if (sigLen != _verify->length()) {
|
||||
_setError(UPDATE_ERROR_SIGN);
|
||||
_reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
int binSize = _size - sigLen - sizeof(uint32_t) /* The siglen word */;
|
||||
_hash->begin();
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Adjusted binsize: %d\n"), binSize);
|
||||
#endif
|
||||
// Calculate the MD5 and hash using proper size
|
||||
uint8_t buff[128];
|
||||
for(int i = 0; i < binSize; i += sizeof(buff)) {
|
||||
ESP.flashRead(_startAddress + i, (uint32_t *)buff, sizeof(buff));
|
||||
size_t read = std::min((int)sizeof(buff), binSize - i);
|
||||
_hash->add(buff, read);
|
||||
}
|
||||
_hash->end();
|
||||
#ifdef DEBUG_UPDATER
|
||||
unsigned char *ret = (unsigned char *)_hash->hash();
|
||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Computed Hash:"));
|
||||
for (int i=0; i<_hash->len(); i++) DEBUG_UPDATER.printf(" %02x", ret[i]);
|
||||
DEBUG_UPDATER.printf("\n");
|
||||
#endif
|
||||
uint8_t *sig = (uint8_t*)malloc(sigLen);
|
||||
if (!sig) {
|
||||
_setError(UPDATE_ERROR_SIGN);
|
||||
_reset();
|
||||
return false;
|
||||
}
|
||||
ESP.flashRead(_startAddress + binSize, (uint32_t *)sig, sigLen);
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Received Signature:"));
|
||||
for (size_t i=0; i<sigLen; i++) {
|
||||
DEBUG_UPDATER.printf(" %02x", sig[i]);
|
||||
}
|
||||
DEBUG_UPDATER.printf("\n");
|
||||
#endif
|
||||
if (!_verify->verify(_hash, (void *)sig, sigLen)) {
|
||||
_setError(UPDATE_ERROR_SIGN);
|
||||
_reset();
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Signature matches\n"));
|
||||
#endif
|
||||
} else if (_target_md5.length()) {
|
||||
_md5.calculate();
|
||||
if (strcasecmp(_target_md5.c_str(), _md5.toString().c_str())) {
|
||||
_setError(UPDATE_ERROR_MD5);
|
||||
_reset();
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG_UPDATER
|
||||
else DEBUG_UPDATER.printf("MD5 Success: %s\n", _target_md5.c_str());
|
||||
else DEBUG_UPDATER.printf_P(PSTR("MD5 Success: %s\n"), _target_md5.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -199,10 +271,10 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
||||
eboot_command_write(&ebcmd);
|
||||
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf("Staged: address:0x%08X, size:0x%08zX\n", _startAddress, _size);
|
||||
DEBUG_UPDATER.printf_P(PSTR("Staged: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
|
||||
}
|
||||
else if (_command == U_SPIFFS) {
|
||||
DEBUG_UPDATER.printf("SPIFFS: address:0x%08X, size:0x%08zX\n", _startAddress, _size);
|
||||
DEBUG_UPDATER.printf_P(PSTR("SPIFFS: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -229,12 +301,12 @@ bool UpdaterClass::_writeBuffer(){
|
||||
if (_currentAddress == _startAddress + FLASH_MODE_PAGE) {
|
||||
flashMode = ESP.getFlashChipMode();
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf("Header: 0x%1X %1X %1X %1X\n", _buffer[0], _buffer[1], _buffer[2], _buffer[3]);
|
||||
DEBUG_UPDATER.printf_P(PSTR("Header: 0x%1X %1X %1X %1X\n"), _buffer[0], _buffer[1], _buffer[2], _buffer[3]);
|
||||
#endif
|
||||
bufferFlashMode = ESP.magicFlashChipMode(_buffer[FLASH_MODE_OFFSET]);
|
||||
if (bufferFlashMode != flashMode) {
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf("Set flash mode from 0x%1X to 0x%1X\n", bufferFlashMode, flashMode);
|
||||
DEBUG_UPDATER.printf_P(PSTR("Set flash mode from 0x%1X to 0x%1X\n"), bufferFlashMode, flashMode);
|
||||
#endif
|
||||
|
||||
_buffer[FLASH_MODE_OFFSET] = flashMode;
|
||||
@ -262,7 +334,9 @@ bool UpdaterClass::_writeBuffer(){
|
||||
_setError(UPDATE_ERROR_WRITE);
|
||||
return false;
|
||||
}
|
||||
_md5.add(_buffer, _bufferLen);
|
||||
if (!_verify) {
|
||||
_md5.add(_buffer, _bufferLen);
|
||||
}
|
||||
_currentAddress += _bufferLen;
|
||||
_bufferLen = 0;
|
||||
return true;
|
||||
@ -426,8 +500,9 @@ void UpdaterClass::printError(Print &out){
|
||||
} else if(_error == UPDATE_ERROR_STREAM){
|
||||
out.println(F("Stream Read Timeout"));
|
||||
} else if(_error == UPDATE_ERROR_MD5){
|
||||
//out.println(F("MD5 Check Failed"));
|
||||
out.printf("MD5 Failed: expected:%s, calculated:%s\n", _target_md5.c_str(), _md5.toString().c_str());
|
||||
out.printf_P(PSTR("MD5 Failed: expected:%s, calculated:%s\n"), _target_md5.c_str(), _md5.toString().c_str());
|
||||
} else if(_error == UPDATE_ERROR_SIGN){
|
||||
out.println(F("Signature verification failed"));
|
||||
} else if(_error == UPDATE_ERROR_FLASH_CONFIG){
|
||||
out.printf_P(PSTR("Flash config wrong real: %d IDE: %d\n"), ESP.getFlashChipRealSize(), ESP.getFlashChipSize());
|
||||
} else if(_error == UPDATE_ERROR_NEW_FLASH_CONFIG){
|
||||
|
@ -17,6 +17,7 @@
|
||||
#define UPDATE_ERROR_NEW_FLASH_CONFIG (9)
|
||||
#define UPDATE_ERROR_MAGIC_BYTE (10)
|
||||
#define UPDATE_ERROR_BOOTSTRAP (11)
|
||||
#define UPDATE_ERROR_SIGN (12)
|
||||
|
||||
#define U_FLASH 0
|
||||
#define U_SPIFFS 100
|
||||
@ -28,9 +29,30 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Abstract class to implement whatever signing hash desired
|
||||
class UpdaterHashClass {
|
||||
public:
|
||||
virtual void begin() = 0;
|
||||
virtual void add(const void *data, uint32_t len) = 0;
|
||||
virtual void end() = 0;
|
||||
virtual int len() = 0;
|
||||
virtual const void *hash() = 0;
|
||||
};
|
||||
|
||||
// Abstract class to implement a signature verifier
|
||||
class UpdaterVerifyClass {
|
||||
public:
|
||||
virtual uint32_t length() = 0; // How many bytes of signature are expected
|
||||
virtual bool verify(UpdaterHashClass *hash, const void *signature, uint32_t signatureLen) = 0; // Verify, return "true" on success
|
||||
};
|
||||
|
||||
class UpdaterClass {
|
||||
public:
|
||||
UpdaterClass();
|
||||
|
||||
/* Optionally add a cryptographic signature verification hash and method */
|
||||
void installSignature(UpdaterHashClass *hash, UpdaterVerifyClass *verify) { _hash = hash; _verify = verify; }
|
||||
|
||||
/*
|
||||
Call this to check the space needed for the update
|
||||
Will return false if there is not enough space
|
||||
@ -165,6 +187,10 @@ class UpdaterClass {
|
||||
|
||||
int _ledPin;
|
||||
uint8_t _ledOn;
|
||||
|
||||
// Optional signed binary verification
|
||||
UpdaterHashClass *_hash;
|
||||
UpdaterVerifyClass *_verify;
|
||||
};
|
||||
|
||||
extern UpdaterClass Update;
|
||||
|
3
cores/esp8266/Updater_Signing.h
Normal file
3
cores/esp8266/Updater_Signing.h
Normal file
@ -0,0 +1,3 @@
|
||||
// This file will be overridden when automatic signing is used.
|
||||
// By default, no signing.
|
||||
#define ARDUINO_SIGNING 0
|
Reference in New Issue
Block a user