670 lines
15 KiB
C
670 lines
15 KiB
C
|
|
#include "device.h"
|
|
#include "usbd_def.h"
|
|
#include "stm32l4xx.h"
|
|
#include "stm32l4xx_ll_gpio.h"
|
|
#include "stm32l4xx_ll_tim.h"
|
|
#include "stm32l4xx_ll_usart.h"
|
|
#include "usbd_hid.h"
|
|
|
|
#include APP_CONFIG
|
|
#include "flash.h"
|
|
#include "rng.h"
|
|
#include "led.h"
|
|
#include "device.h"
|
|
#include "util.h"
|
|
#include "fifo.h"
|
|
#include "log.h"
|
|
#include "ctaphid.h"
|
|
#include "ctap.h"
|
|
#include "crypto.h"
|
|
#include "uECC.h"
|
|
#include "u2f.h"
|
|
|
|
|
|
#define PAGE_SIZE 2048
|
|
#define PAGES 128
|
|
// Pages 119-127 are data
|
|
#define COUNTER2_PAGE (PAGES - 4)
|
|
#define COUNTER1_PAGE (PAGES - 3)
|
|
#define STATE2_PAGE (PAGES - 2)
|
|
#define STATE1_PAGE (PAGES - 1)
|
|
|
|
#define RK_NUM_PAGES 10
|
|
#define RK_START_PAGE (PAGES - 14)
|
|
#define RK_END_PAGE (PAGES - 14 + RK_NUM_PAGES)
|
|
|
|
|
|
#define APPLICATION_START_PAGE (16)
|
|
#define APPLICATION_START_ADDR flash_addr(APPLICATION_START_PAGE)
|
|
|
|
#define APPLICATION_END_PAGE ((PAGES - 19)) // 119 is NOT included in application
|
|
#define APPLICATION_END_ADDR (flash_addr(APPLICATION_END_PAGE)-8) // NOT included in application
|
|
|
|
#define AUTH_WORD_ADDR (flash_addr(APPLICATION_END_PAGE)-8)
|
|
|
|
uint32_t __90_ms = 0;
|
|
uint32_t __device_status = 0;
|
|
uint32_t __last_update = 0;
|
|
extern PCD_HandleTypeDef hpcd;
|
|
|
|
#define IS_BUTTON_PRESSED() (0 == (LL_GPIO_ReadInputPort(SOLO_BUTTON_PORT) & SOLO_BUTTON_PIN))
|
|
|
|
// Timer6 overflow handler. happens every ~90ms.
|
|
void TIM6_DAC_IRQHandler()
|
|
{
|
|
// timer is only 16 bits, so roll it over here
|
|
TIM6->SR = 0;
|
|
__90_ms += 1;
|
|
if ((millis() - __last_update) > 8)
|
|
{
|
|
if (__device_status != CTAPHID_STATUS_IDLE)
|
|
{
|
|
ctaphid_update_status(__device_status);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Global USB interrupt handler
|
|
void USB_IRQHandler(void)
|
|
{
|
|
HAL_PCD_IRQHandler(&hpcd);
|
|
}
|
|
|
|
uint32_t millis()
|
|
{
|
|
return (((uint32_t)TIM6->CNT) + (__90_ms * 90));
|
|
}
|
|
|
|
void device_set_status(int status)
|
|
{
|
|
__disable_irq();
|
|
__last_update = millis();
|
|
__enable_irq();
|
|
|
|
if (status != CTAPHID_STATUS_IDLE && __device_status != status)
|
|
{
|
|
ctaphid_update_status(status);
|
|
}
|
|
__device_status = status;
|
|
}
|
|
|
|
int device_is_button_pressed()
|
|
{
|
|
return IS_BUTTON_PRESSED();
|
|
}
|
|
|
|
void delay(uint32_t ms)
|
|
{
|
|
uint32_t time = millis();
|
|
while ((millis() - time) < ms)
|
|
;
|
|
}
|
|
void device_reboot()
|
|
{
|
|
|
|
}
|
|
void device_init()
|
|
{
|
|
hw_init();
|
|
LL_GPIO_SetPinMode(SOLO_BUTTON_PORT,SOLO_BUTTON_PIN,LL_GPIO_MODE_INPUT);
|
|
LL_GPIO_SetPinPull(SOLO_BUTTON_PORT,SOLO_BUTTON_PIN,LL_GPIO_PULL_UP);
|
|
|
|
#if BOOT_TO_DFU
|
|
flash_option_bytes_init(1);
|
|
#else
|
|
flash_option_bytes_init(0);
|
|
#endif
|
|
|
|
printf1(TAG_GEN,"hello solo\r\n");
|
|
}
|
|
|
|
void usb_init(void);
|
|
void usbhid_init()
|
|
{
|
|
usb_init();
|
|
}
|
|
|
|
int usbhid_recv(uint8_t * msg)
|
|
{
|
|
if (fifo_hidmsg_size())
|
|
{
|
|
fifo_hidmsg_take(msg);
|
|
printf1(TAG_DUMP2,">> ");
|
|
dump_hex1(TAG_DUMP2,msg, HID_PACKET_SIZE);
|
|
return HID_PACKET_SIZE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void usbhid_send(uint8_t * msg)
|
|
{
|
|
|
|
printf1(TAG_DUMP2,"<< ");
|
|
dump_hex1(TAG_DUMP2, msg, HID_PACKET_SIZE);
|
|
while (PCD_GET_EP_TX_STATUS(USB, HID_EPIN_ADDR & 0x0f) == USB_EP_TX_VALID)
|
|
;
|
|
USBD_LL_Transmit(&Solo_USBD_Device, HID_EPIN_ADDR, msg, HID_PACKET_SIZE);
|
|
|
|
|
|
}
|
|
|
|
void ctaphid_write_block(uint8_t * data)
|
|
{
|
|
usbhid_send(data);
|
|
}
|
|
|
|
|
|
void usbhid_close()
|
|
{
|
|
|
|
}
|
|
|
|
void main_loop_delay()
|
|
{
|
|
|
|
}
|
|
|
|
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) led_rgb(val * 2);
|
|
else
|
|
led_rgb((val << 16) | (val*2 << 8));
|
|
}
|
|
|
|
void authenticator_read_state(AuthenticatorState * a)
|
|
{
|
|
uint32_t * ptr = (uint32_t *)flash_addr(STATE1_PAGE);
|
|
memmove(a,ptr,sizeof(AuthenticatorState));
|
|
}
|
|
|
|
void authenticator_read_backup_state(AuthenticatorState * a)
|
|
{
|
|
uint32_t * ptr = (uint32_t *)flash_addr(STATE2_PAGE);
|
|
memmove(a,ptr,sizeof(AuthenticatorState));
|
|
}
|
|
|
|
// Return 1 yes backup is init'd, else 0
|
|
int authenticator_is_backup_initialized()
|
|
{
|
|
uint8_t header[16];
|
|
uint32_t * ptr = (uint32_t *)flash_addr(STATE2_PAGE);
|
|
memmove(header,ptr,16);
|
|
AuthenticatorState * state = (AuthenticatorState*)header;
|
|
return state->is_initialized == INITIALIZED_MARKER;
|
|
}
|
|
|
|
void authenticator_write_state(AuthenticatorState * a, int backup)
|
|
{
|
|
if (! backup)
|
|
{
|
|
flash_erase_page(STATE1_PAGE);
|
|
|
|
flash_write(flash_addr(STATE1_PAGE), (uint8_t*)a, sizeof(AuthenticatorState));
|
|
}
|
|
else
|
|
{
|
|
flash_erase_page(STATE2_PAGE);
|
|
|
|
flash_write(flash_addr(STATE2_PAGE), (uint8_t*)a, sizeof(AuthenticatorState));
|
|
}
|
|
}
|
|
|
|
uint32_t ctap_atomic_count(int sel)
|
|
{
|
|
int offset = 0;
|
|
uint32_t * ptr = (uint32_t *)flash_addr(COUNTER1_PAGE);
|
|
uint32_t erases = *(uint32_t *)flash_addr(COUNTER2_PAGE);
|
|
static uint32_t sc = 0;
|
|
if (erases == 0xffffffff)
|
|
{
|
|
erases = 1;
|
|
flash_erase_page(COUNTER2_PAGE);
|
|
flash_write(flash_addr(COUNTER2_PAGE), (uint8_t*)&erases, 4);
|
|
}
|
|
|
|
uint32_t lastc = 0;
|
|
|
|
if (sel != 0)
|
|
{
|
|
printf2(TAG_ERR,"counter2 not imple\n");
|
|
exit(1);
|
|
}
|
|
|
|
for (offset = 0; offset < PAGE_SIZE/4; offset += 2) // wear-level the flash
|
|
{
|
|
if (ptr[offset] != 0xffffffff)
|
|
{
|
|
if (ptr[offset] < lastc)
|
|
{
|
|
printf2(TAG_ERR,"Error, count went down!\r\n");
|
|
}
|
|
lastc = ptr[offset];
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!lastc) // Happens on initialization as well.
|
|
{
|
|
printf2(TAG_ERR,"warning, power interrupted during previous count. Restoring. lastc==%lu, erases=%lu, offset=%d\r\n", lastc,erases,offset);
|
|
// there are 32 counts per page
|
|
lastc = erases * 256 + 1;
|
|
flash_erase_page(COUNTER1_PAGE);
|
|
flash_write(flash_addr(COUNTER1_PAGE), (uint8_t*)&lastc, 4);
|
|
|
|
erases++;
|
|
flash_erase_page(COUNTER2_PAGE);
|
|
flash_write(flash_addr(COUNTER2_PAGE), (uint8_t*)&erases, 4);
|
|
return lastc;
|
|
}
|
|
|
|
lastc++;
|
|
|
|
if (lastc/256 > erases)
|
|
{
|
|
printf2(TAG_ERR,"warning, power interrupted, erases mark, restoring. lastc==%lu, erases=%lu\r\n", lastc,erases);
|
|
erases = lastc/256;
|
|
flash_erase_page(COUNTER2_PAGE);
|
|
flash_write(flash_addr(COUNTER2_PAGE), (uint8_t*)&erases, 4);
|
|
}
|
|
|
|
if (offset == PAGE_SIZE/4)
|
|
{
|
|
if (lastc/256 > erases)
|
|
{
|
|
printf2(TAG_ERR,"warning, power interrupted, erases mark, restoring lastc==%lu, erases=%lu\r\n", lastc,erases);
|
|
}
|
|
erases = lastc/256 + 1;
|
|
flash_erase_page(COUNTER2_PAGE);
|
|
flash_write(flash_addr(COUNTER2_PAGE), (uint8_t*)&erases, 4);
|
|
|
|
flash_erase_page(COUNTER1_PAGE);
|
|
offset = 0;
|
|
}
|
|
|
|
|
|
flash_write(flash_addr(COUNTER1_PAGE) + offset * 4, (uint8_t*)&lastc, 4);
|
|
|
|
if (lastc == sc)
|
|
{
|
|
printf1(TAG_RED,"no count detected: lastc==%lu, erases=%lu, offset=%d\r\n", lastc,erases,offset);
|
|
while(1)
|
|
;
|
|
}
|
|
|
|
sc = lastc;
|
|
|
|
return lastc;
|
|
}
|
|
|
|
|
|
void device_manage()
|
|
{
|
|
#if NON_BLOCK_PRINTING
|
|
int i = 10;
|
|
uint8_t c;
|
|
while (i--)
|
|
{
|
|
if (fifo_debug_size())
|
|
{
|
|
fifo_debug_take(&c);
|
|
while (! LL_USART_IsActiveFlag_TXE(DEBUG_UART))
|
|
;
|
|
LL_USART_TransmitData8(DEBUG_UART,c);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int handle_packets()
|
|
{
|
|
static uint8_t hidmsg[HID_PACKET_SIZE];
|
|
memset(hidmsg,0, sizeof(hidmsg));
|
|
if (usbhid_recv(hidmsg) > 0)
|
|
{
|
|
if ( ctaphid_handle_packet(hidmsg) == CTAPHID_CANCEL)
|
|
{
|
|
printf1(TAG_GREEN, "CANCEL!\r\n");
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ctap_user_presence_test()
|
|
{
|
|
int ret;
|
|
#if SKIP_BUTTON_CHECK_WITH_DELAY
|
|
int i=500;
|
|
while(i--)
|
|
{
|
|
delay(1);
|
|
ret = handle_packets();
|
|
if (ret) return ret;
|
|
}
|
|
goto done;
|
|
#elif SKIP_BUTTON_CHECK_FAST
|
|
delay(2);
|
|
ret = handle_packets();
|
|
if (ret) return ret;
|
|
goto done;
|
|
#endif
|
|
uint32_t t1 = millis();
|
|
led_rgb(0xff3520);
|
|
|
|
while (IS_BUTTON_PRESSED())
|
|
{
|
|
if (t1 + 5000 < millis())
|
|
{
|
|
printf1(TAG_GEN,"Button not pressed\n");
|
|
goto fail;
|
|
}
|
|
ret = handle_packets();
|
|
if (ret) return ret;
|
|
}
|
|
|
|
t1 = millis();
|
|
|
|
do
|
|
{
|
|
if (t1 + 5000 < millis())
|
|
{
|
|
goto fail;
|
|
}
|
|
delay(1);
|
|
ret = handle_packets();
|
|
if (ret) return ret;
|
|
}
|
|
while (! IS_BUTTON_PRESSED());
|
|
|
|
led_rgb(0x001040);
|
|
|
|
delay(50);
|
|
|
|
done:
|
|
return 1;
|
|
|
|
fail:
|
|
return 0;
|
|
}
|
|
|
|
int ctap_generate_rng(uint8_t * dst, size_t num)
|
|
{
|
|
rng_get_bytes(dst, num);
|
|
return 1;
|
|
}
|
|
|
|
|
|
int ctap_user_verification(uint8_t arg)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
void ctap_reset_rk()
|
|
{
|
|
int i;
|
|
printf1(TAG_GREEN, "resetting RK \r\n");
|
|
for(i = 0; i < RK_NUM_PAGES; i++)
|
|
{
|
|
flash_erase_page(RK_START_PAGE + i);
|
|
}
|
|
}
|
|
|
|
uint32_t ctap_rk_size()
|
|
{
|
|
return RK_NUM_PAGES * (PAGE_SIZE / sizeof(CTAP_residentKey));
|
|
}
|
|
|
|
void ctap_store_rk(int index,CTAP_residentKey * rk)
|
|
{
|
|
int page_offset = (sizeof(CTAP_residentKey) * index) / PAGE_SIZE;
|
|
uint32_t addr = flash_addr(page_offset + RK_START_PAGE) + ((sizeof(CTAP_residentKey)*index) % PAGE_SIZE);
|
|
|
|
printf1(TAG_GREEN, "storing RK %d @ %04x\r\n", index,addr);
|
|
|
|
if (page_offset < RK_NUM_PAGES)
|
|
{
|
|
flash_write(addr, (uint8_t*)rk, sizeof(CTAP_residentKey));
|
|
//dump_hex1(TAG_GREEN,rk,sizeof(CTAP_residentKey));
|
|
}
|
|
else
|
|
{
|
|
printf2(TAG_ERR,"Out of bounds reading index %d for rk\n", index);
|
|
}
|
|
}
|
|
|
|
void ctap_load_rk(int index,CTAP_residentKey * rk)
|
|
{
|
|
int page_offset = (sizeof(CTAP_residentKey) * index) / PAGE_SIZE;
|
|
uint32_t addr = flash_addr(page_offset + RK_START_PAGE) + ((sizeof(CTAP_residentKey)*index) % PAGE_SIZE);
|
|
|
|
printf1(TAG_GREEN, "reading RK %d @ %04x\r\n", index, addr);
|
|
if (page_offset < RK_NUM_PAGES)
|
|
{
|
|
uint32_t * ptr = (uint32_t *)addr;
|
|
memmove((uint8_t*)rk,ptr,sizeof(CTAP_residentKey));
|
|
//dump_hex1(TAG_GREEN,rk,sizeof(CTAP_residentKey));
|
|
}
|
|
else
|
|
{
|
|
printf2(TAG_ERR,"Out of bounds reading index %d for rk\n", index);
|
|
}
|
|
}
|
|
|
|
void ctap_overwrite_rk(int index,CTAP_residentKey * rk)
|
|
{
|
|
uint8_t tmppage[PAGE_SIZE];
|
|
int page_offset = (sizeof(CTAP_residentKey) * index) / PAGE_SIZE;
|
|
int page = page_offset + RK_START_PAGE;
|
|
|
|
printf1(TAG_GREEN, "overwriting RK %d\r\n", index);
|
|
if (page_offset < RK_NUM_PAGES)
|
|
{
|
|
memmove(tmppage, (uint8_t*)flash_addr(page), PAGE_SIZE);
|
|
|
|
memmove(tmppage + (sizeof(CTAP_residentKey) * index) % PAGE_SIZE, rk, sizeof(CTAP_residentKey));
|
|
flash_erase_page(page);
|
|
flash_write(flash_addr(page), tmppage, ((sizeof(CTAP_residentKey) * (index + 1)) % PAGE_SIZE) );
|
|
}
|
|
else
|
|
{
|
|
printf2(TAG_ERR,"Out of bounds reading index %d for rk\n", index);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void _Error_Handler(char *file, int line)
|
|
{
|
|
printf2(TAG_ERR,"Error: %s: %d\r\n", file, line);
|
|
while(1)
|
|
{
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef IS_BOOTLOADER
|
|
|
|
extern uint8_t REBOOT_FLAG;
|
|
|
|
typedef enum
|
|
{
|
|
BootWrite = 0x40,
|
|
BootDone = 0x41,
|
|
BootCheck = 0x42,
|
|
BootErase = 0x43,
|
|
BootVersion = 0x44,
|
|
} BootOperation;
|
|
|
|
|
|
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;
|
|
for(page = APPLICATION_START_PAGE; page < APPLICATION_END_PAGE; page++)
|
|
{
|
|
flash_erase_page(page);
|
|
}
|
|
}
|
|
|
|
static void authorize_application()
|
|
{
|
|
uint32_t zero = 0;
|
|
uint32_t * ptr;
|
|
ptr = (uint32_t *)AUTH_WORD_ADDR;
|
|
flash_write((uint32_t)ptr, (uint8_t *)&zero, 4);
|
|
}
|
|
int is_authorized_to_boot()
|
|
{
|
|
uint32_t * auth = (uint32_t *)AUTH_WORD_ADDR;
|
|
return *auth == 0;
|
|
}
|
|
|
|
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 version = 1;
|
|
|
|
uint8_t * pubkey = (uint8_t*)"\x85\xaa\xce\xda\xd4\xb4\xd8\x0d\xf7\x0e\xe8\x91\x6d\x69\x8e\x00\x7a\x27\x40\x76\x93\x7a\x1d\x63\xb1\xcf\xe8\x22\xdd\x9f\xbc\x43\x3e\x34\x0a\x05\x9d\x8a\x9d\x72\xdc\xc2\x4b\x56\x9c\x64\x3d\xc1\x0d\x14\x64\x69\x52\x31\xd7\x54\xa3\xb6\x69\xa7\x6f\x6b\x81\x8d";
|
|
const struct uECC_Curve_t * curve = NULL;
|
|
|
|
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) | 0x8000000;
|
|
|
|
uint32_t * ptr = (uint32_t *)addr;
|
|
|
|
switch(req->op){
|
|
case BootWrite:
|
|
printf1(TAG_BOOT, "BootWrite: %08lx\r\n",(uint32_t)ptr);
|
|
if ((uint32_t)ptr < APPLICATION_START_ADDR || (uint32_t)ptr >= APPLICATION_END_ADDR)
|
|
{
|
|
printf1(TAG_BOOT,"Bound exceeded [%08lx, %08lx]\r\n",APPLICATION_START_ADDR,APPLICATION_END_ADDR);
|
|
return CTAP2_ERR_NOT_ALLOWED;
|
|
}
|
|
|
|
if (!has_erased || is_authorized_to_boot())
|
|
{
|
|
erase_application();
|
|
has_erased = 1;
|
|
}
|
|
if (is_authorized_to_boot())
|
|
{
|
|
printf2(TAG_ERR, "Error, boot check bypassed\n");
|
|
exit(1);
|
|
}
|
|
|
|
flash_write((uint32_t)ptr,payload, req->len + (req->len%4));
|
|
break;
|
|
case BootDone:
|
|
printf1(TAG_BOOT, "BootDone: ");
|
|
dump_hex1(TAG_BOOT, payload, 32);
|
|
ptr = (uint32_t *)APPLICATION_START_ADDR;
|
|
crypto_sha256_init();
|
|
crypto_sha256_update((uint8_t*)ptr, APPLICATION_END_ADDR-APPLICATION_START_ADDR);
|
|
crypto_sha256_final(hash);
|
|
curve = uECC_secp256r1();
|
|
|
|
if (! uECC_verify(pubkey,
|
|
hash,
|
|
32,
|
|
payload,
|
|
curve))
|
|
{
|
|
return CTAP2_ERR_OPERATION_DENIED;
|
|
}
|
|
authorize_application();
|
|
REBOOT_FLAG = 1;
|
|
break;
|
|
case BootCheck:
|
|
return 0;
|
|
break;
|
|
case BootErase:
|
|
printf1(TAG_BOOT, "BootErase.\r\n");
|
|
erase_application();
|
|
return 0;
|
|
break;
|
|
case BootVersion:
|
|
printf1(TAG_BOOT, "BootVersion.\r\n");
|
|
u2f_response_writeback(&version,1);
|
|
return 0;
|
|
break;
|
|
default:
|
|
return CTAP1_ERR_INVALID_COMMAND;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void bootloader_heartbeat()
|
|
{
|
|
static int state = 0;
|
|
static uint32_t val = 0x10;
|
|
int but = IS_BUTTON_PRESSED();
|
|
|
|
if (state)
|
|
{
|
|
val--;
|
|
}
|
|
else
|
|
{
|
|
val++;
|
|
}
|
|
|
|
if (val > 30 || val < 1)
|
|
{
|
|
state = !state;
|
|
}
|
|
led_rgb((val * 3)<<8 | (val*10) << 16);
|
|
}
|
|
|
|
#endif
|