mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-13 07:48:35 -07:00
@@ -173,6 +173,15 @@ App(
|
|||||||
sources=["plugins/supported_cards/washcity.c"],
|
sources=["plugins/supported_cards/washcity.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="emv_parser",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="emv_plugin_ep",
|
||||||
|
targets=["f7"],
|
||||||
|
requires=["nfc", "storage"],
|
||||||
|
sources=["plugins/supported_cards/emv.c", "helpers/nfc_emv_parser.c"],
|
||||||
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
appid="ndef_parser",
|
appid="ndef_parser",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ static bool nfc_emv_parser_search_data(
|
|||||||
|
|
||||||
bool nfc_emv_parser_get_aid_name(
|
bool nfc_emv_parser_get_aid_name(
|
||||||
Storage* storage,
|
Storage* storage,
|
||||||
uint8_t* aid,
|
const uint8_t* aid,
|
||||||
uint8_t aid_len,
|
uint8_t aid_len,
|
||||||
FuriString* aid_name) {
|
FuriString* aid_name) {
|
||||||
furi_assert(storage);
|
furi_assert(storage);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
*/
|
*/
|
||||||
bool nfc_emv_parser_get_aid_name(
|
bool nfc_emv_parser_get_aid_name(
|
||||||
Storage* storage,
|
Storage* storage,
|
||||||
uint8_t* aid,
|
const uint8_t* aid,
|
||||||
uint8_t aid_len,
|
uint8_t aid_len,
|
||||||
FuriString* aid_name);
|
FuriString* aid_name);
|
||||||
|
|
||||||
|
|||||||
115
applications/main/nfc/helpers/protocol_support/emv/emv.c
Normal file
115
applications/main/nfc/helpers/protocol_support/emv/emv.c
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#include "emv.h"
|
||||||
|
#include "emv_render.h"
|
||||||
|
|
||||||
|
#include <nfc/protocols/emv/emv_poller.h>
|
||||||
|
|
||||||
|
#include "nfc/nfc_app_i.h"
|
||||||
|
|
||||||
|
#include "../nfc_protocol_support_common.h"
|
||||||
|
#include "../nfc_protocol_support_gui_common.h"
|
||||||
|
#include "../iso14443_4a/iso14443_4a_i.h"
|
||||||
|
|
||||||
|
static void nfc_scene_info_on_enter_emv(NfcApp* instance) {
|
||||||
|
const NfcDevice* device = instance->nfc_device;
|
||||||
|
const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv);
|
||||||
|
|
||||||
|
FuriString* temp_str = furi_string_alloc();
|
||||||
|
// furi_string_cat_printf(
|
||||||
|
// temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||||
|
nfc_render_emv_info(data, NfcProtocolFormatTypeFull, temp_str);
|
||||||
|
|
||||||
|
widget_add_text_scroll_element(
|
||||||
|
instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
|
||||||
|
|
||||||
|
furi_string_free(temp_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_scene_more_info_on_enter_emv(NfcApp* instance) {
|
||||||
|
// Jump to advanced scene right away
|
||||||
|
scene_manager_next_scene(instance->scene_manager, NfcSceneEmvMoreInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
static NfcCommand nfc_scene_read_poller_callback_emv(NfcGenericEvent event, void* context) {
|
||||||
|
furi_assert(event.protocol == NfcProtocolEmv);
|
||||||
|
|
||||||
|
NfcApp* instance = context;
|
||||||
|
const EmvPollerEvent* emv_event = event.event_data;
|
||||||
|
|
||||||
|
if(emv_event->type == EmvPollerEventTypeReadSuccess) {
|
||||||
|
nfc_device_set_data(
|
||||||
|
instance->nfc_device, NfcProtocolEmv, nfc_poller_get_data(instance->poller));
|
||||||
|
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
|
||||||
|
return NfcCommandStop;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NfcCommandContinue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_scene_read_on_enter_emv(NfcApp* instance) {
|
||||||
|
nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_emv, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_scene_read_success_on_enter_emv(NfcApp* instance) {
|
||||||
|
const NfcDevice* device = instance->nfc_device;
|
||||||
|
const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv);
|
||||||
|
|
||||||
|
FuriString* temp_str = furi_string_alloc();
|
||||||
|
// furi_string_cat_printf(
|
||||||
|
// temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||||
|
nfc_render_emv_info(data, NfcProtocolFormatTypeShort, temp_str);
|
||||||
|
|
||||||
|
widget_add_text_scroll_element(
|
||||||
|
instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
|
||||||
|
|
||||||
|
furi_string_free(temp_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// static void nfc_scene_emulate_on_enter_emv(NfcApp* instance) {
|
||||||
|
// const Iso14443_4aData* iso14443_4a_data =
|
||||||
|
// nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a);
|
||||||
|
|
||||||
|
// instance->listener =
|
||||||
|
// nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data);
|
||||||
|
// nfc_listener_start(
|
||||||
|
// instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance);
|
||||||
|
// }
|
||||||
|
|
||||||
|
const NfcProtocolSupportBase nfc_protocol_support_emv = {
|
||||||
|
.features = NfcProtocolFeatureMoreInfo,
|
||||||
|
|
||||||
|
.scene_info =
|
||||||
|
{
|
||||||
|
.on_enter = nfc_scene_info_on_enter_emv,
|
||||||
|
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||||
|
},
|
||||||
|
.scene_more_info =
|
||||||
|
{
|
||||||
|
.on_enter = nfc_scene_more_info_on_enter_emv,
|
||||||
|
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||||
|
},
|
||||||
|
.scene_read =
|
||||||
|
{
|
||||||
|
.on_enter = nfc_scene_read_on_enter_emv,
|
||||||
|
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||||
|
},
|
||||||
|
.scene_read_menu =
|
||||||
|
{
|
||||||
|
.on_enter = nfc_protocol_support_common_on_enter_empty,
|
||||||
|
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||||
|
},
|
||||||
|
.scene_read_success =
|
||||||
|
{
|
||||||
|
.on_enter = nfc_scene_read_success_on_enter_emv,
|
||||||
|
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||||
|
},
|
||||||
|
.scene_saved_menu =
|
||||||
|
{
|
||||||
|
.on_enter = nfc_protocol_support_common_on_enter_empty,
|
||||||
|
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||||
|
},
|
||||||
|
.scene_save_name =
|
||||||
|
{
|
||||||
|
.on_enter = nfc_protocol_support_common_on_enter_empty,
|
||||||
|
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||||
|
},
|
||||||
|
};
|
||||||
5
applications/main/nfc/helpers/protocol_support/emv/emv.h
Normal file
5
applications/main/nfc/helpers/protocol_support/emv/emv.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../nfc_protocol_support_base.h"
|
||||||
|
|
||||||
|
extern const NfcProtocolSupportBase nfc_protocol_support_emv;
|
||||||
182
applications/main/nfc/helpers/protocol_support/emv/emv_render.c
Normal file
182
applications/main/nfc/helpers/protocol_support/emv/emv_render.c
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#include "emv_render.h"
|
||||||
|
|
||||||
|
#include "../iso14443_4a/iso14443_4a_render.h"
|
||||||
|
#include "nfc/nfc_app_i.h"
|
||||||
|
|
||||||
|
void nfc_render_emv_info(const EmvData* data, NfcProtocolFormatType format_type, FuriString* str) {
|
||||||
|
nfc_render_emv_header(str);
|
||||||
|
nfc_render_emv_uid(
|
||||||
|
data->iso14443_4a_data->iso14443_3a_data->uid,
|
||||||
|
data->iso14443_4a_data->iso14443_3a_data->uid_len,
|
||||||
|
str);
|
||||||
|
|
||||||
|
if(format_type == NfcProtocolFormatTypeFull) nfc_render_emv_extra(data, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_render_emv_header(FuriString* str) {
|
||||||
|
furi_string_cat_printf(str, "\e#%s\n", "EMV");
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_render_emv_uid(const uint8_t* uid, const uint8_t uid_len, FuriString* str) {
|
||||||
|
if(uid_len == 0) return;
|
||||||
|
|
||||||
|
furi_string_cat_printf(str, "UID: ");
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < uid_len; i++) {
|
||||||
|
furi_string_cat_printf(str, "%02X ", uid[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_cat_printf(str, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_render_emv_aid(const uint8_t* uid, const uint8_t uid_len, FuriString* str) {
|
||||||
|
if(uid_len == 0) return;
|
||||||
|
|
||||||
|
furi_string_cat_printf(str, "UID: ");
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < uid_len; i++) {
|
||||||
|
furi_string_cat_printf(str, "%02X ", uid[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_cat_printf(str, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_render_emv_data(const EmvData* data, FuriString* str) {
|
||||||
|
nfc_render_emv_pan(data->emv_application.pan, data->emv_application.pan_len, str);
|
||||||
|
nfc_render_emv_name(data->emv_application.name, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_render_emv_pan(const uint8_t* data, const uint8_t len, FuriString* str) {
|
||||||
|
if(len == 0) return;
|
||||||
|
|
||||||
|
FuriString* card_number = furi_string_alloc();
|
||||||
|
for(uint8_t i = 0; i < len; i++) {
|
||||||
|
if((i % 2 == 0) && (i != 0)) furi_string_cat_printf(card_number, " ");
|
||||||
|
furi_string_cat_printf(card_number, "%02X", data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut padding 'F' from card number
|
||||||
|
furi_string_trim(card_number, "F");
|
||||||
|
furi_string_cat(str, card_number);
|
||||||
|
furi_string_free(card_number);
|
||||||
|
|
||||||
|
furi_string_cat_printf(str, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_render_emv_expired(const EmvApplication* apl, FuriString* str) {
|
||||||
|
if(apl->exp_month == 0) return;
|
||||||
|
furi_string_cat_printf(str, "Exp: %02X/%02X\n", apl->exp_month, apl->exp_year);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_render_emv_currency(uint16_t cur_code, FuriString* str) {
|
||||||
|
if(!cur_code) return;
|
||||||
|
|
||||||
|
furi_string_cat_printf(str, "Currency code: %04X\n", cur_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_render_emv_country(uint16_t country_code, FuriString* str) {
|
||||||
|
if(!country_code) return;
|
||||||
|
|
||||||
|
furi_string_cat_printf(str, "Country code: %04X\n", country_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) {
|
||||||
|
const uint8_t len = apl->aid_len;
|
||||||
|
|
||||||
|
if(!len) {
|
||||||
|
furi_string_cat_printf(str, "No Pay Application found\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_cat_printf(str, "AID: ");
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < len; i++) furi_string_cat_printf(str, "%02X", apl->aid[i]);
|
||||||
|
|
||||||
|
furi_string_cat_printf(str, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_render_emv_pin_try_counter(uint8_t counter, FuriString* str) {
|
||||||
|
if(counter == 0xff) return;
|
||||||
|
furi_string_cat_printf(str, "PIN try left: %d\n", counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_render_emv_transactions(const EmvApplication* apl, FuriString* str) {
|
||||||
|
if(apl->transaction_counter)
|
||||||
|
furi_string_cat_printf(str, "Transactions: %d\n", apl->transaction_counter);
|
||||||
|
if(apl->last_online_atc)
|
||||||
|
furi_string_cat_printf(str, "Last Online ATC: %d\n", apl->last_online_atc);
|
||||||
|
|
||||||
|
const uint8_t len = apl->active_tr;
|
||||||
|
if(!len) {
|
||||||
|
furi_string_cat_printf(str, "No transactions info\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
FuriString* tmp = furi_string_alloc();
|
||||||
|
|
||||||
|
//furi_string_cat_printf(str, "Transactions:\n");
|
||||||
|
for(int i = 0; i < len; i++) {
|
||||||
|
if(!apl->trans[i].amount) continue;
|
||||||
|
// transaction counter
|
||||||
|
furi_string_cat_printf(str, "\e#%d: ", apl->trans[i].atc);
|
||||||
|
|
||||||
|
// Print transaction amount
|
||||||
|
uint8_t* a = (uint8_t*)&apl->trans[i].amount;
|
||||||
|
bool top = true;
|
||||||
|
for(int x = 0; x < 6; x++) {
|
||||||
|
// cents
|
||||||
|
if(x == 5) {
|
||||||
|
furi_string_cat_printf(str, ".%02X", a[x]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(a[x]) {
|
||||||
|
if(top) {
|
||||||
|
furi_string_cat_printf(str, "%X", a[x]);
|
||||||
|
top = false;
|
||||||
|
} else {
|
||||||
|
furi_string_cat_printf(str, "%02X", a[x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(apl->trans[i].currency) {
|
||||||
|
furi_string_set_str(tmp, "UNK");
|
||||||
|
nfc_emv_parser_get_currency_name(storage, apl->trans[i].currency, tmp);
|
||||||
|
furi_string_cat_printf(str, " %s\n", furi_string_get_cstr(tmp));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(apl->trans[i].country) {
|
||||||
|
furi_string_set_str(tmp, "UNK");
|
||||||
|
nfc_emv_parser_get_country_name(storage, apl->trans[i].country, tmp);
|
||||||
|
furi_string_cat_printf(str, "Country: %s\n", furi_string_get_cstr(tmp));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(apl->trans[i].date)
|
||||||
|
furi_string_cat_printf(
|
||||||
|
str,
|
||||||
|
"%02lx/%02lx/%02lx ",
|
||||||
|
apl->trans[i].date >> 16,
|
||||||
|
(apl->trans[i].date >> 8) & 0xff,
|
||||||
|
apl->trans[i].date & 0xff);
|
||||||
|
|
||||||
|
if(apl->trans[i].time)
|
||||||
|
furi_string_cat_printf(
|
||||||
|
str,
|
||||||
|
"%02lx:%02lx:%02lx\n",
|
||||||
|
apl->trans[i].time & 0xff,
|
||||||
|
(apl->trans[i].time >> 8) & 0xff,
|
||||||
|
apl->trans[i].time >> 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_free(tmp);
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_render_emv_extra(const EmvData* data, FuriString* str) {
|
||||||
|
nfc_render_emv_application(&data->emv_application, str);
|
||||||
|
|
||||||
|
nfc_render_emv_currency(data->emv_application.currency_code, str);
|
||||||
|
nfc_render_emv_country(data->emv_application.country_code, str);
|
||||||
|
nfc_render_emv_pin_try_counter(data->emv_application.pin_try_counter, str);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <nfc/protocols/emv/emv.h>
|
||||||
|
|
||||||
|
#include "../nfc_protocol_support_render_common.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void nfc_render_emv_info(const EmvData* data, NfcProtocolFormatType format_type, FuriString* str);
|
||||||
|
|
||||||
|
void nfc_render_emv_data(const EmvData* data, FuriString* str);
|
||||||
|
|
||||||
|
void nfc_render_emv_pan(const uint8_t* data, const uint8_t len, FuriString* str);
|
||||||
|
|
||||||
|
void nfc_render_emv_name(const char* data, FuriString* str);
|
||||||
|
|
||||||
|
void nfc_render_emv_application(const EmvApplication* data, FuriString* str);
|
||||||
|
|
||||||
|
void nfc_render_emv_extra(const EmvData* data, FuriString* str);
|
||||||
|
|
||||||
|
void nfc_render_emv_expired(const EmvApplication* apl, FuriString* str);
|
||||||
|
|
||||||
|
void nfc_render_emv_country(uint16_t country_code, FuriString* str);
|
||||||
|
|
||||||
|
void nfc_render_emv_currency(uint16_t cur_code, FuriString* str);
|
||||||
|
|
||||||
|
void nfc_render_emv_transactions(const EmvApplication* data, FuriString* str);
|
||||||
|
|
||||||
|
void nfc_render_emv_uid(const uint8_t* uid, const uint8_t uid_len, FuriString* str);
|
||||||
|
|
||||||
|
void nfc_render_emv_header(FuriString* str);
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "mf_ultralight/mf_ultralight.h"
|
#include "mf_ultralight/mf_ultralight.h"
|
||||||
#include "mf_classic/mf_classic.h"
|
#include "mf_classic/mf_classic.h"
|
||||||
#include "mf_desfire/mf_desfire.h"
|
#include "mf_desfire/mf_desfire.h"
|
||||||
|
#include "emv/emv.h"
|
||||||
#include "slix/slix.h"
|
#include "slix/slix.h"
|
||||||
#include "st25tb/st25tb.h"
|
#include "st25tb/st25tb.h"
|
||||||
|
|
||||||
@@ -41,5 +42,6 @@ const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = {
|
|||||||
[NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire,
|
[NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire,
|
||||||
[NfcProtocolSlix] = &nfc_protocol_support_slix,
|
[NfcProtocolSlix] = &nfc_protocol_support_slix,
|
||||||
[NfcProtocolSt25tb] = &nfc_protocol_support_st25tb,
|
[NfcProtocolSt25tb] = &nfc_protocol_support_st25tb,
|
||||||
|
[NfcProtocolEmv] = &nfc_protocol_support_emv,
|
||||||
/* Add new protocol support implementations here */
|
/* Add new protocol support implementations here */
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "helpers/mf_ultralight_auth.h"
|
#include "helpers/mf_ultralight_auth.h"
|
||||||
#include "helpers/mf_user_dict.h"
|
#include "helpers/mf_user_dict.h"
|
||||||
#include "helpers/mfkey32_logger.h"
|
#include "helpers/mfkey32_logger.h"
|
||||||
|
#include "helpers/nfc_emv_parser.h"
|
||||||
#include "helpers/mf_classic_key_cache.h"
|
#include "helpers/mf_classic_key_cache.h"
|
||||||
#include "helpers/nfc_supported_cards.h"
|
#include "helpers/nfc_supported_cards.h"
|
||||||
|
|
||||||
|
|||||||
134
applications/main/nfc/plugins/supported_cards/emv.c
Normal file
134
applications/main/nfc/plugins/supported_cards/emv.c
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* Parser for EMV cards.
|
||||||
|
*
|
||||||
|
* Copyright 2023 Leptoptilos <leptoptilos@icloud.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include "core/string.h"
|
||||||
|
#include "furi_hal_rtc.h"
|
||||||
|
#include "helpers/nfc_emv_parser.h"
|
||||||
|
#include "nfc_supported_card_plugin.h"
|
||||||
|
|
||||||
|
#include "protocols/emv/emv.h"
|
||||||
|
#include "protocols/nfc_protocol.h"
|
||||||
|
#include <flipper_application/flipper_application.h>
|
||||||
|
|
||||||
|
#include <nfc/nfc_device.h>
|
||||||
|
#include <nfc/helpers/nfc_util.h>
|
||||||
|
|
||||||
|
#define TAG "EMV"
|
||||||
|
|
||||||
|
bool emv_get_currency_name(uint16_t cur_code, FuriString* currency_name) {
|
||||||
|
if(!cur_code) return false;
|
||||||
|
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
|
||||||
|
bool succsess = nfc_emv_parser_get_currency_name(storage, cur_code, currency_name);
|
||||||
|
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
return succsess;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool emv_get_country_name(uint16_t country_code, FuriString* country_name) {
|
||||||
|
if(!country_code) return false;
|
||||||
|
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
|
||||||
|
bool succsess = nfc_emv_parser_get_country_name(storage, country_code, country_name);
|
||||||
|
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
return succsess;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool emv_get_aid_name(const EmvApplication* apl, FuriString* aid_name) {
|
||||||
|
const uint8_t len = apl->aid_len;
|
||||||
|
|
||||||
|
if(!len) return false;
|
||||||
|
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
|
||||||
|
bool succsess = nfc_emv_parser_get_aid_name(storage, apl->aid, len, aid_name);
|
||||||
|
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
return succsess;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||||
|
furi_assert(device);
|
||||||
|
bool parsed = false;
|
||||||
|
|
||||||
|
const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv);
|
||||||
|
const EmvApplication app = data->emv_application;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(app.name_found)
|
||||||
|
furi_string_cat_printf(parsed_data, "\e#%s\n", app.name);
|
||||||
|
else
|
||||||
|
furi_string_cat_printf(parsed_data, "\e#%s\n", "EMV");
|
||||||
|
|
||||||
|
if(app.pan_len) {
|
||||||
|
FuriString* pan = furi_string_alloc();
|
||||||
|
for(uint8_t i = 0; i < app.pan_len; i += 2) {
|
||||||
|
furi_string_cat_printf(pan, "%02X%02X ", app.pan[i], app.pan[i + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut padding 'F' from card number
|
||||||
|
size_t end = furi_string_search_rchar(pan, 'F');
|
||||||
|
if(end) furi_string_left(pan, end);
|
||||||
|
furi_string_cat(parsed_data, pan);
|
||||||
|
furi_string_free(pan);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(app.exp_month | app.exp_year)
|
||||||
|
furi_string_cat_printf(parsed_data, "\nExp: %02X/%02X\n", app.exp_month, app.exp_year);
|
||||||
|
|
||||||
|
FuriString* str = furi_string_alloc();
|
||||||
|
bool storage_readed = emv_get_country_name(app.country_code, str);
|
||||||
|
|
||||||
|
if(storage_readed)
|
||||||
|
furi_string_cat_printf(parsed_data, "Country: %s\n", furi_string_get_cstr(str));
|
||||||
|
|
||||||
|
storage_readed = emv_get_currency_name(app.currency_code, str);
|
||||||
|
if(storage_readed)
|
||||||
|
furi_string_cat_printf(parsed_data, "Currency: %s\n", furi_string_get_cstr(str));
|
||||||
|
|
||||||
|
if(app.pin_try_counter != 0xFF)
|
||||||
|
furi_string_cat_printf(parsed_data, "PIN try left: %d\n", app.pin_try_counter);
|
||||||
|
|
||||||
|
parsed = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actual implementation of app<>plugin interface */
|
||||||
|
static const NfcSupportedCardsPlugin emv_plugin = {
|
||||||
|
.protocol = NfcProtocolEmv,
|
||||||
|
.verify = NULL,
|
||||||
|
.read = NULL,
|
||||||
|
.parse = emv_parse,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin descriptor to comply with basic plugin specification */
|
||||||
|
static const FlipperAppPluginDescriptor emv_plugin_descriptor = {
|
||||||
|
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||||
|
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||||
|
.entry_point = &emv_plugin,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin entry point - must return a pointer to const descriptor */
|
||||||
|
const FlipperAppPluginDescriptor* emv_plugin_ep() {
|
||||||
|
return &emv_plugin_descriptor;
|
||||||
|
}
|
||||||
@@ -37,6 +37,8 @@ ADD_SCENE(nfc, mf_ultralight_capture_pass, MfUltralightCapturePass)
|
|||||||
ADD_SCENE(nfc, mf_desfire_more_info, MfDesfireMoreInfo)
|
ADD_SCENE(nfc, mf_desfire_more_info, MfDesfireMoreInfo)
|
||||||
ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp)
|
ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp)
|
||||||
|
|
||||||
|
ADD_SCENE(nfc, emv_more_info, EmvMoreInfo)
|
||||||
|
|
||||||
ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack)
|
ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack)
|
||||||
ADD_SCENE(nfc, mf_classic_detect_reader, MfClassicDetectReader)
|
ADD_SCENE(nfc, mf_classic_detect_reader, MfClassicDetectReader)
|
||||||
ADD_SCENE(nfc, mf_classic_mfkey_nonces_info, MfClassicMfkeyNoncesInfo)
|
ADD_SCENE(nfc, mf_classic_mfkey_nonces_info, MfClassicMfkeyNoncesInfo)
|
||||||
|
|||||||
74
applications/main/nfc/scenes/nfc_scene_emv_more_info.c
Normal file
74
applications/main/nfc/scenes/nfc_scene_emv_more_info.c
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#include "../nfc_app_i.h"
|
||||||
|
|
||||||
|
#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h"
|
||||||
|
#include "../helpers/protocol_support/emv/emv_render.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
EmvMoreInfoStateMenu,
|
||||||
|
EmvMoreInfoStateItem, // MUST be last, states >= this correspond with submenu index
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SubmenuIndex {
|
||||||
|
SubmenuIndexTransactions,
|
||||||
|
SubmenuIndexDynamic, // dynamic indices start here
|
||||||
|
};
|
||||||
|
|
||||||
|
void nfc_scene_emv_more_info_on_enter(void* context) {
|
||||||
|
NfcApp* nfc = context;
|
||||||
|
Submenu* submenu = nfc->submenu;
|
||||||
|
|
||||||
|
text_box_set_font(nfc->text_box, TextBoxFontHex);
|
||||||
|
|
||||||
|
submenu_add_item(
|
||||||
|
submenu,
|
||||||
|
"Transactions",
|
||||||
|
SubmenuIndexTransactions,
|
||||||
|
nfc_protocol_support_common_submenu_callback,
|
||||||
|
nfc);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nfc_scene_emv_more_info_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
NfcApp* nfc = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
const uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmvMoreInfo);
|
||||||
|
const EmvData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolEmv);
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == SubmenuIndexTransactions) {
|
||||||
|
FuriString* temp_str = furi_string_alloc();
|
||||||
|
nfc_render_emv_transactions(&data->emv_application, temp_str);
|
||||||
|
widget_add_text_scroll_element(
|
||||||
|
nfc->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
|
||||||
|
furi_string_free(temp_str);
|
||||||
|
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
|
||||||
|
scene_manager_set_scene_state(
|
||||||
|
nfc->scene_manager,
|
||||||
|
NfcSceneEmvMoreInfo,
|
||||||
|
EmvMoreInfoStateItem + SubmenuIndexTransactions);
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
} else if(event.type == SceneManagerEventTypeBack) {
|
||||||
|
if(state >= EmvMoreInfoStateItem) {
|
||||||
|
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
|
||||||
|
scene_manager_set_scene_state(
|
||||||
|
nfc->scene_manager, NfcSceneEmvMoreInfo, EmvMoreInfoStateMenu);
|
||||||
|
} else {
|
||||||
|
// Return directly to the Info scene
|
||||||
|
scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneInfo);
|
||||||
|
}
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_scene_emv_more_info_on_exit(void* context) {
|
||||||
|
NfcApp* nfc = context;
|
||||||
|
|
||||||
|
// Clear views
|
||||||
|
widget_reset(nfc->widget);
|
||||||
|
submenu_reset(nfc->submenu);
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ env.Append(
|
|||||||
File("protocols/mf_ultralight/mf_ultralight.h"),
|
File("protocols/mf_ultralight/mf_ultralight.h"),
|
||||||
File("protocols/mf_classic/mf_classic.h"),
|
File("protocols/mf_classic/mf_classic.h"),
|
||||||
File("protocols/mf_desfire/mf_desfire.h"),
|
File("protocols/mf_desfire/mf_desfire.h"),
|
||||||
|
File("protocols/emv/emv.h"),
|
||||||
File("protocols/slix/slix.h"),
|
File("protocols/slix/slix.h"),
|
||||||
File("protocols/st25tb/st25tb.h"),
|
File("protocols/st25tb/st25tb.h"),
|
||||||
# Pollers
|
# Pollers
|
||||||
@@ -32,6 +33,7 @@ env.Append(
|
|||||||
File("protocols/mf_ultralight/mf_ultralight_poller.h"),
|
File("protocols/mf_ultralight/mf_ultralight_poller.h"),
|
||||||
File("protocols/mf_classic/mf_classic_poller.h"),
|
File("protocols/mf_classic/mf_classic_poller.h"),
|
||||||
File("protocols/mf_desfire/mf_desfire_poller.h"),
|
File("protocols/mf_desfire/mf_desfire_poller.h"),
|
||||||
|
File("protocols/emv/emv_poller.h"),
|
||||||
File("protocols/st25tb/st25tb_poller.h"),
|
File("protocols/st25tb/st25tb_poller.h"),
|
||||||
# Listeners
|
# Listeners
|
||||||
File("protocols/iso14443_3a/iso14443_3a_listener.h"),
|
File("protocols/iso14443_3a/iso14443_3a_listener.h"),
|
||||||
|
|||||||
@@ -7,6 +7,18 @@
|
|||||||
#define ISO14443_4_BLOCK_PCB_R (5U << 5)
|
#define ISO14443_4_BLOCK_PCB_R (5U << 5)
|
||||||
#define ISO14443_4_BLOCK_PCB_S (3U << 6)
|
#define ISO14443_4_BLOCK_PCB_S (3U << 6)
|
||||||
|
|
||||||
|
#define ISO14443_4_BLOCK_PCB_I_ (0U << 6)
|
||||||
|
#define ISO14443_4_BLOCK_PCB_R_ (2U << 6)
|
||||||
|
#define ISO14443_4_BLOCK_PCB_TYPE_MASK (3U << 6)
|
||||||
|
|
||||||
|
#define ISO14443_4_BLOCK_PCB_S_DESELECT (0U << 4)
|
||||||
|
#define ISO14443_4_BLOCK_PCB_S_WTX (3U << 4)
|
||||||
|
#define ISO14443_4_BLOCK_PCB_BLOCK_NUMBER (1U << 0)
|
||||||
|
|
||||||
|
#define ISO14443_4_BLOCK_PCB_NAD (1U << 2)
|
||||||
|
#define ISO14443_4_BLOCK_PCB_CID (1U << 3)
|
||||||
|
#define ISO14443_4_BLOCK_PCB_CHAINING (1U << 4)
|
||||||
|
|
||||||
struct Iso14443_4Layer {
|
struct Iso14443_4Layer {
|
||||||
uint8_t pcb;
|
uint8_t pcb;
|
||||||
uint8_t pcb_prev;
|
uint8_t pcb_prev;
|
||||||
@@ -62,3 +74,56 @@ bool iso14443_4_layer_decode_block(
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext(
|
||||||
|
Iso14443_4Layer* instance,
|
||||||
|
BitBuffer* output_data,
|
||||||
|
const BitBuffer* block_data) {
|
||||||
|
furi_assert(instance);
|
||||||
|
|
||||||
|
Iso14443_4aError ret = Iso14443_4aErrorProtocol;
|
||||||
|
bit_buffer_reset(output_data);
|
||||||
|
|
||||||
|
do {
|
||||||
|
const uint8_t pcb_field = bit_buffer_get_byte(block_data, 0);
|
||||||
|
const uint8_t block_type = pcb_field & ISO14443_4_BLOCK_PCB_TYPE_MASK;
|
||||||
|
switch(block_type) {
|
||||||
|
case ISO14443_4_BLOCK_PCB_I_:
|
||||||
|
if(pcb_field == instance->pcb_prev) {
|
||||||
|
bit_buffer_copy_right(output_data, block_data, 1);
|
||||||
|
ret = Iso14443_4aErrorNone;
|
||||||
|
} else {
|
||||||
|
// send original request again
|
||||||
|
ret = Iso14443_4aErrorSendExtra;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ISO14443_4_BLOCK_PCB_R_:
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
|
case ISO14443_4_BLOCK_PCB_S:
|
||||||
|
if((pcb_field & ISO14443_4_BLOCK_PCB_S_WTX) == ISO14443_4_BLOCK_PCB_S_WTX) {
|
||||||
|
const uint8_t inf_field = bit_buffer_get_byte(block_data, 1);
|
||||||
|
//const uint8_t power_level = inf_field >> 6;
|
||||||
|
const uint8_t wtxm = inf_field & 0b111111;
|
||||||
|
//uint32_t fwt_temp = MIN((fwt * wtxm), fwt_max);
|
||||||
|
|
||||||
|
bit_buffer_append_byte(
|
||||||
|
output_data,
|
||||||
|
ISO14443_4_BLOCK_PCB_S | ISO14443_4_BLOCK_PCB_S_WTX | ISO14443_4_BLOCK_PCB);
|
||||||
|
bit_buffer_append_byte(output_data, wtxm);
|
||||||
|
ret = Iso14443_4aErrorSendExtra;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
if(ret != Iso14443_4aErrorNone) {
|
||||||
|
FURI_LOG_RAW_T("RAW RX:");
|
||||||
|
for(size_t x = 0; x < bit_buffer_get_size_bytes(block_data); x++) {
|
||||||
|
FURI_LOG_RAW_T("%02X ", bit_buffer_get_byte(block_data, x));
|
||||||
|
}
|
||||||
|
FURI_LOG_RAW_T("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "protocols/iso14443_4a/iso14443_4a.h"
|
||||||
#include <toolbox/bit_buffer.h>
|
#include <toolbox/bit_buffer.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@@ -24,6 +25,11 @@ bool iso14443_4_layer_decode_block(
|
|||||||
BitBuffer* output_data,
|
BitBuffer* output_data,
|
||||||
const BitBuffer* block_data);
|
const BitBuffer* block_data);
|
||||||
|
|
||||||
|
Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext(
|
||||||
|
Iso14443_4Layer* instance,
|
||||||
|
BitBuffer* output_data,
|
||||||
|
const BitBuffer* block_data);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
188
lib/nfc/protocols/emv/emv.c
Normal file
188
lib/nfc/protocols/emv/emv.c
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
//#include "emv_i.h"
|
||||||
|
|
||||||
|
#include "flipper_format.h"
|
||||||
|
#include <core/common_defines.h>
|
||||||
|
#include "protocols/emv/emv.h"
|
||||||
|
#include <furi.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define EMV_PROTOCOL_NAME "EMV"
|
||||||
|
|
||||||
|
const NfcDeviceBase nfc_device_emv = {
|
||||||
|
.protocol_name = EMV_PROTOCOL_NAME,
|
||||||
|
.alloc = (NfcDeviceAlloc)emv_alloc,
|
||||||
|
.free = (NfcDeviceFree)emv_free,
|
||||||
|
.reset = (NfcDeviceReset)emv_reset,
|
||||||
|
.copy = (NfcDeviceCopy)emv_copy,
|
||||||
|
.verify = (NfcDeviceVerify)emv_verify,
|
||||||
|
.load = (NfcDeviceLoad)emv_load,
|
||||||
|
.save = (NfcDeviceSave)emv_save,
|
||||||
|
.is_equal = (NfcDeviceEqual)emv_is_equal,
|
||||||
|
.get_name = (NfcDeviceGetName)emv_get_device_name,
|
||||||
|
.get_uid = (NfcDeviceGetUid)emv_get_uid,
|
||||||
|
.set_uid = (NfcDeviceSetUid)emv_set_uid,
|
||||||
|
.get_base_data = (NfcDeviceGetBaseData)emv_get_base_data,
|
||||||
|
};
|
||||||
|
|
||||||
|
EmvData* emv_alloc() {
|
||||||
|
EmvData* data = malloc(sizeof(EmvData));
|
||||||
|
data->iso14443_4a_data = iso14443_4a_alloc();
|
||||||
|
data->emv_application.pin_try_counter = 0xff;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void emv_free(EmvData* data) {
|
||||||
|
furi_assert(data);
|
||||||
|
|
||||||
|
emv_reset(data);
|
||||||
|
iso14443_4a_free(data->iso14443_4a_data);
|
||||||
|
free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void emv_reset(EmvData* data) {
|
||||||
|
furi_assert(data);
|
||||||
|
|
||||||
|
iso14443_4a_reset(data->iso14443_4a_data);
|
||||||
|
|
||||||
|
memset(&data->emv_application, 0, sizeof(EmvApplication));
|
||||||
|
}
|
||||||
|
|
||||||
|
void emv_copy(EmvData* destination, const EmvData* source) {
|
||||||
|
furi_assert(destination);
|
||||||
|
furi_assert(source);
|
||||||
|
|
||||||
|
emv_reset(destination);
|
||||||
|
|
||||||
|
iso14443_4a_copy(destination->iso14443_4a_data, source->iso14443_4a_data);
|
||||||
|
destination->emv_application = source->emv_application;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool emv_verify(EmvData* data, const FuriString* device_type) {
|
||||||
|
UNUSED(data);
|
||||||
|
return furi_string_equal_str(device_type, EMV_PROTOCOL_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool emv_load(EmvData* data, FlipperFormat* ff, uint32_t version) {
|
||||||
|
furi_assert(data);
|
||||||
|
|
||||||
|
FuriString* temp_str = furi_string_alloc();
|
||||||
|
bool parsed = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Read ISO14443_4A data
|
||||||
|
if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break;
|
||||||
|
|
||||||
|
EmvApplication* app = &data->emv_application;
|
||||||
|
|
||||||
|
//Read name
|
||||||
|
if(!flipper_format_read_string(ff, "Name", temp_str)) break;
|
||||||
|
strcpy(app->name, furi_string_get_cstr(temp_str));
|
||||||
|
if(app->name[0] != '\0') app->name_found = true;
|
||||||
|
|
||||||
|
uint32_t pan_len;
|
||||||
|
if(!flipper_format_read_uint32(ff, "PAN length", &pan_len, 1)) break;
|
||||||
|
app->pan_len = pan_len;
|
||||||
|
|
||||||
|
if(!flipper_format_read_hex(ff, "PAN", app->pan, pan_len)) break;
|
||||||
|
|
||||||
|
uint32_t aid_len;
|
||||||
|
if(!flipper_format_read_uint32(ff, "AID length", &aid_len, 1)) break;
|
||||||
|
app->aid_len = aid_len;
|
||||||
|
|
||||||
|
if(!flipper_format_read_hex(ff, "AID", app->aid, aid_len)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_read_hex(ff, "Country code", (uint8_t*)&app->country_code, 2)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_read_hex(ff, "Currency code", (uint8_t*)&app->currency_code, 2)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_read_hex(ff, "Expiration year", &app->exp_year, 1)) break;
|
||||||
|
if(!flipper_format_read_hex(ff, "Expiration month", &app->exp_month, 1)) break;
|
||||||
|
|
||||||
|
uint32_t pin_try_counter;
|
||||||
|
if(!flipper_format_read_uint32(ff, "PIN counter", &pin_try_counter, 1)) break;
|
||||||
|
app->pin_try_counter = pin_try_counter;
|
||||||
|
|
||||||
|
parsed = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
furi_string_free(temp_str);
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool emv_save(const EmvData* data, FlipperFormat* ff) {
|
||||||
|
furi_assert(data);
|
||||||
|
|
||||||
|
FuriString* temp_str = furi_string_alloc();
|
||||||
|
bool saved = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
EmvApplication app = data->emv_application;
|
||||||
|
if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_comment_cstr(ff, "EMV specific data:\n")) break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_string_cstr(ff, "Name", app.name)) break;
|
||||||
|
|
||||||
|
uint32_t pan_len = app.pan_len;
|
||||||
|
if(!flipper_format_write_uint32(ff, "PAN length", &pan_len, 1)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_hex(ff, "PAN", app.pan, pan_len)) break;
|
||||||
|
|
||||||
|
uint32_t aid_len = app.aid_len;
|
||||||
|
if(!flipper_format_write_uint32(ff, "AID length", &aid_len, 1)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_hex(ff, "AID", app.aid, aid_len)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_hex(ff, "Country code", (uint8_t*)&app.country_code, 2)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_hex(ff, "Currency code", (uint8_t*)&app.currency_code, 2)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_hex(ff, "Expiration year", (uint8_t*)&app.exp_year, 1)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_hex(ff, "Expiration month", (uint8_t*)&app.exp_month, 1)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_uint32(ff, "PIN counter", (uint32_t*)&app.pin_try_counter, 1))
|
||||||
|
break;
|
||||||
|
|
||||||
|
saved = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
furi_string_free(temp_str);
|
||||||
|
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool emv_is_equal(const EmvData* data, const EmvData* other) {
|
||||||
|
furi_assert(data);
|
||||||
|
furi_assert(other);
|
||||||
|
|
||||||
|
return iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data) &&
|
||||||
|
memcmp(&data->emv_application, &other->emv_application, sizeof(EmvApplication)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* emv_get_device_name(const EmvData* data, NfcDeviceNameType name_type) {
|
||||||
|
UNUSED(data);
|
||||||
|
UNUSED(name_type);
|
||||||
|
return EMV_PROTOCOL_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* emv_get_uid(const EmvData* data, size_t* uid_len) {
|
||||||
|
furi_assert(data);
|
||||||
|
|
||||||
|
return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool emv_set_uid(EmvData* data, const uint8_t* uid, size_t uid_len) {
|
||||||
|
furi_assert(data);
|
||||||
|
|
||||||
|
return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iso14443_4aData* emv_get_base_data(const EmvData* data) {
|
||||||
|
furi_assert(data);
|
||||||
|
|
||||||
|
return data->iso14443_4a_data;
|
||||||
|
}
|
||||||
136
lib/nfc/protocols/emv/emv.h
Normal file
136
lib/nfc/protocols/emv/emv.h
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MAX_APDU_LEN 255
|
||||||
|
|
||||||
|
#define EMV_REQ_GET_DATA 0x80CA
|
||||||
|
|
||||||
|
#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_PIN_TRY_COUNTER 0x9F17
|
||||||
|
#define EMV_TAG_LOG_ENTRY 0x9F4D
|
||||||
|
#define EMV_TAG_LOG_FMT 0x9F4F
|
||||||
|
|
||||||
|
#define EMV_TAG_LAST_ONLINE_ATC 0x9F13
|
||||||
|
#define EMV_TAG_ATC 0x9F36
|
||||||
|
#define EMV_TAG_LOG_AMOUNT 0x9F02
|
||||||
|
#define EMV_TAG_LOG_COUNTRY 0x9F1A
|
||||||
|
#define EMV_TAG_LOG_CURRENCY 0x5F2A
|
||||||
|
#define EMV_TAG_LOG_DATE 0x9A
|
||||||
|
#define EMV_TAG_LOG_TIME 0x9F21
|
||||||
|
|
||||||
|
#define EMV_TAG_TRACK_1_EQUIV 0x56
|
||||||
|
#define EMV_TAG_TRACK_2_EQUIV 0x57
|
||||||
|
#define EMV_TAG_PAN 0x5A
|
||||||
|
#define EMV_TAG_AFL 0x94
|
||||||
|
#define EMV_TAG_EXP_DATE 0x5F24
|
||||||
|
#define EMV_TAG_COUNTRY_CODE 0x5F28
|
||||||
|
#define EMV_TAG_CURRENCY_CODE 0x9F42
|
||||||
|
#define EMV_TAG_CARDHOLDER_NAME 0x5F20
|
||||||
|
#define EMV_TAG_TRACK_2_DATA 0x9F6B
|
||||||
|
#define EMV_TAG_GPO_FMT1 0x80
|
||||||
|
|
||||||
|
#define EMV_TAG_RESP_BUF_SIZE 0x6C
|
||||||
|
#define EMV_TAG_RESP_BYTES_AVAILABLE 0x61
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16_t tag;
|
||||||
|
uint8_t data[];
|
||||||
|
} PDOLValue;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t size;
|
||||||
|
uint8_t data[MAX_APDU_LEN];
|
||||||
|
} APDU;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16_t atc;
|
||||||
|
uint64_t amount;
|
||||||
|
uint16_t country;
|
||||||
|
uint16_t currency;
|
||||||
|
uint32_t date;
|
||||||
|
uint32_t time;
|
||||||
|
} Transaction;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t log_sfi;
|
||||||
|
uint8_t log_records;
|
||||||
|
uint8_t log_fmt[50];
|
||||||
|
uint8_t log_fmt_len;
|
||||||
|
uint8_t active_tr;
|
||||||
|
bool saving_trans_list;
|
||||||
|
Transaction trans[16];
|
||||||
|
uint8_t priority;
|
||||||
|
uint8_t aid[16];
|
||||||
|
uint8_t aid_len;
|
||||||
|
char name[32];
|
||||||
|
bool name_found;
|
||||||
|
uint8_t pan[10]; // card_number
|
||||||
|
uint8_t pan_len;
|
||||||
|
uint8_t exp_month;
|
||||||
|
uint8_t exp_year;
|
||||||
|
uint16_t country_code;
|
||||||
|
uint16_t currency_code;
|
||||||
|
uint8_t pin_try_counter;
|
||||||
|
uint16_t transaction_counter;
|
||||||
|
uint16_t last_online_atc;
|
||||||
|
APDU pdol;
|
||||||
|
APDU afl;
|
||||||
|
} EmvApplication;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EmvErrorNone = 0,
|
||||||
|
EmvErrorNotPresent,
|
||||||
|
EmvErrorProtocol,
|
||||||
|
EmvErrorTimeout,
|
||||||
|
} EmvError;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Iso14443_4aData* iso14443_4a_data;
|
||||||
|
EmvApplication emv_application;
|
||||||
|
} EmvData;
|
||||||
|
|
||||||
|
extern const NfcDeviceBase nfc_device_emv;
|
||||||
|
|
||||||
|
// Virtual methods
|
||||||
|
|
||||||
|
EmvData* emv_alloc();
|
||||||
|
|
||||||
|
void emv_free(EmvData* data);
|
||||||
|
|
||||||
|
void emv_reset(EmvData* data);
|
||||||
|
|
||||||
|
void emv_copy(EmvData* data, const EmvData* other);
|
||||||
|
|
||||||
|
bool emv_verify(EmvData* data, const FuriString* device_type);
|
||||||
|
|
||||||
|
bool emv_load(EmvData* data, FlipperFormat* ff, uint32_t version);
|
||||||
|
|
||||||
|
bool emv_save(const EmvData* data, FlipperFormat* ff);
|
||||||
|
|
||||||
|
bool emv_is_equal(const EmvData* data, const EmvData* other);
|
||||||
|
|
||||||
|
const char* emv_get_device_name(const EmvData* data, NfcDeviceNameType name_type);
|
||||||
|
|
||||||
|
const uint8_t* emv_get_uid(const EmvData* data, size_t* uid_len);
|
||||||
|
|
||||||
|
bool emv_set_uid(EmvData* data, const uint8_t* uid, size_t uid_len);
|
||||||
|
|
||||||
|
Iso14443_4aData* emv_get_base_data(const EmvData* data);
|
||||||
|
|
||||||
|
// Getters and tests
|
||||||
|
|
||||||
|
//const EmvApplication* emv_get_application(const EmvData* data);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
202
lib/nfc/protocols/emv/emv_poller.c
Normal file
202
lib/nfc/protocols/emv/emv_poller.c
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#include "emv_poller_i.h"
|
||||||
|
|
||||||
|
#include <nfc/protocols/nfc_poller_base.h>
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
|
||||||
|
#define TAG "EMVPoller"
|
||||||
|
|
||||||
|
// MAX Le is 255 bytes + 2 for CRC
|
||||||
|
#define EMV_BUF_SIZE (512U)
|
||||||
|
|
||||||
|
typedef NfcCommand (*EmvPollerReadHandler)(EmvPoller* instance);
|
||||||
|
|
||||||
|
const EmvData* emv_poller_get_data(EmvPoller* instance) {
|
||||||
|
furi_assert(instance);
|
||||||
|
|
||||||
|
return instance->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static EmvPoller* emv_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) {
|
||||||
|
EmvPoller* instance = malloc(sizeof(EmvPoller));
|
||||||
|
instance->iso14443_4a_poller = iso14443_4a_poller;
|
||||||
|
instance->data = emv_alloc();
|
||||||
|
instance->tx_buffer = bit_buffer_alloc(EMV_BUF_SIZE);
|
||||||
|
instance->rx_buffer = bit_buffer_alloc(EMV_BUF_SIZE);
|
||||||
|
|
||||||
|
instance->state = EmvPollerStateIdle;
|
||||||
|
|
||||||
|
instance->emv_event.data = &instance->emv_event_data;
|
||||||
|
|
||||||
|
instance->general_event.protocol = NfcProtocolEmv;
|
||||||
|
instance->general_event.event_data = &instance->emv_event;
|
||||||
|
instance->general_event.instance = instance;
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emv_poller_free(EmvPoller* instance) {
|
||||||
|
furi_assert(instance);
|
||||||
|
|
||||||
|
emv_free(instance->data);
|
||||||
|
bit_buffer_free(instance->tx_buffer);
|
||||||
|
bit_buffer_free(instance->rx_buffer);
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
static NfcCommand emv_poller_handler_idle(EmvPoller* instance) {
|
||||||
|
bit_buffer_reset(instance->tx_buffer);
|
||||||
|
bit_buffer_reset(instance->rx_buffer);
|
||||||
|
|
||||||
|
iso14443_4a_copy(
|
||||||
|
instance->data->iso14443_4a_data,
|
||||||
|
iso14443_4a_poller_get_data(instance->iso14443_4a_poller));
|
||||||
|
|
||||||
|
instance->state = EmvPollerStateSelectPPSE;
|
||||||
|
return NfcCommandContinue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NfcCommand emv_poller_handler_select_ppse(EmvPoller* instance) {
|
||||||
|
instance->error = emv_poller_select_ppse(instance);
|
||||||
|
|
||||||
|
if(instance->error == EmvErrorNone) {
|
||||||
|
FURI_LOG_D(TAG, "Select PPSE success");
|
||||||
|
instance->state = EmvPollerStateSelectApplication;
|
||||||
|
} else {
|
||||||
|
FURI_LOG_E(TAG, "Failed to select PPSE");
|
||||||
|
instance->state = EmvPollerStateReadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NfcCommandContinue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NfcCommand emv_poller_handler_select_application(EmvPoller* instance) {
|
||||||
|
instance->error = emv_poller_select_application(instance);
|
||||||
|
|
||||||
|
if(instance->error == EmvErrorNone) {
|
||||||
|
FURI_LOG_D(TAG, "Select application success");
|
||||||
|
} else {
|
||||||
|
FURI_LOG_E(TAG, "Failed to select application");
|
||||||
|
// We have to try GPO request with empty tag
|
||||||
|
}
|
||||||
|
instance->state = EmvPollerStateGetProcessingOptions;
|
||||||
|
|
||||||
|
return NfcCommandContinue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NfcCommand emv_poller_handler_get_processing_options(EmvPoller* instance) {
|
||||||
|
instance->error = emv_poller_get_processing_options(instance);
|
||||||
|
|
||||||
|
if(instance->error == EmvErrorNone) {
|
||||||
|
FURI_LOG_D(TAG, "Get processing options success");
|
||||||
|
} else {
|
||||||
|
FURI_LOG_E(TAG, "Failed to get processing options");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read another informations
|
||||||
|
instance->state = EmvPollerStateReadFiles;
|
||||||
|
return NfcCommandContinue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) {
|
||||||
|
emv_poller_read_afl(instance);
|
||||||
|
emv_poller_read_log_entry(instance);
|
||||||
|
|
||||||
|
instance->state = EmvPollerStateReadExtra;
|
||||||
|
return NfcCommandContinue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NfcCommand emv_poller_handler_read_extra_data(EmvPoller* instance) {
|
||||||
|
emv_poller_get_last_online_atc(instance);
|
||||||
|
emv_poller_get_pin_try_counter(instance);
|
||||||
|
|
||||||
|
instance->state = EmvPollerStateReadSuccess;
|
||||||
|
return NfcCommandContinue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NfcCommand emv_poller_handler_read_fail(EmvPoller* instance) {
|
||||||
|
FURI_LOG_D(TAG, "Read failed");
|
||||||
|
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||||
|
instance->emv_event.data->error = instance->error;
|
||||||
|
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
||||||
|
instance->state = EmvPollerStateIdle;
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NfcCommand emv_poller_handler_read_success(EmvPoller* instance) {
|
||||||
|
FURI_LOG_D(TAG, "Read success");
|
||||||
|
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||||
|
instance->emv_event.type = EmvPollerEventTypeReadSuccess;
|
||||||
|
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const EmvPollerReadHandler emv_poller_read_handler[EmvPollerStateNum] = {
|
||||||
|
[EmvPollerStateIdle] = emv_poller_handler_idle,
|
||||||
|
[EmvPollerStateSelectPPSE] = emv_poller_handler_select_ppse,
|
||||||
|
[EmvPollerStateSelectApplication] = emv_poller_handler_select_application,
|
||||||
|
[EmvPollerStateGetProcessingOptions] = emv_poller_handler_get_processing_options,
|
||||||
|
[EmvPollerStateReadFiles] = emv_poller_handler_read_files,
|
||||||
|
[EmvPollerStateReadExtra] = emv_poller_handler_read_extra_data,
|
||||||
|
[EmvPollerStateReadFailed] = emv_poller_handler_read_fail,
|
||||||
|
[EmvPollerStateReadSuccess] = emv_poller_handler_read_success,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
emv_poller_set_callback(EmvPoller* instance, NfcGenericCallback callback, void* context) {
|
||||||
|
furi_assert(instance);
|
||||||
|
furi_assert(callback);
|
||||||
|
|
||||||
|
instance->callback = callback;
|
||||||
|
instance->context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NfcCommand emv_poller_run(NfcGenericEvent event, void* context) {
|
||||||
|
furi_assert(event.protocol == NfcProtocolIso14443_4a);
|
||||||
|
|
||||||
|
EmvPoller* instance = context;
|
||||||
|
furi_assert(instance);
|
||||||
|
furi_assert(instance->callback);
|
||||||
|
|
||||||
|
const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data;
|
||||||
|
furi_assert(iso14443_4a_event);
|
||||||
|
|
||||||
|
NfcCommand command = NfcCommandContinue;
|
||||||
|
|
||||||
|
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
|
||||||
|
command = emv_poller_read_handler[instance->state](instance);
|
||||||
|
} else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) {
|
||||||
|
instance->emv_event.type = EmvPollerEventTypeReadFailed;
|
||||||
|
command = instance->callback(instance->general_event, instance->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool emv_poller_detect(NfcGenericEvent event, void* context) {
|
||||||
|
furi_assert(event.protocol == NfcProtocolIso14443_4a);
|
||||||
|
|
||||||
|
EmvPoller* instance = context;
|
||||||
|
furi_assert(instance);
|
||||||
|
|
||||||
|
const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data;
|
||||||
|
furi_assert(iso14443_4a_event);
|
||||||
|
|
||||||
|
bool protocol_detected = false;
|
||||||
|
|
||||||
|
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
|
||||||
|
const EmvError error = emv_poller_select_ppse(instance);
|
||||||
|
protocol_detected = (error == EmvErrorNone);
|
||||||
|
}
|
||||||
|
|
||||||
|
return protocol_detected;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NfcPollerBase emv_poller = {
|
||||||
|
.alloc = (NfcPollerAlloc)emv_poller_alloc,
|
||||||
|
.free = (NfcPollerFree)emv_poller_free,
|
||||||
|
.set_callback = (NfcPollerSetCallback)emv_poller_set_callback,
|
||||||
|
.run = (NfcPollerRun)emv_poller_run,
|
||||||
|
.detect = (NfcPollerDetect)emv_poller_detect,
|
||||||
|
.get_data = (NfcPollerGetData)emv_poller_get_data,
|
||||||
|
};
|
||||||
59
lib/nfc/protocols/emv/emv_poller.h
Normal file
59
lib/nfc/protocols/emv/emv_poller.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "emv.h"
|
||||||
|
|
||||||
|
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief EmvPoller opaque type definition.
|
||||||
|
*/
|
||||||
|
typedef struct EmvPoller EmvPoller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enumeration of possible Emv poller event types.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
EmvPollerEventTypeReadSuccess, /**< Card was read successfully. */
|
||||||
|
EmvPollerEventTypeReadFailed, /**< Poller failed to read card. */
|
||||||
|
} EmvPollerEventType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Emv poller event data.
|
||||||
|
*/
|
||||||
|
typedef union {
|
||||||
|
EmvError error; /**< Error code indicating card reading fail reason. */
|
||||||
|
} EmvPollerEventData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Emv poller event structure.
|
||||||
|
*
|
||||||
|
* Upon emission of an event, an instance of this struct will be passed to the callback.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
EmvPollerEventType type; /**< Type of emmitted event. */
|
||||||
|
EmvPollerEventData* data; /**< Pointer to event specific data. */
|
||||||
|
} EmvPollerEvent;
|
||||||
|
|
||||||
|
EmvError emv_poller_select_ppse(EmvPoller* instance);
|
||||||
|
|
||||||
|
EmvError emv_poller_select_application(EmvPoller* instance);
|
||||||
|
|
||||||
|
EmvError emv_poller_get_processing_options(EmvPoller* instance);
|
||||||
|
|
||||||
|
EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t record_num);
|
||||||
|
|
||||||
|
EmvError emv_poller_read_afl(EmvPoller* instance);
|
||||||
|
|
||||||
|
EmvError emv_poller_read_log_entry(EmvPoller* instance);
|
||||||
|
|
||||||
|
EmvError emv_poller_get_pin_try_counter(EmvPoller* instance);
|
||||||
|
|
||||||
|
EmvError emv_poller_get_last_online_atc(EmvPoller* instance);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
5
lib/nfc/protocols/emv/emv_poller_defs.h
Normal file
5
lib/nfc/protocols/emv/emv_poller_defs.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <nfc/protocols/nfc_poller_base.h>
|
||||||
|
|
||||||
|
extern const NfcPollerBase emv_poller;
|
||||||
727
lib/nfc/protocols/emv/emv_poller_i.c
Normal file
727
lib/nfc/protocols/emv/emv_poller_i.c
Normal file
@@ -0,0 +1,727 @@
|
|||||||
|
#include "emv_poller_i.h"
|
||||||
|
#include "protocols/emv/emv.h"
|
||||||
|
|
||||||
|
#define TAG "EMVPoller"
|
||||||
|
|
||||||
|
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_addtnl_term_qualifies = {
|
||||||
|
0x9F40,
|
||||||
|
{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* const pdol_values[] = {
|
||||||
|
&pdol_term_info,
|
||||||
|
&pdol_term_type,
|
||||||
|
&pdol_merchant_type,
|
||||||
|
&pdol_term_trans_qualifies,
|
||||||
|
&pdol_addtnl_term_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,
|
||||||
|
};
|
||||||
|
|
||||||
|
EmvError emv_process_error(Iso14443_4aError error) {
|
||||||
|
switch(error) {
|
||||||
|
case Iso14443_4aErrorNone:
|
||||||
|
return EmvErrorNone;
|
||||||
|
case Iso14443_4aErrorNotPresent:
|
||||||
|
return EmvErrorNotPresent;
|
||||||
|
case Iso14443_4aErrorTimeout:
|
||||||
|
return EmvErrorTimeout;
|
||||||
|
default:
|
||||||
|
return EmvErrorProtocol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emv_trace(EmvPoller* instance, const char* message) {
|
||||||
|
if(furi_log_get_level() == FuriLogLevelTrace) {
|
||||||
|
FURI_LOG_T(TAG, "%s", message);
|
||||||
|
|
||||||
|
printf("TX: ");
|
||||||
|
size_t size = bit_buffer_get_size_bytes(instance->tx_buffer);
|
||||||
|
for(size_t i = 0; i < size; i++) {
|
||||||
|
printf("%02X ", bit_buffer_get_byte(instance->tx_buffer, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\r\nRX: ");
|
||||||
|
size = bit_buffer_get_size_bytes(instance->rx_buffer);
|
||||||
|
for(size_t i = 0; i < size; i++) {
|
||||||
|
printf("%02X ", bit_buffer_get_byte(instance->rx_buffer, i));
|
||||||
|
}
|
||||||
|
printf("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
emv_decode_tlv_tag(const uint8_t* buff, uint16_t tag, uint8_t tlen, EmvApplication* app) {
|
||||||
|
uint8_t i = 0;
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
switch(tag) {
|
||||||
|
case EMV_TAG_LOG_FMT:
|
||||||
|
furi_check(tlen < sizeof(app->log_fmt));
|
||||||
|
memcpy(app->log_fmt, &buff[i], tlen);
|
||||||
|
app->log_fmt_len = tlen;
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_LOG_FMT %X: len %d", tag, tlen);
|
||||||
|
break;
|
||||||
|
case EMV_TAG_GPO_FMT1:
|
||||||
|
// skip AIP
|
||||||
|
i += 2;
|
||||||
|
tlen -= 2;
|
||||||
|
furi_check(tlen < sizeof(app->afl.data));
|
||||||
|
memcpy(app->afl.data, &buff[i], tlen);
|
||||||
|
app->afl.size = tlen;
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_GPO_FMT1 %X: ", tag);
|
||||||
|
break;
|
||||||
|
case EMV_TAG_AID:
|
||||||
|
app->aid_len = tlen;
|
||||||
|
memcpy(app->aid, &buff[i], tlen);
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_AID %X: ", tag);
|
||||||
|
for(size_t x = 0; x < tlen; x++) {
|
||||||
|
FURI_LOG_RAW_T("%02X ", app->aid[x]);
|
||||||
|
}
|
||||||
|
FURI_LOG_RAW_T("\r\n");
|
||||||
|
break;
|
||||||
|
case EMV_TAG_PRIORITY:
|
||||||
|
memcpy(&app->priority, &buff[i], tlen);
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_APP_PRIORITY %X: %d", tag, app->priority);
|
||||||
|
break;
|
||||||
|
case EMV_TAG_CARD_NAME:
|
||||||
|
memcpy(app->name, &buff[i], tlen);
|
||||||
|
app->name[tlen] = '\0';
|
||||||
|
app->name_found = true;
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_CARD_NAME %x : %s", tag, app->name);
|
||||||
|
break;
|
||||||
|
case EMV_TAG_PDOL:
|
||||||
|
memcpy(app->pdol.data, &buff[i], tlen);
|
||||||
|
app->pdol.size = tlen;
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_PDOL %x (len=%d)", tag, tlen);
|
||||||
|
break;
|
||||||
|
case EMV_TAG_AFL:
|
||||||
|
memcpy(app->afl.data, &buff[i], tlen);
|
||||||
|
app->afl.size = tlen;
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen);
|
||||||
|
break;
|
||||||
|
// Tracks data https://murdoch.is/papers/defcon20emvdecode.pdf
|
||||||
|
case EMV_TAG_TRACK_1_EQUIV: {
|
||||||
|
// Contain PAN and expire date
|
||||||
|
char track_1_equiv[80];
|
||||||
|
memcpy(track_1_equiv, &buff[i], tlen);
|
||||||
|
track_1_equiv[tlen] = '\0';
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_1_EQUIV %x : %s", tag, track_1_equiv);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EMV_TAG_TRACK_2_DATA:
|
||||||
|
case EMV_TAG_TRACK_2_EQUIV: {
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2 %X", tag);
|
||||||
|
// 0xD0 delimits PAN from expiry (YYMM)
|
||||||
|
for(int x = 1; x < tlen; x++) {
|
||||||
|
if(buff[i + x + 1] > 0xD0) {
|
||||||
|
memcpy(app->pan, &buff[i], x + 1);
|
||||||
|
app->pan_len = x + 1;
|
||||||
|
app->exp_year = (buff[i + x + 1] << 4) | (buff[i + x + 2] >> 4);
|
||||||
|
app->exp_month = (buff[i + x + 2] << 4) | (buff[i + x + 3] >> 4);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert 4-bit to ASCII representation
|
||||||
|
char track_2_equiv[41];
|
||||||
|
uint8_t track_2_equiv_len = 0;
|
||||||
|
for(int x = 0; x < tlen; x++) {
|
||||||
|
char top = (buff[i + x] >> 4) + '0';
|
||||||
|
char bottom = (buff[i + x] & 0x0F) + '0';
|
||||||
|
track_2_equiv[x * 2] = top;
|
||||||
|
track_2_equiv_len++;
|
||||||
|
if(top == '?') break;
|
||||||
|
track_2_equiv[x * 2 + 1] = bottom;
|
||||||
|
track_2_equiv_len++;
|
||||||
|
if(bottom == '?') break;
|
||||||
|
}
|
||||||
|
track_2_equiv[track_2_equiv_len] = '\0';
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2 %X : %s", tag, track_2_equiv);
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EMV_TAG_CARDHOLDER_NAME: {
|
||||||
|
char name[27];
|
||||||
|
memcpy(name, &buff[i], tlen);
|
||||||
|
name[tlen] = '\0';
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_CARDHOLDER_NAME %x: %s", tag, name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EMV_TAG_PAN:
|
||||||
|
memcpy(app->pan, &buff[i], tlen);
|
||||||
|
app->pan_len = tlen;
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_PAN %x", tag);
|
||||||
|
break;
|
||||||
|
case EMV_TAG_EXP_DATE:
|
||||||
|
app->exp_year = buff[i];
|
||||||
|
app->exp_month = buff[i + 1];
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_EXP_DATE %x", tag);
|
||||||
|
break;
|
||||||
|
case EMV_TAG_CURRENCY_CODE:
|
||||||
|
app->currency_code = (buff[i] << 8 | buff[i + 1]);
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_CURRENCY_CODE %x", tag);
|
||||||
|
break;
|
||||||
|
case EMV_TAG_COUNTRY_CODE:
|
||||||
|
app->country_code = (buff[i] << 8 | buff[i + 1]);
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_COUNTRY_CODE %x", tag);
|
||||||
|
break;
|
||||||
|
case EMV_TAG_LOG_ENTRY:
|
||||||
|
app->log_sfi = buff[i];
|
||||||
|
app->log_records = buff[i + 1];
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(
|
||||||
|
TAG,
|
||||||
|
"found EMV_TAG_LOG_ENTRY %x: sfi 0x%x, records %d",
|
||||||
|
tag,
|
||||||
|
app->log_sfi,
|
||||||
|
app->log_records);
|
||||||
|
break;
|
||||||
|
case EMV_TAG_LAST_ONLINE_ATC:
|
||||||
|
app->last_online_atc = (buff[i] << 8 | buff[i + 1]);
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
case EMV_TAG_ATC:
|
||||||
|
if(app->saving_trans_list)
|
||||||
|
app->trans[app->active_tr].atc = (buff[i] << 8 | buff[i + 1]);
|
||||||
|
else
|
||||||
|
app->transaction_counter = (buff[i] << 8 | buff[i + 1]);
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
case EMV_TAG_LOG_AMOUNT:
|
||||||
|
memcpy(&app->trans[app->active_tr].amount, &buff[i], tlen);
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
case EMV_TAG_LOG_COUNTRY:
|
||||||
|
app->trans[app->active_tr].country = (buff[i] << 8 | buff[i + 1]);
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
case EMV_TAG_LOG_CURRENCY:
|
||||||
|
app->trans[app->active_tr].currency = (buff[i] << 8 | buff[i + 1]);
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
case EMV_TAG_LOG_DATE:
|
||||||
|
memcpy(&app->trans[app->active_tr].date, &buff[i], tlen);
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
case EMV_TAG_LOG_TIME:
|
||||||
|
memcpy(&app->trans[app->active_tr].time, &buff[i], tlen);
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
case EMV_TAG_PIN_TRY_COUNTER:
|
||||||
|
app->pin_try_counter = buff[i];
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_PIN_TRY_COUNTER %x: %d", tag, app->pin_try_counter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool emv_response_error(const uint8_t* buff, uint16_t len) {
|
||||||
|
uint8_t i = 0;
|
||||||
|
uint8_t first_byte = 0;
|
||||||
|
bool error = true;
|
||||||
|
|
||||||
|
first_byte = buff[i];
|
||||||
|
|
||||||
|
if((len == 2) && ((first_byte >> 4) == 6)) {
|
||||||
|
switch(buff[i]) {
|
||||||
|
case EMV_TAG_RESP_BUF_SIZE:
|
||||||
|
FURI_LOG_T(TAG, " Wrong length. Read %02X bytes", buff[i + 1]);
|
||||||
|
// Need to request SFI again with this length value
|
||||||
|
return error;
|
||||||
|
case EMV_TAG_RESP_BYTES_AVAILABLE:
|
||||||
|
FURI_LOG_T(TAG, " Bytes available: %02X", buff[i + 1]);
|
||||||
|
// Need to request one more time
|
||||||
|
return error;
|
||||||
|
|
||||||
|
default:
|
||||||
|
FURI_LOG_T(TAG, " Error/warning code: %02X %02X", buff[i], buff[i + 1]);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
emv_parse_tag(const uint8_t* buff, uint16_t len, uint16_t* t, uint8_t* tl, uint8_t* off) {
|
||||||
|
uint8_t i = *off;
|
||||||
|
uint16_t tag = 0;
|
||||||
|
uint8_t first_byte = 0;
|
||||||
|
uint8_t tlen = 0;
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
first_byte = buff[i];
|
||||||
|
|
||||||
|
if(emv_response_error(buff, len)) return success;
|
||||||
|
|
||||||
|
if((first_byte & 31) == 31) { // 2-byte tag
|
||||||
|
tag = buff[i] << 8 | buff[i + 1];
|
||||||
|
i++;
|
||||||
|
FURI_LOG_T(TAG, " 2-byte TLV EMV tag: %x", tag);
|
||||||
|
} else {
|
||||||
|
tag = buff[i];
|
||||||
|
FURI_LOG_T(TAG, " 1-byte TLV EMV tag: %x", tag);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
tlen = buff[i];
|
||||||
|
if((tlen & 128) == 128) { // long length value
|
||||||
|
i++;
|
||||||
|
tlen = buff[i];
|
||||||
|
FURI_LOG_T(TAG, " 2-byte TLV length: %d", tlen);
|
||||||
|
} else {
|
||||||
|
FURI_LOG_T(TAG, " 1-byte TLV length: %d", tlen);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
|
||||||
|
*off = i;
|
||||||
|
*t = tag;
|
||||||
|
*tl = tlen;
|
||||||
|
success = true;
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool emv_decode_tl(
|
||||||
|
const uint8_t* buff,
|
||||||
|
uint16_t len,
|
||||||
|
const uint8_t* fmt,
|
||||||
|
uint8_t fmt_len,
|
||||||
|
EmvApplication* app) {
|
||||||
|
uint8_t i = 0;
|
||||||
|
uint8_t f = 0;
|
||||||
|
uint16_t tag = 0;
|
||||||
|
uint8_t tlen = 0;
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
if(emv_response_error(buff, len)) return success;
|
||||||
|
|
||||||
|
while(f < fmt_len && i < len) {
|
||||||
|
success = emv_parse_tag(fmt, fmt_len, &tag, &tlen, &f);
|
||||||
|
if(!success) return success;
|
||||||
|
emv_decode_tlv_tag(&buff[i], tag, tlen, app);
|
||||||
|
i += tlen;
|
||||||
|
}
|
||||||
|
success = true;
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool emv_decode_response_tlv(const uint8_t* buff, uint8_t len, EmvApplication* app) {
|
||||||
|
uint8_t i = 0;
|
||||||
|
uint16_t tag = 0;
|
||||||
|
uint8_t first_byte = 0;
|
||||||
|
uint8_t tlen = 0;
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
while(i < len) {
|
||||||
|
first_byte = buff[i];
|
||||||
|
|
||||||
|
success = emv_parse_tag(buff, len, &tag, &tlen, &i);
|
||||||
|
if(!success) return success;
|
||||||
|
|
||||||
|
if((first_byte & 32) == 32) { // "Constructed" -- contains more TLV data to parse
|
||||||
|
FURI_LOG_T(TAG, "Constructed TLV %x", tag);
|
||||||
|
if(!emv_decode_response_tlv(&buff[i], tlen, app)) {
|
||||||
|
FURI_LOG_T(TAG, "Failed to decode response for %x", tag);
|
||||||
|
// return false;
|
||||||
|
} else {
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emv_decode_tlv_tag(&buff[i], tag, tlen, app);
|
||||||
|
}
|
||||||
|
i += tlen;
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmvError emv_poller_select_ppse(EmvPoller* instance) {
|
||||||
|
EmvError error = EmvErrorNone;
|
||||||
|
|
||||||
|
const uint8_t emv_select_ppse_cmd[] = {
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
bit_buffer_reset(instance->tx_buffer);
|
||||||
|
bit_buffer_reset(instance->rx_buffer);
|
||||||
|
|
||||||
|
bit_buffer_copy_bytes(instance->tx_buffer, emv_select_ppse_cmd, sizeof(emv_select_ppse_cmd));
|
||||||
|
do {
|
||||||
|
FURI_LOG_D(TAG, "Send select PPSE");
|
||||||
|
|
||||||
|
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block_pwt_ext(
|
||||||
|
instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer);
|
||||||
|
|
||||||
|
if(iso14443_4a_error != Iso14443_4aErrorNone) {
|
||||||
|
FURI_LOG_E(TAG, "Failed select PPSE");
|
||||||
|
error = emv_process_error(iso14443_4a_error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
emv_trace(instance, "Select PPSE answer:");
|
||||||
|
|
||||||
|
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
|
||||||
|
|
||||||
|
if(!emv_decode_response_tlv(
|
||||||
|
buff,
|
||||||
|
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||||
|
&instance->data->emv_application)) {
|
||||||
|
error = EmvErrorProtocol;
|
||||||
|
FURI_LOG_E(TAG, "Failed to parse application");
|
||||||
|
}
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmvError emv_poller_select_application(EmvPoller* instance) {
|
||||||
|
EmvError error = EmvErrorNone;
|
||||||
|
|
||||||
|
const uint8_t emv_select_header[] = {
|
||||||
|
0x00,
|
||||||
|
0xA4, // SELECT application
|
||||||
|
0x04,
|
||||||
|
0x00 // P1:By name, P2:First or only occurence
|
||||||
|
};
|
||||||
|
|
||||||
|
bit_buffer_reset(instance->tx_buffer);
|
||||||
|
bit_buffer_reset(instance->rx_buffer);
|
||||||
|
|
||||||
|
// Copy header
|
||||||
|
bit_buffer_copy_bytes(instance->tx_buffer, emv_select_header, sizeof(emv_select_header));
|
||||||
|
|
||||||
|
// Copy AID
|
||||||
|
bit_buffer_append_byte(instance->tx_buffer, instance->data->emv_application.aid_len);
|
||||||
|
bit_buffer_append_bytes(
|
||||||
|
instance->tx_buffer,
|
||||||
|
instance->data->emv_application.aid,
|
||||||
|
instance->data->emv_application.aid_len);
|
||||||
|
bit_buffer_append_byte(instance->tx_buffer, 0x00);
|
||||||
|
|
||||||
|
do {
|
||||||
|
FURI_LOG_D(TAG, "Start application");
|
||||||
|
|
||||||
|
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block_pwt_ext(
|
||||||
|
instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer);
|
||||||
|
|
||||||
|
emv_trace(instance, "Start application answer:");
|
||||||
|
|
||||||
|
if(iso14443_4a_error != Iso14443_4aErrorNone) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to read PAN or PDOL");
|
||||||
|
error = emv_process_error(iso14443_4a_error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
|
||||||
|
|
||||||
|
if(!emv_decode_response_tlv(
|
||||||
|
buff,
|
||||||
|
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||||
|
&instance->data->emv_application)) {
|
||||||
|
error = EmvErrorProtocol;
|
||||||
|
FURI_LOG_E(TAG, "Failed to parse application");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmvError emv_poller_get_processing_options(EmvPoller* instance) {
|
||||||
|
EmvError error = EmvErrorNone;
|
||||||
|
|
||||||
|
const uint8_t emv_gpo_header[] = {0x80, 0xA8, 0x00, 0x00};
|
||||||
|
|
||||||
|
bit_buffer_reset(instance->tx_buffer);
|
||||||
|
bit_buffer_reset(instance->rx_buffer);
|
||||||
|
|
||||||
|
// Copy header
|
||||||
|
bit_buffer_copy_bytes(instance->tx_buffer, emv_gpo_header, sizeof(emv_gpo_header));
|
||||||
|
|
||||||
|
// Prepare and copy pdol parameters
|
||||||
|
APDU pdol_data = {0, {0}};
|
||||||
|
emv_prepare_pdol(&pdol_data, &instance->data->emv_application.pdol);
|
||||||
|
|
||||||
|
bit_buffer_append_byte(instance->tx_buffer, 0x02 + pdol_data.size);
|
||||||
|
bit_buffer_append_byte(instance->tx_buffer, 0x83);
|
||||||
|
bit_buffer_append_byte(instance->tx_buffer, pdol_data.size);
|
||||||
|
|
||||||
|
bit_buffer_append_bytes(instance->tx_buffer, pdol_data.data, pdol_data.size);
|
||||||
|
bit_buffer_append_byte(instance->tx_buffer, 0x00);
|
||||||
|
|
||||||
|
do {
|
||||||
|
FURI_LOG_D(TAG, "Get proccessing options");
|
||||||
|
|
||||||
|
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block_pwt_ext(
|
||||||
|
instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer);
|
||||||
|
|
||||||
|
emv_trace(instance, "Get processing options answer:");
|
||||||
|
|
||||||
|
if(iso14443_4a_error != Iso14443_4aErrorNone) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to get processing options, error %u", iso14443_4a_error);
|
||||||
|
error = emv_process_error(iso14443_4a_error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
|
||||||
|
|
||||||
|
if(!emv_decode_response_tlv(
|
||||||
|
buff,
|
||||||
|
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||||
|
&instance->data->emv_application)) {
|
||||||
|
error = EmvErrorProtocol;
|
||||||
|
FURI_LOG_E(TAG, "Failed to parse processing options");
|
||||||
|
}
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t record_num) {
|
||||||
|
EmvError error = EmvErrorNone;
|
||||||
|
FuriString* text = furi_string_alloc();
|
||||||
|
|
||||||
|
uint8_t sfi_param = (sfi << 3) | (1 << 2);
|
||||||
|
uint8_t emv_sfi_header[] = {
|
||||||
|
0x00,
|
||||||
|
0xB2, // READ RECORD
|
||||||
|
record_num, // P1:record_number
|
||||||
|
sfi_param, // P2:SFI
|
||||||
|
0x00 // Le
|
||||||
|
};
|
||||||
|
|
||||||
|
bit_buffer_reset(instance->tx_buffer);
|
||||||
|
bit_buffer_reset(instance->rx_buffer);
|
||||||
|
|
||||||
|
bit_buffer_copy_bytes(instance->tx_buffer, emv_sfi_header, sizeof(emv_sfi_header));
|
||||||
|
|
||||||
|
do {
|
||||||
|
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block_pwt_ext(
|
||||||
|
instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer);
|
||||||
|
|
||||||
|
furi_string_printf(text, "SFI 0x%X record %d:", sfi, record_num);
|
||||||
|
emv_trace(instance, furi_string_get_cstr(text));
|
||||||
|
|
||||||
|
if(iso14443_4a_error != Iso14443_4aErrorNone) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to read SFI 0x%X record %d", sfi, record_num);
|
||||||
|
error = emv_process_error(iso14443_4a_error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
furi_string_free(text);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmvError emv_poller_read_afl(EmvPoller* instance) {
|
||||||
|
EmvError error = EmvErrorNone;
|
||||||
|
|
||||||
|
APDU* afl = &instance->data->emv_application.afl;
|
||||||
|
|
||||||
|
if(afl->size == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_LOG_D(TAG, "Search PAN in SFI");
|
||||||
|
|
||||||
|
// Iterate through all files
|
||||||
|
for(size_t i = 0; i < instance->data->emv_application.afl.size; i += 4) {
|
||||||
|
uint8_t sfi = afl->data[i] >> 3;
|
||||||
|
uint8_t record_start = afl->data[i + 1];
|
||||||
|
uint8_t record_end = afl->data[i + 2];
|
||||||
|
// Iterate through all records in file
|
||||||
|
for(uint8_t record = record_start; record <= record_end; ++record) {
|
||||||
|
error = emv_poller_read_sfi_record(instance, sfi, record);
|
||||||
|
if(error != EmvErrorNone) break;
|
||||||
|
|
||||||
|
if(!emv_decode_response_tlv(
|
||||||
|
bit_buffer_get_data(instance->rx_buffer),
|
||||||
|
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||||
|
&instance->data->emv_application)) {
|
||||||
|
error = EmvErrorProtocol;
|
||||||
|
FURI_LOG_T(TAG, "Failed to parse SFI 0x%X record %d", sfi, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some READ RECORD returns 1 byte response 0x12/0x13 (IDK WTF),
|
||||||
|
// then poller return Timeout to all subsequent requests.
|
||||||
|
// TODO: remove below lines when it was fixed
|
||||||
|
if(instance->data->emv_application.pan_len != 0)
|
||||||
|
return EmvErrorNone; // Card number fetched
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static EmvError emv_poller_req_get_data(EmvPoller* instance, uint16_t tag) {
|
||||||
|
EmvError error = EmvErrorNone;
|
||||||
|
|
||||||
|
bit_buffer_reset(instance->tx_buffer);
|
||||||
|
bit_buffer_reset(instance->rx_buffer);
|
||||||
|
|
||||||
|
bit_buffer_append_byte(instance->tx_buffer, EMV_REQ_GET_DATA >> 8);
|
||||||
|
bit_buffer_append_byte(instance->tx_buffer, EMV_REQ_GET_DATA & 0xFF);
|
||||||
|
bit_buffer_append_byte(instance->tx_buffer, tag >> 8);
|
||||||
|
bit_buffer_append_byte(instance->tx_buffer, tag & 0xFF);
|
||||||
|
bit_buffer_append_byte(instance->tx_buffer, 0x00); //Length
|
||||||
|
|
||||||
|
do {
|
||||||
|
FURI_LOG_D(TAG, "Get data for tag 0x%x", tag);
|
||||||
|
|
||||||
|
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block_pwt_ext(
|
||||||
|
instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer);
|
||||||
|
|
||||||
|
emv_trace(instance, "Get log data answer:");
|
||||||
|
|
||||||
|
if(iso14443_4a_error != Iso14443_4aErrorNone) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to get data, error %u", iso14443_4a_error);
|
||||||
|
error = emv_process_error(iso14443_4a_error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
|
||||||
|
|
||||||
|
if(!emv_decode_response_tlv(
|
||||||
|
buff,
|
||||||
|
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||||
|
&instance->data->emv_application)) {
|
||||||
|
error = EmvErrorProtocol;
|
||||||
|
FURI_LOG_E(TAG, "Failed to parse get data");
|
||||||
|
}
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmvError emv_poller_get_pin_try_counter(EmvPoller* instance) {
|
||||||
|
return emv_poller_req_get_data(instance, EMV_TAG_PIN_TRY_COUNTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmvError emv_poller_get_last_online_atc(EmvPoller* instance) {
|
||||||
|
return emv_poller_req_get_data(instance, EMV_TAG_LAST_ONLINE_ATC);
|
||||||
|
}
|
||||||
|
|
||||||
|
static EmvError emv_poller_get_log_format(EmvPoller* instance) {
|
||||||
|
return emv_poller_req_get_data(instance, EMV_TAG_LOG_FMT);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmvError emv_poller_read_log_entry(EmvPoller* instance) {
|
||||||
|
EmvError error = EmvErrorProtocol;
|
||||||
|
|
||||||
|
if(!instance->data->emv_application.log_sfi) return error;
|
||||||
|
uint8_t records = instance->data->emv_application.log_records;
|
||||||
|
if(records == 0) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->data->emv_application.saving_trans_list = true;
|
||||||
|
error = emv_poller_get_log_format(instance);
|
||||||
|
if(error != EmvErrorNone) return error;
|
||||||
|
|
||||||
|
FURI_LOG_D(TAG, "Read Transaction logs");
|
||||||
|
|
||||||
|
uint8_t sfi = instance->data->emv_application.log_sfi;
|
||||||
|
uint8_t record_start = 1;
|
||||||
|
uint8_t record_end = records;
|
||||||
|
// Iterate through all records in file
|
||||||
|
for(uint8_t record = record_start; record <= record_end; ++record) {
|
||||||
|
error = emv_poller_read_sfi_record(instance, sfi, record);
|
||||||
|
if(error != EmvErrorNone) break;
|
||||||
|
if(!emv_decode_tl(
|
||||||
|
bit_buffer_get_data(instance->rx_buffer),
|
||||||
|
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||||
|
instance->data->emv_application.log_fmt,
|
||||||
|
instance->data->emv_application.log_fmt_len,
|
||||||
|
&instance->data->emv_application)) {
|
||||||
|
error = EmvErrorProtocol;
|
||||||
|
FURI_LOG_T(TAG, "Failed to parse log SFI 0x%X record %d", sfi, record);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->data->emv_application.active_tr++;
|
||||||
|
furi_check(
|
||||||
|
instance->data->emv_application.active_tr <
|
||||||
|
COUNT_OF(instance->data->emv_application.trans));
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->data->emv_application.saving_trans_list = false;
|
||||||
|
return error;
|
||||||
|
}
|
||||||
51
lib/nfc/protocols/emv/emv_poller_i.h
Normal file
51
lib/nfc/protocols/emv/emv_poller_i.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "emv_poller.h"
|
||||||
|
|
||||||
|
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EmvPollerStateIdle,
|
||||||
|
EmvPollerStateSelectPPSE,
|
||||||
|
EmvPollerStateSelectApplication,
|
||||||
|
EmvPollerStateGetProcessingOptions,
|
||||||
|
EmvPollerStateReadFiles,
|
||||||
|
EmvPollerStateReadExtra,
|
||||||
|
EmvPollerStateReadFailed,
|
||||||
|
EmvPollerStateReadSuccess,
|
||||||
|
|
||||||
|
EmvPollerStateNum,
|
||||||
|
} EmvPollerState;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EmvPollerSessionStateIdle,
|
||||||
|
EmvPollerSessionStateActive,
|
||||||
|
EmvPollerSessionStateStopRequest,
|
||||||
|
} EmvPollerSessionState;
|
||||||
|
|
||||||
|
struct EmvPoller {
|
||||||
|
Iso14443_4aPoller* iso14443_4a_poller;
|
||||||
|
EmvPollerSessionState session_state;
|
||||||
|
EmvPollerState state;
|
||||||
|
EmvError error;
|
||||||
|
EmvData* data;
|
||||||
|
BitBuffer* tx_buffer;
|
||||||
|
BitBuffer* rx_buffer;
|
||||||
|
EmvPollerEventData emv_event_data;
|
||||||
|
EmvPollerEvent emv_event;
|
||||||
|
NfcGenericEvent general_event;
|
||||||
|
NfcGenericCallback callback;
|
||||||
|
void* context;
|
||||||
|
};
|
||||||
|
|
||||||
|
EmvError emv_process_error(Iso14443_4aError error);
|
||||||
|
|
||||||
|
const EmvData* emv_poller_get_data(EmvPoller* instance);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -13,6 +13,7 @@ typedef enum {
|
|||||||
Iso14443_4aErrorNotPresent,
|
Iso14443_4aErrorNotPresent,
|
||||||
Iso14443_4aErrorProtocol,
|
Iso14443_4aErrorProtocol,
|
||||||
Iso14443_4aErrorTimeout,
|
Iso14443_4aErrorTimeout,
|
||||||
|
Iso14443_4aErrorSendExtra,
|
||||||
} Iso14443_4aError;
|
} Iso14443_4aError;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ Iso14443_4aError iso14443_4a_poller_send_block(
|
|||||||
const BitBuffer* tx_buffer,
|
const BitBuffer* tx_buffer,
|
||||||
BitBuffer* rx_buffer);
|
BitBuffer* rx_buffer);
|
||||||
|
|
||||||
|
Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext(
|
||||||
|
Iso14443_4aPoller* instance,
|
||||||
|
const BitBuffer* tx_buffer,
|
||||||
|
BitBuffer* rx_buffer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Send HALT command to the card.
|
* @brief Send HALT command to the card.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -81,3 +81,50 @@ Iso14443_4aError iso14443_4a_poller_send_block(
|
|||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext(
|
||||||
|
Iso14443_4aPoller* instance,
|
||||||
|
const BitBuffer* tx_buffer,
|
||||||
|
BitBuffer* rx_buffer) {
|
||||||
|
furi_assert(instance);
|
||||||
|
|
||||||
|
uint8_t retry = 5;
|
||||||
|
bit_buffer_reset(instance->tx_buffer);
|
||||||
|
iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer);
|
||||||
|
|
||||||
|
Iso14443_4aError error = Iso14443_4aErrorNone;
|
||||||
|
|
||||||
|
do {
|
||||||
|
bit_buffer_reset(instance->rx_buffer);
|
||||||
|
Iso14443_3aError iso14443_3a_error = iso14443_3a_poller_send_standard_frame(
|
||||||
|
instance->iso14443_3a_poller,
|
||||||
|
instance->tx_buffer,
|
||||||
|
instance->rx_buffer,
|
||||||
|
iso14443_4a_get_fwt_fc_max(instance->data));
|
||||||
|
|
||||||
|
if(iso14443_3a_error != Iso14443_3aErrorNone) {
|
||||||
|
FURI_LOG_RAW_T("RAW RX(%d):", bit_buffer_get_size_bytes(instance->rx_buffer));
|
||||||
|
for(size_t x = 0; x < bit_buffer_get_size_bytes(instance->rx_buffer); x++) {
|
||||||
|
FURI_LOG_RAW_T("%02X ", bit_buffer_get_byte(instance->rx_buffer, x));
|
||||||
|
}
|
||||||
|
FURI_LOG_RAW_T("\r\n");
|
||||||
|
|
||||||
|
error = iso14443_4a_process_error(iso14443_3a_error);
|
||||||
|
break;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
error = iso14443_4_layer_decode_block_pwt_ext(
|
||||||
|
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer);
|
||||||
|
if(error == Iso14443_4aErrorSendExtra) {
|
||||||
|
if(--retry == 0) break;
|
||||||
|
// Send response for Control message
|
||||||
|
if(bit_buffer_get_size_bytes(rx_buffer))
|
||||||
|
bit_buffer_copy(instance->tx_buffer, rx_buffer);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while(true);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
|
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
|
||||||
#include <nfc/protocols/mf_classic/mf_classic.h>
|
#include <nfc/protocols/mf_classic/mf_classic.h>
|
||||||
#include <nfc/protocols/mf_desfire/mf_desfire.h>
|
#include <nfc/protocols/mf_desfire/mf_desfire.h>
|
||||||
|
#include <nfc/protocols/emv/emv.h>
|
||||||
#include <nfc/protocols/slix/slix_device_defs.h>
|
#include <nfc/protocols/slix/slix_device_defs.h>
|
||||||
#include <nfc/protocols/st25tb/st25tb.h>
|
#include <nfc/protocols/st25tb/st25tb.h>
|
||||||
|
|
||||||
@@ -42,5 +43,6 @@ const NfcDeviceBase* nfc_devices[NfcProtocolNum] = {
|
|||||||
[NfcProtocolMfDesfire] = &nfc_device_mf_desfire,
|
[NfcProtocolMfDesfire] = &nfc_device_mf_desfire,
|
||||||
[NfcProtocolSlix] = &nfc_device_slix,
|
[NfcProtocolSlix] = &nfc_device_slix,
|
||||||
[NfcProtocolSt25tb] = &nfc_device_st25tb,
|
[NfcProtocolSt25tb] = &nfc_device_st25tb,
|
||||||
|
[NfcProtocolEmv] = &nfc_device_emv,
|
||||||
/* Add new protocols here */
|
/* Add new protocols here */
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = {
|
|||||||
[NfcProtocolSlix] = &nfc_listener_slix,
|
[NfcProtocolSlix] = &nfc_listener_slix,
|
||||||
[NfcProtocolSt25tb] = NULL,
|
[NfcProtocolSt25tb] = NULL,
|
||||||
[NfcProtocolFelica] = &nfc_listener_felica,
|
[NfcProtocolFelica] = &nfc_listener_felica,
|
||||||
|
[NfcProtocolEmv] = NULL,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_defs.h>
|
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_defs.h>
|
||||||
#include <nfc/protocols/mf_classic/mf_classic_poller_defs.h>
|
#include <nfc/protocols/mf_classic/mf_classic_poller_defs.h>
|
||||||
#include <nfc/protocols/mf_desfire/mf_desfire_poller_defs.h>
|
#include <nfc/protocols/mf_desfire/mf_desfire_poller_defs.h>
|
||||||
|
#include <nfc/protocols/emv/emv_poller_defs.h>
|
||||||
#include <nfc/protocols/slix/slix_poller_defs.h>
|
#include <nfc/protocols/slix/slix_poller_defs.h>
|
||||||
#include <nfc/protocols/st25tb/st25tb_poller_defs.h>
|
#include <nfc/protocols/st25tb/st25tb_poller_defs.h>
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = {
|
|||||||
[NfcProtocolMfClassic] = &mf_classic_poller,
|
[NfcProtocolMfClassic] = &mf_classic_poller,
|
||||||
[NfcProtocolMfDesfire] = &mf_desfire_poller,
|
[NfcProtocolMfDesfire] = &mf_desfire_poller,
|
||||||
[NfcProtocolSlix] = &nfc_poller_slix,
|
[NfcProtocolSlix] = &nfc_poller_slix,
|
||||||
/* Add new pollers here */
|
|
||||||
[NfcProtocolSt25tb] = &nfc_poller_st25tb,
|
[NfcProtocolSt25tb] = &nfc_poller_st25tb,
|
||||||
|
[NfcProtocolEmv] = &emv_poller,
|
||||||
|
/* Add new pollers here */
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,17 +12,19 @@
|
|||||||
* ```
|
* ```
|
||||||
* **************************** Protocol tree structure ***************************
|
* **************************** Protocol tree structure ***************************
|
||||||
*
|
*
|
||||||
* (Start)
|
* (Start)
|
||||||
* |
|
* |
|
||||||
* +------------------------+-----------+---------+------------+
|
* +------------------------+-----------+---------+------------+
|
||||||
* | | | | |
|
* | | | | |
|
||||||
* ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB
|
* ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB
|
||||||
* | | |
|
* | | |
|
||||||
* +---------------+-------------+ ISO14443-4B SLIX
|
* +---------------+-------------+ ISO14443-4B SLIX
|
||||||
* | | |
|
* | | |
|
||||||
* ISO14443-4A Mf Ultralight Mf Classic
|
* ISO14443-4A Mf Ultralight Mf Classic
|
||||||
* |
|
* |
|
||||||
* Mf Desfire
|
* +-----+-----+
|
||||||
|
* | |
|
||||||
|
* Mf Desfire EMV
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* When implementing a new protocol, its place in the tree must be determined first.
|
* When implementing a new protocol, its place in the tree must be determined first.
|
||||||
@@ -61,6 +63,7 @@ static const NfcProtocol nfc_protocol_iso14443_3b_children_protocol[] = {
|
|||||||
/** List of ISO14443-4A child protocols. */
|
/** List of ISO14443-4A child protocols. */
|
||||||
static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = {
|
static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = {
|
||||||
NfcProtocolMfDesfire,
|
NfcProtocolMfDesfire,
|
||||||
|
NfcProtocolEmv,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** List of ISO115693-3 child protocols. */
|
/** List of ISO115693-3 child protocols. */
|
||||||
@@ -146,6 +149,12 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = {
|
|||||||
.children_num = 0,
|
.children_num = 0,
|
||||||
.children_protocol = NULL,
|
.children_protocol = NULL,
|
||||||
},
|
},
|
||||||
|
[NfcProtocolEmv] =
|
||||||
|
{
|
||||||
|
.parent_protocol = NfcProtocolIso14443_4a,
|
||||||
|
.children_num = 0,
|
||||||
|
.children_protocol = NULL,
|
||||||
|
},
|
||||||
/* Add new protocols here */
|
/* Add new protocols here */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -72,19 +72,19 @@
|
|||||||
*
|
*
|
||||||
* ### 2.2 File structure explanation
|
* ### 2.2 File structure explanation
|
||||||
*
|
*
|
||||||
* | Filename | Explanation |
|
* | Filename | Explanation |
|
||||||
* |:------------------------------|:------------|
|
* |:------------------------------|:--------------------------------------------------------------------------------------------------------------------------------|
|
||||||
* | protocol_name.h | Main protocol data structure and associated functions declarations. It is recommended to keep the former as opaque pointer. |
|
* | protocol_name.h | Main protocol data structure and associated functions declarations. It is recommended to keep the former as opaque pointer. |
|
||||||
* | protocol_name.c | Implementations of functions declared in `protocol_name.h`. |
|
* | protocol_name.c | Implementations of functions declared in `protocol_name.h`. |
|
||||||
* | protocol_name_device_defs.h | Declarations for use by the NfcDevice library. See nfc_device_base_i.h for more info. |
|
* | protocol_name_device_defs.h | Declarations for use by the NfcDevice library. See nfc_device_base_i.h for more info. |
|
||||||
* | protocol_name_poller.h | Protocol-specific poller and associated functions declarations. |
|
* | protocol_name_poller.h | Protocol-specific poller and associated functions declarations. |
|
||||||
* | protocol_name_poller.c | Implementation of functions declared in `protocol_name_poller.h`. |
|
* | protocol_name_poller.c | Implementation of functions declared in `protocol_name_poller.h`. |
|
||||||
* | protocol_name_poller_defs.h | Declarations for use by the NfcPoller library. See nfc_poller_base.h for more info. |
|
* | protocol_name_poller_defs.h | Declarations for use by the NfcPoller library. See nfc_poller_base.h for more info. |
|
||||||
* | protocol_name_listener.h | Protocol-specific listener and associated functions declarations. Optional, needed for emulation support. |
|
* | protocol_name_listener.h | Protocol-specific listener and associated functions declarations. Optional, needed for emulation support. |
|
||||||
* | protocol_name_listener.c | Implementation of functions declared in `protocol_name_listener.h`. Optional, needed for emulation support. |
|
* | protocol_name_listener.c | Implementation of functions declared in `protocol_name_listener.h`. Optional, needed for emulation support. |
|
||||||
* | protocol_name_listener_defs.h | Declarations for use by the NfcListener library. See nfc_listener_base.h for more info. Optional, needed for emulation support. |
|
* | protocol_name_listener_defs.h | Declarations for use by the NfcListener library. See nfc_listener_base.h for more info. Optional, needed for emulation support. |
|
||||||
* | protocol_name_sync.h | Synchronous API declarations. (See below for sync API explanation). Optional.|
|
* | protocol_name_sync.h | Synchronous API declarations. (See below for sync API explanation). Optional. |
|
||||||
* | protocol_name_sync.c | Synchronous API implementation. Optional. |
|
* | protocol_name_sync.c | Synchronous API implementation. Optional. |
|
||||||
*
|
*
|
||||||
* ## 3 Implement the code
|
* ## 3 Implement the code
|
||||||
*
|
*
|
||||||
@@ -187,6 +187,7 @@ typedef enum {
|
|||||||
NfcProtocolMfDesfire,
|
NfcProtocolMfDesfire,
|
||||||
NfcProtocolSlix,
|
NfcProtocolSlix,
|
||||||
NfcProtocolSt25tb,
|
NfcProtocolSt25tb,
|
||||||
|
NfcProtocolEmv,
|
||||||
/* Add new protocols here */
|
/* Add new protocols here */
|
||||||
|
|
||||||
NfcProtocolNum, /**< Special value representing the number of available protocols. */
|
NfcProtocolNum, /**< Special value representing the number of available protocols. */
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ Header,+,lib/nfc/nfc_device.h,,
|
|||||||
Header,+,lib/nfc/nfc_listener.h,,
|
Header,+,lib/nfc/nfc_listener.h,,
|
||||||
Header,+,lib/nfc/nfc_poller.h,,
|
Header,+,lib/nfc/nfc_poller.h,,
|
||||||
Header,+,lib/nfc/nfc_scanner.h,,
|
Header,+,lib/nfc/nfc_scanner.h,,
|
||||||
|
Header,+,lib/nfc/protocols/emv/emv.h,,
|
||||||
|
Header,+,lib/nfc/protocols/emv/emv_poller.h,,
|
||||||
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,,
|
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,,
|
||||||
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,,
|
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,,
|
||||||
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,,
|
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,,
|
||||||
@@ -879,6 +881,26 @@ Function,+,elf_symbolname_hash,uint32_t,const char*
|
|||||||
Function,+,empty_screen_alloc,EmptyScreen*,
|
Function,+,empty_screen_alloc,EmptyScreen*,
|
||||||
Function,+,empty_screen_free,void,EmptyScreen*
|
Function,+,empty_screen_free,void,EmptyScreen*
|
||||||
Function,+,empty_screen_get_view,View*,EmptyScreen*
|
Function,+,empty_screen_get_view,View*,EmptyScreen*
|
||||||
|
Function,+,emv_alloc,EmvData*,
|
||||||
|
Function,+,emv_copy,void,"EmvData*, const EmvData*"
|
||||||
|
Function,+,emv_free,void,EmvData*
|
||||||
|
Function,+,emv_get_base_data,Iso14443_4aData*,const EmvData*
|
||||||
|
Function,+,emv_get_device_name,const char*,"const EmvData*, NfcDeviceNameType"
|
||||||
|
Function,+,emv_get_uid,const uint8_t*,"const EmvData*, size_t*"
|
||||||
|
Function,+,emv_is_equal,_Bool,"const EmvData*, const EmvData*"
|
||||||
|
Function,+,emv_load,_Bool,"EmvData*, FlipperFormat*, uint32_t"
|
||||||
|
Function,+,emv_poller_get_last_online_atc,EmvError,EmvPoller*
|
||||||
|
Function,+,emv_poller_get_pin_try_counter,EmvError,EmvPoller*
|
||||||
|
Function,+,emv_poller_get_processing_options,EmvError,EmvPoller*
|
||||||
|
Function,+,emv_poller_read_afl,EmvError,EmvPoller*
|
||||||
|
Function,+,emv_poller_read_log_entry,EmvError,EmvPoller*
|
||||||
|
Function,+,emv_poller_read_sfi_record,EmvError,"EmvPoller*, uint8_t, uint8_t"
|
||||||
|
Function,+,emv_poller_select_application,EmvError,EmvPoller*
|
||||||
|
Function,+,emv_poller_select_ppse,EmvError,EmvPoller*
|
||||||
|
Function,+,emv_reset,void,EmvData*
|
||||||
|
Function,+,emv_save,_Bool,"const EmvData*, FlipperFormat*"
|
||||||
|
Function,+,emv_set_uid,_Bool,"EmvData*, const uint8_t*, size_t"
|
||||||
|
Function,+,emv_verify,_Bool,"EmvData*, const FuriString*"
|
||||||
Function,-,erand48,double,unsigned short[3]
|
Function,-,erand48,double,unsigned short[3]
|
||||||
Function,-,erf,double,double
|
Function,-,erf,double,double
|
||||||
Function,-,erfc,double,double
|
Function,-,erfc,double,double
|
||||||
@@ -1972,6 +1994,7 @@ Function,+,iso14443_4a_load,_Bool,"Iso14443_4aData*, FlipperFormat*, uint32_t"
|
|||||||
Function,+,iso14443_4a_poller_halt,Iso14443_4aError,Iso14443_4aPoller*
|
Function,+,iso14443_4a_poller_halt,Iso14443_4aError,Iso14443_4aPoller*
|
||||||
Function,+,iso14443_4a_poller_read_ats,Iso14443_4aError,"Iso14443_4aPoller*, Iso14443_4aAtsData*"
|
Function,+,iso14443_4a_poller_read_ats,Iso14443_4aError,"Iso14443_4aPoller*, Iso14443_4aAtsData*"
|
||||||
Function,+,iso14443_4a_poller_send_block,Iso14443_4aError,"Iso14443_4aPoller*, const BitBuffer*, BitBuffer*"
|
Function,+,iso14443_4a_poller_send_block,Iso14443_4aError,"Iso14443_4aPoller*, const BitBuffer*, BitBuffer*"
|
||||||
|
Function,+,iso14443_4a_poller_send_block_pwt_ext,Iso14443_4aError,"Iso14443_4aPoller*, const BitBuffer*, BitBuffer*"
|
||||||
Function,+,iso14443_4a_reset,void,Iso14443_4aData*
|
Function,+,iso14443_4a_reset,void,Iso14443_4aData*
|
||||||
Function,+,iso14443_4a_save,_Bool,"const Iso14443_4aData*, FlipperFormat*"
|
Function,+,iso14443_4a_save,_Bool,"const Iso14443_4aData*, FlipperFormat*"
|
||||||
Function,+,iso14443_4a_set_uid,_Bool,"Iso14443_4aData*, const uint8_t*, size_t"
|
Function,+,iso14443_4a_set_uid,_Bool,"Iso14443_4aData*, const uint8_t*, size_t"
|
||||||
@@ -3646,6 +3669,7 @@ Variable,+,message_red_255,const NotificationMessage,
|
|||||||
Variable,+,message_sound_off,const NotificationMessage,
|
Variable,+,message_sound_off,const NotificationMessage,
|
||||||
Variable,+,message_vibro_off,const NotificationMessage,
|
Variable,+,message_vibro_off,const NotificationMessage,
|
||||||
Variable,+,message_vibro_on,const NotificationMessage,
|
Variable,+,message_vibro_on,const NotificationMessage,
|
||||||
|
Variable,-,nfc_device_emv,const NfcDeviceBase,
|
||||||
Variable,-,nfc_device_mf_classic,const NfcDeviceBase,
|
Variable,-,nfc_device_mf_classic,const NfcDeviceBase,
|
||||||
Variable,-,nfc_device_mf_desfire,const NfcDeviceBase,
|
Variable,-,nfc_device_mf_desfire,const NfcDeviceBase,
|
||||||
Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase,
|
Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase,
|
||||||
|
|||||||
|
Reference in New Issue
Block a user