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*