996 lines
32 KiB
C
996 lines
32 KiB
C
/* SPDX-License-Identifier: MIT
|
|
* Copyright 2024 King Kévin <kingkevin@cuvoodoo.info>
|
|
*/
|
|
#include "stm32f0xx_hal.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "bsp/board_api.h"
|
|
#include "tusb.h"
|
|
|
|
#include "usb_descriptors.h"
|
|
#include "keylayouts.h"
|
|
#include "WjCryptLib_Sha256.h"
|
|
#include "date.h"
|
|
|
|
// get the length of an array
|
|
#define LENGTH(x) (sizeof(x) / sizeof((x)[0]))
|
|
// print debug messages
|
|
#define DEBUG (true)
|
|
|
|
#define BUTTON1_PORT GPIOA
|
|
#define BUTTON1_PIN GPIO_PIN_5
|
|
#define BUTTON1_STATE_ACTIVE 0
|
|
#define BUTTON2_PORT GPIOA
|
|
#define BUTTON2_PIN GPIO_PIN_7
|
|
#define BUTTON2_STATE_ACTIVE 0
|
|
|
|
#define BUTTON_DEBOUNCE (50U) // debounce time in ms
|
|
|
|
#define TIMEOUT_GLOBAL (12U * 60) // maximum time to hold the credentials, in minutes
|
|
#define TIMEOUT_REPEAT (3U * 60) // maximum time after last repeat to hold the credentials, in minutes
|
|
|
|
char manuf_key[32 + 1]; // the key set by the manufacturer to authenticate the device
|
|
|
|
static struct config_t {
|
|
uint8_t layout; // keyboard layout
|
|
char user_key[32 + 1]; // the key set by the user to authenticate the device
|
|
bool button_swap; // the order of the buttons (which is for username or password)
|
|
uint16_t timeout_global; // time to hold the credentials, in minutes
|
|
uint16_t timeout_repeat; // time after last repeat to hold the credentials, in minutes
|
|
uint8_t crc; // simple XOR CRC to check config validity
|
|
} config;
|
|
|
|
// page to store manufacturer key (can't be corrupted by save config)
|
|
#define FLASH_MANUF_ADDR ((uint32_t)0x08000000 + 0x400 * 30)
|
|
// last available page of last sector to store config
|
|
#define FLASH_CONFIG_ADDR ((uint32_t)0x08000000 + 0x400 * 31)
|
|
|
|
// the credentials to store and repeat
|
|
static char username[64] = {0}; // the credentials username
|
|
static char password[64] = {0}; // the credentials password
|
|
static bool credentials = false; // if the credentials are valid
|
|
static uint32_t entered_when = 0; // when credentials have been entered
|
|
static uint32_t pasted_when = 0; // last time the credentials have been pasted
|
|
|
|
#define KEY_INTERVAL (20U) // time in ms between keypress
|
|
static bool key_pressed = false; // if HID keyboard press report is sent
|
|
static uint32_t key_when = 0; // when HID keyboard press report is sent
|
|
static uint8_t user_paste = 0; // how much of the username is pasted
|
|
static uint8_t pass_paste = 0; // how much of the username is pasted
|
|
|
|
// LED blink period, in ms
|
|
#define BLINK_TIME (500U)
|
|
|
|
// split long string into USB packets
|
|
static const char* help_str[] = {
|
|
"\r\npress key to enter menu\r\n",
|
|
"h help\r\n",
|
|
"c enter credentials (ACSII only)\r\n",
|
|
"b swap buttons\r\n",
|
|
"g set global timeout\r\n",
|
|
"r set repeat timeout\r\n",
|
|
"k set authentication key\r\n",
|
|
"a authenticate device\r\n",
|
|
"l set keyboard layout\r\n",
|
|
};
|
|
|
|
static const char* prog_str[] = {
|
|
"K set manufacturer key\r\n",
|
|
"L lock device\r\n",
|
|
"E erase firmware\r\n",
|
|
};
|
|
|
|
enum {
|
|
MENU_HOME,
|
|
MENU_USER,
|
|
MENU_PASS,
|
|
MENU_MANUF,
|
|
MENU_KEY,
|
|
MENU_AUTH,
|
|
MENU_LAYOUT,
|
|
MENU_TIMEOUT_GLOBAL,
|
|
MENU_TIMEOUT_REPEAT,
|
|
} menu = MENU_HOME;
|
|
|
|
void led_blinking_task(void);
|
|
void hid_task(void);
|
|
void cdc_task(void);
|
|
void button_task(void);
|
|
|
|
static void board_supplement_init(void)
|
|
{
|
|
GPIO_InitTypeDef GPIO_InitStruct;
|
|
GPIO_InitStruct.Pin = BUTTON1_PIN;
|
|
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
|
|
GPIO_InitStruct.Pull = GPIO_PULLUP;
|
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
|
HAL_GPIO_Init(BUTTON1_PORT, &GPIO_InitStruct);
|
|
GPIO_InitStruct.Pin = BUTTON2_PIN;
|
|
HAL_GPIO_Init(BUTTON2_PORT, &GPIO_InitStruct);
|
|
}
|
|
|
|
uint32_t board_button1_read(void)
|
|
{
|
|
return BUTTON1_STATE_ACTIVE == HAL_GPIO_ReadPin(BUTTON1_PORT, BUTTON1_PIN);
|
|
}
|
|
|
|
uint32_t board_button2_read(void)
|
|
{
|
|
return BUTTON2_STATE_ACTIVE == HAL_GPIO_ReadPin(BUTTON2_PORT, BUTTON2_PIN);
|
|
}
|
|
|
|
static bool locked(void)
|
|
{
|
|
FLASH_OBProgramInitTypeDef ob;
|
|
HAL_FLASHEx_OBGetConfig(&ob);
|
|
return (OB_RDP_LEVEL_2 == ob.RDPLevel);
|
|
}
|
|
|
|
static void clear_credentials(void)
|
|
{
|
|
// clear credentials
|
|
credentials = false;
|
|
memset(username, 0, sizeof(username));
|
|
memset(password, 0, sizeof(password));
|
|
pasted_when = 0;
|
|
entered_when = 0;
|
|
printf("\r\ncredentials cleared\r\n");
|
|
}
|
|
|
|
static void load_config(void)
|
|
{
|
|
memcpy(manuf_key, (uint8_t*)FLASH_MANUF_ADDR, sizeof(manuf_key));
|
|
bool manuf_ok = false;
|
|
for (uint8_t i = 0; i < sizeof(manuf_key); i++) {
|
|
if (0 == manuf_key[i]) {
|
|
manuf_ok = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!manuf_ok) {
|
|
memset(manuf_key, 0, sizeof(manuf_key));
|
|
}
|
|
|
|
memcpy(&config, (uint8_t*)FLASH_CONFIG_ADDR, sizeof(config));
|
|
uint8_t crc = 0x42; // start with non-zero value to have a checksum work with empty config
|
|
for (uint16_t i = 0; i < sizeof(config); i++) {
|
|
crc ^= *(((uint8_t*)&config) + i);
|
|
}
|
|
if (0 == crc) {
|
|
printf("config loaded\r\n");
|
|
} else {
|
|
memset(&config, 0, sizeof(config));
|
|
printf("config initialized\r\n");
|
|
}
|
|
if (config.layout >= LENGTH(name_asciimap)) {
|
|
config.layout = 0;
|
|
}
|
|
if (0 == config.timeout_global || config.timeout_global > TIMEOUT_GLOBAL) {
|
|
config.timeout_global = TIMEOUT_GLOBAL;
|
|
}
|
|
if (0 == config.timeout_repeat || config.timeout_repeat > TIMEOUT_REPEAT) {
|
|
config.timeout_repeat = TIMEOUT_REPEAT;
|
|
}
|
|
}
|
|
|
|
static void save_config(void)
|
|
{
|
|
// update CRC
|
|
config.crc = 0; // clear CRC for correct calcuation
|
|
uint8_t crc = 0x42; // start with non-zero value to have a checksum work with empty config
|
|
for (uint16_t i = 0; i < sizeof(config); i++) {
|
|
crc ^= *(((uint8_t*)&config) + i);
|
|
}
|
|
config.crc = crc;
|
|
|
|
// save to flash
|
|
HAL_FLASH_Unlock();
|
|
FLASH_EraseInitTypeDef EraseInitStruct;
|
|
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
|
|
EraseInitStruct.PageAddress = FLASH_CONFIG_ADDR;
|
|
EraseInitStruct.NbPages = 1;
|
|
uint32_t error;
|
|
if (HAL_FLASHEx_Erase(&EraseInitStruct, &error) != HAL_OK) {
|
|
printf("earsing page failed\r\n");
|
|
goto end;
|
|
}
|
|
for (uint16_t i = 0; i < sizeof(config); i += 4) {
|
|
const uint32_t address = FLASH_CONFIG_ADDR + i;
|
|
uint32_t data;
|
|
memcpy(&data, (uint8_t*)&config + i, sizeof(data)); // works even if the data it not aligned
|
|
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data) != HAL_OK) {
|
|
printf("flash page failed\r\n");
|
|
goto end;
|
|
}
|
|
}
|
|
printf("config saved\r\n");
|
|
end:
|
|
HAL_FLASH_Lock();
|
|
}
|
|
|
|
static void save_manuf(void)
|
|
{
|
|
// save to flash
|
|
HAL_FLASH_Unlock();
|
|
FLASH_EraseInitTypeDef EraseInitStruct;
|
|
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
|
|
EraseInitStruct.PageAddress = FLASH_MANUF_ADDR;
|
|
EraseInitStruct.NbPages = 1;
|
|
uint32_t error;
|
|
if (HAL_FLASHEx_Erase(&EraseInitStruct, &error) != HAL_OK) {
|
|
printf("earsing page failed\r\n");
|
|
goto end;
|
|
}
|
|
for (uint16_t i = 0; i < sizeof(manuf_key); i += 4) {
|
|
const uint32_t address = FLASH_MANUF_ADDR + i;
|
|
uint32_t data;
|
|
memcpy(&data, (uint8_t*)&manuf_key[i], sizeof(data)); // works even if the data it not aligned
|
|
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data) != HAL_OK) {
|
|
printf("flash page failed\r\n");
|
|
goto end;
|
|
}
|
|
}
|
|
printf("manufacturer key saved\r\n");
|
|
end:
|
|
HAL_FLASH_Lock();
|
|
}
|
|
|
|
// convert bytes to ASCII hex (does not add end zero)
|
|
static void b2h(const uint8_t* b, char* h, uint8_t len)
|
|
{
|
|
if (!b || !h || !len) {
|
|
return;
|
|
}
|
|
for (uint8_t i = 0; i < len; i++) {
|
|
uint8_t nibble = b[i] >> 4;
|
|
if (nibble <= 9) {
|
|
h[i * 2 + 0] = '0' + nibble;
|
|
} else {
|
|
h[i * 2 + 0] = 'a' + nibble - 0xa;
|
|
}
|
|
nibble = b[i] & 0xf;
|
|
if (nibble <= 9) {
|
|
h[i * 2 + 1] = '0' + nibble;
|
|
} else {
|
|
h[i * 2 + 1] = 'a' + nibble - 0xa;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ensure string is sent
|
|
void tud_cdc_write_str_flush(const char* str)
|
|
{
|
|
while (tud_cdc_write_available() < CFG_TUD_CDC_TX_BUFSIZE) {
|
|
tud_task();
|
|
}
|
|
tud_cdc_write_str(str);
|
|
tud_cdc_write_flush();
|
|
while (tud_cdc_write_available() < CFG_TUD_CDC_TX_BUFSIZE) {
|
|
tud_task();
|
|
}
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
board_init();
|
|
board_supplement_init();
|
|
// init device stack on configured roothub port
|
|
tud_init(BOARD_TUD_RHPORT);
|
|
|
|
printf("\r\npasskey\r\n");
|
|
printf("hardware version: 2\r\n"); // for now we just have version 2
|
|
printf("firmware date: %04u-%02u-%02u\r\n", BUILD_YEAR, BUILD_MONTH, BUILD_DAY); // show firmware build date
|
|
load_config();
|
|
|
|
FLASH_OBProgramInitTypeDef ob;
|
|
HAL_FLASHEx_OBGetConfig(&ob);
|
|
if (OB_RDP_LEVEL_2 != ob.RDPLevel) {
|
|
printf("device not locked\r\n");
|
|
}
|
|
if (0 == strlen(manuf_key)) {
|
|
printf("manufacturer key not set\r\n");
|
|
}
|
|
|
|
if (board_init_after_tusb) {
|
|
board_init_after_tusb();
|
|
}
|
|
while (true)
|
|
{
|
|
tud_task(); // tinyusb device task
|
|
|
|
// check for credential timeout
|
|
const uint32_t minutes = board_millis();
|
|
if (credentials && ((minutes > entered_when + config.timeout_global * 1000 * 60) || (minutes > entered_when + TIMEOUT_GLOBAL * 1000 * 60))) {
|
|
const char str[] = "global timeout reached, clearing cedentials\r\n";
|
|
tud_cdc_write_str(str);
|
|
tud_cdc_write_flush();
|
|
printf(str);
|
|
clear_credentials();
|
|
}
|
|
if (credentials && ((minutes > pasted_when + config.timeout_repeat * 1000 * 60) || (minutes > pasted_when + TIMEOUT_REPEAT * 1000 * 60))) {
|
|
const char str[] = "repeat timeout reached, clearing cedentials\r\n";
|
|
tud_cdc_write_str(str);
|
|
tud_cdc_write_flush();
|
|
printf(str);
|
|
clear_credentials();
|
|
}
|
|
|
|
// run the usual tasks
|
|
led_blinking_task();
|
|
button_task();
|
|
hid_task();
|
|
cdc_task();
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// Device callbacks
|
|
//--------------------------------------------------------------------+
|
|
|
|
// Invoked when device is mounted
|
|
void tud_mount_cb(void)
|
|
{
|
|
clear_credentials();
|
|
}
|
|
|
|
// Invoked when device is unmounted
|
|
void tud_umount_cb(void)
|
|
{
|
|
clear_credentials();
|
|
}
|
|
|
|
// Invoked when usb bus is suspended
|
|
// remote_wakeup_en : if host allow us to perform remote wakeup
|
|
// Within 7ms, device must draw an average of current less than 2.5 mA from bus
|
|
void tud_suspend_cb(bool remote_wakeup_en)
|
|
{
|
|
(void) remote_wakeup_en;
|
|
}
|
|
|
|
// Invoked when usb bus is resumed
|
|
void tud_resume_cb(void)
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// USB CDC
|
|
//--------------------------------------------------------------------+
|
|
void cdc_task(void)
|
|
{
|
|
static char tmp[64];
|
|
|
|
// connected() check for DTR bit
|
|
// Most but not all terminal client set this when making connection
|
|
// if ( tud_cdc_connected() )
|
|
{
|
|
// connected and there are data available
|
|
if (tud_cdc_available()) {
|
|
// read data
|
|
char buf[CFG_TUD_CDC_RX_BUFSIZE];
|
|
const uint32_t count = tud_cdc_read(buf, sizeof(buf));
|
|
bool echo = true; // if we echo back input
|
|
if (0 == count) {
|
|
return;
|
|
}
|
|
|
|
const char* str = NULL; // message to print
|
|
static uint16_t i = 0; // generic array index
|
|
switch (menu) {
|
|
case MENU_HOME:
|
|
switch (buf[0]) {
|
|
case 'c': // input credentials
|
|
i = 0; // reset index
|
|
menu = MENU_USER; // go to corresponding menu
|
|
str = "\r\nenter credentials to paste\r\nusername: ";
|
|
break;
|
|
case 'b': // swap buttons
|
|
config.button_swap = !config.button_swap;
|
|
str = "\r\nbuttons swapped\r\n";
|
|
save_config();
|
|
break;
|
|
case 'g': // set global timeout
|
|
i = 0; // reset index
|
|
menu = MENU_TIMEOUT_GLOBAL;
|
|
snprintf(tmp, sizeof(tmp), "\r\nenter global timeout in minutes [%u]: ", config.timeout_global);
|
|
str = tmp;
|
|
break;
|
|
case 'r': // set repeat timeout
|
|
i = 0; // reset index
|
|
menu = MENU_TIMEOUT_REPEAT;
|
|
snprintf(tmp, sizeof(tmp), "\r\nenter repeat timeout in minutes [%u]: ", config.timeout_repeat);
|
|
str = tmp;
|
|
break;
|
|
case 'k': // set user key
|
|
i = 0; // reset index
|
|
menu = MENU_KEY;
|
|
str = "\r\nenter authentication key (up to 32 char): ";
|
|
break;
|
|
case 'a': // run authentication
|
|
if (0 == strlen(config.user_key) && 0 == strlen(manuf_key)) {
|
|
str = "\r\nno key set\r\n";
|
|
} else {
|
|
i = 0; // reset index
|
|
menu = MENU_AUTH;
|
|
str = "\r\nenter random string (up to 60 char): ";
|
|
}
|
|
break;
|
|
case 'l': // set keyboard layout
|
|
echo = false;
|
|
tud_cdc_write(buf, 1); // since echo will be off
|
|
tud_cdc_write_str("\n\r");
|
|
tud_cdc_write_flush();
|
|
for (uint8_t j = 0; j < LENGTH(name_asciimap); j++) {
|
|
snprintf(tmp, sizeof(tmp), "%02u %s\r\n", j, name_asciimap[j]);
|
|
tud_cdc_write_str_flush(tmp);
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "set keyboard layout [%s]: ", name_asciimap[config.layout]);
|
|
tud_cdc_write_str_flush(tmp);
|
|
i = 0; // reset index
|
|
menu = MENU_LAYOUT;
|
|
break;
|
|
// hidden menu
|
|
case 'K': // set manufacturer key
|
|
if (!locked()) {
|
|
i = 0; // reset index
|
|
menu = MENU_MANUF;
|
|
str = "\r\nenter manufacturer key (up to 32 char): ";
|
|
}
|
|
break;
|
|
case 'L': // lock device
|
|
if (!locked()) {
|
|
tud_cdc_write_str_flush("\r\nlocking device\r\n");
|
|
FLASH_OBProgramInitTypeDef ob;
|
|
HAL_FLASHEx_OBGetConfig(&ob);
|
|
ob.RDPLevel = OB_RDP_LEVEL_2; // level 2, disabling debug, and write protecting option byte
|
|
ob.WRPState = OB_WRPSTATE_ENABLE; // write protect pages
|
|
ob.WRPPage = 0x7; // all but last sector holding the config
|
|
HAL_FLASH_Unlock();
|
|
HAL_FLASH_OB_Unlock();
|
|
//HAL_FLASHEx_OBProgram(&ob);
|
|
HAL_FLASH_OB_Lock();
|
|
HAL_FLASH_Lock();
|
|
HAL_FLASH_OB_Launch();
|
|
}
|
|
break;
|
|
case 'E': // mass erase to restart STM32 bootloader
|
|
if (!locked()) {
|
|
tud_cdc_write_str_flush("\r\nerasing firmware\r\n");
|
|
HAL_FLASH_Unlock();
|
|
FLASH_EraseInitTypeDef EraseInitStruct;
|
|
EraseInitStruct.TypeErase = FLASH_TYPEERASE_MASSERASE;
|
|
uint32_t error;
|
|
if (HAL_FLASHEx_Erase(&EraseInitStruct, &error) != HAL_OK) {
|
|
printf("mass erase failed\r\n");
|
|
}
|
|
HAL_FLASH_Lock();
|
|
HAL_FLASH_OB_Launch(); // just to restart
|
|
}
|
|
break;
|
|
case '\r':
|
|
case '\n':
|
|
break; // nothing to do
|
|
case 'h':
|
|
default:
|
|
tud_cdc_write(buf, 1); // since echo will be off
|
|
tud_cdc_write_flush();
|
|
echo = false;
|
|
for (uint8_t j = 0; j < LENGTH(help_str); j++) {
|
|
tud_cdc_write_str_flush(help_str[j]);
|
|
}
|
|
if (!locked()) {
|
|
for (uint8_t j = 0; j < LENGTH(prog_str); j++) {
|
|
tud_cdc_write_str_flush(prog_str[j]);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MENU_USER:
|
|
for (uint16_t j = 0; j < count; j++) {
|
|
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
|
|
username[i] = 0; // end string
|
|
str = NULL;
|
|
i = 0; // reset index
|
|
menu = MENU_PASS; // go to next menu
|
|
str = "\r\npassword: ";
|
|
} else if (i >= sizeof(username) - 2) {
|
|
memset(username, 0, sizeof(username)); // clear username
|
|
str = "\r\nlimit reached\r\n";
|
|
} else {
|
|
username[i++] = buf[j]; // save username
|
|
}
|
|
}
|
|
break;
|
|
case MENU_PASS:
|
|
echo = false; // keep secret
|
|
for (uint16_t j = 0; j < count; j++) {
|
|
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
|
|
password[i] = 0; // end password
|
|
if (strlen(username) && strlen(password)) {
|
|
credentials = true;
|
|
entered_when = board_millis();
|
|
pasted_when = board_millis();
|
|
str = "\r\ncredentials saved\r\n";
|
|
} else {
|
|
credentials = false;
|
|
str = "\r\ninvalid credentials\r\n";
|
|
}
|
|
i = 0; // reset index
|
|
menu = MENU_HOME; // go to next menu
|
|
} else if (i >= sizeof(password) - 2) {
|
|
memset(password, 0, sizeof(password)); // clear password
|
|
str = "\r\nlimit reached\r\n";
|
|
} else {
|
|
password[i++] = buf[j]; // save password
|
|
}
|
|
}
|
|
break;
|
|
case MENU_MANUF:
|
|
echo = false; // keep secret
|
|
for (uint16_t j = 0; j < count; j++) {
|
|
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
|
|
tmp[i] = 0; // end key
|
|
if (strlen(tmp)) {
|
|
memset(manuf_key, 0, sizeof(manuf_key));
|
|
memcpy(manuf_key, tmp, i);
|
|
save_manuf();
|
|
str = "\r\nmanufacturer key saved\r\n";
|
|
} else {
|
|
str = "\r\ninvalid input\r\n";
|
|
}
|
|
i = 0; // reset index
|
|
menu = MENU_HOME; // go to next menu
|
|
} else if (i >= sizeof(config.user_key) - 2) {
|
|
memset(tmp, 0, sizeof(tmp)); // clear password
|
|
str = "\r\nlimit reached\r\n";
|
|
} else {
|
|
tmp[i++] = buf[j]; // save password
|
|
}
|
|
}
|
|
break;
|
|
case MENU_KEY:
|
|
echo = false; // keep secret
|
|
for (uint16_t j = 0; j < count; j++) {
|
|
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
|
|
tmp[i] = 0; // end key
|
|
if (strlen(tmp)) {
|
|
memset(config.user_key, 0, sizeof(config.user_key));
|
|
memcpy(config.user_key, tmp, i);
|
|
save_config();
|
|
str = "\r\nauthentication key saved\r\n";
|
|
} else {
|
|
echo = true;
|
|
str = "\r\ninvalid input\r\n";
|
|
}
|
|
i = 0; // reset index
|
|
menu = MENU_HOME; // go to next menu
|
|
} else if (i >= sizeof(config.user_key) - 2) {
|
|
memset(tmp, 0, sizeof(tmp)); // clear password
|
|
str = "\r\nlimit reached\r\n";
|
|
} else {
|
|
tmp[i++] = buf[j]; // save password
|
|
}
|
|
}
|
|
break;
|
|
case MENU_AUTH:
|
|
for (uint16_t j = 0; j < count; j++) {
|
|
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
|
|
tmp[i] = 0; // end string
|
|
if (strlen(tmp)) {
|
|
echo = false; // we need to write now
|
|
if (strlen(manuf_key)) {
|
|
char uid[4 * 3 * 2 + 1];
|
|
b2h((uint8_t*)UID_BASE, uid, 4 * 3);
|
|
uid[sizeof(uid) - 1] = 0; // end string
|
|
tud_cdc_write_str_flush("\r\ngo to https://passkey.cuvoodoo.info/?auth=");
|
|
tud_cdc_write_str_flush(uid);
|
|
tud_cdc_write_str_flush(tmp);
|
|
uint8_t hash_in[sizeof(manuf_key) + sizeof(tmp)];
|
|
memcpy(&hash_in[0], manuf_key, strlen(manuf_key));
|
|
memcpy(&hash_in[strlen(manuf_key)], tmp, strlen(tmp));
|
|
SHA256_HASH hash_out;
|
|
Sha256Calculate(hash_in, strlen(manuf_key) + strlen(tmp), &hash_out);
|
|
char hash_str[SHA256_HASH_SIZE * 2 + 1];
|
|
b2h(hash_out.bytes, hash_str, SHA256_HASH_SIZE);
|
|
hash_str[SHA256_HASH_SIZE * 2] = 0; // end string
|
|
tud_cdc_write_str_flush("\r\nexpected response: ");
|
|
tud_cdc_write_str_flush(hash_str); // luckily the USB packet len is the same as the string
|
|
}
|
|
if (strlen(config.user_key)) {
|
|
uint8_t hash_in[sizeof(config.user_key) + sizeof(tmp)];
|
|
memcpy(&hash_in[0], config.user_key, strlen(config.user_key));
|
|
memcpy(&hash_in[strlen(config.user_key)], tmp, strlen(tmp));
|
|
SHA256_HASH hash_out;
|
|
Sha256Calculate(hash_in, strlen(config.user_key) + strlen(tmp), &hash_out);
|
|
char hash_str[SHA256_HASH_SIZE * 2 + 1];
|
|
b2h(hash_out.bytes, hash_str, SHA256_HASH_SIZE);
|
|
hash_str[SHA256_HASH_SIZE * 2] = 0; // end string
|
|
tud_cdc_write_str_flush("\r\necho -n \"<key>");
|
|
tud_cdc_write_str_flush(tmp);
|
|
tud_cdc_write_str_flush("\" | sha256sum\r\n");
|
|
tud_cdc_write_str_flush(hash_str); // luckily the USB packet len is the same as the string
|
|
}
|
|
str = "\r\n";
|
|
} else {
|
|
str = "\r\ninvalid input\r\n";
|
|
}
|
|
i = 0; // reset index
|
|
menu = MENU_HOME; // go to next menu
|
|
} else if (i >= sizeof(config.user_key) - 2) {
|
|
memset(tmp, 0, sizeof(tmp)); // clear password
|
|
str = "\r\nlimit reached\r\n";
|
|
} else {
|
|
tmp[i++] = buf[j]; // save password
|
|
}
|
|
}
|
|
break;
|
|
case MENU_LAYOUT:
|
|
for (uint16_t j = 0; j < count; j++) {
|
|
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
|
|
tmp[i] = 0; // end time input
|
|
const uint16_t layout = atoi(tmp);
|
|
if (0 == strlen(tmp)) {
|
|
str = "\r\n"; // leave previous
|
|
} else if (layout > LENGTH(map_asciimap)) {
|
|
str = "\r\ninvalid entry\r\n";
|
|
} else {
|
|
config.layout = layout;
|
|
save_config();
|
|
str = "\r\nlayout set\r\n";
|
|
}
|
|
i = 0; // reset index
|
|
menu = MENU_HOME; // go to next menu
|
|
} else if (i >= sizeof(tmp) - 2) {
|
|
memset(tmp, 0, sizeof(tmp)); // clear password
|
|
str = "\r\nlimit reached\r\n";
|
|
} else {
|
|
tmp[i++] = buf[j]; // save user entry
|
|
}
|
|
}
|
|
break;
|
|
case MENU_TIMEOUT_GLOBAL:
|
|
for (uint16_t j = 0; j < count; j++) {
|
|
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
|
|
tmp[i] = 0; // end time input
|
|
const uint16_t time = atoi(tmp);
|
|
if (0 == strlen(tmp)) {
|
|
str = "\r\n"; // leave previous
|
|
} else {
|
|
if (0 == time || config.timeout_global > TIMEOUT_GLOBAL) {
|
|
config.timeout_global = TIMEOUT_GLOBAL;
|
|
}
|
|
save_config();
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "\r\nglobal timeout set to %u minutes\r\n", config.timeout_global);
|
|
str = tmp;
|
|
i = 0; // reset index
|
|
menu = MENU_HOME; // go to next menu
|
|
} else if (i >= sizeof(tmp) - 2) {
|
|
memset(tmp, 0, sizeof(tmp)); // clear password
|
|
str = "\r\nlimit reached\r\n";
|
|
} else {
|
|
tmp[i++] = buf[j]; // save password
|
|
}
|
|
}
|
|
break;
|
|
case MENU_TIMEOUT_REPEAT:
|
|
for (uint16_t j = 0; j < count; j++) {
|
|
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
|
|
tmp[i] = 0; // end time input
|
|
const uint16_t time = atoi(tmp);
|
|
if (0 == strlen(tmp)) {
|
|
str = "\r\n"; // leave previous
|
|
} else {
|
|
if (0 == time || config.timeout_repeat > TIMEOUT_REPEAT) {
|
|
config.timeout_repeat = TIMEOUT_REPEAT;
|
|
}
|
|
save_config();
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "\r\nrepeat timeout set to %u minutes\r\n", config.timeout_repeat);
|
|
str = tmp;
|
|
i = 0; // reset index
|
|
menu = MENU_HOME; // go to next menu
|
|
} else if (i >= sizeof(tmp) - 2) {
|
|
memset(tmp, 0, sizeof(tmp)); // clear password
|
|
str = "\r\nlimit reached\r\n";
|
|
} else {
|
|
tmp[i++] = buf[j]; // save password
|
|
}
|
|
}
|
|
break;
|
|
default: // unknown menu
|
|
menu = MENU_HOME; // revert to home
|
|
}
|
|
|
|
if (echo) {
|
|
tud_cdc_write(buf, count);
|
|
}
|
|
if (str) {
|
|
tud_cdc_write_str(str);
|
|
}
|
|
if (echo || str) {
|
|
tud_cdc_write_flush();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Invoked when cdc when line state changed e.g connected/disconnected
|
|
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
|
|
{
|
|
(void) itf;
|
|
(void) rts;
|
|
|
|
// TODO set some indicator
|
|
if (dtr) {
|
|
// Terminal connected
|
|
} else {
|
|
// Terminal disconnected
|
|
}
|
|
}
|
|
|
|
// Invoked when CDC interface received data from host
|
|
void tud_cdc_rx_cb(uint8_t itf)
|
|
{
|
|
(void) itf;
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// USB HID
|
|
//--------------------------------------------------------------------+
|
|
static void send_hid_report(uint8_t modifier, uint8_t keycode)
|
|
{
|
|
// skip if hid is not ready yet
|
|
if ( !tud_hid_ready() ) {
|
|
return;
|
|
}
|
|
|
|
uint8_t keycodes[6] = {keycode, 0, 0, 0, 0, 0};
|
|
if (keycode) {
|
|
key_when = board_millis(); // remember when we sent the key
|
|
key_pressed = true; // remember we pressed a key
|
|
} else {
|
|
key_pressed = false; // key released
|
|
}
|
|
tud_hid_keyboard_report(REPORT_ID_KEYBOARD, modifier, keycodes);
|
|
}
|
|
|
|
static void send_hid_char(char c)
|
|
{
|
|
// set default to US
|
|
if (config.layout >= LENGTH(map_asciimap)) {
|
|
config.layout = 0;
|
|
}
|
|
|
|
if (0 == c) {
|
|
send_hid_report(0, 0);
|
|
} else if ('\t' == c) {
|
|
send_hid_report(0, KEY_TAB);
|
|
} else if ('\r' == c || '\n' == c) {
|
|
send_hid_report(0, KEY_ENTER);
|
|
} else if (c >= ' ' && c <= '~') {
|
|
const uint16_t code = map_asciimap[config.layout][c - ' '];
|
|
const uint8_t modifier = code >> 8;
|
|
const uint8_t keycode = code & 0xff;
|
|
send_hid_report(modifier, keycode);
|
|
}
|
|
}
|
|
|
|
void hid_task(void)
|
|
{
|
|
if (key_when) { // pasting started
|
|
if (board_millis() < key_when + KEY_INTERVAL) {
|
|
return; // wait more before next key press
|
|
}
|
|
if (!credentials) { // nothing to send
|
|
key_when = 0;
|
|
user_paste = 0;
|
|
pass_paste = 0;
|
|
return;
|
|
}
|
|
if (pass_paste) { // send password
|
|
if (pass_paste < strlen(password)) { // send password
|
|
send_hid_char(password[pass_paste++]);
|
|
} else if (pass_paste == strlen(password)) { // press enter
|
|
send_hid_char('\n');
|
|
pass_paste++;
|
|
} else if (pass_paste == (strlen(password) + 1)) { // finished pasting
|
|
user_paste = 0;
|
|
pass_paste = 0;
|
|
key_when = 0;
|
|
}
|
|
}
|
|
if (user_paste) { // send username
|
|
if (user_paste < strlen(username)) { // send username
|
|
send_hid_char(username[user_paste++]);
|
|
} else if (user_paste == strlen(username)) { // switch to password
|
|
send_hid_char('\t');
|
|
user_paste++;
|
|
} else if (user_paste == (strlen(username) + 1)) { // start sending password
|
|
pass_paste = 0;
|
|
send_hid_char(password[pass_paste++]);
|
|
user_paste++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void paste_credentials(bool user)
|
|
{
|
|
if (!credentials || 0 == strlen(username) || 0 == strlen(password)) {
|
|
printf("no credentials to paste\r\n");
|
|
return;
|
|
}
|
|
|
|
user_paste = 0;
|
|
pass_paste = 0;
|
|
pasted_when = board_millis();
|
|
|
|
if (user) {
|
|
send_hid_char(username[user_paste++]);
|
|
printf("pasting username and password\r\n");
|
|
} else {
|
|
send_hid_char(password[pass_paste++]);
|
|
printf("pasting password\r\n");
|
|
}
|
|
}
|
|
|
|
void button_task(void)
|
|
{
|
|
static uint32_t button1_ms = 0; // when the button has been pressed (in ms)
|
|
static bool button1_ok = false; // if the button is pressed (after debounce)
|
|
static uint32_t button2_ms = 0; // when the button has been pressed (in ms)
|
|
static bool button2_ok = false; // if the button is pressed (after debounce)
|
|
|
|
// Poll every 10ms
|
|
const uint32_t interval_ms = 10;
|
|
static uint32_t start_ms = 0;
|
|
|
|
if ( board_millis() - start_ms < interval_ms) return; // not enough time
|
|
start_ms += interval_ms;
|
|
|
|
const bool button1_pressed = board_button1_read();
|
|
const bool button2_pressed = board_button2_read();
|
|
|
|
// Remote wakeup
|
|
if ( tud_suspended() && (button1_pressed || button2_pressed) ) {
|
|
// Wake up host if we are in suspend mode
|
|
// and REMOTE_WAKEUP feature is enabled by host
|
|
tud_remote_wakeup();
|
|
return;
|
|
}
|
|
|
|
// ensure button is pressed
|
|
if (button1_pressed && !button1_ok) {
|
|
if (0 == button1_ms) {
|
|
button1_ms = board_millis();
|
|
} else if ( board_millis() + BUTTON_DEBOUNCE > button1_ms ) {
|
|
button1_ok = true;
|
|
}
|
|
}
|
|
if (button2_pressed && !button2_ok) {
|
|
if (0 == button2_ms) {
|
|
button2_ms = board_millis();
|
|
} else if ( board_millis() + BUTTON_DEBOUNCE > button2_ms ) {
|
|
button2_ok = true;
|
|
}
|
|
}
|
|
|
|
// check which button is pressed/released
|
|
if (button1_pressed && button2_pressed && button1_ok && button2_ok) { // two buttons pressed
|
|
if (credentials) {
|
|
clear_credentials();
|
|
printf("clearing credentials\r\n");
|
|
}
|
|
} else if (!button1_pressed && button1_ok && !button2_ok) { // button 1 released
|
|
//past_credentials(!config.button_swap); // I have no idea why this does not work
|
|
if (config.button_swap) {
|
|
paste_credentials(false);
|
|
} else {
|
|
paste_credentials(true);
|
|
}
|
|
} else if (!button2_pressed && !button1_ok && button2_ok) { // button 2 released
|
|
if (config.button_swap) {
|
|
paste_credentials(true);
|
|
} else {
|
|
paste_credentials(false);
|
|
}
|
|
}
|
|
|
|
// clear button state
|
|
if (!button1_pressed) {
|
|
button1_ok = false;
|
|
button1_ms = 0;
|
|
}
|
|
if (!button2_pressed) {
|
|
button2_ok = false;
|
|
button2_ms = 0;
|
|
}
|
|
}
|
|
|
|
// Invoked when sent REPORT successfully to host
|
|
// Application can use this to send the next report
|
|
// Note: For composite reports, report[0] is report ID
|
|
void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len)
|
|
{
|
|
(void) instance;
|
|
(void) report;
|
|
(void) len;
|
|
|
|
if ((report[0] == REPORT_ID_KEYBOARD) && key_pressed) {
|
|
//tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, NULL); // release press
|
|
send_hid_report(0, 0); // release key
|
|
//return;
|
|
}
|
|
}
|
|
|
|
// Invoked when received GET_REPORT control request
|
|
// Application must fill buffer report's content and return its length.
|
|
// Return zero will cause the stack to STALL request
|
|
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
|
|
{
|
|
// TODO not Implemented
|
|
(void) instance;
|
|
(void) report_id;
|
|
(void) report_type;
|
|
(void) buffer;
|
|
(void) reqlen;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Invoked when received SET_REPORT control request or
|
|
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
|
|
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize)
|
|
{
|
|
(void) instance;
|
|
static uint8_t kbd_leds_last = 0; // last state
|
|
static uint32_t kbd_leds_when = 0; // when did the state change
|
|
static uint8_t kbd_leds_count = 0; // how many times it changed (within a certain period)
|
|
|
|
if (report_type == HID_REPORT_TYPE_OUTPUT) {
|
|
// Set keyboard LED e.g Capslock, Numlock etc...
|
|
if (report_id == REPORT_ID_KEYBOARD) {
|
|
// bufsize should be (at least) 1
|
|
if ( bufsize < 1 ) return;
|
|
uint8_t const kbd_leds = buffer[0];
|
|
uint8_t const kbd_leds_change = kbd_leds ^ kbd_leds_last;
|
|
if (kbd_leds_change & (KEYBOARD_LED_CAPSLOCK | KEYBOARD_LED_NUMLOCK)) { // numlock or capslock pressed
|
|
if (board_millis() - kbd_leds_when > 500) { // not fast enough
|
|
kbd_leds_count = 0; // reset count
|
|
}
|
|
kbd_leds_count++;
|
|
kbd_leds_when = board_millis();
|
|
if (kbd_leds_count > 3 && credentials) {
|
|
clear_credentials();
|
|
}
|
|
}
|
|
kbd_leds_last = kbd_leds; // remember for next comparision
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// BLINKING TASK
|
|
//--------------------------------------------------------------------+
|
|
void led_blinking_task(void)
|
|
{
|
|
static uint32_t start_ms = 0;
|
|
static bool led_state = false;
|
|
|
|
// Blink every interval ms
|
|
if ( board_millis() - start_ms < BLINK_TIME) return; // not enough time
|
|
start_ms = board_millis();
|
|
|
|
if (!credentials) {
|
|
led_state = !led_state; // toggle
|
|
} else {
|
|
led_state = true;
|
|
}
|
|
board_led_write(led_state);
|
|
|
|
board_uart_write(".", 1); // debug heartbeat
|
|
}
|