[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) { |     if(index == 0) { | ||||||
|         message.type = NfcMessageTypeDetect; |         message.type = NfcMessageTypeDetect; | ||||||
|     } else if(index == 1) { |     } else if(index == 1) { | ||||||
|         message.type = NfcMessageTypeEmulate; |         message.type = NfcMessageTypeReadEMV; | ||||||
|     } else if(index == 2) { |     } else if(index == 2) { | ||||||
|  |         message.type = NfcMessageTypeEmulate; | ||||||
|  |     } else if(index == 3) { | ||||||
|         message.type = NfcMessageTypeField; |         message.type = NfcMessageTypeField; | ||||||
|     } |     } | ||||||
|     furi_check(osMessageQueuePut(message_queue, &message, 0, osWaitForever) == osOK); |     furi_check(osMessageQueuePut(message_queue, &message, 0, osWaitForever) == osOK); | ||||||
| @ -49,8 +51,9 @@ Nfc* nfc_alloc() { | |||||||
|     // Menu
 |     // Menu
 | ||||||
|     nfc->submenu = submenu_alloc(); |     nfc->submenu = submenu_alloc(); | ||||||
|     submenu_add_item(nfc->submenu, "Detect", 0, nfc_menu_callback, nfc); |     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, "Read EMV", 1, nfc_menu_callback, nfc); | ||||||
|     submenu_add_item(nfc->submenu, "Field", 2, 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* submenu_view = submenu_get_view(nfc->submenu); | ||||||
|     view_set_previous_callback(submenu_view, nfc_view_exit); |     view_set_previous_callback(submenu_view, nfc_view_exit); | ||||||
|     view_dispatcher_add_view(nfc->view_dispatcher, NfcViewMenu, submenu_view); |     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_allocate_model(nfc->view_detect, ViewModelTypeLocking, sizeof(NfcViewReadModel)); | ||||||
|     view_dispatcher_add_view(nfc->view_dispatcher, NfcViewRead, nfc->view_detect); |     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
 |     // Emulate
 | ||||||
|     nfc->view_emulate = view_alloc(); |     nfc->view_emulate = view_alloc(); | ||||||
|     view_set_context(nfc->view_emulate, nfc); |     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"); |     printf("Detecting nfc...\r\nPress Ctrl+C to abort\r\n"); | ||||||
|     while(!cmd_exit) { |     while(!cmd_exit) { | ||||||
|         cmd_exit |= cli_cmd_interrupt_received(cli); |         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) { |         if(dev_cnt > 0) { | ||||||
|             printf("Found %d devices\r\n", dev_cnt); |             printf("Found %d devices\r\n", dev_cnt); | ||||||
|             for(uint8_t i = 0; i < dev_cnt; i++) { |             for(uint8_t i = 0; i < dev_cnt; i++) { | ||||||
| @ -196,6 +207,13 @@ int32_t nfc_task(void* p) { | |||||||
|                     return true; |                     return true; | ||||||
|                 }); |                 }); | ||||||
|             nfc_start(nfc, NfcViewRead, NfcWorkerStatePoll); |             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) { |         } else if(message.type == NfcMessageTypeEmulate) { | ||||||
|             nfc_start(nfc, NfcViewEmulate, NfcWorkerStateEmulate); |             nfc_start(nfc, NfcViewEmulate, NfcWorkerStateEmulate); | ||||||
|         } else if(message.type == NfcMessageTypeField) { |         } else if(message.type == NfcMessageTypeField) { | ||||||
| @ -215,6 +233,19 @@ int32_t nfc_task(void* p) { | |||||||
|                     model->found = false; |                     model->found = false; | ||||||
|                     return true; |                     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) { |         } else if(message.type == NfcMessageTypeExit) { | ||||||
|             nfc_free(nfc); |             nfc_free(nfc); | ||||||
|             break; |             break; | ||||||
|  | |||||||
| @ -27,6 +27,7 @@ struct Nfc { | |||||||
|     Submenu* submenu; |     Submenu* submenu; | ||||||
| 
 | 
 | ||||||
|     View* view_detect; |     View* view_detect; | ||||||
|  |     View* view_read_emv; | ||||||
|     View* view_emulate; |     View* view_emulate; | ||||||
|     View* view_field; |     View* view_field; | ||||||
|     View* view_cli; |     View* view_cli; | ||||||
|  | |||||||
| @ -44,9 +44,15 @@ typedef enum { | |||||||
|     NfcDeviceTypeNfcb, |     NfcDeviceTypeNfcb, | ||||||
|     NfcDeviceTypeNfcf, |     NfcDeviceTypeNfcf, | ||||||
|     NfcDeviceTypeNfcv, |     NfcDeviceTypeNfcv, | ||||||
|     NfcDeviceTypeNfcMifare |     NfcDeviceTypeNfcMifare, | ||||||
|  |     NfcDeviceTypeEMV, | ||||||
| } NfcDeviceType; | } NfcDeviceType; | ||||||
| 
 | 
 | ||||||
|  | typedef struct { | ||||||
|  |     char name[32]; | ||||||
|  |     uint8_t number[8]; | ||||||
|  | } EMVCard; | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     NfcDeviceType type; |     NfcDeviceType type; | ||||||
|     union { |     union { | ||||||
| @ -54,6 +60,7 @@ typedef struct { | |||||||
|         rfalNfcbListenDevice nfcb; |         rfalNfcbListenDevice nfcb; | ||||||
|         rfalNfcfListenDevice nfcf; |         rfalNfcfListenDevice nfcf; | ||||||
|         rfalNfcvListenDevice nfcv; |         rfalNfcvListenDevice nfcv; | ||||||
|  |         EMVCard emv_card; | ||||||
|     }; |     }; | ||||||
| } NfcDevice; | } NfcDevice; | ||||||
| 
 | 
 | ||||||
| @ -64,6 +71,7 @@ typedef enum { | |||||||
|     NfcWorkerStateReady, |     NfcWorkerStateReady, | ||||||
|     // Main worker states
 |     // Main worker states
 | ||||||
|     NfcWorkerStatePoll, |     NfcWorkerStatePoll, | ||||||
|  |     NfcWorkerStateReadEMV, | ||||||
|     NfcWorkerStateEmulate, |     NfcWorkerStateEmulate, | ||||||
|     NfcWorkerStateField, |     NfcWorkerStateField, | ||||||
|     // Transition
 |     // Transition
 | ||||||
| @ -72,6 +80,7 @@ typedef enum { | |||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     NfcMessageTypeDetect, |     NfcMessageTypeDetect, | ||||||
|  |     NfcMessageTypeReadEMV, | ||||||
|     NfcMessageTypeEmulate, |     NfcMessageTypeEmulate, | ||||||
|     NfcMessageTypeField, |     NfcMessageTypeField, | ||||||
|     NfcMessageTypeStop, |     NfcMessageTypeStop, | ||||||
| @ -79,6 +88,8 @@ typedef enum { | |||||||
|     // From Worker
 |     // From Worker
 | ||||||
|     NfcMessageTypeDeviceFound, |     NfcMessageTypeDeviceFound, | ||||||
|     NfcMessageTypeDeviceNotFound, |     NfcMessageTypeDeviceNotFound, | ||||||
|  |     NfcMessageTypeEMVFound, | ||||||
|  |     NfcMessageTypeEMVNotFound, | ||||||
| } NfcMessageType; | } NfcMessageType; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|  | |||||||
| @ -104,6 +104,35 @@ void nfc_view_read_nfcv_draw(Canvas* canvas, NfcViewReadModel* model) { | |||||||
|     canvas_draw_str(canvas, 18, 42, buffer); |     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) { | void nfc_view_emulate_draw(Canvas* canvas, void* model) { | ||||||
|     canvas_clear(canvas); |     canvas_clear(canvas); | ||||||
|     canvas_set_font(canvas, FontPrimary); |     canvas_set_font(canvas, FontPrimary); | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ | |||||||
| typedef enum { | typedef enum { | ||||||
|     NfcViewMenu, |     NfcViewMenu, | ||||||
|     NfcViewRead, |     NfcViewRead, | ||||||
|  |     NfcViewReadEmv, | ||||||
|     NfcViewEmulate, |     NfcViewEmulate, | ||||||
|     NfcViewField, |     NfcViewField, | ||||||
|     NfcViewError, |     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_nfcb_draw(Canvas* canvas, NfcViewReadModel* model); | ||||||
| void nfc_view_read_nfcf_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_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); | 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 "nfc_worker_i.h" | ||||||
| #include <api-hal.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_alloc(osMessageQueueId_t message_queue) { | ||||||
|     NfcWorker* nfc_worker = furi_alloc(sizeof(NfcWorker)); |     NfcWorker* nfc_worker = furi_alloc(sizeof(NfcWorker)); | ||||||
|     nfc_worker->message_queue = message_queue; |     nfc_worker->message_queue = message_queue; | ||||||
|     // Worker thread attributes
 |     // Worker thread attributes
 | ||||||
|     nfc_worker->thread_attr.name = "nfc_worker"; |     nfc_worker->thread_attr.name = "nfc_worker"; | ||||||
|     nfc_worker->thread_attr.stack_size = 2048; |     nfc_worker->thread_attr.stack_size = 8192; | ||||||
|     // Initialize rfal
 |     // Initialize rfal
 | ||||||
|     nfc_worker->error = api_hal_nfc_init(); |     nfc_worker->error = api_hal_nfc_init(); | ||||||
|     if(nfc_worker->error == ERR_NONE) { |     if(nfc_worker->error == ERR_NONE) { | ||||||
| @ -59,17 +62,159 @@ void nfc_worker_task(void* context) { | |||||||
| 
 | 
 | ||||||
|     if(nfc_worker->state == NfcWorkerStatePoll) { |     if(nfc_worker->state == NfcWorkerStatePoll) { | ||||||
|         nfc_worker_poll(nfc_worker); |         nfc_worker_poll(nfc_worker); | ||||||
|  |     } else if(nfc_worker->state == NfcWorkerStateReadEMV) { | ||||||
|  |         nfc_worker_read_emv(nfc_worker); | ||||||
|     } else if(nfc_worker->state == NfcWorkerStateEmulate) { |     } else if(nfc_worker->state == NfcWorkerStateEmulate) { | ||||||
|         nfc_worker_emulate(nfc_worker); |         nfc_worker_emulate(nfc_worker); | ||||||
|     } else if(nfc_worker->state == NfcWorkerStateField) { |     } else if(nfc_worker->state == NfcWorkerStateField) { | ||||||
|         nfc_worker_field(nfc_worker); |         nfc_worker_field(nfc_worker); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); |     nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); | ||||||
|     api_hal_power_insomnia_exit(); |     api_hal_power_insomnia_exit(); | ||||||
|     osThreadExit(); |     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) { | void nfc_worker_poll(NfcWorker* nfc_worker) { | ||||||
|     rfalNfcDevice* dev_list; |     rfalNfcDevice* dev_list; | ||||||
|     uint8_t dev_cnt; |     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); |     furi_check(osMessageQueuePut(nfc_worker->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
| 
 | 
 | ||||||
|     while(nfc_worker->state == NfcWorkerStatePoll) { |     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
 |             // Send message with first device found
 | ||||||
|             message.type = NfcMessageTypeDeviceFound; |             message.type = NfcMessageTypeDeviceFound; | ||||||
|             if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCA) { |             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_task(void* context); | ||||||
| 
 | 
 | ||||||
|  | void nfc_worker_read_emv(NfcWorker* nfc_worker); | ||||||
|  | 
 | ||||||
| void nfc_worker_poll(NfcWorker* nfc_worker); | void nfc_worker_poll(NfcWorker* nfc_worker); | ||||||
| 
 | 
 | ||||||
| void nfc_worker_emulate(NfcWorker* nfc_worker); | void nfc_worker_emulate(NfcWorker* nfc_worker); | ||||||
|  | |||||||
| @ -42,7 +42,17 @@ void api_hal_nfc_exit_sleep(); | |||||||
| /**
 | /**
 | ||||||
|  * NFC poll |  * 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 | #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_list); | ||||||
|     furi_assert(dev_cnt); |     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); |         osDelay(5); | ||||||
|     } |     } | ||||||
|     rfalNfcDeactivate(false); |     if(deactivate) { | ||||||
|     rfalLowPowerModeStart(); |         rfalNfcDeactivate(false); | ||||||
|  |         rfalLowPowerModeStart(); | ||||||
|  |     } | ||||||
|     if(!cycles) { |     if(!cycles) { | ||||||
|         FURI_LOG_D("HAL NFC", "Timeout"); |         FURI_LOG_D("HAL NFC", "Timeout"); | ||||||
|         return false; |         return false; | ||||||
| @ -83,3 +85,44 @@ bool api_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t cyc | |||||||
| 
 | 
 | ||||||
|     return true; |     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_list); | ||||||
|     furi_assert(dev_cnt); |     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); |         osDelay(5); | ||||||
|     } |     } | ||||||
|     rfalNfcDeactivate(false); |     if(deactivate) { | ||||||
|     rfalLowPowerModeStart(); |         rfalNfcDeactivate(false); | ||||||
|  |         rfalLowPowerModeStart(); | ||||||
|  |     } | ||||||
|     if(!cycles) { |     if(!cycles) { | ||||||
|         FURI_LOG_D("HAL NFC", "Timeout"); |         FURI_LOG_D("HAL NFC", "Timeout"); | ||||||
|         return false; |         return false; | ||||||
| @ -83,3 +85,44 @@ bool api_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t cyc | |||||||
| 
 | 
 | ||||||
|     return true; |     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)/*.c) | ||||||
| C_SOURCES		+= $(wildcard $(ST25RFAL002_DIR)/source/*.c) | C_SOURCES		+= $(wildcard $(ST25RFAL002_DIR)/source/*.c) | ||||||
| C_SOURCES		+= $(wildcard $(ST25RFAL002_DIR)/source/st25r3916/*.c) | C_SOURCES		+= $(wildcard $(ST25RFAL002_DIR)/source/st25r3916/*.c) | ||||||
|  | 
 | ||||||
|  | CFLAGS			+= -I$(LIB_DIR)/nfc_protocols | ||||||
|  | C_SOURCES		+= $(wildcard $(LIB_DIR)/nfc_protocols/*.c) | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| # callback connector (C to CPP) library
 | # 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