From d6fafd00dba4df5e75060939972582bcbf45af73 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Mon, 19 Dec 2016 13:06:21 +1100 Subject: [PATCH] Secure boot: Option for app & partition table signing to happen outside build system --- components/bootloader/Kconfig.projbuild | 29 +++++++++++++++++-- components/bootloader/Makefile.projbuild | 11 ++++++- components/bootloader_support/component.mk | 20 ++++++++++++- components/esptool_py/Makefile.projbuild | 6 ++-- components/partition_table/Makefile.projbuild | 2 +- docs/security/secure-boot.rst | 23 ++++++++++++++- make/component_wrapper.mk | 1 - make/project.mk | 12 ++++++++ 8 files changed, 94 insertions(+), 10 deletions(-) diff --git a/components/bootloader/Kconfig.projbuild b/components/bootloader/Kconfig.projbuild index 270369b2c..798fcf0b2 100644 --- a/components/bootloader/Kconfig.projbuild +++ b/components/bootloader/Kconfig.projbuild @@ -69,9 +69,20 @@ config SECURE_BOOTLOADER_REFLASHABLE endchoice -config SECURE_BOOT_SIGNING_KEY - string "Secure boot signing key" +config SECURE_BOOT_BUILD_SIGNED_BINARIES + bool "Sign binaries during build" depends on SECURE_BOOT_ENABLED + default y + help + Once secure boot is enabled, bootloader will only boot if partition table and app image are signed. + + If enabled, these binary files are signed as part of the build process. The file named in "Secure boot private signing key" will be used to sign the image. + + If disabled, unsigned app/partition data will be built. They must be signed manually using espsecure.py (for example, on a remote signing server.) + +config SECURE_BOOT_SIGNING_KEY + string "Secure boot private signing key" + depends on SECURE_BOOT_BUILD_SIGNED_BINARIES default secure_boot_signing_key.pem help Path to the key file used to sign partition tables and app images for secure boot. Once secure boot is enabled, bootloader will only boot if partition table and app image are signed. @@ -85,6 +96,20 @@ config SECURE_BOOT_SIGNING_KEY See docs/security/secure-boot.rst for details. +config SECURE_BOOT_VERIFICATION_KEY + string "Secure boot public signature verification key" + depends on SECURE_BOOT_ENABLED && !SECURE_BOOT_BUILD_SIGNED_BINARIES + default signature_verification_key.bin + help + Path to a public key file used to verify signed images. This key is compiled into the bootloader, + and may also be used to verify signatures on OTA images after download. + + Key file is in raw binary format, and can be extracted from a + PEM formatted private key using the espsecure.py + extract_public_key command. + + See docs/security/secure-boot.rst for details. + config SECURE_BOOT_INSECURE bool "Allow potentially insecure options" depends on SECURE_BOOT_ENABLED diff --git a/components/bootloader/Makefile.projbuild b/components/bootloader/Makefile.projbuild index 3f83803ad..49bd934c6 100644 --- a/components/bootloader/Makefile.projbuild +++ b/components/bootloader/Makefile.projbuild @@ -65,8 +65,17 @@ else ifdef CONFIG_SECURE_BOOTLOADER_REFLASHABLE BOOTLOADER_DIGEST_BIN := $(BOOTLOADER_BUILD_DIR)/bootloader-reflash-digest.bin SECURE_BOOTLOADER_KEY := $(BOOTLOADER_BUILD_DIR)/secure-bootloader-key.bin +ifdef CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES $(SECURE_BOOTLOADER_KEY): $(SECURE_BOOT_SIGNING_KEY) - $(Q) $(ESPSECUREPY) digest_private_key -k $< $@ + $(ESPSECUREPY) digest_private_key -k $< $@ +else +$(SECURE_BOOTLOADER_KEY): + @echo "No pre-generated key for a reflashable secure bootloader is available, due to signing configuration." + @echo "To generate one, you can use this command:" + @echo "espsecure.py generate_flash_encryption_key $@" + @echo "then re-run make." + exit 1 +endif bootloader: $(BOOTLOADER_DIGEST_BIN) @echo $(SEPARATOR) diff --git a/components/bootloader_support/component.mk b/components/bootloader_support/component.mk index 1435dbb76..6db815aff 100755 --- a/components/bootloader_support/component.mk +++ b/components/bootloader_support/component.mk @@ -17,8 +17,26 @@ ifdef CONFIG_SECURE_BOOT_ENABLED # this path is created relative to the component build directory SECURE_BOOT_VERIFICATION_KEY := $(abspath signature_verification_key.bin) -$(SECURE_BOOT_VERIFICATION_KEY): $(SECURE_BOOT_SIGNING_KEY) +ifdef CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES +# verification key derived from signing key. +$(SECURE_BOOT_VERIFICATION_KEY): $(SECURE_BOOT_SIGNING_KEY) $(SDKCONFIG_MAKEFILE) $(ESPSECUREPY) extract_public_key --keyfile $< $@ +else +# find the configured public key file +ORIG_SECURE_BOOT_VERIFICATION_KEY := $(call resolvepath,$(call dequote,$(CONFIG_SECURE_BOOT_VERIFICATION_KEY)),$(PROJECT_PATH)) + +$(ORIG_SECURE_BOOT_VERIFICATION_KEY): + @echo "Secure boot verification public key '$@' missing." + @echo "This can be extracted from the private signing key, see" + @echo "docs/security/secure-boot.rst for details." + exit 1 + +# copy it into the build dir, so the secure boot verification key has +# a predictable file name +$(SECURE_BOOT_VERIFICATION_KEY): $(ORIG_SECURE_BOOT_VERIFICATION_KEY) $(SDKCONFIG_MAKEFILE) + $(summary) CP $< $@ + cp $< $@ +endif COMPONENT_EXTRA_CLEAN += $(SECURE_BOOT_VERIFICATION_KEY) diff --git a/components/esptool_py/Makefile.projbuild b/components/esptool_py/Makefile.projbuild index 54221f179..3d8d5948f 100644 --- a/components/esptool_py/Makefile.projbuild +++ b/components/esptool_py/Makefile.projbuild @@ -28,12 +28,12 @@ ESPTOOLPY_WRITE_FLASH=$(ESPTOOLPY_SERIAL) write_flash $(if $(CONFIG_ESPTOOLPY_CO ESPTOOL_ALL_FLASH_ARGS += $(CONFIG_APP_OFFSET) $(APP_BIN) -ifdef CONFIG_SECURE_BOOT_ENABLED +ifdef CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES ifndef IS_BOOTLOADER_BUILD -# for secure boot, add a signing step to get from unsiged app to signed app +# for locally signed secure boot image, add a signing step to get from unsigned app to signed app APP_BIN_UNSIGNED := $(APP_BIN:.bin=-unsigned.bin) -$(APP_BIN): $(APP_BIN_UNSIGNED) $(SECURE_BOOT_SIGNING_KEY) +$(APP_BIN): $(APP_BIN_UNSIGNED) $(SECURE_BOOT_SIGNING_KEY) $(SDKCONFIG_MAKEFILE) $(ESPSECUREPY) sign_data --keyfile $(SECURE_BOOT_SIGNING_KEY) -o $@ $< endif endif diff --git a/components/partition_table/Makefile.projbuild b/components/partition_table/Makefile.projbuild index dbc9d3605..5d1e726a8 100644 --- a/components/partition_table/Makefile.projbuild +++ b/components/partition_table/Makefile.projbuild @@ -21,7 +21,7 @@ PARTITION_TABLE_CSV_PATH := $(call dequote,$(abspath $(PARTITION_TABLE_ROOT)/$(s PARTITION_TABLE_BIN := $(BUILD_DIR_BASE)/$(notdir $(PARTITION_TABLE_CSV_PATH:.csv=.bin)) -ifdef CONFIG_SECURE_BOOT_ENABLED +ifdef CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES PARTITION_TABLE_BIN_UNSIGNED := $(PARTITION_TABLE_BIN:.bin=-unsigned.bin) # add an extra signing step for secure partition table $(PARTITION_TABLE_BIN): $(PARTITION_TABLE_BIN_UNSIGNED) $(SDKCONFIG_MAKEFILE) $(SECURE_BOOT_SIGNING_KEY) diff --git a/docs/security/secure-boot.rst b/docs/security/secure-boot.rst index 65b6bab48..7aaf9abf8 100644 --- a/docs/security/secure-boot.rst +++ b/docs/security/secure-boot.rst @@ -25,7 +25,7 @@ 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 "Secure Boot Configuration". -2. Secure Boot Configuration includes "Secure boot signing key", which is a file path. This file is a ECDSA public/private key pair in a PEM format file. +2. Secure Boot defaults to signing images and partition table data during the build process. The "Secure boot private signing key" config item is a file path to a ECDSA public/private key pair in a PEM format file. 3. The software bootloader image is built by esp-idf with secure boot support enabled and the public key (signature verification) portion of the secure boot signing key compiled in. This software bootloader image is flashed at offset 0x1000. @@ -119,6 +119,27 @@ openssl ecparam -name prime256v1 -genkey -noout -out my_secure_boot_signing_key. Remember that the strength of the secure boot system depends on keeping the signing key private. +Remote Signing of Images +------------------------ + +For production builds, it can be good practice to use a remote signing server rather than have the signing key on the build machine (which is the default esp-idf secure boot configuration). The espsecure.py command line program can be used to sign app images & partition table data for secure boot, on a remote system. + +To use remote signing, disable the option "Sign binaries during build". The private signing key does not need to be present on the build system. However, the public (signature verification) key is required because it is compiled into the bootloader (and can be used to verify image signatures during OTA updates. + +To extract the public key from the private key:: + + espsecure.py extract_public_key --keyfile PRIVATE_SIGNING_KEY PUBLIC_VERIFICATION_KEY + +The path to the public signature verification key needs to be specified in the menuconfig under "Secure boot public signature verification key" in order to build the secure bootloader. + +After the app image and partition table are built, the build system will print signing steps using espsecure.py:: + + espsecure.py sign_data --keyfile PRIVATE_SIGNING_KEY BINARY_FILE + +The above command appends the image signature to the existing binary. You can use the --output argument to place the binary with signature appended into a separate file:: + + espsecure.py sign_data --keyfile PRIVATE_SIGNING_KEY --output SIGNED_BINARY_FILE BINARY_FILE + Secure Boot Best Practices -------------------------- diff --git a/make/component_wrapper.mk b/make/component_wrapper.mk index a84208267..c750341c2 100644 --- a/make/component_wrapper.mk +++ b/make/component_wrapper.mk @@ -74,7 +74,6 @@ endif # Correspond to the files named in COMPONENT_EMBED_FILES & COMPONENT_EMBED_TXTFILES COMPONENT_EMBED_OBJS ?= $(addsuffix .bin.o,$(COMPONENT_EMBED_FILES)) $(addsuffix .txt.o,$(COMPONENT_EMBED_TXTFILES)) - # If we're called to compile something, we'll get passed the COMPONENT_INCLUDES # variable with all the include dirs from all the components in random order. This # means we can accidentally grab a header from another component before grabbing our own. diff --git a/make/project.mk b/make/project.mk index 054800227..35be8a861 100644 --- a/make/project.mk +++ b/make/project.mk @@ -142,6 +142,11 @@ include $(IDF_PATH)/make/common.mk all: ifdef CONFIG_SECURE_BOOT_ENABLED @echo "(Secure boot enabled, so bootloader not flashed automatically. See 'make bootloader' output)" +ifndef CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES + @echo "App built but not signed. Sign app & partition data before flashing, via espsecure.py:" + @echo "espsecure.py sign_data --keyfile KEYFILE $(APP_BIN)" + @echo "espsecure.py sign_data --keyfile KEYFILE $(PARTITION_TABLE_BIN)" +endif @echo "To flash app & partition table, run 'make flash' or:" else @echo "To flash all build output, run 'make flash' or:" @@ -283,8 +288,15 @@ $(APP_ELF): $(foreach libcomp,$(COMPONENT_LIBRARIES),$(BUILD_DIR_BASE)/$(libcomp # Generation of $(APP_BIN) from $(APP_ELF) is added by the esptool # component's Makefile.projbuild app: $(APP_BIN) +ifeq ("$(CONFIG_SECURE_BOOT_ENABLED)$(CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES)","y") # secure boot enabled, but remote sign app image + @echo "App built but not signed. Signing step via espsecure.py:" + @echo "espsecure.py sign_data --keyfile KEYFILE $(APP_BIN)" + @echo "Then flash app command is:" + @echo $(ESPTOOLPY_WRITE_FLASH) $(CONFIG_APP_OFFSET) $(APP_BIN) +else @echo "App built. Default flash app command is:" @echo $(ESPTOOLPY_WRITE_FLASH) $(CONFIG_APP_OFFSET) $(APP_BIN) +endif all_binaries: $(APP_BIN)