From 8af54dae051fd3a638a43fe3e1ede0d150d9949e Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Mon, 1 Jan 2024 10:29:43 +0800 Subject: [PATCH] wip: add pass --- applets/admin/admin.c | 20 +--- applets/oath/oath.c | 17 +-- applets/pass/pass.c | 157 +++++++++++++++++++++++++++ include/admin.h | 8 -- include/oath.h | 2 +- include/pass.h | 29 +++++ interfaces/USB/class/kbdhid/kbdhid.c | 100 ++++++++++------- src/device.c | 2 +- 8 files changed, 251 insertions(+), 84 deletions(-) create mode 100644 applets/pass/pass.c create mode 100644 include/pass.h diff --git a/applets/admin/admin.c b/applets/admin/admin.c index a14e8c92..16218073 100644 --- a/applets/admin/admin.c +++ b/applets/admin/admin.c @@ -16,7 +16,7 @@ static pin_t pin = {.min_length = 6, .max_length = PIN_MAX_LENGTH, .is_validated = 0, .path = "admin-pin"}; -static const admin_device_config_t default_cfg = {.led_normally_on = 1, .ndef_en = 1, .webusb_landing_en = 1, .kbd_with_return_en = 1}; +static const admin_device_config_t default_cfg = {.led_normally_on = 1, .ndef_en = 1, .webusb_landing_en = 1}; static admin_device_config_t current_config; @@ -46,14 +46,10 @@ __attribute__((weak)) int admin_vendor_hw_sn(const CAPDU *capdu, RAPDU *rapdu) { uint8_t cfg_is_led_normally_on(void) { return current_config.led_normally_on; } -uint8_t cfg_is_kbd_interface_enable(void) { return current_config.kbd_interface_en; } - uint8_t cfg_is_ndef_enable(void) { return current_config.ndef_en; } uint8_t cfg_is_webusb_landing_enable(void) { return current_config.webusb_landing_en; } -uint8_t cfg_is_kbd_with_return_enable(void) { return current_config.kbd_with_return_en; } - uint8_t cfg_is_piv_algo_extension_enable(void) { return current_config.piv_algo_ext_en; } void admin_poweroff(void) { pin.is_validated = 0; } @@ -119,18 +115,12 @@ static int admin_config(const CAPDU *capdu, RAPDU *rapdu) { case ADMIN_P1_CFG_LED_ON: current_config.led_normally_on = P2 & 1; break; - case ADMIN_P1_CFG_KBDIFACE: - current_config.kbd_interface_en = P2 & 1; - break; case ADMIN_P1_CFG_NDEF: current_config.ndef_en = P2 & 1; break; case ADMIN_P1_CFG_WEBUSB_LANDING: current_config.webusb_landing_en = P2 & 1; break; - case ADMIN_P1_CFG_KBD_WITH_RETURN: - current_config.kbd_with_return_en = P2 & 1; - break; case ADMIN_P1_CFG_PIV_ALGO_EXT: current_config.piv_algo_ext_en = P2 & 1; break; @@ -147,11 +137,9 @@ static int admin_read_config(const CAPDU *capdu, RAPDU *rapdu) { if (LE < 5) EXCEPT(SW_WRONG_LENGTH); RDATA[0] = current_config.led_normally_on; - RDATA[1] = current_config.kbd_interface_en; - RDATA[2] = ndef_get_read_only(); - RDATA[3] = current_config.ndef_en; - RDATA[4] = current_config.webusb_landing_en; - RDATA[5] = current_config.kbd_with_return_en; + RDATA[1] = ndef_get_read_only(); + RDATA[2] = current_config.ndef_en; + RDATA[3] = current_config.webusb_landing_en; LL = 6; return 0; diff --git a/applets/oath/oath.c b/applets/oath/oath.c index a10bb17f..748cc953 100644 --- a/applets/oath/oath.c +++ b/applets/oath/oath.c @@ -384,7 +384,7 @@ static uint8_t *oath_digest(const OATH_RECORD *record, uint8_t buffer[SHA512_DIG return buffer + offset; } -static int oath_calculate_by_offset(const size_t file_offset, uint8_t result[4]) { +int oath_calculate_by_offset(size_t file_offset, uint8_t result[4]) { if (file_offset % sizeof(OATH_RECORD) != 0) return -2; const int size = get_file_size(OATH_FILE); if (size < 0 || file_offset >= (size_t)size) return -2; @@ -618,21 +618,6 @@ static int oath_send_remaining(const CAPDU *capdu, RAPDU *rapdu) { EXCEPT(SW_CONDITIONS_NOT_SATISFIED); } -int oath_process_one_touch(char *output, const size_t maxlen) { - uint32_t offset = 0xffffffff, otp_code; - if (read_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &offset, sizeof(offset)) < 0) return -2; - int ret = oath_calculate_by_offset(offset, (uint8_t *)&otp_code); - if (ret < 0) return ret; - if ((size_t)(ret + 1) > maxlen) return -1; - output[ret] = '\0'; - otp_code = htobe32(otp_code); - while (ret--) { - output[ret] = otp_code % 10 + '0'; - otp_code /= 10; - } - return 0; -} - // ReSharper disable once CppDFAConstantFunctionResult int oath_process_apdu(const CAPDU *capdu, RAPDU *rapdu) { LL = 0; diff --git a/applets/pass/pass.c b/applets/pass/pass.c new file mode 100644 index 00000000..22ed9383 --- /dev/null +++ b/applets/pass/pass.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include + +#define PASS_FILE "pass" +#define SLOT_SHORT 0 +#define SLOT_LONG 1 + +static pass_slot_t slots[2]; + +int pass_install(const uint8_t reset) { + if (!reset && get_file_size(PASS_FILE) >= 0) { + if (read_file(PASS_FILE, slots, 0, sizeof(slots)) < 0) return -1; + return 0; + } + + memzero(slots, sizeof(slots)); + if (write_file(PASS_FILE, slots, 0, 0, 1) < 0) return -1; + + return 0; +} + +static int dump_slot(const pass_slot_t *slot, uint8_t *buffer) { + int length = 0; + + // First byte is always the type + buffer[0] = (uint8_t)slot->type; + length++; + + switch (slot->type) { + case PASS_SLOT_OFF: + case PASS_SLOT_STATIC: + // For OFF and STATIC, the second byte is with_enter + buffer[1] = slot->with_enter; + length++; + break; + + case PASS_SLOT_OATH: + // For OATH, the next 4 bytes are oath_offset + memcpy(&buffer[1], &slot->oath_offset, sizeof(slot->oath_offset)); + buffer[5] = slot->with_enter; + length += 5; + break; + } + + return length; +} + +int pass_read_config(const CAPDU *capdu, RAPDU *rapdu) { + UNUSED(capdu); + + int length = dump_slot(&slots[SLOT_SHORT], RDATA); + length += dump_slot(&slots[SLOT_LONG], RDATA + length); + LL = length; + + return 0; +} + +int pass_write_config(const CAPDU *capdu, RAPDU *rapdu) { + size_t index = 0; + + for (int i = 0; i < 2; i++) { + if (index >= LC) { + // Data is not enough to parse a slot + EXCEPT(SW_WRONG_LENGTH); + } + + const slot_type_t type = DATA[index++]; + switch (type) { + case PASS_SLOT_OFF: + slots[i].type = type; + break; + + case PASS_SLOT_STATIC: + if (index + sizeof(slots[0].password) + sizeof(slots[0].with_enter) > LC || + slots[i].password[0] > PASS_MAX_PASSWORD_LENGTH) { + // Not enough data for PASS_SLOT_STATIC or password is too long + EXCEPT(SW_WRONG_DATA); + } + slots[i].type = type; + memcpy(slots[i].password, &DATA[index], sizeof(slots[0].password)); + index += sizeof(slots[0].password); + slots[i].with_enter = DATA[index++]; + break; + + case PASS_SLOT_OATH: + if (index + sizeof(slots[0].oath_offset) + sizeof(slots[0].with_enter) > LC) { + // Not enough data for PASS_SLOT_OATH + EXCEPT(SW_WRONG_DATA); + } + slots[i].type = type; + memcpy(&slots[i].oath_offset, &DATA[index], sizeof(slots[0].oath_offset)); + index += sizeof(slots[0].oath_offset); + slots[i].with_enter = DATA[index++]; + break; + + default: + // Invalid slot type + EXCEPT(SW_WRONG_DATA); + } + } + + if (index != LC) { + // Extra data present that doesn't fit in the slot structure + EXCEPT(SW_WRONG_LENGTH); + } + + return write_file(PASS_FILE, slots, 0, sizeof(slots), 1); +} + +static int oath_process_offset(uint32_t offset, char *output) { + uint32_t otp_code; + int ret = oath_calculate_by_offset(offset, (uint8_t *)&otp_code); + if (ret < 0) return ret; + const int len = ret; + + otp_code = htobe32(otp_code); + while (ret--) { + output[ret] = otp_code % 10 + '0'; + otp_code /= 10; + } + output[len] = '\0'; + + return len; +} + +int pass_handle_touch(uint8_t touch_type, char *output) { + pass_slot_t *slot; + if (touch_type == TOUCH_SHORT) + slot = &slots[SLOT_SHORT]; + else if (touch_type == TOUCH_LONG) + slot = &slots[SLOT_LONG]; + else + return -1; + + int length; + switch (slot->type) { + case PASS_SLOT_OFF: + return 0; + case PASS_SLOT_OATH: + length = oath_process_offset(slot->oath_offset, output); + break; + case PASS_SLOT_STATIC: + memcpy(output, slot->password + 1, slot->password[0]); + length = slot->password[0]; + break; + default: + return -1; + } + + if (slot->with_enter) output[length++] = '\r'; + + return length; +} diff --git a/include/admin.h b/include/admin.h index d54b740c..b5d70b33 100644 --- a/include/admin.h +++ b/include/admin.h @@ -24,20 +24,14 @@ #define ADMIN_INS_VENDOR_SPECIFIC 0xFF #define ADMIN_P1_CFG_LED_ON 0x01 -#define ADMIN_P1_CFG_KBDIFACE 0x03 #define ADMIN_P1_CFG_NDEF 0x04 #define ADMIN_P1_CFG_WEBUSB_LANDING 0x05 -#define ADMIN_P1_CFG_KBD_WITH_RETURN 0x06 #define ADMIN_P1_CFG_PIV_ALGO_EXT 0x07 typedef struct { - uint32_t reserved; uint32_t led_normally_on : 1; - uint32_t unused : 1; - uint32_t kbd_interface_en : 1; uint32_t ndef_en : 1; uint32_t webusb_landing_en : 1; - uint32_t kbd_with_return_en : 1; uint32_t piv_algo_ext_en : 1; } __packed admin_device_config_t; @@ -50,10 +44,8 @@ int admin_vendor_hw_variant(const CAPDU *capdu, RAPDU *rapdu); int admin_vendor_hw_sn(const CAPDU *capdu, RAPDU *rapdu); uint8_t cfg_is_led_normally_on(void); -uint8_t cfg_is_kbd_interface_enable(void); uint8_t cfg_is_ndef_enable(void); uint8_t cfg_is_webusb_landing_enable(void); -uint8_t cfg_is_kbd_with_return_enable(void); uint8_t cfg_is_piv_algo_extension_enable(void); #endif // CANOKEY_CORE_ADMIN_ADMIN_H_ diff --git a/include/oath.h b/include/oath.h index 8fd0195e..fcb379c9 100644 --- a/include/oath.h +++ b/include/oath.h @@ -66,6 +66,6 @@ typedef struct { void oath_poweroff(void); int oath_install(uint8_t reset); int oath_process_apdu(const CAPDU *capdu, RAPDU *rapdu); -int oath_process_one_touch(char *output, size_t maxlen); +int oath_calculate_by_offset(size_t file_offset, uint8_t result[4]); #endif // CANOKEY_CORE_OATH_OATH_H_ diff --git a/include/pass.h b/include/pass.h new file mode 100644 index 00000000..c78ca4ee --- /dev/null +++ b/include/pass.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef CANOKEY_CORE_INCLUDE_PASS_H +#define CANOKEY_CORE_INCLUDE_PASS_H + +#include + +#define PASS_MAX_PASSWORD_LENGTH 32 + +typedef enum { + PASS_SLOT_OFF, + PASS_SLOT_OATH, + PASS_SLOT_STATIC, +} slot_type_t; + +typedef struct { + slot_type_t type; + union { + uint8_t password[33]; // 1-byte length + at most 32-byte content + uint32_t oath_offset; + }; + uint8_t with_enter; +} __packed pass_slot_t; + +int pass_install(uint8_t reset); +int pass_read_config(const CAPDU *capdu, RAPDU *rapdu); +int pass_write_config(const CAPDU *capdu, RAPDU *rapdu); +int pass_handle_touch(uint8_t touch_type, char *output); + +#endif // CANOKEY_CORE_INCLUDE_PASS_H diff --git a/interfaces/USB/class/kbdhid/kbdhid.c b/interfaces/USB/class/kbdhid/kbdhid.c index 86db1c08..4b040959 100644 --- a/interfaces/USB/class/kbdhid/kbdhid.c +++ b/interfaces/USB/class/kbdhid/kbdhid.c @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include @@ -13,51 +13,57 @@ static enum { KBDHID_KeyDown, KBDHID_KeyUp, } state; -static char key_sequence[8 + 2]; +static char key_sequence[PASS_MAX_PASSWORD_LENGTH + 2]; // one for enter and one for '\0' static uint8_t key_seq_position; static keyboard_report_t report; -static uint32_t last_sent; static uint8_t ascii2keycode(char ch) { + // digits and letters if ('1' <= ch && ch <= '9') return 30 + ch - '1'; - else if ('0' == ch) - return 30 + 9; - else if ('\r' == ch) - return 40; - else if ('a' <= ch && ch <= 'z') + if ('0' == ch) + return 39; + if ('a' <= ch && ch <= 'z') return 4 + ch - 'a'; - else if ('-' == ch) - return 0x2d; - else - return 0; // do not support non-digits for now -} + if ('A' <= ch && ch <= 'Z') + return 4 + ch - 'A'; -static void KBDHID_UserTouchHandle(void) { - int len = 0; - memset(key_sequence, 0, sizeof(key_sequence)); - const int ret = oath_process_one_touch(key_sequence, sizeof(key_sequence)); - if (ret < 0) { - ERR_MSG("Failed to get the OTP code: %d\n", ret); - if (ret == -2) { - memcpy(key_sequence, "not-set", 7); - len = 7; - } else { - memcpy(key_sequence, "error", 5); - len = 5; - } - } else { - for (size_t i = 0; i < sizeof(key_sequence) - 1; i++) { - if (key_sequence[i] == '\0') { - len = i; - break; - } - } + switch(ch) { + case 32: return 0x2C; // space + case 33: return 0x1E; // ! + case 34: return 0x34; // " + case 35: return 0x20; // # + case 36: return 0x21; // $ + case 37: return 0x22; // % + case 38: return 0x24; // & + case 39: return 0x34; // ' + case 40: return 0x26; // ( + case 41: return 0x27; // ) + case 42: return 0x25; // * + case 43: return 0x2E; // + + case 44: return 0x36; // , + case 45: return 0x2D; // - + case 46: return 0x37; // . + case 47: return 0x38; // / + case 58: return 0x33; // : + case 59: return 0x33; // ; + case 60: return 0x36; // < + case 61: return 0x2E; // = + case 62: return 0x37; // > + case 63: return 0x38; // ? + case 64: return 0x1F; // @ + case 91: return 0x2F; // [ + case 92: return 0x31; // "\" + case 93: return 0x30; // ] + case 94: return 0x23; // ^ + case 95: return 0x2D; // _ + case 96: return 0x35; // ` + case 123: return 0x2F; // { + case 124: return 0x31; // | + case 125: return 0x30; // } + case 126: return 0x35; // ~ + default: return 0; // undefined } - if (cfg_is_kbd_with_return_enable()) key_sequence[len] = '\r'; - key_seq_position = 0; - state = KBDHID_Typing; - DBG_MSG("Start typing %s", key_sequence); } static void KBDHID_TypeKeySeq(void) { @@ -90,18 +96,28 @@ static void KBDHID_TypeKeySeq(void) { } uint8_t KBDHID_Init() { - last_sent = 0; memset(&report, 0, sizeof(report)); state = KBDHID_Idle; return 0; } uint8_t KBDHID_Loop(void) { - if (get_touch_result() == TOUCH_SHORT && state == KBDHID_Idle && device_get_tick() - last_sent > 1000) { - KBDHID_UserTouchHandle(); - last_sent = device_get_tick(); + if (state == KBDHID_Idle) { + const uint8_t touch = get_touch_result(); + if (touch != TOUCH_NO) { + const int len = pass_handle_touch(touch, key_sequence); + if (len <= 0) { + DBG_MSG("HID: do nothing\n"); + return 0; + } + key_sequence[len] = 0; + key_seq_position = 0; + state = KBDHID_Typing; + DBG_MSG("Start typing %s", key_sequence); + } set_touch_result(TOUCH_NO); + } else { + KBDHID_TypeKeySeq(); } - if (state != KBDHID_Idle) KBDHID_TypeKeySeq(); return 0; } diff --git a/src/device.c b/src/device.c index 01cdecd8..b3d1fcf9 100644 --- a/src/device.c +++ b/src/device.c @@ -22,7 +22,7 @@ void device_loop(uint8_t has_touch) { WebUSB_Loop(); if (has_touch && // hardware features the touch pad !device_is_blinking() && // applets are not waiting for touch - cfg_is_kbd_interface_enable() // keyboard emulation enabled + device_get_tick() > 2000 // ignore touch for the first 2 seconds ) KBDHID_Loop(); }