/* SPDX-License-Identifier: MIT * Copyright 2024 King Kévin */ #include "stm32f0xx_hal.h" #include #include #include #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 \""); 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 }