-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Adds feature to decrypt uploaded image bin files. Used esp-idf to encrypt a bin file. #5807
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
94b045d
3c44880
dad1961
3818cd7
d5d1396
70e90d0
94894e8
6f79fb8
51a3344
44a4283
dbcb6e9
2f2a7c5
cb3be3f
b31841c
e042b73
4e5ffcc
78f2efd
d375716
2cd953a
e135775
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
/* | ||
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. | ||
|
||
To protect firmware images files from being copied and used on blank devices, use encrypted image files by using espressif idf. | ||
These can be OTA uploaded to device using 'Update.h', if set it up with the same key, address and flash_crypt_conf as used to encrypt image file or vice versa | ||
or if needed OTA upload with plain(unencrypted) FLASH image file, if OTA_MODE = U_AES_AUTO_FLASH(default) or U_AES_SPIFFS_AUTO_FLASH. | ||
Firmware is also protected on device, when FLASH encryption is enabled on device, suggest not to use the same key for updates 'OTA_KEY' as the device's FLASH key burnt into eFuses 'flash_encryption' | ||
|
||
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_AUTO_FLASH | ||
|
||
OTA_MODE options:- | ||
U_AES_DECRYPT_NONE decryption disabled, loads OTA image files as sent(plain) | ||
U_AES_AUTO_FLASH auto loads both plain & encrypted OTA FLASH image files and loads plain OTA SPIFFS image files | ||
U_AES_FLASH loads encrypted OTA FLASH image files and plain OTA SPIFFS image files | ||
U_AES_SPIFFS loads encrypted OTA SPIFFS image files and plain OTA FLASH image files | ||
U_AES_SPIFFS_AUTO_FLASH loads encrypted OTA SPIFFS image files and auto loads both plain & encrypted OTA FLASH image files | ||
U_AES_DECRYPT_ALL decrypts all OTA image files sent | ||
|
||
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/ | ||
|
||
espsecure.py encrypt_flash_data -k ota_key.bin --flash_crypt_conf 0xf -a 0x1000 -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; | ||
const uint32_t OTA_CFG = 0x0f; | ||
const uint32_t OTA_MODE = U_AES_AUTO_FLASH; | ||
|
||
/*=================================================================*/ | ||
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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
0123456789 this a simpletest key |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
#include <MD5Builder.h> | ||
#include <functional> | ||
#include "esp_partition.h" | ||
#include <hwcrypto/aes.h> | ||
|
||
#define UPDATE_ERROR_OK (0) | ||
#define UPDATE_ERROR_WRITE (1) | ||
|
@@ -19,14 +20,25 @@ | |
#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 | ||
|
||
#define U_FLASH 0 | ||
#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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest to separate the option for app and spiffs and use something like: #define U_AES_DECRYPT_NONE 0
#define U_AES_DECRYPT_AUTO 1
#define U_AES_DECRYPT_ON 2 This will make the example and option selection much easier for the users There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sound good with me, I will simplify user options. Probably just go with those three as you listed and then user can decide image tyoe(app, spiffs, ..) with command U_FLASH, U_SPIFFS when calling Upate.begin() function. I make changes & retest. Note auto only works with app image atm, using magic byte check, I will check but dont think spiffs using a special byte at begining of image. |
||
#define U_AES_AUTO_FLASH 1 | ||
#define U_AES_FLASH 2 | ||
#define U_AES_SPIFFS 4 | ||
#define U_AES_SPIFFS_AUTO_FLASH 5 | ||
#define U_AES_DECRYPT_ALL 6 | ||
#define U_DECRYPTING 8 | ||
|
||
class UpdateClass { | ||
public: | ||
|
@@ -45,6 +57,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, same as the AES key used to encrypt file | ||
Crypt Address is addressed used to encrypt file | ||
Crypt Config is cfg used to encrypt image file | ||
Crypt Mode select if image file is to be decrypted | ||
*/ | ||
bool setupCrypt(const uint8_t *cryptKey=0, size_t cryptAddress=0, uint8_t cryptConfig=0xf, int cryptMode=U_AES_AUTO_FLASH); | ||
|
||
/* | ||
Writes a buffer to the flash and increments the address | ||
Returns the amount written | ||
|
@@ -72,6 +93,26 @@ class UpdateClass { | |
*/ | ||
bool end(bool evenIfRemaining = false); | ||
|
||
/* | ||
sets AES256 key(32 bytes) used to encrpt flash image, sizeOf | ||
*/ | ||
bool setCryptKey(const uint8_t *cryptKey); | ||
|
||
/* | ||
sets address used to encrypt flash image(0x000000 default) | ||
*/ | ||
void setCryptAddress(const size_t cryptAddress){ _cryptAddress = cryptAddress & 0x00fffff0; } | ||
|
||
/* | ||
sets crypt mode used on images(UM_AUTO default) | ||
*/ | ||
bool setCryptMode(const int cryptMode); | ||
|
||
/* | ||
sets crypt config used to encrypt flash image(0xf default) | ||
*/ | ||
void setCryptConfig(const uint8_t cryptConfig){ _cryptCfg = cryptConfig & 0x0f; } | ||
|
||
/* | ||
Aborts the running update | ||
*/ | ||
|
@@ -162,13 +203,17 @@ 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(); | ||
bool _enablePartition(const esp_partition_t* partition); | ||
|
||
|
||
uint8_t _error; | ||
uint8_t *_cryptKey; | ||
uint8_t *_cryptBuffer; | ||
uint8_t *_buffer; | ||
uint8_t *_skipBuffer; | ||
size_t _bufferLen; | ||
|
@@ -184,6 +229,10 @@ class UpdateClass { | |
|
||
int _ledPin; | ||
uint8_t _ledOn; | ||
|
||
size_t _cryptAddress; | ||
uint8_t _cryptMode; | ||
uint8_t _cryptCfg; | ||
}; | ||
|
||
extern UpdateClass Update; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change to "aes/esp_aes.h"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated files with changes and hopefully made more understandable comments and easier to follow tweak code. I tested with encrypted image files. The image file was encrypted using espsecure, tested with crypt config value 0x0 to 0xf and a range of address values from 0x00000000 throught to 0x00800000. All were succesfully loaded and ran on esp-wroom32 module.
I noticed encrypted addresses above 0x00fffff0 aren't decrypt correctly, it seems espsecure.py tweak code doesnt limit lower 24bit address bits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @me-no-dev I've updated Ardunio-esp32 v2.0.1 it was on v1.0.6 before, hence why I was using <hwcrypto/aes.h>. I see the additional boards manage url to json has changed. So now updated to test with 2.0.1, the "aes/esp_aes.h" removed the compiler error. Would it be possible to run workflows to check all is good.