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
<?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);
?>
......@@ -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