mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2024-12-23 21:34:35 +03:00
[FL-3810] Felica emulation (#3673)
* Moved some structs and defs from poller to generic felica * Buffer size increased for transferring more data * Felica HAL Tx function implemented * Some structs and fields for listener * Raw listener implementation * Added new event for felica activation * Proper config fot listener added * Moved some structs from poller in order to use them in listener too * New function for calculating MAC * Listener data structures and function definitions * Private listener functions implementation added * Raw felica listener logic implementation added * Fix total sector count both for poller and listener * Defined type for write handlers * New logic for write operations added * Removed old commented code * Splitted read logic into several separate functions * New type added and some fields to instance * New logic of read command implemented * Defines added for response codes * Functions moved to private namespace * Function visibility changed and some cleanups * Update felica_listener.c, felica_listener_i.c, and felica_listener_i.h * Some type adjustments * Moved frame_exchange function to private namespace * Error handling added * Function to get data_ptr for write request added * Missing declaration added * Add processing of nfc errors * write_with_mac is a local variable now * Adjustments to MAC calculation logic * Values replaced with defines * Update nfc_transport.c with felica logic * Sync felica poller added for unit tests * Felica unit_tests and data dump added * Fixed proper reading of MAC_A block when it is 1st * Macro definitions for MC added * Function simplified * More defines * CRC check for incomming packets added * Readonly logic adjusted * Block write validation adjusted * New logic for ID block writing * Some cleanups * New logic of moving across the block list with different element length * Some cleanups * Adjusted requires_mac logic to cover all blocks needed * Cleanups and renaming * New block list validation logic * Block list logic iteration simplified * Some asserts and checks added * Replaced MC[2] checks with macros * Marked def values as unsigned * Removed old code * Removed commented function declarations * Changed protected block in felica test card dump and adjusted tests * Fixes after merge * Moved defines to header * Now we allocate memory for max possible response pack in any case * Some renaming and documentation * Bump api symbols * Set feature to emulate full for felica * Removed 'More' button and added MoreInfo feature which adds this button back * Types renamed * Removed unnecessary code * Reformat comments * Fixing missing signatures * Replaced crash with error log and return value * Format doxygen comments Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
parent
ba3beeddeb
commit
467e973da2
@ -0,0 +1,40 @@
|
||||
Filetype: Flipper NFC device
|
||||
Version: 4
|
||||
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB
|
||||
Device type: FeliCa
|
||||
# UID is common for all formats
|
||||
UID: 29 9F FA 53 AB 75 87 6E
|
||||
# FeliCa specific data
|
||||
Data format version: 1
|
||||
Manufacture id: 29 9F FA 53 AB 75 87 6E
|
||||
Manufacture parameter: 57 4E 10 2A 94 16 BC 8E
|
||||
Blocks total: 28
|
||||
Blocks read: 28
|
||||
Block 0: 00 00 DE AD BE AF 00 00 00 00 00 00 00 00 DE AD BE AF
|
||||
Block 1: 00 00 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF
|
||||
Block 2: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 3: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 4: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 5: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 6: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 7: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 9: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 11: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 12: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 13: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 14: 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
|
||||
Block 15: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 16: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 17: 00 00 29 9F FA 53 AB 75 87 6E 57 4E 10 2A 94 16 BC 8E
|
||||
Block 18: 00 00 29 9F FA 53 AB 75 87 6E 00 F1 00 00 00 01 43 00
|
||||
Block 19: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 20: 00 00 88 B4 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 21: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 22: 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
|
||||
Block 23: 00 00 FF FF FF 00 FF 00 10 00 00 00 00 00 00 00 00 00
|
||||
Block 24: 00 00 24 FE FF 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 25: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 26: 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 27: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
@ -12,6 +12,8 @@
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include <nfc/protocols/felica/felica.h>
|
||||
#include <nfc/protocols/felica/felica_poller_sync.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller.h>
|
||||
#include <nfc/protocols/iso15693_3/iso15693_3_poller.h>
|
||||
#include <nfc/protocols/slix/slix.h>
|
||||
@ -646,6 +648,56 @@ MU_TEST(mf_classic_dict_test) {
|
||||
"Remove test dict failed");
|
||||
}
|
||||
|
||||
static FelicaError
|
||||
felica_do_request_response(FelicaData* felica_data, const FelicaCardKey* card_key) {
|
||||
NfcDeviceData* nfc_device = nfc_device_alloc();
|
||||
|
||||
FelicaError error = FelicaErrorNone;
|
||||
if(!nfc_device_load(nfc_device, EXT_PATH("unit_tests/nfc/Felica.nfc"))) {
|
||||
error = FelicaErrorNotPresent;
|
||||
} else {
|
||||
Nfc* poller = nfc_alloc();
|
||||
Nfc* listener = nfc_alloc();
|
||||
NfcListener* felica_listener = nfc_listener_alloc(
|
||||
listener, NfcProtocolFelica, nfc_device_get_data(nfc_device, NfcProtocolFelica));
|
||||
nfc_listener_start(felica_listener, NULL, NULL);
|
||||
|
||||
error = felica_poller_sync_read(poller, felica_data, card_key);
|
||||
|
||||
nfc_listener_stop(felica_listener);
|
||||
nfc_listener_free(felica_listener);
|
||||
|
||||
nfc_free(listener);
|
||||
nfc_free(poller);
|
||||
}
|
||||
|
||||
nfc_device_free(nfc_device);
|
||||
return error;
|
||||
}
|
||||
|
||||
MU_TEST(felica_read) {
|
||||
FelicaData* felica_data = felica_alloc();
|
||||
FelicaError error = felica_do_request_response(felica_data, NULL);
|
||||
mu_assert(error == FelicaErrorNone, "felica_poller() failed");
|
||||
mu_assert(felica_data->data.fs.spad[4].SF1 == 0x01, "block[4].SF1 != 0x01");
|
||||
mu_assert(felica_data->data.fs.spad[4].SF2 == 0xB1, "block[4].SF2 != 0xB1");
|
||||
|
||||
felica_free(felica_data);
|
||||
}
|
||||
|
||||
MU_TEST(felica_read_auth) {
|
||||
FelicaData* felica_data = felica_alloc();
|
||||
FelicaCardKey card_key;
|
||||
memset(card_key.data, 0xFF, FELICA_DATA_BLOCK_SIZE);
|
||||
|
||||
FelicaError error = felica_do_request_response(felica_data, &card_key);
|
||||
mu_assert(error == FelicaErrorNone, "felica_poller() failed");
|
||||
mu_assert(felica_data->data.fs.spad[4].SF1 == 0x00, "block[4].SF1 != 0x00");
|
||||
mu_assert(felica_data->data.fs.spad[4].SF2 == 0x00, "block[4].SF2 != 0x00");
|
||||
|
||||
felica_free(felica_data);
|
||||
}
|
||||
|
||||
MU_TEST(slix_file_with_capabilities_test) {
|
||||
NfcDevice* nfc_device_missed_cap = nfc_device_alloc();
|
||||
mu_assert(
|
||||
@ -807,6 +859,8 @@ MU_TEST_SUITE(nfc) {
|
||||
MU_RUN_TEST(mf_classic_value_block);
|
||||
MU_RUN_TEST(mf_classic_send_frame_test);
|
||||
MU_RUN_TEST(mf_classic_dict_test);
|
||||
MU_RUN_TEST(felica_read);
|
||||
MU_RUN_TEST(felica_read_auth);
|
||||
|
||||
MU_RUN_TEST(slix_file_with_capabilities_test);
|
||||
MU_RUN_TEST(slix_set_password_default_cap_correct_pass);
|
||||
|
@ -26,12 +26,6 @@ static void nfc_scene_info_on_enter_felica(NfcApp* instance) {
|
||||
widget_add_text_scroll_element(
|
||||
instance->widget, 0, 0, 128, 48, furi_string_get_cstr(temp_str));
|
||||
|
||||
widget_add_button_element(
|
||||
instance->widget,
|
||||
GuiButtonTypeRight,
|
||||
"More",
|
||||
nfc_protocol_support_common_widget_callback,
|
||||
instance);
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
@ -177,7 +171,7 @@ static bool nfc_scene_read_menu_on_event_felica(NfcApp* instance, SceneManagerEv
|
||||
}
|
||||
|
||||
const NfcProtocolSupportBase nfc_protocol_support_felica = {
|
||||
.features = NfcProtocolFeatureEmulateUid,
|
||||
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
|
||||
|
||||
.scene_info =
|
||||
{
|
||||
|
@ -41,11 +41,13 @@ env.Append(
|
||||
File("protocols/iso14443_4a/iso14443_4a_listener.h"),
|
||||
File("protocols/mf_ultralight/mf_ultralight_listener.h"),
|
||||
File("protocols/mf_classic/mf_classic_listener.h"),
|
||||
File("protocols/felica/felica_listener.h"),
|
||||
# Sync API
|
||||
File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"),
|
||||
File("protocols/mf_ultralight/mf_ultralight_poller_sync.h"),
|
||||
File("protocols/mf_classic/mf_classic_poller_sync.h"),
|
||||
File("protocols/st25tb/st25tb_poller_sync.h"),
|
||||
File("protocols/felica/felica_poller_sync.h"),
|
||||
# Misc
|
||||
File("helpers/nfc_util.h"),
|
||||
File("helpers/iso14443_crc.h"),
|
||||
|
@ -6,6 +6,8 @@
|
||||
#define FELICA_CRC_INIT (0x0000U)
|
||||
|
||||
uint16_t felica_crc_calculate(const uint8_t* data, size_t length) {
|
||||
furi_check(data);
|
||||
|
||||
uint16_t crc = FELICA_CRC_INIT;
|
||||
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
@ -24,6 +26,7 @@ uint16_t felica_crc_calculate(const uint8_t* data, size_t length) {
|
||||
}
|
||||
|
||||
void felica_crc_append(BitBuffer* buf) {
|
||||
furi_check(buf);
|
||||
const uint8_t* data = bit_buffer_get_data(buf);
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
|
||||
@ -32,6 +35,7 @@ void felica_crc_append(BitBuffer* buf) {
|
||||
}
|
||||
|
||||
bool felica_crc_check(const BitBuffer* buf) {
|
||||
furi_check(buf);
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
if(data_size <= FELICA_CRC_SIZE) return false;
|
||||
|
||||
@ -45,6 +49,7 @@ bool felica_crc_check(const BitBuffer* buf) {
|
||||
}
|
||||
|
||||
void felica_crc_trim(BitBuffer* buf) {
|
||||
furi_check(buf);
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
furi_assert(data_size > FELICA_CRC_SIZE);
|
||||
|
||||
|
@ -3,6 +3,9 @@
|
||||
#include <lib/nfc/nfc.h>
|
||||
#include <lib/nfc/helpers/iso14443_crc.h>
|
||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a.h>
|
||||
#include <lib/nfc/protocols/felica/felica.h>
|
||||
#include <lib/nfc/helpers/felica_crc.h>
|
||||
#include <lib/nfc/protocols/felica/felica_poller_sync.h>
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
@ -50,11 +53,31 @@ typedef struct {
|
||||
Iso14443_3aSelResp sel_resp[2];
|
||||
} Iso14443_3aColResData;
|
||||
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t polling_cmd;
|
||||
uint16_t system_code;
|
||||
uint8_t request_code;
|
||||
uint8_t time_slot;
|
||||
} FelicaPollingRequest;
|
||||
|
||||
typedef struct {
|
||||
uint8_t code;
|
||||
FelicaIDm idm;
|
||||
FelicaPMm pmm;
|
||||
} FelicaSensfResData;
|
||||
|
||||
typedef struct {
|
||||
uint16_t system_code;
|
||||
FelicaSensfResData sens_res;
|
||||
} FelicaPTMemory;
|
||||
|
||||
struct Nfc {
|
||||
NfcState state;
|
||||
|
||||
Iso14443_3aColResStatus col_res_status;
|
||||
Iso14443_3aColResData col_res_data;
|
||||
FelicaPTMemory pt_memory;
|
||||
bool software_col_res_required;
|
||||
|
||||
NfcEventCallback callback;
|
||||
@ -243,6 +266,21 @@ static void nfc_worker_listener_pass_col_res(Nfc* instance, uint8_t* rx_data, ui
|
||||
NfcEvent event = {.type = NfcEventTypeListenerActivated};
|
||||
instance->callback(event, instance->context);
|
||||
|
||||
processed = true;
|
||||
}
|
||||
} else if(rx_bits == 8 * 8) {
|
||||
FelicaPollingRequest* request = (FelicaPollingRequest*)rx_data;
|
||||
if(request->system_code == instance->pt_memory.system_code) {
|
||||
uint8_t response_size = sizeof(FelicaSensfResData) + 1;
|
||||
bit_buffer_reset(tx_buffer);
|
||||
bit_buffer_append_byte(tx_buffer, response_size);
|
||||
bit_buffer_append_bytes(
|
||||
tx_buffer, (uint8_t*)&instance->pt_memory.sens_res, sizeof(FelicaSensfResData));
|
||||
felica_crc_append(tx_buffer);
|
||||
nfc_listener_tx(instance, tx_buffer);
|
||||
instance->col_res_status = Iso14443_3aColResStatusDone;
|
||||
NfcEvent event = {.type = NfcEventTypeListenerActivated};
|
||||
instance->callback(event, instance->context);
|
||||
processed = true;
|
||||
}
|
||||
}
|
||||
@ -470,6 +508,12 @@ NfcError nfc_felica_listener_set_sensf_res_data(
|
||||
furi_assert(idm_len == 8);
|
||||
furi_assert(pmm_len == 8);
|
||||
|
||||
instance->pt_memory.system_code = 0xFFFF;
|
||||
instance->pt_memory.sens_res.code = 0x01;
|
||||
instance->software_col_res_required = true;
|
||||
memcpy(instance->pt_memory.sens_res.idm.data, idm, idm_len);
|
||||
memcpy(instance->pt_memory.sens_res.pmm.data, pmm, pmm_len);
|
||||
|
||||
return NfcErrorNone;
|
||||
}
|
||||
|
||||
|
@ -298,15 +298,33 @@ bool felica_check_mac(
|
||||
furi_check(blocks);
|
||||
furi_check(data);
|
||||
|
||||
uint8_t first_block[8];
|
||||
uint8_t mac[8];
|
||||
felica_prepare_first_block(FelicaMACTypeRead, blocks, block_count, first_block);
|
||||
felica_calculate_mac_read(ctx, session_key, rc, blocks, block_count, data, mac);
|
||||
|
||||
uint8_t mac_offset = FELICA_DATA_BLOCK_SIZE * (block_count - 1);
|
||||
uint8_t* mac_ptr = data + mac_offset;
|
||||
return !memcmp(mac, mac_ptr, 8);
|
||||
}
|
||||
|
||||
void felica_calculate_mac_read(
|
||||
mbedtls_des3_context* ctx,
|
||||
const uint8_t* session_key,
|
||||
const uint8_t* rc,
|
||||
const uint8_t* blocks,
|
||||
const uint8_t block_count,
|
||||
const uint8_t* data,
|
||||
uint8_t* mac) {
|
||||
furi_check(ctx);
|
||||
furi_check(session_key);
|
||||
furi_check(rc);
|
||||
furi_check(blocks);
|
||||
furi_check(data);
|
||||
furi_check(mac);
|
||||
|
||||
uint8_t first_block[8];
|
||||
felica_prepare_first_block(FelicaMACTypeRead, blocks, block_count, first_block);
|
||||
uint8_t data_size_without_mac = FELICA_DATA_BLOCK_SIZE * (block_count - 1);
|
||||
felica_calculate_mac(ctx, session_key, rc, first_block, data, data_size_without_mac, mac);
|
||||
|
||||
uint8_t* mac_ptr = data + data_size_without_mac;
|
||||
return !memcmp(mac, mac_ptr, 8);
|
||||
}
|
||||
|
||||
void felica_calculate_mac_write(
|
||||
|
@ -12,7 +12,13 @@ extern "C" {
|
||||
#define FELICA_PMM_SIZE (8U)
|
||||
#define FELICA_DATA_BLOCK_SIZE (16U)
|
||||
|
||||
#define FELICA_BLOCKS_TOTAL_COUNT (27U)
|
||||
#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06U)
|
||||
#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08U)
|
||||
|
||||
#define FELICA_SERVICE_RW_ACCESS (0x0009U)
|
||||
#define FELICA_SERVICE_RO_ACCESS (0x000BU)
|
||||
|
||||
#define FELICA_BLOCKS_TOTAL_COUNT (28U)
|
||||
#define FELICA_BLOCK_INDEX_REG (0x0EU)
|
||||
#define FELICA_BLOCK_INDEX_RC (0x80U)
|
||||
#define FELICA_BLOCK_INDEX_MAC (0x81U)
|
||||
@ -54,6 +60,10 @@ typedef enum {
|
||||
FelicaErrorTimeout,
|
||||
} FelicaError;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||
} FelicaBlockData;
|
||||
|
||||
/** @brief Separate type for card key block. Used in authentication process */
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||
@ -76,6 +86,22 @@ typedef struct {
|
||||
FelicaAuthenticationStatus auth_status; /**< Authentication status*/
|
||||
} FelicaAuthenticationContext;
|
||||
|
||||
/**
|
||||
* @brief Stucture for holding Felica session key which is calculated from rc and ck.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||
} FelicaSessionKey;
|
||||
|
||||
/**
|
||||
* @brief Structure used to hold authentication related fields.
|
||||
*/
|
||||
typedef struct {
|
||||
mbedtls_des3_context des_context; /**< Context for mbedtls des functions. */
|
||||
FelicaSessionKey session_key; /**< Calculated session key. */
|
||||
FelicaAuthenticationContext context; /**< Public auth context provided to upper levels. */
|
||||
} FelicaAuthentication;
|
||||
|
||||
/** @brief Felica ID block */
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_IDM_SIZE];
|
||||
@ -128,6 +154,51 @@ typedef struct {
|
||||
FelicaFSUnion data;
|
||||
} FelicaData;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint8_t code;
|
||||
FelicaIDm idm;
|
||||
uint8_t service_num;
|
||||
uint16_t service_code;
|
||||
uint8_t block_count;
|
||||
} FelicaCommandHeader;
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t response_code;
|
||||
FelicaIDm idm;
|
||||
uint8_t SF1;
|
||||
uint8_t SF2;
|
||||
} FelicaCommandResponseHeader;
|
||||
|
||||
typedef struct {
|
||||
uint8_t service_code : 4;
|
||||
uint8_t access_mode : 3;
|
||||
uint8_t length : 1;
|
||||
uint8_t block_number;
|
||||
} FelicaBlockListElement;
|
||||
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t response_code;
|
||||
FelicaIDm idm;
|
||||
uint8_t SF1;
|
||||
uint8_t SF2;
|
||||
uint8_t block_count;
|
||||
uint8_t data[];
|
||||
} FelicaPollerReadCommandResponse;
|
||||
|
||||
typedef struct {
|
||||
FelicaCommandResponseHeader header;
|
||||
uint8_t block_count;
|
||||
uint8_t data[];
|
||||
} FelicaListenerReadCommandResponse;
|
||||
|
||||
typedef FelicaCommandResponseHeader FelicaListenerWriteCommandResponse;
|
||||
|
||||
typedef FelicaCommandResponseHeader FelicaPollerWriteCommandResponse;
|
||||
|
||||
extern const NfcDeviceBase nfc_device_felica;
|
||||
|
||||
FelicaData* felica_alloc(void);
|
||||
@ -168,6 +239,15 @@ bool felica_check_mac(
|
||||
const uint8_t block_count,
|
||||
uint8_t* data);
|
||||
|
||||
void felica_calculate_mac_read(
|
||||
mbedtls_des3_context* ctx,
|
||||
const uint8_t* session_key,
|
||||
const uint8_t* rc,
|
||||
const uint8_t* blocks,
|
||||
const uint8_t block_count,
|
||||
const uint8_t* data,
|
||||
uint8_t* mac);
|
||||
|
||||
void felica_calculate_mac_write(
|
||||
mbedtls_des3_context* ctx,
|
||||
const uint8_t* session_key,
|
||||
|
@ -1,9 +1,14 @@
|
||||
#include "felica_listener_i.h"
|
||||
|
||||
#include "nfc/protocols/nfc_listener_base.h"
|
||||
#include <nfc/helpers/felica_crc.h>
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
#define FELICA_LISTENER_MAX_BUFFER_SIZE (64)
|
||||
#define TAG "Felica"
|
||||
#define FELICA_LISTENER_MAX_BUFFER_SIZE (128)
|
||||
#define FELICA_LISTENER_RESPONSE_CODE_READ (0x07)
|
||||
#define FELICA_LISTENER_RESPONSE_CODE_WRITE (0x09)
|
||||
|
||||
#define TAG "FelicaListener"
|
||||
|
||||
FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) {
|
||||
furi_assert(nfc);
|
||||
@ -15,8 +20,11 @@ FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) {
|
||||
instance->tx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE);
|
||||
instance->rx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE);
|
||||
|
||||
mbedtls_des3_init(&instance->auth.des_context);
|
||||
nfc_set_fdt_listen_fc(instance->nfc, FELICA_FDT_LISTEN_FC);
|
||||
|
||||
memcpy(instance->mc_shadow.data, instance->data->data.fs.mc.data, FELICA_DATA_BLOCK_SIZE);
|
||||
instance->data->data.fs.state.data[0] = 0;
|
||||
nfc_config(instance->nfc, NfcModeListener, NfcTechFelica);
|
||||
nfc_felica_listener_set_sensf_res_data(
|
||||
nfc, data->idm.data, sizeof(data->idm), data->pmm.data, sizeof(data->pmm));
|
||||
@ -49,6 +57,99 @@ const FelicaData* felica_listener_get_data(const FelicaListener* instance) {
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
static FelicaError felica_listener_command_handler_read(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerGenericRequest* const generic_request) {
|
||||
const FelicaListenerReadRequest* request = (FelicaListenerReadRequest*)generic_request;
|
||||
FURI_LOG_D(TAG, "Read cmd");
|
||||
|
||||
FelicaListenerReadCommandResponse* resp = malloc(
|
||||
sizeof(FelicaCommandResponseHeader) + 1 +
|
||||
FELICA_LISTENER_READ_BLOCK_COUNT_MAX * FELICA_DATA_BLOCK_SIZE);
|
||||
|
||||
resp->header.response_code = FELICA_LISTENER_RESPONSE_CODE_READ;
|
||||
resp->header.idm = request->base.header.idm;
|
||||
resp->header.length = sizeof(FelicaCommandResponseHeader);
|
||||
|
||||
if(felica_listener_validate_read_request_and_set_sf(instance, request, &resp->header)) {
|
||||
resp->block_count = request->base.header.block_count;
|
||||
resp->header.length++;
|
||||
} else {
|
||||
resp->block_count = 0;
|
||||
}
|
||||
|
||||
instance->mac_calc_start = 0;
|
||||
memset(instance->requested_blocks, 0, sizeof(instance->requested_blocks));
|
||||
const FelicaBlockListElement* item =
|
||||
felica_listener_block_list_item_get_first(instance, request);
|
||||
for(uint8_t i = 0; i < resp->block_count; i++) {
|
||||
instance->requested_blocks[i] = item->block_number;
|
||||
FelicaCommanReadBlockHandler handler =
|
||||
felica_listener_get_read_block_handler(item->block_number);
|
||||
|
||||
handler(instance, item->block_number, i, resp);
|
||||
|
||||
item = felica_listener_block_list_item_get_next(instance, item);
|
||||
}
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)resp, resp->header.length);
|
||||
free(resp);
|
||||
|
||||
return felica_listener_frame_exchange(instance, instance->tx_buffer);
|
||||
}
|
||||
|
||||
static FelicaError felica_listener_command_handler_write(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerGenericRequest* const generic_request) {
|
||||
FURI_LOG_D(TAG, "Write cmd");
|
||||
|
||||
const FelicaListenerWriteRequest* request = (FelicaListenerWriteRequest*)generic_request;
|
||||
const FelicaListenerWriteBlockData* data_ptr =
|
||||
felica_listener_get_write_request_data_pointer(instance, generic_request);
|
||||
|
||||
FelicaListenerWriteCommandResponse* resp = malloc(sizeof(FelicaListenerWriteCommandResponse));
|
||||
|
||||
resp->response_code = FELICA_LISTENER_RESPONSE_CODE_WRITE;
|
||||
resp->idm = request->base.header.idm;
|
||||
resp->length = sizeof(FelicaListenerWriteCommandResponse);
|
||||
|
||||
if(felica_listener_validate_write_request_and_set_sf(instance, request, data_ptr, resp)) {
|
||||
const FelicaBlockListElement* item =
|
||||
felica_listener_block_list_item_get_first(instance, request);
|
||||
for(uint8_t i = 0; i < request->base.header.block_count; i++) {
|
||||
FelicaCommandWriteBlockHandler handler =
|
||||
felica_listener_get_write_block_handler(item->block_number);
|
||||
|
||||
handler(instance, item->block_number, &data_ptr->blocks[i]);
|
||||
|
||||
item = felica_listener_block_list_item_get_next(instance, item);
|
||||
}
|
||||
felica_wcnt_increment(instance->data);
|
||||
}
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)resp, resp->length);
|
||||
free(resp);
|
||||
|
||||
return felica_listener_frame_exchange(instance, instance->tx_buffer);
|
||||
}
|
||||
|
||||
static FelicaError felica_listener_process_request(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerGenericRequest* generic_request) {
|
||||
const uint8_t cmd_code = generic_request->header.code;
|
||||
switch(cmd_code) {
|
||||
case FELICA_CMD_READ_WITHOUT_ENCRYPTION:
|
||||
return felica_listener_command_handler_read(instance, generic_request);
|
||||
case FELICA_CMD_WRITE_WITHOUT_ENCRYPTION:
|
||||
return felica_listener_command_handler_write(instance, generic_request);
|
||||
default:
|
||||
FURI_LOG_E(TAG, "FeliCa incorrect command");
|
||||
return FelicaErrorNotPresent;
|
||||
}
|
||||
}
|
||||
|
||||
NfcCommand felica_listener_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||
@ -58,14 +159,44 @@ NfcCommand felica_listener_run(NfcGenericEvent event, void* context) {
|
||||
NfcEvent* nfc_event = event.event_data;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(nfc_event->type == NfcEventTypeListenerActivated) {
|
||||
if(nfc_event->type == NfcEventTypeFieldOn) {
|
||||
FURI_LOG_D(TAG, "Field On");
|
||||
} else if(nfc_event->type == NfcEventTypeListenerActivated) {
|
||||
instance->state = Felica_ListenerStateActivated;
|
||||
FURI_LOG_D(TAG, "Activated");
|
||||
} else if(nfc_event->type == NfcEventTypeFieldOff) {
|
||||
instance->state = Felica_ListenerStateIdle;
|
||||
FURI_LOG_D(TAG, "Field Off");
|
||||
felica_listener_reset(instance);
|
||||
} else if(nfc_event->type == NfcEventTypeRxEnd) {
|
||||
FURI_LOG_D(TAG, "Rx Done");
|
||||
do {
|
||||
if(!felica_crc_check(nfc_event->data.buffer)) {
|
||||
FURI_LOG_E(TAG, "Wrong CRC");
|
||||
break;
|
||||
}
|
||||
|
||||
FelicaListenerGenericRequest* request =
|
||||
(FelicaListenerGenericRequest*)bit_buffer_get_data(nfc_event->data.buffer);
|
||||
|
||||
uint8_t size = bit_buffer_get_size_bytes(nfc_event->data.buffer) - 2;
|
||||
if((request->length != size) ||
|
||||
(!felica_listener_check_block_list_size(instance, request))) {
|
||||
FURI_LOG_E(TAG, "Wrong request length");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!felica_listener_check_idm(instance, &request->header.idm)) {
|
||||
FURI_LOG_E(TAG, "Wrong IDm");
|
||||
break;
|
||||
}
|
||||
|
||||
FelicaError error = felica_listener_process_request(instance, request);
|
||||
if(error != FelicaErrorNone) {
|
||||
FURI_LOG_E(TAG, "Processing error: %2X", error);
|
||||
}
|
||||
} while(false);
|
||||
bit_buffer_reset(nfc_event->data.buffer);
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
738
lib/nfc/protocols/felica/felica_listener_i.c
Normal file
738
lib/nfc/protocols/felica/felica_listener_i.c
Normal file
@ -0,0 +1,738 @@
|
||||
#include "felica_listener_i.h"
|
||||
|
||||
#include <nfc/helpers/felica_crc.h>
|
||||
|
||||
#define FELICA_WCNT_MC2_FF_MAX_VALUE (0x00FFFFFFU)
|
||||
#define FELICA_WCNT_MC2_00_MAX_VALUE (0x00FFFE00U)
|
||||
#define FELICA_WCNT_MC2_00_WARNING_BEGIN_VALUE (0x00001027U)
|
||||
#define FELICA_WCNT_MC2_00_WARNING_END_VALUE (0x00FFFDFFU)
|
||||
|
||||
#define FELICA_MC_SP_REG_ALL_RW_BYTES_0_1 (0U)
|
||||
#define FELICA_MC_ALL_BYTE (2U)
|
||||
#define FELICA_MC_SYS_OP (3U)
|
||||
#define FELICA_MC_RF_PRM (4U)
|
||||
#define FELICA_MC_CKCKV_W_MAC_A (5U)
|
||||
#define FELICA_MC_SP_REG_R_RESTR_BYTES_6_7 (6U)
|
||||
#define FELICA_MC_SP_REG_W_RESTR_BYTES_8_9 (8U)
|
||||
#define FELICA_MC_SP_REG_W_MAC_A_BYTES_10_11 (10U)
|
||||
#define FELICA_MC_STATE_W_MAC_A (12U)
|
||||
#define FELICA_MC_RESERVED_13 (13U)
|
||||
#define FELICA_MC_RESERVED_14 (14U)
|
||||
#define FELICA_MC_RESERVED_15 (15U)
|
||||
|
||||
#define FELICA_MC_BYTE_GET(data, byte) (data->data.fs.mc.data[byte])
|
||||
#define FELICA_SYSTEM_BLOCK_RO_ACCESS(data) (FELICA_MC_BYTE_GET(data, FELICA_MC_ALL_BYTE) == 0x00)
|
||||
#define FELICA_SYSTEM_BLOCK_RW_ACCESS(data) (FELICA_MC_BYTE_GET(data, FELICA_MC_ALL_BYTE) == 0xFF)
|
||||
|
||||
#define FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN ((uint8_t)sizeof(FelicaBlockListElement))
|
||||
#define FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(item) \
|
||||
((item->length == 1) ? FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN : \
|
||||
(FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN + 1))
|
||||
|
||||
static uint32_t felica_wcnt_get_max_value(const FelicaData* data) {
|
||||
if(!FELICA_SYSTEM_BLOCK_RO_ACCESS(data) && !FELICA_SYSTEM_BLOCK_RW_ACCESS(data)) {
|
||||
furi_crash("MC[2] reserved values are forbidden");
|
||||
}
|
||||
return (FELICA_SYSTEM_BLOCK_RW_ACCESS(data)) ? FELICA_WCNT_MC2_FF_MAX_VALUE :
|
||||
FELICA_WCNT_MC2_00_MAX_VALUE;
|
||||
}
|
||||
|
||||
static bool felica_wcnt_check_warning_boundary(const FelicaData* data) {
|
||||
const uint32_t* wcnt_ptr = (uint32_t*)data->data.fs.wcnt.data;
|
||||
return (
|
||||
FELICA_SYSTEM_BLOCK_RO_ACCESS(data) &&
|
||||
((*wcnt_ptr > FELICA_WCNT_MC2_00_WARNING_BEGIN_VALUE) &&
|
||||
(*wcnt_ptr < FELICA_WCNT_MC2_00_WARNING_END_VALUE)));
|
||||
}
|
||||
|
||||
static bool felica_wcnt_check_error_boundary(const FelicaData* data) {
|
||||
const uint32_t wcnt_max = felica_wcnt_get_max_value(data);
|
||||
const uint32_t* wcnt_ptr = (const uint32_t*)data->data.fs.wcnt.data;
|
||||
return *wcnt_ptr != wcnt_max;
|
||||
}
|
||||
|
||||
void felica_wcnt_increment(FelicaData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
const uint32_t wcnt_max = felica_wcnt_get_max_value(data);
|
||||
uint32_t* wcnt_ptr = (uint32_t*)data->data.fs.wcnt.data;
|
||||
if(*wcnt_ptr < wcnt_max) {
|
||||
*wcnt_ptr += 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void felica_wcnt_post_process(FelicaData* data) {
|
||||
uint32_t* wcnt_ptr = (uint32_t*)data->data.fs.wcnt.data;
|
||||
|
||||
if(FELICA_SYSTEM_BLOCK_RO_ACCESS(data) && (*wcnt_ptr > FELICA_WCNT_MC2_00_MAX_VALUE)) {
|
||||
*wcnt_ptr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
felica_listener_block_list_item_validate_block_number(const FelicaBlockListElement* item) {
|
||||
bool valid = true;
|
||||
if(item->length == 0) {
|
||||
uint8_t D2_block_number = *(&item->block_number + 1);
|
||||
valid = D2_block_number == 0;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
static const FelicaBlockListElement* felica_listener_block_list_iterate(
|
||||
FelicaListener* instance,
|
||||
const FelicaBlockListElement* prev_item,
|
||||
const uint8_t item_size) {
|
||||
FelicaBlockListElement* next_item = NULL;
|
||||
if(instance->request_size_buf >= FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN) {
|
||||
next_item = (FelicaBlockListElement*)((uint8_t*)prev_item + item_size);
|
||||
|
||||
uint8_t next_item_size = FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(next_item);
|
||||
|
||||
if(instance->request_size_buf < next_item_size) {
|
||||
next_item = NULL;
|
||||
instance->request_size_buf = 0;
|
||||
} else {
|
||||
instance->request_size_buf -= next_item_size;
|
||||
}
|
||||
}
|
||||
|
||||
return next_item;
|
||||
}
|
||||
|
||||
const FelicaBlockListElement* felica_listener_block_list_item_get_first(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerRequest* request) {
|
||||
furi_assert(instance);
|
||||
furi_assert(request);
|
||||
|
||||
instance->request_size_buf = request->base.length - sizeof(FelicaListenerGenericRequest);
|
||||
return felica_listener_block_list_iterate(instance, request->list, 0);
|
||||
}
|
||||
|
||||
const FelicaBlockListElement* felica_listener_block_list_item_get_next(
|
||||
FelicaListener* instance,
|
||||
const FelicaBlockListElement* prev_item) {
|
||||
furi_assert(instance);
|
||||
furi_assert(prev_item);
|
||||
|
||||
return felica_listener_block_list_iterate(
|
||||
instance, prev_item, FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(prev_item));
|
||||
}
|
||||
|
||||
const FelicaListenerWriteBlockData* felica_listener_get_write_request_data_pointer(
|
||||
const FelicaListener* const instance,
|
||||
const FelicaListenerGenericRequest* const generic_request) {
|
||||
furi_assert(instance);
|
||||
furi_assert(generic_request);
|
||||
|
||||
return (const FelicaListenerWriteBlockData*)((uint8_t*)generic_request +
|
||||
sizeof(FelicaListenerGenericRequest) +
|
||||
instance->block_list_size);
|
||||
}
|
||||
|
||||
static bool felica_listener_check_write_request_data_size(
|
||||
const FelicaListener* const instance,
|
||||
const FelicaListenerRequest* request,
|
||||
const uint8_t fact_item_cnt) {
|
||||
uint8_t possible_data_size = fact_item_cnt * FELICA_DATA_BLOCK_SIZE;
|
||||
uint8_t fact_data_size =
|
||||
request->base.length - sizeof(FelicaListenerGenericRequest) - instance->block_list_size;
|
||||
return (possible_data_size <= fact_data_size);
|
||||
}
|
||||
|
||||
static bool felica_listener_test_block_list_size_bounds(const FelicaListenerGenericRequest* req) {
|
||||
bool valid = false;
|
||||
if(req->header.code == FELICA_CMD_READ_WITHOUT_ENCRYPTION) {
|
||||
valid =
|
||||
(req->header.block_count >= FELICA_LISTENER_READ_BLOCK_COUNT_MIN &&
|
||||
req->header.block_count <= FELICA_LISTENER_READ_BLOCK_COUNT_MAX);
|
||||
} else if(req->header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) {
|
||||
valid =
|
||||
(req->header.block_count >= FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN &&
|
||||
req->header.block_count <= FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
static uint8_t felica_listener_get_block_list_item_count_size(
|
||||
FelicaListener* instance,
|
||||
FelicaListenerRequest* request) {
|
||||
uint8_t list_size = 0;
|
||||
uint8_t item_cnt = 0;
|
||||
|
||||
uint8_t max_item_cnt = (request->base.header.code == FELICA_CMD_READ_WITHOUT_ENCRYPTION) ?
|
||||
FELICA_LISTENER_READ_BLOCK_COUNT_MAX :
|
||||
FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX;
|
||||
|
||||
const FelicaBlockListElement* item =
|
||||
felica_listener_block_list_item_get_first(instance, request);
|
||||
|
||||
while((item != NULL) && (item_cnt < max_item_cnt)) {
|
||||
item_cnt++;
|
||||
|
||||
if(request->base.header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) {
|
||||
if(instance->request_size_buf >= FELICA_DATA_BLOCK_SIZE) {
|
||||
instance->request_size_buf -= FELICA_DATA_BLOCK_SIZE;
|
||||
} else {
|
||||
instance->request_size_buf = 0;
|
||||
}
|
||||
}
|
||||
|
||||
list_size += FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(item);
|
||||
item = felica_listener_block_list_item_get_next(instance, item);
|
||||
}
|
||||
|
||||
instance->block_list_size = list_size;
|
||||
return item_cnt;
|
||||
}
|
||||
|
||||
bool felica_listener_check_block_list_size(
|
||||
FelicaListener* instance,
|
||||
FelicaListenerGenericRequest* req) {
|
||||
furi_assert(instance);
|
||||
furi_assert(req);
|
||||
|
||||
FelicaListenerRequest* request = (FelicaListenerRequest*)req;
|
||||
bool valid = true;
|
||||
|
||||
uint8_t item_cnt = felica_listener_get_block_list_item_count_size(instance, request);
|
||||
valid = (item_cnt > 0);
|
||||
|
||||
if(felica_listener_test_block_list_size_bounds(req) &&
|
||||
(item_cnt < request->base.header.block_count)) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if(valid && request->base.header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) {
|
||||
valid &= felica_listener_check_write_request_data_size(instance, request, item_cnt);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool felica_listener_check_idm(const FelicaListener* instance, const FelicaIDm* request_idm) {
|
||||
furi_assert(instance);
|
||||
furi_assert(request_idm);
|
||||
|
||||
const FelicaIDm* idm = &instance->data->idm;
|
||||
return memcmp(idm->data, request_idm->data, 8) == 0;
|
||||
}
|
||||
|
||||
void felica_listener_reset(FelicaListener* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->auth.context.auth_status.internal = false;
|
||||
instance->auth.context.auth_status.external = false;
|
||||
instance->data->data.fs.state.data[0] = 0;
|
||||
instance->rc_written = false;
|
||||
memset(instance->auth.session_key.data, 0, FELICA_DATA_BLOCK_SIZE);
|
||||
|
||||
memcpy(instance->data->data.fs.mc.data, instance->mc_shadow.data, FELICA_DATA_BLOCK_SIZE);
|
||||
|
||||
felica_wcnt_post_process(instance->data);
|
||||
}
|
||||
|
||||
static uint8_t felica_listener_get_block_index(uint8_t number) {
|
||||
if(number >= FELICA_BLOCK_INDEX_RC && number < FELICA_BLOCK_INDEX_WCNT) {
|
||||
return number - 0x80 + FELICA_BLOCK_INDEX_REG + 1;
|
||||
} else if(number >= FELICA_BLOCK_INDEX_WCNT && number <= FELICA_BLOCK_INDEX_STATE) {
|
||||
return number - 0x90 + 9 + FELICA_BLOCK_INDEX_REG + 1;
|
||||
} else if(number == FELICA_BLOCK_INDEX_CRC_CHECK) {
|
||||
return number - 0x90 + 9 + FELICA_BLOCK_INDEX_REG + 2;
|
||||
}
|
||||
|
||||
return number;
|
||||
}
|
||||
|
||||
static bool felica_block_exists(uint8_t number) {
|
||||
return !(
|
||||
(number > FELICA_BLOCK_INDEX_REG && number < FELICA_BLOCK_INDEX_RC) ||
|
||||
(number > FELICA_BLOCK_INDEX_MC && number < FELICA_BLOCK_INDEX_WCNT) ||
|
||||
(number > FELICA_BLOCK_INDEX_STATE && number < FELICA_BLOCK_INDEX_CRC_CHECK) ||
|
||||
(number > FELICA_BLOCK_INDEX_CRC_CHECK));
|
||||
}
|
||||
|
||||
static bool
|
||||
felica_get_mc_bit(const FelicaListener* instance, uint8_t byte_index, uint8_t bit_number) {
|
||||
uint8_t* mc = instance->data->data.fs.mc.data;
|
||||
|
||||
uint16_t flags = *((uint16_t*)&mc[byte_index]);
|
||||
bool bit = (flags & (1 << bit_number)) != 0;
|
||||
return bit;
|
||||
}
|
||||
|
||||
static bool felica_block_requires_auth(
|
||||
const FelicaListener* instance,
|
||||
uint8_t command,
|
||||
uint8_t block_number) {
|
||||
bool need_auth = false;
|
||||
if(block_number <= FELICA_BLOCK_INDEX_REG) {
|
||||
uint8_t mc_flag_index = command;
|
||||
need_auth = felica_get_mc_bit(instance, mc_flag_index, block_number);
|
||||
}
|
||||
return need_auth;
|
||||
}
|
||||
|
||||
static bool felica_block_is_readonly(const FelicaListener* instance, uint8_t block_number) {
|
||||
bool readonly = false;
|
||||
if(block_number <= FELICA_BLOCK_INDEX_REG) {
|
||||
readonly = !felica_get_mc_bit(instance, FELICA_MC_SP_REG_ALL_RW_BYTES_0_1, block_number);
|
||||
} else if(
|
||||
((block_number == FELICA_BLOCK_INDEX_ID) || (block_number == FELICA_BLOCK_INDEX_SER_C)) ||
|
||||
(block_number >= FELICA_BLOCK_INDEX_CKV && block_number <= FELICA_BLOCK_INDEX_CK)) {
|
||||
const FelicaData* data = instance->data;
|
||||
readonly = FELICA_SYSTEM_BLOCK_RO_ACCESS(data);
|
||||
} else if(
|
||||
(block_number == FELICA_BLOCK_INDEX_MAC) || (block_number == FELICA_BLOCK_INDEX_WCNT) ||
|
||||
(block_number == FELICA_BLOCK_INDEX_D_ID) ||
|
||||
(block_number == FELICA_BLOCK_INDEX_CRC_CHECK)) {
|
||||
readonly = true;
|
||||
}
|
||||
return readonly;
|
||||
}
|
||||
|
||||
static bool felica_block_requires_mac(const FelicaListener* instance, uint8_t block_number) {
|
||||
bool need_mac = false;
|
||||
if(block_number <= FELICA_BLOCK_INDEX_REG) {
|
||||
need_mac = felica_get_mc_bit(instance, FELICA_MC_SP_REG_W_MAC_A_BYTES_10_11, block_number);
|
||||
} else if(block_number == FELICA_BLOCK_INDEX_CK || block_number == FELICA_BLOCK_INDEX_CKV) {
|
||||
need_mac = felica_get_mc_bit(instance, FELICA_MC_CKCKV_W_MAC_A, 0);
|
||||
} else if(block_number == FELICA_BLOCK_INDEX_STATE) {
|
||||
need_mac = felica_get_mc_bit(instance, FELICA_MC_STATE_W_MAC_A, 0);
|
||||
}
|
||||
return need_mac;
|
||||
}
|
||||
|
||||
static void felica_handler_read_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const uint8_t resp_data_index,
|
||||
FelicaListenerReadCommandResponse* response) {
|
||||
uint8_t num = felica_listener_get_block_index(block_number);
|
||||
memcpy(
|
||||
&response->data[resp_data_index * FELICA_DATA_BLOCK_SIZE],
|
||||
&instance->data->data.dump[num * (FELICA_DATA_BLOCK_SIZE + 2) + 2],
|
||||
FELICA_DATA_BLOCK_SIZE);
|
||||
response->header.length += FELICA_DATA_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
static void felica_handler_read_all_zeros(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const uint8_t resp_data_index,
|
||||
FelicaListenerReadCommandResponse* response) {
|
||||
UNUSED(instance);
|
||||
UNUSED(block_number);
|
||||
|
||||
memset(&response->data[resp_data_index * FELICA_DATA_BLOCK_SIZE], 0, FELICA_DATA_BLOCK_SIZE);
|
||||
response->header.length += FELICA_DATA_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
static void felica_handler_read_mac_a_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const uint8_t resp_data_index,
|
||||
FelicaListenerReadCommandResponse* response) {
|
||||
if((resp_data_index != response->block_count - 1) || (resp_data_index == 0)) {
|
||||
felica_handler_read_all_zeros(instance, block_number, resp_data_index, response);
|
||||
instance->mac_calc_start = resp_data_index + 1;
|
||||
} else {
|
||||
felica_calculate_mac_read(
|
||||
&instance->auth.des_context,
|
||||
instance->auth.session_key.data,
|
||||
instance->data->data.fs.rc.data,
|
||||
&instance->requested_blocks[instance->mac_calc_start],
|
||||
response->block_count - instance->mac_calc_start,
|
||||
&response->data[instance->mac_calc_start * FELICA_DATA_BLOCK_SIZE],
|
||||
instance->data->data.fs.mac_a.data);
|
||||
felica_handler_read_block(instance, block_number, resp_data_index, response);
|
||||
}
|
||||
}
|
||||
|
||||
FelicaCommanReadBlockHandler felica_listener_get_read_block_handler(const uint8_t block_number) {
|
||||
FelicaCommanReadBlockHandler handler = felica_handler_read_block;
|
||||
|
||||
if(block_number == FELICA_BLOCK_INDEX_RC || block_number == FELICA_BLOCK_INDEX_CK) {
|
||||
handler = felica_handler_read_all_zeros;
|
||||
} else if(block_number == FELICA_BLOCK_INDEX_MAC_A) {
|
||||
handler = felica_handler_read_mac_a_block;
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
static bool felica_validate_read_block_list(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerReadRequest* const request,
|
||||
FelicaCommandResponseHeader* response) {
|
||||
uint8_t mac_a_pos = 0;
|
||||
bool mac_a_present = false, mac_present = false;
|
||||
|
||||
const FelicaBlockListElement* item =
|
||||
felica_listener_block_list_item_get_first(instance, request);
|
||||
for(uint8_t i = 0; i < request->base.header.block_count; i++) {
|
||||
if(item->service_code != 0) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xA3;
|
||||
return false;
|
||||
} else if(item->access_mode != 0) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xA7;
|
||||
return false;
|
||||
} else if(
|
||||
!felica_listener_block_list_item_validate_block_number(item) ||
|
||||
!felica_block_exists(item->block_number) ||
|
||||
(felica_block_is_readonly(instance, item->block_number) &&
|
||||
request->base.header.service_code != FELICA_SERVICE_RO_ACCESS)) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xA8;
|
||||
return false;
|
||||
} else if(item->block_number == FELICA_BLOCK_INDEX_MAC) {
|
||||
mac_present = true;
|
||||
} else if(item->block_number == FELICA_BLOCK_INDEX_MAC_A) {
|
||||
if(!instance->rc_written) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xB2;
|
||||
return false;
|
||||
}
|
||||
if(!mac_a_present) {
|
||||
mac_a_present = true;
|
||||
mac_a_pos = i;
|
||||
}
|
||||
} else if(
|
||||
felica_block_requires_auth(instance, request->base.header.code, item->block_number) &&
|
||||
!instance->auth.context.auth_status.external) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xB1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(mac_a_present && mac_present) {
|
||||
response->SF1 = (1 << mac_a_pos);
|
||||
response->SF2 = 0xB0;
|
||||
return false;
|
||||
}
|
||||
|
||||
item = felica_listener_block_list_item_get_next(instance, item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool felica_listener_validate_read_request_and_set_sf(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerReadRequest* const request,
|
||||
FelicaCommandResponseHeader* resp_header) {
|
||||
furi_assert(instance);
|
||||
furi_assert(request);
|
||||
furi_assert(resp_header);
|
||||
|
||||
bool valid = false;
|
||||
do {
|
||||
if(request->base.header.service_num != 0x01) {
|
||||
resp_header->SF1 = 0xFF;
|
||||
resp_header->SF2 = 0xA1;
|
||||
break;
|
||||
}
|
||||
if((request->base.header.code == FELICA_CMD_READ_WITHOUT_ENCRYPTION) &&
|
||||
(request->base.header.block_count < FELICA_LISTENER_READ_BLOCK_COUNT_MIN ||
|
||||
request->base.header.block_count > FELICA_LISTENER_READ_BLOCK_COUNT_MAX)) {
|
||||
resp_header->SF1 = 0xFF;
|
||||
resp_header->SF2 = 0xA2;
|
||||
break;
|
||||
}
|
||||
|
||||
if(request->base.header.service_code != FELICA_SERVICE_RO_ACCESS &&
|
||||
request->base.header.service_code != FELICA_SERVICE_RW_ACCESS) {
|
||||
resp_header->SF1 = 0x01;
|
||||
resp_header->SF2 = 0xA6;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!felica_validate_read_block_list(instance, request, resp_header)) break;
|
||||
|
||||
resp_header->SF1 = 0x00;
|
||||
resp_header->SF2 = 0x00;
|
||||
valid = true;
|
||||
} while(false);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
static bool felica_validate_write_block_list(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerWriteRequest* const request,
|
||||
const FelicaListenerWriteBlockData* const data,
|
||||
FelicaListenerWriteCommandResponse* response) {
|
||||
const FelicaBlockListElement* items[FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX] = {};
|
||||
items[0] = felica_listener_block_list_item_get_first(instance, request);
|
||||
items[1] = felica_listener_block_list_item_get_next(instance, items[0]);
|
||||
|
||||
bool write_with_mac = false;
|
||||
if(request->base.header.block_count == FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX &&
|
||||
items[1]->block_number == FELICA_BLOCK_INDEX_MAC_A) {
|
||||
write_with_mac = true;
|
||||
} else if(
|
||||
request->base.header.block_count < FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN ||
|
||||
request->base.header.block_count > FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX) {
|
||||
response->SF1 = 0x02;
|
||||
response->SF2 = 0xB2;
|
||||
return false;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < request->base.header.block_count; i++) {
|
||||
const FelicaBlockListElement* item = items[i];
|
||||
if(!felica_listener_block_list_item_validate_block_number(item) ||
|
||||
!felica_block_exists(item->block_number) ||
|
||||
((i == 1) && (item->block_number != FELICA_BLOCK_INDEX_MAC_A))) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xA8;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(felica_block_is_readonly(instance, item->block_number) ||
|
||||
(felica_block_requires_mac(instance, item->block_number) && !write_with_mac) ||
|
||||
((i == 0) && (item->block_number == FELICA_BLOCK_INDEX_MAC_A))) {
|
||||
response->SF1 = 0x01;
|
||||
response->SF2 = 0xA8;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(item->service_code != 0) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xA3;
|
||||
return false;
|
||||
} else if(item->access_mode != 0) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xA7;
|
||||
return false;
|
||||
} else if((i == 1) && (item->block_number == FELICA_BLOCK_INDEX_MAC_A)) {
|
||||
uint8_t calculated_mac[8];
|
||||
felica_calculate_mac_write(
|
||||
&instance->auth.des_context,
|
||||
instance->auth.session_key.data,
|
||||
instance->data->data.fs.rc.data,
|
||||
data->blocks[1].data + 8,
|
||||
data->blocks[0].data,
|
||||
calculated_mac);
|
||||
|
||||
if(!instance->rc_written || (memcmp(calculated_mac, data->blocks[1].data, 8) != 0) ||
|
||||
!felica_wcnt_check_error_boundary(instance->data)) {
|
||||
response->SF1 = 0x02;
|
||||
response->SF2 = 0xB2;
|
||||
return false;
|
||||
}
|
||||
} else if(
|
||||
felica_block_requires_auth(instance, request->base.header.code, item->block_number) &&
|
||||
!instance->auth.context.auth_status.external) {
|
||||
response->SF1 = 0x01;
|
||||
response->SF2 = 0xB1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool felica_listener_validate_write_request_and_set_sf(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerWriteRequest* const request,
|
||||
const FelicaListenerWriteBlockData* const data,
|
||||
FelicaListenerWriteCommandResponse* response) {
|
||||
furi_assert(instance);
|
||||
furi_assert(request);
|
||||
furi_assert(data);
|
||||
furi_assert(response);
|
||||
|
||||
bool valid = false;
|
||||
do {
|
||||
if(request->base.header.service_num != 0x01) {
|
||||
response->SF1 = 0xFF;
|
||||
response->SF2 = 0xA1;
|
||||
break;
|
||||
}
|
||||
|
||||
if((request->base.header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) &&
|
||||
(request->base.header.block_count < 0x01 || request->base.header.block_count > 0x02)) {
|
||||
response->SF1 = 0xFF;
|
||||
response->SF2 = 0xA2;
|
||||
break;
|
||||
}
|
||||
|
||||
if(request->base.header.service_code != FELICA_SERVICE_RW_ACCESS) {
|
||||
response->SF1 = 0x01;
|
||||
response->SF2 = 0xA6;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!felica_validate_write_block_list(instance, request, data, response)) break;
|
||||
|
||||
if(felica_wcnt_check_warning_boundary(instance->data)) {
|
||||
response->SF1 = 0xFF;
|
||||
response->SF2 = 0x71;
|
||||
} else {
|
||||
response->SF1 = 0x00;
|
||||
response->SF2 = 0x00;
|
||||
}
|
||||
valid = true;
|
||||
} while(false);
|
||||
return valid;
|
||||
}
|
||||
|
||||
static void felica_handler_write_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block) {
|
||||
uint8_t num = felica_listener_get_block_index(block_number);
|
||||
|
||||
memcpy(
|
||||
&instance->data->data.dump[num * (FELICA_DATA_BLOCK_SIZE + 2) + 2],
|
||||
data_block->data,
|
||||
FELICA_DATA_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
static void felica_handler_write_rc_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block) {
|
||||
felica_handler_write_block(instance, block_number, data_block);
|
||||
|
||||
felica_calculate_session_key(
|
||||
&instance->auth.des_context,
|
||||
instance->data->data.fs.ck.data,
|
||||
instance->data->data.fs.rc.data,
|
||||
instance->auth.session_key.data);
|
||||
instance->rc_written = true;
|
||||
}
|
||||
|
||||
static void felica_handler_write_reg_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block) {
|
||||
UNUSED(block_number);
|
||||
|
||||
typedef struct {
|
||||
uint32_t A;
|
||||
uint32_t B;
|
||||
uint64_t C;
|
||||
} FELICA_REG_BLOCK;
|
||||
|
||||
FELICA_REG_BLOCK* Reg = (FELICA_REG_BLOCK*)instance->data->data.fs.reg.data;
|
||||
FELICA_REG_BLOCK* RegNew = (FELICA_REG_BLOCK*)data_block->data;
|
||||
|
||||
if(Reg->A >= RegNew->A) {
|
||||
Reg->A = RegNew->A;
|
||||
}
|
||||
|
||||
if(Reg->B >= RegNew->B) {
|
||||
Reg->B = RegNew->B;
|
||||
}
|
||||
|
||||
if((Reg->A >= RegNew->A) && (Reg->B >= RegNew->B)) {
|
||||
Reg->C = RegNew->C;
|
||||
}
|
||||
}
|
||||
|
||||
static void felica_handler_write_mc_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block) {
|
||||
UNUSED(block_number);
|
||||
|
||||
bool mc_bits_permission = felica_get_mc_bit(instance, FELICA_MC_SP_REG_ALL_RW_BYTES_0_1, 15);
|
||||
const FelicaData* data = instance->data;
|
||||
bool mc_system_block_permission = FELICA_SYSTEM_BLOCK_RW_ACCESS(data);
|
||||
for(uint8_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) {
|
||||
if((i <= 1) && mc_bits_permission) {
|
||||
instance->mc_shadow.data[i] &= data_block->data[i];
|
||||
} else if(
|
||||
(i >= FELICA_MC_ALL_BYTE && i <= FELICA_MC_CKCKV_W_MAC_A) &&
|
||||
(mc_system_block_permission)) {
|
||||
instance->mc_shadow.data[i] = data_block->data[i];
|
||||
} else if(
|
||||
(i >= FELICA_MC_SP_REG_R_RESTR_BYTES_6_7 && i <= FELICA_MC_STATE_W_MAC_A) &&
|
||||
(mc_bits_permission)) {
|
||||
instance->mc_shadow.data[i] |= data_block->data[i];
|
||||
} else if(i >= FELICA_MC_RESERVED_13) {
|
||||
instance->mc_shadow.data[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void felica_handler_write_state_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block) {
|
||||
felica_handler_write_block(instance, block_number, data_block);
|
||||
bool state = instance->data->data.fs.state.data[0] == 0x01;
|
||||
instance->auth.context.auth_status.external = state;
|
||||
instance->auth.context.auth_status.internal = state;
|
||||
}
|
||||
|
||||
static void felica_handler_write_id_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block) {
|
||||
UNUSED(block_number);
|
||||
memcpy(&instance->data->data.fs.id.data[8], &data_block->data[8], 8);
|
||||
}
|
||||
|
||||
FelicaCommandWriteBlockHandler
|
||||
felica_listener_get_write_block_handler(const uint8_t block_number) {
|
||||
FelicaCommandWriteBlockHandler handler = felica_handler_write_block;
|
||||
switch(block_number) {
|
||||
case FELICA_BLOCK_INDEX_RC:
|
||||
handler = felica_handler_write_rc_block;
|
||||
break;
|
||||
case FELICA_BLOCK_INDEX_REG:
|
||||
handler = felica_handler_write_reg_block;
|
||||
break;
|
||||
case FELICA_BLOCK_INDEX_MC:
|
||||
handler = felica_handler_write_mc_block;
|
||||
break;
|
||||
case FELICA_BLOCK_INDEX_STATE:
|
||||
handler = felica_handler_write_state_block;
|
||||
break;
|
||||
case FELICA_BLOCK_INDEX_ID:
|
||||
handler = felica_handler_write_id_block;
|
||||
break;
|
||||
default:
|
||||
handler = felica_handler_write_block;
|
||||
break;
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
static FelicaError felica_listener_process_error(NfcError error) {
|
||||
switch(error) {
|
||||
case NfcErrorNone:
|
||||
return FelicaErrorNone;
|
||||
case NfcErrorTimeout:
|
||||
return FelicaErrorTimeout;
|
||||
default:
|
||||
return FelicaErrorNotPresent;
|
||||
}
|
||||
}
|
||||
|
||||
FelicaError
|
||||
felica_listener_frame_exchange(const FelicaListener* instance, const BitBuffer* tx_buffer) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
|
||||
const size_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer);
|
||||
furi_assert(tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - FELICA_CRC_SIZE);
|
||||
|
||||
felica_crc_append(instance->tx_buffer);
|
||||
|
||||
FelicaError ret = FelicaErrorNone;
|
||||
|
||||
do {
|
||||
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = felica_listener_process_error(error);
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
@ -2,15 +2,72 @@
|
||||
|
||||
#include <nfc/protocols/nfc_generic_event.h>
|
||||
|
||||
#define FELICA_LISTENER_READ_BLOCK_COUNT_MAX (4U)
|
||||
#define FELICA_LISTENER_READ_BLOCK_COUNT_MIN (1U)
|
||||
#define FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX (2U)
|
||||
#define FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN (1U)
|
||||
|
||||
typedef enum {
|
||||
Felica_ListenerStateIdle,
|
||||
Felica_ListenerStateActivated,
|
||||
} FelicaListenerState;
|
||||
|
||||
/** Generic Felica request same for both read and write operations. */
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
FelicaCommandHeader header;
|
||||
} FelicaListenerGenericRequest;
|
||||
|
||||
/** Generic request but with list of requested elements. */
|
||||
typedef struct {
|
||||
FelicaListenerGenericRequest base;
|
||||
FelicaBlockListElement list[];
|
||||
} FelicaListenerRequest;
|
||||
|
||||
typedef FelicaListenerRequest FelicaListenerReadRequest;
|
||||
typedef FelicaListenerRequest FelicaListenerWriteRequest;
|
||||
|
||||
/** Struct for write request data.
|
||||
*
|
||||
* All data are placed right after @ref FelicaListenerRequest data.
|
||||
*/
|
||||
typedef struct {
|
||||
FelicaBlockData blocks[FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX];
|
||||
} FelicaListenerWriteBlockData;
|
||||
|
||||
/** Write command handler signature.
|
||||
*
|
||||
* Depending on requested block write behaviour can be different, therefore
|
||||
* different handlers are used.
|
||||
*/
|
||||
typedef void (*FelicaCommandWriteBlockHandler)(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block);
|
||||
|
||||
/** Read command handler signature.
|
||||
*
|
||||
* Depending on requested block read behaviour can be different, therefore
|
||||
* different handlers are used.
|
||||
*/
|
||||
typedef void (*FelicaCommanReadBlockHandler)(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const uint8_t resp_data_index,
|
||||
FelicaListenerReadCommandResponse* response);
|
||||
|
||||
struct FelicaListener {
|
||||
Nfc* nfc;
|
||||
FelicaData* data;
|
||||
FelicaListenerState state;
|
||||
FelicaAuthentication auth;
|
||||
FelicaBlockData mc_shadow;
|
||||
|
||||
uint8_t request_size_buf;
|
||||
uint8_t block_list_size;
|
||||
uint8_t requested_blocks[FELICA_LISTENER_READ_BLOCK_COUNT_MAX];
|
||||
uint8_t mac_calc_start;
|
||||
bool rc_written;
|
||||
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
@ -18,4 +75,170 @@ struct FelicaListener {
|
||||
NfcGenericEvent generic_event;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
};
|
||||
|
||||
/** Resets card authentication state after field off, also resets session key and
|
||||
* perform post process operations with some blocks.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
*/
|
||||
void felica_listener_reset(FelicaListener* instance);
|
||||
|
||||
/** Performs WCNT increasing in case of write operation.
|
||||
*
|
||||
* @param data pointer to Felica card data.
|
||||
*/
|
||||
void felica_wcnt_increment(FelicaData* data);
|
||||
|
||||
/** Compares IDm of card loaded for emulation with IDm from request.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param request_idm pointer to IDm block from request structure.
|
||||
*
|
||||
* @return True if IDms' are equal, otherwise false.
|
||||
*/
|
||||
bool felica_listener_check_idm(const FelicaListener* instance, const FelicaIDm* request_idm);
|
||||
|
||||
/** This is the first request validation function.
|
||||
*
|
||||
* Its main aim is to check whether the input request is valid in general by
|
||||
* counting the amount of data in request. Function iterates through block list
|
||||
* elements and data, counts real amount of elements and real amount of
|
||||
* coresponding elements data (in case of write operation). If block list
|
||||
* element amount is invalid or request data is missing, such request will be
|
||||
* ignored.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param request pointer to received request.
|
||||
*
|
||||
* @return True if block element list contains valid amount of data,
|
||||
* otherwise false.
|
||||
*/
|
||||
bool felica_listener_check_block_list_size(
|
||||
FelicaListener* instance,
|
||||
FelicaListenerGenericRequest* request);
|
||||
|
||||
/** Used to take first element from block element list.
|
||||
*
|
||||
* Each element in block list can be 2 or 3-bytes length and they can be mixed
|
||||
* together. Therefore before returning the element, function checks whether
|
||||
* there is enough bytes in the request for this element, in order to avoid
|
||||
* memory casting behind the request block.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param request pointer to received request.
|
||||
*
|
||||
* @return Pointer to the first element of the list or NULL if there is not
|
||||
* enough bytes in the request.
|
||||
*/
|
||||
const FelicaBlockListElement* felica_listener_block_list_item_get_first(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerRequest* request);
|
||||
|
||||
/** Used to take next element from block element list.
|
||||
*
|
||||
* It uses pointer to the previous element, takes its length and calculates
|
||||
* pointer to the next element if there is enough bytes left.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param prev_item pointer to the previous item taken.
|
||||
*
|
||||
* @return Pointer to the next element of the list or NULL if there is not
|
||||
* enough bytes in the request.
|
||||
*/
|
||||
const FelicaBlockListElement* felica_listener_block_list_item_get_next(
|
||||
FelicaListener* instance,
|
||||
const FelicaBlockListElement* prev_item);
|
||||
|
||||
/** Calculates pointer to data blocks in case of write operation, because block
|
||||
* list elements size can vary.
|
||||
*
|
||||
* For calculation it uses previously calculated block list size and
|
||||
* FelicaListenerGenericRequest size.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param generic_request pointer to received request.
|
||||
*
|
||||
* @return Pointer to data blocks for write operation.
|
||||
*/
|
||||
const FelicaListenerWriteBlockData* felica_listener_get_write_request_data_pointer(
|
||||
const FelicaListener* const instance,
|
||||
const FelicaListenerGenericRequest* const generic_request);
|
||||
|
||||
/** Function validates write request data and sets Felica SF1 and SF2 flags
|
||||
* directly to the response.
|
||||
*
|
||||
* When something is wrong with data in the request (for example non-existing
|
||||
* block is requested) this function sets proper SF1 and SF2 flags which will be
|
||||
* then send to reader. When every thing is fine SF1 and SF2 are 0. Also this
|
||||
* function performs authentiocation checks by MAC_A calculation if request
|
||||
* contains MAC_A.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param request pointer to received write request.
|
||||
* @param data pointer to data which request wants to write.
|
||||
* @param response pointer to the response to where SF1 and SF2 flags will
|
||||
* be populated.
|
||||
*
|
||||
* @return True if request is fine also SF1 and SF2 will be 0. False if
|
||||
* something is wrong and SF1, SF2 will provide more detailed
|
||||
* information about the error.
|
||||
*/
|
||||
bool felica_listener_validate_write_request_and_set_sf(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerWriteRequest* const request,
|
||||
const FelicaListenerWriteBlockData* const data,
|
||||
FelicaListenerWriteCommandResponse* response);
|
||||
|
||||
/** Function validates read request data and sets Felica SF1 and SF2 flags
|
||||
* directly to the response.
|
||||
*
|
||||
* When something is wrong with data in the request (for example non-existing
|
||||
* block is requested) this function sets proper SF1 and SF2 flags which will be
|
||||
* then send to reader. In such case there will be no any data in the request,
|
||||
* only flags. In case when request is fine SF1 and SF2 will be 0 and data will
|
||||
* be populated to the response after them. Attention! This function doesn't
|
||||
* populate any data, it only allows further processing where those data will be
|
||||
* populated.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param request pointer to received write request.
|
||||
* @param resp_header pointer to the response to where SF1 and SF2 and
|
||||
* data flags will be populated.
|
||||
*
|
||||
* @return True if request is fine also SF1 and SF2 will be 0. False if
|
||||
* something is wrong and SF1, SF2 will provide more detailed
|
||||
* information about the error.
|
||||
*/
|
||||
bool felica_listener_validate_read_request_and_set_sf(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerReadRequest* const request,
|
||||
FelicaCommandResponseHeader* resp_header);
|
||||
|
||||
/** Function returns appropiate block handler for processing read operation
|
||||
* depending on number of block.
|
||||
*
|
||||
* @param block_number number of block.
|
||||
*
|
||||
* @return pointer to block handler function.
|
||||
*/
|
||||
FelicaCommanReadBlockHandler felica_listener_get_read_block_handler(const uint8_t block_number);
|
||||
|
||||
/** Function returns appropiate block handler for processing write operation
|
||||
* depending on number of block.
|
||||
*
|
||||
* @param block_number number of block.
|
||||
*
|
||||
* @return pointer to block handler function.
|
||||
*/
|
||||
FelicaCommandWriteBlockHandler felica_listener_get_write_block_handler(const uint8_t block_number);
|
||||
|
||||
/** Sends response data from buffer to reader.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param tx_buffer buffer with response data.
|
||||
*
|
||||
* @return error code or FelicaErrorNone.
|
||||
*/
|
||||
FelicaError
|
||||
felica_listener_frame_exchange(const FelicaListener* instance, const BitBuffer* tx_buffer);
|
||||
|
@ -24,22 +24,6 @@ typedef enum {
|
||||
FelicaPollerEventTypeRequestAuthContext, /**< Authentication context was requested by poller. */
|
||||
} FelicaPollerEventType;
|
||||
|
||||
/**
|
||||
* @brief Stucture for holding Felica session key which is calculated from rc and ck.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||
} FelicaSessionKey;
|
||||
|
||||
/**
|
||||
* @brief Structure used to hold authentication related fields.
|
||||
*/
|
||||
typedef struct {
|
||||
mbedtls_des3_context des_context; /**< Context for mbedtls des functions. */
|
||||
FelicaSessionKey session_key; /**< Calculated session key. */
|
||||
FelicaAuthenticationContext context; /**< Public auth context provided to upper levels. */
|
||||
} FelicaAuthentication;
|
||||
|
||||
/**
|
||||
* @brief Felica poller event data.
|
||||
*/
|
||||
|
@ -3,11 +3,6 @@
|
||||
#include <nfc/helpers/felica_crc.h>
|
||||
|
||||
#define TAG "FelicaPoller"
|
||||
#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06U)
|
||||
#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08U)
|
||||
|
||||
#define FELICA_SERVICE_RW_ACCESS (0x0009U)
|
||||
#define FELICA_SERVICE_RO_ACCESS (0x000BU)
|
||||
|
||||
static FelicaError felica_poller_process_error(NfcError error) {
|
||||
switch(error) {
|
||||
|
@ -55,41 +55,6 @@ typedef struct {
|
||||
uint8_t request_data[2];
|
||||
} FelicaPollerPollingResponse;
|
||||
|
||||
typedef struct {
|
||||
uint8_t service_code : 4;
|
||||
uint8_t access_mode : 3;
|
||||
uint8_t length : 1;
|
||||
uint8_t block_number;
|
||||
} FelicaBlockListElement;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint8_t code;
|
||||
FelicaIDm idm;
|
||||
uint8_t service_num;
|
||||
uint16_t service_code;
|
||||
uint8_t block_count;
|
||||
} FelicaCommandHeader;
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t response_code;
|
||||
FelicaIDm idm;
|
||||
uint8_t SF1;
|
||||
uint8_t SF2;
|
||||
uint8_t block_count;
|
||||
uint8_t data[];
|
||||
} FelicaPollerReadCommandResponse;
|
||||
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t response_code;
|
||||
FelicaIDm idm;
|
||||
uint8_t SF1;
|
||||
uint8_t SF2;
|
||||
} FelicaPollerWriteCommandResponse;
|
||||
|
||||
const FelicaData* felica_poller_get_data(FelicaPoller* instance);
|
||||
|
||||
/**
|
||||
|
70
lib/nfc/protocols/felica/felica_poller_sync.c
Normal file
70
lib/nfc/protocols/felica/felica_poller_sync.c
Normal file
@ -0,0 +1,70 @@
|
||||
#include "felica_poller_sync.h"
|
||||
|
||||
#include "felica_poller_i.h"
|
||||
#include <nfc/nfc_poller.h>
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
#define FELICA_POLLER_FLAG_COMMAND_COMPLETE (1UL << 0)
|
||||
|
||||
typedef struct {
|
||||
FelicaAuthenticationContext auth_ctx;
|
||||
FuriThreadId thread_id;
|
||||
FelicaError error;
|
||||
FelicaData data;
|
||||
} Felica_PollerContext;
|
||||
|
||||
NfcCommand felica_poller_read_callback(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.instance);
|
||||
furi_assert(event.protocol == NfcProtocolFelica);
|
||||
|
||||
Felica_PollerContext* poller_context = context;
|
||||
FelicaPoller* felica_poller = event.instance;
|
||||
|
||||
FelicaPollerEvent* felica_event = event.event_data;
|
||||
|
||||
if(felica_event->type == FelicaPollerEventTypeReady ||
|
||||
felica_event->type == FelicaPollerEventTypeIncomplete) {
|
||||
felica_copy(&poller_context->data, felica_poller->data);
|
||||
} else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) {
|
||||
felica_event->data->auth_context->skip_auth = poller_context->auth_ctx.skip_auth;
|
||||
memcpy(
|
||||
felica_event->data->auth_context->card_key.data,
|
||||
poller_context->auth_ctx.card_key.data,
|
||||
FELICA_DATA_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
furi_thread_flags_set(poller_context->thread_id, FELICA_POLLER_FLAG_COMMAND_COMPLETE);
|
||||
|
||||
return NfcCommandStop;
|
||||
}
|
||||
|
||||
FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCardKey* card_key) {
|
||||
furi_check(nfc);
|
||||
furi_check(data);
|
||||
|
||||
Felica_PollerContext poller_context = {};
|
||||
if(card_key == NULL) {
|
||||
poller_context.auth_ctx.skip_auth = true;
|
||||
} else {
|
||||
poller_context.auth_ctx.skip_auth = false;
|
||||
memcpy(poller_context.auth_ctx.card_key.data, card_key->data, FELICA_DATA_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
poller_context.thread_id = furi_thread_get_current_id();
|
||||
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolFelica);
|
||||
nfc_poller_start(poller, felica_poller_read_callback, &poller_context);
|
||||
furi_thread_flags_wait(FELICA_POLLER_FLAG_COMMAND_COMPLETE, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_thread_flags_clear(FELICA_POLLER_FLAG_COMMAND_COMPLETE);
|
||||
|
||||
nfc_poller_stop(poller);
|
||||
nfc_poller_free(poller);
|
||||
|
||||
if(poller_context.error == FelicaErrorNone) {
|
||||
*data = poller_context.data;
|
||||
}
|
||||
|
||||
return poller_context.error;
|
||||
}
|
14
lib/nfc/protocols/felica/felica_poller_sync.h
Normal file
14
lib/nfc/protocols/felica/felica_poller_sync.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "felica.h"
|
||||
#include <nfc/nfc.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCardKey* card_key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,64.1,,
|
||||
Version,+,64.2,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||
Header,+,applications/services/cli/cli.h,,
|
||||
|
|
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,64.1,,
|
||||
Version,+,64.2,,
|
||||
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||
@ -136,7 +136,9 @@ Header,+,lib/nfc/nfc_listener.h,,
|
||||
Header,+,lib/nfc/nfc_poller.h,,
|
||||
Header,+,lib/nfc/nfc_scanner.h,,
|
||||
Header,+,lib/nfc/protocols/felica/felica.h,,
|
||||
Header,+,lib/nfc/protocols/felica/felica_listener.h,,
|
||||
Header,+,lib/nfc/protocols/felica/felica_poller.h,,
|
||||
Header,+,lib/nfc/protocols/felica/felica_poller_sync.h,,
|
||||
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,,
|
||||
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,,
|
||||
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,,
|
||||
@ -1005,6 +1007,7 @@ Function,-,fdimf,float,"float, float"
|
||||
Function,-,fdiml,long double,"long double, long double"
|
||||
Function,-,fdopen,FILE*,"int, const char*"
|
||||
Function,+,felica_alloc,FelicaData*,
|
||||
Function,+,felica_calculate_mac_read,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, const uint8_t*, uint8_t*"
|
||||
Function,+,felica_calculate_mac_write,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*"
|
||||
Function,+,felica_calculate_session_key,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, uint8_t*"
|
||||
Function,+,felica_check_mac,_Bool,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*"
|
||||
@ -1016,6 +1019,7 @@ Function,+,felica_get_uid,const uint8_t*,"const FelicaData*, size_t*"
|
||||
Function,+,felica_is_equal,_Bool,"const FelicaData*, const FelicaData*"
|
||||
Function,+,felica_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t"
|
||||
Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*"
|
||||
Function,+,felica_poller_sync_read,FelicaError,"Nfc*, FelicaData*, const FelicaCardKey*"
|
||||
Function,+,felica_reset,void,FelicaData*
|
||||
Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*"
|
||||
Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t"
|
||||
|
|
@ -77,6 +77,9 @@ FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms) {
|
||||
if(irq & ST25R3916_IRQ_MASK_WU_A_X) {
|
||||
event |= FuriHalNfcEventListenerActive;
|
||||
}
|
||||
if(irq & ST25R3916_IRQ_MASK_WU_F) {
|
||||
event |= FuriHalNfcEventListenerActive;
|
||||
}
|
||||
}
|
||||
if(event_flag & FuriHalNfcEventInternalTypeTimerFwtExpired) {
|
||||
event |= FuriHalNfcEventTimerFwtExpired;
|
||||
|
@ -55,6 +55,7 @@ static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(FuriHalSpiBusHandle* ha
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* handle) {
|
||||
furi_assert(handle);
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_SET_DEFAULT);
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_OP_CONTROL,
|
||||
@ -89,8 +90,8 @@ static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* ha
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00);
|
||||
// No gain reduction on AM and PM channels
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00);
|
||||
// 10% ASK modulation
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_am_mod_10percent);
|
||||
// 40% ASK modulation
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_am_mod_40percent);
|
||||
|
||||
// Correlator setup
|
||||
st25r3916_write_reg(
|
||||
@ -142,9 +143,7 @@ FuriHalNfcError furi_hal_nfc_felica_listener_tx(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
const uint8_t* tx_data,
|
||||
size_t tx_bits) {
|
||||
UNUSED(handle);
|
||||
UNUSED(tx_data);
|
||||
UNUSED(tx_bits);
|
||||
furi_hal_nfc_common_fifo_tx(handle, tx_data, tx_bits);
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user