Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ClusterM/flipperzero-firmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/nfc/protocols')
-rw-r--r--lib/nfc/protocols/crypto1.c75
-rw-r--r--lib/nfc/protocols/crypto1.h23
-rw-r--r--lib/nfc/protocols/emv.c429
-rwxr-xr-xlib/nfc/protocols/emv.h87
-rw-r--r--lib/nfc/protocols/mifare_classic.c989
-rw-r--r--lib/nfc/protocols/mifare_classic.h145
-rw-r--r--lib/nfc/protocols/mifare_common.c17
-rw-r--r--lib/nfc/protocols/mifare_common.h12
-rw-r--r--lib/nfc/protocols/mifare_desfire.c623
-rw-r--r--lib/nfc/protocols/mifare_desfire.h169
-rw-r--r--lib/nfc/protocols/mifare_ultralight.c1791
-rw-r--r--lib/nfc/protocols/mifare_ultralight.h222
-rw-r--r--lib/nfc/protocols/nfc_util.c47
-rw-r--r--lib/nfc/protocols/nfc_util.h11
-rwxr-xr-xlib/nfc/protocols/nfca.c142
-rw-r--r--lib/nfc/protocols/nfca.h28
16 files changed, 4810 insertions, 0 deletions
diff --git a/lib/nfc/protocols/crypto1.c b/lib/nfc/protocols/crypto1.c
new file mode 100644
index 00000000..f08164ba
--- /dev/null
+++ b/lib/nfc/protocols/crypto1.c
@@ -0,0 +1,75 @@
+#include "crypto1.h"
+#include "nfc_util.h"
+#include <furi.h>
+
+// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git
+
+#define SWAPENDIAN(x) (x = (x >> 8 & 0xff00ff) | (x & 0xff00ff) << 8, x = x >> 16 | x << 16)
+#define LF_POLY_ODD (0x29CE5C)
+#define LF_POLY_EVEN (0x870804)
+
+#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24)
+
+void crypto1_reset(Crypto1* crypto1) {
+ furi_assert(crypto1);
+ crypto1->even = 0;
+ crypto1->odd = 0;
+}
+
+void crypto1_init(Crypto1* crypto1, uint64_t key) {
+ furi_assert(crypto1);
+ crypto1->even = 0;
+ crypto1->odd = 0;
+ for(int8_t i = 47; i > 0; i -= 2) {
+ crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7);
+ crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7);
+ }
+}
+
+uint32_t crypto1_filter(uint32_t in) {
+ uint32_t out = 0;
+ out = 0xf22c0 >> (in & 0xf) & 16;
+ out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8;
+ out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4;
+ out |= 0x1e458 >> (in >> 12 & 0xf) & 2;
+ out |= 0x0d938 >> (in >> 16 & 0xf) & 1;
+ return FURI_BIT(0xEC57E80A, out);
+}
+
+uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) {
+ furi_assert(crypto1);
+ uint8_t out = crypto1_filter(crypto1->odd);
+ uint32_t feed = out & (!!is_encrypted);
+ feed ^= !!in;
+ feed ^= LF_POLY_ODD & crypto1->odd;
+ feed ^= LF_POLY_EVEN & crypto1->even;
+ crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed));
+
+ FURI_SWAP(crypto1->odd, crypto1->even);
+ return out;
+}
+
+uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) {
+ furi_assert(crypto1);
+ uint8_t out = 0;
+ for(uint8_t i = 0; i < 8; i++) {
+ out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i;
+ }
+ return out;
+}
+
+uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) {
+ furi_assert(crypto1);
+ uint32_t out = 0;
+ for(uint8_t i = 0; i < 32; i++) {
+ out |= crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i);
+ }
+ return out;
+}
+
+uint32_t prng_successor(uint32_t x, uint32_t n) {
+ SWAPENDIAN(x);
+ while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31;
+
+ return SWAPENDIAN(x);
+}
diff --git a/lib/nfc/protocols/crypto1.h b/lib/nfc/protocols/crypto1.h
new file mode 100644
index 00000000..07b39c22
--- /dev/null
+++ b/lib/nfc/protocols/crypto1.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef struct {
+ uint32_t odd;
+ uint32_t even;
+} Crypto1;
+
+void crypto1_reset(Crypto1* crypto1);
+
+void crypto1_init(Crypto1* crypto1, uint64_t key);
+
+uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted);
+
+uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted);
+
+uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted);
+
+uint32_t crypto1_filter(uint32_t in);
+
+uint32_t prng_successor(uint32_t x, uint32_t n);
diff --git a/lib/nfc/protocols/emv.c b/lib/nfc/protocols/emv.c
new file mode 100644
index 00000000..935c9f63
--- /dev/null
+++ b/lib/nfc/protocols/emv.c
@@ -0,0 +1,429 @@
+#include "emv.h"
+
+#include <core/common_defines.h>
+
+#define TAG "Emv"
+
+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,
+};
+
+static const uint8_t select_ppse_ans[] = {0x6F, 0x29, 0x84, 0x0E, 0x32, 0x50, 0x41, 0x59, 0x2E,
+ 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31,
+ 0xA5, 0x17, 0xBF, 0x0C, 0x14, 0x61, 0x12, 0x4F, 0x07,
+ 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x10, 0x50, 0x04,
+ 0x56, 0x49, 0x53, 0x41, 0x87, 0x01, 0x01, 0x90, 0x00};
+static const uint8_t select_app_ans[] = {0x6F, 0x20, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03,
+ 0x10, 0x10, 0xA5, 0x15, 0x50, 0x04, 0x56, 0x49, 0x53,
+ 0x41, 0x9F, 0x38, 0x0C, 0x9F, 0x66, 0x04, 0x9F, 0x02,
+ 0x06, 0x9F, 0x37, 0x04, 0x5F, 0x2A, 0x02, 0x90, 0x00};
+static const uint8_t pdol_ans[] = {0x77, 0x40, 0x82, 0x02, 0x20, 0x00, 0x57, 0x13, 0x55, 0x70,
+ 0x73, 0x83, 0x85, 0x87, 0x73, 0x31, 0xD1, 0x80, 0x22, 0x01,
+ 0x38, 0x84, 0x77, 0x94, 0x00, 0x00, 0x1F, 0x5F, 0x34, 0x01,
+ 0x00, 0x9F, 0x10, 0x07, 0x06, 0x01, 0x11, 0x03, 0x80, 0x00,
+ 0x00, 0x9F, 0x26, 0x08, 0x7A, 0x65, 0x7F, 0xD3, 0x52, 0x96,
+ 0xC9, 0x85, 0x9F, 0x27, 0x01, 0x00, 0x9F, 0x36, 0x02, 0x06,
+ 0x0C, 0x9F, 0x6C, 0x02, 0x10, 0x00, 0x90, 0x00};
+
+static void emv_trace(FuriHalNfcTxRxContext* tx_rx, const char* message) {
+ if(furi_log_get_level() == FuriLogLevelTrace) {
+ FURI_LOG_T(TAG, "%s", message);
+ printf("TX: ");
+ for(size_t i = 0; i < tx_rx->tx_bits / 8; i++) {
+ printf("%02X ", tx_rx->tx_data[i]);
+ }
+ printf("\r\nRX: ");
+ for(size_t i = 0; i < tx_rx->rx_bits / 8; i++) {
+ printf("%02X ", tx_rx->rx_data[i]);
+ }
+ printf("\r\n");
+ }
+}
+
+static bool emv_decode_response(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_CARD_NUM: // Track 2 Equivalent Data. 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;
+ break;
+ }
+ }
+ success = true;
+ FURI_LOG_T(
+ TAG,
+ "found EMV_TAG_CARD_NUM %x (len=%d)",
+ EMV_TAG_CARD_NUM,
+ app->card_number_len);
+ 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;
+}
+
+bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) {
+ bool app_aid_found = false;
+ 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
+ };
+
+ memcpy(tx_rx->tx_data, emv_select_ppse_cmd, sizeof(emv_select_ppse_cmd));
+ tx_rx->tx_bits = sizeof(emv_select_ppse_cmd) * 8;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+
+ FURI_LOG_D(TAG, "Send select PPSE");
+ if(furi_hal_nfc_tx_rx(tx_rx, 300)) {
+ emv_trace(tx_rx, "Select PPSE answer:");
+ if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) {
+ app_aid_found = true;
+ } else {
+ FURI_LOG_E(TAG, "Failed to parse application");
+ }
+ } else {
+ FURI_LOG_E(TAG, "Failed select PPSE");
+ }
+
+ return app_aid_found;
+}
+
+bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) {
+ bool select_app_success = false;
+ const uint8_t emv_select_header[] = {
+ 0x00,
+ 0xA4, // SELECT application
+ 0x04,
+ 0x00 // P1:By name, P2:First or only occurence
+ };
+ uint16_t size = sizeof(emv_select_header);
+
+ // Copy header
+ memcpy(tx_rx->tx_data, emv_select_header, size);
+ // Copy AID
+ tx_rx->tx_data[size++] = app->aid_len;
+ memcpy(&tx_rx->tx_data[size], app->aid, app->aid_len);
+ size += app->aid_len;
+ tx_rx->tx_data[size++] = 0x00;
+ tx_rx->tx_bits = size * 8;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+
+ FURI_LOG_D(TAG, "Start application");
+ if(furi_hal_nfc_tx_rx(tx_rx, 300)) {
+ emv_trace(tx_rx, "Start application answer:");
+ if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) {
+ select_app_success = true;
+ } else {
+ FURI_LOG_E(TAG, "Failed to read PAN or PDOL");
+ }
+ } else {
+ FURI_LOG_E(TAG, "Failed to start application");
+ }
+
+ return select_app_success;
+}
+
+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_get_processing_options(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) {
+ bool card_num_read = false;
+ const uint8_t emv_gpo_header[] = {0x80, 0xA8, 0x00, 0x00};
+ uint16_t size = sizeof(emv_gpo_header);
+
+ // Copy header
+ memcpy(tx_rx->tx_data, emv_gpo_header, size);
+ APDU pdol_data = {0, {0}};
+ // Prepare and copy pdol parameters
+ emv_prepare_pdol(&pdol_data, &app->pdol);
+ tx_rx->tx_data[size++] = 0x02 + pdol_data.size;
+ tx_rx->tx_data[size++] = 0x83;
+ tx_rx->tx_data[size++] = pdol_data.size;
+ memcpy(tx_rx->tx_data + size, pdol_data.data, pdol_data.size);
+ size += pdol_data.size;
+ tx_rx->tx_data[size++] = 0;
+ tx_rx->tx_bits = size * 8;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+
+ FURI_LOG_D(TAG, "Get proccessing options");
+ if(furi_hal_nfc_tx_rx(tx_rx, 300)) {
+ emv_trace(tx_rx, "Get processing options answer:");
+ if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) {
+ if(app->card_number_len > 0) {
+ card_num_read = true;
+ }
+ }
+ } else {
+ FURI_LOG_E(TAG, "Failed to get processing options");
+ }
+
+ return card_num_read;
+}
+
+static bool emv_read_sfi_record(
+ FuriHalNfcTxRxContext* tx_rx,
+ EmvApplication* app,
+ uint8_t sfi,
+ uint8_t record_num) {
+ bool card_num_read = false;
+ 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
+ };
+
+ memcpy(tx_rx->tx_data, emv_sfi_header, sizeof(emv_sfi_header));
+ tx_rx->tx_bits = sizeof(emv_sfi_header) * 8;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+
+ if(furi_hal_nfc_tx_rx(tx_rx, 300)) {
+ emv_trace(tx_rx, "SFI record:");
+ if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) {
+ card_num_read = true;
+ }
+ } else {
+ FURI_LOG_E(TAG, "Failed to read SFI record %d", record_num);
+ }
+
+ return card_num_read;
+}
+
+static bool emv_read_files(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) {
+ bool card_num_read = false;
+
+ if(app->afl.size == 0) {
+ return false;
+ }
+
+ FURI_LOG_D(TAG, "Search PAN in SFI");
+ // Iterate through all files
+ for(size_t i = 0; i < app->afl.size; i += 4) {
+ uint8_t sfi = app->afl.data[i] >> 3;
+ uint8_t record_start = app->afl.data[i + 1];
+ uint8_t record_end = app->afl.data[i + 2];
+ // Iterate through all records in file
+ for(uint8_t record = record_start; record <= record_end; ++record) {
+ card_num_read |= emv_read_sfi_record(tx_rx, app, sfi, record);
+ }
+ }
+
+ return card_num_read;
+}
+
+bool emv_search_application(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) {
+ furi_assert(tx_rx);
+ furi_assert(emv_app);
+ memset(emv_app, 0, sizeof(EmvApplication));
+
+ return emv_select_ppse(tx_rx, emv_app);
+}
+
+bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) {
+ furi_assert(tx_rx);
+ furi_assert(emv_app);
+ bool card_num_read = false;
+ memset(emv_app, 0, sizeof(EmvApplication));
+
+ do {
+ if(!emv_select_ppse(tx_rx, emv_app)) break;
+ if(!emv_select_app(tx_rx, emv_app)) break;
+ if(emv_get_processing_options(tx_rx, emv_app)) {
+ card_num_read = true;
+ } else {
+ card_num_read = emv_read_files(tx_rx, emv_app);
+ }
+ } while(false);
+
+ return card_num_read;
+}
+
+bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx) {
+ furi_assert(tx_rx);
+ bool emulation_complete = false;
+ tx_rx->tx_bits = 0;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+
+ do {
+ FURI_LOG_D(TAG, "Read select PPSE command");
+ if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
+
+ memcpy(tx_rx->tx_data, select_ppse_ans, sizeof(select_ppse_ans));
+ tx_rx->tx_bits = sizeof(select_ppse_ans) * 8;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+ FURI_LOG_D(TAG, "Send select PPSE answer and read select App command");
+ if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
+
+ memcpy(tx_rx->tx_data, select_app_ans, sizeof(select_app_ans));
+ tx_rx->tx_bits = sizeof(select_app_ans) * 8;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+ FURI_LOG_D(TAG, "Send select App answer and read get PDOL command");
+ if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
+
+ memcpy(tx_rx->tx_data, pdol_ans, sizeof(pdol_ans));
+ tx_rx->tx_bits = sizeof(pdol_ans) * 8;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+ FURI_LOG_D(TAG, "Send get PDOL answer");
+ if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
+
+ emulation_complete = true;
+ } while(false);
+
+ return emulation_complete;
+}
diff --git a/lib/nfc/protocols/emv.h b/lib/nfc/protocols/emv.h
new file mode 100755
index 00000000..b5a0c574
--- /dev/null
+++ b/lib/nfc/protocols/emv.h
@@ -0,0 +1,87 @@
+#pragma once
+
+#include <furi_hal_nfc.h>
+
+#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_CARD_NUM 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 {
+ char name[32];
+ uint8_t aid[16];
+ uint16_t aid_len;
+ uint8_t number[10];
+ uint8_t number_len;
+ uint8_t exp_mon;
+ uint8_t exp_year;
+ uint16_t country_code;
+ uint16_t currency_code;
+} EmvData;
+
+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;
+ 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;
+
+/** Read bank card data
+ * @note Search EMV Application, start it, try to read AID, PAN, card name,
+ * expiration date, currency and country codes
+ *
+ * @param tx_rx FuriHalNfcTxRxContext instance
+ * @param emv_app EmvApplication instance
+ *
+ * @return true on success
+ */
+bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app);
+
+/** Search for EMV Application
+ *
+ * @param tx_rx FuriHalNfcTxRxContext instance
+ * @param emv_app EmvApplication instance
+ *
+ * @return true on success
+ */
+bool emv_search_application(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app);
+
+/** Emulate bank card
+ * @note Answer to application selection and PDOL
+ *
+ * @param tx_rx FuriHalNfcTxRxContext instance
+ *
+ * @return true on success
+ */
+bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx);
diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c
new file mode 100644
index 00000000..93fe6f69
--- /dev/null
+++ b/lib/nfc/protocols/mifare_classic.c
@@ -0,0 +1,989 @@
+#include "mifare_classic.h"
+#include "nfca.h"
+#include "nfc_util.h"
+#include <furi_hal_rtc.h>
+
+// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git
+
+#define TAG "MfClassic"
+
+#define MF_CLASSIC_AUTH_KEY_A_CMD (0x60U)
+#define MF_CLASSIC_AUTH_KEY_B_CMD (0x61U)
+#define MF_CLASSIC_READ_SECT_CMD (0x30)
+
+typedef enum {
+ MfClassicActionDataRead,
+ MfClassicActionDataWrite,
+ MfClassicActionDataInc,
+ MfClassicActionDataDec,
+
+ MfClassicActionKeyARead,
+ MfClassicActionKeyAWrite,
+ MfClassicActionKeyBRead,
+ MfClassicActionKeyBWrite,
+ MfClassicActionACRead,
+ MfClassicActionACWrite,
+} MfClassicAction;
+
+const char* mf_classic_get_type_str(MfClassicType type) {
+ if(type == MfClassicType1k) {
+ return "MIFARE Classic 1K";
+ } else if(type == MfClassicType4k) {
+ return "MIFARE Classic 4K";
+ } else {
+ return "Unknown";
+ }
+}
+
+static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) {
+ furi_assert(sector < 40);
+ if(sector < 32) {
+ return sector * 4;
+ } else {
+ return 32 * 4 + (sector - 32) * 16;
+ }
+}
+
+uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector) {
+ furi_assert(sector < 40);
+ if(sector < 32) {
+ return sector * 4 + 3;
+ } else {
+ return 32 * 4 + (sector - 32) * 16 + 15;
+ }
+}
+
+uint8_t mf_classic_get_sector_by_block(uint8_t block) {
+ if(block < 128) {
+ return (block | 0x03) / 4;
+ } else {
+ return 32 + ((block | 0xf) - 32 * 4) / 16;
+ }
+}
+
+static uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) {
+ furi_assert(sector < 40);
+ return sector < 32 ? 4 : 16;
+}
+
+uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block) {
+ if(block < 128) {
+ return block | 0x03;
+ } else {
+ return block | 0x0f;
+ }
+}
+
+bool mf_classic_is_sector_trailer(uint8_t block) {
+ return block == mf_classic_get_sector_trailer_num_by_block(block);
+}
+
+MfClassicSectorTrailer*
+ mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector) {
+ furi_assert(data);
+ uint8_t sec_tr_block_num = mf_classic_get_sector_trailer_block_num_by_sector(sector);
+ return (MfClassicSectorTrailer*)data->block[sec_tr_block_num].value;
+}
+
+uint8_t mf_classic_get_total_sectors_num(MfClassicType type) {
+ if(type == MfClassicType1k) {
+ return MF_CLASSIC_1K_TOTAL_SECTORS_NUM;
+ } else if(type == MfClassicType4k) {
+ return MF_CLASSIC_4K_TOTAL_SECTORS_NUM;
+ } else {
+ return 0;
+ }
+}
+
+static uint16_t mf_classic_get_total_block_num(MfClassicType type) {
+ if(type == MfClassicType1k) {
+ return 64;
+ } else if(type == MfClassicType4k) {
+ return 256;
+ } else {
+ return 0;
+ }
+}
+
+bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num) {
+ furi_assert(data);
+
+ return (FURI_BIT(data->block_read_mask[block_num / 32], block_num % 32) == 1);
+}
+
+void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data) {
+ furi_assert(data);
+
+ if(mf_classic_is_sector_trailer(block_num)) {
+ memcpy(&data->block[block_num].value[6], &block_data->value[6], 4);
+ } else {
+ memcpy(data->block[block_num].value, block_data->value, MF_CLASSIC_BLOCK_SIZE);
+ }
+ FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32);
+}
+
+bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) {
+ furi_assert(data);
+
+ bool key_found = false;
+ if(key_type == MfClassicKeyA) {
+ key_found = (FURI_BIT(data->key_a_mask, sector_num) == 1);
+ } else if(key_type == MfClassicKeyB) {
+ key_found = (FURI_BIT(data->key_b_mask, sector_num) == 1);
+ }
+
+ return key_found;
+}
+
+void mf_classic_set_key_found(
+ MfClassicData* data,
+ uint8_t sector_num,
+ MfClassicKey key_type,
+ uint64_t key) {
+ furi_assert(data);
+
+ uint8_t key_arr[6] = {};
+ MfClassicSectorTrailer* sec_trailer =
+ mf_classic_get_sector_trailer_by_sector(data, sector_num);
+ nfc_util_num2bytes(key, 6, key_arr);
+ if(key_type == MfClassicKeyA) {
+ memcpy(sec_trailer->key_a, key_arr, sizeof(sec_trailer->key_a));
+ FURI_BIT_SET(data->key_a_mask, sector_num);
+ } else if(key_type == MfClassicKeyB) {
+ memcpy(sec_trailer->key_b, key_arr, sizeof(sec_trailer->key_b));
+ FURI_BIT_SET(data->key_b_mask, sector_num);
+ }
+}
+
+bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num) {
+ furi_assert(data);
+
+ bool sector_read = false;
+ do {
+ if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) break;
+ if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) break;
+ uint8_t start_block = mf_classic_get_first_block_num_of_sector(sector_num);
+ uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num);
+ uint8_t block_read = true;
+ for(size_t i = start_block; i < start_block + total_blocks; i++) {
+ block_read = mf_classic_is_block_read(data, i);
+ if(!block_read) break;
+ }
+ sector_read = block_read;
+ } while(false);
+
+ return sector_read;
+}
+
+void mf_classic_get_read_sectors_and_keys(
+ MfClassicData* data,
+ uint8_t* sectors_read,
+ uint8_t* keys_found) {
+ furi_assert(data);
+ *sectors_read = 0;
+ *keys_found = 0;
+ uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type);
+ for(size_t i = 0; i < sectors_total; i++) {
+ if(mf_classic_is_key_found(data, i, MfClassicKeyA)) {
+ *keys_found += 1;
+ }
+ if(mf_classic_is_key_found(data, i, MfClassicKeyB)) {
+ *keys_found += 1;
+ }
+ uint8_t first_block = mf_classic_get_first_block_num_of_sector(i);
+ uint8_t total_blocks_in_sec = mf_classic_get_blocks_num_in_sector(i);
+ bool blocks_read = true;
+ for(size_t i = first_block; i < first_block + total_blocks_in_sec; i++) {
+ blocks_read = mf_classic_is_block_read(data, i);
+ if(!blocks_read) break;
+ }
+ if(blocks_read) {
+ *sectors_read += 1;
+ }
+ }
+}
+
+static bool mf_classic_is_allowed_access_sector_trailer(
+ MfClassicEmulator* emulator,
+ uint8_t block_num,
+ MfClassicKey key,
+ MfClassicAction action) {
+ uint8_t* sector_trailer = emulator->data.block[block_num].value;
+ uint8_t AC = ((sector_trailer[7] >> 5) & 0x04) | ((sector_trailer[8] >> 2) & 0x02) |
+ ((sector_trailer[8] >> 7) & 0x01);
+ switch(action) {
+ case MfClassicActionKeyARead: {
+ return false;
+ }
+ case MfClassicActionKeyAWrite: {
+ return (
+ (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) ||
+ (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03)));
+ }
+ case MfClassicActionKeyBRead: {
+ return (key == MfClassicKeyA && (AC == 0x00 || AC == 0x02 || AC == 0x01));
+ }
+ case MfClassicActionKeyBWrite: {
+ return (
+ (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) ||
+ (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03)));
+ }
+ case MfClassicActionACRead: {
+ return (
+ (key == MfClassicKeyA) ||
+ (key == MfClassicKeyB && !(AC == 0x00 || AC == 0x02 || AC == 0x01)));
+ }
+ case MfClassicActionACWrite: {
+ return (
+ (key == MfClassicKeyA && (AC == 0x01)) ||
+ (key == MfClassicKeyB && (AC == 0x03 || AC == 0x05)));
+ }
+ default:
+ return false;
+ }
+ return true;
+}
+
+static bool mf_classic_is_allowed_access_data_block(
+ MfClassicEmulator* emulator,
+ uint8_t block_num,
+ MfClassicKey key,
+ MfClassicAction action) {
+ uint8_t* sector_trailer =
+ emulator->data.block[mf_classic_get_sector_trailer_num_by_block(block_num)].value;
+
+ uint8_t sector_block;
+ if(block_num <= 128) {
+ sector_block = block_num & 0x03;
+ } else {
+ sector_block = (block_num & 0x0f) / 5;
+ }
+
+ uint8_t AC;
+ switch(sector_block) {
+ case 0x00: {
+ AC = ((sector_trailer[7] >> 2) & 0x04) | ((sector_trailer[8] << 1) & 0x02) |
+ ((sector_trailer[8] >> 4) & 0x01);
+ break;
+ }
+ case 0x01: {
+ AC = ((sector_trailer[7] >> 3) & 0x04) | ((sector_trailer[8] >> 0) & 0x02) |
+ ((sector_trailer[8] >> 5) & 0x01);
+ break;
+ }
+ case 0x02: {
+ AC = ((sector_trailer[7] >> 4) & 0x04) | ((sector_trailer[8] >> 1) & 0x02) |
+ ((sector_trailer[8] >> 6) & 0x01);
+ break;
+ }
+ default:
+ return false;
+ }
+
+ switch(action) {
+ case MfClassicActionDataRead: {
+ return (
+ (key == MfClassicKeyA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) ||
+ (key == MfClassicKeyB && !(AC == 0x07)));
+ }
+ case MfClassicActionDataWrite: {
+ return (
+ (key == MfClassicKeyA && (AC == 0x00)) ||
+ (key == MfClassicKeyB && (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03)));
+ }
+ case MfClassicActionDataInc: {
+ return (
+ (key == MfClassicKeyA && (AC == 0x00)) ||
+ (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06)));
+ }
+ case MfClassicActionDataDec: {
+ return (
+ (key == MfClassicKeyA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) ||
+ (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06 || AC == 0x01)));
+ }
+ default:
+ return false;
+ }
+
+ return false;
+}
+
+static bool mf_classic_is_allowed_access(
+ MfClassicEmulator* emulator,
+ uint8_t block_num,
+ MfClassicKey key,
+ MfClassicAction action) {
+ if(mf_classic_is_sector_trailer(block_num)) {
+ return mf_classic_is_allowed_access_sector_trailer(emulator, block_num, key, action);
+ } else {
+ return mf_classic_is_allowed_access_data_block(emulator, block_num, key, action);
+ }
+}
+
+bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
+ UNUSED(ATQA1);
+ if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) {
+ return true;
+ } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
+ UNUSED(ATQA1);
+ if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) {
+ return MfClassicType1k;
+ } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) {
+ return MfClassicType4k;
+ }
+ return MfClassicType1k;
+}
+
+bool mf_classic_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK, MfClassicReader* reader) {
+ UNUSED(ATQA1);
+ furi_assert(reader);
+ memset(reader, 0, sizeof(MfClassicReader));
+
+ if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) {
+ reader->type = MfClassicType1k;
+ } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) {
+ reader->type = MfClassicType4k;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+void mf_classic_reader_add_sector(
+ MfClassicReader* reader,
+ uint8_t sector,
+ uint64_t key_a,
+ uint64_t key_b) {
+ furi_assert(reader);
+ furi_assert(sector < MF_CLASSIC_SECTORS_MAX);
+ furi_assert((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY));
+
+ if(reader->sectors_to_read < MF_CLASSIC_SECTORS_MAX) {
+ reader->sector_reader[reader->sectors_to_read].key_a = key_a;
+ reader->sector_reader[reader->sectors_to_read].key_b = key_b;
+ reader->sector_reader[reader->sectors_to_read].sector_num = sector;
+ reader->sectors_to_read++;
+ }
+}
+
+void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector) {
+ furi_assert(auth_ctx);
+ auth_ctx->sector = sector;
+ auth_ctx->key_a = MF_CLASSIC_NO_KEY;
+ auth_ctx->key_b = MF_CLASSIC_NO_KEY;
+}
+
+static bool mf_classic_auth(
+ FuriHalNfcTxRxContext* tx_rx,
+ uint32_t block,
+ uint64_t key,
+ MfClassicKey key_type,
+ Crypto1* crypto) {
+ bool auth_success = false;
+ uint32_t cuid = 0;
+ memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data));
+ memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity));
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+
+ do {
+ if(!furi_hal_nfc_activate_nfca(200, &cuid)) break;
+ if(key_type == MfClassicKeyA) {
+ tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_A_CMD;
+ } else {
+ tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_B_CMD;
+ }
+ tx_rx->tx_data[1] = block;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxNoCrc;
+ tx_rx->tx_bits = 2 * 8;
+ if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break;
+
+ uint32_t nt = (uint32_t)nfc_util_bytes2num(tx_rx->rx_data, 4);
+ crypto1_init(crypto, key);
+ crypto1_word(crypto, nt ^ cuid, 0);
+ uint8_t nr[4] = {};
+ nfc_util_num2bytes(prng_successor(DWT->CYCCNT, 32), 4, nr);
+ for(uint8_t i = 0; i < 4; i++) {
+ tx_rx->tx_data[i] = crypto1_byte(crypto, nr[i], 0) ^ nr[i];
+ tx_rx->tx_parity[0] |=
+ (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nr[i])) & 0x01) << (7 - i));
+ }
+ nt = prng_successor(nt, 32);
+ for(uint8_t i = 4; i < 8; i++) {
+ nt = prng_successor(nt, 8);
+ tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ (nt & 0xff);
+ tx_rx->tx_parity[0] |=
+ (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt & 0xff)) & 0x01)
+ << (7 - i));
+ }
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
+ tx_rx->tx_bits = 8 * 8;
+ if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break;
+ if(tx_rx->rx_bits == 32) {
+ crypto1_word(crypto, 0, 0);
+ auth_success = true;
+ }
+ } while(false);
+
+ return auth_success;
+}
+
+bool mf_classic_authenticate(
+ FuriHalNfcTxRxContext* tx_rx,
+ uint8_t block_num,
+ uint64_t key,
+ MfClassicKey key_type) {
+ furi_assert(tx_rx);
+
+ Crypto1 crypto = {};
+ bool key_found = mf_classic_auth(tx_rx, block_num, key, key_type, &crypto);
+ furi_hal_nfc_sleep();
+ return key_found;
+}
+
+bool mf_classic_auth_attempt(
+ FuriHalNfcTxRxContext* tx_rx,
+ MfClassicAuthContext* auth_ctx,
+ uint64_t key) {
+ furi_assert(tx_rx);
+ furi_assert(auth_ctx);
+ bool found_key = false;
+ bool need_halt = (auth_ctx->key_a == MF_CLASSIC_NO_KEY) &&
+ (auth_ctx->key_b == MF_CLASSIC_NO_KEY);
+
+ Crypto1 crypto;
+ if(auth_ctx->key_a == MF_CLASSIC_NO_KEY) {
+ // Try AUTH with key A
+ if(mf_classic_auth(
+ tx_rx,
+ mf_classic_get_first_block_num_of_sector(auth_ctx->sector),
+ key,
+ MfClassicKeyA,
+ &crypto)) {
+ auth_ctx->key_a = key;
+ found_key = true;
+ }
+ }
+
+ if(need_halt) {
+ furi_hal_nfc_sleep();
+ }
+
+ if(auth_ctx->key_b == MF_CLASSIC_NO_KEY) {
+ // Try AUTH with key B
+ if(mf_classic_auth(
+ tx_rx,
+ mf_classic_get_first_block_num_of_sector(auth_ctx->sector),
+ key,
+ MfClassicKeyB,
+ &crypto)) {
+ auth_ctx->key_b = key;
+ found_key = true;
+ }
+ }
+
+ return found_key;
+}
+
+bool mf_classic_read_block(
+ FuriHalNfcTxRxContext* tx_rx,
+ Crypto1* crypto,
+ uint8_t block_num,
+ MfClassicBlock* block) {
+ furi_assert(tx_rx);
+ furi_assert(crypto);
+ furi_assert(block);
+
+ bool read_block_success = false;
+ uint8_t plain_cmd[4] = {MF_CLASSIC_READ_SECT_CMD, block_num, 0x00, 0x00};
+ nfca_append_crc16(plain_cmd, 2);
+ memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data));
+ memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity));
+
+ for(uint8_t i = 0; i < 4; i++) {
+ tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ plain_cmd[i];
+ tx_rx->tx_parity[0] |=
+ ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_cmd[i])) & 0x01) << (7 - i);
+ }
+ tx_rx->tx_bits = 4 * 9;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
+
+ if(furi_hal_nfc_tx_rx(tx_rx, 50)) {
+ if(tx_rx->rx_bits == 8 * (MF_CLASSIC_BLOCK_SIZE + 2)) {
+ uint8_t block_received[MF_CLASSIC_BLOCK_SIZE + 2];
+ for(uint8_t i = 0; i < MF_CLASSIC_BLOCK_SIZE + 2; i++) {
+ block_received[i] = crypto1_byte(crypto, 0, 0) ^ tx_rx->rx_data[i];
+ }
+ uint16_t crc_calc = nfca_get_crc16(block_received, MF_CLASSIC_BLOCK_SIZE);
+ uint16_t crc_received = (block_received[MF_CLASSIC_BLOCK_SIZE + 1] << 8) |
+ block_received[MF_CLASSIC_BLOCK_SIZE];
+ if(crc_received != crc_calc) {
+ FURI_LOG_E(
+ TAG,
+ "Incorrect CRC while reading block %d. Expected %04X, Received %04X",
+ block_num,
+ crc_received,
+ crc_calc);
+ } else {
+ memcpy(block->value, block_received, MF_CLASSIC_BLOCK_SIZE);
+ read_block_success = true;
+ }
+ }
+ }
+ return read_block_success;
+}
+
+void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num) {
+ furi_assert(tx_rx);
+ furi_assert(data);
+
+ furi_hal_nfc_sleep();
+ bool key_a_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyA);
+ bool key_b_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyB);
+ uint8_t start_block = mf_classic_get_first_block_num_of_sector(sec_num);
+ uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sec_num);
+ MfClassicBlock block_tmp = {};
+ uint64_t key = 0;
+ MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sec_num);
+ Crypto1 crypto = {};
+
+ uint8_t blocks_read = 0;
+ do {
+ if(!key_a_found) break;
+ FURI_LOG_D(TAG, "Try to read blocks with key A");
+ key = nfc_util_bytes2num(sec_tr->key_a, sizeof(sec_tr->key_a));
+ if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto)) break;
+ for(size_t i = start_block; i < start_block + total_blocks; i++) {
+ if(!mf_classic_is_block_read(data, i)) {
+ if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
+ mf_classic_set_block_read(data, i, &block_tmp);
+ blocks_read++;
+ }
+ } else {
+ blocks_read++;
+ }
+ }
+ FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks);
+ } while(false);
+ do {
+ if(blocks_read == total_blocks) break;
+ if(!key_b_found) break;
+ FURI_LOG_D(TAG, "Try to read blocks with key B");
+ key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b));
+ furi_hal_nfc_sleep();
+ if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto)) break;
+ for(size_t i = start_block; i < start_block + total_blocks; i++) {
+ if(!mf_classic_is_block_read(data, i)) {
+ if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
+ mf_classic_set_block_read(data, i, &block_tmp);
+ blocks_read++;
+ }
+ } else {
+ blocks_read++;
+ }
+ }
+ FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks);
+ } while(false);
+}
+
+static bool mf_classic_read_sector_with_reader(
+ FuriHalNfcTxRxContext* tx_rx,
+ Crypto1* crypto,
+ MfClassicSectorReader* sector_reader,
+ MfClassicSector* sector) {
+ furi_assert(tx_rx);
+ furi_assert(sector_reader);
+ furi_assert(sector);
+
+ uint64_t key;
+ MfClassicKey key_type;
+ uint8_t first_block;
+ bool sector_read = false;
+
+ furi_hal_nfc_sleep();
+ do {
+ // Activate card
+ first_block = mf_classic_get_first_block_num_of_sector(sector_reader->sector_num);
+ if(sector_reader->key_a != MF_CLASSIC_NO_KEY) {
+ key = sector_reader->key_a;
+ key_type = MfClassicKeyA;
+ } else if(sector_reader->key_b != MF_CLASSIC_NO_KEY) {
+ key = sector_reader->key_b;
+ key_type = MfClassicKeyB;
+ } else {
+ break;
+ }
+
+ // Auth to first block in sector
+ if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto)) break;
+ sector->total_blocks = mf_classic_get_blocks_num_in_sector(sector_reader->sector_num);
+
+ // Read blocks
+ for(uint8_t i = 0; i < sector->total_blocks; i++) {
+ mf_classic_read_block(tx_rx, crypto, first_block + i, &sector->block[i]);
+ }
+ // Save sector keys in last block
+ if(sector_reader->key_a != MF_CLASSIC_NO_KEY) {
+ nfc_util_num2bytes(
+ sector_reader->key_a, 6, &sector->block[sector->total_blocks - 1].value[0]);
+ }
+ if(sector_reader->key_b != MF_CLASSIC_NO_KEY) {
+ nfc_util_num2bytes(
+ sector_reader->key_b, 6, &sector->block[sector->total_blocks - 1].value[10]);
+ }
+
+ sector_read = true;
+ } while(false);
+
+ return sector_read;
+}
+
+uint8_t mf_classic_read_card(
+ FuriHalNfcTxRxContext* tx_rx,
+ MfClassicReader* reader,
+ MfClassicData* data) {
+ furi_assert(tx_rx);
+ furi_assert(reader);
+ furi_assert(data);
+
+ uint8_t sectors_read = 0;
+ data->type = reader->type;
+ data->key_a_mask = 0;
+ data->key_b_mask = 0;
+ MfClassicSector temp_sector = {};
+ for(uint8_t i = 0; i < reader->sectors_to_read; i++) {
+ if(mf_classic_read_sector_with_reader(
+ tx_rx, &reader->crypto, &reader->sector_reader[i], &temp_sector)) {
+ uint8_t first_block =
+ mf_classic_get_first_block_num_of_sector(reader->sector_reader[i].sector_num);
+ for(uint8_t j = 0; j < temp_sector.total_blocks; j++) {
+ mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]);
+ }
+ if(reader->sector_reader[i].key_a != MF_CLASSIC_NO_KEY) {
+ mf_classic_set_key_found(
+ data,
+ reader->sector_reader[i].sector_num,
+ MfClassicKeyA,
+ reader->sector_reader[i].key_a);
+ }
+ if(reader->sector_reader[i].key_b != MF_CLASSIC_NO_KEY) {
+ mf_classic_set_key_found(
+ data,
+ reader->sector_reader[i].sector_num,
+ MfClassicKeyB,
+ reader->sector_reader[i].key_b);
+ }
+ sectors_read++;
+ }
+ }
+
+ return sectors_read;
+}
+
+uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data) {
+ furi_assert(tx_rx);
+ furi_assert(data);
+
+ uint8_t sectors_read = 0;
+ Crypto1 crypto = {};
+ uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type);
+ uint64_t key_a = 0;
+ uint64_t key_b = 0;
+ MfClassicSectorReader sec_reader = {};
+ MfClassicSector temp_sector = {};
+
+ for(size_t i = 0; i < total_sectors; i++) {
+ MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i);
+ // Load key A
+ if(mf_classic_is_key_found(data, i, MfClassicKeyA)) {
+ sec_reader.key_a = nfc_util_bytes2num(sec_tr->key_a, 6);
+ } else {
+ sec_reader.key_a = MF_CLASSIC_NO_KEY;
+ }
+ // Load key B
+ if(mf_classic_is_key_found(data, i, MfClassicKeyB)) {
+ sec_reader.key_b = nfc_util_bytes2num(sec_tr->key_b, 6);
+ } else {
+ sec_reader.key_b = MF_CLASSIC_NO_KEY;
+ }
+ if((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY)) {
+ sec_reader.sector_num = i;
+ if(mf_classic_read_sector_with_reader(tx_rx, &crypto, &sec_reader, &temp_sector)) {
+ uint8_t first_block = mf_classic_get_first_block_num_of_sector(i);
+ for(uint8_t j = 0; j < temp_sector.total_blocks; j++) {
+ mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]);
+ }
+ sectors_read++;
+ }
+ }
+ }
+ return sectors_read;
+}
+
+void mf_crypto1_decrypt(
+ Crypto1* crypto,
+ uint8_t* encrypted_data,
+ uint16_t encrypted_data_bits,
+ uint8_t* decrypted_data) {
+ if(encrypted_data_bits < 8) {
+ uint8_t decrypted_byte = 0;
+ decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 0)) << 0;
+ decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 1)) << 1;
+ decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 2)) << 2;
+ decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 3)) << 3;
+ decrypted_data[0] = decrypted_byte;
+ } else {
+ for(size_t i = 0; i < encrypted_data_bits / 8; i++) {
+ decrypted_data[i] = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i];
+ }
+ }
+}
+
+void mf_crypto1_encrypt(
+ Crypto1* crypto,
+ uint8_t* keystream,
+ uint8_t* plain_data,
+ uint16_t plain_data_bits,
+ uint8_t* encrypted_data,
+ uint8_t* encrypted_parity) {
+ if(plain_data_bits < 8) {
+ encrypted_data[0] = 0;
+ for(size_t i = 0; i < plain_data_bits; i++) {
+ encrypted_data[0] |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i;
+ }
+ } else {
+ memset(encrypted_parity, 0, plain_data_bits / 8 + 1);
+ for(uint8_t i = 0; i < plain_data_bits / 8; i++) {
+ encrypted_data[i] = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^
+ plain_data[i];
+ encrypted_parity[i / 8] |=
+ (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01)
+ << (7 - (i & 0x0007)));
+ }
+ }
+}
+
+bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx) {
+ furi_assert(emulator);
+ furi_assert(tx_rx);
+ bool command_processed = false;
+ bool is_encrypted = false;
+ uint8_t plain_data[MF_CLASSIC_MAX_DATA_SIZE];
+ MfClassicKey access_key = MfClassicKeyA;
+
+ // Read command
+ while(!command_processed) {
+ if(!is_encrypted) {
+ memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8);
+ } else {
+ if(!furi_hal_nfc_tx_rx(tx_rx, 300)) {
+ FURI_LOG_D(
+ TAG,
+ "Error in tx rx. Tx :%d bits, Rx: %d bits",
+ tx_rx->tx_bits,
+ tx_rx->rx_bits);
+ break;
+ }
+ mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data);
+ }
+
+ if(plain_data[0] == 0x50 && plain_data[1] == 0x00) {
+ FURI_LOG_T(TAG, "Halt received");
+ furi_hal_nfc_listen_sleep();
+ command_processed = true;
+ break;
+ } else if(plain_data[0] == 0x60 || plain_data[0] == 0x61) {
+ uint8_t block = plain_data[1];
+ uint64_t key = 0;
+ uint8_t sector_trailer_block = mf_classic_get_sector_trailer_num_by_block(block);
+ MfClassicSectorTrailer* sector_trailer =
+ (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value;
+ if(plain_data[0] == 0x60) {
+ key = nfc_util_bytes2num(sector_trailer->key_a, 6);
+ access_key = MfClassicKeyA;
+ } else {
+ key = nfc_util_bytes2num(sector_trailer->key_b, 6);
+ access_key = MfClassicKeyB;
+ }
+
+ uint32_t nonce = prng_successor(DWT->CYCCNT, 32);
+ uint8_t nt[4];
+ uint8_t nt_keystream[4];
+ nfc_util_num2bytes(nonce, 4, nt);
+ nfc_util_num2bytes(nonce ^ emulator->cuid, 4, nt_keystream);
+ crypto1_init(&emulator->crypto, key);
+ if(!is_encrypted) {
+ crypto1_word(&emulator->crypto, emulator->cuid ^ nonce, 0);
+ memcpy(tx_rx->tx_data, nt, sizeof(nt));
+ tx_rx->tx_parity[0] = 0;
+ for(size_t i = 0; i < sizeof(nt); i++) {
+ tx_rx->tx_parity[0] |= nfc_util_odd_parity8(nt[i]) << (7 - i);
+ }
+ tx_rx->tx_bits = sizeof(nt) * 8;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
+ } else {
+ mf_crypto1_encrypt(
+ &emulator->crypto,
+ nt_keystream,
+ nt,
+ sizeof(nt) * 8,
+ tx_rx->tx_data,
+ tx_rx->tx_parity);
+ tx_rx->tx_bits = sizeof(nt) * 8;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
+ }
+ if(!furi_hal_nfc_tx_rx(tx_rx, 500)) {
+ FURI_LOG_E(TAG, "Error in NT exchange");
+ command_processed = true;
+ break;
+ }
+
+ if(tx_rx->rx_bits != 64) {
+ FURI_LOG_W(TAG, "Incorrect nr + ar");
+ command_processed = true;
+ break;
+ }
+
+ uint32_t nr = nfc_util_bytes2num(tx_rx->rx_data, 4);
+ uint32_t ar = nfc_util_bytes2num(&tx_rx->rx_data[4], 4);
+
+ FURI_LOG_D(
+ TAG,
+ "%08x key%c block %d nt/nr/ar: %08x %08x %08x",
+ emulator->cuid,
+ access_key == MfClassicKeyA ? 'A' : 'B',
+ sector_trailer_block,
+ nonce,
+ nr,
+ ar);
+
+ crypto1_word(&emulator->crypto, nr, 1);
+ uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0);
+ if(cardRr != prng_successor(nonce, 64)) {
+ FURI_LOG_T(TAG, "Wrong AUTH! %08X != %08X", cardRr, prng_successor(nonce, 64));
+ // Don't send NACK, as tag don't send it
+ command_processed = true;
+ break;
+ }
+
+ uint32_t ans = prng_successor(nonce, 96);
+ uint8_t responce[4] = {};
+ nfc_util_num2bytes(ans, 4, responce);
+ mf_crypto1_encrypt(
+ &emulator->crypto,
+ NULL,
+ responce,
+ sizeof(responce) * 8,
+ tx_rx->tx_data,
+ tx_rx->tx_parity);
+ tx_rx->tx_bits = sizeof(responce) * 8;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
+
+ is_encrypted = true;
+ } else if(is_encrypted && plain_data[0] == 0x30) {
+ uint8_t block = plain_data[1];
+ uint8_t block_data[18] = {};
+ memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE);
+ if(mf_classic_is_sector_trailer(block)) {
+ if(!mf_classic_is_allowed_access(
+ emulator, block, access_key, MfClassicActionKeyARead)) {
+ memset(block_data, 0, 6);
+ }
+ if(!mf_classic_is_allowed_access(
+ emulator, block, access_key, MfClassicActionKeyBRead)) {
+ memset(&block_data[10], 0, 6);
+ }
+ if(!mf_classic_is_allowed_access(
+ emulator, block, access_key, MfClassicActionACRead)) {
+ memset(&block_data[6], 0, 4);
+ }
+ } else {
+ if(!mf_classic_is_allowed_access(
+ emulator, block, access_key, MfClassicActionDataRead)) {
+ memset(block_data, 0, 16);
+ }
+ }
+ nfca_append_crc16(block_data, 16);
+
+ mf_crypto1_encrypt(
+ &emulator->crypto,
+ NULL,
+ block_data,
+ sizeof(block_data) * 8,
+ tx_rx->tx_data,
+ tx_rx->tx_parity);
+ tx_rx->tx_bits = 18 * 8;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
+ } else if(is_encrypted && plain_data[0] == 0xA0) {
+ uint8_t block = plain_data[1];
+ if(block > mf_classic_get_total_block_num(emulator->data.type)) {
+ break;
+ }
+ // Send ACK
+ uint8_t ack = 0x0A;
+ mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
+ tx_rx->tx_bits = 4;
+
+ if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
+ if(tx_rx->rx_bits != 18 * 8) break;
+
+ mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data);
+ uint8_t block_data[16] = {};
+ memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE);
+ if(mf_classic_is_sector_trailer(block)) {
+ if(mf_classic_is_allowed_access(
+ emulator, block, access_key, MfClassicActionKeyAWrite)) {
+ memcpy(block_data, plain_data, 6);
+ }
+ if(mf_classic_is_allowed_access(
+ emulator, block, access_key, MfClassicActionKeyBWrite)) {
+ memcpy(&block_data[10], &plain_data[10], 6);
+ }
+ if(mf_classic_is_allowed_access(
+ emulator, block, access_key, MfClassicActionACWrite)) {
+ memcpy(&block_data[6], &plain_data[6], 4);
+ }
+ } else {
+ if(mf_classic_is_allowed_access(
+ emulator, block, access_key, MfClassicActionDataWrite)) {
+ memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE);
+ }
+ }
+ if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE)) {
+ memcpy(emulator->data.block[block].value, block_data, MF_CLASSIC_BLOCK_SIZE);
+ emulator->data_changed = true;
+ }
+ // Send ACK
+ ack = 0x0A;
+ mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
+ tx_rx->tx_bits = 4;
+ } else {
+ // Unknown command
+ break;
+ }
+ }
+
+ if(!command_processed) {
+ // Send NACK
+ uint8_t nack = 0x04;
+ if(is_encrypted) {
+ mf_crypto1_encrypt(
+ &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity);
+ } else {
+ tx_rx->tx_data[0] = nack;
+ }
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
+ tx_rx->tx_bits = 4;
+ furi_hal_nfc_tx_rx(tx_rx, 300);
+ }
+
+ return true;
+}
diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h
new file mode 100644
index 00000000..85f67b11
--- /dev/null
+++ b/lib/nfc/protocols/mifare_classic.h
@@ -0,0 +1,145 @@
+#pragma once
+
+#include <furi_hal_nfc.h>
+
+#include "crypto1.h"
+
+#define MF_CLASSIC_BLOCK_SIZE (16)
+#define MF_CLASSIC_TOTAL_BLOCKS_MAX (256)
+#define MF_CLASSIC_1K_TOTAL_SECTORS_NUM (16)
+#define MF_CLASSIC_4K_TOTAL_SECTORS_NUM (40)
+
+#define MF_CLASSIC_SECTORS_MAX (40)
+#define MF_CLASSIC_BLOCKS_IN_SECTOR_MAX (16)
+
+#define MF_CLASSIC_NO_KEY (0xFFFFFFFFFFFFFFFF)
+#define MF_CLASSIC_MAX_DATA_SIZE (16)
+#define MF_CLASSIC_KEY_SIZE (6)
+#define MF_CLASSIC_ACCESS_BYTES_SIZE (4)
+
+typedef enum {
+ MfClassicType1k,
+ MfClassicType4k,
+} MfClassicType;
+
+typedef enum {
+ MfClassicKeyA,
+ MfClassicKeyB,
+} MfClassicKey;
+
+typedef struct {
+ uint8_t value[MF_CLASSIC_BLOCK_SIZE];
+} MfClassicBlock;
+
+typedef struct {
+ uint8_t key_a[MF_CLASSIC_KEY_SIZE];
+ uint8_t access_bits[MF_CLASSIC_ACCESS_BYTES_SIZE];
+ uint8_t key_b[MF_CLASSIC_KEY_SIZE];
+} MfClassicSectorTrailer;
+
+typedef struct {
+ uint8_t total_blocks;
+ MfClassicBlock block[MF_CLASSIC_BLOCKS_IN_SECTOR_MAX];
+} MfClassicSector;
+
+typedef struct {
+ MfClassicType type;
+ uint32_t block_read_mask[MF_CLASSIC_TOTAL_BLOCKS_MAX / 32];
+ uint64_t key_a_mask;
+ uint64_t key_b_mask;
+ MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX];
+} MfClassicData;
+
+typedef struct {
+ uint8_t sector;
+ uint64_t key_a;
+ uint64_t key_b;
+} MfClassicAuthContext;
+
+typedef struct {
+ uint8_t sector_num;
+ uint64_t key_a;
+ uint64_t key_b;
+} MfClassicSectorReader;
+
+typedef struct {
+ MfClassicType type;
+ Crypto1 crypto;
+ uint8_t sectors_to_read;
+ MfClassicSectorReader sector_reader[MF_CLASSIC_SECTORS_MAX];
+} MfClassicReader;
+
+typedef struct {
+ uint32_t cuid;
+ Crypto1 crypto;
+ MfClassicData data;
+ bool data_changed;
+} MfClassicEmulator;
+
+const char* mf_classic_get_type_str(MfClassicType type);
+
+bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
+
+MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
+
+bool mf_classic_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK, MfClassicReader* reader);
+
+uint8_t mf_classic_get_total_sectors_num(MfClassicType type);
+
+uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector);
+
+bool mf_classic_is_sector_trailer(uint8_t block);
+
+uint8_t mf_classic_get_sector_by_block(uint8_t block);
+
+bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type);
+
+void mf_classic_set_key_found(
+ MfClassicData* data,
+ uint8_t sector_num,
+ MfClassicKey key_type,
+ uint64_t key);
+
+bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num);
+
+void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data);
+
+bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num);
+
+void mf_classic_get_read_sectors_and_keys(
+ MfClassicData* data,
+ uint8_t* sectors_read,
+ uint8_t* keys_found);
+
+MfClassicSectorTrailer*
+ mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector);
+
+void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector);
+
+bool mf_classic_authenticate(
+ FuriHalNfcTxRxContext* tx_rx,
+ uint8_t block_num,
+ uint64_t key,
+ MfClassicKey key_type);
+
+bool mf_classic_auth_attempt(
+ FuriHalNfcTxRxContext* tx_rx,
+ MfClassicAuthContext* auth_ctx,
+ uint64_t key);
+
+void mf_classic_reader_add_sector(
+ MfClassicReader* reader,
+ uint8_t sector,
+ uint64_t key_a,
+ uint64_t key_b);
+
+void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num);
+
+uint8_t mf_classic_read_card(
+ FuriHalNfcTxRxContext* tx_rx,
+ MfClassicReader* reader,
+ MfClassicData* data);
+
+uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data);
+
+bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx);
diff --git a/lib/nfc/protocols/mifare_common.c b/lib/nfc/protocols/mifare_common.c
new file mode 100644
index 00000000..fd622765
--- /dev/null
+++ b/lib/nfc/protocols/mifare_common.c
@@ -0,0 +1,17 @@
+#include "mifare_common.h"
+
+MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
+ MifareType type = MifareTypeUnknown;
+
+ if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) {
+ type = MifareTypeUltralight;
+ } else if(
+ ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) ||
+ ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18))) {
+ type = MifareTypeClassic;
+ } else if(ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20) {
+ type = MifareTypeDesfire;
+ }
+
+ return type;
+}
diff --git a/lib/nfc/protocols/mifare_common.h b/lib/nfc/protocols/mifare_common.h
new file mode 100644
index 00000000..2b694d90
--- /dev/null
+++ b/lib/nfc/protocols/mifare_common.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <stdint.h>
+
+typedef enum {
+ MifareTypeUnknown,
+ MifareTypeUltralight,
+ MifareTypeClassic,
+ MifareTypeDesfire,
+} MifareType;
+
+MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
diff --git a/lib/nfc/protocols/mifare_desfire.c b/lib/nfc/protocols/mifare_desfire.c
new file mode 100644
index 00000000..1822d5c1
--- /dev/null
+++ b/lib/nfc/protocols/mifare_desfire.c
@@ -0,0 +1,623 @@
+#include "mifare_desfire.h"
+#include <furi.h>
+#include <furi_hal_nfc.h>
+
+#define TAG "MifareDESFire"
+
+void mf_df_clear(MifareDesfireData* data) {
+ free(data->free_memory);
+ if(data->master_key_settings) {
+ MifareDesfireKeyVersion* key_version = data->master_key_settings->key_version_head;
+ while(key_version) {
+ MifareDesfireKeyVersion* next_key_version = key_version->next;
+ free(key_version);
+ key_version = next_key_version;
+ }
+ }
+ free(data->master_key_settings);
+ MifareDesfireApplication* app = data->app_head;
+ while(app) {
+ MifareDesfireApplication* next_app = app->next;
+ if(app->key_settings) {
+ MifareDesfireKeyVersion* key_version = app->key_settings->key_version_head;
+ while(key_version) {
+ MifareDesfireKeyVersion* next_key_version = key_version->next;
+ free(key_version);
+ key_version = next_key_version;
+ }
+ }
+ free(app->key_settings);
+ MifareDesfireFile* file = app->file_head;
+ while(file) {
+ MifareDesfireFile* next_file = file->next;
+ free(file->contents);
+ free(file);
+ file = next_file;
+ }
+ free(app);
+ app = next_app;
+ }
+ data->free_memory = NULL;
+ data->master_key_settings = NULL;
+ data->app_head = NULL;
+}
+
+void mf_df_cat_data(MifareDesfireData* data, string_t out) {
+ mf_df_cat_card_info(data, out);
+ for(MifareDesfireApplication* app = data->app_head; app; app = app->next) {
+ mf_df_cat_application(app, out);
+ }
+}
+
+void mf_df_cat_card_info(MifareDesfireData* data, string_t out) {
+ mf_df_cat_version(&data->version, out);
+ if(data->free_memory) {
+ mf_df_cat_free_mem(data->free_memory, out);
+ }
+ if(data->master_key_settings) {
+ mf_df_cat_key_settings(data->master_key_settings, out);
+ }
+}
+
+void mf_df_cat_version(MifareDesfireVersion* version, string_t out) {
+ string_cat_printf(
+ out,
+ "%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
+ version->uid[0],
+ version->uid[1],
+ version->uid[2],
+ version->uid[3],
+ version->uid[4],
+ version->uid[5],
+ version->uid[6]);
+ string_cat_printf(
+ out,
+ "hw %02x type %02x sub %02x\n"
+ " maj %02x min %02x\n"
+ " size %02x proto %02x\n",
+ version->hw_vendor,
+ version->hw_type,
+ version->hw_subtype,
+ version->hw_major,
+ version->hw_minor,
+ version->hw_storage,
+ version->hw_proto);
+ string_cat_printf(
+ out,
+ "sw %02x type %02x sub %02x\n"
+ " maj %02x min %02x\n"
+ " size %02x proto %02x\n",
+ version->sw_vendor,
+ version->sw_type,
+ version->sw_subtype,
+ version->sw_major,
+ version->sw_minor,
+ version->sw_storage,
+ version->sw_proto);
+ string_cat_printf(
+ out,
+ "batch %02x:%02x:%02x:%02x:%02x\n"
+ "week %d year %d\n",
+ version->batch[0],
+ version->batch[1],
+ version->batch[2],
+ version->batch[3],
+ version->batch[4],
+ version->prod_week,
+ version->prod_year);
+}
+
+void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, string_t out) {
+ string_cat_printf(out, "freeMem %d\n", free_mem->bytes);
+}
+
+void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, string_t out) {
+ string_cat_printf(out, "changeKeyID %d\n", ks->change_key_id);
+ string_cat_printf(out, "configChangeable %d\n", ks->config_changeable);
+ string_cat_printf(out, "freeCreateDelete %d\n", ks->free_create_delete);
+ string_cat_printf(out, "freeDirectoryList %d\n", ks->free_directory_list);
+ string_cat_printf(out, "masterChangeable %d\n", ks->master_key_changeable);
+ if(ks->flags) {
+ string_cat_printf(out, "flags %d\n", ks->flags);
+ }
+ string_cat_printf(out, "maxKeys %d\n", ks->max_keys);
+ for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) {
+ string_cat_printf(out, "key %d version %d\n", kv->id, kv->version);
+ }
+}
+
+void mf_df_cat_application_info(MifareDesfireApplication* app, string_t out) {
+ string_cat_printf(out, "Application %02x%02x%02x\n", app->id[0], app->id[1], app->id[2]);
+ if(app->key_settings) {
+ mf_df_cat_key_settings(app->key_settings, out);
+ }
+}
+
+void mf_df_cat_application(MifareDesfireApplication* app, string_t out) {
+ mf_df_cat_application_info(app, out);
+ for(MifareDesfireFile* file = app->file_head; file; file = file->next) {
+ mf_df_cat_file(file, out);
+ }
+}
+
+void mf_df_cat_file(MifareDesfireFile* file, string_t out) {
+ char* type = "unknown";
+ switch(file->type) {
+ case MifareDesfireFileTypeStandard:
+ type = "standard";
+ break;
+ case MifareDesfireFileTypeBackup:
+ type = "backup";
+ break;
+ case MifareDesfireFileTypeValue:
+ type = "value";
+ break;
+ case MifareDesfireFileTypeLinearRecord:
+ type = "linear";
+ break;
+ case MifareDesfireFileTypeCyclicRecord:
+ type = "cyclic";
+ break;
+ }
+ char* comm = "unknown";
+ switch(file->comm) {
+ case MifareDesfireFileCommunicationSettingsPlaintext:
+ comm = "plain";
+ break;
+ case MifareDesfireFileCommunicationSettingsAuthenticated:
+ comm = "auth";
+ break;
+ case MifareDesfireFileCommunicationSettingsEnciphered:
+ comm = "enciphered";
+ break;
+ }
+ string_cat_printf(out, "File %d\n", file->id);
+ string_cat_printf(out, "%s %s\n", type, comm);
+ string_cat_printf(
+ out,
+ "r %d w %d rw %d c %d\n",
+ file->access_rights >> 12 & 0xF,
+ file->access_rights >> 8 & 0xF,
+ file->access_rights >> 4 & 0xF,
+ file->access_rights & 0xF);
+ uint16_t size = 0;
+ uint16_t num = 1;
+ switch(file->type) {
+ case MifareDesfireFileTypeStandard:
+ case MifareDesfireFileTypeBackup:
+ size = file->settings.data.size;
+ string_cat_printf(out, "size %d\n", size);
+ break;
+ case MifareDesfireFileTypeValue:
+ size = 4;
+ string_cat_printf(
+ out, "lo %d hi %d\n", file->settings.value.lo_limit, file->settings.value.hi_limit);
+ string_cat_printf(
+ out,
+ "limit %d enabled %d\n",
+ file->settings.value.limited_credit_value,
+ file->settings.value.limited_credit_enabled);
+ break;
+ case MifareDesfireFileTypeLinearRecord:
+ case MifareDesfireFileTypeCyclicRecord:
+ size = file->settings.record.size;
+ num = file->settings.record.cur;
+ string_cat_printf(out, "size %d\n", size);
+ string_cat_printf(out, "num %d max %d\n", num, file->settings.record.max);
+ break;
+ }
+ uint8_t* data = file->contents;
+ if(data) {
+ for(int rec = 0; rec < num; rec++) {
+ for(int ch = 0; ch < size; ch++) {
+ string_cat_printf(out, "%02x", data[rec * size + ch]);
+ }
+ string_cat_printf(out, " \n");
+ }
+ }
+}
+
+bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
+ return ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20;
+}
+
+uint16_t mf_df_prepare_get_version(uint8_t* dest) {
+ dest[0] = MF_DF_GET_VERSION;
+ return 1;
+}
+
+bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out) {
+ if(len < 1 || *buf) {
+ return false;
+ }
+ len--;
+ buf++;
+ if(len < sizeof(MifareDesfireVersion)) {
+ return false;
+ }
+ memcpy(out, buf, sizeof(MifareDesfireVersion));
+ return true;
+}
+
+uint16_t mf_df_prepare_get_free_memory(uint8_t* dest) {
+ dest[0] = MF_DF_GET_FREE_MEMORY;
+ return 1;
+}
+
+bool mf_df_parse_get_free_memory_response(uint8_t* buf, uint16_t len, MifareDesfireFreeMemory* out) {
+ if(len < 1 || *buf) {
+ return false;
+ }
+ len--;
+ buf++;
+ if(len != 3) {
+ return false;
+ }
+ out->bytes = buf[0] | (buf[1] << 8) | (buf[2] << 16);
+ return true;
+}
+
+uint16_t mf_df_prepare_get_key_settings(uint8_t* dest) {
+ dest[0] = MF_DF_GET_KEY_SETTINGS;
+ return 1;
+}
+
+bool mf_df_parse_get_key_settings_response(
+ uint8_t* buf,
+ uint16_t len,
+ MifareDesfireKeySettings* out) {
+ if(len < 1 || *buf) {
+ return false;
+ }
+ len--;
+ buf++;
+ if(len < 2) {
+ return false;
+ }
+ out->change_key_id = buf[0] >> 4;
+ out->config_changeable = (buf[0] & 0x8) != 0;
+ out->free_create_delete = (buf[0] & 0x4) != 0;
+ out->free_directory_list = (buf[0] & 0x2) != 0;
+ out->master_key_changeable = (buf[0] & 0x1) != 0;
+ out->flags = buf[1] >> 4;
+ out->max_keys = buf[1] & 0xF;
+ return true;
+}
+
+uint16_t mf_df_prepare_get_key_version(uint8_t* dest, uint8_t key_id) {
+ dest[0] = MF_DF_GET_KEY_VERSION;
+ dest[1] = key_id;
+ return 2;
+}
+
+bool mf_df_parse_get_key_version_response(uint8_t* buf, uint16_t len, MifareDesfireKeyVersion* out) {
+ if(len != 2 || *buf) {
+ return false;
+ }
+ out->version = buf[1];
+ return true;
+}
+
+uint16_t mf_df_prepare_get_application_ids(uint8_t* dest) {
+ dest[0] = MF_DF_GET_APPLICATION_IDS;
+ return 1;
+}
+
+bool mf_df_parse_get_application_ids_response(
+ uint8_t* buf,
+ uint16_t len,
+ MifareDesfireApplication** app_head) {
+ if(len < 1 || *buf) {
+ return false;
+ }
+ len--;
+ buf++;
+ if(len % 3 != 0) {
+ return false;
+ }
+ while(len) {
+ MifareDesfireApplication* app = malloc(sizeof(MifareDesfireApplication));
+ memset(app, 0, sizeof(MifareDesfireApplication));
+ memcpy(app->id, buf, 3);
+ len -= 3;
+ buf += 3;
+ *app_head = app;
+ app_head = &app->next;
+ }
+ return true;
+}
+
+uint16_t mf_df_prepare_select_application(uint8_t* dest, uint8_t id[3]) {
+ dest[0] = MF_DF_SELECT_APPLICATION;
+ dest[1] = id[0];
+ dest[2] = id[1];
+ dest[3] = id[2];
+ return 4;
+}
+
+bool mf_df_parse_select_application_response(uint8_t* buf, uint16_t len) {
+ return len == 1 && !*buf;
+}
+
+uint16_t mf_df_prepare_get_file_ids(uint8_t* dest) {
+ dest[0] = MF_DF_GET_FILE_IDS;
+ return 1;
+}
+
+bool mf_df_parse_get_file_ids_response(uint8_t* buf, uint16_t len, MifareDesfireFile** file_head) {
+ if(len < 1 || *buf) {
+ return false;
+ }
+ len--;
+ buf++;
+ while(len) {
+ MifareDesfireFile* file = malloc(sizeof(MifareDesfireFile));
+ memset(file, 0, sizeof(MifareDesfireFile));
+ file->id = *buf;
+ len--;
+ buf++;
+ *file_head = file;
+ file_head = &file->next;
+ }
+ return true;
+}
+
+uint16_t mf_df_prepare_get_file_settings(uint8_t* dest, uint8_t file_id) {
+ dest[0] = MF_DF_GET_FILE_SETTINGS;
+ dest[1] = file_id;
+ return 2;
+}
+
+bool mf_df_parse_get_file_settings_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out) {
+ if(len < 5 || *buf) {
+ return false;
+ }
+ len--;
+ buf++;
+ out->type = buf[0];
+ out->comm = buf[1];
+ out->access_rights = buf[2] | (buf[3] << 8);
+ switch(out->type) {
+ case MifareDesfireFileTypeStandard:
+ case MifareDesfireFileTypeBackup:
+ if(len != 7) {
+ return false;
+ }
+ out->settings.data.size = buf[4] | (buf[5] << 8) | (buf[6] << 16);
+ break;
+ case MifareDesfireFileTypeValue:
+ if(len != 17) {
+ return false;
+ }
+ out->settings.value.lo_limit = buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24);
+ out->settings.value.hi_limit = buf[8] | (buf[9] << 8) | (buf[10] << 16) | (buf[11] << 24);
+ out->settings.value.limited_credit_value = buf[12] | (buf[13] << 8) | (buf[14] << 16) |
+ (buf[15] << 24);
+ out->settings.value.limited_credit_enabled = buf[16];
+ break;
+ case MifareDesfireFileTypeLinearRecord:
+ case MifareDesfireFileTypeCyclicRecord:
+ if(len != 13) {
+ return false;
+ }
+ out->settings.record.size = buf[4] | (buf[5] << 8) | (buf[6] << 16);
+ out->settings.record.max = buf[7] | (buf[8] << 8) | (buf[9] << 16);
+ out->settings.record.cur = buf[10] | (buf[11] << 8) | (buf[12] << 16);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len) {
+ dest[0] = MF_DF_READ_DATA;
+ dest[1] = file_id;
+ dest[2] = offset;
+ dest[3] = offset >> 8;
+ dest[4] = offset >> 16;
+ dest[5] = len;
+ dest[6] = len >> 8;
+ dest[7] = len >> 16;
+ return 8;
+}
+
+uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id) {
+ dest[0] = MF_DF_GET_VALUE;
+ dest[1] = file_id;
+ return 2;
+}
+
+uint16_t
+ mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len) {
+ dest[0] = MF_DF_READ_RECORDS;
+ dest[1] = file_id;
+ dest[2] = offset;
+ dest[3] = offset >> 8;
+ dest[4] = offset >> 16;
+ dest[5] = len;
+ dest[6] = len >> 8;
+ dest[7] = len >> 16;
+ return 8;
+}
+
+bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out) {
+ if(len < 1 || *buf) {
+ return false;
+ }
+ len--;
+ buf++;
+ out->contents = malloc(len);
+ memcpy(out->contents, buf, len);
+ return true;
+}
+
+bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data) {
+ furi_assert(tx_rx);
+ furi_assert(data);
+
+ bool card_read = false;
+ do {
+ // Get version
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_version(tx_rx->tx_data);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting version");
+ break;
+ }
+ if(!mf_df_parse_get_version_response(tx_rx->rx_data, tx_rx->rx_bits / 8, &data->version)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_VERSION responce");
+ }
+
+ // Get free memory
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_free_memory(tx_rx->tx_data);
+ if(furi_hal_nfc_tx_rx_full(tx_rx)) {
+ data->free_memory = malloc(sizeof(MifareDesfireFreeMemory));
+ if(!mf_df_parse_get_free_memory_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, data->free_memory)) {
+ FURI_LOG_D(TAG, "Bad DESFire GET_FREE_MEMORY response (normal for pre-EV1 cards)");
+ free(data->free_memory);
+ data->free_memory = NULL;
+ }
+ }
+
+ // Get key settings
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_D(TAG, "Bad exchange getting key settings");
+ } else {
+ data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings));
+ if(!mf_df_parse_get_key_settings_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, data->master_key_settings)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response");
+ free(data->master_key_settings);
+ data->master_key_settings = NULL;
+ } else {
+ MifareDesfireKeyVersion** key_version_head =
+ &data->master_key_settings->key_version_head;
+ for(uint8_t key_id = 0; key_id < data->master_key_settings->max_keys; key_id++) {
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting key version");
+ continue;
+ }
+ MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion));
+ memset(key_version, 0, sizeof(MifareDesfireKeyVersion));
+ key_version->id = key_id;
+ if(!mf_df_parse_get_key_version_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response");
+ free(key_version);
+ continue;
+ }
+ *key_version_head = key_version;
+ key_version_head = &key_version->next;
+ }
+ }
+ }
+
+ // Get application IDs
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_application_ids(tx_rx->tx_data);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting application IDs");
+ break;
+ } else {
+ if(!mf_df_parse_get_application_ids_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, &data->app_head)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_APPLICATION_IDS response");
+ break;
+ }
+ }
+
+ for(MifareDesfireApplication* app = data->app_head; app; app = app->next) {
+ tx_rx->tx_bits = 8 * mf_df_prepare_select_application(tx_rx->tx_data, app->id);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx) ||
+ !mf_df_parse_select_application_response(tx_rx->rx_data, tx_rx->rx_bits / 8)) {
+ FURI_LOG_W(TAG, "Bad exchange selecting application");
+ continue;
+ }
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting key settings");
+ } else {
+ app->key_settings = malloc(sizeof(MifareDesfireKeySettings));
+ memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings));
+ if(!mf_df_parse_get_key_settings_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, app->key_settings)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response");
+ free(app->key_settings);
+ app->key_settings = NULL;
+ continue;
+ }
+
+ MifareDesfireKeyVersion** key_version_head = &app->key_settings->key_version_head;
+ for(uint8_t key_id = 0; key_id < app->key_settings->max_keys; key_id++) {
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting key version");
+ continue;
+ }
+ MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion));
+ memset(key_version, 0, sizeof(MifareDesfireKeyVersion));
+ key_version->id = key_id;
+ if(!mf_df_parse_get_key_version_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response");
+ free(key_version);
+ continue;
+ }
+ *key_version_head = key_version;
+ key_version_head = &key_version->next;
+ }
+ }
+
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_file_ids(tx_rx->tx_data);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting file IDs");
+ } else {
+ if(!mf_df_parse_get_file_ids_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, &app->file_head)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_FILE_IDS response");
+ }
+ }
+
+ for(MifareDesfireFile* file = app->file_head; file; file = file->next) {
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_file_settings(tx_rx->tx_data, file->id);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting file settings");
+ continue;
+ }
+ if(!mf_df_parse_get_file_settings_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, file)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_FILE_SETTINGS response");
+ continue;
+ }
+ switch(file->type) {
+ case MifareDesfireFileTypeStandard:
+ case MifareDesfireFileTypeBackup:
+ tx_rx->tx_bits = 8 * mf_df_prepare_read_data(tx_rx->tx_data, file->id, 0, 0);
+ break;
+ case MifareDesfireFileTypeValue:
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_value(tx_rx->tx_data, file->id);
+ break;
+ case MifareDesfireFileTypeLinearRecord:
+ case MifareDesfireFileTypeCyclicRecord:
+ tx_rx->tx_bits =
+ 8 * mf_df_prepare_read_records(tx_rx->tx_data, file->id, 0, 0);
+ break;
+ }
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange reading file %d", file->id);
+ continue;
+ }
+ if(!mf_df_parse_read_data_response(tx_rx->rx_data, tx_rx->rx_bits / 8, file)) {
+ FURI_LOG_W(TAG, "Bad response reading file %d", file->id);
+ continue;
+ }
+ }
+ }
+
+ card_read = true;
+ } while(false);
+
+ return card_read;
+}
diff --git a/lib/nfc/protocols/mifare_desfire.h b/lib/nfc/protocols/mifare_desfire.h
new file mode 100644
index 00000000..e59743a2
--- /dev/null
+++ b/lib/nfc/protocols/mifare_desfire.h
@@ -0,0 +1,169 @@
+#pragma once
+
+#include <m-string.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <furi_hal_nfc.h>
+
+#define MF_DF_GET_VERSION (0x60)
+#define MF_DF_GET_FREE_MEMORY (0x6E)
+#define MF_DF_GET_KEY_SETTINGS (0x45)
+#define MF_DF_GET_KEY_VERSION (0x64)
+#define MF_DF_GET_APPLICATION_IDS (0x6A)
+#define MF_DF_SELECT_APPLICATION (0x5A)
+#define MF_DF_GET_FILE_IDS (0x6F)
+#define MF_DF_GET_FILE_SETTINGS (0xF5)
+
+#define MF_DF_READ_DATA (0xBD)
+#define MF_DF_GET_VALUE (0x6C)
+#define MF_DF_READ_RECORDS (0xBB)
+
+typedef struct {
+ uint8_t hw_vendor;
+ uint8_t hw_type;
+ uint8_t hw_subtype;
+ uint8_t hw_major;
+ uint8_t hw_minor;
+ uint8_t hw_storage;
+ uint8_t hw_proto;
+
+ uint8_t sw_vendor;
+ uint8_t sw_type;
+ uint8_t sw_subtype;
+ uint8_t sw_major;
+ uint8_t sw_minor;
+ uint8_t sw_storage;
+ uint8_t sw_proto;
+
+ uint8_t uid[7];
+ uint8_t batch[5];
+ uint8_t prod_week;
+ uint8_t prod_year;
+} MifareDesfireVersion;
+
+typedef struct {
+ uint32_t bytes;
+} MifareDesfireFreeMemory; // EV1+ only
+
+typedef struct MifareDesfireKeyVersion {
+ uint8_t id;
+ uint8_t version;
+ struct MifareDesfireKeyVersion* next;
+} MifareDesfireKeyVersion;
+
+typedef struct {
+ uint8_t change_key_id;
+ bool config_changeable;
+ bool free_create_delete;
+ bool free_directory_list;
+ bool master_key_changeable;
+ uint8_t flags;
+ uint8_t max_keys;
+ MifareDesfireKeyVersion* key_version_head;
+} MifareDesfireKeySettings;
+
+typedef enum {
+ MifareDesfireFileTypeStandard = 0,
+ MifareDesfireFileTypeBackup = 1,
+ MifareDesfireFileTypeValue = 2,
+ MifareDesfireFileTypeLinearRecord = 3,
+ MifareDesfireFileTypeCyclicRecord = 4,
+} MifareDesfireFileType;
+
+typedef enum {
+ MifareDesfireFileCommunicationSettingsPlaintext = 0,
+ MifareDesfireFileCommunicationSettingsAuthenticated = 1,
+ MifareDesfireFileCommunicationSettingsEnciphered = 3,
+} MifareDesfireFileCommunicationSettings;
+
+typedef struct MifareDesfireFile {
+ uint8_t id;
+ MifareDesfireFileType type;
+ MifareDesfireFileCommunicationSettings comm;
+ uint16_t access_rights;
+ union {
+ struct {
+ uint32_t size;
+ } data;
+ struct {
+ uint32_t lo_limit;
+ uint32_t hi_limit;
+ uint32_t limited_credit_value;
+ bool limited_credit_enabled;
+ } value;
+ struct {
+ uint32_t size;
+ uint32_t max;
+ uint32_t cur;
+ } record;
+ } settings;
+ uint8_t* contents;
+
+ struct MifareDesfireFile* next;
+} MifareDesfireFile;
+
+typedef struct MifareDesfireApplication {
+ uint8_t id[3];
+ MifareDesfireKeySettings* key_settings;
+ MifareDesfireFile* file_head;
+
+ struct MifareDesfireApplication* next;
+} MifareDesfireApplication;
+
+typedef struct {
+ MifareDesfireVersion version;
+ MifareDesfireFreeMemory* free_memory;
+ MifareDesfireKeySettings* master_key_settings;
+ MifareDesfireApplication* app_head;
+} MifareDesfireData;
+
+void mf_df_clear(MifareDesfireData* data);
+
+void mf_df_cat_data(MifareDesfireData* data, string_t out);
+void mf_df_cat_card_info(MifareDesfireData* data, string_t out);
+void mf_df_cat_version(MifareDesfireVersion* version, string_t out);
+void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, string_t out);
+void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, string_t out);
+void mf_df_cat_application_info(MifareDesfireApplication* app, string_t out);
+void mf_df_cat_application(MifareDesfireApplication* app, string_t out);
+void mf_df_cat_file(MifareDesfireFile* file, string_t out);
+
+bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
+
+uint16_t mf_df_prepare_get_version(uint8_t* dest);
+bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out);
+
+uint16_t mf_df_prepare_get_free_memory(uint8_t* dest);
+bool mf_df_parse_get_free_memory_response(uint8_t* buf, uint16_t len, MifareDesfireFreeMemory* out);
+
+uint16_t mf_df_prepare_get_key_settings(uint8_t* dest);
+bool mf_df_parse_get_key_settings_response(
+ uint8_t* buf,
+ uint16_t len,
+ MifareDesfireKeySettings* out);
+
+uint16_t mf_df_prepare_get_key_version(uint8_t* dest, uint8_t key_id);
+bool mf_df_parse_get_key_version_response(uint8_t* buf, uint16_t len, MifareDesfireKeyVersion* out);
+
+uint16_t mf_df_prepare_get_application_ids(uint8_t* dest);
+bool mf_df_parse_get_application_ids_response(
+ uint8_t* buf,
+ uint16_t len,
+ MifareDesfireApplication** app_head);
+
+uint16_t mf_df_prepare_select_application(uint8_t* dest, uint8_t id[3]);
+bool mf_df_parse_select_application_response(uint8_t* buf, uint16_t len);
+
+uint16_t mf_df_prepare_get_file_ids(uint8_t* dest);
+bool mf_df_parse_get_file_ids_response(uint8_t* buf, uint16_t len, MifareDesfireFile** file_head);
+
+uint16_t mf_df_prepare_get_file_settings(uint8_t* dest, uint8_t file_id);
+bool mf_df_parse_get_file_settings_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out);
+
+uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len);
+uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id);
+uint16_t mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len);
+bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out);
+
+bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data);
diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c
new file mode 100644
index 00000000..9dcd1d6a
--- /dev/null
+++ b/lib/nfc/protocols/mifare_ultralight.c
@@ -0,0 +1,1791 @@
+#include <limits.h>
+#include "mifare_ultralight.h"
+#include <furi.h>
+#include <m-string.h>
+
+#define TAG "MfUltralight"
+
+bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
+ if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) {
+ return true;
+ }
+ return false;
+}
+
+static MfUltralightFeatures mf_ul_get_features(MfUltralightType type) {
+ switch(type) {
+ case MfUltralightTypeUL11:
+ case MfUltralightTypeUL21:
+ return MfUltralightSupportFastRead | MfUltralightSupportCompatWrite |
+ MfUltralightSupportReadCounter | MfUltralightSupportIncrCounter |
+ MfUltralightSupportAuth | MfUltralightSupportSignature |
+ MfUltralightSupportTearingFlags | MfUltralightSupportVcsl;
+ case MfUltralightTypeNTAG213:
+ case MfUltralightTypeNTAG215:
+ case MfUltralightTypeNTAG216:
+ return MfUltralightSupportFastRead | MfUltralightSupportCompatWrite |
+ MfUltralightSupportReadCounter | MfUltralightSupportAuth |
+ MfUltralightSupportSignature | MfUltralightSupportSingleCounter |
+ MfUltralightSupportAsciiMirror;
+ case MfUltralightTypeNTAGI2C1K:
+ case MfUltralightTypeNTAGI2C2K:
+ return MfUltralightSupportFastRead | MfUltralightSupportSectorSelect;
+ case MfUltralightTypeNTAGI2CPlus1K:
+ case MfUltralightTypeNTAGI2CPlus2K:
+ return MfUltralightSupportFastRead | MfUltralightSupportAuth |
+ MfUltralightSupportFastWrite | MfUltralightSupportSignature |
+ MfUltralightSupportSectorSelect;
+ case MfUltralightTypeNTAG203:
+ return MfUltralightSupportCompatWrite | MfUltralightSupportCounterInMemory;
+ default:
+ // Assumed original MFUL 512-bit
+ return MfUltralightSupportCompatWrite;
+ }
+}
+
+static void mf_ul_set_default_version(MfUltralightReader* reader, MfUltralightData* data) {
+ data->type = MfUltralightTypeUnknown;
+ reader->pages_to_read = 16;
+}
+
+static void mf_ul_set_version_ntag203(MfUltralightReader* reader, MfUltralightData* data) {
+ data->type = MfUltralightTypeNTAG203;
+ reader->pages_to_read = 42;
+}
+
+bool mf_ultralight_read_version(
+ FuriHalNfcTxRxContext* tx_rx,
+ MfUltralightReader* reader,
+ MfUltralightData* data) {
+ bool version_read = false;
+
+ do {
+ FURI_LOG_D(TAG, "Reading version");
+ tx_rx->tx_data[0] = MF_UL_GET_VERSION_CMD;
+ tx_rx->tx_bits = 8;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+ if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits != 64) {
+ FURI_LOG_D(TAG, "Failed reading version");
+ mf_ul_set_default_version(reader, data);
+ furi_hal_nfc_sleep();
+ furi_hal_nfc_activate_nfca(300, NULL);
+ break;
+ }
+ MfUltralightVersion* version = (MfUltralightVersion*)tx_rx->rx_data;
+ data->version = *version;
+ if(version->storage_size == 0x0B || version->storage_size == 0x00) {
+ data->type = MfUltralightTypeUL11;
+ reader->pages_to_read = 20;
+ } else if(version->storage_size == 0x0E) {
+ data->type = MfUltralightTypeUL21;
+ reader->pages_to_read = 41;
+ } else if(version->storage_size == 0x0F) {
+ data->type = MfUltralightTypeNTAG213;
+ reader->pages_to_read = 45;
+ } else if(version->storage_size == 0x11) {
+ data->type = MfUltralightTypeNTAG215;
+ reader->pages_to_read = 135;
+ } else if(version->prod_subtype == 5 && version->prod_ver_major == 2) {
+ // NTAG I2C
+ bool known = false;
+ if(version->prod_ver_minor == 1) {
+ if(version->storage_size == 0x13) {
+ data->type = MfUltralightTypeNTAGI2C1K;
+ reader->pages_to_read = 231;
+ known = true;
+ } else if(version->storage_size == 0x15) {
+ data->type = MfUltralightTypeNTAGI2C2K;
+ reader->pages_to_read = 485;
+ known = true;
+ }
+ } else if(version->prod_ver_minor == 2) {
+ if(version->storage_size == 0x13) {
+ data->type = MfUltralightTypeNTAGI2CPlus1K;
+ reader->pages_to_read = 236;
+ known = true;
+ } else if(version->storage_size == 0x15) {
+ data->type = MfUltralightTypeNTAGI2CPlus2K;
+ reader->pages_to_read = 492;
+ known = true;
+ }
+ }
+
+ if(!known) {
+ mf_ul_set_default_version(reader, data);
+ }
+ } else if(version->storage_size == 0x13) {
+ data->type = MfUltralightTypeNTAG216;
+ reader->pages_to_read = 231;
+ } else {
+ mf_ul_set_default_version(reader, data);
+ break;
+ }
+ version_read = true;
+ } while(false);
+
+ reader->supported_features = mf_ul_get_features(data->type);
+ return version_read;
+}
+
+static int16_t mf_ultralight_page_addr_to_tag_addr(uint8_t sector, uint8_t page) {
+ return sector * 256 + page;
+}
+
+static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_1k(
+ int16_t linear_address,
+ uint8_t* sector,
+ int16_t* valid_pages) {
+ // 0 - 226: sector 0
+ // 227 - 228: config registers
+ // 229 - 230: session registers
+
+ if(linear_address > 230) {
+ *valid_pages = 0;
+ return -1;
+ } else if(linear_address >= 229) {
+ *sector = 3;
+ *valid_pages = 2 - (linear_address - 229);
+ return linear_address - 229 + 248;
+ } else if(linear_address >= 227) {
+ *sector = 0;
+ *valid_pages = 2 - (linear_address - 227);
+ return linear_address - 227 + 232;
+ } else {
+ *sector = 0;
+ *valid_pages = 227 - linear_address;
+ return linear_address;
+ }
+}
+
+static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_2k(
+ int16_t linear_address,
+ uint8_t* sector,
+ int16_t* valid_pages) {
+ // 0 - 255: sector 0
+ // 256 - 480: sector 1
+ // 481 - 482: config registers
+ // 483 - 484: session registers
+
+ if(linear_address > 484) {
+ *valid_pages = 0;
+ return -1;
+ } else if(linear_address >= 483) {
+ *sector = 3;
+ *valid_pages = 2 - (linear_address - 483);
+ return linear_address - 483 + 248;
+ } else if(linear_address >= 481) {
+ *sector = 1;
+ *valid_pages = 2 - (linear_address - 481);
+ return linear_address - 481 + 232;
+ } else if(linear_address >= 256) {
+ *sector = 1;
+ *valid_pages = 225 - (linear_address - 256);
+ return linear_address - 256;
+ } else {
+ *sector = 0;
+ *valid_pages = 256 - linear_address;
+ return linear_address;
+ }
+}
+
+static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_1k(
+ int16_t linear_address,
+ uint8_t* sector,
+ int16_t* valid_pages) {
+ // 0 - 233: sector 0 + registers
+ // 234 - 235: session registers
+
+ if(linear_address > 235) {
+ *valid_pages = 0;
+ return -1;
+ } else if(linear_address >= 234) {
+ *sector = 0;
+ *valid_pages = 2 - (linear_address - 234);
+ return linear_address - 234 + 236;
+ } else {
+ *sector = 0;
+ *valid_pages = 234 - linear_address;
+ return linear_address;
+ }
+}
+
+static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_2k(
+ int16_t linear_address,
+ uint8_t* sector,
+ int16_t* valid_pages) {
+ // 0 - 233: sector 0 + registers
+ // 234 - 235: session registers
+ // 236 - 491: sector 1
+
+ if(linear_address > 491) {
+ *valid_pages = 0;
+ return -1;
+ } else if(linear_address >= 236) {
+ *sector = 1;
+ *valid_pages = 256 - (linear_address - 236);
+ return linear_address - 236;
+ } else if(linear_address >= 234) {
+ *sector = 0;
+ *valid_pages = 2 - (linear_address - 234);
+ return linear_address - 234 + 236;
+ } else {
+ *sector = 0;
+ *valid_pages = 234 - linear_address;
+ return linear_address;
+ }
+}
+
+static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag(
+ MfUltralightData* data,
+ MfUltralightReader* reader,
+ int16_t linear_address,
+ uint8_t* sector,
+ int16_t* valid_pages) {
+ switch(data->type) {
+ case MfUltralightTypeNTAGI2C1K:
+ return mf_ultralight_ntag_i2c_addr_lin_to_tag_1k(linear_address, sector, valid_pages);
+
+ case MfUltralightTypeNTAGI2C2K:
+ return mf_ultralight_ntag_i2c_addr_lin_to_tag_2k(linear_address, sector, valid_pages);
+
+ case MfUltralightTypeNTAGI2CPlus1K:
+ return mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_1k(linear_address, sector, valid_pages);
+
+ case MfUltralightTypeNTAGI2CPlus2K:
+ return mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_2k(linear_address, sector, valid_pages);
+
+ default:
+ *sector = 0xff;
+ *valid_pages = reader->pages_to_read - linear_address;
+ return linear_address;
+ }
+}
+
+static int16_t
+ mf_ultralight_ntag_i2c_addr_tag_to_lin_1k(uint8_t page, uint8_t sector, uint16_t* valid_pages) {
+ bool valid = false;
+ int16_t translated_page;
+ if(sector == 0) {
+ if(page <= 226) {
+ *valid_pages = 227 - page;
+ translated_page = page;
+ valid = true;
+ } else if(page >= 232 && page <= 233) {
+ *valid_pages = 2 - (page - 232);
+ translated_page = page - 232 + 227;
+ valid = true;
+ }
+ } else if(sector == 3) {
+ if(page >= 248 && page <= 249) {
+ *valid_pages = 2 - (page - 248);
+ translated_page = page - 248 + 229;
+ valid = true;
+ }
+ }
+
+ if(!valid) {
+ *valid_pages = 0;
+ translated_page = -1;
+ }
+ return translated_page;
+}
+
+static int16_t
+ mf_ultralight_ntag_i2c_addr_tag_to_lin_2k(uint8_t page, uint8_t sector, uint16_t* valid_pages) {
+ bool valid = false;
+ int16_t translated_page;
+ if(sector == 0) {
+ *valid_pages = 256 - page;
+ translated_page = page;
+ valid = true;
+ } else if(sector == 1) {
+ if(page <= 224) {
+ *valid_pages = 225 - page;
+ translated_page = 256 + page;
+ valid = true;
+ } else if(page >= 232 && page <= 233) {
+ *valid_pages = 2 - (page - 232);
+ translated_page = page - 232 + 481;
+ valid = true;
+ }
+ } else if(sector == 3) {
+ if(page >= 248 && page <= 249) {
+ *valid_pages = 2 - (page - 248);
+ translated_page = page - 248 + 483;
+ valid = true;
+ }
+ }
+
+ if(!valid) {
+ *valid_pages = 0;
+ translated_page = -1;
+ }
+ return translated_page;
+}
+
+static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_1k(
+ uint8_t page,
+ uint8_t sector,
+ uint16_t* valid_pages) {
+ bool valid = false;
+ int16_t translated_page;
+ if(sector == 0) {
+ if(page <= 233) {
+ *valid_pages = 234 - page;
+ translated_page = page;
+ valid = true;
+ } else if(page >= 236 && page <= 237) {
+ *valid_pages = 2 - (page - 236);
+ translated_page = page - 236 + 234;
+ valid = true;
+ }
+ } else if(sector == 3) {
+ if(page >= 248 && page <= 249) {
+ *valid_pages = 2 - (page - 248);
+ translated_page = page - 248 + 234;
+ valid = true;
+ }
+ }
+
+ if(!valid) {
+ *valid_pages = 0;
+ translated_page = -1;
+ }
+ return translated_page;
+}
+
+static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_2k(
+ uint8_t page,
+ uint8_t sector,
+ uint16_t* valid_pages) {
+ bool valid = false;
+ int16_t translated_page;
+ if(sector == 0) {
+ if(page <= 233) {
+ *valid_pages = 234 - page;
+ translated_page = page;
+ valid = true;
+ } else if(page >= 236 && page <= 237) {
+ *valid_pages = 2 - (page - 236);
+ translated_page = page - 236 + 234;
+ valid = true;
+ }
+ } else if(sector == 1) {
+ *valid_pages = 256 - page;
+ translated_page = page + 236;
+ valid = true;
+ } else if(sector == 3) {
+ if(page >= 248 && page <= 249) {
+ *valid_pages = 2 - (page - 248);
+ translated_page = page - 248 + 234;
+ valid = true;
+ }
+ }
+
+ if(!valid) {
+ *valid_pages = 0;
+ translated_page = -1;
+ }
+ return translated_page;
+}
+
+static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin(
+ MfUltralightData* data,
+ uint8_t page,
+ uint8_t sector,
+ uint16_t* valid_pages) {
+ switch(data->type) {
+ case MfUltralightTypeNTAGI2C1K:
+ return mf_ultralight_ntag_i2c_addr_tag_to_lin_1k(page, sector, valid_pages);
+
+ case MfUltralightTypeNTAGI2C2K:
+ return mf_ultralight_ntag_i2c_addr_tag_to_lin_2k(page, sector, valid_pages);
+
+ case MfUltralightTypeNTAGI2CPlus1K:
+ return mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_1k(page, sector, valid_pages);
+
+ case MfUltralightTypeNTAGI2CPlus2K:
+ return mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_2k(page, sector, valid_pages);
+
+ default:
+ *valid_pages = data->data_size / 4 - page;
+ return page;
+ }
+}
+
+static MfUltralightConfigPages* mf_ultralight_get_config_pages(MfUltralightData* data) {
+ if(data->type >= MfUltralightTypeUL11 && data->type <= MfUltralightTypeNTAG216) {
+ return (MfUltralightConfigPages*)&data->data[data->data_size - 4 * 4];
+ } else if(
+ data->type >= MfUltralightTypeNTAGI2CPlus1K &&
+ data->type <= MfUltralightTypeNTAGI2CPlus2K) {
+ return (MfUltralightConfigPages*)&data->data[0xe3 * 4];
+ } else {
+ return NULL;
+ }
+}
+
+static uint16_t mf_ultralight_calc_auth_count(MfUltralightData* data) {
+ if(mf_ul_get_features(data->type) & MfUltralightSupportAuth) {
+ MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data);
+ uint16_t scaled_authlim = config->access.authlim;
+ // NTAG I2C Plus uses 2^AUTHLIM attempts rather than the direct number
+ if(scaled_authlim > 0 && data->type >= MfUltralightTypeNTAGI2CPlus1K &&
+ data->type <= MfUltralightTypeNTAGI2CPlus2K) {
+ scaled_authlim = 1 << scaled_authlim;
+ }
+ return scaled_authlim;
+ }
+
+ return 0;
+}
+
+// NTAG21x will NAK if NFC_CNT_EN unset, so preempt
+static bool mf_ultralight_should_read_counters(MfUltralightData* data) {
+ if(data->type < MfUltralightTypeNTAG213 || data->type > MfUltralightTypeNTAG216) return true;
+
+ MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data);
+ return config->access.nfc_cnt_en;
+}
+
+static bool mf_ultralight_sector_select(FuriHalNfcTxRxContext* tx_rx, uint8_t sector) {
+ FURI_LOG_D(TAG, "Selecting sector %u", sector);
+ tx_rx->tx_data[0] = MF_UL_SECTOR_SELECT;
+ tx_rx->tx_data[1] = 0xff;
+ tx_rx->tx_bits = 16;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+ if(!furi_hal_nfc_tx_rx(tx_rx, 50)) {
+ FURI_LOG_D(TAG, "Failed to issue sector select command");
+ return false;
+ }
+
+ tx_rx->tx_data[0] = sector;
+ tx_rx->tx_data[1] = 0x00;
+ tx_rx->tx_data[2] = 0x00;
+ tx_rx->tx_data[3] = 0x00;
+ tx_rx->tx_bits = 32;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+ // This is NOT a typo! The tag ACKs by not sending a response within 1ms.
+ if(furi_hal_nfc_tx_rx(tx_rx, 20)) {
+ // TODO: what gets returned when an actual NAK is received?
+ FURI_LOG_D(TAG, "Sector %u select NAK'd", sector);
+ return false;
+ }
+
+ return true;
+}
+
+bool mf_ultralight_read_pages_direct(
+ FuriHalNfcTxRxContext* tx_rx,
+ uint8_t start_index,
+ uint8_t* data) {
+ FURI_LOG_D(TAG, "Reading pages %d - %d", start_index, start_index + 3);
+ tx_rx->tx_data[0] = MF_UL_READ_CMD;
+ tx_rx->tx_data[1] = start_index;
+ tx_rx->tx_bits = 16;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+ if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits < 16 * 8) {
+ FURI_LOG_D(TAG, "Failed to read pages %d - %d", start_index, start_index + 3);
+ return false;
+ }
+ memcpy(data, tx_rx->rx_data, 16);
+ return true;
+}
+
+bool mf_ultralight_read_pages(
+ FuriHalNfcTxRxContext* tx_rx,
+ MfUltralightReader* reader,
+ MfUltralightData* data) {
+ uint8_t pages_read_cnt = 0;
+ uint8_t curr_sector_index = 0xff;
+ reader->pages_read = 0;
+ for(size_t i = 0; i < reader->pages_to_read; i += pages_read_cnt) {
+ uint8_t tag_sector;
+ int16_t valid_pages;
+ int16_t tag_page = mf_ultralight_ntag_i2c_addr_lin_to_tag(
+ data, reader, (int16_t)i, &tag_sector, &valid_pages);
+
+ furi_assert(tag_page != -1);
+ if(curr_sector_index != tag_sector) {
+ if(!mf_ultralight_sector_select(tx_rx, tag_sector)) return false;
+ curr_sector_index = tag_sector;
+ }
+
+ FURI_LOG_D(TAG, "Reading pages %d - %d", i, i + (valid_pages > 4 ? 4 : valid_pages) - 1);
+ tx_rx->tx_data[0] = MF_UL_READ_CMD;
+ tx_rx->tx_data[1] = tag_page;
+ tx_rx->tx_bits = 16;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+ if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits < 16 * 8) {
+ FURI_LOG_D(
+ TAG,
+ "Failed to read pages %d - %d",
+ i,
+ i + (valid_pages > 4 ? 4 : valid_pages) - 1);
+ break;
+ }
+ if(valid_pages > 4) {
+ pages_read_cnt = 4;
+ } else {
+ pages_read_cnt = valid_pages;
+ }
+ reader->pages_read += pages_read_cnt;
+ data->data_size = reader->pages_read * 4;
+ memcpy(&data->data[i * 4], tx_rx->rx_data, pages_read_cnt * 4);
+ }
+
+ return reader->pages_read == reader->pages_to_read;
+}
+
+bool mf_ultralight_fast_read_pages(
+ FuriHalNfcTxRxContext* tx_rx,
+ MfUltralightReader* reader,
+ MfUltralightData* data) {
+ uint8_t curr_sector_index = 0xff;
+ reader->pages_read = 0;
+ while(reader->pages_read < reader->pages_to_read) {
+ uint8_t tag_sector;
+ int16_t valid_pages;
+ int16_t tag_page = mf_ultralight_ntag_i2c_addr_lin_to_tag(
+ data, reader, reader->pages_read, &tag_sector, &valid_pages);
+
+ furi_assert(tag_page != -1);
+ if(curr_sector_index != tag_sector) {
+ if(!mf_ultralight_sector_select(tx_rx, tag_sector)) return false;
+ curr_sector_index = tag_sector;
+ }
+
+ FURI_LOG_D(
+ TAG, "Reading pages %d - %d", reader->pages_read, reader->pages_read + valid_pages - 1);
+ tx_rx->tx_data[0] = MF_UL_FAST_READ_CMD;
+ tx_rx->tx_data[1] = tag_page;
+ tx_rx->tx_data[2] = valid_pages - 1;
+ tx_rx->tx_bits = 24;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+ if(furi_hal_nfc_tx_rx(tx_rx, 50)) {
+ memcpy(&data->data[reader->pages_read * 4], tx_rx->rx_data, valid_pages * 4);
+ reader->pages_read += valid_pages;
+ data->data_size = reader->pages_read * 4;
+ } else {
+ FURI_LOG_D(
+ TAG,
+ "Failed to read pages %d - %d",
+ reader->pages_read,
+ reader->pages_read + valid_pages - 1);
+ break;
+ }
+ }
+
+ return reader->pages_read == reader->pages_to_read;
+}
+
+bool mf_ultralight_read_signature(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) {
+ bool signature_read = false;
+
+ FURI_LOG_D(TAG, "Reading signature");
+ tx_rx->tx_data[0] = MF_UL_READ_SIG;
+ tx_rx->tx_data[1] = 0;
+ tx_rx->tx_bits = 16;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+ if(furi_hal_nfc_tx_rx(tx_rx, 50)) {
+ memcpy(data->signature, tx_rx->rx_data, sizeof(data->signature));
+ signature_read = true;
+ } else {
+ FURI_LOG_D(TAG, "Failed redaing signature");
+ }
+
+ return signature_read;
+}
+
+bool mf_ultralight_read_counters(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) {
+ uint8_t counter_read = 0;
+
+ FURI_LOG_D(TAG, "Reading counters");
+ bool is_single_counter = (mf_ul_get_features(data->type) & MfUltralightSupportSingleCounter) !=
+ 0;
+ for(size_t i = is_single_counter ? 2 : 0; i < 3; i++) {
+ tx_rx->tx_data[0] = MF_UL_READ_CNT;
+ tx_rx->tx_data[1] = i;
+ tx_rx->tx_bits = 16;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+ if(!furi_hal_nfc_tx_rx(tx_rx, 50)) {
+ FURI_LOG_D(TAG, "Failed to read %d counter", i);
+ break;
+ }
+ data->counter[i] = (tx_rx->rx_data[2] << 16) | (tx_rx->rx_data[1] << 8) |
+ tx_rx->rx_data[0];
+ counter_read++;
+ }
+
+ return counter_read == (is_single_counter ? 1 : 3);
+}
+
+bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) {
+ uint8_t flag_read = 0;
+
+ FURI_LOG_D(TAG, "Reading tearing flags");
+ for(size_t i = 0; i < 3; i++) {
+ tx_rx->tx_data[0] = MF_UL_CHECK_TEARING;
+ tx_rx->rx_data[1] = i;
+ tx_rx->tx_bits = 16;
+ tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+ if(!furi_hal_nfc_tx_rx(tx_rx, 50)) {
+ FURI_LOG_D(TAG, "Failed to read %d tearing flag", i);
+ break;
+ }
+ data->tearing[i] = tx_rx->rx_data[0];
+ flag_read++;
+ }
+
+ return flag_read == 2;
+}
+
+bool mf_ul_read_card(
+ FuriHalNfcTxRxContext* tx_rx,
+ MfUltralightReader* reader,
+ MfUltralightData* data) {
+ furi_assert(tx_rx);
+ furi_assert(reader);
+ furi_assert(data);
+
+ bool card_read = false;
+
+ // Read Mifare Ultralight version
+ if(mf_ultralight_read_version(tx_rx, reader, data)) {
+ if(reader->supported_features & MfUltralightSupportSignature) {
+ // Read Signature
+ mf_ultralight_read_signature(tx_rx, data);
+ }
+ } else {
+ // No GET_VERSION command, check for NTAG203 by reading last page (41)
+ uint8_t dummy[16];
+ if(mf_ultralight_read_pages_direct(tx_rx, 41, dummy)) {
+ mf_ul_set_version_ntag203(reader, data);
+ reader->supported_features = mf_ul_get_features(data->type);
+ } else {
+ // We're really an original Mifare Ultralight, reset tag for safety
+ furi_hal_nfc_sleep();
+ furi_hal_nfc_activate_nfca(300, NULL);
+ }
+ }
+
+ card_read = mf_ultralight_read_pages(tx_rx, reader, data);
+
+ if(card_read) {
+ if(reader->supported_features & MfUltralightSupportReadCounter &&
+ mf_ultralight_should_read_counters(data)) {
+ mf_ultralight_read_counters(tx_rx, data);
+ }
+ if(reader->supported_features & MfUltralightSupportTearingFlags) {
+ mf_ultralight_read_tearing_flags(tx_rx, data);
+ }
+ data->curr_authlim = 0;
+ }
+
+ return card_read;
+}
+
+static void mf_ul_protect_auth_data_on_read_command_i2c(
+ uint8_t* tx_buff,
+ uint8_t start_page,
+ uint8_t end_page,
+ MfUltralightEmulator* emulator) {
+ if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K) {
+ // Blank out PWD and PACK
+ if(start_page <= 229 && end_page >= 229) {
+ uint16_t offset = (229 - start_page) * 4;
+ uint8_t count = 4;
+ if(end_page >= 230) count += 2;
+ memset(&tx_buff[offset], 0, count);
+ }
+
+ // Handle AUTH0 for sector 0
+ if(!emulator->auth_success) {
+ if(emulator->config_cache.access.prot) {
+ uint8_t auth0 = emulator->config_cache.auth0;
+ if(auth0 < end_page) {
+ // start_page is always < auth0; otherwise is NAK'd already
+ uint8_t page_offset = auth0 - start_page;
+ uint8_t page_count = end_page - auth0;
+ memset(&tx_buff[page_offset * 4], 0, page_count * 4);
+ }
+ }
+ }
+ }
+}
+
+static void mf_ul_ntag_i2c_fill_cross_area_read(
+ uint8_t* tx_buff,
+ uint8_t start_page,
+ uint8_t end_page,
+ MfUltralightEmulator* emulator) {
+ // For copying config or session registers in fast read
+ int16_t tx_page_offset;
+ int16_t data_page_offset;
+ uint8_t page_length;
+ bool apply = false;
+ MfUltralightType type = emulator->data.type;
+ if(emulator->curr_sector == 0) {
+ if(type == MfUltralightTypeNTAGI2C1K) {
+ if(start_page <= 233 && end_page >= 232) {
+ tx_page_offset = start_page - 232;
+ data_page_offset = 227;
+ page_length = 2;
+ apply = true;
+ }
+ } else if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) {
+ if(start_page <= 237 && end_page >= 236) {
+ tx_page_offset = start_page - 236;
+ data_page_offset = 234;
+ page_length = 2;
+ apply = true;
+ }
+ }
+ } else if(emulator->curr_sector == 1) {
+ if(type == MfUltralightTypeNTAGI2C2K) {
+ if(start_page <= 233 && end_page >= 232) {
+ tx_page_offset = start_page - 232;
+ data_page_offset = 483;
+ page_length = 2;
+ apply = true;
+ }
+ }
+ }
+
+ if(apply) {
+ while(tx_page_offset < 0 && page_length > 0) {
+ ++tx_page_offset;
+ ++data_page_offset;
+ --page_length;
+ }
+ memcpy(
+ &tx_buff[tx_page_offset * 4],
+ &emulator->data.data[data_page_offset * 4],
+ page_length * 4);
+ }
+}
+
+static bool mf_ul_check_auth(MfUltralightEmulator* emulator, uint8_t start_page, bool is_write) {
+ if(!emulator->auth_success) {
+ if(start_page >= emulator->config_cache.auth0 &&
+ (emulator->config_cache.access.prot || is_write))
+ return false;
+ }
+
+ if(is_write && emulator->config_cache.access.cfglck) {
+ uint16_t config_start_page = emulator->page_num - 4;
+ if(start_page == config_start_page || start_page == config_start_page + 1) return false;
+ }
+
+ return true;
+}
+
+static bool mf_ul_ntag_i2c_plus_check_auth(
+ MfUltralightEmulator* emulator,
+ uint8_t start_page,
+ bool is_write) {
+ if(!emulator->auth_success) {
+ // Check NFC_PROT
+ if(emulator->curr_sector == 0 && (emulator->config_cache.access.prot || is_write)) {
+ if(start_page >= emulator->config_cache.auth0) return false;
+ } else if(emulator->curr_sector == 1) {
+ // We don't have to specifically check for type because this is done
+ // by address translator
+ uint8_t pt_i2c = emulator->data.data[231 * 4];
+ // Check 2K_PROT
+ if(pt_i2c & 0x08) return false;
+ }
+ }
+
+ if(emulator->curr_sector == 1) {
+ // Check NFC_DIS_SEC1
+ if(emulator->config_cache.access.nfc_dis_sec1) return false;
+ }
+
+ return true;
+}
+
+static int16_t mf_ul_get_dynamic_lock_page_addr(MfUltralightData* data) {
+ switch(data->type) {
+ case MfUltralightTypeNTAG203:
+ return 0x28;
+ case MfUltralightTypeUL21:
+ case MfUltralightTypeNTAG213:
+ case MfUltralightTypeNTAG215:
+ case MfUltralightTypeNTAG216:
+ return data->data_size / 4 - 5;
+ case MfUltralightTypeNTAGI2C1K:
+ case MfUltralightTypeNTAGI2CPlus1K:
+ case MfUltralightTypeNTAGI2CPlus2K:
+ return 0xe2;
+ case MfUltralightTypeNTAGI2C2K:
+ return 0x1e0;
+ default:
+ return -1; // No dynamic lock bytes
+ }
+}
+
+// Returns true if page not locked
+// write_page is tag address
+static bool mf_ul_check_lock(MfUltralightEmulator* emulator, int16_t write_page) {
+ if(write_page < 2) return false; // Page 0-1 is always locked
+ if(write_page == 2) return true; // Page 2 does not have a lock flag
+
+ // Check static lock bytes
+ if(write_page <= 15) {
+ uint16_t static_lock_bytes = emulator->data.data[10] | (emulator->data.data[11] << 8);
+ return (static_lock_bytes & (1 << write_page)) == 0;
+ }
+
+ // Check dynamic lock bytes
+
+ // Check max page
+ switch(emulator->data.type) {
+ case MfUltralightTypeNTAG203:
+ // Counter page can be locked and is after dynamic locks
+ if(write_page == 40) return true;
+ break;
+ case MfUltralightTypeUL21:
+ case MfUltralightTypeNTAG213:
+ case MfUltralightTypeNTAG215:
+ case MfUltralightTypeNTAG216:
+ if(write_page >= emulator->page_num - 5) return true;
+ break;
+ case MfUltralightTypeNTAGI2C1K:
+ case MfUltralightTypeNTAGI2CPlus1K:
+ if(write_page > 225) return true;
+ break;
+ case MfUltralightTypeNTAGI2C2K:
+ if(write_page > 479) return true;
+ break;
+ case MfUltralightTypeNTAGI2CPlus2K:
+ if(write_page >= 226 && write_page <= 255) return true;
+ if(write_page >= 512) return true;
+ break;
+ default:
+ furi_assert(false);
+ return true;
+ }
+
+ int16_t dynamic_lock_index = mf_ul_get_dynamic_lock_page_addr(&emulator->data);
+ if(dynamic_lock_index == -1) return true;
+ // Run address through converter because NTAG I2C 2K is special
+ uint16_t valid_pages; // unused
+ dynamic_lock_index =
+ mf_ultralight_ntag_i2c_addr_tag_to_lin(
+ &emulator->data, dynamic_lock_index & 0xff, dynamic_lock_index >> 8, &valid_pages) *
+ 4;
+
+ uint16_t dynamic_lock_bytes = emulator->data.data[dynamic_lock_index] |
+ (emulator->data.data[dynamic_lock_index + 1] << 8);
+ uint8_t shift;
+
+ switch(emulator->data.type) {
+ // low byte LSB range, MSB range
+ case MfUltralightTypeNTAG203:
+ if(write_page >= 16 && write_page <= 27)
+ shift = (write_page - 16) / 4 + 1;
+ else if(write_page >= 28 && write_page <= 39)
+ shift = (write_page - 28) / 4 + 5;
+ else if(write_page == 41)
+ shift = 12;
+ else {
+ furi_assert(false);
+ shift = 0;
+ }
+
+ break;
+ case MfUltralightTypeUL21:
+ case MfUltralightTypeNTAG213:
+ // 16-17, 30-31
+ shift = (write_page - 16) / 2;
+ break;
+ case MfUltralightTypeNTAG215:
+ case MfUltralightTypeNTAG216:
+ case MfUltralightTypeNTAGI2C1K:
+ case MfUltralightTypeNTAGI2CPlus1K:
+ // 16-31, 128-129
+ // 16-31, 128-143
+ shift = (write_page - 16) / 16;
+ break;
+ case MfUltralightTypeNTAGI2C2K:
+ // 16-47, 240-271
+ shift = (write_page - 16) / 32;
+ break;
+ case MfUltralightTypeNTAGI2CPlus2K:
+ // 16-47, 256-271
+ if(write_page >= 208 && write_page <= 225)
+ shift = 6;
+ else if(write_page >= 256 && write_page <= 271)
+ shift = 7;
+ else
+ shift = (write_page - 16) / 32;
+ break;
+ default:
+ furi_assert(false);
+ shift = 0;
+ break;
+ }
+
+ return (dynamic_lock_bytes & (1 << shift)) == 0;
+}
+
+static void mf_ul_make_ascii_mirror(MfUltralightEmulator* emulator, string_t str) {
+ // Locals to improve readability
+ uint8_t mirror_page = emulator->config->mirror_page;
+ uint8_t mirror_byte = emulator->config->mirror.mirror_byte;
+ MfUltralightMirrorConf mirror_conf = emulator->config_cache.mirror.mirror_conf;
+ uint16_t last_user_page_index = emulator->page_num - 6;
+ bool uid_printed = false;
+
+ if(mirror_conf == MfUltralightMirrorUid || mirror_conf == MfUltralightMirrorUidCounter) {
+ // UID range check
+ if(mirror_page < 4 || mirror_page > last_user_page_index - 3 ||
+ (mirror_page == last_user_page_index - 3 && mirror_byte > 2)) {
+ if(mirror_conf == MfUltralightMirrorUid) return;
+ // NTAG21x has the peculiar behavior when UID+counter selected, if UID does not fit but
+ // counter will fit, it will actually mirror the counter
+ string_cat_str(str, " ");
+ } else {
+ for(int i = 0; i < 3; ++i) {
+ string_cat_printf(str, "%02X", emulator->data.data[i]);
+ }
+ // Skip BCC0
+ for(int i = 4; i < 8; ++i) {
+ string_cat_printf(str, "%02X", emulator->data.data[i]);
+ }
+ uid_printed = true;
+ }
+
+ uint16_t next_byte_offset = mirror_page * 4 + mirror_byte + 14;
+ if(mirror_conf == MfUltralightMirrorUidCounter) ++next_byte_offset;
+ mirror_page = next_byte_offset / 4;
+ mirror_byte = next_byte_offset % 4;
+ }
+
+ if(mirror_conf == MfUltralightMirrorCounter || mirror_conf == MfUltralightMirrorUidCounter) {
+ // Counter is only printed if counter enabled
+ if(emulator->config_cache.access.nfc_cnt_en) {
+ // Counter protection check
+ if(emulator->config_cache.access.nfc_cnt_pwd_prot && !emulator->auth_success) return;
+ // Counter range check
+ if(mirror_page < 4) return;
+ if(mirror_page > last_user_page_index - 1) return;
+ if(mirror_page == last_user_page_index - 1 && mirror_byte > 2) return;
+
+ if(mirror_conf == MfUltralightMirrorUidCounter)
+ string_cat_str(str, uid_printed ? "x" : " ");
+
+ string_cat_printf(str, "%06X", emulator->data.counter[2]);
+ }
+ }
+}
+
+static void mf_ul_increment_single_counter(MfUltralightEmulator* emulator) {
+ if(!emulator->read_counter_incremented && emulator->config_cache.access.nfc_cnt_en) {
+ if(emulator->data.counter[2] < 0xFFFFFF) {
+ ++emulator->data.counter[2];
+ emulator->data_changed = true;
+ }
+ emulator->read_counter_incremented = true;
+ }
+}
+
+static bool
+ mf_ul_emulate_ntag203_counter_write(MfUltralightEmulator* emulator, uint8_t* page_buff) {
+ // We'll reuse the existing counters for other NTAGs as staging
+ // Counter 0 stores original value, data is new value
+ uint32_t counter_value;
+ if(emulator->data.tearing[0] == MF_UL_TEARING_FLAG_DEFAULT) {
+ counter_value = emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] |
+ (emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] << 8);
+ } else {
+ // We've had a reset here, so load from original value
+ counter_value = emulator->data.counter[0];
+ }
+ // Although the datasheet says increment by 0 is always possible, this is not the case on
+ // an actual tag. If the counter is at 0xFFFF, any writes are locked out.
+ if(counter_value == 0xFFFF) return false;
+ uint32_t increment = page_buff[0] | (page_buff[1] << 8);
+ if(counter_value == 0) {
+ counter_value = increment;
+ } else {
+ // Per datasheet specifying > 0x000F is supposed to NAK, but actual tag doesn't
+ increment &= 0x000F;
+ if(counter_value + increment > 0xFFFF) return false;
+ counter_value += increment;
+ }
+ // Commit to new value counter
+ emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] = (uint8_t)counter_value;
+ emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] = (uint8_t)(counter_value >> 8);
+ emulator->data.tearing[0] = MF_UL_TEARING_FLAG_DEFAULT;
+ if(counter_value == 0xFFFF) {
+ // Tag will lock out counter if final number is 0xFFFF, even if you try to roll it back
+ emulator->data.counter[1] = 0xFFFF;
+ }
+ emulator->data_changed = true;
+ return true;
+}
+
+static void mf_ul_emulate_write(
+ MfUltralightEmulator* emulator,
+ int16_t tag_addr,
+ int16_t write_page,
+ uint8_t* page_buff) {
+ // Assumption: all access checks have been completed
+
+ if(tag_addr == 2) {
+ // Handle static locks
+ uint16_t orig_static_locks = emulator->data.data[write_page * 4 + 2] |
+ (emulator->data.data[write_page * 4 + 3] << 8);
+ uint16_t new_static_locks = page_buff[2] | (page_buff[3] << 8);
+ if(orig_static_locks & 1) new_static_locks &= ~0x08;
+ if(orig_static_locks & 2) new_static_locks &= ~0xF0;
+ if(orig_static_locks & 4) new_static_locks &= 0xFF;
+ new_static_locks |= orig_static_locks;
+ page_buff[0] = emulator->data.data[write_page * 4];
+ page_buff[1] = emulator->data.data[write_page * 4 + 1];
+ page_buff[2] = new_static_locks & 0xff;
+ page_buff[3] = new_static_locks >> 8;
+ } else if(tag_addr == 3) {
+ // Handle OTP/capability container
+ *(uint32_t*)page_buff |= *(uint32_t*)&emulator->data.data[write_page * 4];
+ } else if(tag_addr == mf_ul_get_dynamic_lock_page_addr(&emulator->data)) {
+ // Handle dynamic locks
+ if(emulator->data.type == MfUltralightTypeNTAG203) {
+ // NTAG203 lock bytes are a bit different from the others
+ uint8_t orig_page_lock_byte = emulator->data.data[write_page * 4];
+ uint8_t orig_cnt_lock_byte = emulator->data.data[write_page * 4 + 1];
+ uint8_t new_page_lock_byte = page_buff[0];
+ uint8_t new_cnt_lock_byte = page_buff[1];
+
+ if(orig_page_lock_byte & 0x01) // Block lock bits 1-3
+ new_page_lock_byte &= ~0x0E;
+ if(orig_page_lock_byte & 0x10) // Block lock bits 5-7
+ new_page_lock_byte &= ~0xE0;
+ for(uint8_t i = 0; i < 4; ++i) {
+ if(orig_cnt_lock_byte & (1 << i)) // Block lock counter bit
+ new_cnt_lock_byte &= ~(1 << (4 + i));
+ }
+
+ new_page_lock_byte |= orig_page_lock_byte;
+ new_cnt_lock_byte |= orig_cnt_lock_byte;
+ page_buff[0] = new_page_lock_byte;
+ page_buff[1] = new_cnt_lock_byte;
+ } else {
+ uint16_t orig_locks = emulator->data.data[write_page * 4] |
+ (emulator->data.data[write_page * 4 + 1] << 8);
+ uint8_t orig_block_locks = emulator->data.data[write_page * 4 + 2];
+ uint16_t new_locks = page_buff[0] | (page_buff[1] << 8);
+ uint8_t new_block_locks = page_buff[2];
+
+ int block_lock_count;
+ switch(emulator->data.type) {
+ case MfUltralightTypeUL21:
+ block_lock_count = 5;
+ break;
+ case MfUltralightTypeNTAG213:
+ block_lock_count = 6;
+ break;
+ case MfUltralightTypeNTAG215:
+ block_lock_count = 4;
+ break;
+ case MfUltralightTypeNTAG216:
+ case MfUltralightTypeNTAGI2C1K:
+ case MfUltralightTypeNTAGI2CPlus1K:
+ block_lock_count = 7;
+ break;
+ case MfUltralightTypeNTAGI2C2K:
+ case MfUltralightTypeNTAGI2CPlus2K:
+ block_lock_count = 8;
+ break;
+ default:
+ furi_assert(false);
+ block_lock_count = 0;
+ break;
+ }
+
+ for(int i = 0; i < block_lock_count; ++i) {
+ if(orig_block_locks & (1 << i)) new_locks &= ~(3 << (2 * i));
+ }
+
+ new_locks |= orig_locks;
+ new_block_locks |= orig_block_locks;
+
+ page_buff[0] = new_locks & 0xff;
+ page_buff[1] = new_locks >> 8;
+ page_buff[2] = new_block_locks;
+ if(emulator->data.type >= MfUltralightTypeUL21 &&
+ emulator->data.type <= MfUltralightTypeNTAG216)
+ page_buff[3] = MF_UL_TEARING_FLAG_DEFAULT;
+ else
+ page_buff[3] = 0;
+ }
+ }
+
+ memcpy(&emulator->data.data[write_page * 4], page_buff, 4);
+ emulator->data_changed = true;
+}
+
+void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle) {
+ emulator->curr_sector = 0;
+ emulator->ntag_i2c_plus_sector3_lockout = false;
+ emulator->auth_success = false;
+ if(is_power_cycle) {
+ if(emulator->config != NULL) emulator->config_cache = *emulator->config;
+
+ if(emulator->supported_features & MfUltralightSupportSingleCounter) {
+ emulator->read_counter_incremented = false;
+ }
+
+ if(emulator->data.type == MfUltralightTypeNTAG203) {
+ // Apply lockout if counter ever reached 0xFFFF
+ if(emulator->data.counter[1] == 0xFFFF) {
+ emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] = 0xFF;
+ emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] = 0xFF;
+ }
+ // Copy original counter value from data
+ emulator->data.counter[0] =
+ emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] |
+ (emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] << 8);
+ }
+ } else {
+ if(emulator->config != NULL) {
+ // ACCESS (less CFGLCK) and AUTH0 are updated when reactivated
+ // MIRROR_CONF is not; don't know about STRG_MOD_EN, but we're not using that anyway
+ emulator->config_cache.access.value = (emulator->config->access.value & 0xBF) |
+ (emulator->config_cache.access.value & 0x40);
+ emulator->config_cache.auth0 = emulator->config->auth0;
+ }
+ }
+ if(emulator->data.type == MfUltralightTypeNTAG203) {
+ // Mark counter as dirty
+ emulator->data.tearing[0] = 0;
+ }
+}
+
+void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data) {
+ FURI_LOG_D(TAG, "Prepare emulation");
+ emulator->data = *data;
+ emulator->supported_features = mf_ul_get_features(data->type);
+ emulator->config = mf_ultralight_get_config_pages(&emulator->data);
+ emulator->page_num = emulator->data.data_size / 4;
+ emulator->data_changed = false;
+ emulator->comp_write_cmd_started = false;
+ emulator->sector_select_cmd_started = false;
+ mf_ul_reset_emulation(emulator, true);
+}
+
+bool mf_ul_prepare_emulation_response(
+ uint8_t* buff_rx,
+ uint16_t buff_rx_len,
+ uint8_t* buff_tx,
+ uint16_t* buff_tx_len,
+ uint32_t* data_type,
+ void* context) {
+ furi_assert(context);
+ MfUltralightEmulator* emulator = context;
+ uint16_t tx_bytes = 0;
+ uint16_t tx_bits = 0;
+ bool command_parsed = false;
+ bool send_ack = false;
+ bool respond_nothing = false;
+ bool reset_idle = false;
+
+#ifdef FURI_DEBUG
+ string_t debug_buf;
+ string_init(debug_buf);
+ for(int i = 0; i < (buff_rx_len + 7) / 8; ++i) {
+ string_cat_printf(debug_buf, "%02x ", buff_rx[i]);
+ }
+ string_strim(debug_buf);
+ FURI_LOG_T(TAG, "Emu RX (%d): %s", buff_rx_len, string_get_cstr(debug_buf));
+ string_reset(debug_buf);
+#endif
+
+ // Check composite commands
+ if(emulator->comp_write_cmd_started) {
+ if(buff_rx_len == 16 * 8) {
+ if(emulator->data.type == MfUltralightTypeNTAG203 &&
+ emulator->comp_write_page_addr == MF_UL_NTAG203_COUNTER_PAGE) {
+ send_ack = mf_ul_emulate_ntag203_counter_write(emulator, buff_rx);
+ command_parsed = send_ack;
+ } else {
+ mf_ul_emulate_write(
+ emulator,
+ emulator->comp_write_page_addr,
+ emulator->comp_write_page_addr,
+ buff_rx);
+ send_ack = true;
+ command_parsed = true;
+ }
+ }
+ emulator->comp_write_cmd_started = false;
+ } else if(emulator->sector_select_cmd_started) {
+ if(buff_rx_len == 4 * 8) {
+ if(buff_rx[0] <= 0xFE) {
+ emulator->curr_sector = buff_rx[0] > 3 ? 0 : buff_rx[0];
+ emulator->ntag_i2c_plus_sector3_lockout = false;
+ command_parsed = true;
+ respond_nothing = true;
+ FURI_LOG_D(TAG, "Changing sector to %d", emulator->curr_sector);
+ }
+ }
+ emulator->sector_select_cmd_started = false;
+ } else if(buff_rx_len >= 8) {
+ uint8_t cmd = buff_rx[0];
+ if(cmd == MF_UL_GET_VERSION_CMD) {
+ if(emulator->data.type >= MfUltralightTypeUL11) {
+ if(buff_rx_len == 1 * 8) {
+ tx_bytes = sizeof(emulator->data.version);
+ memcpy(buff_tx, &emulator->data.version, tx_bytes);
+ *data_type = FURI_HAL_NFC_TXRX_DEFAULT;
+ command_parsed = true;
+ }
+ }
+ } else if(cmd == MF_UL_READ_CMD) {
+ if(buff_rx_len == (1 + 1) * 8) {
+ int16_t start_page = buff_rx[1];
+ tx_bytes = 16;
+ if(emulator->data.type < MfUltralightTypeNTAGI2C1K) {
+ if(start_page < emulator->page_num) {
+ do {
+ uint8_t copied_pages = 0;
+ uint8_t src_page = start_page;
+ uint8_t last_page_plus_one = start_page + 4;
+ uint8_t pwd_page = emulator->page_num - 2;
+ string_t ascii_mirror;
+ size_t ascii_mirror_len = 0;
+ const char* ascii_mirror_cptr = NULL;
+ uint8_t ascii_mirror_curr_page = 0;
+ uint8_t ascii_mirror_curr_byte = 0;
+ if(last_page_plus_one > emulator->page_num)
+ last_page_plus_one = emulator->page_num;
+ if(emulator->supported_features & MfUltralightSupportAuth) {
+ if(!mf_ul_check_auth(emulator, start_page, false)) break;
+ if(!emulator->auth_success && emulator->config_cache.access.prot &&
+ emulator->config_cache.auth0 < last_page_plus_one)
+ last_page_plus_one = emulator->config_cache.auth0;
+ }
+ if(emulator->supported_features & MfUltralightSupportSingleCounter)
+ mf_ul_increment_single_counter(emulator);
+ if(emulator->supported_features & MfUltralightSupportAsciiMirror &&
+ emulator->config_cache.mirror.mirror_conf !=
+ MfUltralightMirrorNone) {
+ ascii_mirror_curr_byte = emulator->config->mirror.mirror_byte;
+ ascii_mirror_curr_page = emulator->config->mirror_page;
+ // Try to avoid wasting time making mirror if we won't copy it
+ // Conservatively check with UID+counter mirror size
+ if(last_page_plus_one > ascii_mirror_curr_page &&
+ start_page + 3 >= ascii_mirror_curr_page &&
+ start_page <= ascii_mirror_curr_page + 6) {
+ string_init(ascii_mirror);
+ mf_ul_make_ascii_mirror(emulator, ascii_mirror);
+ ascii_mirror_len = string_length_u(ascii_mirror);
+ ascii_mirror_cptr = string_get_cstr(ascii_mirror);
+ // Move pointer to where it should be to start copying
+ if(ascii_mirror_len > 0 &&
+ ascii_mirror_curr_page < start_page &&
+ ascii_mirror_curr_byte != 0) {
+ uint8_t diff = 4 - ascii_mirror_curr_byte;
+ ascii_mirror_len -= diff;
+ ascii_mirror_cptr += diff;
+ ascii_mirror_curr_byte = 0;
+ ++ascii_mirror_curr_page;
+ }
+ while(ascii_mirror_len > 0 &&
+ ascii_mirror_curr_page < start_page) {
+ uint8_t diff = ascii_mirror_len > 4 ? 4 : ascii_mirror_len;
+ ascii_mirror_len -= diff;
+ ascii_mirror_cptr += diff;
+ ++ascii_mirror_curr_page;
+ }
+ }
+ }
+
+ uint8_t* dest_ptr = buff_tx;
+ while(copied_pages < 4) {
+ // Copy page
+ memcpy(dest_ptr, &emulator->data.data[src_page * 4], 4);
+
+ // Note: don't have to worry about roll-over with ASCII mirror because
+ // lowest valid page for it is 4, while roll-over will at best read
+ // pages 0-2
+ if(ascii_mirror_len > 0 && src_page == ascii_mirror_curr_page) {
+ // Copy ASCII mirror
+ size_t copy_len = 4 - ascii_mirror_curr_byte;
+ if(copy_len > ascii_mirror_len) copy_len = ascii_mirror_len;
+ for(size_t i = 0; i < copy_len; ++i) {
+ if(*ascii_mirror_cptr != ' ')
+ dest_ptr[ascii_mirror_curr_byte] =
+ (uint8_t)*ascii_mirror_cptr;
+ ++ascii_mirror_curr_byte;
+ ++ascii_mirror_cptr;
+ }
+ ascii_mirror_len -= copy_len;
+ // Don't care if this is inaccurate after ascii_mirror_len = 0
+ ascii_mirror_curr_byte = 0;
+ ++ascii_mirror_curr_page;
+ }
+
+ if(emulator->supported_features & MfUltralightSupportAuth) {
+ if(src_page == pwd_page || src_page == pwd_page + 1) {
+ // Blank out PWD and PACK pages
+ memset(dest_ptr, 0, 4);
+ }
+ }
+
+ dest_ptr += 4;
+ ++copied_pages;
+ ++src_page;
+ if(src_page >= last_page_plus_one) src_page = 0;
+ }
+ if(ascii_mirror_cptr != NULL) {
+ string_clear(ascii_mirror);
+ }
+ *data_type = FURI_HAL_NFC_TXRX_DEFAULT;
+ command_parsed = true;
+ } while(false);
+ }
+ } else {
+ uint16_t valid_pages;
+ start_page = mf_ultralight_ntag_i2c_addr_tag_to_lin(
+ &emulator->data, start_page, emulator->curr_sector, &valid_pages);
+ if(start_page != -1) {
+ if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K ||
+ mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], false)) {
+ if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K &&
+ emulator->curr_sector == 3 && valid_pages == 1) {
+ // Rewind back a sector to match behavior on a real tag
+ --start_page;
+ ++valid_pages;
+ }
+
+ uint16_t copy_count = (valid_pages > 4 ? 4 : valid_pages) * 4;
+ FURI_LOG_D(
+ TAG,
+ "NTAG I2C Emu: page valid, %02x:%02x -> %d, %d",
+ emulator->curr_sector,
+ buff_rx[1],
+ start_page,
+ valid_pages);
+ memcpy(buff_tx, &emulator->data.data[start_page * 4], copy_count);
+ // For NTAG I2C, there's no roll-over; remainder is filled by null bytes
+ if(copy_count < tx_bytes)
+ memset(&buff_tx[copy_count], 0, tx_bytes - copy_count);
+ // Special case: NTAG I2C Plus sector 0 page 233 read crosses into page 236
+ if(start_page == 233)
+ memcpy(
+ &buff_tx[12], &emulator->data.data[(start_page + 1) * 4], 4);
+ mf_ul_protect_auth_data_on_read_command_i2c(
+ buff_tx, start_page, start_page + copy_count / 4 - 1, emulator);
+ *data_type = FURI_HAL_NFC_TXRX_DEFAULT;
+ command_parsed = true;
+ }
+ } else {
+ FURI_LOG_D(
+ TAG,
+ "NTAG I2C Emu: page invalid, %02x:%02x",
+ emulator->curr_sector,
+ buff_rx[1]);
+ if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K &&
+ emulator->curr_sector == 3 &&
+ !emulator->ntag_i2c_plus_sector3_lockout) {
+ // NTAG I2C Plus has a weird behavior where if you read sector 3
+ // at an invalid address, it responds with zeroes then locks
+ // the read out, while if you read the mirrored session registers,
+ // it returns both session registers on either pages
+ memset(buff_tx, 0, tx_bytes);
+ *data_type = FURI_HAL_NFC_TXRX_DEFAULT;
+ command_parsed = true;
+ emulator->ntag_i2c_plus_sector3_lockout = true;
+ }
+ }
+ }
+ if(!command_parsed) tx_bytes = 0;
+ }
+ } else if(cmd == MF_UL_FAST_READ_CMD) {
+ if(emulator->supported_features & MfUltralightSupportFastRead) {
+ if(buff_rx_len == (1 + 2) * 8) {
+ int16_t start_page = buff_rx[1];
+ uint8_t end_page = buff_rx[2];
+ if(start_page <= end_page) {
+ tx_bytes = ((end_page + 1) - start_page) * 4;
+ if(emulator->data.type < MfUltralightTypeNTAGI2C1K) {
+ if((start_page < emulator->page_num) &&
+ (end_page < emulator->page_num)) {
+ do {
+ if(emulator->supported_features & MfUltralightSupportAuth) {
+ // NAK if not authenticated and requested pages cross over AUTH0
+ if(!emulator->auth_success &&
+ emulator->config_cache.access.prot &&
+ (start_page >= emulator->config_cache.auth0 ||
+ end_page >= emulator->config_cache.auth0))
+ break;
+ }
+ if(emulator->supported_features &
+ MfUltralightSupportSingleCounter)
+ mf_ul_increment_single_counter(emulator);
+
+ // Copy requested pages
+ memcpy(
+ buff_tx, &emulator->data.data[start_page * 4], tx_bytes);
+
+ if(emulator->supported_features &
+ MfUltralightSupportAsciiMirror &&
+ emulator->config_cache.mirror.mirror_conf !=
+ MfUltralightMirrorNone) {
+ // Copy ASCII mirror
+ // Less stringent check here, because expecting FAST_READ to
+ // only be issued once rather than repeatedly
+ string_t ascii_mirror;
+ string_init(ascii_mirror);
+ mf_ul_make_ascii_mirror(emulator, ascii_mirror);
+ size_t ascii_mirror_len = string_length_u(ascii_mirror);
+ const char* ascii_mirror_cptr =
+ string_get_cstr(ascii_mirror);
+ int16_t mirror_start_offset =
+ (emulator->config->mirror_page - start_page) * 4 +
+ emulator->config->mirror.mirror_byte;
+ if(mirror_start_offset < 0) {
+ if(mirror_start_offset < -(int16_t)ascii_mirror_len) {
+ // Past ASCII mirror, don't copy
+ ascii_mirror_len = 0;
+ } else {
+ ascii_mirror_cptr += -mirror_start_offset;
+ ascii_mirror_len -= -mirror_start_offset;
+ mirror_start_offset = 0;
+ }
+ }
+ if(ascii_mirror_len > 0) {
+ int16_t mirror_end_offset =
+ mirror_start_offset + ascii_mirror_len;
+ if(mirror_end_offset > (end_page + 1) * 4) {
+ mirror_end_offset = (end_page + 1) * 4;
+ ascii_mirror_len =
+ mirror_end_offset - mirror_start_offset;
+ }
+ for(size_t i = 0; i < ascii_mirror_len; ++i) {
+ if(*ascii_mirror_cptr != ' ')
+ buff_tx[mirror_start_offset] =
+ (uint8_t)*ascii_mirror_cptr;
+ ++mirror_start_offset;
+ ++ascii_mirror_cptr;
+ }
+ }
+ string_clear(ascii_mirror);
+ }
+
+ if(emulator->supported_features & MfUltralightSupportAuth) {
+ // Clear PWD and PACK pages
+ uint8_t pwd_page = emulator->page_num - 2;
+ int16_t pwd_page_offset = pwd_page - start_page;
+ // PWD page
+ if(pwd_page_offset >= 0 && pwd_page <= end_page) {
+ memset(&buff_tx[pwd_page_offset * 4], 0, 4);
+ // PACK page
+ if(pwd_page + 1 <= end_page)
+ memset(&buff_tx[(pwd_page_offset + 1) * 4], 0, 4);
+ }
+ }
+ *data_type = FURI_HAL_NFC_TXRX_DEFAULT;
+ command_parsed = true;
+ } while(false);
+ }
+ } else {
+ uint16_t valid_pages;
+ start_page = mf_ultralight_ntag_i2c_addr_tag_to_lin(
+ &emulator->data, start_page, emulator->curr_sector, &valid_pages);
+ if(start_page != -1) {
+ if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K ||
+ mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], false)) {
+ uint16_t copy_count = tx_bytes;
+ if(copy_count > valid_pages * 4) copy_count = valid_pages * 4;
+ memcpy(
+ buff_tx, &emulator->data.data[start_page * 4], copy_count);
+ if(copy_count < tx_bytes)
+ memset(&buff_tx[copy_count], 0, tx_bytes - copy_count);
+ mf_ul_ntag_i2c_fill_cross_area_read(
+ buff_tx, buff_rx[1], buff_rx[2], emulator);
+ mf_ul_protect_auth_data_on_read_command_i2c(
+ buff_tx,
+ start_page,
+ start_page + copy_count / 4 - 1,
+ emulator);
+ *data_type = FURI_HAL_NFC_TXRX_DEFAULT;
+ command_parsed = true;
+ }
+ }
+ }
+ if(!command_parsed) tx_bytes = 0;
+ }
+ }
+ }
+ } else if(cmd == MF_UL_WRITE) {
+ if(buff_rx_len == (1 + 5) * 8) {
+ do {
+ uint8_t orig_write_page = buff_rx[1];
+ int16_t write_page = orig_write_page;
+ uint16_t valid_pages; // unused
+ write_page = mf_ultralight_ntag_i2c_addr_tag_to_lin(
+ &emulator->data, write_page, emulator->curr_sector, &valid_pages);
+ if(write_page == -1) // NTAG I2C range check
+ break;
+ else if(write_page < 2 || write_page >= emulator->page_num) // Other MFUL/NTAG range check
+ break;
+
+ if(emulator->supported_features & MfUltralightSupportAuth) {
+ if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K) {
+ if(!mf_ul_ntag_i2c_plus_check_auth(emulator, orig_write_page, true))
+ break;
+ } else {
+ if(!mf_ul_check_auth(emulator, orig_write_page, true)) break;
+ }
+ }
+ int16_t tag_addr = mf_ultralight_page_addr_to_tag_addr(
+ emulator->curr_sector, orig_write_page);
+ if(!mf_ul_check_lock(emulator, tag_addr)) break;
+ if(emulator->data.type == MfUltralightTypeNTAG203 &&
+ orig_write_page == MF_UL_NTAG203_COUNTER_PAGE) {
+ send_ack = mf_ul_emulate_ntag203_counter_write(emulator, &buff_rx[2]);
+ command_parsed = send_ack;
+ } else {
+ mf_ul_emulate_write(emulator, tag_addr, write_page, &buff_rx[2]);
+ send_ack = true;
+ command_parsed = true;
+ }
+ } while(false);
+ }
+ } else if(cmd == MF_UL_FAST_WRITE) {
+ if(emulator->supported_features & MfUltralightSupportFastWrite) {
+ if(buff_rx_len == (1 + 66) * 8) {
+ if(buff_rx[1] == 0xF0 && buff_rx[2] == 0xFF) {
+ // TODO: update when SRAM emulation implemented
+ send_ack = true;
+ command_parsed = true;
+ }
+ }
+ }
+ } else if(cmd == MF_UL_COMP_WRITE) {
+ if(emulator->supported_features & MfUltralightSupportCompatWrite) {
+ if(buff_rx_len == (1 + 1) * 8) {
+ uint8_t write_page = buff_rx[1];
+ do {
+ if(write_page < 2 || write_page >= emulator->page_num) break;
+ if(emulator->supported_features & MfUltralightSupportAuth &&
+ !mf_ul_check_auth(emulator, write_page, true))
+ break;
+ // Note we don't convert to tag addr here because there's only one sector
+ if(!mf_ul_check_lock(emulator, write_page)) break;
+
+ emulator->comp_write_cmd_started = true;
+ emulator->comp_write_page_addr = write_page;
+ send_ack = true;
+ command_parsed = true;
+ } while(false);
+ }
+ }
+ } else if(cmd == MF_UL_READ_CNT) {
+ if(emulator->supported_features & MfUltralightSupportReadCounter) {
+ if(buff_rx_len == (1 + 1) * 8) {
+ do {
+ uint8_t cnt_num = buff_rx[1];
+
+ // NTAG21x checks
+ if(emulator->supported_features & MfUltralightSupportSingleCounter) {
+ if(cnt_num != 2) break; // Only counter 2 is available
+ if(!emulator->config_cache.access.nfc_cnt_en)
+ break; // NAK if counter not enabled
+ if(emulator->config_cache.access.nfc_cnt_pwd_prot &&
+ !emulator->auth_success)
+ break;
+ }
+
+ if(cnt_num < 3) {
+ buff_tx[0] = emulator->data.counter[cnt_num] & 0xFF;
+ buff_tx[1] = (emulator->data.counter[cnt_num] >> 8) & 0xFF;
+ buff_tx[2] = (emulator->data.counter[cnt_num] >> 16) & 0xFF;
+ tx_bytes = 3;
+ *data_type = FURI_HAL_NFC_TXRX_DEFAULT;
+ command_parsed = true;
+ }
+ } while(false);
+ }
+ }
+ } else if(cmd == MF_UL_INC_CNT) {
+ if(emulator->supported_features & MfUltralightSupportIncrCounter) {
+ if(buff_rx_len == (1 + 5) * 8) {
+ uint8_t cnt_num = buff_rx[1];
+ uint32_t inc = (buff_rx[2] | (buff_rx[3] << 8) | (buff_rx[4] << 16));
+ // TODO: can you increment by 0 when counter is at 0xffffff?
+ if((cnt_num < 3) && (emulator->data.counter[cnt_num] != 0x00FFFFFF) &&
+ (emulator->data.counter[cnt_num] + inc <= 0x00FFFFFF)) {
+ emulator->data.counter[cnt_num] += inc;
+ // We're RAM-backed, so tearing never happens
+ emulator->data.tearing[cnt_num] = MF_UL_TEARING_FLAG_DEFAULT;
+ emulator->data_changed = true;
+ send_ack = true;
+ command_parsed = true;
+ }
+ }
+ }
+ } else if(cmd == MF_UL_AUTH) {
+ if(emulator->supported_features & MfUltralightSupportAuth) {
+ if(buff_rx_len == (1 + 4) * 8) {
+ uint16_t scaled_authlim = mf_ultralight_calc_auth_count(&emulator->data);
+ if(scaled_authlim != 0 && emulator->data.curr_authlim >= scaled_authlim) {
+ if(emulator->data.curr_authlim != UINT16_MAX) {
+ // Handle case where AUTHLIM has been lowered or changed from 0
+ emulator->data.curr_authlim = UINT16_MAX;
+ emulator->data_changed = true;
+ }
+ // AUTHLIM reached, always fail
+ buff_tx[0] = MF_UL_NAK_AUTHLIM_REACHED;
+ tx_bits = 4;
+ *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT;
+ mf_ul_reset_emulation(emulator, false);
+ command_parsed = true;
+ } else {
+ if(memcmp(&buff_rx[1], emulator->config->auth_data.pwd.raw, 4) == 0) {
+ // Correct password
+ buff_tx[0] = emulator->config->auth_data.pack.raw[0];
+ buff_tx[1] = emulator->config->auth_data.pack.raw[1];
+ tx_bytes = 2;
+ *data_type = FURI_HAL_NFC_TXRX_DEFAULT;
+ emulator->auth_success = true;
+ command_parsed = true;
+ if(emulator->data.curr_authlim != 0) {
+ // Reset current AUTHLIM
+ emulator->data.curr_authlim = 0;
+ emulator->data_changed = true;
+ }
+ } else if(!emulator->config->auth_data.pwd.value) {
+ // Unknown password, pretend to be an Amiibo
+ buff_tx[0] = 0x80;
+ buff_tx[1] = 0x80;
+ tx_bytes = 2;
+ *data_type = FURI_HAL_NFC_TXRX_DEFAULT;
+ emulator->auth_success = true;
+ command_parsed = true;
+ } else {
+ // Wrong password, increase negative verification count
+ if(emulator->data.curr_authlim < UINT16_MAX) {
+ ++emulator->data.curr_authlim;
+ emulator->data_changed = true;
+ }
+ if(scaled_authlim != 0 &&
+ emulator->data.curr_authlim >= scaled_authlim) {
+ emulator->data.curr_authlim = UINT16_MAX;
+ buff_tx[0] = MF_UL_NAK_AUTHLIM_REACHED;
+ tx_bits = 4;
+ *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT;
+ mf_ul_reset_emulation(emulator, false);
+ command_parsed = true;
+ } else {
+ // Should delay here to slow brute forcing
+ }
+ }
+ }
+ }
+ }
+ } else if(cmd == MF_UL_READ_SIG) {
+ if(emulator->supported_features & MfUltralightSupportSignature) {
+ // Check 2nd byte = 0x00 - RFU
+ if(buff_rx_len == (1 + 1) * 8 && buff_rx[1] == 0x00) {
+ tx_bytes = sizeof(emulator->data.signature);
+ memcpy(buff_tx, emulator->data.signature, tx_bytes);
+ *data_type = FURI_HAL_NFC_TXRX_DEFAULT;
+ command_parsed = true;
+ }
+ }
+ } else if(cmd == MF_UL_CHECK_TEARING) {
+ if(emulator->supported_features & MfUltralightSupportTearingFlags) {
+ if(buff_rx_len == (1 + 1) * 8) {
+ uint8_t cnt_num = buff_rx[1];
+ if(cnt_num < 3) {
+ buff_tx[0] = emulator->data.tearing[cnt_num];
+ tx_bytes = 1;
+ *data_type = FURI_HAL_NFC_TXRX_DEFAULT;
+ command_parsed = true;
+ }
+ }
+ }
+ } else if(cmd == MF_UL_HALT_START) {
+ reset_idle = true;
+ FURI_LOG_D(TAG, "Received HLTA");
+ } else if(cmd == MF_UL_SECTOR_SELECT) {
+ if(emulator->supported_features & MfUltralightSupportSectorSelect) {
+ if(buff_rx_len == (1 + 1) * 8 && buff_rx[1] == 0xFF) {
+ // Send ACK
+ emulator->sector_select_cmd_started = true;
+ send_ack = true;
+ command_parsed = true;
+ }
+ }
+ } else if(cmd == MF_UL_READ_VCSL) {
+ if(emulator->supported_features & MfUltralightSupportVcsl) {
+ if(buff_rx_len == (1 + 20) * 8) {
+ buff_tx[0] = emulator->config_cache.vctid;
+ tx_bytes = 1;
+ *data_type = FURI_HAL_NFC_TXRX_DEFAULT;
+ command_parsed = true;
+ }
+ }
+ } else {
+ // NTAG203 appears to NAK instead of just falling off on invalid commands
+ if(emulator->data.type != MfUltralightTypeNTAG203) reset_idle = true;
+ FURI_LOG_D(TAG, "Received invalid command");
+ }
+ } else {
+ reset_idle = true;
+ FURI_LOG_D(TAG, "Received invalid buffer less than 8 bits in length");
+ }
+
+ if(reset_idle) {
+ mf_ul_reset_emulation(emulator, false);
+ tx_bits = 0;
+ command_parsed = true;
+ }
+
+ if(!command_parsed) {
+ // Send NACK
+ buff_tx[0] = MF_UL_NAK_INVALID_ARGUMENT;
+ tx_bits = 4;
+ *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT;
+ // Every NAK should cause reset to IDLE
+ mf_ul_reset_emulation(emulator, false);
+ } else if(send_ack) {
+ buff_tx[0] = MF_UL_ACK;
+ tx_bits = 4;
+ *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT;
+ }
+
+ if(respond_nothing) {
+ *buff_tx_len = UINT16_MAX;
+ *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT;
+ } else {
+ // Return tx buffer size in bits
+ if(tx_bytes) {
+ tx_bits = tx_bytes * 8;
+ }
+ *buff_tx_len = tx_bits;
+ }
+
+#ifdef FURI_DEBUG
+ if(*buff_tx_len == UINT16_MAX) {
+ FURI_LOG_T(TAG, "Emu TX: no reply");
+ } else if(*buff_tx_len > 0) {
+ int count = (*buff_tx_len + 7) / 8;
+ for(int i = 0; i < count; ++i) {
+ string_cat_printf(debug_buf, "%02x ", buff_tx[i]);
+ }
+ string_strim(debug_buf);
+ FURI_LOG_T(TAG, "Emu TX (%d): %s", *buff_tx_len, string_get_cstr(debug_buf));
+ string_clear(debug_buf);
+ } else {
+ FURI_LOG_T(TAG, "Emu TX: HALT");
+ }
+#endif
+
+ return tx_bits > 0;
+}
diff --git a/lib/nfc/protocols/mifare_ultralight.h b/lib/nfc/protocols/mifare_ultralight.h
new file mode 100644
index 00000000..77dbd1e4
--- /dev/null
+++ b/lib/nfc/protocols/mifare_ultralight.h
@@ -0,0 +1,222 @@
+#pragma once
+
+#include <furi_hal_nfc.h>
+
+// Largest tag is NTAG I2C Plus 2K, both data sectors plus SRAM
+#define MF_UL_MAX_DUMP_SIZE ((238 + 256 + 16) * 4)
+
+#define MF_UL_TEARING_FLAG_DEFAULT (0xBD)
+
+#define MF_UL_HALT_START (0x50)
+#define MF_UL_GET_VERSION_CMD (0x60)
+#define MF_UL_READ_CMD (0x30)
+#define MF_UL_FAST_READ_CMD (0x3A)
+#define MF_UL_WRITE (0xA2)
+#define MF_UL_FAST_WRITE (0xA6)
+#define MF_UL_COMP_WRITE (0xA0)
+#define MF_UL_READ_CNT (0x39)
+#define MF_UL_INC_CNT (0xA5)
+#define MF_UL_AUTH (0x1B)
+#define MF_UL_READ_SIG (0x3C)
+#define MF_UL_CHECK_TEARING (0x3E)
+#define MF_UL_READ_VCSL (0x4B)
+#define MF_UL_SECTOR_SELECT (0xC2)
+
+#define MF_UL_ACK (0xa)
+#define MF_UL_NAK_INVALID_ARGUMENT (0x0)
+#define MF_UL_NAK_AUTHLIM_REACHED (0x4)
+
+#define MF_UL_NTAG203_COUNTER_PAGE (41)
+
+// Important: order matters; some features are based on positioning in this enum
+typedef enum {
+ MfUltralightTypeUnknown,
+ MfUltralightTypeNTAG203,
+ // Below have config pages and GET_VERSION support
+ MfUltralightTypeUL11,
+ MfUltralightTypeUL21,
+ MfUltralightTypeNTAG213,
+ MfUltralightTypeNTAG215,
+ MfUltralightTypeNTAG216,
+ // Below also have sector select
+ // NTAG I2C's *does not* have regular config pages, so it's a bit of an odd duck
+ MfUltralightTypeNTAGI2C1K,
+ MfUltralightTypeNTAGI2C2K,
+ // NTAG I2C Plus has stucture expected from NTAG21x
+ MfUltralightTypeNTAGI2CPlus1K,
+ MfUltralightTypeNTAGI2CPlus2K,
+
+ // Keep last for number of types calculation
+ MfUltralightTypeNum,
+} MfUltralightType;
+
+typedef enum {
+ MfUltralightSupportNone = 0,
+ MfUltralightSupportFastRead = 1 << 0,
+ MfUltralightSupportTearingFlags = 1 << 1,
+ MfUltralightSupportReadCounter = 1 << 2,
+ MfUltralightSupportIncrCounter = 1 << 3,
+ MfUltralightSupportSignature = 1 << 4,
+ MfUltralightSupportFastWrite = 1 << 5,
+ MfUltralightSupportCompatWrite = 1 << 6,
+ MfUltralightSupportAuth = 1 << 7,
+ MfUltralightSupportVcsl = 1 << 8,
+ MfUltralightSupportSectorSelect = 1 << 9,
+ // NTAG21x only has counter 2
+ MfUltralightSupportSingleCounter = 1 << 10,
+ // ASCII mirror is not a command, but handy to have as a flag
+ MfUltralightSupportAsciiMirror = 1 << 11,
+ // NTAG203 counter that's in memory rather than through a command
+ MfUltralightSupportCounterInMemory = 1 << 12,
+} MfUltralightFeatures;
+
+typedef enum {
+ MfUltralightMirrorNone,
+ MfUltralightMirrorUid,
+ MfUltralightMirrorCounter,
+ MfUltralightMirrorUidCounter,
+} MfUltralightMirrorConf;
+
+typedef struct {
+ uint8_t header;
+ uint8_t vendor_id;
+ uint8_t prod_type;
+ uint8_t prod_subtype;
+ uint8_t prod_ver_major;
+ uint8_t prod_ver_minor;
+ uint8_t storage_size;
+ uint8_t protocol_type;
+} MfUltralightVersion;
+
+typedef struct {
+ uint8_t sn0[3];
+ uint8_t btBCC0;
+ uint8_t sn1[4];
+ uint8_t btBCC1;
+ uint8_t internal;
+ uint8_t lock[2];
+ uint8_t otp[4];
+} MfUltralightManufacturerBlock;
+
+typedef struct {
+ MfUltralightType type;
+ MfUltralightVersion version;
+ uint8_t signature[32];
+ uint32_t counter[3];
+ uint8_t tearing[3];
+ uint16_t curr_authlim;
+ uint16_t data_size;
+ uint8_t data[MF_UL_MAX_DUMP_SIZE];
+} MfUltralightData;
+
+typedef struct __attribute__((packed)) {
+ union {
+ uint8_t raw[4];
+ uint32_t value;
+ } pwd;
+ union {
+ uint8_t raw[2];
+ uint16_t value;
+ } pack;
+} MfUltralightAuth;
+
+// Common configuration pages for MFUL EV1, NTAG21x, and NTAG I2C Plus
+typedef struct __attribute__((packed)) {
+ union {
+ uint8_t value;
+ struct {
+ uint8_t rfui1 : 2;
+ bool strg_mod_en : 1;
+ bool rfui2 : 1;
+ uint8_t mirror_byte : 2;
+ MfUltralightMirrorConf mirror_conf : 2;
+ };
+ } mirror;
+ uint8_t rfui1;
+ uint8_t mirror_page;
+ uint8_t auth0;
+ union {
+ uint8_t value;
+ struct {
+ uint8_t authlim : 3;
+ bool nfc_cnt_pwd_prot : 1;
+ bool nfc_cnt_en : 1;
+ bool nfc_dis_sec1 : 1; // NTAG I2C Plus only
+ bool cfglck : 1;
+ bool prot : 1;
+ };
+ } access;
+ uint8_t vctid;
+ uint8_t rfui2[2];
+ MfUltralightAuth auth_data;
+ uint8_t rfui3[2];
+} MfUltralightConfigPages;
+
+typedef struct {
+ uint16_t pages_to_read;
+ int16_t pages_read;
+ MfUltralightFeatures supported_features;
+} MfUltralightReader;
+
+typedef struct {
+ MfUltralightData data;
+ MfUltralightConfigPages* config;
+ // Most config values don't apply until power cycle, so cache config pages
+ // for correct behavior
+ MfUltralightConfigPages config_cache;
+ MfUltralightFeatures supported_features;
+ uint16_t page_num;
+ bool data_changed;
+ bool comp_write_cmd_started;
+ uint8_t comp_write_page_addr;
+ bool auth_success;
+ uint8_t curr_sector;
+ bool sector_select_cmd_started;
+ bool ntag_i2c_plus_sector3_lockout;
+ bool read_counter_incremented;
+} MfUltralightEmulator;
+
+bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
+
+bool mf_ultralight_read_version(
+ FuriHalNfcTxRxContext* tx_rx,
+ MfUltralightReader* reader,
+ MfUltralightData* data);
+
+bool mf_ultralight_read_pages_direct(
+ FuriHalNfcTxRxContext* tx_rx,
+ uint8_t start_index,
+ uint8_t* data);
+
+bool mf_ultralight_read_pages(
+ FuriHalNfcTxRxContext* tx_rx,
+ MfUltralightReader* reader,
+ MfUltralightData* data);
+
+bool mf_ultralight_fast_read_pages(
+ FuriHalNfcTxRxContext* tx_rx,
+ MfUltralightReader* reader,
+ MfUltralightData* data);
+
+bool mf_ultralight_read_signature(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data);
+
+bool mf_ultralight_read_counters(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data);
+
+bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data);
+
+bool mf_ul_read_card(
+ FuriHalNfcTxRxContext* tx_rx,
+ MfUltralightReader* reader,
+ MfUltralightData* data);
+
+void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle);
+
+void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data);
+
+bool mf_ul_prepare_emulation_response(
+ uint8_t* buff_rx,
+ uint16_t buff_rx_len,
+ uint8_t* buff_tx,
+ uint16_t* buff_tx_len,
+ uint32_t* data_type,
+ void* context);
diff --git a/lib/nfc/protocols/nfc_util.c b/lib/nfc/protocols/nfc_util.c
new file mode 100644
index 00000000..9de6f982
--- /dev/null
+++ b/lib/nfc/protocols/nfc_util.c
@@ -0,0 +1,47 @@
+#include "nfc_util.h"
+
+#include <furi.h>
+
+static const uint8_t nfc_util_odd_byte_parity[256] = {
+ 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0,
+ 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1,
+ 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1,
+ 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0,
+ 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1,
+ 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0,
+ 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1,
+ 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+ 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1};
+
+void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest) {
+ furi_assert(dest);
+ furi_assert(len <= 8);
+
+ while(len--) {
+ dest[len] = (uint8_t)src;
+ src >>= 8;
+ }
+}
+
+uint64_t nfc_util_bytes2num(uint8_t* src, uint8_t len) {
+ furi_assert(src);
+ furi_assert(len <= 8);
+
+ uint64_t res = 0;
+ while(len--) {
+ res = (res << 8) | (*src);
+ src++;
+ }
+ return res;
+}
+
+uint8_t nfc_util_even_parity32(uint32_t data) {
+ // data ^= data >> 16;
+ // data ^= data >> 8;
+ // return !nfc_util_odd_byte_parity[data];
+ return (__builtin_parity(data) & 0xFF);
+}
+
+uint8_t nfc_util_odd_parity8(uint8_t data) {
+ return nfc_util_odd_byte_parity[data];
+}
diff --git a/lib/nfc/protocols/nfc_util.h b/lib/nfc/protocols/nfc_util.h
new file mode 100644
index 00000000..d530badc
--- /dev/null
+++ b/lib/nfc/protocols/nfc_util.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <stdint.h>
+
+void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest);
+
+uint64_t nfc_util_bytes2num(uint8_t* src, uint8_t len);
+
+uint8_t nfc_util_even_parity32(uint32_t data);
+
+uint8_t nfc_util_odd_parity8(uint8_t data);
diff --git a/lib/nfc/protocols/nfca.c b/lib/nfc/protocols/nfca.c
new file mode 100755
index 00000000..c401f8cc
--- /dev/null
+++ b/lib/nfc/protocols/nfca.c
@@ -0,0 +1,142 @@
+#include "nfca.h"
+#include <string.h>
+#include <stdio.h>
+#include <furi.h>
+
+#define NFCA_CMD_RATS (0xE0U)
+
+#define NFCA_CRC_INIT (0x6363)
+
+#define NFCA_F_SIG (13560000.0)
+#define T_SIG 7374 //73.746ns*100
+#define T_SIG_x8 58992 //T_SIG*8
+#define T_SIG_x8_x8 471936 //T_SIG*8*8
+#define T_SIG_x8_x9 530928 //T_SIG*8*9
+
+#define NFCA_SIGNAL_MAX_EDGES (1350)
+
+typedef struct {
+ uint8_t cmd;
+ uint8_t param;
+} nfca_cmd_rats;
+
+static uint8_t nfca_default_ats[] = {0x05, 0x78, 0x80, 0x80, 0x00};
+
+static uint8_t nfca_sleep_req[] = {0x50, 0x00};
+
+uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len) {
+ uint16_t crc = NFCA_CRC_INIT;
+ uint8_t byte = 0;
+
+ for(uint8_t i = 0; i < len; i++) {
+ byte = buff[i];
+ byte ^= (uint8_t)(crc & 0xff);
+ byte ^= byte << 4;
+ crc = (crc >> 8) ^ (((uint16_t)byte) << 8) ^ (((uint16_t)byte) << 3) ^
+ (((uint16_t)byte) >> 4);
+ }
+
+ return crc;
+}
+
+void nfca_append_crc16(uint8_t* buff, uint16_t len) {
+ uint16_t crc = nfca_get_crc16(buff, len);
+ buff[len] = (uint8_t)crc;
+ buff[len + 1] = (uint8_t)(crc >> 8);
+}
+
+bool nfca_emulation_handler(
+ uint8_t* buff_rx,
+ uint16_t buff_rx_len,
+ uint8_t* buff_tx,
+ uint16_t* buff_tx_len) {
+ bool sleep = false;
+ uint8_t rx_bytes = buff_rx_len / 8;
+
+ if(rx_bytes == sizeof(nfca_sleep_req) && !memcmp(buff_rx, nfca_sleep_req, rx_bytes)) {
+ sleep = true;
+ } else if(rx_bytes == sizeof(nfca_cmd_rats) && buff_rx[0] == NFCA_CMD_RATS) {
+ memcpy(buff_tx, nfca_default_ats, sizeof(nfca_default_ats));
+ *buff_tx_len = sizeof(nfca_default_ats) * 8;
+ }
+
+ return sleep;
+}
+
+static void nfca_add_bit(DigitalSignal* signal, bool bit) {
+ if(bit) {
+ signal->start_level = true;
+ for(size_t i = 0; i < 7; i++) {
+ signal->edge_timings[i] = T_SIG_x8;
+ }
+ signal->edge_timings[7] = T_SIG_x8_x9;
+ signal->edge_cnt = 8;
+ } else {
+ signal->start_level = false;
+ signal->edge_timings[0] = T_SIG_x8_x8;
+ for(size_t i = 1; i < 9; i++) {
+ signal->edge_timings[i] = T_SIG_x8;
+ }
+ signal->edge_cnt = 9;
+ }
+}
+
+static void nfca_add_byte(NfcaSignal* nfca_signal, uint8_t byte, bool parity) {
+ for(uint8_t i = 0; i < 8; i++) {
+ if(byte & (1 << i)) {
+ digital_signal_append(nfca_signal->tx_signal, nfca_signal->one);
+ } else {
+ digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero);
+ }
+ }
+ if(parity) {
+ digital_signal_append(nfca_signal->tx_signal, nfca_signal->one);
+ } else {
+ digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero);
+ }
+}
+
+NfcaSignal* nfca_signal_alloc() {
+ NfcaSignal* nfca_signal = malloc(sizeof(NfcaSignal));
+ nfca_signal->one = digital_signal_alloc(10);
+ nfca_signal->zero = digital_signal_alloc(10);
+ nfca_add_bit(nfca_signal->one, true);
+ nfca_add_bit(nfca_signal->zero, false);
+ nfca_signal->tx_signal = digital_signal_alloc(NFCA_SIGNAL_MAX_EDGES);
+
+ return nfca_signal;
+}
+
+void nfca_signal_free(NfcaSignal* nfca_signal) {
+ furi_assert(nfca_signal);
+
+ digital_signal_free(nfca_signal->one);
+ digital_signal_free(nfca_signal->zero);
+ digital_signal_free(nfca_signal->tx_signal);
+ free(nfca_signal);
+}
+
+void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity) {
+ furi_assert(nfca_signal);
+ furi_assert(data);
+ furi_assert(parity);
+
+ nfca_signal->tx_signal->edge_cnt = 0;
+ nfca_signal->tx_signal->start_level = true;
+ // Start of frame
+ digital_signal_append(nfca_signal->tx_signal, nfca_signal->one);
+
+ if(bits < 8) {
+ for(size_t i = 0; i < bits; i++) {
+ if(FURI_BIT(data[0], i)) {
+ digital_signal_append(nfca_signal->tx_signal, nfca_signal->one);
+ } else {
+ digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero);
+ }
+ }
+ } else {
+ for(size_t i = 0; i < bits / 8; i++) {
+ nfca_add_byte(nfca_signal, data[i], parity[i / 8] & (1 << (7 - (i & 0x07))));
+ }
+ }
+}
diff --git a/lib/nfc/protocols/nfca.h b/lib/nfc/protocols/nfca.h
new file mode 100644
index 00000000..498ef284
--- /dev/null
+++ b/lib/nfc/protocols/nfca.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <lib/digital_signal/digital_signal.h>
+
+typedef struct {
+ DigitalSignal* one;
+ DigitalSignal* zero;
+ DigitalSignal* tx_signal;
+} NfcaSignal;
+
+uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len);
+
+void nfca_append_crc16(uint8_t* buff, uint16_t len);
+
+bool nfca_emulation_handler(
+ uint8_t* buff_rx,
+ uint16_t buff_rx_len,
+ uint8_t* buff_tx,
+ uint16_t* buff_tx_len);
+
+NfcaSignal* nfca_signal_alloc();
+
+void nfca_signal_free(NfcaSignal* nfca_signal);
+
+void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity);