From 7ed90007ef7003c5d9b20bd0af8dcdd243a8303e Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 19 Jan 2026 16:37:19 +0100 Subject: [PATCH] Add support for slots 3 & 4 in OTP. Both slots are activated by clicking three or four times the BOOTSEL button. Signed-off-by: Pol Henarejos --- src/fido/files.h | 2 ++ src/fido/otp.c | 81 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/src/fido/files.h b/src/fido/files.h index 1fe844b..715a306 100644 --- a/src/fido/files.h +++ b/src/fido/files.h @@ -40,6 +40,8 @@ #define EF_OATH_CODE 0xBAFF #define EF_OTP_SLOT1 0xBB00 #define EF_OTP_SLOT2 0xBB01 +#define EF_OTP_SLOT3 0xBB02 +#define EF_OTP_SLOT4 0xBB03 #define EF_OTP_PIN 0x10A0 // Nitrokey OTP PIN extern file_t *ef_keydev; diff --git a/src/fido/otp.c b/src/fido/otp.c index 29d7da8..3cc5811 100644 --- a/src/fido/otp.c +++ b/src/fido/otp.c @@ -49,6 +49,11 @@ void append_keyboard_buffer(const uint8_t *buf, size_t len) {} #define CONFIG_LED_INV 0x10 #define CONFIG_STATUS_MASK 0x1f +#define CONFIG3_VALID 0x01 +#define CONFIG4_VALID 0x02 +#define CONFIG3_TOUCH 0x04 +#define CONFIG4_TOUCH 0x08 + /* EXT Flags */ #define SERIAL_BTN_VISIBLE 0x01 // Serial number visible at startup (button press) #define SERIAL_USB_VISIBLE 0x02 // Serial number visible in USB iSerial field @@ -161,7 +166,7 @@ extern void scan_all(); void init_otp() { if (scanned == false) { scan_all(); - for (uint8_t i = 0; i < 2; i++) { + for (uint8_t i = 0; i < 4; i++) { file_t *ef = search_dynamic_file(EF_OTP_SLOT1 + i); uint8_t *data = file_get_data(ef); otp_config_t *otp_config = (otp_config_t *) data; @@ -207,7 +212,8 @@ int otp_button_pressed(uint8_t slot) { if (!cap_supported(CAP_OTP)) { return 3; } - file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2); + uint16_t slot_ef = EF_OTP_SLOT1 + slot - 1; + file_t *ef = search_dynamic_file(slot_ef); const uint8_t *data = file_get_data(ef); otp_config_t *otp_config = (otp_config_t *) data; if (file_has_data(ef) == false) { @@ -333,6 +339,39 @@ int otp_unload() { } uint8_t status_byte = 0x0; +uint16_t otp_status_ext() { + for (int i = 0; i < 4; i++) { + file_t *ef = search_dynamic_file(EF_OTP_SLOT1 + i); + if (file_has_data(ef)) { + res_APDU[res_APDU_size++] = 0xB0 + i; + res_APDU[res_APDU_size++] = 0; // Filled later + uint8_t *p = res_APDU + res_APDU_size; + otp_config_t *otp_config = (otp_config_t *)file_get_data(ef); + *p++ = 0xA0; + *p++ = 2; + *p++ = otp_config->tkt_flags; + *p++ = otp_config->cfg_flags; + if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) { + + } + else if (otp_config->tkt_flags & OATH_HOTP) { + } + else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) { + } + else { + *p++ = 0xC0; + *p++ = 6; + memcpy(p, otp_config->fixed_data, 6); + p += 6; + } + uint8_t len = p - (res_APDU + res_APDU_size); + res_APDU[res_APDU_size - 1] = len; + res_APDU_size += len; + } + } + return SW_OK(); +} + uint16_t otp_status(bool is_otp) { if (scanned == false) { scan_all(); @@ -384,12 +423,13 @@ bool check_crc(const otp_config_t *data) { bool _is_otp = false; int cmd_otp() { uint8_t p1 = P1(apdu), p2 = P2(apdu); - if (p2 != 0x00) { - return SW_INCORRECT_P1P2(); - } if (p1 == 0x01 || p1 == 0x03) { // Configure slot otp_config_t *odata = (otp_config_t *) apdu.data; - file_t *ef = file_new(p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2); + if (p1 == 0x03 && p2 != 0x0) { + return SW_INCORRECT_P1P2(); + } + uint16_t slot = (p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2; + file_t *ef = file_new(slot); if (file_has_data(ef)) { otp_config_t *otpc = (otp_config_t *) file_get_data(ef); if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) { @@ -415,10 +455,14 @@ int cmd_otp() { } else if (p1 == 0x04 || p1 == 0x05) { // Update slot otp_config_t *odata = (otp_config_t *) apdu.data; + if (p1 == 0x05 && p2 != 0x0) { + return SW_INCORRECT_P1P2(); + } + uint16_t slot = (p1 == 0x04 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2; if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) { return SW_WRONG_DATA(); } - file_t *ef = search_dynamic_file(p1 == 0x04 ? EF_OTP_SLOT1 : EF_OTP_SLOT2); + file_t *ef = search_dynamic_file(slot); if (file_has_data(ef)) { otp_config_t *otpc = (otp_config_t *) file_get_data(ef); if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) { @@ -446,8 +490,16 @@ int cmd_otp() { else if (p1 == 0x06) { // Swap slots uint8_t tmp[otp_config_size + 8]; bool ef1_data = false; - file_t *ef1 = file_new(EF_OTP_SLOT1); - file_t *ef2 = file_new(EF_OTP_SLOT2); + uint16_t slot1 = EF_OTP_SLOT1, slot2 = EF_OTP_SLOT2; + if (apdu.ne > 0) { + if (apdu.ne != 2) { + return SW_WRONG_LENGTH(); + } + slot1 += apdu.data[0]; + slot2 += apdu.data[1]; + } + file_t *ef1 = file_new(slot1); + file_t *ef2 = file_new(slot2); if (file_has_data(ef1)) { memcpy(tmp, file_get_data(ef1), file_get_size(ef1)); ef1_data = true; @@ -458,7 +510,7 @@ int cmd_otp() { else { delete_file(ef1); // When a dynamic file is deleted, existing referenes are invalidated - ef2 = file_new(EF_OTP_SLOT2); + ef2 = file_new(slot2); } if (ef1_data) { file_put_data(ef2, tmp, sizeof(tmp)); @@ -478,8 +530,15 @@ int cmd_otp() { else if (p1 == 0x13) { // Get config man_get_config(); } + else if (p1 == 0x14) { + otp_status_ext(); + } else if (p1 == 0x30 || p1 == 0x38 || p1 == 0x20 || p1 == 0x28) { // Calculate OTP - file_t *ef = search_dynamic_file(p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2); + if ((p1 == 0x38 || p1 == 0x28) && p2 != 0x0) { + return SW_INCORRECT_P1P2(); + } + uint16_t slot = (p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2; + file_t *ef = search_dynamic_file(slot); if (file_has_data(ef)) { otp_config_t *otp_config = (otp_config_t *) file_get_data(ef); if (!(otp_config->tkt_flags & CHAL_RESP)) {