Merge pull request #700 from Leptopt1los/dev

EMV protocol+parser
This commit is contained in:
MMX
2024-01-30 13:22:16 +03:00
committed by GitHub
31 changed files with 2113 additions and 26 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

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

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

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

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