diff --git a/Makefile.dep b/Makefile.dep index a2a31ff59c1a..a92f206e55b0 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -656,6 +656,22 @@ USEPKG += nanocoap USEMODULE += gnrc_sock_udp endif +ifneq (,$(filter firmware,$(USEMODULE))) + USEPKG += tweetnacl + USEMODULE += checksum + FEATURES_REQUIRED += periph_flashpage +endif + +ifneq (,$(filter ota_update_%,$(USEMODULE))) +USEMODULE += gcoap +USEMODULE += ota_update +USEMODULE += firmware +USEMODULE += sema + ifneq (,$(filter ota_update_tftp,$(USEMODULE))) + USEMODULE += gnrc_tftp + endif +endif + # include package dependencies -include $(USEPKG:%=$(RIOTPKG)/%/Makefile.dep) diff --git a/boards/iotlab-common/Makefile.include b/boards/iotlab-common/Makefile.include index 93260391c7c0..2e6fc5f6fee6 100644 --- a/boards/iotlab-common/Makefile.include +++ b/boards/iotlab-common/Makefile.include @@ -6,6 +6,18 @@ export CPU_MODEL = stm32f103re PORT_LINUX ?= /dev/ttyUSB1 PORT_DARWIN ?= $(firstword $(sort $(wildcard /dev/tty.usbserial*B))) +# export ROM partitions +export ROM_SIZE = 0x80000 +export SLOT0_ADDR = 0x8000000 +export SLOT0_SIZE = 0x4000 +CFLAGS +=-DSLOT0_SIZE=$(SLOT0_SIZE) +export SLOT_SIZE = 0x3E000 +CFLAGS +=-DSLOT_SIZE=$(SLOT_SIZE) +export SLOT1_SIZE = 0x3E000 +CFLAGS +=-DSLOT1_SIZE=$(SLOT1_SIZE) +export SLOT2_SIZE = 0x3E000 +CFLAGS +=-DSLOT2_SIZE=$(SLOT2_SIZE) + # setup serial terminal export BAUD = 500000 include $(RIOTMAKE)/tools/serial.inc.mk diff --git a/boards/samr21-xpro/Makefile.include b/boards/samr21-xpro/Makefile.include index ba753cc0a92b..f73e9424ec93 100644 --- a/boards/samr21-xpro/Makefile.include +++ b/boards/samr21-xpro/Makefile.include @@ -5,4 +5,16 @@ export CPU_MODEL = samr21g18a # set edbg device type EDBG_DEVICE_TYPE = atmel_cm0p +# export ROM partitions +export ROM_SIZE = 0x40000 +export SLOT0_ADDR = 0x0 +export SLOT0_SIZE = 0x4000 +CFLAGS +=-DSLOT0_SIZE=$(SLOT0_SIZE) +export SLOT_SIZE = 0x1E000 +CFLAGS +=-DSLOT_SIZE=$(SLOT_SIZE) +export SLOT1_SIZE = 0x1E000 +CFLAGS +=-DSLOT1_SIZE=$(SLOT1_SIZE) +export SLOT2_SIZE = 0x1E000 +CFLAGS +=-DSLOT2_SIZE=$(SLOT2_SIZE) + include $(RIOTMAKE)/boards/sam0.inc.mk diff --git a/bootloader/Makefile b/bootloader/Makefile new file mode 100644 index 000000000000..8f6bc549d51e --- /dev/null +++ b/bootloader/Makefile @@ -0,0 +1,13 @@ +APPLICATION = bootloader +BOARD ?= samr21-xpro + +USEMODULE += bootloader +USEMODULE += firmware + +#CFLAGS += -DNDEBUG -DLOG_LEVEL=LOG_NONE +#DISABLE_MODULE += auto_init +CFLAGS += -DTHREAD_STACKSIZE_MAIN=\(5*THREAD_STACKSIZE_DEFAULT\) +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/.. + +include $(RIOTBASE)/Makefile.include diff --git a/bootloader/main.c b/bootloader/main.c new file mode 100644 index 000000000000..dc32133fb711 --- /dev/null +++ b/bootloader/main.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * 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 bootloader + * @{ + * + * @file + * @brief RIOT Bootloader + * + * @author Kaspar Schleiser + * @author Francisco Acosta + * + * @} + */ +#include +#include + +#include "firmware.h" + + +static int validate_and_boot(uint8_t slot) +{ + if (firmware_validate_int_slot(slot) != -1) { + printf("[bootlaoder] slot %d validated! Booting...\n", slot); + firmware_jump_to_slot(slot); + } + else { + puts("[bootloader] no valid signature!"); + return -1; + } + + /* Should not happen */ + return -1; +} + +static int boot_img(void) +{ + uint8_t boot_slot = 0; + uint32_t appid_slot1 = 0, appid_slot2 = 0; + firmware_metadata_t metadata; + + puts("[bootlaoder] Checking for slots metadata..."); + memcpy(&metadata, firmware_get_metadata(1), sizeof(firmware_metadata_t)); + if (firmware_validate_metadata_checksum(&metadata) != -1) { + appid_slot1 = metadata.appid; + printf("[bootlaoder] Found slot 1 with APPID: 0x%lx \n", appid_slot1); + } + else { + puts("[bootloader] Slot 1 not valid"); + } + + memcpy(&metadata, firmware_get_metadata(2), sizeof(firmware_metadata_t)); + if (firmware_validate_metadata_checksum(&metadata) != -1) { + appid_slot2 = metadata.appid; + printf("[bootlaoder] Found slot 2 with APPID: 0x%lx \n", appid_slot2); + } + else { + puts("[bootloader] Slot 2 not valid"); + } + + if ((appid_slot1 == 0) && (appid_slot2 == 0)) { + puts("[bootloader] No valid slot found!"); + return -1; + } + else { + if (appid_slot1 != appid_slot2) { + puts("[bootloader] Warning! application IDs are different!"); + puts("[bootloader] falling back to slot 1"); + if (validate_and_boot(1) == -1) { + puts("[bootloader] Booting failed!"); + } + else { + puts("[bootlaoder] Slot 1 inconsistent, no image to boot..."); + return -1; + } + } + else { + puts("[bootloader] Looking for the newest image on ROM, listing..."); + boot_slot = firmware_find_newest_int_image(); + + if (boot_slot > 0) { + printf("[bootloader] newest image found on slot %d, checking...\n", + boot_slot); + if (validate_and_boot(boot_slot) == -1) { + puts("[bootloader] Booting failed!"); + } + } + else { + (void) puts("No bootable slot found!\n"); + return -1; + } + } + } + + /* Shouldn't happen */ + return 0; +} + +int main(void) +{ + return boot_img(); + +} diff --git a/cpu/cortexm_common/include/cpu.h b/cpu/cortexm_common/include/cpu.h index b8e7e29e79b5..106a2837b00a 100644 --- a/cpu/cortexm_common/include/cpu.h +++ b/cpu/cortexm_common/include/cpu.h @@ -114,6 +114,92 @@ static inline void cortexm_isr_end(void) } } +/** + * @brief Jumps to another image in flash + * + * This function is supposed to be called by a bootloader application. + * + * @param[in] image_address address in flash of other image + */ +static inline void cpu_jump_to_image(uint32_t image_address) +{ + /* Disable IRQ */ + __disable_irq(); + + /* set PSP */ + __set_PSP(*(uint32_t*)image_address); + + /* skip stack pointer */ + image_address += 4; + + /* load the images reset_vector address */ + uint32_t destination_address = *(uint32_t*)image_address; + + /* Make sure the Thumb State bit is set. */ + destination_address |= 0x1; + + /* Branch execution */ + __asm("BX %0" :: "r" (destination_address)); +} + +/* The following register is only present for Cortex-M0+, -M3, -M4 and -M7 CPUs */ +#if defined(CPU_ARCH_CORTEX_M0PLUS) || defined(CPU_ARCH_CORTEX_M3) || \ + defined(CPU_ARCH_CORTEX_M4) || defined(CPU_ARCH_CORTEX_M4F) || \ + defined(CPU_ARCH_CORTEX_M7) +static inline unsigned cpu_get_image_baseaddr(void) +{ + return (unsigned)SCB->VTOR; +} +#endif + +#if defined(SLOT0_SIZE) && defined(SLOT1_SIZE) +/** + * @brief Get FW internal address for a given slot + * + * @param[in] slot FW slot + * + * @return FW slot address + */ +static inline uint32_t cpu_get_slot_address(uint8_t slot) +{ + switch (slot) { + case 1: + return SLOT0_SIZE; + break; + + case 2: + return SLOT0_SIZE + SLOT1_SIZE; + break; + } + + return 0; +} +#endif + +#if defined(SLOT0_SIZE) && defined(SLOT2_SIZE) +/** + * @brief Get internal page for a given slot + * + * @param[in] slot FW slot + * + * @return FW slot page + */ +static inline uint32_t cpu_get_slot_page(uint8_t slot) +{ + switch (slot) { + case 1: + return SLOT0_SIZE / FLASHPAGE_SIZE; + break; + + case 2: + return (SLOT0_SIZE + SLOT1_SIZE) / FLASHPAGE_SIZE; + break; + } + + return 0; +} +#endif + #ifdef __cplusplus } #endif diff --git a/cpu/sam0_common/ldscripts/samr21g18a.ld b/cpu/sam0_common/ldscripts/samr21g18a.ld index c6770f7c3036..540486167884 100644 --- a/cpu/sam0_common/ldscripts/samr21g18a.ld +++ b/cpu/sam0_common/ldscripts/samr21g18a.ld @@ -18,10 +18,14 @@ * @} */ +rom_length = 256K; + +INCLUDE multislot.ld + MEMORY { - rom (rx) : ORIGIN = 0x00000000, LENGTH = 256K - ram (rwx) : ORIGIN = 0x20000000, LENGTH = 32K + rom (rx) : ORIGIN = 0x00000000 + boot_offset, LENGTH = rom_length + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } INCLUDE cortexm_base.ld diff --git a/cpu/stm32f1/ldscripts/stm32f103re.ld b/cpu/stm32f1/ldscripts/stm32f103re.ld index a322d128e31f..9e43de625e4c 100644 --- a/cpu/stm32f1/ldscripts/stm32f103re.ld +++ b/cpu/stm32f1/ldscripts/stm32f103re.ld @@ -18,11 +18,15 @@ * @} */ +rom_length = 512K; + +INCLUDE multislot.ld + MEMORY { - rom (rx) : ORIGIN = 0x08000000, LENGTH = 512K - ram (xrw) : ORIGIN = 0x20000000, LENGTH = 64K - cpuid (r) : ORIGIN = 0x1ffff7e8, LENGTH = 12 + rom (rx) : ORIGIN = 0x08000000 + boot_offset, LENGTH = rom_length + ram (xrw) : ORIGIN = 0x20000000, LENGTH = 64K + cpuid (r) : ORIGIN = 0x1ffff7e8, LENGTH = 12 } _cpuid_address = ORIGIN(cpuid); diff --git a/dist/tools/firmware_metadata/Makefile b/dist/tools/firmware_metadata/Makefile new file mode 100644 index 000000000000..fea020ac04f2 --- /dev/null +++ b/dist/tools/firmware_metadata/Makefile @@ -0,0 +1,50 @@ +RIOTBASE := ../../.. +RIOT_INCLUDE := $(RIOTBASE)/sys/include +SHA256_DIR := $(RIOTBASE)/sys/hashes +SHA256_INCLUDE := $(RIOT_INCLUDE)/hashes +TWEETNACL_DIR := tweetnacl +TWEETNACL_SRC := $(TWEETNACL_DIR)/tweetnacl.c randombytes.c +TWEETNACL_HDR := $(TWEETNACL_DIR)/tweetnacl.h +COMMON_SRC := common.c +COMMON_HDR := common.h + +RIOT_FIRMWARE_SRC := \ + $(SHA256_DIR)/sha256.c \ + $(RIOTBASE)/sys/checksum/fletcher32.c \ + $(RIOTBASE)/sys/firmware/firmware.c + +RIOT_FIRMWARE_HDR := $(RIOT_INCLUDE)/firmware.h \ + $(RIOT_INCLUDE)/hashes/sha256.h \ + $(RIOT_INCLUDE)/checksum/fletcher32.h + +FIRMWARE_SRC := $(COMMON_SRC) $(TWEETNACL_SRC) $(RIOT_FIRMWARE_SRC) \ + firmware.c verify.c genkeys.c genmeta.c + +FIRMWARE_HDR := $(COMMON_HDR) $(RIOT_FIRMWARE_HDR) + +GITCACHE = ../git/git-cache +TWEETNACL_URL = https://github.com/RIOT-OS/tweetnacl.git +TWEETNACL_VERSION = 7ea05c7098a16c87fa66e9166ce301666f3f2623 + +CFLAGS += -g -I. -O3 -Wall -Wextra -pedantic -std=c99 + +all: bin/firmware + +bin/: + mkdir -p bin + +git-fetch-tweetnacl: + rm -Rf $(TWEETNACL_DIR) + mkdir -p $(TWEETNACL_DIR) + $(GITCACHE) clone "$(TWEETNACL_URL)" "$(TWEETNACL_VERSION)" "$(TWEETNACL_DIR)" + touch $@ + +bin/firmware: git-fetch-tweetnacl $(FIRMWARE_HDR) $(FIRMWARE_SRC) Makefile | bin/ + $(CC) $(CFLAGS) -I$(RIOT_INCLUDE) -I$(TWEETNACL_DIR) $(FIRMWARE_SRC) -o $@ + +clean: + rm -rf bin/firmware + +distclean: + rm -rf $(TWEETNACL_DIR) + rm -f git-fetch-tweetnacl diff --git a/dist/tools/firmware_metadata/common.c b/dist/tools/firmware_metadata/common.c new file mode 100644 index 000000000000..12a46d5df725 --- /dev/null +++ b/dist/tools/firmware_metadata/common.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file LICENSE for more details. + */ + +#include +#include +#include + +#include "hashes/sha256.h" + +off_t fsize(const char *filename) +{ + struct stat st; + + if (stat(filename, &st) == 0) + return st.st_size; + + return -1; +} + +int to_file(const char *filename, void *buf, size_t len) +{ + int fd = open(filename, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); + if (fd > 0) { + ssize_t res = write(fd, buf, len); + close(fd); + return res == (ssize_t)len; + } else { + return fd; + } +} + +int from_file(const char *filename, void *buf, size_t len) +{ + int fd = open(filename, O_RDONLY); + if (fd > 0) { + ssize_t res = read(fd, buf, len); + close(fd); + return res == (ssize_t)len; + } else { + return fd; + } +} + +ssize_t do_sha256(const char *filename, void *tgt, off_t offset) +{ + sha256_context_t sha256; + sha256_init(&sha256); + + ssize_t bytes_read; + ssize_t total = 0; + char buf[1024]; + + int fd = open(filename, O_RDONLY); + if (!fd) { + return -1; + } + + if (offset) { + if (lseek(fd, offset, SEEK_SET) == -1) { + return -1; + } + } + + while ((bytes_read = read(fd, buf, sizeof(buf)))) { + sha256_update(&sha256, buf, bytes_read); + total += bytes_read; + } + + sha256_final(&sha256, tgt); + + close(fd); + + return total; +} diff --git a/dist/tools/firmware_metadata/common.h b/dist/tools/firmware_metadata/common.h new file mode 100644 index 000000000000..078b87d2f60d --- /dev/null +++ b/dist/tools/firmware_metadata/common.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file LICENSE for more details. + */ + +#ifndef COMMON_H +#define COMMON_H + +#include +#include + +#ifdef __cplusplus + extern "C" { +#endif + +off_t fsize(const char *filename); +int to_file(const char *filename, void *buf, size_t len); +int from_file(const char *filename, void *buf, size_t len); +int do_sha256(const char *filename, void *tgt, size_t offset); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* COMMON_H */ diff --git a/dist/tools/firmware_metadata/firmware.c b/dist/tools/firmware_metadata/firmware.c new file mode 100644 index 000000000000..acf6d6e79e76 --- /dev/null +++ b/dist/tools/firmware_metadata/firmware.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file LICENSE for more details. + */ + +#include +#include + +int verify(int argc, char *argv[]); +extern const char verify_usage[]; + +int genkeys(int argc, char *argv[]); +extern const char genkeys_usage[]; + +int genmeta(int argc, char *argv[]); +extern const char genmeta_usage[]; + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + goto usage; + } + if (!strcmp(argv[1], "verify")) { + return verify(argc, argv); + } else if (!strcmp(argv[1], "genkeys")) { + return genkeys(argc, argv); + } else if (!strcmp(argv[1], "genmeta")) { + return genmeta(argc -1, &argv[1]); + } + +usage: + fprintf(stderr, "usage: %s\n" \ + " %s\n" \ + " %s\n", \ + verify_usage, genkeys_usage, genmeta_usage); + return 1; +} diff --git a/dist/tools/firmware_metadata/genkeys.c b/dist/tools/firmware_metadata/genkeys.c new file mode 100644 index 000000000000..5b31d235dedd --- /dev/null +++ b/dist/tools/firmware_metadata/genkeys.c @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file LICENSE for more details. + */ + +#include + +#include "common.h" +#include "tweetnacl.h" + +const char genkeys_usage[] = "firmware genkeys "; +int genkeys(int argc, const char *argv[]) +{ + unsigned char pk[crypto_sign_PUBLICKEYBYTES]; + unsigned char sk[crypto_sign_SECRETKEYBYTES]; + + if (argc < 4) { + fprintf(stderr, "usage: %s\n", genkeys_usage); + return 1; + } + + crypto_sign_keypair(pk,sk); + + const char err[] = "error writing to %s\n"; + if (!to_file(argv[2], sk, sizeof(sk))) { + fprintf(stderr, err, argv[2]); + return -1; + } + + if (!to_file(argv[3], pk, sizeof(pk))) { + fprintf(stderr, err, argv[3]); + return -1; + } + + return 0; +} diff --git a/dist/tools/firmware_metadata/genmeta.c b/dist/tools/firmware_metadata/genmeta.c new file mode 100644 index 000000000000..a8eab3dec998 --- /dev/null +++ b/dist/tools/firmware_metadata/genmeta.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2017 Kaspar Schleiser + * 2016 Mark Solters + * 2016 Inria + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @ingroup FW + * @file + * @brief Meta-data generation for FW images + * + * @author Mark Solters + * @author Francisco Acosta + * @author Kaspar Schleiser + */ + +#include +#include +#include + +#include "firmware.h" +#include "common.h" + +const char genmeta_usage[] = "firmware genmeta "; + +int genmeta(int argc, char *argv[]) +{ + firmware_metadata_t metadata; + unsigned char sk[FIRMWARE_SECKEY_LEN]; + ssize_t firmware_size; + + memset(&metadata, '\0', sizeof(firmware_metadata_t)); + + if (argc < 7) { + fprintf(stderr, "usage: %s\n", genmeta_usage); + return -1; + } + + /* Read secret key to sign metadata */ + if (!from_file(argv[5], sk, sizeof(sk))) { + fprintf(stderr, "Error: cannot read keyfile\n"); + return -1; + } + + firmware_size = do_sha256(argv[1], metadata.hash, 0); + if (firmware_size == -1) { + fprintf(stderr, "Error: cannot read firmware file\n"); + return -1; + } + + /* Generate FW image metadata */ + memcpy(&metadata.magic_number, "RIOT", 4); + metadata.size = firmware_size; + sscanf(argv[2], "%xu", (unsigned int *)&(metadata.version)); + sscanf(argv[3], "%xu", &(metadata.appid)); + sscanf(argv[4], "%xu", &(metadata.start_addr)); + + /* calculate metadata checksum */ + metadata.chksum = firmware_metadata_checksum(&metadata); + + /* sign */ + firmware_sign_metadata(&metadata, sk); + + /* talk to the user */ + firmware_metadata_print(&metadata); + + /* Write the metadata */ + printf("Metadata size: %lu\n", sizeof(firmware_metadata_t)); + if (!to_file(argv[6], &metadata, sizeof(firmware_metadata_t))) { + fprintf(stderr, "Error: cannot write output\n"); + return -1; + } + + return 0; +} diff --git a/dist/tools/firmware_metadata/randombytes.c b/dist/tools/firmware_metadata/randombytes.c new file mode 100644 index 000000000000..e9170bb9ae6a --- /dev/null +++ b/dist/tools/firmware_metadata/randombytes.c @@ -0,0 +1,62 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * randombytes/devurandom.h version 20080713 + * D. J. Bernstein + * Public domain. + */ + +#include +#include +#include +#include + +/* it's really stupid that there isn't a syscall for this */ + +static int fd = -1; + +void randombytes(unsigned char *x,unsigned long long xlen) +{ + int i; + + if (fd == -1) { + for (;;) { + fd = open("/dev/urandom",O_RDONLY); + if (fd != -1) break; + sleep(1); + } + } + + while (xlen > 0) { + if (xlen < 1048576) i = xlen; else i = 1048576; + + i = read(fd,x,i); + if (i < 1) { + sleep(1); + continue; + } + + x += i; + xlen -= i; + } +} diff --git a/dist/tools/firmware_metadata/verify.c b/dist/tools/firmware_metadata/verify.c new file mode 100644 index 000000000000..a49be7481e1a --- /dev/null +++ b/dist/tools/firmware_metadata/verify.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file LICENSE for more details. + */ + +#include +#include +#include + +#include "firmware.h" +#include "common.h" +#include "tweetnacl.h" + +const char verify_usage[] = "firmware verify "; + +int verify(int argc, char *argv[]) +{ + unsigned char pubkey[FIRMWARE_PUBKEY_LEN]; + firmware_metadata_t metadata; + + if (argc < 4) { + goto usage; + } + + if (!from_file(argv[2], &metadata, sizeof(metadata))) { + fprintf(stderr, "error reading image file\n"); + return 1; + } + + if (!from_file(argv[3], pubkey, sizeof(pubkey))) { + fprintf(stderr, "error reading keyfile\n"); + return 1; + } + + int res = firmware_validate_metadata_signature(&metadata, pubkey) ? 1 : 0; + if (res) { + printf("signature check failed\n"); + return -1; + } else { + printf("signature check passed\n"); + } + + off_t size = fsize(argv[2]); + if (size == sizeof(firmware_metadata_t)) { + printf("hash check skipped\n"); + goto out; + } + + char hash[SHA256_DIGEST_LENGTH]; + ssize_t hash_size; + hash_size = do_sha256(argv[2], hash, sizeof(firmware_metadata_t)); + printf("Hashed bytes: %zd\n", hash_size); + res = memcmp(hash, metadata.hash, SHA256_DIGEST_LENGTH) ? 1 : 0; + if (res) { + printf("hash check failed\n"); + puts("Metadata hash:"); + for (unsigned long i = 0; i < sizeof(metadata.hash); i++) { + printf("%02x ", metadata.hash[i]); + } + printf("\n"); + puts("Calculated hash:"); + for (unsigned long i = 0; i < sizeof(hash); i++) { + printf("%02x ", hash[i]); + } + printf("\n"); + } else { + printf("hash check passed\n"); + } + +out: + return res; + +usage: + fprintf(stderr, "usage: %s\n", verify_usage); + return 1; +} diff --git a/dist/tools/openocd/openocd.sh b/dist/tools/openocd/openocd.sh index d44eab208575..71869662344f 100755 --- a/dist/tools/openocd/openocd.sh +++ b/dist/tools/openocd/openocd.sh @@ -105,6 +105,20 @@ test_elffile() { fi } +test_binfile() { + if [ ! -f "${BINFILE}" ]; then + echo "Error: Unable to locate BINFILE" + echo " (${BINFILE})" + exit 1 + fi + if [ -z "${FLASH_ADDR}" ]; then + echo "Error: FLASH_ADDR not defined" + exit 1 + else + echo "Flashing at ${FLASH_ADDR}" + fi +} + test_ports() { if [ -z "${GDB_PORT}" ]; then GDB_PORT=${_GDB_PORT} @@ -186,6 +200,36 @@ do_flash_elf() { echo 'Done flashing' } +do_flash_bin() { + test_config + test_binfile + if [ -n "${PRE_FLASH_CHECK_SCRIPT}" ]; then + sh -c "${PRE_FLASH_CHECK_SCRIPT} '${HEXFILE}'" + RETVAL=$? + if [ $RETVAL -ne 0 ]; then + echo "pre-flash checks failed, status=$RETVAL" + exit $RETVAL + fi + fi + # flash device + sh -c "${OPENOCD} -f '${OPENOCD_CONFIG}' \ + ${OPENOCD_EXTRA_INIT} \ + -c 'tcl_port 0' \ + -c 'telnet_port 0' \ + -c 'gdb_port 0' \ + -c 'init' \ + -c 'targets' \ + -c 'reset halt' \ + ${OPENOCD_PRE_FLASH_CMDS} \ + -c 'program \"${BINFILE}\" \"${FLASH_ADDR}\"' \ + -c 'reset halt' \ + ${OPENOCD_PRE_VERIFY_CMDS} \ + -c 'verify_image \"${BINFILE}\" \"${FLASH_ADDR}\" bin' \ + -c 'reset run' \ + -c 'shutdown'" && + echo 'Done flashing' +} + do_debug() { test_config test_elffile @@ -263,6 +307,10 @@ case "${ACTION}" in echo "### Flashing Target ###" do_flash_elf "$@" ;; + flash-bin) + echo "### Flashing Target ###" + do_flash_bin "$@" + ;; debug) echo "### Starting Debugging ###" do_debug "$@" diff --git a/makefiles/multislot.mk b/makefiles/multislot.mk index 94fb5f35d8e1..e1624838ff4f 100644 --- a/makefiles/multislot.mk +++ b/makefiles/multislot.mk @@ -1,3 +1,4 @@ +ifneq (,$(filter mcuboot create-key flash-bootloader flash-mcuboot, $(MAKECMDGOALS))) ifdef SLOT0_SIZE IMGTOOL ?= $(RIOTBASE)/dist/tools/mcuboot/imgtool.py @@ -51,4 +52,136 @@ else mcuboot: $(Q)echo "error: mcuboot not supported on board $(BOARD)!" $(Q)false + endif # SLOT0_SIZE +endif # MAKECMDGOALS + +ifneq (,$(filter multislot combined flash-multislot keys verify bootloader multislot-clean, $(MAKECMDGOALS))) + +ifndef SLOT0_SIZE +$(error Board $(BOARD) does not define multislot parameters!) +endif + +ELFBOOTLOADER := $(ELFFILE) +ELFSLOT1 := $(ELFFILE:%.elf=%.slot1.elf) +ELFSLOT2 := $(ELFFILE:%.elf=%.slot2.elf) + +BINBOOTLOADER := $(ELFFILE:%.elf=%.bin) +BINSLOT1 := $(ELFSLOT1:%.elf=%.bin) +BINSLOT2 := $(ELFSLOT2:%.elf=%.bin) + +COMBINED_BIN := $(ELFFILE:%.elf=%-slot1-$(FW_APPID)-$(FW_VERSION).combined.bin) +IMAGE_SLOT1 := $(ELFFILE:%.elf=%-slot1-$(FW_APPID)-$(FW_VERSION).bin) +IMAGE_SLOT2 := $(ELFFILE:%.elf=%-slot2-$(FW_APPID)-$(FW_VERSION).bin) + +FIRMWARE_METADATA_SIZE ?= 256 +SLOT0_ADDR_DEC = $(shell printf "%d" $(SLOT0_ADDR)) +SLOT0_SIZE_DEC = $(shell printf "%d" $(SLOT0_SIZE)) +SLOT1_SIZE_DEC = $(shell printf "%d" $(SLOT1_SIZE)) +FIRMWARE_METADATA_SIZE_DEC = $(shell printf "%d" $(FIRMWARE_METADATA_SIZE)) +SLOT1_ADDR = $(shell echo 'obase=16; ${SLOT0_ADDR_DEC}+${SLOT0_SIZE_DEC}+${FIRMWARE_METADATA_SIZE_DEC}' | bc) +SLOT2_ADDR = $(shell echo 'obase=16; ${SLOT0_ADDR_DEC}+${SLOT0_SIZE_DEC}+${SLOT1_SIZE_DEC}+${FIRMWARE_METADATA_SIZE_DEC}' | bc) + +FIRMWARE_TOOLS = $(RIOTBASE)/dist/tools/firmware_metadata +FIRMWARE = $(RIOTBASE)/dist/tools/firmware_metadata/bin/firmware +GENMETA = $(RIOTBASE)/dist/tools/firmware_metadata/bin/firmware genmeta +GENKEYS = $(RIOTBASE)/dist/tools/firmware_metadata/bin/firmware genkeys +VERIFY = $(RIOTBASE)/dist/tools/firmware_metadata/bin/firmware verify +PUBKEY_DIR = $(RIOTBASE)/sys/include/crypto + +ifndef_any_of = $(filter undefined,$(foreach v,$(1),$(origin $(v)))) + +ifneq ($(call ifndef_any_of,PUBKEY SECKEY),) +PUBKEY = $(BINDIR)/ed25519.pub +SECKEY = $(BINDIR)/ed25519.sec +keys: firmware-tools $(PUBKEY_DIR)/ed25519_pub.h +$(PUBKEY_DIR)/ed25519_pub.h: + $(Q)mkdir -p $(BINDIR) + @$(COLOR_ECHO) + @$(COLOR_ECHO) '${COLOR_RED}Providing default crypto keys to current firmware. \ +Please provide PUBKEY and SECKEY env variables to use your keys \ +${COLOR_RESET}' + @$(COLOR_ECHO) + $(Q)$(GENKEYS) $(SECKEY) $(PUBKEY) + $(Q)cp $(PUBKEY) ./ed25519.pub + $(Q)xxd -i ed25519.pub > $(PUBKEY_DIR)/ed25519_pub.h + $(Q)rm ./ed25519.pub +else +keys:$(PUBKEY_DIR)/ed25519_pub.h +$(PUBKEY_DIR)/ed25519_pub.h: + $(info Using given keys) + $(Q)cp $(PUBKEY) ./ed25519.pub + $(Q)xxd -i ed25519.pub > $(PUBKEY_DIR)/ed25519_pub.h + $(Q)rm ./ed25519.pub +endif # SECKEY + +firmware-tools: $(FIRMWARE) +$(FIRMWARE): + $(Q)env -i PATH=$(PATH) CFLAGS+=-DFIRMWARE_METADATA_SIZE=$(FIRMWARE_METADATA_SIZE) \ + make -C $(FIRMWARE_TOOLS) + +ifneq (, $(filter $(USEMODULE),bootloader)) +$(info BOOTLOADER BUILD) +BOOTLOADER = 1 +else +BOOTLOADER = 0 +BOOTLOADER_BIN = $(RIOTBASE)/bootloader/bin/$(BOARD)/bootloader.bin +$(BOOTLOADER_BIN): + @env -i PATH=$(PATH) BOARD=$(BOARD) make -C $(RIOTBASE)/bootloader clean bootloader +endif + +bootloader: link + @$(_LINK) \ + $(LINKFLAGPREFIX)--defsym=length="$(SLOT0_SIZE)" \ + -o $(ELFBOOTLOADER) && \ + $(OBJCOPY) -Obinary $(ELFBOOTLOADER) $(BINBOOTLOADER) && \ + truncate -s $$(($(SLOT0_SIZE))) $(BINBOOTLOADER) + +multislot: keys link $(BOOTLOADER_BIN) firmware-tools + $(Q)$(_LINK) \ + $(LINKFLAGPREFIX)--defsym=offset="$(SLOT0_SIZE)+$(FIRMWARE_METADATA_SIZE)" \ + $(LINKFLAGPREFIX)--defsym=length="$(SLOT1_SIZE)-$(FIRMWARE_METADATA_SIZE)" \ + -o $(ELFSLOT1) && \ + $(OBJCOPY) -Obinary $(ELFSLOT1) $(BINSLOT1) && \ + $(GENMETA) $(BINSLOT1) $(FW_VERSION) $(FW_APPID) $(SLOT1_ADDR) $(SECKEY) \ + $(BINSLOT1).meta ; \ + \ + $(_LINK) \ + $(LINKFLAGPREFIX)--defsym=offset="$(SLOT0_SIZE)+$(SLOT1_SIZE)+$$(($(FIRMWARE_METADATA_SIZE)))" \ + $(LINKFLAGPREFIX)--defsym=length="$(SLOT2_SIZE)-$(FIRMWARE_METADATA_SIZE)" \ + -o $(ELFSLOT2) && \ + $(OBJCOPY) -Obinary $(ELFSLOT2) $(BINSLOT2) && \ + $(GENMETA) $(BINSLOT2) $(FW_VERSION) $(FW_APPID) $(SLOT2_ADDR) $(SECKEY) \ + $(BINSLOT2).meta ; \ + +combined: multislot + $(Q)sh -c 'cat $(BINSLOT1).meta $(BINSLOT1) > $(IMAGE_SLOT1)' + $(Q)sh -c 'cat $(BINSLOT2).meta $(BINSLOT2) > $(IMAGE_SLOT2)' + $(Q)sh -c 'cat $(BOOTLOADER_BIN) $(BINSLOT1).meta $(BINSLOT1) > $(COMBINED_BIN)' + +.PHONY: flash-multislot verify + +verify: firmware-tools $(PUBKEY) combined + $(Q)$(VERIFY) $(IMAGE_SLOT1) $(PUBKEY) + $(Q)$(VERIFY) $(IMAGE_SLOT2) $(PUBKEY) + +ifneq (, $(filter $(BOARD),iotlab-m3)) +export BINFILE = $(COMBINED_BIN) +FFLAGS = flash-bin +FLASH_ADDR = $(SLOT0_ADDR) +else +flash-multislot: HEXFILE = $(COMBINED_BIN) +endif + +flash-multislot: combined $(FLASHDEPS) + $(FLASHER) $(FFLAGS) + +firmware-tools-clean: + make clean distclean -C $(FIRMWARE_TOOLS) + +multislot-clean: firmware-tools-clean + $(Q)rm -rf $(BOOTLOADER_BIN) + $(Q)rm -rf $(FIRMWARE) + $(Q)rm -rf $(PUBKEY_DIR)/ed25519_pub.h + +endif # multislot targets filter diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 3c3e4dd89b03..7a1c0e59cd7c 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -50,6 +50,7 @@ PSEUDOMODULES += netstats_rpl PSEUDOMODULES += newlib PSEUDOMODULES += newlib_nano PSEUDOMODULES += openthread +PSEUDOMODULES += ota_update_tftp PSEUDOMODULES += pktqueue PSEUDOMODULES += posix PSEUDOMODULES += printf_float diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index bbbd2ae2ef53..76dbe5f504d4 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -88,6 +88,10 @@ #include "net/gcoap.h" #endif +#ifdef MODULE_OTA_UPDATE +#include "ota_update.h" +#endif + #define ENABLE_DEBUG (0) #include "debug.h" @@ -157,6 +161,10 @@ void auto_init(void) extern void auto_init_devfs(void); auto_init_devfs(); #endif +#ifdef MODULE_OTA_UPDATE + DEBUG("Auto init ota_update module.\n"); + ota_update_init(); +#endif /* initialize network devices */ #ifdef MODULE_AUTO_INIT_GNRC_NETIF diff --git a/sys/firmware/Makefile b/sys/firmware/Makefile new file mode 100644 index 000000000000..acc57a318e04 --- /dev/null +++ b/sys/firmware/Makefile @@ -0,0 +1,6 @@ +CFLAGS += -DFIRMWARE_NUM_SLOTS=3 +CFLAGS += -DFIRMWARE_SLOT0_SIZE=$(SLOT0_SIZE) +CFLAGS += -DFIRMWARE_SLOT1_SIZE=$(SLOT1_SIZE) +CFLAGS += -DFIRMWARE_SLOT2_SIZE=$(SLOT2_SIZE) + +include $(RIOTBASE)/Makefile.base diff --git a/sys/firmware/firmware.c b/sys/firmware/firmware.c new file mode 100644 index 000000000000..9862912e84ef --- /dev/null +++ b/sys/firmware/firmware.c @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * 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_firmware + * @{ + * + * @file + * @brief Firmware module + * + * @author Kaspar Schleiser + * @author Francisco Acosta + * + * @} + */ + +#include + +#ifdef RIOT_VERSION +#include "cpu.h" +#include "log.h" +#include "crypto/ed25519_pub.h" +#include "periph/flashpage.h" +#else +#include +#define LOG_INFO(...) printf(__VA_ARGS__) +#endif + +#include "firmware.h" +#include "checksum/fletcher32.h" +#include "tweetnacl.h" + +void firmware_metadata_print(firmware_metadata_t *metadata) +{ + printf("Firmware magic_number: 0x%08x\n", (unsigned)metadata->magic_number); + printf("Firmware Size: %lu\n", metadata->size); + printf("Firmware APPID: %#lx\n", metadata->appid); + printf("Firmware Version: %#lx\n", metadata->version); + printf("Firmware start address: 0x%08x\n", (unsigned)metadata->start_addr); + printf("Firmware HASH: "); + for (unsigned long i = 0; i < sizeof(metadata->hash); i++) { + printf("%02x ", metadata->hash[i]); + } + printf("\n"); + printf("Firmware chksum: 0x%08x\n", (unsigned)metadata->chksum); + printf("Firmware signature: "); + for (unsigned long i = 0; i < sizeof(metadata->sig); i++) { + printf("%02x ", metadata->sig[i]); + } + printf("\n"); +} + +int firmware_validate_metadata_checksum(firmware_metadata_t *metadata) +{ + if (memcmp(metadata, "RIOT", 4)) { + printf("%s: metadata magic number invalid\n", __func__); + return -1; + } + + int res = firmware_metadata_checksum(metadata) == metadata->chksum ? 0 : -1; + if (res) { + printf("%s: metadata checksum invalid\n", __func__); + } + return res; +} + +int firmware_validate_metadata_signature(firmware_metadata_t *metadata, unsigned char *pk) +{ + if (firmware_validate_metadata_checksum(metadata)) { + return -1; + } + + unsigned char sm[FIRMWARE_SIGN_BYTES + crypto_sign_BYTES]; + memcpy(sm, ((unsigned char *)metadata) + FIRMWARE_SIGN_BYTES, crypto_sign_BYTES); + memcpy(sm + crypto_sign_BYTES, metadata, FIRMWARE_SIGN_BYTES); + + unsigned char m[FIRMWARE_SIGN_BYTES + crypto_sign_BYTES]; + unsigned long long mlen; + int res = crypto_sign_open(m, &mlen, sm, FIRMWARE_SIGN_BYTES + crypto_sign_BYTES, pk); + if (res) { + printf("%s: metadata signature invalid\n", __func__); + } + return res; +} + +uint32_t firmware_metadata_checksum(firmware_metadata_t *metadata) +{ + return fletcher32((uint16_t*)metadata, FIRMWARE_CHECKSUM_LEN / 2); +} + +int firmware_sign_metadata(firmware_metadata_t *metadata, unsigned char *sk) +{ + unsigned char sm[FIRMWARE_SIGN_BYTES + crypto_sign_BYTES]; + unsigned long long smlen; + crypto_sign(sm, &smlen, (unsigned char*)metadata, FIRMWARE_SIGN_BYTES, sk); + memcpy(metadata->sig, sm, crypto_sign_BYTES); + return 0; +} + +#ifdef RIOT_VERSION +static const unsigned _firmware_slot_start[] = { + CPU_FLASH_BASE, + CPU_FLASH_BASE + FIRMWARE_SLOT0_SIZE, + CPU_FLASH_BASE + FIRMWARE_SLOT0_SIZE + FIRMWARE_SLOT1_SIZE +}; + +void firmware_jump_to_image(firmware_metadata_t *metadata) +{ + uint32_t addr = (unsigned)metadata + (unsigned)sizeof(firmware_metadata_t); + printf("[bootloader] jumping to %#lx\n", addr); + cpu_jump_to_image(addr); +} + +int firmware_current_slot(void) +{ + unsigned base_addr = cpu_get_image_baseaddr() - sizeof(firmware_metadata_t); + + for (unsigned i = 1; i < FIRMWARE_NUM_SLOTS; i++) { + if (base_addr == _firmware_slot_start[i]) { + return i; + } + } + + return -1; +} + +firmware_metadata_t *firmware_get_metadata(int slot) { + assert(slot < FIRMWARE_NUM_SLOTS); + return (firmware_metadata_t *)_firmware_slot_start[slot]; +} + +unsigned firmware_get_image_startaddr(unsigned slot) +{ + assert(slot < FIRMWARE_NUM_SLOTS); + return _firmware_slot_start[slot] + sizeof(firmware_metadata_t); +} + +uint32_t firmware_get_slot_page(uint8_t slot) +{ + return cpu_get_slot_page(slot); +} + +void firmware_jump_to_slot(unsigned slot) +{ + firmware_jump_to_image(firmware_get_metadata(slot)); +} + +void firmware_dump_slot_addrs(void) +{ + for (unsigned i = 1; i < FIRMWARE_NUM_SLOTS; i++) { + printf("slot %u: metadata: 0x%08x image: 0x%08x\n", i, \ + (unsigned)_firmware_slot_start[i], firmware_get_image_startaddr(i)); + } +} + +uint32_t firmware_get_slot_address(uint8_t slot) +{ + return cpu_get_slot_address(slot); +} + +int firmware_validate_int_slot(uint8_t slot) +{ + firmware_metadata_t metadata; + int result; + + assert(slot < FIRMWARE_NUM_SLOTS); + + memcpy(&metadata, firmware_get_metadata(slot), sizeof(firmware_metadata_t)); + result = firmware_validate_metadata_signature(&metadata, ed25519_pub); + + if (result == -1) { + printf("[firmware] Signature check failed!\n"); + return result; + } + + return result; +} + +int firmware_find_newest_int_image(void) +{ + /* At first, we only assume knowledge of version v0 */ + uint32_t slot1_version = 0; + uint32_t slot2_version = 0; + firmware_metadata_t metadata[2]; + + memcpy(&metadata[0], firmware_get_metadata(1), sizeof(firmware_metadata_t)); + memcpy(&metadata[1], firmware_get_metadata(2), sizeof(firmware_metadata_t)); + + if (firmware_validate_metadata_checksum(&metadata[0]) != -1) { + firmware_metadata_print(&metadata[0]); + slot1_version = metadata[0].version; + } + else { + printf("[firmware] ERROR cannot get slot 1 metadata.\n"); + } + + if (firmware_validate_metadata_checksum(&metadata[1]) != -1) { + firmware_metadata_print(&metadata[1]); + slot2_version = metadata[1].version; + } + else { + printf("[firmware] ERROR cannot get slot 2 metadata.\n"); + } + + if (slot1_version > slot2_version) { + return 1; + } + else if (slot1_version < slot2_version) { + return 2; + } + else { + return -1; + } + + return -1; +} + +int firmware_erase_int_image(uint8_t slot) +{ + /* Get the page where the fw_slot is located */ + uint32_t slot_page; + uint32_t slot_pages_nbr = SLOT_SIZE / FLASHPAGE_SIZE; + + printf("[firmware] Erasing %ld pages for slot %d\n", slot_pages_nbr, slot); + + assert(slot < FIRMWARE_NUM_SLOTS); + + slot_page = firmware_get_slot_page(slot); + + printf("[firmware] Starting at page: %ld\n", slot_page); + + /* Erase each page in the FW internal slot! */ + for (int page = slot_page; page < slot_page + slot_pages_nbr; page++) { + flashpage_write(page, NULL); + } + + return 0; +} + +#endif /* RIOT_VERSION */ diff --git a/sys/include/firmware.h b/sys/include/firmware.h new file mode 100644 index 000000000000..4d1440ff93c5 --- /dev/null +++ b/sys/include/firmware.h @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * 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_firmware + * @{ + * + * @file + * @brief Firmware module + * + * @author Kaspar Schleiser + * @author Francisco Acosta + * + * @} + */ + +#ifndef FIRMWARE_H +#define FIRMWARE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "hashes/sha256.h" +#include "tweetnacl.h" + +/** + * @brief FIRMWARE_SIG_LEN: + * Provisional length for signed hash + */ +#define FIRMWARE_SIG_LEN (crypto_sign_BYTES) + +/** + * @brief FIRMWARE_SECKEY_LEN: + * Length of secret Ed25519 key + */ +#define FIRMWARE_SECKEY_LEN (crypto_sign_SECRETKEYBYTES) + +/** + * @brief FIRMWARE_SECKEY_LEN: + * Length of public Ed25519 key + */ +#define FIRMWARE_PUBKEY_LEN (crypto_sign_PUBLICKEYBYTES) + +/** + * @brief FIRMWARE_CHECKSUM_WORDS: + * number of words to be fletcher32()'ed + * + */ +#define FIRMWARE_CHECKSUM_WORDS (10) + +/** + * @brief FIRMWARE_CHECKSUM_LEN: + * TODO. + */ +#define FIRMWARE_CHECKSUM_LEN (20) + +/** + * @brief FIRMWARE_SIGN_BYTES: + * Length of bytes string to be signed + */ +#define FIRMWARE_SIGN_BYTES (FIRMWARE_CHECKSUM_LEN + 4 + SHA256_DIGEST_LENGTH) + +/** + * @brief FIRMWARE_METADATA_SIZE: + * Length of appended metadata on firmware binaries. + * It can be overrode. + */ +#ifndef FIRMWARE_METADATA_SIZE +#define FIRMWARE_METADATA_SIZE (256) +#endif + +/** + * @brief FIRMWARE_PADDING: + * Length of 0xFF padding to align metadata size + */ +#define FIRMWARE_PADDING (FIRMWARE_METADATA_SIZE-(FIRMWARE_SIGN_BYTES + FIRMWARE_SIG_LEN)) + +/** + * @brief Structure to store firmware metadata + * @{ + */ +typedef struct firmware_metadata { + uint32_t magic_number; /**< metadata magic_number (always "RIOT") */ + uint32_t appid; /**< Integer representing the application ID*/ + uint32_t version; /**< Integer representing firmware version */ + uint32_t size; /**< Size of firmware image */ + uint32_t start_addr; /**< Start address in flash */ + uint32_t chksum; /**< checksum of metadata */ + uint8_t hash[SHA256_DIGEST_LENGTH]; /**< SHA256 Hash of firmware image */ + uint8_t sig[FIRMWARE_SIG_LEN]; /**< Firmware signature */ + uint8_t pad[FIRMWARE_PADDING]; /**< padding to total of FIRMWARE_METADATA_SIZE bytes */ +} firmware_metadata_t; +/** @} */ + +/** + * @brief Print formatted FW image metadata to STDIO. + * + * @param[in] metadata ptr to firmware metadata + * + */ +void firmware_metadata_print(firmware_metadata_t *metadata); + +/** + * @brief Validate FW image metadata + * + * @param[in] metadata ptr to firmware metadata + * + */ +int firmware_validate_metadata_checksum(firmware_metadata_t *metadata); + +/** + * @brief Calculate metadata checksum + * + * @param[in] metadata ptr to firmware metadata + * + */ +uint32_t firmware_metadata_checksum(firmware_metadata_t *metadata); + +/** + * @brief Sign metadata + * + * @param[in] metadata ptr to firmware metadata + * @param[in] sk NaCL secret signing key to use + * + */ +int firmware_sign_metadata(firmware_metadata_t *metadata, unsigned char *sk); + +/** + * @brief Validate FW metadata signature + * + * @param[in] metadata ptr to firmware metadata + * @param[in] pk NaCL public signing key to use + * + */ +int firmware_validate_metadata_signature(firmware_metadata_t *metadata, unsigned char *pk); + +/** + * @brief Jump to image + * + * @param[in] metadata ptr to firmware metadata + * + */ +void firmware_jump_to_image(firmware_metadata_t *metadata); + +/** + * @brief Get currently running image slot + */ +int firmware_current_slot(void); + +/** + * @brief Get metadata of firmware slot + */ +firmware_metadata_t *firmware_get_metadata(int slot); + +/** + * @brief Get jump-to address of firmware slot + */ +unsigned firmware_get_image_startaddr(unsigned slot); + +/** + * @brief Boot into image in slot @p slot + */ +void firmware_jump_to_slot(unsigned slot); + +/** + * @brief Dump firmware slot addresses + */ +void firmware_dump_slot_addrs(void); + +/** + * @brief Validate internal slot as a secure firmware + * + * @param[in] slot The slot to be validated. + * + * @return 0 on success or error code + */ +int firmware_validate_int_slot(uint8_t slot); + +/** + * @brief Get the address corresponding to a given slot + * + * @param[in] slot The slot to get the address. + * + * + * @return 0 on success or error code + */ +uint32_t fw_slots_get_slot_address(uint8_t slot); + +/** + * @brief Find the slot containing the most recent firmware version. + * slots are in internal flash. + * + * @return The slot index of the newest firmware version. + */ +int firmware_find_newest_int_image(void); + +/** + * @brief Clear an slot in internal flash. + * + * @param[in] slot The slot index of the firmware image to be erased. + * + * @return -1 or error code + */ +int firmware_erase_int_image(uint8_t slot); + +/** + * @brief Get the page corresponding to a given slot + * + * @param[in] slot The slot to get the page. + * + * + * @return 0 on success or error code + */ +uint32_t firmware_get_slot_page(uint8_t slot); + +#ifdef __cplusplus +} +#endif + +#endif /* FIRMWARE_H */ diff --git a/sys/include/hashes/sha256.h b/sys/include/hashes/sha256.h index 0a288342a214..22ff558a013e 100644 --- a/sys/include/hashes/sha256.h +++ b/sys/include/hashes/sha256.h @@ -47,6 +47,7 @@ #define HASHES_SHA256_H #include +#include #ifdef __cplusplus extern "C" { diff --git a/sys/include/ota_update.h b/sys/include/ota_update.h new file mode 100644 index 000000000000..958acbaa593a --- /dev/null +++ b/sys/include/ota_update.h @@ -0,0 +1,100 @@ +/** + * Copyright (C) 2017 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. + */ + +/** + * @file + * @author Francisco Acosta + */ + +#ifndef OTA_UPDATE_H +#define OTA_UPDATE_H + +#include "cpu_conf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Size for module message queue + */ +#define OTA_UPDATE_MSG_QUEUE_SIZE (4) + +/** + * @brief Timeout for a CoAP update request + */ +#define OTA_REQ_TIMEOUT (5000000UL) + +/** + * @brief Default firmware name update + */ +#ifndef FIRMWARE_NAME +#define FIRMWARE_NAME "firmware.bin" +#endif + +/** + * s@brief Default updates server address + */ +#ifndef OTA_SERVER_ADDRESS +#define OTA_SERVER_ADDRESS "fd00:dead:beef::1" +#endif + +/** + * @brief Default CoAP updates server port + */ +#ifndef OTA_SERVER_COAP_PORT +#define OTA_SERVER_COAP_PORT "5683" +#endif + +/** + * @brief Stack size for OTA module thread + */ +#ifndef OTA_UPDATE_STACK +#define OTA_UPDATE_STACK ((THREAD_STACKSIZE_DEFAULT*2) + FLASHPAGE_SIZE) +#endif + +/** + * @brief Peiodic request time for update + */ +#ifndef OTA_PERIODIC_REQ_TIME +#define OTA_PERIODIC_REQ_TIME (120) +#endif + +/** + * @brief Size for module message queue + */ +#define OTA_MSG_QUEUE_SIZE (4) + +/** + * @brief Length of the firmware string filename + */ +#define FIRMWARE_NAME_LENGTH (128) + +/** + * @brief Enum for CoAP requests types for the update process + * @{ + */ +typedef enum { + COAP_GET_VERSION = 0, /**< Request for the newest available firmware version */ + COAP_GET_NAME = 1, /**< Request for the newest firmware name */ +} ota_request_t; +/** @} */ + +/** + * @brief Init function for the ota_update module + * + * @return The kernel PID for the running module + * + */ +kernel_pid_t ota_update_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* OTA_UPDATE_H */ diff --git a/sys/ota_update/Makefile b/sys/ota_update/Makefile new file mode 100644 index 000000000000..48422e909a47 --- /dev/null +++ b/sys/ota_update/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/ota_update/ota_update.c b/sys/ota_update/ota_update.c new file mode 100644 index 000000000000..47715b52d78a --- /dev/null +++ b/sys/ota_update/ota_update.c @@ -0,0 +1,396 @@ +/** + * Copyright (C) 2017 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. + */ + +/** + * @file + * @author Francisco Acosta + */ + +#include +#include +#include +#include +#include + +#include "net/gcoap.h" +#include "ota_update.h" +#include "net/gnrc/tftp.h" +#include "periph/flashpage.h" +#include "cpu_conf.h" +#include "firmware.h" +#include "xtimer.h" +#include "periph/pm.h" +#include "sema.h" + +static kernel_pid_t ota_update_pid = KERNEL_PID_UNDEF; +static char _msg_stack[OTA_UPDATE_STACK]; +static msg_t _ota_update_msg_queue[OTA_UPDATE_MSG_QUEUE_SIZE]; + +#ifdef MODULE_OTA_UPDATE_TFTP +static tftp_action_t _tftp_action; +#endif +static uint8_t buf[FLASHPAGE_SIZE]; +static uint32_t buf_ptr = 0; +static uint32_t page_sum = 0; +static uint32_t slot_addr; +static uint16_t remote_version = 0; +static uint8_t remote_slot = 0; +static uint16_t local_version; +static uint32_t local_appid; +static ota_request_t ota_request; +static char update_name[FIRMWARE_NAME_LENGTH]; +/* Counts requests sent by ota_update. */ +static uint16_t req_count = 0; +static sema_t sema_fw_version = SEMA_CREATE_LOCKED(); +static sema_t sema_fw_name = SEMA_CREATE_LOCKED(); + +/* + * Response callback. + */ +static void _resp_handler(unsigned req_state, coap_pkt_t* pdu, + sock_udp_ep_t *remote) +{ + (void)remote; /* not interested in the source currently */ + + if (req_state == GCOAP_MEMO_TIMEOUT) { + printf("[ota_update] timeout for msg ID %02u\n", coap_get_id(pdu)); + return; + } + else if (req_state == GCOAP_MEMO_ERR) { + printf("[ota_update] error in response\n"); + return; + } + + char *class_str = (coap_get_code_class(pdu) == COAP_CLASS_SUCCESS) + ? "Success" : "Error"; + printf("[ota_update] %s, response code %1u.%02u", class_str, + coap_get_code_class(pdu), + coap_get_code_detail(pdu)); + if (pdu->payload_len) { + char remote_version_str[10]; + printf(", %u bytes :%.*s\n", pdu->payload_len, pdu->payload_len, + (char *)pdu->payload); + switch (ota_request) { + case COAP_GET_VERSION: + strncpy(remote_version_str, (char *)pdu->payload, pdu->payload_len); + remote_version = strtol(remote_version_str, NULL, 16); + printf("[ota_update] Got remote version %#x\n", remote_version); + sema_post(&sema_fw_version); + break; + case COAP_GET_NAME: + strncpy(update_name, (char *)pdu->payload, pdu->payload_len); + printf("[ota_update] Got remote name %s\n", update_name); + sema_post(&sema_fw_name); + break; + } + } + else { + printf(", empty payload\n"); + } +} + +static size_t _send(uint8_t *buf, size_t len, char *addr_str, char *port_str) +{ + ipv6_addr_t addr; + size_t bytes_sent; + sock_udp_ep_t remote; + + remote.family = AF_INET6; + remote.netif = SOCK_ADDR_ANY_NETIF; + + /* parse destination address */ + if (ipv6_addr_from_str(&addr, addr_str) == NULL) { + puts("[ota_update] unable to parse destination address"); + return 0; + } + memcpy(&remote.addr.ipv6[0], &addr.u8[0], sizeof(addr.u8)); + + /* parse port */ + remote.port = atoi(port_str); + if (remote.port == 0) { + puts("[ota_update] unable to parse destination port"); + return 0; + } + + bytes_sent = gcoap_req_send2(buf, len, &remote, _resp_handler); + if (bytes_sent > 0) { + req_count++; + } + return bytes_sent; +} + +static bool _save_to_page(uint8_t *data, size_t data_len) +{ + if ((buf_ptr + data_len) <= FLASHPAGE_SIZE) { + memcpy(buf + buf_ptr, data, data_len); + buf_ptr += data_len; + } + else { + uint32_t rest = (buf_ptr + data_len) - FLASHPAGE_SIZE; + size_t tmp_len = data_len - rest; + memcpy(buf + buf_ptr, data, tmp_len); + int err; + err = flashpage_write_and_verify(page_sum, buf); + if (err == FLASHPAGE_OK) { + printf("[ota_update] Successfully written page %lu at %p\n", + page_sum, flashpage_addr(page_sum)); + page_sum++; + memcpy(buf, data + tmp_len, rest); + buf_ptr = rest; + } + else { + printf("[ota_update] Flash program failed with error %d\n", err); + return false; + } + } + + return true; +} + +static int _write_last_page(uint32_t page, uint32_t slot_page) +{ + if (page != slot_page) { + int err; + err = flashpage_write_and_verify(page, buf); + if (err == FLASHPAGE_OK) { + printf("[ota_update] Successfully written page %lu at %p\n", + page, flashpage_addr(page)); + page_sum = slot_page; + buf_ptr = 0; + memset(buf, 0xFF, sizeof(buf)); + return true; + } else { + printf("[ota_update] Flash program failed with error %d\n", err); + return false; + } + } + + return false; +} + +#ifdef MODULE_OTA_UPDATE_TFTP +/* default server text which can be received */ +static const char _tftp_client_hello[] = "Hello,\n" + "\n" + "Client text would also need to exist to be able to put data.\n" + "\n" + "Enjoy the RIOT-OS\n"; + +static int _tftp_client_data_cb(uint32_t offset, void *data, size_t data_len) +{ + char *c = (char *) data; + + /* we received a data block which we save on the corresponding slot */ + _save_to_page((uint8_t*)c, data_len); + + /* return the length of the data block */ + return data_len; +} + +/** + * @brief called at every transaction start + */ +static bool _tftp_client_start_cb(tftp_action_t action, tftp_mode_t mode, + const char *file_name, size_t *len) +{ + /* translate the mode */ + const char *str_mode = "ascii"; + + if (mode == TTM_OCTET) { + str_mode = "bin"; + } + else if (mode == TTM_MAIL) { + str_mode = "mail"; + } + + /* translate the action */ + const char *str_action = "read"; + if (action == TFTP_WRITE) { + str_action = "write"; + } + + /* display the action being performed */ + printf("tftp_client: %s %s %s:%lu\n", str_mode, str_action, file_name, (unsigned long)*len); + + /* return the length of the text, if this is an read action */ + if (action == TFTP_READ) { + *len = sizeof(_tftp_client_hello); + } + + /* remember the action of the current transfer */ + _tftp_action = action; + + /* we accept the transfer to take place so we return true */ + return true; +} + +/** + * @brief the transfer has stopped, see the event argument to determined if it was successful + * or not. + */ +static void _tftp_client_stop_cb(tftp_event_t event, const char *msg) +{ + /* decode the stop event received */ + const char *cause = "UNKOWN"; + + if (event == TFTP_SUCCESS) { + cause = "SUCCESS"; + puts("tftp_client: writing last page"); + _write_last_page(page_sum, slot_addr); + } + else if (event == TFTP_PEER_ERROR) { + cause = "ERROR From Client"; + } + else if (event == TFTP_INTERN_ERROR) { + cause = "ERROR Internal Server Error"; + } + + /* print the transfer result to the console */ + if (msg != NULL) { + printf("tftp_client: %s: %s\n", cause, msg); + } + else { + printf("tftp_client: %s\n", cause); + } +} + +static void ota_update_tftp(uint8_t firmware_slot, char *name, char *server_ip_address) +{ + ipv6_addr_t ip; + tftp_mode_t mode = TTM_OCTET; + bool use_options = true; + int ret; + + page_sum = firmware_get_slot_page(firmware_slot); + + puts("[ota_update] RIOT TFTP OTA update starting..."); + printf("[ota_update] Erasing slot %d...\n", firmware_slot); + firmware_erase_int_image(firmware_slot); + + + ipv6_addr_from_str(&ip, server_ip_address); + _tftp_action = TFTP_READ; + + printf("[ota_update] Sending request for %s to the server %s\n", + name, + server_ip_address); + + ret = gnrc_tftp_client_read(&ip, name, mode, _tftp_client_data_cb, + _tftp_client_start_cb, _tftp_client_stop_cb, + use_options); + + if (ret > 0) { + puts("[ota_update] Update finished, rebooting in 5 seconds..."); + xtimer_sleep(5); + pm_reboot(); + } else { + puts("[ota_update] Update failed!"); + printf("[ota_update] Error: %d\n", ret); + } +} +#endif /* MODULE_OTA_UPDATE_TFTP */ + +static void *ota_update_start(void *arg) +{ + (void)arg; + msg_init_queue(_ota_update_msg_queue, OTA_UPDATE_MSG_QUEUE_SIZE); + + /* Wait a bit to initialise network addresses */ + xtimer_sleep(5); + + coap_pkt_t pdu; + firmware_metadata_t metadata; + uint8_t buf[GCOAP_PDU_BUF_SIZE]; + char coap_resource[32]; + int local_slot = firmware_current_slot(); + + memcpy(&metadata, firmware_get_metadata(local_slot), sizeof(firmware_metadata_t)); + + if (firmware_validate_metadata_checksum(&metadata) != -1) { + local_appid = metadata.appid; + local_version = metadata.version; + firmware_metadata_print(&metadata); + } + else { + puts("[ota_update] Error! Cannot retrieve local metadata\n"); + return 0; + } + + while(1) { + size_t len; + ota_request = 0; + memset(coap_resource, 0, sizeof(coap_resource)); + sprintf(coap_resource, "/0x%lX/version", local_appid); + + printf("[ota_update] Requesting resource %s\n", coap_resource); + len = gcoap_request(&pdu, &buf[0], GCOAP_PDU_BUF_SIZE, COAP_METHOD_GET, + coap_resource); + printf("[ota_update] sending msg ID %u, %u bytes\n", coap_get_id(&pdu), + (unsigned) len); + if (!_send(&buf[0], len, OTA_SERVER_ADDRESS, OTA_SERVER_COAP_PORT)) { + puts("[ota_update] msg send failed"); + } + + if (sema_wait_timed(&sema_fw_version, OTA_REQ_TIMEOUT) + == -ETIMEDOUT) { + printf("[ota_update] Request %u timed out\n", coap_get_id(&pdu)); + } + else { + + if (local_version >= remote_version) { + puts("[ota_update] No new firmware available"); + } + else { + if (local_slot == 1) { + remote_slot = 2; + } + else if (local_slot == 2) { + remote_slot = 1; + } + ota_request = 1; + memset(coap_resource, 0, sizeof(coap_resource)); + sprintf(coap_resource, "/0x%lX/slot%u/name", local_appid, remote_slot); + printf("[ota_update] Requesting resource %s\n", coap_resource); + len = gcoap_request(&pdu, &buf[0], GCOAP_PDU_BUF_SIZE, COAP_METHOD_GET, + coap_resource); + printf("[ota_update] sending msg ID %u, %u bytes\n", coap_get_id(&pdu), + (unsigned) len); + if (!_send(&buf[0], len, OTA_SERVER_ADDRESS, OTA_SERVER_COAP_PORT)) { + puts("[ota_update] msg send failed"); + } + + if (sema_wait_timed(&sema_fw_name, OTA_REQ_TIMEOUT) + == -ETIMEDOUT) { + printf("[ota_update] Request %u timed out\n", coap_get_id(&pdu)); + } + else { +#ifdef MODULE_OTA_UPDATE_TFTP + ota_update_tftp(remote_slot, update_name, OTA_SERVER_ADDRESS); +#endif + } + } + } + + xtimer_sleep(OTA_PERIODIC_REQ_TIME); + } + + /* Should not happen */ + return 0; +} + +kernel_pid_t ota_update_init(void) +{ + if (ota_update_pid != KERNEL_PID_UNDEF) { + return -EEXIST; + } + + ota_update_pid = thread_create(_msg_stack, sizeof(_msg_stack), THREAD_PRIORITY_MAIN - 1, + THREAD_CREATE_STACKTEST, ota_update_start, NULL, "ota_update"); + + return ota_update_pid; +} diff --git a/tests/bootloader_test/Makefile b/tests/bootloader_test/Makefile new file mode 100644 index 000000000000..47606a3dd8d2 --- /dev/null +++ b/tests/bootloader_test/Makefile @@ -0,0 +1,28 @@ +# name of your application +APPLICATION = bootloader_test + +# If no BOARD is found in the environment, use this default: +BOARD ?= samr21-xpro + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +CFLAGS += -DDEVELHELP + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +USEMODULE += firmware + +FW_APPID = 0xBB8 +FW_VERSION = 0x1 + +BOARD_WHITELIST := samr21-xpro iotlab-m3 + +# this test is supposed to always build an slotted image +all: combined + +include $(RIOTBASE)/Makefile.include diff --git a/tests/bootloader_test/README.md b/tests/bootloader_test/README.md new file mode 100644 index 000000000000..2c61aa38e311 --- /dev/null +++ b/tests/bootloader_test/README.md @@ -0,0 +1,21 @@ +Hello World! +============ + +This is a basic example how to use RIOT in your embedded application. +It prints out the famous text `Hello World!`. + +This example should foremost give you an overview how to use the Makefile system: + +* First you must give your application a name, which is commonly the same as the name of the directory it resides in. + Then you can define a default BOARD for which the application was written. + By using e.g. `make BOARD=msba2` you can override the default board. + With `make buildtest` the application gets compiled for all supported boards. + +* The variable `RIOTBASE` contains an absolute or relative path to the directory where you have checked out RIOT. + If your code resides in a subdirectory of RIOT, then you can use `$(CURDIR)` as it's done in here. + +* The variable `QUIET`, which is either `1` or `0`, defines whether to print verbose compile information, or hide them, respectively. + +* The last line of your Makefile must be `include $(RIOTBASE)/Makefile.include`. + +The code itself may look like your usual *C* beginners hello-world example. diff --git a/tests/bootloader_test/main.c b/tests/bootloader_test/main.c new file mode 100644 index 000000000000..9987836a0ab3 --- /dev/null +++ b/tests/bootloader_test/main.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * 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 tests + * @{ + * + * @file + * @brief Bootloader test application + * + * @author Kaspar Schleiser + * @author Francisco Acosta + * + * @} + */ + +#include + +#include "firmware.h" +#include "cpu.h" + +int main(void) +{ + puts("Hello World!"); + + printf("You are running RIOT on a(n) %s board.\n", RIOT_BOARD); + printf("This board features a(n) %s MCU.\n", RIOT_MCU); + printf("active firmware slot: %i\n", firmware_current_slot()); + + firmware_dump_slot_addrs(); + + return 0; +}