Skip to content

Commit f5d3c49

Browse files
committed
U2F: Fix on safari/macos
U2F is typically a stateless protocol, so between two transactions (request+response) there is not supposed to be any lock held. It was assumed that the "FIDO Client" (typically web browser) would keep sending only U2F `register` or `authenticate` messages when it had started doing so. But it is also legal to send for example a new U2FHID `init` message in between. (the U2F reference test cases do this for example). Before this commit we held a "lock" while the confirm screen was active until the user had confirmed and didn't allow any other messages in between. This breaks the U2F protocol as explained above. After this commit we instead only hold the "lock" for the transaction (request + response), which is the correct way according to the u2f reference: The application channel that manages to get through the first initialization packet when the device is in idle state will keep the device locked for other channels until the last packet of the response message has been received. The device then returns to idle state, ready to perform another transaction for the same or a different channel. Between two transactions, no state is maintained in the device and a host application must assume that any other process may execute other transactions at any time.
1 parent 6c7cc7f commit f5d3c49

File tree

11 files changed

+97
-138
lines changed

11 files changed

+97
-138
lines changed

src/firmware.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
#include "screen.h"
2424
#include "ui/screen_stack.h"
2525

26+
#if APP_U2F == 1
27+
#include <u2f.h>
28+
#endif
29+
2630
uint32_t __stack_chk_guard = 0;
2731

2832
int main(void)
@@ -36,6 +40,9 @@ int main(void)
3640
qtouch_init();
3741
common_main();
3842
bitbox02_smarteeprom_init();
43+
#if APP_U2F == 1
44+
u2f_device_setup();
45+
#endif
3946
firmware_main_loop();
4047
return 0;
4148
}

src/firmware_main_loop.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ void firmware_main_loop(void)
5959
}
6060
if (!u2f_data) {
6161
u2f_data = queue_pull(queue_u2f_queue());
62+
// If USB stack was locked and there is no more messages to send out, time to
63+
// unlock it.
64+
if (!u2f_data && usb_processing_locked(usb_processing_u2f())) {
65+
usb_processing_unlock();
66+
}
6267
}
6368
#endif
6469
// Only read new messages if we have nothing to send

src/u2f.c

Lines changed: 49 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <usb/u2f/u2f_keys.h>
3333
#include <usb/usb_packet.h>
3434
#include <usb/usb_processing.h>
35+
#include <utils_assert.h>
3536
#include <version.h>
3637
#include <wally_crypto.h>
3738

@@ -68,19 +69,14 @@ typedef enum {
6869
} u2f_auth_state_t;
6970

7071
typedef struct {
71-
uint32_t cid;
7272
/**
73-
* Command that is currently executing
74-
* (blocking) on the U2F stack.
73+
* CID and last command are used to check that we eventually respond to the right command after
74+
* the user has confirmed on screen. U2F is normally stateless between transactions, but since
75+
* we have a display and Yes/No input we can ask the user on the first request and later respond
76+
* that the user is present only if the user confirmed specifically that app-id.
7577
*/
78+
uint32_t cid;
7679
uint8_t last_cmd;
77-
/**
78-
* Keeps track of whether there is an outstanding
79-
* U2F operation going on in the background.
80-
* This is not strictly necessary, but it's useful
81-
* to have as a sanity checking mechanism.
82-
*/
83-
bool locked;
8480
/**
8581
* Keeps track of which part of a registration we're currently in.
8682
*/
@@ -131,30 +127,6 @@ static void _clear_state(void)
131127
{
132128
_state.reg = U2F_REGISTER_IDLE;
133129
_state.auth = U2F_AUTHENTICATE_IDLE;
134-
_state.locked = false;
135-
}
136-
137-
/**
138-
* Unlocks the USB stack. Resets the U2F state.
139-
*/
140-
static void _unlock(void)
141-
{
142-
usb_processing_unlock();
143-
_clear_state();
144-
}
145-
146-
/**
147-
* Locks the USB stack to process the given
148-
* U2F request.
149-
*
150-
* @param apdu U2F packet that will own the lock on the USB stack. No other request
151-
* will be processed by the USB stack until this request completes.
152-
*/
153-
static void _lock(const USB_APDU* apdu)
154-
{
155-
usb_processing_lock(usb_processing_u2f());
156-
_state.locked = true;
157-
_state.last_cmd = apdu->ins;
158130
}
159131

160132
static component_t* _nudge_label = NULL;
@@ -183,7 +155,7 @@ static void _start_refresh_webpage_screen(void)
183155
// Unfortunately unlocking takes more time than U2F is allowed to take. With the current
184156
// architecture of the firmware we cannot run it concurrently to other requests. Therefore
185157
// we will call it here, make the user unlock the device and then ask the user to refresh
186-
// the webpage. (refreshing is only needed for some browsers)
158+
// the webpage. (refreshing is only needed for some FIDO Clients)
187159
_state.refresh_webpage = _create_refresh_webpage();
188160
ui_screen_stack_push(_state.refresh_webpage);
189161
_state.refresh_webpage_timeout = 0;
@@ -421,7 +393,7 @@ static void _register_start(const USB_APDU* apdu, Packet* out_packet)
421393
const U2F_REGISTER_REQ* reg_request = (const U2F_REGISTER_REQ*)apdu->data;
422394
uint16_t req_error = _register_sanity_check_req(apdu);
423395
if (req_error) {
424-
_unlock();
396+
_clear_state();
425397
_error(req_error, out_packet);
426398
return;
427399
}
@@ -462,7 +434,7 @@ static void _register_continue(const USB_APDU* apdu, Packet* out_packet)
462434
}
463435

464436
/* No more pending operations for U2F register */
465-
_unlock();
437+
_clear_state();
466438

467439
if (async_result == ASYNC_OP_FALSE) {
468440
_error(U2F_SW_CONDITIONS_NOT_SATISFIED, out_packet);
@@ -582,7 +554,7 @@ static void _authenticate_start(const USB_APDU* apdu, Packet* out_packet)
582554
{
583555
uint16_t req_error = _authenticate_sanity_check_req(apdu);
584556
if (req_error) {
585-
_unlock();
557+
_clear_state();
586558
_error(req_error, out_packet);
587559
return;
588560
}
@@ -596,7 +568,7 @@ static void _authenticate_start(const USB_APDU* apdu, Packet* out_packet)
596568
}
597569
uint16_t key_error = _authenticate_start_confirm(apdu);
598570
if (key_error) {
599-
_unlock();
571+
_clear_state();
600572
_error(key_error, out_packet);
601573
return;
602574
}
@@ -608,7 +580,7 @@ static void _authenticate_wait_refresh(const USB_APDU* apdu, Packet* out_packet)
608580
_assert_unlocked();
609581
uint16_t key_error = _authenticate_start_confirm(apdu);
610582
if (key_error) {
611-
_unlock();
583+
_clear_state();
612584
_error(key_error, out_packet);
613585
return;
614586
}
@@ -624,10 +596,18 @@ static void _authenticate_continue(const USB_APDU* apdu, Packet* out_packet)
624596
U2F_AUTHENTICATE_SIG_STR sig_base;
625597
uint16_t req_error = _authenticate_sanity_check_req(apdu);
626598
if (req_error) {
599+
_clear_state();
627600
_error(req_error, out_packet);
628601
return;
629602
}
630603

604+
uint16_t key_error = _authenticate_verify_key_valid(apdu);
605+
if (key_error) {
606+
_clear_state();
607+
_error(key_error, out_packet);
608+
return;
609+
}
610+
631611
const U2F_AUTHENTICATE_REQ* auth_request = (const U2F_AUTHENTICATE_REQ*)apdu->data;
632612
async_op_result_t async_result =
633613
u2f_app_confirm_retry(U2F_APP_AUTHENTICATE, auth_request->appId);
@@ -637,7 +617,7 @@ static void _authenticate_continue(const USB_APDU* apdu, Packet* out_packet)
637617
}
638618

639619
/* No more blocking operations pending for authentication. */
640-
_unlock();
620+
_clear_state();
641621

642622
if (async_result == ASYNC_OP_FALSE) {
643623
_error(U2F_SW_CONDITIONS_NOT_SATISFIED, out_packet);
@@ -789,12 +769,13 @@ static void _cmd_register(const Packet* in_packet, Packet* out_packet)
789769
/* Sanity-check our state. */
790770
if (_state.reg != U2F_REGISTER_IDLE &&
791771
(_state.last_cmd != U2F_REGISTER || _state.cid != in_packet->cid)) {
792-
Abort("U2F reg arbitration failed.");
772+
util_log("u2f: ERROR register invalid state");
773+
_clear_state();
774+
return;
793775
}
794776

795777
switch (_state.reg) {
796778
case U2F_REGISTER_IDLE:
797-
_lock(apdu);
798779
_register_start(apdu, out_packet);
799780
break;
800781
case U2F_REGISTER_UNLOCKING:
@@ -811,29 +792,6 @@ static void _cmd_register(const Packet* in_packet, Packet* out_packet)
811792
}
812793
}
813794

814-
/**
815-
* Abort an existing registration request.
816-
*/
817-
static void _abort_register(void)
818-
{
819-
switch (_state.reg) {
820-
case U2F_REGISTER_UNLOCKING:
821-
rust_workflow_abort_current();
822-
_clear_state();
823-
break;
824-
case U2F_REGISTER_CONFIRMING:
825-
u2f_app_confirm_abort();
826-
_clear_state();
827-
break;
828-
case U2F_REGISTER_WAIT_REFRESH:
829-
_stop_refresh_webpage_screen();
830-
_clear_state();
831-
break;
832-
default:
833-
Abort("Bad U2F register abort status");
834-
}
835-
}
836-
837795
/**
838796
* Processes an incoming registration request.
839797
*/
@@ -843,12 +801,13 @@ static void _cmd_authenticate(const Packet* in_packet, Packet* out_packet)
843801
/* Sanity-check our state. */
844802
if (_state.auth != U2F_AUTHENTICATE_IDLE &&
845803
(_state.last_cmd != U2F_AUTHENTICATE || _state.cid != in_packet->cid)) {
846-
Abort("U2F auth arbitration failed.");
804+
util_log("u2f: ERROR authenticate invalid state");
805+
_clear_state();
806+
return;
847807
}
848808

849809
switch (_state.auth) {
850810
case U2F_AUTHENTICATE_IDLE:
851-
_lock(apdu);
852811
_authenticate_start(apdu, out_packet);
853812
break;
854813
case U2F_AUTHENTICATE_UNLOCKING:
@@ -865,35 +824,20 @@ static void _cmd_authenticate(const Packet* in_packet, Packet* out_packet)
865824
}
866825
}
867826

868-
/**
869-
* Abort an existing authentication request.
870-
*/
871-
static void _abort_authenticate(void)
872-
{
873-
switch (_state.auth) {
874-
case U2F_AUTHENTICATE_UNLOCKING:
875-
rust_workflow_abort_current();
876-
_clear_state();
877-
break;
878-
case U2F_AUTHENTICATE_CONFIRMING:
879-
u2f_app_confirm_abort();
880-
_clear_state();
881-
break;
882-
case U2F_AUTHENTICATE_WAIT_REFRESH:
883-
_stop_refresh_webpage_screen();
884-
_clear_state();
885-
break;
886-
default:
887-
Abort("Bad U2F register abort status");
888-
}
889-
}
890-
891827
/**
892828
* Process a U2F message
893829
*/
894830
static void _cmd_msg(const Packet* in_packet, Packet* out_packet, const size_t max_out_len)
895831
{
896832
(void)max_out_len;
833+
834+
// Give the user 5s to respond to u2f requests (the FIDO Client in u2f is not required to ping
835+
// every 200ms like the BitBoxApp does even though most implementations do).
836+
usb_processing_timeout_reset(-50);
837+
838+
// The usb stack must be locked until we have responded to this request.
839+
usb_processing_lock(usb_processing_u2f());
840+
897841
// By default always use the recieved cid
898842
_state.cid = in_packet->cid;
899843

@@ -911,33 +855,27 @@ static void _cmd_msg(const Packet* in_packet, Packet* out_packet, const size_t m
911855
switch (apdu->ins) {
912856
case U2F_REGISTER:
913857
_cmd_register(in_packet, out_packet);
858+
_state.last_cmd = apdu->ins;
914859
break;
915860
case U2F_AUTHENTICATE:
916861
_cmd_authenticate(in_packet, out_packet);
862+
_state.last_cmd = apdu->ins;
917863
break;
918864
case U2F_VERSION:
919865
_version(apdu, out_packet);
920866
break;
921867
default:
922868
_error(U2F_SW_INS_NOT_SUPPORTED, out_packet);
923-
return;
869+
break;
924870
}
925871
}
926872

873+
// U2F only locks USB stack between a request and a response. A request is always immediately
874+
// followed by a response. New requests must wait until the current one is finished.
927875
bool u2f_blocking_request_can_go_through(const Packet* in_packet)
928876
{
929-
if (!_state.locked) {
930-
Abort("USB stack thinks we're busy, but we're not.");
931-
}
932-
/*
933-
* Check if this request is the same one we're currently operating on.
934-
* For now, this checks the request type and channel ID only.
935-
* FUTURE: Maybe check that the key handle is maintained between requests?
936-
* so that when we're asking for confirmation, we ask to confirm
937-
* "a particular key handle" instead of "a particular tab".
938-
*/
939-
const USB_APDU* apdu = (const USB_APDU*)in_packet->data_addr;
940-
return apdu->ins == _state.last_cmd && in_packet->cid == _state.cid;
877+
(void)in_packet;
878+
return false;
941879
}
942880

943881
void u2f_blocked_req_error(Packet* out_packet, const Packet* in_packet)
@@ -980,7 +918,7 @@ static void _process_wait_refresh(void)
980918
{
981919
if (_state.refresh_webpage_timeout == 25000) {
982920
_stop_refresh_webpage_screen();
983-
_unlock();
921+
_clear_state();
984922
} else {
985923
_state.refresh_webpage_timeout++;
986924
/* Prevent the USB watchdog from killing this workflow. */
@@ -1024,16 +962,16 @@ static void _process_authenticate(void)
1024962

1025963
void u2f_process(void)
1026964
{
1027-
if (!_state.locked) {
1028-
return;
1029-
}
1030965
switch (_state.last_cmd) {
1031966
case U2F_REGISTER:
1032967
_process_register();
1033968
break;
1034969
case U2F_AUTHENTICATE:
1035970
_process_authenticate();
1036971
break;
972+
case 0:
973+
// initial value
974+
break;
1037975
default:
1038976
Abort("Bad U2F process state.");
1039977
}
@@ -1044,6 +982,8 @@ void u2f_process(void)
1044982
*/
1045983
void u2f_device_setup(void)
1046984
{
985+
_clear_state();
986+
_state.last_cmd = 0;
1047987
const CMD_Callback u2f_cmd_callbacks[] = {
1048988
{U2FHID_PING, _cmd_ping},
1049989
{U2FHID_WINK, _cmd_wink},
@@ -1056,17 +996,5 @@ void u2f_device_setup(void)
1056996

1057997
void u2f_abort_outstanding_op(void)
1058998
{
1059-
if (!_state.locked) {
1060-
Abort("USB stack thinks U2F is busy, but it's not.");
1061-
}
1062-
switch (_state.last_cmd) {
1063-
case U2F_REGISTER:
1064-
_abort_register();
1065-
break;
1066-
case U2F_AUTHENTICATE:
1067-
_abort_authenticate();
1068-
break;
1069-
default:
1070-
Abort("Invalid U2F status on abort.");
1071-
}
999+
util_log("u2f_abort_outstanding_op");
10721000
}

src/u2f/u2f_app.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,9 @@ void u2f_app_confirm_start(enum u2f_app_confirm_t type, const uint8_t* app_id)
219219
async_op_result_t u2f_app_confirm_retry(enum u2f_app_confirm_t type, const uint8_t* app_id)
220220
{
221221
if (_state.outstanding_confirm != type || !MEMEQ(app_id, _state.app_id, 32)) {
222-
Abort("Arbitration failed for U2F confirmation.");
222+
// CID used a new app_id, clearly invalid
223+
// TODO: Clear u2f state
224+
return ASYNC_OP_NOT_READY;
223225
}
224226
bool result = false;
225227
if (!rust_workflow_confirm_poll(&result)) {

0 commit comments

Comments
 (0)