sys/suit: add SUIT draft v4 firmware upgrade module

This commit adds a sys module implementing SUIT draft v4 compatible
firmware updates.

Co-authored-by: Alexandre Abadie <alexandre.abadie@inria.fr>
Co-authored-by: Koen Zandberg <koen@bergzand.net>
Co-authored-by: Francisco Molina <femolina@uc.cl>
This commit is contained in:
Kaspar Schleiser 2019-07-05 11:57:55 +02:00
parent db96f31a54
commit fb12c4aa8d
19 changed files with 2253 additions and 1 deletions

3
.gitignore vendored
View File

@ -68,3 +68,6 @@ results/
# Clangd compile flags (language server)
compile_commands.json
compile_flags.txt
# suit manifest keys
keys/

View File

@ -932,6 +932,31 @@ ifneq (,$(filter sock_dtls, $(USEMODULE)))
USEMODULE += sock_udp
endif
ifneq (,$(filter suit_v4_%,$(USEMODULE)))
USEMODULE += suit_v4
endif
ifneq (,$(filter suit_v4,$(USEMODULE)))
USEPKG += tinycbor
USEPKG += libcose
USEMODULE += libcose_crypt_hacl
USEMODULE += suit_conditions
# 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
ifneq (,$(filter suit_conditions,$(USEMODULE)))
USEMODULE += uuid
endif
ifneq (,$(filter suit_%,$(USEMODULE)))
USEMODULE += suit
endif
# Enable periph_gpio when periph_gpio_irq is enabled
ifneq (,$(filter periph_gpio_irq,$(USEMODULE)))
FEATURES_REQUIRED += periph_gpio

View File

@ -148,6 +148,11 @@ riotboot/flash: riotboot/flash-slot0 riotboot/flash-bootloader
# It also makes 'flash' and 'flash-only' work without specific command.
FLASHFILE = $(RIOTBOOT_EXTENDED_BIN)
# include suit targets
ifneq (,$(filter suit_v4, $(USEMODULE)))
include $(RIOTMAKE)/suit.v4.inc.mk
endif
else
riotboot:
$(Q)echo "error: riotboot feature not selected! (try FEATURES_REQUIRED += riotboot)"

View File

@ -84,7 +84,10 @@ PSEUDOMODULES += stdin
PSEUDOMODULES += stdio_ethos
PSEUDOMODULES += stdio_cdc_acm
PSEUDOMODULES += stdio_uart_rx
PSEUDOMODULES += sock_dtls
PSEUDOMODULES += suit_%
# handle suit_v4 being a distinct module
NO_PSEUDOMODULES += suit_v4
# print ascii representation in function od_hex_dump()
PSEUDOMODULES += od_string

103
makefiles/suit.v4.inc.mk Normal file
View File

@ -0,0 +1,103 @@
#
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.suitv4.$(APP_VER).bin
SUIT_MANIFEST_LATEST ?= $(BINDIR_APP)-riot.suitv4.latest.bin
SUIT_MANIFEST_SIGNED ?= $(BINDIR_APP)-riot.suitv4_signed.$(APP_VER).bin
SUIT_MANIFEST_SIGNED_LATEST ?= $(BINDIR_APP)-riot.suitv4_signed.latest.bin
SUIT_NOTIFY_VERSION ?= latest
SUIT_NOTIFY_MANIFEST ?= $(BINDIR_APP)-riot.suitv4_signed.$(SUIT_NOTIFY_VERSION).bin
# Long manifest names require more buffer space when parsing
export CFLAGS += -DSOCK_URLPATH_MAXLEN=128
SUIT_VENDOR ?= "riot-os.org"
SUIT_SEQNR ?= $(APP_VER)
SUIT_CLASS ?= $(BOARD)
#
# SUIT encryption keys
#
# Specify key to use.
# Will use $(SUIT_KEY_DIR)/$(SUIT_KEY) $(SUIT_KEY_DIR)/$(SUIT_KEY).pub as
# private/public key files, similar to how ssh names its 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)
SUIT_PUB ?= $(SUIT_KEY_DIR)/$(SUIT_KEY).pub
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) $(SUIT_PUB): $(CLEAN)
@echo suit: generating key pair in $(SUIT_KEY_DIR)
@mkdir -p $(SUIT_KEY_DIR)
@$(RIOTBASE)/dist/tools/suit_v4/gen_key.py $(SUIT_SEC) $(SUIT_PUB)
# 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_PUB) FORCE | $(CLEAN)
@mkdir -p $(SUIT_PUB_HDR_DIR)
@cp $(SUIT_PUB) $(SUIT_PUB_HDR_DIR)/public.key
@cd $(SUIT_PUB_HDR_DIR) && xxd -i public.key \
| '$(LAZYSPONGE)' $(LAZYSPONGE_FLAGS) '$@'
suit/genkey: $(SUIT_SEC) $(SUIT_PUB)
#
$(SUIT_MANIFEST): $(SLOT0_RIOT_BIN) $(SLOT1_RIOT_BIN)
$(RIOTBASE)/dist/tools/suit_v4/gen_manifest.py \
--template $(RIOTBASE)/dist/tools/suit_v4/test-2img.json \
--urlroot $(SUIT_COAP_ROOT) \
--seqnr $(SUIT_SEQNR) \
--uuid-vendor $(SUIT_VENDOR) \
--uuid-class $(SUIT_CLASS) \
--offsets $(SLOT0_OFFSET),$(SLOT1_OFFSET) \
-o $@ \
$^
$(SUIT_MANIFEST_SIGNED): $(SUIT_MANIFEST) $(SUIT_SEC) $(SUIT_PUB)
$(RIOTBASE)/dist/tools/suit_v4/sign-04.py \
$(SUIT_SEC) $(SUIT_PUB) $< $@
$(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 -t $(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)/$$(basename $(SUIT_NOTIFY_MANIFEST))" && \
echo "Triggered $(SUIT_CLIENT) to update."

View File

@ -154,6 +154,9 @@ endif
ifneq (,$(filter credman,$(USEMODULE)))
DIRS += net/credman
endif
ifneq (,$(filter suit%,$(USEMODULE)))
DIRS += suit
endif
DIRS += $(dir $(wildcard $(addsuffix /Makefile, $(USEMODULE))))

View File

@ -580,4 +580,9 @@ void auto_init(void)
auto_init_candev();
#endif /* MODULE_AUTO_INIT_CAN */
#ifdef MODULE_SUIT
extern void suit_init_conditions(void);
suit_init_conditions();
#endif /* MODULE_SUIT */
}

151
sys/include/suit/coap.h Normal file
View File

@ -0,0 +1,151 @@
/*
* 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.
*/
/**
* @defgroup sys_suit SUIT secure firmware updates
* @ingroup sys
* @brief SUIT secure firmware updates
*
* @experimental
*
* @{
*
* @brief SUIT CoAP helper API
* @author Kaspar Schleiser <kaspar@schleiser.de>
*
*/
#ifndef SUIT_COAP_H
#define SUIT_COAP_H
#include "net/nanocoap.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Start SUIT CoAP thread
*/
void suit_coap_run(void);
/**
* @brief SUIT CoAP endpoint entry.
*
* In order to use, include this header, then add SUIT_COAP_SUBTREE to the nanocoap endpoint array.
* Mind the alphanumerical sorting!
*
* See examples/suit_update for an example.
*/
#define SUIT_COAP_SUBTREE \
{ \
.path="/suit/", \
.methods=COAP_MATCH_SUBTREE | COAP_METHOD_GET | COAP_METHOD_POST | COAP_METHOD_PUT, \
.handler=coap_subtree_handler, \
.context=(void*)&coap_resource_subtree_suit \
}
/*
* Dear Reviewer,
*
* At the time of PR'ing this code, there was a pile of CoAP PR's waiting for
* reviews. Some of that functionality is needed in one way or another for
* SUIT. In order to not block software updates with CoAP refactoring, some of
* the work-in-progress code has been copied here. We expect this to be
* removed as soon as CoAP in master provides similar functionality.
*
* As this is internal code that will go soon, I exclude this from Doxygen.
*
* Kaspar (July 2019)
*/
#ifndef DOXYGEN
/**
* @brief Coap subtree handler
*
* @param[in,out] pkt Packet struct containing the request. Is reused for
* the response
* @param[in] buf Buffer to write reply to
* @param[in] len Total length of the buffer associated with the
* request
* @param[in] buf Buffer to write reply to
*
* @returns ssize_t Size of the reply
*/
ssize_t coap_subtree_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len,
void *context);
/**
* @brief Type for CoAP resource subtrees
*/
typedef const struct {
const coap_resource_t *resources; /**< ptr to resource array */
const size_t resources_numof; /**< nr of entries in array */
} coap_resource_subtree_t;
/**
* @brief Coap blockwise request callback descriptor
*
* @param[in] arg Pointer to be passed as arguments to the callback
* @param[in] offset Offset of received data
* @param[in] buf Pointer to the received data
* @param[in] len Length of the received data
* @param[in] more -1 for no option, 0 for last block, 1 for more blocks
*
* @returns 0 on success
* @returns -1 on error
*/
typedef int (*coap_blockwise_cb_t)(void *arg, size_t offset, uint8_t *buf, size_t len, int more);
/**
* @brief Reference to the coap resource subtree
*/
extern const coap_resource_subtree_t coap_resource_subtree_suit;
/**
* @brief Coap block-wise-transfer size SZX
*/
typedef enum {
COAP_BLOCKSIZE_32 = 1,
COAP_BLOCKSIZE_64,
COAP_BLOCKSIZE_128,
COAP_BLOCKSIZE_256,
COAP_BLOCKSIZE_512,
COAP_BLOCKSIZE_1024,
} coap_blksize_t;
/**
* @brief Performs a blockwise coap get request to the specified url.
*
* This function will fetch the content of the specified resource path via
* block-wise-transfer. A coap_blockwise_cb_t will be called on each received
* block.
*
* @param[in] url url pointer to source path
* @param[in] blksize sender suggested SZX for the COAP block request
* @param[in] callback callback to be executed on each received block
* @param[in] arg optional function arguments
*
* @returns -EINVAL if an invalid url is provided
* @returns -1 if failed to fetch the url content
* @returns 0 on success
*/
int suit_coap_get_blockwise_url(const char *url,
coap_blksize_t blksize,
coap_blockwise_cb_t callback, void *arg);
#endif /* DOXYGEN */
#ifdef __cplusplus
}
#endif
#endif /* SUIT_COAP_H */
/** @} */

View File

@ -0,0 +1,109 @@
/*
* 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.
*/
/**
* @ingroup sys_suit
* @brief SUIT conditions
*
* @{
*
* @brief SUIT conditions API
* @author Koen Zandberg <koen@bergzand.net>
* @author Kaspar Schleiser <kaspar@schleiser.de>
*
*/
#ifndef SUIT_CONDITIONS_H
#define SUIT_CONDITIONS_H
#include <stddef.h>
#include <stdint.h>
#include "uuid.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief The SUIT vendor ID source
*
* The basis of the UUID must be the vendor domain, please change this when
* using this module in a product
*/
#ifndef SUIT_VENDOR_DOMAIN
#define SUIT_VENDOR_DOMAIN "riot-os.org" /**< Device vendor domain */
#endif
/**
* @brief The SUIT class ID source
*
* By default the RIOT_VERSION define is used for this
*/
#ifndef SUIT_CLASS_ID
#define SUIT_CLASS_ID RIOT_BOARD
#endif
/**
* @brief SUIT conditionals
*/
enum {
SUIT_COND_VENDOR_ID = 1, /**< Vendor ID match conditional */
SUIT_COND_CLASS_ID = 2, /**< Class ID match conditional */
SUIT_COND_DEV_ID = 3, /**< Device ID match conditional */
SUIT_COND_BEST_BEFORE = 4, /**< Best before conditional */
};
/**
* @brief SUIT condition parameters
*/
typedef struct {
uuid_t vendor; /**< Vendor url as UUID */
uuid_t class; /**< Device class UUID */
uuid_t device; /**< Device specific information as UUID */
} suit_condition_params_t;
/**
* @brief Initialize boot-time conditions for SUIT manifests
*
* This initializes the device-based conditions for validating manifest
* preconditions
*
* Vendor url as UUID: UUID5(DNS_PREFIX, SUIT_VENDOR_DOMAIN)
* Device class UUID: UUID5(vendor, SUIT_CLASS_ID)
* Device specific UUID: UUID5(vendor, Device ID)
*/
void suit_init_conditions(void);
/**
* @brief Retrieve the generated vendor ID
*
* @returns The vendor ID as UUID
*/
uuid_t *suit_get_vendor_id(void);
/**
* @brief Retrieve the generated class ID
*
* @returns The class ID as UUID
*/
uuid_t *suit_get_class_id(void);
/**
* @brief Retrieve the generated device ID
*
* @returns The device ID as UUID
*/
uuid_t *suit_get_device_id(void);
#ifdef __cplusplus
}
#endif
#endif /* SUIT_CONDITIONS_H */
/** @} */

View File

@ -0,0 +1,64 @@
/*
* 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_v4
* @brief SUIT v4 manifest handlers
*
* @experimental
*
* @{
*
* @brief Handler functions for SUIT manifests
* @author Koen Zandberg <koen@bergzand.net>
* @author Kaspar Schleiser <kaspar@schleiser.de>
*/
#ifndef SUIT_V4_HANDLERS_H
#define SUIT_V4_HANDLERS_H
#include <stddef.h>
#include <stdint.h>
#include "suit/v4/suit.h"
#include "uuid.h"
#include "cbor.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief suit handler prototype
*
* @param manifest SUIT v4 manifest context
* @param it CborValue iterator to the content the handler must handle
*
* @return 1 on success
* @return negative on error
*/
typedef int (*suit_manifest_handler_t)(suit_v4_manifest_t *manifest, int key, CborValue *it);
/**
* @brief Get suit manifest handler for given integer key
*
* @param[in] key: integer key
*
* @return ptr to handler function
* @return NULL (if handler unavailable or key out of range)
*/
suit_manifest_handler_t suit_manifest_get_manifest_handler(int key);
#ifdef __cplusplus
}
#endif
#endif /* SUIT_V4_HANDLERS_H */
/** @} */

View File

@ -0,0 +1,58 @@
/*
* 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_v4
* @brief SUIT policy definitions
*
* @{
*
* @brief SUIT policy definitions
* @author Koen Zandberg <koen@bergzand.net>
* @author Kaspar Schleiser <kaspar@schleiser.de>
*
*/
#ifndef SUIT_V4_POLICY_H
#define SUIT_V4_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_V4_POLICY_H */
/** @} */

288
sys/include/suit/v4/suit.h Normal file
View File

@ -0,0 +1,288 @@
/*
* 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_v4 SUIT draft v4
* @ingroup sys_suit
* @brief SUIT manifest handling
*
* @{
*
* @brief Handler functions for SUIT manifests
* @author Koen Zandberg <koen@bergzand.net>
* @author Kaspar Schleiser <kaspar@schleiser.de>
*
*/
#ifndef SUIT_V4_SUIT_H
#define SUIT_V4_SUIT_H
#include <stddef.h>
#include <stdint.h>
#include "cose/sign.h"
#include "cbor.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 (512U)
#endif
/**
* @brief Maximum number of components used for SUIT v4
*/
#define SUIT_V4_COMPONENT_MAX (1U)
/**
* @brief Supported SUIT manifest version
*/
#define SUIT_MANIFEST_VERSION (4)
/**
* @brief Current SUIT serialization format version
*
* see https://tools.ietf.org/html/draft-moran-suit-manifest-04#section-8.2 for
* details
*/
#define SUIT_VERSION (1)
/**
* @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 manifest 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_v4_error_t;
/**
* @brief TinyCBOR validation mode to use
*/
#define SUIT_TINYCBOR_VALIDATION_MODE CborValidateStrictMode
/**
* @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_v4_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_v4_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 v4 component struct
*/
typedef struct {
uint32_t size; /**< Size */
CborValue identifier; /**< Identifier*/
CborValue url; /**< Url */
CborValue digest; /**< Digest */
} suit_v4_component_t;
/**
* @brief SUIT manifest struct
*/
typedef struct {
cose_sign_dec_t verify; /**< COSE signature validation struct */
const uint8_t *buf; /**< ptr to the buffer of the manifest */
size_t len; /**< length of the manifest */
uint32_t validated; /**< bitfield of validated policies */
uint32_t state; /**< bitfield holding state information */
/** List of components in the manifest */
suit_v4_component_t components[SUIT_V4_COMPONENT_MAX];
unsigned components_len; /**< Current number of components */
int 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];
cose_key_t *key; /**< Ptr to the public key for validation */
char *urlbuf; /**< Buffer containing the manifest url */
size_t urlbuf_len; /**< Length of the manifest url */
} suit_v4_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_v4_error_t code on error
*/
int suit_v4_parse(suit_v4_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_v4_policy_check(suit_v4_manifest_t *manifest);
/**
* @brief Initialize a cbor iterator for SUIT cbor map container parsing
*
* @param[in] map the cbor container
* @param[in] it the cbor iterator
*
* @return SUIT_OK when initialization is successful
* @return SUIT_ERR_INVALID_MANIFEST if the manifest is not a cbor container
*/
int suit_cbor_map_iterate_init(CborValue *map, CborValue *it);
/**
* @brief Iterate over a cbor map container
*
* @param[in] it cbor container iterator
* @param[out] key the returned key
* @param[out] value the returned value
*
* @return 0 when the iterator is already at the end of the container
* @return the number of returned (key, value) pair, e.g. 1
*/
int suit_cbor_map_iterate(CborValue *it, CborValue *key, CborValue *value);
/**
* @brief Get cbor value as int
*
* @param[in] it cbor container iterator
* @param[out] out address of the returned integer
*
* @return SUIT_OK on success
* @return SUIT_ERR_INVALID_MANIFEST if value doesn't fit in an int
*/
int suit_cbor_get_int(const CborValue *it, int *out);
/**
* @brief Get cbor value as unsigned
*
* @param[in] it cbor container iterator
* @param[out] out address of the returned unsigned
*
* @return SUIT_OK on success
* @return SUIT_ERR_INVALID_MANIFEST if value doesn't fit or cannot
* be converted to unsigned
*/
int suit_cbor_get_uint(const CborValue *it, unsigned *out);
/**
* @brief Get cbor value as unsigned long
*
* @param[in] it cbor container iterator
* @param[out] out address of the returned unsigned long
*
* @return SUIT_OK on success
* @return SUIT_ERR_INVALID_MANIFEST if value doesn't fit or cannot
* be converted to unsigned long
*/
int suit_cbor_get_uint32(const CborValue *it, uint32_t *out);
/**
* @brief Get cbor value as string
*
* @param[in] it cbor container iterator
* @param[out] buf address of the string buffer
* @param[out] len address of the len of the string
*
* @return SUIT_OK on success
* @return SUIT_ERR_INVALID_MANIFEST if value is not a valid string
*/
int suit_cbor_get_string(const CborValue *it, const uint8_t **buf, size_t *len);
/**
* @brief Parser a cbor subsequence
*
* @param[in] parser ptr to cbor subparser
* @param[out] bseq subsequence value
* @param[out] it cbor iterator
*
* @return 0 on success
* @return -1 if bseq is not a cbor string
* @return CborError code on other cbor parser errors
*/
int suit_cbor_subparse(CborParser *parser, CborValue *bseq, CborValue *it);
/**
* @brief Helper function for writing bytes on flash a specified offset
*
* @param[in] arg ptr to flash writer
* @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 comming
*
* @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_V4_SUIT_H */
/** @} */

9
sys/suit/Makefile Normal file
View File

@ -0,0 +1,9 @@
SUBMODULES := 1
# don't complain about missing submodule .c file.
# necessary to not fail for suit_v*_*.
SUBMODULES_NOFORCE := 1
DIRS += v4
include $(RIOTBASE)/Makefile.base

513
sys/suit/coap.c Normal file
View File

@ -0,0 +1,513 @@
/*
* Copyright (C) 2019 Freie Universität Berlin
* 2019 Inria
* 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.
*/
/**
* @ingroup sys_suit
* @{
*
* @file
* @brief SUIT coap
*
* @author Koen Zandberg <koen@bergzand.net>
* @author Kaspar Schleiser <kaspar@schleiser.de>
* @}
*/
#include <inttypes.h>
#include <string.h>
#include "msg.h"
#include "log.h"
#include "net/nanocoap.h"
#include "net/nanocoap_sock.h"
#include "thread.h"
#include "periph/pm.h"
#include "suit/coap.h"
#include "net/sock/util.h"
#ifdef MODULE_RIOTBOOT_SLOT
#include "riotboot/slot.h"
#include "riotboot/flashwrite.h"
#endif
#ifdef MODULE_SUIT_V4
#include "suit/v4/suit.h"
#endif
#define ENABLE_DEBUG (0)
#include "debug.h"
#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)
#endif
#ifndef SUIT_COAP_PRIO
#define SUIT_COAP_PRIO THREAD_PRIORITY_MAIN - 1
#endif
#ifndef SUIT_URL_MAX
#define SUIT_URL_MAX 128
#endif
#ifndef SUIT_MANIFEST_BUFSIZE
#define SUIT_MANIFEST_BUFSIZE 640
#endif
#define SUIT_MSG_TRIGGER 0x12345
static char _stack[SUIT_COAP_STACKSIZE];
static char _url[SUIT_URL_MAX];
static uint8_t _manifest_buf[SUIT_MANIFEST_BUFSIZE];
static kernel_pid_t _suit_coap_pid;
ssize_t coap_subtree_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len,
void *context)
{
uint8_t uri[NANOCOAP_URI_MAX];
unsigned method_flag = coap_method2flag(coap_get_code_detail(pkt));
if (coap_get_uri_path(pkt, uri) > 0) {
coap_resource_subtree_t *subtree = context;
for (unsigned i = 0; i < subtree->resources_numof; i++) {
const coap_resource_t *resource = &subtree->resources[i];
if (!(resource->methods & method_flag)) {
continue;
}
int res = coap_match_path(resource, uri);
if (res > 0) {
continue;
}
else if (res < 0) {
break;
}
else {
return resource->handler(pkt, buf, len, resource->context);
}
}
}
return coap_reply_simple(pkt, COAP_CODE_INTERNAL_SERVER_ERROR, buf,
len, COAP_FORMAT_TEXT, NULL, 0);
}
static inline uint32_t _now(void)
{
return xtimer_now_usec();
}
static inline uint32_t deadline_from_interval(int32_t interval)
{
assert(interval >= 0);
return _now() + (uint32_t)interval;
}
static inline uint32_t deadline_left(uint32_t deadline)
{
int32_t left = (int32_t)(deadline - _now());
if (left < 0) {
left = 0;
}
return left;
}
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;
uint32_t id = coap_get_id(pkt);
/* TODO: timeout random between between ACK_TIMEOUT and (ACK_TIMEOUT *
* ACK_RANDOM_FACTOR) */
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 */
while (tries_left) {
if (res == -EAGAIN) {
res = sock_udp_send(sock, buf, pdu_len, NULL);
if (res <= 0) {
DEBUG("nanocoap: error sending coap request, %d\n", (int)res);
break;
}
}
res = sock_udp_recv(sock, buf, len, deadline_left(deadline), NULL);
if (res <= 0) {
if (res == -ETIMEDOUT) {
DEBUG("nanocoap: timeout\n");
tries_left--;
if (!tries_left) {
DEBUG("nanocoap: maximum retries reached\n");
break;
}
else {
timeout *= 2;
deadline = deadline_from_interval(timeout);
res = -EAGAIN;
continue;
}
}
DEBUG("nanocoap: error receiving coap response, %d\n", (int)res);
break;
}
else {
if (coap_parse(pkt, (uint8_t *)buf, res) < 0) {
DEBUG("nanocoap: error parsing packet\n");
res = -EBADMSG;
}
else if (coap_get_id(pkt) != id) {
res = -EBADMSG;
continue;
}
break;
}
}
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)
{
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_opt_put_uri_path(pktpos, 0, path);
pktpos += coap_opt_put_uint(pktpos, COAP_OPT_URI_PATH, COAP_OPT_BLOCK2, (num << 4) | blksize);
pkt->payload = pktpos;
pkt->payload_len = 0;
int res = _nanocoap_request(sock, pkt, 64 + (0x1 << (blksize + 4)));
if (res < 0) {
return res;
}
res = coap_get_code(pkt);
DEBUG("code=%i\n", res);
if (res != 205) {
return -res;
}
return 0;
}
int suit_coap_get_blockwise(sock_udp_ep_t *remote, const char *path,
coap_blksize_t blksize,
coap_blockwise_cb_t callback, void *arg)
{
/* mmmmh dynamically sized array */
uint8_t buf[64 + (0x1 << (blksize + 4))];
sock_udp_ep_t local = SOCK_IPV6_EP_ANY;
coap_pkt_t pkt;
/* 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;
while (more == 1) {
DEBUG("fetching block %u\n", (unsigned)num);
res = _fetch_block(&pkt, buf, &sock, path, blksize, num);
DEBUG("res=%i\n", res);
if (!res) {
coap_block1_t block2;
coap_get_block2(&pkt, &block2);
more = block2.more;
if (callback(arg, block2.offset, pkt.payload, pkt.payload_len, more)) {
DEBUG("callback res != 0, aborting.\n");
res = -1;
goto out;
}
}
else {
DEBUG("error fetching block\n");
res = -1;
goto out;
}
num += 1;
}
out:
sock_udp_close(&sock);
return res;
}
int suit_coap_get_blockwise_url(const char *url,
coap_blksize_t blksize,
coap_blockwise_cb_t callback, void *arg)
{
char hostport[SOCK_HOSTPORT_MAXLEN];
char urlpath[SOCK_URLPATH_MAXLEN];
sock_udp_ep_t remote;
if (strncmp(url, "coap://", 7)) {
LOG_INFO("suit: URL doesn't start with \"coap://\"\n");
return -EINVAL;
}
if (sock_urlsplit(url, hostport, urlpath) < 0) {
LOG_INFO("suit: invalid URL\n");
return -EINVAL;
}
if (sock_udp_str2ep(&remote, hostport) < 0) {
LOG_INFO("suit: invalid URL\n");
return -EINVAL;
}
if (!remote.port) {
remote.port = COAP_PORT;
}
return suit_coap_get_blockwise(&remote, urlpath, blksize, callback, arg);
}
typedef struct {
size_t offset;
uint8_t *ptr;
size_t len;
} _buf_t;
static int _2buf(void *arg, size_t offset, uint8_t *buf, size_t len, int more)
{
(void)more;
_buf_t *_buf = arg;
if (_buf->offset != offset) {
return 0;
}
if (len > _buf->len) {
return -1;
}
else {
memcpy(_buf->ptr, buf, len);
_buf->offset += len;
_buf->ptr += len;
_buf->len -= len;
return 0;
}
}
ssize_t suit_coap_get_blockwise_url_buf(const char *url,
coap_blksize_t blksize,
uint8_t *buf, size_t 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);
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;
memset(&manifest, 0, sizeof(manifest));
manifest.writer = &writer;
manifest.urlbuf = _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);
return;
}
LOG_INFO("suit_v4_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);
if (res) {
return;
}
#endif
if (res == 0) {
LOG_INFO("suit_coap: finalizing image flash\n");
riotboot_flashwrite_finish(&writer);
const riotboot_hdr_t *hdr = riotboot_slot_get_hdr(riotboot_slot_other());
riotboot_hdr_print(hdr);
xtimer_sleep(1);
if (riotboot_hdr_validate(hdr) == 0) {
LOG_INFO("suit_coap: rebooting...");
pm_reboot();
}
else {
LOG_INFO("suit_coap: update failed, hdr invalid");
}
}
}
else {
LOG_INFO("suit_coap: error getting manifest\n");
}
}
int suit_flashwrite_helper(void *arg, size_t offset, uint8_t *buf, size_t len,
int more)
{
riotboot_flashwrite_t *writer = arg;
if (offset == 0) {
if (len < RIOTBOOT_FLASHWRITE_SKIPLEN) {
LOG_WARNING("_suit_flashwrite(): offset==0, len<4. aborting\n");
return -1;
}
offset = RIOTBOOT_FLASHWRITE_SKIPLEN;
buf += RIOTBOOT_FLASHWRITE_SKIPLEN;
len -= RIOTBOOT_FLASHWRITE_SKIPLEN;
}
if (writer->offset != offset) {
LOG_WARNING("_suit_flashwrite(): writer->offset=%u, offset==%u, aborting\n",
(unsigned)writer->offset, (unsigned)offset);
return -1;
}
DEBUG("_suit_flashwrite(): writing %u bytes at pos %u\n", len, offset);
return riotboot_flashwrite_putbytes(writer, buf, len, more);
}
static void *_suit_coap_thread(void *arg)
{
(void)arg;
LOG_INFO("suit_coap: started.\n");
msg_t msg_queue[4];
msg_init_queue(msg_queue, 4);
_suit_coap_pid = thread_getpid();
msg_t m;
while (true) {
msg_receive(&m);
DEBUG("suit_coap: got msg with type %" PRIu32 "\n", m.content.value);
switch (m.content.value) {
case SUIT_MSG_TRIGGER:
LOG_INFO("suit_coap: trigger received\n");
_suit_handle_url(_url);
break;
default:
LOG_WARNING("suit_coap: warning: unhandled msg\n");
}
}
return NULL;
}
void suit_coap_run(void)
{
thread_create(_stack, SUIT_COAP_STACKSIZE, SUIT_COAP_PRIO,
THREAD_CREATE_STACKTEST,
_suit_coap_thread, NULL, "suit_coap");
}
static ssize_t _version_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len,
void *context)
{
(void)context;
return coap_reply_simple(pkt, COAP_CODE_205, buf, len,
COAP_FORMAT_TEXT, (uint8_t *)"NONE", 4);
}
#ifdef MODULE_RIOTBOOT_SLOT
static ssize_t _slot_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len,
void *context)
{
/* context is passed either as NULL or 0x1 for /active or /inactive */
char c = '0';
if (context) {
c += riotboot_slot_other();
}
else {
c += riotboot_slot_current();
}
return coap_reply_simple(pkt, COAP_CODE_205, buf, len,
COAP_FORMAT_TEXT, (uint8_t *)&c, 1);
}
#endif
static ssize_t _trigger_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len,
void *context)
{
(void)context;
unsigned code;
size_t payload_len = pkt->payload_len;
if (payload_len) {
if (payload_len >= SUIT_URL_MAX) {
code = COAP_CODE_REQUEST_ENTITY_TOO_LARGE;
}
else {
memcpy(_url, pkt->payload, payload_len);
_url[payload_len] = '\0';
code = COAP_CODE_CREATED;
LOG_INFO("suit: received URL: \"%s\"\n", _url);
msg_t m = { .content.value = SUIT_MSG_TRIGGER };
msg_send(&m, _suit_coap_pid);
}
}
else {
code = COAP_CODE_REQUEST_ENTITY_INCOMPLETE;
}
return coap_reply_simple(pkt, code, buf, len,
COAP_FORMAT_NONE, NULL, 0);
}
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 },
#endif
{ "/suit/trigger", COAP_METHOD_PUT | COAP_METHOD_POST, _trigger_handler, NULL },
{ "/suit/version", COAP_METHOD_GET, _version_handler, NULL },
};
const coap_resource_subtree_t coap_resource_subtree_suit =
{
.resources = &_subtree[0],
.resources_numof = ARRAY_SIZE(_subtree)
};

67
sys/suit/conditions.c Normal file
View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2019 Koen Zandberg
* 2019 Inria
* 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.
*/
/**
* @ingroup sys_suit
* @{
*
* @file
* @brief SUIT conditions
*
* @author Koen Zandberg <koen@bergzand.net>
* @author Kaspar Schleiser <kaspar@schleiser.de>
* @}
*/
#include <string.h>
#include "suit/conditions.h"
#include "uuid.h"
#include "luid.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
#define SUIT_DEVID_BYTES 32
static suit_condition_params_t _conditions;
void suit_init_conditions(void)
{
/* Generate UUID's following the instructions from
* https://tools.ietf.org/html/draft-moran-suit-manifest-03#section-7.7.1
*/
uuid_v5(&_conditions.vendor, &uuid_namespace_dns,
(uint8_t *)SUIT_VENDOR_DOMAIN, strlen(SUIT_VENDOR_DOMAIN));
uuid_v5(&_conditions.class, &_conditions.vendor, (uint8_t *)SUIT_CLASS_ID,
strlen(SUIT_CLASS_ID));
uint8_t devid[SUIT_DEVID_BYTES];
/* Use luid_base to ensure an identical ID independent of previous luid
* calls */
luid_base(devid, SUIT_DEVID_BYTES);
uuid_v5(&_conditions.device, &_conditions.vendor, devid, SUIT_DEVID_BYTES);
}
uuid_t *suit_get_vendor_id(void)
{
return &_conditions.vendor;
}
uuid_t *suit_get_class_id(void)
{
return &_conditions.class;
}
uuid_t *suit_get_device_id(void)
{
return &_conditions.device;
}

2
sys/suit/v4/Makefile Normal file
View File

@ -0,0 +1,2 @@
MODULE := suit_v4
include $(RIOTBASE)/Makefile.base

275
sys/suit/v4/cbor.c Normal file
View File

@ -0,0 +1,275 @@
/*
* Copyright (C) 2018 Freie Universität Berlin
* Copyright (C) 2018 Inria
* 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.
*/
/**
* @ingroup sys_suit
* @{
*
* @file
* @brief SUIT manifest parser library for CBOR based manifests
*
* @author Koen Zandberg <koen@bergzand.net>
* @author Kaspar Schleiser <kaspar@schleiser.de>
*
* @}
*/
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "suit/v4/handlers.h"
#include "suit/v4/suit.h"
#include "suit/v4/policy.h"
#include "cbor.h"
#include "cose/sign.h"
#include "public_key.h"
#include "log.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
static suit_manifest_handler_t _manifest_get_auth_wrapper_handler(int key);
typedef suit_manifest_handler_t (*suit_manifest_handler_getter_t)(int key);
int suit_cbor_map_iterate_init(CborValue *map, CborValue *it)
{
if (!cbor_value_is_map(map)) {
LOG_INFO("suit_v4_parse(): manifest not an map\n)");
return SUIT_ERR_INVALID_MANIFEST;
}
cbor_value_enter_container(map, it);
return SUIT_OK;
}
int suit_cbor_map_iterate(CborValue *it, CborValue *key, CborValue *value)
{
if (cbor_value_at_end(it)) {
return 0;
}
*key = *it;
cbor_value_advance(it);
*value = *it;
cbor_value_advance(it);
return 1;
}
int suit_cbor_get_int(const CborValue *it, int *out)
{
if (!cbor_value_is_integer(it)) {
LOG_DEBUG("expected integer type, got %u\n", cbor_value_get_type(it));
return SUIT_ERR_INVALID_MANIFEST;
}
/* This check tests whether the integer fits into "int", thus the check
* is platform dependent. This is for lack of specification of actually
* allowed values, to be made explicit at some point. */
if (cbor_value_get_int_checked(it, out) == CborErrorDataTooLarge) {
LOG_DEBUG("integer doesn't fit into int type\n");
return SUIT_ERR_INVALID_MANIFEST;
}
return SUIT_OK;
}
int suit_cbor_get_string(const CborValue *it, const uint8_t **buf, size_t *len)
{
if (!(cbor_value_is_text_string(it) || cbor_value_is_byte_string(it) || cbor_value_is_length_known(it))) {
return SUIT_ERR_INVALID_MANIFEST;
}
CborValue next = *it;
cbor_value_get_string_length(it, len);
cbor_value_advance(&next);
*buf = next.ptr - *len;
return SUIT_OK;
}
int suit_cbor_get_uint32(const CborValue *it, uint32_t *out)
{
int res;
int64_t val;
if (!cbor_value_is_unsigned_integer(it)) {
return CborErrorIllegalType;
}
if ((res = cbor_value_get_int64_checked(it, &val))) {
return res;
}
if (val > 0xFFFFFFFF) {
return CborErrorDataTooLarge;
}
*out = (val & 0xFFFFFFFF);
return CborNoError;
}
int suit_cbor_get_uint(const CborValue *it, unsigned *out)
{
return suit_cbor_get_uint32(it, (uint32_t *)out);
}
int suit_cbor_subparse(CborParser *parser, CborValue *bseq, CborValue *it)
{
const uint8_t *bytes;
size_t bytes_len = 0;
if (!cbor_value_is_byte_string(bseq)) {
LOG_DEBUG("suit_cbor_subparse(): bseq not a byte string\n");
return -1;
}
suit_cbor_get_string(bseq, &bytes, &bytes_len);
return cbor_parser_init(bytes, bytes_len, SUIT_TINYCBOR_VALIDATION_MODE, parser,
it);
}
static int _v4_parse(suit_v4_manifest_t *manifest, const uint8_t *buf,
size_t len, suit_manifest_handler_getter_t getter)
{
CborParser parser;
CborValue it, map, key, value;
CborError err = cbor_parser_init(buf, len, SUIT_TINYCBOR_VALIDATION_MODE,
&parser, &it);
if (err != 0) {
return SUIT_ERR_INVALID_MANIFEST;
}
map = it;
if (suit_cbor_map_iterate_init(&map, &it) != SUIT_OK) {
LOG_DEBUG("manifest not map!\n");
return SUIT_ERR_INVALID_MANIFEST;
}
LOG_DEBUG("jumping into map\n)");
while (suit_cbor_map_iterate(&it, &key, &value)) {
int integer_key;
if (suit_cbor_get_int(&key, &integer_key) != SUIT_OK){
return SUIT_ERR_INVALID_MANIFEST;
}
LOG_DEBUG("got key val=%i\n", integer_key);
suit_manifest_handler_t handler = getter(integer_key);
if (handler) {
int res = handler(manifest, integer_key, &value);
LOG_DEBUG("handler res=%i\n", res);
if (res < 0) {
LOG_INFO("handler returned <0\n)");
return SUIT_ERR_INVALID_MANIFEST;
}
}
else {
LOG_DEBUG("no handler found\n");
}
}
cbor_value_leave_container(&map, &it);
return SUIT_OK;
}
int suit_v4_parse(suit_v4_manifest_t *manifest, const uint8_t *buf,
size_t len)
{
manifest->buf = buf;
manifest->len = len;
return _v4_parse(manifest, buf, len, _manifest_get_auth_wrapper_handler);
}
static int _auth_handler(suit_v4_manifest_t *manifest, int key, CborValue *it)
{
(void)key;
const uint8_t *cose_buf;
size_t cose_len = 0;
int res = suit_cbor_get_string(it, &cose_buf, &cose_len);
if (res < 0) {
LOG_INFO("Unable to get COSE signature\n");
return SUIT_ERR_INVALID_MANIFEST;
}
res = cose_sign_decode(&manifest->verify, cose_buf, cose_len);
if (res < 0) {
LOG_INFO("Unable to parse COSE signature\n");
return SUIT_ERR_INVALID_MANIFEST;
}
return 0;
}
static int _manifest_handler(suit_v4_manifest_t *manifest, int key, CborValue *it)
{
(void)key;
const uint8_t *manifest_buf;
size_t manifest_len;
suit_cbor_get_string(it, &manifest_buf, &manifest_len);
/* Validate the COSE struct first now that we have the payload */
cose_sign_decode_set_payload(&manifest->verify, manifest_buf, manifest_len);
/* 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(&manifest->verify, &signature)) {
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,
public_key, NULL, NULL);
LOG_INFO("suit: verifying manifest signature...\n");
int verification = cose_sign_verify(&manifest->verify, &signature,
&pkey, manifest->validation_buf, SUIT_COSE_BUF_SIZE);
if (verification != 0) {
LOG_INFO("Unable to validate signature\n");
return SUIT_ERR_SIGNATURE;
}
return _v4_parse(manifest, manifest_buf,
manifest_len, suit_manifest_get_manifest_handler);
}
static suit_manifest_handler_t _suit_manifest_get_handler(int key,
const suit_manifest_handler_t *handlers,
size_t len)
{
if (key < 0 || (size_t)key >= len) {
return NULL;
}
return handlers[key];
}
/* begin{code-style-ignore} */
static suit_manifest_handler_t _auth_handlers[] = {
[ 0] = NULL,
[ 1] = _auth_handler,
[ 2] = _manifest_handler,
};
/* end{code-style-ignore} */
static const unsigned _auth_handlers_len = ARRAY_SIZE(_auth_handlers);
static suit_manifest_handler_t _manifest_get_auth_wrapper_handler(int key)
{
return _suit_manifest_get_handler(key, _auth_handlers,
_auth_handlers_len);
}

532
sys/suit/v4/handlers.c Normal file
View File

@ -0,0 +1,532 @@
/*
* 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_v4
* @{
*
* @file
* @brief SUIT v4
*
* @author Koen Zandberg <koen@bergzand.net>
*
* @}
*/
#include <inttypes.h>
#include "suit/coap.h"
#include "suit/conditions.h"
#include "suit/v4/suit.h"
#include "suit/v4/handlers.h"
#include "suit/v4/policy.h"
#include "suit/v4/suit.h"
#include "riotboot/hdr.h"
#include "riotboot/slot.h"
#include "cbor.h"
#include "log.h"
#define HELLO_HANDLER_MAX_STRLEN 32
static int _handle_command_sequence(suit_v4_manifest_t *manifest, CborValue *it,
suit_manifest_handler_t handler);
static int _common_handler(suit_v4_manifest_t *manifest, int key, CborValue *it);
static int _common_sequence_handler(suit_v4_manifest_t *manifest, int key, CborValue *it);
static int _hello_handler(suit_v4_manifest_t *manifest, int key, CborValue *it)
{
(void)manifest;
(void)key;
if (cbor_value_is_text_string(it)) {
size_t len = HELLO_HANDLER_MAX_STRLEN;
char buf[HELLO_HANDLER_MAX_STRLEN];
cbor_value_copy_text_string(it, buf, &len, NULL);
return SUIT_OK;
}
else {
LOG_DEBUG("_hello_handler(): unexpected value type: %u\n", cbor_value_get_type(
it));
return -1;
}
}
static int _validate_uuid(suit_v4_manifest_t *manifest, CborValue *it, uuid_t *uuid)
{
(void)manifest;
uuid_t uuid_manifest;
char uuid_str[UUID_STR_LEN + 1];
char uuid_str2[UUID_STR_LEN + 1];
size_t len = sizeof(uuid_t);
cbor_value_copy_byte_string(it, (uint8_t*)&uuid_manifest, &len, NULL);
uuid_to_string(&uuid_manifest, 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_manifest) ? 0 : -1;
}
static int _cond_vendor_handler(suit_v4_manifest_t *manifest, int key, CborValue *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_v4_manifest_t *manifest, int key, CborValue *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_v4_manifest_t *manifest, int key, CborValue *it)
{
(void)manifest;
(void)key;
uint32_t offset;
suit_cbor_get_uint32(it, &offset);
uint32_t other_offset = (uint32_t)riotboot_slot_get_hdr(riotboot_slot_other()) \
- CPU_FLASH_BASE;
LOG_INFO("Comparing manifest offset %u with other slot offset %u\n",
(unsigned)offset, (unsigned)other_offset);
return other_offset == offset ? 0 : -1;
}
static int _dtv_set_comp_idx(suit_v4_manifest_t *manifest, int key, CborValue *it)
{
(void)key;
if (cbor_value_is_boolean(it)) {
LOG_DEBUG("_dtv_set_comp_idx() ignoring boolean\n)");
return 0;
}
int res = suit_cbor_get_int(it, &manifest->component_current);
if (!res) {
LOG_DEBUG("Setting component index to %d\n", manifest->component_current);
}
return res;
}
static int _dtv_run_seq_cond(suit_v4_manifest_t *manifest, int key, CborValue *it)
{
(void)key;
LOG_DEBUG("Starting conditional sequence handler\n");
_handle_command_sequence(manifest, it, _common_sequence_handler);
return 0;
}
static int _param_get_uri_list(suit_v4_manifest_t *manifest, CborValue *it)
{
LOG_DEBUG("got url list\n");
manifest->components[manifest->component_current].url = *it;
return 0;
}
static int _param_get_digest(suit_v4_manifest_t *manifest, CborValue *it)
{
LOG_DEBUG("got digest\n");
manifest->components[manifest->component_current].digest = *it;
return 0;
}
static int _param_get_img_size(suit_v4_manifest_t *manifest, CborValue *it)
{
int res = suit_cbor_get_uint32(it, &manifest->components[0].size);
if (res) {
LOG_DEBUG("error getting image size\n");
return res;
}
return res;
}
static int _dtv_set_param(suit_v4_manifest_t *manifest, int key, CborValue *it)
{
(void)key;
/* `it` points to the entry of the map containing the type and value */
CborValue map;
cbor_value_enter_container(it, &map);
while (!cbor_value_at_end(&map)) {
/* map points to the key of the param */
int param_key;
suit_cbor_get_int(&map, &param_key);
cbor_value_advance(&map);
LOG_DEBUG("Setting component index to %d\n", manifest->component_current);
LOG_DEBUG("param_key=%i\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:
res = -1;
}
cbor_value_advance(&map);
if (res) {
return res;
}
}
return SUIT_OK;
}
static int _dtv_fetch(suit_v4_manifest_t *manifest, int key, CborValue *_it)
{
(void)key; (void)_it; (void)manifest;
LOG_DEBUG("_dtv_fetch() key=%i\n", key);
const uint8_t *url;
size_t url_len;
/* TODO: there must be a simpler way */
{
/* the url list is a binary sequence containing a cbor array of
* (priority, url) tuples (represented as array with length two)
* */
CborParser parser;
CborValue it;
/* open sequence with cbor parser */
int err = suit_cbor_subparse(&parser, &manifest->components[0].url, &it);
if (err < 0) {
LOG_DEBUG("subparse failed\n)");
return err;
}
/* confirm the document contains an array */
if (!cbor_value_is_array(&it)) {
LOG_DEBUG("url list no array\n)");
LOG_DEBUG("type: %u\n", cbor_value_get_type(&it));
}
/* enter container, confirm it is an array, too */
CborValue url_it;
cbor_value_enter_container(&it, &url_it);
if (!cbor_value_is_array(&url_it)) {
LOG_DEBUG("url entry no array\n)");
}
/* expect two entries: priority as int, url as byte string. bail out if not. */
CborValue url_value_it;
cbor_value_enter_container(&url_it, &url_value_it);
/* check that first array entry is an int (the priority of the url) */
if (cbor_value_get_type(&url_value_it) != CborIntegerType) {
return -1;
}
cbor_value_advance(&url_value_it);
int res = suit_cbor_get_string(&url_value_it, &url, &url_len);
if (res) {
LOG_DEBUG("error parsing URL\n)");
return -1;
}
if (url_len >= manifest->urlbuf_len) {
LOG_INFO("url too large: %u>%u\n)", (unsigned)url_len, (unsigned)manifest->urlbuf_len);
return -1;
}
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 = suit_coap_get_blockwise_url(manifest->urlbuf, COAP_BLOCKSIZE_64, suit_flashwrite_helper,
manifest->writer);
if (res) {
LOG_INFO("image download failed\n)");
return res;
}
const uint8_t *digest;
size_t digest_len;
res = suit_cbor_get_string(&manifest->components[0].digest, &digest, &digest_len);
if (res) {
return res;
}
/* "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) {
LOG_INFO("image verification failed\n");
return res;
}
manifest->state |= SUIT_MANIFEST_HAVE_IMAGE;
return SUIT_OK;
}
static int _version_handler(suit_v4_manifest_t *manifest, int key,
CborValue *it)
{
(void)manifest;
(void)key;
/* Validate manifest version */
int version = -1;
if (cbor_value_is_integer(it) &&
(cbor_value_get_int(it, &version) == CborNoError)) {
if (version == SUIT_VERSION) {
manifest->validated |= SUIT_VALIDATED_VERSION;
LOG_INFO("suit: validated manifest version\n)");
return 0;
}
else {
return -1;
}
}
return -1;
}
static int _seq_no_handler(suit_v4_manifest_t *manifest, int key, CborValue *it)
{
(void)manifest;
(void)key;
(void)it;
int64_t seq_nr;
if (cbor_value_is_unsigned_integer(it) &&
(cbor_value_get_int64_checked(it, &seq_nr) == CborNoError)) {
const riotboot_hdr_t *hdr = riotboot_slot_get_hdr(riotboot_slot_current());
if (seq_nr <= (int64_t)hdr->version) {
LOG_INFO("%"PRIu64" <= %"PRIu32"\n", seq_nr, hdr->version);
LOG_INFO("seq_nr <= running image\n)");
return -1;
}
hdr = riotboot_slot_get_hdr(riotboot_slot_other());
if (riotboot_hdr_validate(hdr) == 0) {
if (seq_nr <= (int64_t)hdr->version) {
LOG_INFO("%"PRIu64" <= %"PRIu32"\n", seq_nr, hdr->version);
LOG_INFO("seq_nr <= other image\n)");
return -1;
}
}
LOG_INFO("suit: validated sequence number\n)");
manifest->validated |= SUIT_VALIDATED_SEQ_NR;
return 0;
}
LOG_INFO("Unable to get sequence number\n");
return -1;
}
static int _dependencies_handler(suit_v4_manifest_t *manifest, int key,
CborValue *it)
{
(void)manifest;
(void)key;
(void)it;
/* No dependency support */
return 0;
}
static int _component_handler(suit_v4_manifest_t *manifest, int key,
CborValue *it)
{
(void)manifest;
(void)key;
CborValue arr;
LOG_DEBUG("storing components\n)");
if (!cbor_value_is_array(it)) {
LOG_DEBUG("components field not an array\n");
return -1;
}
cbor_value_enter_container(it, &arr);
unsigned n = 0;
while (!cbor_value_at_end(&arr)) {
CborValue map, key, value;
if (n < SUIT_V4_COMPONENT_MAX) {
manifest->components_len += 1;
}
else {
LOG_DEBUG("too many components\n)");
return SUIT_ERR_INVALID_MANIFEST;
}
suit_cbor_map_iterate_init(&arr, &map);
suit_v4_component_t *current = &manifest->components[n];
while (suit_cbor_map_iterate(&map, &key, &value)) {
/* handle key, value */
int integer_key;
if (suit_cbor_get_int(&key, &integer_key)) {
return SUIT_ERR_INVALID_MANIFEST;
}
switch (integer_key) {
case SUIT_COMPONENT_IDENTIFIER:
current->identifier = value;
break;
case SUIT_COMPONENT_SIZE:
LOG_DEBUG("skipping SUIT_COMPONENT_SIZE");
break;
case SUIT_COMPONENT_DIGEST:
current->digest = value;
break;
default:
LOG_DEBUG("ignoring unexpected component data (nr. %i)\n", integer_key);
}
LOG_DEBUG("component %u parsed\n", n);
}
cbor_value_advance(&arr);
n++;
}
manifest->state |= SUIT_MANIFEST_HAVE_COMPONENTS;
cbor_value_enter_container(it, &arr);
LOG_DEBUG("storing components done\n)");
return 0;
}
/* begin{code-style-ignore} */
static suit_manifest_handler_t global_handlers[] = {
[ 0] = _hello_handler,
[ 1] = _version_handler,
[ 2] = _seq_no_handler,
[ 3] = _dependencies_handler,
[ 4] = _component_handler,
[ 5] = NULL,
[ 6] = _common_handler,
[ 9] = _common_handler,
};
/* end{code-style-ignore} */
static const unsigned global_handlers_len = ARRAY_SIZE(global_handlers);
/* begin{code-style-ignore} */
static suit_manifest_handler_t _sequence_handlers[] = {
[ 0] = NULL,
[ 1] = _cond_vendor_handler,
[ 2] = _cond_class_handler,
[10] = _cond_comp_offset,
/* Directives */
[11] = _dtv_set_comp_idx,
/* [12] = _dtv_set_man_idx, */
/* [13] = _dtv_run_seq, */
[14] = _dtv_run_seq_cond,
[16] = _dtv_set_param,
[20] = _dtv_fetch,
/* [22] = _dtv_run, */
};
/* end{code-style-ignore} */
static const unsigned _sequence_handlers_len = ARRAY_SIZE(_sequence_handlers);
static suit_manifest_handler_t _suit_manifest_get_handler(int key,
const suit_manifest_handler_t *handlers,
size_t len)
{
if (key < 0 || (size_t)key >= len) {
return NULL;
}
return handlers[key];
}
suit_manifest_handler_t suit_manifest_get_manifest_handler(int key)
{
return _suit_manifest_get_handler(key, global_handlers,
global_handlers_len);
}
static int _common_sequence_handler(suit_v4_manifest_t *manifest, int key, CborValue *it)
{
suit_manifest_handler_t handler = _suit_manifest_get_handler(key, _sequence_handlers, _sequence_handlers_len);
LOG_DEBUG("Handling handler with key %d at %p\n", key, handler);
if (handler) {
return handler(manifest, key, it);
}
else {
LOG_DEBUG("Sequence handler not implemented, ID: %d\n", key);
return -1;
}
}
static int _common_handler(suit_v4_manifest_t *manifest, int key, CborValue *it)
{
(void)key;
return _handle_command_sequence(manifest, it, _common_sequence_handler);
}
int _handle_command_sequence(suit_v4_manifest_t *manifest, CborValue *bseq,
suit_manifest_handler_t handler)
{
LOG_DEBUG("Handling command sequence\n");
CborParser parser;
CborValue it, arr;
int err = suit_cbor_subparse(&parser, bseq, &it);
if (err < 0) {
return err;
}
if (!cbor_value_is_array(&it)) {
LOG_DEBUG("Not a byte array\n");
return -1;
}
cbor_value_enter_container(&it, &arr);
while (!cbor_value_at_end(&arr)) {
CborValue map;
if (!cbor_value_is_map(&arr)) {
return SUIT_ERR_INVALID_MANIFEST;
}
cbor_value_enter_container(&arr, &map);
int integer_key;
if (suit_cbor_get_int(&map, &integer_key)) {
return SUIT_ERR_INVALID_MANIFEST;
}
cbor_value_advance(&map);
int res = handler(manifest, integer_key, &map);
if (res < 0) {
LOG_DEBUG("Sequence handler error\n");
return res;
}
cbor_value_advance(&map);
cbor_value_leave_container(&arr, &map);
}
cbor_value_leave_container(&it, &arr);
return 0;
}

37
sys/suit/v4/policy.c Normal file
View File

@ -0,0 +1,37 @@
/*
* 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_v4
* @{
*
* @file
* @brief SUIT v4 policy checking code
*
* @author Kaspar Schleiser <kaspar@schleiser.de>
*
* @}
*/
#include "suit/v4/suit.h"
#include "suit/v4/policy.h"
#include "log.h"
int suit_v4_policy_check(suit_v4_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;
}
}