sys/suit: Add SUIT draft ietf-v3 firmware upgrade module

Co-authored-by: Kaspar Schleiser <kaspar@schleiser.de>
This commit is contained in:
Koen Zandberg 2020-02-26 14:48:24 +01:00
parent 009a317b14
commit 14bdf8f46b
No known key found for this signature in database
GPG Key ID: 0E63411F8FCA8247
21 changed files with 1624 additions and 63 deletions

View File

@ -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
# 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)))

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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)

72
makefiles/suit.v3.inc.mk Normal file
View File

@ -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."

207
sys/include/suit.h Normal file
View File

@ -0,0 +1,207 @@
/*
* Copyright (C) 2019 Koen Zandberg
* 2019 Kaspar Schleiser <kaspar@schleiser.de>
*
* 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 <koen@bergzand.net>
* @author Kaspar Schleiser <kaspar@schleiser.de>
*
*/
#ifndef SUIT_V3_SUIT_H
#define SUIT_V3_SUIT_H
#include <stddef.h>
#include <stdint.h>
#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 */
/** @} */

201
sys/include/suit/handlers.h Normal file
View File

@ -0,0 +1,201 @@
/*
* Copyright (C) 2019 Koen Zandberg
* 2019 Kaspar Schleiser <kaspar@schleiser.de>
* 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 <koen@bergzand.net>
* @author Kaspar Schleiser <kaspar@schleiser.de>
*/
#ifndef SUIT_V3_HANDLERS_H
#define SUIT_V3_HANDLERS_H
#include <stddef.h>
#include <stdint.h>
#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 */
/** @} */

59
sys/include/suit/policy.h Normal file
View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2019 Koen Zandberg
* 2019 Kaspar Schleiser <kaspar@schleiser.de>
* 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 <koen@bergzand.net>
* @author Kaspar Schleiser <kaspar@schleiser.de>
*
*/
#ifndef SUIT_V3_POLICY_H
#define SUIT_V3_POLICY_H
#include <stddef.h>
#include <stdint.h>
#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 */
/** @} */

View File

@ -22,7 +22,7 @@
#include <string.h>
#include <inttypes.h>
#include "suit/coap.h"
#include "suit/transport/coap.h"
int _suit_handler(int argc, char **argv)

View File

@ -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

97
sys/suit/handlers.c Normal file
View File

@ -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 <koen@bergzand.net>
*
* @}
*/
#include <inttypes.h>
#include <nanocbor/nanocbor.h>
#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);
}

112
sys/suit/handlers_common.c Normal file
View File

@ -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 <koen@bergzand.net>
*
* @}
*/
#include <inttypes.h>
#include <nanocbor/nanocbor.h>
#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);

View File

@ -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 <koen@bergzand.net>
*
* @}
*/
#include <inttypes.h>
#include <nanocbor/nanocbor.h>
#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, &param_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);

View File

@ -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 <koen@bergzand.net>
*
* @}
*/
#include <cose/sign.h>
#include <nanocbor/nanocbor.h>
#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);

106
sys/suit/handlers_global.c Normal file
View File

@ -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 <koen@bergzand.net>
*
* @}
*/
#include <inttypes.h>
#include <nanocbor/nanocbor.h>
#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);

36
sys/suit/policy.c Normal file
View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2019 Kaspar Schleiser <kaspar@schleiser.de>
* 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 <kaspar@schleiser.de>
*
* @}
*/
#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;
}
}

49
sys/suit/suit.c Normal file
View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2018 Freie Universität Berlin
* Copyright (C) 2019 Kaspar Schleiser <kaspar@schleiser.de>
* 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 <koen@bergzand.net>
* @author Kaspar Schleiser <kaspar@schleiser.de>
*
* @}
*/
#include <nanocbor/nanocbor.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#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);
}

View File

@ -0,0 +1,5 @@
MODULE := suit_transport
SUBMODULES := 1
BASE_MODULE := suit_transport
include $(RIOTBASE)/Makefile.base

View File

@ -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)
@ -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;
}
@ -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;
@ -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;
@ -351,20 +359,22 @@ ssize_t suit_coap_get_blockwise_url_buf(const char *url,
{
_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,
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,7 +439,8 @@ 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",
LOG_WARNING(
"_suit_flashwrite(): writer->offset=%u, offset==%u, aborting\n",
(unsigned)writer->offset, (unsigned)offset);
return -1;
}
@ -485,6 +497,7 @@ static ssize_t _slot_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len,
{
/* context is passed either as NULL or 0x1 for /active or /inactive */
char c = '0';
if (context) {
c += riotboot_slot_other();
}
@ -534,7 +547,8 @@ static const coap_resource_t _subtree[] = {
{ "/suit/slot/active", COAP_METHOD_GET, _slot_handler, NULL },
{ "/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 },
};

81
sys/suit/transport/mock.c Normal file
View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2020 Kaspar Schleiser <kaspar@schleiser.de>
*
* 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 <assert.h>
#include <stdio.h>
#include <stdbool.h>
#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;
}