mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-14 08:08:36 -07:00
Merge branch 'emv-fixes' of https://github.com/wosk/unleashed-firmware into xfw-dev
This commit is contained in:
@@ -64,16 +64,6 @@ static void nfc_scene_read_success_on_enter_emv(NfcApp* instance) {
|
|||||||
furi_string_free(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 = {
|
const NfcProtocolSupportBase nfc_protocol_support_emv = {
|
||||||
.features = NfcProtocolFeatureMoreInfo,
|
.features = NfcProtocolFeatureMoreInfo,
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "emv_render.h"
|
#include "emv_render.h"
|
||||||
|
|
||||||
#include "../iso14443_4a/iso14443_4a_render.h"
|
#include "../iso14443_4a/iso14443_4a_render.h"
|
||||||
|
#include <bit_lib.h>
|
||||||
#include "nfc/nfc_app_i.h"
|
#include "nfc/nfc_app_i.h"
|
||||||
|
|
||||||
void nfc_render_emv_info(const EmvData* data, NfcProtocolFormatType format_type, FuriString* str) {
|
void nfc_render_emv_info(const EmvData* data, NfcProtocolFormatType format_type, FuriString* str) {
|
||||||
@@ -29,21 +30,9 @@ void nfc_render_emv_uid(const uint8_t* uid, const uint8_t uid_len, FuriString* s
|
|||||||
furi_string_cat_printf(str, "\n");
|
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) {
|
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_pan(data->emv_application.pan, data->emv_application.pan_len, str);
|
||||||
nfc_render_emv_name(data->emv_application.name, str);
|
nfc_render_emv_name(data->emv_application.application_name, str);
|
||||||
}
|
}
|
||||||
|
|
||||||
void nfc_render_emv_pan(const uint8_t* data, const uint8_t len, FuriString* str) {
|
void nfc_render_emv_pan(const uint8_t* data, const uint8_t len, FuriString* str) {
|
||||||
@@ -63,11 +52,6 @@ void nfc_render_emv_pan(const uint8_t* data, const uint8_t len, FuriString* str)
|
|||||||
furi_string_cat_printf(str, "\n");
|
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) {
|
void nfc_render_emv_currency(uint16_t cur_code, FuriString* str) {
|
||||||
if(!cur_code) return;
|
if(!cur_code) return;
|
||||||
|
|
||||||
@@ -83,21 +67,23 @@ void nfc_render_emv_country(uint16_t country_code, FuriString* str) {
|
|||||||
void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) {
|
void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) {
|
||||||
const uint8_t len = apl->aid_len;
|
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: ");
|
furi_string_cat_printf(str, "AID: ");
|
||||||
|
|
||||||
for(uint8_t i = 0; i < len; i++) furi_string_cat_printf(str, "%02X", apl->aid[i]);
|
for(uint8_t i = 0; i < len; i++) furi_string_cat_printf(str, "%02X", apl->aid[i]);
|
||||||
|
|
||||||
furi_string_cat_printf(str, "\n");
|
furi_string_cat_printf(str, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nfc_render_emv_pin_try_counter(uint8_t counter, FuriString* str) {
|
void nfc_render_emv_application_interchange_profile(const EmvApplication* apl, FuriString* str) {
|
||||||
if(counter == 0xff) return;
|
uint16_t data = bit_lib_bytes_to_num_be(apl->application_interchange_profile, 2);
|
||||||
furi_string_cat_printf(str, "PIN attempts left: %d\n", counter);
|
|
||||||
|
if(!data) {
|
||||||
|
furi_string_cat_printf(str, "No Interchange profile found\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_cat_printf(str, "Interchange profile: ");
|
||||||
|
for(uint8_t i = 0; i < 2; i++)
|
||||||
|
furi_string_cat_printf(str, "%02X", apl->application_interchange_profile[i]);
|
||||||
|
furi_string_cat_printf(str, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void nfc_render_emv_transactions(const EmvApplication* apl, FuriString* str) {
|
void nfc_render_emv_transactions(const EmvApplication* apl, FuriString* str) {
|
||||||
@@ -126,23 +112,14 @@ void nfc_render_emv_transactions(const EmvApplication* apl, FuriString* str) {
|
|||||||
if(!apl->trans[i].amount) {
|
if(!apl->trans[i].amount) {
|
||||||
furi_string_cat_printf(str, "???");
|
furi_string_cat_printf(str, "???");
|
||||||
} else {
|
} else {
|
||||||
uint8_t* a = (uint8_t*)&apl->trans[i].amount;
|
uint8_t amount_bytes[6];
|
||||||
bool top = true;
|
bit_lib_num_to_bytes_le(apl->trans[i].amount, 6, amount_bytes);
|
||||||
for(int x = 0; x < 6; x++) {
|
|
||||||
// cents
|
bool junk = false;
|
||||||
if(x == 5) {
|
uint64_t amount = bit_lib_bytes_to_num_bcd(amount_bytes, 6, &junk);
|
||||||
furi_string_cat_printf(str, ".%02X", a[x]);
|
uint8_t amount_cents = amount % 100;
|
||||||
break;
|
|
||||||
}
|
furi_string_cat_printf(str, "%llu.%02u", amount / 100, amount_cents);
|
||||||
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) {
|
if(apl->trans[i].currency) {
|
||||||
@@ -183,8 +160,8 @@ void nfc_render_emv_transactions(const EmvApplication* apl, FuriString* str) {
|
|||||||
|
|
||||||
void nfc_render_emv_extra(const EmvData* data, FuriString* str) {
|
void nfc_render_emv_extra(const EmvData* data, FuriString* str) {
|
||||||
nfc_render_emv_application(&data->emv_application, str);
|
nfc_render_emv_application(&data->emv_application, str);
|
||||||
|
nfc_render_emv_application_interchange_profile(&data->emv_application, str);
|
||||||
|
|
||||||
nfc_render_emv_currency(data->emv_application.currency_code, str);
|
nfc_render_emv_currency(data->emv_application.currency_code, str);
|
||||||
nfc_render_emv_country(data->emv_application.country_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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ void nfc_render_emv_name(const char* data, FuriString* str);
|
|||||||
|
|
||||||
void nfc_render_emv_application(const EmvApplication* 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_application_interchange_profile(const EmvApplication* apl, FuriString* str);
|
||||||
|
|
||||||
void nfc_render_emv_expired(const EmvApplication* apl, FuriString* str);
|
void nfc_render_emv_extra(const EmvData* data, FuriString* str);
|
||||||
|
|
||||||
void nfc_render_emv_country(uint16_t country_code, FuriString* str);
|
void nfc_render_emv_country(uint16_t country_code, FuriString* str);
|
||||||
|
|
||||||
|
|||||||
@@ -70,9 +70,11 @@ static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) {
|
|||||||
const EmvApplication app = data->emv_application;
|
const EmvApplication app = data->emv_application;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if(app.name_found)
|
if(strlen(app.application_label)) {
|
||||||
furi_string_cat_printf(parsed_data, "\e#%s\n", app.name);
|
furi_string_cat_printf(parsed_data, "\e#%s\n", app.application_label);
|
||||||
else
|
} else if(strlen(app.application_name)) {
|
||||||
|
furi_string_cat_printf(parsed_data, "\e#%s\n", app.application_name);
|
||||||
|
} else
|
||||||
furi_string_cat_printf(parsed_data, "\e#%s\n", "EMV");
|
furi_string_cat_printf(parsed_data, "\e#%s\n", "EMV");
|
||||||
|
|
||||||
if(app.pan_len) {
|
if(app.pan_len) {
|
||||||
@@ -84,25 +86,75 @@ static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) {
|
|||||||
// Cut padding 'F' from card number
|
// Cut padding 'F' from card number
|
||||||
size_t end = furi_string_search_rchar(pan, 'F');
|
size_t end = furi_string_search_rchar(pan, 'F');
|
||||||
if(end) furi_string_left(pan, end);
|
if(end) furi_string_left(pan, end);
|
||||||
|
furi_string_cat_printf(pan, "\n");
|
||||||
furi_string_cat(parsed_data, pan);
|
furi_string_cat(parsed_data, pan);
|
||||||
|
|
||||||
furi_string_free(pan);
|
furi_string_free(pan);
|
||||||
|
parsed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(app.exp_month | app.exp_year)
|
if(strlen(app.cardholder_name)) {
|
||||||
furi_string_cat_printf(parsed_data, "\nExp: %02X/%02X\n", app.exp_month, app.exp_year);
|
furi_string_cat_printf(parsed_data, "Cardholder name: %s\n", app.cardholder_name);
|
||||||
|
parsed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(app.effective_month) {
|
||||||
|
char day[] = "??";
|
||||||
|
if(app.effective_day) itoa(app.effective_day, day, 16);
|
||||||
|
if(day[1] == '\0') {
|
||||||
|
day[1] = day[0];
|
||||||
|
day[0] = '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"Effective: %s.%02X.20%02X\n",
|
||||||
|
day,
|
||||||
|
app.effective_month,
|
||||||
|
app.effective_year);
|
||||||
|
|
||||||
|
parsed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(app.exp_month) {
|
||||||
|
char day[] = "??";
|
||||||
|
if(app.exp_day) itoa(app.exp_day, day, 16);
|
||||||
|
if(day[1] == '\0') {
|
||||||
|
day[1] = day[0];
|
||||||
|
day[0] = '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data, "Expires: %s.%02X.20%02X\n", day, app.exp_month, app.exp_year);
|
||||||
|
|
||||||
|
parsed = true;
|
||||||
|
}
|
||||||
|
|
||||||
FuriString* str = furi_string_alloc();
|
FuriString* str = furi_string_alloc();
|
||||||
bool storage_readed = emv_get_country_name(app.country_code, str);
|
bool storage_readed = emv_get_country_name(app.country_code, str);
|
||||||
|
|
||||||
if(storage_readed)
|
if(storage_readed) {
|
||||||
furi_string_cat_printf(parsed_data, "Country: %s\n", furi_string_get_cstr(str));
|
furi_string_cat_printf(parsed_data, "Country: %s\n", furi_string_get_cstr(str));
|
||||||
|
parsed = true;
|
||||||
|
}
|
||||||
|
|
||||||
storage_readed = emv_get_currency_name(app.currency_code, str);
|
storage_readed = emv_get_currency_name(app.currency_code, str);
|
||||||
if(storage_readed)
|
if(storage_readed) {
|
||||||
furi_string_cat_printf(parsed_data, "Currency: %s\n", furi_string_get_cstr(str));
|
furi_string_cat_printf(parsed_data, "Currency: %s\n", furi_string_get_cstr(str));
|
||||||
|
parsed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if(app.pin_try_counter != 0xFF)
|
if(app.pin_try_counter != 0xFF) {
|
||||||
furi_string_cat_printf(parsed_data, "PIN attempts left: %d\n", app.pin_try_counter);
|
furi_string_cat_printf(parsed_data, "PIN attempts left: %d\n", app.pin_try_counter);
|
||||||
|
parsed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((app.application_interchange_profile[1] >> 6) & 0b1) {
|
||||||
|
furi_string_cat_printf(parsed_data, "Mobile: yes\n");
|
||||||
|
parsed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!parsed) furi_string_cat_printf(parsed_data, "No data was parsed\n");
|
||||||
|
|
||||||
parsed = true;
|
parsed = true;
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ bool nfc_scene_emv_more_info_on_event(void* context, SceneManagerEvent event) {
|
|||||||
const EmvData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolEmv);
|
const EmvData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolEmv);
|
||||||
|
|
||||||
if(event.type == SceneManagerEventTypeCustom) {
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
widget_reset(nfc->widget);
|
||||||
|
|
||||||
if(event.event == SubmenuIndexTransactions) {
|
if(event.event == SubmenuIndexTransactions) {
|
||||||
FuriString* temp_str = furi_string_alloc();
|
FuriString* temp_str = furi_string_alloc();
|
||||||
nfc_render_emv_transactions(&data->emv_application, temp_str);
|
nfc_render_emv_transactions(&data->emv_application, temp_str);
|
||||||
|
|||||||
@@ -76,10 +76,14 @@ bool emv_load(EmvData* data, FlipperFormat* ff, uint32_t version) {
|
|||||||
|
|
||||||
EmvApplication* app = &data->emv_application;
|
EmvApplication* app = &data->emv_application;
|
||||||
|
|
||||||
//Read name
|
flipper_format_read_string(ff, "Cardholder name", temp_str);
|
||||||
if(!flipper_format_read_string(ff, "Name", temp_str)) break;
|
strcpy(app->cardholder_name, furi_string_get_cstr(temp_str));
|
||||||
strcpy(app->name, furi_string_get_cstr(temp_str));
|
|
||||||
if(app->name[0] != '\0') app->name_found = true;
|
flipper_format_read_string(ff, "Application name", temp_str);
|
||||||
|
strcpy(app->application_name, furi_string_get_cstr(temp_str));
|
||||||
|
|
||||||
|
flipper_format_read_string(ff, "Application label", temp_str);
|
||||||
|
strcpy(app->application_label, furi_string_get_cstr(temp_str));
|
||||||
|
|
||||||
uint32_t pan_len;
|
uint32_t pan_len;
|
||||||
if(!flipper_format_read_uint32(ff, "PAN length", &pan_len, 1)) break;
|
if(!flipper_format_read_uint32(ff, "PAN length", &pan_len, 1)) break;
|
||||||
@@ -93,15 +97,24 @@ bool emv_load(EmvData* data, FlipperFormat* ff, uint32_t version) {
|
|||||||
|
|
||||||
if(!flipper_format_read_hex(ff, "AID", app->aid, aid_len)) break;
|
if(!flipper_format_read_hex(ff, "AID", app->aid, aid_len)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_read_hex(
|
||||||
|
ff, "Application interchange profile", app->application_interchange_profile, 2))
|
||||||
|
break;
|
||||||
|
|
||||||
if(!flipper_format_read_hex(ff, "Country code", (uint8_t*)&app->country_code, 2)) 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, "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 year", &app->exp_year, 1)) break;
|
||||||
if(!flipper_format_read_hex(ff, "Expiration month", &app->exp_month, 1)) break;
|
if(!flipper_format_read_hex(ff, "Expiration month", &app->exp_month, 1)) break;
|
||||||
|
if(!flipper_format_read_hex(ff, "Expiration day", &app->exp_day, 1)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_read_hex(ff, "Effective year", &app->effective_year, 1)) break;
|
||||||
|
if(!flipper_format_read_hex(ff, "Effective month", &app->effective_month, 1)) break;
|
||||||
|
if(!flipper_format_read_hex(ff, "Effective day", &app->effective_day, 1)) break;
|
||||||
|
|
||||||
uint32_t pin_try_counter;
|
uint32_t pin_try_counter;
|
||||||
if(!flipper_format_read_uint32(ff, "PIN counter", &pin_try_counter, 1)) break;
|
if(!flipper_format_read_uint32(ff, "PIN try counter", &pin_try_counter, 1)) break;
|
||||||
app->pin_try_counter = pin_try_counter;
|
app->pin_try_counter = pin_try_counter;
|
||||||
|
|
||||||
parsed = true;
|
parsed = true;
|
||||||
@@ -124,7 +137,12 @@ bool emv_save(const EmvData* data, FlipperFormat* ff) {
|
|||||||
|
|
||||||
if(!flipper_format_write_comment_cstr(ff, "EMV specific data:\n")) break;
|
if(!flipper_format_write_comment_cstr(ff, "EMV specific data:\n")) break;
|
||||||
|
|
||||||
if(!flipper_format_write_string_cstr(ff, "Name", app.name)) break;
|
if(!flipper_format_write_string_cstr(ff, "Cardholder name", app.cardholder_name)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_string_cstr(ff, "Application name", app.application_name)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_string_cstr(ff, "Application label", app.application_label))
|
||||||
|
break;
|
||||||
|
|
||||||
uint32_t pan_len = app.pan_len;
|
uint32_t pan_len = app.pan_len;
|
||||||
if(!flipper_format_write_uint32(ff, "PAN length", &pan_len, 1)) break;
|
if(!flipper_format_write_uint32(ff, "PAN length", &pan_len, 1)) break;
|
||||||
@@ -136,15 +154,25 @@ bool emv_save(const EmvData* data, FlipperFormat* ff) {
|
|||||||
|
|
||||||
if(!flipper_format_write_hex(ff, "AID", app.aid, aid_len)) break;
|
if(!flipper_format_write_hex(ff, "AID", app.aid, aid_len)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_hex(
|
||||||
|
ff, "Application interchange profile", app.application_interchange_profile, 2))
|
||||||
|
break;
|
||||||
|
|
||||||
if(!flipper_format_write_hex(ff, "Country code", (uint8_t*)&app.country_code, 2)) 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, "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 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_hex(ff, "Expiration month", (uint8_t*)&app.exp_month, 1)) break;
|
||||||
|
if(!flipper_format_write_hex(ff, "Expiration day", (uint8_t*)&app.exp_day, 1)) break;
|
||||||
|
|
||||||
if(!flipper_format_write_uint32(ff, "PIN counter", (uint32_t*)&app.pin_try_counter, 1))
|
if(!flipper_format_write_hex(ff, "Effective year", (uint8_t*)&app.effective_year, 1))
|
||||||
|
break;
|
||||||
|
if(!flipper_format_write_hex(ff, "Effective month", (uint8_t*)&app.effective_month, 1))
|
||||||
|
break;
|
||||||
|
if(!flipper_format_write_hex(ff, "Effective day", (uint8_t*)&app.effective_day, 1)) break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_uint32(ff, "PIN try counter", (uint32_t*)&app.pin_try_counter, 1))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
saved = true;
|
saved = true;
|
||||||
|
|||||||
@@ -10,12 +10,15 @@ extern "C" {
|
|||||||
|
|
||||||
#define EMV_REQ_GET_DATA 0x80CA
|
#define EMV_REQ_GET_DATA 0x80CA
|
||||||
|
|
||||||
#define EMV_TAG_APP_TEMPLATE 0x61
|
#define UNKNOWN_TAG 0x0B
|
||||||
|
|
||||||
#define EMV_TAG_AID 0x4F
|
#define EMV_TAG_AID 0x4F
|
||||||
#define EMV_TAG_PRIORITY 0x87
|
#define EMV_TAG_PRIORITY 0x87
|
||||||
|
#define EMV_TAG_APPL_INTERCHANGE_PROFILE 0x82
|
||||||
#define EMV_TAG_PDOL 0x9F38
|
#define EMV_TAG_PDOL 0x9F38
|
||||||
#define EMV_TAG_CARD_NAME 0x50
|
#define EMV_TAG_APPL_LABEL 0x50
|
||||||
#define EMV_TAG_FCI 0xBF0C
|
#define EMV_TAG_APPL_NAME 0x9F12
|
||||||
|
#define EMV_TAG_APPL_EFFECTIVE 0x5F25
|
||||||
#define EMV_TAG_PIN_TRY_COUNTER 0x9F17
|
#define EMV_TAG_PIN_TRY_COUNTER 0x9F17
|
||||||
#define EMV_TAG_LOG_ENTRY 0x9F4D
|
#define EMV_TAG_LOG_ENTRY 0x9F4D
|
||||||
#define EMV_TAG_LOG_FMT 0x9F4F
|
#define EMV_TAG_LOG_FMT 0x9F4F
|
||||||
@@ -36,12 +39,19 @@ extern "C" {
|
|||||||
#define EMV_TAG_COUNTRY_CODE 0x5F28
|
#define EMV_TAG_COUNTRY_CODE 0x5F28
|
||||||
#define EMV_TAG_CURRENCY_CODE 0x9F42
|
#define EMV_TAG_CURRENCY_CODE 0x9F42
|
||||||
#define EMV_TAG_CARDHOLDER_NAME 0x5F20
|
#define EMV_TAG_CARDHOLDER_NAME 0x5F20
|
||||||
|
#define EMV_TAG_CARDHOLDER_NAME_EXTENDED 0x9F0B
|
||||||
#define EMV_TAG_TRACK_2_DATA 0x9F6B
|
#define EMV_TAG_TRACK_2_DATA 0x9F6B
|
||||||
#define EMV_TAG_GPO_FMT1 0x80
|
#define EMV_TAG_GPO_FMT1 0x80
|
||||||
|
|
||||||
#define EMV_TAG_RESP_BUF_SIZE 0x6C
|
#define EMV_TAG_RESP_BUF_SIZE 0x6C
|
||||||
#define EMV_TAG_RESP_BYTES_AVAILABLE 0x61
|
#define EMV_TAG_RESP_BYTES_AVAILABLE 0x61
|
||||||
|
|
||||||
|
// Not used tags
|
||||||
|
#define EMV_TAG_FORM_FACTOR 0x9F6E
|
||||||
|
#define EMV_TAG_APP_TEMPLATE 0x61
|
||||||
|
#define EMV_TAG_FCI 0xBF0C
|
||||||
|
#define EMV_TAG_DEPOSIT_LOG_ENTRY 0xDF4D
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint16_t tag;
|
uint16_t tag;
|
||||||
uint8_t data[];
|
uint8_t data[];
|
||||||
@@ -72,12 +82,18 @@ typedef struct {
|
|||||||
uint8_t priority;
|
uint8_t priority;
|
||||||
uint8_t aid[16];
|
uint8_t aid[16];
|
||||||
uint8_t aid_len;
|
uint8_t aid_len;
|
||||||
char name[32];
|
uint8_t application_interchange_profile[2];
|
||||||
bool name_found;
|
char application_name[16 + 1];
|
||||||
|
char application_label[16 + 1];
|
||||||
|
char cardholder_name[24 + 1];
|
||||||
uint8_t pan[10]; // card_number
|
uint8_t pan[10]; // card_number
|
||||||
uint8_t pan_len;
|
uint8_t pan_len;
|
||||||
|
uint8_t exp_day;
|
||||||
uint8_t exp_month;
|
uint8_t exp_month;
|
||||||
uint8_t exp_year;
|
uint8_t exp_year;
|
||||||
|
uint8_t effective_day;
|
||||||
|
uint8_t effective_month;
|
||||||
|
uint8_t effective_year;
|
||||||
uint16_t country_code;
|
uint16_t country_code;
|
||||||
uint16_t currency_code;
|
uint16_t currency_code;
|
||||||
uint8_t pin_try_counter;
|
uint8_t pin_try_counter;
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ static bool emv_poller_detect(NfcGenericEvent event, void* context) {
|
|||||||
|
|
||||||
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
|
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
|
||||||
const EmvError error = emv_poller_select_ppse(instance);
|
const EmvError error = emv_poller_select_ppse(instance);
|
||||||
protocol_detected = (error == EmvErrorNone);
|
protocol_detected = (error == EmvErrorNone) && (instance->data->emv_application.aid_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
return protocol_detected;
|
return protocol_detected;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#define TAG "EMVPoller"
|
#define TAG "EMVPoller"
|
||||||
|
|
||||||
|
// "Terminal" parameters, which could be requested by card
|
||||||
const PDOLValue pdol_term_info = {0x9F59, {0xC8, 0x80, 0x00}}; // Terminal transaction information
|
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_term_type = {0x9F5A, {0x00}}; // Terminal transaction type
|
||||||
const PDOLValue pdol_merchant_type = {0x9F58, {0x01}}; // Merchant type indicator
|
const PDOLValue pdol_merchant_type = {0x9F58, {0x01}}; // Merchant type indicator
|
||||||
@@ -76,39 +77,6 @@ static void emv_trace(EmvPoller* instance, const char* message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
static bool
|
||||||
emv_decode_tlv_tag(const uint8_t* buff, uint16_t tag, uint8_t tlen, EmvApplication* app) {
|
emv_decode_tlv_tag(const uint8_t* buff, uint16_t tag, uint8_t tlen, EmvApplication* app) {
|
||||||
uint8_t i = 0;
|
uint8_t i = 0;
|
||||||
@@ -147,12 +115,35 @@ static bool
|
|||||||
success = true;
|
success = true;
|
||||||
FURI_LOG_T(TAG, "found EMV_TAG_APP_PRIORITY %X: %d", tag, app->priority);
|
FURI_LOG_T(TAG, "found EMV_TAG_APP_PRIORITY %X: %d", tag, app->priority);
|
||||||
break;
|
break;
|
||||||
case EMV_TAG_CARD_NAME:
|
case EMV_TAG_APPL_INTERCHANGE_PROFILE:
|
||||||
memcpy(app->name, &buff[i], tlen);
|
furi_check(tlen == 2);
|
||||||
app->name[tlen] = '\0';
|
memcpy(app->application_interchange_profile, &buff[i], tlen);
|
||||||
app->name_found = true;
|
|
||||||
success = true;
|
success = true;
|
||||||
FURI_LOG_T(TAG, "found EMV_TAG_CARD_NAME %x : %s", tag, app->name);
|
FURI_LOG_T(TAG, "found EMV_TAG_APPL_INTERCHANGE_PROFILE %x: ", tag);
|
||||||
|
for(size_t x = 0; x < tlen; x++) {
|
||||||
|
FURI_LOG_RAW_T("%02X ", app->application_interchange_profile[x]);
|
||||||
|
}
|
||||||
|
FURI_LOG_RAW_T("\r\n");
|
||||||
|
break;
|
||||||
|
case EMV_TAG_APPL_LABEL:
|
||||||
|
memcpy(app->application_label, &buff[i], tlen);
|
||||||
|
app->application_label[tlen] = '\0';
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_APPL_LABEL %x: %s", tag, app->application_label);
|
||||||
|
break;
|
||||||
|
case EMV_TAG_APPL_NAME:
|
||||||
|
furi_check(tlen < sizeof(app->application_name));
|
||||||
|
memcpy(app->application_name, &buff[i], tlen);
|
||||||
|
app->application_name[tlen] = '\0';
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_APPL_NAME %x: %s", tag, app->application_name);
|
||||||
|
break;
|
||||||
|
case EMV_TAG_APPL_EFFECTIVE:
|
||||||
|
app->effective_year = buff[i];
|
||||||
|
app->effective_month = buff[i + 1];
|
||||||
|
app->effective_day = buff[i + 2];
|
||||||
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_APPL_ISSUE %x:", tag);
|
||||||
break;
|
break;
|
||||||
case EMV_TAG_PDOL:
|
case EMV_TAG_PDOL:
|
||||||
memcpy(app->pdol.data, &buff[i], tlen);
|
memcpy(app->pdol.data, &buff[i], tlen);
|
||||||
@@ -209,11 +200,19 @@ static bool
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EMV_TAG_CARDHOLDER_NAME: {
|
case EMV_TAG_CARDHOLDER_NAME: {
|
||||||
char name[27];
|
if(strlen(app->cardholder_name) > tlen) break;
|
||||||
memcpy(name, &buff[i], tlen);
|
memcpy(app->cardholder_name, &buff[i], tlen);
|
||||||
name[tlen] = '\0';
|
app->cardholder_name[tlen] = '\0';
|
||||||
|
|
||||||
|
// use space char as terminator
|
||||||
|
for(size_t i = 0; i < tlen; i++)
|
||||||
|
if(app->cardholder_name[i] == 0x20) {
|
||||||
|
app->cardholder_name[i] = '\0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
FURI_LOG_T(TAG, "found EMV_TAG_CARDHOLDER_NAME %x: %s", tag, name);
|
FURI_LOG_T(TAG, "found EMV_TAG_CARDHOLDER_NAME %x: %s", tag, app->cardholder_name);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EMV_TAG_PAN:
|
case EMV_TAG_PAN:
|
||||||
@@ -225,6 +224,7 @@ static bool
|
|||||||
case EMV_TAG_EXP_DATE:
|
case EMV_TAG_EXP_DATE:
|
||||||
app->exp_year = buff[i];
|
app->exp_year = buff[i];
|
||||||
app->exp_month = buff[i + 1];
|
app->exp_month = buff[i + 1];
|
||||||
|
app->exp_day = buff[i + 2];
|
||||||
success = true;
|
success = true;
|
||||||
FURI_LOG_T(TAG, "found EMV_TAG_EXP_DATE %x", tag);
|
FURI_LOG_T(TAG, "found EMV_TAG_EXP_DATE %x", tag);
|
||||||
break;
|
break;
|
||||||
@@ -406,6 +406,36 @@ static bool emv_decode_response_tlv(const uint8_t* buff, uint8_t len, EmvApplica
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void emv_prepare_pdol(APDU* dest, APDU* src) {
|
||||||
|
uint16_t tag = 0;
|
||||||
|
uint8_t tlen = 0;
|
||||||
|
uint8_t i = 0;
|
||||||
|
while(i < src->size) {
|
||||||
|
bool tag_found = false;
|
||||||
|
if(!emv_parse_tag(src->data, src->size, &tag, &tlen, &i)) {
|
||||||
|
FURI_LOG_T(TAG, "Parsing PDOL failed at 0x%x", i);
|
||||||
|
dest->size = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_check(dest->size + tlen < sizeof(dest->data));
|
||||||
|
for(uint8_t j = 0; j < COUNT_OF(pdol_values); j++) {
|
||||||
|
if(tag == pdol_values[j]->tag) {
|
||||||
|
memcpy(dest->data + dest->size, pdol_values[j]->data, tlen);
|
||||||
|
dest->size += tlen;
|
||||||
|
tag_found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!tag_found) {
|
||||||
|
// Unknown tag, fill zeros
|
||||||
|
memset(dest->data + dest->size, 0, tlen);
|
||||||
|
dest->size += tlen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EmvError emv_poller_select_ppse(EmvPoller* instance) {
|
EmvError emv_poller_select_ppse(EmvPoller* instance) {
|
||||||
EmvError error = EmvErrorNone;
|
EmvError error = EmvErrorNone;
|
||||||
|
|
||||||
@@ -602,6 +632,11 @@ EmvError emv_poller_read_afl(EmvPoller* instance) {
|
|||||||
|
|
||||||
FURI_LOG_D(TAG, "Search PAN in SFI");
|
FURI_LOG_D(TAG, "Search PAN in SFI");
|
||||||
|
|
||||||
|
uint8_t sfi_2_mask = 0;
|
||||||
|
uint8_t sfi_3_mask = 0;
|
||||||
|
|
||||||
|
bool pan_fetched = (instance->data->emv_application.pan_len);
|
||||||
|
|
||||||
// Iterate through all files
|
// Iterate through all files
|
||||||
for(size_t i = 0; i < instance->data->emv_application.afl.size; i += 4) {
|
for(size_t i = 0; i < instance->data->emv_application.afl.size; i += 4) {
|
||||||
uint8_t sfi = afl->data[i] >> 3;
|
uint8_t sfi = afl->data[i] >> 3;
|
||||||
@@ -609,6 +644,9 @@ EmvError emv_poller_read_afl(EmvPoller* instance) {
|
|||||||
uint8_t record_end = afl->data[i + 2];
|
uint8_t record_end = afl->data[i + 2];
|
||||||
// Iterate through all records in file
|
// Iterate through all records in file
|
||||||
for(uint8_t record = record_start; record <= record_end; ++record) {
|
for(uint8_t record = record_start; record <= record_end; ++record) {
|
||||||
|
if((sfi == 2) && (record < 8)) FURI_BIT_SET(sfi_2_mask, record);
|
||||||
|
if((sfi == 3) && (record < 8)) FURI_BIT_SET(sfi_3_mask, record);
|
||||||
|
|
||||||
error = emv_poller_read_sfi_record(instance, sfi, record);
|
error = emv_poller_read_sfi_record(instance, sfi, record);
|
||||||
if(error != EmvErrorNone) break;
|
if(error != EmvErrorNone) break;
|
||||||
|
|
||||||
@@ -620,15 +658,41 @@ EmvError emv_poller_read_afl(EmvPoller* instance) {
|
|||||||
FURI_LOG_T(TAG, "Failed to parse SFI 0x%X record %d", sfi, record);
|
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),
|
if(instance->data->emv_application.pan_len) pan_fetched = true; // Card number fetched
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bool cardholder_name_fetched = strlen(instance->data->emv_application.cardholder_name);
|
||||||
|
// Bruteforse files 2-3
|
||||||
|
FURI_LOG_T(TAG, "Bruteforce files 2-3");
|
||||||
|
for(size_t sfi = 2; sfi <= 3; sfi++) {
|
||||||
|
// Iterate through records 1-5 in file
|
||||||
|
for(size_t record = 1; record <= 5; record++) {
|
||||||
|
// Skip previously readed sfi
|
||||||
|
if(sfi == 2) {
|
||||||
|
if((sfi_2_mask >> record) & (0b1)) continue;
|
||||||
|
}
|
||||||
|
if(sfi == 3) {
|
||||||
|
if((sfi_3_mask >> record) & (0b1)) continue;
|
||||||
|
}
|
||||||
|
|
||||||
return error;
|
if(strlen(instance->data->emv_application.cardholder_name))
|
||||||
|
cardholder_name_fetched = true;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(pan_fetched || cardholder_name_fetched)
|
||||||
|
return EmvErrorNone;
|
||||||
|
else
|
||||||
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
static EmvError emv_poller_req_get_data(EmvPoller* instance, uint16_t tag) {
|
static EmvError emv_poller_req_get_data(EmvPoller* instance, uint16_t tag) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#define TAG "Iso14443_4aPoller"
|
#define TAG "Iso14443_4aPoller"
|
||||||
|
|
||||||
#define ISO14443_4A_FSDI_256 (0x8U)
|
#define ISO14443_4A_FSDI_256 (0x8U)
|
||||||
|
#define ISO14443_4A_SEND_BLOCK_MAX_ATTEMPTS (20)
|
||||||
|
|
||||||
Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance) {
|
Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance) {
|
||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
@@ -88,7 +89,7 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext(
|
|||||||
BitBuffer* rx_buffer) {
|
BitBuffer* rx_buffer) {
|
||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
|
|
||||||
uint8_t retry = 5;
|
uint8_t attempts_left = ISO14443_4A_SEND_BLOCK_MAX_ATTEMPTS;
|
||||||
bit_buffer_reset(instance->tx_buffer);
|
bit_buffer_reset(instance->tx_buffer);
|
||||||
iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer);
|
iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer);
|
||||||
|
|
||||||
@@ -103,6 +104,8 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext(
|
|||||||
iso14443_4a_get_fwt_fc_max(instance->data));
|
iso14443_4a_get_fwt_fc_max(instance->data));
|
||||||
|
|
||||||
if(iso14443_3a_error != Iso14443_3aErrorNone) {
|
if(iso14443_3a_error != Iso14443_3aErrorNone) {
|
||||||
|
FURI_LOG_T(
|
||||||
|
TAG, "Attempt: %u", ISO14443_4A_SEND_BLOCK_MAX_ATTEMPTS + 1 - attempts_left);
|
||||||
FURI_LOG_RAW_T("RAW RX(%d):", bit_buffer_get_size_bytes(instance->rx_buffer));
|
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++) {
|
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("%02X ", bit_buffer_get_byte(instance->rx_buffer, x));
|
||||||
@@ -116,7 +119,7 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext(
|
|||||||
error = iso14443_4_layer_decode_block_pwt_ext(
|
error = iso14443_4_layer_decode_block_pwt_ext(
|
||||||
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer);
|
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer);
|
||||||
if(error == Iso14443_4aErrorSendExtra) {
|
if(error == Iso14443_4aErrorSendExtra) {
|
||||||
if(--retry == 0) break;
|
if(--attempts_left == 0) break;
|
||||||
// Send response for Control message
|
// Send response for Control message
|
||||||
if(bit_buffer_get_size_bytes(rx_buffer))
|
if(bit_buffer_get_size_bytes(rx_buffer))
|
||||||
bit_buffer_copy(instance->tx_buffer, rx_buffer);
|
bit_buffer_copy(instance->tx_buffer, rx_buffer);
|
||||||
|
|||||||
Reference in New Issue
Block a user