diff --git a/src/u2f_device.c b/src/u2f_device.c index 01cfdce1..bdd97bb7 100644 --- a/src/u2f_device.c +++ b/src/u2f_device.c @@ -25,6 +25,7 @@ */ +#include #include #include "bip32.h" @@ -65,7 +66,7 @@ static uint32_t _cid = 0; volatile bool _state_continue = false; volatile uint16_t _current_time_ms = 0; -const uint8_t _hijack_code[U2F_HIJACK_ORIGIN_TOTAL][U2F_APPID_SIZE] = { +const uint8_t U2F_HIJACK_CODE[U2F_HIJACK_ORIGIN_TOTAL][U2F_APPID_SIZE] = { { /* Corresponds to U2F client challenge filled with `0xdb` */ /* Origin `https://digitalbitbox.com` */ @@ -94,15 +95,6 @@ const uint8_t _hijack_code[U2F_HIJACK_ORIGIN_TOTAL][U2F_APPID_SIZE] = { } }; -typedef enum HIJACK_STATE { - // Do not change the order! - // Order affects third party integrations that make use of the hijack mode - HIJACK_STATE_RESPONSE_READY, - HIJACK_STATE_PROCESSING_COMMAND, - HIJACK_STATE_INCOMPLETE_COMMAND, - HIJACK_STATE_IDLE, -} HIJACK_STATE; - typedef struct { uint8_t reserved; uint8_t appId[U2F_APPID_SIZE]; @@ -297,7 +289,7 @@ static void _hijack(const U2F_AUTHENTICATE_REQ *req) static char hijack_io_buffer[COMMANDER_REPORT_SIZE] = {0}; char byte_report[U2F_FRAME_SIZE + 1] = {0}; uint16_t report_len; - size_t kh_len = MIN(U2F_MAX_KH_SIZE - 2, strlens((const char *)req->keyHandle + 2)); + int kh_len = MIN(U2F_MAX_KH_SIZE - 2, strlens((const char *)req->keyHandle + 2)); uint8_t tot = req->keyHandle[0]; uint8_t cnt = req->keyHandle[1]; size_t idx = cnt * (U2F_MAX_KH_SIZE - 2); @@ -369,7 +361,7 @@ static void _authenticate(const USB_APDU *a) for (i = 0; i < U2F_HIJACK_ORIGIN_TOTAL; i++) { // As an alternative interface, hijack the U2F AUTH key handle data field. // Slower but works in browsers for specified sites without requiring an extension. - if (MEMEQ(req->appId, _hijack_code[i], U2F_APPID_SIZE)) { + if (MEMEQ(req->appId, U2F_HIJACK_CODE[i], U2F_APPID_SIZE)) { if (!(memory_report_ext_flags() & MEM_EXT_MASK_U2F_HIJACK)) { // Abort U2F hijack commands if the U2F_hijack bit is not set (== disabled). u2f_queue_error_hid(_cid, U2FHID_ERR_CHANNEL_BUSY); diff --git a/src/u2f_device.h b/src/u2f_device.h index 24beff01..df846ef7 100644 --- a/src/u2f_device.h +++ b/src/u2f_device.h @@ -40,6 +40,15 @@ extern const uint8_t U2F_HIJACK_CODE[U2F_HIJACK_ORIGIN_TOTAL][U2F_APPID_SIZE]; +typedef enum HIJACK_STATE { + // Do not change the order! + // Order affects third party integrations that make use of the hijack mode + HIJACK_STATE_RESPONSE_READY, + HIJACK_STATE_PROCESSING_COMMAND, + HIJACK_STATE_INCOMPLETE_COMMAND, + HIJACK_STATE_IDLE, +} HIJACK_STATE; + void u2f_queue_message(const uint8_t *data, const uint32_t len); void u2f_queue_error_hid(uint32_t fcid, uint8_t err); diff --git a/tests/api.h b/tests/api.h index eda78f2f..3d13b136 100644 --- a/tests/api.h +++ b/tests/api.h @@ -326,31 +326,6 @@ static int api_hid_init(void) #endif -static void api_hid_read(uint8_t *key) -{ - int res; - int u2fhid_cmd = TEST_U2FAUTH_HIJACK ? U2FHID_MSG : U2FHID_HWW; - memset(HID_REPORT, 0, HID_REPORT_SIZE); - res = api_hid_read_frames(HWW_CID, u2fhid_cmd, HID_REPORT, HID_REPORT_SIZE); - if (res < 0) { - strcpy(decrypted_report, "/* " API_READ_ERROR " */"); - return; - } - if (TEST_U2FAUTH_HIJACK) { - // If the hijack command was sent in chunks, the first chunks return empty frames. - // The last chunk holds the JSON response. So poll until get a non-empty frame. - // First 5 bytes are the frame header. - // Set the appended U2F success byte 0x90 to zero. Otherwise cannot decrypt. - char *r = (char *)(HID_REPORT + 5); - r[strlens(r) - 1] = 0; - strlens(r) ? api_decrypt_report(r, key) : api_hid_read(key); - } else { - api_decrypt_report((char *)HID_REPORT, key); - } - //printf("received: >>%s<<\n", api_read_decrypted_report()); -} - - static void api_hid_send_len(const char *cmd, int cmdlen) { if (TEST_U2FAUTH_HIJACK) { @@ -401,6 +376,50 @@ static void api_hid_send_encrypt(const char *cmd, uint8_t *key) } +static void api_hid_read(uint8_t *key) +{ + int res; + int u2fhid_cmd = TEST_U2FAUTH_HIJACK ? U2FHID_MSG : U2FHID_HWW; + memset(HID_REPORT, 0, HID_REPORT_SIZE); + res = api_hid_read_frames(HWW_CID, u2fhid_cmd, HID_REPORT, HID_REPORT_SIZE); + if (res < 0) { + strcpy(decrypted_report, "/* " API_READ_ERROR " */"); + return; + } + if (TEST_U2FAUTH_HIJACK) { + // If the hijack command was sent in multiple chunks, the first chunks are replied + // with a single-byte (framed) having the value HIJACK_STATE_INCOMPLETE_COMMAND. + // + // After receiving the all chunks, the firware replies with a single-byte + // HIJACK_STATE_PROCESSING_COMMAND. + // + // The client can poll the firmware for the JSON response by sending a single-byte + // (framed) having any value. If the firmware is busy, for example waiting for user touch + // button press, the firmware will reply with a single-byte HIJACK_STATE_PROCESSING_COMMAND. + // If the firmware finished processing, the reply will contain the JSON response. + // + // The first 5 bytes are the frame header. The last two bytes contain the U2F status, + // which should be the success bytes \x90\x00 in order for the U2F hijack approach + // to work in browsers. + char *r = (char *)(HID_REPORT + 1 + U2F_CTR_SIZE); + r[strlens(r) - 1] = 0; + if (strlens(r) == 1) { + if (r[0] == HIJACK_STATE_PROCESSING_COMMAND) { + api_hid_send(" "); + } + api_hid_read(key); + } else if (strlens(r) > 1) { + api_decrypt_report(r, key); + } else { + strcpy(decrypted_report, "/* " API_READ_ERROR " */"); + } + } else { + api_decrypt_report((char *)HID_REPORT, key); + } + //printf("received: >>%s<<\n", api_read_decrypted_report()); +} + + static void api_send_cmd(const char *command, uint8_t *key) { memset(command_sent, 0, sizeof(command_sent)); diff --git a/tests/tests_api.c b/tests/tests_api.c index 6b57a784..3255af00 100644 --- a/tests/tests_api.c +++ b/tests/tests_api.c @@ -1059,6 +1059,8 @@ static void tests_u2f(void) api_format_send_cmd(cmd_str(CMD_backup), "{\"erase\":\"u2f_test_0.pdf\"}", KEY_STANDARD); + ASSERT_SUCCESS; + // U2F command runs api_hid_send_frame(&f); api_hid_read_frame(&r); @@ -1066,9 +1068,11 @@ static void tests_u2f(void) u_assert_int_eq(r.init.cmd, U2FHID_WINK); u_assert_int_eq(r.init.bcntl, 0); - // Disable U2F + // Disable U2F - Must be done through HWW interface. + TEST_U2FAUTH_HIJACK = 0; api_format_send_cmd(cmd_str(CMD_feature_set), "{\"U2F\":false}", KEY_STANDARD); ASSERT_SUCCESS; + TEST_U2FAUTH_HIJACK = test_u2fauth_hijack; api_format_send_cmd(cmd_str(CMD_device), attr_str(ATTR_info), KEY_STANDARD); if (TEST_U2FAUTH_HIJACK) { @@ -1097,9 +1101,11 @@ static void tests_u2f(void) ASSERT_REPORT_HAS("\"U2F_hijack\":true"); - // Disable U2F hijack + // Disable U2F - Must be done through HWW interface. + TEST_U2FAUTH_HIJACK = 0; api_format_send_cmd(cmd_str(CMD_feature_set), "{\"U2F_hijack\":false}", KEY_STANDARD); ASSERT_SUCCESS; + TEST_U2FAUTH_HIJACK = test_u2fauth_hijack; api_format_send_cmd(cmd_str(CMD_device), attr_str(ATTR_info), KEY_STANDARD); if (TEST_U2FAUTH_HIJACK) {