From 17741af7f3bc7c40012934bb0112f90e4e096e96 Mon Sep 17 00:00:00 2001 From: GOB Date: Mon, 19 Jan 2026 19:11:30 +0900 Subject: [PATCH] Fixes for DESFire --- examples/UnitUnified/NFCA/NDEF/main/NDEF.cpp | 65 ++- src/nfc/a/nfca.cpp | 5 +- src/nfc/apdu/apdu.hpp | 9 +- src/nfc/isoDEP/desfire_file_system.cpp | 331 ++++++------ src/nfc/isoDEP/desfire_file_system.hpp | 39 +- src/nfc/isoDEP/file_system.cpp | 25 +- src/nfc/layer/a/nfc_layer_a.cpp | 254 ++++++++- src/nfc/layer/a/nfc_layer_a.hpp | 9 +- src/nfc/layer/ndef_layer.cpp | 511 +++++++++++++++---- src/nfc/layer/ndef_layer.hpp | 6 + src/nfc/ndef/ndef.hpp | 13 +- 11 files changed, 939 insertions(+), 328 deletions(-) diff --git a/examples/UnitUnified/NFCA/NDEF/main/NDEF.cpp b/examples/UnitUnified/NFCA/NDEF/main/NDEF.cpp index cf7e8be..a6f1db7 100644 --- a/examples/UnitUnified/NFCA/NDEF/main/NDEF.cpp +++ b/examples/UnitUnified/NFCA/NDEF/main/NDEF.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include // ************************************************************* @@ -30,6 +31,7 @@ #include #endif +using namespace m5::nfc; using namespace m5::nfc::a; using namespace m5::nfc::a::mifare; using namespace m5::nfc::ndef; @@ -96,8 +98,33 @@ constexpr uint8_t poji_64_png[] = { constexpr uint32_t poji_64_png_len = 738; +void format_desfire() +{ + auto& picc = nfc_a.activatedPICC(); + + if (picc.isMifareDESFire()) { + desfire::DESFireFileSystem dfs(nfc_a); + if (picc.type == Type::MIFARE_DESFire_Light) { + M5_LOGW("DESFire light can NOT format"); + return; + } else { + if (!dfs.formatPICC(type4::DESFIRE_DEFAULT_KEY)) { + M5_LOGE("Failed to formatPICC"); + return; + } + uint32_t free_size{}; + if (dfs.selectApplication() && dfs.getFreeMemory(free_size)) { + M5_LOGI("free(picc):%u", free_size); + } + } + } +} + void read_ndef() { + // auto& picc = nfc_a.activatedPICC(); + + // Disable non-test NDEF read path; keep only the current debug logging. bool valid{}; if (!nfc_a.ndefIsValidFormat(valid)) { M5_LOGE("Failed to ndefIsValidFormat"); @@ -143,26 +170,56 @@ void read_ndef() void write_ndef() { + auto& picc = nfc_a.activatedPICC(); + /* - **** NOTICE ********************************************* + **** MIFARE Ultralight NOTICE *************************** Change the Ultralight series to NDEF format Note: This change cannot be undone ********************************************************* */ - if (nfc_a.activatedPICC().isMifareUltralight()) { + if (picc.isMifareUltralight()) { if (!nfc_a.mifareUltralightChangeFormatToNDEF()) { M5_LOGE("Failed to mifareUltralightChangeFormatToNTAG"); lcd.fillScreen(TFT_RED); return; } + M5_LOGI("Changed NDEF format"); } + /* + **** MIFARE DESFire NOTICE ****************************** + If the DESFire card is not in NDEF format, the PICC will be formatted + This means all existing files and applications will be deleted! + For DESFire light, the file structure is changed to comply with the NDEF specification, + and the data is overwritten. + ********************************************************* + */ + if (picc.isMifareDESFire() && picc.type != Type::MIFARE_DESFire_Light) { + bool valid{}; + if (!nfc_a.ndefIsValidFormat(valid)) { + lcd.fillScreen(TFT_RED); + return; + } + M5_LOGI("NDEF format valid?:%u", valid); + valid = false; + if (!valid) { + format_desfire(); + if (!nfc_a.ndefPrepareDesfire(picc.userAreaSize())) { + M5_LOGE("Failed to prepare NDEF files"); + lcd.fillScreen(TFT_RED); + return; + } + M5_LOGI("Prepare for NDEF OK"); + } + } + + // Build message and write TLV msg{Tag::Message}; Record r[5] = {}; // Wellknown as default // URI record r[0].setURIPayload("m5stack.com/", URIProtocol::HTTPS); - // Text record with langage type const char* zh_data = "你好 M5Stack"; r[1].setTextPayload(zh_data, "zh"); @@ -191,7 +248,7 @@ void write_ndef() lcd.fillScreen(TFT_RED); return; } - nfc_a.dump(); + // nfc_a.dump(); } } // namespace diff --git a/src/nfc/a/nfca.cpp b/src/nfc/a/nfca.cpp index 8bacf42..0776b25 100644 --- a/src/nfc/a/nfca.cpp +++ b/src/nfc/a/nfca.cpp @@ -167,11 +167,12 @@ constexpr uint16_t user_area_size_table[] = { 64, 256, 2048, 8192, // ST25 0, // 1504, 3440, 752, // Plus - 2048, 4096, 8192, 512, // Desfire (2K/4K/8K/Light) + 2048, 4096, 8192, 256, // Desfire, Light:The total is 512, but the maximum per file is 256 0, // NTAG 4xx 0, }; +// TODO Support DESFire Light constexpr NFCForumTag nfc_forum_tag_table[] = { NFCForumTag::None, // NFCForumTag::None, NFCForumTag::None, NFCForumTag::None, NFCForumTag::None, // Classic @@ -181,7 +182,7 @@ constexpr NFCForumTag nfc_forum_tag_table[] = { NFCForumTag::Type4, NFCForumTag::Type4, NFCForumTag::Type4, NFCForumTag::Type4, // ST25TA NFCForumTag::None, // NFCForumTag::None, NFCForumTag::None, NFCForumTag::None, // Plus - NFCForumTag::Type4, NFCForumTag::Type4, NFCForumTag::Type4, NFCForumTag::Type4, // DESFire + NFCForumTag::Type4, NFCForumTag::Type4, NFCForumTag::Type4, NFCForumTag::None, // DESFire NFCForumTag::Type4, // NTAG 4xx NFCForumTag::None, // }; diff --git a/src/nfc/apdu/apdu.hpp b/src/nfc/apdu/apdu.hpp index 236bcf6..f5b9473 100644 --- a/src/nfc/apdu/apdu.hpp +++ b/src/nfc/apdu/apdu.hpp @@ -96,6 +96,7 @@ enum class INS : uint8_t { DF_CREATE_STD_DATA_FILE = 0xCD, DF_GET_FREE_MEMORY = 0x6E, DF_GET_FILE_IDS = 0x6F, + DF_GET_ISO_FILE_IDS = 0x61, DF_GET_KEY_SETTINGS = 0x45, DF_SET_CONFIGURATION = 0x5C, DF_CHANGE_FILE_SETTINGS = 0x5F, @@ -104,9 +105,11 @@ enum class INS : uint8_t { DF_AUTHENTICATE_AES = 0xAA, DF_AUTHENTICATE_EV2 = 0x71, DF_FORMAT_PICC = 0xFC, - DF_READ_DATA = 0xBD, - DF_WRITE_DATA = 0x3D, - DF_GET_FILE_SETTINGS = 0xF5, + DF_READ_DATA = 0xBD, + DF_WRITE_DATA = 0x3D, + DF_GET_FILE_SETTINGS = 0xF5, + DF_DELETE_TRANSACTION_MAC_FILE = 0xDF, + DF_CREATE_TRANSACTION_MAC_FILE = 0xCE, }; ///@} diff --git a/src/nfc/isoDEP/desfire_file_system.cpp b/src/nfc/isoDEP/desfire_file_system.cpp index 3714fa8..92bc579 100644 --- a/src/nfc/isoDEP/desfire_file_system.cpp +++ b/src/nfc/isoDEP/desfire_file_system.cpp @@ -351,11 +351,14 @@ bool transceive_sm_full(m5::nfc::isodep::IsoDEP& iso_dep, const uint8_t cmd, con auto apdu = m5::nfc::a::mifare::desfire::make_native_wrap_command(cmd, cmd_data_sm.data(), static_cast(cmd_data_sm.size())); + /* M5_LIB_LOGE("SM tx cmd:%02X hdr:%u data:%u mac:%u", cmd, static_cast(cmd_header_len), static_cast(enc_data.size()), static_cast(sizeof(mac_trunc))); m5::utility::log::dump(apdu.data(), apdu.size(), false); M5_LIB_LOGE("SM tx MAC input"); m5::utility::log::dump(mac_input.data(), mac_input.size(), false); + */ + std::vector rx(DEFAULT_RX_LEN); uint16_t rx_len = rx.size(); if (!iso_dep.transceiveINF(rx.data(), rx_len, apdu.data(), apdu.size(), nullptr) || rx_len < 2) { @@ -630,8 +633,8 @@ m5::stl::expected DESFireFileSystem::createApplication(const uint auto cmd = make_native_wrap_command(m5::stl::to_underlying(INS::DF_CREATE_APPLICATION), data.data(), data.size()); - M5_LIB_LOGE("cmd:"); - m5::utility::log::dump(cmd.data(), cmd.size(), false); + //M5_LIB_LOGE("cmd:"); + //m5::utility::log::dump(cmd.data(), cmd.size(), false); uint8_t rx[16]{}; uint16_t rx_len = sizeof(rx); @@ -748,7 +751,7 @@ bool DESFireFileSystem::readDataLight(std::vector& out, const uint8_t f } bool DESFireFileSystem::readDataLightEV2Full(std::vector& out, const uint8_t file_no, const uint32_t offset, - const uint32_t length, Ev2Context& ctx) + const uint32_t length, Ev2Context& ctx) { out.clear(); @@ -759,8 +762,7 @@ bool DESFireFileSystem::readDataLightEV2Full(std::vector& out, const ui pack_le24(header + 4, length); std::vector resp; - if (!transceive_sm_full(_isoDEP, m5::stl::to_underlying(INS::DF_READ_DATA), header, sizeof(header), nullptr, 0, ctx, - &resp)) { + if (!transceive_sm_full(_isoDEP, DESFIRE_LIGHT_INS_READ_DATA, header, sizeof(header), nullptr, 0, ctx, &resp)) { return false; } out = std::move(resp); @@ -768,7 +770,7 @@ bool DESFireFileSystem::readDataLightEV2Full(std::vector& out, const ui } bool DESFireFileSystem::readDataLightEV2(std::vector& out, const uint8_t file_no, const uint32_t offset, - const uint32_t length, Ev2Context& ctx) + const uint32_t length, Ev2Context& ctx) { out.clear(); @@ -779,8 +781,7 @@ bool DESFireFileSystem::readDataLightEV2(std::vector& out, const uint8_ pack_le24(header + 4, length); std::vector resp; - if (!transceive_sm_mac(_isoDEP, m5::stl::to_underlying(INS::DF_READ_DATA), header, sizeof(header), nullptr, 0, ctx, - &resp)) { + if (!transceive_sm_mac(_isoDEP, DESFIRE_LIGHT_INS_READ_DATA, header, sizeof(header), nullptr, 0, ctx, &resp)) { return false; } out = std::move(resp); @@ -887,8 +888,8 @@ bool DESFireFileSystem::writeDataLight(const uint8_t file_no, const uint32_t off return true; } -bool DESFireFileSystem::writeDataEV2Mac(const uint8_t file_no, const uint32_t offset, const uint8_t* data, - const uint32_t data_len, Ev2Context& ctx) +bool DESFireFileSystem::writeDataLightEV2(const uint8_t file_no, const uint32_t offset, const uint8_t* data, + const uint32_t data_len, Ev2Context& ctx) { if (!data || data_len == 0) { return false; @@ -921,8 +922,8 @@ bool DESFireFileSystem::writeDataEV2Mac(const uint8_t file_no, const uint32_t of return true; } -bool DESFireFileSystem::writeDataEV2Full(const uint8_t file_no, const uint32_t offset, const uint8_t* data, - const uint32_t data_len, Ev2Context& ctx) +bool DESFireFileSystem::writeDataLightEV2Full(const uint8_t file_no, const uint32_t offset, const uint8_t* data, + const uint32_t data_len, Ev2Context& ctx) { if (!data || data_len == 0) { return false; @@ -961,158 +962,6 @@ bool DESFireFileSystem::writeDataEV2Full(const uint8_t file_no, const uint32_t o return true; } -bool DESFireFileSystem::createNDEFFiles(const uint32_t max_ndef_size) -{ - if (!max_ndef_size) { - return false; - } - - // select - if (!selectApplication()) { - M5_LIB_LOGE("createNDEFFiles: select PICC"); - return false; - } - - // adjust NDEF file size - uint32_t free_mem{}; - uint16_t ndef_file_size{}; - if (getFreeMemory(free_mem)) { - const uint32_t reserve = (free_mem > 4096) ? 256 : 0; // For 8K - const uint32_t capped = (free_mem > reserve) ? (free_mem - reserve) : 0; - ndef_file_size = static_cast(std::min(max_ndef_size, capped)); - if (ndef_file_size < 32) { - M5_LIB_LOGE("createNDEFFiles: NDEF size too small %u", ndef_file_size); - return false; - } - } - - NdefFormatOptions opt{}; - opt.picc_master_key = type4::DESFIRE_DEFAULT_KEY; - opt.app_master_key = type4::DESFIRE_DEFAULT_KEY; - opt.ndef_file_size = ndef_file_size; - type4::FileControlTLV fct{}; - fct.tag = 0x04; - fct.len = 0x06; - fct.ndef_file_id = type4::NDEF_FILE_ID; - fct.ndef_file_size = ndef_file_size; - fct.read_access = 0x00; - fct.write_access = 0x00; - opt.cc.fctlvs.push_back(fct); - - // picc auth - bool picc_des_ok{}; - bool picc_iso_ok{}; - bool picc_aes_ok{}; - if (opt.picc_master_key) { - bool ok = false; - if (opt.auth_mode == AuthMode::DES || opt.auth_mode == AuthMode::Auto) { - picc_des_ok = authenticateDES(0x00, opt.picc_master_key); - if (!picc_des_ok) { - picc_iso_ok = authenticateISO(0x00, opt.picc_master_key); - } - ok = picc_des_ok || picc_iso_ok; - } - if (!ok && (opt.auth_mode == AuthMode::AES || opt.auth_mode == AuthMode::Auto)) { - picc_aes_ok = authenticateAES(0x00, opt.picc_master_key); - ok = picc_aes_ok; - } - if (!ok) { - M5_LIB_LOGE("createNDEFFiles: PICC master auth failed"); - return false; - } - } - constexpr uint16_t app_iso_fid = type4::NDEF_APP_FID; - const auto* df_name = type4::NDEF_AID; - constexpr uint8_t df_name_len = sizeof(type4::NDEF_AID); - - uint8_t key_settings2 = opt.key_settings2; - if (opt.auth_mode == AuthMode::Auto && opt.picc_master_key) { - const uint8_t base = static_cast(opt.key_settings2 & 0x3F); - if (picc_aes_ok) { - key_settings2 = static_cast(0x80 | base); - } else if (picc_iso_ok) { - key_settings2 = base; - } else if (picc_des_ok) { - key_settings2 = base; - } - } - - // create app - auto created = createApplication(opt.aid, opt.key_settings1, key_settings2, app_iso_fid, df_name, df_name_len); - if (!created.has_value() && created.error() == 0x9E) { - created = createApplication(opt.aid, opt.key_settings1, key_settings2, 0, nullptr, 0); - } - if (!created.has_value()) { - M5_LIB_LOGE("createNDEFFiles: create application failed"); - return false; - } - if (!selectApplication(opt.aid)) { - M5_LIB_LOGE("createNDEFFiles: select application failed"); - return false; - } - // app auth - if (opt.app_master_key) { - bool ok = false; - if (opt.auth_mode == AuthMode::Auto && opt.picc_master_key) { - if (picc_aes_ok) { - ok = authenticateAES(0x00, opt.app_master_key); - } else if (picc_iso_ok) { - ok = authenticateISO(0x00, opt.app_master_key); - } else { - ok = authenticateDES(0x00, opt.app_master_key); - if (!ok) { - ok = authenticateISO(0x00, opt.app_master_key); - } - } - } else { - if (opt.auth_mode == AuthMode::DES || opt.auth_mode == AuthMode::Auto) { - ok = authenticateDES(0x00, opt.app_master_key); - if (!ok) { - ok = authenticateISO(0x00, opt.app_master_key); - } - } - if (!ok && (opt.auth_mode == AuthMode::AES || opt.auth_mode == AuthMode::Auto)) { - ok = authenticateAES(0x00, opt.app_master_key); - } - } - if (!ok) { - M5_LIB_LOGE("createNDEFFiles: app master auth failed"); - return false; - } - } - - // create CC file - if (!createStdDataFile(opt.cc_file_no, type4::CC_FILE_ID, opt.comm_mode, opt.access_rights, opt.cc_file_size)) { - M5_LIB_LOGE("createNDEFFiles: create CC file failed"); - return false; - } - - std::vector cc; - uint16_t ndef_fid{}; - uint16_t ndef_size{}; - if (!build_cc(cc, ndef_fid, ndef_size, opt)) { - M5_LIB_LOGE("createNDEFFiles: build CC failed"); - return false; - } - if (!writeData(opt.cc_file_no, 0, cc.data(), static_cast(cc.size()))) { - M5_LIB_LOGE("createNDEFFiles: write CC failed"); - return false; - } - - // create NDEF file - if (!createStdDataFile(opt.ndef_file_no, ndef_fid, opt.comm_mode, opt.access_rights, ndef_size)) { - M5_LIB_LOGE("createNDEFFiles: create NDEF file failed"); - return false; - } - - // Write empty (len == 0) - const uint8_t nlen0[2] = {0x00, 0x00}; - if (!writeData(opt.ndef_file_no, 0, nlen0, sizeof(nlen0))) { - M5_LIB_LOGE("Failed to write"); - } - return true; -} - bool DESFireFileSystem::selectApplication(const uint8_t aid[3]) { auto cmd = make_native_wrap_command(m5::stl::to_underlying(INS::DF_SELECT_APPLICATION), aid, 3); @@ -1179,19 +1028,18 @@ bool DESFireFileSystem::getFreeMemory(uint32_t& out) uint8_t rx[16]{}; uint16_t rx_len = sizeof(rx); if (!transceive(rx, rx_len, cmd.data(), cmd.size()) || rx_len < 2) { + M5_LIB_LOGE("transceive failed, rx_len=%u", rx_len); return false; } - // M5_LIB_LOGW("getFreeMemory: rx_len=%u", rx_len); - // m5::utility::log::dump(rx, rx_len, false); - if (!is_successful(rx, rx_len)) { + M5_LIB_LOGE("not successful, status=%02X", status_code(rx, rx_len)); return false; } const uint16_t data_len = static_cast(rx_len - 2); if (data_len > 3) { - M5_LIB_LOGW("getFreeMemory: secured response not supported"); + M5_LIB_LOGE("Secured response not supported"); return false; } if (data_len == 2) { @@ -1202,6 +1050,7 @@ bool DESFireFileSystem::getFreeMemory(uint32_t& out) out = unpack_le24(rx); return true; } + M5_LIB_LOGE("unexpected data_len=%u", data_len); return false; } @@ -1219,7 +1068,7 @@ bool DESFireFileSystem::getKeySettings(uint8_t& key_settings, uint8_t& key_count } const uint16_t data_len = static_cast(rx_len - 2); if (data_len > 2) { - M5_LIB_LOGW("getKeySettings: secured response not supported"); + M5_LIB_LOGW("Secured response not supported"); return false; } key_settings = rx[0]; @@ -1318,6 +1167,61 @@ bool DESFireFileSystem::setConfigurationFileRenamingEV2Full(const FileRename& fi sizeof(cmd_header), data.data(), data.size(), ctx, nullptr); } +bool DESFireFileSystem::setConfigurationAppNameEV2Full(const uint8_t* df_name, uint8_t df_name_len, uint16_t iso_fid, + Ev2Context& ctx) +{ + if (!df_name || df_name_len == 0 || df_name_len > 16) { + return false; + } + + std::vector data{}; + data.reserve(1 + 16 + 2); + const uint8_t app_opt = static_cast(0x80 | (df_name_len & 0x1F)); // bit7=1 + name length + data.push_back(app_opt); + for (uint8_t i = 0; i < 16; ++i) { + data.push_back((i < df_name_len) ? df_name[i] : 0x00); + } + data.push_back(static_cast(iso_fid & 0xFF)); // LSB first + data.push_back(static_cast((iso_fid >> 8) & 0xFF)); + + const uint8_t cmd_header[1] = {0x06}; + return transceive_sm_full(_isoDEP, m5::stl::to_underlying(INS::DF_SET_CONFIGURATION), cmd_header, + sizeof(cmd_header), data.data(), data.size(), ctx, nullptr); +} + +bool DESFireFileSystem::deleteTransactionMACFileEV2Full(const uint8_t file_no, Ev2Context& ctx) +{ + // DeleteTransactionMACFile: CmdHeader = FileNo, CmdData = none + // This command deletes the TMAC file, which is required for ISOReadBinary to work on DESFire Light + const uint8_t cmd_header[1] = {file_no}; + return transceive_sm_full(_isoDEP, m5::stl::to_underlying(INS::DF_DELETE_TRANSACTION_MAC_FILE), cmd_header, + sizeof(cmd_header), nullptr, 0, ctx, nullptr); +} + +bool DESFireFileSystem::createTransactionMACFileEV2Full(const uint8_t file_no, const uint8_t comm_mode, + const uint16_t access_rights, const uint8_t tmac_key[16], + const uint8_t tmac_key_ver, Ev2Context& ctx) +{ + // CreateTransactionMACFile: + // CmdHeader = FileNo + FileOption + AccessRights + TMACKeyOption (5 bytes) + // CmdData = TMACKey + TMACKeyVer (17 bytes, encrypted) + uint8_t cmd_header[5]; + cmd_header[0] = file_no; + cmd_header[1] = comm_mode & 0x03; // FileOption (CommMode in bits 1-0) + cmd_header[2] = static_cast(access_rights & 0xFF); // AccessRights LSB + cmd_header[3] = static_cast((access_rights >> 8) & 0xFF); // AccessRights MSB + cmd_header[4] = 0x02; // TMACKeyOption: 02h = AES key + + std::vector data; + data.reserve(16 + 1); + for (int i = 0; i < 16; ++i) { + data.push_back(tmac_key[i]); + } + data.push_back(tmac_key_ver); + return transceive_sm_full(_isoDEP, m5::stl::to_underlying(INS::DF_CREATE_TRANSACTION_MAC_FILE), cmd_header, + sizeof(cmd_header), data.data(), data.size(), ctx, nullptr); +} + bool DESFireFileSystem::getFileIDs(std::vector& out) { out.clear(); @@ -1338,6 +1242,27 @@ bool DESFireFileSystem::getFileIDs(std::vector& out) out.assign(rx.begin(), rx.begin() + data_len); return true; } + M5_LIB_LOGE("getFileIDs failed status %02X", status_code(rx.data(), rx_len)); + return false; +} + +bool DESFireFileSystem::getISOFileIDs(std::vector& out) +{ + out.clear(); + auto cmd = make_native_wrap_command(m5::stl::to_underlying(INS::DF_GET_ISO_FILE_IDS)); + + // ISO File IDs are 2 bytes each, max 32 files = 64 bytes + status + std::vector rx(MAXIMUM_FILES * 2 + 2); + uint16_t rx_len = rx.size(); + if (!transceive(rx.data(), rx_len, cmd.data(), cmd.size()) || rx_len < 2) { + M5_LIB_LOGE("Failed to getISOFileIDs %u", rx_len); + return false; + } + if (is_successful(rx.data(), rx_len)) { + const uint16_t data_len = rx_len - 2; + out.assign(rx.begin(), rx.begin() + data_len); + return true; + } return false; } @@ -1355,13 +1280,25 @@ bool DESFireFileSystem::getFileSettings(FileSettings& out, const uint8_t file_no return false; } const uint16_t data_len = static_cast(rx_len - 2); + if (data_len < 4) { + return false; + } + const uint8_t file_type = rx[0]; + if (file_type == 0x05) { // TransactionMAC file + const uint8_t file_option = rx[1]; + out.file_type = file_type; + out.comm_mode = (file_option & 0x03); + out.access_rights = static_cast(rx[2]) | (static_cast(rx[3]) << 8); + out.file_size = 0; + return true; + } if (data_len < 7) { return false; } - if (data_len > 7) { - M5_LIB_LOGW("getFileSettings: secured response not supported"); - } - out.file_type = rx[0]; + // if (data_len > 7) { + // M5_LIB_LOGW("getFileSettings: secured response not supported"); + // } + out.file_type = file_type; out.comm_mode = rx[1]; out.access_rights = static_cast(rx[2]) | (static_cast(rx[3]) << 8); out.file_size = unpack_le24(rx + 4); @@ -1376,10 +1313,52 @@ bool DESFireFileSystem::getFileSettingsEV2(FileSettings& out, const uint8_t file &resp)) { return false; } + if (resp.size() < 4) { + return false; + } + const uint8_t file_type = resp[0]; + if (file_type == 0x05) { // TransactionMAC file + const uint8_t file_option = resp[1]; + out.file_type = file_type; + out.comm_mode = (file_option & 0x03); + out.access_rights = static_cast(resp[2]) | (static_cast(resp[3]) << 8); + out.file_size = 0; + return true; + } if (resp.size() < 7) { return false; } - out.file_type = resp[0]; + out.file_type = file_type; + out.comm_mode = resp[1]; + out.access_rights = static_cast(resp[2]) | (static_cast(resp[3]) << 8); + out.file_size = unpack_le24(resp.data() + 4); + return true; +} + +bool DESFireFileSystem::getFileSettingsEV2Full(FileSettings& out, const uint8_t file_no, Ev2Context& ctx) +{ + out = {}; + std::vector resp; + if (!transceive_sm_full(_isoDEP, m5::stl::to_underlying(INS::DF_GET_FILE_SETTINGS), &file_no, 1, nullptr, 0, ctx, + &resp)) { + return false; + } + if (resp.size() < 4) { + return false; + } + const uint8_t file_type = resp[0]; + if (file_type == 0x05) { // TransactionMAC file + const uint8_t file_option = resp[1]; + out.file_type = file_type; + out.comm_mode = (file_option & 0x03); + out.access_rights = static_cast(resp[2]) | (static_cast(resp[3]) << 8); + out.file_size = 0; + return true; + } + if (resp.size() < 7) { + return false; + } + out.file_type = file_type; out.comm_mode = resp[1]; out.access_rights = static_cast(resp[2]) | (static_cast(resp[3]) << 8); out.file_size = unpack_le24(resp.data() + 4); @@ -1408,8 +1387,8 @@ bool DESFireFileSystem::changeFileSettingsEV2(const uint8_t file_no, const uint8 data[0] = (file_option & 0x03); data[1] = static_cast(access_rights & 0xFF); data[2] = static_cast((access_rights >> 8) & 0xFF); - return transceive_sm_mac(_isoDEP, m5::stl::to_underlying(INS::DF_CHANGE_FILE_SETTINGS), header, sizeof(header), data, - sizeof(data), ctx, nullptr); + return transceive_sm_mac(_isoDEP, m5::stl::to_underlying(INS::DF_CHANGE_FILE_SETTINGS), header, sizeof(header), + data, sizeof(data), ctx, nullptr); } bool DESFireFileSystem::changeFileSettings(const uint8_t file_no, const uint8_t file_option, diff --git a/src/nfc/isoDEP/desfire_file_system.hpp b/src/nfc/isoDEP/desfire_file_system.hpp index 0c57fd2..dfb8eea 100644 --- a/src/nfc/isoDEP/desfire_file_system.hpp +++ b/src/nfc/isoDEP/desfire_file_system.hpp @@ -196,14 +196,16 @@ public: bool getFreeMemory(uint32_t& out); bool getKeySettings(uint8_t& key_settings, uint8_t& key_count); bool getFileIDs(std::vector& out); + bool getISOFileIDs(std::vector& out); bool getFileSettings(FileSettings& out, const uint8_t file_no); bool getFileSettingsEV2(FileSettings& out, const uint8_t file_no, Ev2Context& ctx); + bool getFileSettingsEV2Full(FileSettings& out, const uint8_t file_no, Ev2Context& ctx); - bool changeFileSettingsEV2Full(const uint8_t file_no, const uint8_t file_option, const uint16_t access_rights, - Ev2Context& ctx); + bool changeFileSettings(const uint8_t file_no, const uint8_t file_option, const uint16_t access_rights); bool changeFileSettingsEV2(const uint8_t file_no, const uint8_t file_option, const uint16_t access_rights, Ev2Context& ctx); - bool changeFileSettings(const uint8_t file_no, const uint8_t file_option, const uint16_t access_rights); + bool changeFileSettingsEV2Full(const uint8_t file_no, const uint8_t file_option, const uint16_t access_rights, + Ev2Context& ctx); bool formatPICC(const uint8_t* picc_master_key, const AuthMode mode = AuthMode::Auto); @@ -214,7 +216,28 @@ public: bool setConfigurationFileRenaming(const FileRename& first, const FileRename* second = nullptr); bool setConfigurationFileRenamingEV2Full(const FileRename& first, const FileRename* second, Ev2Context& ctx); - bool createNDEFFiles(const uint32_t max_ndef_size); + /*! + @brief Delete TransactionMAC file (required for ISOReadBinary to work) + @param file_no File number of TMAC file (default 0) + @param ctx EV2 context (must be authenticated with AppMasterKey) + @return True if successful + @note DESFire Light blocks ISOReadBinary when TMAC file exists + */ + bool deleteTransactionMACFileEV2Full(const uint8_t file_no, Ev2Context& ctx); + + /*! + @brief Create TransactionMAC file + @param file_no File number for TMAC file + @param comm_mode Communication mode (0=Plain, 1=MAC, 3=Full) + @param access_rights Access rights + @param tmac_key 16-byte TMAC key + @param tmac_key_ver Key version + @param ctx EV2 context (must be authenticated with AppMasterKey) + @return True if successful + */ + bool createTransactionMACFileEV2Full(const uint8_t file_no, const uint8_t comm_mode, const uint16_t access_rights, + const uint8_t tmac_key[16], const uint8_t tmac_key_ver, Ev2Context& ctx); + bool setConfigurationAppNameEV2Full(const uint8_t* df_name, uint8_t df_name_len, uint16_t iso_fid, Ev2Context& ctx); /*! @brief Read data from DESFire file @@ -232,10 +255,10 @@ public: const uint32_t length, Ev2Context& ctx); bool writeData(const uint8_t file_no, const uint32_t offset, const uint8_t* data, const uint32_t data_len); bool writeDataLight(const uint8_t file_no, const uint32_t offset, const uint8_t* data, const uint32_t data_len); - bool writeDataEV2Mac(const uint8_t file_no, const uint32_t offset, const uint8_t* data, const uint32_t data_len, - Ev2Context& ctx); - bool writeDataEV2Full(const uint8_t file_no, const uint32_t offset, const uint8_t* data, const uint32_t data_len, - Ev2Context& ctx); + bool writeDataLightEV2(const uint8_t file_no, const uint32_t offset, const uint8_t* data, const uint32_t data_len, + Ev2Context& ctx); + bool writeDataLightEV2Full(const uint8_t file_no, const uint32_t offset, const uint8_t* data, + const uint32_t data_len, Ev2Context& ctx); bool authenticateDES(const uint8_t key_no, const uint8_t key[16]); bool authenticateISO(const uint8_t key_no, const uint8_t key[16]); diff --git a/src/nfc/isoDEP/file_system.cpp b/src/nfc/isoDEP/file_system.cpp index 42b0eb3..e53e61a 100644 --- a/src/nfc/isoDEP/file_system.cpp +++ b/src/nfc/isoDEP/file_system.cpp @@ -30,6 +30,9 @@ bool FileSystem::selectFile(const m5::nfc::apdu::SelectBy by, const m5::nfc::apd const uint16_t le = need_select_file_le(p2) ? 256 : 0; auto cmd = make_apdu_command(0x00, m5::stl::to_underlying(INS::SELECT_FILE), p1, p2, param, param_len, le); + // M5_LIB_LOGE("SELECT p1:%02X p2:%02X le:%u len:%u", p1, p2, le, param_len); + // m5::utility::log::dump(cmd.data(), cmd.size(), false); + uint8_t rx[256]{}; uint16_t rx_len = sizeof(rx); if (!_isoDEP.transceiveAPDU(rx, rx_len, cmd.data(), cmd.size()) || rx_len < 2) { @@ -41,9 +44,17 @@ bool FileSystem::selectFile(const m5::nfc::apdu::SelectBy by, const m5::nfc::apd auto tlvs = parse_tlv(rx, rx_len - 2); // dump_tlv(tlvs); + /* + // Debug: Show SELECT response + M5_LIB_LOGE("SELECT response: rx_len=%u SW=%02X:%02X", rx_len, rx[rx_len - 2], rx[rx_len - 1]); + if (rx_len > 2) { + m5::utility::log::dump(rx, rx_len - 2, false); + } + */ + if (!is_response_OK(rx + rx_len - 2)) { - M5_LIB_LOGE("Response error SW:%02X:%02X", rx[rx_len - 2], rx[rx_len - 1]); - // M5_DUMPE(cmd.data(), cmd.size()); + // M5_LIB_LOGE("Response error SW:%02X:%02X", rx[rx_len - 2], rx[rx_len - 1]); + // M5_DUMPE(cmd.data(), cmd.size()); return false; } return true; @@ -62,8 +73,8 @@ bool FileSystem::selectByFileId(const uint16_t fid, const m5::nfc::apdu::SelectR bool FileSystem::selectFileIdAuto(const uint16_t fid, const m5::nfc::apdu::SelectOccurrence occ) { // Response handling varies depending on PICC, so fallback is required - return selectByFileId(fid, m5::nfc::apdu::SelectResponse::None, occ) || - selectByFileId(fid, m5::nfc::apdu::SelectResponse::FCI, occ) || + return selectByFileId(fid, m5::nfc::apdu::SelectResponse::FCI, occ) || + selectByFileId(fid, m5::nfc::apdu::SelectResponse::None, occ) || selectByFileId(fid, m5::nfc::apdu::SelectResponse::FCP, occ); } @@ -151,8 +162,11 @@ bool FileSystem::readBinary(std::vector& out, const uint16_t offset, const uint8_t p1 = static_cast((offset >> 8) & 0xFF); const uint8_t p2 = static_cast(offset & 0xFF); + // const bool sfi = (p1 & 0x80) != 0; + // M5_LIB_LOGE("READ BINARY off:%u p1:%02X p2:%02X le:%u sfi:%u", offset, p1, p2, le, sfi ? 1 : 0); auto cmd = make_apdu_case2(0x00, m5::stl::to_underlying(INS::READ_BINARY), p1, p2, le); + // m5::utility::log::dump(cmd.data(), cmd.size(), false); std::vector rx; rx.resize(le + 2 + 16); @@ -180,8 +194,11 @@ bool FileSystem::updateBinary(const uint16_t offset, const uint8_t* data, const const uint8_t p1 = static_cast((offset >> 8) & 0xFF); const uint8_t p2 = static_cast(offset & 0xFF); + const bool sfi = (p1 & 0x80) != 0; + // M5_LIB_LOGE("UPDATE BINARY off:%u p1:%02X p2:%02X len:%u sfi:%u", offset, p1, p2, data_len, sfi ? 1 : 0); auto cmd = make_apdu_case3(0x00, m5::stl::to_underlying(INS::UPDATE_BINARY), p1, p2, data, data_len); + // m5::utility::log::dump(cmd.data(), cmd.size(), false); uint8_t rx[2]{}; uint16_t rx_len = sizeof(rx); diff --git a/src/nfc/layer/a/nfc_layer_a.cpp b/src/nfc/layer/a/nfc_layer_a.cpp index c5249e6..f8e2c88 100644 --- a/src/nfc/layer/a/nfc_layer_a.cpp +++ b/src/nfc/layer/a/nfc_layer_a.cpp @@ -75,6 +75,27 @@ void rotate_byte_left(uint8_t out[8], const uint8_t in[8]) out[7] = in[0]; } +constexpr int8_t kAccessDenied{-1}; +constexpr int8_t kAccessFree{-2}; +int8_t required_read_key_no_from_access_rights(const uint16_t access_rights) +{ + const uint8_t read_key = (access_rights >> 12) & 0x0F; + const uint8_t rw_key = (access_rights >> 4) & 0x0F; + if (read_key == 0x0E) { + return kAccessFree; + } + if (read_key != 0x0F) { + return read_key; + } + if (rw_key == 0x0E) { + return kAccessFree; + } + if (rw_key != 0x0F) { + return rw_key; + } + return kAccessDenied; +} + } // namespace namespace m5 { @@ -648,12 +669,11 @@ bool NFCLayerA::dump(const Key& mkey) return dump_sector_structure(_activePICC, mkey); } else if (_activePICC.supportsNFC()) { return dump_page_structure(_activePICC.blocks); - } else if (_activePICC.isISO14443_4()) { - return dump_iso_dep(); + } else if (_activePICC.isMifareDESFire()) { + return _activePICC.type == Type::MIFARE_DESFire_Light ? dump_desfire_light() : dump_desfire(); } M5_LIB_LOGW("Not supported %s", _activePICC.typeAsString().c_str()); } - M5_LIB_LOGW("Invalid PICC %s", _activePICC.typeAsString().c_str()); return false; } @@ -963,6 +983,14 @@ bool NFCLayerA::ndefPrepareDesfireLight() return _ndef.prepare_desfire_light(); } +bool NFCLayerA::ndefPrepareDesfire(const uint32_t max_ndef_size) +{ + if (!_activePICC.isMifareDESFire() || _activePICC.type == Type::MIFARE_DESFire_Light) { + return false; + } + return _ndef.prepare_desfire(max_ndef_size); +} + bool NFCLayerA::ndefRead(m5::nfc::ndef::TLV& msg) { msg = TLV{}; @@ -1102,31 +1130,223 @@ bool NFCLayerA::dump_page(const uint8_t page, uint16_t maxPage) return false; } -bool NFCLayerA::dump_iso_dep() +bool NFCLayerA::dump_desfire() { - DESFireFileSystem fs(*this); - std::vector aids{}; + const auto cfg = _isoDEP.config(); + uint16_t max_chunk_len = std::min(256, std::min(cfg.fsc, cfg.pcd_max_frame_rx)); + if (max_chunk_len == 0) { + max_chunk_len = 16; + } + DESFireFileSystem dfs{_isoDEP}; - uint8_t ver[256]{}; - uint16_t ver_len = sizeof(ver); - if (!fs.selectApplication()) { + std::vector aids; + if (!dfs.getApplicationIDs(aids)) { + M5_LIB_LOGE("getApplicationIDs failed"); return false; } - if (!fs.getApplicationIDs(aids)) { - return false; - } - if (!aids.empty()) { + + for (const auto& aid : aids) { + // App + if (!dfs.selectApplication(aid)) { + M5_LIB_LOGW("selectApplication failed %X", aid.aid24()); + continue; + } + printf("AID %02X%02X%02X ", aid.aid[0], aid.aid[1], aid.aid[2]); + + dfs.authenticateDES(0, type4::DESFIRE_DEFAULT_KEY); + + uint8_t key_settings{}; + uint8_t key_count{}; + if (dfs.getKeySettings(key_settings, key_count)) { + printf("key_settings:%02X key_count:%u", key_settings, key_count); + } + putchar('\n'); + + // Files + std::vector file_nos; + if (!dfs.getFileIDs(file_nos)) { + M5_LIB_LOGW(" getFileIDs failed"); + continue; + } + + printf("files:%zu\n", file_nos.size()); + uint32_t idx{}; - for (auto& a : aids) { - M5_LIB_LOGD(" AID[%2u]:%02X:%02X:%02X", idx, a.aid[0], a.aid[1], a.aid[2]); + for (const auto file_no : file_nos) { ++idx; + FileSettings settings{}; + if (!dfs.getFileSettings(settings, file_no)) { + M5_LIB_LOGW("getFileSettings failed file_no %u", file_no); + continue; + } + printf("---- [%02u]:file_no %u type=%u comm=%u ar=%04X size=%u\n", idx - 1, file_no, settings.file_type, + settings.comm_mode, settings.access_rights, settings.file_size); + if (settings.file_size == 0) { + continue; + } + { + const int8_t read_key = required_read_key_no_from_access_rights(settings.access_rights); + if (read_key == kAccessDenied) { + M5_LIB_LOGW("dump_desfire: read access denied file_no %u", file_no); + continue; + } + if (read_key >= 0) { + constexpr uint8_t kDefaultKey[16]{}; + if (!dfs.authenticateAES(static_cast(read_key), kDefaultKey)) { + M5_LIB_LOGW("dump_desfire: authenticateAES failed key_no %d file_no %u", read_key, file_no); + continue; + } + } + } + + uint32_t offset = 0; + uint32_t total = 0; + std::vector data{}; + while (offset < settings.file_size) { + const uint32_t remain = settings.file_size - offset; + const uint16_t len = static_cast(std::min(remain, max_chunk_len)); + std::vector chunk; + if (!dfs.readData(chunk, file_no, offset, len)) { + if (offset == 0) { + M5_LIB_LOGW("readData failed"); + } + break; + } + if (chunk.empty()) { + break; + } + offset += chunk.size(); + total += chunk.size(); + data.insert(data.end(), chunk.begin(), chunk.end()); + if (chunk.size() < len) { + break; + } + } + m5::utility::log::dump(data.data(), data.size(), false); } - } else { - puts("No applications"); } return true; } +bool NFCLayerA::dump_desfire_light() +{ + const auto cfg = _isoDEP.config(); + uint16_t max_chunk_len = std::min(16, std::min(cfg.fsc, cfg.pcd_max_frame_rx)); + if (max_chunk_len == 0) { + max_chunk_len = 16; + } + + DESFireFileSystem dfs{_isoDEP}; + // Light default or NDEF + if (!dfs.selectDfNameAuto(type4::DESFIRE_LIGHT_DF_NAME, sizeof(type4::DESFIRE_LIGHT_DF_NAME)) && + !dfs.selectDfNameAuto(type4::NDEF_AID, sizeof(type4::NDEF_AID))) { + M5_LIB_LOGE("selectDFname failed"); + return false; + } + + std::vector file_nos; + if (!dfs.getFileIDs(file_nos)) { + M5_LIB_LOGW("getFileIDs failed"); + return false; + } + printf("files:%zu\n", file_nos.size()); + + desfire::Ev2Context ctx{}; + uint32_t idx{}; + for (const auto file_no : file_nos) { + ++idx; + +#if 0 + if (file_no == 0x0F /*TMAC*/) { + printf("---- [%02u]:file_no %u Probably TMAC, so it can't be read\n", idx - 1, file_no); + continue; + } +#endif + + // For clear auth status + if (!dfs.selectDfNameAuto(type4::DESFIRE_LIGHT_DF_NAME, sizeof(type4::DESFIRE_LIGHT_DF_NAME)) && + !dfs.selectDfNameAuto(type4::NDEF_AID, sizeof(type4::NDEF_AID))) { + M5_LIB_LOGE("selectDFname failed"); + return false; + } + + // Get fileSeetings + FileSettings settings{}; + if (!dfs.getFileSettings(settings, file_no)) { + if (!dfs.authenticateEV2First(0x00 /* key 0*/, type4::DESFIRE_DEFAULT_KEY, ctx) || + (!dfs.getFileSettingsEV2Full(settings, file_no, ctx) && + !dfs.getFileSettingsEV2(settings, file_no, ctx))) { + M5_LIB_LOGW("getFileSettings failed file_no [%02u]:%u", idx - 1, file_no); + continue; + } + } + const int8_t read_key = required_read_key_no_from_access_rights(settings.access_rights); + if (read_key == kAccessDenied) { + printf("read access denied file_no %u\n", file_no); + continue; + } + if (read_key >= 0 || settings.comm_mode != 0) { + ctx = {}; + if (!dfs.authenticateEV2First(static_cast(read_key), type4::DESFIRE_DEFAULT_KEY, ctx)) { + M5_LIB_LOGW("authenticateEV2First failed key_no %d file_no %u", read_key, file_no); + } + } + + switch (settings.file_type) { + case 0x02: // Value file + printf("---- [%02u]:file_no %u Value file, so it can't be read\n", idx - 1, file_no); + continue; + break; + case 0x04: // CyclicRecord file + printf("---- [%02u]:file_no %u CyclicRecord file, so it can't be read\n", idx - 1, file_no); + continue; + break; + case 0x05: // TMAC file + printf("---- [%02u]:file_no %u TMAC, so it can't be read\n", idx - 1, file_no); + continue; + break; + default: + printf("---- [%02u]:file_no %u type=%u comm=%u ar=%04X size=%u\n", idx - 1, file_no, settings.file_type, + settings.comm_mode, settings.access_rights, settings.file_size); + break; + } + + uint32_t offset{}, total{}; + std::vector data{}; + while (offset < settings.file_size) { + const uint32_t remain = settings.file_size - offset; + const uint16_t len = static_cast(std::min(remain, max_chunk_len)); + std::vector chunk; + bool ok{}; + switch (settings.comm_mode) { + case 0: + ok = dfs.readDataLight(chunk, file_no, offset, len); + break; + case 1: + ok = dfs.readDataLightEV2(chunk, file_no, offset, len, ctx); + break; + case 3: + ok = dfs.readDataLightEV2Full(chunk, file_no, offset, len, ctx); + break; + default: + break; + } + if (!ok || chunk.empty()) { + break; + } + offset += chunk.size(); + total += chunk.size(); + data.insert(data.end(), chunk.begin(), chunk.end()); + if (chunk.size() < len) { + break; + } + } + m5::utility::log::dump(data.data(), data.size(), false); + } + + return true; +} + bool NFCLayerA::push_back_picc(std::vector& v, const m5::nfc::a::PICC& picc) { // Keep unique valid PICC diff --git a/src/nfc/layer/a/nfc_layer_a.hpp b/src/nfc/layer/a/nfc_layer_a.hpp index 470848d..3178820 100644 --- a/src/nfc/layer/a/nfc_layer_a.hpp +++ b/src/nfc/layer/a/nfc_layer_a.hpp @@ -431,6 +431,12 @@ public: @return True if successful */ bool ndefPrepareDesfireLight(); + /*! + @brief Prepare NDEF files on MIFARE DESFire (EV1/EV2/EV3) + @param max_ndef_size Max size for NDEF file + @return True if successful + */ + bool ndefPrepareDesfire(const uint32_t max_ndef_size); /*! @brief Read NDEF Message TLV @param[out] msg Messgae If it does not exist, a Null TLV is returned @@ -519,7 +525,8 @@ protected: bool dump_sector(const uint8_t sector); bool dump_page_structure(const uint16_t maxPage); bool dump_page(const uint8_t page, const uint16_t maxPage); - bool dump_iso_dep(); + bool dump_desfire(); + bool dump_desfire_light(); static bool push_back_picc(std::vector& v, const m5::nfc::a::PICC& picc); diff --git a/src/nfc/layer/ndef_layer.cpp b/src/nfc/layer/ndef_layer.cpp index 00f303b..68021ec 100644 --- a/src/nfc/layer/ndef_layer.cpp +++ b/src/nfc/layer/ndef_layer.cpp @@ -70,8 +70,11 @@ bool build_type4_cc(std::vector& out, const uint16_t ndef_fid, const ui { constexpr uint16_t kCcLen = 0x000F; constexpr uint8_t kMappingVersion = 0x20; - constexpr uint16_t kMle = 0x003A; - constexpr uint16_t kMlc = 0x0034; + // constexpr uint16_t kMle = 0x003A; + // constexpr uint16_t kMlc = 0x0034; + + constexpr uint16_t kMle = 120; + constexpr uint16_t kMlc = 120; out.clear(); if (!ndef_fid || !ndef_size) { @@ -96,6 +99,50 @@ bool build_type4_cc(std::vector& out, const uint16_t ndef_fid, const ui return out.size() >= 7; } +bool build_desfire_cc(std::vector& out, uint16_t& ndef_fid, uint16_t& ndef_size, + const a::mifare::desfire::NdefFormatOptions& opt) +{ + out.clear(); + if (opt.cc.fctlvs.empty()) { + return false; + } + const auto& fct = opt.cc.fctlvs.front(); + if (!fct.ndef_file_id) { + return false; + } + const uint16_t cc_len = opt.cc_file_size ? opt.cc_file_size : 0x000F; + const uint8_t mapping = opt.cc.mapping_version ? opt.cc.mapping_version : 0x20; + const uint16_t mle = opt.cc.mle ? opt.cc.mle : 0x003A; + const uint16_t mlc = opt.cc.mlc ? opt.cc.mlc : 0x0034; + + ndef_fid = fct.ndef_file_id; + ndef_size = fct.ndef_file_size ? fct.ndef_file_size : opt.ndef_file_size; + if (fct.ndef_file_size && opt.ndef_file_size && fct.ndef_file_size != opt.ndef_file_size) { + return false; + } + if (!ndef_size) { + return false; + } + + out.reserve(cc_len); + out.push_back(static_cast((cc_len >> 8) & 0xFF)); + out.push_back(static_cast(cc_len & 0xFF)); + out.push_back(mapping); + out.push_back(static_cast((mle >> 8) & 0xFF)); + out.push_back(static_cast(mle & 0xFF)); + out.push_back(static_cast((mlc >> 8) & 0xFF)); + out.push_back(static_cast(mlc & 0xFF)); + out.push_back(0x04); + out.push_back(0x06); + out.push_back(static_cast((ndef_fid >> 8) & 0xFF)); + out.push_back(static_cast(ndef_fid & 0xFF)); + out.push_back(static_cast((ndef_size >> 8) & 0xFF)); + out.push_back(static_cast(ndef_size & 0xFF)); + out.push_back(fct.read_access); + out.push_back(fct.write_access); + return out.size() >= 7; +} + constexpr int8_t kAccessDenied{-1}; constexpr int8_t kAccessFree{-2}; int8_t required_key_no_from_access_rights(const uint16_t access_rights) @@ -117,6 +164,26 @@ int8_t required_key_no_from_access_rights(const uint16_t access_rights) return kAccessDenied; } +inline bool is_file_system_ISO(const file_system_feature_t fsf) +{ + return (fsf & FILE_SYSTEM_ISO7816_4); +} + +inline bool is_file_system_desfire(const file_system_feature_t fsf) +{ + return (fsf & (FILE_SYSTEM_DESFIRE | FILE_SYSTEM_DESFIRE_LIGHT)); +} + +inline bool is_file_system_desfire_ev(const file_system_feature_t fsf) +{ + return (fsf & FILE_SYSTEM_DESFIRE); +} + +inline bool is_file_system_desfire_light(const file_system_feature_t fsf) +{ + return (fsf & FILE_SYSTEM_DESFIRE_LIGHT); +} + } // namespace namespace m5 { @@ -150,9 +217,8 @@ bool NDEFLayer::isValidFormat(bool& valid, const m5::nfc::NFCForumTag ftag) valid = cc.valid(); return true; } - // Unformatted DESFire returns success - if (_interface.supportsFilesystem() & (FILE_SYSTEM_DESFIRE | FILE_SYSTEM_DESFIRE_LIGHT)) { - M5_LIB_LOGE(">>>> DESFire not in NDEF format"); + // In the case of DESFire, failures are considered to be non-NEDF format + if (is_file_system_desfire(_interface.supportsFilesystem())) { valid = false; return true; } @@ -178,9 +244,11 @@ bool NDEFLayer::prepare_desfire_light() using type4::DESFIRE_LIGHT_DF_NAME; using type4::DESFIRE_LIGHT_NDEF_FILE_NO; using type4::DESFIRE_LIGHT_NDEF_FILE_SIZE; + using type4::NDEF_AID; + using type4::NDEF_APP_FID; using type4::NDEF_FILE_ID; - if (!(_interface.supportsFilesystem() & FILE_SYSTEM_DESFIRE_LIGHT)) { + if (!is_file_system_desfire_light(_interface.supportsFilesystem())) { return false; } auto* dep = _interface.isoDEP(); @@ -190,19 +258,22 @@ bool NDEFLayer::prepare_desfire_light() // Select DF a::mifare::desfire::DESFireFileSystem dfs(*dep); - if (!dfs.selectDfNameAuto(DESFIRE_LIGHT_DF_NAME, sizeof(DESFIRE_LIGHT_DF_NAME))) { - M5_LIB_LOGE("prepare_desfire_light: select DF failed"); - return false; + const bool ndef_aid_available = dfs.selectDfNameAuto(NDEF_AID, sizeof(NDEF_AID)); + if (!ndef_aid_available) { + // If NDEF is not selectable, select Light default + if (!dfs.selectDfNameAuto(DESFIRE_LIGHT_DF_NAME, sizeof(DESFIRE_LIGHT_DF_NAME))) { + M5_LIB_LOGE("select default DF failed"); + return false; + } } // Rename file ID to NDEF format if (!dfs.selectFileIdAuto(type4::CC_FILE_ID)) { - M5_LIB_LOGE("select CC(E103) failed -> rename"); + M5_LIB_LOGE(">>>> select CC(E103) failed -> rename"); a::mifare::desfire::FileRename cc_rename{}; cc_rename.old_file_no = DESFIRE_LIGHT_CC_FILE_NO; cc_rename.new_file_no = DESFIRE_LIGHT_CC_FILE_NO; cc_rename.new_file_id = type4::CC_FILE_ID; - a::mifare::desfire::FileRename ndef_rename{}; ndef_rename.old_file_no = DESFIRE_LIGHT_NDEF_FILE_NO; ndef_rename.new_file_no = DESFIRE_LIGHT_NDEF_FILE_NO; @@ -263,12 +334,6 @@ bool NDEFLayer::prepare_desfire_light() constexpr uint8_t kPlainCommMode{0x00}; // Plain constexpr uint8_t file_option = (kPlainCommMode & 0x03); if (cc_settings.access_rights != kFreeAccessRights || cc_settings.comm_mode != kPlainCommMode) { -#if 0 - if (cc_settings.comm_mode != kPlainCommMode) { - M5_LIB_LOGE("prepare_desfire_light: CC comm_mode %u not supported", cc_settings.comm_mode); - return false; - } -#endif if (!change_file_settings_mode(DESFIRE_LIGHT_CC_FILE_NO, file_option, kFreeAccessRights, cc_settings.access_rights)) { M5_LIB_LOGE("changeFileSettings CC failed"); @@ -278,12 +343,6 @@ bool NDEFLayer::prepare_desfire_light() cc_settings.comm_mode = kPlainCommMode; } if (ndef_settings.access_rights != kFreeAccessRights || ndef_settings.comm_mode != kPlainCommMode) { -#if 0 - if (ndef_settings.comm_mode != kPlainCommMode) { - M5_LIB_LOGE("NDEF comm_mode %u not supported", ndef_settings.comm_mode); - return false; - } -#endif if (!change_file_settings_mode(DESFIRE_LIGHT_NDEF_FILE_NO, file_option, kFreeAccessRights, ndef_settings.access_rights)) { M5_LIB_LOGE("changeFileSettings NDEF failed"); @@ -300,9 +359,9 @@ bool NDEFLayer::prepare_desfire_light() case 0x00: return dfs.writeDataLight(file_no, 0, data, data_len); case 0x01: - return dfs.writeDataEV2Mac(file_no, 0, data, data_len, ctx); + return dfs.writeDataLightEV2(file_no, 0, data, data_len, ctx); case 0x03: - return dfs.writeDataEV2Full(file_no, 0, data, data_len, ctx); + return dfs.writeDataLightEV2Full(file_no, 0, data, data_len, ctx); default: return false; } @@ -335,7 +394,265 @@ bool NDEFLayer::prepare_desfire_light() M5_LIB_LOGE("write NLEN failed (comm %u)", ndef_settings.comm_mode); return false; } - M5_LIB_LOGE("prepare_desfire_light: done"); + + // Write FCI file (1Fh) for ISO SELECT response + // This is required for iPhone/NFC readers to recognize the tag as T4T + // FCI format: 6F (FCI Template) containing 84 (DF Name = NDEF AID) + { + constexpr uint8_t DESFIRE_LIGHT_FCI_FILE_NO{0x1F}; + // clang-format off + const uint8_t fci_data[] = { + 0x6F, 0x09, // FCI Template, length=9 + 0x84, 0x07, // DF Name tag, length=7 + 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01 // NDEF AID + }; + // clang-format on + + // Re-authenticate for FCI operations (ctx may be stale) + a::mifare::desfire::Ev2Context ctx_fci{}; + if (!dfs.authenticateEV2First(0x00, DESFIRE_DEFAULT_KEY, ctx_fci)) { + M5_LIB_LOGW("auth EV2 failed for FCI - skipping FCI write"); + } else { + a::mifare::desfire::FileSettings fci_settings{}; + if (dfs.getFileSettingsEV2(fci_settings, DESFIRE_LIGHT_FCI_FILE_NO, ctx_fci)) { + M5_LIB_LOGD("FCI file settings: type=%u comm=%u ar=%04X size=%u", fci_settings.file_type, + fci_settings.comm_mode, fci_settings.access_rights, fci_settings.file_size); + + // Change to free access and plain mode if needed + if (fci_settings.access_rights != kFreeAccessRights || fci_settings.comm_mode != kPlainCommMode) { + if (dfs.changeFileSettingsEV2Full(DESFIRE_LIGHT_FCI_FILE_NO, file_option, kFreeAccessRights, + ctx_fci)) { + fci_settings.access_rights = kFreeAccessRights; + fci_settings.comm_mode = kPlainCommMode; + // Re-authenticate after changing settings + dfs.authenticateEV2First(0x00, DESFIRE_DEFAULT_KEY, ctx_fci); + } + } + + // Write FCI data + bool fci_written = false; + if (fci_settings.comm_mode == 0x00) { + fci_written = dfs.writeDataLight(DESFIRE_LIGHT_FCI_FILE_NO, 0, fci_data, sizeof(fci_data)); + } else if (fci_settings.comm_mode == 0x03) { + fci_written = + dfs.writeDataLightEV2Full(DESFIRE_LIGHT_FCI_FILE_NO, 0, fci_data, sizeof(fci_data), ctx_fci); + } else { + fci_written = + dfs.writeDataLightEV2(DESFIRE_LIGHT_FCI_FILE_NO, 0, fci_data, sizeof(fci_data), ctx_fci); + } + + if (fci_written) { + M5_LIB_LOGI("FCI file written for T4T compatibility"); + } else { + M5_LIB_LOGW("write FCI failed (comm %u) - continuing anyway", fci_settings.comm_mode); + } + } else { + M5_LIB_LOGW("getFileSettings FCI failed - skipping FCI write"); + } + } + } + + // Change application to NDEF DF name after NDEF setup completes + // NOTICE : You can only change your DF name / Fild ID ONCE! + // Default: DF Name=A00000039656434103F015400000000B, ISO File ID=DF01 + // NDEF: DF Name=D2760000850101, ISO File ID=E110. + if (!ndef_aid_available) { + a::mifare::desfire::Ev2Context ctx_rename{}; + if (!dfs.authenticateEV2First(0x00, DESFIRE_DEFAULT_KEY, ctx_rename)) { + M5_LIB_LOGE("auth EV2 failed (DF name)"); + return false; + } + if (!dfs.setConfigurationAppNameEV2Full(NDEF_AID, sizeof(NDEF_AID), NDEF_APP_FID, ctx_rename)) { + M5_LIB_LOGE(" setConfiguration app name failed"); + return false; + } + } + + // Delete TransactionMAC file (File 0Fh) if it exists + // This is required for ISOReadBinary/ISOUpdateBinary to work on DESFire Light + // Without this, ISO7816-4 commands fail with SW:69:85 + // Need fresh authentication because previous ctx may be stale + constexpr uint8_t DESFIRE_LIGHT_TMAC_FILE_NO{0x0F}; + if (dfs.selectDfNameAuto(NDEF_AID, sizeof(NDEF_AID)) || + dfs.selectDfNameAuto(DESFIRE_LIGHT_DF_NAME, sizeof(DESFIRE_LIGHT_DF_NAME))) { + a::mifare::desfire::Ev2Context ctx_tmac{}; + if (dfs.authenticateEV2First(0x00, DESFIRE_DEFAULT_KEY, ctx_tmac)) { + if (dfs.deleteTransactionMACFileEV2Full(DESFIRE_LIGHT_TMAC_FILE_NO, ctx_tmac)) { + M5_LIB_LOGI("TMAC file deleted for ISO7816 compatibility"); + } + // Note: Delete may fail if TMAC file doesn't exist - that's OK + } + } + + M5_LIB_LOGE("done"); + return true; +} + +bool NDEFLayer::prepare_desfire(const uint32_t max_ndef_size) +{ + using a::mifare::desfire::NdefFormatOptions; + using type4::DESFIRE_CC_FILE_NO; + using type4::DESFIRE_DEFAULT_KEY; + using type4::DESFIRE_NDEF_FILE_NO; + using type4::NDEF_AID; + using type4::NDEF_APP_FID; + using type4::NDEF_FILE_ID; + + if (!max_ndef_size) { + return false; + } + if (!is_file_system_desfire_ev(_interface.supportsFilesystem())) { + return false; + } + auto* dep = _interface.isoDEP(); + if (!dep) { + return false; + } + + a::mifare::desfire::DESFireFileSystem dfs(*dep); + + uint32_t free_mem{}; + uint16_t ndef_file_size{}; + if (dfs.getFreeMemory(free_mem)) { + const uint32_t overhead = 64 + 15; // App header + CC file + uint32_t available = (free_mem > overhead) ? (free_mem - overhead) : 0; + if (free_mem >= 7936) { + constexpr uint32_t kAllocUnit = 256; + if (available > kAllocUnit) { + // Reserve one allocation unit for the CC file on 8K+ devices. + available -= kAllocUnit; + } + available = (available / kAllocUnit) * kAllocUnit; + } + ndef_file_size = static_cast(std::min(max_ndef_size, available)); + M5_LIB_LOGV("Free memory: %u, NDEF size: %u", free_mem, ndef_file_size); + } + if (ndef_file_size < 32) { + M5_LIB_LOGE("NDEF size too small: %u (free: %u)", ndef_file_size, free_mem); + return false; + } + + NdefFormatOptions opt{}; + opt.cc_file_no = DESFIRE_CC_FILE_NO; + opt.ndef_file_no = DESFIRE_NDEF_FILE_NO; + opt.picc_master_key = DESFIRE_DEFAULT_KEY; + opt.app_master_key = DESFIRE_DEFAULT_KEY; + opt.ndef_file_size = ndef_file_size; + type4::FileControlTLV fct{}; + fct.tag = 0x04; + fct.len = 0x06; + fct.ndef_file_id = NDEF_FILE_ID; + fct.ndef_file_size = ndef_file_size; + fct.read_access = 0x00; + fct.write_access = 0x00; + opt.cc.fctlvs.push_back(fct); + + bool picc_des_ok{}; + bool picc_iso_ok{}; + bool picc_aes_ok{}; + if (opt.picc_master_key) { + bool ok = false; + if (opt.auth_mode == a::mifare::desfire::AuthMode::DES || opt.auth_mode == a::mifare::desfire::AuthMode::Auto) { + picc_des_ok = dfs.authenticateDES(0x00, opt.picc_master_key); + if (!picc_des_ok) { + picc_iso_ok = dfs.authenticateISO(0x00, opt.picc_master_key); + } + ok = picc_des_ok || picc_iso_ok; + } + if (!ok && (opt.auth_mode == a::mifare::desfire::AuthMode::AES || + opt.auth_mode == a::mifare::desfire::AuthMode::Auto)) { + picc_aes_ok = dfs.authenticateAES(0x00, opt.picc_master_key); + ok = picc_aes_ok; + } + if (!ok) { + M5_LIB_LOGE("PICC master auth failed"); + return false; + } + } + + constexpr uint16_t app_iso_fid = NDEF_APP_FID; + const auto* df_name = NDEF_AID; + constexpr uint8_t df_name_len = sizeof(NDEF_AID); + + uint8_t key_settings2 = opt.key_settings2; + if (opt.auth_mode == a::mifare::desfire::AuthMode::Auto && opt.picc_master_key) { + const uint8_t base = static_cast(opt.key_settings2 & 0x3F); + if (picc_aes_ok) { + key_settings2 = static_cast(0x80 | base); + } else if (picc_iso_ok || picc_des_ok) { + key_settings2 = base; + } + } + + auto created = dfs.createApplication(opt.aid, opt.key_settings1, key_settings2, app_iso_fid, df_name, df_name_len); + if (!created.has_value()) { + M5_LIB_LOGE("create application failed (0x%02X)", created.error()); + return false; + } + + if (!dfs.selectApplication(opt.aid)) { + M5_LIB_LOGE("select application failed"); + return false; + } + if (opt.app_master_key) { + bool ok = false; + if (opt.auth_mode == a::mifare::desfire::AuthMode::Auto && opt.picc_master_key) { + if (picc_aes_ok) { + ok = dfs.authenticateAES(0x00, opt.app_master_key); + } else if (picc_iso_ok) { + ok = dfs.authenticateISO(0x00, opt.app_master_key); + } else { + ok = dfs.authenticateDES(0x00, opt.app_master_key); + if (!ok) { + ok = dfs.authenticateISO(0x00, opt.app_master_key); + } + } + } else { + if (opt.auth_mode == a::mifare::desfire::AuthMode::DES || + opt.auth_mode == a::mifare::desfire::AuthMode::Auto) { + ok = dfs.authenticateDES(0x00, opt.app_master_key); + if (!ok) { + ok = dfs.authenticateISO(0x00, opt.app_master_key); + } + } + if (!ok && (opt.auth_mode == a::mifare::desfire::AuthMode::AES || + opt.auth_mode == a::mifare::desfire::AuthMode::Auto)) { + ok = dfs.authenticateAES(0x00, opt.app_master_key); + } + } + if (!ok) { + M5_LIB_LOGE("prepare_desfire: app master auth failed"); + return false; + } + } + + if (!dfs.createStdDataFile(opt.cc_file_no, type4::CC_FILE_ID, opt.comm_mode, opt.access_rights, opt.cc_file_size)) { + M5_LIB_LOGE("prepare_desfire: create CC file failed"); + return false; + } + + std::vector cc; + uint16_t ndef_fid{}; + uint16_t ndef_size{}; + if (!build_desfire_cc(cc, ndef_fid, ndef_size, opt)) { + M5_LIB_LOGE("prepare_desfire: build CC failed"); + return false; + } + if (!dfs.writeData(opt.cc_file_no, 0, cc.data(), static_cast(cc.size()))) { + M5_LIB_LOGE("prepare_desfire: write CC failed"); + return false; + } + + if (!dfs.createStdDataFile(opt.ndef_file_no, ndef_fid, opt.comm_mode, opt.access_rights, ndef_size)) { + M5_LIB_LOGE("prepare_desfire: create NDEF file failed"); + return false; + } + + const uint8_t nlen0[2] = {0x00, 0x00}; + if (!dfs.writeData(opt.ndef_file_no, 0, nlen0, sizeof(nlen0))) { + M5_LIB_LOGE("prepare_desfire: write NLEN failed"); + return false; + } return true; } @@ -386,15 +703,15 @@ bool NDEFLayer::readAttributeBlock(m5::nfc::ndef::type3::AttributeBlock& ab) bool NDEFLayer::readCapabilityContainer(m5::nfc::ndef::type4::CapabilityContainer& cc) { - return (_interface.supportsFilesystem() & (FILE_SYSTEM_DESFIRE | FILE_SYSTEM_DESFIRE_LIGHT)) - ? read_capability_container_type4_desfire(cc) - : read_capability_container_type4_iso7816(cc); + return is_file_system_desfire_ev(_interface.supportsFilesystem()) ? read_capability_container_type4_desfire(cc) + : read_capability_container_type4_iso7816(cc); } bool NDEFLayer::read_capability_container_type4_iso7816(m5::nfc::ndef::type4::CapabilityContainer& cc) { using type4::CapabilityContainer; using type4::NDEF_AID; + using type4::NDEF_APP_FID; cc = CapabilityContainer{}; @@ -407,11 +724,9 @@ bool NDEFLayer::read_capability_container_type4_iso7816(m5::nfc::ndef::type4::Ca if (!fs.selectDfNameAuto(NDEF_AID, sizeof(NDEF_AID))) { return false; } - const bool selected_cc = fs.selectFileIdAuto(m5::nfc::ndef::type4::CC_FILE_ID); - if (!selected_cc) { + if (!fs.selectFileIdAuto(m5::nfc::ndef::type4::CC_FILE_ID)) { return false; } - const auto read_cc = [&fs](std::vector& out, const uint16_t offset, const uint16_t len) { return fs.readBinary(out, offset, len); }; @@ -425,6 +740,7 @@ bool NDEFLayer::read_capability_container_type4_desfire(m5::nfc::ndef::type4::Ca using type4::DESFIRE_LIGHT_CC_FILE_NO; using type4::DESFIRE_LIGHT_DF_NAME; using type4::DESFIRE_NDEF_APP_ID; + using type4::NDEF_AID; cc = CapabilityContainer{}; @@ -434,11 +750,10 @@ bool NDEFLayer::read_capability_container_type4_desfire(m5::nfc::ndef::type4::Ca } a::mifare::desfire::DESFireFileSystem dfs(*dep); - const bool is_light = _interface.supportsFilesystem() & FILE_SYSTEM_DESFIRE_LIGHT; - if (is_light) { - M5_LIB_LOGE(">>>>>>>>>>> CC light"); - - if (!dfs.selectDfNameAuto(DESFIRE_LIGHT_DF_NAME, sizeof(DESFIRE_LIGHT_DF_NAME))) { + // Light + if (is_file_system_desfire_light(_interface.supportsFilesystem())) { + // if (!dfs.selectDfNameAuto(DESFIRE_LIGHT_DF_NAME, sizeof(DESFIRE_LIGHT_DF_NAME))) { + if (!dfs.selectDfNameAuto(NDEF_AID, sizeof(NDEF_AID))) { return false; } const auto read_cc = [&dfs](std::vector& out, const uint16_t offset, const uint16_t len) { @@ -446,9 +761,7 @@ bool NDEFLayer::read_capability_container_type4_desfire(m5::nfc::ndef::type4::Ca }; return read_cc_common(read_cc, cc); } - - M5_LIB_LOGE(">>>>>>>>>>> CC DES"); - + // DESFire if (!dfs.selectApplication(DESFIRE_NDEF_APP_ID)) { return false; } @@ -642,9 +955,8 @@ skip: bool NDEFLayer::read_type4(std::vector& tlvs, const m5::nfc::ndef::type4::FileControlTagBits fc_bits) { - return (_interface.supportsFilesystem() & (FILE_SYSTEM_DESFIRE | FILE_SYSTEM_DESFIRE_LIGHT)) - ? read_type4_desfire(tlvs, fc_bits) - : read_type4_iso7816(tlvs, fc_bits); + return is_file_system_desfire_ev(_interface.supportsFilesystem()) ? read_type4_desfire(tlvs, fc_bits) + : read_type4_iso7816(tlvs, fc_bits); } namespace { @@ -697,16 +1009,20 @@ bool build_ndef_payload(const m5::nfc::ndef::TLV& src, const uint32_t user_size, std::accumulate(src.records().begin(), src.records().end(), 0U, [](uint32_t acc, const m5::nfc::ndef::Record& r) { return acc + r.required(); }); if (record_size == 0) { + M5_LIB_LOGE("recodrd size 0"); return false; } if (ndef_file_size && ndef_file_size < 2) { + M5_LIB_LOGE("ndef_file_size too small"); return false; } if (ndef_file_size && record_size > (ndef_file_size - 2)) { + M5_LIB_LOGE("over size"); return false; } const uint32_t total_size = record_size + 2; // NLEN + payload if (user_size && total_size > user_size) { + M5_LIB_LOGE("over size 2"); return false; } @@ -716,6 +1032,7 @@ bool build_ndef_payload(const m5::nfc::ndef::TLV& src, const uint32_t user_size, for (auto&& r : src.records()) { auto len = r.encode(msg.data() + offset, record_size - offset); if (!len) { + M5_LIB_LOGE("decode error"); return false; } offset += len; @@ -835,6 +1152,7 @@ bool NDEFLayer::read_type4_desfire(std::vector& tlvs, using type4::DESFIRE_NDEF_APP_ID; using type4::DESFIRE_NDEF_FILE_NO; using type4::FileControlTag; + using type4::NDEF_AID; tlvs.clear(); @@ -849,42 +1167,43 @@ bool NDEFLayer::read_type4_desfire(std::vector& tlvs, } a::mifare::desfire::DESFireFileSystem dfs(*dep); - const bool is_light = _interface.supportsFilesystem() & FILE_SYSTEM_DESFIRE_LIGHT; - if (is_light) { - if (!dfs.selectDfNameAuto(DESFIRE_LIGHT_DF_NAME, sizeof(DESFIRE_LIGHT_DF_NAME))) { + ReadFn read_message{}; + + // Light + if (is_file_system_desfire_light(_interface.supportsFilesystem())) { + // if (!dfs.selectDfNameAuto(DESFIRE_LIGHT_DF_NAME, sizeof(DESFIRE_LIGHT_DF_NAME))) { + if (!dfs.selectDfNameAuto(NDEF_AID, sizeof(NDEF_AID))) { return false; } - for (auto&& fctlv : cc.fctlvs) { - if (!type4::contains_file_control_tag(fc_bits, fctlv.fctag())) { - continue; - } - if (fctlv.fctag() == FileControlTag::Proprietary) { - return false; - } - const ReadFn read_message = [&dfs](std::vector& out, const uint16_t offset, const uint16_t len) { - return dfs.readDataLight(out, DESFIRE_LIGHT_NDEF_FILE_NO, offset, len); - }; - if (!read_type4_message(tlvs, cc, fctlv, read_message)) { - return false; - } - } - return true; + read_message = [&dfs](std::vector& out, const uint16_t offset, const uint16_t len) { + return dfs.readDataLight(out, DESFIRE_LIGHT_NDEF_FILE_NO, offset, len); + }; } - if (!dfs.selectApplication(DESFIRE_NDEF_APP_ID)) { - return false; + // DESFire + else { + if (!dfs.selectApplication(DESFIRE_NDEF_APP_ID)) { + return false; + } + read_message = [&dfs](std::vector& out, const uint16_t offset, const uint16_t len) { + return dfs.readData(out, DESFIRE_NDEF_FILE_NO, offset, len); + }; } for (auto&& fctlv : cc.fctlvs) { if (!type4::contains_file_control_tag(fc_bits, fctlv.fctag())) { continue; } - if (fctlv.fctag() == FileControlTag::Proprietary) { - return false; - } - const ReadFn read_message = [&dfs](std::vector& out, const uint16_t offset, const uint16_t len) { - return dfs.readData(out, DESFIRE_NDEF_FILE_NO, offset, len); - }; - if (!read_type4_message(tlvs, cc, fctlv, read_message)) { + + if (fctlv.fctag() == type4::FileControlTag::Proprietary) { + if (!read_type4_proprietary(tlvs, cc, fctlv, read_message)) { + return false; + } + } else if (fctlv.fctag() == type4::FileControlTag::Message) { + if (!read_type4_message(tlvs, cc, fctlv, read_message)) { + return false; + } + } else { + M5_LIB_LOGE("Illegal type? %02X", fctlv.fctag()); return false; } } @@ -1187,13 +1506,12 @@ bool NDEFLayer::write_type4(const std::vector& tlvs) } CapabilityContainer cc{}; - const bool is_desfire = _interface.supportsFilesystem() & (FILE_SYSTEM_DESFIRE | FILE_SYSTEM_DESFIRE_LIGHT); - ; if (!readCapabilityContainer(cc) || !cc.valid()) { M5_LIB_LOGE("Failed to readCapabilityContainer"); return false; } - return is_desfire ? write_type4_desfire(tlvs, cc, *dep) : write_type4_iso7816(tlvs, cc, *dep); + return is_file_system_desfire_ev(_interface.supportsFilesystem()) ? write_type4_desfire(tlvs, cc, *dep) + : write_type4_iso7816(tlvs, cc, *dep); } bool NDEFLayer::write_type4_desfire(const std::vector& tlvs, const type4::CapabilityContainer& cc, @@ -1204,6 +1522,7 @@ bool NDEFLayer::write_type4_desfire(const std::vector& tlvs, using type4::DESFIRE_DEFAULT_KEY; using type4::DESFIRE_LIGHT_DF_NAME; using type4::DESFIRE_LIGHT_NDEF_FILE_NO; + using type4::NDEF_AID; const TLV* src = find_message_tlv(tlvs); if (!src) { @@ -1217,40 +1536,15 @@ bool NDEFLayer::write_type4_desfire(const std::vector& tlvs, } a::mifare::desfire::DESFireFileSystem dfs(dep); - const bool is_light = _interface.supportsFilesystem() & FILE_SYSTEM_DESFIRE_LIGHT; - if (is_light) { - if (!dfs.selectDfNameAuto(DESFIRE_LIGHT_DF_NAME, sizeof(DESFIRE_LIGHT_DF_NAME))) { + + // Light + if (is_file_system_desfire_light(_interface.supportsFilesystem())) { + // if (!dfs.selectDfNameAuto(DESFIRE_LIGHT_DF_NAME, sizeof(DESFIRE_LIGHT_DF_NAME))) { + if (!dfs.selectDfNameAuto(NDEF_AID, sizeof(NDEF_AID))) { return false; } -#if 0 - a::mifare::desfire::Ev2Context ctx{}; - if (!dfs.authenticateEV2First(0x00, DESFIRE_DEFAULT_KEY, ctx)) { - return false; - } - a::mifare::desfire::FileSettings ndef_settings{}; - if (!dfs.getFileSettingsEV2(ndef_settings, DESFIRE_LIGHT_NDEF_FILE_NO, ctx)) { - return false; - } - const int ndef_key = required_key_no_from_access_rights(ndef_settings.access_rights); - if (ndef_key == kAccessDenied || - (ndef_key >= 0 && !dfs.authenticateEV2First(static_cast(ndef_key), DESFIRE_DEFAULT_KEY, ctx))) { - return false; - } - const auto write_with_mode = [&dfs, &ctx](const uint8_t file_no, const uint32_t offset, const uint8_t* data, - const uint32_t data_len, const uint8_t comm_mode) { - switch (comm_mode) { - case 0x00: - return dfs.writeData(file_no, offset, data, data_len); - case 0x01: - return dfs.writeDataEV2Mac(file_no, offset, data, data_len, ctx); - case 0x03: - return dfs.writeDataEV2Full(file_no, offset, data, data_len, ctx); - default: - return false; - } - }; - return write_with_mode(DESFIRE_LIGHT_NDEF_FILE_NO, 0, payload.data(), static_cast(payload.size()), - ndef_settings.comm_mode); + return dfs.writeDataLight(type4::DESFIRE_LIGHT_NDEF_FILE_NO, 0, payload.data(), + static_cast(payload.size())); } // DESFire @@ -1268,9 +1562,11 @@ bool NDEFLayer::write_type4_iso7816(const std::vector& tlvs, FileSystem fs(dep); const bool selected_df = fs.selectDfNameAuto(NDEF_AID, sizeof(NDEF_AID)); if (!selected_df) { + M5_LIB_LOGE("selectDF failed"); return false; } + const uint32_t user_size = _interface.user_area_size(); const uint16_t max_lc = cc.mlc ? std::min(cc.mlc, 32) : 0x00FF; const WriteFn write_binary = [&fs](const uint16_t offset, const uint8_t* data, const uint16_t len) { return fs.updateBinary(offset, data, len); @@ -1284,6 +1580,7 @@ bool NDEFLayer::write_type4_iso7816(const std::vector& tlvs, const bool selected_file = fs.selectFileIdAuto(fctlv.ndef_file_id); if (!selected_file) { + M5_LIB_LOGE("select file failed"); return false; } @@ -1303,10 +1600,12 @@ bool NDEFLayer::write_type4_iso7816(const std::vector& tlvs, } std::vector payload; - if (!build_ndef_payload(*src, 0, fctlv.ndef_file_size, payload)) { + if (!build_ndef_payload(*src, user_size, fctlv.ndef_file_size, payload)) { + M5_LIB_LOGW("Not enough area %u/%u", fctlv.ndef_file_size, user_size); return false; } if (!write_chunks(write_binary, payload, max_lc)) { + M5_LIB_LOGE("write_chunk failed"); return false; } } diff --git a/src/nfc/layer/ndef_layer.hpp b/src/nfc/layer/ndef_layer.hpp index 13a10d5..dfd34ad 100644 --- a/src/nfc/layer/ndef_layer.hpp +++ b/src/nfc/layer/ndef_layer.hpp @@ -66,6 +66,12 @@ public: @return True if successful */ bool prepare_desfire_light(); + /*! + @brief Prepare NDEF files on MIFARE DESFire (EV1/EV2/EV3) + @param max_ndef_size Max size for NDEF file + @return True if successful + */ + bool prepare_desfire(const uint32_t max_ndef_size); /*! @brief Read Type2 Capability Container diff --git a/src/nfc/ndef/ndef.hpp b/src/nfc/ndef/ndef.hpp index 09f5916..33efb44 100644 --- a/src/nfc/ndef/ndef.hpp +++ b/src/nfc/ndef/ndef.hpp @@ -358,8 +358,7 @@ struct AttributeBlock { */ namespace type4 { -constexpr uint16_t CC_FILE_ID{0xE103}; //!< CC file id - +constexpr uint16_t CC_FILE_ID{0xE103}; //!< CC file id constexpr uint8_t NDEF_AID[] = {0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01}; //!< AID for NDEF constexpr uint16_t NDEF_APP_FID{0xE110}; //!< ISO DF FID for NDEF app constexpr uint16_t NDEF_FILE_ID{0xE104}; //!< ISO EF FID for NDEF file @@ -371,12 +370,12 @@ constexpr uint8_t DESFIRE_NDEF_AID[] = {0x00, 0x00, 0x01}; //!< DESFire NDEF AI constexpr uint8_t DESFIRE_LIGHT_DF_NAME[] = { //!< DESFire Light default DF Name 0xA0, 0x00, 0x00, 0x03, 0x96, 0x56, 0x43, 0x41, 0x03, 0xF0, 0x15, 0x40, 0x00, 0x00, 0x00, 0x0B}; -constexpr uint16_t DESFIRE_LIGHT_DF_FID{0xDF01}; //!< DESFire Light default DF FID -constexpr uint8_t DESFIRE_LIGHT_CC_FILE_NO{0x00}; //!< DESFire Light CC file number -constexpr uint8_t DESFIRE_LIGHT_NDEF_FILE_NO{0x04}; //!< DESFire Light NDEF file number -constexpr uint16_t DESFIRE_LIGHT_CC_FILE_ID{0xEF00}; //!< DESFire Light CC file ID +constexpr uint16_t DESFIRE_LIGHT_DF_FID{0xDF01}; //!< DESFire Light default DF FID +constexpr uint8_t DESFIRE_LIGHT_CC_FILE_NO{0x00}; //!< DESFire Light CC file number +constexpr uint8_t DESFIRE_LIGHT_NDEF_FILE_NO{0x04}; //!< DESFire Light NDEF file number +constexpr uint16_t DESFIRE_LIGHT_CC_FILE_ID{0xEF00}; //!< DESFire Light CC file ID constexpr uint16_t DESFIRE_LIGHT_NDEF_FILE_ID{0xEF04}; //!< DESFire Light NDEF file ID -constexpr uint16_t DESFIRE_LIGHT_NDEF_FILE_SIZE{256}; //!< DESFire Light NDEF file size (bytes) +constexpr uint16_t DESFIRE_LIGHT_NDEF_FILE_SIZE{256}; //!< DESFire Light NDEF file size (bytes) constexpr uint8_t DESFIRE_DEFAULT_KEY[16]{}; //! DESFire default key