From 0fc8b985e88309fce280b892b963dcb936b33b6b Mon Sep 17 00:00:00 2001 From: GOB Date: Wed, 10 Dec 2025 12:48:17 +0900 Subject: [PATCH] Fixes for ULC, and access with MAC for NFC-F --- src/M5UnitUnifiedNFC.hpp | 4 + src/nfc/a/nfca.cpp | 2 +- src/nfc/a/nfca.hpp | 2 +- src/nfc/f/nfcf.cpp | 95 ++++++ src/nfc/f/nfcf.hpp | 125 +++++-- src/nfc/layer/ndef_layer.cpp | 4 +- src/nfc/layer/nfc_layer_a.cpp | 98 +++++- src/nfc/layer/nfc_layer_a.hpp | 17 +- src/nfc/layer/nfc_layer_f.cpp | 338 ++++++++++++++++++- src/nfc/layer/nfc_layer_f.hpp | 56 ++- src/nfc/layer/unit/nfc_layer_a_ST25R3916.cpp | 12 + src/nfc/layer/unit/nfc_layer_f_ST25R3916.cpp | 4 +- src/unit/ST25R3916_definition.hpp | 7 + src/unit/unit_ST25R3916.cpp | 147 ++++---- src/unit/unit_ST25R3916.hpp | 52 ++- src/unit/unit_ST25R3916_nfca.cpp | 114 +++++-- src/unit/unit_ST25R3916_nfcf.cpp | 6 +- 17 files changed, 897 insertions(+), 186 deletions(-) diff --git a/src/M5UnitUnifiedNFC.hpp b/src/M5UnitUnifiedNFC.hpp index 38b2c00..a97a406 100644 --- a/src/M5UnitUnifiedNFC.hpp +++ b/src/M5UnitUnifiedNFC.hpp @@ -34,6 +34,10 @@ namespace m5 { @brief Unit-related namespace */ namespace unit { + +using UnitNFC = m5::unit::UnitST25R3916; +using HackerCapNFC = m5::unit::CapST25R3916; + } // namespace unit } // namespace m5 #endif diff --git a/src/nfc/a/nfca.cpp b/src/nfc/a/nfca.cpp index a5b3fbe..ee11c8e 100644 --- a/src/nfc/a/nfca.cpp +++ b/src/nfc/a/nfca.cpp @@ -116,7 +116,7 @@ constexpr uint16_t user_area_size_table[] = { constexpr NFCForumTag nfc_forum_tag_table[] = { NFCForumTag::None, // NFCForumTag::None, NFCForumTag::None, NFCForumTag::None, NFCForumTag::None, // Classic - NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, // UltraLight + NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, // Light NFCForumTag::None, NFCForumTag::None, // Plus NFCForumTag::Type4, NFCForumTag::Type4, NFCForumTag::Type4, // DESFire NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, // NTAG diff --git a/src/nfc/a/nfca.hpp b/src/nfc/a/nfca.hpp index 96e6f53..cee48e1 100644 --- a/src/nfc/a/nfca.hpp +++ b/src/nfc/a/nfca.hpp @@ -281,7 +281,7 @@ enum class Command : uint8_t { PERSONALIZE_UID_USAGE = 0x40, //!< MIFARE Classic Personalize UID Usage SET_MOD_TYPE = 0x43, //!< MIFARE Classic SET_MOD_TYPE // NTAG - GET_VERSION = 0x60, //!< NTAG 21x. Gets the version information + GET_VERSION = 0x60, //!< NTAG 21x/UL EV1,Nano Gets the version information FAST_READ = 0x3A, //!< NTAG 21x. excluding 210u. Read multiple pages READ_CNT = 0x39, //!< NTAG 213/5/6. Read counter value PWD_AUTH = 0x1B, //!< NTAG 21x excluding 210u. Authentication for protected area diff --git a/src/nfc/f/nfcf.cpp b/src/nfc/f/nfcf.cpp index fa3be19..d0ce23f 100644 --- a/src/nfc/f/nfcf.cpp +++ b/src/nfc/f/nfcf.cpp @@ -86,6 +86,101 @@ uint8_t get_maxumum_write_blocks(const Type t) return max_write_block_table[idx < m5::stl::size(max_block_table) ? idx : 0]; } +bool make_session_key(uint8_t sk[16], const uint8_t ck[16], const uint8_t rc[16]) +{ + using m5::utility::crypto::TripleDES; + using Key16 = TripleDES::Key16; + + // 1) (CK[7..0] reversed + CK[15..8] reversed) + Key16 key{}; + for (int i = 0; i < 8; ++i) { + key[i] = ck[7 - i]; // CK1 reversed + key[8 + i] = ck[15 - i]; // CK2 reversed + } + + // 2) (RC[7..0] reversed + RC[15..8] reversed) + uint8_t data[16]{}; + for (int i = 0; i < 8; ++i) { + data[i] = rc[7 - i]; // RC1 reversed + data[8 + i] = rc[15 - i]; // RC2 reversed + } + + // 3) 2-key 3DES CBC(IV=0) + TripleDES des(TripleDES::Mode::CBC, TripleDES::Padding::None); + + uint8_t tmp[16]{}; + if (des.encrypt(tmp, data, sizeof(data), key) != 16) { + return false; + } + + // 4) expand + // SK1 = reverse(tmp[0..7]) + // SK2 = reverse(tmp[8..15]) + for (int i = 0; i < 8; ++i) { + sk[i] = tmp[7 - i]; // SK1 + sk[8 + i] = tmp[15 - i]; // SK2 + } + + return true; +} + +bool generate_mac(uint8_t mac[8], const uint8_t* plain, uint32_t plain_len, const uint8_t* block_data, + uint32_t block_len, const uint8_t sk1[8], const uint8_t sk2[8], const uint8_t rc[16]) +{ + using m5::utility::crypto::TripleDES; + + if (!mac || !block_data || !block_len || !sk1 || !sk2 || !rc) { + return false; + } + + // key1[::-1] + key2[::-1] + TripleDES::Key16 key{}; + for (int i = 0; i < 8; ++i) { + key[i] = sk1[7 - i]; + key[8 + i] = sk2[7 - i]; + } + + // IV = RC1[7..0] (first 8byte in RC) + uint8_t iv[8]{}; + for (int i = 0; i < 8; ++i) { + iv[i] = rc[7 - i]; + } + + TripleDES des(TripleDES::Mode::CBC, TripleDES::Padding::None, iv); + + // plain[::-1] + concat(each 8byte chunk reversed) + std::vector buf; + buf.reserve(((plain_len + block_len) + 7) & ~7u); + + // plain[::-1] + if (plain && plain_len) { + for (uint32_t i = 0; i < plain_len; ++i) { + buf.push_back(plain[plain_len - 1 - i]); + } + } + + // block_data[i:i+8][::-1] + for (uint32_t off = 0; off < block_len; off += 8) { + uint32_t chunk = std::min(8, block_len - off); + uint8_t tmp[8]{}; + std::memcpy(tmp, block_data + off, chunk); + for (uint32_t i = 0; i < 8; ++i) { + buf.push_back(tmp[7 - i]); + } + } + + std::vector out(buf.size()); + auto len = des.encrypt(out.data(), buf.data(), static_cast(buf.size()), key); + if (len != buf.size()) { + return false; + } + + for (int i = 0; i < 8; ++i) { + mac[i] = out[out.size() - 1 - i]; + } + return true; +} + // std::string PICC::idmAsString() const { diff --git a/src/nfc/f/nfcf.hpp b/src/nfc/f/nfcf.hpp index 6832725..c45a7e7 100644 --- a/src/nfc/f/nfcf.hpp +++ b/src/nfc/f/nfcf.hpp @@ -45,18 +45,6 @@ inline m5::nfc::NFCForumTag get_nfc_forum_tag_type(const Type t) return (t != Type::Unknown) ? NFCForumTag::Type3 : NFCForumTag::None; } -/*! - @enum Mode - @brief NFC-F Mode status - */ -enum class Mode : uint8_t { - Mode0, //!< Power was supplied to the PICC - Mode1, //!< Certification for PICC has been completed (Auth1) - Mode2, //!< After mutual authentication is complete (Auth2) - Mode3, //!< After registering area services or executing system partitioning - -}; - ///@name Format bits ///@{ using Format = uint8_t; @@ -224,12 +212,33 @@ struct block_t { } }; +/*! + @namespacce lite + @brief For FeliCa Standard + */ +namespace standard { +/*! + @enum Mode + @brief Mode for Standard + */ +enum class Mode : uint8_t { + Mode0, //!< Power was supplied to the PICC + Mode1, //!< Certification for PICC has been completed (Auth1) + Mode2, //!< After mutual authentication is complete (Auth2) + Mode3, //!< After registering area services or executing system partitioning + +}; +} // namespace standard + /*! @namespacce lite @brief For FeliCa Lite */ namespace lite { ///@name + +///@name Block +///@{ constexpr block_t S_PAD0{0x00}; constexpr block_t S_PAD1{0x01}; constexpr block_t S_PAD2{0x02}; @@ -254,6 +263,7 @@ constexpr block_t SYS_C{0x85}; constexpr block_t CKV{0x86}; constexpr block_t CK{0x87}; constexpr block_t MC{0x88}; +///@} } // namespace lite @@ -262,34 +272,49 @@ constexpr block_t MC{0x88}; @brief For FeliCa Lite-S */ namespace lite_s { -constexpr block_t S_PAD0{0x00}; -constexpr block_t S_PAD1{0x01}; -constexpr block_t S_PAD2{0x02}; -constexpr block_t S_PAD3{0x03}; -constexpr block_t S_PAD4{0x04}; -constexpr block_t S_PAD5{0x05}; -constexpr block_t S_PAD6{0x06}; -constexpr block_t S_PAD7{0x07}; -constexpr block_t S_PAD8{0x08}; -constexpr block_t S_PAD9{0x09}; -constexpr block_t S_PAD10{0x0A}; -constexpr block_t S_PAD11{0x0B}; -constexpr block_t S_PAD12{0X0C}; -constexpr block_t S_PAD13{0x0D}; -constexpr block_t REG{0x0E}; -constexpr block_t RC{0x80}; -constexpr block_t MAC{0x81}; -constexpr block_t ID{0x082}; -constexpr block_t D_ID{0x83}; -constexpr block_t SER_C{0x84}; -constexpr block_t SYS_C{0x85}; -constexpr block_t CKV{0x86}; -constexpr block_t CK{0x87}; -constexpr block_t MC{0x88}; + +/*! + @enum Mode + @brief Mode for LiteS + */ +enum class Mode : uint8_t { + Mode00, //!< External authentication incomplete, polling response possible + Mode01, //!< External authentication incomplete, polling response not possible + Mode10, //!< External authentication complete, polling response possibl + Mode11, //!< External authentication complete, polling response not possible +}; + +///@name Block +///@{ +constexpr block_t S_PAD0{0x00}; // Same as Lite +constexpr block_t S_PAD1{0x01}; // Same as Lite +constexpr block_t S_PAD2{0x02}; // Same as Lite +constexpr block_t S_PAD3{0x03}; // Same as Lite +constexpr block_t S_PAD4{0x04}; // Same as Lite +constexpr block_t S_PAD5{0x05}; // Same as Lite +constexpr block_t S_PAD6{0x06}; // Same as Lite +constexpr block_t S_PAD7{0x07}; // Same as Lite +constexpr block_t S_PAD8{0x08}; // Same as Lite +constexpr block_t S_PAD9{0x09}; // Same as Lite +constexpr block_t S_PAD10{0x0A}; // Same as Lite +constexpr block_t S_PAD11{0x0B}; // Same as Lite +constexpr block_t S_PAD12{0X0C}; // Same as Lite +constexpr block_t S_PAD13{0x0D}; // Same as Lite +constexpr block_t REG{0x0E}; // Same as Lite +constexpr block_t RC{0x80}; // Same as Lite +constexpr block_t MAC{0x81}; // Same as Lite +constexpr block_t ID{0x082}; // Same as Lite +constexpr block_t D_ID{0x83}; // Same as Lite +constexpr block_t SER_C{0x84}; // Same as Lite +constexpr block_t SYS_C{0x85}; // Same as Lite +constexpr block_t CKV{0x86}; // Same as Lite +constexpr block_t CK{0x87}; // Same as Lite +constexpr block_t MC{0x88}; // Same as Lite constexpr block_t WCNT{0x90}; constexpr block_t MAC_A{0x91}; constexpr block_t STATE{0x92}; constexpr block_t CRC_CHECK{0xA0}; +///@} } // namespace lite_s @@ -499,6 +524,34 @@ inline bool can_write_reg(const REG& o, const REG& n) return (o.regA() >= n.regA()) && (o.regB() >= n.regB()); } +///@name For MAC +///@{ +/*! + @brief Make session key + @param[out] sk Session key (sk1 8byte + sk2 8byte) + @param ck Card key + @param rc Random challenge + @return True if successful + */ +bool make_session_key(uint8_t sk[16], const uint8_t ck[16], const uint8_t rc[16]); + +/*! + @brief Generate MAC + @param[out] mac MAC + @param plain Plain blocks (If nullptr, do not use) + @param plain_num Number of plain (If zero, do not use) + @param block_data Block data + @param block_len Length of block_data + @param sk1 Session key 1 + @param sk2 Session key 2 + @param rc Random challenge + @return True if successful +*/ +bool generate_mac(uint8_t mac[8], const uint8_t* plain, uint32_t plain_len, const uint8_t* block_data, + uint32_t block_len, const uint8_t sk1[8], const uint8_t sk2[8], const uint8_t rc[16]); + +///@} + } // namespace f } // namespace nfc } // namespace m5 diff --git a/src/nfc/layer/ndef_layer.cpp b/src/nfc/layer/ndef_layer.cpp index 173e9a8..b7aeaec 100644 --- a/src/nfc/layer/ndef_layer.cpp +++ b/src/nfc/layer/ndef_layer.cpp @@ -111,8 +111,7 @@ bool NDEFLayer::read_with_tlv(std::vector& tlvs, const m5::n } uint16_t buf_size = (last_block - block + 1) * _interface.userBlockUnitSize(); - ; - buf = static_cast(malloc(buf_size)); + buf = static_cast(malloc(buf_size)); if (!buf) { M5_LIB_LOGE("Failed to allocate memory %u", buf_size); return false; @@ -460,6 +459,7 @@ bool calculate_ndef_size(uint32_t& size, const uint8_t* p, const uint8_t* end, c size = required; return true; } + #endif } // namespace ndef diff --git a/src/nfc/layer/nfc_layer_a.cpp b/src/nfc/layer/nfc_layer_a.cpp index c4de260..5e679a4 100644 --- a/src/nfc/layer/nfc_layer_a.cpp +++ b/src/nfc/layer/nfc_layer_a.cpp @@ -13,6 +13,7 @@ #include #include #include +#include using namespace m5::nfc::a; using namespace m5::nfc::a::mifare; @@ -61,6 +62,15 @@ void dump_block(const uint8_t buf[16], const int16_t block = -1, const int16_t s } ::puts(tmp); } + +void rotate_byte_left(uint8_t out[8], const uint8_t in[8]) +{ + for (int i = 0; i < 7; ++i) { + out[i] = in[i + 1]; + } + out[7] = in[0]; +} + } // namespace namespace m5 { @@ -97,16 +107,18 @@ bool NFCLayerA::detect(std::vector& piccs, const uint32_t timeout_ms) uint16_t atqa{}; do { // Exists PICC? + M5_LIB_LOGI(">>>"); if (!request(atqa)) { break; } + M5_LIB_LOGI("<<<"); // M5_LIB_LOGE("==> ATQA:%04X", atqa); // Select if (!select(picc)) { return false; } - M5_LIB_LOGV("Detect:%s %s", picc.uidAsString().c_str(), picc.typeAsString().c_str()); + M5_LIB_LOGE("Detect:%s %s", picc.uidAsString().c_str(), picc.typeAsString().c_str()); // Hlt if (!deactivate()) { @@ -147,7 +159,11 @@ bool NFCLayerA::reactivate(const PICC& picc) // If arg referrence is the same as _activePICC, it will cause an error, so it must be a separate instance (*1) PICC tmp = picc; uint16_t discard{}; - return tmp.valid() && deactivate() && wakeup(discard) && activate(tmp); + if (tmp.valid() && deactivate()) { + m5::utility::delay(2); + return wakeup(discard) && activate(tmp); + } + return false; } bool NFCLayerA::deactivate() @@ -650,6 +666,84 @@ bool NFCLayerA::mifareUltralightChangeFormatToNTAG() return true; } +bool NFCLayerA::mifareUltralightCAuthenticate(const uint8_t key[16]) +{ + using m5::utility::crypto::TripleDES; + + TripleDES::Key16 key16{}; + memcpy(key16.data(), key, 16); + + // Auth step 1. Receive ek(RndB) + uint8_t ek_rndB[8]{}; + if (!_impl->mifare_ultralightC_authenticate1(ek_rndB)) { + M5_LIB_LOGE("Failed to auth1"); + return false; + } + + // Decrypt ek + uint8_t iv[8]{}; + uint8_t rndB[8]{}; + { + TripleDES des{TripleDES::Mode::CBC, TripleDES::Padding::None, iv}; + if (!des.decrypt(rndB, ek_rndB, sizeof(ek_rndB), key16)) { + M5_LIB_LOGE("Failed to decrypt"); + return false; + } + } + + // Make rndA + uint8_t rndA[8]{}; + for (auto& r : rndA) { + r = esp_random(); + } + + // Make RndB',RandA' + uint8_t rndB_rot[8]{}; + uint8_t rndA_rot[8]{}; + rotate_byte_left(rndB_rot, rndB); + rotate_byte_left(rndA_rot, rndA); + + // Make plain + uint8_t plain_AB[16]{}; + memcpy(plain_AB, rndA, 8); + memcpy(plain_AB + 8, rndB_rot, 8); + + // Make ek(RndA || RndB') + uint8_t ek_AB[16]{}; + { + TripleDES des{TripleDES::Mode::CBC, TripleDES::Padding::None, ek_rndB}; + if (!des.encrypt(ek_AB, plain_AB, sizeof(plain_AB), key16)) { + M5_LIB_LOGE("Failed to encrypt"); + return false; + } + } + + // Auth step 2. Send [AF || ek(RndA||RndB')], Receive [RndA'] + uint8_t ek_rndA_rot_from_card[8]{}; + if (!_impl->mifare_ultralightC_authenticate2(ek_rndA_rot_from_card, ek_AB)) { + M5_LIB_LOGE("Failed to auth2"); + return false; + } + + // Decrypt RndA' + uint8_t rndA_rot_from_card[8]{}; + { + TripleDES des{TripleDES::Mode::CBC, TripleDES::Padding::None, ek_AB + 8}; + if (!des.decrypt(rndA_rot_from_card, ek_rndA_rot_from_card, sizeof(ek_rndA_rot_from_card), key16)) { + return false; + } + } + + // Compare + if (memcmp(rndA_rot, rndA_rot_from_card, 8) != 0) { + M5_LIB_LOGE("Not match"); + m5::utility::log::dump(rndA_rot, 8, false); + m5::utility::log::dump(rndA_rot_from_card, 8, false); + return false; + } + return true; +} + bool NFCLayerA::ndefIsValidFormat(bool& valid) { valid = false; diff --git a/src/nfc/layer/nfc_layer_a.hpp b/src/nfc/layer/nfc_layer_a.hpp index 2ab2119..c1983da 100644 --- a/src/nfc/layer/nfc_layer_a.hpp +++ b/src/nfc/layer/nfc_layer_a.hpp @@ -183,7 +183,7 @@ public: @param tx_len Buffer size @param safety Fail to write to out of the user memory area if true (safety measure) @return True if successful - @warning Supports NTAG and UltraLight only + @warning Supports NTAG and Ultralight series only @warning If the tx_len is less than 4 bytes, the remaining space is filled with 0x00 @warning If the tx_len is larger than 4 bytes, only the first 4 bytes will be written */ @@ -207,7 +207,7 @@ public: @param tx Buffer @param tx_len buffer size @return True if successful - @warning For NTAG and UltraLight, the tx is in 4-byte units; for others, it is in 16-byte units + @warning For NTAG and Ultralight series, the tx is in 4-byte units; for others, it is in 16-byte units @warning If the value is less than the unit, it is padded with 0x00 @pre Target blocks must be authenticatable using the specified key if MIFARE classic */ @@ -368,11 +368,16 @@ public: @brief Write change to NFC Type-2 (NDEF) format for MIFARE Ultralight/C @return True if successful @note Returns true if the data is already in NDEF format or if the PICC is an NTAG - @warning Only MIFARE Ultralight,UltralightC + @warning Only MIFARE Ultralight series @warning Changes are irreversible and cannot be undone @warning If the relevant area has already been overwritten, changes may not be possible */ bool mifareUltralightChangeFormatToNTAG(); + + /*! + @brief Authentication for MIFARE UltralightC + */ + bool mifareUltralightCAuthenticate(const uint8_t key[16]); ///@} ///@note For activated PICC @@ -478,9 +483,11 @@ struct NFCLayerA::Adapter { virtual bool nfca_write_page(const uint8_t addr, const uint8_t tx[4]) = 0; // WRITE_PAGE virtual bool mifare_classic_authenticate(const bool auth_a, const m5::nfc::a::PICC& picc, const uint8_t block, - const m5::nfc::a::mifare::classic::Key& key) = 0; + const m5::nfc::a::mifare::classic::Key& key) = 0; virtual bool mifare_classic_value_block(const m5::nfc::a::Command cmd, const uint8_t block, - const uint32_t arg = 0) = 0; + const uint32_t arg = 0) = 0; + virtual bool mifare_ultralightC_authenticate1(uint8_t ek[8]) = 0; + virtual bool mifare_ultralightC_authenticate2(uint8_t rx_ek[8], const uint8_t tx_ek[16]) = 0; virtual bool ntag_read_page(uint8_t* rx, uint16_t& rx_len, const uint8_t spage, const uint8_t epage) = 0; // FAST_READ diff --git a/src/nfc/layer/nfc_layer_f.cpp b/src/nfc/layer/nfc_layer_f.cpp index 011e4e2..e05c224 100644 --- a/src/nfc/layer/nfc_layer_f.cpp +++ b/src/nfc/layer/nfc_layer_f.cpp @@ -13,6 +13,7 @@ #include #include #include +#include using namespace m5::nfc::f; using namespace m5::nfc::ndef; @@ -52,6 +53,14 @@ inline bool is_same_idm_and_pmm(const PICC& a, const PICC& b) return a.idm == b.idm && a.pmm == b.pmm; } +const uint8_t* make_rc(uint8_t rc[16]) +{ + for (uint_fast8_t i = 0; i < 16; ++i) { + rc[i] = esp_random(); + } + return rc; +} + } // namespace namespace m5 { @@ -81,6 +90,7 @@ bool NFCLayerF::detect(std::vector& piccs, const uint16_t* pri { uint8_t slots = timeslot_to_slot(time_slot); + _authenticated = false; piccs.clear(); piccs.reserve(slots); @@ -101,6 +111,8 @@ bool NFCLayerF::detect(std::vector& piccs, const uint16_t* pri continue; } + M5_LIB_LOGV("detect %s", picc1.idmAsString().c_str()); + _activePICC = picc1; // 2. Check IDm for NFCIP-1 Transport Protocol / DFC @@ -196,7 +208,7 @@ bool NFCLayerF::detect(std::vector& piccs, const uint16_t* pri // Re-check if (type == Type::FeliCaStandard) { - Mode mode{}; + standard::Mode mode{}; if (!_impl->requestResponse(mode, _activePICC)) { continue; } @@ -220,7 +232,8 @@ bool NFCLayerF::detect(std::vector& piccs, const uint16_t* pri bool NFCLayerF::activate(const m5::nfc::f::PICC& picc) { if (picc.valid()) { - _activePICC = picc; + _activePICC = picc; + _authenticated = false; return true; } return false; @@ -228,7 +241,8 @@ bool NFCLayerF::activate(const m5::nfc::f::PICC& picc) bool NFCLayerF::deactivate() { - _activePICC = PICC{}; + _activePICC = PICC{}; + _authenticated = false; return true; } @@ -243,7 +257,7 @@ bool NFCLayerF::requestService(uint16_t key_version[], const uint16_t* node_code return _activePICC.valid() && _impl->requestService(key_version, _activePICC, node_code, node_size); } -bool NFCLayerF::requestResponse(m5::nfc::f::Mode& mode) +bool NFCLayerF::requestResponse(m5::nfc::f::standard::Mode& mode) { return _activePICC.valid() && _impl->requestResponse(mode, _activePICC); } @@ -253,14 +267,6 @@ bool NFCLayerF::requestSystemCode(uint16_t code_list[255], uint8_t& code_num) return _activePICC.valid() && _impl->requestSystemCode(code_list, code_num, _activePICC); } -bool NFCLayerF::read_16(uint8_t rx[16], const block_t block, const bool check_valid) -{ - uint16_t rx_len{16}; - uint16_t sc{service_random_read}; - return rx && (check_valid ? _activePICC.valid() : true) && - _impl->readWithoutEncryption(rx, rx_len, _activePICC, &sc, 1, &block, 1) && rx_len == 16; -} - bool NFCLayerF::read16(uint8_t rx[16], const m5::nfc::f::block_t block, const uint16_t service_code) { return read16(rx, &block, 1, &service_code, 1); @@ -275,6 +281,17 @@ bool NFCLayerF::read16(uint8_t rx[16], const m5::nfc::f::block_t* block, const u rx_len == 16; } +bool NFCLayerF::read(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::block_t* block, const uint8_t block_num) +{ + if (!_activePICC.valid() || !rx || !rx_len || !block || !block_num || block_num > _activePICC.maximumReadBlocks()) { + return false; + } + + uint16_t sc{service_random_read}; + return _impl->readWithoutEncryption(rx, rx_len, _activePICC, &sc, 1, block, block_num) || + (rx_len != 16 * block_num); +} + bool NFCLayerF::read(uint8_t* rx, uint16_t& rx_len, const block_t sblock) { auto rx_len_org = rx_len; @@ -317,6 +334,67 @@ bool NFCLayerF::read(uint8_t* rx, uint16_t& rx_len, const block_t sblock) return true; } +bool NFCLayerF::readWithMAC16(uint8_t rx[16], const m5::nfc::f::block_t block) +{ + if (!_authenticated) { + M5_LIB_LOGW("NOT authenticated"); + return false; + } + if (!_activePICC.valid() || (_activePICC.type != Type::FeliCaLite && _activePICC.type != Type::FeliCaLiteS)) { + return false; + } + + const bool liteS = (_activePICC.type == Type::FeliCaLiteS); + block_t mac_block = liteS ? lite_s::MAC_A : lite::MAC; + block_t block_list[] = {block, mac_block}; + + uint8_t rbuf[16 * 2]{}; + uint16_t rx_len = sizeof(rbuf); + + if (!read(rbuf, rx_len, block_list, 2) || rx_len < sizeof(rbuf)) { + M5_LIB_LOGE("Failed to read"); + return false; + } + + const uint8_t* data_block = rbuf; + const uint8_t* mac_card = rbuf + 16; + const uint8_t* sk1 = _sk; + const uint8_t* sk2 = _sk + 8; + const uint8_t* rc = _rc; + uint8_t mac_host[8]{}; + + if (liteS) { + uint8_t plain[8] = { + static_cast(block.block()), + 0x00, + static_cast(mac_block.block()), + 0x00, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + }; + if (!generate_mac(mac_host, plain, sizeof(plain), data_block, 16, sk1, sk2, rc)) { + return false; + } + } else { + if (!generate_mac(mac_host, nullptr, 0, data_block, 16, sk1, sk2, rc)) { + return false; + } + } + + if (std::memcmp(mac_host, mac_card, 8) != 0) { + M5_LIB_LOGE("MAC mismatch"); + // M5_LIB_LOGE("Not match %u", liteS); + m5::utility::log::dump(mac_host, 8, false); + m5::utility::log::dump(mac_card, 8, false); + return false; + } + + std::memcpy(rx, data_block, 16); + return true; +} + bool NFCLayerF::write16(const m5::nfc::f::block_t block, const uint8_t tx[16], const uint16_t tx_len) { if (_activePICC.valid() && tx && tx_len) { @@ -349,6 +427,85 @@ bool NFCLayerF::write(const m5::nfc::f::block_t sblock, const uint8_t* tx, const return true; } +bool NFCLayerF::writeWithMAC16(const m5::nfc::f::block_t block, const uint8_t tx[16], const uint16_t tx_len) +{ + if (!_authenticated) { + M5_LIB_LOGW("NOT authenticated"); + return false; + } + if (!_activePICC.valid() || (_activePICC.type != Type::FeliCaLite && _activePICC.type != Type::FeliCaLiteS)) { + return false; + } + + uint8_t tx2[16]{}; + memcpy(tx2, tx, std::min(tx_len, sizeof(tx2))); + + const bool liteS = (_activePICC.type == Type::FeliCaLiteS); + block_t mac_block = liteS ? lite_s::MAC_A : lite::MAC; + const uint8_t* sk1 = _sk; + const uint8_t* sk2 = _sk + 8; + const uint8_t* rc = _rc; + uint8_t mac_host[8]{}; + uint8_t wcnt[4]{}; + if (liteS) { + // Read WCNT + uint8_t wcnt_block[16]{}; + if (!read16(wcnt_block, lite_s::WCNT)) { + return false; + } + std::memcpy(wcnt, wcnt_block, 4); // Using first 4 bytes (for Link Lite-S mode) + + uint8_t plain[8] = { + wcnt[0], + wcnt[1], + wcnt[2], + wcnt[3], // Always 0 if Lite/Lite-S + static_cast(block.block()), + 0x00, + static_cast(mac_block.block()), + 0x00, + }; + if (!generate_mac(mac_host, plain, sizeof(plain), tx2, 16, sk2, sk1, rc)) { + return false; + } + } else { + if (!generate_mac(mac_host, nullptr /* plain*/, 0 /*plain num */, tx2, 16, sk1, sk2, rc)) { + return false; + } + } + + uint8_t wbuf[32]{}; + std::memcpy(wbuf, tx2, 16); + std::memcpy(wbuf + 16, mac_host, 8); + std::memcpy(wbuf + 24, wcnt, 4); + block_t block_list[2] = {block, mac_block}; + + m5::utility::log::dump(wbuf, 32); + return false; + + return write_32(block_list, wbuf); +} + +bool NFCLayerF::internalAuthenticate(const uint8_t ck[16], const uint16_t ckv, const uint8_t rc[16]) +{ + if (!_activePICC.valid() || (_activePICC.type != Type::FeliCaLiteS)) { + return false; + } + return internal_authenticate_lite_s(ck, ckv, rc); +} + +bool NFCLayerF::externalAuthenticate(const uint8_t ck[16], const uint16_t ckv) +{ + if (!_activePICC.valid() || _activePICC.type != Type::FeliCaLiteS) { + return false; + } + if (!_authenticated) { + M5_LIB_LOGW("NOT authenticated"); + return false; + } + return external_authenticate_lite_s(ck, ckv); +} + bool NFCLayerF::ndefIsValidFormat(bool& valid) { valid = false; @@ -422,6 +579,163 @@ bool NFCLayerF::dump(const block_t block) } // +bool NFCLayerF::read_16(uint8_t rx[16], const block_t block, const bool check_valid) +{ + uint16_t rx_len{16}; + uint16_t sc{service_random_read}; + return rx && (check_valid ? _activePICC.valid() : true) && + _impl->readWithoutEncryption(rx, rx_len, _activePICC, &sc, 1, &block, 1) && rx_len == 16; +} + +bool NFCLayerF::write_32(const m5::nfc::f::block_t block[2], const uint8_t tx[32]) +{ + // 2nd block must be MAC_A + uint16_t sc{service_random_read_write}; + if (block && tx && block[1].block() == lite_s::MAC_A.block()) { + return _impl->writeWithoutEncryption(_activePICC, &sc, 1, block, 2, tx, 32); + } + return false; +} + +bool NFCLayerF::internal_authenticate_lite_s(const uint8_t ck[16], const uint16_t ckv, const uint8_t rc[16], + const bool include_wcnt) +{ + _authenticated = false; + + // Make session key + uint8_t sk[16]{}; + const uint8_t* sk1 = sk; + const uint8_t* sk2 = sk + 8; + if (!make_session_key(sk, ck, rc)) { + M5_LIB_LOGE("Failed to make_session_key"); + return false; + } + // m5::utility::log::dump(sk, 16, false); + + // Write RC + if (!write16(lite::RC, rc, 16)) { + M5_LIB_LOGE("Failed to write CK"); + return false; + } + + // Read ID,CKV, WCNT, MAC(Lite)/MAC_A(Lite-S) + block_t block_list[4] = {lite::ID, lite::CKV, // + (include_wcnt ? lite_s::WCNT : lite_s::MAC_A), // + (include_wcnt ? lite_s::MAC_A : block_t{0x00})}; // + uint8_t rbuf[16 * 4]{}; + uint16_t rx_len = include_wcnt ? (4 * 16) : (3 * 16); + auto needs = rx_len; + if (!read(rbuf, rx_len, block_list, include_wcnt ? 4 : 3) || rx_len != needs) { + M5_LIB_LOGE("Failed to read blocks %u/%u", rx_len, needs); + return false; + } + // m5::utility::log::dump(rbuf, rx_len, false); + + // Compare CKV + const uint8_t* ckv_block = rbuf + 16; // 2nd block + const uint16_t ckv_card = (static_cast(ckv_block[0]) << 8) | static_cast(ckv_block[1]); + if (ckv_card != ckv) { + M5_LIB_LOGE("CKV mismatch %04X,%04X", ckv_card, ckv); + return false; + } + + // Compare MAC + uint8_t mac_host[8]{}; + uint8_t plain[8] = {static_cast(block_list[0].block()), + 0x00, + static_cast(block_list[1].block()), + 0x00, + static_cast(block_list[2].block()), + 0x00, + static_cast(include_wcnt ? block_list[3].block() : 0xFF), + static_cast(include_wcnt ? 0x00 : 0xFF)}; + if (!generate_mac(mac_host, plain, sizeof(plain), // + rbuf, 32 /*ID + CKV*/ + (include_wcnt != 0) * 16 /*WCNT*/, // + sk1, sk2, rc)) { + M5_LIB_LOGE("Failed to generate_mac"); + return false; + } + // m5::utility::log::dump(mac, 8, false); + + const uint8_t* mac_card = rbuf + 32 + (include_wcnt != 0) * 16; // MAC_A + if (std::memcmp(mac_host, mac_card, 8) != 0) { + M5_LIB_LOGE("MAC mismatch CKV:%04X,%04X", ckv_card, ckv); + // M5_LIB_LOGE("Not match %u", liteS); + m5::utility::log::dump(mac_host, 8, false); + m5::utility::log::dump(mac_card, 8, false); + return false; + } + + memcpy(_sk, sk, sizeof(_sk)); + memcpy(_rc, rc, sizeof(_rc)); + _authenticated = true; + + return true; +} + +bool NFCLayerF::external_authenticate_lite_s(const uint8_t ck[16], const uint16_t ckv) +{ + // Write RC + uint8_t rc[16]{}; + make_rc(rc); + if (!write16(lite_s::RC, rc, sizeof(rc))) { + return false; + } + + // internal auth with WCNT + if (!internal_authenticate_lite_s(ck, ckv, rc)) { + M5_LIB_LOGE("Failed to internal_authenticate_lite_s"); + return false; + } + + uint8_t wcnt[16]{}; + if (!read16(wcnt, lite_s::WCNT)) { + M5_LIB_LOGE("Failed to internal_authenticate_lite_s"); + return false; + } + M5_LIB_LOGE("WCNT:%02X:%02X:%02X:%02X", wcnt[0], wcnt[1], wcnt[2], wcnt[3]); + + // + uint8_t state[16]{ + 0x01 /* Authed */, + }; + + uint8_t plain_w[8] = {wcnt[0], + wcnt[1], + wcnt[2], + wcnt[3], // Always 0x00 if Lite/Lite-S + static_cast(lite_s::STATE.block()), + 0x00, + static_cast(lite_s::MAC_A.block()), + 0x00}; + + const uint8_t* sk1 = _sk; + const uint8_t* sk2 = _sk + 8; + uint8_t mac_w[8]{}; + if (!generate_mac(mac_w, plain_w, sizeof(plain_w), state, sizeof(state), sk2, sk1, rc)) { + M5_LIB_LOGE("Failed to generate_mac"); + return false; + } + + uint8_t tx[32]{}; + std::memcpy(tx, state, 16); + std::memcpy(tx + 16, mac_w, 8); + std::memcpy(tx + 24, wcnt, 4); + + block_t block_list[2] = {lite_s::STATE, lite_s::MAC_A}; + if (!write_32(block_list, tx)) { + M5_LIB_LOGE("Failed to write_32"); + return false; + } + + /* + read16(state, lite_s::STATE); + M5_LIB_LOGE("=== STATE"); + m5::utility::log::dump(state, 16); + */ + + return true; +} bool NFCLayerF::dump_felica_lite() { diff --git a/src/nfc/layer/nfc_layer_f.hpp b/src/nfc/layer/nfc_layer_f.hpp index f2589a7..d51621a 100644 --- a/src/nfc/layer/nfc_layer_f.hpp +++ b/src/nfc/layer/nfc_layer_f.hpp @@ -175,7 +175,7 @@ public: @param[out] mode Mode if detected @warning FeliCa Standard only */ - bool requestResponse(m5::nfc::f::Mode& mode); + bool requestResponse(m5::nfc::f::standard::Mode& mode); /*! @brief Request system code @@ -244,6 +244,17 @@ public: */ bool read(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::block_t sblock); + /*! + @breif Read the specified block list + @param[out] rx Buffer (At least 16 * block_num) + @param[in/out] rx_len in:buffer size, out:actual read size + @param block Target block array + @param block_num Number of block + @return True if successful + @warning rx in 16-byte units + */ + bool read(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::block_t* block, const uint8_t block_num); + /*! @brief Write the 1 block @param block Target block @@ -267,11 +278,34 @@ public: ///@note For activated PICC ///@name Read/Write with MAC + ///@{ + /*! + @brief Internal authentication + @param ck Card key + @param ckv Card key version + @param rc Random challenge + @return True if successful + */ + bool internalAuthenticate(const uint8_t ck[16], const uint16_t ckv, const uint8_t rc[16]); + /*! + @brief External authentication + @param wcnt WCNT value + @return True if successful + @pre internalAuthenticate + */ + bool externalAuthenticate(const uint8_t ck[16], const uint16_t ckv); + + void clearAuthenticate() + { + _authenticated = false; + } + /*! @brief Read the 1 block @param[out] rx Output buffer @param block Target block @return True if successful + @pre internalAuthentication */ bool readWithMAC16(uint8_t rx[16], const m5::nfc::f::block_t block); @@ -292,7 +326,7 @@ public: ///@{ /*! @brief Is the PICC data in NDEF format? - @param[out] valid True if NDEF format +[ @param[out] valid True if NDEF format @return True if successful */ bool ndefIsValidFormat(bool& valid); @@ -334,18 +368,32 @@ protected: virtual uint8_t maximumReadBlocks() const override; virtual uint8_t maximumWriteBlocks() const override; - bool read_16(uint8_t rx[16], const m5::nfc::f::block_t block, const bool check_valid); + bool read_16(uint8_t rx[16], const m5::nfc::f::block_t block, const bool check_picc_valid); + bool write_32(const m5::nfc::f::block_t block[2], const uint8_t tx[32]); // For write with MAC bool dump_felica_lite(); bool dump_felica_lite_s(); bool dump_block(m5::nfc::f::block_t block); + bool internal_authenticate_lite_s(const uint8_t ck[16], const uint16_t ckv, const uint8_t rc[16], + const bool include_wcnt = false); + bool external_authenticate_lite_s(const uint8_t ck[16], const uint16_t ckv); + protected: m5::nfc::f::PICC _activePICC{}; private: std::unique_ptr _impl; m5::nfc::ndef::NDEFLayer _ndef; + union { + uint8_t _sk[16]{}; + struct { + uint8_t _sk1[8]; + uint8_t _sk2[8]; + }; + }; + uint8_t _rc[16]{}; + bool _authenticated{}; }; ///@cond @@ -358,7 +406,7 @@ struct NFCLayerF::Adapter { virtual bool requestService(uint16_t key_version[], const m5::nfc::f::PICC& picc, const uint16_t* node_code, const uint8_t node_num) = 0; - virtual bool requestResponse(m5::nfc::f::Mode& mode, const m5::nfc::f::PICC& picc) = 0; + virtual bool requestResponse(m5::nfc::f::standard::Mode& mode, const m5::nfc::f::PICC& picc) = 0; virtual bool requestSystemCode(uint16_t code_list[255], uint8_t& code_num, const m5::nfc::f::PICC& picc) = 0; virtual bool readWithoutEncryption(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc, diff --git a/src/nfc/layer/unit/nfc_layer_a_ST25R3916.cpp b/src/nfc/layer/unit/nfc_layer_a_ST25R3916.cpp index cac1285..02972db 100644 --- a/src/nfc/layer/unit/nfc_layer_a_ST25R3916.cpp +++ b/src/nfc/layer/unit/nfc_layer_a_ST25R3916.cpp @@ -47,6 +47,8 @@ struct AdapterST25R3916ForA final : NFCLayerA::Adapter { const m5::nfc::a::mifare::classic::Key& key) override; virtual bool mifare_classic_value_block(const m5::nfc::a::Command cmd, const uint8_t block, const uint32_t arg = 0) override; + virtual bool mifare_ultralightC_authenticate1(uint8_t ek[8]) override; + virtual bool mifare_ultralightC_authenticate2(uint8_t rx_ek[8], const uint8_t tx_ek[16]) override; virtual bool ntag_read_page(uint8_t* rx, uint16_t& rx_len, const uint8_t spage, const uint8_t epage) override; // FAST_READ @@ -119,6 +121,16 @@ bool AdapterST25R3916ForA::mifare_classic_value_block(const m5::nfc::a::Command return _u.mifareClassicValueBlock(cmd, block, arg); } +bool AdapterST25R3916ForA::mifare_ultralightC_authenticate1(uint8_t ek[8]) +{ + return _u.mifareUltralightCAuthenticate1(ek); +} + +bool AdapterST25R3916ForA::mifare_ultralightC_authenticate2(uint8_t rx_ek[8], const uint8_t tx_ek[16]) +{ + return _u.mifareUltralightCAuthenticate2(rx_ek, tx_ek); +} + // namespace { std::unique_ptr make_st25r3916_adapter(UnitST25R3916& u) diff --git a/src/nfc/layer/unit/nfc_layer_f_ST25R3916.cpp b/src/nfc/layer/unit/nfc_layer_f_ST25R3916.cpp index a3b9381..07d19c6 100644 --- a/src/nfc/layer/unit/nfc_layer_f_ST25R3916.cpp +++ b/src/nfc/layer/unit/nfc_layer_f_ST25R3916.cpp @@ -31,7 +31,7 @@ struct AdapterST25R3916ForF final : NFCLayerF::Adapter { virtual bool requestService(uint16_t key_version[], const m5::nfc::f::PICC& picc, const uint16_t* node_code, const uint8_t node_num) override; - virtual bool requestResponse(m5::nfc::f::Mode& mode, const m5::nfc::f::PICC& picc) override; + virtual bool requestResponse(m5::nfc::f::standard::Mode& mode, const m5::nfc::f::PICC& picc) override; virtual bool requestSystemCode(uint16_t code_list[255], uint8_t& code_num, const m5::nfc::f::PICC& picc) override; virtual bool readWithoutEncryption(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc, @@ -56,7 +56,7 @@ bool AdapterST25R3916ForF::requestService(uint16_t key_version[], const m5::nfc: return _u.nfcfRequestService(key_version, picc, node_code, node_num); } -bool AdapterST25R3916ForF::requestResponse(m5::nfc::f::Mode& mode, const m5::nfc::f::PICC& picc) +bool AdapterST25R3916ForF::requestResponse(m5::nfc::f::standard::Mode& mode, const m5::nfc::f::PICC& picc) { return _u.nfcfRequestResponse(mode, picc); } diff --git a/src/unit/ST25R3916_definition.hpp b/src/unit/ST25R3916_definition.hpp index 7ce70be..3c85196 100644 --- a/src/unit/ST25R3916_definition.hpp +++ b/src/unit/ST25R3916_definition.hpp @@ -232,6 +232,13 @@ constexpr uint8_t OP_DIRECT_COMMAND{0xC0}; // 11xxxxxxb; */ namespace regval { ///@cond +// 0x00 IO configuration register 1 +constexpr uint8_t i2c_thd1{0x20}; +constexpr uint8_t i2c_thd0{0x10}; + +constexpr uint16_t i2c_thd116{0x2000}; +constexpr uint16_t i2c_thd016{0x1000}; + // 0x01 IO configuration register 2 constexpr uint8_t sup3v{0x80}; constexpr uint8_t io_drv_lvl{0x04}; diff --git a/src/unit/unit_ST25R3916.cpp b/src/unit/unit_ST25R3916.cpp index 5e0be2e..6e012fd 100644 --- a/src/unit/unit_ST25R3916.cpp +++ b/src/unit/unit_ST25R3916.cpp @@ -66,6 +66,11 @@ constexpr uint16_t io_config12_i2c{io_drv_lvl}; constexpr uint16_t io_config12_spi{miso_pd1 | miso_pd2}; // constexpr uint16_t io_config12_spi{miso_pd1 | miso_pd2 | io_drv_lvl}; +constexpr uint16_t get_i2c_thd_bits16(const uint32_t clk) +{ + return (clk >= 1000 * 1000u) ? (i2c_thd016 | i2c_thd116) : ((clk >= 400 * 1000u) ? i2c_thd016 : 0x0000); +} + float regulated_voltages(const uint8_t regulator_display_reg_value, const bool voltage5V = false) { auto rv = (regulator_display_reg_value >> 4) & 0x0F; @@ -131,7 +136,6 @@ void IRAM_ATTR UnitST25R3916::on_irq(void* arg) bool UnitST25R3916::begin() { // Attach interrupt - M5_LIB_LOGE(">>>> IRQ:%u", _cfg.using_irq); if (_cfg.using_irq) { M5_LIB_LOGE("Using IRQ:%u", _cfg.irq); pinMode(_cfg.irq, INPUT_PULLDOWN); @@ -140,51 +144,63 @@ bool UnitST25R3916::begin() } // Chip detection - M5_LIB_LOGE(">>>> Chip detection"); uint8_t type{}, rev{}; if (!readICIdentity(type, rev) || type != VALID_IDENTIFY_TYPE || rev == 0) { M5_LIB_LOGE("Not detected ST25R3916 %02X,%02X", type, rev); return false; } - M5_LIB_LOGE("<<<< Chip detection %02X:%02X", type, rev); // Power-on sequence // 1) Set to default - M5_LIB_LOGE(">>>> Set to default"); if (!writeDirectCommand(CMD_SET_DEFAULT)) { M5_LIB_LOGE("Failed to CMD_SET_DEFAULT"); return false; } // 2) To prevent the internal overheat protection to trigger below the junction temperature - M5_LIB_LOGE(">>>> Protection"); if (!writeDirectCommand(CMD_TEST_ACCESS, protection_command, sizeof(protection_command))) { M5_LIB_LOGE("Failed to send protection command"); return false; } // 3) I/O settings - M5_LIB_LOGE(">>>> I/O"); - if (!writeIOConfiguration((adapter()->type() == Adapter::Type::I2C ? io_config12_i2c : io_config12_spi) | - (_cfg.vdd_voltage_5V ? 0x0000 : sup3v))) { + uint16_t params{}; + if (adapter()->type() == Adapter::Type::I2C) { + // I2C settings + uint16_t i2c_thd = get_i2c_thd_bits16(component_config().clock); + params = i2c_thd | io_config12_i2c | (_cfg.vdd_voltage_5V ? 0x0000 : sup3v); + } else if (adapter()->type() == Adapter::Type::SPI) { + // SPI settings + params = io_config12_spi | (_cfg.vdd_voltage_5V ? 0x0000 : sup3v); + } else { + M5_LIB_LOGE("Not support connection %u", adapter()->type()); + return false; + } + if (!writeIOConfiguration(params)) { M5_LIB_LOGE("Failed to writeIOConfiguration"); return false; } +#if 0 + { + uint8_t io1{}, io2{}; + readIOConfiguration1(io1); + readIOConfiguration2(io2); + M5_LIB_LOGE(">>>>> IO1:%02X IO2:%02X", io1, io2); + } +#endif + // 4) The internal voltage regulators have to be configuration // It is recommended to use direct command Adjust regulators to improve the system PSRR. - M5_LIB_LOGE(">>>> Mask"); if (!writeMaskInterrupts(0xFFFF00FF) && clearInterrupts()) { // Mask all interrupts exclusive error M5_LIB_LOGE("Failed to writeMaskInterrupt"); return false; } // Adjust regulators - M5_LIB_LOGE(">>>> Adjust 1"); if (!writeOperationControl(en)) { M5_LIB_LOGE("Failed to writeOperationControl"); return false; } - M5_LIB_LOGE(">>>> Adjust 2"); if (!writeDirectCommand(CMD_ADJUST_REGULATORS)) { M5_LIB_LOGE("Failed to CMD_ADJUST_REGULATORS"); return false; @@ -192,14 +208,12 @@ bool UnitST25R3916::begin() m5::utility::delay(5); // Need wait // Check vdd voltage - M5_LIB_LOGE(">>>> VDD"); uint8_t value{}; if (readRegulatorDisplay(value)) { M5_LIB_LOGD("Regulated voltages:%02X:%1.1fV", value, regulated_voltages(value, _cfg.vdd_voltage_5V)); } // Antenna Settings - M5_LIB_LOGE(">>>> Antenna"); uint8_t txd{}; if (!readTXDriver(txd) || !writeTXDriver((txd & 0x0F) | ((_cfg.tx_am_modulation & 0x0F) << 4))) { M5_LIB_LOGE("Failed to TXDriver"); @@ -209,7 +223,6 @@ bool UnitST25R3916::begin() M5_LIB_LOGD("TXD:%02X", txd); // - #if 0 // MRT/SQT if (!write_mask_receiver_timer(0) || !write_squelch_timer(0)) { @@ -222,7 +235,6 @@ bool UnitST25R3916::begin() M5_LIB_LOGE("====== MRT:%02X SQT:%02X", mrt, sqt); #endif - M5_LIB_LOGE(">>>> Config"); return configureNFCMode(_cfg.mode); } @@ -340,73 +352,6 @@ bool UnitST25R3916::nfc_initial_field_on() return ret && modify_bit_register8(REG_OPERATION_CONTROL, tx_en | rx_en, 0x00); #endif - -#if 0 -/*******************************************************************************/ -ReturnCode RfalRfST25R3916Class::st25r3916PerformCollisionAvoidance(uint8_t FieldONCmd, uint8_t pdThreshold, uint8_t caThreshold, uint8_t nTRFW) -{ - uint8_t treMask; - uint32_t irqs; - ReturnCode err; - - if ((FieldONCmd != ST25R3916_CMD_INITIAL_RF_COLLISION) && (FieldONCmd != ST25R3916_CMD_RESPONSE_RF_COLLISION_N)) { - return ERR_PARAM; - } - - err = ERR_INTERNAL; - - - /* Check if new thresholds are to be applied */ - if ((pdThreshold != ST25R3916_THRESHOLD_DO_NOT_SET) || (caThreshold != ST25R3916_THRESHOLD_DO_NOT_SET)) { - treMask = 0; - - if (pdThreshold != ST25R3916_THRESHOLD_DO_NOT_SET) { - treMask |= ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask; - } - - if (caThreshold != ST25R3916_THRESHOLD_DO_NOT_SET) { - treMask |= ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask; - } - - /* Set Detection Threshold and|or Collision Avoidance Threshold */ - st25r3916ChangeRegisterBits(ST25R3916_REG_FIELD_THRESHOLD_ACTV, treMask, (pdThreshold & ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask) | (caThreshold & ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask)); - } - - /* Set n x TRFW */ - st25r3916ChangeRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_n_mask, nTRFW); - - /*******************************************************************************/ - /* Enable and clear CA specific interrupts and execute command */ - st25r3916GetInterrupt((ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_APON)); - st25r3916EnableInterrupts((ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_APON)); - - st25r3916ExecuteCommand(FieldONCmd); - - /*******************************************************************************/ - /* Wait for initial APON interrupt, indicating anticollision avoidance done and ST25R3916's - * field is now on, or a CAC indicating a collision */ - irqs = st25r3916WaitForInterruptsTimed((ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_APON), ST25R3916_TOUT_CA); - - if ((ST25R3916_IRQ_MASK_CAC & irqs) != 0U) { /* Collision occurred */ - err = ERR_RF_COLLISION; - } else if ((ST25R3916_IRQ_MASK_APON & irqs) != 0U) { - /* After APON wait for CAT interrupt, indication field was switched on minimum guard time has been fulfilled */ - irqs = st25r3916WaitForInterruptsTimed((ST25R3916_IRQ_MASK_CAT), ST25R3916_TOUT_CA); - - if ((ST25R3916_IRQ_MASK_CAT & irqs) != 0U) { /* No Collision detected, Field On */ - err = ERR_NONE; - } - } else { - /* MISRA 15.7 - Empty else */ - } - - /* Clear any previous External Field events and disable CA specific interrupts */ - st25r3916GetInterrupt((ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_EON)); - st25r3916DisableInterrupts((ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_APON)); - - return err; -} -#endif } bool UnitST25R3916::configure_nfc_b() @@ -481,7 +426,7 @@ bool UnitST25R3916::readFIFOSize(uint16_t& bytes, uint8_t& bits) return false; } -bool UnitST25R3916::readFIFO(uint16_t& actual, uint8_t* buf, const uint16_t buf_size) +uint32_t UnitST25R3916::readFIFO(uint16_t& actual, uint8_t* buf, const uint16_t buf_size) { actual = 0; @@ -497,9 +442,9 @@ bool UnitST25R3916::readFIFO(uint16_t& actual, uint8_t* buf, const uint16_t buf_ return false; } actual = readSz; - return true; + return ((uint16_t)bits << 16) | bytes; } - return false; + return 0u; } bool UnitST25R3916::writeFIFO(const uint8_t* buf, const uint16_t buf_size) @@ -528,6 +473,32 @@ bool UnitST25R3916::readICIdentity(uint8_t& type, uint8_t& rev) return false; } +bool UnitST25R3916::disableField() +{ + return writeDirectCommand(CMD_STOP_ALL_ACTIVITIES) && writeOperationControl(0x00); +} + +bool UnitST25R3916::enableField() +{ + // Adjust regulators + if (!writeOperationControl(en)) { + M5_LIB_LOGE("Failed to writeOperationControl"); + return false; + } + if (!writeDirectCommand(CMD_ADJUST_REGULATORS)) { + M5_LIB_LOGE("Failed to CMD_ADJUST_REGULATORS"); + return false; + } + m5::utility::delay(5); // Need wait + + // Check vdd voltage + uint8_t value{}; + if (readRegulatorDisplay(value)) { + M5_LIB_LOGD("Regulated voltages:%02X:%1.1fV", value, regulated_voltages(value, _cfg.vdd_voltage_5V)); + } + return configureNFCMode(_cfg.mode); +} + // bool UnitST25R3916::read_register8(const uint8_t reg, uint8_t& v) { @@ -661,6 +632,7 @@ bool UnitST25R3916::wait_for_FIFO(const uint32_t timeout_ms, const uint16_t requ const uint16_t reqSize = required_size ? required_size : 1; if (is_irq32_rxe(irq)) { + // M5_LIB_LOGE(" rxe OK IRQ:%08X", irq); return true; } // M5_LIB_LOGE("IRQ:%08X %u", irq, timeout_ms); @@ -671,11 +643,10 @@ bool UnitST25R3916::wait_for_FIFO(const uint32_t timeout_ms, const uint16_t requ uint16_t bytes{}; uint8_t bits{}; do { - readFIFOSize(bytes, bits); - if (bytes >= reqSize) { + if (readFIFOSize(bytes, bits) && bytes >= reqSize) { break; } - m5::utility::delay(1); + std::this_thread::yield(); } while (m5::utility::millis() <= timeout_at); // M5_LIB_LOGE(" FIFO:%u,%u/%u", bytes, bits, required_size); return readFIFOSize(bytes, bits) && bytes >= reqSize; diff --git a/src/unit/unit_ST25R3916.hpp b/src/unit/unit_ST25R3916.hpp index d78af44..69a054e 100644 --- a/src/unit/unit_ST25R3916.hpp +++ b/src/unit/unit_ST25R3916.hpp @@ -149,9 +149,10 @@ public: @param[out] actual Actual read size @param[out] buf Buffer @param buf_size Buffer size - @return True if successful + @retval == 0 Failed + @retval != 0 Upper 16 bits: Number of bits read Lower 16 bits: Number of bytes read */ - bool readFIFO(uint16_t& actual, uint8_t* buf, const uint16_t buf_size); + uint32_t readFIFO(uint16_t& actual, uint8_t* buf, const uint16_t buf_size); /*! @brief Write to FIFO @param buf Buffer @@ -1685,6 +1686,22 @@ public: bool readICIdentity(uint8_t& type, uint8_t& rev); ///@} + ///@name Field + ///@{ + /*! + @brief Disable the Field to stop communication with the PICC + @return True if successful + @note Disconnect power supply to the PICC + */ + bool disableField(); + /*! + @brief Enable the Field to begin communication with the PICC + @return True if successful + @brief Begin supplying power to the PICC + */ + bool enableField(); + ///@} + // ---------------------------------------------------------------------------------------------- ///@name NFC-A ///@{ @@ -1695,10 +1712,11 @@ public: @param tx Send buffer @param tx_len Size of send buffer @param timeout_ms Timeout(ms) - @return True if successful + @retval == 0 Failed + @retval != 0 Upper 16 bits: Number of bits read Lower 16 bits: Number of bytes read */ - bool nfcaTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len, - const uint32_t timeout_ms); + uint32_t nfcaTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len, + const uint32_t timeout_ms); /*! @brief Request for idle PICC @param[out atqa ATQA @@ -1761,7 +1779,7 @@ public: bool nfcaHlt(); ///@} - ///@name MIFARE classic + ///@name MIFARE ///@{ /*! @brief Authentication using keyA of the specified block @@ -1794,8 +1812,23 @@ public: @param cmd Command @param block Block address @param arg Arrgument for command if needs + @return True if successful */ bool mifareClassicValueBlock(const m5::nfc::a::Command cmd, const uint8_t block, const uint32_t arg = 0); + + /*! + @brief Authentication step 1 for UltralightC + @param[out] ek ek(RndB) 8-byte encrypted PICC random number RndB + @return True if successful + */ + bool mifareUltralightCAuthenticate1(uint8_t ek[8]); + /*! + @brief Authentication step 1 for UltralightC + @param[out] rx_ek ek(RndA') 8-byte encrypted, shifted PCD random number RndA' + @param tx_ek ek(RandA || RndB') 16-byte encrypted random numbers RNDA concatenated by RndB' + @return True if successful + */ + bool mifareUltralightCAuthenticate2(uint8_t rx_ek[8], const uint8_t tx_ek[16]); ///@} ///@name NTAG @@ -1857,7 +1890,7 @@ public: @return True if successful @warning FeliCa Standard only */ - bool nfcfRequestResponse(m5::nfc::f::Mode& mode, const m5::nfc::f::PICC& picc); + bool nfcfRequestResponse(m5::nfc::f::standard::Mode& mode, const m5::nfc::f::PICC& picc); /*! @brief Request system code @@ -1943,12 +1976,15 @@ protected: // NFC-A bool nfca_request_wakeup(uint16_t& atqa, const bool req); bool nfca_anti_collision(uint8_t rbuf[5], const uint8_t lv); + bool mifare_transceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len, + const uint32_t timeout_ms); bool mifare_classic_send_encrypt(const uint8_t* tx, const uint16_t tx_len); bool mifare_classic_transceive_encrypt(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len, const uint32_t timeout_ms, const bool include_crc, const bool decrypt); bool mifare_classic_authenticate(const m5::nfc::a::Command cmd, const m5::nfc::a::PICC& picc, const uint8_t block, const m5::nfc::a::mifare::classic::Key& key); - bool ntag_get_version(uint8_t info[10]); + + bool ntag_get_version(uint8_t info[8]); private: config_t _cfg{}; diff --git a/src/unit/unit_ST25R3916_nfca.cpp b/src/unit/unit_ST25R3916_nfca.cpp index f03a48a..28a2b40 100644 --- a/src/unit/unit_ST25R3916_nfca.cpp +++ b/src/unit/unit_ST25R3916_nfca.cpp @@ -9,6 +9,7 @@ */ #include "unit_ST25R3916.hpp" #include +#include using namespace m5::utility::mmh3; @@ -127,8 +128,8 @@ bool UnitST25R3916::configure_nfc_a() nfc_initial_field_on(); } -bool UnitST25R3916::nfcaTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len, - const uint32_t timeout_ms) +uint32_t UnitST25R3916::nfcaTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len, + const uint32_t timeout_ms) { CHECK_MODE(); @@ -157,11 +158,13 @@ bool UnitST25R3916::nfcaTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* } uint16_t actual{}; - if (readFIFO(actual, rx, rx_len_org)) { + auto bb = readFIFO(actual, rx, rx_len_org); + if (bb) { + // M5_LIB_LOGE("readFIFO %u/%u %u/%u %02X", actual, rx_len_org, bb >> 16, bb & 0xFFFF, rx[0]); rx_len = actual; - return true; + return bb; } - M5_LIB_LOGE("Failed to readFIFO %u/%u", actual, rx_len_org); + M5_LIB_LOGD("Failed to readFIFO %u/%u", actual, rx_len_org); return false; } @@ -192,9 +195,9 @@ bool UnitST25R3916::nfca_request_wakeup(uint16_t& atqa, const bool request) if (readFIFO(actual, rbuf, sizeof(rbuf)) && actual) { if (actual == 2) { atqa = ((uint16_t)rbuf[1] << 8) | (uint16_t)rbuf[0]; - M5_LIB_LOGD("ATQA:%04X", atqa); } - // When ocuur collisions, the ATQA value is inaccurate + // M5_LIB_LOGE("ATQA:%04X %u", atqa, actual); + // When ocuur collisions, the ATQA value is inaccurate return true; } } @@ -234,7 +237,7 @@ bool UnitST25R3916::nfca_anti_collision(uint8_t rbuf[5], const uint8_t lv) auto irq = wait_for_interrupt(I_rxe32 | I_col32, TIMEOUT_ANTICOLL); collision = is_irq32_collision(irq); if ((!collision && !is_irq32_rxe(irq))) { - M5_LIB_LOGD("Failed ANTICOL:%02X %08X", lv, irq); + M5_LIB_LOGE("Failed Lv::%u col:%u %08X", lv, collision, irq); return false; } uint8_t cd{}; @@ -312,19 +315,38 @@ bool UnitST25R3916::nfcaSelectWithAnticollision(bool& completed, PICC& picc, con picc.type = sak_to_type(sak); // WARNING: This is a preliminary diagnosis; a more accurate diagnosis is required picc.blocks = get_number_of_blocks(picc.type); - // More check for type + + // More detailed type identification if (picc.type == Type::MIFARE_Ultralight) { - uint8_t ver[10]{}; + picc.type = Type::Unknown; + uint8_t ver[8]{}; + uint16_t discard{}; + // GetVersion if (ntag_get_version(ver)) { - picc.type = version_to_type(ver); - picc.blocks = get_number_of_blocks(picc.type); + picc.type = version_to_type(ver); } else { - // PICC to IDLE... so need reactivate - uint16_t discard{}; + // PICC is IDLE... so need reactivate + completed = nfcaWakeup(discard) && nfcaSelect(picc); + } + if (picc.type == Type::Unknown) { + uint8_t discard_ek[8]{}; + if (mifareUltralightCAuthenticate1(discard_ek)) { + // ULC has AUTH + picc.type = Type::MIFARE_UltralightC; + // Throw an Hlt to transition to IDLE + // Otherwise, subsequent commands become invalid in an incomplete state, causing unexpected IDLE + nfcaHlt(); + } else { + // really UL + picc.type = Type::MIFARE_Ultralight; + } + picc.blocks = get_number_of_blocks(picc.type); + // PICC is IDLE... so need reactivate completed = nfcaWakeup(discard) && nfcaSelect(picc); } } } + // M5_LIB_LOGE(">>>> Select %02X %u %u", sak, completed, has_sak_dependent_bit(sak)); return completed || has_sak_dependent_bit(sak); // completed or continue } @@ -334,9 +356,7 @@ bool UnitST25R3916::nfcaSelect(const PICC& picc) CHECK_MODE(); - if (!picc.valid()) { - return false; - } + // Select even if picc is not valid bool completed{}; uint8_t select_frame[7] = {0x93, 0x70}; @@ -372,6 +392,8 @@ bool UnitST25R3916::nfcaSelect(const PICC& picc) ++lv; } while (!completed && lv < 4); + // M5_LIB_LOGE(" >>>> SELECT Result:%u", completed); + return completed; } @@ -379,6 +401,8 @@ bool UnitST25R3916::nfcaHlt() { CHECK_MODE(); + _encrypted = false; + const uint8_t hlt_frame[2] = {m5::stl::to_underlying(Command::HLTA), 0x00}; if (_encrypted) { @@ -391,10 +415,10 @@ bool UnitST25R3916::nfcaHlt() !clearInterrupts() || !writeDirectCommand(CMD_CLEAR_FIFO) || // !writeFIFO(hlt_frame, sizeof(hlt_frame)) || !writeNumberOfTransmittedBytes(sizeof(hlt_frame), 0) || // !writeDirectCommand(CMD_TRANSMIT_WITH_CRC)) { + M5_LIB_LOGE("Failed to hlt"); return false; } } - _encrypted = false; // No response is coming back, so need to confirm if it was sent auto irq = wait_for_interrupt(I_txe32, TIMEOUT_HALT); return is_irq32_txe(irq); @@ -485,6 +509,22 @@ bool UnitST25R3916::nfcaWritePage(const uint8_t page, const uint8_t tx[4]) } // -------------------------------- For MIFARE classic +bool UnitST25R3916::mifare_transceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len, + const uint32_t timeout_ms) +{ + if (!rx | !rx_len || !tx || !tx_len) { + return false; + } + auto bb = nfcaTransceive(rx, rx_len, tx, tx_len, timeout_ms); + if (!bb) { + return false; + } + // Check NACK + uint16_t bytes = bb & 0xffff; + uint16_t bits = (bb >> 16) & 0xFF; + return (bytes == 1 && bits == 4) ? rx[0] == ACK_NIBBLE : true; +} + bool UnitST25R3916::mifare_classic_send_encrypt(const uint8_t* tx, const uint16_t tx_len) { if (!tx || !tx_len || tx_len > 32) { @@ -720,6 +760,36 @@ bool UnitST25R3916::mifareClassicValueBlock(const m5::nfc::a::Command cmd, const return !wait_for_FIFO(TIMEOUT_VALUE_BLOCK); // Consider the timeout a success } +bool UnitST25R3916::mifareUltralightCAuthenticate1(uint8_t ek[8]) +{ + uint8_t cmd[2] = {m5::stl::to_underlying(Command::AUTHENTICATE_1), 0x00}; + uint8_t rx[9]{}; + uint16_t rx_len{9}; + if (ek && mifare_transceive(rx, rx_len, cmd, sizeof(cmd), TIMEOUT_AUTH1) && rx_len == 9 && rx[0] == 0xAF) { + memcpy(ek, rx + 1, 8); + return true; + } + return false; +} + +bool UnitST25R3916::mifareUltralightCAuthenticate2(uint8_t rx_ek[8], const uint8_t tx_ek[16]) +{ + if (!rx_ek || !tx_ek) { + return false; + } + + uint8_t cmd[1 + 16] = {m5::stl::to_underlying(Command::AUTHENTICATE_2)}; + memcpy(cmd + 1, tx_ek, 16); + + uint8_t rx[9]{}; + uint16_t rx_len{9}; + if (mifare_transceive(rx, rx_len, cmd, sizeof(cmd), TIMEOUT_AUTH2) && rx_len == 9 && rx[0] == 0x00) { + memcpy(rx_ek, rx + 1, 8); + return true; + } + return false; +} + // -------------------------------- For NTAG bool UnitST25R3916::ntagReadPage(uint8_t* rx, uint16_t& rx_len, const uint8_t spage, const uint8_t epage) { @@ -745,11 +815,11 @@ bool UnitST25R3916::ntagReadPage(uint8_t* rx, uint16_t& rx_len, const uint8_t sp return true; } -bool UnitST25R3916::ntag_get_version(uint8_t info[10]) +bool UnitST25R3916::ntag_get_version(uint8_t info[8]) { - uint8_t gv[1] = {m5::stl::to_underlying(Command::GET_VERSION)}; - uint16_t rx_len = 10; - return nfcaTransceive(info, rx_len, gv, sizeof(gv), TIMEOUT_GET_VERSION); + uint8_t cmd[1] = {m5::stl::to_underlying(Command::GET_VERSION)}; + uint16_t rx_len = 8; + return info && mifare_transceive(info, rx_len, cmd, sizeof(cmd), TIMEOUT_GET_VERSION); } } // namespace unit diff --git a/src/unit/unit_ST25R3916_nfcf.cpp b/src/unit/unit_ST25R3916_nfcf.cpp index a6a0c2d..fe9416e 100644 --- a/src/unit/unit_ST25R3916_nfcf.cpp +++ b/src/unit/unit_ST25R3916_nfcf.cpp @@ -218,11 +218,11 @@ bool UnitST25R3916::nfcfRequestService(uint16_t key_version[], const m5::nfc::f: return true; } -bool UnitST25R3916::nfcfRequestResponse(m5::nfc::f::Mode& mode, const m5::nfc::f::PICC& picc) +bool UnitST25R3916::nfcfRequestResponse(m5::nfc::f::standard::Mode& mode, const m5::nfc::f::PICC& picc) { CHECK_MODE(); - mode = Mode::Mode0; + mode = standard::Mode::Mode0; if (picc.type != Type::FeliCaStandard) { return false; @@ -249,7 +249,7 @@ bool UnitST25R3916::nfcfRequestResponse(m5::nfc::f::Mode& mode, const m5::nfc::f // m5::utility::log::dump(rbuf, rx_len, false); - mode = static_cast(rbuf[10]); + mode = static_cast(rbuf[10]); return true; }