Unverified Commit aceea3e5 authored by theeprawn's avatar theeprawn Committed by GitHub

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: default avatarMe No Dev <me-no-dev@users.noreply.github.com>
Co-authored-by: default avatarLucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com>
parent bbbbec97
/*
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() {
}
<?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);
?>
/*
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){
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment