From d0c466ccc0c1185e1d6f69058cfc0a7f8951afe3 Mon Sep 17 00:00:00 2001 From: Methodius Date: Thu, 11 Jan 2024 00:48:55 +0900 Subject: [PATCH 01/28] EMV protocol added --- lib/nfc/protocols/emv/emv.c | 111 ++++++ lib/nfc/protocols/emv/emv.h | 100 +++++ lib/nfc/protocols/emv/emv_poller.c | 206 ++++++++++ lib/nfc/protocols/emv/emv_poller.h | 51 +++ lib/nfc/protocols/emv/emv_poller_defs.h | 5 + lib/nfc/protocols/emv/emv_poller_i.c | 477 ++++++++++++++++++++++++ lib/nfc/protocols/emv/emv_poller_i.h | 52 +++ lib/nfc/protocols/nfc_poller_defs.c | 2 + lib/nfc/protocols/nfc_protocol.c | 31 +- lib/nfc/protocols/nfc_protocol.h | 25 +- 10 files changed, 1037 insertions(+), 23 deletions(-) create mode 100644 lib/nfc/protocols/emv/emv.c create mode 100644 lib/nfc/protocols/emv/emv.h create mode 100644 lib/nfc/protocols/emv/emv_poller.c create mode 100644 lib/nfc/protocols/emv/emv_poller.h create mode 100644 lib/nfc/protocols/emv/emv_poller_defs.h create mode 100644 lib/nfc/protocols/emv/emv_poller_i.c create mode 100644 lib/nfc/protocols/emv/emv_poller_i.h diff --git a/lib/nfc/protocols/emv/emv.c b/lib/nfc/protocols/emv/emv.c new file mode 100644 index 000000000..2de6fb132 --- /dev/null +++ b/lib/nfc/protocols/emv/emv.c @@ -0,0 +1,111 @@ +//#include "emv_i.h" + +#include +#include "protocols/emv/emv.h" +#include +#include + +#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(); + + 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); + UNUSED(data); + UNUSED(ff); + UNUSED(version); + + return false; +} + +bool emv_save(const EmvData* data, FlipperFormat* ff) { + furi_assert(data); + UNUSED(data); + UNUSED(ff); + + return false; +} + +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; +} \ No newline at end of file diff --git a/lib/nfc/protocols/emv/emv.h b/lib/nfc/protocols/emv/emv.h new file mode 100644 index 000000000..feb390c7d --- /dev/null +++ b/lib/nfc/protocols/emv/emv.h @@ -0,0 +1,100 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_APDU_LEN 255 + +#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_LOG_CTRL 0x9F4D +#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 + +typedef struct { + uint16_t tag; + uint8_t data[]; +} PDOLValue; + +typedef struct { + uint8_t size; + uint8_t data[MAX_APDU_LEN]; +} APDU; + +typedef struct { + uint8_t priority; + uint8_t aid[16]; + uint8_t aid_len; + bool app_started; + char name[32]; + bool name_found; + uint8_t card_number[10]; + uint8_t card_number_len; + uint8_t exp_month; + uint8_t exp_year; + uint16_t country_code; + uint16_t currency_code; + 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 diff --git a/lib/nfc/protocols/emv/emv_poller.c b/lib/nfc/protocols/emv/emv_poller.c new file mode 100644 index 000000000..46f02b363 --- /dev/null +++ b/lib/nfc/protocols/emv/emv_poller.c @@ -0,0 +1,206 @@ +#include "emv_poller_i.h" + +#include + +#include + +#define TAG "EMVPoller" + +// SKOLKO????????????????????????????????????????????????????????????????? +#define EMV_BUF_SIZE (512U) +#define EMV_RESULT_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->input_buffer = bit_buffer_alloc(EMV_BUF_SIZE); + instance->result_buffer = bit_buffer_alloc(EMV_RESULT_BUF_SIZE); + + 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); + bit_buffer_free(instance->input_buffer); + bit_buffer_free(instance->result_buffer); + free(instance); +} + +static NfcCommand emv_poller_handler_idle(EmvPoller* instance) { + bit_buffer_reset(instance->input_buffer); + bit_buffer_reset(instance->result_buffer); + 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"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = EmvPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand emv_poller_handler_select_application(EmvPoller* instance) { + instance->error = emv_poller_select_ppse(instance); + if(instance->error == EmvErrorNone) { + FURI_LOG_D(TAG, "Select application success"); + instance->state = EmvPollerStateGetProcessingOptions; + } else { + FURI_LOG_E(TAG, "Failed to select application"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = EmvPollerStateReadFailed; + } + + 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"); + instance->state = EmvPollerStateReadSuccess; + } else { + FURI_LOG_E(TAG, "Failed to get processing options"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = EmvPollerStateReadFiles; + } + + return NfcCommandContinue; +} + +static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) { + instance->error = emv_poller_read_files(instance); + + if(instance->error == EmvErrorNone) { + FURI_LOG_D(TAG, "Read files success"); + instance->state = EmvPollerStateReadSuccess; + } else { + FURI_LOG_E(TAG, "Failed to read files"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = EmvPollerStateReadFailed; + } + + 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, + [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, +}; \ No newline at end of file diff --git a/lib/nfc/protocols/emv/emv_poller.h b/lib/nfc/protocols/emv/emv_poller.h new file mode 100644 index 000000000..f86186fe2 --- /dev/null +++ b/lib/nfc/protocols/emv/emv_poller.h @@ -0,0 +1,51 @@ +#pragma once + +#include "emv.h" + +#include + +#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_files(EmvPoller* instance); + +EmvError emv_poller_read(EmvPoller* instance); \ No newline at end of file diff --git a/lib/nfc/protocols/emv/emv_poller_defs.h b/lib/nfc/protocols/emv/emv_poller_defs.h new file mode 100644 index 000000000..001910112 --- /dev/null +++ b/lib/nfc/protocols/emv/emv_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase emv_poller; \ No newline at end of file diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c new file mode 100644 index 000000000..22c862661 --- /dev/null +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -0,0 +1,477 @@ +#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_response(const uint8_t* buff, uint16_t len, EmvApplication* app) { + uint16_t i = 0; + uint16_t tag = 0, first_byte = 0; + uint16_t tlen = 0; + bool success = false; + + while(i < len) { + first_byte = buff[i]; + 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++; + if((first_byte & 32) == 32) { // "Constructed" -- contains more TLV data to parse + FURI_LOG_T(TAG, "Constructed TLV %x", tag); + if(!emv_decode_response(&buff[i], tlen, app)) { + FURI_LOG_T(TAG, "Failed to decode response for %x", tag); + // return false; + } else { + success = true; + } + } else { + switch(tag) { + 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); + break; + case EMV_TAG_PRIORITY: + memcpy(&app->priority, &buff[i], tlen); + success = true; + 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; + case EMV_TAG_TRACK_1_EQUIV: { + 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_EQUIV: { + // 0xD0 delimits PAN from expiry (YYMM) + for(int x = 1; x < tlen; x++) { + if(buff[i + x + 1] > 0xD0) { + memcpy(app->card_number, &buff[i], x + 1); + app->card_number_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'; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2_EQUIV %x : %s", tag, track_2_equiv); + break; + } + case EMV_TAG_PAN: + memcpy(app->card_number, &buff[i], tlen); + app->card_number_len = tlen; + success = true; + break; + case EMV_TAG_EXP_DATE: + app->exp_year = buff[i]; + app->exp_month = buff[i + 1]; + success = true; + break; + case EMV_TAG_CURRENCY_CODE: + app->currency_code = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + case EMV_TAG_COUNTRY_CODE: + app->country_code = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + } + } + 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( + 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( + 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; + + // DELETE IT??????????????????????????????????????????????????????????????????????????????????????? + instance->data->emv_application.app_started = false; + + 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( + instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + FURI_LOG_E(TAG, "Failed to read PAN or PDOL"); + error = emv_process_error(iso14443_4a_error); + break; + } + + emv_trace(instance, "Start application answer:"); + + const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); + + if(!emv_decode_response( + buff, + bit_buffer_get_size_bytes(instance->rx_buffer), + &instance->data->emv_application)) { + error = EmvErrorProtocol; + FURI_LOG_E(TAG, "Failed to parse application"); + break; + } + + instance->data->emv_application.app_started = true; + } 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( + instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + FURI_LOG_E(TAG, "Failed to get processing options"); + error = emv_process_error(iso14443_4a_error); + break; + } + + emv_trace(instance, "Get processing options answer:"); + + const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); + + if(!emv_decode_response( + 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; + + 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( + instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + error = emv_process_error(iso14443_4a_error); + break; + } + + emv_trace(instance, "SFI record:"); + + const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); + + if(!emv_decode_response( + buff, + bit_buffer_get_size_bytes(instance->rx_buffer), + &instance->data->emv_application)) { + error = EmvErrorProtocol; + FURI_LOG_E(TAG, "Failed to read SFI record %d", record_num); + } + } while(false); + + return error; +} + +EmvError emv_poller_read_files(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); + } + } + + return error; +} + +EmvError emv_poller_read(EmvPoller* instance) { + furi_assert(instance); + EmvError error = EmvErrorNone; + + memset(&instance->data->emv_application, 0, sizeof(EmvApplication)); + do { + error |= emv_poller_select_ppse(instance); + if(error != EmvErrorNone) break; + + error |= emv_poller_select_application(instance); + if(error != EmvErrorNone) break; + + if(emv_poller_get_processing_options(instance) != EmvErrorNone) + error = emv_poller_read_files(instance); + + } while(false); + + return error; +} \ No newline at end of file diff --git a/lib/nfc/protocols/emv/emv_poller_i.h b/lib/nfc/protocols/emv/emv_poller_i.h new file mode 100644 index 000000000..4809a8668 --- /dev/null +++ b/lib/nfc/protocols/emv/emv_poller_i.h @@ -0,0 +1,52 @@ +#pragma once + +#include "emv_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + EmvPollerStateIdle, + EmvPollerStateSelectPPSE, + EmvPollerStateSelectApplication, + EmvPollerStateGetProcessingOptions, + EmvPollerStateReadFiles, + 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; + BitBuffer* input_buffer; + BitBuffer* result_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 diff --git a/lib/nfc/protocols/nfc_poller_defs.c b/lib/nfc/protocols/nfc_poller_defs.c index 7553c74de..55a59cfd6 100644 --- a/lib/nfc/protocols/nfc_poller_defs.c +++ b/lib/nfc/protocols/nfc_poller_defs.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -22,6 +23,7 @@ const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolMfUltralight] = &mf_ultralight_poller, [NfcProtocolMfClassic] = &mf_classic_poller, [NfcProtocolMfDesfire] = &mf_desfire_poller, + [NfcProtocolEmv] = &emv_poller, [NfcProtocolSlix] = &nfc_poller_slix, /* Add new pollers here */ [NfcProtocolSt25tb] = &nfc_poller_st25tb, diff --git a/lib/nfc/protocols/nfc_protocol.c b/lib/nfc/protocols/nfc_protocol.c index 2ea9b3982..54ee5ba0d 100644 --- a/lib/nfc/protocols/nfc_protocol.c +++ b/lib/nfc/protocols/nfc_protocol.c @@ -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. */ @@ -134,6 +137,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, + }, [NfcProtocolSlix] = { .parent_protocol = NfcProtocolIso15693_3, diff --git a/lib/nfc/protocols/nfc_protocol.h b/lib/nfc/protocols/nfc_protocol.h index ee6345333..d597de152 100644 --- a/lib/nfc/protocols/nfc_protocol.h +++ b/lib/nfc/protocols/nfc_protocol.h @@ -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 * @@ -185,6 +185,7 @@ typedef enum { NfcProtocolMfUltralight, NfcProtocolMfClassic, NfcProtocolMfDesfire, + NfcProtocolEmv, NfcProtocolSlix, NfcProtocolSt25tb, /* Add new protocols here */ From e9454b629b7853b0ca374871c2fa3015c5774f4e Mon Sep 17 00:00:00 2001 From: Methodius Date: Thu, 11 Jan 2024 18:11:54 +0900 Subject: [PATCH 02/28] NFC fap: EMV protocol added --- .../nfc/helpers/protocol_support/emv/emv.c | 115 ++++++++++++++++++ .../nfc/helpers/protocol_support/emv/emv.h | 5 + .../helpers/protocol_support/emv/emv_render.c | 35 ++++++ .../helpers/protocol_support/emv/emv_render.h | 16 +++ .../nfc_protocol_support_defs.c | 2 + .../main/nfc/scenes/nfc_scene_config.h | 2 + .../main/nfc/scenes/nfc_scene_emv_more_info.c | 75 ++++++++++++ lib/nfc/SConscript | 2 + lib/nfc/protocols/emv/emv.h | 4 +- lib/nfc/protocols/emv/emv_poller_i.c | 8 +- targets/f7/api_symbols.csv | 24 +++- 11 files changed, 281 insertions(+), 7 deletions(-) create mode 100644 applications/main/nfc/helpers/protocol_support/emv/emv.c create mode 100644 applications/main/nfc/helpers/protocol_support/emv/emv.h create mode 100644 applications/main/nfc/helpers/protocol_support/emv/emv_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/emv/emv_render.h create mode 100644 applications/main/nfc/scenes/nfc_scene_emv_more_info.c diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv.c b/applications/main/nfc/helpers/protocol_support/emv/emv.c new file mode 100644 index 000000000..035f8d220 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/emv/emv.c @@ -0,0 +1,115 @@ +#include "emv.h" +#include "emv_render.h" + +#include + +#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 = NfcProtocolFeatureNone, + + .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, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv.h b/applications/main/nfc/helpers/protocol_support/emv/emv.h new file mode 100644 index 000000000..c68564f36 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/emv/emv.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_emv; diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c new file mode 100644 index 000000000..46cdc974f --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c @@ -0,0 +1,35 @@ +#include "emv_render.h" + +#include "../iso14443_4a/iso14443_4a_render.h" + +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); + + if(format_type != NfcProtocolFormatTypeFull) return; + + 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) { + 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) { + for(uint8_t i = 0; i < len; i++) furi_string_cat_printf(str, "%u", data[i]); + furi_string_cat_printf(str, "\n"); +} + +void nfc_render_emv_name(const char* data, FuriString* str) { + UNUSED(data); + furi_string_cat_printf(str, "\n"); +} + +void nfc_render_emv_application(const EmvApplication* data, FuriString* str) { + UNUSED(data); + furi_string_cat_printf(str, "\n"); +} \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.h b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h new file mode 100644 index 000000000..16fc2e172 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" +#include + +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); \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c index 215ffc455..9e61585c9 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c @@ -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" @@ -39,6 +40,7 @@ const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = { [NfcProtocolMfUltralight] = &nfc_protocol_support_mf_ultralight, [NfcProtocolMfClassic] = &nfc_protocol_support_mf_classic, [NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire, + [NfcProtocolEmv] = &nfc_protocol_support_emv, [NfcProtocolSlix] = &nfc_protocol_support_slix, [NfcProtocolSt25tb] = &nfc_protocol_support_st25tb, /* Add new protocol support implementations here */ diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index a9887996d..c0e28f480 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -36,6 +36,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) diff --git a/applications/main/nfc/scenes/nfc_scene_emv_more_info.c b/applications/main/nfc/scenes/nfc_scene_emv_more_info.c new file mode 100644 index 000000000..5825190d1 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_emv_more_info.c @@ -0,0 +1,75 @@ +#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 { + SubmenuIndexCardInfo, + 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, + "Card info", + SubmenuIndexCardInfo, + 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) { + TextBox* text_box = nfc->text_box; + furi_string_reset(nfc->text_box_store); + + if(event.event == SubmenuIndexCardInfo) { + nfc_render_emv_data(data, nfc->text_box_store); + text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + nfc->scene_manager, + NfcSceneEmvMoreInfo, + EmvMoreInfoStateItem + SubmenuIndexCardInfo); + 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 + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + submenu_reset(nfc->submenu); +} diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 41332362c..3ad62f322 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -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"), diff --git a/lib/nfc/protocols/emv/emv.h b/lib/nfc/protocols/emv/emv.h index feb390c7d..253101df7 100644 --- a/lib/nfc/protocols/emv/emv.h +++ b/lib/nfc/protocols/emv/emv.h @@ -41,8 +41,8 @@ typedef struct { bool app_started; char name[32]; bool name_found; - uint8_t card_number[10]; - uint8_t card_number_len; + uint8_t pan[10]; + uint8_t pan_len; uint8_t exp_month; uint8_t exp_year; uint16_t country_code; diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c index 22c862661..4bb85eab7 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.c +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -186,8 +186,8 @@ static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplicatio // 0xD0 delimits PAN from expiry (YYMM) for(int x = 1; x < tlen; x++) { if(buff[i + x + 1] > 0xD0) { - memcpy(app->card_number, &buff[i], x + 1); - app->card_number_len = x + 1; + 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; @@ -213,8 +213,8 @@ static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplicatio break; } case EMV_TAG_PAN: - memcpy(app->card_number, &buff[i], tlen); - app->card_number_len = tlen; + memcpy(app->pan, &buff[i], tlen); + app->pan_len = tlen; success = true; break; case EMV_TAG_EXP_DATE: diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 0ce105b05..1857a77ca 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,50.1,, +Version,v,50.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -120,6 +120,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,, @@ -877,6 +879,25 @@ 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_application,const EmvApplication*,const 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_processing_options,EmvError,EmvPoller* +Function,?,emv_poller_read,EmvError,EmvPoller* +Function,?,emv_poller_read_files,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 @@ -3619,6 +3640,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, From e8b468b492f81650a55eabdbcd72e7e4a1f6b6d2 Mon Sep 17 00:00:00 2001 From: Methodius Date: Fri, 12 Jan 2024 17:08:34 +0900 Subject: [PATCH 03/28] EMV Poller fix --- lib/nfc/protocols/emv/emv.c | 1 + lib/nfc/protocols/emv/emv.h | 2 +- lib/nfc/protocols/emv/emv_poller.h | 6 +++- lib/nfc/protocols/nfc_device_defs.c | 2 ++ targets/f7/api_symbols.csv | 45 ++++++++++++++--------------- 5 files changed, 31 insertions(+), 25 deletions(-) diff --git a/lib/nfc/protocols/emv/emv.c b/lib/nfc/protocols/emv/emv.c index 2de6fb132..2a6c83101 100644 --- a/lib/nfc/protocols/emv/emv.c +++ b/lib/nfc/protocols/emv/emv.c @@ -4,6 +4,7 @@ #include "protocols/emv/emv.h" #include #include +#include #define EMV_PROTOCOL_NAME "EMV" diff --git a/lib/nfc/protocols/emv/emv.h b/lib/nfc/protocols/emv/emv.h index 253101df7..45318292b 100644 --- a/lib/nfc/protocols/emv/emv.h +++ b/lib/nfc/protocols/emv/emv.h @@ -93,7 +93,7 @@ Iso14443_4aData* emv_get_base_data(const EmvData* data); // Getters and tests -const EmvApplication* emv_get_application(const EmvData* data); +//const EmvApplication* emv_get_application(const EmvData* data); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/emv/emv_poller.h b/lib/nfc/protocols/emv/emv_poller.h index f86186fe2..8c053ede4 100644 --- a/lib/nfc/protocols/emv/emv_poller.h +++ b/lib/nfc/protocols/emv/emv_poller.h @@ -48,4 +48,8 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re EmvError emv_poller_read_files(EmvPoller* instance); -EmvError emv_poller_read(EmvPoller* instance); \ No newline at end of file +EmvError emv_poller_read(EmvPoller* instance); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/nfc_device_defs.c b/lib/nfc/protocols/nfc_device_defs.c index 870bcafd9..0dbe8a155 100644 --- a/lib/nfc/protocols/nfc_device_defs.c +++ b/lib/nfc/protocols/nfc_device_defs.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,7 @@ const NfcDeviceBase* nfc_devices[NfcProtocolNum] = { [NfcProtocolMfUltralight] = &nfc_device_mf_ultralight, [NfcProtocolMfClassic] = &nfc_device_mf_classic, [NfcProtocolMfDesfire] = &nfc_device_mf_desfire, + [NfcProtocolEmv] = &nfc_device_emv, [NfcProtocolSlix] = &nfc_device_slix, [NfcProtocolSt25tb] = &nfc_device_st25tb, /* Add new protocols here */ diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1857a77ca..ad6a4c1bd 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,v,50.2,, +Version,+,50.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -120,8 +120,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/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,, @@ -879,25 +879,24 @@ 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_application,const EmvApplication*,const 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_processing_options,EmvError,EmvPoller* -Function,?,emv_poller_read,EmvError,EmvPoller* -Function,?,emv_poller_read_files,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,+,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_processing_options,EmvError,EmvPoller* +Function,+,emv_poller_read,EmvError,EmvPoller* +Function,+,emv_poller_read_files,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 @@ -3640,7 +3639,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_emv,const NfcDeviceBase, Variable,-,nfc_device_mf_classic,const NfcDeviceBase, Variable,-,nfc_device_mf_desfire,const NfcDeviceBase, Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase, From d337222cbe0ca9fc410fe1969d44c4160621b4f1 Mon Sep 17 00:00:00 2001 From: Methodius Date: Fri, 12 Jan 2024 22:14:21 +0900 Subject: [PATCH 04/28] minor fixes --- lib/nfc/protocols/emv/emv_poller.c | 6 +++++- lib/nfc/protocols/emv/emv_poller_i.c | 14 +++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/nfc/protocols/emv/emv_poller.c b/lib/nfc/protocols/emv/emv_poller.c index 46f02b363..61ef1c30e 100644 --- a/lib/nfc/protocols/emv/emv_poller.c +++ b/lib/nfc/protocols/emv/emv_poller.c @@ -27,6 +27,8 @@ static EmvPoller* emv_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { instance->input_buffer = bit_buffer_alloc(EMV_BUF_SIZE); instance->result_buffer = bit_buffer_alloc(EMV_RESULT_BUF_SIZE); + instance->state = EmvPollerStateIdle; + instance->emv_event.data = &instance->emv_event_data; instance->general_event.protocol = NfcProtocolEmv; @@ -63,6 +65,7 @@ static NfcCommand emv_poller_handler_idle(EmvPoller* instance) { 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; @@ -76,7 +79,8 @@ static NfcCommand emv_poller_handler_select_ppse(EmvPoller* instance) { } static NfcCommand emv_poller_handler_select_application(EmvPoller* instance) { - instance->error = emv_poller_select_ppse(instance); + instance->error = emv_poller_select_application(instance); + if(instance->error == EmvErrorNone) { FURI_LOG_D(TAG, "Select application success"); instance->state = EmvPollerStateGetProcessingOptions; diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c index 4bb85eab7..da8503744 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.c +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -314,14 +314,14 @@ EmvError emv_poller_select_application(EmvPoller* instance) { Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( 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; } - emv_trace(instance, "Start application answer:"); - const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); if(!emv_decode_response( @@ -367,14 +367,14 @@ EmvError emv_poller_get_processing_options(EmvPoller* instance) { Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( 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"); + FURI_LOG_E(TAG, "Failed to get processing options, error %u", iso14443_4a_error); error = emv_process_error(iso14443_4a_error); break; } - emv_trace(instance, "Get processing options answer:"); - const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); if(!emv_decode_response( @@ -410,13 +410,13 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); + emv_trace(instance, "SFI record:"); + if(iso14443_4a_error != Iso14443_4aErrorNone) { error = emv_process_error(iso14443_4a_error); break; } - emv_trace(instance, "SFI record:"); - const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); if(!emv_decode_response( From 08a5adf18ef95389f838879f5d07fcb73d2984b6 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov <1042932+wosk@users.noreply.github.com> Date: Mon, 15 Jan 2024 03:53:03 +0000 Subject: [PATCH 05/28] 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. --- .../nfc/helpers/protocol_support/emv/emv.c | 8 +-- .../helpers/protocol_support/emv/emv_render.c | 53 +++++++++++++---- .../helpers/protocol_support/emv/emv_render.h | 10 +++- lib/nfc/protocols/emv/emv.h | 2 +- lib/nfc/protocols/emv/emv_poller.c | 13 ++-- lib/nfc/protocols/emv/emv_poller_i.c | 59 ++++++++++++------- 6 files changed, 99 insertions(+), 46 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv.c b/applications/main/nfc/helpers/protocol_support/emv/emv.c index 035f8d220..0b60bea6e 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv.c @@ -14,8 +14,8 @@ static void nfc_scene_info_on_enter_emv(NfcApp* instance) { 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)); + // 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( @@ -54,8 +54,8 @@ static void nfc_scene_read_success_on_enter_emv(NfcApp* instance) { 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)); + // 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( diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c index 46cdc974f..ead426a15 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c @@ -3,15 +3,11 @@ #include "../iso14443_4a/iso14443_4a_render.h" 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_pan(data->emv_application.pan, data->emv_application.pan_len, str); + nfc_render_emv_expired(&data->emv_application, str); - if(format_type != NfcProtocolFormatTypeFull) return; - - furi_string_cat(str, "\n\e#ISO14443-4 data"); - nfc_render_iso14443_4a_extra(emv_get_base_data(data), str); + if(format_type == NfcProtocolFormatTypeFull) nfc_render_emv_extra(data, 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) { - 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"); } +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) { - UNUSED(data); + if(strlen(data) == 0) return; + furi_string_cat_printf(str, "\e#"); + furi_string_cat(str, data); furi_string_cat_printf(str, "\n"); } -void nfc_render_emv_application(const EmvApplication* data, FuriString* str) { - UNUSED(data); +void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) { + 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"); -} \ No newline at end of file +} + +void nfc_render_emv_extra(const EmvData* data, FuriString* str) { + nfc_render_emv_application(&data->emv_application, str); +} diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.h b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h index 16fc2e172..8fb31eaea 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv_render.h +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h @@ -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_application(const EmvApplication* data, FuriString* str); \ No newline at end of file +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); diff --git a/lib/nfc/protocols/emv/emv.h b/lib/nfc/protocols/emv/emv.h index 45318292b..913bdb0cb 100644 --- a/lib/nfc/protocols/emv/emv.h +++ b/lib/nfc/protocols/emv/emv.h @@ -41,7 +41,7 @@ typedef struct { bool app_started; char name[32]; bool name_found; - uint8_t pan[10]; + uint8_t pan[10]; // card_number uint8_t pan_len; uint8_t exp_month; uint8_t exp_year; diff --git a/lib/nfc/protocols/emv/emv_poller.c b/lib/nfc/protocols/emv/emv_poller.c index 61ef1c30e..41ae8afba 100644 --- a/lib/nfc/protocols/emv/emv_poller.c +++ b/lib/nfc/protocols/emv/emv_poller.c @@ -71,7 +71,6 @@ static NfcCommand emv_poller_handler_select_ppse(EmvPoller* instance) { instance->state = EmvPollerStateSelectApplication; } else { FURI_LOG_E(TAG, "Failed to select PPSE"); - iso14443_4a_poller_halt(instance->iso14443_4a_poller); instance->state = EmvPollerStateReadFailed; } @@ -86,7 +85,6 @@ static NfcCommand emv_poller_handler_select_application(EmvPoller* instance) { instance->state = EmvPollerStateGetProcessingOptions; } else { FURI_LOG_E(TAG, "Failed to select application"); - iso14443_4a_poller_halt(instance->iso14443_4a_poller); instance->state = EmvPollerStateReadFailed; } @@ -98,10 +96,14 @@ static NfcCommand emv_poller_handler_get_processing_options(EmvPoller* instance) if(instance->error == EmvErrorNone) { 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 { FURI_LOG_E(TAG, "Failed to get processing options"); - iso14443_4a_poller_halt(instance->iso14443_4a_poller); instance->state = EmvPollerStateReadFiles; } @@ -116,7 +118,6 @@ static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) { instance->state = EmvPollerStateReadSuccess; } else { FURI_LOG_E(TAG, "Failed to read files"); - iso14443_4a_poller_halt(instance->iso14443_4a_poller); 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) { - FURI_LOG_D(TAG, "Read success."); + 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); diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c index da8503744..739747296 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.c +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -183,6 +183,7 @@ static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplicatio break; } case EMV_TAG_TRACK_2_EQUIV: { + FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2_EQUIV %x", tag); // 0xD0 delimits PAN from expiry (YYMM) for(int x = 1; x < tlen; x++) { 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 - 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'; + // // 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_EQUIV %x : %s", tag, track_2_equiv); success = true; - FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2_EQUIV %x : %s", tag, track_2_equiv); 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; } } @@ -413,6 +418,7 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re emv_trace(instance, "SFI record:"); 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); break; } @@ -423,8 +429,9 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re buff, bit_buffer_get_size_bytes(instance->rx_buffer), &instance->data->emv_application)) { - error = EmvErrorProtocol; - FURI_LOG_E(TAG, "Failed to read SFI record %d", record_num); + // It's ok while bruteforcing + //error = EmvErrorProtocol; + FURI_LOG_T(TAG, "Failed to parse SFI %d record %d", sfi, record_num); } } while(false); @@ -449,8 +456,12 @@ EmvError emv_poller_read_files(EmvPoller* instance) { 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); + 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; @@ -462,15 +473,19 @@ EmvError emv_poller_read(EmvPoller* instance) { memset(&instance->data->emv_application, 0, sizeof(EmvApplication)); do { - error |= emv_poller_select_ppse(instance); + error = emv_poller_select_ppse(instance); if(error != EmvErrorNone) break; - error |= emv_poller_select_application(instance); + error = emv_poller_select_application(instance); 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); - + if(error != EmvErrorNone) break; + } } while(false); return error; From 4b7b0ad6b9ed84eb3890720c77be96ee58647b7f Mon Sep 17 00:00:00 2001 From: Methodius Date: Tue, 16 Jan 2024 02:43:17 +0900 Subject: [PATCH 06/28] EMV parser added --- applications/main/nfc/application.fam | 9 + .../main/nfc/plugins/supported_cards/emv.c | 923 ++++++++++++++++++ lib/nfc/protocols/emv/emv.h | 1 - lib/nfc/protocols/emv/emv_poller.h | 2 - lib/nfc/protocols/emv/emv_poller_i.c | 28 - targets/f7/api_symbols.csv | 3 +- 6 files changed, 933 insertions(+), 33 deletions(-) create mode 100644 applications/main/nfc/plugins/supported_cards/emv.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index d74447844..0ed7a6241 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -182,6 +182,15 @@ App( sources=["plugins/supported_cards/ndef.c"], ) +App( + appid="emv_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="emv_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/emv.c"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c new file mode 100644 index 000000000..fabf721ae --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/emv.c @@ -0,0 +1,923 @@ +/* + * Parser for EMV cards. + * + * Copyright 2023 Leptoptilos + * + * 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 . + */ +#include "core/string.h" +#include "furi_hal_rtc.h" +#include "nfc_supported_card_plugin.h" + +#include "protocols/emv/emv.h" +#include "protocols/nfc_protocol.h" +#include + +#include +#include + +#define TAG "EMV" + +char* get_country_name(uint16_t country_code) { + switch(country_code) { + case 0x0004: + return "AFG"; + case 0x0008: + return "ALB"; + case 0x0010: + return "ATA"; + case 0x0012: + return "DZA"; + case 0x0016: + return "ASM"; + case 0x0020: + return "AND"; + case 0x0024: + return "AGO"; + case 0x0028: + return "ATG"; + case 0x0031: + return "AZE"; + case 0x0032: + return "ARG"; + case 0x0036: + return "AUS"; + case 0x0040: + return "AUT"; + case 0x0044: + return "BHS"; + case 0x0048: + return "BHR"; + case 0x0050: + return "BGD"; + case 0x0051: + return "ARM"; + case 0x0052: + return "BRB"; + case 0x0056: + return "BEL"; + case 0x0060: + return "BMU"; + case 0x0064: + return "BTN"; + case 0x0068: + return "BOL"; + case 0x0070: + return "BIH"; + case 0x0072: + return "BWA"; + case 0x0074: + return "BVT"; + case 0x0076: + return "BRA"; + case 0x0084: + return "BLZ"; + case 0x0086: + return "IOT"; + case 0x0090: + return "SLB"; + case 0x0092: + return "VGB"; + case 0x0096: + return "BRN"; + case 0x0100: + return "BGR"; + case 0x0104: + return "MMR"; + case 0x0108: + return "BDI"; + case 0x0112: + return "BLR"; + case 0x0116: + return "KHM"; + case 0x0120: + return "CMR"; + case 0x0124: + return "CAN"; + case 0x0132: + return "CPV"; + case 0x0136: + return "CYM"; + case 0x0140: + return "CAF"; + case 0x0144: + return "LKA"; + case 0x0148: + return "TCD"; + case 0x0152: + return "CHL"; + case 0x0156: + return "CHN"; + case 0x0158: + return "TWN"; + case 0x0162: + return "CXR"; + case 0x0166: + return "CCK"; + case 0x0170: + return "COL"; + case 0x0174: + return "COM"; + case 0x0175: + return "MYT"; + case 0x0178: + return "COG"; + case 0x0180: + return "COD"; + case 0x0184: + return "COK"; + case 0x0188: + return "CRI"; + case 0x0191: + return "HRV"; + case 0x0192: + return "CUB"; + case 0x0196: + return "CYP"; + case 0x0203: + return "CZE"; + case 0x0204: + return "BEN"; + case 0x0208: + return "DNK"; + case 0x0212: + return "DMA"; + case 0x0214: + return "DOM"; + case 0x0218: + return "ECU"; + case 0x0222: + return "SLV"; + case 0x0226: + return "GNQ"; + case 0x0231: + return "ETH"; + case 0x0232: + return "ERI"; + case 0x0233: + return "EST"; + case 0x0234: + return "FRO"; + case 0x0238: + return "FLK"; + case 0x0239: + return "SGS"; + case 0x0242: + return "FJI"; + case 0x0246: + return "FIN"; + case 0x0248: + return "ALA"; + case 0x0250: + return "FRA"; + case 0x0254: + return "GUF"; + case 0x0258: + return "PYF"; + case 0x0260: + return "ATF"; + case 0x0262: + return "DJI"; + case 0x0266: + return "GAB"; + case 0x0268: + return "GEO"; + case 0x0270: + return "GMB"; + case 0x0275: + return "PSE"; + case 0x0276: + return "DEU"; + case 0x0288: + return "GHA"; + case 0x0292: + return "GIB"; + case 0x0296: + return "KIR"; + case 0x0300: + return "GRC"; + case 0x0304: + return "GRL"; + case 0x0308: + return "GRD"; + case 0x0312: + return "GLP"; + case 0x0316: + return "GUM"; + case 0x0320: + return "GTM"; + case 0x0324: + return "GIN"; + case 0x0328: + return "GUY"; + case 0x0332: + return "HTI"; + case 0x0334: + return "HMD"; + case 0x0336: + return "VAT"; + case 0x0340: + return "HND"; + case 0x0344: + return "HKG"; + case 0x0348: + return "HUN"; + case 0x0352: + return "ISL"; + case 0x0356: + return "IND"; + case 0x0360: + return "IDN"; + case 0x0364: + return "IRN"; + case 0x0368: + return "IRQ"; + case 0x0372: + return "IRL"; + case 0x0376: + return "ISR"; + case 0x0380: + return "ITA"; + case 0x0384: + return "CIV"; + case 0x0388: + return "JAM"; + case 0x0392: + return "JPN"; + case 0x0398: + return "KAZ"; + case 0x0400: + return "JOR"; + case 0x0404: + return "KEN"; + case 0x0408: + return "PRK"; + case 0x0410: + return "KOR"; + case 0x0414: + return "KWT"; + case 0x0417: + return "KGZ"; + case 0x0418: + return "LAO"; + case 0x0422: + return "LBN"; + case 0x0426: + return "LSO"; + case 0x0428: + return "LVA"; + case 0x0430: + return "LBR"; + case 0x0434: + return "LBY"; + case 0x0438: + return "LIE"; + case 0x0440: + return "LTU"; + case 0x0442: + return "LUX"; + case 0x0446: + return "MAC"; + case 0x0450: + return "MDG"; + case 0x0454: + return "MWI"; + case 0x0458: + return "MYS"; + case 0x0462: + return "MDV"; + case 0x0466: + return "MLI"; + case 0x0470: + return "MLT"; + case 0x0474: + return "MTQ"; + case 0x0478: + return "MRT"; + case 0x0480: + return "MUS"; + case 0x0484: + return "MEX"; + case 0x0492: + return "MCO"; + case 0x0496: + return "MNG"; + case 0x0498: + return "MDA"; + case 0x0499: + return "MNE"; + case 0x0500: + return "MSR"; + case 0x0504: + return "MAR"; + case 0x0508: + return "MOZ"; + case 0x0512: + return "OMN"; + case 0x0516: + return "NAM"; + case 0x0520: + return "NRU"; + case 0x0524: + return "NPL"; + case 0x0528: + return "NLD"; + case 0x0531: + return "CUW"; + case 0x0533: + return "ABW"; + case 0x0534: + return "SXM"; + case 0x0535: + return "BES"; + case 0x0540: + return "NCL"; + case 0x0548: + return "VUT"; + case 0x0554: + return "NZL"; + case 0x0558: + return "NIC"; + case 0x0562: + return "NER"; + case 0x0566: + return "NGA"; + case 0x0570: + return "NIU"; + case 0x0574: + return "NFK"; + case 0x0578: + return "NOR"; + case 0x0580: + return "MNP"; + case 0x0581: + return "UMI"; + case 0x0583: + return "FSM"; + case 0x0584: + return "MHL"; + case 0x0585: + return "PLW"; + case 0x0586: + return "PAK"; + case 0x0591: + return "PAN"; + case 0x0598: + return "PNG"; + case 0x0600: + return "PRY"; + case 0x0604: + return "PER"; + case 0x0608: + return "PHL"; + case 0x0612: + return "PCN"; + case 0x0616: + return "POL"; + case 0x0620: + return "PRT"; + case 0x0624: + return "GNB"; + case 0x0626: + return "TLS"; + case 0x0630: + return "PRI"; + case 0x0634: + return "QAT"; + case 0x0638: + return "REU"; + case 0x0642: + return "ROU"; + case 0x0643: + return "RUS"; + case 0x0646: + return "RWA"; + case 0x0652: + return "BLM"; + case 0x0654: + return "SHN"; + case 0x0659: + return "KNA"; + case 0x0660: + return "AIA"; + case 0x0662: + return "LCA"; + case 0x0663: + return "MAF"; + case 0x0666: + return "SPM"; + case 0x0670: + return "VCT"; + case 0x0674: + return "SMR"; + case 0x0678: + return "STP"; + case 0x0682: + return "SAU"; + case 0x0686: + return "SEN"; + case 0x0688: + return "SRB"; + case 0x0690: + return "SYC"; + case 0x0694: + return "SLE"; + case 0x0702: + return "SGP"; + case 0x0703: + return "SVK"; + case 0x0704: + return "VNM"; + case 0x0705: + return "SVN"; + case 0x0706: + return "SOM"; + case 0x0710: + return "ZAF"; + case 0x0716: + return "ZWE"; + case 0x0724: + return "ESP"; + case 0x0728: + return "SSD"; + case 0x0729: + return "SDN"; + case 0x0732: + return "ESH"; + case 0x0740: + return "SUR"; + case 0x0744: + return "SJM"; + case 0x0748: + return "SWZ"; + case 0x0752: + return "SWE"; + case 0x0756: + return "CHE"; + case 0x0760: + return "SYR"; + case 0x0762: + return "TJK"; + case 0x0764: + return "THA"; + case 0x0768: + return "TGO"; + case 0x0772: + return "TKL"; + case 0x0776: + return "TON"; + case 0x0780: + return "TTO"; + case 0x0784: + return "ARE"; + case 0x0788: + return "TUN"; + case 0x0792: + return "TUR"; + case 0x0795: + return "TKM"; + case 0x0796: + return "TCA"; + case 0x0798: + return "TUV"; + case 0x0800: + return "UGA"; + case 0x0804: + return "UKR"; + case 0x0807: + return "MKD"; + case 0x0818: + return "EGY"; + case 0x0826: + return "GBR"; + case 0x0831: + return "GGY"; + case 0x0832: + return "JEY"; + case 0x0833: + return "IMN"; + case 0x0834: + return "TZA"; + case 0x0840: + return "USA"; + case 0x0850: + return "VIR"; + case 0x0854: + return "BFA"; + case 0x0858: + return "URY"; + case 0x0860: + return "UZB"; + case 0x0862: + return "VEN"; + case 0x0876: + return "WLF"; + case 0x0882: + return "WSM"; + case 0x0887: + return "YEM"; + case 0x0894: + return "ZMB"; + default: + return "UNKNOWN"; + } +} + +char* get_currency_name(uint16_t currency_code) { + switch(currency_code) { + case 0x0997: + return "USN"; + case 0x0994: + return "XSU"; + case 0x0990: + return "CLF"; + case 0x0986: + return "BRL"; + case 0x0985: + return "PLN"; + case 0x0984: + return "BOV"; + case 0x0981: + return "GEL"; + case 0x0980: + return "UAH"; + case 0x0979: + return "MXV"; + case 0x0978: + return "EUR"; + case 0x0977: + return "BAM"; + case 0x0976: + return "CDF"; + case 0x0975: + return "BGN"; + case 0x0973: + return "AOA"; + case 0x0972: + return "TJS"; + case 0x0971: + return "AFN"; + case 0x0970: + return "COU"; + case 0x0969: + return "MGA"; + case 0x0968: + return "SRD"; + case 0x0967: + return "ZMW"; + case 0x0965: + return "XUA"; + case 0x0960: + return "XDR"; + case 0x0953: + return "XPF"; + case 0x0952: + return "XOF"; + case 0x0951: + return "XCD"; + case 0x0950: + return "XAF"; + case 0x0949: + return "TRY"; + case 0x0948: + return "CHW"; + case 0x0947: + return "CHE"; + case 0x0946: + return "RON"; + case 0x0944: + return "AZN"; + case 0x0943: + return "MZN"; + case 0x0941: + return "RSD"; + case 0x0940: + return "UYI"; + case 0x0938: + return "SDG"; + case 0x0937: + return "VEF"; + case 0x0936: + return "GHS"; + case 0x0934: + return "TMT"; + case 0x0933: + return "BYN"; + case 0x0932: + return "ZWL"; + case 0x0931: + return "CUC"; + case 0x0930: + return "STN"; + case 0x0929: + return "MRU"; + case 0x0901: + return "TWD"; + case 0x0886: + return "YER"; + case 0x0882: + return "WST"; + case 0x0860: + return "UZS"; + case 0x0858: + return "UYU"; + case 0x0840: + return "USD"; + case 0x0834: + return "TZS"; + case 0x0826: + return "GBP"; + case 0x0818: + return "EGP"; + case 0x0807: + return "MKD"; + case 0x0800: + return "UGX"; + case 0x0788: + return "TND"; + case 0x0784: + return "AED"; + case 0x0780: + return "TTD"; + case 0x0776: + return "TOP"; + case 0x0764: + return "THB"; + case 0x0760: + return "SYP"; + case 0x0756: + return "CHF"; + case 0x0752: + return "SEK"; + case 0x0748: + return "SZL"; + case 0x0728: + return "SSP"; + case 0x0710: + return "ZAR"; + case 0x0706: + return "SOS"; + case 0x0704: + return "VND"; + case 0x0702: + return "SGD"; + case 0x0694: + return "SLL"; + case 0x0690: + return "SCR"; + case 0x0682: + return "SAR"; + case 0x0654: + return "SHP"; + case 0x0646: + return "RWF"; + case 0x0643: + return "RUB"; + case 0x0634: + return "QAR"; + case 0x0608: + return "PHP"; + case 0x0604: + return "PEN"; + case 0x0600: + return "PYG"; + case 0x0598: + return "PGK"; + case 0x0590: + return "PAB"; + case 0x0586: + return "PKR"; + case 0x0578: + return "NOK"; + case 0x0566: + return "NGN"; + case 0x0558: + return "NIO"; + case 0x0554: + return "NZD"; + case 0x0548: + return "VUV"; + case 0x0533: + return "AWG"; + case 0x0532: + return "ANG"; + case 0x0524: + return "NPR"; + case 0x0516: + return "NAD"; + case 0x0512: + return "OMR"; + case 0x0504: + return "MAD"; + case 0x0498: + return "MDL"; + case 0x0496: + return "MNT"; + case 0x0484: + return "MXN"; + case 0x0480: + return "MUR"; + case 0x0462: + return "MVR"; + case 0x0458: + return "MYR"; + case 0x0454: + return "MWK"; + case 0x0446: + return "MOP"; + case 0x0434: + return "LYD"; + case 0x0430: + return "LRD"; + case 0x0426: + return "LSL"; + case 0x0422: + return "LBP"; + case 0x0418: + return "LAK"; + case 0x0417: + return "KGS"; + case 0x0414: + return "KWD"; + case 0x0410: + return "KRW"; + case 0x0408: + return "KPW"; + case 0x0404: + return "KES"; + case 0x0400: + return "JOD"; + case 0x0398: + return "KZT"; + case 0x0392: + return "JPY"; + case 0x0388: + return "JMD"; + case 0x0376: + return "ILS"; + case 0x0368: + return "IQD"; + case 0x0364: + return "IRR"; + case 0x0360: + return "IDR"; + case 0x0356: + return "INR"; + case 0x0352: + return "ISK"; + case 0x0348: + return "HUF"; + case 0x0344: + return "HKD"; + case 0x0340: + return "HNL"; + case 0x0332: + return "HTG"; + case 0x0328: + return "GYD"; + case 0x0324: + return "GNF"; + case 0x0320: + return "GTQ"; + case 0x0292: + return "GIP"; + case 0x0270: + return "GMD"; + case 0x0262: + return "DJF"; + case 0x0242: + return "FJD"; + case 0x0238: + return "FKP"; + case 0x0232: + return "ERN"; + case 0x0230: + return "ETB"; + case 0x0222: + return "SVC"; + case 0x0214: + return "DOP"; + case 0x0208: + return "DKK"; + case 0x0203: + return "CZK"; + case 0x0192: + return "CUP"; + case 0x0191: + return "HRK"; + case 0x0188: + return "CRC"; + case 0x0174: + return "KMF"; + case 0x0170: + return "COP"; + case 0x0156: + return "CNY"; + case 0x0152: + return "CLP"; + case 0x0144: + return "LKR"; + case 0x0136: + return "KYD"; + case 0x0132: + return "CVE"; + case 0x0124: + return "CAD"; + case 0x0116: + return "KHR"; + case 0x0108: + return "BIF"; + case 0x0104: + return "MMK"; + case 0x0096: + return "BND"; + case 0x0090: + return "SBD"; + case 0x0084: + return "BZD"; + case 0x0072: + return "BWP"; + case 0x0068: + return "BOB"; + case 0x0064: + return "BTN"; + case 0x0060: + return "BMD"; + case 0x0052: + return "BBD"; + case 0x0051: + return "AMD"; + case 0x0050: + return "BDT"; + case 0x0048: + return "BHD"; + case 0x0044: + return "BSD"; + case 0x0036: + return "AUD"; + case 0x0032: + return "ARS"; + case 0x0012: + return "DZD"; + case 0x0008: + return "ALL"; + default: + return "UNKNOWN"; + } +} + +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 { + furi_string_cat_printf(parsed_data, "\e#AID:\n"); + for(uint8_t i = 0; i < app.aid_len; i++) + furi_string_cat_printf(parsed_data, "%02X ", app.aid[i]); + + furi_string_cat_printf(parsed_data, "\nCountry: %s", get_country_name(app.country_code)); + + furi_string_cat_printf( + parsed_data, "\nCurrency: %s", get_currency_name(app.currency_code)); + + if(app.name_found) furi_string_cat_printf(parsed_data, "\nName: %s", app.name); + + 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; +} \ No newline at end of file diff --git a/lib/nfc/protocols/emv/emv.h b/lib/nfc/protocols/emv/emv.h index 913bdb0cb..a10450b7e 100644 --- a/lib/nfc/protocols/emv/emv.h +++ b/lib/nfc/protocols/emv/emv.h @@ -38,7 +38,6 @@ typedef struct { uint8_t priority; uint8_t aid[16]; uint8_t aid_len; - bool app_started; char name[32]; bool name_found; uint8_t pan[10]; // card_number diff --git a/lib/nfc/protocols/emv/emv_poller.h b/lib/nfc/protocols/emv/emv_poller.h index 8c053ede4..36f27578a 100644 --- a/lib/nfc/protocols/emv/emv_poller.h +++ b/lib/nfc/protocols/emv/emv_poller.h @@ -48,8 +48,6 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re EmvError emv_poller_read_files(EmvPoller* instance); -EmvError emv_poller_read(EmvPoller* instance); - #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c index 739747296..9494ca199 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.c +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -289,9 +289,6 @@ EmvError emv_poller_select_ppse(EmvPoller* instance) { EmvError emv_poller_select_application(EmvPoller* instance) { EmvError error = EmvErrorNone; - // DELETE IT??????????????????????????????????????????????????????????????????????????????????????? - instance->data->emv_application.app_started = false; - const uint8_t emv_select_header[] = { 0x00, 0xA4, // SELECT application @@ -338,7 +335,6 @@ EmvError emv_poller_select_application(EmvPoller* instance) { break; } - instance->data->emv_application.app_started = true; } while(false); return error; @@ -464,29 +460,5 @@ EmvError emv_poller_read_files(EmvPoller* instance) { error = EmvErrorProtocol; } - return error; -} - -EmvError emv_poller_read(EmvPoller* instance) { - furi_assert(instance); - EmvError error = EmvErrorNone; - - memset(&instance->data->emv_application, 0, sizeof(EmvApplication)); - do { - error = emv_poller_select_ppse(instance); - if(error != EmvErrorNone) break; - - error = emv_poller_select_application(instance); - if(error != EmvErrorNone) break; - - 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); - if(error != EmvErrorNone) break; - } - } while(false); - return error; } \ No newline at end of file diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 5272c0945..95b0bd5cd 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,50.2,, +Version,+,51.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -888,7 +888,6 @@ 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_processing_options,EmvError,EmvPoller* -Function,+,emv_poller_read,EmvError,EmvPoller* Function,+,emv_poller_read_files,EmvError,EmvPoller* Function,+,emv_poller_read_sfi_record,EmvError,"EmvPoller*, uint8_t, uint8_t" Function,+,emv_poller_select_application,EmvError,EmvPoller* From e3930a30c0efdcb32d04c3bc356ee478e49872f3 Mon Sep 17 00:00:00 2001 From: Methodius Date: Sat, 20 Jan 2024 05:00:53 +0900 Subject: [PATCH 07/28] emv parser updated --- .../main/nfc/plugins/supported_cards/emv.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c index fabf721ae..cc5465a31 100644 --- a/applications/main/nfc/plugins/supported_cards/emv.c +++ b/applications/main/nfc/plugins/supported_cards/emv.c @@ -885,16 +885,25 @@ static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) { const EmvApplication app = data->emv_application; do { - furi_string_cat_printf(parsed_data, "\e#AID:\n"); - for(uint8_t i = 0; i < app.aid_len; i++) - furi_string_cat_printf(parsed_data, "%02X ", app.aid[i]); + if(app.name_found) + furi_string_cat_printf(parsed_data, "\e#%s", app.name); + else + furi_string_cat_printf(parsed_data, "\e#%s", "EMV"); + + furi_string_cat_printf(parsed_data, "\nPAN: "); + for(uint8_t i = 0; i < app.pan_len; i++) { + furi_string_cat_printf(parsed_data, "%02X", app.pan[i]); + if((i != 0) && (i % 2 != 0)) furi_string_cat_printf(parsed_data, " "); + } furi_string_cat_printf(parsed_data, "\nCountry: %s", get_country_name(app.country_code)); furi_string_cat_printf( parsed_data, "\nCurrency: %s", get_currency_name(app.currency_code)); - if(app.name_found) furi_string_cat_printf(parsed_data, "\nName: %s", app.name); + furi_string_cat_printf(parsed_data, "\nAID: "); + for(uint8_t i = 0; i < app.aid_len; i++) + furi_string_cat_printf(parsed_data, "%02X", app.aid[i]); parsed = true; } while(false); From b5964b97953a759a0564948ff4f3c6e56fded03f Mon Sep 17 00:00:00 2001 From: Methodius Date: Sat, 20 Jan 2024 05:18:16 +0900 Subject: [PATCH 08/28] Enum order fixes by Willy-JL Co-authored-by: Willy-JL <49810075+Willy-JL@users.noreply.github.com> --- .../protocol_support/nfc_protocol_support_defs.c | 2 +- lib/nfc/protocols/nfc_device_defs.c | 2 +- lib/nfc/protocols/nfc_poller_defs.c | 4 ++-- lib/nfc/protocols/nfc_protocol.c | 12 ++++++------ lib/nfc/protocols/nfc_protocol.h | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c index 9e61585c9..6b42a1660 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c @@ -40,8 +40,8 @@ const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = { [NfcProtocolMfUltralight] = &nfc_protocol_support_mf_ultralight, [NfcProtocolMfClassic] = &nfc_protocol_support_mf_classic, [NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire, - [NfcProtocolEmv] = &nfc_protocol_support_emv, [NfcProtocolSlix] = &nfc_protocol_support_slix, [NfcProtocolSt25tb] = &nfc_protocol_support_st25tb, + [NfcProtocolEmv] = &nfc_protocol_support_emv, /* Add new protocol support implementations here */ }; diff --git a/lib/nfc/protocols/nfc_device_defs.c b/lib/nfc/protocols/nfc_device_defs.c index 0dbe8a155..e09523f23 100644 --- a/lib/nfc/protocols/nfc_device_defs.c +++ b/lib/nfc/protocols/nfc_device_defs.c @@ -41,8 +41,8 @@ const NfcDeviceBase* nfc_devices[NfcProtocolNum] = { [NfcProtocolMfUltralight] = &nfc_device_mf_ultralight, [NfcProtocolMfClassic] = &nfc_device_mf_classic, [NfcProtocolMfDesfire] = &nfc_device_mf_desfire, - [NfcProtocolEmv] = &nfc_device_emv, [NfcProtocolSlix] = &nfc_device_slix, [NfcProtocolSt25tb] = &nfc_device_st25tb, + [NfcProtocolEmv] = &nfc_device_emv, /* Add new protocols here */ }; diff --git a/lib/nfc/protocols/nfc_poller_defs.c b/lib/nfc/protocols/nfc_poller_defs.c index 55a59cfd6..e79c96d98 100644 --- a/lib/nfc/protocols/nfc_poller_defs.c +++ b/lib/nfc/protocols/nfc_poller_defs.c @@ -23,8 +23,8 @@ const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolMfUltralight] = &mf_ultralight_poller, [NfcProtocolMfClassic] = &mf_classic_poller, [NfcProtocolMfDesfire] = &mf_desfire_poller, - [NfcProtocolEmv] = &emv_poller, [NfcProtocolSlix] = &nfc_poller_slix, - /* Add new pollers here */ [NfcProtocolSt25tb] = &nfc_poller_st25tb, + [NfcProtocolEmv] = &emv_poller, + /* Add new pollers here */ }; diff --git a/lib/nfc/protocols/nfc_protocol.c b/lib/nfc/protocols/nfc_protocol.c index 54ee5ba0d..252f86de2 100644 --- a/lib/nfc/protocols/nfc_protocol.c +++ b/lib/nfc/protocols/nfc_protocol.c @@ -137,12 +137,6 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = { .children_num = 0, .children_protocol = NULL, }, - [NfcProtocolEmv] = - { - .parent_protocol = NfcProtocolIso14443_4a, - .children_num = 0, - .children_protocol = NULL, - }, [NfcProtocolSlix] = { .parent_protocol = NfcProtocolIso15693_3, @@ -155,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 */ }; diff --git a/lib/nfc/protocols/nfc_protocol.h b/lib/nfc/protocols/nfc_protocol.h index d597de152..39e8045fe 100644 --- a/lib/nfc/protocols/nfc_protocol.h +++ b/lib/nfc/protocols/nfc_protocol.h @@ -185,9 +185,9 @@ typedef enum { NfcProtocolMfUltralight, NfcProtocolMfClassic, NfcProtocolMfDesfire, - NfcProtocolEmv, NfcProtocolSlix, NfcProtocolSt25tb, + NfcProtocolEmv, /* Add new protocols here */ NfcProtocolNum, /**< Special value representing the number of available protocols. */ From ecabcbc58adc8eb37cf1408285df6180200e8e0f Mon Sep 17 00:00:00 2001 From: Methodius Date: Sat, 20 Jan 2024 05:35:37 +0900 Subject: [PATCH 09/28] Kostyly for iso14443-4a poller (pwt_ext) Co-authored-by: Nikita Vostokov <1042932+wosk@users.noreply.github.com> --- lib/nfc/helpers/iso14443_4_layer.c | 57 +++++++++++++++++++ lib/nfc/helpers/iso14443_4_layer.h | 6 ++ lib/nfc/protocols/emv/emv_poller_i.c | 8 +-- lib/nfc/protocols/iso14443_4a/iso14443_4a.h | 1 + .../iso14443_4a/iso14443_4a_poller.h | 5 ++ .../iso14443_4a/iso14443_4a_poller_i.c | 38 +++++++++++++ targets/f7/api_symbols.csv | 3 +- 7 files changed, 113 insertions(+), 5 deletions(-) diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 26f4dc3b7..75282c1d6 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -7,6 +7,18 @@ #define ISO14443_4_BLOCK_PCB_R (5U << 5) #define ISO14443_4_BLOCK_PCB_S (3U << 6) +//KOSTYLY +#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 (1U << 1) +#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,48 @@ 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; + + 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 { + // TODO: Need send request again + ret = Iso14443_4aErrorProtocol; + } + 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_reset(output_data); + 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_4aErrorSendCtrl; + } + break; + } + } while(false); + + return ret; +} \ No newline at end of file diff --git a/lib/nfc/helpers/iso14443_4_layer.h b/lib/nfc/helpers/iso14443_4_layer.h index 712173ce1..14e435c2f 100644 --- a/lib/nfc/helpers/iso14443_4_layer.h +++ b/lib/nfc/helpers/iso14443_4_layer.h @@ -1,5 +1,6 @@ #pragma once +#include "protocols/iso14443_4a/iso14443_4a.h" #include #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 diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c index 9494ca199..7e85c7dcc 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.c +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -261,7 +261,7 @@ EmvError emv_poller_select_ppse(EmvPoller* instance) { do { FURI_LOG_D(TAG, "Send select PPSE"); - Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( + 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) { @@ -313,7 +313,7 @@ EmvError emv_poller_select_application(EmvPoller* instance) { do { FURI_LOG_D(TAG, "Start application"); - Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( + 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:"); @@ -365,7 +365,7 @@ EmvError emv_poller_get_processing_options(EmvPoller* instance) { do { FURI_LOG_D(TAG, "Get proccessing options"); - Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( + 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:"); @@ -408,7 +408,7 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re 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( + 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, "SFI record:"); diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h index df212152d..add93cea1 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h @@ -13,6 +13,7 @@ typedef enum { Iso14443_4aErrorNotPresent, Iso14443_4aErrorProtocol, Iso14443_4aErrorTimeout, + Iso14443_4aErrorSendCtrl, } Iso14443_4aError; typedef enum { diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h index fef565e51..019beb2be 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h @@ -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. * diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c index 938e4e715..529c74e27 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -81,3 +81,41 @@ 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); + + 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) { + 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_4aErrorSendCtrl) { + // Send response for Control message + bit_buffer_copy(instance->tx_buffer, rx_buffer); + continue; + } + break; + } + } while(true); + + return error; +} \ No newline at end of file diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 95b0bd5cd..1976ef2de 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,51.0,, +Version,+,52.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1969,6 +1969,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" From 5f041a22e56ef3a85a3fb431cffff1274e67cf3b Mon Sep 17 00:00:00 2001 From: Methodius Date: Sat, 20 Jan 2024 06:39:34 +0900 Subject: [PATCH 10/28] EMV parser: exp date added --- applications/main/nfc/plugins/supported_cards/emv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c index cc5465a31..deb693755 100644 --- a/applications/main/nfc/plugins/supported_cards/emv.c +++ b/applications/main/nfc/plugins/supported_cards/emv.c @@ -896,6 +896,8 @@ static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) { if((i != 0) && (i % 2 != 0)) furi_string_cat_printf(parsed_data, " "); } + furi_string_cat_printf(parsed_data, "\nExp: %02X/%02X", app.exp_month, app.exp_year); + furi_string_cat_printf(parsed_data, "\nCountry: %s", get_country_name(app.country_code)); furi_string_cat_printf( From 84abb537121ed2489b71787ea2a9c4f1b48c7aa7 Mon Sep 17 00:00:00 2001 From: Methodius Date: Tue, 23 Jan 2024 19:51:59 +0900 Subject: [PATCH 11/28] Track2 support Co-authored-by: Nikita Vostokov <1042932+wosk@users.noreply.github.com> --- .../main/nfc/plugins/supported_cards/emv.c | 4 +- lib/nfc/protocols/emv/emv.h | 8 +- lib/nfc/protocols/emv/emv_poller_i.c | 77 ++++++++++++++----- 3 files changed, 66 insertions(+), 23 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c index deb693755..703a98cb6 100644 --- a/applications/main/nfc/plugins/supported_cards/emv.c +++ b/applications/main/nfc/plugins/supported_cards/emv.c @@ -890,10 +890,10 @@ static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) { else furi_string_cat_printf(parsed_data, "\e#%s", "EMV"); - furi_string_cat_printf(parsed_data, "\nPAN: "); + furi_string_cat_printf(parsed_data, "\nPAN:"); for(uint8_t i = 0; i < app.pan_len; i++) { + if((i % 2 == 0)) furi_string_cat_printf(parsed_data, " "); furi_string_cat_printf(parsed_data, "%02X", app.pan[i]); - if((i != 0) && (i % 2 != 0)) furi_string_cat_printf(parsed_data, " "); } furi_string_cat_printf(parsed_data, "\nExp: %02X/%02X", app.exp_month, app.exp_year); diff --git a/lib/nfc/protocols/emv/emv.h b/lib/nfc/protocols/emv/emv.h index a10450b7e..c7463ab98 100644 --- a/lib/nfc/protocols/emv/emv.h +++ b/lib/nfc/protocols/emv/emv.h @@ -14,7 +14,7 @@ extern "C" { #define EMV_TAG_PDOL 0x9F38 #define EMV_TAG_CARD_NAME 0x50 #define EMV_TAG_FCI 0xBF0C -#define EMV_TAG_LOG_CTRL 0x9F4D +#define EMV_TAG_LOG_ENTRY 0x9F4D #define EMV_TAG_TRACK_1_EQUIV 0x56 #define EMV_TAG_TRACK_2_EQUIV 0x57 #define EMV_TAG_PAN 0x5A @@ -23,6 +23,10 @@ extern "C" { #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_RESP_BUF_SIZE 0x6C +#define EMV_TAG_GPO_FMT1 0x80 typedef struct { uint16_t tag; @@ -35,6 +39,8 @@ typedef struct { } APDU; typedef struct { + uint8_t log_sfi; + uint8_t log_records; uint8_t priority; uint8_t aid[16]; uint8_t aid_len; diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c index 7e85c7dcc..2b9bdc6e0 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.c +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -145,15 +145,34 @@ static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplicatio } } else { switch(tag) { + case EMV_TAG_GPO_FMT1: + // skip AIP + i += 2; + tlen -= 2; + 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_RESP_BUF_SIZE: + //success = true; + FURI_LOG_T(TAG, "found EMV_TAG_RESP_BUF_SIZE %X: %d", tag, buff[i]); + // Need to request SFI again with this length value + 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); + 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); @@ -174,7 +193,9 @@ static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplicatio 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'; @@ -182,8 +203,9 @@ static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplicatio 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_EQUIV %x", tag); + 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) { @@ -195,21 +217,21 @@ static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplicatio } } - // // 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_EQUIV %x : %s", tag, track_2_equiv); + // 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; } @@ -235,6 +257,17 @@ static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplicatio 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; } } i += tlen; @@ -392,6 +425,7 @@ EmvError emv_poller_get_processing_options(EmvPoller* instance) { 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[] = { @@ -411,10 +445,11 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re 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, "SFI record:"); + 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 %d record %d", sfi, record_num); + FURI_LOG_E(TAG, "Failed to read SFI 0x%X record %d", sfi, record_num); error = emv_process_error(iso14443_4a_error); break; } @@ -427,10 +462,12 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re &instance->data->emv_application)) { // It's ok while bruteforcing //error = EmvErrorProtocol; - FURI_LOG_T(TAG, "Failed to parse SFI %d record %d", sfi, record_num); + FURI_LOG_T(TAG, "Failed to parse SFI 0x%X record %d", sfi, record_num); } } while(false); + furi_string_free(text); + return error; } From 87f8f1d9c450538bd9990a35bbb3e9fd6a9c0773 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov <1042932+wosk@users.noreply.github.com> Date: Tue, 23 Jan 2024 20:47:17 +0000 Subject: [PATCH 12/28] Remove kostyly, add raw debug --- lib/nfc/helpers/iso14443_4_layer.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 75282c1d6..5b57d918c 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -3,14 +3,11 @@ #include #define ISO14443_4_BLOCK_PCB (1U << 1) -#define ISO14443_4_BLOCK_PCB_I (0U) -#define ISO14443_4_BLOCK_PCB_R (5U << 5) +#define ISO14443_4_BLOCK_PCB_I (0U << 6) +#define ISO14443_4_BLOCK_PCB_R (2U << 6) #define ISO14443_4_BLOCK_PCB_S (3U << 6) - -//KOSTYLY -#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) @@ -87,7 +84,7 @@ Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( 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_: + case ISO14443_4_BLOCK_PCB_I: if(pcb_field == instance->pcb_prev) { bit_buffer_copy_right(output_data, block_data, 1); ret = Iso14443_4aErrorNone; @@ -96,7 +93,7 @@ Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( ret = Iso14443_4aErrorProtocol; } break; - case ISO14443_4_BLOCK_PCB_R_: + case ISO14443_4_BLOCK_PCB_R: // TODO break; case ISO14443_4_BLOCK_PCB_S: @@ -117,5 +114,13 @@ Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( } } 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; -} \ No newline at end of file +} From 3f6092d95c2d6dc407da0077b544673ee8ef11d4 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov <1042932+wosk@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:03:54 +0000 Subject: [PATCH 13/28] Don't stop if SELECT APPLICATION failed --- lib/nfc/protocols/emv/emv_poller.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/nfc/protocols/emv/emv_poller.c b/lib/nfc/protocols/emv/emv_poller.c index 41ae8afba..e26c4e137 100644 --- a/lib/nfc/protocols/emv/emv_poller.c +++ b/lib/nfc/protocols/emv/emv_poller.c @@ -82,11 +82,11 @@ static NfcCommand emv_poller_handler_select_application(EmvPoller* instance) { if(instance->error == EmvErrorNone) { FURI_LOG_D(TAG, "Select application success"); - instance->state = EmvPollerStateGetProcessingOptions; } else { FURI_LOG_E(TAG, "Failed to select application"); - instance->state = EmvPollerStateReadFailed; + // We have to try GPO request with empty tag } + instance->state = EmvPollerStateGetProcessingOptions; return NfcCommandContinue; } From 3fce83eb79b1088f5540cac5cf4a6b73781453c0 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov <1042932+wosk@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:34:39 +0000 Subject: [PATCH 14/28] Process error codes --- lib/nfc/protocols/emv/emv.h | 3 ++- lib/nfc/protocols/emv/emv_poller_i.c | 23 ++++++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/nfc/protocols/emv/emv.h b/lib/nfc/protocols/emv/emv.h index c7463ab98..699a0eca1 100644 --- a/lib/nfc/protocols/emv/emv.h +++ b/lib/nfc/protocols/emv/emv.h @@ -24,9 +24,10 @@ extern "C" { #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_GPO_FMT1 0x80 +#define EMV_TAG_RESP_BYTES_AVAILABLE 0x61 typedef struct { uint16_t tag; diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c index 2b9bdc6e0..a2353b52a 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.c +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -117,6 +117,24 @@ static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplicatio while(i < len) { 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 success; + case EMV_TAG_RESP_BYTES_AVAILABLE: + FURI_LOG_T(TAG, " Bytes available: %02X", buff[i + 1]); + // Need to request one more time + return success; + + default: + FURI_LOG_T(TAG, " Error/warning code: %02X %02X", buff[i], buff[i + 1]); + return success; + } + } + if((first_byte & 31) == 31) { // 2-byte tag tag = buff[i] << 8 | buff[i + 1]; i++; @@ -154,11 +172,6 @@ static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplicatio success = true; FURI_LOG_T(TAG, "found EMV_TAG_GPO_FMT1 %X: ", tag); break; - case EMV_TAG_RESP_BUF_SIZE: - //success = true; - FURI_LOG_T(TAG, "found EMV_TAG_RESP_BUF_SIZE %X: %d", tag, buff[i]); - // Need to request SFI again with this length value - break; case EMV_TAG_AID: app->aid_len = tlen; memcpy(app->aid, &buff[i], tlen); From 5e384ccc4348c6f538eb5e9e21f6df58f0157d10 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov <1042932+wosk@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:54:32 +0000 Subject: [PATCH 15/28] Fix log --- lib/nfc/protocols/emv/emv_poller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nfc/protocols/emv/emv_poller.c b/lib/nfc/protocols/emv/emv_poller.c index e26c4e137..25d3e0507 100644 --- a/lib/nfc/protocols/emv/emv_poller.c +++ b/lib/nfc/protocols/emv/emv_poller.c @@ -99,7 +99,7 @@ static NfcCommand emv_poller_handler_get_processing_options(EmvPoller* instance) if(instance->data->emv_application.pan_len > 0) { instance->state = EmvPollerStateReadSuccess; } else { - FURI_LOG_D(TAG, "No AFL still. Fallback to bruteforce files"); + FURI_LOG_D(TAG, "No PAN still. Read SFI files"); instance->state = EmvPollerStateReadFiles; } } else { From c014491f55113c90d6d3d4439b9a598dc9c9293e Mon Sep 17 00:00:00 2001 From: Nikita Vostokov <1042932+wosk@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:03:02 +0000 Subject: [PATCH 16/28] Support 19 bytes PAN (eg.MIR virt) --- applications/main/nfc/plugins/supported_cards/emv.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c index 703a98cb6..f870b6393 100644 --- a/applications/main/nfc/plugins/supported_cards/emv.c +++ b/applications/main/nfc/plugins/supported_cards/emv.c @@ -896,6 +896,10 @@ static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_cat_printf(parsed_data, "%02X", app.pan[i]); } + // Cut padding 'F' from card number + size_t end = furi_string_search_rchar(parsed_data, 'F'); + if(end) furi_string_left(parsed_data, end); + furi_string_cat_printf(parsed_data, "\nExp: %02X/%02X", app.exp_month, app.exp_year); furi_string_cat_printf(parsed_data, "\nCountry: %s", get_country_name(app.country_code)); From 39055ff701307863a29223ce179ac6a3fafdeecc Mon Sep 17 00:00:00 2001 From: Nikita Vostokov <1042932+wosk@users.noreply.github.com> Date: Sat, 27 Jan 2024 22:43:01 +0000 Subject: [PATCH 17/28] Improve info screen * search F only in card_number --- .../helpers/protocol_support/emv/emv_render.c | 1 + .../main/nfc/plugins/supported_cards/emv.c | 24 ++++++++----------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c index ead426a15..7727816cc 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c @@ -17,6 +17,7 @@ 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) { if(len == 0) return; + furi_string_cat_printf(str, "PAN: "); for(uint8_t i = 0; i < len; i += 2) { furi_string_cat_printf(str, "%02X%02X ", data[i], data[i + 1]); } diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c index f870b6393..09eb804e5 100644 --- a/applications/main/nfc/plugins/supported_cards/emv.c +++ b/applications/main/nfc/plugins/supported_cards/emv.c @@ -886,30 +886,26 @@ static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) { do { if(app.name_found) - furi_string_cat_printf(parsed_data, "\e#%s", app.name); + furi_string_cat_printf(parsed_data, "\e#%s\n", app.name); else - furi_string_cat_printf(parsed_data, "\e#%s", "EMV"); + furi_string_cat_printf(parsed_data, "\e#%s\n", "EMV"); - furi_string_cat_printf(parsed_data, "\nPAN:"); - for(uint8_t i = 0; i < app.pan_len; i++) { - if((i % 2 == 0)) furi_string_cat_printf(parsed_data, " "); - furi_string_cat_printf(parsed_data, "%02X", app.pan[i]); + FuriString* card_number = furi_string_alloc(); + for(uint8_t i = 0; i < app.pan_len; i += 2) { + furi_string_cat_printf(card_number, "%02X%02X ", app.pan[i], app.pan[i + 1]); } // Cut padding 'F' from card number - size_t end = furi_string_search_rchar(parsed_data, 'F'); - if(end) furi_string_left(parsed_data, end); + size_t end = furi_string_search_rchar(card_number, 'F'); + if(end) furi_string_left(card_number, end); + furi_string_cat(parsed_data, card_number); + furi_string_free(card_number); furi_string_cat_printf(parsed_data, "\nExp: %02X/%02X", app.exp_month, app.exp_year); furi_string_cat_printf(parsed_data, "\nCountry: %s", get_country_name(app.country_code)); - furi_string_cat_printf( - parsed_data, "\nCurrency: %s", get_currency_name(app.currency_code)); - - furi_string_cat_printf(parsed_data, "\nAID: "); - for(uint8_t i = 0; i < app.aid_len; i++) - furi_string_cat_printf(parsed_data, "%02X", app.aid[i]); + parsed_data, " Currency: %s", get_currency_name(app.currency_code)); parsed = true; } while(false); From 4b786fb77e2e5c353e4de84184699376aaca267a Mon Sep 17 00:00:00 2001 From: Nikita Vostokov <1042932+wosk@users.noreply.github.com> Date: Sun, 28 Jan 2024 03:57:12 +0000 Subject: [PATCH 18/28] Refactor response decoder Read transactions history --- .../helpers/protocol_support/emv/emv_render.c | 50 ++ .../helpers/protocol_support/emv/emv_render.h | 2 + lib/nfc/protocols/emv/emv.h | 22 + lib/nfc/protocols/emv/emv_poller.c | 21 +- lib/nfc/protocols/emv/emv_poller.h | 4 +- lib/nfc/protocols/emv/emv_poller_i.c | 526 ++++++++++++------ lib/nfc/protocols/emv/emv_poller_i.h | 1 + targets/f7/api_symbols.csv | 5 +- 8 files changed, 451 insertions(+), 180 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c index 7727816cc..10333a936 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c @@ -60,6 +60,56 @@ void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) { furi_string_cat_printf(str, "\n"); } +void nfc_render_emv_transactions(const EmvApplication* apl, FuriString* str) { + const uint8_t len = apl->active_tr; + if(!len) { + return; + } + furi_string_cat_printf(str, "Transactions:\n"); + for(int i = 0; i < len; i++) { + if(!apl->trans[i].amount) continue; + uint8_t* a = (uint8_t*)&apl->trans[i].amount; + furi_string_cat_printf(str, "%d: ", apl->trans[i].atc); + bool top = true; + for(int x = 0; x < 6; x++) { + 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]); + } + } + } + // TODO to string + furi_string_cat_printf(str, " %x\n", apl->trans[i].currency); + + // TODO to string + if(apl->trans[i].country) + furi_string_cat_printf(str, "country: %x\n", apl->trans[i].country); + + if(apl->trans[i].time) + furi_string_cat_printf( + str, + "%02lx:%02lx:%02lx ", + apl->trans[i].time & 0xff, + (apl->trans[i].time >> 8) & 0xff, + apl->trans[i].time >> 16); + if(apl->trans[i].date) + furi_string_cat_printf( + str, + "%02lx/%02lx/%02lx\n", + apl->trans[i].date >> 16, + (apl->trans[i].date >> 8) & 0xff, + apl->trans[i].date & 0xff); + } +} + void nfc_render_emv_extra(const EmvData* data, FuriString* str) { nfc_render_emv_application(&data->emv_application, str); + //nfc_render_emv_transactions(&data->emv_application, str); } diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.h b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h index 8fb31eaea..fd73cfc6b 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv_render.h +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h @@ -22,3 +22,5 @@ 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); + +void nfc_render_emv_transactions(const EmvApplication* data, FuriString* str); \ No newline at end of file diff --git a/lib/nfc/protocols/emv/emv.h b/lib/nfc/protocols/emv/emv.h index 699a0eca1..b565ee334 100644 --- a/lib/nfc/protocols/emv/emv.h +++ b/lib/nfc/protocols/emv/emv.h @@ -15,6 +15,15 @@ extern "C" { #define EMV_TAG_CARD_NAME 0x50 #define EMV_TAG_FCI 0xBF0C #define EMV_TAG_LOG_ENTRY 0x9F4D +#define EMV_TAG_LOG_FMT 0x9F4F + +#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 @@ -39,9 +48,22 @@ typedef struct { 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; + Transaction trans[16]; uint8_t priority; uint8_t aid[16]; uint8_t aid_len; diff --git a/lib/nfc/protocols/emv/emv_poller.c b/lib/nfc/protocols/emv/emv_poller.c index 25d3e0507..70051afcd 100644 --- a/lib/nfc/protocols/emv/emv_poller.c +++ b/lib/nfc/protocols/emv/emv_poller.c @@ -111,11 +111,14 @@ static NfcCommand emv_poller_handler_get_processing_options(EmvPoller* instance) } static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) { - instance->error = emv_poller_read_files(instance); + instance->error = emv_poller_read_afl(instance); if(instance->error == EmvErrorNone) { FURI_LOG_D(TAG, "Read files success"); - instance->state = EmvPollerStateReadSuccess; + if(instance->data->emv_application.log_sfi) + instance->state = EmvPollerStateReadLogs; + else + instance->state = EmvPollerStateReadSuccess; } else { FURI_LOG_E(TAG, "Failed to read files"); instance->state = EmvPollerStateReadFailed; @@ -124,6 +127,19 @@ static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) { return NfcCommandContinue; } +static NfcCommand emv_poller_handler_read_logs(EmvPoller* instance) { + instance->error = emv_poller_read_log_entry(instance); + + if(instance->error == EmvErrorNone) { + FURI_LOG_D(TAG, "Log entries had been read"); + } else { + FURI_LOG_D(TAG, "No log entry"); + } + + 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); @@ -147,6 +163,7 @@ static const EmvPollerReadHandler emv_poller_read_handler[EmvPollerStateNum] = { [EmvPollerStateSelectApplication] = emv_poller_handler_select_application, [EmvPollerStateGetProcessingOptions] = emv_poller_handler_get_processing_options, [EmvPollerStateReadFiles] = emv_poller_handler_read_files, + [EmvPollerStateReadLogs] = emv_poller_handler_read_logs, [EmvPollerStateReadFailed] = emv_poller_handler_read_fail, [EmvPollerStateReadSuccess] = emv_poller_handler_read_success, }; diff --git a/lib/nfc/protocols/emv/emv_poller.h b/lib/nfc/protocols/emv/emv_poller.h index 36f27578a..c2335bfa4 100644 --- a/lib/nfc/protocols/emv/emv_poller.h +++ b/lib/nfc/protocols/emv/emv_poller.h @@ -46,7 +46,9 @@ 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_files(EmvPoller* instance); +EmvError emv_poller_read_afl(EmvPoller* instance); + +EmvError emv_poller_read_log_entry(EmvPoller* instance); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c index a2353b52a..eb27786cc 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.c +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -109,179 +109,277 @@ static uint16_t emv_prepare_pdol(APDU* dest, APDU* src) { return dest->size; } -static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplication* app) { - uint16_t i = 0; - uint16_t tag = 0, first_byte = 0; - uint16_t tlen = 0; +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_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_ATC: + app->trans[app->active_tr].atc = (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; + } + 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]; - 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 success; - case EMV_TAG_RESP_BYTES_AVAILABLE: - FURI_LOG_T(TAG, " Bytes available: %02X", buff[i + 1]); - // Need to request one more time - return success; + success = emv_parse_tag(buff, len, &tag, &tlen, &i); + if(!success) return success; - default: - FURI_LOG_T(TAG, " Error/warning code: %02X %02X", buff[i], buff[i + 1]); - 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++; if((first_byte & 32) == 32) { // "Constructed" -- contains more TLV data to parse FURI_LOG_T(TAG, "Constructed TLV %x", tag); - if(!emv_decode_response(&buff[i], tlen, app)) { + 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 { - switch(tag) { - case EMV_TAG_GPO_FMT1: - // skip AIP - i += 2; - tlen -= 2; - 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_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; - } + emv_decode_tlv_tag(&buff[i], tag, tlen, app); } i += tlen; } @@ -320,7 +418,7 @@ EmvError emv_poller_select_ppse(EmvPoller* instance) { const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); - if(!emv_decode_response( + if(!emv_decode_response_tlv( buff, bit_buffer_get_size_bytes(instance->rx_buffer), &instance->data->emv_application)) { @@ -372,7 +470,7 @@ EmvError emv_poller_select_application(EmvPoller* instance) { const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); - if(!emv_decode_response( + if(!emv_decode_response_tlv( buff, bit_buffer_get_size_bytes(instance->rx_buffer), &instance->data->emv_application)) { @@ -424,7 +522,7 @@ EmvError emv_poller_get_processing_options(EmvPoller* instance) { const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); - if(!emv_decode_response( + if(!emv_decode_response_tlv( buff, bit_buffer_get_size_bytes(instance->rx_buffer), &instance->data->emv_application)) { @@ -466,17 +564,6 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re error = emv_process_error(iso14443_4a_error); break; } - - const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); - - if(!emv_decode_response( - buff, - bit_buffer_get_size_bytes(instance->rx_buffer), - &instance->data->emv_application)) { - // It's ok while bruteforcing - //error = EmvErrorProtocol; - FURI_LOG_T(TAG, "Failed to parse SFI 0x%X record %d", sfi, record_num); - } } while(false); furi_string_free(text); @@ -484,7 +571,7 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re return error; } -EmvError emv_poller_read_files(EmvPoller* instance) { +EmvError emv_poller_read_afl(EmvPoller* instance) { EmvError error = EmvErrorNone; APDU* afl = &instance->data->emv_application.afl; @@ -504,6 +591,14 @@ EmvError emv_poller_read_files(EmvPoller* instance) { 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); + } if(instance->data->emv_application.pan_len != 0) return EmvErrorNone; // Card number fetched } @@ -511,4 +606,85 @@ EmvError emv_poller_read_files(EmvPoller* instance) { } return error; -} \ No newline at end of file +} + +static EmvError emv_poller_get_log_format(EmvPoller* instance) { + EmvError error = EmvErrorNone; + + const uint8_t cla_ins[] = {0x80, 0xCA}; + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_copy_bytes(instance->tx_buffer, cla_ins, sizeof(cla_ins)); + bit_buffer_append_byte(instance->tx_buffer, EMV_TAG_LOG_FMT >> 8); + bit_buffer_append_byte(instance->tx_buffer, EMV_TAG_LOG_FMT & 0xFF); + bit_buffer_append_byte(instance->tx_buffer, 0x00); //Length + + do { + FURI_LOG_D(TAG, "Get log format"); + + 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 format answer:"); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + FURI_LOG_E(TAG, "Failed to get log format, 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 log format"); + } + } while(false); + + return error; +} + +EmvError emv_poller_read_log_entry(EmvPoller* instance) { + EmvError error = EmvErrorProtocol; + + uint8_t records = instance->data->emv_application.log_records; + if(records == 0) { + return false; + } + + error = emv_poller_get_log_format(instance); + if(error != EmvErrorNone) return false; + + 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)); + } + + return error; +} diff --git a/lib/nfc/protocols/emv/emv_poller_i.h b/lib/nfc/protocols/emv/emv_poller_i.h index 4809a8668..554560a25 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.h +++ b/lib/nfc/protocols/emv/emv_poller_i.h @@ -14,6 +14,7 @@ typedef enum { EmvPollerStateSelectApplication, EmvPollerStateGetProcessingOptions, EmvPollerStateReadFiles, + EmvPollerStateReadLogs, EmvPollerStateReadFailed, EmvPollerStateReadSuccess, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1976ef2de..f53c4d0ca 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,52.0,, +Version,+,52.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -888,7 +888,8 @@ 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_processing_options,EmvError,EmvPoller* -Function,+,emv_poller_read_files,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* From 786f3568c0444f3aec98e04724fa326472f278f2 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov <1042932+wosk@users.noreply.github.com> Date: Sun, 28 Jan 2024 20:18:17 +0000 Subject: [PATCH 19/28] Fix retry loop (on Android HCE) --- lib/nfc/helpers/iso14443_4_layer.c | 8 ++++---- lib/nfc/protocols/emv/emv_poller_i.c | 12 ++++++++++-- lib/nfc/protocols/iso14443_4a/iso14443_4a.h | 2 +- lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c | 7 +++++-- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 5b57d918c..7f0f0a25e 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -79,6 +79,7 @@ Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( 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); @@ -89,8 +90,8 @@ Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( bit_buffer_copy_right(output_data, block_data, 1); ret = Iso14443_4aErrorNone; } else { - // TODO: Need send request again - ret = Iso14443_4aErrorProtocol; + // send original request again + ret = Iso14443_4aErrorSendExtra; } break; case ISO14443_4_BLOCK_PCB_R: @@ -103,12 +104,11 @@ Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( const uint8_t wtxm = inf_field & 0b111111; //uint32_t fwt_temp = MIN((fwt * wtxm), fwt_max); - bit_buffer_reset(output_data); 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_4aErrorSendCtrl; + ret = Iso14443_4aErrorSendExtra; } break; } diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c index eb27786cc..99e7c9759 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.c +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -208,6 +208,14 @@ static bool 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; @@ -654,11 +662,11 @@ EmvError emv_poller_read_log_entry(EmvPoller* instance) { uint8_t records = instance->data->emv_application.log_records; if(records == 0) { - return false; + return error; } error = emv_poller_get_log_format(instance); - if(error != EmvErrorNone) return false; + if(error != EmvErrorNone) return error; FURI_LOG_D(TAG, "Read Transaction logs"); diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h index add93cea1..05a7bd577 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h @@ -13,7 +13,7 @@ typedef enum { Iso14443_4aErrorNotPresent, Iso14443_4aErrorProtocol, Iso14443_4aErrorTimeout, - Iso14443_4aErrorSendCtrl, + Iso14443_4aErrorSendExtra, } Iso14443_4aError; typedef enum { diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c index 529c74e27..b8e2ebda6 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -88,6 +88,7 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext( 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); @@ -108,9 +109,11 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext( } else { error = iso14443_4_layer_decode_block_pwt_ext( instance->iso14443_4_layer, rx_buffer, instance->rx_buffer); - if(error == Iso14443_4aErrorSendCtrl) { + if(error == Iso14443_4aErrorSendExtra) { + if(--retry == 0) break; // Send response for Control message - bit_buffer_copy(instance->tx_buffer, rx_buffer); + if(bit_buffer_get_size_bytes(rx_buffer)) + bit_buffer_copy(instance->tx_buffer, rx_buffer); continue; } break; From 1074af905c960b7a9a5ca1cd6e15413306f76d34 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov <1042932+wosk@users.noreply.github.com> Date: Sun, 28 Jan 2024 22:45:26 +0000 Subject: [PATCH 20/28] Use render * Add transactions menu item * Use nfc assets for country/currency/aid * remove deprecated render --- applications/main/nfc/application.fam | 8 - .../main/nfc/helpers/nfc_emv_parser.c | 2 +- .../main/nfc/helpers/nfc_emv_parser.h | 2 +- .../nfc/helpers/protocol_support/emv/emv.c | 2 +- .../helpers/protocol_support/emv/emv_render.c | 119 ++- .../helpers/protocol_support/emv/emv_render.h | 4 +- applications/main/nfc/nfc_app_i.h | 1 + .../main/nfc/plugins/supported_cards/emv.c | 934 ------------------ .../main/nfc/scenes/nfc_scene_emv_more_info.c | 25 +- 9 files changed, 105 insertions(+), 992 deletions(-) delete mode 100644 applications/main/nfc/plugins/supported_cards/emv.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 0ed7a6241..86eefe620 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -182,14 +182,6 @@ App( sources=["plugins/supported_cards/ndef.c"], ) -App( - appid="emv_parser", - apptype=FlipperAppType.PLUGIN, - entry_point="emv_plugin_ep", - targets=["f7"], - requires=["nfc"], - sources=["plugins/supported_cards/emv.c"], -) App( appid="nfc_start", diff --git a/applications/main/nfc/helpers/nfc_emv_parser.c b/applications/main/nfc/helpers/nfc_emv_parser.c index 30e102405..f48a63d8f 100644 --- a/applications/main/nfc/helpers/nfc_emv_parser.c +++ b/applications/main/nfc/helpers/nfc_emv_parser.c @@ -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); diff --git a/applications/main/nfc/helpers/nfc_emv_parser.h b/applications/main/nfc/helpers/nfc_emv_parser.h index c636ca77d..03edab0a8 100644 --- a/applications/main/nfc/helpers/nfc_emv_parser.h +++ b/applications/main/nfc/helpers/nfc_emv_parser.h @@ -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); diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv.c b/applications/main/nfc/helpers/protocol_support/emv/emv.c index 0b60bea6e..e543291cc 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv.c @@ -75,7 +75,7 @@ static void nfc_scene_read_success_on_enter_emv(NfcApp* instance) { // } const NfcProtocolSupportBase nfc_protocol_support_emv = { - .features = NfcProtocolFeatureNone, + .features = NfcProtocolFeatureMoreInfo, .scene_info = { diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c index 10333a936..463f7355e 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c @@ -1,6 +1,7 @@ #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_name(data->emv_application.name, str); @@ -17,10 +18,18 @@ 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) { if(len == 0) return; - furi_string_cat_printf(str, "PAN: "); - for(uint8_t i = 0; i < len; i += 2) { - furi_string_cat_printf(str, "%02X%02X ", data[i], data[i + 1]); + + 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"); } @@ -29,16 +38,27 @@ void nfc_render_emv_expired(const EmvApplication* apl, FuriString* str) { 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_currency(uint16_t cur_code, FuriString* str) { + if(!cur_code) return; + + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* currency_name = furi_string_alloc(); + if(nfc_emv_parser_get_currency_name(storage, cur_code, currency_name)) { + furi_string_cat_printf(str, "Currency: %s\n", furi_string_get_cstr(currency_name)); + } + furi_string_free(currency_name); + furi_record_close(RECORD_STORAGE); } -void nfc_render_emv_country(const EmvApplication* apl, FuriString* str) { - UNUSED(apl); - UNUSED(str); - // nfc/assets/country_code.nfc +void nfc_render_emv_country(uint16_t country_code, FuriString* str) { + if(!country_code) return; + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* country_name = furi_string_alloc(); + if(nfc_emv_parser_get_country_name(storage, country_code, country_name)) { + furi_string_cat_printf(str, "Country: %s\n", furi_string_get_cstr(country_name)); + } + furi_string_free(country_name); + furi_record_close(RECORD_STORAGE); } void nfc_render_emv_name(const char* data, FuriString* str) { @@ -50,28 +70,48 @@ void nfc_render_emv_name(const char* data, FuriString* str) { void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) { 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"); + + if(!len) { + furi_string_cat_printf(str, "No Pay Application found\n"); + return; } + + furi_string_cat_printf(str, "AID: "); + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* aid_name = furi_string_alloc(); + + if(nfc_emv_parser_get_aid_name(storage, apl->aid, len, aid_name)) { + furi_string_cat_printf(str, "%s", furi_string_get_cstr(aid_name)); + } else { + 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_free(aid_name); + furi_record_close(RECORD_STORAGE); } void nfc_render_emv_transactions(const EmvApplication* apl, FuriString* str) { const uint8_t len = apl->active_tr; if(!len) { + furi_string_cat_printf(str, "No transaction info\n"); return; } - furi_string_cat_printf(str, "Transactions:\n"); + + 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; - furi_string_cat_printf(str, "%d: ", apl->trans[i].atc); bool top = true; for(int x = 0; x < 6; x++) { + // cents if(x == 5) { furi_string_cat_printf(str, ".%02X", a[x]); break; @@ -85,31 +125,46 @@ void nfc_render_emv_transactions(const EmvApplication* apl, FuriString* str) { } } } - // TODO to string - furi_string_cat_printf(str, " %x\n", apl->trans[i].currency); - // TODO to string - if(apl->trans[i].country) - furi_string_cat_printf(str, "country: %x\n", apl->trans[i].country); + 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 ", + "%02lx:%02lx:%02lx\n", apl->trans[i].time & 0xff, (apl->trans[i].time >> 8) & 0xff, apl->trans[i].time >> 16); - if(apl->trans[i].date) - furi_string_cat_printf( - str, - "%02lx/%02lx/%02lx\n", - apl->trans[i].date >> 16, - (apl->trans[i].date >> 8) & 0xff, - apl->trans[i].date & 0xff); } + + furi_string_free(tmp); + furi_record_close(RECORD_STORAGE); } void nfc_render_emv_extra(const EmvData* data, FuriString* str) { + nfc_render_emv_currency(data->emv_application.currency_code, str); + nfc_render_emv_country(data->emv_application.country_code, str); nfc_render_emv_application(&data->emv_application, str); + // PIN try + // transactions counter + //nfc_render_emv_transactions(&data->emv_application, str); } diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.h b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h index fd73cfc6b..80f80d423 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv_render.h +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h @@ -19,8 +19,8 @@ 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_country(uint16_t country_code, FuriString* str); -void nfc_render_emv_currency(const EmvApplication* apl, FuriString* str); +void nfc_render_emv_currency(uint16_t cur_code, FuriString* str); void nfc_render_emv_transactions(const EmvApplication* data, FuriString* str); \ No newline at end of file diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 943d722f8..0339bf92e 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -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" diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c deleted file mode 100644 index 09eb804e5..000000000 --- a/applications/main/nfc/plugins/supported_cards/emv.c +++ /dev/null @@ -1,934 +0,0 @@ -/* - * Parser for EMV cards. - * - * Copyright 2023 Leptoptilos - * - * 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 . - */ -#include "core/string.h" -#include "furi_hal_rtc.h" -#include "nfc_supported_card_plugin.h" - -#include "protocols/emv/emv.h" -#include "protocols/nfc_protocol.h" -#include - -#include -#include - -#define TAG "EMV" - -char* get_country_name(uint16_t country_code) { - switch(country_code) { - case 0x0004: - return "AFG"; - case 0x0008: - return "ALB"; - case 0x0010: - return "ATA"; - case 0x0012: - return "DZA"; - case 0x0016: - return "ASM"; - case 0x0020: - return "AND"; - case 0x0024: - return "AGO"; - case 0x0028: - return "ATG"; - case 0x0031: - return "AZE"; - case 0x0032: - return "ARG"; - case 0x0036: - return "AUS"; - case 0x0040: - return "AUT"; - case 0x0044: - return "BHS"; - case 0x0048: - return "BHR"; - case 0x0050: - return "BGD"; - case 0x0051: - return "ARM"; - case 0x0052: - return "BRB"; - case 0x0056: - return "BEL"; - case 0x0060: - return "BMU"; - case 0x0064: - return "BTN"; - case 0x0068: - return "BOL"; - case 0x0070: - return "BIH"; - case 0x0072: - return "BWA"; - case 0x0074: - return "BVT"; - case 0x0076: - return "BRA"; - case 0x0084: - return "BLZ"; - case 0x0086: - return "IOT"; - case 0x0090: - return "SLB"; - case 0x0092: - return "VGB"; - case 0x0096: - return "BRN"; - case 0x0100: - return "BGR"; - case 0x0104: - return "MMR"; - case 0x0108: - return "BDI"; - case 0x0112: - return "BLR"; - case 0x0116: - return "KHM"; - case 0x0120: - return "CMR"; - case 0x0124: - return "CAN"; - case 0x0132: - return "CPV"; - case 0x0136: - return "CYM"; - case 0x0140: - return "CAF"; - case 0x0144: - return "LKA"; - case 0x0148: - return "TCD"; - case 0x0152: - return "CHL"; - case 0x0156: - return "CHN"; - case 0x0158: - return "TWN"; - case 0x0162: - return "CXR"; - case 0x0166: - return "CCK"; - case 0x0170: - return "COL"; - case 0x0174: - return "COM"; - case 0x0175: - return "MYT"; - case 0x0178: - return "COG"; - case 0x0180: - return "COD"; - case 0x0184: - return "COK"; - case 0x0188: - return "CRI"; - case 0x0191: - return "HRV"; - case 0x0192: - return "CUB"; - case 0x0196: - return "CYP"; - case 0x0203: - return "CZE"; - case 0x0204: - return "BEN"; - case 0x0208: - return "DNK"; - case 0x0212: - return "DMA"; - case 0x0214: - return "DOM"; - case 0x0218: - return "ECU"; - case 0x0222: - return "SLV"; - case 0x0226: - return "GNQ"; - case 0x0231: - return "ETH"; - case 0x0232: - return "ERI"; - case 0x0233: - return "EST"; - case 0x0234: - return "FRO"; - case 0x0238: - return "FLK"; - case 0x0239: - return "SGS"; - case 0x0242: - return "FJI"; - case 0x0246: - return "FIN"; - case 0x0248: - return "ALA"; - case 0x0250: - return "FRA"; - case 0x0254: - return "GUF"; - case 0x0258: - return "PYF"; - case 0x0260: - return "ATF"; - case 0x0262: - return "DJI"; - case 0x0266: - return "GAB"; - case 0x0268: - return "GEO"; - case 0x0270: - return "GMB"; - case 0x0275: - return "PSE"; - case 0x0276: - return "DEU"; - case 0x0288: - return "GHA"; - case 0x0292: - return "GIB"; - case 0x0296: - return "KIR"; - case 0x0300: - return "GRC"; - case 0x0304: - return "GRL"; - case 0x0308: - return "GRD"; - case 0x0312: - return "GLP"; - case 0x0316: - return "GUM"; - case 0x0320: - return "GTM"; - case 0x0324: - return "GIN"; - case 0x0328: - return "GUY"; - case 0x0332: - return "HTI"; - case 0x0334: - return "HMD"; - case 0x0336: - return "VAT"; - case 0x0340: - return "HND"; - case 0x0344: - return "HKG"; - case 0x0348: - return "HUN"; - case 0x0352: - return "ISL"; - case 0x0356: - return "IND"; - case 0x0360: - return "IDN"; - case 0x0364: - return "IRN"; - case 0x0368: - return "IRQ"; - case 0x0372: - return "IRL"; - case 0x0376: - return "ISR"; - case 0x0380: - return "ITA"; - case 0x0384: - return "CIV"; - case 0x0388: - return "JAM"; - case 0x0392: - return "JPN"; - case 0x0398: - return "KAZ"; - case 0x0400: - return "JOR"; - case 0x0404: - return "KEN"; - case 0x0408: - return "PRK"; - case 0x0410: - return "KOR"; - case 0x0414: - return "KWT"; - case 0x0417: - return "KGZ"; - case 0x0418: - return "LAO"; - case 0x0422: - return "LBN"; - case 0x0426: - return "LSO"; - case 0x0428: - return "LVA"; - case 0x0430: - return "LBR"; - case 0x0434: - return "LBY"; - case 0x0438: - return "LIE"; - case 0x0440: - return "LTU"; - case 0x0442: - return "LUX"; - case 0x0446: - return "MAC"; - case 0x0450: - return "MDG"; - case 0x0454: - return "MWI"; - case 0x0458: - return "MYS"; - case 0x0462: - return "MDV"; - case 0x0466: - return "MLI"; - case 0x0470: - return "MLT"; - case 0x0474: - return "MTQ"; - case 0x0478: - return "MRT"; - case 0x0480: - return "MUS"; - case 0x0484: - return "MEX"; - case 0x0492: - return "MCO"; - case 0x0496: - return "MNG"; - case 0x0498: - return "MDA"; - case 0x0499: - return "MNE"; - case 0x0500: - return "MSR"; - case 0x0504: - return "MAR"; - case 0x0508: - return "MOZ"; - case 0x0512: - return "OMN"; - case 0x0516: - return "NAM"; - case 0x0520: - return "NRU"; - case 0x0524: - return "NPL"; - case 0x0528: - return "NLD"; - case 0x0531: - return "CUW"; - case 0x0533: - return "ABW"; - case 0x0534: - return "SXM"; - case 0x0535: - return "BES"; - case 0x0540: - return "NCL"; - case 0x0548: - return "VUT"; - case 0x0554: - return "NZL"; - case 0x0558: - return "NIC"; - case 0x0562: - return "NER"; - case 0x0566: - return "NGA"; - case 0x0570: - return "NIU"; - case 0x0574: - return "NFK"; - case 0x0578: - return "NOR"; - case 0x0580: - return "MNP"; - case 0x0581: - return "UMI"; - case 0x0583: - return "FSM"; - case 0x0584: - return "MHL"; - case 0x0585: - return "PLW"; - case 0x0586: - return "PAK"; - case 0x0591: - return "PAN"; - case 0x0598: - return "PNG"; - case 0x0600: - return "PRY"; - case 0x0604: - return "PER"; - case 0x0608: - return "PHL"; - case 0x0612: - return "PCN"; - case 0x0616: - return "POL"; - case 0x0620: - return "PRT"; - case 0x0624: - return "GNB"; - case 0x0626: - return "TLS"; - case 0x0630: - return "PRI"; - case 0x0634: - return "QAT"; - case 0x0638: - return "REU"; - case 0x0642: - return "ROU"; - case 0x0643: - return "RUS"; - case 0x0646: - return "RWA"; - case 0x0652: - return "BLM"; - case 0x0654: - return "SHN"; - case 0x0659: - return "KNA"; - case 0x0660: - return "AIA"; - case 0x0662: - return "LCA"; - case 0x0663: - return "MAF"; - case 0x0666: - return "SPM"; - case 0x0670: - return "VCT"; - case 0x0674: - return "SMR"; - case 0x0678: - return "STP"; - case 0x0682: - return "SAU"; - case 0x0686: - return "SEN"; - case 0x0688: - return "SRB"; - case 0x0690: - return "SYC"; - case 0x0694: - return "SLE"; - case 0x0702: - return "SGP"; - case 0x0703: - return "SVK"; - case 0x0704: - return "VNM"; - case 0x0705: - return "SVN"; - case 0x0706: - return "SOM"; - case 0x0710: - return "ZAF"; - case 0x0716: - return "ZWE"; - case 0x0724: - return "ESP"; - case 0x0728: - return "SSD"; - case 0x0729: - return "SDN"; - case 0x0732: - return "ESH"; - case 0x0740: - return "SUR"; - case 0x0744: - return "SJM"; - case 0x0748: - return "SWZ"; - case 0x0752: - return "SWE"; - case 0x0756: - return "CHE"; - case 0x0760: - return "SYR"; - case 0x0762: - return "TJK"; - case 0x0764: - return "THA"; - case 0x0768: - return "TGO"; - case 0x0772: - return "TKL"; - case 0x0776: - return "TON"; - case 0x0780: - return "TTO"; - case 0x0784: - return "ARE"; - case 0x0788: - return "TUN"; - case 0x0792: - return "TUR"; - case 0x0795: - return "TKM"; - case 0x0796: - return "TCA"; - case 0x0798: - return "TUV"; - case 0x0800: - return "UGA"; - case 0x0804: - return "UKR"; - case 0x0807: - return "MKD"; - case 0x0818: - return "EGY"; - case 0x0826: - return "GBR"; - case 0x0831: - return "GGY"; - case 0x0832: - return "JEY"; - case 0x0833: - return "IMN"; - case 0x0834: - return "TZA"; - case 0x0840: - return "USA"; - case 0x0850: - return "VIR"; - case 0x0854: - return "BFA"; - case 0x0858: - return "URY"; - case 0x0860: - return "UZB"; - case 0x0862: - return "VEN"; - case 0x0876: - return "WLF"; - case 0x0882: - return "WSM"; - case 0x0887: - return "YEM"; - case 0x0894: - return "ZMB"; - default: - return "UNKNOWN"; - } -} - -char* get_currency_name(uint16_t currency_code) { - switch(currency_code) { - case 0x0997: - return "USN"; - case 0x0994: - return "XSU"; - case 0x0990: - return "CLF"; - case 0x0986: - return "BRL"; - case 0x0985: - return "PLN"; - case 0x0984: - return "BOV"; - case 0x0981: - return "GEL"; - case 0x0980: - return "UAH"; - case 0x0979: - return "MXV"; - case 0x0978: - return "EUR"; - case 0x0977: - return "BAM"; - case 0x0976: - return "CDF"; - case 0x0975: - return "BGN"; - case 0x0973: - return "AOA"; - case 0x0972: - return "TJS"; - case 0x0971: - return "AFN"; - case 0x0970: - return "COU"; - case 0x0969: - return "MGA"; - case 0x0968: - return "SRD"; - case 0x0967: - return "ZMW"; - case 0x0965: - return "XUA"; - case 0x0960: - return "XDR"; - case 0x0953: - return "XPF"; - case 0x0952: - return "XOF"; - case 0x0951: - return "XCD"; - case 0x0950: - return "XAF"; - case 0x0949: - return "TRY"; - case 0x0948: - return "CHW"; - case 0x0947: - return "CHE"; - case 0x0946: - return "RON"; - case 0x0944: - return "AZN"; - case 0x0943: - return "MZN"; - case 0x0941: - return "RSD"; - case 0x0940: - return "UYI"; - case 0x0938: - return "SDG"; - case 0x0937: - return "VEF"; - case 0x0936: - return "GHS"; - case 0x0934: - return "TMT"; - case 0x0933: - return "BYN"; - case 0x0932: - return "ZWL"; - case 0x0931: - return "CUC"; - case 0x0930: - return "STN"; - case 0x0929: - return "MRU"; - case 0x0901: - return "TWD"; - case 0x0886: - return "YER"; - case 0x0882: - return "WST"; - case 0x0860: - return "UZS"; - case 0x0858: - return "UYU"; - case 0x0840: - return "USD"; - case 0x0834: - return "TZS"; - case 0x0826: - return "GBP"; - case 0x0818: - return "EGP"; - case 0x0807: - return "MKD"; - case 0x0800: - return "UGX"; - case 0x0788: - return "TND"; - case 0x0784: - return "AED"; - case 0x0780: - return "TTD"; - case 0x0776: - return "TOP"; - case 0x0764: - return "THB"; - case 0x0760: - return "SYP"; - case 0x0756: - return "CHF"; - case 0x0752: - return "SEK"; - case 0x0748: - return "SZL"; - case 0x0728: - return "SSP"; - case 0x0710: - return "ZAR"; - case 0x0706: - return "SOS"; - case 0x0704: - return "VND"; - case 0x0702: - return "SGD"; - case 0x0694: - return "SLL"; - case 0x0690: - return "SCR"; - case 0x0682: - return "SAR"; - case 0x0654: - return "SHP"; - case 0x0646: - return "RWF"; - case 0x0643: - return "RUB"; - case 0x0634: - return "QAR"; - case 0x0608: - return "PHP"; - case 0x0604: - return "PEN"; - case 0x0600: - return "PYG"; - case 0x0598: - return "PGK"; - case 0x0590: - return "PAB"; - case 0x0586: - return "PKR"; - case 0x0578: - return "NOK"; - case 0x0566: - return "NGN"; - case 0x0558: - return "NIO"; - case 0x0554: - return "NZD"; - case 0x0548: - return "VUV"; - case 0x0533: - return "AWG"; - case 0x0532: - return "ANG"; - case 0x0524: - return "NPR"; - case 0x0516: - return "NAD"; - case 0x0512: - return "OMR"; - case 0x0504: - return "MAD"; - case 0x0498: - return "MDL"; - case 0x0496: - return "MNT"; - case 0x0484: - return "MXN"; - case 0x0480: - return "MUR"; - case 0x0462: - return "MVR"; - case 0x0458: - return "MYR"; - case 0x0454: - return "MWK"; - case 0x0446: - return "MOP"; - case 0x0434: - return "LYD"; - case 0x0430: - return "LRD"; - case 0x0426: - return "LSL"; - case 0x0422: - return "LBP"; - case 0x0418: - return "LAK"; - case 0x0417: - return "KGS"; - case 0x0414: - return "KWD"; - case 0x0410: - return "KRW"; - case 0x0408: - return "KPW"; - case 0x0404: - return "KES"; - case 0x0400: - return "JOD"; - case 0x0398: - return "KZT"; - case 0x0392: - return "JPY"; - case 0x0388: - return "JMD"; - case 0x0376: - return "ILS"; - case 0x0368: - return "IQD"; - case 0x0364: - return "IRR"; - case 0x0360: - return "IDR"; - case 0x0356: - return "INR"; - case 0x0352: - return "ISK"; - case 0x0348: - return "HUF"; - case 0x0344: - return "HKD"; - case 0x0340: - return "HNL"; - case 0x0332: - return "HTG"; - case 0x0328: - return "GYD"; - case 0x0324: - return "GNF"; - case 0x0320: - return "GTQ"; - case 0x0292: - return "GIP"; - case 0x0270: - return "GMD"; - case 0x0262: - return "DJF"; - case 0x0242: - return "FJD"; - case 0x0238: - return "FKP"; - case 0x0232: - return "ERN"; - case 0x0230: - return "ETB"; - case 0x0222: - return "SVC"; - case 0x0214: - return "DOP"; - case 0x0208: - return "DKK"; - case 0x0203: - return "CZK"; - case 0x0192: - return "CUP"; - case 0x0191: - return "HRK"; - case 0x0188: - return "CRC"; - case 0x0174: - return "KMF"; - case 0x0170: - return "COP"; - case 0x0156: - return "CNY"; - case 0x0152: - return "CLP"; - case 0x0144: - return "LKR"; - case 0x0136: - return "KYD"; - case 0x0132: - return "CVE"; - case 0x0124: - return "CAD"; - case 0x0116: - return "KHR"; - case 0x0108: - return "BIF"; - case 0x0104: - return "MMK"; - case 0x0096: - return "BND"; - case 0x0090: - return "SBD"; - case 0x0084: - return "BZD"; - case 0x0072: - return "BWP"; - case 0x0068: - return "BOB"; - case 0x0064: - return "BTN"; - case 0x0060: - return "BMD"; - case 0x0052: - return "BBD"; - case 0x0051: - return "AMD"; - case 0x0050: - return "BDT"; - case 0x0048: - return "BHD"; - case 0x0044: - return "BSD"; - case 0x0036: - return "AUD"; - case 0x0032: - return "ARS"; - case 0x0012: - return "DZD"; - case 0x0008: - return "ALL"; - default: - return "UNKNOWN"; - } -} - -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"); - - FuriString* card_number = furi_string_alloc(); - for(uint8_t i = 0; i < app.pan_len; i += 2) { - furi_string_cat_printf(card_number, "%02X%02X ", app.pan[i], app.pan[i + 1]); - } - - // Cut padding 'F' from card number - size_t end = furi_string_search_rchar(card_number, 'F'); - if(end) furi_string_left(card_number, end); - furi_string_cat(parsed_data, card_number); - furi_string_free(card_number); - - furi_string_cat_printf(parsed_data, "\nExp: %02X/%02X", app.exp_month, app.exp_year); - - furi_string_cat_printf(parsed_data, "\nCountry: %s", get_country_name(app.country_code)); - furi_string_cat_printf( - parsed_data, " Currency: %s", get_currency_name(app.currency_code)); - - 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; -} \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_emv_more_info.c b/applications/main/nfc/scenes/nfc_scene_emv_more_info.c index 5825190d1..0cddce20a 100644 --- a/applications/main/nfc/scenes/nfc_scene_emv_more_info.c +++ b/applications/main/nfc/scenes/nfc_scene_emv_more_info.c @@ -9,7 +9,7 @@ enum { }; enum SubmenuIndex { - SubmenuIndexCardInfo, + SubmenuIndexTransactions, SubmenuIndexDynamic, // dynamic indices start here }; @@ -21,8 +21,8 @@ void nfc_scene_emv_more_info_on_enter(void* context) { submenu_add_item( submenu, - "Card info", - SubmenuIndexCardInfo, + "Transactions", + SubmenuIndexTransactions, nfc_protocol_support_common_submenu_callback, nfc); @@ -37,17 +37,17 @@ bool nfc_scene_emv_more_info_on_event(void* context, SceneManagerEvent event) { const EmvData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolEmv); if(event.type == SceneManagerEventTypeCustom) { - TextBox* text_box = nfc->text_box; - furi_string_reset(nfc->text_box_store); - - if(event.event == SubmenuIndexCardInfo) { - nfc_render_emv_data(data, nfc->text_box_store); - text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + 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 + SubmenuIndexCardInfo); + EmvMoreInfoStateItem + SubmenuIndexTransactions); consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { @@ -69,7 +69,6 @@ void nfc_scene_emv_more_info_on_exit(void* context) { NfcApp* nfc = context; // Clear views - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); + widget_reset(nfc->widget); submenu_reset(nfc->submenu); } From 653af9a5cd49ac9fa05f0c4e679a915605ae2295 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov <1042932+wosk@users.noreply.github.com> Date: Mon, 29 Jan 2024 00:07:17 +0000 Subject: [PATCH 21/28] Read PIN tries and transactions counters --- .../helpers/protocol_support/emv/emv_render.c | 17 +++++-- lib/nfc/protocols/emv/emv.c | 1 + lib/nfc/protocols/emv/emv.h | 8 ++++ lib/nfc/protocols/emv/emv_poller.c | 28 ++++------- lib/nfc/protocols/emv/emv_poller.h | 4 ++ lib/nfc/protocols/emv/emv_poller_i.c | 48 ++++++++++++++----- lib/nfc/protocols/emv/emv_poller_i.h | 2 +- targets/f7/api_symbols.csv | 4 +- 8 files changed, 74 insertions(+), 38 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c index 463f7355e..cc8a46efe 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c @@ -91,10 +91,20 @@ void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) { furi_record_close(RECORD_STORAGE); } +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 transaction info\n"); + furi_string_cat_printf(str, "No transactions info\n"); return; } @@ -163,8 +173,5 @@ void nfc_render_emv_extra(const EmvData* data, FuriString* str) { nfc_render_emv_currency(data->emv_application.currency_code, str); nfc_render_emv_country(data->emv_application.country_code, str); nfc_render_emv_application(&data->emv_application, str); - // PIN try - // transactions counter - - //nfc_render_emv_transactions(&data->emv_application, str); + nfc_render_emv_pin_try_counter(data->emv_application.pin_try_counter, str); } diff --git a/lib/nfc/protocols/emv/emv.c b/lib/nfc/protocols/emv/emv.c index 2a6c83101..bbeacffb8 100644 --- a/lib/nfc/protocols/emv/emv.c +++ b/lib/nfc/protocols/emv/emv.c @@ -27,6 +27,7 @@ const NfcDeviceBase nfc_device_emv = { EmvData* emv_alloc() { EmvData* data = malloc(sizeof(EmvData)); data->iso14443_4a_data = iso14443_4a_alloc(); + data->emv_application.pin_try_counter = 0xff; return data; } diff --git a/lib/nfc/protocols/emv/emv.h b/lib/nfc/protocols/emv/emv.h index b565ee334..42aa1a703 100644 --- a/lib/nfc/protocols/emv/emv.h +++ b/lib/nfc/protocols/emv/emv.h @@ -8,15 +8,19 @@ extern "C" { #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 @@ -63,6 +67,7 @@ typedef struct { 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]; @@ -75,6 +80,9 @@ typedef struct { 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; diff --git a/lib/nfc/protocols/emv/emv_poller.c b/lib/nfc/protocols/emv/emv_poller.c index 70051afcd..7907908fd 100644 --- a/lib/nfc/protocols/emv/emv_poller.c +++ b/lib/nfc/protocols/emv/emv_poller.c @@ -96,17 +96,12 @@ static NfcCommand emv_poller_handler_get_processing_options(EmvPoller* instance) if(instance->error == EmvErrorNone) { FURI_LOG_D(TAG, "Get processing options success"); - if(instance->data->emv_application.pan_len > 0) { - instance->state = EmvPollerStateReadSuccess; - } else { - FURI_LOG_D(TAG, "No PAN still. Read SFI files"); - instance->state = EmvPollerStateReadFiles; - } } else { FURI_LOG_E(TAG, "Failed to get processing options"); - instance->state = EmvPollerStateReadFiles; } + // Read another informations + instance->state = EmvPollerStateReadFiles; return NfcCommandContinue; } @@ -115,10 +110,7 @@ static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) { if(instance->error == EmvErrorNone) { FURI_LOG_D(TAG, "Read files success"); - if(instance->data->emv_application.log_sfi) - instance->state = EmvPollerStateReadLogs; - else - instance->state = EmvPollerStateReadSuccess; + instance->state = EmvPollerStateReadExtra; } else { FURI_LOG_E(TAG, "Failed to read files"); instance->state = EmvPollerStateReadFailed; @@ -127,14 +119,10 @@ static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) { return NfcCommandContinue; } -static NfcCommand emv_poller_handler_read_logs(EmvPoller* instance) { - instance->error = emv_poller_read_log_entry(instance); - - if(instance->error == EmvErrorNone) { - FURI_LOG_D(TAG, "Log entries had been read"); - } else { - FURI_LOG_D(TAG, "No log entry"); - } +static NfcCommand emv_poller_handler_read_extra_data(EmvPoller* instance) { + emv_poller_read_log_entry(instance); + emv_poller_get_last_online_atc(instance); + emv_poller_get_pin_try_counter(instance); instance->state = EmvPollerStateReadSuccess; return NfcCommandContinue; @@ -163,7 +151,7 @@ static const EmvPollerReadHandler emv_poller_read_handler[EmvPollerStateNum] = { [EmvPollerStateSelectApplication] = emv_poller_handler_select_application, [EmvPollerStateGetProcessingOptions] = emv_poller_handler_get_processing_options, [EmvPollerStateReadFiles] = emv_poller_handler_read_files, - [EmvPollerStateReadLogs] = emv_poller_handler_read_logs, + [EmvPollerStateReadExtra] = emv_poller_handler_read_extra_data, [EmvPollerStateReadFailed] = emv_poller_handler_read_fail, [EmvPollerStateReadSuccess] = emv_poller_handler_read_success, }; diff --git a/lib/nfc/protocols/emv/emv_poller.h b/lib/nfc/protocols/emv/emv_poller.h index c2335bfa4..64bd0be9d 100644 --- a/lib/nfc/protocols/emv/emv_poller.h +++ b/lib/nfc/protocols/emv/emv_poller.h @@ -50,6 +50,10 @@ 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 \ No newline at end of file diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c index 99e7c9759..7288c473c 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.c +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -249,8 +249,15 @@ static bool 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: - app->trans[app->active_tr].atc = (buff[i] << 8 | buff[i + 1]); + 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: @@ -273,6 +280,11 @@ static bool 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; } @@ -616,29 +628,28 @@ EmvError emv_poller_read_afl(EmvPoller* instance) { return error; } -static EmvError emv_poller_get_log_format(EmvPoller* instance) { +static EmvError emv_poller_req_get_data(EmvPoller* instance, uint16_t tag) { EmvError error = EmvErrorNone; - const uint8_t cla_ins[] = {0x80, 0xCA}; - bit_buffer_reset(instance->tx_buffer); bit_buffer_reset(instance->rx_buffer); - bit_buffer_copy_bytes(instance->tx_buffer, cla_ins, sizeof(cla_ins)); - bit_buffer_append_byte(instance->tx_buffer, EMV_TAG_LOG_FMT >> 8); - bit_buffer_append_byte(instance->tx_buffer, EMV_TAG_LOG_FMT & 0xFF); + 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 log format"); + 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 format answer:"); + emv_trace(instance, "Get log data answer:"); if(iso14443_4a_error != Iso14443_4aErrorNone) { - FURI_LOG_E(TAG, "Failed to get log format, error %u", iso14443_4a_error); + FURI_LOG_E(TAG, "Failed to get data, error %u", iso14443_4a_error); error = emv_process_error(iso14443_4a_error); break; } @@ -650,21 +661,35 @@ static EmvError emv_poller_get_log_format(EmvPoller* instance) { bit_buffer_get_size_bytes(instance->rx_buffer), &instance->data->emv_application)) { error = EmvErrorProtocol; - FURI_LOG_E(TAG, "Failed to parse log format"); + 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; @@ -694,5 +719,6 @@ EmvError emv_poller_read_log_entry(EmvPoller* instance) { COUNT_OF(instance->data->emv_application.trans)); } + instance->data->emv_application.saving_trans_list = false; return error; } diff --git a/lib/nfc/protocols/emv/emv_poller_i.h b/lib/nfc/protocols/emv/emv_poller_i.h index 554560a25..620d2f359 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.h +++ b/lib/nfc/protocols/emv/emv_poller_i.h @@ -14,7 +14,7 @@ typedef enum { EmvPollerStateSelectApplication, EmvPollerStateGetProcessingOptions, EmvPollerStateReadFiles, - EmvPollerStateReadLogs, + EmvPollerStateReadExtra, EmvPollerStateReadFailed, EmvPollerStateReadSuccess, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f53c4d0ca..948f957b5 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,52.1,, +Version,+,52.3,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -887,6 +887,8 @@ 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* From 1165e25f0030f151e029936dbc2c5b2755930366 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov <1042932+wosk@users.noreply.github.com> Date: Mon, 29 Jan 2024 13:48:33 +0000 Subject: [PATCH 22/28] Read all files --- lib/nfc/protocols/emv/emv_poller_i.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c index 7288c473c..2fbae4fc4 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.c +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -619,10 +619,7 @@ EmvError emv_poller_read_afl(EmvPoller* instance) { error = EmvErrorProtocol; FURI_LOG_T(TAG, "Failed to parse SFI 0x%X record %d", sfi, record); } - if(instance->data->emv_application.pan_len != 0) - return EmvErrorNone; // Card number fetched } - error = EmvErrorProtocol; } return error; From 3612814a18e4ceaa85d3f2ac5b10e95b7d46433c Mon Sep 17 00:00:00 2001 From: Methodius Date: Mon, 29 Jan 2024 23:12:17 +0900 Subject: [PATCH 23/28] back to parser --- applications/main/nfc/application.fam | 10 +- .../nfc/helpers/protocol_support/emv/emv.c | 2 +- .../helpers/protocol_support/emv/emv_render.c | 9 +- .../main/nfc/plugins/supported_cards/emv.c | 131 ++++++++++++++++++ lib/nfc/helpers/iso14443_4_layer.c | 13 +- lib/nfc/protocols/nfc_listener_defs.c | 1 + 6 files changed, 155 insertions(+), 11 deletions(-) create mode 100644 applications/main/nfc/plugins/supported_cards/emv.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 86eefe620..569c680eb 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -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"], + sources=["plugins/supported_cards/emv.c"], +) + App( appid="ndef_parser", apptype=FlipperAppType.PLUGIN, @@ -182,7 +191,6 @@ App( sources=["plugins/supported_cards/ndef.c"], ) - App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv.c b/applications/main/nfc/helpers/protocol_support/emv/emv.c index e543291cc..0b60bea6e 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv.c @@ -75,7 +75,7 @@ static void nfc_scene_read_success_on_enter_emv(NfcApp* instance) { // } const NfcProtocolSupportBase nfc_protocol_support_emv = { - .features = NfcProtocolFeatureMoreInfo, + .features = NfcProtocolFeatureNone, .scene_info = { diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c index cc8a46efe..2d76a3fb9 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c @@ -4,11 +4,12 @@ #include "nfc/nfc_app_i.h" void nfc_render_emv_info(const EmvData* data, NfcProtocolFormatType format_type, FuriString* 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); + nfc_render_iso14443_4a_info(data->iso14443_4a_data, format_type, 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) nfc_render_emv_extra(data, str); + // if(format_type == NfcProtocolFormatTypeFull) nfc_render_emv_extra(data, str); } void nfc_render_emv_data(const EmvData* data, FuriString* str) { diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c new file mode 100644 index 000000000..99842f2d6 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/emv.c @@ -0,0 +1,131 @@ +/* + * Parser for EMV cards. + * + * Copyright 2023 Leptoptilos + * + * 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 . + */ +#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 + +#include +#include + +#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"); + + 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); + + furi_string_cat_printf(parsed_data, "\nExp: %02X/%02X", 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, "\nCountry: %s", furi_string_get_cstr(str)); + + storage_readed = emv_get_currency_name(app.currency_code, str); + if(storage_readed) + furi_string_cat_printf(parsed_data, "\nCurrency: %s", furi_string_get_cstr(str)); + + if(app.pin_try_counter != 0xFF) + furi_string_cat_printf(str, "\nPIN 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; +} \ No newline at end of file diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 7f0f0a25e..b21e22423 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -3,15 +3,18 @@ #include #define ISO14443_4_BLOCK_PCB (1U << 1) -#define ISO14443_4_BLOCK_PCB_I (0U << 6) -#define ISO14443_4_BLOCK_PCB_R (2U << 6) +#define ISO14443_4_BLOCK_PCB_I (0U) +#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 (1U << 1) + #define ISO14443_4_BLOCK_PCB_NAD (1U << 2) #define ISO14443_4_BLOCK_PCB_CID (1U << 3) #define ISO14443_4_BLOCK_PCB_CHAINING (1U << 4) @@ -85,7 +88,7 @@ Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( 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: + case ISO14443_4_BLOCK_PCB_I_: if(pcb_field == instance->pcb_prev) { bit_buffer_copy_right(output_data, block_data, 1); ret = Iso14443_4aErrorNone; @@ -94,7 +97,7 @@ Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( ret = Iso14443_4aErrorSendExtra; } break; - case ISO14443_4_BLOCK_PCB_R: + case ISO14443_4_BLOCK_PCB_R_: // TODO break; case ISO14443_4_BLOCK_PCB_S: diff --git a/lib/nfc/protocols/nfc_listener_defs.c b/lib/nfc/protocols/nfc_listener_defs.c index 2a6167e9c..ecfe98c10 100644 --- a/lib/nfc/protocols/nfc_listener_defs.c +++ b/lib/nfc/protocols/nfc_listener_defs.c @@ -20,4 +20,5 @@ const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolSlix] = &nfc_listener_slix, [NfcProtocolSt25tb] = NULL, [NfcProtocolFelica] = &nfc_listener_felica, + [NfcProtocolEmv] = NULL, }; From a15312e0528bce3a01afa068b09173962ce7fb3c Mon Sep 17 00:00:00 2001 From: Methodius Date: Tue, 30 Jan 2024 00:29:06 +0900 Subject: [PATCH 24/28] parser fix --- applications/main/nfc/application.fam | 4 +- .../helpers/protocol_support/emv/emv_render.c | 74 ++++++++++--------- .../helpers/protocol_support/emv/emv_render.h | 6 +- .../main/nfc/plugins/supported_cards/emv.c | 8 +- 4 files changed, 50 insertions(+), 42 deletions(-) diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 569c680eb..ab9690ec1 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -178,8 +178,8 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="emv_plugin_ep", targets=["f7"], - requires=["nfc"], - sources=["plugins/supported_cards/emv.c"], + requires=["nfc", "storage"], + sources=["plugins/supported_cards/emv.c", "helpers/nfc_emv_parser.c"], ) App( diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c index 2d76a3fb9..c1320a077 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c @@ -4,12 +4,41 @@ #include "nfc/nfc_app_i.h" void nfc_render_emv_info(const EmvData* data, NfcProtocolFormatType format_type, FuriString* str) { - nfc_render_iso14443_4a_info(data->iso14443_4a_data, format_type, 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); + 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); + 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) { @@ -42,31 +71,13 @@ void nfc_render_emv_expired(const EmvApplication* apl, FuriString* str) { void nfc_render_emv_currency(uint16_t cur_code, FuriString* str) { if(!cur_code) return; - Storage* storage = furi_record_open(RECORD_STORAGE); - FuriString* currency_name = furi_string_alloc(); - if(nfc_emv_parser_get_currency_name(storage, cur_code, currency_name)) { - furi_string_cat_printf(str, "Currency: %s\n", furi_string_get_cstr(currency_name)); - } - furi_string_free(currency_name); - furi_record_close(RECORD_STORAGE); + 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; - Storage* storage = furi_record_open(RECORD_STORAGE); - FuriString* country_name = furi_string_alloc(); - if(nfc_emv_parser_get_country_name(storage, country_code, country_name)) { - furi_string_cat_printf(str, "Country: %s\n", furi_string_get_cstr(country_name)); - } - furi_string_free(country_name); - furi_record_close(RECORD_STORAGE); -} -void nfc_render_emv_name(const char* data, FuriString* str) { - 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, "Country code: %04X\n", country_code); } void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) { @@ -78,18 +89,10 @@ void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) { } furi_string_cat_printf(str, "AID: "); - Storage* storage = furi_record_open(RECORD_STORAGE); - FuriString* aid_name = furi_string_alloc(); - if(nfc_emv_parser_get_aid_name(storage, apl->aid, len, aid_name)) { - furi_string_cat_printf(str, "%s", furi_string_get_cstr(aid_name)); - } else { - 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_free(aid_name); - furi_record_close(RECORD_STORAGE); } static void nfc_render_emv_pin_try_counter(uint8_t counter, FuriString* str) { @@ -171,8 +174,9 @@ void nfc_render_emv_transactions(const EmvApplication* apl, FuriString* str) { } 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_application(&data->emv_application, str); nfc_render_emv_pin_try_counter(data->emv_application.pin_try_counter, str); } diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.h b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h index 80f80d423..855acdc4a 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv_render.h +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h @@ -23,4 +23,8 @@ 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); \ No newline at end of file +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); \ No newline at end of file diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c index 99842f2d6..f0bdded4a 100644 --- a/applications/main/nfc/plugins/supported_cards/emv.c +++ b/applications/main/nfc/plugins/supported_cards/emv.c @@ -89,20 +89,20 @@ static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_cat(parsed_data, pan); furi_string_free(pan); - furi_string_cat_printf(parsed_data, "\nExp: %02X/%02X", 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, "\nCountry: %s", furi_string_get_cstr(str)); + 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, "\nCurrency: %s", furi_string_get_cstr(str)); + furi_string_cat_printf(parsed_data, "Currency: %s\n", furi_string_get_cstr(str)); if(app.pin_try_counter != 0xFF) - furi_string_cat_printf(str, "\nPIN try left: %d\n", app.pin_try_counter); + furi_string_cat_printf(str, "PIN try left: %d\n", app.pin_try_counter); parsed = true; } while(false); From fee4a5a8f76559b812a443fadb256059c2297ec1 Mon Sep 17 00:00:00 2001 From: Methodius Date: Tue, 30 Jan 2024 02:22:21 +0900 Subject: [PATCH 25/28] EMV save/load dump options added --- .../main/nfc/plugins/supported_cards/emv.c | 4 +- lib/nfc/protocols/emv/emv.c | 89 +++++++++++++++++-- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 2 +- 4 files changed, 86 insertions(+), 11 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c index f0bdded4a..d2cd8c4a7 100644 --- a/applications/main/nfc/plugins/supported_cards/emv.c +++ b/applications/main/nfc/plugins/supported_cards/emv.c @@ -101,8 +101,8 @@ static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) { 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(str, "PIN try left: %d\n", app.pin_try_counter); + // 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); diff --git a/lib/nfc/protocols/emv/emv.c b/lib/nfc/protocols/emv/emv.c index bbeacffb8..4cdacaefe 100644 --- a/lib/nfc/protocols/emv/emv.c +++ b/lib/nfc/protocols/emv/emv.c @@ -1,5 +1,6 @@ //#include "emv_i.h" +#include "flipper_format.h" #include #include "protocols/emv/emv.h" #include @@ -65,19 +66,93 @@ bool emv_verify(EmvData* data, const FuriString* device_type) { bool emv_load(EmvData* data, FlipperFormat* ff, uint32_t version) { furi_assert(data); - UNUSED(data); - UNUSED(ff); - UNUSED(version); - return false; + 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); - UNUSED(data); - UNUSED(ff); - return false; + 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) { diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 56e47318e..add27f40a 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,50.2,, +Version,+,53.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 948f957b5..6923b79da 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,52.3,, +Version,+,53.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, From 92a25af3c3d4b9831c53d49c995f00ad16c0b2e9 Mon Sep 17 00:00:00 2001 From: Methodius Date: Tue, 30 Jan 2024 02:37:59 +0900 Subject: [PATCH 26/28] minor parser fixes --- .../main/nfc/plugins/supported_cards/emv.c | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c index d2cd8c4a7..ebcc392c8 100644 --- a/applications/main/nfc/plugins/supported_cards/emv.c +++ b/applications/main/nfc/plugins/supported_cards/emv.c @@ -78,18 +78,21 @@ static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) { else furi_string_cat_printf(parsed_data, "\e#%s\n", "EMV"); - 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]); + 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); } - // 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); - - furi_string_cat_printf(parsed_data, "\nExp: %02X/%02X\n", app.exp_month, app.exp_year); + 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); @@ -101,8 +104,8 @@ static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) { 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); + 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); From 51d8b18f3eaa627e2c9f3c869a6079fc13dd45f6 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov <1042932+wosk@users.noreply.github.com> Date: Tue, 30 Jan 2024 00:26:16 +0000 Subject: [PATCH 27/28] Read SFI until PAN find * get rid of input result buffers --- lib/nfc/protocols/emv/emv_poller.c | 22 ++++--------------- lib/nfc/protocols/emv/emv_poller_i.c | 6 +++++ lib/nfc/protocols/emv/emv_poller_i.h | 2 -- .../iso14443_4a/iso14443_4a_poller_i.c | 6 +++++ 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/lib/nfc/protocols/emv/emv_poller.c b/lib/nfc/protocols/emv/emv_poller.c index 7907908fd..6ca21df1c 100644 --- a/lib/nfc/protocols/emv/emv_poller.c +++ b/lib/nfc/protocols/emv/emv_poller.c @@ -6,9 +6,8 @@ #define TAG "EMVPoller" -// SKOLKO????????????????????????????????????????????????????????????????? +// MAX Le is 255 bytes + 2 for CRC #define EMV_BUF_SIZE (512U) -#define EMV_RESULT_BUF_SIZE (512U) typedef NfcCommand (*EmvPollerReadHandler)(EmvPoller* instance); @@ -24,8 +23,6 @@ static EmvPoller* emv_poller_alloc(Iso14443_4aPoller* 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->input_buffer = bit_buffer_alloc(EMV_BUF_SIZE); - instance->result_buffer = bit_buffer_alloc(EMV_RESULT_BUF_SIZE); instance->state = EmvPollerStateIdle; @@ -44,14 +41,10 @@ static void emv_poller_free(EmvPoller* instance) { emv_free(instance->data); bit_buffer_free(instance->tx_buffer); bit_buffer_free(instance->rx_buffer); - bit_buffer_free(instance->input_buffer); - bit_buffer_free(instance->result_buffer); free(instance); } static NfcCommand emv_poller_handler_idle(EmvPoller* instance) { - bit_buffer_reset(instance->input_buffer); - bit_buffer_reset(instance->result_buffer); bit_buffer_reset(instance->tx_buffer); bit_buffer_reset(instance->rx_buffer); @@ -106,21 +99,14 @@ static NfcCommand emv_poller_handler_get_processing_options(EmvPoller* instance) } static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) { - instance->error = emv_poller_read_afl(instance); - - if(instance->error == EmvErrorNone) { - FURI_LOG_D(TAG, "Read files success"); - instance->state = EmvPollerStateReadExtra; - } else { - FURI_LOG_E(TAG, "Failed to read files"); - instance->state = EmvPollerStateReadFailed; - } + 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_read_log_entry(instance); emv_poller_get_last_online_atc(instance); emv_poller_get_pin_try_counter(instance); diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c index 2fbae4fc4..c237125b2 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.c +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -619,6 +619,12 @@ EmvError emv_poller_read_afl(EmvPoller* instance) { 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 } } diff --git a/lib/nfc/protocols/emv/emv_poller_i.h b/lib/nfc/protocols/emv/emv_poller_i.h index 620d2f359..704365747 100644 --- a/lib/nfc/protocols/emv/emv_poller_i.h +++ b/lib/nfc/protocols/emv/emv_poller_i.h @@ -35,8 +35,6 @@ struct EmvPoller { EmvData* data; BitBuffer* tx_buffer; BitBuffer* rx_buffer; - BitBuffer* input_buffer; - BitBuffer* result_buffer; EmvPollerEventData emv_event_data; EmvPollerEvent emv_event; NfcGenericEvent general_event; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c index b8e2ebda6..2065b81a2 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -103,6 +103,12 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext( 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; From 872987f002bdd5077dd335783597dbb2ebb1fa69 Mon Sep 17 00:00:00 2001 From: Methodius Date: Tue, 30 Jan 2024 13:50:57 +0900 Subject: [PATCH 28/28] Protocol featore: more info --- applications/main/nfc/helpers/protocol_support/emv/emv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv.c b/applications/main/nfc/helpers/protocol_support/emv/emv.c index 0b60bea6e..e543291cc 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv.c @@ -75,7 +75,7 @@ static void nfc_scene_read_success_on_enter_emv(NfcApp* instance) { // } const NfcProtocolSupportBase nfc_protocol_support_emv = { - .features = NfcProtocolFeatureNone, + .features = NfcProtocolFeatureMoreInfo, .scene_info = {