diff --git a/Makefile.dep b/Makefile.dep index 4809a88733..1b844a8049 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -979,25 +979,29 @@ ifneq (,$(filter sock_dtls, $(USEMODULE))) USEMODULE += sock_udp endif -ifneq (,$(filter suit_v4_%,$(USEMODULE))) - USEMODULE += suit_v4 -endif - -ifneq (,$(filter suit_v4,$(USEMODULE))) +ifneq (,$(filter suit,$(USEMODULE))) USEPKG += nanocbor USEPKG += libcose USEMODULE += libcose_crypt_c25519 - USEMODULE += suit_conditions + USEMODULE += uuid - # SUIT depends on riotboot support and some extra riotboot modules - FEATURES_REQUIRED += riotboot - USEMODULE += riotboot_slot - USEMODULE += riotboot_flashwrite - USEMODULE += riotboot_flashwrite_verify_sha256 + # tests/suit_manifest has some mock implementations, + # only add the non-mock dependencies if not building that test. + ifeq (,$(filter suit_transport_mock,$(USEMODULE))) + # SUIT depends on riotboot support and some extra riotboot modules + FEATURES_REQUIRED += riotboot + USEMODULE += riotboot_slot + USEMODULE += riotboot_flashwrite + USEMODULE += riotboot_flashwrite_verify_sha256 + endif endif -ifneq (,$(filter suit_conditions,$(USEMODULE))) - USEMODULE += uuid +ifneq (,$(filter suit_transport_%, $(USEMODULE))) + USEMODULE += suit_transport +endif + +ifneq (,$(filter suit_transport_coap, $(USEMODULE))) + USEMODULE += nanocoap endif ifneq (,$(filter suit_%,$(USEMODULE))) diff --git a/Makefile.include b/Makefile.include index a30a056111..27a10d37af 100644 --- a/Makefile.include +++ b/Makefile.include @@ -480,6 +480,10 @@ ELFFILE ?= $(BINDIR)/$(APPLICATION).elf HEXFILE ?= $(ELFFILE:.elf=.hex) BINFILE ?= $(ELFFILE:.elf=.bin) + +# include basic suit-tool and key handling +include $(RIOTMAKE)/suit.v3.base.inc.mk + # include bootloaders support. It should be included early to allow using # variables defined in `riotboot.mk` for `FLASHFILE` before it is evaluated. # It should be included after defining 'BINFILE' for 'riotboot.bin' handling. diff --git a/makefiles/boot/riotboot.mk b/makefiles/boot/riotboot.mk index 1cdbba05e7..3e5dc8f760 100644 --- a/makefiles/boot/riotboot.mk +++ b/makefiles/boot/riotboot.mk @@ -146,8 +146,8 @@ riotboot/flash: riotboot/flash-slot0 riotboot/flash-bootloader FLASHFILE = $(RIOTBOOT_EXTENDED_BIN) # include suit targets -ifneq (,$(filter suit_v4, $(USEMODULE))) - include $(RIOTMAKE)/suit.v4.inc.mk +ifneq (,$(filter suit, $(USEMODULE))) + include $(RIOTMAKE)/suit.inc.mk endif else diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 2dd7267fd0..4ada31fd53 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -91,13 +91,10 @@ PSEUDOMODULES += stdin PSEUDOMODULES += stdio_ethos PSEUDOMODULES += stdio_cdc_acm PSEUDOMODULES += stdio_uart_rx -PSEUDOMODULES += suit_% +PSEUDOMODULES += suit_transport_% PSEUDOMODULES += wakaama_objects_% PSEUDOMODULES += zptr -# handle suit_v4 being a distinct module -NO_PSEUDOMODULES += suit_v4 - # print ascii representation in function od_hex_dump() PSEUDOMODULES += od_string diff --git a/makefiles/suit.v3.base.inc.mk b/makefiles/suit.v3.base.inc.mk new file mode 100644 index 0000000000..ab0d84c103 --- /dev/null +++ b/makefiles/suit.v3.base.inc.mk @@ -0,0 +1,40 @@ +# +# path to suit-tool +SUIT_TOOL ?= $(RIOTBASE)/dist/tools/suit_v3/suit-manifest-generator/bin/suit-tool + +# +# SUIT encryption keys +# + +# Specify key to use. +# Will use $(SUIT_KEY_DIR)/$(SUIT_KEY).pem as combined private/public key +# files. +SUIT_KEY ?= default + +ifeq (1, $(RIOT_CI_BUILD)) + SUIT_KEY_DIR ?= $(BINDIR) +else + SUIT_KEY_DIR ?= $(RIOTBASE)/keys +endif + +SUIT_SEC ?= $(SUIT_KEY_DIR)/$(SUIT_KEY).pem + +SUIT_PUB_HDR = $(BINDIR)/riotbuild/public_key.h +SUIT_PUB_HDR_DIR = $(dir $(SUIT_PUB_HDR)) +CFLAGS += -I$(SUIT_PUB_HDR_DIR) +BUILDDEPS += $(SUIT_PUB_HDR) + +$(SUIT_SEC): $(CLEAN) + @echo suit: generating key in $(SUIT_KEY_DIR) + @mkdir -p $(SUIT_KEY_DIR) + @$(RIOTBASE)/dist/tools/suit_v3/gen_key.py $(SUIT_SEC) + +# 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) + @mkdir -p $(SUIT_PUB_HDR_DIR) + @$(SUIT_TOOL) pubkey -k $(SUIT_SEC) \ + | '$(LAZYSPONGE)' $(LAZYSPONGE_FLAGS) '$@' + +suit/genkey: $(SUIT_SEC) diff --git a/makefiles/suit.v3.inc.mk b/makefiles/suit.v3.inc.mk new file mode 100644 index 0000000000..d65f40dc2e --- /dev/null +++ b/makefiles/suit.v3.inc.mk @@ -0,0 +1,72 @@ +# +# This file contains stuff related to SUIT manifest generation. +# It depends on SUIT key generation, which can be found in +# makefiles/suit.v3.base.inc.mk +# +# +SUIT_COAP_BASEPATH ?= fw/$(BOARD) +SUIT_COAP_SERVER ?= localhost +SUIT_COAP_ROOT ?= coap://$(SUIT_COAP_SERVER)/$(SUIT_COAP_BASEPATH) +SUIT_COAP_FSROOT ?= $(RIOTBASE)/coaproot + +# +SUIT_MANIFEST ?= $(BINDIR_APP)-riot.suitv3.$(APP_VER).bin +SUIT_MANIFEST_LATEST ?= $(BINDIR_APP)-riot.suitv3.latest.bin +SUIT_MANIFEST_SIGNED ?= $(BINDIR_APP)-riot.suitv3_signed.$(APP_VER).bin +SUIT_MANIFEST_SIGNED_LATEST ?= $(BINDIR_APP)-riot.suitv3_signed.latest.bin + +SUIT_NOTIFY_VERSION ?= latest +SUIT_NOTIFY_MANIFEST ?= $(APPLICATION)-riot.suitv3_signed.$(SUIT_NOTIFY_VERSION).bin + +# Long manifest names require more buffer space when parsing +export CFLAGS += -DCONFIG_SOCK_URLPATH_MAXLEN=128 + +SUIT_VENDOR ?= "riot-os.org" +SUIT_SEQNR ?= $(APP_VER) +SUIT_CLASS ?= $(BOARD) + +# +$(SUIT_MANIFEST): $(SLOT0_RIOT_BIN) $(SLOT1_RIOT_BIN) + $(RIOTBASE)/dist/tools/suit_v3/gen_manifest.py \ + --urlroot $(SUIT_COAP_ROOT) \ + --seqnr $(SUIT_SEQNR) \ + --uuid-vendor $(SUIT_VENDOR) \ + --uuid-class $(SUIT_CLASS) \ + -o $@.tmp \ + $(SLOT0_RIOT_BIN):$(SLOT0_OFFSET) \ + $(SLOT1_RIOT_BIN):$(SLOT1_OFFSET) + + $(SUIT_TOOL) create -f suit -i $@.tmp -o $@ + + rm -f $@.tmp + + +$(SUIT_MANIFEST_SIGNED): $(SUIT_MANIFEST) $(SUIT_SEC) + $(SUIT_TOOL) sign -k $(SUIT_SEC) -m $(SUIT_MANIFEST) -o $@ + +$(SUIT_MANIFEST_LATEST): $(SUIT_MANIFEST) + @ln -f -s $< $@ + +$(SUIT_MANIFEST_SIGNED_LATEST): $(SUIT_MANIFEST_SIGNED) + @ln -f -s $< $@ + +SUIT_MANIFESTS := $(SUIT_MANIFEST) \ + $(SUIT_MANIFEST_LATEST) \ + $(SUIT_MANIFEST_SIGNED) \ + $(SUIT_MANIFEST_SIGNED_LATEST) + +suit/manifest: $(SUIT_MANIFESTS) + +suit/publish: $(SUIT_MANIFESTS) $(SLOT0_RIOT_BIN) $(SLOT1_RIOT_BIN) + @mkdir -p $(SUIT_COAP_FSROOT)/$(SUIT_COAP_BASEPATH) + @cp $^ $(SUIT_COAP_FSROOT)/$(SUIT_COAP_BASEPATH) + @for file in $^; do \ + echo "published \"$$file\""; \ + echo " as \"$(SUIT_COAP_ROOT)/$$(basename $$file)\""; \ + done + +suit/notify: | $(filter suit/publish, $(MAKECMDGOALS)) + @test -n "$(SUIT_CLIENT)" || { echo "error: SUIT_CLIENT unset!"; false; } + aiocoap-client -m POST "coap://$(SUIT_CLIENT)/suit/trigger" \ + --payload "$(SUIT_COAP_ROOT)/$(SUIT_NOTIFY_MANIFEST)" && \ + echo "Triggered $(SUIT_CLIENT) to update." diff --git a/sys/include/suit.h b/sys/include/suit.h new file mode 100644 index 0000000000..530e757a6a --- /dev/null +++ b/sys/include/suit.h @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * 2019 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @defgroup sys_suit_v3 SUIT IETF draft v3 + * @ingroup sys_suit + * @brief SUIT manifest handling + * + * @see https://tools.ietf.org/html/draft-ietf-suit-manifest-03 + * + * @{ + * + * @brief Handler functions for SUIT manifests + * @author Koen Zandberg + * @author Kaspar Schleiser + * + */ + +#ifndef SUIT_V3_SUIT_H +#define SUIT_V3_SUIT_H + +#include +#include + +#include "cose/sign.h" +#include "nanocbor/nanocbor.h" +#include "uuid.h" +#include "riotboot/flashwrite.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Buffer size used for Cose + */ +#ifndef SUIT_COSE_BUF_SIZE +#define SUIT_COSE_BUF_SIZE (180U) +#endif + +/** + * @brief Maximum number of components supported in a SUIT manifest + */ +#define SUIT_V3_COMPONENT_MAX (1U) + +/** + * @brief Current SUIT serialization format version + * + * see https://tools.ietf.org/html/draft-ietf-suit-manifest-03#section-7 for + * details + */ +#define SUIT_VERSION (1) + +/** + * @brief COSE signature OK + */ +#define SUIT_STATE_COSE_AUTHENTICATED (1 << 1) + +/** + * @brief COSE payload matches SUIT manifest digest + */ +#define SUIT_STATE_FULLY_AUTHENTICATED (1 << 2) + +/** + * @brief SUIT error codes + */ +typedef enum { + SUIT_OK = 0, /**< Manifest parsed and validated */ + SUIT_ERR_INVALID_MANIFEST = -1, /**< Unexpected CBOR structure detected */ + SUIT_ERR_UNSUPPORTED = -2, /**< Unsupported SUIT feature detected */ + SUIT_ERR_NOT_SUPPORTED = -3, /**< Unsupported features detected */ + SUIT_ERR_COND = -4, /**< Conditionals evaluate to false */ + SUIT_ERR_SEQUENCE_NUMBER = -5, /**< Sequence number less or equal to + current sequence number */ + SUIT_ERR_SIGNATURE = -6, /**< Unable to verify signature */ + SUIT_ERR_DIGEST_MISMATCH = -7, /**< Digest mismatch with COSE and SUIT */ +} suit_error_t; + +/** + * @brief SUIT payload digest algorithms + * + * Unofficial list from + * [suit-manifest-generator](https://github.com/ARMmbed/suit-manifest-generator) + */ +typedef enum { + SUIT_DIGEST_NONE = 0, /**< No digest algo supplied */ + SUIT_DIGEST_SHA256 = 1, /**< SHA256 */ + SUIT_DIGEST_SHA384 = 2, /**< SHA384 */ + SUIT_DIGEST_SHA512 = 3, /**< SHA512 */ +} suit_digest_t; + +/** + * @brief SUIT payload digest types + * + * Unofficial list from + * [suit-manifest-generator](https://github.com/ARMmbed/suit-manifest-generator) + */ +typedef enum { + SUIT_DIGEST_TYPE_RAW = 1, /**< Raw payload digest */ + SUIT_DIGEST_TYPE_INSTALLED = 2, /**< Installed firmware digest */ + SUIT_DIGEST_TYPE_CIPHERTEXT = 3, /**< Ciphertext digest */ + SUIT_DIGEST_TYPE_PREIMAGE = 4 /**< Pre-image digest */ +} suit_digest_type_t; + +/** + * @brief SUIT component types + * + * Unofficial list from + * [suit-manifest-generator](https://github.com/ARMmbed/suit-manifest-generator) + */ +enum { + SUIT_COMPONENT_IDENTIFIER = 1, /**< Identifier component */ + SUIT_COMPONENT_SIZE = 2, /**< Size component */ + SUIT_COMPONENT_DIGEST = 3, /**< Digest component */ +}; + +/** + * @brief SUIT component struct + */ +typedef struct { + uint32_t size; /**< Size */ + nanocbor_value_t identifier; /**< Identifier */ + nanocbor_value_t url; /**< Url */ + nanocbor_value_t digest; /**< Digest */ +} suit_component_t; + +/** + * @brief SUIT manifest struct + */ +typedef struct { + const uint8_t *buf; /**< ptr to the buffer of the manifest */ + size_t len; /**< length of the manifest */ + const uint8_t *cose_payload; /**< ptr to the payload of the COSE sign */ + size_t cose_payload_len; /**< length of the COSE payload */ + uint32_t validated; /**< bitfield of validated policies */ + uint32_t state; /**< bitfield holding state information */ + /** List of components in the manifest */ + suit_component_t components[SUIT_V3_COMPONENT_MAX]; + unsigned components_len; /**< Current number of components */ + uint32_t component_current; /**< Current component index */ + riotboot_flashwrite_t *writer; /**< Pointer to the riotboot flash writer */ + /** Manifest validation buffer */ + uint8_t validation_buf[SUIT_COSE_BUF_SIZE]; + char *urlbuf; /**< Buffer containing the manifest url */ + size_t urlbuf_len; /**< Length of the manifest url */ +} suit_manifest_t; + +/** + * @brief Bit flags used to determine if SUIT manifest contains components + */ +#define SUIT_MANIFEST_HAVE_COMPONENTS (0x1) +/** + * @brief Bit flags used to determine if SUIT manifest contains an image + */ +#define SUIT_MANIFEST_HAVE_IMAGE (0x2) + +/** + * @brief Parse a manifest + * + * @note The buffer is still required after parsing, please don't reuse the + * buffer while the @p manifest is used + * + * @param[in] manifest manifest context to store information in + * @param[in] buf buffer to parse the manifest from + * @param[in] len length of the manifest data in the buffer + * + * @return SUIT_OK on parseable manifest + * @return negative @ref suit_error_t code on error + */ +int suit_parse(suit_manifest_t *manifest, const uint8_t *buf, size_t len); + +/** + * @brief Check a manifest policy + * + * @param[in] manifest manifest context to check the policy for + * + * @return 0 on valid manifest policy + * @return -1 on invalid manifest policy + */ +int suit_policy_check(suit_manifest_t *manifest); + +/** + * @brief Helper function for writing bytes on flash a specified offset + * + * @param[in] arg ptr to the SUIT manifest + * @param[in] offset offset to write to on flash + * @param[in] buf bytes to write + * @param[in] len length of bytes to write + * @param[in] more whether more data is coming + * + * @return 0 on success + * @return <0 on error + */ +int suit_flashwrite_helper(void *arg, size_t offset, uint8_t *buf, size_t len, + int more); + +#ifdef __cplusplus +} +#endif + +#endif /* SUIT_V3_SUIT_H */ +/** @} */ diff --git a/sys/include/suit/handlers.h b/sys/include/suit/handlers.h new file mode 100644 index 0000000000..48ef97e1a4 --- /dev/null +++ b/sys/include/suit/handlers.h @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * 2019 Kaspar Schleiser + * 2019 Inria + * 2019 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @ingroup sys_suit_v3 + * @brief SUIT draft-ietf-suit-manifest-03 manifest handlers + * + * @experimental + * + * @{ + * + * @brief Handler functions for SUIT manifests + * @author Koen Zandberg + * @author Kaspar Schleiser + */ + +#ifndef SUIT_V3_HANDLERS_H +#define SUIT_V3_HANDLERS_H + +#include +#include + +#include "suit.h" +#include "uuid.h" +#include "nanocbor/nanocbor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name SUIT outer wrapper identifiers + * @{ + */ +#define SUIT_WRAPPER_AUTHENTICATION (2) +#define SUIT_WRAPPER_MANIFEST (3) +/** @} */ + +/** + * @name SUIT container identifiers + * @{ + */ +#define SUIT_CONTAINER_VERSION (1) +#define SUIT_CONTAINER_SEQ_NO (2) +#define SUIT_CONTAINER_COMMON (3) +#define SUIT_CONTAINER_DEPS_RESOLUTION (7) +#define SUIT_CONTAINER_PAYLOAD_FETCH (8) +#define SUIT_CONTAINER_INSTALL (9) +#define SUIT_CONTAINER_VALIDATE (10) +#define SUIT_CONTAINER_LOAD (11) +#define SUIT_CONTAINER_RUN (12) +#define SUIT_CONTAINER_TEXT (13) +/** @} */ + +/** + * @name SUIT common section identifiers + * @{ + */ +#define SUIT_COMMON_DEPENDENCIES (1) +#define SUIT_COMMON_COMPONENTS (2) +#define SUIT_COMMON_DEP_COMPONENTS (3) +#define SUIT_COMMON_COMMAND_SEQUENCE (4) +/** @} */ + +/** + * @name SUIT condition identifiers + * @{ + */ +#define SUIT_COND_VENDOR_ID (1) +#define SUIT_COND_CLASS_ID (2) +#define SUIT_COND_IMAGE_MATCH (3) +#define SUIT_COND_USE_BEFORE (4) +#define SUIT_COND_COMPONENT_OFFSET (5) +#define SUIT_COND_DEVICE_ID (24) +#define SUIT_COND_IMAGE_NOT_MATCH (25) +#define SUIT_COND_MIN_BATTERY (26) +#define SUIT_COND_UPDATE_AUTHZ (27) +#define SUIT_COND_VERSION (28) +/** @} */ + +/** + * @name SUIT directive identifiers + * @{ + */ +#define SUIT_DIR_SET_COMPONENT_IDX (12) +#define SUIT_DIR_SET_DEPENDENCY_IDX (13) +#define SUIT_DIR_ABORT (14) +#define SUIT_DIR_TRY_EACH (15) +#define SUIT_DIR_PROCESS_DEPS (18) +#define SUIT_DIR_SET_PARAM (19) +#define SUIT_DIR_OVERRIDE_PARAM (20) +#define SUIT_DIR_FETCH (21) +#define SUIT_DIR_COPY (22) +#define SUIT_DIR_RUN (23) +#define SUIT_DIR_WAIT (29) +#define SUIT_DIR_RUN_SEQUENCE (30) +#define SUIT_DIR_RUN_WITH_ARGS (31) +#define SUIT_DIR_SWAP (32) +/** @} */ + +/** + * @brief suit handler prototype + * + * @param manifest SUIT manifest context + * @param key SUIT map index of this content + * @param it nanocbor_value_t iterator to the content + * + * @return SUIT_OK on success + * @return negative on error + */ +typedef int (*suit_manifest_handler_t)(suit_manifest_t *manifest, int key, + nanocbor_value_t *it); + +/** + * @brief global handler reference + */ +extern const suit_manifest_handler_t suit_global_handlers[]; +extern const size_t suit_global_handlers_len; + +/** + * @brief SUIT sequence handler reference + */ +extern const suit_manifest_handler_t suit_sequence_handlers[]; + +/** + * @brief SUIT sequence handler length + */ +extern const size_t suit_sequence_handlers_len; + +/** + * @brief SUIT container handlers reference + */ +extern const suit_manifest_handler_t suit_container_handlers[]; + +/** + * @brief length of the SUIT container handlers + */ +extern const size_t suit_container_handlers_len; + +/** + * @brief SUIT common handlers reference + */ +extern const suit_manifest_handler_t suit_common_handlers[]; + +/** + * @brief length of the SUIT common handlers + */ +extern const size_t suit_common_handlers_len; + +/** + * @brief Manifest structure handler function + * + * Iterates over the supplied nanocbor map or array and calls the manifest + * handler function for every key. + * + * @param manifest SUIT manifest context + * @param it Nanocbor map/array element + * @param handlers Array of SUIT manifest handlers to use + * @param handlers_len Length of the SUIT manifest handlers + * + * @returns SUIT_OK if all handlers executed succesfully + * @returns negative on error, see @ref suit_v3_error_t + */ +int suit_handle_manifest_structure(suit_manifest_t *manifest, + nanocbor_value_t *it, + const suit_manifest_handler_t *handlers, + size_t handlers_len); + +/** + * @brief Byte string wrapped manifest structure handler function + * + * Extracts the nanocbor byte string and Iterates over the CBOR map or array + * contained in the bytestring and calls the manifest handler function for + * every key. + * + * @param manifest SUIT manifest context + * @param bseq Nanocbor byte string + * @param handlers Array of SUIT manifest handlers to use + * @param handlers_len Length of the SUIT manifest handlers + * + * @returns SUIT_OK if all handlers executed succesfully + * @returns negative on error, see @ref suit_v3_error_t + */ +int suit_handle_manifest_structure_bstr(suit_manifest_t *manifest, + nanocbor_value_t *bseq, + const suit_manifest_handler_t *handlers, + size_t handlers_len); + +#ifdef __cplusplus +} +#endif + +#endif /* SUIT_V3_HANDLERS_H */ +/** @} */ diff --git a/sys/include/suit/policy.h b/sys/include/suit/policy.h new file mode 100644 index 0000000000..d8200a626d --- /dev/null +++ b/sys/include/suit/policy.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * 2019 Kaspar Schleiser + * 2019 Inria + * 2019 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @ingroup sys_suit_v3 + * @brief SUIT policy definitions + * + * @{ + * + * @brief SUIT policy definitions + * @author Koen Zandberg + * @author Kaspar Schleiser + * + */ + +#ifndef SUIT_V3_POLICY_H +#define SUIT_V3_POLICY_H + +#include +#include + +#include "uuid.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name bitfield of required policies + * @{ + */ +#define SUIT_VALIDATED_AUTH 0x1 /**< currently unused */ +#define SUIT_VALIDATED_VERSION 0x2 /**< SUIT format version */ +#define SUIT_VALIDATED_SEQ_NR 0x4 /**< new seq nr > old seq nr */ +#define SUIT_VALIDATED_VENDOR 0x8 /**< vendor UUID matches */ +#define SUIT_VALIDATED_CLASS 0x10 /**< class UUID matches */ +#define SUIT_VALIDATED_DEVICE 0x20 /**< device UUID matches */ +/** @} */ + +/** + * @brief SUIT default policy + */ +#define SUIT_DEFAULT_POLICY \ + (SUIT_VALIDATED_VERSION | SUIT_VALIDATED_SEQ_NR | SUIT_VALIDATED_VENDOR | \ + SUIT_VALIDATED_CLASS) + +#ifdef __cplusplus +} +#endif + +#endif /* SUIT_V3_POLICY_H */ +/** @} */ diff --git a/sys/shell/commands/sc_suit.c b/sys/shell/commands/sc_suit.c index 29ae844e03..89e0320f09 100644 --- a/sys/shell/commands/sc_suit.c +++ b/sys/shell/commands/sc_suit.c @@ -22,7 +22,7 @@ #include #include -#include "suit/coap.h" +#include "suit/transport/coap.h" int _suit_handler(int argc, char **argv) diff --git a/sys/suit/Makefile b/sys/suit/Makefile index 5d9edcc723..c763337223 100644 --- a/sys/suit/Makefile +++ b/sys/suit/Makefile @@ -1,9 +1,5 @@ -SUBMODULES := 1 - -# don't complain about missing submodule .c file. -# necessary to not fail for suit_v*_*. -SUBMODULES_NOFORCE := 1 - -DIRS += v4 +ifneq (,$(filter suit_transport_%,$(USEMODULE))) + DIRS += transport +endif include $(RIOTBASE)/Makefile.base diff --git a/sys/suit/handlers.c b/sys/suit/handlers.c new file mode 100644 index 0000000000..5d094a3229 --- /dev/null +++ b/sys/suit/handlers.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @ingroup sys_suit_v3 + * @{ + * + * @file + * @brief SUIT draft-ietf-suit-manifest-03 content handler helper + * functions + * + * @author Koen Zandberg + * + * @} + */ + +#include +#include + +#include "suit/handlers.h" +#include "suit.h" + +#include "log.h" + +static suit_manifest_handler_t _get_handler(int key, + const suit_manifest_handler_t *map, + size_t len) +{ + if (key < 0 || (size_t)key >= len) { + return NULL; + } + return map[key]; +} + +int suit_handle_manifest_structure(suit_manifest_t *manifest, + nanocbor_value_t *it, + const suit_manifest_handler_t *handlers, + size_t handlers_len) +{ + LOG_DEBUG("Handling command sequence\n"); + nanocbor_value_t container; + + if ((nanocbor_enter_array(it, &container) < 0) && + (nanocbor_enter_map(it, &container) < 0)) { + LOG_DEBUG("Neither array nor map: %d\n", nanocbor_get_type(it)); + return SUIT_ERR_INVALID_MANIFEST; + } + + while (!nanocbor_at_end(&container)) { + int32_t key; + if (nanocbor_get_int32(&container, &key) < 0) { + LOG_DEBUG("No key found: %d\n", nanocbor_get_type(&container)); + return SUIT_ERR_INVALID_MANIFEST; + } + nanocbor_value_t value = container; + LOG_DEBUG("Executing handler with key %" PRIi32 "\n", key); + suit_manifest_handler_t handler = _get_handler(key, handlers, + handlers_len); + if (!handler) { + return SUIT_ERR_UNSUPPORTED; + } + int res = handler(manifest, key, &value); + if (res < 0) { + LOG_DEBUG("Sequence handler error\n"); + return res; + } + nanocbor_skip(&container); + } + nanocbor_leave_container(it, &container); + LOG_DEBUG("Leaving sequence handler\n"); + + return 0; +} + +int suit_handle_manifest_structure_bstr(suit_manifest_t *manifest, + nanocbor_value_t *bseq, + const suit_manifest_handler_t *handlers, + size_t handlers_len) +{ + const uint8_t *buf; + size_t len; + + LOG_DEBUG("Handling command sequence starting with CBOR type %d\n", + nanocbor_get_type(bseq)); + if (nanocbor_get_bstr(bseq, &buf, &len) < 0) { + return SUIT_ERR_INVALID_MANIFEST; + } + + nanocbor_value_t it; + nanocbor_decoder_init(&it, buf, len); + return suit_handle_manifest_structure(manifest, &it, handlers, + handlers_len); +} diff --git a/sys/suit/handlers_common.c b/sys/suit/handlers_common.c new file mode 100644 index 0000000000..afb4aa000a --- /dev/null +++ b/sys/suit/handlers_common.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * 2020 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @ingroup sys_suit_v3 + * @{ + * + * @file + * @brief SUIT draft-ietf-suit-manifest-03 Handler implementations for + * the Common manifest sections + * + * @author Koen Zandberg + * + * @} + */ + +#include +#include + +#ifdef MODULE_SUIT_COAP +#include "suit/coap.h" +#endif + +#include "kernel_defines.h" +#include "suit/handlers.h" +#include "suit.h" + +#include "log.h" + +static int _component_handler(suit_manifest_t *manifest, int key, + nanocbor_value_t *it) +{ + (void)manifest; + (void)key; + const uint8_t *subcbor; + size_t sub_size; + nanocbor_value_t _it; + nanocbor_get_bstr(it, &subcbor, &sub_size); + nanocbor_decoder_init(&_it, subcbor, sub_size); + + /* This is a list of lists, something like: + * [ + * [ "sda" "firmwareA" ], + * [ "sda" "firmwareB" ] + * ] + * */ + nanocbor_value_t arr; + if (nanocbor_enter_array(&_it, &arr) < 0) { + LOG_DEBUG("components field not an array %d\n", nanocbor_get_type(it)); + return SUIT_ERR_INVALID_MANIFEST; + } + unsigned n = 0; + while (!nanocbor_at_end(&arr)) { + nanocbor_value_t comp; + if (nanocbor_enter_array(&arr, &comp) < 0) { + LOG_DEBUG("component elements field not an array %d\n", + nanocbor_get_type(it)); + return SUIT_ERR_INVALID_MANIFEST; + } + while (!nanocbor_at_end(&comp)) { + const uint8_t *identifier; + size_t id_len; + if (nanocbor_get_bstr(&comp, &identifier, &id_len) < 0) { + LOG_DEBUG("Component name not a byte string\n"); + return SUIT_ERR_INVALID_MANIFEST; + } + } + nanocbor_leave_container(&arr, &comp); + n++; + } + if (n > 1) { + LOG_INFO("More than 1 component found, exiting\n"); + return SUIT_ERR_UNSUPPORTED; + } + manifest->state |= SUIT_MANIFEST_HAVE_COMPONENTS; + return 0; +} + +static int _dependencies_handler(suit_manifest_t *manifest, int key, + nanocbor_value_t *it) +{ + (void)manifest; + (void)key; + (void)it; + /* No dependency support */ + return SUIT_ERR_UNSUPPORTED; +} + +int _common_sequence_handler(suit_manifest_t *manifest, int key, + nanocbor_value_t *it) +{ + (void)key; + LOG_DEBUG("Starting conditional sequence handler\n"); + return suit_handle_manifest_structure_bstr(manifest, it, + suit_sequence_handlers, + suit_sequence_handlers_len); +} + +/* begin{code-style-ignore} */ +const suit_manifest_handler_t suit_common_handlers[] = { + [SUIT_COMMON_DEPENDENCIES] = _dependencies_handler, + [SUIT_COMMON_COMPONENTS] = _component_handler, + [SUIT_COMMON_COMMAND_SEQUENCE] = _common_sequence_handler, +}; +/* end{code-style-ignore} */ + +const size_t suit_common_handlers_len = ARRAY_SIZE(suit_common_handlers); diff --git a/sys/suit/handlers_common_seq.c b/sys/suit/handlers_common_seq.c new file mode 100644 index 0000000000..700b212f08 --- /dev/null +++ b/sys/suit/handlers_common_seq.c @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * 2020 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @ingroup sys_suit_v3 + * @{ + * + * @file + * @brief SUIT draft-ietf-suit-manifest-03 Handlers for the command + * sequences in the common section + * + * @author Koen Zandberg + * + * @} + */ + +#include +#include + +#include "kernel_defines.h" +#include "suit/coap.h" +#include "suit/conditions.h" +#include "suit/handlers.h" +#include "suit/policy.h" +#include "suit.h" +#include "riotboot/hdr.h" +#include "riotboot/slot.h" + +#include "log.h" + +static int _validate_uuid(suit_manifest_t *manifest, + nanocbor_value_t *it, + uuid_t *uuid) +{ + (void)manifest; + const uint8_t *uuid_manifest_ptr; + size_t len = sizeof(uuid_t); + char uuid_str[UUID_STR_LEN + 1]; + char uuid_str2[UUID_STR_LEN + 1]; + if (nanocbor_get_bstr(it, &uuid_manifest_ptr, &len) < 0) { + return SUIT_ERR_INVALID_MANIFEST; + } + + uuid_to_string((uuid_t *)uuid_manifest_ptr, uuid_str); + uuid_to_string(uuid, uuid_str2); + LOG_INFO("Comparing %s to %s from manifest\n", uuid_str2, uuid_str); + return uuid_equal(uuid, (uuid_t *)uuid_manifest_ptr) + ? SUIT_OK + : SUIT_ERR_COND; +} + +static int _cond_vendor_handler(suit_manifest_t *manifest, + int key, + nanocbor_value_t *it) +{ + (void)key; + LOG_INFO("validating vendor ID\n"); + int rc = _validate_uuid(manifest, it, suit_get_vendor_id()); + if (rc == SUIT_OK) { + LOG_INFO("validating vendor ID: OK\n"); + manifest->validated |= SUIT_VALIDATED_VENDOR; + } + return rc; +} + +static int _cond_class_handler(suit_manifest_t *manifest, + int key, + nanocbor_value_t *it) +{ + (void)key; + LOG_INFO("validating class id\n"); + int rc = _validate_uuid(manifest, it, suit_get_class_id()); + if (rc == SUIT_OK) { + LOG_INFO("validating class id: OK\n"); + manifest->validated |= SUIT_VALIDATED_CLASS; + } + return rc; +} + +static int _cond_comp_offset(suit_manifest_t *manifest, + int key, + nanocbor_value_t *it) +{ + (void)manifest; + (void)key; + uint32_t offset; + + int rc = nanocbor_get_uint32(it, &offset); + if (rc < 0) { + LOG_WARNING("_cond_comp_offset(): expected int, got rc=%i type=%i\n", + rc, nanocbor_get_type(it)); + return SUIT_ERR_INVALID_MANIFEST; + } + uint32_t other_offset = (uint32_t)riotboot_slot_offset( + riotboot_slot_other()); + + LOG_INFO("Comparing manifest offset %u with other slot offset %u\n", + (unsigned)offset, (unsigned)other_offset); + return other_offset == offset ? SUIT_OK : SUIT_ERR_COND; +} + +static int _dtv_set_comp_idx(suit_manifest_t *manifest, + int key, + nanocbor_value_t *it) +{ + (void)key; + if (nanocbor_get_type(it) == NANOCBOR_TYPE_FLOAT) { + LOG_DEBUG("_dtv_set_comp_idx() ignoring boolean and floats\n)"); + nanocbor_skip(it); + } + else if (nanocbor_get_uint32(it, &manifest->component_current) < 0) { + return SUIT_ERR_INVALID_MANIFEST; + } + LOG_DEBUG("Setting component index to %d\n", + (int)manifest->component_current); + return 0; +} + +static int _dtv_run_seq_cond(suit_manifest_t *manifest, + int key, + nanocbor_value_t *it) +{ + (void)key; + LOG_DEBUG("Starting conditional sequence handler\n"); + return suit_handle_manifest_structure_bstr(manifest, it, + suit_sequence_handlers, + suit_sequence_handlers_len); +} + +static int _dtv_try_each(suit_manifest_t *manifest, + int key, nanocbor_value_t *it) +{ + (void)key; + LOG_DEBUG("Starting suit-directive-try-each handler\n"); + nanocbor_value_t container; + + if ((nanocbor_enter_array(it, &container) < 0) && + (nanocbor_enter_map(it, &container) < 0)) { + return SUIT_ERR_INVALID_MANIFEST; + } + + int res = SUIT_ERR_COND; + while (!nanocbor_at_end(&container)) { + nanocbor_value_t _container = container; + /* `_container` should be CBOR _bstr wrapped according to the spec, but + * it is not */ + res = suit_handle_manifest_structure(manifest, &_container, + suit_sequence_handlers, + suit_sequence_handlers_len); + + nanocbor_skip(&container); + + if (res != SUIT_ERR_COND) { + break; + } + } + + return res; +} + +static int _param_get_uri_list(suit_manifest_t *manifest, + nanocbor_value_t *it) +{ + LOG_DEBUG("got url list\n"); + manifest->components[manifest->component_current].url = *it; + return 0; +} +static int _param_get_digest(suit_manifest_t *manifest, nanocbor_value_t *it) +{ + LOG_DEBUG("got digest\n"); + manifest->components[manifest->component_current].digest = *it; + return 0; +} + +static int _param_get_img_size(suit_manifest_t *manifest, + nanocbor_value_t *it) +{ + int res = nanocbor_get_uint32(it, &manifest->components[0].size); + + if (res < 0) { + LOG_DEBUG("error getting image size\n"); + return res; + } + return res; +} + +static int _dtv_set_param(suit_manifest_t *manifest, int key, + nanocbor_value_t *it) +{ + (void)key; + /* `it` points to the entry of the map containing the type and value */ + nanocbor_value_t map; + + nanocbor_enter_map(it, &map); + + while (!nanocbor_at_end(&map)) { + /* map points to the key of the param */ + int32_t param_key; + nanocbor_get_int32(&map, ¶m_key); + LOG_DEBUG("Setting component index to %" PRIi32 "\n", + manifest->component_current); + LOG_DEBUG("param_key=%" PRIi32 "\n", param_key); + int res; + switch (param_key) { + case 6: /* SUIT URI LIST */ + res = _param_get_uri_list(manifest, &map); + break; + case 11: /* SUIT DIGEST */ + res = _param_get_digest(manifest, &map); + break; + case 12: /* SUIT IMAGE SIZE */ + res = _param_get_img_size(manifest, &map); + break; + default: + LOG_DEBUG("Unsupported parameter %" PRIi32 "\n", param_key); + res = SUIT_ERR_UNSUPPORTED; + } + + nanocbor_skip(&map); + + if (res) { + return res; + } + } + return SUIT_OK; +} + +static int _dtv_fetch(suit_manifest_t *manifest, int key, + nanocbor_value_t *_it) +{ + (void)key; (void)_it; + LOG_DEBUG("_dtv_fetch() key=%i\n", key); + + const uint8_t *url; + size_t url_len; + + int err = nanocbor_get_tstr(&manifest->components[0].url, &url, &url_len); + if (err < 0) { + LOG_DEBUG("URL parsing failed\n)"); + return err; + } + memcpy(manifest->urlbuf, url, url_len); + manifest->urlbuf[url_len] = '\0'; + + LOG_DEBUG("_dtv_fetch() fetching \"%s\" (url_len=%u)\n", manifest->urlbuf, + (unsigned)url_len); + + int target_slot = riotboot_slot_other(); + riotboot_flashwrite_init(manifest->writer, target_slot); + + int res = -1; + + if (0) {} +#ifdef MODULE_SUIT_TRANSPORT_COAP + else if (strncmp(manifest->urlbuf, "coap://", 7) == 0) { + res = suit_coap_get_blockwise_url(manifest->urlbuf, COAP_BLOCKSIZE_64, + suit_flashwrite_helper, + manifest); + } +#endif +#ifdef MODULE_SUIT_TRANSPORT_MOCK + else if (strncmp(manifest->urlbuf, "test://", 7) == 0) { + res = SUIT_OK; + } +#endif + else { + LOG_WARNING("suit: unsupported URL scheme!\n)"); + return res; + } + + if (res) { + LOG_INFO("image download failed\n)"); + return res; + } + + manifest->state |= SUIT_MANIFEST_HAVE_IMAGE; + + LOG_DEBUG("Update OK\n"); + return SUIT_OK; +} + +static int _dtv_verify_image_match(suit_manifest_t *manifest, int key, + nanocbor_value_t *_it) +{ + (void)key; (void)_it; + LOG_DEBUG("dtv_image_match\n"); + const uint8_t *digest; + size_t digest_len; + int target_slot = riotboot_slot_other(); + + LOG_INFO("Verifying image digest\n"); + nanocbor_value_t _v = manifest->components[0].digest; + int res = nanocbor_get_subcbor(&_v, &digest, &digest_len); + if (res < 0) { + LOG_DEBUG("Unable to parse digest structure\n"); + return SUIT_ERR_INVALID_MANIFEST; + } + + /* "digest" points to a 36 byte string that includes the digest type. + * riotboot_flashwrite_verify_sha256() is only interested in the 32b digest, + * so shift the pointer accordingly. + */ + res = riotboot_flashwrite_verify_sha256(digest + 4, + manifest->components[0].size, + target_slot); + if (res != 0) { + return SUIT_ERR_COND; + } + return SUIT_OK; +} + +/* begin{code-style-ignore} */ +const suit_manifest_handler_t suit_sequence_handlers[] = { + [SUIT_COND_VENDOR_ID] = _cond_vendor_handler, + [SUIT_COND_CLASS_ID] = _cond_class_handler, + [SUIT_COND_IMAGE_MATCH] = _dtv_verify_image_match, + [SUIT_COND_COMPONENT_OFFSET] = _cond_comp_offset, + [SUIT_DIR_SET_COMPONENT_IDX] = _dtv_set_comp_idx, + [SUIT_DIR_TRY_EACH] = _dtv_try_each, + [SUIT_DIR_SET_PARAM] = _dtv_set_param, + [SUIT_DIR_OVERRIDE_PARAM] = _dtv_set_param, + [SUIT_DIR_FETCH] = _dtv_fetch, + [SUIT_DIR_RUN_SEQUENCE] = _dtv_run_seq_cond, +}; +/* end{code-style-ignore} */ + +const size_t suit_sequence_handlers_len = ARRAY_SIZE(suit_sequence_handlers); diff --git a/sys/suit/handlers_container.c b/sys/suit/handlers_container.c new file mode 100644 index 0000000000..0184acda3a --- /dev/null +++ b/sys/suit/handlers_container.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * 2020 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @ingroup sys_suit_v3 + * @{ + * + * @file + * @brief SUIT draft-ietf-suit-manifest-03 Handlers for the outer SUIT + * container + * + * @author Koen Zandberg + * + * @} + */ + +#include +#include + +#include "hashes/sha256.h" +#include "kernel_defines.h" +#include "log.h" +#include "public_key.h" +#include "suit/conditions.h" +#include "suit/handlers.h" +#include "suit.h" + +static int _auth_handler(suit_manifest_t *manifest, int key, + nanocbor_value_t *it) +{ + (void)key; + cose_sign_dec_t verify; + const uint8_t *cose_buf; + const uint8_t *cose_container; + size_t container_len; + size_t cose_len = 0; + /* It is a list of cose signatures */ + int res = nanocbor_get_bstr(it, &cose_container, &container_len); + if (res < 0) { + LOG_INFO("Unable to get COSE signature\n"); + return SUIT_ERR_INVALID_MANIFEST; + } + + nanocbor_value_t _cont, arr; + nanocbor_decoder_init(&_cont, cose_container, container_len); + + int rc = nanocbor_enter_array(&_cont, &arr); + if (rc < 0) { + LOG_INFO("Unable to enter COSE signatures\n"); + return SUIT_ERR_INVALID_MANIFEST; + } + + uint32_t tag; + nanocbor_get_tag(&arr, &tag); + arr.remaining++; + res = nanocbor_get_subcbor(&arr, &cose_buf, &cose_len); + if (res < 0) { + LOG_INFO("Unable to get subcbor: %d\n", res); + } + + res = cose_sign_decode(&verify, cose_buf, cose_len); + if (res < 0) { + LOG_INFO("Unable to parse COSE signature\n"); + return SUIT_ERR_INVALID_MANIFEST; + } + + /* Iterate over signatures, should only be a single signature */ + cose_signature_dec_t signature; + + cose_sign_signature_iter_init(&signature); + if (!cose_sign_signature_iter(&verify, &signature)) { + LOG_INFO("Unable to get signature iteration\n"); + return SUIT_ERR_INVALID_MANIFEST; + } + + /* Initialize key from hardcoded public key */ + cose_key_t pkey; + cose_key_init(&pkey); + cose_key_set_keys(&pkey, COSE_EC_CURVE_ED25519, COSE_ALGO_EDDSA, + (uint8_t *)public_key, NULL, NULL); + + LOG_INFO("suit: verifying manifest signature\n"); + int verification = cose_sign_verify(&verify, &signature, + &pkey, manifest->validation_buf, + SUIT_COSE_BUF_SIZE); + if (verification != 0) { + LOG_INFO("Unable to validate signature: %d\n", verification); + return SUIT_ERR_SIGNATURE; + } + + manifest->cose_payload = verify.payload; + manifest->cose_payload_len = verify.payload_len; + manifest->state |= SUIT_STATE_COSE_AUTHENTICATED; + + return 0; +} + +static int _manifest_handler(suit_manifest_t *manifest, int key, + nanocbor_value_t *it) +{ + (void)key; + const uint8_t *manifest_buf; + size_t manifest_len; + + if (!(manifest->state & SUIT_STATE_COSE_AUTHENTICATED)) { + return SUIT_ERR_SIGNATURE; + } + + nanocbor_value_t cbor_buf = *it; + + nanocbor_get_subcbor(&cbor_buf, &manifest_buf, &manifest_len); + + uint8_t digest_struct[4 + SHA256_DIGEST_LENGTH] = + /* CBOR array of length 2, sha256 digest and a bytestring of SHA256 + * length + */ + { 0x82, 0x02, 0x58, SHA256_DIGEST_LENGTH }; + sha256(manifest_buf, manifest_len, digest_struct + 4); + + /* The COSE payload and the sha256 of the manifest itself is public info and + * verification does not depend on secret info. No need for cryptographic + * memcmp here */ + if (memcmp(digest_struct, manifest->cose_payload, + sizeof(digest_struct)) != 0) { + LOG_ERROR("SUIT manifest digest and COSE digest mismatch\n"); + return SUIT_ERR_DIGEST_MISMATCH; + } + + manifest->state |= SUIT_STATE_FULLY_AUTHENTICATED; + + LOG_DEBUG("Starting global sequence handler\n"); + return suit_handle_manifest_structure_bstr(manifest, it, + suit_global_handlers, + suit_global_handlers_len); +} + +/* begin{code-style-ignore} */ +const suit_manifest_handler_t suit_container_handlers[] = { + [SUIT_WRAPPER_AUTHENTICATION] = _auth_handler, + [SUIT_WRAPPER_MANIFEST] = _manifest_handler, +}; +/* end{code-style-ignore} */ + +const size_t suit_container_handlers_len = ARRAY_SIZE(suit_container_handlers); diff --git a/sys/suit/handlers_global.c b/sys/suit/handlers_global.c new file mode 100644 index 0000000000..002c2b2825 --- /dev/null +++ b/sys/suit/handlers_global.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * 2020 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @ingroup sys_suit_v3 + * @{ + * + * @file + * @brief SUIT draft-ietf-suit-manifest-03 Handlers for the global SUIT + * manifest content. + * + * @author Koen Zandberg + * + * @} + */ + +#include +#include + +#include "kernel_defines.h" +#include "log.h" +#include "suit/conditions.h" +#include "suit/handlers.h" +#include "suit/policy.h" +#include "suit.h" + +extern int _common_sequence_handler(suit_manifest_t *manifest, int key, + nanocbor_value_t *it); + +static int _version_handler(suit_manifest_t *manifest, int key, + nanocbor_value_t *it) +{ + (void)manifest; + (void)key; + /* Validate manifest version */ + int32_t version = -1; + if (nanocbor_get_int32(it, &version) >= 0) { + if (version == SUIT_VERSION) { + manifest->validated |= SUIT_VALIDATED_VERSION; + LOG_INFO("suit: validated manifest version\n)"); + return SUIT_OK; + } + } + return SUIT_ERR_SEQUENCE_NUMBER; +} + +static int _seq_no_handler(suit_manifest_t *manifest, int key, + nanocbor_value_t *it) +{ + (void)key; + + int32_t seq_nr; + + if (nanocbor_get_int32(it, &seq_nr) < 0) { + LOG_INFO("Unable to get sequence number\n"); + return SUIT_ERR_INVALID_MANIFEST; + } + const riotboot_hdr_t *hdr = riotboot_slot_get_hdr(riotboot_slot_current()); + if (seq_nr <= (int32_t)hdr->version) { + LOG_INFO("%" PRId32 " <= %" PRId32 "\n", seq_nr, hdr->version); + LOG_INFO("seq_nr <= running image\n)"); + return SUIT_ERR_SEQUENCE_NUMBER; + } + + hdr = riotboot_slot_get_hdr(riotboot_slot_other()); + if (riotboot_hdr_validate(hdr) == 0) { + if (seq_nr <= (int32_t)hdr->version) { + LOG_INFO("%" PRIu32 " <= %" PRIu32 "\n", seq_nr, hdr->version); + LOG_INFO("seq_nr <= other image\n)"); + return SUIT_ERR_SEQUENCE_NUMBER; + } + } + LOG_INFO("suit: validated sequence number\n)"); + manifest->validated |= SUIT_VALIDATED_SEQ_NR; + return SUIT_OK; + +} + +static int _common_handler(suit_manifest_t *manifest, int key, + nanocbor_value_t *it) +{ + (void)key; + LOG_DEBUG("Starting common section handler\n"); + return suit_handle_manifest_structure_bstr(manifest, it, + suit_common_handlers, + suit_common_handlers_len); +} + +/* begin{code-style-ignore} */ +const suit_manifest_handler_t suit_global_handlers[] = { + [ 0] = NULL, + [SUIT_CONTAINER_VERSION] = _version_handler, + [SUIT_CONTAINER_SEQ_NO] = _seq_no_handler, + [SUIT_CONTAINER_COMMON] = _common_handler, + /* Install and validate both consist of a command sequence */ + [SUIT_CONTAINER_INSTALL] = _common_sequence_handler, + [SUIT_CONTAINER_VALIDATE] = _common_sequence_handler, +}; +/* end{code-style-ignore} */ + +const size_t suit_global_handlers_len = ARRAY_SIZE(suit_global_handlers); diff --git a/sys/suit/policy.c b/sys/suit/policy.c new file mode 100644 index 0000000000..875212ca18 --- /dev/null +++ b/sys/suit/policy.c @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 Kaspar Schleiser + * 2019 Inria + * 2019 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @ingroup sys_suit_v3 + * @{ + * + * @file + * @brief SUIT draft-ietf-suit-manifest-03 policy checking code + * + * @author Kaspar Schleiser + * + * @} + */ + +#include "log.h" +#include "suit/policy.h" +#include "suit.h" + +int suit_policy_check(suit_manifest_t *manifest) +{ + if (SUIT_DEFAULT_POLICY & ~(manifest->validated)) { + LOG_INFO("SUIT policy check failed!\n"); + return -1; + } + else { + LOG_INFO("SUIT policy check OK.\n"); + return 0; + } +} diff --git a/sys/suit/suit.c b/sys/suit/suit.c new file mode 100644 index 0000000000..46af71daec --- /dev/null +++ b/sys/suit/suit.c @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 Freie Universität Berlin + * Copyright (C) 2019 Kaspar Schleiser + * 2020 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @ingroup sys_suit + * @{ + * + * @file + * @brief SUIT draft-ietf-suit-manifest-03 manifest parser library for + * CBOR based manifests + * + * @author Koen Zandberg + * @author Kaspar Schleiser + * + * @} + */ + +#include +#include +#include +#include + +#include "log.h" +#include "suit/handlers.h" +#include "suit/policy.h" +#include "suit.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +int suit_parse(suit_manifest_t *manifest, const uint8_t *buf, + size_t len) +{ + nanocbor_value_t it; + + manifest->buf = buf; + manifest->len = len; + nanocbor_decoder_init(&it, buf, len); + LOG_DEBUG("Starting container sequence handler\n"); + return suit_handle_manifest_structure(manifest, &it, + suit_container_handlers, + suit_container_handlers_len); +} diff --git a/sys/suit/transport/Makefile b/sys/suit/transport/Makefile new file mode 100644 index 0000000000..fbda59b2f9 --- /dev/null +++ b/sys/suit/transport/Makefile @@ -0,0 +1,5 @@ +MODULE := suit_transport +SUBMODULES := 1 +BASE_MODULE := suit_transport + +include $(RIOTBASE)/Makefile.base diff --git a/sys/suit/coap.c b/sys/suit/transport/coap.c similarity index 87% rename from sys/suit/coap.c rename to sys/suit/transport/coap.c index 7873b5f9a7..e32cf54f71 100644 --- a/sys/suit/coap.c +++ b/sys/suit/transport/coap.c @@ -40,8 +40,8 @@ #include "riotboot/flashwrite.h" #endif -#ifdef MODULE_SUIT_V4 -#include "suit/v4/suit.h" +#ifdef MODULE_SUIT +#include "suit.h" #endif #if defined(MODULE_PROGRESS_BAR) @@ -53,7 +53,7 @@ #ifndef SUIT_COAP_STACKSIZE /* allocate stack needed to keep a page buffer and do manifest validation */ -#define SUIT_COAP_STACKSIZE (3*THREAD_STACKSIZE_LARGE + FLASHPAGE_SIZE) +#define SUIT_COAP_STACKSIZE (3 * THREAD_STACKSIZE_LARGE + FLASHPAGE_SIZE) #endif #ifndef SUIT_COAP_PRIO @@ -74,8 +74,9 @@ static char _stack[SUIT_COAP_STACKSIZE]; static char _url[SUIT_URL_MAX]; static uint8_t _manifest_buf[SUIT_MANIFEST_BUFSIZE]; -#ifdef MODULE_SUIT_V4 -static inline void _print_download_progress(size_t offset, size_t len, uint32_t image_size) +#ifdef MODULE_SUIT_V3 +static inline void _print_download_progress(size_t offset, size_t len, + uint32_t image_size) { (void)offset; (void)len; @@ -145,6 +146,7 @@ static inline uint32_t deadline_from_interval(int32_t interval) static inline uint32_t deadline_left(uint32_t deadline) { int32_t left = (int32_t)(deadline - _now()); + if (left < 0) { left = 0; } @@ -155,7 +157,7 @@ static ssize_t _nanocoap_request(sock_udp_t *sock, coap_pkt_t *pkt, size_t len) { ssize_t res = -EAGAIN; size_t pdu_len = (pkt->payload - (uint8_t *)pkt->hdr) + pkt->payload_len; - uint8_t *buf = (uint8_t*)pkt->hdr; + uint8_t *buf = (uint8_t *)pkt->hdr; uint32_t id = coap_get_id(pkt); /* TODO: timeout random between between ACK_TIMEOUT and (ACK_TIMEOUT * @@ -163,7 +165,9 @@ static ssize_t _nanocoap_request(sock_udp_t *sock, coap_pkt_t *pkt, size_t len) uint32_t timeout = COAP_ACK_TIMEOUT * US_PER_SEC; uint32_t deadline = deadline_from_interval(timeout); - unsigned tries_left = COAP_MAX_RETRANSMIT + 1; /* add 1 for initial transmit */ + /* add 1 for initial transmit */ + unsigned tries_left = COAP_MAX_RETRANSMIT + 1; + while (tries_left) { if (res == -EAGAIN) { res = sock_udp_send(sock, buf, pdu_len, NULL); @@ -210,14 +214,19 @@ static ssize_t _nanocoap_request(sock_udp_t *sock, coap_pkt_t *pkt, size_t len) return res; } -static int _fetch_block(coap_pkt_t *pkt, uint8_t *buf, sock_udp_t *sock, const char *path, coap_blksize_t blksize, size_t num) +static int _fetch_block(coap_pkt_t *pkt, uint8_t *buf, sock_udp_t *sock, + const char *path, coap_blksize_t blksize, size_t num) { uint8_t *pktpos = buf; + pkt->hdr = (coap_hdr_t *)buf; - pktpos += coap_build_hdr(pkt->hdr, COAP_TYPE_CON, NULL, 0, COAP_METHOD_GET, num); + pktpos += coap_build_hdr(pkt->hdr, COAP_TYPE_CON, NULL, 0, COAP_METHOD_GET, + num); pktpos += coap_opt_put_uri_path(pktpos, 0, path); - pktpos += coap_opt_put_uint(pktpos, COAP_OPT_URI_PATH, COAP_OPT_BLOCK2, (num << 4) | blksize); + pktpos += + coap_opt_put_uint(pktpos, COAP_OPT_URI_PATH, COAP_OPT_BLOCK2, + (num << 4) | blksize); pkt->payload = pktpos; pkt->payload_len = 0; @@ -237,8 +246,8 @@ static int _fetch_block(coap_pkt_t *pkt, uint8_t *buf, sock_udp_t *sock, const c } int suit_coap_get_blockwise(sock_udp_ep_t *remote, const char *path, - coap_blksize_t blksize, - coap_blockwise_cb_t callback, void *arg) + coap_blksize_t blksize, + coap_blockwise_cb_t callback, void *arg) { /* mmmmh dynamically sized array */ uint8_t buf[64 + (0x1 << (blksize + 4))]; @@ -248,14 +257,12 @@ int suit_coap_get_blockwise(sock_udp_ep_t *remote, const char *path, /* HACK: use random local port */ local.port = 0x8000 + (xtimer_now_usec() % 0XFFF); - sock_udp_t sock; int res = sock_udp_create(&sock, &local, remote, 0); if (res < 0) { return res; } - int more = 1; size_t num = 0; res = -1; @@ -269,7 +276,8 @@ int suit_coap_get_blockwise(sock_udp_ep_t *remote, const char *path, coap_get_block2(&pkt, &block2); more = block2.more; - if (callback(arg, block2.offset, pkt.payload, pkt.payload_len, more)) { + if (callback(arg, block2.offset, pkt.payload, pkt.payload_len, + more)) { DEBUG("callback res != 0, aborting.\n"); res = -1; goto out; @@ -290,8 +298,8 @@ out: } int suit_coap_get_blockwise_url(const char *url, - coap_blksize_t blksize, - coap_blockwise_cb_t callback, void *arg) + coap_blksize_t blksize, + coap_blockwise_cb_t callback, void *arg) { char hostport[CONFIG_SOCK_HOSTPORT_MAXLEN]; char urlpath[CONFIG_SOCK_URLPATH_MAXLEN]; @@ -346,25 +354,27 @@ static int _2buf(void *arg, size_t offset, uint8_t *buf, size_t len, int more) } ssize_t suit_coap_get_blockwise_url_buf(const char *url, - coap_blksize_t blksize, - uint8_t *buf, size_t len) + coap_blksize_t blksize, + uint8_t *buf, size_t len) { - _buf_t _buf = { .ptr=buf, .len=len }; + _buf_t _buf = { .ptr = buf, .len = len }; int res = suit_coap_get_blockwise_url(url, blksize, _2buf, &_buf); + return (res < 0) ? (ssize_t)res : (ssize_t)_buf.offset; } static void _suit_handle_url(const char *url) { LOG_INFO("suit_coap: downloading \"%s\"\n", url); - ssize_t size = suit_coap_get_blockwise_url_buf(url, COAP_BLOCKSIZE_64, _manifest_buf, - SUIT_MANIFEST_BUFSIZE); + ssize_t size = suit_coap_get_blockwise_url_buf(url, COAP_BLOCKSIZE_64, + _manifest_buf, + SUIT_MANIFEST_BUFSIZE); if (size >= 0) { LOG_INFO("suit_coap: got manifest with size %u\n", (unsigned)size); riotboot_flashwrite_t writer; -#ifdef MODULE_SUIT_V4 - suit_v4_manifest_t manifest; +#ifdef MODULE_SUIT_V3 + suit_manifest_t manifest; memset(&manifest, 0, sizeof(manifest)); manifest.writer = &writer; @@ -372,18 +382,18 @@ static void _suit_handle_url(const char *url) manifest.urlbuf_len = SUIT_URL_MAX; int res; - if ((res = suit_v4_parse(&manifest, _manifest_buf, size)) != SUIT_OK) { - LOG_INFO("suit_v4_parse() failed. res=%i\n", res); + if ((res = suit_v3_parse(&manifest, _manifest_buf, size)) != SUIT_OK) { + LOG_INFO("suit_v3_parse() failed. res=%i\n", res); return; } - LOG_INFO("suit_v4_parse() success\n"); + LOG_INFO("suit_v3_parse() success\n"); if (!(manifest.state & SUIT_MANIFEST_HAVE_IMAGE)) { LOG_INFO("manifest parsed, but no image fetched\n"); return; } - res = suit_v4_policy_check(&manifest); + res = suit_v3_policy_check(&manifest); if (res) { return; } @@ -393,7 +403,8 @@ static void _suit_handle_url(const char *url) LOG_INFO("suit_coap: finalizing image flash\n"); riotboot_flashwrite_finish(&writer); - const riotboot_hdr_t *hdr = riotboot_slot_get_hdr(riotboot_slot_other()); + const riotboot_hdr_t *hdr = riotboot_slot_get_hdr( + riotboot_slot_other()); riotboot_hdr_print(hdr); xtimer_sleep(1); @@ -414,7 +425,7 @@ static void _suit_handle_url(const char *url) int suit_flashwrite_helper(void *arg, size_t offset, uint8_t *buf, size_t len, int more) { - suit_v4_manifest_t *manifest = (suit_v4_manifest_t *)arg; + suit_manifest_t *manifest = (suit_manifest_t *)arg; riotboot_flashwrite_t *writer = manifest->writer; if (offset == 0) { @@ -428,8 +439,9 @@ int suit_flashwrite_helper(void *arg, size_t offset, uint8_t *buf, size_t len, } if (writer->offset != offset) { - LOG_WARNING("_suit_flashwrite(): writer->offset=%u, offset==%u, aborting\n", - (unsigned)writer->offset, (unsigned)offset); + LOG_WARNING( + "_suit_flashwrite(): writer->offset=%u, offset==%u, aborting\n", + (unsigned)writer->offset, (unsigned)offset); return -1; } @@ -481,10 +493,11 @@ static ssize_t _version_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, #ifdef MODULE_RIOTBOOT_SLOT static ssize_t _slot_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, - void *context) + void *context) { /* context is passed either as NULL or 0x1 for /active or /inactive */ char c = '0'; + if (context) { c += riotboot_slot_other(); } @@ -509,7 +522,7 @@ static ssize_t _trigger_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, } else { code = COAP_CODE_CREATED; - LOG_INFO("suit: received URL: \"%s\"\n", (char*)pkt->payload); + LOG_INFO("suit: received URL: \"%s\"\n", (char *)pkt->payload); suit_coap_trigger(pkt->payload, payload_len); } } @@ -532,9 +545,10 @@ void suit_coap_trigger(const uint8_t *url, size_t len) static const coap_resource_t _subtree[] = { #ifdef MODULE_RIOTBOOT_SLOT { "/suit/slot/active", COAP_METHOD_GET, _slot_handler, NULL }, - { "/suit/slot/inactive", COAP_METHOD_GET, _slot_handler, (void*)0x1 }, + { "/suit/slot/inactive", COAP_METHOD_GET, _slot_handler, (void *)0x1 }, #endif - { "/suit/trigger", COAP_METHOD_PUT | COAP_METHOD_POST, _trigger_handler, NULL }, + { "/suit/trigger", COAP_METHOD_PUT | COAP_METHOD_POST, _trigger_handler, + NULL }, { "/suit/version", COAP_METHOD_GET, _version_handler, NULL }, }; diff --git a/sys/suit/transport/mock.c b/sys/suit/transport/mock.c new file mode 100644 index 0000000000..3489247f75 --- /dev/null +++ b/sys/suit/transport/mock.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include +#include +#include + +#include "kernel_defines.h" +#include "cpu_conf.h" + +#include "riotboot/flashwrite.h" +#include "riotboot/hdr.h" + +#define SLOT0_OFFSET 0x1000 +#define SLOT1_OFFSET 0x2000 + +static riotboot_hdr_t _riotboot_slots[] = { + { .magic_number = RIOTBOOT_MAGIC, + .version = 1, + .start_addr=0x100000, + }, + { .magic_number = RIOTBOOT_MAGIC, + .version = 2, + .start_addr=0x200000, + }, +}; + +const riotboot_hdr_t * const riotboot_slots[] = { + &_riotboot_slots[0], + &_riotboot_slots[1], +}; + +const unsigned riotboot_slot_numof = ARRAY_SIZE(riotboot_slots); + +static int _current_slot; + +int riotboot_slot_current(void) +{ + return _current_slot; +} + +int riotboot_slot_other(void) +{ + return (_current_slot == 0) ? 1 : 0; +} + +const riotboot_hdr_t *riotboot_slot_get_hdr(unsigned slot) +{ + assert(slot < riotboot_slot_numof); + + return riotboot_slots[slot]; +} + +size_t riotboot_slot_offset(unsigned slot) +{ + return (slot == 0) ? SLOT0_OFFSET : SLOT1_OFFSET; +} + +int riotboot_flashwrite_init_raw(riotboot_flashwrite_t *state, int target_slot, + size_t offset) +{ + (void)state; + (void)target_slot; + (void)offset; + puts("riotboot_flashwrite_init_raw() empty mock"); + return 0; +} + +int riotboot_flashwrite_verify_sha256(const uint8_t *sha256_digest, + size_t img_size, int target_slot) +{ + (void)sha256_digest; + (void)img_size; + (void)target_slot; + return 0; +}