Files
M5Unit-NFC/examples/UnitUnified/NFCF/ReadWriteMAC/main/ReadWriteMAC.cpp

579 lines
17 KiB
C++

/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Example using M5UnitUnified for ST25R3916
Read/Write with MAC example for FeliCa Lite-S
*******************************************************************************************************************
NOTICE: Please note that cards that have undergone the initial issuance procedure cannot be read without subsequent
authentication.
*******************************************************************************************************************
*/
#include <M5Unified.h>
#include <M5UnitUnified.h>
#include <M5UnitUnifiedNFC.h>
#include <M5Utility.h>
#include <vector>
#include <esp_random.h>
#include <mbedtls/md.h>
// *************************************************************
// Choose one define symbol to match the unit you are using
// *************************************************************
#if !defined(USING_UNIT_NFC) && !defined(USING_CAP_CC1101)
// For UnitNFC
// #define USING_UNIT_NFC
// For CapNFC
// #define USING_CAP_CC1101
#endif
using namespace m5::nfc;
using namespace m5::nfc::f;
using m5::utility::crypto::TripleDES;
namespace {
auto& lcd = M5.Display;
m5::unit::UnitUnified Units;
#if defined(USING_UNIT_NFC)
#pragma message "Choose UnitNFC"
m5::unit::UnitNFC unit{}; // I2C
#elif defined(USING_CAP_CC1101)
#pragma message "Choose CapCC1101NFC"
m5::unit::CapCC1101NFC unit{}; // CapCC1101 (SPI)
#else
#error Choose unit please!
#endif
m5::nfc::NFCLayerF nfc_f{unit};
// The master key used for create CK
// For the sake of this example, it's written as source code, but it should actually outside externally (SD, Cloud...)
constexpr uint8_t example_master_key[24] = {0xE3, 0x92, 0xCA, 0xC2, 0xF9, 0x21, 0x3B, 0xF2, 0xC0, 0x4F, 0x65, 0xC4,
0x8E, 0xB6, 0xF6, 0x34, 0x5F, 0x02, 0x36, 0xD6, 0x26, 0xD5, 0x97, 0xA1};
// Card key version(Format is free)
// For the sake of this example, it's written as source code, but it should actually outside externally (SD, Cloud...)
constexpr uint16_t example_ckv{0x0509};
// Enable for custom CK derivation (e.g. HMAC-SHA256); otherwise use make_personalized_card_key_lite_s.
// The official standard algorithm derives the CK from the 16-byte ID block (0x82).
// A different method is acceptable if it still produces a unique CK per card.
// #define USE_CUSTOM_CARD_KEY_DERIVATION
// HMAC-SHA256 (custom derivation example)
void hmac_sha256(uint8_t out[32], const uint8_t* key, const uint32_t key_len, const uint8_t* input,
const uint32_t input_len)
{
mbedtls_md_context_t ctx;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1 /* HMAC */);
mbedtls_md_hmac_starts(&ctx, (const unsigned char*)key, key_len);
mbedtls_md_hmac_update(&ctx, (const unsigned char*)input, input_len);
mbedtls_md_hmac_finish(&ctx, out);
mbedtls_md_free(&ctx);
}
#if defined(USE_CUSTOM_CARD_KEY_DERIVATION)
/*
Example of Creating a Card Key from the Master Key
We are uniquely determining each card's CK from the IDm and master_key.
If you have a non-SONY official method that still produces a unique CK per card,
you can use that as a substitute.
CK = first 16 bytes (HMAC-SHA256(master_key, "M5Stack" || CKV || IDm))
*/
void derive_card_key(uint8_t ck[16], const uint8_t master_key[24], const uint16_t ckv, const uint8_t idm[8])
{
uint8_t msg[32]{};
uint32_t pos = 0;
// Any prefix
msg[pos++] = 'M';
msg[pos++] = '5';
msg[pos++] = 'S';
msg[pos++] = 't';
msg[pos++] = 'a';
msg[pos++] = 'c';
msg[pos++] = 'k';
// Key version
msg[pos++] = (ckv >> 8) & 0xFF;
msg[pos++] = (ckv >> 0) & 0xFF;
// IDm
std::memcpy(msg + pos, idm, 8);
pos += 8;
// Additional context information may be added if necessary
//...
uint8_t digest[32]{};
hmac_sha256(digest, master_key, 24, msg, pos);
std::memcpy(ck, digest, 16);
}
#else
bool make_card_key_from_id_block(uint8_t ck[16], const uint8_t master_key[24], const uint8_t id_block[16])
{
return make_personalized_card_key_lite_s(ck, master_key, id_block);
}
#endif
bool internal_auth()
{
const auto& picc = nfc_f.activatedPICC();
if (!picc.valid()) {
return false;
}
uint8_t ck[16]{};
#if defined(USE_CUSTOM_CARD_KEY_DERIVATION)
derive_card_key(ck, example_master_key, example_ckv, picc.idm);
#else
uint8_t id_block[16]{};
if (!nfc_f.read16(id_block, lite_s::ID)) {
return false;
}
if (!make_card_key_from_id_block(ck, example_master_key, id_block)) {
return false;
}
#endif
M5.Log.printf("==== CK:\n");
m5::utility::log::dump(ck, 16, false);
// Make random challenge
uint8_t rc[16]{};
for (auto& r : rc) {
r = esp_random();
}
// m5::utility::log::dump(rc, 16, false);
// Authentication
return nfc_f.internalAuthenticate(ck, example_ckv, rc);
}
bool external_auth()
{
const auto& picc = nfc_f.activatedPICC();
if (!picc.valid()) {
return false;
}
uint8_t ck[16]{};
#if defined(USE_CUSTOM_CARD_KEY_DERIVATION)
derive_card_key(ck, example_master_key, example_ckv, picc.idm);
#else
uint8_t id_block[16]{};
if (!nfc_f.read16(id_block, lite_s::ID)) {
return false;
}
if (!make_card_key_from_id_block(ck, example_master_key, id_block)) {
return false;
}
#endif
// M5.Log.printf("==== CK:\n");
// m5::utility::log::dump(ck, 16, false);
// Authentication
return nfc_f.externalAuthenticate(ck, example_ckv);
}
void access_example()
{
uint8_t rx[16]{};
constexpr char data0[] = "0-ABCDEF";
constexpr char data1[] = "1-GHIJKL";
constexpr char data2[] = "2-MNOPQR";
nfc_f.dump();
// No auth
M5.Log.printf("======== No auth\n");
M5.Log.printf("Block 2 does not require authentication)\n");
if (nfc_f.write16(2, (uint8_t*)data2, sizeof(data2))) {
M5.Log.printf(" OK W\n");
}
if (nfc_f.read16(rx, 2)) {
M5.Log.printf(" OK R\n");
m5::utility::log::dump(rx, 16, false);
}
M5.Log.printf("Block 0 requires authentication\n");
if (!nfc_f.write16(0, (uint8_t*)data0, sizeof(data0))) {
M5.Log.printf(" OK NW\n");
} else {
M5_LOGE(" NG");
}
if (!nfc_f.read16(rx, 0)) {
M5.Log.printf(" OK NR\n");
} else {
M5_LOGE(" NG");
}
M5.Log.printf("Block 1 requires authentication\n");
if (!nfc_f.write16(1, (uint8_t*)data1, sizeof(data1))) {
M5.Log.printf(" OK NW\n");
} else {
M5_LOGE(" NG");
}
if (!nfc_f.read16(rx, 1)) {
M5.Log.printf(" OK NR\n");
} else {
M5_LOGE(" NG");
}
// Internal auth
if (!internal_auth()) {
// ******************************************************************************
// If the first_issuance_procedure_lite_s() has not been executed, an error will occur here.
// ******************************************************************************
M5_LOGE("Failed to internal authenticate");
lcd.fillScreen(TFT_RED);
return;
}
M5.Log.printf("======== Internal auth OK\n");
M5.Log.printf("Block 2 does not require authentication)\n");
if (nfc_f.read16(rx, 2)) {
M5.Log.printf(" OK\n");
}
M5.Log.printf("Block 0 requires authentication\n");
if (!nfc_f.read16(rx, 0)) {
M5.Log.printf(" OK NR\n");
} else {
M5_LOGE(" NG");
}
if (!nfc_f.readWithMAC16(rx, 0)) {
M5.Log.printf(" OK NR\n");
} else {
M5_LOGE(" NG");
}
if (!nfc_f.write16(0, (uint8_t*)data0, sizeof(data0))) {
M5.Log.printf(" OK NW\n");
} else {
M5_LOGE(" NG");
}
if (!nfc_f.writeWithMAC16(0, (uint8_t*)data0, sizeof(data0))) {
M5.Log.printf(" OK NWMAC\n");
} else {
M5_LOGE(" NG");
}
M5.Log.printf("Block 1 requires authentication\n");
if (!nfc_f.read16(rx, 1)) {
M5.Log.printf(" OK NR\n");
} else {
M5_LOGE(" NG");
}
if (!nfc_f.readWithMAC16(rx, 1)) {
M5.Log.printf(" OK NR\n");
} else {
M5_LOGE(" NG");
}
if (!nfc_f.write16(1, (uint8_t*)data1, sizeof(data0))) {
M5.Log.printf(" OK NW\n");
} else {
M5_LOGE(" NG");
}
if (!nfc_f.writeWithMAC16(1, (uint8_t*)data1, sizeof(data0))) {
M5.Log.printf(" OK NWMAC\n");
} else {
M5_LOGE(" NG");
}
// External auth
if (!external_auth()) {
M5_LOGE("Failed to external authenticate");
lcd.fillScreen(TFT_RED);
return;
}
M5.Log.printf("======== External auth OK\n");
M5.Log.printf("Block 2 does not require authentication)\n");
if (nfc_f.read16(rx, 2)) {
M5.Log.printf(" OK\n");
}
M5.Log.printf("Block 0 requires authentication\n");
if (nfc_f.read16(rx, 0)) {
M5.Log.printf(" OK R\n");
m5::utility::log::dump(rx, 16, false);
} else {
M5_LOGE(" NG");
}
if (nfc_f.readWithMAC16(rx, 0)) {
M5.Log.printf(" OK RMAC\n");
m5::utility::log::dump(rx, 16, false);
} else {
M5_LOGE(" NG");
}
// Block 0 can write without encryption
if (nfc_f.write16(0, (uint8_t*)data0, sizeof(data0))) {
M5.Log.printf(" OK W\n");
} else {
M5_LOGE(" NG");
}
M5.Log.printf("Block 1 requires authentication\n");
if (nfc_f.read16(rx, 1)) {
M5.Log.printf(" OK R\n");
m5::utility::log::dump(rx, 16, false);
} else {
M5_LOGE(" NG");
}
if (nfc_f.readWithMAC16(rx, 1)) {
M5.Log.printf(" OK RMAC\n");
m5::utility::log::dump(rx, 16, false);
} else {
M5_LOGE(" NG");
}
// Block 1 can NOT write without encryption
if (!nfc_f.write16(1, (uint8_t*)data1, sizeof(data1))) {
M5.Log.printf(" OK NW\n");
} else {
M5_LOGE(" NG");
}
// Block 1 write needs with MAC
if (nfc_f.writeWithMAC16(1, (uint8_t*)data1, sizeof(data1))) {
M5.Log.printf(" OK WMAC\n");
} else {
M5_LOGE(" NG");
}
lcd.fillScreen(0);
}
bool first_issuance_procedure_lite_s(const PICC& picc, const uint8_t master_key[24], const uint16_t ckv)
{
uint8_t rbuf[16 * 4]{};
M5.Log.printf("First issuance procedure...\n");
// 7.3.2 Write ID (No DFC)
if (!nfc_f.write16(lite_s::ID, picc.idm, 8) || !nfc_f.read16(rbuf, lite_s::ID)) {
M5_LOGE("Failed to write/read ID");
return false;
}
if (memcmp(rbuf, picc.idm, 8) != 0) {
M5_LOGE("Failed to verify ID");
return false;
}
M5.Log.printf(" Write ID OK\n");
// 7.3.3 Write CK
uint8_t ck[16]{};
uint8_t wbuf[2]{};
wbuf[0] = ckv >> 8;
wbuf[1] = ckv & 0xFF;
#if defined(USE_CUSTOM_CARD_KEY_DERIVATION)
derive_card_key(ck, master_key, ckv, picc.idm);
#else
uint8_t id_block[16]{};
if (!nfc_f.read16(id_block, lite_s::ID)) {
M5_LOGE("Failed to read ID for CK");
return false;
}
if (!make_card_key_from_id_block(ck, master_key, id_block)) {
M5_LOGE("Failed to make CK");
return false;
}
#endif
// M5.Log.printf("==== CK:\n");
// m5::utility::log::dump(ck, 16, false);
if (!nfc_f.write16(lite_s::CK, ck, sizeof(ck)) || !nfc_f.write16(lite_s::CKV, wbuf, 2)) {
M5_LOGE("Failed to write/read CK");
return false;
}
M5.Log.printf(" Write CK OK\n");
// 7,3.4 Verify CK
uint8_t rc[16]{};
for (auto& r : rc) {
r = esp_random();
}
if (!nfc_f.internalAuthenticate(ck, ckv, rc)) {
M5_LOGE("Failed to verify CK");
return false;
}
nfc_f.clearAuthenticate();
M5.Log.printf(" Verify CK OK\n");
// 7.3.5 Write CKV
wbuf[0] = ckv >> 8;
wbuf[1] = ckv & 0xFF;
m5::utility::log::dump(wbuf, 2, false);
if (!nfc_f.write16(lite_s::CKV, wbuf, 2) || !nfc_f.read16(rbuf, lite_s::CKV)) {
M5_LOGE("Failed to write/read CKV");
return false;
}
if (memcmp(wbuf, rbuf, 2) != 0) {
M5_LOGE("Failed to verify CKV");
return false;
}
M5.Log.printf(" Write/Verify CKV OK\n");
#if 0
// 7.3.6 Write user block (optional)
constexpr char msg[16] = "M5Stack-NFC";
if (!nfc_f.write16(0, (uint8_t*)msg, sizeof(msg)) || !nfc_f.read16(rbuf, 0)) {
M5_LOGE("Failed to write/read 0");
return false;
}
if (memcmp(msg, rbuf, sizeof(msg)) != 0) {
M5_LOGE("Failed to verify 0");
return false;
}
#endif
// 7.3.7 MC settings
uint8_t mc[16]{};
if (!nfc_f.read16(mc, lite_s::MC)) {
M5_LOGE("Failed to read MC");
return false;
}
constexpr uint8_t MC_STATE_W_MAC_A{12};
constexpr uint8_t MC_SP_REG_W_MAC_A{10};
constexpr uint8_t MC_SP_REG_W_RESTR{8};
constexpr uint8_t MC_SP_REG_R_RESTR{6};
constexpr uint8_t RF_PRM{4};
constexpr uint8_t SYS_OP{3};
constexpr uint8_t MC_ALL{2};
mc[MC_STATE_W_MAC_A] = 0x01;
mc[MC_SP_REG_W_MAC_A] = 0x02; // SPAD_1 write needs MAC
mc[MC_SP_REG_W_RESTR] = 0x01 | 0x02; // SPAD_0.1 write needs Auth
mc[MC_SP_REG_R_RESTR] = 0x01 | 0x02; // SPAD_0.1 read needs Auth
mc[RF_PRM] = 0x07; // Fixed value
mc[SYS_OP] = 0x00; // 0x01 NDEF
mc[MC_ALL] = 0xFF; // RO
// Some blocks should be set to read-only (not done in this example)
if (!nfc_f.write16(lite_s::MC, mc, 16) || !nfc_f.read16(rbuf, lite_s::MC)) {
M5_LOGE("Failed to write/read MC");
return false;
}
/*
if (memcmp(mc, rbuf, 16) != 0) {
M5_LOGE("Failed to verify MC");
return false;
}
*/
M5.Log.printf(" Permission settings OK\n");
// 7.3.8 Confirmed (Disconnection of power supply from the reader)
if (unit.disableField()) {
m5::utility::delay(50);
if (unit.enableField()) {
M5.Log.printf(" DONE\n");
return true;
}
}
M5_LOGE("Failed to confirm");
return false;
}
} // namespace
void setup()
{
M5.begin();
M5.setTouchButtonHeightByRatio(100);
auto cfg = unit.config();
cfg.mode = NFC::F;
unit.config(cfg);
#if defined(USING_UNIT_NFC)
auto board = M5.getBoard();
bool unit_ready{};
// NessoN1: SoftwareI2C too slow for NFC RF timing -> use port_a (Wire) via else branch
if (board == m5::board_t::board_M5NanoC6) {
M5_LOGI("Using M5.Ex_I2C");
unit_ready = Units.add(unit, M5.Ex_I2C) && Units.begin();
} else {
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
Wire.end();
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
unit_ready = Units.add(unit, Wire) && Units.begin();
}
if (!unit_ready) {
M5_LOGE("Failed to begin");
lcd.fillScreen(TFT_RED);
while (true) {
m5::utility::delay(10000);
}
}
#elif defined(USING_CAP_CC1101)
if (!SPI.bus()) {
auto spi_sclk = M5.getPin(m5::pin_name_t::sd_spi_sclk);
auto spi_mosi = M5.getPin(m5::pin_name_t::sd_spi_mosi);
auto spi_miso = M5.getPin(m5::pin_name_t::sd_spi_miso);
M5_LOGI("getPin: %d,%d,%d", spi_sclk, spi_mosi, spi_miso);
SPI.begin(spi_sclk, spi_miso, spi_mosi /* SS is shared SD, CC1101, ST25R3916 */);
}
SPISettings settings = {10000000, MSBFIRST, SPI_MODE1};
if (!Units.add(unit, SPI, settings) || !Units.begin()) {
M5_LOGE("Failed to begin");
lcd.fillScreen(TFT_RED);
while (true) {
m5::utility::delay(10000);
}
}
#endif
M5_LOGI("M5UnitUnified initialized");
M5_LOGI("%s", Units.debugInfo().c_str());
if (lcd.height() > lcd.width()) {
lcd.setRotation(1);
}
lcd.setFont(&fonts::Font0);
lcd.fillScreen(0);
lcd.setCursor(0, 0);
lcd.printf("Please put the PICC and click/hold G0");
M5.Log.printf("Please put the PICC and click/hold G0\n");
}
void loop()
{
M5.update();
Units.update();
bool clicked = M5.BtnA.wasClicked(); // For access
bool held = M5.BtnA.wasHold(); // For first issuance
if (clicked || held) {
lcd.fillRect(0, lcd.fontHeight(), lcd.width(), lcd.height() - lcd.fontHeight());
PICC picc{};
if (nfc_f.detect(picc)) {
M5.Log.printf("%s:%s %s F:%02X DF:%04X\n", picc.idmAsString().c_str(), picc.pmmAsString().c_str(),
picc.typeAsString().c_str(), picc.format, picc.dfc_format);
if (picc.type == Type::FeliCaLiteS) {
if (nfc_f.activate(picc)) {
if (clicked) {
access_example();
} else {
if (!first_issuance_procedure_lite_s(picc, example_master_key, example_ckv)) {
M5_LOGE("Failed to first_issuance_procedure_lite_s");
}
}
nfc_f.deactivate();
}
} else {
M5.Log.printf("Not support\n");
}
lcd.setCursor(0, 0);
lcd.printf("Please put the PICC and click/hold G0");
M5.Log.printf("Please put the PICC and click/hold G0\n");
} else {
M5.Log.printf("PICC NOT exists\n");
}
}
}