From b69ee61f075fa36843acd9381640c48fc18006ba Mon Sep 17 00:00:00 2001 From: Sergei Glushchenko Date: Sun, 8 Apr 2018 02:44:43 +0700 Subject: [PATCH] Support toboot API 2.0 - Support toboot API 2.0 - Allow injecting private key into firmware --- .travis.yml | 16 ++++-- mcu/sys-efm32.c | 6 +-- u2f/README.md | 66 +++++++++++------------ u2f/cert/dump-der.py | 4 +- u2f/inject_key.py | 68 +++++++++++++++++++++++ u2f/platform.c | 89 +++++------------------------- u2f/toboot.h | 125 +++++++++++++++++++++++++++++++++++++++++++ u2f/u2f-apdu.c | 43 ++++++++------- u2f/u2f.ld | 10 ++-- 9 files changed, 281 insertions(+), 146 deletions(-) create mode 100755 u2f/inject_key.py create mode 100644 u2f/toboot.h diff --git a/.travis.yml b/.travis.yml index 71dc844..146a953 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,24 @@ language: c +sudo: required dist: trusty addons: apt: packages: - - gcc-arm-none-eabi - - libnewlib-arm-none-eabi - python-pip - openssl install: - pip install asn1crypto --user + - sudo apt-add-repository -y ppa:team-gcc-arm-embedded/ppa + - sudo apt-get update + - sudo apt-get install -yy gcc-arm-embedded script: - - 'cd u2f && make && cd ..' - - 'cd u2f && make clean certclean && cd ..' - - 'cd u2f && make ENFORCE_DEBUG_LOCK=1 && cd ..' + - 'cd u2f' + - 'make' + - 'make clean certclean' + - 'make ENFORCE_DEBUG_LOCK=1' + - 'openssl ecparam -name prime256v1 -genkey -noout -outform der -out key.der' + - './inject_key.py --key key.der --ctr 1001' + - 'cd ..' diff --git a/mcu/sys-efm32.c b/mcu/sys-efm32.c index b6e46ee..2ebfe10 100644 --- a/mcu/sys-efm32.c +++ b/mcu/sys-efm32.c @@ -35,11 +35,7 @@ void reset (void); static uint32_t stack_entry[] __attribute__ ((section(".first_page.first_words"),used)) = { - (uint32_t)&__ram_end__, (uint32_t)reset, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0x0070b0 /* don't allow to enter toboot by shorting outer pads */, - 0x106fb0 /* let toboot know that starting offset is 0x4000 */ + (uint32_t)&__ram_end__, (uint32_t)reset }; typedef void (*handler)(void); diff --git a/u2f/README.md b/u2f/README.md index bb37074..5448b23 100644 --- a/u2f/README.md +++ b/u2f/README.md @@ -1,6 +1,6 @@ -# U2F firmware for STM32 +# U2F firmware for Tomu -U2F firmware for STM32F103 (ARM Cortex-M3). +U2F firmware for Tomu. ## Installing @@ -70,62 +70,62 @@ sudo apt install openocd ### Building ``` sh -cd u2f-token/u2f +cd u2f +make ``` -Chopstx comes with support for multiple STM32 boards. This firmware is known to -work on Maple Mini and Blue Pill. Default target is Maple Mini. To build -firmware for Blue Pill run: +### Flashing + +Providing you have Toboot installed: ``` sh -ln -s ../board/board-blue-pill.h board.h +dfu-util -v -d 1209:70b1 -D build/u2f.bin ``` -Then -``` -make -``` +### Readout protection + +Make sure to enable readout protection if you are going to use Tomu as 2FA for +your accounts. Build firmware with `ENFORCE_DEBUG_LOCK=1`: -will produce firmware file `build/u2f.elf`. +``` sh +make clean +make ENFORCE_DEBUG_LOCK=1 +``` -### Flashing +### Injecting private key -#### Using ST-LINK/V2 and OpenOCD +Firmware generates EC private key on its first boot and erases it when it +enters the bootloader. You may want to backup your private key and make it +survive firmware upgrade. To achieve this, generate the key on your host machine +and inject it into the firmware binary. -Start OpenOCD: +Generate your private key: ``` sh -openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg +openssl ecparam -name prime256v1 -genkey -noout -outform der -out key.der ``` -On other terminal run: +You may want to encrypt your `key.der` and back it up. -``` sh -telnet localhost 4444 -> reset halt -> stm32f1x unlock 0 -> reset halt -> flash write_image erase build/u2f.elf -> stm32f1x lock 0 -> reset halt -> shutdown -``` +Check device's authentication counter if you are going to perform the firmware +upgrade. You can see it in Yubikey demo site output. For the new device, you can +skip `ctr` parameter all together or set it to 1. Let's say the current counter +value is 1000. -Do not flash two devices with the same binary. Currently all certificates are -built into that binary. Before flashing new device run: +Use this command to patch firmware binary: ``` sh -make certclean -make +./inject_key.py --key key.der --ctr 1001 ``` + ## License -Great thanks to Niibe Yutaka author of Chopstx and Gnuk. +This project is using code components of Chopstx and Gnuk written by Niibe Yutaka. -Copyright © 2017 Sergei Glushchenko +Copyright © 2017, 2018 Sergei Glushchenko This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/u2f/cert/dump-der.py b/u2f/cert/dump-der.py index 58cb79e..9b1d78b 100644 --- a/u2f/cert/dump-der.py +++ b/u2f/cert/dump-der.py @@ -9,9 +9,7 @@ def pk_to_c_array(name, pk_der): pk_native = pk['private_key'].native # translate to hex string - pk_hex = format(pk_native, 'x') - # pad with zeros to 32 bytes - pk_hex = ("0" * (64 - len(pk_hex))) + pk_hex + pk_hex = format(pk_native, '064x') # split by pairs of characters hex_bytes = ["0x" + pk_hex[i:i + 2] for i in range(0, len(pk_hex), 2)] diff --git a/u2f/inject_key.py b/u2f/inject_key.py new file mode 100755 index 0000000..80a0c61 --- /dev/null +++ b/u2f/inject_key.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +# +# Use this script to inject your own private key and authentication counter +# into U2F binary. Might be useful if you want keys to survive firmware updates. +# +# Example: +# +# Generate EC private key with openssl: +# > openssl ecparam -name prime256v1 -genkey -noout -outform der > key.der +# +# Inject generated key into u2f.bin and set auth counter to 100: +# > python3 inject_key.py --key key.der --ctr 100 +# + +from __future__ import print_function +from asn1crypto.keys import ECPrivateKey +import hashlib +import argparse +import sys +import struct +import os +import tempfile +import subprocess + +parser = argparse.ArgumentParser() +parser.add_argument("--elf", default="build/u2f.elf", + help=".elf file to inject keys into") +parser.add_argument("--key", help="EC private key in DER format") +parser.add_argument("--ctr", default=1, type=int, help="value of auth counter") +args = parser.parse_args() + +# load and parse private key +if args.key: + with open(args.key, "rb") as f: + der = f.read() +else: + stdin = sys.stdin.buffer if hasattr(sys.stdin, "buffer") else sys.stdin + der = stdin.read() +key = ECPrivateKey.load(der) + +# convert key into raw bytes and calculate it's sha256 +key_bytes = bytearray.fromhex(format(key["private_key"].native, '064x')) +key_hash = hashlib.sha256(key_bytes).digest() + +# fill authentication counter +ctr_bytes = struct.pack(" #include "board.h" #include "sys.h" +#include "toboot.h" #include -#define SYSRESETREQ 0x04 - -/* Perform reset (common for all Cortex-M* processors?) */ -static void -nvic_system_reset (void) -{ - SCB->AIRCR = (0x05FA0000 | (SCB->AIRCR & 0x70) | SYSRESETREQ); - asm volatile ("dsb"); - for (;;); -} +static struct toboot_configuration +__attribute__ ((used, section(".toboot.config"))) +toboot_config = { + .align = 0, + .magic = TOBOOT_V2_MAGIC, + .reserved_gen = 0, + .start = 16, + .config = TOBOOT_CONFIG_FLAG_AUTORUN, + .lock_entry = 0, + .erase_mask_lo = 0x00000000, + .erase_mask_hi = 0xc0000000, + .reserved_hash = 0 +}; #define LOCKBITS_BASE (0x0FE04000UL) /* Lock-bits page base address */ #define DEBUG_LOCK_WORD (LOCKBITS_BASE + (127 * 4)) @@ -63,69 +67,6 @@ debug_lock_maybe (void) } #endif -static void -busy_wait (void) -{ - int i; - for (i = 0; i < 20000; i++) - asm ("nop"); -} - -extern uint32_t _device_key_base; -extern uint32_t _auth_ctr_base; - -uint32_t *boot_vectors = 0x0; - -/* Erase app secrets and enter bootloader if outer PADs (PC1 and PE12) -are shorted. */ -static void -erase_sercets_maybe (void) -{ - int in[4]; - int i; - - /* Setup PC1 as push-pull output */ - GPIO->P[2].MODEL &= ~0xF0UL; - GPIO->P[2].MODEL |= 0x00000004UL << 4; - - /* Setup PE12 as input with pull-down */ - GPIO->P[4].MODEH &= ~0xF0000UL; - GPIO->P[4].MODEH |= 0x00000002UL << 16; - - busy_wait (); - - /* toggle PC1 output couple of times to see if PE12 input changes - accordingly */ - GPIO->P[2].DOUTSET = 2; - - for (i = 0; i < 4; i++) - { - busy_wait (); - in[i] = GPIO->P[4].DIN & (1 << 12); - GPIO->P[2].DOUTTGL = 2; - } - - GPIO->P[2].DOUTTGL = 2; - - if (in[0] && !in[1] && in[2] && !in[3]) - { - /* erase device key and auth counter */ - flash_erase_page ((uintptr_t) &_device_key_base); - flash_erase_page ((uintptr_t) &_auth_ctr_base); - - /* erase first app page to make bootloader enter DFU mode */ - flash_erase_page ((uintptr_t) 0x4000); - - /* reset and boot bootloader */ - nvic_system_reset (); - } - - GPIO->P[2].MODEL = 0; - GPIO->P[2].MODEH = 0; - GPIO->P[4].MODEL = 0; - GPIO->P[4].MODEH = 0; -} - /* Preform platform-specific actions */ void platform_init (void) @@ -134,6 +75,4 @@ platform_init (void) #if defined(ENFORCE_DEBUG_LOCK) debug_lock_maybe (); #endif - - erase_sercets_maybe (); } diff --git a/u2f/toboot.h b/u2f/toboot.h new file mode 100644 index 0000000..3a87127 --- /dev/null +++ b/u2f/toboot.h @@ -0,0 +1,125 @@ +#ifndef TOBOOT_API_H_ +#define TOBOOT_API_H_ + +#include + +/// Store this configuration struct at offset 0x94 from the start +/// of your binary image. +/// You may set all RESERVED values to 0. as they will be calculated +/// when the program is written to flash. +struct toboot_configuration { + /// Our LD script using ALIGN(8). + uint32_t align; + + /// Set to 0x907070b2 to indicate a valid configuration header. + uint32_t magic; + + /// Reserved value. Used as a generational counter. Toboot will + /// overwrite this value with a monotonically-increasing counter + /// every time a new image is burned. This is used to determine + /// which image is the newest. + uint16_t reserved_gen; + + /// The starting page for your program in 1024-byte blocks. + /// Toboot itself sets this to 0. If this is nonzero, then it + /// must be located after the Toboot image. Toboot is currently + /// under 5 kilobytes, so make sure this value is at least 6. + uint8_t start; + + /// Configuration bitmask. See below for values. + uint8_t config; + + /// Set to 0x18349420 to prevent the user from entering Toboot manually. + /// Use this value with caution, as it can be used to lockout a Tomu. + uint32_t lock_entry; + + /// A bitmask of sectors to erase when updating the program. Each "1" + /// causes that sector to be erased, unless it's Toboot itself. Bit values + /// determine flash blocks 0-31. + uint32_t erase_mask_lo; + + /// A bitmask of sectors to erase when updating the program. Each "1" + /// causes that sector to e erased. Use these two values to e.g. force + /// private keys to be erased when updating. Bit values determine flash + /// blocks 32-63. + uint32_t erase_mask_hi; + + /// A hash of the entire header, minus this entry. Toboot calculates + /// this when it programs the first block. A Toboot configuration + /// header MUST have a valid hash in order to be considered valid. + uint32_t reserved_hash; +} __attribute__((packed)); + +/// Toboot V1.0 leaves IRQs enabled, mimicking the behavior of +/// AN0042. Toboot V2.0 makes this configurable by adding a +/// bit to the configuration area. +#define TOBOOT_CONFIG_FLAG_ENABLE_IRQ_MASK 0x01 +#define TOBOOT_CONFIG_FLAG_ENABLE_IRQ_SHIFT 0 +#define TOBOOT_CONFIG_FLAG_ENABLE_IRQ (1 << 0) +#define TOBOOT_CONFIG_FLAG_DISABLE_IRQ (0 << 0) + +/// When running a normal program, you won't want Toboot to run. +/// However, when developing new software it is handy to have +/// Toboot run at poweron. Set this flag to enter Toboot whenever +/// the system has powered on for the first time. +#define TOBOOT_CONFIG_FLAG_AUTORUN_MASK 0x02 +#define TOBOOT_CONFIG_FLAG_AUTORUN_SHIFT 1 +#define TOBOOT_CONFIG_FLAG_AUTORUN (1 << 1) + +/// When we create a fake header, this flag will be set. Otherwise, +/// leave the flag cleared. +#define TOBOOT_CONFIG_FAKE_MASK 0x04 +#define TOBOOT_CONFIG_FAKE_SHIFT 2 +#define TOBOOT_CONFIG_FAKE (1 << 2) + +/// Various magic values describing Toboot configuration headers. +#define TOBOOT_V1_MAGIC 0x70B0 +#define TOBOOT_V1_MAGIC_MASK 0x0000ffff +#define TOBOOT_V2_MAGIC 0x907070b2 +#define TOBOOT_V2_MAGIC_MASK 0xffffffff + +/// This value is used to prevent manual entry into Toboot. +#define TOBOOT_LOCKOUT_MAGIC 0x18349420 + +/// The seed value for the hash of Toboot's configuration header +#define TOBOOT_HASH_SEED 0x037a5cf1 + +/// This is the runtime part that lives at the start of RAM. +/// Running programs can use this to force entry into Toboot +/// during the next reboot. +struct toboot_runtime { + /// Set this to 0x74624346 and reboot to enter bootloader, + /// even if LOCKOUT is enabled. + uint32_t magic; + + /// Set this to 0 when your program starts. + uint8_t boot_count; + + /// The bootloader should set this to 0x23 for Tomu. + uint8_t board_model; + + /// Unused. + uint16_t reserved; +}; + +/// Set runtime.magic to this value and reboot to force +/// entry into Toboot. +#define TOBOOT_FORCE_ENTRY_MAGIC 0x74624346 + +/// Use this macro to define a Toboot V2 configuration. If unspecified, +/// your program will default to a legacy configuration, and will not have +/// access to features such as autoboot. +#define TOBOOT_CONFIGURATION(cfg) \ + __attribute__ ((used, section(".vectors"))) \ + const struct toboot_configuration __toboot_configuration = { \ + .magic = TOBOOT_V2_MAGIC, \ + .reserved_gen = 0, \ + .start = 16, \ + .config = cfg, \ + .lock_entry = 0, \ + .erase_mask_lo = 0, \ + .erase_mask_hi = 0, \ + .reserved_hash = 0, \ +} + +#endif /* TOBOOT_API_H_ */ diff --git a/u2f/u2f-apdu.c b/u2f/u2f-apdu.c index 98b79b0..bd2dd1d 100644 --- a/u2f/u2f-apdu.c +++ b/u2f/u2f-apdu.c @@ -161,11 +161,10 @@ struct device_key uint8_t resrved[1024 - U2F_PRIV_K_SIZE - HASH_RES_SIZE]; }; -extern struct device_key _device_key_base; -const struct device_key *device_key = (struct device_key *) &_device_key_base; +struct device_key __attribute__ ((section(".device.key"))) device_key = { 0 }; -extern uint32_t _auth_ctr_base; -static uint32_t *ctr_addr = &_auth_ctr_base; +uint32_t __attribute__ ((section(".auth.ctr"))) auth_ctr[256] = { 0 }; +static uint32_t *ctr_addr = &(auth_ctr[0]); static void device_key_gen (void) @@ -175,10 +174,10 @@ device_key_gen (void) sha256_context ctx; sha256_start (&ctx); - sha256_update (&ctx, device_key->key, U2F_PRIV_K_SIZE); + sha256_update (&ctx, device_key.key, U2F_PRIV_K_SIZE); sha256_finish (&ctx, key_hash); - if (memcmp (key_hash, device_key->key_hash, HASH_RES_SIZE) == 0) + if (memcmp (key_hash, device_key.key_hash, HASH_RES_SIZE) == 0) return; /* new device key needs to be generated */ @@ -189,9 +188,9 @@ device_key_gen (void) sha256_finish (&ctx, key_hash); /* write device key to flash */ - flash_erase_page ((uintptr_t) device_key); - flash_write ((uintptr_t) device_key->key, key, U2F_PRIV_K_SIZE); - flash_write ((uintptr_t) device_key->key_hash, key_hash, HASH_RES_SIZE); + flash_erase_page ((uintptr_t) &device_key); + flash_write ((uintptr_t) device_key.key, key, U2F_PRIV_K_SIZE); + flash_write ((uintptr_t) device_key.key_hash, key_hash, HASH_RES_SIZE); /* erase auth counter */ flash_erase_page ((uintptr_t) ctr_addr); @@ -203,10 +202,10 @@ new_private_key (uint8_t *app_id, uint8_t *nonce, uint8_t *private_key) { hmac_sha256_context ctx; - hmac_sha256_init (&ctx, device_key->key); + hmac_sha256_init (&ctx, device_key.key); hmac_sha256_update (&ctx, app_id, U2F_APPID_SIZE); hmac_sha256_update (&ctx, nonce, U2F_NONCE_SIZE); - hmac_sha256_finish (&ctx, device_key->key, private_key); + hmac_sha256_finish (&ctx, device_key.key, private_key); } static void @@ -215,10 +214,10 @@ make_key_handle (uint8_t *private_key, uint8_t *app_id, { hmac_sha256_context ctx; - hmac_sha256_init (&ctx, device_key->key); + hmac_sha256_init (&ctx, device_key.key); hmac_sha256_update (&ctx, private_key, U2F_PRIV_K_SIZE); hmac_sha256_update (&ctx, app_id, U2F_APPID_SIZE); - hmac_sha256_finish (&ctx, device_key->key, key_handle); + hmac_sha256_finish (&ctx, device_key.key, key_handle); memcpy (key_handle + HASH_RES_SIZE, nonce, U2F_NONCE_SIZE); } @@ -233,15 +232,15 @@ recover_private_key (uint8_t *app_id, uint8_t *key_handle, if (key_handle_len != U2F_KH_SIZE) return -1; - hmac_sha256_init (&ctx, device_key->key); + hmac_sha256_init (&ctx, device_key.key); hmac_sha256_update (&ctx, app_id, U2F_APPID_SIZE); hmac_sha256_update (&ctx, key_handle + HASH_RES_SIZE, U2F_NONCE_SIZE); - hmac_sha256_finish (&ctx, device_key->key, private_key); + hmac_sha256_finish (&ctx, device_key.key, private_key); - hmac_sha256_init (&ctx, device_key->key); + hmac_sha256_init (&ctx, device_key.key); hmac_sha256_update (&ctx, private_key, U2F_PRIV_K_SIZE); hmac_sha256_update (&ctx, app_id, U2F_APPID_SIZE); - hmac_sha256_finish (&ctx, device_key->key, control_mac); + hmac_sha256_finish (&ctx, device_key.key, control_mac); return memcmp(control_mac, key_handle, HASH_RES_SIZE); } @@ -359,12 +358,12 @@ u2f_read_ctr (void) while (*ctr_addr != 0xffffffff) { - if (ctr_addr - &_auth_ctr_base == page_size) + if (ctr_addr - &(auth_ctr[0]) == page_size) break; ctr_addr++; } - if (ctr_addr == &_auth_ctr_base) + if (ctr_addr == &(auth_ctr[0])) return 0; return ctr_addr[-1]; @@ -377,10 +376,10 @@ u2f_write_ctr (uint32_t val) while (*ctr_addr != 0xffffffff) { - if (ctr_addr - &_auth_ctr_base == page_size) + if (ctr_addr - &(auth_ctr[0]) == page_size) { - flash_erase_page ((uintptr_t) &_auth_ctr_base); - ctr_addr = &_auth_ctr_base; + flash_erase_page ((uintptr_t) &(auth_ctr[0])); + ctr_addr = &(auth_ctr[0]); break; } ctr_addr++; diff --git a/u2f/u2f.ld b/u2f/u2f.ld index f4a77be..9354444 100644 --- a/u2f/u2f.ld +++ b/u2f/u2f.ld @@ -12,7 +12,7 @@ __process6_stack_size__ = 0x0200; /* pbt / csn */ MEMORY { - flash : org = 0x00004000, len = 47k + flash : org = 0x00004000, len = 46k ram : org = 0x20000000, len = 8k flash1 : org = 0x00004000+0xb800, len = 2k } @@ -31,6 +31,8 @@ SECTIONS { . = ALIGN(16); KEEP(*(.first_page.first_words)) + . = ORIGIN(flash) + 0x90; + KEEP(*(.toboot.config)) KEEP(*(.sys.vectors)) *(.text.startup.*) build/sys-*.o(.text) @@ -157,9 +159,11 @@ SECTIONS { . = ALIGN (1024); _device_key_base = .; - . += 1024; + *(.device.key); + . = ALIGN (1024); _auth_ctr_base = .; - . += 1024; + *(.auth.ctr); + . = ALIGN (1024); } > flash1 PROVIDE(end = .);