From b5de58139919e1143f289efc426569bb2d111106 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 3 Nov 2016 17:33:30 +1100 Subject: [PATCH] Secure boot: initial image signature support --- .gitmodules | 3 + components/bootloader/Kconfig.projbuild | 16 +++ components/bootloader/Makefile.projbuild | 24 +++-- components/bootloader/src/Makefile | 2 +- .../bootloader/src/main/bootloader_start.c | 25 +++++ .../bootloader_support/Makefile.projbuild | 3 + components/bootloader_support/component.mk | 25 +++++ .../include/esp_secure_boot.h | 10 +- .../src/secure_boot_signatures.c | 99 +++++++++++++++++++ components/esptool_py/Makefile.projbuild | 6 ++ components/esptool_py/esptool | 2 +- components/micro-ecc/component.mk | 8 ++ components/micro-ecc/micro-ecc | 1 + components/partition_table/Makefile.projbuild | 3 + docs/security/secure-boot.rst | 87 +++++++++------- make/common.mk | 20 +++- make/component_common.mk | 6 +- make/project.mk | 1 + 18 files changed, 291 insertions(+), 50 deletions(-) create mode 100644 components/bootloader_support/Makefile.projbuild create mode 100644 components/bootloader_support/src/secure_boot_signatures.c create mode 100644 components/micro-ecc/component.mk create mode 160000 components/micro-ecc/micro-ecc diff --git a/.gitmodules b/.gitmodules index df4084826..c26f92e80 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "components/bt/lib"] path = components/bt/lib url = https://github.com/espressif/esp32-bt-lib.git +[submodule "components/micro-ecc/micro-ecc"] + path = components/micro-ecc/micro-ecc + url = https://github.com/kmackay/micro-ecc.git diff --git a/components/bootloader/Kconfig.projbuild b/components/bootloader/Kconfig.projbuild index 55c2eebd1..b568f61a0 100644 --- a/components/bootloader/Kconfig.projbuild +++ b/components/bootloader/Kconfig.projbuild @@ -77,6 +77,22 @@ config SECURE_BOOTLOADER_KEY_FILE See docs/security/secure-boot.rst for details. +config SECURE_BOOT_SIGNING_KEY + string "Secure boot signing key" + depends on SECURE_BOOTLOADER_ENABLED + default secure_boot_signing_key.pem + help + Path to the key file used to sign partition tables and app images for secure boot. + + Key file is an ECDSA private key (NIST256p curve) in PEM format. + + Path is evaluated relative to the project directory. + + You can generate a new signing key by running the following command: + espsecure.py generate_signing_key secure_boot_signing_key.pem + + See docs/security/secure-boot.rst for details. + config SECURE_BOOTLOADER_ENABLED bool default SECURE_BOOTLOADER_ONE_TIME_FLASH || SECURE_BOOTLOADER_REFLASHABLE diff --git a/components/bootloader/Makefile.projbuild b/components/bootloader/Makefile.projbuild index 3799e913c..831d98858 100644 --- a/components/bootloader/Makefile.projbuild +++ b/components/bootloader/Makefile.projbuild @@ -15,13 +15,17 @@ BOOTLOADER_BUILD_DIR=$(abspath $(BUILD_DIR_BASE)/bootloader) BOOTLOADER_BIN=$(BOOTLOADER_BUILD_DIR)/bootloader.bin BOOTLOADER_SDKCONFIG=$(BOOTLOADER_BUILD_DIR)/sdkconfig -SECURE_BOOT_KEYFILE=$(abspath $(call dequote,$(CONFIG_SECURE_BOOTLOADER_KEY_FILE))) +# both signing key paths are resolved relative to the project directory +SECURE_BOOTLOADER_KEY=$(abspath $(call dequote,$(CONFIG_SECURE_BOOTLOADER_KEY_FILE))) +SECURE_BOOT_SIGNING_KEY=$(abspath $(call dequote,$(CONFIG_SECURE_BOOT_SIGNING_KEY))) +export SECURE_BOOT_SIGNING_KEY # used by bootloader_support component # Custom recursive make for bootloader sub-project BOOTLOADER_MAKE=+$(MAKE) -C $(BOOTLOADER_COMPONENT_PATH)/src \ V=$(V) SDKCONFIG=$(BOOTLOADER_SDKCONFIG) \ BUILD_DIR_BASE=$(BOOTLOADER_BUILD_DIR) IS_BOOTLOADER_BUILD=1 + .PHONY: bootloader-clean bootloader-flash bootloader $(BOOTLOADER_BIN) $(BOOTLOADER_BIN): | $(BOOTLOADER_BUILD_DIR)/sdkconfig @@ -59,16 +63,15 @@ bootloader: $(BOOTLOADER_BIN) @echo "* IMPORTANT: After first boot, BOOTLOADER CANNOT BE RE-FLASHED on same device" else ifdef CONFIG_SECURE_BOOTLOADER_REFLASHABLE -# Reflashable secure bootloader (recommended for testing only) -# generates a digest binary (bootloader + digest) and prints -# instructions for reflashing. User must run commands manually. +# Reflashable secure bootloader +# generates a digest binary (bootloader + digest) BOOTLOADER_DIGEST_BIN=$(BOOTLOADER_BUILD_DIR)/bootloader-reflash-digest.bin bootloader: $(BOOTLOADER_DIGEST_BIN) @echo $(SEPARATOR) @echo "Bootloader built and secure digest generated. First time flash command is:" - @echo "$(ESPEFUSEPY) burn_key secure_boot $(SECURE_BOOT_KEYFILE)" + @echo "$(ESPEFUSEPY) burn_key secure_boot $(SECURE_BOOTLOADER_KEY)" @echo "$(ESPTOOLPY_WRITE_FLASH) 0x1000 $(BOOTLOADER_BIN)" @echo $(SEPARATOR) @echo "To reflash the bootloader after initial flash:" @@ -77,15 +80,16 @@ bootloader: $(BOOTLOADER_DIGEST_BIN) @echo "* After first boot, only re-flashes of this kind (with same key) will be accepted." @echo "* Not recommended to re-use the same secure boot keyfile on multiple production devices." -$(BOOTLOADER_DIGEST_BIN): $(BOOTLOADER_BIN) $(SECURE_BOOT_KEYFILE) - @echo "DIGEST $< + $(SECURE_BOOT_KEYFILE) -> $@" - $(Q) $(ESPSECUREPY) digest_secure_bootloader -k $(SECURE_BOOT_KEYFILE) -o $@ $< +$(BOOTLOADER_DIGEST_BIN): $(BOOTLOADER_BIN) $(SECURE_BOOTLOADER_KEY) + @echo "DIGEST $(notdir $@)" + $(Q) $(ESPSECUREPY) digest_secure_bootloader -k $(SECURE_BOOTLOADER_KEY) -o $@ $< -$(SECURE_BOOT_KEYFILE): +$(SECURE_BOOTLOADER_KEY): @echo $(SEPARATOR) - @echo "Need to generate secure boot digest key first. Run following command:" + @echo "Need to generate secure boot signing key. Run following command:" @echo "$(ESPSECUREPY) generate_key $@" @echo "Keep key file safe after generating." + @echo "(See secure boot documentation for caveats & alternatives.)") @exit 1 else diff --git a/components/bootloader/src/Makefile b/components/bootloader/src/Makefile index 278e0e9af..1cfc80c5f 100644 --- a/components/bootloader/src/Makefile +++ b/components/bootloader/src/Makefile @@ -4,7 +4,7 @@ # PROJECT_NAME := bootloader -COMPONENTS := esptool_py bootloader bootloader_support log spi_flash +COMPONENTS := esptool_py bootloader bootloader_support log spi_flash micro-ecc # The bootloader pseudo-component is also included in this build, for its Kconfig.projbuild to be included. # diff --git a/components/bootloader/src/main/bootloader_start.c b/components/bootloader/src/main/bootloader_start.c index 59b180fd8..3ebb8cf52 100644 --- a/components/bootloader/src/main/bootloader_start.c +++ b/components/bootloader/src/main/bootloader_start.c @@ -110,6 +110,7 @@ void IRAM_ATTR call_start_cpu0() */ bool load_partition_table(bootloader_state_t* bs, uint32_t addr) { + esp_err_t err; const esp_partition_info_t *partitions; const int PARTITION_TABLE_SIZE = 0x1000; const int MAX_PARTITIONS = PARTITION_TABLE_SIZE / sizeof(esp_partition_info_t); @@ -118,6 +119,14 @@ bool load_partition_table(bootloader_state_t* bs, uint32_t addr) ESP_LOGI(TAG, "Partition Table:"); ESP_LOGI(TAG, "## Label Usage Type ST Offset Length"); + if(esp_secure_boot_enabled()) { + err = esp_secure_boot_verify_signature(addr, 0x1000); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to verify partition table signature."); + return false; + } + } + partitions = bootloader_mmap(addr, 0x1000); if (!partitions) { ESP_LOGE(TAG, "bootloader_mmap(0x%x, 0x%x) failed", addr, 0x1000); @@ -334,7 +343,23 @@ void bootloader_main() static void unpack_load_app(const esp_partition_pos_t* partition) { + esp_err_t err; esp_image_header_t image_header; + uint32_t image_length; + + if (esp_secure_boot_enabled()) { + /* TODO: verify the app image as part of OTA boot decision, so can have fallbacks */ + err = esp_image_basic_verify(partition->offset, &image_length); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to verify app image @ 0x%x (%d)", partition->offset, err); + return; + } + err = esp_secure_boot_verify_signature(partition->offset, image_length); + if (err != ESP_OK) { + ESP_LOGE(TAG, "App image @ 0x%x failed signature verification (%d)", partition->offset, err); + return; + } + } if (esp_image_load_header(partition->offset, &image_header) != ESP_OK) { ESP_LOGE(TAG, "Failed to load app image header @ 0x%x", partition->offset); diff --git a/components/bootloader_support/Makefile.projbuild b/components/bootloader_support/Makefile.projbuild new file mode 100644 index 000000000..f93967a71 --- /dev/null +++ b/components/bootloader_support/Makefile.projbuild @@ -0,0 +1,3 @@ +# projbuild file for bootloader support +# (included in bootloader & main app) + diff --git a/components/bootloader_support/component.mk b/components/bootloader_support/component.mk index 2988fe287..7f546dcba 100755 --- a/components/bootloader_support/component.mk +++ b/components/bootloader_support/component.mk @@ -1,11 +1,36 @@ COMPONENT_ADD_INCLUDEDIRS := include COMPONENT_PRIV_INCLUDEDIRS := include_priv +# include configuration macros early +include $(IDF_PATH)/make/common.mk + ifdef IS_BOOTLOADER_BUILD # share "private" headers with the bootloader component +# eventual goal: all functionality that needs this lives in bootloader_support COMPONENT_ADD_INCLUDEDIRS += include_priv endif COMPONENT_SRCDIRS := src +# +# Secure boot signing key support +# +ifdef CONFIG_SECURE_BOOTLOADER_ENABLED + +SECURE_BOOT_VERIFICATION_KEY := $(abspath signature_verification_key.bin) + +COMPONENT_EMBED_FILES := $(SECURE_BOOT_VERIFICATION_KEY) + +$(SECURE_BOOT_SIGNING_KEY): + @echo "Need to generate secure boot signing key." + @echo "One way is to run this command:" + @echo "$(ESPSECUREPY) generate_signing_key $@" + @echo "Keep key file safe after generating." + @echo "(See secure boot documentation for risks & alternatives.)" + @exit 1 + +$(SECURE_BOOT_VERIFICATION_KEY): $(SECURE_BOOT_SIGNING_KEY) + $(ESPSECUREPY) extract_public_key --keyfile $< $@ +endif + include $(IDF_PATH)/make/component_common.mk diff --git a/components/bootloader_support/include/esp_secure_boot.h b/components/bootloader_support/include/esp_secure_boot.h index 4bf2dc8b2..c1c017151 100644 --- a/components/bootloader_support/include/esp_secure_boot.h +++ b/components/bootloader_support/include/esp_secure_boot.h @@ -60,6 +60,14 @@ static inline bool esp_secure_boot_enabled(void) { */ esp_err_t esp_secure_boot_permanently_enable(void); - +/** @brief Verify the signature appended to some binary data in flash. + * + * @param src_addr Starting offset of the data in flash. + * @param length Length of data in bytes. Signature is appended -after- length bytes. + * + * @return ESP_OK if signature is valid, ESP_ERR_INVALID_STATE if + * signature fails, ESP_FAIL for other failures (ie can't read flash). + */ +esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length); #endif diff --git a/components/bootloader_support/src/secure_boot_signatures.c b/components/bootloader_support/src/secure_boot_signatures.c new file mode 100644 index 000000000..5f02de38b --- /dev/null +++ b/components/bootloader_support/src/secure_boot_signatures.c @@ -0,0 +1,99 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "sdkconfig.h" + +#include "bootloader_flash.h" +#include "esp_log.h" +#include "esp_image_format.h" +#include "esp_secure_boot.h" + +#include "uECC.h" + +#ifdef BOOTLOADER_BUILD +#include "rom/sha.h" +typedef SHA_CTX sha_context; +#else +#include "hwcrypto/sha.h" +typedef esp_sha_context sha_context; +#endif + +typedef struct { + uint32_t version; + uint8_t signature[64]; +} signature_block_t; + +static const char* TAG = "secure_boot"; + +extern const uint8_t signature_verification_key_start[] asm("_binary_signature_verification_key_bin_start"); +extern const uint8_t signature_verification_key_end[] asm("_binary_signature_verification_key_bin_end"); + +#define SIGNATURE_VERIFICATION_KEYLEN 64 + +esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) +{ + sha_context sha; + uint8_t digest[64]; + ptrdiff_t keylen; + const uint8_t *data; + const signature_block_t *sigblock; + bool is_valid; + + ESP_LOGD(TAG, "verifying signature src_addr 0x%x length 0x%x", src_addr, length); + + data = bootloader_mmap(src_addr, length + sizeof(signature_block_t)); + if(data == NULL) { + ESP_LOGE(TAG, "bootloader_mmap(0x%x, 0x%x) failed", src_addr, length+sizeof(signature_block_t)); + return ESP_FAIL; + } + + sigblock = (const signature_block_t *)(data + length); + + if (sigblock->version != 0) { + ESP_LOGE(TAG, "src 0x%x has invalid signature version field 0x%08x", src_addr, sigblock->version); + goto unmap_and_fail; + } + +#ifdef BOOTLOADER_BUILD + /* Use ROM SHA functions directly */ + ets_sha_enable(); + ets_sha_init(&sha); + ets_sha_update(&sha, SHA2_512, data, length); + ets_sha_finish(&sha, SHA2_512, digest); + ets_sha_disable(); +#else + /* Use thread-safe esp-idf SHA layer */ + esp_sha512_init(&sha); + esp_sha512_start(&sha, false); + esp_sha512_update(&sha, data, length); + esp_sha512_finish(&sha, digest); + esp_sha512_free(&sha); +#endif + + keylen = signature_verification_key_end - signature_verification_key_start; + if(keylen != SIGNATURE_VERIFICATION_KEYLEN) { + ESP_LOGE(TAG, "Embedded public verification key has wrong length %d", keylen); + goto unmap_and_fail; + } + + is_valid = uECC_verify(signature_verification_key_start, + digest, sizeof(digest), sigblock->signature, + uECC_secp256r1()); + + bootloader_unmap(data); + return is_valid ? ESP_OK : ESP_ERR_IMAGE_INVALID; + + unmap_and_fail: + bootloader_unmap(data); + return ESP_FAIL; +} diff --git a/components/esptool_py/Makefile.projbuild b/components/esptool_py/Makefile.projbuild index dfb9dfc4c..5807512b8 100644 --- a/components/esptool_py/Makefile.projbuild +++ b/components/esptool_py/Makefile.projbuild @@ -18,6 +18,7 @@ ESPTOOLPY_SERIAL := $(ESPTOOLPY) --port $(ESPPORT) --baud $(ESPBAUD) # Supporting esptool command line tools ESPEFUSEPY := $(PYTHON) $(COMPONENT_PATH)/esptool/espefuse.py ESPSECUREPY := $(PYTHON) $(COMPONENT_PATH)/esptool/espsecure.py +export ESPSECUREPY # is used in bootloader_support component ESPTOOL_FLASH_OPTIONS := --flash_mode $(ESPFLASHMODE) --flash_freq $(ESPFLASHFREQ) --flash_size $(ESPFLASHSIZE) @@ -33,6 +34,11 @@ ESPTOOL_ALL_FLASH_ARGS += $(CONFIG_APP_OFFSET) $(APP_BIN) $(APP_BIN): $(APP_ELF) $(ESPTOOLPY_SRC) $(Q) $(ESPTOOLPY) elf2image $(ESPTOOL_FLASH_OPTIONS) $(ESPTOOL_ELF2IMAGE_OPTIONS) -o $@ $< +ifdef CONFIG_SECURE_BOOTLOADER_ENABLED +ifndef IS_BOOTLOADER_BUILD + $(Q) $(ESPSECUREPY) sign_data --keyfile $(SECURE_BOOT_SIGNING_KEY) $@ # signed in-place +endif +endif flash: all_binaries $(ESPTOOLPY_SRC) @echo "Flashing binaries to serial port $(ESPPORT) (app at offset $(CONFIG_APP_OFFSET))..." diff --git a/components/esptool_py/esptool b/components/esptool_py/esptool index 95dae1651..68ed7c7a4 160000 --- a/components/esptool_py/esptool +++ b/components/esptool_py/esptool @@ -1 +1 @@ -Subproject commit 95dae1651e5aea1adb2b6018b23f65a305f67387 +Subproject commit 68ed7c7a4e4409899f10dddda1e02b20e5cb32f0 diff --git a/components/micro-ecc/component.mk b/components/micro-ecc/component.mk new file mode 100644 index 000000000..df29f400d --- /dev/null +++ b/components/micro-ecc/component.mk @@ -0,0 +1,8 @@ +# only compile the micro-ecc/uECC.c source file +# (SRCDIRS is needed so build system can find the source file) +COMPONENT_SRCDIRS := micro-ecc +COMPONENT_OBJS := micro-ecc/uECC.o + +COMPONENT_ADD_INCLUDEDIRS := micro-ecc + +include $(IDF_PATH)/make/component_common.mk diff --git a/components/micro-ecc/micro-ecc b/components/micro-ecc/micro-ecc new file mode 160000 index 000000000..14222e062 --- /dev/null +++ b/components/micro-ecc/micro-ecc @@ -0,0 +1 @@ +Subproject commit 14222e062d77f45321676e813d9525f32a88e8fa diff --git a/components/partition_table/Makefile.projbuild b/components/partition_table/Makefile.projbuild index 98631cc85..e6f3bce8f 100644 --- a/components/partition_table/Makefile.projbuild +++ b/components/partition_table/Makefile.projbuild @@ -21,6 +21,9 @@ PARTITION_TABLE_BIN := $(BUILD_DIR_BASE)/$(notdir $(PARTITION_TABLE_CSV_PATH:.cs $(PARTITION_TABLE_BIN): $(PARTITION_TABLE_CSV_PATH) @echo "Building partitions from $(PARTITION_TABLE_CSV_PATH)..." $(Q) $(GEN_ESP32PART) $< $@ +ifdef CONFIG_SECURE_BOOTLOADER_ENABLED + $(Q) $(ESPSECUREPY) sign_data --keyfile $(SECURE_BOOT_SIGNING_KEY) $@ # signed in-place +endif all_binaries: $(PARTITION_TABLE_BIN) diff --git a/docs/security/secure-boot.rst b/docs/security/secure-boot.rst index 5e33367b4..169f2d56b 100644 --- a/docs/security/secure-boot.rst +++ b/docs/security/secure-boot.rst @@ -8,9 +8,9 @@ Secure Boot is separate from the Encrypted Flash feature, and you can use secure Background ---------- -- Most data is stored in flash. Flash access does not need to protected for secure boot to function, because critical data is stored in Efuses internal to the chip. +- Most data is stored in flash. Flash access does not need to be protected from physical access in order for secure boot to function, because critical data is stored in Efuses internal to the chip. -- Efuses are used to store the secure bootloader key (in efuse block 2), and also a single Efuse (ABS_DONE_0) is burned (written to 1) to permanently enable secure boot on the chip. For more details about efuse, see the (forthcoming) chapter in the Technical Reference Manual. +- Efuses are used to store the secure bootloader key (in efuse block 2), and also a single Efuse bit (ABS_DONE_0) is burned (written to 1) to permanently enable secure boot on the chip. For more details about efuse, see the (forthcoming) chapter in the Technical Reference Manual. - To understand the secure boot process, first familiarise yourself with the standard `esp-idf boot process`. @@ -21,57 +21,62 @@ This is a high level overview of the secure boot process. Step by step instructi 1. The options to enable secure boot are provided in the ``make menuconfig`` hierarchy, under "Bootloader Config". -2. Bootloader Config includes the path to a ECDSA public key. This "image public key" is compiled into the software bootloader. +2. Bootloader Config includes the path to a secure boot signing key. This is a ECDSA public/private key pair in a PEM format file. -2. The software bootloader image is built by esp-idf with the public key embedded, and with a secure boot flag set in its header. This software bootloader image is flashed at offset 0x1000. +2. The software bootloader image is built by esp-idf with the public key (signature verification) portion of the secure boot signing key compiled in, and with a secure boot flag set in its header. This software bootloader image is flashed at offset 0x1000. 3. On first boot, the software bootloader tests the secure boot flag. If it is set, the following process is followed to enable secure boot: - - Hardware secure boot support generates a device secure bootloader key (stored read/write protected in efuse), and a secure digest. The digest is derived from the key, an IV, and the bootloader image contents. + - Hardware secure boot support generates a device secure bootloader key (generated via hardware RNG, then stored read/write protected in efuse), and a secure digest. The digest is derived from the key, an IV, and the bootloader image contents. - The secure digest is flashed at offset 0x0 in the flash. - - Bootloader permanently enables secure boot by burning the ABS_DONE_0 efuse. The software bootloader then becomes protected (the chip will only boot this bootloader image.) + - Bootloader permanently enables secure boot by burning the ABS_DONE_0 efuse. The software bootloader then becomes protected (the chip will only boot a bootloader image if the digest matches.) - Bootloader also disables JTAG via efuse. -4. On subsequent boots the ROM bootloader sees that the secure boot efuse is burned, reads the saved digest at 0x0 and uses hardware secure boot support to compare it with a newly calculated digest. If the digest does not match then booting will not continue. +4. On subsequent boots the ROM bootloader sees that the secure boot efuse is burned, reads the saved digest at 0x0 and uses hardware secure boot support to compare it with a newly calculated digest. If the digest does not match then booting will not continue. The digest and comparison are performed entirely by hardware, for technical details see `Hardware Secure Boot Support`. -5. When running in secure boot mode, the software bootloader uses the image public key (embedded in the bootloader itself) to verify all subsequent partition tables and app images before they are booted. +5. When running in secure boot mode, the software bootloader uses the secure boot signing key's public key (embedded in the bootloader itself, and therefore validated as part of the bootloader digest) to verify all subsequent partition tables and app images before they are booted. Keys ---- The following keys are used by the secure boot process: -- "secure bootloader key" is a 256-bit AES key that is stored in Efuse block 2. The bootloader can generate this key itself from hardware, the user does not need to supply it. The Efuse holding this key must be read & write protected (preventing software access) before secure boot is enabled. +- "secure bootloader key" is a 256-bit AES key that is stored in Efuse block 2. The bootloader can generate this key itself from a hardware random number generation, the user does not need to supply it (it is optionally possible to supply this key, see `Re-Flashable Software Bootloader`). The Efuse holding this key is read & write protected (preventing software access) before secure boot is enabled. -- "image public key" is a ECDSA public key (curve TBD) in format TBD. This public key is flashed into the software bootloader and used to verify the remaining parts of the flash (partition table, app image) before booting continues. The public key can be freely distributed, it does not need to be kept secret. +- "secure boot signing key" is a standard ECDSA public/private key pair (NIST256p aka prime256v1 curve) in PEM format. + + - The public key from this key pair (for signature verificaton but not signature creation) is compiled into the software bootloader and used to verify the second stage of booting (partition table, app image) before booting continues. The public key can be freely distributed, it does not need to be kept secret. + + - The private key from this key pair *must be securely kept private*, as anyone who has this key can authenticate to a bootloader with secure boot using the matching public key. -- "image private key" is a ECDSA private key, a matching pair with "image public key". This private key is used to sign partition tables and app images for the secure boot process. **This private key must be securely kept private** as anyone who has this key can authenticate to the secure boot process. It is acceptable to use the same private key across multiple production devices. How To Enable Secure Boot ------------------------- -1. Run ``make menuconfig``, navigate to "Bootloader Config" -> "Secure Boot" and select the option "One-time Flash". (For the alternative "Reflashable" choice, see `Re-Flashable Software Bootloader`.) +1. Run ``make menuconfig``, navigate to "Bootloader Config" -> "Secure Boot" and select the option "One-time Flash". (To understand the alternative "Reflashable" choice, see `Re-Flashable Software Bootloader`.) -2. Select names for public & private image key files "Image Public Key File" and "Image Private Key File". These options will appear after secure boot is enabled. The files can be anywhere on your system. Relative paths are evaluated from the project directory. The files don't need to exist yet. +2. Select a name for the secure boot signing key. This option will appear after secure boot is enabled. The file can be anywhere on your system. A relative path will be evaluated from the project directory. The file does not need to exist yet. 3. Set other config options (as desired). Pay particular attention to the "Bootloader Config" options, as you can only flash the bootloader once. Then exit menuconfig and save your configuration -4. Run ``make generate_image_keypair`` to generate an image public key and a matching image private key. +4. The first time you run ``make``, if the signing key is not found then an error message will be printed with a command to generate a signing key via ``espsecure.py generate_signing_key``. - **IMPORTANT** The key pair is generated using the best random number source available via the OS and its Python installation (`/dev/urandom` on OSX/Linux and `CryptGenRandom()` on Windows). If this random number source is weak, then the private key will be weak. + **IMPORTANT** A signing key genereated this way will use the best random number source available to the OS and its Python installation (`/dev/urandom` on OSX/Linux and `CryptGenRandom()` on Windows). If this random number source is weak, then the private key will be weak. + + **IMPORTANT** For production environments, we recommend generating the keypair using openssl or another industry standard encryption program. See `Generating Secure Boot Signing Key` for more details. 5. Run ``make bootloader`` to build a secure boot enabled bootloader. The output of `make` will include a prompt for a flashing command, using `esptool.py write_flash`. -6. When you're ready to flash the bootloader, run the specified command (you have to enter it yourself, this step is not automated) and then wait for flashing to complete. **Remember this is a one time flash, you can't change the bootloader after this!**. +6. When you're ready to flash the bootloader, run the specified command (you have to enter it yourself, this step is not performed by make) and then wait for flashing to complete. **Remember this is a one time flash, you can't change the bootloader after this!**. -7. Run `make flash` to build and flash the partition table and the just-built app image. The app image will be signed using the private key you generated in step 4. +7. Run `make flash` to build and flash the partition table and the just-built app image. The app image will be signed using the signing key you generated in step 4. *NOTE*: `make flash` doesn't flash the bootloader if secure boot is enabled. 8. Reset the ESP32 and it will boot the software bootloader you flashed. The software bootloader will enable secure boot on the chip, and then it verifies the app image signature and boots the app. You should watch the serial console output from the ESP32 to verify that secure boot is enabled and no errors have occured due to the build configuration. -**IMPORTANT** Secure boot won't ever be enabled until after a valid partition table and app image have been flashed. This is to prevent accidents before the system is fully configured. +*NOTE* Secure boot won't be enabled until after a valid partition table and app image have been flashed. This is to prevent accidents before the system is fully configured. -9. On subsequent boots, the secure boot hardware will verify the software bootloader (using the secure bootloader key) and then the software bootloader will verify the partition table and app image (using the image public key). +9. On subsequent boots, the secure boot hardware will verify the software bootloader (using the secure bootloader key) and then the software bootloader will verify the partition table and app image (using the signing key). Re-Flashable Software Bootloader -------------------------------- @@ -80,19 +85,34 @@ The "Secure Boot: One-Time Flash" is the recommended software bootloader configu However, an alternative mode "Secure Boot: Reflashable" is also available. This mode allows you to supply a 256-bit key file that is used for the secure bootloader key. As you have the key file, you can generate new bootloader images and secure boot digests for them. -*NOTE*: Although it's possible, we strongly recommend not generating one secure boot key and flashing it to every device in a production environment. +In the esp-idf build process, this 256-bit key file is derived from the app signing key generated during the generate_signing_key step above. The private key's SHA-256 value is used to generate an 256-bit value which is then burned to efuse and used to protect the bootloader. This is a convenience so you only need to generate/protect a single private key. + +*NOTE*: Although it's possible, we strongly recommend not generating one secure boot key and flashing it to every device in a production environment. The "One-Time Flash" option is recommended for production environments. + +To enable a reflashable bootloader: 1. In the ``make menuconfig`` step, select "Bootloader Config" -> "Secure Boot" -> "Reflashable". -2. Select a name for the "Secure bootloader key file". The file can be anywhere on your system, and does not have to exist yet. A path is evaluated relative to the project directory. The file doesn't have to exist yet. +2. Follow the steps shown above to choose a signing key file, and generate the key file. -3. The first time you run ``make bootloader``, the system will prompt you with a ``espsecure.py generate_key`` command that can be used to generate the secure bootloader key. +3. Run ``make bootloader``. A 256-bit key file will be created, derived from the private key you generated for signing. Two sets of flashing steps will be printed - the first set of steps includes an ``espefuse.py burn_key`` command which is used to write the derived key to efuse. (Flashing this key is a one-time-only process.) The second set of steps can be used to reflash the bootloader with a pre-generated digest (generated during the build process, using the derived key). - **IMPORTANT** The new key is generated using the best random number source available via the OS and its Python installation (`/dev/urandom` on OSX/Linux and `CryptGenRandom()` on Windows). If this random number source is weak, then the secure bootloader key will be weak. +4. Resume from `Step 6` of the one-time process, to flash the bootloader and enable secure boot. Watch the console log output closely to ensure there were no errors in the secure boot configuration. -4. Run ``make bootloader`` again. Two sets of flashing steps will be printed - the first set of steps includes an ``espefuse.py burn_key`` command which is used to write the secure bootloader key to efuse. (Flashing this key is a one-time-only process.) The second set of steps can be used to reflash the bootloader with a pre-generated digest (generated during the build process, using the secure bootloader key file). +Generating Secure Boot Signing Key +---------------------------------- -5. Resume from `Step 6` of the one-time process, to flash the bootloader and enable secure boot. Watch the console log output closely to ensure there were no errors in the secure boot configuration. +The build system will prompt you with a command to generate a new signing key via ``espsecure.py generate_signing_key``. This uses the python-ecdsa library, which in turn uses Python's os.urandom() as a random number source. + +The strength of the signing key is proportional to (a) the random number source of the system, and (b) the correctness of the algorithm used. For production devices, we recommend generating signing keys from a system with a quality entropy source, and using the best available EC key generation utilities. + +For example, to generate a signing key using the openssl command line: + +``` +openssl ecparam -name prime256v1 -genkey -noout -out my_secure_boot_signing_key.pem +``` + +Remember that the strength of the secure boot system depends on keeping the signing key private. Technical Details @@ -107,9 +127,9 @@ The Secure Boot support hardware can perform three basic operations: 1. Generate a random sequence of bytes from a hardware random number generator. -2. Generate a digest from data (usually the bootloader image from flash) using a key stored in Efuse block 2. The key in Efuse can (& should) be read/write protected, which prevents software access. For full details of this algorithm see `Secure Bootloader Digest Algorithm`. The digest can only be read from hardware if Efuse ABS_DONE_0 is *not* burned (ie still 0), to prevent new digests from being calculated on the device after secure boot is enabled. +2. Generate a digest from data (usually the bootloader image from flash) using a key stored in Efuse block 2. The key in Efuse can (& should) be read/write protected, which prevents software access. For full details of this algorithm see `Secure Bootloader Digest Algorithm`. The digest can only be read back by software if Efuse ABS_DONE_0 is *not* burned (ie still 0). -3. Verify a digest from data (usually the bootloader image from flash), and compare it to a pre-existing digest (usually read from flash offset 0x0). The hardware returns a true/false comparison without making the digest available to software. +3. Verify a digest from data (usually the bootloader image from flash), and compare it to a pre-existing digest (usually read from flash offset 0x0). The hardware returns a true/false comparison without making the digest available to software. This function is available even when Efuse ABS_DONE_0 is burned. Secure Bootloader Digest Algorithm ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -122,9 +142,9 @@ Items marked with (^) are to fulfill hardware restrictions, as opposed to crypto 1. Prefix the image with a 128 byte randomly generated IV. 2. If the image is not modulo 128, pad the image to a 128 byte boundary with 0xFF. (^) -3. For each 16 byte block of the input image: - - Reverse the byte order of the block (^) - - Use the AES256 algorithm in ECB mode to encrypt the block. +3. For each 16 byte plaintext block of the input image: + - Reverse the byte order of the plaintext block (^) + - Apply AES256 in ECB mode to the plaintext block. - Reverse the byte order of the 16 bytes of ciphertext output. (^) - Append to the overall ciphertext output. 4. Byte-swap each 4 byte word of the ciphertext (^) @@ -137,9 +157,10 @@ Image Signing Algorithm Deterministic ECDSA as specified by `RFC6979`. -Curve is TBD. -Key format is TBD. -Output format is TBD. +- Curve is NIST256p (openssl calls this curve "prime256v1", it is also sometimes called secp256r1). +- Key format used for storage is PEM. + - In the bootloader, the public key (for signature verification) is flashed as 64 raw bytes. +- Image signature is 68 bytes - a 4 byte version word (currently zero), followed by a 64 bytes of signature data. These 68 bytes are appended to an app image or partition table data. .. _esp-idf boot process: ../boot-process.rst diff --git a/make/common.mk b/make/common.mk index 2b7376a4d..0c523c877 100644 --- a/make/common.mk +++ b/make/common.mk @@ -53,8 +53,26 @@ endef # convenience variable for printing an 80 asterisk wide separator line SEPARATOR:="*******************************************************************************" -# macro to remove quotes from an argument, ie $(call dequote (CONFIG_BLAH)) +# macro to remove quotes from an argument, ie $(call dequote,$(CONFIG_BLAH)) define dequote $(subst ",,$(1)) endef # " comment kept here to keep syntax highlighting happy + + +# macro to keep an absolute path as-is, but resolve a relative path +# against a particular parent directory +# +# $(1) path to resolve +# $(2) directory to resolve non-absolute path against +# +# Path and directory don't have to exist (definition of a "relative +# path" is one that doesn't start with /) +# +# $(2) can contain a trailing forward slash or not, result will not +# double any path slashes. +# +# example $(call resolvepath,$(CONFIG_PATH),$(CONFIG_DIR)) +define resolvepath +$(if $(filter /%,$(1)),$(1),$(subst //,/,$(2)/$(1))) +endef diff --git a/make/component_common.mk b/make/component_common.mk index 58711b7c6..70a6f60f0 100644 --- a/make/component_common.mk +++ b/make/component_common.mk @@ -134,10 +134,10 @@ OBJCOPY_EMBED_ARGS := --input binary --output elf32-xtensa-le --binary-architect # because objcopy generates the symbol name from the full command line # path to the input file. define GenerateEmbedTarget -$(1).$(2).o: $$(COMPONENT_PATH)/$(1) | $$(dir $(1)) +$(1).$(2).o: $(call resolvepath,$(1),$(COMPONENT_PATH)) | $$(dir $(1)) $$(summary) EMBED $$@ - $$(Q) cp $$< $$(notdir $$<) - $$(Q) $(if $(subst bin,,$(2)),echo -ne '\0' >> $$(notdir $$<) ) + $$(Q) $(if $(filter-out $$(notdir $$(abspath $$<)),$$(abspath $$(notdir $$<))), cp $$< $$(notdir $$<) ) # copy input file to build dir, unless already in build dir + $$(Q) $(if $(subst bin,,$(2)),echo -ne '\0' >> $$(notdir $$<) ) # trailing NUL byte on text output $$(Q) $$(OBJCOPY) $(OBJCOPY_EMBED_ARGS) $$(notdir $$<) $$@ $$(Q) rm $$(notdir $$<) endef diff --git a/make/project.mk b/make/project.mk index 7e8dc46d5..5a12732a4 100644 --- a/make/project.mk +++ b/make/project.mk @@ -151,6 +151,7 @@ LDFLAGS ?= -nostdlib \ -L$(IDF_PATH)/ld \ $(addprefix -L$(BUILD_DIR_BASE)/,$(COMPONENTS) $(SRCDIRS)) \ -u call_user_start_cpu0 \ + $(EXTRA_LDFLAGS) \ -Wl,--gc-sections \ -Wl,-static \ -Wl,--start-group \