From fbf1cd16a634e2619f767f1afe98510b7c1f2d76 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Mon, 9 Sep 2024 19:28:59 +0200 Subject: [PATCH 1/3] makefiles/suit: store public keys This makes it easier to work with encrypted keys and multiple keys. The firmware binary can contain multiple public keys that are used to verify the manifest. The use case is that we want to include the production public key in the debug build, so we can seamlessly update to the production version without re-flashing the device. If the public keys is always generated on the fly, this would still require the production key password even for the debug build. Instead if we store the (unencrypted) public key, we can always include it in the debug build. --- makefiles/suit.base.inc.mk | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/makefiles/suit.base.inc.mk b/makefiles/suit.base.inc.mk index 633d859fe332..8109b5e436ba 100644 --- a/makefiles/suit.base.inc.mk +++ b/makefiles/suit.base.inc.mk @@ -18,10 +18,11 @@ else SUIT_KEY_DIR ?= $(XDG_DATA_HOME)/RIOT/keys endif -SUIT_SEC ?= $(SUIT_KEY_DIR)/$(SUIT_KEY).pem +# we may accept multiple keys for the firmware +SUIT_SEC ?= $(foreach item,$(SUIT_KEY),$(SUIT_KEY_DIR)/$(item).pem) -# Multiple keys can be specified with "key0:pw0 key1:pw1 …" (pw may be empty) -SUIT_SECS ?= $(SUIT_SEC):$(SUIT_SEC_PASSWORD) +# generate a list of the public keys +SUIT_PUBS ?= $(SUIT_SEC:.pem=.pem.pub) SUIT_PUB_HDR = $(BINDIR)/riotbuild/public_key.h SUIT_PUB_HDR_DIR = $(dir $(SUIT_PUB_HDR)) @@ -31,26 +32,24 @@ BUILDDEPS += $(SUIT_PUB_HDR) $(SUIT_SEC): | $(CLEAN) $(Q)echo suit: generating key in $(SUIT_KEY_DIR) $(Q)mkdir -p $(SUIT_KEY_DIR) - $(Q)$(RIOTBASE)/dist/tools/suit/gen_key.py $(SUIT_SEC) $(SUIT_SEC_PASSWORD) + $(Q)$(RIOTBASE)/dist/tools/suit/gen_key.py $@ $(SUIT_SEC_PASSWORD) +%.pem.pub: %.pem + $(Q)openssl ec -inform pem -in $< -outform pem -pubout -out $@ + +# Convert public keys to C headers - only last 32 bytes are key material +# # set FORCE so switching between keys using "SUIT_KEY=foo make ..." # triggers a rebuild even if the new key would otherwise not (because the other # key's mtime is too far back). -$(SUIT_PUB_HDR): $(SUIT_SEC) FORCE | $(CLEAN) +$(SUIT_PUB_HDR): $(SUIT_PUBS) FORCE | $(CLEAN) $(Q)mkdir -p $(SUIT_PUB_HDR_DIR) $(Q)( \ echo "const uint8_t public_key[][32] = {"; \ - for i in $(SUIT_SECS); do \ - key=$${i%:*}; \ - pw=$${i#*:}; \ - if [ "$$key" = "$$pw" ]; then \ - unset pw; \ - fi; \ - if [ -z "$$pw" ]; then \ - $(SUIT_TOOL) pubkey -f header -k $$key; \ - else \ - $(SUIT_TOOL) pubkey -f header -k $$key -p $$pw; \ - fi \ + for i in $(SUIT_PUBS); do \ + echo " {"; \ + openssl ec -inform pem -pubin -in $$i -outform der | tail -c 32 | xxd -i; \ + echo " },"; \ done; \ echo "};" \ ) | '$(LAZYSPONGE)' $(LAZYSPONGE_FLAGS) '$@' From 99285d3ec50ad1cc1644087bc696fd7c243cec10 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Wed, 11 Sep 2024 11:13:15 +0200 Subject: [PATCH 2/3] makefiles/suit: add support for multiple & encrypted signing key --- makefiles/suit.base.inc.mk | 8 +++++++- makefiles/suit.inc.mk | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/makefiles/suit.base.inc.mk b/makefiles/suit.base.inc.mk index 8109b5e436ba..77fd304b21df 100644 --- a/makefiles/suit.base.inc.mk +++ b/makefiles/suit.base.inc.mk @@ -6,10 +6,14 @@ SUIT_TOOL ?= $(RIOTBASE)/dist/tools/suit/suit-manifest-generator/bin/suit-tool # SUIT encryption keys # -# Specify key to use. +# Specify key(s) to use. # Will use $(SUIT_KEY_DIR)/$(SUIT_KEY).pem as combined private/public key # files. +# Multiple keys can be specified, that means that the firmware will accept +# updates signed with either one of those keys. +# If the firmware accepts multiple keys, let the first key be the signing key. SUIT_KEY ?= default +SUIT_KEY_SIGN ?= $(word 1, $(SUIT_KEY)) XDG_DATA_HOME ?= $(HOME)/.local/share ifeq (1, $(RIOT_CI_BUILD)) @@ -20,6 +24,8 @@ endif # we may accept multiple keys for the firmware SUIT_SEC ?= $(foreach item,$(SUIT_KEY),$(SUIT_KEY_DIR)/$(item).pem) +# but there can only be one signing key +SUIT_SEC_SIGN ?= $(SUIT_KEY_DIR)/$(SUIT_KEY_SIGN).pem # generate a list of the public keys SUIT_PUBS ?= $(SUIT_SEC:.pem=.pem.pub) diff --git a/makefiles/suit.inc.mk b/makefiles/suit.inc.mk index b06e0b9fbd6d..9d59290c6bde 100644 --- a/makefiles/suit.inc.mk +++ b/makefiles/suit.inc.mk @@ -32,6 +32,10 @@ SUIT_MANIFEST_SIGNED_LATEST ?= $(BINDIR_SUIT)/$(SUIT_MANIFEST_BASENAME).latest.b SUIT_NOTIFY_VERSION ?= latest SUIT_NOTIFY_MANIFEST ?= $(SUIT_MANIFEST_BASENAME).$(SUIT_NOTIFY_VERSION).bin +ifneq (,$(SUIT_SEC_PASSWORD)) + SUIT_TOOL_ARGS += -p $(SUIT_SEC_PASSWORD) +endif + # Long manifest names require more buffer space when parsing export CFLAGS += -DCONFIG_SOCK_URLPATH_MAXLEN=128 export CFLAGS += -DSUIT_VENDOR_DOMAIN="\"$(SUIT_VENDOR)\"" @@ -54,7 +58,7 @@ $(SUIT_MANIFEST): $(SUIT_MANIFEST_PAYLOADS) $(BINDIR_SUIT) $(Q)rm -f $@.tmp $(SUIT_MANIFEST_SIGNED): $(SUIT_MANIFEST) $(SUIT_SEC) - $(Q)$(SUIT_TOOL) sign $(SUIT_TOOL_ARGS) -k $(SUIT_SEC) -m $(SUIT_MANIFEST) -o $@ + $(Q)$(SUIT_TOOL) sign $(SUIT_TOOL_ARGS) -k $(SUIT_SEC_SIGN) -m $(SUIT_MANIFEST) -o $@ $(SUIT_MANIFEST_LATEST): $(SUIT_MANIFEST) $(Q)ln -f -s $< $@ From 765dd68753b886123319e7be07ea37d8bd89ed91 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Thu, 12 Sep 2024 00:06:06 +0200 Subject: [PATCH 3/3] makefiles/suit: use OpenSSL to generate key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fabian Hüßler --- makefiles/suit.base.inc.mk | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/makefiles/suit.base.inc.mk b/makefiles/suit.base.inc.mk index 77fd304b21df..fe4fc4b8b36c 100644 --- a/makefiles/suit.base.inc.mk +++ b/makefiles/suit.base.inc.mk @@ -35,10 +35,30 @@ SUIT_PUB_HDR_DIR = $(dir $(SUIT_PUB_HDR)) CFLAGS += -I$(SUIT_PUB_HDR_DIR) BUILDDEPS += $(SUIT_PUB_HDR) +# OpenSSL leaves an empty file if key generation fails - remove it manually +# see https://github.com/openssl/openssl/issues/25440 $(SUIT_SEC): | $(CLEAN) $(Q)echo suit: generating key in $(SUIT_KEY_DIR) $(Q)mkdir -p $(SUIT_KEY_DIR) - $(Q)$(RIOTBASE)/dist/tools/suit/gen_key.py $@ $(SUIT_SEC_PASSWORD) + $(Q)( \ + printf "0) none\n"; \ + printf "1) aes-256-cbc\n"; \ + printf "Choose encryption for key file $@: "; \ + if [ -z "$(RIOT_CI_BUILD)" ]; then read encryption; else encryption=0; fi; \ + case $$encryption in \ + 0) \ + openssl genpkey -algorithm ed25519 -out $@; \ + ;; \ + 1) \ + openssl genpkey -algorithm ed25519 -aes-256-cbc -out $@ || :; \ + ;; \ + *) \ + echo "Invalid choice"; \ + exit 1; \ + ;; \ + esac; \ + ) + $(Q)if [ ! -s $@ ]; then rm $@; fi %.pem.pub: %.pem $(Q)openssl ec -inform pem -in $< -outform pem -pubout -out $@