Преглед изворни кода

[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
gornekich пре 4 година
родитељ
комит
5741ed2bd5

+ 12 - 0
applications/nfc/nfc_device.c

@@ -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;

+ 1 - 1
applications/nfc/nfc_device.h

@@ -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;
 

+ 64 - 6
applications/nfc/nfc_worker.c

@@ -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,
     };
-
-    while(nfc_worker->state == NfcWorkerStateEmulateEMV) {
+    // 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 == 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 {

+ 1 - 1
applications/nfc/nfc_worker.h

@@ -14,7 +14,7 @@ typedef enum {
     NfcWorkerStateEmulate,
     NfcWorkerStateReadEMVApp,
     NfcWorkerStateReadEMV,
-    NfcWorkerStateEmulateEMV,
+    NfcWorkerStateEmulateApdu,
     NfcWorkerStateField,
     NfcWorkerStateReadMifareUl,
     NfcWorkerStateEmulateMifareUl,

+ 1 - 1
applications/nfc/nfc_worker_i.h

@@ -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);
 

+ 1 - 1
applications/nfc/scenes/nfc_scene_emulate_apdu_sequence.c

@@ -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) {

+ 6 - 0
applications/nfc/scenes/nfc_scene_read_emv_data_success.c

@@ -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);
 }

+ 2 - 2
applications/nfc/views/bank_card.c

@@ -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);
 }

+ 1 - 1
applications/nfc/views/bank_card.h

@@ -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);

+ 7 - 2
lib/nfc_protocols/emv_decoder.c

@@ -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) {

+ 5 - 0
lib/nfc_protocols/emv_decoder.h

@@ -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;