1
0
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:
Earle F. Philhower, III
2018-12-02 19:57:47 -08:00
committed by GitHub
parent e5d50a6e4b
commit d8acfffdb0
11 changed files with 532 additions and 27 deletions

View File

@ -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){

View File

@ -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;

View File

@ -0,0 +1,3 @@
// This file will be overridden when automatic signing is used.
// By default, no signing.
#define ARDUINO_SIGNING 0