diff --git a/src/nfc/a/mifare.hpp b/src/nfc/a/mifare.hpp index 3800672..2c8a90d 100644 --- a/src/nfc/a/mifare.hpp +++ b/src/nfc/a/mifare.hpp @@ -192,8 +192,10 @@ namespace plus { */ using AESKey = std::array; //! @brief Default key for MIFARE Plus -constexpr AESKey DEFAULT_KEY{}; // All 0x00 - +constexpr AESKey DEFAULT_KEY{}; // All 0x00 +//! @brief Default AES sector key (as classic DEFAULT_KEY) +constexpr AESKey DEFAULT_FF_KEY = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; /*! @struct Keys diff --git a/src/nfc/a/nfca.hpp b/src/nfc/a/nfca.hpp index e8928b8..9cc879e 100644 --- a/src/nfc/a/nfca.hpp +++ b/src/nfc/a/nfca.hpp @@ -121,6 +121,12 @@ inline bool is_mifare_plus(const Type t) return t >= Type::MIFARE_Plus_2K && t <= Type::MIFARE_Plus_SE; } +//! @brief Is type MIFARE classic compatible? (Plus SL1) +inline bool is_mifare_classic_compatible(const Type t, const uint8_t sl) +{ + return is_mifare_plus(t) && (sl == 1); +} + //! @brief Is type MIFARE DESFire? inline bool is_mifare_desfire(const Type t) { @@ -349,10 +355,10 @@ struct PICC { { return is_mifare(type); } - //! @brief Is MIFARE classic? (include Plus SL1) + //! @brief Is MIFARE classic? (include Plus compatible SL) inline bool isMifareClassic() const { - return is_mifare_classic(type) || (is_mifare_plus(type) && security_level == 1); + return is_mifare_classic(type) || isMifareClassicCompatible(); } //! @brief Is MIFARE Ultralight series? inline bool isMifareUltralight() const @@ -364,6 +370,20 @@ struct PICC { { return is_mifare_plus(type); } + //! @brief Is MIFARE Plus X? + inline bool isMifarePlusX() const + { + return is_mifare_plus(type) && sub_type_plus == SubTypePlus::X; + } + //! @brief Is MIFARE Plus S? + inline bool isMifarePlusS() const + { + return is_mifare_plus(type) && sub_type_plus == SubTypePlus::S; + } + inline bool isMifareClassicCompatible() const + { + return is_mifare_classic_compatible(type, security_level); + } //! @brief Is MIFARE DESFire? inline bool isMifareDESFire() const { diff --git a/src/nfc/isoDEP/isoDEP.cpp b/src/nfc/isoDEP/isoDEP.cpp index bf4e0e3..11466d3 100644 --- a/src/nfc/isoDEP/isoDEP.cpp +++ b/src/nfc/isoDEP/isoDEP.cpp @@ -246,7 +246,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* // Send I-Block and receive first frame if (!_layer.transceive(rx_buf, rlen, tx_buf, tpos, timeout_ms)) { if (retries++ < _cfg.max_retries) continue; - M5_LIB_LOGE(">>>>ERROR 1"); + // M5_LIB_LOGE(">>>>ERROR 1"); return false; } @@ -257,7 +257,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* // resend I-Block break; } - M5_LIB_LOGE(">>>>ERROR 2"); + // M5_LIB_LOGE(">>>>ERROR 2"); return false; } if (_cfg.rx_crc && rlen >= 3) { @@ -270,7 +270,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* // resend I-Block break; } - M5_LIB_LOGE(">>>>ERROR 3"); + // M5_LIB_LOGE(">>>>ERROR 3"); return false; } @@ -284,13 +284,13 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* if (retries++ < _cfg.max_retries) { break; // resend I-Block } - M5_LIB_LOGE(">>>>ERROR 4"); + // M5_LIB_LOGE(">>>>ERROR 4"); return false; } const uint8_t wtxm = get_wtxm(rx_buf[rx_overhead_min]); if (!is_valid_wtxm(wtxm)) { - M5_LIB_LOGE(">>>>ERROR 5"); + // M5_LIB_LOGE(">>>>ERROR 5"); return false; } @@ -308,7 +308,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* if (retries++ < _cfg.max_retries) { break; // resend I-Block } - M5_LIB_LOGE(">>>>ERROR 6"); + // M5_LIB_LOGE(">>>>ERROR 6"); return false; } // Parse the newly received frame in the same loop @@ -320,7 +320,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* if (retries++ < _cfg.max_retries) { break; // resend I-Block } - M5_LIB_LOGE(">>>>ERROR 7"); + // M5_LIB_LOGE(">>>>ERROR 7"); return false; } @@ -330,7 +330,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* if (retries++ < _cfg.max_retries) { break; // resend I-Block } - M5_LIB_LOGE(">>>>ERROR 8"); + // M5_LIB_LOGE(">>>>ERROR 8"); return false; } @@ -338,7 +338,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* if (retries++ < _cfg.max_retries) { break; // resend I-Block } - M5_LIB_LOGE(">>>>ERROR 9"); + // M5_LIB_LOGE(">>>>ERROR 9"); return false; } @@ -352,7 +352,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* if (retries++ < _cfg.max_retries) { break; } - M5_LIB_LOGE(">>>>ERROR 10"); + // M5_LIB_LOGE(">>>>ERROR 10"); return false; } @@ -362,14 +362,14 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* if (_cfg.use_cid) idx++; if (_cfg.use_nad) idx++; if (rlen < idx) { - M5_LIB_LOGE(">>>>ERROR 11"); + // M5_LIB_LOGE(">>>>ERROR 11"); return false; } const uint16_t inf_len = (uint16_t)(rlen - idx); if (rx_written + inf_len > rx_inf_len) { - M5_LIB_LOGE("rx_written %u inf_len %u rx_inf_len %u", rx_written, inf_len, rx_inf_len); - m5::utility::log::dump(rx_inf, rx_written, false); + // M5_LIB_LOGE("rx_written %u inf_len %u rx_inf_len %u", rx_written, inf_len, rx_inf_len); + // m5::utility::log::dump(rx_inf, rx_written, false); return false; } @@ -388,7 +388,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* uint16_t rlen2 = sizeof(rx_buf); if (!_layer.transceive(rx_buf, rlen2, r_ack, rp, _cfg.fwt_ms)) { - M5_LIB_LOGE(">>>>ERROR 12"); + // M5_LIB_LOGE(">>>>ERROR 12"); return false; } @@ -401,7 +401,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* // If any S-Block other than WTX exists within the chain, // it shall be treated as unsupported and result in failure. if (is_s_block(pcb2) && !is_s_wtx(pcb2)) { - M5_LIB_LOGE(">>>>ERROR 13"); + // M5_LIB_LOGE(">>>>ERROR 13"); return false; } @@ -423,7 +423,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* rlen2 = sizeof(rx_buf); if (!_layer.transceive(rx_buf, rlen2, s_ack, sp, wtx_timeout)) { - M5_LIB_LOGE(">>>>ERROR 14"); + // M5_LIB_LOGE(">>>>ERROR 14"); return false; } continue; @@ -436,7 +436,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* // ACK: receive again rlen2 = sizeof(rx_buf); if (!_layer.receive(rx_buf, rlen2, _cfg.fwt_ms)) { - M5_LIB_LOGE(">>>>ERROR 15"); + // M5_LIB_LOGE(">>>>ERROR 15"); return false; } continue; @@ -449,9 +449,9 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t* const uint16_t inf_len2 = (uint16_t)(rlen2 - idx2); if (rx_written + inf_len2 > rx_inf_len) { - M5_LIB_LOGE("rx_written %u inf_len2 %u rx_inf_len %u", rx_written, inf_len2, - rx_inf_len); - m5::utility::log::dump(rx_inf, rx_written, false); + // M5_LIB_LOGE("rx_written %u inf_len2 %u rx_inf_len %u", rx_written, inf_len2, + // rx_inf_len); + // m5::utility::log::dump(rx_inf, rx_written, false); return false; } diff --git a/src/nfc/isoDEP/isoDEP.hpp b/src/nfc/isoDEP/isoDEP.hpp index 986bd23..09972a7 100644 --- a/src/nfc/isoDEP/isoDEP.hpp +++ b/src/nfc/isoDEP/isoDEP.hpp @@ -33,10 +33,7 @@ inline uint16_t fsci_to_fsc(const uint8_t fsci) struct config_t { #if 0 - // PICCが受けられる最大INF(FSC) uint16_t fsc{256}; - // PCD側の送受信制約(Unit依存: FIFO/内部バッファ) - // ISO-DEP フレーム全体(PCB+CID/NAD+INF)をこのサイズ以内に収める uint16_t pcd_max_frame_tx{256}; uint16_t pcd_max_frame_rx{256}; #else @@ -44,11 +41,10 @@ struct config_t { uint16_t pcd_max_frame_tx{64}; uint16_t pcd_max_frame_rx{64}; #endif - // 待ち(FWT/WTX)は後で詰められるように一旦msで持つ uint32_t fwt_ms{100}; uint32_t wtx_max_ms{5000}; - // ISO-DEPオプション + // optios bool use_cid{}; uint8_t cid{}; bool use_nad{}; diff --git a/src/nfc/layer/a/nfc_layer_a.cpp b/src/nfc/layer/a/nfc_layer_a.cpp index 4e0de75..ec3b141 100644 --- a/src/nfc/layer/a/nfc_layer_a.cpp +++ b/src/nfc/layer/a/nfc_layer_a.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include using namespace m5::nfc; @@ -75,6 +76,11 @@ void rotate_byte_left(uint8_t out[8], const uint8_t in[8]) out[7] = in[0]; } +uint16_t mifare_plus_key_no(const uint8_t sector, const bool key_b) +{ + return static_cast(0x4000 + sector * 2 + (key_b ? 1 : 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) @@ -195,7 +201,7 @@ bool NFCLayerA::select(m5::nfc::a::PICC& picc) { _activePICC = PICC{}; if (_impl->select(picc)) { - if (picc.isISO14443_4() && !(picc.isMifarePlus() && picc.security_level == 1)) { + if (picc.isISO14443_4() && !picc.isMifareClassicCompatible()) { if (!nfca_request_ats(picc.ats)) { return false; } @@ -206,11 +212,13 @@ bool NFCLayerA::select(m5::nfc::a::PICC& picc) return false; } -bool NFCLayerA::activate(const PICC& picc) +bool NFCLayerA::activate(const PICC& picc, const bool force_rats) { _activePICC = PICC{}; if (_impl->activate(picc)) { - if (picc.isISO14443_4() && !(picc.isMifarePlus() && picc.security_level == 1)) { + M5_LIB_LOGE(" >>>> SEL"); + if (force_rats || (picc.isISO14443_4() && !picc.isMifareClassicCompatible())) { + M5_LIB_LOGE(" >>>> RATS"); ATS discard{}; if (!nfca_request_ats(discard)) { M5_LIB_LOGE("Failed to RATS"); @@ -225,7 +233,7 @@ bool NFCLayerA::activate(const PICC& picc) return false; } -bool NFCLayerA::reactivate(const PICC& picc) +bool NFCLayerA::reactivate(const PICC& picc, const bool force_rats) { PICC tmp = picc; if (_activePICC.valid()) { @@ -239,7 +247,7 @@ bool NFCLayerA::reactivate(const PICC& picc) M5_LIB_LOGE("Failed to wakeup"); return false; } - if (!activate(tmp)) { + if (!activate(tmp, force_rats)) { M5_LIB_LOGE("Failed to activate"); return false; } @@ -252,7 +260,7 @@ bool NFCLayerA::deactivate() _activePICC = PICC{}; auto ret = false; - if (tmp.isMifarePlus() && tmp.security_level == 1) { + if (tmp.isMifareClassicCompatible()) { ret = _impl->hlt(); } else { ret = tmp.isISO14443_4() ? nfca_deselect() : _impl->hlt(); @@ -703,6 +711,8 @@ bool NFCLayerA::dump(const Key& mkey) if (_activePICC.valid()) { if (_activePICC.isMifareClassic()) { return dump_sector_structure(_activePICC, mkey); + } else if (_activePICC.isMifarePlus() && _activePICC.security_level == 2) { + return dump_mifare_plus_sl2(m5::nfc::a::mifare::plus::DEFAULT_KEY); } else if (_activePICC.supportsNFC()) { return dump_page_structure(_activePICC.blocks); } else if (_activePICC.isMifareDESFire()) { @@ -713,6 +723,17 @@ bool NFCLayerA::dump(const Key& mkey) return false; } +bool NFCLayerA::dump(const m5::nfc::a::mifare::plus::AESKey& key) +{ + if (_activePICC.valid()) { + if (_activePICC.isMifarePlus() && _activePICC.security_level == 2) { + return dump_mifare_plus_sl2(key); + } + M5_LIB_LOGW("Not supported %s", _activePICC.typeAsString().c_str()); + } + return false; +} + bool NFCLayerA::dump(const uint8_t block) { if (_activePICC.valid()) { @@ -1007,7 +1028,9 @@ bool NFCLayerA::mifareUltralightCAuthenticate(const uint8_t key[16]) bool NFCLayerA::mifarePlusUpgradeSecurityLevel1(const mifare::plus::AESKey& card_config_key, const mifare::plus::AESKey& card_master_key, + const mifare::plus::AESKey& l2_switch_key, const mifare::plus::AESKey& l3_switch_key, + const mifare::plus::AESKey& aes_sector_key, const mifare::classic::Key& key_a, const mifare::classic::Key& key_b) { if (!_activePICC.valid() || !_activePICC.isMifarePlus() || _activePICC.security_level != 0) { @@ -1053,10 +1076,26 @@ bool NFCLayerA::mifarePlusUpgradeSecurityLevel1(const mifare::plus::AESKey& card if (!write_perso_block(0x9000, card_master_key.data())) { return false; } + if ((_activePICC.sub_type == m5::stl::to_underlying(SubTypePlus::EV2) || + _activePICC.sub_type == m5::stl::to_underlying(SubTypePlus::X)) && + !write_perso_block(0x9002, l2_switch_key.data())) { + return false; + } if (!write_perso_block(0x9003, l3_switch_key.data())) { return false; } + for (uint16_t sector = 0; sector < sectors; ++sector) { + const uint16_t key_a_no = mifare_plus_key_no((uint8_t)sector, false); + const uint16_t key_b_no = mifare_plus_key_no((uint8_t)sector, true); + if (!write_perso_block(key_a_no, aes_sector_key.data())) { + return false; + } + if (!write_perso_block(key_b_no, aes_sector_key.data())) { + return false; + } + } + for (uint16_t sector = 0; sector < sectors; ++sector) { const uint16_t st_block = get_sector_trailer_block_from_sector(sector); if (!write_perso_block(st_block, block)) { @@ -1076,6 +1115,24 @@ bool NFCLayerA::mifarePlusUpgradeSecurityLevel1(const mifare::plus::AESKey& card return true; } +bool NFCLayerA::mifarePlusUpgradeSecurityLevel2(const mifare::plus::AESKey& sl2_switch_key) +{ + if (!_activePICC.valid() || !_activePICC.isMifarePlus() || _activePICC.security_level != 1) { + return false; + } + if (!(_activePICC.sub_type == m5::stl::to_underlying(SubTypePlus::EV2) || + _activePICC.sub_type == m5::stl::to_underlying(SubTypePlus::X))) { + return false; + } + + if (!mifare_plus_authenticateAES(0x9002, sl2_switch_key)) { + M5_LIB_LOGE("SL2 auth failed"); + return false; + } + _activePICC.security_level = 2; + return true; +} + bool NFCLayerA::ndefIsValidFormat(bool& valid) { valid = false; @@ -1237,6 +1294,67 @@ bool NFCLayerA::dump_page(const uint8_t page, uint16_t maxPage) return false; } +bool NFCLayerA::mifare_plus_read_plain_nomac(const uint16_t block, const uint8_t count, std::vector& out) +{ + out.clear(); + if (count == 0 || count > 3) { + return false; + } + uint8_t tx[] = {0x36, (uint8_t)(block & 0xFF), (uint8_t)((block >> 8) & 0xFF), count}; + uint8_t rx[64]{}; + uint16_t rx_len = sizeof(rx); + if (!_isoDEP.transceiveINF(rx, rx_len, tx, sizeof(tx)) || rx_len < 1) { + return false; + } + + const bool has_status = (rx[0] == 0x90 || rx[0] == 0xAF); + const uint8_t* payload = has_status ? rx + 1 : rx; + size_t pay_len = has_status ? (rx_len - 1) : rx_len; + if (pay_len >= 2 && payload[pay_len - 2] == 0x90 && payload[pay_len - 1] == 0x00) { + pay_len -= 2; + } + if (pay_len < static_cast(count) * 16) { + return false; + } + out.insert(out.end(), payload, payload + (size_t)count * 16); + return true; +} + +bool NFCLayerA::dump_mifare_plus_sl2(const m5::nfc::a::mifare::plus::AESKey& key) +{ + uint8_t sectors = get_number_of_sectors(_activePICC.type); + if (!sectors) { + return false; + } + puts( + "Sec[Blk]:00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F [Access]\n" + "-----------------------------------------------------------------"); + + for (uint8_t sector = 0; sector < sectors; ++sector) { + const uint8_t blocks = (sector < 32) ? 4U : 16U; + const uint16_t base = (sector < 32) ? sector * blocks : 128U + (sector - 32) * blocks; + const uint16_t key_no = mifare_plus_key_no(sector, false); + if (!mifare_plus_authenticateAES(key_no, key)) { + M5_LIB_LOGE("SL2 auth failed sector %u key_no %04X", sector, key_no); + return false; + } + + for (uint8_t i = 0; i < blocks; i += 3) { + const uint16_t block = base + i; + const uint8_t cnt = std::min(3U, blocks - i); + std::vector data{}; + if (!mifare_plus_read_plain_nomac(block, cnt, data)) { + M5_LIB_LOGE("SL2 read failed block %u", block); + return false; + } + for (uint8_t j = 0; j < cnt; ++j) { + print_block(data.data() + j * 16, block + j, (j == 0) ? sector : -1, 0x00, false, false); + } + } + } + return true; +} + bool NFCLayerA::dump_desfire() { const auto cfg = _isoDEP.config(); @@ -1770,6 +1888,111 @@ bool NFCLayerA::mifare_get_version_L4_raw(uint8_t* ver, uint16_t& ver_len) return ver_len >= 6; } +bool NFCLayerA::mifare_plus_authenticateAES(const uint16_t key_no, const mifare::plus::AESKey& key) +{ + uint8_t rx[256]{}; + uint16_t rx_len{}; + + // Step 1: 0x70 KeyNo LSB/MSB 0x00 + uint8_t cmd1[] = {0x70, (uint8_t)(key_no & 0xFF), (uint8_t)(key_no >> 8), 0x00}; + rx_len = sizeof(rx); + if (!_isoDEP.transceiveINF(rx, rx_len, cmd1, sizeof(cmd1)) || rx_len < 1) { + M5_LIB_LOGE("AuthAES step1 transceive failed"); + return false; + } + if ((rx[0] != 0x90 && rx[0] != 0xAF) || rx_len < 17) { + M5_LIB_LOGE("AuthAES step1 invalid response len=%u st=%02X", rx_len, rx[0]); + m5::utility::log::dump(rx, rx_len, false); + return false; + } + + const bool step1_has_status = (rx[0] == 0x90 || rx[0] == 0xAF); + const uint8_t* step1_payload = step1_has_status ? rx + 1 : rx; + size_t step1_len = step1_has_status ? (rx_len - 1) : rx_len; + if (step1_len >= 2 && step1_payload[step1_len - 2] == 0x90 && step1_payload[step1_len - 1] == 0x00) { + step1_len -= 2; + } + if (step1_len < 16) { + M5_LIB_LOGE("AuthAES step1 payload too short len=%u", (unsigned)step1_len); + return false; + } + + uint8_t rndB[16]{}; + { + uint8_t iv[16]{}; + mbedtls_aes_context aes{}; + mbedtls_aes_init(&aes); + mbedtls_aes_setkey_dec(&aes, key.data(), 128); + mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, sizeof(rndB), iv, step1_payload, rndB); + mbedtls_aes_free(&aes); + } + + uint8_t rndA[16]{}; + for (auto& b : rndA) { + b = (uint8_t)(esp_random() & 0xFF); + } + + uint8_t rndB_rot[16]{}; + memcpy(rndB_rot, rndB + 1, 15); + rndB_rot[15] = rndB[0]; + + uint8_t ab_plain[32]{}; + memcpy(ab_plain, rndA, 16); + memcpy(ab_plain + 16, rndB_rot, 16); + + uint8_t cmd2[33]{}; + cmd2[0] = 0x72; + { + uint8_t iv[16]{}; + mbedtls_aes_context aes{}; + mbedtls_aes_init(&aes); + mbedtls_aes_setkey_enc(&aes, key.data(), 128); + mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_ENCRYPT, sizeof(ab_plain), iv, ab_plain, cmd2 + 1); + mbedtls_aes_free(&aes); + } + + rx_len = sizeof(rx); + if (!_isoDEP.transceiveINF(rx, rx_len, cmd2, sizeof(cmd2)) || rx_len < 1) { + M5_LIB_LOGE("AuthAES step2 transceive failed"); + return false; + } + if ((rx[0] != 0x90 && rx[0] != 0xAF) || rx_len < 33) { + M5_LIB_LOGE("AuthAES step2 invalid response len=%u st=%02X", rx_len, rx[0]); + m5::utility::log::dump(rx, rx_len, false); + return false; + } + + const bool step2_has_status = (rx[0] == 0x90 || rx[0] == 0xAF); + const uint8_t* step2_payload = step2_has_status ? rx + 1 : rx; + size_t step2_len = step2_has_status ? (rx_len - 1) : rx_len; + if (step2_len >= 2 && step2_payload[step2_len - 2] == 0x90 && step2_payload[step2_len - 1] == 0x00) { + step2_len -= 2; + } + if (step2_len < 32) { + M5_LIB_LOGE("AuthAES step2 payload too short len=%u", (unsigned)step2_len); + return false; + } + + uint8_t ab_resp[32]{}; + { + uint8_t iv[16]{}; + mbedtls_aes_context aes{}; + mbedtls_aes_init(&aes); + mbedtls_aes_setkey_dec(&aes, key.data(), 128); + mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, sizeof(ab_resp), iv, step2_payload, ab_resp); + mbedtls_aes_free(&aes); + } + + uint8_t rndA_rot[16]{}; + memcpy(rndA_rot, rndA + 1, 15); + rndA_rot[15] = rndA[0]; + if (memcmp(ab_resp + 4, rndA_rot, 16) != 0) { + M5_LIB_LOGE("AuthAES rndA mismatch"); + return false; + } + return true; +} + bool NFCLayerA::ntag_read_page(uint8_t* rx, uint16_t& rx_len, const uint8_t spage, const uint8_t epage) { if (!rx || !rx_len || spage > epage || !_activePICC.valid()) { diff --git a/src/nfc/layer/a/nfc_layer_a.hpp b/src/nfc/layer/a/nfc_layer_a.hpp index 51841d1..a5fdb65 100644 --- a/src/nfc/layer/a/nfc_layer_a.hpp +++ b/src/nfc/layer/a/nfc_layer_a.hpp @@ -131,18 +131,20 @@ public: /*! @brief Activate a specific PICC (anti-collision against the given PICC) @param picc PICC + @param force_rats Force Request_rats (For Plus SL1/2) @return True if successful @pre PICC is READY state @post PICC transitions: READY -> ACTIVE on successful response */ - bool activate(const m5::nfc::a::PICC& picc); + bool activate(const m5::nfc::a::PICC& picc, const bool force_rats = false); /*! @brief Wake and activate a specific PICC by PICC @param picc Target PICC + @param force_rats Force Request_rats (For Plus SL1/2) @return True if successful @post PICC transitions: IDLE/HALT -> READY -> ACTIVE on a successful sequence */ - bool reactivate(const m5::nfc::a::PICC& picc); + bool reactivate(const m5::nfc::a::PICC& picc, const bool force_rats = false); /*! @brief Reactivate the previously selected PICC @details This function attempts to recover communication with the currently stored @@ -253,6 +255,7 @@ public: @pre All blocks must be authenticatable using the specified key if MIFARE classic */ bool dump(const m5::nfc::a::mifare::classic::Key& mkey = m5::nfc::a::mifare::classic::DEFAULT_KEY); + bool dump(const m5::nfc::a::mifare::plus::AESKey& key); /*! @brief Dump 1 block @param addr Block address @@ -427,7 +430,9 @@ public: @brief Upgrade security level to SL1 (Classic compatibility mode) @param card_config_key Card Configuration Key (AES) @param card_master_key Card Master Key (AES) - @param l3_switch_key Level 3 Switch Key (AES) + @param l2_switch_key Level 2 Switch Key (AES, for MIFARE Plus X/EV2 SL2 only) + @param l3_switch_key Level 3 Switch Key (AES, for SL3) + @param aes_sector_key AES sector key (for SL2/SL3, written to 0x4000+) @param key_a Crypto1 Key A (applies to all sectors) @param key_b Crypto1 Key B (applies to all sectors) @warning This operation is irreversible @@ -436,9 +441,19 @@ public: bool mifarePlusUpgradeSecurityLevel1( const m5::nfc::a::mifare::plus::AESKey& card_config_key = m5::nfc::a::mifare::plus::DEFAULT_KEY, const m5::nfc::a::mifare::plus::AESKey& card_master_key = m5::nfc::a::mifare::plus::DEFAULT_KEY, + const m5::nfc::a::mifare::plus::AESKey& l2_switch_key = m5::nfc::a::mifare::plus::DEFAULT_KEY, const m5::nfc::a::mifare::plus::AESKey& l3_switch_key = m5::nfc::a::mifare::plus::DEFAULT_KEY, + const m5::nfc::a::mifare::plus::AESKey& aes_sector_key = m5::nfc::a::mifare::plus::DEFAULT_FF_KEY, const m5::nfc::a::mifare::classic::Key& key_a = m5::nfc::a::mifare::classic::DEFAULT_KEY, const m5::nfc::a::mifare::classic::Key& key_b = m5::nfc::a::mifare::classic::DEFAULT_KEY); + /*! + @brief Upgrade security level to SL2 (AES over CRYPTO1) + @param sl2_switch_key SL2 Switch Key (AES) + @warning This operation is irreversible + @note Only supported on MIFARE Plus X / EV2 + */ + bool mifarePlusUpgradeSecurityLevel2( + const m5::nfc::a::mifare::plus::AESKey& sl2_switch_key = m5::nfc::a::mifare::plus::DEFAULT_KEY); ///@} @@ -540,7 +555,14 @@ protected: bool mifare_get_version_L3(uint8_t ver[8]); bool mifare_get_version_L4_raw(uint8_t* ver, uint16_t& ver_len); bool mifare_get_version_L4_wrapped(uint8_t* ver, uint16_t& ver_len); + + bool mifare_plus_authenticateAES(const uint16_t key_no, const m5::nfc::a::mifare::plus::AESKey& key); + bool mifare_plus_authenticateAES_L3(const uint16_t key_no, const m5::nfc::a::mifare::plus::AESKey& key); + bool mifare_plus_read_plain_nomac(const uint16_t block, const uint8_t count, std::vector& out); + bool mifare_plus_read_plain_mac(const uint16_t block, const uint8_t count, std::vector& out); + bool mifare_classic_value_block(const m5::nfc::a::Command cmd, const uint8_t block, const uint32_t arg = 0); + bool mifare_ultralightC_authenticate1(uint8_t ek[8]); bool mifare_ultralightC_authenticate2(uint8_t rx_ek[8], const uint8_t tx_ek[16]); @@ -551,6 +573,7 @@ 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_mifare_plus_sl2(const m5::nfc::a::mifare::plus::AESKey& key); bool dump_desfire(); bool dump_desfire_light(); @@ -562,6 +585,19 @@ protected: m5::nfc::isodep::IsoDEP _isoDEP; private: + bool mifare_plus_transceive_raw(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len); + + struct MifarePlusSession { + bool authenticated{}; + uint16_t key_no{}; + uint16_t r_ctr{}; + uint16_t w_ctr{}; + uint8_t frame_num{}; + std::array ti{}; + std::array kmac{}; + }; + + MifarePlusSession _mfp_session{}; std::unique_ptr _impl; };