Adds feature to decrypt uploaded image bin files. Used esp-idf to encrypt a bin file. (#5807)
* Update Update.h * Update Updater.cpp * Add files via upload * Add files via upload * Add files via upload * Update Update.h * Update Updater.cpp * Add files via upload * Revert changes * Revert changes * Fix CI * Fix format * Skip H2 * Use new * Fix comments and formatting * Format example * Remove binaries and QoL improvements --------- Co-authored-by: Me No Dev <me-no-dev@users.noreply.github.com> Co-authored-by: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com>
This commit is contained in:
parent
bbbbec977a
commit
aceea3e519
7 changed files with 843 additions and 1 deletions
|
|
@ -0,0 +1,333 @@
|
|||
/*
|
||||
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 relavent 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 <WiFiClient.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: %d%%\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;
|
||||
}
|
||||
WiFiClient *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;
|
||||
WiFiClient 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 avaliable
|
||||
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 avaliable");
|
||||
}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;
|
||||
WiFiClient 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 avaliable");
|
||||
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 Sucessfull, 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 Sucessfull, rebooting");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
|
|
@ -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 avaliable
|
||||
exit;
|
||||
}else{
|
||||
header('update: 1' );//update avaliable
|
||||
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,235 @@
|
|||
/*
|
||||
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 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 <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <SPIFFS.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: %d%%", 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, get's 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(SPIFFS.totalBytes(), U_SPIFFS)) {//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: %u\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
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include <MD5Builder.h>
|
||||
#include <functional>
|
||||
#include "esp_partition.h"
|
||||
#include "aes/esp_aes.h"
|
||||
|
||||
#define UPDATE_ERROR_OK (0)
|
||||
#define UPDATE_ERROR_WRITE (1)
|
||||
|
|
@ -19,6 +20,7 @@
|
|||
#define UPDATE_ERROR_NO_PARTITION (10)
|
||||
#define UPDATE_ERROR_BAD_ARGUMENT (11)
|
||||
#define UPDATE_ERROR_ABORT (12)
|
||||
#define UPDATE_ERROR_DECRYPT (13)
|
||||
|
||||
#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF
|
||||
|
||||
|
|
@ -26,7 +28,15 @@
|
|||
#define U_SPIFFS 100
|
||||
#define U_AUTH 200
|
||||
|
||||
#define ENCRYPTED_BLOCK_SIZE 16
|
||||
#define ENCRYPTED_BLOCK_SIZE 16
|
||||
#define ENCRYPTED_TWEAK_BLOCK_SIZE 32
|
||||
#define ENCRYPTED_KEY_SIZE 32
|
||||
|
||||
#define U_AES_DECRYPT_NONE 0
|
||||
#define U_AES_DECRYPT_AUTO 1
|
||||
#define U_AES_DECRYPT_ON 2
|
||||
#define U_AES_DECRYPT_MODE_MASK 3
|
||||
#define U_AES_IMAGE_DECRYPTING_BIT 4
|
||||
|
||||
#define SPI_SECTORS_PER_BLOCK 16 // usually large erase block is 32k/64k
|
||||
#define SPI_FLASH_BLOCK_SIZE (SPI_SECTORS_PER_BLOCK*SPI_FLASH_SEC_SIZE)
|
||||
|
|
@ -48,6 +58,15 @@ class UpdateClass {
|
|||
*/
|
||||
bool begin(size_t size=UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW, const char *label = NULL);
|
||||
|
||||
/*
|
||||
Setup decryption configuration
|
||||
Crypt Key is 32bytes(256bits) block of data, use the same key as used to encrypt image file
|
||||
Crypt Address, use the same value as used to encrypt image file
|
||||
Crypt Config, use the same value as used to encrypt image file
|
||||
Crypt Mode, used to select if image files should be decrypted or not
|
||||
*/
|
||||
bool setupCrypt(const uint8_t *cryptKey=0, size_t cryptAddress=0, uint8_t cryptConfig=0xf, int cryptMode=U_AES_DECRYPT_AUTO);
|
||||
|
||||
/*
|
||||
Writes a buffer to the flash and increments the address
|
||||
Returns the amount written
|
||||
|
|
@ -75,6 +94,26 @@ class UpdateClass {
|
|||
*/
|
||||
bool end(bool evenIfRemaining = false);
|
||||
|
||||
/*
|
||||
sets AES256 key(32 bytes) used for decrypting image file
|
||||
*/
|
||||
bool setCryptKey(const uint8_t *cryptKey);
|
||||
|
||||
/*
|
||||
sets crypt mode used on image files
|
||||
*/
|
||||
bool setCryptMode(const int cryptMode);
|
||||
|
||||
/*
|
||||
sets address used for decrypting image file
|
||||
*/
|
||||
void setCryptAddress(const size_t cryptAddress){ _cryptAddress = cryptAddress & 0x00fffff0; }
|
||||
|
||||
/*
|
||||
sets crypt config used for decrypting image file
|
||||
*/
|
||||
void setCryptConfig(const uint8_t cryptConfig){ _cryptCfg = cryptConfig & 0x0f; }
|
||||
|
||||
/*
|
||||
Aborts the running update
|
||||
*/
|
||||
|
|
@ -165,6 +204,8 @@ class UpdateClass {
|
|||
private:
|
||||
void _reset();
|
||||
void _abort(uint8_t err);
|
||||
void _cryptKeyTweak(size_t cryptAddress, uint8_t *tweaked_key);
|
||||
bool _decryptBuffer();
|
||||
bool _writeBuffer();
|
||||
bool _verifyHeader(uint8_t data);
|
||||
bool _verifyEnd();
|
||||
|
|
@ -173,6 +214,8 @@ class UpdateClass {
|
|||
|
||||
|
||||
uint8_t _error;
|
||||
uint8_t *_cryptKey;
|
||||
uint8_t *_cryptBuffer;
|
||||
uint8_t *_buffer;
|
||||
uint8_t *_skipBuffer;
|
||||
size_t _bufferLen;
|
||||
|
|
@ -188,6 +231,10 @@ class UpdateClass {
|
|||
|
||||
int _ledPin;
|
||||
uint8_t _ledOn;
|
||||
|
||||
uint8_t _cryptMode;
|
||||
size_t _cryptAddress;
|
||||
uint8_t _cryptCfg;
|
||||
};
|
||||
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_UPDATE)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ static const char * _err2str(uint8_t _error){
|
|||
return ("Bad Argument");
|
||||
} else if(_error == UPDATE_ERROR_ABORT){
|
||||
return ("Aborted");
|
||||
} else if(_error == UPDATE_ERROR_DECRYPT){
|
||||
return ("Decryption error");
|
||||
}
|
||||
return ("UNKNOWN");
|
||||
}
|
||||
|
|
@ -59,7 +61,10 @@ bool UpdateClass::_enablePartition(const esp_partition_t* partition){
|
|||
|
||||
UpdateClass::UpdateClass()
|
||||
: _error(0)
|
||||
, _cryptKey(0)
|
||||
, _cryptBuffer(0)
|
||||
, _buffer(0)
|
||||
, _skipBuffer(0)
|
||||
, _bufferLen(0)
|
||||
, _size(0)
|
||||
, _progress_callback(NULL)
|
||||
|
|
@ -67,6 +72,9 @@ UpdateClass::UpdateClass()
|
|||
, _paroffset(0)
|
||||
, _command(U_FLASH)
|
||||
, _partition(NULL)
|
||||
, _cryptMode(U_AES_DECRYPT_AUTO)
|
||||
, _cryptAddress(0)
|
||||
, _cryptCfg(0xf)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -83,6 +91,7 @@ void UpdateClass::_reset() {
|
|||
delete[] _skipBuffer;
|
||||
}
|
||||
|
||||
_cryptBuffer = nullptr;
|
||||
_buffer = nullptr;
|
||||
_skipBuffer = nullptr;
|
||||
_bufferLen = 0;
|
||||
|
|
@ -176,6 +185,48 @@ bool UpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, con
|
|||
return true;
|
||||
}
|
||||
|
||||
bool UpdateClass::setupCrypt(const uint8_t *cryptKey, size_t cryptAddress, uint8_t cryptConfig, int cryptMode){
|
||||
if(setCryptKey(cryptKey)){
|
||||
if(setCryptMode(cryptMode)){
|
||||
setCryptAddress(cryptAddress);
|
||||
setCryptConfig(cryptConfig);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UpdateClass::setCryptKey(const uint8_t *cryptKey){
|
||||
if(!cryptKey){
|
||||
if (_cryptKey){
|
||||
delete[] _cryptKey;
|
||||
_cryptKey = 0;
|
||||
log_d("AES key unset");
|
||||
}
|
||||
return false; //key cleared, no key to decrypt with
|
||||
}
|
||||
//initialize
|
||||
if(!_cryptKey){
|
||||
_cryptKey = new (std::nothrow) uint8_t[ENCRYPTED_KEY_SIZE];
|
||||
}
|
||||
if(!_cryptKey){
|
||||
log_e("new failed");
|
||||
return false;
|
||||
}
|
||||
memcpy(_cryptKey, cryptKey, ENCRYPTED_KEY_SIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdateClass::setCryptMode(const int cryptMode){
|
||||
if(cryptMode >= U_AES_DECRYPT_NONE && cryptMode <= U_AES_DECRYPT_ON){
|
||||
_cryptMode = cryptMode;
|
||||
}else{
|
||||
log_e("bad crypt mode arguement %i", cryptMode);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateClass::_abort(uint8_t err){
|
||||
_reset();
|
||||
_error = err;
|
||||
|
|
@ -185,7 +236,119 @@ void UpdateClass::abort(){
|
|||
_abort(UPDATE_ERROR_ABORT);
|
||||
}
|
||||
|
||||
void UpdateClass::_cryptKeyTweak(size_t cryptAddress, uint8_t *tweaked_key){
|
||||
memcpy(tweaked_key, _cryptKey, ENCRYPTED_KEY_SIZE );
|
||||
if(_cryptCfg == 0) return; //no tweaking needed, use crypt key as-is
|
||||
|
||||
const uint8_t pattern[] = { 23, 23, 23, 14, 23, 23, 23, 12, 23, 23, 23, 10, 23, 23, 23, 8 };
|
||||
int pattern_idx = 0;
|
||||
int key_idx = 0;
|
||||
int bit_len = 0;
|
||||
uint32_t tweak = 0;
|
||||
cryptAddress &= 0x00ffffe0; //bit 23-5
|
||||
cryptAddress <<= 8; //bit23 shifted to bit31(MSB)
|
||||
while(pattern_idx < sizeof(pattern)){
|
||||
tweak = cryptAddress<<(23 - pattern[pattern_idx]); //bit shift for small patterns
|
||||
// alternative to: tweak = rotl32(tweak,8 - bit_len);
|
||||
tweak = (tweak<<(8 - bit_len)) | (tweak>>(24 + bit_len)); //rotate to line up with end of previous tweak bits
|
||||
bit_len += pattern[pattern_idx++] - 4; //add number of bits in next pattern(23-4 = 19bits = 23bit to 5bit)
|
||||
while(bit_len > 7){
|
||||
tweaked_key[key_idx++] ^= tweak; //XOR byte
|
||||
// alternative to: tweak = rotl32(tweak, 8);
|
||||
tweak = (tweak<<8) | (tweak>>24); //compiler should optimize to use rotate(fast)
|
||||
bit_len -=8;
|
||||
}
|
||||
tweaked_key[key_idx] ^= tweak; //XOR remaining bits, will XOR zeros if no remaining bits
|
||||
}
|
||||
if(_cryptCfg == 0xf) return; //return with fully tweaked key
|
||||
|
||||
//some of tweaked key bits need to be restore back to crypt key bits
|
||||
const uint8_t cfg_bits[] = { 67, 65, 63, 61 };
|
||||
key_idx = 0;
|
||||
pattern_idx = 0;
|
||||
while(key_idx < ENCRYPTED_KEY_SIZE){
|
||||
bit_len += cfg_bits[pattern_idx];
|
||||
if( (_cryptCfg & (1<<pattern_idx)) == 0 ){ //restore crypt key bits
|
||||
while(bit_len > 0){
|
||||
if( bit_len > 7 || ((_cryptCfg & (2<<pattern_idx)) == 0) ){ //restore a crypt key byte
|
||||
tweaked_key[key_idx] = _cryptKey[key_idx];
|
||||
}else{ //MSBits restore crypt key bits, LSBits keep as tweaked bits
|
||||
tweaked_key[key_idx] &= (0xff>>bit_len);
|
||||
tweaked_key[key_idx] |= (_cryptKey[key_idx] & (~(0xff>>bit_len)) );
|
||||
}
|
||||
key_idx++;
|
||||
bit_len -= 8;
|
||||
}
|
||||
}else{ //keep tweaked key bits
|
||||
while(bit_len > 0){
|
||||
if( bit_len <8 && ((_cryptCfg & (2<<pattern_idx)) == 0) ){ //MSBits keep as tweaked bits, LSBits restore crypt key bits
|
||||
tweaked_key[key_idx] &= (~(0xff>>bit_len));
|
||||
tweaked_key[key_idx] |= (_cryptKey[key_idx] & (0xff>>bit_len));
|
||||
}
|
||||
key_idx++;
|
||||
bit_len -= 8;
|
||||
}
|
||||
}
|
||||
pattern_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdateClass::_decryptBuffer(){
|
||||
if(!_cryptKey){
|
||||
log_w("AES key not set");
|
||||
return false;
|
||||
}
|
||||
if(_bufferLen%ENCRYPTED_BLOCK_SIZE !=0 ){
|
||||
log_e("buffer size error");
|
||||
return false;
|
||||
}
|
||||
if(!_cryptBuffer){
|
||||
_cryptBuffer = new (std::nothrow) uint8_t[ENCRYPTED_BLOCK_SIZE];
|
||||
}
|
||||
if(!_cryptBuffer){
|
||||
log_e("new failed");
|
||||
return false;
|
||||
}
|
||||
uint8_t tweaked_key[ENCRYPTED_KEY_SIZE]; //tweaked crypt key
|
||||
int done = 0;
|
||||
|
||||
esp_aes_context ctx; //initialize AES
|
||||
esp_aes_init( &ctx );
|
||||
while((_bufferLen - done) >= ENCRYPTED_BLOCK_SIZE){
|
||||
for(int i=0; i < ENCRYPTED_BLOCK_SIZE; i++) _cryptBuffer[(ENCRYPTED_BLOCK_SIZE - 1) - i] = _buffer[i + done]; //reverse order 16 bytes to decrypt
|
||||
if( ((_cryptAddress + _progress + done) % ENCRYPTED_TWEAK_BLOCK_SIZE) == 0 || done == 0 ){
|
||||
_cryptKeyTweak(_cryptAddress + _progress + done, tweaked_key); //update tweaked crypt key
|
||||
if( esp_aes_setkey( &ctx, tweaked_key, 256 ) ){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if( esp_aes_crypt_ecb( &ctx, ESP_AES_ENCRYPT, _cryptBuffer, _cryptBuffer ) ){ //use ESP_AES_ENCRYPT to decrypt flash code
|
||||
return false;
|
||||
}
|
||||
for(int i=0; i < ENCRYPTED_BLOCK_SIZE; i++) _buffer[i + done] = _cryptBuffer[(ENCRYPTED_BLOCK_SIZE - 1) - i]; //reverse order 16 bytes from decrypt
|
||||
done += ENCRYPTED_BLOCK_SIZE;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdateClass::_writeBuffer(){
|
||||
//first bytes of loading image, check to see if loading image needs decrypting
|
||||
if(!_progress){
|
||||
_cryptMode &= U_AES_DECRYPT_MODE_MASK;
|
||||
if( ( _cryptMode == U_AES_DECRYPT_ON )
|
||||
|| ((_command == U_FLASH) && (_cryptMode & U_AES_DECRYPT_AUTO) && (_buffer[0] != ESP_IMAGE_HEADER_MAGIC))
|
||||
){
|
||||
_cryptMode |= U_AES_IMAGE_DECRYPTING_BIT; //set to decrypt the loading image
|
||||
log_d("Decrypting OTA Image");
|
||||
}
|
||||
}
|
||||
//check if data in buffer needs decrypting
|
||||
if( _cryptMode & U_AES_IMAGE_DECRYPTING_BIT ){
|
||||
if( !_decryptBuffer() ){
|
||||
_abort(UPDATE_ERROR_DECRYPT);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//first bytes of new firmware
|
||||
uint8_t skip = 0;
|
||||
if(!_progress && _command == U_FLASH){
|
||||
|
|
|
|||
Loading…
Reference in a new issue