Page 1 of 1

Flash Encryption - First boot and random key generated

Posted: Thu Apr 04, 2019 7:29 pm
by Berlese
Hello everybody :D ,

I have a very specific question, the company where I work is finishing a project and going to mass production.

The production is in China (and we are from another country), so the first firmware for the ESP32 will be pass in a factory on China by a test jig. After the first boot (yet at the factory) the calibration parameters will be recorded in the flash (by NVS).

And for security purposes we need to encrypt the flash and disable "UART Bootloader Encryption/Decryption", as described here: Flash Encryption Initialisation

Ok, this works fine, but now comes the question, how can I ensure that the Flash Encryption Key stored in the eFUSE (Block 1) was randomly generated (by the inner firmware)?

Why this questions?
Well, "assuming a situation where the manufacturer may have bad intentions" (we don't expect this, but is always good to be 100% safe), they can use the follow commands:

Code: Select all

python $IDF_PATH/components/esptool_py/esptool/espsecure.py generate_flash_encryption_key flash_encryption_key.bin
python $IDF_PATH/components/esptool_py/esptool/espefuse.py burn_key flash_encryption flash_encryption_key.bin
And by doing this, they can have the key to decrypt our product.

So, digging inside the esp-idf and flash encryption I found this lib: bootloader_utility.c
And in this lib (that makes part of the bootloader) I found the part that checks the flash encryption:

Code: Select all

#ifdef CONFIG_FLASH_ENCRYPTION_ENABLED
    /* encrypt flash */
    ESP_LOGI(TAG, "Checking flash encryption...");
    bool flash_encryption_enabled = esp_flash_encryption_enabled();
    err = esp_flash_encrypt_check_and_update();
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Flash encryption check failed (%d).", err);
        return;
    }

    if (!flash_encryption_enabled && esp_flash_encryption_enabled()) {
        /* Flash encryption was just enabled for the first time,
           so issue a system reset to ensure flash encryption
           cache resets properly */
        ESP_LOGI(TAG, "Resetting with flash encryption enabled...");
        bootloader_reset();
    }
#endif
I was thinking about inject some code here (to certify that the key was randomly generated), or maybe don't encrypt the code by the bootloader (not enabling the flash encryption in "make menuconfig") and doing the encryption through OTA and using the follow libs:
flash_encrypt.c
esp_flash_encrypt.h

So I'm forgetting something? Have another way or a eFuse that can help me with this question?

I hope that I have been able to explain the entire situation! ;)

Re: Flash Encryption - First boot and random key generated

Posted: Fri Apr 05, 2019 12:34 am
by ESP_Angus
Hi Berlese,

Thanks for carefully explaining your situation.

Is there any part of the production process that you can trust, for the purposes of enabling flash encryption correctly? It sounds like you're not giving your production firmware to the third party factory, instead they're flashing a test firmware and then production firmware is flashed at a second trusted location. Is that right?

Option 1:

If this is the case then the very simplest option is to completely re-flash the device via serial at the second location, with new bootloader.bin and firmware. Then it will reset to enable flash encryption. But this may be unnecessarily fiddly due to the need to flash via serial.

Option 2:

If you want to do this second stage via OTA only, and you have a controlled environment (no risk of power failures or other interruptions), then I would recommend writing a custom routine to also download and overwrite the factory bootloader.bin with a whole new bootloader.bin that enables flash encryption, and then the device resets and encrypts itself during a "second first boot".

Option 3:

If Option 2 isn't viable for some reason then you can probably do something like:
  • Patch bootloader to skip the "enable flash encryption" steps if there is no flash encryption key set in efuse yet, so it keeps behaving like the plaintext bootloader until there is a key set.
  • The "trusted" OTA updated app can boot, check no flash encryption key is already set in efuse (ie all bits are zero and read/write protection is disabled), generate a new flash encryption key and it write to efuse, read and write protect the new key and then reboot.
  • The bootloader will then see there is a key and enable the default "first boot" flash encryption logic, including encrypting the flash contents.
This approach still needs a controlled environment though, as a power failure during this initial "encrypt itself" step will leave the device bricked.

Option 4:
doing the encryption through OTA and using the follow libs:
It'd be technically possible but very difficult to enable encryption while running in the app (not the bootloader). The steps would need to be something like:
  • Verify flash encryption is off, generate new key, write to efuse and set other config efuses.
  • OTA update a new app into a different app slot, encrypting the data as it is written there.
  • Erase and overwrite the ota_data partition with encrypted data that points to this new encrypted app partition.
  • Encrypt the contents of bootloader.bin, in place. A failure at this point would brick the device (serial recovery).
  • Verify all code from here on is running from IRAM not the flash cache, set the FLASH_CRYPT_CNT efuse, then reset immediately. A failure before these steps were complete would also brick the device (serial recovery).
I don't recommend doing this one, although it's an interesting idea for us to support it in ESP-IDF in the future.

Re: Flash Encryption - First boot and random key generated

Posted: Fri Apr 05, 2019 2:39 pm
by Berlese
Hi ESP_Angus! :D

I have no words to thank you!
Your answer was amazing and very completely.

Is there any part of the production process that you can trust, for the purposes of enabling flash encryption correctly?
No, not exactly. :?

It sounds like you're not giving your production firmware to the third party factory, instead they're flashing a test firmware and then production firmware is flashed at a second trusted location. Is that right?
Yes, it exactly that, you understood very well the situation.

Option 3:

If Option 2 isn't viable for some reason then you can probably do something like:
  • Patch bootloader to skip the "enable flash encryption" steps if there is no flash encryption key set in efuse yet, so it keeps behaving like the plaintext bootloader until there is a key set.
  • The "trusted" OTA updated app can boot, check no flash encryption key is already set in efuse (ie all bits are zero and read/write protection is disabled), generate a new flash encryption key and it write to efuse, read and write protect the new key and then reboot.
  • The bootloader will then see there is a key and enable the default "first boot" flash encryption logic, including encrypting the flash contents.
This approach still needs a controlled environment though, as a power failure during this initial "encrypt itself" step will leave the device bricked.
This option looks great.

Again thanks for carefully answer my question, helped a lot! ;)