From 8af54dae051fd3a638a43fe3e1ede0d150d9949e Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Mon, 1 Jan 2024 10:29:43 +0800 Subject: [PATCH 1/7] 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(); } From 38314bb1a8be9ea97681aabe69dc6a74b370163c Mon Sep 17 00:00:00 2001 From: dangfan Date: Mon, 1 Jan 2024 17:18:56 +0800 Subject: [PATCH 2/7] add config commands for pass --- applets/admin/admin.c | 12 ++++++++++++ applets/pass/pass.c | 32 ++++++++++++++++++++++++-------- include/admin.h | 3 +++ include/pass.h | 15 --------------- src/applets.c | 2 ++ 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/applets/admin/admin.c b/applets/admin/admin.c index 16218073..45a8a933 100644 --- a/applets/admin/admin.c +++ b/applets/admin/admin.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -180,6 +181,8 @@ static int admin_factory_reset(const CAPDU *capdu, RAPDU *rapdu) { if (ret < 0) return ret; ret = ndef_install(1); if (ret < 0) return ret; + ret = pass_install(1); + if (ret < 0) return ret; ret = admin_install(1); if (ret < 0) return ret; return 0; @@ -254,6 +257,9 @@ int admin_process_apdu(const CAPDU *capdu, RAPDU *rapdu) { case ADMIN_INS_TOGGLE_NDEF_READ_ONLY: ret = ndef_toggle_read_only(capdu, rapdu); break; + case ADMIN_INS_RESET_PASS: + ret = pass_install(1); + break; case ADMIN_INS_CHANGE_PIN: ret = admin_change_pin(capdu, rapdu); break; @@ -269,6 +275,12 @@ int admin_process_apdu(const CAPDU *capdu, RAPDU *rapdu) { case ADMIN_INS_READ_CONFIG: ret = admin_read_config(capdu, rapdu); break; + case ADMIN_INS_READ_PASS_CONFIG: + ret = pass_read_config(capdu, rapdu); + break; + case ADMIN_INS_WRITE_PASS_CONFIG: + ret = pass_write_config(capdu, rapdu); + break; case ADMIN_INS_VENDOR_SPECIFIC: ret = admin_vendor_specific(capdu, rapdu); break; diff --git a/applets/pass/pass.c b/applets/pass/pass.c index 22ed9383..a6d88417 100644 --- a/applets/pass/pass.c +++ b/applets/pass/pass.c @@ -9,6 +9,21 @@ #define SLOT_SHORT 0 #define SLOT_LONG 1 +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; + static pass_slot_t slots[2]; int pass_install(const uint8_t reset) { @@ -32,17 +47,18 @@ static int dump_slot(const pass_slot_t *slot, uint8_t *buffer) { switch (slot->type) { case PASS_SLOT_OFF: + break; + case PASS_SLOT_STATIC: - // For OFF and STATIC, the second byte is with_enter - buffer[1] = slot->with_enter; - length++; + // For STATIC, the second byte is with_enter + buffer[length++] = slot->with_enter; 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; + memcpy(&buffer[length], &slot->oath_offset, sizeof(slot->oath_offset)); + length += sizeof(slot->oath_offset); + buffer[length++] = slot->with_enter; break; } @@ -81,8 +97,8 @@ int pass_write_config(const CAPDU *capdu, RAPDU *rapdu) { EXCEPT(SW_WRONG_DATA); } slots[i].type = type; - memcpy(slots[i].password, &DATA[index], sizeof(slots[0].password)); - index += sizeof(slots[0].password); + memcpy(slots[i].password, &DATA[index], DATA[index]); + index += DATA[index]; slots[i].with_enter = DATA[index++]; break; diff --git a/include/admin.h b/include/admin.h index b5d70b33..bdc887c6 100644 --- a/include/admin.h +++ b/include/admin.h @@ -11,6 +11,7 @@ #define ADMIN_INS_RESET_OATH 0x05 #define ADMIN_INS_RESET_NDEF 0x07 #define ADMIN_INS_TOGGLE_NDEF_READ_ONLY 0x08 +#define ADMIN_INS_RESET_PASS 0x13 #define ADMIN_INS_VERIFY 0x20 #define ADMIN_INS_CHANGE_PIN 0x21 #define ADMIN_INS_WRITE_SN 0x30 @@ -19,6 +20,8 @@ #define ADMIN_INS_CONFIG 0x40 #define ADMIN_INS_FLASH_USAGE 0x41 #define ADMIN_INS_READ_CONFIG 0x42 +#define ADMIN_INS_READ_PASS_CONFIG 0x43 +#define ADMIN_INS_WRITE_PASS_CONFIG 0x44 #define ADMIN_INS_FACTORY_RESET 0x50 #define ADMIN_INS_SELECT 0xA4 #define ADMIN_INS_VENDOR_SPECIFIC 0xFF diff --git a/include/pass.h b/include/pass.h index c78ca4ee..9aaab1d0 100644 --- a/include/pass.h +++ b/include/pass.h @@ -6,21 +6,6 @@ #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); diff --git a/src/applets.c b/src/applets.c index b73855b9..e91872e7 100644 --- a/src/applets.c +++ b/src/applets.c @@ -5,6 +5,7 @@ #include #include #include +#include #include void applets_install(void) { @@ -14,6 +15,7 @@ void applets_install(void) { ctap_install(0); admin_install(0); ndef_install(0); + pass_install(0); } void applets_poweroff(void) { From 53049cbf17769e8b22e704426d0d17818d894b10 Mon Sep 17 00:00:00 2001 From: dangfan Date: Mon, 1 Jan 2024 18:43:33 +0800 Subject: [PATCH 3/7] fix init error and length error --- applets/pass/pass.c | 11 +++++------ interfaces/USB/class/kbdhid/kbdhid.c | 3 ++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/applets/pass/pass.c b/applets/pass/pass.c index a6d88417..8845c409 100644 --- a/applets/pass/pass.c +++ b/applets/pass/pass.c @@ -33,7 +33,7 @@ int pass_install(const uint8_t reset) { } memzero(slots, sizeof(slots)); - if (write_file(PASS_FILE, slots, 0, 0, 1) < 0) return -1; + if (write_file(PASS_FILE, slots, 0, sizeof(slots), 1) < 0) return -1; return 0; } @@ -91,14 +91,13 @@ int pass_write_config(const CAPDU *capdu, RAPDU *rapdu) { 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 + if (DATA[index] > PASS_MAX_PASSWORD_LENGTH) { + // Password is too long EXCEPT(SW_WRONG_DATA); } slots[i].type = type; - memcpy(slots[i].password, &DATA[index], DATA[index]); - index += DATA[index]; + memcpy(slots[i].password, &DATA[index], DATA[index] + 1); + index += DATA[index] + 1; slots[i].with_enter = DATA[index++]; break; diff --git a/interfaces/USB/class/kbdhid/kbdhid.c b/interfaces/USB/class/kbdhid/kbdhid.c index 4b040959..ba39ac46 100644 --- a/interfaces/USB/class/kbdhid/kbdhid.c +++ b/interfaces/USB/class/kbdhid/kbdhid.c @@ -29,6 +29,7 @@ static uint8_t ascii2keycode(char ch) { return 4 + ch - 'A'; switch(ch) { + case 13: return 0x28; // \r case 32: return 0x2C; // space case 33: return 0x1E; // ! case 34: return 0x34; // " @@ -107,7 +108,7 @@ uint8_t KBDHID_Loop(void) { if (touch != TOUCH_NO) { const int len = pass_handle_touch(touch, key_sequence); if (len <= 0) { - DBG_MSG("HID: do nothing\n"); + DBG_MSG("Do nothing\n"); return 0; } key_sequence[len] = 0; From 578c5dd1ae9ac677a106574cdfda8ebbbaa8897b Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Tue, 2 Jan 2024 23:37:38 +0800 Subject: [PATCH 4/7] revert admin_read_config --- applets/admin/admin.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/applets/admin/admin.c b/applets/admin/admin.c index 0767df8a..0127ad49 100644 --- a/applets/admin/admin.c +++ b/applets/admin/admin.c @@ -133,9 +133,11 @@ 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] = ndef_get_read_only(); - RDATA[2] = current_config.ndef_en; - RDATA[3] = current_config.webusb_landing_en; + RDATA[1] = 0; // reserved + RDATA[2] = ndef_get_read_only(); + RDATA[3] = current_config.ndef_en; + RDATA[4] = current_config.webusb_landing_en; + RDATA[5] = 0; // reserved LL = 6; return 0; From 3859c77f4f0a6e39a72343774c3a858cb3428ca8 Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Tue, 9 Jan 2024 09:38:26 +0800 Subject: [PATCH 5/7] make union packed in pass_slot_t --- applets/pass/pass.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applets/pass/pass.c b/applets/pass/pass.c index 8845c409..fc31c2aa 100644 --- a/applets/pass/pass.c +++ b/applets/pass/pass.c @@ -20,7 +20,7 @@ typedef struct { union { uint8_t password[33]; // 1-byte length + at most 32-byte content uint32_t oath_offset; - }; + } __packed; uint8_t with_enter; } __packed pass_slot_t; From d7ada3fe0f4128a13a72e8d8c4a4dfd222573ecf Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Wed, 10 Jan 2024 21:56:47 +0800 Subject: [PATCH 6/7] fix missing break and sw --- applets/admin/admin.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applets/admin/admin.c b/applets/admin/admin.c index 0127ad49..2384edaf 100644 --- a/applets/admin/admin.c +++ b/applets/admin/admin.c @@ -182,6 +182,7 @@ static int admin_factory_reset(const CAPDU *capdu, RAPDU *rapdu) { if (ret < 0) return ret; ret = admin_install(1); if (ret < 0) return ret; + return 0; } @@ -256,6 +257,7 @@ int admin_process_apdu(const CAPDU *capdu, RAPDU *rapdu) { break; case ADMIN_INS_RESET_PASS: ret = pass_install(1); + break; case ADMIN_INS_RESET_CTAP: ret = ctap_install(1); break; From f82ec90346095069f07906449111123a6dd9321b Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Thu, 11 Jan 2024 22:31:06 +0800 Subject: [PATCH 7/7] wip: oath config --- applets/oath/oath.c | 24 ++++----- applets/pass/pass.c | 117 +++++++++++++++++++++++--------------------- include/oath.h | 1 - include/pass.h | 1 + 4 files changed, 75 insertions(+), 68 deletions(-) diff --git a/applets/oath/oath.c b/applets/oath/oath.c index 748cc953..b51c9016 100644 --- a/applets/oath/oath.c +++ b/applets/oath/oath.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -30,8 +31,6 @@ int oath_install(const uint8_t reset) { oath_poweroff(); if (!reset && get_file_size(OATH_FILE) >= 0) return 0; if (write_file(OATH_FILE, NULL, 0, 0, 1) < 0) return -1; - const uint32_t default_item = 0xffffffff; - if (write_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &default_item, sizeof(default_item)) < 0) return -1; if (write_attr(OATH_FILE, ATTR_KEY, NULL, 0) < 0) return -1; uint8_t handle[HANDLE_LEN]; random_buffer(handle, sizeof(handle)); @@ -166,12 +165,13 @@ static int oath_delete(const CAPDU *capdu, RAPDU *rapdu) { for (size_t i = 0; i != n_records; ++i) { if (read_file(OATH_FILE, &record, i * sizeof(OATH_RECORD), sizeof(OATH_RECORD)) < 0) return -1; if (record.name_len == name_len && memcmp(record.name, name_ptr, name_len) == 0) { - uint32_t default_item; - if (read_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &default_item, sizeof(default_item)) < 0) return -1; - if (default_item == i) { // clear the default set if it is to be deleted - default_item = 0xffffffff; - if (write_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &default_item, sizeof(default_item)) < 0) return -1; - } + // uint32_t default_item; + // if (read_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &default_item, sizeof(default_item)) < 0) return -1; + // if (default_item == i) { // clear the default set if it is to be deleted + // default_item = 0xffffffff; + // if (write_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &default_item, sizeof(default_item)) < 0) return -1; + // } + // TODO: delete pass config record.name_len = 0; return write_file(OATH_FILE, &record, i * sizeof(OATH_RECORD), sizeof(OATH_RECORD), 0); @@ -417,7 +417,8 @@ int oath_calculate_by_offset(size_t file_offset, uint8_t result[4]) { } static int oath_set_default(const CAPDU *capdu, RAPDU *rapdu) { - if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (P1 != 0x01 && P1 != 0x02) EXCEPT(SW_WRONG_P1P2); + if (P2 != 0x00) EXCEPT(SW_WRONG_P1P2); uint16_t offset = 0; if (offset + 1 >= LC) EXCEPT(SW_WRONG_LENGTH); @@ -433,7 +434,7 @@ static int oath_set_default(const CAPDU *capdu, RAPDU *rapdu) { if (size < 0) return -1; const uint32_t n_records = size / sizeof(OATH_RECORD); uint32_t i; - uint32_t file_offset; + uint32_t file_offset = 0; OATH_RECORD record; for (i = 0; i != n_records; ++i) { file_offset = i * sizeof(OATH_RECORD); @@ -443,8 +444,7 @@ static int oath_set_default(const CAPDU *capdu, RAPDU *rapdu) { if (i == n_records) EXCEPT(SW_DATA_INVALID); if ((record.key[0] & OATH_TYPE_MASK) == OATH_TYPE_TOTP) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); - if (write_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &file_offset, sizeof(file_offset)) < 0) return -1; - return 0; + return pass_update_oath(P1 -1, file_offset, record.name_len, record.name); } static int oath_calculate(const CAPDU *capdu, RAPDU *rapdu) { diff --git a/applets/pass/pass.c b/applets/pass/pass.c index fc31c2aa..09c9b380 100644 --- a/applets/pass/pass.c +++ b/applets/pass/pass.c @@ -18,26 +18,37 @@ typedef enum { typedef struct { slot_type_t type; union { - uint8_t password[33]; // 1-byte length + at most 32-byte content - uint32_t oath_offset; - } __packed; + struct { + uint8_t password_len; + uint8_t password[PASS_MAX_PASSWORD_LENGTH]; + } __packed; + struct { + uint32_t oath_offset; + uint8_t name_len; + uint8_t name[MAX_NAME_LEN]; + } __packed; + }; uint8_t with_enter; } __packed pass_slot_t; static pass_slot_t slots[2]; int pass_install(const uint8_t reset) { - if (!reset && get_file_size(PASS_FILE) >= 0) { + if (reset || get_file_size(PASS_FILE) != sizeof(slots)) { + memzero(slots, sizeof(slots)); + if (write_file(PASS_FILE, slots, 0, sizeof(slots), 1) < 0) return -1; + } else { 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, sizeof(slots), 1) < 0) return -1; - return 0; } +// Dump slots to buffer, return the length of the buffer +// For each slot, the first byte is the type. +// For PASS_SLOT_OFF, there is no more data +// For PASS_SLOT_STATIC, the second byte is with_enter +// For PASS_SLOT_OATH, the next byte is the length of the name, followed by the name, and the next byte is with_enter static int dump_slot(const pass_slot_t *slot, uint8_t *buffer) { int length = 0; @@ -55,9 +66,12 @@ static int dump_slot(const pass_slot_t *slot, uint8_t *buffer) { break; case PASS_SLOT_OATH: - // For OATH, the next 4 bytes are oath_offset - memcpy(&buffer[length], &slot->oath_offset, sizeof(slot->oath_offset)); - length += sizeof(slot->oath_offset); + // For OATH, the second byte is the length of the name + buffer[length++] = slot->name_len; + // The next bytes are the name + memcpy(buffer + length, slot->name, slot->name_len); + length += slot->name_len; + // The next byte is with_enter buffer[length++] = slot->with_enter; break; } @@ -75,57 +89,50 @@ int pass_read_config(const CAPDU *capdu, RAPDU *rapdu) { return 0; } +// P1 for the slot index, 1 for short slot, 2 for long slot +// DATA contains the slot data: +// The first byte is the slot type +// For OFF, there is no more data +// For STATIC, the second byte is the length of the password, followed by the password, and the next byte is with_enter +// OATH is not allowed to be written here 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 (DATA[index] > PASS_MAX_PASSWORD_LENGTH) { - // Password is too long - EXCEPT(SW_WRONG_DATA); - } - slots[i].type = type; - memcpy(slots[i].password, &DATA[index], DATA[index] + 1); - index += DATA[index] + 1; - 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 (P1 != 1 && P1 != 2) EXCEPT(SW_WRONG_P1P2); + if (LC < 1) EXCEPT(SW_WRONG_LENGTH); + + pass_slot_t *slot = &slots[P1 - 1]; + slot->type = (slot_type_t)DATA[0]; + + switch (slot->type) { + case PASS_SLOT_OFF: + if (LC != 1) EXCEPT(SW_WRONG_LENGTH); + break; - if (index != LC) { - // Extra data present that doesn't fit in the slot structure - EXCEPT(SW_WRONG_LENGTH); + case PASS_SLOT_STATIC: + if (LC < 3) EXCEPT(SW_WRONG_LENGTH); + slot->password_len = DATA[1]; + if (slot->password_len > PASS_MAX_PASSWORD_LENGTH) EXCEPT(SW_WRONG_LENGTH); + memcpy(slot->password, DATA + 2, slot->password_len); + slot->with_enter = DATA[2 + slot->password_len]; + if (LC != 3 + slot->password_len) EXCEPT(SW_WRONG_LENGTH); + break; + + default: + EXCEPT(SW_WRONG_DATA); } return write_file(PASS_FILE, slots, 0, sizeof(slots), 1); } +int pass_update_oath(uint8_t slot_index, uint32_t offset, uint8_t name_len, const uint8_t *name) { + pass_slot_t *slot = &slots[slot_index]; + slot->type = PASS_SLOT_OATH; + slot->oath_offset = offset; + slot->name_len = name_len; + memcpy(slot->name, name, name_len); + + 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); diff --git a/include/oath.h b/include/oath.h index fcb379c9..37806607 100644 --- a/include/oath.h +++ b/include/oath.h @@ -4,7 +4,6 @@ #include -#define ATTR_DEFAULT_RECORD 0x01 #define ATTR_KEY 0x02 #define ATTR_HANDLE 0x03 diff --git a/include/pass.h b/include/pass.h index 9aaab1d0..8e4f86ba 100644 --- a/include/pass.h +++ b/include/pass.h @@ -10,5 +10,6 @@ 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); +int pass_update_oath(uint8_t slot_index, uint32_t offset, uint8_t name_len, const uint8_t *name); #endif // CANOKEY_CORE_INCLUDE_PASS_H