[FL-1595] Add EMV tags (#625)
* nfc: add expiration date tag to emv parser * nfc: add expiration date save and display * nfc: add long apdu test command
This commit is contained in:
		
							parent
							
								
									841804026e
								
							
						
					
					
						commit
						5741ed2bd5
					
				| @ -203,6 +203,10 @@ uint16_t nfc_device_prepare_bank_card_string(NfcDevice* dev, string_t bank_card_ | |||||||
|     for(uint8_t i = 0; i < sizeof(data->number); i++) { |     for(uint8_t i = 0; i < sizeof(data->number); i++) { | ||||||
|         string_cat_printf(bank_card_string, " %02X", data->number[i]); |         string_cat_printf(bank_card_string, " %02X", data->number[i]); | ||||||
|     } |     } | ||||||
|  |     if(data->exp_mon) { | ||||||
|  |         string_cat_printf( | ||||||
|  |             bank_card_string, "\nExp date: %02X/%02X", data->exp_mon, data->exp_year); | ||||||
|  |     } | ||||||
|     return string_size(bank_card_string); |     return string_size(bank_card_string); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -236,6 +240,14 @@ bool nfc_device_parse_bank_card_string(NfcDevice* dev, string_t bank_card_string | |||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         parsed = true; |         parsed = true; | ||||||
|  |         // Check expiration date presence
 | ||||||
|  |         ws = string_search_str(bank_card_string, "Exp date: "); | ||||||
|  |         if(ws != STRING_FAILURE) { | ||||||
|  |             // strlen("Exp date: ") = 10
 | ||||||
|  |             string_right(bank_card_string, 10); | ||||||
|  |             nfc_device_read_hex(bank_card_string, &data->exp_mon, 1); | ||||||
|  |             nfc_device_read_hex(bank_card_string, &data->exp_year, 1); | ||||||
|  |         } | ||||||
|     } while(0); |     } while(0); | ||||||
| 
 | 
 | ||||||
|     return parsed; |     return parsed; | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ typedef struct { | |||||||
|     uint16_t aid_len; |     uint16_t aid_len; | ||||||
|     uint8_t number[8]; |     uint8_t number[8]; | ||||||
|     uint8_t exp_mon; |     uint8_t exp_mon; | ||||||
|     uint16_t exp_year; |     uint8_t exp_year; | ||||||
|     char cardholder[32]; |     char cardholder[32]; | ||||||
| } NfcEmvData; | } NfcEmvData; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -81,8 +81,8 @@ void nfc_worker_task(void* context) { | |||||||
|         nfc_worker_read_emv_app(nfc_worker); |         nfc_worker_read_emv_app(nfc_worker); | ||||||
|     } else if(nfc_worker->state == NfcWorkerStateReadEMV) { |     } else if(nfc_worker->state == NfcWorkerStateReadEMV) { | ||||||
|         nfc_worker_read_emv(nfc_worker); |         nfc_worker_read_emv(nfc_worker); | ||||||
|     } else if(nfc_worker->state == NfcWorkerStateEmulateEMV) { |     } else if(nfc_worker->state == NfcWorkerStateEmulateApdu) { | ||||||
|         nfc_worker_emulate_emv(nfc_worker); |         nfc_worker_emulate_apdu(nfc_worker); | ||||||
|     } else if(nfc_worker->state == NfcWorkerStateReadMifareUl) { |     } else if(nfc_worker->state == NfcWorkerStateReadMifareUl) { | ||||||
|         nfc_worker_read_mifare_ul(nfc_worker); |         nfc_worker_read_mifare_ul(nfc_worker); | ||||||
|     } else if(nfc_worker->state == NfcWorkerStateEmulateMifareUl) { |     } else if(nfc_worker->state == NfcWorkerStateEmulateMifareUl) { | ||||||
| @ -324,6 +324,10 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) { | |||||||
|                             result->emv_data.number, |                             result->emv_data.number, | ||||||
|                             emv_app.card_number, |                             emv_app.card_number, | ||||||
|                             sizeof(emv_app.card_number)); |                             sizeof(emv_app.card_number)); | ||||||
|  |                         if(emv_app.exp_month) { | ||||||
|  |                             result->emv_data.exp_mon = emv_app.exp_month; | ||||||
|  |                             result->emv_data.exp_year = emv_app.exp_year; | ||||||
|  |                         } | ||||||
|                         // Notify caller and exit
 |                         // Notify caller and exit
 | ||||||
|                         if(nfc_worker->callback) { |                         if(nfc_worker->callback) { | ||||||
|                             nfc_worker->callback(nfc_worker->context); |                             nfc_worker->callback(nfc_worker->context); | ||||||
| @ -348,7 +352,7 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void nfc_worker_emulate_emv(NfcWorker* nfc_worker) { | void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { | ||||||
|     ReturnCode err; |     ReturnCode err; | ||||||
|     uint8_t tx_buff[255] = {}; |     uint8_t tx_buff[255] = {}; | ||||||
|     uint16_t tx_len = 0; |     uint16_t tx_len = 0; | ||||||
| @ -362,8 +366,46 @@ void nfc_worker_emulate_emv(NfcWorker* nfc_worker) { | |||||||
|         .device = NfcDeviceNfca, |         .device = NfcDeviceNfca, | ||||||
|         .protocol = NfcDeviceProtocolEMV, |         .protocol = NfcDeviceProtocolEMV, | ||||||
|     }; |     }; | ||||||
|  |     // Test RX data
 | ||||||
|  |     const uint8_t debug_rx[] = { | ||||||
|  |         0xba, 0x0b, 0xba, 0xba, 0x20, 0x00, 0x02, 0x28, 0xde, 0xad, 0xbe, 0xef, 0x00, 0xca, 0xca, | ||||||
|  |         0xca, 0xfe, 0xfa, 0xce, 0x14, 0x88, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, | ||||||
|  |         0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0xba, | ||||||
|  |         0x0b, 0xba, 0xba, 0x20, 0x00, 0x02, 0x28, 0xde, 0xad, 0xbe, 0xef, 0x00, 0xca, 0xca, 0xca, | ||||||
|  |         0xfe, 0xfa, 0xce, 0x14, 0x88, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, | ||||||
|  |         0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0xba, 0x0b, | ||||||
|  |         0xba, 0xba, 0x20, 0x00, 0x02, 0x28, 0xde, 0xad, 0xbe, 0xef, 0x00, 0xca, 0xca, 0xca, 0xfe, | ||||||
|  |         0xfa, 0xce, 0x14, 0x88, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, | ||||||
|  |         0xbb, 0xcc, 0xdd, 0xee, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0xba, 0x0b, 0xba, | ||||||
|  |         0xba, 0x20, 0x00, 0x02, 0x28, 0xde, 0xad, 0xbe, 0xef, 0x00, 0xca, 0xca, 0xca, 0xfe, 0xfa, | ||||||
|  |         0xce, 0x14, 0x88, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, | ||||||
|  |         0xcc, 0xdd, 0xee, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0xba, 0x0b, 0xba, 0xba, | ||||||
|  |         0x20, 0x00, 0x02, 0x28, 0xde, 0xad, 0xbe, 0xef, 0x00, 0xca, 0xca, 0xca, 0xfe, 0xfa, 0xce, | ||||||
|  |         0x14, 0x88, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, | ||||||
|  |         0xdd, 0xee, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0xba, 0x0b, 0xba, 0xba, 0x20, | ||||||
|  |         0x00, 0x02, 0x28, 0xde, 0xad, 0xbe, 0xef, 0x00, 0xca, 0xca, 0xca, 0xfe, 0xfa, 0xce, 0x14, | ||||||
|  |         0x88, 0x00}; | ||||||
|  |     // Test TX data
 | ||||||
|  |     const uint8_t debug_tx[] = { | ||||||
|  |         0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xff, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, | ||||||
|  |         0x10, 0x14, 0x88, 0x02, 0x28, 0x00, 0x00, 0xca, 0xca, 0x00, 0xc0, 0xc0, 0x00, 0xde, 0xad, | ||||||
|  |         0xbe, 0xef, 0xce, 0xee, 0xec, 0xca, 0xfe, 0xba, 0xba, 0xb0, 0xb0, 0xac, 0xdc, 0x11, 0x12, | ||||||
|  |         0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xff, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, | ||||||
|  |         0x14, 0x88, 0x02, 0x28, 0x00, 0x00, 0xca, 0xca, 0x00, 0xc0, 0xc0, 0x00, 0xde, 0xad, 0xbe, | ||||||
|  |         0xef, 0xce, 0xee, 0xec, 0xca, 0xfe, 0xba, 0xba, 0xb0, 0xb0, 0xac, 0xdc, 0x11, 0x12, 0x34, | ||||||
|  |         0x56, 0x78, 0x9a, 0xbc, 0xde, 0xff, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x14, | ||||||
|  |         0x88, 0x02, 0x28, 0x00, 0x00, 0xca, 0xca, 0x00, 0xc0, 0xc0, 0x00, 0xde, 0xad, 0xbe, 0xef, | ||||||
|  |         0xce, 0xee, 0xec, 0xca, 0xfe, 0xba, 0xba, 0xb0, 0xb0, 0xac, 0xdc, 0x11, 0x12, 0x34, 0x56, | ||||||
|  |         0x78, 0x9a, 0xbc, 0xde, 0xff, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x14, 0x88, | ||||||
|  |         0x02, 0x28, 0x00, 0x00, 0xca, 0xca, 0x00, 0xc0, 0xc0, 0x00, 0xde, 0xad, 0xbe, 0xef, 0xce, | ||||||
|  |         0xee, 0xec, 0xca, 0xfe, 0xba, 0xba, 0xb0, 0xb0, 0xac, 0xdc, 0x11, 0x12, 0x34, 0x56, 0x78, | ||||||
|  |         0x9a, 0xbc, 0xde, 0xff, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x14, 0x88, 0x02, | ||||||
|  |         0x28, 0x00, 0x00, 0xca, 0xca, 0x00, 0xc0, 0xc0, 0x00, 0xde, 0xad, 0xbe, 0xef, 0xce, 0xee, | ||||||
|  |         0xec, 0xca, 0xfe, 0xba, 0xba, 0xb0, 0xb0, 0xac, 0xdc, 0x11, 0x12, 0x34, 0x56, 0x78, 0x9a, | ||||||
|  |         0xbc, 0xde, 0xff, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x14, 0x88, 0x02, 0x28, | ||||||
|  |         0x00, 0x00}; | ||||||
| 
 | 
 | ||||||
|     while(nfc_worker->state == NfcWorkerStateEmulateEMV) { |     while(nfc_worker->state == NfcWorkerStateEmulateApdu) { | ||||||
|         if(api_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, 300)) { |         if(api_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, 300)) { | ||||||
|             FURI_LOG_I(NFC_WORKER_TAG, "POS terminal detected"); |             FURI_LOG_I(NFC_WORKER_TAG, "POS terminal detected"); | ||||||
|             // Read data from POS terminal
 |             // Read data from POS terminal
 | ||||||
| @ -401,7 +443,23 @@ void nfc_worker_emulate_emv(NfcWorker* nfc_worker) { | |||||||
|             tx_len = emv_get_proc_opt_ans(tx_buff); |             tx_len = emv_get_proc_opt_ans(tx_buff); | ||||||
|             err = api_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); |             err = api_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); | ||||||
|             if(err == ERR_NONE) { |             if(err == ERR_NONE) { | ||||||
|                 FURI_LOG_I(NFC_WORKER_TAG, "Received PDOL"); |                 FURI_LOG_I(NFC_WORKER_TAG, "Transive PDOL ANS"); | ||||||
|  |             } else { | ||||||
|  |                 FURI_LOG_E(NFC_WORKER_TAG, "Error in 4rd data exchange: Transive PDOL ANS"); | ||||||
|  |                 api_hal_nfc_deactivate(); | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if(*rx_len != sizeof(debug_rx) || memcmp(rx_buff, debug_rx, sizeof(debug_rx))) { | ||||||
|  |                 FURI_LOG_E(NFC_WORKER_TAG, "Failed long message test"); | ||||||
|  |             } else { | ||||||
|  |                 FURI_LOG_I(NFC_WORKER_TAG, "Correct debug message received"); | ||||||
|  |                 tx_len = sizeof(debug_tx); | ||||||
|  |                 err = api_hal_nfc_data_exchange( | ||||||
|  |                     (uint8_t*)debug_tx, tx_len, &rx_buff, &rx_len, false); | ||||||
|  |                 if(err == ERR_NONE) { | ||||||
|  |                     FURI_LOG_I(NFC_WORKER_TAG, "Transive Debug message"); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             api_hal_nfc_deactivate(); |             api_hal_nfc_deactivate(); | ||||||
|         } else { |         } else { | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ typedef enum { | |||||||
|     NfcWorkerStateEmulate, |     NfcWorkerStateEmulate, | ||||||
|     NfcWorkerStateReadEMVApp, |     NfcWorkerStateReadEMVApp, | ||||||
|     NfcWorkerStateReadEMV, |     NfcWorkerStateReadEMV, | ||||||
|     NfcWorkerStateEmulateEMV, |     NfcWorkerStateEmulateApdu, | ||||||
|     NfcWorkerStateField, |     NfcWorkerStateField, | ||||||
|     NfcWorkerStateReadMifareUl, |     NfcWorkerStateReadMifareUl, | ||||||
|     NfcWorkerStateEmulateMifareUl, |     NfcWorkerStateEmulateMifareUl, | ||||||
|  | |||||||
| @ -36,7 +36,7 @@ void nfc_worker_read_emv_app(NfcWorker* nfc_worker); | |||||||
| 
 | 
 | ||||||
| void nfc_worker_read_emv(NfcWorker* nfc_worker); | void nfc_worker_read_emv(NfcWorker* nfc_worker); | ||||||
| 
 | 
 | ||||||
| void nfc_worker_emulate_emv(NfcWorker* nfc_worker); | void nfc_worker_emulate_apdu(NfcWorker* nfc_worker); | ||||||
| 
 | 
 | ||||||
| void nfc_worker_detect(NfcWorker* nfc_worker); | void nfc_worker_detect(NfcWorker* nfc_worker); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ const void nfc_scene_emulate_apdu_sequence_on_enter(void* context) { | |||||||
|     // Setup and start worker
 |     // Setup and start worker
 | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); | ||||||
|     nfc_worker_start(nfc->worker, NfcWorkerStateEmulateEMV, &nfc->dev.dev_data, NULL, nfc); |     nfc_worker_start(nfc->worker, NfcWorkerStateEmulateApdu, &nfc->dev.dev_data, NULL, nfc); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const bool nfc_scene_emulate_apdu_sequence_on_event(void* context, SceneManagerEvent event) { | const bool nfc_scene_emulate_apdu_sequence_on_event(void* context, SceneManagerEvent event) { | ||||||
|  | |||||||
| @ -59,6 +59,12 @@ void nfc_scene_read_emv_data_success_on_enter(void* context) { | |||||||
|     char sak_str[16]; |     char sak_str[16]; | ||||||
|     snprintf(sak_str, sizeof(sak_str), "SAK: %02X", nfc_data->sak); |     snprintf(sak_str, sizeof(sak_str), "SAK: %02X", nfc_data->sak); | ||||||
|     widget_add_string_element(nfc->widget, 121, 42, AlignRight, AlignTop, FontSecondary, sak_str); |     widget_add_string_element(nfc->widget, 121, 42, AlignRight, AlignTop, FontSecondary, sak_str); | ||||||
|  |     if(emv_data->exp_mon) { | ||||||
|  |         char exp_str[16]; | ||||||
|  |         snprintf( | ||||||
|  |             exp_str, sizeof(exp_str), "Exp: %02X/%02X", emv_data->exp_mon, emv_data->exp_year); | ||||||
|  |         widget_add_string_element(nfc->widget, 7, 32, AlignLeft, AlignTop, FontSecondary, exp_str); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); | ||||||
| } | } | ||||||
|  | |||||||
| @ -49,10 +49,10 @@ void bank_card_set_number(BankCard* bank_card, uint8_t* number) { | |||||||
|     string_clear(num_str); |     string_clear(num_str); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bank_card_set_exp_date(BankCard* bank_card, uint8_t mon, uint16_t year) { | void bank_card_set_exp_date(BankCard* bank_card, uint8_t mon, uint8_t year) { | ||||||
|     furi_assert(bank_card); |     furi_assert(bank_card); | ||||||
|     char exp_date_str[16]; |     char exp_date_str[16]; | ||||||
|     snprintf(exp_date_str, sizeof(exp_date_str), "Exp: %02d/%02d", mon, year % 100); |     snprintf(exp_date_str, sizeof(exp_date_str), "Exp: %02X/%02X", mon, year); | ||||||
|     widget_add_string_element( |     widget_add_string_element( | ||||||
|         bank_card->widget, 122, 54, AlignRight, AlignBottom, FontSecondary, exp_date_str); |         bank_card->widget, 122, 54, AlignRight, AlignBottom, FontSecondary, exp_date_str); | ||||||
| } | } | ||||||
|  | |||||||
| @ -18,6 +18,6 @@ void bank_card_set_name(BankCard* bank_card, char* name); | |||||||
| 
 | 
 | ||||||
| void bank_card_set_number(BankCard* bank_card, uint8_t* number); | void bank_card_set_number(BankCard* bank_card, uint8_t* number); | ||||||
| 
 | 
 | ||||||
| void bank_card_set_exp_date(BankCard* bank_card, uint8_t mon, uint16_t year); | void bank_card_set_exp_date(BankCard* bank_card, uint8_t mon, uint8_t year); | ||||||
| 
 | 
 | ||||||
| void bank_card_set_cardholder_name(BankCard* bank_card, char* name); | void bank_card_set_cardholder_name(BankCard* bank_card, char* name); | ||||||
|  | |||||||
| @ -214,13 +214,18 @@ uint16_t emv_prepare_read_sfi_record(uint8_t* dest, uint8_t sfi, uint8_t record_ | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool emv_decode_read_sfi_record(uint8_t* buff, uint16_t len, EmvApplication* app) { | bool emv_decode_read_sfi_record(uint8_t* buff, uint16_t len, EmvApplication* app) { | ||||||
|  |     bool pan_parsed = false; | ||||||
|     for(uint16_t i = 0; i < len; i++) { |     for(uint16_t i = 0; i < len; i++) { | ||||||
|         if(buff[i] == EMV_TAG_PAN) { |         if(buff[i] == EMV_TAG_PAN) { | ||||||
|             memcpy(app->card_number, &buff[i + 2], 8); |             memcpy(app->card_number, &buff[i + 2], 8); | ||||||
|             return true; |             pan_parsed = true; | ||||||
|  |         } else if((buff[i] << 8 | buff[i + 1]) == EMV_TAG_EXP_DATE) { | ||||||
|  |             i += 3; | ||||||
|  |             app->exp_year = buff[i++]; | ||||||
|  |             app->exp_month = buff[i++]; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     return false; |     return pan_parsed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| uint16_t emv_select_ppse_ans(uint8_t* buff) { | uint16_t emv_select_ppse_ans(uint8_t* buff) { | ||||||
|  | |||||||
| @ -16,6 +16,8 @@ | |||||||
| #define EMV_TAG_CARD_NUM 0x57 | #define EMV_TAG_CARD_NUM 0x57 | ||||||
| #define EMV_TAG_PAN 0x5A | #define EMV_TAG_PAN 0x5A | ||||||
| #define EMV_TAG_AFL 0x94 | #define EMV_TAG_AFL 0x94 | ||||||
|  | #define EMV_TAG_EXP_DATE 0x5F24 | ||||||
|  | #define EMV_TAG_CARDHOLDER_NAME 0x5F20 | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint16_t tag; |     uint16_t tag; | ||||||
| @ -35,6 +37,9 @@ typedef struct { | |||||||
|     uint8_t aid_len; |     uint8_t aid_len; | ||||||
|     char name[32]; |     char name[32]; | ||||||
|     uint8_t card_number[8]; |     uint8_t card_number[8]; | ||||||
|  |     uint8_t exp_month; | ||||||
|  |     uint8_t exp_year; | ||||||
|  |     char crdholder_name[32]; | ||||||
|     APDU pdol; |     APDU pdol; | ||||||
|     APDU afl; |     APDU afl; | ||||||
| } EmvApplication; | } EmvApplication; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 gornekich
						gornekich