[FL-663] Read EMV cards (#460)
* 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: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									df4a170213
								
							
						
					
					
						commit
						725981f431
					
				| @ -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; | ||||
|  | ||||
| @ -27,6 +27,7 @@ struct Nfc { | ||||
|     Submenu* submenu; | ||||
| 
 | ||||
|     View* view_detect; | ||||
|     View* view_read_emv; | ||||
|     View* view_emulate; | ||||
|     View* view_field; | ||||
|     View* view_cli; | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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); | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										151
									
								
								applications/nfc/nfc_worker.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										151
									
								
								applications/nfc/nfc_worker.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -1,12 +1,15 @@ | ||||
| #include "nfc_worker_i.h" | ||||
| #include <api-hal.h> | ||||
| #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) { | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
| @ -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(); | ||||
| } | ||||
|  | ||||
| @ -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(); | ||||
| } | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
							
								
								
									
										205
									
								
								lib/nfc_protocols/emv_decoder.c
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										205
									
								
								lib/nfc_protocols/emv_decoder.c
									
									
									
									
									
										Executable file
									
								
							| @ -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; | ||||
| } | ||||
							
								
								
									
										50
									
								
								lib/nfc_protocols/emv_decoder.h
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										50
									
								
								lib/nfc_protocols/emv_decoder.h
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,50 @@ | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #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); | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 gornekich
						gornekich