This commit is contained in:
Willy-JL
2024-01-30 20:57:42 +00:00
63 changed files with 2434 additions and 156 deletions

View File

@@ -173,6 +173,15 @@ App(
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(
appid="ndef_parser",
apptype=FlipperAppType.PLUGIN,

View File

@@ -34,7 +34,7 @@ static bool nfc_emv_parser_search_data(
bool nfc_emv_parser_get_aid_name(
Storage* storage,
uint8_t* aid,
const uint8_t* aid,
uint8_t aid_len,
FuriString* aid_name) {
furi_assert(storage);

View File

@@ -13,7 +13,7 @@
*/
bool nfc_emv_parser_get_aid_name(
Storage* storage,
uint8_t* aid,
const uint8_t* aid,
uint8_t aid_len,
FuriString* aid_name);

View 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,
},
};

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_emv;

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

View File

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

View File

@@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_felica(NfcApp* instance) {
const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_felica_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -58,8 +59,8 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) {
furi_string_free(temp_str);
}
static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEdit) {
static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}

View File

@@ -13,6 +13,8 @@ static void nfc_scene_info_on_enter_iso14443_3a(NfcApp* instance) {
const Iso14443_3aData* data = nfc_device_get_data(device, NfcProtocolIso14443_3a);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_iso14443_3a_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -95,8 +97,8 @@ static void nfc_scene_emulate_on_enter_iso14443_3a(NfcApp* instance) {
instance->listener, nfc_scene_emulate_listener_callback_iso14443_3a, instance);
}
static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEmulate) {
static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
return true;
}

View File

@@ -6,13 +6,17 @@ void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const d
}
}
void nfc_render_iso14443_tech_type(const Iso14443_3aData* data, FuriString* str) {
const char iso_type = iso14443_3a_supports_iso14443_4(data) ? '4' : '3';
furi_string_cat_printf(str, "Tech: ISO 14443-%c (NFC-A)\n", iso_type);
}
void nfc_render_iso14443_3a_info(
const Iso14443_3aData* data,
NfcProtocolFormatType format_type,
FuriString* str) {
if(format_type == NfcProtocolFormatTypeFull) {
const char iso_type = iso14443_3a_supports_iso14443_4(data) ? '4' : '3';
furi_string_cat_printf(str, "ISO 14443-%c (NFC-A)\n", iso_type);
nfc_render_iso14443_tech_type(data, str);
}
nfc_render_iso14443_3a_brief(data, str);
@@ -30,5 +34,5 @@ void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str)
void nfc_render_iso14443_3a_extra(const Iso14443_3aData* data, FuriString* str) {
furi_string_cat_printf(str, "\nATQA: %02X %02X ", data->atqa[1], data->atqa[0]);
furi_string_cat_printf(str, "SAK: %02X", data->sak);
furi_string_cat_printf(str, "\nSAK: %02X", data->sak);
}

View File

@@ -9,6 +9,8 @@ void nfc_render_iso14443_3a_info(
NfcProtocolFormatType format_type,
FuriString* str);
void nfc_render_iso14443_tech_type(const Iso14443_3aData* data, FuriString* str);
void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size);
void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str);

View File

@@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_iso14443_3b(NfcApp* instance) {
const Iso14443_3bData* data = nfc_device_get_data(device, NfcProtocolIso14443_3b);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_iso14443_3b_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -59,8 +60,8 @@ static void nfc_scene_read_success_on_enter_iso14443_3b(NfcApp* instance) {
furi_string_free(temp_str);
}
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEdit) {
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}
@@ -68,7 +69,7 @@ bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t
return false;
}
static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, uint32_t event) {
static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, SceneManagerEvent event) {
return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event);
}

View File

@@ -4,4 +4,4 @@
#include "iso14443_3b.h"
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event);
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event);

View File

@@ -6,7 +6,7 @@ void nfc_render_iso14443_3b_info(
FuriString* str) {
if(format_type == NfcProtocolFormatTypeFull) {
const char iso_type = iso14443_3b_supports_iso14443_4(data) ? '4' : '3';
furi_string_cat_printf(str, "ISO 14443-%c (NFC-B)\n", iso_type);
furi_string_cat_printf(str, "Tech: ISO 14443-%c (NFC-B)\n", iso_type);
}
furi_string_cat_printf(str, "UID:");
@@ -20,7 +20,7 @@ void nfc_render_iso14443_3b_info(
if(format_type != NfcProtocolFormatTypeFull) return;
furi_string_cat_printf(str, "\n\e#Protocol info\n");
furi_string_cat_printf(str, "\n::::::::::::::::[Protocol info]:::::::::::::::\n");
if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRateBoth106Kbit)) {
furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n");
@@ -68,7 +68,7 @@ void nfc_render_iso14443_3b_info(
iso14443_3b_supports_frame_option(data, Iso14443_3bFrameOptionCid) ? "" : "not ";
furi_string_cat_printf(str, "CID: %ssupported", cid_support_str);
furi_string_cat_printf(str, "\n\e#Application data\nRaw:");
furi_string_cat_printf(str, "\n::::::::::::[Application data]::::::::::::\nRaw:");
size_t app_data_size;
const uint8_t* app_data = iso14443_3b_get_application_data(data, &app_data_size);

View File

@@ -14,6 +14,7 @@ static void nfc_scene_info_on_enter_iso14443_4a(NfcApp* instance) {
const Iso14443_4aData* data = nfc_device_get_data(device, NfcProtocolIso14443_4a);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_iso14443_4a_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -99,8 +100,8 @@ static void nfc_scene_emulate_on_enter_iso14443_4a(NfcApp* instance) {
instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance);
}
static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEmulate) {
static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
return true;
}

View File

@@ -14,11 +14,12 @@ void nfc_render_iso14443_4a_info(
}
void nfc_render_iso14443_4a_brief(const Iso14443_4aData* data, FuriString* str) {
nfc_render_iso14443_tech_type(iso14443_4a_get_base_data(data), str);
nfc_render_iso14443_3a_brief(iso14443_4a_get_base_data(data), str);
}
void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str) {
furi_string_cat_printf(str, "\n\e#Protocol info\n");
furi_string_cat_printf(str, "\n::::::::::::::::[Protocol info]:::::::::::::::\n");
if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRateBoth106Kbit)) {
furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n");
@@ -72,7 +73,7 @@ void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str)
const uint8_t* hist_bytes = iso14443_4a_get_historical_bytes(data, &hist_bytes_count);
if(hist_bytes_count > 0) {
furi_string_cat_printf(str, "\n\e#Historical bytes\nRaw:");
furi_string_cat_printf(str, "\n:::::::::::::[Historical bytes]:::::::::::::\nRaw:");
for(size_t i = 0; i < hist_bytes_count; ++i) {
furi_string_cat_printf(str, " %02X", hist_bytes[i]);

View File

@@ -14,6 +14,7 @@ static void nfc_scene_info_on_enter_iso14443_4b(NfcApp* instance) {
const Iso14443_4bData* data = nfc_device_get_data(device, NfcProtocolIso14443_4b);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_iso14443_4b_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -64,8 +65,8 @@ static void nfc_scene_saved_menu_on_enter_iso14443_4b(NfcApp* instance) {
UNUSED(instance);
}
static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEmulate) {
static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
return true;
}
@@ -73,7 +74,7 @@ static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t
return false;
}
static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) {
static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) {
return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event);
}

View File

@@ -14,16 +14,30 @@ static void nfc_scene_info_on_enter_iso15693_3(NfcApp* instance) {
const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_iso15693_3_info(data, NfcProtocolFormatTypeFull, temp_str);
widget_reset(instance->widget);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
}
static void nfc_scene_more_info_on_enter_iso15693_3(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3);
FuriString* temp_str = furi_string_alloc();
nfc_render_iso15693_3_system_info(data, temp_str);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
}
static NfcCommand nfc_scene_read_poller_callback_iso15693_3(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolIso15693_3);
@@ -94,8 +108,8 @@ static void nfc_scene_emulate_on_enter_iso15693_3(NfcApp* instance) {
instance->listener, nfc_scene_emulate_listener_callback_iso15693_3, instance);
}
static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEdit) {
static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}
@@ -104,13 +118,19 @@ static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, uint32_t
}
const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = {
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid,
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid |
NfcProtocolFeatureMoreInfo,
.scene_info =
{
.on_enter = nfc_scene_info_on_enter_iso15693_3,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_more_info =
{
.on_enter = nfc_scene_more_info_on_enter_iso15693_3,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read =
{
.on_enter = nfc_scene_read_on_enter_iso15693_3,

View File

@@ -36,32 +36,7 @@ void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str) {
}
}
void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) {
furi_string_cat(str, "\n\e#General info\n");
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
furi_string_cat_printf(str, "DSFID: %02X\n", data->system_info.ic_ref);
}
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
furi_string_cat_printf(str, "AFI: %02X\n", data->system_info.afi);
}
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) {
furi_string_cat_printf(str, "IC Reference: %02X\n", data->system_info.ic_ref);
}
furi_string_cat(str, "\e#Lock bits\n");
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
furi_string_cat_printf(
str, "DSFID: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
}
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
furi_string_cat_printf(
str, "AFI: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
}
void nfc_render_iso15693_3_system_info(const Iso15693_3Data* data, FuriString* str) {
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) {
furi_string_cat(str, "\e#Memory data\n\e*--------------------\n");
@@ -88,5 +63,34 @@ void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) {
"(Data is too big. Showing only the first %u bytes.)",
display_block_count * block_size);
}
} else {
furi_string_cat(str, "\e#No available data\n");
}
}
void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) {
furi_string_cat(str, "\n::::::::::::::::[General info]:::::::::::::::::\n");
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
furi_string_cat_printf(str, "DSFID: %02X\n", data->system_info.ic_ref);
}
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
furi_string_cat_printf(str, "AFI: %02X\n", data->system_info.afi);
}
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) {
furi_string_cat_printf(str, "IC Reference: %02X\n", data->system_info.ic_ref);
}
furi_string_cat(str, ":::::::::::::::::::[Lock bits]::::::::::::::::::::\n");
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
furi_string_cat_printf(
str, "DSFID: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
}
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
furi_string_cat_printf(
str, "AFI: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
}
}

View File

@@ -12,3 +12,5 @@ void nfc_render_iso15693_3_info(
void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str);
void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str);
void nfc_render_iso15693_3_system_info(const Iso15693_3Data* data, FuriString* str);

View File

@@ -21,6 +21,7 @@ static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) {
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
furi_string_replace(temp_str, "Mifare", "MIFARE");
@@ -99,8 +100,9 @@ static void nfc_scene_read_on_enter_mf_classic(NfcApp* instance) {
nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_classic, instance);
}
static bool nfc_scene_read_on_event_mf_classic(NfcApp* instance, uint32_t event) {
if(event == NfcCustomEventPollerIncomplete) {
static bool nfc_scene_read_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom &&
event.event == NfcCustomEventPollerIncomplete) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack);
}
@@ -170,8 +172,8 @@ static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) {
nfc_listener_start(instance->listener, NULL, NULL);
}
static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexDetectReader) {
static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexDetectReader) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm);
dolphin_deed(DolphinDeedNfcDetectReader);
return true;
@@ -180,27 +182,29 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, uint32_t e
return false;
}
static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) {
static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) {
bool consumed = false;
if(event == SubmenuIndexDetectReader) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader);
consumed = true;
} else if(event == SubmenuIndexWrite) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial);
consumed = true;
} else if(event == SubmenuIndexUpdate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial);
consumed = true;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexDetectReader) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader);
consumed = true;
} else if(event.event == SubmenuIndexWrite) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial);
consumed = true;
} else if(event.event == SubmenuIndexUpdate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial);
consumed = true;
}
}
return consumed;
}
static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, uint32_t event) {
static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) {
bool consumed = false;
if(event == NfcCustomEventTextInputDone) {
if(event.type == SceneManagerEventTypeCustom && event.event == NfcCustomEventTextInputDone) {
mf_classic_key_cache_save(
instance->mfc_key_cache,
nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic));

View File

@@ -18,13 +18,48 @@ void nfc_render_mf_classic_info(
furi_string_cat_printf(str, "\nSectors Read: %u/%u", sectors_read, sectors_total);
}
static void
mf_classic_render_raw_data(const uint8_t* data, size_t size, bool data_read, FuriString* str) {
furi_assert((size % 2) == 0);
for(size_t i = 0; i < size; i += 2) {
if(data_read) {
furi_string_cat_printf(str, "%02X%02X ", data[i], data[i + 1]);
} else {
furi_string_cat_printf(str, "???? ");
}
}
}
static void
mf_classic_render_block(const MfClassicData* data, uint8_t block_num, FuriString* str) {
if(mf_classic_is_sector_trailer(block_num)) {
uint8_t sec_num = mf_classic_get_sector_by_block(block_num);
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sec_num);
// Render key A
bool key_read = mf_classic_is_key_found(data, sec_num, MfClassicKeyTypeA);
mf_classic_render_raw_data(sec_tr->key_a.data, sizeof(MfClassicKey), key_read, str);
// Render access bits
bool access_bits_read = mf_classic_is_block_read(data, block_num);
mf_classic_render_raw_data(
sec_tr->access_bits.data, sizeof(MfClassicAccessBits), access_bits_read, str);
// Render key B
key_read = mf_classic_is_key_found(data, sec_num, MfClassicKeyTypeB);
mf_classic_render_raw_data(sec_tr->key_b.data, sizeof(MfClassicKey), key_read, str);
} else {
const uint8_t* block_data = data->block[block_num].data;
bool block_read = mf_classic_is_block_read(data, block_num);
mf_classic_render_raw_data(block_data, sizeof(MfClassicBlock), block_read, str);
}
}
void nfc_render_mf_classic_dump(const MfClassicData* data, FuriString* str) {
uint16_t total_blocks = mf_classic_get_total_block_num(data->type);
for(size_t i = 0; i < total_blocks; i++) {
for(size_t j = 0; j < sizeof(MfClassicBlock); j += 2) {
furi_string_cat_printf(
str, "%02X%02X ", data->block[i].data[j], data->block[i].data[j + 1]);
}
mf_classic_render_block(data, i, str);
}
}

View File

@@ -14,8 +14,10 @@ static void nfc_scene_info_on_enter_mf_desfire(NfcApp* instance) {
const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
furi_string_replace(temp_str, "Mifare", "MIFARE");
nfc_render_mf_desfire_info(data, NfcProtocolFormatTypeFull, temp_str);
widget_add_text_scroll_element(
@@ -56,6 +58,7 @@ static void nfc_scene_read_success_on_enter_mf_desfire(NfcApp* instance) {
FuriString* temp_str = furi_string_alloc();
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
furi_string_replace(temp_str, "Mifare", "MIFARE");
nfc_render_mf_desfire_info(data, NfcProtocolFormatTypeShort, temp_str);
widget_add_text_scroll_element(

View File

@@ -2,6 +2,7 @@
#include "mf_ultralight_render.h"
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller.h>
#include <toolbox/pretty_format.h>
#include "nfc/nfc_app_i.h"
@@ -15,11 +16,18 @@ enum {
SubmenuIndexWrite,
};
enum {
NfcSceneMoreInfoStateASCII,
NfcSceneMoreInfoStateRawData,
};
static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_mf_ultralight_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -35,11 +43,62 @@ static void nfc_scene_more_info_on_enter_mf_ultralight(NfcApp* instance) {
const MfUltralightData* mfu = nfc_device_get_data(device, NfcProtocolMfUltralight);
furi_string_reset(instance->text_box_store);
nfc_render_mf_ultralight_dump(mfu, instance->text_box_store);
uint32_t scene_state =
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMoreInfo);
text_box_set_font(instance->text_box, TextBoxFontHex);
text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store));
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
if(scene_state == NfcSceneMoreInfoStateASCII) {
pretty_format_bytes_hex_canonical(
instance->text_box_store,
MF_ULTRALIGHT_PAGE_SIZE,
PRETTY_FORMAT_FONT_MONOSPACE,
(uint8_t*)mfu->page,
mfu->pages_read * MF_ULTRALIGHT_PAGE_SIZE);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store));
widget_add_button_element(
instance->widget,
GuiButtonTypeRight,
"Raw Data",
nfc_protocol_support_common_widget_callback,
instance);
widget_add_button_element(
instance->widget,
GuiButtonTypeLeft,
"Info",
nfc_protocol_support_common_widget_callback,
instance);
} else if(scene_state == NfcSceneMoreInfoStateRawData) {
nfc_render_mf_ultralight_dump(mfu, instance->text_box_store);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store));
widget_add_button_element(
instance->widget,
GuiButtonTypeLeft,
"ASCII",
nfc_protocol_support_common_widget_callback,
instance);
}
}
static bool nfc_scene_more_info_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) {
bool consumed = false;
if((event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeLeft) ||
(event.type == SceneManagerEventTypeBack)) {
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateASCII);
scene_manager_previous_scene(instance->scene_manager);
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeRight) {
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateRawData);
scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo);
consumed = true;
}
return consumed;
}
static NfcCommand
@@ -132,15 +191,17 @@ static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) {
nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance);
}
bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, uint32_t event) {
if(event == NfcCustomEventCardDetected) {
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound);
nfc_scene_read_setup_view(instance);
} else if((event == NfcCustomEventPollerIncomplete)) {
notification_message(instance->notifications, &sequence_semi_success);
scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
dolphin_deed(DolphinDeedNfcReadSuccess);
bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventCardDetected) {
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound);
nfc_scene_read_setup_view(instance);
} else if((event.event == NfcCustomEventPollerIncomplete)) {
notification_message(instance->notifications, &sequence_semi_success);
scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
dolphin_deed(DolphinDeedNfcReadSuccess);
}
}
return true;
}
@@ -202,14 +263,17 @@ static void nfc_scene_emulate_on_enter_mf_ultralight(NfcApp* instance) {
nfc_listener_start(instance->listener, NULL, NULL);
}
static bool
nfc_scene_read_and_saved_menu_on_event_mf_ultralight(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexUnlock) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu);
return true;
} else if(event == SubmenuIndexWrite) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite);
return true;
static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight(
NfcApp* instance,
SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexUnlock) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu);
return true;
} else if(event.event == SubmenuIndexWrite) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite);
return true;
}
}
return false;
}
@@ -225,7 +289,7 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = {
.scene_more_info =
{
.on_enter = nfc_scene_more_info_on_enter_mf_ultralight,
.on_event = nfc_protocol_support_common_on_event_empty,
.on_event = nfc_scene_more_info_on_event_mf_ultralight,
},
.scene_read =
{

View File

@@ -36,10 +36,11 @@ void nfc_render_mf_ultralight_info(
}
void nfc_render_mf_ultralight_dump(const MfUltralightData* data, FuriString* str) {
furi_string_cat_printf(str, "\e*");
for(size_t i = 0; i < data->pages_read; i++) {
const uint8_t* page_data = data->page[i].data;
for(size_t j = 0; j < MF_ULTRALIGHT_PAGE_SIZE; j += 2) {
furi_string_cat_printf(str, "%02X%02X ", page_data[j], page_data[j + 1]);
furi_string_cat_printf(str, " %02X%02X", page_data[j], page_data[j + 1]);
}
}
}

View File

@@ -74,7 +74,7 @@ void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context)
nfc_protocol_support_scenes[scene].on_exit(instance);
}
static bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) {
bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) {
return nfc_protocol_support[protocol]->features & feature;
}
@@ -131,16 +131,15 @@ static bool
nfc_protocol_support_scene_more_info_on_event(NfcApp* instance, SceneManagerEvent event) {
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event.event);
}
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event);
return consumed;
}
static void nfc_protocol_support_scene_more_info_on_exit(NfcApp* instance) {
text_box_reset(instance->text_box);
widget_reset(instance->widget);
furi_string_reset(instance->text_box_store);
}
@@ -188,8 +187,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana
} else {
const NfcProtocol protocol =
instance->protocols_detected[instance->protocols_detected_selected_idx];
consumed =
nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event);
consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event);
}
} else if(event.event == NfcCustomEventPollerFailure) {
nfc_poller_stop(instance->poller);
@@ -202,7 +200,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana
} else if(event.event == NfcCustomEventCardDetected) {
const NfcProtocol protocol =
instance->protocols_detected[instance->protocols_detected_selected_idx];
consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event);
consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event);
}
} else if(event.type == SceneManagerEventTypeBack) {
nfc_poller_stop(instance->poller);
@@ -287,8 +285,7 @@ static bool
consumed = true;
} else {
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
consumed =
nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event.event);
consumed = nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event);
}
} else if(event.type == SceneManagerEventTypeBack) {
@@ -456,8 +453,7 @@ static bool
consumed = true;
} else {
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
consumed =
nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event.event);
consumed = nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event);
}
} else if(event.type == SceneManagerEventTypeBack) {
@@ -530,8 +526,8 @@ static bool
DolphinDeedNfcSave);
const NfcProtocol protocol =
instance->protocols_detected[instance->protocols_detected_selected_idx];
consumed = nfc_protocol_support[protocol]->scene_save_name.on_event(
instance, event.event);
consumed =
nfc_protocol_support[protocol]->scene_save_name.on_event(instance, event);
} else {
consumed = scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, NfcSceneStart);
@@ -572,11 +568,11 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) {
FuriString* temp_str = furi_string_alloc();
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61);
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_51x64);
if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) {
widget_add_string_element(
widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating UID");
widget, 90, 26, AlignCenter, AlignCenter, FontPrimary, "Emulating UID");
size_t uid_len;
const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len);
@@ -588,7 +584,8 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) {
furi_string_trim(temp_str);
} else {
widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating");
widget_add_string_element(
widget, 90, 26, AlignCenter, AlignCenter, FontPrimary, "Emulating");
if(!furi_string_empty(instance->file_name)) {
furi_string_printf(
temp_str,
@@ -600,11 +597,12 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) {
temp_str,
"Unsaved\n%s",
nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull));
furi_string_replace_str(temp_str, "Mifare", "MIFARE");
}
}
widget_add_text_box_element(
widget, 56, 28, 71, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false);
widget, 56, 33, 71, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false);
furi_string_free(temp_str);

View File

@@ -76,6 +76,7 @@
#pragma once
#include <gui/scene_manager.h>
#include <lib/nfc/protocols/nfc_protocol.h>
#include "nfc_protocol_support_common.h"
@@ -111,3 +112,5 @@ bool nfc_protocol_support_on_event(
* @param[in,out] context pointer to a user-specified context (will be passed to concrete handler).
*/
void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context);
bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature);

View File

@@ -7,6 +7,7 @@
#include <core/string.h>
#include "../../nfc_app.h"
#include "../../nfc_app_i.h"
/**
* @brief Scene entry handler.
@@ -19,10 +20,10 @@ typedef void (*NfcProtocolSupportOnEnter)(NfcApp* instance);
* @brief Scene event handler.
*
* @param[in,out] instance pointer to the NFC application instance.
* @param[in] event custom event that has occurred.
* @param[in] event scene manager event that has occurred.
* @returns true if the event was handled, false otherwise.
*/
typedef bool (*NfcProtocolSupportOnEvent)(NfcApp* instance, uint32_t event);
typedef bool (*NfcProtocolSupportOnEvent)(NfcApp* instance, SceneManagerEvent event);
/**
* @brief Abstract scene interface.

View File

@@ -18,6 +18,7 @@
#include "mf_ultralight/mf_ultralight.h"
#include "mf_classic/mf_classic.h"
#include "mf_desfire/mf_desfire.h"
#include "emv/emv.h"
#include "slix/slix.h"
#include "st25tb/st25tb.h"
@@ -41,5 +42,6 @@ const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = {
[NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire,
[NfcProtocolSlix] = &nfc_protocol_support_slix,
[NfcProtocolSt25tb] = &nfc_protocol_support_st25tb,
[NfcProtocolEmv] = &nfc_protocol_support_emv,
/* Add new protocol support implementations here */
};

View File

@@ -35,8 +35,8 @@ void nfc_protocol_support_common_on_enter_empty(NfcApp* instance) {
UNUSED(instance);
}
bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event) {
bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, SceneManagerEvent event) {
UNUSED(instance);
UNUSED(event);
return true;
return event.type != SceneManagerEventTypeBack;
}

View File

@@ -7,6 +7,7 @@
#include <gui/modules/widget.h>
#include "nfc/nfc_app.h"
#include "nfc/nfc_app_i.h"
/**
* @brief Common submenu indices.
@@ -82,4 +83,4 @@ void nfc_protocol_support_common_on_enter_empty(NfcApp* instance);
* @param[in] event custom event type that has occurred.
* @returns always true.
*/
bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event);
bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, SceneManagerEvent event);

View File

@@ -14,16 +14,30 @@ static void nfc_scene_info_on_enter_slix(NfcApp* instance) {
const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_slix_info(data, NfcProtocolFormatTypeFull, temp_str);
widget_reset(instance->widget);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
}
static void nfc_scene_more_info_on_enter_slix(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix);
FuriString* temp_str = furi_string_alloc();
nfc_render_iso15693_3_system_info(slix_get_base_data(data), temp_str);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
}
static NfcCommand nfc_scene_read_poller_callback_slix(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolSlix);
@@ -91,8 +105,8 @@ static void nfc_scene_emulate_on_enter_slix(NfcApp* instance) {
nfc_listener_start(instance->listener, nfc_scene_emulate_listener_callback_slix, instance);
}
static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEdit) {
static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}
@@ -101,13 +115,18 @@ static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, uint32_t event)
}
const NfcProtocolSupportBase nfc_protocol_support_slix = {
.features = NfcProtocolFeatureEmulateFull,
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
.scene_info =
{
.on_enter = nfc_scene_info_on_enter_slix,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_more_info =
{
.on_enter = nfc_scene_more_info_on_enter_slix,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read =
{
.on_enter = nfc_scene_read_on_enter_slix,

View File

@@ -1,14 +1,12 @@
#include "slix_render.h"
#include "../iso15693_3/iso15693_3_render.h"
void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str) {
nfc_render_iso15693_3_brief(slix_get_base_data(data), str);
if(format_type != NfcProtocolFormatTypeFull) return;
const SlixType slix_type = slix_get_type(data);
furi_string_cat(str, "\n\e#Passwords\n");
furi_string_cat(str, "\n::::::::::::::::::[Passwords]:::::::::::::::::\n");
static const char* slix_password_names[] = {
"Read",
@@ -25,7 +23,7 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ
}
}
furi_string_cat(str, "\e#Lock bits\n");
furi_string_cat(str, ":::::::::::::::::::[Lock bits]::::::::::::::::::::\n");
if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS)) {
furi_string_cat_printf(
@@ -38,7 +36,7 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ
const SlixProtection protection = data->system_info.protection;
furi_string_cat(str, "\e#Page protection\n");
furi_string_cat(str, "::::::::::::[Page protection]::::::::::::\n");
furi_string_cat_printf(str, "Pointer: H >= %02X\n", protection.pointer);
const char* rh = (protection.condition & SLIX_PP_CONDITION_RH) ? "" : "un";
@@ -52,12 +50,12 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ
}
if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PRIVACY)) {
furi_string_cat(str, "\e#Privacy\n");
furi_string_cat(str, "::::::::::::::::::::[Privacy]::::::::::::::::::::::\n");
furi_string_cat_printf(str, "Privacy mode: %sabled\n", data->privacy ? "en" : "dis");
}
if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) {
furi_string_cat(str, "\e#Signature\n");
furi_string_cat(str, ":::::::::::::::::::[Signature]::::::::::::::::::\n");
for(uint32_t i = 0; i < 4; ++i) {
furi_string_cat_printf(str, "%02X ", data->signature[i]);
}

View File

@@ -3,5 +3,6 @@
#include <nfc/protocols/slix/slix.h>
#include "../nfc_protocol_support_render_common.h"
#include "../iso15693_3/iso15693_3_render.h"
void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str);

View File

@@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_st25tb(NfcApp* instance) {
const St25tbData* data = nfc_device_get_data(device, NfcProtocolSt25tb);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_st25tb_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -60,8 +61,8 @@ static void nfc_scene_read_success_on_enter_st25tb(NfcApp* instance) {
furi_string_free(temp_str);
}
static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEdit) {
static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}

View File

@@ -1,4 +1,5 @@
#include "nfc_app_i.h"
#include "helpers/protocol_support/nfc_protocol_support.h"
#include <dolphin/dolphin.h>
#include <applications/main/archive/helpers/archive_helpers_ext.h>
@@ -446,6 +447,15 @@ void nfc_app_reset_detected_protocols(NfcApp* instance) {
instance->protocols_detected_num = 0;
}
void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string) {
furi_assert(instance);
furi_assert(string);
if(!furi_string_empty(instance->file_name)) {
furi_string_cat_printf(string, "Name:%s\n", furi_string_get_cstr(instance->file_name));
}
}
static bool nfc_is_hal_ready() {
if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) {
// No connection to the chip, show an error screen
@@ -467,6 +477,15 @@ static bool nfc_is_hal_ready() {
}
}
static void nfc_show_initial_scene_for_device(NfcApp* nfc) {
NfcProtocol prot = nfc_device_get_protocol(nfc->nfc_device);
uint32_t scene = nfc_protocol_support_has_feature(
prot, NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEmulateUid) ?
NfcSceneEmulate :
NfcSceneSavedMenu;
scene_manager_next_scene(nfc->scene_manager, scene);
}
int32_t nfc_app(void* p) {
if(!nfc_is_hal_ready()) return 0;
@@ -487,7 +506,7 @@ int32_t nfc_app(void* p) {
furi_string_set(nfc->file_path, args);
if(nfc_load_file(nfc, nfc->file_path, false)) {
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulate);
nfc_show_initial_scene_for_device(nfc);
} else {
view_dispatcher_stop(nfc->view_dispatcher);
}

View File

@@ -30,6 +30,7 @@
#include "helpers/mf_ultralight_auth.h"
#include "helpers/mf_user_dict.h"
#include "helpers/mfkey32_logger.h"
#include "helpers/nfc_emv_parser.h"
#include "helpers/mf_classic_key_cache.h"
#include "helpers/nfc_supported_cards.h"
@@ -192,3 +193,5 @@ void nfc_make_app_folder(NfcApp* instance);
void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count);
void nfc_app_reset_detected_protocols(NfcApp* instance);
void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string);

View 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;
}

View File

@@ -147,7 +147,7 @@ static bool metromoney_parse(const NfcDevice* device, FuriString* parsed_data) {
const uint8_t* block_start_ptr =
&data->block[start_block_num + ticket_block_number].data[0];
uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4);
uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4) - 100;
uint32_t balance_lari = balance / 100;
uint8_t balance_tetri = balance % 100;

View File

@@ -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_app, MfDesfireApp)
ADD_SCENE(nfc, emv_more_info, EmvMoreInfo)
ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack)
ADD_SCENE(nfc, mf_classic_detect_reader, MfClassicDetectReader)
ADD_SCENE(nfc, mf_classic_mfkey_nonces_info, MfClassicMfkeyNoncesInfo)

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

View File

@@ -29,6 +29,8 @@ void nfc_scene_select_protocol_on_enter(void* context) {
"%s %s",
prefix,
nfc_device_get_protocol_name(instance->protocols_detected[i]));
furi_string_replace_str(temp_str, "Mifare", "MIFARE");
submenu_add_item(
submenu,
furi_string_get_cstr(temp_str),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -22,6 +22,7 @@ env.Append(
File("protocols/mf_ultralight/mf_ultralight.h"),
File("protocols/mf_classic/mf_classic.h"),
File("protocols/mf_desfire/mf_desfire.h"),
File("protocols/emv/emv.h"),
File("protocols/slix/slix.h"),
File("protocols/st25tb/st25tb.h"),
# Pollers
@@ -32,6 +33,7 @@ env.Append(
File("protocols/mf_ultralight/mf_ultralight_poller.h"),
File("protocols/mf_classic/mf_classic_poller.h"),
File("protocols/mf_desfire/mf_desfire_poller.h"),
File("protocols/emv/emv_poller.h"),
File("protocols/st25tb/st25tb_poller.h"),
# Listeners
File("protocols/iso14443_3a/iso14443_3a_listener.h"),

View File

@@ -7,6 +7,18 @@
#define ISO14443_4_BLOCK_PCB_R (5U << 5)
#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 {
uint8_t pcb;
uint8_t pcb_prev;
@@ -62,3 +74,56 @@ bool iso14443_4_layer_decode_block(
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;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "protocols/iso14443_4a/iso14443_4a.h"
#include <toolbox/bit_buffer.h>
#ifdef __cplusplus
@@ -24,6 +25,11 @@ bool iso14443_4_layer_decode_block(
BitBuffer* output_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
}
#endif

188
lib/nfc/protocols/emv/emv.c Normal file
View 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
View 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

View 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,
};

View 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

View File

@@ -0,0 +1,5 @@
#pragma once
#include <nfc/protocols/nfc_poller_base.h>
extern const NfcPollerBase emv_poller;

View 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;
}

View 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

View File

@@ -13,6 +13,7 @@ typedef enum {
Iso14443_4aErrorNotPresent,
Iso14443_4aErrorProtocol,
Iso14443_4aErrorTimeout,
Iso14443_4aErrorSendExtra,
} Iso14443_4aError;
typedef enum {

View File

@@ -56,6 +56,11 @@ Iso14443_4aError iso14443_4a_poller_send_block(
const BitBuffer* tx_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.
*

View File

@@ -81,3 +81,50 @@ Iso14443_4aError iso14443_4a_poller_send_block(
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;
}

View File

@@ -21,6 +21,7 @@
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
#include <nfc/protocols/mf_classic/mf_classic.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/st25tb/st25tb.h>
@@ -42,5 +43,6 @@ const NfcDeviceBase* nfc_devices[NfcProtocolNum] = {
[NfcProtocolMfDesfire] = &nfc_device_mf_desfire,
[NfcProtocolSlix] = &nfc_device_slix,
[NfcProtocolSt25tb] = &nfc_device_st25tb,
[NfcProtocolEmv] = &nfc_device_emv,
/* Add new protocols here */
};

View File

@@ -20,4 +20,5 @@ const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = {
[NfcProtocolSlix] = &nfc_listener_slix,
[NfcProtocolSt25tb] = NULL,
[NfcProtocolFelica] = &nfc_listener_felica,
[NfcProtocolEmv] = NULL,
};

View File

@@ -9,6 +9,7 @@
#include <nfc/protocols/mf_ultralight/mf_ultralight_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/emv/emv_poller_defs.h>
#include <nfc/protocols/slix/slix_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,
[NfcProtocolMfDesfire] = &mf_desfire_poller,
[NfcProtocolSlix] = &nfc_poller_slix,
/* Add new pollers here */
[NfcProtocolSt25tb] = &nfc_poller_st25tb,
[NfcProtocolEmv] = &emv_poller,
/* Add new pollers here */
};

View File

@@ -12,17 +12,19 @@
* ```
* **************************** Protocol tree structure ***************************
*
* (Start)
* |
* +------------------------+-----------+---------+------------+
* | | | | |
* ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB
* | | |
* +---------------+-------------+ ISO14443-4B SLIX
* | | |
* ISO14443-4A Mf Ultralight Mf Classic
* |
* Mf Desfire
* (Start)
* |
* +------------------------+-----------+---------+------------+
* | | | | |
* ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB
* | | |
* +---------------+-------------+ ISO14443-4B SLIX
* | | |
* ISO14443-4A Mf Ultralight Mf Classic
* |
* +-----+-----+
* | |
* Mf Desfire EMV
* ```
*
* 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. */
static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = {
NfcProtocolMfDesfire,
NfcProtocolEmv,
};
/** List of ISO115693-3 child protocols. */
@@ -146,6 +149,12 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = {
.children_num = 0,
.children_protocol = NULL,
},
[NfcProtocolEmv] =
{
.parent_protocol = NfcProtocolIso14443_4a,
.children_num = 0,
.children_protocol = NULL,
},
/* Add new protocols here */
};

View File

@@ -72,19 +72,19 @@
*
* ### 2.2 File structure 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.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_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_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.c | Implementation of functions declared in `protocol_name_listener.h`. Optional, needed for emulation support. |
* | 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.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_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_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.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_sync.h | Synchronous API declarations. (See below for sync API explanation). Optional.|
* | protocol_name_sync.c | Synchronous API implementation. Optional. |
* | protocol_name_sync.h | Synchronous API declarations. (See below for sync API explanation). Optional. |
* | protocol_name_sync.c | Synchronous API implementation. Optional. |
*
* ## 3 Implement the code
*
@@ -187,6 +187,7 @@ typedef enum {
NfcProtocolMfDesfire,
NfcProtocolSlix,
NfcProtocolSt25tb,
NfcProtocolEmv,
/* Add new protocols here */
NfcProtocolNum, /**< Special value representing the number of available protocols. */

View File

@@ -126,6 +126,8 @@ Header,+,lib/nfc/nfc_device.h,,
Header,+,lib/nfc/nfc_listener.h,,
Header,+,lib/nfc/nfc_poller.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_listener.h,,
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,,
@@ -911,6 +913,26 @@ Function,+,elf_symbolname_hash,uint32_t,const char*
Function,+,empty_screen_alloc,EmptyScreen*,
Function,+,empty_screen_free,void,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,-,erf,double,double
Function,-,erfc,double,double
@@ -2020,6 +2042,7 @@ Function,+,iso14443_4a_load,_Bool,"Iso14443_4aData*, FlipperFormat*, uint32_t"
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_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_save,_Bool,"const Iso14443_4aData*, FlipperFormat*"
Function,+,iso14443_4a_set_uid,_Bool,"Iso14443_4aData*, const uint8_t*, size_t"
@@ -3993,6 +4016,7 @@ Variable,+,message_red_255,const NotificationMessage,
Variable,+,message_sound_off,const NotificationMessage,
Variable,+,message_vibro_off,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_desfire,const NfcDeviceBase,
Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase,
1 entry status name type params
126 Header + lib/nfc/nfc_listener.h
127 Header + lib/nfc/nfc_poller.h
128 Header + lib/nfc/nfc_scanner.h
129 Header + lib/nfc/protocols/emv/emv.h
130 Header + lib/nfc/protocols/emv/emv_poller.h
131 Header + lib/nfc/protocols/iso14443_3a/iso14443_3a.h
132 Header + lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h
133 Header + lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h
913 Function + empty_screen_alloc EmptyScreen*
914 Function + empty_screen_free void EmptyScreen*
915 Function + empty_screen_get_view View* EmptyScreen*
916 Function + emv_alloc EmvData*
917 Function + emv_copy void EmvData*, const EmvData*
918 Function + emv_free void EmvData*
919 Function + emv_get_base_data Iso14443_4aData* const EmvData*
920 Function + emv_get_device_name const char* const EmvData*, NfcDeviceNameType
921 Function + emv_get_uid const uint8_t* const EmvData*, size_t*
922 Function + emv_is_equal _Bool const EmvData*, const EmvData*
923 Function + emv_load _Bool EmvData*, FlipperFormat*, uint32_t
924 Function + emv_poller_get_last_online_atc EmvError EmvPoller*
925 Function + emv_poller_get_pin_try_counter EmvError EmvPoller*
926 Function + emv_poller_get_processing_options EmvError EmvPoller*
927 Function + emv_poller_read_afl EmvError EmvPoller*
928 Function + emv_poller_read_log_entry EmvError EmvPoller*
929 Function + emv_poller_read_sfi_record EmvError EmvPoller*, uint8_t, uint8_t
930 Function + emv_poller_select_application EmvError EmvPoller*
931 Function + emv_poller_select_ppse EmvError EmvPoller*
932 Function + emv_reset void EmvData*
933 Function + emv_save _Bool const EmvData*, FlipperFormat*
934 Function + emv_set_uid _Bool EmvData*, const uint8_t*, size_t
935 Function + emv_verify _Bool EmvData*, const FuriString*
936 Function - erand48 double unsigned short[3]
937 Function - erf double double
938 Function - erfc double double
2042 Function + iso14443_4a_poller_halt Iso14443_4aError Iso14443_4aPoller*
2043 Function + iso14443_4a_poller_read_ats Iso14443_4aError Iso14443_4aPoller*, Iso14443_4aAtsData*
2044 Function + iso14443_4a_poller_send_block Iso14443_4aError Iso14443_4aPoller*, const BitBuffer*, BitBuffer*
2045 Function + iso14443_4a_poller_send_block_pwt_ext Iso14443_4aError Iso14443_4aPoller*, const BitBuffer*, BitBuffer*
2046 Function + iso14443_4a_reset void Iso14443_4aData*
2047 Function + iso14443_4a_save _Bool const Iso14443_4aData*, FlipperFormat*
2048 Function + iso14443_4a_set_uid _Bool Iso14443_4aData*, const uint8_t*, size_t
4016 Variable + message_sound_off const NotificationMessage
4017 Variable + message_vibro_off const NotificationMessage
4018 Variable + message_vibro_on const NotificationMessage
4019 Variable - nfc_device_emv const NfcDeviceBase
4020 Variable - nfc_device_mf_classic const NfcDeviceBase
4021 Variable - nfc_device_mf_desfire const NfcDeviceBase
4022 Variable - nfc_device_mf_ultralight const NfcDeviceBase