[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:
		| @@ -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++) { | ||||
|         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); | ||||
| } | ||||
|  | ||||
| @@ -236,6 +240,14 @@ bool nfc_device_parse_bank_card_string(NfcDevice* dev, string_t bank_card_string | ||||
|             break; | ||||
|         } | ||||
|         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); | ||||
|  | ||||
|     return parsed; | ||||
|   | ||||
| @@ -42,7 +42,7 @@ typedef struct { | ||||
|     uint16_t aid_len; | ||||
|     uint8_t number[8]; | ||||
|     uint8_t exp_mon; | ||||
|     uint16_t exp_year; | ||||
|     uint8_t exp_year; | ||||
|     char cardholder[32]; | ||||
| } NfcEmvData; | ||||
|  | ||||
|   | ||||
| @@ -81,8 +81,8 @@ void nfc_worker_task(void* context) { | ||||
|         nfc_worker_read_emv_app(nfc_worker); | ||||
|     } else if(nfc_worker->state == NfcWorkerStateReadEMV) { | ||||
|         nfc_worker_read_emv(nfc_worker); | ||||
|     } else if(nfc_worker->state == NfcWorkerStateEmulateEMV) { | ||||
|         nfc_worker_emulate_emv(nfc_worker); | ||||
|     } else if(nfc_worker->state == NfcWorkerStateEmulateApdu) { | ||||
|         nfc_worker_emulate_apdu(nfc_worker); | ||||
|     } else if(nfc_worker->state == NfcWorkerStateReadMifareUl) { | ||||
|         nfc_worker_read_mifare_ul(nfc_worker); | ||||
|     } else if(nfc_worker->state == NfcWorkerStateEmulateMifareUl) { | ||||
| @@ -324,6 +324,10 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) { | ||||
|                             result->emv_data.number, | ||||
|                             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 | ||||
|                         if(nfc_worker->callback) { | ||||
|                             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; | ||||
|     uint8_t tx_buff[255] = {}; | ||||
|     uint16_t tx_len = 0; | ||||
| @@ -362,8 +366,46 @@ void nfc_worker_emulate_emv(NfcWorker* nfc_worker) { | ||||
|         .device = NfcDeviceNfca, | ||||
|         .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)) { | ||||
|             FURI_LOG_I(NFC_WORKER_TAG, "POS terminal detected"); | ||||
|             // 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); | ||||
|             err = api_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); | ||||
|             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(); | ||||
|         } else { | ||||
|   | ||||
| @@ -14,7 +14,7 @@ typedef enum { | ||||
|     NfcWorkerStateEmulate, | ||||
|     NfcWorkerStateReadEMVApp, | ||||
|     NfcWorkerStateReadEMV, | ||||
|     NfcWorkerStateEmulateEMV, | ||||
|     NfcWorkerStateEmulateApdu, | ||||
|     NfcWorkerStateField, | ||||
|     NfcWorkerStateReadMifareUl, | ||||
|     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_emulate_emv(NfcWorker* nfc_worker); | ||||
| void nfc_worker_emulate_apdu(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 | ||||
|  | ||||
|     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) { | ||||
|   | ||||
| @@ -59,6 +59,12 @@ void nfc_scene_read_emv_data_success_on_enter(void* context) { | ||||
|     char sak_str[16]; | ||||
|     snprintf(sak_str, sizeof(sak_str), "SAK: %02X", nfc_data->sak); | ||||
|     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); | ||||
| } | ||||
|   | ||||
| @@ -49,10 +49,10 @@ void bank_card_set_number(BankCard* bank_card, uint8_t* number) { | ||||
|     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); | ||||
|     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( | ||||
|         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_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); | ||||
|   | ||||
| @@ -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 pan_parsed = false; | ||||
|     for(uint16_t i = 0; i < len; i++) { | ||||
|         if(buff[i] == EMV_TAG_PAN) { | ||||
|             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) { | ||||
|   | ||||
| @@ -16,6 +16,8 @@ | ||||
| #define EMV_TAG_CARD_NUM 0x57 | ||||
| #define EMV_TAG_PAN 0x5A | ||||
| #define EMV_TAG_AFL 0x94 | ||||
| #define EMV_TAG_EXP_DATE 0x5F24 | ||||
| #define EMV_TAG_CARDHOLDER_NAME 0x5F20 | ||||
|  | ||||
| typedef struct { | ||||
|     uint16_t tag; | ||||
| @@ -35,6 +37,9 @@ typedef struct { | ||||
|     uint8_t aid_len; | ||||
|     char name[32]; | ||||
|     uint8_t card_number[8]; | ||||
|     uint8_t exp_month; | ||||
|     uint8_t exp_year; | ||||
|     char crdholder_name[32]; | ||||
|     APDU pdol; | ||||
|     APDU afl; | ||||
| } EmvApplication; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user