From 71bf243f6c99b70ae08c98cd3b562c537e5fe9b0 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sat, 6 Oct 2018 12:37:18 -0700 Subject: [PATCH 01/16] Add cryptographically signed update support 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). --- cores/esp8266/Updater.cpp | 69 ++++++++++++++++++++ cores/esp8266/Updater.h | 22 +++++++ libraries/ESP8266WiFi/src/BearSSLHelpers.cpp | 33 ++++++++++ libraries/ESP8266WiFi/src/BearSSLHelpers.h | 27 ++++++++ 4 files changed, 151 insertions(+) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index fb539cc3da..3960e3566e 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -23,6 +23,8 @@ UpdaterClass::UpdaterClass() , _startAddress(0) , _currentAddress(0) , _command(U_FLASH) +, _hash(nullptr) +, _verify(nullptr) { } @@ -140,6 +142,9 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { #endif _md5.begin(); + if (_hash) { + _hash->begin(); + } return true; } @@ -176,6 +181,42 @@ bool UpdaterClass::end(bool evenIfRemaining){ _size = progress(); } + + uint32_t sigLen = 0; + if (_verify) { + ESP.flashRead(_startAddress + _size - sizeof(uint32_t), &sigLen, sizeof(uint32_t)); +#ifdef DEBUG_UPDATER + DEBUG_UPDATER.printf("[Updater] sigLen: %d\n", sigLen); +#endif + if (!sigLen || sigLen > 256) { + _setError(UPDATE_ERROR_SIGN); + _reset(); + return false; + } + } + + int binSize = _size; + if (_hash) { + _hash->begin(); + binSize -= sigLen + sizeof(uint32_t); + } +#ifdef DEBUG_UPDATER + DEBUG_UPDATER.printf("[Updater] Adjusted binsize: %d\n", binSize); +#endif + // Calculate the MD5 and hash using proper size + uint8_t buff[32]; + for(int i = 0; i < binSize; i += 32) { + ESP.flashRead(_startAddress + i, (uint32_t *)buff, 32); + + int read = binSize - i; + if(read > 32) { + read = 32; + } + _md5.add(buff, read); + if (_hash) { + _hash->add(buff, read); + } + } _md5.calculate(); if(_target_md5.length()) { if(_target_md5 != _md5.toString()){ @@ -188,6 +229,29 @@ bool UpdaterClass::end(bool evenIfRemaining){ #endif } + if (_verify && _hash) { + _hash->end(); +#ifdef DEBUG_UPDATER + unsigned char *ret = (unsigned char *)_hash->hash(); + DEBUG_UPDATER.printf("[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[256]; + ESP.flashRead(_startAddress + binSize, (uint32_t *)sig, sigLen); +#ifdef DEBUG_UPDATER + DEBUG_UPDATER.printf("[Updater] sig[]:"); + for (size_t i=0; iverify(_hash, (void *)sig, sigLen)) { + _setError(UPDATE_ERROR_SIGN); + return false; + } + } + if(!_verifyEnd()) { _reset(); return false; @@ -266,6 +330,9 @@ bool UpdaterClass::_writeBuffer(){ return false; } _md5.add(_buffer, _bufferLen); + if (_hash) { + _hash->add(_buffer, _bufferLen); + } _currentAddress += _bufferLen; _bufferLen = 0; return true; @@ -431,6 +498,8 @@ void UpdaterClass::printError(Print &out){ } 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()); + } else if(_error == UPDATE_ERROR_SIGN){ + out.printf("Signature verification failed\n"); } 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){ diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h index 7fcbb56a5c..ff74608e74 100644 --- a/cores/esp8266/Updater.h +++ b/cores/esp8266/Updater.h @@ -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,27 @@ #endif #endif +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 void *hash() = 0; +}; + +class UpdaterVerifyClass { + public: + virtual bool verify(UpdaterHashClass *hash, void *signature, uint32_t signatureLen) = 0; +}; + 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 @@ -166,6 +185,9 @@ class UpdaterClass { int _ledPin; uint8_t _ledOn; int _ledStateRestore; + + UpdaterHashClass *_hash; + UpdaterVerifyClass *_verify; }; extern UpdaterClass Update; diff --git a/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp b/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp index 38fcccd70d..687e96653f 100644 --- a/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp +++ b/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp @@ -819,3 +819,36 @@ bool BearSSLX509List::append(const uint8_t *derCert, size_t derLen) { return true; } + + +// SHA256 hash for updater +void BearSSLHashSHA256::begin() { + br_sha256_init( &_cc ); + memset( _sha256, 0, sizeof(_sha256) ); +} + +void BearSSLHashSHA256::add(const void *data, uint32_t len) { + br_sha256_update( &_cc, data, len ); +} + +void BearSSLHashSHA256::end() { + br_sha256_out( &_cc, _sha256 ); +} + +int BearSSLHashSHA256::len() { + return sizeof(_sha256); +} + +void *BearSSLHashSHA256::hash() { + return (void*) _sha256; +} + +// SHA256 verifier +bool BearSSLVerifier::verify(UpdaterHashClass *hash, void *signature, uint32_t signatureLen) { + if (!_pubKey || !hash || !signature || signatureLen != 256) return false; + br_rsa_pkcs1_vrfy vrfy = br_rsa_pkcs1_vrfy_get_default(); + unsigned char vrf[32]; + bool ret = vrfy((const unsigned char *)signature, signatureLen, NULL, sizeof(vrf), _pubKey->getRSA(), vrf); + if (!ret) return false; + return !memcmp(vrf, hash->hash(), sizeof(vrf)); +}; diff --git a/libraries/ESP8266WiFi/src/BearSSLHelpers.h b/libraries/ESP8266WiFi/src/BearSSLHelpers.h index 6cfe4e4aec..cf62cac0e9 100644 --- a/libraries/ESP8266WiFi/src/BearSSLHelpers.h +++ b/libraries/ESP8266WiFi/src/BearSSLHelpers.h @@ -24,6 +24,8 @@ #define _BEARSSLHELPERS_H #include +#include + // Internal opaque structures, not needed by user applications namespace brssl { @@ -136,4 +138,29 @@ class BearSSLSession { br_ssl_session_parameters _session; }; + +// Updater SHA256 hash and signature verification +class BearSSLHashSHA256 : public UpdaterHashClass { + public: + virtual void begin() override; + virtual void add(const void *data, uint32_t len) override; + virtual void end() override; + virtual int len() override; + virtual void *hash() override; + private: + br_sha256_context _cc; + unsigned char _sha256[32]; +}; + +class BearSSLVerifier : public UpdaterVerifyClass { + public: + virtual bool verify(UpdaterHashClass *hash, void *signature, uint32_t signatureLen) override; + + public: + BearSSLVerifier(BearSSLPublicKey *pubKey) { _pubKey = pubKey; } + + private: + BearSSLPublicKey *_pubKey; +}; + #endif From ac2587785d445412b4156f497c621d362e83e222 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sat, 6 Oct 2018 12:43:13 -0700 Subject: [PATCH 02/16] Add a simple example --- .../httpUpdateSigned/httpUpdateSigned.ino | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino diff --git a/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino new file mode 100644 index 0000000000..1c2a4a07b0 --- /dev/null +++ b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino @@ -0,0 +1,96 @@ +/** + httpUpdate.ino + + Created on: 27.11.2015 + +*/ + +#include + +#include +#include + +#include +#include + +#define USE_SERIAL Serial + +ESP8266WiFiMulti WiFiMulti; + +const char pubkey[] PROGMEM = R"EOF( +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyW5a4OO7xd6pRDTETO7h +vEMBOr/wCqcTi/gi2/99rPnVvT7IH/qGSiYMxpGFKCXVqS4rU5k2XspALEquyGse +Uav5hqsgHO6CQFFALqXzUVNCsJA9V6raUFhBaIqqCKmWzmeAkV+avM/zDQR9Wj1Q +TCmi997sJJ5ICQc8cGSdvrhisUSbfPpKI9Ql4FApOZRABBBuZKhN9ujIzTv3OIAa +rpQVfACKKuv7a2N2qU0uxRDojeO6odT1c6AZv6BlcF76GQGTo+/oBhqPdbAQuaBy +cuWNgTnDQd6KUzV0E4it2fNG+cHN4kEvofN6gHx8IbOrXwFttlpAH/o7bcfCnUVh +TQIDAQAB +-----END PUBLIC KEY----- +)EOF"; +BearSSLPublicKey *signPubKey = nullptr; +BearSSLHashSHA256 *hash; +BearSSLVerifier *sign; + +void setup() { + + USE_SERIAL.begin(115200); + // USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for (uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFi.mode(WIFI_STA); + WiFiMulti.addAP("SSID", "PASS"); + + signPubKey = new BearSSLPublicKey(pubkey); + hash = new BearSSLHashSHA256(); + sign = new BearSSLVerifier(signPubKey); +} + + +void loop() { + // wait for WiFi connection + if ((WiFiMulti.run() == WL_CONNECTED)) { + + WiFiClient client; + + + Update.installSignature(hash, sign); + + // The line below is optional. It can be used to blink the LED on the board during flashing + // The LED will be on during download of one buffer of data from the network. The LED will + // be off during writing that buffer to flash + // On a good connection the LED should flash regularly. On a bad connection the LED will be + // on much longer than it will be off. Other pins than LED_BUILTIN may be used. The second + // value is used to put the LED on. If the LED is on with HIGH, that value should be passed + ESPhttpUpdate.setLedPin(LED_BUILTIN, LOW); + + t_httpUpdate_return ret = ESPhttpUpdate.update(client, "http://192.168.1.8/esp8266.bin"); + // Or: + //t_httpUpdate_return ret = ESPhttpUpdate.update(client, "server", 80, "file.bin"); + + switch (ret) { + case HTTP_UPDATE_FAILED: + USE_SERIAL.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); + break; + + case HTTP_UPDATE_NO_UPDATES: + USE_SERIAL.println("HTTP_UPDATE_NO_UPDATES"); + break; + + case HTTP_UPDATE_OK: + USE_SERIAL.println("HTTP_UPDATE_OK"); + break; + } + } + delay(10000); +} + From 882e5466d8d8177de6ed1b9bdf398f73b8c2ebd5 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sat, 6 Oct 2018 14:39:42 -0700 Subject: [PATCH 03/16] Make verifier EC/RSA compatible at any bit length Allow EC or RSA keys to be used to verify, as well as any RSA key length supported by BearSSL (currently 4096 bits). --- cores/esp8266/Updater.cpp | 33 ++++++------ cores/esp8266/Updater.h | 5 +- libraries/ESP8266WiFi/src/BearSSLHelpers.cpp | 38 +++++++++++--- libraries/ESP8266WiFi/src/BearSSLHelpers.h | 5 +- .../httpUpdateSigned/httpUpdateSigned.ino | 52 +++++++++---------- 5 files changed, 78 insertions(+), 55 deletions(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 3960e3566e..f15fdf728a 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -141,10 +141,6 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { DEBUG_UPDATER.printf("[begin] _size: 0x%08X (%d)\n", _size, _size); #endif - _md5.begin(); - if (_hash) { - _hash->begin(); - } return true; } @@ -188,13 +184,14 @@ bool UpdaterClass::end(bool evenIfRemaining){ #ifdef DEBUG_UPDATER DEBUG_UPDATER.printf("[Updater] sigLen: %d\n", sigLen); #endif - if (!sigLen || sigLen > 256) { + if (sigLen != _verify->length()) { _setError(UPDATE_ERROR_SIGN); _reset(); return false; } } + _md5.begin(); int binSize = _size; if (_hash) { _hash->begin(); @@ -204,13 +201,13 @@ bool UpdaterClass::end(bool evenIfRemaining){ DEBUG_UPDATER.printf("[Updater] Adjusted binsize: %d\n", binSize); #endif // Calculate the MD5 and hash using proper size - uint8_t buff[32]; - for(int i = 0; i < binSize; i += 32) { - ESP.flashRead(_startAddress + i, (uint32_t *)buff, 32); + uint8_t buff[128]; + for(int i = 0; i < binSize; i += sizeof(buff)) { + ESP.flashRead(_startAddress + i, (uint32_t *)buff, sizeof(buff)); - int read = binSize - i; - if(read > 32) { - read = 32; + size_t read = binSize - i; + if(read > sizeof(buff)) { + read = sizeof(buff); } _md5.add(buff, read); if (_hash) { @@ -237,10 +234,15 @@ bool UpdaterClass::end(bool evenIfRemaining){ for (int i=0; i<_hash->len(); i++) DEBUG_UPDATER.printf(" %02x", ret[i]); DEBUG_UPDATER.printf("\n"); #endif - uint8_t sig[256]; + 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("[Updater] sig[]:"); + DEBUG_UPDATER.printf("[Updater] Received Signature:"); for (size_t i=0; iverify(_hash, (void *)sig, sigLen)) { _setError(UPDATE_ERROR_SIGN); + _reset(); return false; } } @@ -329,10 +332,6 @@ bool UpdaterClass::_writeBuffer(){ _setError(UPDATE_ERROR_WRITE); return false; } - _md5.add(_buffer, _bufferLen); - if (_hash) { - _hash->add(_buffer, _bufferLen); - } _currentAddress += _bufferLen; _bufferLen = 0; return true; diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h index ff74608e74..1f3deed82a 100644 --- a/cores/esp8266/Updater.h +++ b/cores/esp8266/Updater.h @@ -29,6 +29,7 @@ #endif #endif +// Abstract class to implement whatever signing hash desired class UpdaterHashClass { public: virtual void begin() = 0; @@ -38,9 +39,11 @@ class UpdaterHashClass { virtual void *hash() = 0; }; +// Abstract class to implement a signature verifier class UpdaterVerifyClass { public: - virtual bool verify(UpdaterHashClass *hash, void *signature, uint32_t signatureLen) = 0; + virtual uint32_t length() = 0; // How many bytes of signature are expected + virtual bool verify(UpdaterHashClass *hash, void *signature, uint32_t signatureLen) = 0; // Verify, return "true" on success }; class UpdaterClass { diff --git a/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp b/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp index 687e96653f..18a6cd9dca 100644 --- a/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp +++ b/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp @@ -844,11 +844,35 @@ void *BearSSLHashSHA256::hash() { } // SHA256 verifier -bool BearSSLVerifier::verify(UpdaterHashClass *hash, void *signature, uint32_t signatureLen) { - if (!_pubKey || !hash || !signature || signatureLen != 256) return false; - br_rsa_pkcs1_vrfy vrfy = br_rsa_pkcs1_vrfy_get_default(); - unsigned char vrf[32]; - bool ret = vrfy((const unsigned char *)signature, signatureLen, NULL, sizeof(vrf), _pubKey->getRSA(), vrf); - if (!ret) return false; - return !memcmp(vrf, hash->hash(), sizeof(vrf)); +uint32_t BearSSLSigningVerifier::length() +{ + if (!_pubKey) { + return 0; + } else if (_pubKey->isRSA()) { + return _pubKey->getRSA()->nlen; + } else if (_pubKey->isEC()) { + return _pubKey->getEC()->qlen; + } else { + return 0; + } +} + +bool BearSSLSigningVerifier::verify(UpdaterHashClass *hash, void *signature, uint32_t signatureLen) { + if (!_pubKey || !hash || !signature || signatureLen != length()) return false; + + if (_pubKey->isRSA()) { + bool ret; + unsigned char vrf[hash->len()]; + br_rsa_pkcs1_vrfy vrfy = br_rsa_pkcs1_vrfy_get_default(); + ret = vrfy((const unsigned char *)signature, signatureLen, NULL, sizeof(vrf), _pubKey->getRSA(), vrf); + if (!ret || memcmp(vrf, hash->hash(), sizeof(vrf)) ) { + return false; + } else { + return true; + } + } else { + br_ecdsa_vrfy vrfy = br_ecdsa_vrfy_raw_get_default(); + // The EC verifier actually does the compare, unlike the RSA one + return vrfy(br_ec_get_default(), hash->hash(), hash->len(), _pubKey->getEC(), (const unsigned char *)signature, signatureLen); + } }; diff --git a/libraries/ESP8266WiFi/src/BearSSLHelpers.h b/libraries/ESP8266WiFi/src/BearSSLHelpers.h index cf62cac0e9..26ac48d8fb 100644 --- a/libraries/ESP8266WiFi/src/BearSSLHelpers.h +++ b/libraries/ESP8266WiFi/src/BearSSLHelpers.h @@ -152,12 +152,13 @@ class BearSSLHashSHA256 : public UpdaterHashClass { unsigned char _sha256[32]; }; -class BearSSLVerifier : public UpdaterVerifyClass { +class BearSSLSigningVerifier : public UpdaterVerifyClass { public: + virtual uint32_t length() override; virtual bool verify(UpdaterHashClass *hash, void *signature, uint32_t signatureLen) override; public: - BearSSLVerifier(BearSSLPublicKey *pubKey) { _pubKey = pubKey; } + BearSSLSigningVerifier(BearSSLPublicKey *pubKey) { _pubKey = pubKey; } private: BearSSLPublicKey *_pubKey; diff --git a/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino index 1c2a4a07b0..d6068e8cf1 100644 --- a/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino +++ b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino @@ -1,8 +1,11 @@ -/** - httpUpdate.ino - - Created on: 27.11.2015 - +/* + httpUpdateSigned.ino - Earle F. Philhower, III + Released into the Public Domain + + Shows how to use a public key extracted from your private certificate to + only allow updates that you have signed to be applied over HTTP. Remote + updates will require your private key to sign them, but of course + **ANYONE WITH PHYSICAL ACCESS CAN UPDATE THE 8266 VIA THE SERIAL PORT**. */ #include @@ -13,10 +16,11 @@ #include #include -#define USE_SERIAL Serial - ESP8266WiFiMulti WiFiMulti; +// This key is taken from the server public certificate in BearSSL examples +// You should make your own private/public key pair and guard the private +// key (never upload it to the 8266). const char pubkey[] PROGMEM = R"EOF( -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyW5a4OO7xd6pRDTETO7h @@ -30,20 +34,20 @@ TQIDAQAB )EOF"; BearSSLPublicKey *signPubKey = nullptr; BearSSLHashSHA256 *hash; -BearSSLVerifier *sign; +BearSSLSigningVerifier *sign; void setup() { - USE_SERIAL.begin(115200); - // USE_SERIAL.setDebugOutput(true); + Serial.begin(115200); + // Serial.setDebugOutput(true); - USE_SERIAL.println(); - USE_SERIAL.println(); - USE_SERIAL.println(); + Serial.println(); + Serial.println(); + Serial.println(); for (uint8_t t = 4; t > 0; t--) { - USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); - USE_SERIAL.flush(); + Serial.printf("[SETUP] WAIT %d...\n", t); + Serial.flush(); delay(1000); } @@ -52,7 +56,7 @@ void setup() { signPubKey = new BearSSLPublicKey(pubkey); hash = new BearSSLHashSHA256(); - sign = new BearSSLVerifier(signPubKey); + sign = new BearSSLSigningVerifier(signPubKey); } @@ -62,32 +66,24 @@ void loop() { WiFiClient client; - + // Ensure all updates are signed appropriately. W/o this call, all will be accepted. Update.installSignature(hash, sign); - // The line below is optional. It can be used to blink the LED on the board during flashing - // The LED will be on during download of one buffer of data from the network. The LED will - // be off during writing that buffer to flash - // On a good connection the LED should flash regularly. On a bad connection the LED will be - // on much longer than it will be off. Other pins than LED_BUILTIN may be used. The second - // value is used to put the LED on. If the LED is on with HIGH, that value should be passed ESPhttpUpdate.setLedPin(LED_BUILTIN, LOW); t_httpUpdate_return ret = ESPhttpUpdate.update(client, "http://192.168.1.8/esp8266.bin"); - // Or: - //t_httpUpdate_return ret = ESPhttpUpdate.update(client, "server", 80, "file.bin"); switch (ret) { case HTTP_UPDATE_FAILED: - USE_SERIAL.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); + Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); break; case HTTP_UPDATE_NO_UPDATES: - USE_SERIAL.println("HTTP_UPDATE_NO_UPDATES"); + Serial.println("HTTP_UPDATE_NO_UPDATES"); break; case HTTP_UPDATE_OK: - USE_SERIAL.println("HTTP_UPDATE_OK"); + Serial.println("HTTP_UPDATE_OK"); break; } } From 29d8c63cdec05a8e9ab47ea11acd7bd53250feb3 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sun, 7 Oct 2018 16:42:41 -0700 Subject: [PATCH 04/16] Make certain hash bits constant --- cores/esp8266/Updater.h | 4 ++-- libraries/ESP8266WiFi/src/BearSSLHelpers.cpp | 6 +++--- libraries/ESP8266WiFi/src/BearSSLHelpers.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h index 1f3deed82a..248ad97a50 100644 --- a/cores/esp8266/Updater.h +++ b/cores/esp8266/Updater.h @@ -36,14 +36,14 @@ class UpdaterHashClass { virtual void add(const void *data, uint32_t len) = 0; virtual void end() = 0; virtual int len() = 0; - virtual void *hash() = 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, void *signature, uint32_t signatureLen) = 0; // Verify, return "true" on success + virtual bool verify(UpdaterHashClass *hash, const void *signature, uint32_t signatureLen) = 0; // Verify, return "true" on success }; class UpdaterClass { diff --git a/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp b/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp index 18a6cd9dca..9a1f36a98b 100644 --- a/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp +++ b/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp @@ -839,8 +839,8 @@ int BearSSLHashSHA256::len() { return sizeof(_sha256); } -void *BearSSLHashSHA256::hash() { - return (void*) _sha256; +const void *BearSSLHashSHA256::hash() { + return (const void*) _sha256; } // SHA256 verifier @@ -857,7 +857,7 @@ uint32_t BearSSLSigningVerifier::length() } } -bool BearSSLSigningVerifier::verify(UpdaterHashClass *hash, void *signature, uint32_t signatureLen) { +bool BearSSLSigningVerifier::verify(UpdaterHashClass *hash, const void *signature, uint32_t signatureLen) { if (!_pubKey || !hash || !signature || signatureLen != length()) return false; if (_pubKey->isRSA()) { diff --git a/libraries/ESP8266WiFi/src/BearSSLHelpers.h b/libraries/ESP8266WiFi/src/BearSSLHelpers.h index 26ac48d8fb..c929aecaa3 100644 --- a/libraries/ESP8266WiFi/src/BearSSLHelpers.h +++ b/libraries/ESP8266WiFi/src/BearSSLHelpers.h @@ -146,7 +146,7 @@ class BearSSLHashSHA256 : public UpdaterHashClass { virtual void add(const void *data, uint32_t len) override; virtual void end() override; virtual int len() override; - virtual void *hash() override; + virtual const void *hash() override; private: br_sha256_context _cc; unsigned char _sha256[32]; @@ -155,7 +155,7 @@ class BearSSLHashSHA256 : public UpdaterHashClass { class BearSSLSigningVerifier : public UpdaterVerifyClass { public: virtual uint32_t length() override; - virtual bool verify(UpdaterHashClass *hash, void *signature, uint32_t signatureLen) override; + virtual bool verify(UpdaterHashClass *hash, const void *signature, uint32_t signatureLen) override; public: BearSSLSigningVerifier(BearSSLPublicKey *pubKey) { _pubKey = pubKey; } From 5fb8cd54a5825dbe067e4a4c5c90a332ff063fa5 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 10 Oct 2018 11:08:22 -0700 Subject: [PATCH 05/16] When update signed, don't do MD5 work or checking Signed updates provide a better guarantee than unsigned MD5 checking, and the signature may change the MD5 value anyway. Because of this, don't even bother doing MD5 work when a signed update is expected. --- cores/esp8266/Updater.cpp | 58 ++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 2a93a7fb1c..01e28f4e05 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -138,6 +138,9 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { DEBUG_UPDATER.printf("[begin] _size: 0x%08X (%d)\n", _size, _size); #endif + if (!_verify) { + _md5.begin(); + } return true; } @@ -186,44 +189,19 @@ bool UpdaterClass::end(bool evenIfRemaining){ _reset(); return false; } - } - _md5.begin(); - int binSize = _size; - if (_hash) { + int binSize = _size - sigLen - sizeof(uint32_t) /* The siglen word */; _hash->begin(); - binSize -= sigLen + sizeof(uint32_t); - } #ifdef DEBUG_UPDATER - DEBUG_UPDATER.printf("[Updater] Adjusted binsize: %d\n", binSize); + DEBUG_UPDATER.printf("[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 = binSize - i; - if(read > sizeof(buff)) { - read = sizeof(buff); - } - _md5.add(buff, read); - if (_hash) { + // 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); } - } - _md5.calculate(); - if(_target_md5.length()) { - if(_target_md5 != _md5.toString()){ - _setError(UPDATE_ERROR_MD5); - _reset(); - return false; - } -#ifdef DEBUG_UPDATER - else DEBUG_UPDATER.printf("MD5 Success: %s\n", _target_md5.c_str()); -#endif - } - - if (_verify && _hash) { _hash->end(); #ifdef DEBUG_UPDATER unsigned char *ret = (unsigned char *)_hash->hash(); @@ -250,6 +228,19 @@ bool UpdaterClass::end(bool evenIfRemaining){ _reset(); return false; } +#ifdef DEBUG_UPDATER + DEBUG_UPDATER.printf("[Updater] Signature matches\n"); +#endif + } else if (_target_md5.length()) { + _md5.calculate(); + if(_target_md5 != _md5.toString()){ + _setError(UPDATE_ERROR_MD5); + _reset(); + return false; + } +#ifdef DEBUG_UPDATER + else DEBUG_UPDATER.printf("MD5 Success: %s\n", _target_md5.c_str()); +#endif } if(!_verifyEnd()) { @@ -329,6 +320,9 @@ bool UpdaterClass::_writeBuffer(){ _setError(UPDATE_ERROR_WRITE); return false; } + if (!_verify) { + _md5.add(_buffer, _bufferLen); + } _currentAddress += _bufferLen; _bufferLen = 0; return true; From b9344f6f00bdf8a3de06e90601c96d2c166760c3 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Mon, 15 Oct 2018 14:01:20 -0700 Subject: [PATCH 06/16] Add python automatic signing if keys present --- platform.txt | 6 ++++- tools/signing.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100755 tools/signing.py diff --git a/platform.txt b/platform.txt index 56e42984fe..74815fa0ab 100644 --- a/platform.txt +++ b/platform.txt @@ -10,6 +10,7 @@ version=2.5.0-dev runtime.tools.xtensa-lx106-elf-gcc.path={runtime.platform.path}/tools/xtensa-lx106-elf runtime.tools.esptool.path={runtime.platform.path}/tools/esptool +runtime.tools.signing={runtime.platform.path}/tools/signing.py compiler.warning_flags=-w compiler.warning_flags.none=-w @@ -74,6 +75,8 @@ compiler.elf2hex.extra_flags= ## needs bash, git, and echo recipe.hooks.core.prebuild.1.pattern=bash -c "mkdir -p {build.path}/core && echo \#define ARDUINO_ESP8266_GIT_VER 0x`git --git-dir {runtime.platform.path}/.git rev-parse --short=8 HEAD 2>/dev/null || echo ffffffff` >{build.path}/core/core_version.h" recipe.hooks.core.prebuild.2.pattern=bash -c "mkdir -p {build.path}/core && echo \#define ARDUINO_ESP8266_GIT_DESC `cd "{runtime.platform.path}"; git describe --tags 2>/dev/null || echo unix-{version}` >>{build.path}/core/core_version.h" +recipe.hooks.core.prebuild.2.pattern="{runtime.tools.signing}" --mode header --publickey "{build.source.path}/public.key" --out "{build.path}/core/Updater_Signing.h" + ## windows-compatible version without git recipe.hooks.core.prebuild.1.pattern.windows=cmd.exe /c mkdir {build.path}\core & (echo #define ARDUINO_ESP8266_GIT_VER 0x00000000 & echo #define ARDUINO_ESP8266_GIT_DESC win-{version} ) > {build.path}\core\core_version.h recipe.hooks.core.prebuild.2.pattern.windows= @@ -102,7 +105,8 @@ recipe.objcopy.eep.pattern= ## Create hex #recipe.objcopy.hex.pattern="{compiler.path}{compiler.elf2hex.cmd}" {compiler.elf2hex.flags} {compiler.elf2hex.extra_flags} "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.hex" -recipe.objcopy.hex.pattern="{runtime.tools.esptool.path}/{compiler.esptool.cmd}" -eo "{runtime.platform.path}/bootloaders/eboot/eboot.elf" -bo "{build.path}/{build.project_name}.bin" -bm {build.flash_mode} -bf {build.flash_freq} -bz {build.flash_size} -bs .text -bp 4096 -ec -eo "{build.path}/{build.project_name}.elf" -bs .irom0.text -bs .text -bs .data -bs .rodata -bc -ec +recipe.objcopy.hex.1.pattern="{runtime.tools.esptool.path}/{compiler.esptool.cmd}" -eo "{runtime.platform.path}/bootloaders/eboot/eboot.elf" -bo "{build.path}/{build.project_name}.bin" -bm {build.flash_mode} -bf {build.flash_freq} -bz {build.flash_size} -bs .text -bp 4096 -ec -eo "{build.path}/{build.project_name}.elf" -bs .irom0.text -bs .text -bs .data -bs .rodata -bc -ec +recipe.objcopy.hex.2.pattern="{runtime.tools.signing}" --mode sign --privatekey "{build.source.path}/private.key" --bin "{build.path}/{build.project_name}.bin" --out "{build.path}/{build.project_name}.bin.signed" ## Save hex recipe.output.tmp_file={build.project_name}.bin diff --git a/tools/signing.py b/tools/signing.py new file mode 100755 index 0000000000..57eccc00f3 --- /dev/null +++ b/tools/signing.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +import sys +import argparse +import subprocess +import hashlib + +def parse_args(): + parser = argparse.ArgumentParser(description='Binary signing tool') + parser.add_argument('-m', '--mode', help='Mode (header, sign)') + parser.add_argument('-b', '--bin', help='Unsigned binary') + parser.add_argument('-o', '--out', help='Output file'); + parser.add_argument('-p', '--publickey', help='Public key file'); + parser.add_argument('-s', '--privatekey', help='Private(secret) key file'); + return parser.parse_args() + + +def main(): + args = parse_args() + if args.mode == "header": + val = "" + try: + with open(args.publickey, "rb") as f: + pub = f.read() + val += "#include \n" + val += "#define ARDUINO_SIGNING 1\n" + val += "static const char signing_pubkey[] PROGMEM = {\n" + for i in pub: + val += "0x%02x, \n" % ord(i) + val = val[:-3] + val +="\n};\n" + print "Enabling binary signing\n" + except: + print "Not enabling binary signing\n" + val += "#define ARDUINO_SIGNING 0\n" + with open(args.out, "w") as f: + f.write(val) + return 0 + elif args.mode == "sign": + val = "" + try: + with open(args.bin, "rb") as b: + bin = b.read() + sha256 = hashlib.sha256(bin) + print "Binary SHA256 = " + sha256.hexdigest() + signcmd = [ 'openssl', 'rsautl', '-sign', '-inkey', args.privatekey ] + proc = subprocess.Popen(signcmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) + signout = proc.communicate(input=sha256.digest())[0] + with open(args.out, "wb") as out: + out.write(bin) + out.write(signout) + out.write(b'\x00\x01\x00\x00') + print "Signed binary: " + args.out + except: + print "Not signing the generated binary\n" + return 0 + else: + print "ERROR: Mode not specified as header or sign\n" + +if __name__ == '__main__': + sys.exit(main()) + From 0ae91aebde95628d6ac32cb290de0fd73bb18b18 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Mon, 15 Oct 2018 15:26:30 -0700 Subject: [PATCH 07/16] Automatically include validation in updater --- cores/esp8266/Updater.cpp | 15 +++++++++++ cores/esp8266/Updater_Signing.h | 3 +++ .../httpUpdateSigned/httpUpdateSigned.ino | 15 +++++++++++ .../examples/httpUpdateSigned/private.key | 27 +++++++++++++++++++ .../examples/httpUpdateSigned/public.key | 9 +++++++ tools/signing.py | 12 ++++----- 6 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 cores/esp8266/Updater_Signing.h create mode 100644 libraries/ESP8266httpUpdate/examples/httpUpdateSigned/private.key create mode 100644 libraries/ESP8266httpUpdate/examples/httpUpdateSigned/public.key diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 01e28f4e05..11c2e8aa5a 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -6,6 +6,18 @@ //#define DEBUG_UPDATER Serial +#include +#ifndef ARDUINO_SIGNING + #define ARDUINO_SIGNING 0 +#endif + +#if ARDUINO_SIGNING + #include "../../libraries/ESP8266WiFi/src/BearSSLHelpers.h" + static BearSSLPublicKey signPubKey(signing_pubkey); + static BearSSLHashSHA256 hash; + static BearSSLSigningVerifier sign(&signPubKey); +#endif + extern "C" { #include "c_types.h" #include "spi_flash.h" @@ -26,6 +38,9 @@ UpdaterClass::UpdaterClass() , _hash(nullptr) , _verify(nullptr) { +#if ARDUINO_SIGNING + installSignature(&hash, &sign); +#endif } void UpdaterClass::_reset() { diff --git a/cores/esp8266/Updater_Signing.h b/cores/esp8266/Updater_Signing.h new file mode 100644 index 0000000000..58f3089f21 --- /dev/null +++ b/cores/esp8266/Updater_Signing.h @@ -0,0 +1,3 @@ +// This file will be overridden when automatic signing is used. +// By default, no signing. +#define ARDUINO_SIGNING 0 diff --git a/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino index d6068e8cf1..1867acb5e7 100644 --- a/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino +++ b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino @@ -18,6 +18,13 @@ ESP8266WiFiMulti WiFiMulti; +#define MANUAL_SIGNING 0 + +// This example is now configured to use the automated signing support +// present in the Arduino IDE by having a "private.key" and "public.key" +// in the sketch folder. You can also programmatically enable signing +// using the method shown here. + // This key is taken from the server public certificate in BearSSL examples // You should make your own private/public key pair and guard the private // key (never upload it to the 8266). @@ -32,9 +39,11 @@ cuWNgTnDQd6KUzV0E4it2fNG+cHN4kEvofN6gHx8IbOrXwFttlpAH/o7bcfCnUVh TQIDAQAB -----END PUBLIC KEY----- )EOF"; +#if MANUAL_SIGNING BearSSLPublicKey *signPubKey = nullptr; BearSSLHashSHA256 *hash; BearSSLSigningVerifier *sign; +#endif void setup() { @@ -54,9 +63,11 @@ void setup() { WiFi.mode(WIFI_STA); WiFiMulti.addAP("SSID", "PASS"); + #if MANUAL_SIGNING signPubKey = new BearSSLPublicKey(pubkey); hash = new BearSSLHashSHA256(); sign = new BearSSLSigningVerifier(signPubKey); + #endif } @@ -66,8 +77,12 @@ void loop() { WiFiClient client; + #if MANUAL_SIGNING // Ensure all updates are signed appropriately. W/o this call, all will be accepted. Update.installSignature(hash, sign); + #endif + // If the key files are present in the build directory, signing will be + // enabled using them automatically ESPhttpUpdate.setLedPin(LED_BUILTIN, LOW); diff --git a/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/private.key b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/private.key new file mode 100644 index 0000000000..09e3bc125b --- /dev/null +++ b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/private.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAu1Pt7yEk/xI+6cozLj5Bu4xV8gXDXcHS0rSJFfl4wBTk4UXp +aJRaLfR1k0juEEa5LBRZaoA0iLj2e6kfCibONx0VVoWmeqN2HBc3zkA1eqCksI0Q +Uudzto4KhKHp0odiZ2zo6c/2Tn1zqD/m3OLoSjVTbsJmGuwx8RGMBXozpg/uL0hH +flihX+HND4Xfw92QXv7SaPBhgvM9xyRxn0/w3J2nNjtuPuVN5vcQkd8ncMexVfy9 +AWp+HSA5AT5N8CJ/EeIsdDMY1US28bUePzj1WIo75bZHKZNFw/iXe2xoPpm74qri +MNSlW2craFP2K3KYnI28vJeUU6t9I6LS9zt2zQIDAQABAoIBAE5GpuDKb8Qp4qIc +fMBxAVSWMn+cSuONj0O+bp4BDaTt1ioP5ZVukDQtt0ehLOEePFgf9LEc+1a6Ozy3 +EaJTTs4W2Ai8djE+xqa8SPRlPjOMluSzPUP3NRHuTpTXd3YiXksrZjP1U02+/Cos +8ZIROtFvcPqSPso3MjMyitjrFFPqEtf1P+UiamjDrMSM72YX4W55kOkiCWCnAOmw +mGTlXOIqDSTBb1lloKWJfpB3RdnNo2izkU1HMBn7hVi433NUBA22o+RZhDFSZdD4 +3kbkUqXd4p+vc/sh6muJtWS/COSIPFkLzdEYpBdt3XQ4FhlsRtILJaPWXa4OPjR6 +ZoOwMB0CgYEA6OHfIofQiu4+HlTDN5YdyTmtYEYcrtbaQUxuQSEa2mshBphHP8uT +mYRVl2BzuprFmXZPz+FcjnPnfxqEehljvA3wMjA/PE+nQo9yyOC0N4ulXpkkqHdR +f+4KZVR7D+hesGe+57OQmvTqYZSHEt/ubjC9wZ90UFonLjsa4zibbrsCgYEAzexn +XDnThb3ffyBgvprP0IJjgMAEY0pXD++PKPQqPu9JMz68t7roYzkKFCFVOsaWpKxC +vX9mvYjTBjLpWh+ltIAN+EFz6seIbeSJ0RNybsAXYwT/mFWGHx2tMtlW6DgBu3UD +J2Yf76n0JaddBkfNMQI00Dl41+MU+AwwTB9fTBcCgYB2+f6Pm6d1cyYVROS/X1g0 +V9011FwPDwFOXwftCka31Ad5YQ71jsIHqk44GjTF3xCYyJMZ917cAGcCzr9jydjk +WJKgcXm9DEy9ep//9Jzdy+BepgrObrcajriM8E424FaP9VDY+yojoICl/cXMZM9h +SFGJvDcmXgiqW9PuxhrSxQKBgAMN2oqXoPd+1W3BQS4ShbqF9IvYTThbxebKmsj0 +thuw2NkVuR7Qetnd4rRhui3g/CL9GxBMb22oNdkFsEhR59dBfvOLpPh6dR+MIC8l +prDV0IL7c/8CZbbYbdUvPAa9rejl12IiNZ8MWj6kuNB7CCQN8FKWR6CMEaeMJrs6 +S+OJAoGAbehNOUwEzmUKkfxf+279kBkgabcQ3NTaeSx0QOnI9KWHFGLYLQk9cMSu +maQJ1TYpbIoP1njzJ4bI2tynhwEuSMEhh4afP6U5H10NJX4PqSd0Rqc1vSJYcszr +5mUWil8FfbCBZ8jod2NQ55KYMVY5CphCqaK/s2bw2pvIR3uqJGg= +-----END RSA PRIVATE KEY----- diff --git a/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/public.key b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/public.key new file mode 100644 index 0000000000..054e88b16d --- /dev/null +++ b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/public.key @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1Pt7yEk/xI+6cozLj5B +u4xV8gXDXcHS0rSJFfl4wBTk4UXpaJRaLfR1k0juEEa5LBRZaoA0iLj2e6kfCibO +Nx0VVoWmeqN2HBc3zkA1eqCksI0QUudzto4KhKHp0odiZ2zo6c/2Tn1zqD/m3OLo +SjVTbsJmGuwx8RGMBXozpg/uL0hHflihX+HND4Xfw92QXv7SaPBhgvM9xyRxn0/w +3J2nNjtuPuVN5vcQkd8ncMexVfy9AWp+HSA5AT5N8CJ/EeIsdDMY1US28bUePzj1 +WIo75bZHKZNFw/iXe2xoPpm74qriMNSlW2craFP2K3KYnI28vJeUU6t9I6LS9zt2 +zQIDAQAB +-----END PUBLIC KEY----- diff --git a/tools/signing.py b/tools/signing.py index 57eccc00f3..6dbbfaa447 100755 --- a/tools/signing.py +++ b/tools/signing.py @@ -30,9 +30,9 @@ def main(): val += "0x%02x, \n" % ord(i) val = val[:-3] val +="\n};\n" - print "Enabling binary signing\n" + sys.stderr.write("Enabling binary signing\n") except: - print "Not enabling binary signing\n" + sys.stderr.write("Not enabling binary signing\n") val += "#define ARDUINO_SIGNING 0\n" with open(args.out, "w") as f: f.write(val) @@ -43,7 +43,7 @@ def main(): with open(args.bin, "rb") as b: bin = b.read() sha256 = hashlib.sha256(bin) - print "Binary SHA256 = " + sha256.hexdigest() + sys.stderr.write("Signing SHA256 = " + sha256.hexdigest() + "\n"); signcmd = [ 'openssl', 'rsautl', '-sign', '-inkey', args.privatekey ] proc = subprocess.Popen(signcmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) signout = proc.communicate(input=sha256.digest())[0] @@ -51,12 +51,12 @@ def main(): out.write(bin) out.write(signout) out.write(b'\x00\x01\x00\x00') - print "Signed binary: " + args.out + sys.stderr.write("Signed binary: " + args.out + "\n") except: - print "Not signing the generated binary\n" + sys.stderr.write("Not signing the generated binary\n") return 0 else: - print "ERROR: Mode not specified as header or sign\n" + sys.stderr.write("ERROR: Mode not specified as header or sign\n") if __name__ == '__main__': sys.exit(main()) From 98232902858e8a13a620be08935342f02e08f097 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Mon, 5 Nov 2018 12:01:16 -0800 Subject: [PATCH 08/16] Add documentation on signing process 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. --- doc/ota_updates/readme.rst | 98 +++++++++++++++++++++++++++++++++++--- tools/signing.py | 1 - 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/doc/ota_updates/readme.rst b/doc/ota_updates/readme.rst index abdf3c453d..79dda812e7 100644 --- a/doc/ota_updates/readme.rst +++ b/doc/ota_updates/readme.rst @@ -17,12 +17,17 @@ Arduino IDE option is intended primarily for software development phase. The two In any case, the first firmware upload has to be done over a serial port. If the OTA routines are correctly implemented in a sketch, then all subsequent uploads may be done over the air. -There is no imposed security on OTA process from being hacked. It is up to developer to ensure that updates are allowed only from legitimate / trusted sources. Once the update is complete, the module restarts, and the new code is executed. The developer should ensure that the application running on the module is shut down and restarted in a safe manner. Chapters below provide additional information regarding security and safety of OTA process. +By default there is no imposed security on OTA process. It is up to developer to ensure that updates are allowed only from legitimate / trusted sources. Once the update is complete, the module restarts, and the new code is executed. The developer should ensure that the application running on the module is shut down and restarted in a safe manner. Chapters below provide additional information regarding security and safety of OTA process. -Security -~~~~~~~~ +Security Disclaimer +~~~~~~~~~~~~~~~~~~~ + +No guarantees as to the level of security provided for your application by the following methods is implied. Please refer to the GNU LGPL license associated for this project for full disclaimers. If you do find security weaknesses, please don't hesitate to contact the maintainers or supply pull requests with fixes. The MD5 verification and password protection schemes are already known as supplying a very weak level of security. + +Basic Security +~~~~~~~~~~~~~~ -Module has to be exposed wirelessly to get it updated with a new sketch. That poses chances of module being violently hacked and loaded with some other code. To reduce likelihood of being hacked consider protecting your uploads with a password, selecting certain OTA port, etc. +The module has to be exposed wirelessly to get it updated with a new sketch. That poses chances of module being violently hacked and loaded with some other code. To reduce likelihood of being hacked consider protecting your uploads with a password, selecting certain OTA port, etc. Check functionality provided with `ArduinoOTA `__ library that may improve security: @@ -36,6 +41,87 @@ Certain protection functionality is already built in and do not require any addi Make your own risk analysis and depending on application decide what library functions to implement. If required, consider implementation of other means of protection from being hacked, e.g. exposing module for uploads only according to specific schedule, trigger OTA only be user pressing dedicated “Update” button wired to ESP, etc. +Advanced Security - Signed Updates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +While the above password-based security will dissuade casual hacking attempts, it is not highly secure. For applications where a higher level of security is needed, cryptographically signed OTA updates can be required. This is a significantly higher level of security for your application than the basic levels described above. It uses SHA256 hashing in place of MD5 (which is known to be cryptographically broken) and RSA-2048 bit level encryption to guarantee only the holder of a cryptographic private key can generate code accepted by the OTA update mechanisms. + +These are updates whose compiled binary are signed with a private key (held by the developer) and verified with a public key (stored in the application and available for all to see). The signing process computes a hash of the binary code, encrypts the hash with the developer's private key, and appends this encrypted hash to the binary that is uploaded (via OTA, web, or HTTP server). If the code is modified or replaced in any way by anyone by the developer with the key, the hash will not match and the ESP8266 will reject the upload and not accept it. + +Cryptographic signing only protects against tampering of binaries delivered OTA. If someone has physical access they will always be able to flash the device over the serial port. Signing also does not encrypt anything but the hash (so that it can't be modified), so this does not provide protection for code inside the device. Again, if a user has physical access they can read out your program. + +Securing your private key is paramount. The same private/public keypair needs to be used to sign binaries as the original upload. Loss of the private key associated with a binary means that you will not be able to OTA to update any of your devices in the field. Alternatively, if the private key is copied, then the copy can be used to sign binaries which will be accepted. + +Signed Binary Format +^^^^^^^^^^^^^^^^^^^^ + +The format of a signed binary is compatible with the standard binary format, and can be uploaded to a non-signed ESP8266 via serial or OTA without any conditions. Note, however, that once an unsigned OTA app is overwritten by this signed version, further updates will require signing. + +As shown below, the signed hash is appended to the unsigned binary followed by the total length of the signed hash and length dword (i.e. if the signed has was 64 bytes, then this uint32 will contain 64+4=68). This format allows for extensibility (such as adding in a CA-based validation scheme allowing multiple signing keys all based off of a trust anchor), and pull requests are always welcome. + +.. code:: + + NORMAL-BINARY + +Signed Binary Prequisites +^^^^^^^^^^^^^^^^^^^^^^^^^ + +OpenSSL is required to run the standard signing steps, and should be available on any UNIX-like or Windows system. As usual, the latest stable version of OpenSSL is recommended. + +Signing requires the generation of an RSA-2048 key (other bit lengths are supported as well, but 2048 is a good selection today) using any appropriate tool. The following lines will generate a new public/private keypair. Run them in the sketch directory: + +.. code:: bash + + openssl genrsa -out private.key 2048 + openssl rsa -in private.key -outform PEM -pubout -out public.key + +Automatic Signing +^^^^^^^^^^^^^^^^^ + +The simplest way of implementing signing is to use the automatic mode. This mode uses the IDE to configure the source code to enable sigining verification with a given public key, and signs binaries as part of the standard build process using a given public key. + +To enable this mode, just include `private.key` and `public.key` in the sketch `.ino` directory. The IDE will call a helper script (`tools/signing.py`) before the build begins to create a header to enable key validation using the given public key, and after the build process to actually do the signing, generating a `sketch.bin.signed` file. When OTA is enabled (ArduinoOTA, Web, or HTTP) the binary will only accept signed updates automatically. + +When the signing process starts, the message: +.. code:: + + Enabling binary signing + +Will appear in the IDE window before a compile is launched, and at the completion of the build the signed binary file well be displayed in the IDE build window as: +.. code:: + + Signed binary: /full/path/to/sketch.bin.signed + +If you receive either of the following messages in the IDE window, the signing was not completed and you will need to verify the `public.key` and `private.key`: +.. code:: + + Not enabling binary signing + ... or ... + Not signing the generated binary + +Manual Signing +^^^^^^^^^^^^^^ + +Users may also manually sign executables and require the OTA process to verify their signature. In the main code, before enabling any update methods, add the call: + +.. code:: cpp + + + BearSSLPublicKey signPubKey( ... key contents ... ); + BearSSLHashSHA256 hash; + BearSSLSigningVerifier sign( &signPubKey ); + ... + + Update.installSignature( &hash, &sign ); + +The above snipped creates a BearSSL public key, a SHA256 hash verifier, and tells the Update object to use them to validate any updates it receives from any method. + +Compile the sketch normally and, onve a `.bin` file is available, sign it using the signer script: + +.. code:: bash + + /tools/signing.py --mode sign --privatekey --bin --out + Safety ~~~~~~ @@ -52,8 +138,8 @@ The following functions are provided with `ArduinoOTA Date: Tue, 6 Nov 2018 11:44:41 -0800 Subject: [PATCH 09/16] Update documentation formatting --- doc/ota_updates/readme.rst | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/doc/ota_updates/readme.rst b/doc/ota_updates/readme.rst index 79dda812e7..987d8a8834 100644 --- a/doc/ota_updates/readme.rst +++ b/doc/ota_updates/readme.rst @@ -44,20 +44,20 @@ Make your own risk analysis and depending on application decide what library fun Advanced Security - Signed Updates ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -While the above password-based security will dissuade casual hacking attempts, it is not highly secure. For applications where a higher level of security is needed, cryptographically signed OTA updates can be required. This is a significantly higher level of security for your application than the basic levels described above. It uses SHA256 hashing in place of MD5 (which is known to be cryptographically broken) and RSA-2048 bit level encryption to guarantee only the holder of a cryptographic private key can generate code accepted by the OTA update mechanisms. +While the above password-based security will dissuade casual hacking attempts, it is not highly secure. For applications where a higher level of security is needed, cryptographically signed OTA updates can be required. It uses SHA256 hashing in place of MD5 (which is known to be cryptographically broken) and RSA-2048 bit level encryption to guarantee only the holder of a cryptographic private key can generate code accepted by the OTA update mechanisms. These are updates whose compiled binary are signed with a private key (held by the developer) and verified with a public key (stored in the application and available for all to see). The signing process computes a hash of the binary code, encrypts the hash with the developer's private key, and appends this encrypted hash to the binary that is uploaded (via OTA, web, or HTTP server). If the code is modified or replaced in any way by anyone by the developer with the key, the hash will not match and the ESP8266 will reject the upload and not accept it. Cryptographic signing only protects against tampering of binaries delivered OTA. If someone has physical access they will always be able to flash the device over the serial port. Signing also does not encrypt anything but the hash (so that it can't be modified), so this does not provide protection for code inside the device. Again, if a user has physical access they can read out your program. -Securing your private key is paramount. The same private/public keypair needs to be used to sign binaries as the original upload. Loss of the private key associated with a binary means that you will not be able to OTA to update any of your devices in the field. Alternatively, if the private key is copied, then the copy can be used to sign binaries which will be accepted. +**Securing your private key is paramount. The same private/public keypair needs to be used to sign binaries as the original upload. Loss of the private key associated with a binary means that you will not be able to OTA to update any of your devices in the field. Alternatively, if the private key is copied, then the copy can be used to sign binaries which will be accepted.** Signed Binary Format ^^^^^^^^^^^^^^^^^^^^ The format of a signed binary is compatible with the standard binary format, and can be uploaded to a non-signed ESP8266 via serial or OTA without any conditions. Note, however, that once an unsigned OTA app is overwritten by this signed version, further updates will require signing. -As shown below, the signed hash is appended to the unsigned binary followed by the total length of the signed hash and length dword (i.e. if the signed has was 64 bytes, then this uint32 will contain 64+4=68). This format allows for extensibility (such as adding in a CA-based validation scheme allowing multiple signing keys all based off of a trust anchor), and pull requests are always welcome. +As shown below, the signed hash is appended to the unsigned binary followed by the total length of the signed hash (i.e. if the signed has was 64 bytes, then this uint32 will contain 64). This format allows for extensibility (such as adding in a CA-based validation scheme allowing multiple signing keys all based off of a trust anchor), and pull requests are always welcome. .. code:: @@ -83,24 +83,27 @@ The simplest way of implementing signing is to use the automatic mode. This mod To enable this mode, just include `private.key` and `public.key` in the sketch `.ino` directory. The IDE will call a helper script (`tools/signing.py`) before the build begins to create a header to enable key validation using the given public key, and after the build process to actually do the signing, generating a `sketch.bin.signed` file. When OTA is enabled (ArduinoOTA, Web, or HTTP) the binary will only accept signed updates automatically. When the signing process starts, the message: + .. code:: Enabling binary signing Will appear in the IDE window before a compile is launched, and at the completion of the build the signed binary file well be displayed in the IDE build window as: + .. code:: Signed binary: /full/path/to/sketch.bin.signed If you receive either of the following messages in the IDE window, the signing was not completed and you will need to verify the `public.key` and `private.key`: + .. code:: Not enabling binary signing ... or ... Not signing the generated binary -Manual Signing -^^^^^^^^^^^^^^ +Manual Signing Binaries +^^^^^^^^^^^^^^^^^^^^^^^ Users may also manually sign executables and require the OTA process to verify their signature. In the main code, before enabling any update methods, add the call: @@ -116,7 +119,7 @@ Users may also manually sign executables and require the OTA process to verify t The above snipped creates a BearSSL public key, a SHA256 hash verifier, and tells the Update object to use them to validate any updates it receives from any method. -Compile the sketch normally and, onve a `.bin` file is available, sign it using the signer script: +Compile the sketch normally and, once a `.bin` file is available, sign it using the signer script: .. code:: bash From b3b7477c3c96e65deee90d1cdcf5876f955494eb Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Fri, 9 Nov 2018 14:15:01 -0800 Subject: [PATCH 10/16] Move to new BearSSL:: namespace for classes --- cores/esp8266/Updater.cpp | 6 +++--- doc/ota_updates/readme.rst | 6 +++--- libraries/ESP8266WiFi/src/BearSSLHelpers.cpp | 14 +++++++------- libraries/ESP8266WiFi/src/BearSSLHelpers.h | 8 ++++---- .../examples/httpUpdateSigned/httpUpdateSigned.ino | 12 ++++++------ 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 9f31726581..1b62d56d0c 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -13,9 +13,9 @@ #if ARDUINO_SIGNING #include "../../libraries/ESP8266WiFi/src/BearSSLHelpers.h" - static BearSSLPublicKey signPubKey(signing_pubkey); - static BearSSLHashSHA256 hash; - static BearSSLSigningVerifier sign(&signPubKey); + static BearSSL::PublicKey signPubKey(signing_pubkey); + static BearSSL::HashSHA256 hash; + static BearSSL::SigningVerifier sign(&signPubKey); #endif extern "C" { diff --git a/doc/ota_updates/readme.rst b/doc/ota_updates/readme.rst index 987d8a8834..7f407a185c 100644 --- a/doc/ota_updates/readme.rst +++ b/doc/ota_updates/readme.rst @@ -110,9 +110,9 @@ Users may also manually sign executables and require the OTA process to verify t .. code:: cpp - BearSSLPublicKey signPubKey( ... key contents ... ); - BearSSLHashSHA256 hash; - BearSSLSigningVerifier sign( &signPubKey ); + BearSSL::PublicKey signPubKey( ... key contents ... ); + BearSSL::HashSHA256 hash; + BearSSL::SigningVerifier sign( &signPubKey ); ... Update.installSignature( &hash, &sign ); diff --git a/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp b/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp index abedf2776e..97845e6418 100644 --- a/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp +++ b/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp @@ -826,29 +826,29 @@ bool X509List::append(const uint8_t *derCert, size_t derLen) { } // SHA256 hash for updater -void BearSSLHashSHA256::begin() { +void HashSHA256::begin() { br_sha256_init( &_cc ); memset( _sha256, 0, sizeof(_sha256) ); } -void BearSSLHashSHA256::add(const void *data, uint32_t len) { +void HashSHA256::add(const void *data, uint32_t len) { br_sha256_update( &_cc, data, len ); } -void BearSSLHashSHA256::end() { +void HashSHA256::end() { br_sha256_out( &_cc, _sha256 ); } -int BearSSLHashSHA256::len() { +int HashSHA256::len() { return sizeof(_sha256); } -const void *BearSSLHashSHA256::hash() { +const void *HashSHA256::hash() { return (const void*) _sha256; } // SHA256 verifier -uint32_t BearSSLSigningVerifier::length() +uint32_t SigningVerifier::length() { if (!_pubKey) { return 0; @@ -861,7 +861,7 @@ uint32_t BearSSLSigningVerifier::length() } } -bool BearSSLSigningVerifier::verify(UpdaterHashClass *hash, const void *signature, uint32_t signatureLen) { +bool SigningVerifier::verify(UpdaterHashClass *hash, const void *signature, uint32_t signatureLen) { if (!_pubKey || !hash || !signature || signatureLen != length()) return false; if (_pubKey->isRSA()) { diff --git a/libraries/ESP8266WiFi/src/BearSSLHelpers.h b/libraries/ESP8266WiFi/src/BearSSLHelpers.h index 9d2b3ad3ef..966fe3f2de 100644 --- a/libraries/ESP8266WiFi/src/BearSSLHelpers.h +++ b/libraries/ESP8266WiFi/src/BearSSLHelpers.h @@ -139,7 +139,7 @@ class Session { }; // Updater SHA256 hash and signature verification -class BearSSLHashSHA256 : public UpdaterHashClass { +class HashSHA256 : public UpdaterHashClass { public: virtual void begin() override; virtual void add(const void *data, uint32_t len) override; @@ -151,16 +151,16 @@ class BearSSLHashSHA256 : public UpdaterHashClass { unsigned char _sha256[32]; }; -class BearSSLSigningVerifier : public UpdaterVerifyClass { +class SigningVerifier : public UpdaterVerifyClass { public: virtual uint32_t length() override; virtual bool verify(UpdaterHashClass *hash, const void *signature, uint32_t signatureLen) override; public: - BearSSLSigningVerifier(BearSSLPublicKey *pubKey) { _pubKey = pubKey; } + SigningVerifier(PublicKey *pubKey) { _pubKey = pubKey; } private: - BearSSLPublicKey *_pubKey; + PublicKey *_pubKey; }; }; diff --git a/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino index 1867acb5e7..582b0115f0 100644 --- a/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino +++ b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino @@ -40,9 +40,9 @@ TQIDAQAB -----END PUBLIC KEY----- )EOF"; #if MANUAL_SIGNING -BearSSLPublicKey *signPubKey = nullptr; -BearSSLHashSHA256 *hash; -BearSSLSigningVerifier *sign; +BearSSL::PublicKey *signPubKey = nullptr; +BearSSL::HashSHA256 *hash; +BearSSL::SigningVerifier *sign; #endif void setup() { @@ -64,9 +64,9 @@ void setup() { WiFiMulti.addAP("SSID", "PASS"); #if MANUAL_SIGNING - signPubKey = new BearSSLPublicKey(pubkey); - hash = new BearSSLHashSHA256(); - sign = new BearSSLSigningVerifier(signPubKey); + signPubKey = new BearSSL::PublicKey(pubkey); + hash = new BearSSL::HashSHA256(); + sign = new BearSSL::SigningVerifier(signPubKey); #endif } From 31b22fb72fc264040e0690c61cc16c6a206f4bff Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 28 Nov 2018 19:21:36 -0800 Subject: [PATCH 11/16] Move 2 strings into PROGMEM A couple error strings weren't in PMEM. Move them. --- cores/esp8266/Updater.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 1b62d56d0c..39fbde4bae 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -500,10 +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.printf("Signature verification failed\n"); + 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){ From 4164bceab989819df24fe34be0abc38632f18b8b Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 28 Nov 2018 20:13:27 -0800 Subject: [PATCH 12/16] Add openssl return code error checking --- tools/signing.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tools/signing.py b/tools/signing.py index 9bdbf1e19b..08180f115e 100755 --- a/tools/signing.py +++ b/tools/signing.py @@ -1,10 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -import sys import argparse -import subprocess import hashlib +import os +import subprocess +import sys def parse_args(): parser = argparse.ArgumentParser(description='Binary signing tool') @@ -39,19 +40,25 @@ def main(): return 0 elif args.mode == "sign": val = "" + if not os.path.isfile(args.privatekey): + return try: with open(args.bin, "rb") as b: bin = b.read() sha256 = hashlib.sha256(bin) signcmd = [ 'openssl', 'rsautl', '-sign', '-inkey', args.privatekey ] proc = subprocess.Popen(signcmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) - signout = proc.communicate(input=sha256.digest())[0] - with open(args.out, "wb") as out: - out.write(bin) - out.write(signout) - out.write(b'\x00\x01\x00\x00') - sys.stderr.write("Signed binary: " + args.out + "\n") - except: + signout, signerr = proc.communicate(input=sha256.digest()) + if proc.returncode: + sys.stderr.write("OpenSSL returned an error signing the binary: " + str(proc.returncode) + "\nSTDERR: " + str(signerr)) + else: + with open(args.out, "wb") as out: + out.write(bin) + out.write(signout) + out.write(b'\x00\x01\x00\x00') + sys.stderr.write("Signed binary: " + args.out + "\n") + except Exception as e: + sys.stderr.write(str(e)) sys.stderr.write("Not signing the generated binary\n") return 0 else: From e530a8a89bd11beca25b2fb2aa6209e824b9b649 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 28 Nov 2018 20:20:54 -0800 Subject: [PATCH 13/16] Completely silence normal unsigned builds Users normally won't be signing their apps. In this case a message like "Not signing binaries" would make them worry over nothing. Silence it. Users who do care about signing will still get the Signing binaries and other output. --- tools/signing.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/signing.py b/tools/signing.py index 08180f115e..e0a6a16765 100755 --- a/tools/signing.py +++ b/tools/signing.py @@ -33,7 +33,10 @@ def main(): val +="\n};\n" sys.stderr.write("Enabling binary signing\n") except: - sys.stderr.write("Not enabling binary signing\n") +# Silence the default case to avoid people thinking something is wrong. +# Only people who care about signing will know what it means, anyway, +# and they can check for the positive acknowledgement above. +# sys.stderr.write("Not enabling binary signing\n") val += "#define ARDUINO_SIGNING 0\n" with open(args.out, "w") as f: f.write(val) From 5b243e27d2006b02c6d8f8ff3b904ee6e0864656 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 28 Nov 2018 21:07:56 -0800 Subject: [PATCH 14/16] Move debug strings to PMEM Saves ~600 bytes when in debug mode. --- cores/esp8266/Updater.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 66981cb07b..de57ef3c66 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -113,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) @@ -148,9 +148,9 @@ 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 if (!_verify) { @@ -178,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(); @@ -196,7 +196,7 @@ bool UpdaterClass::end(bool evenIfRemaining){ if (_verify) { ESP.flashRead(_startAddress + _size - sizeof(uint32_t), &sigLen, sizeof(uint32_t)); #ifdef DEBUG_UPDATER - DEBUG_UPDATER.printf("[Updater] sigLen: %d\n", sigLen); + DEBUG_UPDATER.printf_P(PSTR("[Updater] sigLen: %d\n"), sigLen); #endif if (sigLen != _verify->length()) { _setError(UPDATE_ERROR_SIGN); @@ -207,7 +207,7 @@ bool UpdaterClass::end(bool evenIfRemaining){ int binSize = _size - sigLen - sizeof(uint32_t) /* The siglen word */; _hash->begin(); #ifdef DEBUG_UPDATER - DEBUG_UPDATER.printf("[Updater] Adjusted binsize: %d\n", binSize); + DEBUG_UPDATER.printf_P(PSTR("[Updater] Adjusted binsize: %d\n"), binSize); #endif // Calculate the MD5 and hash using proper size uint8_t buff[128]; @@ -219,7 +219,7 @@ bool UpdaterClass::end(bool evenIfRemaining){ _hash->end(); #ifdef DEBUG_UPDATER unsigned char *ret = (unsigned char *)_hash->hash(); - DEBUG_UPDATER.printf("[Updater] Computed 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 @@ -231,7 +231,7 @@ bool UpdaterClass::end(bool evenIfRemaining){ } ESP.flashRead(_startAddress + binSize, (uint32_t *)sig, sigLen); #ifdef DEBUG_UPDATER - DEBUG_UPDATER.printf("[Updater] Received Signature:"); + DEBUG_UPDATER.printf_P(PSTR("[Updater] Received Signature:")); for (size_t i=0; i Date: Fri, 30 Nov 2018 10:35:03 -0800 Subject: [PATCH 15/16] Fix prebuild numbering, typo in docs --- doc/ota_updates/readme.rst | 2 +- platform.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ota_updates/readme.rst b/doc/ota_updates/readme.rst index 7f407a185c..f5d1183a16 100644 --- a/doc/ota_updates/readme.rst +++ b/doc/ota_updates/readme.rst @@ -57,7 +57,7 @@ Signed Binary Format The format of a signed binary is compatible with the standard binary format, and can be uploaded to a non-signed ESP8266 via serial or OTA without any conditions. Note, however, that once an unsigned OTA app is overwritten by this signed version, further updates will require signing. -As shown below, the signed hash is appended to the unsigned binary followed by the total length of the signed hash (i.e. if the signed has was 64 bytes, then this uint32 will contain 64). This format allows for extensibility (such as adding in a CA-based validation scheme allowing multiple signing keys all based off of a trust anchor), and pull requests are always welcome. +As shown below, the signed hash is appended to the unsigned binary followed by the total length of the signed hash (i.e. if the signed hash was 64 bytes, then this uint32 will contain 64). This format allows for extensibility (such as adding in a CA-based validation scheme allowing multiple signing keys all based off of a trust anchor), and pull requests are always welcome. .. code:: diff --git a/platform.txt b/platform.txt index 74815fa0ab..39f4575d13 100644 --- a/platform.txt +++ b/platform.txt @@ -75,7 +75,7 @@ compiler.elf2hex.extra_flags= ## needs bash, git, and echo recipe.hooks.core.prebuild.1.pattern=bash -c "mkdir -p {build.path}/core && echo \#define ARDUINO_ESP8266_GIT_VER 0x`git --git-dir {runtime.platform.path}/.git rev-parse --short=8 HEAD 2>/dev/null || echo ffffffff` >{build.path}/core/core_version.h" recipe.hooks.core.prebuild.2.pattern=bash -c "mkdir -p {build.path}/core && echo \#define ARDUINO_ESP8266_GIT_DESC `cd "{runtime.platform.path}"; git describe --tags 2>/dev/null || echo unix-{version}` >>{build.path}/core/core_version.h" -recipe.hooks.core.prebuild.2.pattern="{runtime.tools.signing}" --mode header --publickey "{build.source.path}/public.key" --out "{build.path}/core/Updater_Signing.h" +recipe.hooks.core.prebuild.3.pattern="{runtime.tools.signing}" --mode header --publickey "{build.source.path}/public.key" --out "{build.path}/core/Updater_Signing.h" ## windows-compatible version without git recipe.hooks.core.prebuild.1.pattern.windows=cmd.exe /c mkdir {build.path}\core & (echo #define ARDUINO_ESP8266_GIT_VER 0x00000000 & echo #define ARDUINO_ESP8266_GIT_DESC win-{version} ) > {build.path}\core\core_version.h From 15ca564f74ed3ef3d13c793ba506e24e663956ef Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Fri, 30 Nov 2018 11:25:04 -0800 Subject: [PATCH 16/16] Warn about Windows incompatibility in build and docs 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. --- doc/ota_updates/readme.rst | 6 +++--- .../examples/httpUpdateSigned/httpUpdateSigned.ino | 5 +++++ platform.txt | 7 ++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/doc/ota_updates/readme.rst b/doc/ota_updates/readme.rst index f5d1183a16..17d16b4ebf 100644 --- a/doc/ota_updates/readme.rst +++ b/doc/ota_updates/readme.rst @@ -75,10 +75,10 @@ Signing requires the generation of an RSA-2048 key (other bit lengths are suppor openssl genrsa -out private.key 2048 openssl rsa -in private.key -outform PEM -pubout -out public.key -Automatic Signing -^^^^^^^^^^^^^^^^^ +Automatic Signing -- Only available on Linux and Mac +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The simplest way of implementing signing is to use the automatic mode. This mode uses the IDE to configure the source code to enable sigining verification with a given public key, and signs binaries as part of the standard build process using a given public key. +The simplest way of implementing signing is to use the automatic mode, which is only possible on Linux and Mac presently due to missing tools under Windows. This mode uses the IDE to configure the source code to enable sigining verification with a given public key, and signs binaries as part of the standard build process using a given public key. To enable this mode, just include `private.key` and `public.key` in the sketch `.ino` directory. The IDE will call a helper script (`tools/signing.py`) before the build begins to create a header to enable key validation using the given public key, and after the build process to actually do the signing, generating a `sketch.bin.signed` file. When OTA is enabled (ArduinoOTA, Web, or HTTP) the binary will only accept signed updates automatically. diff --git a/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino index 582b0115f0..b503c1d5b4 100644 --- a/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino +++ b/libraries/ESP8266httpUpdate/examples/httpUpdateSigned/httpUpdateSigned.ino @@ -2,6 +2,11 @@ httpUpdateSigned.ino - Earle F. Philhower, III Released into the Public Domain + For use while building under Linux or Mac. + + Automatic code signing is not supported on Windows, so this example + DOES NOT WORK UNDER WINDOWS. + Shows how to use a public key extracted from your private certificate to only allow updates that you have signed to be applied over HTTP. Remote updates will require your private key to sign them, but of course diff --git a/platform.txt b/platform.txt index 39f4575d13..9efbf00302 100644 --- a/platform.txt +++ b/platform.txt @@ -79,7 +79,8 @@ recipe.hooks.core.prebuild.3.pattern="{runtime.tools.signing}" --mode header --p ## windows-compatible version without git recipe.hooks.core.prebuild.1.pattern.windows=cmd.exe /c mkdir {build.path}\core & (echo #define ARDUINO_ESP8266_GIT_VER 0x00000000 & echo #define ARDUINO_ESP8266_GIT_DESC win-{version} ) > {build.path}\core\core_version.h -recipe.hooks.core.prebuild.2.pattern.windows= +recipe.hooks.core.prebuild.2.pattern.windows=cmd.exe /c if exist {build.source.path}\public.key echo #error Cannot automatically build signed binaries on Windows > {build.path}\core\Updater_Signing.h +recipe.hooks.core.prebuild.3.pattern.windows= ## Build the app.ld linker file recipe.hooks.linking.prelink.1.pattern="{compiler.path}{compiler.c.cmd}" -CC -E -P {build.vtable_flags} "{runtime.platform.path}/tools/sdk/ld/eagle.app.v6.common.ld.h" -o "{build.path}/local.eagle.app.v6.common.ld" @@ -108,6 +109,10 @@ recipe.objcopy.eep.pattern= recipe.objcopy.hex.1.pattern="{runtime.tools.esptool.path}/{compiler.esptool.cmd}" -eo "{runtime.platform.path}/bootloaders/eboot/eboot.elf" -bo "{build.path}/{build.project_name}.bin" -bm {build.flash_mode} -bf {build.flash_freq} -bz {build.flash_size} -bs .text -bp 4096 -ec -eo "{build.path}/{build.project_name}.elf" -bs .irom0.text -bs .text -bs .data -bs .rodata -bc -ec recipe.objcopy.hex.2.pattern="{runtime.tools.signing}" --mode sign --privatekey "{build.source.path}/private.key" --bin "{build.path}/{build.project_name}.bin" --out "{build.path}/{build.project_name}.bin.signed" +# No signing on Windows +recipe.objcopy.hex.1.pattern.windows="{runtime.tools.esptool.path}/{compiler.esptool.cmd}" -eo "{runtime.platform.path}/bootloaders/eboot/eboot.elf" -bo "{build.path}/{build.project_name}.bin" -bm {build.flash_mode} -bf {build.flash_freq} -bz {build.flash_size} -bs .text -bp 4096 -ec -eo "{build.path}/{build.project_name}.elf" -bs .irom0.text -bs .text -bs .data -bs .rodata -bc -ec +recipe.objcopy.hex.2.pattern.windows= + ## Save hex recipe.output.tmp_file={build.project_name}.bin recipe.output.save_file={build.project_name}.{build.variant}.bin