OTA update with pre-encrypted file

rybakowskijakub
Posts: 1
Joined: Fri Mar 11, 2022 11:48 pm

OTA update with pre-encrypted file

Postby rybakowskijakub » Sat Mar 12, 2022 12:24 am

I needed to do encrypted OTA over BLE. I wanted to use an pre-encrypted file for the update. I have not found any information on this subject anywhere. And I know a lot of people ask about it. Here I will show you how to achieve ota with pre-generated keys and pre-encrypted file.

This is not a step by step tutorial. Just a proof of concept and a collection of information.

First of all, I will focus on OTA and not on the bluetooth connection.

Assumptions:
-flash encryption
-secure boot v2.
-pre-generated keys on PC

Goal:
-OTA update with an pre-encrypted file on the PC

1. We have to enable in sdk config secure boot v2 and flash encryption:

We need to resize the partition according to the tutorial.
https://docs.espressif.com/projects/esp ... ot-v2.html

-Activate secure boot and flash ecnryption
Image

-It is important to disable Sign binaries during build


2. Generating keys, flashing by uart, file encryption and signing.

Code: Select all

import esptool
import espefuse
import espsecure
import os
import sys
import shutil

from pathlib import Path

bootloader_offset = 0x1000
partitions_offset = 0xF000
otadata_offset = 0x32000
phydata_offset = 0x31000
app_offset0 = 0x40000
app_offset1 = 0x240000

nvs_key_offset = 0x30000
nvs_key_length = 0x1000

nvs_data_offset = 0x10000
nvs_data_length = 0x20000

app_name = "myappname"
ota_data_name = "ota_data_initial"
partitions_name = "partition-table"
bootloader_name = "bootloader"
phydata_name = "phy_init_data"

esp = None

class Args(object):
    pass

def genkeys(x):
    Path("data/" + str(x) + "/keys").mkdir(parents=True, exist_ok=True)

    if Path("data/" + str(x) + "/keys/signkey.pem").exists() == False:
        args = Args()
        args.keyfile = "data/" + str(x) + "/keys/signkey.pem"
        args.version = "2"
        espsecure.generate_signing_key(args)
    else:
        print("SIGNKEY EXIST")

    if Path("data/" + str(x) + "/keys/flashkey.bin").exists() == False:
        args = Args()
        args.keylen = 256
        args.key_file = open("data/" + str(x) + "/keys/flashkey.bin", 'wb')
        espsecure.generate_flash_encryption_key(args)
    else:
        print("FLASHKEY EXIST")

def sign(x):
    Path("temp/" + str(x)).mkdir(parents=True, exist_ok=True)
    
    ###################################################################################################################

    args = Args()
    args.keyfile = {open("data/" + str(x) + "/keys/signkey.pem", "rb")}
    args.output = "temp/" + str(x) + "/signed_" + bootloader_name + ".bin"
    args.datafile = open("../build/bootloader/" + bootloader_name + ".bin", "rb")
    args.version = "2"
    espsecure.sign_data(args)

    ###################################################################################################################

    args = Args()
    args.keyfile = {open("data/" + str(x) + "/keys/signkey.pem", "rb")}
    args.output = "temp/" + str(x) + "/signed_" + partitions_name + ".bin"
    args.datafile = open("../build/partition_table/" + partitions_name + ".bin", "rb")
    args.version = "2"
    args.append_signatures = True
    espsecure.sign_data(args)

    ###################################################################################################################

    args = Args()
    args.keyfile = {open("data/" + str(x) + "/keys/signkey.pem", "rb")}
    args.output = "temp/" + str(x) + "/signed_" + app_name + "0.bin"
    args.datafile = open("../build/" + app_name + ".bin", "rb")
    args.version = "2"
    args.append_signatures = True
    espsecure.sign_data(args)

    ###################################################################################################################

    args = Args()
    args.keyfile = {open("data/" + str(x) + "/keys/signkey.pem", "rb")}
    args.output = "temp/" + str(x) + "/signed_" + app_name + "1.bin"
    args.datafile = open("../build/" + app_name + ".bin", "rb")
    args.version = "2"
    args.append_signatures = True
    espsecure.sign_data(args)

    ###################################################################################################################
    
def encrypt(x):
    Path("update/" + str(x)).mkdir(parents=True, exist_ok=True)

    ###################################################################################################################

    shutil.copyfile("../build/" + phydata_name + ".bin", "update/" + str(x) + "/no_crypt_" + phydata_name + ".bin")

    ###################################################################################################################

    args = Args()
    args.keyfile = open("data/" + str(x) + "/keys/flashkey.bin", "rb")
    args.output = open("update/" + str(x) + "/crypt_" + bootloader_name + ".bin", "wb")
    args.plaintext_file = open("temp/" + str(x) + "/signed_" + bootloader_name + ".bin", "rb")
    args.aes_xts = False
    args.flash_crypt_conf = 15
    args.address = bootloader_offset
    espsecure.encrypt_flash_data(args)

    args.plaintext_file.close()
    os.remove(args.plaintext_file.name)

    ###################################################################################################################

    args = Args()
    args.keyfile = open("data/" + str(x) + "/keys/flashkey.bin", "rb")
    args.output = open("update/" + str(x) + "/crypt_" + partitions_name + ".bin", "wb")
    args.plaintext_file = open("temp/" + str(x) + "/signed_" + partitions_name + ".bin", "rb")
    args.aes_xts = False
    args.flash_crypt_conf = 15
    args.address = partitions_offset
    espsecure.encrypt_flash_data(args)

    args.plaintext_file.close()
    os.remove(args.plaintext_file.name)

    ###################################################################################################################

    args = Args()
    args.keyfile = open("data/" + str(x) + "/keys/flashkey.bin", "rb")
    args.output = open("update/" + str(x) + "/crypt_" + ota_data_name + ".bin", "wb")
    args.plaintext_file = open("../build/" + ota_data_name + ".bin", "rb")
    args.aes_xts = False
    args.flash_crypt_conf = 15
    args.address = otadata_offset
    espsecure.encrypt_flash_data(args)

    args.plaintext_file.close()

    ###################################################################################################################

    args = Args()
    args.keyfile = open("data/" + str(x) + "/keys/flashkey.bin", "rb")
    args.output = open("update/" + str(x) + "/crypt_" + app_name + "0.bin", "wb")
    args.plaintext_file = open("temp/" + str(x) + "/signed_" + app_name + "0.bin", "rb")
    args.aes_xts = False
    args.flash_crypt_conf = 15
    args.address = app_offset0
    espsecure.encrypt_flash_data(args)

    args.plaintext_file.close()
    os.remove(args.plaintext_file.name)

    ###################################################################################################################
    args = Args()
    args.keyfile = open("data/" + str(x) + "/keys/flashkey.bin", "rb")
    args.output = open("update/" + str(x) + "/crypt_" + app_name + "1.bin", "wb")
    args.plaintext_file = open("temp/" + str(x) + "/signed_" + app_name + "1.bin", "rb")
    args.aes_xts = False
    args.flash_crypt_conf = 15
    args.address = app_offset1
    espsecure.encrypt_flash_data(args)

    args.plaintext_file.close()
    os.remove(args.plaintext_file.name)

    ###################################################################################################################

    os.rmdir("temp/" + str(x))
    os.rmdir("temp")

def flash():
    if len(sys.argv) > 1:
        port = sys.argv[1]
    else:
        port = "COM8"

    print("Serial port %s" % port)

    chip = "esp32"
    initial_baud = 115200
    trace = False
    before = 'default_reset'
    connect_attempts = 15

    chip_class = esptool._chip_to_rom_loader(chip)
    esp = chip_class(port, initial_baud, trace)
    esp.connect(before, connect_attempts)

    esp = esp.run_stub()
    esp.change_baud(921600)

    mac_addr = ':'.join(map('{:02x}'.format, esp.read_mac())).upper()
    print(mac_addr)

    x = mac_addr.replace(":", "")

    genkeys(x)
    sign(x)
    encrypt(x)

    file = open("data/" + str(x) + "/mac.txt", "w+")
    file.write(mac_addr)
    file.close()

    efuses, efuse_operations = espefuse.get_efuses(esp, do_not_confirm=True)

    if efuses['BLOCK1'].is_writeable():
        args = Args()
        args.block = ['flash_encryption']
        args.keyfile = [open("data/" + str(x) + "/keys/flashkey.bin", "rb")]
        args.force_write_always = 0
        args.no_protect_key = 0
        args.only_burn_at_end = False
        efuse_operations.burn_key(esp, efuses, args)

    if efuses['BLOCK2'].is_writeable():
        args = Args()
        args.keyfile = open("data/" + str(x) + "/keys/signkey.pem", "rb")
        args.no_protect_key = 0
        args.only_burn_at_end = False
        efuse_operations.burn_key_digest(esp, efuses, args)

    args = Args()
    args.name_value_pairs = {'FLASH_CRYPT_CNT': 1,
                             'DISABLE_DL_ENCRYPT': 1, 'DISABLE_DL_DECRYPT': 1, 'DISABLE_DL_CACHE': 1,
                             'JTAG_DISABLE': 1,
                             'ABS_DONE_1': 1, 'FLASH_CRYPT_CONFIG': 0xf}
    args.only_burn_at_end = False
    efuse_operations.burn_efuse(esp, efuses, args)

    args = Args()
    args.efuse_name = ['MAC', 'FLASH_CRYPT_CNT', 'DISABLE_DL_ENCRYPT', 'DISABLE_DL_DECRYPT', 'DISABLE_DL_CACHE',
                       'JTAG_DISABLE', 'ABS_DONE_1', 'FLASH_CRYPT_CONFIG', 'ABS_DONE_0', 'BLOCK1', 'BLOCK2']
    args.only_burn_at_end = False
    efuse_operations.write_protect_efuse(esp, efuses, args)

    args = Args()
    args.address = nvs_key_offset
    args.size = nvs_key_length
    esptool.erase_region(esp, args)

    args = Args()
    args.address = nvs_data_offset
    args.size = nvs_data_length
    esptool.erase_region(esp, args)

    args = Args()
    args.compress = True
    args.no_compress = False
    args.no_stub = False
    args.erase_all = False
    args.encrypt = False
    args.verify = False
    args.encrypt_files = None
    args.flash_size = 'keep'
    args.flash_mode = 'keep'
    args.flash_freq = 'keep'
    args.addr_filename = [
        (bootloader_offset, open("update/" + str(x) + "/crypt_" + bootloader_name + ".bin", "rb")),
        (partitions_offset, open("update/" + str(x) + "/crypt_" + partitions_name + ".bin", "rb")),
        (otadata_offset, open("update/" + str(x) + "/crypt_" + ota_data_name + ".bin", "rb")),
        (phydata_offset, open("update/" + str(x) + "/no_crypt_" + phydata_name + ".bin", "rb")),
        (app_offset0, open("update/" + str(x) + "/crypt_" + app_name + "0.bin", "rb"))]
    esptool.write_flash(esp, args)

    args = Args()
    args.format = 'summary'
    args.file = sys.stdout
    efuse_operations.summary(esp, efuses, args)

    esp.hard_reset()

    esp._port.close()

ESP code:

Code: Select all

class OTAUpdate {
    public:
        bool begin(size_t size);
        esp_err_t write(uint8_t *data, size_t len, size_t offset);
        bool end();
        bool reboot();
        const esp_partition_t* _partition;
};

Code: Select all

bool OTAUpdate::begin(size_t size) {
    _partition = esp_ota_get_next_update_partition(NULL);
    esp_err_t a = esp_partition_erase_range(_partition, 0, 0x200000);
    if(!_partition){
        Serial.println("NO PARTITION TO OTA");
        return false;
    } else {
        Serial.println("OTA Partition");
        Serial.println(_partition->label);
    }

    if(size > _partition->size) {
        Serial.println("PARTITION TO SMALL");
        return false;
    } else {
        Serial.println("PARTITION SIZE OK");
    }
    return true;
}

esp_err_t OTAUpdate::write(uint8_t *data, size_t len, size_t offset)  {
    esp_err_t b = esp_partition_write_raw(_partition, offset, data, len);

    return b;
}

bool OTAUpdate::end() {
    esp_ota_set_boot_partition(_partition);
    return true;
}

bool OTAUpdate::reboot() {
    ESP.restart();
    return true;
}
You need to add validations and other things to this code to safely upload. This is the minimum code that allows you to upload an encrypted file.

Check which partition esp32 is running from. Because the file for the ota0 and ota1 partitions are different. They are encrypted with a key shift.

Code: Select all

const esp_partition_t* partition = esp_ota_get_running_partition();
If you flash ota0 partition please send the file app_name0.bin for ota1 send file app_name1.bin.

stoumk
Posts: 11
Joined: Tue May 24, 2022 10:54 am

Re: OTA update with pre-encrypted file

Postby stoumk » Tue Jun 27, 2023 5:29 am

Thank you!
Can you send what your sdkconfig file looks like?

Who is online

Users browsing this forum: No registered users and 27 guests