3.3.7
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
AWS S3 OTA Update
|
||||
Date: 14th June 2017
|
||||
Author: Arvind Ravulavaru <https://github.com/arvindr21>
|
||||
Purpose: Perform an OTA update from a bin located in Amazon S3 (HTTP Only)
|
||||
|
||||
Upload:
|
||||
Step 1 : Download the sample bin file from the examples folder
|
||||
Step 2 : Upload it to your Amazon S3 account, in a bucket of your choice
|
||||
Step 3 : Once uploaded, inside S3, select the bin file >> More (button on top of the file list) >> Make Public
|
||||
Step 4 : You S3 URL => http://bucket-name.s3.ap-south-1.amazonaws.com/sketch-name.ino.bin
|
||||
Step 5 : Build the above URL and fire it either in your browser or curl it `curl -I -v http://bucket-name.ap-south-1.amazonaws.com/sketch-name.ino.bin` to validate the same
|
||||
Step 6: Plug in your SSID, Password, S3 Host and Bin file below
|
||||
|
||||
Build & upload
|
||||
Step 1 : Menu > Sketch > Export Compiled Library. The bin file will be saved in the sketch folder (Menu > Sketch > Show Sketch folder)
|
||||
Step 2 : Upload bin to S3 and continue the above process
|
||||
|
||||
// Check the bottom of this sketch for sample serial monitor log, during and after successful OTA Update
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <Update.h>
|
||||
|
||||
NetworkClient client;
|
||||
|
||||
// Variables to validate
|
||||
// response from S3
|
||||
long contentLength = 0;
|
||||
bool isValidContentType = false;
|
||||
|
||||
// Your SSID and PSWD that the chip needs
|
||||
// to connect to
|
||||
const char *SSID = "YOUR-SSID";
|
||||
const char *PSWD = "YOUR-SSID-PSWD";
|
||||
|
||||
// S3 Bucket Config
|
||||
String host = "bucket-name.s3.ap-south-1.amazonaws.com"; // Host => bucket-name.s3.region.amazonaws.com
|
||||
int port = 80; // Non https. For HTTPS 443. As of today, HTTPS doesn't work.
|
||||
String bin = "/sketch-name.ino.bin"; // bin file name with a slash in front.
|
||||
|
||||
// Utility to extract header value from headers
|
||||
String getHeaderValue(String header, String headerName) {
|
||||
return header.substring(strlen(headerName.c_str()));
|
||||
}
|
||||
|
||||
// OTA Logic
|
||||
void execOTA() {
|
||||
Serial.println("Connecting to: " + String(host));
|
||||
// Connect to S3
|
||||
if (client.connect(host.c_str(), port)) {
|
||||
// Connection Succeed.
|
||||
// Fetching the bin
|
||||
Serial.println("Fetching Bin: " + String(bin));
|
||||
|
||||
// Get the contents of the bin file
|
||||
client.print(String("GET ") + bin + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n\r\n");
|
||||
|
||||
// Check what is being sent
|
||||
// Serial.print(String("GET ") + bin + " HTTP/1.1\r\n" +
|
||||
// "Host: " + host + "\r\n" +
|
||||
// "Cache-Control: no-cache\r\n" +
|
||||
// "Connection: close\r\n\r\n");
|
||||
|
||||
unsigned long timeout = millis();
|
||||
while (client.available() == 0) {
|
||||
if (millis() - timeout > 5000) {
|
||||
Serial.println("Client Timeout !");
|
||||
client.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Once the response is available,
|
||||
// check stuff
|
||||
|
||||
/*
|
||||
Response Structure
|
||||
HTTP/1.1 200 OK
|
||||
x-amz-id-2: NVKxnU1aIQMmpGKhSwpCBh8y2JPbak18QLIfE+OiUDOos+7UftZKjtCFqrwsGOZRN5Zee0jpTd0=
|
||||
x-amz-request-id: 2D56B47560B764EC
|
||||
Date: Wed, 14 Jun 2017 03:33:59 GMT
|
||||
Last-Modified: Fri, 02 Jun 2017 14:50:11 GMT
|
||||
ETag: "d2afebbaaebc38cd669ce36727152af9"
|
||||
Accept-Ranges: bytes
|
||||
Content-Type: application/octet-stream
|
||||
Content-Length: 357280
|
||||
Server: AmazonS3
|
||||
|
||||
{{BIN FILE CONTENTS}}
|
||||
|
||||
*/
|
||||
while (client.available()) {
|
||||
// read line till /n
|
||||
String line = client.readStringUntil('\n');
|
||||
// remove space, to check if the line is end of headers
|
||||
line.trim();
|
||||
|
||||
// if the the line is empty,
|
||||
// this is end of headers
|
||||
// break the while and feed the
|
||||
// remaining `client` to the
|
||||
// Update.writeStream();
|
||||
if (!line.length()) {
|
||||
//headers ended
|
||||
break; // and get the OTA started
|
||||
}
|
||||
|
||||
// Check if the HTTP Response is 200
|
||||
// else break and Exit Update
|
||||
if (line.startsWith("HTTP/1.1")) {
|
||||
if (line.indexOf("200") < 0) {
|
||||
Serial.println("Got a non 200 status code from server. Exiting OTA Update.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// extract headers here
|
||||
// Start with content length
|
||||
if (line.startsWith("Content-Length: ")) {
|
||||
contentLength = atol((getHeaderValue(line, "Content-Length: ")).c_str());
|
||||
Serial.println("Got " + String(contentLength) + " bytes from server");
|
||||
}
|
||||
|
||||
// Next, the content type
|
||||
if (line.startsWith("Content-Type: ")) {
|
||||
String contentType = getHeaderValue(line, "Content-Type: ");
|
||||
Serial.println("Got " + contentType + " payload.");
|
||||
if (contentType == "application/octet-stream") {
|
||||
isValidContentType = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Connect to S3 failed
|
||||
// May be try?
|
||||
// Probably a choppy network?
|
||||
Serial.println("Connection to " + String(host) + " failed. Please check your setup");
|
||||
// retry??
|
||||
// execOTA();
|
||||
}
|
||||
|
||||
// Check what is the contentLength and if content type is `application/octet-stream`
|
||||
Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType));
|
||||
|
||||
// check contentLength and content type
|
||||
if (contentLength && isValidContentType) {
|
||||
// Check if there is enough to OTA Update
|
||||
bool canBegin = Update.begin(contentLength);
|
||||
|
||||
// If yes, begin
|
||||
if (canBegin) {
|
||||
Serial.println("Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!");
|
||||
// No activity would appear on the Serial monitor
|
||||
// So be patient. This may take 2 - 5mins to complete
|
||||
size_t written = Update.writeStream(client);
|
||||
|
||||
if (written == contentLength) {
|
||||
Serial.println("Written : " + String(written) + " successfully");
|
||||
} else {
|
||||
Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?");
|
||||
// retry??
|
||||
// execOTA();
|
||||
}
|
||||
|
||||
if (Update.end()) {
|
||||
Serial.println("OTA done!");
|
||||
if (Update.isFinished()) {
|
||||
Serial.println("Update successfully completed. Rebooting.");
|
||||
ESP.restart();
|
||||
} else {
|
||||
Serial.println("Update not finished? Something went wrong!");
|
||||
}
|
||||
} else {
|
||||
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
|
||||
}
|
||||
} else {
|
||||
// not enough space to begin OTA
|
||||
// Understand the partitions and
|
||||
// space availability
|
||||
Serial.println("Not enough space to begin OTA");
|
||||
client.clear();
|
||||
}
|
||||
} else {
|
||||
Serial.println("There was no content in the response");
|
||||
client.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
//Begin Serial
|
||||
Serial.begin(115200);
|
||||
delay(10);
|
||||
|
||||
Serial.println("Connecting to " + String(SSID));
|
||||
|
||||
// Connect to provided SSID and PSWD
|
||||
WiFi.begin(SSID, PSWD);
|
||||
|
||||
// Wait for connection to establish
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
Serial.print("."); // Keep the serial monitor lit!
|
||||
delay(500);
|
||||
}
|
||||
|
||||
// Connection Succeed
|
||||
Serial.println("");
|
||||
Serial.println("Connected to " + String(SSID));
|
||||
|
||||
// Execute OTA Update
|
||||
execOTA();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// chill
|
||||
}
|
||||
|
||||
/*
|
||||
* Serial Monitor log for this sketch
|
||||
*
|
||||
* If the OTA succeeded, it would load the preference sketch, with a small modification. i.e.
|
||||
* Print `OTA Update succeeded!! This is an example sketch : Preferences > StartCounter`
|
||||
* And then keeps on restarting every 10 seconds, updating the preferences
|
||||
*
|
||||
*
|
||||
rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||
configsip: 0, SPIWP:0x00
|
||||
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||
mode:DIO, clock div:1
|
||||
load:0x3fff0008,len:8
|
||||
load:0x3fff0010,len:160
|
||||
load:0x40078000,len:10632
|
||||
load:0x40080000,len:252
|
||||
entry 0x40080034
|
||||
Connecting to SSID
|
||||
......
|
||||
Connected to SSID
|
||||
Connecting to: bucket-name.s3.ap-south-1.amazonaws.com
|
||||
Fetching Bin: /StartCounter.ino.bin
|
||||
Got application/octet-stream payload.
|
||||
Got 357280 bytes from server
|
||||
contentLength : 357280, isValidContentType : 1
|
||||
Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!
|
||||
Written : 357280 successfully
|
||||
OTA done!
|
||||
Update successfully completed. Rebooting.
|
||||
ets Jun 8 2016 00:22:57
|
||||
|
||||
rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||
configsip: 0, SPIWP:0x00
|
||||
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||
mode:DIO, clock div:1
|
||||
load:0x3fff0008,len:8
|
||||
load:0x3fff0010,len:160
|
||||
load:0x40078000,len:10632
|
||||
load:0x40080000,len:252
|
||||
entry 0x40080034
|
||||
|
||||
OTA Update succeeded!! This is an example sketch : Preferences > StartCounter
|
||||
Current counter value: 1
|
||||
Restarting in 10 seconds...
|
||||
E (102534) wifi: esp_wifi_stop 802 wifi is not init
|
||||
ets Jun 8 2016 00:22:57
|
||||
|
||||
rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||
configsip: 0, SPIWP:0x00
|
||||
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||
mode:DIO, clock div:1
|
||||
load:0x3fff0008,len:8
|
||||
load:0x3fff0010,len:160
|
||||
load:0x40078000,len:10632
|
||||
load:0x40080000,len:252
|
||||
entry 0x40080034
|
||||
|
||||
OTA Update succeeded!! This is an example sketch : Preferences > StartCounter
|
||||
Current counter value: 2
|
||||
Restarting in 10 seconds...
|
||||
|
||||
....
|
||||
*
|
||||
*/
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,85 @@
|
||||
// This sketch provide the functionality of OTA Firmware Upgrade
|
||||
#include "WiFi.h"
|
||||
#include "HttpsOTAUpdate.h"
|
||||
// This sketch shows how to implement HTTPS firmware update Over The Air.
|
||||
// Please provide your WiFi credentials, https URL to the firmware image and the server certificate.
|
||||
|
||||
static const char *ssid = "your-ssid"; // your network SSID (name of wifi network)
|
||||
static const char *password = "your-password"; // your network password
|
||||
|
||||
static const char *url = "https://example.com/firmware.bin"; //state url of your firmware image
|
||||
|
||||
static const char *server_certificate = "-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\n"
|
||||
"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n"
|
||||
"DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\n"
|
||||
"SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\n"
|
||||
"GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\n"
|
||||
"AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\n"
|
||||
"q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\n"
|
||||
"SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\n"
|
||||
"Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\n"
|
||||
"a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\n"
|
||||
"/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\n"
|
||||
"AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\n"
|
||||
"CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\n"
|
||||
"bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\n"
|
||||
"c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\n"
|
||||
"VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\n"
|
||||
"ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\n"
|
||||
"MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\n"
|
||||
"Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\n"
|
||||
"AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\n"
|
||||
"uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\n"
|
||||
"wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\n"
|
||||
"X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\n"
|
||||
"PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\n"
|
||||
"KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\n"
|
||||
"-----END CERTIFICATE-----";
|
||||
|
||||
static HttpsOTAStatus_t otastatus;
|
||||
|
||||
void HttpEvent(HttpEvent_t *event) {
|
||||
switch (event->event_id) {
|
||||
case HTTP_EVENT_ERROR: Serial.println("Http Event Error"); break;
|
||||
case HTTP_EVENT_ON_CONNECTED: Serial.println("Http Event On Connected"); break;
|
||||
case HTTP_EVENT_HEADER_SENT: Serial.println("Http Event Header Sent"); break;
|
||||
case HTTP_EVENT_ON_HEADER: Serial.printf("Http Event On Header, key=%s, value=%s\n", event->header_key, event->header_value); break;
|
||||
case HTTP_EVENT_ON_DATA: break;
|
||||
case HTTP_EVENT_ON_FINISH: Serial.println("Http Event On Finish"); break;
|
||||
case HTTP_EVENT_DISCONNECTED: Serial.println("Http Event Disconnected"); break;
|
||||
case HTTP_EVENT_REDIRECT: Serial.println("Http Event Redirect"); break;
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
Serial.print("Attempting to connect to SSID: ");
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
// attempt to connect to Wifi network:
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
Serial.print(".");
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
|
||||
HttpsOTA.onHttpEvent(HttpEvent);
|
||||
Serial.println("Starting OTA");
|
||||
HttpsOTA.begin(url, server_certificate);
|
||||
|
||||
Serial.println("Please Wait it takes some time ...");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
otastatus = HttpsOTA.status();
|
||||
if (otastatus == HTTPS_OTA_SUCCESS) {
|
||||
Serial.println("Firmware written successfully. To reboot device, call API ESP.restart() or PUSH restart button on device");
|
||||
} else if (otastatus == HTTPS_OTA_FAIL) {
|
||||
Serial.println("Firmware Upgrade Fail");
|
||||
}
|
||||
delay(1000);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
# OTA Firmware Upgrade for Arduino
|
||||
This sketch allows Arduino user to perform Over The Air (OTA) firmware upgrade. It uses HTTPS.
|
||||
|
||||
# API introduced for OTA
|
||||
|
||||
## HttpsOTA.begin(const char * url, const char * server_certificate, bool skip_cert_common_name_check)
|
||||
|
||||
Main API which starts firmware upgrade
|
||||
|
||||
### Parameters
|
||||
* url : URL for the uploaded firmware image
|
||||
* server_certificate : Provide the ota server certificate for authentication via HTTPS
|
||||
* skip_cert_common_name_check : Skip any validation of server certificate CN field
|
||||
|
||||
The default value provided to skip_cert_common_name_check is true
|
||||
|
||||
## HttpsOTA.onHttpEvent(function)
|
||||
|
||||
This API exposes HTTP Events to the user
|
||||
|
||||
### Parameter
|
||||
Function passed has following signature
|
||||
void HttpEvent (HttpEvent_t * event);
|
||||
|
||||
# HttpsOTA.otaStatus()
|
||||
|
||||
It tracks the progress of OTA firmware upgrade.
|
||||
* HTTPS_OTA_IDLE : OTA upgrade have not started yet.
|
||||
* HTTPS_OTA_UPDATNG : OTA upgrade is in progress.
|
||||
* HTTPS_OTA_SUCCESS : OTA upgrade is successful.
|
||||
* HTTPS_OTA_FAIL : OTA upgrade failed.
|
||||
* HTTPS_OTA_ERR : Error occurred while creating xEventGroup().
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
An example of how to use HTTPClient to download an encrypted and plain image files OTA from a web server.
|
||||
This example uses Wifi & HTTPClient to connect to webserver and two functions for obtaining firmware image from webserver.
|
||||
One uses the example 'updater.php' code on server to check and/or send relevant download firmware image file,
|
||||
the other directly downloads the firmware file from web server.
|
||||
|
||||
To use:-
|
||||
Make a folder/directory on your webserver where your firmware images will be uploaded to. ie. /firmware
|
||||
The 'updater.php' file can also be uploaded to the same folder. Edit and change definitions in 'update.php' to suit your needs.
|
||||
In sketch:
|
||||
set HTTPUPDATE_HOST to domain name or IP address if on LAN of your web server
|
||||
set HTTPUPDATE_UPDATER_URI to path and file to call 'updater.php'
|
||||
or set HTTPUPDATE_DIRECT_URI to path and firmware file to download
|
||||
edit other HTTPUPDATE_ as needed
|
||||
|
||||
Encrypted image will help protect your app image file from being copied and used on blank devices, encrypt your image file by using espressif IDF.
|
||||
First install an app on device that has Update setup with the OTA decrypt mode on, same key, address and flash_crypt_conf as used in IDF to encrypt image file or vice versa.
|
||||
|
||||
For easier development use the default U_AES_DECRYPT_AUTO decrypt mode. This mode allows both plain and encrypted app images to be uploaded.
|
||||
|
||||
Note:- App image can also encrypted on device, by using espressif IDF to configure & enabled FLASH encryption, suggest the use of a different 'OTA_KEY' key for update from the eFuses 'flash_encryption' key used by device.
|
||||
|
||||
ie. "Update.setupCrypt(OTA_KEY, OTA_ADDRESS, OTA_CFG);"
|
||||
|
||||
defaults:- {if not set ie. "Update.setupCrypt();" }
|
||||
OTA_KEY = 0 ( 0 = no key, disables decryption )
|
||||
OTA_ADDRESS = 0 ( suggest dont set address to app0=0x10000 usually or app1=varies )
|
||||
OTA_CFG = 0xf
|
||||
OTA_MODE = U_AES_DECRYPT_AUTO
|
||||
|
||||
OTA_MODE options:-
|
||||
U_AES_DECRYPT_NONE decryption disabled, loads OTA image files as sent(plain)
|
||||
U_AES_DECRYPT_AUTO auto loads both plain & encrypted OTA FLASH image files, and plain OTA SPIFFS image files
|
||||
U_AES_DECRYPT_ON decrypts OTA image files
|
||||
|
||||
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/
|
||||
|
||||
Example:
|
||||
espsecure.py encrypt_flash_data -k ota_key.bin --flash_crypt_conf 0xf -a 0x4320 -o output_filename.bin source_filename.bin
|
||||
|
||||
espsecure.py encrypt_flash_data = runs the idf encryption function to make a encrypted output file from a source file
|
||||
-k text = path/filename to the AES 256bit(32byte) encryption key file
|
||||
--flash_crypt_conf 0xn = 0x0 to 0xf, the more bits set the higher the security of encryption(address salting, 0x0 would use ota_key with no address salting)
|
||||
-a 0xnnnnnn00 = 0x00 to 0x00fffff0 address offset(must be a multiple of 16, but better to use multiple of 32), used to offset the salting (has no effect when = --flash_crypt_conf 0x0)
|
||||
-o text = path/filename to save encrypted output file to
|
||||
text = path/filename to open source file from
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <NetworkClient.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <Update.h>
|
||||
|
||||
//==========================================================================
|
||||
//==========================================================================
|
||||
const char *WIFI_SSID = "wifi-ssid";
|
||||
const char *WIFI_PASSWORD = "wifi-password";
|
||||
|
||||
const uint8_t OTA_KEY[32] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20,
|
||||
0x61, 0x20, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6b, 0x65, 0x79};
|
||||
|
||||
/*
|
||||
const uint8_t OTA_KEY[32] = {'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', ' ', 't', 'h', 'i', 's', ' ',
|
||||
'a', ' ', 's', 'i', 'm', 'p', 'l', 'e',
|
||||
't', 'e', 's', 't', ' ', 'k', 'e', 'y' };
|
||||
*/
|
||||
|
||||
//const uint8_t OTA_KEY[33] = "0123456789 this a simpletest key";
|
||||
|
||||
const uint32_t OTA_ADDRESS = 0x4320;
|
||||
const uint32_t OTA_CFG = 0x0f;
|
||||
const uint32_t OTA_MODE = U_AES_DECRYPT_AUTO;
|
||||
|
||||
const char *HTTPUPDATE_USERAGRENT = "ESP32-Updater";
|
||||
//const char* HTTPUPDATE_HOST = "www.yourdomain.com";
|
||||
const char *HTTPUPDATE_HOST = "192.168.1.2";
|
||||
const uint16_t HTTPUPDATE_PORT = 80;
|
||||
const char *HTTPUPDATE_UPDATER_URI = "/firmware/updater.php"; //uri to 'updater.php'
|
||||
const char *HTTPUPDATE_DIRECT_URI = "/firmware/HTTP_Client_AES_OTA_Update-v1.1.xbin"; //uri to image file
|
||||
|
||||
const char *HTTPUPDATE_USER = NULL; //use NULL if no authentication needed
|
||||
//const char* HTTPUPDATE_USER = "user";
|
||||
const char *HTTPUPDATE_PASSWORD = "password";
|
||||
|
||||
const char *HTTPUPDATE_BRAND = "21"; /* Brand ID */
|
||||
const char *HTTPUPDATE_MODEL = "HTTP_Client_AES_OTA_Update"; /* Project name */
|
||||
const char *HTTPUPDATE_FIRMWARE = "0.9"; /* Firmware version */
|
||||
|
||||
//==========================================================================
|
||||
//==========================================================================
|
||||
String urlEncode(const String &url, const char *safeChars = "-_.~") {
|
||||
String encoded = "";
|
||||
char temp[4];
|
||||
|
||||
for (int i = 0; i < url.length(); i++) {
|
||||
temp[0] = url.charAt(i);
|
||||
if (temp[0] == 32) { //space
|
||||
encoded.concat('+');
|
||||
} else if ((temp[0] >= 48 && temp[0] <= 57) /*0-9*/
|
||||
|| (temp[0] >= 65 && temp[0] <= 90) /*A-Z*/
|
||||
|| (temp[0] >= 97 && temp[0] <= 122) /*a-z*/
|
||||
|| (strchr(safeChars, temp[0]) != NULL) /* "=&-_.~" */
|
||||
) {
|
||||
encoded.concat(temp[0]);
|
||||
} else { //character needs encoding
|
||||
snprintf(temp, 4, "%%%02X", temp[0]);
|
||||
encoded.concat(temp);
|
||||
}
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
bool addQuery(String *query, const String name, const String value) {
|
||||
if (name.length() && value.length()) {
|
||||
if (query->length() < 3) {
|
||||
*query = "?";
|
||||
} else {
|
||||
query->concat('&');
|
||||
}
|
||||
query->concat(urlEncode(name));
|
||||
query->concat('=');
|
||||
query->concat(urlEncode(value));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//==========================================================================
|
||||
void printProgress(size_t progress, const size_t &size) {
|
||||
static int last_progress = -1;
|
||||
if (size > 0) {
|
||||
progress = (progress * 100) / size;
|
||||
progress = (progress > 100 ? 100 : progress); //0-100
|
||||
if (progress != last_progress) {
|
||||
Serial.printf("Progress: %zu%%\n", progress);
|
||||
last_progress = progress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
bool http_downloadUpdate(HTTPClient &http, uint32_t size = 0) {
|
||||
size = (size == 0 ? http.getSize() : size);
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
NetworkClient *client = http.getStreamPtr();
|
||||
|
||||
if (!Update.begin(size, U_FLASH)) {
|
||||
Serial.printf("Update.begin failed! (%s)\n", Update.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Update.setupCrypt(OTA_KEY, OTA_ADDRESS, OTA_CFG, OTA_MODE)) {
|
||||
Serial.println("Update.setupCrypt failed!");
|
||||
}
|
||||
|
||||
if (Update.writeStream(*client) != size) {
|
||||
Serial.printf("Update.writeStream failed! (%s)\n", Update.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Update.end()) {
|
||||
Serial.printf("Update.end failed! (%s)\n", Update.errorString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
int http_sendRequest(HTTPClient &http) {
|
||||
|
||||
//set request Headers to be sent to server
|
||||
http.useHTTP10(true); // use HTTP/1.0 for update since the update handler not support any transfer Encoding
|
||||
http.setTimeout(8000);
|
||||
http.addHeader("Cache-Control", "no-cache");
|
||||
|
||||
//set own name for HTTPclient user-agent
|
||||
http.setUserAgent(HTTPUPDATE_USERAGRENT);
|
||||
|
||||
int code = http.GET(); //send the GET request to HTTP server
|
||||
int len = http.getSize();
|
||||
|
||||
if (code == HTTP_CODE_OK) {
|
||||
return (len > 0 ? len : 0); //return 0 or length of image to download
|
||||
} else if (code < 0) {
|
||||
Serial.printf("Error: %s\n", http.errorToString(code).c_str());
|
||||
return code; //error code should be minus between -1 to -11
|
||||
} else {
|
||||
Serial.printf("Error: HTTP Server response code %i\n", code);
|
||||
return -code; //return code should be minus between -100 to -511
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
/* http_updater sends a GET request to 'update.php' on web server */
|
||||
bool http_updater(const String &host, const uint16_t &port, String uri, const bool &download, const char *user = NULL, const char *password = NULL) {
|
||||
//add GET query params to be sent to server (are used by server 'updater.php' code to determine what action to take)
|
||||
String query = "";
|
||||
addQuery(&query, "cmd", (download ? "download" : "check")); //action command
|
||||
|
||||
//setup HTTPclient to be ready to connect & send a request to HTTP server
|
||||
HTTPClient http;
|
||||
NetworkClient client;
|
||||
uri.concat(query); //GET query added to end of uri path
|
||||
if (!http.begin(client, host, port, uri)) {
|
||||
return false; //httpclient setup error
|
||||
}
|
||||
Serial.printf("Sending HTTP request 'http://%s:%i%s'\n", host.c_str(), port, uri.c_str());
|
||||
|
||||
//set basic authorization, if needed for webpage access
|
||||
if (user != NULL && password != NULL) {
|
||||
http.setAuthorization(user, password); //set basic Authorization to server, if needed be gain access
|
||||
}
|
||||
|
||||
//add unique Headers to be sent to server used by server 'update.php' code to determine there a suitable firmware update image available
|
||||
http.addHeader("Brand-Code", HTTPUPDATE_BRAND);
|
||||
http.addHeader("Model", HTTPUPDATE_MODEL);
|
||||
http.addHeader("Firmware", HTTPUPDATE_FIRMWARE);
|
||||
|
||||
//set headers to look for to get returned values in servers http response to our http request
|
||||
const char *headerkeys[] = {"update", "version"}; //server returns update 0=no update found, 1=update found, version=version of update found
|
||||
size_t headerkeyssize = sizeof(headerkeys) / sizeof(char *);
|
||||
http.collectHeaders(headerkeys, headerkeyssize);
|
||||
|
||||
//connect & send HTTP request to server
|
||||
int size = http_sendRequest(http);
|
||||
|
||||
//is there an image to download
|
||||
if (size > 0 || (!download && size == 0)) {
|
||||
if (!http.header("update") || http.header("update").toInt() == 0) {
|
||||
Serial.println("No Firmware available");
|
||||
} else if (!http.header("version") || http.header("version").toFloat() <= String(HTTPUPDATE_FIRMWARE).toFloat()) {
|
||||
Serial.println("Firmware is upto Date");
|
||||
} else {
|
||||
//image avaliabe to download & update
|
||||
if (!download) {
|
||||
Serial.printf("Found V%s Firmware\n", http.header("version").c_str());
|
||||
} else {
|
||||
Serial.printf("Downloading & Installing V%s Firmware\n", http.header("version").c_str());
|
||||
}
|
||||
if (!download || http_downloadUpdate(http)) {
|
||||
http.end(); //end connection
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
http.end(); //end connection
|
||||
return false;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
/* this downloads Firmware image file directly from web server */
|
||||
bool http_direct(const String &host, const uint16_t &port, const String &uri, const char *user = NULL, const char *password = NULL) {
|
||||
//setup HTTPclient to be ready to connect & send a request to HTTP server
|
||||
HTTPClient http;
|
||||
NetworkClient client;
|
||||
if (!http.begin(client, host, port, uri)) {
|
||||
return false; //httpclient setup error
|
||||
}
|
||||
Serial.printf("Sending HTTP request 'http://%s:%i%s'\n", host.c_str(), port, uri.c_str());
|
||||
|
||||
//set basic authorization, if needed for webpage access
|
||||
if (user != NULL && password != NULL) {
|
||||
http.setAuthorization(user, password); //set basic Authorization to server, if needed be gain access
|
||||
}
|
||||
|
||||
//connect & send HTTP request to server
|
||||
int size = http_sendRequest(http);
|
||||
|
||||
//is there an image to download
|
||||
if (size > 0) {
|
||||
if (http_downloadUpdate(http)) {
|
||||
http.end();
|
||||
return true; //end connection
|
||||
}
|
||||
} else {
|
||||
Serial.println("Image File not found");
|
||||
}
|
||||
|
||||
http.end(); //end connection
|
||||
return false;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//==========================================================================
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println();
|
||||
Serial.printf("Booting %s V%s\n", HTTPUPDATE_MODEL, HTTPUPDATE_FIRMWARE);
|
||||
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
Serial.println("WiFi failed, retrying.");
|
||||
}
|
||||
int i = 0;
|
||||
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
Serial.print(".");
|
||||
if ((++i % 100) == 0) {
|
||||
Serial.println();
|
||||
}
|
||||
delay(100);
|
||||
}
|
||||
Serial.printf("Connected to Wifi\nLocal IP: %s\n", WiFi.localIP().toString().c_str());
|
||||
|
||||
Update.onProgress(printProgress);
|
||||
|
||||
Serial.println("Checking with Server, if New Firmware available");
|
||||
if (http_updater(HTTPUPDATE_HOST, HTTPUPDATE_PORT, HTTPUPDATE_UPDATER_URI, 0, HTTPUPDATE_USER, HTTPUPDATE_PASSWORD)) { //check for new firmware
|
||||
if (http_updater(HTTPUPDATE_HOST, HTTPUPDATE_PORT, HTTPUPDATE_UPDATER_URI, 1, HTTPUPDATE_USER, HTTPUPDATE_PASSWORD)) { //update to new firmware
|
||||
Serial.println("Firmware Update Successful, rebooting");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println("Checking Server for Firmware Image File to Download & Install");
|
||||
if (http_direct(HTTPUPDATE_HOST, HTTPUPDATE_PORT, HTTPUPDATE_DIRECT_URI, HTTPUPDATE_USER, HTTPUPDATE_PASSWORD)) {
|
||||
Serial.println("Firmware Update Successful, rebooting");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/* Updater Server-side Example */
|
||||
$brand_codes = array("20", "21");
|
||||
$commands = array("check", "download");
|
||||
|
||||
function verify($valid){
|
||||
if(!$valid){
|
||||
http_response_code(404);
|
||||
echo "Sorry, page not found";
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
$headers = array();
|
||||
foreach (getallheaders() as $name => $value) {
|
||||
$headers += [$name => $value];
|
||||
}
|
||||
verify( in_array($headers['Brand-Code'], $brand_codes) );
|
||||
|
||||
$GetArgs = filter_input_array(INPUT_GET);
|
||||
verify( in_array($GetArgs['cmd'], $commands) );
|
||||
|
||||
if($GetArgs['cmd'] == "check" || $GetArgs['cmd'] == "download"){
|
||||
/*********************************************************************************/
|
||||
/* $firmware version & filename definitions for different Brands, Models & Firmware versions */
|
||||
if($headers['Brand-Code'] == "21"){
|
||||
if($headers['Model'] == "HTTP_Client_AES_OTA_Update"){
|
||||
|
||||
if($headers['Firmware'] < "0.9"){//ie. update to latest of this major version
|
||||
$firmware = array('version'=>"0.9", 'filename'=>"HTTP_Client_AES_OTA_Update-v0.9.xbin");
|
||||
}
|
||||
elseif($headers['Firmware'] == "0.9"){//ie. update between major versions
|
||||
$firmware = array('version'=>"1.0", 'filename'=>"HTTP_Client_AES_OTA_Update-v1.0.xbin");
|
||||
}
|
||||
elseif($headers['Firmware'] <= "1.4"){//ie. update to latest version
|
||||
$firmware = array('version'=>"1.4", 'filename'=>"HTTP_Client_AES_OTA_Update-v1.4.xbin");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
/* end of $firmware definitions for firmware update images on server */
|
||||
/*********************************************************************************/
|
||||
|
||||
if( !$firmware['filename'] || !file_exists($firmware['filename']) ){
|
||||
header('update: 0' );//no update available
|
||||
exit;
|
||||
}else{
|
||||
header('update: 1' );//update available
|
||||
header('version: ' . $firmware['version'] );
|
||||
if($GetArgs['cmd'] == "download"){
|
||||
//Get file type and set it as Content Type
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
header('Content-Type: ' . finfo_file($finfo, $firmware['filename']));//application/octet-stream for binary file
|
||||
finfo_close($finfo);
|
||||
//Define file size
|
||||
header('Content-Length: ' . filesize($firmware['filename']));
|
||||
readfile($firmware['filename']); //send file
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
verify(false);
|
||||
?>
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
An example of how to use Update to upload encrypted and plain image files OTA. This example uses a simple webserver & Wifi connection via AP or STA with mDNS and DNS for simple host URI.
|
||||
|
||||
Encrypted image will help protect your app image file from being copied and used on blank devices, encrypt your image file by using espressif IDF.
|
||||
First install an app on device that has Update setup with the OTA decrypt mode on, same key, address and flash_crypt_conf as used in IDF to encrypt image file or vice versa.
|
||||
|
||||
For easier development use the default U_AES_DECRYPT_AUTO decrypt mode. This mode allows both plain and encrypted app images to be uploaded.
|
||||
|
||||
Note:- App image can also encrypted on device, by using espressif IDF to configure & enabled FLASH encryption, suggest the use of a different 'OTA_KEY' key for update from the eFuses 'flash_encryption' key used by device.
|
||||
|
||||
ie. "Update.setupCrypt(OTA_KEY, OTA_ADDRESS, OTA_CFG);"
|
||||
|
||||
defaults:- {if not set ie. "Update.setupCrypt();" }
|
||||
OTA_KEY = 0 ( 0 = no key, disables decryption )
|
||||
OTA_ADDRESS = 0 ( suggest dont set address to app0=0x10000 usually or app1=varies )
|
||||
OTA_CFG = 0xf
|
||||
OTA_MODE = U_AES_DECRYPT_AUTO
|
||||
|
||||
OTA_MODE options:-
|
||||
U_AES_DECRYPT_NONE decryption disabled, loads OTA image files as sent(plain)
|
||||
U_AES_DECRYPT_AUTO auto loads both plain & encrypted OTA FLASH image files, and plain OTA File System image files
|
||||
U_AES_DECRYPT_ON decrypts OTA image files
|
||||
|
||||
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/
|
||||
|
||||
Example:
|
||||
espsecure.py encrypt_flash_data -k ota_key.bin --flash_crypt_conf 0xf -a 0x4320 -o output_filename.bin source_filename.bin
|
||||
|
||||
espsecure.py encrypt_flash_data = runs the idf encryption function to make a encrypted output file from a source file
|
||||
-k text = path/filename to the AES 256bit(32byte) encryption key file
|
||||
--flash_crypt_conf 0xn = 0x0 to 0xf, the more bits set the higher the security of encryption(address salting, 0x0 would use ota_key with no address salting)
|
||||
-a 0xnnnnnn00 = 0x00 to 0x00fffff0 address offset(must be a multiple of 16, but better to use multiple of 32), used to offset the salting (has no effect when = --flash_crypt_conf 0x0)
|
||||
-o text = path/filename to save encrypted output file to
|
||||
text = path/filename to open source file from
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <NetworkClient.h>
|
||||
#include <Update.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
WebServer httpServer(80);
|
||||
|
||||
//with WIFI_MODE_AP defined the ESP32 is a wifi AP, with it undefined ESP32 tries to connect to wifi STA
|
||||
#define WIFI_MODE_AP
|
||||
|
||||
#ifdef WIFI_MODE_AP
|
||||
#include <DNSServer.h>
|
||||
DNSServer dnsServer;
|
||||
#endif
|
||||
|
||||
const char *host = "esp32-web";
|
||||
const char *ssid = "wifi-ssid";
|
||||
const char *password = "wifi-password";
|
||||
|
||||
const uint8_t OTA_KEY[32] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20,
|
||||
0x61, 0x20, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6b, 0x65, 0x79};
|
||||
|
||||
/*
|
||||
const uint8_t OTA_KEY[32] = {'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', ' ', 't', 'h', 'i', 's', ' ',
|
||||
'a', ' ', 's', 'i', 'm', 'p', 'l', 'e',
|
||||
't', 'e', 's', 't', ' ', 'k', 'e', 'y' };
|
||||
*/
|
||||
|
||||
//const uint8_t OTA_KEY[33] = "0123456789 this a simpletest key";
|
||||
|
||||
const uint32_t OTA_ADDRESS = 0x4320; //OTA_ADDRESS value has no effect when OTA_CFG = 0x00
|
||||
const uint32_t OTA_CFG = 0x0f;
|
||||
const uint32_t OTA_MODE = U_AES_DECRYPT_AUTO;
|
||||
|
||||
/*=================================================================*/
|
||||
const char *update_path = "update";
|
||||
|
||||
static const char UpdatePage_HTML[] PROGMEM =
|
||||
R"(<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<title>Image Upload</title>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'/>
|
||||
</head>
|
||||
<body style='background-color:black;color:#ffff66;text-align: center;font-size:20px;'>
|
||||
<form method='POST' action='' enctype='multipart/form-data'>
|
||||
Firmware:<br><br>
|
||||
<input type='file' accept='.bin,.bin.gz' name='firmware' style='font-size:20px;'><br><br>
|
||||
<input type='submit' value='Update' style='font-size:25px; height:50px; width:100px'>
|
||||
</form>
|
||||
<br><br><br>
|
||||
<form method='POST' action='' enctype='multipart/form-data'>
|
||||
FileSystem:<br><br>
|
||||
<input type='file' accept='.bin,.bin.gz,.image' name='filesystem' style='font-size:20px;'><br><br>
|
||||
<input type='submit' value='Update' style='font-size:25px; height:50px; width:100px'>
|
||||
</form>
|
||||
</body>
|
||||
</html>)";
|
||||
|
||||
/*=================================================================*/
|
||||
|
||||
void printProgress(size_t progress, size_t size) {
|
||||
static int last_progress = -1;
|
||||
if (size > 0) {
|
||||
progress = (progress * 100) / size;
|
||||
progress = (progress > 100 ? 100 : progress); //0-100
|
||||
if (progress != last_progress) {
|
||||
Serial.printf("\nProgress: %zu%%", progress);
|
||||
last_progress = progress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setupHttpUpdateServer() {
|
||||
//redirecting not found web pages back to update page
|
||||
httpServer.onNotFound([&]() { //webpage not found
|
||||
httpServer.sendHeader("Location", String("../") + String(update_path));
|
||||
httpServer.send(302, F("text/html"), "");
|
||||
});
|
||||
|
||||
// handler for the update web page
|
||||
httpServer.on(String("/") + String(update_path), HTTP_GET, [&]() {
|
||||
httpServer.send_P(200, PSTR("text/html"), UpdatePage_HTML);
|
||||
});
|
||||
|
||||
// handler for the update page form POST
|
||||
httpServer.on(
|
||||
String("/") + String(update_path), HTTP_POST,
|
||||
[&]() {
|
||||
// handler when file upload finishes
|
||||
if (Update.hasError()) {
|
||||
httpServer.send(200, F("text/html"), String(F("<META http-equiv=\"refresh\" content=\"5;URL=/\">Update error: ")) + String(Update.errorString()));
|
||||
} else {
|
||||
httpServer.client().setNoDelay(true);
|
||||
httpServer.send(200, PSTR("text/html"), String(F("<META http-equiv=\"refresh\" content=\"15;URL=/\">Update Success! Rebooting...")));
|
||||
delay(100);
|
||||
httpServer.client().stop();
|
||||
ESP.restart();
|
||||
}
|
||||
},
|
||||
[&]() {
|
||||
// handler for the file upload, gets the sketch bytes, and writes
|
||||
// them through the Update object
|
||||
HTTPUpload &upload = httpServer.upload();
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
Serial.printf("Update: %s\n", upload.filename.c_str());
|
||||
if (upload.name == "filesystem") {
|
||||
if (!Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASHFS)) { //start with max available size
|
||||
Update.printError(Serial);
|
||||
}
|
||||
} else {
|
||||
uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
|
||||
if (!Update.begin(maxSketchSpace, U_FLASH)) { //start with max available size
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
} else if (upload.status == UPLOAD_FILE_ABORTED || Update.hasError()) {
|
||||
if (upload.status == UPLOAD_FILE_ABORTED) {
|
||||
if (!Update.end(false)) {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
Serial.println("Update was aborted");
|
||||
}
|
||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
Serial.printf(".");
|
||||
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
} else if (upload.status == UPLOAD_FILE_END) {
|
||||
if (Update.end(true)) { //true to set the size to the current progress
|
||||
Serial.printf("Update Success: %zu\nRebooting...\n", upload.totalSize);
|
||||
} else {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
delay(0);
|
||||
}
|
||||
);
|
||||
|
||||
Update.onProgress(printProgress);
|
||||
}
|
||||
|
||||
/*=================================================================*/
|
||||
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
Serial.println();
|
||||
Serial.println("Booting Sketch...");
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
#ifdef WIFI_MODE_AP
|
||||
WiFi.softAP(ssid, password);
|
||||
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
|
||||
dnsServer.start(53, "*", WiFi.softAPIP()); //if DNS started with "*" for domain name, it will reply with provided IP to all DNS request
|
||||
Serial.printf("Wifi AP started, IP address: %s\n", WiFi.softAPIP().toString().c_str());
|
||||
Serial.printf("You can connect to ESP32 AP use:-\n ssid: %s\npassword: %s\n\n", ssid, password);
|
||||
#else
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
Serial.println("WiFi failed, retrying.");
|
||||
}
|
||||
int i = 0;
|
||||
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
Serial.print(".");
|
||||
if ((++i % 100) == 0) {
|
||||
Serial.println();
|
||||
}
|
||||
delay(100);
|
||||
}
|
||||
Serial.printf("Connected to Wifi\nLocal IP: %s\n", WiFi.localIP().toString().c_str());
|
||||
#endif
|
||||
|
||||
if (MDNS.begin(host)) {
|
||||
Serial.println("mDNS responder started");
|
||||
}
|
||||
|
||||
setupHttpUpdateServer();
|
||||
|
||||
if (Update.setupCrypt(OTA_KEY, OTA_ADDRESS, OTA_CFG, OTA_MODE)) {
|
||||
Serial.println("Upload Decryption Ready");
|
||||
}
|
||||
|
||||
httpServer.begin();
|
||||
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
#ifdef WIFI_MODE_AP
|
||||
Serial.printf("HTTPUpdateServer ready with Captive DNS!\nOpen http://anyname.xyz/%s in your browser\n", update_path);
|
||||
#else
|
||||
Serial.printf("HTTPUpdateServer ready!\nOpen http://%s.local/%s in your browser\n", host, update_path);
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
httpServer.handleClient();
|
||||
#ifdef WIFI_MODE_AP
|
||||
dnsServer.processNextRequest(); //DNS captive portal for easy access to this device webserver
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,148 @@
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <Update.h>
|
||||
#include <Ticker.h>
|
||||
#include "html.h"
|
||||
|
||||
#define SSID_FORMAT "ESP32-%06lX" // 12 chars total
|
||||
//#define PASSWORD "test123456" // generate if remarked
|
||||
|
||||
// Set the username and password for firmware upload
|
||||
const char *authUser = "........";
|
||||
const char *authPass = "........";
|
||||
|
||||
WebServer server(80);
|
||||
Ticker tkSecond;
|
||||
uint8_t otaDone = 0;
|
||||
|
||||
const char *csrfHeaders[2] = {"Origin", "Host"};
|
||||
static bool authenticated = false;
|
||||
|
||||
const char *alphanum = "0123456789!@#$%^&*abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
String generatePass(uint8_t str_len) {
|
||||
String buff;
|
||||
for (int i = 0; i < str_len; i++) {
|
||||
buff += alphanum[random(strlen(alphanum) - 1)];
|
||||
}
|
||||
return buff;
|
||||
}
|
||||
|
||||
void apMode() {
|
||||
char ssid[13];
|
||||
char passwd[11];
|
||||
long unsigned int espmac = ESP.getEfuseMac() >> 24;
|
||||
snprintf(ssid, 13, SSID_FORMAT, espmac);
|
||||
#ifdef PASSWORD
|
||||
snprintf(passwd, 11, PASSWORD);
|
||||
#else
|
||||
snprintf(passwd, 11, generatePass(10).c_str());
|
||||
#endif
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP(ssid, passwd); // Set up the SoftAP
|
||||
MDNS.begin("esp32");
|
||||
Serial.printf("AP: %s, PASS: %s\n", ssid, passwd);
|
||||
}
|
||||
|
||||
void handleUpdateEnd() {
|
||||
if (!authenticated) {
|
||||
return server.requestAuthentication();
|
||||
}
|
||||
server.sendHeader("Connection", "close");
|
||||
if (Update.hasError()) {
|
||||
server.send(502, "text/plain", Update.errorString());
|
||||
} else {
|
||||
server.sendHeader("Refresh", "10");
|
||||
server.sendHeader("Location", "/");
|
||||
server.send(307);
|
||||
delay(500);
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
|
||||
void handleUpdate() {
|
||||
size_t fsize = UPDATE_SIZE_UNKNOWN;
|
||||
if (server.hasArg("size")) {
|
||||
fsize = server.arg("size").toInt();
|
||||
}
|
||||
HTTPUpload &upload = server.upload();
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
authenticated = server.authenticate(authUser, authPass);
|
||||
if (!authenticated) {
|
||||
Serial.println("Authentication fail!");
|
||||
otaDone = 0;
|
||||
return;
|
||||
}
|
||||
String origin = server.header(String(csrfHeaders[0]));
|
||||
String host = server.header(String(csrfHeaders[1]));
|
||||
String expectedOrigin = String("http://") + host;
|
||||
if (origin != expectedOrigin) {
|
||||
Serial.printf("Wrong origin received! Expected: %s, Received: %s\n", expectedOrigin.c_str(), origin.c_str());
|
||||
authenticated = false;
|
||||
otaDone = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf("Receiving Update: %s, Size: %zu\n", upload.filename.c_str(), fsize);
|
||||
if (!Update.begin(fsize)) {
|
||||
otaDone = 0;
|
||||
Update.printError(Serial);
|
||||
}
|
||||
} else if (authenticated && upload.status == UPLOAD_FILE_WRITE) {
|
||||
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
||||
Update.printError(Serial);
|
||||
} else {
|
||||
otaDone = 100 * Update.progress() / Update.size();
|
||||
}
|
||||
} else if (authenticated && upload.status == UPLOAD_FILE_END) {
|
||||
if (Update.end(true)) {
|
||||
Serial.printf("Update Success: %zu bytes\nRebooting...\n", upload.totalSize);
|
||||
} else {
|
||||
Serial.printf("%s\n", Update.errorString());
|
||||
otaDone = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void webServerInit() {
|
||||
server.collectHeaders(csrfHeaders, 2);
|
||||
server.on(
|
||||
"/update", HTTP_POST,
|
||||
[]() {
|
||||
handleUpdateEnd();
|
||||
},
|
||||
[]() {
|
||||
handleUpdate();
|
||||
}
|
||||
);
|
||||
server.on("/favicon.ico", HTTP_GET, []() {
|
||||
server.sendHeader("Content-Encoding", "gzip");
|
||||
server.send_P(200, "image/x-icon", favicon_ico_gz, favicon_ico_gz_len);
|
||||
});
|
||||
server.onNotFound([]() {
|
||||
if (!server.authenticate(authUser, authPass)) {
|
||||
return server.requestAuthentication();
|
||||
}
|
||||
server.send(200, "text/html", indexHtml);
|
||||
});
|
||||
server.begin();
|
||||
Serial.printf("Web Server ready at http://esp32.local or http://%s\n", WiFi.softAPIP().toString().c_str());
|
||||
}
|
||||
|
||||
void everySecond() {
|
||||
if (otaDone > 1) {
|
||||
Serial.printf("ota: %d%%\n", otaDone);
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
apMode();
|
||||
webServerInit();
|
||||
tkSecond.attach(1, everySecond);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
delay(150);
|
||||
server.handleClient();
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,73 @@
|
||||
// Literal string
|
||||
const char *indexHtml = R"literal(
|
||||
<!DOCTYPE html>
|
||||
<link rel='icon' href='/favicon.ico' sizes='any'>
|
||||
<body style='width:480px'>
|
||||
<h2>ESP Firmware Update</h2>
|
||||
<form method='POST' enctype='multipart/form-data' id='upload-form'>
|
||||
<input type='file' id='file' name='update'>
|
||||
<input type='submit' value='Update'>
|
||||
</form>
|
||||
<br>
|
||||
<div id='prg' style='width:0;color:white;text-align:center'>0%</div>
|
||||
</body>
|
||||
<script>
|
||||
var prg = document.getElementById('prg');
|
||||
var form = document.getElementById('upload-form');
|
||||
form.addEventListener('submit', el=>{
|
||||
prg.style.backgroundColor = 'blue';
|
||||
el.preventDefault();
|
||||
var data = new FormData(form);
|
||||
var req = new XMLHttpRequest();
|
||||
var fsize = document.getElementById('file').files[0].size;
|
||||
req.open('POST', '/update?size=' + fsize);
|
||||
req.upload.addEventListener('progress', p=>{
|
||||
let w = Math.round(p.loaded/p.total*100) + '%';
|
||||
if(p.lengthComputable){
|
||||
prg.innerHTML = w;
|
||||
prg.style.width = w;
|
||||
}
|
||||
if(w == '100%') prg.style.backgroundColor = 'black';
|
||||
});
|
||||
req.send(data);
|
||||
});
|
||||
</script>
|
||||
)literal";
|
||||
|
||||
// Compressed gzip in C include file style
|
||||
// listing was created using `xxd -i favicon.ico.gz`
|
||||
const char favicon_ico_gz[] = {
|
||||
0x1f, 0x8b, 0x08, 0x08, 0x13, 0xb6, 0xa5, 0x62, 0x00, 0x03, 0x66, 0x61, 0x76, 0x69, 0x63, 0x6f, 0x6e, 0x2e, 0x69, 0x63, 0x6f, 0x00, 0xa5, 0x53, 0x69, 0x48,
|
||||
0x54, 0x51, 0x14, 0x7e, 0x41, 0x99, 0x4b, 0xa0, 0x15, 0x24, 0xb4, 0x99, 0x8e, 0x1b, 0x25, 0xa8, 0x54, 0xb4, 0x19, 0x56, 0x62, 0x41, 0xab, 0x14, 0x45, 0x61,
|
||||
0x51, 0x62, 0x86, 0x69, 0x1a, 0xd4, 0x60, 0xda, 0xa2, 0x99, 0x4b, 0x69, 0x1b, 0x42, 0x26, 0x5a, 0x09, 0x59, 0xd8, 0xfe, 0xab, 0x88, 0xa0, 0x82, 0xe8, 0x4f,
|
||||
0x90, 0xa5, 0x14, 0x68, 0x0b, 0xe6, 0x2c, 0x6f, 0x9c, 0xed, 0xcd, 0x9b, 0xcd, 0x19, 0x9d, 0x99, 0x3b, 0xe3, 0xd7, 0x79, 0x6f, 0x34, 0xac, 0xe8, 0x57, 0xf7,
|
||||
0x72, 0x1e, 0x87, 0xb3, 0xbe, 0xf3, 0x9d, 0xef, 0x72, 0xdc, 0x24, 0xba, 0x51, 0x51, 0x1c, 0x7d, 0x17, 0x70, 0x85, 0x93, 0x39, 0x6e, 0x16, 0xc7, 0x71, 0xc9,
|
||||
0x24, 0x64, 0x22, 0x4b, 0xd0, 0x2e, 0x1f, 0xf2, 0xcd, 0x8c, 0x08, 0xca, 0xf8, 0x89, 0x48, 0x54, 0x7b, 0x49, 0xd8, 0xb8, 0x84, 0xc6, 0xab, 0xd9, 0xd4, 0x58,
|
||||
0x15, 0x8b, 0x4e, 0xd1, 0xb0, 0xa4, 0x0c, 0x9e, 0x2d, 0x5a, 0xa3, 0x63, 0x31, 0x4b, 0xb4, 0x6c, 0x9a, 0x22, 0x68, 0x0f, 0x9f, 0x10, 0x3b, 0x21, 0x17, 0x92,
|
||||
0x84, 0xc5, 0xa9, 0x90, 0xbc, 0x8a, 0x47, 0xe1, 0x31, 0x01, 0x95, 0xf5, 0x22, 0x6a, 0x1a, 0xac, 0x28, 0x3f, 0x2b, 0xa2, 0x8a, 0x74, 0xe5, 0x19, 0x0b, 0xb2,
|
||||
0xb7, 0xeb, 0x11, 0x49, 0x71, 0xe1, 0x09, 0xc1, 0xf8, 0x09, 0x75, 0x10, 0xae, 0x50, 0x61, 0xf3, 0x6e, 0x03, 0x2a, 0xeb, 0x44, 0xe4, 0xec, 0x31, 0xa0, 0xf9,
|
||||
0x86, 0x1d, 0xb7, 0x3b, 0x9d, 0x58, 0x9e, 0xa5, 0xc3, 0xb5, 0x1b, 0x0e, 0xd4, 0x5f, 0xb4, 0xe2, 0xa8, 0x52, 0xc0, 0xf1, 0x53, 0x16, 0xcc, 0x49, 0xd3, 0x8c,
|
||||
0xd7, 0x90, 0xf3, 0xa5, 0xbe, 0xdb, 0x72, 0x0d, 0x78, 0xff, 0x71, 0x04, 0x77, 0x1f, 0x3a, 0x91, 0x9e, 0xc9, 0xe3, 0xed, 0xbb, 0x61, 0xb8, 0x87, 0x03, 0x28,
|
||||
0x56, 0x9a, 0xc1, 0x0f, 0x32, 0xf4, 0x7d, 0xf5, 0x42, 0x91, 0xaa, 0xc1, 0xba, 0x6d, 0x7a, 0xc4, 0xaf, 0xe0, 0x11, 0x16, 0x1f, 0xcc, 0x0f, 0xa5, 0xb9, 0x16,
|
||||
0xae, 0xe6, 0x51, 0x7b, 0xc9, 0x8a, 0x17, 0xaf, 0xdd, 0x18, 0xf1, 0x04, 0xb0, 0x65, 0x97, 0x01, 0x1d, 0xf7, 0x9c, 0xf0, 0xfb, 0x03, 0x28, 0x2a, 0x13, 0xd0,
|
||||
0xfb, 0xcd, 0x8b, 0xfe, 0x1f, 0x5e, 0x1c, 0x38, 0x6c, 0xc2, 0xe3, 0xa7, 0x2e, 0x2c, 0xcd, 0x1e, 0x44, 0x98, 0x62, 0x2c, 0x9f, 0x30, 0x39, 0x52, 0x6e, 0x41,
|
||||
0xe6, 0x56, 0x3d, 0x16, 0x65, 0xf0, 0xd8, 0xb9, 0xd7, 0x88, 0x0d, 0x39, 0x7a, 0x0c, 0xa8, 0x7d, 0x30, 0x99, 0x19, 0x0e, 0x95, 0x9a, 0x60, 0xb6, 0x30, 0x74,
|
||||
0xf5, 0x8c, 0xa0, 0xbc, 0x52, 0xc0, 0x28, 0xdd, 0xcb, 0xd7, 0xed, 0x20, 0x2c, 0xe5, 0xfc, 0xd9, 0xa9, 0x1a, 0x56, 0xdd, 0x68, 0x45, 0x5c, 0xba, 0x16, 0xf9,
|
||||
0xa5, 0x66, 0xb4, 0xdd, 0x72, 0xa0, 0x7f, 0xc0, 0x0b, 0x50, 0x5c, 0x6b, 0xbb, 0x1d, 0xf5, 0x97, 0xad, 0xb2, 0x7e, 0xb3, 0xc3, 0x81, 0x96, 0x5b, 0x76, 0x59,
|
||||
0x3f, 0x5e, 0x69, 0xc1, 0xe4, 0x98, 0x60, 0x7e, 0x0a, 0xed, 0xe7, 0xd4, 0x39, 0x11, 0xe7, 0x29, 0x8e, 0xb1, 0x00, 0x46, 0x47, 0x47, 0x21, 0x50, 0xbf, 0x36,
|
||||
0xca, 0x2d, 0x2c, 0x31, 0x41, 0x6f, 0x64, 0xb0, 0x88, 0x0c, 0x05, 0xa4, 0x7f, 0xa1, 0x39, 0x2c, 0x56, 0x3f, 0x76, 0xec, 0x37, 0x22, 0x76, 0x99, 0x56, 0xc2,
|
||||
0x80, 0xa5, 0xad, 0xd5, 0xb1, 0x33, 0x84, 0x79, 0x17, 0x61, 0xe7, 0x72, 0xfb, 0x71, 0xe1, 0x8a, 0x0d, 0x07, 0x8b, 0x4d, 0xa8, 0x6d, 0x14, 0xa1, 0x1b, 0xf4,
|
||||
0xc1, 0x47, 0x35, 0xa5, 0xda, 0x4d, 0x2d, 0x36, 0xb9, 0xf7, 0xfd, 0x27, 0x4e, 0xd4, 0xd2, 0x2e, 0xf6, 0x15, 0x99, 0x11, 0x12, 0xa3, 0x62, 0x8a, 0xe5, 0x3c,
|
||||
0xab, 0x23, 0xff, 0xb3, 0x17, 0x2e, 0xb9, 0xf7, 0xf7, 0x7e, 0x2f, 0x0c, 0x26, 0x26, 0xeb, 0x16, 0x91, 0xea, 0x91, 0xaf, 0x82, 0x76, 0xef, 0x1c, 0xf2, 0x43,
|
||||
0xab, 0xf3, 0xc9, 0xb5, 0xbb, 0xba, 0x3d, 0x58, 0x4f, 0x18, 0x87, 0xc4, 0xaa, 0x59, 0x64, 0x82, 0x9a, 0x9d, 0xac, 0x15, 0x91, 0x9b, 0x6f, 0xc4, 0xab, 0x37,
|
||||
0xc3, 0x50, 0x6b, 0x7c, 0xe8, 0xfe, 0x34, 0x82, 0xf6, 0x3b, 0x0e, 0x14, 0x50, 0xec, 0xd5, 0x6b, 0x36, 0x38, 0x9c, 0x7e, 0xd8, 0x1c, 0x7e, 0x28, 0x4f, 0x0a,
|
||||
0xe8, 0x7c, 0xe4, 0xc4, 0xc7, 0x4f, 0x1e, 0xcc, 0x25, 0x0e, 0x84, 0x25, 0x04, 0x39, 0xb9, 0x25, 0xd7, 0x88, 0xfc, 0x12, 0x33, 0xa2, 0x93, 0xd5, 0xc8, 0x2b,
|
||||
0x32, 0xe1, 0xc4, 0x69, 0x01, 0xcd, 0xad, 0x76, 0xf4, 0xf6, 0x79, 0xe4, 0xff, 0xe0, 0xa9, 0xaf, 0xb2, 0x42, 0x40, 0x53, 0xb3, 0x0d, 0x43, 0x2e, 0xe2, 0x04,
|
||||
0xed, 0x34, 0x74, 0x0c, 0x7f, 0xe2, 0x11, 0x9b, 0x4e, 0x79, 0xe5, 0xd5, 0x22, 0xb2, 0x88, 0x1b, 0xcf, 0x5f, 0xba, 0xe5, 0x39, 0x25, 0x11, 0x09, 0xab, 0x07,
|
||||
0x34, 0xef, 0x41, 0xda, 0xfb, 0x3d, 0xea, 0xeb, 0x72, 0x07, 0x70, 0xf5, 0xba, 0x0d, 0x33, 0x92, 0xd5, 0xbf, 0xf3, 0x8f, 0xb8, 0x34, 0x7f, 0xb1, 0x16, 0x89,
|
||||
0x2b, 0x79, 0x64, 0x6c, 0x1a, 0x44, 0x53, 0x9b, 0x1d, 0x65, 0x55, 0x22, 0x76, 0x12, 0xce, 0x35, 0x0d, 0x22, 0x3e, 0xf4, 0x78, 0xd0, 0xfd, 0xd9, 0x43, 0x5c,
|
||||
0x14, 0x30, 0x23, 0xe9, 0x1f, 0xfc, 0x27, 0x9b, 0x54, 0x87, 0xf8, 0x28, 0xff, 0xdb, 0x94, 0xf9, 0x2a, 0x24, 0x10, 0x4f, 0xf3, 0x68, 0xae, 0x8d, 0xf4, 0x1e,
|
||||
0xe6, 0x11, 0x3f, 0x24, 0x7b, 0x78, 0xe2, 0xaf, 0x5c, 0xfc, 0xf1, 0x06, 0xff, 0x12, 0x69, 0xbf, 0x53, 0x68, 0x47, 0x21, 0x84, 0x91, 0xa4, 0x4f, 0xf4, 0x8d,
|
||||
0xbd, 0x63, 0x2f, 0xf7, 0x9f, 0xe7, 0x27, 0x40, 0xbb, 0x5a, 0x53, 0x7e, 0x04, 0x00, 0x00
|
||||
};
|
||||
const int favicon_ico_gz_len = 821;
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Name: SD_Update.ino
|
||||
Created: 12.09.2017 15:07:17
|
||||
Author: Frederik Merz <frederik.merz@novalight.de>
|
||||
Purpose: Update firmware from SD card
|
||||
|
||||
Steps:
|
||||
1. Flash this image to the ESP32 an run it
|
||||
2. Copy update.bin to a SD-Card, you can basically
|
||||
compile this or any other example
|
||||
then copy and rename the app binary to the sd card root
|
||||
3. Connect SD-Card as shown in SD example,
|
||||
this can also be adapted for SPI
|
||||
3. After successful update and reboot, ESP32 shall start the new app
|
||||
*/
|
||||
|
||||
#include <Update.h>
|
||||
#include <FS.h>
|
||||
#include <SD.h>
|
||||
|
||||
// perform the actual update from a given stream
|
||||
void performUpdate(Stream &updateSource, size_t updateSize) {
|
||||
if (Update.begin(updateSize)) {
|
||||
size_t written = Update.writeStream(updateSource);
|
||||
if (written == updateSize) {
|
||||
Serial.println("Written : " + String(written) + " successfully");
|
||||
} else {
|
||||
Serial.println("Written only : " + String(written) + "/" + String(updateSize) + ". Retry?");
|
||||
}
|
||||
if (Update.end()) {
|
||||
Serial.println("OTA done!");
|
||||
if (Update.isFinished()) {
|
||||
Serial.println("Update successfully completed. Rebooting.");
|
||||
} else {
|
||||
Serial.println("Update not finished? Something went wrong!");
|
||||
}
|
||||
} else {
|
||||
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
|
||||
}
|
||||
|
||||
} else {
|
||||
Serial.println("Not enough space to begin OTA");
|
||||
}
|
||||
}
|
||||
|
||||
// check given FS for valid update.bin and perform update if available
|
||||
void updateFromFS(fs::FS &fs) {
|
||||
File updateBin = fs.open("/update.bin");
|
||||
if (updateBin) {
|
||||
if (updateBin.isDirectory()) {
|
||||
Serial.println("Error, update.bin is not a file");
|
||||
updateBin.close();
|
||||
return;
|
||||
}
|
||||
|
||||
size_t updateSize = updateBin.size();
|
||||
|
||||
if (updateSize > 0) {
|
||||
Serial.println("Try to start update");
|
||||
performUpdate(updateBin, updateSize);
|
||||
} else {
|
||||
Serial.println("Error, file is empty");
|
||||
}
|
||||
|
||||
updateBin.close();
|
||||
|
||||
// when finished remove the binary from sd card to indicate end of the process
|
||||
fs.remove("/update.bin");
|
||||
} else {
|
||||
Serial.println("Could not load update.bin from sd root");
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
uint8_t cardType;
|
||||
Serial.begin(115200);
|
||||
Serial.println("Welcome to the SD-Update example!");
|
||||
|
||||
// You can uncomment this and build again
|
||||
// Serial.println("Update successful");
|
||||
|
||||
//first init and check SD card
|
||||
if (!SD.begin()) {
|
||||
rebootEspWithReason("Card Mount Failed");
|
||||
}
|
||||
|
||||
cardType = SD.cardType();
|
||||
|
||||
if (cardType == CARD_NONE) {
|
||||
rebootEspWithReason("No SD_MMC card attached");
|
||||
} else {
|
||||
updateFromFS(SD);
|
||||
}
|
||||
}
|
||||
|
||||
void rebootEspWithReason(String reason) {
|
||||
Serial.println(reason);
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
//will not be reached
|
||||
void loop() {}
|
||||
@@ -0,0 +1,202 @@
|
||||
# Signed OTA Update Example
|
||||
|
||||
This example demonstrates how to perform secure OTA (Over-The-Air) updates with signature verification on ESP32 devices using Arduino.
|
||||
|
||||
## Overview
|
||||
|
||||
Code signing ensures that only firmware signed with your private key will be accepted by your devices. This protects against unauthorized firmware updates, even if an attacker gains access to your update server.
|
||||
|
||||
## Features
|
||||
|
||||
- **RSA Signature Verification**: Supports RSA-2048, RSA-3072, and RSA-4096
|
||||
- **ECDSA Signature Verification**: Supports ECDSA-P256 and ECDSA-P384
|
||||
- **Multiple Hash Algorithms**: SHA-256, SHA-384, and SHA-512
|
||||
- **Automatic Signature Verification**: Signatures are verified automatically during OTA update
|
||||
- **Secure by Default**: Update fails if signature verification fails
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Python 3** with the `cryptography` package:
|
||||
```bash
|
||||
pip install cryptography
|
||||
```
|
||||
|
||||
2. **ESP32 Arduino Core** with Update library
|
||||
|
||||
## Quick Start Guide
|
||||
|
||||
### Step 1: Generate Key Pair
|
||||
|
||||
Generate an RSA-2048 key pair (recommended):
|
||||
```bash
|
||||
python <ARDUINO_ROOT>/tools/bin_signing.py --generate-key rsa-2048 --out private_key.pem
|
||||
python <ARDUINO_ROOT>/tools/bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
|
||||
```
|
||||
|
||||
Or generate an ECDSA-P256 key pair (smaller, faster):
|
||||
```bash
|
||||
python <ARDUINO_ROOT>/tools/bin_signing.py --generate-key ecdsa-p256 --out private_key.pem
|
||||
python <ARDUINO_ROOT>/tools/bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
|
||||
```
|
||||
|
||||
Where `<ARDUINO_ROOT>` is your ESP32 Arduino installation path (e.g., `~/Arduino/hardware/espressif/esp32/`).
|
||||
|
||||
**IMPORTANT**: Keep `private_key.pem` secure! Anyone with access to it can sign firmware for your devices.
|
||||
|
||||
### Step 2: Update the Example Sketch
|
||||
|
||||
1. Copy the generated `public_key.h` to the example directory
|
||||
2. Open `Signed_OTA_Update.ino`
|
||||
3. Update Wi-Fi credentials:
|
||||
```cpp
|
||||
const char *ssid = "YOUR_SSID";
|
||||
const char *password = "YOUR_PASSWORD";
|
||||
```
|
||||
4. Update firmware URL:
|
||||
```cpp
|
||||
const char *firmwareUrl = "http://your-server.com/firmware_signed.bin";
|
||||
```
|
||||
5. Uncomment the appropriate key type (RSA or ECDSA)
|
||||
6. Uncomment the appropriate hash algorithm (SHA-256, SHA-384, or SHA-512)
|
||||
|
||||
### Step 3: Build and Upload Initial Firmware
|
||||
|
||||
1. Compile and upload the sketch to your ESP32
|
||||
2. Open Serial Monitor to verify it's running
|
||||
|
||||
### Step 4: Build and Sign New Firmware
|
||||
|
||||
1. Make changes to your sketch (e.g., add a version number)
|
||||
2. Build the sketch and export the binary:
|
||||
- Arduino IDE: `Sketch` → `Export Compiled Binary`
|
||||
- Find the application `.bin` file in the `build` folder of your sketch folder. For example `build/espressif.esp32.esp32c6/Signed_OTA_Update.ino.bin`.
|
||||
|
||||
3. Sign the binary:
|
||||
```bash
|
||||
python <ARDUINO_ROOT>/tools/bin_signing.py --bin <APPLICATION_BIN_FILE> --key private_key.pem --out firmware_signed.bin
|
||||
```
|
||||
|
||||
For other hash algorithms (for example SHA-384):
|
||||
```bash
|
||||
python <ARDUINO_ROOT>/tools/bin_signing.py --bin <APPLICATION_BIN_FILE> --key private_key.pem --out firmware_signed.bin --hash sha384
|
||||
```
|
||||
|
||||
### Step 5: Host the Signed Firmware
|
||||
|
||||
Upload `firmware_signed.bin` to your web server and make it accessible at the URL you configured.
|
||||
|
||||
### Step 6: Perform OTA Update
|
||||
|
||||
Reset your ESP32. It will:
|
||||
1. Connect to Wi-Fi
|
||||
2. Download the signed firmware
|
||||
3. Verify the signature
|
||||
4. Apply the update if signature is valid
|
||||
5. Reboot with the new firmware
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Private Key Management
|
||||
|
||||
- **NEVER** commit your private key to version control
|
||||
- Store it securely (encrypted storage, HSM, etc.)
|
||||
- Limit access to authorized personnel only
|
||||
- Consider using separate keys for development and production
|
||||
|
||||
### Recommended Practices
|
||||
|
||||
1. **Use HTTPS**: While signature verification protects firmware integrity, HTTPS protects against MitM attacks
|
||||
2. **Key Rotation**: Periodically rotate keys (requires firmware update to include new public key)
|
||||
|
||||
## Signature Schemes Comparison
|
||||
|
||||
| Scheme | Key Size | Signature Size | Verification Speed | Security |
|
||||
|--------|----------|----------------|-------------------|----------|
|
||||
| RSA-2048 | 2048 bits | 256 bytes | Medium | High |
|
||||
| RSA-3072 | 3072 bits | 384 bytes | Slower | Very High |
|
||||
| RSA-4096 | 4096 bits | 512 bytes | Slowest | Maximum |
|
||||
| ECDSA-P256 | 256 bits | 64 bytes | Fast | High |
|
||||
| ECDSA-P384 | 384 bits | 96 bytes | Fast | Very High |
|
||||
|
||||
**Recommendation**: RSA-2048 or ECDSA-P256 provide good security with reasonable performance.
|
||||
|
||||
## Hash Algorithms Comparison
|
||||
|
||||
| Algorithm | Output Size | Speed | Security |
|
||||
|-----------|-------------|-------|----------|
|
||||
| SHA-256 | 32 bytes | Fast | High |
|
||||
| SHA-384 | 48 bytes | Medium | Very High |
|
||||
| SHA-512 | 64 bytes | Medium | Very High |
|
||||
|
||||
**Recommendation**: SHA-256 is sufficient for most applications.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Signature verification failed"
|
||||
|
||||
- Ensure the firmware was signed with the correct private key
|
||||
- Verify that the public key in the sketch matches the private key used for signing
|
||||
- Check that the signature scheme (RSA/ECDSA) and hash algorithm match between signing and verification
|
||||
- Ensure the signed binary wasn't corrupted during transfer
|
||||
|
||||
### "Failed to install signature verification"
|
||||
|
||||
- Check that `installSignature()` is called before `Update.begin()`
|
||||
- Ensure hash and sign objects are properly initialized
|
||||
|
||||
### "Public key parsing failed"
|
||||
|
||||
- Verify the public key PEM format is correct
|
||||
- Ensure PUBLIC_KEY_LEN matches the actual key length
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Verifying a Signed Binary
|
||||
|
||||
You can verify a signed binary without flashing it:
|
||||
|
||||
```bash
|
||||
python bin_signing.py --verify firmware_signed.bin --pubkey public_key.pem
|
||||
```
|
||||
|
||||
### Using Different Hash Algorithms
|
||||
|
||||
Match the hash algorithm between signing and verification:
|
||||
|
||||
**Signing with SHA-384:**
|
||||
```bash
|
||||
python bin_signing.py --bin firmware.bin --key private_key.pem --out firmware_signed.bin --hash sha384
|
||||
```
|
||||
|
||||
**Sketch configuration:**
|
||||
```cpp
|
||||
#define USE_SHA384
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Classes
|
||||
|
||||
- **UpdaterRSAVerifier**: RSA signature verifier
|
||||
- **UpdaterECDSAVerifier**: ECDSA signature verifier
|
||||
|
||||
### Methods
|
||||
|
||||
```cpp
|
||||
// Install signature verification (call before Update.begin())
|
||||
bool Update.installSignature(UpdaterVerifyClass *sign);
|
||||
```
|
||||
|
||||
### Error Codes
|
||||
|
||||
- `UPDATE_ERROR_SIGN (14)`: Signature verification failed
|
||||
|
||||
## License
|
||||
|
||||
This example is part of the Arduino-ESP32 project and is licensed under the Apache License 2.0.
|
||||
|
||||
## Support
|
||||
|
||||
For issues and questions:
|
||||
- GitHub: https://github.com/espressif/arduino-esp32/issues
|
||||
- Documentation: https://docs.espressif.com/
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
Signed OTA Update Example
|
||||
|
||||
This example demonstrates how to perform a secure OTA update with signature verification.
|
||||
Only firmware signed with the correct private key will be accepted.
|
||||
|
||||
NOTE: This example requires signature verification support to be enabled.
|
||||
This is done automatically via the build_opt.h file in this directory.
|
||||
|
||||
Steps to use this example:
|
||||
1. Generate a key pair (see instructions below)
|
||||
2. Include the public key in this sketch (see public_key.h)
|
||||
3. Build and upload this sketch to your ESP32
|
||||
4. Build your new firmware binary
|
||||
5. Sign the binary with the private key (see instructions below)
|
||||
6. Upload the signed firmware via OTA (HTTP/HTTPS server)
|
||||
|
||||
Generating keys:
|
||||
------------------
|
||||
RSA (recommended for maximum compatibility):
|
||||
python bin_signing.py --generate-key rsa-2048 --out private_key.pem
|
||||
python bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
|
||||
|
||||
ECDSA (smaller keys, faster verification):
|
||||
python bin_signing.py --generate-key ecdsa-p256 --out private_key.pem
|
||||
python bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
|
||||
|
||||
Signing firmware:
|
||||
-----------------
|
||||
python bin_signing.py --bin firmware.bin --key private_key.pem --out firmware_signed.bin
|
||||
|
||||
IMPORTANT: Keep your private_key.pem secure! Anyone with access to it can
|
||||
sign firmware that will be accepted by your devices.
|
||||
|
||||
Created by lucasssvaz
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <Update.h>
|
||||
#include <SHA2Builder.h>
|
||||
|
||||
// WiFi credentials
|
||||
const char *ssid = "YOUR_SSID";
|
||||
const char *password = "YOUR_PASSWORD";
|
||||
|
||||
// URL to the signed firmware binary
|
||||
const char *firmwareUrl = "http://your-server.com/firmware_signed.bin";
|
||||
|
||||
// Public key for signature verification
|
||||
// Generated with: python bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
|
||||
// This will create a public_key.h file that you should include below
|
||||
#include "public_key.h"
|
||||
|
||||
// Uncomment the key type you're using:
|
||||
#define USE_RSA // RSA signature verification
|
||||
//#define USE_ECDSA // ECDSA signature verification
|
||||
|
||||
// Uncomment the hash algorithm you're using (must match the one used for signing):
|
||||
#define USE_SHA256 // SHA-256 (recommended and default)
|
||||
//#define USE_SHA384 // SHA-384
|
||||
//#define USE_SHA512 // SHA-512
|
||||
|
||||
void performOTAUpdate() {
|
||||
HTTPClient http;
|
||||
|
||||
Serial.println("Starting OTA update...");
|
||||
Serial.print("Firmware URL: ");
|
||||
Serial.println(firmwareUrl);
|
||||
|
||||
http.begin(firmwareUrl);
|
||||
int httpCode = http.GET();
|
||||
|
||||
if (httpCode != HTTP_CODE_OK) {
|
||||
Serial.printf("HTTP GET failed, error: %s\n", http.errorToString(httpCode).c_str());
|
||||
http.end();
|
||||
return;
|
||||
}
|
||||
|
||||
int contentLength = http.getSize();
|
||||
Serial.printf("Firmware size: %d bytes\n", contentLength);
|
||||
|
||||
if (contentLength <= 0) {
|
||||
Serial.println("Invalid content length");
|
||||
http.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// The signed firmware includes the signature (512 bytes padding)
|
||||
// The actual firmware size is contentLength - 512
|
||||
const size_t signatureSize = 512;
|
||||
size_t firmwareSize = contentLength - signatureSize;
|
||||
|
||||
Serial.printf("Actual firmware size: %zu bytes\n", firmwareSize);
|
||||
Serial.printf("Signature size: %zu bytes\n", signatureSize);
|
||||
|
||||
// Select hash algorithm
|
||||
#ifdef USE_SHA256
|
||||
int hashType = HASH_SHA256;
|
||||
Serial.println("Using SHA-256 hash");
|
||||
#elif defined(USE_SHA384)
|
||||
int hashType = HASH_SHA384;
|
||||
Serial.println("Using SHA-384 hash");
|
||||
#elif defined(USE_SHA512)
|
||||
int hashType = HASH_SHA512;
|
||||
Serial.println("Using SHA-512 hash");
|
||||
#else
|
||||
#error "Please define a hash algorithm (USE_SHA256, USE_SHA384, or USE_SHA512)"
|
||||
#endif
|
||||
|
||||
// Create verifier object
|
||||
#ifdef USE_RSA
|
||||
UpdaterRSAVerifier sign(PUBLIC_KEY, PUBLIC_KEY_LEN, hashType);
|
||||
Serial.println("Using RSA signature verification");
|
||||
#elif defined(USE_ECDSA)
|
||||
UpdaterECDSAVerifier sign(PUBLIC_KEY, PUBLIC_KEY_LEN, hashType);
|
||||
Serial.println("Using ECDSA signature verification");
|
||||
#else
|
||||
#error "Please define a signature scheme (USE_RSA or USE_ECDSA)"
|
||||
#endif
|
||||
|
||||
// Install signature verification BEFORE calling Update.begin()
|
||||
if (!Update.installSignature(&sign)) {
|
||||
Serial.println("Failed to install signature verification");
|
||||
http.end();
|
||||
return;
|
||||
}
|
||||
Serial.println("Signature verification installed");
|
||||
|
||||
// Begin update with the TOTAL size (firmware + signature)
|
||||
if (!Update.begin(contentLength)) {
|
||||
Serial.printf("Update.begin failed: %s\n", Update.errorString());
|
||||
http.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the stream
|
||||
WiFiClient *stream = http.getStreamPtr();
|
||||
|
||||
// Write firmware data
|
||||
Serial.println("Writing firmware...");
|
||||
size_t written = 0;
|
||||
uint8_t buff[1024];
|
||||
int progress = 0;
|
||||
|
||||
while (http.connected() && (written < contentLength)) {
|
||||
size_t available = stream->available();
|
||||
|
||||
if (available) {
|
||||
int bytesRead = stream->readBytes(buff, min(available, sizeof(buff)));
|
||||
|
||||
if (bytesRead > 0) {
|
||||
size_t bytesWritten = Update.write(buff, bytesRead);
|
||||
|
||||
if (bytesWritten > 0) {
|
||||
written += bytesWritten;
|
||||
|
||||
// Print progress
|
||||
int newProgress = (written * 100) / contentLength;
|
||||
if (newProgress != progress && newProgress % 10 == 0) {
|
||||
progress = newProgress;
|
||||
Serial.printf("Progress: %d%%\n", progress);
|
||||
}
|
||||
} else {
|
||||
Serial.printf("Update.write failed: %s\n", Update.errorString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
delay(1);
|
||||
}
|
||||
|
||||
Serial.printf("Written: %zu bytes\n", written);
|
||||
|
||||
// End the update - this will verify the signature
|
||||
if (Update.end()) {
|
||||
Serial.println("OTA update completed successfully!");
|
||||
Serial.println("Signature verified!");
|
||||
|
||||
if (Update.isFinished()) {
|
||||
Serial.println("Update successfully completed. Rebooting...");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
} else {
|
||||
Serial.println("Update not finished? Something went wrong!");
|
||||
}
|
||||
} else {
|
||||
Serial.printf("Update.end failed: %s\n", Update.errorString());
|
||||
|
||||
// Check if it was a signature verification failure
|
||||
if (Update.getError() == UPDATE_ERROR_SIGN) {
|
||||
Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
||||
Serial.println("SIGNATURE VERIFICATION FAILED!");
|
||||
Serial.println("The firmware was not signed with the");
|
||||
Serial.println("correct private key or is corrupted.");
|
||||
Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
||||
}
|
||||
}
|
||||
|
||||
http.end();
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println("\n\nSigned OTA Update Example");
|
||||
Serial.println("=========================\n");
|
||||
|
||||
// Connect to WiFi
|
||||
Serial.printf("Connecting to WiFi: %s\n", ssid);
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
|
||||
Serial.println("\nWiFi connected!");
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
// Wait a bit before starting OTA
|
||||
delay(2000);
|
||||
|
||||
// Perform OTA update
|
||||
performOTAUpdate();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Nothing to do here
|
||||
delay(1000);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
-DUPDATE_SIGN
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,32 @@
|
||||
// Public key for OTA signature verification
|
||||
// Include this in your Arduino sketch
|
||||
|
||||
// ⚠️ THIS IS A TEST KEY - DO NOT USE IN PRODUCTION!
|
||||
// Generate your own keys using:
|
||||
// python <ARDUINO_ROOT>/tools/bin_signing.py --generate-key rsa-2048 --out private_key.pem
|
||||
// python <ARDUINO_ROOT>/tools/bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
|
||||
//
|
||||
// Then replace this file with the generated public_key.h
|
||||
|
||||
// Test RSA-2048 Public Key (PEM format)
|
||||
const uint8_t PUBLIC_KEY[] PROGMEM = {
|
||||
0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
|
||||
0x0a, 0x4d, 0x49, 0x49, 0x42, 0x49, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41,
|
||||
0x41, 0x4f, 0x43, 0x41, 0x51, 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, 0x36, 0x42, 0x33, 0x52, 0x67, 0x34, 0x39,
|
||||
0x6b, 0x4e, 0x47, 0x72, 0x44, 0x2b, 0x50, 0x46, 0x6e, 0x39, 0x64, 0x69, 0x4b, 0x0a, 0x57, 0x50, 0x34, 0x65, 0x42, 0x59, 0x4d, 0x2f, 0x49, 0x79, 0x6b, 0x55,
|
||||
0x4b, 0x4d, 0x34, 0x39, 0x63, 0x6a, 0x65, 0x56, 0x56, 0x4f, 0x39, 0x42, 0x4f, 0x30, 0x66, 0x6c, 0x47, 0x47, 0x6e, 0x47, 0x71, 0x79, 0x34, 0x50, 0x72, 0x69,
|
||||
0x4e, 0x71, 0x32, 0x62, 0x4a, 0x4a, 0x6a, 0x7a, 0x68, 0x38, 0x46, 0x32, 0x42, 0x53, 0x4f, 0x75, 0x74, 0x48, 0x77, 0x75, 0x7a, 0x6d, 0x62, 0x45, 0x52, 0x6f,
|
||||
0x0a, 0x30, 0x38, 0x51, 0x72, 0x32, 0x30, 0x4e, 0x61, 0x52, 0x72, 0x7a, 0x6e, 0x71, 0x6e, 0x59, 0x4e, 0x57, 0x4e, 0x69, 0x6e, 0x43, 0x67, 0x7a, 0x34, 0x34,
|
||||
0x49, 0x4e, 0x50, 0x50, 0x78, 0x70, 0x45, 0x55, 0x65, 0x68, 0x61, 0x32, 0x66, 0x6d, 0x6d, 0x39, 0x77, 0x5a, 0x67, 0x57, 0x31, 0x69, 0x31, 0x67, 0x31, 0x77,
|
||||
0x70, 0x68, 0x56, 0x51, 0x6c, 0x5a, 0x30, 0x49, 0x63, 0x72, 0x6d, 0x5a, 0x5a, 0x0a, 0x42, 0x61, 0x33, 0x49, 0x64, 0x6a, 0x78, 0x63, 0x52, 0x67, 0x51, 0x6c,
|
||||
0x69, 0x32, 0x4b, 0x74, 0x78, 0x72, 0x41, 0x4a, 0x67, 0x33, 0x4a, 0x47, 0x43, 0x54, 0x2f, 0x39, 0x6d, 0x7a, 0x52, 0x31, 0x70, 0x37, 0x59, 0x34, 0x50, 0x34,
|
||||
0x65, 0x71, 0x30, 0x6b, 0x2b, 0x78, 0x2b, 0x45, 0x72, 0x6f, 0x35, 0x73, 0x47, 0x69, 0x49, 0x7a, 0x33, 0x44, 0x67, 0x61, 0x50, 0x43, 0x54, 0x41, 0x37, 0x52,
|
||||
0x0a, 0x4b, 0x69, 0x75, 0x6e, 0x2f, 0x67, 0x64, 0x56, 0x71, 0x34, 0x35, 0x2f, 0x75, 0x62, 0x64, 0x53, 0x58, 0x65, 0x62, 0x50, 0x46, 0x43, 0x73, 0x36, 0x66,
|
||||
0x46, 0x73, 0x52, 0x39, 0x6d, 0x43, 0x6f, 0x37, 0x70, 0x43, 0x4b, 0x74, 0x45, 0x55, 0x51, 0x78, 0x34, 0x4d, 0x68, 0x55, 0x4e, 0x5a, 0x48, 0x48, 0x31, 0x49,
|
||||
0x33, 0x62, 0x79, 0x57, 0x35, 0x7a, 0x39, 0x36, 0x49, 0x6a, 0x46, 0x44, 0x68, 0x0a, 0x54, 0x2f, 0x64, 0x5a, 0x71, 0x32, 0x6d, 0x44, 0x54, 0x64, 0x76, 0x59,
|
||||
0x2b, 0x6d, 0x5a, 0x75, 0x51, 0x4d, 0x37, 0x6c, 0x72, 0x31, 0x4d, 0x4e, 0x6a, 0x35, 0x36, 0x79, 0x74, 0x41, 0x56, 0x4a, 0x39, 0x56, 0x7a, 0x74, 0x44, 0x75,
|
||||
0x35, 0x4f, 0x6a, 0x48, 0x32, 0x76, 0x6f, 0x32, 0x6b, 0x59, 0x46, 0x4f, 0x72, 0x52, 0x49, 0x57, 0x70, 0x5a, 0x4c, 0x56, 0x35, 0x6c, 0x47, 0x79, 0x7a, 0x45,
|
||||
0x0a, 0x33, 0x77, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20,
|
||||
0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x00,
|
||||
};
|
||||
const size_t PUBLIC_KEY_LEN = 452;
|
||||
Reference in New Issue
Block a user