mirror of
https://github.com/esp8266/Arduino.git
synced 2025-06-12 01:53:07 +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
@ -826,6 +826,62 @@ bool X509List::append(const uint8_t *derCert, size_t derLen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// SHA256 hash for updater
|
||||
void HashSHA256::begin() {
|
||||
br_sha256_init( &_cc );
|
||||
memset( _sha256, 0, sizeof(_sha256) );
|
||||
}
|
||||
|
||||
void HashSHA256::add(const void *data, uint32_t len) {
|
||||
br_sha256_update( &_cc, data, len );
|
||||
}
|
||||
|
||||
void HashSHA256::end() {
|
||||
br_sha256_out( &_cc, _sha256 );
|
||||
}
|
||||
|
||||
int HashSHA256::len() {
|
||||
return sizeof(_sha256);
|
||||
}
|
||||
|
||||
const void *HashSHA256::hash() {
|
||||
return (const void*) _sha256;
|
||||
}
|
||||
|
||||
// SHA256 verifier
|
||||
uint32_t SigningVerifier::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 SigningVerifier::verify(UpdaterHashClass *hash, const 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);
|
||||
}
|
||||
};
|
||||
|
||||
#if !CORE_MOCK
|
||||
|
||||
// Second stack thunked helpers
|
||||
|
@ -24,6 +24,8 @@
|
||||
#define _BEARSSLHELPERS_H
|
||||
|
||||
#include <bearssl/bearssl.h>
|
||||
#include <Updater.h>
|
||||
|
||||
|
||||
// Internal opaque structures, not needed by user applications
|
||||
namespace brssl {
|
||||
@ -136,6 +138,31 @@ class Session {
|
||||
br_ssl_session_parameters _session;
|
||||
};
|
||||
|
||||
// Updater SHA256 hash and signature verification
|
||||
class HashSHA256 : 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 const void *hash() override;
|
||||
private:
|
||||
br_sha256_context _cc;
|
||||
unsigned char _sha256[32];
|
||||
};
|
||||
|
||||
class SigningVerifier : public UpdaterVerifyClass {
|
||||
public:
|
||||
virtual uint32_t length() override;
|
||||
virtual bool verify(UpdaterHashClass *hash, const void *signature, uint32_t signatureLen) override;
|
||||
|
||||
public:
|
||||
SigningVerifier(PublicKey *pubKey) { _pubKey = pubKey; }
|
||||
|
||||
private:
|
||||
PublicKey *_pubKey;
|
||||
};
|
||||
|
||||
// Stack thunked versions of calls
|
||||
extern "C" {
|
||||
extern unsigned char *thunk_br_ssl_engine_recvapp_buf( const br_ssl_engine_context *cc, size_t *len);
|
||||
|
@ -0,0 +1,112 @@
|
||||
/*
|
||||
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
|
||||
**ANYONE WITH PHYSICAL ACCESS CAN UPDATE THE 8266 VIA THE SERIAL PORT**.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266WiFiMulti.h>
|
||||
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266httpUpdate.h>
|
||||
|
||||
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).
|
||||
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";
|
||||
#if MANUAL_SIGNING
|
||||
BearSSL::PublicKey *signPubKey = nullptr;
|
||||
BearSSL::HashSHA256 *hash;
|
||||
BearSSL::SigningVerifier *sign;
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
// Serial.setDebugOutput(true);
|
||||
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
|
||||
for (uint8_t t = 4; t > 0; t--) {
|
||||
Serial.printf("[SETUP] WAIT %d...\n", t);
|
||||
Serial.flush();
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFiMulti.addAP("SSID", "PASS");
|
||||
|
||||
#if MANUAL_SIGNING
|
||||
signPubKey = new BearSSL::PublicKey(pubkey);
|
||||
hash = new BearSSL::HashSHA256();
|
||||
sign = new BearSSL::SigningVerifier(signPubKey);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
// wait for WiFi connection
|
||||
if ((WiFiMulti.run() == WL_CONNECTED)) {
|
||||
|
||||
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);
|
||||
|
||||
t_httpUpdate_return ret = ESPhttpUpdate.update(client, "http://192.168.1.8/esp8266.bin");
|
||||
|
||||
switch (ret) {
|
||||
case HTTP_UPDATE_FAILED:
|
||||
Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
|
||||
break;
|
||||
|
||||
case HTTP_UPDATE_NO_UPDATES:
|
||||
Serial.println("HTTP_UPDATE_NO_UPDATES");
|
||||
break;
|
||||
|
||||
case HTTP_UPDATE_OK:
|
||||
Serial.println("HTTP_UPDATE_OK");
|
||||
break;
|
||||
}
|
||||
}
|
||||
delay(10000);
|
||||
}
|
||||
|
@ -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-----
|
@ -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-----
|
Reference in New Issue
Block a user