/* * Copyright (C) 2018 SoloKeys, Inc. * * This file is part of Solo. * * Solo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Solo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Solo. If not, see * * This code is available under licenses for commercial use. * Please contact SoloKeys for more information. */ /* * device.c * * Created on: Jun 27, 2018 * Author: conor */ #include #include #include #include "em_chip.h" #include "em_gpio.h" #include "em_usart.h" #include "em_adc.h" #include "em_cmu.h" #include "em_msc.h" #include "em_i2c.h" #include "em_timer.h" #include "InitDevice.h" #include "cbor.h" #include "log.h" #include "ctaphid.h" #include "util.h" #include "app.h" #include "uECC.h" #include "crypto.h" #include "nfc.h" #ifdef USING_DEV_BOARD #define MSG_AVAIL_PIN gpioPortC,9 #define RDY_PIN gpioPortC,10 #define RW_PIN gpioPortD,11 #define RESET_PIN gpioPortB,13 #define LED_RED_PIN gpioPortF,4 #define LED_GREEN_PIN gpioPortF,5 #else #define MSG_AVAIL_PIN gpioPortA,1 #define RDY_PIN gpioPortA,0 #define RW_PIN gpioPortD,15 #define RESET_PIN gpioPortB,15 #define LED_RED_PIN gpioPortD,10 #define LED_GREEN_PIN gpioPortD,14 #define LED_BLUE_PIN gpioPortD,9 #define BUTTON_PIN gpioPortD,13 #define RED_CHANNEL 0 #define GREEN_CHANNEL 2 #define BLUE_CHANNEL 1 #endif #define PAGE_SIZE 2048 #define PAGES 64 #define COUNTER_PAGE (PAGES - 3) #define STATE1_PAGE (PAGES - 2) #define STATE2_PAGE (PAGES - 1) #define APPLICATION_START_ADDR 0x4000 #define APPLICATION_START_PAGE (0x4000/PAGE_SIZE) #define APPLICATION_END_ADDR (PAGE_SIZE*(PAGES - 3)-4) // NOT included in application #define APPLICATION_END_PAGE ((PAGES - 3)) // 125 is NOT included in application #define AUTH_WORD_ADDR (PAGE_SIZE*(PAGES - 3)-4) static void init_atomic_counter() { int offset = 0; uint32_t count; uint32_t one = 1; uint32_t * ptr = PAGE_SIZE * COUNTER_PAGE; for (offset = 0; offset < PAGE_SIZE/4; offset += 1) { count = *(ptr+offset); if (count != 0xffffffff) { return; } } MSC_WriteWordFast(ptr,&one,4); } uint32_t ctap_atomic_count(int sel) { int offset = 0; uint32_t count; uint32_t zero = 0; uint32_t * ptr = PAGE_SIZE * COUNTER_PAGE; if (sel != 0) { printf2(TAG_ERR,"counter2 not imple\n"); exit(1); } for (offset = 0; offset < PAGE_SIZE/4; offset += 1) // wear-level the flash { count = *(ptr+offset); if (count != 0) { count++; offset++; if (offset == PAGE_SIZE/4) { offset = 0; MSC_ErasePage(ptr); /*printf("RESET page counter\n");*/ } else { MSC_WriteWordFast(ptr+offset-1,&zero,4); } MSC_WriteWordFast(ptr+offset,&count,4); break; } } return count; } static uint32_t _color; uint32_t get_RBG() { return _color; } void RGB(uint32_t hex) { uint16_t r = 256 - ((hex & 0xff0000) >> 16); uint16_t g = 256 - ((hex & 0xff00) >> 8); uint16_t b = 256 - ((hex & 0xff) >> 0); TIMER_CompareBufSet(TIMER0, GREEN_CHANNEL, g); // green TIMER_CompareBufSet(TIMER0, RED_CHANNEL, r); // red TIMER_CompareBufSet(TIMER0, BLUE_CHANNEL, b); // blue _color = hex; } #define IS_BUTTON_PRESSED() (GPIO_PinInGet(BUTTON_PIN) == 0) // Verify the user // return 1 if user is verified, 0 if not int ctap_user_verification(uint8_t arg) { return 1; } // Test for user presence // Return 1 for user is present, 0 user not present int ctap_user_presence_test() { #ifdef SKIP_BUTTON_CHECK return 1; #endif uint32_t t1 = millis(); RGB(0x304010); #ifdef USE_BUTTON_DELAY delay(3000); RGB(0x001040); delay(50); return 1; #endif while (IS_BUTTON_PRESSED()) { if (t1 + 5000 < millis()) { printf1(TAG_GEN,"Button not pressed\n"); return 0; } } t1 = millis(); do { if (t1 + 5000 < millis()) { return 0; } if (! IS_BUTTON_PRESSED()) continue; delay(1); } while (! IS_BUTTON_PRESSED()); RGB(0x001040); delay(50); return 1; } // Must be implemented by application // data is HID_MESSAGE_SIZE long in bytes #ifndef TEST_POWER void ctaphid_write_block(uint8_t * data) { printf1(TAG_DUMP,"<< "); dump_hex1(TAG_DUMP, data, HID_MESSAGE_SIZE); usbhid_send(data); } #endif #ifdef IS_BOOTLOADER // two different colors between bootloader and app void heartbeat() { static int state = 0; static uint32_t val = (LED_INIT_VALUE >> 8) & 0xff; int but = IS_BUTTON_PRESSED(); if (state) { val--; } else { val++; } if (val > 30 || val < 1) { state = !state; } // if (but) RGB(val * 2); // else RGB((val << 16) | (val*2 << 8)); } #else void heartbeat() { static int state = 0; static uint32_t val = (LED_INIT_VALUE >> 8) & 0xff; int but = IS_BUTTON_PRESSED(); #if 0 RGB(0x100000); // bright ass light return; #endif if (state) { val--; } else { val++; } if (val >120/3 || val < 1) { state = !state; } if (but) RGB(val * 2); else RGB(val*3 | ((val*3) << 8) | (val << 16) ); // else RGB((val*3) << 8); } #endif uint32_t millis() { return CRYOTIMER->CNT; } void usbhid_init() { } static int msgs_to_recv = 0; static void wait_for_efm8_ready() { // Wait for efm8 to be ready while (GPIO_PinInGet(RDY_PIN) == 0) ; } static void wait_for_efm8_busy() { // Wait for efm8 to be ready while (GPIO_PinInGet(RDY_PIN) != 0) ; } #ifndef TEST_POWER int usbhid_recv(uint8_t * msg) { int i; if (GPIO_PinInGet(MSG_AVAIL_PIN) == 0) { GPIO_PinOutClear(RW_PIN); // Drive low to indicate READ wait_for_efm8_ready(); for (i = 0; i < 64; i++) { msg[i] = USART_SpiTransfer(USART1, 'A'); // delay(1); } GPIO_PinOutSet(RW_PIN); wait_for_efm8_busy(); // // msgs_to_recv--; // printf(">> "); // dump_hex(msg,64); return 64; } return 0; } #endif void usbhid_send(uint8_t * msg) { int i; // uint32_t t1 = millis(); USART_SpiTransfer(USART1, *msg++); // Send 1 byte wait_for_efm8_ready(); for (i = 1; i < HID_MESSAGE_SIZE; i++) { USART_SpiTransfer(USART1, *msg++); } wait_for_efm8_busy(); delay(10); // uint32_t t2 = millis(); // printf("wait time: %u\n", (uint32_t)(t2-t1)); } void usbhid_close() { } void main_loop_delay() { } void delay(int ms) { int t1 = millis(); while(millis() - t1 < ms) ; } void GPIO_ODD_IRQHandler() { uint32_t flag = GPIO->IF; GPIO->IFC = flag; if (flag & (1<<9)) { // printf("pin 9 interrupt\r\n"); msgs_to_recv++; } else { printf1(TAG_ERR,"wrong pin int %x\r\n",flag); } } void init_adc() { /* Enable ADC Clock */ CMU_ClockEnable(cmuClock_ADC0, true); ADC_Init_TypeDef init = ADC_INIT_DEFAULT; ADC_InitSingle_TypeDef singleInit = ADC_INITSINGLE_DEFAULT; /* Initialize the ADC with the required values */ init.timebase = ADC_TimebaseCalc(0); init.prescale = ADC_PrescaleCalc(7000000, 0); ADC_Init(ADC0, &init); /* Initialize for single conversion specific to RNG */ singleInit.reference = adcRefVEntropy; singleInit.diff = true; singleInit.posSel = adcPosSelVSS; singleInit.negSel = adcNegSelVSS; ADC_InitSingle(ADC0, &singleInit); /* Set VINATT to maximum value and clear FIFO */ ADC0->SINGLECTRLX |= _ADC_SINGLECTRLX_VINATT_MASK; ADC0->SINGLEFIFOCLEAR = ADC_SINGLEFIFOCLEAR_SINGLEFIFOCLEAR; } void authenticator_read_state(AuthenticatorState * state) { uint32_t * ptr = PAGE_SIZE*STATE1_PAGE; memmove(state,ptr,sizeof(AuthenticatorState)); } void authenticator_read_backup_state(AuthenticatorState * state ) { uint32_t * ptr = PAGE_SIZE*STATE2_PAGE; memmove(state,ptr,sizeof(AuthenticatorState)); } void authenticator_write_state(AuthenticatorState * state, int backup) { uint32_t * ptr; if (! backup) { ptr = PAGE_SIZE*STATE1_PAGE; MSC_ErasePage(ptr); // for (i = 0; i < sizeof(AuthenticatorState)/4; i++ ) MSC_WriteWordFast(ptr,state,sizeof(AuthenticatorState) + (sizeof(AuthenticatorState)%4)); } else { ptr = PAGE_SIZE*STATE2_PAGE; MSC_ErasePage(ptr); // for (i = 0; i < sizeof(AuthenticatorState)/4; i++ ) MSC_WriteWordFast(ptr,state,sizeof(AuthenticatorState) + (sizeof(AuthenticatorState)%4)); } } // Return 1 yes backup is init'd, else 0 int authenticator_is_backup_initialized() { uint8_t header[16]; uint32_t * ptr = PAGE_SIZE*STATE2_PAGE; memmove(header,ptr,16); AuthenticatorState * state = (AuthenticatorState*)header; return state->is_initialized == INITIALIZED_MARKER; } uint8_t adc_rng(void); void reset_efm8() { // Reset EFM8 GPIO_PinOutClear(RESET_PIN); delay(2); GPIO_PinOutSet(RESET_PIN); } void bootloader_init(void) { /* Chip errata */ // Reset EFM8 GPIO_PinModeSet(RESET_PIN, gpioModePushPull, 1); // status LEDS GPIO_PinModeSet(LED_RED_PIN, gpioModePushPull, 1); // red GPIO_PinModeSet(LED_GREEN_PIN, gpioModePushPull, 1); // green GPIO_PinModeSet(LED_BLUE_PIN, gpioModePushPull, 1); // blue // EFM8 RDY/BUSY GPIO_PinModeSet(RDY_PIN, gpioModeInput, 0); // EFM8 MSG Available GPIO_PinModeSet(MSG_AVAIL_PIN, gpioModeInput, 0); // SPI R/w Indicator GPIO_PinModeSet(RW_PIN, gpioModePushPull, 1); printing_init(); MSC_Init(); } void device_init(void) { /* Chip errata */ CHIP_Init(); enter_DefaultMode_from_RESET(); // status LEDS GPIO_PinModeSet(LED_RED_PIN, gpioModePushPull, 1); // red GPIO_PinModeSet(LED_GREEN_PIN, gpioModePushPull, 1); // green GPIO_PinModeSet(LED_BLUE_PIN, gpioModePushPull, 1); // blue // EFM8 RDY/BUSY GPIO_PinModeSet(RDY_PIN, gpioModeInput, 0); // EFM8 MSG Available GPIO_PinModeSet(MSG_AVAIL_PIN, gpioModeInput, 0); // SPI R/w Indicator GPIO_PinModeSet(RW_PIN, gpioModePushPull, 1); // Reset EFM8 GPIO_PinModeSet(RESET_PIN, gpioModePushPull, 1); TIMER_TopSet(TIMER0, 255); RGB(LED_INIT_VALUE); printing_init(); init_adc(); MSC_Init(); init_atomic_counter(); if (sizeof(AuthenticatorState) > PAGE_SIZE) { printf2(TAG_ERR, "not enough room in page\n"); exit(1); } CborEncoder test; uint8_t buf[64]; cbor_encoder_init(&test, buf, 20, 0); reset_efm8(); printf1(TAG_GEN,"Device init\r\n"); int i=0; for (i = 0; i < sizeof(buf); i++) { buf[i] = adc_rng(); } } #ifdef IS_BOOTLOADER typedef enum { BootWrite = 0x40, BootDone = 0x41, BootCheck = 0x42, BootErase = 0x43, } WalletOperation; typedef struct { uint8_t op; uint8_t addr[3]; uint8_t tag[4]; uint8_t len; uint8_t payload[255 - 9]; } __attribute__((packed)) BootloaderReq; //#define APPLICATION_START_ADDR 0x8000 //#define APPLICATION_START_PAGE (0x8000/PAGE_SIZE) //#define APPLICATION_END_ADDR (PAGE_SIZE*125-4) // NOT included in application static void erase_application() { int page; uint32_t * ptrpage; for(page = APPLICATION_START_PAGE; page < APPLICATION_END_PAGE; page++) { ptrpage = page * PAGE_SIZE; MSC_ErasePage(ptrpage); } } static void authorize_application() { uint32_t zero = 0; uint32_t * ptr; ptr = AUTH_WORD_ADDR; MSC_WriteWordFast(ptr,&zero, 4); } int bootloader_bridge(uint8_t klen, uint8_t * keyh) { static int has_erased = 0; BootloaderReq * req = (BootloaderReq * )keyh; uint8_t payload[256]; uint8_t hash[32]; uint8_t * pubkey = (uint8_t*)"\x57\xe6\x80\x39\x56\x46\x2f\x0c\x95\xac\x72\x71\xf0\xbc\xe8\x2d\x67\xd0\x59\x29\x2e\x15\x22\x89\x6a\xbd\x3f\x7f\x27\xf3\xc0\xc6\xe2\xd7\x7d\x8a\x9f\xcc\x53\xc5\x91\xb2\x0c\x9c\x3b\x4e\xa4\x87\x31\x67\xb4\xa9\x4b\x0e\x8d\x06\x67\xd8\xc5\xef\x2c\x50\x4a\x55"; const struct uECC_Curve_t * curve = NULL; /*printf("bootloader_bridge\n");*/ if (req->len > 255-9) { return CTAP1_ERR_INVALID_LENGTH; } memset(payload, 0xff, sizeof(payload)); memmove(payload, req->payload, req->len); uint32_t addr = (*((uint32_t*)req->addr)) & 0xffffff; uint32_t * ptr = addr; switch(req->op){ case BootWrite: /*printf("BootWrite 0x%08x\n", addr);*/ if (ptr < APPLICATION_START_ADDR || ptr >= APPLICATION_END_ADDR) { return CTAP2_ERR_NOT_ALLOWED; } if (!has_erased) { erase_application(); has_erased = 1; } if (is_authorized_to_boot()) { printf2(TAG_ERR, "Error, boot check bypassed\n"); exit(1); } MSC_WriteWordFast(ptr,payload, req->len + (req->len%4)); break; case BootDone: // printf("BootDone\n"); ptr = APPLICATION_START_ADDR; crypto_sha256_init(); crypto_sha256_update(ptr, APPLICATION_END_ADDR-APPLICATION_START_ADDR); crypto_sha256_final(hash); // printf("hash: "); dump_hex(hash, 32); // printf("sig: "); dump_hex(payload, 64); curve = uECC_secp256r1(); if (! uECC_verify(pubkey, hash, 32, payload, curve)) { return CTAP2_ERR_OPERATION_DENIED; } authorize_application(); REBOOT_FLAG = 1; break; case BootCheck: /*printf("BootCheck\n");*/ return 0; break; case BootErase: /*printf("BootErase\n");*/ erase_application(); return 0; break; default: return CTAP1_ERR_INVALID_COMMAND; } return 0; } int is_authorized_to_boot() { uint32_t * auth = AUTH_WORD_ADDR; return *auth == 0; } #endif