diff --git a/libraries/Update/examples/HTTP_Client_AES_OTA_Update/.skip.esp32h2 b/libraries/Update/examples/HTTP_Client_AES_OTA_Update/.skip.esp32h2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/Update/examples/HTTP_Client_AES_OTA_Update/HTTP_Client_AES_OTA_Update.ino b/libraries/Update/examples/HTTP_Client_AES_OTA_Update/HTTP_Client_AES_OTA_Update.ino new file mode 100644 index 00000000000..e9b1aa75a5e --- /dev/null +++ b/libraries/Update/examples/HTTP_Client_AES_OTA_Update/HTTP_Client_AES_OTA_Update.ino @@ -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 +#include +#include +#include +#include + +//========================================================================== +//========================================================================== +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() { +} diff --git a/libraries/Update/examples/HTTP_Client_AES_OTA_Update/updater.php b/libraries/Update/examples/HTTP_Client_AES_OTA_Update/updater.php new file mode 100644 index 00000000000..4edfb75a126 --- /dev/null +++ b/libraries/Update/examples/HTTP_Client_AES_OTA_Update/updater.php @@ -0,0 +1,64 @@ + $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); +?> diff --git a/libraries/Update/examples/HTTP_Server_AES_OTA_Update/.skip.esp32h2 b/libraries/Update/examples/HTTP_Server_AES_OTA_Update/.skip.esp32h2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/Update/examples/HTTP_Server_AES_OTA_Update/HTTP_Server_AES_OTA_Update.ino b/libraries/Update/examples/HTTP_Server_AES_OTA_Update/HTTP_Server_AES_OTA_Update.ino new file mode 100644 index 00000000000..b4c611d2739 --- /dev/null +++ b/libraries/Update/examples/HTTP_Server_AES_OTA_Update/HTTP_Server_AES_OTA_Update.ino @@ -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 +#include +#include +#include +#include +#include + +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 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"( + + + Image Upload + + + + +
+ Firmware:

+

+ +
+


+
+ FileSystem:

+

+ +
+ + )"; + +/*=================================================================*/ + +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("Update error: ")) + String(Update.errorString()) ); + } else { + httpServer.client().setNoDelay(true); + httpServer.send(200, PSTR("text/html"), String(F("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 +} diff --git a/libraries/Update/src/Update.h b/libraries/Update/src/Update.h index d34efe73196..cb99508238b 100644 --- a/libraries/Update/src/Update.h +++ b/libraries/Update/src/Update.h @@ -5,6 +5,7 @@ #include #include #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) diff --git a/libraries/Update/src/Updater.cpp b/libraries/Update/src/Updater.cpp index 9980558dbba..bd22419d37a 100644 --- a/libraries/Update/src/Updater.cpp +++ b/libraries/Update/src/Updater.cpp @@ -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< 0){ + if( bit_len > 7 || ((_cryptCfg & (2<>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<>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){ diff --git a/libraries/WebServer/src/WebServer.cpp b/libraries/WebServer/src/WebServer.cpp index dd443dbd8e8..e423900ca52 100644 --- a/libraries/WebServer/src/WebServer.cpp +++ b/libraries/WebServer/src/WebServer.cpp @@ -402,6 +402,11 @@ void WebServer::handleClient() { _contentLength = CONTENT_LENGTH_NOT_SET; _handleRequest(); + if (_currentClient.isSSE()) { + _currentStatus = HC_WAIT_CLOSE; + _statusChange = millis(); + keepCurrentClient = true; + } // Fix for issue with Chrome based browsers: https://github.com/espressif/arduino-esp32/issues/3652 // if (_currentClient.connected()) { // _currentStatus = HC_WAIT_CLOSE; @@ -417,6 +422,10 @@ void WebServer::handleClient() { } break; case HC_WAIT_CLOSE: + if (_currentClient.isSSE()) { + // Never close connection + _statusChange = millis(); + } // Wait for client to close the connection if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) { keepCurrentClient = true; diff --git a/libraries/WiFi/src/WiFiClient.cpp b/libraries/WiFi/src/WiFiClient.cpp index 52aca2f1bd9..96eab3b5602 100644 --- a/libraries/WiFi/src/WiFiClient.cpp +++ b/libraries/WiFi/src/WiFiClient.cpp @@ -187,7 +187,7 @@ class WiFiClientSocketHandle { } }; -WiFiClient::WiFiClient():_rxBuffer(nullptr),_connected(false),_timeout(WIFI_CLIENT_DEF_CONN_TIMEOUT_MS),next(NULL) +WiFiClient::WiFiClient():_rxBuffer(nullptr),_connected(false),_sse(false),_timeout(WIFI_CLIENT_DEF_CONN_TIMEOUT_MS),next(NULL) { } @@ -347,7 +347,7 @@ int WiFiClient::setOption(int option, int *value) int WiFiClient::getOption(int option, int *value) { - socklen_t size = sizeof(int); + socklen_t size = sizeof(int); int res = getsockopt(fd(), IPPROTO_TCP, option, (char *)value, &size); if(res < 0) { log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno)); @@ -661,3 +661,14 @@ int WiFiClient::fd() const return clientSocketHandle->fd(); } } + +void WiFiClient::setSSE(bool sse) +{ + _sse = sse; +} + +bool WiFiClient::isSSE() +{ + return _sse; +} + diff --git a/libraries/WiFi/src/WiFiClient.h b/libraries/WiFi/src/WiFiClient.h index d78e00fb19e..5eee16c8070 100644 --- a/libraries/WiFi/src/WiFiClient.h +++ b/libraries/WiFi/src/WiFiClient.h @@ -42,6 +42,7 @@ class WiFiClient : public ESPLwIPClient std::shared_ptr clientSocketHandle; std::shared_ptr _rxBuffer; bool _connected; + bool _sse; int _timeout; int _lastWriteTimeout; int _lastReadTimeout; @@ -66,6 +67,8 @@ class WiFiClient : public ESPLwIPClient void flush(); void stop(); uint8_t connected(); + void setSSE(bool sse); + bool isSSE(); operator bool() {