Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions applications/main/u2f/resources/u2f/assets/u2f.nfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Filetype: Flipper NFC device
Version: 4
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB
Device type: ISO14443-4A
# UID is common for all formats
UID: CF 72 D4 40
# ISO14443-3A specific data
ATQA: 00 04
SAK: 20
# ISO14443-4A specific data
T0: 78
TA(1): 77
TB(1): 94
TC(1): 02
2 changes: 2 additions & 0 deletions applications/main/u2f/scenes/u2f_scene_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ void u2f_scene_main_on_enter(void* context) {
if(app->u2f_ready == true) {
u2f_set_event_callback(app->u2f_instance, u2f_scene_main_event_callback, app);
app->u2f_hid = u2f_hid_start(app->u2f_instance);
app->u2f_nfc = u2f_nfc_start(app->u2f_instance);
u2f_view_set_ok_callback(app->u2f_view, u2f_scene_main_ok_callback, app);
} else {
u2f_free(app->u2f_instance);
Expand All @@ -117,6 +118,7 @@ void u2f_scene_main_on_exit(void* context) {
furi_timer_free(app->timer);
if(app->u2f_ready == true) {
u2f_hid_stop(app->u2f_hid);
u2f_nfc_stop(app->u2f_nfc);
u2f_free(app->u2f_instance);
}
}
22 changes: 19 additions & 3 deletions applications/main/u2f/u2f.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@

#define MCHECK(expr) furi_check((expr) == 0)

#define U2F_CMD_REGISTER 0x01
#define U2F_CMD_AUTHENTICATE 0x02
#define U2F_CMD_VERSION 0x03
#define U2F_CMD_REGISTER 0x01
#define U2F_CMD_AUTHENTICATE 0x02
#define U2F_CMD_VERSION 0x03
#define U2F_CMD_APPLET_SELECTION 0xA4

typedef enum {
U2fCheckOnly = 0x07, // "check-only" - only check key handle, don't send auth response
Expand Down Expand Up @@ -85,10 +86,12 @@ typedef struct {

static const uint8_t ver_str[] = {"U2F_V2"};

static const uint8_t rid_ac_ax[] = {0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01};
static const uint8_t state_no_error[] = {0x90, 0x00};
static const uint8_t state_not_supported[] = {0x6D, 0x00};
static const uint8_t state_user_missing[] = {0x69, 0x85};
static const uint8_t state_wrong_data[] = {0x6A, 0x80};
static const uint8_t state_app_not_found[] = {0x6A, 0x82};

struct U2fData {
uint8_t device_key[U2F_EC_KEY_SIZE];
Expand Down Expand Up @@ -421,6 +424,17 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
return sizeof(U2fAuthResp) + signature_len + 2;
}

uint16_t u2f_applet_selection(uint8_t* buf) {
if(buf[6] != 8 || memcmp(rid_ac_ax, &buf[7], 8) != 0) {
memcpy(&buf[0], state_app_not_found, 2);
return 2;
}

memcpy(&buf[0], ver_str, 6);
memcpy(&buf[6], state_no_error, 2);
return 8;
}

uint16_t u2f_msg_parse(U2fData* U2F, uint8_t* buf, uint16_t len) {
furi_assert(U2F);
if(!U2F->ready) return 0;
Expand All @@ -435,6 +449,8 @@ uint16_t u2f_msg_parse(U2fData* U2F, uint8_t* buf, uint16_t len) {
memcpy(&buf[0], ver_str, 6);
memcpy(&buf[6], state_no_error, 2);
return 8;
} else if(buf[1] == U2F_CMD_APPLET_SELECTION) { // Applet selection
return u2f_applet_selection(buf);
} else {
memcpy(&buf[0], state_not_supported, 2);
return 2;
Expand Down
2 changes: 2 additions & 0 deletions applications/main/u2f/u2f_app_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <gui/modules/widget.h>
#include "views/u2f_view.h"
#include "u2f_hid.h"
#include "u2f_nfc.h"
#include "u2f.h"

typedef enum {
Expand Down Expand Up @@ -53,6 +54,7 @@ struct U2fApp {
Widget* widget;
FuriTimer* timer;
U2fHid* u2f_hid;
U2fNfc* u2f_nfc;
U2fView* u2f_view;
U2fData* u2f_instance;
GpioCustomEvent event_cur;
Expand Down
1 change: 1 addition & 0 deletions applications/main/u2f/u2f_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extern "C" {
#define U2F_DATA_FOLDER EXT_PATH("u2f/")
#define U2F_CERT_FILE U2F_DATA_FOLDER "assets/cert.der"
#define U2F_CERT_KEY_FILE U2F_DATA_FOLDER "assets/cert_key.u2f"
#define U2F_NFC_FILE U2F_DATA_FOLDER "assets/u2f.nfc"
#define U2F_KEY_FILE U2F_DATA_FOLDER "key.u2f"
#define U2F_CNT_FILE U2F_DATA_FOLDER "cnt.u2f"

Expand Down
254 changes: 254 additions & 0 deletions applications/main/u2f/u2f_nfc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
#include <furi.h>
#include "u2f_nfc.h"
#include "u2f.h"
#include "u2f_data.h"
#include <furi_hal_random.h>
#include <nfc/nfc_listener.h>
#include <nfc/protocols/iso14443_4a/iso14443_4a_listener.h>
#include <nfc_device.h>

#define TAG "U2fNfc"

#define NAD_MASK 0x08
#define U2F_APDU_GET_RESPONSE 0xC0
#define U2F_APDU_SELECT 0xA4
#define U2F_APDU_CHUNK_SIZE 96
#define U2F_NFC_PAYLOAD_SIZE 960
#define U2F_NFC_TX_BUFFER_SIZE 480

#define U2F_SW_CONDITIONS_NOT_SATISFIED_1 0x69
#define U2F_SW_CONDITIONS_NOT_SATISFIED_2 0x85
#define U2F_SW_NO_ERROR_1 0x90
#define U2F_SW_NO_ERROR_2 0x00

struct U2fNfc {
Nfc* nfc;
NfcDevice* nfc_device;
NfcListener* listener;
U2fData* u2f_instance;
uint8_t payload[U2F_NFC_PAYLOAD_SIZE];
uint16_t payload_len;
uint16_t payload_cursor;
bool applet_selected;
BitBuffer* tx_buffer;
};

static void u2f_nfc_clear_pending_response(U2fNfc* u2f_nfc) {
furi_assert(u2f_nfc);
u2f_nfc->payload_len = 0;
u2f_nfc->payload_cursor = 0;
}

static bool u2f_nfc_transmit_apdu(
U2fNfc* u2f_nfc,
Iso14443_4aListener* iso14443_listener,
const uint8_t* apdu,
uint16_t apdu_len) {
furi_assert(u2f_nfc);
furi_assert(iso14443_listener);
furi_assert(apdu);

BitBuffer* tx_buffer = u2f_nfc->tx_buffer;
bit_buffer_reset(tx_buffer);

bit_buffer_append_bytes(tx_buffer, apdu, apdu_len);

Iso14443_4aError error = iso14443_4a_listener_send_block(iso14443_listener, tx_buffer);
if(error != Iso14443_4aErrorNone) {
FURI_LOG_W(TAG, "Tx error: %d", error);
return false;
}

return true;
}

static bool
u2f_nfc_send_pending_response(U2fNfc* u2f_nfc, Iso14443_4aListener* iso14443_listener) {
furi_assert(u2f_nfc);

if(u2f_nfc->payload_len == 0) {
FURI_LOG_W(TAG, "No pending response to send");
return false;
}

uint16_t response_data_len = (u2f_nfc->payload_len >= 2) ? (u2f_nfc->payload_len - 2) :
u2f_nfc->payload_len;

if(u2f_nfc->payload_cursor > response_data_len) {
u2f_nfc_clear_pending_response(u2f_nfc);
return false;
}

uint16_t remaining = response_data_len - u2f_nfc->payload_cursor;
uint16_t chunk_len = MIN(remaining, (uint16_t)U2F_APDU_CHUNK_SIZE);
const uint8_t* response = u2f_nfc->payload + u2f_nfc->payload_cursor;

if(chunk_len == remaining) {
uint8_t final_response[U2F_APDU_CHUNK_SIZE + 2];
if(chunk_len > 0) {
memcpy(final_response, response, chunk_len);
}
memcpy(final_response + chunk_len, u2f_nfc->payload + response_data_len, 2);

bool success =
u2f_nfc_transmit_apdu(u2f_nfc, iso14443_listener, final_response, chunk_len + 2);

u2f_nfc_clear_pending_response(u2f_nfc);
return success;
}

uint16_t rest = remaining - chunk_len;
uint8_t chunk_response[U2F_APDU_CHUNK_SIZE + 2];
memcpy(chunk_response, response, chunk_len);
chunk_response[chunk_len] = 0x61;
chunk_response[chunk_len + 1] = (rest > 0xFF) ? 0x00 : (uint8_t)rest;

if(!u2f_nfc_transmit_apdu(u2f_nfc, iso14443_listener, chunk_response, chunk_len + 2)) {
return false;
}

u2f_nfc->payload_cursor += chunk_len;
return true;
}

NfcCommand u2f_nfc_worker_listener_callback(NfcGenericEvent event, void* context) {
furi_assert(context);
furi_assert(event.protocol == NfcProtocolIso14443_4a);
furi_assert(event.event_data);
U2fNfc* u2f_nfc = context;
U2fData* u2f = u2f_nfc->u2f_instance;

NfcCommand ret = NfcCommandContinue;
Iso14443_4aListenerEvent* iso14443_4a_event = event.event_data;
Iso14443_4aListener* iso14443_listener = event.instance;

BitBuffer* tx_buffer = u2f_nfc->tx_buffer;
bit_buffer_reset(tx_buffer);

switch(iso14443_4a_event->type) {
case Iso14443_4aListenerEventTypeReceivedData: {
BitBuffer* rx_buffer = iso14443_4a_event->data->buffer;
const uint8_t* rx_data = bit_buffer_get_data(rx_buffer);
uint16_t rx_size = bit_buffer_get_size_bytes(rx_buffer);

if(rx_size == 0) {
FURI_LOG_W(TAG, "No APDU in frame: %u", rx_size);
break;
}

uint8_t ins = rx_data[1];
FURI_LOG_D(
TAG, "Req ins=%02x len=%u sel=%u", ins, rx_size, u2f_nfc->applet_selected ? 1 : 0);

if(u2f_nfc->payload_len > 0) {
if(rx_size >= 2 && ins == U2F_APDU_GET_RESPONSE) {
u2f_nfc_send_pending_response(u2f_nfc, iso14443_listener);
break;
}

FURI_LOG_D(TAG, "Replacing pending response with new APDU");
u2f_nfc_clear_pending_response(u2f_nfc);
}

if(!u2f_nfc->applet_selected && ins != U2F_APDU_SELECT) {
const uint8_t reject_sw[2] = {
U2F_SW_CONDITIONS_NOT_SATISFIED_1,
U2F_SW_CONDITIONS_NOT_SATISFIED_2,
};
FURI_LOG_W(TAG, "Reject APDU before SELECT: ins=%02x", ins);
if(!u2f_nfc_transmit_apdu(u2f_nfc, iso14443_listener, reject_sw, sizeof(reject_sw))) {
break;
}
break;
}

// Leave 2 bytes for HID len field
memcpy(u2f_nfc->payload, rx_data, MIN(rx_size, 4));
if(rx_size > 4) {
memcpy(u2f_nfc->payload + 6, rx_data + 4, rx_size - 4);
}

u2f_confirm_user_present(u2f);
u2f_nfc->payload_len = u2f_msg_parse(u2f, u2f_nfc->payload, rx_size + 2);

if(u2f_nfc->payload_len == 0) {
FURI_LOG_W(TAG, "U2F parsing failed");
break;
}

if(ins == U2F_APDU_SELECT) {
if(u2f_nfc->payload_len >= 2 &&
u2f_nfc->payload[u2f_nfc->payload_len - 2] == U2F_SW_NO_ERROR_1 &&
u2f_nfc->payload[u2f_nfc->payload_len - 1] == U2F_SW_NO_ERROR_2) {
u2f_nfc->applet_selected = true;
FURI_LOG_D(TAG, "Select ok");
} else {
u2f_nfc->applet_selected = false;
FURI_LOG_D(TAG, "Applet select failed");
}
}

u2f_nfc_send_pending_response(u2f_nfc, iso14443_listener);
break;
}
case Iso14443_4aListenerEventTypeHalted:
case Iso14443_4aListenerEventTypeFieldOff: {
FURI_LOG_D(TAG, "Disconnect");
u2f_nfc_clear_pending_response(u2f_nfc);
u2f_nfc->applet_selected = false;
u2f_set_state(u2f, 0);

// Update UID
uint8_t random_uid[4] = {0x08};
furi_hal_random_fill_buf(&random_uid[1], 3);

Iso14443_4aData* data =
(Iso14443_4aData*)nfc_device_get_data(u2f_nfc->nfc_device, NfcProtocolIso14443_4a);
Iso14443_3aData* base_data = iso14443_4a_get_base_data(data);

iso14443_4a_set_uid(data, random_uid, sizeof(random_uid));
nfc_iso14443a_listener_set_col_res_data(
u2f_nfc->nfc, random_uid, sizeof(random_uid), base_data->atqa, base_data->sak);
break;
}
}

return ret;
}

U2fNfc* u2f_nfc_start(U2fData* u2f_inst) {
furi_assert(u2f_inst);
FURI_LOG_D(TAG, "Init");

U2fNfc* u2f_nfc = malloc(sizeof(U2fNfc));
u2f_nfc->u2f_instance = u2f_inst;
u2f_nfc->nfc = nfc_alloc();
u2f_nfc->nfc_device = nfc_device_alloc();
u2f_nfc->tx_buffer = bit_buffer_alloc(U2F_NFC_TX_BUFFER_SIZE);

nfc_device_load(u2f_nfc->nfc_device, U2F_NFC_FILE);
Iso14443_4aData* data =
(Iso14443_4aData*)nfc_device_get_data(u2f_nfc->nfc_device, NfcProtocolIso14443_4a);

// Randomize UID for Anti-Tracking (ISO14443-3 4-byte random UID starts with 0x08)
uint8_t random_uid[4] = {0x08};
furi_hal_random_fill_buf(&random_uid[1], 3);
iso14443_4a_set_uid(data, random_uid, sizeof(random_uid));

u2f_nfc->listener = nfc_listener_alloc(u2f_nfc->nfc, NfcProtocolIso14443_4a, data);
nfc_listener_start(u2f_nfc->listener, u2f_nfc_worker_listener_callback, u2f_nfc);

return u2f_nfc;
}

void u2f_nfc_stop(U2fNfc* u2f_nfc) {
furi_assert(u2f_nfc);
FURI_LOG_D(TAG, "End");
nfc_listener_stop(u2f_nfc->listener);
nfc_listener_free(u2f_nfc->listener);
nfc_device_free(u2f_nfc->nfc_device);
nfc_free(u2f_nfc->nfc);
bit_buffer_free(u2f_nfc->tx_buffer);

free(u2f_nfc);
}
21 changes: 21 additions & 0 deletions applications/main/u2f/u2f_nfc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#ifdef __cplusplus
extern "C" {
#endif

#include <furi.h>
#include <nfc.h>
#include <nfc/protocols/nfc_generic_event.h>
#include "u2f.h"

typedef struct U2fNfc U2fNfc;

NfcCommand u2f_nfc_worker_listener_callback(NfcGenericEvent event, void* context);

U2fNfc* u2f_nfc_start(U2fData* u2f_inst);
void u2f_nfc_stop(U2fNfc* u2f_nfc);

#ifdef __cplusplus
}
#endif
3 changes: 2 additions & 1 deletion applications/main/u2f/views/u2f_view.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ static void u2f_view_draw_callback(Canvas* canvas, void* _model) {

if(model->display_msg == U2fMsgNotConnected) {
canvas_draw_icon(canvas, 22, 15, &I_Connect_me_62x31);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Connect to a device");
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Connect or tap to a device");
} else if(model->display_msg == U2fMsgIdle) {
canvas_draw_icon(canvas, 22, 15, &I_Connected_62x31);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Connected!");
Expand Down