From 725981f431915508ffc02a4336258d24ae25ee4e Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 18 May 2021 21:12:01 +0300 Subject: [PATCH] [FL-663] Read EMV cards (#460) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: add emv decoder * api-hal-nfc: add data exchange api * nfc_worker: add read emv routine * nfc: add emv reader view * nfc: add support for Mastercard reading * api-hal-nfc: fix incorrect merge changes * nfc_worker: set to zero emv app object on each cycle * api-hal-nfc: add api for f6 target * nfc: move emv_decoder to lib folder Co-authored-by: あく --- applications/nfc/nfc.c | 39 +++- applications/nfc/nfc_i.h | 1 + applications/nfc/nfc_types.h | 13 +- applications/nfc/nfc_views.c | 29 +++ applications/nfc/nfc_views.h | 2 + applications/nfc/nfc_worker.c | 151 ++++++++++++- applications/nfc/nfc_worker_i.h | 2 + .../targets/api-hal-include/api-hal-nfc.h | 12 +- firmware/targets/f5/api-hal/api-hal-nfc.c | 49 ++++- firmware/targets/f6/api-hal/api-hal-nfc.c | 49 ++++- lib/lib.mk | 3 + lib/nfc_protocols/emv_decoder.c | 205 ++++++++++++++++++ lib/nfc_protocols/emv_decoder.h | 50 +++++ 13 files changed, 590 insertions(+), 15 deletions(-) mode change 100644 => 100755 applications/nfc/nfc_worker.c create mode 100755 lib/nfc_protocols/emv_decoder.c create mode 100755 lib/nfc_protocols/emv_decoder.h diff --git a/applications/nfc/nfc.c b/applications/nfc/nfc.c index 9faa761a..d82f32ee 100755 --- a/applications/nfc/nfc.c +++ b/applications/nfc/nfc.c @@ -25,8 +25,10 @@ void nfc_menu_callback(void* context, uint32_t index) { if(index == 0) { message.type = NfcMessageTypeDetect; } else if(index == 1) { - message.type = NfcMessageTypeEmulate; + message.type = NfcMessageTypeReadEMV; } else if(index == 2) { + message.type = NfcMessageTypeEmulate; + } else if(index == 3) { message.type = NfcMessageTypeField; } furi_check(osMessageQueuePut(message_queue, &message, 0, osWaitForever) == osOK); @@ -49,8 +51,9 @@ Nfc* nfc_alloc() { // Menu nfc->submenu = submenu_alloc(); submenu_add_item(nfc->submenu, "Detect", 0, nfc_menu_callback, nfc); - submenu_add_item(nfc->submenu, "Emulate", 1, nfc_menu_callback, nfc); - submenu_add_item(nfc->submenu, "Field", 2, nfc_menu_callback, nfc); + submenu_add_item(nfc->submenu, "Read EMV", 1, nfc_menu_callback, nfc); + submenu_add_item(nfc->submenu, "Emulate", 2, nfc_menu_callback, nfc); + submenu_add_item(nfc->submenu, "Field", 3, nfc_menu_callback, nfc); View* submenu_view = submenu_get_view(nfc->submenu); view_set_previous_callback(submenu_view, nfc_view_exit); view_dispatcher_add_view(nfc->view_dispatcher, NfcViewMenu, submenu_view); @@ -63,6 +66,14 @@ Nfc* nfc_alloc() { view_allocate_model(nfc->view_detect, ViewModelTypeLocking, sizeof(NfcViewReadModel)); view_dispatcher_add_view(nfc->view_dispatcher, NfcViewRead, nfc->view_detect); + // Read EMV + nfc->view_read_emv = view_alloc(); + view_set_context(nfc->view_read_emv, nfc); + view_set_draw_callback(nfc->view_read_emv, nfc_view_read_emv_draw); + view_set_previous_callback(nfc->view_read_emv, nfc_view_stop); + view_allocate_model(nfc->view_read_emv, ViewModelTypeLocking, sizeof(NfcViewReadModel)); + view_dispatcher_add_view(nfc->view_dispatcher, NfcViewReadEmv, nfc->view_read_emv); + // Emulate nfc->view_emulate = view_alloc(); view_set_context(nfc->view_emulate, nfc); @@ -143,7 +154,7 @@ void nfc_cli_detect(Cli* cli, string_t args, void* context) { printf("Detecting nfc...\r\nPress Ctrl+C to abort\r\n"); while(!cmd_exit) { cmd_exit |= cli_cmd_interrupt_received(cli); - cmd_exit |= api_hal_nfc_detect(&dev_list, &dev_cnt, 100); + cmd_exit |= api_hal_nfc_detect(&dev_list, &dev_cnt, 100, true); if(dev_cnt > 0) { printf("Found %d devices\r\n", dev_cnt); for(uint8_t i = 0; i < dev_cnt; i++) { @@ -196,6 +207,13 @@ int32_t nfc_task(void* p) { return true; }); nfc_start(nfc, NfcViewRead, NfcWorkerStatePoll); + } else if(message.type == NfcMessageTypeReadEMV) { + with_view_model( + nfc->view_read_emv, (NfcViewReadModel * model) { + model->found = false; + return true; + }); + nfc_start(nfc, NfcViewReadEmv, NfcWorkerStateReadEMV); } else if(message.type == NfcMessageTypeEmulate) { nfc_start(nfc, NfcViewEmulate, NfcWorkerStateEmulate); } else if(message.type == NfcMessageTypeField) { @@ -215,6 +233,19 @@ int32_t nfc_task(void* p) { model->found = false; return true; }); + } else if(message.type == NfcMessageTypeEMVFound) { + with_view_model( + nfc->view_read_emv, (NfcViewReadModel * model) { + model->found = true; + model->device = message.device; + return true; + }); + } else if(message.type == NfcMessageTypeEMVNotFound) { + with_view_model( + nfc->view_read_emv, (NfcViewReadModel * model) { + model->found = false; + return true; + }); } else if(message.type == NfcMessageTypeExit) { nfc_free(nfc); break; diff --git a/applications/nfc/nfc_i.h b/applications/nfc/nfc_i.h index e3d139db..ecc18e52 100644 --- a/applications/nfc/nfc_i.h +++ b/applications/nfc/nfc_i.h @@ -27,6 +27,7 @@ struct Nfc { Submenu* submenu; View* view_detect; + View* view_read_emv; View* view_emulate; View* view_field; View* view_cli; diff --git a/applications/nfc/nfc_types.h b/applications/nfc/nfc_types.h index ab4b10fa..15441ace 100644 --- a/applications/nfc/nfc_types.h +++ b/applications/nfc/nfc_types.h @@ -44,9 +44,15 @@ typedef enum { NfcDeviceTypeNfcb, NfcDeviceTypeNfcf, NfcDeviceTypeNfcv, - NfcDeviceTypeNfcMifare + NfcDeviceTypeNfcMifare, + NfcDeviceTypeEMV, } NfcDeviceType; +typedef struct { + char name[32]; + uint8_t number[8]; +} EMVCard; + typedef struct { NfcDeviceType type; union { @@ -54,6 +60,7 @@ typedef struct { rfalNfcbListenDevice nfcb; rfalNfcfListenDevice nfcf; rfalNfcvListenDevice nfcv; + EMVCard emv_card; }; } NfcDevice; @@ -64,6 +71,7 @@ typedef enum { NfcWorkerStateReady, // Main worker states NfcWorkerStatePoll, + NfcWorkerStateReadEMV, NfcWorkerStateEmulate, NfcWorkerStateField, // Transition @@ -72,6 +80,7 @@ typedef enum { typedef enum { NfcMessageTypeDetect, + NfcMessageTypeReadEMV, NfcMessageTypeEmulate, NfcMessageTypeField, NfcMessageTypeStop, @@ -79,6 +88,8 @@ typedef enum { // From Worker NfcMessageTypeDeviceFound, NfcMessageTypeDeviceNotFound, + NfcMessageTypeEMVFound, + NfcMessageTypeEMVNotFound, } NfcMessageType; typedef struct { diff --git a/applications/nfc/nfc_views.c b/applications/nfc/nfc_views.c index 9832a3bf..de2c3e6c 100644 --- a/applications/nfc/nfc_views.c +++ b/applications/nfc/nfc_views.c @@ -104,6 +104,35 @@ void nfc_view_read_nfcv_draw(Canvas* canvas, NfcViewReadModel* model) { canvas_draw_str(canvas, 18, 42, buffer); } +void nfc_view_read_emv_draw(Canvas* canvas, void* model) { + NfcViewReadModel* m = model; + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + char buffer[32]; + + if(m->found) { + canvas_draw_str(canvas, 0, 12, "Found EMV card"); + canvas_set_font(canvas, FontSecondary); + snprintf(buffer, sizeof(buffer), "Type:\n"); + canvas_draw_str(canvas, 2, 22, buffer); + snprintf(buffer, sizeof(buffer), "%s", m->device.emv_card.name); + canvas_draw_str(canvas, 2, 32, buffer); + snprintf(buffer, sizeof(buffer), "Number:\n"); + canvas_draw_str(canvas, 2, 42, buffer); + uint8_t card_num_len = sizeof(m->device.emv_card.number); + for(uint8_t i = 0; i < card_num_len; i++) { + snprintf( + buffer + (i * 2), sizeof(buffer) - (i * 2), "%02X", m->device.emv_card.number[i]); + } + buffer[card_num_len * 2] = 0; + canvas_draw_str(canvas, 2, 52, buffer); + } else { + canvas_draw_str(canvas, 0, 12, "Searching"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 22, "Place card to the back"); + } +} + void nfc_view_emulate_draw(Canvas* canvas, void* model) { canvas_clear(canvas); canvas_set_font(canvas, FontPrimary); diff --git a/applications/nfc/nfc_views.h b/applications/nfc/nfc_views.h index f9d69f38..365186b9 100644 --- a/applications/nfc/nfc_views.h +++ b/applications/nfc/nfc_views.h @@ -10,6 +10,7 @@ typedef enum { NfcViewMenu, NfcViewRead, + NfcViewReadEmv, NfcViewEmulate, NfcViewField, NfcViewError, @@ -25,6 +26,7 @@ void nfc_view_read_nfca_draw(Canvas* canvas, NfcViewReadModel* model); void nfc_view_read_nfcb_draw(Canvas* canvas, NfcViewReadModel* model); void nfc_view_read_nfcf_draw(Canvas* canvas, NfcViewReadModel* model); void nfc_view_read_nfcv_draw(Canvas* canvas, NfcViewReadModel* model); +void nfc_view_read_emv_draw(Canvas* canvas, void* model); void nfc_view_emulate_draw(Canvas* canvas, void* model); diff --git a/applications/nfc/nfc_worker.c b/applications/nfc/nfc_worker.c old mode 100644 new mode 100755 index 3f5193b5..074974b9 --- a/applications/nfc/nfc_worker.c +++ b/applications/nfc/nfc_worker.c @@ -1,12 +1,15 @@ #include "nfc_worker_i.h" #include +#include "nfc_protocols/emv_decoder.h" + +#define NFC_WORKER_TAG "nfc worker" NfcWorker* nfc_worker_alloc(osMessageQueueId_t message_queue) { NfcWorker* nfc_worker = furi_alloc(sizeof(NfcWorker)); nfc_worker->message_queue = message_queue; // Worker thread attributes nfc_worker->thread_attr.name = "nfc_worker"; - nfc_worker->thread_attr.stack_size = 2048; + nfc_worker->thread_attr.stack_size = 8192; // Initialize rfal nfc_worker->error = api_hal_nfc_init(); if(nfc_worker->error == ERR_NONE) { @@ -59,17 +62,159 @@ void nfc_worker_task(void* context) { if(nfc_worker->state == NfcWorkerStatePoll) { nfc_worker_poll(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateReadEMV) { + nfc_worker_read_emv(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateEmulate) { nfc_worker_emulate(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateField) { nfc_worker_field(nfc_worker); } - nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); api_hal_power_insomnia_exit(); osThreadExit(); } +void nfc_worker_read_emv(NfcWorker* nfc_worker) { + ReturnCode err; + rfalNfcDevice* dev_list; + rfalNfcDevice* dev_active; + EmvApplication emv_app = {}; + uint8_t dev_cnt = 0; + uint8_t tx_buff[255] = {}; + uint16_t tx_len = 0; + uint8_t* rx_buff; + uint16_t* rx_len; + + // Update screen before start searching + NfcMessage message = {.type = NfcMessageTypeEMVNotFound}; + while(nfc_worker->state == NfcWorkerStateReadEMV) { + furi_check( + osMessageQueuePut(nfc_worker->message_queue, &message, 0, osWaitForever) == osOK); + memset(&emv_app, 0, sizeof(emv_app)); + if(api_hal_nfc_detect(&dev_list, &dev_cnt, 100, false)) { + // Card was found. Check that it supports EMV + if(dev_list[0].rfInterface == RFAL_NFC_INTERFACE_ISODEP) { + dev_active = &dev_list[0]; + FURI_LOG_I(NFC_WORKER_TAG, "Send select PPSE command"); + tx_len = emv_prepare_select_ppse(tx_buff); + err = api_hal_nfc_data_exchange( + dev_active, tx_buff, tx_len, &rx_buff, &rx_len, false); + if(err != ERR_NONE) { + FURI_LOG_E(NFC_WORKER_TAG, "Error during selection PPSE request: %d", err); + message.type = NfcMessageTypeEMVNotFound; + api_hal_nfc_deactivate(); + continue; + } + FURI_LOG_I( + NFC_WORKER_TAG, "Select PPSE response received. Start parsing response"); + if(emv_decode_ppse_response(rx_buff, *rx_len, &emv_app)) { + FURI_LOG_I(NFC_WORKER_TAG, "Select PPSE responce parced"); + } else { + FURI_LOG_E(NFC_WORKER_TAG, "Can't find pay application"); + message.type = NfcMessageTypeEMVNotFound; + api_hal_nfc_deactivate(); + continue; + } + FURI_LOG_I(NFC_WORKER_TAG, "Starting application ..."); + tx_len = emv_prepare_select_app(tx_buff, &emv_app); + err = api_hal_nfc_data_exchange( + dev_active, tx_buff, tx_len, &rx_buff, &rx_len, false); + if(err != ERR_NONE) { + FURI_LOG_E( + NFC_WORKER_TAG, "Error during application selection request: %d", err); + message.type = NfcMessageTypeEMVNotFound; + api_hal_nfc_deactivate(); + continue; + } + FURI_LOG_I( + NFC_WORKER_TAG, + "Select application response received. Start parsing response"); + if(emv_decode_select_app_response(rx_buff, *rx_len, &emv_app)) { + FURI_LOG_I(NFC_WORKER_TAG, "Card name: %s", emv_app.name); + memcpy(message.device.emv_card.name, emv_app.name, sizeof(emv_app.name)); + } else { + FURI_LOG_E(NFC_WORKER_TAG, "Can't read card name"); + message.type = NfcMessageTypeEMVNotFound; + api_hal_nfc_deactivate(); + continue; + } + FURI_LOG_I(NFC_WORKER_TAG, "Starting Get Processing Options command ..."); + tx_len = emv_prepare_get_proc_opt(tx_buff, &emv_app); + err = api_hal_nfc_data_exchange( + dev_active, tx_buff, tx_len, &rx_buff, &rx_len, false); + if(err != ERR_NONE) { + FURI_LOG_E( + NFC_WORKER_TAG, "Error during Get Processing Options command: %d", err); + message.type = NfcMessageTypeEMVNotFound; + api_hal_nfc_deactivate(); + continue; + } + if(emv_decode_get_proc_opt(rx_buff, *rx_len, &emv_app)) { + FURI_LOG_I(NFC_WORKER_TAG, "Card number parsed"); + message.type = NfcMessageTypeEMVFound; + memcpy( + message.device.emv_card.number, + emv_app.card_number, + sizeof(emv_app.card_number)); + api_hal_nfc_deactivate(); + continue; + } else { + // Mastercard doesn't give PAN / card number as GPO response + // Iterate over all files found in application + bool pan_found = false; + for(uint8_t i = 0; (i < emv_app.afl.size) && !pan_found; i += 4) { + uint8_t sfi = emv_app.afl.data[i] >> 3; + uint8_t record_start = emv_app.afl.data[i + 1]; + uint8_t record_end = emv_app.afl.data[i + 2]; + + // Iterate over all records in file + for(uint8_t record = record_start; record <= record_end; ++record) { + tx_len = emv_prepare_read_sfi_record(tx_buff, sfi, record); + err = api_hal_nfc_data_exchange( + dev_active, tx_buff, tx_len, &rx_buff, &rx_len, false); + if(err != ERR_NONE) { + FURI_LOG_E( + NFC_WORKER_TAG, + "Error reading application sfi %d, record %d", + sfi, + record); + } + if(emv_decode_read_sfi_record(rx_buff, *rx_len, &emv_app)) { + pan_found = true; + break; + } + } + } + if(pan_found) { + FURI_LOG_I(NFC_WORKER_TAG, "Card PAN found"); + message.type = NfcMessageTypeEMVFound; + memcpy( + message.device.emv_card.number, + emv_app.card_number, + sizeof(emv_app.card_number)); + } else { + FURI_LOG_E(NFC_WORKER_TAG, "Can't read card number"); + message.type = NfcMessageTypeEMVNotFound; + } + api_hal_nfc_deactivate(); + } + } else { + // Can't find EMV card + FURI_LOG_W(NFC_WORKER_TAG, "Card doesn't support EMV"); + message.type = NfcMessageTypeEMVNotFound; + api_hal_nfc_deactivate(); + } + } else { + // Can't find EMV card + FURI_LOG_W(NFC_WORKER_TAG, "Can't find any cards"); + message.type = NfcMessageTypeEMVNotFound; + api_hal_nfc_deactivate(); + } + osDelay(20); + } + api_hal_nfc_deactivate(); +} + void nfc_worker_poll(NfcWorker* nfc_worker) { rfalNfcDevice* dev_list; uint8_t dev_cnt; @@ -78,7 +223,7 @@ void nfc_worker_poll(NfcWorker* nfc_worker) { furi_check(osMessageQueuePut(nfc_worker->message_queue, &message, 0, osWaitForever) == osOK); while(nfc_worker->state == NfcWorkerStatePoll) { - if(api_hal_nfc_detect(&dev_list, &dev_cnt, 100)) { + if(api_hal_nfc_detect(&dev_list, &dev_cnt, 100, true)) { // Send message with first device found message.type = NfcMessageTypeDeviceFound; if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCA) { diff --git a/applications/nfc/nfc_worker_i.h b/applications/nfc/nfc_worker_i.h index 21f7da6f..7877aca5 100644 --- a/applications/nfc/nfc_worker_i.h +++ b/applications/nfc/nfc_worker_i.h @@ -29,6 +29,8 @@ void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state); void nfc_worker_task(void* context); +void nfc_worker_read_emv(NfcWorker* nfc_worker); + void nfc_worker_poll(NfcWorker* nfc_worker); void nfc_worker_emulate(NfcWorker* nfc_worker); diff --git a/firmware/targets/api-hal-include/api-hal-nfc.h b/firmware/targets/api-hal-include/api-hal-nfc.h index 8b7f4932..bded81d7 100644 --- a/firmware/targets/api-hal-include/api-hal-nfc.h +++ b/firmware/targets/api-hal-include/api-hal-nfc.h @@ -42,7 +42,17 @@ void api_hal_nfc_exit_sleep(); /** * NFC poll */ -bool api_hal_nfc_detect(rfalNfcDevice** dev_list, uint8_t* dev_cnt, uint32_t cycles); +bool api_hal_nfc_detect(rfalNfcDevice** dev_list, uint8_t* dev_cnt, uint32_t cycles, bool deactivate); + +/** + * NFC data exchange + */ +ReturnCode api_hal_nfc_data_exchange(rfalNfcDevice* dev, uint8_t* tx_buff, uint16_t tx_len, uint8_t** rx_buff, uint16_t** rx_len, bool deactivate); + +/** + * NFC deactivate and start sleep + */ +void api_hal_nfc_deactivate(); #ifdef __cplusplus } diff --git a/firmware/targets/f5/api-hal/api-hal-nfc.c b/firmware/targets/f5/api-hal/api-hal-nfc.c index 411e409f..ecc4825a 100644 --- a/firmware/targets/f5/api-hal/api-hal-nfc.c +++ b/firmware/targets/f5/api-hal/api-hal-nfc.c @@ -40,7 +40,7 @@ static void api_hal_nfc_change_state_cb(rfalNfcState st) { } } -bool api_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t cycles) { +bool api_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t cycles, bool deactivate) { furi_assert(dev_list); furi_assert(dev_cnt); @@ -74,8 +74,10 @@ bool api_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t cyc } osDelay(5); } - rfalNfcDeactivate(false); - rfalLowPowerModeStart(); + if(deactivate) { + rfalNfcDeactivate(false); + rfalLowPowerModeStart(); + } if(!cycles) { FURI_LOG_D("HAL NFC", "Timeout"); return false; @@ -83,3 +85,44 @@ bool api_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t cyc return true; } + +ReturnCode api_hal_nfc_data_exchange(rfalNfcDevice* dev, uint8_t* tx_buff, uint16_t tx_len, uint8_t** rx_buff, uint16_t** rx_len, bool deactivate) { + furi_assert(dev); + furi_assert(tx_buff); + furi_assert(rx_buff); + furi_assert(rx_len); + + ReturnCode ret; + rfalNfcDevice* active_dev; + rfalNfcState state = RFAL_NFC_STATE_ACTIVATED; + + ret = rfalNfcGetActiveDevice(&active_dev); + if(ret != ERR_NONE) { + return ret; + } + if (active_dev != dev) { + return ERR_NOTFOUND; + } + ret = rfalNfcDataExchangeStart(tx_buff, tx_len, rx_buff, rx_len, 0); + if(ret != ERR_NONE) { + return ret; + } + FURI_LOG_D("HAL NFC", "Start data exchange"); + while(state != RFAL_NFC_STATE_DATAEXCHANGE_DONE) { + rfalNfcWorker(); + state = rfalNfcGetState(); + FURI_LOG_D("HAL NFC", "Data exchange status: %d", rfalNfcDataExchangeGetStatus()); + osDelay(10); + } + FURI_LOG_D("HAL NFC", "Data exchange complete"); + if(deactivate) { + rfalNfcDeactivate(false); + rfalLowPowerModeStart(); + } + return ERR_NONE; +} + +void api_hal_nfc_deactivate() { + rfalNfcDeactivate(false); + rfalLowPowerModeStart(); +} diff --git a/firmware/targets/f6/api-hal/api-hal-nfc.c b/firmware/targets/f6/api-hal/api-hal-nfc.c index 411e409f..ecc4825a 100644 --- a/firmware/targets/f6/api-hal/api-hal-nfc.c +++ b/firmware/targets/f6/api-hal/api-hal-nfc.c @@ -40,7 +40,7 @@ static void api_hal_nfc_change_state_cb(rfalNfcState st) { } } -bool api_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t cycles) { +bool api_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t cycles, bool deactivate) { furi_assert(dev_list); furi_assert(dev_cnt); @@ -74,8 +74,10 @@ bool api_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t cyc } osDelay(5); } - rfalNfcDeactivate(false); - rfalLowPowerModeStart(); + if(deactivate) { + rfalNfcDeactivate(false); + rfalLowPowerModeStart(); + } if(!cycles) { FURI_LOG_D("HAL NFC", "Timeout"); return false; @@ -83,3 +85,44 @@ bool api_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t cyc return true; } + +ReturnCode api_hal_nfc_data_exchange(rfalNfcDevice* dev, uint8_t* tx_buff, uint16_t tx_len, uint8_t** rx_buff, uint16_t** rx_len, bool deactivate) { + furi_assert(dev); + furi_assert(tx_buff); + furi_assert(rx_buff); + furi_assert(rx_len); + + ReturnCode ret; + rfalNfcDevice* active_dev; + rfalNfcState state = RFAL_NFC_STATE_ACTIVATED; + + ret = rfalNfcGetActiveDevice(&active_dev); + if(ret != ERR_NONE) { + return ret; + } + if (active_dev != dev) { + return ERR_NOTFOUND; + } + ret = rfalNfcDataExchangeStart(tx_buff, tx_len, rx_buff, rx_len, 0); + if(ret != ERR_NONE) { + return ret; + } + FURI_LOG_D("HAL NFC", "Start data exchange"); + while(state != RFAL_NFC_STATE_DATAEXCHANGE_DONE) { + rfalNfcWorker(); + state = rfalNfcGetState(); + FURI_LOG_D("HAL NFC", "Data exchange status: %d", rfalNfcDataExchangeGetStatus()); + osDelay(10); + } + FURI_LOG_D("HAL NFC", "Data exchange complete"); + if(deactivate) { + rfalNfcDeactivate(false); + rfalLowPowerModeStart(); + } + return ERR_NONE; +} + +void api_hal_nfc_deactivate() { + rfalNfcDeactivate(false); + rfalLowPowerModeStart(); +} diff --git a/lib/lib.mk b/lib/lib.mk index 29f90b5b..119a6ebd 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -51,6 +51,9 @@ CFLAGS += -I$(ST25RFAL002_DIR)/source/st25r3916 C_SOURCES += $(wildcard $(ST25RFAL002_DIR)/*.c) C_SOURCES += $(wildcard $(ST25RFAL002_DIR)/source/*.c) C_SOURCES += $(wildcard $(ST25RFAL002_DIR)/source/st25r3916/*.c) + +CFLAGS += -I$(LIB_DIR)/nfc_protocols +C_SOURCES += $(wildcard $(LIB_DIR)/nfc_protocols/*.c) endif # callback connector (C to CPP) library diff --git a/lib/nfc_protocols/emv_decoder.c b/lib/nfc_protocols/emv_decoder.c new file mode 100755 index 00000000..527811f5 --- /dev/null +++ b/lib/nfc_protocols/emv_decoder.c @@ -0,0 +1,205 @@ +#include "emv_decoder.h" + +const PDOLValue pdol_term_info = {0x9F59, {0xC8, 0x80, 0x00}}; // Terminal transaction information +const PDOLValue pdol_term_type = {0x9F5A, {0x00}}; // Terminal transaction type +const PDOLValue pdol_merchant_type = {0x9F58, {0x01}}; // Merchant type indicator +const PDOLValue pdol_term_trans_qualifies = { + 0x9F66, + {0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers +const PDOLValue pdol_amount_authorise = { + 0x9F02, + {0x00, 0x00, 0x00, 0x10, 0x00, 0x00}}; // Amount, authorised +const PDOLValue pdol_amount = {0x9F03, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // Amount +const PDOLValue pdol_country_code = {0x9F1A, {0x01, 0x24}}; // Terminal country code +const PDOLValue pdol_currency_code = {0x5F2A, {0x01, 0x24}}; // Transaction currency code +const PDOLValue pdol_term_verification = { + 0x95, + {0x00, 0x00, 0x00, 0x00, 0x00}}; // Terminal verification results +const PDOLValue pdol_transaction_date = {0x9A, {0x19, 0x01, 0x01}}; // Transaction date +const PDOLValue pdol_transaction_type = {0x9C, {0x00}}; // Transaction type +const PDOLValue pdol_transaction_cert = {0x98, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; // Transaction cert +const PDOLValue pdol_unpredict_number = {0x9F37, {0x82, 0x3D, 0xDE, 0x7A}}; // Unpredictable number + +const PDOLValue* pdol_values[] = { + &pdol_term_info, + &pdol_term_type, + &pdol_merchant_type, + &pdol_term_trans_qualifies, + &pdol_amount_authorise, + &pdol_amount, + &pdol_country_code, + &pdol_currency_code, + &pdol_term_verification, + &pdol_transaction_date, + &pdol_transaction_type, + &pdol_transaction_cert, + &pdol_unpredict_number, +}; + +static uint16_t emv_parse_TLV(uint8_t* dest, uint8_t* src, uint16_t* idx) { + uint8_t len = src[*idx + 1]; + memcpy(dest, &src[*idx + 2], len); + *idx = *idx + len + 1; + return len; +} + +uint16_t emv_prepare_select_ppse(uint8_t* dest) { + const uint8_t emv_select_ppse[] = { + 0x00, 0xA4, // SELECT ppse + 0x04, 0x00, // P1:By name, P2: empty + 0x0e, // Lc: Data length + 0x32, 0x50, 0x41, 0x59, 0x2e, 0x53, 0x59, // Data string: + 0x53, 0x2e, 0x44, 0x44, 0x46, 0x30, 0x31, // 2PAY.SYS.DDF01 (PPSE) + 0x00 // Le + }; + memcpy(dest, emv_select_ppse, sizeof(emv_select_ppse)); + return sizeof(emv_select_ppse); +} + +bool emv_decode_ppse_response(uint8_t* buff, uint16_t len, EmvApplication* app) { + uint16_t i = 0; + bool app_aid_found = false; + + while(i < len) { + if(buff[i] == EMV_TAG_APP_TEMPLATE) { + uint8_t app_len = buff[++i]; + for(uint16_t j = i; j < i + app_len; j++) { + if(buff[j] == EMV_TAG_AID) { + app_aid_found = true; + app->aid_len = buff[j + 1]; + emv_parse_TLV(app->aid, buff, &j); + } else if(buff[j] == EMV_TAG_PRIORITY) { + emv_parse_TLV(&app->priority, buff, &j); + } + } + i += app_len; + } + i++; + } + return app_aid_found; +} + +uint16_t emv_prepare_select_app(uint8_t* dest, EmvApplication* app) { + const uint8_t emv_select_header[] = { + 0x00, + 0xA4, // SELECT application + 0x04, + 0x00 // P1:By name, P2:First or only occurence + }; + uint16_t size = sizeof(emv_select_header); + // Copy header + memcpy(dest, emv_select_header, size); + // Copy AID + dest[size++] = app->aid_len; + memcpy(&dest[size], app->aid, app->aid_len); + size += app->aid_len; + dest[size++] = 0; + return size; +} + +bool emv_decode_select_app_response(uint8_t* buff, uint16_t len, EmvApplication* app) { + uint16_t i = 0; + bool found_name = false; + + while(i < len) { + if(buff[i] == EMV_TAG_CARD_NAME) { + uint8_t name_len = buff[i + 1]; + emv_parse_TLV((uint8_t*)app->name, buff, &i); + app->name[name_len] = '\0'; + found_name = true; + } else if(((buff[i] << 8) | buff[i + 1]) == EMV_TAG_PDOL) { + i++; + app->pdol.size = emv_parse_TLV(app->pdol.data, buff, &i); + } + i++; + } + return found_name; +} + +static uint16_t emv_prepare_pdol(APDU* dest, APDU* src) { + bool tag_found; + for(uint16_t i = 0; i < src->size; i++) { + tag_found = false; + for(uint8_t j = 0; j < sizeof(pdol_values) / sizeof(PDOLValue*); j++) { + if(src->data[i] == pdol_values[j]->tag) { + // Found tag with 1 byte length + uint8_t len = src->data[++i]; + memcpy(dest->data + dest->size, pdol_values[j]->data, len); + dest->size += len; + tag_found = true; + break; + } else if(((src->data[i] << 8) | src->data[i + 1]) == pdol_values[j]->tag) { + // Found tag with 2 byte length + i += 2; + uint8_t len = src->data[i]; + memcpy(dest->data + dest->size, pdol_values[j]->data, len); + dest->size += len; + tag_found = true; + break; + } + } + if(!tag_found) { + // Unknown tag, fill zeros + i += 2; + uint8_t len = src->data[i]; + memset(dest->data + dest->size, 0, len); + dest->size += len; + } + } + return dest->size; +} + +uint16_t emv_prepare_get_proc_opt(uint8_t* dest, EmvApplication* app) { + // Get processing option header + const uint8_t emv_gpo_header[] = {0x80, 0xA8, 0x00, 0x00}; + uint16_t size = sizeof(emv_gpo_header); + // Copy header + memcpy(dest, emv_gpo_header, size); + APDU pdol_data = {0, {0}}; + // Prepare and copy pdol parameters + emv_prepare_pdol(&pdol_data, &app->pdol); + dest[size++] = 0x02 + pdol_data.size; + dest[size++] = 0x83; + dest[size++] = pdol_data.size; + memcpy(dest + size, pdol_data.data, pdol_data.size); + size += pdol_data.size; + dest[size++] = 0; + return size; +} + +bool emv_decode_get_proc_opt(uint8_t* buff, uint16_t len, EmvApplication* app) { + for(uint16_t i = 0; i < len; i++) { + if(buff[i] == EMV_TAG_CARD_NUM) { + memcpy(app->card_number, &buff[i + 2], 8); + return true; + } else if(buff[i] == EMV_TAG_AFL) { + app->afl.size = emv_parse_TLV(app->afl.data, buff, &i); + } + } + return false; +} + +uint16_t emv_prepare_read_sfi_record(uint8_t* dest, uint8_t sfi, uint8_t record_num) { + const uint8_t sfi_param = (sfi << 3) | (1 << 2); + const uint8_t emv_sfi_header[] = { + 0x00, + 0xB2, // READ RECORD + record_num, + sfi_param, // P1:record_number and P2:SFI + 0x00 // Le + }; + uint16_t size = sizeof(emv_sfi_header); + memcpy(dest, emv_sfi_header, size); + return size; +} + +bool emv_decode_read_sfi_record(uint8_t* buff, uint16_t len, EmvApplication* app) { + for(uint16_t i = 0; i < len; i++) { + if(buff[i] == EMV_TAG_PAN) { + memcpy(app->card_number, &buff[i + 2], 8); + return true; + } + } + return false; +} diff --git a/lib/nfc_protocols/emv_decoder.h b/lib/nfc_protocols/emv_decoder.h new file mode 100755 index 00000000..8239218d --- /dev/null +++ b/lib/nfc_protocols/emv_decoder.h @@ -0,0 +1,50 @@ +#include +#include +#include + +#define MAX_APDU_LEN 255 + +#define EMV_TAG_APP_TEMPLATE 0x61 +#define EMV_TAG_AID 0x4F +#define EMV_TAG_PRIORITY 0x87 +#define EMV_TAG_PDOL 0x9F38 +#define EMV_TAG_CARD_NAME 0x50 +#define EMV_TAG_FCI 0xBF0C +#define EMV_TAG_LOG_CTRL 0x9F4D +#define EMV_TAG_CARD_NUM 0x57 +#define EMV_TAG_PAN 0x5A +#define EMV_TAG_AFL 0x94 + +typedef struct { + uint16_t tag; + uint8_t data[]; +} PDOLValue; + +extern const PDOLValue* pdol_values[]; + +typedef struct { + uint8_t size; + uint8_t data[MAX_APDU_LEN]; +} APDU; + +typedef struct { + uint8_t priority; + uint8_t aid[16]; + uint8_t aid_len; + char name[32]; + uint8_t card_number[8]; + APDU pdol; + APDU afl; +} EmvApplication; + +uint16_t emv_prepare_select_ppse(uint8_t* dest); +bool emv_decode_ppse_response(uint8_t* buff, uint16_t len, EmvApplication* app); + +uint16_t emv_prepare_select_app(uint8_t* dest, EmvApplication* app); +bool emv_decode_select_app_response(uint8_t* buff, uint16_t len, EmvApplication* app); + +uint16_t emv_prepare_get_proc_opt(uint8_t* dest, EmvApplication* app); +bool emv_decode_get_proc_opt(uint8_t* buff, uint16_t len, EmvApplication* app); + +uint16_t emv_prepare_read_sfi_record(uint8_t* dest, uint8_t sfi, uint8_t record_num); +bool emv_decode_read_sfi_record(uint8_t* buff, uint16_t len, EmvApplication* app);