mirror of
https://github.com/m5stack/M5Unit-NFC.git
synced 2026-05-20 11:48:34 -07:00
Add SL2 for MIFARE Plus (WIP)
This commit is contained in:
@@ -192,8 +192,10 @@ namespace plus {
|
||||
*/
|
||||
using AESKey = std::array<uint8_t, 16>;
|
||||
//! @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
|
||||
|
||||
+22
-2
@@ -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
|
||||
{
|
||||
|
||||
+20
-20
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <M5Utility.hpp>
|
||||
#include <algorithm>
|
||||
#include <mbedtls/aes.h>
|
||||
#include <esp_random.h>
|
||||
|
||||
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<uint16_t>(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<uint8_t>& 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<size_t>(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<uint8_t>(3U, blocks - i);
|
||||
std::vector<uint8_t> 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()) {
|
||||
|
||||
@@ -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<uint8_t>& out);
|
||||
bool mifare_plus_read_plain_mac(const uint16_t block, const uint8_t count, std::vector<uint8_t>& 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<uint8_t, 4> ti{};
|
||||
std::array<uint8_t, 16> kmac{};
|
||||
};
|
||||
|
||||
MifarePlusSession _mfp_session{};
|
||||
std::unique_ptr<Adapter> _impl;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user