mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2025-02-18 14:28:31 +00:00
Fix EMV reading
2 MasterCard were successfully read Issues: some VISA and Mastercard and all UnionPay can't be read TODO: currency, country, Application name TODO: Support multi application mode to read co-branded card.
This commit is contained in:
parent
3a82b3aa3c
commit
08a5adf18e
6 changed files with 99 additions and 46 deletions
|
@ -14,8 +14,8 @@ static void nfc_scene_info_on_enter_emv(NfcApp* instance) {
|
||||||
const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv);
|
const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv);
|
||||||
|
|
||||||
FuriString* temp_str = furi_string_alloc();
|
FuriString* temp_str = furi_string_alloc();
|
||||||
furi_string_cat_printf(
|
// furi_string_cat_printf(
|
||||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
// temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||||
nfc_render_emv_info(data, NfcProtocolFormatTypeFull, temp_str);
|
nfc_render_emv_info(data, NfcProtocolFormatTypeFull, temp_str);
|
||||||
|
|
||||||
widget_add_text_scroll_element(
|
widget_add_text_scroll_element(
|
||||||
|
@ -54,8 +54,8 @@ static void nfc_scene_read_success_on_enter_emv(NfcApp* instance) {
|
||||||
const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv);
|
const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv);
|
||||||
|
|
||||||
FuriString* temp_str = furi_string_alloc();
|
FuriString* temp_str = furi_string_alloc();
|
||||||
furi_string_cat_printf(
|
// furi_string_cat_printf(
|
||||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
// temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||||
nfc_render_emv_info(data, NfcProtocolFormatTypeShort, temp_str);
|
nfc_render_emv_info(data, NfcProtocolFormatTypeShort, temp_str);
|
||||||
|
|
||||||
widget_add_text_scroll_element(
|
widget_add_text_scroll_element(
|
||||||
|
|
|
@ -3,15 +3,11 @@
|
||||||
#include "../iso14443_4a/iso14443_4a_render.h"
|
#include "../iso14443_4a/iso14443_4a_render.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) {
|
||||||
nfc_render_iso14443_4a_brief(emv_get_base_data(data), 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.name, str);
|
||||||
|
nfc_render_emv_pan(data->emv_application.pan, data->emv_application.pan_len, str);
|
||||||
|
nfc_render_emv_expired(&data->emv_application, str);
|
||||||
|
|
||||||
if(format_type != NfcProtocolFormatTypeFull) return;
|
if(format_type == NfcProtocolFormatTypeFull) nfc_render_emv_extra(data, str);
|
||||||
|
|
||||||
furi_string_cat(str, "\n\e#ISO14443-4 data");
|
|
||||||
nfc_render_iso14443_4a_extra(emv_get_base_data(data), str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void nfc_render_emv_data(const EmvData* data, FuriString* str) {
|
void nfc_render_emv_data(const EmvData* data, FuriString* str) {
|
||||||
|
@ -20,16 +16,49 @@ 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_pan(const uint8_t* data, const uint8_t len, FuriString* str) {
|
||||||
for(uint8_t i = 0; i < len; i++) furi_string_cat_printf(str, "%u", data[i]);
|
if(len == 0) return;
|
||||||
|
for(uint8_t i = 0; i < len; i += 2) {
|
||||||
|
furi_string_cat_printf(str, "%02X%02X ", data[i], data[i + 1]);
|
||||||
|
}
|
||||||
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(const EmvApplication* apl, FuriString* str) {
|
||||||
|
UNUSED(apl);
|
||||||
|
UNUSED(str);
|
||||||
|
// nfc/assets/currency_code.nfc
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_render_emv_country(const EmvApplication* apl, FuriString* str) {
|
||||||
|
UNUSED(apl);
|
||||||
|
UNUSED(str);
|
||||||
|
// nfc/assets/country_code.nfc
|
||||||
|
}
|
||||||
|
|
||||||
void nfc_render_emv_name(const char* data, FuriString* str) {
|
void nfc_render_emv_name(const char* data, FuriString* str) {
|
||||||
UNUSED(data);
|
if(strlen(data) == 0) return;
|
||||||
|
furi_string_cat_printf(str, "\e#");
|
||||||
|
furi_string_cat(str, data);
|
||||||
furi_string_cat_printf(str, "\n");
|
furi_string_cat_printf(str, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void nfc_render_emv_application(const EmvApplication* data, FuriString* str) {
|
void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) {
|
||||||
UNUSED(data);
|
const uint8_t len = apl->aid_len;
|
||||||
|
if(len) {
|
||||||
|
furi_string_cat_printf(str, "AID: ");
|
||||||
|
for(uint8_t i = 0; i < len; i++) furi_string_cat_printf(str, "%02X", apl->aid[i]);
|
||||||
|
// nfc/assets/aid.nfc
|
||||||
|
} else {
|
||||||
|
furi_string_cat_printf(str, "No Pay Application found");
|
||||||
|
}
|
||||||
furi_string_cat_printf(str, "\n");
|
furi_string_cat_printf(str, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void nfc_render_emv_extra(const EmvData* data, FuriString* str) {
|
||||||
|
nfc_render_emv_application(&data->emv_application, str);
|
||||||
|
}
|
||||||
|
|
|
@ -13,4 +13,12 @@ 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_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_expired(const EmvApplication* apl, FuriString* str);
|
||||||
|
|
||||||
|
void nfc_render_emv_country(const EmvApplication* apl, FuriString* str);
|
||||||
|
|
||||||
|
void nfc_render_emv_currency(const EmvApplication* apl, FuriString* str);
|
||||||
|
|
|
@ -41,7 +41,7 @@ typedef struct {
|
||||||
bool app_started;
|
bool app_started;
|
||||||
char name[32];
|
char name[32];
|
||||||
bool name_found;
|
bool name_found;
|
||||||
uint8_t pan[10];
|
uint8_t pan[10]; // card_number
|
||||||
uint8_t pan_len;
|
uint8_t pan_len;
|
||||||
uint8_t exp_month;
|
uint8_t exp_month;
|
||||||
uint8_t exp_year;
|
uint8_t exp_year;
|
||||||
|
|
|
@ -71,7 +71,6 @@ static NfcCommand emv_poller_handler_select_ppse(EmvPoller* instance) {
|
||||||
instance->state = EmvPollerStateSelectApplication;
|
instance->state = EmvPollerStateSelectApplication;
|
||||||
} else {
|
} else {
|
||||||
FURI_LOG_E(TAG, "Failed to select PPSE");
|
FURI_LOG_E(TAG, "Failed to select PPSE");
|
||||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
|
||||||
instance->state = EmvPollerStateReadFailed;
|
instance->state = EmvPollerStateReadFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +85,6 @@ static NfcCommand emv_poller_handler_select_application(EmvPoller* instance) {
|
||||||
instance->state = EmvPollerStateGetProcessingOptions;
|
instance->state = EmvPollerStateGetProcessingOptions;
|
||||||
} else {
|
} else {
|
||||||
FURI_LOG_E(TAG, "Failed to select application");
|
FURI_LOG_E(TAG, "Failed to select application");
|
||||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
|
||||||
instance->state = EmvPollerStateReadFailed;
|
instance->state = EmvPollerStateReadFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,10 +96,14 @@ static NfcCommand emv_poller_handler_get_processing_options(EmvPoller* instance)
|
||||||
|
|
||||||
if(instance->error == EmvErrorNone) {
|
if(instance->error == EmvErrorNone) {
|
||||||
FURI_LOG_D(TAG, "Get processing options success");
|
FURI_LOG_D(TAG, "Get processing options success");
|
||||||
instance->state = EmvPollerStateReadSuccess;
|
if(instance->data->emv_application.pan_len > 0) {
|
||||||
|
instance->state = EmvPollerStateReadSuccess;
|
||||||
|
} else {
|
||||||
|
FURI_LOG_D(TAG, "No AFL still. Fallback to bruteforce files");
|
||||||
|
instance->state = EmvPollerStateReadFiles;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
FURI_LOG_E(TAG, "Failed to get processing options");
|
FURI_LOG_E(TAG, "Failed to get processing options");
|
||||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
|
||||||
instance->state = EmvPollerStateReadFiles;
|
instance->state = EmvPollerStateReadFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +118,6 @@ static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) {
|
||||||
instance->state = EmvPollerStateReadSuccess;
|
instance->state = EmvPollerStateReadSuccess;
|
||||||
} else {
|
} else {
|
||||||
FURI_LOG_E(TAG, "Failed to read files");
|
FURI_LOG_E(TAG, "Failed to read files");
|
||||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
|
||||||
instance->state = EmvPollerStateReadFailed;
|
instance->state = EmvPollerStateReadFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +134,7 @@ static NfcCommand emv_poller_handler_read_fail(EmvPoller* instance) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static NfcCommand emv_poller_handler_read_success(EmvPoller* instance) {
|
static NfcCommand emv_poller_handler_read_success(EmvPoller* instance) {
|
||||||
FURI_LOG_D(TAG, "Read success.");
|
FURI_LOG_D(TAG, "Read success");
|
||||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||||
instance->emv_event.type = EmvPollerEventTypeReadSuccess;
|
instance->emv_event.type = EmvPollerEventTypeReadSuccess;
|
||||||
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
||||||
|
|
|
@ -183,6 +183,7 @@ static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplicatio
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EMV_TAG_TRACK_2_EQUIV: {
|
case EMV_TAG_TRACK_2_EQUIV: {
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2_EQUIV %x", tag);
|
||||||
// 0xD0 delimits PAN from expiry (YYMM)
|
// 0xD0 delimits PAN from expiry (YYMM)
|
||||||
for(int x = 1; x < tlen; x++) {
|
for(int x = 1; x < tlen; x++) {
|
||||||
if(buff[i + x + 1] > 0xD0) {
|
if(buff[i + x + 1] > 0xD0) {
|
||||||
|
@ -194,41 +195,45 @@ static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplicatio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert 4-bit to ASCII representation
|
// // Convert 4-bit to ASCII representation
|
||||||
char track_2_equiv[41];
|
// char track_2_equiv[41];
|
||||||
uint8_t track_2_equiv_len = 0;
|
// uint8_t track_2_equiv_len = 0;
|
||||||
for(int x = 0; x < tlen; x++) {
|
// for(int x = 0; x < tlen; x++) {
|
||||||
char top = (buff[i + x] >> 4) + '0';
|
// char top = (buff[i + x] >> 4) + '0';
|
||||||
char bottom = (buff[i + x] & 0x0F) + '0';
|
// char bottom = (buff[i + x] & 0x0F) + '0';
|
||||||
track_2_equiv[x * 2] = top;
|
// track_2_equiv[x * 2] = top;
|
||||||
track_2_equiv_len++;
|
// track_2_equiv_len++;
|
||||||
if(top == '?') break;
|
// if(top == '?') break;
|
||||||
track_2_equiv[x * 2 + 1] = bottom;
|
// track_2_equiv[x * 2 + 1] = bottom;
|
||||||
track_2_equiv_len++;
|
// track_2_equiv_len++;
|
||||||
if(bottom == '?') break;
|
// if(bottom == '?') break;
|
||||||
}
|
// }
|
||||||
track_2_equiv[track_2_equiv_len] = '\0';
|
// track_2_equiv[track_2_equiv_len] = '\0';
|
||||||
|
// FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2_EQUIV %x : %s", tag, track_2_equiv);
|
||||||
success = true;
|
success = true;
|
||||||
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2_EQUIV %x : %s", tag, track_2_equiv);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EMV_TAG_PAN:
|
case EMV_TAG_PAN:
|
||||||
memcpy(app->pan, &buff[i], tlen);
|
memcpy(app->pan, &buff[i], tlen);
|
||||||
app->pan_len = tlen;
|
app->pan_len = tlen;
|
||||||
success = true;
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_PAN %x", tag);
|
||||||
break;
|
break;
|
||||||
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];
|
||||||
success = true;
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_EXP_DATE %x", tag);
|
||||||
break;
|
break;
|
||||||
case EMV_TAG_CURRENCY_CODE:
|
case EMV_TAG_CURRENCY_CODE:
|
||||||
app->currency_code = (buff[i] << 8 | buff[i + 1]);
|
app->currency_code = (buff[i] << 8 | buff[i + 1]);
|
||||||
success = true;
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_CURRENCY_CODE %x", tag);
|
||||||
break;
|
break;
|
||||||
case EMV_TAG_COUNTRY_CODE:
|
case EMV_TAG_COUNTRY_CODE:
|
||||||
app->country_code = (buff[i] << 8 | buff[i + 1]);
|
app->country_code = (buff[i] << 8 | buff[i + 1]);
|
||||||
success = true;
|
success = true;
|
||||||
|
FURI_LOG_T(TAG, "found EMV_TAG_COUNTRY_CODE %x", tag);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -413,6 +418,7 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re
|
||||||
emv_trace(instance, "SFI record:");
|
emv_trace(instance, "SFI record:");
|
||||||
|
|
||||||
if(iso14443_4a_error != Iso14443_4aErrorNone) {
|
if(iso14443_4a_error != Iso14443_4aErrorNone) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to read SFI %d record %d", sfi, record_num);
|
||||||
error = emv_process_error(iso14443_4a_error);
|
error = emv_process_error(iso14443_4a_error);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -423,8 +429,9 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re
|
||||||
buff,
|
buff,
|
||||||
bit_buffer_get_size_bytes(instance->rx_buffer),
|
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||||
&instance->data->emv_application)) {
|
&instance->data->emv_application)) {
|
||||||
error = EmvErrorProtocol;
|
// It's ok while bruteforcing
|
||||||
FURI_LOG_E(TAG, "Failed to read SFI record %d", record_num);
|
//error = EmvErrorProtocol;
|
||||||
|
FURI_LOG_T(TAG, "Failed to parse SFI %d record %d", sfi, record_num);
|
||||||
}
|
}
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|
||||||
|
@ -449,8 +456,12 @@ EmvError emv_poller_read_files(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) {
|
||||||
error |= emv_poller_read_sfi_record(instance, sfi, record);
|
error = emv_poller_read_sfi_record(instance, sfi, record);
|
||||||
|
if(error != EmvErrorNone) break;
|
||||||
|
if(instance->data->emv_application.pan_len != 0)
|
||||||
|
return EmvErrorNone; // Card number fetched
|
||||||
}
|
}
|
||||||
|
error = EmvErrorProtocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
|
@ -462,15 +473,19 @@ EmvError emv_poller_read(EmvPoller* instance) {
|
||||||
|
|
||||||
memset(&instance->data->emv_application, 0, sizeof(EmvApplication));
|
memset(&instance->data->emv_application, 0, sizeof(EmvApplication));
|
||||||
do {
|
do {
|
||||||
error |= emv_poller_select_ppse(instance);
|
error = emv_poller_select_ppse(instance);
|
||||||
if(error != EmvErrorNone) break;
|
if(error != EmvErrorNone) break;
|
||||||
|
|
||||||
error |= emv_poller_select_application(instance);
|
error = emv_poller_select_application(instance);
|
||||||
if(error != EmvErrorNone) break;
|
if(error != EmvErrorNone) break;
|
||||||
|
|
||||||
if(emv_poller_get_processing_options(instance) != EmvErrorNone)
|
error = emv_poller_get_processing_options(instance);
|
||||||
|
if(error != EmvErrorNone) break;
|
||||||
|
|
||||||
|
if(instance->data->emv_application.pan_len == 0) {
|
||||||
error = emv_poller_read_files(instance);
|
error = emv_poller_read_files(instance);
|
||||||
|
if(error != EmvErrorNone) break;
|
||||||
|
}
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
|
|
Loading…
Add table
Reference in a new issue